Skip to content

games: pace the ball off its own clock, and play both games faster - #378

Merged
ralyodio merged 1 commit into
mainfrom
worktree-games-ball-physics
Aug 12, 2026
Merged

games: pace the ball off its own clock, and play both games faster#378
ralyodio merged 1 commit into
mainfrom
worktree-games-ball-physics

Conversation

@ralyodio

Copy link
Copy Markdown
Contributor

The ball in /games breakout and /games pong was still slow and still jiggled after #373 (tick rate) and #376 (half-row grid). Measuring the drawn ball rather than the physics showed two separate causes left.

The jiggle was cadence, not step size

Rounding the true position to the lattice every tick moves the drawn ball whenever it crosses a column edge or a half-row edge — and those two are on unrelated schedules. When their periods are close but not equal (a near-diagonal, which is exactly what breakout launches at) they beat against each other:

old gap histogram worst jump
pong 16ms ×90, 32 ×100, 48 ×189, 64 ×328 1.41
breakout 16 ×24, 32 ×20, 48 ×21, 64 ×32, 80 ×21 1.41

The steps were the right size and the ball still looked like it was struggling, because nothing was moving at a rate. And whenever both crossings landed on the same tick, it lurched a diagonal 1.41 units in a single frame.

So the true position no longer decides when the ball is drawn — only which way it owes a step. Steps are paid out by a clock accruing at |vx| + 2|vy| lattice units per tick, so the ball moves every 1/speed ticks at any angle, trailing the truth by under a unit (under half a character).

The slowness was just slowness

The old ball crossed the board in about three seconds. Both games now scale every tuned speed by a single PACE, the pong machine and the paddle spin along with the ball, so the balance that makes a ball into the corner beat the machine is exactly the one that was tuned — only the clock it is played against changes. Breakout's paddle goes to three columns a press to stay ahead.

Result

drawn steps/sec longest stall worst jump
pong 20.4 → 42.6 64ms → 32ms 1.41 → 1.00
breakout 20.5 → 43.7 80ms → 48ms 1.41 → 1.00

Testing

Full suite green (1853 tests, 0 failures). The new test pins the cadence rather than the speed, and it bites — sampling the old way at the new pace still fails it:

old sampling (round the true position), at the NEW pace:
  pong        worst jump 1.41  longest stall 48ms  37.6 steps/s   FAIL
  breakout    worst jump 1.41  longest stall 48ms  37.0 steps/s   FAIL

even clock (what ships):
  pong        worst jump 1.00  longest stall 32ms  42.6 steps/s   PASS
  breakout    worst jump 1.00  longest stall 48ms  43.7 steps/s   PASS

The existing balance tests (the machine can be beaten, but not by doing nothing, a wall can be cleared, and cannot be cleared by leaving the paddle alone) pass unchanged at the new pace.

🤖 Generated with Claude Code

The ball in pong and breakout was still slow and still jiggled after the
tick rate and the half-row grid were both fixed. Measured, two things were
left, and they were separate.

The jiggle was cadence. Rounding the true position to the lattice every
tick moves the drawn ball when it crosses a column edge or a half-row
edge, and those two are on unrelated schedules. When their periods are
close but not equal — which is what a near-diagonal is, and what breakout
launches at — they beat: breakout held a cell for 16ms, then 80ms, then
48ms, several times a second, and pong ran 16/32/48/64. The steps were
the right size and the ball still looked like it was struggling, because
nothing was moving at a rate. Whenever both crossings landed on the same
tick it also lurched a diagonal 1.41 units in one frame.

So the true position no longer decides when the ball is drawn, only which
way it owes a step. Steps are paid out by a clock running at the ball's
own speed, so it moves every 1/speed ticks at any angle, trailing the
truth by under half a character. Worst jump is now exactly one unit, and
the longest stall drops from 64ms to 32ms in pong and 80ms to 48ms in
breakout.

The slowness was just slowness: the old ball crossed the board in about
three seconds. Both games now scale every tuned speed by a single PACE,
the machine and the spin along with the ball, so the balance that makes a
ball into the corner beat the pong machine is exactly the one that was
tuned — only the clock it is played against changes. That takes the drawn
ball from 20 steps a second to 43 in both games. Breakout's paddle goes to
three columns a press to stay ahead of the faster ball.

The new test pins the cadence rather than the speed, and fails against
the old sampling even at the new pace.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

ThreatCrush Security Scan

97 finding(s)

HIGH/CRITICAL: 5 | MEDIUM: 41 | LOW: 51

Severity Rule Location
HIGH js-ssrf-outbound-request apps/pwa/public/sw.js:45
HIGH tls-verification-disabled apps/pwa/src/lib/moshpit-gateway.mjs:299
HIGH sh-remote-script-execution install.sh:79
HIGH sh-remote-script-execution install.sh:83
HIGH tls-verification-disabled src/dns.mjs:741
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:61
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:75
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:101
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:265
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:269
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:314
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:499
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:675
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:677
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:736
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:782
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:852
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:955
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:1063
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:1199
MEDIUM js-unescaped-html-sink apps/pwa/src/routes/moshpit.mjs:1419
MEDIUM js-timing-unsafe-mac-compare apps/pwa/src/routes/settings-sync.mjs:179
MEDIUM js-dynamic-code-execution apps/pwa/test/apikey-mask.test.mjs:129
MEDIUM sql-template-interpolation apps/pwa/test/credits-webhook-event-match.test.mjs:111
MEDIUM sql-template-interpolation apps/pwa/test/credits-webhook-event-match.test.mjs:131
MEDIUM sql-template-interpolation apps/pwa/test/moshpit-terms.test.mjs:192
MEDIUM sql-string-concatenation src/cli-schema.mjs:128
MEDIUM sql-string-concatenation src/cli-schema.mjs:417
MEDIUM sql-string-concatenation src/cli-schema.mjs:543
MEDIUM js-timing-unsafe-mac-compare src/settings-sync.mjs:309
MEDIUM insecure-temp-file test/dns-disable-restore.test.mjs:93
MEDIUM insecure-temp-file test/dns-disable-restore.test.mjs:310
MEDIUM insecure-temp-file test/news.test.mjs:479
MEDIUM insecure-temp-file test/plugins.test.mjs:152
MEDIUM insecure-temp-file test/pty.test.mjs:28
MEDIUM insecure-temp-file test/pty.test.mjs:31
MEDIUM insecure-temp-file test/pty.test.mjs:40
MEDIUM insecure-temp-file test/pty.test.mjs:42
MEDIUM insecure-temp-file test/pty.test.mjs:47
MEDIUM insecure-temp-file test/pty.test.mjs:48
MEDIUM insecure-temp-file test/pty.test.mjs:49
MEDIUM insecure-temp-file test/tabs.test.mjs:8
MEDIUM insecure-temp-file test/tabs.test.mjs:13
MEDIUM insecure-temp-file test/tabs.test.mjs:14
MEDIUM insecure-temp-file test/tabs.test.mjs:22
MEDIUM insecure-temp-file test/trust.test.mjs:240
LOW secret-generic-credential apps/pwa/test/apikey-bearer-scheme.test.mjs:30
LOW secret-generic-credential apps/pwa/test/apikey-mask.test.mjs:38
LOW secret-generic-credential apps/pwa/test/apikey-reveal.test.mjs:35
LOW secret-generic-credential apps/pwa/test/approvals-context.test.mjs:28

…and 47 more. Full results in the Security tab.

Snippets are redacted; ThreatCrush never prints matched credential material.

@ralyodio
ralyodio marked this pull request as ready for review August 12, 2026 19:40
@ralyodio
ralyodio merged commit d3bc6a6 into main Aug 12, 2026
4 checks passed
@ralyodio ralyodio mentioned this pull request Aug 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant