Skip to content

feat(simulation): deterministic world kernel (Phase 2, PR 4/5) - #89

Merged
CodeByBryant merged 1 commit into
mainfrom
feature/world-kernel
Sep 27, 2026
Merged

CodeByBryant merged 1 commit into
mainfrom
feature/world-kernel

Conversation

@CodeByBryant

Copy link
Copy Markdown
Owner

Summary

Phase 2, PR 4 of 5. The headless engine itself, built on the contracts (PR2) and foundations (PR3): entity stores, spatial index, event log, metrics, the eight pipeline systems, and World, orchestrated exactly per docs/simulation/update-order.md.

  • Entities: IdGenerator (one shared monotonic counter), OrganismStore/ResourceStore (ascending id order enforced at add() time — an id not greater than the store's current max throws immediately, rather than letting order-corruption surface only later in validate()).
  • Spatial: SpatialHash<T>, a uniform grid; every query result is explicitly sorted by (distanceSquared, id), so results never depend on the grid's internal bucket order.
  • Events: EventLog, a fixed-capacity ring buffer (capacity: 0 retains nothing); emittedCount counts every event regardless of retention, matching determinism.md section 8.
  • Metrics: MetricsCollector derives all counters purely from that tick's events (never written to directly by other systems); extinctionTick is set once, first time active population hits 0, and never cleared.
  • Systems (systems/): Environment (spawns with fractional accumulation, spawnInitial for setup), Perception (nearest resource in sensor range), Decision (the agreed placeholder: seek, else wander), Movement (turn/speed clamped, position clamped to the rectangle), Interaction (never double-consumes a resource in one tick, energy capped with waste reported), Metabolism (basalCost + movementCost * speed^2), Reproduction (literal no-op, reserved for Phase 3), Death (starvation/age, in ascending id order).
  • World (world/World.ts, world/invariants.ts): create/step/run/snapshot/stateHash/validate/events/clock/isExtinct, plus an opt-in validateEveryTicks for tests/dev. stateHash() follows the determinism.md 7.3 layout field-for-field.

Simulation impact

  • Changes organism behavior (the engine now exists)
  • Changes ecology or resources
  • Changes determinism (implements the accepted spec)

Determinism

  • Uses the seeded random source throughout (environment, world streams)
  • Does not use Math.random(), wall-clock time, or platform transcendentals
  • Determinism tests were added

Testing

186 simulation tests (up from 85), plus the unchanged 3 contracts / 24 config / 12 tool tests. Fresh clone: install --frozen-lockfile, format:check, check, build all pass.

  • Scenario A (roadmap: 10 organisms, 100 resources, fixed seed): pinned exact outcome (9 active, 1 starved, 0 by age, resources consumed, no extinction), validate() clean throughout.
  • Scenario B (roadmap: 10 organisms, 0 resources): extinction at tick 500 (exactly initialEnergy / metabolic cost — deterministic and seed-independent, since wandering thrust is fixed at 0.5), stays extinct 5,500 ticks later, no hidden respawn.
  • Determinism: same seed -> identical hash and snapshot; different seed -> different hash; run(137) + run(163) == run(300) from a fresh world; hash is pure (repeat calls, no step, unchanged) and changes after step().
  • Manually ran 100,000 ticks (not committed — PR5 owns the permanent long-run suite): completes in ~8s, validate() clean every 1,000 ticks, extinction at tick 6,000 exactly (maxAge / timestep), no crash, no dropped-event surprises.
  • A real bug was caught by a correctness assertion, not by a determinism assertion: initial-population organism-born/resource-spawned events weren't fed to MetricsCollector at construction time, so organismsBorn/resourcesSpawned read 0 right after World.create. The determinism tests (same-seed-same-hash, chunked==direct) all still passed throughout, since the bug was consistent across compared runs — worth noting as a reminder that determinism tests alone don't catch counting bugs, only direct value assertions do. Fixed and covered by a new test.
  • Negative checks (temporary, reverted) on real production files: a system importing a sibling system failed the dependency checker; Math.random/Math.sin/** in a real system file failed lint.
  • OrganismStore/ResourceStore now reject an add() with an id not greater than the current max (a stricter, self-enforcing version of the "ascending id order" contract, added after a test caught that plain Map insertion order only produces ascending output when callers insert in increasing-id order, which is true in practice but wasn't enforced).

Review notes

  • EnvironmentSystem/World.spawnInitialPopulation also emit resource-spawned/organism-born at tick 0 during setup; update-order.md only explicitly calls out organism-born for setup, but treating resource creation the same way seemed the more consistent reading of "every important state transition emits an event."
  • Resources are consumed whole in one bite in Phase 2 (matches "start with one resource type"); the remaining field on resources exists for forward compatibility with Phase 6's variable/regenerating resources.
  • Default config produces fairly high energyWasted relative to energyConsumed in the foraging scenario; noted as a tuning question for later, not a correctness issue (config is intentionally excluded from the state hash).

🤖 Generated with Claude Code

Implements the Phase 2 headless engine on top of the foundations from
PR3: entity stores (id-ordered, monotonic-id-enforcing), a uniform-grid
SpatialHash, a ring-buffer EventLog, MetricsCollector, and the eight
pipeline systems (Environment, Perception, Decision, Movement,
Interaction, Metabolism, Reproduction (no-op), Death) orchestrated by
World exactly per docs/simulation/update-order.md. World exposes
create/step/run/snapshot/stateHash/validate/events/clock/isExtinct.

The placeholder controller seeks the nearest perceived resource, else
wanders, per the agreed Phase 2 design.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings September 27, 2026 01:05

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@CodeByBryant
CodeByBryant merged commit 6df5575 into main Sep 27, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants