Expand material support and checkpoint the completed building toolkit

Expose the runtime block/item registry through compact material search and
single-material descriptions. Preserve private block-entity data through
checked edits and durable undo without sending payloads to model context.

Include the completed terrain tools, isolated world/map plugin, station lift,
ACP streaming and guidance fixes, local camera auto-connect, construction
scripts, and their public documentation, references and verification records.
Active station decoration and private runtime data remain outside this commit.

Validation: 145 Maven tests, 47 Bridge tests, successful camera Gradle build,
and isolated Paper verification of all 1,196 block defaults plus 5,392
independent property cases for placement, same-material edits and restoration.
This commit is contained in:
Emil
2026-09-13 16:59:13 +03:00
parent 0290559abf
commit 89c53d7058
179 changed files with 37719 additions and 162 deletions
@@ -0,0 +1,21 @@
package dev.minecraftbuilder.camera;
/** Opt-in loopback connection policy. No game or network dependencies. */
public final class AutoConnectPolicy {
private final String target;
private long nextAttempt;
private int attempts;
public AutoConnectPolicy(String target, long now) {
if (!target.matches("127\\.0\\.0\\.1:[0-9]{1,5}")) throw new IllegalArgumentException("Auto-connect requires a literal loopback address and port");
int port = Integer.parseInt(target.substring(target.indexOf(':')+1));
if (port < 1024 || port > 65535) throw new IllegalArgumentException("Invalid local server port");
this.target = target; nextAttempt = now + 10_000;
}
public String target() { return target; }
public int attempts() { return attempts; }
public boolean due(long now, boolean connected, boolean eligibleScreen, boolean paused) {
if (connected) { nextAttempt = now + 10_000; return false; }
if (paused || !eligibleScreen || now < nextAttempt) return false;
nextAttempt = now + 10_000; attempts++; return true;
}
}
@@ -10,6 +10,12 @@ import net.minecraft.client.Minecraft;
import net.minecraft.client.Screenshot;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.client.gui.screens.ConnectScreen;
import net.minecraft.client.gui.screens.DisconnectedScreen;
import net.minecraft.client.gui.screens.TitleScreen;
import net.minecraft.client.gui.screens.multiplayer.JoinMultiplayerScreen;
import net.minecraft.client.multiplayer.ServerData;
import net.minecraft.client.multiplayer.resolver.ServerAddress;
import net.minecraft.world.level.chunk.status.ChunkStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -42,6 +48,10 @@ public final class CameraClient implements ClientModInitializer, CameraHttpServe
.daemon(true).name("mcb-camera-watchdog").factory());
private volatile JsonObject cachedHealth = CameraHttpServer.error("starting", "Waiting for client tick");
private CameraHttpServer http;
private AutoConnectPolicy autoConnect;
private boolean autoConnectSuspended;
private boolean autoConnectPaused;
private long nextPauseCheck;
@Override public void onInitializeClient() {
String token = System.getenv("MCB_CAMERA_TOKEN");
@@ -55,6 +65,8 @@ public final class CameraClient implements ClientModInitializer, CameraHttpServe
int port = Integer.parseInt(System.getenv().getOrDefault("MCB_CAMERA_PORT", "8766"));
if (port < 1024 || port > 65535) throw new IllegalArgumentException("Invalid camera port");
http = new CameraHttpServer(port, token, this);
String target = System.getenv("MCB_CAMERA_AUTO_CONNECT");
if (target != null && !target.isBlank()) autoConnect = new AutoConnectPolicy(target, System.currentTimeMillis());
instance = this;
ClientTickEvents.END_CLIENT_TICK.register(this::tick);
ClientLifecycleEvents.CLIENT_STOPPING.register(this::stop);
@@ -93,12 +105,19 @@ public final class CameraClient implements ClientModInitializer, CameraHttpServe
}
private void tick(Minecraft client) {
autoConnect(client);
Job job = active.get();
JsonObject health = new JsonObject();
health.addProperty("status", "ok");
health.addProperty("connected", client.level != null && client.player != null);
health.addProperty("spectator", client.player != null && client.player.isSpectator());
health.addProperty("busy", job != null);
health.addProperty("autoConnectEnabled", autoConnect != null);
if (autoConnect != null) {
health.addProperty("autoConnectTarget", autoConnect.target());
health.addProperty("autoConnectAttempts", autoConnect.attempts());
health.addProperty("autoConnectPaused", autoConnectPaused || autoConnectSuspended);
}
health.addProperty("updatedAt", Instant.now().toString());
if (client.level != null) health.addProperty("dimension", dimension(client));
if (client.player != null) health.addProperty("playerId", client.player.getUUID().toString());
@@ -138,6 +157,26 @@ public final class CameraClient implements ClientModInitializer, CameraHttpServe
if (chunksLoaded(client)) job.stableTicks++; else { job.stableTicks = 0; job.readyFrames = 0; }
}
private void autoConnect(Minecraft client) {
if (autoConnect == null) return;
long now = System.currentTimeMillis();
if (now >= nextPauseCheck) {
autoConnectPaused = java.nio.file.Files.exists(client.gameDirectory.toPath().resolve("config/minecraft-builder-camera.autojoin-disabled"));
nextPauseCheck = now + 1000;
}
if (client.level != null) {
ServerData server = client.getCurrentServer();
if (server == null || !server.ip.equals(autoConnect.target())) autoConnectSuspended = true;
}
var screen = client.gui.screen();
boolean eligible = screen instanceof TitleScreen || screen instanceof DisconnectedScreen || screen instanceof JoinMultiplayerScreen;
if (!autoConnect.due(now, client.level != null || client.getConnection() != null,
eligible, autoConnectPaused || autoConnectSuspended || client.gui.overlay() != null)) return;
LOGGER.info("Observer connecting to configured local server (attempt {})", autoConnect.attempts());
ConnectScreen.startConnecting(new TitleScreen(), client, ServerAddress.parseString(autoConnect.target()),
new ServerData("Minecraft Builder local camera", autoConnect.target(), ServerData.Type.OTHER), false, null);
}
/** Called after GameRenderer.render, on Minecraft's render thread. Uses the supported GPU screenshot API. */
public static void afterRender(boolean renderWorld) {
CameraClient worker = instance;
@@ -0,0 +1,25 @@
package dev.minecraftbuilder.camera;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class AutoConnectPolicyTest {
@Test void onlyExplicitUnprivilegedLoopbackTargetsAreAccepted() {
assertEquals("127.0.0.1:25575", new AutoConnectPolicy("127.0.0.1:25575",0).target());
for(String target:new String[]{"example.com:25565","localhost:25575","127.0.0.1:80","127.0.0.1:99999","127.0.0.1:25575/","127.0.0.1:25575\n"})
assertThrows(IllegalArgumentException.class,()->new AutoConnectPolicy(target,0));
}
@Test void retriesAreThrottledAndNeverInterruptAConnectionOrOtherScreen() {
var policy=new AutoConnectPolicy("127.0.0.1:25575",0);
assertFalse(policy.due(9999,false,true,false));
assertTrue(policy.due(10000,false,true,false));
assertFalse(policy.due(10001,false,true,false));
assertFalse(policy.due(20000,false,false,false));
assertFalse(policy.due(20000,false,true,true));
assertTrue(policy.due(20000,false,true,false));
assertFalse(policy.due(40000,true,true,false));
assertFalse(policy.due(40001,false,true,false));
assertTrue(policy.due(50000,false,true,false));
assertEquals(3,policy.attempts());
}
}