The first real deployment surfaced everything a header comment
couldn't hold: SDL's Linux build dependencies (and the XTEST error a
partial set produces), the stale-CMake-cache gotcha after a failed
configure (cargo clean -p sdl3-sys), what sdl-static-headless actually
builds and its --tty-only caveat, the install paths the units expect,
how to watch sessions (systemctl list-units / journalctl), operating
knobs (machine, port, limits, banner), the security posture, and a
troubleshooting section written from the actual failures.
The guide lives next to the units as scripts/systemd/README.md; the
socket header shrinks to a pointer (one source of truth), and the main
README's Experimental bullet and notes/APPLE1.md link to it.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Deploying the telnet Apple 1 hit two gaps. First, the default build
links SDL3 dynamically and sdl-static compiles SDL with full video —
which demands X11 dev packages (XTEST et al) that a headless server
neither has nor needs. The new sdl-static-headless feature stacks
sdl3's build-from-source-unix-console on sdl-static: a static SDL
console build with no X11/Wayland at all. Only --tty runs on such a
binary (documented); it builds in a fraction of the time and links no
dynamic SDL.
Second, ewm-one.socket's header said "install both units" without
saying how much more there is: it now carries the full checklist —
the headless build command, binary/banner/unit installs,
daemon-reload, enable, and where the per-instance logs live.
Verified: the headless binary builds, serves a banner + monitor
session over a pipe, and carries no dynamic SDL reference.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Phase T3 of plans/20260719-04-apple1-telnet.md (added after T2): a
--tty-banner <path> text file prints to the session before the machine
says anything — where the operator puts the instructions a bare Woz
Monitor prompt doesn't offer. Line endings are CRLF-normalized for raw
terminals; a missing file fails loud at startup (a systemd misconfig
should be visible in the journal, not silently banner-less).
The flag is tty-session tooling, not machine description — it lives
with --tty, outside the config document, like the debug flags do.
scripts/systemd/banner.txt ships as the example (monitor and BASIC
starters, the reset chord, how to leave telnet) and the service unit
mounts it from /usr/local/share/ewm — readable under
ProtectSystem=strict.
Test: the banner precedes the machine's prompt, CRLF-normalized, and
the flag without a value falls into usage. Verified live: piped
session shows banner then prompt; a bad path exits 1 with the error.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Phase T2, closing plans/20260719-04-apple1-telnet.md.
The tty session gains a hand-rolled RFC 854 subset: a state-machine
filter that stays dormant until the first inbound IAC — nc and local
terminals never see a protocol byte — then announces WILL ECHO + WILL
SGA (character-at-a-time, remote echo), refuses every other
negotiation, swallows subnegotiations, drops IAC IAC (a literal 0xFF
is not an Apple 1 key), and maps BRK/IP — telnet's `send brk`, the
serial-terminal attention — to the RESET button: immediate, flushing
typed-ahead, like the real thing.
scripts/systemd/ carries the inetd-style units: ewm-one.socket
(ListenStream=6502, Accept=yes, MaxConnectionsPerSource) and
ewm-one@.service (StandardInput=socket, DynamicUser, ProtectSystem=
strict, MemoryMax, RuntimeMaxSec) — the sandbox is tight because the
binary needs no filesystem; configs and ROMs are embedded. README's
Experimental section gains the entry; notes/APPLE1.md records the
tty/telnet design as built; the plan is complete.
Tests: the filter unit-tested byte for byte (activation volley, the
no-reply-to-DO-ECHO loop guard, WONT/DONT refusals, subnegotiation
swallowing, BRK, IAC IAC); a session-level test where a staged reader
delivers the volley, enters BASIC, then BREAKs back to the monitor —
staged because BREAK is deliberately out-of-band. Verified live:
piping an IAC volley plus a command through `ewm one --tty` yields
prompt, WILLs, echo, and the dump in one stream.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
scripts/make-app.sh assembles dist/EWM.app, a self-contained
double-clickable bundle:
- SDL3 is compiled from source and linked statically (a new sdl-static
cargo feature; dev/CI builds keep the Homebrew pkg-config path as the
default feature). Verified: otool -L shows only system frameworks.
- The icon is "][" rendered by the emulator's own Apple ][ character
generator (examples/icon.rs), green phosphor on a dark bezel, drawn at
integer scales per icon size and written as PNGs by a hand-rolled
~40-line PNG encoder (flate2 as a dev-dependency for the zlib stream);
iconutil turns the set into EWM.icns at bundle time, so no binary icon
is committed.
- Info.plist declares CFBundleDocumentTypes for .dsk/.do/.po/.nib/.woz
and .hdv at Alternate rank, ad-hoc codesign for local use (real
signing/notarization is Phase 2).
Opening a disk image from the Finder arrives as an SDL drop event, which
also buys drag-and-drop: the boo menu boots the ][+ with a dropped floppy
in drive 1 (or .hdv on slot 7), and dropping a floppy on a running
machine swaps drive 1. The new media module classifies images -- the one
subtlety is .po, which is a floppy at exactly 143,360 bytes and a ProDOS
hard-drive volume when larger.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* 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>
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>