port agent
Closing the two things I flagged as unconfirmed when I committed the fix.
The unit gate is green: cargo test -p sylpheed-formats with SYLPHEED_DISC
set gives 131 passed, 0 failed across six binaries including the
disc-gated ones. The earlier background run that produced an empty log
had died with SIGTERM, which is why it looked like nothing happened.
And the disc-wide check the classification requires. Reimplementing both
rules over every RATC bundle on the disc -- 2859 bundles, 13991 elements
with at least two keyframes -- the fix moves rest for 30 elements, which
is 0.21 percent. Four go invisible to visible. Zero go visible to
invisible, which is the safety property I wanted and did not have when I
committed. The four are ptframe1 and ptframe2 in GP_TITLE entries 5 and
8, the same pair once per language.
That surfaces a discrepancy worth handing back rather than smoothing
over. On the English main menu exactly TWO elements satisfy the port
agent's pose-equality condition, not six, so their six must span the
whole twelve-screen export. That fits their own observation that the
timeline and rest differ in exactly one region, the bounding box of
ptframe1 and ptframe2 and nothing else. But if any of their other four
have a TRANSPARENT trailing run, my alpha rule leaves them alone
deliberately -- that exclusion is what protects PAUSE -- so I have asked
which screens they are on and whether a capture shows any of them drawn.
If one is, the alpha rule is incomplete and needs a third discriminator.
VISIBLE, not when its pose repeats
The port agent's report was right about the defect and about which
elements it hits, and its proposed condition does not survive the case
the exclusion was written for.
Their test -- the final untimed keyframe has the same pose as the last
timed one -- is also true of pgptitle.rat, whose trailing run is two
identical transparent frames. Adopting it would erase the word PAUSE
again, which a committed capture disproves. What separates the two is
alpha: an exit fades the element out so its last keyframe is transparent,
while an element with no exit ends on the pose you can see. So
rest_plateau now accepts a trailing run exactly when it is visible.
Verified against a capture rather than against another renderer, which is
the point their own report made: ptframe1's rest moves from (620,108)
t=16 to (440,108) t=62; the changed pixels are 10082 in a bounding box of
x 440-839, y 108-577, which is exactly the 400x470 at (440,108) they
predicted; and correlation against main-menu-oracle.png over that region
improves from 0.9596 to 0.9748. The PAUSE wordmark is unchanged across
all three pause builds.
This also closes a question ui-paint-order-key.md has carried for a
while, that ptframe1 and ptframe2 rest at alpha 0 while the capture shows
the menu frame plainly.
One trap cost most of this iteration and goes in METHOD: CARGO_TARGET_DIR
is redirected in this container, so ./target/debug/sylpheed-cli is hours
stale and every render I made against it was old code. Byte-identical
before and after reads as "no effect" when it actually means "you ran the
old binary".
The full disc-gated test suite was still executing when this was
committed; the verification above is artifact-based.
assumed
Continuing the pulse's cycle length, which I had called a structural
limit of the format. It is, and now I can show it rather than assert it.
Dumping past what I thought was the final keyframe found an EIGHTH block:
fade 0x00ffffff at the same position, so the glow returns to fully
transparent and the pulse is a closed cycle rather than a one-shot ramp.
That is worth having on its own -- the port knows the animation ends
where it starts.
And the eighth block's time slot contains the four bytes "end ", the
record's ASCII terminator. So the corpus's rule that a group's last block
has no time of its own holds here in a second form: not the next group's
element index, but the chunk terminator. The value does not exist. That
goes into ui-rat-layout.md, because it generalises beyond this record.
The measured 2.3 s would need a final step of about 33 units. I have
written that number down explicitly labelled as fitted to the measurement
rather than read from the file, so nobody later re-derives it as a
decode.
One thing I had half-assumed and can now rule out: the word at +0x004 is
not a keyframe count. It reads 60 here with 8 keyframes and 30 in the
loop records with 3. Unknown, and marked so.
Closing the residual I left explicitly unidentified last iteration, and
correcting my own claim twice over in the process.
The cheap test first: is the oscillation global? No -- the bottom-right
corner is flat at sd 0.003 and uncorrelated with the whole frame. So a
per-tile amplitude map over an 8x6 grid, which localises it hard: sd 7.65
in the band x 318..954, y 560..672 at lag 2.3 s, against 0.06 on the
wordmark. That band is the PRESS (A) BUTTON plate's rest position.
Decoding ptbtn00f.rat, the plate's highlight variant, gives the pulse
itself: alpha 0x00 -> 0x06 -> 0x4a -> 0x50, held, then back down through
0x4a to 0x06 across t=6..105. A glow that fades in and out, which is
exactly what a press-start prompt does.
So "the title screen loops at 2.2 s" was wrong in both halves. The title
ART is near-static apart from the two decoded sweeps at 7.5 s and 9.5 s;
what pulses is the PLATE, and the plate is build 2, not build 4. REFUTED
carries it under my own name.
Still amber, and said so rather than rounded off: the cycle LENGTH is not
readable. The keyframe group's last block has no time -- that slot
belongs to the next group -- so the declared span is at least 105 units,
1.75 s, against a measured 2.3 s. Consistent with a final block
extending the tail. Not confirmed.
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.
The previous commit's handoff edit failed its own assertion -- the text
had been rewrapped since I wrote the pattern -- so the finding landed in
docs/re and REFUTED but not on the page the port agent actually reads.
An answer not reachable from HANDOFF.md has not been delivered.
did not work
I set out to do for the title what worked for the splash: measure the
build-in and check it against the declared keyframes. That part failed,
and I am recording the failure rather than the number.
It failed for two reasons, both mine. The title was reached by skipping
the movie with A, which cuts to black and brings the title up on a path
that may not be the normal one. And the visible rise, about 2.7 s, is a
luminance curve -- which screen-transitions.md already establishes is not
the fade quad's ramp. So 2.7 s neither confirms nor contradicts build 4's
declared 16 -> 261, four point oh eight seconds. They are not measuring
the same thing, and I have said so instead of reporting a discrepancy.
What the recording did establish is worth having anyway: the title never
settles. Over 22 s the mean luminance oscillates continuously, peaks at
5.6, 7.8, 10.0, 12.7, 15.2, 17.1, 19.2 and 21.3 s, mean interval 2.24 s.
Marked as about 2.2 s plus or minus 0.4 rather than a precise period,
because peak-picking a low-amplitude signal is coarse.
The mechanism was already decoded and I did not re-derive it: build 4
declares ptloop01.rat and ptloop02.rat, and loop*.rat is a looping sprite
animation rather than a composition. What is new is the period, and that
it runs forever. For the port that matters -- drawing the resting pose
and stopping will look wrong.
Not confirmed, and flagged: that 2.2 s is the loop's OWN declared cycle.
screen info prints the element's placement keyframes but not the frame
count inside the .rat, and I did not open it.
Closing the caveat I left last iteration -- the splash timing was +/-0.5s
because I sampled at 2 fps, and at that rate I could not see ramps at
all, only plateaus.
Re-recorded at 10 fps: the splash FADES, both in and out, rather than
cutting. And the bundles declare it. palogo_sqex.t32 carries keyframes
[15 30 235 239 251 255] and palogo_gamearts.t32 [15 30 190 194 206 210],
each with an _eff glow child on [15 30 45]. Under Q1's 1 unit = 1/60 s
that is a 0.25 s ramp in, a 3.42 s or 2.67 s hold, and a 0.33 s fade out
-- against measured holds of about 3.5 s and 2.4 s and fade-outs of about
0.3 s. The constant 0.35 s offset between declared and measured start is
just that my recording's t=0 is when the WINDOW appears, not when the
guest starts drawing.
So the first screen's animation moves from measured to decoded: the port
reads it off the disc instead of trusting my stopwatch.
It also explains something the capture showed and I had no account for.
The brightness overshoots on the way in -- peaks at 0.8 s, settles by
1.1 s -- which reads as a bloom. It is the _eff glow child, whose
keyframes run 15 -> 30 -> 45, ramping in after the logo and back down
while the logo holds. Mechanical, not a rendering artifact.
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.
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.
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.
this corpus thought
Last iteration I proposed that the boot title is phase 2 and the
attract-returned title is phase 4 state 0, and that only phase 2 wires up
A -- a candidate answer to the oldest open question in
canary-scripted-input-traps.md. I marked it untested and named a test
that needed reading this+132 at runtime.
There was a cheaper one. The decoded graph says B from the main menu is
the edge 4 -> 0, so the B-returned title IS phase 4 state 0. If that
state were the inert one, A would not work there.
A works. Control first -- A on the boot title opened the menu -- then A
on the B-returned title opened it too. So phase 4 state 0 handles A and
the hypothesis is refuted, one iteration after I raised it. Recorded in
REFUTED under my own name, and the conditional stated: the edge is
decoded, but which title it produces is my inference.
The test earned something better than it cost, though. That page frames
the problem as "the boot title accepts A, a later title does not", and
that is too broad -- the B-returned title accepts A perfectly well, a
case nobody had tried. Only the ATTRACT-returned title is inert. Whatever
causes it is tied to the attract cycle specifically rather than to the
title having been shown before, which is a much smaller haystack.
Also reproduced in passing: 4 s after B the plate is absent and fades in
after, the same near-miss this corpus has already been caught by once.
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.
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.
Chasing where the event code comes from turned up the level above and
corrected my own framing three sections running.
sub_821C6458 has exactly one caller, sub_821C7850, and that caller is
itself a dispatcher -- on a SECOND, outer state field at this+132, five
phases, gated on this+16 == 3. Phase 4 is the title/menu machine I have
been decoding. Phase 0 is sub_821C5690, which the iterate3E notes had
already fingered as the splash mechanics from a completely different
direction. That agreement is the useful check here.
So GamePart_Title has two nested state fields: a phase at this+132
choosing which sub-machine runs, and the state at this+136 inside phase
4. Everything I decoded over the last three iterations -- ten states,
eighteen edges, the event dispatch -- is phase 4 ONLY. Phases 1, 2 and 3
are untouched. I have called that out in REFUTED against my own earlier
wording rather than quietly restating it.
And the event code is forwarded, not created: all five phase handlers are
called with the same (this, r29, r28), where r28 is sub_821C7850's own
third argument passed through untouched. So the event vocabulary is
defined at least one level further up, and finding what 3, 5, 8, 10 and
25 mean means going up again. Not done.
Continuing Q6's last item: the condition on each edge.
sub_821C6458's third argument is an EVENT CODE, and state 4 -- reached
straight after the main menu is installed, so the input-waiting state --
dispatches on it through a second jump table at 0x821c6b7c, 26 cases.
Six are handled and twenty fall through changing nothing:
event 0 -> state 0 (TITLE_SCREEN, back to the title)
events 3, 5, 8, 25 -> state 8 (LOADING)
event 10 -> state 5
So the edges I listed last iteration as "4 -> 0, 5, 8x4" are each now
attributed to a specific event, and the input state's graph is complete.
One thing I deliberately did NOT promote. The main menu has five items, B
returns to the title, and this table has one event to the title, four to
LOADING and one elsewhere. That is a tempting fit -- B plus four items
that load an external archive plus EXTRAS staying inside GP_TITLE -- and
it matches everything measured. It is also a count-match and nothing
more: the events are unnamed, nothing shows event 3 is a particular menu
row, and state 5 installs no named screen so the EXTRAS half has no
support at all. Written down as an observation with that said plainly,
because the same shape of reasoning already cost me DIFFICULTY and
EXTRA_MENU two iterations ago.
Still open: what the event numbers mean, the conditions on edges out of
the other states, and what states 1/3/5/6/7/9 do.
Q6's last open piece was which state leads to which, and it turned out to
be a plain switch.
state = this+136, compared against 9, dispatched through a jump table at
0x821C6498. The ten words at that address disassemble as lwz r16,N(r28)
instructions and are nothing of the kind -- they are the table. That is
the same "self-reference" I flagged two iterations ago as looking like a
jump table; it is one, and the disassembler was decoding its data.
Three states install a named screen: 0 is TITLE_SCREEN, 2 is TITLE_MENU,
8 is LOADING. Eighteen transitions, every one a literal li/stw pair into
the state field, giving the full graph.
It lines up with the behaviour measured weeks of iterations ago without
either side knowing about the other: boot reaches state 0 and A opens
state 2; B at the main menu returns to the title, and 4 -> 0 is the ONLY
edge back to state 0, reached from 2 -> 4; entering a submenu goes
through LOADING and comes back, which is 4 -> 8 at four separate sites
then 8 -> 2. I have marked that as corroboration and said plainly it is
me matching a graph to observations -- the conditions on the edges are
not decoded, so nothing here proves which input picks which branch.
Still open and written down as such: the condition on each edge, what
states 1/3/5/6/7/9 do, and the fact that state 3 is never a destination
in this function, so something outside sets it.
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.
Continuing Q6's last residual -- the control flow I left unread.
All three state-name sites in sub_821C6458 compile to the same shape:
load the name, then bl 0x821CC860 with it in r5 and 0 in r6, then hand
the result to sub_82187B78. So a transition is lookup-by-string then
install: sub_821CC860(this+88, this+24, "TITLE_SCREEN", 0). Not a numeric
id and not a table index. That also explains something that had been
sitting unexplained -- GP_ADVERTISE_DEMO having zero xrefs -- because at
this level the screen graph is keyed by name rather than by GamePart id.
sub_821CC860 has 28 callers, and the upper-case identifiers in that
neighbourhood split cleanly into screen names and config keys. Three of
the screen names are corroborated by measurements I took before ever
opening this function: DIFFICULTY is what NEW GAME opens, EXTRA_MENU is
the EXTRAS submenu, TUTORIAL_MENU the lesson list. That is the static
side agreeing with the dynamic side on names neither knew about the
other.
Held at amber deliberately. The 35 strings are what those callers
REFERENCE, not proven arguments, and the list plainly mixes screen names
with things like TEXT_FONT and GAMMA_RGB. Confirming it means checking
per call site which string actually lands in r5, and I did not do that.
Still unread: which state leads to which. The three lookups sit in
different branches and TITLE_MENU's is guarded by a cmplwi/bne, but I did
not trace the branch structure, so the ORDER still comes from measurement
rather than from the code.
TITLE_MENU, LOADING
Taking the one residual I had ranked as worth doing: sub_821C6458, which
the previous note left explicitly unread.
It is the title part's screen-state function -- 4460 bytes, has EH,
called from exactly one place, sub_821C7850, which sits beside the
creator the registration site points at. Its string references are
TITLE_SCREEN, TITLE_MENU, LOADING and BASE_INFO. Those are precisely the
states measured off the running game, in the game's own words, and
BASE_INFO is this corpus's own marker for a screen-config lookup rather
than a table read.
That sharpens Q6's negative rather than overturning it. The title part
DOES ask for configuration keyed by TITLE_SCREEN and TITLE_MENU, and
config.ini -- the disc's only config -- has no such sections, only an
empty [SYSTEM] and [LANGUAGE]. So the game asks the question, the shipped
disc does not answer it, and the defaults live in code. The port can
transcribe the state names even though it must author their contents.
Marked amber, not green: this is characterisation from string references
and call counts, not a read of the control flow. Two self-references
inside the function look like jump tables, which is what a state machine
compiles to, but I did not confirm that and nothing here shows which
state leads to which.
One refutation recorded because it looked like a find for several
minutes: the {func, func, ptr} triples at 0x820a3b48 read convincingly as
a state/handler table and are nothing of the kind -- the bytes before
them are the tail of a RegisterToFactory diagnostic string and the data
column is zero-filled descriptors. Static-init records, not dispatch.
located, and two of them reproduce
Continuing Q8 rather than opening anything new. The gap that mattered was
the confirm cue: a menu needs a sound on A, and I had only cursor and
cancel.
The fix was where I was counting from. The first run started counting
streams at the main menu, so the confirm cue had already fired during
boot and showed up as "played before any input". Counting from the TITLE
instead attributes it cleanly: the A that advances title -> main menu
fires the 12288-byte wave at 0x5d6c0, together with the two stereo BGM
stems, which is the menu's music starting.
So move (0x1ec0, 0.533 s), confirm (0x5d6c0, 1.016 s) and back (0x0ec0,
0.344 s) are all located and decodable. Move and back came back with
IDENTICAL head bytes and sizes on a second independent boot, so the
dedup-keyed method is stable and those two are now n=2 rather than n=1.
Two honest limits recorded rather than smoothed over. The A press both
confirms and opens a screen, so its wave could be the cue the vocabulary
calls DECIDE or the one it calls SUB_WIN_OPN -- the port gets the sound
the game plays, not a name. And left/right fired no new stream, which
excludes a DISTINCT invalid cue but cannot exclude them quietly replaying
one of the three already heard, because the probe dedups on head bytes.
Finishing the step I left open last iteration rather than starting
something new. Offsets and packet counts were in hand; what was missing
was proof they are actually waves.
slb_extract_wave.py wraps a (bank, offset, packets, channels, rate) slice
in a synthesized XMA1 RIFF, following the layout the Rust decoder already
uses. The three located cues decode to 0.533 s, 0.344 s and 1.016 s of
mono 48 kHz audio, audible from sample 0, each with the percussive
attack-and-decay envelope of a UI blip. Bitrates come out at 12-15 kB/s,
about half the stereo BGM rate, which is what mono should be.
The control matters more than the results. The SAME wrapper applied to
BGM_001's first wave decodes to 173.808875 s -- identical to the duration
that bank's own on-disc RIFF header produced back when Q10 was answered.
So the header I synthesized is not approximately right, it reproduces a
known-good decode exactly, and the cue durations are trustworthy for the
same reason.
I did not commit the decoded audio. Three commands regenerate it from the
disc, and the corpus's job is measurements and tooling rather than
extracted game assets. The offsets, the packet counts and the tool are
the deliverable.
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.
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.
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.
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.
residue in one place
Every MISSION row is answered, so this iteration checked the deliverable
itself rather than opening new scope: an answer that is not reachable
from HANDOFF.md has not been delivered, and I have been editing that page
by surgical replacement for a dozen iterations.
Four things were wrong or stale:
- the Q5 row still said initial focus was 2x TUTORIAL, 1x NEW GAME while
its own bullet said four boots -- I updated the body last time and not
the row;
- the Q2 bullet still described the other four main-menu destinations as
"likely by name -- an inference, not a measurement", which Q4 has since
MEASURED, and it listed GP_MISSION_SELECT as a main-menu destination
when MISSION SELECT lives under EXTRAS;
- the Q1 bullet claimed that if the game presents at 60 Hz "nothing else
on this page changes". That is now false: Q7's declared fade-in lengths
are keyframe-derived and would halve. The same bullet also missed that
the transition capture is a SECOND independent measurement supporting
1 unit = 1/60 s -- the quad is declared black for 12 units, 0.20 s
under the conversion, and the capture measured the plateau at
0.17-0.23 s. Recorded as corroboration, not proof, because the plateau
spans two screens' fades and cannot separate them;
- bgm-two-stems.md linked ../port/MISSION.md from inside structures/,
which resolves nowhere.
Also named the two videos actually in scope, since "3.3 GB of video, two
files are in scope" is not much use without the filenames.
And added a What Is Still Open section. With every row green the residue
was scattered across eight pages; it is now one table with why each item
is stuck. Two entries share a single unblocker -- an emulator whose audio
path can be observed -- and they are the only ones that would change a
port decision.
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.
rather than leaving it amber
Q6's second half asked what the game READS to decide the boot order. The
answer is nothing, and the value here is the reach rather than a find.
Four places checked, the order in none of them: config.ini's [SYSTEM] is
empty and it is the disc's only config; the movie manifest carries the
boot-side assets but no transitions; the requested GamePart id was
already shown to exist only as a stack argument in flight, with no
persistent field and no literal store; and the string GP_ADVERTISE_DEMO
has zero xrefs of any kind, so nothing in the code reads the attract
entry of the id table. A transition is a call with an id argument.
Traced as far as it goes cheaply: the RegisterToFactory<0,
GamePart_Title> string is referenced from exactly one site,
sub_8280E148, which also takes the address of sub_821C7D98 -- where a
factory template puts its creator. Marked amber, because that is position
and convention rather than proof, and sub_821C7D98 has zero .rdata
references, which fits a new+ctor thunk and not a state machine. The
substantial function in that neighbourhood is sub_821C6458 and I did not
read it.
So Q6 closes as answered with the driver classified as code rather than
data, which means the port AUTHORS the sequence -- and that is fine,
because the sequence itself is measured end to end and the handoff now
carries it in one line.
GameParts exist at all
Q6's second half, advanced but not closed, and the negative is the point.
config.ini is the disc's ONLY config file -- one find over the whole
extract -- and its own Shift-JIS header calls it the "Application /
GamePart initial settings table". Its [SYSTEM] section, which that same
comment says holds what the game and every game part share, is EMPTY. So
the boot order is not in disc-side configuration at all, and that search
space is now closed rather than merely unexplored.
What the file DOES carry is the language: XC_LANGUAGE_* -> eng/jpn/deu/
fra/esp/ita, defaulting to eng. That is the mechanism behind the EN/JP
build pairs in GP_TITLE and the <lang>.pak families -- a question the
corpus had described but never traced to its input.
Then the registry. Pulling every RegisterToFactory diagnostic string
binds 24 of the 29 GamePart ids to a C++ class, and five ids have no
registration site: 1, 2, 16, 18, 28. Id 1 is GP_ADVERTISE_DEMO, which
agrees with what I measured two iterations ago -- the attract loop is the
TITLE replaying ADV.wmv, not a transition into an advertise part. Marked
amber, not green: it is an argument from an error message, not from code.
Two things fall out for Q4: ids 3 and 4 are the same class
(GamePart_SaveLoad, one part with two ids), and the menu buttons' ids now
match the executable's own class names rather than a list of table names.
Still a name match, one level closer to the code.
What is still missing is the transitions themselves, and I say so: the
manifest gives the boot-side assets, config.ini the language, the
registry which parts exist. What decides to advance is in
GamePart_Title's code and that dig has not been started.
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.
not
Q8. The cheapest thing nobody had tried was to look at the SE cue names,
and they are semantic: SE_UI_CURSOR (2), SE_UI_DECIDE (3), SE_UI_CANSEL
(4), SE_UI_IMPOSI (5, the error buzz), SE_UI_SUB_WIN_OPN/CLS, and
SE_UI_SPLASH_IN/OUT. That is exactly the move/confirm/back/error
vocabulary the question asks for, named by the authors after the EVENT
rather than the sound. 322 SE cues committed as reference data.
They all live in one bank: BANK_SE is a single field reading Static.slb,
and the disc-wide check agrees -- 0 of the 322 has an entry in FILES, the
5135-path list that names every voice, briefing and BGM bank.
I am calling the binding a NAME MATCH, not a measurement, and the page
says so in the same breath as it gives the table. It is a stronger name
match than Q4's GamePart ids -- these are the authors' own event names --
but nobody has watched the game emit cue 2 on a d-pad press, and this
container is muted against a dummy device so there is no audio path to
watch either.
One new negative, with its reach. Static.slb's 8 353 472 readable bytes
contain zero RIFF, zero seek and zero WAVE, scanned over the whole
buffer. The boundary marker that works for all 7 620 other banks is
absent, so the cue is named, the bank is named, and the wave inside it is
not locatable. The UI sound effects cannot be exported yet, and that is a
gap in the assets rather than in the naming. The named next step is
Pj_Silph.xgs -- the XACT project SETTINGS points at, which IS in
sound.pak at TOC 9454, though its 533 bytes carry no XGSF magic and its
region's phase says they are probably the previous bank's tail.
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.
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.
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.
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.
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.
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.
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.
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.
BGM_001.slb is three sub-waves (10 KB, 4.47 MB, 4.67 MB) and the decoder
concatenates them into one 347 s track. That is a default nobody chose, not a
decision: two near-equal halves could be intro + loop, two variations, or two
halves of one piece, and a menu that loops its music needs to know which.
Found while wiring the Audio Library up to the shared banks. Recorded in
HANDOFF.md as a trap too, so the port does not build looping on top of the
concatenated track before the question is answered.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Corrects the split I got wrong in b5a1938. The container is for reverse
engineering, now focused on the menu port; a SEPARATE agent builds the port from
its investigation results. My previous version had the container agent writing
the exporter and the Godot project, which is not the intent.
This lands on the research/engineering line that was already in the estimate:
the container agent takes the research half, the port agent the engineering half.
docs/port/MISSION.md is now a list of open QUESTIONS (Q1-Q9) rather than build
milestones, ordered by what blocks the port earliest -- the keyframe time unit,
which build is which screen state, paint order for the six screens, button ->
GamePart, navigation semantics, the boot sequence driver, transitions, menu
audio bindings, and video binding. Each is done when a written result with
evidence exists, not when something compiles. S1, the Ready Room probe, stays
gated at one iteration and a go/no-go.
Most of these are BEHAVIOUR questions -- timing, transitions, what a d-pad press
does at the end of a list -- so the mission and the loop prompt both push hard
on measuring the oracle rather than reasoning from the file.
docs/port/FORMAT.md is deleted. The export schema is the port agent's design and
was not mine to specify. It is replaced by docs/port/HANDOFF.md, the single page
the port agent reads: a status table, what is already settled and can be relied
on today, and the facts that will trip the port up (the WMV3/WMA Pro intro, the
Static.slb size over-declaration, the voice-vs-music downmix, JNGL_001).
The derived/authored idea survives as the thing it always was -- a finding, not
a design. Every answer must be classified DECODED, MEASURED or UNDECODABLE-with-
reach, and never a fourth thing, because measured and undecodable both mean the
port agent is authoring that value and has to know it. Labelling a guess as a
decode would put it into the port wearing the badge of a measurement.
Reverts the Godot install from the RE container, its AGENT.md section, and the
export/ gitignore entry -- none of that belongs on this side of the wall.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sets a new mission: boot the real disc through developer splash -> intro video
-> title -> main menu -> submenus in Godot 4, interactively, with no gameplay,
no 3D and no emulator.
docs/port/MISSION.md defines it -- eight gated milestones, each finished by an
ARTIFACT rather than by compiling, plus the Ready Room as an explicitly gated
stretch goal with a one-iteration probe that decides go/no-go. GP_READY_ROOM is
1106 entries with 6 recoverable names and is ISL-scripted, so it is either a
week or a quarter, and the agent must not start it on its own authority.
Architecture, per the user's decision: the Godot project is INDEPENDENT of the
Rust viewer and never reads a disc format. An offline Rust exporter converts the
disc into open formats; Godot reads only those. No GDExtension, no Rust in the
Godot project, and sylpheed-viewer is off limits -- it stays the human's
verification tool with its static-data rule intact.
docs/port/FORMAT.md specifies the open format, versioned, because modding is the
port's second goal and that makes the layout a deliverable rather than a temp
directory: JSON over XML (Godot parses JSON natively; its XMLParser is SAX),
names never hashes, provenance in every generated file, and unknowns listed
rather than guessed.
The discipline the whole thing rests on is the derived/authored split. `export/`
is regenerated wholesale and never hand-edited; `authored/` is hand-written and
survives a re-export. Three things this milestone needs are NOT on the disc in
any decoded form -- which button does what, paint order, and menu sound cues --
so they live in `authored/` with a stated `why`. Deleting an authored entry
because the exporter can now emit it IS the measure of progress.
`export/` is gitignored: it is generated from the user's own disc and this stays
a clean-room repo.
Container: adds a pinned Godot 4 (windowed under Xvfb for screenshots, plus a
headless wrapper). ffmpeg already carries libtheora, which is the video target --
Godot 4 plays only Ogg Theora natively and the disc's ADV.wmv is WMV3/WMA Pro.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>