Skip to content

package-m3-demo: a "just run it" disk for the uci-m3 variant - #275

Merged
JC-000 merged 16 commits into
masterfrom
feat/m3-demo-package
Oct 6, 2026
Merged

JC-000 merged 16 commits into
masterfrom
feat/m3-demo-package

Conversation

@JC-000

@JC-000 JC-000 commented Oct 1, 2026 •

Copy link
Copy Markdown
Owner

What

make package-m3-demo builds a "just run it" disk for the BACKEND=uci-m3 variant (#271): TLS runs on the Ultimate's ESP32, and the C64 speaks HTTP. It writes four files to dist/m3-demo/, the release assets:

  • c64-https-uci-m3-demo.prg: 6,519 B, sha256 e4814599c783aeefe03d37b194debc067c18afff3589069a97af8630edb34e24. Default target en.wikipedia.org /wiki/Commodore_64; host and path can be typed.
  • c64-https-uci-m3-demo.d64: sha256 28a8741fe7cb0b152901a24634e440a548380a41b710f1e15ead7959d00f98ba. It holds that PRG alone, as c64-https-m3.
  • README.txt: provenance (commit, make line, flags, toolchain path + sha256), what it needs, how to run it, three test URLs, and measured results.
    • Firmware line: "M3-SPEC v1 + errata v1.1/v1.2 firmware (validated on esp-tls/m3-master 3a1ff9ff: fw 3.15, FPGA 126, ESP 1.309)", with a marked slot for the 1541ultimate upstream PR number(s).
  • SHA256SUMS: sha256 of the other three files.

Scope: PACKAGE_VARIANTS, make package and the shipped products are untouched. No release or tag was created.

The build script (tools/package/build_m3_demo.sh)

  • Refuses a dirty tree, untracked files included. The README names a commit, so the PRG must be what that commit builds.
    • Red: the old git diff --quiet HEAD check exits 0 with only an untracked src/stray.s present.
    • Green: the script dies listing ?? src/stray.s.
  • Builds in a scrubbed environment (env -i PATH HOME TMPDIR), so neither an exported knob nor one passed on the make package-m3-demo VAR=… command line (it arrives through MAKEFLAGS) reaches the demo build. It then asserts build/flags.stamp: BACKEND=uci-m3, -C cfg/c64-https-uci-m3.cfg, exactly -D BACKEND_UCI=1 -D BACKEND_UCI_M3=1, and the demo target in https_host.inc.
    • Red: make package-m3-demo HTTPS_PORT=8443 built PRG 217e043e with -D HTTPS_PORT=8443 in the README.
    • Green: the same command now gives the clean hash. A mutant without env -i dies on "unexpected defines: … -D HTTPS_PORT=8443".
  • Records the toolchain: the README carries flags.stamp's TOOLCHAIN line (path + sha256 of ca65/ar65/ld65).
    • Red: a ca65 shim on PATH built PRG 217e043e behind a silent README.
    • Green: the same shim is now named in the README.
  • Proves its own output: two clean builds must give identical PRG bytes, and c1541 reads the PRG back out of the .d64 byte for byte. C1541= overrides the tool, in the script and the rig.

Rigs

  • tools/uci/rig_https_m3_demo.py mounts the .d64 on drive A, runs LOAD"*",8,1 / RUN, then fetches three targets:
    • the default target: HTTP 200, complete by its own framing;
    • github.com /robots.txt: HTTP 200, complete;
    • a name-mismatch host, judged by rig_https_m3.verdict_refuse (94 on screen, $8D, no handle, no REQUEST SENT), using only rows after its own banner.
  • Shared fix in both M3 rigs: waits no longer count stale screen text.
  • Drive A is restored and checked against what was read:
    • the old image is re-mounted by path (fw 3.15 reports it whole in image_file);
    • the drive is switched back off if it was off, including when our mount failed after drive_on;
    • our own /Temp/cache/upload/image[_N].d64 upload is deleted over FTP, last, with ftplib.all_errors caught.
    • Red: adv-m3demo/delete_aborts_restore.log. An FTP 421 escaped the delete, which ran first, leaving calls == ['unmount']: the operator's disk not re-mounted and the drive left on.
  • Per-run artifact dir: both M3 rigs pass _create_run_dir(...), not the base dir.
  • Device prep for uci-m3 (_reu_preflight.py, _device_prep.py). A uci-m3 build was classified as REU-profile, so prep ENABLED a 16 MB REU, though the README says "No REU".
    • It is now profile "none": m3_owned present, viewer_enter absent, and no nistcurves equate (which stays authoritative).
    • For "none", prep sets the REU Disabled. An unreadable state writes anyway; a refused write is exit 4. The preflight makes no device call.
    • An M3 build with HTTPS_BODY_TO_REU=1 (it links the viewer) still classifies reu.
    • Red: the new tests fail against the old modules, 5 of 6 plus the body-sink case.
  • rig_https_m3 ab refuses a B that is not on-chip. The device is prepared for A, which now runs with the REU off. Red by code reading: the previous rig never classified argv[5].
  • tools/test_device_prep.py: the demo rig (it boots from the drive, so the discovery rule cannot see it) is registered by name; rig_https_m3.py is now discovered.

Acceptance (head b54fcb6, on master 489efd0)

  • make package-m3-demo: PRG e4814599…, .d64 28a8741f…, shasum -c SHA256SUMS OK. The bytes are identical to the build on the c26e268 base, where the hardware ran.
  • check_m3_client.py: 50/50, 335 assertions.
  • mutate_m3_client.py: 62 mutants, 62 killed. No M3 check tool or uci-m3 source changed between c26e268 and 489efd0.
  • test_device_prep + test_reu_preflight + test_rig_skip_contract: 63 passed.
  • uci-m3 margin (demo target): MAIN $0801-$9FFF, last used $2175, 32,394 B free.

Hardware

U64E fw 3.15 git 3a1ff9ff, FPGA 126, core 1.50; 48 MHz; DeviceLock; PRG e4814599, .d64 28a8741f. Both runs had the REU Disabled by device prep (device_state.json in each per-run dir: profile none, wanted Disabled, REU Enabled → Disabled).

  • Mounted .d64 (rig_https_m3_demo.py, 2026-10-06): PASS.
    • Wikipedia: HTTP 200, 751,848 B, complete (chunked), 207 s.
    • github /robots.txt: HTTP 200, 16,739 B == Content-Length, 9 s.
    • nip.io name mismatch: 94,CERTIFICATE NAME MISMATCH: 0x00000004, $8D, m3_owned=0.
    • Drive A restored exactly; the rig printed deleted /Temp/cache/upload/image.d64.
  • rig_https_banner.py: PASS. The banner names EN.WIKIPEDIA.ORG; HTTP 200, 751,848 B == Content-Length; the fetch had settled when its state was read.

🤖 Generated with Claude Code

JC-000 and others added 16 commits October 6, 2026 13:23
`make package-m3-demo` builds dist/m3-demo/ from a clean tree: the M3
client PRG (default target en.wikipedia.org /wiki/Commodore_64, typed-host
prompt), a .d64 holding it alone, and a README with the source commit,
build flags, sha256s, required firmware (esp-tls/m3 f2e46946+), the
M3-SPEC version and errata, hardware, usage, three test URLs and what an
unsupported device shows. The script rebuilds from clean to prove the PRG
reproduces, and reads it back out of the .d64 with c1541. It does not
touch PACKAGE_VARIANTS or the shipped products.

tools/uci/rig_https_m3_demo.py runs the disk the way a user does: mounted
on drive A, LOAD"*",8,1 / RUN, the default fetch to completion, then a
name-mismatch negative (94, $8D).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The first fetch's CONNECTION CLOSED is still on screen when the negative
starts; the first run matched it at once. Run 1 (that bug) still PASSED the
drive LOAD and the default fetch (751,108 B == Content-Length).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…upstream PR slot

The firmware requirement is now "M3-SPEC v1 + errata v1.1/v1.2 firmware
(validated on esp-tls/m3 f2e46946)": f2e46946 is a local branch, not
fetchable upstream. The README carries a marked slot for the 1541ultimate
upstream PR numbers (UPSTREAM_PRS fills it). dist/m3-demo/ gains
SHA256SUMS, so the directory is exactly the pre-release's asset set.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…v-275)

- "What an unsupported device shows" is labelled as per the M3 spec and
  the model tests, not observed on hardware with this image.
- The timing and size claims are replaced by numbers measured on THIS
  image (PRG ed646d56) from the mounted .d64 on a U64E at 48 MHz:
  Wikipedia 751,108 B == Content-Length in 207 s from G to CONNECTION
  CLOSED; github.com/robots.txt 14,181 B == Content-Length in 7 s; the
  nip.io name refused with 94. The unmeasured 1 MHz claim is gone.
  tools/uci/rig_https_m3_demo.py now times the default fetch and runs the
  github URL too.
- The nip.io negative's dependence on nip.io and on Wikipedia's address
  is stated; "validated on an Ultimate 64 Elite (U64E)" only; the
  SHA256SUMS comment names its files.
PRG and .d64 bytes are unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… tree

make ran with the caller's environment, and a recipe sees the outer
`make package-m3-demo VAR=...` through MAKEFLAGS, so HTTPS_SNI=, VIC_BLANK=0
and the like reached the demo build and its README. make now runs under
env -i (PATH, HOME, TMPDIR), and build/flags.stamp must show the uci-m3
cfg and only the backend's two -D's, build/https_host.inc the demo target.

A modified or untracked file now stops the build instead of annotating the
README: the README names a commit, and the PRG must be what it builds.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… helpers

The boot prints "DHCP OK" before the menu, so waiting for it after 'I'
returned at once and 'G' could land while the re-init ran; a failing
re-init read as a pass. press_init (rig_https_m3.py) counts only text after
the menu line and waits for the whole address. Both M3 rigs use it.

The demo rig imports rig_https_m3's helpers and judges the negative with
its full verdict_refuse (status line code and on screen, no REQUEST SENT),
over the rows from the negative's own banner on. It restores drive A fully
(our /Temp upload deleted, the drive switched back off if it was, checked
against what was read), and honours C1541.

test_device_prep: the discovery rule cannot see either M3 rig (no comb
profile; the demo boots from the drive), so they are registered by name.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…delete

fw 3.15 (3a1ff9ff) reports a mounted image as image_file='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/Temp/cache/
upload/image.d64' with image_path empty, so the restore's 'path and file'
test would never re-mount a disk the operator had in drive A, and the
/Temp delete (keyed on image_path == /Temp) never fired. The upload is
overwritten per mount, so there is nothing to delete.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
detect_crypto_profile knew two answers. A BACKEND=uci-m3 build has neither
the nistcurves equate nor a row generator, so it fell through to the
fail-closed "reu" answer: prep ENABLED a 16 MB REU for an image that links
no 6510 crypto, and every M3 hardware run so far had one, though the demo
README says "No REU". It now recognises the M3 adapter (m3_owned) as profile
"none", after the manifest equate (still authoritative) and before the
fail-closed default (still what anything unrecognised gets).

For "none", prep sets RAM Expansion Unit Disabled (unreadable state writes
too: off is the safe direction; a refused write is exit 4), so a no-REU
claim is measured on the device, recorded in device_state.json. The
preflight treats it like onchip: no device call.

Red (old modules): 5 new cases fail, among them the M3 preflight touching
the device and prep leaving/turning the REU on. Green: all pass.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
c72352f said the upload is overwritten per mount. The second hardware run
shows otherwise: the U64E (fw 3.15 3a1ff9ff) named it
/Temp/cache/upload/image_1.d64, next to the first run's image.d64, which
matches the harness's own note (a repeat is numbered, not overwritten).
Each run leaked one .d64 into the RAM disk. The restore now FTP-deletes
the file its mount created, never one that was mounted before, and
only under /Temp/.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…n artifact dir

adv-m3demo on c72352f.

B1: wait_after fell back to the whole screen while its anchor was not yet
printed, and push_keys returns before the KERNAL drains the keys, so in the
demo's steps 1b and 2 the previous fetch's CONNECTION CLOSED satisfied the
wait: a FAIL, and fetch_in_flight=False skipped the socket teardown. The
fallback is gone (nothing counts until the anchor is on screen) and the
demo waits for each fetch's own banner first. press_init never needed it:
the menu sits at row 22 at the lowest and a good re-init prints four rows.
press_init also matched an address still being printed; it now needs the
screen to read the same twice.

B2: an earlier upload in drive A (/Temp/...) was re-mounted by name, which
can be OUR disk where the firmware overwrites image.d64; it is now left
empty with a warning. A mount that failed after drive_on left the drive on
unannounced; the restore now runs once drive A is touched, and undoes only
the power when nothing was mounted.

Both M3 rigs passed artifact_dir=RUN_DIR, the base dir the next run
overwrites; they now use rig_https_local._create_run_dir like the others.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ags.stamp

adv-m3demo N1: check_stamp read the flags but not the stamp's TOOLCHAIN
line, so a ca65 shim on PATH that adds a -D built a different PRG with a
README that said nothing. Asserting a toolchain hash would pin one
machine's cc65 build; recording it is the cheaper half and makes a shim
visible in the release's own provenance.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…, never raising

Reverts ec7640a's 'earlier /Temp upload -> leave drive A empty': the U64E
numbers a repeated upload (image_1.d64), so the operator's file is intact
and ejecting it was the defect (adv-m3demo, B2 withdrawn). A mount that
failed after drive_on still powers the drive back off, now with a warning.

F3: delete_upload caught OSError/error_perm only; ftplib.error_temp,
error_reply and error_proto escaped, and since it ran BEFORE the re-mount
and drive_off, an FTP 4xx left the operator's disk unmounted and the drive
on. It now catches ftplib.all_errors and runs last, after the comparison,
and says so when our mount reported no image_file.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
adv-m3demo F2: make BACKEND=uci-m3 HTTPS_BODY_TO_REU=1 links the REU
viewer and streams the body into the REU, but classified 'none', so prep
would switch the REU OFF under it. 'none' now needs m3_owned AND no
viewer_enter; anything else falls through to the fail-closed 'reu'.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
adv-m3demo F1: the device is prepared for A only, and A (uci-m3) now gets
the REU switched off, so a comb or REU-profile B would run without one
(the ~44 min spin). B's labels must classify 'onchip'; anything else is
a fatal exit 2 before the lock.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
9958997's ab guard names the comb profile, so the discovery rule finds
rig_https_m3.py by itself and the registration guard said to drop it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… run

Firmware line as decided: validated on esp-tls/m3-master 3a1ff9ff (fw
3.15, FPGA 126, ESP 1.309); the stale 'LOCAL branch f2e46946' comment is
dropped. Figures from rig_https_m3_demo.py on PRG e4814599 (REU disabled
by device prep, per its device_state.json).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@JC-000
JC-000 force-pushed the feat/m3-demo-package branch from 6b9225f to b54fcb6 Compare October 6, 2026 18:28
@JC-000
JC-000 merged commit 693727e into master Oct 6, 2026
@JC-000
JC-000 deleted the feat/m3-demo-package branch October 14, 2026 15:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant