JSVecX
JSVecX is a JavaScript port of the VecX Vectrex emulator by raz0red, running the GCE Vectrex vector console entirely in-browser. It emulates the Motorola 6809, the 6522 VIA and the AY-3-8910 sound chip, and rasterises the machine's analog vector generator to an HTML5 canvas. With no cartridge inserted it boots the Vectrex system BIOS straight into the built-in Mine Storm game.
Runs on: Web browser
JSVecX Online Emulator
Play JSVecX using JavaScript directly in your browser.
Controls
Configurations
| Configuration | Emulator | Machine | OS | Legal | |
|---|---|---|---|---|---|
| Mine Storm | JSVecX | Vectrex | grey | Open ⛶ |
Machines emulated
Chips
Notes
Embedding
jsvecx is a set of plain JavaScript files (no modules). Vendor them and load them in dependency order — utils, globals, the BIOS fastromdata, the e6809 CPU, the e8910 sound chip, the osint vector renderer, vector_t and vecx — but skip upstream's jQuery page driver. That driver owns a setTimeout loop and status UI that can't be paused or single-stepped, which the debugger needs. Instead replicate its main(): build the machine, point its renderer at a <canvas id="screen">, reset it, and drive it from a loop you control.
var vecx = new VecX();
vecx.osint.init(vecx); // analog vector generator -> canvas#screen
vecx.e6809.init(vecx); // 6809 core bound to the machine bus
vecx.vecx_reset(); // loads the BIOS; with no cart it boots Mine Storm
var CPF = (Globals.VECTREX_MHZ / 50) | 0; // ~30000 cycles = one 50 Hz frame
(function loop(){ vecx.vecx_emu(CPF); requestAnimationFrame(loop); })();
The machine is plain objects. Everything the debugger needs is a method or field on the machine or its CPU:
| Member | Kind | What it does |
|---|---|---|
vecx.vecx_emu(cycles) | method | Run the machine for cycles 6809 clocks — steps the CPU, the VIA timers and the vector generator, rendering when the phosphor budget expires. vecx_emu(1) runs exactly one instruction: the single-step primitive. |
vecx.vecx_reset() | method | Reset the machine and reload the BIOS (and cartridge, if any). |
vecx.read8(a) / vecx.write8(a,v) | method | CPU bus read/write across cart, RAM, the VIA and the BIOS. |
vecx.e6809 | field | The 6809: reg_pc, reg_a, reg_b, reg_dp, reg_cc (numbers) and reg_x/y/u/s (each an fptr whose .value is the 16-bit register). |
vecx.e6809.e6809_sstep(irq,firq) | method | Execute one 6809 instruction and return the cycles it took — what vecx_emu calls in its inner loop. |
vecx.ram / vecx.rom / vecx.cart | field | The 1K RAM, the 8K system BIOS and the 32K cartridge, as plain byte arrays — readable and pokeable live. |
vecx.alg_jch0/1, vecx.snd_regs[14] | field | The analog joystick axes and the button port — how input reaches the machine each frame. |
Because the CPU and memory are ordinary JavaScript, the debugger single-steps with vecx_emu(1), reads and writes the registers directly on e6809, and implements breakpoints and watchpoints as host-side checks around the loop — no changes to the emulator.
Debugger integration
The debugger plug-in reads the live machine from window.EMU_BOOT and hands the shared framework a description of the Vectrex. Nothing is patched into the emulator; the whole surface is ordinary field access.
- Registers are read straight off
vecx.e6809each refresh —A B D X Y U S DP PC CCplus theE F H I N Z V Ccondition flags decoded fromreg_cc— and written back the same way. - Memory is exposed as four chips: a side-effect-free 64K CPU bus (the
$C000–$DFFFVIA I/O window is skipped so auto-polling never trips a timer), plus the raw RAM, BIOS and cartridge. Disassembly uses the sharedm6809decoder. - Single step is
vecx_emu(1)— one 6809 instruction with its VIA and video cycles. - Breakpoints are host-side: with any set, the loop steps one instruction at a time and compares
e6809.reg_pcbefore each. Watchpoints wrapvecx.write8and pause on a write to a watched address. - Input is a 4-button keypad plus the analog stick shown as a D-pad; press/release set the held-direction flags and the button port the run loop feeds to the machine each frame.
Architecture
The Vectrex is unusual: it has no framebuffer. A Motorola 6809 drives a 6522 VIA whose ports feed a set of integrators and a digital-to-analog converter that steer the electron beam directly, drawing crisp vectors on a tall built-in CRT. jsvecx keeps each block as its own object hanging off VecX:
e6809— the Motorola 6809 core, single-stepped bye6809_sstep.vecx— the machine: the 6522 VIA, its timers and shift register, the DAC/integrator maths that turn port writes into beam movement, and theread8/write8bus.osint— the "analog" vector generator: it accumulates drawn line segments and rasterises them to the 2D canvas, fading like phosphor.e8910— the General Instrument AY-3-8910 sound chip; each video frame it rendersEmuAudio.sampleRate/60samples and pushes them to the shared audio sink.fastromdata— the 8K system BIOS, which contains the built-in Mine Storm game the console runs with no cartridge inserted.
Each vecx_emu chunk runs the 6809 while ticking the VIA and integrating beam motion, then hands the accumulated vectors to osint to draw — a complete, inspectable machine that boots straight to Mine Storm.
Sound
Upstream e8910 opened its own AudioContext and pulled the AY-3-8910 through a ScriptProcessorNode. We removed that context entirely and instead feed the shared EmuAudio sink (/debugger/src/audio.js): each video frame the run loop pulls the same AY renderer for exactly round(EmuAudio.sampleRate / 60) samples, converts them to interleaved-stereo Int16 (the AY is mono here, so each sample is duplicated to L and R) and calls EmuAudio.push. No second AudioContext is left running; the sink owns the only one.
The renderer has no built-in sample rate — it advances the chip a fixed four ticks per output sample and uses the tone-period registers directly, so a tone comes out at f = 2 * sampleRate / Period while the real chip is f = AY_CLOCK / (16 * Period). To get correct pitch at whatever rate the sink reports, start() computes a scale factor and every derived period counter (tone A, B and C, the noise and the envelope) is multiplied by it:
this.AY_CLOCK = 1500000; // AY-3-8910: 6 MHz crystal / 4
this.rateScale = 32 * EmuAudio.sampleRate / this.AY_CLOCK;
this.psg.PeriodA = (regs[0] + 256 * regs[1]) * this.rateScale; // same for B, C, noise, envelope
Programming a known period and measuring the generated fundamental confirms the tones land within 0.15% of f = 1.5 MHz / (16 * Period) at both 48 kHz and 44.1 kHz. The Sound button is wired through the transport: transport.isMuted and transport.setMute delegate straight to EmuAudio, and playback starts muted (browsers block audio before a gesture) until the first click unmutes it.