Skip to content

feat(games): the arcade goes from six games to twenty-two - #366

Merged
ralyodio merged 1 commit into
mainfrom
worktree-games-asteroids-blackjack
Aug 11, 2026
Merged

feat(games): the arcade goes from six games to twenty-two#366
ralyodio merged 1 commit into
mainfrom
worktree-games-asteroids-blackjack

Conversation

@ralyodio

@ralyodio ralyodio commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Sixteen new games for /games, all on the same frame, key decoder and driver the original six already used. Every one of them is pure — create a state, hand it a key, hand it a tick, ask it for rows — so test/games.test.mjs plays them all without a TTY anywhere near it.

game
invaders forty of them, and the last one moves fastest
centipede shoot it in the middle and now there are two of them
asteroids turn, thrust, shoot — every rock you break becomes two
breakout dig a channel up the side and let the ball do the rest
pong first to seven, and the angle is all in where you hit it
tank two tanks, one yard, five hits — line it up and let go
digdug dig the tunnels, pump the monsters, drop rocks on the rest
frogger the road kills what it touches, the river kills what it doesn't
kong five girders, four ladders, and a barrel with your name on it
pitfall jump the logs, swing the pits, and get the gold before dark
choplifter fly out, land, fill the back, and get them home
spyhunter keep it on the tarmac, shoot the ones shooting back
outrun a road that bends, traffic that doesn't, and a clock that always wins
excitebike turbo until it cooks, and land the way you took off
stagedive run the barricade, hop the gear, duck the crowd, take the picks
blackjack hit, stand, double, split — dealer stands on 17

Every game has to be winnable and losable, and both are tested

The pattern throughout: a bot that plays properly has to get somewhere, and a bot that doesn't has to die. That is what caught the real bugs.

  • Invaders ranked its aliens two rows apart, making the fleet nine rows tall on a board with eleven above the bunkers. It "landed" after two drops and no wave was survivable by anyone. Now an aiming player always dies to the bombs instead — never to the fleet.
  • Kong flipped a barrel's direction when it landed, sending it back where it came from and off the board; nothing reached the lower girders and an idle player could not be hit at all. Then, with one ladder per girder, the only way up was the chute the barrels come down. Two ladders and a fixed direction per girder later, a climbing bot reaches level 21.
  • Tank faced you and drove, which grinds into the same wall for ever — the first duel finished 0–0 after twenty thousand ticks with neither tank having found the other. It searches the yard now.
  • Excitebike's landing was inert: the ramp set your pitch and nothing changed it. The nose drops on its own now, and a rider who ignores it spills 200 times where one who doesn't spills none.
  • Pitfall's swing was one column short of a pit and its jump landed on the back half of a log.
  • Pong rallied for ever against a perfect player; OutRun's checkpoints handed back more clock than the road cost.
  • Shots that moved faster than their target was tall passed straight through it, in three separate games. They travel in sub-steps now.

Also: two bugs the letter games were losing to the shared decoder

h j k l were mapped to arrows for every game and r was always a restart, so hangman could not be told the letters in refactor and h could never mean hit. Games that read letters opt out with vim: false and restartable: false; arrows are untouched, so blackjack still bets with them.

Not built

Donkey Kong's sloped girders and Pole Position's perspective road are the two things a terminal row cannot honestly draw. Kong is here with flat girders — the slope's job is kept, since each girder has a direction and the chute sits at its far end — and Pole Position's road is here as OutRun, which is the same renderer with a clock worth playing against. A second copy of it with a lap counter would have been the same game twice.

189 tests in test/games.test.mjs; full suite 1546 pass, 0 fail. Played in a real pty as well as in the harness.

🤖 Generated with Claude Code

`/games asteroids` and `/games blackjack` (also `21`), sharing the frame, the
key decoder and the driver every other game in the cabinet already uses.

Asteroids is the real-time one. The 1979 cabinet did not have to care that a
terminal cell is twice as tall as it is wide; everything here measures distance
in columns and counts a row as two, which is the only reason the rocks come out
round and a collision happens where the screen showed it. Rocks break into two
of the next size down, small ones pay most, the screen has no edges, and the
ship blinks while it cannot be hit.

Blackjack deals before the frame lands — no bet prompt, no options screen. Hit,
stand, double and split, dealer stands on all 17s, blackjack pays 3:2, and a
split 21 is twenty-one rather than a natural, which is the rule most home
versions get wrong. Between hands the arrows are the chips; the stack is 100 and
the game ends when it is gone.

Both are pure the way the rest of the arcade is, so test/games.test.mjs plays
them: it wrecks a ship, splits rocks down to nothing, walks a stack to broke and
proves the box does not change width between hands.

Fixes two things the letter games had been quietly losing to the shared decoder.
`h j k l` became arrows for every game, and `r` was always a restart — so
hangman could not be told the letters in `refactor`, and `h` could never mean
hit. Games that read letters now opt out with `vim: false` and
`restartable: false`; arrows are untouched, so blackjack still bets with them.

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

Copy link
Copy Markdown

ThreatCrush Security Scan

95 finding(s)

HIGH/CRITICAL: 5 | MEDIUM: 39 | 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:415
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/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
LOW secret-generic-credential apps/pwa/test/approvals-credits.test.mjs:28
LOW secret-generic-credential apps/pwa/test/approvals-notify.test.mjs:26

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

Snippets are redacted; ThreatCrush never prints matched credential material.

@ralyodio
ralyodio merged commit 92a1dbc into main Aug 11, 2026
4 checks passed
@ralyodio ralyodio changed the title feat(games): asteroids and blackjack — the arcade goes to eight feat(games): asteroids, blackjack and stagedive — the arcade goes to nine Aug 11, 2026
@ralyodio ralyodio changed the title feat(games): asteroids, blackjack and stagedive — the arcade goes to nine feat(games): the arcade goes from six games to twenty-two Aug 11, 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