diff --git a/README.md b/README.md index 2ec5ae2..51f19a8 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ nothing to do with speed — and it claims none. | --- | --- | | [`falco-anvil`](https://github.com/OneLiteFeatherNET/Falco/wiki/Anvil-Chunk-Loader) | A `ChunkLoader` for the Anvil region format. Genuinely parallel: reading, decompression and NBT parsing do not share one lock. A read failure throws instead of reporting the chunk as absent, so the server cannot overwrite real data with a freshly generated chunk. | | [`falco-light`](https://github.com/OneLiteFeatherNET/Falco/wiki/Light-Engine) | A block and sky light engine. Thread-safe per call and tied to no chunk implementation, so it works with chunk types Minestom's own engine ignores. Call it yourself, or let a chunk keep its own light up to date. | -| [`falco-instance`](https://github.com/OneLiteFeatherNET/Falco/wiki/Rationale-Instances-And-Chunks) | An `Instance` and its `Chunk`. **No speed gain is claimed and none is measured** — ticking lives in the server's global `ThreadDispatcher`, not in the instance. What it buys is an unload path of its own, where `InstanceManager.unregisterInstance` leaks every chunk a foreign instance ever loaded. It cannot back a `SharedInstance`. | +| [`falco-instance`](https://github.com/OneLiteFeatherNET/Falco/wiki/Rationale-Instances-And-Chunks) | An `Instance` and its `Chunk`. **No speed gain is claimed and none is measured** — ticking lives in the server's global `ThreadDispatcher`, not in the instance. What it buys is an unload path of its own, where `InstanceManager.unregisterInstance` leaks every chunk a foreign instance ever loaded. It cannot back a `SharedInstance`; shared worlds are served by `FalcoSharedInstance` on a plain container instead. | All three modules are **experimental**. Every public type carries `@ApiStatus.Experimental`; signatures and behaviour may still change in a minor release. @@ -135,6 +135,46 @@ flag — for a pre-lit world the engine is doing work nobody asked for. It earns without stored light and after blocks change at runtime. Which case is which is spelled out in [Light Engine](https://github.com/OneLiteFeatherNET/Falco/wiki/Light-Engine). +## Shared worlds + +Shared worlds are the one case `FalcoInstance` cannot serve, because `SharedInstance` takes an +`InstanceContainer` and nothing else. `FalcoSharedInstance` accepts that and builds on the container +instead: + +```java +InstanceManager manager = MinecraftServer.getInstanceManager(); +InstanceContainer world = manager.createInstanceContainer(); +world.setChunkSupplier(FalcoChunk::new); + +// Not manager.createSharedInstance(world): that factory always builds Minestom's own type. +FalcoSharedInstance view = new FalcoSharedInstance(UUID.randomUUID(), world); +manager.registerSharedInstance(view); +``` + +The order in that example is not decoration. `createInstanceContainer` registers the container, and +the view's constructor refuses one that is not registered: `registerSharedInstance` performs no such +check, whereas the `createSharedInstance` this class cannot be built by does, and an unregistered +container is ticked by nobody. + +The view keeps its own generator, chunk supplier and auto-load setting, where Minestom's writes all +three through to the container and lets one view reconfigure another. All three are read once, in the +constructor, which cuts the other way too: turning auto chunk loading off on the container no longer +stops a view that already exists, so that has to be said to each view. Its tags were always its own — +`Instance` gives every instance a `TagHandler` and `SharedInstance` does not override it — but +`saveInstance()` handed the loader the container, so they were never written; here the view's own +data is what the loader is given. + +What none of that changes is who owns the blocks: `setBlock` reaches the container, and the container +serialises every write on its own monitor, because the method that performs the write is +`private synchronized` and is reached from three further places that an override cannot follow. A +world built this way keeps what the Falco chunk saves and keeps the container's write path; a world +that needs the write path uses `FalcoInstance` and gives up sharing. + +The per-view generator and chunk supplier are a repair and not a capability: they stop one view from +reconfiguring another, and nothing inside Minestom reads them, because the container creates every +chunk from its own. The reasoning is in +[Rationale: Instances and Chunks](https://github.com/OneLiteFeatherNET/Falco/wiki/Rationale-Instances-And-Chunks). + ## What "high-performance" means here Measured, not asserted. Every figure comes from a JMH benchmark in this repository and is quoted diff --git a/docs/superpowers/plans/2026-08-02-falco-shared-instance.md b/docs/superpowers/plans/2026-08-02-falco-shared-instance.md new file mode 100644 index 0000000..24f7bda --- /dev/null +++ b/docs/superpowers/plans/2026-08-02-falco-shared-instance.md @@ -0,0 +1,1857 @@ +# Falco Shared Instance — Stage 4 Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Give Falco a shared instance that keeps Minestom's chunk-resend fast path and repairs the four places where Minestom's `SharedInstance` writes through to the container it borrows chunks from, so that two views of one world stop reconfiguring each other. + +**Architecture:** `FalcoSharedInstance extends SharedInstance`. Not a new type beside `SharedInstance` — a subclass of it, and the reason is a measurement. `SharedInstance#areLinked` decides whether `Player#setInstance` re-sends every chunk in view distance or none; it compares `getInstanceContainer()` rather than testing for a concrete class, so a subclass keeps the fast path for free. M16 puts a full resend at 765 ms and 86.5 MB of allocation against zero for the fast path. Nothing in `SharedInstance` is `final`, so every delegating method is replaceable. Four of them are replaced: `setGenerator`, `setChunkSupplier` and `enableAutoChunkLoad` stop aliasing the container, and `saveInstance` stops persisting the container's tags in place of this instance's. `setBlock` is deliberately **not** replaced — see *The wall* below. + +**Tech Stack:** Java 25, Gradle, JUnit 5, Cyano 0.6.2 (Minestom test extension, `MicrotusExtension` / `Env` / `TestConnection`), Minestom `2026.06.20-26.1.2`. + +## Global Constraints + +Copied verbatim from the spec (`docs/superpowers/specs/2026-08-01-falco-instance-chunk-design.md`): + +- **NFR-001** — compile and run against the pinned Minestom version without reflection, `--add-opens` or an open module. +- **NFR-002** — only language and JDK features final in Java 25. No preview, no incubator. +- **NFR-003** — if a performance claim is published, a JMH or JOL measurement in this repository supports it, stated with its conditions. +- **NFR-005** — when a chunk read fails, the failure reaches the caller instead of being reported as an absent chunk. +- **NFR-006** — while a block is written, the lock held is the lock of the chunk it touches, not a monitor over the instance. +- **NFR-009** — every new public type carries `@ApiStatus.Experimental`. + +**NFR-006 is not met on this stage, and that is the design.** §3 of the spec lists *"removing the instance monitor from a container that carries a shared instance"* as an explicit non-goal, and §4.4 gives the reason. NFR-006 is written unconditionally in §7 and the two statements are never reconciled in the spec; this plan reads NFR-006 as scoped to Falco's own instance and treats the shared path as the carve-out §3 already made. US-4.04 exists precisely so that the gap is written down instead of papered over. If the project owner reads it the other way, stage 4 cannot be built at all and this plan is void — settle that before Task 1. + +Repository conventions, non-negotiable: + +- **Source and Javadoc are English**, and Javadoc *justifies* decisions in `
` paragraphs and `
+ * The second half is the one that matters. {@code areLinked} is consulted in exactly one place, + * {@code Player#setInstance}, and when it answers false the player receives every chunk in view + * distance again. Nothing fails, nothing logs, the world simply costs a full resend per instance + * change. A test is the only thing that notices. + *
+ * + * @author TheMeinerLP + * @version 1.0.0 + * @since 0.4.0 + */ +@ExtendWith(MicrotusExtension.class) +@DisplayName("A Falco shared instance") +class FalcoSharedInstanceTest { + + private static FalcoSharedInstance registered(Env env, InstanceContainer container) { + final FalcoSharedInstance shared = new FalcoSharedInstance(UUID.randomUUID(), container); + env.process().instance().registerSharedInstance(shared); + return shared; + } + + @Test + @DisplayName("registers through registerSharedInstance and is known to its container") + void testRegistration(Env env) { + final InstanceManager manager = env.process().instance(); + final InstanceContainer container = manager.createInstanceContainer(); + + final FalcoSharedInstance shared = registered(env, container); + + assertTrue(shared.isRegistered()); + assertTrue(manager.getInstances().contains(shared)); + assertSame(shared, manager.getInstance(shared.getUuid())); + assertSame(container, shared.getInstanceContainer()); + assertTrue(container.getSharedInstances().contains(shared), + "the container has to know the view, or its chunks never take the view's players as viewers"); + } + + @Test + @DisplayName("is refused by registerInstance, which is why registerSharedInstance exists") + void testPlainRegistrationIsRefused(Env env) { + final InstanceContainer container = env.process().instance().createInstanceContainer(); + final FalcoSharedInstance shared = new FalcoSharedInstance(UUID.randomUUID(), container); + + assertThrows(IllegalStateException.class, () -> env.process().instance().registerInstance(shared)); + assertFalse(shared.isRegistered()); + } + + @Test + @DisplayName("cannot come from createSharedInstance, which always builds the stock type") + void testTheFactoryOfMinestomBuildsTheStockType(Env env) { + final InstanceContainer container = env.process().instance().createInstanceContainer(); + + final SharedInstance stock = env.process().instance().createSharedInstance(container); + + assertSame(SharedInstance.class, stock.getClass(), + "if this ever changes, the hand registration in the README can go"); + } + + @Test + @DisplayName("counts as linked to its container in both argument orders") + void testLinkedToItsContainer(Env env) { + final InstanceContainer container = env.process().instance().createInstanceContainer(); + final FalcoSharedInstance shared = registered(env, container); + + assertTrue(SharedInstance.areLinked(container, shared)); + assertTrue(SharedInstance.areLinked(shared, container)); + } + + @Test + @DisplayName("counts as linked to a sibling view of the same container") + void testLinkedToASibling(Env env) { + final InstanceContainer container = env.process().instance().createInstanceContainer(); + final FalcoSharedInstance first = registered(env, container); + final FalcoSharedInstance second = registered(env, container); + + assertTrue(SharedInstance.areLinked(first, second)); + assertTrue(SharedInstance.areLinked(second, first)); + } + + @Test + @DisplayName("counts as linked to a stock shared instance over the same container") + void testLinkedToAStockSharedInstance(Env env) { + final InstanceContainer container = env.process().instance().createInstanceContainer(); + final FalcoSharedInstance falco = registered(env, container); + final SharedInstance stock = env.process().instance().createSharedInstance(container); + + assertTrue(SharedInstance.areLinked(falco, stock)); + } + + @Test + @DisplayName("counts as unlinked to a view of a different container") + void testUnlinkedAcrossContainers(Env env) { + final InstanceManager manager = env.process().instance(); + final InstanceContainer first = manager.createInstanceContainer(); + final InstanceContainer second = manager.createInstanceContainer(); + final FalcoSharedInstance sharedOnFirst = registered(env, first); + final FalcoSharedInstance sharedOnSecond = registered(env, second); + + assertFalse(SharedInstance.areLinked(sharedOnFirst, sharedOnSecond)); + assertFalse(SharedInstance.areLinked(sharedOnFirst, second)); + } +} +``` + +- [ ] **Step 2: Run the test to verify it fails** + +```bash +cd /mnt/projects/oss/onelitefeather/Falco-worktrees/shared-instance +./gradlew :falco-instance:test --tests "*FalcoSharedInstanceTest*" +``` + +Expected: compilation failure — `FalcoSharedInstance` does not exist. + +- [ ] **Step 3: Write the class** + +Create `FalcoSharedInstance.java`. The class Javadoc explains the choice of a subclass; Task 7 appends the two `+ * This is a subclass of {@link SharedInstance} rather than a type of its own, and the reason is a + * single static method. {@code SharedInstance#areLinked} decides whether a player who moves between + * two instances keeps the chunks it already has or receives all of them again, and it is consulted + * in exactly one place: {@code Player#setInstance}. It compares + * {@link SharedInstance#getInstanceContainer()} rather than testing for a concrete class, so a + * subclass inherits the answer. A separate type with the same behaviour would answer false, nothing + * would fail, nothing would be logged, and every instance change would silently cost a full resend + * of the view distance. + *
+ *+ * The price of the subclass is that the block owner has to be an {@link InstanceContainer}: that is + * the only constructor {@link SharedInstance} has. A world used this way therefore sets its chunk + * supplier to {@code FalcoChunk::new} on the container and keeps everything stages one and two + * bought at the chunk, while the container keeps its own write path. + *
+ *+ * Registration goes through {@code InstanceManager#registerSharedInstance}. Its sibling + * {@code createSharedInstance} always constructs the stock type and can never produce this one, and + * {@code registerInstance} refuses anything that is a {@link SharedInstance} outright. + *
+ * + * @author TheMeinerLP + * @version 1.0.0 + * @since 0.4.0 + */ +@ApiStatus.Experimental +public class FalcoSharedInstance extends SharedInstance { + + /** + * Creates a view over the chunks of a container. + * + * @param uuid the identity of this instance + * @param instanceContainer the container which owns the chunks this instance shows + */ + public FalcoSharedInstance(UUID uuid, InstanceContainer instanceContainer) { + super(uuid, Objects.requireNonNull(instanceContainer, "a shared instance needs a container to share")); + } +} +``` + +- [ ] **Step 4: Run the test to verify it passes** + +```bash +cd /mnt/projects/oss/onelitefeather/Falco-worktrees/shared-instance +./gradlew :falco-instance:test --tests "*FalcoSharedInstanceTest*" +``` + +Expected: seven tests, all PASS. + +- [ ] **Step 5: Prove the test bites, by mutation** + +The `areLinked` cases would stay green against a class that had lost the link, if they were written carelessly. Prove they are not. Add this to `FalcoSharedInstance`, temporarily: + +```java + // TEMPORARY MUTATION — revert after the run + @Override + public InstanceContainer getInstanceContainer() { + return new InstanceContainer(UUID.randomUUID(), getDimensionType()); + } +``` + +Re-run the same command. Expected: `testLinkedToItsContainer`, `testLinkedToASibling`, `testLinkedToAStockSharedInstance` and `testRegistration` all FAIL — the last one because `registerSharedInstance` reads the getter and would attach the view to a container nobody holds. Then **delete the mutation** and re-run to confirm green again. Do not commit with the mutation in place. + +- [ ] **Step 6: Commit** + +```bash +cd /mnt/projects/oss/onelitefeather/Falco-worktrees/shared-instance +git add falco-instance/src/main/java/net/onelitefeather/falco/instance/FalcoSharedInstance.java \ + falco-instance/src/test/java/net/onelitefeather/falco/instance/FalcoSharedInstanceTest.java +git commit -m "feat(instance): add FalcoSharedInstance, a shared instance Minestom still recognises" +``` + +--- + +### Task 2: The resend fast path, proven on the wire + +**Files:** +- Test: `falco-instance/src/test/java/net/onelitefeather/falco/instance/FalcoSharedInstanceResendTest.java` +- Modify: nothing. This task adds no production code on purpose. + +**Interfaces:** +- Consumes: `FalcoSharedInstance` from Task 1, `net.minestom.testing.TestConnection#trackIncoming(Class)`, `net.minestom.server.entity.Player#setInstance(Instance, Pos)`. +- Produces: nothing. It produces evidence. + +Task 1 proved `areLinked` answers true. That is the mechanism, not the outcome. US-4.01 is about what the player's connection receives, and the two are only the same as long as `Player#setInstance` keeps its shape. This task asserts the outcome directly, so that an upgrade which changes the shape is caught here rather than in production. + +**Read this before writing the assertion.** The intuitive marker — `ChunkDataPacket` — does not work. `Player#resetChunkQueue()` does not reset `chunkBatchLead`, and `maxChunkBatchLead` starts at 1, so after the first spawn no further chunk batch is emitted until the client replies with a batch acknowledgement, which a `TestConnection` never does. A count of zero `ChunkDataPacket` would therefore be green on both paths and prove nothing. The markers that are emitted unconditionally on the slow path are `UpdateViewPositionPacket` (once) and `UnloadChunkPacket` (one per chunk in the old view), both from `Player#spawnPlayer` under `updateChunks == true`. + +- [ ] **Step 1: Write the test** + +Create `FalcoSharedInstanceResendTest.java`: + +```java +package net.onelitefeather.falco.instance; + +import net.minestom.server.coordinate.Pos; +import net.minestom.server.entity.Player; +import net.minestom.server.instance.InstanceContainer; +import net.minestom.server.network.packet.server.play.ChunkDataPacket; +import net.minestom.server.network.packet.server.play.UnloadChunkPacket; +import net.minestom.server.network.packet.server.play.UpdateViewPositionPacket; +import net.minestom.testing.Collector; +import net.minestom.testing.Env; +import net.minestom.testing.TestConnection; +import net.minestom.testing.extension.MicrotusExtension; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertSame; + +/** + * Asserts what US-4.01 actually asks for: a player moving into a shared instance receives no chunk + * traffic at all. + *+ * {@code areLinked} is the mechanism and is covered next door; this class covers the outcome, so + * that a change to {@code Player#setInstance} is caught here instead of costing a full resend per + * transfer in production. The markers are {@code UpdateViewPositionPacket} and + * {@code UnloadChunkPacket}, both sent unconditionally by the slow path. + * {@code ChunkDataPacket} is asserted as well but carries no weight on its own: after the first + * spawn Minestom holds the chunk queue until the client acknowledges a batch, which a test + * connection never does, so that counter reads zero on both paths. + *
+ * + * @author TheMeinerLP + * @version 1.0.0 + * @since 0.4.0 + */ +@ExtendWith(MicrotusExtension.class) +@DisplayName("A player moving into a Falco shared instance") +class FalcoSharedInstanceResendTest { + + private static final Pos SPAWN = new Pos(0.5, 40, 0.5); + + private static InstanceContainer container(Env env) { + final InstanceContainer container = env.process().instance().createInstanceContainer(); + // Keeps the transfer at 25 chunks instead of 289; the paths under test do not depend on it. + container.viewDistance(1); + return container; + } + + @Test + @DisplayName("receives no view update and no chunk unload, because the chunks are the same") + void testTheFastPathSendsNothing(Env env) { + final InstanceContainer container = container(env); + final FalcoSharedInstance shared = new FalcoSharedInstance(UUID.randomUUID(), container); + env.process().instance().registerSharedInstance(shared); + shared.viewDistance(1); + + final TestConnection connection = env.createConnection(); + final Player player = connection.connect(container, SPAWN); + + final Collector+ * Every case here uses two shared instances over one container and inspects the one which + * was not touched. A case that only looked at the instance it had just configured would be green + * with the defect in place, because the defect is not that the value is lost — it is that the value + * lands somewhere else as well. + *
+ * + * @author TheMeinerLP + * @version 1.0.0 + * @since 0.4.0 + */ +@ExtendWith(MicrotusExtension.class) +@DisplayName("The configuration of a Falco shared instance") +class FalcoSharedInstanceStateTest { + + private static FalcoSharedInstance registered(Env env, InstanceContainer container) { + final FalcoSharedInstance shared = new FalcoSharedInstance(UUID.randomUUID(), container); + env.process().instance().registerSharedInstance(shared); + return shared; + } + + @Test + @DisplayName("starts with the generator its container had") + void testTheGeneratorIsSeededFromTheContainer(Env env) { + final InstanceContainer container = env.process().instance().createInstanceContainer(); + final Generator generator = unit -> unit.modifier().fill(Block.STONE); + container.setGenerator(generator); + + final FalcoSharedInstance shared = registered(env, container); + + assertSame(generator, shared.generator()); + } + + @Test + @DisplayName("keeps a generator to itself: neither the sibling nor the container sees it") + void testTheGeneratorDoesNotAlias(Env env) { + final InstanceContainer container = env.process().instance().createInstanceContainer(); + final FalcoSharedInstance first = registered(env, container); + final FalcoSharedInstance second = registered(env, container); + final Generator generator = unit -> unit.modifier().fill(Block.STONE); + + first.setGenerator(generator); + + assertSame(generator, first.generator()); + assertNull(second.generator(), "a sibling view must not be reconfigured by this call"); + assertNull(container.generator(), "the container must not be reconfigured by this call"); + } + + @Test + @DisplayName("does not lose the container's generator when it clears its own") + void testClearingTheGeneratorDoesNotClearTheContainer(Env env) { + final InstanceContainer container = env.process().instance().createInstanceContainer(); + final Generator generator = unit -> unit.modifier().fill(Block.STONE); + container.setGenerator(generator); + final FalcoSharedInstance shared = registered(env, container); + + shared.setGenerator(null); + + assertNull(shared.generator()); + assertSame(generator, container.generator(), + "clearing a view must not empty the world it looks at"); + } +} +``` + +The third case is the one that would hurt in production: with the defect, `shared.setGenerator(null)` empties the world for everyone. + +- [ ] **Step 2: Run the test to verify it fails** + +```bash +cd /mnt/projects/oss/onelitefeather/Falco-worktrees/shared-instance +./gradlew :falco-instance:test --tests "*FalcoSharedInstanceStateTest*" +``` + +Expected: `testTheGeneratorIsSeededFromTheContainer` PASSES (the inherited delegation happens to answer correctly), the other two FAIL — `testTheGeneratorDoesNotAlias` because `second.generator()` and `container.generator()` both return the generator, `testClearingTheGeneratorDoesNotClearTheContainer` because the container's generator is gone. That split is worth noticing: the first case cannot distinguish the fix from the defect, and it is kept anyway because it pins the seeding that tasks 4 and 5 repeat. + +- [ ] **Step 3: Implement** + +Add the field, seed it in the constructor and override both methods. The constructor becomes: + +```java + /** + * The generator of this instance, which is deliberately not the generator of the container. + *+ * Volatile because a shared instance is configured from wherever the world is set up and read + * from wherever it is asked, and those are not the same thread. + *
+ */ + private volatile @Nullable Generator generator; + + /** + * Creates a view over the chunks of a container. + *+ * The configuration of the container is copied once, here. That is what makes a fresh view + * behave like the world it looks at while still being able to diverge from it — the alternative, + * starting empty, would answer {@code null} to {@link #generator()} on a world that has one. + *
+ * + * @param uuid the identity of this instance + * @param instanceContainer the container which owns the chunks this instance shows + */ + public FalcoSharedInstance(UUID uuid, InstanceContainer instanceContainer) { + super(uuid, Objects.requireNonNull(instanceContainer, "a shared instance needs a container to share")); + this.generator = instanceContainer.generator(); + } + + /** + * Gets the generator of this instance. + * + * @return the generator of this instance, null if it has none + */ + @Override + public @Nullable Generator generator() { + return this.generator; + } + + /** + * Sets the generator of this instance, and of nothing else. + *+ * Minestom's shared instance forwards this call to its container, which means that configuring + * one view reconfigures the world and every other view of it. That is the defect this class + * exists to repair, and repairing it has a consequence worth stating: no chunk is generated from + * this value. Chunks are created by the container, and the container asks its own generator. Use + * {@code getInstanceContainer().setGenerator(…)} to decide what the world is made of. + *
+ * + * @param generator the generator of this instance, null to have none + */ + @Override + public void setGenerator(@Nullable Generator generator) { + this.generator = generator; + } +``` + +New imports on the class: `net.minestom.server.instance.generator.Generator`, `org.jetbrains.annotations.Nullable`. + +- [ ] **Step 4: Run the test to verify it passes** + +```bash +cd /mnt/projects/oss/onelitefeather/Falco-worktrees/shared-instance +./gradlew :falco-instance:test --tests "*FalcoSharedInstanceStateTest*" +``` + +Expected: three tests, all PASS. + +- [ ] **Step 5: Prove the test bites, by mutation** + +Two mutations, run one at a time and reverted each time. The pair of methods can be broken independently, and a test that caught only one of them would be a false witness for the other. + +1. Replace the body of `setGenerator` with `super.setGenerator(generator);`, keeping `generator()` as it is. Expected: `testTheGeneratorDoesNotAlias` FAILS on `assertNull(container.generator())` — the write reached the container. `testClearingTheGeneratorDoesNotClearTheContainer` FAILS as well, on `assertSame(generator, container.generator())`. +2. Restore `setGenerator`, then delete the `generator()` override. Expected: `testTheGeneratorDoesNotAlias` FAILS on `assertSame(generator, first.generator())` — the reader now answers with the container's value, which the repaired setter no longer wrote. `testTheGeneratorIsSeededFromTheContainer` stays green, which is exactly why it is not evidence for this story. + +Restore both overrides and confirm green. + +- [ ] **Step 6: Commit** + +```bash +cd /mnt/projects/oss/onelitefeather/Falco-worktrees/shared-instance +git add falco-instance/src/main/java/net/onelitefeather/falco/instance/FalcoSharedInstance.java \ + falco-instance/src/test/java/net/onelitefeather/falco/instance/FalcoSharedInstanceStateTest.java +git commit -m "fix(instance): stop a shared instance from writing its generator into the container" +``` + +--- + +### Task 4: A chunk supplier of its own + +**Files:** +- Modify: `falco-instance/src/main/java/net/onelitefeather/falco/instance/FalcoSharedInstance.java` +- Test: `falco-instance/src/test/java/net/onelitefeather/falco/instance/FalcoSharedInstanceStateTest.java` (append) + +**Interfaces:** +- Consumes: `net.minestom.server.utils.chunk.ChunkSupplier`. +- Produces: `public ChunkSupplier getChunkSupplier()` and `public void setChunkSupplier(ChunkSupplier chunkSupplier)` on `FalcoSharedInstance`. + +- [ ] **Step 1: Write the failing test** + +Append to `FalcoSharedInstanceStateTest.java` (and add the imports `net.minestom.server.utils.chunk.ChunkSupplier` and `static org.junit.jupiter.api.Assertions.assertNotSame`): + +```java + @Test + @DisplayName("starts with the chunk supplier its container had") + void testTheChunkSupplierIsSeededFromTheContainer(Env env) { + final InstanceContainer container = env.process().instance().createInstanceContainer(); + + final FalcoSharedInstance shared = registered(env, container); + + assertSame(container.getChunkSupplier(), shared.getChunkSupplier()); + } + + @Test + @DisplayName("keeps a chunk supplier to itself: neither the sibling nor the container sees it") + void testTheChunkSupplierDoesNotAlias(Env env) { + final InstanceContainer container = env.process().instance().createInstanceContainer(); + final ChunkSupplier stock = container.getChunkSupplier(); + final FalcoSharedInstance first = registered(env, container); + final FalcoSharedInstance second = registered(env, container); + final ChunkSupplier supplier = FalcoChunk::new; + + first.setChunkSupplier(supplier); + + assertSame(supplier, first.getChunkSupplier()); + assertSame(stock, second.getChunkSupplier(), "a sibling view must not be reconfigured by this call"); + assertSame(stock, container.getChunkSupplier(), "the container must not be reconfigured by this call"); + assertNotSame(supplier, container.getChunkSupplier()); + } + + @Test + @DisplayName("refuses a null chunk supplier instead of storing it") + void testTheChunkSupplierIsNotNullable(Env env) { + final InstanceContainer container = env.process().instance().createInstanceContainer(); + final FalcoSharedInstance shared = registered(env, container); + + assertThrows(NullPointerException.class, () -> shared.setChunkSupplier(null)); + assertSame(container.getChunkSupplier(), shared.getChunkSupplier()); + } +``` + +Add `static org.junit.jupiter.api.Assertions.assertThrows` to the imports as well. + +The second case is the sharpest of the three: `assertSame(stock, container.getChunkSupplier())` fails the moment the setter writes through, and it names the value it expected rather than merely asserting "not the new one". + +- [ ] **Step 2: Run the test to verify it fails** + +```bash +cd /mnt/projects/oss/onelitefeather/Falco-worktrees/shared-instance +./gradlew :falco-instance:test --tests "*FalcoSharedInstanceStateTest*" +``` + +Expected: `testTheChunkSupplierIsSeededFromTheContainer` PASSES through the inherited delegation, the other two FAIL. + +- [ ] **Step 3: Implement** + +Add to `FalcoSharedInstance`: + +```java + /** + * The chunk supplier of this instance, which is deliberately not the one of the container. + */ + private volatile ChunkSupplier chunkSupplier; +``` + +Seed it in the constructor, next to the generator: + +```java + this.chunkSupplier = instanceContainer.getChunkSupplier(); +``` + +And the two methods: + +```java + /** + * Gets the chunk supplier of this instance. + * + * @return the chunk supplier of this instance + */ + @Override + public ChunkSupplier getChunkSupplier() { + return this.chunkSupplier; + } + + /** + * Sets the chunk supplier of this instance, and of nothing else. + *+ * Minestom's shared instance forwards this call to its container, so configuring one view + * changes what type of chunk the whole world is made of. That is repaired here, with the same + * consequence the generator has: no chunk is created from this value, because chunks are created + * by the container and the container asks its own supplier — as does a chunk loader, which is + * handed the container rather than the view. Use + * {@code getInstanceContainer().setChunkSupplier(FalcoChunk::new)} to decide what the world is + * built from. + *
+ * + * @param chunkSupplier the chunk supplier of this instance + * @throws NullPointerException if {@code chunkSupplier} is null + */ + @Override + public void setChunkSupplier(ChunkSupplier chunkSupplier) { + this.chunkSupplier = Objects.requireNonNull(chunkSupplier, "the chunk supplier cannot be null"); + } +``` + +New import: `net.minestom.server.utils.chunk.ChunkSupplier`. + +- [ ] **Step 4: Run the test to verify it passes** + +```bash +cd /mnt/projects/oss/onelitefeather/Falco-worktrees/shared-instance +./gradlew :falco-instance:test --tests "*FalcoSharedInstanceStateTest*" +``` + +Expected: six tests, all PASS. + +- [ ] **Step 5: Prove the test bites, by mutation** + +Replace the body of `setChunkSupplier` with `super.setChunkSupplier(chunkSupplier);` — the exact defect, restored. Re-run. Expected: `testTheChunkSupplierDoesNotAlias` FAILS on `assertSame(stock, container.getChunkSupplier())` and `testTheChunkSupplierIsNotNullable` FAILS because the container accepts what this class refuses. Restore the body and confirm green. + +- [ ] **Step 6: Commit** + +```bash +cd /mnt/projects/oss/onelitefeather/Falco-worktrees/shared-instance +git add falco-instance/src/main/java/net/onelitefeather/falco/instance/FalcoSharedInstance.java \ + falco-instance/src/test/java/net/onelitefeather/falco/instance/FalcoSharedInstanceStateTest.java +git commit -m "fix(instance): stop a shared instance from writing its chunk supplier into the container" +``` + +--- + +### Task 5: Auto chunk load of its own, and the one place it can act + +**Files:** +- Modify: `falco-instance/src/main/java/net/onelitefeather/falco/instance/FalcoSharedInstance.java` +- Test: `falco-instance/src/test/java/net/onelitefeather/falco/instance/FalcoSharedInstanceStateTest.java` (append) + +**Interfaces:** +- Consumes: `InstanceContainer#getChunk(int, int)`, `InstanceContainer#loadChunk(int, int)`. +- Produces: `public void enableAutoChunkLoad(boolean enable)`, `public boolean hasEnabledAutoChunkLoad()` and `public CompletableFuture<@Nullable Chunk> loadOptionalChunk(int chunkX, int chunkZ)` on `FalcoSharedInstance`. + +This is the only one of the three flags that can be given behaviour rather than merely storage. `loadOptionalChunk` is a method of the view, so the view may answer it: hand back a chunk the container already holds, refuse to trigger a load when this view has auto load disabled, and otherwise delegate. It is also the method `Player#chunkAdder` calls, so the flag reaches something a player can observe. + +- [ ] **Step 1: Write the failing test** + +Append to `FalcoSharedInstanceStateTest.java` (imports: `net.minestom.server.instance.Chunk`, and the assertions `assertFalse`, `assertTrue`, `assertNotNull`): + +```java + @Test + @DisplayName("starts with the auto chunk load setting its container had") + void testAutoChunkLoadIsSeededFromTheContainer(Env env) { + final InstanceContainer container = env.process().instance().createInstanceContainer(); + container.enableAutoChunkLoad(false); + + final FalcoSharedInstance shared = registered(env, container); + + assertFalse(shared.hasEnabledAutoChunkLoad()); + } + + @Test + @DisplayName("keeps the auto chunk load flag to itself") + void testAutoChunkLoadDoesNotAlias(Env env) { + final InstanceContainer container = env.process().instance().createInstanceContainer(); + final FalcoSharedInstance first = registered(env, container); + final FalcoSharedInstance second = registered(env, container); + + first.enableAutoChunkLoad(false); + + assertFalse(first.hasEnabledAutoChunkLoad()); + assertTrue(second.hasEnabledAutoChunkLoad(), "a sibling view must not be reconfigured by this call"); + assertTrue(container.hasEnabledAutoChunkLoad(), "the container must not be reconfigured by this call"); + } + + @Test + @DisplayName("does not trigger a load of its own when the flag is off, and the container still can") + void testAutoChunkLoadDecidesTheOptionalLoad(Env env) { + final InstanceContainer container = env.process().instance().createInstanceContainer(); + final FalcoSharedInstance disabled = registered(env, container); + final FalcoSharedInstance enabled = registered(env, container); + disabled.enableAutoChunkLoad(false); + + assertNull(disabled.loadOptionalChunk(4, 4).join(), + "a view with auto load off must not pull a chunk into the world"); + assertNull(container.getChunk(4, 4), "and it must not have done so as a side effect either"); + + final Chunk loaded = enabled.loadOptionalChunk(4, 4).join(); + + assertNotNull(loaded); + assertSame(loaded, container.getChunk(4, 4)); + } + + @Test + @DisplayName("hands back a chunk that is already there even with the flag off") + void testAutoChunkLoadDoesNotHideLoadedChunks(Env env) { + final InstanceContainer container = env.process().instance().createInstanceContainer(); + final FalcoSharedInstance shared = registered(env, container); + shared.enableAutoChunkLoad(false); + final Chunk loaded = container.loadChunk(4, 4).join(); + + assertSame(loaded, shared.loadOptionalChunk(4, 4).join(), + "the flag governs whether a load is started, not whether the world is visible"); + } +``` + +- [ ] **Step 2: Run the test to verify it fails** + +```bash +cd /mnt/projects/oss/onelitefeather/Falco-worktrees/shared-instance +./gradlew :falco-instance:test --tests "*FalcoSharedInstanceStateTest*" +``` + +Expected: `testAutoChunkLoadIsSeededFromTheContainer` and `testAutoChunkLoadDoesNotHideLoadedChunks` PASS through the inherited delegation; `testAutoChunkLoadDoesNotAlias` FAILS on the sibling and the container, and `testAutoChunkLoadDecidesTheOptionalLoad` FAILS because the delegation asks the container's flag, which is on. + +- [ ] **Step 3: Implement** + +Add to `FalcoSharedInstance`: + +```java + /** + * Whether this instance pulls chunks into the world when it is asked for one it has not got. + */ + private volatile boolean autoChunkLoad; +``` + +Seed it in the constructor: + +```java + this.autoChunkLoad = instanceContainer.hasEnabledAutoChunkLoad(); +``` + +And the three methods: + +```java + /** + * Decides whether this instance pulls chunks into the world on demand. + *+ * Minestom's shared instance forwards this to its container, which turns a per-view decision + * into a per-world one. Here it stays with the view, and it reaches exactly one method: + * {@link #loadOptionalChunk(int, int)}. It does not reach {@code setBlock} — that call + * belongs to the container and asks the container's flag, for the reason given in the class + * documentation. + *
+ * + * @param enable true to pull chunks in on demand + */ + @Override + public void enableAutoChunkLoad(boolean enable) { + this.autoChunkLoad = enable; + } + + /** + * Gets whether this instance pulls chunks into the world on demand. + * + * @return true if it does + */ + @Override + public boolean hasEnabledAutoChunkLoad() { + return this.autoChunkLoad; + } + + /** + * Hands back the chunk at a position, loading it only if this instance is allowed to. + *+ * A chunk the container already holds is handed back whatever the flag says: the flag decides + * whether this view may cause a load, not whether it may see the world. Only the second branch + * consults it, and only that branch is a decision this instance is entitled to make — the chunk + * itself is still created, cached and published by the container. + *
+ * + * @param chunkX the chunk X + * @param chunkZ the chunk Z + * @return a future completed with the chunk, or with null if it is absent and may not be loaded + */ + @Override + public CompletableFuture<@Nullable Chunk> loadOptionalChunk(int chunkX, int chunkZ) { + final InstanceContainer container = getInstanceContainer(); + final Chunk loaded = container.getChunk(chunkX, chunkZ); + if (loaded != null) return CompletableFuture.completedFuture(loaded); + if (!this.autoChunkLoad) return CompletableFuture.completedFuture(null); + return container.loadChunk(chunkX, chunkZ); + } +``` + +New imports: `net.minestom.server.instance.Chunk`, `java.util.concurrent.CompletableFuture`. + +- [ ] **Step 4: Run the test to verify it passes** + +```bash +cd /mnt/projects/oss/onelitefeather/Falco-worktrees/shared-instance +./gradlew :falco-instance:test --tests "*FalcoSharedInstanceStateTest*" +``` + +Expected: ten tests, all PASS. + +- [ ] **Step 5: Prove the test bites, by mutation** + +Two mutations, run one at a time and reverted each time: + +1. Replace the body of `enableAutoChunkLoad` with `super.enableAutoChunkLoad(enable);`. Expected: `testAutoChunkLoadDoesNotAlias` FAILS on the sibling and on the container. +2. Delete the `loadOptionalChunk` override. Expected: `testAutoChunkLoadDecidesTheOptionalLoad` FAILS on its first assertion — the container's flag is on, so a chunk arrives where none should have. + +Confirm green after each revert. + +- [ ] **Step 6: Commit** + +```bash +cd /mnt/projects/oss/onelitefeather/Falco-worktrees/shared-instance +git add falco-instance/src/main/java/net/onelitefeather/falco/instance/FalcoSharedInstance.java \ + falco-instance/src/test/java/net/onelitefeather/falco/instance/FalcoSharedInstanceStateTest.java +git commit -m "fix(instance): give a shared instance its own auto chunk load decision" +``` + +--- + +### Task 6: `saveInstance` writes this instance's tags + +**Files:** +- Modify: `falco-instance/src/main/java/net/onelitefeather/falco/instance/FalcoSharedInstance.java` +- Test: `falco-instance/src/test/java/net/onelitefeather/falco/instance/FalcoSharedInstanceSaveTest.java` + +**Interfaces:** +- Consumes: `InstanceContainer#getChunkLoader()`, `ChunkLoader#saveInstance(Instance)`, `ChunkLoader#supportsParallelSaving()`. +- Produces: `public CompletableFuture+ * The defect this covers is silent by construction: Minestom's shared instance forwards the call to + * its container, the container hands itself to the loader, the loader writes the container's tags, + * and the operation reports success. Nothing is lost that anyone could notice at the time — the tags + * of the view are simply never written. So the assertion has to be on the argument the loader + * received, not on whether the call succeeded. + *
+ * + * @author TheMeinerLP + * @version 1.0.0 + * @since 0.4.0 + */ +@ExtendWith(MicrotusExtension.class) +@DisplayName("Saving a Falco shared instance") +class FalcoSharedInstanceSaveTest { + + private static final Tag+ * Minestom's shared instance forwards this to {@code InstanceContainer#saveInstance()}, which + * hands the loader the container. The tags of the view are therefore never written and the call + * still reports success — the anvil loader writes {@code instance.tagHandler().asCompound()} of + * whatever it was given. Reaching the loader directly and handing it {@code this} is the whole + * of the repair. + *
+ *+ * What it does not repair: a loader writes to one place per world. An + * {@code AnvilLoader} puts instance data in a single {@code level.dat}, so a container and every + * view of it write over one another and the last save wins. Saving one view of a world is + * therefore meaningful; saving several and expecting to read all of them back is not. + *
+ * + * @return a future completed once the data is written, completed exceptionally if it threw + */ + @Override + public CompletableFuture+ * The block owner is the container, its {@code UNSAFE_setBlock} is private and synchronised on the + * instance, and it is reached from four places of which {@code setBlock} is only one. Overriding + * {@code setBlock} here would leave the other three on the private path and create two write paths + * over one chunk, one of them unsynchronised. This class asserts the consequences of not doing that, + * so that the documentation which states them cannot quietly stop being true. + *
+ * + * @author TheMeinerLP + * @version 1.0.0 + * @since 0.4.0 + */ +@ExtendWith(MicrotusExtension.class) +@DisplayName("Writing through a Falco shared instance") +class FalcoSharedInstanceWriteTest { + + private static FalcoSharedInstance registered(Env env, InstanceContainer container) { + final FalcoSharedInstance shared = new FalcoSharedInstance(UUID.randomUUID(), container); + env.process().instance().registerSharedInstance(shared); + return shared; + } + + @Test + @DisplayName("shows the same chunk object as the container and as its siblings") + void testTheChunkIsTheContainersChunk(Env env) { + final InstanceContainer container = env.process().instance().createInstanceContainer(); + final FalcoSharedInstance first = registered(env, container); + final FalcoSharedInstance second = registered(env, container); + + final Chunk chunk = container.loadChunk(0, 0).join(); + + assertSame(chunk, first.getChunk(0, 0)); + assertSame(chunk, second.getChunk(0, 0)); + } + + @Test + @DisplayName("lands in the container, where every other view reads it") + void testAWriteReachesEveryView(Env env) { + final InstanceContainer container = env.process().instance().createInstanceContainer(); + container.setChunkSupplier(FalcoChunk::new); + final FalcoSharedInstance first = registered(env, container); + final FalcoSharedInstance second = registered(env, container); + container.loadChunk(0, 0).join(); + + first.setBlock(1, 40, 2, Block.DIAMOND_BLOCK); + + assertEquals(Block.DIAMOND_BLOCK, first.getBlock(1, 40, 2)); + assertEquals(Block.DIAMOND_BLOCK, second.getBlock(1, 40, 2)); + assertEquals(Block.DIAMOND_BLOCK, container.getBlock(1, 40, 2)); + } + + @Test + @DisplayName("still auto-loads on write when the container does, whatever the view was told") + void testTheViewFlagDoesNotReachTheWritePath(Env env) { + final InstanceContainer container = env.process().instance().createInstanceContainer(); + container.setChunkSupplier(FalcoChunk::new); + final FalcoSharedInstance shared = registered(env, container); + + shared.enableAutoChunkLoad(false); + shared.setBlock(20, 40, 20, Block.STONE); + + assertNotNull(container.getChunk(1, 1), + "setBlock belongs to the container and asks the container's flag; this is the wall, not a defect"); + assertEquals(Block.STONE, container.getBlock(20, 40, 20)); + } +} +``` + +The third case asserts a limitation rather than a feature, and it is deliberate: it is the one place where a reader of the class could reasonably expect the per-view flag to act, and the answer has to be recorded rather than discovered. + +- [ ] **Step 2: Run the test** + +```bash +cd /mnt/projects/oss/onelitefeather/Falco-worktrees/shared-instance +./gradlew :falco-instance:test --tests "*FalcoSharedInstanceWriteTest*" +``` + +Expected: three tests, all PASS. Like Task 2, this characterises behaviour that already exists; the bite is proven in step 3. + +- [ ] **Step 3: Prove the test bites, by mutation** + +Add this to `FalcoSharedInstance`, temporarily: + +```java + // TEMPORARY MUTATION — revert after the run + @Override + public @Nullable Chunk getChunk(int chunkX, int chunkZ) { + final Chunk chunk = getInstanceContainer().getChunk(chunkX, chunkZ); + return chunk == null ? null : chunk.copy(this, chunkX, chunkZ); + } +``` + +Re-run. Expected: `testTheChunkIsTheContainersChunk` FAILS on both `assertSame` calls, and `testAWriteReachesEveryView` FAILS because the reads now go through per-view copies. **Delete the mutation** and confirm green. + +- [ ] **Step 4: Extend the class Javadoc** + +Append two `+ * They have to, and the reason is a {@code private} modifier in a foreign class. + * {@code InstanceContainer#UNSAFE_setBlock} is {@code private synchronized} and is called from four + * places: {@code setBlock}, {@code placeBlock}, {@code breakBlock} and the neighbour update that + * runs a block placement rule. Overriding {@code setBlock} here would take over one of the four and + * leave the other three on the private, synchronised path — two write paths over the same chunk + * data, one holding the monitor of the whole instance and one not. That is a race introduced by the + * class which claims to remove one, and it is worth less than what it would buy. + *
+ *+ * A shared world therefore pays the container's monitor on every block write and keeps everything + * the chunk layer gained. A world that needs write throughput and does not need to be shared uses + * {@code FalcoInstance}, which holds the lock of the chunk it touches instead. + *
+ *+ * {@code enableAutoChunkLoad} reaches {@link #loadOptionalChunk(int, int)}, which is the method a + * player's chunk loading goes through, so the flag has an effect a player can observe. It does not + * reach {@code setBlock}: that call is the container's and asks the container's flag, which follows + * directly from the paragraph above. + *
+ *+ * {@code setGenerator} and {@code setChunkSupplier} reach nothing inside Minestom at all. Chunks are + * created by the container, which asks its own generator and its own supplier, and a chunk loader is + * handed the container rather than a view. Both setters exist here so that configuring one view + * stops reconfiguring the world and every other view of it — a repair, not a capability. To decide + * what the world is made of, call them on {@link #getInstanceContainer()}. + *
+``` + +Bump `@version` on the class from `1.0.0` to `1.1.0`, since the documented contract of the type grew after Task 1. + +- [ ] **Step 5: Extend `package-info.java`** + +Insert this paragraph after the one about `FalcoInstanceException` and before the one beginning "This package is about clarity, not throughput.": + +```java + *+ * {@link net.onelitefeather.falco.instance.FalcoSharedInstance} is the one type here which does not + * avoid {@code InstanceContainer} but builds on it. It extends {@code SharedInstance}, because the + * fast path that spares a player a full chunk resend on an instance change is decided by + * {@code SharedInstance#areLinked}, and that method compares containers rather than classes — a + * subclass keeps it, a look-alike does not. What it repairs is the three setters and the save which + * Minestom's shared instance writes through to the container it borrows from. What it does not + * repair is the write path: the block owner is an {@code InstanceContainer} and its instance monitor + * comes with it. + *
+``` + +- [ ] **Step 6: Correct the README** + +Replace the paragraph that currently begins "A foreign instance has to be registered by hand" (in `### Using falco-instance`) with: + +````markdown +A foreign instance has to be registered by hand, which is what `registerInstance` above does. The +chunk supplier stays at `FalcoChunk::new` — the lifecycle hooks a chunk needs to be marked unloaded +are `protected` in Minestom's own package, so any other chunk type is refused rather than accepted +and then left unloadable. The reasoning, and the four places where Minestom quietly treats a foreign +instance differently, are in +[Rationale: Instances and Chunks](https://github.com/OneLiteFeatherNET/Falco/wiki/Rationale-Instances-And-Chunks). + +Shared worlds are the one case this instance cannot serve, because `SharedInstance` takes an +`InstanceContainer` and nothing else. `FalcoSharedInstance` accepts that and builds on the container +instead: + +```java +InstanceManager manager = MinecraftServer.getInstanceManager(); +InstanceContainer world = manager.createInstanceContainer(); +world.setChunkSupplier(FalcoChunk::new); + +// Not manager.createSharedInstance(world): that factory always builds Minestom's own type. +FalcoSharedInstance view = new FalcoSharedInstance(UUID.randomUUID(), world); +manager.registerSharedInstance(view); +``` + +The view keeps its own generator, chunk supplier, auto-load setting and tags, where Minestom's writes +all four through to the container and lets one view reconfigure another. What it does not change is +who owns the blocks: `setBlock` reaches the container, and the container serialises every write on +its own monitor. A world built this way keeps what the Falco chunk saves and keeps the container's +write path; a world that needs the write path uses `FalcoInstance` and gives up sharing. +```` + +- [ ] **Step 7: Record the outcome in the research document** + +`docs/research/shared-instances-and-batches.md` recommended a Falco-owned delegating instance and recorded that `areLinked` would stay false. That recommendation was not taken. Append this subsection at the end of section 1, after *Cost and recommendation*, in the same shape the batch section already uses for its own correction: + +```markdown +#### How it was actually built: neither of the two options above + +The question this section asked was whether a `FalcoInstance` can back a shared instance. It cannot, +and everything above about that is still true. Stage 4 asked a different question and gave up the +premise instead: the block owner is a plain `InstanceContainer` whose chunk supplier is +`FalcoChunk::new`, and `FalcoSharedInstance extends SharedInstance` on top of it. + +With the premise gone, so is the wall. The constructor is satisfied, `InstanceManager +#registerSharedInstance` takes any subclass, `InstanceContainer#addSharedInstance` is reached by +`InstanceManager` from inside its own package and never by Falco, and `areLinked` — which compares +`getInstanceContainer()` and not a class — answers **true**. The measured `areLinked(foreign, +falcoSharedView) = false` in the list above stands as a statement about the delegating view that was +never built; it does not describe what exists. + +The viewer union this section worried about is not needed either. `Chunk`'s constructor takes +`instanceContainer.getSharedInstances()`, and a `FalcoSharedInstance` is in that list, so the players +of a view are viewers of the container's chunks without a line of Falco code. The cost is the one +this section did not price: the container is the block owner, its `UNSAFE_setBlock` is +`private synchronized`, and a shared world therefore keeps the instance monitor on every write. +``` + +- [ ] **Step 8: Run the module suite** + +```bash +cd /mnt/projects/oss/onelitefeather/Falco-worktrees/shared-instance +./gradlew :falco-instance:test +``` + +Expected: all PASS, including everything stages 1 to 3 left behind. + +- [ ] **Step 9: Commit** + +```bash +cd /mnt/projects/oss/onelitefeather/Falco-worktrees/shared-instance +git add falco-instance/src/main/java/net/onelitefeather/falco/instance/FalcoSharedInstance.java \ + falco-instance/src/main/java/net/onelitefeather/falco/instance/package-info.java \ + falco-instance/src/test/java/net/onelitefeather/falco/instance/FalcoSharedInstanceWriteTest.java \ + README.md docs/research/shared-instances-and-batches.md +git commit -m "docs(instance): state that a shared world keeps the container's write monitor" +``` + +--- + +### Task 8: Acceptance + +**Files:** +- Modify: `docs/superpowers/plans/2026-08-02-falco-shared-instance.md` + +**Interfaces:** +- Consumes: everything. +- Produces: a `## Stage 4 result` section in this file. + +- [ ] **Step 1: Run every module** + +```bash +cd /mnt/projects/oss/onelitefeather/Falco-worktrees/shared-instance +./gradlew :falco-instance:test :falco-light:test :falco-anvil:test :falco-demo:test :falco-benchmarks:test --rerun-tasks +``` + +Expected: all PASS. `falco-light` and `falco-anvil` matter because both build chunks into instances; `falco-benchmarks` carries the equivalence and footprint tests of stages 1 and 2 and is the regression net for anything this stage might have disturbed. Record the test counts per module — the stage 2 result records 143 / 189 / 193 / 139 / 38 and a divergence needs explaining rather than accepting. + +- [ ] **Step 2: Check the whole branch compiles as a published artefact** + +```bash +cd /mnt/projects/oss/onelitefeather/Falco-worktrees/shared-instance +./gradlew build -x test +``` + +Expected: PASS, with Javadoc generation included. A missing `@param` or a broken `{@link}` in the new type fails here and not in the test task. + +- [ ] **Step 3: Re-read the four stories against the code** + +Walk US-4.01 to US-4.04 and name, for each, the test method that carries it. If a story has no method name, it is not done. The mapping this plan intends: + +| Story | Carried by | +|---|---| +| US-4.01 | `FalcoSharedInstanceResendTest#testTheFastPathSendsNothing`, backed by `FalcoSharedInstanceTest#testLinkedToItsContainer` and its siblings | +| US-4.02 | `FalcoSharedInstanceStateTest#testTheGeneratorDoesNotAlias` and `#testClearingTheGeneratorDoesNotClearTheContainer` | +| US-4.03 | `FalcoSharedInstanceSaveTest#testTheViewSavesItsOwnTags` | +| US-4.04 | the two `The sentence is repeated in four places — the class Javadoc of + * {@code FalcoSharedInstance}, {@code net.onelitefeather.falco.instance.package-info}, the + * {@code Shared worlds} section of the README and the stage 4 result — and every one of them rests + * on a {@code private} modifier and an {@code ACC_SYNCHRONIZED} flag inside Minestom. + * {@code FalcoSharedInstanceWriteTest} cannot reach any of that: it observes blocks and chunks, so + * all three of its cases stay green if Minestom drops the {@code synchronized}, opens + * {@code UNSAFE_setBlock} up, or grows a fifth caller. The documentation would then be false and + * nothing would say so, which is precisely the failure mode this module exists for. + * + *
These rules read the bytecode of {@code net.minestom.server.instance} instead of Falco's own, + * which is why they sit in a class of their own with its own {@code @AnalyzeClasses} scope rather + * than in {@link ConcurrencyTest}: widening the shared scope would put a few thousand foreign + * classes in front of every rule that only ever wanted to look at Falco. Three of the four rules + * below therefore fail on a Minestom upgrade rather than on a Falco commit — that is the point. The + * message they carry is not "Minestom is wrong" but "the paragraph in {@code FalcoSharedInstance} + * has to be rewritten, and the decision not to override {@code setBlock} has to be taken again". + * + *
Verified against Minestom {@code 2026.06.20-26.1.2} with {@code javap} before the rules were + * written: {@code UNSAFE_setBlock} is {@code private synchronized} at {@code InstanceContainer:149}, + * and the four call sites are in {@code setBlock} ({@code :135}), {@code placeBlock} ({@code :223}), + * {@code breakBlock} ({@code :250}) and {@code executeNeighboursBlockPlacementRule} ({@code :756}). + * The rules assert exactly that, so all four were green on their first run and each had to be proved + * to bite on its own: W1 by pointing {@link #UNSAFE_SET_BLOCK} at {@code setBlock}, which is neither + * private nor synchronised; W2 by swapping {@code breakBlock} for {@code loadChunk} in + * {@link #KNOWN_WRITE_ENTRY_POINTS}, which reported one missing and one unexpected caller; W3 by + * demanding the forward go to {@code Chunk}; W4 by giving {@code FalcoSharedInstance} an override of + * {@code setBlock} that does nothing but call {@code super}. That last mutation left all three cases + * of {@code FalcoSharedInstanceWriteTest} green, which is the gap this class was written to close. + * + *
One structural limit, the same one {@link ConcurrencyTest} names: ArchUnit models members and
+ * accesses, never {@code monitorenter}. W1 sees the {@code ACC_SYNCHRONIZED} flag on the method. If
+ * Minestom ever moved the same lock into a {@code synchronized (this)} block inside the body, W1
+ * would go red although nothing had changed for a caller — which is a false alarm on the safe side,
+ * because the paragraph would still have to be re-read.
+ *
+ * @author TheMeinerLP
+ * @version 1.0.0
+ * @since 0.4.0
+ */
+@AnalyzeClasses(
+ packages = {"net.minestom.server.instance", "net.onelitefeather.falco.instance"},
+ importOptions = ImportOption.DoNotIncludeTests.class)
+class ForeignWritePathTest {
+
+ static final String INSTANCE_CONTAINER = "net.minestom.server.instance.InstanceContainer";
+ static final String SHARED_INSTANCE = "net.minestom.server.instance.SharedInstance";
+ static final String FALCO_SHARED_INSTANCE =
+ "net.onelitefeather.falco.instance.FalcoSharedInstance";
+
+ /** The private, synchronised method every block write of a container ends in. */
+ static final String UNSAFE_SET_BLOCK = "UNSAFE_setBlock";
+
+ /**
+ * The three write entry points a subclass could take over, and the neighbour update it could
+ * not. {@code executeNeighboursBlockPlacementRule} is {@code private} itself and runs a block
+ * placement rule on the four horizontal neighbours of a placed block; it is the reason an
+ * override of {@code setBlock} cannot be complete even if the other two were overridden as well.
+ */
+ static final Set Read from {@code getAccessesToSelf()} rather than from the call sites, so a method
+ * reference — {@code this::UNSAFE_setBlock} handed to something else — counts as a caller too.
+ */
+ static final ArchCondition
+ * This is a subclass of {@link SharedInstance} rather than a type of its own, and the reason is a
+ * single static method. {@code SharedInstance#areLinked} decides whether a player who moves between
+ * two instances keeps the chunks it already has or receives all of them again, and it is consulted
+ * in exactly one place: {@code Player#setInstance}. It compares
+ * {@link SharedInstance#getInstanceContainer()} rather than testing for a concrete class, so a
+ * subclass inherits the answer. A separate type with the same behaviour would answer false, nothing
+ * would fail, nothing would be logged, and every instance change would silently cost a full resend
+ * of the view distance.
+ *
+ * The price of the subclass is that the block owner has to be an {@link InstanceContainer}: that is
+ * the only constructor {@link SharedInstance} has. A world used this way therefore sets its chunk
+ * supplier to {@code FalcoChunk::new} on the container and keeps everything stages one and two
+ * bought at the chunk, while the container keeps its own write path.
+ *
+ * Registration goes through {@code InstanceManager#registerSharedInstance}. Its sibling
+ * {@code createSharedInstance} always constructs the stock type and can never produce this one, and
+ * {@code registerInstance} refuses anything that is a {@link SharedInstance} outright.
+ *
+ * That route change costs a check, and the constructor pays it back.
+ * {@code createSharedInstance} refuses a container which is not registered
+ * ({@code Check.stateCondition(!instanceContainer.isRegistered(), …)}); {@code registerSharedInstance},
+ * the only route which can register this type, does not. The failure it lets through is quiet: an
+ * unregistered container is in no {@code InstanceManager}, so {@code ServerProcess} never ticks it,
+ * so {@code InstanceContainer#tick} never clears {@code currentlyChangingBlocks} — every repeat
+ * write of the same block value at a position is suppressed for good and that map grows without
+ * bound, while the view itself ticks normally and looks healthy. The constructor therefore refuses
+ * an unregistered container, which is the guard the abandoned route performed.
+ *
+ * They have to, and the reason is a {@code private} modifier in a foreign class.
+ * {@code InstanceContainer#UNSAFE_setBlock} is {@code private synchronized} and is called from four
+ * places: {@code setBlock}, {@code placeBlock}, {@code breakBlock} and the neighbour update that
+ * runs a block placement rule. Overriding {@code setBlock} here would take over one of the four and
+ * leave the other three on the private, synchronised path — two write paths over the same chunk
+ * data, one holding the monitor of the whole instance and one not. That is a race introduced by the
+ * class which claims to remove one, and it is worth less than what it would buy.
+ *
+ * A shared world therefore pays the container's monitor on every block write and keeps everything
+ * the chunk layer gained. A world that needs write throughput and does not need to be shared uses
+ * {@code FalcoInstance}, which holds the lock of the chunk it touches instead.
+ *
+ * Both paragraphs are statements about a foreign class, so they have a guard of their own:
+ * {@code ForeignWritePathTest} in {@code falco-archunit} reads the bytecode of
+ * {@code InstanceContainer} and fails if {@code UNSAFE_setBlock} stops being private, stops carrying
+ * {@code ACC_SYNCHRONIZED}, or is reached from anywhere but those four methods; it also fails if
+ * this class ever overrides one of the write entry points after all. Without it the reasoning here
+ * would rest on a reading of Minestom taken on a single day, because every case of
+ * {@code FalcoSharedInstanceWriteTest} observes blocks and chunks and stays green through all four
+ * of those changes.
+ *
+ * {@code enableAutoChunkLoad} reaches {@link #loadOptionalChunk(int, int)}, which is the method a
+ * player's chunk loading goes through, so the flag has an effect a player can observe. It does not
+ * reach {@code setBlock}: that call is the container's and asks the container's flag, which follows
+ * directly from the paragraph above.
+ *
+ * {@code setGenerator} and {@code setChunkSupplier} reach nothing inside Minestom at all. Chunks are
+ * created by the container, which asks its own generator and its own supplier, and a chunk loader is
+ * handed the container rather than a view. Both setters exist here so that configuring one view
+ * stops reconfiguring the world and every other view of it — a repair, not a capability. To decide
+ * what the world is made of, call them on {@link #getInstanceContainer()}.
+ *
+ * Volatile because a shared instance is configured from wherever the world is set up and read
+ * from wherever it is asked, and those are not the same thread.
+ *
+ * Volatile for the same reason the generator is: the thread which configures a view is not the
+ * thread which reads it back.
+ *
+ * Volatile for the reason the other two fields are: a view is configured from wherever the world
+ * is set up and read from the thread which happens to ask it for a chunk.
+ *
+ * The configuration of the container is copied once, here. That is what makes a fresh view
+ * behave like the world it looks at while still being able to diverge from it — the alternative,
+ * starting empty, would answer {@code null} to {@link #generator()} on a world that has one.
+ *
+ * The container has to be registered already, and this is the one place that can insist on it:
+ * the registration route this class uses does not check, and the route that checks cannot
+ * construct this class. An unregistered container is never ticked, and the consequences of that
+ * are in the class documentation. Register the container first —
+ * {@code InstanceManager#createInstanceContainer} does it for you — then construct the view.
+ *
+ * The answer is what {@link #setGenerator(Generator)} last stored on this view, or the
+ * generator the container carried when this view was created — never a read of the container as
+ * it stands now. That is the repair: two views over one container answer independently instead
+ * of overwriting each other. It is also the limit: no chunk is ever generated from this value,
+ * because chunks are created by the container and the container asks its own generator. Ask
+ * {@code getInstanceContainer().generator()} for the one that actually builds the world.
+ *
+ * Minestom's shared instance forwards this call to its container, which means that configuring
+ * one view reconfigures the world and every other view of it. That is the defect this class
+ * exists to repair, and repairing it has a consequence worth stating: no chunk is generated from
+ * this value. Chunks are created by the container, and the container asks its own generator. Use
+ * {@code getInstanceContainer().setGenerator(…)} to decide what the world is made of.
+ *
+ * The answer is what {@link #setChunkSupplier(ChunkSupplier)} last stored on this view, or the
+ * supplier the container carried when this view was created — never a read of the container as it
+ * stands now. Two views over one container therefore answer independently instead of overwriting
+ * each other. The value is also inert: no chunk is ever created from it, because chunks are
+ * created by the container, which asks its own supplier, and a chunk loader is handed the
+ * container rather than the view. Ask {@code getInstanceContainer().getChunkSupplier()} for the
+ * one the world is actually built from.
+ *
+ * Minestom's shared instance forwards this call to its container, so configuring one view
+ * changes what type of chunk the whole world is made of, for every other view along with it.
+ * That is the defect repaired here, and the repair has the same consequence the generator has:
+ * no chunk is created from this value, because chunks are created by the container and the
+ * container asks its own supplier — as does a chunk loader, which is handed the container rather
+ * than the view. Use {@code getInstanceContainer().setChunkSupplier(FalcoChunk::new)} to decide
+ * what the world is built from.
+ *
+ * Minestom's shared instance forwards this to its container, which turns a per-view decision
+ * into a per-world one. Here the value stays with the view, so two views over one container no
+ * longer overwrite each other. The one method that reads it is
+ * {@link #loadOptionalChunk(int, int)}; it does not reach {@code setBlock} — that call
+ * belongs to the container and asks the container's flag, for the reason given in the class
+ * documentation.
+ *
+ * The separation holds in the other direction too, and that half is the one that surprises:
+ * calling this on the container no longer reaches a view which already exists, because the view
+ * read the setting once, in its constructor. Turning chunk loading off for a whole world
+ * therefore means calling this method on every view of it — what a view does while the
+ * container's flag is off is written out at {@link #loadOptionalChunk(int, int)}.
+ *
+ * One method is not a small reach. Minestom routes the player view, entity spawns, entity
+ * teleports and player instance changes through {@link #loadOptionalChunk(int, int)}, and most
+ * of those callers do not survive the {@code null} it hands back once this is off — the list is
+ * on that method. Passing {@code false} therefore configures a failure mode rather than a
+ * saving, and because the value is now the view's, it is a failure mode stock
+ * {@link SharedInstance} could only produce for a whole world at once. Use it on a view whose
+ * chunks are brought in through the container beforehand.
+ *
+ * The answer is what {@link #enableAutoChunkLoad(boolean)} last stored on this view, or the
+ * setting the container carried when this view was created — never a read of the container as it
+ * stands now. Two views over one container therefore answer independently. Unlike the generator
+ * and the chunk supplier this value is not inert: {@link #loadOptionalChunk(int, int)} consults
+ * it, and every Minestom path that wants a chunk this world has not got yet goes through that
+ * method — five call sites, enumerated there. So the players and entities of this view, and of
+ * no other view, feel the answer. It stops at the block write: {@code setBlock} is forwarded to
+ * the container and asks {@code getInstanceContainer().hasEnabledAutoChunkLoad()}.
+ *
+ * A chunk the container already holds is handed back whatever the flag says: the flag decides
+ * whether this view may cause a load, not whether it may see the world. Only the second branch
+ * consults it, and only that branch is a decision this instance is entitled to make — the chunk
+ * itself is still created, cached and published by the container.
+ *
+ * Handing back {@code null} is not a clean skip anywhere in Minestom. Five call sites reach this
+ * method, and each of them treats the absent chunk differently:
+ *
+ * Stock {@link SharedInstance} can reach every one of those states too, but only for a whole
+ * world at a time, because the flag it asks belongs to the container. Per view is the new part:
+ * this view can be that strict while its siblings and the container are not, and that is the
+ * consequence to weigh before turning the flag off.
+ *
+ * The flag consulted is this view's alone, which has a consequence in the other direction: a view
+ * whose flag is on loads a chunk even where the container's own flag is off, because the load
+ * itself is delegated to {@code InstanceContainer#loadChunk} and an explicit load was never
+ * governed by that flag either.
+ *
+ * Reaching that state takes no deliberate act, and this is the ordering to read twice. The value
+ * is a snapshot taken in the constructor, so a container which turns its own flag off
+ * after its views exist stops none of them: every view keeps the {@code true} it was
+ * seeded with, and each of them goes on pulling chunks into the container that the container is
+ * refusing to pull for itself — where the container and every sibling view then see them. Stock
+ * {@link SharedInstance} could not produce that, because it asked the container on every call:
+ * the container's off switch was authoritative for every view the instant it was thrown. Here it
+ * governs the container's own {@link #loadOptionalChunk(int, int)} and the views constructed
+ * after it, and nothing else. A shutdown or save path which stops chunks arriving by calling
+ * {@code container.enableAutoChunkLoad(false)} therefore has to call it on every view as well.
+ *
+ * Minestom's shared instance forwards this to {@code InstanceContainer#saveInstance()}, which
+ * hands the loader the container. The tags of the view are therefore never written and the call
+ * still reports success — the anvil loader writes {@code instance.tagHandler().asCompound()} of
+ * whatever it was given. Reaching the loader directly and handing it {@code this} is the whole
+ * of the repair.
+ *
+ * The repair has the consequence every other override in this class has, and it is the one to
+ * read twice: this call no longer writes the data of the container. Stock
+ * {@link SharedInstance} saved the container here, so code which called it on a view and got the
+ * world written is now writing the view instead — and a view starts with an empty tag handler,
+ * because the constructor copies the container's configuration but not its tags and
+ * {@link SharedInstance} does not share one. An {@code AnvilLoader} handed an empty compound
+ * returns without touching the file, so on a view that was never tagged this call writes nothing
+ * at all. Call {@code getInstanceContainer().saveInstance()} to write the data of the world.
+ *
+ * What it does not repair: a loader writes to one place per world. An
+ * {@code AnvilLoader} puts instance data in a single {@code level.dat}, so a container and every
+ * view of it write over one another and the last save wins. Saving one view of a world is
+ * therefore meaningful; saving several and expecting to read all of them back is not.
+ *
+ * A failure is reported to the caller and to nobody else, and that is a second deviation from
+ * {@code InstanceContainer}. Its {@code optionalAsync} hands a parallel failure to
+ * {@code MinecraftServer.getExceptionManager()} as well as to the future, and throws a
+ * synchronous one at the call site instead of returning it. Here both branches do the same
+ * thing: the returned future is completed exceptionally, the {@code ExceptionManager} is not
+ * told, and nothing is thrown out of this method. The consequence is the caller's to carry.
+ * Firing this call and not observing the future is a save that failed in silence — stock would
+ * at least have logged it — and a {@code try}/{@code catch} around the call no longer catches
+ * anything, because the synchronous branch does not throw either. Observe the future.
+ * {@code FalcoInstance#runSave} makes the same choice, so a code base using both is told once.
+ *
+ * {@link net.onelitefeather.falco.instance.FalcoSharedInstance} is the one type here which does not
+ * avoid {@code InstanceContainer} but builds on it. It extends {@code SharedInstance}, because the
+ * fast path that spares a player a full chunk resend on an instance change is decided by
+ * {@code SharedInstance#areLinked}, and that method compares containers rather than classes — a
+ * subclass keeps it, a look-alike does not. What it repairs is the three setters and the save which
+ * Minestom's shared instance writes through to the container it borrows from. What it does not
+ * repair is the write path: the block owner is an {@code InstanceContainer} and its instance monitor
+ * comes with it.
+ *
* This package is about clarity, not throughput. The parallelism of chunk and entity ticking lives
* in the global {@code ThreadDispatcher} of the server process, so no instance implementation can
* change it. What does change is that a block write is guarded by the lock of the chunk it touches
diff --git a/falco-instance/src/test/java/net/onelitefeather/falco/instance/FalcoSharedInstanceResendTest.java b/falco-instance/src/test/java/net/onelitefeather/falco/instance/FalcoSharedInstanceResendTest.java
new file mode 100644
index 0000000..e64ae42
--- /dev/null
+++ b/falco-instance/src/test/java/net/onelitefeather/falco/instance/FalcoSharedInstanceResendTest.java
@@ -0,0 +1,132 @@
+package net.onelitefeather.falco.instance;
+
+import net.minestom.server.coordinate.Pos;
+import net.minestom.server.entity.Player;
+import net.minestom.server.instance.InstanceContainer;
+import net.minestom.server.network.packet.server.play.ChunkDataPacket;
+import net.minestom.server.network.packet.server.play.UnloadChunkPacket;
+import net.minestom.server.network.packet.server.play.UpdateViewPositionPacket;
+import net.minestom.testing.Collector;
+import net.minestom.testing.Env;
+import net.minestom.testing.TestConnection;
+import net.minestom.testing.extension.MicrotusExtension;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import java.util.UUID;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertSame;
+
+/**
+ * Asserts what US-4.01 actually asks for: a player moving into a shared instance receives no chunk
+ * traffic at all.
+ *
+ * {@code areLinked} is the mechanism and is covered next door; this class covers the outcome, so
+ * that a change to {@code Player#setInstance} is caught here instead of costing a full resend per
+ * transfer in production.
+ *
+ * {@code ChunkDataPacket} is the direct measure of a resend, and here it does read a real number.
+ * Cyano installs {@code net.minestom.testing.TestPlayerImpl} as the player provider of every test
+ * connection, and that class overrides {@code Player#sendChunk(Chunk)} to push the full data packet
+ * out at once instead of queueing it. {@code chunkAdder} dispatches virtually, so the batching that
+ * would otherwise swallow the counter — a {@code chunkBatchLead} that {@code resetChunkQueue} never
+ * clears, plus a test connection that never acknowledges a batch — is never reached. The unlinked
+ * control below measures 25 of them, one per chunk of the 5x5 view that {@code viewDistance(1)}
+ * produces. That makes the empty chunk collector in the fast-path test the strongest and most
+ * direct US-4.01 statement in this file, not a decorative one.
+ *
+ * {@code UpdateViewPositionPacket} and {@code UnloadChunkPacket} are asserted next to it because
+ * they leave {@code Player#spawnPlayer} unconditionally under {@code updateChunks == true},
+ * independently of how chunk bodies are delivered. They keep the file honest if a future Cyano
+ * stops overriding {@code sendChunk} — at which point the chunk counter would silently drop to zero
+ * on both paths, and the control's {@code assertCount(25)} would say so instead of hiding it.
+ *
+ * The defect this covers is silent by construction: Minestom's shared instance forwards the call to
+ * its container, the container hands itself to the loader, the loader writes the container's tags,
+ * and the operation reports success. Nothing is lost that anyone could notice at the time — the tags
+ * of the view are simply never written. So the assertion has to be on the argument the loader
+ * received, not on whether the call succeeded.
+ *
+ * The two cases which follow cover the halves the first two never enter: a loader that saves in
+ * parallel, and a loader that throws. The second pins a deviation from {@code InstanceContainer},
+ * which hands a parallel failure to the {@code ExceptionManager} and to the returned
+ * future — handled twice, logged twice. Here the caller is the only one told.
+ *
+ * The last case pins the other edge of the same repair: a view is constructed with an empty tag
+ * handler, so an untagged view hands the loader an empty compound. That is what the method's
+ * documentation warns about — an {@code AnvilLoader} returns on an empty compound without touching
+ * the file, so the container's data has to be saved through the container.
+ *
+ * The thread is recorded because {@code supportsParallelSaving} is what selects between the two
+ * halves of the method under test, and only the recorded thread tells the halves apart from the
+ * outside — both of them complete the same future with the same value.
+ *
+ * The aliasing cases here use two shared instances over one container and inspect the one
+ * which was not touched. A case that only looked at the instance it had just configured would be
+ * green with the defect in place, because the defect is not that the value is lost — it is that the
+ * value lands somewhere else as well.
+ *
+ * The three snapshot cases run the same relation backwards, with one view and the container: they
+ * change the container after the view exists and assert that the view does not follow. That
+ * direction is the one the class documentation asserts three times over — "never a read of the
+ * container as it stands now" — and it is not implied by the aliasing cases, which all configure
+ * before or without a container change. The auto-load one goes as far as a chunk, because there the
+ * divergence is not an answer but a chunk pulled into a container which was refusing to load one.
+ *
+ * The second half is the one that matters. {@code areLinked} is consulted in exactly one place,
+ * {@code Player#setInstance}, and when it answers false the player receives every chunk in view
+ * distance again. Nothing fails, nothing logs, the world simply costs a full resend per instance
+ * change. A test is the only thing that notices.
+ *
+ * The first case guards the other end of the same route change: {@code createSharedInstance} refuses
+ * an unregistered container and {@code registerSharedInstance} does not, so the constructor has to,
+ * and the case asserts both halves of that asymmetry rather than only the throw.
+ *
+ * The block owner is the container, its {@code UNSAFE_setBlock} is private and synchronised on the
+ * instance, and it is reached from four places of which {@code setBlock} is only one. Overriding
+ * {@code setBlock} here would leave the other three on the private path and create two write paths
+ * over one chunk, one of them unsynchronised. This class asserts the consequences of not doing that,
+ * so that the documentation which states them cannot quietly stop being true.
+ *
+ * It pins the observable half only, and the distinction matters: these three cases look at blocks
+ * and chunks, so they stay green if Minestom drops the {@code synchronized}, opens
+ * {@code UNSAFE_setBlock} up or gives it a fifth caller — and they stayed green when an override of
+ * {@code setBlock} was added to {@code FalcoSharedInstance} as a mutation. The premise itself is
+ * pinned by {@code ForeignWritePathTest} in {@code falco-archunit}, which reads the modifiers and
+ * the caller set out of Minestom's bytecode. Neither class replaces the other.
+ * Why writes still serialise on the container
+ * What the per-instance state reaches, and what it does not
+ * What a null answer costs its caller
+ *
+ *
+ * Which marker carries the weight
+ *