|
|
c3a57256ac
|
McfoldBridge 改反射 + 新增 Sodium tracker 通知与 hideGui
三件事,其中后两件是实测查出来的。
## ① McfoldBridge:「同包直调」在 intermediary 下失效 ⇒ 改反射
原来把桥类放进 `net.minecraft.client.renderer` 包,靠**同包**访问
GameRenderer 的 package-private 方法。这个技巧只在 **named** 下成立:
named 桥与 GameRenderer 同在 net.minecraft.client.renderer ⇒ 同包 ✅
intermediary GameRenderer → net.minecraft.class_757(包变成 net.minecraft)
桥仍在 net.minecraft.client.renderer ⇒ 异包 ❌
⇒ IllegalAccessError: McfoldBridge tried to access method
'void net.minecraft.class_757.method_34538(...)'
★ **包名本身也被 remap**,所以「靠包名对齐」切换命名空间就断了。
反射 + 语义名(McfoldNames)与命名空间无关,是唯一两态都通的形态。
(gen_1_21 本来就为 loadEffect 用了反射,现在两处统一。)
## ② Sodium 通知:装载地形渲染的 mod 现在能看见了(根因三层,逐层实测)
**症状**:装了 Sodium 后画面只有天空、chunks=0,而 blockinfo 能正确读出
方块 ⇒ 数据在,只是它不知道。
**三层根因(每层都有实测数据)**:
┌ ① mixin 永不命中
│ Sodium 挂在 replaceWithPacketData 内 @At(INVOKE onChunkLoaded, AFTER)
│ 而 1.20.1 该方法**内部不调** onChunkLoaded(在 getChunk/drop 里各有一处)
│ ⇒ 它的 ChunkTracker 从未被告知任何区块
│ 【修】notifyRenderTracker() 补通知
│
├ ② 反射签名错
│ ChunkTrackerHolder.get 的参数是 **ClientLevel** 不是 Level
│ 第三参数是**位掩码 int** 不是 ChunkStatus 对象(签名 (III)V)
│
└ ③ ★ status 值错(真正的那道坎)
Sodium 的语义:
FLAG_HAS_BLOCK_DATA = 1 FLAG_HAS_LIGHT_DATA = 2 FLAG_ALL = 3
而 ChunkTracker.updateMerged 的 2×2 邻域判定是
`四邻 status 按位与 == 3` (字节码 :54 iand / :71 iconst_3)
⇒ 传 1:1&1&1&1=1 ≠ 3 ⇒ 全部判成"凑不齐 2×2"
⇒ chunkReady 恒 0、loadQueue 恒空、画面无地形
传 3:3&3&3&3=3 == 3 ⇒ 25 个 2×2 组就绪 ⇒ 重建队列有 25 项
(我们确实有光照数据:注入时跑了 light.runLightUpdates())
**实测对比**(同一场景 7×7=49 区块):
传 1: chunkStatus=49 chunkReady=0 loadQueue=0 colors=4748
传 3: chunkStatus=49 chunkReady=25 loadQueue=25 colors=113546
★ 走反射 + 类不存在时静默跳过 ⇒ 不装 Sodium 时零影响。
这不是"给某个 mod 打补丁":注入路径缺了"区块已就绪"广播,
补上它对所有依赖该事件的 mod 都有效。
**同时广播官方的 level.onChunkLoaded(pos)** —— 与网络包路径等价的那一步。
## ③ hideGui:截图场景关掉 HUD
走**官方选项** `options.hideGui`(= 游戏里 F1),不改渲染代码 ——
GameRenderer.render 里就是 `if (!options.hideGui) gui.render(...)`。
为什么要关:截图只关心画面,HUD 是噪声,而且它给 distinctColors
掺一大块固定像素(实测污染判据)。
两个世代后端都加了 hideGui;Python 侧 set_view(hide_gui=...)。
|
2026-09-26 10:17:48 +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 |
|
|
|
6827f4d0e1
|
实测 Fabric 客户端可启动 + 加载器后端设计文档
用户判断:mod 支持分 fabric/forge,且不同版本还有差异 ⇒ 各有独立后端。
实测支持此结论,且差异比方法签名更深一层。
实测成功(关键):
Fabric Loader 0.19.5 完整启动 1.20.1 客户端
'Loading Minecraft 1.20.1 with Fabric Loader 0.19.5' + Mixin 子系统 + 12 个资源图集
★ 必须剔除 1.20.1 自带的 asm 9.2/9.6,改用 Fabric 的 9.10.1
(否则 Knot.<clinit> 抛 duplicate ASM classes)
架构结论:后端身份是 (加载器, API 世代) 二元组
因为 Fabric/Forge 下**我们不拥有启动流程**(Loader 起游戏+跑主循环),
与我们「不跑主循环+手动喂数据」的模型根本冲突。
两条可行路线已写入 scaffold/LOADERS.md(A: 作为 mod 被加载;
B: 只借 Fabric 的注册表与资源包,保留我们的渲染循环)——均待实测。
已实现(零加载器,两个 vanilla 后端编译通过、待实测):
cmd=mods:Java 侧自己扫 jar/注册占位方块/挂资源包
覆盖:外观 ✅ state id ✅ 变体 ⚠️ 行为 ❌ Iris ❌
|
2026-09-25 19:55:13 +08:00 |
|
|
|
d0fb5517a0
|
修复 gen_1_21 渲染:1.21 的 gameLoadFinished 门禁(第 13 处世代差异)
症状:gen_1_21 启动成功、数据注入成功(loadedChunks=49),
但 chunksRendered=0、画面停在 Mojang 加载屏(distinctColors≈199)。
根因(1.21 GameRenderer.render 内部,编译期看不出):
boolean ok = minecraft.isGameLoadFinished();
if (ok && renderLevel && level != null) renderLevel(...);
该标志只在 reloadResourcePacks 回调(onResourceLoadFinished)里置 true。
我们不跑异步 reload(会死锁)⇒ 恒 false ⇒ 世界永不渲染。
1.20.x 无此门禁 ⇒ gen_1_20 从未受影响。
修法:gen_1_21 的 McfoldBridge 加 markGameLoadFinished(反射置位),
在 boot 里 shader 编译后调用 —— 差异留在 Java 侧。
★ 教训:这类差异不崩不报错,只是画面不对 ⇒ 判据必须用
chunksRendered/distinctColors,不能只看进程活着。
顺带修:tick 异常不再静默吞掉(try{tick()}catch(ignore){} → 打印第一帧异常)
新增诊断字段:screenNow/overlayNow/levelNow/playerNow/noRender
实测:gen_1_21 现在 chunksRendered=25、has_world_content=True
(草地/天空/太阳/HUD 正常)
|
2026-09-25 19:45:24 +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 |
|