eb3f75d0f5fd7ccbdca7a679ce07e6e695ff6b72
451 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
1eb36c2b9d |
re(media): the disc ships movies in TWO audio profiles, and 28 of them are 5.1
Probed all 97 movies. 28 are wmapro 48 kHz 6-channel 5.1 -- ADV.wmv and every S*.wmv story cutscene; the other 69 are wmav2 48 kHz stereo, every RT*.wmv and hokyu_*.wmv. The split is cinematics vs in-mission radio chatter. Both movies the menu milestone needs, ADV.wmv (the boot/attract intro) and S00A.wmv (the new-game intro), are in the SURROUND group. Why it matters: one ffmpeg command over dat/movie/ produces two different kinds of result and records neither. The 69 stereo files pass through unchanged; the 28 surround files get downmixed 5.1 -> stereo by ffmpeg's DEFAULT matrix, folding centre-channel dialogue into L/R at a weighting nobody chose and which is not stable across ffmpeg versions. That is a content decision inherited by accident, so it should be stated explicitly and recorded beside the command. Credit where due: found by the human while checking a transcode, verified independently here and widened from one file to the whole disc. Two METHOD entries from the same episode, both about measurement rather than format: don't probe a file another process is still writing (a half-written transcode reported 33 s against a 137 s source, no error, nearly a filed bug), and a difference-signal RMS is meaningless before cross-correlation alignment (-34.2 dB against a -25.3 dB source looks like failure and is inconclusive). |
||
|
|
76653ca41b |
re(ui): the focus ring SPINS, the game draws it, and the leaf owns the f record
Three things, all from parsing ptbtn0Nf.rat as a build. 1. THE RING SPINS. Its two keyframes differ in exactly one field: rotation_deg ramps 0 -> 360 with position, scale, alpha and tint all constant. A spin in place, the same shape as the GP_BUNK example already recorded. 2. THE ORACLE CONFIRMS THE GAME RENDERS IT. In the OPTIONS-focused capture the ring's bright head sits in a completely different angular position from the sprite's own -- caught mid-spin. This is a SECOND independent confirmation that rotation_deg is drawn, now on a different screen and a different element from the ptloop sweeps, and it raises rotation's priority: it is not a title-only concern that sits off-screen at rest, it is the main menu's focus marker. NO ANGLE IS QUOTED. A brightest-region centroid says ~250 deg, but the control refuses that precision -- rotating the sprite by a known 30/90/180/270 and re-measuring gives errors up to 19.8 deg. What survives the error bar is that a <=20 deg error cannot manufacture a ~250 deg displacement. 3. WHICH PLACEMENT WINS -- correcting this page's own earlier caveat, which said to use the leaf only for elements the parent does not declare. Right for a BASE record, wrong for an f record: the parent declares NO element for ptbtn0Nf.rat at all (zero of build 5's 16), so the f record's placement comes from its leaf for BOTH elements, label included. The label's (-7,-7) is load-bearing -- the f sprite is 13px larger per axis and -7 keeps them concentric (535+96/2 = 583 vs 542+83/2 = 583.5). Corroborated against the oracle: the focused-minus-unfocused region is x 505..703, and the leaf predicts a right edge near 707 where the parent reading predicts 714. Also exposes UiBuild::records (name -> (offset, size) of a nested .rat leaf). Nested records were parsed into a PRIVATE map, so a consumer holding a UiBuild could not locate a leaf's bytes at all -- which is exactly what blocked the port from reaching the ring. |
||
|
|
0ee0bb8565 |
re(ui): the focus ring's position is decoded -- a .rat leaf parses as a build
The port needed ptbtneff01.t32's placement and was about to author it from an eyeballed PNG measurement. It does not have to: a `.rat` leaf needs no new reader. Its first 32 bytes have a bundle header's shape -- "RATC", 0x3c declaration-entry size at +4, element count at +20, design 1280x720 at +24/+28 -- so ui_layout::parse_build reads it unchanged. The control is the base record, whose position is known independently: the parent screen reports ptbtn01.rat resting at (542,162), and parsing the leaf alone returns ptbtn01.t32 at (542,162). It reproduces all five buttons. Positions are absolute design-space top-left. The ring rests at (500, 156/236/ 316/396/476) for buttons 1-5 -- a uniform (-42,-6) from each button's own rest, identical in the Japanese bundle. The bright label is a uniform (-7,-7). Two things recorded rather than smoothed over: a leaf's placement DUPLICATES the parent's rather than being relative to it, and the two copies are not always byte-equal (ptbtn04's parent says y=401, its leaf says 402) -- the parent is what compose honours, so the leaf is the source only for elements the parent does not declare, which is exactly the ring. And `screen render --focus` is blind to the ring for the same reason the port's exporter was: el.focused is name-based on top-level elements and neither walks into the leaf. |
||
|
|
0fd8e6953e |
re(ui): answer four of the port's five asks -- splash, fade-out, focus, gamma
1 SPLASH ADDRESSING (was blocking P3). No content predicate exists: design size fails (every extra composable bundle sampled is 1280x720, like every screen) and element count fails (fragments run 2..15, the splash halves are 3 and 7). But GP_TITLE needs none -- `--all` adds exactly four bundles there and all four are real screens, with the --all index equal to the pak entry index 1:1. And there are TWO splash screens: 11/14 are the developer logos, 10/13 are the SQUARE ENIX publisher wordmark, which the port did not have and which the boot shows first. 2 FADE-OUT (was blocking P3). It is (a), and it is bigger than the fade quad. Every element ends on exactly ONE untimed keyframe, which rules out (b); that block is where the screen plays out -- quad to a=255, buttons/labels/glows to a=0, frames hold. (c) is refuted by a null test that discriminates: a black quad alone holds the button/background brightness ratio constant, and through the fade it falls 6.50 -> 1.94, 3.4x monotonic. 3 FOCUS (saves P5 rework). Over-vs-instead is unobservable -- the focused sprite covers the base at 100.0% of base-visible pixels on three pairs once aligned (true offset (7,7); the centre alignment reads a misleading 78-84%), and compositing both ways differs by RMSE 1.1 inside the button rect. The real defect is the focus record's SECOND element: ptbtn0Nf.rat declares ptbtneff01.t32 (a 42x46 glowing ring, focus only) plus the bright label, where the base declares one sprite. That ring is the marker the port draws nowhere. 5 GAMMA. The capture is not neutral: capture ~ 255*(render/255)^g, g ~ 1.34-1.49, and the chain says it is a ramp the GAME installed, not a capture artefact. So RMSE against captures has a floor. Reach stated: the flat patches are all dark (render ~0-60), so midtones and highlights are unconstrained. 4 ROTATION is a human's call and is recorded in MISSION, not acted on -- the port rotating while the reference renderer does not would make verify-screen report a large diff meaning "the port is right". The RE half is answered: rotation is about the declared pivot, measured against a GPU capture. The focus record's +20 element-count word is marked 🟡 not ✅ -- read on GP_TITLE's ten button records only; the disc-wide check is written and still running. |
||
|
|
1a82ade187 |
re(ui): the 60 nameless RATC children are frames, not children -- .tan decoded
Closes the reach caveat the `opt ` name fix left behind: 60 of 18 002 RATC children carry no `opt ` block, and it was not established whether they lack one or sit past our 128-byte window. Neither. They are not children. `examples/ratc_optless_children.rs` re-runs `ratc::parse`'s own guards over the disc and reports which one fired: all 60 are "tag beyond the window", none is rejected by length, gap or charset, none is child #0, and all 60 live in six bundles of one archive. Within a bundle the distances back to the nearest tag are an exact arithmetic progression, step 60 600 -- ten different records finding the SAME tag, because there is only one. Reading a bundle directly: children 1..10 are equal-size T8aD blocks under a single `opt ` name, `pb_f15_eg_anm.tan`. `.tan` is a FRAME SEQUENCE. One block declares the resource; its payload is a run of T8aD frames. Disc-wide, over all 18 718 `opt ` names in all 33 paks: a RATC bundle names exactly six kinds of resource -- `.t32` 14 756, `.rat` 3 311, `.prm` 367, `.tbm` 224, `.sbo` 54, `.tan` 6. Six `.tan`, ten frames each = 60, the entire population with nothing left over. The negative is closed, not narrowed. Consequence recorded but deliberately not fixed: `ratc::parse` over-reports there, listing a `.tan`'s frames as anonymous children. Nothing in the menu milestone reads a `.tan` -- it occurs only in GP_READY_ROOM, which S1 ruled out -- so no screen the port draws changes. Also a METHOD entry for this container OOM-killing `slb_leading_segment_disc` under default test parallelism (SIGKILL, no assertion; 8/8 pass with --test-threads=1). |
||
|
|
0ed33bcd38 |
re(ui): a RATC child's name is stated, not inferred -- and it was hiding every menu background
`ratc::parse` named each child by scanning backwards for the last printable run of bytes before its magic. The format states the name explicitly instead, in an `opt ` block: `"opt " | BE32 len | name | NUL | 3 bytes | magic` -- the same block `ui_layout::opt_link` already read for a button's focus link. The scan agrees with it 17 918 times out of 17 942 and is wrong 24 times, every one the same failure: the 3 trailing payload bytes are themselves printable and beat the real name. For `pteff05.t32` those bytes are `38 41 58` = `8AX`, so the full-resolution background of all five menu screens registered under a name no element declares, resolved to no sprite, and `compose` dropped it through an early `continue` that -- unlike the two arms above it -- records nothing. The screen lost its background and `screen render` still reported "all resolved". `8AX` was never a name. Docs that treated it as one are corrected here. Disc-wide, and the control is the 17 918 the scan already got right: the `opt ` reading reproduces every one of them. Effect on the five screens is the signature of the same art at twice the resolution -- mean brightness unmoved, high-frequency detail x1.15..x1.30 -- which is what the separately-measured `ui-8ax-fullres-background` result said the game draws. Also closes a long-standing dangling reference: `pmbase.t32`, recorded as "on the disc nowhere", is the `GP_STAGE_CLEAR` child the scan called `8AX`. RATC sibling references now resolve 10 148 of 10 148. Verified: 114/114 sylpheed-formats unit tests (including two new ones pinning the `8AX` case byte for byte and the no-block fallback), and every disc-gated integration suite in sylpheed-formats/sylpheed-cli. |
||
|
|
04a3ae1389 |
docs: withdraw yesterday's "paint order is a sequence" -- wrong source
Last iteration I claimed the splash's measured_paint_order [0,2,4,6,1,3,5]
records, between its glow and logo halves, the temporal order they were
seen in rather than depth -- because the halves never share a frame.
The no-overlap measurement is right (glows f94-115, logos f116-211). The
inference is wrong, on two independent grounds:
* Wrong source. That vector is not a read of the draw capture. It is a
read of the live screen object's CHILD ARRAY -- ui-screen-runtime.md
records it literally as "paint order (child slots)". A child list has
a definite order whether or not its children are ever drawn together,
so co-occurrence does not bear on it. The capture was the CHECK.
* The order is in the file anyway. paint_order_audit on GP_TITLE entry
11: derived == measured, 0 inverted pairs, 0 same-layer-key ties. The
glows and logos carry distinct T8aD keys (0xa100 < 0xa110), so the
file orders the halves statically, no capture involved.
I asked the question that started this iteration -- do the title and menu
orders have the same problem -- and the answer is that none of the three
does, for the same reason.
What survives is narrower and now recorded with numbers: how much of each
order its capture actually cross-checks. The title capture is stable (8
draws / 12 quads / 5 textures, identical in all five captured frames
across two logs) and confirms 7 of 24 positions; the menu capture is not
(texture 0x11C30000 present in frame 0, gone by frame 3); the splash
capture cannot cross-check its middle at all.
A counting trap worth the tool: count QUADS, not draws. The menu's draw 9
is indices=24 -- six quads batched from one texture. Counting draws reads
9 where 16 are on screen, and an earlier pass of this analysis briefly
"found" three quads for six declarations that way and concluded elements
were missing. They were batched.
METHOD: check what a "measured" value was measured FROM before reasoning
about its limits. The co-occurrence rule is real, and it is specific to
orders read from draw captures.
|
||
|
|
a7257c96d6 |
docs: the splash .prm measured -- and its paint order is a sequence, not depth
ui-prm-primitives recorded that where a .prm paints on a screen without a measured order is unsolved. For the developer splash it is now measured. Every frame opens with two full-screen draws before any sprite. The second is untextured in all 212 frames with a constant vertex colour of FF000000 -- opaque black -- matching palogo_eff0.prm's declaration exactly: kind 0x10, pivot (640,360) -> 1280x720, one keyframe, a = 255. So the splash backdrop is an opaque black full-screen quad from the bundle itself, painted behind every sprite, which is why a splash render needs --black rather than the default backdrop. Not a general rule, and said so: the measured main-menu order puts pteff02.prm at position 4 and pteff00.prm LAST, the latter being the screen-transition fade. And a correction to an existing row. measured_paint_order returns [0, 2, 4, 6, 1, 3, 5] for the splash, described as "the .prm, then all three glows, then the three logos". But the glows and the logos never appear in the same frame -- 0 overlapping frames in 235 -- and two elements that never co-occur have no observable relative depth. Between those halves the vector records the order they were SEEN IN, not a front-to-back relationship. That does not make the render wrong, and element 0 is a real depth observation since the .prm co-occurs with everything. But the type of the claim matters: reading the vector as depth invites compositing all seven elements at once, which is exactly what does not reproduce the screen. METHOD: two things that never co-occur have no observable relative order; when recording an order, note which pairs actually appeared together. |
||
|
|
11aa4ec7cf |
re(ui): deliver the measured splash sequence the port has to author
The activation decision is code, and MISSION already says the port authors the sequence -- so the useful move is to hand over the sequence measured rather than chase the code. From the 235-frame draw capture, at 1 frame = 1/30 s (2 units/frame, 1 unit = 1/60 s, both settled in Q1): publisher: SQUARE ENIX logo f1-90 90 frames 3.00 s+ at 0.00 (gap, nothing drawn) f91-93 3 frames 0.10 s at 3.00 developer: both glows f94-115 22 frames 0.73 s at 3.10 developer: both logos f116-211 96 frames 3.20 s at 3.83 Three limits, stated with the numbers rather than after them. The capture opens with palogo_sqex already at a=255, so the publisher phase began before the window and 3.00 s is a FLOOR -- every "starts at" is relative to the capture, not to boot. palogo_anima and palogo_anima_eff get 0 draws in all 214 frames, so a third pair's phase is not in this measurement. And it is one capture, one run: the glow->logo switch being a single frame boundary with no overlap is a strong shape, but each duration is one sample. What is solid is the part that matters: the 0.73 s and 3.20 s phases are each within 2% of their element's declared span, so the durations are the bundle's own and only the ordering is authored. That is the difference between a port transcribing timing and inventing it. |
||
|
|
0db76bcc57 |
re(ui): re-establish selective activation by killing the alternative statically
Last iteration I withdrew "a bundle is a palette" because the evidence did not choose between selective activation within one bundle and two compositions shown in sequence. The alternative can be killed from the disc, which I had not tried. Hypothesis 2 needs a bundle declaring the GLOWS WITHOUT THE LOGOS. There is none. Every GP_TITLE entry carrying palogo elements: 10, 13 (publisher twins) palogo_eff0.prm, palogo_sqex, palogo_sqex_eff 11, 14 (developer twins) palogo_eff0.prm + all three logos + all three glows Four entries, and each developer entry declares the complete set of six. So whichever bundle was active across frames 94-211 -- entry 11, entry 14, or both in turn -- it declared the logos and the glows, while the game drew two sprites at a time in disjoint phases. Therefore only a subset of a bundle's elements is drawn at any moment, whatever the bundle-loading story is. The conclusion no longer depends on resolving how many bundles are involved, which is why the texture-base test's failure stopped mattering. So the claim is reinstated -- this time by eliminating the alternative rather than by assuming it away. What worked was not a better capture but asking what the competing hypothesis would REQUIRE on the disc and finding it absent. METHOD: a hypothesis that predicts an artefact can be killed by looking for the artefact, which is often far cheaper than measuring the behaviour. |
||
|
|
be937e3d5d |
re(ui): withdraw the mechanism -- "palette" was an explanation, not a finding
Last iteration I wrote that a bundle is a palette whose elements are
selectively activated. The disjoint glow/logo phases have two
explanations and I asserted one:
1. one bundle, some elements run then others;
2. two bundle-loads shown in sequence (entries 11 and 14 are twins
declaring identical sprites).
The draw log's tex[base=...] looked like it would separate them. It does
not, and the control is in the same table:
publisher splash f1-90 0x11C30000, 0x10000000
glows f94-115 0x11C30000, 0x10000000
logos f116-211 0x11C30000, 0x10000000
The publisher splash is certainly a DIFFERENT bundle from the developer
splash, and it uses the same base. So 0x11C30000 is a reused upload slot,
not a bundle identity, and the test cannot choose between the two
hypotheses.
Survives: a bundle's declared elements are not what gets drawn.
palogo_anima and palogo_gamearts carry byte-identical keyframe times and
in the same run one is drawn 95 frames and the other none -- and
whichever twin was active declares both. The phases are strictly disjoint
(0 overlapping frames in 235).
Withdrawn: the mechanism. The practical consequence is unchanged --
compositing every element of a bundle does not reproduce what the game
shows over time -- but the why is not established and I stated it as
though it were.
What would separate them: a per-draw capture recording the bundle each
draw came from, or a file-IO log showing whether a second RATC entry is
read between frames 115 and 116.
METHOD: a shared resource address does not identify the resource's owner;
and state the mechanism as a separate claim from the observation, or the
weaker one inherits the stronger one's evidence.
|
||
|
|
c4b5752713 |
re(ui): the sequencing survives refutation -- and a bundle is a palette
Two checks on last iteration's "sequential, not simultaneous" reading. First, the phases really are disjoint. If glows and logos ever shared a frame the claim would be wrong. Across all 235 captured frames the count of frames containing both is ZERO, and the switch is a single clean boundary -- f110-115 draw 1280x720 + 262x108 + 525x90, f116 onward 1280x720 + 243x86 + 499x72. Two sprites either side, no transition frame. Second, and larger: a third of the bundle is never drawn. Entry 11 declares three logo/glow pairs and only two appear. palogo_gamearts / _eff 95 / 22 frames palogo_seta / _eff 95 / 22 frames palogo_anima / _eff never palogo_anima declares the SAME keyframe times as palogo_gamearts. Two elements with byte-identical data, 95 frames and 0 frames in one run. Reach: the capture covers frames 1-214, so this is "never in the window". So a bundle is a palette, not a script. Its elements say what to draw and for how long; which of them run, and when each starts, is decided outside the placement data. That is the same conclusion the boot-order work reached from the other end -- the driver is code, not data -- now with a per-element measurement behind it. For the port, concretely: compositing every element of a bundle does not reproduce what the game shows over time. It is the right thing for a static screen that settles, and it is not a timeline. METHOD: two elements with identical data and different outcomes is the strongest possible evidence that the decision is elsewhere. |
||
|
|
29d59cdb06 |
re(ui): a group's duration is in the data, its start time is not
Tested whether the splash timeline, played, reproduces the capture -- the last gap in the animation model. Half of it does. Durations match. At 2 units/frame under the shifted reading, from the 235-frame draw capture of the developer splash: glows drawn f94-115 (22 frames = 44 units) declared ~0..45 = 45 97.8% logos drawn f116-211 (96 frames = 192 units) declared 15..210 = 195 98.5% Each element is on screen for its declared span to within 2%. Starts do not. Every glow declares the same times 15,30,45 and every logo the same 15,30,190,194,206,210, so on one clock they would overlap almost entirely -- and they do not overlap at all. The glows run 94-115 and the logos 116-211, strictly sequential, the logos starting the frame after the glows end. Fitting one origin needs f0 ~ 93.5 for gamearts_eff and ~103 for gamearts, about 19 units apart, and aligning one throws the other off by ~9 frames at both ends. The obvious candidate is refuted. parse_placements reads each group header as (element index, keyframe count) plus one undecoded LEAD-IN WORD -- exactly where a per-group start offset would live. It is 0x00000000 for all seven elements, glows and logos alike. Reach: not the keyframe times (identical within each family), not that word (zero), not declaration order (which interleaves logos and glows where the observed sequencing groups them), not the RATC child order. What remains is that the sequencing is code-driven, which agrees with what the boot-order work concluded independently. For the port: a group says how long an element animates and what it does, not when it starts relative to its neighbours. The observed order on the developer splash -- both glows, then both logos -- is measured for one screen, not a decoded rule, so the sequencing has to be authored. METHOD: when a model reproduces durations but not positions, the missing piece is an origin, not a rate. |
||
|
|
a46a922bc8 |
re(ui): a static composite is only meaningful for a screen that settles
The model's sharpest prediction, tested with its control. The draw log says that on the developer splash the _eff glows are drawn on frames 94-115 and the logos on 116-211, so at the moment the reference capture was taken EVERY glow is already finished -- including the two that have plateaus and which rest_plateau therefore renders visible. Suppressing them should help on the splashes and hurt where a screen genuinely settles. publisher splash +0.9604 -> +0.9982 +0.0377 developer splash +0.9659 -> +0.9980 +0.0321 title (control) +0.9500 -> +0.9480 -0.0020 main menu(control) +0.9460 -> +0.8544 -0.0916 EXTRAS (control) +0.9440 -> +0.8370 -0.1070 Both splashes jump to about 0.998; all three persistent screens get worse. The control is what makes this a finding rather than a coincidence: the same edit helps exactly where the model says it should and hurts exactly where it says it should not. So rest_plateau is not over-drawing in general -- it over-draws on TRANSIENT screens. A plateau mid-animation means the element is held at that point in the timeline, not that it is on screen once the screen has settled. Where a screen settles, the held pose IS the settled pose and the rule is measurably right. And that answers the question left open several iterations ago -- what "rest" means for a transient element. It does not mean anything: the splashes never rest. A static composite of them can match a chosen frame, and about 0.998 is what these captures' frame is worth, but the format does not answer a question the screen never poses. For the port: play the timeline for the two splashes, which the settled keyframe timing now supports, and composite statically for title, main menu and EXTRAS. METHOD: an edit that improves one set of cases is only interesting once you have shown it damages the cases where it should. |
||
|
|
6be691222b |
re(ui): the structural case for last -- 2 293 of 2 305, checked disc-wide
The weakness in the rest-rule finding was that `last` had been SCORED on only two elements. It cannot be scored on more -- only two ambiguous elements sit on a screen with a live capture -- but the entry -> hold -> exit model makes a prediction that can be checked on all 2 305: what does each element's FINAL keyframe look like? final keyframe invisible (a = 0) 1 618 transient: gone at rest final keyframe visible, at max alpha 675 faded in and stopped final keyframe visible, BELOW max alpha 12 genuinely unclear Of the 687 that end visible, 472 have monotonically non-decreasing alpha -- a plain fade-in that stops, [0, 255] over two keyframes in the commonest case (pjex_eff.rat, pghud_speed_cut.t32) -- and another 203 end at their maximum after dipping. So `last` is structurally defensible for 2 293 of 2 305 (99.5 %), against a dwell rule that returns a mid-movement frame by construction. Observed correct for 2, structural for 675, model-consistent for 1 618, unclear for 12. The assumption carrying the 1 618 is stated rather than buried: that a plateau-less element's animation has finished by the time the screen is settled. The draw log establishes exactly this for the two splash glows (drawn frames 94-115, logos 116-211) and establishes nothing for the rest. Default still unchanged. The case is now observational, structural and model-based rather than two data points, but it would move 1 896 elements and the decision belongs with whoever owns the renderer. |
||
|
|
e63efabcfb |
re(ui): why rest_plateau is right -- and last is right only for a transient
The shifted keyframe-time reading looked like it implied something simple: the final pose is reached at a definite time and nothing follows, so rest should just be the last keyframe and the plateau heuristic could go. Tested by applying it to EVERY element: title +0.9500 -> +0.6819 -0.2681 main menu +0.9460 -> +0.6416 -0.3044 EXTRAS +0.9440 -> +0.5745 -0.3695 publisher splash +0.9600 -> blank (zero variance, corr undefined) developer splash +0.9643 -> blank Refuted, and the failure supplies the model. A group is entry -> hold -> exit, and the exit is the screen's DISMISSAL. While a screen is displayed it has not reached its last keyframe; it is sitting at the hold. So rest_plateau is the correct primary rule, and the last keyframe is the post-exit state -- correct only once the screen is gone, which is why applying it everywhere blanks the splashes. This does not contradict the shifted reading. That reading says when each pose is reached; it says nothing about the group being played to completion while the screen is still up. The step between them was mine. And it explains why last wins for the two plateau-less elements: an element with no hold is a transient, it flashes and is over, and at any settled moment it is gone -- which is its last keyframe. The draw capture says the same independently: on the developer splash the _eff glows draw on frames 94-115 and the logos on 116-211, so the glows are already finished when the logos are up. Three independent observables -- animation timing, static composites, and the per-frame draw log -- now agree on one rule: plateau where there is one, last keyframe where there is not. METHOD: a blank render is a NaN correlation, not a low score, and that NaN was the strongest form of the result; and when a model predicts something the measurement refuses, suspect the step you supplied between them. |
||
|
|
6a7c42366c |
re(ui): quantify what changing the rest rule would do disc-wide
The open question was whether "last keyframe" holds beyond the two
elements I could score against a capture. It cannot be scored disc-wide --
only two ambiguous elements sit on a screen with a live capture -- but the
blast radius can be measured, and it argues the same way.
genuinely ambiguous elements 2 305
the two rules AGREE on 409 (17.7 %)
they DIFFER on 1 896 (82.3 %)
dwell (current): invisible pose 1 711 (74.2 %), zero-scale 195 (8.5 %)
last : invisible pose 1 618 (70.2 %), zero-scale 43 (1.9 %)
Two things follow. It is not a marginal choice: the rules disagree on 82%
of the affected elements, so "either is fine" is not available. And the
current rule produces 4.5x more degenerate poses -- a zero-scale pose is
collapsed to nothing, i.e. an element's PRE-ROLL before it has grown in,
which is definitionally not a rest. 195 elements currently rest at a frame
they are only passing through, against 43 under last.
That is an argument from the data's own structure rather than from the two
captures, and it points the same direction.
Kept honest: it is indirect. Fewer degenerate results is not the same as
more correct results, and last still returns an invisible pose 70% of the
time -- right for a transient element, wrong for a persistent one. The
default stays put; the numbers are in HANDOFF for whoever decides.
|
||
|
|
7347db24df |
re(ui): the rest fallback fires on 2 elements, and "last keyframe" wins there
Scored candidate rest-pose rules by rendering and correlating instead of
arguing, and both results correct something I had published.
First, the exposure. The guessing fallback is reached only by an element
that is plateau-less AND multi-keyframe -- a single-keyframe element
short-circuits at `match len { 1 => first }`. Per screen:
title (4) 24 elements 2 plateau-less 0 reach the fallback
main menu (5) 16 5 0
EXTRAS (6) 18 5 0
publisher splash (10) 3 2 1
developer splash (11) 7 2 1
So on the three screens the port cares most about, rest() never guesses.
That is why three different rules render builds 4/5/6 to identical
correlations -- the code is unreachable there, which I nearly read as
"the choice does not matter".
Second, where it does fire, the last keyframe is markedly better:
publisher splash dwell +0.9600 last +0.9982 maxalpha +0.9600
developer splash dwell +0.9643 last +0.9758 maxalpha +0.9643
That refutes my own earlier refutation. I had killed the last-keyframe
rule by arguing it makes palogo_anima_eff invisible while its two
siblings stay lit, which looked like an artefact. The capture says
otherwise: making it invisible is what improves the match. The sibling
symmetry was my expectation, not evidence.
Caveat kept in front: both captures are single frames of a transient
animation, so this fixes which pose matches THOSE frames, not which is
canonically at rest. Default unchanged -- better on both screens where it
fires and identical on the other three, but it would move 2 305 elements
disc-wide on two measurements. Reachable via SYLPHEED_REST_RULE=last.
Also confirmed: all 195 zero-scale rest poses are inside the corrected
2 305 ambiguous population; none is a single-keyframe element.
METHOD: score a rule where it can differ, or you measure nothing; and an
argument from symmetry is a prediction, not a refutation.
|
||
|
|
5b218e6887 |
re(ui): refute my own fix for rest(), and correct the defect rate by 65%
Two corrections from one experiment.
A keyframe group is entry -> hold -> exit, and the exit ends invisible:
on the five port screens the final keyframe is invisible for 21/24
(title), 8/16 (main menu), 12/18 (EXTRAS), 2/3 and 6/7 (splashes). So the
screen as seen is the HOLD, which is why rest_plateau is the right
primary rule and why "rest = last keyframe" would empty every screen.
That suggested a fix: an element with no hold has no representative pose,
so draw nothing rather than guess an endpoint. Tested through compose's
visible mask and correlated against the live captures:
title +0.9500 -> +0.6839 -0.2661
main menu +0.9460 -> +0.9037 -0.0423
EXTRAS +0.9440 -> +0.9094 -0.0346
Refuted on all three, and the reason invalidates a number I published. An
element with a SINGLE keyframe has no adjacent pair, so the plateau test
marks it plateau-less -- but its one pose is unambiguously its rest.
Suppressing those removes backgrounds and full-screen layers, which is
the title's -0.27.
no plateau (as published) 3 807 (24.57 %)
... single-keyframe 1 502 trivially at rest, not a guess
genuinely ambiguous 2 305 (14.88 %)
So rest() guesses for 2 305 elements, not 3 807 -- the figure I gave the
port overstated the defect by 65%. Corrected in HANDOFF and the page.
METHOD: a predicate over adjacent PAIRS silently misclassifies a
one-element list; and acting on a claim is a better test of it than
re-reading it -- this flaw survived a census, a write-up and a handoff
row, and died the moment the rule was used to change a rendering.
|
||
|
|
cdc3186a80 |
re(ui): premultiplied alpha refuted for bit 0x02; parking the field
A per-sprite premultiplied-vs-straight-alpha flag would matter a lot to a port and has a sharp static signature: premultiplied means RGB <= A everywhere. Over the 170 decoded GP_TITLE textures that pair to a flag word: bit SET n= 61 mean %(RGB>A) 55.52 median 52.52 bit clear n=109 mean %(RGB>A) 33.66 median 30.17 Premultiplied requires ~0% for the flagged group. Both groups are far from it and the flagged group violates MORE -- the opposite of the hypothesis. Refuted. What remains is a weak association: flagged sprites carry more bright-RGB/low-alpha pixels, which is what glow art looks like. But the best single threshold classifies 76.5% against a 64.1% base rate -- a 12-point lift with badly overlapping distributions. A tendency, not a rule, and reported with its base rate so it cannot read as more. Noted for whoever returns: "0x02 selects an additive blend" was refuted by blending those sprites additively and finding every measure worse against the capture -- but that ran through a title render since fixed twice (rest_plateau, and the 8AX background the composer drops). The refutation may well stand; it was measured through a renderer with known other errors, so it is worth one re-run if blit ever gains additive blending. Parking the field. Four candidate meanings are dead -- additive blend, eff name in both directions, transient element, premultiplied alpha -- none produced a positive account, and the bit blocks nothing: the port's screens composite at 0.947 correlation against a capture without it. The negative space and the sound attribution method (child order, not size) are written down so a later attempt starts here. METHOD: report a classifier's lift over its base rate; and park a field after N failed hypotheses, saying what was eliminated. |
||
|
|
2740cbef55 |
re(ui): the eff-name implication for bit 0x02 is refuted disc-wide
Last iteration I killed the biconditional and reported that the one-way
reading survived: all 10 bit-set sprites on GP_TITLE build 4 are eff
names, so "bit set => eff name". Checked over the disc, that is false.
sprites with a resolvable preceding name 14 709
bit SET & name has 'eff' 2 338
bit SET & name lacks 'eff' 2 657 <-- counterexamples
bit clear & name has 'eff' 1 399
bit clear & name lacks 'eff' 8 315
P(eff | set) = 0.468
P(eff | clear) = 0.144
The implication fails more often than it holds. What survives is an
association -- 3.3x enrichment -- and build 4's 10/10 was a local naming
habit in an 18-element bundle, not a format rule.
The counterexamples are the useful part: pv_loading_ring0,
pv_loading_light0-3, pv_loading_line, px_bunk_line, px_top_extra. Rings,
glows, lights, thin lines -- effect-like artwork that does not carry the
eff naming convention. Consistent with the bit marking effect sprites by
authoring intent rather than by name, which is a description and not a
decode, and is labelled as such.
Names here come from the string immediately preceding each T8aD,
validated 17/18 on build 4 against the RATC child order; the single
mismatch is the known pteff04.t32 -> registered as 8AX case, so this is
the element (opt) name rather than the sprite's registered name. That
mismatch is itself an independent confirmation of the 8AX finding,
reached from the opposite direction.
METHOD: a pattern perfect on one screen can be near-chance on the disc;
and when an association survives a refuted implication, the
counterexamples are the finding.
|
||
|
|
4ef0e30788 |
re(ui): kill two candidate meanings for the T8aD 0x02 bit, and fix attribution
The bit at +0x04 was recorded as a real field with its meaning "not
diagnosed", noting ptlogo_back2eff is 0x8830 "despite its name". That note
rested on a size match -- and its size is ambiguous, which is the trap
this corpus already records.
First, a sound attribution. T8aD headers appear in the bundle in RATC
CHILD ORDER, verified on GP_TITLE build 4 against an independent property
-- each header's decoded dimensions versus the dimensions the named child
should have: 18 of 18 match, 0 mismatches. Two of those eighteen share a
size (ptlogo_back2eff and ptlogo_back2eff5, both 1133x280), so a size-keyed
lookup cannot separate them; ordering can. Index 12 is back2eff5 (0x8832,
bit set), index 14 is back2eff (0x8830, bit clear). The documented
counterexample is real and correctly attributed -- now on evidence.
Two candidate meanings tested and refuted:
bit <=> name contains "eff" REFUTED: ptlogo_back2eff is an eff
name with the bit clear. All 10
bit-set sprites are eff names, so
the implication holds one way only.
bit <=> the element is transient REFUTED: pteff03/pteff03a carry the
bit and run to t=250, ramping to
a=255 and holding.
Up close, the exception pair differs in two header words: +0x04
0x8832/0x8830 and +0x08 0x8083/0x8081 -- layer keys 32899 and 32897. They
are NOT duplicates: their alpha summaries agree to one decimal (4.5%
opaque, 86.7% clear, mean 19.3) but a pixel compare gives max abs diff 21.
Two renditions of one image at one size, which is why the summaries were
not trusted.
Still not diagnosed, and said so -- but the search space is two smaller
and the attribution beneath it is now sound.
METHOD: T8aD headers sit in child order, use that not the size; and
identical summary statistics are not identical data.
|
||
|
|
81625e8f29 |
re(ui): close the gamma chain from canary's defaults -- the game writes a ramp
Continues the previous iteration, where the game was measured calling VdGetCurrentDisplayGamma at video init. The remaining link -- does it then WRITE the ramp -- is a GPU register operation (XE_GPU_REG_DC_LUT_RW_INDEX in CommandProcessor::WriteRegister), unlogged and invisible to kernel logging. Two facts from the source close it without instrumenting. 1. The swap-path gamma stage is a PURE LUT. apply_gamma_table.xesli is the whole transform: index by input*255, fetch from a 256-entry ramp buffer, output. No sRGB encode, no second transfer function. 2. The table DEFAULTS TO IDENTITY. CommandProcessor::Initialize fills it with value = i * 0x3FF / 0xFF, and its own comment says the linear default is "what games set when starting with the sRGB (return value 1) VdGetCurrentDisplayGamma". An unwritten ramp is a no-op. So the only transform is a LUT, the LUT is identity unless written, the game queries the display gamma at init, and the capture differs from our composite by gamma 1.34-1.49 -- which identity cannot produce. The guest wrote a non-identity ramp. Labelled an inference, with its weak joint named: it assumes our composite reproduces the PRE-RAMP framebuffer, which it does not exactly. What carries it is the shape -- a systematic ~1.4 fitted on flat patches across three screens is not a compositor bug. The obvious alternative, a fixed sRGB stage in the presenter, fits neither direction: an encode (^0.45) brightens and we measured darkening; a decode (^2.2) darkens far more than 1.4. Direct observation remains available and cheap, and needs the emulator only to boot: a GPU trace records gamma ramps as their own command type, or one log line at the DC_LUT register write would settle it outright. Not done. METHOD: a default value is evidence; and name the weak joint of an inference in the same breath as the conclusion. |
||
|
|
5b783e276b |
re(ui): the game does query the display gamma -- measured, with its control
Last iteration's corrected experiment, run. Boot with --log_mask=12 --log_level=3 (Kernel logging on, Cpu/Gpu off), which changes nothing about the output and so cannot perturb the capture harness the way the gamma-cvar experiment would have. VdGetCurrentDisplayGamma is called once, at video init: d> VdGetSystemCommandBuffer(701CF830, 701CF804) d> VdGetCurrentDisplayGamma(701CE1F8(00000000), 701CE1F0(0)) d> VdSetDisplayMode(40000000) d> VdGetCurrentDisplayInformation(701CF110) The control is in the same log: 359 VdRetrainEDRAM and 358 VdGetSystemCommandBuffer lines, so an absent call would have been visible. Per the export's own comment the returned type is "used in D3D SetGammaRamp/SetPWLGamma" -- the game asks the question a ramp-builder asks, at the moment one would ask it. Still open, and stated: whether it then WRITES the ramp, and whether the measured gamma 1.34-1.49 is that ramp. The write is a GPU register operation (DC_LUT), invisible to kernel logging; a GPU trace records gamma ramps as a command type (TraceWriter::WriteGammaRamp), which is where to look next. Worth its own METHOD line: this had been parked behind "needs the emulator to reach a menu" for several iterations, and it needed the emulator only to BOOT -- video init happens in the first seconds. A blocker that stops one experiment does not stop every experiment in the same area. |
||
|
|
7919ed90e2 |
re(ui): the gamma confound is refuted from source -- canary applies none of its own
I had parked the tone-curve finding behind "this may be the emulator, not the game: canary applies kernel_display_gamma_type = 2 (BT.709) on output", with a planned run setting it to 0 and re-fitting. Reading the source kills both the confound and the experiment. VdGetCurrentDisplayGamma_entry is a kStub GETTER the guest calls (xboxkrnl_video.cc). Its own comment: "Used in D3D SetGammaRamp/ SetPWLGamma to adjust the ramp for the display." The cvar is a value REPORTED TO THE GAME, which then builds its own ramp. Canary's role is downstream: the guest writes DC_LUT, command_processor.cc reads it into gamma_ramp_256_entry_table_, and the swap path applies it via swap_apply_gamma_pipeline_layout with apply_gamma_table.ps / apply_gamma_pwl.ps compiled in. So there is no emulator-side BT.709 post-process to subtract, and any gamma in a captured frame is a ramp the game installed. What is NOT established, and the reach is stated: that this game installs a ramp at all, or that the measured 1.34-1.49 is it. The run logs cannot say -- kernel exports log at Debug and this harness masks Kernel logging (log_mask = 13, per boot_menu.sh's own comment), so their silence is guaranteed regardless of what the game did. The planned experiment was wrong in design: changing the cvar changes what the GUEST is told and therefore which ramp the GAME builds, so it could never isolate a stage that does not exist -- and it perturbs the capture harness, since skip_intro classifies movie-vs-static on an absolute rmse threshold that a brighter frame biases. The right run changes nothing about the output: LOG_MASK=12 LOG_LEVEL=3 and look for the call and the DC_LUT writes. Also for the port: the ramp depends on the display type the game is told, and canary hard-codes TV/BT.709 where hardware uses a console setting. So this is a display profile, not a fixed property of the game. METHOD: read what a cvar does before building an experiment around it; and an absence in a log is only evidence if the log would have shown it. |
||
|
|
6c4e5d3d71 |
re(ui): settle 8AX vs ptbase statically -- the game draws the full-res one
I had parked this as "needs a per-draw capture recording texture base addresses". It did not. 8AX (1280x720) and ptbase (640x360 at 200%) are the SAME artwork at two resolutions, which is exactly why comparing either against a capture is inconclusive -- and why comparing their DIFFERENCE is not. Compute 8AX - upscale(ptbase), the detail only 8AX has, and ask whether the capture contains it. Both candidates are first mapped into the capture's tone domain with the measured gamma; without that the residual is dominated by the tone difference and the test is blind. main menu corr +0.0475 controls +0.0032 shift, -0.0075 flip 68% of ceiling title corr +0.0634 controls +0.0095 shift, +0.0086 flip 68% of ceiling Two independent screens, both at 68% of the theoretical ceiling (sd of the 8AX-only detail over sd of the capture residual), 7-15x their matched controls. The controls preserve spatial correlation and destroy only alignment, so they are what "no signal" looks like. So the recommendation changes: resolve the name and draw 8AX at 1:1. Upscaling ptbase 2x is wrong, not merely softer. Still do not draw both -- an opaque layer over an identical one costs fill and hides later changes, and ptbase's element is the one carrying the keyframes, so a consumer needs its timing with 8AX's pixels. Also recorded and withdrawn: a cruder pixel-pair test gave 0.00-0.72 for upscales, 0.98 native and 1.01 for the capture -- apparently decisive. Additive noise raises both terms of that ratio equally and drives any value toward 1; fitting a noise term, both "native + noise" and "bilinear + noise" reproduce the observed numbers. The conclusion is right, that test does not establish it, and it is in REFUTED because the number looks conclusive and is not. Not shown: whether ptbase is also drawn underneath. 8AX is ~86% opaque and carries the same art, so it would hide it either way. |
||
|
|
6c8ee2ee6a |
docs: the UI decode's own evidence images were unreachable -- 11 links repaired
The brief's rule is to commit reference data beside the finding so the port can be built without a disc. Nothing had ever checked that the docs' cited artifacts actually exist. doc_link_check.py walks every markdown file under docs/, resolves each relative link, and reports targets that are missing -- and separately targets that resolve to a ZERO-BYTE file, which looks fine in any listing. links resolving 1038 -> 1049 missing targets 16 -> 5 empty targets 0 -> 0 +11 resolving and -11 missing against 11 edits: the counts pair, which is the confirmation the pass did what it claimed and touched nothing else. Two of the sixteen were the evidence for the UI layout decode itself. structures/ui-rat-layout.md is what the port is built on, and its two figures -- backing "the tutorial PAUSE menu rebuilds pixel-accurately from its sprites" and "the same method reproduces the main menu" -- were written as captures/ui-layout/... from a file in structures/, one directory too shallow. The headline evidence for the decode could not be opened from its own document. Eleven links had the wrong relative depth with the target present. Each was rewritten only where exactly one candidate path resolved, so nothing was guessed; the first pass left three alone because equivalent spellings (captures/../captures/x) failed to collapse, and a second pass normalised them. Five remain genuinely absent and are left rather than invented: two point at MEMORY.md outside the repo, one at a header in the separate xenia-canary-native tree, and two name documents that were never written (weapon-datasheet-runtime.md, canary-build-verified-env-confound.md). None is port-relevant. A missing document is a different problem from a bad path and is not something a link fix should paper over. |
||
|
|
3eba09aae0 |
re: the port was still being told SE audio is undecodable -- it is not
A resolve-check on HANDOFF's own rows. Q8 read "SE audio is undecodable
from the disc -- no XACT container exists anywhere". menu-audio-cues.md
retracted exactly that ("### Retracting 'cannot be extracted'") and
locates three cues in Static.slb that decode to PCM: d-pad move 0x1ec0
(4 packets), (B) back 0x0ec0 (2), (A) confirm 0x5d6c0 (6), all mono
48 kHz. The retraction landed in docs/re/ and the page the port reads
kept the superseded text -- the fourth time in this corpus.
Writing the rule down has not worked, so there is a tool now.
handoff_lint.py flags every HANDOFF line making a strong negative claim
that links a doc containing retraction language. First run: found the Q8
row, plus one benign false positive (Q3 links a doc whose retraction is
about a sprite count, not about the tie-break -- checked, and HANDOFF
repeats none of the retracted figures). The lint also caught its own bug
first: it reported existing docs as missing because it joined a guessed
repo root, so it now resolves links relative to the file as markdown does.
Separately, EXTRAS's paint-order risk narrows twice more. Of its 15 tied
pairs only 2 overlap, and of those, ptloop01 x ptloop02 are loop*
animations compose skips by default -- so exactly ONE tie can be drawn:
ptframe3 x ptframe4, overlapping 102x132 px. Against live-extras.png that
contested region correlates +0.9622, better than the whole frame (+0.9440)
and inside the range of regions where order cannot matter (+0.8502 /
+0.9903). Consistent with our order, not proof: correlation cannot see a
swap between locally similar art.
15 -> 2 -> 1 -> consistent is now the whole paint-order risk on the five
screens, and HANDOFF says so.
|
||
|
|
9ff9e8e633 |
re(ui): the paint-order tie-break is undecodable from the bundle
Q3 was delivered as "decoded: a u16 layer key at +0x0A". The audit last
iteration showed the key does not fully order a screen -- elements
sharing a key are tied, and on the title that tie-break decides two total
occlusions. This searches for what breaks the tie, and closes it as a
negative with reach.
The game paints the five tied ptlogo_back2eff glows in the order
eff1, eff2, eff5, eff3, eff4. Three static structures were searched:
1. The declaration table. Entries 14-18 are byte-identical apart from the
pivot, which is only half the sprite's own size.
2. The T8aD headers. All five carry identical +0x04 (0x8832) and
identical +0x08/+0x0A (32899, the key itself), differing only in
position and tile count. Searched exhaustively -- every offset
0x00-0x7f, u8/u16/u32, ascending and descending:
fields sorting to the MEASURED order: 0
fields sorting to the DECLARATION order (control): 64
The control is the point: 64 fields can be found that reproduce a
known ordering, so the scan finds ordering fields when they exist. It
finds none for the order the game uses.
3. The RATC child order -- a genuinely different permutation on other
screens -- gives eff1..eff5 here, declaration order again.
All three static orderings give eff1..eff5; the game gives eff1,2,5,3,4.
That agrees with ui-screen-runtime's conclusion from the other direction:
the game builds a reordered child list at load time and paints that.
Q3 now reads honestly: the layer key is decoded and orders 4 of the 5
measured bundles exactly; the tie-break within a key is undecodable, and
a consumer must use a measured order or accept declaration index as an
arbitrary stand-in. The port's exposure remains 2 overlapping tied pairs
on EXTRAS.
METHOD: an exhaustive field search needs a positive control, or "found
nothing" is worthless.
|
||
|
|
af43563806 |
re(ui): measure the paint-order hedge -- exact on 4 of 5, and bound the rest
`compose` claimed the derived paint order "reproduces both measured orders up to ties". That sentence was never measured and was stale by one: there are three measured orders, not two. examples/paint_order_audit.rs checks it. main menu (entries 5, 8) derived == measured 0 inverted pairs developer splash (11, 14) derived == measured 0 inverted pairs title (entry 4) DIFFERS 8, all same-key ties So the claim holds and the exception is entirely ties -- but two of those ties are total occlusions, not near-misses. The tied family is the five ptlogo_back2eff glows (key 32899); back2eff5 is 1133x280 and FULLY CONTAINS back2eff3 (82,824 px^2 = 100% of the smaller) and back2eff4 (152,047 px^2 = 100%). Derived paints it on top of two glows it entirely covers; the game paints it underneath. A tie-break by declaration index can therefore be wrong by a whole layer. The title itself is unaffected -- it has a measured order. The port's actual exposure, per screen: title, main menu and developer splash all use MEASURED orders; the publisher splash is derived but has ZERO ties, so it is fully determined; EXTRAS is derived with 15 tied pairs of which only 2 OVERLAP. Two element pairs on one screen is the whole risk, and that is what HANDOFF now says -- not the raw 15, which would have overstated it 7x. Reach stated: this compares the derived order against orders measured from the game, not an independent derivation, so where no measured order exists only the tie exposure can be checked. Overlap uses pivot*2 as the element size at its resting placement. Stale comment in compose corrected. METHOD: a hedge in a code comment is an unmeasured claim; and count the cases that can bite, not the ones that match the pattern. |
||
|
|
969947e0e6 |
re(ui): put a number on the render-vs-capture tone difference
Closes an observation I left dangling last iteration ("the capture is ~4x
darker than the render") and puts a figure on the ❔ INDEX's texture row
already carried: exact gamma/sRGB fidelity untested because a hue
comparison cannot see it.
Geometry first: cross-correlating the main-menu capture against our
render over +/-6 px puts the best alignment at exactly dy=0 dx=0,
correlation 0.9466. Only the tone differs.
Two methods failed before one worked, and both failures are recorded.
Three dark patches gave "4x darker" -- the whole-frame best linear scale
is 0.914, so three patches from one region are not a transfer curve. A
pixel-wise fit over 854,685 pixels then produced a NON-MONOTONIC transfer
(render 96-127 mapping brighter than render 128-159) with mean abs error
10-14 for every candidate model. That is edge misalignment, not a tone
curve: at correlation 0.947 a bright pixel routinely lands on a dark one.
Flat patches fix it -- 16x16 blocks with std < 8 in BOTH images, a
threshold chosen from the counts (0/83/404/1055/1788 at std<3/5/8/12/20):
main menu 404 patches gamma 1.491 err 0.28 (best linear 0.276, 0.34)
EXTRAS 382 patches gamma 1.493 err 0.22 (best linear 0.273, 0.28)
title 506 patches gamma 1.338 err 1.08 (best linear 0.842, 9.02)
Reach, stated because it is narrow: those patches span only render values
~0-60, where gamma and a plain scale are nearly indistinguishable -- the
two menus decide nothing (0.28 vs 0.34, 0.22 vs 0.28) and only the title
separates them. Nothing constrains midtones or highlights.
The held-out control FAILED TO DISCRIMINATE and is reported as such: the
splash's 2918 flat patches are pure black (render 0-4), so every model
scores ~0.00. That is a test with no power, not corroboration.
Confound left open: this compares our composite to what canary DISPLAYS,
and canary applies kernel_display_gamma_type = 2 (BT.709). The exponent
may be its output stage. The discriminating run -- set it to 0, recapture,
refit -- needs one emulator session reaching the main menu and was not
done.
Classified measured, not decoded; HANDOFF says plainly that a port
applying it is authoring.
|
||
|
|
1e82096519 |
re(ui): a full-screen element is dropped on three port screens -- do not "fix" it
Audited what `screen render` silently omits on the port's five screens,
since an element the game draws but we skip is the one defect class the
port agent has actually hit. Everything is accounted for -- kind & 0x4
ghost instances, .prm primitives, loop* animations -- except pteff04.t32
on the title and pteff05.t32 on both menus. Those are kind 0x0, one
keyframe, rest a=255, pivot (640,360): full-screen and opaque.
Cause: the element declares pteff05.t32, but the T8aD behind its `opt `
link is registered under the name 8AX, so build.sprites.get() misses and
compose hits a silent continue. Bytes at 0x0e2035 of GP_TITLE entry 5:
opt ... 70 74 65 66 66 30 35 2e 74 33 32 00 38 41 58 54 38 61 44
p t e f f 0 5 . t 3 2 \0 8 A X T 8 a D
8AX is 1280x720 and present in all six title-family bundles; it is a
sprite in builds 4/5/6 and never an element.
It does not currently show, and that is the useful half. ptbase.t32 is
640x360 drawn at 200% and carries THE SAME ARTWORK: its 2x upscale
differs from 8AX by mean abs diff 2.05 (max 80), and our rendered
background is pixel-identical to 8AX in every patch sampled. So resolving
the name and drawing it in addition would double-draw an opaque
full-screen layer -- invisible as a doubling, which is worse than a
visible bug. Written into HANDOFF as a do-not-do.
The free win, offered and not taken: use 8AX at 1:1 and drop ptbase
instead of upscaling a half-res copy. That is a rendering choice and
ptbase's element carries the keyframes, so it is the port's call.
Not established: which of the two the game actually draws. Both carry the
same art, so pixels cannot separate them; it needs a per-draw capture
recording texture base addresses, since the two differ in size.
|
||
|
|
00b6259e9a |
re(ui): finish the top-level rotation census; record the JP-capture blocker
Two threads had converged on needing one capture this container cannot take, so this iteration records that and finishes something reachable. BLOCKED, written into MISSION.md rather than worked around: Q1's keyframe time association and the rest() rule for plateau-less elements both now hinge on a running capture of GP_TITLE build 7, the Japanese title. The console language is not settable here -- user_language appears only as DECLARE_int32 at four call sites with no DEFINE anywhere in the tree, and it is absent from the registered cvars in xenia-canary.config.toml. There is no flag to pass, and guessing one is specifically unsafe: run-canary's own header records that xenia calls ShowSimpleMessageBox from ParseLaunchArguments before logging starts, so a bad flag blocks forever with an empty log. Rebuilding canary to add the cvar would be improvising around the blocker; it needs a human decision. Neither question blocks the five menu screens. FINISHED: the disc-wide top-level rotation count, left running four iterations ago as a shell loop over `screen info --geometry` that never completed (it decodes every texture per build). Walking the placement region directly takes seconds. top-level elements with a keyframe group 15 493 carrying a non-zero rotation 2 152 (13.89 %) Both controls pass: GP_TITLE build 4 reports 0 (its rotations are the nested ptloop records) and GP_DIALOG build 0 reports the expected two. The control earned its place -- the first version indexed the pak with a `screen list` BUILD number and got 0 for a screen that has two, because GP_DIALOG build 0 is entry 2. GP_TITLE maps 1:1, which is how the assumption survived. METHOD line added. Two free corroborations of the rotation decode. The rotated population is dominated by tactical-map ship icons -- pbb_destroyer 444, pbr_destroyer 402, pbr_fighter 276 -- i.e. markers rotated to heading, the single largest use of the field on the disc. And GP_TITLE entry 7's Japanese wordmark pieces settle from ALTERNATING tilts: ptlogo3a r = 0, -14, -4, -1, 0, ... ptlogo3b r = 0, +14, +4, +1, 0, ... ptlogo3c r = 0, -14, -4, -1, 0, ... Same magnitudes, opposite signs, all decaying to upright. A misread field does not produce that. |
||
|
|
d6a24f40f7 |
re(ui): scale 0 means collapsed, not "unset" -- stop drawing it full size
blit() and fill_quad() both opened with
let sx_pct = if kf.scale_x == 0 { 100 } else { kf.scale_x };
so an element whose pose is collapsed to nothing rendered at FULL SIZE.
I first described this as "a 1-pixel sliver", reading the .max(1) in the
size arithmetic two lines below -- the guard above it meant .max(1) never
saw a zero. Read the whole function, not the lines you went looking for.
Control run before the change: if 0 meant "unset", something on the disc
would use it throughout. Nothing does.
elements with a keyframe group 15 493
at least one keyframe at scale 0 2 166
EVERY keyframe at scale 0 0
zero is a transient the element grows out of 1 762
ptlogo_eff3.t32 runs 0% -> 200%. An "unset" marker no element ever uses
throughout is not a marker.
Both functions now return without drawing when either scale is 0.
Reach: 24 of 24 renders byte-identical across GP_TITLE (all 16 builds),
GP_PAUSE_MENU and GP_OPTIONS -- additive on every screen the port needs.
195 elements have a guessed rest pose at scale 0 and 126 of those have a
non-zero alpha, so the old code painted them; all 126 are in
GP_READY_ROOM.pak, which S1 already declared a no-go. They are
tactical-map sprites at sx=0/sy=100, mid-horizontal-wipe, that the
coercion drew full-width.
So: a real correctness fix with no visible effect on the menu port.
Suite green, 122 passed / 0 failed across 3 suites at commit time.
|
||
|
|
786ca36c97 |
re(ui): size the rest() guess disc-wide, and refute my own proposed fix
Follows up the defect found last iteration: rest()'s dwell fallback is
guessing whenever it runs. Two things were open -- how big it is, and
whether "rest = the last keyframe" is the fix. Both are now answered, and
the second is answered no.
plateau_census.py walks the placement regions directly instead of going
through `screen info --geometry`, which decodes every texture and cannot
do a disc-wide pass in reasonable time. Its control reproduces GP_TITLE
build 7's three fallback elements and names ptlogo_eff3.t32 among them
before counting anything.
elements with a keyframe group 15 493
no plateau -> rest pose is guessed 3 807 (24.57 %)
... current rule returns invisible 1 711 (44.9 %)
... current rule returns scale=0 195 ( 5.1 %)
the two candidate rules agree 1 911 (50.2 %)
195 elements get a rest pose with scale 0%, which is not a pose. And
disc-wide the choice of rule is not cosmetic: the candidates agree half
the time.
But the port's exposure is one element. Across main menu, EXTRAS, title
and the developer splash, 14 elements are plateau-less and the two rules
agree on 13. The single disagreement is palogo_anima_eff.t32.
And "last keyframe" loses there, on a control that needed no new capture:
the splash carries three sibling glows with identical structure and
identical times --
palogo_gamearts_eff 15:a=0 30:a=255 45:a=255 -:a=0 plateau -> visible
palogo_seta_eff 15:a=0 30:a=255 45:a=255 -:a=0 plateau -> visible
palogo_anima_eff 15:a=0 30:a=255 45:a=212 -:a=0 no plateau
-- differing in one byte. "Last keyframe" makes anima alone invisible
while its two siblings stay lit. The capture agrees weakly: box-mean
ratios capture/render are gamearts 0.717, seta 0.723, anima 0.772, and a
glow we drew that the game does not would put anima below its siblings,
not above.
So the defect is measured and the fix is still undecided. Nothing in the
decoder changed.
|
||
|
|
c65c564ac9 |
re(ui): withdraw the render evidence -- rest()'s dwell fallback is unsound
Last iteration I reported a 13.1% render change in GP_TITLE build 7 as
evidence AGAINST the keyframe-time shift, arguing that language twins
should match in brightness. Withdrawn. Localising the diff to an element
shows it is not about the time association at all.
The element is ptlogo_eff3.t32, a transient bloom:
46: (98,42) 100%,100% a=0
61: (108,72) 0%,0% a=0
103: (108,72) 200%,200% a=255 r=80
-: (108,72) 0%,0% a=0 r=150
No two adjacent poses are equal, so there is no plateau, and rest() falls
through to its longest-dwell fallback. The longest gap is 61->103, during
which the sprite grows from nothing to 200% at full alpha and then
collapses. The rule returns whichever end of that movement the indexing
lands on: the invisible frame as decoded, the 200% peak shifted. An
896x389 sprite at 200% is larger than the screen, which accounts for the
entire 13.1% and the entire 4.9-unit luminance gap. I was comparing a
heuristic against itself.
The defect generalises, and structurally. A dwell gap is time spent
interpolating FROM pose k TO pose k+1; neither pose is held during it
unless the two are equal -- which is a plateau, and the plateau path has
already returned by then. So the fallback is guaranteed to be guessing
every time it is reached, under any reading of the times. Every element
with no two adjacent identical poses has a guessed rest pose, in our
renderer and in anything built from it.
Consequences: the case FOR the shift (26x on the hold:fade-out ratio) is
no longer opposed by render evidence -- 10 of 11 builds byte-identical,
the 11th differing only through this heuristic. It is still not adopted,
now because it flips this element to the visibly wrong answer, so the
shift and a decision about plateau-less elements must land together, and
neither half has a capture to verify against.
Default-mode suite green (122 passed, 0 failed across 3 suites so far).
|
||
|
|
f5097779e4 |
re(ui): rotation is not nested-only, and the pivot-anchored scale is measured
Two corrections and one new confirmation, all from finishing the sweep I left running last iteration. Refuted, by my own sweep, within the hour: "rotation appears to live only in nested .rat leaf records". That held for GP_TITLE, GP_BUNK and GP_CHALLENGE -- the three archives the sweep had reached when I wrote it -- and fails on GP_DIALOG and GP_DEBRIEFING_PILOTLOG, which rotate top-level elements. Scoping the claim made it cheap to withdraw, but the sentence should have waited for the sweep. Those top-level cases are the best evidence on the disc, and they show up in `screen info --geometry` without a hex dump. GP_DIALOG build 0: pceff03/pceff04 ramp r = 90 -> 30 -> 10 -> 3 -> 0 while alpha ramps 0 -> 255 and they slide into place -- a swing-in settling upright. Build 6: pzeff02 ramps 43 -> 61 -> 75 -> 90 while scaling 112% -> 200% and fading to 0 -- a spin-out. Rows committed as reference data. New: the pivot-anchored scale term in blit, kf.x - pivot*(scale-100)/100, was implemented and reasoned about but never measured, because every element previously examined sits at 100% scale where the term is exactly zero. The ptloop pair scale 600% and 800%, where it is worth 450 and 630 px. Formula predicts centre y = 360.0 for both; capture measures 359.1 and 360.0. Top-left anchoring predicts 810 and 990. Horizontally it makes t-from-position agree with t-from-alpha to 0.33 / 0.65 units against ~8 without it. That agreement does NOT prove linear interpolation -- both fields were inverted through the same linear map, so a shared easing curve cancels. It shows position and alpha ride one shared parameter. Recorded as such. The disc-wide sweep is still running; the count is incomplete, the existence is settled. |
||
|
|
ffd1a30ba7 |
re(ui): decode keyframe +12 as screen-plane rotation in degrees
The rotated quads on the title screen come from the keyframe block after all. The earlier negative -- "every GP_TITLE build 4 element has all three angle words at zero" -- read the right bytes over too small a region: it walked the top-level declaration table, and the rotated elements are the nested leaf records ptloop01.rat / ptloop02.rat. Confirmed against the framebuffer rather than against our own renderer. The two records declare +12 = 30 and -45; the GPU capture submits their quads at +30.26 and -45.28 degrees -- magnitude and sign, two different values. Corroborated by shape in GP_BUNK 117ca14f, where +12 ramps 0 -> 360 with position, scale and alpha constant: a spin in place. Identifying which draw it was needed edge lengths, not bounding boxes: 400x1076 and 400x1444 against pteff03/pteff03a 399x180 at the elements' two different declared scales, 600% (1080) and 800% (1440). The same test names three known-positives in the capture (ptlogo1, ptcopyright, ptbtn00), so it passes its own control. Keyframe gains rotation_deg plus unknown_4/unknown_8, carried rather than dropped. NOT rendered -- ui_layout::blit is axis-aligned only, so the reference renderer and the port will both draw these upright until a rotating blit exists. The census tool ships with the trap that broke its first version: nested RATC blobs are not 4-byte aligned, so an aligned scan found 0/3 of its own control blocks and missed 16 341 blocks. Disc-wide +12 is non-zero in 14.50 % of 83 862 blocks. sylpheed-formats tests, SYLPHEED_DISC set: 131 passed, 0 failed across the 6 suites finished at commit time; the run had not yet completed. |
||
|
|
4cee06c07f |
re: additive blending refuted, the swoosh is not displaced, and the
residual is smaller than I said Testing the candidate I raised last iteration rather than carrying it. Blending bit-0x02 sprites additively moves every measure the wrong way -- whole-frame mean diff +0.55 to +1.04, swoosh-band mean +1.83 to +3.98, band edge-correlation 0.6971 down to 0.5578. So the bit is real and independent but does not select an additive blend. I reverted the experiment and kept the word as T8adImage::flags, documented and not acted on; the render is byte-identical to before. Second refutation: the swoosh is not displaced. Shifting the band over plus or minus 80 by 8 pixels peaks sharply at zero, 0.7342, falling to 0.22 at 24 px. So the pivot story is dead twice over -- inert at scale 100, and no displacement to explain anyway. And I have restated the residual, because earlier sections overstated it. The +16 to +34 band tiles I quoted were measured WITHOUT --primitives. With the dim drawn the band's average is nearly right at +1.83; what is wrong is its structure, tiles running -38.6 then +33.8 and cancelling. Six candidates eliminated now and none confirmed. One caveat I owe the port agent about the capture I gave them: it is at t=4.0s, roughly 174 keyframe units into a screen whose elements have keyframes out to t=600. I judged "settled" from mean luminance, which cannot see a thin sprite still moving. It is settled for the bulk of the screen and not proven settled for every element -- which is a live alternative explanation for a structural difference in exactly the band the sweeps cross. METHOD: cargo build passing does not mean cargo test compiles. Adding the field built the library in 1.48s and broke two test-only struct literals; cargo test failed with exit 101. |
||
|
|
0788584e9f |
re: a candidate blend flag -- T8aD header +0x04, bit 0x02
Continuing the swoosh. Last iteration ended with "the next step is finding where a blend mode would be encoded, and I do not know the format carries one". It does carry a candidate. The kind field is not it -- the swoosh sprites are kind 0x0, the same as ordinary ones. But the T8aD header word at +0x04 splits the title's sprites exactly along effect versus normal: pteff01, pteff03a, ptlogo_back2eff1..5 and both ptlogoall_eff are 0x8832, while ptlogo1, ptlogo2, ptlogo_tm, ptbase2, ptlogo_back2 and ptcopyright are 0x8830. One bit, 0x02. Disc-wide it behaves like a real flag rather than an artefact: 19216 sprites, 18 distinct values, bit 0x02 set in 27.1 percent, and it toggles against otherwise identical words -- 0x8830 against 0x8832, 0x0830 against 0x0832, 0x0810 against 0x0812, 0x0030 against 0x0032. Marked as correlation and not decode, because nothing here shows the bit MEANS additive. The one thing that makes it more than a guess is ptlogo_back2eff, which carries 0x8830 despite having eff in its name -- so the split is the field's and not my pattern-matching on names. The test is to blend bit-0x02 sprites additively and re-correlate the title against the plate-free capture. METHOD gets the trap that cost the first attempt: searching for a sprite's name and taking the next T8aD returns the SAME header for every sprite, because the names all live together in the declaration table. It failed its own control at once -- different sprites reporting identical dimensions -- and the fix was to match on width and height instead of on proximity. |
||
|
|
b436e5a812 |
re: the rest() fix gets its disc-wide check, and one question back to the
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. |
||
|
|
5e6cf0db92 |
re: rest() fixed -- a trailing keyframe run is the hold when it is
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. |
||
|
|
dcf2090d0f |
re: build 4 captured without the plate, and keyframe groups hold rather
than loop The port agent ranked a plate-free capture of build 4 above any further static RE, so that came first. B from the main menu returns to the title and the plate fades in a beat later, which opens a clean window. Recorded at 20 fps from the press: the art appears at 1.10 s, builds in to 3.70 s, sits settled and unobstructed until 5.00 s, and the plate arrives at 5.10 s -- the band jumps from 282 to 3755 bright pixels. Committed the frame at 4.0 s as the reference for the cyan glow slab they report drawing and the game not having. Their other sub-question -- whether a keyframe group loops or holds -- falls out of the decoded sweeps plus a measurement I already had, and the two agree. ptloop01's final keyframe parks pteff03.t32 at x=1521 and ptloop02's parks pteff03a.t32 at x=-839, both off-screen on a 1280-wide design; and over 18 s of settled title the centre tiles sit at sd <= 0.01 when a looping group would recross the screen every 7.5 s. So groups HOLD at the last keyframe. The loop*.rat name is misleading. Also recorded, in the corpus rather than only in their report: the rest_plateau bug, with their exact identifying condition -- the final untimed keyframe has the same pose as the last timed one -- the six elements it misses on main_menu, and the bracket it drops. That closes an open question ui-paint-order-key.md has carried for a while about ptframe1 and ptframe2 resting at alpha 0 while the capture shows the frame plainly. Same two elements, same cause. Not fixed yet; the change is in ui_layout's rest(). And a METHOD line I would not have written myself: two renderers agreeing is not evidence the field is right. Their composite and screen render matched to 3/255 on main_menu and both omitted two elements the game draws, because both read one field through one decoder. |
||
|
|
a72e7c614e |
re: the last keyframe time is the chunk terminator -- observed, not
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. |
||
|
|
91efa66e9c |
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. |
||
|
|
cae8375b70 |
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. |
||
|
|
72f4f1bd76 |
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. |
||
|
|
386a97e2b6 |
port: audit the handoff -- four stale statements, a broken link, and the
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. |
||
|
|
c848668b1f |
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. |
||
|
|
df23d7bff0 |
re: Q3 was already answered in docs/re -- deliver it, and test it on EXTRAS
The handoff had Q3 as "runtime-solved only". It is not: the layer key at +0x0A of the T8aD sprite header, stable-sorted, is a file-derivable paint order, already checked against five measured orders and already driving the compositor for every build on the disc. That answer had simply never reached the page the port agent reads. The new evidence is EXTRAS -- the first screen composited from the rule alone and scored against a framebuffer capture the rule had never seen. 0.9620 at zero shift, against a same-tooling control of 0.9657 on the screen the rule was fitted to. Written down with its limit rather than its headline: align_to_capture correlates edges, and a paint-order change moves blends, not edges, by =<45/255 on a few per cent of pixels. So the score is evidence for placement and only CONSISTENT with the order. The sharp A/B needs a rebuild and is named in the doc instead of being claimed. |