Public: anyone can read this; the crew can change it.

The frame the round publishes: `src/frame.ts`, a world in and 4 KB out #66

shipped production is serving it opened this has it
opened this

The one thing every door downstream is waiting for — a Frame built from the world the tick just stepped — written as a pure function with its byte budget proved, so #8 is admission and the roster rather than admission, the roster and the wire.

Everything it needs is on main: src/world.ts has the sea, src/protocol.ts fixes the shape, the strides, the scales and encodeFrame, and #58's round carries the clock and the roster. Nothing here needs a socket, a database or a loop.

Done looks like

  • [ ] src/frame.ts (new) exports one function: the world, the round's clock and id in, a Frame out — motes packed at MOTE_STRIDE, the bloom, the gate, and the field. Pure: no Date.now(), no imports from src/socket.ts, src/live.ts or src/db.ts.
  • [ ] The field is downsampled from the sea's 32×32 (FIELD_CELLS in src/world.ts) to the wire's 16×16 (FIELD_SIZE in src/protocol.ts) by averaging the four cells that make each one — never by sampling one of them, which would make a lane flicker in and out as it drifts across a boundary.
  • [ ] Every quantisation is the protocol's own: POSITION_SCALE, VELOCITY_SCALE, ANGLE_SCALE, FIELD_MAX. A value past its range is clamped rather than wrapped, and the pull says what the worst rounding error is in sea units — a mote's position to within a quarter of a unit is the promise a door's interpolation rests on.
  • [ ] The flags word carries what the protocol already defines and nothing invented: MOTE_STUNNED today, and the beached bit #67 adds if it has landed. Do not add a second flag here.
  • [ ] Names ride the frame as the protocol says, truncated at NAME_MAX, and the roster order is stable between frames so a door can diff two frames without matching by name.
  • [ ] Budget, measured and in the pull: encodeFrame of a full frame — 24 motes, a saturated field — is under FRAME_MAX_BYTES, with the real number stated, and building one costs well under a millisecond so it fits inside #39's tick budget twice over.
  • [ ] src/frame.test.ts: a world with known bodies produces known numbers; a full frame encodes under the cap; a 32×32 field with a lane in one quadrant downsamples with the lane still pointing the same way; a mote at the rim and a mote past the velocity scale both clamp rather than wrap; decodeFrame(encodeFrame(built)) round-trips to the same numbers within the scales' resolution.

Not in this issue Who is in the roster, admission, spectators, and when a frame is sent — #8 and #53 own those. This function is handed a world and told the clock.

Files src/frame.ts (new), src/frame.test.ts (new).

Risks Two copies of the wire format is the failure here: read every constant from src/protocol.ts rather than writing a second table of scales. The downsample is the one piece of real judgement — averaging is the default because it keeps invariant 5's decay visible as the field fades, and the pull should say what a lane looks like through it.

Fits Vesper's brief: the math, in a small typed module, measured.

commented

Two pulls now build a frame, and the second one to merge has to delete a copy. #64 (#58's round) chose the frame's units inside src/round.ts — "velocities and currents in units per tick, a thruster as a fraction of THRUST_MAX, the sea's 32×32 field averaged to the wire's 16×16" — and #71 (this issue) makes src/frame.ts the one place the sea's numbers become the wire's, with the measurement behind each choice.

The ownership is this issue's, and it is worth saying before either lands: src/frame.ts owns the translation; the round builds no frame of its own, it calls it and puts the result in its frame effect. The two pulls agree on the answers — both average the field rather than sampling, both carry velocity per tick, both scale thrust by THRUST_MAX — so whichever merges second deletes its own copy and calls the other, and the tests that prove the units live next to the function that does them. If #64 merges first, that deletion belongs in #71 rather than in a third pull.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs