Engine
The PHIDS engine is a deterministic tick machine implemented around SimulationLoop.step().
Each tick advances a shared ecological state through a fixed sequence of transformations. This
ordered execution is the operational heart of the simulator: it is where biological assumptions,
data-oriented constraints, and performance rules become a concrete runtime.
Execution Model Overview
The live runtime consists of two principal state stores and one orchestrator:
SimulationLoop— orders the phases and owns runtime coordination,ECSWorld— stores discrete plant, swarm, and substance entities,GridEnvironment— stores continuous and grid-aligned fields.
The engine should therefore be read as a hybrid ECS + cellular automata system:
- entity-centric processes are expressed through components and system passes,
- field-centric processes are expressed through vectorized NumPy layers and convolution-style updates.
Tick Ordering
The current tick ordering implemented by SimulationLoop.step() is:
- Flow-field generation
- Camouflage attenuation
- Lifecycle system
- Interaction system
- Signaling system
- Telemetry recording and replay snapshotting
- Termination evaluation
This ordering is not cosmetic. Each later phase assumes the side effects of the previous one.
flowchart TD
A[Tick begins under asyncio.Lock] --> B[Compute flow field]
B --> C[Apply plant camouflage attenuation]
C --> D[run_lifecycle]
D --> E[run_interaction]
E --> F[run_signaling]
F --> G[Record telemetry]
G --> H[Append replay snapshot]
H --> I[Evaluate Z1-Z7 termination]
I --> J[Increment tick]
Runtime Ownership and Invariants
ECSWorld
ECSWorld owns:
- entity allocation and destruction,
- per-component indexing,
- a spatial hash mapping
(x, y)cells to entity IDs.
This structure enables O(1)-style co-location queries through entities_at() and supports the
project rule that local interactions should not devolve into pairwise global scans.
GridEnvironment
GridEnvironment owns:
- aggregate plant energy,
- per-species plant energy layers,
- signal layers,
- toxin layers,
- wind fields,
- the current scalar flow field.
It also owns the most explicit double-buffered mechanics in the engine. Plant-energy aggregates, signal layers, and toxin layers all have corresponding write buffers that are swapped after the relevant update step.
SimulationLoop
SimulationLoop owns:
- the global tick counter,
- live/paused/terminated state,
- the async lock guarding
step(), - cached parameter tables derived from
SimulationConfig, - telemetry and replay integration.
It is the only object that composes all systems into a scientifically meaningful update order.
Phase-by-Phase Semantics
1. Flow-field generation
The tick begins by computing env.flow_field from the current read-visible plant-energy and
toxin layers:
compute_flow_field(self.env.plant_energy_layer, self.env.toxin_layers, ...)
The flow field is a scalar guidance surface used by the interaction system. Positive values are associated with attractive plant energy; toxin contribution is subtractive and therefore can act as a repulsive signal.
This phase is performance-sensitive and centralized in phids.engine.core.flow_field.
2. Camouflage attenuation
Immediately after global flow-field generation, the loop traverses live plants and attenuates the field at cells where camouflage is enabled.
This is a useful example of PHIDS’s design style: a global field is computed once, then modified by local plant traits before it is consumed by swarm movement logic.
3. Lifecycle system
run_lifecycle() updates flora-centric state. Its current responsibilities include:
- growth according to the configured energy formula,
- reproduction attempts when interval and energy constraints permit,
- pruning of dead mycorrhizal links,
- plant death and garbage collection,
- deterministic, interval-gated mycorrhizal link formation,
- rebuilding of the aggregate plant-energy layer after writes.
Important current-state details:
- reproduction is stochastic in placement but bounded by the environment and occupancy rules,
- root-network growth is deterministic in ordering and cadence,
- at most one new mycorrhizal link is formed per permitted growth attempt,
- dead plants are removed from spatial occupancy before garbage collection.
4. Interaction system
run_interaction() updates swarm-centric behavior. Its current responsibilities include:
- movement cooldown handling,
- gradient-following movement via the flow field,
- random-walk behavior for repelled swarms,
- diet-matrix gated feeding on co-located plants,
- starvation accumulation and attrition,
- toxin casualty application from toxin layers,
- reproduction by converting stored energy into new individuals,
- mitosis when population reaches twice the current initial baseline.
Two architectural features are especially important here:
- plant feeding is resolved through
world.entities_at(swarm.x, swarm.y), not by scanning the entire world, - swarm movement reads a single global field rather than running individualized search.
5. Signaling system
run_signaling() is responsible for defensive substance logic. In current implementation it:
- evaluates trigger conditions plant by plant,
- materializes
SubstanceComponententities when a trigger first fires, - advances synthesis countdowns,
- activates substances once synthesis is complete and any activation condition passes,
- emits signals and toxins into the environment,
- relays signals through mycorrhizal connections,
- updates aftereffect timers,
- delegates diffusion to
GridEnvironment, - removes expired or orphaned substance entities.
Current-state nuance matters here:
- toxin layers are rebuilt from active emitters each signaling pass,
- active toxin effects are also applied directly to swarms through the signaling logic,
env.diffuse_signals()andenv.diffuse_toxins()are both called at the end of the phase,- signal persistence and toxin persistence are therefore not identical in semantics, even though both currently pass through environment-layer helpers.
This is one of the richest areas of the simulator and deserves a dedicated chapter later.
6. Telemetry and replay
After ecological state transitions are complete for the tick, the loop records telemetry and then appends a serialized snapshot of the current environment state.
The order matters: replay and telemetry are intended to describe the post-phase state of the completed tick, not an intermediate partial state.
7. Termination evaluation
Finally, the loop evaluates termination conditions through check_termination().
The currently implemented conditions are:
Z1— maximum tick count reached,Z2— extinction of a configured flora species,Z3— extinction of all flora,Z4— extinction of a configured predator species,Z5— extinction of all predators,Z6— total flora energy exceeds a threshold,Z7— total predator population exceeds a threshold.
The tick counter is incremented after the termination check has been computed for the current state.
Double-Buffering in Practice
PHIDS’s documentation and architectural rules refer to double-buffering. In the current engine,
this is most concretely implemented in GridEnvironment.
Plant-energy buffering
- writes go to
_plant_energy_by_species_write, rebuild_energy_layer()aggregates and swaps the write and read buffers,- the aggregate
plant_energy_layerthereby becomes a read-visible summary for later phases.
Diffusion-layer buffering
signal_layersand_signal_layers_writeform a read/write pair,toxin_layersand_toxin_layers_writeform a read/write pair,- each diffusion helper computes into the write buffer and then swaps.
This means PHIDS currently has field-level double-buffering rather than a universally cloned entire simulation state. The engine relies on phase ordering plus buffered fields to preserve deterministic behavior.
Performance-Sensitive Regions
Several parts of the engine are architecturally hot paths.
Flow field
The flow-field kernel is Numba-compiled and benchmark-tested because it is computed every tick and used globally by moving swarms.
Diffusion
Signal and toxin diffusion use scipy.signal.convolve2d and explicitly truncate subnormal tails
below SIGNAL_EPSILON to preserve sparsity and avoid performance degradation.
Spatial queries
All cell-local ecological interactions rely on spatial-hash lookup rather than global scans.
Methodological Limits of the Current Engine
The current implementation is highly structured, but it should be described precisely rather than idealized.
- It is deterministic in phase ordering and bounded state handling, but not every biological event is purely deterministic because some processes, such as seed dispersal, use randomness.
- It uses buffered environmental state, but it is not a fully duplicated read/write world state in the strongest ECS-simulation sense.
- Some behavior spans both environment layers and entity-side state transitions, especially in the signaling/toxin region.
These limitations are not documentation problems; they are part of the present engine model.
Canonical Deep-Dive Chapters
biotope-and-double-buffering.mdecs-and-spatial-hash.mdflow-field.mdlifecycle.mdinteraction.mdsignaling.md
These chapters now document the strongest current architectural invariants beneath the engine overview. The engine phase sequence is now documented end-to-end at both overview and subsystem depth.
Key Source Modules
src/phids/engine/loop.pysrc/phids/engine/core/biotope.pysrc/phids/engine/core/ecs.pysrc/phids/engine/core/flow_field.pysrc/phids/engine/systems/lifecycle.pysrc/phids/engine/systems/interaction.pysrc/phids/engine/systems/signaling.py