Phase M2 of plans/20260721-01: a working AppleMouse II card when polled —
config, the device, the firmware, and the machine wiring. No interrupts yet
(Phase M4) and no host input yet (Phase M3); a program that calls the entry
points and reads the screen holes sees a real mouse.
- `ewm/src/mouse.rs`: `mouse_rom(slot)` — synthetic 256-byte firmware
implementing the eight documented entry points (found via the $Cn12 offset
table), moving bytes between the caller's screen holes and the card's
DEVSEL ports; and `Mou`, the `Device` + `Persist` holding position, per-axis
clamp, button (now + last), the moved flag, mode, and the read stream. The
DEVSEL protocol is ours (only our firmware touches it) — documented in the
module header. Identification: Pascal 1.1 ($Cn05=$38/$Cn07=$18/$Cn0B=$01/
$Cn0C=$20) + AppleMouse ($CnFB=$D6), with $Cn01≠$20 so the Autostart scan
skips it.
- Config: `SlotCard::Mouse` ("mouse"), any slot 1–7 (slot 0 already rejects
it), both families, at most one; no files, nothing to resolve. Schemas
regenerated.
- Machine: `SlotDevice::Mouse` wired into new_2plus / new_apple2 / new_2e
(DEVSEL device + $Cn00 firmware ROM); build_machine maps the card; `Two`
holds the handle for M3/M4.
Gates: the golden `mouse_rom(4)` byte test + a moved-slot operand test; the
device driven directly over its ports (Init→Clamp→Pos→Read, clamping); and
`two_mouse.rs` — a ][+ with a slot-4 mouse runs real 6502 through the
firmware entry points and the clamped result lands in the screen holes.
cargo fmt / clippy -D warnings / cargo test (377 pass, golden-BMP intact —
no mouse in those configs).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Emulated Woz Machine
EWM is an emulator for the machines Steve Wozniak built: the Apple 1, the Replica 1 and the Apple ][+ — plus the original and Enhanced Apple //e. It started life many years ago as a tiny 6502 emulator written between christmas and new year, and has since grown into a full emulator with Disk II support, graphics, and sound.
Note: EWM is a hobby project and still under development. Things may be incomplete, quirky, or broken — bug reports and pull requests are welcome. See REWRITE.md for the project's verification gates and a list of known quirks and deliberate divergences.
![]() |
![]() |
| Replica 1 — KRUSADER 1.3 from the Woz Monitor | Apple ][+ — booting the DOS 3.3 sample programs disk |
![]() |
![]() |
| Frogger in color hi-res graphics | Bandits by Sirius Software |
What's emulated
- Apple 1 — 6502, 8KB RAM, Woz Monitor
- Replica 1 — 65C02, 32KB RAM, KRUSADER assembler ROM
- Apple ][ — the original 1978 machine: 6502, 48KB RAM, Integer
BASIC and the non-autostart Monitor in ROM (
builtin:apple2) - Apple ][+ — 6502, 48KB RAM, Apple Language Card, Disk II with two
drives, a slot 7 hard drive for 32MB ProDOS block images (boots
Total Replay!), 40-column text,
low-resolution and high-resolution graphics on a green, amber or white
monochrome monitor or in color (
--set display:monitor=rgb) with an optional scanline effect (--set display:scanlines=light), both switchable at runtime from the command palette, speaker sound, joystick paddles and buttons (game controllers hot-plug — Bluetooth pads connect any time — and the command palette picks between several) - Disk images —
.dsk/.do/.posector images,.nibnibble images, bit-accurate WOZ 1.0 images with copy-protection support (E7, RWTS18, half-tracks, MC3470 weak bits — seenotes/WOZ1.mdfor the compatibility table), and.2mgimages of 400K/800K 3.5" disks in the UniDisk 3.5 Controller ("Liron",{"card": "liron"}), a SmartPort card ProDOS boots from - Apple //e — the original (6502,
builtin:apple2e) and the Enhanced (65C02 + MouseText,builtin:apple2enhanced); 128KB main + auxiliary RAM, the built-in language card and MMU/IOU soft switches, a swappable auxiliary slot (machine.aux): the Extended 80-Column Text Card (64K, default), the plain 80-Column Text Card (1K), or an Applied Engineering RamWorks III with up to 8MB (--set 'machine:aux={"card":"ramworksiii","size":"1m"}'), 40- and 80-column text with lower case (and MouseText on the Enhanced; the original shows inverse upper case there), lo-res / hi-res / double-lo-res / double-hi-res graphics, and the //e keyboard (Open/Solid-Apple keys). Reuses the Disk II, hard drive, clock and sound.
Requirements
- A Rust toolchain (the pinned version is in
rust-toolchain.toml; rustup picks it up automatically) - SDL2 —
brew install sdl2on macOS,apt install libsdl2-devon Debian/Ubuntu
Building
cargo build --release
Running
Running EWM with no arguments opens the bootloader, a menu where keys 1/2/3 select the machine to start:
cargo run --release
Or start a machine directly:
# Apple ][+ with color graphics and the DOS 3.3 sample programs disk
cargo run --release -- two --set display:monitor=rgb \
--set machine:slots:6:drive1=disks/DOS33-SamplePrograms.dsk
# Apple ][+ booting Total Replay from a ProDOS hard drive image
cargo run --release -- two --set display:monitor=rgb \
--set 'machine:slots:7={"card":"harddrive","image":"disks/Total Replay v6.0.1.hdv"}'
# A copy-protected WOZ 1.0 image (bit-accurate Disk II emulation)
cargo run --release -- two --set display:monitor=rgb \
--set "machine:slots:6:drive1=disks/woz/WOZ 1.0/Commando - Disk 1, Side A.woz"
# Enhanced Apple //e (a built-in config) booting DOS 3.3 (try PR#3 for 80-column lower case)
cargo run --release -- two --config builtin:apple2enhanced \
--set machine:slots:6:drive1=disks/DOS33-SystemMaster.dsk
# Enhanced Apple //e with an 8MB RamWorks III in the auxiliary slot
cargo run --release -- two --config builtin:apple2enhanced \
--set machine:aux:card=ramworksiii \
--set machine:slots:6:drive1=disks/DOS33-SystemMaster.dsk
# Replica 1 (Woz Monitor + KRUSADER)
cargo run --release -- one --config builtin:replica1
# Classic Apple 1
cargo run --release -- one --config builtin:apple1
The two machine profiles
Bare ewm two boots builtin:apple2plus — the default machine is
a built-in config, not a separate in-code layout, so ewm two and
ewm two --config builtin:apple2plus build the identical machine: an
Apple ][+ with the 64K Language Card and a Disk II in slot 6, on a
green monochrome monitor.
The built-in machine configs ship inside the binary —
--config builtin:list lists them, and the same files live in
configs/:
builtin:apple2plus — an Apple ][+ with the 64K Language Card and a
Disk II in slot 6, on a green monochrome monitor. The default machine
minus the clock card:
{
"description": "Apple ][+ — 64K Language Card, Disk II in slot 6, green monitor",
"machine": {
"model": "apple2plus",
"slots": {
"0": { "card": "language" },
"6": { "card": "diskii" }
}
},
"display": { "monitor": "green" }
}
cargo run --release -- two --config builtin:apple2plus
builtin:apple2e — the original (unenhanced, 1983) Apple //e: a 6502
with the same layout as builtin:apple2enhanced below (Extended 80-Column
Card, UniDisk 3.5 in slot 5, Disk II in slot 6, RGB monitor), but without the
65C02 and MouseText — the alternate character set shows inverse upper case
where the Enhanced //e shows MouseText:
{
"description": "Apple //e — Extended 80-Column Card, UniDisk 3.5 in slot 5, Disk II in slot 6, RGB monitor",
"machine": {
"model": "apple2e",
"aux": { "card": "ext80col" },
"slots": {
"5": { "card": "liron" },
"6": { "card": "diskii" }
}
},
"display": { "monitor": "rgb" }
}
cargo run --release -- two --config builtin:apple2e
builtin:apple2enhanced — an Enhanced Apple //e with the Extended 80-Column
Card (64K) in the auxiliary slot, a UniDisk 3.5 controller in slot 5,
a Disk II in slot 6, and an RGB color monitor:
{
"description": "Enhanced Apple //e — Extended 80-Column Card, UniDisk 3.5 in slot 5, Disk II in slot 6, RGB monitor",
"machine": {
"model": "apple2enhanced",
"aux": { "card": "ext80col" },
"slots": {
"5": { "card": "liron" },
"6": { "card": "diskii" }
}
},
"display": { "monitor": "rgb" }
}
cargo run --release -- two --config builtin:apple2enhanced
builtin:apple2 — the original 1978 Apple ][: a 48K machine
with Integer BASIC and the non-autostart Monitor in ROM (distinct
from the ][+'s Applesoft/Autostart), no language card, and a Disk II
in slot 6:
{
"description": "Apple ][ — 48K, Integer BASIC, Disk II in slot 6, green monitor",
"machine": {
"model": "apple2",
"slots": {
"6": { "card": "diskii" }
}
},
"display": { "monitor": "green" }
}
cargo run --release -- two --config builtin:apple2
With no Autostart, this machine resets to the Monitor * prompt rather
than booting a disk: Ctrl-B enters Integer BASIC, and a disk in slot
6 boots with C600G (from the Monitor) or PR#6 (from BASIC) — the
1978 experience.
None of the profiles mounts a disk — pair them with a --set override
or an overlay, as in the examples above.
The one machine profiles
ewm one speaks the same configuration language (same sources, same
built-ins mechanism, same --print-config), and its profiles are the
machine's schematic: the CPU, every RAM bank with its size and address,
and every ROM image with its address, all in the JSON. ROM images ship
inside the binary and mount with builtin:<name> in a region's path
— builtin:WozMon, builtin:apple1-basic, builtin:Krusader-1.3-6502,
builtin:Krusader-1.3-65C02 — or from your own ROM file by path. The
only fixed hardware is the PIA (keyboard in, video out) at $D010.
Bare ewm one boots the Replica 1. (The memory maps and ROM
provenance are in notes/APPLE1.md.)
builtin:apple1 — the classic Apple 1: a 6502, 4KB RAM at
$0000, a second 4KB RAM bank at $E000 preloaded with Woz's Integer
BASIC (the real machine loaded it from cassette every power-on; type
E000R at the monitor to start it), and the 256-byte Woz Monitor ROM
at $FF00:
{
"description": "Classic Apple 1 — 6502, 4KB+4KB RAM, Integer BASIC preloaded, Woz Monitor",
"machine": {
"model": "apple1",
"cpu": "6502",
"memory": [
{ "type": "ram", "address": "0x0000", "size": "4k" },
{ "type": "ram", "address": "0xe000", "path": "builtin:apple1-basic" },
{ "type": "rom", "address": "0xff00", "path": "builtin:WozMon" }
]
}
}
cargo run --release -- one --config builtin:apple1
builtin:replica1 — the Replica 1: a 65C02, 32KB RAM, and the
real board's ROM set — Integer BASIC at $E000 and Krusader 1.3 (the
65C02 build, which carries its own monitor page at $FF00) at
$F000. Byte for byte, $E000-$FFFF is the Krusader 1.3
distribution image:
{
"description": "Replica 1 — 65C02, 32KB RAM, Integer BASIC + Krusader 1.3 in ROM",
"machine": {
"model": "replica1",
"cpu": "65C02",
"memory": [
{ "type": "ram", "address": "0x0000", "size": "32k" },
{ "type": "rom", "address": "0xe000", "path": "builtin:apple1-basic" },
{ "type": "rom", "address": "0xf000", "path": "builtin:Krusader-1.3-65C02" }
]
}
}
cargo run --release -- one --config builtin:replica1
When a config names machine.memory, it describes the whole board —
absent, the model's built-in board applies — and validation keeps it
honest: regions must not overlap each other or the PIA, and something
must cover the reset vector ($FFFC-$FFFD) or the machine cannot
boot. Beyond the components, an apple1/replica1 config takes only
the keys that exist on the machine — cpu.strict and debug.trace;
anything else (slots, display, remote, …) is rejected by
validation with an error naming the key.
Composing a machine
--set <key>=<value> overrides one value in the machine configuration
by its colon-separated key path — any key the JSON config (below)
accepts, so --set display:monitor=amber or --set cpu:speed=3.58mhz
work the same way. Values are JSON when they parse as JSON (numbers,
booleans, whole objects like the hard-drive example above) and plain
strings otherwise.
The machine configuration is fully compositional: three source kinds, one document, merged strictly in command-line order —
--config <source>— a complete machine, from a JSON file or a built-in config; at most one, the base of the document;--config-overlay <source>— a partial config layered on top; repeatable;--set <key>=<value>— single-value overrides.
An overlay describes just the part of the machine it cares about. This
one (examples/drive-with-total-replay.json) adds a hard drive with
Total Replay to whatever machine it lands on:
{
"$schema": "https://raw.githubusercontent.com/st3fan/ewm/main/schema/ewm-config-overlay.schema.json",
"machine": {
"slots": {
"7": { "card": "harddrive", "image": "../disks/Total Replay v6.0.1.hdv" }
}
}
}
cargo run --release -- two --config builtin:apple2plus \
--config-overlay examples/drive-with-total-replay.json \
--set display:monitor=amber
Overlays without a --config extend the default machine, so the same
overlay alone means "the default ][+ plus a hard drive in slot 7":
cargo run --release -- two --config-overlay examples/drive-with-total-replay.json
A whole machine also fits in one file (examples/myiie.json):
{
"$schema": "https://raw.githubusercontent.com/st3fan/ewm/main/schema/ewm-config.schema.json",
"machine": {
"model": "apple2enhanced",
"aux": { "card": "ramworksiii", "size": "1m" },
"slots": {
"1": { "card": "thunderclock" },
"5": { "card": "diskii", "drive1": "../disks/work.dsk" },
"6": { "card": "diskii", "drive1": "../disks/DOS33-SystemMaster.dsk" },
"7": { "card": "harddrive", "image": "../disks/Total Replay v6.0.1.hdv" }
}
},
"display": { "monitor": "green", "scanlines": "light" },
"cpu": { "speed": "normal" }
}
cargo run --release -- two --config examples/myiie.json
The machine's physical layout lives in slots: any card in any slot,
up to three Disk ][ controllers (six drives), multiple hard drives,
empty slots — the Autostart scan boots the highest populated slot, as
on hardware. On the ][+, slot 0 is the memory-expansion socket: the
default machine has a Language Card (the classic 64K build), and
--set machine:slots:0:card=saturn128 swaps it for a Saturn Systems
128K RAM Board — eight Language-Card-compatible 16K banks. An explicit
slots table is taken literally, so leave out "0" — or pass
--set machine:slots:0:card=empty — for a stock 48K machine.
Relative paths resolve against their file's directory, so a config — or an overlay — travels with its disks.
A disk image can also be an http(s):// URL, so a config travels
without its disks at all:
cargo run --release -- two \
--set 'machine:slots:6:drive1=https://example.com/disks/Frogger.dsk'
The image is downloaded once into $XDG_CACHE_HOME/ewm (default
~/.cache/ewm), keyed by a hash of the URL and keeping its own file
name. Later boots send a conditional request and reuse the cached copy
on a 304 — and if the server is unreachable, the cached copy is used
anyway, so a downloaded machine still boots offline. Downloaded hard
drives mount memory-only: ProDOS can write to them, but the writes
stay in RAM (exactly as floppies behave) so refreshing the cache can
never destroy them.
The committed JSON Schemas
(schema/ewm-config.schema.json for complete configs,
schema/ewm-config-overlay.schema.json for overlays) give editors
validation and autocomplete via the $schema key;
notes/JSON_CONFIG.md has the full plan.
With sources layering, --print-config answers "what machine did I
just describe?" — it prints the final merged configuration (sources
and convenience flags applied) as JSON and exits, nonzero on any
error, so it doubles as a config linter for scripts and CI:
cargo run --release -- two --config examples/myiie.json --set display:monitor=amber --print-config
Each subcommand accepts --help for all options.
Key commands
| Key | Action | Machines |
|---|---|---|
| Cmd-K | Open or close the Command Palette | all |
| Cmd-R | Reset the machine (warm) | all |
| Cmd-Shift-R | Reboot: power off, power on | ][+ / //e |
| Cmd-P | Pause / unpause | ][+ / //e |
| Cmd-Return | Toggle fullscreen | all |
| Cmd-I | Toggle the status bar with drive lights | ][+ / //e |
While the palette is open it owns the keyboard: type to filter, ↑/↓ to move, Return to run the selected command, Esc (or Cmd-K again) to close. Every other key goes to the emulated machine — including Ctrl-A…Z, which arrive as control characters. Dropping a disk image on a running ][+ / //e window swaps it into drive 1.
There are also headless terminal consoles, handy for quick experiments without a window:
cargo run --release -- one --tty # Woz Monitor / Integer BASIC (Meta-R resets)
cargo run -p ewm --example two -- disks/DOS33-SystemMaster.dsk # AppleSoft / DOS 3.3
Testing
cargo test
This runs the full verification suite: the Klaus Dormann 6502 and 65C02
functional tests, golden instruction traces, headless machine boot tests,
a complete DOS 3.3 boot with CATALOG, and a golden screenshot comparison.
Experimental
Working, but still settling — each has a full working document in
notes/:
- WozBug — a minimal Woz-Monitor-dialect debugger served on a local
TCP port:
--wozbug [port],--break RWTSarms breakpoints at boot, thennc localhost 6502. See DEBUGGING_TOOLS.md. - Remote console (VNC) —
--serve vnc://…(or the configremoteblock) boots headless and serves the machine over RFB to any VNC client, with an optional embedded browser console (noVNC). See REMOTE.md. - Native Mac app —
scripts/make-app.shbuilds a self-contained, double-clickabledist/EWM.app; opening or dropping a disk image on it boots that disk. See MAC_APP.md. - Apple 1 over telnet —
ewm one --ttyis a headless machine whose keyboard and display are stdin/stdout (Meta-R or telnet'ssend brkis the RESET button),--tty-bannergreets callers with a text file of instructions, and inetd-style socket units maketelnet host 6502boot a fresh Apple 1 per connection. Build & server setup guide: scripts/systemd/README.md; design notes: APPLE1.md.
History
EWM was originally written in C and was ported to Rust in 2026 — the full phase-by-phase plan, parity checklist, and benchmark numbers are preserved in REWRITE.md. The original C implementation lives in the git history.
License
MIT, as declared in Cargo.toml (the same license the original C carried
in its source headers).


![Apple ][+ booting DOS 3.3](/Apple-2-Tools/ewm/media/branch/mouse-m2-substrate/screenshots/Screenshot%202026-07-05%20at%2011.30.59.png)

