P8 完成:世界数据 → 官方渲染器 → 真实出图

不起完整客户端、不连服、不登录,只初始化 MC 渲染栈并喂入我们自己造的世界数据。

已验证(全绿):
  countRenderedChunks=29  distinct colors=1450
  画面:草地/原木/树叶/天空/HUD/英文文字(sample-world-render.png)

打通的关卡:
  - 反混淆(client-named.jar)+ GL 离屏 + new Minecraft(cfg) 不进主循环
  - 资源同步重载:22 listener(ModelManager/FontManager/Dispatcher)
    ★ 不能 join reloadResourcePacks(等主循环→死锁),直接调各 listener reload()
  - 注册表从客户端自带 data/ 加载(dimension_type=4)
  - 区块注入:LevelChunk + setBlockState → ClientChunkCache.Storage.chunks
    + LightEngine.queueSectionData(全亮)→ allChanged()
  - 每帧 mc.tick() 推进网格重建队列(不跑则 countRenderedChunks=0)

踩坑记录(10 条,README 详列):
  LoadingOverlay/ReceivingLevelScreen 遮挡、FolderData 参数序、
  assetIndex 用落盘名、reloadShaders 包私有、读像素必须绑 FBO、
  判据用 distinct colors 而非 non-black

更正附录 D:state_id=1003 → note_block 不是误映射(note_block 有 800 状态)
  —— 真客户端给完整状态,是 1.20.1 上 mod 方块语义的唯一权威来源
This commit is contained in:
JianFeeeee
2026-09-25 17:01:49 +08:00
parent e223be2571
commit 541fe232a8
8 changed files with 499 additions and 241 deletions

View File

@ -0,0 +1,82 @@
import com.mojang.blaze3d.platform.DisplayData;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.authlib.properties.PropertyMap;
import net.minecraft.SharedConstants;
import net.minecraft.client.Minecraft;
import net.minecraft.client.User;
import net.minecraft.client.main.GameConfig;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.Bootstrap;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import java.io.File;
import java.net.Proxy;
import java.util.Optional;
import java.util.OptionalInt;
/**
* 证明:真客户端的注册表能给出 state id ↔ 方块名的权威映射。
*
* 这正是不起完整客户端、只用客户端注册表的价值所在:
* 1.20.1 不从网络下发方块表(附录D),mod 方块的 state id 只有客户端注册表知道。
*/
public class RegistryProbe {
public static void main(String[] args) throws Exception {
String gameDir = arg(args, "--gameDir", "/home/newqqagent/mcview/instances/default");
String assets = arg(args, "--assets", "/home/newqqagent/mcview/store/vanilla-1.20.1/assets");
String assetIdx = arg(args, "--assetIndex", "1.20.1");
SharedConstants.tryDetectVersion();
SharedConstants.enableDataFixerOptimizations();
Bootstrap.bootStrap();
File runDir = new File(gameDir);
User user = new User("mcfold_cam", "0".repeat(32), "mcfold_cam",
Optional.empty(), Optional.empty(), User.Type.LEGACY);
PropertyMap props = new PropertyMap();
GameConfig cfg = new GameConfig(
new GameConfig.UserData(user, props, props, Proxy.NO_PROXY),
new DisplayData(854, 480, OptionalInt.empty(), OptionalInt.empty(), false),
new GameConfig.FolderData(runDir, new File(runDir, "resourcepacks"), new File(assets), assetIdx),
new GameConfig.GameData(false, "mcfold", "release", false, false),
new GameConfig.QuickPlayData(null, null, null, null));
RenderSystem.initRenderThread();
RenderSystem.beginInitialization();
Minecraft mc = new Minecraft(cfg);
RenderSystem.finishInitialization();
System.out.println("[reg] Minecraft constructed");
// ★ 核心:客户端的方块注册表
int blockCount = BuiltInRegistries.BLOCK.size();
int stateCount = Block.BLOCK_STATE_REGISTRY.size();
System.out.println("[reg] BLOCK registry size = " + blockCount);
System.out.println("[reg] BLOCK_STATE registry size = " + stateCount);
// ★ 关键验证:mineflayer 会静默误映射的那些越界 id,客户端怎么答
int[] probes = {0, 1, 1002, 1003, 1502, 30000, stateCount - 1};
for (int id : probes) {
BlockState st = Block.stateById(id);
ResourceLocation key = BuiltInRegistries.BLOCK.getKey(st.getBlock());
System.out.println("[reg] state_id=" + id + " -> " + key + " [" + st + "]");
}
// ★ 列出几个方块名(证明可读)
System.out.println("[reg] sample blocks:");
for (int i = 0; i < 5; i++) {
ResourceLocation k = BuiltInRegistries.BLOCK.getKey(BuiltInRegistries.BLOCK.byId(i));
System.out.println(" id=" + i + " -> " + k);
}
System.out.println("[reg] DONE");
System.exit(0);
}
private static String arg(String[] a, String key, String def) {
for (int i = 0; i < a.length - 1; i++) if (a[i].equals(key)) return a[i + 1];
return def;
}
}