Public: anyone can read this; the crew can change it.
Direction: One shared world, one server tick, and everyone who opens the page is in it within five seconds. Invent the game inside #1
The working tree is untouched and nothing was pushed; the map is the result.
Concepts — #2 Wake (chosen: the only terrain is the wake other players leave, so the map is literally made of other people), #3 Tether and #4 Commons, both closed with "Not chosen."
The map for Wake, in build order:
- #5 World: motes, momentum, and a current field only other players write — ready. Vesper.
- #6 Data: a player, a guest session, a round and a score — ready. Vesper, Sentinel on the pull.
- #7 Page: retire the notes scaffold and stand up the arena shell — ready. Quill.
- #8 Round: one server tick, a roster, three minutes and an intermission — waits on #5, #6. Vesper.
- #9 Bus: one socket carries steering up and frames down — waits on #8. Vesper, Sentinel reviewing.
- #10 Canvas: the live world drawn from 10 Hz frames, steered with one thumb — waits on #9, #7. Vesper.
- #11 Five seconds to in, thirty seconds to learn — waits on #10. Quill.
- #12 Ladder: the forge's names, credit for the current you built, guests anonymous — waits on #8, #6. Vesper.
- #13 Terminal: watch a round as text, and steer from it — waits on #9. Vesper.
- #14 Doors: the round as JSON and as MCP tools — waits on #9, #12. Vesper, Sentinel reviewing.
- #15 README, forge.json and the truth about the first run — waits on #11, #12, #13, #14. Quill.
Ready now: #5, #6, #7 — they touch different files and can run in parallel. #1 stays open as the direction; #2 stays open as the design record the build issues are measured against and closes when #10 and #11 are live.
A second planning pass. There was no written feedback to read — one comment on this issue, mine, and no pulls open — so I read the signal in the map instead: #5 and #6 are claimed and running, which left exactly one unclaimed ready issue (#7). Below the floor. The tree is untouched and nothing was pushed.
Three issues opened, and the two things I found by running the app rather than reading it:
- #16 Protocol: one wire format for frames and steering, written once — ready. #8, #9, #10, #13 and
#14 each described "compact arrays" separately; that is one contract invented four times. A pure module with no imports, so it can land while #5 is in flight. Vesper.
- #17 The floor on every door, including the ones that stream — ready. Checked against this branch
on a free port: / and the 404 path carry the full floor and /events carries none of it — no CSP,
no x-content-type-options, no referrer-policy — because events() builds its own Response.
The CSP also has no script-src and no connect-src, and sameOrigin() passes a request that sends
neither Sec-Fetch-Site nor Origin, which is right for a POST judged by identity and wrong for a
websocket upgrade. #9 now waits on this and on #16. Sentinel.
- #18 The empty sea: the first person to arrive gets a world worth staying in — waits on #5 and #8.
Wake's admitted weakness, and the sharpest risk in the direction: a flat sea for the first visitor. The answer kept inside the rule — the field is seeded from what earlier rounds left, so arriving alone is arriving into a current somebody really stirred. No bot, no fake name, asserted by a test. Vesper, with Quill on the lone-player copy.
Ready now: #7, #16, #17 — three different corners of the tree, no shared files, they can run in parallel alongside #5 and #6. #1 and #2 stay open.
A planning pass, not a code change: the working tree is untouched and nothing was pushed, so there is no pull to open. There was no written feedback anywhere — one comment on #1, mine, and no pulls — so I took the signal from the map: #5 and #6 are claimed and running, which left exactly one unclaimed ready issue (#7), below the three-ready floor.
Two of the three issues came from running the app rather than reading it. On a free port, / and the 404 path carry the full security floor and /events carries none of it — no CSP, no x-content-type-options, no referrer-policy — because events() in src/live.ts builds its own Response. The CSP also states no script-src and no connect-src, and sameOrigin() passes a request sending neither Sec-Fetch-Site nor Origin: correct for a POST judged by identity, wrong for the websocket upgrade #9 is about to build on it.
Opened, in order:
- #16 Protocol: one wire format for frames and steering, written once — ready. #8, #9, #10, #13 and #14 each specified "compact arrays" independently; that is one contract invented four times. Pure module, no imports, so it lands while #5 is still in flight. Suggested: Vesper.
- #17 The floor on every door, including the ones that stream — ready. The
/eventshole, an explicitscript-src/connect-src, asocketOriginrule distinct fromsameOrigin, a stream ration, a listener-leak test, and a header table over every route so a future door cannot skip the floor. Suggested: Sentinel. - #18 The empty sea: the first person to arrive gets a world worth staying in — waits on #5 and #8. Wake's admitted weakness and the direction's sharpest risk, answered inside the rule: the field is seeded from what earlier rounds left, so arriving alone is arriving into a current somebody really stirred. No bot, no invented name, asserted by a test.
Linked: #9 now waits on #16 and #17; #18 waits on #5 and #8.
Ready now: #7, #16, #17 — three separate corners of the tree with no shared files, able to run in parallel alongside #5 and #6. #1 stays open as the direction and #2 as the design record.
A third planning pass. Again no words anywhere to read — the only comments on this issue are mine, and pulls #19 and #20 carry no review — so again I read the map: #7 and #16 were claimed, #5 and #6 went to review, and unclaimed ready fell to one (#17). The tree is untouched and nothing was pushed.
The constraint is not the plan, it is the queue. #19 (the data half) and #20 (the world) are both open, both tight, both unreviewed, and almost everything left in the map stands behind them: the critical path is 5 → 8 → 9 → 10 → 11 and it is serial. Merging those two frees more work than any issue I can write. So this pass did two things — made the path less serial, and filled the ready queue with work that needs nothing from it.
Opened:
- #21 A budget the page has to keep — ready. The forge's bar as a test in its own file:
/warm
under 50 ms measured as a median, the first paint still saying something with every <script>
stripped, versioned assets immutable and unversioned ones not. It asserts the budget and never the
markup, so it survives #7's rewrite and #10's canvas. Vesper.
- #22 The check typechecks — ready.
forge.jsonrunsbun testas the check, and Bun strips types
without checking them: a wrong type deploys green today, on ~1,900 lines with ~1,600 more in flight.
A strict tsconfig.json, the typecheck in the check, and a deliberate type error proving it fails.
Cheap now, expensive after three branches land. Vesper.
- #23 Socket: the connection, its rules and its limits — waits on #16 and #17 only. Split out of #9:
the upgrade, the Origin rule, message validation, the per-socket ration, the idle close and the
backpressure policy need nothing from the round, so they can be built against a stub publisher while
#8 is still being written. #9 now waits on #23 and keeps only the frames; a comment on #9 says which
of its bullets moved. This takes one step out of the critical path. Vesper, Sentinel reviewing.
- #24 Own-wake without a fitted curve — waits on #5. #20 implements the one rule the game stands on
— a body never feels its own wake — as a closed-form fit, and guards it with a test that fails when
FIELD_SPREAD or FIELD_HALF_LIFE move. Its author flags this himself, and he is right to: #11 tunes
exactly those constants. Make the subtraction exact, then prove invariant 1 holds across ±50% of both
constants instead of at one point. Vesper.
Ready now: #17, #21, #22 — no shared files, none of them blocked by the two pulls in review. The best next move for a person is reviewing #19 and #20.
A planning pass: the working tree is untouched and nothing was pushed, so there is no pull to open. There was again no written feedback to read — the only comments on #1 are my own, and pulls #19 and #20 carry no review — so I took the signal from the map: #7 and #16 were claimed, #5 and #6 moved to review, and unclaimed ready fell to one.
The real constraint is the queue, not the plan. #19 (the data half) and #20 (the world) are both open, both tight, both unreviewed, and the critical path behind them is serial: 5 → 8 → 9 → 10 → 11. Merging those two frees more work than any issue I can write. So this pass made the path less serial and filled ready with work that needs nothing from it.
Opened, in order:
- #21 A budget the page has to keep: first paint without JavaScript,
/warm under 50 ms, assets cached by content — ready. The forge's bar as a test in its own file, asserting the budget and never the markup so it survives #7's rewrite and #10's canvas. Suggested: Vesper. - #22 The check typechecks:
bun testruns TypeScript, it does not verify it — ready.forge.jsonnamesbun testas the check and Bun strips types without checking them, so a wrong type deploys green today on ~1,900 lines with ~1,600 more in flight. Cheap now, expensive once three branches land. Suggested: Vesper. - #23 Socket: the connection, its rules and its limits, before any frame goes through it — waits on #16 and #17. Split out of #9: the upgrade, the
Originrule, message validation, the per-socket ration, the idle close and backpressure need nothing from the round, so they can be built against a stub publisher while #8 is still being written. This takes a step out of the critical path. Suggested: Vesper, with Sentinel reviewing. - #24 Own-wake without a fitted curve: a body cannot ride its own trail, exactly — waits on #5. #20 implements the one rule the game stands on as a closed-form fit and guards it with a test that fails whenever
FIELD_SPREADorFIELD_HALF_LIFEmove; its author flags this himself, and #11 tunes exactly those constants. Suggested: Vesper.
Linked: #23 waits on #16 and #17; #9 now waits on #23; #24 waits on #5. The issue API's PATCH accepts only a state change, so #9 could not be rewritten in place — a comment on it records which of its bullets moved to #23 and what remains.
Ready now: #17, #21, #22 — three issues with no shared files, none of them blocked by the two pulls in review. #1 stays open as the direction and #2 as the design record.
A fourth planning pass. No words again — the comments here are all mine and none of the four pulls carries a review — so this time I stopped writing issues from the map and went to the previews, which are the one thing in this project that can still tell me something I did not already know. The tree is untouched and nothing was pushed.
The state of it. main is still f60d5fd Start drift. Four pulls are open and unreviewed — #19
(data), #20 (world), #25 (protocol), #26 (page) — and between them they touch actions.ts, db.ts,
http.ts, session.ts, world.ts, protocol.ts, page.ts, routes.ts, mcp.ts, tty.ts,
server.test.ts, app.css and README.md: most of the tree. That is why I opened only two issues
and both wait on work in review. More parallel build issues right now would not make the app better,
they would make the merge worse.
A merge order, since the branches overlap. #20 and #25 are effectively standalone — land them
first, in either order, for nearly nothing. #19 next. #26 last and rebased: it deletes the note action
from actions.ts while #19 adds to the same file, so one of them pays, and it is cheaper for the page
to pay than for the data layer to.
What the previews said (all four boot; / answers in ~130 ms from here, 3.2 KB, floor headers on):
- #27 The line that tells you how to reach the terminal arrives as
[email protected]— waits on
#7. Email obfuscation at the CDN sees name@host in the footer, replaces the value of
FORGE_APP_SSH with the words [email protected], and injects a decoder script into the page.
It is on production and on every preview. A local test cannot see it. I left the same note on #26,
where it is a few lines instead of a whole run. Quill.
- #28 The link you send a friend — waits on #7. A game about other people spreads by one person
pasting a URL, and pasted into a chat drift is a bare domain: the icon is data:, and there is no
card. An icon, Open Graph tags built from publicOrigin and never from a request header, a static
card, and a robots.txt so a crawler never takes a seat. Quill.
Two things the previews confirmed rather than revealed. /events on the preview carries
content-type and cache-control and no floor at all — #17 is guarding a real hole, in production.
And the CDN rewrites cache-control on an unversioned static file to max-age=14400 even though the
app says no-cache: #21 should assert what the app sends and know it may not be what a browser
receives.
Ready now: #21, #22, and #2 as the design record. That is the honest number, and it is small because the queue is full, not because the map is. Reviewing and merging #20 and #25 costs almost nothing and frees #16, #23 and #24 immediately.
A planning pass: the working tree is untouched and nothing was pushed, so there is no pull to open. There was no written feedback again — every comment on #1 is mine and none of the four pulls carries a review — so this time I went to the previews instead of the map, and they had something new to say.
main is still f60d5fd Start drift. Four pulls are open and unreviewed (#19 data, #20 world, #25 protocol, #26 page), and between them they touch most of src/. So I opened only two issues, both waiting on work already in review: more parallel build work right now would not make the app better, it would make the merge worse. A merge order that costs least: #20 and #25 first (effectively standalone), then #19, then #26 rebased — it deletes the note action from actions.ts while #19 adds to the same file, and it is cheaper for the page to pay that than for the data layer to.
Opened, in order:
- #27 The line that tells you how to reach the terminal arrives as
[email protected]— waits on #7. Verified on the live previews and on production: email obfuscation at the CDN seesname@hostin the footer, replaces the value ofFORGE_APP_SSHwith the words[email protected], and injects a decoder script into the document. A person copying that line gets a command that cannot work, and no local test can see it. Suggested: Quill. - #28 The link you send a friend: a shared world is shared by pasting a URL — waits on #7. The icon is
data:,and there is no card, so a game whose whole mechanic is other people previews in a chat as a bare domain. Icon, Open Graph built frompublicOriginand never from a request header, a static card, and arobots.txtso a crawler never takes a seat. Suggested: Quill.
I also left the terminal finding on pull #26 directly, where it is a few lines rather than a whole run, and said I will close #27 if its author takes it there.
Two things the previews confirmed rather than revealed: /events in production carries no security floor at all, so #17 is guarding a real hole; and the CDN rewrites cache-control on an unversioned static file to max-age=14400 despite the app sending no-cache, which #21 should know before it asserts what a browser receives.
Ready now: #21, #22, with #2 as the design record. That number is small because the queue is full, not because the map is — reviewing and merging #20 and #25 costs almost nothing and frees #16, #23 and #24 immediately.
A fifth planning pass. Still no words anywhere, so again: the previews, then the map. Nothing pushed, tree untouched.
#26 merged, and the arena is on main. I checked it as a visitor rather than as a diff: the notes
scaffold is genuinely gone (/api/notes is 404, the word "note" appears zero times on the page), /
is 3.2 KB served in ~107 ms from here, the floor headers are on, and the empty states say what is
coming instead of apologising. #7 is done properly.
#27 is now a production defect, and it is ready. I offered to close it if the fix landed inside
#26; it did not, so it stands — and with #7 done it is unblocked. Production right now serves
ssh -p 2222 [email protected]. Decoding the obfuscator's own payload gives what the page was
supposed to say: [email protected]. That is the whole terminal door,
broken on the live app, and no local test can see it.
Two gaps nothing in the map covered. Both wait on work that is still ahead, but both would otherwise be found the hard way:
- #30 The loop that dies quietly — waits on #8. The failure a 20 Hz game actually has: one throwing
tick kills the interval, the process keeps serving, / answers 200, the page draws a roster and a
clock, and the sea is frozen. Everything looks healthy except the only thing that matters. A tick
that throws must not end the loop, the round must know whether it is alive, /health must say so
without naming a player, and the page must not draw a still frame as if it were calm water.
- #31 Reconnect — waits on #23 and #10. Every push to this repository redeploys the app and drops
every socket at once. The five-second promise has to hold for the second visit too, the one caused by the server going away mid-round. Backoff with jitter (a reconnect storm after a deploy is the app knocking itself over), rejoining as the same player, and no lurch on the first frame back.
The queue is still the constraint. #19 (data), #20 (world), #25 (protocol) and #29 (the floor) are open and unreviewed, and #8 — the keystone everything downstream waits on — cannot start until #19 and #20 land. The merge order from the last pass still holds: #20 and #25 are standalone and nearly free, then #19.
Ready now: #21, #22, #27, #28. Four, none of them blocked by anything in review, and #27 is a live production defect that should go first.
A planning pass: the working tree is untouched and nothing was pushed, so there is no pull to open. No written feedback again, so I read the live app first and the map second.
#26 merged and the arena is on main. Checked as a visitor rather than as a diff: the notes scaffold is genuinely gone (/api/notes is 404, the word "note" appears zero times), / is 3.2 KB in ~107 ms, the floor headers are on, and the empty states say what is coming instead of apologising. #7 is done properly.
#27 is now a production defect and is unblocked. I had offered to close it if the fix landed inside #26; it did not. Production currently serves ssh -p 2222 [email protected], and decoding the obfuscator's own payload gives what the page meant to say: [email protected]. That is the entire terminal door, broken on the live app, invisible to any local test.
Opened, in order — two failure modes nothing in the map covered:
- #30 The loop that dies quietly: a world that stops ticking must say so — waits on #8. One throwing tick kills the interval while the process keeps serving:
/answers 200, the page draws a roster and a clock, and the sea is frozen. A tick that throws must not end the loop, the round must know whether it is alive,/healthmust say so without naming a player, and the page must not draw a still frame as if it were calm water. Suggested: Vesper. - #31 Reconnect: a deploy lasts a second, and nobody should have to reload to get back in — waits on #23 and #10. Every push here redeploys the app and drops every socket at once, so the five-second promise has to survive the server going away mid-round: backoff with jitter (without it the app knocks itself over on its own restart), rejoining as the same player, and no lurch on the first frame back. Suggested: Vesper.
The queue is still the constraint: #19, #20, #25 and #29 are open and unreviewed, and #8 — the keystone everything downstream waits on — cannot start until #19 and #20 land. The merge order stands: #20 and #25 are standalone and nearly free, then #19.
Ready now: #21, #22, #27, #28 — four, none blocked by anything in review. #27 should go first; it is broken in production today.
A sixth planning pass. No words again; I checked main by running it rather than by reading the diff.
Nothing pushed, tree untouched.
What landed. #32 merged the concept as src/concept.ts, docs/wake.md and a test that holds them
to each other. I went in expecting to have to file a conflict — a design record turned into code is
usually two sources of truth for the same numbers — and it is the opposite: the module keeps the words
and the shape of a round, says in its own header that thrust, half-life, spread and the gate stay in
src/world.ts, and leaves them there. That is the right seam. main is healthy: 18 tests pass, and
the duplicate merge commit on #32 changed nothing.
Two gaps nothing covered.
- #34 The MCP door is advertised on the page and answers with an empty list — ready now. The footer
on production says POST /mcp answers here already, and tools/list returns []. An empty door is
worse than no door. The round's tools are #14 and wait on the socket and the ladder; this is the
nearer half — rule and concept, reading the constants #32 just landed, so the page, the terminal
and a model quote one sentence and cannot drift apart. Vesper, or Quill for the words.
- #33 The sea sleeps when nobody is in it, and wakes inside the five seconds — waits on #8. A 20 Hz
loop that never stops burns a core on an empty arena, and drift will spend most of its life empty. Nothing in the map said what the world does at roster zero. Asleep costs nothing, the first join wakes it in the same tick, and falling asleep is where #18's inherited field gets written down. The trap is the catch-up burst: waking resets the clock, it never replays the hour it missed.
Still the queue. #19 (data), #20 (world), #25 (protocol) and #29 (the floor) have all been open and
unreviewed across three of my passes now, and #8 — which #33, #30, #18, #12 and everything downstream
wait on — cannot start until #19 and #20 land. They will also need a rebase now that main has moved
twice under them. This is the third pass where the honest bottleneck is review, not planning.
Ready now: #21, #22, #27, #34. #27 is still the one to take first — production is serving
ssh -p 2222 [email protected] as its terminal instructions.
A planning pass: the working tree is untouched and nothing was pushed, so there is no pull to open. No written feedback again, so I checked main by running it rather than by reading diffs. (I left a scratch clone at /tmp/mainchk; the cleanup command wasn't permitted, so it can be removed by hand.)
What landed. #32 merged the concept as src/concept.ts, docs/wake.md and a test holding them to each other. I went in expecting to file a conflict — a design record turned into code usually means two sources of truth for the same numbers — and it is the opposite: the module keeps the words and the shape of a round, states in its own header that thrust, half-life, spread and the gate stay in src/world.ts, and leaves them there. main is healthy: 18 tests pass, and the duplicate merge commit on #32 changed nothing.
Opened, in order:
- #34 The MCP door is advertised on the page and answers with an empty list — ready now. The production footer says
POST /mcpanswers here already, andtools/listreturns[]. The round's tools are #14 and wait on the socket and the ladder; this is the nearer half —ruleandconcept, reading the constants #32 just landed, so the page, the terminal and a model quote one sentence and cannot drift apart. Suggested: Vesper, or Quill for the words. - #33 The sea sleeps when nobody is in it, and wakes inside the five seconds — waits on #8. A 20 Hz loop that never stops burns a core on an empty arena, and drift will spend most of its life empty; nothing in the map said what the world does at roster zero. Asleep costs nothing, the first join wakes it in the same tick, and falling asleep is where #18's inherited field gets written down. The trap named in the issue is the catch-up burst — waking resets the clock, it never replays the hour it missed. Suggested: Vesper.
Ready now: #21, #22, #27, #34. #27 should go first — production is currently serving ssh -p 2222 [email protected] as its terminal instructions.
One thing worth a person's attention more than anything I can plan: #19, #20, #25 and #29 have been open and unreviewed across three passes, they now need a rebase since main moved twice under them, and #8 — which #33, #30, #18, #12 and the whole downstream path wait on — cannot start until #19 and #20 land.
A seventh planning pass. Nothing pushed, tree untouched. This one found the actual reason the queue is stuck, and it is not that nobody is reviewing.
Every open pull is unmergeable, and they all conflict on the same file. The forge reports
mergeable: false on #19, #20 and #35, and README.md is in all three conflict lists. The cause is
the thirteen-row file table: every builder who adds a module inserts a row, git cannot merge two
insertions into the same place, so every branch that adds a file collides with every other branch that
adds a file. Nine issues in the map still add modules. I have spent three passes saying "the bottleneck
is review" — that was wrong, or at best half of it. It is a mechanical conflict that recurs forever.
- #36 Every open pull conflicts on README.md, and it is always the same table — ready now. Stop
hand-maintaining the table: generate it from the module headers with a test that fails when it is
stale, or delete it and let AGENTS.md and the headers carry it. Put the rule in AGENTS.md so the
next twelve builders do not reintroduce it. It cannot unstick the three branches already conflicted —
those need a rebase either way — but it makes the fourth pull onward free. Quill.
What landed, checked on production rather than read. #25 (protocol) and #29 (the floor) merged, and
the floor fix is real: /events now answers with content-security-policy, x-content-type-options
and referrer-policy, and the CSP names connect-src 'self' wss://… explicitly, which is what #23
needs. The split of the socket out of #9 paid off exactly as intended — #23 is being built now, while
#8 is still waiting on #19 and #20.
Still true from last pass: #27 is live on production, unchanged — the footer still serves
[email protected] where the terminal command should be.
Ready now: #21, #22, #27, #34, #36. Five, and #36 is the one that makes the other four able to land without fighting each other. #19 and #20 need a rebase from their authors before anything downstream of #8 can start.
A planning pass: the working tree is untouched and nothing was pushed, so there is no pull to open. This one found the actual reason the queue is stuck, and it is not that nobody is reviewing.
Every open pull is unmergeable, and they all conflict on the same file. The forge reports mergeable: false on #19, #20 and #35, with README.md in all three conflict lists. The cause is the thirteen-row file table: every builder who adds a module inserts a row, git cannot merge two insertions at the same place, so every branch that adds a file collides with every other branch that adds a file — and nine issues in the map still add modules. I spent three passes saying the bottleneck was review; that was at best half of it.
Opened:
- #36 Every open pull conflicts on README.md, and it is always the same table — ready now. Stop hand-maintaining the table: generate it from the module headers with a test that fails when it goes stale, or delete it and let
AGENTS.mdand the headers carry it, and put the rule inAGENTS.mdso the next twelve builders don't reintroduce it. It cannot unstick the three already-conflicted branches — those need a rebase either way — but it makes the fourth pull onward free. Suggested: Quill.
What landed, checked on production rather than read. #25 (protocol) and #29 (the floor) merged, and the floor fix is real: /events now answers with content-security-policy, x-content-type-options and referrer-policy, and the CSP names connect-src 'self' wss://… explicitly — which is exactly what #23 needs. Splitting the socket out of #9 paid off as intended: #23 is being built now while #8 still waits on #19 and #20.
Unchanged from last pass: #27 is still live on production, serving [email protected] where the terminal command belongs.
Ready now: #21, #22, #27, #34, #36 — five, and #36 is the one that lets the other four land without fighting each other. #19 and #20 need a rebase from their authors before anything downstream of #8 can start.
A planning pass: the working tree is untouched and nothing was pushed. There was no written feedback to read — the thirteen comments on this issue are all mine and the three open pulls carry none — so I read production instead, and the map covers the direction's nine clauses well enough that this pass is about what the map does not cover rather than what it repeats.
Opened:
- #38 Credit, decided before it is coded: what a round pays for, and to whom — ready now.
docs/wake.mdfiles scoring under "what this record does not decide" and #12 says the credit rule is "a rule you choose and write down in the pull". That is the one number where "the map is made of other people" becomes arithmetic, and it is the rule players will attack; deciding it under the pressure of a failing test is how it ends up paying for standing near the bloom. Invariant 6, the three gaming attacks answered in the record, no simulation code. #12 now waits on it. Vesper, Quill on the sentence. - #39 A budget the tick has to keep: one step at 24 motes, measured, with headroom — waits on #5. #21 measures the page; nothing measures the world. A step that cannot finish inside its 50 ms slice is the failure where every door still answers 200 and the sea is dead — #30 catches the tick that throws, not the one that is merely slow. Median
step()at the cap, the frame throughencodeFrameunder 4 KB, and whether a step allocates 72 000 arrays an hour. Vesper. - #40 One bus, as the direction says: what
/eventskeeps and what the socket takes — waits on #9. Once the socket lands the app has two live paths and nothing says which owns which fact, so #10, #12 and #13 will each answer it differently and the clock will arrive from two places. The socket takes frame-rate facts,/eventskeeps human-rate ones, no fact on both, and a refused upgrade still leaves a live page rather than an empty arena. Vesper on the bus, Quill on the page's half. - #41 The card is the only picture of drift, and it is 238 KB that disappears in a thumbnail — ready now. I fetched it and looked at it. The geometry is genuinely good — three wakes braiding, the bloom leaving through the gate, no name in it — but the arena square sits in the middle with a third of the width dark on each side, and chat clients crop toward a square or scale to 360 px, which is exactly where the rim and the gate go. 238 765 bytes for a picture with no photograph in it. Quill, Vesper if
png.tschanges.
What I checked on production rather than read. / answers warm in 88–123 ms through the CDN with the full floor and a CSP that names connect-src … wss://; /events carries the floor; a cross-origin POST /mcp is a 403; tools/list is still {"tools":[]} (#34); the head carries icons, canonical and the card tags; /ws is a 404 on production and a 426 on #37's preview, which is the socket arriving. #27 is still live: the footer still serves [email protected] where the terminal command belongs.
The queue. #19 and #20 are both mergeable: false and both conflict on README.md alone — one table row each. They cannot rebase (a force push is refused), so either a person resolves one row at the merge or their authors take main into the branch. Between them they hold up twelve and thirteen issues, which is every downstream thing in the map. I did not put #36 behind them: removing the table makes their resolution simpler, not harder, so it should go first.
Ready now: #21, #22, #27, #34, #36, #38, #41 — seven, on seven different files, none of them touching what is in flight. No issue is parked. #1 and #2 stay open.
A planning pass on the direction: nothing was pushed and the working tree is untouched, so there is no pull. There was no written feedback anywhere — all thirteen comments on #1 are mine and the three open pulls carry none — so I read production and the map instead, and filed the four things the map does not cover.
- #38 Credit, decided before it is coded: what a round pays for, and to whom — ready now.
docs/wake.mddefers scoring, and #12 defers it again to "a rule you choose and write down in the pull". That is the one place where "the map is made of other people" becomes arithmetic, and it is the rule players will attack; it belongs in the record with a sixth invariant, before any attribution maths. #12 now waits on it. Vesper, with Quill on the sentence a player reads. - #39 A budget the tick has to keep: one step at 24 motes, measured, with headroom — waits on #5. #21 measures the page; nothing measures the world, and a step that misses its 50 ms slice is the failure where every door answers 200 and the sea is dead. Vesper.
- #40 One bus, as the direction says: what
/eventskeeps and what the socket takes — waits on #9. Two live paths arrive with the socket and nothing says which owns which fact, so #10, #12 and #13 will each answer it differently. Vesper on the bus, Quill on the page's half. - #41 The card is the only picture of drift, and it is 238 KB that disappears in a thumbnail — ready now. I fetched
/card.pngand looked at it: the geometry is good, but the arena sits in the middle with a third of the width dark on each side, which is exactly what a square crop or a 360 px scale throws away. Quill, Vesper ifpng.tschanges.
Checked on production rather than read: / warm at 88–123 ms with the full floor and a CSP naming connect-src … wss://, /events carrying the floor, cross-origin POST /mcp refused with 403, tools/list still empty (#34), /ws a 404 on production and a 426 on #37's preview, and #27 still serving [email protected] in the footer.
One thing a person has to decide: #19 and #20 are both mergeable: false and both conflict on README.md alone, one table row each. They cannot rebase — a force push is refused — so either the merge resolves one row or their authors take main into their branches. Between them they hold up every downstream issue in the map. I deliberately did not put #36 behind them: removing the table makes their resolution simpler, so it should land first.
Ready now: #21, #22, #27, #34, #36, #38, #41 — seven, on seven different files, none touching what is in flight. No issue is parked; #1 and #2 stay open.
A planning pass. Nothing pushed, the working tree is untouched. The change since last pass is good news: #19, #20 and #37 all report mergeable: true against 4280fbc — their authors took main in, so the README conflicts that stalled three passes are gone. All three now wait only on a person. I noted that on #36 and dropped its urgency: the table is ordinary tidying now, not the thing unsticking the queue.
Opened:
- #42 The shared danger has no teeth: decide what the rim costs, in the record and in the world — waits on #5. Read from #20 rather than guessed: the world answers a fast rim arrival with velocity halved and reversed and thrust off for ~1.5 s, while drag keeps acting — so a scattered mote goes on riding the lane that scattered it.
docs/wake.mdmeanwhile promises the rim "throws you out of the round". Those are two different games, and #10 will draw whichever it finds while #11 teaches it in ten seconds. A sixth invariant, appended, plus the three attacks answered. #11 now waits on it. Vesper, Quill on the sentence. - #43 When the sea goes wrong, an admin starts the next round without a deploy — waits on #8.
who.tsseparates admin from user and nothing uses it; #30 makes a dead loop say so and nothing answers it. One action, every door, the abandoned round marked so the ladder can decline to count it. Vesper. - #44 What a phone pays to watch: the frame measured at 24 motes, with a ceiling that holds — waits on #9. #39 measures the tick, #21 the page, nothing measures the wire: 4 KB at 10 Hz is 2.4 MB a minute of someone's mobile data, times 24 viewers out of one process. Worst-case frame, a per-viewer ceiling, and the
dropped/skippedcounters #37 already keeps asserted rather than trusted. Vesper. - #45 The doors nobody means to open: a 404 a person can read, a 405 with
Allow, an answeredOPTIONS— ready now. Checked on production:GET /nopeis nine bytes of plain text,GET /mcpis a 405 with noAllow, andPOST /andOPTIONS /are both 404s. Quill.
Checked on production rather than read. / warm at 85–110 ms with the full floor; the preview CSP names its own wss:// origin correctly, so #10 will not be blocked by it; a cross-origin POST /mcp is 403 and tools/list is still empty (#34); versioned assets are immutable for a year. #27 is still live, and I confirmed the cause in the served bytes — the CDN email obfuscator rewrites the ssh command and injects a decoder script — so I added the <!--email_off--> opt-out to that issue as the fix to try first, since it keeps the command copy-pasteable.
One thing only a person can do. Three pulls that nobody has reviewed now merge cleanly, and between them #19 and #20 hold up every downstream issue in the map. Sentinel is on this crew and reviews pulls without merging them: a security review on #19, #20 and #37 is the highest-value thing available right now, and it needs no code from anyone.
Ready now: #21, #22, #27, #34, #36, #41, #45 — seven. No issue is parked. #1 stays open.
The working tree is clean and identical to main (4280fbc) — this was a planning run, so there is nothing to commit or push and no code changes to test. The result is on the forge: four new issues, two dependency links, and three comments.
Filed against the direction (#1):
- #42 The shared danger has no teeth: decide what the rim costs, in the record and in the world — waits on #5, and #11 now waits on #42.
src/world.tsin pull #20 answers a fast rim arrival by halving and reversing velocity and cutting thrust for ~1.5 s while drag keeps acting, so a scattered mote keeps riding the lane that scattered it;docs/wake.mdpromises the rim throws you out of the round. Suggested: Vesper, with Quill on the sentence a player reads. - #43 When the sea goes wrong, an admin starts the next round without a deploy — waits on #8. Suggested: Vesper.
- #44 What a phone pays to watch: the frame measured at 24 motes, with a ceiling that holds — waits on #9. Suggested: Vesper.
- #45 The doors nobody means to open: a 404 a person can read, a 405 with
Allow, an answeredOPTIONS— ready now, verified on production. Suggested: Quill.
Also commented: on #27 with the <!--email_off--> opt-out, having confirmed in the bytes production serves that the CDN's email obfuscator is what mangles the ssh line; on #36 that #19, #20 and #37 now all report mergeable: true, so the README table is ordinary tidying rather than the queue's blocker.
What a person should look at: the three pulls that merge cleanly and have no review. #19 and #20 stand in front of every downstream issue in the map, and Sentinel can review them without merging anything. Ready now: #21, #22, #27, #34, #36, #41, #45. No issue is parked; #1 stays open.
Budget reached at $2.56; this is where it stopped.
A planning pass. The working tree is untouched and nothing was pushed; the map is the result.
The feedback carried no words, so I read the repository as it now stands and production rather than diffs. #5 and #22 landed: the sea is real arithmetic in src/world.ts on main — the field, the own-wake subtraction, the rim, the seeded RNG, 499 lines with the constants in one block — and the check now typechecks before it tests. #6, #23 and #38 are all in review as #19, #37 and #47, and all three report mergeable: true.
Opened, in order:
- #48 The page knows your name and says nothing about who may cache it — ready now. Checked on production:
GET /carries the whole floor and nocache-controland novary, while the HTML holdswho.name, the sign-out link and (with #6) a guest session. Nothing in front of us caches HTML today; that is a default, not a rule. The directive joins the floor insrc/http.tsso a door cannot forget it, and the pull says which ofno-storeandno-cacheit chose and what that costs bfcache. Vesper. - #49 A seat in the sea: decide what one is, before
joininvents it — ready now, and #8 now waits on it. The roster caps at 24 and nothing says a seat belongs to a person; a guest is whoever holds a cookie, and a script chooses not to hold one — twenty-four seats in a second, filled with motes nobody steers. The usual per-address answer is not available here, andsrc/live.tssays why in its own comment: behind the forge every guest arrives from the forge's address. The record decides it, #8 and #9 enforce it, in the shape #38 and #42 already set. Vesper, Quill on the sentence a spectator reads. - #50 The first paint is a box that says the sea is flat; make it the sea — waits on #8. Once frames exist the fastest picture of the world is one the server already has, and this is the only thing in the map that gives a JavaScript-off visitor the sea itself — #10 promises them the clock, the roster and an empty square. A still, under 8 KB at 24 motes, no animation, the canvas keeps motion. Quill, Vesper on the frame source.
- #51 Add to home screen: a phone game that gives the browser's chrome back — ready now. A thumb game on a 360px screen pays 12–15% of it to an address bar. Thirty lines of manifest from the name, icons and colours that already exist; no new image in the repository. Quill, next to #41.
Ready now: #21, #24, #27, #34, #36, #39, #41, #45, #48, #49, #51. Eleven, on eleven different files. #49 is the one to take first: it is small, it is a decision rather than code, and #8 — which fourteen issues stand behind — now waits on it.
No issue is parked. #1 stays open.
Nothing was pushed and the working tree is identical to main (df130d5) — this was a planning run, so there is no diff to review. The result is on the forge: four issues, two dependency links, and a comment on the direction.
Filed against #1, in order:
- #48 The page knows your name and says nothing about who may cache it — ready now. Production's
GET /carries the whole security floor and nocache-controland novary, while the HTML holdswho.name, the sign-out link and (with #6) a guest session. The directive joins the floor insrc/http.tsso no door can forget it. Vesper. - #49 A seat in the sea: decide what one is, before
joininvents it — ready now, and #8 now waits on it. The roster caps at 24 and nothing says a seat belongs to a person; a guest is whoever holds a cookie, and a script simply doesn't hold one. The usual per-address answer isn't available here —src/live.tssays in its own comment that behind the forge every guest arrives from the forge's address. Decided in the record, enforced by #8 and #9, in the shape #38 and #42 set. Vesper, Quill on the sentence a spectator reads. - #50 The first paint is a box that says the sea is flat; make it the sea — waits on #8. A server-rendered still from the frame the round already publishes: the only thing in the map that gives a JavaScript-off visitor the sea rather than an empty square. Quill, Vesper on the frame source.
- #51 Add to home screen: a phone game that gives the browser's chrome back — ready now. A manifest built from the name, icons and colours that already exist; no new image in the repository. Quill, next to #41.
Ready now: #49, #21, #24, #27, #34, #36, #39, #41, #45, #48, #51 — eleven, on eleven different files, none touching what is in flight. #49 is the one to take first: it is a decision rather than code, and #8 — which fifteen issues stand behind — now waits on it. #19, #37 and #47 all merge cleanly and none has a review; that is still the only thing planning cannot move. No issue is parked, and #1 stays open.
A planning pass. The working tree is untouched and nothing was pushed; the map is the result.
The feedback carried no words again, and there is nothing written on either open pull (#37, #47), so I read the repository as it stands and ran it: GET / answers in 0.6–0.9 ms warm, 3.7 KB, the whole floor on the response, /events streaming with a heartbeat and the data-live regions listening for events that nothing has ever emitted. What the map says out loud is that the game is still five sequential issues away from being playable — #49 → #8 → #9 → #10 → #11 — with one of them, #8, doing four jobs at once.
Opened, in order:
- #53 The loop, alone: one 20 Hz tick that starts a round, ends it and writes it down — ready now, and #8 now waits on it. #8 is the loop, the roster, the input queue and the frame in one issue, and only the roster needs #49. #5 and #6 are both on
main, so the loop can be built today:src/round.tswithstart/stop/tick, the clock from the tick count, a round that ends on the clock or on the gate, the intermission, the write throughrecordRound, and the firstround.startedthe page's live regions have ever had to hear. Split in the shape #23 was split from #9. Vesper. - #54 What the sea looks like, decided once: three doors are about to draw the same world three ways — ready now, and #10, #50 and #13 now wait on it. The record says it does not decide the look of the sea; three issues are queued to decide it separately, and the field's legibility is the entire game. One section in
docs/wake.mdin #42's shape: the marks, your mote against everyone else's without relying on colour, stunned and beached, contrast in both themes, whatprefers-reduced-motionturns off, and a palette named instatic/app.cssthat the terminal maps to ANSI. Quill, Vesper on what a 10 Hz frame can honestly show. - #55 The sea as a picture, before there is a round to picture:
src/arena.ts, pure and tested — waits on #54, and #50 now waits on it. #16 decided the frame shape, so the SVG still is a pure function a test can hand a frame it wrote by hand: under 8 KB at 24 motes, no inline style, every name escaped, a flat sea that still draws the rim, the gate and the bloom. #50 keeps the route, the frame source and the caching question. Quill, Vesper on the field strokes. - #56 The rows nobody deletes: a sweep that never runs, and the scaffold's table that never left — ready now.
sweepGuestsis correct, tested, refuses any caller butSYSTEM, and is called by nothing: a guest is a cookie, the cookie lasts a year, and the file underFORGE_APP_DATAonly grows. In the same schema, migration 1 still createsnotefour merges after the page that used it left. A sweep that actually runs and is cleared on close, a migration that drops the dead table, and the test that a guest holding a score survives. Vesper, Sentinel on the pull for the delete.
Ready now: #53, #54, #56, #21, #24, #27, #34, #36, #39, #41, #45, #48, #51 — thirteen, on different files, none touching what is in flight. #53 is the one to take first: it is on the critical path, it needs nothing that is not already on main, and fifteen issues stand behind #8.
No issue is parked. #19, #37 and #47 still merge cleanly and none has a review, which remains the only thing planning cannot move. #1 stays open.
The map is the result; the working tree is identical to main (d677f6e) and nothing was pushed, so there is no diff to review.
Filed against #1, in order:
- #53 The loop, alone: one 20 Hz tick that starts a round, ends it and writes it down — ready now, and #8 now waits on it. #8 was four jobs in one issue and only one of them (the roster) needs #49; #5 and #6 are both on
main, so the loop, the clock from the tick count, the round that ends on the clock or on the gate, the intermission, the write throughrecordRoundand the firstround.startedthe page's live regions have ever had to hear can all be built today. Split in the shape #23 was split from #9. Vesper. - #54 What the sea looks like, decided once: three doors are about to draw the same world three ways — ready now, and #10, #50 and #13 now wait on it. The record says it does not decide the look of the sea, and three issues are queued to decide it separately; the field's legibility is the whole game. One section in
docs/wake.mdin #42's shape, plus a palette named instatic/app.cssthat the terminal maps to ANSI. Quill, Vesper on what a 10 Hz frame can honestly show. - #55 The sea as a picture, before there is a round to picture:
src/arena.ts, pure and tested — waits on #54; #50 now waits on it. #16 decided the frame shape, so the SVG still is a pure function a test can hand a frame it wrote by hand. Quill, Vesper on the field strokes. - #56 The rows nobody deletes: a sweep that never runs, and the scaffold's table that never left — ready now.
sweepGuestsis tested, refuses any caller butSYSTEM, and is called by nothing; migration 1 still createsnotefour merges after its page left. Vesper, Sentinel on the pull for the delete.
Ready now: #53, #54, #56, #21, #24, #27, #34, #36, #39, #41, #45, #48, #51 — thirteen, on different files, none touching what is in flight; #49 and #23 are already claimed. #53 is the one to take first: it is on the critical path, needs nothing that is not on main, and fifteen issues stand behind #8. No issue is parked; #37 and #47 still have no review, which remains the only thing planning cannot move. #1 stays open.
A planning pass. Nothing was pushed and the working tree is identical to main (ea28cb9); the map is the result.
Since the last pass #57 (the seat record) and #37 (the socket) merged, #54 is being worked, and #53 came back parked — "the run ended without a pull" on a provider's weekly limit, not on the issue's design. It is rewritten rather than left as it was. The feedback here carried no words again, so the rest of this pass is what reading the two merges turned up, and one of the things it turned up is a hole on main today.
- #58 The round, as state and one
tick: no timer, no database, no socket in it — ready now, and #53 now waits on it. Half of the parked issue, and the half with the rules in it: the clock from the tick count, the end on the clock or the gate, the intermission, the next gate from the seeded RNG, the idle and unsteered seat releases #57 decided, and effects returned as plain data so no test needs a database or a timer. Vesper. - #53, rewritten to the other half — one interval, the effects spent on
recordRound,emitandpublish,onSteerdrained in arrival order,serve's lifecycle,socket.sweepon the same interval — and a checklist that fits a short run. Vesper. - #59 A guest gets a new seat every time they open a socket:
seatOfstill mints a random key — ready now, and #8 now waits on it.src/socket.tshands an anonymous socketanon:<12 random bytes>and explains in its own comment that reading a guest's cookie issrc/session.ts, "which is not merged yet" — it merged first (b98061e, under ea28cb9). So onmain: one key holds one seat is false for every guest,seatHoldernever matches one, the second tab is a second swimmer, and a client with no cookie jar takes a seat per socket — the exact attacker #49 was opened about. The fix is the line that comment promised, plus the record's spectator answer for a request carrying no session. Vesper, Sentinel on the pull: this is authorization on the door that admits people to the world. - #60 The client's arithmetic, before the canvas: decode, interpolate and the steering vector, as functions — ready now, and #10 now waits on it. #16 fixed the frame and #23 fixed the steer, so the three pieces of the canvas issue that are arithmetic can be written and tested without a DOM or a round: angles the short way round, a clamped fraction so a late frame never throws a mote off the rim, one steering function for a drag and for the keys. Vesper.
Ready now: #58, #59, #60, #56, #21, #24, #27, #34, #36, #39, #41, #45, #48, #51 — fourteen. #59 first: it is a hole in production, it is one import and a spectator branch, and #8 believes what it returns. Then #58, which is the shortest road to the first round.started this app has ever emitted.
#54 is in hand and #38 still has no review. #1 stays open.
The map is the result; nothing was pushed and the working tree is identical to main (ea28cb9), so there is no diff to review.
Since the last pass #57 (the seat record) and #37 (the socket) merged, #54 is in hand, and #53 came back parked — the run ended on a provider's weekly limit, not on the issue's design. It is rewritten rather than left as it was, and reading the two merges turned up a hole that is on main today.
Filed against #1, in order:
- #58 The round, as state and one
tick: no timer, no database, no socket in it — ready now; #53 now waits on it. The half of the parked issue with the rules in it: the clock from the tick count, the end on the clock or the gate, the intermission, the next gate from the seeded RNG, the idle and unsteered seat releases #57 decided, and effects returned as plain data so no test needs a database or a timer. Vesper. - #53, rewritten to the other half — one interval, effects spent on
recordRound,emitandpublish,onSteerdrained in arrival order,serve's lifecycle,socket.sweepon the same interval — sized for a short run. Vesper. - #59 A guest gets a new seat every time they open a socket:
seatOfstill mints a random key — ready now; #8 now waits on it.src/socket.tsgives an anonymous socketanon:<12 random bytes>and says in its own comment that reading a guest's cookie issrc/session.ts, "which is not merged yet" — it merged first (b98061e, under ea28cb9). So one key holds one seat is false for every guest,seatHoldernever matches one, and a client with no cookie jar takes a seat per socket: the attacker #49 was opened about. Vesper, Sentinel on the pull. - #60 The client's arithmetic, before the canvas: decode, interpolate and the steering vector, as functions — ready now; #10 now waits on it. #16 fixed the frame and #23 the steer, so the canvas issue's arithmetic can be written and tested without a DOM or a round. Vesper.
Ready now: #58, #59, #60, #54, #56, #21, #24, #27, #34, #36, #39, #41, #45, #48, #51 — and nothing is parked. #59 is the one to take first: it is a hole in production, it is one import and a spectator branch, and #8 believes what it returns; then #58, the shortest road to the first round.started this app has ever emitted. #38 still has no review, which planning cannot move. #1 stays open.
A planning pass. Nothing was pushed; the working tree is main (ea28cb9) and the map is the result.
The feedback carried no words, and this time the repository said something louder: main fails its own check. bunx tsc --noEmit — the first half of the command forge.json names — errors at src/socket.test.ts(451,5), Bun.connect against a server.port Bun types as optional, merged in #37 over #46's work. bun test is green, the types are not, and every pull opened from here is red before a builder writes a line. That is why #38 came back parked: I fetched its closed pull's head (2ff1e9f) and ran the check — 117 tests passing, the same one error, and nothing in that pull touched TypeScript.
Opened, in order:
- #62
mainfails its own check, and it parked an issue that never touched the code — ready now, and the first thing anyone should take. The smallest pull in the repository: read the port once, prove it exists once, handBun.connecta number, do not touchtsconfig.json. Pull #61 (#54's look record) carries the same fix as a drive-by and I confirmed the check passes on its head — so if #61 merges first, close #62 with a word. It exists because #47 was mergeable too, right up until it was closed, and a red default branch should not depend on an unrelated pull surviving. Vesper. - #63 The sea in text, before the terminal wires it:
src/textworld.ts, pure and tested — waits on #54; #13 now waits on it. The direction asks for the terminal by name andsrc/tty.tsstill says the sea is not running. The renderer needs no socket and no round: a frame in, lines out, every line the width it was asked for so #13 can overwrite rather than clear, and a mote named\x1b[2Jcannot clear anybody's terminal. The same split #55 makes for the SVG still. Vesper, Quill on the side panel.
Rewritten rather than left as they were:
- #38, rewritten — cause named, scope cut to the record and the one sentence the app says out loud, explicitly no attribution maths and no README table, and it now waits on #62. The closed pull's work is still fetchable by oid (
git fetch origin 2ff1e9f8), so the next run starts from a review rather than a rewrite. - #9, re-scoped — #37 landed the upgrade, the
Originrule, validation, the ration, the idle close andbackedUp, and #53 takespublishandonSteer. What is left is delivery: who a frame is for —publishwrites to every open socket today — catching up a socket that opens mid-round, honouring #40's split, and the bytes-per-second number against #44's ceiling. Eight issues stand behind it; now they stand behind a short one.
Ready now: #62, #60, #56, #54, #21, #24, #27, #34, #36, #39, #41, #45, #48, #51 — and #58 and #59 are in hand. #62 first, because it is the reason two pulls have died; then #60, which needs nothing that is not on main.
Nothing is parked. #1 stays open.
The map is the result; the working tree is main (ea28cb9) and nothing was pushed, so there is no diff to review.
The loud finding this pass: main fails its own check. bunx tsc --noEmit errors at src/socket.test.ts(451,5) — Bun.connect against a server.port Bun types as optional, merged in #37 over #46's work — while bun test stays green. Every pull opened from main is red before a builder writes a line, and that is why #38 came back parked: I fetched its closed pull's head (2ff1e9f) and ran the check, and it was 117 tests passing with that one unrelated error.
Filed against #1:
- #62
mainfails its own check, and it parked an issue that never touched the code — ready now, and the first thing to take. Read the port once, prove it exists once, handBun.connecta number;tsconfig.jsonuntouched. Pull #61 carries the same fix as a drive-by and the check passes on its head, so if #61 merges first, close #62 with a word — it exists because #47 was mergeable too, right until it was closed. Vesper. - #63 The sea in text, before the terminal wires it:
src/textworld.ts, pure and tested — waits on #54; #13 now waits on it. The direction names this door andsrc/tty.tsstill says the sea is not running; the renderer needs no socket and no round, every line comes back the width it was asked for so #13 can overwrite rather than clear, and a mote named\x1b[2Jcannot clear anybody's terminal. Vesper, Quill on the side panel.
Rewritten rather than left as they were:
- #38 — cause named, scope cut to the record plus the one sentence the app says out loud, no attribution maths and no README table, now waiting on #62, with the closed pull's work still fetchable by oid so the next run is a review rather than a rewrite.
- #9 — re-scoped against what #37 landed and what #53 now takes: what is left is delivery, chiefly that
publishwrites to every open socket today and a frame needs to know who it is for. Eight issues stand behind it; now they stand behind a short one.
Ready now: #62, #60, #56, #54, #21, #24, #27, #34, #36, #39, #41, #45, #48, #51, with #58 and #59 in hand. Nothing is parked, and #1 stays open.
A planning pass. Nothing was pushed; the working tree is main (c047586) and the map is the result.
#54 merged, and it brought the typecheck fix with it: I ran the forge's own command on main — bunx tsc --noEmit exits 0, bun test is 114 pass — so #62 is closed with a word, as its own last paragraph said to. #58 and #59 are open as pulls #64 and #65. The look record is a good one; reading it is where three of these came from.
Opened, in order:
- #66 The frame the round publishes:
src/frame.ts, a world in and 4 KB out — ready now, and #8 now waits on it. The one thing every door downstream is waiting for, and it needs no socket, no database and no loop: the world and the clock in, aFrameout, the 32×32 sea averaged down to the wire's 16×16 so a lane cannot flicker across a cell boundary, every scale read fromsrc/protocol.tsrather than copied, and the real byte count in the pull. #8 is then admission and the roster rather than admission, the roster and the wire. Vesper. - #67 "Beached" is a rule three doors are about to guess at separately: settle it on the wire — ready now, and #55 and #63 now wait on it.
src/concept.tsnames three readings a mote has and says of one, in its own comment, that it is not on the wire and a door reads it from where the mote is. Three doors are queued to do that, each with its own "close enough to the rim" — and it is not derivable from position anyway:rim()keeps what runs along the wall, so a mote overtaking you at the rim looks exactly like one beached against it. One bit besideMOTE_STUNNED, set where the rule already lives. Vesper. - #68 The sea remembers: a field stored as one small blob and handed to the next round, decayed hard — ready now, and #18 now waits on it. The half of the empty-sea answer that needs no round running: a field to bytes and back, quantised rather than stored as doubles, decayed by one named constant, a bad blob giving a flat sea rather than a crash, and the test the record already promises — the strongest inherited cell weaker than a lane two live motes build in five seconds, with invariant 5 still holding from a seeded start. Vesper.
Ready now: #66, #67, #68, #60, #56, #21, #24, #27, #34, #36, #39, #41, #45, #48, #51. #66 first: fifteen issues stand behind #8 and this is the half of it that can be built today. #67 is twenty minutes and it is in front of three drawing issues, so it is the cheapest thing on the board.
Nothing is parked. #1 stays open.
The map is the result; the working tree is main (c047586) and nothing was pushed, so there is no diff to review.
#54 merged and carried the typecheck fix with it — I ran the forge's own command on main: bunx tsc --noEmit exits 0, bun test is 114 pass — so #62 is closed with a word, as its own last paragraph said to. #58 and #59 are open as pulls #64 and #65. Reading the merged look record is where three of these came from.
Filed against #1, in order:
- #66 The frame the round publishes:
src/frame.ts, a world in and 4 KB out — ready now; #8 now waits on it. What every door downstream is waiting for, needing no socket, database or loop: the 32×32 sea averaged down to the wire's 16×16 so a lane cannot flicker across a cell boundary, every scale read fromsrc/protocol.tsrather than copied, the real byte count in the pull. Vesper. - #67 "Beached" is a rule three doors are about to guess at separately: settle it on the wire — ready now; #55 and #63 now wait on it.
src/concept.tssays in its own comment that beached is not on the wire and a door reads it from where the mote is — and it isn't derivable from position:rim()keeps what runs along the wall, so a mote overtaking you at the rim looks exactly like one beached against it. One bit besideMOTE_STUNNED, set where the rule already lives. Vesper. - #68 The sea remembers: a field stored as one small blob and handed to the next round, decayed hard — ready now; #18 now waits on it. The half of the empty-sea answer that needs no round running, with the test the record already promises: the strongest inherited cell weaker than a lane two live motes build in five seconds, invariant 5 still holding from a seeded start. Vesper.
Ready now: #66, #67, #68, #60, #56, #38, #21, #24, #27, #34, #36, #39, #41, #45, #48, #51, with #58 and #59 in review. #66 first — fifteen issues stand behind #8 and this is the half of it buildable today; #67 is the cheapest thing on the board and sits in front of three drawing issues. Nothing is parked, and #1 stays open.
A planning pass. Nothing was pushed; the working tree is main (c047586) and the map is the result.
Nothing has merged since the last pass — #64 (#58) and #65 (#59) are both open, both mergeable, both green by their own report — so I read them instead, and the two pulls' own "what I chose not to do" paragraphs are where this pass came from. Nothing is parked.
Opened, in order:
- #69 No door issues a guest session, so every guest is a spectator and nobody can play — ready now, and #11 now waits on it.
session()insrc/session.tsmints the guest key and hands back theset-cookiea response owes; its own comment names the caller — the door that wants a guest to exist callssessionand sets the cookie it returns — and no route calls it.GET /carries noset-cookieat all. Harmless until #59 lands, and #59's pull says what happens then in its own words: every browser on the preview is a spectator and is told so. There is a real detail inside it: the notice cookie and the guest cookie are bothset-cookieandhtml()takes a plain record, so the two collide unless the header is built to carry both. This is the smallest change between the app as it is and a person swimming. Quill, with Vesper on what the socket then finds. - #70 Two tabs should be two windows on one mote; today the second one evicts the first — waits on #59. The merged seat record promises two windows on one mote, steering from either; #59's branch implements newest-wins and closes the older socket with 4000. One person still holds one mote, which is the half that matters, but once anything reconnects on its own — #31, or any canvas — two open tabs evict each other in a loop, forever, over a mote that is drifting while they argue. The fix carries a security half: the ration has to move per key, or opening sockets divides it. Vesper, Sentinel on the ration.
Rewritten rather than left as it was:
- #8 gains the two rules its neighbours' pulls exposed. #64 releases every seat at round end and nothing re-takes one, while #11 promises nobody does anything to be in the next round — so the rollover is now in #8's acceptance: every connected socket holding a key is seated at round start, in arrival order, spectators promoted when the sea has room, proved by a test that runs two rounds back to back. And the seat taken during the intermission, which #64 flagged in its pull, is decided there rather than in a pull description.
Ready now: #69, #66, #67, #68, #60, #56, #38, #21, #24, #27, #34, #36, #39, #41, #45, #48, #51. #69 first — it is small, it is the difference between spectating and swimming, and it lands cleanly next to both pulls in flight. The one thing planning cannot move is still the merge queue: two pulls, both mergeable, neither reviewed.
#1 stays open.
The map is the result; the working tree is main (c047586) and nothing was pushed, so there is no diff to review.
Nothing merged since the last pass — #64 (#58) and #65 (#59) are both open, mergeable and green by their own report — so this pass came from reading them, and from the two lines in src/session.ts that no door calls.
Filed against #1:
- #69 No door issues a guest session, so every guest is a spectator and nobody can play — ready now; #11 now waits on it.
session()mints the guest key and returns theset-cookiea response owes, its own comment names the caller, and no route calls it:GET /carries noset-cookieat all. Harmless until #59 lands — and #59's pull says what happens then in its own words, that every browser on the preview is a spectator and is told so. It carries one real detail: the notice cookie and the guest cookie are bothset-cookiewhilehtml()takes a plain record, so they collide unless the header is built to hold both. Quill, with Vesper on what the socket then finds. - #70 Two tabs should be two windows on one mote; today the second one evicts the first — waits on #59. The merged seat record promises two windows on one mote; that branch implements newest-wins and closes the older socket with 4000, so once anything reconnects on its own two open tabs evict each other in a loop over a mote that drifts while they argue. The fix has a security half: the ration must move per key, or opening sockets divides it. Vesper, Sentinel on the ration.
Rewritten rather than left as it was:
- #8 now owns the rollover its neighbours exposed: #64 releases every seat at round end and nothing re-takes one, while #11 promises nobody does anything to be in the next round — so every connected socket holding a key is seated at round start, in arrival order, spectators promoted when there is room, proved by a test that runs two rounds back to back. The intermission seat that #64 flagged in its pull is decided there too.
Ready now: #69, #66, #67, #68, #60, #56, #38, #21, #24, #27, #34, #36, #39, #41, #45, #48, #51. #69 first: small, and it is the difference between spectating and swimming. Nothing is parked; the one thing planning cannot move is the merge queue — two pulls, both mergeable, neither reviewed. #1 stays open.
A planning pass. Nothing was pushed; the working tree is main (c047586) and the map is the result.
Still nothing merged: four pulls are open — #64 (#58), #65 (#59), #71 (#66), #72 (#38) — all mergeable, all green by their own report, none reviewed. That is the whole of the bottleneck and planning cannot move it. So this pass I used the running deployments instead: the forge's app record names them, and production plus all four previews are up.
What the previews say: a preview's CSP is correct per environment (wss://drift--65--… on pull 65's), GET / answers production in ~120 ms over the network with the floor and HSTS — and GET / still sends no set-cookie on the branch that makes a session-less browser a spectator, which is #69 filed last pass, now visible in a preview rather than argued from code.
Opened, in order:
- #73 The sea has to remember whose current it is: a per-player share of every cell, budgeted — waits on #38; #12 now waits on it. #72's record pays a round by whose current carried the bloom and rests on the sea being linear in each player's shares. The sea cannot answer that:
Fieldis two flat arrays with nobody's name on any of it. This is the ledger — a layer per seat, spread and decayed exactly as the total is, the sum of layers equal to the field cell by cell after a thousand ticks — and, because it is the tick's hot loop, its measured cost against #39's budget. Choosing a coarser ledger is allowed; choosing it silently is not. Vesper. - #74 The floor as the world actually receives it — ready now.
src/socket.tssays the 101 "is a response like any other and carries the same floor", and locally it does: I measured the handshake against a preview and it arrives with none of the four headers, because the edge rewrites it. Also written down so nobody loses an afternoon twice: a plaincurlupgrade gets 426 rather than 101 because HTTP/2 has noUpgrade. One table of what is sent, what arrives and what the edge adds, and the claims made to match. Sentinel reading, Quill placing the sentence.
Rewritten rather than left as they were:
- #66 and #58 — two open pulls both turn a world into a frame: #64 chose the wire's units inside
src/round.ts, #71 makessrc/frame.tsthe one place it happens. They agree on every answer, so the note on both says what the map's answer is —src/frame.tsowns the translation, the round calls it — and that whichever merges second deletes its own copy rather than leaving two places a scale can drift. - #12 — its first line asked for attribution "computed in
src/world.ts", which the sea cannot do yet. That ledger is #73; #12 is now reading it, paying the round, proving the credit invariant over a real round, and the board.
Ready now: #74, #69, #66, #67, #68, #60, #56, #38, #21, #24, #27, #34, #36, #39, #41, #45, #48, #51. #69 is still the one that changes what a visitor can do. Nothing is parked; #1 stays open.
The map is the result; the working tree is main (c047586) and nothing was pushed, so there is no diff to review.
Nothing merged since the last pass: four pulls are open — #64 (#58), #65 (#59), #71 (#66), #72 (#38) — all mergeable, all green by their own report, none reviewed. So this pass I used the running deployments instead; the forge's app record names them, and production and all four previews are up. A preview's CSP is correct per environment, production's / answers in ~120 ms with the floor and HSTS, and GET / still sends no set-cookie on the very branch that turns a session-less browser into a spectator — #69, now visible in a preview rather than argued from code.
Filed against #1:
- #73 The sea has to remember whose current it is: a per-player share of every cell, budgeted — waits on #38; #12 now waits on it. The credit record pays by whose current carried the bloom and rests on the sea being linear in each player's shares, and the sea cannot answer that:
Fieldis two flat arrays with nobody's name on any of it. A layer per seat, spread and decayed exactly as the total is, the sum of layers equal to the field cell by cell after a thousand ticks — and, because this is the tick's hot loop, its measured cost against #39's budget. Vesper. - #74 The floor as the world actually receives it — ready now.
src/socket.tsclaims the 101 carries the same floor as every other response; locally it does, and through the edge it arrives with none of the four headers. One table of what is sent, what arrives, what the edge adds, and the claims made to match — plus the note that a plaincurlupgrade gets 426 rather than 101 because HTTP/2 has noUpgrade. Sentinel reading, Quill placing the sentence.
Rewritten rather than left as they were: #66 and #58, because both open pulls turn a world into a frame and they agree on every answer — src/frame.ts owns the translation, the round calls it, and whichever merges second deletes its own copy; and #12, whose first line asked the sea for attribution it cannot yet give, now reading #73's ledger, paying the round and proving the credit invariant.
Ready now: #74, #69, #66, #67, #68, #60, #56, #38, #21, #24, #27, #34, #36, #39, #41, #45, #48, #51 — nothing parked, and #69 is still the one that changes what a visitor can do. #1 stays open.
One shared world, one server tick, and everyone who opens the page is in it within five seconds. Invent the game inside these bounds: momentum-based movement on a phone-friendly canvas, thirty seconds to learn, a round under four minutes, and a mechanic that only exists because other people are there (a shared resource, a shared danger, a shared goal). Identity comes from the forge (x-forge-identity, verified with FORGE_APP_IDENTITY_KEY; the template's src/who.ts is the check): it names players and keeps a ladder; guests play anonymously. The planner opens with three concepts as issues, each a paragraph naming the one mechanic that makes it novel, picks the one that best meets only-exists-because-others-are-there, closes the other two with a word, and maps the winner. Live over server-sent events or a websocket on one bus; the terminal shows the world as text and lets you watch a round. No known game re-skinned.
This issue carries the repository's direction. The planner is given it when the map has run dry; it opens and links the issues that follow from it, and this one stays open.