package dev.modzuozhi; import dev.modzuozhi.core.dimthread.DimThreadCore; import net.minecraft.core.BlockPos; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.resources.ResourceLocation; import net.neoforged.bus.api.SubscribeEvent; import net.neoforged.fml.common.EventBusSubscriber; import net.neoforged.neoforge.event.tick.ServerTickEvent; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; /** Local opt-in fixture. Removing the CC safe phase must fail the real identity check. */ @EventBusSubscriber(modid = "dimensionalripper") public final class OwnershipRegression { private static boolean ran; private static boolean verified; private static long worldTime; @SubscribeEvent public static void tick(ServerTickEvent.Post event) throws Exception { var server = event.getServer(); if (verified && server.getTickCount() >= 80) { DimThreadCore.MANAGER.setPaused(server, true); if (server.overworld().getGameTime() <= worldTime) throw new AssertionError("Parallel worlds did not resume"); ModZuozhi.LOGGER.info("OWNERSHIP RESUME PASS: world continued ticking after compatibility phases"); verified = false; server.halt(false); } if (ran || server.getTickCount() < 20) return; ran = true; var level = server.overworld(); var manager = DimThreadCore.MANAGER; var release = new CountDownLatch(1); float originalRate = server.tickRateManager().tickrate(); try { manager.setPaused(server, true); level.setChunkForced(0, 0, true); level.getChunk(0, 0); var pos = new BlockPos(8, 110, 8); var block = BuiltInRegistries.BLOCK.get(ResourceLocation.parse("computercraft:monitor_normal")); level.setBlockAndUpdate(pos, block.defaultBlockState()); var monitor = level.getBlockEntity(pos); if (monitor == null) throw new AssertionError("Monitor fixture not installed"); var scheduler = Class.forName("dan200.computercraft.shared.util.TickScheduler"); var tokenClass = Class.forName("dan200.computercraft.shared.util.TickScheduler$Token"); var token = tokenClass.getConstructor(net.minecraft.world.level.block.entity.BlockEntity.class).newInstance(monitor); scheduler.getMethod("schedule", tokenClass).invoke(null, token); var entered = new CountDownLatch(1); var bridgeFailure = new java.util.concurrent.atomic.AtomicReference(); manager.getLoop(level).execute(() -> { // A Sable-like main-thread dependency: safe waiting must pump this callback. server.execute(release::countDown); entered.countDown(); try { if (!release.await(10, TimeUnit.SECONDS)) throw new AssertionError("Main callback was not pumped"); dev.modzuozhi.core.compat.SableMainThreadBridge.runForLevel(level, () -> { if (!server.isSameThread() || level.getBlockEntity(pos) != monitor) throw new AssertionError("Bridge did not obtain main-thread world ownership"); }); } catch (Throwable e) { bridgeFailure.set(e); } }); // Amplify idle polling waste: stopping an idle poll must not wait a 1-second tick budget. server.tickRateManager().setTickRate(1.0F); manager.setPaused(server, false); if (!entered.await(10, TimeUnit.SECONDS)) throw new AssertionError("Worker did not start"); if (level.getBlockEntity(pos) != null) throw new AssertionError("Did not reproduce guarded main-thread lookup"); for (int i = 0; i < 1000; i++) level.getBlockEntity(pos); long start = System.nanoTime(); scheduler.getMethod("tick").invoke(null); server.tickRateManager().setTickRate(originalRate); if (System.nanoTime() - start > TimeUnit.MILLISECONDS.toNanos(750)) throw new AssertionError("Safe phase waited out idle polling budget after pause request"); if (bridgeFailure.get() != null) throw new AssertionError("Sable bridge failed", bridgeFailure.get()); ModZuozhi.LOGGER.info("CC PHASE MEASUREMENT: {}", dev.modzuozhi.core.compat.ThreadOwnershipDiagnostics.timing()); manager.setPaused(server, true); if (level.getBlockEntity(pos) != monitor) throw new AssertionError("Monitor identity changed"); var state = tokenClass.getDeclaredField("$state"); state.setAccessible(true); if (!state.get(token).toString().equals("IDLE")) throw new AssertionError("CC token was not completed"); // The same scope must preserve an existing pause, even when nested or throwing. var expected = new IllegalArgumentException("regression sentinel"); try { manager.runMainThreadPhase(server, "outer-test", () -> manager.runMainThreadPhase(server, "inner-test", () -> { throw expected; })); throw new AssertionError("Phase swallowed exception"); } catch (IllegalArgumentException failure) { if (failure != expected) throw failure; } if (!manager.getLoop(level).isPaused()) throw new AssertionError("Existing pause was lost"); manager.setPaused(server, false); try { manager.runMainThreadPhase(server, "exception-resume-test", () -> { throw expected; }); throw new AssertionError("Running phase swallowed exception"); } catch (IllegalArgumentException failure) { if (failure != expected) throw failure; } if (manager.getLoop(level).isPaused()) throw new AssertionError("Failed phase did not resume running world"); manager.setPaused(server, true); // Removed owners are valid stale tokens. Real identity mismatches must still throw. var removed = net.minecraft.world.level.block.entity.BlockEntityType.CHEST.create(pos, net.minecraft.world.level.block.Blocks.CHEST.defaultBlockState()); removed.setLevel(level); var removedToken = tokenClass.getConstructor(net.minecraft.world.level.block.entity.BlockEntity.class).newInstance(removed); scheduler.getMethod("schedule", tokenClass).invoke(null, removedToken); removed.setRemoved(); scheduler.getMethod("tick").invoke(null); if (!state.get(removedToken).toString().equals("IDLE")) throw new AssertionError("Removed token not discarded"); removed.clearRemoved(); scheduler.getMethod("schedule", tokenClass).invoke(null, removedToken); try { scheduler.getMethod("tick").invoke(null); throw new AssertionError("Real mismatch was hidden"); } catch (java.lang.reflect.InvocationTargetException failure) { if (!(failure.getCause() instanceof IllegalStateException)) throw failure; } var far = new BlockPos(1600000, 100, 1600000); var unloaded = net.minecraft.world.level.block.entity.BlockEntityType.CHEST.create(far, net.minecraft.world.level.block.Blocks.CHEST.defaultBlockState()); unloaded.setLevel(level); var unloadedToken = tokenClass.getConstructor(net.minecraft.world.level.block.entity.BlockEntity.class).newInstance(unloaded); scheduler.getMethod("schedule", tokenClass).invoke(null, unloadedToken); int chunks = level.getChunkSource().getLoadedChunksCount(); scheduler.getMethod("tick").invoke(null); if (!state.get(unloadedToken).toString().equals("UNLOADED") || chunks != level.getChunkSource().getLoadedChunksCount()) throw new AssertionError("Unloaded token forced chunk loading or was dropped"); scheduler.getMethod("onChunkUnload", net.minecraft.world.level.chunk.LevelChunk.class).invoke(null, new net.minecraft.world.level.chunk.LevelChunk(level, new net.minecraft.world.level.ChunkPos(far))); if (!state.get(unloadedToken).toString().equals("IDLE")) throw new AssertionError("Unload callback lost delayed token"); scheduler.getMethod("schedule", tokenClass).invoke(null, unloadedToken); scheduler.getMethod("tick").invoke(null); // Ticket promotion requeues a delayed token; unload discards it without masking errors. scheduler.getMethod("onChunkTicketChanged", net.minecraft.server.level.ServerLevel.class, long.class, int.class, int.class) .invoke(null, level, net.minecraft.world.level.ChunkPos.asLong(far), 40, 31); if (!state.get(unloadedToken).toString().equals("SCHEDULED")) throw new AssertionError("Ticket callback lost token"); unloaded.setRemoved(); scheduler.getMethod("tick").invoke(null); var report = server.fillSystemReport(new net.minecraft.SystemReport()).toLineSeparatedString(); if (!report.contains("DimensionalRipper Thread Ownership") || !report.contains("observedOwner=") || !report.contains("8, 110, 8") || !report.contains("rejected=1001")) throw new AssertionError("Crash report lacks ownership evidence: " + report); if (!report.contains("handoff=completed-owner-restored")) throw new AssertionError("Bridge ownership history missing"); concurrentCallbacks(server, scheduler, tokenClass, state); ModZuozhi.LOGGER.info("OWNERSHIP EDGE PASS: nested/exception pauses, removed/mismatched/unloaded tokens, ticket callback, crash diagnostics"); ModZuozhi.LOGGER.info("OWNERSHIP REGRESSION PASS: real CC monitor identity, callback pumping, scheduled tick; elapsed={}ms", (System.nanoTime()-start)/1_000_000.0); worldTime = level.getGameTime(); level.setBlockAndUpdate(pos, net.minecraft.world.level.block.Blocks.AIR.defaultBlockState()); verified = true; } finally { server.tickRateManager().setTickRate(originalRate); release.countDown(); manager.setPaused(server, !verified); if (!verified) server.halt(false); } } private static void concurrentCallbacks(net.minecraft.server.MinecraftServer server, Class scheduler, Class tokenClass, java.lang.reflect.Field state) throws Exception { var levels = java.util.List.of(server.overworld(), server.getLevel(net.minecraft.world.level.Level.NETHER)); var tokens = new Object[2][40]; var owners = new net.minecraft.world.level.block.entity.BlockEntity[2][40]; for (int d = 0; d < 2; d++) for (int i = 0; i < 40; i++) { var pos = new BlockPos(1600000 + i * 16, 100, 1600000); var owner = net.minecraft.world.level.block.entity.BlockEntityType.CHEST.create(pos, net.minecraft.world.level.block.Blocks.CHEST.defaultBlockState()); owner.setLevel(levels.get(d)); owners[d][i] = owner; tokens[d][i] = tokenClass.getConstructor(net.minecraft.world.level.block.entity.BlockEntity.class).newInstance(owner); scheduler.getMethod("schedule", tokenClass).invoke(null, tokens[d][i]); } scheduler.getMethod("tick").invoke(null); // all 80 are deferred, no chunks are loaded var gate = new CountDownLatch(2); var done = new CountDownLatch(2); var failure = new java.util.concurrent.atomic.AtomicReference(); for (int d = 0; d < 2; d++) { final int dimension = d; var level = levels.get(d); DimThreadCore.MANAGER.getLoop(level).execute(() -> { try { gate.countDown(); if (!gate.await(10, TimeUnit.SECONDS)) throw new AssertionError("Dimension callback overlap not reached"); for (int i = 0; i < 40; i++) { var pos = owners[dimension][i].getBlockPos(); if (i % 2 == 0) scheduler.getMethod("onChunkUnload", net.minecraft.world.level.chunk.LevelChunk.class) .invoke(null, new net.minecraft.world.level.chunk.LevelChunk(level, new net.minecraft.world.level.ChunkPos(pos))); else scheduler.getMethod("onChunkTicketChanged", net.minecraft.server.level.ServerLevel.class, long.class, int.class, int.class) .invoke(null, level, net.minecraft.world.level.ChunkPos.asLong(pos), 40, 31); } } catch (Throwable error) { failure.set(error); } finally { done.countDown(); } }); } DimThreadCore.MANAGER.setPaused(server, false); server.managedBlock(() -> done.getCount() == 0); DimThreadCore.MANAGER.setPaused(server, true); if (failure.get() != null) throw new AssertionError("Concurrent callbacks failed", failure.get()); for (int d = 0; d < 2; d++) for (int i = 0; i < 40; i++) { String expected = i % 2 == 0 ? "IDLE" : "SCHEDULED"; if (!state.get(tokens[d][i]).toString().equals(expected)) throw new AssertionError("Delayed token lost across dimensions"); owners[d][i].setRemoved(); } scheduler.getMethod("tick").invoke(null); ModZuozhi.LOGGER.info("OWNERSHIP CALLBACK PASS: 80 deferred tokens, concurrent overworld/nether ticket/unload callbacks"); } }