Threading contract: every read or mutation of Minecraft client state
* ({@link #snapshot}, {@link #connect}, AltoClef commands) runs ONLY on the
* Minecraft client thread, reached through {@code Executor} dispatch (the
* client implements {@code java.util.concurrent.Executor}) with a reflective
* {@code execute(Runnable)} fallback. Dispatch is never run inline on a
* telemetry or HTTP thread: a failed dispatch records a diagnostic and the
* bridge keeps reporting a loading/error heartbeat WITHOUT claiming fresh
* connected world observations.
*
*
Freshness contract for /state: {@code heartbeat_at} is refreshed by the
* scheduler tick itself (bridge alive), while {@code observed_at} is refreshed
* only by a snapshot that actually ran on the client thread. At most one
* snapshot is ever queued, so rendering load cannot build a backlog.
*
*
Connect contract: the client starts at the normal TitleScreen (no
* {@code --server/--port}, which would bypass it and leave upstream
* uninitialized) and this bridge connects only on a Title/Disconnected
* screen with no resource reload overlay (connecting mid-reload NPEs
* WorldRenderer via ShaderProgram.addSampler), on the client thread only.
*/
public final class SpaceBridge implements ClientModInitializer {
private static final MappingResolver MAP = FabricLoader.getInstance().getMappingResolver();
private volatile String state = "{\"connected\":false,\"loading\":true,\"heartbeat_at\":0}";
private volatile String lastError = "";
private volatile String lastCommand = "";
private final AtomicBoolean snapshotPending = new AtomicBoolean(false);
private long connectAttempt;
private long connectAttempts;
private volatile String connectState = "startup";
private String lastGate = "";
private long creditsSince;
private boolean creditsObserved;
private boolean enteredEnd;
private String previousDimension = "";
private static Class> mc(String intermediary) throws Exception {
return Class.forName(MAP.mapClassName("intermediary", "net.minecraft." + intermediary));
}
private static Method accessibleMethod(Class> owner, String name, Class>[] types) throws Exception {
try {
return owner.getMethod(name, types);
} catch (NoSuchMethodException missing) {
Method fallback = owner.getDeclaredMethod(name, types);
fallback.setAccessible(true);
return fallback;
}
}
private static Object method(Object obj, String owner, String id, String desc,
Class>[] types, Object... args) throws Exception {
String name = MAP.mapMethodName("intermediary", "net.minecraft." + owner, id, desc);
return accessibleMethod(mc(owner), name, types).invoke(obj, args);
}
private static Object call(Object obj, String name, Class>[] types, Object... args) throws Exception {
return accessibleMethod(obj.getClass(), name, types).invoke(obj, args);
}
private static Object call(Object obj, String name) throws Exception {
return call(obj, name, new Class>[0]);
}
private static Object client() throws Exception {
return method(null, "class_310", "method_1551", "()Lnet/minecraft/class_310;", new Class>[0]);
}
/** Enqueue on the client thread. Never runs the task inline: throws on failure. */
private static void dispatchToClient(Object client, Runnable task) throws Exception {
if (client instanceof Executor) {
((Executor) client).execute(task);
return;
}
accessibleMethod(client.getClass(), "execute", new Class>[]{Runnable.class}).invoke(client, task);
}
private static Object screen(Object client) throws Exception {
String name = MAP.mapFieldName("intermediary", "net.minecraft.class_310", "field_1755", "Lnet/minecraft/class_437;");
try {
return mc("class_310").getField(name).get(client);
} catch (NoSuchFieldException missing) {
Field fallback = mc("class_310").getDeclaredField(name);
fallback.setAccessible(true);
return fallback.get(client);
}
}
private static void respond(HttpExchange ex, int code, String body) throws Exception {
byte[] data = body.getBytes(StandardCharsets.UTF_8);
ex.getResponseHeaders().set("Content-Type", "application/json; charset=utf-8");
ex.sendResponseHeaders(code, data.length);
ex.getResponseBody().write(data);
ex.close();
}
/** Loading/error heartbeat: fresh heartbeat_at, no connected observations claimed. */
private void heartbeat(String detail) {
Map data = new LinkedHashMap<>();
data.put("connected", false);
data.put("loading", true);
data.put("heartbeat_at", System.currentTimeMillis() / 1000.0);
data.put("dispatch_error", detail == null ? "" : detail);
data.put("error", lastError);
data.put("last_command", lastCommand);
state = json(data);
}
private void queueSnapshot() {
// At most one pending snapshot: skip the tick when the client thread
// has not drained the previous one (e.g. under rendering load).
if (!snapshotPending.compareAndSet(false, true)) {
heartbeat("snapshot still queued on client thread");
return;
}
boolean queued = false;
try {
dispatchToClient(client(), () -> {
try {
snapshot();
} finally {
snapshotPending.set(false);
}
});
queued = true;
} catch (Exception err) {
lastError = "client dispatch failed: " + err;
} finally {
if (!queued) {
snapshotPending.set(false);
heartbeat(lastError);
}
}
}
@Override public void onInitializeClient() {
try {
int port = Integer.parseInt(System.getenv().getOrDefault("ALTOCLEF_BRIDGE_PORT", "3002"));
HttpServer server = HttpServer.create(new InetSocketAddress("127.0.0.1", port), 8);
server.createContext("/state", ex -> {
try { respond(ex, 200, state); } catch (Exception ignored) { ex.close(); }
});
server.createContext("/command", ex -> {
try {
if (!"POST".equals(ex.getRequestMethod())) { respond(ex, 405, "{}"); return; }
String command = new String(ex.getRequestBody().readNBytes(128), StandardCharsets.UTF_8).trim();
if (!Set.of("gamer", "stop", "status").contains(command)) {
respond(ex, 400, "{\"error\":\"unsupported command\"}"); return;
}
try {
dispatchToClient(client(), () -> {
try {
Class> alto = Class.forName("adris.altoclef.AltoClef");
Object executor = alto.getMethod("getCommandExecutor").invoke(null);
if (executor == null) throw new IllegalStateException("AltoClef is not initialized");
executor.getClass().getMethod("executeWithPrefix", String.class).invoke(executor, command);
lastCommand = command;
lastError = "";
System.out.println("[space-altoclef] command=" + command);
} catch (Exception err) { lastError = err.toString(); }
});
} catch (Exception err) {
lastError = "command dispatch failed: " + err;
respond(ex, 503, json(Map.of("error", lastError)));
return;
}
respond(ex, 202, "{\"accepted\":true}");
} catch (Exception err) {
try { respond(ex, 503, json(Map.of("error", err.toString()))); } catch (Exception ignored) { ex.close(); }
}
});
server.setExecutor(Executors.newFixedThreadPool(2));
server.start();
Executors.newSingleThreadScheduledExecutor(r -> {
Thread t = new Thread(r, "space-altoclef-telemetry"); t.setDaemon(true); return t;
}).scheduleWithFixedDelay(() -> {
try {
queueSnapshot();
} catch (Throwable err) {
lastError = "telemetry scheduler: " + err;
heartbeat(lastError);
}
}, 2, 1, TimeUnit.SECONDS);
System.out.println("[space-altoclef] local bridge ready on 127.0.0.1:" + port);
} catch (Exception err) { throw new RuntimeException("Space bridge failed", err); }
}
private void connect(Object client, Object currentScreen) throws Exception {
// Gated connect: NEVER while a resource/shader reload overlay
// is up (that NPEs WorldRenderer via ShaderProgram.addSampler on a
// half-built reload), never while vanilla is already connecting, and
// never before upstream initialized on the TitleScreen.
// Pinned Yarn 1.19.4+build.2: METHOD emh ()Lesx; aJ method_18506 getOverlay
// (owner class_310 MinecraftClient, returns class_4071 Overlay).
Object overlay = method(client, "class_310", "method_18506", "()Lnet/minecraft/class_4071;",
new Class>[0]);
boolean title = currentScreen != null
&& (mc("class_442").isInstance(currentScreen) || mc("class_419").isInstance(currentScreen));
boolean connecting = currentScreen != null && mc("class_412").isInstance(currentScreen);
String screenName = currentScreen == null ? "" : currentScreen.getClass().getName();
if (overlay != null) {
gate("waiting resource reload (overlay=" + overlay.getClass().getName() + ")");
return;
}
if (connecting) {
connectState = "connecting (vanilla ConnectScreen)";
return;
}
if (!title) {
gate("waiting title screen (screen=" + screenName + ")");
return;
}
if (System.currentTimeMillis() - connectAttempt < 30000) {
connectState = "retry waits 30s throttle (screen=" + screenName + ")";
return;
}
connectAttempt = System.currentTimeMillis();
connectAttempts++;
String address = System.getenv().getOrDefault("ALTOCLEF_SERVER", "127.0.0.1:25568");
Object parsed = method(null, "class_639", "method_2950", "(Ljava/lang/String;)Lnet/minecraft/class_639;",
new Class>[]{String.class}, address);
Object info = mc("class_642").getConstructor(String.class, String.class, boolean.class)
.newInstance("Minecraft Friends", address, false);
method(null, "class_412", "method_36877",
"(Lnet/minecraft/class_437;Lnet/minecraft/class_310;Lnet/minecraft/class_639;Lnet/minecraft/class_642;)V",
new Class>[]{mc("class_437"), mc("class_310"), mc("class_639"), mc("class_642")},
screen(client), client, parsed, info);
connectState = "connecting to " + address + " (attempt " + connectAttempts + ")";
System.out.println("[space-altoclef] connecting to " + address + " (attempt " + connectAttempts + ")");
}
/** Throttled gate diagnostic: log only when the reason changes. */
private void gate(String reason) {
connectState = reason;
if (!reason.equals(lastGate)) {
lastGate = reason;
System.out.println("[space-altoclef] connect gated: " + reason);
}
}
private String currentDimension() throws Exception {
// Upstream WorldHelper.getCurrentDimension is package-private static;
// resolve it as a declared method so snapshots survive visibility.
Class> helper = Class.forName("adris.altoclef.util.helpers.WorldHelper");
return String.valueOf(accessibleMethod(helper, "getCurrentDimension", new Class>[0]).invoke(null));
}
/** Runs ONLY on the Minecraft client thread. Refreshes observed_at. */
private void snapshot() {
Map data = new LinkedHashMap<>();
try {
Object client = client();
Class> alto = Class.forName("adris.altoclef.AltoClef");
Object mod = alto.getMethod("getInstance").invoke(null);
Object executor = alto.getMethod("getCommandExecutor").invoke(null);
Object player = mod == null ? null : call(mod, "getPlayer");
data.put("connected", player != null);
data.put("initialized", executor != null);
data.put("last_command", lastCommand);
data.put("error", lastError);
Object currentScreen = screen(client);
boolean credits = currentScreen != null && mc("class_445").isInstance(currentScreen);
if (credits) {
if (enteredEnd) creditsObserved = true;
if (creditsSince == 0) creditsSince = System.currentTimeMillis();
if (System.currentTimeMillis() - creditsSince > 12000) {
method(currentScreen, "class_437", "method_25419", "()V", new Class>[0]);
}
}
data.put("credits_screen", credits);
data.put("credits_observed", creditsObserved);
data.put("entered_end", enteredEnd);
data.put("connect_state", connectState);
data.put("connect_attempts", connectAttempts);
if (player == null) {
if (executor != null && !credits) connect(client, currentScreen);
} else {
String dimension = currentDimension();
if ("END".equals(dimension)) enteredEnd = true;
data.put("dimension", dimension);
data.put("returned_from_end", enteredEnd && "OVERWORLD".equals(dimension));
if (!dimension.equals(previousDimension)) {
System.out.println("[space-altoclef] dimension=" + dimension);
previousDimension = dimension;
}
data.put("position", Map.of("x", method(player,"class_1297","method_23317","()D",new Class>[0]),
"y", method(player,"class_1297","method_23318","()D",new Class>[0]),
"z", method(player,"class_1297","method_23321","()D",new Class>[0])));
data.put("health", method(player,"class_1309","method_6032","()F",new Class>[0]));
Object controller = call(mod, "getController");
data.put("creative", method(controller,"class_636","method_2914","()Z",new Class>[0]));
Object inventory = method(player,"class_1657","method_31548","()Lnet/minecraft/class_1661;",new Class>[0]);
List