From d0fb5517a0b6facd875d00fc8be3f142976bea3d Mon Sep 17 00:00:00 2001 From: JianFeeeee Date: Fri, 25 Sep 2026 19:45:24 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20gen=5F1=5F21=20=E6=B8=B2?= =?UTF-8?q?=E6=9F=93=EF=BC=9A1.21=20=E7=9A=84=20gameLoadFinished=20?= =?UTF-8?q?=E9=97=A8=E7=A6=81=EF=BC=88=E7=AC=AC=2013=20=E5=A4=84=E4=B8=96?= =?UTF-8?q?=E4=BB=A3=E5=B7=AE=E5=BC=82=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 症状: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 正常) --- scaffold/java/VERSION-DIFFS.md | 32 +++++++++++++++++++ .../java/gen_1_20/McfoldRenderServer.java | 6 +++- .../java/gen_1_21/McfoldRenderServer.java | 23 ++++++++++++- .../client/renderer/McfoldBridge.java | 20 ++++++++++++ 4 files changed, 79 insertions(+), 2 deletions(-) diff --git a/scaffold/java/VERSION-DIFFS.md b/scaffold/java/VERSION-DIFFS.md index 7f7a5da..6ef535b 100644 --- a/scaffold/java/VERSION-DIFFS.md +++ b/scaffold/java/VERSION-DIFFS.md @@ -79,3 +79,35 @@ Java 编译期绑定签名。实测: 不走老版 `downloads.classifiers` 结构 ⇒ 两种形态都要认。 - 跳过音频资源可省 **3744/3911 条**(仅 167 条实际下载), 实测 1.21.1 全量 274 文件 / 200MB / 32s,0 失败。 + +## 补充 2:1.21 的 gameLoadFinished 门禁(第 13 处,编译期看不出) + +gen_1_21 曾经「启动成功但渲染 0 区块、画面停在 Mojang 加载屏」。 +根因在 `GameRenderer.render(DeltaTracker, boolean)` 内部(1.21 反编译): + +```java +boolean ok = this.minecraft.isGameLoadFinished(); +if (ok && renderLevel && this.minecraft.level != null) { + this.renderLevel(deltaTracker); // ← 只有 ok 才渲染世界 +} +``` + +而该标志只在 **`reloadResourcePacks()` 的回调**里被置 true +(`Minecraft.onResourceLoadFinished` 第 628 行)。 + +⇒ 我们不跑异步 reload(会死锁,见前文)⇒ 该位恒 false ⇒ 世界永不渲染。 +**1.20.x 没有这个门禁**,所以 gen_1_20 一直正常。 + +修法(在 Java 侧,符合「差异留在 Java」的原则): + +```java +// gen_1_21/net/minecraft/client/renderer/McfoldBridge.java +public static void markGameLoadFinished(Minecraft mc) { + Field f = Minecraft.class.getDeclaredField("gameLoadFinished"); + f.setAccessible(true); + f.setBoolean(mc, true); +} +``` + +★ 这类差异**只在上层失败**,不会崩、不会报错,只是画面停在加载屏 —— +所以判据要用「`chunksRendered` / `distinctColors`」,不能只看「进程活着」。 diff --git a/scaffold/java/gen_1_20/McfoldRenderServer.java b/scaffold/java/gen_1_20/McfoldRenderServer.java index 36f29da..31ccf6f 100644 --- a/scaffold/java/gen_1_20/McfoldRenderServer.java +++ b/scaffold/java/gen_1_20/McfoldRenderServer.java @@ -233,6 +233,8 @@ public class McfoldRenderServer { }); light.setLightEnabled(new ChunkPos(cx, cz), true); light.runLightUpdates(); + // ★ 必须触发网格重建 —— 否则渲染器不知道该 section 有数据(实测) + mc.levelRenderer.allChanged(); JsonObject r = new JsonObject(); r.addProperty("ok", true); r.addProperty("placed", placed); @@ -529,7 +531,9 @@ public class McfoldRenderServer { static void tickFrames(int n) { MainTarget target = (MainTarget) mc.getMainRenderTarget(); for (int f = 0; f < n; f++) { - try { mc.tick(); } catch (Throwable ignore) {} + try { mc.tick(); } catch (Throwable t) { + if (f == 0) { System.err.println("[mcfold] tick 异常(第1帧): " + t); t.printStackTrace(System.err); } + } RenderSystem.clear(16640, Minecraft.ON_OSX); target.bindWrite(true); mc.gameRenderer.render(0.0F, (long) f * 16_000_000L, true); diff --git a/scaffold/java/gen_1_21/McfoldRenderServer.java b/scaffold/java/gen_1_21/McfoldRenderServer.java index 04e6763..44570b3 100644 --- a/scaffold/java/gen_1_21/McfoldRenderServer.java +++ b/scaffold/java/gen_1_21/McfoldRenderServer.java @@ -233,6 +233,8 @@ public class McfoldRenderServer { }); light.setLightEnabled(new ChunkPos(cx, cz), true); light.runLightUpdates(); + // ★ 必须触发网格重建 —— 否则渲染器不知道该 section 有数据(实测) + mc.levelRenderer.allChanged(); JsonObject r = new JsonObject(); r.addProperty("ok", true); r.addProperty("placed", placed); @@ -438,6 +440,12 @@ public class McfoldRenderServer { var cc = (net.minecraft.client.multiplayer.ClientChunkCache) level.getChunkSource(); r.addProperty("loadedChunks", cc.getLoadedChunksCount()); r.addProperty("chunksRendered", mc.levelRenderer.countRenderedSections()); + r.addProperty("screenNow", mc.screen != null ? mc.screen.getClass().getSimpleName() : "null"); + r.addProperty("overlayNow", mc.getOverlay() != null ? mc.getOverlay().getClass().getSimpleName() : "null"); + r.addProperty("levelNow", mc.level != null); + r.addProperty("playerNow", mc.player != null); + r.addProperty("cameraEntity", mc.getCameraEntity() != null ? "set" : "null"); + r.addProperty("noRender", mc.noRender); r.addProperty("hasRenderedAllChunks", mc.levelRenderer.hasRenderedAllSections()); var got = cc.getChunk(pcx, pcz, net.minecraft.world.level.chunk.status.ChunkStatus.FULL, false); r.addProperty("chunkAtPlayer", got != null ? got.getClass().getSimpleName() : "null"); @@ -529,7 +537,9 @@ public class McfoldRenderServer { static void tickFrames(int n) { MainTarget target = (MainTarget) mc.getMainRenderTarget(); for (int f = 0; f < n; f++) { - try { mc.tick(); } catch (Throwable ignore) {} + try { mc.tick(); } catch (Throwable t) { + if (f == 0) { System.err.println("[mcfold] tick 异常(第1帧): " + t); t.printStackTrace(System.err); } + } RenderSystem.clear(16640, Minecraft.ON_OSX); target.bindWrite(true); // ★ 世代差异(1.21+):render(DeltaTracker, boolean) @@ -676,6 +686,17 @@ public class McfoldRenderServer { net.minecraft.client.renderer.McfoldBridge.reloadShaders(mc.gameRenderer, mc.getResourceManager()); + // ★★ 世代差异(1.21+,编译期看不出): + // GameRenderer.render(DeltaTracker,boolean) 里有门禁 + // boolean ok = minecraft.isGameLoadFinished(); + // if (ok && renderLevel && level != null) renderLevel(...); + // 而 gameLoadFinished 只在 **reloadResourcePacks 的回调** + // (onResourceLoadFinished)里被置 true。 + // 我们不跑异步 reload(会死锁)⇒ 该位恒 false ⇒ 世界永不渲染 + // (症状:画面停在 Mojang 加载屏,distinctColors≈199,chunksRendered=0)。 + // 1.20.x 无此门禁,所以 gen_1_20 不受影响。 + net.minecraft.client.renderer.McfoldBridge.markGameLoadFinished(mc); + mc.setOverlay(null); mc.setScreen(null); mc.options.setCameraType(net.minecraft.client.CameraType.THIRD_PERSON_BACK); diff --git a/scaffold/java/gen_1_21/net/minecraft/client/renderer/McfoldBridge.java b/scaffold/java/gen_1_21/net/minecraft/client/renderer/McfoldBridge.java index fff0d60..81d56c5 100644 --- a/scaffold/java/gen_1_21/net/minecraft/client/renderer/McfoldBridge.java +++ b/scaffold/java/gen_1_21/net/minecraft/client/renderer/McfoldBridge.java @@ -12,6 +12,26 @@ import net.minecraft.server.packs.resources.ResourceProvider; * loadEffect —— 官方由 onResourceManagerReload / 药水效果改 */ public class McfoldBridge { + /** + * 标志「游戏加载完成」。 + * + * ★ 1.21 新增的门禁:GameRenderer.render 里 + * boolean ok = minecraft.isGameLoadFinished(); + * if (ok && ...) renderLevel(...); + * 而该位只在 reloadResourcePacks 的回调里置 true(Minecraft 第 628 行)。 + * 我们不跑异步 reload ⇒ 必须手动置位。 + * 1.20.x 没有这个字段/方法,故本方法只存在于 gen_1_21 的 bridge。 + */ + public static void markGameLoadFinished(net.minecraft.client.Minecraft mc) { + try { + java.lang.reflect.Field f = net.minecraft.client.Minecraft.class + .getDeclaredField("gameLoadFinished"); + f.setAccessible(true); + f.setBoolean(mc, true); + } catch (Throwable t) { + throw new RuntimeException("无法置 gameLoadFinished(1.21 门禁)", t); + } + } public static void reloadShaders(GameRenderer gr, ResourceProvider provider) { gr.reloadShaders(provider); }