750 lines
45 KiB
Markdown
750 lines
45 KiB
Markdown
# PERF-AUDIT Fix Plan
|
|
|
|
Execution plan for all 71 confirmed findings in PERF-AUDIT.md plus the
|
|
benchmark-harness fix from its preamble. Finding numbers below (#N) are
|
|
PERF-AUDIT.md numbers. Read each finding's "Verifier correction" before
|
|
implementing -- several tighten the fix or the claimed impact, and this
|
|
plan encodes those corrections as constraints.
|
|
|
|
This plan was itself adversarially reviewed (completeness, ordering
|
|
conflicts, gate adequacy -- 31 issues found and folded in). The most
|
|
important consequence: Phase 0 builds the verification harness that
|
|
every later gate depends on, because the existing gates cannot see most
|
|
of the changes this plan makes.
|
|
|
|
Ground rules for every phase:
|
|
- Source toolchains/env.sh before any make; a missing $AMIGA_CC etc.
|
|
silently produces stale binaries.
|
|
- After editing any IIgs .s/.asm file, delete the affected example
|
|
binaries before rebuilding (stale-binary trap).
|
|
- Hash gates compare against the golden hashes captured at the end of
|
|
Phase 0 (the harness additions change the old hashes once, then they
|
|
are frozen).
|
|
- "iigs-verify" in a gate always names its example and compares
|
|
before/after checksums: `make iigs-verify VERIFY_EXAMPLE=<name>`
|
|
(default is draw; that only covers draw-path changes). It reads SHR
|
|
memory -- there is NO MAME headless screenshot; do not plan for one.
|
|
- Scott commits; the plan never includes git operations.
|
|
- Style rules apply to all touched code (camelCase, // comments, braces
|
|
always, prototypes at top and alphabetized, two blank lines between
|
|
functions, ASCII only).
|
|
|
|
Phase order rationale: fix the clock and build the gates first
|
|
(otherwise no perf change can be validated and most bug fixes have no
|
|
covering check), then correctness bugs, then shared infrastructure,
|
|
then per-subsystem work, and the new platform asm last (highest effort
|
|
and risk, benefits from everything before it being measurable).
|
|
|
|
---
|
|
|
|
## Phase 0 -- Fix the clock and build the verification harness
|
|
|
|
The audit's headline: every small-op UBER number carries ~600-800
|
|
cycles of per-iteration harness tax on IIgs (GetTick toolbox chain).
|
|
And the plan review's headline: most fixes below are invisible to the
|
|
current gates (UBER only hashes on-surface compiled-path ops; Amiga/ST
|
|
have no screen-content check; several ops are not exercised at all).
|
|
Phase 0 fixes both.
|
|
|
|
1. **uber.c batch polling.** In runForFrames, run the op in batches
|
|
between jlFrameCount() polls. Batch adaptively: batch=1 for ops
|
|
slower than ~1 frame (present, surfaceClear, fillRect-full would
|
|
otherwise overrun the window by up to a full batch), 8-16 for fast
|
|
ops (pick from the first iteration's duration). Fix the false
|
|
"~10-30 cyc" comment. Ops are fixed-coordinate/idempotent (review
|
|
verified), so batching does not change what an iteration means.
|
|
2. **#15 + #71** -- collapse the IIgs jlFrameCount forwarding: declare
|
|
iigsGetTickWord in the IIgs section of port.h and
|
|
`#define jlpFrameCount() iigsGetTickWord()`; delete the hal.c
|
|
wrapper AND its stale 15-line $C019/gFrameCount comment (#71 -- the
|
|
comment sits on the function being deleted, so it lands here, not in
|
|
Phase 8); put a one-line GetTick pointer next to the new macro.
|
|
Remove the now-stale JL_HAS_FRAME_COUNT entry at platform.h:108.
|
|
CAUTION (verifier): do NOT do the same for jlpFrameHz -- gFrameHz is
|
|
hal.c-private state; leave it a function.
|
|
3. **uber.c non-timed correctness section.** New hashed/logged checks
|
|
that run after the timed ops and before the final hash, added NOW so
|
|
golden hashes cover every path later phases touch:
|
|
- edge-case draws: off-surface pixel spam (both signs, both axes),
|
|
jlDrawRect w=65535 and h=2 cases, jlFillCircle huge-r (r=300 with
|
|
clipping, r=40000 full-coverage), jlDrawLine far-off-surface
|
|
endpoints;
|
|
- clipped-sprite draws: all four edges, a corner, fully off-surface
|
|
(exercises the interpreted path on every port);
|
|
- jlTilePasteMono and jlFloodFill ops drawn into the stage (neither
|
|
is currently in the hashed set -- Phase 7 replaces both paths);
|
|
- jlDrawText including a string starting out-of-range (Phase 5
|
|
changes its validation);
|
|
- jlPaletteSet readback assert via jlPaletteGet: one conforming row,
|
|
one deliberately non-conforming row, row[0] forced to 0x0000,
|
|
logged PASS/FAIL (palette is not in the surface hash);
|
|
- jlRandom golden-sequence check (first N values from a known seed);
|
|
- allocator torture: sprite create/compile/destroy churn to fragment
|
|
then compact the arena (#5/#34 coverage), a sprite-bank
|
|
load/destroy pass (#31 coverage -- allocator mismatch is fatal on
|
|
IIgs), and a jlShutdown -> jlInit -> destroy-stale-sprite sequence
|
|
(#34) IF the harness structure allows re-init; otherwise this one
|
|
becomes a dedicated tiny example.
|
|
4. **bench-iigs.sh** -- the IIgs capture path, which does not exist
|
|
today (PERF.md's IIgs column was carried over, not re-run): boot
|
|
UBER off a writable joey.2mg under headless MAME, cadius-extract
|
|
joeylog.txt afterward (or extend the verify-iigs.sh Lua to dump the
|
|
log lines from emulated memory). Without this, no "hashes identical
|
|
x4" gate in this plan is executable on the reference port.
|
|
5. **millis host harness** -- audioSfxMix-style host compile of
|
|
genericPort millis logic (check-blank.sh pattern) driving simulated
|
|
frame-delta sequences including the 16-bit wrap, for Phase 1 #23.
|
|
6. **#25 (first half)** -- run the new harness on all four ports;
|
|
record the new honest baseline table in PERF.md marked
|
|
post-clock-fix. MANDATORY: tag the SEI-holding rows (present,
|
|
surfaceClear, fillRect 320x200) as clock-unreliable in the table --
|
|
the SEI tick-loss artifact survives the batching fix.
|
|
7. **#73 (landed during Phase 0 out of necessity)** -- DOS jlpFrameCount
|
|
was an $3DA edge-poll counter that lost every frame whose ~1ms
|
|
retrace pulse fell between calls; the batched timing loop stalled on
|
|
it, and it had been inflating the old DOS column 5-40x on slow ops.
|
|
Now derived from the PIT/uclock millis source (poll-rate-independent);
|
|
jlpWaitVBL still syncs to real retrace.
|
|
8. **#76 (landed during Phase 0 out of necessity)** -- IIgs jlpWaitVBL
|
|
polled $C019's VBL bit, which never asserts under MAME's apple2gs:
|
|
every VBL-paced program hung forever in emulation, and UBER (the
|
|
only in-tree caller) could never complete headless -- the real
|
|
reason PERF.md's IIgs column was "carried over". Now waits on a
|
|
GetTick change (VBL-interrupt-driven, same boundary semantics).
|
|
9. **Freeze golden hashes** -- DONE 2026-07-04 for DOS/ST/Amiga in
|
|
tests/goldens/uber/ (43/43/42 hash lines; the Amiga log lost its
|
|
final check line to the bench-script timeout -- re-capture with a
|
|
longer TIMEOUT when convenient). The IIgs golden is PARTIAL (3 op
|
|
lines, codegen-off): the clang-era IIgs port cannot complete UBER
|
|
yet -- sprite codegen corrupts memory (#77), the codegen-off run
|
|
stalls in the jlDrawPixel timing window after 3 ops (uninvestigated
|
|
-- first Phase 1 stabilization item), and jlAudioInit fails
|
|
headless. PERF.md documents the 3-port baseline and the IIgs
|
|
status; the old IIgs column was ORCA-era and is retired.
|
|
|
|
Gate: 4-port build; all new correctness checks PASS; goldens recorded.
|
|
Expect small-op numbers to jump 2-10x against the old table.
|
|
|
|
Effort: ~half a day to a day (the harness additions are the bulk).
|
|
|
|
---
|
|
|
|
## Phase 1 -- Correctness bugs + IIgs stabilization
|
|
|
|
All behavior fixes land before perf work so later phases optimize
|
|
correct code. Every item here has a covering check from Phase 0.
|
|
|
|
THE IIGS STABILIZATION CLUSTER COMPLETED 2026-07-04: UBER runs
|
|
end-to-end (green screen, audio live, all checks PASS, 39/43 hashes
|
|
matching DOS, honest floor numbers in PERF.md). Fixed along the way:
|
|
#81 (phantom stage / tiny-heap malloc-returns-1), #82 (RAM-ring
|
|
logger), #83/#86 (line+fillCircle asm DBR-vs-D scratch stash), #85
|
|
(framebuffer + bank-0 MM reservations), audio packaging. #77 is
|
|
root-caused to the toolchain (loader does not MM-reserve segment
|
|
BSS spans; arena landed at $08:4200 inside the app's own bank) --
|
|
codegen stays off until LLVM816-ASKS item 5 lands. New/remaining
|
|
items for the rest of Phase 1: #87 (circle-outline pixel divergence,
|
|
the last 4 hash mismatches). Original cluster notes follow:
|
|
(a) #77 sprite-codegen corruption (MAME write-watchpoint hunt);
|
|
(b) the codegen-off UBER "stall" -- RESOLVED 2026-07-04 in three
|
|
layers: #80 (log-durability illusion misdirected the hunt), #81
|
|
(the master bug: 665-byte unprotected heap + malloc-returns-1 ->
|
|
phantom stage struct; fixed by moving all core structs to
|
|
jlpBigAlloc), and #82 (per-line floppy log writes stalled the app
|
|
in SmartPort firmware; fixed with the RAM-ring logger + live Lua
|
|
drain). UBER now completes end-to-end on IIgs (33 ops + 17 checks,
|
|
green screen); 20 hashes match DOS. Remaining IIgs defects filed
|
|
as #83 (diag-line asm pixel divergence) and #84 (interpreted
|
|
sprite ops ~1000x slow + wrong) -- both queued in this cluster;
|
|
(c) headless jlAudioInit failure -- FIXED 2026-07-04: the audio code
|
|
was wired all along; the merlin32-built "ntpplayer" binary was
|
|
simply never packed onto joey.2mg. iigs-disk now builds and adds
|
|
it; UBER logs "audioInit OK";
|
|
(d) #79 flag to the llvm816 session (libc memset/memcpy byte loops).
|
|
Only after (b) is fixed can the IIgs golden hashes be frozen and the
|
|
IIgs column return to PERF.md.
|
|
|
|
1. **#1 (CRITICAL)** draw.c:549 -- non-IIgs jlDrawPixel: bounds-check
|
|
x/y (same `(uint16_t)x >= SURFACE_WIDTH` form as the IIgs branch)
|
|
after the NULL check and return early; then call jlpDrawPixel
|
|
directly and mark. Deletes the redundant plotPixelNoMark layer on
|
|
the public path.
|
|
2. **#11** draw.c:591 -- jlDrawRect general path: compute bottom/right
|
|
edge coordinates in int32_t; skip an edge whose coordinate is
|
|
off-surface instead of letting the int16_t cast wrap. ALSO (from the
|
|
#65 verifier correction, same function): skip the interior-edge
|
|
fills entirely when h == 2, so jlDrawRect stops passing h == 0 rects
|
|
into fillRectOnSurface/surfaceMarkDirtyRect -- this restores the
|
|
"already clipped, positive dimensions" contract that Phase 2 #65
|
|
relies on before it drops the guard.
|
|
3. **#72** draw.c:417 -- jlDrawLine H/V fast paths: clip x0 (resp. y0)
|
|
into range BEFORE anchoring the clamped span (`if (x0 < 0) { span +=
|
|
x0; x0 = 0; }` after the 32-bit span, return if span <= 0, then clamp
|
|
to the surface size). Today a line from far off-surface through the
|
|
whole visible row draws nothing. Found during Phase 0 harness work;
|
|
covered by the "edge-lines" uber check (its hash changes with this
|
|
fix).
|
|
4. **#12** draw.c:656 -- jlFillCircle/jlDrawCircle huge-r handling:
|
|
32-bit full-coverage test (farthest corner dist^2 <= r^2 -> full
|
|
surface fill / early out), clamp the span loop to intersecting rows,
|
|
uint16_t loop variable. Verifier notes: seed yy = y0*y0 when jumping
|
|
into the row range (the incremental chain breaks on a jump); the
|
|
initial x walk-down from r stays O(r) once per call -- document it.
|
|
5. **#23** genericPort.c:40 -- millisElapsed: accumulate milliseconds
|
|
incrementally from uint16_t frame deltas (per verifier: do NOT keep
|
|
a total frame count and multiply -- that re-overflows at ~20h; carry
|
|
the fractional remainder, e.g. delta*20 at 50Hz, delta*50/3 with
|
|
mod-3 carry at 60Hz). Update the statics with explicit
|
|
load/add/store-style C (no `++`/`+=` on globals -- ORCA-C inc-abs
|
|
DBR trap). Document the poll-at-least-once-per-65535-frames bound
|
|
(~18 min @60Hz, ~22 min @50Hz -- NOT hours). Delete the dangling
|
|
iigs/hal.c:382-386 comment. Verify against the Phase 0 host harness.
|
|
6. **#8** codegenArena.c:236 -- IIgs: clamp totalBytes to 64KB with a
|
|
specific coreSetError string on failure; fix adventure.c to set
|
|
codegenBytes per-platform (#ifdef: small on chunky ports, 256KB only
|
|
where a flat heap exists). Document the cap at jlConfigT.codegenBytes.
|
|
7. **#46** sprite.c:346 -- gate the IIgs/DOS compiled-draw fast path on
|
|
`routineOffsets[shift][SPRITE_OP_DRAW] != SPRITE_NOT_COMPILED` (read
|
|
once via the existing byte-pointer idiom; reuse for the call).
|
|
8. **#34** codegenArena.c:294 -- shutdown/re-init dangling slots.
|
|
PREFERRED (the audit's own fix): a small sprite registry in sprite.c
|
|
-- spriteSystemShutdown, called from jlShutdown BEFORE
|
|
codegenArenaShutdown, walks live sprites and NULLs sp->slot. Zero
|
|
hot-path cost and it fixes BOTH arms: the destroy-after-reinit heap
|
|
corruption AND the draw-after-reinit stale-offset execution (an
|
|
epoch checked only at free time would miss the second arm).
|
|
Alternatives if the registry is unwanted: arena epoch stamped into
|
|
jlSpriteT and checked at every dispatch gate (hot-path cost), or a
|
|
documented "jlShutdown invalidates all sprites" contract (doc-only).
|
|
9. **#5** spriteCompile.c:202 -- on codegenArenaAlloc failure, run
|
|
codegenArenaCompact() and retry once; coreSetError on final failure
|
|
so prewarm failures are diagnosable.
|
|
10. **#31** assetLoad.c:167 -- move sprite allocations to
|
|
jlpBigAlloc/jlpBigFree UNIFORMLY. Alloc sites: cel/tileData buffers
|
|
(assetLoad.c:167, sprite.c jlSpriteCreateFromSurface buf) AND the
|
|
jlSpriteT header mallocs (sprite.c:215, sprite.c:306,
|
|
assetLoad.c:175 -- verifier requires these switch in the same
|
|
change). Free sites -- ALL FOUR for tileData plus the headers:
|
|
jlSpriteDestroy (sprite.c:325 tileData, sprite.c free(sp)), the
|
|
jlSpriteCreateFromSurface error path (sprite.c:308), and both
|
|
assetLoad error paths (assetLoad.c:172, assetLoad.c:177). A single
|
|
missed free() of a jlpBigAlloc pointer corrupts the IIgs heap
|
|
(BIG_ALLOC_HDR offset), and three of these sites are failure-path
|
|
only -- exercised by the Phase 0 torture check.
|
|
11. **#58** atarist/hal.c:1410 -- DELETE the dead dst/pd NULL checks
|
|
(callers validate; the finding's category is
|
|
unnecessary-conditional, not misordered-check), and apply the same
|
|
cleanup to the identical pattern in jlpTileSnap (deref at 1454,
|
|
dead checks at 1458/1462) per the verifier correction.
|
|
12. **#74** -- root-cause the DOS jlSpriteCompile failure (pre-existing;
|
|
UBER logs it; every DOS sprite bench measured the interpreter).
|
|
Instrument the sizing/emit/arena-alloc chain under DOSBox; the x86
|
|
emitter itself emits real code for shifts 0-1. Gates Phase 4's
|
|
compiled-path verification on DOS.
|
|
13. **#75** -- resolve the jlTilePasteMono three-way pixel divergence
|
|
(chunky generic vs Amiga vs ST overrides, caught by the Phase 0
|
|
tilePasteMono hash). Decide the contract truth (tile.h: non-zero
|
|
mono nibble -> fgColor), fix the divergent implementations, and
|
|
re-golden that hash line on the fixed ports. Blocks Phase 7 #3.
|
|
14. **#77** -- root-cause the IIgs sprite-codegen memory corruption
|
|
(MAME write-watchpoint on the stage struct / gStage static during
|
|
jlSpriteCompile + first compiled draw). Until fixed, IIgs builds
|
|
run JOEY_IIGS_SPRITE_CODEGEN=0 and the IIgs baseline measures the
|
|
interpreter. HIGHEST PRIORITY of the phase with #74: both reference
|
|
platforms' compiled sprite paths are currently dead.
|
|
15. **#78** -- fix ST jlFillCircle and jlTileFill pixel divergence vs
|
|
the chunky reference (DOS==Amiga agree); re-golden ST hash lines.
|
|
16. **#79** -- FLAG TO SCOTT (toolchain-owned): llvm-mos runtime
|
|
memset/memcpy are ~30 cyc/byte byte-loops vs ORCA's ~7 cyc/byte
|
|
MVN; reference-platform regression ~4x on every memset/memcpy path.
|
|
Library-side mitigations are #9/#19/#21; the libc fix belongs to
|
|
the llvm816 session.
|
|
|
|
Gate: 4-port builds; Phase 0 correctness checks all PASS (they cover
|
|
every edge case fixed here); hashes match Phase 0 goldens;
|
|
`make iigs-verify VERIFY_EXAMPLE=draw` checksum unchanged.
|
|
|
|
Effort: ~4-6 hours.
|
|
|
|
---
|
|
|
|
## Phase 2 -- Shared hot-path infrastructure
|
|
|
|
The cross-TU-call cluster. Everything here touches surfaceInternal.h /
|
|
port.h / surface.c / draw.c and ripples through every port; do it as
|
|
one unit with a full 4-port verify.
|
|
|
|
1. **#10 + #17** -- make gStage a plain extern (`extern jlSurfaceT
|
|
*gStage;` in surfaceInternal.h, drop static in surface.c; keep
|
|
jlStageGet() as the public accessor). Replace jlStageGet() with a
|
|
direct `== gStage` compare in: port.h IIgs macros (jlpFillRect,
|
|
jlpSurfaceClear, jlpFillCircle), draw.c:533 jlDrawPixel, palette.c,
|
|
scb.c, surface.c internal uses. Precedent: codegenArenaBase did
|
|
exactly this.
|
|
2. **#20 + #65** -- surfaceMarkDirtyRect: move the stage test and the
|
|
h==1 widen into a macro in surfaceInternal.h (ORCA/C 2.2.1 has no
|
|
inline -- macro form is a hard constraint; audit macro argument
|
|
usage for multiple-evaluation before converting call sites).
|
|
Non-stage surfaces then cost one inline compare; single-row marks
|
|
widen with no call; multi-row marks keep one call
|
|
(iigsMarkDirtyRowsInner on IIgs, C loop elsewhere). Drop the w/h
|
|
guard (#65) ONLY after Phase 1 item 2 restored the contract
|
|
(jlDrawRect h==2 was the violator); empty ranges then cannot reach
|
|
the macro.
|
|
3. **#21** -- stageDirtyClearAll: two memsets (0xFF / 0x00), matching
|
|
its sibling surfaceMarkDirtyAll.
|
|
4. **#47** -- dirty-row test unification, TWO-macro form per the
|
|
verifier correction (NOT a blanket four-site replacement): convert
|
|
DOS/ST to STAGE_DIRTY_ROW_CLEAN (min > max); keep Amiga's
|
|
`min != 0xFF` as a second documented macro (STAGE_DIRTY_ROW_TOUCHED
|
|
-- it is deliberate and cheaper under the clipped-marks invariant);
|
|
pin the IIgs present asm's compare with a comment naming the
|
|
sentinel values it assumes.
|
|
5. **#14 + #24** -- pixel read: expose the chunky nibble read as a
|
|
macro in surfaceInternal.h using SURFACE_ROW_OFFSET (verifier:
|
|
macro, not static inline, so floodFillInternal's chunky-fallback
|
|
reads can use it too). jlSamplePixel uses it inline after its bounds
|
|
check on chunky builds; jlpGenericSamplePixel body becomes the same
|
|
macro. (Do NOT expect a sprite-capture delta: the sprite.c:296-297
|
|
branch only runs on planar ports, which use their own platform
|
|
jlpSamplePixel -- review corrected the audit here.)
|
|
6. **#13** -- jlFillRect: add the 16-bit on-surface fast path before
|
|
the 32-bit clip, in the guarded form (test `w <= SURFACE_WIDTH` and
|
|
`h <= SURFACE_HEIGHT` FIRST, then `x >= 0 && y >= 0 &&
|
|
x <= (int16_t)(SURFACE_WIDTH - (int16_t)w)` etc. -- guarding before
|
|
the subtraction is what makes 16-bit safe against w up to 65535).
|
|
Verifier: keep the three span emitters calling fillRectOnSurface
|
|
directly; do NOT reroute them.
|
|
7. **#16** -- jlPaletteSet: memcpy the row + force row[0] = 0x0000,
|
|
dropping the undocumented per-entry $0RGB re-mask (pending the
|
|
decision-table sign-off; if the mask must stay, at least widen the
|
|
loop counter to uint16_t). Mirrors jlPaletteGet. Covered by the
|
|
Phase 0 palette readback assert (the surface hash cannot see
|
|
palette changes).
|
|
|
|
Gate: hashes match goldens x4 (dirty-band changes are hash-blind --
|
|
that is what the next line is for); present-output check: captureDos.sh
|
|
against a pre-phase golden PNG for DOS, iigs-verify
|
|
VERIFY_EXAMPLE=draw checksum compare for IIgs, and a documented
|
|
run-amiga.sh / run-atarist.sh visual pass of the draw example after
|
|
multiple presents (68k C dirty loops diverge from the IIgs asm; a
|
|
dirty-band under-mark shows as stale rows only at present time).
|
|
Capture bench delta against the Phase 0 baseline.
|
|
|
|
Effort: ~4-6 hours (mostly the 4-port present verification).
|
|
|
|
---
|
|
|
|
## Phase 3 -- draw.c micro-optimizations
|
|
|
|
1. **#9 + #37 + #36** -- jlDrawLine/jlDrawCircle fallbacks: compute the
|
|
dirty box from the endpoints (line) / center+radius (circle) before
|
|
the loop, clamped after, as now; delete the per-pixel and
|
|
per-iteration box accumulation (the circle's 8 compares per
|
|
iteration go with it). RISK NOTE: a too-small precomputed box
|
|
under-marks and is invisible to hashes -- the Phase 2 gate's
|
|
present-output checks re-run here.
|
|
2. **#38** -- jlFillCircle C span loop: one bounding-box dirty mark
|
|
after the loop instead of per-span marks. Over-approximates each
|
|
row's band to the circle width; pixel output identical; matches
|
|
what the IIgs asm path already does. (Over-marking is the SAFE
|
|
direction, unlike item 1.)
|
|
3. **#35** -- floodFillInternal C fallback: hoist the matchEqual
|
|
predicate out of the walk loops (two loop variants), use the Phase 2
|
|
read macro instead of jlpSamplePixel calls where the port is chunky.
|
|
4. **#61** -- plotPixelNoMark: drop the NULL re-check (all callers
|
|
validate); KEEP the per-pixel bounds check (load-bearing for
|
|
clipping).
|
|
5. **#62** -- define the shared on-surface rect predicate ONCE, here,
|
|
in its final form (review blocker): the #13-style guarded 16-bit
|
|
macro -- `w <= SURFACE_WIDTH && h <= SURFACE_HEIGHT` first, then the
|
|
subtraction compares. That form is correct for BOTH users: jlDrawRect
|
|
(raw uint16_t w/h up to 65535) and sprite.c (widthPx/heightPx up to
|
|
2040 = 255 tiles * 8 -- NOT "bounded by 320/200"). jlDrawRect's
|
|
fast-path test switches to it now; sprite.c merges in Phase 4 as a
|
|
pure call-site change with NO rewrite of the macro.
|
|
|
|
Gate: hashes match goldens x4 (the Phase 0 edge-case checks cover the
|
|
w=65535 / huge-r paths through the new predicate); present-output
|
|
checks as in Phase 2; bench diag-line/circle deltas on DOS (the only
|
|
port that always runs the fallbacks).
|
|
|
|
Effort: ~2-3 hours.
|
|
|
|
---
|
|
|
|
## Phase 4 -- Sprite pipeline + codegen
|
|
|
|
Landing order within the phase (review): land items 2-12 first as one
|
|
reviewable, hash-gated unit; land item 1 (the risky inner-loop rewrite)
|
|
LAST as its own change with its own before/after clipped-sprite hash
|
|
comparison, so a failed rewrite reverts alone. Within items 2-12, do
|
|
the deletions (#55, #27) BEFORE the shared-derive refactor (#29 + #64)
|
|
so the refactor never touches code that is about to be deleted.
|
|
|
|
1. **#2 + #18 + #45** -- rewrite spriteDrawInterpreted's inner loop
|
|
(LAND LAST):
|
|
- hoist row-invariant source math out of the column loop (#45),
|
|
- carry dst byte pointer + parity incrementally (kill the
|
|
writeDstNibble call per pixel, drop its redundant & 0x0F masks),
|
|
- aligned-phase byte loop: src byte == 0 skips two pixels,
|
|
both-nibbles-opaque stores the whole byte, single-nibble merges
|
|
stay RMW; chunk per tile column (4 contiguous bytes, then advance
|
|
srcTileRow by TILE_BYTES -- verifier: the naive *src++ sketch
|
|
glosses this); keep the per-pixel walker for the misaligned phase.
|
|
Rollback: revert just the loop; the writeDstNibble path stays in git
|
|
history.
|
|
2. **#19** -- jlSpriteSaveUnder/RestoreUnder fallbacks: hoist
|
|
SURFACE_ROW_OFFSET-based start pointers, walk with += stride adds;
|
|
kill both per-row multiplies.
|
|
3. **#44** -- switch isFullyOnSurface's body to the Phase 3 shared
|
|
predicate (pure call-site merge; the macro is already 16-bit-safe
|
|
for the 2040-px sprite domain -- do not modify it).
|
|
4. **#55** -- remove spriteEmit68k.c from the Amiga/ST build lists.
|
|
DECISION for Scott: delete the file (git history keeps it) or leave
|
|
it unbuilt.
|
|
5. **#27** -- Amiga compiled save/restore dispatchers: replace the
|
|
bodies with ST-style empty linker stubs -- FULL DELETION BREAKS THE
|
|
LINK (sprite.c calls them unconditionally; verifier correction).
|
|
Delete the shift-alias loop and the aspirational comments at
|
|
spriteCompile.c:172-178/241-245. Note for the future: when real
|
|
Amiga save/restore emitters land, the restore gate must accept
|
|
planar copyBytes = spriteBytesPerRow+4.
|
|
6. **#29 + #64** -- one shared macro/helper derives (shift, routeIdx,
|
|
routeOffset, fnAddr) once; the surviving dispatcher gates and
|
|
spriteCompiled* callees consume it.
|
|
7. **#28** -- spriteCompiledDraw (IIgs): byte-pointer idiom for
|
|
routineOffsets like the rest of the file.
|
|
8. **#59** -- SPLIT_BACKUP_CACHED: move the split cache per-sprite (the
|
|
jlSpriteT already caches banks) or delete it; measure both on IIgs.
|
|
9. **#32** -- jlSpriteBankLoad calls spriteInitFields (then overrides
|
|
ownsTileData); fix the spriteInternal.h comment.
|
|
10. **#39** -- DEFAULT_CODEGEN_BYTES per-platform (#ifdef: keep 8KB
|
|
IIgs, size 68k ports from their emitters' measured bytes/sprite;
|
|
document at jlConfigT).
|
|
11. **#56** -- dedupe emitter helpers (c2pColumn/putWord into a shared
|
|
m68k header; shiftedByteAt/spriteSourceByte into a shared codegen
|
|
header).
|
|
12. **#54 + #63** -- fix the stale spriteEmitIigs.c contract comment
|
|
and the sprite.c:343 "IIgs never takes this" comment.
|
|
|
|
Gate: hashes match goldens x4 -- the Phase 0 clipped-sprite checks are
|
|
the load-bearing coverage here (UBER's timed sprite ops are compiled
|
|
and on-surface; they cannot see interpreter regressions).
|
|
`make iigs-verify VERIFY_EXAMPLE=sprite` checksum before/after;
|
|
captureDos.sh sprite example compare on DOS. Bench: clipped-sprite ops
|
|
expect 2-3x; compiled ops modest gains from dispatch dedup.
|
|
|
|
Effort: ~1-2 days.
|
|
|
|
---
|
|
|
|
## Phase 5 -- Tile path + generic fallbacks
|
|
|
|
1. **#22** -- jlDrawText: validate bx/by once at entry (verifier: the
|
|
wrap logic does not sanitize the FIRST position -- reject
|
|
out-of-range starts), validate srcBx/srcBy from asciiMap per glyph,
|
|
call jlpTileCopyMasked directly, accumulate one union dirty rect for
|
|
the string. Covered by the Phase 0 drawText hash check (uber.c has
|
|
no other drawText coverage).
|
|
2. **#48** -- tile.c wrappers: shifts (<< 3) for pixel coords, or fold
|
|
coord derivation into the dirty-mark call site.
|
|
3. **#51** -- jlpGenericTileCopyMasked: whole-byte store when both
|
|
nibbles are opaque; keep the merge for mixed bytes. Bit-identical.
|
|
4. **#50 + #70** -- shared 4-byte row-copy macro used by TileCopy,
|
|
TilePaste, TileSnap; jlpGenericTileFill uses GENERIC_TILE_ROW0.
|
|
5. **#69** -- drop the duplicate fg/bg masks (mask once in the public
|
|
wrapper, document the precondition in the generic).
|
|
6. **#49** -- jlpGenericFillRect: hoist pxStart/pxEnd/midBytes/edge
|
|
nibble work out of the row loop; walk the row pointer with
|
|
+= stride.
|
|
7. **#30** -- assetLoad constants: use TILE_BYTES from joey/tile.h;
|
|
derive ASSET_MAX_TILES_W/H from SURFACE_WIDTH/HEIGHT and
|
|
TILE_PIXELS_PER_SIDE.
|
|
8. **#66, #67, #68, #57, #60** -- delete SURFACE_PIXELS_PER_WORD;
|
|
200 -> SURFACE_HEIGHT in the LUT decl; fix tile.c stale comment; fix
|
|
surface.h hash doc; fix or implement the assetLoad segment comment
|
|
(DECISION: add the segment directive it promises, or correct the
|
|
comment -- check IIgs root-segment pressure first).
|
|
|
|
Gate: hashes match goldens x4 (#51 especially must be bit-identical;
|
|
the drawText check covers #22); DOS bench deltas on tile ops (DOS runs
|
|
the generics); hello example on DOS + IIgs for text rendering.
|
|
|
|
Effort: ~3-4 hours.
|
|
|
|
---
|
|
|
|
## Phase 6 -- SFX mixer (DOS/ST builds only)
|
|
|
|
Verification FIRST (review: no named gate could actually verify this
|
|
phase): audioSfxMix.c is portable C -- build a host harness on the
|
|
check-blank.sh pattern that compiles the OLD and NEW mixer with host
|
|
cc, mixes a fixed seed sample set (fixed-buffer and streaming slots,
|
|
rate conversion up and down), and diffs output buffers. This proves #7
|
|
and #33 bit-exact and bounds #6 to 1 LSB concretely before any
|
|
emulator run.
|
|
|
|
1. **#6** -- interp(): the verifier-validated int16 form (explicit
|
|
int16_t locals so gcc emits one muls.w):
|
|
`int16_t d = (int16_t)(s1 - s0);`
|
|
`int16_t fH = (int16_t)(uint16_t)(frac >> 1);`
|
|
`return (int)s0 + (int)(((int32_t)d * fH) >> 15);`
|
|
Output may differ by 1 LSB of 8-bit audio (inaudible) -- accepted
|
|
deviation, document it.
|
|
2. **#7** -- posFp: uint32_t index + uint16_t frac with carry-compare;
|
|
split stepFp once per slot; update audioSfxSlotArm/ArmStream/
|
|
streamRefill. Bit-identical.
|
|
3. **#33** -- cache slot fields in locals across the inner loop
|
|
(volatile dst stores force re-reads today), re-reading only after
|
|
streamRefill; AND split the fixed-buffer vs streaming cases into two
|
|
inner loops so the loop-invariant isStream branch runs once per slot
|
|
(both halves are the audited fix; the plan review caught the split
|
|
being dropped).
|
|
|
|
Gate: host-harness diff (above); DOS + ST builds; audio example smoke
|
|
on DOSBox/Hatari; absence of the ST ~83ms refill warning under load as
|
|
a secondary signal.
|
|
|
|
Effort: ~3-4 hours including the harness.
|
|
|
|
---
|
|
|
|
## Phase 7 -- Platform override work (biggest wins, biggest effort)
|
|
|
|
Each item is independently landable. Golden hashes for jlTilePasteMono
|
|
and jlFloodFill were captured in Phase 0 against the generic paths --
|
|
the new implementations must reproduce them exactly.
|
|
|
|
1. **#3** -- IIgs jlpTilePasteMono: new iigsTilePasteMonoInner in
|
|
joeyDraw.s -- 16-entry nibble-pair LUT expands each mono byte
|
|
directly into surface rows, no intermediate jlTileT, no second paste
|
|
pass. Add JL_HAS_TILE_PASTE_MONO to platform.h IIgs block + port.h
|
|
macro. ORCA-M rules: explicit LONGA/LONGI after every REP/SEP, same
|
|
load segment as the C callers (DRAWPRIMS), delete stale example
|
|
binaries before rebuild. Gate: uber tilePasteMono hash identical to
|
|
the Phase 0 generic-path golden on IIgs; `make iigs-verify
|
|
VERIFY_EXAMPLE=spacetaxi` before/after (title screen is the
|
|
acceptance test -- logo repaint currently costs ~10 frame budgets
|
|
per 4-frame period).
|
|
2. **#4** -- ST flood plane hooks: implement jlpFloodWalkPlanes +
|
|
jlpFloodScanRowPlanes against the word-interleaved StPlanarT layout,
|
|
modeled on the Amiga versions (walk 16 px per 4-word group via
|
|
combined match masks). Pure 68000, no blitter. C first; hand-asm
|
|
only if the C version misses the target. Update the port.h comment
|
|
claiming these hooks are Amiga-only. Gate: uber floodFill hash
|
|
identical to golden on ST; Hatari flood timing (expect ~5-15x).
|
|
3. **#42** -- DOS: #define the planar no-op hooks to ((void)0) like the
|
|
IIgs block.
|
|
4. **#41** -- fillWord derivation: pick ONE owner. Either the asm
|
|
derives it from the nibble (as the port.h comment claims) or C keeps
|
|
deriving via a 16-entry uint16_t LUT instead of the * 0x1111
|
|
multiply helper. Update the comment to match reality.
|
|
5. **#53** -- iigsDrawPixelInner: remove the dead PHB/PLB framing and
|
|
fold the row offset via `adc gRowOffsetLut,x` to eliminate the
|
|
dpxlTmp round-trip. Two verifier-identified hazards (the plan review
|
|
killed the earlier AUXWRITE caveat -- it does not apply here, and
|
|
dpxlTmp is DBR-absolute, not long-absolute):
|
|
(a) deleting phb renumbers EVERY stack-relative arg offset
|
|
(8/10/12,s -> 7/9/11,s) -- miss one and the routine reads the wrong
|
|
arg while still "working"; update the comment block at
|
|
joeyDraw.s:1402-1405;
|
|
(b) do NOT relocate the scratch D-relative -- D-relative stores land
|
|
at the bank-0 alias, not the data-bank .bss.
|
|
Gate: iigs-verify VERIFY_EXAMPLE=draw checksum + before/after bench
|
|
on the fixed clock (hottest inner op).
|
|
6. **#52** -- iigsBlitStageToShr dead upload paths, verifier keep-list
|
|
(the review caught two traps here): the entry becomes zero-arg --
|
|
only ONE far pointer + flags word is currently pushed, not two;
|
|
delete the marshalling at joeyDraw.s:2765-2772 and both flag-gated
|
|
MVN blocks (2787-2859); KEEP the caller-DBR stash at 2780-2784 (the
|
|
narrow-row MVN path reloads $E1:9DFE after every MVN -- deleting the
|
|
stash corrupts presents); delete the orphaned bsFrame BSS
|
|
(4417-4420); update hal.c:98/298 and the stale jlpInit comment at
|
|
hal.c:270-273. Gate: iigs-verify against an example that produces
|
|
PARTIAL-ROW dirty bands (not just the default draw demo), checksum
|
|
before/after.
|
|
7. **#26** -- platform.h decorative IIgs flags: make the registry
|
|
authoritative AND loud (review: plain #ifdef-consume recreates the
|
|
silent-fallback rot this finding describes): keep the flags, and
|
|
pair the port.h IIgs macro block with
|
|
`#if !defined(JL_HAS_<OP>) #error` checks so a missing/removed flag
|
|
is a compile failure, not a silent generic fallback.
|
|
|
|
Gate (phase-wide): full 4-port UBER re-run against goldens;
|
|
per-item gates above.
|
|
|
|
Effort: ~2-4 days (dominated by the two new implementations).
|
|
|
|
---
|
|
|
|
## Phase 8 -- API-surface decisions + final re-baseline
|
|
|
|
1. **#40** -- input predicates: DECISION for Scott. Option A: macro
|
|
fast paths in joey/input.h reading the extern state arrays (keeps
|
|
functions for ABI; exposes internals in a public header). Option B:
|
|
accept the call cost (the honest post-Phase-0 number is far better
|
|
than the old 3382 anyway). Recommendation: B unless a real game
|
|
shows input in its frame profile.
|
|
2. **#43** -- jlRandom 16-bit-half rewrite, IIGS-ONLY behind a platform
|
|
#if (verifier: the decomposed form is a wash-to-slight-LOSS on
|
|
68000 -- keep the current 32-bit C for the GCC ports). Output is
|
|
bit-identical either way; the Phase 0 golden-sequence check covers
|
|
it on all ports.
|
|
3. **#25 (final)** -- regenerate PERF.md completely: new IIgs floor,
|
|
new ratios, document BOTH artifacts (SEI clock corruption + the
|
|
now-fixed per-iteration poll tax) and the honest full-screen
|
|
ceiling; drop the stale-baseline caveats.
|
|
|
|
Gate: full 4-port bench + hash run; PERF.md tells the truth.
|
|
|
|
Effort: ~1-2 hours plus bench runs.
|
|
|
|
---
|
|
|
|
## Decisions Scott must make (collected)
|
|
|
|
| # | Decision | Recommendation |
|
|
| --- | --- | --- |
|
|
| #16 | Drop the undocumented $0RGB re-mask in jlPaletteSet (memcpy + force color 0)? Slight behavior change for non-conforming input. | Yes -- contract already requires $0RGB |
|
|
| #22 | jlDrawText: out-of-range initial bx/by becomes an explicit reject instead of silently absorbed per-glyph. | Reject at entry |
|
|
| #8 | Arena >64KB on IIgs: clamp silently or fail with error? | Clamp + coreSetError note |
|
|
| #34 | Sprite registry in sprite.c vs arena epoch vs doc-only contract? | Registry (fixes both corruption arms, zero hot-path cost) |
|
|
| #55 | spriteEmit68k.c: delete or keep unbuilt? | Delete (git history keeps it) |
|
|
| #38 | Accept dirty-band over-approximation for fillCircle fallback spans? | Yes -- matches IIgs asm path |
|
|
| #6 | Accept 1-LSB audio deviation for the muls.w interp rewrite? | Yes -- inaudible |
|
|
| #40 | Input predicate macros in public header vs keep functions? | Keep functions (B) -- DECIDED 2026-07-05, documented at the predicate block in src/core/input.c |
|
|
| #60 | assetLoad IIgs segment: add the promised directive or fix the comment? | Check root-segment pressure, then decide |
|
|
|
|
## What this plan deliberately does NOT do
|
|
|
|
- No runtime dispatch, no C++/newer-C features, no ST blitter, no
|
|
wholesale planar-architecture changes (per the audit's ground rules
|
|
and the standing 68k-asm-rewrite plan -- deep C micro-opts to the
|
|
Amiga/ST HALs beyond the listed findings are out of scope because the
|
|
hand-asm rewrite will replace them).
|
|
- No toolchain changes (llvm-mos/ORCA are owned elsewhere; anything
|
|
needing compiler work gets flagged to Scott instead).
|
|
- Refuted findings (PERF-AUDIT.md bottom) are not actioned.
|
|
|
|
## Progress tracking
|
|
|
|
Mark each finding number done here as it lands:
|
|
|
|
- Phase 0: [x] uber-batch [x] #15 [x] #71 [x] uber-checks [x] bench-iigs.sh [x] millis-harness [x] #73 [x] #76 [x] #25a (3-port; IIgs blocked on stabilization) [x] goldens (DOS/ST/Amiga; IIgs partial)
|
|
- Phase 1 COMPLETE 2026-07-05 (all four ports 43/43 bit-identical hashes): [x] #1 [x] #11 (+h==2) [x] #72 [x] #12 [x] #74 [x] #75 [x] #77 (root-caused; fix=toolchain ask 5; codegen off) [x] #78 [x] #79-flag [x] #81 [x] #82 [x] #83 [x] #85 [x] #86 [x] #87 [x] #23 [x] #8 [x] #46 [x] #34 [x] #5 [x] #58 [x] #31 -- new: #88 filed (jlTileT snap/mono semantics wart, Phase 5) [ ] #23 [ ] #8 [ ] #46 [ ] #34 [ ] #5 [ ] #31 [ ] #58
|
|
- Phase 2 COMPLETE 2026-07-05 (bit-neutral, 43/43 held on all four ports; DOS paletteSet +92%, drawPixel +37%, samplePixel +21%; IIgs paletteSet +8%, samplePixel +6%): [x] #10 [x] #17 [x] #20 [x] #65 [x] #21 [x] #47 [x] #14 [x] #24 [x] #13 [x] #16 (final form = unrolled word copy, NOT memcpy -- the clang IIgs libc memcpy byte-loop regressed it; finding #79 caveat)
|
|
- Phase 3 COMPLETE 2026-07-05 (bit-neutral, 43/43 x4; IIgs drawCircle r=80 -> 70 ops/s, fillCircle r=40 -> 42): [x] #9 [x] #37 [x] #36 [x] #38 [x] #35 [x] #61 [x] #62 -- new: #89 filed (diag fallback int16 dx/dy latent, opportunistic)
|
|
- Phase 4 COMPLETE 2026-07-05 (43/43 x4; IIgs interpreted spriteDraw 18->48 ops/s (2.7x), save +18%, restore +15%; review harness: 726,915 differential cases, 0 mismatches): all items incl. #19 + #29-runtime stragglers; #55 file deleted; #59 cache deleted; sprite.h docs modernized
|
|
- Phase 5 COMPLETE 2026-07-05 (bit-neutral 43/43 x4; DOS tileSnap +42%, tileCopyMasked +29%, tilePaste +18%, fillRect +8-9%): all items; #22 landed as entry-sanitation preserving historical wrap semantics (strict reject would have broken the offgrid golden); jlDrawText needed a noinline union helper for the w65816 reg allocator
|
|
- Phase 6 COMPLETE 2026-07-05 (host harness 12/12: #7+#33 bit-identical in exact mode pre-#6; #6 maxAbsDelta=1 on the 3 odd-step scenarios, within the 1-LSB bound; baseline recaptured post-#6; DOS+ST builds clean; ST asm shows 1 muls.w per split loop, 0 __mulsi3): [x] #6 [x] #7 [x] #33 -- emulator audio smoke (DOSBox/Hatari) not yet run
|
|
- Phase 7 COMPLETE 2026-07-05: [x] #3 [x] #4 [x] #42 [x] #41 [x] #53 [x] #52 [x] #26 -- #3/#4 landed
|
|
+ hash-identical x4 (tilePasteMono=B0829554, floodFill=780DC48B). Perf captured
|
|
2026-07-05 via isolated UBER timed ops (same-CPU override-vs-generic before/after,
|
|
built by toggling the port.h #3 macro / platform.h #4 flags): #3 IIgs tilePasteMono
|
|
asm 1083 vs generic 323 ops/sec = 3.35x (timed hash 8A39C22B identical both paths --
|
|
asm output bit-exact); #4 ST flood plane-hooks 13.5 vs generic 75 frames/flood = 5.6x
|
|
(within the 5-15x target). Measured via the UBER micro-benchmark rather than the
|
|
plan's spacetaxi/flood-scenario gates -- more isolated, and correctness already
|
|
proven by the x4 golden match.
|
|
+ Golden coverage extended 2026-07-05 (re-analysis follow-up): the original
|
|
tilePasteMono/floodFill goldens left two paths hash-blind -- the #3 asm combos
|
|
idx 0 (bg:bg) and idx 3 (fg:fg) were never selected by the alternating 0x0F/0xF0
|
|
mono tile, and only jlFloodFill (matchEqual) exercised the #4 ST plane hooks, never
|
|
the bounded stop-mask + group-skip branch. Added checkTileMonoFloodExtra() in
|
|
uber.c (runs LAST so no earlier cumulative hash shifts): tilePasteMonoAll pastes a
|
|
0x00/0xFF/0x0F/0xF0 tile (all four combos) and floodFillBounded fills an enclosed
|
|
color-3 box across four 16px groups. Re-ran all four ports: both new checks
|
|
hash-identical x4 (tilePasteMonoAll=30C3A2C9, floodFillBounded=EF6B6F51); goldens
|
|
updated to 45/45. The two previously by-inspection-only paths are now empirically
|
|
bit-exact vs the DOS generic reference. NOTE: Amiga bench needed TIMEOUT=600 (the
|
|
last-running checks overran the default 150s, same truncation as the Phase 0 note).
|
|
+ #41 verified COMPLETE 2026-07-05: found already landed (opportunistically,
|
|
during the earlier tileFill/fillCircle/fillRect asm work) -- iigsTileFillInner,
|
|
iigsFillCircleInner, and iigsFillRectInner all take the raw nibble and derive
|
|
the fill word/bytes in asm (joeyDraw.s:420-430 etc.); the port.h macros pass
|
|
(c & 0x0F); no C-side * 0x1111 survives anywhere (grep-verified, comments
|
|
only); the port.h:100-105/136-138 comments match reality. surfaceClear's
|
|
fillWord stays C-derived via (d | d<<8) -- shifts/OR, no multiply helper, so
|
|
ownership is consistent per-op and documented at each site. No code change;
|
|
checkbox was stale.
|
|
+ #52 verified COMPLETE 2026-07-05: also found already landed (the clang-era
|
|
joeyDraw.s rewrite folded it in) -- iigsBlitStageToShr is zero-arg (hal.c:106
|
|
extern, hal.c:309 call), the scbPtr/palettePtr/uploadFlags marshalling and both
|
|
flag-gated MVN blocks are gone, the caller-DBR stash to $E1:9DFE is KEPT per the
|
|
verifier correction (joeyDraw.s:3214-3218), bsFrame BSS deleted (grep-verified),
|
|
and the jlpInit/jlpPresent comments in hal.c already tell the truth (jlpPresent
|
|
even credits #52). Gate evidence: today's IIgs UBER run exercised the narrow-row
|
|
MVN path (fillRect-32x32 present = 16-word dirty band < the 32-word threshold,
|
|
~300 iterations) and completed to the green done-screen, which requires the DBR
|
|
stash restore to work; UBER 45/45 hashes matched.
|
|
+ #53 verified COMPLETE 2026-07-05: same story -- iigsDrawPixelInner has no
|
|
PHB/PLB (frame = php+phd = +3; args correctly renumbered to x@7,s y@9,s
|
|
nib@11,s with the ABI comment updated, joeyDraw.s:1856-1858), the row offset
|
|
folds directly via `adc gRowOffsetLut,x` (line 1899), dpxlTmp is deleted
|
|
(grep-verified), and scratch was NOT relocated D-relative per the verifier
|
|
hazard note. No isolated before/after bench: #53 landed inside the wholesale
|
|
clang-era rewrite (no "before" tree), and the verifier-predicted ~1% delta is
|
|
below MAME run-to-run noise. Correctness gates: UBER jlDrawPixel + edge-pixels
|
|
hashes bit-exact vs DOS x4 (45/45), and `make iigs-verify VERIFY_EXAMPLE=draw`
|
|
PASS -- checksum=08483E, distinctNibbles=15 (RECORDED AS THE BASELINE; no prior
|
|
checksum was ever written down -- future draw-path gates compare against this).
|
|
RESIDUAL flagged, out of #53 scope: iigsDrawLineInner / iigsDrawCircleInner /
|
|
the tile inners still carry phb in their prologues -- the audit verifier
|
|
explicitly deferred that same-pattern audit to the IIGS_PERF_PLAN Phase 4
|
|
line/circle work; their arg-offset comments all assume the +4 frame, so any
|
|
future phb deletion must renumber every N,s in each routine.
|
|
+ #3 SPACETAXI TITLE-SCREEN ACCEPTANCE TEST attempted 2026-07-05 (the plan's
|
|
original gate, deferred from the Phase 7 landing): BLOCKED ON FINDING #77
|
|
(toolchain) -- but the attempt built the entire test rig and fixed four real
|
|
bugs found along the way. Landed: IIgs asset bake (examples/spacetaxi/
|
|
generated/iigs, .tbk target byte 4), make-iigs-disk.sh DATA-tree packing
|
|
(CREATEFOLDER + per-file ADDFILE when STAXI is on the disk -- the spacetaxi
|
|
PLAN.md Phase 6 packager task), STAXI now BUILDS on IIgs (the iigs.mk:74
|
|
"blocked on 2-byte cross-seg reloc" comment is stale -- the toolchain landed
|
|
it; STAXI still deliberately omitted from iigs-examples pending the #77 fix),
|
|
scripts/bench-staxi-title.sh (per-frame SHR logo-cell cadence sampler; the
|
|
intended 4-frame period vs measured), scripts/diag-staxi-screen.sh (SHR/RAM
|
|
dump + PC histogram + ring extraction). Bug fixes landed (all 45/45
|
|
golden-verified x4): (1) IIgs jlLogReset did a floppy-truncate write --
|
|
first-ever floppy WRITE on the headless path, #82 class; now ring-only, with
|
|
jlLogFlush switched to "w" (self-truncating full-ring dump, idempotent);
|
|
(2) jlTileBankLoad read tile banks 32 bytes/fread (1000 freads per font
|
|
bank!) -- now batched 8KB/fread (ASSET_TILE_READ_BATCH), same truncated-file
|
|
contract; (3) NEW API jlTileBankLoadToSurface (tile.h) streams a bank
|
|
straight onto a surface's block grid with a 256-byte static scratch --
|
|
no caller-side 32KB buffer, which no IIgs example can allocate (heap is
|
|
665 bytes; a 32KB static blows the bank-0 BSS ceiling); spacetaxi's
|
|
loadFontSheet now uses it; (4) jlpGenericSurfaceAllocPixels used calloc --
|
|
on IIgs that's the malloc-returns-1 trap (#81) shredding bank 0 through
|
|
pointer 1 on the first OFFSCREEN jlSurfaceCreate (stage uses the pinned
|
|
path, so UBER never hit it); now jlpBigAlloc + memset with matching
|
|
jlpBigFree in jlpGenericSurfaceFreePixels. THE BLOCKER: with all four
|
|
fixed, STAXI boots to "title loaded, tilebank=0" but dies layout-dependently
|
|
when jlpBigAlloc handles land inside the app's own unreserved BSS spans
|
|
(finding #77 exactly; pixels@$09:0BF4 run survived to title-load, other
|
|
layouts died in fopen). An app-side BSS-claim mitigation was built and
|
|
REVERTED: the linker's __bss_seg symbols ARE readable via a DATA32 .long
|
|
table (C IMM16 refs trip the 2-byte cINTERSEG limit), but a SUCCESSFUL
|
|
NewHandle claim over the app's own BSS span kills the app instantly
|
|
(silent return-to-Finder, reproduced twice) -- the reservation must be made
|
|
loader-side. Full recipe + evidence filed in LLVM816-ASKS.md item 5.
|
|
Re-run the gate (scripts/bench-staxi-title.sh both port.h-macro arms) once
|
|
the toolchain lands item 5.
|
|
- Phase 8 COMPLETE 2026-07-05: [x] #40 [x] #43 [x] #25b -- ALL PHASES DONE.
|
|
#40 decided per recommendation B (keep functions; documented at the predicate
|
|
block in src/core/input.c and in the decisions table above). #43 landed as an
|
|
IIgs-only #if in src/core/random.c: state stored as two uint16_t halves, the
|
|
13/17/5 xorshift triple decomposed to 16-bit shift identities (hi-before-lo
|
|
update order per step); bit-identity host-proven (UBER golden sequence 4/4 +
|
|
50M-state sweep vs the 32-bit reference, scratchpad rngHalvesCheck.c) and
|
|
emulator-gated (random-golden PASS x4); GCC ports keep the 32-bit form per the
|
|
verifier correction. Note: UBER has no timed jlRandom row, so the ~2-3x/call
|
|
win is analytic, not benched. #25b: PERF.md fully regenerated from same-day
|
|
4-port captures -- fresh table via tools/uber-perf-table, both clock artifacts
|
|
documented (poll tax fixed / SEI tick loss inherent with the three IIgs rows
|
|
tagged clock-unreliable against the ~29 ops/sec ceiling), 45/45 x4 correctness
|
|
statement, per-port sprite-row caveats (DOS compiled, IIgs interpreter pending
|
|
#77), below-floor punch list, stale stabilization/history sections dropped.
|
|
Phase-wide gate: full 4-port bench + hash run 2026-07-05, 45/45 goldens on all
|
|
four ports, random-golden PASS x4.
|