3dbfa320aed9851094f880306d976ee8eb8afcf3
167 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
3dbfa320ae |
formats: drop the 1.5 MB cap that truncated 17 voice regions' first stream
The cause, and the fix, with a disc-wide check. resolve_movie_voice_region picks start = the predecessor cue's trailer, then filtered it with 'end - s < 1_500_000' -- 'only within one bank'. ADV's predecessor sits 3 618 816 B before end, so the filter rejected it and start fell back to anchor, which is a TOC offset and not a stream boundary. That explains the shape of the defect exactly: it strikes regions larger than 1.5 MB, which is why the three-stream multichannel regions are hit and single-stream ones never are. 17 of 95 resolving movies took the fallback. ADV's predecessor trailer at 433 425 776 plus 17 040 B of descriptor and padding is 433 442 816 -- the -238-packet start measured against the decoder, to the byte. Dropping the cap: unchanged 78, fixed cleanly 17, changed in any other way ZERO. In all 17 the only difference is a larger first chunk with every later chunk byte-identical, which is what a corrected start looks like and what pulling in a neighbouring asset does not. Regression test pinned to the RUNNING DECODER's byte_sizes rather than to this crate's own output. That is the point of it: every internal check passed happily while a third of a stream was missing, so only an external number could have caught this class of bug. sylpheed-formats: 136 tests pass, 0 fail (the one still running at commit time is an unrelated long mesh test). Exact clips for the other 16 are not independently verified -- the sweep is strong but ADV is the only one with a decoder measurement behind it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v |
||
|
|
3e8235ddbd |
re: resolve_movie_voice_region starts INSIDE the first stream, 8 of 10 multichannel regions
Found because the port refused to apply my stream assignment and did the arithmetic instead: the running decoder's three ADV contexts sum to 3 584 000 B against a resolved region of 3 114 352 -- 15 % too small to hold them. Two spans, one wrong, and it was the disc side. The gap is 238 packets exactly (487 424 B), which is what a start offset looks like; ctx0 declares 632 packets and the resolver's leading chunk has 394. Verified against the decoder's own byte_sizes, which cannot be fitted to: at -238 packets to_xma_riffs yields [1294336, 1118208, 1171456], all three exactly. It is a real boundary and not the end of a sweep -- at -300 the previous asset's chunks appear while the three ADV sizes stay stable. Disc-wide: 24 of 24 single-chunk regions start at a boundary; 8 of 10 three-chunk regions start mid-stream. The defect is specific to the multichannel case. The audit's per-movie number is an UPPER BOUND, not the clip -- its stopping rule is the chunk count changing, and to_xma_riffs absorbs a few packets of the previous asset first (243 reported for ADV against a true 238). Only ADV has external ground truth. Consequence: in those 8 movies the leading chunk is a truncated first stream, not a spurious artefact, and anything measured on it was measured on a fragment -- including this corpus's own chunk-0 level, though the assignment survives because its ratio test was chosen to be immune to the clipping. The resolver is NOT patched. Why the predecessor cue's trailer lands 238 packets into the next asset is unanswered, and a fix guessed from one movie would be worse than a documented defect. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v |
||
|
|
8c71520ab8 |
re: which ADV stream sits where -- settled by level, not by waveform
Completes ask #4. The three chunks were dumped from the resolved voice region and decoded; the assignment is ctx0 -> FL/FR, ctx1 -> FC with LFE silent, ctx2 -> BL/BR. Two instruments failed first and both look like results, so both are recorded. Envelope correlation with a per-pair lag search returns 0.86-0.95 for EVERY chunk against EVERY channel, because all six residual channels share the dialogue's activity timing -- that is an instrument with no resolving power, not a finding. Sample-level correlation returns about zero, because the chunks do not start with the movie and the XMA decode's framing offset is unknown. Level settles it under the same 0.600 gain the bed uses: each stream lands within 0.5 dB of exactly one residual pair and misses the others by 4-6 dB. The ratio test is immune to chunk 0 being a clipped tail of ctx0 -- chunk0 - chunk2 is +5.88 dB against FL - BL at +6.18 dB, agreeing to 0.30 dB, where a swap would be wrong by 11.76 dB. Structural confirmation: chunk 1 is the only chunk with a digitally silent channel and LFE is the only output channel with an empty residual (-115.73 dBFS), one to one; and the internal L/R correlations track the residual pairs' (0.932 vs 0.918, 0.962 vs 0.929). Worth having on its own: the same 0.600 scales both the movie bed and the voice, so it is one mixer gain rather than two. Reach: levels, not waveforms; one boot, one movie; and whether 0.600 is a fixed constant or a volume setting is still unknown. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v |
||
|
|
232ae0fa9c |
re: ask #4 answered -- the intro is a 5.1 WMAPro bed at 0.600 plus three streams
ADV.wmv carries ONE audio stream and it is wmapro 5.1, not XMA. Any framing of the intro's audio as only 'which of three voice streams to ship' was missing the bed. Aligned the 148 s capture against that track (envelope r 0.769 against a median of -0.001, refined to +224 samples, r 0.900) and solved capture = g x movie + residual per channel. The gain is 0.600 on every channel -- a uniform -4.44 dB, a mixer setting rather than a fit artefact. LFE reproduces to -115.73 dBFS, 72 dB down, which is what rules out codec difference as the explanation for the other residuals. FC is the exception: the movie explains NOTHING of it (-0.09 dB), and the movie's own FC is 91.6 % silent. The residual is three signals, not one: a front pair (r 0.918), a rear pair (r 0.929), and a centre whose partner LFE is empty. The FC residual spans 34 dB across 100 ms frames -- bursty, not steady noise. That CONFIRMS the corpus's 5.1 reading, which voice-three-streams-are-concurrent recorded as not established, and it confirms the specific detail it offered: that the mono-in-stereo stream is 'a centre paired with a silent LFE'. Measured from the output with no access to the stream contents. Also corrects my own census page: it labelled channels with the ALSA permutation [0,1,4,5,2,3] from the recipe page, which does NOT apply to this capture. The 6x6 matrix was computed assuming no order, every row's max falls on a distinct movie channel, and the answer is the identity -- so the census's 'BR is 82 % silent' was really LFE, reconciling with the movie's own 80.64 % silent LFE. Reach: one boot, one movie; which XMA context is front/centre/rear is not determined, only that the residual occupies those positions; and whether 0.600 is a fixed constant or a volume setting is unknown. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v |
||
|
|
ef4ea8fe86 |
re: the boot intro's audio output -- five live channels, not a stereo mix
Groundwork for the port's ask #4; it does not settle #4. Captured the game's own output over the boot intro following the ALSA file-tee recipe exactly -- paced pulse slave, --gpu=null, both mutes off. 148.02 s, 6ch float32 48 kHz, 0.15-0.16 % silence against the 0.31 % the recipe page records for its own clean run. Provenance is the XMA probe rather than a screenshot, which is the right evidence for an audio question: ADV's three contexts appear byte-exact (1294336 / 1118208 / 1171456), then the documented BGM_102 pair. Five of the six channels carry distinct content; BR is 82 % silent and 11-15 dB down. No channel is a copy of another -- the largest pairwise correlation is 0.70 between FL and FR. That rules out a stereo mix, so 'ship one stream' cannot be right and the port's held-wrong value stays wrong. It does NOT establish that summing is right, and the 6-channel count is Xenia's hardcoded kFrameChannelsDefault -- what is evidence is that five of them differ, which a stereo guest cannot produce. NOT settled and named as such: the stream-to-channel mapping. The cross-correlation of each captured channel against each decoded ADV stream has not been run. One boot, one movie, and --gpu=null means no video cross-check. Raw is 170 MB and is not committed; sent over share to the port. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v |
||
|
|
e0de5c6f22 |
re: the A-press A/B is run -- signed-in profile, no swallow, menu opens
The debt from two iterations ago. Two boots, same binary and ISO, one A tap each, fired only after the plate's pulse had been seen for 12 consecutive samples. ARGV recorded per leg, because the config dump provably cannot say. leg A no profile flag 3811 swallow lines and climbing leg B --logged_profile_... 0 swallow lines, final glyph 327 = MAIN MENU 327 is the documented main-menu glyph count, reproduced by this instrument's own control, so leg B's press opened the menu. Capture committed. Leg A demonstrates the SWALLOW, not the crash: I stopped it at ~2.3 M swallowed calls because kernel tracing at log_level=3 was eating the 300 MB budget the crash dumps need. The fault itself remains measured once, historically. One run per leg. A void pair came first and is recorded, because it is why the detector is what it is. The first version fired on a single frame over a glyph threshold and hit the INTRO MOVIE -- green flashes of 1298..5433 lasting under a second -- about 6 s before the title, in both legs. The presses were real (each skipped the rest of the movie, which is Q9's behaviour) but the pair tested nothing. The fixed detector requires 12 consecutive in-band samples, and was replayed against the void runs' own series as its control: it declines the movie flash at 84.8/85.5 s and fires at 93.9/94.7 s inside the sustained pulse. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v |
||
|
|
3c004845fa |
re: the PRESS A plate PULSES -- measured, and it keeps an authored entry
Answers the port's ask #1, which it had flagged as the only one of its four that could delete an authored entry rather than confirm one. It confirms one. Held at the title with no input, the plate oscillates continuously: two windows in one boot, 58 s and 57 s, ~23 cycles each, no decay. Periods 2.530 and 2.540 s by upward mid-crossings -- 0.4 % apart. It never goes off. The plate-absent floor is 159 green pixels, measured on the committed live-title-build4-no-plate.png; the pulse bottoms at 714, 4.5x that. So the port's 'flash and nothing after', reasoned from ptbtn00 expiring at t=244, is wrong on the boot's end state -- ptbtn00f's 120-unit cycle is what runs. Instrument controls were run before it was pointed at anything unknown: the glyph counter reproduces the documented 753 on live-title-press-a.png and 327 on live-main-menu.png exactly. Two estimators, and only one replicates. Mid-crossings agree across the two windows to 0.4 %; a single-sinusoid least-squares fit does not (2.553 vs 2.413), because the waveform is fast-rise/slow-decay rather than sinusoidal -- its own r2 of 0.468 and 0.228 is the tell. Both were controlled on synthetic sinusoids at 2.24/2.55/3.10 s laid on the ACTUAL timestamps and recovered every one exactly, so neither is broken; one is misspecified. Recorded as such. The wall-clock is 13 % longer than the corpus's earlier 2.24 s mean. Same declared 120 units, different emulator pacing (x1.27 here against x1.12), so this corroborates 'author the units' rather than disturbing it. Reach stated: one boot; does not distinguish the boot title from an attract-loop title; and the glyph count is a thresholded pixel count, so 714/1520 is not an alpha ratio and no duty cycle can be read off it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v |
||
|
|
59f3ac6846 |
re: ask #2 -- t=357.7 came from a GPU draw capture, and a sweep cannot date a frame
The port asked whether the refined sweep fit t=357.7 was measured against live-title-build4-no-plate.png, because if so one of us is 42 units out. It was not. 357.7 was solved against title-draw-capture-vertex-colours.log, a GPU per-draw capture of the submitted vertex buffer -- four observables at once, two quad centres and two vertex alphas. No framebuffer, no PNG. The gap is not a fitting error either. Posing the leaves directly, t=400 misses the captured quads by +169.0 and -172.2 px. Probe control: it reproduces the page's published t=355 centres, 981 and 478, exactly. Refutation attempted and FAILED: I expected the port's fit to be minimised by the quad leaving the screen -- 'best fit' meaning 'draws least', the same shape as the .tbm control that could not fail. At t=400 quad B is fully on screen and quad A is 319 of 400 px. Their number is fitting something present and it survives. The real reason the two must differ is better than 'different frames'. The sweeps are nested records on a free-running loop and their cycles differ -- 600 and 720, read from the record header +0x08 -- while the top-level clock stops at settle. So two captures of the same settled title share a screen time and not a sweep phase, by construction. Consequence for the port: a sweep position does not date a frame; it gives a phase on a 600- or 720-unit loop. And 357.7 is a joint fit over both leaves while the port's ~400 poses one, so the two are not comparable in kind -- the phases coincide only every LCM 3 600 units. Discriminator handed to the port rather than taken: if its ~400 is pteff03 and the frame is in the first cycle, pteff03a must be at centre 295 in that same frame. The fit is against its renderer, so it owns the check. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v |
||
|
|
bec7f02c0a |
re: not ONE of the 80 forced instances has a file-read key
The port agent pointed out that forced_backdrop_necessity.rs collapsed
sprite_layer_key (a u16 read from the T8aD header, decoded) with
implied_layer_key (this crate's table of positions MEASURED in the running
game), and that 'has its own key' therefore reads as file-backed when it is
not. Splitting them is stronger than either of us stated:
read from the T8aD header: 0
implied (measured): 14 10x pfbase.tbm, 4x palogo_eff0.prm
nothing at all: 66 62 decided, 4 inert
Zero. There is no instance on the disc where a forced element also carries a
file-read layer key, so this rule has never been checked against a decoded
field -- there is no case where both can speak. That is what a keyless-element
fallback necessarily looks like, but it removes a check a reader would assume
exists.
Also corrects something I said to the port and had wrong. 'None of the 18 is
evidence for the rule in any direction' conflated two questions. Whether the
rule changes the composite: no, the sort already had the key. Whether the rule
gets the RIGHT answer: yes, and the 14 implied keys are measured positions, so
this is the rule agreeing with the oracle -- its only external corroboration,
and there are 14 instances of it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
|
||
|
|
f8abb56bc4 |
re: reconcile the two ink counts -- we agree at >1 and not at >0
The port agent produced a genuine second witness for the pixel-cost claim: it
re-checked GP_TITLE entry 12 in Godot, which shares no code with compose,
swapping only paint_order. 59 530 px ink with the rule, exactly 0 without it.
The strong form -- the screen ceasing to exist, not merely changing a lot --
now has two real renderers behind it on that entry.
Its figures did not match ours, so I counted the same composite every way:
RGB > 0 ours 49 771 Godot 59 530 16 % apart
RGB > 1 ours 48 043 Godot 48 368 0.68 % apart
The entire disagreement lives in pixels whose value is exactly 1. That is a
1-LSB sampling artefact between two samplers, not a different set of inked
pixels. So '>0' is not a portable ink convention between renderers on a
mostly-dark frame and '>1' is; any future cross-renderer ink figure should say
which it used.
Also worth recording: our 49 771 was never a threshold figure. It is exact RGBA
inequality between the two paint orders, which over a black backdrop coincides
with ink>0 -- so it belongs against the port's 59 530, not its 48 368. Matching
it to the 48 368 would have made the two renderers look like they agreed for
the wrong reason.
The without-the-rule column is 0 at every threshold here too, matching Godot:
the strong form is not threshold-sensitive in either renderer.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
|
||
|
|
aa9b7ef340 |
re: the forced-backdrop rule's pixel cost -- 38 screens go black without it
Follows the necessity census. 'The order moves' is a property of the sort; the
tie-break work already found reorders costing zero pixels, so the picture
moving is a separate claim. Rendered each of the 62 deciding builds twice and
diffed.
38 .prm deciders: changed_px == ink_px in ALL 38. Without the rule the
primitive sorts last, paints over everything, and the
screen composites to pure black. The port's original
contradiction argument, measured on 38 builds across seven
archives instead of argued on two.
24 .tbm deciders: zero -- and that is MY INSTRUMENT, not a finding.
The control asked whether the composite had ink; it always does. The question
was whether the reordered ELEMENT has ink, and compose draws no pixels at all
for a .tbm. So those 24 zeros measure our renderer's blindness by construction.
tie_break_pixel_cost.rs already had the per-element ink_mask this needed.
Reported rather than quietly patched: a control that cannot fail is the shape
this corpus keeps paying for.
Also corrects two things the port agent caught:
- 'Two renderers, same answer' was true of the six GP_TITLE instances and not
of the other 74. The port's re-run of my probe is my code executed twice;
its independent leg was removing its own exporter post-pass, which covers
GP_TITLE only. The disc-wide 62 has one witness and the page now says so.
- forced_backdrop_necessity.rs defaulted to GP_TITLE with no argument, so a
bare run printed 6 instances in the same format as 80. It now walks every
dat/*.pak and reports the archive count.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
|
||
|
|
304ce9efaa |
re: forced_backdrop DECIDES 62 of its 80 instances, not 6
The port agent raised that every check this corpus ran on the rule measured its STABILITY -- that no verdict moved -- and never its NECESSITY. It is right, and the distinction is load-bearing. New probe: recompute derived_paint_order with the forced_backdrop fallback removed and diff the orders, over every dat/*.pak. 80 forced instances = 62 the rule DECIDES + 18 it merely AGREES with. The 80 reproduces the page's own census exactly, which is the check that the probe sees the same set. Every one of the 62 deciders is keyless; no keyed element is ever moved. Of the 18 that agree, 14 have their own key -- and that includes the palogo_eff0.prm 'control', whose implied key is 0x00000000 and would sort it first regardless. So that agreement is the rule reproducing our crate, not the game confirming the rule. The port saw this before I did. The remaining 4 are keyless but inert: every element on those two builds is forced, so the tie-break gives the same order either way. Confirms the port's GP_TITLE finding from the other side: entries 10/11/13/14 unchanged without the rule, entries 12/15 decided by it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v |
||
|
|
e94e203a71 |
re: the A-press fault is SOLVED -- Xenia swallows input, the guest pump is unbounded
The 326 MB log from the failing run was still on disk, so this needed no emulator time at all. Mechanism: Xenia's XamInputGetKeystrokeEx returns X_ERROR_SUCCESS with a zeroed keystroke on every call while a XAM dialog is up (xam_input.cc:197, upstream Canary). The game's keystroke pump -- sub_82457038, read out of the image -- is an unbounded 'while (GetKeystrokeEx() == SUCCESS) queue.push_back()'. It queued 8 388 608 empty keystrokes, grew its vector to 64 MB, asked for 128 MB, got a failed allocation back unchecked, and copied off the top of the guest stack. Two independent instruments agree to within 7: the Canary counter's last report before the crash says 8 388 601 swallowed calls; the crash dump's r29 says the vector held 8 388 608. The reporting granularity is 600. Retracts this page's own 'r9 is a wild pointer above 4 GB'. Xenia prints si_addr, a host address; the guest is mapped at 0x100000000, so the fault address is guest 0x701D0000 -- which is exactly r9 in the register dump. Also refutes nothing of the port's, but answers its ask #3: the two press-a captures are different frames (40.84 % of the band's pixels differ at the best alignment, which has a sharp minimum), so its 0.301 % is not an instrument floor. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v |
||
|
|
1a2f4fdc2d |
re: the boot splash black gap is ~9 units, measured in draws not luminance
The port found its boot had no black frame between the publisher and developer splashes and authored 12 units by analogy with the menus' transition quad. On the boot path that analogy has nothing behind it -- palogo_eff0.prm is a single static keyframe, so the splash bundles declare no fade quad. I had agreed with the dismissal that hid the defect: told the residual was 0.03 s against a bound built from two measured ranges plus jitter slack, I said it said more about the bound than the game. The real gap was 0.2 s. Measured in the draw stream, which separates true black from a fade tail where luminance cannot: palogo_sqex is drawn to frame 125 at alpha 7, frames 126-129 submit NO sprite quad at all, and the developer fades in at frame 130 from alpha 34. Four presented frames, the only such run in the sequence. Converted with the disc as its own clock rather than a frame rate -- this run presented at 13.1 fps against 28 elsewhere -- palogo_sqex declares alpha >= 1 for 239.8 units and is drawn in 105 frames, giving 2.284 units per presented frame, which the title capture independently corroborates at 2.231. So the gap is ~9.1 units (0.152 s), against the 12 authored; +-1 frame is 6.9-11.4. And the true black is SHORTER, since both boundary frames still carry picture. Second finding: the developer splash is ONE composited 525x259 quad at the bounding box of its three declared logos, none of whose individual sizes is ever submitted. That is why an earlier pass reported "developer splash: 0 frames". Declaration sites ruled out: the splash bundles (no fade quad) and the top-level +0x08 (a family constant, 300/60, slack 12-226 units). The executable is NOT looked at and is named as the next place rather than claimed. Also answers the port's sweep question: +0x08 canNOT settle it, because ptloop01/ptloop02 have zero slack and a zero-slack record cannot distinguish "loops" from "runs once and stops". The oracle settles it for the TITLE -- the sweep oscillates over its whole range and resets hard to the same start, once in dwell 1 and twice in dwell 2, so it does not park. The MENU is unmeasured and stays open. Tooling: GRACE and NOTAP knobs for ui_draw_capture.sh. The script taps A on "the screen changed a lot", which is also true of a fading splash -- a first run tapped through the publisher and the developer never appeared. The instrument was perturbing what it measured. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd |
||
|
|
d98c8214cc |
re: the title's build-in measured in the guest's draw stream -- the flashes are real
The settle-time decode was confirmed only against a SETTLED frame, which shows the end state is right and says nothing about whether the five flashes ever happen. This runs the oracle: a draw capture armed before the title exists, so the window contains the frames in which the screen is built. The flashes fire in a six-frame window and are absent from all 155 other sampled frames. `ptlogo_back2eff1` is drawn in exactly two frames at t = 54.0 against a decoded peak of t54-56; `ptlogo1` first appears at t = 42.2 against a decoded t42. Units-per-frame was taken from the GLOW's period alone, a different element, so the timings are not circular. The two holders are continuous from frame 134. The plate glow's quad carries a per-vertex colour whose alpha IS the element's fade alpha, so the ramp is read straight out of the guest: observed range 0..80 against a decoded peak of 80, exact and unfitted; period 51.158 presented frames over 20 cycle starts. Fitting the decoded ramp gives RMS 13.16 alpha levels against 38.18 for the same ramp REVERSED -- if the shape carried no information those would be equal, so the asymmetry is real and correctly directed. Further controls: symmetric triangle 15.73, flat 31.13. `ptlogo_back2eff3` was never drawn, and that is expected rather than a miss: a 2-unit flash peak is 0.85 of a presented frame, so catching one is a matter of phase. A port drawing all five every time shows more sweep than the console. METHOD.md gains the trap this cost: a 2D draw's identity is its vertex geometry, not its bound texture. These sprites sample shared pages, and matching texture dimensions produced a false negative (no flash is ever drawn) and a false positive (the intro movie's 640x360 YUV planes read as `ptbase2`) in the same pass. Also records the top-level restriction on the settle window, which the port raised and which is verified here: top-level [160,236] width 76, including the `ptloop` leaves [269,540] width 271 -- an instant past the end of every top-level element's timeline. Evidence committed as a derived per-frame series, not the 7 MB raw log. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd |
||
|
|
4c74e579a0 |
re: 210 focus records pulse, not 2 -- and 8 of them fail in a way that looks right
The port censused focus-record alpha over its own export -- 34 elements, 2 varying, both `ptbtn00f` -- and concluded there is nothing to fix. That is correct and correctly scoped. This asks the same question of the whole disc. 1 130 focus records, 2 664 timed elements, 210 with a varying alpha. 202 have `rest()` returning the PEAK, the `ui-settle-time` pathology. By pak: PILOTLOG 116, MOVIE_THEATER 54, HANGAR_ARSENAL 30, LEADERBOARD 8, GP_TITLE 2. So the port's 2 is right because GP_TITLE has 2. The scope was load-bearing and was not stated as a limit -- "only 2 have a varying alpha" reads as a fact about the format and is a fact about one pak. The pathology is concentrated in exactly the screens a wider port reaches next. The 8 LEADERBOARD ones are the worse mode. `py_ranking_btn01f` swings 255->127->255 with no two adjacent keyframes equal, so `rest()` falls through to its longest-dwell rule and returns 244 -- neither the peak nor the trough. A glow stuck at its peak is visibly wrong; one stuck at 244 of a 127..255 range looks entirely plausible and nothing reports it. Verified rather than asserted: two hits dumped keyframe by keyframe, and a control on `ptbtn01f`, which is genuinely constant across its cycle and is correctly NOT flagged. `py_ranking_btn01f` also confirms the loop-length decode independently -- its ramp ends at t=90 inside a declared 120-unit cycle, holding bright for 30 units. Reach stated: 210 is a floor. Focus records are matched by the `Xf.rat` name rule, and elements with constant alpha but varying scale, rotation or position have the same problem and are not counted. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd |
||
|
|
d4a08ad194 |
re: a nested record's +0x08 is its loop length -- the plate's period is 120, not 105
Answers the question the port agent asked: does the `PRESS A` plate's pulse group loop from its start, or hold at alpha 0 between cycles? It holds. A nested record is itself a RATC bundle with its own header, and that header's `+0x08` is the loop length -- the same field ui_header_time_disc already tests as an animation length at the top level. Its keyframes need not fill it, and the slack is a hold at the final pose. `ptbtn00f` is 105 units of ramp inside a 120-unit cycle, so the glow rests dark for 15 units between pulses. The five main-menu focus records fill their 120 exactly, which is what shows the slack belongs to this record rather than to the format. Disc-wide over 1 781 timed nested records: 92.3% declare exactly their last keyframe time, 7.7% declare more, and 0 declare less. That last row is the falsifier -- a cycle cannot restart before its own last pose -- and it never fires; the 7.7% is what keeps the reading from being an unfalsifiable relabelling of the keyframes. Falsification against the running game, using a pacing factor measured INDEPENDENTLY on the main menu's focus ring (declared 120 units, measured 2.177 s, factor 1.0885): to reach the corpus's four measurements of the plate pulse (2.12/2.19/2.34/2.31 s), a 105-unit period needs a factor of 1.211-1.337, which EXCLUDES the ring's; a 120-unit period needs 1.060-1.170, which CONTAINS it. Predicted 2.177 s against a measured 2.12-2.34. The two elements are in different bundles and were measured in separate runs; the only thing tying them together is that both declare 120. So the port should stop shipping 105. Its 123-vs-129 ambiguity straddled the right answer without containing it, and 129 only fitted because it was 105 + the exit_ramp_units constant it has since correctly deleted. Reach is stated: this says where a cycle ends, not which records cycle, and the TOP-level +0x08 is a different field left untouched -- every GP_TITLE entry declares 300 while its elements end at 244-269. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd |
||
|
|
7b5a4aa041 |
re: the paint-order tie-break costs one pixel, on one screen we do not ship
Closes the open half of Q3. `ui-paint-order-derived-check.md` bounded WHERE a wrong tie-break could show -- overlapping same-key pairs -- and said outright that nobody had measured how many change a pixel. At the instant the player sees, the answer is: at most 1 px at max channel difference 1, on the JAPANESE title only (`ptlogo2` x `ptlogo_tm`, 5 px of shared ink). Exactly 0 px on all five port screens. The earlier 24-pair bound was counted at `rest()`, and 10 of the title's 11 overlapping tied pairs are between `ptlogo_back2eff1`..`eff5` -- the five transient flashes from the settle-time finding, transparent on the settled screen. A tie between two invisible elements cannot cost a pixel. Not a knife-edge. Sweeping every keyframe time and every midpoint between keyframe times, the live-pair count is flat across the ENTIRE settle window: 1 on the EN title, 2 on the JP title, 0 on all four loading bundles -- whose tie is live only at t17..t33, during the build-in, which matters because their settle windows are narrow enough to deserve little trust otherwise. Controls: every entry reporting zero also swaps an overlapping DIFFERENT-key pair, which must and does move pixels (25 310 / 268 698 / ~765 000 px). Zeros are explained by shared-ink counts rather than asserted -- the `ptframe` pairs overlap by bounding box and share 0 px of ink. Entries 0/1/12/15 have NO live control and their zeros rest on keyframe data rather than a render; recorded as the weaker claim it is. Refutation attempt on the corpus's "24 overlapping pairs": it SURVIVES as a rest-pose count -- an independent recount reproduces entry 7's 16 exactly. What is overturned is its interpretation as the risk surface. `tie_break_pixel_cost` gains a settle-time case and an alpha/scale filter on its rect test; `tie_cost_over_time` is new. Also strips 611 bytes of captured cargo warnings from the head of the committed tie census. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd |
||
|
|
1f9609c37f |
re: the keyframe block s +4 and +8 are angles used almost always as a 180 flip
Narrows a standing unexplained pair without claiming to decode it, and states precisely why it cannot be closed in this container. Census over every UI pak on the disc -- 2859 builds, 90347 keyframes, parents and nested leaves. +4 has 12 distinct values and +8 has 11, against 157 for the decoded rotation at +12. Per sprite-instance across 14241 of them, with +12 as a control because it is known to hold a real angle: +4 takes more than two distinct values on 7 instances, +8 on 99, and +12 on 396. So +4 is in practice a two-state field whose state is 180 -- and for a screen-plane sprite a 180 degree rotation about an in-plane axis is a mirror. But they are not booleans. GP_TITLE entry 7 s ptlogo3a runs +4 = -72, -18, -4, -1 against +12 = -14, -4, -1, 0: the two decay to zero together with +4 roughly four to five times +12 at each keyframe. That is a coupled two-axis settle and the strongest support the disc offers for the three-axis reading. So the readings reconcile -- the field is an angle whose overwhelmingly common use is the 180 degree special case. The reach is the important half. All six non-zero +4/+8 keyframes in GP_TITLE are in entry 7, the Japanese title, which has no oracle capture and which MISSION scopes out as localisation beyond English. The five English screens that do have captures carry +4 = +8 = 0 on every keyframe, so they never exercise the fields. The paks that use them heavily, GP_READY_ROOM at 4686 and GP_DIALOG at 1058, are also out of scope and GP_READY_ROOM is a recorded no-go. So this is untestable against every oracle the project holds rather than merely unfinished. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd |
||
|
|
a1adb17d35 |
re: 125 percent is not the only odd scale, and ptlogo_eff2 is a pop not a steady state
Refutes a DECISIONS claim and decodes the element the port withheld on my say-so. The claim that title_jp s ptlogo_eff2 at 125 percent is the single drawn element in the whole export at a scale that is not a whole multiple of 100 percent rested on a census of PARENTS only. Opening the 45 leaves as well finds thirteen distinct non-whole-multiple scales -- 75, 96, 99, 101, 103, 112, 125, 150, 204x208, 210x220, 250, and the 75x100 / 96x100 / 99x100 pairs -- with 125 among the rarest at two occurrences. ptlogo1 and ptlogo2 carry 101/103/112 on the ENGLISH title too, so it is not a Japanese-build peculiarity. The claim s real content was "the only one the port draws", which is about the export s element set rather than the disc. And ptlogo_eff2 is decoded. The 125 percent lasts 57 units, about 0.95 s -- a scale-0 to 125 to scale-0 flash between t=50 and t=107, a transient rather than a steady state, which is why it looked anomalous in a census of resting poses. The leaf draws at 100 percent as two superimposed copies of the same sprite at alpha 160 and 80, each rotating a full 360 degrees over 960 units: a slow double-layered spin, 16 s per revolution. This is exactly the case the ptloop rule could not separate. There the parent had expired so leaf-wins and parent-ignored were indistinguishable; here the parent carries real geometry including a scale that reaches zero twice. If parent scale gates the leaf the spin is a 0.95 s flash; if the leaf runs on its own timeline it spins for 16 s. Nothing on the disc chooses between them, and title_jp has no oracle capture, so it is undecodable in this container -- the port is right to withhold it, and the Japanese-locale capture MISSION has parked would settle it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd |
||
|
|
062e17bf34 |
re: the three streams of a voice cue are decoded CONCURRENTLY, not alternatives
Refutes a framing of mine that two documents and the port s exporter were built on, so it is a new page rather than an edit. voice-region-leading-chunk.md read a long cue s three streams as three presentations of one take, and from that came the instruction "take one stream, do not sum", which the port implemented. The open question was which presentation the game plays. It has no answer. Booted with --xma_param_probe=true, the cvar whose own comment says it is keyed to reveal which sub-wave of a movie s .slb the game actually decodes. The guest opens three XMA contexts and decodes all three concurrently: ctx=0 packets=632 byte_size=1294336 ch=2 48000 ADV stream 1 ctx=1 packets=546 byte_size=1118208 ch=2 48000 ADV stream 2 ctx=2 packets=572 byte_size=1171456 ch=2 48000 ADV stream 3 Byte-exact against the three streams payloads taken independently off the disc (RIFF size minus 60). Only these three contexts appear in the run. So a consumer that picks one discards two thirds of what the game mixes. Both "three presentations of one take" and "take one stream" are withdrawn -- and the previous behaviour is not thereby right either, because an equal-gain 1/n sum of channel pairs is not a downmix and the port s measured 6.02 dB loss was real. Neither rule is established; a consumer is authoring. Three concurrent stereo streams is six channels and N stereo streams is how XMA carries multichannel on the 360, which would also explain the 1-or-3-never-2 span census. Recorded as a hypothesis, not a result: all three fmt chunks declare ChannelMask 0x0002 identically, which is not what distinct channel roles should look like. Everything byte-level survives: the leading chunk being stream 1 clipped by our own guard, the 70 + 8 + 17 decomposition, the bank-header discriminator. Reach: one cue, one boot. That 28 cues are 3-stream is decoded; that all three decode concurrently is measured on ADV alone. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd |
||
|
|
b82f75979e |
re: the dual-mono explanation for the extra stream bytes does not generalise
The port chose a voice presentation on the argument that ADV chunk 1 is mono-in-stereo and chunk 2 is dual-mono, so chunk 2 s extra bytes encode a duplicated channel rather than fidelity -- which would explain its higher declared PsuedoBytesPerSec without appealing to encode quality. Their ADV channel measurement stands. The generalisation does not. If stream 3 were systematically the same take with its channel duplicated, its size ratio to stream 2 would be tight across the 28 three-stream cues. Measured: min 0.0778 (S00A, the silent one) median 1.2565 max 2.9163 (S06A) sd 0.5057 within 15 percent of 1.0: 12 of 28 A 37x spread is not a duplicated channel, and the declared rates scatter with them -- S06A is 5661 against 16513 B/s. Whatever distinguishes the three streams varies per cue rather than being a fixed channel-configuration triple. This does not touch the port s decision, which is to take the loudest presentation: that is a per-asset content measurement, not a structural rule, so a scattering ratio cannot undermine it. It touches the explanation, which should not harden into a fact about the format. Two curiosities recorded: S12B s three streams are byte-size identical at 14396 each, and BIRD_224 is 3-stream while being a non-movie cue, so the shape is not exclusive to cutscenes. Also narrows the settle-time page s own generalisation. The port measured its boot the way this corpus measured the game and found the sequencer NOT late -- its 0.6 s discrepancy was arrival-to-arrival timestamps compared against visible spans, the plate-delay trap in a second place. So what is supported is that rest.t is the wrong landmark for the TITLE, not that everything paced off it is late. And the offered re-take of the one-run menu figures is recorded as declined, with the reason, rather than left looking unfinished. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd |
||
|
|
63573d2b50 |
re: settle_time measured -- the title is drawn at 2 s, not at rest.t s 4.18
The port s boot sequencer paces every screen off rest.t, which is the last hold keyframe rather than when a screen arrives. Measured on one cold boot: the container had no Xenia storage root at all, so this is a fresh profile with no shader cache, the slowest case. title build-in (first ink -> art fully drawn) 0.23 s title settled -> PRESS A plate on 2.247 s (disc declares 120 units) plate pulse period ~2.37 s main menu build-in 0.531 s B -> title 0.482 s A -> menu 3.763 s DO NOT AUTHOR, see below The title s rest.t is 251 units = 4.183 s and its art is finished at about 2 s, so a sequencer pacing off rest.t holds it roughly twice as long as the game does. Instrument controlled before the run: 9/9 on the content classifier including the movie-frame and difficulty-screen negatives, 4/4 on the plate detector; the run sampled 7.99 fps against a requested 8 with an independent one-shot grab cross-checking every 20 s. Records a refutation attempt of mine that FAILED. The probe s own marks gave a plate delay of 3.203 s against the corpus s 2.13 s, which on a cold-cache boot looked like a real effect. It was the instrument: the plate pulse period is an internal clock for presentation rate and measures 2.369 s here against the corpus s 2.3, so the run is not slowed, and re-measuring from content gives 2.247 s. The probe s title_static mark fires during the crossfade out of the attract movie, before the wordmark has drawn -- glyph was still 0 when it fired. Also a third independent reproduction of the A-path load stall: 13 frames, 1.53 s, surface mean 26.631 against the earlier 14/1.53 and 12/1.39 at 26.626. This boot had no shader cache, so it is not a warm-cache artefact. Noted that the earlier pair agreed to six decimals and mine agrees to three. Reach: one run. The menu build-in and B->title rest on it alone. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd |
||
|
|
3f945808d6 |
re: a long voice cue is three presentations of one take, and our guard clips the first
Closes the last open question on the voice regions: why one cue s byte span decodes to ~2.6x the movie s length. The port measured, with controls including a cross-movie negative, that a region s leading chunk is the TAIL of the full-length chunk that follows it -- r = 0.998 at a lag that puts it flush against that chunk s end, residual 16.7 dB down over 84.5 s. They withdrew their own earlier 0.768, which came from a search that scored best on the boundary of its own lag range. Checked it here by an independent route that needs no decoder. If the leading chunk is the tail of a full-length first stream, the whole leading stream should be one complete take of chunk 1 s duration. For ADV: 504464 + 808304 = 1312768 B at chunk 0 s byte rate of 9559.7 B/s is 137.323 s, against chunk 1 s measured 137.324 s. One millisecond over 137 seconds, from byte rates rather than from envelope correlation. And the byte structure settles the shape disc-wide. Counting stream starts inside every inter-descriptor span: 258 hold exactly 1 stream, 28 hold exactly 3, and nothing holds 2 or any other number. All 20 spans over 1.5 MB are 3-stream. The 95 movie regions decompose 70 + 8 + 17, and the 8 are independently the same 8 the first census found as bank-header-with-3-chunks. So 359 s = 84.55 + 137.32 + 137.32: three presentations of one take, the first clipped by resolve_movie_voice_region s own 1.5 MB guard. Consequences recorded for the port: dropping the leading chunk is removing a duplicate rather than truncating, so the hedge is lifted; but summing chunk 1 and chunk 2 is wrong, because they are the same take at different gain, not two stems. Take one stream. Also flags a coincidence I nearly built on: the 504464 B constant is structural, not proportional -- ADV s proportional prediction lands within 8 bytes of it and S00A s is 4305 B out. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd |
||
|
|
432fb7450b |
re: the voice region s leading chunk is the movie s OWN dialogue, and a guard puts it there
My own leading hypothesis -- that the leading chunk is an in-mission VOICE_D_* line -- is refuted, on the route the port suggested: widen the enumeration past the 95 manifest-bound movies and the byte-span test settles it without anyone listening. Scanning the stream for every trailer descriptor (the (id, 0x11) pair whose id repeats at +0x800) gives the complete cue partition, mission lines included: 287 descriptors in a 116.2 MB window, all 287 carrying an id the 4280-name registry names. Every one of the 17 leading spans is bracketed by desc(N-1)..desc(N) where desc(N) is that movie s OWN cue id. Zero mission lines. The mechanism is a guard in our own resolver. resolve_movie_voice_region takes the predecessor trailer as the region start, guards it with end - start < 1_500_000, and falls back to the .slb TOC anchor when that fails. Cues with a true span over the guard: 17, of which 17 are stream-opening. Cues under it: 78, of which 0. Perfect discrimination both ways. The anchor sits a constant 504464 B after the true predecessor trailer on all 17, which is unexplained. Not established, and stated as such: this does NOT mean the export truncates N seconds. The port s decode already has ADV s region at 359 s against a 137 s movie, so it over-covers and the byte-to-time mapping is not linear. No XMA1 decoder in this container to check. Also withdraws a claim this page had adopted from the port -- that chunks 1 and 2 are two stems of one performance. The port refuted its own claim by decoding: S00A chunk 2 is digital silence, ADV chunk 2 is 0.60x chunk 1 with the residual 26.8 dB down. Equal duration was a shape match and Q10 s music census should not have been carried across to voice on it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd |
||
|
|
de2fe4a110 |
re: the voice-region third chunk is a different structure from the BGM one
The port hit a 2+1 chunk signature on a resolved movie-voice region and asked
whether the bank-header explanation that closed HANDOFF Q10 also covers it,
rather than assuming it. It does not, and the discriminator is mechanical.
Disc-wide over the 95 English movie-voice regions the manifest binds:
78 open with a bank header -- bank_header_len fires, 10240 B = 5 packets
exactly, every time. That is the BGM case.
17 open with a leading headerless stream -- bank_header_len is None, and all
17 have length congruent to 1392 mod 2048, the disc s own derived data
offset. No other residue occurs.
0 begin at a RIFF.
Counting chunks does not discriminate: 8 bank-header regions also yield three
chunks. slb.rs already predicted this in its own doc comment -- the header
signature has "zero false positives on the 7993 mid-bank windows, where the
leading region IS real" -- and a voice region is a mid-bank window by
construction.
Also tested the obvious defence of dropping the leading chunk, that it is the
predecessor cue s audio: 0 of 17 leading spans lie inside any other resolved
region, 0.0 percent on every one. The test finds overlaps where they exist (16
overlapping pairs among the regions, 60 exactly-adjacent boundaries, 73 of 78
bank-header regions starting where another ends), so the zero is not the
instrument.
Left open, with reach: the census covers movie-voice regions only, and the same
stream carries the in-mission VOICE_D_* cues, which are not enumerated -- the
leading bytes plausibly belong to one of those. Could not be settled by
listening: no XMA1 decoder in this container, and sylpheed-cli audio info
reports these chunks as 16 channels / 4310 Hz / 2-bit, which is visibly wrong.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd
|
||
|
|
884822ebe3 |
re: the paint-order tie-break costs the port zero pixels, not 24 pairs
The census bounded WHERE a wrong tie-break could show and said outright that nobody had measured how many of those pairs change a pixel. Measured. `compose_with_order` renders a bundle in a caller-supplied order; the new example renders each screen twice, once derived and once with one tied pair swapped, and diffs. Same-key elements are contiguous under a stable sort on (key, i), so a swap paints nothing else in between. Controlled per entry: swapping an OVERLAPPING pair with DIFFERENT keys moves 36 305 to 771 479 px (max delta 254). Where no such pair is drawn the output says so rather than reporting an uninterpretable zero. * EXTRAS (entries 6/9) and the main menu (5/8): 0 px. The tied ptframe pairs ink ~3 600 px each and share NONE of them -- the 102x132 rect overlap was an artefact of approximating an element as pivot x 2. Blend-independent. * Across all 31 drawable overlapping tied pairs in GP_TITLE, the largest change any of them makes to any channel is 3/255. * Withdrawn: "a wrong tie-break can be wrong by a whole layer". That rested on ptlogo_back2eff5 geometrically containing two other glows. Rendered, the swap moves 6 390 px by max delta 2. Containment is not occlusion when the container is a near-transparent glow, and nobody had rendered it before asserting it. Reach: this measures our compositor's sensitivity to order, not the game's. The zero-shared-ink results hold under any per-pixel blend; the delta<=3 figures assume ours. 13 disc-gated ui_paint_order_disc tests and 114 lib tests pass unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd |
||
|
|
6c05b72099 |
re: the paint-order tie-break costs 24 overlapping pairs, not one element
The port challenged HANDOFF's "costs one element's blend on one screen" with a census of 105 elements sharing a layer key across 12 of 16 screens. The two numbers count different things -- elements vs overlapping pairs -- so it is not the contradiction it looked like, but the objection stands and the line was wrong. paint_order_audit already reports overlapping ties per entry, and over all 16 GP_TITLE entries: 5 use a measured order and carry no tie risk; of the 11 that fall back to the derived order, 7 have overlapping ties, 24 pairs in total. The Japanese title (entry 7) alone has 16, because it is the twin of the one build whose measured order exists and has none of its own. Overlap bounds where a wrong tie-break COULD show, not what it costs; nobody has measured how many of the 24 change a pixel, and the port is right about that too. The rule itself is unaffected: the layer key is still decoded and the derived order still reproduces every measured order exactly except the title's eight tied pairs. Census committed at docs/re/data/paint-order-ties-gp_title.txt. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Nsxw1A9JseUw99Yw1ZRQzY |
||
|
|
5744f379b2 |
formats: a keyframe's time comes before its pose, and none of them was missing
The placement region is `frames` records of `{u32 time; 36-byte pose}` after an
8-byte header, so the time word PRECEDES the pose it belongs to. Our parser's
40-byte window opened at the pose, four bytes into the record, and then read the
word at its `+36` as that pose's time -- which is the NEXT pose's. Every pose
field was right; only the time association slipped by one.
Two things the corpus has carried for weeks are that off-by-one and nothing
else: "a group's data stops 4 bytes short of its final block's time slot", and
"the last keyframe carries no time". The group is not short (8 + frames*40 is
exact) and no time is missing -- the first pose's time is the lead-in word at
`header + 8` that `parse_placements` skipped without asking what it was.
Disc-wide, 33 archives, 13 991 groups, each test with a control:
A lead-in prepended to the shifted times is non-decreasing 13991/13991
B a non-zero lead-in is strictly below the next time 5058/5058
control (another group's lead-in, same bundle) 70.9%
C multi-segment alpha ramp at a constant rate, corrected 857/1540
the same, under the old reading 0/1042
C is the one that cannot be argued with: interpolation between keyframes is
linear, and under the old reading not one multi-keyframe ramp on the disc comes
out at a constant rate.
Adoption is free on every static composite, which is what the corpus previously
declined it over. `SYLPHEED_KF_TIME_SHIFT=1` moved GP_TITLE build 7 by 13.1% of
its pixels because it left pose 0 untimed; with the lead-in restored, all 12
GP_TITLE builds render byte-identically, and across 217 builds in six archives
only two elements pick a different rest pose -- both times between two poses
that are equally invisible.
`SYLPHEED_KF_TIME_SHIFT` is gone; `SYLPHEED_KF_TIME_LEGACY=1` restores the old
reading for A/B work.
ui_header_time_disc needed one line: 546 bundles whose every group is a single
static pose now report max_time = 0 where they previously reported no time at
all. Excluding them, the result it guards strengthened -- the bound holds over
2 859 bundles instead of 2 313, still with zero violations.
Not established: the executable's own parser. Reach is written down.
docs/re/ui-keyframe-record-layout.md
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nsxw1A9JseUw99Yw1ZRQzY
|
||
|
|
14fced07b6 |
re: I cannot measure this emulator's clock -- and that answers the port's 2%
The port put two of my pages against each other: settle->plate 2.135 s and one
focus-ring revolution 2.177 s, both a declared 120 units during a static hold,
2% apart against a 6 ms run-to-run agreement. Fair challenge.
The resolution is that the question assumes a stable wall clock. Same interval,
same container, same day: 2.138, 2.132, and 2.549 s -- a 19% swing, caused by
adding --log_ui_draws=true. The 2% is a fifth of that. The two pages were never
in conflict about the game; they are three readings of one declared quantity
through a clock that moves. What settles the quantity is the disc.
Wall clock cannot separate the hypotheses, so I tried to measure frames instead.
Both instruments are recorded as failures rather than published as numbers:
* Canary's own [UI-CAP] counter -- the one that produced the corpus's 28.5 fps
-- costs a third of the frame rate. 300 frames in 16.567 s = 18.11 fps on a
screen that gives ~28 without it. That reclassifies 28.5 as a load-dependent
lower bound; it does not overturn it.
* A distinct-frame counter over the spinning ring FAILED its decisive control:
15.88 fps against the game's own 17.59 in the same window, 10% low, so the
ring does not change on every presented frame. Its static control also read
2.63 instead of ~0. Dead, not tuneable, per METHOD.md.
The rule that follows, and it applies to everything I hand the port: a measured
interval landing near a round number of declared units almost certainly IS that
number of units. Ship the units.
Also recovered here, because the same question needed it: the static PPC route.
Four tools open /work/xenia-rs/sylpheed.db and nothing in this repository builds
it -- no disassembler, no PPC decoder, and default.xex is encrypted (zero
plaintext "GamePart"). Xenia decompresses the image at load, so dump_image.py
reads it out of guest memory and validates it against the corpus's own landmarks:
the 29-entry GamePart id table at 0x820A1630 and the Xbox 360 D3D runtime
strings. String search and table dumps work again; instruction-level work does
not, and the present interval I wanted is an immediate, not a string.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014voBspJ6kFncNErZJuZcLw
|
||
|
|
5fcc89be55 |
re: the PRESS-A plate arrives 2.13 s after the title settles, measured twice
The one number the port said decides a structural question on its side: whether
`ScreenView` has to draw two builds at once. It does. The boot title shows
build 4 alone, and 2.13 s after build 4 stops animating it composites build 2
over it. Two independent boots agree to 6 ms (2.138 / 2.132), which is under one
sample interval.
Measure from SETTLED, not from first pixels. "First drawn -> plate" is 3.78 s in
one run and 4.26 s in the other, because the build-in animation itself ran 1.64 s
and 2.13 s -- that spread is the emulator's frame pacing, and it is exactly the
kind of number that looks like a measurement.
Ruled out before believing it: that the plate was pulsing all along, too dim for
a thresholded glyph counter. The counter reads EXACTLY 154 -- the committed
no-plate title's own value -- for every frame of a plateau nearly one full pulse
period long, with zero variation, and the surface mean is flat to +/-0.03 across
it. A cycling overlay moves both.
Also settled, and also not:
* the black hold between two screens is 0.14-0.30 s, which brackets the port's
authored 0.17-0.23 s and the file's declared 12 units. Their constant stands.
* the (A)->menu latency is STILL not available, and now the reason is known.
Both runs freeze one frame for ~1.4 s at surface mean 26.626 -- agreeing
between runs to six decimals, and reproduced in run 2 with stream restarts
disabled, so it is not the capture path. It is a guest load stall: the (B)
path, which loads nothing, has no freeze at all. Any figure from it would be
an emulator load time.
Refutation attempt, recorded whether or not it survived: navigation.md's "the
title is not input-ready for about ten seconds, and even then (A) registers
roughly half the time". At 7.29 s and 7.28 s after the title settled, (A) was
accepted first press in both runs, as was (B) on the menu. n=2 only makes "half
the time" unlikely (p ~ 0.25); it contradicts the ten seconds outright.
And the standing red banner is withdrawn: the interactive title IS reachable in
this container, twice, with no pad input, in ~3.5 minutes. Why it changed is NOT
established -- this container came up with no Xenia storage root at all, so run 1
created a profile -- and that is written as a correlation for the next session to
test rather than as a cause.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014voBspJ6kFncNErZJuZcLw
|
||
|
|
724e06b134 |
re: the main menu's focus ring spins continuously -- period 2.18 s, measured
Answers the port's ask: ptbtneff01 is ANIMATED while a button is focused, not drawn once and held. The existing page said 'the ring SPINS' from one frame at a large angle, which is equally consistent with a static draw at a fixed angle. No angle is quoted anywhere. The 360-bin angular estimator written for this FAILED its own control -- a synthetic 30 deg came back as 0 deg (peak 0.596) while 90/180/270 came back exactly -- so it was not used. What settles it needs no angle: total annulus brightness is conserved to 0.4 % while individual angular bins swing by 24, i.e. brightness moving AROUND the ring, which excludes a pulse. The temporal-std map is a clean annulus, falling to ~1 both inside and outside the stroke, which excludes positional jitter. Period from the profile's autocorrelation: eight evenly spaced peaks, mean 2.177 s over nine revolutions. Even spacing is the internal check a drifting instrument cannot pass. That is 120 units = 60 frames = 2.00 s at a true 30 Hz. Also measured, same run: the ring is the ONLY moving thing on the settled main menu -- temporal std is exactly 0.000 on every unfocused button, the labels and the footer. And the ring's centre, located from the std map at game (520.7, 339.7), matches the declared leaf offset's prediction of (521, 340). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KNR5Y79D1T4bBr6gJQaWFP |
||
|
|
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). |
||
|
|
6779d9c807 |
media: expose se_wave_riff -- the menu's SE cues, assembled where the format lives
The port is forbidden from reimplementing media assembly and Static.slb is exactly that case: no RIFF, no seek chunk, no XACT container, just a packed run of whole 2048-byte XMA1 packets, so a wave is defined only by (offset, packet count) and the header has to be synthesized. That step now happens once, in the crate that owns the format, instead of in each consumer. `slb::xma1_wave_riff` wraps raw packets; `media::se_wave_riff` looks the bank up and reads just the packets asked for. Both reuse the existing synth_xma1_fmt / build_riff, which are already byte-identical to what tools/re-capture/ slb_extract_wave.py writes -- so this is exposure, not a second implementation. It reads a TARGETED range rather than the whole bank, and that is load-bearing: Static.slb is the ONE entry of sound.pak's 9 519 whose declared extent runs past the end of the extracted segments -- by exactly 616 768 B -- so reading it whole fails outright on this extraction. Every cue we need is in the first few hundred KB. Recorded rather than worked around silently. Verified as an artifact, not a compile: all three cues decode through ffmpeg to mono 48 kHz PCM at 0.533 / 0.344 / 1.016 s, non-silent (rms 2085 / 2985 / 4327, peaks 29813 / 16973 / 32767). The refusal path is exercised in the same run -- an impossible packet count is rejected rather than returning a short stream, because a truncated XMA decodes to plausible-sounding garbage. Also adds docs/re/captures/ORACLE-CAPTURES.md: an index of the nine canary framebuffer captures already in this repo, and a plain statement that THEY are the reference and `screen render` is not. |
||
|
|
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.
|
||
|
|
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. |
||
|
|
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. |
||
|
|
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.
|
||
|
|
00431a7a8a |
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". |
||
|
|
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.
|
||
|
|
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. |
||
|
|
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. |
||
|
|
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. |
||
|
|
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.
|