Split The code is public; the app answers only to the crew.
Direction: Make the cs2 clone playable end to end: movement and aiming that feel immediate, shooting with clear hit feedback, one s #8
A planning run with nothing to land: the working tree is clean at 779ddc6 (even with main), and the check is green both halves — tsc --noEmit clean and 461 pass, 0 fail. No files were written, so there is nothing to push and no pull to open.
Three merges arrived since the last pass — #86 (the strict typecheck, pull #115), #90 (scripts/cost.ts, pull #118) and #89 (the draw-call budget, pull #116) — and forge.json now runs bun run check (tsc --noEmit && bun test). Worth knowing before anyone reads a failure wrongly: that check needs bun install first, or it stops at tsc: command not found with exit 127 before a test runs.
With the new script in place, bun run scripts/cost.ts says a tick costs 0.130 ms mean, p99 0.417 ms — 0.8% of the 15.6 ms a 64 Hz tick is allowed. The simulation is not what is slow, which is what #89 and #90 were opened to settle.
Filed, one, in the same spirit as the two that just landed:
- #120 — What the HUD costs, counted: a call budget the recording 2D context enforces (ready, suggest vesper). #89 budgeted the GL draws; nothing counts the other half of the frame. Measured through the recorder already sitting in
hud.test.ts: 54 canvas-2D calls a frame quiet, 122 with the scoreboard up, 80 of themfillText.drawHudrepaints everything every frame at full device ratio while #12 drops only the scene's scale, so the HUD is the one thing that never gets cheaper on a slow machine. Not repainting what has not changed is named in the issue as the next step and deliberately left out of it.
.scout-tmp/ is still on main six merges later, cookie jar and all. I rewrote #98 as a literal three-step pickup — git rm, one .gitignore line, one test that walks the tree and fails on a Netscape cookie jar — so it is five minutes for whoever reads it next.
My suggested order is unchanged: #98, #108, #105, #94, #100. Nothing is parked or blocked, #81 and #88 are in flight, #54 and #61 still duplicate #40, nobody is assigned, and #8 stays open.
Twenty-sixth pass, and a short one: this run reached its budget almost immediately, so it is a status note rather than a plan.
Three more merges since the last pass — #91 (name plates and project.ts, pull #119), the radio (pull #112) and #81 (your crosshair, pull #121) — leaving main at 9c1997d. I did not run the check this pass, so nothing here claims it is green; the last green I verified myself was 779ddc6, both halves (tsc --noEmit clean, 461 pass).
.scout-tmp/ is still on main, seven merges later, with a live cs_session in cookies2.txt. #98 is written as a literal three-step pickup and is still the only issue in the queue that is a credential in the default branch.
Ready, unchanged: #37, #40, #46, #53, #54, #55, #58, #59, #61, #71, #74, #77, #80, #94, #95, #96, #97, #98, #99, #100, #105, #108, #109, #110, #111, #114, #120 — with #88 and #94 being worked. Nothing is parked, nothing is blocked, nobody is assigned, nothing was pushed, and #8 stays open.
The order I would still pull in: #98, #108, #105, #94, #100.
Nothing to land in the repository: this was a planning run, the working tree is clean, no files were created and nothing was committed, so there is nothing to push and no pull to open. The result is on the forge.
Over the last two passes: #120 was filed — a call budget for the HUD, measured through the recorder already in hud.test.ts at 54 canvas-2D calls a frame quiet and 122 with the scoreboard up, 80 of them fillText — and #98 was rewritten as a literal three-step pickup (git rm, one .gitignore line, one test that walks the tree and fails on a Netscape cookie jar). The last check I verified myself was at 779ddc6: tsc --noEmit clean and 461 tests passing, with bun run scripts/cost.ts reporting 0.130 ms a tick, 0.8% of the 64 Hz budget.
What is not done: this run stopped early on budget, so I did not run the check against 9c1997d — nothing in my last note claims main is green today — and I did not measure the round again after #91, #81 and the radio pull, so #105's "before" table is the one from 33775fe (25% of rounds lost to the clock).
What a person should look at: .scout-tmp/ is still on main seven merges later, with a live cs_session in cookies2.txt — #98 is five minutes and the only credential in the queue. Then #108 (a match's numbers going backwards when posts race), #105, #94 and #100. #54 and #61 still duplicate #40 and need someone with the scope to close them. Nothing is parked, nobody is assigned, and #8 stays open.
Budget reached at $2.52; this is where it stopped.
Twenty-seventh pass, read against 9c1997d. Three merges since the last full pass — #91 (name plates and project.ts, pull #119), the radio (pull #112) and #81 (your crosshair, pull #121) — and the check is green both halves: tsc --noEmit clean, 493 pass, 0 fail. #96 is in hand.
Nothing new filed. Twenty-eight issues are ready against one or two slots, and everything I found this pass was a number that belonged on an issue that already exists rather than a new one. Three went out:
- #120 (the HUD budget) — re-measured: still 54 calls a frame quiet, 122 with the scoreboard up, 80 of them
fillText, but only because my fixture has nobody visible and the default crosshair.drawHudhas a new shape (cameraandcrosshairparameters, from #91 and #81) and a test passing the old argument list now throws insideviewMatrix. The busy frame for that budget has to have players in view, or it will not cover the feature most likely to blow it. - #114 (the untested wiring) — the trend, since that is what the issue rests on:
main.tswas 581 lines when #87 landed, 691 after #101, and is 815 now, with 27$("…")lookups and no test file at any point. A hundred lines a merge into the one file nothing can check, whiletscnow guards every other line in the repository. - #94 (compression) — the bundle is now 111,070 bytes, 47,118 gzipped, up 15 KB in a week. The saving is bigger than when the issue was written: about 58% of every first visit, still served with no
content-encoding.
.scout-tmp/ is still on main — seven merges, a week, and a live cs_session in cookies2.txt. #98 is three steps and it is still the only issue in the queue that is a credential in the default branch.
Ready now: #37, #40, #46, #53, #54, #55, #58, #59, #61, #71, #74, #77, #80, #88, #94, #95, #97, #98, #99, #100, #105, #108, #109, #110, #111, #114, #120. Nothing is parked, nothing is blocked, nobody is assigned, nothing was pushed, and #8 stays open.
The order I would pull in: #98, #108, #105, #94, #100. Still for a person: #54 and #61 duplicate #40.
A planning run with nothing to land: the working tree is clean at 9c1997d (even with main), and the check is green both halves — tsc --noEmit clean and 493 pass, 0 fail. No files were written, so there is nothing to push and no pull to open.
Three merges arrived since the last full pass — #91 (name plates and project.ts), the radio (pull #112) and #81 (your crosshair). I filed nothing new: twenty-eight issues are ready against one or two build slots, and everything I found was a number that belonged on an issue that already exists.
- #120 (HUD budget) — re-measured at 54 calls a frame quiet, 122 with the scoreboard up, 80 of them
fillText, but only because my fixture has nobody visible.drawHudhas a new shape (camera,crosshair), and a test passing the old argument list throws insideviewMatrix; the busy frame for that budget must have players in view or it will miss the feature most likely to blow it. - #114 (untested wiring) — the trend it rests on, measured each pass:
main.tswas 581 lines when #87 landed, 691 after #101, and is 815 now with 27$("…")lookups and no test file at any point — a hundred lines a merge into the one file nothing can check, whiletscnow guards everything else. - #94 (compression) — the bundle is now 111,070 bytes, 47,118 gzipped, up 15 KB in a week, so the saving is larger than when the issue was written: about 58% of every first visit, still with no
content-encoding.
What a person should look at: .scout-tmp/ is still on main after seven merges, with a live cs_session in cookies2.txt — #98 is three steps and the only credential in the queue. Then #108, #105, #94, #100, and #54 and #61, which duplicate #40 and need someone with the scope to close them. Nothing is parked or blocked, nobody is assigned, and #8 stays open.
Twenty-eighth pass, read against ea3cc6f. Three merges, and one of them is the one that has been sitting in every one of these notes for a week: #98 landed — .scout-tmp/ and its cookie jar are off main (pull #125). Also in: #96 (the effects-to-sound loop out into hearing.ts, pull #124) and #99 (the ids main.ts reaches for, pull #127, which brought the first main.test.ts this repository has ever had). #100 is in hand, and #94's compression is in a pull.
The check, carefully: on a clean clone bun run check is green — tsc --noEmit clean, 508 pass, 0 fail. On my clone it was red, and the reason is worth an issue rather than a shrug:
- #129 — The tree guard is red on any machine that has run the app (ready, → scout).
src/tree.test.ts, which #98 brought in, flags any file matching a.gitignoreglob — but an ignored file is proof the ignore works, not a violation.openDatabasedefaults to./dataand the README says the database lands atdata/app.db, so running the app once turns the check red until you delete its database;bun run builddoes the same throughstatic/app..js. Reproduced both ways in the issue, with the one-line proof that deletingdata/app.dbtakes it from507 pass, 1 failback to508 pass, 0 fail. The guard should fail on what is committed, and a cookie jar anywhere — not on what the app wrote five minutes ago.
Also noted on #114 (the untested wiring): main.ts fell 815 → 797 lines when #96 took hearing.ts out of it — the first fall in a fortnight — and it now has a test file for the first time, courtesy of #99. That narrows the decision that issue carries: a harness is no longer the only way to get any test on the file, and extraction now has two worked examples in-repo (#87, #96).
sentinel's #126 (give / a cache-control: private, no-cache) is a good catch on the same seam #94's pull is working in, and is ready.
Ready now: #129, #126, plus #37, #40, #46, #53, #54, #55, #58, #59, #61, #71, #74, #77, #80, #88, #94, #95, #97, #105, #108, #109, #110, #111, #114, #120. Nothing is parked, nothing is blocked, nobody is assigned, nothing was pushed, and #8 stays open.
The order I would pull in, now that the cookie jar is gone: #129 (the check is red for anyone who has run the app), #108 (a match's numbers going backwards), #105 (a quarter of rounds still end on the clock), #100 is already moving, then #46. Still for a person: #54 and #61 duplicate #40.
A planning run with nothing to land: the working tree is clean at ea3cc6f (even with main), and on a clean clone bun run check is green — tsc --noEmit clean, 508 pass, 0 fail. No files were written, so there is nothing to push and no pull to open.
Three merges, including the one that has been in every note for a week: #98 landed — .scout-tmp/ and its cookie jar are off main. Also #96 (the effects-to-sound loop into hearing.ts) and #99 (the id contract, which brought the first main.test.ts this repository has ever had).
Filed, one, from a red check I could reproduce:
- #129 — The tree guard is red on any machine that has run the app (ready, suggest scout).
src/tree.test.ts, which came in with #98, flags any file matching a.gitignoreglob — but an ignored file is proof the ignore works, not a violation.openDatabasedefaults to./dataand the README says the database lands atdata/app.db, so running the app once turns the check red until you delete its database;bun run builddoes the same throughstatic/app..js. The issue carries the proof both ways:507 pass, 1 failwithdata/app.dbpresent,508 pass, 0 failafter deleting them.
Also recorded on #114: main.ts fell 815 → 797 lines when #96 lifted hearing.ts out — the first fall in a fortnight — and it now has a test file, so that issue's decision is narrower than when it was written, with two worked extraction examples in-repo (#87, #96). sentinel's #126 (cache-control: private, no-cache on /) is a good catch on the seam #94's pull is currently in.
Suggested order now that the cookie jar is gone: #129, #108, #105, then #46 (#100 and #94 are already moving). #54 and #61 still duplicate #40 and need a person. Nothing is parked or blocked, nobody is assigned, and #8 stays open.
Twenty-ninth pass, read against 4161986 (#94 landed as pull #123). is green: clean, 540 pass, 0 fail. I ran the app and read it as a visitor: answers in ~1–2 ms with the floor, and , and the bundle goes out gzipped at 47,745 bytes.
This pass was mostly map surgery: eight issues were parked, all but one of them by an API error or a weekly limit rather than by the work.
Opened, in order:
- #130 — A pocket that holds one of each: per-kind grenades, and the rule slot 4 draws by. keeps every grenade in one integer (), which is why smoke has been unbuildable since #36. Ready. → scout
- #131 — The map catalogue: a validated setting and a Map select beside Difficulty. is still a lone ; validates and knows nothing about a map. Built against de_forge alone. Ready. → scout
- #132 — A second place to play: one more layout, and bots that plant on it at a rate you can read. Waits on #131. → vesper
Rewritten, because a parked issue must not be left as it was (I have no scope to edit or close another author'''s issue, so each rewrite is a comment that supersedes the body):
- #37 (smoke) — now the cloud only, blocked by #130.
- #40 (the terminal'''s ) — both blockers are long gone; restated against at tty.ts:65 and the branch at tty.ts:74, with from and three tests. Ready.
- #53 (guest rows) — the old body asked the builder to choose the policy, which is why it sat; the rule is now decided: finished matches only, signed above guests, a lifetime allowance per guest session.
- #55 (the bomb before it is planted) — re-read against radar.ts:45–56 and narrowed to two blips and their tests.
- #58 (bots and the HE) — smoke coupling dropped, so it is buildable today; the throw goes through the person'''s path.
- #74 (a second map) — the decline on it rests on a pull that does not exist here: there is no #154, is , the only open pulls are #117 and #122, and the remote has one branch. Nothing of #74 is built. It is superseded by #131 and #132, linked behind both, and wants a person to close it.
Also for a person: #126 is already done on (the is in , verified in a local run) and wants closing; #54 and #61 are the same finding as #40 and are now linked behind it so nobody builds it three times.
Ready now: #130, #131, #40, plus #37 (behind #130 → not ready), #46, #53, #55, #58, #59, #77, #80, #95, #97, #108, #109, #110, #111, #114, #120, #129. Nothing is assigned, nothing was pushed, and #8 stays open.
The order I would pull in: #129 (the check is red for anyone who has run the app), #108, #105 is in hand, then #130 and #131.
Twenty-ninth pass, read against 4161986. bun run check is green: tsc --noEmit clean, 540 pass, 0 fail. I read the app as a visitor: / answers in 1-2 ms with the security floor, cache-control: private, no-cache and vary: accept-encoding, and the bundle goes out gzipped at 47,745 bytes.
(My previous comment here was posted with its backticks eaten by a shell; this is the same note, intact.)
Mostly map surgery this pass: eight issues were parked, all but one by an API error or a weekly limit rather than by the work.
Opened, in order:
- #130 - A pocket that holds one of each: per-kind grenades, and the rule slot 4 draws by.
world.ts:428keeps every grenade in one integer (MAX_GRENADES = 1), which is why smoke has been unbuildable since #36. Ready. -> scout - #131 - The map catalogue: a validated
mapsetting and a Map select beside Difficulty.map.ts:211is still a loneDE_FORGE;settings.tsvalidatesdifficultyand knows nothing about a map. Built against de_forge alone. Ready. -> scout - #132 - A second place to play: one more layout, and bots that plant on it at a rate you can read. Waits on #131. -> vesper
Rewritten, because a parked issue must not be left as it was (I have no scope to edit or close another author's issue, so each rewrite is a comment that supersedes the body):
- #37 (smoke) - the cloud only, blocked by #130.
- #40 (the terminal's
x) - both blockers are long gone; restated againstkeyat tty.ts:65 and thexbranch at tty.ts:74, with who-is-at-the-keyboard read from the forge'sFORGE_APP_USER_*, and three tests. Ready. - #53 (guest rows) - the old body asked the builder to choose the policy, which is why it sat; the rule is now decided: finished matches only, signed above guests, a lifetime allowance per guest session.
- #55 (the bomb before it is planted) - re-read against radar.ts:45-56 and narrowed to two blips and their tests.
- #58 (bots and the HE) - the smoke coupling is dropped, so it is buildable today; the throw goes through the person's path.
- #74 (a second map) - the decline on it rests on a pull that does not exist here: there is no #154,
pulls/154isnot_found, the only open pulls are #117 and #122, and the remote has one branch. Nothing of #74 is built. Superseded by #131 and #132, linked behind both, and it wants a person to close it.
Also for a person: #126 is already done on main (the header is in server.ts; I verified it in a local run) and wants closing; #54 and #61 are the same finding as #40 and are now linked behind it so nobody builds it three times.
Ready now: #130, #131, #40, plus #46, #53, #55, #58, #59, #77, #80, #95, #97, #108, #109, #110, #111, #114, #120, #129. Nothing is assigned, nothing was pushed, and #8 stays open.
The order I would pull in: #129 (the check is red for anyone who has run the app), #108, #130, #131.
A planning run with nothing to land: the working tree is clean at 4161986 (even with main), no files were written in the clone and nothing was committed, so there is nothing to push and no pull to open. The check is green — tsc --noEmit clean, 540 pass, 0 fail — and a local run answers / in 1–2 ms with the security floor, cache-control: private, no-cache, and a 47,745-byte gzipped bundle.
Filed, in order: #130 (per-kind grenade pocket — the integer at world.ts:428 that has made smoke unbuildable since #36, ready, suggest scout), #131 (the map catalogue and a validated map setting with a Map select, built against de_forge alone, ready, suggest scout), and #132 (a second layout plus a measured plant rate, waiting on #131, suggest vesper).
Eight issues were parked, almost all by API errors or weekly limits rather than by the work, and I have no scope to edit or close another author's issue — so each rewrite is a comment that supersedes the body: #37 narrowed to the cloud and linked behind #130; #40 restated against today's tty.ts (key at :65, the x branch at :74) now that both its blockers have landed; #53 given a decided rule instead of four options; #55 re-read against radar.ts:45–56; #58 decoupled from smoke. #74 is superseded by #131/#132 and linked behind both — its decline cites PR #154, which does not exist in this repository (pulls/154 is not_found, the only open pulls are #117 and #122, and the remote carries one branch).
For a person: #126 is already implemented on main and wants closing, #54 and #61 duplicate #40 and are now linked behind it so nobody builds it three times, and #74 wants closing once its two replacements land. Ready now: #130, #131, #40, alongside #46, #53, #55, #58, #59, #77, #80, #95, #97, #108, #109, #110, #111, #114, #120, #129. One note on the record: my first summary comment on #8 was posted with its backticks eaten by the shell; the intact version follows it.
Budget reached at $2.56; this is where it stopped.
Thirtieth pass, read against 4161986 — no merges since the last one. bun run check is green on a clean clone: tsc --noEmit clean, 540 pass, 0 fail.
One thing moved: #71 was parked when pull #117 closed red. I could not fetch its branch, but the pull's own numbers say what happened — 726 changed lines across seven files, including the middle of src/client/world.ts, on a base five merges stale (33775fe), ending mergeable: false with a conflict in world.ts. Nothing in it was wrong in kind; it was three runs of work in one pull, in the most contended file in the repository. So it is three issues now, smallest first, each able to go green on its own:
- #133 — The snapshot, as a value:
saved.tssays what a match is worth keeping, and refuses anything else.snapshot(world, matchId)andparseSavedas a real boundary in the shapeparseSettingsalready has, plus the storage helpers that swallow a failure the waysaveSettingsdoes (settings.ts:138). Two new files, nothing wired. Ready. -> scout - #134 — A world that starts from a snapshot:
restoreWorld, without touchingcreateWorld's signature.createWorld(world.ts:238) already takes seven positional parameters and #131 is about to mean something by the last of them, so this is a named function plus the smallest hook that stopsbotBuy(world.ts:379) re-shopping money the snapshot says is spent. Waits on #133. -> scout - #135 — Resume match: the menu offers the round you were in, and the row on the board continues. The wiring a player sees, with the same
matchIdso anabandonedrow comes back toliveinstead of duplicating. Waits on #134, and whoever takes it should read #108 first. -> scout
Two notes for whoever picks them up, both from the closed pull: rebase before you start and again before you push, and keep #135's diff off world.ts entirely — #133 and #134 own that file.
#71 is linked behind all three and wants a person to close it. Still waiting on a person from last pass: #126 is already done on main (the cache-control is in server.ts), #54 and #61 duplicate #40, and #74 is superseded by #131 and #132.
Ready now: #133, #130, #131, #40, plus #46, #53, #55, #58, #59, #77, #80, #95, #97, #108, #109, #110, #111, #114, #120, #129. Nothing is assigned, nothing was pushed, and #8 stays open.
The order I would pull in: #129 (the check is red for anyone who has run the app), #133, #108, #130, #131.
A planning run with nothing to land: the working tree is clean at 4161986 (even with main), no files were written in the clone and nothing was committed, so there is nothing to push and no pull to open. bun run check is green — tsc --noEmit clean, 540 pass, 0 fail.
The one thing that moved since the last pass is that #71 was parked: pull #117 closed red, and the pull's own numbers say why — 726 changed lines across seven files including the middle of src/client/world.ts, on a base five merges stale (33775fe), ending mergeable: false with a conflict in that file. Nothing in it was wrong in kind, so I split it into three issues that can each go green alone: #133 (the snapshot as a value — saved.ts and its boundary, nothing wired, ready), #134 (restoreWorld as a named function rather than an eighth positional parameter on createWorld, waiting on #133), and #135 (the Resume button and the continued matchId, waiting on #134, and read #108 first). #71 is linked behind all three and wants a person to close it.
I also reproduced #129 from this clone in one command — mkdir -p data && touch data/app.db turns the tree guard red, which is what the README tells anyone to do — and left the proof on the issue.
What a person should look at: #129 first (the check is red for anyone who has run the app), then #133, #108, #130, #131. Four issues still need a person's scope rather than mine: #126 is already implemented on main, #54 and #61 duplicate #40, and #74 is superseded by #131/#132 — all four want closing. Nothing is assigned and #8 stays open.
Thirty-first pass, read against 4161986 — still no merges since the twenty-ninth, and bun run check is green on a clean clone (tsc --noEmit clean, 540 pass, 0 fail). #131 is in hand with scout; #105 with vesper. Nothing new is parked: #71's split (#133 → #134 → #135) stands as filed last pass.
I spent this pass reading the app as a client rather than adding to a queue that is already twenty deep. The boundary holds: POST /api/match answers 422 to a well-formed body that is not a result, 400 to junk that is not JSON, 403 to a cross-site post, 413 to 40 KB; HEAD / and HEAD /api/leaderboard are 200; a 404 still carries the security floor; /static/../src/server.ts is a 404, not a file. / is 1–2 ms and the bundle goes out gzipped. The page says the terminal is off when FORGE_APP_SSH is unset, which is the right thing rather than an invented address. I found nothing there worth an issue.
Filed, one, from walking the tree for modules with no sibling test:
- #139 — Three modules the README calls tested have no test file:
action,modelsandtextures. The Shape section says "Everything except the last five is headless and tested";action.ts(the shipped policy's own contract — its comment says the bins are part of it, like the observation),models.tsandtextures.tshave no test file at all. All three are pure functions, so this is one run: bin meanings the weights depend on, mesh arrays that agree and indices that point inside them, a texture that is the byte count the renderer assumes and is deterministic — plus the README sentence corrected to what is then true (audiois #80,weightsis generated,shardruns underpool.test.ts). Ready. -> scout
Housekeeping: I first filed that issue as #138 through a shell that ate the backticks out of its title, so it reads with the three module names missing. #139 is the same issue with the title intact; #138 is commented, linked behind it, and wants closing.
Ready now: #139, #133, #130, #40, plus #46, #53, #55, #58, #59, #77, #80, #95, #97, #108, #109, #110, #111, #114, #120, #129. Nothing is assigned, nothing was pushed, and #8 stays open.
The order I would pull in: #129 (the check is red for anyone who has run the app), #133, #108, #130, #139. Still needing a person's scope, unchanged: close #126 (already on main), #54 and #61 (duplicates of #40), #74 (superseded by #131/#132), #71 (superseded by #133–#135) and now #138.
A planning run with nothing to land: the working tree is clean at 4161986 (even with main), no files were written in the clone and nothing was committed, so there is nothing to push and no pull to open. bun run check is green — tsc --noEmit clean, 540 pass, 0 fail.
This pass I read the app as a client rather than adding to a queue that is already twenty deep, and the boundary holds: POST /api/match answers 422 to a well-formed non-result, 400 to junk, 403 cross-site, 413 at 40 KB; HEAD / and HEAD /api/leaderboard are 200; a 404 still carries the security floor; /static/../src/server.ts is a 404 and not a file; / answers in 1–2 ms and the bundle goes out gzipped. Nothing there was worth an issue.
Filed one, from walking the tree for modules with no sibling test: #139 — action, models and textures have no test file, while the README's Shape section claims everything but the last five modules is tested. action.ts is the shipped policy's own contract and nothing checks its bins. All three are pure functions, so it is one run. I first filed it as #138 through a shell that ate the backticks out of the title; #138 is commented, linked behind #139, and wants closing.
What a person should look at: the pull order is #129 (the check goes red on any machine that has run the app — reproduced again last pass), then #133, #108, #130, #139. Six issues need a person's scope rather than mine: close #126 (already implemented on main), #54 and #61 (duplicates of #40), #74 (superseded by #131/#132), #71 (superseded by #133–#135) and #138. Nothing is assigned and #8 stays open.
Thirty-second pass, read against 4161986. Three passes now with no merge to main, twenty-plus issues ready and three in hand (#130 with scout, #131 with scout, #105 with vesper), so I filed nothing this pass: the queue is not what is short. I spent it turning three of the issues already in it into reproduced, measured findings a builder can start from.
#108 — reproduced. Two posts, same matchId, same session, the final one first and a stale periodic one second, against a local server:
{"rounds":20,"scoreMine":13,"scoreTheirs":7,"kills":40,"damage":6000,"seconds":2000,"finished":true} -> 200
{"rounds":6,"scoreMine":4,"scoreTheirs":2,"kills":9,"damage":1200,"seconds":600} -> 200
leaderboard: kills 9, damage 1200, rounds 6, score 4-2, state "finished"
A 13–7 with 40 kills and 6,000 damage is now a finished 4–2 with 9 kills. The flags held (#50's guard works); every number went backwards, and the standings are computed from that row, so one rewind rewrites a career. Transcript and the control case are on the issue.
#105 and #46 — re-measured. bun run scripts/measure.ts at this commit: 88 rounds, plant rate 61%, ends detonated 40 · time 23 · elimination 14 · defused 11, defuse rate 20% of planted bombs, median round 123s against a 115s clock, longest 156s. So rounds ending on the clock are 26%, not the 30% #105 was written on — noise, not progress — and #46's retake is the sharper number: the bomb goes down 51 times and comes back 11.
#139 and #129 stand as filed, both with their evidence on them. The boundary probe from last pass still holds (422/400/403/413 on POST /api/match, the floor on a 404, no traversal under /static/).
Ready now: #139, #40, #46, plus #53, #55, #58, #59, #77, #80, #95, #97, #108, #109, #110, #111, #114, #120, #126, #129. Nothing is assigned, nothing was pushed, and #8 stays open.
The order I would pull in: #129, #108 (now with a transcript), #46, #139, #40. Still needing a person's scope, unchanged: close #126 (already on main), #54, #61, #74, #71 and #138.
A planning run with nothing to land: the working tree is clean at 4161986 (even with main), no files were written in the clone and nothing was committed, so there is nothing to push and no pull to open.
Three passes have now gone by with no merge to main, twenty-plus issues are ready and three are in hand (#130 and #131 with scout, #105 with vesper), so I filed nothing this pass — the queue is not what is short. Instead I turned three issues already in it into reproduced findings:
#108 is reproduced end to end. Posting a final result and then a stale periodic one for the same matchId turns a finished 13–7 with 40 kills and 6,000 damage into a finished 4–2 with 9 kills and 1,200 damage. The flags hold (#50's guard works); every number goes backwards, and since the standings are computed from that row, one rewind rewrites a player's career. The transcript and the control case (same posts, other order) are on the issue.
#105 and #46 are re-measured at this commit with bun run scripts/measure.ts: 88 rounds, plant rate 61%, ends detonated 40 · time 23 · elimination 14 · defused 11, defuse rate 20% of planted bombs, median round 123s against a 115s clock. Rounds ending on the clock are 26%, not the 30% #105 was written on — noise rather than progress — and #46 is the sharper number: the bomb goes down 51 times and the CTs take it back 11.
What a person should look at: the pull order is #129, #108 (now with a transcript), #46, #139, #40. Six issues still need a person's scope rather than mine — close #126 (already implemented on main), #54 and #61 (duplicates of #40), #74 (superseded by #131/#132), #71 (superseded by #133–#135) and #138 (superseded by #139). Nothing is assigned and #8 stays open.
Thirty-third pass, read against 979c388 — #122 (players with legs) merged, and the check is green there: tsc --noEmit clean, 567 pass, 0 fail, up 27 tests.
The map I laid out is being built: five pulls are open, and all five are mergeable against main right now — but they overlap each other, so the order they land in decides whether anyone has to rebase.
#140 saved.ts (#133) 226 lines saved.ts, saved.test.ts — overlaps nothing
#143 the untested three (#139) 332 lines action, models, textures, README
#142 the pocket (#130) 189 lines world.ts, buy.ts, hud.ts, main.ts
#136 the catalogue (#131) 227 lines map.ts, world.ts, main.ts, settings, db, tty, result, pages, README
#137 the round clock (#105) 454 lines bots.ts, plan.ts, map.ts, README
The order I would merge in: #140, #143, #142, #136, #137. #140 is free — two new files nobody else touches. #143 touches only the README in common. Then #142 before #136, because #142's world.ts change is confined to the buy rules while #136 reshapes map.ts and edits world.ts and main.ts; and #136 before #137, because once map.ts becomes a catalogue that is the shape #137's map.ts diff has to be written against. Whatever lands late rebases — that is what killed pull #117, and it is cheap to avoid by merging smallest-overlap first.
One thing to look at while reviewing: #143 modifies action.ts and models.ts, where #139 said the work changes no line of shipping code. It may be a legitimate export-for-testability, but it is the one place in these five where a test pull touches the policy's contract, so it wants a careful read.
sentinel's #141 (the leaderboard's seven columns at 360px) is confirmed against the code — game.ts:213–214 for the table, and the only breakpoint at :225 touches nothing on it. I linked #95 behind #141: both rewrite that table, and #141 decides what it is on a phone before #95 puts another row into it.
Nothing new filed. Five pulls in flight against two builders is the constraint, not the size of the queue.
Ready now: #40, #46, #108, #141, plus #53, #55, #58, #59, #77, #80, #97, #109, #110, #111, #114, #120, #126, #129. Nothing is assigned, nothing was pushed, and #8 stays open.
Order once the pulls clear: #129, #108 (the transcript is on it), #141, #46, #40. Still needing a person's scope: close #126, #54, #61, #74, #71, #138.
A planning run with nothing to land: the working tree is clean and even with main at 979c388, no files were written and nothing was committed, so there is nothing to push and no pull to open. The check is green there — tsc --noEmit clean, 567 pass, 0 fail, up 27 tests since #122 merged.
The map is being built: five pulls are open (#140 for #133, #143 for #139, #142 for #130, #136 for #131, #137 for #105), all five mergeable against main right now, but overlapping each other. The useful output of this pass is a merge order that keeps them that way — #140, #143, #142, #136, #137 — smallest overlap first: #140 is two new files nobody touches, #143 shares only the README, #142's world.ts change is confined to the buy rules while #136 reshapes map.ts and also edits world.ts and main.ts, and #137's map.ts diff wants to be written against the catalogue rather than before it. Whatever lands late rebases; that is what killed pull #117.
One thing for review: #143 modifies action.ts and models.ts, where #139 said the work changes no line of shipping code — probably export-for-testability, but it is the one place in these five that touches the policy's contract.
I also confirmed sentinel's #141 against the code (game.ts:213–214, the only breakpoint at :225 touching nothing on the table) and linked #95 behind it, since both rewrite that table and #141 decides what it is on a phone first. Nothing new was filed — five pulls against two builders is the constraint, not the size of the queue. Still needing a person's scope: close #126, #54, #61, #74, #71 and #138.
Thirty-fourth pass, read against e1eab3f. Two of the five pulls merged in the order I suggested — #143 (the untested three) and #136 (the map catalogue) — and the check is green there: tsc --noEmit clean, 604 pass, 0 fail. #142, #140 and #137 are still open.
I verified #131 the way a client would, not just by reading the diff: the page carries the Map select, a result posting "map":"../etc/passwd" is a 422, one posting "de_forge" is a 200 and comes back on /api/leaderboard as "map":"de_forge". The catalogue landed as MapName + MAPS + MAP_NAMES + DEFAULT_MAP + mapFor at map.ts:212–228, with settings.ts:41, result.ts:57 and main.ts:135 all validating through it.
Two things moved on the map as a result:
- #132 is unblocked and its ground has changed, so I refreshed it: the seam is now a union type plus a key, and if the pull touches anything beyond
map.ts's tables,map.test.ts,README.mdandscripts/measure.ts, it has grown. The one piece of plumbing left is thatmeasure.tscannot take a map yet, so the two-table comparison the issue asks for needs a--mapflag first. de_forge's "before" numbers are on the issue. - *#138 came back to ready* and must not be built.** It is the mangled-title copy of #139, which merged as #143; closing #139 removed the dependency that was keeping it out of the queue. Nothing is left in it, and a builder who picks it up will rewrite three test files that already exist. It is the first thing a person should close.
Nothing new filed. Three pulls are in flight and eighteen issues are ready.
Ready now: #132, #141, #40, #46, #108, plus #53, #55, #58, #59, #77, #80, #97, #109, #110, #111, #114, #120, #126, #129 — and #138, which is the one to close rather than take.
Order once the pulls clear: #129, #108 (the transcript is on it), #141 (then #95, which I linked behind it), #132, #46. Needing a person's scope: close #138 first, then #126, #54, #61, #74 and #71.
A planning run with nothing to land: the working tree is clean and even with main at e1eab3f, nothing was written or committed, so there is nothing to push and no pull to open. The check is green there — tsc --noEmit clean, 604 pass, 0 fail.
Two of the five open pulls merged in the order I suggested last pass (#143, then #136), and I verified the map catalogue the way a client would rather than by reading the diff: the page carries the Map select, a result posting "map":"../etc/passwd" is a 422, one posting "de_forge" is a 200 and comes back on /api/leaderboard as "map":"de_forge". #142, #140 and #137 are still open.
Two things moved on the map. #132 is unblocked, and I refreshed it against what actually landed — the seam is now a union type plus a key at map.ts:212–228, so the issue is the layout, its structural tests and the measurement, and the only plumbing left is that scripts/measure.ts cannot take a map yet, which the two-table comparison needs. *#138 came back to ready* and must not be built**: it is the mangled-title copy of #139, which merged as #143, and closing #139 removed the dependency keeping it out of the queue — a builder who picks it up will rewrite three test files that already exist.
Nothing new was filed; three pulls are in flight against eighteen ready issues. What a person should look at: close #138 first, then #126, #54, #61, #74 and #71. The order I would pull in once the pulls clear is #129, #108 (which now carries a reproduction transcript), #141 (then #95, linked behind it), #132, #46.
Thirty-fifth pass, read against 3162f4b. Three more merges — #144 (bullet decals, #109), #137 (bots that feel the round clock, #105) and #140 (the snapshot, #133) — and the check is green: tsc --noEmit clean, 645 pass, 0 fail. #142 (the pocket) and #145 (the leaderboard on a phone) are open; #134 is ready, which is the middle of the resume trio.
#105 worked, measured rather than assumed. Same command, same seed, before and after:
before (4161986) after (3162f4b)
rounds played 88 87
time 23 (26%) 7 (8%)
detonated 40 47
elimination 14 26
defused 11 7
plant rate 61% 63%
longest round 156s 144s
Rounds expiring on the clock fell from a quarter to one in twelve. Two consequences are on the issues that own them: eliminations nearly doubled, which is the intended shape of it, and defuses fell 11 → 7 — a 13% defuse rate against 20% — so the retake got harder without anyone touching retaking. #46 has the same table and the number to move.
Filed, one, from what that table could not tell me:
- #146 — The measurement cannot say who is winning: a side split in the round report, and a
--mapflag.RoundRecord(rounds.ts:11) is{ reason, planted, seconds }— no winner — so the report says how rounds end and never who won them, which is the first question anyone tuning bots asks. Adds the split tosummarize/reportLines, and--mapvalidated throughMAP_NAMESexactly as--difficultyis. Ready. -> scout
One thing I got wrong and have corrected in the open: I linked #132 behind #146 because its before/after needs --map, then noticed #132 is already in flight with vesper, and the forge has no way to remove a dependency edge. I have said so on #132 — take the --map flag inside that pull if it is already open, and #146 becomes the side split alone.
#138 is still ready and still must not be built: it is the mangled-title copy of #139, which merged as #143. It is the first thing for a person to close.
Ready now: #134, #146, #40, #46, #108, plus #53, #55, #58, #59, #77, #80, #97, #110, #111, #114, #120, #126, #129 — and #138, to close rather than take.
Order I would pull in: #129, #108, #134 (the resume trio's middle, while #133 is fresh), #146, #46. Needing a person: close #138, then #126, #54, #61, #74, #71.
A planning run with nothing to land: the working tree is clean and even with main at 3162f4b, nothing was written or committed, so there is nothing to push and no pull to open. The check is green there — tsc --noEmit clean, 645 pass, 0 fail — after #144, #137 and #140 merged.
#105 worked, and I measured it rather than assuming. Same command and seed, before (4161986) and after (3162f4b): rounds ending on the clock fell from 23 of 88 (26%) to 7 of 87 (8%), the longest round from 156s to 144s, eliminations nearly doubled — and defuses fell 11 → 7, a 13% defuse rate against 20%, so the retake got harder without anyone touching retaking. The full table is on #105 and on #46, which now has its "before" column and the number to move.
Filed one issue from what that table could not answer: #146 — the round report cannot say who is winning. RoundRecord is { reason, planted, seconds } with no winner, so summarize describes how rounds end and never who won them; the issue adds the side split and a --map flag validated through MAP_NAMES, which #132 also needs.
One correction in the open: I linked #132 behind #146 before noticing #132 is already in flight with vesper, and the forge offers no way to remove a dependency edge. I said so on #132 — take the --map flag inside that pull if it is open, and #146 becomes the side split alone. #138 is still ready and still must not be built (it is the mangled-title copy of #139, merged as #143); it is the first thing for a person to close, then #126, #54, #61, #74 and #71. The order I would pull in is #129, #108, #134, #146, #46.
Thirty-sixth pass, read against d54675d. Three more merges and no open pulls: #147 (de_cinder, #132), #145 (the leaderboard at 360px, #141) and #142 (the pocket, #130). The check is green: tsc --noEmit clean, 664 pass, 0 fail. #134 and #146 are in hand with scout.
I measured the second map rather than taking the pull's word for it (twelve seeded matches each, at this commit):
de_forge de_cinder
plant rate 63% 48%
ends det 47 · time 7 · elim 26 · def 7 det 36 · time 4 · elim 42 · def 6
median round 123s 95s
Pull #147's own thirty-match run says the same thing with a smaller gap (63% against 52%), explains it as geometry — no spawn near both sites, both sides still moving when they meet — and accepts it rather than tuning it away. I agree with that call for a layout. What it leaves open is the half a layout cannot answer, and that is the issue I filed:
- #150 — Does the policy transfer? The arena only knows de_forge, so nobody has measured the bots on the second map.
arena.ts:17importsDE_FORGEdirectly and builds its spawn grid from it, so every duel the policy was trained and evaluated in happens on one map;CARD.winRate(72%) on the page and in the terminal is that number. Thread the map through the arena, reachevaluatewith it, and state four numbers — policy and scripted baseline, on each map, same 240 seeded duels. No retraining in that issue: an honest number is the deliverable, and a training run is its own pull. Ready. -> scout
Also refreshed: #37 (smoke) is unblocked and now has the pocket's real shape on it — grenades: Partial<Record<WeaponId, number>> at world.ts:47–52, a per-kind MAX_GRENADES with no shared total (so whether HE + smoke is capped at two is a decision that belongs in that pull), and GRENADE_IDS derived from WEAPONS, so a smoke joins the pocket by being a grenade.
#138 is still ready and still must not be built — the mangled-title copy of #139, merged as #143. First thing for a person to close.
Ready now: #150, #37, #40, #46, #95, #108, plus #53, #55, #58, #59, #77, #80, #97, #110, #111, #114, #120, #126, #129 — and #138, to close.
Order I would pull in: #129, #108, #150, #46, #37. Needing a person: close #138, then #126, #54, #61, #74, #71.
A planning run with nothing to land: the working tree is clean and even with main at d54675d, nothing was written or committed, so there is nothing to push and no pull to open. Three more merges (#147 de_cinder, #145 the leaderboard at 360px, #142 the pocket), no open pulls, and the check is green — tsc --noEmit clean, 664 pass, 0 fail.
I measured the new map rather than taking the pull's word for it: twelve seeded matches each give de_forge a 63% plant rate with a 123s median round, and de_cinder 48% with a 95s median and eliminations up from 26 to 42 of ~88 rounds. Pull #147's own thirty-match run reports the same shape with a smaller gap and accepts it as geometry, which is the right call for a layout — but it leaves the half a layout cannot answer, which is the issue I filed.
#150 — does the policy transfer? arena.ts:17 imports DE_FORGE directly and builds its spawn grid from it, so every duel the shipped weights were trained and evaluated in happened on one map, and the 72% on the policy card is that number. The issue threads the map through the arena, reaches evaluate with it, and asks for four numbers — policy and scripted baseline on each map over the same 240 seeded duels — with no retraining, because an honest number is the deliverable.
I also refreshed #37 (smoke), now unblocked, with the pocket's real shape: grenades: Partial<Record<WeaponId, number>>, a per-kind MAX_GRENADES with no shared total (so whether HE + smoke is capped at two is a decision for that pull), and GRENADE_IDS derived from WEAPONS.
What a person should look at: #138 is still ready and must not be built — it is the mangled-title copy of #139, merged as #143 — then close #126, #54, #61, #74 and #71. The order I would pull in is #129, #108, #150, #46, #37.
Thirty-seventh pass, read against a3d302b. Three merges — #149 (the side split and --map, #146), #152 (audio's recording context, #80) and #153, which is worth singling out: vesper opened it against #74, found the feature already on main from #136/#147/#149, and shipped only what was missing — a seeded headless match played to a result on every map in MAP_NAMES, crates checked for standing on floor with two open sides, and settings.ts round-tripping every map name. That is the right answer to a superseded issue, and it closes #74 properly rather than duplicating it. The check is green: tsc --noEmit clean, 685 pass, 0 fail. #148 (#134) and #151 (#108) are open.
The side split's first reading is a finding. Twenty seeded matches a map, same shipped policy, the person idle:
de_forge de_cinder
rounds played 145 131
side split T 62% (90) · CT 38% (55) T 47% (61) · CT 53% (70)
plant rate 65% 52%
defused 12 of 95 plants (13%) 10 of 68 plants (15%)
median round 123s 97s
Ninety rounds to fifty-five on the default map, three standard deviations from even, with the sides swapping at half time — so it is the side winning, not a lucky team — while the same bots are within a coin flip on de_cinder. Filed:
- #154 — de_forge is terrorist-sided: T win 62% of rounds there and 47% on de_cinder, with the same bots. Diagnosis before change, out of the data (bomb too easy to plant, retake never arrives, or CTs lose the opening fight), then the smallest fix in whatever the measurement blames — layout tables or
plan.ts, not both, no per-map special case, no retraining. After: within ten points of even over 140+ seeded rounds with plant rate and round length still in band. Linked behind #46, because a retake that works moves this exact number. -> vesper
The same table is now on #46, which is the other half and is ready today: twelve defuses in ninety-five plants on de_forge.
#138 is still ready and still must not be built — the mangled-title copy of #139 (merged as #143). It and #126 (already implemented) are the two a person can close in a minute.
Ready now: #46, #150, #37, #40, #95, plus #53, #55, #58, #59, #77, #97, #110, #111, #114, #120, #126, #129 — and #138, to close.
Order I would pull in: #129, #46 (then #154 re-measures), #150, #37, #95.
A planning run with nothing to land: the working tree is clean and even with main at a3d302b, nothing was written or committed, so there is nothing to push and no pull to open. Three merges since the last pass (#149 the side split, #152 audio's recording context, #153 the tests that finish #74), and the check is green — tsc --noEmit clean, 685 pass, 0 fail.
The new side split earns its keep immediately. Twenty seeded matches a map, same shipped policy: de_forge is T 62% (90 rounds) to CT 38% (55), while de_cinder is 47/53 — three standard deviations from even, with sides swapping at half time, so it is the side winning rather than a lucky team, and the same bots are balanced on the other map. Twelve of ninety-five plants on de_forge come back defused.
I filed #154 for that: diagnosis out of the data before any change, then the smallest fix in whatever the measurement blames — layout tables or plan.ts, not both, no per-map special case, no retraining — with the after-table having to show the split within ten points of even while the plant rate and round length stay in band. It is linked behind #46, because a retake that works moves the same number; #46 now carries the same table and is ready today.
Worth noting for a person: vesper's #153 is how a superseded issue should be handled — it found #74's feature already on main and shipped only the missing tests instead of rebuilding it. Still one minute of work for someone with the scope: close #138 (the mangled-title copy of #139, merged as #143) and #126 (already implemented). The order I would pull in is #129, #46, #150, #37, #95.
Thirty-eighth pass, read against a3d302b — no merges since the last one, so the code is where I measured it and I have not re-run what has not changed. Four pulls are open and, unusually, none of them can conflict: their file sets are disjoint.
#148 restoreWorld (#134) 193 lines world.ts, world.test.ts
#151 the posts race (#108) 78 lines db.ts, db.test.ts
#156 the rebind harness (#114) 327 lines capture.ts, capture.test.ts, main.ts
#157 the HUD budget (#120) 396 lines hud.ts, hud.test.ts, record2d.ts, README
Any order merges cleanly. The only one worth reading twice is #151, since it is the fix for the rewind I reproduced on #108 — the transcript there is the test pair to check it against: the same two posts in either order must leave the same row.
sentinel's #155 is a real hole in my own plan and I have folded it in. snapshot was fixed in #133 when de_forge was the only layout; #153 landed the second one and main.ts:612 now builds its world through mapFor(settings.map), so a snapshot says nothing about which map it was played on and a resume would come back on whatever the Map select happens to hold. #135 now waits on #155 as well as #134, and I have written on it what that changes: pass mapFor(save.map) into restoreWorld, say the map on the resume line — Resume match — de_cinder, round 9, 5–3 — and test that a snapshot whose map differs from the select still resumes on its own.
Nothing new filed. Four pulls in flight, twenty issues ready, and the two findings from last pass (#154 behind #46, both carrying the side-split table) are still the sharpest things in the queue.
Ready now: #46, #150, #37, #40, #95, plus #53, #55, #58, #59, #77, #97, #111, #126, #129 — and #138, which is the mangled-title copy of #139 (merged as #143) and must be closed, not taken.
Order I would pull in: #129, #46 (then #154 re-measures), #150, #37, #95. A person's minute: close #138 and #126.
A planning run with nothing to land: the working tree is clean and even with main at a3d302b, nothing was written or committed, so there is nothing to push and no pull to open. No merges since the last pass, so I did not re-run measurements against code that has not changed.
Four pulls are open — #148 (restoreWorld), #151 (the posts race), #156 (the rebind harness), #157 (the HUD budget) — and their file sets are disjoint, so any merge order is clean. The one worth reading twice is #151: it is the fix for the rewind I reproduced on #108, and the transcript there is the test pair to check it against — the same two posts in either order must leave the same row.
The substantive planning change is that sentinel's #155 is a real hole in my own resume plan, and I folded it in: a snapshot records no map, while main.ts:612 now builds its world through mapFor(settings.map), so a resume would come back on whatever the Map select happens to hold. #135 now waits on #155 as well as #134, with a note on it saying what that means concretely — pass mapFor(save.map) into restoreWorld, name the map on the resume line, and test a snapshot whose map differs from the select.
Nothing new was filed; the queue is twenty deep with four pulls in flight, and last pass's findings (#46 and #154, both carrying the side-split table) are still the sharpest things in it. A person's minute: close #138 (the mangled-title copy of #139) and #126 (already implemented). The order I would pull in is #129, #46, #150, #37, #95.
Thirty-ninth pass, read against 7b34305. Two merges — #151 (the posts race, #108) and #148 (restoreWorld, #134) — and the check is green: tsc --noEmit clean, 692 pass, 0 fail. #156 and #157 are open.
#151 verified the way I broke it. The same two posts that rewound a match last week — the final one first, a stale periodic one second — now leave the row alone:
before the fix: kills 9, damage 1200, rounds 6, score 4-2, seconds 600, state finished
after the fix: kills 40, damage 6000, rounds 20, score 13-7, seconds 2000, state finished
#155 parked (pull #158 closed on a check whose entire summary was test exited with code 1), and it is split rather than retried. Nothing in the finding was wrong; the pull's shape was — it changed saved.ts and world.ts in one diff on a branch stacked on another pull's branch (scout/134, six commits), so a red check had no readable half.
- #160 —
Savedgainsmap, validated like every other field.saved.ts+saved.test.tsonly: written bysnapshotfrom the world's own map, refused byparseSavedwhen missing, not a string, or outsideMAP_NAMES, with "old snapshots are refused, no migration" stated in one line.restoreWorldkeeps its parameter for now. Ready. -> scout - #161 —
restoreWorldreads the map from the save.world.ts+world.test.tsonly: the parameter goes,MAPS[save.map]replaces it, and a match snapshotted onde_cindercomes back onde_cinder. Waits on #160. -> scout
Both carry the one concrete instruction #158 missed: branch from main, not from another pull's branch, and run bun run check on a clean clone first — main is green, so a red check is the branch's own, and bun test --rerun-each 2 is worth a minute when the summary says nothing. #155 is linked behind both and wants closing; #135 now waits on #161.
Ready now: #160, #46, #150, #37, #40, #95, plus #53, #55, #58, #59, #77, #97, #111, #126 — and #138, still the mangled-title copy of #139 and still to be closed rather than taken.
Order I would pull in: #160, #46 (then #154 re-measures the side split), #150, #37, #95. A person's minute: close #138 and #126.
A planning run with nothing to land: the working tree is clean and even with main at 7b34305, nothing was written or committed, so there is nothing to push and no pull to open. Two merges (#151 the posts race, #148 restoreWorld), and the check is green — tsc --noEmit clean, 692 pass, 0 fail.
#151 is verified the way I broke it. The same two posts that rewound a match — the final result first, a stale periodic one second — now leave the row at 40 kills, 6,000 damage, 20 rounds and 13–7 instead of collapsing it to 9 kills and 4–2.
#155 parked, on a check whose entire summary was test exited with code 1, so I split it rather than asking for a retry. The finding was right; the pull's shape was not — it changed saved.ts and world.ts in one diff on a branch stacked on another pull's branch, leaving a red check with no readable half. It is now #160 (Saved gains map, saved.ts and its test only, ready) and #161 (restoreWorld reads the map from the save, world.ts and its test only, waiting on #160), both carrying the one concrete instruction the failed attempt missed: branch from main, not from another pull's branch, and run bun run check on a clean clone first. #155 is linked behind both, and #135 now waits on #161.
The order I would pull in is #160, #46 (then #154 re-measures the side split), #150, #37, #95. A person's minute, unchanged: close #138 (the mangled-title copy of #139) and #126 (already implemented on main).
Filed, in order:
- #166 — The check is red on
mainand production is not serving: one test steps a whole match and times out at 5 s. Production isfailedwithtest: test exited with code 1and the app answers 404; the single failure isworld.test.ts:710, which walks a ten-player world to half time by stepping 57,600 real ticks (6.4 s on the forge box, 3.1 s here, against bun's 5 s default). It is also what closed pull #164 and got #111 parked for a fault that was not its own. Suggested: scout. - #167 — The map names its own rooms:
placesonMapDefandplaceAt. The first half of #111, on its own, with nothing reading it yet and a structural test that every walkable cell of every map has a name. Waits on #166. Suggested: scout. - #168 — A bot says what it sees, once: a sighting callout on the message line, to its own side only. The second half of #111, narrowed to contact sightings — the defuse call, the last-magazine call and noise are explicitly left out. Waits on #167 and #166. Suggested: vesper.
- #169 — A bot on a pistol round walks past the rifle on the floor.
updatePickupsgives any player a gun they step over, butbots.tsnever readsworld.pickups, so the drop economy is a rule only the person plays by. Waits on #166. Suggested: vesper.
Housekeeping on the map: #111 closed and split into #167/#168 with the parked comment answered; #138 closed (its successor #139 merged as pull #143). I could not close #126 (already true on main), #54 and #61 (both duplicates of #40) — the API refuses a close on issues I did not author — so each now carries a comment saying done, do not build; a person can close them with a click.
Ready now: #166 — and it should go first, because nothing else can go green until it does — alongside the existing ready set (#40, #46, #37, #53, #55, #58, #59, #77, #95, #97, #150, #154, #161). Nothing was pushed; the branch is unchanged and #8 stays open.
Since the last pass the map moved under it: #166 merged as #171 (the half-time restore test now forces rules.tick's timeout path instead of stepping 57,600 ticks, and bots.test.ts and the full-match test carry explicit timeouts), the check is green on main at 6b43481, and production is serving again. Pull #172 then reopened #111's work, green, carrying both halves I had split.
Map repaired first:
- #155 commented and left to a person to close — its two halves merged as #163 and #170, so a resumed match already comes back on the map it was played on; it is authored by sentinel and the API refuses my close. It is still showing as
workingwith scout on it, which is a run being spent on finished work. - #167 and #168 commented: pull #172 contains exactly their diffs, so they are to be closed against it if it merges, not built twice. A note on #172 itself says the same to whoever decides it, and that the red on the earlier attempt was #166's timeout and nothing to do with that code.
Opened, in order:
- #173 — The match you just played has no page:
GET /m/<id>, linked from every row on the board and reachable from the terminal. Every column a page needs is already onmatchinsrc/db.ts; the server has four routes and none of them shows one match. Id validated before it reaches the database, the security floor plusprivate, no-cache, nothing the board does not already show, and the same view onEnterin the terminal. Suggested: scout. Ready. - *#174 — Bomb planted — where*: the message line names the place, in the map's own words. One
placeAtlookup at the moment of the plant; no second vocabulary, nothing the bots or the policy see. Waits on #167 (so on #172). Suggested: scout**. - #175 — The knife is a 55-damage hitscan with no back to stab. One damage number and 64 units of range; nothing reads which way the victim faces, so a flank is worth nothing. One geometric test with a named threshold, armour unchanged, rifles provably untouched. Suggested: vesper. Ready.
- *#176 — README's What is real and what is not* is one 84-line paragraph. A reordering into headed subsections with not one claim changed. Suggested: scout**. Ready.
Ready now: #173, #175, #176, plus #40, #46, #37, #53, #55, #58, #59, #77, #95, #97 — and #169 is already in hand with vesper. Nothing was assigned and nothing was pushed: the branch is at 6b43481, identical to main, so there is no diff to open a pull with. Still on a person's minute: close #155, #126, #54 and #61, all four of which carry a comment saying why.
Since the last pass, four merges landed: #172 (the callout and the map's places), #165 (#150's transfer measurement), #177 (#155, confirmed a no-op) and #178 (#175's knife backstab, filed last pass and already built). main is at 53ce30c and bun run check is green — 733 pass, types clean. I closed #167 and #168 against #172; #150, #155 and #175 closed themselves on merge.
Opened, in order:
- #180 — The menu remembers your map and your difficulty but forgets your name, your side and the match length.
settings.tspersists seven things; the three fields atgame.ts:279-289are not among them, so a player who always plays Competitive as T under one name refills the form every visit. Each field guarded the waydifficultyandmapalready are. Suggested: scout. Ready. - #181 — A teammate's call you can hear. #172 taught four bots to talk and #103 gave the person three radio calls; both are silent, and a line of text mid-firefight is a line nobody reads. A non-positional blip, muted and rate-limited by
callout.ts's existing gaps, tested against the recordingAudioContext. Waits on #110, whose pull is rewritingaudio.ts. Suggested: vesper. - #182 — The AWP's scope zooms the view but not the mouse.
main.ts:782draws the scope at 40° against 90° at the hip whilecontrols.sensitivityis one number, so a scoped flick sweeps the same degrees across less than half the world: the precision gun is the twitchiest thing in the game. One derivation, one remembered setting, touch included. Suggested: vesper. Ready. - #183 — The HUD shows one message at a time, so a bot's callout wipes
Bomb planted.hud.ts:496draws only the newest of the sixsay()keeps, for four seconds — and #172 setSIDE_GAPto four seconds precisely because of that, so an enemy seen just after a plant erases the plant. A stack or a rank, inside the HUD's draw budget and readable at 360px. Waits on #120 and #110, both holdinghud.ts. Suggested: scout.
I also rewrote the scope of #154 in a comment rather than leaving it to be built as written: its T 62% on de_forge is one sample, and three more runs today read 56%, 58% and 48%, so done now starts with a pooled multi-seed measurement (and an accounting of the idle person) and closes the issue outright if the split turns out to be within a couple of points of even — changing walls to make one seed read 50% is fitting the map to a table.
Ready now: #180, #182, #173, #176, plus #40, #46, #37, #53, #55, #58, #59, #77, #95, #97; #174 and #169 are already in hand, and #135 is unblocked at last. Nothing was assigned and nothing was pushed — the branch is at 53ce30c, byte-identical to main, so there is no diff to open a pull with. Still a person's minute: close #126, #54 and #61, which the API will not let me close and which each carry a comment saying why.
No code was written this run — planning only — so there is nothing to commit; the working tree is clean and the branch is at 0644e74, byte-identical to main, whose check (tsc --noEmit + bun test) I ran green as found. The result is on the map, not in a diff.
Filed against direction #8, in order:
- #184 — The first paint groups every match ever played.
standingsinsrc/db.tsis a full-tableGROUP BY identityasked for onGET /, in everyPOST /api/matchresponse and on every terminal frame. Seeded throughsaveMatchitself: 41.6 ms at 50k rows, 171.1 ms at 200k, whilerecentMatchesstays at 0.07 ms becausematch_by_timeserves it. Budget test first, then a flat cost; theMAX(...)-on-conflict update is the trap an aggregate has to respect. Ready. → scout - #185 — Switching weapons is instant.
world.ts:1196setsp.weaponand never touchesnextShotAt, so knife-running at 250 u/s into a same-frame AK shot is free and the threecarrySpeednumbers are decoration;takePickuphas the same hole. Asks fordeployTime, with the arena measured before and after and the shipped weights left alone. Ready. → vesper - #186 — Reloading is silent.
SoundNamehas no reload andreload/finishReload/takePickuppush no effect, so the loudest two seconds in the game are inaudible to you and to whoever is next door, withears.tsalready able to pan it. Waits on #110 (pull #159 is rewritingaudio.ts). → vesper - #187 — A bot reloads only when its magazine is empty.
bots.ts:426/:598are the only reload a bot ever asks for, so it pushes a site on three rounds and then stands still for 2.5 s in front of what it was shooting at. Waits on #169, same file and in hand. → vesper
Ready now: #184, #185, plus #40, #46, #37, #53, #55, #58, #59, #77, #95, #97, #126, #173, #174, #176, #180, #182. Nothing is parked. Nobody is assigned and #8 stays open. Left for a person's click: close #126 (already true on main), #54 and #61 (duplicates of #40) — the API refuses a close on issues I did not author, and each carries a comment saying why.
🤖 Generated with Claude Code
Budget reached at $2.55; this is where it stopped.
A planning pass against direction #8, read against main at 9ba7609 with bun run check green as found (738 pass, types clean). No code was written, so this branch carries no diff — it was fast-forwarded to main and pushed; the result is on the issue map.
Filed, in order:
- #189 — A bullet that lands does not slow the player it hits: no tagging, so trading a shot costs the enemy nothing.
shootresolves damage, pusheshurtand callsflinch, and touches the victim's velocity nowhere, so hitting someone first buys nothing and there is no reason to hold an angle. Tagging cannot takeflinch's escape hatch (a slow only onworld.mewould handicap the person alone), so it goes in the tick symmetrically with the arena as the gate:scripts/measure.tsbefore and after, both numbers in the pull. Waits on #185, which is already in the same movement-speed plumbing. → vesper - #190 — A drop from anywhere is free:
fallDamageis written, tested and called by nothing.physics.ts:221is exported, documented and asserted twice;moveclearsvel[2]before anyone reads the impact speed. Carries the measurement that matters: the tallest standable surface on either map is a 128-unit crate top, about 452 u/s, under the 580 threshold — so wiring it is about the simulation being whole, and the README says so plainly instead of inventing a drop. Deleting the function is an acceptable answer if argued. Ready. → scout - #191 — A thumb aims with the mouse's sensitivity: touch look has no setting of its own.
input.ts:240multiplies a drag by the same number pointer lock uses, and the one field on the page moves both, so tuning a phone breaks the desktop. Asks for a validated, rememberedtouchSensitivityand a 44px control in the pause overlay, shown only where touch exists. Waits on #182. → vesper - #192 — The security floor is missing its two isolation headers: COOP and CORP on every response. The floor has CSP, nosniff, referrer, permissions and HSTS; a window handle and subresource embedding are still open. Two lines in
SECURITYso every path inherits them, asserted across the page, the bundle, both APIs and the 403/404/413/429/500, with COEP deliberately left out. Ready. → scout
Map repair: #174 closed against pull #188, which built exactly it. #71 was parked for a failed check on pull #117 and is now simply done — #163, #170, #177 and #179 landed the snapshot, its map and the Resume button — so it carries a comment saying so; it is authored by someone else and the API refuses my close. The other parked comments on #37, #40, #46, #53, #54, #55, #58, #59, #61 and #169 are quota and DNS failures, not work that could not be done as written, so they stand unchanged.
Ready now: #190, #192, plus #173, #176, #180, #182, #184, #185, #37, #40, #46, #53, #55, #58, #59, #77, #95, #97, #126. Nobody is assigned and #8 stays open. Still a person's click: close #126 (already true on main), #54 and #61 (duplicates of #40) and #71 — each carries a comment saying why.
🤖 Generated with Claude Code
Budget reached at $2.56; this is where it stopped.
Nothing was written this run — planning only — so there is nothing to commit or fix: the working tree is clean, the branch is at 9ba7609, byte-identical to main, and git push reports everything up to date. No half-done change exists to remove.
The result is on the issue map for direction #8, read against main with every claim measured against the working tree:
- #194 — A bot with an AWP fires it from the hip: no bot ever scopes, so the most accurate gun in the game sprays a 7.7° cone.
input.scopeappears nowhere inbots.ts;botBuystill hands rich bots an AWP. Measured through the shippedinaccuracy: 7.73° hip-strafing — a 136-unit cone at 1000 units against a 32-unit body — versus 0.49° scoped. Waits on #169 (same file, in hand). → vesper - #195 — The match-over screen hides the scoreboard it was just showing.
summarizereadsworld.meonly, so the overlay replaces the ten-row board with four lines about you. Ready. → scout - #196 — The renderer owns the frame and nobody counts it: a per-frame draw budget the recording WebGL2 context enforces. The tick is alarmed by
scripts/cost.tsand the HUD by #120; the 3D renderer has no ceiling, thoughrenderer.test.tsalready counts draw calls for relative claims. Ready. → scout - #197 — A 64-unit crate is 7 units too tall to jump onto, and crouch-jumping does nothing. Measured with the real
move: apex 57.0 units with duck and without, landing beside the box, becausehullHeightshrinks from the top and never lifts the feet. 15 such boxes on de_forge, 19 on de_cinder. Ready. → vesper
What a person should look at: the four issues above, and four closes the API refuses me — #126 (already true on main: / answers cache-control: private, no-cache), #54 and #61 (duplicates of #40), and #71 (done by #163/#170/#177/#179). Each carries a comment saying why. Nobody is assigned and #8 stays open.
🤖 Generated with Claude Code
Budget reached at $2.54; this is where it stopped.
This was a planning run, so there is no diff: the working tree is clean, the branch sits on main at 9ba7609, and git push reports everything up to date. Nothing half-done exists to remove. The result is on the issue map for direction #8, every claim read against the working tree and the running app (/ answers in 1.4–2.0 ms with the security floor and cache-control: private, no-cache intact).
Filed, in order:
- #199 — A kill looks exactly like a hit, and a grenade that lands looks like nothing.
shootpushes{ kind: "hitmark", headshot }and nothing that says killed, so the bullet that drops someone draws the same mark as the four before it;detonate(world.ts:1018) applies blast damage, records it and callsonKillwhile pushing no hitmark at all, so your own HE on two people looks like one thrown at a wall. Asks forkilledon the effect and on theearshotcue, a blast hitmark guarded toworld.meso the RL arena's hot loop is untouched, and deliberately leavesaudio.tsalone — the kill tone waits for the rewrite in flight under #110, with the flag put on the cue ready for it. Ready. → vesper - #200 — A result the server refused vanishes without a word.
postResult(main.ts:564) ignores 409, 413, 422, 429, 500 and a thrownfetchalike: the player reads a board that silently disagrees with the match they just played, and theretry-afterthe server carefully sends is read by nobody. Asks for a headlesssrc/client/posting.tsthat maps status → wording and one retry for the final post, ahiddenline in the leaderboard section written withtextContent, and words that never claim a save that did not happen. Ready. → scout - #201 — The round's damage report is 64 lines nobody tests.
reportPanel.tsis one of two client modules with no test beside it, and it carries arithmetic worth pinning — the row count,MAX_LINES, thewidth - 24clamp, and drawing nothing outsidephase === "over"— whilehud.test.ts:20already has the recording 2D context it needs. Ready. → scout
Ready now: #199, #200, #201, plus #37, #40, #46, #53, #55, #58, #59, #77, #95, #97, #126, #173, #176, #180, #184, #185, #190, #191, #192, #195, #196, #197. Nobody is assigned and #8 stays open.
What a person should look at: the three issues above, and four closes the API refuses me — #126 (already true on main), #54 and #61 (duplicates of #40), and #71 (the one parked issue, done by #163/#170/#177/#179). Each already carries a comment saying why.
🤖 Generated with Claude Code
Budget reached at $2.52; this is where it stopped.
A planning pass against direction #8, read against main at 9ba7609 with the app booted and probed. No code was written, so this branch carries no diff.
Filed, in order:
- #203 — A round your own teammate stops vanishes.
shoot(world.ts:843) returns before every line that says the bullet arrived — no blood, no impact, no mark, no word — so pushing a site behind a bot looks like a broken gun. Feedback only, no friendly fire, gated to the person so the arena's loop is untouched. Waits on #199, same six lines. → vesper - #204 — The death notice has no killer health.
FeedEntrycarries who and what but not how close you came; recorded at the kill, never read live, so a spectator learns nothing they should not see. Ready. → vesper - #205 — The terminal draws 29 lines into a 24-row window. Measured with the real
renderand 20 rows: 29 lines on both list tabs, so the title, the help line and theFORGE_APP_SSHline scroll away andxdeletes a row off screen.process.stdout.rowsappears nowhere. Ready. → scout - #206 —
/favicon.icois a 404 on every visit, and there is nothing to add to a phone's home screen. Verified against the running server. An SVG icon generated by a module and a manifest, no binary in the tree, the floor andHEADon both. Ready. → scout
What I looked at and did not file: the fixed 64 Hz step with interpolation, freeze-time locking, walk/duck silence, alive counts, spectating and the death camera, match rules with halftime and the loss bonus, gzip and the immutable bundle, the same-origin gate and the byte-counted body — all already right.
Ready now: #204, #205, #206, plus #37, #40, #46, #53, #55, #58, #59, #77, #95, #97, #126, #173, #176, #180, #184, #189, #190, #191, #192, #195, #196, #197, #199, #200, #201. Nobody is assigned and #8 stays open. Still a person's click: close #126 (already true on main), #54 and #61 (duplicates of #40) and #71 (done by #163/#170/#177/#179) — each carries a comment saying why.
A planning run against direction #8: no code was written, so this branch is byte-identical to main at 9ba7609, the working tree is clean, and git push reports everything up to date. Nothing half-done exists to remove; the check was not re-run because there is no diff to check. The result is on the issue map, with every claim read against the working tree and the booted server.
Filed, in order:
- #203 — A round your own teammate stops vanishes: no blood, no impact, no word that a bot was in your line.
shoot(src/client/world.ts:843) returns above every line that says the bullet arrived, so the tracer ends on nothing while a teammate eats your spray. Feedback only, no friendly fire, gated to the person so the RL arena's hot loop stays flat. Waits on #199, which edits the same six lines. → vesper - #204 — Killed by whom, with what — but not how close you came.
FeedEntrycarries no killer health; it is recorded at the kill and never read live, so a spectator learns nothing they should not see. Ready. → vesper - #205 — The terminal draws 29 lines into a 24-row window. Measured by running the real
renderwith 20 rows: 29 lines on both list tabs, so the title, the help line and theFORGE_APP_SSHline scroll off andxdeletes a row that is off screen;process.stdout.rowsappears nowhere insrc/tty.ts. Ready. → scout - #206 —
/favicon.icoanswers with the 404 page on every visit, and there is nothing to add to a phone's home screen. Verified against the running server. A generated SVG icon and a manifest, no binary in the tree, the security floor andHEADon both. Ready. → scout
What a person should look at: those four issues, the new edge #203 → #199, and four closes the API refuses me — #126 (already true on main), #54 and #61 (duplicates of #40) and #71 (the parked one, done by #163/#170/#177/#179); each carries a comment saying why. Nobody is assigned and #8 stays open.
🤖 Generated with Claude Code
Budget reached at $2.52; this is where it stopped.
A planning pass against direction #8, read against main at 598c302 — two commits newer than the last pass, with #198 (scoped mouse speed) and #202 (deployTime) landed. The branch was fast-forwarded to main; no code was written.
Filed, in order:
- #208 — The gun is up on screen a second before it can fire. #202 gave the AWP a 1.25 s deploy;
renderer.ts:231still raises every weapon over a fixedRAISE_TIME = 0.24, and the comment at:774still says the switch "is instantaneous and stays that way". You click for a second and nothing happens. The reload dip ten lines below already runs on the weapon's realreloadTime— that is the pattern. Ready. → vesper - #209 — The bots fight with a policy trained on 2026-09-17.
CARDclaims 72% against the scripted baseline; the simulation has since gained a knife with a back (#178) and a deploy time on every weapon (#202), with tagging (#189) and crouch-jump (#197) queued.scripts/duel.tsalready runs the shipped weights on both maps: re-measure, record the simulation's commit on the card, retrain only if the number moved. Ready. → vesper - #210 — 90° of view, take it or leave it.
HIP_FOVis a constant and the camera already takesfovas data; a phone at 360 px sees exactly what a 27-inch monitor does. A validated, remembered 70–110 setting, with the explicit rule that a chosen hip FOV must not ride #198'sscreenSharesensitivity curve. Ready. → vesper - #211 —
renderer.tsis four concepts in 855 lines. Four GLSL sources, the GL plumbing, and a 470-line closure that is the camera, the level, the effects and the viewmodel. Lift outshaders.tsand aviewmodel.tsthat is testable with no GL at all; the recording context's draw-call counts are the guard that nothing moved. Waits on #208 and #196, which both edit those lines. → scout
Map repair: #71 is closed — it was the one parked issue, and #163/#170/#177/#179 built it; the close went through this time. Nothing is parked now.
Ready now: #208, #209, #210, plus #37, #40, #46, #53, #55, #58, #59, #77, #95, #126, #173, #176, #180, #184, #189, #190, #191, #192, #195, #196, #197, #200, #201, #204, #205, #206. Nobody is assigned and #8 stays open. Still a person's click: close #126 (already true on main), #54 and #61 (duplicates of #40).
A planning run against direction #8, read against main at 598c302 — two commits newer than the last pass, with #198 (scoped mouse speed) and #202 (deployTime) landed. The branch was fast-forwarded to main and pushed; no code was written, so there is no diff and nothing to check.
Filed, in order:
- #208 — The gun is up on screen a second before it can fire. #202 gave the AWP a 1.25 s deploy, but
renderer.ts:231still raises every weapon over a fixedRAISE_TIME = 0.24, and the comment at:774still claims the switch "is instantaneous and stays that way": you click for a full second and the game does nothing. The reload dip ten lines below already runs on the weapon's realreloadTime— that is the pattern to copy. Ready. → vesper - #209 — The bots fight with a policy trained on 2026-09-17.
CARDclaims 72% against the scripted baseline, measured before the knife got a back (#178) and before every weapon got a deploy time (#202).scripts/duel.tsalready runs the shipped weights on both maps: re-measure, record the simulation's commit on the card, retrain only if the number moved. Ready. → vesper - #210 — 90° of view, take it or leave it.
HIP_FOVis a constant and the camera already takesfovas data, so a phone at 360 px sees exactly what a 27-inch monitor does; a validated, remembered 70–110 setting, with the explicit rule that a chosen hip FOV must not ride #198's sensitivity curve. Ready. → vesper - #211 —
renderer.tsis four concepts in 855 lines. Lift outshaders.tsand aviewmodel.tstestable with no GL at all, with the recording context's draw-call counts as the guard that nothing moved. Waits on #208 and #196. → scout
Map repair: #71 is closed — it was the only parked issue and #163/#170/#177/#179 had already built it; the close went through this time, so nothing is parked. What a person should still click: close #126 (already true on main) and #54/#61 (duplicates of #40), each of which carries a comment saying why. Nobody is assigned and #8 stays open.
🤖 Generated with Claude Code
The map's direction issue (#8) stays open; nothing was pushed, since a planning run's result is the map.
Opened this run, in order
- #208 — the raise is a fixed 0.24 s while the AWP's deploy is 1.25 s, so the gun is on screen long before it can fire → vesper
- #209 — the bots fight with a policy trained 2026-09-17 against a simulation that has changed four times since → scout
- #210 — no field-of-view setting: 90° take it or leave it, and a phone sees the least of the map (without letting the choice touch hip sensitivity) → scout
- #211 —
renderer.tsis four concepts in 855 lines: liftshaders.tsandviewmodel.tsout, behaviour byte-for-byte unchanged; waits on #196 and #208 → scout - #215 — the detour predicate in
plan.ts: is a gun on the floor worth walking to, as pure arithmetic tested without a world → scout - #216 — a bot with an empty primary slot walks over the rifle on its way: the goal in
bots.ts, a headless test, and one 8-match single-map table; waits on #215 → vesper
The parked issue. #169 had been parked twice with "no changes pushed (timeout)", and the reason was in its own acceptance criteria: measure.ts --matches 30 --seed 41 on both maps, before and after, is roughly half an hour of compute — I timed --matches 2 at 30.2 s wall clock. I closed it with a comment and split it into #215 (arithmetic, no measurement) and #216 (wiring, a two-minute table), and moved #187 and #194 behind #216 so edits to bots.ts stay serial.
Ready now: 27 issues, 0 parked. The cheapest useful starts are #215 (pure arithmetic), #192 (COOP/CORP on every response), #126 (cache-control on the first paint) and #206 (a favicon, since every visit currently gets a 404 page); the largest wins waiting to be taken are #184 (standings at 171 ms on 200k rows), #209 (a stale bot policy) and #46 (a CT that actually defuses).
🤖 Generated with Claude Code
A truth-and-repair pass on the map for direction #8, read against main at 6cb6517 (#214 landed the weapon raise since the last pass) with bun run check green as found — 771 pass, 0 fail, 17.2 s. No code was written, so this branch carries no diff; it was fast-forwarded to main and pushed.
I booted the app and probed it before proposing anything: / answers in 1.6–7 ms, 21 KB of HTML that gzips to 6.8 KB, the bundle is 127 KB raw / 53 KB gzip behind a year of immutable, a cross-origin POST to /api/match is refused 403 and a malformed result 422, and the security floor is on the 404 too. The combat model I checked for gaps and found none worth an issue: spray patterns with a 0.35 s recovery, hitgroups, armour, penetration, crouch and scope accuracy are all there.
Filed, in order
- #219 — every refresh re-sends the whole first paint.
cache-control: private, no-cacheis correct, but with noetaga revalidation is a full 200: 6.8 KB on every refresh when a 304 would do.encoded(server.ts:54) is the one place that knows the bytes, and the trap is spelled out — never answer 304 to a visitor who is owed aset-cookie. Ready. → scout - #220 — JavaScript off: 27 buttons, two canvases, and
grep -rn noscript srcfinds nothing, so a scripting-off visitor gets the whole menu, dead, with the working leaderboard underneath making it look alive.main.ts:100's "No WebGL2" message is the voice to copy. Ready. → scout - #221 — a phone held upright plays through the 4:3 box
game.ts:242pins under 640 px;grepfor orientation acrosssrcfinds nothing at all. A portrait hint, a guardedscreen.orientation.lockon fullscreen that ignores rejection, and landscape given the room it is owed. Waits on #220 (same sheet) and #77 (in flight, same CSS). → vesper
Map repair. #169 was the parked issue from last pass and is now closed and split into #215/#216. Nothing is parked. I verified the premises of #189, #190, #191, #192 and #206 still hold on current main.
One thing I got wrong, mechanically: I tried to close #126 (already true on main — / really does answer private, no-cache) and #54/#61 (three issues for one fault in tty.ts's x; #40 says it first and best). The API accepts state changes only on issues I authored, so the PATCHes silently failed and all three are still open — and my comments are now the third copy of the same note on those threads. Those three closes need a person's click; I won't comment on them again.
Ready now: 29 issues, 0 parked. #219 and #220 are both an hour's work with tests; #215 (the detour predicate, pure arithmetic) is the cheapest start; #192 (COOP/CORP), #206 (favicon) and #205 (the terminal overflowing a 24-row window) are small and true; the biggest wins still untaken are #184 (standings at 171 ms on 200k rows), #209 (bot policy measured against a simulation four changes newer) and #46 (a CT that actually defuses).
🤖 Generated with Claude Code
Planning pass against main at 3118dc1 (#217, #218, #222 landed since the last pass), bun run check green: 791 pass, 0 fail. No new feedback text was on this issue or on a pull for this branch, so this is an ordinary pass. The branch was fast-forwarded; no code was written.
Filed:
- #224: footsteps go quiet on the Shift flag (
world.ts:1250), not on speed. A phone has no walk button, so a half-pushed stick walks at ~125 u/s and bots still hear every step. Waits for #189. → vesper - #225:
requestPointerLock()has nounadjustedMovement, so OS acceleration shapes every flick. Thevoidalso swallows a refused lock: Esc then a quick Resume leaves a live match behind a free cursor. → scout
Parked issues: none new. #37, #40, #46, #53, #55, #58, #59 were parked on API/limit errors and were already rewritten; #54/#61 still need a person to close them as duplicates of #40.
I opened two issues from a planning pass against main at 3118dc1. I couldn't find any feedback in words: the newest comment on #8 is my own, and no pull exists for this branch. So this was an ordinary planning pass. The branch is fast-forwarded to main and pushed, with no code change. bun run check passes: 791 tests, 0 failures.
Opened, in order
- #224: footsteps depend on the Shift flag, not on speed, so on a phone every step is heard.
footsteps(src/client/world.ts:1250) stays silent only when the player is ducking or the walk flag is set. The phone controls have no walk button. A half-pushed stick already moves you at about 125 u/s, walking pace, yet every stride still makes astepsound the bots listen for. Done means a single speed threshold constant decides the silence, with a test that fails onmaintoday and a line in the touch help text. It waits for #189, which is in progress and edits the lines just above. Suggested: vesper. - #225: aim goes through the operating system's mouse acceleration, and a refused pointer lock leaves a live match you can't aim in.
main.ts:411asks for the lock withoutunadjustedMovement(raw mouse input). It also usesvoid, which discards a refused request. Chrome refuses a new lock for about a second after Esc. So Esc followed by a quick Resume leaves the match running behind a free cursor: the mouse doesn't aim and nothing pauses the game again. Done means a small, testedlock.tsthat tries raw input, then the plain lock, and treats anything else as refused. A refused lock keeps the match paused with a one-line reason. Suggested: scout.
Parked issues: nothing new is parked. #37, #40, #46, #53, #55, #58 and #59 were parked on API or usage-limit errors and have already been rewritten. #54 and #61 still need a person to close them as duplicates of #40, because the API only lets me close issues I opened.
Ready now: #225, plus the queue already on the map. The unassigned ones are #37, #40, #46, #53, #55, #58, #59, #95, #126, #173, #176, #180, #190, #191, #192, #195, #197, #200, #201, #203, #204, #205, #206, #209, #210 and #219. #224 is ready once #189 lands. Nobody is assigned and #8 stays open.
🤖 Generated with Claude Code
I opened no new issues this run; the map was already in good shape, so I only corrected #224. There was still no feedback in words: the newest message on #8 was my own summary from last run, and no pull exists for this branch. I fast-forwarded the branch to main at 56838f9 and pushed it with no code change. I didn't re-run the check this run.
What changed on the map
- #224 is now ready. The issue it waited on, #189 (a hit slows the player it lands on), was merged. I commented on #224 to fix one gap that merge opened. A player who gets hit can briefly drop under the new walking-speed threshold, so they go quiet for up to half a second. That is intended, because speed alone decides whether you're heard. The comment asks for a fifth test that pins it, so nobody later exempts hit players. It also notes the line numbers in the issue have moved.
- #221 is now ready, because #220 (the page with JavaScript off) was merged.
Ready now: 29 issues, none parked.
- Opened last run and still unassigned: #224 and #225.
- Other unassigned: #37, #40, #46, #53, #55, #58, #59, #95, #126, #173, #176, #180, #187, #192, #194, #195, #197, #200, #201, #203, #204, #205, #206, #209, #210, #219 and #221.
Nobody is assigned and #8 stays open. #54 and #61 still need a person to close them as duplicates of #40, because the API only lets me close issues I opened.
🤖 Generated with Claude Code
I opened no issues this run and posted no comments. The "new message" on #8 was again my own summary from the previous run, and no pull exists for this branch, so there was no new feedback.
The branch is fast-forwarded to main at 48b4494, which now includes #228 (fall damage), #229 (a touch-look setting) and #230 (the menu remembers name, side and match length), and pushed with no code change. I didn't re-run the check this run.
The map needs nothing else: 26 issues are ready, 3 are being worked on, 7 are blocked and none are parked. That includes #224 and #225 from the earlier pass.
My own summaries on #8 seem to be what starts each new run, so another comment there would only start another empty run. #8 stays open and nobody is assigned.
🤖 Generated with Claude Code
Nothing to act on again: the "new message" on #8 was my own summary from the last run, and no pull exists for this branch. I opened no issues and posted no comments. #8 stays open and nobody is assigned.
- Branch: fast-forwarded to
mainat894d4f6and pushed, with no code change. That brings in #231 (bots reload before a fight, not in it) and #232 (the two missing security headers). I didn't run the check this time. - Map: 23 ready, 3 in progress, 7 blocked, none parked. That's more than enough to keep the crew busy, so I added nothing.
Each of these runs seems to be started by my own summary being posted back to #8, which then counts as a new message. Unless someone writes feedback there in words, the next run will be empty too. That's a problem with what triggers a run, and a person would need to fix it.
🤖 Generated with Claude Code
No new feedback this time either. The latest message on #8 was my own summary from the previous run, and there is still no pull for this branch. I opened no issues and posted no comments. #8 stays open and nobody is assigned.
- Branch: fast-forwarded to
mainat979784fand pushed, with no code change. That brings in #233 (bots scope the AWP), #234 (crates you can jump onto, and crouch-jumping) and #236 (feedback when a teammate stops your shot). I didn't run the check this time. - Map: 19 ready, 2 in progress, 7 blocked, none parked. That is still well over the three ready issues the map needs, so I added nothing.
Each empty run is started by my own summary landing on #8. I'll keep these runs this short until someone writes feedback there in words.
🤖 Generated with Claude Code
I opened one issue this run and commented on one. There was no feedback in words on #8 again: the newest message there was my own summary. I did check the parked issues, and #126 had been parked twice in the last hour.
Opened
- #241: The check fails on a busy machine.
src/db.test.ts:236times one coldstandingscall against a 10 ms wall-clock limit. The second builder run on #126 sawbun run checkfail on it, and it passed on a rerun, so it can fail previews for pulls that never toucheddb.ts. Done means the test checks the query plan (standing_by_rankused, no scan ofmatch, no temporary sort). The time limit stays only as a loose guard, measured after a warm-up the waytextures.test.tsdoes it. The check must then pass 20 runs in a row. I suggest scout, who found it; a person assigns.
Commented
- #126: both builder runs found nothing to build, because
/already sendscache-control: private, no-cacheandsrc/server.test.ts:136-142tests it. It's done, but sentinel opened it and I can only close issues I opened, so a person needs to close it. #54 and #61 still need the same, as duplicates of #40.
The other issues with "Parked by the crew" comments were parked on API errors or usage limits, not on the work. Each already has a later rewrite or a note that it is still valid, so I left them alone.
Branch: fast-forwarded to main at 1875a50 (#235) and pushed, with no code change. I didn't run the check.
Ready now: 26 issues, including #241. That's more than the three the map needs. Nobody is assigned and #8 stays open.
🤖 Generated with Claude Code
Make the cs2 clone playable end to end: movement and aiming that feel immediate, shooting with clear hit feedback, one small map, and a bot to play against. 60 fps on a laptop, usable on a phone. Keep the check green and every change small.
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.