diff --git a/generated/amiga/sprites/sprites.spr b/generated/amiga/sprites/sprites.spr index 8dd31fe..90ef8db 100644 Binary files a/generated/amiga/sprites/sprites.spr and b/generated/amiga/sprites/sprites.spr differ diff --git a/generated/amiga/tiles/tilebank0.tbk b/generated/amiga/tiles/tilebank0.tbk new file mode 100644 index 0000000..27bdef1 Binary files /dev/null and b/generated/amiga/tiles/tilebank0.tbk differ diff --git a/generated/atarist/sprites/sprites.spr b/generated/atarist/sprites/sprites.spr index 8dd31fe..90ef8db 100644 Binary files a/generated/atarist/sprites/sprites.spr and b/generated/atarist/sprites/sprites.spr differ diff --git a/generated/atarist/tiles/tilebank0.tbk b/generated/atarist/tiles/tilebank0.tbk new file mode 100644 index 0000000..a943d40 Binary files /dev/null and b/generated/atarist/tiles/tilebank0.tbk differ diff --git a/generated/iigs/font.tbk b/generated/iigs/font.tbk new file mode 100644 index 0000000..f9883f4 Binary files /dev/null and b/generated/iigs/font.tbk differ diff --git a/generated/iigs/levels/level01.dat b/generated/iigs/levels/level01.dat new file mode 100644 index 0000000..f8a6f9a Binary files /dev/null and b/generated/iigs/levels/level01.dat differ diff --git a/generated/iigs/levels/level02.dat b/generated/iigs/levels/level02.dat new file mode 100644 index 0000000..dea5903 Binary files /dev/null and b/generated/iigs/levels/level02.dat differ diff --git a/generated/iigs/levels/level03.dat b/generated/iigs/levels/level03.dat new file mode 100644 index 0000000..9b5a703 Binary files /dev/null and b/generated/iigs/levels/level03.dat differ diff --git a/generated/iigs/levels/level04.dat b/generated/iigs/levels/level04.dat new file mode 100644 index 0000000..74993ec Binary files /dev/null and b/generated/iigs/levels/level04.dat differ diff --git a/generated/iigs/levels/level05.dat b/generated/iigs/levels/level05.dat new file mode 100644 index 0000000..8467bf0 Binary files /dev/null and b/generated/iigs/levels/level05.dat differ diff --git a/generated/iigs/levels/level06.dat b/generated/iigs/levels/level06.dat new file mode 100644 index 0000000..d232194 Binary files /dev/null and b/generated/iigs/levels/level06.dat differ diff --git a/generated/iigs/levels/level07.dat b/generated/iigs/levels/level07.dat new file mode 100644 index 0000000..805246c Binary files /dev/null and b/generated/iigs/levels/level07.dat differ diff --git a/generated/iigs/levels/level08.dat b/generated/iigs/levels/level08.dat new file mode 100644 index 0000000..3d6d273 Binary files /dev/null and b/generated/iigs/levels/level08.dat differ diff --git a/generated/iigs/levels/level09.dat b/generated/iigs/levels/level09.dat new file mode 100644 index 0000000..fc9246e Binary files /dev/null and b/generated/iigs/levels/level09.dat differ diff --git a/generated/iigs/levels/level10.dat b/generated/iigs/levels/level10.dat new file mode 100644 index 0000000..c4717cc Binary files /dev/null and b/generated/iigs/levels/level10.dat differ diff --git a/generated/iigs/levels/level11.dat b/generated/iigs/levels/level11.dat new file mode 100644 index 0000000..4c3ce87 Binary files /dev/null and b/generated/iigs/levels/level11.dat differ diff --git a/generated/iigs/levels/level12.dat b/generated/iigs/levels/level12.dat new file mode 100644 index 0000000..6a5ba48 Binary files /dev/null and b/generated/iigs/levels/level12.dat differ diff --git a/generated/iigs/levels/level13.dat b/generated/iigs/levels/level13.dat new file mode 100644 index 0000000..459177b Binary files /dev/null and b/generated/iigs/levels/level13.dat differ diff --git a/generated/iigs/levels/level14.dat b/generated/iigs/levels/level14.dat new file mode 100644 index 0000000..4a4374a Binary files /dev/null and b/generated/iigs/levels/level14.dat differ diff --git a/generated/iigs/levels/level15.dat b/generated/iigs/levels/level15.dat new file mode 100644 index 0000000..f091239 Binary files /dev/null and b/generated/iigs/levels/level15.dat differ diff --git a/generated/iigs/levels/level16.dat b/generated/iigs/levels/level16.dat new file mode 100644 index 0000000..e6471a5 Binary files /dev/null and b/generated/iigs/levels/level16.dat differ diff --git a/generated/iigs/levels/level17.dat b/generated/iigs/levels/level17.dat new file mode 100644 index 0000000..4aa266c Binary files /dev/null and b/generated/iigs/levels/level17.dat differ diff --git a/generated/iigs/levels/level18.dat b/generated/iigs/levels/level18.dat new file mode 100644 index 0000000..2303e0d Binary files /dev/null and b/generated/iigs/levels/level18.dat differ diff --git a/generated/iigs/levels/level19.dat b/generated/iigs/levels/level19.dat new file mode 100644 index 0000000..76c0dd3 Binary files /dev/null and b/generated/iigs/levels/level19.dat differ diff --git a/generated/iigs/levels/level20.dat b/generated/iigs/levels/level20.dat new file mode 100644 index 0000000..0c70215 Binary files /dev/null and b/generated/iigs/levels/level20.dat differ diff --git a/generated/iigs/levels/level21.dat b/generated/iigs/levels/level21.dat new file mode 100644 index 0000000..cd178e5 Binary files /dev/null and b/generated/iigs/levels/level21.dat differ diff --git a/generated/iigs/levels/level22.dat b/generated/iigs/levels/level22.dat new file mode 100644 index 0000000..89df634 Binary files /dev/null and b/generated/iigs/levels/level22.dat differ diff --git a/generated/iigs/levels/level23.dat b/generated/iigs/levels/level23.dat new file mode 100644 index 0000000..ccf8623 Binary files /dev/null and b/generated/iigs/levels/level23.dat differ diff --git a/generated/iigs/levels/level24.dat b/generated/iigs/levels/level24.dat new file mode 100644 index 0000000..a499a94 Binary files /dev/null and b/generated/iigs/levels/level24.dat differ diff --git a/generated/iigs/levels/title.dat b/generated/iigs/levels/title.dat new file mode 100644 index 0000000..9887b56 Binary files /dev/null and b/generated/iigs/levels/title.dat differ diff --git a/generated/iigs/sprites/sprites.spr b/generated/iigs/sprites/sprites.spr new file mode 100644 index 0000000..90ef8db Binary files /dev/null and b/generated/iigs/sprites/sprites.spr differ diff --git a/generated/iigs/tiles/tbank0.tbk b/generated/iigs/tiles/tbank0.tbk new file mode 100644 index 0000000..8fd5cd9 Binary files /dev/null and b/generated/iigs/tiles/tbank0.tbk differ diff --git a/generated/iigs/tiles/tbank1.tbk b/generated/iigs/tiles/tbank1.tbk new file mode 100644 index 0000000..8cf9956 Binary files /dev/null and b/generated/iigs/tiles/tbank1.tbk differ diff --git a/generated/iigs/tiles/tbank2.tbk b/generated/iigs/tiles/tbank2.tbk new file mode 100644 index 0000000..2ead5e1 Binary files /dev/null and b/generated/iigs/tiles/tbank2.tbk differ diff --git a/scripts/bench-staxi-title.sh b/scripts/bench-staxi-title.sh new file mode 100755 index 0000000..0bdc138 --- /dev/null +++ b/scripts/bench-staxi-title.sh @@ -0,0 +1,160 @@ +#!/usr/bin/env bash +# bench-staxi-title.sh - Space Taxi title-screen logo cadence measurement +# (the PERF-AUDIT #3 acceptance test: the logo repaints via 103 +# jlTilePasteMono calls, intended every 4th frame -- stRender.c +# titleAnimateLogo's `& 3u` divider mirroring the C64 original). +# +# Boots GS/OS under headless MAME with a WORK COPY of joey.2mg (must +# contain STAXI + its DATA tree; build with +# JOEY_DISK_EXAMPLES="STAXI" scripts/make-iigs-disk.sh), +# launches STAXI via the standard Finder keystroke timeline, then +# samples 8 known logo tile cells from the SHR framebuffer EVERY frame. +# Each frame the sampled 256 bytes fold into a rolling hash; a hash +# change means the logo visibly changed on screen (the flip-book tile +# differs on every 4-frame step, so a healthy title changes every 4 +# frames; a slow repaint stretches the observed period). +# +# Emits "STAXI-CHG frame=N sum=XXXXXX" per change, heartbeats, and a +# final verify-style summary. Post-process the change frames into +# periods with the analysis snippet in the acceptance report. +# +# Usage: scripts/bench-staxi-title.sh [label] +# MAME_ROMPATH override ROM dir (default ~/.mame/roms) +# STAXI_MAX_FRAMES frame cap (default 15000) +# STAXI_MAX_CHANGES stop after this many logo changes (default 80) +set -euo pipefail + +repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) +: "${LLVM816_ROOT:?bench-staxi-title.sh: source toolchains/env.sh first}" +CADIUS="${CADIUS:-$LLVM816_ROOT/tools/cadius/cadius}" + +sys_disk=$repo/toolchains/emulators/support/gsos-system.po +data_disk=$repo/build/iigs/bin/joey.2mg +rompath="${MAME_ROMPATH:-$HOME/.mame/roms}" +maxFrames="${STAXI_MAX_FRAMES:-15000}" +maxChanges="${STAXI_MAX_CHANGES:-80}" +label="${1:-run}" + +for f in "$sys_disk" "$data_disk"; do + [ -f "$f" ] || { echo "bench-staxi-title: missing $f" >&2; exit 2; } +done +if ! "$CADIUS" CATALOG "$data_disk" 2>/dev/null | grep -qE '^ STAXI +S16'; then + echo "bench-staxi-title: STAXI not on joey.2mg (JOEY_DISK_EXAMPLES=\"STAXI\" scripts/make-iigs-disk.sh)" >&2 + exit 2 +fi + +work=$(mktemp -d -t joeylib-staxi.XXXXXX) +trap 'rm -rf "$work"' EXIT +cp "$sys_disk" "$work/boot.po" +cp "$data_disk" "$work/joey.2mg" + +cat > "$work/staxi.lua" <> 4) & 0x0F] = true + checksum = (checksum + b) & 0xFFFFFF + end + local n = 0 + for _ in pairs(distinct) do n = n + 1 end + io.write(string.format("STAXI-%s label=$label frame=%d changes=%d distinctNibbles=%d nonZeroBytes=%d checksum=%06X\n", + tag, frame, nChanges, n, nonzero, checksum)) + io.flush() +end + +emu.register_frame_done(function() + frame = frame + 1 + while idx <= #steps and frame >= steps[idx][1] do + steps[idx][2]() + idx = idx + 1 + end + if frame >= 3800 then + local s = logoSum() + if s ~= prevSum then + io.write(string.format("STAXI-CHG frame=%d sum=%06X\n", frame, s)) + io.flush() + prevSum = s + nChanges = nChanges + 1 + if nChanges >= $maxChanges then + summary("DONE") + manager.machine:exit() + end + end + end + if frame % 1500 == 0 then + io.write(string.format("STAXI-HEART frame=%d changes=%d\n", frame, nChanges)) + io.flush() + end + if frame >= $maxFrames then + summary("TIMEOUT") + manager.machine:exit() + end +end) +LUA + +echo "bench-staxi-title[$label]: running STAXI headless under MAME (cap $maxFrames frames, $maxChanges changes)..." >&2 +cd "$work" +out=$(QT_QPA_PLATFORM=offscreen SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy \ + timeout "${STAXI_WALL_TIMEOUT:-900}" mame apple2gs \ + -rompath "$rompath" \ + -flop3 "$work/boot.po" -flop4 "$work/joey.2mg" \ + -video none -sound none -nothrottle \ + -autoboot_script "$work/staxi.lua" &1) || true + +echo "$out" | grep -E '^STAXI-(CHG|HEART|DONE|TIMEOUT)' > "$repo/build/iigs/bin/staxi-title-$label.log" || true +tail -3 "$repo/build/iigs/bin/staxi-title-$label.log" >&2 +lines=$(grep -c '^STAXI-CHG' "$repo/build/iigs/bin/staxi-title-$label.log" || true) +echo "bench-staxi-title[$label]: $lines change events -> build/iigs/bin/staxi-title-$label.log" >&2 diff --git a/scripts/diag-staxi-screen.sh b/scripts/diag-staxi-screen.sh new file mode 100755 index 0000000..0e63232 --- /dev/null +++ b/scripts/diag-staxi-screen.sh @@ -0,0 +1,143 @@ +#!/usr/bin/env bash +# diag-staxi-screen.sh - one-off diagnostic for the STAXI title test: +# boot + launch STAXI, dump the full SHR framebuffer + palettes as hex +# at a target frame, then extract JOEYLOG.TXT from the work disk so the +# game's own init/asset-load log is visible. Outputs land in +# build/iigs/bin/ (staxi-shr.hex, staxi-joeylog.txt). +set -euo pipefail + +repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) +: "${LLVM816_ROOT:?diag-staxi-screen.sh: source toolchains/env.sh first}" +CADIUS="${CADIUS:-$LLVM816_ROOT/tools/cadius/cadius}" + +sys_disk=$repo/toolchains/emulators/support/gsos-system.po +data_disk=$repo/build/iigs/bin/joey.2mg +rompath="${MAME_ROMPATH:-$HOME/.mame/roms}" +dumpFrame="${STAXI_DUMP_FRAME:-9000}" + +work=$(mktemp -d -t joeylib-staxidiag.XXXXXX) +trap 'rm -rf "$work"' EXIT +cp "$sys_disk" "$work/boot.po" +cp "$data_disk" "$work/joey.2mg" + +cat > "$work/diag.lua" <= steps[idx][1] do + steps[idx][2]() + idx = idx + 1 + end + -- Hang localization: sample the PC over 60 frames before the dump, + -- and probe NEWVIDEO (0xE0C029: bit7 = SHR on -> jlpInit ran). + if frame == $dumpFrame - 120 then + io.write(string.format("SHRPROBE newvideo=%02X shadow=%02X\n", + mem:read_u8(0xE0C029), mem:read_u8(0xE0C035))) + io.flush() + end + if frame >= $dumpFrame - 100 and frame < $dumpFrame - 40 then + local pc = cpu.state["PC"].value + local k = string.format("%06X", pc) + pcs[k] = (pcs[k] or 0) + 1 + end + if frame == $dumpFrame - 40 then + for k, v in pairs(pcs) do + io.write(string.format("SHRPC %s count=%d\n", k, v)) + end + io.flush() + end + if frame == $dumpFrame - 20 then + -- Locate the jlLog RAM ring without UBER's mailbox: bulk-read every + -- RAM bank and emit it as hex; the host greps for the build-stamp + -- text. read_range failures are reported, not swallowed. + local banks = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,0xE0,0xE1} + for _, bank in ipairs(banks) do + local base = bank * 65536 + local ok, buf = pcall(function() return mem:read_range(base, base + 65535, 8) end) + if ok and buf ~= nil then + for off = 0, 65535, 256 do + local chunk = string.sub(buf, off + 1, off + 256) + local hex = {} + for i = 1, #chunk do + hex[#hex + 1] = string.format("%02X", string.byte(chunk, i)) + end + io.write(string.format("SHRMEM %02X%04X %s\n", bank, off, table.concat(hex))) + end + else + io.write(string.format("SHRMEM-FAIL bank=%02X err=%s\n", bank, tostring(buf))) + end + end + io.flush() + end + if frame == $dumpFrame then + -- SHR pixel dump: 200 rows x 160 bytes as hex lines. + for row = 0, 199 do + local parts = {} + for b = 0, 159 do + parts[#parts + 1] = string.format("%02X", mem:read_u8(0xE12000 + row * 160 + b)) + end + io.write("SHRROW " .. table.concat(parts) .. "\n") + end + -- SCBs + palettes. + local scb = {} + for i = 0, 15 do scb[#scb + 1] = string.format("%02X", mem:read_u8(0xE19D00 + i)) end + io.write("SHRSCB " .. table.concat(scb, " ") .. "\n") + local pal = {} + for i = 0, 31 do pal[#pal + 1] = string.format("%02X", mem:read_u8(0xE19E00 + i)) end + io.write("SHRPAL " .. table.concat(pal, " ") .. "\n") + io.flush() + manager.machine:exit() + end +end) +LUA + +echo "diag-staxi: dumping SHR at frame $dumpFrame..." >&2 +cd "$work" +out=$(QT_QPA_PLATFORM=offscreen SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy \ + timeout 900 mame apple2gs \ + -rompath "$rompath" \ + -flop3 "$work/boot.po" -flop4 "$work/joey.2mg" \ + -video none -sound none -nothrottle \ + -autoboot_script "$work/diag.lua" &1) || true + +echo "$out" | grep -E '^SHR(PROBE|PC)|^SHRMEM-FAIL' | sed 's/^/diag-staxi: /' >&2 +echo "$out" | grep -E '^SHRMEM ' > "$repo/build/iigs/bin/staxi-mem.hex" || true +echo "$out" | grep -E '^SHR(ROW|SCB|PAL)' > "$repo/build/iigs/bin/staxi-shr.hex" +wc -l "$repo/build/iigs/bin/staxi-shr.hex" >&2 + +# The game flushed its log to the volume root; pull it off the WORK copy. +rm -f "$repo/build/iigs/bin/staxi-joeylog.txt" "$work/JOEYLOG.TXT"* 2>/dev/null || true +"$CADIUS" EXTRACTFILE "$work/joey.2mg" "/JOEYLIB/JOEYLOG.TXT" "$work/" >/dev/null 2>&1 || true +found=$(ls "$work"/JOEYLOG.TXT* 2>/dev/null | head -1) +if [ -n "$found" ]; then + tr -d '\r' < "$found" > "$repo/build/iigs/bin/staxi-joeylog.txt" + echo "diag-staxi: extracted JOEYLOG.TXT:" >&2 +else + echo "diag-staxi: no JOEYLOG.TXT on work disk (flush never ran?)" >&2 +fi diff --git a/stRender.c b/stRender.c index c0fd1aa..bdab0cf 100644 --- a/stRender.c +++ b/stRender.c @@ -1139,42 +1139,34 @@ static void destroySprites(void) { } +// The 1000-glyph font streams straight onto the font surface via +// jlTileBankLoadToSurface -- no 32KB staging buffer. The old +// malloc(32000) was lethal on IIgs (the clang libc heap is ~665 +// bytes and malloc returns 1, not NULL, at exhaustion -- LLVM816-ASKS +// item 4 -- so jlTileBankLoad fread the payload through pointer 1 and +// shredded bank 0 before the first present), and a 32KB static blows +// the bank-0 BSS ceiling. The streaming loader exists for exactly +// this case; its block grid matches ST_FONT_COLS row-major layout. static bool loadFontSheet(jlSurfaceT *stage) { - jlTileT *fontTiles; - uint16_t count; - uint16_t i; - uint16_t palette[16]; + uint16_t count; + uint16_t palette[16]; (void)stage; - // Load font.tbk into a temp tile array, then paste each glyph - // into a regular surface that jlDrawText can sample. ~32 KB of - // temp memory for the 1000-tile font; freed before returning. - fontTiles = (jlTileT *)malloc(sizeof(jlTileT) * ST_FONT_TILES_MAX); - if (fontTiles == NULL) { - return false; - } - count = jlTileBankLoad(ST_FONT_PATH, fontTiles, ST_FONT_TILES_MAX, - NULL, palette); - if (count == 0u) { - free(fontTiles); - return false; - } gRender.fontSurface = jlSurfaceCreate(); if (gRender.fontSurface == NULL) { - free(fontTiles); + return false; + } + count = jlTileBankLoadToSurface(ST_FONT_PATH, gRender.fontSurface, palette); + if (count == 0u) { + jlSurfaceDestroy(gRender.fontSurface); + gRender.fontSurface = NULL; return false; } // Font palette is authoritative for the font surface so jlDrawText // reads back the authored colors. Stage palette is untouched. jlPaletteSet(gRender.fontSurface, 0u, palette); jlScbSetRange(gRender.fontSurface, 0, SURFACE_HEIGHT - 1, 0); - for (i = 0u; i < count; i++) { - uint8_t bx = (uint8_t)(i % ST_FONT_COLS); - uint8_t by = (uint8_t)(i / ST_FONT_COLS); - jlTilePaste(gRender.fontSurface, bx, by, &fontTiles[i]); - } gRender.fontReady = true; - free(fontTiles); return true; }