diff --git a/apps/termtris/HANDOFF.md b/apps/termtris/HANDOFF.md new file mode 100644 index 0000000..5a5ad87 --- /dev/null +++ b/apps/termtris/HANDOFF.md @@ -0,0 +1,133 @@ +# Handoff — termtris running inside the VT1200 + +Everything here is on branch **`termtris`** of +`https://git.wut.ee/opencode.energia.arti.ee/vt1200.git` (remote `origin`). +The branch was developed in a worktree at +`/home/arti/.local/share/opencode/worktree/26e92e/termtris`; any checkout of the +branch is equivalent. + +```sh +git fetch origin termtris +git checkout -b termtris origin/termtris +apps/termtris/verify.sh +``` + +`verify.sh` is the whole acceptance run: ROM checksum, toolchain check, MAME +build (skip with `--skip-mame`), the six tests, the acceptance summary and the +evidence list. It exits non-zero on the first failure and keeps every test log +under `apps/termtris/build/verify_.log`. `RUNTIME` is roughly 12 minutes +once MAME is built; the MAME build itself is the long part. + +## What is done + +The upstream game (John Tsiombikas' `termtris`, commit `7c5435d`) builds as a +native TMS34010 program with the 1990 TI r5.01 C toolchain under 2ine, loads +into VT1200 RAM through `dwt/loader`, opens a 480×312 window on the terminal's +own X server through the ROM Xlib, and reads real X11 key events. It is the +actual game: board, score/level/lines panels, piece preview, help and +high-score panels, moving/rotating/dropping pieces, and `q` to quit. + +| task | state | commit | +|---|---|---| +| vendor + toolchain + XG smoke test | done, `smoke.sh` PASS | `a861268` | +| `sprintf`/stdio compat | done, `fmt.sh` PASS | `a804181` | +| cell-grid X11 backend | done, `render.sh` PASS | `264b53a` | +| key map, clock, main loop | done, `input.sh` PASS | `c1945cf` | +| game integration, scores, COFF | done, `play.sh`/`play_keys.sh` PASS | `41d3cec` | +| docs + harness report | done | `3e2ddde` | +| review pass (seed, focus, expose, assertions) | done | `7a2cef5` | +| handoff script + this note | done | (this commit) | + +`VT1200.md` is the port documentation; `harness/mame/TERMTRIS_REPORT.md` is the +harness-side report; the design and plan are in +`docs/superpowers/specs/2026-11-03-termtris-vt1200-design.md` and +`docs/superpowers/plans/2026-11-03-termtris-vt1200.md`. + +## What is *not* done yet — read this first + +All screenshots and the acceptance numbers in the docs were produced with an +**older MAME binary** (`/home/arti/code/k-space/mame0289/vt1200d`, 30 Oct) that +predates the driver's VR319 default. That binary has no `VT1200_MONITOR`, and +its frames are 1024×864, i.e. the old VR150 geometry. The driver in this tree +defaults to **VR319** (1280×1024 active, 1696×1063 raster), which changes the +frame cadence and therefore the frame at which the loader and the tests act. + +So on the new box, after `harness/mame/build.sh`: + +1. Re-run `apps/termtris/verify.sh --skip-mame` and note the new + `DIAG: loops=... events=... quit=...` values. +2. Re-capture the screenshots (the test scripts write them into + `apps/termtris/evidence/`) and look at them: the board must still be a + bordered well with `[`/`]` tetrominoes, the score/level/lines boxes on the + right, and the preview box. The window is positioned at (80,80) inside a + larger raster, so the PNGs get bigger but the window content is unchanged. +3. Update the numbers in `VT1200.md` ("Acceptance values") and in + `harness/mame/TERMTRIS_REPORT.md`, and say plainly that the earlier figures + were VR150. + +If the behaviour itself changes at VR319 (pieces not visible, no events), that +is a real finding, not a formatting detail — the loader acts at frame 2500 and +the keys at 3100–3290, which may need re-timing if the new raster shifts the +frame budget. + +## Prerequisites on the new box + +* a MAME 0.289 checkout (`MAME_SRC`, default `/home/arti/code/k-space/mame0289`); +* the 2ine build with `lx_loader` (`NINE=`, default + `/home/arti/os2-work/2ine/build`); +* the r5.01 SDK zips, already in the repo under + `tools/tms34010-sdk/1990-11 TMS340 Family SDK/`; +* `vt1200_v22.bin` in the repo root (the harness ROM dir symlinks to it). + +`verify.sh` checks the ROM and the 2ine loader before doing anything, and +`harness/mame/build.sh` re-registers the driver in `mame.lst` and builds the +single-driver `vt1200` subtarget. + +## Expected results + +Last run here (older MAME, VR150 geometry): + +``` +smoke PASS type=2 keycode=194 lookup_n=1 ch=61 ks=0061 +fmt PASS six formats exact +render PASS flus=464C5553 progress=00FFFFFF +input PASS bytes 60 61 9B 41 9B 42 9B 44 9B 43 30 0A 7F 71 70 + (60 is the Expose redraw, then a/up/down/left/right/0/Return/Delete/q/p) +play PASS DIAG: tick=887 quit=0 +play_keys PASS move DIAG: loops=348 events=19 focus=00400001 quit=0 + panels DIAG: loops=338 events=13 focus=00400001 quit=0 + quit DIAG: loops=292 events=11 focus=00400001 quit=1 +``` + +`focus=00400001` is the window the process created; `quit=1` is the game's own +global after `q` — that is the proof the key reached the game and not just the +window. + +## Known failure modes (all seen, all diagnosed) + +| symptom | cause | handling | +|---|---|---| +| build stage "fails" with exit 139 but produced its file | 2ine-hosted TI tools crash during teardown | `build.sh` retries only when the output is missing; `gspac` needs `-i.` | +| linker points `cinit` into `.text` | empty `.cinit` section | `build.sh` relinks with the one-word `CINITZ.obj` terminator | +| `XNextEvent` never returns an event, keycode zero | child's per-process Xlib context is NULL | `src/vt1200/xgfix.c` writes a zeroed 1 KiB block before `XOpenDisplay` (RAM only) | +| the game exits immediately at startup, sometimes | `XOpenDisplay`/window creation while the display server settles | `main()` retries 40 × 50 ms, then returns 1 | +| window paints once, then hangs; a debug marker stops mid-flush | per-cell flushing overflowed the X socket / `XSync` per row deadlocked the client | flush repaints whole rows (fill + one 80-char string + inverse spans + one `XFlush`) | +| playfield is a white block with invisible pieces | the 1-bit raster cannot show colour | backend drops colour and uses bold (`0x80`) as reverse; pieces are `[`/`]` glyphs | + +## Layout + +``` +apps/termtris/ +├── build.sh TI r5.01 -> COFF (OUTNAME=termtris for the game) +├── run.sh interactive MAME session (lightgun/tablet input) +├── verify.sh one-command acceptance (see above) +├── VT1200.md port documentation +├── src/game.c upstream game; only srand(vt_rand_seed()) differs +├── src/vt1200/ main.c x11.c xgfix.c compat.c xlib.h scoredb.c *.asm +├── tests/ smoke fmt render input play play_keys (+ .lua drivers) +├── evidence/ screenshots +└── build/termtris.coff the built image (tracked for convenience) +``` + +The tests build from source every run (`play.sh`/`play_keys.sh` rebuild +`termtris.coff`), so an edit cannot silently pass by using a stale binary. diff --git a/apps/termtris/VT1200.md b/apps/termtris/VT1200.md index 7ad4b72..18fb0ab 100644 --- a/apps/termtris/VT1200.md +++ b/apps/termtris/VT1200.md @@ -89,7 +89,7 @@ level 0). Keys arrive as X11 `KeyPress` events and go through `XLookupString`; letters are lower-cased and handed to `game_input()`. Arrows are sent as the game's own CSI codes (`0x9b` + `A`/`B`/`C`/`D`), which `game_input` translates back to -`w`/`s`/`d`/`a`. `0x7F` is Backspace/Delete. `Expose` triggers a full redraw and +`w`/`s`/`d`/`a`. `0x7F` is Backspace/Delete. `Expose` (selected with `0x22800F`) triggers a full redraw and `FocusOut` (only after a `FocusIn`) pauses the game. ## Running it in MAME @@ -105,6 +105,7 @@ harness/mame/build.sh # rebuild the vt1200 subtarget (JOBS=4 on small h Then: ```sh +apps/termtris/verify.sh # build MAME + run every test (see HANDOFF.md) apps/termtris/tests/play.sh # load, click, snapshot apps/termtris/tests/play_keys.sh # move / panels / quit acceptance runs apps/termtris/run.sh # same run, interactive session @@ -129,9 +130,9 @@ which copies the sections to their linked addresses and calls Acceptance values from the last full run (`play_keys.sh`): ``` -move DIAG: loops=333 events=18 focus=00400001 quit=0 -panels DIAG: loops=323 events=12 focus=00400001 quit=0 -quit DIAG: loops=277 events=10 focus=00400001 quit=1 +move DIAG: loops=348 events=19 focus=00400001 quit=0 +panels DIAG: loops=338 events=13 focus=00400001 quit=0 +quit DIAG: loops=292 events=11 focus=00400001 quit=1 ``` `focus=00400001` is the window this process created (it is the only one in the diff --git a/apps/termtris/build/termtris.coff b/apps/termtris/build/termtris.coff index cf4275a..0889510 100644 Binary files a/apps/termtris/build/termtris.coff and b/apps/termtris/build/termtris.coff differ diff --git a/apps/termtris/build/termtris.map b/apps/termtris/build/termtris.map index 78e98d3..bd21a62 100644 --- a/apps/termtris/build/termtris.map +++ b/apps/termtris/build/termtris.map @@ -1,7 +1,7 @@ ***************************************************** TMS340 COFF Linker Version 5.01 ***************************************************** -Wed Nov 11 17:58:44 2026 +Thu Nov 12 05:12:58 2026 OUTPUT FILE NAME: ENTRY POINT SYMBOL: "_start" address: 018a0000 diff --git a/apps/termtris/evidence/termtris_move_3200.png b/apps/termtris/evidence/termtris_move_3200.png index 1da3486..49e895a 100644 Binary files a/apps/termtris/evidence/termtris_move_3200.png and b/apps/termtris/evidence/termtris_move_3200.png differ diff --git a/apps/termtris/evidence/termtris_panels_3200.png b/apps/termtris/evidence/termtris_panels_3200.png index 586a0a1..87af787 100644 Binary files a/apps/termtris/evidence/termtris_panels_3200.png and b/apps/termtris/evidence/termtris_panels_3200.png differ diff --git a/apps/termtris/evidence/termtris_quit_3200.png b/apps/termtris/evidence/termtris_quit_3200.png index 098ab58..decc0bd 100644 Binary files a/apps/termtris/evidence/termtris_quit_3200.png and b/apps/termtris/evidence/termtris_quit_3200.png differ diff --git a/apps/termtris/verify.sh b/apps/termtris/verify.sh new file mode 100755 index 0000000..5d0420f --- /dev/null +++ b/apps/termtris/verify.sh @@ -0,0 +1,107 @@ +#!/bin/sh +# verify.sh -- one-command acceptance for the VT1200 termtris port. +# +# apps/termtris/verify.sh # build MAME, then run every test +# apps/termtris/verify.sh --skip-mame # use the emulator already installed +# +# Exits non-zero on the first failed check. Each test's full output is kept in +# apps/termtris/build/verify_.log. +set -eu + +DIR=$(cd "$(dirname "$0")" && pwd) +ROOT=$(cd "$DIR/../.." && pwd) +M=${MAME:-/home/arti/code/k-space/mame0289/vt1200} +ROM_MD5=b46a3e442ccb86134d2356bd70d22fd2 + +skip_mame=0 +for arg in "$@"; do + case $arg in + --skip-mame) skip_mame=1 ;; + -h|--help) + sed -n '2,8p' "$0" | sed 's/^# \{0,1\}//' + exit 0 + ;; + *) + echo "verify.sh: unknown argument: $arg" >&2 + exit 2 + ;; + esac +done + +mkdir -p "$DIR/build" + +echo "== ROM checksum" +got=$(md5sum "$ROOT/vt1200_v22.bin" | cut -d' ' -f1) +if [ "$got" != "$ROM_MD5" ]; then + echo "FAIL: vt1200_v22.bin md5 is $got, expected $ROM_MD5" >&2 + exit 1 +fi +echo "ok: $got" + +echo "== 2ine toolchain" +LX="${NINE:-/home/arti/os2-work/2ine/build}/lx_loader" +if [ ! -x "$LX" ]; then + echo "FAIL: no 2ine lx_loader at $LX (set NINE=/path/to/2ine/build)" >&2 + exit 1 +fi +echo "ok: $LX" + +echo "== emulator" +if [ "$skip_mame" -eq 0 ]; then + JOBS=${JOBS:-8} "$ROOT/harness/mame/build.sh" +fi +if [ ! -x "$M" ]; then + echo "FAIL: no vt1200 binary at $M (set MAME=... or drop --skip-mame)" >&2 + exit 1 +fi +echo "ok: $M" + +export MAME="$M" +fail=0 + +run() { + name=$1 + shift + printf '== %-9s ' "$name" + if "$@" >"$DIR/build/verify_$name.log" 2>&1; then + echo "PASS" + else + echo "FAIL (apps/termtris/build/verify_$name.log)" + tail -20 "$DIR/build/verify_$name.log" | sed 's/^/ /' + fail=1 + fi +} + +run smoke "$DIR/tests/smoke.sh" +run fmt "$DIR/tests/fmt.sh" +run render "$DIR/tests/render.sh" +run input "$DIR/tests/input.sh" +run play "$DIR/tests/play.sh" +run play_keys "$DIR/tests/play_keys.sh" + +echo +echo "== play acceptance" +if [ -f "$DIR/build/play_move.log" ]; then + for mode in move panels quit; do + grep -a -h 'DIAG: loops' "$DIR/build/play_$mode.log" 2>/dev/null | + head -1 | sed "s/^/ $mode /" + done +fi + +echo +echo "== evidence" +ls -1 "$DIR"/evidence/*.png 2>/dev/null | sed 's/^/ /' + +echo +echo "== ROM checksum (after the suite)" +got=$(md5sum "$ROOT/vt1200_v22.bin" | cut -d' ' -f1) +[ "$got" = "$ROM_MD5" ] || { echo "FAIL: ROM changed during the run" >&2; exit 1; } +echo "ok: $got" + +if [ "$fail" -ne 0 ]; then + echo + echo "verify: FAILED" >&2 + exit 1 +fi +echo +echo "verify: PASS" diff --git a/harness/mame/TERMTRIS_REPORT.md b/harness/mame/TERMTRIS_REPORT.md index aaa78d2..88a7688 100644 --- a/harness/mame/TERMTRIS_REPORT.md +++ b/harness/mame/TERMTRIS_REPORT.md @@ -94,9 +94,9 @@ The run is `VT1200_SKIP_LEVEL1=1 LOADER_AT=2500` with the loader payload at bit Last full acceptance run: ``` -move DIAG: loops=333 events=18 focus=00400001 quit=0 -panels DIAG: loops=323 events=12 focus=00400001 quit=0 -quit DIAG: loops=277 events=10 focus=00400001 quit=1 +move DIAG: loops=348 events=19 focus=00400001 quit=0 +panels DIAG: loops=338 events=13 focus=00400001 quit=0 +quit DIAG: loops=292 events=11 focus=00400001 quit=1 ``` `focus=00400001` is the termtris window, and `quit=1` is the game's global after