为实验室构建 mod¶
智能体可能想把自己的观测代码放进游戏里——一个 logger、一个 tick 采样器、一条命令。在实验室里这是一个两步循环:对着实验室已经有的 jar 构建,然后交给 lab_server.py 部署并重启。Minecraft 26.2 的类文件没有混淆,所以不需要 Gradle,也不需要下载映射;tools/build_mod.py 直接用 javac 编译。
前置条件¶
- 一个至少启动过一次的实验室。 第一次启动会让 Fabric 启动器把服务端 jar 解到
labs/<lab>/versions/<mc>/server-<mc>.jar并安装依赖库;这些文件就是编译 classpath。 - 带
javac的 JDK 25。 可以用--jdk传 JDK home,或在 provision 时用--jdk PATH记录下来,也可以走JAVA_HOME或PATH。实验室自己的java已经记在lab.json里,所以通常只需要多传--jdk。纯 JRE 不能编译。 - 构建不需要联网:一切都来自
labs/<lab>/和labs/_cache/。
源码结构¶
my-mod/
src/main/java/... Java 源码
src/main/resources/
fabric.mod.json mod 元数据(必需)
assets/... 其他资源,会原样拷进 jar
fabric.mod.json 至少要写清楚 entrypoint 和 environment,例如:
{
"schemaVersion": 1,
"id": "example-logger",
"version": "${version}",
"name": "Example Logger",
"environment": "*",
"entrypoints": { "main": ["dev.example.logger.LoggerMod"] },
"depends": {
"fabricloader": ">=0.19.0",
"minecraft": "*",
"fabric-api": "*",
"java": ">=25"
}
}
打包时 ${version} 会被 --version 替换。depends 里只写实验室确实装了的依赖;只要声明的依赖缺失,Fabric 就会拒绝启动——这对可审计实例来说正是想要的行为。
服务端 mod 用 "environment": "*";如果它绝不能在客户端加载,就用 "server"。实验室是独立服务端,entrypoint 走 main 阶段(net.fabricmc.api.ModInitializer)。
构建¶
python tools/build_mod.py --source examples/smoke-mod --lab lab-a `
--out labs/build/smoke-mod.jar --version 0.1.0 --compression store
输出:
building examples\\smoke-mod for lab lab-a (minecraft 26.2)
javac C:\\...\\bin\\javac.exe (javac 25.0.1)
javac @...\\javac.args (1 sources, 87 classpath entries)
built labs\\build\\smoke-mod.jar (14.8 KB, sha256 e66ebdb16d53238a...)
version 0.1.0, compression store, release 25
87 classpath entries from lab lab-a
metadata labs\\build\\smoke-mod.jar.build.json
classpath 按这个顺序从实验室拼出来:
labs/<lab>/versions/<mc>/server-<mc>.jar——解包后的、未混淆的 Minecraft 服务端类;labs/<lab>/libraries/下的每个 jar——Fabric loader、mixin、Brigadier、Gson 以及服务端的其他依赖库;labs/<lab>/.fabric/processedMods/下的每个 jar——运行中的服务端实际会加载的 Fabric API 模块;labs/<lab>/mods/里的每个 jar——这样 mod 可以对着 Carpet 或其他已安装 mod 编译;--classpath-extra传入的任何东西。
全部用 --release 25 编译,并由同一个工具打包。jar 是确定性的:条目排序、时间戳固定,默认 --compression store 让等长修改保持等长。相同源码重复构建得到逐字节相同的结果,改一个字符串就会改变哈希。旁边的 <out>.build.json 记录了 jar 哈希与大小、JDK 与 javac 版本、服务端 jar 哈希、classpath 摘要和每个源码文件的哈希——请把它和实验审计一起保存。
部署并读取结果¶
Fabric 只在服务端启动时加载 mod,运行中永远不会。停止后再部署,然后启动:
python tools/lab_server.py stop --name lab-a
python tools/lab_server.py provision --name lab-a --mod-jar labs/build/smoke-mod.jar
python tools/lab_server.py start --name lab-a --wait 300
python tools/lab_server.py exec --name lab-a "mcagent-smoke status"
python tools/lab_server.py exec --name lab-a "mcagent-smoke sample first"
provision 按 SHA-256 比较 jar 与已部署文件。报 replaced 说明字节确实换了(哪怕大小没变);报 unchanged 说明字节确实一样。它拒绝把 mod 部署进运行中的实验室。start 会把磁盘上的哈希写进 run.json(modsAtStart),让这次运行与确切的构建绑定。
游戏侧 mod 把证据写到实验室指定的地方:start 会传 -Dmcagent.auditDir=...(以及 -Dmcagent.labName、-Dmcagent.labInstance、-Dmcagent.worldDir、-Dmcagent.serverDir、-Dmcagent.serverPort),mod 不必猜自己在哪个实验室。要读的结果在 labs/<lab>/audit/ 和控制台日志 labs/<lab>/logs/console.log。
smoke mod¶
examples/smoke-mod/ 是一个刻意保持通用的观测 mod:加载时打印 [mc-agent-smoke] loaded build=... lab=...,服务端 ready 时写 audit/start.json 和一行 smoke.log,并注册两条控制台命令(mcagent-smoke status、mcagent-smoke sample <label>)。它不含 ROM 或任何用例逻辑——它存在的意义是验证构建/部署/重启这条路径。把真实 logger 建在它上面即可。
构建或加载失败时¶
工具从不隐藏编译器输出。缺少符号时是:
...\\SmokeMod.java:51: error: cannot find symbol
private static final Object BROKEN = doesNotExist();
^
symbol: method doesNotExist()
error: build failed: javac exited 1; the compiler output above is the error
加载失败会出现在服务端日志和 start 的失败输出里。缺依赖时 Fabric 会打印清晰的块:
[main/ERROR]: Incompatible mods found!
Fix: add [add:mc-agent-absent-mod 1 (( -∞,∞ ))], remove [], replace []
Mod 'MC Agent Lab Smoke Mod' (mc-agent-lab-smoke) 0.1.0 requires mc-agent-absent-mod ...
entrypoint 写错时:
java.lang.ClassNotFoundException: dev.mcagent.smoke.AbsentEntrypoint
lab_server.py start 以非零退出,打印日志尾部,并把看起来像错误的行(exception、failed、missing、requires、incompatible)加上 !! 前缀重复一遍。如果实验室把某个 mod 标记为必需(--test-mod 或 --require-mod),该 jar 缺失时 start 会直接拒绝,坏掉的审计环境不可能被当成干净运行。
限制¶
build_mod.py只编译服务端 mod。实验室里只有服务端 jar;客户端 mod 必须对着已安装的客户端实例构建(interface mod 自己的build.py就是例子)。- 工具对着实验室当前记录的状态构建。如果 Minecraft、loader 或某个 mod 升级了,先用新 jar 重新 provision 再构建。
- 没有 Gradle/loom 式的资源处理;
fabric.mod.json和普通资源只是被拷贝(带${version}替换)。 - 实验室构建不做 remap,这对未混淆的 26.2 服务端是正确的;更老的、混淆过的 Minecraft 需要 intermediary 映射和另一条构建路径。