Hierarchical spatial environments with cascading property propagation.
plato-spatial combines typed Pydantic models, a NetworkX-backed containment graph, and a DeltaTick propagation engine to manage hierarchical spatial containers where state changes cascade upward through the hierarchy.
World → Station → Room → Object
When an object's atmosphere changes, the change propagates to its parent room, then to the station, then to the world — all in a single tick.
Any system that models nested spatial containers needs to answer:
"When something changes down here, what happens up the chain?"
plato-spatial answers that question with a small, well-tested library.
| Domain | Example |
|---|---|
| Game worlds | A poison gas grenade detonates in a room — the room, zone, and region all register the atmospheric change. |
| IoT hierarchies | A temperature sensor in a rack reports a spike — the aisle, row, and datacenter inherit the alert. |
| Building management | A CO₂ detector trips in an office — the floor and building update their aggregate status. |
| MUD / text adventures | A spell alters the magical field of a chamber — the zone and world record the shift. |
| Simulation | A chemical reaction in a vat changes pressure — the lab, sector, and habitat propagate the delta. |
pip install plato-spatialFor development:
git clone https://github.com/SuperInstance/plato-spatial.git
cd plato-spatial
pip install -e ".[dev]"from plato_spatial import GraphEngine, Entity, EntityId
ge = GraphEngine()
ge.add_entity(Entity(EntityId("world"), "Earth-Orbit World"))
ge.add_entity(Entity(EntityId("station"), "Alpha Station"), parent_id=EntityId("world"))
ge.add_entity(Entity(EntityId("room"), "Medbay"), parent_id=EntityId("station"))
ge.add_entity(Entity(EntityId("tank"), "Medical Tank"), parent_id=EntityId("room"))from plato_spatial import DeltaTickEngine
engine = DeltaTickEngine(ge)
# Something happens at the tank level
engine.queue_update(EntityId("tank"), "atmosphere", "Toxic-Nitrogen")
engine.tick()
# The change cascaded upward
print(ge.get_entity(EntityId("room")).properties["atmosphere"]) # → "Toxic-Nitrogen"
print(ge.get_entity(EntityId("station")).properties["atmosphere"]) # → "Toxic-Nitrogen"
print(ge.get_entity(EntityId("world")).properties["atmosphere"]) # → "Toxic-Nitrogen"For Pydantic-validated entities with dimensions, tags, and timestamps:
from plato_spatial import SpatialWorld, SpatialStation, SpatialRoom, SpatialObject
from uuid import uuid4
world = SpatialWorld(name="Sol System Alpha", metadata__tags=["primary"])
station = SpatialStation(name="Aegis Station", parent_id=world.id)
room = SpatialRoom(name="Command Deck", parent_id=station.id, dimensions={"x": 20, "y": 10, "z": 10})
console = SpatialObject(name="Main Console", parent_id=room.id, weight=150.5)plato_spatial/
├── types.py # EntityId, Entity dataclass, EntityType enum
├── models.py # Pydantic spatial models (World, Station, Room, Object)
├── graph_engine.py # NetworkX-backed containment graph
├── delta_tick.py # Batched propagation engine with configurable rules
└── __init__.py # Public API
The engine uses pluggable rules to decide which properties cascade. A rule is a callable:
def my_rule(key: str, value, entity: Entity, engine: GraphEngine) -> bool:
"""Return True if the change should cascade to the parent."""
return key == "radiation_level"Register custom rules:
engine = DeltaTickEngine(ge)
engine.register_rule(my_rule)Built-in rules:
| Rule | Behaviour |
|---|---|
atmosphere_rule |
Atmosphere changes propagate from child → parent (replace) |
temperature_rule |
Temperature changes propagate from child → parent (replace) |
tag_union_rule |
Child tags are unioned into the parent's tag list |
| Method | Description |
|---|---|
add_entity(entity, parent_id=None) |
Register an entity, optionally under a parent |
remove_entity(entity_id) |
Remove an entity from the graph |
get_entity(entity_id) |
Fetch an Entity by ID |
get_children(entity_id) |
Direct child IDs |
get_parent(entity_id) |
Immediate parent ID (or None) |
get_lineage(entity_id) |
Path from root to entity |
get_root() / get_roots() |
Root node(s) |
find_all_descendants(entity_id) |
Full descendant set |
get_siblings(entity_id) |
Siblings sharing the same parent |
depth(entity_id) |
Tree depth (root = 0) |
| Method | Description |
|---|---|
queue_update(entity_id, key, value) |
Queue a property change |
tick() |
Process all queued updates and cascade |
register_rule(rule) |
Add a custom propagation rule |
clear_rules() |
Remove all rules |
pending_count |
Queued updates waiting for next tick |
tick_count |
Total ticks processed |
pip install -e ".[dev]"
pytest -vMIT