Dual-Layer Shape Architecture, Spatial Memory & L3 Backlog Selection¶
Scope of this document. This document specifies and visualizes the V3 dual-layer spatial shape architecture, the hardware-conscious spatial memory storage hierarchy, the adaptive dyadic downsampling method, and the distinct selection and harvesting path for the L3 unverified backlog queue.
Companion architectural decisions and references:
- ADR-001: Legacy Archimedean spiral 1D mapping.
- ADR-028: L3 unverified backlog cache (backlogLocations), .mca binning, and head-blocking promotion.
- ADR-034: Memory shape catalog (CircleOptimizedDualLayer, SquareOptimizedDualLayer).
- ADR-081 & ADR-082: Blocked run tables and cache-conscious primitive array pages.
- ADR-085: Spiral-addressed Hilbert key space for learned state.
- ADR-088: Configurable adaptive dyadic downsampling stride filter and keyed Feistel permutation.
Executive Overview (Dual-Layer Spatial Selection)¶
Dual-layer spatial shapes eliminate random coordinate rerolls by indexing chunk space into coarse Chebyshev macro-tiles traversed by fine Hilbert space-filling curves. Known invalid regions are skipped in $O(1)$ time via segmented prefix-sum tables.
flowchart LR
A["Virtual Target Index<br/>(0 .. totalValidSpace)"] --> B["Two-Tier Segmented Table<br/>(Skip Bad Chunks O(1))"]
B --> C["1D Hilbert Key<br/>(Within Macro-Tile)"]
C --> D["Chebyshev Macro Ring<br/>(2D Tile Placement)"]
D --> E["Physical Chunk Coordinates<br/>(cx, cz)"]
classDef step fill:#cfe2ff,stroke:#1f4e8a,stroke-width:1px,color:#0b1f3a;
classDef target fill:#b7e4b7,stroke:#1f6b1f,stroke-width:2px,color:#0b2a0b;
class A,B,C,D step;
class E target;
1. Dual-Layer Coordinate Addressing and Point Selection Pipeline¶
Dual-layer shapes (CircleOptimizedDualLayer, SquareOptimizedDualLayer) address discrete chunk space at native 1-chunk resolution ($S=1$, 16 blocks per cell). Space is decomposed into a two-tier hierarchy:
1. Coarse Macro-Grid: Chebyshev square rings of macro-tiles (point edge $P$ chunks, derived dynamically as a power of two from radius $R$, e.g., $P \in {8, 16, 32}$).
2. Fine Intra-Tile Traversal: Continuous space-filling Hilbert curve $\mathcal{H}$ of order $m = \log_2(P)$ within each macro-tile ($P^2$ chunks per tile), rotated to match the macro-spiral travel direction (seam matching).
Point Selection Flowchart¶
%% Color legend: green=success/accept, red=rejection/boundary fail, blue=computation/transformation, yellow=table lookup/state
flowchart TD
Start([Shape Candidate Selection<br/>select or rand]):::async --> StrideDerive[Derive Effective Stride S<br/>deriveEffectiveStride]:::data
StrideDerive --> ModeCheck{Selection Mode?<br/>ACCUMULATE vs REJECT}
subgraph StridePermutation [Dyadic Stride & Feistel Permutation]
ModeCheck -- ACCUMULATE --> RangeCalc[Compute totalGood = range - badCells<br/>Virtual Good Domain 0..totalGood-1]:::data
RangeCalc --> PhaseSeq[Compute Dyadic Phase Offset φ<br/>reverse t mod S >>> 32 - log2 S]:::data
PhaseSeq --> SubsetBound[Compute Subset Size<br/>subsetSize = totalGood - 1 - φ / S + 1]:::data
SubsetBound --> FeistelPRP[4-Round Keyed Feistel Network<br/>permutedK = FeistelPermute k, subsetSize, K_epoch]:::async
FeistelPRP --> VirtualIdx[Calculate Virtual Good Index<br/>vIndex = permutedK * S + φ]:::data
VirtualIdx --> ResolveAcc[Resolve Virtual Index via Segmented Table<br/>Tier 1 Directory Bisect -> Tier 2 Local Bin]:::data
ModeCheck -- REJECT / Direct --> DirectPRP[Feistel Permutation on Raw Range<br/>permutedLoc = FeistelPermute t, range, K_epoch]:::async
DirectPRP --> ResolveAccDirect[Raw 1D Key = permutedLoc]:::data
end
ResolveAcc --> KeyDecode[Key Decomposition<br/>key = macroIndex * P^2 + hilbertOffset]:::data
ResolveAccDirect --> KeyDecode
subgraph CoordinateDecomposition [1D Key to 2D Chunk Resolution]
KeyDecode --> MacroRing[Decode Chebyshev Ring K & Side<br/>px, pz macro-tile coordinates]:::data
MacroRing --> SeamOrientation[Derive Seam Orientation<br/>orientationFor px, pz]:::data
SeamOrientation --> HilbertDecode[Inverse Hilbert Transform<br/>hilbertToXY h, P, orientation -> localX, localZ]:::data
HilbertDecode --> PhysicalCoord[Compose Physical Chunk Coordinates<br/>cx = cenX + px * P + localX<br/>cz = cenZ + pz * P + localZ]:::data
end
PhysicalCoord --> BoundaryCheck{In Geometric Bounds?<br/>Circular distSq or Square Chebyshev}
BoundaryCheck -- In Bounds --> Success([Return Physical Chunk Coordinates<br/>cx, cz]):::success
BoundaryCheck -- Out of Bounds --> RerollCheck{Attempts < 100?}
RerollCheck -- Yes --> Start
RerollCheck -- No --> Exhausted([Selection Fallback / Null]):::fail
classDef success fill:#b7e4b7,stroke:#1f6b1f,stroke-width:2px,color:#0b2a0b;
classDef fail fill:#f2b8b8,stroke:#8a1f1f,stroke-width:1px,color:#2a0b0b;
classDef async fill:#c9dcf5,stroke:#28518a,stroke-width:1px,color:#0b1f3a;
classDef data fill:#f2e6a8,stroke:#8a6b1f,stroke-width:1px,color:#2a220b;
2. Virtual Index Resolution via Two-Tier Segmented Table (ACCUMULATE Mode)¶
When operating under MODE_ACCUMULATE, candidate selection generates a virtual rank $T \in [0, \text{totalGood})$ where all known bad chunks have been subtracted from the addressable domain ($\text{totalGood} = \text{totalRange} - \text{totalCovered}$).
To achieve zero hole re-rolls without scanning large fragmented run arrays, SegmentedKeyRunTable.resolveAccumulate(long target) performs a two-tier prefix-sum directory resolution:
1. Tier-1 Directory Bisect ($O(\log \text{Bins})$): Evaluates cumulative good counts across fixed-size bins via dirBadPrefixSums ($32 \times 32$ chunks = $1{,}024$ chunks/bin) to identify the target bin $b$.
2. Tier-2 Local Bin Resolution ($O(\log \text{RunsPerBin})$): Computes $\text{localTarget} = T - \text{goodBefore}$, then iteratively searches the local primitive starts and badPrefixSums arrays until physical convergence.
3. Physical 1D Key Composition: $\text{physicalKey} = b \cdot \text{binSize} + \text{localOffset}$.
Virtual-to-Physical Resolution Flowchart¶
%% Color legend: green=resolved output, blue=computation/logic, yellow=directory/table lookup, red=failure/clamp
flowchart TD
InVirtual([Virtual Target Rank T<br/>0 ≤ T < totalGood]):::data --> InitialGuess[Initial Bin Estimate<br/>b = min floor T / binSize, numBins - 1]:::data
InitialGuess --> PrefixLookup[Read Preceding Bad Sum<br/>badBefore = dirBadPrefixSums b - 1<br/>goodBefore = b * binSize - badBefore]:::fast
PrefixLookup --> RangeCheck{goodBefore ≤ T < goodBefore + binGood?}
RangeCheck -- "T < goodBefore" --> BisectLeft[Tier-1 Binary Search Left<br/>Search range 0 .. b-1 in dirBadPrefixSums<br/>Find highest mid where mid*binSize - badBefore ≤ T]:::async
RangeCheck -- "T ≥ goodBefore + binGood" --> BisectRight[Tier-1 Binary Search Right<br/>Search range b+1 .. numBins-1 in dirBadPrefixSums<br/>Find highest mid where mid*binSize - badBefore ≤ T]:::async
RangeCheck -- Matches Bin b --> TargetBinIdentified[Target Bin b Confirmed]:::fast
BisectLeft --> TargetBinIdentified
BisectRight --> TargetBinIdentified
TargetBinIdentified --> LocalTargetCalc[Calculate Local Target Offset<br/>localTarget = T - goodBefore<br/>Fetch Bin b Reference]:::data
subgraph Tier2Local [Tier-2 Cache-Conscious Local Bin Resolution]
LocalTargetCalc --> BinStateCheck{Bin State?}
BinStateCheck -- "isEmpty() (count == 0)" --> FastEmpty[localOffset = localTarget]:::success
BinStateCheck -- "isFull()" --> FullFail([Return -1: Bin Completely Covered]):::fail
BinStateCheck -- Mixed Bad Runs --> IterativeSearch[Iterative Physical Guess Loop<br/>guess = localTarget + currentBad]:::async
IterativeSearch --> LocalBisect[Binary Search starts Array in Bin b<br/>idx = binarySearch starts, 0, count, guess]:::data
LocalBisect --> SumLookup[Lookup Local Prefix Sum<br/>newBad = idx > 0 ? badPrefixSums idx - 1 : 0]:::data
SumLookup --> ConvergeCheck{newBad == currentBad?}
ConvergeCheck -- No --> UpdateBad[currentBad = newBad]:::async --> IterativeSearch
ConvergeCheck -- Yes (Converged) --> LocalResolved[localOffset = localTarget + currentBad]:::success
end
FastEmpty --> BoundsCheck{localOffset < binSize?}
LocalResolved --> BoundsCheck
BoundsCheck -- Yes --> ComposePhysical[Compose Physical 1D Hilbert Key<br/>key = b * binSize + localOffset]:::success
BoundsCheck -- No --> OverflowFail([Return -1: Exceeds Bin Boundary]):::fail
ComposePhysical --> OutPhysical([Physical 1D Hilbert Coordinate Key]):::success
classDef success fill:#b7e4b7,stroke:#1f6b1f,stroke-width:2px,color:#0b2a0b;
classDef fail fill:#f2b8b8,stroke:#8a1f1f,stroke-width:1px,color:#2a0b0b;
classDef async fill:#c9dcf5,stroke:#28518a,stroke-width:1px,color:#0b1f3a;
classDef data fill:#f2e6a8,stroke:#8a6b1f,stroke-width:1px,color:#2a220b;
classDef fast fill:#d5e8d4,stroke:#82b366,stroke-width:1.5px,color:#1b381b;
3. Spatial Memory and Run Table Data Storage Architecture¶
Spatial hazards, bad chunks, and learned terrain memory are managed through MemoryShape and cached in SegmentedKeyRunTable. Ground truth is retained at strict 1-chunk resolution ($S=1$).
Storage Hierarchy and On-Disk Layout¶
%% Color legend: green=CPU cache/fast, blue=memory structure, yellow=disk format, grey=bounds
flowchart TB
subgraph MemoryHierarchy [In-Memory Storage Hierarchy - SegmentedKeyRunTable]
direction TB
MemShape[MemoryShape Instance<br/>Continuous 1D Key Space 0..Range-1]:::data
subgraph Tier1 [Tier 1 Directory - L1/L2 Cache Resident]
DirKeys[dirBinStarts: long array<br/>Monotonic starting keys per bin]:::fast
DirBad[dirBadPrefixSums: int array<br/>Cumulative bad chunks per bin ≤ 64 ints]:::fast
DirBins[bins: Bin array reference table]:::fast
end
subgraph Tier2 [Tier 2 Local Bins - Cache-Conscious Primitive Pages]
Bin0["Bin 0 (Span 0..B-1)<br/>starts: int[] (relative offset)<br/>lengths: int[] (run lengths)<br/>badPrefixSums: int[]<br/>coveredCells: long"]:::page
Bin1["Bin 1 (Span B..2B-1)<br/>starts: int[]<br/>lengths: int[]<br/>badPrefixSums: int[]<br/>coveredCells: long"]:::page
BinN["Bin N (Span NB..Range-1)<br/>starts: int[]<br/>lengths: int[]<br/>badPrefixSums: int[]<br/>coveredCells: long"]:::page
end
end
subgraph DiskPersistence [Persistent Disk Layout - .bin Format Version 5]
direction TB
Header["File Header (16-32 bytes)<br/>- Magic: 0x5254504D ('RTPM')<br/>- Format Version: 5<br/>- Curve ID: CURVE_SPIRAL_HILBERT (0x02)<br/>- Point Edge P: 8, 16, or 32<br/>- Key Width: 4 bytes (int) or 8 bytes (long)<br/>- Config Hash: stable 64-bit hash (ADR-022)"]:::disk
Payload["Run Record Payload<br/>[Run 0: startKey (int/long), length (int/long)]<br/>[Run 1: startKey (int/long), length (int/long)]<br/>...<br/>[Run M: startKey, length]"]:::disk
end
MemShape --> DirKeys
MemShape --> DirBad
MemShape --> DirBins
DirBins --> Bin0
DirBins --> Bin1
DirBins --> BinN
Bin0 -.flushAndRebuild / save.-> Payload
Bin1 -.flushAndRebuild / save.-> Payload
BinN -.flushAndRebuild / save.-> Payload
Header --- Payload
classDef fast fill:#b7e4b7,stroke:#1f6b1f,stroke-width:1.5px,color:#0b2a0b;
classDef page fill:#c9dcf5,stroke:#28518a,stroke-width:1px,color:#0b1f3a;
classDef disk fill:#f2e6a8,stroke:#8a6b1f,stroke-width:1px,color:#2a220b;
classDef data fill:#e8d5f5,stroke:#5c288a,stroke-width:1px,color:#220b3a;
4. Downsampling Method: Dyadic Stride and Keyed Permutation¶
To prevent player arrival clumping across massive borders while retaining 100% safety ground truth, an adaptive dyadic downsampling filter operates inside the selection tier (ADR-088).
Downsampling State Machine and Phase Permutation¶
%% Color legend: green=active phase candidate, blue=cryptographic mixing, yellow=phase progression, red=epoch transition
flowchart TD
subgraph StrideConfig [Stride Derivation & Full-Bin Bound]
InitDomain[Domain Size N / totalGood]:::data --> DeriveS[deriveEffectiveStride<br/>resolution / uniquePlacements / adaptive]:::data
DeriveS --> BinCheck{Full-Bin Bound Check<br/>S ≤ binArea ?<br/>binArea = P^2}
BinCheck -- S > binArea --> ClampS[Clamp Stride S = max 1, binArea]:::data
BinCheck -- S ≤ binArea --> ValidS[Final Stride S & bits = log2 S]:::data
end
subgraph PhaseProgression [Dyadic Bit-Reversal Anti-Resonance]
ValidS --> AdvCounter[Advance Monotonic Counter t<br/>t = selectionCounter.getAndIncrement]:::async
AdvCounter --> EpochSlice[Epoch Slice: epoch = t / subsetCapacity<br/>subsetIdx = epoch mod S]:::data
EpochSlice --> BitRev[Dyadic Bit-Reversal Bisection Sequence<br/>φ = reverse subsetIdx >>> 32 - bits]:::data
BitRev --> PhaseExhaustion["Exhaust all candidates in active phase φ before next rotation<br/>(S = P^2: d ≥ P chunks except at spiral ring corners)"]:::data
end
subgraph FeistelNetwork [4-Round Feistel Pseudorandom Permutation]
PhaseExhaustion --> KeyDerive["Derive Round Key K_epoch<br/>feistelSalt ^ (epoch * 0x517CC1B727220A95L) ^ (φ * 0x9E3779B97F4A7C15L)"]:::async
KeyDerive --> Permute["Cycle-Walking ARX PRF Feistel (SipRound)<br/>Tiny-Domain Masking & Strict Bijection"]:::async
Permute --> KeyCoord[Physical Hilbert Key = permutedK * S + φ]:::success
end
subgraph ExpansionRatchet [Radial Expansion Ratchet]
AdjustRange[Dynamic adjustRange triggered]:::data --> IncEpoch[expansionEpoch++<br/>Atomic Ratchet]:::data
IncEpoch --> ResetCounter[Reset selectionCounter & backlogCounter to 0]:::data
ResetCounter -.New Salt / Modulus.-> KeyDerive
end
classDef success fill:#b7e4b7,stroke:#1f6b1f,stroke-width:2px,color:#0b2a0b;
classDef async fill:#c9dcf5,stroke:#28518a,stroke-width:1px,color:#0b1f3a;
classDef data fill:#f2e6a8,stroke:#8a6b1f,stroke-width:1px,color:#2a220b;
5. Distinct Selection Path for Filling L3 Backlog and Anvil Verification¶
The L3 backlog cache (backlogLocations, ADR-028) uses a decoupled, high-throughput selection path upstream of the cold (unkeptLocations) and hot (keptLocations) caches.
Key Architectural Distinctions: L3 vs. Standard Cache Pipeline¶
| Dimension | Standard Cache Pipeline (L1 / L2) | L3 Backlog Cache Pipeline (backlogLocations) |
|---|---|---|
| Selection Entrypoint | shape.select() via LocationGenerator |
CircleOptimizedDualLayer.selectBacklogCandidate() / shape.select() |
| Verification Depth | Full pipeline: chunk I/O + vert.adjust() + biome + 2r+1 safety + external claims |
Off-tick Anvil pre-filter only (AnvilIoPool, zero chunk loading) |
| Candidate State | Fully verified safe coordinate | Tri-state validity: UNVERIFIED $\to$ VALIDATED or INVALIDATED |
| Spatial Grouping | Independent random candidates | Binned into $32 \times 32$ chunk .mca region files via WorldBacklogBinIndex |
| Promotion Model | Immediate cache push on attempt completion | Head-blocking contiguous promotion: only VALIDATED head entries advance |
L3 Backlog Lifecycle and Bin Verification Flowchart¶
%% Color legend: green=promoted/validated, red=invalidated/dropped, blue=async/off-tick worker, yellow=queue/binning
sequenceDiagram
autonumber
participant Reg as Region.execute() Pulse
participant Backlog as BacklogLocationBuffer (L3)
participant BinIdx as WorldBacklogBinIndex (World-Level)
participant Anvil as AnvilIoPool / RegionFileReader
participant Cold as unkeptLocations (L2 Cold Cache)
Note over Reg,Backlog: Phase 1: Shape-Only Refill (Time-Sliced & Hysteresis Gated)
Reg->>Reg: Check Hysteresis: size < threshold * capacity?
alt Refill Active & Heap Not Under Pressure
loop While backlog not full & pulse budget remains
Reg->>Reg: selectBacklogCandidate() via Dyadic Stride PRP
Reg->>Backlog: offerUnverified(RTPLocation coords, unverified)
Backlog-->>Reg: BacklogEntry (State = UNVERIFIED)
Reg->>BinIdx: insert(RegionFileCoord (cx>>5, cz>>5), BacklogEntry)
end
end
Note over Reg,Anvil: Phase 2: One-Bin-Per-Pulse Off-Tick Anvil Verification
Reg->>Backlog: peekOldestUnverified()
alt Oldest Entry Found
Backlog-->>Reg: oldestEntry (targetBin = RegionFileCoord)
Reg->>BinIdx: snapshot(targetBin)
BinIdx-->>Reg: List<BacklogEntry> (cross-region candidates in same .mca)
Reg->>Anvil: Batch check biome & block headers off-tick (ADR-016)
Anvil-->>Reg: Batch verification decisions
loop For each entry in bin snapshot
alt Anvil prefilter passes
Reg->>Backlog: setValidity(VALIDATED)
else Hazardous or rejected biome
Reg->>Backlog: setValidity(INVALIDATED)
end
end
end
Note over Reg,Cold: Phase 3: Head-Blocking Promotion to L2
loop Drain Head of Backlog
Backlog->>Backlog: peek head entry
alt Head is INVALIDATED
Backlog->>Backlog: drop head entry (reclaim capacity)
else Head is VALIDATED
Backlog->>Backlog: poll head entry
Backlog->>Cold: push into unkeptLocations (promoted to L2)
else Head is UNVERIFIED
Note over Backlog: Stop draining! Head-of-line blocking preserves FIFO order
end
end
6. Time and Memory Complexity: Architectural Benchmarks & Competitor Comparison¶
The architectural migration from flat 1D Archimedean spirals to dual-layer space-filling Hilbert geometries backed by two-tier SegmentedKeyRunTable structures yields provable asymptotic and empirical improvements in time and heap complexity.
Asymptotic Complexity & Operational Predictability Comparison¶
| Architecture / Engine | Selection Time Complexity | ACCUMULATE Resolution Time | Dynamic Mark Reconciliation | Retained Heap Memory Complexity | Latency Variance / Predictability |
|---|---|---|---|---|---|
| Dual-Layer Segmented Hilbert (Current RTP) | $\mathcal{O}(1)$ dyadic stride + 4-round Feistel PRP | $\mathcal{O}(\log \text{Bins} + \log \text{RunsPerBin})$ ($\sim 4\text{ to }6\text{ bisect steps}$) | $\mathcal{O}(\text{RunsPerBin} \log \text{RunsPerBin})$ (dirty bin local only) | $\mathcal{O}(M)$ primitive arrays, near-full collapse to $\mathcal{O}(1)$ singleton ($\le 25\text{ kB}$) | Deterministic: Bound L1/L2 cache traversal ($13\text{--}35\text{ ns}$); zero hole re-rolls; strictly bounded jitter |
| Pure Archimedean Spiral (Prior RTP RLE) | $\mathcal{O}(1)$ polar spiral math ($\sqrt{k/\pi}$) | $\mathcal{O}(I \cdot \log M)$ iterative fixed-point over global array ($I \approx 3\text{--}5$) | $\mathcal{O}(M \log M)$ global rebuild across full world array | $\mathcal{O}(M)$ global flat long[] arrays, fragmented across spiral rings ($31\text{ kB}$) |
Moderate Variance: Global binary search miss penalties; high reconciliation pauses ($27\text{ }\mu\text{s/mark}$) |
| EzRTP (Coarse Online Verification) | $\mathcal{O}(1)$ random coordinate generation (max-attempts: 16) |
No ACCUMULATE mode; $\mathcal{O}(\text{re-roll})$ on rejection | $\mathcal{O}(1)$ per-attempt check; no run table compression | $\mathcal{O}(N)$ monitoring/stats heap maps, unbounded over uptime | High Variance / Unpredictable: Exponential geometric rejection tails (measured p99 tail $\sim 1.9\text{ s}$); heap pressure drives GC pauses |
| JustRTP (TTL Pre-Cache & Positive Reuse Pool) | $\mathcal{O}(1)$ ConcurrentLinkedQueue.poll() or positive reuse pool |
No ACCUMULATE mode; cache misses fall back to full pipeline | No persistent negative hazard index; periodic async refill timer | $\mathcal{O}(C)$ queue objects (Location references) + bounded spot pool with time TTL |
Bimodal & Skewed Variance: Fast when pool warm, but exhibits heavy spatial clustering / Pólya urn feedback and severe latency spikes when TTL drains |
Empirical Proofs & Benchmark Findings¶
1. ACCUMULATE Mode Resolution Latency (11.2x Speedup)¶
Empirical runs on real Minecraft world save data ($r = 96$ chunks, SegmentedKeyRunTableBenchmarkTest / AccumulateOffsetResolutionBenchmarkTest):
* Prior Flat Global Loop (MemoryShape.resolve): $1{,}571.4\text{ ns/op}$ average. Required repeated global binary searches over the complete set of runs.
* Dual-Layer Segmented Two-Tier Resolution: $140.0\text{ ns/op}$ average (11.22x faster). Tier-1 directory lookup bisects dirBadPrefixSums ($\le 64$ entries) strictly in L1/L2 cache, leaving local resolution to $\le 15$ runs within the bin.
* Accuracy: 100.00% coordinate equivalence over $>50{,}000$ random samples against the flat model.
2. Stored Runs, Heap Footprint, and Scale (Three-Way Benchmark)¶
Across varying world radii (measured via ThreeWayScaleBenchmarkTest on real terrain):
| World Scale | Model | Stored Runs | Heap Footprint | Lookup Latency | Reconcile Latency | Speedup vs Original |
|---|---|---|---|---|---|---|
| 0.8 km (spawn) $r = 50\text{ chunks}$ |
Prior Spiral RLE Flat Hilbert Dual-Layer Segmented |
331 189 210 |
5.3 kB 3.0 kB 4.7 kB |
75.3 ns 67.2 ns 62.0 ns |
2,422 ns/mark 1,380 ns/mark 519 ns/mark |
1.0x 1.12x 1.21x |
| 4.1 km (medium) $r = 256\text{ chunks}$ |
Prior Spiral RLE Flat Hilbert Dual-Layer Segmented |
1,937 1,273 1,322 |
31.0 kB 20.4 kB 14.1 kB |
34.9 ns 26.8 ns 20.6 ns |
4,966 ns/mark 4,232 ns/mark 1,148 ns/mark |
1.0x 1.30x 1.70x |
| 8.2 km (large) $r = 512\text{ chunks}$ |
Prior Spiral RLE Flat Hilbert Dual-Layer Segmented |
1,937 1,273 1,506 |
31.0 kB 20.4 kB 24.4 kB |
20.9 ns 18.4 ns 13.4 ns |
27,904 ns/mark 32,043 ns/mark 1,292 ns/mark |
1.0x 1.14x 1.56x |
- Run Reduction & Heap Inversion: The Archimedean spiral slices 2D terrain clusters into discontinuous 1D ring segments, multiplying runs. The 2D-localized Hilbert curve packs adjacent chunks into contiguous runs (producing 42% fewer coalesced runs on circular domains). Combined with near-full bin collapse, the segmented table at 4.1 km uses 14.1 kB—less than half the heap of the prior spiral (31.0 kB).
- Reconciliation Speedup (21.6x faster): When learning a new hazard or bad chunk, flat models must sort and re-coalesce the entire world run table ($27{,}904\text{ ns/mark}$). Segmented tables dirty only the affected local bin, rebuilding $10\text{--}25$ local runs off-tick in $1{,}292\text{ ns/mark}$.
3. Time Cost, Memory Footprint & Predictability Comparison: Code-Audited Competitor Analysis¶
EzRTP: Unbounded Heap Tracking vs. Segmented Primitive Bounds¶
- Memory Bounds & Footprint Cost:
- As published in
ez-plugins/EzRTP, coordinate selection relies on online shape generation (search-pattern: random|circle|square|triangle|diamond) with a configured candidate attempt ceiling (max-attempts: 16). Unsafe location monitoring (unsafe-location-monitoring.monitoring.enabled) and performance stats record metrics into heap-resident collections. Without continuous Run-Length Encoding or space-filling key spaces, tracking unsafe coordinates incurs Java object overhead ($\sim 160\text{--}200\text{ bytes}$ per coordinate), which scales unbounded with exploration ($O(N)$). - In contrast, RTP's dual-layer spatial memory bounds retained footprint to $\le 25\text{ kB}$ total per world via Run-Length Encoding and primitive arrays (
int[]), collapsing ocean regions to $O(1)$ singletons. - Lookup Time & Variance Profile:
- EzRTP has no continuous domain mapping or ACCUMULATE mode. Point generation relies on rejection sampling: candidate coordinates are picked at random and evaluated online against safety filters (biomes, WorldGuard, unsafe blocks). In dense hazard regions (e.g., oceans), rejection rates compound, causing long re-roll tails (measured p99 tail $\sim 1.9\text{ seconds}$).
- RTP's Segmented Hilbert table provides deterministic $O(\log \text{Bins} + \log \text{RunsPerBin})$ lookup ($13.4\text{--}20.6\text{ ns/op}$) with near-zero latency variance, guaranteeing zero re-rolls within known bad runs.
JustRTP: TTL Pre-Cache & Positive Reuse Pool vs. Continuous Spatial Memory¶
- Recall Architecture & Memory Cost:
- As published in
kotorinet/justRTP(LocationCacheManager.java), JustRTP implements pre-caching via per-world FIFO queues:ConcurrentHashMap<String, ConcurrentLinkedQueue<Location>>targeting a fixed capacity (defaultcache_size: 20) serialized tocache.yml. - Later updates also incorporate positive reuse memory ("smart location cache" learning good landing spots from real teleports with a bounded pool and an entry TTL of $\sim 15\text{ minutes}$).
- Rather than storing a mathematical negative index of unsafe terrain, JustRTP caches and recycles known-good coordinates. Memory cost is bounded to the positive pool ($O(C)$ boxed
Locationinstances) with time-based eviction, discarding spatial state once the TTL elapses. - Time Cost, Bimodal Variance & Spatial Clumping Skew:
- Warm State: When cached entries are available in the queue or positive reuse pool, retrieval is $O(1)$ ($\sim 30\text{--}65\text{ ms}$ dispatch latency).
- Drained / Burst State: When the pre-cache empties under burst traffic or when entries expire past their TTL, requests fall back to on-demand generation. In hazardous areas, refill loops repeatedly test, load, and reject the exact same unsafe chunks across cycles because rejected hazard geometry is not retained.
- Preferential Attachment / Spatial Skew: As proved in
DistributionSkewBenchmarkTest, positive reuse memory introduces a Pólya urn feedback loop: early random discoveries get recycled and reinforced, creating intense player arrival clumping into localized hotspots (higher Gini inequality and lower spatial entropy) while leaving wide swaths of the map unvisited. - RTP Dual-Layer Predictability:
- RTP maintains negative rejection memory over the mathematical complement of bad space via Run-Length Encoding.
- Combined with permanent off-tick Anvil pre-filtering (
rtp-anvil), the unverified L3 backlog (backlogLocations), and the dyadic downsampling stride filter with cycle-walking Feistel PRP (ADR-088), RTP guarantees uniform spatial dispersion, zero duplicate chunks, and flat, deterministic response times regardless of server uptime.
Related Code and Implementation Map¶
- Dual-Layer Shapes:
CircleOptimizedDualLayer: continuous Chebyshev-addressed Hilbert curve, inscribed inner-donut bound, adaptive dyadic downsampling stride filter.SquareOptimizedDualLayer: square Chebyshev macro-grid, dynamicPointEdgeSelector, loopless $O(1)$ candidate coordinate generation.SegmentedKeyRunTable: two-tier hardware cache-conscious secondary run table with bin-local binary search and prefix sum routing.- L3 Backlog Subsystem:
BacklogLocationBuffer: order-preserving FIFO buffer with tri-state validity (UNVERIFIED,VALIDATED,INVALIDATED) and heuristic cleaning.WorldBacklogBinIndex: cross-region weak index mappingRegionFileCoordto candidate entries for.mcabatch verification.Region.processBacklog(): hysteresis-gated refill, one-bin-per-pulse Anvil pre-filtering, and head-blocking L2 promotion.- Verification and Safety:
AnvilRegionByteCache/anvil-api: off-tick.mca(and addon-registered format) NBT pre-filtering without server chunk tickets (REQ-RTP-S-005).