From 70799488febe27efbc567864b728fd709ac0971d Mon Sep 17 00:00:00 2001 From: Sylpheed port agent Date: Tue, 1 Sep 2026 18:53:59 +0000 Subject: [PATCH] port: delete the authored blend map for the decoded field, and find a counter-example doing it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PORT-MISSION ยง3: "When the RE agent later decodes something you had authored, delete the authored entry and let the exporter emit it. That deletion is the measure of progress." This is that deletion. authored/rendering.json's `additive_elements` -- a per-screen list transcribed from the Decoder's per-draw RB_BLENDCONTROL0 log -- is gone. The exporter emits `blend_additive` per element and per nested focus/leaf element from `T8aD +0x04` bit 0x02, and ScreenView reads it there. Both accessor spellings are needed: `ptbtn00f.t32` is in build.sprites while no element carries it as `sprite`, and it is the sharp case -- the plate alpha-over, its own glow additive, adjacent draws on one screen. CHECKED BEFORE THE SWAP, and the map turned out to be a SUBSET, not the answer: 15 elements it called additive the disc agrees with, ZERO contradictions, and 17 MORE the disc marks that it did not. Those include the sweep LEAVES (draw_leaf_for means pteff03/pteff03a are what reach the screen while the map listed their parents) and TWELVE on `title`, where the map was deliberately empty -- so the port has been drawing every title effect with the wrong blend. H6 closes with no capture at all: the JP asymmetry was an artefact of a NAME-KEYED map, and the bit is on the disc for every screen at once. ๐Ÿ”ด AND IT INTRODUCED A REGRESSION, WHICH IS REPORTED, NOT HIDDEN. Against the oracle captures on the GPU: main_menu 10.88 -> 13.02, main_menu_options 11.56 -> 13.57. Deterministic to the digit over three runs, so not sampling noise. Isolated to ONE element, with a control: - main_menu's only newly-additive top-level element is pteff10; - extras gained none and did not move -- the same change on a screen with nothing new moves nothing; - the leaf rule was disabled separately and main_menu stayed at 13.02, so pteff03/pteff03a are NOT the cause. That prediction of mine failed; the rule is restored, being provably neutral here; - title did not move despite twelve newly-additive elements, consistent with verify-capture posing at settle t=198 where those quads are transparent. That is a potential COUNTER-EXAMPLE to a โœ… DECODED claim, and it is a sharp question rather than a guess: their own map lists pteff10 additive on `extras` and not on `main_menu`, and they logged BOTH screens. Asked in BLOCKED.md H6. Shipped anyway, for reasons stated rather than assumed: +2.14 is inside the harness's own ยฑ3.78 capture-phase term for that screen and cannot adjudicate a disc fact; the decoded source is far better evidenced (35 elements, zero errors, out-of-sample prediction 3 of 16); and fitting an exception for one element would put an authored entry back to make one number smaller, which is the move this project keeps having to undo. It is a KNOWN regression, not an unnoticed one. Also settled this iteration, for the Decoder's open question: the port FADES the plate, it does not pop it. Frozen sweep of the plate region -- 210u 0.1457, 216u 0.1573, 222u 0.1727, 228u 0.1900, 236u 0.2142 -- a clean monotone ramp across the declared 214->236. So t=236 is the port's COMPLETION, not its onset, and the 0.367 s "late plus a pop" reading does not apply. Not settled: whether pteff10 has a counter-example; H1's repeat half; the four red verify-screen rows; and finding 3, which still has no cause now that units/s is settled at 60. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_018AHUQvXGyNcKonSEWsgWcX --- authored/rendering.json | 131 ++++++--------------------- crates/sylpheed-export/src/screen.rs | 32 +++++++ docs/port/BLOCKED.md | 2 +- docs/port/blend-decoded-adoption.md | 116 ++++++++++++++++++++++++ port/scripts/boot.gd | 10 +- port/scripts/screen_view.gd | 42 +++++++-- 6 files changed, 213 insertions(+), 120 deletions(-) create mode 100644 docs/port/blend-decoded-adoption.md diff --git a/authored/rendering.json b/authored/rendering.json index 7676a8bd..55684693 100644 --- a/authored/rendering.json +++ b/authored/rendering.json @@ -136,116 +136,37 @@ "on the menu' would have contradicted the pixels for no reason." ], "loop_leaf_kind": "measured", - "additive_elements": { - "main_menu": [ - "pteff12", - "ptframe1", - "ptframe2", - "ptloop01", - "ptloop02" - ], - "extras": [ - "pteff10", - "pteff20", - "ptframe3", - "ptframe4", - "pteff21", - "pteff22", - "pteff23", - "ptloop01", - "ptloop02" - ], - "press_start": [ - "ptbtn00f" - ], - "title": [] - }, - "additive_elements_why": [ - "TRANSCRIBED, not authored, and the distinction is the whole entry. The port", - "composited every element with straight alpha-over and four came out too dark", - "against the capture. I measured from my side that the shortfall scales with", - "the BACKGROUND, and that of alpha-over / additive / screen, additive roughly", - "halves the error (DECISIONS.md, two-background composite solve, control 0.0000).", - "That was a PROPOSAL and stayed one: nothing on the disc selects a blend mode", - "(`docs/re/structures/t32-blend-mode-not-on-disc.md`), so choosing one was not", - "mine to do.", + "additive_elements_deleted_why": [ + "โœ… DELETED 2026-09-01, and the deletion is the point.", "", - "It is now MEASURED OFF THE RUNNING GAME. The Decoder logged RB_BLENDCONTROL0", - "per draw in Canary and drove the game to both screens:", - "`docs/re/structures/ui-blend-mode-measured.md`, table in", - "`docs/re/data/ui-blend-mode-measured.txt`. 0x01010101 is src=ONE dst=ONE,", - "additive. Elements are identified by the PIXEL SIZE of their quad against", - "sprite dimensions.", + "This held `additive_elements`, a per-screen list of element ids transcribed", + "from the Decoder's per-draw RB_BLENDCONTROL0 log. PORT-MISSION section 3: 'When", + "the RE agent later decodes something you had authored, delete the authored", + "entry and let the exporter emit it. That deletion is the measure of progress.'", "", - "โœ… Their control, and it is what makes this a blend result rather than a shader", - "result: ONE pixel shader, 0xE59B2B3DA4AA9008, runs with BOTH states on the main", - "menu -- 12 draws additive and 18 alpha-over. The frames and `ptbase` share a", - "shader. Only the blend register differs.", + "The blend is now DECODED -- `T8aD +0x04` bit 0x02, docs/re/structures/", + "ui-blend-mode-decoded.md -- and reachable since formats-pin-2026-09-01 exposed", + "`ui_layout::sprite_blend_additive` and `blend_additive_by_name`. The exporter", + "emits `blend_additive` per element and per nested focus/leaf element, and", + "ScreenView reads it there.", "", - "`ptloop01` and `ptloop02` are the two ROTATED SWEEP STRIPS, added 2026-08-31.", - "`docs/re/structures/ui-blend-mode-measured.md`'s summary table names them", - "additive in the same row as the frames, and their control 1 identifies them: the", - "NDC->pixel conversion reproduces 1134 and 1303 px, two heights measured by a", - "different tool in a different session (`data/title-sweep-drawn-at-rest.txt`), on", - "BOTH screens. So this is an identification with an independent check behind it,", - "not a name match -- the automatic matcher reports 'no match' for these two.", + "๐Ÿ”ด CHECKED BEFORE THE SWAP, and the map turned out to be a SUBSET rather than", + "the answer. Over main_menu, extras, press_start and title:", "", - "โš ๏ธ AND IT CHANGES NOTHING VISIBLE TODAY, which is why it is worth writing down.", - "On the menus the port runs the leaf group ONCE and parks it off-screen, so these", - "two paint no pixels at any pose the port can be put in (checked: suppressing", - "both sprites at leaf-time 100, 200 and 300 changes 0 pixels). The entry is here", - "because it is measured, not because it does anything. What it DOES do is fix the", - "instrument for the menu-looping question -- see `loop_leaf_on_screens_why`.", + " 15 the map called additive AND the disc agrees", + " 0 the map called additive and the disc does not <- no contradictions", + " 17 the disc calls additive and the map did not", "", - "โš ๏ธ EVERY ID HERE IS A MEASURED DRAW, and the list stops where the measurement", - "does. THREE THINGS ARE NOT IN IT:", + "So nothing transcribed was wrong; it was incomplete, and was being read as", + "complete. The 17 include `pteff03`/`pteff03a` -- the sweep LEAVES, which are", + "what `draw_leaf_for` actually puts on screen while the map listed their parents", + "`ptloop01`/`ptloop02` -- and TWELVE on `title`, where this map was deliberately", + "empty and the port therefore drew every title effect alpha-over.", "", - " `ptframe4` -- NOT MEASURED. It never appears in either capture's draw list,", - " and the port measures it at 10.46x the frame mean, dark, exactly like its", - " three siblings. It is the most tempting entry on this page and it is not", - " going in: 'the other three frames are additive' is a RULE, and the Decoder's", - " own warning with the table was to take it as per-element facts because which", - " field selects the mode is still unknown. A fourth frame added on the strength", - " of a pattern would be indistinguishable from a measured one in a month.", - "", - " The two unidentified additive draws per screen (889.6x1134.0 and", - " 1299.2x1303.2) -- these are the rotated sweep strips, whose height the", - " Decoder's OWN control reproduces independently. Additive, but the port draws", - " them through `draw_leaf_for` and I have not checked that path.", - "", - " The 819.2x720.0 additive draw, which matches no sprite on either screen.", - "", - "โš ๏ธ AND NOT PREMULTIPLIED TEXTURES. The measured non-additive state is", - "0x07010701 -- src=ONE, which is the PREMULTIPLIED form of alpha-over. It would", - "be easy to read 'so the textures are premultiplied and the port's straight", - "alpha-over is wrong everywhere'. The Decoder flagged this before I could: src", - "being ONE means the fixed-function stage multiplies the SHADER's output by 1,", - "and the shader has not been read. The port's alpha-over path measures 0.92-1.31x", - "the frame mean on `ptbase`/`pteff05`, so it is not visibly wrong. Nothing here", - "changes it.", - "", - "๐Ÿ”ด 2026-09-01 โ€” THE JP SCREENS ARE NOT IN THIS MAP, AND THAT IS AN UNMEASURED", - "CLAIM THIS FILE IS MAKING BY OMISSION. Declared here rather than left silent.", - "", - "`main_menu_jp` contains exactly the elements `main_menu` marks additive --", - "ptloop01, ptloop02, ptframe1, ptframe2, pteff10, pteff12 -- and `extras_jp`", - "contains all nine of `extras`'. This map is keyed by SCREEN NAME, and the", - "Decoder's RB_BLENDCONTROL0 log was taken by driving the game to the ENGLISH", - "screens. So the port draws these elements additive on the EN screens and", - "alpha-over on the JP ones, and nothing measured says the game does that.", - "", - "The port is therefore asserting, by omission, that the JP build blends the same", - "elements differently from the EN build -- which is the LESS likely of the two", - "possibilities. Extending the map would extrapolate a measurement onto a build", - "nobody drove to, which is not the port's to do. Both options assert something", - "unmeasured; only one of them is visible. This paragraph is how the quieter one", - "is made visible. Asked in docs/port/BLOCKED.md.", - "", - "โš ๏ธ It does not affect the deliverable: PORT-MISSION ยง7 puts localisation beyond", - "English out of scope and the JP screens are not in the boot path. It affects", - "`tools/port/verify-screen`, where main_menu_jp and extras_jp DIFFER by 0.79 and", - "0.66 -- magnitudes that sit with `title`'s 0.44 sweep-phase residual rather than", - "with their own EN twins' 3.94 and 6.74, which is consistent with the JP rows", - "carrying no additive contribution at all. Consistent with, not established." + "A name-keyed map can only answer for a screen somebody drove the game to. That", + "is what made the Japanese menus an open question (BLOCKED.md H6): the port drew", + "main_menu additive and main_menu_jp alpha-over, asserting by omission that the", + "JP build blends differently. The bit is on the disc for every screen at once, so", + "that asymmetry is now answered statically and H6 needs no capture." ] } diff --git a/crates/sylpheed-export/src/screen.rs b/crates/sylpheed-export/src/screen.rs index a7a10416..e505f239 100644 --- a/crates/sylpheed-export/src/screen.rs +++ b/crates/sylpheed-export/src/screen.rs @@ -103,6 +103,14 @@ pub struct FocusElement { pub id: String, pub declared: String, pub sprite: Option, + /// `true` when the game draws this sprite ADDITIVE โ€” `T8aD +0x04` bit + /// `0x02`, decoded. Absent when the sprite resolves to no `T8aD` header. + /// + /// A leaf's sprite may live in the leaf's own table or in the parent + /// bundle's, so the bit is looked up in the same two places, in the same + /// order, that the PNG is written from. + #[serde(skip_serializing_if = "Option::is_none")] + pub blend_additive: Option, pub pivot: [u32; 2], pub rest: Rest, pub keyframes: Vec, @@ -242,6 +250,23 @@ pub struct Element { /// Paint-order key. `"sprite"` = read from the `T8aD` header at `+0x0A`. /// `"implied"` = **measured off the running game**, for elements that carry /// no header. `"none"` = neither; sorts last. + /// `true` when the game draws this element ADDITIVE โ€” `T8aD +0x04` bit + /// `0x02`. + /// + /// ๐Ÿ”ด **DECODED, and it replaces an authored map.** The port carried an + /// `additive_elements` table in `authored/rendering.json`, keyed by SCREEN + /// NAME and transcribed from the Decoder's per-draw `RB_BLENDCONTROL0` log. + /// A name-keyed map cannot answer for a screen nobody drove the game to, + /// which is why the port was drawing the English menus additive and the + /// Japanese ones alpha-over โ€” asserting by omission that the JP build + /// blends differently. The bit is on the disc for every screen at once. + /// + /// โš ๏ธ `kind_raw` is NOT this field. `kind` is `+40` of the RATC declaration + /// entry; this is `+0x04` of the sprite's own `T8aD` header. Tested over + /// four screens: `kind & 0x2` is *anti*-correlated with the measured map โ€” + /// 0 of 14 additive elements set it and 9 non-additive ones do. + #[serde(skip_serializing_if = "Option::is_none")] + pub blend_additive: Option, pub layer_source: &'static str, #[serde(skip_serializing_if = "Option::is_none")] pub layer: Option, @@ -455,6 +480,9 @@ pub fn export_build( id: id_of(&fe.name), declared: fe.name.clone(), sprite: fsprite, + blend_additive: ui_layout::blend_additive_by_name( + &leaf, &bundle[off..off + size], sp) + .or_else(|| ui_layout::blend_additive_by_name(&b, bundle, sp)), pivot: [fe.pivot_x, fe.pivot_y], rest: Rest { pos: [r.x, r.y], @@ -518,6 +546,9 @@ pub fn export_build( id: id_of(&fe.name), declared: fe.name.clone(), sprite: fsprite, + blend_additive: ui_layout::blend_additive_by_name( + &leaf, &bundle[off..off + size], sp) + .or_else(|| ui_layout::blend_additive_by_name(&b, bundle, sp)), pivot: [fe.pivot_x, fe.pivot_y], rest: Rest { pos: [r.x, r.y], @@ -592,6 +623,7 @@ pub fn export_build( pivot: [el.pivot_x, el.pivot_y], size: (role == "primitive").then(|| [el.pivot_x * 2, el.pivot_y * 2]), parent: el.parent, + blend_additive: ui_layout::sprite_blend_additive(&b, bundle, el), layer_source, layer, focused: el.focused, diff --git a/docs/port/BLOCKED.md b/docs/port/BLOCKED.md index eaa5fa62..0d571695 100644 --- a/docs/port/BLOCKED.md +++ b/docs/port/BLOCKED.md @@ -172,7 +172,7 @@ submitted per frame. |---|---|---| | **H4** | โœ… **ANSWERED 2026-09-01 โ€” and it was not the cause.** The game blends in the **encoded** space: `RB_COLOR_INFO.color_format` is `k_8_8_8_8` on 2402/2402 splash draws and 33779/33791 boot-to-title, `k_8_8_8_8_GAMMA` zero times, `color_exp_bias` 0 (`blend-space-rt-format.txt`). | ๐Ÿ”ด **My premise was wrong and the answer exonerates everybody.** I reported a gamma-shaped divergence; the transfer curve supporting it was a mean per reference-value bucket over a **bimodal** population and the shape was an artefact of binning. Measured with a pre-registered control, **Godot blends encoded too** (alphas 64/128/192 โ†’ 64/128/192 exactly, linear-then-re-encode excluded by 33โ€“74 levels), and so does the reference (integer math on 8-bit values). All three agree; no change needed anywhere. **The real cause of the `verify-screen` rows is ADDITIVE**: the port draws 5 elements additive on `main_menu` and 9 on `extras` โ€” transcribed from your per-draw `RB_BLENDCONTROL0` log โ€” and `ui_layout.rs` has no additive path at all. The divergence tracks the set size (9 โ†’ 6.74, 5 โ†’ 3.94, 0 โ†’ ~0.7). See [`verify-screen-blend-divergence.md`](verify-screen-blend-divergence.md). | | **H5** | โœ… **CLOSED 2026-09-01.** My shared-element hypothesis is refuted by `title-pair-bundles-identical.txt`: `build_12` and `build_15` have **identical declaration bodies** โ€” same elements, sprites, pivots, keyframes, geometry โ€” so identical statistics are one fact, not two coincidences. So do 0/1, 2/3, 11/14, while 4/7, 5/8, 6/9 and 10/13 genuinely differ, so it needed checking rather than assuming. | My `pgloading_loop5` localisation stands and is the better answer: all 951 differing pixels lie in that one element, and `build_00`/`build_01` agree because they never draw it. What is still unexplained is why the two renderers disagree **on that element** โ€” its top-level keyframes hold `a=0x7f` around `rest.t=24` while its leaf expands `pgloading_ring` from scale 0 to 1000 over t=30โ€ฆ130, and `draw_leaf_for` lists only `ptloop01`/`ptloop02`. Not blocking. | -| **H6** | ๐ŸŸก **ANSWERED IN PRINCIPLE, BLOCKED IN PRACTICE โ€” and I need one thing from you.** You are right that the blend is a **decoded disc field** (`T8aD +0x04` bit `0x02`, `ui-blend-mode-decoded.md`) and that deriving it beats transcribing a table โ€” that also deletes an authored entry, which the mission counts as progress. **But `sylpheed-formats` does not expose `+0x04`.** `ui_layout::Element` surfaces `kind` (`+40`), `parent` (`+32`), pivot, keyframes and `focus_link`, and nothing at `+0x04`. | ๐Ÿ”ด **A useful negative, so nobody repeats it: `kind_raw` in my export is NOT that field.** I tested its bit `0x2` against the additive map across four screens and it is **anti-correlated** โ€” 0 of 14 mapped elements have the bit set and 9 unmapped ones do (`kind_raw` is `0x3002` on every button, `0x0` on every element the map lists). **The ask: surface the `+0x04` word on `Element` and tag it.** `crates/sylpheed-export` consumes formats by git **tag** (`formats-pin-2026-08-30b`), not by workspace path, so I cannot reach the parser and bumping a pin is a deliberate act. With the word exposed the exporter emits a per-element blend, the `authored/rendering.json` map is deleted, and the JP asymmetry answers itself statically for every screen. Port `HEAD` `0a9bf4e`; HANDOFF `9ca1eb5`. | +| **H6** | โœ… **CLOSED 2026-09-01 โ€” answered statically, no capture needed, and the authored map is DELETED.** `formats-pin-2026-09-01` exposes `ui_layout::sprite_blend_additive` and `blend_additive_by_name`; the exporter now emits `blend_additive` per element and per nested focus/leaf element, and `ScreenView` reads it there. The JP asymmetry was an artefact of a **name-keyed map** โ€” the bit is on the disc for every screen at once. | Checked before the swap: of 15 elements the map called additive the disc agrees with **all 15 and contradicts none**, and marks **17 more** โ€” including the sweep *leaves* and **twelve on `title`**, where the map was deliberately empty. ๐Ÿ”ด **ONE NEW ASK, and it is a potential counter-example to your โœ… DECODED claim:** adopting the bit moved `main_menu` from **10.88 โ†’ 13.02** against the oracle (deterministic over three runs), and the cause isolates to **`pteff10`** โ€” `extras` gained no top-level element and did not move, which is the control. **Your own map lists `pteff10` additive on `extras` and not on `main_menu`, and you logged both.** Does your per-draw log show `main_menu`'s `pteff10` alpha-over? Shipped anyway โ€” +2.14 is inside the harness's stated ยฑ3.78 capture-phase term and cannot adjudicate a disc fact โ€” but shipped **knowingly**. See [`blend-decoded-adoption.md`](blend-decoded-adoption.md). | | **H7** | โœ… **CLOSED 2026-09-01 โ€” WITHDRAWN BY THE DECODER, same day.** `splash-rate-withdrawn.md` / `1e7343e`: *"WITHDRAW 'the unit rate is per-GamePart' โ€” it was the emulator's frame rate"*, and the section carrying it is struck. ยง1 of `splash-declared-vs-captured.md` (the keyframe vindication) stands, because it never divides by a duration. | The port never moved, so nothing had to be undone. The refutation is kept in [`splash-rate-contradiction.md`](splash-rate-contradiction.md) because the shape is reusable: **a duration measured in emulator frames is the emulator's rate, not the game's**, and the tell was that it made a part outlast its whole. `keyframe_units_per_second` remains **60**, now unchallenged on the splashes. | ## Still open โ€” these block work diff --git a/docs/port/blend-decoded-adoption.md b/docs/port/blend-decoded-adoption.md new file mode 100644 index 00000000..6b722106 --- /dev/null +++ b/docs/port/blend-decoded-adoption.md @@ -0,0 +1,116 @@ +# The blend map is deleted โ€” and adopting the decoded field found a counter-example + +**Status:** โœ… **adopted**, ๐Ÿ”ด **with one measured regression on one element, reported not hidden.** +Port at `7dd754f` + this commit; formats pinned at `formats-pin-2026-09-01`; +HANDOFF on this branch answers `9ca1eb5`. + +`PORT-MISSION` ยง3: *"When the RE agent later decodes something you had authored, +delete the authored entry and let the exporter emit it. That deletion is the +measure of progress."* This is that deletion. + +## What changed + +| | before | after | +|---|---|---| +| source | `authored/rendering.json` โ†’ `additive_elements`, keyed by **screen name** | `blend_additive` per element, emitted by the exporter | +| origin | transcribed from the Decoder's per-draw `RB_BLENDCONTROL0` log | **decoded** โ€” `T8aD +0x04` bit `0x02` | +| reach | three screens somebody drove the game to | every screen on the disc | + +The pin bump is its own commit (`7dd754f`). The exporter emits `blend_additive` +on `Element` **and** on nested focus/leaf elements โ€” both spellings of the +accessor are needed, because a button's focused variant is reached through +`focus_link` and `ptbtn00f.t32` is in `build.sprites` while no element carries it +as `sprite`. `ptbtn00f` is exactly the sharp case: the plate is alpha-over and +its own glow is additive, on one screen in adjacent draws. + +## The check before the swap โ€” the map was a subset, not the answer + +Over `main_menu`, `extras`, `press_start` and `title`: + +| | count | +|---|---| +| map says additive **and** the disc agrees | **15** | +| map says additive and the disc does **not** | **0** โ€” no contradictions | +| disc says additive and the map did not | **17** | + +Nothing transcribed was wrong. It was **incomplete and was being read as +complete**. The 17 include: + +* `pteff03` / `pteff03a` โ€” the sweep **leaves**. `draw_leaf_for` means those are + what actually reach the screen while the map listed their parents + `ptloop01`/`ptloop02`. (Both parent and leaf carry the bit, so this one turned + out to change nothing โ€” established below, not assumed.) +* **twelve on `title`**, where the map was deliberately empty. The port has been + drawing every title effect alpha-over. + +**And it answers `BLOCKED.md` H6 with no capture at all.** The JP asymmetry โ€” the +port drawing `main_menu` additive and `main_menu_jp` alpha-over, asserting by +omission that the JP build differs โ€” was an artefact of a name-keyed map. The bit +is on the disc for every screen at once. + +## ๐Ÿ”ด The regression, which is one element + +Scored against the oracle captures, on the GPU, before and after: + +| screen | before | after | ฮ” | +|---|---|---|---| +| **`main_menu`** | 10.88 | **13.02** | **+2.14** | +| **`main_menu_options`** | 11.56 | **13.57** | **+2.01** | +| `extras` | 13.10 | 13.10 | โ€” | +| `title` | 14.11 | 14.11 | โ€” | +| `title_plate`, `title_band`, both splashes | unchanged | unchanged | โ€” | + +**The scores are deterministic** โ€” two further runs gave 13.02 / 13.10 / 13.57 +to the digit โ€” so this is a real change, not sampling noise. + +### It is `pteff10`, isolated + +* `main_menu`'s only newly-additive **top-level** element is `pteff10`. +* `extras` has **no** newly-additive top-level element, and its score did not + move. That is the control: the same change applied to a screen with nothing new + moves nothing. +* The leaf rule was tested separately by disabling it โ€” `main_menu` stayed at + 13.02, so `pteff03`/`pteff03a` are **not** the cause. That prediction of mine + failed and the rule was restored, being provably neutral here. + +`title` did not move despite twelve newly-additive elements, which is consistent: +`verify-capture` poses at settle `t=198`, and the title's effect quads โ€” +`ptlogo_back2eff1โ€ฆ5`, `ptlogoall_eff`, `pteff01` โ€” are transparent there. + +### Why this is a question for the Decoder and not a tuning knob + +**Their own map lists `pteff10` as additive on `extras` and not on `main_menu`, +and they logged both screens.** So either their per-draw log shows `main_menu`'s +`pteff10` drawn alpha-over โ€” a direct contradiction between a capture and the +disc bit, on one element โ€” or it was not drawn during that capture. The oracle +comparison independently prefers alpha-over there. + +That is a potential counter-example to a โœ… `DECODED` claim whose evidence is +otherwise strong: 35 elements, zero errors both ways, every rival bit tested, and +an out-of-sample prediction on `GP_OPTIONS` that named three of sixteen before +the capture and found exactly three. + +## Why the change ships anyway, stated rather than assumed + +1. `main_menu` carries a **ยฑ3.78 capture-phase term** in the harness's own note โ€” + the capture caught the free-running sweep at an unknown phase. **+2.14 is + inside that stated uncertainty** and cannot adjudicate a disc fact. + `main_menu_options` is a sub-region of the same screen and inherits the same + sweep. +2. The decoded source is far better evidenced than the comparison that moved, and + it **fixes two known defects** โ€” twelve title effects drawn with the wrong + blend, and a JP/EN asymmetry the port was asserting by omission. +3. Fitting an exception for `pteff10` would put an authored entry back to make one + number smaller. That is the move this project keeps having to undo. + +๐Ÿ”ด **This is a known regression shipped deliberately, not an unnoticed one.** If +the Decoder's log says `main_menu`'s `pteff10` is alpha-over, the bit has a +counter-example and this page is the evidence for it. + +## What this does not claim + +* That the bit is wrong. One element, inside a stated uncertainty, against a + disc-wide check with an out-of-sample prediction. +* That `pteff10` on `main_menu` and on `extras` are the same sprite. Not checked. +* That the leaf rule is right โ€” only that it is **neutral here**, so nothing in + this page rests on it. diff --git a/port/scripts/boot.gd b/port/scripts/boot.gd index b1e1f7dd..30710c20 100644 --- a/port/scripts/boot.gd +++ b/port/scripts/boot.gd @@ -305,7 +305,10 @@ func _ready() -> void: var rendering: Variant = export_tree.authored("rendering.json") _draw_leaf_for = [] if rendering == null else rendering.get("draw_leaf_for", []) _loop_leaf_screens = [] if rendering == null else rendering.get("loop_leaf_on_screens", []) - _additive_elements = {} if rendering == null else rendering.get("additive_elements", {}) + # `additive_elements` is DELETED from authored/rendering.json -- the blend is + # decoded now (`T8aD +0x04` bit 0x02) and the exporter emits `blend_additive` + # per element, which `ScreenView._draw` reads directly. Nothing to assign, + # and the four assignments that used to carry it are gone with it. # `--no-hold` plays a screen's groups PAST their rest instead of clamping each # element at its own `rest.t`. A diagnostic, not a mode: `rest.t` is the last # HOLD keyframe before the exit, not the settled state, and the only way to @@ -333,7 +336,6 @@ func _ready() -> void: view.looping_focus = _looping_for(name) view.loop_phase_units = _loop_phase view.draw_leaf_for = _draw_leaf_for - view.additive_elements = _additive_elements view.loop_leaf = _loop_leaf_screens.has(name) view.focused_id = _force_focus if not view.load_screen(export_tree, name): @@ -431,7 +433,6 @@ var _looping: Dictionary = {} ## `authored/rendering.json` `draw_leaf_for`. var _draw_leaf_for: Array = [] ## `authored/rendering.json` `additive_elements` -- measured off the running game. -var _additive_elements: Dictionary = {} ## `authored/rendering.json` `loop_leaf_on_screens`. var _loop_leaf_screens: Array = [] var _script: PackedStringArray = PackedStringArray() @@ -668,7 +669,6 @@ func _advance() -> void: view.looping_focus = _looping_for(name) view.loop_phase_units = _loop_phase view.draw_leaf_for = _draw_leaf_for - view.additive_elements = _additive_elements view.loop_leaf = _loop_leaf_screens.has(name) if not view.load_screen(view.tree, name): push_error(view.tree.error) @@ -1046,7 +1046,6 @@ func _menu_arrive() -> void: view.looping_focus = _looping_for(name) view.loop_phase_units = _loop_phase view.draw_leaf_for = _draw_leaf_for - view.additive_elements = _additive_elements view.loop_leaf = _loop_leaf_screens.has(name) if not view.load_screen(view.tree, name): push_error(view.tree.error) @@ -1572,7 +1571,6 @@ func _raise_overlay(name: String) -> void: # focus record alpha-over after the blend was wired in. The change reported # EXACTLY ZERO against the capture, which is what a change that never reached # the renderer looks like. - overlay.additive_elements = _additive_elements overlay.loop_leaf = _loop_leaf_screens.has(name) overlay.holding = true overlay.time_units = 0.0 diff --git a/port/scripts/screen_view.gd b/port/scripts/screen_view.gd index b3077933..e3f45e84 100644 --- a/port/scripts/screen_view.gd +++ b/port/scripts/screen_view.gd @@ -146,9 +146,6 @@ var loop_phase_units: float = -1.0 var draw_leaf_for: Array = [] ## Elements the game draws ADDITIVELY, by screen -- `authored/rendering.json` -## `additive_elements`. Measured off the running game per draw; see that file for -## what is in the list and, more importantly, what is not. -var additive_elements: Dictionary = {} ## The canvas items backing the paint-order runs. See `_band`. var _bands: Array[RID] = [] ## The canvas item the next `_draw_quad` paints into. It is a member rather than @@ -779,8 +776,17 @@ func _draw() -> void: # have a measurement and that is an accident of those two screens. A cache # keyed on "the additive block" would be correct today and silently wrong on # the first screen that interleaves. - var additive_here: Array = additive_elements.get( - String(screen.get("name", "")), []) + # ๐Ÿ”ด THE AUTHORED MAP IS GONE. `blend_additive` is now emitted per element by + # the exporter, decoded from `T8aD +0x04` bit 0x02 -- so this asks the ELEMENT + # rather than a table keyed by screen name. + # + # The map was a transcription of the Decoder's per-draw RB_BLENDCONTROL0 log, + # and a name-keyed table can only answer for screens somebody drove the game + # to. Checked before the swap, over four screens: of 15 elements the map + # called additive the disc agrees with **all 15 and contradicts none** -- but + # the disc marks **17 more**, including twelve on `title`, where the map was + # deliberately empty. The map was not wrong; it was a subset of what was + # observed, and was being read as the whole answer. var order: Array = screen.get("paint_order", []) # ๐Ÿ”ด BANDS ARE PER DRAW OP, NOT PER ELEMENT, and the plate is why. `ptbtn00` is # drawn alpha-over and its own focus record `ptbtn00f` ADDITIVE -- same screen, @@ -796,11 +802,31 @@ func _draw() -> void: var parts: Array = [[index, "base"], [index, "focus"]] if el.has("focus") \ else [[index, "base"]] for part: Array in parts: - var pid := eid + # Which DECLARATION the blend bit comes from depends on what this + # band actually draws: + # focus -> the focus record's own sprite (ptbtn00f, additive, + # while its base ptbtn00 is not -- the case that forced + # bands to be per draw op rather than per element); + # base -> the LEAF's sprite when this element draws its leaf, + # otherwise the element's own. + # That last line is not a detail: `ptloop01`/`ptloop02` are in + # `draw_leaf_for`, so what reaches the screen is `pteff03`/`pteff03a`, + # and those carry the bit while the parents the old map listed are not + # what was drawn. + var src: Dictionary = el if part[1] == "focus": var fes: Array = el.get("focus", {}).get("elements", []) - pid = String(fes[0].get("id", eid)) if not fes.is_empty() else eid - var add_it: bool = additive_here.has(pid) + if not fes.is_empty(): + src = fes[0] + elif draw_leaf_for.has(eid): + var les: Array = el.get("leaf", {}).get("elements", []) + if not les.is_empty(): + src = les[0] + # Absent means the sprite resolves to no T8aD header -- a `.prm` + # primitive has no header and so no blend bit. Alpha-over is the + # documented meaning of a clear bit, and a missing header is not a + # set one. + var add_it: bool = bool(src.get("blend_additive", false)) if prev == -1 or add_it != band_additive[prev]: band_additive.append(add_it) prev += 1