diff --git a/docs/port/BLOCKED.md b/docs/port/BLOCKED.md index 5bc19c27..d8cc2f01 100644 --- a/docs/port/BLOCKED.md +++ b/docs/port/BLOCKED.md @@ -147,6 +147,43 @@ HANDOFF. | ~~P1–P7 — the keyframe record layout~~ | ~~adopt the corrected pose/time pairing~~ | — | ✅ **ADOPTED 2026-08-29 by pinning `formats-pin-2026-08-29c`.** This row was wrong twice: it said the change *"cannot be taken yet"* and that it *"reaches the port only when that branch lands on `main`"*. **It arrives when the tag is pinned**, which is what MISSION §2's tagging rule exists for. ⚠️ And the knob I tested first, `SYLPHEED_KF_TIME_SHIFT`, is a **retired partial fix** that left pose 0 untimed — the real correction is the tagged crate's default, with the old reading behind `SYLPHEED_KF_TIME_LEGACY=1`. **The blast radius was far smaller than this row predicted**: under the correction *every pose is timed* (866 keyframes, 0 untimed), so `pose_at`'s synthetic-exit branch became dead code rather than wrong code and nothing needed re-deriving. Oracle: `publisher_logo` 1.00 %→**0.75 %**, `developer_logos` 0.39 %→**0.33 %**, `extras`' differing region collapsing from 736×525 to **398×295 at the sweep position**. 🔴 Open cost: `sylpheed-cli` builds from the workspace crate, so `verify-screen` compares two decoder eras until the tag reaches `main`. Revert to the path dependency then. | | ~~P7 / naming — the four unnamed builds~~ | ~~which locale and variant is each of entries 0, 1, 12, 15?~~ | — | ✅ **answered 2026-08-29** (`docs/re/ui-title-build-map.md`): all four are the loading screen, two variants — plain (7 elements) and dressed (10) — decoded from their own `pgloading_*` element names. ⚠️ **Not adopted as names yet, for two reasons the RE agent gave and one the port found.** Theirs: the executable names exactly two, and *which* bundle takes which name is 🟡 undecided, so `LOADING`/`LOADING2` must not go in an asset path; and locale is 🟡 — the English member of a pair is the one in the first half of `GP_TITLE.p00`, 8/8 structurally but only 3/3 where a capture can check, and the three pairs that matter are the three no capture can check. Mine: **the message gives the bundles as "0/1 and 10/11", which is the `is_build` ordinal, and `authored/screen_names.json` is keyed by PAK ENTRY** — in entry space 10 and 11 are `palogo_sqex` and `palogo_gamearts`, the splashes. See the refutation section in `DECISIONS.md`. | +## New ask, 2026-08-31 — derived from HANDOFF `5a7f34d`, at port `HEAD` `6af06bd` + +**What blend mode do `ptframe1` and `ptframe2` use on the main menu?** + +`crates/sylpheed-export/src/screen.rs` already records blend mode as undecoded — +*"assumes straight alpha-over. Blend mode is undecoded, and an additive quad at +alpha 255 would not occlude."* This turns that from a caveat into a **measured +cost**, at a specific place, with a number. + +Measured by suppression — each sprite shadowed with a transparent PNG through the +mod tree, so the footprint is the pixels that actually changed and no coordinate +transform is assumed: + +| element | footprint | signed mean, render − capture | render vs capture | render-brighter | +|---|---|---|---|---| +| **`ptframe1`** | 0.45 % of frame | **−22.72** | **88.4 vs 129.1** | **0.1 %** | +| **`ptframe2`** | 0.50 % | −12.31 | 83.6 vs 111.3 | 12.5 % | +| `pteff12` | 17.31 % | −4.53 | 45.6 vs 47.2 | 5.8 % | + +📌 **The port draws both frames too dark, in one direction, on essentially every +pixel** — 99.9 % of `ptframe1`'s. And it is **not an edge effect**: `ptframe1`'s +residual is *higher on flat pixels* (25.41) than on edges (19.85), the only +elements on the screen where that is true. Body intensity, not outline. + +Those two elements carry **21.9 % of the frame's total squared error from under +1 % of its pixels**, after a global tone LUT has already been applied — so this is +element-specific and survives every global correction. + +⚠️ **I am not fixing this and the answer may not be guessed.** Brightening +`ptframe1` until it matches is exactly *"tuning until they match"*, and a blend +mode invented here is indistinguishable from a decoded one in a month. **What the +port needs is the blend/alpha mode bits for these two elements**, or a statement +that they are not on the disc. + +⚠️ Reach: one screen, one pose (`--loop-phase=0 --leaf-time=0`, focus `ptbtn01`). +I have not checked whether other screens' frame elements do the same. + ## Closed on my own tooling, 2026-08-31 — derived from HANDOFF `abeea3b` | Milestone | Needs | HANDOFF | State | diff --git a/docs/port/DECISIONS.md b/docs/port/DECISIONS.md index 9815ccbf..e7e83ffe 100644 --- a/docs/port/DECISIONS.md +++ b/docs/port/DECISIONS.md @@ -9,7 +9,7 @@ dies, which is what this file is for. -326 sections. Search this before re-deriving anything. +327 sections. Search this before re-deriving anything. * [P0 — the exporter, 2026-08-28](#p0--the-exporter-2026-08-28) * [P1 — Godot draws the screen, 2026-08-28](#p1--godot-draws-the-screen-2026-08-28) @@ -337,6 +337,7 @@ dies, which is what this file is for. * [The menu's edge residual is **not** a misregistration — the Decoder's discriminator, run](#the-menus-edge-residual-is-not-a-misregistration--the-decoders-discriminator-run) * [`GP_DIALOG` 2/3 restored to `authored/flow.json` — on a measurement this time](#gp_dialog-23-restored-to-authoredflowjson--on-a-measurement-this-time) * [The residual map: no local displacement either, and the split I expected is not there](#the-residual-map-no-local-displacement-either-and-the-split-i-expected-is-not-there) +* [Suppression beats coordinates: the menu residual is two frame elements, drawn too dark](#suppression-beats-coordinates-the-menu-residual-is-two-frame-elements-drawn-too-dark) ## P0 — the exporter, 2026-08-28 @@ -15864,3 +15865,73 @@ rather than after. **So the hot region is not one anomalous element with a character of its own**, and that is a finding, not an absence of one. + +## Suppression beats coordinates: the menu residual is two frame elements, drawn too dark + +The Decoder named what sits under my hot tiles (`docs/re/data/menu-hot-tile-inventory.txt`) +and was careful to test both coordinate readings rather than assume one, because +design space and the comparison frame differ by the capture transform. + +📌 **I did not need the transform.** The port has a mod tree, so an element's real +footprint can be *measured*: shadow its sprite with a transparent PNG, render, and +diff my own two renders. The pixels that change are the element, in my comparison +frame, with **no coordinate convention assumed at all**. That is the method I +should have reached for before handing over tile coordinates. + +### The ranking, by residual density on each element's own visible pixels + +| element | footprint | mean \|resid\| | vs frame mean 2.40 | +|---|---|---|---| +| **`ptframe1`** | 0.45 % | **22.72** | **9.47×** | +| **`ptframe2`** | 0.50 % | **13.09** | 5.46× | +| `ptmsg` | 0.46 % | 8.46 | 3.52× | +| `pteff12` | 17.31 % | 4.98 | 2.07× | +| `ptbase` | 3.98 % | 3.34 | 1.39× | +| `pteff10` | 52.15 % | 3.34 | 1.39× | + +### 🔴 This refutes the hypothesis I came in with + +I predicted **the effect element**: `screen.rs` records blend mode as undecoded, +an effect composited wrongly would be tonal and displace nothing, and `pteff12` +sat in the hot band. The measurement says **the frames** — 9.47× against the +effect's 2.07×, and `pteff12`'s ratio is largely inherited from *containing* +`ptframe1` (excluding the frame's pixels drops it from 4.98 to 4.61). + +The mechanism I proposed may still be right. **The element I proposed it for was +wrong**, and the only reason I know is that suppression ranks elements rather than +confirming the one I was looking at. + +### Not an edge effect — and that is what makes it specific + +| | edge px | \|r\| edge | flat px | \|r\| flat | +|---|---|---|---|---| +| **`ptframe1`** | 1868 | 19.85 | 1986 | **25.41** | +| **`ptframe2`** | 2201 | 9.82 | 2086 | **16.54** | +| `pteff12` | 12102 | 7.73 | 137306 | 4.73 | +| `ptbase` | 14092 | 4.69 | 20229 | 2.40 | + +🔴 **The two frames are the only elements whose residual is higher on FLAT pixels +than on edges.** Everything else is edge-weighted, as any render/capture pair is. +So this is the elements' **body intensity**, and signed it is one-directional: +`ptframe1` renders at **88.4 against the capture's 129.1**, with **0.1 % of its +pixels render-brighter**. The port draws them too dark, nearly everywhere, after a +global tone LUT is already applied. + +Filed as an ask in `BLOCKED.md` against HANDOFF `5a7f34d`. **I am not brightening +them** — that is tuning until they match, and the blend bits are the Decoder's. + +### ⚠️ And a defect I nearly reported that was not one + +Suppressing `ptbtn01` — the *focused* button — changed **zero pixels**, and an +**opaque magenta** replacement changed zero too. `ptbtn02` and `ptbtn03` change +9 331 and 7 654. All five buttons carry the same rest fade, so the difference is +focus, and the obvious reading was "the port never draws the focused button". + +`screen_view.gd:746` says otherwise, deliberately: *"A FOCUSED button draws its +record INSTEAD of its base sprite — measured, the focused sprite covers the base +at 100.0 % of base-visible pixels."* + +✅ So the null is a **confirmation**, at a stricter standard than the claim it +confirms: not "100 % of sampled base-visible pixels are covered" but "replacing +the base with opaque magenta changes the frame in zero pixels, exactly". I went +looking for a bug and independently re-derived a documented measurement.