-
Notifications
You must be signed in to change notification settings - Fork 0
Emulators
Three emulators are vendored under emulators/ with bundled local ROMs. Once python3 -m http.server is running, they need no network — every byte is on the same origin.
| Folder | System | Vendor | Runtime | ROMs |
|---|---|---|---|---|
emulators/jsbeeb/ |
BBC Micro | mattgodbolt/jsbeeb v1.12.0 | Vite-built dist/
|
10× .ssd in dist/discs/<vendor>/
|
emulators/jsnes/ |
NES | bfirsh/jsnes v2.1.0 |
dist/jsnes.min.js (~135 KB) |
10× .nes in roms/
|
emulators/genesis/ |
Sega Genesis | lrusso/Genesis (Emscripten port of PicoDrive) |
Genesis.min.js (~2.1 MB) |
10× .bin in roms/
|
- The user picks a number in a
GAMESmenu. -
N.batruns the named launcher, e.g.sonic1. -
sonic1.batis alink:entry pointing at the emulator's URL. - The terminal opens that URL in a new tab via
window.open(link, "_blank").
For NES and Genesis the URL pattern is:
../emulators/<name>/play.html?game=<key>
The wrapper page reads ?game=<key>, looks it up in games.json, fetches roms/<key>.<ext>, and starts the emulator.
For BBC Micro the URL pattern is:
../emulators/jsbeeb/dist/?disc1=<vendor>/<title>.ssd&autoboot
?autoboot triggers *EXEC !BOOT. Drop it to land at the BASIC > prompt instead.
Same recipe each time:
-
Vendor the emulator under
emulators/<name>/. Drop the upstreamnode_modules, the inner.git, and any.gitignorethat excludes deploy artefacts (e.g.*.ssd,/dist). -
Strip telemetry from the source's
index.htmlbefore deploying (jsbeeb shipped Google Analytics; we removed it). -
Write
play.html— a thin wrapper that:- reads
?game=<key>, - fetches
games.json, - looks up
{title, rom}, - boots the emulator with that ROM.
Use absolute paths via
location.pathname.replace(/[^/]*$/, "")for any framework that does its own path mangling (EmulatorJS's loader does — bit us once). - reads
-
Bundle the ROMs under
emulators/<name>/roms/. archive.org doesn't set CORS for direct browser fetch, soplay.htmlcan't pull at runtime — they must be on the same origin. -
Wire
fs.js: each game's.batgets alink:field pointing at../emulators/<name>/play.html?game=<key>.
jsbeeb's empty-schema disc URLs don't unzip.
?disc1=path/file.zip falls through to the default: branch in loadDiscImage() (main.js), which calls fdc.load("discs/" + path) and hands raw bytes to discFor(). Only the sth: / http: / https: schemas unzip. Workaround: extract the inner .ssd from each upstream .zip and serve that raw — URL becomes ?disc1=<path>.ssd&autoboot.
Vendored .gitignore files silently exclude deploy artefacts.
emulators/jsbeeb/.gitignore had /dist (upstream's build-artefact rule); dist/discs/.gitignore had *.ssd (anti-piracy guard). Both meant the deployed jsbeeb on GitHub was actually broken for several commits — only worked locally where the files existed on disk. When vendoring an upstream tree, audit every .gitignore it ships.
EmulatorJS was tried first and dropped.
Its data/loader.js prepends "../" to relative EJS_pathtodata, which conflicts with how dynamic-import vs. document-relative paths resolve, blank-screening silently. JSNES's pure-JS API was a half-hour drop-in instead.
archive.org collection access varies.
nintendo-entertainment-system-nes-roms-europeusa and sega-genesis-romset-ultra-usa allow direct file download via https://archive.org/download/<item>/<path>. Some other items return 401/403 (auth-gated). Probe with curl -I before scripting bulk downloads.
python3 -m http.server 8765After editing fs.js or any emulator wrapper, hard-refresh the prompt page (Ctrl+Shift+R). Browsers aggressively cache fs.js between visits.
For BBC Micro there's a headless test harness from upstream at emulators/jsbeeb/.claude/skills/emulator/SKILL.md. It needs the upstream dev tree (src/, plus npm install sharp) which we strip in the deploy build, so re-clone fresh if you want to drive the BBC headlessly.