Search › A-Z › A › Atari Lynx (Handy)
Atari Lynx (Handy)
This is the Atari Lynx in your browser, driven by a compact core written from scratch in plain JavaScript. It runs a real WDC 65C02, the CPU inside the Lynx's Mikey chip, and models Mikey's display DMA and 16-pen colour palette, so it boots straight to a colourful Lynx insert screen with no downloads and no proprietary boot ROM.
The core is authored as ordinary JavaScript on purpose. Every register and every byte of memory is a live object, so it plugs into the shared in-browser debugger with real live 65C02 registers (A, X, Y, SP, PC, P and the N V B D I Z C flags), side-effect-free memory, a genuine single-instruction step, breakpoints and watchpoints. That level of access is what a compiled wasm core such as libretro Handy cannot give a browser debugger.
Play with the on-screen Lynx gamepad (D-pad, A and B, Option 1, Option 2 and Pause) or the physical keyboard. This core models Mikey and the Suzy joystick, so it runs framebuffer homebrew rather than commercial cartridges, which drive the Suzy sprite engine.
Runs on: Web browser
Atari Lynx (Handy) Online Emulator
Play Atari Lynx (Handy) using JavaScript directly in your browser.
Controls
Configurations
| Configuration | Emulator | Machine | OS | Legal | |
|---|---|---|---|---|---|
| Lynx Colour Insert Screen | Atari Lynx (Handy) | Atari Lynx | open | Open ⛶ |
Machines
Chips
Notes
Embedding
This Lynx core is one plain-JavaScript file, lynx.js, exposing a factory window.Lynx.create(). There is no wasm heap and no bundler. The whole machine is ordinary objects, so the host page owns the loop and the debugger reaches straight into it.
Boot. Create a machine, load a 65C02 program with its reset vector, then run your own loop that steps the CPU and paints the framebuffer to a 160×102 <canvas>:
var m = Lynx.create();
m.loadProgram(bytes, 0x0200, 0x0200); // bytes at $0200, reset -> $0200
var img = ctx.createImageData(160, 102);
(function loop(){
for (var i = 0; i < 40000; i++) m.step(); // one 65C02 instruction each
m.render(img); ctx.putImageData(img, 0, 0);
requestAnimationFrame(loop);
})();
The machine is plain objects. Every part the debugger needs is a live method or field:
| Member | Kind | What it does |
|---|---|---|
m.step() | method | Execute exactly one 65C02 instruction. The single-step primitive. |
m.render(imageData) | method | Rasterise Mikey's framebuffer (at DISPADR) through the 16-pen palette into a 160×102 image. |
m.mem | field | The 64 KB address space as a Uint8Array - RAM, the Mikey/Suzy register windows and the vectors. |
m.peek(a) / m.poke(a,v) | method | Side-effect-free byte read / write of the bus (poke routes through the watchpoint hook). |
m.cpu.getA/X/Y/SP/PC/P() & set…() | method | Read and write the 65C02 registers live. |
m.cpu.flags() / setFlag(n,on) | method | The N V B D I Z C status bits, individually. |
m.pressJoy(bit) / releaseJoy(bit) | method | Set / clear a bit in the Suzy joystick latch ($FCB0); m.JOY names the bits. |
m.setWriteHook(fn) | method | Install a callback on every bus write, how watchpoints are built. |
Because the CPU, bus and RAM are ordinary JavaScript, the host page can inspect and control the whole machine at runtime.
Debugger integration
The plug-in handy-lynx-debug.js reads the live machine from window.EMU_BOOT and hands the shared debugger a description of it, with no changes to the core.
Own the loop. The boot script runs its own requestAnimationFrame loop. With no breakpoints set it runs a fixed budget of instructions per frame; once a breakpoint or watchpoint exists it single-steps, checking the program counter before each instruction:
while (budget-- > 0) {
if (breakpoints.has(m.cpu.getPC())) { running = false; break; }
m.step();
if (watchHit) { running = false; break; }
}
Registers. The eight-bit A X Y SP, the 16-bit PC and the packed P are read live each refresh and written back through each register's set(). The N V B D I Z C flags are shown as individual toggles.
Watchpoints. The boot installs a write hook via setWriteHook; a write to a watched address pauses the loop. Memory is exposed as chips over CPU RAM and the framebuffer, all read through peek (side-effect-free). Disassembly uses the shared mos6502 decoder, it aligns 65C02 code correctly because the 6502 immediates are one byte wide (the 65C816 decoder would over-read them).
Architecture
The Atari Lynx (1989) is a colour handheld built around two Atari custom chips. This core models the parts a framebuffer program touches:
- Mikey - contains the 16-bit-addressed 65C02 CPU (a 65SC02-class core, modelled here fully: loads/stores, the full 6502 instruction set plus the 65C02 additions
STZ,BRA,PHX/PLX,(zp),INC/DEC A), the display DMA (DISPADRat$FD94) and the 16-entry colour palette (GREEN0..Fat$FDA0,BLUERED0..Fat$FDB0), giving 12-bit colour (4 bits each of red, green, blue). - Suzy - the sprite/maths engine. This core models only its joystick and switch latches (
$FCB0/$FCB1); it does not emulate the Suzy blitter, so it runs framebuffer homebrew rather than commercial cartridges.
The screen is 160×102 pixels at 4 bits per pixel - two pixels per byte, 80 bytes per line, read from RAM at DISPADR and looked up in the pen palette. Because every component is a plain object, the whole machine is inspectable, steppable and a good debugging target.