plan.md:按用户口径重写 —— 粗/细渲染分工、抽象模型(参考 mineflayer)、机位降为用例
This commit is contained in:
263
plan.md
263
plan.md
@ -28,6 +28,11 @@
|
||||
|
||||
## 1. 用户设想的场景(2026-09-26,本计划的新方向)
|
||||
|
||||
★ **这是抽象模型的一个用例,不是全部**(用户 2026-09-26:
|
||||
「机位这个只是一个具体应用场景,而不是只有这一个场景」)。
|
||||
它用来验证 §1b 那套「状态 + 事件 + 操作」是否立得住 ——
|
||||
若拍照能跑通,agent 的日常感知/交互也就能跑通。
|
||||
|
||||
> 我设想的场景是玩家可以在聊天框给 agent 发消息,然后比如让 agent 给他**拍照**,
|
||||
> 如果是他和某个景物的**合照**,agent 可以先通过 **ass 粗渲染**,找好角度
|
||||
> 再去**渲染实图**。
|
||||
@ -38,48 +43,127 @@
|
||||
① 玩家在聊天框发消息 → agent 通过统一接口收到
|
||||
② agent 要"给玩家拍合照" → 需要:玩家位置/朝向 + 景物位置 + 相机可摆放范围
|
||||
③ 选角度 → 用 **ass 粗渲染**(低成本、快)试若干机位,
|
||||
挑一个好的,再用**真渲染**出图
|
||||
挑一个好的,再用**细渲染**出图
|
||||
```
|
||||
|
||||
### ★ ass 粗渲染是什么(这里是关键设计点)
|
||||
★ 注意:这只是粗渲染的**一个用例**。它的本职是 **agent 的常驻感知层**
|
||||
(日常游玩),见 §4。
|
||||
|
||||
「ass」= **ASCII / 低保真近似**(用户 2026-09-26 提出)。
|
||||
它要能回答「这个角度拍出来大概是什么样」,但**不启动 MC**:
|
||||
### ★ 粗渲染与细渲染的分工(用户 2026-09-26 明确,关键)
|
||||
|
||||
- **不是**把真渲染结果降采样 —— 那还是要跑 MC,没省。
|
||||
- **是**用一个**纯数据的近似模型**:从方块数据直接推
|
||||
「视线方向上有哪些方块、遮挡关系、大致明暗」,
|
||||
在文本/低分辨率网格上画出来。
|
||||
> **粗渲染是 agent 日常游玩用的**;**细渲染是出图和 agent 判断精细状态用的**;
|
||||
> **细渲染是单次调用的而非常驻的**。
|
||||
|
||||
**为什么它值得做**(三个具体收益):
|
||||
| | 粗渲染(ass) | 细渲染 |
|
||||
|---|---|---|
|
||||
| 用途 | **agent 日常游玩** | **出图 + 判断精细状态** |
|
||||
| 生命周期 | **常驻**(随 agent 会话) | **单次调用**(起 → 渲染 → 销毁) |
|
||||
| 频率 | 高(agent 每一步都可能问) | 低(要图 / 需要精细判断时) |
|
||||
| 延迟预算 | 毫秒级 | 20-40s 可接受 |
|
||||
| 依赖 | 纯 Python(**无 JVM**) | JVM + 完整 MC 栈 + 加载器/光影 |
|
||||
| 数据源 | 轻量世界快照 | 完整渲染管线 |
|
||||
|
||||
1. **试错便宜**:一次真渲染要起 JVM(~20-40s),粗渲染是毫秒级
|
||||
⇒ agent 可以试几十个机位,只把最好的那一个送进真渲染。
|
||||
2. **给 agent 可读的中间结果**:ASCII 图能被 LLM 直接「看」和理解,
|
||||
而 PNG 需要多模态输入。agent 可以在文本空间里推理"该往哪挪"。
|
||||
3. **失败模式友好**:机位选错时,粗渲染已经暴露(比如"镜头埋在地里"、
|
||||
"景物被树挡住"),不用浪费一次完整渲染。
|
||||
★ **这条分工决定了架构**:
|
||||
- 粗渲染**不是**细渲染的降级,而是 agent 的**常驻感知层** ——
|
||||
它要回答 agent 平时的所有感知问题(周围有什么、挡不挡视线、
|
||||
前面走得通吗、那边是什么),这些都必须廉价。
|
||||
- 细渲染**按需起、用完走**(JVM 启停成本不敏感)⇒ 慢一点没关系。
|
||||
- ⇒ 所以**「粗渲染↔细渲染一致性」不是刚需**:两者服务于不同问题,
|
||||
不要求粗渲染预测细渲染的像素。真正要测的是
|
||||
**各自对各自用途的正确性**(见 4.5)。
|
||||
|
||||
### mineflayer 的参考价值(用户 2026-09-26 点名)
|
||||
---
|
||||
|
||||
> 记得参考 mineflayer 的实现,我觉得它对于**行为触发条件**与**展示方式**
|
||||
> 以及 **ass 粗略绘图**的形式很优秀。
|
||||
## 1b. 抽象模型:参考 mineflayer 的「状态 + 事件 + 操作」三层
|
||||
|
||||
mineflayer 相关能力(本机已有:`/home/newqqagent/mcbridge/node/node_modules/mineflayer`):
|
||||
> 至于事件更新通报与操作抽象,我觉得可以参考 mineflayer,
|
||||
> 使用它的抽象模型调用我们的后端。(用户 2026-09-26)
|
||||
|
||||
| mineflayer 的做法 | 对我们的启发 |
|
||||
|---|---|
|
||||
| **事件驱动**:`bot.on('chat')` / `'entitySpawn'` / `'physicsTick'` | 行为触发条件:聊天消息 → 意图识别 → 动作(拍照)|
|
||||
| **`bot.chat()` / `bot.entity.position`** | agent 拿到玩家位置/朝向;回复走聊天 |
|
||||
| **`prismarine-viewer`**:三种视角(first_person / third_person / fixed)| ★ 相机摆放的**现成语义**:合照 = third_person + 朝向景物 |
|
||||
| **`.saveScreenshot()`** | 截图接口的形态 |
|
||||
| **blockAt / findBlock / raycast** | ★ **粗渲染的核心原语**:从方块数据做视线求交,算遮挡 |
|
||||
| **minecraft-data**:权威方块/物品数据 | 服务端交互的注册表来源之一 |
|
||||
★ **注意**:机位/拍照只是这个抽象的**一个用例**,不是目的。
|
||||
下面这套模型要覆盖 agent 的日常:感知、移动、交互、观察、问答。
|
||||
|
||||
**⚠️ 但要注意 mineflayer 与我们的根本差别**:
|
||||
mineflayer 是**真连服务端**、有完整世界状态;
|
||||
我们是**离屏渲染 + 自喂数据**。所以它的**行为触发条件**能直接借鉴,
|
||||
而**世界访问**要走我们自己的数据通路(存档 / 服务端注入)。
|
||||
### 1b.1 mineflayer 的实际形态(已读源码核实)
|
||||
|
||||
```
|
||||
① 状态(挂在 bot 上的对象,agent 直接读)
|
||||
world 方块/生物群系/光照 entities {id: Entity}
|
||||
players {username: Player} time / game / controlState
|
||||
inventory / experience / tablist / teamMap / spawnPoint / settings
|
||||
|
||||
② 事件(bot.emit(name, ...),细分层用 ':')
|
||||
消息类 message / messagestr / chat / chat:<pattern> / actionBar
|
||||
世界类 blockUpdate / chunkColumnLoad / weatherUpdate
|
||||
实体类 entitySpawn / entityMoved / playerJoined / playerLeft
|
||||
玩家类 moved / heldItemChanged / lookingAt / physicsTick
|
||||
流程类 login / spawn / health / death / kicked / error
|
||||
|
||||
③ 操作(bot.<verb>(...))
|
||||
查询 blockAt / findBlocks / raycast / nearestEntity
|
||||
移动 move / look / setControlState / simpleClick
|
||||
交互 dig / placeBlock / activateBlock / attackEntity / equip
|
||||
社交 chat / whisper / tell
|
||||
观察 saveScreenshot
|
||||
容器 openContainer / clickWindow / closeWindow / …
|
||||
```
|
||||
|
||||
**★ 三个值得直接照搬的设计**:
|
||||
|
||||
1. **状态是「对象」而不是「方法调用」** —— agent 读 `bot.entities` /
|
||||
`bot.world` 就拿到快照,不必每次查询。
|
||||
⇒ 我们的粗渲染(常驻)就该是这个角色。
|
||||
|
||||
2. **事件分「粗粒度 + 细分层」**(`message` 与 `chat:<pattern>` 并存)
|
||||
—— agent 可以只订阅粗粒度(省 token),需要时再挂细分。
|
||||
⇒ 避免「每个方块变化都推给 agent」把上下文淹掉。
|
||||
|
||||
3. **操作是动词,参数是结构化对象**
|
||||
—— `bot.findBlocks({ matching, maxDistance, count })`
|
||||
⇒ 不是「一条命令 + 位置参数」,而是「意图 + 可选约束」。
|
||||
|
||||
### 1b.2 映射到我们的后端
|
||||
|
||||
```
|
||||
mineflayer 概念 我们的实现 备注
|
||||
─────────────────────────────────────────────────────────────────
|
||||
bot.world mcfold 的 World(已有 block_at) ★ 粗渲染的底座
|
||||
bot.entities 待加:EntityStore 从 chunk/实体数据来
|
||||
bot.on(...) 事件总线(待加) 粗/细分层同 mineflayer
|
||||
bot.findBlocks({...}) rough.find_blocks(matching=…, …) 纯 Python,毫秒级
|
||||
bot.raycast rough.cast_ray(…) DDA
|
||||
bot.move / bot.look 待定:见 1b.4(我们不做移动)
|
||||
bot.chat 走 agentmail / 服务端聊天 已有通路
|
||||
bot.saveScreenshot WorldRenderer.screenshot() 细渲染(单次调用)
|
||||
```
|
||||
|
||||
★ **最重要的一条**:`bot` 那些方法最终落到 `bot._client`(协议层),
|
||||
而我们落到 **jar 端**(用户定调:「jar 端接受操作意图,输出包」)。
|
||||
所以**抽象形状照抄 mineflayer,落地换到 jar 端** —— 这正是用户那句话的含义。
|
||||
|
||||
### 1b.3 我们的三层(与 mineflayer 同形)
|
||||
|
||||
```
|
||||
状态层 常驻、无 JVM、毫秒级
|
||||
World / EntityStore / 玩家与相机状态 / 时间天气
|
||||
★ 这层就是「粗渲染」
|
||||
|
||||
操作层 意图式动词
|
||||
find_blocks / cast_ray / look / say / place / dig …
|
||||
多数落粗渲染(快);需精细视觉的转细渲染(慢,单次)
|
||||
|
||||
事件层 粗粒度 + 细分层
|
||||
moved / block_changed / entity_appeared / message / chat:<pattern>
|
||||
★ 分层是为省 token,不是为性能
|
||||
```
|
||||
|
||||
### 1b.4 一个必须先定的边界
|
||||
|
||||
⚠️ 我们**不做移动/战斗**(用户:范围是「渲染 + 与服务端 mod 物品交互」)。
|
||||
所以 mineflayer 的 `bot.move` / `dig` / `attackEntity` **不照抄** ——
|
||||
它们要么不需要,要么该由「服务端交互」那条线以别的方式提供。
|
||||
|
||||
⇒ 待定:`bot.move` 这类「改变世界状态」的操作,在我们的定位里
|
||||
究竟**不做**、还是**转交服务端**(agent 在真实世界里动,我们在旁边看/记)。
|
||||
这决定 `操作层` 的边界。
|
||||
|
||||
---
|
||||
|
||||
@ -224,9 +308,9 @@ protodef 描述**不随版本变** ⇒ `ProtocolVersion` 只影响少数结构
|
||||
已有 `WorldRenderer` / `Instance` / `create_instance`,缺一个把
|
||||
「渲染」+「服务端交互」串起来的门面。
|
||||
|
||||
### ⬜ G4. ass 粗渲染(§1,新方向)
|
||||
### ⬜ G4. 粗渲染(ass):agent 的常驻感知层(§1、§4)
|
||||
|
||||
见 §4 设计。
|
||||
见 §4 设计(**常驻**,与细渲染的**单次调用**互补)。
|
||||
|
||||
### ⬜ G5. 聊天框 → agent → 拍照 的行为链(§1,新方向)
|
||||
|
||||
@ -234,57 +318,86 @@ protodef 描述**不随版本变** ⇒ `ProtocolVersion` 只影响少数结构
|
||||
|
||||
---
|
||||
|
||||
## 4. ass 粗渲染设计(⬜ 未开始)
|
||||
## 4. 粗渲染(ass)设计(⬜ 未开始)
|
||||
|
||||
### 4.1 定位
|
||||
### 4.1 定位:agent 的**常驻感知层**
|
||||
|
||||
按 §1 的分工,粗渲染服务于**日常游玩**,所以它不是一个"拍照辅助",
|
||||
而是一个**常驻的、无 JVM 的世界感知接口**。
|
||||
|
||||
```
|
||||
真渲染: JVM + 完整 MC 渲染栈 + (可选)Iris 光影 ~20-40s ~1MB PNG
|
||||
粗渲染: 纯 Python,从方块数据做视线近似 <100ms 文本网格
|
||||
细渲染: JVM + 完整 MC 栈 + Iris 单次调用(起→渲→销) ~20-40s
|
||||
粗渲染: 纯 Python + 世界快照 常驻 <100ms
|
||||
```
|
||||
|
||||
**两者不是精度不同,是用途不同**:
|
||||
- 粗渲染用来**搜索机位**(试几十个)
|
||||
- 真渲染用来**出最终图**(只跑选中的那个)
|
||||
### 4.2 它要回答的问题(agent 游玩时会问的)
|
||||
|
||||
### 4.2 核心原语(借鉴 mineflayer / prismarine 的做法)
|
||||
| 问法 | 需要的能力 |
|
||||
|---|---|
|
||||
| 「我面前是什么?」 | 视线求交(DDA)|
|
||||
| 「这堵墙后面有没有通路?」 | 射线 + 邻域查询 |
|
||||
| 「我站的地方是空的吗?」 | `block_at`(已有)|
|
||||
| 「那边那座山是什么?」 | 远距离扫描 / 俯瞰索引 |
|
||||
| 「周围一圈都有什么?」 | 局部切面(ASCII 最有用的场景)|
|
||||
| 「从这里能不能看到它?」 | 可见性测试(遮挡判定)|
|
||||
|
||||
**其中「ASCII 网格」只是其中一种展示形态** —— 它的价值在于
|
||||
让 LLM 能直接读,而 PNG 需要多模态。
|
||||
|
||||
### 4.3 核心原语(借鉴 mineflayer / prismarine 的做法)
|
||||
|
||||
mineflayer 的 `raycast` / `blockAt` 是纯数据的视线求交 —— 我们不连服务端,
|
||||
但**同样的计算**可以跑在存档数据上:
|
||||
但**同样的计算**可以跑在存档/注入的世界快照上。
|
||||
|
||||
```python
|
||||
# 输入:世界的方块数据(已能拿到,见 render.py 的 SaveChunk)
|
||||
# 输出:ASCII 网格 + 结构化线索
|
||||
# 基础已有(mcfold/world.py):
|
||||
# World.block_at(x,y,z) -> BlockHit(name, state_id, loaded, source)
|
||||
# ★ 它已能区分 unloaded / unknown-state ⇒ mod 方块不会被当成空气
|
||||
|
||||
for 每条视线(从相机出发,过每个网格像素):
|
||||
沿射线步进(DDA,与 MC 的方块网格对齐)
|
||||
记录第一个命中的非空气方块
|
||||
→ 得到 depth / block / 明暗(按法线与光方向)
|
||||
# 待加(mcfold/rough.py):
|
||||
def cast_ray(world, origin, direction, max_dist) -> RayHit
|
||||
"""沿方向步进(DDA,与 MC 方块网格对齐),返回首个非空气方块"""
|
||||
|
||||
def visibility(world, eye, target) -> Visibility
|
||||
"""两点之间是否通视(被几个方块挡住)"""
|
||||
|
||||
def slice_view(world, eye, yaw, pitch, w, h) -> AsciiGrid
|
||||
"""把视野切成 w×h 的 ASCII 网格(agent 直接读)"""
|
||||
|
||||
def scene_summary(world, center, radius) -> dict
|
||||
"""结构化线索:方块分布 / 开阔度 / 遮挡率(LLM 读数字比读 ASCII 准)"""
|
||||
```
|
||||
|
||||
### 4.3 输出形态(三件,服务于"让 agent 能推理")
|
||||
### 4.4 「ass」的三种展示形态(服务于不同用途)
|
||||
|
||||
```
|
||||
① ASCII 网格 相机看到的大致形状("镜头埋地里"一眼可见)
|
||||
② 结构化线索 视野内方块分布 / 主景物的屏幕占比 / 遮挡率
|
||||
③ 建议 "往左挪 3 格可让景物居中"(可选,规则驱动)
|
||||
① ASCII 网格 视野切面 —— 人看 + LLM 直接读
|
||||
② 结构化线索 开阔度/遮挡率/最近景物/可通行方向 —— LLM 推理用(比①准)
|
||||
③ 可选:俯视 ASCII 局部地图(w×h 格,每格一个地标字符)—— 定位用
|
||||
```
|
||||
|
||||
**②比①更重要** —— LLM 读数字比读 ASCII 准;
|
||||
ASCII 主要是给人看和做快速否决。
|
||||
### 4.5 验证口径(★ 改成"各自对各自用途",不是互相预测)
|
||||
|
||||
### 4.4 与真渲染的一致性(关键风险)
|
||||
```
|
||||
粗渲染的判据(不需要细渲染参与):
|
||||
· block_at 已区分 unloaded / unknown-state ⇒ 不能把未加载当空气
|
||||
· cast_ray 的结果与人工推算一致(可构造的简单场景:单方块、空走廊、斜坡)
|
||||
· 局部切面的 ASCII 与直接读方块数据一致
|
||||
|
||||
⚠️ 粗渲染的结论必须**能预测**真渲染。要测:
|
||||
- 同一机位,粗渲染说"看得见主景物" ⇒ 真渲染确实看得见
|
||||
- 建立**一致性判据**(不是"看起来差不多",而是可判的:
|
||||
「主景物的屏幕包围盒重叠率 > 阈值」)
|
||||
细渲染的判据(不需要粗渲染参与):
|
||||
· 现有:注入地形 → distinctColors / chunksRendered 达阈
|
||||
· 现有:Sodium tracker 通知后 chunkReady > 0
|
||||
|
||||
### 4.5 实现落点
|
||||
★ **不再要求**「粗渲染能预测细渲染」—— 两者服务不同问题
|
||||
(用户口径:粗=日常游玩,细=出图与精细判断)
|
||||
唯一的交集是「拍照选机位」这一个用例,那里是**用法**上的配合,
|
||||
不是**正确性**上的依赖。
|
||||
```
|
||||
|
||||
`mcfold/rough.py`(新模块)+ `tests/test_rough.py`
|
||||
- 输入:`WorldRenderer` 的方块数据(或直接读存档)
|
||||
- 不依赖 JVM ⇒ 可在默认测试套件里跑(秒级)
|
||||
### 4.6 实现落点
|
||||
|
||||
`mcfold/rough.py`(新模块,纯 Python、**不依赖 JVM**)
|
||||
`tests/test_rough.py`(默认套件里跑,秒级)
|
||||
|
||||
---
|
||||
|
||||
@ -301,9 +414,9 @@ ASCII 主要是给人看和做快速否决。
|
||||
↓
|
||||
③ 需要什么 玩家位置/朝向 + 景物位置 + 可用机位
|
||||
↓
|
||||
④ 规划 粗渲染试 N 个机位 → 挑最好的
|
||||
④ 规划 粗渲染(常驻)试 N 个机位 → 挑最好的
|
||||
↓
|
||||
⑤ 出图 真渲染(Iris + 光影)
|
||||
⑤ 出图 细渲染(单次调用:起 JVM → Iris+光影 → 销毁)
|
||||
↓
|
||||
⑥ 送达 回聊天 / 发邮件(走现有 agentmail 通路)
|
||||
```
|
||||
@ -332,7 +445,7 @@ shot = capture_photo(
|
||||
subject_scale=20, # 景物半径
|
||||
with_player=True, # 是否要合照
|
||||
player_pos=(x, y, z), # 合照时的"玩家"位置
|
||||
samples=20, # 粗渲染试几个机位
|
||||
samples=20, # 粗渲染试几个机位(常驻,毫秒级)
|
||||
)
|
||||
shot.png → /path/out.png
|
||||
shot.rough → ASCII + 线索(给 agent 看它为什么选了这个角度)
|
||||
@ -345,14 +458,20 @@ shot.rough → ASCII + 线索(给 agent 看它为什么选了这个角度)
|
||||
| # | 内容 | 依赖 | 状态 |
|
||||
|---|---|---|---|
|
||||
| M1 | 修 G1(build.sh 编译 CP 加 Fabric 库),1.21.1 构建跑通 | — | 🔄 |
|
||||
| M2 | `rough.py` 粗渲染(纯 Python,DDA 视线) | — | ⬜ |
|
||||
| M3 | 粗渲染 ↔ 真渲染一致性判据 | M2 | ⬜ |
|
||||
| M4 | `capture_photo()` 机位搜索 + 统一接口门面 | M2, M3 | ⬜ |
|
||||
| M2 | **抽象模型骨架**:状态/事件/操作三层(照 mineflayer 形状) | — | ⬜ |
|
||||
| M2a | └ `rough.py` 常驻粗渲染(World 视图 + DDA 视线 + ASCII/线索) | M2 | ⬜ |
|
||||
| M2b | └ 事件总线(粗粒度 + 细分层) | M2 | ⬜ |
|
||||
| M3 | 粗渲染自身判据(构造场景;**不要求预测细渲染**) | M2a | ⬜ |
|
||||
| M4 | `capture_photo()`(**作为第一个用例**:粗搜机位 → 细出图) | M2a, M2b, M3 | ⬜ |
|
||||
| M5 | 服务端交互(configuration / registry_data / 物品表) | M1 | ⬜ |
|
||||
| M6 | 聊天框 → agent → 拍照 全链 | M1, M4, M5 | ⬜ |
|
||||
| M6 | 聊天框 → agent → 拍照 全链(§5) | M1, M4, M5 | ⬜ |
|
||||
|
||||
**建议先做 M1 + M2**:M1 解阻断,M2 是用户新设想里最有独立价值的一块
|
||||
(纯 Python、不依赖 JVM、可秒级验证)。
|
||||
**建议先做 M1 + M2**:M1 解阻断;M2 是**主线**(抽象模型骨架),
|
||||
拍照只是它的第一个用例。M2 做完后 agent 就有常驻感知能力了,
|
||||
而这正是"日常游玩"的地基。
|
||||
★ M3 特意**不叫**"一致性判据":按用户口径粗/细服务于不同问题,
|
||||
不要求粗渲染预测细渲染的像素。M3 只验粗渲染**自己**对日常感知问题的正确性
|
||||
(构造简单场景,人工可推算)。
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user