mirror of
https://git.k-space.ee/arti/vt1200.git
synced 2026-11-02 12:23:17 +02:00
168 lines
6.9 KiB
Markdown
168 lines
6.9 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)
|
||
|
||
## 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.
|