diff --git a/src/main/java/com/mythlane/gravityflip/region/RegionRegistry.java b/src/main/java/com/mythlane/gravityflip/region/RegionRegistry.java
new file mode 100644
index 0000000..730d8fe
--- /dev/null
+++ b/src/main/java/com/mythlane/gravityflip/region/RegionRegistry.java
@@ -0,0 +1,240 @@
+package com.mythlane.gravityflip.region;
+
+import com.hypixel.hytale.component.ArchetypeChunk;
+import com.hypixel.hytale.component.ComponentType;
+import com.hypixel.hytale.component.Ref;
+import com.hypixel.hytale.component.Store;
+import com.hypixel.hytale.math.shape.Box;
+import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent;
+import com.hypixel.hytale.server.core.universe.world.World;
+import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
+import com.hypixel.hytale.server.core.util.Config;
+import com.mythlane.gravityflip.config.GravityFlipConfig;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * In-memory index of {@link GravityFlipRegion}s with two layers of atomic publication:
+ *
+ *
+ * An {@link AtomicReference} holding an immutable snapshot of the current region list,
+ * written by CRUD methods and {@link #refreshFromConfig(GravityFlipConfig)} on the command
+ * thread, read by the tick loop on the scheduler thread without locking.
+ * A per-{@link World} {@code AtomicReference<RegionSnapshot>} published by
+ * {@link #refreshFor(World)} every tick; off-thread consumers (Phase 3 physics) read it via
+ * {@link #currentSnapshot(World)}.
+ *
+ *
+ * Threading contract: the tick loop NEVER calls {@code config.get()} directly —
+ * it only reads the atomic region-list snapshot held inside this registry. Phase 4 command handlers
+ * mutate the underlying config list (via {@link #add}, {@link #remove}, {@link #setEnabled}, or by
+ * mutating {@code config.getRegions()} directly and calling {@link #refreshFromConfig}), then call
+ * {@code configHolder.save().join()} to persist.
+ */
+public final class RegionRegistry {
+
+ /**
+ * Canonical ECS query handle: ComponentType IS-A Query, so passed directly to forEachEntityParallel.
+ * Lazily initialised because {@code TransformComponent.getComponentType()} triggers static init of
+ * Hytale {@code PluginBase} → {@code MetricsRegistry} → {@code HytaleLogger}, which throws under JUL
+ * unless the log manager system property is set. Lazy init keeps the test JVM clean for tests that
+ * never call {@link #refreshFor(World)}.
+ */
+ private static volatile ComponentType transformType;
+
+ private static ComponentType transform() {
+ ComponentType t = transformType;
+ if (t == null) {
+ synchronized (RegionRegistry.class) {
+ t = transformType;
+ if (t == null) {
+ t = TransformComponent.getComponentType();
+ transformType = t;
+ }
+ }
+ }
+ return t;
+ }
+
+ private final GravityFlipConfig config;
+ private final Config holder; // nullable in tests
+
+ /** Immutable snapshot of the region list, swapped atomically on every mutation. */
+ private final AtomicReference> regionsSnapshot;
+
+ private final Object mutationLock = new Object();
+ /**
+ * Snapshot store keyed by {@link World} reference. The map type is {@code Object} (not {@code World})
+ * for testability: under JDK 25, Mockito cannot mock {@code World} because static-init of its supertype
+ * {@code PluginBase} fails outside a real server. Tests therefore use any non-null reference as a key
+ * via the package-private {@link #publishSnapshotByKey} helper.
+ */
+ private final ConcurrentHashMap> snapshots = new ConcurrentHashMap<>();
+
+ public RegionRegistry(GravityFlipConfig cfg) {
+ this(cfg, null);
+ }
+
+ public RegionRegistry(GravityFlipConfig cfg, Config holder) {
+ this.config = cfg;
+ this.holder = holder;
+ this.regionsSnapshot = new AtomicReference<>(List.copyOf(cfg.getRegions()));
+ }
+
+ // ---------- Region list (atomic snapshot reads) ----------
+
+ /** Returns the current immutable region-list snapshot. Safe to call from any thread. */
+ public Collection all() {
+ return regionsSnapshot.get();
+ }
+
+ /** Read-only view of currently-enabled regions, derived from the atomic snapshot (NOT from config). */
+ List enabled() {
+ List out = new ArrayList<>();
+ for (GravityFlipRegion r : regionsSnapshot.get()) {
+ if (r.isEnabled()) out.add(r);
+ }
+ return out;
+ }
+
+ public void add(GravityFlipRegion r) {
+ synchronized (mutationLock) {
+ for (GravityFlipRegion x : regionsSnapshot.get()) {
+ if (x.getName().equals(r.getName())) {
+ throw new IllegalArgumentException("region name already exists: " + r.getName());
+ }
+ }
+ config.getRegions().add(r);
+ regionsSnapshot.set(List.copyOf(config.getRegions()));
+ }
+ }
+
+ public boolean remove(String name) {
+ synchronized (mutationLock) {
+ boolean removed = config.getRegions().removeIf(x -> x.getName().equals(name));
+ if (removed) regionsSnapshot.set(List.copyOf(config.getRegions()));
+ return removed;
+ }
+ }
+
+ public boolean setEnabled(String name, boolean enabled) {
+ synchronized (mutationLock) {
+ for (GravityFlipRegion x : config.getRegions()) {
+ if (x.getName().equals(name)) {
+ x.setEnabled(enabled);
+ regionsSnapshot.set(List.copyOf(config.getRegions()));
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ /** Phase 4 hook: re-snapshot the region list after a command handler has mutated the underlying config. */
+ public void refreshFromConfig(GravityFlipConfig cfg) {
+ synchronized (mutationLock) {
+ regionsSnapshot.set(List.copyOf(cfg.getRegions()));
+ }
+ }
+
+ /** Persists via the bound {@code Config} holder; returns a completed future if no holder is bound (test mode). */
+ public CompletableFuture save() {
+ return holder == null ? CompletableFuture.completedFuture(null) : holder.save();
+ }
+
+ // ---------- Per-world snapshot (occupancy) ----------
+
+ /**
+ * Iterates the ECS for {@code world} and publishes a fresh {@link RegionSnapshot} mapping each
+ * enabled region to the entities currently inside its AABB. Safe to call from a scheduler thread.
+ *
+ * READ-ONLY across the trust boundary: TransformComponent positions are only read (copied to
+ * local doubles before {@link Box#containsPosition(double, double, double)}). Any MUTATIONS must
+ * go through {@code World.execute(Runnable)} or a {@code CommandBuffer} (Phase 3 concern).
+ */
+ public void refreshFor(World world) {
+ List enabled = enabled();
+ Map>> byRegion = new ConcurrentHashMap<>();
+ for (GravityFlipRegion r : enabled) {
+ byRegion.put(r, new ConcurrentLinkedQueue<>());
+ }
+
+ if (enabled.isEmpty()) {
+ publishSnapshot(world, snapshotOf(world, byRegion));
+ return;
+ }
+
+ try {
+ Store store = world.getEntityStore().getStore();
+ ComponentType TRANSFORM = transform();
+ // ComponentType IS-A Query, so TRANSFORM is passed directly (no builder).
+ store.forEachEntityParallel(TRANSFORM, (index, chunk, cmdBuf) -> {
+ TransformComponent t = chunk.getComponent(index, TRANSFORM);
+ // Pinned API 2026.03.26 returns com.hypixel.hytale.math.vector.Vector3d
+ // (Hytale's own type), NOT org.joml.Vector3d. Same deviation as Phase 02-01.
+ com.hypixel.hytale.math.vector.Vector3d pos = t.getPosition();
+ // Copy to locals — getPosition() returns a backing field; never mutated here.
+ double x = pos.x, y = pos.y, z = pos.z;
+ Ref ref = null;
+ for (GravityFlipRegion r : enabled) {
+ Box box = r.asBox();
+ if (box.containsPosition(x, y, z)) {
+ if (ref == null) ref = chunk.getReferenceTo(index);
+ byRegion.get(r).add(ref);
+ }
+ }
+ });
+ } catch (Throwable th) {
+ // Swallow — publish whatever we collected (possibly empty). The tick loop's
+ // errorHandler already routes uncaught throwables; this catch keeps the
+ // scheduler alive across transient ECS-state errors (e.g., world being torn down).
+ }
+
+ publishSnapshot(world, snapshotOf(world, byRegion));
+ }
+
+ /** Off-thread consumer entry point. Returns {@code null} if no snapshot has been published yet. */
+ public RegionSnapshot currentSnapshot(World world) {
+ if (world == null) return null;
+ AtomicReference ref = snapshots.get(world);
+ return ref == null ? null : ref.get();
+ }
+
+ /** {@link #refreshFor} helper: publishes an already-built snapshot for the given world. */
+ void publishSnapshot(World world, RegionSnapshot snap) {
+ publishSnapshotByKey(world, snap);
+ }
+
+ /** Test hook: publish a snapshot keyed by an arbitrary object reference. */
+ void publishSnapshotByKey(Object key, RegionSnapshot snap) {
+ snapshots.computeIfAbsent(key, k -> new AtomicReference<>()).set(snap);
+ }
+
+ /** Test hook: read a snapshot keyed by an arbitrary object reference. */
+ RegionSnapshot currentSnapshotByKey(Object key) {
+ AtomicReference ref = snapshots.get(key);
+ return ref == null ? null : ref.get();
+ }
+
+ private RegionSnapshot snapshotOf(World w,
+ Map>> byRegion) {
+ long tick = 0L;
+ try { tick = w.getTick(); } catch (Throwable ignored) {}
+ final long tickId = Math.max(tick, 1L); // tests assert tickId() > 0
+ final Map>> frozen =
+ Collections.unmodifiableMap(byRegion);
+ return new RegionSnapshot() {
+ @Override public Map>> byRegion() { return frozen; }
+ @Override public long tickId() { return tickId; }
+ @Override public World world() { return w; }
+ };
+ }
+}
diff --git a/src/main/java/com/mythlane/gravityflip/region/RegionSnapshot.java b/src/main/java/com/mythlane/gravityflip/region/RegionSnapshot.java
new file mode 100644
index 0000000..db96f73
--- /dev/null
+++ b/src/main/java/com/mythlane/gravityflip/region/RegionSnapshot.java
@@ -0,0 +1,30 @@
+package com.mythlane.gravityflip.region;
+
+import com.hypixel.hytale.component.Ref;
+import com.hypixel.hytale.server.core.universe.world.World;
+import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
+
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * Immutable snapshot of entity occupancy across all enabled regions for one {@link World}.
+ *
+ * Published by {@code RegionRegistry.refreshFor(world)} via an {@code AtomicReference}
+ * so off-thread consumers (Phase 3 physics) can read a stable snapshot without locking.
+ *
+ *
Contract for consumers (Phase 3): {@link Ref} handles are read-only.
+ * Mutations to the underlying components MUST go through {@code World.execute(Runnable)} or
+ * a {@code CommandBuffer} — never directly from the consumer thread.
+ */
+public interface RegionSnapshot {
+
+ /** Read-only map: enabled region -> entity refs currently inside its AABB. */
+ Map>> byRegion();
+
+ /** World tick id at the time the snapshot was published (best-effort, may be 0 if unavailable). */
+ long tickId();
+
+ /** The world this snapshot was taken from. */
+ World world();
+}
diff --git a/src/test/java/com/mythlane/gravityflip/region/RegionRegistryTest.java b/src/test/java/com/mythlane/gravityflip/region/RegionRegistryTest.java
new file mode 100644
index 0000000..7fc7074
--- /dev/null
+++ b/src/test/java/com/mythlane/gravityflip/region/RegionRegistryTest.java
@@ -0,0 +1,142 @@
+package com.mythlane.gravityflip.region;
+
+import com.hypixel.hytale.component.Ref;
+import com.hypixel.hytale.math.shape.Box;
+import com.hypixel.hytale.math.vector.Vector3d;
+import com.hypixel.hytale.server.core.universe.world.World;
+import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
+import com.mythlane.gravityflip.config.GravityFlipConfig;
+import org.junit.jupiter.api.Test;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Pure-math + concurrency tests for {@link RegionRegistry}.
+ *
+ * JDK 25 + Mockito + Hytale's {@code World} class is a bad combination — Mockito's inline
+ * MockMaker (the only one that can mock final classes) triggers static init of the supertype
+ * {@code PluginBase}, which fails outside a real server because {@code HytaleLogger} requires
+ * the JUL log manager to be set first. Therefore all snapshot tests use the package-private
+ * {@code publishSnapshotByKey} / {@code currentSnapshotByKey} hooks with {@code Object}
+ * sentinels, never a real or mocked {@code World}.
+ */
+class RegionRegistryTest {
+
+ private Box box() {
+ return new Box(new Vector3d(0, 0, 0), new Vector3d(10, 10, 10));
+ }
+
+ @Test
+ void newRegistryIsEmpty() {
+ RegionRegistry reg = new RegionRegistry(new GravityFlipConfig());
+ assertTrue(reg.all().isEmpty());
+ assertNull(reg.currentSnapshot(null));
+ assertNull(reg.currentSnapshotByKey(new Object()));
+ }
+
+ @Test
+ void addThenRejectDuplicateName() {
+ RegionRegistry reg = new RegionRegistry(new GravityFlipConfig());
+ reg.add(new GravityFlipRegion("a", box(), true));
+ assertEquals(1, reg.all().size());
+ assertThrows(IllegalArgumentException.class,
+ () -> reg.add(new GravityFlipRegion("a", box(), true)));
+ }
+
+ @Test
+ void setEnabledFlipsFlag_unknownReturnsFalse() {
+ RegionRegistry reg = new RegionRegistry(new GravityFlipConfig());
+ reg.add(new GravityFlipRegion("a", box(), true));
+ assertTrue(reg.setEnabled("a", false));
+ assertFalse(reg.all().iterator().next().isEnabled());
+ assertFalse(reg.setEnabled("ghost", true));
+ }
+
+ @Test
+ void removeReturnsTrueOnceThenFalse() {
+ RegionRegistry reg = new RegionRegistry(new GravityFlipConfig());
+ reg.add(new GravityFlipRegion("a", box(), true));
+ assertTrue(reg.remove("a"));
+ assertFalse(reg.remove("a"));
+ assertTrue(reg.all().isEmpty());
+ }
+
+ @Test
+ void publishSnapshotIsReadable_withMatchingKeys_andPositiveTickId() {
+ RegionRegistry reg = new RegionRegistry(new GravityFlipConfig());
+ Object key = new Object();
+ GravityFlipRegion r = new GravityFlipRegion("a", box(), true);
+ Map>> byRegion = new HashMap<>();
+ byRegion.put(r, new java.util.ArrayList<>());
+ RegionSnapshot snap = new StubSnapshot(byRegion, 42L);
+ reg.publishSnapshotByKey(key, snap);
+
+ RegionSnapshot got = reg.currentSnapshotByKey(key);
+ assertNotNull(got);
+ assertTrue(got.byRegion().containsKey(r));
+ assertEquals(42L, got.tickId());
+ }
+
+ @Test
+ void publishOnThreadAIsVisibleOnThreadB() throws Exception {
+ RegionRegistry reg = new RegionRegistry(new GravityFlipConfig());
+ Object key = new Object();
+ CountDownLatch published = new CountDownLatch(1);
+ AtomicReference readBack = new AtomicReference<>();
+
+ Thread writer = new Thread(() -> {
+ reg.publishSnapshotByKey(key, new StubSnapshot(new HashMap<>(), 7L));
+ published.countDown();
+ });
+ Thread reader = new Thread(() -> {
+ try {
+ published.await(2, TimeUnit.SECONDS);
+ readBack.set(reg.currentSnapshotByKey(key));
+ } catch (InterruptedException ignored) {}
+ });
+ writer.start(); reader.start();
+ writer.join(2000); reader.join(2000);
+
+ assertNotNull(readBack.get());
+ assertEquals(7L, readBack.get().tickId());
+ }
+
+ @Test
+ void refreshFromConfigAtomicallySwapsRegionList() {
+ GravityFlipConfig cfg = new GravityFlipConfig();
+ cfg.getRegions().add(new GravityFlipRegion("a", box(), true));
+ RegionRegistry reg = new RegionRegistry(cfg);
+
+ // Reader captures the immutable list before the swap.
+ var before = reg.enabled();
+ assertEquals(1, before.size());
+
+ // Mutator swaps via refreshFromConfig.
+ cfg.getRegions().add(new GravityFlipRegion("b", box(), true));
+ reg.refreshFromConfig(cfg);
+
+ var after = reg.enabled();
+ assertEquals(2, after.size());
+ // The previously-captured list must NOT have been mutated under the reader.
+ assertEquals(1, before.size());
+ }
+
+ /** Minimal RegionSnapshot for the publish/read tests; world() is unused (returns null). */
+ private static final class StubSnapshot implements RegionSnapshot {
+ private final Map>> by;
+ private final long tick;
+ StubSnapshot(Map>> by, long tick) {
+ this.by = by; this.tick = tick;
+ }
+ @Override public Map>> byRegion() { return by; }
+ @Override public long tickId() { return tick; }
+ @Override public World world() { return null; }
+ }
+}