9d695debae26c868059a0776a2e040ed23e1cfcd
76 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
647390e1dc |
register: reclassify under R1 -- name every instrument, re-open the ten our own killed
Both agents asked for this and neither could do it: the register is the file they both read to decide what NOT to try, so two agents agreeing is not the authority for changing it (RETRO-2026-08-31-agreed §7.1). R1: a refutation whose instrument is one of our renderers is not a refutation, it is "our renderer disagrees" -- 🟡, not ❌. The motivating case was not careless work. "Blending those sprites additively worsens every measure against the capture" killed a real disc field for weeks, and read exactly like a publishable negative; the renderer behind it had a stale keyframe association, no leaf geometry and no rotation. Nothing in the entry could have told you. All 222 entries now end with ⟨instrument⟩, read off each entry's OWN stated evidence -- never inferred. An entry that states none gets `unrecorded`, which is 83 of them. Ten moved ❌ -> 🟡, each naming what would settle it: 8 render-vs-capture, 1 our-reader, 1 harness. Three things the pass turned up: * The rest() question is OPEN and had been reading as settled in both directions -- "rest = last keyframe" was refuted by a sibling argument, and that refutation refuted by correlating our render against captures. Both legs are our renderer. Which one you believed depended on which entry you found first, and it decides the pose every plateau-less element is drawn at. * A withdrawal never reached its sibling: "2 391 frames, max glyph 0" was withdrawn because a long-lived x11grab stream freezes and repeats a stale frame; the 1 674-sample negative three lines above it, same probe, was left standing as a reinstated measurement. * 83 of 222 -- 37 % -- record no instrument at all. Not disputed, not safe: unauditable, and larger than every other group combined. tools/stale-instrument is the --stale query the rule needs, because a colour alone re-opens nothing: the failure was that nothing re-opened a claim when the instrument that killed it improved. Its own --check found a real bug on the first run -- sys.exit("text") exits 1, so three faults documented as exit 2 were reporting the same code as an ordinary miss. R1 is now standing text in PROTOCOL.md, with R5's exception: our tool is the right instrument for a question about our tool. |
||
|
|
f4987977a3 |
re: the blend mode IS on the disc -- T8aD +0x04 bit 0x02, DECODED
Reverses two of my own pages. t32-blend-mode-not-on-disc.md said the mode is not
on the disc; ui-blend-mode-measured.md classified it measured and told the port
that which field selects it was unknown. Both were honest and neither is current.
Bit 0x02 of +0x04 set means the game draws that sprite ADDITIVE
(RB_BLENDCONTROL0 = 0x01010101); clear means premultiplied alpha-over.
Fit: 35 elements over three screens, 16 set/additive and 19 clear/alpha-over,
zero errors, every label read out of the guest command stream rather than off a
render.
Control: of every bit of the first twelve header words, exactly one separates
those 35 without error. Nothing ties with it -- which is precisely what the
+0x08 = 0x8050 candidate failed.
Within-pair: ptbtn00 0x0110 alpha-over against ptbtn00f 0x0112 additive, same
screen, same bundle, adjacent draws, one bit apart. And other f variants are
bit-clear and alpha-over, so it is not 'focused variants are additive'.
Out of sample: a prediction committed at
|
||
|
|
cf73cfcbcf |
refuted: my own 0x8050 candidate (twice), and the port's frame sharpener
0x8050 dies disc-wide (38 sprites, high byte tracks the archive) and again on EXTRAS, where pteff21/22/23 share it with the frames. The port's 'neither frame has a fully-opaque pixel' is true and is not the discriminator: pteff10 has max alpha 130, no opaque pixel, and they measure it as nearly exact. Their direction survives -- the draw path did answer -- so what is refuted is the reason, not the conclusion. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v |
||
|
|
a111f28b63 |
re: GP_DIALOG 0/1 is a byte-identical duplicate; 2/3 is a structural pair
The two adjacent pairs with identical element sets split. 0/1 are the same 59 810 bytes stored twice -- a duplicate, not a language pair. 2/3, the DIFFICULTY build, differ in size and in 2.77 % of bytes from offset 0x1BB while sharing every element name, which is what a language pair looks like. Control: entries 10/11, known to be two different dialogs, differ in 54.90 % of the common prefix, so the comparator separates unrelated dialogs. Supported, not proven, with the untested step named: I have not captured DIFFICULTY in ja. That the two are ENGLISH and JAPANESE rests on the disc's convention, not on a capture of this screen. This partially restores a claim I withdrew, at lower strength than the original phrasing. Also records a refutation attempt on the port's BGM_103 exclusion: it survives and is tighter than they stated -- of 32 census rows, exactly one bank carries EITHER wave size, not merely both. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v |
||
|
|
d48781262e |
re: menu focus does not survive a reboot -- six fresh boots, three following a session that ended elsewhere
No new boot was spent: six runs had already captured the first menu entry of a fresh boot, and all six read NEW GAME. Three of them follow a session that ended with the cursor on EXTRAS or OPTIONS, which is what makes it a test of persistence rather than a repeated observation. Reach stated rather than implied: every session ends with the emulator KILLED, so a game that writes menu state on a clean shutdown would never get the chance. This measures 'does not survive a killed session'. Refutation attempt on the port's extras/initial_focus: ptbtn11 -- it SURVIVES. ptbtn11 is the top button on the EXTRAS build, with the main menu as a control where ptbtn01 is top and is known to be NEW GAME. Incidentally corrects ring_row.py's stated calibration. It cited capture_y = 49.5 + 1.060*design_y, fitted against menu_focus.py's row centres, which are NOT the disc's button rows -- the disc says 162/242/322/401/482, spacing 80, and menu_focus.py drifts up to 17 px against them. Re-fitted: 64.82 + 0.9919*design_y, residuals under 0.7 px. No item assignment changes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v |
||
|
|
5e89f21f94 |
re: refutation attempt on the band-energy check -- survives, with the margin quantified
A 1 s misalignment costs 0.16 dB against their 0.66 dB pass band, so the alignment-free claim holds. Two caveats measured: at 10 s it reaches 1.00 dB, since a fixed window covers different material once the shift is large; and the separation floor is material-dependent -- two unrelated music banks give 5.28 dB where an unrelated movie gave them 19-20, so the threshold's margin depends on the known-negative chosen. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v |
||
|
|
dcfb53bc9b |
re: a bank's wave 1 is not a filtered copy of wave 0 -- and my own discriminator cannot finish the job
Coherence on BGM_103, the menu's bank, with controls run first: a real linear filter of wave 0 reads 0.93-0.94 in every band, a different bank reads 0.001, and wave 0 misaligned by 1 s reads 0.004-0.057. The measurement reads 0.027 at 1-4 kHz, so the 'wave 1 is wave 0 filtered' model is refuted. The frequency structure is inverted relative to any mic-pair or reverb model: coherence rises with frequency (0.169 -> 0.827) while energy falls (71 % -> 0.2 %), and a rear pair decorrelates fastest at HF. In the midrange the two waves are 13x further apart than the two channels of one wave. But the L-R control is what limits the tool and it is recorded as such: within one wave, genuinely one performance in two channels, coherence is only 0.221-0.497. So 'same performance' does not imply high coherence here, my positive control was the wrong model of the rear-pair reading, and the 🟡 is NOT settled. The tool tests for linear filtering and neither surviving reading requires it. Also corrects MISSION's Q10 row, which still carried the refuted three-sub-wave premise and had directed work at a dead question for days. Its gate is in fact met. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v |
||
|
|
81e84d6ec6 |
re: the refuted register ignored the deaths I just wrote, and two false positives shared a cause
Three refutations written as prose under ### headings never entered the register:
check_refuted.py parses * "claim" lines, so the count stayed at 188. Registered
them properly (188 -> 192). A register that parses one syntax silently ignores
every other, and it is invisible from the author's side -- ask the register what it
holds, do not re-read what you wrote.
Both standing false positives were bullets under a header that retracts the whole
list, with no marker in the +-4-line window: scope marks them, not proximity. The
scan now includes the nearest preceding header and matches markers
case-insensitively ('An earlier version' was missed by the marker 'an earlier
version'). Controlled by planting a real revival and confirming it is still caught;
register now runs clean at 0.
Also records sylpheed-port's diagnosis of the phase-lock fallout: a number can be
inapplicable rather than wrong, and a tension built on one is manufactured. Plus
their point that some claims are not registrable in a substring register at all.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
|
||
|
|
27ce59b3a7 |
re: the sweep IS drawn on the JP title -- and my gate was phase-locking the shutter
The occlusion hypothesis is refuted: build 7 draws the same three ROT strips at higher alpha than English, so there was never an absence to explain. The 0.32-vs-11.9 tension that motivated it was an artefact of my own instrument. Both JP captures were shuttered on the plate pulse, and the plate's pulse is part of the animation -- so the gate synchronises the shutter to the animation's phase. Measured at the shutter instant, the sweep sits 25-26 px apart across two runs in different locales and different sessions: 1.6 % of a ~1600 px traverse. So the 0.32 I recorded as between-session capture noise measures my trigger's repeatability, and I read it as evidence the title is still when it is evidence the gate works. The era adjudication is unaffected -- margin 16.72 clears even the un-locked 11.9 -- and unaffected for the reason that file already gave: correlated noise cancels in a margin. Refutation attempt on sylpheed-port's positional-mechanism rejection: FAILED, the claim stands. Its residual sits inside lit logos, and the logo ROI is byte-identical across five differently-phased frames in two sessions. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v |
||
|
|
9b25fd8233 |
re: eight refutations from this session had never reached REFUTED.md
sylpheed-port audited the documents their brief names and found two stale blockers in a table they are instructed to consult, having audited everything else. Mine names eight documents; I had audited MISSION.md and never PROTOCOL, REFUTED, INDEX or CONTAINER-NOTES. REFUTED.md is the dangerous one, because a wrongly-dead entry stops someone re-investigating something live. Checked the keyframe cluster first for the opposite failure -- entries refuted USING the stale time association, which would make their deaths unsound. They are sound: the additive-blend and pivot entries rest on scale values and capture measurements that the association does not move, and the one entry that did depend on it is already struck through. The real gap is the other direction. EIGHT claims died this session -- the fade-out duration "not in the file", the ~14 units as a black hold, the black interval as a load, "(B) has no black interval", ptloop01/02 not free-running, the splash dwells running 8.5 % long, EXTRAS's "structural" n=1, and the gap being determined by the outgoing screen. Every one was recorded in its own page at the time. NONE of them reached REFUTED.md, the file the brief says to grep before proposing anything. Added as a dated section with the true answer after each arrow, following the file's stated format, and each carrying what made it wrong rather than only that it was. METHOD: a refutation that lives only where it was made is not reachable by the person about to repeat it. The pages are where a refutation is argued; the index is where it is found -- the same split as docs versus tool, and only the second one saves anyone. The check is mechanical: after withdrawing a claim, grep REFUTED.md for its noun. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v |
||
|
|
5b0a6e6666 |
re: the plate delay was on the disc all along -- my instruction is refuted
The port caught this with arithmetic off the disc, and it was right: build 2 has a keyframe group of its own, so "when build 4 has settled, wait 2.13 s, then composite build 2" puts the plate at settle + 2.13 + 3.97 s. Confirmed build 2's group here independently of their message: `ptbtn00.t32` reaches a=255 at t=238. The reconciliation needs no free parameter. Both builds run on ONE clock, started together, and the premise that fails is `rest.t`: rest.t is NOT when a screen settles. It is the last HOLD keyframe before the exit. ptlogo1 rests at t=251 and stops moving at t=42. The title's visible build-in ends at t=118, where pteff01, pteff02.prm and ptlogoall_eff end their ramps together. 238 - 118 = 120 units = 2.000 s, against a measured 2.138 and 2.132. So the interval the two runs agreed on to 6 ms was a DECLARED one and I handed over a wall-clock reading of it. That reading is 6.7% long, and the corpus already knew why: 120 units in 2.135 s is the game presenting at 28.06 / 28.14 fps against a nominal 30, and the idle title was independently measured at 28.5 fps before these runs. Corroborated from inside the same two runs -- first pixels -> settle is 1.643 s and 2.131 s, a 30% spread, while settle -> plate is 2.138 and 2.132. Frames are dropped during the build-in, not during the hold, which a change in the game's own timing could not do. So the port authors nothing here. What is unchanged: ScreenView still has to draw two builds at once and the boot's end state is still not plate-free. Not settled, and said so on the page: which reading of the keyframe times is right (it moves the plate by 2 units and I cannot separate them from these traces), and my settle landmark to better than +/-5 units. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014voBspJ6kFncNErZJuZcLw |
||
|
|
9a10258738 |
re: the main menu has no idle self-return -- and four of my own durations were a backlog
Two results and one retraction, all from the same session. REFUTED: 'an ~8-10 s idle returns to the title' does not apply to the main menu. Held untouched it stayed put for >= 60 s, correlation never leaving 0.9245-0.9249. That timer is real but belongs to the TITLE. It was the only reason 'B leaves the main menu' was classed as authored, so Q5's B rule is upgraded to measured-ordering: B is delivered (canary logs vk=5801) and is the only input in >= 100 s before the return. The PRESS (A) plate: the boot title presents build 4 WITHOUT the plate first -- green-glyph 154, against 159 on the committed no-plate capture and 753/977/1493 on plate titles -- and the plate arrives after. That is the port's third option. RETRACTED: four durations taken the same day. classify_array costs 1503 ms per frame; running it per frame against an 8 fps x11grab drained the pipe at 0.64 fps, so every classified frame was stale and increasingly so. It manufactured 'plate 24.66 s after the title art', 'B->title 15.58 s', 'B->title 25.60 s' and 'A->menu 20.26 s'. The tell: a transition, a press and a fade do not share a duration, and the two B figures GREW across a longer run. A backlog preserves ordering and destroys durations, which is why the sequence results above stand and every timing does not. The ring's period is unaffected and that was checked, not assumed -- ring_period ran at 15.03 fps against a requested 15. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KNR5Y79D1T4bBr6gJQaWFP |
||
|
|
56cc7acfc3 |
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. |
||
|
|
7a4e4333f4 |
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.
|
||
|
|
2532c056be |
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. |
||
|
|
7156591654 |
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. |
||
|
|
2a086051b3 |
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.
|
||
|
|
baed44a9ea |
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. |
||
|
|
7fb6bdfad8 |
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. |
||
|
|
1f2b469f7e |
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. |
||
|
|
4012d5b555 |
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. |
||
|
|
93e9b185ea |
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.
|
||
|
|
0265da31a1 |
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.
|
||
|
|
60285a6ead |
re(ui): replicate the keyframe-time shift -- three elements, two screens
The case for reading +36 as "the time the NEXT pose is reached" rested on one element's fade-out shape, then on one element's hold duration. Both splash halves supply more, and they agree. element screen observed hold as decoded shifted palogo_gamearts developer splash 83 f 8 f 80 f palogo_seta developer splash 83 f 6 f 80 f palogo_sqex publisher splash >=77 f * 6 f 102 f * the capture opens mid-hold at frame 1, so 77 is a floor. The readings predict opposite structures. For palogo_gamearts, as decoded: hold 8f, in 80f, hold 2f, out 6f, out 2f -- an eighty-frame FADE-IN and a two-frame hold. Shifted: in 8f, hold 80f, out 2f, out 6f, out 2f. The capture shows an 83-frame hold and no fade-in at all. The elements that cannot discriminate are not contradicted: palogo_gamearts_eff observed in 7f / hold 7f / out 8f, and both readings give 8f phases -- with four blocks the shift only relabels which phase is which. So the glows, which is where Q1's linear law was measured, say nothing either way rather than arguing against. The decoder's default is still unchanged, and the reason is now articulated rather than assumed. The single thing opposing the shift is rest() on ptlogo_eff3, where the shifted reading makes the longest-dwell fallback return the bloom's 200% peak. That fallback is unsound whenever it runs -- it returns an endpoint of a movement, neither of which is held -- and checked: the shift does not fix it either. So the objection was never evidence about the times. Timing had three discriminating measurements; pose selection had a heuristic guessing. For the port: animation timing should use the shift; static composites are unaffected and the five screens' correlations stand. Classified measured, not decoded -- three elements in one screen family, not a disc-wide check. |
||
|
|
0efd692b4c |
docs: the suite is heavy, not hung -- correcting my own "cannot terminate"
Last iteration I wrote that build-reborn test cannot finish in a working session, from having watched it run 3h26m. That was the stronger claim and I made it without measuring the work. Timing `mesh info` on each of the 166 .xpr containers with a 25 s cap: files scanned 166 exceeding 25 s 19 Hangar, 17 Stage_*, ptc_pack Stage_S02 to completion 144 s, rc = 0 Nothing hangs. Nineteen heavy containers at roughly two minutes each is about 45-60 minutes for one pass, before the 147 fast ones. The 3h26m observed was that hour of work running at a load average of 9-14 -- inflated by the two duplicate runs I had left going, which did not merely coexist with the slowness but multiplied it. The practical conclusion is unchanged and only the wording softens: an hour-scale suite is not an iteration-scale gate, and every "green" I reported from it this session was partial. But an hour-scale gate can be run deliberately, whereas a hung one cannot be run at all, so the distinction is worth having right. File list committed as reference data so the cost is attributable without re-scanning. METHOD: a slow thing observed under contention looks like a stuck thing; measure the work before choosing between "cannot finish" and "takes an hour". |
||
|
|
714a26769d |
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. |
||
|
|
489ea12759 |
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.
|
||
|
|
5f701db588 |
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.
|
||
|
|
c463f98164 |
re: the GPU trace is compiled out of the release build -- both my guesses wrong
Last iteration left two candidates for why trace_gpu_stream produced no
file: the CLI flag not reaching the cvar, or BeginTracing failing
silently. Neither. Following the code instead of guessing:
BeginTracing only sets trace_state_ = kStreaming ("Streaming starts on
the next primary buffer execute"). The file is opened later, in
ExecutePrimaryBuffer, inside
#if XE_ENABLE_TRACE_WRITER_INSTRUMENTATION == 1
and trace_writer.h defines that as 0 under NDEBUG, 1 otherwise -- the
trace writer exists only in debug builds.
Confirmed against the binaries, with a control. The format string
"{:08X}_stream.xtr" lives only inside that guard:
build/bin/Linux/Release/xenia_canary 0 occurrences
build/bin/Linux/Debug/xenia_canary 1 occurrence
/sylph-home/re/canary-build/.../Release/... 0 <- what run-canary uses
The debug binary is the control: it proves the test finds the string when
it is present, so the release zero means something.
So trace_gpu_stream is a no-op in this container's emulator -- the cvar
parses, BeginTracing runs, and nothing can open a file. The kill -9 was
not the cause either, though it would have destroyed a trace had one
existed.
The route exists but is not cheap: a debug build with the writer compiled
in sits at build/bin/Linux/Debug/xenia_canary, 253 MB against Release's
18 MB, so a much slower boot plus a trace of every GPU packet on a disk
at 95%. Recorded as available rather than attempted -- what it would
confirm, the DC_LUT write, is already a well-supported inference, and the
cost is out of proportion to the gain.
METHOD: a cvar existing does not mean the feature is compiled in; and
test a compile-time gate against the binary, with a control.
|
||
|
|
93b0a6b20f |
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. |
||
|
|
3490fba9e3 |
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. |
||
|
|
7373035868 |
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.
|
||
|
|
f7f9b555f6 |
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.
|
||
|
|
46006406a4 |
re: the fast probe stalls -- its own dense negatives are withdrawn
Cross-checked the instrument built last iteration against an independent grabber while both watched the same screen, and it fails. A single long-lived ffmpeg x11grab stream degrades and then freezes: 862 frames in 540.1 s = 1.60 fps (it starts at 3.98) t=450/480/510/540 s: surface mean 5.21, identical every time At that same moment `import` read surface mean 125.65, and a freshly started ffmpeg stream read 122.43 -- agreeing with import to 3%. So the acquisition was broken, not the analysis: the stream replayed a stale frame while the screen was 24x brighter. That withdraws last iteration's headline. "2391 frames over 600 s from t=0, max glyph 0" cannot distinguish "the title never appeared" from "the stream froze early and repeated one frame 2391 times". Its 3.98 fps was measured over the first 20 s, before the degradation. Sample count is not coverage unless the samples are known independent. Fixed: the stream is now torn down and restarted every 30 s. Startup is ~0.3 s, cheap against the title's window, and it guarantees live frames. Separately, the cache hypothesis was tested and is SUPPORTED. cache, cache0, cache1, cache_host moved aside (to /tmp/xenia-cache-aside, not deleted) and the surface renders again: import reads mean 54.8 and 68.6 with 100% non-black warm content, against 0.07 and 0.08% non-black in the black run; 773 of 862 probe frames had >2% non-black. One run each side and many kill -9s before the black one, so it is supported, not proven -- the old caches are kept for reproduction. Still no title, but that number now comes from a stalling probe and establishes nothing either way. METHOD: validating a probe on static images tests its analysis, not its acquisition -- cross-check against an independent grabber during a run. |
||
|
|
a3946e5005 |
re: the game surface is rendering BLACK -- check that before explaining absences
The named experiment was to attach the fast probe at t=0 so the boot title could not be missed. Done, default config, English: 2391 frames in 600.4 s = 3.98 fps; max glyph 0; hits 0 Ten minutes sampled four times a second FROM LAUNCH, no green-(A) glyph. So "the plate only shows in an early boot window I keep missing" is mine and refuted -- the third explanation refuted in three iterations. Then the check that should have come first. Splitting the raw root grab into bands: y 0- 44 (GTK menu bar) 100.00% non-black mean 210.50 y 45-719 (game surface) 0.08% non-black mean 0.07 The game is rendering black, reproducibly across back-to-back samples, while the guest is alive and polling input (XamInputGetKeystrokeEx past 1201 calls) and MEM-WATCH keeps reporting. The crop and every pixel oracle were correct; there was nothing on the surface to detect. What this does NOT do is retroactively explain the earlier failures, and claiming so would be the fourth over-reach in a row. Those runs had content: run 2 sampled mean 33.1, run 3's classifier measured real frame-to-frame rmse, the gamma_type=0 run measured mean 122.8. The failure mode CHANGED over the session; black is the newest and worst. Hypothesis for the regression, untested: canary's shader/pipeline cache is 47 MB and was last written 23:49 on Aug 28, during the failed runs, and this session has kill -9'd the emulator repeatedly. The test is to move cache* aside and boot again -- one line and one run, not done. Two METHOD lines: ask whether the screen is drawing anything before explaining why a feature of it is missing; and a newly found fault does not retroactively explain older failures. |
||
|
|
e9924ff9e8 |
re: build the fast probe -- and it refutes the diagnosis that motivated it
Last iteration I blamed four failed runs on the probe sampling every ~41 s, slower than the title screen lasts, and withdrew three earlier conclusions on that basis. Building the fix tested the claim and killed it. The speedup is real and control-verified. One long-lived ffmpeg x11grab stream, raw RGB, glyph counted in numpy -- no per-sample process startup, no PNG encode, no convert -crop: wrapper `screenshot` 3.98 s per sample (emulator running) import -window root -> PPM 1.20 s long-lived x11grab stream 0.29 s 13.7x The counter is byte-identical to is_title.py: 753 on the committed title capture, 327 on the main menu. Pointed at a running game it says the opposite of what I expected: 332 frames in 85.3 s = 3.89 fps; max glyph 0 1674 frames in 420.0 s = 3.99 fps; max glyph 0 1674 consecutive samples over seven unbroken minutes, four per second, zero green-(A) pixels. Sampling rate was a real defect that happened not to be the cause. So "neither locale reaches the interactive title without a pad press" -- withdrawn last iteration for want of evidence -- is reinstated, now as a dense measurement, with its reach stated: a MID-RUN window only, silent about the boot title. Leading hypothesis, unconfirmed: the PRESS (A) plate appears only in the boot title window and the attract loop's title carries none, which is exactly what title_states_capture.sh was written to test. The experiment is to start the fast probe from t=0 rather than attach to a run already in progress. METHOD: fixing the instrument is how you test the explanation that blamed it -- a plausible mechanism is a hypothesis, and the fix is its experiment, not its proof. |
||
|
|
cc4e5e04a7 |
re: the boot harness was blinking slower than the title -- diagnosed
Four consecutive runs failed to reach the interactive title, across two locales, two launch paths and two display-gamma settings. I attributed it in turn to a stale oracle, to the locale, and to the attract loop. It was none of those. one `screenshot` call, emulator running: 10.8 s one `screenshot` call, emulator killed: 0.117 s 92x, measured at a 1-minute load average of 1.80 -- so it is contention with the emulator through the X server, not background load. skip_intro.sh takes two grabs per iteration plus a numpy import, giving a median sampling interval of 41 s in the last run (38/82/41/20/30/30/35/ 47/46/45/44/43/21/19). The title lasts "a few seconds" before the attract loop reclaims it -- wait_title.sh's own header says so. The harness was sampling slower than the event it was waiting for. That also explains why runs at 16:43-18:05 the same day succeeded. Withdrawn as CAUSES, though the observations stand: "the JP run never reaches the interactive title", "neither locale reaches it without a pad press", and "the game sat in the attract loop for 604 s". The English control did control for locale -- it just shared the same defect. Also recorded: I set kernel_display_gamma_type = 0 for the gamma control run, which brightens the frame (mid-attract mean 122.8 vs 52.5/82.8 at type 2) -- and skip_intro classifies movie-vs-static on an ABSOLUTE rmse threshold, so the gamma change biased the very classifier the run depended on. Changing a display setting and a capture behaviour in one run confounds both. Config restored to type 2. The fix is not applied: make the probe cheap enough to outpace the title window (small region, no convert round trip, one long-lived process). Every remaining emulator-side question is waiting on that. |
||
|
|
8783adda14 |
re: the English control removes the locale from the title-capture problem
Two iterations framed the JP title capture as possibly locale-specific. It is not. Same flags, same oracle, English locale: 75 samples over 734 s, every one glyph = 0. The English boot does not present the interactive title either. Canary was alive throughout, polling XamInputGetKeystrokeEx (1801 calls); the frame at 734 s has content but correlates 0.15 with build 4's render and 0.05 with the main menu -- an attract-movie frame, not title art. So neither locale reaches the interactive title in ~12 minutes without a pad press. What is actually untested is the pad: pad.py and nav_to_flight.sh exist, and title_states_capture.sh reaches title states with a flag set this probe did not replicate (--log_ui_draws --ui_draw_capture_frames, plus xdotool window focus). It is a capture problem, not a locale one. MISSION.md updated. Also resolved a caveat I had given the port agent without checking it: "rotation is decoded but not rendered" does NOT affect their five screens at rest. Title, main menu, EXTRAS and both splash halves have ZERO top-level elements with a non-zero rotation. The only rotations on any of them are the title's two nested ptloop records (r = 30 and -45), and at rest those sit at x = 1521 and x = -839 -- a 399-wide sprite entirely off both edges of a 1280 screen. The caveat now applies only to animating the title build-in, where the sweeps cross the screen rotated. Two METHOD lines: run the control before theorising about the difference, and log every sample so a failure is a measurement rather than a silence. Emulator stopped, lock cleared, locale English. |
||
|
|
f10edf1e79 |
re: fix wait_title.sh's stale oracle; the JP title still is not reached
Last iteration's "never reached the title in 787 s" was a broken tool reporting on the world. wait_title.sh was still sampling the single pixel (625,618) that is_title.py had already been written to replace -- its docstring says why: a 1280x720 coordinate sampled against the 1279x675 game surface, so it always reads the copyright line. The replacement sat in the same directory. wait_title.sh now delegates to it. is_title.py passes its own controls before being trusted here: 753 green-glyph pixels on the committed English title capture, 327 on the main menu, threshold 400. Re-ran with the working oracle and the profile flag the English captures use. The game STILL did not present the interactive title -- but that is now a measurement rather than an artefact: not one frame showed a single green-(A) glyph pixel, and content correlation against either build-7 render never exceeded 0.22. Canary was alive and polling XamInputGetKeystrokeEx (601 calls), sitting in the attract movie. So the open question narrowed again, and is written into MISSION.md: whether the attract loop returns to the INTERACTIVE title without a pad press. title_states_capture.sh claims it does on the English boot with no pad input; if that holds, the difference is the locale. Nothing decided about the keyframe-time association or the rest() rule. Emulator stopped, lock cleared, locale restored to English. |
||
|
|
0f1c0f4e14 |
re: the JP-locale capture is not blocked -- I stopped one grep too early
Last iteration I wrote into MISSION.md that a Japanese-locale capture is impossible here, because user_language is DECLARE_int32 at four call sites with no DEFINE and no entry in xenia-canary.config.toml. That is true, and it was not the question. The language is PERSISTED: kernel_state.cc builds XConfig over <storage_root>/xconfig.settings, SetDefaults() only supplies a value when the file has none, and the file is writable. Checking where a setting is stored rather than where it is configured turned "blocked, needs a human decision" into a two-line edit. Withdrawn from MISSION.md; METHOD and REFUTED lines added. The field is located from struct landmarks rather than a hard-coded offset, and the check re-runs on every invocation so it fails loudly if the layout moves: music_volume 0.7f at User+449 -> BE float at 2727 -> User base 0x8e6 language at User+44 -> reads 1 (kEnglish) at 0x912 country at User+64 -> reads 103 (US) at 0x926 XLanguage::kJapanese = 2 (xbox.h:307). set_console_language.py wraps it with a backup and a --restore. The capture itself is still NOT taken, for a smaller reason than I claimed. A run with the locale set to Japanese booted fine but never reached the title in 787 s: wait_title.sh's green-(A) oracle never fired and burst-sampling found no frame correlating above 0.18 with either build-7 render -- the run sat in the attract loop. So it needs a longer or pad-driven run, not a rebuilt emulator. Emulator stopped, lock cleared, locale restored to English. Nothing is decided about the keyframe-time association or the rest() rule; this only changes what standing between us and deciding them. |
||
|
|
b973d8cde3 |
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.
|
||
|
|
c2c69b02be |
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.
|
||
|
|
b947fa8e8e |
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).
|
||
|
|
6f23f4d113 |
re(ui): the keyframe-time shift is favoured 26x by timing, rejected by a render
Follow-up on last iteration's unadopted candidate (+36 holds the NEXT pose's time, not its own). Two new results, pointing opposite ways, and both are reported. FOR, and calibration-free: the observed full-alpha hold : fade-out ratio on palogo_gamearts is 83 : 13 frames = 6.38. The shifted reading predicts 8.00. The current reading predicts 0.25 -- off by 26x. With the glow's 2 units/frame fixed and nothing else free, the current reading says the logo holds full alpha for 2.0 frames; the capture holds it for 83. This is no longer the shape argument the candidate rested on. Also for: rest()'s plain max-dwell fallback picks a=0 -- a transparent pose, for a publisher logo -- under the current reading, and the visible a=255 hold under the shift. Only the rest_plateau special case rescues the render today, and that is the case the port agent reported a bug in. AGAINST: rendering every build of six UI paks under both readings, 10 of 11 compared are byte-identical and one changes -- GP_TITLE build 7, the Japanese twin of build 4, by 13.1% of pixels. Build 4, the one verified against a live capture, is unchanged either way, so the single build the shift moves is the one with no capture to adjudicate it. The proxy goes against the shift: language twins are the same artwork, and build 7 reads 70.94 mean luminance as decoded against build 4's 71.41, but 76.32 shifted. Correlation does not separate them (0.6206 vs 0.6201). These constrain different things -- timing versus pose selection -- and rest() is a heuristic layered on the times, so moving the times moves its tie-breaks. Adopting the shift means revisiting that heuristic in the same change, with no build-7 capture to verify against. Default UNCHANGED. Experiment reachable via SYLPHEED_KF_TIME_SHIFT=1. Shifted-mode suite: 122 passed, 0 failed across 3 suites at commit time. |
||
|
|
531840b417 |
re(ui): Q1 -- the interpolation law holds, the group timeline does not
Q1's gate asks whether the ramp is linear. It is, and that result stands: it rests on the splash's _eff glows, which reproduce exactly. This adds the part that does not. The test is a calibration, not a fit. Fix the clock on palogo_gamearts_eff -- declared 15-unit fade-in 0@15 -> 255@30 against captured alphas 34,68,102,136,170,204,238, a constant step of 34, giving t = 2f - 171 -- then check that against the glow's own next landmark: its declared hold ends t=45, predicted frame 108.0, observed last full-alpha frame 107. Then apply it to palogo_gamearts in the same bundle and the same frames, with no free parameter left: declared a=232 at t=206 -> frame 188.5, observed alpha 255 declared a= 32 at t=210 -> frame 190.5, observed alpha 255 The logo is still at full alpha nine frames after it should read 32; its fade-out runs ~17 frames late; its declared 80-frame fade-in is never drawn. Not culling -- the same element is submitted down to a=7 on the way out. Calibration-free version: the declared fade-out spends 12 of 16 units dropping 23/255 of the alpha, and the capture has no such plateau. Candidate, offered and NOT adopted: if +36 held the NEXT keyframe's time, the fade-out shape fits (RMS 4.05 vs 12.13, two elements) and the decoder's "last block's time is unreadable" special case disappears -- the last block would simply have no successor. Rejected for now because it explains neither the missing fade-in nor the lateness, and because the _eff elements cannot discriminate between the readings at all (with four blocks the shift only relabels the phases). Decoder unchanged. Also withdrawn, mine, within the iteration: "the _eff glows hold a constant alpha 33". They ramp 34 -> 255 in steps of 34. I printed the series minimum and read it as its range, with a "14 distinct colours" column sitting next to it saying otherwise. |
||
|
|
f11fde51e5 |
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. |
||
|
|
67fa1a1b0b |
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. |
||
|
|
130ad8d1b3 |
re: the skewed draw is not the swoosh -- refuting the identification five
iterations of work were built on I flagged last iteration that "draw 2 is the swoosh" was an inference I had never checked. Checking it took one line, and it is wrong. Converting the quads from NDC to screen space: quad A spans y -209 to 925, quad B spans y -292 to 1012. Both cover the full screen height and run well off it. The swoosh, ptlogo_back2, rests at (71,126) and is a 234-pixel band at y 126 to 360. Draw 2 is not it. A candidate offered as one: the two ptloop sweeps. pteff03a is 399x180 with element scale (100,800), so 399x1440, and pteff03 carries (100,600) -- two long thin sprites, two quads, and the drawn vertex alphas 0xC3 and 0xB6 sit inside the 0x80 to 0xff ramp those records declare. Not confirmed; no texture or position match was made. If that is right it matters, and I have written it conditionally rather than acting on it: our renderer parks those sweeps at their final keyframe, both off screen, and draws nothing, while the game draws them across the screen. That would mean the capture caught them mid-sweep inside their t=150 to 600 window, and that the "groups hold" reading needs re-examining for those two elements. What this cost is worth stating plainly. Five iterations -- the additive blend test, the pivot analysis, the vertex-colour capture -- were built on an identification made by elimination on one screen and never checked against the draw's own coordinates. The eliminations themselves stand, because each was measured against the capture rather than against the identification. The chain that pointed them at ptlogo_back2 did not. METHOD takes the general form. |
||
|
|
5115b4894a |
re: three angle fields found in the keyframe -- and they are not the
title's rotation Looking for where the rotated quads come from, the obvious candidates were the three keyframe words at +4, +8 and +12 that ui_layout.rs documents as zero. They are not zero. Across 72287 keyframe blocks disc-wide they are non-zero in 4.81, 4.56 and 15.82 percent of blocks, and read as signed values clustering on 180, -180, 90, -90, 120 and 22 -- degrees. Three of them, so plausibly rotation about three axes. I have marked that amber because it is the shape of the numbers and nothing more; no observed rotation has been tied to a value. The doc comment is corrected regardless: "0 on every frame seen" was an artefact of the sample. And they do not explain the screen I was chasing. Every element of GP_TITLE build 4 has all three at zero, element by element, while the game demonstrably submits rotated parallelograms there. So the title's rotation comes from outside the keyframe data and is still unidentified. One correction to my own last write-up, flagged rather than left: I stated that the skewed draw IS the swoosh. It is the only skewed geometry in the capture and the swoosh is the only diagonal element on the screen, so the inference is reasonable -- but I never confirmed it by matching the draw's texture or screen position to that element, and I should have said so the first time. |
||
|
|
0ecb20bdbc |
re: the swoosh is solved -- the game draws it as rotated quads and our
blit cannot Ran the route I corrected last iteration, and it answered the question I had classified as undecodable. First the ninth candidate died: every vertex colour in the whole capture is <alpha>FFFFFF, white RGB with only alpha varying. The game passes no colour, so that hypothesis is refuted from the running game rather than from the file. Then the geometry gave it away. Draw 2 submits TWO PARALLELOGRAMS, neither axis-aligned -- edges (0.54,-0.56) and (0.44,0.79), roughly 45 and 61 degrees, both extending to y=+/-1.81 in NDC, well off screen. That is the diagonal Z stroke. And ui_layout::blit walks rows and columns of an axis-aligned rectangle; it has no rotation at all. So we blit the sprite upright where the game draws it skewed, which is exactly the signature I measured: right on average at +1.83, right in position with the correlation peaking at zero shift, wrong in structure at 0.70. Nine candidates, and the answer was never going to be a field, because the difference is not in the sprite -- it is in how the quad is built. Two things left open and written as such. The decoded keyframe carries fade, scale, tint, x, y and time and NO rotation, so where the rotation comes from is not decoded. And the pink-versus-white reading I have been carrying was a visual comparison of two differently-shaped renderings; it should be re-checked after geometry rather than treated as a separate defect. |