Commit Graph

189 Commits

Author SHA1 Message Date
MechaCat02
2e7e4b17a8 docs(re): give docs/re/ the citation check docs/port/ has always had, and prune
All checks were successful
CI / Native — linux (pull_request) Successful in 37m10s
CI / WASM — Web (pull_request) Successful in 28m25s
CI / Formatting (pull_request) Successful in 57s
CONSOLIDATION.md Phase 4. `tools/port/check-citations` has checked
`docs/port/*.md` since it was written; `docs/re/` never had one, and
`docs/re/captures/` is the largest thing in the repository -- the one place a
file can be added, never cited, and never noticed.

  captures committed          : 212
    cited by a page or a tool : 212
    cited but NOT committed   : 0

🔴 THE PREMISE I STARTED FROM WAS WRONG FOUR TIMES, AND EACH CORRECTION IS IN
THE TOOL RATHER THAN JUST IN MY HEAD:

  * "10 dangling citations" -- the real number was ONE. Eight were DIRECTORY
    references, which resolve and are simply absent from `git ls-tree`; one was
    a path at the end of a sentence with the full stop pulled into the match.
    A checker that cries wolf nine times in ten teaches you to ignore it.
  * The one real dangler, `live-submenu-unidentified.png`, was COMMITTED and
    then deleted on 2026-08-30 while the page kept citing it and kept making
    the claim it backs. Restored from cab62796 rather than dropping the link:
    deleting evidence while keeping the conclusion is the thing this corpus
    exists to prevent.
  * Two "orphans" are opened BY FILENAME from `screen_match.py`, which builds
    the directory separately. A path-only scan called them unreferenced and
    deleting them would have broken the tool. The check now counts any
    basename named anywhere.
  * Pruning then EMPTIED two directories that pages cite as directories, and
    the check went red on the very citations that made them evidence. It
    caught its own damage. A file inside a cited directory is cited.

The selftest covers all of it, including the failure that actually happened:
an earlier version passed five green ticks while the scan silently returned
NOTHING, because `git grep -E` is POSIX ERE and cannot compile `(?:`. A
selftest that cannot see the failure that occurred is decoration.

46 orphaned captures dropped, 30.7 MB from the checkout. ⚠️ That reclaims no
repository space -- the blobs stay in history -- and it is not meant to. It
means every capture here is now evidence some page or tool actually uses.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 21:14:52 +02:00
sylph-decoder
f4987977a3 re: the blend mode IS on the disc -- T8aD +0x04 bit 0x02, DECODED
Reverses two of my own pages. t32-blend-mode-not-on-disc.md said the mode is not
on the disc; ui-blend-mode-measured.md classified it measured and told the port
that which field selects it was unknown. Both were honest and neither is current.

Bit 0x02 of +0x04 set means the game draws that sprite ADDITIVE
(RB_BLENDCONTROL0 = 0x01010101); clear means premultiplied alpha-over.

Fit: 35 elements over three screens, 16 set/additive and 19 clear/alpha-over,
zero errors, every label read out of the guest command stream rather than off a
render.

Control: of every bit of the first twelve header words, exactly one separates
those 35 without error. Nothing ties with it -- which is precisely what the
+0x08 = 0x8050 candidate failed.

Within-pair: ptbtn00 0x0110 alpha-over against ptbtn00f 0x0112 additive, same
screen, same bundle, adjacent draws, one bit apart. And other f variants are
bit-clear and alpha-over, so it is not 'focused variants are additive'.

Out of sample: a prediction committed at bbd85e9, before its capture, on a
different archive -- GP_OPTIONS entry 19, 3 additive of 16, falsified if any
other element drew additive. The game drew exactly po_menu_eff01/02/03 additive
and nothing else.

This revives a claim REFUTED.md killed. The refutation said blending those
sprites additively worsens every measure against the capture -- a claim about
our renderer, made while that renderer had a stale keyframe association, no leaf
geometry and no rotation. Recorded as a revival with its evidence;
check_refuted.py passes with 0 unmarked assertions.

Two other readings of the same bit stay refuted and are explicitly not revived:
'the name contains eff' and 'premultiplied storage'. Those were the bit's
meaning; this is its effect.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-31 07:20:12 +00:00
sylph-decoder
f122b4de4e re: EXTRAS complete -- ptframe4, pteff21/22/23 and pteff10 are all ADDITIVE
The four elements the port measured as the worst on EXTRAS, and which appeared
in no draw, were in a draw all along: the 24-index additive batch holds six
quads and Canary printed the first two. Cap raised to 64, screen re-captured,
all six named. Same draw as ptframe3, whose state was already measured -- the
one-way implication doing real work.

pteff10 is identified too, and it needed the resting SCALE: it ships as 409x144
and is drawn at 200 % x 500 % = 816x720. The matcher's 'try 1x and 2x' rule
could not name it at any scale and reported a near miss against something else,
which is a failure wearing the clothes of an answer. Candidates are now the
declaration's pivot*2 scaled by the resting keyframe as well as the texture at
1x and 2x, and the tolerance is the log's own NDC print quantisation rather than
a chosen number.

Flagged rather than buried: pteff10 measuring additive is in tension with the
port measuring it nearly exact under alpha-over. Both can be true for a dim
semi-transparent glow over a dark background, and it is the one row a rendering
check does not corroborate.

Also stated: the three full-screen alpha-over draws are NOT individually
identified -- four elements declare 1280x720 -- so the label on those rows is a
candidate, not an identification.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-31 06:59:11 +00:00
sylph-decoder
d4bef47e38 re: the TITLE's blend states, the menu replicated twice, and the sweeps are on screen
Two more emulator runs close two of the three reach limits on the blend page.

THE TITLE (entries 4 + 2 composited). Everything alpha-over except the two
rotated sweep strips and -- the finding -- ptbtn00f, the PRESS (A) plate's
focused variant, which is ADDITIVE while its own base ptbtn00 is not. That is
what the documented plate pulse is made of, and a port drawing both alpha-over
cannot reach the pulse's peak by any pacing.

And ptlogo_back2 / ptlogo_back2eff are ALPHA-OVER. They are frame-shaped, large,
dark and 94 %/87 % transparent -- every surface property the menu's ptframe* have
-- so 'frame-shaped and mostly transparent implies additive' is refuted on the
one screen that could test it.

THE MENU replicates draw for draw in two further sessions; the one-session
caveat is retired.

THE VERTEX CAP was 8 = two quads, so a batched draw reported its first two
elements and dropped the rest. Raised to 64. This is why ptframe4, pteff21,
pteff22 and pteff23 looked like elements the game never draws: EXTRAS' 24-index
additive draw holds six quads and the log printed two.

THE SWEEPS are on screen on the main menu in every captured frame, stepping
~0.03 NDC per frame in opposite directions with their vertex alpha ramping, at
different phases in two sessions. The leaf group runs on the menu. What that
does not say is how much they contribute.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-31 06:42:05 +00:00
sylph-decoder
3a617ffafe re: the UI blend mode is MEASURED -- the frames are drawn ADDITIVE
Closes the one route t32-blend-mode-not-on-disc.md left open: the executable's
draw path. Canary's UI draw capture now logs RB_BLENDCONTROL0 per draw, and the
game was driven to the main menu and to EXTRAS with F10 at each.

The title-side UI uses two blend states and ONE pixel shader:

  0x07010701  src=ONE dst=1-SRC_ALPHA  alpha-over (premultiplied)
              ptbase, pteff05, the fade quad, ptmsg, ptmsg2, pttitle, buttons
  0x01010101  src=ONE dst=ONE          ADDITIVE
              ptframe1, ptframe2, ptframe3, pteff20, both rotated sweep strips

Two controls, both run before the result was read:

* the NDC->pixel conversion that identifies a draw by its quad size reproduces
  1134 and 1303 px for the two rotated sweep strips -- numbers measured by a
  different tool in a different session -- on BOTH screens. The tool prints
  PASS/FAIL and disclaims its own output on FAIL.
* pixel shader 0xE59B2B3DA4AA9008 is used with BOTH states, 12 draws additive
  and 18 alpha-over. ptframe1 and ptbase run the same shader; only the blend
  register differs. So this is a blend result, not a shader result.

This confirms the port's independent measurement -- it solved the composite per
pixel from two backgrounds and found additive halves alpha-over's error on both
frames -- by a route with nothing in common with it.

So the blend is no longer authored: 'any blend you choose is authored' was true
of the disc and is not true of the game. What is still unknown is which field
selects it; elements sharing a mode are batched into one draw call, so the
selection happens before the draw.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-31 06:06:30 +00:00
sylph-decoder
45076164df re: GP_DIALOG 2/3 ARE an English/Japanese pair -- measured, closing a withdrawn claim
The ja capture of DIFFICULTY is taken. EN and JP differ in 1.82 % of pixels, in four
bands and nowhere else: the heading (DIFFICULTY -> 難易度選択), a 2 px ring shift, the
BACK label (-> 戻る) and the footer. EASY/NORMAL/HARD do not appear in the differing
set -- the Japanese release leaves the three difficulty names in Latin script.

So 2/3 are a language pair, and the disc agrees quantitatively: 2.77 % of bytes
differ against 1.82 % of pixels on screen. This closes the item left open when I
withdrew 'an EN/JP pair' as a bare assertion, and it is now measured rather than
inferred from the disc's convention.

The JP screen also opens on NORMAL like the English one, and the sweep reproduced
the reset finding in Japanese.

Reach: one JP boot, one screen. It does not generalise -- GP_TITLE 4/7 is already
known to differ by more than text.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-31 04:52:19 +00:00
sylph-decoder
bf36ae9aa6 re: JP DIFFICULTY not reached -- but the first JP main-menu capture, and initial focus is 新規
The run was launched to settle whether GP_DIALOG 2/3 are specifically EN and JP. It
did not reach DIFFICULTY: the reach probe's round trip failed at the final A, the
game sat off-menu at glyph 11654, and the sweep then timed out without pressing NEW
GAME.

Not a detector problem, which is what I assumed while watching. The JP menu detects
perfectly at glyph 320 against English's 327, both inside the 250..420 band; the
11654 is a later phase.

What it did establish: the locale took, and this is the first JP main-menu capture
in the corpus. JP initial focus is 新規 -- NEW GAME, top item, ring y 225.5, the same
item and row as six English boots. And the ring rows match English exactly, so the
JP build places its buttons where the English one does. That is language-pair
structure at the MENU, which is not the dialog pair the question is about.

Locale restored and verified at language = 1 by the trap, on a failing exit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-31 04:30:57 +00:00
sylph-decoder
abeea3b834 re: settled -- a submenu resets to the item it OPENS on, not to its top item
DIFFICULTY is the screen that separates the readings: it opens on NORMAL, second of
EASY/NORMAL/HARD/BACK. Reproduced on a fresh boot rather than inherited from the
2026-08-29 capture. After one confirmed DOWN to HARD, B out and A back in, it
returns to NORMAL -- in-cursor 1.0 from opened against 93.9 from where left.

So reset targets a per-screen opening default that need not be the first item. The
other four submenus could not settle it because on each of them the opening item IS
the first, so both readings predict the same observation.

sylpheed-port's refusal to promote 4/4 to a rule was right on the evidence: a
generalisation from those four would have got this fifth screen wrong.

The probe never presses A inside a submenu, so it cannot reach SELECT DATA and the
guest throw at PC 0x82307128 -- recorded in the tool's source.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-31 01:47:29 +00:00
sylph-decoder
4b8afbe85e re: all four measured submenus reset -- the main menu is the only screen that remembers
LOAD GAME, TUTORIAL and OPTIONS all reset on re-entry, joining EXTRAS. With the
main menu persisting, the rule is four of four submenus resetting and one
exception -- the opposite of what a single screen had suggested.

Fourth attempt, and every control in it is a previous failure: the decision uses
the cursor's own region so no per-screen geometry is assumed (sweep 1 read the main
menu's gutter on screens whose cursors are elsewhere); the back-on-the-menu test is
the narrow ring row (sweep 2 died when a crash dialog defeated whole-frame
comparison); absolute row checks after every press (a constant offset passes a
differential control); and a two-sided self-test that constructs both verdicts,
which had already caught a rule I broke myself.

Screens confirmed by eye and committed as evidence, because an earlier run was
fooled about which screen it was on. LOAD GAME's 21.6 is explained by its list
scrolling rather than moving a ring.

Still not separated: resets-to-named-item vs resets-to-top-item. None of these
three has an opening item that is not its first.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-31 01:23:28 +00:00
sylph-decoder
a4a089cea1 re: a second JP capture closes the transfer -- and corrects my own noise claim
Last turn I transferred the EN title's capture noise to the JP box and flagged
the gap: build 7 carries ptloop01/02.rat which may animate inside that region
where the EN plate does not, and the era adjudication rests on a single capture.
Took a second, independent capture from a fresh boot in a separate session
(jp_title_session.sh -- sets ja, captures, always restores en; verified back at
language=1).

Within-run stability reproduces: 0 of 138 600 px in the ROI across four
comparisons, with 47k-73k px moving whole-frame as the contrast control.

BETWEEN SESSIONS, inside the box the adjudication uses: 645 of 164 124 px
differ, RMSE 0.3215, against 116 492 px whole-frame -- genuinely different
sessions. And the verdict reproduces to three decimals: stale 58.412 -> 58.413,
fixed 41.690 -> 41.692, margin 16.722 -> 16.721.

The shape is the useful part: capture noise moves both candidates together, so it
nearly cancels in a MARGIN. Absolute scores moved 0.001-0.002 while the margin
moved 0.001 against an in-box noise of 0.32. A margin between two renders scored
on one capture is far more robust than either score is.

CORRECTION to a claim I made earlier today and sent to the port: I said the
settle-vs-rest negative was STRENGTHENED because 1.48 sits below the whole-frame
capture spread of 2.8. Wrong comparison -- the measurement lives in the box, and
in-box between-session noise is 0.32, so 1.48 is well above it. The negative
rests on the render axis alone (1.2, ratio 1.2x), exactly as first stated. I
reached for a number that was to hand rather than the one that applies, which is
the same family as the errors we have both been cataloguing.

METHOD gains: match the noise floor to the quantity, including which noise
applies; and sylpheed-port's point that an instrument which rounds away the thing
being verified cannot verify it (they called a harness reproducible from an RMSE
printed to two decimals when the residual was 0.0565).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-30 15:24:56 +00:00
sylph-decoder
77b4ccecc4 re: the Japanese title at rest -- the capture MISSION has wanted since 2026-08-29
Asked for by the port: its title_jp row drifted, localized to a 350x396 block at
(405,74) -- the logo stack -- and with no JP capture in the corpus it could say
the renderers moved apart but not which one moved.

Three earlier attempts failed to reach the interactive title in either locale.
The reason is now known and was never the locale: A at the title needs a
signed-in profile, and no run had one.

Locale set through canary's own persisted XConfig and restored afterwards,
verified back at language=1. INDEPENDENT confirmation it took: the XMA probe
logged a different voice-context set from every English run (ja 1112064 /
1150976 / 1177600 against en 1294336 / 1118208 / 1171456), so the switch reached
the guest rather than being a menu-language cosmetic.

'At rest' is demonstrated rather than assumed. Five frames ~1.5 s apart after the
plate pulse says the screen has settled: the port's ROI is byte-identical across
all of them, max |delta| 0 over 138 600 px, while the WHOLE FRAME moves 39 584 to
71 927 px -- the plate pulse and sweeps. That contrast is the control: the
instrument can see motion and the ROI still shows none.

The capture shows what the English title does not -- the katakana subtitle, and a
crystalline burst behind the wordmark, the ptlogo3a/b/c + ptlogo_back2eff* stack
that this corpus records as transparent at rest in English. Exactly the region
the port's drift is localized to.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-30 11:37:35 +00:00
sylph-decoder
6066094221 re: three empty evidence cells filled in one run -- auto-repeat, B on a settled title, plate redraw
1. NO AUTO-REPEAT. A 2.0 s held DOWN moves the cursor exactly once. The counter
   passes its control first: a single 0.12 s tap gives exactly 1 spike and the
   hold gives 1, with the move spike at 0.0202-0.0220 against a 0.0003-0.0038
   noise floor. The port had flagged that the hedge 'at the durations tried' was
   carrying the claim, and it was -- nothing recorded a HELD direction.

2. B ON THE SETTLED TITLE DOES NOTHING. Twenty seconds after a
   delivery-confirmed B the screen is still the title with PRESS (A) BUTTON up,
   read off a capture that names itself. This is the run the previous attempt
   could not be: it waited for the plate pulse, the title's own settled
   signature, instead of pressing during the build-in.

3. THE PLATE IS RE-DRAWN after B from the menu -- pressed at 351.2 s, pulse
   detected at 358.5 s. That was the other unevidenced half of the B-on-menu row.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-30 11:18:18 +00:00
sylph-decoder
628e6c00a3 re: B on the main menu goes to the TITLE -- measured, filling an empty evidence cell
menu-navigation-semantics.md had this row at yellow with an EMPTY evidence cell,
and it is what the port still authors as on_cancel.

Delivery-confirmed via [RE-INPUT] (B is kXInputPadB = 0x5801), change detected
rather than timed. B delivered at 331.2 s; the glyph leaves 327 by 331.6 and
73.5 % of pixels differ. Both captures name themselves: PROJECT SYLPHEED with the
(C)2006,2007 SQUARE ENIX line.

Three things measured:
  * B on the main menu goes to the title;
  * latency <= 0.4 s at a 4 Hz sample rate, where the corpus previously had this
    as 'not measured (a backlogged probe void)';
  * NO loading screen in between -- the disc carries four pgloading_* bundles and
    none appears on this path.

What the run CANNOT say, recorded in the table rather than glossed: 'B on the
title -> nothing' is still unevidenced. The second B was delivered during the
title's build-in, so the glyph 0 -> 154 change after it is the build-in
completing, not a response. A run that answers that row must wait for the title
to settle before pressing.

The 're-draws PRESS A after a beat' half of the first row is also still
unevidenced -- the run ended with the plate absent.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-30 11:06:18 +00:00
sylph-decoder
6afffca550 re: a .tbm DRAWS -- the TUTORIAL screen captured, and the 'inert' reading refuted
Closes the open second reading in ui-forced-backdrop.md: that a .tbm contributes
no pixels, leaving 24 of its 62 deciding verdicts harmless rather than correct.

The TUTORIAL screen was reached and captured. It carries a full-screen blue
circuit/hex background. GP_TUTORIAL build 0's element 0 is pubase.tbm with pivot
(640,360) -- 1280x720, the only full-screen TEXTURED element in the bundle; the
one other full-screen element is pueff00.prm, an untextured primitive the colour
census puts at pure black. Our render of the same build is the identical layout
on pure black, 6.0-6.4 % inked against the game's 99.7 %. The only difference is
the background and the only thing it can be is the .tbm.

So the 24 .tbm verdicts are correct rather than harmless, and they are
load-bearing in the full sense. Reach: one .tbm observed; the class question is
settled, the ten other families are not individually seen.

Also: screen render is wrong on every screen carrying a .tbm -- it drops the
background silently, with no diagnostic.

And the identification is worth its own METHOD entry. Two statistical identifiers
were built. Masked correlation FAILED its control, picking EXTRAS over the known
main menu by 0.004 because the shared background dominates. A high-passed variant
PASSED by 1.28x, which is not a margin that licenses identifying an unknown, so it
was not used. The screen says TUTORIAL across the top. Ask whether the artefact
already states the answer before building a matcher.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-30 10:53:34 +00:00
sylph-decoder
cab62796db re: a submenu is REACHED -- and correlation cannot identify it
Third attempt at the .tbm question. All three fixes from the previous page were
applied and all three were needed: hold A for 0.5 s, confirm delivery from
[RE-INPUT] rather than from the pad, and detect the screen change instead of
timing it. Title at 288.6 s, both presses delivered on attempt 1, submenu at
303.4 s with 87.1 % of pixels changed.

The capture is 99.7 % inked and uniform top to bottom -- a full-screen
background. Our renderer gives 1.9-3.0 % for all 19 GP_SAVE_LOAD builds,
6.0-6.4 % for GP_TUTORIAL, 78.4 % for GP_SYSTEM 0/1. So two of the three
archives render essentially nothing where the game draws a full screen.

But WHICH screen was captured is not established, and the reason is worth more
than the run: correlation cannot discriminate when the candidate renders are
near-blank. All 19 GP_SAVE_LOAD builds score -0.004..-0.010 -- a ranking with no
information. A matching statistic is useless against a hypothesis that predicts
an empty image, which is exactly the hypothesis under test.

Focus could not be read either: the two labelled menu captures fit at 2.52 and
2.48 mean absolute difference, 1.6 % apart. That is a SECOND statistic failing on
the focus problem after the per-row brightness one, so it is an open item rather
than an oversight.

Kept regardless: the game surface sits at y=45 in the 1280x720 display frame,
fitting the committed 1279x675 captures to 2.5 mean absolute difference. That is
the alignment the earlier cross-geometry comparison got wrong.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-30 10:49:48 +00:00
sylph-decoder
8dfce96b79 re: the .tbm submenu is not reached -- and the second tap was never delivered
Two runs, neither answering whether a .tbm draws pixels.

Run 1 TIMED the title->menu transition and was still on the title 8 s later
(glyph 714, the plate's pulse trough), so the second tap did the transition and
the 'submenu' capture is the menu. Void.

Run 2 DETECTED the menu instead -- glyph 327, matching live-main-menu.png
exactly -- tapped 0.8 s later, and 12 s after that was still on the menu. The
log says why: 2 file-pad vk=5800 lines, i.e. ONE press, and one RE-INPUT
delivery. The second tap was never delivered, with zero swallow lines so it is
not the sign-in path. A 0.12 s press issued while the guest is still loading a
screen is missed outright.

So 'the press did nothing' and 'there was no press' look identical from the
screen, and only the log separates them.

Worth more than the run: this is the third time in one iteration that timing was
used where detection was required -- the title->menu wait, the menu->submenu
wait, and the press itself. Each fix is the same substitution, and each was
written only after the timed version had produced a confident wrong answer.

Also records that no focus detector is needed for this question, since every
main-menu destination except EXTRAS carries a .tbm decider.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-30 10:30:49 +00:00
sylph-decoder
9e35fc26cc re: the A-press A/B is run -- signed-in profile, no swallow, menu opens
The debt from two iterations ago. Two boots, same binary and ISO, one A tap
each, fired only after the plate's pulse had been seen for 12 consecutive
samples. ARGV recorded per leg, because the config dump provably cannot say.

  leg A  no profile flag        3811 swallow lines and climbing
  leg B  --logged_profile_...   0 swallow lines, final glyph 327 = MAIN MENU

327 is the documented main-menu glyph count, reproduced by this instrument's own
control, so leg B's press opened the menu. Capture committed.

Leg A demonstrates the SWALLOW, not the crash: I stopped it at ~2.3 M swallowed
calls because kernel tracing at log_level=3 was eating the 300 MB budget the
crash dumps need. The fault itself remains measured once, historically. One run
per leg.

A void pair came first and is recorded, because it is why the detector is what
it is. The first version fired on a single frame over a glyph threshold and hit
the INTRO MOVIE -- green flashes of 1298..5433 lasting under a second -- about
6 s before the title, in both legs. The presses were real (each skipped the rest
of the movie, which is Q9's behaviour) but the pair tested nothing. The fixed
detector requires 12 consecutive in-band samples, and was replayed against the
void runs' own series as its control: it declines the movie flash at 84.8/85.5 s
and fires at 93.9/94.7 s inside the sustained pulse.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-30 08:05:43 +00:00
sylph-decoder
19cdec3942 re: the PRESS A plate PULSES -- measured, and it keeps an authored entry
Answers the port's ask #1, which it had flagged as the only one of its four
that could delete an authored entry rather than confirm one. It confirms one.

Held at the title with no input, the plate oscillates continuously: two windows
in one boot, 58 s and 57 s, ~23 cycles each, no decay. Periods 2.530 and 2.540 s
by upward mid-crossings -- 0.4 % apart.

It never goes off. The plate-absent floor is 159 green pixels, measured on the
committed live-title-build4-no-plate.png; the pulse bottoms at 714, 4.5x that.
So the port's 'flash and nothing after', reasoned from ptbtn00 expiring at
t=244, is wrong on the boot's end state -- ptbtn00f's 120-unit cycle is what
runs.

Instrument controls were run before it was pointed at anything unknown: the
glyph counter reproduces the documented 753 on live-title-press-a.png and 327 on
live-main-menu.png exactly.

Two estimators, and only one replicates. Mid-crossings agree across the two
windows to 0.4 %; a single-sinusoid least-squares fit does not (2.553 vs 2.413),
because the waveform is fast-rise/slow-decay rather than sinusoidal -- its own r2
of 0.468 and 0.228 is the tell. Both were controlled on synthetic sinusoids at
2.24/2.55/3.10 s laid on the ACTUAL timestamps and recovered every one exactly,
so neither is broken; one is misspecified. Recorded as such.

The wall-clock is 13 % longer than the corpus's earlier 2.24 s mean. Same
declared 120 units, different emulator pacing (x1.27 here against x1.12), so
this corroborates 'author the units' rather than disturbing it.

Reach stated: one boot; does not distinguish the boot title from an attract-loop
title; and the glyph count is a thresholded pixel count, so 714/1520 is not an
alpha ratio and no duty cycle can be read off it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-30 07:37:49 +00:00
sylph-decoder
6f4ba93c5c re: the title's light arc is five transient flashes, not a tone or rotation error
Records the settle-time decode, and corrects two claims it overturns.

docs/re/structures/ui-settle-time.md is the finding: a settled screen is one
instant every element is posed at, the disc names it (the midpoint of the
longest keyframe-free interval), and the title's arc closes from 33.22 to
11.79 with the clipped-pixel count landing on the console's 1459 within 0.5%.
Includes the disc-wide reach -- only 30% of bundles have a window wide enough
to trust -- and a three-way figure.

Withdrawn in ui-rotation-implemented.md:

  * "Flat. No minimum." was not a property of rotation. `at` posed LEAVES ONLY,
    so the scan moved the light sweeps and never touched the top-level flashes.
  * "our renderer does not draw ptlogo1/ptlogo2 at all" is wrong. Both are
    drawn; the four elements the diagnostic named are kind-0x4 ghosts sharing
    their template's name. Hiding the real ones makes the error WORSE by
    +5.20 and +7.47.
  * Its 10.92 baseline is not reproducible -- 14.07 at its own pre-change tag
    and 14.07 today -- so the "1.7% better" verdict rests on an unrecorded
    recipe. Flagged in title-residual-tone-vs-geometry.md too.

METHOD.md gains two traps: a shared CARGO_TARGET_DIR makes a `git worktree`
build silently replace the binary you run next (it cost three renders here,
caught only because a missing flag was a hard error rather than a wrong
number); and an aggregate computed per-element is not a state of the system.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd
2026-08-29 19:29:12 +00:00
sylph-decoder
0fd21bd744 re: the boot shows the publisher first -- the TSV that says otherwise attached late
The port flagged docs/re/data/boot-timeline-2026-08-29.tsv, whose label column
runs splash_dev before splash_pub, as a possible boot-order bug in its tree.

Three cold boots, t=0 at launch, no pad input, and the frames looked at rather
than only correlated: SQUARE ENIX 3.05-7.34 / 1.18-5.78 / 1.19-5.56 s, then a
~0.25 s black hold, then GAME ARTS/SETA/studio anima. Publisher first, 3/3.

The TSV is not wrong about any frame; its t=0 is ~7.7 s into the guest's boot,
so the publisher splash had been and gone before the stream opened. The tell is
in the file: its first twelve rows are byte-identical to four decimals -- one
held frame sampled twelve times -- and those exact numbers reappear in my run 1
at 8.42-10.94 s.

Second trap, new: ADV.wmv opens with its own SQUARE ENIX card, bloomed and below
centre, scoring 0.59-0.75 against live-splash-publisher.png. The classifier
fires splash_pub twice per boot and the second one is a movie frame. The real
splash holds perfectly still and scores 0.93-0.94.

And the dwells are DECODED, not measured: the publisher declares 240 units
(4.000 s) and the developer 195 (3.250 s), against measured 4.30/4.60/4.37 and
3.51/3.50/3.37. Measured over declared is 1.085 on average across six spans --
a 30 Hz timeline at 27.6 fps, which is the presentation rate this corpus has
measured independently three times. The port authors nothing here.

Instrument control run first: 11/11 content, 4/4 plate, the two splash
references rejecting each other at 0.035.

docs/re/boot-order-and-splash-dwell.md

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nsxw1A9JseUw99Yw1ZRQzY
2026-08-29 14:15:28 +00:00
sylph-decoder
fb536df8b4 re: the PRESS-A plate arrives 2.13 s after the title settles, measured twice
The one number the port said decides a structural question on its side: whether
`ScreenView` has to draw two builds at once. It does. The boot title shows
build 4 alone, and 2.13 s after build 4 stops animating it composites build 2
over it. Two independent boots agree to 6 ms (2.138 / 2.132), which is under one
sample interval.

Measure from SETTLED, not from first pixels. "First drawn -> plate" is 3.78 s in
one run and 4.26 s in the other, because the build-in animation itself ran 1.64 s
and 2.13 s -- that spread is the emulator's frame pacing, and it is exactly the
kind of number that looks like a measurement.

Ruled out before believing it: that the plate was pulsing all along, too dim for
a thresholded glyph counter. The counter reads EXACTLY 154 -- the committed
no-plate title's own value -- for every frame of a plateau nearly one full pulse
period long, with zero variation, and the surface mean is flat to +/-0.03 across
it. A cycling overlay moves both.

Also settled, and also not:

  * the black hold between two screens is 0.14-0.30 s, which brackets the port's
    authored 0.17-0.23 s and the file's declared 12 units. Their constant stands.
  * the (A)->menu latency is STILL not available, and now the reason is known.
    Both runs freeze one frame for ~1.4 s at surface mean 26.626 -- agreeing
    between runs to six decimals, and reproduced in run 2 with stream restarts
    disabled, so it is not the capture path. It is a guest load stall: the (B)
    path, which loads nothing, has no freeze at all. Any figure from it would be
    an emulator load time.

Refutation attempt, recorded whether or not it survived: navigation.md's "the
title is not input-ready for about ten seconds, and even then (A) registers
roughly half the time". At 7.29 s and 7.28 s after the title settled, (A) was
accepted first press in both runs, as was (B) on the menu. n=2 only makes "half
the time" unlikely (p ~ 0.25); it contradicts the ten seconds outright.

And the standing red banner is withdrawn: the interactive title IS reachable in
this container, twice, with no pad input, in ~3.5 minutes. Why it changed is NOT
established -- this container came up with no Xenia storage root at all, so run 1
created a profile -- and that is written as a correlation for the next session to
test rather than as a cause.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014voBspJ6kFncNErZJuZcLw
2026-08-29 12:15:22 +00:00
sylph-decoder
4fa3099249 re: the main menu's focus ring spins continuously -- period 2.18 s, measured
Answers the port's ask: ptbtneff01 is ANIMATED while a button is focused, not
drawn once and held. The existing page said 'the ring SPINS' from one frame at a
large angle, which is equally consistent with a static draw at a fixed angle.

No angle is quoted anywhere. The 360-bin angular estimator written for this
FAILED its own control -- a synthetic 30 deg came back as 0 deg (peak 0.596)
while 90/180/270 came back exactly -- so it was not used. What settles it needs
no angle: total annulus brightness is conserved to 0.4 % while individual
angular bins swing by 24, i.e. brightness moving AROUND the ring, which excludes
a pulse. The temporal-std map is a clean annulus, falling to ~1 both inside and
outside the stroke, which excludes positional jitter.

Period from the profile's autocorrelation: eight evenly spaced peaks, mean
2.177 s over nine revolutions. Even spacing is the internal check a drifting
instrument cannot pass. That is 120 units = 60 frames = 2.00 s at a true 30 Hz.

Also measured, same run: the ring is the ONLY moving thing on the settled main
menu -- temporal std is exactly 0.000 on every unfocused button, the labels and
the footer. And the ring's centre, located from the std map at game
(520.7, 339.7), matches the declared leaf offset's prediction of (521, 340).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KNR5Y79D1T4bBr6gJQaWFP
2026-08-29 11:50:32 +00:00
sylph-decoder
7737df6ba1 tools: a screen oracle that matches CONTENT, with movie frames as its controls
The statistics oracle (green/white/mean) cannot reject the class it exists to
reject. A frame of ADV.wmv with a bright green laser reads green 0.0018 /
white 0.086 / mean (53,67,76) -- the title's numbers -- and a probe built on it
tapped (A) into the movie, then waited 120 s for a menu that was never coming.

screen_match correlates against committed captures instead. Controls run before
it was ever used live: 8/8, and the negatives are COMMITTED movie frames rather
than scratch grabs -- an earlier list pointed at two scratch files and a later
run of the same probe overwrote one, failing the control for the wrong reason.

Two paths, both controlled. The exact path costs 1503 ms/frame, which is fine
offline and catastrophic in a live loop; fast=True decimates 4x for 38-75 ms and
agrees with the exact path to +/-0.005 on all eight.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KNR5Y79D1T4bBr6gJQaWFP
2026-08-29 11:50:18 +00:00
Sylpheed RE agent
7eeae3006a re(ui): the focus ring SPINS, the game draws it, and the leaf owns the f record
Three things, all from parsing ptbtn0Nf.rat as a build.

1. THE RING SPINS. Its two keyframes differ in exactly one field: rotation_deg
ramps 0 -> 360 with position, scale, alpha and tint all constant. A spin in
place, the same shape as the GP_BUNK example already recorded.

2. THE ORACLE CONFIRMS THE GAME RENDERS IT. In the OPTIONS-focused capture the
ring's bright head sits in a completely different angular position from the
sprite's own -- caught mid-spin. This is a SECOND independent confirmation that
rotation_deg is drawn, now on a different screen and a different element from the
ptloop sweeps, and it raises rotation's priority: it is not a title-only concern
that sits off-screen at rest, it is the main menu's focus marker.
NO ANGLE IS QUOTED. A brightest-region centroid says ~250 deg, but the control
refuses that precision -- rotating the sprite by a known 30/90/180/270 and
re-measuring gives errors up to 19.8 deg. What survives the error bar is that a
<=20 deg error cannot manufacture a ~250 deg displacement.

3. WHICH PLACEMENT WINS -- correcting this page's own earlier caveat, which said
to use the leaf only for elements the parent does not declare. Right for a BASE
record, wrong for an f record: the parent declares NO element for ptbtn0Nf.rat at
all (zero of build 5's 16), so the f record's placement comes from its leaf for
BOTH elements, label included. The label's (-7,-7) is load-bearing -- the f
sprite is 13px larger per axis and -7 keeps them concentric (535+96/2 = 583 vs
542+83/2 = 583.5). Corroborated against the oracle: the focused-minus-unfocused
region is x 505..703, and the leaf predicts a right edge near 707 where the
parent reading predicts 714.

Also exposes UiBuild::records (name -> (offset, size) of a nested .rat leaf).
Nested records were parsed into a PRIVATE map, so a consumer holding a UiBuild
could not locate a leaf's bytes at all -- which is exactly what blocked the port
from reaching the ring.
2026-08-29 08:46:53 +00:00
Sylpheed RE agent
d110cf38c7 media: expose se_wave_riff -- the menu's SE cues, assembled where the format lives
The port is forbidden from reimplementing media assembly and Static.slb is
exactly that case: no RIFF, no seek chunk, no XACT container, just a packed run
of whole 2048-byte XMA1 packets, so a wave is defined only by (offset, packet
count) and the header has to be synthesized. That step now happens once, in the
crate that owns the format, instead of in each consumer.

`slb::xma1_wave_riff` wraps raw packets; `media::se_wave_riff` looks the bank up
and reads just the packets asked for. Both reuse the existing synth_xma1_fmt /
build_riff, which are already byte-identical to what tools/re-capture/
slb_extract_wave.py writes -- so this is exposure, not a second implementation.

It reads a TARGETED range rather than the whole bank, and that is load-bearing:
Static.slb is the ONE entry of sound.pak's 9 519 whose declared extent runs past
the end of the extracted segments -- by exactly 616 768 B -- so reading it whole
fails outright on this extraction. Every cue we need is in the first few hundred
KB. Recorded rather than worked around silently.

Verified as an artifact, not a compile: all three cues decode through ffmpeg to
mono 48 kHz PCM at 0.533 / 0.344 / 1.016 s, non-silent (rms 2085 / 2985 / 4327,
peaks 29813 / 16973 / 32767). The refusal path is exercised in the same run --
an impossible packet count is rejected rather than returning a short stream,
because a truncated XMA decodes to plausible-sounding garbage.

Also adds docs/re/captures/ORACLE-CAPTURES.md: an index of the nine canary
framebuffer captures already in this repo, and a plain statement that THEY are
the reference and `screen render` is not.
2026-08-29 08:41:42 +00:00
Sylpheed RE agent
9ca1eb50fd re(ui): answer four of the port's five asks -- splash, fade-out, focus, gamma
1 SPLASH ADDRESSING (was blocking P3). No content predicate exists: design size
fails (every extra composable bundle sampled is 1280x720, like every screen) and
element count fails (fragments run 2..15, the splash halves are 3 and 7). But
GP_TITLE needs none -- `--all` adds exactly four bundles there and all four are
real screens, with the --all index equal to the pak entry index 1:1. And there
are TWO splash screens: 11/14 are the developer logos, 10/13 are the SQUARE ENIX
publisher wordmark, which the port did not have and which the boot shows first.

2 FADE-OUT (was blocking P3). It is (a), and it is bigger than the fade quad.
Every element ends on exactly ONE untimed keyframe, which rules out (b); that
block is where the screen plays out -- quad to a=255, buttons/labels/glows to
a=0, frames hold. (c) is refuted by a null test that discriminates: a black quad
alone holds the button/background brightness ratio constant, and through the
fade it falls 6.50 -> 1.94, 3.4x monotonic.

3 FOCUS (saves P5 rework). Over-vs-instead is unobservable -- the focused sprite
covers the base at 100.0% of base-visible pixels on three pairs once aligned
(true offset (7,7); the centre alignment reads a misleading 78-84%), and
compositing both ways differs by RMSE 1.1 inside the button rect. The real defect
is the focus record's SECOND element: ptbtn0Nf.rat declares ptbtneff01.t32 (a
42x46 glowing ring, focus only) plus the bright label, where the base declares
one sprite. That ring is the marker the port draws nowhere.

5 GAMMA. The capture is not neutral: capture ~ 255*(render/255)^g, g ~ 1.34-1.49,
and the chain says it is a ramp the GAME installed, not a capture artefact. So
RMSE against captures has a floor. Reach stated: the flat patches are all dark
(render ~0-60), so midtones and highlights are unconstrained.

4 ROTATION is a human's call and is recorded in MISSION, not acted on -- the port
rotating while the reference renderer does not would make verify-screen report a
large diff meaning "the port is right". The RE half is answered: rotation is
about the declared pivot, measured against a GPU capture.

The focus record's +20 element-count word is marked 🟡 not  -- read on GP_TITLE's
ten button records only; the disc-wide check is written and still running.
2026-08-29 08:19:23 +00:00
Sylpheed RE agent
0ecb20bdbc re: the swoosh is solved -- the game draws it as rotated quads and our
blit cannot

Ran the route I corrected last iteration, and it answered the question I
had classified as undecodable.

First the ninth candidate died: every vertex colour in the whole capture
is <alpha>FFFFFF, white RGB with only alpha varying. The game passes no
colour, so that hypothesis is refuted from the running game rather than
from the file.

Then the geometry gave it away. Draw 2 submits TWO PARALLELOGRAMS,
neither axis-aligned -- edges (0.54,-0.56) and (0.44,0.79), roughly 45
and 61 degrees, both extending to y=+/-1.81 in NDC, well off screen.
That is the diagonal Z stroke. And ui_layout::blit walks rows and columns
of an axis-aligned rectangle; it has no rotation at all. So we blit the
sprite upright where the game draws it skewed, which is exactly the
signature I measured: right on average at +1.83, right in position with
the correlation peaking at zero shift, wrong in structure at 0.70.

Nine candidates, and the answer was never going to be a field, because
the difference is not in the sprite -- it is in how the quad is built.

Two things left open and written as such. The decoded keyframe carries
fade, scale, tint, x, y and time and NO rotation, so where the rotation
comes from is not decoded. And the pink-versus-white reading I have been
carrying was a visual comparison of two differently-shaped renderings; it
should be re-checked after geometry rather than treated as a separate
defect.
2026-08-28 22:13:50 +00:00
Sylpheed RE agent
cdb5a99def re: the title "slab" is the logo swoosh drawn white and thick, and it
lands on the known tie-break group

Chasing the residual I left open last iteration, and it turns out I had
attributed the port agent's slab to the wrong thing.

First the refutation. My hypothesis was that our dim is applied over the
whole frame rather than beneath the UI where its layer key puts it. If
that were true the logo would render too dark. It reads +2.36 against a
background of -0.74, so the compositor honours the paint order and the
hypothesis is dead.

What the residual actually is: with the dim in place the error is not
uniform but concentrated in one band, running -38.6 in one tile and +33.8
three tiles to its right. Cropping that band from capture and render
shows it immediately -- the game draws the logo's Z swoosh thin with a
pink edge, and we draw it thick and solid white. That, not the missing
dim, is the washed-out slab over the title logo the port agent reported.
The dim explains a uniform +13; this explains the slab.

The elements are ptlogo_back2, its glow, and the five ptlogo_back2eff1..5
segments -- which are also the group carrying this corpus's known
unsolved paint-order tie-break at key 0x8083, on the same screen. I have
recorded that as a lead and explicitly not as a diagnosis, because a
blend-order swap is a poor explanation for white instead of pink and I
would expect a second cause.
2026-08-28 21:31:52 +00:00
Sylpheed RE agent
d217c07db2 re: the title's arrived pose is rest, and the washed-out slab is a
MISSING dim quad

Both halves of what the port agent could not settle, answered against the
plate-free capture rather than against another renderer.

Rendering build 4 and edge-correlating to the capture of the real screen
gives 0.9163 at zero shift, 0.92/0.78/0.93 per band. The geometry of rest
is the title's arrived pose, so the timeline is not needed there.

The slab is the more useful half, and it is inverted from how it looked.
rest alone renders uniformly +13.14 too bright, R +12.35, G +13.58,
B +13.48. Drawing the .prm primitives collapses that to +0.55. The
element is pteff02.prm, the 25 percent dim at rest t=46 with fade 0x40,
and --primitives is OFF BY DEFAULT. So the washed-out cyan slab is not
something being drawn that should not be; it is a dim that should be
drawn and is not, and because the title art is blue-dominant the
shortfall reads as a cyan wash. Any consumer of screen render that omits
--primitives on this screen gets it.

Two residuals recorded rather than tidied. With the dim drawn the mean is
essentially exact but per-pixel agreement gets slightly WORSE --
edge-correlation 0.9163 down to 0.9066 and pixels off by more than 20
rising from 108051 to 162636 -- so the dim's average contribution is
right while its application is not exactly the game's. And separately the
per-tile map shows a +25 to +38 hot spot on the upper-right Z swoosh,
which a capture from the first iteration of this work already showed pink
in the game and white in ours. Same region, still undiagnosed.
2026-08-28 21:27:04 +00:00
Sylpheed RE agent
ed4e5c7b37 re: build 4 captured without the plate, and keyframe groups hold rather
than loop

The port agent ranked a plate-free capture of build 4 above any further
static RE, so that came first.

B from the main menu returns to the title and the plate fades in a beat
later, which opens a clean window. Recorded at 20 fps from the press: the
art appears at 1.10 s, builds in to 3.70 s, sits settled and unobstructed
until 5.00 s, and the plate arrives at 5.10 s -- the band jumps from 282
to 3755 bright pixels. Committed the frame at 4.0 s as the reference for
the cyan glow slab they report drawing and the game not having.

Their other sub-question -- whether a keyframe group loops or holds --
falls out of the decoded sweeps plus a measurement I already had, and the
two agree. ptloop01's final keyframe parks pteff03.t32 at x=1521 and
ptloop02's parks pteff03a.t32 at x=-839, both off-screen on a 1280-wide
design; and over 18 s of settled title the centre tiles sit at sd <= 0.01
when a looping group would recross the screen every 7.5 s. So groups HOLD
at the last keyframe. The loop*.rat name is misleading.

Also recorded, in the corpus rather than only in their report: the
rest_plateau bug, with their exact identifying condition -- the final
untimed keyframe has the same pose as the last timed one -- the six
elements it misses on main_menu, and the bracket it drops. That closes an
open question ui-paint-order-key.md has carried for a while about
ptframe1 and ptframe2 resting at alpha 0 while the capture shows the
frame plainly. Same two elements, same cause. Not fixed yet; the change
is in ui_layout's rest().

And a METHOD line I would not have written myself: two renderers agreeing
is not evidence the field is right. Their composite and screen render
matched to 3/255 on main_menu and both omitted two elements the game
draws, because both read one field through one decoder.
2026-08-28 21:05:35 +00:00
Sylpheed RE agent
9f39e8ee72 re: the splash confirmed against the running game, and timed
Closing the amber I raised last iteration: the splash renders had no
framebuffer capture to diff against. Recording the boot from the moment
the window appears, at 2 fps, catches it before the movie.

Both halves match their own render at zero shift, each with the OTHER
half as a negative control: publisher frame 0.9146 against entry 10 and
0.027 against entry 11; developer frame 0.9792 against entry 11 and
-0.033 against entry 10. The EN/JP twins score 0.876 and 0.966, so the
test tells a screen from a different screen but cannot tell a pair apart,
and I have said so rather than implying it discriminates.

The recording also times the front of the boot, which Q6 and Q7 only had
from the title onward: SQUARE ENIX from about 0.5 to 4.0 s, black,
developer logos from about 5.0 to 7.5 s, black, then ADV.wmv from about
9.5 s.

One trap avoided and written into METHOD. The frames around 9.5 to 12.5 s
show SQUARE ENIX again in cyan, and read from the capture alone that is a
third logo screen. It is not -- it is the intro movie's own opening,
which the milestone-2 notes describe as white SQUARE ENIX plus cyan glow
plus red diamonds. A logo appearing twice may be two different things.
2026-08-28 20:35:10 +00:00
Sylpheed RE agent
b302558d26 re: the developer splash renders -- screen 1 of 5 finally has a composite
Two pages of this corpus disagreed. MISSION says the splash "is the RATC
screen, which already renders"; ui-paint-order-key.md says it "cannot be
rendered by screen render at all". Running the tool both ways settles it.

screen list --all shows all 16 GP_TITLE entries instead of 12, and the
four the default listing drops are the splash, each half shipped twice:
entries 10 and 13 are the white SQUARE ENIX publisher logo, entries 11
and 14 the GAME ARTS / SETA / studio anima developer logos. Entry 11's
seven elements are the three logos, their three _eff glows and the
palogo_eff0.prm backdrop -- exactly the composition ui-paint-order-key.md
had measured for the splash without being able to draw it.

So the "cannot be rendered" line is wrong and is corrected in place. What
is true is narrower and worth keeping: the splash is invisible to the
DEFAULT listing because is_build wants a .rat child, so anyone who does
not pass --all concludes it is missing. That goes in METHOD -- a default
filter can hide a whole screen and the corpus will record it as absent.

The payoff is for the port rather than for the RE: the first of the five
screens now has a reference composite, which it did not have. Marked
amber on one point -- there is no framebuffer capture of the splash in
this repo, so the match to the running game is by description against the
milestone-1 notes, not by pixels.
2026-08-28 20:29:33 +00:00
Sylpheed RE agent
aea529e7dc re: S00A.wmv confirmed off the running game -- the movie was never
ambiguous, the sampling was

Continuing last iteration's amber rather than starting something new. The
fix was already named there: stop using 5-second screenshots and record
the display.

Recorded with ffmpeg x11grab at 10 fps and matched every 0.5 s. Across
the 25 consecutive samples from capture 5.0 s to 17.5 s the S00A playhead
is strictly non-decreasing -- 1.0 through 11.0 s, advancing at
essentially real time -- with scores at or above 0.96 and four of them at
0.999 or 1.000 against a runner-up in the 0.78-0.83 range. S00A is the
top match on 23 of the 27 frames carrying signal. So MS00A -> S00A.wmv is
decoded from the manifest AND measured off the game, and the intro begins
about 4.5 s after A on the save slot.

The previous attempt's failure is worth its own METHOD line, because it
did not look like a sampling problem: it looked like weak evidence for
the wrong film. Sparse sampling produced contrast-23 frames, a playhead
that would not join up, and one frame preferring ADV. Sampling does not
weaken a signal gracefully; it turns it into noise shaped like a
different answer.

One aside recorded and not chased: in the S00A 5.5-10 s window, ADV also
scores 0.97-0.99 at its OWN monotone playhead of 33.5-37.5 s. Two films
matching strongly with two consistent playheads is not noise -- it reads
as the boot movie being a trailer cut from the story cutscenes, which
also explains why the sparse run kept flipping between them.
2026-08-28 19:23:57 +00:00
Sylpheed RE agent
935f7ecab8 re: one A skips the intro, and NEW GAME was never a hang
Two corpus claims died in one boot, and both were costing us.

Q9's last row: a movie IS skippable with a single A. One tap ~45s into
the boot put the title up at ~57s, against a ~193s no-input baseline
measured over three boots, and Canary's own keystroke counter went 3->4
so exactly one press was delivered. The skipped-to title is fully
functional -- it draws the PRESS A plate and a second A opens the main
menu. What actually breaks the boot is hammering: the 88-press run in the
traps doc. The scripts' "tapping breaks the title" comment is too broad
and costs every scripted boot two and a half minutes.

Q4's last row: A on NEW GAME does not hang. It opens DIFFICULTY
(EASY/NORMAL/HARD/BACK, focus on NORMAL), then SELECT DATA, and only then
does the guest throw -- at PC 0x82307128, which is inside sub_823070B0,
the cache-manager STL erase this corpus already documents and which has
nothing to do with the menu path. The screen sat unchanged for 90s
because it was a menu waiting for input from a loop that never pressed
anything. That is now a METHOD line: a screen that never changes is not
necessarily hung, and the fix is to look at it and press something.

Also METHOD: never run ps -ef in this container -- all three long-lived
processes carry the entire loop prompt as argv.
2026-08-28 18:45:59 +00:00
Sylpheed RE agent
e64965b3f9 re: S1 -- the Ready Room probe is a no-go, and not for the reason the
question expected

One iteration, as the mission gates it, and no Ready Room work started.

The two criteria it named both pass. It is NOT 3D: the corpus already
holds a capture of the running screen and it is a flat six-item menu in
the same visual language as the title. And unrecoverable paths do not
mean unrenderable screens -- content enumeration finds 60 builds in the
1106 entries.

What kills it is something the question did not ask. GP_READY_ROOM.pak
does not appear to hold the Ready Room's own menu at all. Two signals
agree: the four largest builds composite to briefing panels -- a planet
readout, unit markers, direction arrows -- and, much harder, the 902
bundles declare 317 distinct element names of which NONE matches any of
the six labels on the screen (takeoff, brief, hangar, pilot, option,
mission, ready all return nothing). The pak is the briefing /
tactical-map content behind the BRIEFINGS item. If this is ever picked
up, the first job is finding where the menu lives -- GP_BUNK.pak is the
obvious place -- not naming this pak's keys.

The probe also reaches back into something already delivered: kind ==
0x3002 finds ZERO buttons across all 902 bundles here, while GP_TITLE has
16. 0x3002 is one member of a 0x3000 family with sub-bits, and this pak
uses 0x3000/0x3004/0x300c/0x3008. Nothing in the milestone changes --
every screen in scope is GP_TITLE -- but the handoff now says not to ship
that equality as a general button test.
2026-08-28 18:24:48 +00:00
Sylpheed RE agent
7fef19b3a0 re: the transition between screens is a fade through black, and most of
its timing is on the disc

Q7. Every title-side screen carries a full-screen black .prm quad that
paints last, and its keyframe group IS the transition: black at T0, clear
by T1, clear until T2, then back to black on exit. Read with the corpus's
start-of-a-ramp rule and Q1's time unit that gives 0.87s for EXTRAS,
0.97s for the main menu, 4.08s for the title -- from the file, not from a
stopwatch.

The disc-wide check is per-pak all-or-nothing rather than the 41% the
headline count suggests, and GP_TITLE's 6 of 12 is the useful row: the
six builds carrying a fade quad are exactly the six SCREENS, and the six
without are exactly the six overlays. GP_DIALOG is 0 of 133. That is
independent corroboration of the overlay finding from two iterations ago.

One piece is NOT on the disc and says so: the fade-OUT length. The fourth
keyframe has no time slot, because a group's last block stops four bytes
short. Measured instead, at 30fps, ~0.4s and the same both directions.

And a warning I earned: the luminance rise after a transition is NOT the
quad's ramp. The incoming screen's own elements animate in after the quad
has cleared -- 1.47s observed against a declared 0.97s. Time the fade
from where the frame is pure black.

Rig: screenshot samples at 0.5 Hz and cannot see a 0.4s fade at all,
which is why an earlier burst called this an instant cut. ffmpeg x11grab
at 30fps instead; both go in METHOD.
2026-08-28 18:05:19 +00:00
Sylpheed RE agent
4e745c8177 re: the title menu wraps -- Q5 measured, Q4 driven, and one of my own
method lines withdrawn

Q5, measured off two boots: up/down move one item and WRAP at both ends
on the 5-item main menu and the 3-item EXTRAS alike; left/right do
nothing; B goes up one level and restores focus to the item you came
from (4/4); B on the main menu returns to the title; B on the title does
nothing. The menu opens on TUTORIAL -- the middle item -- 2/2, though a
third recorded run implies NEW GAME, so that one is reproducible rather
than invariant and says so.

Q4 by driving: LOAD GAME opens the save-slot list, TUTORIAL the lesson
list, OPTIONS the settings menu, EXTRAS build 6, MISSION SELECT the stage
list. NEW GAME is not tested -- A on it hangs the emulator and this
iteration needed the session. The GamePart ID behind each is NOT
measured: it is the entry of the decoded id table whose name matches the
screen I saw, and the page says so rather than wearing the badge.

And the withdrawal. Last iteration I wrote that these menus drop d-pad
presses shorter than ~0.3s. They do not. Once wrap is measured, every
press count I had is exactly right -- four presses moved four steps
THROUGH the bottom, which lands one above where a non-wrapping menu
would. I invented hardware flakiness instead of testing the ends of the
list. METHOD keeps the withdrawal rather than deleting the line.

Also: label brightness is not a cursor oracle here -- the background art
outshines the highlight on some rows. menu_focus.py reads the focus ring
instead, 254 vs <82, no tuning.
2026-08-28 17:53:46 +00:00
Sylpheed RE agent
bebdb18a02 re: Q3 was already answered in docs/re -- deliver it, and test it on EXTRAS
The handoff had Q3 as "runtime-solved only". It is not: the layer key at
+0x0A of the T8aD sprite header, stable-sorted, is a file-derivable paint
order, already checked against five measured orders and already driving
the compositor for every build on the disc. That answer had simply never
reached the page the port agent reads.

The new evidence is EXTRAS -- the first screen composited from the rule
alone and scored against a framebuffer capture the rule had never seen.
0.9620 at zero shift, against a same-tooling control of 0.9657 on the
screen the rule was fitted to.

Written down with its limit rather than its headline: align_to_capture
correlates edges, and a paint-order change moves blends, not edges, by
=<45/255 on a few per cent of pixels. So the score is evidence for
placement and only CONSISTENT with the order. The sharp A/B needs a
rebuild and is named in the doc instead of being claimed.
2026-08-28 17:39:29 +00:00
Sylpheed RE agent
88b3ce9af5 re: which GP_TITLE build is which screen, measured against the game
Q2. The archive is eight screens shipped twice, English and Japanese --
not the "build 4 title, 5 main menu, 6/8/9 submenus" the handoff claimed.
Build 8 is the JAPANESE main menu; 6 and 9 are the EN and JP EXTRAS, and
EXTRAS is the only submenu GP_TITLE holds. The PRESS (A) BUTTON plate is
its own build (2/3), composited over the title art and faded in a beat
later, not a state of build 4.

Confirmed by booting to the main menu and walking it: title, PRESS (A),
main menu and EXTRAS each match their render element for element. Builds
0/1 and 10/11 -- a DELTASABER / SYLPHEED A.I. plate -- were looked for in
the whole boot filmstrip, every title-side screen and the attract loop,
and appear in none of them; the reach of that negative is written down
rather than filled in with a guess.

Two rig traps went into METHOD: the menus drop d-pad presses shorter than
~0.3 s, and a grab 2.5 s after a transition can catch a screen mid-fade
-- which nearly wrote "the returned title has no plate" into the corpus.
2026-08-28 16:55:33 +00:00
Sylpheed RE agent
27e8c51978 re: a keyframe time is worth half a rendered frame, and the ramp is linear
Q1 of the menu port, measured against the running game rather than reasoned
about. The developer-logo splash is the cheap target: it is the first thing the
guest draws and its bundle declares short, unambiguous ramps.

Two results, both frame-exact and both emulator-speed-independent (frame numbers
are VdSwap counts, the guest's own frames):

  * the ramp is LINEAR. A declared 15-unit fade lands on round(255*k/15) for all
    seven of its samples with zero error, k stepping 2,4,6,8,10,12,14. No ease
    can reproduce a constant step of 34 at both ends.
  * the animation clock advances 2.000 time units per submitted frame, over six
    consecutive intervals with no residual, with 1 unit as the quantum
    underneath (one frame in the fade-out advances by 1).

The conversion to seconds is one step further and is flagged as such: 300 frames
took 10.87 s = 27.6 present-frames/second, which reads as a 30 Hz title at 92 %
under the emulator and gives 1 unit = 1/60 s -- the title build 4.2 s, the main
menu build 1.1 s. That reading is not proven, because the rate was measured
while the guest was still streaming from the ISO; the page names the one test
that would settle it and says what changes if it goes the other way.

Committed beside it: the raw draw capture and the per-frame quad CSV, so the
numbers can be re-derived without a disc or an emulator.
2026-08-28 15:56:33 +00:00
Sylpheed RE agent
2c44692ec6 re: THE DECREMENT IS MEASURED -- REMAINING OB is fully solved
SYLPH_WEAKEST=1 (00052b0) worked on its first live run: concentrating fire on the
already-damaged attacker produced the kill that four previous runs could not.

All gates enforced first -- FLIGHT confirmed, stage asserted, mission clock shown
advancing -- then:

  live e010 floor: 16 for samples 0-11, then 15 for samples 12-39 (one death)
  counter: 004 -> 008 at 02:06.33 (t=120 arrival, +4)
           008 -> 007 at 03:10.28  <-- DECREMENT, bracket (171.2s, 190.3s]
           007 -> 011 at 03:48.40 (t=210 arrival, +4 FROM 7, not from 8)
           011 held to 06:50.30 -- ceiling 11, where every prior run reached 12

Conclusive because: it fell by exactly 1 and not by 4, so the counter tracks CRAFT
not squadrons; exactly one attacker died and exactly one decrement occurred, in the
same window; and the CEILING moved with it -- a counter that merely read arrivals
would still have shown 012.  Control held: turrets fell 109 -> 92, seventeen
deaths, none of which moved it.

Settled reading: REMAINING OB is the number of objective-marked craft still alive,
the marked craft being exactly the members of the phase's A-route squadrons.  It
rises by a squadron's membership on that squadron's route arrival time and falls by
one per marked craft destroyed.  Every number was predicted from Route_S02.tbl and
UnitGroup_S02.tbl before it was measured.
2026-08-27 03:53:08 +00:00
Sylpheed RE agent
b69cc2325a re: quantify why the OB kill test keeps failing -- attackers are damaged, not missed
Fourth Stage 02 run: 520s of pilot with SYLPH_PREFER=e010 (198 fire=1), per-class
counts every 10s, OB+TIME throughout.

ARRIVAL TIMING n=4: 008 first at 02:05.00 (step bracket contains 120), 012 first
at 03:45.47 (bracket contains 210), then 012 held 3.5 more minutes with no fall
and no 016.

THE BLOCKER IS MEASURED, and it is not aim.  Reading hull at pos+0x154 for every
live e010 at the end: 14 of 16 are damaged, hulls 360..500, lowest 360/500 = 28%
gone.  The shots land; they just do not finish.  A kill needs roughly 3-4x longer
on one target than ~8 minutes of combat produced.

The contrast with the control shows the mechanism is hit points: e007 turrets
(HP 100) lost ~9 dead, 106 -> 97 live, while e010 attackers (HP 500) lost none and
the floor never moved off 16.

Decrement therefore still unproven after four runs, and "fly longer" is not a fix
since ~half of runs end early.  Options recorded for a redesign: point the pilot
at the WEAKEST attacker (hull is readable per entity; one is at 360/500), use
missiles (never deliberately fired), or wait on a scripted kill.
2026-08-27 03:07:33 +00:00
Sylpheed RE agent
1fc6338122 re: turret control CONFIRMED, arrival timing n=3, decrement still unproven
Third Stage 02 run, pilot with SYLPH_PREFER=e010 (312 fire=1 samples), per-class
live counts logged every 11s beside the HUD (new tools/re-capture/class_count.py).

CONTROL CONFIRMED: the live turret population fell 108 -> 101 -- seven e007 deaths
-- and REMAINING OB never decremented, only rose.  Previously this was inferred
from a run whose kill log happened to be turrets; it is now measured with the
classes counted directly.

ARRIVAL TIMING n=3: 004 -> 008 in (108.7s, 125.5s] and 008 -> 012 in (204.2s,
221.7s], both brackets containing the predicted 120 and 210.

The live e010 count sat at exactly 16 in 20 of 26 samples -- precisely phase 1's
e010 roster (ADT102/ADT107/ADT113/ADS151, each n=4) -- an independent runtime
corroboration of the static roster.

DECREMENT STILL UNPROVEN: the e010 floor never fell, so no marked attacker died
and the counter had no chance to move.  Three runs have failed to kill one.  The
blocker is combat effectiveness, not instrumentation.

Artifact recorded: six of 26 class samples read 17-28.  Spikes are always upward
and transient -- the tool dedups on a position triple read just after the pattern
scan, so an entity written between the two reads is counted twice.
2026-08-27 02:42:46 +00:00
Sylpheed RE agent
b0fc1e6e7b re: kill run -- arrival timing reproduced (n=2), decrement inconclusive, "live attackers" refuted
pilot.py with SYLPH_PREFER=e010, 300s, mission clock sampled throughout.

RISES REPRODUCED: 004 -> 008 in (114.8s, 134.7s] and 008 -> 012 in (196.4s,
213.3s], both brackets containing the predicted 120 and 210.  Second independent
run, so the arrival half is now n=2.

DECREMENT INCONCLUSIVE.  The pilot fought properly -- 424 fire=1 samples, 865 with
the target inside 1500 units, closest approach 79, target e010 throughout, hull
untouched -- and 13 ADAN died (129 -> 116).  The counter held 012 for 98s.  That is
NOT evidence against the decrement: the 13 dead were not identified by class, this
pilot's kills historically skew to turrets, and the live e010 count ROSE over the
run so attacker deaths cannot be inferred from it.  Recording it as inconclusive
rather than as a negative, which is the error this corpus has already logged twice.

POSITIVE RESULT: at TIME 05:56.85, both arms sampled together, REMAINING OB = 012
while live UN_e010_ADAN_Attacker_S = 23.  So the counter is NOT the live attacker
head-count; it tracks a subset, and 12 is exactly the three A-route squadrons'
membership (3 x n=4).  Phase 1 fields only 16 e010, so 23 live means later-phase or
F-route squadrons joined without touching the counter.

Anomaly recorded, not explained: the enumeration reports 2 _Player entities at two
distinct positions.
2026-08-27 02:18:29 +00:00
Sylpheed RE agent
92b9ad6b01 re: CONFIRMED -- the A-route arrival times predict the OB counter's steps exactly
Predicted from the disc alone, before the run: ADT102/ADT107/ADT113, each n=4,
arrive at t=0/120/210, so the counter reads 004, steps to 008 at t=120 and 012 at
t=210, and goes no higher in phase 1.

Measured with the mission clock sampled beside the counter throughout:

  004 held over six samples to 01:45.44
  008 first seen at 02:00.87  -> step bracketed in (105.4s, 120.9s]
  008 held over five samples to 03:17.55
  012 first seen at 03:30.66  -> step bracketed in (197.6s, 210.7s]
  012 held four more samples to 04:36.95, no 016

Both steps land on the predicted second, and the ceiling holds.  Three independent
features -- starting value, both step TIMES, and the ceiling -- came from
Route_S02.tbl and UnitGroup_S02.tbl with nothing fitted to runtime data.  So the
counter's rises ARE the A-route attacker squadrons arriving.

Status upgraded to CONFIRMED for the rises.  The DECREMENT half stays 🟡 and is
now the only open part: this run killed nothing, and the single observed 12 -> 11
remains one sample.
2026-08-27 01:55:07 +00:00
Sylpheed RE agent
d8baf92fb5 re: mission TIME is the liveness oracle, and it explains the earlier "0 movers"
The OB kill test did not run -- the guest froze about a minute into the mission --
but sampling the HUD's own mission clock beside the counter caught it instantly.
Twelve samples over ~4 minutes of wall time: the first reads TIME 00:43.24, every
one after reads 01:02.23 unchanged, with REMAINING OB 004 throughout.  frozen.py
agrees (max_pixel_delta=0), movers in the entity window are 0, and screen_id still
says `flight` with the process alive.

Worth a file because the mission clock defeats both traps this corpus has paid
for: it is the simulation's own counter, so neither a frozen world nor a finished
one advances it, where screen_id and pixel-churn are fooled by a GAME OVER screen
that animates.  Rule recorded: "X never changed" is only evidence if TIME changed
across the same window.

It also closes an open  from earlier this session -- entity-position-anchor-
refuted.md's "0 of 64 regions changed across 357 MB, cause unknown, not
reproduced".  Same signature, now reproduced: it was this freeze.

Still unrun for the fourth iteration: the kill test itself.  Not conceptually
blocked -- blocked by attrition, with this run dying at t~62s, before even the
t=120 arrival the trajectory prediction needs.
2026-08-27 01:34:09 +00:00
Sylpheed RE agent
e3098f7316 re: LOCATE REMAINING OB at 0xbdb69668, and refine the ...9668 pattern
Ran this file's own transition filter on a guarded Stage 02 run (stage asserted),
reading the HUD from a crop taken at the same instant as each memory sample:
scan at HUD 004 -> 41537 candidates; filter at HUD 008 -> 8; verify across the
008 -> 012 transition, which was NOT selected on -> exactly ONE survivor.

That survivor, 0xbdb69668, tracked 4 -> 8 -> 12 against the HUD's 004 -> 008 ->
012.  The other seven collapsed into noise at the first unselected transition,
which is precisely what that rule exists to catch.

The file's "try 0xbdb59668 first, re-scan when it reads 0" rule worked verbatim:
it read a hard 0 here, and the re-scan cost about the predicted five minutes.

The ...9668 page-offset pattern is REFINED, not reinstated: the three located
addresses (0xbdb49668, 0xbdb59668, 0xbdb69668) are three ADJACENT 64 KB pages at
one offset, and in this run exactly one of 8192 probed pages held 12 -- the
counter -- making it a one-step lookup.  But the 2026-08-26 refutation stands as
measured (zero ...9668 VAs held the HUD value in that run), so this is a fast
heuristic to be HUD-checked, not a law.
2026-08-27 00:23:22 +00:00
Sylpheed RE agent
7e89249f46 re: refute the "counter sits at page offset 0x9668" prediction
The file proposed that the counter lives at a fixed offset inside an allocation
whose base moves by whole 64 KB pages, and stated the test itself: "a third scan
should again land on ...9668".  Ran it on a fresh guarded Stage 02 run.

Probing all 8192 pages of the form 0x????9668 across 0xa0000000-0xbfffffff: with
the HUD at 004, exactly two VAs held 4 (0xbc3f9668, 0xbe3f9668); with the HUD at
012, ZERO held 12.  Both candidates also failed the file's own transition rule --
over 252 s 0xbc3f9668 held a flat 4 and 0xbe3f9668 flickered 4/0 while the HUD
went 004 -> 012.

Both arms were sampled at the same instant (cropped HUD digits beside each memory
read), after a stale-screenshot comparison earlier in this session produced a
spurious 13-vs-004 mismatch.

Scope kept narrow: this refutes the page-offset prediction, not the confirmed
finding that 0xbdb59668 carries the counter in some runs.  The counter's address
in THIS run remains unknown -- no transition filter was run.
2026-08-26 23:57:25 +00:00
Sylpheed RE agent
dd052287e9 re: REMAINING OB's address is run-dependent -- cross-run stability refuted
The doc carried a 🟡 saying cross-run stability was untested.  Tested now on a
fresh guarded Stage 02 run (stage asserted): the HUD reads "Remaining OB : 004"
while RAM at the documented 0xbdb59668 reads 95748078, constant over four samples
12s apart.  Not 4, not near 4, not moving.  So the address belongs to that run's
heap, as the corpus's own heap-reallocation warning predicts.

No constant-shift shortcut either: a BE u32 equal to 4 occurs 1654 times within
+-1 MB of the old address and 11202 times within +-16 MB, far too many to isolate
without the transition filter.  The durable result is the METHOD (ob_scan.py:
scan at one value, filter against live memory at a DIFFERENT value), not the
number.

Also fixes a contradiction in INDEX.md, which said in one row that the address is
"still " while another row linked the doc that had already CONFIRMED it.
2026-08-26 23:34:36 +00:00
Sylpheed RE agent
549134c415 re: the guarded route now verifies end-to-end, and the menu order is confirmed
With the sliver stopped at both ends (290cbe3 producer, 30e53f5 consumer) a full
guarded run reached Stage 02 and asserted it: "guard: menu confirmed (1279x675)"
then "OK: UN_f101_TCAF_Acropolis is in the 14 definitions", exit 0.

Read the menu the guard confirmed rather than continuing to assume it:
NEW GAME / LOAD GAME / TUTORIAL / OPTIONS / EXTRAS with the cursor on NEW GAME.
So dpad-down x1 = LOAD GAME is correct and dpad-down x2 = TUTORIAL, one press
further -- exactly consistent with the observed mis-selection when the guard was
being fed a sliver.  Capture committed as captures/main-menu-items.png.

Stated plainly: n=1 for "reproducible", and why the game window leaves the window
tree during a load was guarded against, not investigated.
2026-08-26 23:08:11 +00:00