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

The sea has to remember whose current it is: a per-player share of every cell, budgeted #73

open not picked up yet opened this
opened this

The credit record (#38, in review as #72) pays a round by whose current carried the bloom, and its ledger rests on one fact about the simulation: because drag is linear, the bloom's velocity is exactly the sum of the velocity each player's shares alone would have given it. The sea cannot answer that question today. Field is { x: number[], y: number[] } — 1,024 cells of summed momentum with nobody's name on any of it — and deposit adds a body's wake into cells that then spread into four neighbours and decay. Whose momentum a cell is holding is gone the tick it arrives.

So before #12 can pay anybody, the field has to carry shares, and that is a change to the hottest loop in the app rather than a new module beside it. This issue is that change and its budget, and nothing else.

Done looks like

  • [ ] The field carries a share per player alongside the total: one layer per seat, each a (x, y) field of its own, and the total stays exactly what it is today so nothing about the physics changes. A test asserts the sum of the layers equals the field, cell by cell, after a thousand ticks of ordinary play — that identity is what makes the record's linearity argument true rather than assumed.
  • [ ] Spread and decay apply to a layer exactly as they apply to the total — the same FIELD_SPREAD, the same FIELD_DECAY, the same floor — so a share cannot outlive the current it is part of, and invariant 5 holds layer by layer: with every thruster off, each layer's magnitude strictly decreases and reaches zero.
  • [ ] A layer belongs to a seat for as long as the seat exists. When a seat is released mid-round — the idle sweep, a disconnect — its layer keeps decaying and keeps its credit, because the record pays the lane-builder who leaves on the tick their lane does the work. The layer is freed when it is empty, not when its player goes.
  • [ ] The bloom's own wake is nobody's share, as the record says: it deposits into the total and into no layer, and a test proves a round in which only the bloom moves pays nobody.
  • [ ] The budget, measured and in the pull. This is 24 layers over 1,024 cells, stepped 20 times a second: state the cost of one step at 24 motes with a full ledger, against #39's tick budget, and state the memory. If flat arrays per layer are too slow, say what you measured and what you chose instead — a sparse layer, a coarser share grid than the field, or shares only where a lane exists — and argue it against the credit invariant's tolerance rather than by feel. Choosing a coarser ledger is allowed; choosing it silently is not.
  • [ ] sampleField grows a way to ask what a cell's layers say at a point, because that is what the attribution in #12 reads, and it stays pure.
  • [ ] #68's blob is considered: a stored field is the total only, since shares belong to seats that a new round does not have. Say so where the blob is written, so an inherited lane pays nobody rather than paying a stranger.

Not in this issue Turning shares into points. The per-tick attribution, the purse, the ledger's arithmetic and the board all stay #12, which now waits on this and reads it.

Files src/world.ts, src/world.test.ts.

Risks This is the tick's hot loop and the one place in the app where a wrong constant is invisible: the sum-of-layers test is the guard, and it should run over real play rather than over a hand-built field. Second risk: the temptation to store shares as a map per cell, which allocates inside the tick — 1,024 maps churning 20 times a second is the shape that fails #39. Third: a layer per seat and not per socket, or two tabs pay twice (#70).

Fits Vesper's brief exactly: the math, in the module that owns it, measured and budgeted.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs