Erinaldordso commited on
Commit
407b4fa
·
verified ·
1 Parent(s): 8f6b7b7

Prevent Naldo shader startup crash

Browse files

Use vanilla autojoin after resource loading, gate manual reconnect by mapped overlay and safe screens, skip first-run onboarding. Rebuilt Java17 bridge 1.0.3 and 61 targeted tests pass.

altoclef/SpaceBridge.java CHANGED
@@ -27,6 +27,12 @@ import java.util.concurrent.atomic.AtomicBoolean;
27
  * scheduler tick itself (bridge alive), while {@code observed_at} is refreshed
28
  * only by a snapshot that actually ran on the client thread. At most one
29
  * snapshot is ever queued, so rendering load cannot build a backlog.
 
 
 
 
 
 
30
  */
31
  public final class SpaceBridge implements ClientModInitializer {
32
  private static final MappingResolver MAP = FabricLoader.getInstance().getMappingResolver();
@@ -35,6 +41,9 @@ public final class SpaceBridge implements ClientModInitializer {
35
  private volatile String lastCommand = "";
36
  private final AtomicBoolean snapshotPending = new AtomicBoolean(false);
37
  private long connectAttempt;
 
 
 
38
  private long creditsSince;
39
  private boolean creditsObserved;
40
  private boolean enteredEnd;
@@ -179,9 +188,38 @@ public final class SpaceBridge implements ClientModInitializer {
179
  System.out.println("[space-altoclef] local bridge ready on 127.0.0.1:" + port);
180
  } catch (Exception err) { throw new RuntimeException("Space bridge failed", err); }
181
  }
182
- private void connect(Object client) throws Exception {
183
- if (System.currentTimeMillis() - connectAttempt < 30000) return;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
184
  connectAttempt = System.currentTimeMillis();
 
185
  String address = System.getenv().getOrDefault("ALTOCLEF_SERVER", "127.0.0.1:25568");
186
  Object parsed = method(null, "class_639", "method_2950", "(Ljava/lang/String;)Lnet/minecraft/class_639;",
187
  new Class<?>[]{String.class}, address);
@@ -191,7 +229,16 @@ public final class SpaceBridge implements ClientModInitializer {
191
  "(Lnet/minecraft/class_437;Lnet/minecraft/class_310;Lnet/minecraft/class_639;Lnet/minecraft/class_642;)V",
192
  new Class<?>[]{mc("class_437"), mc("class_310"), mc("class_639"), mc("class_642")},
193
  screen(client), client, parsed, info);
194
- System.out.println("[space-altoclef] connecting to " + address);
 
 
 
 
 
 
 
 
 
195
  }
196
  private String currentDimension() throws Exception {
197
  // Upstream WorldHelper.getCurrentDimension is package-private static;
@@ -224,8 +271,10 @@ public final class SpaceBridge implements ClientModInitializer {
224
  data.put("credits_screen", credits);
225
  data.put("credits_observed", creditsObserved);
226
  data.put("entered_end", enteredEnd);
 
 
227
  if (player == null) {
228
- if (executor != null && !credits) connect(client);
229
  } else {
230
  String dimension = currentDimension();
231
  if ("END".equals(dimension)) enteredEnd = true;
 
27
  * scheduler tick itself (bridge alive), while {@code observed_at} is refreshed
28
  * only by a snapshot that actually ran on the client thread. At most one
29
  * snapshot is ever queued, so rendering load cannot build a backlog.
30
+ *
31
+ * <p>Connect contract: primary autojoin is vanilla {@code --server/--port},
32
+ * which sequences itself after resource/shader load. The manual fallback in
33
+ * this bridge only fires on a Title/Disconnected screen with no resource
34
+ * reload overlay (connecting mid-reload NPEs WorldRenderer via
35
+ * ShaderProgram.addSampler), on the client thread only.
36
  */
37
  public final class SpaceBridge implements ClientModInitializer {
38
  private static final MappingResolver MAP = FabricLoader.getInstance().getMappingResolver();
 
41
  private volatile String lastCommand = "";
42
  private final AtomicBoolean snapshotPending = new AtomicBoolean(false);
43
  private long connectAttempt;
44
+ private long connectAttempts;
45
+ private volatile String connectState = "startup";
46
+ private String lastGate = "";
47
  private long creditsSince;
48
  private boolean creditsObserved;
49
  private boolean enteredEnd;
 
188
  System.out.println("[space-altoclef] local bridge ready on 127.0.0.1:" + port);
189
  } catch (Exception err) { throw new RuntimeException("Space bridge failed", err); }
190
  }
191
+ private void connect(Object client, Object currentScreen) throws Exception {
192
+ // Gated manual connect: NEVER while a resource/shader reload overlay
193
+ // is up (that NPEs WorldRenderer via ShaderProgram.addSampler on a
194
+ // half-built reload), and never while vanilla is already connecting.
195
+ // Primary autojoin is vanilla --server/--port; this is only the
196
+ // safe-reconnect fallback for Title/Disconnected screens.
197
+ // Pinned Yarn 1.19.4+build.2: METHOD emh ()Lesx; aJ method_18506 getOverlay
198
+ // (owner class_310 MinecraftClient, returns class_4071 Overlay).
199
+ Object overlay = method(client, "class_310", "method_18506", "()Lnet/minecraft/class_4071;",
200
+ new Class<?>[0]);
201
+ boolean title = currentScreen != null
202
+ && (mc("class_442").isInstance(currentScreen) || mc("class_419").isInstance(currentScreen));
203
+ boolean connecting = currentScreen != null && mc("class_412").isInstance(currentScreen);
204
+ String screenName = currentScreen == null ? "<none>" : currentScreen.getClass().getName();
205
+ if (overlay != null) {
206
+ gate("waiting resource reload (overlay=" + overlay.getClass().getName() + ")");
207
+ return;
208
+ }
209
+ if (connecting) {
210
+ connectState = "connecting (vanilla ConnectScreen)";
211
+ return;
212
+ }
213
+ if (!title) {
214
+ gate("waiting title screen (screen=" + screenName + ")");
215
+ return;
216
+ }
217
+ if (System.currentTimeMillis() - connectAttempt < 30000) {
218
+ connectState = "retry waits 30s throttle (screen=" + screenName + ")";
219
+ return;
220
+ }
221
  connectAttempt = System.currentTimeMillis();
222
+ connectAttempts++;
223
  String address = System.getenv().getOrDefault("ALTOCLEF_SERVER", "127.0.0.1:25568");
224
  Object parsed = method(null, "class_639", "method_2950", "(Ljava/lang/String;)Lnet/minecraft/class_639;",
225
  new Class<?>[]{String.class}, address);
 
229
  "(Lnet/minecraft/class_437;Lnet/minecraft/class_310;Lnet/minecraft/class_639;Lnet/minecraft/class_642;)V",
230
  new Class<?>[]{mc("class_437"), mc("class_310"), mc("class_639"), mc("class_642")},
231
  screen(client), client, parsed, info);
232
+ connectState = "connecting to " + address + " (attempt " + connectAttempts + ")";
233
+ System.out.println("[space-altoclef] connecting to " + address + " (attempt " + connectAttempts + ")");
234
+ }
235
+ /** Throttled gate diagnostic: log only when the reason changes. */
236
+ private void gate(String reason) {
237
+ connectState = reason;
238
+ if (!reason.equals(lastGate)) {
239
+ lastGate = reason;
240
+ System.out.println("[space-altoclef] connect gated: " + reason);
241
+ }
242
  }
243
  private String currentDimension() throws Exception {
244
  // Upstream WorldHelper.getCurrentDimension is package-private static;
 
271
  data.put("credits_screen", credits);
272
  data.put("credits_observed", creditsObserved);
273
  data.put("entered_end", enteredEnd);
274
+ data.put("connect_state", connectState);
275
+ data.put("connect_attempts", connectAttempts);
276
  if (player == null) {
277
+ if (executor != null && !credits) connect(client, currentScreen);
278
  } else {
279
  String dimension = currentDimension();
280
  if ("END".equals(dimension)) enteredEnd = true;
altoclef/build.py CHANGED
@@ -52,7 +52,7 @@ def build():
52
  ],
53
  check=True,
54
  )
55
- metadata = {"schemaVersion": 1, "id": "space_altoclef_bridge", "version": "1.0.2",
56
  "name": "Space AltoClef Bridge", "environment": "client",
57
  "entrypoints": {"client": ["space.minecraft.SpaceBridge"]},
58
  "depends": {"fabricloader": ">=0.16.2", "minecraft": "1.19.4",
 
52
  ],
53
  check=True,
54
  )
55
+ metadata = {"schemaVersion": 1, "id": "space_altoclef_bridge", "version": "1.0.3",
56
  "name": "Space AltoClef Bridge", "environment": "client",
57
  "entrypoints": {"client": ["space.minecraft.SpaceBridge"]},
58
  "depends": {"fabricloader": ">=0.16.2", "minecraft": "1.19.4",
altoclef/space-altoclef-bridge.jar CHANGED
Binary files a/altoclef/space-altoclef-bridge.jar and b/altoclef/space-altoclef-bridge.jar differ
 
altoclef_stack.py CHANGED
@@ -152,10 +152,17 @@ class AltoClefStack:
152
  shutil.copyfile(MOD_CACHE / "fabric-api-0.87.2+1.19.4.jar", mods / "fabric-api-0.87.2+1.19.4.jar")
153
  shutil.copyfile(SOURCE / "altoclef/space-altoclef-bridge.jar", mods / "space-altoclef-bridge.jar")
154
  settings = CLIENT / "options.txt"
 
 
 
 
 
 
155
  if not settings.exists():
156
- settings.write_text("maxFps:20\nrenderDistance:4\nsimulationDistance:4\nentityDistanceScaling:0.5\n"
157
- "graphicsMode:0\nparticles:2\nsoundCategory_master:0.0\nuseVbo:true\n"
158
- "pauseOnLostFocus:false\nfullscreen:false\nshowSubtitles:false\n")
 
159
  config_dir = CLIENT / "altoclef/configs"
160
  config_dir.mkdir(parents=True, exist_ok=True)
161
  config = config_dir / "beat_minecraft.json"
@@ -164,10 +171,16 @@ class AltoClefStack:
164
  "placeSpawnNearEndPortal":False,"requiredBeds":10}))
165
  # Match Java's offline UUID algorithm without changing server auth.
166
  player_uuid = str(uuid.UUID(bytes=hashlib.md5(("OfflinePlayer:" + USERNAME).encode()).digest(), version=3))
 
 
 
 
 
167
  return launcher.command.get_minecraft_command(version, str(CLIENT), {
168
  "username": USERNAME, "uuid": player_uuid, "token": "0",
169
  "executablePath": str(java), "jvmArguments": ["-Xms256M", "-Xmx2G", "-XX:ActiveProcessorCount=2"],
170
  "customResolution":True, "resolutionWidth":"640", "resolutionHeight":"360",
 
171
  })
172
 
173
  def _loading_grace(self) -> bool:
 
152
  shutil.copyfile(MOD_CACHE / "fabric-api-0.87.2+1.19.4.jar", mods / "fabric-api-0.87.2+1.19.4.jar")
153
  shutil.copyfile(SOURCE / "altoclef/space-altoclef-bridge.jar", mods / "space-altoclef-bridge.jar")
154
  settings = CLIENT / "options.txt"
155
+ defaults = ("maxFps:20\nrenderDistance:4\nsimulationDistance:4\nentityDistanceScaling:0.5\n"
156
+ "graphicsMode:0\nparticles:2\nsoundCategory_master:0.0\nuseVbo:true\n"
157
+ "pauseOnLostFocus:false\nfullscreen:false\nshowSubtitles:false\n"
158
+ # 1.19.4 first-run accessibility onboarding would park the
159
+ # headless client on a setup screen; skip it when present.
160
+ "onboardAccessibility:false\n")
161
  if not settings.exists():
162
+ settings.write_text(defaults)
163
+ elif "onboardAccessibility" not in settings.read_text(errors="replace"):
164
+ with settings.open("a", encoding="utf-8") as out:
165
+ out.write("onboardAccessibility:false\n")
166
  config_dir = CLIENT / "altoclef/configs"
167
  config_dir.mkdir(parents=True, exist_ok=True)
168
  config = config_dir / "beat_minecraft.json"
 
171
  "placeSpawnNearEndPortal":False,"requiredBeds":10}))
172
  # Match Java's offline UUID algorithm without changing server auth.
173
  player_uuid = str(uuid.UUID(bytes=hashlib.md5(("OfflinePlayer:" + USERNAME).encode()).digest(), version=3))
174
+ # Primary autojoin is vanilla --server/--port (supported by official
175
+ # 1.19.4 and minecraft_launcher_lib 7.1): vanilla sequences the connect
176
+ # itself after resource/shader load, so the bridge never has to force
177
+ # a connect mid-reload. The bridge keeps only a readiness-gated
178
+ # safe-reconnect fallback for Title/Disconnected screens.
179
  return launcher.command.get_minecraft_command(version, str(CLIENT), {
180
  "username": USERNAME, "uuid": player_uuid, "token": "0",
181
  "executablePath": str(java), "jvmArguments": ["-Xms256M", "-Xmx2G", "-XX:ActiveProcessorCount=2"],
182
  "customResolution":True, "resolutionWidth":"640", "resolutionHeight":"360",
183
+ "server": "127.0.0.1", "port": "25568",
184
  })
185
 
186
  def _loading_grace(self) -> bool:
tests/test_altoclef_stack.py CHANGED
@@ -88,6 +88,64 @@ def test_boot_resets_launch_clocks_before_startup():
88
  assert "_reset_launch_clocks()" in source
89
  assert source.index("_reset_launch_clocks()") < source.index("minecraft_ready.wait")
90
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
  def test_survival_verified_requires_explicit_survival():
92
  good = {"connected": True, "error": "", "creative": False}
93
  assert module.AltoClefStack._survival_verified(good) is True
 
88
  assert "_reset_launch_clocks()" in source
89
  assert source.index("_reset_launch_clocks()") < source.index("minecraft_ready.wait")
90
 
91
+ def _bridge_source() -> str:
92
+ return (Path(module.__file__).parents[0] / "altoclef" / "SpaceBridge.java").read_text(encoding="utf-8")
93
+
94
+ def test_bridge_gates_manual_connect_on_readiness():
95
+ # Pinned Yarn 1.19.4+build.2: TitleScreen class_442, DisconnectedScreen
96
+ # class_419, ConnectScreen class_412, overlay via getOverlay. The manual
97
+ # connect must take the current screen and refuse while a reload overlay
98
+ # is up or vanilla is already connecting.
99
+ source = _bridge_source()
100
+ assert "class_442" in source and "class_419" in source
101
+ # Pinned Yarn 1.19.4+build.2 row: METHOD emh ()Lesx; aJ method_18506
102
+ # getOverlay. The live runtime is intermediary, so the mapped ID (not the
103
+ # Yarn name) must be used.
104
+ assert "method_18506" in source
105
+ assert '"getOverlay"' not in source
106
+ assert "connect(client, currentScreen)" in source
107
+ assert "mc(\"class_412\").isInstance(currentScreen)" in source
108
+ import re
109
+ assert not re.search(r"(?<!, )connect\(client\)", source), \
110
+ "ungated single-arg connect call remains"
111
+
112
+ def test_bridge_reports_connect_diagnostics():
113
+ source = _bridge_source()
114
+ assert '"connect_state"' in source and '"connect_attempts"' in source
115
+
116
+ def test_built_bridge_jar_matches_source_version():
117
+ import json as _json
118
+ import zipfile as _zip
119
+ jar = Path(module.__file__).parents[0] / "altoclef" / "space-altoclef-bridge.jar"
120
+ meta = _json.loads(_zip.ZipFile(jar).read("fabric.mod.json").decode())
121
+ assert meta["version"] == "1.0.3"
122
+ assert "space/minecraft/SpaceBridge.class" in _zip.ZipFile(jar).namelist()
123
+
124
+ def test_client_uses_vanilla_autojoin_and_skips_onboarding(runner, monkeypatch, tmp_path):
125
+ import sys as _sys
126
+ seen: dict = {}
127
+ fake_launcher = SimpleNamespace(
128
+ fabric=SimpleNamespace(install_fabric=lambda *a, **k: None),
129
+ command=SimpleNamespace(
130
+ get_minecraft_command=lambda *a, **k: (seen.update(a[2]), ["java"])[1]),
131
+ )
132
+ monkeypatch.setitem(_sys.modules, "minecraft_launcher_lib", fake_launcher)
133
+ monkeypatch.setattr(module, "CLIENT", tmp_path / "client")
134
+ monkeypatch.setattr(module, "MOD_CACHE", tmp_path / "cache")
135
+ monkeypatch.setattr(module, "_download",
136
+ lambda url, path, sha=None: (path.parent.mkdir(parents=True, exist_ok=True),
137
+ path.write_bytes(b"x"))[1])
138
+ assert runner._ensure_client(Path("java")) == ["java"]
139
+ # Vanilla --server/--port autojoin via ViaProxy; bridge keeps only fallback.
140
+ assert seen.get("server") == "127.0.0.1"
141
+ assert seen.get("port") == "25568"
142
+ options = (tmp_path / "client" / "options.txt").read_text(encoding="utf-8")
143
+ assert "onboardAccessibility:false" in options
144
+ # Second ensure must not duplicate the key.
145
+ runner._ensure_client(Path("java"))
146
+ options = (tmp_path / "client" / "options.txt").read_text(encoding="utf-8")
147
+ assert options.count("onboardAccessibility:false") == 1
148
+
149
  def test_survival_verified_requires_explicit_survival():
150
  good = {"connected": True, "error": "", "creative": False}
151
  assert module.AltoClefStack._survival_verified(good) is True