Search › A-Z › S › SAE - Scripted Amiga Emulator
SAE - Scripted Amiga Emulator
The Scripted Amiga Emulator (SAE), by Rupert Hausberger, is a from-source port of the UAE core to pure JavaScript. Here it runs a Commodore Amiga 500 (Motorola 68000) live in the browser and plugs into the shared in-page debugger.
To keep the boot licence-clean, it uses the open-source AROS Kickstart replacement ROM in place of the Cloanto-copyright Amiga Kickstart. With that ROM and no floppy inserted, the Amiga reaches its animated insert-disk boot screen, a genuine, non-black Amiga picture rendered by the real Agnus / Denise / Copper chain.
The debugger reads and writes the 68000 registers (D0-D7, A0-A7, PC, SR with the X N Z V C flags), disassembles and hex-dumps the 24-bit address space with the shared 68000 decoder, and lets you pause, resume and frame-step the machine. Type on the physical keyboard once the screen has focus, or use the on-screen keyboard on mobile.
Runs on: Web browser
SAE, Scripted Amiga Emulator Online Emulator
Play SAE, Scripted Amiga Emulator using JavaScript directly in your browser.
Controls
Configurations
| Configuration | Emulator | Machine | OS | Legal | |
|---|---|---|---|---|---|
| Amiga 500 (AROS Kickstart) | SAE - Scripted Amiga Emulator | Amiga 500 | open | Open ⛶ |
Machines emulated
Operating systems
Chips
Notes
Embedding
SAE is a set of plain-global JavaScript modules (no bundler). Vendor src/sae/*.js and load them in SAE's own dependency order, construct the emulator, hand it a Kickstart ROM and a <canvas>, then start it:
var sae = new ScriptedAmigaEmulator();
var cfg = sae.getConfig();
sae.setModel(SAEC_Model_A500, 0); // an Amiga 500 (68000, OCS)
cfg.video.id = "saeVideo"; // a <canvas> already in the page
cfg.video.api = SAEC_Config_Video_API_Canvas; // 2D canvas (headless-friendly)
cfg.audio.mode = SAEC_Config_Audio_Mode_Off; // no autoplay gesture needed
cfg.memory.rom.data = arosKickstartBytes; // Uint8Array, 512 KiB AROS ROM
cfg.memory.rom.size = arosKickstartBytes.length;
cfg.memory.extRom.data = arosExtBytes; // AROS extended ROM
cfg.memory.extRom.size = arosExtBytes.length;
sae.start(); // async; hook.event.started fires when live
The machine is plain globals. Once constructed, every part of the Amiga is reachable as an ordinary global, no wasm heap to reach into:
| Member | Kind | What it does |
|---|---|---|
SAER_CPU_regs | field | The live 68000 register file: .d[0..7], .a[0..7], .pc, the supervisor bit .s and the condition flags .x .n .z .v .c (plus .intmask, .t1/.t0). |
SAER_CPU_getPC() | method | The live program counter (accounts for the prefetch offset). |
SAER_Memory_get8(a) | method | Read one byte of the 24-bit 68000 address space through the memory-bank table (chip RAM at $000000, ROM at $F80000 …). |
SAER_Memory_put8(a,v) | method | Write the bus, poke memory live. |
SAER_CPU_run_func() | method | Run the 68000 until the next vertical blank, one video frame of execution. The frame-step primitive. |
sae.pause(b) / sae.reset(h,k) | method | Pause/resume and reset requests (processed by SAE's own loop; SAER.paused reflects the state). |
Because the CPU, bus and RAM are ordinary JavaScript, the debugger reads and writes the registers straight off SAER_CPU_regs, scrubs memory with SAER_Memory_get8, disassembles it with the shared 68000 decoder, and advances a frame at a time with SAER_CPU_run_func - with no changes to the emulator core.
Debugger integration
Wiring SAE into the shared in-browser debugger needed two things: a boot shim that layers a controllable loop over SAE's own loop, and an open, license-clean boot ROM.
1 · A controllable loop over SAE's loop. SAE owns its execution: m68k_cycle() runs one frame via SAER_CPU_run_func() and reschedules itself with setTimeout(…,0), and pause/resume/reset go through an internal command flag. We drive that surface directly so the debugger's transport works:
// pause / resume use SAE's own command path
pause: function(){ if (SAER.running && !SAER.paused) sae.pause(true); },
resume: function(){ if (SAER.running && SAER.paused) sae.pause(false); },
// frame-step: run exactly one frame WITHOUT rescheduling the auto-loop
step: function(n){ while (n-- > 0) SAER_CPU_run_func(); }
Limitation: granularity is a frame, not an instruction. SAE's per-instruction work lives inside a private compiled dispatch loop (runNormal/runPrefetch000) whose locals (the opcode table, the cycle counters, the prefetch) are not reachable from outside, so a true single-instruction step would mean forking the CPU core. The debugger therefore steps a whole video frame at a time via SAER_CPU_run_func(). For the same reason execution breakpoints and watchpoints are surfaced in the UI but are not enforced by SAE's dispatch loop. Registers, memory, disassembly, pause/resume, reset and frame-step are all live and accurate.
2 · An open boot ROM. The original Amiga Kickstart is Cloanto copyright and is not hosted here. The boot instead uses the AROS Kickstart replacement (AROS Public Licence, freely redistributable): a 512 KiB ROM whose CRC32 matches the value SAE's roms.js records for the built-in AROS ROM. With that ROM and no floppy, the Amiga reaches its animated insert-disk boot screen, a genuine, non-black Amiga picture rendered by the real Agnus/Denise/Copper chain.
Techniques for deeper access. The plug-in reaches into the running machine through SAE's globals, with no fork of the core:
- Direct registers.
SAER_CPU_regsholdsd[],a[]and the flag booleans; the status register is assembled from them (T1 T0 S M, the interrupt mask, andX N Z V C), read and written live. - Side-effect-free reads. The hex and disassembly views read with
SAER_Memory_get8but skip the custom-chip and CIA windows ($A00000–$BFFFFF and $D80000–$DFFFFF) so auto-polling the view can never clear a latch or acknowledge an interrupt. - Shared 68000 decoder. Disassembly uses the framework's
m68000decoder pointed at that byte reader, the same big-endian 68000 map SAE itself follows. - Frame-step. Calling
SAER_CPU_run_func()while paused advances exactly one frame and returns at the next vertical blank, without restarting SAE's self-scheduling loop.
Architecture
SAE is a faithful, from-source JavaScript port of the WinUAE/UAE core. Each Amiga chip is its own global object hung off the emulator instance:
cpu.js/m68k.js- the Motorola 68000 (and optional 020/030/040) interpreter and its run loop.SAER_CPU_regsis the register file;SAER_CPU_run_funcruns a frame.memory.js- the 24-bit address-bank table: chip RAM, slow/fast RAM, the Kickstart and extended ROMs, and the custom/CIA I/O windows.custom.js,blitter.js,copper.js,playfield.js- Agnus/Denise: DMA, the blitter, the copper co-processor and the bitplane playfield, rastered into the canvas byvideo.js.cia.js×2, the 8520 CIAs (timers, the keyboard serial line, floppy control, the parallel/serial handshakes).disk.js,audio.js- the floppy subsystem (ADF/DMS) and Paula's four audio channels.roms.js- the ROM database; here it recognises the open AROS Kickstart replacement by CRC32 and maps it at $F80000.
Because it is all ordinary JavaScript with global handles to the CPU, the bus and RAM, SAE makes a rich 68000 debugging target; the whole Amiga is inspectable while it runs.