Spatial Tiers & Adaptive Dispatch
Per-cluster simulation tiers let systems run near entities every tick and far entities at reduced/amortized/dormant rates.
Status: β Implemented Β· Visibility: Public Β· Level: π£ Advanced Β· Category: Runtime
π― What it solves
A 10M-entity world can't run every system at full frequency for every entity β distance-based detail reduction is mandatory, but a per-entity distance check every tick costs O(N) before any tiering even pays off. Game code needs a cheap, engine-owned way to say "this spatial region gets full simulation, that one gets a fraction of it, that other one barely runs at all" β and have every system, view, and parallel dispatch path downstream honor that classification automatically, at cluster granularity, with no per-system spatial bookkeeping.
βοΈ How it works (in brief)
Game code assigns one of four SimTier flags (Tier0..Tier3) to each spatial grid cell, once per
tick, via TickContext.SpatialGrid. At tick start the engine rebuilds β per cluster-eligible archetype,
skipped whenever nothing changed β a compact list of active clusters grouped by tier
(TierClusterIndex). Three runtime mechanisms read that index to scope dispatch: a system's Tier(...)
filter restricts it to matching clusters and can add CellAmortize(N) to process only 1/N of them per
tick; Checkerboard() splits a tier-filtered cluster set into two conflict-free Red/Black dispatch
phases for systems that touch neighboring cells; and per-cluster dormancy puts clusters that haven't
been written to in a configurable number of ticks to sleep, removing them from every dispatch path at
zero cost until something wakes them. All three compose β a system can be tier-filtered, amortized, and
checkerboard-dispatched at once, and dormancy filters out sleeping clusters underneath all of them.
Sub-features
| Sub-feature | Use it when... |
|---|---|
| Tier-Filtered & Amortized Dispatch | A system should only run against near/far cells, or should spread coarse-tier work across several ticks instead of doing it all at once |
| Cluster Dormancy (Sleep/Wake) | A large share of clusters go idle for long stretches (empty regions, parked units) and shouldn't be dispatched at all while quiet |
| Checkerboard (Red/Black) Dispatch | A parallel system reads/writes neighboring cells (diffusion, propagation) and needs conflict-free dispatch without going single-threaded |
β οΈ Guarantees & limits
- All three mechanisms operate on cluster lists, not per-entity checks β the cost of tiering scales with active cluster count, never with entity count.
- One tick of staleness end to end β a cell's tier change, a migration, or a dormancy wake all take
effect at the next tick's dispatch, never the current one (
BuildTierIndexesAtTickStartruns before any system in the tick). TierClusterIndexrebuild is dual-invalidated (grid tier version + per-archetype cluster-set version) β a stationary camera with no migrations pays two integer compares per archetype, not a re-scan.- Composing tier filtering, amortization, checkerboard, and dormancy is additive and engine-managed β game code never has to intersect cluster sets by hand.
TickContext.TierBudgetMetricsexposes per-tier wall-clock cost and entity counts from the previous tick so aTierAssignment-style system can adapt tier boundaries to the actual frame budget instead of fixed radii.
π§ͺ Tests
- TierDispatchTests β
TierClusterIndexrebuild/staleness, tier-filtered dispatch,CellAmortize,AmortizedDeltaTime - DormancyTests β sleep-threshold counter, wake on write/heartbeat, dispatch skips sleeping clusters
- CheckerboardTests β Red/Black two-phase split, no-overlap, composition with tier filtering and dormancy
π Related
- Source: src/Typhon.Engine/Runtime/public/SimTier.cs, TierBudgetMetrics.cs
- Also cataloged from the spatial-grid side: Spatial category β Tiered Simulation Dispatch, Cluster Dormancy, Checkerboard Dispatch cover the same mechanisms from the cell/grid-configuration angle
- Sub-features: Tier-Filtered & Amortized Dispatch, Cluster Dormancy (Sleep/Wake), Checkerboard (Red/Black) Dispatch