A home for a series of browser-based arcade-game clones — mostly glowing vector lines on black, Canvas 2D, no backend, no game engine. Each game is small, self-contained, and playable in a browser.
This is one repository: a plugin host. The games live in plugins/<id>/, the
shared library in src/shared/, and the lobby that fronts them all in lobby/. Until
2026-07-30 they were nine separate repos; docs/ops/migration-manifest.md records
where each tree came from.
The arcade is live: arcade.slabgorb.com — the lobby front door, with every game one tile away.
| Game | Play | Description | Source |
|---|---|---|---|
| Tempest | tempest.slabgorb.com | Faithful clone of Atari's 1981 vector arcade game — ride the rim of a tube and blast enemies climbing the lanes. | plugins/tempest |
| Star Wars | star-wars.slabgorb.com | Faithful clone of Atari's 1983 vector arcade game — first-person cockpit shooter (TIEs → Death Star surface → trench run). | plugins/star-wars |
| Asteroids | asteroids.slabgorb.com | Faithful clone of Atari's 1979 vector arcade game — inertial ship flight, splitting rocks, and a roaming saucer. | plugins/asteroids |
| Battlezone | battlezone.slabgorb.com | Faithful clone of Atari's 1980 vector arcade game — first-person 3D wireframe tank duel with dual-tread steering. | plugins/battlezone |
| Red Baron | red-baron.slabgorb.com | Faithful clone of Atari's 1980 vector arcade game — first-person WWI biplane dogfight over a vector landscape. | plugins/red-baron |
| Centipede | centipede.slabgorb.com † | Faithful clone of Atari's 1981 raster arcade game — trackball shooter in a mushroom field, with spider, flea and scorpion. | plugins/centipede |
| Joust | joust.slabgorb.com † | Faithful clone of Williams' 1982 raster arcade game — flapping knight duel over a lava pit. | plugins/joust |
† The two newest cabinets — the arcade's raster titles, added after the per-game hosting
table was last written. Both were once doubted (centipede's deploy was unconfirmed from
this repo; joust's was known only from a green CI deploy), but on 2026-08-05 each was
MEASURED serving a live build — at its own hostname and via arcade.slabgorb.com/<id>/,
each against a nonsense control (mg1-12). See docs/ops/hosting.md.
Plus the front door: the lobby, the vector-style menu that lists and launches every game above.
The cabinet is collapsing onto one origin. Every app now ships into a single
bucket behind arcade.slabgorb.com, the lobby at / and each game at /<id>/. The
per-game hostnames above become permanent redirects to those paths, so they keep
working either way. See docs/ops/hosting.md.
- Vector aesthetic — glowing vector lines on black, rendered with HTML5 Canvas 2D. No 3D engine, no physics engine. (Centipede and Joust are raster originals and are drawn as such.)
- Pure-sim core — game logic is a deterministic, framework-free simulation
(
src/core), kept separate from the rendering/audio/input shell (src/shell). This keeps the logic unit-testable without a DOM, and every game has a test that scans its ownsrc/core/to enforce it. - No backend — everything runs client-side; high scores live in
localStorage. - Share late — code moves into
src/shared/only once a second game proves the duplication is real.
arcade/ # ONE repo — this is the whole thing
├── .pennyfarthing/ # multi-agent dev workflow (pf) + repos.yaml (one entry)
├── justfile # task runner — `just serve` (dev), `just release` (ship)
├── vite.config.ts # one config factory, parameterised by app id
├── vitest.config.ts # one vitest project per app
├── src/host/ # the plugin contract + the generated game registry
├── src/shared/ # the shared library, in-tree (imported as `@shared/…`)
├── lobby/ # the arcade lobby shell — served at the origin root
├── plugins/<id>/ # one directory per game
├── scripts/ # build-app · release · deploy-r2 · gen-registry
└── tests/ # the orchestrator suite (node:test)
The orchestration tooling is Pennyfarthing, a multi-agent development framework.
The canonical way to serve the arcade in dev is a single command at the repo root:
npm install # once per fresh checkout — one install for the whole cabinet
just serve # ONE vite dev server: http://127.0.0.1:5270/just serve is the one authoritative dev launch command, and it is a bare npx vite —
the port 5270, the host and strictPort all come from vite.config.ts, so an
invocation that forgets the flags is pinned too. A port collision fails loudly;
only one server can hold 5270 at a time, which is what stops a sibling checkout serving
you its working tree by accident. Local servers are dev-only; they never affect the
live site. See CLAUDE.md → Serving the arcade (dev) for the full workflow.
Production is Cloudflare R2 static hosting: one public bucket behind
arcade.slabgorb.com, with the lobby at the root keys and each game under its own
<id>/ key prefix. A tag deploys, not a branch — main carries every app's
commits, so a push to it could not say which app to ship.
just release <app> [patch|minor|major] # test+build gate, version bump, tag
# <app>-vX.Y.Z, push — CI then deploys to R2
just release-all [level] # every app; games first, lobby last
just deploy # manual fallback: build + upload straight
# to the bucket from this checkoutA bare vX.Y.Z tag is invalid here: it names no app and deploys nothing.
Details and runbook: docs/ops/hosting.md.
just serve # the ONE dev server: lobby at /, each game at /<id>/
just test-all # every project's tests, in one vitest process
just test-one tempest # one app's tests — what the release gate runs
just build-all # build every app (eleven games + the lobby) into dist/
git status # one repo, one history — `just status` is retiredThere are no per-game dev-*/test-*/build-* recipes any more, and no
just status/just pull: each was cd <name> && npm run … across eight sibling
checkouts that are now one repo. Take the app id as an argument instead.
just serve serves the whole cabinet on one port: the lobby at / and each game at
/<id>/ from its own plugin sources — the same paths the R2 build uses, so a dev URL and
a production URL differ only in origin. A screenshot taken at /tempest/ is Tempest.
Unknown paths (/banana/) still fall through to the lobby's SPA fallback, so an all-200
sweep of known paths proves nothing about whether the games are really wired in — until
mg1-2 that fallback answered every path with identical lobby HTML. Check with a nonsense
control and assert a game path DIFFERS from it, as tests/canonical-serve.test.mjs does.
A new cabinet is a plugins/<id>/ directory of four files plus three registrations —
the mechanical runbook is docs/ops/hosting.md → Adding a new
game. For a sprite/raster game there is also a sequenced field guide distilled from
the joust, missile-command and pac-man builds — the epic order, the raster-only seams
(tile/sprite ROM decode, colour PROM, WSG sound, the bake step) and the traps that
shipped green tests while being wrong: docs/playbooks/next-sprite-game.md.