The simulation as one pure module — plain state in, the next state out, no clock, no database, no
network — so the mechanic in #WAKE is proven by a test before anything renders it.
Done looks like
[ ] src/world.ts exports createWorld(options), step(state) and applyInput(state, moteId, input),
all pure: same input, same output, no Date.now(), no bare Math.random() (a seeded generator
lives in the state).
[ ] State is plain JSON-able data: motes { id, name, x, y, vx, vy, thrust, stunnedUntil }, the
bloom, the gate arc, the tick count, and the field as fixed-size numeric arrays (32×32 × 2).
[ ] Momentum only: input sets a thrust acceleration capped at THRUST_MAX; position moves through
velocity; nothing stops instantly and nothing teleports.
[ ] Deposit — each moving body adds momentum, scaled by mass, to the four nearest field cells.
[ ] Decay and spread — the field decays every tick to a ≈ 2 s half-life and diffuses one cell.
[ ] Drag — every body accelerates toward the local field velocity, so a lane carries what is in it.
[ ] Rim — a body arriving above SCATTER_SPEED is scattered (velocity reversed and halved, stunned
≈ 1.5 s); below it, it slides along the rim.
[ ] The bloom is mass 50 with no thruster; step reports the tick it crosses the gate.
[ ] Every constant is a named export in one block at the top of the file, so tuning never touches the
logic.
[ ] src/world.test.ts proves the five invariants in #WAKE, and each of those tests fails if the
current field is removed.
[ ] The module imports nothing from db.ts, live.ts, env.ts or http.ts; bun test green.
Filessrc/world.ts (new), src/world.test.ts (new).
Risks The numbers are a game, not a proof — expect the tuning pass in #LEARN to move them, which
is why they live in one block. A 32×32 field at 20 Hz is nothing, but do not allocate fresh arrays
every tick. Resist mote-on-mote collision here: the sea is the interaction.
Fits Vesper's brief: the math written by hand, in a small typed module, with tests.
The simulation as one pure module — plain state in, the next state out, no clock, no database, no network — so the mechanic in #WAKE is proven by a test before anything renders it.
Done looks like
src/world.tsexportscreateWorld(options),step(state)andapplyInput(state, moteId, input),all pure: same input, same output, no
Date.now(), no bareMath.random()(a seeded generator lives in the state).{ id, name, x, y, vx, vy, thrust, stunnedUntil }, thebloom, the gate arc, the tick count, and the field as fixed-size numeric arrays (32×32 × 2).
THRUST_MAX; position moves throughvelocity; nothing stops instantly and nothing teleports.
SCATTER_SPEEDis scattered (velocity reversed and halved, stunned≈ 1.5 s); below it, it slides along the rim.
stepreports the tick it crosses the gate.logic.
src/world.test.tsproves the five invariants in #WAKE, and each of those tests fails if thecurrent field is removed.
db.ts,live.ts,env.tsorhttp.ts;bun testgreen.Files
src/world.ts(new),src/world.test.ts(new).Risks The numbers are a game, not a proof — expect the tuning pass in #LEARN to move them, which is why they live in one block. A 32×32 field at 20 Hz is nothing, but do not allocate fresh arrays every tick. Resist mote-on-mote collision here: the sea is the interaction.
Fits Vesper's brief: the math written by hand, in a small typed module, with tests.