Spatial Grid Configuration & Tier Control
One global grid, one cell size, and a per-cell simulation-tier control surface for multi-resolution worlds.
Status: โ Implemented ยท Visibility: Public ยท Level: ๐ต Core ยท Category: Spatial
๐ฏ What it solves
Large worlds can't afford to simulate every entity at full frequency every tick โ a 10M-entity world needs the few thousand entities near a player to run physics at 60 Hz while everything else runs coarser or not at all. Doing this with per-entity distance checks costs O(N) every frame and collapses well before six figures. Spatial Grid Configuration sets up the engine-wide coordinate grid every spatial archetype shares, and the tier control surface lets game code assign a simulation tier (full / reduced / coarse / dormant) per cell โ cheaply, once per tick โ instead of per entity.
โ๏ธ How it works (in brief)
SpatialGridConfig is computed once: world bounds and a single cell size derive the grid dimensions, and the config is handed to DatabaseEngine.ConfigureSpatialGrid before InitializeArchetypes โ it cannot change afterward. All spatial archetypes share this one grid; there's no per-archetype sizing. At runtime, a TierAssignment-style callback system (run with SystemPriority.High so it executes before other systems) reads TickContext.SpatialGrid โ an SpatialGridAccessor โ and assigns each cell a SimTier flag (Tier0..Tier3). The engine consumes these per-cell tiers downstream to filter which clusters a system or query touches (see tier-filtered system dispatch in the Runtime category) โ assignment itself is entirely game-owned policy; Typhon only provides storage and the helper methods below.
๐ป Usage
// Once, before InitializeArchetypes:
dbe.ConfigureSpatialGrid(new SpatialGridConfig(
worldMin: new Vector2(-1000f, -1000f),
worldMax: new Vector2( 1000f, 1000f),
cellSize: 32f));
// Every tick, a high-priority callback system assigns tiers:
schedule.CallbackSystem("TierAssignment", ctx =>
{
var grid = ctx.SpatialGrid;
if (!grid.IsValid) return;
grid.ResetAllTiers(SimTier.Tier3); // start everyone at lowest priority
foreach (var observer in connectedPlayers)
{
grid.SetTierInAABB(observer.Tier0MinX, observer.Tier0MinY,
observer.Tier0MaxX, observer.Tier0MaxY, SimTier.Tier0);
grid.SetTierInAABB(observer.Tier1MinX, observer.Tier1MinY,
observer.Tier1MaxX, observer.Tier1MaxY, SimTier.Tier1);
}
}, priority: SystemPriority.High);
| Config field | Meaning | Notes |
|---|---|---|
WorldMin / WorldMax |
World-space extent | WorldMax is exclusive on both axes |
CellSize |
Side length of one cell, world units | Must be > 0; one size for the whole grid |
MigrationHysteresisRatio |
Dead-zone fraction for migration | Default 0.05; reserved, currently passive |
โ ๏ธ Guarantees & limits
- Set once, immutable after โ
ConfigureSpatialGridthrowsInvalidOperationExceptionif called twice or afterInitializeArchetypes. - One grid, one cell size, for every spatial archetype โ no per-archetype grid sizing; entity scale should be roughly uniform across archetypes sharing the grid.
KeySpaceDimcapped at 32,768 per axis โ a consequence of 32-bit Morton cell-key encoding; oversized world/cell-size combinations throwArgumentOutOfRangeExceptionat config time.SetCellTierrequires a single-bitSimTierflag โ passing a combined flag (e.g.Tier0 | Tier1) is rejected; use theMinvariants or per-call assignment for unions.SetCellTierMin/SetTierInAABBare promote-only โ they never demote a cell already holding a higher-priority (lower-valued) tier, which is what makes the multi-observer "union of zones" pattern correct without per-observer bookkeeping.- All accessor methods throw
InvalidOperationExceptionwhen no grid is configured โ checkSpatialGridAccessor.IsValidfirst, especially in non-spatial engines or during shutdown. - One tick of staleness โ tier assignment runs once per tick (before other systems); cells crossing a tier boundary mid-tick are dispatched at their prior tier until the next tick.
- Tier filtering itself lives downstream โ this feature only assigns and exposes tiers; consuming them to filter system/query dispatch is a separate mechanism (rules
TI-01..TI-03).
๐งช Tests
- SpatialGridTests โ grid dimension derivation,
WorldToCellKey/CellKeyToCoordsround-trips,SetCellTiersingle-bit validation,KeySpaceDimoverflow throws - CheckerboardTests โ
SetCellTierMin_OnlyPromotes,ResetAllTiers_BulkSetsAllCells,SetTierInAABB_MinSemantics,SpatialGridAccessor_AccessibleFromTickContext/_MultiObserver_Union(promote-only tiering, multi-observer union)
๐ Related
- Source: src/Typhon.Engine/Spatial/public/SpatialGridConfig.cs
- Source: src/Typhon.Engine/Spatial/public/SpatialGridAccessor.cs
- Source: src/Typhon.Engine/Ecs/public/DatabaseEngine.cs (
ConfigureSpatialGrid) - Related catalog entry: Spatial Query API (the per-component query layer this grid complements)
- Sibling: Tiered Simulation Dispatch โ the primary consumer of the per-cell
SimTierthis feature exposes - Overview: Spatial Architecture Overview โ this grid vs. the R-Tree's own, unrelated Layer-1 occupancy filter