feat(simulation): deterministic world kernel (Phase 2, PR 4/5) - #89
Merged
Merged
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 perdocs/simulation/update-order.md.IdGenerator(one shared monotonic counter),OrganismStore/ResourceStore(ascending id order enforced atadd()time — an id not greater than the store's current max throws immediately, rather than letting order-corruption surface only later invalidate()).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.EventLog, a fixed-capacity ring buffer (capacity: 0retains nothing);emittedCountcounts every event regardless of retention, matching determinism.md section 8.MetricsCollectorderives all counters purely from that tick's events (never written to directly by other systems);extinctionTickis set once, first time active population hits 0, and never cleared.systems/): Environment (spawns with fractional accumulation,spawnInitialfor 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.ts,world/invariants.ts):create/step/run/snapshot/stateHash/validate/events/clock/isExtinct, plus an opt-invalidateEveryTicksfor tests/dev.stateHash()follows the determinism.md 7.3 layout field-for-field.Simulation impact
Determinism
environment,worldstreams)Math.random(), wall-clock time, or platform transcendentalsTesting
186 simulation tests (up from 85), plus the unchanged 3 contracts / 24 config / 12 tool tests. Fresh clone:
install --frozen-lockfile,format:check,check,buildall pass.validate()clean throughout.initialEnergy / metabolic cost— deterministic and seed-independent, since wandering thrust is fixed at 0.5), stays extinct 5,500 ticks later, no hidden respawn.run(137)+run(163)==run(300)from a fresh world; hash is pure (repeat calls, no step, unchanged) and changes afterstep().validate()clean every 1,000 ticks, extinction at tick 6,000 exactly (maxAge / timestep), no crash, no dropped-event surprises.organism-born/resource-spawnedevents weren't fed toMetricsCollectorat construction time, soorganismsBorn/resourcesSpawnedread 0 right afterWorld.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.Math.random/Math.sin/**in a real system file failed lint.OrganismStore/ResourceStorenow reject anadd()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 plainMapinsertion 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.spawnInitialPopulationalso emitresource-spawned/organism-bornat tick 0 during setup;update-order.mdonly 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."remainingfield on resources exists for forward compatibility with Phase 6's variable/regenerating resources.energyWastedrelative toenergyConsumedin 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