Skip to content

feat: organisms carry genomes, behavior-preserving (Phase 4, PR 3/5) - #99

Merged
CodeByBryant merged 1 commit into
mainfrom
feature/organisms-carry-genomes
Oct 24, 2026
Merged

CodeByBryant merged 1 commit into
mainfrom
feature/organisms-carry-genomes

Conversation

@CodeByBryant

Copy link
Copy Markdown
Owner

Summary

Third of five Phase 4 PRs (design in #97, engine in #98). Every organism now owns an immutable genome, and every trait a system reads comes from it instead of global config. Simulated behavior is unchanged from Phase 3, which is the point of this PR: it lets the change be reviewed as "nothing moved" before PR 4 turns inheritance on.

  • Founders get identical genomes built from the config baseline, each with its own random brain from the genetics stream.
  • Children get a new GenomeId and, for now, a copy of their mother's (the lower-id parent's) genes and brain. Recombination, mutation and lineage arrive in PR 4.
  • Systems read genes: Movement (maxSpeed, maxTurnRate, life-stage ages), Interaction (radius), Metabolism (cost genes), Perception (sensorRadius), Decision (wanderJitter), Death (maxAge), Reproduction (minEnergy, offspringInvestment, cooldown, searchRadius).
  • Capability is coupled to cost via derived traits (organisms/traits.ts): energy capacity and basal cost scale with body size, sensor cost with sensor radius, and a parent pays its own investment plus overhead. Reproduction also requires energy >= max(minEnergy, cost), so paying can never drive energy negative.
  • Contracts: genomeId on OrganismSnapshot, OrganismBornEvent and HistoricalOrganismRecord; World.genomeOf(organismId) returns the deeply frozen genome; snapshot radius is the organism's own gene.
  • Validation: World.validate() now checks every living genome against the gene table and brain architecture, and that genome ids are unique.
  • DeathSystem and PerceptionSystem lose their now-unused config parameter. Spatial grid cell sizes become the gene range maximum (queries are correct for any cell size; this only affects efficiency).

The equivalence gate

Two independent checks that nothing moved:

  1. world.test.ts (the pinned Phase 3 scenarios: Scenario A 26/47/21, extinction at tick 845, Scenario C, the 50k-tick stability band) is byte-identical to main and passes.
  2. 53 full-state digests recorded from the pristine Phase 3 engine (a throwaway worktree of main) match the refactored engine exactly. Each digest covers every organism's position, velocity, energy, age and cooldown, plus the complete event lists, across 7 scenarios (default, Scenario A/B/C, verbose events, a 20,000-tick run, and custom baselines that exercise every gene plumbing) sampled at regular intervals up to 20,000 ticks. Re-run on the final tree: still 53 of 53.

Hash schema v3 and goldens

HASH_SCHEMA_VERSION 2 -> 3 (nextGenomeId plus each organism's genome). The published StateHasher vectors and all six golden hashes are regenerated. The golden values change only because the schema did, not because behavior did, which is what the gate above proves. stream-usage.test.ts drops genetics from the reserved list since founders draw their brains from it.

Tests

378 simulation tests (up from 326). New: gene-driven behavior per system (two organisms under the same config behave differently when their genes differ), derived-trait exactness at baseline and scaling, world-level genome wiring, and genome invariants. The existing system tests derive each organism's genome from the test's own config via a shared helper.

Test plan

  • pnpm format:check clean in a fresh clone
  • pnpm check green in a fresh clone: 7 contracts, 55 config, 378 simulation
  • pnpm build green (all 8 packages/apps)
  • pnpm test:determinism: 17/17 against the regenerated v3 goldens
  • Equivalence harness: 53/53 digests identical to Phase 3 (twice)

Next: PR 4 switches on recombination and mutation, registers lineage, and adds the stability gate (the 50k-tick band must still hold with default mutation).

🤖 Generated with Claude Code

Phase 4 PR 3. Every organism now owns an immutable genome, and every trait
a system reads comes from it instead of global config. Founders get identical
genomes built from the config baseline (each with its own random brain from
the genetics stream); a child gets a new GenomeId and, for now, a copy of its
mother's (the lower-id parent's) genes and brain. Recombination and mutation
are switched on in the next PR.

Movement, Interaction, Metabolism, Perception, Decision, Death and
Reproduction read the organism's own genes. Capability is coupled to cost
through derived traits (organisms/traits.ts): energy capacity and basal cost
scale with body size, sensor cost with sensor radius, and a parent pays its
own investment plus overhead. Reproduction also requires energy >=
max(minEnergy gene, cost), so paying can never drive energy negative. At the
baseline genome every derived value equals its Phase 3 value exactly.

genomeId is added to the snapshot, birth event and history record;
World.genomeOf returns the frozen genome; snapshot radius is the organism's
own gene. World.validate() now checks every living genome against the gene
table and brain architecture and that genome ids are unique. Spatial grid
cell sizes become the gene range maximum. DeathSystem and PerceptionSystem
lose their now-unused config parameter.

Hash schema v3 (nextGenomeId plus each organism's genome); published hash
vectors and all golden hashes are regenerated, and stream-usage drops
genetics from the reserved list. The golden values change only because the
schema did.

Equivalence gate: world.test.ts (the pinned Phase 3 scenarios) is
byte-identical and passes. Beyond that, 53 full-state digests (every
organism's position, energy, age and cooldown, plus the complete event lists)
recorded from the pristine Phase 3 engine match the refactored engine
exactly across 7 scenarios, including custom baselines, up to 20,000 ticks.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 24, 2026 02:44

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 insufficient remaining quota.

@CodeByBryant
CodeByBryant merged commit 82577dd into main Oct 24, 2026
11 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