Skip to content

feat(static): Pocket Static — one TypeScript game, three cartridges (GBA/GB/NES), starring BOARDROOM#121

Draft
doodlewind wants to merge 6 commits into
mainfrom
zippy-radio
Draft

feat(static): Pocket Static — one TypeScript game, three cartridges (GBA/GB/NES), starring BOARDROOM#121
doodlewind wants to merge 6 commits into
mainfrom
zippy-radio

Conversation

@doodlewind

Copy link
Copy Markdown
Collaborator

What

Pocket Static (static/, @pocketjs/static) — a new static game compiler that replaces @pocketjs/aot: you write a game as one TypeScript module (build-time declarations + generator scripts that never execute), and the compiler partially evaluates it into real cartridges for GBA, Game Boy, and NES. Supersedes #52's prototype with a fresh implementation, and ships a launch game: BOARDROOM, the November 2023 OpenAI board crisis as a five-day RPG.

How

  • A real script compiler, not a statement whitelist. script(function* (s, v, f) {...}) bodies lower from their ASTs to a stack VM: locals, i16 expressions with folding, if/while/for/switch, short-circuit &&/||, break/continue, subroutine CALLs, and macro inlining with static binding (for...of unrolls, static if folds, return values). ${...} interpolation compiles to scratch-var stores + textbox format tokens. 23 compiler tests run against a TypeScript reference VM — no emulator in the loop.
  • One engine, three consoles. The VM interpreter (~600 lines) and the whole RPG engine (movement, collision, actors, triggers/warps, textbox/choice state machines) are portable C compiled by arm-none-eabi-gcc, sdcc and cc65; each console adds one small HAL. Battle-tested quirks are documented in-tree: DMG drops VRAM writes outside vblank (STAT-gated queue), sdcc 4.6 miscompiles some u8×u8 multiply frames (engine uses accumulation/shifts), NES scenarios pace on engine ticks because cc65 frames stretch.
  • Determinism as the contract. Seeded story RNG advanced only by script rnd(); a fixed-layout debug block mirrored at a per-console address; headless harnesses (libmgba for GBA+GB, jsnes for NES) drive identical scenarios and hold every console to the reference VM's answers. For the story suite, the reference VM plays the whole game first and its event log becomes the console input script.
  • rpg/battle.ts: a complete turn-based battle system written as compiler macros — zero bespoke runtime; each encounter compiles to specialized bytecode.
  • BOARDROOM (static/games/boardroom/): the Vegas call, the guest badge, three CEOs in three days, the Microsoft feint, 743 signatures, Ilya's flip, and a final negotiation battle vs THE BOARD. Every quoted line pinned to the public record in dossier.md; the rest is labeled parody. Cast art is a deterministic pixel-person generator in the declaration zone.
  • Retire aot/: tsconfig aliases, the site product page + docs (now /static/, 6 fresh pages, E2E captures as the demo), and skills updated; site/build.ts green end-to-end.

Testing

bun test .                        41 pass   (vm semantics, script compiler, pipeline)
bun test/e2e.ts                  105 pass   (smoke contract game × gba/gb/nes)
bun games/boardroom/test/e2e.ts   51 pass   (full story playthrough × gba/gb/nes)
bun site/build.ts                 ok        (static/docs 6 pages)

ROM sizes: BOARDROOM ≈ 28.5 KB (.gba), 32 KB (.gb, MBC5), 32 KB (.nes, UNROM+CHR-RAM). Headers are flashcart-correct; real-hardware runs not yet attempted.

Follow-ups (not in this PR): codex/PixelLab imagegen sheet upgrade for the cast (the deterministic art is the fallback by design), VN/platformer categories, cjk16 text mode, NES scrolling.

🤖 Generated with Claude Code

doodlewind and others added 6 commits July 17, 2026 17:28
… runtime E2E green

- spec/isa.ts single source of truth: stack-VM ISA, text tokens, debug
  block, per-target table; gen-c.ts emits runtime/gen/spec_gen.h
- vm/ref.ts reference interpreter + strict semantics tests (14)
- compiler: real expression compiler over generator ASTs (locals, control
  flow, short-circuit, switch, CALL subroutines, macro inlining with
  static binding + for...of unroll, FMT text interpolation) (23 tests)
- rpg category: DSL, model builder, linker, portable C engine (vm.c/rpg.c)
- GBA: packager + HAL + libmgba harness; smoke-game E2E 35/35

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… with GBA

- sdcc/SM83 bare-metal runtime (own crt0, no GBDK): window-layer textbox,
  STAT-gated VRAM write queue, HRAM OAM DMA, MBC5 blob banking
- targets/gb.ts packager: DMG 2bpp shade LUTs, banked data emission,
  makebin + rgbfix drive
- portable-core hardening: sdcc 4.6 SM83 miscompiles some u8*u8 multiply
  frames (__muluchar) — engine arithmetic rewritten as accumulation/shifts
- vblank-budget lesson: writes past mode 1 are silently dropped on DMG;
  the queue drains max 12 cells per vblank behind a STAT+LY gate

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…105/105)

- cc65/ca65 runtime with custom crt0: NMI-owned OAM DMA + VRAM write ring
  (single + fill-8 entries), CHR-RAM upload, UNROM bus-conflict-safe bank
  latch, attribute-row-aligned textbox (rows 24-29, subpal 3)
- targets/nes.ts: planar 2bpp + NES master-palette reduction, generated
  ld65 config + iNES header, per-bank data segments
- jsnes harness paces scenarios on the debug block's tick counter, so
  cc65's multi-frame ticks keep scenarios tick-accurate
- nes maps cap at 32x24 (attr granularity): closing the box restores fills

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Five days in November 2023, playable: the Vegas call, the guest badge,
Shear's 72 hours, the Microsoft feint, 743 signatures, Ilya's flip, and a
final negotiation battle against THE BOARD (rpg/battle.ts — a whole battle
system as compiler macros, zero new runtime).

- games/boardroom: game.ts + deterministic pixel-people art + dossier.md
  (verified quotes/dates; original parody dialogue)
- rpg/battle.ts: reusable battle DSL library (macro inlining + static
  unroll + FMT damage lines)
- compiler: helper macros importable across modules; registry survives
  cached asset modules
- story E2E: the reference VM plays the whole beat list and its event log
  becomes the console input script — 17 checkpoints x 3 consoles, 51/51

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…product line

- remove aot/ (superseded by static/; PR #52's multi-target prototype is
  re-implemented fresh there)
- tsconfig: @pocketjs/static path aliases; include static/
- site: /aot/ product page + docs -> /static/ (new copy, 6 fresh doc pages,
  E2E captures as the demo slideshow), home targets card updated
- skills: release + imagegen notes point at Pocket Static
- static/README.md: the product face; static/docs/*.png site captures

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
gba/gb open in the Homebrew mGBA.app; nes serves a local jsnes web player
(same core as the E2E harness), so seeing the NES build needs no installs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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