// The organism record — the thing we save, version, and (later) publish to atproto. Persistence // is abstracted behind Repository; this file is just the typed shape, deliberately settled // against an in-memory store before any network format work. // // Four levels, kept separate on purpose (see the design discussion / docs/web_architecture.md §7): // - GENOTYPE : genome.cppn — the bred truth. // - ORGANISM : genome.cppn (compiled -> rule) + genome.morphology — the heritable creature. // - ENVIRONMENT : the shared "dish". Multiple organisms in one world share it. // - SIM (repro pins): what the stochastic render needs to reproduce the phenotype. // Plus VIEW (display-only, NOT needed to reproduce dynamics) and lineage (a DAG of parents). import type { CppnDict } from "../genome/cppn"; export const SCHEMA_VERSION = 0; /** v0 substrate semantics (docs/v0_normalization.md). A separate axis from schemaVersion. */ export const ENGINE_VERSION = 0; /** Deterministic render length for a reproducible capture; the live view ignores it. */ export const RENDER_STEPS = 200; /** * Scalar body-plan genes the CPPN doesn't (yet) encode. Heritable in principle; whether each * actually mutates is governed by a separate sigma table in the evolution layer, NOT stored * here. `dragCoefficient` is dimensionless (default 1.0) and multiplies the environment's * damping: effective_retention = 1 - (1 - environment.drag) * dragCoefficient. */ export interface Morphology { sensorGain: number; sensorAngle: number; sensorDistance: number; strafePower: number; axialForce: number; lateralForce: number; forceGain: number; // overall force gain (renamed from globalForceMult; desktop `global_force_mult`) depositGain: number; // pheromone strength — affects DYNAMICS (in the field feedback loop) dragCoefficient: number; // default 1.0; the agent half of Stokes drag (medium half is environment.drag) } /** * The heritable organism. Two flavours, distinguished by whether `cppn` is present: * - bred organism : `cppn` is truth; the record's 244-float `rule` is a regenerable cache. * - sterile import: no `cppn` (the desktop Core creatures have no genome); the record's `rule` * is then CANONICAL — there's nothing to recompile it from. Such organisms can't mutate/breed. * See `isSterile`. */ export interface Genome { cppn?: CppnDict; morphology: Morphology; } /** Edge behaviour for the [-1,1]^2 world (mirrors desktop boundary_conditions). `reset` is stored * but not yet rendered — the engine falls back to wrap for it (see particle_update.frag). */ export type Boundary = "wrap" | "bounce" | "reset"; /** The shared substrate — one per world; multiple organisms reference/share it. */ export interface Environment { trailPersistence: number; trailDiffusion: number; drag: number; // medium-viscosity baseline (the μ half of Stokes drag) boundary: Boundary; worldSize: number; // 1 in v0 } /** Reproducibility pins — the phenotype is a stochastic sim, so "same genome -> same render" * needs these (docs/cppn_picbreeder_design.md §6). */ export interface SimPins { seed: number; warmup: number; steps: number; fieldRes: number; particleCount: number; init: "uniform"; // reserved: "ring" | "grid" } /** Display-only. Affects the picture, never the dynamics; not required to reproduce. */ export interface ViewSettings { exposure: number; } /** How an organism came to be — sets the parents arity (DAG). */ export type Origin = "root" | "mutate" | "cross"; export interface OrganismRecord { schemaVersion: number; engineVersion: number; createdAt: string; // ISO 8601 name?: string; // Lineage — a DAG (crossover gives multiple parents). Pointer-based: the tree/graph is // reconstructed by traversal (Repository.children / .ancestry), never embedded/rewritten. parents: string[]; // [] root · [a] mutation · [a,b,…] crossover origin: Origin; depth: number; // longest path to a root = max(parent depths) + 1 (convenience; reconstructable) genome: Genome; environment: Environment; sim: SimPins; view?: ViewSettings; // 244-float rule. A compile cache when genome.cppn is present (regenerable); CANONICAL when it's // absent (a sterile import — see Genome). Either way the engine renders this directly. rule?: number[]; // Stable provenance key for idempotent seeding of built-in imports (e.g. "core/Butterflies"). // Absent on user-bred organisms. Survives renames, so the seeder dedupes on this, not the id. importKey?: string; } /** A sterile organism has no CPPN: it can't mutate, breed, or be saved as a continuation. */ export function isSterile(record: OrganismRecord): boolean { return !record.genome.cppn; } export interface RecordParts { cppn?: CppnDict; // omitted for sterile imports (rule is then canonical) morphology: Morphology; environment: Environment; sim: SimPins; view?: ViewSettings; rule?: number[]; parents: string[]; origin: Origin; depth: number; name?: string; importKey?: string; } export function buildOrganismRecord(p: RecordParts): OrganismRecord { return { schemaVersion: SCHEMA_VERSION, engineVersion: ENGINE_VERSION, createdAt: new Date().toISOString(), name: p.name, parents: p.parents, origin: p.origin, depth: p.depth, genome: p.cppn ? { cppn: p.cppn, morphology: p.morphology } : { morphology: p.morphology }, environment: p.environment, sim: p.sim, view: p.view, rule: p.rule, importKey: p.importKey, }; }