Skip to content

Emulator tiny8bit CPC

Retro Jack edited this page Jun 9, 2026 · 9 revisions

floooh/tiny8bit — Amstrad CPC

The Amstrad CPC bundle took three failed core swaps before landing on tiny8bit. The reasons each previous candidate fell out are worth keeping on record so we don't tread the path again.

Where we started — three dead ends

  1. EmulatorJS + cap32. The CDN's cap32-legacy-wasm.data exports are incomplete; the core aborts with getControllerPortInfo undefined on GameManager.cwrap(). Same pattern as the Odyssey² EJS dead-end — the vanilla libretro core wasn't EJS-forked. Out.

  2. crocods. Boots cleanly. Then stops at a catalog menu. The source has a literal // TODO: ID_AUTORUN so there's no API to skip the menu and go straight to the loaded image. Out.

  3. cpcbox. Minified and obfuscated. Rejects our .dsk images with [SNA Parser] Error: Invalid SNA id because internal vars are like $Kyk3LdzJw. Debugging surface area is zero. Out.

So we landed on Andre Weissflog's tiny8bit — a standalone WASM with a clean URL-param config API, no EJS dependency, MIT-licensed, CPC 6128 + Locomotive BASIC 1.1 default.

The sokol_args URL-param API

tiny8bit reads its configuration from window.location.search via its sokol_args library (function Za() in the minified WASM glue). Standard keys:

  • file=<url> — load .dsk/.sna/.tap on boot (relative path OK, resolves against document URL)
  • input=<keys> — type these keys after load (URL-encoded; %0A is Enter; usually run"NAME%0A)
  • type=cpc464|cpc6128 — model override (default cpc6128)
  • joystick=<mode> — joystick mode

The trick: sokol_args reads location.search at startup, so the URL has to be in the right shape before cpc.js runs. play.html reads our ?game=KEY, looks up {rom, input, type} in games.json, then rewrites the URL via history.replaceState() to ?file=<rom>&input=<boot-cmd> BEFORE WASM init. Only then inject cpc.js.

The autoboot problem

CPC .dsk autoboot isn't built into the AMSDOS format. Each game needs its run"<file> command supplied via input=. Figuring out the right filename per disk means parsing the AMSDOS catalog: track 0 sectors 0xC1-0xC4, 256-byte header + per-track 0x100 track-info preface, then 64 catalog entries of 32 bytes each.

For example, Roland on the Run's autoboot file is ONTHERUN.BAS, not the obvious-looking DISC.BAS. Get it wrong and the disk loads but does nothing.

The single-fire-button problem

floooh's chips library only wires one fire button — BTN0 == BTN1, both mapped to Space. CPC games that expected two-button joysticks (PgUp/PgDn was the convention for second-button arcade ports) get nothing on PgUp/PgDn.

The fix is a capture-phase keydown listener that intercepts PageUp/PageDown, calls preventDefault + stopPropagation, then dispatches a synthetic Space:

document.dispatchEvent(new KeyboardEvent(e.type, {
  key: ' ', code: 'Space', keyCode: 32, which: 32,
  bubbles: true, cancelable: true
}));

sokol's handler sees Space; the game sees a fire button. PgUp and PgDn become alternative fire keys without the user noticing.

The no-game (BASIC) branch

If no ?game= URL param, skip genxLoadGame() (which fails on missing key by design), set document.title = 'Amstrad CPC', clear the search via history.replaceState(null, '', location.pathname), and let cpc.js init with empty sokol_args. The WASM defaults to CPC 6128 + Locomotive BASIC. User gets Ready and a blinking cursor.

A game-sourcing note

TOSEC .dsk is the preferred source — period-correct for the 3-inch floppy CPC era. .sna snapshots boot faster but feel inauthentic. ("I prefer .dsk — this isn't a cartridge system.")

The 1985 Sorcery (Virgin) TOSEC release is French-only. We swapped in 1985's Elite (Firebird) for CPC instead. The C64 Elite had Lenslok copy-protection issues that needed a cracked release; the TOSEC CPC Elite dump is already cracked (autoboots via run"ontherun skipping the protection check), so no workaround needed.

What this taught us

When evaluating a new platform's WASM emulator:

  1. Check the source for exposed URL-param / JS API hooks (sokol_args, location.search consumers, init callbacks). If it's all baked-in defaults, you'll need source patching.
  2. Check the source for // TODO: AUTORUN or similar incomplete features that block headless usage.
  3. Avoid minified + obfuscated standalone JS — debug surface area is zero.

Bundle layout

emulators/amstradcpc/
├── play.html            ← URL rewrite + PgUp/PgDn remap
├── cpc.js               ← tiny8bit emscripten glue
├── cpc.wasm             ← emulator
├── controls.html
├── games.json
└── games/
    └── *.dsk            ← floppy images, with per-game boot command

Related

Clone this wiki locally