|
|
8b1fc9289d
|
Fabric 加载器路线跑通并落地验证(launcher="fabric",6/6 绿)
上个会话把 `launcher="fabric"` 写进了后端,却**没有留下任何可重跑的验证** ——
「经加载器能起来」这件事只活在对话里。本次把它变成可复算的。
## 机制(路线 C,不是 LOADERS.md 原来写的 A/B)
A/B 都假设「游戏由 Loader 起」。但 `Knot.init()` 是 public 的:它做完
「发现 mod → 建 KnotClassLoader → 应用 mixin → invokeEntrypoints(preLaunch)」
后**返回**,不进入官方主循环(那在 GameProvider.launch())。
⇒ `knot.loadIntoTarget("McfoldRenderServer")` 把我们的服务端注入同一个
KnotClassLoader —— **mod 生态与我们的控制权同时拿到,不必把服务改写成 mod**。
## 主要改动
### ① `RenderBackend._fabric_classpath()` —— 组装从用户手里移到后端
两个变换是实测必需、且用户不该自己拼的机械操作:
- 剔除 1.20.1 自带的 asm(9.2 与 9.6 两版并存)—— 否则 Knot.<clinit> 抛
"duplicate ASM classes found on classpath"
- 追加 Fabric 的 8 个库(asm×5 / sponge-mixin / intermediary / fabric-loader)
库来源改为内容根 `.mcfold/fabric/`(原在 /tmp/fab,tmpfs 且违反版本隔离)。
实测 67 条 → 去 7 asm + 加 8 fabric = 68 条。
### ② `tests/test_loader_render.py`(新增,6 项)
✓ 启动架构与世代正交(动态子类,不写 N 个类)
✓ 两个架构可枚举
✓ classpath 组装(67→68:去 7 asm,加 8 fabric)
✓ 非 asm 库原样保留(去 asm 是外科手术,不是重写)
✓ **Fabric 加载器真启动并渲染**(KnotClassLoader / chunks=10..25 / colors=4391..4547)
✓ **Fabric 拒绝不兼容 mod** ← 反向对照:缺 fabric-api 的 Explorify 被拒
最后一条是**两条路线的可判差异**:零加载器是「无脑注册」,不知道 mod 要
fabric-api;Fabric 会。没有它,"加载器真的介入了"这句话就没有反面证据。
### ③ `run_tests.sh` —— 慢套件改为列表驱动 + 全跳过不再报绿
- SLOW 列表含 test_render_bridge / test_loader_render(默认不跑)
- ★ 全部跳过的套件**不算绿**:报"跳过 N 项"而非计入通过数 ——
否则"没测"会被读成"测过了"。
## 验证
./run_tests.sh → 62 项(2 个慢套件标 ⊘)
./run_tests.sh --with-render → **76 项全绿**
(62 单元 + test_render_bridge 8 + test_loader_render 6)
## 仍未做
Iris 光影真实加载(加载器已通,未验过一次 shader pack)
forge / neoforge 后端(gen_1_20 / gen_1_21 均待设计)
|
2026-09-26 07:12:41 +08:00 |
|
|
|
a1ce560c05
|
修复渲染联测的两个阻断缺陷(之前 5/7 → 现 7/7)
## ① natives 必须平铺,不能保留包路径
natives-linux jar 里的路径是 `linux/x64/org/lwjgl/<module>/lib*.so`,
而 LWJGL 经 java.library.path / LD_LIBRARY_PATH 按**文件名**找库 ——
嵌套一层的目录它找不到。实测:
UnsatisfiedLinkError: Failed to locate library: liblwjgl.so
at RenderSystem.<clinit> → Tesselator.<clinit>
build.sh 原来直接 `unzip -d $OUT/natives`(保留包路径)⇒ 产物不可用。
改为解到临时目录再 `find -name '*.so' -exec cp` 平铺。
(已用新逻辑重铺 store/vanilla-1.20.1/.mcfold/natives,7 个 .so 在根下。)
## ② api_notes 应为 staticmethod
35534b1 把世代分派改成类级调用后,`api_notes` 仍是实例方法,
测试里 `Gen_1_20.api_notes()` 直接 TypeError。它不读 self ⇒ 一律 staticmethod。
这也是它能"不启动后端就做世代对照"的前提。
## ③ classpath.txt 的语义写进注释
它**只含第三方 libraries**;client-named.jar 与 classes/ 由使用方拼接
(build.sh:88 自己拼,测试/后端从各自路径取)。
在 build.sh 里注明,避免下一个人以为它是"完整运行 classpath"。
## 验证
./run_tests.sh → 62 项全绿
.mcfold classpath + 平铺 natives → 渲染联测 **7/7 全绿**
(injected=49 chunks=31 colors=11714 / placed=1003716 chunks=25 colors=4540)
|
2026-09-26 06:53:30 +08:00 |
|
|
|
35534b1564
|
feat: get_block_info —— 方块信息查询(Java 侧回答,Python 只路由)
新增 cmd=blockinfo(两代后端编译通过、实测):
返回 block 名(含 mod 方块)、stateId、属性、isAir/isSolid、
光照/硬度、贴图缺失的程序化判据、是否来自 mod。
★ 修掉一个真实缺陷(附录 D 的语义区分落到了实处):
未加载区块原先返回 void_air(known=true),实测发现两个 API 都不可用:
- ClientLevel.hasChunk() 反编译实测**恒返回 true**
- ChunkSource.hasChunk() 走 getChunk(...,false),而 ClientChunkCache
覆写后在该参数下返回 emptyChunk(占位,非 null)⇒ 也恒 true
正解:getChunk(..., requireChunk=**false**) 判 null
(反编译:return requireChunk ? emptyChunk : null;)
⇒ 现在未加载明确回 known=false + reason=chunk_not_loaded
实测(1.20.1):
草地 → minecraft:grass_block props={'snowy':'false'} missing=False
原木 → minecraft:oak_log props={'axis':'y'}
空气 → minecraft:air
远处 → known=false reason=chunk_not_loaded ← 与空气区分
实测(1.21.1 + mod):
mts:mtsofficialpack.fancydirt_block mod=True tex=mtsofficialpack:block/fancydirt
createbigcannons:steel_block mod=True tex=createbigcannons:block/steel_block
⇒ mod 方块同样能查到正确贴图名与 fromMod 标记
顺带修 gen_1_21 两处世代差异(ChunkStatus 换包、bridge 编译顺序)。
测试:69 项全绿(新增 test_get_block_info 6 条判据)
|
2026-09-26 00:24:29 +08:00 |
|
|
|
16f2e04fff
|
架构重构:差异全部下移到 Java,Python 只做路由
用户口径:差异放在 Java 层,Python 相当于路由。
下移到 Java(新增 cmd=setblocks):
Python 只发 RLE([[stateId,count],...]),Java 用官方 API 完成
调色板组装 / 位打包 / 官方序列化 / 入 cache / 渲染。
★ 意外收益:走 ClientboundLevelChunkPacketData(chunk).getReadBuffer()
+ replaceWithPacketData(...) 这条官方路径,两代签名完全一致
⇒ 原先的 Storage.chunks 反射注入**整段删掉**。
新增 cmd=capacity:世界尺寸(sectionsCount/minSection/defaultBiome)
由运行时回答,Python 不再硬编码 24/-4(1.16 时代是 16/0)。
Python 侧删除(防回归,见 test_import_surface):
render_bridge.py(旧桥,无世代概念)
render._pack/_long_array/_encode_paletted/_encode_chunk/_varint
anvil.encode_block_section/encode_chunk_payload/save_chunk_to_payload
合计 454→374 行(render.py)、300→177 行(anvil.py)
保留在 Python(都是「不随版本变」的知识):
RLE 压缩(rle_encode/rle_decode,具名可测)
存档读取(region/NBT)
方块名→id 的路由(查询在 Java)
NDJSON 协议
测试:62 项单元 + 6 项渲染联测全绿
新测试反映新架构:RLE 边界、运行时容量、世代分派、新路径渲染
test_anvil 里原有 8 项协议位打包测试已删(那已下移到 Java,
由两代后端各自的编译验证覆盖)
新增 scaffold/ARCHITECTURE.md(分层原则 + 加新世代步骤 + 防回归清单)
|
2026-09-25 19:21:45 +08:00 |
|
|
|
253146cffe
|
gen_1_21 实测启动成功 + provision 工具
决定性验证:gen_1_21 后端在 1.21.1 上收到 {"event":"ready"}
⇒ 世代分派架构成立:同一套 Python 层,两个独立编译的 Java 后端
运行时发现第 12 处世代差异(编译期看不出):
1.20.x registryAccess 是可变 LayeredRegistryAccess → 事后反射替换
1.21.x registryAccess 是 final RegistryAccess$Frozen → 反射抛
"Can not set final field"
处理:gen_1_21 改为先载注册表 → 经 CommonListenerCookie.receivedRegistries
传入 → 再构造 listener。比 1.20 的反射做法更干净。
新增 mcfold/provision.py(从官方 manifest 装任意版本,版本无关):
- 跳过音频资源:1.21.1 实测 3911 条里跳过 3744 条
- natives 两种形态都认(1.21 起是独立库条目,非 classifiers)
- 实测 1.21.1:274 文件 / 200MB / 32s / 0 失败
|
2026-09-25 18:35:28 +08:00 |
|
|
|
8c7d5b9d38
|
世代分派体系:Python 层 switch + 各世代独立编译的 Java 后端
用户定向:在 python 层写 switch,调用不同 java 后端(而非一个文件里 switch)。
新增:
mcfold/backends/{__init__,base,gen_1_20,gen_1_21}.py 后端体系与分派
scaffold/java/gen_1_20/ 对 1.20.x client jar 编译
scaffold/java/gen_1_21/ 对 1.21.1 client jar 编译(✅ 实测编译通过)
scaffold/java/VERSION-DIFFS.md 实测差异全表
判据:DeltaTracker 类是否存在(1.21 引入,正是 render 签名变化的根源)
⇒ 比硬编码版本号稳
实测差异共 11 处(21 个检查点里 13 个跨世代完全一致):
setLevel / GameRenderer.render / countRendered* / createPackRepository /
ResourceLocation 构造 / enableDataFixerOptimizations(移除) / User 构造 /
ChunkStatus 包路径 / ClientPacketListener 构造(参数个数语义都变) /
loadEffect(变 private)
为什么必须分后端:DeltaTracker 在 1.20.x jar 中不存在,
两代调用无法共存于一份源码(实测编译报 cannot find symbol)。
1.21.1 反混淆已验证(29MB named jar,DeltaTracker 3 处)
|
2026-09-25 18:21:08 +08:00 |
|