The shape of a frame and the shape of a steer, defined and tested in one pure module, so the round
(#8), the socket (#9), the canvas (#10), the terminal (#13) and the JSON door (#14) all agree instead
of each inventing it a fourth time.
Done looks like
[ ] src/protocol.ts (new) exports the wire types and three pure functions: encodeFrame(frame),
decodeFrame(payload) and parseSteer(message). It imports nothing from world.ts, db.ts,
live.ts or http.ts — plain data in, plain data out — so it can land while #5 is still in
flight.
[ ] A frame is compact arrays rather than objects: a version, the tick, the clock, the motes as one
flat array with a documented stride, the bloom, the gate arc, the downsampled field as one flat
array, and the round id. The stride and the field resolution are named constants in one block at
the top.
[ ] Numbers are quantised on the way out — positions and velocities to integers at a stated scale,
field cells to one byte — the module says in a sentence what precision it keeps, and
decodeFrame(encodeFrame(f)) round-trips every field within that tolerance.
[ ] parseSteer is the only place steering is validated, and it is validated at the boundary: an
object, x and y finite and clamped into the unit disc, seq a non-negative integer. A string,
an array, a missing field, a NaN, an Infinity and a 300-byte payload each return a named
failure and never a NaN downstream.
[ ] A frame carrying the roster cap of 24 motes encodes under 4 KB, asserted by a test that puts the
real byte count in its message so the budget is visible when it moves.
[ ] src/protocol.test.ts: the round trip, the size budget, every parseSteer rejection by name, and
a decoder fed a truncated or foreign payload that returns a failure and throws nothing.
Risks This module is a contract, so changing it later touches four doors — which is exactly the
cost of not having it, paid once instead of four times. Keep game rules out: a frame that knows what a
bloom means belongs in world.ts. If #5 has landed by the time this starts, take its field
resolution and constants as given rather than restating them; if it has not, declare them here and let
#5 import them.
Fits Vesper's brief: a small typed module, pure, with tests.
The shape of a frame and the shape of a steer, defined and tested in one pure module, so the round (#8), the socket (#9), the canvas (#10), the terminal (#13) and the JSON door (#14) all agree instead of each inventing it a fourth time.
Done looks like
src/protocol.ts(new) exports the wire types and three pure functions:encodeFrame(frame),decodeFrame(payload)andparseSteer(message). It imports nothing fromworld.ts,db.ts,live.tsorhttp.ts— plain data in, plain data out — so it can land while #5 is still in flight.flat array with a documented stride, the bloom, the gate arc, the downsampled field as one flat array, and the round id. The stride and the field resolution are named constants in one block at the top.
field cells to one byte — the module says in a sentence what precision it keeps, and
decodeFrame(encodeFrame(f))round-trips every field within that tolerance.parseSteeris the only place steering is validated, and it is validated at the boundary: anobject,
xandyfinite and clamped into the unit disc,seqa non-negative integer. A string, an array, a missing field, aNaN, anInfinityand a 300-byte payload each return a named failure and never aNaNdownstream.real byte count in its message so the budget is visible when it moves.
src/protocol.test.ts: the round trip, the size budget, everyparseSteerrejection by name, anda decoder fed a truncated or foreign payload that returns a failure and throws nothing.
bun testgreen.Files
src/protocol.ts(new),src/protocol.test.ts(new).Risks This module is a contract, so changing it later touches four doors — which is exactly the cost of not having it, paid once instead of four times. Keep game rules out: a frame that knows what a bloom means belongs in
world.ts. If #5 has landed by the time this starts, take its field resolution and constants as given rather than restating them; if it has not, declare them here and let #5 import them.Fits Vesper's brief: a small typed module, pure, with tests.