From 7ae55975307069204b0a0393622e098ef09bbcd5 Mon Sep 17 00:00:00 2001 From: sylph-decoder Date: Mon, 31 Aug 2026 06:16:37 +0000 Subject: [PATCH] re: retract 'the game batches elements that share a mode' -- its own log refutes it I wrote that yesterday in HANDOFF, the finding page and INDEX. It is false: in one main-menu frame, draws 5, 6 and 7 are three separate additive draws -- consecutive, identical blend state, not merged. Only the one-way implication holds: elements inside one draw share a blend state; sharing a state does not put elements in one draw. The wrong version would have licensed the port inferring a mode for an element nobody observed, which is the one thing that page must not do. The ptframe4 conclusion is unaffected -- it is in the same DRAW as ptframe3, not merely the same mode. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v --- docs/port/HANDOFF.md | 11 ++++++++--- docs/re/INDEX.md | 2 +- docs/re/structures/ui-blend-mode-measured.md | 14 +++++++++++--- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/docs/port/HANDOFF.md b/docs/port/HANDOFF.md index dd71402e..fc993a78 100644 --- a/docs/port/HANDOFF.md +++ b/docs/port/HANDOFF.md @@ -62,9 +62,14 @@ than either alone. *separate* question and I have not read the shader. Do not conclude premultiplied textures from this. * **`ptframe1` and `ptframe2` are ONE draw call**, and so are `pteff20` + - `ptframe3`. A draw call carries one blend state, so the game batches elements - that share a mode. `ptframe4` needs no separate argument: it is inside the same - additive batch as `ptframe3`. + `ptframe3`. A draw call carries one blend state, so `ptframe4` needs no + separate argument: it is inside the same additive draw as `ptframe3`. + πŸ”΄ **Correction to my own first phrasing of this, which said "the game batches + elements that share a mode".** That is false and would have let you infer a + mode for an element I did not observe. In one menu frame, draws **5, 6 and 7 + are three separate additive draws** β€” consecutive, same state, not merged. What + holds is only the one-way implication: **elements inside one draw share a blend + state; sharing a state does not put elements in one draw.** * **Read the table as per-element facts, not as a rule.** *Which field* selects the mode is still unknown β€” nothing on the disc does it, and the batching means the choice is made before the draw. Two additive menu draws (819Γ—720 and diff --git a/docs/re/INDEX.md b/docs/re/INDEX.md index 3d66b67b..55492ad3 100644 --- a/docs/re/INDEX.md +++ b/docs/re/INDEX.md @@ -180,7 +180,7 @@ files, which is how the same ground got covered twice. | [`structures/ui-clock-freezes-at-settle.md`](structures/ui-clock-freezes-at-settle.md) | The top-level clock stops at the settle point β€” observed in the running game | βœ… **measured**: `GP_TITLE` build 4 declares `t = 0…269`, about 120 presented frames at this run's pacing, and the dwell lasted **~1 100**. `ptcopyright` declares alphaβ‰₯1 for **106 units** (t=138…244) and is **drawn for 1 050 frames**; `ptlogo1` declares an exit at t=264 and is drawn for 1 095. Both vanish within three frames of the dwell ending. **The clock advances through the build-in, stops inside the settle window `[160,236]`, and holds; the exit ramp plays when the screen leaves, not on a timer** β€” [`ui-settle-time.md`](structures/ui-settle-time.md)'s decode observed from the other side. A nested record keeps looping on its own clock throughout. πŸ”΄ **This closes the 114-vs-120 gap, and it was my arithmetic**: 2.231 units/frame was regressed over *build-in* events (the only stretch the top-level clock advances) and applied to a period measured during the freeze β€” two different clocks. The declared **120** was never in doubt from the calibration-free dark-fraction test. βœ… The 51.158-frame period is now confirmed by a **second independent estimator** (autocorrelation, lag 51 with harmonics at 102/154) β€” ⚠️ whose first version **failed its control**, returning 48, because it indexed by sample position where the log's frame numbers have gaps. ❔ The **sweeps'** period stays unmeasured: the same validated estimator disagrees between two dwells of one screen (515 vs 452 frames). πŸ”΄ **Blocker: a single β’Ά on the title faults the guest** β€” 3 attempts, 2 register dumps of 223 MB and 519 MB, against 3 no-input runs that all completed; bounds menu-side dynamic RE here, and any scripted button press needs a `canary.stdout` size guard | | [`structures/boot-splash-dwells-are-declared.md`](structures/boot-splash-dwells-are-declared.md) | How long each boot splash is shown | βœ… **decoded**: the dwells are the bundles' own declared timelines β€” publisher **t=0…255 = 4.250 s**, developer **t=0…210 = 3.500 s** at 60 units/s. The corpus's independent screenshot timing over 3 cold boots gives 4.30/4.60/4.37 and **3.51/3.50/3.37** β€” the developer agreeing to **1.1 %**, two of its three runs to 0.3 %. πŸ”΄ **Wall clock is the wrong unit to author**: a fresh no-input boot measured the same two dwells at **5.10–5.61 s** and 3.83–4.30 s, 15–20 % longer than both the declared values and the corpus's runs, on the same disc β€” so a seconds figure is one run's emulator pacing. Boundaries from the draw stream: publisher wordmark frames 6–119, **3 frames with no sprite drawn**, developer glows 123, wordmarks 140–209, intro video 216. πŸ”΄ **The frameβ†’wall-clock instrument resolves to one BUFFER FLUSH, not one frame** β€” 69 of 125 samples showed no advance and the rest jumped 7–15 frames, making the apparent rate swing 0.0164–0.0316 s/frame; frames 119 and 123 fall in one burst, so the inter-splash gap is **not separable** by it. Quoted as brackets; sub-flush estimates withdrawn before reporting. ⚠️ `palogo_anima` never appears β€” almost certainly the 8-vertex cap (7 elements batched, 2 logged), the same trap as the `eff3` false negative, so it is named not reported. ❔ the publisher's 4.1 % error vs the developer's 1.1 % is unexplained | | [`structures/ui-forced-backdrop.md`](structures/ui-forced-backdrop.md) *(colour census + self-refutation)* | What colour a keyless element is, and which forced verdicts the argument actually supports | βœ… **decoded, disc-wide**: every full-screen `*eff00*` **primitive** is **pure black** at its various alphas (`ff000000`, `7f000000`, `40000000`, `b2000000`, `cc000000`, `d4000000`, `00000000`) β€” exactly an alpha-over dim or fade, and an *additive* black quad would be a no-op nobody would author. The **only** non-black primitive on the disc is `pbafc.prm`, RGB `00e8e0` cyan at alphas to `ff`, and it is **844Γ—600, not full-screen**, so outside the backdrop rule's geometry guard β€” ❔ it is now the sole additive candidate. πŸ”΄ **Self-refutation: of the 80 forced-first instances only 42 are `.prm`; 38 are `.tbm` carrying fade `ffffffff`.** A *solid* white quad painted first would make the screen white and no screen is white, so a `.tbm` is a white **modulation on a texture** β€” and element alpha does not establish its coverage. That is the `.t32` error one extension further out: I had fixed the symptom (`el.sprite.is_some()`) not the cause, **an element's alpha is not its texture's opacity, and only an untextured primitive makes the two the same fact**. So 42 verdicts stay **decoded**, 38 drop to 🟑 (still almost certainly right β€” all named `*base*`, full-screen, and `pfbase.tbm`'s first position is *measured* β€” but on a name-and-role argument this page elsewhere calls the weaker kind). ⚠️ Code deliberately unchanged: restricting to `.prm` would send eleven screens' backgrounds back to last, the blank-screen bug the rule fixed. Split pinned by a test | -| [`structures/ui-blend-mode-measured.md`](structures/ui-blend-mode-measured.md) | Which blend the UI draws each element with | βœ… **measured** off the GPU, per draw, on the main menu and `EXTRAS`. **Two states and one pixel shader**: `RB_BLENDCONTROL0 = 0x07010701` (src `ONE`, dst `1βˆ’SRC_ALPHA`, alpha-over) for `ptbase`, `pteff05`, the fade quad, `ptmsg`, `ptmsg2`, `pttitle` and every button; **`0x01010101` (src `ONE`, dst `ONE`, ADDITIVE)** for **`ptframe1`, `ptframe2`, `ptframe3`**, `pteff20` and both rotated sweep strips. βœ… **Control 1** β€” the NDCβ†’pixel conversion that names a draw by its quad size reproduces **1134** and **1303** px for the two sweep strips, measured by a different tool in a different session, on both screens; the tool prints PASS/FAIL and disclaims itself on FAIL. βœ… **Control 2** β€” pixel shader `0xE59B2B3DA4AA9008` is used with **both** states (12 additive, 18 alpha-over), so `ptframe1` and `ptbase` run the same shader and this is a blend result, not a shader result. βœ… Independently corroborated by the port, which solved the composite per pixel from two backgrounds and ranked additive 34.3/28.9 against alpha-over's 65.0/71.3. πŸ”΄ Supersedes the *conclusion* of [`t32-blend-mode-not-on-disc.md`](structures/t32-blend-mode-not-on-disc.md) β€” its negative and reach stand, its instruction *"any blend you choose is authored"* does not. ❔ **Which field selects the mode is still unknown**; elements sharing a mode are batched into one draw call, so the choice is made before the draw. ⚠️ Two additive menu draws unidentified; the **title screen was not captured** | +| [`structures/ui-blend-mode-measured.md`](structures/ui-blend-mode-measured.md) | Which blend the UI draws each element with | βœ… **measured** off the GPU, per draw, on the main menu and `EXTRAS`. **Two states and one pixel shader**: `RB_BLENDCONTROL0 = 0x07010701` (src `ONE`, dst `1βˆ’SRC_ALPHA`, alpha-over) for `ptbase`, `pteff05`, the fade quad, `ptmsg`, `ptmsg2`, `pttitle` and every button; **`0x01010101` (src `ONE`, dst `ONE`, ADDITIVE)** for **`ptframe1`, `ptframe2`, `ptframe3`**, `pteff20` and both rotated sweep strips. βœ… **Control 1** β€” the NDCβ†’pixel conversion that names a draw by its quad size reproduces **1134** and **1303** px for the two sweep strips, measured by a different tool in a different session, on both screens; the tool prints PASS/FAIL and disclaims itself on FAIL. βœ… **Control 2** β€” pixel shader `0xE59B2B3DA4AA9008` is used with **both** states (12 additive, 18 alpha-over), so `ptframe1` and `ptbase` run the same shader and this is a blend result, not a shader result. βœ… Independently corroborated by the port, which solved the composite per pixel from two backgrounds and ranked additive 34.3/28.9 against alpha-over's 65.0/71.3. πŸ”΄ Supersedes the *conclusion* of [`t32-blend-mode-not-on-disc.md`](structures/t32-blend-mode-not-on-disc.md) β€” its negative and reach stand, its instruction *"any blend you choose is authored"* does not. ❔ **Which field selects the mode is still unknown**; πŸ”΄ and a first phrasing of this page β€” *"elements sharing a mode are batched into one draw call"* β€” is **refuted by its own log**: menu draws 5, 6 and 7 are three separate additive draws. Only the one-way implication holds, elements inside one draw share a state. ⚠️ Two additive menu draws unidentified; the **title screen was not captured** | | [`structures/ui-prm-blend-mode.md`](structures/ui-prm-blend-mode.md) | Whether a primitive blends additively or alpha-over | ❔ **undecodable, with reach** β€” but the consequence is closed. Looked in **the bundle** (no field: the declaration words are constant and a primitive has no RATC child at all), **the colour census** (every full-screen `*eff00*` primitive is **pure black**; the only non-black primitive on the disc is `pbafc.prm`, cyan `00e8e0`), **the occlusion constraint** (inapplicable β€” `pbafc.prm` strobes 255/124 every 2 units, travels, and is scaled **2 %Γ—3 %**, so it draws ~**17Γ—18 px**, not its declared 844Γ—600), and **the oracle** (`GP_READY_ROOM` is a recorded no-go and gameplay needs the β’Ά that faults the guest). βœ… **Why it stopped mattering:** for a *black* quad the hypotheses differ only in whether it hides what is beneath β€” drawn **first** it is correct under **both**, drawn **last** only under additive. So `forced_backdrop`'s verdict is robust to the open question, and the port's original "layerless sorts last" was wrong under alpha-over and merely pointless under additive. ⚠️ This is not evidence *for* alpha-over. πŸ”΄ The investigation found `forced_backdrop` judged coverage from the **pivot alone**, ignoring scale; checked first, **all 80 forced instances are at 100 %**, so no verdict moved and the added guard is defensive | | [`structures/title-a-press-fault.md`](structures/title-a-press-fault.md) | Why a single β’Ά on the title faults the guest β€” the blocker on all menu-side dynamic RE | βœ… **SOLVED 2026-08-30, and it is the emulator, not the game.** Xenia returns `X_ERROR_SUCCESS` with a *zeroed* keystroke on every `XamInputGetKeystrokeEx` while a XAM dialog is up (`xam_input.cc:197`, upstream); the game's pump is an **unbounded** `while (GetKeystrokeEx()==SUCCESS) queue.push_back()`, so 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 thread stack. βœ… **The number is the argument**: the Canary counter reports **8 388 601** swallowed calls at the last report before the crash, the dump's `r29` says the vector held **8 388 608** β€” two independent instruments, 7 apart, inside the 600-call reporting granularity. No new boot: the failing run's 326 MB log was still on disk. πŸ”΄ **RETRACTED β€” "`r9` is a wild pointer above 4 GB"**. Xenia prints `si_addr`, a *host* address, and the guest is mapped at `0x100000000`: `0x1701D0000 βˆ’ 0x100000000 = 0x701D0000`, which **is** `r9` in the dump β€” an ordinary guest heap address on an uncommitted page. Subtract `0x100000000` from every `Access Violation … at 0x1________` before reading it. βœ… **Decoded code path**, image-checked with **0 mismatches** over 586 instructions: `sub_824574C0` the input-manager singleton at `0x828F3888`, `sub_82457038` the keystroke pump, `sub_82457780` its `vector` insert-with-grow. βœ… **It explains the earlier successes**: whether a XAM dialog is up is *emulator* state, so "reproduced 4/4" and "Q4/Q5 pressed β’Ά fine" were both always true. 🟑 **Which** dialog is still open β€” `XamShowDeviceSelectorUI` is ruled out (`storage_selection_dialog = false` takes the headless path), `XamShowSigninUI` / `XamShowMessageBoxUIEx` are not; the settling experiment is one log line per `is_xam_dialog_present_.store(true)` site, not another blind boot. 🟑 Three untried routes out: dismiss the dialog, `--headless`, or return `X_ERROR_EMPTY` from the swallow. βœ… `frame_clock.sh`'s 300 MB guard killed the run as designed β€” keep it | | [`structures/plate-pulse-phase-lock.md`](structures/plate-pulse-phase-lock.md) | Does gating on the plate pulse bias what a title capture can show? | βœ… **measured β€” it PHASE-LOCKS the shutter.** The plate's pulse is part of the animation, so `wait_plate_pulse.py` does not only wait for settling, it synchronises the shutter to the animation's phase: at the shutter instant the sweep strips sit **25–26 px apart across two runs in different locales and different sessions** β€” **1.6 %** of a ~1600 px traverse. πŸ”΄ **Consequence: the RMSE 0.32 recorded as "between-session capture noise" is a lower bound produced by the instrument, not a property of the game**; the honest figure at an arbitrary phase is **11.9**, a factor of 37, and I had read 0.32 as evidence the JP title is still when it is evidence the gate works. βœ… **The era adjudication survives** β€” margin **16.72** clears even 11.9 β€” and survives *for the reason its own file gave*: correlated noise moves both candidates together and cancels in a margin, so prefer margins to absolute scores under a gated shutter. βœ… The within-run at-rest result also survives (five frames ~1.5 s apart are not gated individually). ⚠️ Reach: this shows the lock, not its mechanism β€” both runs boot the same ISO from the same state, so a deterministic boot could produce it without the gate; **two runs deliberately shuttered at gate + k frames would separate those** and were not run | diff --git a/docs/re/structures/ui-blend-mode-measured.md b/docs/re/structures/ui-blend-mode-measured.md index 0fc7a905..0d71af07 100644 --- a/docs/re/structures/ui-blend-mode-measured.md +++ b/docs/re/structures/ui-blend-mode-measured.md @@ -65,9 +65,17 @@ different shader", which is a different finding. | extras 8 | 179.2 Γ— 32.4 | `pttitle.t32` **182Γ—34** | alpha-over | `ptframe1` and `ptframe2` are in **one draw call**; so are `pteff20` and -`ptframe3`. A draw call carries one blend state, so elements that share a mode -are **batched together** β€” which is also why `ptframe4` needs no separate -argument: it is inside `EXTRAS`' 24-index additive batch with `ptframe3`. +`ptframe3`. A draw call carries one blend state, so `ptframe4` needs no separate +argument: it is inside `EXTRAS`' 24-index additive draw with `ptframe3`. + +πŸ”΄ **The first version of this paragraph said "elements that share a mode are +batched together". That is false**, and it is refuted by the very log it was +written from: in one main-menu frame, draws **5, 6 and 7 are three separate +additive draws** β€” consecutive, identical state, not merged. Only the one-way +implication holds: **elements inside one draw share a blend state; sharing a +state does not put elements in one draw.** The wrong version would have licensed +inferring a mode for an element that was never observed, which is exactly what +this page must not do. ## What this settles, and what it does not