Commit Graph

21 Commits

Author SHA1 Message Date
Sylpheed RE agent
83293c3f81 port: land the loop decode on the handoff, and note the edit trap
Second iteration running where a scripted HANDOFF edit failed its
assertion on rewrapped text, leaving the port's page stale while docs/re
had the finding. Patched by reading the file first, and METHOD now says
to check the commit's file count for a silently dropped edit.
2026-08-28 20:53:33 +00:00
Sylpheed RE agent
3717293fa9 re: the loop records decode -- and they are not what I measured
Two of my own claims from last iteration die here, and the decode is the
thing that kills them.

Searching for the SHAPE instead of an assumed count found it immediately:
fade words matching 0x??ffffff at alternating strides of 24 and 16, which
is a 40-byte block carrying fade at +0 and tint at +24 -- exactly the
build layout, starting at +0x68. So "a leaf record's keyframes are not in
the build's 40-byte layout" is withdrawn. They are. My scan failed
because I demanded 29 strictly-increasing times, having read the word at
+0x004 as a keyframe count; the records hold THREE.

Decoded, both are horizontal light sweeps. ptloop01 runs pteff03.t32
from x=-639 to x=1521 over t=150..600, which is 450 units or 7.5 s;
ptloop02 runs pteff03a.t32 the other way over 570 units or 9.5 s.

Which refutes the second thing. Last iteration I attributed the measured
2.2 s oscillation to these elements because build 4 declares them --
association, not evidence. A 7.5 s cycle would give about three peaks in
a 22 s capture and I counted eight. So the loops are not what I measured,
the 2.2 s stands as a measurement, and its source is now explicitly
unidentified rather than wrongly assigned.

METHOD gets the general form, because it is what cost the previous
iteration: do not hard-code an expected element count into a structure
scan. Scan for the shape and let the count fall out.
2026-08-28 20:53:08 +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
cc72f8b5a8 re: the phase transitions -- and withdrawing yesterday's refutation
because the test probed the wrong state

Nine stores to the phase field this+132, control-gated on the 136 query
returning at least the 18 known ones. Attributed to the handler each
lives in, they give the phase graph: entry -> 2, then 2 -> 0 (splash),
2 -> 3, 3 -> 4, and 4 -> 2 on event 0.

That last edge is the problem with what I did last iteration. The event-0
block sets TWO fields one instruction apart -- stw r28,136(r30) for state
0 and stw r11,132(r30) with r11 = 2 for phase 2. So B from the menu lands
in PHASE 2, the same phase as the boot title, and the test I ran
believing it probed phase 4 state 0 probed nothing of the kind.

So the refutation is withdrawn. Worse for me and better for the idea: A
working on the B-returned title is exactly what the hypothesis predicts,
since phase 2 is the phase that references BUTTON. The hypothesis is back
to untested, now consistent with two observations rather than one, and
what it still needs is the attract-returned title's phase -- which no
test so far has read.

The measurement from that iteration stands, because it does not depend on
the phase: the B-returned title accepts A, so only the ATTRACT-returned
title is inert. I have said so explicitly in the page rather than letting
the withdrawal take the good half down with it.

METHOD gets the lesson: before testing "state X behaves like this", check
your route actually reaches state X. When a transition writes more than
one field, read the whole block and not just the store you were looking
for.
2026-08-28 20:25:06 +00:00
Sylpheed RE agent
4380305921 re: phase 0 is the splash, phase 2 is the title -- and a candidate answer
to this corpus's oldest input puzzle

Continuing Q6 on the phases left unread. Strings each phase handler
references, plus whether it carries its own jump table:

  phase 0  sub_821C5690   LOGO                                    no switch
  phase 2  sub_821C5818   BASE_INFO, BUTTON, TITLE_SCREEN         no switch
  phase 3  sub_821C5EC0   (none)                                  one switch
  phase 4  sub_821C6458   BASE_INFO, LOADING, TITLE_MENU, TITLE_SCREEN

Phase 0 referencing LOGO is a second independent confirmation that it is
the developer splash -- the iterate3E notes reached the same function
from the guest side and named the splash's LOGO items. Phase 2 draws the
title WITH the PRESS A plate, which the archive side had already
established is a build of its own.

Which produces something worth more than either: the title is installed
from TWO places, phase 2 and phase 4 state 0. Same screen, different
code. canary-scripted-input-traps.md has recorded for months, and never
explained, that the boot title accepts A while the attract-returned title
accepts nothing, with the giveaway that a draw capture in each is
identical. Two code paths installing one screen is exactly that shape. I
have written it into that page as a candidate with the cheap test named
-- read this+132 on each title -- and marked it untested, because it is.

One query in this iteration failed its own control and I threw its half
away: counting stw rX,136(r30) per phase returned 0 for phase 4, which
has 18, because the operand text has a space the pattern did not allow.
The bctr half passes its control and is reported. METHOD gets the
underlying trap: instructions.function is unpopulated for most rows, so a
query scoped on it silently returns nothing.
2026-08-28 20:15:02 +00:00
Sylpheed RE agent
86e52b01d0 re: the menu's music is BGM_103 -- found by accident while chasing Q6,
confirmed three ways

Chasing where the event code comes from, sub_821C5580 turned out to do
two things worth having.

For Q6 it arms the outer gate: li r11,3 ; stw r11,16(r28) is exactly the
value sub_821C7850 tests before dispatching, which answers the "what does
this+16 == 3 gate on" question I left open last iteration. It then passes
r5 = [r27+4], an event read out of a structure, so the vocabulary is
still not enumerable. Of the four callers of sub_821C7850, three pass
constants -- 0, 0 and 5 -- and this one does not.

The accident is a few instructions earlier: li r5, 1103 into a sound-play
call. 1103 is a BGM cue id, BGM_103. That closes a residual I had written
off as undecodable: which bank the menu plays. The cue table genuinely
cannot say, since its BGM entries are numeric -- but the code can, and it
checks out three independent ways. The census says BGM_103.slb is two
waves of 3876864 and 3930112 bytes; the XMA probe at the main menu saw
two stereo streams of 3876864 and 3930112 bytes. Byte for byte on both.

That also corrects me. bgm-two-stems.md said those observed sizes matched
no bank's declared waves and inferred the decoder gets a window rather
than a whole wave. Wrong on both counts -- I had checked only the BGM_0xx
rows of the census because that was the block on screen. METHOD gets it:
check a measurement against the whole reference set, not the part you
happened to be reading.
2026-08-28 20:10:41 +00:00
Sylpheed RE agent
c8c7677f6d re: the lookup argument is decoded at 46 of 48 sites -- and it refutes my
own corroboration from last iteration

Last iteration I listed a candidate name vocabulary from "upper-case
strings the callers reference", marked it amber because those are not
proven arguments, and named the check: confirm per call site which string
actually lands in r5. Ran the check. It fired.

The first extractor FAILED ITS OWN CONTROL -- a backward scan for
addi r5 recovered 7 of 48 sites and missed both title sites I had already
read by eye, because there the name is built in r27 and reaches r5 via a
later mr. Rewritten as a forward register simulation it reproduces all
three known sites and recovers 46 of 48, giving 28 distinct names.

Two of my own claims die. DIFFICULTY and EXTRA_MENU are NOT arguments at
any site -- they are strings that merely live in the same functions, so
the "independently corroborated by measurement" line I wrote was an
artifact of the loose filter. Only TUTORIAL_MENU survives. And
sub_821CC860 is not a screen factory at all: its real arguments include
BG, BLACK, FADE, FILE, KEY, PAD, SOUND and GAMMA_RGB. It is a generic
name-keyed lookup, mostly config, which the title part happens to call
with its three state names.

The title finding itself is untouched -- sub_821C6458 really does
transition by calling this with TITLE_SCREEN, TITLE_MENU and LOADING.
What is gone is the inference that the 28 names are a screen vocabulary.

Two METHOD lines, both earned here: strings a function references are not
arguments it passes, and a backward scan for a destination register
misses arguments staged through a scratch register.
2026-08-28 19:52:02 +00:00
Sylpheed RE agent
0d7199bc22 re: the UI sound effects ARE extractable -- retracting "cannot be
extracted", and the tool was in the build all along

Two iterations ago I closed Q8 by declaring the SE audio undecodable:
Static.slb has no wave boundaries, there is no XACT container anywhere,
and I said the index "exists only at runtime" as though that put it out
of reach. The disc half of that stands. The conclusion did not.

This build of Canary carries a cvar called xma_param_probe, added by this
project, whose own comment says it logs each XMA stream's parameters and
head bytes so raw sound.pak entries can be matched to real decode params.
It has been sitting in the startup CONFIG DUMP of every log I have read
this session.

Run with it, driving the main menu: a d-pad move spawns a new mono 48 kHz
stream of 4 packets / 8192 bytes, and B spawns a different one of 2
packets / 4096 bytes. Searching their logged head bytes in Static.slb
finds each at exactly one offset -- 0x1ec0 and 0x0ec0 -- and the two are
contiguous, 0x0ec0 + 4096 = 0x1ec0. So the bank is a packed run of whole
2048-byte packets with no delimiters, which is precisely why the seek
scan found nothing: there is nothing to find. A wave is (offset, packet
count) and nothing else.

That splits Q8's binding cleanly. Event -> WAVE is now measured: the port
can have the audio. Event -> cue NAME is still a name match on the
authors' identifiers, and the page says so.

The same run settled something for Q10 too. Sitting on the main menu, TWO
stereo 48 kHz streams were decoding simultaneously. bgm-two-stems.md said
that observation was what it needed and that this container could not
make it; it can, and a music bank's two waves are now measured as
simultaneous rather than only inferred.

METHOD gets the general lesson, because it cost two iterations: check
what instrumentation the local build already has before declaring a
question blocked on tooling.
2026-08-28 19:30:03 +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
dfcaac221e re: the new-game path completes, the intro is only weakly identified, and
this container kills long runs

Three results, and the middle one is deliberately not upgraded.

First: NEW GAME -> DIFFICULTY -> NORMAL -> SELECT DATA -> pick a slot ->
a movie plays, with no crash. The previous run's throw at 0x82307128 is
therefore not inherent to the menu path -- the same six presses got
through it -- which fits the incomplete-cache trigger the corpus already
names. n=1 either way, and the note says so. Worth recording because one
observation had already hardened once into "A on NEW GAME hangs", and it
would have hardened again into "the new-game path crashes".

Second: with the path open, the manifest's MS00A -> S00A.wmv finally got
a runtime test, and it did NOT reproduce the quality of the attract-movie
identification. The first four samples give a clean monotonic S00A
playhead of 2, 6, 9, 13 s and the strongest frame matches at 0.916, but
the run as a whole is not one advancing timeline, most frames sit at
contrast 23-37 where this page's own rule says discrimination is poor,
and one high-contrast frame prefers ADV. So it stays amber: consistent
with S00A, corroborating the manifest decode, not confirming it. A clean
answer needs the 30fps x11grab route rather than 5-second screenshots.

Third, and it bounds the rest: the emulator vanished at ~145 s with no
crash line in its own log, last entries routine MEM-WATCH at 1.15 GB RSS.
Second time this session -- the other was ~50 s into a boot. That is an
external kill on a box with ~1 GB free and swap exhausted, not a guest
fault. METHOD and the handoff's residue table both now say dynamic
experiments here have to fit in about two minutes of guest time, which is
why the DELTASABER plates went unseen again: they need a mission load and
the run never got there.
2026-08-28 19:19:14 +00:00
Sylpheed RE agent
bc6354dce0 re: the title presents at 28.5 fps idle -- the keyframe unit is settled at
1/60 s, and the 60 Hz reading is excluded

ui-keyframe-time-unit.md named its own re-test and nobody had run it: time
300 submitted frames on the IDLE title, where nothing is streaming from
the ISO. Reaching the title is cheap now that one A skips the intro, so
this cost one boot.

Two clean trials: 300 frames in 10.40 s and 10.60 s, i.e. 28.8 and 28.3
fps. That is the same rate as the 27.6 fps measured during the loading
splash, which is exactly the page's own criterion for "the game is 30 Hz
and the unit is 1/60 s". The 60 Hz alternative is now excluded rather
than disfavoured: it needs the emulator at 47% of real time while sitting
idle on a screen the capture says costs 1526 draws over 300 frames, about
five per frame. Nothing there halves an emulator.

So the durations stand as written -- title 4.2 s, main menu 1.1 s, EXTRAS
0.87 s -- and Q1 leaves the handoff's open-residue table. It stays
MEASURED, not decoded: no field on the disc says sixtieths of a second.

One trap, and it nearly cost the conclusion. My first attempt reported
39.5 fps, which sits between the two hypotheses and would have left the
question open. It was a harness bug: I polled the log for a "done" line
rather than for the COUNT of them increasing, so it matched a capture
that had already completed and timed 0.1 s of nothing. METHOD gets the
general form -- a log-polling probe must compare a count, not test for
presence.
2026-08-28 19:11:11 +00:00
Sylpheed RE agent
f5e7426cfa re: there is no XACT container on this disc -- retracting my own lead and
closing Q8

Last iteration I named Pj_Silph.xgs as the route to the SE cue index, on
the reasoning that XACT keeps cue->wave there. That route is dead, and
the negative is properly bounded: its 533-byte entry is high entropy with
no XGSF magic, +/-8 KB around it has none either, all 1.08 GB of
sound.pak contains zero XGSF, SDBK and WBND, and the executable has no
XACT or .xgs string anywhere. Control run first -- the same scan finds
RIFF in BGM_001 exactly where the bank structure says it should be, so
the scan works and the magic genuinely is not there.

So the .xgs and .slb extensions came from the authoring tool and survived
into shipped data that is not XACT at all. That goes in METHOD as a
general trap: check for the magic before committing to a parser for the
format an extension advertises. Writing an XACT parser would have been
wasted work.

What that leaves for the SE audio is undecodable-with-reach rather than
pending: Static.slb has no wave boundaries, there is no project file, and
none of the sound table's five records carries an offset. The bank is
certainly the right one -- 8 353 472 bytes over 322 cues is 0.84 to 1.01
seconds each at the bitrates the disc uses elsewhere, exactly the shape
of a bank of short effects. Only the index into it is missing, and it
exists only at runtime.

Q8 closes: vocabulary and bank decoded, the event binding a name match on
the authors' own event names, and the audio explicitly something the port
must source or author rather than extract. With this every MISSION row is
answered.
2026-08-28 18:58:06 +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
e81dcad632 re: the boot intro and the attract loop are the same video, and the
new-game intro has a name

Q9, answered twice over -- once from the disc and once from frames I had
already captured and not used.

The movie manifest's first eight slots are the whole boot-side flow, and
the slot key is the role: LOGO1-4 -> logo1-4.wmv (not on the disc, which
is why the splash is a screen), ADVERTISE_MOVIE -> ADV.wmv, STAFF_ROLL ->
the credits reel, MS00A -> S00A.wmv, MS01A -> S01A.wmv. So the new-game
intro is S00A.wmv, decoded -- which is the half I could not test at
runtime, because A on NEW GAME hangs the emulator.

And there is no separate boot-intro slot: ADV.wmv IS the advertise movie,
the boot just plays it first. Confirmed independently by matching 19
attract frames against five candidates -- 15 hit ADV with a playhead that
advances monotonically at the sampling rate and ends at 137 s, its full
length, with the title back on the next sample. So the attract movie
plays to its end; nothing cuts it short.

That corrects me. Two iterations ago I recorded the attract movie as
"~85 s, so probably not ADV.wmv" -- arithmetic on a start I never
observed, since sampling began 39 s in. REFUTED and METHOD both take it,
along with the matcher's real failure mode: a near-black frame has no
signature, and its runner-up is not evidence.

Skippability I did NOT settle, and the corpus contradicts itself: one
page says A skips a movie every time, while the boot harness deliberately
never taps during one because it breaks the title. Named the one-boot
test rather than picking a side.
2026-08-28 18:29:39 +00:00
Sylpheed RE agent
b3c8632006 re: a music bank is two stems that play together -- not intro+loop, and
not something to concatenate

Q10, and it starts by withdrawing the question's own premise. BGM_001 is
not three sub-waves of 10 KB / 4.47 MB / 4.67 MB: the 10 KB is the bank
header. A bank is exactly TWO waves, and across all 32 BGM banks on the
disc the two always have the SAME duration -- equal to 0.01 s over
lengths from 37 s to 277 s. That alone kills intro+loop and kills two
halves of one piece, both of which require unequal lengths.

Four banks appear to break the rule and do not: BGM_106-109 are the known
leading-region straddle, and the giveaway is that the entry named
BGM_107.slb contains BANK id=1108. The seek packet counts pin each join
exactly, so they realign to the same two-equal-waves shape rather than
being dropped as noise. That trap goes in METHOD.

Then the roles, by decoding both waves to PCM. They are
sample-synchronous: transient-envelope correlation searched over +/-5 s
peaks at lag +0.00 s, and both waves stop at the same millisecond,
167.663 s. Two stems of one performance, meant to sound at once. Wave 1
is quieter, has almost no bass and is far more L/R-decorrelated, which
reads as a surround-rear pair or a second intensity layer -- I cannot
separate those two from the file, and say so: ChannelMask is 0x0002 on
both, and this game's channel metadata is already documented as
meaningless.

Two things the port needs that are NOT on the disc, both marked as
authored: the track is not a seamless loop (BGM_001 fades out and is
followed by 6.15 s of silence, no loop-point field found), and nothing
names which bank the menu plays -- all 32 BGM cues are numeric.
2026-08-28 18:13:10 +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
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
e1dcc689bc agent: move the loop prompt's memory into the corpus, and let it push
The autonomous agent's loop prompt had grown to ~9 000 words of accumulated
findings, refutations and traps. That is a symptom, not a style: it was doing
the job the corpus should do, in the one place that dies with the container.
Three concrete failures followed from it, and each gets a structural fix rather
than a louder instruction.

1. IT REPEATED WORK IT HAD ALREADY DONE.
   The "do not revive" list and the method traps existed ONLY in the prompt, so
   nothing pointed a fresh iteration at them. Extracted verbatim into two
   tracked files:

     docs/re/REFUTED.md  105 claims tested and dead, grouped by subject so a
                         grep for your noun finds the neighbourhood
     docs/re/METHOD.md   the traps already paid for -- controls, inference,
                         searching, reading data, runtime

   Both are linked from INDEX.md, and the loop prompt now opens by requiring
   them to be read. This is the fix for "re-derived something already known":
   the knowledge is now where the next iteration looks, not in a context window.

2. IT FORGOT TO ARM THE NEXT WAKEUP.
   The prompt tried to solve this by shouting at itself in the first line. The
   real fix is to stop asking: `loose` now defaults to a FIXED interval (45m),
   so the harness owns the cadence and a forgotten ScheduleWakeup cannot end the
   run. SYLPH_LOOP_INTERVAL= (empty) restores self-pacing.

3. IT COULD NOT PUBLISH, SO THE WORK ONLY EXISTED IN THE CONTAINER.
   New `push-work`, plus a read-only credentials mount
   (SYLPH_GIT_CREDENTIALS, default ~/.sylph-git-credentials). It pushes the
   CURRENT branch only, refuses anything that is not auto/*, and never
   force-pushes -- so the consolidated line stays a human's decision and a
   confused iteration cannot rewrite history. The loop prompt now requires a
   push on every iteration that commits, rather than at the end of some longer
   arc, which is exactly when a container dies.

The prompt itself drops from ~9 000 words to 85 lines and, more importantly,
stops accumulating: findings go in the corpus, and the prompt points at it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-28 15:46:59 +02:00