diff --git a/harness/badapple/README.md b/harness/badapple/README.md index b7b62a6..c890a77 100644 --- a/harness/badapple/README.md +++ b/harness/badapple/README.md @@ -8,7 +8,7 @@ against MAME's debugger API and is not how anything reaches a machine. ``` harness/badapple/ -├── README.md this file +├── README.md this file (setup, data preparation, run) ├── demo.sh one command: wait for the OS, load, send ├── rdebug_load.py payload image → terminal RAM over rdebug (TCP 771) ├── clip_from_video.py video → raw 1-bpp frames for make_tiles.py @@ -42,28 +42,93 @@ Two facts about it are load-bearing and were measured: ## Setup +Two builds and one piece of data preparation. Steps 1 and 2 are C builds; step 3 +is the part that needs a source video. + ```sh cd /path/to/vt1200 -# 1. MAME + the driver (skip if you already have vt1200 built) +# 1. MAME + the driver (skip if vt1200 is already built) harness/mame/build.sh -# 2. the payload (TI SDK under DOSBox) -harness/udpstream/build-stream.sh # -> harness/udpstream/build/vidstream.bin +# 2. the payload (TI SDK under DOSBox) -> harness/udpstream/build/vidstream.bin +harness/udpstream/build-stream.sh +``` -# 3. a clip cache (skip to use the committed 30-frame synthetic clip) +Step 3 is described below, because the numbers (time, disk, what "good" looks +like) are what make it repeatable. + +## Data preparation: a video to a tile cache + +Nothing ships a full clip: `harness/udpstream/tiles/` in the repo is the +**30-frame synthetic clip**, which is enough to smoke-test the transport but shows +a moving ellipse, not the animation. A real clip is three commands and about +five minutes. + +### 3a. Get a source video + +Any file ffmpeg can read. For Bad Apple the usable public sources are: + +| source | why | +|---|---| +| `archive.org/details/hd-60-fps-bad-apple-shadow-art-pv-720-x-960-60fps` | 960x720, 60 fps, 219 s, ~35 MB — the one this demo was measured on (the item name is scrambled; the file inside is 960x720) | +| `yt-dlp ` from YouTube | fine, but the 4K/8K uploads are upscales of the same 2009 render and add no detail | +| the original PV's Flash file (`archive.org/details/dagobah_Bad_Apple_HQ`) | the true master, vector — needs a Flash renderer to rasterise, which nothing here does | + +Resolution matters less than it looks: the terminal shows **1 bpp**, and the +pipeline scales to 1280 px wide before thresholding, so anything at 960 px or +more lands on the same picture. + +### 3b. Frames + +```sh python3 harness/badapple/clip_from_video.py bad-apple.mp4 /tmp/badapple --fps 15 +``` + +* one `*.bin` per frame, 1280x960, 1 bpp, 160-byte rows, **LSB first** — bit 0 of + a byte is the row's leftmost pixel, the order both the terminal's scanout and + the payload's encoder use. Do not substitute `ffmpeg`'s `format=monob`: it + packs MSB first and mirrors every eight pixels on the display. +* `--fps 15` on the 219 s source gives **3,292 frames**; `--height 960` fills 30 + of the 32 tile bands (a 4:3 source at 1280x960, the bottom two rows black), and + `--height 1024` fills the screen by stretching 4:3. +* measured cost: **~12 s**, **489 MB** on disk (153,600 B per frame). +* the source is white paper with black silhouettes, which is already the right + polarity. If yours is inverted, add `--invert`; `--threshold` (default 127) + decides what counts as ink. + +### 3c. The cache + +```sh python3 harness/udpstream/make_tiles.py --clip /tmp/badapple --out /tmp/badapple-tiles ``` -`clip_from_video.py` writes **LSB-first** 1-bpp rows (bit 0 of a byte is the -leftmost pixel), which is the order both the terminal's scanout and the -payload's encoder use. Do not substitute `ffmpeg`'s `format=monob`: it packs -MSB first and mirrors every eight pixels on the display. +Takes **~2 min 47 s** (pure-Python Lloyd's algorithm over 3,292 frames) and +writes four files, **6.1 MB** total: -`make_tiles.py` prints what it built — on the real clip (3,292 frames at 15 fps, -1280x960) that is 256/256 dictionary entries, 55 keyframes, mean delta 496 B, -1.63 MB of stream, PSNR 24.4 dB. +| file | size | what it is | +|---|---|---| +| `dictionary.bin` | 32,768 B | 256 32x32 tiles, the vocabulary every frame is expressed in | +| `indices.bin` | 4,213,760 B | one 1,280-byte index frame per clip frame | +| `stream.bin` | 1,645,081 B | the plan: 55 keyframes + 3,141 deltas + 96 "nothing changed" | +| `manifest.json` | 199 KB | the numbers below, per frame | + +It prints the same summary. What it said for this clip, and what to check: + +``` +3292 frames, 256 dictionary entries (256 used) <- the dictionary is saturated +55 keyframe(s), 3141 delta(s), 96 unchanged +payload 1280 B full, 496 B mean delta (5..3602), 1628615 B total +reconstruction 15823231 bit(s) over 3292 frame(s), BER 0.003667, PSNR 24.36 dB +``` + +Saturated entries and a 24 dB PSNR are expected, not errors: one byte per tile is +the format's ceiling, and shadow-art edges are expensive. A clip that reports +`inf` PSNR has every tile in the dictionary — that happens only for very flat +footage. + +Keep the cache outside the repo: it is 6.1 MB of derived data. Pass it to the +demo with `--tiles /tmp/badapple-tiles`. ## Run it in MAME