Spatial R-Tree Index
Page-backed R-Tree attached to a component field, giving sub-microsecond AABB/radius/ray queries shared across every archetype that uses it.
Status: โ Implemented ยท Visibility: Internal ยท Category: Spatial
๐ฏ What it solves
Any component holding a position or bounds needs spatial answers โ "what's near this point", "what's inside this box" โ and a linear scan over every entity falls apart well before 10K entities at 60 Hz. A B+Tree field index doesn't help either: it orders on a scalar key, not on 2D/3D overlap. The R-Tree gives a component field a real spatial index: O(log n) descent instead of O(n) scan, durable across crashes and restarts like any other Typhon structure, and shared by every archetype that happens to use the component โ no per-archetype index duplication.
โ๏ธ How it works (in brief)
Annotate a component field holding an AABB2F/AABB3F/BSphere2F/BSphere3F (or the f64 equivalents) with [SpatialIndex]. The engine infers the field's spatial type, picks the matching tree variant (2D/3D ร f32/f64), and builds one R-Tree per ComponentTable โ not per archetype, so entities of different archetypes sharing the component land in the same tree. Each tree node is a single page-backed chunk (512 B for 2D/3D-f32, 768 B for 3D-f64) holding 12โ24 entries depending on variant, so a handful of page reads resolve any query. Entries store a fat AABB โ the tight bounds enlarged by margin โ so small moves are absorbed without touching the tree; only a margin-escaping move costs a real remove+reinsert (see Fat-AABB Incremental Update). A back-pointer per entity gives O(1) leaf lookup on update/remove, and all node access goes through Optimistic Lock Coupling โ readers never block writers, a concurrent mutation mid-traversal just restarts the descent. An optional Layer-1 coarse occupancy hashmap (cellSize > 0) rejects entirely-empty query regions before the tree is touched at all.
Sub-features
| Sub-feature | Use it when... |
|---|---|
| Static / Dynamic Tree Separation | A component's spatial data is either rarely-moving (terrain, buildings, fixed triggers) or moves every tick (units, projectiles), and you want each to pay only its own maintenance cost |
โ ๏ธ Guarantees & limits
- Crash-safe โ tree mutations ride the same WAL/tick-fence durability path as the component's storage mode (tick fence for SingleVersion, transaction commit for Versioned); no separate persistence story to manage.
- Zero boot cost โ the tree lives in the database file; reopening a database does not rebuild it.
- No MVCC โ the tree always reflects current/latest-committed state, regardless of the component's storage discipline. Not available on
Transientcomponents (registration fails at schema time). - One tree per ComponentTable, not per archetype โ every archetype that shares the spatially-indexed component is queried by a single tree traversal; archetype filtering on results is a cheap bitmask check, not a separate index probe.
- Query completeness is guaranteed โ every entity whose geometry matches a query is in the result set; node MBRs are refit on every mutation to keep this true (false positives at the leaf/fat-AABB level are expected and filtered by the caller; false negatives are not).
- Fixed fanout per variant โ 20 (2D f32) / 14 (3D f32) / 12 (2D f64) / 13 (3D f64) leaf entries per node; not configurable per component.
- Lazy underflow โ nodes below minimum fill are tolerated, not merged; only fully empty leaves are reclaimed. Slightly higher storage overhead under heavy churn, no correctness impact.
- ~600 ยตs total spatial budget at 100K entities / 60 Hz (update + 100 queries/tick) โ roughly 3.6% of a 16 ms frame.
- At most one
[SpatialIndex]field per component, and exactly one R-Tree variant is selected for it at registration time โ switching field type later means re-registering the schema, not a runtime reconfiguration.
๐งช Tests
- SpatialRTreeTests โ insert/split/remove correctness across all 4 variants, AABB query vs brute force, category-mask pruning
- SpatialNodeDescriptorTests โ node capacities/header sizes/SOA layout match the design doc, per variant
- SpatialRTreeBulkTests โ many sequential inserts,
TreeValidatorstructural invariant checks
๐ Related
- Related catalog entry: Field Attribute & Schema Integration (the
[SpatialIndex]attribute that configures this index) - Related catalog entry: Spatial Query API and Querying / Spatial Query Predicates (the algorithms that run against this index)
- Related catalog entry: Fat-AABB Incremental Update (per-tick maintenance mechanics)
- Sub-features: Static / Dynamic Tree Separation
- Overview: Spatial Architecture Overview โ clarifies that the Layer-1 occupancy hashmap below is not the engine-wide spatial grid