From 150a6d86c0570093b5db0d653a31d1b8497665c9 Mon Sep 17 00:00:00 2001 From: Arti Zirk Date: Sun, 1 Nov 2026 10:10:52 +0200 Subject: [PATCH] feat(badapple): playback loop and vblank pacing --- bad-apple/.gitignore | 1 + bad-apple/player/main.c | 78 +++++++++++++++++++++++++++++++---- bad-apple/tools/load_mame.lua | 50 ++++++++++++++++------ bad-apple/tools/run_mame.sh | 33 +++++++++++++++ 4 files changed, 140 insertions(+), 22 deletions(-) create mode 100755 bad-apple/tools/run_mame.sh diff --git a/bad-apple/.gitignore b/bad-apple/.gitignore index 2fce34c..4410c71 100644 --- a/bad-apple/.gitignore +++ b/bad-apple/.gitignore @@ -1,5 +1,6 @@ data/* !data/manifest.json +data5/ preview/ player/build/ player/work/ diff --git a/bad-apple/player/main.c b/bad-apple/player/main.c index ddcadb4..0dea369 100644 --- a/bad-apple/player/main.c +++ b/bad-apple/player/main.c @@ -6,6 +6,11 @@ #define VRAM_BASE 0x04000000 #define ROW_BITS 2048 #define MARK 0x01810000 +#define CTRL_START (MARK + 0x20 * 8) +#define CTRL_COUNT (MARK + 0x24 * 8) +#define CTRL_PACE (MARK + 0x28 * 8) +#define CTRL_MOVIE (MARK + 0x2C * 8) +#define CTRL_MOVIELEN (MARK + 0x30 * 8) extern void fill_run_(); extern void fill_init_(); @@ -42,33 +47,88 @@ void *ctx; fill_rect(x, y, w, h, color); } +/* Poll the display interrupt pending bit. Interrupts are disabled; this is + * a status bit, not an ISR. A missing DPYINT only costs a bounded spin. */ +static void wait_vsync() +{ + unsigned int i; + unsigned short pending; + + /* Clear a stale DI, then wait for the next one. DPYINT is programmed to + * line 1 once at startup, so DI appears at the top of every frame. */ + *(unsigned short *)0xC0000120 = 0; + for (i = 0; i < 200000; i++) { + pending = *(unsigned short *)0xC0000120; + if (pending & 0x0400) { + return; + } + } +} + int main() { struct movie_header header; + unsigned int frame; + unsigned int last; + unsigned int start; + unsigned int count; + unsigned int pace; + unsigned int movie_base; + unsigned int movie_len; int rc; fill_init_(); + /* Display interrupt at line 1: our only timebase. Interrupts stay off; + * wait_vsync() polls the pending bit. */ + *(unsigned short *)0xC00000A0 = 1; + /* Start from white; the OS left its own screen behind. */ fill_rect(0, 0, 1280, 1024, 0); - rc = movie_open((unsigned char *)MOVIE_BASE, MOVIE_MAX, &header); + movie_base = *(unsigned int *)CTRL_MOVIE; + movie_len = *(unsigned int *)CTRL_MOVIELEN; + if (movie_base == 0) { + movie_base = MOVIE_BASE; + } + if (movie_len == 0) { + movie_len = MOVIE_MAX; + } + + rc = movie_open((unsigned char *)movie_base, movie_len, &header); if (rc != 0) { *(unsigned int *)MARK = 0xBAD00001; for (;;) { } } - rc = movie_decode_frame((unsigned char *)MOVIE_BASE, MOVIE_MAX, 0, - &header, paint, 0); - if (rc != 0) { - *(unsigned int *)MARK = 0xBAD00002; - for (;;) { - } + start = *(unsigned int *)CTRL_START; + count = *(unsigned int *)CTRL_COUNT; + pace = *(unsigned int *)CTRL_PACE; + last = header.frame_count; + if (last != 0) { + last = last - 1; + } + if (count != 0 && start + count - 1 < last) { + last = start + count - 1; } - *(unsigned int *)MARK = 0; /* FRAME */ - *(unsigned int *)(MARK + 0x10 * 8) = 1; /* STATUS: painted */ + for (frame = 0; frame <= last; frame++) { + if (pace != 0 && frame >= start) { + wait_vsync(); + } + rc = movie_decode_frame((unsigned char *)movie_base, movie_len, frame, + &header, paint, 0); + if (rc != 0) { + *(unsigned int *)MARK = 0xBAD00002; + *(unsigned int *)(MARK + 0x14 * 8) = frame; + for (;;) { + } + } + *(unsigned int *)MARK = frame; + } + + *(unsigned int *)(MARK + 0x10 * 8) = (count != 0) ? 2 : 1; for (;;) { } } diff --git a/bad-apple/tools/load_mame.lua b/bad-apple/tools/load_mame.lua index b66ab3e..679d297 100644 --- a/bad-apple/tools/load_mame.lua +++ b/bad-apple/tools/load_mame.lua @@ -28,11 +28,14 @@ if not dbg then end local ORIGIN = 0x018C0000 -local MOVIE = 0x00080000 +local MOVIE = tonumber(os.getenv("BA_MOVIE_BASE") or "0x01D00000") local MARK = 0x01810000 local ENTRY_PC = 0x3F579970 local AT = tonumber(os.getenv("BA_AT") or "2500") local STOP_AT = tonumber(os.getenv("BA_END") or "2600") +local START = tonumber(os.getenv("BA_START") or "0") +local COUNT = tonumber(os.getenv("BA_COUNT") or "0") +local PACE = tonumber(os.getenv("BA_PACE") or "0") local MOVIE_PATH = os.getenv("BA_MOVIE") or "bad-apple/data/test_movie.bin" local PAYLOAD_PATH = os.getenv("BA_PAYLOAD") or "bad-apple/player/build/player.bin" local ENV_PATH = os.getenv("BA_ENV") or "bad-apple/player/build/player.env" @@ -80,8 +83,20 @@ print(string.format("BA: payload %d bytes, movie %d bytes, entry %s", local function write_bytes(data, base) local i - for i = 0, #data - 1 do + local n = #data + i = 0 + while i + 4 <= n do + local b0 = data:byte(i + 1) + local b1 = data:byte(i + 2) + local b2 = data:byte(i + 3) + local b3 = data:byte(i + 4) + local word = b0 | (b1 << 8) | (b2 << 16) | (b3 << 24) + prog:write_u32(base + i * 8, word) + i = i + 4 + end + while i < n do prog:write_u8(base + i * 8, data:byte(i + 1)) + i = i + 1 end end @@ -102,21 +117,28 @@ local frames = 0 local function load_and_enter() write_bytes(payload, ORIGIN) - write_bytes(movie, MOVIE) local ok, bad = verify(payload, ORIGIN) if not ok then print("BA ERROR: payload readback mismatch at byte " .. bad) machine:exit() return end + prog:write_u32(MARK + 0x20 * 8, START) + prog:write_u32(MARK + 0x24 * 8, COUNT) + prog:write_u32(MARK + 0x28 * 8, PACE) + prog:write_u32(MARK + 0x2C * 8, MOVIE) + prog:write_u32(MARK + 0x30 * 8, #movie) + -- Arm the entry breakpoint before the large movie write; the write blocks + -- emulation, so it cannot fire until the movie is in place. + dbg:command("bpset " .. hex(ENTRY_PC) .. ",,{ pc = " .. hex(entry) .. " ; g }") + write_bytes(movie, MOVIE) ok, bad = verify(movie, MOVIE) if not ok then print("BA ERROR: movie readback mismatch at byte " .. bad) machine:exit() return end - print("BA: loaded; breakpoint at " .. hex(ENTRY_PC)) - dbg:command("bpset " .. hex(ENTRY_PC) .. ",,{ pc = " .. hex(entry) .. " ; g }") + print(string.format("BA: loaded; start=%d count=%d pace=%d", START, COUNT, PACE)) end emu.register_frame_done(function() @@ -125,16 +147,18 @@ emu.register_frame_done(function() loaded = true load_and_enter() end - if loaded and not entered and prog:read_u16(MARK) == 0x0000 - and prog:read_u16(MARK + 0x10 * 8) == 0x0001 then - entered = true - done = true - print(string.format("BA: STATUS=painted at frame %d; FRAME=%d", - frames, prog:read_u16(MARK))) + if loaded and not entered then + local status = prog:read_u16(MARK + 0x10 * 8) + if status == 1 or status == 2 then + entered = true + done = true + print(string.format("BA: STATUS=%d at frame %d; FRAME=%d", + status, frames, prog:read_u16(MARK))) + end end - if loaded and not done and frames > AT + 30 then + if loaded and not done and frames > AT + 10 then local err = prog:read_u32(MARK) - if err ~= 0 then + if err >= 0xBAD00000 then print(string.format("BA ERROR: marker %s at frame %d", hex(err), frames)) machine:exit() diff --git a/bad-apple/tools/run_mame.sh b/bad-apple/tools/run_mame.sh new file mode 100755 index 0000000..a35a4de --- /dev/null +++ b/bad-apple/tools/run_mame.sh @@ -0,0 +1,33 @@ +#!/bin/sh +# Run the payload in MAME with the debugger loader. +# +# tools/run_mame.sh one # one frame, no pacing +# tools/run_mame.sh play # full movie, paced +# BA_MOVIE=data5/movie.bin BA_START=180 BA_COUNT=1 tools/run_mame.sh play +set -eu + +HERE=$(cd "$(dirname "$0")" && pwd) +ROOT=$(cd "$HERE/../.." && pwd) +M=${M:-/home/arti/code/k-space/mame0289/vt1200} + +MODE=${1:-one} +BA_MOVIE=${BA_MOVIE:-$ROOT/bad-apple/data/test_movie.bin} +BA_PACE=${BA_PACE:-0} +BA_START=${BA_START:-0} +BA_COUNT=${BA_COUNT:-0} +BA_AT=${BA_AT:-2500} +BA_END=${BA_END:-3000} +BA_MOVIE_BASE=${BA_MOVIE_BASE:-0x01D00000} + +if [ "$MODE" = "play" ]; then + BA_PACE=${BA_PACE:-1} +fi + +cd "$ROOT" +VT1200_SKIP_LEVEL1=1 \ +BA_MOVIE="$BA_MOVIE" BA_PACE="$BA_PACE" BA_START="$BA_START" \ +BA_COUNT="$BA_COUNT" BA_AT="$BA_AT" BA_END="$BA_END" \ +BA_MOVIE_BASE="$BA_MOVIE_BASE" \ +"$M" vt1200 -rompath harness/mame/roms -video none -sound none -nothrottle \ + -skip_gameinfo -debug -debugger none \ + -autoboot_script "$ROOT/bad-apple/tools/load_mame.lua" -autoboot_delay 0