Files
vt1200/bad-apple/README.md
T

168 lines
6.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Bad Apple on a VT1200 — 30 s vector prototype
A standalone, polygon-styled 30-second Bad Apple demo for the VT1200
(TMS34010). The host pipeline turns the first 30 s of `hd60.mp4` into a
640×512 binary picture (2×2 screen blocks), traces it into simplified
straight-edged polygons, and packs only changed 32×32 tiles as rectangles.
The payload paints those rectangles with `FILL L`.
The payload takes over the terminal: it disables interrupts and never returns.
**Recovery is a power cycle.** The ROM is never modified.
Design: [`../docs/superpowers/specs/2026-10-31-bad-apple-vector-design.md`](../docs/superpowers/specs/2026-10-31-bad-apple-vector-design.md)
Plan: [`../docs/superpowers/plans/2026-11-01-bad-apple-vector.md`](../docs/superpowers/plans/2026-11-01-bad-apple-vector.md)
Measurements and gaps: [`REPORT.md`](REPORT.md)
## Source video
`hd60.mp4` was **downloaded by the agent** in the earlier OpenCode session
**“Bad Apple demo tool”** (`ses_e6d6b504dffeIRKLu3RqZAiFRp`, message seq 641),
not supplied by the user. The design doc's “human-supplied” wording is
inaccurate.
- Item page: <https://archive.org/details/hd-60-fps-bad-apple-shadow-art-pv-720-x-960-60fps>
- Direct file: <https://archive.org/download/hd-60-fps-bad-apple-shadow-art-pv-720-x-960-60fps/_HD_60FPS_%20Bad%20Apple%21%21%20_Shadow%20Art%20PV_%20%28%20720%20X%20960%2060fps%20%29.mp4>
- The fetch command was `curl -sL -o hd60.mp4 "<direct file>"`.
- Measured: **34,903,925 bytes**, **960×720**, **60 fps**, **219.521451 s**,
h264 / yuv420p, AAC stereo.
- Local path used by this demo: `/tmp/opencode/badapple-real/hd60.mp4`.
The archive.org item name is scrambled (`720-x-960`); the file inside is
960×720, 4:3.
## Quick start — current MAME demo
From the repository root:
```sh
# 1. video -> data/movie.bin
cd bad-apple
python3 -m pipeline.build \
--video /tmp/opencode/badapple-real/hd60.mp4 \
--out data --duration 30
cd ..
# 2. payload
bad-apple/player/build.sh
# 3. watch it
bad-apple/tools/run_mame_gui.sh
```
`run_mame_gui.sh` starts MAME with `VT1200_SKIP_LEVEL1=1`, boots the OS, loads
the payload at frame 2500, lets it take over, writes the movie into low RAM,
and plays it in a window. Expect the 30 s clip at full screen, upright.
`BA_END` (default 8000) is the MAME frame at which the loader script exits;
raise it if your run is slower.
The **network path is currently disabled in practice**: the OS heap cannot
provide one contiguous 2.4 MB `malloc_` block, so `tools/load.sh` stops before
it binds UDP. Use the debugger path above. See `REPORT.md`.
## Build the 30 s stream
```sh
cd bad-apple
python3 -m pipeline.build \
--video /tmp/opencode/badapple-real/hd60.mp4 \
--out data --duration 30
```
Outputs:
| file | size | what |
|---|---|---|
| `data/movie.bin` | 2,401,942 B | 1,757 records, 43 source frames dropped |
| `data/manifest.json` | small | data size, dirty-tile/rect/cost percentiles |
| `data/preview/frame_*.png` | — | still previews |
Measured: dirty tiles mean 44.9 / p95 72; rectangles mean 393 / p95 670;
estimated paint cost mean 7.47 ms / p95 12.01 ms against a 13.9 ms refresh
gate. The data area is 3.03 MB, so this fits with ~0.6 MB spare.
## Build the payload
```sh
bad-apple/player/build.sh # debugger build: build/player.bin
NET=1 bad-apple/player/build.sh # network build: build/player-net.bin
```
The 1987 TI TMS34010 SDK runs under DOSBox. The C sources are K&R C: the
1987 compiler rejects ANSI prototypes and `const`. `.text` is linked at bit
`0x018C0000`, `.bss` at `0x01C00000`.
The 1990 **r5.01 OS/2** toolchain under `2ine-tms34010` also builds it (see
`tools/tms34010-sdk/2INE-501.md` for setup). Build `MAIN.C` without
`gspopt -O2`: the optimizer hoists the `CTRL_READY` polling load and the
payload waits forever at takeover. `MOVIE.C` is fine at `-O2`. The verified
r5.01 build is `.text` 3,168 bytes, entry `0x018C5C70`, and is MAME-exact at
frames 0 and 180. `gspac`/`gspcg` exit 139 at teardown but still write valid
files.
## Run in MAME (debugger path)
For a window, use `tools/run_mame_gui.sh` (above). For scripted/headless
checks:
```sh
# one frame, then dump VRAM at MAME frame 2700
VT1200_DUMP_VRAM_FRAMES=2700 VT1200_DUMP_DIR=/tmp/ba-one \
bad-apple/tools/run_mame.sh one
# 5 s slice, paced (headless)
BA_MOVIE=bad-apple/data5/movie.bin BA_START=0 BA_COUNT=300 \
bad-apple/tools/run_mame.sh play
# full 30 s, paced (headless)
BA_MOVIE=bad-apple/data/movie.bin BA_MOVIE_BASE=0x00080000 \
BA_WAIT=1 BA_PACE=1 bad-apple/tools/run_mame.sh play
```
`run_mame.sh` runs with `-video none`; it exits at `BA_END`. The debugger
loader writes the movie into high RAM (`0x01D00000`) for small slices; for the
full 30 s it uses `BA_WAIT=1`, lets the payload take over, then writes low RAM
at `0x00080000` — the same ordering as the real delivery path.
## Deliver to a real unit (network path)
The terminal must be booted and answering ping. The payload is loaded over
`rdebug`, then receives `movie.bin` over UDP into an OS `malloc` buffer, then
disables interrupts and plays.
```sh
bad-apple/tools/load.sh --host <unit-ip> --movie bad-apple/data/movie.bin
```
**This path is currently blocked** at `malloc_(2401942)`: the OS heap does not
give one contiguous 2.4 MB block, so the payload never binds UDP. The debugger
path above is the working demo. A fixed low-RAM staging receive or raw-LANCE
loader is the next step. See `REPORT.md`.
## Data format
Header 36 bytes (`BAPL`, version, geometry, frame count, index/data offsets).
Index entry: `u32 offset, u8 ticks, u8 flags`. Record: `u16 dirty tile count`,
then per tile `u16 tile index, u16 (bg<<15)|rect count`, then 3-byte rects
(`x0:5 y0:5 w-1:5 h-1:5`). A tile is repainted by filling its 64×64 screen
block with the background colour, then filling the minority-colour rectangles.
## Gotchas this prototype records
* **PSIZE is re-asserted on every FILL.** The resident OS leaves PSIZE at 2 bpp;
a one-time write in `_fill_init_` was overwritten, and every span came out
twice as wide. The fix is the per-call `movi 1 / move @0C0000150h`.
* **VRAM polarity is inverted from the canvas**: bit 1 = white, bit 0 = black.
`tools/check_vram.py` downsamples with that polarity.
* **The OS dies before the movie is in low RAM.** The network path receives
into an OS heap buffer while the stack is alive, then DINTs. The debugger
path does the same ordering with `BA_WAIT=1`.
* **MAME is not the unit's performance verdict.** MAME needed ~2.1 emulated
frames per movie record on the full stream; the unit's 50 MHz FILL rate is
the number that matters. See `REPORT.md`.
* **The marker/control block lives in `.bss`**, not at bit `0x01810000`: a
2.4 MB heap allocation can cross that address. `player.env` publishes
`MARK_PTR`; `load_mame.lua` reads it.
* **Pacing reads each record's `ticks`.** 60 source frames took 74 MAME frames
(72 display frames plus startup), so the 60-on-72 cadence is measured, not
assumed.