Commit Graph
166 Commits
Author SHA1 Message Date
Stefan ArentzandClaude Fable 5 7669759f98 Rework the palette around per-activation command registration; Cmd-K
The palette is now generic over an opaque action value: each frontend
registers (label, callback) pairs when the palette is activated, so
labels can reflect the current state (Pause vs Unpause, Enter vs Leave
Full Screen), and executing a command hands the callback back to the
frontend to invoke against its own context. The key moves from Cmd-P
to Cmd-K, the panel height now follows the filtered list, and the
Apple 1 gets the palette too (with a new pause flag). Both machines
register the three real commands: Reset, Pause/Unpause, and
Enter/Leave Full Screen; the Screenshot and Debugger stubs are gone.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 22:11:54 -04:00
Stefan ArentzandClaude Fable 5 8e61f75fdd Add a VS Code-style command palette on Cmd-P
Cmd-P opens a top-centered panel over the ][+ screen listing stub
commands (Screenshot, Reset, Full Screen Toggle, Pause, Debugger);
selecting one logs a not-implemented line for now. The emulator pauses
while the palette is open; Escape or Cmd-P dismisses it and resumes.
Typing filters the list, arrows move the selection, Enter executes.

The panel is drawn with a vendored Inter font (SIL OFL) rasterized by
fontdue at window resolution, so it reads as native chrome rather than
emulated screen. The palette module is SDL-free and unit-tested; the
old Cmd-P pause overlay stays in the code but loses its binding.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 21:28:23 -04:00
Stefan ArentzandClaude Fable 5 fec25cfec8 Add 4 window pixels of padding around the screen contents (#210)
* Add 4 window pixels of padding around the screen contents

Move the canvases from emulated-pixel to window-pixel coordinates and
draw the screen texture at an explicit 3x rect inset by PAD, so the
contents no longer touch the window border. The dst rect stays an exact
integer 3x of the texture, keeping the nearest-neighbor crispness.

In the ][+ this also stops the status-bar mode from stretching the
screen over the bar strip before overdrawing it: the screen rect is now
mode-independent and the bar sits below it inside the side padding.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Make the window padding configurable via EWM_WINDOW_PADDING

Defaults to 4; missing or unparsable values fall back to the default.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 21:04:38 -04:00
Stefan ArentzandClaude Fable 5 f638c535a2 Restore nearest-neighbor texture scaling lost in the SDL3 port (#209)
SDL2 defaulted texture filtering to nearest; SDL3 defaults to linear,
which blurs the low-res screen textures when the renderer scales them
3x to the window. Set ScaleMode::Nearest on every streaming texture to
bring back the crisp pixels.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 20:41:17 -04:00
Stefan ArentzandClaude Fable 5 ccc1968818 Upgrade the pinned Rust toolchain to 1.96.1 (#208)
* Upgrade the pinned Rust toolchain to 1.96.1

rustup picks the version up from rust-toolchain.toml, locally and in CI.
The 1.96 clippy collapsible_match lint fires on the TextInput handlers;
fold the length check into the match guard as it suggests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Remove accidentally committed .DS_Store

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 20:33:14 -04:00
Stefan ArentzandClaude Fable 5 b4fc82a0cc Upgrade the frontend from SDL2 to SDL3 (#207)
* Upgrade the frontend from SDL2 to SDL3

Port the ewm crate from the sdl2 crate (0.38) to sdl3 (0.18.4),
linking against Homebrew SDL3 via pkg-config. Behavior is unchanged.

- sdl.rs: SDL3 removed SDL_RendererInfo, so the texture-format probe
  walks the renderer properties instead; the accelerated check becomes
  a software-renderer name check (the ACCELERATED flag is gone), and
  RGB888 is now spelled XRGB8888.
- snd.rs: the SDL2 AudioQueue becomes an SDL3 audio stream. The
  queued_bytes() backpressure cap replaces AudioQueue::size(), and the
  prime-then-resume order keeps the underrun headroom (SDL3 devices
  start paused). SDL3 has no per-device sample-buffer request.
- boo.rs/one.rs/two.rs: into_canvas() is infallible with no builder,
  set_logical_size() takes a presentation mode, the timer subsystem is
  replaced by sdl3::timer::ticks(), text input is per-window,
  fullscreen is a bool, Keycode::Num1 is Keycode::_1, and the game
  controller subsystem is now gamepads with positional button names
  (A/B/X/Y -> South/East/West/North).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* CI: build on ubuntu-26.04 with libsdl3-dev

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 20:09:27 -04:00
Stefan ArentzandClaude Fable 5 7ef065e9e9 Switch machine reset from Cmd-Esc to Cmd-R (#205)
Cmd-Esc never reaches the emulator on macOS: AppKit offers every
Cmd-combo around as a key equivalent before it can become a keyDown,
and Escape is claimed by Cocoa's cancelOperation: handling — the event
dies before SDL's queue exists. Confirmed with the new --debug key
logging: plain Esc arrives (keycode 27, NOMOD), Cmd-Return arrives
(LGUIMOD), Cmd-Esc produces nothing. The C had the same binding on the
same SDL and was equally broken on macOS.

Reset is now Cmd-R in both frontends; Cmd-letter combos are reliably
delivered (Cmd-I and Cmd-P already depend on that). Plain Escape still
reaches the machine as $1B. The --debug flag now also logs KeyDown
events (keycode/scancode/keymod), which is how this was diagnosed.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 23:00:41 -04:00
Stefan ArentzandClaude Fable 5 e2a681185b Fix speaker clicks: model the AC-coupled speaker (#204)
The speaker clicked during game loading with zero $C030 activity (a
20-second Total Replay boot probe showed toggles only during the
power-on beep). Cause: snd.c's synthesis, ported 1:1, held the output
at a DC rail (±8000 forever after the last toggle), so any queue
underrun — a late frame during loading — snapped the output to silence
and back, an artifact real AC-coupled hardware cannot make. It also
dropped whole frames on backpressure and popped at startup.

The synthesis is now a pure, SDL-free Wave: a toggle jumps the level to
a rail (a single toggle still clicks, like real hardware) and the level
decays toward zero with a ~4ms time constant, like the cone relaxing to
rest. Silence is actually silence, so underruns during silence are
inaudible. The queue is primed with 64ms of zeros at startup, all-silent
frames are skipped when the queue is comfortably full, and frames
carrying signal are never dropped.

Five unit tests cover the waveform (idle silence, click-then-decay,
beep-then-silence — the regression test — sample pacing, square-wave
rails). Documented as a deliberate divergence from snd.c in
notes/REWRITE.md; also fixes the README links to REWRITE.md's new
location under notes/.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 22:28:11 -04:00
Stefan Arentz 3cf0f87959 Add a notes directory 2026-07-05 22:08:31 -04:00
Stefan ArentzandClaude Fable 5 e96d984b53 Add a slot 7 hard drive for ProDOS block images (boots Total Replay) (#203)
New ewm/src/hdd.rs: a virtual hard disk controller card, the device
every emulator adds for 32MB .hdv images. A 256-byte hand-assembled
firmware ROM at $C700 (assembly listing in comments, embedded like
DSK_ROM) speaks the ProDOS block-driver protocol — command/unit/buffer/
block in $42-$47, entry published at $C7FF — and pumps data byte by
byte through magic I/O ports at $C0F0-$C0FF, so blocks move over the
normal bus and the Device never touches machine RAM.

The Autostart ROM's slot scan (verified in 341-0020: it checks
$Cn01/03/05/07 = $20/$00/$03/$3C, slots 7 down to 1) finds the card
before the slot 6 Disk II, so 'ewm two --hdd image.hdv' boots straight
from the hard drive; without --hdd the card does not exist and floppy
boot is unchanged.

Writes persist: committed blocks go to the in-memory image and are
flushed to the .hdv file, so Total Replay preferences and high scores
survive restarts. Read-only files mount with writes failing as WRITE
PROTECTED ($2B).

Tests: port protocol, write persistence to the file, the firmware
driver called ProDOS-style (READ/WRITE/STATUS via JSR $C740), and a
full boot from a synthetic image through the Autostart scan. Total
Replay v6.0.1 verified booting to its menu (472 games detected).

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 21:33:10 -04:00
Stefan ArentzandClaude Fable 5 bc9c345178 Split crates by meaning: ewm-core is the 6502 kernel, ewm is the machines (#202)
ewm-core used to mean "everything headless", which put the Apple wiring
(pia, dsk, alc, chr, one, two) next to the generic CPU. The crates now
split kernel-vs-machine:

- ewm-core keeps only what is common to machines: cpu, mem, ins, fmt,
  plus the CPU-level tests (Dormann, golden trace) and the benches.
- ewm gains a library target beside its binary and owns everything
  Apple-specific: the devices (pia, dsk, alc, chr) and the machines.
  one.rs and two.rs now each hold the machine *and* its SDL loop,
  restoring the original one.c/two.c file layout. main.rs is the thin
  ewm.c-style dispatcher over the library.
- The machine tests (boots, DOS 3.3 + CATALOG, language card) and the
  headless example consoles move to ewm with the machines; the consoles
  run as 'cargo run -p ewm --example ...' (README updated). scr stays
  in the frontend, unchanged.

Pure code motion, no behavior change: all 39 tests pass unchanged.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 20:44:21 -04:00
Stefan ArentzandClaude Fable 5 8e348461ce Restore the C architecture: Machine owns Cpu, Cpu owns Memory (#201)
The port had inverted the C ownership chain: the Cpu held no memory,
and One/Two implemented a Bus trait and pattern-matched addresses to
their peripherals inline. This puts the mem.c design back:

- New ewm-core/src/mem.rs: Memory is the mem.c region table — a
  base-RAM fast path (addr < ram_size) plus RAM/ROM/Device regions
  walked newest-first (the C list-prepend order). Devices implement
  the Device trait (the cpu_add_iom handlers + obj pointer) and get
  the absolute address; typed DeviceHandles give machines access to
  their peripherals afterwards.
- Cpu owns the Memory; step/reset/irq/nmi lose the bus parameter and
  instruction handlers land on the C typedef shapes (fn(&mut Cpu),
  fn(&mut Cpu, u8), fn(&mut Cpu, u16)). Cpu::step stamps mem.cycles
  from counter, replacing the Two.cycles mirror every run loop had to
  maintain.
- Machines shrink to composition, as in ewm_one_init/ewm_two_init:
  One = RAM + ROM + Pia; Two = RAM + TwoIo (the $C000-$C07F soft
  switches, moved out of Two with the state they own) + Alc (which
  now owns the machine ROM it shadows, covering $C08x and
  $D000-$FFFF) + Dsk + the slot 6 boot ROM region.
- The Bus trait and TestBus are gone; CPU tests and benches use a
  flat 64K Memory, which is what cpu_test.c set up.

Zero behavior change: all golden gates pass unchanged (Dormann 6502 +
65C02, the 100k golden trace, machine boots, DOS 3.3 boot + CATALOG,
golden screenshot). One documented alignment: One's --memory extras
now lose to base RAM like the C fast path (REWRITE.md).

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 20:10:38 -04:00
Stefan ArentzandClaude Fable 5 121f03a079 Add the new screenshots to the README (#200)
* Add the new screenshots to the README

The bootloader menu as the hero image, plus a gallery of the Replica 1,
a DOS 3.3 boot, Frogger and Bandits. The old 2016 screenshot the README
pointed at was deleted, so the previous image link was broken.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Escape the ][ in the Apple ][+ image alt text

The unescaped ] closed the image bracket early and broke the
markdown rendering of the gallery table.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 11:46:23 -04:00
Stefan Arentz 652c1a0fd7 Screenshots 2026-07-05 11:38:04 -04:00
Stefan ArentzandClaude Fable 5 774d98a4e7 Rewrite README for the Rust-era project
Fresh structure: what's emulated, requirements, build/run/test
instructions, key bindings, the headless consoles, and an explicit
under-development note. The old C-era goals checklist and the
first-person intro from 2016 are replaced; history points at
REWRITE.md and the git history for the original C.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 11:27:44 -04:00
Stefan ArentzandClaude Fable 5 d05006ec3a Phase 9: remove the C, promote Rust to the root (#199)
* Phase 9: remove the C tree, promote the Rust workspace to the root

Pure git mv / git rm plus the mechanical path fixes:

- Delete src/*.c, src/*.h, src/Makefile, src/CMakeLists.txt, the Lua
  scripts, .travis.yml, and scripts/gen-golden-trace.* (the golden
  trace regeneration needed the C tree; the checked-in golden file is
  now frozen — regenerating requires the C from git history).
- git mv src/rom -> rom and rust/* -> the repo root.
- Fix include_bytes! and test paths for the new layout (src/rom ->
  rom, one directory level less). No other code changes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Phase 9: CI drops the C job; ignore /target

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Phase 9: rewrite README for the Rust build; REWRITE.md complete

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Phase 9: rustfmt after the path shortening

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 11:23:52 -04:00
Stefan ArentzandClaude Fable 5 12acc8c307 Phase 8: parity sweep, benches, docs (#198)
- Benches: cpu_bench.c and mem_bench.c ported as plain std::time
  cargo benches (harness = false, no criterion), same output formats
  so C and Rust numbers compare directly. The mem addressing helpers
  in ins.rs become pub, mirroring mem.h. Numbers recorded in
  REWRITE.md: parity or better on most paths (Rust ADC 2x faster,
  JSR 1.7x faster; ind* modes ~2.5x slower from dyn Bus dispatch —
  all orders of magnitude beyond the 1.023 MHz requirement).
- --trace wired end to end: Cpu grows a trace sink written before
  each step via the fmt.rs formatters ('PC: disassembly  state');
  both frontends open it from --trace[=file]. In C the write path
  was dead code — recorded as a documented divergence. Unit-tested
  against exact expected lines, and smoke-tested (767k lines from a
  3s replica1 run, matching the Woz monitor reset sequence).
- --strict audited: accepted but inert at runtime in both C and Rust
  (the only runtime caller of irq/nmi discards the error) — recorded
  as quirk #7.
- REWRITE.md: flag-by-flag and feature-by-feature parity checklist,
  a new 'Documented divergences' section (exact --flag parsing, no
  PC in unexpected-I/O messages, frame-sampled joystick, working
  --trace, Rust-only --screenshot, the skipped dsk.c OOB write,
  no Lua), bench numbers, Phase 8 -> Done.
- README: 'Building the Rust version' section.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 11:09:17 -04:00
Stefan ArentzandClaude Fable 5 be29afa1b8 Phase 7: Apple ][+ SDL frontend and boo menu (#197)
ewm two and ewm boo run windowed; the frontend is feature-complete
against the C binary (minus Lua, by decision):

- ewm/src/scr.rs: scr.c — TEXT with interleaved rows and flashing,
  LGR 16-color blocks, HGR in green monochrome or color (including
  the fringing fix from #187: adjacent-pixel white detection and
  column-parity coloring), mixed mode, page 2. Rendering is pure
  pixels (no SDL) so the golden screenshot gate runs headless in CI;
  the loop uploads the buffer as a streaming texture. Includes a
  dependency-free BMP encoder for --screenshot and the golden test.
- ewm/src/snd.rs: snd.c (#188) — the core's cycle-stamped $C030
  toggles replay into a square-wave sample buffer queued to SDL,
  with the same 100ms backpressure check.
- ewm/src/two.rs: the SDL loop of two.c — fixed 40 fps step with
  1023000/fps cycle bursts, --drive1/--drive2/--color/--fps/--memory/
  --trace/--strict/--debug, controller buttons and joystick paddles,
  the key mapping (with two.c's TAB fallthrough and alt-keyup-only
  button clearing), Cmd-Esc reset, Cmd-Return fullscreen, Cmd-P pause
  with the tty overlay, Cmd-I status bar with the fake ~1.023 MHz
  display (quirk #3) and drive lights, and the hidden
  --screenshot=<path> debug flag (BMP after 120 frames, then exit).
- ewm/src/boo.rs: boo.c — the bootloader menu; main.rs dispatches
  one/two/boo with no args running boo, as ewm.c does.
- Golden gate: rust/ewm/golden/two-boot.bmp is the System Master
  boot screen rendered after a fixed 100M cycles; the test
  boot_screen_matches_golden_bmp re-renders and compares byte for
  byte (regenerate with EWM_WRITE_GOLDEN=1).
- Core: Two::ram() read access for the renderers, extra --memory
  regions (dispatched after base RAM like the C fast path), and
  Dsk::active_drive() for the drive lights.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 10:54:58 -04:00
Stefan ArentzandClaude Fable 5 5f4299e665 Phase 6: Disk II — DOS 3.3 boots headless (#196)
* Phase 6: Disk II — DOS 3.3 boots headless

Ports dsk.c:

- dsk.rs: the 16-sector slot 6 controller with two drives. Disk
  images (.dsk/.do/.po/.nib) are nibblized to GCR 6-and-2 tracks on
  load: interleave tables, 4-and-4 address fields with volume/track/
  sector/checksum, the 6-and-2 data field with the Beneath Apple DOS
  write table, and per-track gap layout. The embedded 256-byte boot
  ROM lands at $C600; $C0E0-$C0EF handles stepper phases (the
  dsk_phase_delta half-track table, clamped), drive select/on/off,
  and the read/write latch — including dsk.c's skip counter that
  makes every fourth read return 0. Writes only touch the in-memory
  nibble stream and never persist (quirk #2).
- One deliberate divergence, documented in the code: dsk_convert_
  sector in C writes 2 bytes past its 342-byte nibble buffer for
  idx6 = 0x100/0x101 (the apple2js source it derives from discards
  those writes into a JS typed array). Rust bounds checks caught it;
  the store is skipped, matching the effective C/JS behavior.
- Two: bus dispatch for the boot ROM and IOM, plus load_disk()
  (ewm_two_load_disk).
- Tests: nibblizer unit tests (4-and-4 round trip, address/data
  field layout and checksum, track interleave order, the every-
  fourth-read-is-zero quirk, stepper clamping) and tests/two_dos.rs —
  boots DOS33-SystemMaster.dsk to the DOS banner, types CATALOG,
  asserts DISK VOLUME 254 and HELLO.
- The interactive example takes a disk image now:
  cargo run -p ewm-core --example two -- ../disks/DOS33-SystemMaster.dsk

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Fix two_boot for the always-present Disk II

Since Phase 6 the machine always has the Disk II controller, as in
ewm_two_init — so booting without a disk hangs in the slot 6 boot
ROM polling the empty drive, exactly like the C build and the real
machine. The Phase 5 boot test now boots the System Master and
evaluates PRINT 2+2 at the DOS-loaded AppleSoft prompt.

(The local gate had masked this: the test failure disappeared behind
a pipe. Verified with unmasked exit codes now — 31 tests green.)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 10:34:58 -04:00
Stefan ArentzandClaude Fable 5 d12d62d015 Phase 5: headless Apple ][+ machine (#195)
* Phase 5: headless Apple ][+ machine

Ports the machine half of two.c and the language card:

- two.rs: Two implements Bus with the ewm_two_init memory map — 48K
  RAM, the six AppleSoft/Autostart ROMs at $D000-$FFFF (embedded),
  and the full $C000-$C07F soft-switch dispatch: keyboard latch and
  strobe, TEXT/GRAPHICS, LGR/HGR, FULL/MIXED, PAGE1/PAGE2, speaker
  toggle recording at $C030, annunciators, buttons $C060-$C063,
  and the paddle trigger/timers at $C070/$C064-$C065 (including
  two.c's PADL1 never clearing its timer). apple2 and apple2e return
  an error, per quirk #4. Speaker and paddle timestamps come from a
  cycles field the run loop mirrors from cpu.counter, since the C
  handlers read cpu->counter and the Bus trait has no CPU access.
- two.rs: Two::text_screen() decodes the interleaved $0400 text page
  — the workhorse assertion helper for all later headless gates.
- alc.rs: the language card from alc.c as plain state consulted by
  Two's bus dispatch: bank select on bit 3, write-enable only after
  two consecutive switch reads (writes reset the count and the
  $C081/$C083 read cases preserve a previously enabled write flag),
  reads falling through to ROM when read-disabled, and writes
  swallowed when write-disabled.
- tests/two_boot.rs: boots to the APPLE ][ banner and ] prompt with
  no disk card, types PRINT 2+2 through the key latch and asserts 4;
  plus language-card tests for initial state, double-read write
  enable, count reset on switch write, bank separation and shared
  $E000 RAM, cycle-stamped speaker toggles, and the apple2/apple2e
  error.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Add an interactive BASIC console example for the headless Two

cargo run -p ewm-core --example two boots to the AppleSoft prompt and
redraws the 40x24 text screen after each entered line.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 10:18:49 -04:00
Stefan ArentzandClaude Fable 5 908bf92fc1 Phase 4: Apple 1 SDL frontend (#194)
ewm one now runs windowed:

- ewm-core/src/chr.rs: the bitmap half of chr.c — the 2716 character
  ROM (3410036.bin, embedded) decodes to 7x8 glyphs indexed by Apple ][
  screen code, with the same table layout as ewm_chr_init (normal
  $A0-$DF, inverse $00-$3F, flashing-as-inverse $40-$7F). Unit
  tests pin the 'A' glyph, inverse glyphs, and unmapped slots.
- ewm/src/tty.rs: tty.c — 24x40 screen buffer, scrolling, blinking
  cursor, rendered into a 280x192 pixel buffer.
- ewm/src/sdl.rs: sdl.c — pixel-format pick and green phosphor color.
- ewm/src/one.rs: the SDL loop of one.c — 40 fps cycle bursts
  (1023000/40), the same key mapping (including one.c's TAB
  fallthrough that also sends DEL, and ctrl-a mapping to 0x00),
  Cmd-Esc reset, Cmd-Return fullscreen, and --model/--memory/
  --trace/--strict flags. Extra --memory regions are supported by
  new One::add_ram/add_rom (dispatched LIFO like the C mem list).
- ewm/src/main.rs: subcommand dispatch; two/boo report not ported.

The sdl2 crate now uses the use-pkgconfig feature: linking the real
SDL symbols needs the Homebrew/pkg-config library path.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 09:56:51 -04:00
Stefan ArentzandClaude Fable 5 7ed9c6c6ae Phase 3: headless Apple 1 / Replica 1 (#193)
* Phase 3: headless Apple 1 / Replica 1

Ports the machine layer for the One:

- pia.rs: the 6820 PIA from pia.c. The C output callback becomes a
  return value — Pia::write yields (ddr, byte) when an output register
  is written — and reads keep the IRQA1-clear side effect.
- one.rs: One implements Bus with the C memory map (Apple 1: 8K RAM +
  Woz monitor ROM at $FF00 on a 6502; Replica 1: 32K RAM + Krusader
  ROM at $E000 on a 65C02; PIA at $D010). ROMs are embedded with
  include_bytes! so nothing needs to run from src/. Display bytes are
  collected in a drainable buffer, with the Apple 1's 7-bit masking
  applied exactly where ewm_one_pia_callback did. One::key latches
  key | 0x80 into the PIA and raises IRQA1, per ewm_one_keydown.
- tests/one_boot.rs: both machines boot to the Woz monitor prompt;
  FF00.FF0F (Apple 1) and E000.E00F (Replica 1) dumps are verified
  against the actual ROM bytes; typed characters echo back.

utl.c is not ported: ewm_utl_endswith is str::ends_with, and the
clock_gettime shim is obsolete.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Add an interactive console example for the headless One

cargo run -p ewm-core --example one -- [apple1|replica1] gives a
line-based Woz monitor console, so the machine can be tried by hand
before the SDL frontend lands in Phase 4.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Make the example's demo program print HI!

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 09:32:31 -04:00
Stefan ArentzandClaude Fable 5 8c158618d5 Phase 2: 65C02, disassembler, and golden-trace harness (#192)
Completes the CPU core port:

- ins.rs: the 65C02 override handlers ((zp) modes, BRA, STZ, PHX/PHY/
  PLX/PLY, TRB/TSB, RMB/SMB, BBR/BBS, INC/DEC A, BIT imm/zpgx/absx,
  JMP (abs,X)) and the override table transcribed verbatim from
  instructions_65C02 — including its 1-cycle NOPs and the stray 0x03
  opcode fields. The table is overlaid onto the 6502 table once, the
  Rust version of cpu_initialize()'s back-fill. C's arity-cast NOPs
  become explicit nop_byte/nop_word wrappers.
- fmt.rs: cpu_format_state, cpu_format_stack, and the disassembler
  from fmt.c, bugs preserved (0x9c prints JMP; BBR/BBS targets are
  computed from pc+2).
- tests/dormann.rs: dormann_65c02 passes at $24a8 after 65,094,140
  cycles — exactly the C emulator's count.
- Golden-trace harness: scripts/gen-golden-trace.{c,sh} builds a
  capture program against the unmodified C sources and records the
  first 100k instructions of the functional test as normalized
  'PC A X Y SP P' lines; checked in gzipped. trace_compare.rs replays
  it on the Rust core and reports the first divergent instruction
  (currently: zero divergence). flate2 is a dev-dependency only.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 09:16:20 -04:00
Stefan ArentzandClaude Fable 5 880100ac90 Phase 1: 6502 core, passing the Dormann functional test (#191)
Ports cpu.c, mem.c, and the 6502 half of ins.c to rust/ewm-core:

- bus.rs: the Bus trait (replacing the mem_t linked list) and a flat
  64K TestBus for the CPU test harness
- cpu.rs: registers, separate raw-valued flag fields packed/unpacked
  only in status()/set_status(), stack ops with u8 sp wraparound,
  reset/irq/nmi (keeping the pc+1 interrupt push quirk the Dormann
  tests depend on), strict-mode stack overflow checks, and step()
  with pc advanced before handler dispatch
- ins.rs: the 3-variant Handler arity enum, addressing-mode and RMW
  helpers ported expression-for-expression from mem.c (including the
  indx zero-page wrap and the indy non-wrap), ~200 handlers in ins.c
  source order, and the 256-entry table transcribed verbatim
- tests/dormann.rs: the cpu_test.c harness (success PC $3399,
  branch-to-self deadlock detection) plus unit tests for status
  round-trip, stack wraparound, and the indexed-indirect quirks

The Rust core executes 88,957,316 cycles for the functional test —
exactly the C emulator's count.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 09:02:50 -04:00
Stefan ArentzandClaude Fable 5 fbad7d01de Phase 0: Rust workspace scaffolding + CI (#190)
* Phase 0: Rust workspace scaffolding + CI

Adds the parallel Rust workspace under rust/ per REWRITE.md:
- ewm-core (headless lib, zero dependencies) and ewm (binary,
  declares sdl2 now so CI installs SDL2 headers from day one;
  prints the same usage line as the C binary)
- rust-toolchain.toml pinning 1.91.1 with rustfmt + clippy
- GitHub Actions CI: C build job (ubuntu + libsdl2-dev) and Rust
  job (fmt --check, clippy -D warnings, test)
- Anchor the 'ewm' gitignore pattern to src/ so rust/ewm is tracked;
  ignore rust/target/
- README note pointing at REWRITE.md

The C tree in src/ is untouched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* CI: build the C tree on macOS

gcc on ubuntu fails -Werror with -Wimplicit-fallthrough in two.c;
src/ is frozen until Phase 9, so build C where it has always built
(macOS, as Travis did).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* CI: point clang at the Homebrew prefix via CPATH/LIBRARY_PATH

The committed Makefile does not search /opt/homebrew, so SDL2
headers are not found on the arm64 macOS runner. src/ is frozen,
so pass the paths through the environment instead.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 08:42:08 -04:00
Stefan Arentz 7befac8e6d Add phased plan for rewriting EWM in Rust (#189) 2026-07-05 00:51:33 -04:00
Stefan ArentzandClaude Opus 4.5 1c00e6f271 Implement speaker sound support (#188)
Add sound subsystem that emulates the Apple II speaker by:
- Tracking speaker state toggles when $C030 is accessed
- Generating audio samples based on CPU cycle timing
- Outputting 44.1 kHz audio via SDL

New files: snd.h, snd.c

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 13:07:28 -05:00
Stefan ArentzandClaude Opus 4.5 4ad5d458f9 Fix HGR color fringing artifacts (#187)
Replace the 2-bit pairing approach with per-pixel rendering to eliminate
color artifacts at edges. The new implementation processes each pixel
individually, uses each byte's own high bit for palette selection, and
properly detects adjacent pixels for white rendering.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 12:56:34 -05:00
Joe Hildebrand f797e02836 Add small doc improvements for first time users (#182) 2018-01-25 16:38:53 -05:00
Stefan Arentz 71bbe91ac3 Fixes #84 - Unexpected write at $C000 2017-10-15 22:21:56 -04:00
Stefan Arentz ccfa250823 Fixes #154 - Unexpected read at $C020 2017-10-15 22:18:20 -04:00
Stefan Arentz 3168e11814 Fixes #181 - In color mode, text should be white 2017-10-15 21:30:15 -04:00
Stefan Arentz 31ecfb98f5 Fixes #180 - Implement flashing text 2017-10-15 20:01:08 -04:00
Stefan Arentz 6e32724ffa Fixes #179 - Blinking in tty is irregular 2017-10-15 17:02:22 -04:00
Stefan Arentz d2a8386f72 Fixes #178 - Allow the emulator to be paused 2017-10-15 16:40:30 -04:00
Stefan Arentz 7a68e0909b Fixes #175 - Support software rendering in scr (#176) 2017-10-15 15:45:53 -04:00
Stefan Arentz 8a8438d0c2 Fix for link issue 2017-10-03 20:39:37 -04:00
Stefan Arentz cb783a3957 Fixes #174 - Support CMake (Debug vs Release) 2017-10-03 01:28:22 +00:00
Stefan Arentz 95dcce4bba Fixes #174 - Support CMake (Lower CMake version) 2017-10-03 01:19:28 +00:00
Stefan Arentz 3f1d258646 Fixes #174 - Support CMake 2017-10-02 21:12:21 -04:00
Stefan Arentz a28b9180ff Fixes #173 - Crash in scr_test 2017-10-02 20:36:15 -04:00
Stefan Arentz 839ce07e9c Fixes #164 - Make Lua Optional (#165) 2017-10-02 20:32:43 -04:00
Stefan Arentz 9a517223e2 Fixes #171 - Support software rendering in tty (#172) 2017-10-02 20:20:53 -04:00
Stefan Arentz 699072aa86 Fixes #170 - Create a benchmark for tty.c 2017-10-01 11:08:23 -04:00
Stefan Arentz a38375554d Fixes #162 - Introduce a DEBUG compilation mode 2017-09-23 11:57:40 +00:00
Stefan Arentz fd6818e2ab Performance Improvements Part II (#161) 2017-09-23 07:45:36 -04:00
Stefan Arentz ab4c8e025e Update cpu_test.lua 2017-09-22 11:41:51 +00:00
Stefan Arentz a4324737d5 Performance improvements (#160) 2017-09-21 22:18:53 -04:00
Stefan Arentz 58bb3d7739 Fixes #158 - Make the project compile on Raspbian 9.1 (Stretch) (#159) 2017-09-21 21:39:31 -04:00
Stefan Arentz de9212a47f Script for Burger Time cheat 2017-09-20 18:06:30 -04:00