Skip to content

Commit 158709d

Browse files
committed
trim-distro-card.sh builds the staged SD card from the official distro at full capacity, restoring big self-streaming games to the staged-primary browser: the interim 512 MB card was too small for the Atic Atac Next remake (111 MB zip) and TX-1696, so the staged tbblue.mmc is now built FROM the official 24.11 card image — its full 1 GB capacity and its prepped system tree (TestPrepDistroCard_OfficialImage bakes in the welcome-pager delete + config.ini seed, since staged images mount untouched) with every per-title-licensed item removed (apps/demos/docs/extras/games/src; dot/machines/nextzxos/sys/home/tmp/TBBLUE.FW/TBBLUE.TBU/LICENSE.md/README.md stay). Two constraints shaped the build and are documented where they bit: (1) deleting the payload in place leaves freed clusters holding the deleted bytes (a ~50 MB zip instead of 2.6 MB) AND leaves free space fragmented — self-streaming games raw-stream their own .nex and die with the game's own "FILE FRAGMENTATION ERROR" screen (reproduced with Atic Atac) — so the script REBUILDS the filesystem fresh (mformat + mcopy) leaving one contiguous ~1 GB free run (verified at the FAT level: 386 allocated clusters in a single prefix run); (2) cloning the official geometry (32 KB clusters, partition at LBA 63) reproduces the known-gaps.md faithful-firmware-boot gap ("Error opening 'menu.ini/.def'") and failed all 8 firmware-path suite rows, so the rebuild uses the proven staged geometry (LBA 2048, type 0x0C, 4 KB clusters, 32 reserved) at distro capacity — cluster size or a dependent FAT parameter is now the gap's isolated suspect (offset was already excluded). Verified with the built card staged in apps/*/public/next/: headless faithful AND direct boots reach the NextZXOS menu; go test -count=1 ./cmd/zx_go/ fully green (the earlier "green" against the geometry-clone card was a stale test cache — forced reruns exposed the 8 failures); in the play app, plain boot to menu, Atic Atac to character select with its story scroller live, TX-1696 to its animated title via the Browser-launch route; distro ROMs byte-identical to the staged ones. The card itself stays gitignored as always (2.6 MB zip, ~8 MB resident in the sparse card; the old 512 MB card kept locally as tbblue-512mb-backup.mmc). Docs: emulator-core README (build + rationale), known-gaps (staged-card consequence on the firmware-geometry row), frontends.md resident-size, LICENSES.md route 2 names the builder script.
1 parent 309dd11 commit 158709d

5 files changed

Lines changed: 118 additions & 5 deletions

File tree

packages/emulator-core/LICENSES.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,12 @@ served alongside at `/next/licenses/`. The routes:
6868
emulator-core README ("Next boot modes").
6969
2. **Staged bare system (browser source during the interim; also offline
7070
dev, CI, gif-service's renderer).** `scripts/stage-zxnext-assets.sh`
71-
fetches the trimmed assets onto a developer's machine; the deploy host
72-
stages them for the sites' `/next/` route (served to users during the
73-
interim) and for gif-service's server-side renderer. The "free parts"
71+
fetches the trimmed assets onto a developer's machine, and
72+
`scripts/trim-distro-card.sh` builds the staged card from an official
73+
distro image (full capacity, system tree only — every per-title item
74+
removed); the deploy host stages them for the sites' `/next/` route
75+
(served to users during the interim) and for gif-service's
76+
server-side renderer. The "free parts"
7477
analysis below is the basis for serving this staged copy — it applies
7578
to the official deployment while route 1 is disabled, and to any
7679
self-host that chooses to serve a staged copy.

packages/emulator-core/README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,21 @@ committed. See `LICENSES.md` for the distribution policy and the
6161
conditions that apply to hosting a staged copy (the deployment serving
6262
`/next/` must satisfy them).
6363

64+
The staged card is BUILT FROM the official distro card by
65+
`scripts/trim-distro-card.sh`: the distro's full capacity (1 GB for
66+
24.11 — big self-streaming games like Atic Atac and TX-1696 need it)
67+
and its prepped system tree, with the per-title-licensed payload
68+
removed and the filesystem REBUILT fresh (~2.6 MB zipped, ~8 MB
69+
resident in the sparse card). The rebuild matters twice over: freed
70+
clusters would otherwise keep the payload bytes (zip bloat) and leave
71+
free space fragmented — self-streaming games raw-stream their own .nex
72+
and die with "FILE FRAGMENTATION ERROR" unless the card's free space
73+
is one contiguous run. The rebuilt card uses the STAGED geometry
74+
(partition at LBA 2048, 4 KB clusters), not the official one: the
75+
official 32 KB-cluster layout hits the faithful-firmware-boot gap in
76+
`docs/architecture/known-gaps.md`, while the staged layout boots both
77+
paths.
78+
6479
## Next boot modes — MAINTENANCE GOTCHA
6580

6681
The core's default Next boot is the hardware-faithful path: FPGA bootrom →
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
#!/usr/bin/env bash
2+
# Build the staged tbblue.mmc from an OFFICIAL SpecNext distro card image:
3+
# the distro's FULL capacity (so big self-streaming games — Atic Atac,
4+
# TX-1696 — fit), with the per-title-licensed payload removed — the
5+
# bare-bootable-system deployment posture in ../../LICENSES.md.
6+
#
7+
# trim-distro-card.sh <pristine-distro.img> [out.mmc]
8+
#
9+
# Steps:
10+
# 1. PREP the pristine card exactly as the browser's distro path does
11+
# (delete the first-boot welcome pager, seed config.ini) via
12+
# TestPrepDistroCard_OfficialImage — staged images mount untouched,
13+
# so the prep must be baked in.
14+
# 2. KEEP only the system tree (see KEEP below); everything else on the
15+
# card (apps/demos/docs/extras/games/src) is per-title licensed and
16+
# never distributed by this project.
17+
# 3. REBUILD the filesystem fresh (mformat + mcopy) instead of deleting
18+
# in place — for three reasons:
19+
# - deletions leave the payload bytes in freed clusters (a ~50 MB
20+
# zip instead of ~3 MB) and leave free space FRAGMENTED;
21+
# self-streaming games raw-stream their own .nex and hard-error
22+
# ("FILE FRAGMENTATION ERROR") unless free space is one
23+
# contiguous run;
24+
# - the OFFICIAL geometry (32 KB clusters) hits the known-gaps.md
25+
# faithful-firmware-boot gap ("Error opening 'menu.ini/.def'"), so
26+
# the rebuilt card uses the STAGED geometry both boot paths handle:
27+
# partition at LBA 2048, type 0x0C, 4 KB clusters, 32 reserved
28+
# sectors — the classic tbblue.mmc layout at the distro's size.
29+
# Capacity comes from the SOURCE image (1 GB for the 24.11 distro).
30+
# 4. Zip via zip-sd-image.sh (the browser downloads the zip and streams
31+
# it into the sparse in-wasm card).
32+
#
33+
# After building, re-verify per "Next boot modes" in ../README.md (both
34+
# boot paths) and stage the .mmc + .mmc.zip into apps/*/public/next/.
35+
# Needs: GNU mtools, python3, a Go toolchain (for the prep test).
36+
set -euo pipefail
37+
38+
SRC="${1:?usage: trim-distro-card.sh <pristine-distro.img> [out.mmc]}"
39+
OUT="${2:-$(dirname "$SRC")/tbblue.mmc}"
40+
SCRIPTS="$(cd "$(dirname "$0")" && pwd)"
41+
ZXGO="$SCRIPTS/../zx_go"
42+
TMP="$(mktemp -d)"
43+
trap 'rm -rf "$TMP"' EXIT
44+
45+
# The system tree that stays on the card. LICENSE/README travel with the
46+
# copy per The Next License; home/tmp are part of the OS card layout.
47+
KEEP=(dot home LICENSE.md machines nextzxos README.md sys TBBLUE.FW TBBLUE.TBU tmp)
48+
49+
echo "1/4 prep (welcome pager + config.ini) ..."
50+
(cd "$ZXGO" && ZX_GO_DISTRO_IMG="$SRC" ZX_GO_DISTRO_IMG_OUT="$TMP/prepped.img" \
51+
go test -count=1 -run TestPrepDistroCard_OfficialImage ./cmd/zx_go/ >/dev/null)
52+
53+
# Source partition offset (for extraction) and total image size (for the
54+
# rebuilt card's capacity).
55+
read -r SRC_START TOT_SEC < <(python3 - "$TMP/prepped.img" <<'EOF'
56+
import os, struct, sys
57+
with open(sys.argv[1], 'rb') as f:
58+
start, = struct.unpack_from('<I', f.read(512), 0x1BE + 8)
59+
print(start, os.path.getsize(sys.argv[1]) // 512)
60+
EOF
61+
)
62+
63+
echo "2/4 extract system tree ..."
64+
mkdir "$TMP/tree"
65+
for e in "${KEEP[@]}"; do
66+
mcopy -i "$TMP/prepped.img@@$((SRC_START * 512))" -s -p -m "::/$e" "$TMP/tree/"
67+
done
68+
69+
echo "3/4 rebuild at staged geometry (LBA 2048, 4 KB clusters) ..."
70+
P_START=2048
71+
P_SIZE=$((TOT_SEC - P_START))
72+
python3 - "$TMP/out.img" "$TOT_SEC" "$P_START" "$P_SIZE" <<'EOF'
73+
import struct, sys
74+
path, tot, start, size = sys.argv[1], *map(int, sys.argv[2:])
75+
mbr = bytearray(512)
76+
e = 0x1BE
77+
mbr[e+1:e+4] = bytes([0xFE, 0xFF, 0xFF])
78+
mbr[e+4] = 0x0C
79+
mbr[e+5:e+8] = bytes([0xFE, 0xFF, 0xFF])
80+
struct.pack_into('<II', mbr, e+8, start, size)
81+
mbr[510:512] = b'\x55\xAA'
82+
with open(path, 'wb') as f:
83+
f.write(mbr)
84+
f.truncate(tot * 512)
85+
EOF
86+
mformat -i "$TMP/out.img@@$((P_START * 512))" -F -c 8 -R 32 -T "$P_SIZE" -h 255 -s 63 -H "$P_START" ::
87+
for e in "${KEEP[@]}"; do
88+
mcopy -i "$TMP/out.img@@$((P_START * 512))" -s -p -m "$TMP/tree/$e" ::/
89+
done
90+
mdir -i "$TMP/out.img@@$((P_START * 512))" :: | tail -2
91+
92+
echo "4/4 zip ..."
93+
mv "$TMP/out.img" "$OUT"
94+
"$SCRIPTS/zip-sd-image.sh" "$OUT"
95+
echo "done: $OUT (+ .zip) — re-verify per README 'Next boot modes', then stage into apps/*/public/next/"

packages/emulator-core/zx_go/docs/architecture/frontends.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ replacement for the JSSpeccy3 Emulator class:
174174
into a sparse card (r55): JSZip's `internalStream` feeds chunks to
175175
`zxSdIngestBegin/Chunk` and `zxBootNext()` mounts the result — the
176176
flat image is never materialised; only its real content is resident
177-
(~136 MB for the full official card, ~5 MB for the staged trimmed
177+
(~136 MB for the full official card, ~8 MB for the staged trimmed
178178
one). On the distro path `zxSdPrepDistro()` runs between ingest and
179179
boot: it deletes the pristine card's `/nextzxos/autoexec.1st`
180180
(first-boot welcome pager, re-shown every boot until disabled — it

packages/emulator-core/zx_go/docs/architecture/known-gaps.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ This is a body of work, not a single gap.
101101
| Desktop run loop does not use the fastboot fast-forward (browser only) | deferred | `cmd/zx_go/fastboot.go` |
102102
| The Next cannot tape-load in the browser player; .tap on the Next falls back to the 128K | deferred | `GoEmulator.js` |
103103
| Browser direct-boot seed table and the nexload menu-index are coupled to the SD distro version; re-verification procedure documented | maintenance coupling | `packages/emulator-core/README.md` |
104-
| Faithful firmware boot (FPGA bootrom → TBBLUE.FW) of the OFFICIAL distro card geometry (1 GB, 32 KB clusters, partition at LBA 63) lands in the config tool with "Error opening 'menu.ini/.def'" — every firmware file open fails, while NextZXOS itself reads the same card fine. Not the partition offset (rebasing to LBA 2048 reproduces), not firmware content or config.ini (byte-identical to the staged card, which boots). The browser's direct-boot path is unaffected (all boot regression tests pass against the prepped official image), but the "delete the go.env lines" fallback does not currently work on the official card | open (r60) | `cmd/zx_go/distro_prep.go`, `packages/emulator-core/README.md` |
104+
| Faithful firmware boot (FPGA bootrom → TBBLUE.FW) of the OFFICIAL distro card geometry (1 GB, 32 KB clusters, partition at LBA 63) lands in the config tool with "Error opening 'menu.ini/.def'" — every firmware file open fails, while NextZXOS itself reads the same card fine. Not the partition offset (rebasing to LBA 2048 reproduces), not firmware content or config.ini (byte-identical to the staged card, which boots). The browser's direct-boot path is unaffected (all boot regression tests pass against the prepped official image), but the "delete the go.env lines" fallback does not currently work on the official card. Consequence for the STAGED card: `scripts/trim-distro-card.sh` rebuilds the trimmed distro system at distro capacity but STAGED geometry (LBA 2048, 4 KB clusters) — both boot paths verified — instead of cloning the official layout, which reproduces this gap. Cluster size (32 KB) or a dependent FAT parameter is the remaining suspect | open (r60) | `cmd/zx_go/distro_prep.go`, `packages/emulator-core/README.md` |
105105
| Warm-boot path (`--warm-boot`) uses captured reference dumps; non-faithful by design, default off | dev tool | `cmd/zx_go/next.go` |
106106
| TZX blocks with pause=0 get the legacy ~1 s TAP silence instead of the spec's "no pause" (back-to-back turbo chains could over-wait; harmless now that pause chunks emit no edges, #192) | deferred | `pkg/ula/tape.go` |
107107
| Custom loaders using the ROM's LD-EDGE (mid-LD-BYTES entries, RAM byte loops CALLing $05E7/$05E3) are O(1)-trapped timeline-neutrally; loaders that COPY the sampling routine into RAM (Speedlock class) still interpret at real time under the fast-tape turbo — byte-matching the copied routine is the known extension if one shows up | partial (by design) | `cmd/zx_go/main.go` |

0 commit comments

Comments
 (0)