Table of Contents

Field Attribute & Schema Integration

Declare a component field as spatially indexed, validated against schema rules the moment the component is registered.

Status: โœ… Implemented ยท Visibility: Public ยท Level: ๐Ÿ”ต Core ยท Category: Spatial

๐ŸŽฏ What it solves

A spatial index needs to know, before a single entity exists, which field holds an entity's bounds, how much slack to give that field before a tree mutation is needed, whether it belongs in the static or dynamic tree, and whether a coarse occupancy filter is worth maintaining. Wiring this up imperatively โ€” manual tree construction, manual field-offset bookkeeping, ad-hoc checks scattered through game code โ€” is exactly the kind of boilerplate Typhon's schema reflection already removes for secondary indexes and foreign keys. [SpatialIndex] extends that same declarative path to spatial fields, and pushes every configuration mistake to startup instead of runtime.

โš™๏ธ How it works (in brief)

Decorate one field of a supported geometry type (AABB2F/AABB3F/BSphere2F/BSphere3F, plus their f64 equivalents) with [SpatialIndex(margin, cellSize)], optionally setting Mode and Category. At component registration the engine checks the field's type, the component's storage mode, and that no second [SpatialIndex] field exists on the struct โ€” any violation throws immediately, before the schema is built. On success, the attribute's values flow into the component's DBComponentDefinition.SpatialField and into the runtime state that backs the R-Tree, the back-pointer segment, and โ€” when cellSize > 0 โ€” the Layer-1 occupancy hashmap.

๐Ÿ’ป Usage

[Component("Game.Ship", revision: 1, StorageMode = StorageMode.SingleVersion)]
public struct ShipComponent
{
    [Field] public String64 Name;

    // margin/cellSize are constructor args; Mode/Category are named properties.
    [Field] [SpatialIndex(margin: 5.0f, Mode = SpatialMode.Dynamic, Category = 1u << 2)]
    public AABB3F Bounds;
}

dbe.RegisterComponentFromAccessor<ShipComponent>();   // throws here on any violation, not later

// Inspecting the reflected metadata:
DBComponentDefinition def = dbe.DBD.GetComponent("Game.Ship", revision: 1);
DBComponentDefinition.Field spatial = def.SpatialField;
float margin = spatial.SpatialMargin;
SpatialFieldType type = spatial.SpatialFieldType;     // AABB3F
[SpatialIndex] arg Default Effect
margin (ctor) required Fat-AABB enlargement in world units โ€” see Spatial R-Tree Index
cellSize (ctor) 0 0 = no Layer-1 hashmap; >0 enables a coarse occupancy filter at that cell size
Mode SpatialMode.Dynamic Static bulk-loads once and is skipped by per-tick/commit maintenance; Dynamic gets fat-AABB updates
Category uint.MaxValue Archetype-level bitmask consumed by the cluster broadphase to skip whole clusters

โš ๏ธ Guarantees & limits

  • Exactly 8 supported field types: AABB2F/AABB3F/BSphere2F/BSphere3F and their f64 (...D) equivalents โ€” any other field type throws InvalidOperationException at registration.
  • At most one [SpatialIndex] field per component โ€” a second one throws at registration, not at first use.
  • margin must be โ‰ฅ 0 โ€” a negative value throws at registration.
  • Not supported on StorageMode.Transient โ€” registering a [SpatialIndex] field on a Transient component throws InvalidOperationException.
  • Validation runs once, at RegisterComponentFromAccessor/RegisterComponentByType time, before any UnitOfWork exists โ€” by the time the schema is usable, the spatial configuration is already known-good.
  • BSphere* fields are accepted directly โ€” the engine converts to an enclosing AABB internally for indexing; the component's stored field stays a sphere, unchanged.
  • The attribute only declares configuration; it does not itself maintain the tree โ€” see the sub-feature below for when the tree picks up a write, and Spatial R-Tree Index for the index structure it configures.

๐Ÿงช Tests

  • SpatialFieldTypeTests โ€” [SpatialIndex] reflection (margin/cellSize), FieldType.FromType mapping for all 8 supported types, SpatialFieldInfo.ToVariant/IsSphere
  • SpatialEcsIntegrationTests โ€” schema validation at registration (Schema_TransientWithSpatialIndex_Throws, Schema_ValidSpatialField_CreatesSpatialIndex, Schema_NoSpatialField_NullSpatialIndex, Schema_CellSizeZero_NoHashmap)