Commit Graph

326 Commits

Author SHA1 Message Date
Sylpheed RE agent
2efc0b7c59 docs: backlog — tie-break refuted six ways; residual is one blend on one screen 2026-08-19 09:34:29 +00:00
Sylpheed RE agent
a15e0ad39c formats: the tie-break, refuted six ways and measured down to the pixel
With the layer key and the primitives' implied keys in place, the tie-break -
how the game orders elements sharing a key - is all that is left between the
derived order and ground truth. Three measured screens now constrain it.

On the menu and the splash every tied group comes out in declaration order,
which is what the stable sort already gives. The title is the only screen that
discriminates, and nothing predicts it: 0x8083 x5 paints eff1, eff2, eff5, eff3,
eff4, and 0x80a0 x7 paints logo1 x3, tm, logo2 x3.

Refuted: declaration order; RATC child order; first keyframe time (52, 56, 62,
58, 60 - the measured order is not sorted by them); resting keyframe time;
resting X or Y (938, 938, 64, 788, 447); and T8aD header words +00, +04, +0c and
+10, which are either identical within a group or unsorted.

Child order is worth its own line: a strict improvement over declaration order
(7 misplaced positions on the title instead of 9, and it recovers the logo
grouping) and exactly right on the other two screens. NOT adopted, because on the
only screen that can tell them apart it is still wrong.

Adds a test that measures what the residual costs instead of assuming it. Of the
3 disagreeing pairs of drawn elements across all three screens, all 3 have
overlapping bounding boxes and 2 share opaque pixels: ptlogo_back2eff5 against
eff3 (22568 px) and eff4 (32395 px). The third pair, ptlogo2 vs ptlogo_tm,
overlaps by two columns and shares NO opaque pixel - the wordmark is transparent
there. A bounding-box test called that a defect; reading the alpha says it is
not, which is why the test reads pixels. The set is pinned, so a change that
makes it worse fails.

15 disc tests green.
2026-08-19 09:34:10 +00:00
Sylpheed RE agent
6c93d43446 docs: backlog — primitive layers tabled; the tie-break is now the only gap 2026-08-19 09:16:13 +00:00
Sylpheed RE agent
610b21c434 formats: a primitive layer is not in the file - measure it, table it, prove it
Two places checked for the key a .prm element sorts by, both empty:

  * the declaration entry's four unread words are constant across every element
    of all three measured screens (+28=0, +36=0xffffffff, +56=0, and +44 is a
    button ordinal 1-5, 0xffffffff elsewhere);
  * the bundle carries NO data for a primitive at all - the menu build declares
    pteff00.prm, pteff02.prm and pteff05.t32 and has zero RATC children for any
    of them, its 34 children being 21 T8aD sprites and 13 .rat records.

So the layer comes from the game code. But it is consistent, which is what makes
a per-name table a measurement rather than a fudge. Bracketing each unkeyed
element between the keys of its measured neighbours: pteff02.prm falls in
(0x8010, 0x8040) on BOTH screens it appears on, pteff00.prm is past the maximum
on both, and palogo_eff0.prm is below the minimum on the splash.

implied_layer_key records exactly those and nothing else; an unlisted primitive
keeps u32::MAX and still sorts last. With it, derived_paint_order produces the
same layer-key sequence as the order read off the running game on all three
measured screens - primitives included - and matches element-for-element on four
of the five bundle instances. The fifth is the title, differing only inside its
tied groups, which is a separate open question.

This does not make include_primitives safe by default: the 36 builds that come
out one colour are wiped by pzeff00.prm and pceff00.prm, never measured, hence
not in the table.

14 disc tests green.
2026-08-19 09:15:55 +00:00
Sylpheed RE agent
a1624af24e docs: input was never the blocker; the third permutation came from the main menu
The game receives the (A) press - measured, vk=5800 flags=0001, out of ~6500
polls. Input delivery was never broken; four harness bugs were, and with those
fixed the boot goes title -> one (A) -> main menu.

GP_DIALOG DIFFICULTY is still unreached (NEW GAME hits the standing black-screen
hang), but the main menu carries two primitives, which is what the question
needed. Records the slip in the instrumentation too: logging on XSUCCEEDED
counts X_ERROR_EMPTY as success and buried the two real keystrokes under 6499
empty polls.

Next step is now STATIC: a primitive's position is stable by role across three
measured screens but nothing in the file predicts it, so dump the full 60-byte
declaration entries - four words unread - and look for a word that sorts like
the measured orders.
2026-08-19 08:57:01 +00:00
Sylpheed RE agent
23565ec5ec formats: a third measured paint order — the main menu, with two primitives
Read off the running game with screen_children.py and identified by pivot
signature as GP_TITLE.pak ratc-index 8, the NEW GAME / LOAD GAME / TUTORIAL /
OPTIONS / EXTRAS screen:

  paint order: 1 3 4 2 5 8 9 6 7 15 10 11 12 13 14 0

It is the first measured screen carrying TWO primitives, and they land in
different places, which is the point. pteff02.prm (the 25% dim) paints 4th,
beneath the whole UI; pteff00.prm (the transition fade, resting transparent)
paints last. Both match their positions on the title screen exactly. So a
primitive's place is per-element and stable by role - backdrop first, dim at
slot 4, fade last - and there are now three permutations to test a derivation
against rather than two.

Wired into measured_paint_order, keyed by element names so both language builds
get it. The English build composited with --primitives edge-correlates at 0.9591
at shift (0,0) against a framebuffer capture taken in the same session - a third
screen confirming paint order, resting pose, fade alpha and primitives at once,
against a capture this project had not seen before.

13 disc tests green.
2026-08-19 08:56:21 +00:00
Sylpheed RE agent
20b0edc2c2 docs: backlog — the keystroke swallow is fixed and refuted as the cause 2026-08-19 08:21:27 +00:00
Sylpheed RE agent
e3ad6ad28c tools: fix four boot-harness bugs; find the swallowed-keystroke cause and refute it
Chasing why (A) does not advance the title turned up four harness bugs, one real
root cause, and a refutation of that root cause as the explanation.

FIXED, each verified:
  * boot_menu.sh sent the emulator stdout to /dev/null, which is why none of the
    rest was visible. It now keeps a log and prints its path.
  * skip_intro.sh probed pixel (625,618) for the green (A) glyph - a 1280x720
    coordinate. screenshot returns the 1279x675 game surface, where that point
    is on the copyright line and reads (8,17,31). Symptom: a 600s TIMEOUT with
    the title on screen. Now classified with screen_id.py; next boot printed
    "TITLE at 239s -> A".
  * skip_intro.sh no longer taps through the movies. Making that press real (the
    vgamepad fix) was a REGRESSION: the boot then reached the title in 90s and
    that title accepted nothing. menu_draw_capture.sh records the same finding
    independently.
  * boot_menu.sh signs in whichever profile exists instead of a hard-coded XUID.

ROOT CAUSE FOUND: naming a XUID with no profile behind it opens a sign-in
dialog, xam_dialogs_shown_ goes to 1, and IsUIActive() then discards every
XamInputGetKeystrokeEx. Measured 8,388,601 swallowed calls in one boot with the
pad log showing presses arriving normally throughout. Only B13EBABEBABEBABE
exists on disc; the script asked for E0300000EFBEA3D4. Signing in the real
profile takes the swallow count 8,388,601 -> 0.

AND REFUTED: that is not why the title is stuck. With a valid profile and zero
swallowed keystrokes, a single (A) on the title that ends the boot still does
nothing. The dialog bug was real and worth fixing; it is not the explanation.

Next probe is specific: --log_mask=13 records no kernel calls, so it is not even
known whether the game polls at the title. A log on the SUCCESSFUL
XamInputGetKeystrokeEx return, mirroring the one already on the swallow path,
answers it in one boot.
2026-08-19 08:21:08 +00:00
Sylpheed RE agent
99081a6198 docs: the boot log confirms the vgamepad failure verbatim
'skip_intro.sh: line 44: vgamepad: command not found' right after 'movie -> skip
A'. Every '-> skip A' and 'TITLE -> A' this harness has printed pressed nothing,
which is also why the intro movie ran to 434s: it was never skipped.
2026-08-19 07:49:37 +00:00
Sylpheed RE agent
efee309d8f docs+tools: screen_children.py, validated; the third paint order is blocked on input
screen_children.py walks every resident screen object (vtable 0x820b30b4), reads
the element array at +0x08 and the child array at +0x30, and prints the paint
permutation with each pivot. Validated in one run off the live title: it
reproduces BOTH previously measured orders character-for-character - the splash
[0,2,4,6,1,3,5] and the 24-element title permutation - so the next screen it is
pointed at can be trusted.

The screen that matters was not reached. GP_DIALOG DIFFICULTY carries exactly
one primitive and would say whether the game paints it first or last, which is
the bit that decides where primitives belong. But (A) does not advance the title
screen at holds of 0.10, 0.25 or 0.40s, and neither does START, with the pad
verified end to end: pad.py writes /tmp/xenia_pad.txt, the emulator runs with
--hid=file --pad_file pointing at it, and the mtime updates on every press. The
title is unambiguously the interactive one - PRESS (A) BUTTON is rendered.

Recorded as a blocker rather than worked around. Next step is specific: boot with
the emulator stdout kept and read the [RE-INPUT] IsUIActive log already present
on the canary branch, which would say whether the keystroke is being swallowed
emulator-side.
2026-08-19 07:48:58 +00:00
Sylpheed RE agent
01580d294e tools: two dead flags that made every emulator boot script fail silently
Trying to read a third paint order off the running game turned up two bugs in
the capture harness, both of which fail in ways that look like the game
misbehaving rather than the script being wrong.

1. `--audio` is not a cvar in this tree, and eight boot scripts passed it.
   Xenia calls ShowSimpleMessageBox from ParseLaunchArguments, BEFORE logging is
   initialised, so the symptom is a 10x10 window, no log, no guest memory and a
   dialog that blocks on XIfEvent forever - i.e. a hang deep in the emulator.
   run-canary`s own header documents this exact trap; the scripts predate it.
   Removed from all eight.

2. `vgamepad` no longer exists - the uinput pad was replaced by the --hid=file
   driver and pad.py - but skip_intro.sh still called it. The script runs
   without `set -e`, so the call failed silently and the title branch pressed
   nothing while still exiting 0. A caller was told "TITLE -> A" with the game
   sitting on the title screen. It now presses through pad.py and exits 6 if
   that fails.

The first bug is fixed and verified: the boot now reaches the title screen with
PRESS (A) BUTTON. The second is fixed but does NOT unblock the title - see the
next commit.
2026-08-19 07:48:16 +00:00
Sylpheed RE agent
71d9b73afe docs: backlog — primitives drawn opt-in; their paint order needs a runtime read 2026-08-19 07:21:48 +00:00
Sylpheed RE agent
eb86142e4f formats/cli: draw the .prm primitives, opt-in, because their paint order is unsolved
fill_quad composites an untextured primitive as a solid rectangle of the
keyframes fade colour, pivot x 2 in size, placed and scaled exactly as a sprite
is. Behind ComposeOptions::include_primitives and screen render --primitives.

On the title screen, whose paint order is ground truth, it is measurably right:
mean luminance 76.30 -> 63.72 against the captures 64.58, i.e. from +18% to
-1.3%, and mean absolute difference 16.07 -> 13.08. The background was ~40% too
bright; pteff02.prm, a 25% black dim, was what was missing. The wordmark is not
dimmed by it because the measured order paints that quad at slot 4, beneath the
logo. Edge correlation moves 0.9538 -> 0.9480, which is not informative here: a
uniform dim scales gradients uniformly so a normalised edge score barely sees it.

OFF BY DEFAULT, and that is the finding. A primitive has no T8aD header, so no
layer key, and derived_paint_order sorts the keyless to the end. GP_DIALOGs
pzeff00.prm is a single keyframe of opaque black at full screen; painted last it
wipes the build. Of the 125 builds that draw a visible primitive, 36 come out
>99% one colour with the derived order.

No constant default works, because the two screens read off the running game
disagree: the splash paints its primitive FIRST (the black backdrop) while the
title paints one at slot 4 and another LAST (the fade-out). Declaration order
fails the title too. A disc test measures the damage rather than asserting the
feature works, so the number moves when the ordering is solved.

Also records a false alarm worth keeping: a first pass reported 36 GP_DIALOG
builds at "100% black", which was a crude near-black pixel threshold and not a
black screen - those dialogs are dimmed 50% and perfectly legible. The genuinely
wiped builds are a different set.
2026-08-19 07:21:32 +00:00
Sylpheed RE agent
6b65f7ea91 docs: backlog — fade alpha applied; drawing the .prm quads is unblocked 2026-08-19 06:56:56 +00:00
Sylpheed RE agent
7fbdcc078c formats: apply the keyframe fade alpha, and fix the resting rule it exposed
blit modulated by tint only, which is 0xffffffff on essentially every keyframe,
so the fade word was decoded, stored and then discarded. Applying it as an ARGB
modulate takes the title composite's edge correlation against the framebuffer
capture of the running game from 0.4597 to 0.9538, both at zero shift. The white
wordmark with its blue outline, the trademark, the copyright and the orange
exploding planet all appear -- the planet because a full-screen blue effect that
rests at alpha 0 had been painting over it at full opacity.

ARGB is measured: across a fade-in the high byte walks 0x00 -> 0x80 -> 0xc0 ->
0xe0 -> 0xff while the low three stay ffffff, and the low 24 bits are 0xffffff on
5276 of the disc's 5453 resting keyframes.

A modulate can only remove pixels, so the risk is a blank screen. Measured: no-op
on 4060 of 5200 sprite elements, partial on 453, hides 687 -- transient HUD
indicators that should not be lit at rest -- and NO build is left with nothing
visible. A disc test asserts that, and that the no-op share stays high, which
also guards the resting rule against drifting onto ramp frames.

Both changes are in one commit because the second is invisible without the first.
Applying fade erased the word PAUSE, which the running-game capture plainly
shows: pgptitle.rat has three runs of two identical keyframes -- pre-roll, hold,
exit -- and the 'later run wins' tie-break grabbed the exit. A group carries the
screen's entry animation AND its exit, so a run ending on the last keyframe is
now excluded unless it is the only one. The title correlation is unchanged.
2026-08-19 06:56:38 +00:00
Sylpheed RE agent
2d2c281805 docs: backlog — resting pose settled; the unapplied fade alpha is next 2026-08-19 06:41:41 +00:00
Sylpheed RE agent
b5c44b7c2a formats: the resting pose is the hold, not the longest dwell
Element::rest() picked the keyframe with the largest gap to the next keyframe's
time. That reads a keyframe as a value held until the next one; it is the start
of a ramp toward it. A long gap after keyframe k means the screen spends that
time arriving at k+1, so the settled pose is at the far end of the gap.

The title wordmark zooms in over five frames and holds at (184,193) at 100% from
t=251 to t=264. The old rule picked the frame before the long gap: (179,186) at
101%, still mid-zoom.

Measured against the framebuffer capture of the running title screen, which is a
1:1 crop so frame coordinates map directly (confirmed: the copyright line lands
on row 669 in the capture and in both composites). Edge-correlated over the
wordmark box:

    plateau (landed)      best 0.4597 at shift (0,0)
    longest dwell (old)   best 0.1511 at shift (+3,+8), 0.1268 at (0,0)

The old composite scores 3x lower and only peaks after being moved, by about the
(-5,-7) that picking kf4 instead of kf5 predicts.

It also fixes six title elements the old rule rested at alpha 0x00 where the
capture plainly shows them, and pteff00.prm - the full-screen fade quad painted
last - which rested at opaque black. That was the blocker on .prm compositing.

Adds tools/re-capture/align_to_capture.py, which is how this was scored, and
turns the .prm test that deliberately asserted the old defect into a guard on
the fix.

Not settled and now the next item: compose ignores the keyframe fade alpha
entirely (blit modulates by tint only), which is why choosing the wrong keyframe
was invisible until now.
2026-08-19 06:41:24 +00:00
Sylpheed RE agent
eb61368d23 docs: backlog — .prm decoded; the resting rule is the new blocker 2026-08-19 06:30:23 +00:00
Sylpheed RE agent
393e549752 formats: settle what .prm elements are — untextured quads marked by kind bit 0x10
369 .prm elements exist in the disc's screen builds and every composite is
missing them. Swept statically:

  * none of the 369 has a RATC child of its own name — no payload, nothing to
    draw, so .prm is a primitive and not a sprite;
  * kind & 0x10 and a .prm name agree with ZERO exceptions in either direction
    over all 965 builds, so the format marks primitives as a decoded field and a
    port need not parse filenames;
  * 361 of 369 are exactly 1280x720 at scale 100% in the corner, and their
    keyframe 'fade' ARGB is overwhelmingly black at some alpha — these are the
    fade-to-black, dim-behind-menu and flash layers, i.e. the PRMD dim-quad the
    compositor's backdrop has been standing in for.

Refuted before believing: drawing them at Element::rest() is wrong. The title's
pteff00.prm is opaque -> transparent -> transparent -> opaque, a transition whose
resting pose is the transparent plateau; rest() picks by longest dwell and lands
on the opaque endpoint, which is painted LAST on that screen and would black out
the title. A test asserts that wrong answer deliberately so that fixing the
resting rule fails it and leads to the note.

No compositing change: the resting rule is not .prm-specific and has to be A/B'd
against the title framebuffer capture first.
2026-08-19 06:30:05 +00:00
Sylpheed RE agent
a87d5b5255 docs: backlog — two items solved, four opened by them 2026-08-19 06:04:10 +00:00
Sylpheed RE agent
a1c370e810 cli/formats: let the screen commands reach bundles with no .rat child
The developer-logo splash declares its sprites directly and has no .rat layout
child, so is_build rejected it and no screen command could render it — despite
it being one of only two screens whose paint order has been read off the running
game, and the one where the layer key explains the whole permutation.

Adds ui_layout::is_composable (a declaration table plus at least one element
resolving to a T8aD the bundle carries) and an opt-in --all on screen
list/info/render. Measured on the disc: 2859 RATC bundles, 965 pass is_build,
2751 pass is_composable, and 0 pass is_build without passing it — a strict
superset. It is opt-in because the 1786 extra bundles are mostly two-element
fragments (a button and its glow), and because widening the default would
renumber --build for every pak, invalidating the build indices the corpus's
notes cite by number.

The splash now renders 6/7 elements, painting its glows first in the order
measured off the game; a disc test pins that order.
2026-08-19 06:03:52 +00:00
Sylpheed RE agent
b28763db0b docs: record the _eff-vs-focused-state confusion and both refuted rules 2026-08-19 05:57:38 +00:00
Sylpheed RE agent
2708abb482 formats: a focused-state record needs the element it is the focused state of
`compose` skips focused-state records by default, so whatever that flag matches
vanishes from every composite. It matched a trailing `f` in the name and nothing
else, which is not the convention — it is a letter.

Measured across the disc: 2458 elements match the suffix and only 54 of them
have the base element they would be the focused version of; all 54 are
`pgmenu_btnNNf.t32`. The other 2404, spread over 864 bundles, are `_eff` glow
layers whose names end in the same letter — `pb_name_eff.t32` alone accounts for
1122 of them, and `palogo_gamearts_eff.t32` is one the draw capture shows the
game painting, before its logo, with its own layer key.

Requiring the pair recovers 587 glows in the composable builds. GP_OPTIONS is
the plainest case: without `pbmwindow_eff.t32` the panel had no window at all,
just two floating brackets (captures/ui-layout/options-with{out,}-its-glow-layer.png).

Two other candidate rules were tried and refuted first. The `opt` link is not a
focus link: 221 elements are opt-targets, only 2 of them suffix-match, and the
targets include `pjnet_bg.rat` and `pv_loading_loop1.rat`. And the suffix alone,
as shown above, is unrelated to anything.
2026-08-19 05:57:12 +00:00
Sylpheed RE agent
fb4f035f08 docs: record the paint-order key as landed, with what it left unsettled 2026-08-19 05:43:29 +00:00
Sylpheed RE agent
deff5a4d6d formats: check the derived paint order against the screens already verified
The layer-key order was adopted from two measured screens and then applied to
every build on the disc, so it owed a regression check against the screens the
corpus had already validated against the running game.

Rendered the tutorial PAUSE menu and the title main menu both ways and diffed:
3.8 % and 1.1 % of pixels differ, max delta 45/255 and 34/255, and the two
renders are indistinguishable in layout — the change is confined to blends where
translucent sprites overlap. No regression, but which order is more faithful on
those two screens is unsettled and recorded as such.

Adds a corpus-wide test asserting every composite's draw list is strictly
increasing in (layer key, declaration index), streaming one pak at a time so it
does not OOM alongside the other whole-disc tests. It reports the rule's reach:
341 of 965 builds are reordered, and it fails if that share collapses.
2026-08-19 05:43:08 +00:00
Sylpheed RE agent
d9ae42dd55 formats: derive the paint order from the sprite layer key
compose now sorts elements by the word at +0x08 of their sprite's T8aD header
instead of painting in declaration order, for every build except the two whose
measured order is hard-coded. That word is non-decreasing in the order the game
actually paints both measured screens, so every screen nobody has captured now
gets its layering from the file rather than from the declaration table, which is
provably not the paint order.

Verified with artifacts and both ways, not by a green build: the disc test
asserts the measured orders never invert the key and that the composite's key
sequence is sorted, and reading the word from +0x0c instead makes it fail; the
title composites identically; and GP_MISSION_SELECT — uncaptured — now composites
cleanly, committed as a capture.

Two things recorded rather than smoothed over: ties keep declaration order
because the game breaks them some other way that is not known, and the
developer-logo splash has no .rat child, so is_build rejects it and the
compositor never sees that bundle at all — its measured order is inert in
practice and screen render cannot draw it.
2026-08-19 05:28:05 +00:00
Sylpheed RE agent
aa1f49633e docs/re: point the paint-order item at the derivation
The entry said deriving the order from the bundle was open. A key has been found
— the T8aD header word at +0x08 — so the entry now says that, what it does not
cover (ties, bit meanings), and that it is not wired into the compositor yet.
2026-08-19 05:17:44 +00:00
Sylpheed RE agent
56ac88e128 docs/re: the paint order is sorted by a layer key in the T8aD sprite header
The word at +0x08 of a T8aD header — which this project's decoder never read,
taking width/height/tiles from +0x14 onward — is non-decreasing in the order the
game paints a screen, on BOTH screens whose order has been measured: 20 of the
title's 24 elements (the other four have no T8aD sprite) and 6 of the splash's 7.
No inversion anywhere.

On the splash it explains the whole permutation: the three _eff glows carry
0xa100 and their base logos 0xa110, so the glows paint first even though the
declaration table interleaves them.

This is the first FILE-DERIVABLE account of the paint order. Everything checked
before failed — declaration order and its reverse, the placement region, the RATC
child order, keyframe start and rest times, resting Y, the runtime record's
fields, and every other build's table.

Recorded as unsettled: the ties (two groups share a key and are painted in an
order that is not declaration order), what the bits actually mean (the values
look like flag words, and the two screens use different ranges), and the fact
that two screens is two screens — a third permutation either promotes this to a
rule or breaks it.
2026-08-19 05:17:28 +00:00
Sylpheed RE agent
a290791a30 docs/re: point the ship item at the end-to-end mission-path measurement
The entry said the crash blocks it; it now also says how far a run gets (past
SELECT DATA, into a cinematic) and that the crash's firing point moves, so nobody
goes looking for a menu route around it.
2026-08-19 05:08:59 +00:00
Sylpheed RE agent
fa48e39d90 docs/re: the mission path measured end to end — the cache crash is the blocker
Driving menu -> NEW GAME -> DIFFICULTY -> SELECT DATA -> slot 01 with plain flags
gets further than any run so far: SELECT DATA is reached with ZERO crashes (the
screen is alive — a log_ui_draws probe there records 140 draws over 8 frames),
and then choosing a slot lets the game proceed into a cinematic or load, where it
crashes at 0x82307128 — the same cache-flush std::map erase.

So the crash is intermittent in WHERE it fires, not whether: boot, SELECT DATA,
and now after slot selection. There is no safe path through the menus to be found
by choosing different options.

And it is the blocker for every mission-side experiment — the second capital-ship
capture included. Navigation is not the obstacle any more; it is scripted and
works.

Not settled and said so: how to get past it. --mem_watch=false does not (twice).
--eh_dispatch is still untested because no run with it on has reached a throw.
The black-screen hang is a separate intermittent failure with no diagnosis. A fix
is emulator guest-race work, not RE.
2026-08-19 05:08:46 +00:00
Sylpheed RE agent
d0e3dc6f33 docs/re: three more runs, and eh_dispatch is still untested — with the proof why
Drove NEW GAME deliberately (the menu's first item, no d-pad, which is what the
run that first hit the crash actually did). Three runs with --eh_dispatch on: no
crash, no throw, black screen before the save-slot screen. It is tempting to read
that as the flag working.

The run with --cache_throw_diag as well disproves it. That cvar logs the throw
BEFORE any dispatch is attempted, so a throw would appear whatever dispatch then
did — and there were zero. No guest exception happened, so neither flag ran any
code, so neither can explain the difference. What took those runs out is the
intermittent content-load hang, before the crash path was reached.

eh_dispatch therefore stays untested, and the entry says so with the reasoning
rather than banking a false pass.

Confirmed on the way: the screen after NEW GAME is DIFFICULTY — its whole-image
signature matches the earlier capture exactly — so the menu path is understood
even though the runs die after it.
2026-08-19 04:43:25 +00:00
Sylpheed RE agent
743ddda8b1 docs/re: eh_dispatch is untested, and the black screen follows a content load
The run with --eh_dispatch=true produced zero crashes and zero throws, which is
not the result it looks like: the crash path was never reached, so the flag is
UNTESTED. Recording that rather than banking a false pass.

What the run did establish, with screenshots: TUTORIAL leads to a lesson list
(a fifth screen nobody had captured, committed), and selecting BASIC CONTROLS
puts the guest on a permanent black screen — no crash, no GUEST-THROW, emulator
healthy. That gives the black screen a trigger it did not have before: it follows
a CONTENT LOAD, which also fits the earlier black screens.

So the two blockers on the way to a mission are distinct and both unbeaten: the
SELECT DATA crash when the path goes through save-slot enumeration, and the
black-screen hang on loading content. The eh_dispatch test needs a run that
actually reaches SELECT DATA.
2026-08-19 04:17:36 +00:00
Sylpheed RE agent
10b89c77a3 docs/re: restate the ship item's blocker as the crash, not navigation
Navigation is solved and scripted as far as the save-slot screen; what stops the
second capital-ship capture is the cache-flush crash firing at SELECT DATA. The
backlog entry now says that, so the next person does not re-solve navigation.
2026-08-19 04:01:09 +00:00
Sylpheed RE agent
72b0e23e3b docs/re: the cache-flush crash also fires at SELECT DATA, blocking every mission
Driving toward a mission for the second capital-ship capture works as far as two
screens nobody had captured — DIFFICULTY, then SELECT DATA — and then the guest
dies at PC 0x82307128, the SAME std::map/set erase as the boot-time throw, with
537 stacked dumps. Both screenshots are committed.

So the ship item's blocker is the crash, not navigation: the game reaches the
save-slot screen and dies enumerating save data. mem_watch stays eliminated —
this run had it off. And the save/cache path is the common factor across both
firings.

Stated as unsettled: whether a warm cache prevents this firing the way it
prevents the boot-time one. The cache WAS warm here, so it looks like no, but
that is a single run and the cold/warm A/B was only ever run against the
boot-time throw.
2026-08-19 04:00:53 +00:00
Sylpheed RE agent
24b68ed063 docs/re: flag the load confound behind the input and boot-time numbers
The container runs the emulator, the Canary build and the disc test suite on the
same 12 cores, and Xenia alone takes about 5.5 of them. Several (A) experiments
and most boot-duration numbers in this entry were measured while a cargo test or
a build was running; one boot under that load never reached the title in 400 s,
against 130-290 s unloaded.

It does not explain the (A) result by itself — the interleaved A/B alternated
arms under whatever load was present, and the latest title recorded accepted
while an earlier one refused — but "a starved guest misses the input window" is a
hypothesis the existing data cannot exclude, and it should have been flagged when
the numbers were taken rather than after. The same navigation is now being run on
an idle box.
2026-08-19 03:43:32 +00:00
Sylpheed RE agent
0b4dcc6e8f tools/docs: the second-capital-ship blocker is stale; retry boots, not presses
BACKLOG said the per-class ship generalisation needs "the capture-ship-placement
build of Canary". Checked: the ship capture is in the CURRENT build —
RequestShipCaptureFrame and CaptureShipDrawForRE are in command_processor.cc on
auto/re-ui-draw-order, and F10 wrote a 2.9 MB ship capture from this session's
binary. No separate build is needed.

What actually blocks it is navigation: a mission is behind the main menu, and (A)
at the title is accepted on about half of boots with nothing observable
predicting which. tutorial_launch.sh drives boot -> title -> menu -> TUTORIAL and
retries the WHOLE BOOT on refusal, because re-pressing the same title never
works — measured, not assumed.

First run of it: (A) refused, so it rebooted. That is the expected shape rather
than a failure of the harness.
2026-08-19 03:24:40 +00:00
Sylpheed RE agent
343864cc2f docs/re: note that the viewer shares the compositor path
Checked rather than assumed: the Bevy viewer's UI Screens browser calls the same
ui_layout::compose, so the measured paint order and the ghost skip reach the
window a person looks at, not just the CLI's screen render. Its element table
stays in declaration order, so the per-element visibility toggles still index
correctly.
2026-08-19 03:13:37 +00:00
Sylpheed RE agent
8c08c211a3 formats: narrow the ghost skip, and withdraw the claim that it fixed a bug
Checking the blast radius of the previous commit's `kind = 0x4` skip found 174
elements on the disc that are 0x4 with no non-0x4 element of the same sprite —
GP_READY_ROOM pak entry 75 is 56 elements and every one is 0x4, a list of real
icons a blanket skip would erase. So the rule is narrowed: skip an instance only
when its template is present, which is the case the title capture covers. The
title render is byte-identical before and after, so the narrowing changes nothing
the evidence covers.

And the claim that this "caught a regression" is WITHDRAWN, because measuring it
refuted it: none of those 174 elements is in a bundle `is_build` accepts, so none
of them ever reaches the compositor. The blanket skip would have been harmless in
practice. The narrow rule is a precaution, not a fix, and the comment says so.

The test that was going to assert the regression could not find such a build —
correctly, since none is composable. It is replaced by one that pins the fact
which makes the narrow rule safe: no composable build on the disc has a 0x4
element without its template, checked across 500+ builds. If that stops being
true it fails, instead of a screen quietly going empty.
2026-08-19 02:45:48 +00:00
Sylpheed RE agent
f66e9de430 docs/re: the paint-order item now has a landed fix, not just a finding
BACKLOG said the viewer paints in declaration order and the title composites
wrongly. That is no longer true for the two builds with measured orders, so the
entry says what landed, what it exposed (the kind=0x4 ghosts), and what is still
open: deriving the order for screens nobody has captured. The order is
deterministic, so a rule exists; it is not in any decoded structure, and the
loader is what is left to read.
2026-08-19 02:32:31 +00:00
Sylpheed RE agent
c3c79ad028 formats: composite in the MEASURED paint order, and drop the ghost instances
The compositor painted in declaration order, which the draw capture proved wrong
— the title's background is declared ninth and paints first. The order the game
uses is its runtime child list, and no decoded field reproduces it, so rather
than leave the screen composited wrongly this lands the orders that HAVE been
read off the running game and falls back to declaration order everywhere else.
Two builds are covered: GP_TITLE's title build and the GAME ARTS / SETA / studio
anima splash. Keyed by element names, which identify a build across paks and
language variants.

Rendering it exposed a second defect, and the same capture settles it: the
kind = 0x4 elements are motion-trail ghosts, not resting content. The bundle
declares three instances of each wordmark; the capture shows exactly ONE quad at
each wordmark's position. Drawing them at their resting keyframe put three
oversized PROJECT SYLPHEED copies across the composite. They are now skipped.

Verified with an artifact, not a green build: the composite is committed
(captures/title-composited-measured-order.png) and now reads as the title screen
— background, planet, ship, wordmark, TM, copyright, correctly layered.

The test is disc-gated and was checked BOTH ways: it passes as landed, and
disabling the order table makes it fail. It reads one pak rather than every
build on the disc — the first version used the all-builds helper and got the
test process OOM-killed running alongside the other three.
2026-08-19 02:32:14 +00:00
Sylpheed RE agent
f35a871baa docs/re: no ordering field in the runtime element record either
The next suspect was a role or depth field in the 48-byte element record, so
every undecoded word was dumped for all 24 title elements against its paint slot.

Clean negative: +0x08, +0x0C, +0x18, +0x1C and +0x2C are zero on 21 of 24
elements, and the three exceptions hold what looks like live animation state.
Nothing there orders anything.

One confirmation on the way: +0x04 is the declaration entry's `kind`, verified
against the file for all 24 — 0x10 on the two .prm elements, 0x4 on the four
repeat instances, 0x3000 on the two ptlogoall_eff, zero elsewhere. The record
mirrors the file here as the pivot and keyframe count already did. And `kind`
does not explain the order either: the paint order interleaves kinds freely.

So the ordering is in none of the decoded data — not the declaration entry, not
the placement region, not the runtime record. What is left is the loader that
appends to +0x30, which is worth reading precisely because the order is
deterministic. Stated without promising a static rule exists merely because one
could.
2026-08-19 02:20:01 +00:00
Sylpheed RE agent
72afde7115 docs/re: the paint order is deterministic, and the 7-element screen is the logo splash
Two things this iteration, both of which change what is worth doing next.

The order is DETERMINISTIC: two independent boots give byte-for-byte identical
permutations for both live screens. That kills the hypothesis that the child list
is built in I/O-completion order — which mattered, because a run-dependent list
would have made deriving a rule pointless. It is a pure function of the bundle.

And the 7-element bundle is identified by matching its pivots against every
7-element build on the disc: it is the developer-logo splash (GAME ARTS / SETA /
studio anima, GP_TITLE entries 11/14). With names attached, its paint order
0 2 4 6 1 3 5 reads as the full-screen .prm, then all three _eff glows, then all
three base logos — glow behind, logo on top.

That refines the earlier "grouped by sprite" reading and partly withdraws it:
here every element has its own sprite, so the grouping is by ROLE, not sprite
identity. The title build could not tell the two apart because its repeated logo
instances share both.

Still not derived: where the role comes from. No decoded field carries it, and
sorting on a "_eff" name suffix would be an odd thing for a loader to do.
2026-08-19 02:08:50 +00:00
Sylpheed RE agent
875ac2632d docs/re: two ground-truth paint permutations, and four more refutations
Deriving the child order from the bundle is what the port needs, so this collects
data rather than guessing. Both multi-element screen objects live on the title:

* a 7-element bundle paints 0 2 4 6 1 3 5 — evens then odds, a stable partition;
* the 24-element title build paints 9 11 12 10 13 6 20 19 14 15 18 16 17 0 2 4 7
  1 3 5 22 23 21 8.

Two things the pair establishes. The list is GROUPED, not shuffled: elements
sharing a sprite are contiguous (ptlogo1's three instances at slots 13-15,
ptlogo2's at 17-19, the back2eff family at 8-12). And the five single-keyframe
elements lead — exactly elements 9,10,11,12,13 have one keyframe, and the list
opens with all five, which is a file-visible property.

Refuted on this data: first-keyframe time and resting time (back2eff1 starts at
52 and rests at 56, yet paints after back2 at 66/80), declaration order and its
reverse, and the RATC child order (ptbase2 is child 4 and paints first while
ptlogo1 is child 0 and paints fourteenth).

Not settled, and said so: the rule. The group CONTENTS are explained; the group
ORDER is not, and two permutations are too few to build it.
2026-08-19 01:56:04 +00:00
Sylpheed RE agent
069165c03d docs/re: mark the paint-order item solved, and say what is left
The item that has been open through this whole run is answered: the paint order
is the screen object's reordered child array at +0x30, not any table in the file.
BACKLOG and INDEX now lead with that, and the investigation behind it is kept in
full underneath — most of it refutations, which is what made the answer findable.

What is left is stated in the same breath, because it is what the port needs:
deriving that order from the bundle without running the game. Until then the
viewer paints in declaration order and the title screen composites wrongly, which
is now a documented defect rather than a mystery.
2026-08-19 01:46:20 +00:00
Sylpheed RE agent
688851122d docs/re: SOLVED — the paint order is the screen object's child array at +0x30
The screen object holds a SECOND list of its elements, a reordering built at load
time, and that list is the paint order. It is 24 pointers at +0x30, each the
+0x00 field of one of the 48-byte element records, so both arrays hold the same
objects in different orders.

Checked against the draw capture rather than asserted: the seven elements the
capture can name sit at child slots 0, 6, 7, 13, 16, 17, 22 — strictly ascending,
in exactly the captured submission order. It also resolves the one sub-order no
static field could explain, the pair that decodes to the same 1133x280: slot 6 is
element 20 and slot 7 is element 19, so they paint 20-then-19, DESCENDING in
declaration terms. And the kind=0x4 repeat instances sit immediately after their
template, where the declaration table interleaves them.

Stated as unsolved, because the port cannot read a runtime array: deriving this
order from the bundle. The order is clearly structured rather than arbitrary —
elements sharing a sprite are adjacent and the full-screen effects lead — so it
is worth attacking, but it is not attacked here.
2026-08-19 01:46:00 +00:00
Sylpheed RE agent
4134c21766 docs/re: the UI screen object, found and verified in live guest memory
The item class's vtable (0x820b30b4) is a fixed 4-byte value at offset 0 of every
instance, so gmem.py finds the objects in the running game with no debugger and
no emulator change. Seven are live on the title screen.

Their first {ptr,count,capacity} triplet identifies them outright: counts of 24,
7 and 1 — exactly GP_TITLE's title build, loading overlay and PRESS (A) BUTTON
bundle. The two-bundle composition of the title screen, which the draw capture
had inferred, is visible directly in memory.

The element array at +0x08 is 48-byte entries in declaration order, and it was
checked against the file at five positions with no misses: elements 0, 1, 6, 9
and 21 carry pivots (451,50), (449,46), (320,160), (320,180) and (309,10) as
floats, matching build 4 exactly, and elements 0/1 carry keyframe count 8, which
is their count in the file.

Stated as the reason this was done and NOT achieved: the array is in declaration
order, which the capture already proves is not the paint order, so the renderer
walks something else or sorts. Three further triplets in the object are
unidentified; the 24-entry one at +0x14 is already ruled out as an index list
(its entries are pointers, not indices). The gain is that the question is now a
data question on a structure that dumps in seconds.
2026-08-19 01:35:13 +00:00
Sylpheed RE agent
82b4a9e523 docs/re: more UI map, no walk — and say that this is becoming a rabbit hole
Three facts, none of them the thing being looked for:

* the drawing API is enumerable — the renderer accessor the quad emitter uses has
  exactly 6 callers, four of them sibling quad emitters;
* sub_823C2990 is a FACTORY, not the singleton accessor its use in the top-level
  render function suggested: it allocates 4 bytes plus a 244-byte object from the
  heap at [0x828E2B14] and runs the chain that ends at the constructor installing
  vtable 0x820b30b4. So a UI item is 244 bytes;
* the 0x823C region holds both the item class and four of the RATC-fourcc
  loaders, so bundle parsing and item construction live together.

And the part worth writing down more than any of them: three iterations have
added map without answering the question, and each step has been a plausible next
query rather than a decisive test — the shape of a search that can run forever.
The decisive alternative is costed instead of started: a Canary memory watch on
the UI vertex buffers would report the guest PC that writes them, naming the
emitter and its caller outright. That is real emulator work, and it is now a
choice to weigh against leaving the paint order unresolved, which costs the port
one screen's fidelity and nothing else.
2026-08-19 01:23:12 +00:00
Sylpheed RE agent
4808751113 docs/re: find the guest's UI quad class from the layout the capture measured
The capture pinned the UI vertex exactly — prim 13, stride 24, float3 position at
+0, k_8_8_8_8 colour at +12, float2 UV at +16 — so the binary was searched for
code that writes that shape. Fifteen candidates; the tightest, sub_82250138, is
unambiguous: it asks a renderer for 4 vertices of primitive type 13, then writes
x/y/z, colour 0xFFFFFFFF at +12, u/v at +16/+20, stepping by 24. Every constant
matches something measured rather than assumed.

Its caller is a constructor that installs vtable 0x820A7264
(ANON_Class_AAFDBF89, 2 slots) — the UI quad/sprite class.

Recorded with equal weight: this is NOT what the search was for. The chain up
from it ends at sub_821A8578, which turns out to be an unrolled run of ~30
identical call triplets — a fixed subsystem sequence, not an element walk. No
function that iterates screen elements has been found, and nothing here bears on
the paint order yet.
2026-08-19 01:16:14 +00:00
Sylpheed RE agent
38df977b30 docs/re: alpha does not break the tie — but it validates the fade decode
The plan was to separate ptlogo_back2eff from ptlogo_back2eff5 (same 1133x280,
declaration 20 vs 18, either side of ptlogo_back2 at 19) by their resting fade
alphas. Carried out, and refuted: both quads come back at alpha FF, and reading
the bundle again, both elements REST at 255 under this project's own max-dwell
rule — back2eff5's longest hold is t=74 at 255, and the 192 I had quoted is a
later, shorter keyframe, not its resting value. The ambiguity stands.

What the colour did buy is worth more than the tie-break: the first check of the
fade/keyframe decode against the RUNNING GAME rather than against another parse.
Every static element draws at exactly the resting alpha the bundle predicts, and
the only two quads whose alpha moves between consecutive frames (88->88/86->87
and 3C->38) are the rotating effect pair and the PRESS (A) glow — the two things
visibly animating on screen. Nothing had confirmed before that the alpha channel
of a keyframe is what drives that throb.
2026-08-19 01:09:25 +00:00
Sylpheed RE agent
3dc6d9d32c docs/re: the loading screen never boots, and no other build explains the order
Two routes closed, both cheaply.

The loading screen was the best discriminator reachable WITHOUT navigation — its
full-screen element is declared 7th of 10, so the two candidate orders would look
completely different. It does not appear during the boot at all: 23 captures
armed across an entire boot are all intro-movie frames, two full-screen quads
apiece. It belongs to a content load, which is behind the main menu, which is
behind the (A) that works half the time.

And a loophole this document had left open is shut: the title's sprites were
matched to build 4 BY SIZE, so the game might have been running some other build
whose table lists them in the captured order. Only two bundles in GP_TITLE.pak
contain all seven sprites, and neither is ascending in the captured order.

Recorded rather than glossed: one sub-order is genuinely ambiguous, because
ptlogo_back2eff and ptlogo_back2eff5 decode to the same 1133x280 and sit at
declaration 20 and 18, either side of ptlogo_back2 at 19. The UI quad carries a
k_8_8_8_8 colour attribute the hook does not log yet, and the two rest at
different fade alphas — logging it would name them apart.
2026-08-19 00:58:46 +00:00