mirror of
https://github.com/st3fan/ewm.git
synced 2026-10-23 19:02:15 +00:00
main
92
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
619b4dd92a |
ROM catalog R6: built-in configs ship as .jsonc; user files may use .json or .jsonc (#344)
The six built-in configs carry comments now (R5), so ship them as configs/*.jsonc — editors keyed off the extension treat them as JSONC instead of flagging the comments. git mv keeps history (all renames R100), and the BUILTINS include_str! paths + the file-reading tests move to .jsonc in lock-step. The loader reads config sources by path and never dispatches on the extension, so a user's --config / --config-overlay file may use .json or .jsonc freely. Add a commented .jsonc fixture and jsonc_and_json_user_files_both_load to pin that (a .jsonc with comments and a plain .json both load). Verified: fmt/clippy clean; full suite green — including the builtin-config load/round-trip tests against the renamed files. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
4aa5364c3d |
Mouse PIA plan P5: docs (notes/MOUSE.md as-built) + cleanup (#336)
Rewrite notes/MOUSE.md as the real-hardware as-built: the 6520 PIA + 6805 controller + banked 342-0270-C ROM, the port-B handshake, the commands, the X=$Cn/Y=$n0 calling convention and the screen-hole layout (Pos/Read slot-indexed; ClampMouse's fixed slot-0 quirk), the interrupt model (][+ works, //e backlog), host input, the EWM integration, and the MousePaint confirmation. Drop the retired synthetic-firmware description and its (now resolved) "Known limitation". Update README and notes/IDEAS.md to describe the mouse as the card's real hardware, and mark plans/20260721-01 (the synthetic card) as superseded by this plan. Docs only; no code change. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
91f3128f59 |
Plan (revised) + the 342-0270-C ROM: real AppleMouse hardware (#331)
The reference implementations turn the mouse-hardware plan from blind reverse-engineering into a port of known-good code. Two sources: oliverschmidt/mouse-interface (MIT) — PIA6520.c + MouseInterfaceCard.c, which document the whole card in their headers — and freitz85/AppleIIMouse, whose 342-0270-C ROM + disassembly this commit lands. Findings that reshape the plan: - The card is a 6520 PIA + a 6805 microcontroller + a 2KB ROM. - The ROM banks into the 256-byte $Cn00 slot area as 8 pages, selected by PIA port B bits 1-3 — NO $C800 expansion (a real simplification over the prior revision's guess). - The 6502 firmware talks to the 6805 over the PIA: port A = data, port B = RDACK/WRREQUEST/RDREADY/WRACK handshake + ROM bank + sync. No timeouts, so a wrong handshake hangs it — exactly today's MousePaint symptom. - The 6805 drives the slot IRQ directly (the PIA's IRQ is unused). plans/20260721-03 rewritten to this reference-grounded design and five phases (P1 PIA + banked ROM, P2 the 6805 + handshake, P3 host input + MousePaint flagship, P4 interrupts, P5 retire the synthetic card + docs). The card works on both ][+ and //e with its own ROM — no family split. notes/MOUSE.md points at the reference + ROM. - roms/Apple Mouse Interface Card ROM - 342-0270-C.bin (2KB, sha1 3a9d881a8a8d30f55b9719aceebbcf717f829d6f), owner-directed fetch. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
7feab74572 |
Plan: real AppleMouse hardware (6821 PIA) for the //e firmware path (#330)
MousePaint hangs on the //e: it runs the //e's internal mouse firmware (INTCXROM=1), which drives the card as its real 6821 PIA ($C0n0-$C0n3), not the synthetic card's $Cn00 entry points. Diagnosed by booting the real disk headlessly (reaches the menu, RETURN ignored, the device touched twice). The synthetic firmware (plans/20260721-01) only covered the card's own entry points — the gap the "no committed MousePaint image" kickoff decision left. - notes/MOUSE.md: a "Known limitation" section recording the diagnosis and pointing at the new plan. - plans/20260721-03-mouse-pia-hardware.md: a six-phase plan to model the card as its real hardware — a 6821 PIA (reusing ewm/src/pia.rs, the Apple 1's) with the mouse mechanism (quadrature X/Y, button, IRQ lines) wired to its ports, so the //e internal firmware drives it. P1 reverse-engineers the wiring (the linchpin); P5 rewrites mouse_rom to drive the PIA for the ][+. Three kickoff decisions (][+ path, flagship gate, PR granularity). Docs only. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
a45b920e8f |
Mouse plan M5: docs + as-built notes/MOUSE.md (#329)
The closer for the AppleMouse II card (plans/20260721-01, now complete). No code — the as-built record and the config surface's documentation. - notes/MOUSE.md: the as-built — synthetic firmware and the identification / screen-hole map, the private DEVSEL protocol, the interrupt model and the reusable maskable-IRQ line (so Mockingboard inherits it), and the coordinate decision (absolute/mapped, revised from the plan's relative/captured). - README: the "What's emulated" list gains the AppleMouse II; a runnable example (examples/mouse.json, a slot-4 mouse //e) checked by readme_examples_parse. - notes/JSON_CONFIG.md: the slot-card inventory gains "mouse" (one, any slot 1-7, both families); notes/STATE.md lists the Mou Persist fields; notes/IDEAS.md ticks the AppleMouse item and points Mockingboard at the shared IRQ line. Gate: readme_examples_parse + standard gates (382 pass). Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
8232da812c |
Backlog: //e Ctrl-Reset should clear the MMU soft switches (#324)
Record the reset-fidelity gap found while testing the original //e: EWM's Ctrl-Reset is only cpu.reset() (CPU registers), but a real //e's RESET line also clears the memory soft switches (80STORE/RAMRD/RAMWRT/ALTZP/INTCXROM/ SLOTC3ROM) before the ROM handler runs — and the ROM relies on that. Measured: after Ctrl-Reset from an all-switches-on state, RAMRD/RAMWRT/ALTZP/INTCXROM persist on both //e, so a program that leaves aux memory active and resets comes back still in aux memory (never happens on hardware). New "Emulation accuracy" section in notes/IDEAS.md with the story, the fix shape (a hardware-reset hook distinct from a program's soft reset through the vector), what to pin against a reference before implementing, and the //e-only scope. No code change here — backlog only. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
9a27b68b2b |
Add builtin:apple2e config + docs (//e original-plan E4) (#321)
The closer for the original //e: a `builtin:apple2e` config so the machine E3 added is reachable by name, and the docs to match. This completes plans/20260720-02. - configs/apple2e.json: model apple2e, aux ext80col, UniDisk 3.5 (Liron) in slot 5, Disk ][ in slot 6, RGB — the same layout as apple2enhanced, on the 6502. Registered in BUILTINS (sorted apple2 < apple2e < apple2enhanced); the builtin list / title / model / self-containment tests extend. - Boot gate: builtin_apple2e_boots_dos33 builds the machine through the config pipeline and boots DOS 3.3 to the Applesoft `]` prompt (the //e autostarts the slot 6 disk; DOS 3.3 runs on the 6502). - Docs: README "What's emulated" folds the original and Enhanced //e into one entry and gains a builtin:apple2e profile; notes/JSON_CONFIG.md model inventory + the aux-validity note cover both //e models; IDEAS.md marks "NMOS //e" landed. Gates: cargo fmt / clippy -D warnings / cargo test (365 pass, incl. the new boot gate and readme_examples_parse with the new builtin:apple2e example). Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
869ba8175e |
Rename builtin:apple2e -> apple2enhanced (//e original-plan E1) (#316)
Our `apple2e` was really the Enhanced //e (65C02 + 342-0303/0304 system ROMs + the 342-0265 MouseText video ROM). Rename the config-level token to the honest `apple2enhanced`, freeing `apple2e` for the true original (unenhanced) //e that E2-E4 add. Mechanical, mirroring the `2plus`->`apple2plus` rename (#308); no ROM or machine changes. What moved (the config surface only): - `config::Model` serde token + `Model::token()`; `git mv configs/apple2e.json -> apple2enhanced.json` with model updated and title made honest ("Apple //e" -> "Enhanced Apple //e", reserving "Apple //e" for E4's original). BUILTINS re-sorts in place (apple2enhanced sorts before apple2plus, so no manual reorder). - The `Persist` INFO model string mirrors the token by design (apple2/apple2plus/...), so it renames too, keeping save-states self-describing as `apple2enhanced`. - Every `"apple2e"` config-token literal in tests/error strings; usage and `--config` help text; README, notes/JSON_CONFIG.md, notes/MAC_APP.md, examples/myiie.json. Both JSON schemas regenerated from source (EWM_UPDATE_SCHEMA=1), not hand-edited. - `builtin:list` column widened 12->16 so the longer name still aligns. Deliberately NOT touched (internal, renamed later by E3): the Rust identifiers `TwoType::Apple2E` / `Model::TwoE`, the `two_e_*` test files, and their "apple2e must construct" panic strings — they name the type, which stays until E3 splits the two machines. Verified: cargo fmt --all --check, cargo clippy --all-targets -D warnings, and cargo test (359 pass, incl. the golden-BMP screenshots and readme_examples_parse). Gate: `builtin:list` shows apple2enhanced; the old `builtin:apple2e` errors "no built-in config ... (available: apple1, apple2, apple2enhanced, apple2plus, replica1)". Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
548040ff7c |
Plan: SKU-keyed ROM catalog (notes/ROMS.md + plan) (#315)
Capture the ROM-organisation idea as a living design note and a phased plan. The note inventories today's roms/ folder — five naming styles, three extensions, an exact duplicate (3410036.bin ≡ AppleII/…341-0036), size-mismatched .rom/.rom.bin Krusader pairs, and two code paths for embedding ROMs (two.rs/chr.rs statics vs config::ROM_BUILTINS) — and proposes SKU-first filenames plus one lookup table SKU → (desc, bytes). The plan sequences it after the original-//e ROMs land so the reorg happens once: R1 folder rename → R2 catalog table → R3 builtin:SKU resolver → R4 optional config migration. Docs only; no code. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
2150f7e0a8 |
Bare ewm two boots builtin:apple2plus (no in-code default machine) (#313)
Owner's decision: the default machine *is* a config, not an in-code layout. When parse_options collects no sources, it now loads builtin:apple2plus, so `ewm two` and `ewm two --config builtin:apple2plus` build the identical machine — code and configs describe the ][+ once, the way `ewm one` already builds its default from the replica1 builtin. Consequences: the old in-code default's Thunderclock in slot 1 is retired (builtin:apple2plus is language@0 + diskii@6, no clock — a stock ][+); and, combined with the title field, the bare-two window now reads "EWM - Apple ][+" (the owner's first computer). The golden-BMP screenshots are unaffected — the Thunderclock never rendered. Two default-pinning tests updated to the new behavior: bare_two_is_the_apple2plus_builtin asserts bare == the builtin, and the print-config round trip checks the title and language card instead of the retired clock. default_slot_cards stays as Two::new's constructor default (the lib/test path, distinct from the CLI default). Docs: the README profiles section and JSON_CONFIG note now say the default is builtin:apple2plus; the config-sources backlog item is marked done. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
2a36ca634f |
Window title from the config: EWM, or EWM - <title> (#312)
The window bar was picked by matching options.model, so an apple2 machine fell into the ][+ arm and showed "Apple ][+". Replace it with a config-driven title: a new top-level `title` field, window reads "EWM - <title>" when set and plain "EWM" otherwise (config::window_title, shared by both frontends). The five built-in configs set it to the machine name: Apple 1, Replica 1, Apple ][, Apple ][+, Apple //e. The old "EWM v0.1 / …" strings are gone; the bootloader window aligns to "EWM - Bootloader". Both `two` and `one` carry the title in Options (from apply_config, and round-tripped by options_to_config so --print-config shows it). It comes from the document only: a bare `ewm two`/`ewm one` stays "EWM" even though one's normalize fills the board from a builtin — so the title tracks what you asked for, not the machine underneath. Tests: window_title formatting, and every built-in carries its machine-name title. Schemas regenerated for the new field. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
c76edb68c3 |
builtin:apple2 config + PR#6 DOS 3.3 boot gate (A3) (#311)
Phase A3 of plans/20260720-01-original-apple2.md, closing the plan. configs/apple2.json is the machine the request started from: a 48K Apple ][ (no slot-0 language card), Integer BASIC, a Disk ][ in slot 6, green monitor — registered in BUILTINS (sorted: apple1, apple2, apple2e, apple2plus, replica1). The self-containment sweep and the builtin-name/list assertions extend to it. Boot gate: builtin:apple2 with the DOS 3.3 System Master in drive 1 resets to the Monitor — asserting it does NOT autostart the disk (a ][+ would) — then C600G runs the slot 6 boot ROM and DOS 3.3 loads (its cold-start banner is the marker). No `]` check: the original ][ has Integer BASIC, not Applesoft, so DOS drops to `>`. Docs: the README What's-emulated list gains the Apple ][; the two profiles section adds builtin:apple2 with its JSON and the C600G/PR#6 boot note; JSON_CONFIG's model inventory lists apple2. Plan complete. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
64dc24f7a5 |
Rename model tokens: 2plus -> apple2plus, 2e -> apple2e (#308)
Spell out the two-family model tokens so every machine reads the same way — apple1, apple2e, apple2plus, replica1 — ahead of adding the original Apple ][ as `apple2` (plans/20260720-01). Pre-1.0, so the old terse tokens go outright: `--set machine:model=2plus` and `--config builtin:2plus` are now apple2plus / apple2e, and an old name errors with the available list. Mechanical and wide: the config::Model serde tokens and Model::token(), the BUILTINS table (renamed, re-sorted, include_str paths), configs/ files (git mv 2plus.json/2e.json -> apple2plus.json/apple2e.json) and their model values, the ewm/tests/configs fixtures, every "2plus"/"2e" string literal in tests and error messages, the examples, both schemas, and the live docs (README, JSON_CONFIG, IDEAS, MAC_APP). Guarded word-boundary replacement so e2e, new_2e and hex escaped it. The builtin:list column widened to fit the 10-char "apple2plus". Verified: fmt, clippy -D warnings, the full suite (37 binaries incl. golden-BMP and the README example check) pass; builtin:list and the old-token error checked against the real binary. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
c9c1a75fb6 |
Disk images over HTTP, cached with ETag revalidation (#306)
* --tty-telnet: proactive echo negotiation (fix telnet double-echo) The telnet echo suppression was reactive — the server only announced WILL ECHO / WILL SGA after the client sent an IAC first. But a classic telnet client sits in line-mode-with-local-echo and waits for the server to negotiate, so the announcement never went out, the client kept echoing locally, and every line appeared twice (its echo plus the machine's own authentic echo). A telnet server that wants remote echo has to offer it proactively on connect. The new --tty-telnet flag (implies --tty) does exactly that: it writes WILL ECHO + WILL SGA before the banner and marks the filter active so the reactive path doesn't double-announce. Bare --tty stays byte-clean for local terminals and nc (kickoff option B); the reactive fallback still helps a plain --tty client that does negotiate first. The systemd @.service switches its ExecStart to --tty-telnet. The growing session-behavior params (throttle, banner, telnet) fold into a TtyConfig struct. Tests: proactive announce leads the stream (before the banner) and doesn't double-announce when the client then sends its own IAC; the flag implies --tty and parses. Verified live: `ewm one --tty-telnet | xxd` shows FF FB 01 FF FB 03 then the prompt. Docs: scripts/systemd/README.md and notes/APPLE1.md explain proactive --tty-telnet vs byte-clean --tty, with troubleshooting for the double-echo and the nc negotiation bytes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Disk images over HTTP, downloaded once into a cache A floppy, hard-drive or 3.5" image path may now be an http(s):// URL: ewm two --set 'machine:slots:6:drive1=https://example.com/Frogger.dsk' ewm/src/fetch.rs downloads it into $XDG_CACHE_HOME/ewm (else ~/.cache/ewm) / <sha1 of the URL> / <file name from the URL>, beside a meta.json holding the ETag and Last-Modified. The digest keys the directory because URLs are not filesystem-safe; the file keeps its own name so the cache is browsable and errors read normally. The hash is the crate's own SHA-1 (ws::sha1) — no dependency for that. Freshness is one *conditional* GET (If-None-Match / If-Modified-Since) rather than HEAD-then-GET: a 304 costs a single round trip. If the request fails and a copy is cached, the cached copy is used, so a machine assembled from URLs still boots offline; a cold cache with no server is a loud error instead. A URL is a source, not a path: resolve_paths leaves it alone (like builtin:), while referenced_files still counts it, so a built-in config may not carry one — builtins must run offline. Downloaded hard drives mount memory-only. This needed a real change, not just leaving the file handle unopened: in hdd.rs an unopenable file means WRITE PROTECTED, which is a different (and still supported) state for a local image. Hdd now carries an explicit `writeback` flag — false for downloads, so ProDOS writes update the in-memory image and never reach the cached file, and revalidating a download cannot destroy them. Floppies already behaved this way. Dependency: ureq (blocking + rustls), owner's decision — the one place the tiny-dependency budget gives way, because TLS is not something to hand-roll. It brings ~25 transitive crates including ring, so the headless server build now compiles native crypto too. Tests: the cache round trip against a real local HTTP server (cold download, conditional revalidation with a 304, offline fallback, and a cold cache with nothing listening failing loudly); URL naming and digest stability including degenerate URLs; memory-only vs write-back block writes at the register level; URLs surviving path resolution; and the wiring gate — a machine builds with DOS 3.3 served over HTTP into drive 1, which only passes if the mount path really fetched. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
75bce7aec8 | --tty-telnet: proactive echo negotiation (fix telnet double-echo) (#305) | ||
|
|
25f59cc451 |
scripts/systemd/README.md: the telnet Apple 1 build & setup guide (#304)
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> |
||
|
|
60a161ecee |
--tty-banner: greet telnet visitors with instructions (T3) (#302)
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> |
||
|
|
3e177a8bb6 |
Telnet negotiation, BREAK reset, systemd units (T2) (#301)
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> |
||
|
|
237955a54a |
Docs: the one profiles as schematics; as-built notes (R4) (#298)
Phase R4, closing plans/20260719-03-one-machine-components.md. The README's one-profiles section now shows what the plan was for: the profile JSON is the machine's schematic — CPU, every RAM bank with size and address, every ROM image with its address — with the mountable builtin: ROM names listed, user ROM paths mentioned, the whole-board semantics and reset-vector rule explained, and the faithful details told in English (BASIC preloaded in the $E000 RAM bank as the cassette would have; the Replica 1's $E000-$FFFF being the Krusader distribution byte for byte). notes/APPLE1.md gains the as-built section (registry, byte-identity gates, from_components, whole-board semantics, the profile decisions); notes/JSON_CONFIG.md's schema inventory adds machine.cpu and the generalized memory regions with the three-way family rules. Plan marked complete. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
c9a16fb616 |
Plan: one machine components — CPU, RAM, and ROMs in the document (#294)
The one-family profiles should describe the machine like the schematics do: machine.cpu, RAM banks with sizes and addresses, ROM images with addresses — embedded in the binary and mounted with builtin:<name> in a memory region's path (or a user file path). The only fixed hardware is the PIA at $D010-$D013. notes/APPLE1.md records the research: the real Apple 1 and Replica 1 memory maps (applefritter ch. 7), and the byte-level answer to the Krusader/BASIC question — they don't overlap; the 8KB Krusader images are BASIC ($E000) + Krusader ($F000) + a per-build modified WozMon ($FF00) side by side. Our krusader.rom is the 6502 build, so today's Replica 1 pairs a 65C02 CPU with the 6502 Krusader — the new profile fixes that. On the real Apple 1, BASIC was cassette-loaded into a 4KB RAM bank at $E000, never ROM — the profile preloads that bank. Four phases (R1 roms/ + registry + builtin: resolution, R2 config keys + overlap/reset-vector validation, R3 One built from components with byte-identity gates, R4 README profiles). Six kickoff decisions listed with recommendations. Status: draft. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
d672893b05 |
Docs: one profiles, JSON_CONFIG as-built, REMOTE Phase 7 note (O5) (#292)
Phase O5, the last phase of plans/20260719-02-one-config.md. The README gains "The one machine profiles" alongside two's — both builtins in English and JSON with their boot commands, a note that bare `ewm one` boots the Replica 1, and the one-family key rules in a sentence. The example check extends to `-- one` commands (readme_examples_parse dispatches on the subcommand; one::parse_options is pub(crate) for it) so one's examples are enforced like two's. JSON_CONFIG.md records the as-built decisions: one document type with a derived family, the cross-check table and its one-place relax point, the shared collect_document parameterization, minimal builtins, boo booting via builtin:, and one's flag retirement. The schema inventory names which keys are apple2-family-only, and the status table gains the one-family row. REMOTE.md's Phase 7 notes that the config surface it wanted now exists — relaxing the remote-block family check is a one-place change. Plan marked complete. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
924557fc7e |
Retire one's flags: --model, --memory, --trace, --strict (O4) (#291)
Phase O4 of plans/20260719-02-one-config.md, same v0.0.1 ruling as plan 20260719-01: the four flags are gone from `ewm one` — they were duplicates of machine.model, machine.memory, debug.trace and cpu.strict, all reachable through the sources O3 added. Retired flags fall into the generic usage error; a test sweeps the list. one's private parse_memory_option goes with them (MemoryOption stays — the config path fills it), and the usage screen shrinks to the source surface, mirroring two's. The boo bootloader now boots the Apple 1 / Replica 1 via `--config builtin:apple1` / `builtin:replica1` (kickoff decision 2 as recommended — the builtins gain a user besides the CLI). README's one examples and REMOTE.md's Phase 7 example speak the config spelling; One::add_ram/add_rom doc comments reference machine.memory instead of the dead flag. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
de6abcd1ce |
Flag retirement: the CLI after config sources (F1–F3) (#286)
* Flag retirement F1: the quiet seven Plan 20260719-01: --scanlines, --fps, --strict, --debug, --boot-delay, --trace/--trace= and --state are gone from `ewm two` — they were single-value duplicates of config keys (display.scanlines, display.fps, cpu.strict, debug.enabled, boot.delay, debug.trace, state.path), all reachable via --set or any config source. Retired flags fall into the generic usage error (owner's ruling: v0.0.1, no transition ceremony); a test sweeps the list and pins the --set spellings. Scanlines::parse went with its only caller (the flag's optional-value peek); MonitorStyle::parse follows in F2 with --color. Docs swept: README's feature blurb, STATE.md and REMOTE.md speak state.path / --set state:path now, IDEAS.md's trace item names debug.trace. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Flag retirement F2: the muscle-memory trio Plan 20260719-01: --model, --color and --aux are gone from `ewm two` — machine.model, display.monitor and machine.aux are config keys, and `--config builtin:2e` is as short as `--model 2e` was. The --model alias spellings (2+, ][+, //e, iie) go with it, as does --color's optional-value peek (bare --color meant rgb) and MonitorStyle::parse, its last caller. The aux token survives internally (Options.aux, rebuilt by apply_config); only the flag is gone. Swept the consumers: main.rs's getting-started hint and subcommand line, the README feature blurbs and quick-start (builtin-first now, enforced by readme_two_examples_parse), JSON_CONFIG.md's schema inventory (sources are config + --set for everything), and the --color/--aux comment references in scr.rs, aux.rs, config.rs and two.rs. `one` keeps its --model — it has no config surface (plan non-goal). Historical notes (APPLE_IIE_ENHANCED, REWRITE, landed IDEAS items) keep their period wording. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Flag retirement F3: --memory Plan 20260719-01, last phase: --memory is gone from `ewm two`; memory regions come from config or overlay files (machine.memory), the one setting --set cannot reach (arrays) — apply_set's error text now says so. two's parse_memory_option went with the flag (one keeps its own copy and its --memory flag: no config surface there, plan non-goal). The print-config round trip composes the region through an overlay now, which is exactly the documented replacement. With this, `ewm two`'s surface is the plan's goal shape: the four sources (--config, --config-overlay, --set, --print-config), --serve, and the debug tooling (--wozbug, --break, hidden --screenshot). Plan marked complete with the kickoff decisions recorded as built. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * README: wrap the quick-start commands; document the three two profiles The "start a machine directly" commands wrap with backslash continuations (the readme_two_examples_parse test joins them, so they stay checked). A new section explains that bare `ewm two` boots the default machine — a ][+ with the 16K Language Card (the classic 64K build), Thunderclock, and Disk II — and documents all three machine profiles in English and JSON: the default (with its config-terms equivalent, printable via --print-config), builtin:2plus, and builtin:2e, each with its explicit `--config builtin:<name>` boot command. The later ready-made-configs paragraph folded into it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * README: brief Experimental section for WozBug, VNC, and the Mac app WozBug's walkthrough and the Native Mac app section fold into a new Experimental section at the bottom — three short bullets, each linking to its working document (notes/DEBUGGING_TOOLS.md, notes/REMOTE.md, notes/MAC_APP.md). The remote console gets its first README mention there; its --serve bullet leaves the source-kinds list, which is back to the three document sources. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * README: drop the cc65 tests/ sentence The C-era assembly test programs are being removed in #284; the Testing section no longer mentions them. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * README: complete Key commands section The keys table becomes a "Key commands" section and gains the missing entries: Cmd-K (the Command Palette, available on every machine) and Cmd-Shift-R (reboot, power off/on), plus a Machines column instead of per-row parentheticals. A short paragraph documents the palette's own keys (type to filter, arrows, Return, Esc), that everything else — including Ctrl-A..Z control characters — goes to the emulated machine, and drag-and-drop disk swapping. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
a3c856aab3 |
Docs sweep: README around the four config sources (C5) (#282)
Phase C5, the last phase of plans/20260718-02-config-sources.md. The README's config section is rewritten around the compositional model — one document, four source kinds merged in command-line order (--config base, --config-overlay layers, --set overrides, convenience flags on top) — with the Total Replay overlay as the worked example and --config builtin:2e in the quick-start. The example configs are now committed under examples/ (myiie.json and drive-with-total-replay.json, shown verbatim in the README), so every example references real files. --print-config closes the section as the "what machine did I just describe?" / linter answer. The gate's scripted check is the readme_two_examples_parse test in two.rs: it extracts every `cargo run --release -- two ...` example from the README (quote-aware, continuation-joining) and runs it through parse_options, which opens every --config/--config-overlay source — so a renamed flag, a bad example path, or an example config that stops validating fails the suite. Notes brought up to date: JSON_CONFIG.md's status table gains the config-sources row (C1-C5 done) and Phase C is marked as seeded by options_to_config; IDEAS.md moves machine config files from "in progress" to "landed" with the remaining palette work named; MAC_APP.md's Phase 3 bullet records that .ewmachine bundles can be a thin layer over the landed source surface. Also fixes the --fps usage text (the default is 40, not 30 — drift spotted during C4). Verified: the README's composed, complete-config, and overlay-only examples run against the real binary from the repo root (exit 0, expected documents); fmt, clippy -D warnings, and the full suite (including the golden-BMP screenshot tests) pass. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
5ee1546db8 |
--print-config: print the machine the command line describes (C4) (#281)
Phase C4 of plans/20260718-02-config-sources.md. With four layering
sources, "what machine did I just describe?" needs a first-class
answer:
ewm two --config builtin:2e \
--config-overlay drive-with-total-replay.json \
--set display:monitor=amber --print-config
prints the final machine — sources *and* convenience flags applied,
i.e. exactly what a real run would build — as config JSON on stdout
and exits 0. Load and validation errors still exit nonzero first, so
the flag doubles as a config linter for scripts and CI.
options_to_config (two.rs) is the one Options→Config mapping, the
inverse of apply_config, kept as a single function so the palette's
"save current setup" (JSON_CONFIG Phase C) can reuse it. The plan's
full-mapping option worked out; the pre-convenience-flags fallback
wasn't needed. --wozbug, --break and the hidden --screenshot are debug
tooling, not machine configuration, and don't appear in the document.
The printed document is explicit where it matters and quiet where it
doesn't: model, slot table, display and cpu settings print even at
their defaults (stable against future default changes), while
off-by-default extras (strict, debug, boot delay, remote) appear only
when enabled. config::compact_document does the shaping — it drops
nulls, empty arrays, and sections that emptied out, but keeps a
genuinely bare "slots": {} table, which means "no cards", not
"default layout".
Verified: e2e tests pin the gate — a command line composed from every
source kind prints a document that, fed back via --config, yields
identical Options (Options/ServeOptions/MemoryOption grew PartialEq
for exactly this) — plus the default machine and bare-table round
trips and the exit codes; fmt, clippy -D warnings, and the full suite
(including the golden-BMP screenshot tests) pass; the real binary was
exercised by hand for the composed and linter cases.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
37f5e788a5 |
--config-overlay: layer partial configs onto the base (C3) (#280)
Phase C3 of plans/20260718-02-config-sources.md, on top of C2's
partial parsing. The machine configuration surface is now
compositional:
ewm two --config builtin:2plus \
--config-overlay drive-with-total-replay.json \
--set display:monitor=amber
--config-overlay <source> is repeatable, and all document sources —
the --config base, overlays, --set — apply strictly in command-line
order through the existing merge. Overlay files load through the
structural-only typed path: the overlay file is named in errors and
its relative paths resolve against its own directory, the same
portability property config files have. The builtin: scheme is shared
with --config (a complete config is a valid overlay), and overlay-only
command lines start from the default machine, like bare --set.
Slots materialization extends to overlays (merge_overlay_document): an
overlay carrying machine.slots onto a slotless document materializes
the default table first, so `--config-overlay hdd7.json` means "the
default machine plus a hard drive in slot 7" rather than a literal
one-slot machine. A base's explicit table stays literal — the plan's
asymmetry — and all four base x overlay slots combinations are pinned
in config.rs tests before the flag wiring, per the plan's hazard note.
--set's materialization is untouched, so the boo launcher's drag-drop
paths behave identically.
Kickoff decision, recorded as built: at most one --config, per the
plan's recommendation — a second errors with "only one --config
allowed; use --config-overlay for additional layers". Two complete
machines silently deep-merging read as an accident once partial layers
had their own flag.
Verified: fmt, clippy -D warnings, the full test suite (including the
golden-BMP screenshot tests), plus the real binary exercised by hand —
the one-config error, the partial-to---config refusal, the overlay
composition, and the known (accepted, documented) edge that
`--config-overlay builtin:2e` over the ][+ default fails the //e
slot-0 completeness check.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
fc90fff5a8 |
Partial configs: relax the parse, split validation (C2) (#279)
Phase C2 of plans/20260718-02-config-sources.md — the enabling change
for --config-overlay (C3), landed separately so C3 stays small.
Config.machine and Machine.model become Option in the serde types, so
any partial fragment ({}, a slots-only overlay) parses. Validation
splits to match: validate() keeps every check a lone fragment can be
judged on (unknown keys, enum values, slot rules, multiplicity), while
the new validate_complete() runs only on the final layered document —
machine.model present, plus the model cross-checks (aux is //e-only,
the //e has no slot 0) that an overlay can't answer until the merged
document names the model.
The loader contract keeps configs and overlays distinct concepts:
load/load_source_document (the --config path) still require
completeness per file — a partial file fails early with "machine.model
is required (is this an overlay? use --config-overlay)" — while
load_document now runs only the structural pass (file named in errors,
relative paths resolved) and is the path overlays will load through.
from_document runs both passes on the merged document and points a
missing model at the fix: "start from --config, e.g. --config
builtin:2plus".
Schema: option (2) from the plan. The generated schema is now
overlay-shaped, so the golden test post-processes the requiredness
(machine, machine.model) back into schema/ewm-config.schema.json and
commits the relaxed variant as schema/ewm-config-overlay.schema.json
with its own title/description, giving overlay files editor support.
Round-tripping the generated schema through serde_json::Value would
lose schemars' canonical keyword ordering, so the test converts back
to schemars::Schema before serializing.
Verified: fmt, clippy -D warnings, and the full test suite (including
the golden-BMP screenshot tests) pass; new tests pin the fragment
round-trip through load_document, the per-path completeness messages,
and the model cross-checks moving to the final-document pass. Behavior
of every existing command line is unchanged.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
aae19c97b8 |
Built-in machine configs: --config builtin:2plus / builtin:2e (C1) (#278)
Phase C1 of plans/20260718-02-config-sources.md. The configs/ files are embedded at compile time (include_str!, a static table in config.rs), so --config builtin:<name> works from any installed binary; names are the schema's model tokens, matching the renamed files 1:1 (configs/2plus.json, configs/2e.json). builtin:list prints names with descriptions and exits like --help; an unknown name errors listing the available names. Built-ins are self-contained by rule: load_builtin rejects any file reference (there is no directory to resolve relative paths against) and a test pins the property, along with every builtin carrying the new top-level description field (schema regenerated) and the table staying sorted. Verified: builtin:2plus/2e produce the same machine as their committed files (unit + integration tests), built-ins layer with --set like any source, builtin:list and unknown-name exits exercised against the real binary, and the full suite (306 tests, golden BMPs included) is green with fmt and clippy clean. Claude-Session: https://claude.ai/code/session_014tBgXL3aSDMgV2CoxTmBQ6 Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
5171ff69de |
Remote console Phase 1: shared overlay compositor (pause box over VNC) (#275)
The prototype ran a parallel headless serve() loop that published the bare scr.frame(), while the SDL loop composited its overlays (drive lights, the pause box) with SDL canvas calls the VNC path never saw. So a machine paused from the web button looked frozen-but-unmarked in a browser, and the drive lights were window-only. This is the deferred Phase 1 debt coming due. Fixed the way the plan discussion concluded: not a Two-owning Driver (both frontends already build the machine identically via power_on_machine, so wrapping ownership buys little), but a shared *pure Vec<u32> compositor* - the piece that was actually duplicated and missing remotely. - overlay.rs (new): Compositor::compose(screen, w, h, &Overlays) -> &[u32] stamps the passive overlays onto the machine's screen frame, SDL-free and unit-tested. Drive lights reuse led.rs; the pause / restored-state box reuses tty.rs, horizontally nearest-scaled to the frame width (identity on the ][+, 2x on the //e). Darkening is the pixel-space stand-in for the SDL alpha-224 black blend. PixelLayout gains unpack_rgb for it. - Both frontends now hold a Compositor: the SDL loop uploads its output to the single screen texture (the separate LED and pause textures, and status_tty, are gone - a net simplification); the serve loop publishes it. - Interactive/window chrome stays per-frontend by design (REMOTE.md §15, now resolved): the command palette is SDL-only UI, the status bar is a strip below the screen, scanlines are an up-scaling effect. Golden-BMP tests still pass - they capture the pre-overlay scr.frame(), so the machine's rendered screen is byte-for-byte unchanged. Verified in a real Chrome tab: pausing over the web now shows the PAUSED box over the darkened DOS screen with the drive lights in the corner, and resuming clears it. fmt/clippy/test green (37 groups; 5 new compositor unit tests). Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
66b338bd78 |
Web console: Reset, Reboot, Pause buttons (#274)
The web page gains machine-control buttons, the first of the operator controls the owned-page architecture was chosen for (notes/VNC.md §2). Reset/Reboot/Pause aren't RFB messages - RFB has no way to pause or power-cycle - so the buttons POST to /control/<action> on the same web port. rfb.rs routes /control/* to an InputEvent::Control on the input channel the serve loop already drains each frame (204 on hit, 404 otherwise), so no new port, protocol, or noVNC change; view-only servers drop control like all other input. The serve loop acts on them: Reset is Ctrl-Reset; Reboot rebuilds from the same options via power_on_machine (cold boot), carrying over the trace sink and restarting the wave/keys, always coming up running; Pause toggles a flag that freezes CPU stepping while the framebuffer keeps publishing, so the frozen screen shows and - reusing the phase gate - the FLASH cursor freezes too. The page tracks its own Pause/Resume label best-effort; Reset/Reboot reset it since a fresh machine runs. Verified in a real Chrome tab against the embedded console: Reboot cold-booted DOS 3.3, Pause froze it at the prompt with a solid (non-blinking) cursor, Resume continued and PRINT 3*4 printed 12; the endpoints answer 204/404 and the reset/pause/reboot actions log server-side. Unit tests cover /control routing and the view-only drop. fmt/clippy/test green (37 groups). Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
c3d0e85b22 |
Cmd-P toggles pause (#272)
The pause toggle existed only as a command-palette row; Cmd-P now flips it directly, alongside the other Cmd shortcuts (Cmd-R reset, Cmd-Return fullscreen, Cmd-I status bar, Cmd-K palette). Same toggle the palette uses, so the palette's Pause/Unpause label stays in sync, the pause box (and the restored-from-state banner) appear exactly as before, and the FLASH-freeze behavior applies. Comments and notes/STATE.md now name Cmd-P as the way to set a restored machine going. fmt/clippy/test green (37 groups). The binding itself lives in the SDL event loop, so the eyes-on check: Cmd-P pauses, Cmd-P resumes. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
b3aa295f5e |
Start paused when restored from a saved state, naming the save (#270)
A machine restored via --state now starts paused in the SDL frontend, so it does not run off without you: the pause screen shows RESTORED FROM SAVE STATE and the save's local time (YYYY-MM-DD HH:MM:SS), taken from the state file's mtime - which the atomic write-then-rename stamps at the moment of saving, so no format change or version bump. The first unpause (command palette, which already reads Unpause) dismisses the banner; later manual pauses show the plain PAUSED box. Headless serve has no pause UI, so a restored machine runs immediately there - noted in notes/STATE.md §6 alongside the behavior. The banner timestamp line is centered in the box's 26 inner columns and unit-tested to be exactly the TTY's 40 columns, like every other pause-box line. fmt/clippy/test green (150 lib tests, 37 groups). Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
973e06f187 |
state S5: the determinism gate; plan and notes brought up to date
The state analogue of the golden-BMP tests (notes/STATE.md §8), run at the harshest moment: save mid-boot - motor on, arm seeking, DOS half loaded - restore into a fresh twin, then run BOTH machines millions of further cycles comparing text screens, cycle counters and PC along the way, finishing with pixel-level equality through the pure renderer. Any component that forgot a field diverges the trajectories and fails this loudly. It passes, and both machines finish booting to identical screens. Docs: the plan's phase table is all Done; notes/STATE.md gains the as-built notes (S0's stub never existed - the CLI landed inside S4; the INFO model seatbelt shipped early; Clk/Pia save slightly more than planned; the gate saves mid-boot); notes/REMOTE.md points out that --state gives the headless VM fleet suspend/resume for free. Completes plans/20260718-01-machine-state.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
fc48f10444 |
Plan: machine state save/restore (--state), and the plan-driven workflow (#268)
* Add notes/STATE.md: the machine-state save/restore plan (--state) Plans suspend/resume: --state=/path restores at startup and saves at quit. Core design: a Persist trait made a supertrait of Device and AuxCard, so every component keeps its store/load code local and the compiler forces new cards to answer the persistence question; the existing ownership tree (Two -> Cpu -> Memory -> devices) is the serialization tree; config builds the machine, state overlays it (same-config precondition, mismatch detection in the backlog); a hand-rolled LE chunk container in the house style. Includes the component inventory (what saves, what pointedly does not - floppy media is in, block-device media is not), cycle-stamp and quiescence rules, the determinism gate, and a five-phase plan. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Adopt plan-driven workflow: plans/ directory and project CLAUDE.md Work now flows idea -> notes/ design doc -> phased plan in plans/YYYYMMDD-NN-slug.md -> pull requests (per phase or whole plan, the user's call per plan). CLAUDE.md records the methodology, the standing gates, and the house style; plans/20260718-01-machine-state.md is the first plan, phasing the notes/STATE.md design into S0-S5. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
979e536117 |
Remote console: speaker audio in the browser (WebAudio side-channel, notes/VNC.md A1+A2) (#266)
* Add notes/VNC.md: the browser console as built + the WebAudio plan Describes the embedded noVNC setup (Phases 4-5) and plans speaker audio in the browser as a WebAudio side-channel: a /audio route on the existing WebSocket port streaming Wave's 44.1 kHz mono i16 PCM, played by an AudioWorklet in EWM's own console page. No noVNC fork needed - the engine is consumed as a library (core/rfb.js) inside a page we own, so buttons and audio live beside the RFB canvas, never inside noVNC. REMOTE.md §9 and Track B2 now point here. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Remote console: speaker audio in the browser (WebAudio side-channel) RFB has no audio channel, so the speaker was dropped for every remote client. Implements notes/VNC.md phases A1+A2: the WebSocket port gains an /audio route that streams the speaker's PCM, and the embedded console page plays it — the beep is back, in the browser, with no noVNC changes (the engine stays a vendored library; audio lives beside it in our page). Server (A1): - snd.rs: Wave (the SDL-free toggles->PCM synthesizer) goes pub, with set_cpu_frequency moved onto it so the headless path pitches up under acceleration exactly like the SDL path. - audio.rs (new): a broadcast Hub in the frame-Publisher shape. The serve loop renders each frame's toggles through Wave and publishes; every /audio client has a small bounded queue (~200 ms) drained by its own writer thread, with drop-oldest backpressure so a slow tab loses stale chunks instead of accumulating latency. Wire format: one JSON text frame ({"format":"s16le","rate":44100,"channels":1}), then binary frames of raw little-endian mono i16 (~88 KB/s per listener, no compression, no deps). - rfb.rs: a WebSocket upgrade whose path is /audio attaches to the hub; everything else is unchanged (RFB on the default path, console page on plain GET). ws.rs gains WsWriter::write_text for the header. Page (A2): - audio.js + audio-worklet.js (EWM's own files, embedded like the rest): /audio socket -> Int16->Float32 -> AudioWorklet ring buffer -> speakers, 44.1 kHz context. Browsers refuse audio before a user gesture, so the first click/keypress (the one that focuses the screen anyway) arms the pipeline; a speaker button in the status bar toggles mute. Underrun plays silence, which the decay-to-silence speaker model was converging to anyway. window.ewmAudio exposes live counters for debugging. Verified: a scripted /audio client heard PRINT CHR$(7) at peak amplitude 8000 (the full speaker rail) among 194 streamed chunks; in a real Chrome tab the console page's pipeline armed on gesture, streamed continuously, and the in-page stats flipped heardSignal on a fresh bell. New tests: hub framing + drop-oldest, attach round-trip over loopback, and the rfb route test (audio + RFB + console on one port). fmt/clippy/test green (149 lib tests). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
859309fdd9 |
Remote console Phase 5: embedded web console (vendored noVNC, optional web server) (#265)
* Vendor the noVNC engine v1.6.0 (core/ + vendor/pako) for the web console The pinned noVNC client engine and its one dependency, copied unmodified from the v1.6.0 release (MPL-2.0; LICENSE.txt vendored alongside), plus EWM's own minimal console page (index.html) that loads core/rfb.js and connects back to the host and port it was served from. The full app/ UI chrome (~800 KB of settings panels, images, and locales) is deliberately not vendored. See ewm/novnc/README.md for the update procedure. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Remote console Phase 5: embedded web console served from the binary http://host:PORT/ is now a live Apple II in a browser tab with no external tooling: a small built-in HTTP handler serves the vendored noVNC console on the WebSocket port, and the page connects back to the same host:port with the RFB WebSocket upgrade — one port carries both, the Proxmox shape. Strictly opt-in tiers, per the design note: --serve vnc://... is native VNC only; ?ws=PORT adds the raw RFB-over-WebSocket listener (bring-your-own noVNC; plain HTTP still answers 426); ?web=PORT additionally serves the embedded console (web=PORT is sugar for web&ws=PORT; bare ?web defaults the port to 5701). Config equivalent: remote.websocket + remote.web. Bind stays the single remote.bind for all listeners. - build.rs: embeds the novnc/ tree as a generated (path, bytes) table of include_bytes! entries — no embed crate, no runtime filesystem access. - web.rs: static-table asset lookup (literal keys only, so no traversal; query strings ignored; "/" -> index.html) and a minimal one-shot HTTP responder (200 with content-type/length, 404, Connection: close). - ws.rs: upgrade() split into read_http_request / accept_upgrade / refuse_plain_http so the connection handler can branch. - rfb.rs: the WebSocket listener's Transport carries the web flag; plain HTTP requests route to the console when enabled, 426 otherwise. Verified in a real Chrome tab: http://127.0.0.1:5701/ served the console from the binary, booted the DOS 3.3 System Master, and CATALOG typed at browser speed listed the disk. fmt/clippy/test green (145 lib tests, up from 140: 3 web unit tests + 2 console integration tests). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
b48f635d85 |
Re-land Phase 4 (WebSocket transport + keyboard pacing) onto remote-console (#264)
* Remote console Phase 4: embedded WebSocket transport, noVNC connects directly Browsers cannot open raw TCP, so noVNC speaks RFB-over-WebSocket. This adds an optional second listener that serves the identical RFB byte stream inside WebSocket binary frames — no websockify sidecar, no new dependencies. - ws.rs: a hand-rolled WebSocket (RFC 6455) server transport in the des.rs spirit, instead of the plan's tungstenite (which would add ~9 transitive crates; kept as documented fallback). HTTP upgrade with from-scratch SHA-1 + base64 (RFC-vector-tested, incl. the RFC 6455 accept-key example), binary frame encode/decode across all three length encodings, client-mask enforcement, ping->pong, close echo, subprotocol echo for older noVNC. Exposed as io::Read/io::Write adapters (WsReader/WsWriter). - rfb.rs: the connection handling is now generic over Read/Write, so the RFB state machine (handshake, VNC auth, input, framebuffer) is shared verbatim between plain TCP and WebSocket; Server::start takes an Options struct and binds both listeners, feeding one machine. noVNC's SetPixelFormat request (little-endian, shifts 0/8/16) is byte-identical on the wire to our advertised big-endian 24/16/8, so keeping our format remains safe. - config/CLI: remote.websocket config field and ?ws=5701 on the --serve URL (bare ?ws defaults to 5701); validation rejects port 0 and a ws port equal to the RFB port; schema regenerated. Verified: a from-scratch Python WebSocket client (accept key checked against hashlib's reference SHA-1) booted DOS 3.3 through the WS port and computed PRINT 640*1.6 -> 1024; plain TCP verified in parallel on the same machine. New tests: 6 ws unit tests, a full WS-transport RFB session test, and parse_serve URL tests. fmt/clippy/test green (139 lib tests). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Remote console: pace remote key input through the one-byte keyboard latch The Phase 4 browser gate (stock noVNC in Chrome) caught a real bug: a browser delivers a whole typed word within a single frame, and the serve loop latched every pending key back-to-back with no CPU cycles in between. The Apple II keyboard is a one-byte latch, so each byte overwrote the previous before the ROM could read it — typing CATALOG arrived as just "G". Scripted clients had masked this by sleeping between keystrokes. Fix: translated key bytes now land in a queue (RemoteKeys), and the frame loop pumps at most one byte per frame, only after the ROM has consumed the previous one (keyboard strobe cleared via $C010). Reset and pointer buttons still apply immediately; the queue is bounded against flooding. Free side effect: correct type-ahead while the machine is busy booting. Verified in a real Chrome tab via noVNC over the WebSocket transport: booted DOS 3.3, typed PRINT "HELLO FROM CHROME" at browser speed, every character arrived; a zero-delay scripted burst (PRINT 6*7 -> 42) passed as a second concurrent viewer of the same machine. New unit test drives the latch/strobe pacing against a real machine. fmt/clippy/test green (140 lib tests). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
f2544dde19 |
Remote console: headless VNC server for the Apple ][+ / //e (prototype) (#262)
* Remote console prototype: headless VNC server for `two` (notes/REMOTE.md) A first working prototype of the remote-console plan, collapsing Phases 0/2/3 so an EWM machine can be booted headless and reached with a native VNC client. - config: new `remote` block (protocol/bind/port/view_only) with validation (rejects reserved "rdp" and port 0); schema regenerated. - rfb.rs: hand-rolled RFB 3.8/3.7/3.3 server on std::net in the wozbug shape — security None, Raw encoding, one handler thread per client (several viewers of one machine), mpsc input back to the emulator, big-endian-RGBA frames via u32::to_be_bytes (no per-pixel conversion). Unit-tested: full handshake + first FramebufferUpdate decode, key/pointer delivery, view-only. - two.rs: `--serve vnc://host:port` parsing and a headless `serve()` loop — the SDL frame loop's shape without SDL, reusing build_machine() and the pure Scr renderer. Keysym->byte translation mirrors the SDL keyboard table (printable, arrows, Return, ESC, Tab/Del, Ctrl-letter, Ctrl+F12 reset). The SDL path is left byte-for-byte unchanged (a parallel headless path rather than Phase 1's full Driver refactor, which is deferred — see the prototype note in notes/REMOTE.md). Verified end-to-end: booted DOS 3.3 and bare Applesoft over vnc://, typed PRINT 2+2 and saw 4. fmt/clippy/test all green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Remote console: VNC authentication (DES challenge) so macOS clients connect macOS Screen Sharing refuses the RFB "None" security type and always prompts for a password, so the None-only prototype could not be used from the built-in client. Add standard VNC authentication (the RFB DES challenge, RFC 6143 §7.2.2), gated on a password. - des.rs: a small from-scratch DES (FIPS 46-3, ECB, encrypt-only) — the project hand-rolls its primitives and keeps deps tiny. Tested against the textbook and all-zero vectors, and cross-checked end-to-end against OpenSSL's DES (an OpenSSL-computed auth response is accepted by the server). - rfb.rs: offer security type VNC (2) with the DES challenge when a password is set, else None (1) as before; SecurityResult handling per 3.3/3.7/3.8. New tests cover auth accept and reject. - config/CLI: `remote.password` config field and a `?password=` (and `?view_only=`) query on the --serve URL; --serve now merges onto config- provided serve options so a config password survives an explicit --serve. Verified: macOS-style VNC auth handshake accepted with the right password and rejected with the wrong one, then booted Applesoft and typed PRINT 2+2 over an authenticated session. fmt/clippy/test (129 lib tests) all green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * REMOTE.md: record the actual branch layout (remote-console integration branch) The integration branch is 'remote-console' (cut from main); work branches like 'claude/remote-console' PR into it, and one final PR promotes it to main. PR #262 is the first such sub-phase PR. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
309b371b46 |
Add the remote-console (VNC/RDP) plan (notes/REMOTE.md) (#260)
A working document, in the style of APPLE_IIE_ENHANCED.md and JSON_CONFIG.md, for booting EWM machines headless as network-reachable "VMs" — one machine per process, each on its own port, connectable from a native VNC client and from a web page like the Proxmox console. The plan picks RFB (VNC) over RDP as the v1 protocol and explains why: a minimal Raw-encoding RFB server is a few hundred lines of blocking std::net (the wozbug.rs embedded-server pattern), the pure Scr/Tty renderers already hand back exactly the Vec<u32> framebuffer such a server needs, and noVNC gives the browser console for free. It documents the accelerated-renderer constraint that rules out the Xvfb + x11vnc route, a frontend-agnostic driver refactor that keeps the SDL path byte-for-byte unchanged, RFB keysym -> two.key() input mapping, an embedded WebSocket transport so noVNC connects with no websockify, multi-VM orchestration, security posture, and RDP (ironrdp) as an optional later track. Sliced into PR-sized phases with per-phase verification gates. Claude-Session: https://claude.ai/code/session_01W9SGzucxQrUm3mPrYCHziz Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
185f64895f |
Emulate the UniDisk 3.5 Controller (Liron) with .2mg images (liron.rs) (#259)
The SmartPort card that put 800K 3.5" drives on the Apple II, as
virtual hardware in the hdd.rs style: hand-assembled $Cn00 firmware
over magic DEVSEL ports, no IWM emulation. Two drives, .2mg images of
exactly 400K or 800K, ProDOS order; the 2IMG locked flag mounts
read-only and writes land back in the file at data_offset, header
preserved.
The firmware carries a real SmartPort identity - signature $Cn07=$00,
ID type at $CnFB, ProDOS entry via $CnFF with the SmartPort dispatch
at entry+3 - and implements both protocols: the ProDOS block driver
with the drive bit in $43, and a SmartPort dispatch (the full
JSR-with-inline-bytes convention, return address fixed up past the
command and parameter-list bytes) serving STATUS, READ_BLOCK and
WRITE_BLOCK, with block calls translated onto the ProDOS pump.
Squeezing two protocols into one 256-byte page took a tiny two-pass
assembler (Asm) instead of hdd_rom's hand-counted offsets, plus a few
documented economies: the third SmartPort block byte is ignored (800K
is 1600 blocks), $45 is not preserved across transfers, and empty
drives report 0 blocks from ProDOS STATUS.
The boot test settled the plan's open question: the Enhanced //e
Autostart scan accepts the SmartPort signature, so ProDOS 2.4.3 boots
to Bitsy Bye from an 800K .2mg in slot 5 through this firmware.
Config: {"card": "liron", "drive1": ..., "drive2": ...}, any slot 1-7;
configs/enhanced.json now carries one in slot 5. WozBug SLOTS shows
the drives: S5: UniDisk 3.5 (D1 800K, D2 -).
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
9fad92a5ed |
Emulate the Saturn Systems 128K RAM Board in slot 0 (saturn.rs) (#257)
The classic third-party alternative to the Apple Language Card, per
the Saturn Operations Manual ch. 9: 128K as eight 16K banks that all
live at $D000-$FFFF, each bank two 4K banks (A/B) at $D000 plus its
own 8K. Bank 1 speaks the exact Language Card protocol, which is the
card's compatibility story - DOS 3.3, Pascal and VisiCalc treat it as
a 16K card and never notice the other seven banks.
The $C08x decode: A2=0 is the Language Card state machine (A3 now the
4K bank, A0/A1 read/write enables, copied from alc.rs including the
read-twice write-enable - the manual's "PEEK or POKE" prose is looser,
but the LC-compatible semantics are what software relies on); A2=1
selects the 16K bank ($C084-7 are banks 1-4, $C08C-F banks 5-8), with
the board state persisting across switches. Power-up: ROM read, write
protect, bank 1.
The slot-0 plumbing grows from a language_card bool into two::Slot0
(Language / Saturn128 / Empty), mirroring how SlotDevice mirrors the
config cards. The config gains {"card": "saturn128"}, slot 0 only,
][+ only, same rules as the language card; WozBug's SLOTS shows
S0: Saturn 128K (bank n).
Tested: all sixteen $C08x offsets against the manual's table, eight
banks holding independent contents on the bus with the A/B split at
$D000 and the shared per-bank 8K, and the compatibility claim end to
end - DOS 3.3 boots, prints (LOADING INTEGER INTO LANGUAGE CARD),
and INT/FP swap between Integer BASIC in Saturn bank 1 and Applesoft
in ROM.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
7ccd4b369d |
Model slot 0: the ][+ Language Card becomes a configurable card (#256)
The Alc was hardwired into Two::new_2plus, so every ][+ was a 64K
machine and configs/plus.json could not even mention the card it was
supposed to demonstrate. Slot 0 now lives in the machine.slots table
(owner's decision — hardware-faithful, keyed "0"), restricted to
"language" or "empty": the socket has no $Cn00 firmware space, so
nothing else can work there, and the //e rejects "0" outright — its
language card is soldered onto the motherboard.
The literal-table rule now covers slot 0, a deliberate breaking change:
a ][+ config whose slots table omits "0" is a stock 48K machine, with
the motherboard ROM directly at $D000-$FFFF and slot 0's DEVSEL range
as unmapped as any other empty slot's. The default table (and the
--set materialization) gains "0": {"card": "language"}, so bare
command lines keep the classic 64K build and
--set machine:slots:0:card=empty
is the 48K opt-out. configs/plus.json now declares the card.
Slot 0 never becomes a SlotDevice: build_machine consumes it as a
language_card flag on Two::new_with_slots, and new_2plus either wires
the Alc as before or maps the ROM straight onto the bus.
Two::language_card() reports the socket and WozBug's SLOTS shows an
S0 line on the ][+. A guard catches the --set plus --model 2e path
that bypasses config validation.
Tested: the $C083/$C083 write-enable sequence changes $D000 with the
card and bounces off ROM without it, and DOS 3.3 boots to the prompt
on the 48K machine — it just skips loading Integer BASIC.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
f29d2e9e13 |
WozBug: a resident Woz-Monitor-style debugger with breakpoints, watchpoints, and a TCP line server (#254)
* Add the debugging-tools plan (notes/DEBUGGING_TOOLS.md) WozBug: a MicroBug-inspired (TN1136), Woz-Monitor-dialect minimal debugger — one command core with test, TCP-line-server, and breakpoint-pause frontends — plus CPU breakpoints, device-state commands, and a built-in symbol table. Grounded in what the PR #253 RWTS hunt actually required. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * WozBug phase 1: CPU breakpoints and the monitor command core Cpu gains PC breakpoints (add/remove/stopped/stop/resume): a hit makes step() return 0 without executing until resume(), which skips the breakpoint at the current PC once so stepping off a hit makes progress. The empty-breakpoint fast path is one always-false branch — the Dormann suite times identically (0.19s release) before and after, so this lives in the normal build with no debug split. ewm/src/wozbug.rs is the command core: MicroBug in role (TN1136), Woz Monitor in dialect. 280.29F dumps (a bare Return continues from the dot address), 300:A9 20 deposits, R / PC=BD00 / A=FF registers, S [n] stepping with trace lines, G / 300G, B breakpoints — plus the machine-aware commands DSK, SW, TEXT, and SLOTS, and a built-in symbol table for Monitor/DOS/ProDOS landmarks (B RWTS works; R prints PC=BD25 (RWTS+$25)). Dumps read through the bus, soft-switch side effects included, as on real hardware. ewm/tests/wozbug.rs replays the PR #253 hunt with tools instead of temporary code: boot DOS 3.3, B RWTS, CATALOG lands on the breakpoint, R names the site, DSK shows the controller, Y/A -> IOB verified, S/G resume cleanly to the catalog. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * WozBug phase 2: the --wozbug line server and --break flag `ewm two --wozbug [port]` (default 6502) serves WozBug on a local TCP port, one nc-friendly client at a time; `--break addr[,addr]` arms breakpoints at boot — hex or built-in symbols (--break RWTS) — and implies the server. The server threads only move strings over channels: the frame loop drains commands and executes them on the machine's own thread between frames, so there is no locking and an idle server costs one try_recv per loop. The frame burst breaks on a 0-cycle step, and a rising stopped edge announces "stopped at ..." + registers to both the client and stderr. Verified live: --break RWTS with the DOS 3.3 disk, nc connect receives the stop announcement, R shows the IOB pointer in Y/A, S 3 traces RWTS's first instructions, G resumes the boot. README gains a "Debugging with WozBug" section. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * WozBug Phase 3: watchpoints, trace toggle, and disassembly Watchpoints live in Memory as inclusive address ranges. When any are set, reads and writes record the first watched access per instruction (WatchHit: address, direction, value); the CPU checks after each instruction completes and stops post-instruction, so registers show the state right after the touching access. Opcode/operand fetches count as reads; the trace formatter's peeks are excluded by clearing the hit after the trace block. With no watchpoints armed the recording paths are gated on an empty-vec check: the Dormann suite measures 0.19s release either way, and 0.21s with a watchpoint armed, so the feature stays in the normal build. New WozBug commands: W list watchpoints W 2000.3FFF watch a range (W 400 for one byte) W-2000.3FFF clear one, W- clears all T / T- CPU trace to stderr on/off L [addr] disassemble 16 instructions, plain L continues The stop banner now names the access that fired: watch: write 0427 = C1. Dialect fix recorded as a regression test: B, W, and L only act as commands when the argument is separated by a space or '-'. B and D are hex digits, so BD00 must examine memory at $BD00, never set a breakpoint at $D00. ewm_core::fmt grows disassemble(cpu, addr) -> (line, next_addr), reusing the trace formatter and instruction table. Also adds a server test that sequential client connections are served after a client disconnects. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
6a6fd790eb |
Make machine construction slot-table-driven (JSON_CONFIG Phase B) (#252)
* Make machine construction slot-table-driven (JSON_CONFIG Phase B) The machine builders no longer hard-wire the card set — Two::new_* constructs from a slot table, so a config can put any card in any slot: - Dsk/Clk/Hdd decode only the DEVSEL low nibble, so one implementation serves any slot; the clock and hard-disk firmware are generated per slot (clk_rom/hdd_rom patch the unit byte, $Cn page, and port operands), with golden tests pinning the slot-1/slot-7 images byte-for-byte to the pre-generator statics. The IouE holds owned slot ROM copies so generated firmware can be installed. - Two::new_with_slots builds up to three Disk ][ controllers (the classic maximum), one Thunderclock, any number of hard drives, and empty slots (reading $00, which the Autostart scan skips). new/ new_with_aux delegate with the default table, so the whole existing suite runs unchanged. Boot stays the real Autostart ROM slot scan — highest populated slot wins, as on hardware; no config boot hint. - The Phase A layout gate in config validation is replaced by multiplicity rules; machine.slots is now optional (absent = the default layout, present = literal). --drive1/--drive2/--hdd became slot 6/7 sugar merged into the table, preserving CLI-over-config precedence. Drive lights OR across controllers; drag-drop targets the boot controller. - New ewm/tests/two_slots.rs: controller independence at the bus level, boot-scan order, empty slots falling through to BASIC, a clock in slot 2 surviving a full ProDOS 2.4.3 boot, and hard drives in two slots serving distinct images. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Add ready-made machine configs for the classic setups configs/plus.json is the Apple ][+ (language card built in): green monitor, Disk ][ in slot 6, nothing else. configs/enhanced.json is the Enhanced //e (65C02 inherent to the model): extended 80-column 64K card in the aux slot, Disk ][ in slot 6, color monitor. Neither names a disk — the --drive1/--drive2 flags merge into the config's slot 6 card. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Replace the drive flags with generic --set config overrides --set <key>=<value> overrides any config value by colon-separated key path (--set machine:slots:6:drive1=game.dsk, --set display:monitor= amber); the --drive1/--drive2/--hdd sugar flags are removed. Config sources now layer left-to-right into one JSON document: --config files load through the typed path (per-file validation and relative-path resolution intact) and serialize back to JSON — the config types gained serde::Serialize, fronting Phase C — while --set mutates the document directly; one typed conversion + validation runs at the end. Deep-merge rules: objects recurse, null/empty-array overlays are no-ops, and objects whose "card" discriminators differ replace wholesale. A --set entering machine:slots on a slotless document materializes the default table first, so a bare --set machine:slots:6:drive1=x behaves exactly like the removed --drive1. Values parse as JSON when they are JSON (numbers, booleans, whole objects — the one-line --hdd replacement) and as plain strings otherwise. The boo-menu launcher speaks --set for dropped media; README and the notes record the new syntax and the multi-file deep-merge semantic. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Fix CI: point the full.json fixture at a committed disk image The disks directory is gitignored with only four images force-added; "Total Replay v6.0.1.hdv" exists only in local working trees, so the two_config.rs existence assertion failed on CI (and has kept main red since #251 merged). The fixture's slot 7 hard drive now uses the committed ProDOS_2_4_3.po block image instead. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
a37fba0a98 |
Merge pull request #251 from st3fan/claude/json-config-phase-a
Add JSON machine configuration via --config (JSON_CONFIG Phase A) |
||
|
|
e83139331e |
Add JSON machine configuration via --config (JSON_CONFIG Phase A)
A whole machine can now be described in one JSON file and started with `ewm two --config myemulator.json`: - ewm/src/config.rs: serde types mirroring the schema; load() parses, validates semantically (Phase A accepts only today's buildable layout: slot 1 thunderclock, slot 6 diskii, slot 7 harddrive, any aux card), and resolves relative paths against the config file's directory. - --config in parse_options via a two-pass parse: pass 1 seeds Options from the file, pass 2 is the unchanged flag loop — so explicitly given CLI flags override config values structurally. - Config-only settings cpu.speed and input.controller wire to the existing palette mechanisms at startup (including the audio cycle-to-sample pairing and a preferred-gamepad name scan). - schema/ewm-config.schema.json: draft 2020-12, derived from the config structs by a golden-file test (schemars is a dev-dependency only; regenerate with EWM_UPDATE_SCHEMA=1 cargo test -p ewm). - Tests: schema lockstep, parse/validation/rejection messages, relative-path resolution, CLI-overrides-config precedence, and a boot gate (a config naming the DOS 3.3 disk boots to the ] prompt exactly like --drive1 does). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
21243789ea |
Follow the default-branch rename: CI triggers on main
The push trigger in ci.yml still said master, so CI would silently stop running on pushes after the GitHub-side rename to main. Also the one living-document reference (MAC_APP.md branch strategy). Historical notes keep their original wording. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
6f7a79c9ad |
Add the JSON machine-configuration plan (notes/JSON_CONFIG.md)
Plan for `ewm two --config myemulator.json`, per the owner's decisions: JSON over TOML (better supported); a committed JSON Schema (schema/ewm-config.schema.json, derived from the serde structs via schemars and locked by a round-trip test) covering everything the CLI and Command Palette configure today -- model, drives, hdd, aux card, monitor style, scanlines, boot delay, fps, memory regions, the palette's accelerator speeds and controller pick; plus configurable slots 1-7 (diskii with drive images, harddrive, thunderclock, empty -- multiple Disk ][ controllers legal in the schema). CLI options stay; only --config is added, with explicit flags overriding config values, and relative paths resolving against the config file's directory (the property .ewmachine bundles need). Three phases: A = --config + schema + today's layout expressible (other layouts error clearly); B = table-driven machine construction for real slot flexibility (multiple controllers at $C080+slot*16 with per-slot P5 ROMs, empty slots, boot-scan order); C = palette "save machine configuration" round-tripping into the .ewmachine story. MAC_APP.md Phase 3 rewritten TOML->JSON; the IDEAS.md bullet now points here. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
3bb74324c0 |
Package EWM as a native Mac application (MAC_APP.md Phase 1)
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> |
||
|
|
a42cbdd912 |
Add the native Mac application plan (notes/MAC_APP.md)
Four phases: a self-contained double-clickable EWM.app (static SDL3, hand-rolled bundle script, ad-hoc signing, .dsk/.woz/.hdv file associations delivered as SDL drop events, an icon rendered by the emulator's own character generator); distribution polish (Developer ID + notarization + DMG); .ewmachine machine documents built on the config-file idea; and finally the VMware-Fusion-style library app that spawns each machine as its own app instance. Option 1 first, as the owner decided -- it produces exactly the building blocks option 2 orchestrates. Grounded against the tree: the binary currently links Homebrew SDL3 dynamically, sdl3 0.18 offers build-from-source-static, and the rewrite already made the binary Finder-launch-safe (include_bytes ROMs, no cwd dependence). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |