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 条判据)
This commit is contained in:
JianFeeeee
2026-09-26 00:24:29 +08:00
parent 6827f4d0e1
commit 35534b1564
25 changed files with 1038 additions and 522 deletions

View File

@ -0,0 +1,67 @@
import net.fabricmc.api.EnvType;
import net.fabricmc.loader.impl.launch.knot.Knot;
/**
* 【加载器后端】用 Fabric 加载器,但不写 mod。
*
* 用户定向(2026-09-25):
* 「不写 mod」+「用 mod 加载器」+「功能在现有后端里」。
*
* 实现关键(全部实测):
* 1. Knot.init() 是 **public**,它做完
* 「发现 mod → 建 KnotClassLoader → 应用 mixin → invokeEntrypoints(preLaunch)」
* 后返回,**不**启动官方主循环(那在 GameProvider.launch() 里 —— 我们不调)。
* 2. Knot.loadIntoTarget(name) 把我们的启动类加载进同一个 KnotClassLoader
* ⇒ 我们的代码与 mod 共享 classloader 与 mixin 变换,但控制权仍在我们手里。
*
* 实测输出:
* [loaderboot] ✅ init 返回: net.fabricmc.loader.impl.launch.knot.KnotClassLoader
* [loaderboot] - mcfold_loadprobe 1.0 ← mod 被识别
* [loaderboot] - minecraft 1.20.1
* [loaderboot] ✅ 加载到: McfoldRenderServer / CL=KnotClassLoader
* {"event":"ready"} ← 我们的渲染服务同时就绪
*
* ★ 两个必需条件(缺一则我们的 named 代码无法在加载器下运行):
* a) classpath 里放 **反混淆后的** client jar(client-named.jar),不是官方 obf jar
* b) -Dfabric.gameMappingNamespace=named
* 否则游戏被 remap 成 intermediary,堆栈里是 `enn.<init>`,
* 我们的代码会抛 NoClassDefFoundError: net/minecraft/client/Minecraft(实测)。
*
* 启动示例:
* java -cp "<vanilla基础库(去asm)>:<fabric的8库>:client-named.jar:<我们的classes>" \
* -Dfabric.gameMappingNamespace=named \
* -Dfabric.modsFolder=/path/to/mods \
* LoaderBoot --gameDir ... --assets ... --assetIndex 1.20.1
*
* ★ asm 必须只留一份(用 Fabric 的 9.10.1):1.20.1 自带 9.2 与 9.6 两版,
* 与 Fabric 的并存会让 Knot.<clinit> 抛 "duplicate ASM classes found on classpath"。
*/
public class LoaderBoot {
/** 我们自己的服务端主类(默认包,与 McfoldRenderServer 同名)。 */
private static final String TARGET = "McfoldRenderServer";
public static void main(String[] args) throws Exception {
Knot knot = new Knot(EnvType.CLIENT);
// ① 加载 mod + 应用 mixin;不起官方主循环
ClassLoader gameCl = knot.init(args);
System.err.println("[loaderboot] KnotClassLoader = " + gameCl.getClass().getName());
// 报告已加载的 mod(诊断用;也是「加载器真的在工作」的证据)
try {
for (var m : net.fabricmc.loader.api.FabricLoader.getInstance().getAllMods()) {
System.err.println("[loaderboot] mod " + m.getMetadata().getId()
+ " " + m.getMetadata().getVersion().getFriendlyString());
}
} catch (Throwable t) {
System.err.println("[loaderboot] 读 mod 列表失败: " + t);
}
// ② 把我们的服务端注入同一个 classloader,然后调用它的 main
Class<?> boot = knot.loadIntoTarget(TARGET);
System.err.println("[loaderboot] 注入 " + boot.getName()
+ " 到 " + boot.getClassLoader().getClass().getSimpleName());
boot.getMethod("main", String[].class).invoke(null, (Object) args);
}
}