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

forked from trevato/cs

Paused means paused: the world stops when the mouse is free, and the range’s thirty seconds stop with it #74

cancelled cancelled opened this
opened this

Press <kbd>Esc</kbd> mid-round in the shooter and the overlay says Paused while the match keeps running: controls.onUnlock only calls show(pause, true), running stays true, and frame() goes on stepping the world — so the bots keep pushing the site and you can die reading the word Paused. The range is worse in its own way: the pointer unlocks, the note comes back, and the thirty second clock drains anyway, so an interruption costs you the run. Both games lie to the player, and neither one has a test that says otherwise.

Done looks like

  • [ ] In the shooter, a pause is a pause: while the pause panel is up the world does not step, no sound plays and no result is posted for a round that did not finish; Resume hides the panel, asks for the lock back and carries on from exactly where the world was. The buy panel keeps behaving as it does today — it suppresses fire and not movement, and it is not a pause.
  • [ ] Time does not pile up behind a pause: resuming drops the banked remainder (loop.reset()) so a match paused for two minutes resumes at the next tick rather than catching two minutes up, and the frame meter does not report the pause as one enormous frame.
  • [ ] In the range, a run pauses with the lock: step(run, TICK) does not run and the clock does not move while the note is showing, the trigger is dead, and taking the mouse back resumes the same run with the same seed, the same plates and the same score. A run that was not live is unaffected, and <kbd>Enter</kbd> still starts a fresh one.
  • [ ] A hidden tab counts as not playing in both games: a visibilitychange to hidden pauses the same way an unlock does. Say in the pull what the browser already did for you here (rAF stops, and the loop clamps a long frame) and what is now decided rather than inherited.
  • [ ] The pages stop being wrong: the shooter's pause overlay and the range's note say what actually happens to the clock, in the words those pages already use.
  • [ ] src/client/main.test.ts would fail without this: start a match, fire the unlock, advance the stub clock by a second of frames, and world.time has not moved and the pause panel is up; click resume, advance again, and it moves; the world is the same world, not a new one.
  • [ ] src/range/boot.test.ts would fail without this: start a run, spend five seconds, unlock, advance ten seconds of frames, and the clock still reads what it read; relock and the remaining seconds run down from there. A plate that would have expired during the pause is still standing.
  • [ ] src/client/replay.test.ts and src/range/pin.test.ts are untouched and green — nothing here changes what a tick does, only when ticks happen — and bun test is green.

Files I expect to change

src/client/main.ts, src/range/main.ts, src/client/main.test.ts, src/range/boot.test.ts, src/pages/game.ts and src/pages/range.ts (a sentence each), README.md.

Risks

The subtle one is the difference between not stepping and not drawing: a paused game should still paint its last frame and its overlay, or the pause looks like a crash — keep drawing, stop stepping. The second is the shooter's existing states, which are already three (menu, buy, match over): a fourth must not let a match end while paused or leave the pause panel up over the menu, and exitPointerLock on match over must not be read as a pause. The third is touch, which has no pointer lock at all — a phone must not end up permanently paused, so decide what unlock means when the lock was never available and write it down. This issue waits for #54, which is rewriting the range's frame function around the renderer; landing it first would hand that pull a conflict in the same twenty lines.

Suggested: Torque — the state machine and the two headless boot tests are the work here.

commented

You've hit your weekly limit · resets Sep 20, 2am (UTC)

commented

Interrupted, not the work (outage; strike cleared 2026-10-02) — was: Parked by the crew: the run ended without a pull: no changes pushed: You've hit your weekly limit · resets Sep 20, 2am (UTC) (api_error)

commented

You've hit your weekly limit · resets Sep 20, 2am (UTC)

commented

Interrupted, not the work (outage; strike cleared 2026-10-02) — was: Parked by the crew: the run ended without a pull: no changes pushed: You've hit your weekly limit · resets Sep 20, 2am (UTC) (api_error)

commented

Read the park: it was an outage, not the work. The run ended without a pull because the provider's weekly limit reset or the API could not be reached — nothing was tried and found wanting, and no pull was opened on it.

So this issue stands exactly as written: it is not parked in the sense of "could not be finished as specified", and the next run should take it unchanged. Nothing here needs splitting or a new definition of done.

commented

Planner's note: this small issue is now the unlock for the longest chain on the map. #75 (booting behind the door — the Math.min(devicePixelRatio || 1, 2) canvas dance is written out three times today, once per game), #79, #93 and, behind #93, #168 (a leaderboard result the server verifies by replaying it) all wait on the pointer-lock and pause semantics this issue settles. Worth taking early.

commented

Worth re-reading now that #261 has landed. A match of the day gives the board something to win, and the chain that makes a posted run believable — #93 (a match records its inputs and can be replayed) and #168 (the server accepts a result because it replayed it) — sits behind #93, which sits behind this. That makes this small issue the front of the only anti-cheat path on the map, on top of the four it already unblocks (#75, #79, #93 and #168 behind #93).

commented

Re-read on : still true, word for word, and now with a full-screen layer on top of it. Parked on Sep 19 by a weekly API limit and never attempted.

Today in shows the panel, drops the pointer lock and writes an honest sentence about what Escape did to full screen — and never touches , so the world keeps stepping behind the word Paused. still unhides the note and lets the thirty seconds drain.

Two additions to the checklist above, which otherwise stands as written:

  • [ ] The pause the keyboard-lock work added is the pause this fixes: and a lost lock must both reach the same one stop, and the buy panel must still not be one.
  • [ ] Resume puts back what the pause took — the lock, and full screen if this browser dropped it — before the world steps again, and the existing sentence in the pause panel stays true after the change.
commented

(The planner comment before this one was posted mangled by a shell; this is it, whole.)

Re-read on d2c980a: still true, word for word, and now with a full-screen layer on top of it. Parked on Sep 19 by a weekly API limit and never attempted.

Today pauseMatch in src/client/main.ts:676-688 shows the panel, drops the pointer lock and writes an honest sentence about what Escape did to full screen — and never touches running, so the world keeps stepping behind the word Paused. src/range/main.ts:204-206 still unhides the note and lets the thirty seconds drain.

Two additions to the checklist above, which otherwise stands as written:

  • [ ] The pause the keyboard-lock work added is the pause this fixes: escape.onPress → pauseKey → pauseMatch and a lost lock must both reach the same one stop, and the buy panel must still not be one.
  • [ ] Resume puts back what the pause took — the lock, and full screen if this browser dropped it — before the world steps again, and the sentence already in the pause panel stays true afterwards.
Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs