forked from arti/vt1200
103 lines
4.1 KiB
Markdown
103 lines
4.1 KiB
Markdown
# 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)
|
||
|
||
## 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`.
|
||
|
||
## Run in MAME (debugger path)
|
||
|
||
```sh
|
||
# one frame, then dump VRAM at MAME frame 2700
|
||
bad-apple/tools/run_mame.sh one
|
||
|
||
# 5 s slice, paced
|
||
BA_MOVIE=bad-apple/data5/movie.bin BA_START=0 BA_COUNT=300 \
|
||
bad-apple/tools/run_mame.sh play
|
||
|
||
# full 30 s, paced
|
||
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` exits when the payload sets its status marker. 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 implemented but **not yet verified on the unit or over MAME's
|
||
tap** in this worktree: the tap was down. The OS socket API it uses is the one
|
||
already proven by `harness/udpstream/udpstream.c`. 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`.
|