Natura Schema -- the formal data shapes
The precise types the framework passes around, so every system agrees on the wire format (operator:
"schema?"). Notation is TypeScript-ish for clarity; Natura itself is plain JS (README conventions). This
is the contract: implement a System to this shape and it plugs into any World; expose Fields to
this shape and any downstream system can read them. Pairs with README (conventions) + ALGORITHMS (per-
system field lists).
Core types
// A SYSTEM DEFINITION -- the unit of simulation. Plain object; all methods pure + deterministic.
interface System {
name: string; // unique key; how others read your fields (ctx.fields[name])
domain: 'physical' | 'life' | 'mind' | 'society'; // ONTOLOGY layer
scale: 'cosmic'|'planetary'|'regional'|'weather'|'ecological'|'local'|'micro'; // scale-ladder rung
params?: object; // preset defaults (like a Terrain Engine preset)
init(rng: Rng, params: object, clock: Clock): State; // build initial state (seeded, deterministic)
step(state: State, ctx: Ctx, dt: Seconds): State; // advance one tick; return (or mutate+return) state
fields?(state: State): Fields; // OPTIONAL: what you expose for coupling (omit if you produce nothing)
events?(state: State): Event[]; // OPTIONAL: discrete happenings this tick
}
type State = any; // opaque to the framework; a system owns its own shape
type Rng = () => number; // uniform [0,1); mulberry32, seeded (never Math.random)
type Seconds = number;
// The CONTEXT handed to step() -- the only way a system sees the world outside itself.
interface Ctx {
clock: Clock;
rng: Rng; // the world's shared stream (per-system streams come from init)
fields: { [systemName: string]: Fields }; // PRE-STEP snapshot of every system's fields (1-frame delayed)
world: { [name: string]: { def: System, state: State, params: object } }; // escape hatch; prefer fields
dt: Seconds;
}
// The CLOCK -- one time, every cadence derived by phase.
interface Clock {
t: Seconds; // absolute time
advance(dt: Seconds): Seconds;
phase(period: Seconds): number; // fraction [0,1) through a cycle of that length
dayPhase(): number; yearPhase(): number; seasonPhase(): number; // 0..1
daylight(): number; // 0 night .. 1 noon
season(): 'spring'|'summer'|'autumn'|'winter';
years(): number; eons(): number; // absolute, for deep-time systems
units: { SECOND:number, MINUTE:number, HOUR:number, DAY:number, WEEK:number,
MONTH:number, YEAR:number, DECADE:number, CENTURY:number, MILLENNIUM:number, EON:number };
}
Fields (the coupling wire format)
fields() returns a plain object of named quantities. Rules:
- Name for the QUANTITY, not the system:
moisture,temp,biomass-- notweatherOutput. - Scalar quantity: a
number. Grid quantity: aFloat32Arrayof lengthN*N, row-major (k = j*N + i), AND includeN: numberin the object so readers can resample. - Vector grid: two paired grids (
windU,windV), not an array of tuples (cache-friendly). - Agent set: an array of
Entity(for discrete things -- creatures, herds).
interface Fields { [quantity: string]: number | Float32Array | Entity[] | any; N?: number; }
// Concrete examples (from ALGORITHMS.md):
// weather.fields() -> { N, rain, moisture, temp, cloud, pressure, windU, windV, land: Uint8Array }
// terrain.fields() -> { N, elevation, slope, flow, biome: Uint8Array, seaLevel }
// vegetation.fields() -> { N, biomass, canopy, food, fuel, litter, greenness }
// ecosystem.fields() -> { populations: {[species]: number}, herds: Entity[] }
// stellar.fields() -> { luminosity, radius, temperature, insolation } // all scalars
Entity (for agent-based systems -- life-cycle, ecosystem)
The ONTOLOGY's "bag of STATE + the processes it's subject to." Not a class hierarchy -- LAYERS.
interface Entity {
id: number;
kind: 'organism' | 'matter' | 'aggregate'; // ONTOLOGY entity kind
species?: string; // for organisms
pos: [number, number] | [number, number, number]; // world coords [-1,1]
vel?: [number, number, number]; // for movers (boids)
state: { // the bag
energy?: number; water?: number; mass?: number; age?: Seconds; health?: number;
stage?: 'egg'|'seed'|'juvenile'|'adult'|'senescent'|'dead'; // LIFE-CYCLE state machine
sex?: 'm' | 'f';
[k: string]: any; // system-specific extras
};
layers?: ('physical'|'life'|'mind'|'society')[]; // which domains this entity participates in
}
Event
interface Event { system: string; t: Seconds; kind: string; [payload: string]: any; }
// e.g. { system:'weather', t, kind:'storm', x, y, str }
// { system:'life', t, kind:'birth', species, pos }
// { system:'fire', t, kind:'ignition', x, y }
World (the runner)
// SPEC -- what you pass to build a world
interface WorldSpec {
seed: number;
t0?: Seconds; // start time (default 0)
systems: (string | System)[]; // a registered name, or a System object inline
// couplings are IMPLICIT via ctx.fields for now; an explicit wiring map is a future refinement:
// couplings?: { from: string, field: string, to: string, as: string }[]
}
// INSTANCE -- what World(spec) returns
interface World {
clock: Clock;
seed: number;
step(dt: Seconds): { t: Seconds, events: Event[] };
run(steps: number, dt: Seconds): Event[];
fields(): { [systemName: string]: Fields };
get(name: string): State | undefined;
has(name: string): boolean;
systems: string[];
events(): Event[]; // last tick's events
}
Invariants (what the framework guarantees / requires)
- Determinism:
World(spec)run with the samespecand the samedtsequence yields identical state and events. Requires: noDate.now/Math.randomin any system; all randomness via theRngfrominit; the clockt/dtare inputs. (Also satisfies the sandbox no-wall-clock rule.) - Coupling ordering:
ctx.fieldsis snapshotted BEFORE anystepruns this tick, so system order within a tick does not affect results (simultaneous update, one-frame-delayed reads). - Isolation: a system reads other systems ONLY through
ctx.fields(thectx.worldescape hatch exists but using it couples to another system's internal state -- avoid). - Scale/cadence: a system may act at its own cadence off the shared clock (e.g. do a daily update only
when
floor(t/DAY)ticks over), even thoughstepis called every world tick. - Grids: all grid fields in one world should share
N(or carry their ownNand resample) and the[-1,1]^2domain, so bilinear resampling between systems is well-defined.