Search › A-Z › S › Watara Supervision
Watara Supervision
The Watara Supervision (1992), also sold as the QuickShot Supervision, is an 8-bit handheld game console and one of the many Game Boy rivals of its era. This is the Potator emulator, whose portable C core is compiled to WebAssembly so it runs the machine directly in the browser. It is wired into the in-page debugger, where you can single-step the 65C02 CPU, set breakpoints and watchpoints, and inspect the whole memory map. The 65C02 core it reuses is a compact portable 6502/65C02 emulator, credited in the notes below.
Runs on: Web browser
Watara Supervision Online Emulator
Play Watara Supervision using JavaScript directly in your browser.
Controls
Configurations
| Configuration | Emulator | Machine | OS | Legal | |
|---|---|---|---|---|---|
| Chimera | Watara Supervision | Watara Supervision | grey | Open ⛶ | |
| Super Pang | Watara Supervision | Watara Supervision | grey | Open ⛶ | |
| SSSnake | Watara Supervision | Watara Supervision | grey | Open ⛶ | |
| Journey to the West | Watara Supervision | Watara Supervision | grey | Open ⛶ | |
| Tennis Pro ’92 | Watara Supervision | Watara Supervision | grey | Open ⛶ | |
| Crystball | Watara Supervision | Watara Supervision | grey | Open ⛶ | |
| Plasma (65C02 demo) | Watara Supervision | Watara Supervision | open | Open ⛶ |
Machines emulated
Chips
Notes
Embedding
The online build is Potator, a Watara Supervision emulator (originally by Normmatt, this fork by infval), whose emulation core is portable C with no SDL dependency. We compile only that core to WebAssembly with Emscripten and reach it through a small hand-written shim (sv_shim.c) that exposes a handful of EMSCRIPTEN_KEEPALIVE entry points — there is never a per-cycle callback across the JS/wasm boundary.
Boot and load a ROM. The module is built with MODULARIZE, so loading supervision_core.js defines a SupervisionModule() factory. Instantiate it, cwrap the shim, copy the ROM image into the wasm heap and hand it over:
const Mod = await SupervisionModule();
const sv_init = Mod.cwrap('sv_init', null, []);
const sv_load = Mod.cwrap('sv_load', 'number', ['number', 'number']);
const sv_frame = Mod.cwrap('sv_frame', null, []);
sv_init(); // gpu_init + memorymap_init, 65C02 IPeriod = 256
const rom = new Uint8Array(await (await fetch(romUrl)).arrayBuffer());
const p = Mod._malloc(rom.length);
Mod.HEAPU8.set(rom, p);
sv_load(p, rom.length); // memorymap_load + hard reset
The shim surface. Everything the debugger needs is a plain C function; the wasm holds all machine state:
| Export | What it does |
|---|---|
sv_frame() | Run one whole video frame (256 CPU slices + timer + the vblank NMI) and blit the 160×160 LCD into an RGBA buffer. |
sv_step() | Execute exactly one 65C02 instruction (Exec6502). The single-step primitive. |
sv_render() | Re-scan the LCD out of video RAM without advancing the CPU, so a stepped state shows on screen. |
sv_fb() | Pointer to the 160×160 RGBA framebuffer inside the wasm heap, painted to a 2D canvas. |
sv_read(a) / sv_write(a,v) | Bus access over RAM / I/O / video RAM / ROM. sv_read is side-effect-free (reads the register file directly, never clearing a latch). |
sv_pc() | The 16-bit program counter. |
sv_getreg(i) / sv_setreg(i,v) | Read / write the register file: A, P, X, Y, S, PC. |
sv_key(bit,down) | Press or release a button (D-pad, A, B, Start, Select) on the controls latch. |
Debugger integration
Wiring the wasm core into the shared in-browser debugger needed one boot shim that owns the loop, plus a new 65C02 disassembler.
A host-owned loop. Rather than call any built-in frontend, the boot drives the core itself so pause / step / breakpoints work. With no breakpoints or watchpoints it runs a whole frame at once; otherwise it steps one instruction at a time and checks state between instructions:
function stepFrame(){
for (let i = 0; i < INSNS_PER_FRAME; i++) {
if (bps.has(sv.pc())) { running = false; return; } // execution breakpoint
const before = snapshotWatched();
sv.step(); // one 65C02 instruction
if (watchedChanged(before)) { running = false; return; }
}
sv.render();
}
Techniques for deeper access.
- A single-instruction primitive. The stock core only ran to a cycle boundary, so
Exec6502was added to the 65C02: a one-line step flag makesRun6502return right after one instruction, giving the debugger an exact step. - Side-effect-free reads. The hex and disassembly views read through
sv_read, which indexes the register file, RAM and ROM directly instead of routing through the hardware read path, so auto-polling a view never clears the timer or DMA latches. - Direct register state.
sv_getreg/sv_setregread and write the liveM6502struct; the status byte is surfaced as clickable N/V/B/D/I/Z/C flag chips. - Execution breakpoints. Because the loop is host-owned, a breakpoint is a JavaScript
Setof PC values compared againstsv_pc()before each instruction. - Write watchpoints. With no per-write hook across the wasm boundary, watched addresses are snapshotted before each instruction and compared after; a change pauses the loop — the same visible behaviour as a store watchpoint.
A new 65C02 decoder. debugger/src/cpus/w65c02.js was added: the shared NMOS 6502 table plus the 65C02 additions the Supervision runs (STZ, BRA, the stack ops PHX/PHY/PLX/PLY, TSB/TRB, BIT immediate, the (zp) indirect mode, JMP (abs,X)) and the Rockwell bit ops, with a .byte fall-back for raw data.
Architecture
The Watara Supervision (1992), also sold as the QuickShot Supervision, is an 8-bit handheld game console and one of the many Game Boy rivals of its era. It is built around a 65C02 (a CMOS 6502) running at about 4 MHz, with a 160×160 monochrome LCD showing four shades of grey.
- 65C02 CPU — the CMOS 6502: the accumulator A, index registers X and Y, stack pointer S, 16-bit PC and the N/V/B/D/I/Z/C status flags, plus the 65C02 instruction additions.
- Memory map — 8 KB system RAM at
$0000, the I/O register block at$2000, 8 KB video RAM at$4000that the LCD scans, and the cartridge ROM banked into$8000–$FFFF. - Video — a scanline is read straight out of video RAM using the
XPOS/YPOS/XSIZEregisters; each byte packs four 2-bit pixels. - Timer & interrupts — a programmable timer and a vertical-blank NMI drive the machine; two bytes at
$2024/$2025latch the timer and DMA interrupt sources.
The 65C02 core the machine runs is a compact portable 6502/65C02 emulator (the M6502 / M65C02 by Marat Fayzullin and contributors), reused unchanged inside Potator; only a single-instruction step entry point was added for the debugger. The whole machine lives inside the WebAssembly module, and the debugger reads it each refresh through the shim.
Sound
Pattern S (register-driven synthesis), because the mixed samples never cross the wasm boundary. The Potator core compiles its sound.c, but the hand-written shim exposes no audio buffer and the C build sources are not on disk to relink, so the emulated mix is computed inside the wasm and discarded. What the shim does expose is the I/O register block, read side-effect-free through sv_read. So supervision-sound.js is a faithful JavaScript port of Potator's sound.c synthesis, driven by watching those registers.
The sound chip. The Supervision has two pulse/tone channels (12.5 / 25 / 50 / 75% duty, 4-bit volume, a length counter), a 15/7-bit LFSR noise channel, and a 4-bit DMA/PCM channel that streams nibbles out of memory. Each of these is reproduced exactly from the register semantics at $2010–$201C (channels 1 & 2 and the DMA) and $2028–$202A (noise).
How writes are captured. Once per video frame the boot loop calls SupervisionSound.frame(), which reads the sound registers through sv_read, diffs them against the previous frame, and re-runs the exact C write-handlers (sound_wave_write / sound_dma_write / sound_noise_write) on any register that changed. That rebuilds the derived state sound.c computes at write time (period size, DMA/noise step, on-flags, length counters), including the “super-duper wave” period-boundary syncing. It then calls sound_decrement() once — matching watara.c, which ticks the length counters once per supervision_exec — and renders the frame with a direct port of sound_stream_update.
Sample rate / pitch. Potator's period and step formulas are written against SV_SAMPLE_RATE; the port substitutes EmuAudio.sampleRate into every one of them, so pitch is correct at whatever rate the AudioContext runs and no resampling is needed. It emits exactly Math.round(EmuAudio.sampleRate / 60) interleaved-stereo Int16 samples per frame and EmuAudio.push()es them to the shared sink. The chip's stream is unsigned (silence = 0, tones swing 0…volume), so a one-pole DC blocker removes the offset before a fixed gain scales to signed Int16 with hard clamping.
Mute contract. window.EMU_BOOT.transport exposes isMuted() / setMute(m), delegated straight to EmuAudio. Audio starts muted (browsers block audio before a gesture); the shell's Sound button resumes the AudioContext and unmutes from a real click. Sound is fully supported and plays whatever the running game produces; the bundled titles (e.g. Chimera) drive the sound hardware from their own title screens. The page starts muted, so click Sound to hear it.
Caveat. Because register state is sampled once per frame, several writes to the same register within a single frame collapse to the last value, and the DMA/PCM channel is read back through the normal CPU bus (sv_read) rather than the core's banked ROM pointer, so its source bank is a best-effort approximation — the pulse and noise channels, which carry the music, are exact.