feat(package): four PRG variants, per-variant D64s, and a dependency-free single-file listener - #96
Merged
Merged
Conversation
dependency-free single-file listener The release is aimed at people who never want to build anything, so `make package` now produces every combination of backend x crypto profile rather than the two UCI images it built before: c64-https-uci-reu.prg make BACKEND=uci c64-https-uci-onchip.prg make BACKEND=uci USE_NISTCURVES_ONCHIP=1 c64-https-ip65-reu.prg make BACKEND=ip65 c64-https-ip65-onchip.prg make BACKEND=ip65 USE_NISTCURVES_ONCHIP=1 ip65 was previously excluded on a link failure that the #68 refit closed. It is the only artifact that serves a stock C64 + RR-Net cartridge, and ip65-onchip is the only image a bone-stock machine with no REU can run at all — that gap was the point of this change. Disk images: one per variant plus one per backend. There is deliberately no all-in-one image, and that is arithmetic rather than preference — the four PRGs total 868 blocks against a .d64's 664 free. Each backend's pair does fit (UCI 496, ip65 372). The listener stops needing `cryptography`. That library is a compiled extension and cannot be embedded in a portable source bundle, which is what blocked a single-file build — but it was never needed for TLS. The server was always stdlib `ssl`; the library was used for exactly one thing, minting the self-signed P-256 cert. gen_certs.py now does that in pure Python (P-256 point arithmetic, a minimal DER encoder, ECDSA-SHA256), so dist/c64-https-listener.py is one self-extracting file with no third-party dependency: no pip, no venv, no network. What remains is a property of the interpreter, not an installable package: an ssl module with TLS 1.3. macOS's /usr/bin/python3 is LibreSSL 2.8.3 and cannot serve this client at any price. That is detected at startup and reported in one line rather than a traceback, and it is stated in MANIFEST.txt so nobody discovers it by running the thing. Packaging is now data-driven: tools/package/_common.sh holds the variant matrix, one line per shipped PRG, and build_prgs.sh / build_d64.sh / write_manifest.sh all derive from it. Nothing is version-specific — sizes, hashes, git HEAD and submodule pins are read at run time — so re-running `make package` after a library bump regenerates everything with zero edits. `make package-verify` is the new acceptance gate: it rebuilds every variant and compares PRG hashes (object hashes are not evidence, ca65 stamps build time into every .o), reads each PRG back out of its .d64 with c1541 and byte-compares, boots every image in VICE asserting the banner, and runs the listener selftest. Measured at this commit, all clean-build: c64-https-uci-reu.prg 62977 B 741f0e8cd99470a2… c64-https-uci-onchip.prg 62977 B fe959dad5edc1dc7… c64-https-ip65-reu.prg 47105 B 417c70859411e8b5… c64-https-ip65-onchip.prg 47105 B 573561dab4af9e37… All four reproduce byte-for-byte on a second build from clean; all six disk images boot to the correct backend banner in VICE; the listener selftest passes 4/4 from a clean temp dir with no venv. Booting a .d64 in VICE needs `-trapdevice8 +drive8truedrive`: under true drive emulation the ~250-block load never completes in any sane budget and the screen sits on LOADING, which reads as a corrupt image rather than a slow one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This was referenced Aug 14, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Lane P of the release-prep team: the packaging machinery for a tagged release
of prebuilt artifacts, aimed at people who never want to build anything.
No
libs/,cfg/*.cfgorsrc/changes — the two library-bump lanes aremoving those underneath this. Everything here is data-driven and re-runnable:
sizes, hashes, git HEAD and submodule pins are read at run time, and the
variant matrix is one line per shipped PRG in
tools/package/_common.sh, sore-running
make packageafter their bumps regenerates every artifact withzero edits.
What ships
Four PRGs — every combination of backend x crypto profile.
make packagepreviously built the two UCI images only:
ip65 was excluded on a link failure that the #68 refit closed. It is the only
artifact that serves a stock C64 + RR-Net cartridge, and
ip65-onchipis theonly image a bone-stock machine with no REU can run end to end. Getting those
into the release is the point of the lane.
Six disk images: one per variant, plus one per backend carrying both of that
backend's profiles.
c64-https-listener.py— one self-extracting file, no dependencies.MANIFEST.txt— sizes, sha256, git HEAD, submodule pins, and a "which one doI want?" section keyed on the two questions that actually decide it.
Two decisions that needed making
There is no all-in-one D64, and that is arithmetic rather than preference.
A .d64 has 664 free blocks = 168,656 usable bytes; the four PRGs total 868
blocks (~220 KB). Each backend's pair does fit — UCI 496 blocks, ip65 372 —
which makes the per-backend disk the largest bundle a real 1541 can hold, so
that is where the line got drawn. File names are shared between a variant's
single disk and its backend disk, so a user only ever learns one name, and
every single-variant disk works with
LOAD"*",8,1.The listener's
cryptographydependency is gone, not worked around. Thebrief flagged the honest constraint:
cryptographyis a compiled extension,wheels are per-platform and per-Python-version, so it cannot be embedded in a
portable source bundle. The three suggested outs were embed-and-error,
vendor a pure-Python TLS stack, or bootstrap a venv on first run.
None were taken, because the premise was wrong.
cryptographywas neverneeded for TLS — the server has always been stdlib
ssl. It was needed forexactly one thing: minting the self-signed P-256 certificate. So
gen_certs.pynow does that in pure Python (~200 lines: P-256 pointarithmetic, a minimal DER encoder, ECDSA-SHA256), and the bundle has no
third-party dependency at all. Nothing to pip install, no venv, no network.
openssl x509parses the result andopenssl verifyaccepts theself-signature.
What remains is a property of the interpreter, not of any installable
package: an
sslmodule with TLS 1.3, i.e. OpenSSL 1.1.1+. macOS's/usr/bin/python3is LibreSSL 2.8.3 and cannot serve this client at anyprice. That is detected at startup, reported in one line rather than a
traceback (
--debugputs the traceback back), and — per the brief — stated inMANIFEST.txtso nobody discovers it by running the thing:The cert generator deliberately emits the same extension set as the old
cryptographypath (subjectAltName only, non-critical). The C64 parses thiswith a hand-written 6502 DER walker, and that is the shape it has been
validated against; adding basicConstraints/keyUsage "for correctness" without
a hardware e2e would be a silent change to a tested interface.
Acceptance —
make package-verify, 18/18Sizes and hashes at this commit, all clean-build:
ip65-reumatches the417c7085…recorded in CLAUDE.md for master 2ceb5b1 —an independent cross-check that the ip65 build is the intended one. The ip65
blob rebuilt to the documented 6,951 B /
cf1a5ff7….Object hashes are deliberately not compared anywhere: ca65 stamps wall-clock
time into every
.oheader, so nobody can reproduce their own object hashtwice.
ld65does not propagate that field, which is exactly what makes thePRG comparable.
Two traps found, both now in CLAUDE.md
Booting a .d64 in VICE needs
-trapdevice8 +drive8truedrive. Under truedrive emulation the ~250-block serial load of a 63 KB PRG does not finish
inside any budget worth waiting for, and the symptom — a screen frozen on
LOADING— reads as a corrupt image rather than a slow one. With the KERNALload traps the banner lands at ~5 s. Image contents are byte-compared
separately, so nothing is lost by loading fast.
The menu is not a usable VICE pass criterion. A 900 s probe on
c64-https-uci-reu.d64saw the banner at 6.0 s and never reachedQ=QUIT:boot's table init runs at emulated 1 MHz because VICE 3.10 has no usable warp.
The banner is the verdict; whether the menu appeared is reported, not
asserted. (The ip65 images also print
NETWORK INIT FAILEDwith no networkattached — expected, and explicitly not part of the criteria.)
Also corrected
The packaging section's REU-vs-onchip guidance still said the crossover was
~7 MHz. The measured figure is ~18 MHz — on a U64E the sign flips between
the 16 and 20 MHz CPU-speed settings, and the five-point fits put it at
17.9 MHz.
MANIFEST.txtand the new README section use ~18 MHz and explainthe mechanism (the REU profile carries a DMA-anchored wall-clock floor that
turbo cannot touch; the on-chip profile has none).
For the supervisor
PACKAGE_PYTHONmatters only for the listener build and its selftest; themacOS system
python3will fail the selftest with the LibreSSL message above.SKIP_REBUILD=1/SKIP_VICE=1/SKIP_LISTENER=1narrow the gate if youneed a fast re-run.
🤖 Generated with Claude Code