29 KiB
mcfold 工程计划
最后更新:2026-09-26 状态标记:✅ 已完成 · 🔄 进行中 · ⬜ 未开始 · ⚠️ 有已知缺口
0. 目的(最顶层,2026-09-26 用户定调)
因为我们的这个插件目的就是为了让 agent 可以借助我们提供的工具包去玩我的世界。
★ 这是最顶层的目的,下面所有东西都是为它服务的手段。
我们提供:一副「能在 MC 世界里感知 + 行动」的**工具包**
agent 提供:决策(该干什么、去哪、为什么)—— 我们**不替它决定**
推论(三条,改变工程重心):
- 工具形状要照 LLM 的用法设计,不是照程序员的用法。
mineflayer 是
create minecraft bots with a stable, high level API—— 它是给写 bot 的人用的库;我们面向的是 LLM 直接调用。 差别很实:参数要自解释、返回要可读、错误要说清"为什么不行", 而不是抛个异常让调用者去读栈。 - 「能感知 + 能行动」两者缺一不可。 只有感知(渲染)⇒ agent 是个观众; 只有行动(操作)⇒ agent 是瞎子。 所以 §1b 的「状态 / 事件 / 操作」三层是工具包的正式骨架, 不是"以后再补"。
- 渲染是工具之一,不是全部。
细渲染(出图)是工具;粗渲染(常驻感知)是工具;
move/dig/place/chat也是工具。 之前把它当主线,是重心错了。
0b. 目标(用户定向,按时间顺序)
- 照相机式渲染:给一个世界(存档数据 / 服务端),渲染出图。
- 用户原话(2026-09-25):「不起完整客户端」——抽出 MC 的渲染能力, 喂我们自己的数据。
- 用户纠正:「不写 mod,而不是不用 mod 加载器」⇒ 用加载器( 让真实 mod / Iris 光影能工作),但不写自己的 mod。
- 架构分层(用户 2026-09-26 明确定调):
jar 部分封装为统一的接口,非通用数据通过 extern 参数传入。 jar 端接受操作意图,输出包,python 再将包发送到服务端。 python 相当于只是包装,让 agent 可以通过统一的接口调用。 同时不要忘了版本隔离。同一个版本应当可以存在多个实例(因为要装不同的 mod)。
- 范围(用户 2026-09-26 明确限定):
我们只是为了渲染以及与服务端的 mod 物品交互。 ⇒ 不做完整客户端、不做玩法、不做移动/战斗。
- HUD 不显示(用户 2026-09-26):截图只关心画面,HUD 是噪声,
而且它给
distinctColors掺固定像素(污染判据)。 ⇒ 走官方选项options.hideGui(= 游戏里 F1)。
1. 用户设想的场景(2026-09-26,本计划的新方向)
★ 这是抽象模型的一个用例,不是全部(用户 2026-09-26: 「机位这个只是一个具体应用场景,而不是只有这一个场景」)。 它用来验证 §1b 那套「状态 + 事件 + 操作」是否立得住 —— 若拍照能跑通,agent 的日常感知/交互也就能跑通。
我设想的场景是玩家可以在聊天框给 agent 发消息,然后比如让 agent 给他拍照, 如果是他和某个景物的合照,agent 可以先通过 ass 粗渲染,找好角度 再去渲染实图。
拆成可执行的三段:
① 玩家在聊天框发消息 → agent 通过统一接口收到
② agent 要"给玩家拍合照" → 需要:玩家位置/朝向 + 景物位置 + 相机可摆放范围
③ 选角度 → 用 **ass 粗渲染**(低成本、快)试若干机位,
挑一个好的,再用**细渲染**出图
★ 注意:这只是粗渲染的一个用例。它的本职是 agent 的常驻感知层 (日常游玩),见 §4。
★ 粗渲染与细渲染的分工(用户 2026-09-26 明确,关键)
粗渲染是 agent 日常游玩用的;细渲染是出图和 agent 判断精细状态用的; 细渲染是单次调用的而非常驻的。
| 粗渲染(ass) | 细渲染 | |
|---|---|---|
| 用途 | agent 日常游玩 | 出图 + 判断精细状态 |
| 生命周期 | 常驻(随 agent 会话) | 单次调用(起 → 渲染 → 销毁) |
| 频率 | 高(agent 每一步都可能问) | 低(要图 / 需要精细判断时) |
| 延迟预算 | 毫秒级 | 20-40s 可接受 |
| 依赖 | 纯 Python(无 JVM) | JVM + 完整 MC 栈 + 加载器/光影 |
| 数据源 | 轻量世界快照 | 完整渲染管线 |
★ 这条分工决定了架构:
- 粗渲染不是细渲染的降级,而是 agent 的常驻感知层 —— 它要回答 agent 平时的所有感知问题(周围有什么、挡不挡视线、 前面走得通吗、那边是什么),这些都必须廉价。
- 细渲染按需起、用完走(JVM 启停成本不敏感)⇒ 慢一点没关系。
- ⇒ 所以**「粗渲染↔细渲染一致性」不是刚需**:两者服务于不同问题, 不要求粗渲染预测细渲染的像素。真正要测的是 各自对各自用途的正确性(见 4.5)。
1b. 抽象模型:参考 mineflayer 的「状态 + 事件 + 操作」三层
至于事件更新通报与操作抽象,我觉得可以参考 mineflayer, 使用它的抽象模型调用我们的后端。(用户 2026-09-26)
★ 注意:机位/拍照只是这个抽象的一个用例,不是目的。 下面这套模型要覆盖 agent 的日常:感知、移动、交互、观察、问答。
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 / …
★ 三个值得直接照搬的设计:
-
状态是「对象」而不是「方法调用」 —— agent 读
bot.entities/bot.world就拿到快照,不必每次查询。 ⇒ 我们的粗渲染(常驻)就该是这个角色。 -
事件分「粗粒度 + 细分层」(
message与chat:<pattern>并存) —— agent 可以只订阅粗粒度(省 token),需要时再挂细分。 ⇒ 避免「每个方块变化都推给 agent」把上下文淹掉。 -
操作是动词,参数是结构化对象 ——
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 在真实世界里动,我们在旁边看/记)。
这决定 操作层 的边界。
1c. 空白测试服(用户 2026-09-26 定向)
我觉得可以起一个空白新服用来测试功能,而不是用现在的服。
理由(不只是"干净"):
- 现有服
/home/minecraft:world 15G、mods 64 个、neoforge 1.21.1 —— 任何协议层改动都可能污染它,且测不出边界(模组太多、状态太杂)。 - 我们要验的是协议与行为,需要可预期的环境:已知方块集、已知出生点、 可重置的世界。
形态(待定,需你定):
| 项 | 选项 |
|---|---|
| 版本 | vanilla 1.21.1(最简,协议最干净) / neoforge 1.21.1(与现有一致) |
| 模组 | 先 0 个(验协议)→ 再逐个加(验 mod 差异) |
| 端口 | 25565(避开现役) / 25566+ |
| 目录 | 与 /home/minecraft 同级(版本隔离:内容根 / 实例 / 运行期) |
| 世界 | 全新生成,固定种子(可预期) |
★ 磁盘:/home 尚有 136G ⇒ 放得下(现役 world 已 15G,空白服初期只占几十 M)。
它同时是「版本隔离」策略的第二个实例:
store/vanilla-1.21.1/server/ + runtime/test-server/ —— 与我们给
agent 建的实例同一套口径。
✅ M0 已完成(2026-09-26,提交 480fe8e)
形态 vanilla 1.21.1 / 0 mods / 超平坦 / 固定种子 mcfold-test-2026
creative · peaceful · 无怪物 · 无结构 · spawn-protection=0
位置 store/vanilla-1.21.1/server/ (内容根)
runtime/test-server/ (运行期:log + pid)
端口 25566(避开现役 25565)
接口 python3 -m mcfold.server {start,stop,status,reset}
探活 协议级 Server List Ping ⇒ 1.21.1 / protocol 767 / players 0/4
判据 tests/test_server.py(4 项,默认套件)+ 变异验证过(能红)
★ 两个踩过的坑(已写进判据守护):
- properties 行内注释会被解析进值里(
k=v # c)⇒ 生成器不写行内注释。 - 端口要看清楚 —— 我第一版以为 25566,写完启动日志才发现跑在 25565
(配置被自己写坏后回落到默认)。端口占用要
ss实测,不能凭配置推断。
1d. 面向 LLM 的 API 形状(§0 推论 1 的落点)
mineflayer 的 API 面向程序员;我们面向 LLM。具体差别与对策:
| 维度 | 程序员友好的做法 | LLM 需要的 | 我们的对策 |
|---|---|---|---|
| 参数 | 位置参数 + 选项对象 | 全命名、少量必填 | find_blocks(matching=..., max_distance=...) |
| 返回 | 原始对象/布尔 | 可读摘要 + 关键字段 | 附 describe()(BlockHit 已有) |
| 失败 | 抛异常 | 说清"为什么不行 + 怎么办" | source="unloaded" / "unknown-state" 那种自述 |
| 规模 | 返回全部 | 限量 + 计数(省 token) | 默认 count=10,另给 total |
| 发现 | 读文档 | 可自省 | bot.describe() 列出可用操作与必需参数 |
★ 已有可借鉴的样本:BlockHit.describe() —— 明确区分
「未加载」/「超出世界高度」/「unknown(id),本机注册表无此 state id,
可能来自 mod 方块」/ 正常。这正是 LLM 需要的说法:
不是报错,而是告诉它「这个方块你暂时看不到,为什么」。
⚠️ unknown(id) 这一点尤其关键(§0 推论):mod 方块的 state id 只有服务端知道,
客户端本机注册表里没有 ⇒ 粗渲染遇到它必须如实说"我不认识",不能瞎猜一个。
这与「与服务端 mod 物品交互」那条线直接相关。
2. 已完成(可复用,有提交)
2.1 渲染桥(P8/P9)
pg2srg.py:ProGuard → SRG 映射转换client-named.jar:官方 jar 反混淆产物- 官方初始化序列(反编译确认):
tryDetectVersion→enableDataFixerOptimizations→Bootstrap.bootStrap()→initRenderThread→beginInitialization→new Minecraft(cfg)→finishInitialization(不跑主循环) - 单帧渲染:
clear→bindWrite→gameRenderer.render→unbindWrite→blitToScreen→updateDisplay
2.2 架构(用户定调的落地)
- Python 只做路由:0 处
net.minecraft引用(实测) - 差异全在 Java:
cmd=setblocks(Python 发 RLE,Java 组装协议)、cmd=capacity(问运行时) - 世代分派:
DeltaTracker类存在 ⇔ 1.21+;每代独立编译的 Java 后端 - 加载器 × 世代正交:动态子类组合,不写 N 个类
2.3 加载器路线 + 光影 ✅(2026-09-26 打通)
关键突破:Knot.init() + loadIntoTarget() —— 拿到 mod 生态,但不起官方主循环。
三个必需条件(缺一失败,全部实测):
- classpath 放
client-named.jar(不是官方 obf jar) -Dfabric.gameMappingNamespace=named- asm 只留一份(1.20.1 自带 9.2+9.6 两版 ⇒
Knot.<clinit>抛 duplicate ASM classes)
B 方案(用户选定):统一接口 + 非通用数据走 extern
McfoldNames:源码写语义路径(net/minecraft/client/Minecraft/resourceManager), 运行时按names.json查物理名- 19 处字符串反射 → 0(remap 改不了字符串字面量,这是唯一两态都通的形态)
gen_names.py:从两份映射复合生成表(11/11 解析成功)
remap 链(build.sh 第 7 步,含两个实测坑):
- 坑①:SRG 的 desc 两侧都是 official ⇒ 左必须转 named、右转 intermediary
(只改类名不改 desc ⇒ 类对了、方法没改 ⇒
NoSuchMethodError) - 坑②:SpecialSource 按 owner 精确匹配、不查继承链 ⇒ 必须
-h inheritance.map(不带漏 ~60 处)
结果:Iris 光影 + 地形同时渲染 ✅
before: colors=4748 (只有天空)
after: colors=113546 (地形 + 光影 + 阴影)
2.4 Sodium tracker 通知(notifyRenderTracker)✅
症状:装 Sodium 后只有天空、chunks=0,而 blockinfo 能正确读方块
⇒ 数据在,渲染器不知道。
三层根因(逐层实测):
- Sodium 的 mixin 挂
replaceWithPacketData内@At(INVOKE onChunkLoaded), 而 1.20.1 该方法内部不调onChunkLoaded⇒ 注入点永不命中 - 反射签名:参数是
ClientLevel不是Level;第三参是位掩码 int - ★ status 值:
FLAG_HAS_BLOCK_DATA=1/FLAG_HAS_LIGHT_DATA=2/FLAG_ALL=3, 而updateMerged的 2×2 判定要四邻 & == 3⇒ 传 1:1&1&1&1=1 ≠ 3⇒ 全部判成"凑不齐" ⇒ 队列恒空
传 1: chunkStatus=49 chunkReady=0 loadQueue=0 colors=4748
传 3: chunkStatus=49 chunkReady=25 loadQueue=25 colors=113546
2.5 版本隔离 + 实例层 ✅
- 三层(权威口径
mcview/lib/versions.cjs:59/61):BASE/store/<loader>-<version>/ 内容根 共享只读:jar/libraries/assets/.mcfold 产物 BASE/instances/<name>/ 实例 独占:mods/shaderpacks/config/saves BASE/runtime/<name>/ 运行期 独占:X display/screenshots/logs mcfold/instances.py(Python 接口,用户定调):create_instance(name, version=, loader=, mods=[...], force=False) # → Path list_instances() # → 摘要 find_content_root(version, loader)- 实例内部:
instance.json+ mods/ shaderpacks/ resourcepacks/ datapacks/ (★ agent 的接口面,放进去即生效)+ config/saves/logs - 不在实例里建 versions/libraries/assets(属内容根,只读共享)
- 实例名白名单
^[A-Za-z0-9._-]{1,64}$;已存在则报错而非默默复用
- 实例内部:
- 版本隔离守卫(
tests/test_version_isolation.py,默认套件里跑)- 首次运行就抓出两处真实回流(
store/vanilla-1.20.1/instances/与store/vanilla-1.21.1/instances/) - ★ 教训:「我检查过了」≠「有一条会自己跑的检查」
- 首次运行就抓出两处真实回流(
2.6 HUD 开关 ✅
hide_gui(两世代后端 + Python set_view(hide_gui=...)),走官方 options.hideGui。
2.7 工程卫生 ✅
.gitignore(运行期产物 / 实例配置 / 构建缓存)git rm --cached28 个误入库的文件- Sodium 往进程 cwd 写 config/ 的漏洞已修(
cwd=self.cwd or self.game_dir)
测试:./run_tests.sh 66 项 · --with-render 80 项(实测全绿)
3. 当前缺口(按依赖顺序)
⚠️ G1. 1.21.1 的构建没跑通(阻断服务端那条线)
build.sh 的路径 bug 已修($HERE/common → $HERE/java/common),
但 1.21.1 编译仍有错:
LoaderBoot.java:1: error: package net.fabricmc.api does not exist
LoaderBoot.java:2: error: package net.fabricmc.loader.impl.launch.knot does not exist
LoaderBoot.java:53: error: package net.fabricmc.loader.api does not exist
gen_1_21/McfoldRenderServer.java:761: error: cannot find symbol
根因:build.sh 第 5 步的编译 classpath(CP)没有包含 Fabric 库,
而 LoaderBoot 需要 net.fabricmc.*。1.20.1 那次是侥幸(Fabric 库在
hand-composed classpath 里),build.sh 本身没把它加进编译 CP。
修法:第 5 步编译时若有 $OUT/fabric/,把它加进 CP。
⬜ G2. 服务端交互没实现(目标里明写的那半)
现状:client.py 只有 handshake / login_start / run_smoke(冒烟级)。
缺:
- configuration 阶段(1.20.2+,登录后、play 前):这里有
registry_data - 服务端注册表:方块 id / 物品 id 的权威来源
- 物品 ↔ 方块映射
★ 技术核心:mod 方块的 state id 只有服务端知道 (客户端只知道自己 jar 里那套)。这正是「与服务端 mod 物品交互」的难点。
已实测确认:ServerboundChatPacket / ClientboundSystemChatPacket 的
protodef 描述不随版本变 ⇒ ProtocolVersion 只影响少数结构。
⬜ G3. 统一接口门面(用户口径:agent 调 Python)
已有 WorldRenderer / Instance / create_instance,缺一个把
「渲染」+「服务端交互」串起来的门面。
⬜ G4. 粗渲染(ass):agent 的常驻感知层(§1、§4)
见 §4 设计(常驻,与细渲染的单次调用互补)。
⬜ G5. 聊天框 → agent → 拍照 的行为链(§1,新方向)
见 §5 设计。
4. 粗渲染(ass)设计(⬜ 未开始)
4.1 定位:agent 的常驻感知层
按 §1 的分工,粗渲染服务于日常游玩,所以它不是一个"拍照辅助", 而是一个常驻的、无 JVM 的世界感知接口。
细渲染: JVM + 完整 MC 栈 + Iris 单次调用(起→渲→销) ~20-40s
粗渲染: 纯 Python + 世界快照 常驻 <100ms
4.2 它要回答的问题(agent 游玩时会问的)
| 问法 | 需要的能力 |
|---|---|
| 「我面前是什么?」 | 视线求交(DDA) |
| 「这堵墙后面有没有通路?」 | 射线 + 邻域查询 |
| 「我站的地方是空的吗?」 | block_at(已有) |
| 「那边那座山是什么?」 | 远距离扫描 / 俯瞰索引 |
| 「周围一圈都有什么?」 | 局部切面(ASCII 最有用的场景) |
| 「从这里能不能看到它?」 | 可见性测试(遮挡判定) |
其中「ASCII 网格」只是其中一种展示形态 —— 它的价值在于 让 LLM 能直接读,而 PNG 需要多模态。
4.3 核心原语(借鉴 mineflayer / prismarine 的做法)
mineflayer 的 raycast / blockAt 是纯数据的视线求交 —— 我们不连服务端,
但同样的计算可以跑在存档/注入的世界快照上。
# 基础已有(mcfold/world.py):
# World.block_at(x,y,z) -> BlockHit(name, state_id, loaded, source)
# ★ 它已能区分 unloaded / unknown-state ⇒ mod 方块不会被当成空气
# 待加(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.4 「ass」的三种展示形态(服务于不同用途)
① ASCII 网格 视野切面 —— 人看 + LLM 直接读
② 结构化线索 开阔度/遮挡率/最近景物/可通行方向 —— LLM 推理用(比①准)
③ 可选:俯视 ASCII 局部地图(w×h 格,每格一个地标字符)—— 定位用
4.5 验证口径(★ 改成"各自对各自用途",不是互相预测)
粗渲染的判据(不需要细渲染参与):
· block_at 已区分 unloaded / unknown-state ⇒ 不能把未加载当空气
· cast_ray 的结果与人工推算一致(可构造的简单场景:单方块、空走廊、斜坡)
· 局部切面的 ASCII 与直接读方块数据一致
细渲染的判据(不需要粗渲染参与):
· 现有:注入地形 → distinctColors / chunksRendered 达阈
· 现有:Sodium tracker 通知后 chunkReady > 0
★ **不再要求**「粗渲染能预测细渲染」—— 两者服务不同问题
(用户口径:粗=日常游玩,细=出图与精细判断)
唯一的交集是「拍照选机位」这一个用例,那里是**用法**上的配合,
不是**正确性**上的依赖。
4.6 实现落点
mcfold/rough.py(新模块,纯 Python、不依赖 JVM)
tests/test_rough.py(默认套件里跑,秒级)
5. 聊天框 → 拍照 行为链设计(⬜ 未开始)
5.1 事件流(借鉴 mineflayer 的事件触发形态)
玩家在聊天框发消息
↓
① 收消息 ClientboundSystemChatPacket(或玩家聊天)
↓
② 意图识别 "给我拍张照" / "和那座山合个影"
↓
③ 需要什么 玩家位置/朝向 + 景物位置 + 可用机位
↓
④ 规划 粗渲染(常驻)试 N 个机位 → 挑最好的
↓
⑤ 出图 细渲染(单次调用:起 JVM → Iris+光影 → 销毁)
↓
⑥ 送达 回聊天 / 发邮件(走现有 agentmail 通路)
mineflayer 的参考点:它的 bot.on('chat', ...) + bot.chat() 就是
这个闭环的极简形态。我们要加的是 ③④⑤(拍照特有的)。
5.2 三个必须先确认的事实(否则设计是空中楼阁)
- 玩家位置从哪来? 我们是离屏渲染,没有真玩家实体。
- 若接服务端:从
ClientboundPlayerPositionPacket拿真玩家位置 - 若不接:由调用方指定一个"玩家"坐标(合成场景)
- 若接服务端:从
- "景物"怎么定位? agent 怎么知道"那座山"在哪?
- 自然语言 → 坐标,这是另一个问题(可能需要俯瞰图 + 索引)
- 相机可摆范围? 不能在方块里、必须在世界高度内、不能超出视距
5.3 最小可用版本(建议先做这个)
不接服务端,用调用方指定坐标打通全链:
# agent 调统一接口
shot = capture_photo(
world=<存档或注入的场景>,
subject=(x, y, z), # 景物
subject_scale=20, # 景物半径
with_player=True, # 是否要合照
player_pos=(x, y, z), # 合照时的"玩家"位置
samples=20, # 粗渲染试几个机位(常驻,毫秒级)
)
shot.png → /path/out.png
shot.rough → ASCII + 线索(给 agent 看它为什么选了这个角度)
6. 里程碑(按 §0 目的重排)
★ 重心:「让 agent 能玩」 = 工具包(感知 + 行动)。 渲染是工具之一,不是主线;机位/拍照是用例,不是目的。
| # | 内容 | 依赖 | 状态 |
|---|---|---|---|
| M0 | 起空白测试服(§1c),版本隔离三件套 | — | ✅ |
| M1 | 修 build.sh 编译 CP(加 Fabric 库)⇒ 1.21.1 构建跑通 | — | 🔄 |
| M2 | 协议客户端:连测试服 → configuration → registry_data → 进 play | M0 | ⬜ |
(这是最大缺口:现有只到 login_start 冒烟级) |
|||
| M3 | 状态层:World / entities / inventory 从服务端真状态来 | M2 | ⬜ |
| M4 | 事件层:粗粒度 + 细分层(照 mineflayer,省 token) | M2 | ⬜ |
| M5 | 操作层:chat / look / move / dig / place(能改世界) | M2, M3 | ⬜ |
| M6 | 粗渲染(常驻感知):基于 M3 的真状态 | M3 | ⬜ |
| M7 | LLM 友好的 API 形状(§1d):自省 / 可读返回 / 限量 | M3–M6 | ⬜ |
| M8 | 细渲染(单次调用):出图,带光影 | M2 | ⬜ |
| M9 | 第一个用例:capture_photo()(粗搜机位 → 细出图) |
M6, M8 | ⬜ |
建议顺序:M0 → M1 → M2。
- M0/M1 是解阻断(M1 是我已经查到根因的那个 classpath bug)
- M2 是真正的门槛:从「能渲染」到「能玩」,缺的不是渲染,是完整协议层。
mineflayer 的 41 个 plugins 全建立在它之上,我们目前只到
login_start。
★ M8(细渲染)不是早期优先级 —— 它已经能跑(Iris 光影 + 地形,
colors=113546 是实测),只是接不上服务端真数据。等 M2/M3 通了,
它自然就在链路上。
★ M9(拍照)放最后 —— 它是验证工具包立不立得住的用例, 不是工具包本身。
7. 工程纪律(本项目已确立的口径)
- Python 只路由,差异进 Java —— Python 侧 0 处 MC 类名引用
- 验证要能失败 —— 判据必须构造触发,不靠观察现状 (「今天有反例」与「今天没反例」都可能抓不到缺陷)
- extern 参数只传路径 —— 命名空间知识留在 jar 侧
- 报数必须带:来源 + 取数时刻 + 谓词/域
- 门禁要放默认套件 —— 放
--with-render才跑的守卫等于没守卫 - 实例与内容根同级 —— 绝不嵌套;同版本可多实例
- 诚实标注射程 —— 「加载器能起动」≠「mod 能用」
8. 环境备忘
仓库 /home/newqqagent/plugindev/mcfold (远端 ssh://git@jianfgit.xyz:220/jianf/agentmc.git)
内容根 /home/newqqagent/mcview/store/vanilla-{1.20.1,1.21.1}/
实例 /home/newqqagent/mcview/instances/<name>/
构建产物 内容根/.mcfold/{classes,classes-intermediary.jar,client-named.jar,
names*.json,named2intermediary.srg,inheritance.map,classpath.txt,
natives/,fabric/}
服务端 /home/minecraft (neoforge 1.21.1,mods 若干,端口 25565)
mineflayer /home/newqqagent/mcbridge/node/node_modules/mineflayer ← 参考实现
Xvfb :98
测试 ./run_tests.sh 66 项
./run_tests.sh --with-render 80 项(需 MCFOLD_RENDER_CLASSPATH 等)
渲染环境变量:
export MCFOLD_RENDER_CLASSPATH="$(paste -sd: $STORE/.mcfold/classpath.txt):$STORE/.mcfold/client-named.jar:$STORE/.mcfold/classes"
export MCFOLD_NATIVES_DIR=$STORE/.mcfold/natives
export DISPLAY=:98