mirror of
https://git.k-space.ee/arti/vt1200.git
synced 2026-11-01 12:14:56 +02:00
docs(badapple): usage, measurements and gaps
This commit is contained in:
1 parent
8a1cd1bfb1
commit
4013f75434
2 files changed
+195
-9
No files matched your search
+99
-9
@@ -1,12 +1,102 @@
|
||||
# Bad Apple on VT1200 — 30 s vector prototype
|
||||
# Bad Apple on a VT1200 — 30 s vector prototype
|
||||
|
||||
This is the standalone, polygon-styled 30-second Bad Apple demo described in
|
||||
[`docs/superpowers/specs/2026-10-31-bad-apple-vector-design.md`](../docs/superpowers/specs/2026-10-31-bad-apple-vector-design.md).
|
||||
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 host pipeline turns the first 30 s of `hd60.mp4` into a 640×512 black-and-white
|
||||
vector picture, packs only changed 32×32 tiles as rectangles, and a TMS34010
|
||||
payload paints them with `FILL L`. The payload takes over the terminal, so a hang
|
||||
ends at a power cycle.
|
||||
The payload takes over the terminal: it disables interrupts and never returns.
|
||||
**Recovery is a power cycle.** The ROM is never modified.
|
||||
|
||||
Status: under construction. See the implementation plan at
|
||||
[`docs/superpowers/plans/2026-11-01-bad-apple-vector.md`](../docs/superpowers/plans/2026-11-01-bad-apple-vector.md).
|
||||
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`.
|
||||
@@ -0,0 +1,96 @@
|
||||
# Bad Apple 30 s vector prototype — report
|
||||
|
||||
Date: 2026-11-01
|
||||
Design: [`2026-10-31-bad-apple-vector-design.md`](../docs/superpowers/specs/2026-10-31-bad-apple-vector-design.md)
|
||||
|
||||
## What was built
|
||||
|
||||
* Host pipeline: video → temporal median → blur/threshold → potrace
|
||||
straight-line polygons → RDP → even-odd rasteriser → 32×32 tile deltas →
|
||||
`movie.bin`.
|
||||
* Payload: TMS34010 C decoder + `FILL L` primitive, 16-bit marker page,
|
||||
display-interrupt pacing, UDP receive path.
|
||||
* Tools: MAME debugger loader, VRAM checker, UDP sender, rdebug load script.
|
||||
|
||||
## Measured numbers (first 30 s, 1,800 source frames)
|
||||
|
||||
| quantity | value |
|
||||
|---|---|
|
||||
| source | 960×720, 60 fps, first 30 s |
|
||||
| logical grid | 640×512, 2×2 screen blocks |
|
||||
| records | 1,757 (43 source frames dropped by the cost gate) |
|
||||
| data | **2,401,942 bytes** (cap 3.03 MB, gate 3.0 MB) |
|
||||
| dirty tiles/frame | mean 44.9, p50 47, p95 72, max 320 |
|
||||
| rectangles/frame | mean 393, p50 409, p95 670, max 1,175 |
|
||||
| estimated cost/frame | mean 7.47 ms, p50 7.95 ms, **p95 12.01 ms**, max 37.99 ms |
|
||||
| gate | 13.9 ms (one 72 Hz refresh) |
|
||||
|
||||
The stream fits with ~0.6 MB spare. The p95 frame is inside one refresh; the
|
||||
max is a scene cut that necessarily takes more than one.
|
||||
|
||||
## Exactness evidence (MAME)
|
||||
|
||||
`tools/check_vram.py` unpacks the 256-byte-stride VRAM dump, downsamples 2×2,
|
||||
and compares bit-for-bit against the Python application of the records.
|
||||
|
||||
| run | movie frame | result |
|
||||
|---|---|---|
|
||||
| test pattern | 0 | **EXACT** |
|
||||
| 5 s slice | 0 | **EXACT** |
|
||||
| 5 s slice | 180 | **EXACT** |
|
||||
| 5 s slice | 297 | **EXACT** |
|
||||
| 30 s stream | 0 | **EXACT** |
|
||||
| 30 s stream | 900 | **EXACT** |
|
||||
|
||||
The one-frame MAME run also proved the FILL geometry: a logical black
|
||||
rectangle 10..109 × 20..69 became exactly screen x 20..219. The first version
|
||||
was exactly twice as wide; the cause was the OS leaving PSIZE at 2 bpp. The
|
||||
primitive now re-asserts PSIZE=1 on every call.
|
||||
|
||||
## Pacing
|
||||
|
||||
`wait_vsync()` programs `DPYINT=1`, clears `INTPEND`, and polls the DI bit at
|
||||
`0xC0000120`. With `BA_PACE=1`, ten source frames took roughly fifteen MAME
|
||||
frames (startup included), i.e. the 60-on-72 cadence is being honoured rather
|
||||
than free-running.
|
||||
|
||||
## The performance gap MAME cannot close
|
||||
|
||||
MAME needed ~2.1 emulated frames per movie record while replaying frames 0..900
|
||||
of the full stream. The encoder's model says a mean record is 7.47 ms of a
|
||||
13.9 ms refresh, so MAME's TMS34010 interpreter is several times slower than the
|
||||
model. MAME proves the picture and the delta chain; it cannot prove the unit's
|
||||
60 fps. The unit's measured `FILL` rate (0.40 µs per 16-pixel word) is the
|
||||
number the cost model uses. A real-unit run is the remaining performance
|
||||
measurement.
|
||||
|
||||
## Network delivery
|
||||
|
||||
`player-net.bin` uses the OS UDP socket API (`socket_`, `bind_`,
|
||||
`recvfrom_`, `malloc_`) to receive `movie.bin` on port 7001 into a heap buffer,
|
||||
then DINTs and plays. The sender is `tools/send_movie.py`.
|
||||
|
||||
Status: the packet format and sender are unit-tested; the MAME tap was down in
|
||||
this worktree, so the UDP receive was not exercised end-to-end. The same OS
|
||||
socket API is already proven on the unit by `harness/udpstream/udpstream.c`.
|
||||
This is the one delivery-path gap.
|
||||
|
||||
## Regression
|
||||
|
||||
The no-env MAME baseline at 200 frames is unchanged:
|
||||
|
||||
```
|
||||
--- distinct PCs sampled: 2
|
||||
PC=3FDCE400 samples=199
|
||||
PC=3FDCE350 samples=1
|
||||
```
|
||||
|
||||
## Remaining gaps
|
||||
|
||||
1. Real-unit 30 s performance: needs a power-cycle run with
|
||||
`tools/load.sh`.
|
||||
2. UDP receive on the unit: socket API proven, this payload not yet.
|
||||
3. Incremental painting can show a brief wipe on a cut; accepted for this
|
||||
prototype.
|
||||
4. Potrace/RDP temporal jitter was reduced with a 3-frame median but not
|
||||
measured as a motion score.
|
||||
Reference in new issue
Block a user