From 60595d406263b71e1d83e903dc392970f853237a Mon Sep 17 00:00:00 2001 From: Sylpheed port agent Date: Sat, 29 Aug 2026 10:46:05 +0000 Subject: [PATCH] port: P5 groundwork -- the focus record, measured and checked against a capture P5 is the lowest unfinished milestone. This does not implement navigation; it settles how a focused button is drawn, because three claims sat under that and none had been checked from this side. Refutation attempts, all three failed -- recorded either way, per PROTOCOL: * HANDOFF ask 3's '(7,7)' focus offset SURVIVES, and more strongly than stated: over a 15x14 scan of the whole offset space it is a UNIQUE ISOLATED cell at 100% coverage on all five buttons, with (6,6) and (7,6) both below 90%. The centre and pivot alignments reproduce the 78-84% band the RE agent called misleading. * ORACLE-CAPTURES' 'a crop, not a scale' SURVIVES. Its own evidence -- a +/-6 px cross-correlation -- cannot tell a crop from a 0.9375 scale, so it was re-tested with a scale-sensitive one: button text bands land at design y + 23 for all three unoccluded buttons, an exactly 1:1 vertical mapping. (The prose understates 45 missing rows as 'the missing row'.) * My own suspicion that the declared geometry disagreed with the capture by 6 px was MY ARITHMETIC ERROR, written up rather than quietly dropped: I took the top-left as pos - pivot. pos IS the top-left; the pivot is the anchor scale grows about and cancels at 100%, exactly the 'can be got wrong invisibly' that screen_view.gd:120 warns about. What is actually true, and what P5 does with it: * The exporter ALREADY emits the focus record's second element, the 42x46 ring ptbtneff01, for all five buttons. That gap is in the renderer, not the exporter -- nothing to change in crates/sylpheed-export. * Base minus focus is (7,7) directly from the declared positions, so P5 draws each focus element at its own pos and authors no constant. * ptbtn04 is 1 px off the 80 px grid ON THE DISC (base rows 162 242 322 401 482; focus rows a clean 155 235 315 395 475). So its base->focus delta is (7,6) while its art aligns at (7,7). Do NOT derive focus placement from the base by a constant: it would be wrong on exactly one button and right on the other four. Verified against captures rather than our other renderer: diffing live-main-menu against live-main-menu-options-focused isolates one cluster at x 506..702, y 398..445, and ptbtn04's focus record under pos-as-top-left spans x 500..706, y 395..451. Under pos - pivot it predicts x 433..604, y 367..422, which matches nothing in the capture and no other button either. Also records an instrument that FAILED ITS CONTROL and was discarded: a masked NCC template matcher returned NCC 0.096-0.206 with three of five results pinned to the search boundary when asked to re-find the base sprites at their known positions. None of its output is used. Filed so this is not rebuilt. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01Evuhbt8pxKJEUvwfniwYkU --- docs/port/DECISIONS.md | 119 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) diff --git a/docs/port/DECISIONS.md b/docs/port/DECISIONS.md index 60f0a13c..5239929c 100644 --- a/docs/port/DECISIONS.md +++ b/docs/port/DECISIONS.md @@ -809,3 +809,122 @@ The port keeps running `verify-screen` over all 16 screens every iteration. A consistency check is still worth having — it is total, it is cheap, and it is what catches a divergence the RE agent introduces on their side. It is simply not a grade, and this file will stop quoting it as one. + +## P5 groundwork — the focus record, checked against a capture, 2026-08-29 + +P5 is the lowest unfinished milestone (P0–P4 are gated above). This iteration did +not implement navigation; it did the measurement P5 needs first, because the one +thing P5 is built on — how a focused button is drawn — had three claims attached +to it and none of them had been checked from this side. + +### The exporter already emits the focus record's second element + +HANDOFF ask 3 answers with a 🔴: *"what you are actually missing is the focus +record's SECOND element"* — `ptbtneff01.t32`, a 42×46 glowing ring, declared +before the bright label in `ptbtn0Nf.rat`. + +**That gap is in the renderer, not the exporter.** `export/screens/title/main_menu.json` +already carries both, in declaration order, under `focus.elements`, for all five +buttons — `ptbtneff01` then `ptbtn0Nf`, each with its own pivot, rest pose and +keyframes. Nothing needs to change in `crates/sylpheed-export` for the ring. What +is missing is that `screen_view.gd` draws only one sprite per focused button. +Recording this so P5 does not re-open the exporter looking for it. + +The ring's size checks out exactly: `ptbtneff01.png` is **42×46**, as stated. + +### The `(7,7)` focus offset survives a refutation attempt, uniquely + +Ask 3 states the focused sprite covers the base at 100.0 % of base-visible pixels +"once aligned properly (the true offset is **(7,7)**, and at the centre alignment +it reads a misleading 78–84 %)". P5 builds directly on this, so it was worth +attacking. + +Re-ran the RE agent's own metric on the exported PNGs — over every pixel where the +base sprite is visible, the fraction where the focus sprite's alpha ≥ the base's — +scanning the whole offset space, not just the stated answer: + +| alignment | ptbtn01 | ptbtn02 | ptbtn03 | ptbtn04 | ptbtn05 | +|---|---|---|---|---|---| +| **(7,7)** | **100.00 %** | **100.00 %** | **100.00 %** | **100.00 %** | **100.00 %** | +| geometric centre | 80.58 % | 79.20 % | 79.58 % | 79.45 % | 79.45 % | +| pivot-to-pivot | 80.58 % | 86.59 % | 87.40 % | 79.45 % | 84.58 % | + +**The refutation fails, and more strongly than the original claim.** Over a +15×14 offset scan, `(7,7)` is a *unique isolated cell* at 100 % on every one of +the five buttons — every neighbouring offset, including (6,6) and (7,6), falls +below 90 %. The centre and pivot alignments reproduce the 78–84 % band the RE +agent reported as misleading. A claim that survives a search of its whole +parameter space is worth more than one checked at a single point, so this is +recorded as strengthened, not merely unrefuted. + +### `(7,7)` is not a constant to apply — it is what the declared positions already say + +An earlier version of this analysis had the port disagreeing with the capture by +6 px. **That was my arithmetic error and it is worth writing down**, because it is +the mistake this format invites: I computed each element's top-left as +`pos - pivot`, which gives base→focus deltas of (13,13) and a 29 % coverage — a +confident wrong number. + +`pos` **is** the top-left. `screen_view.gd:121` is +`Rect2(pos - pivot*(s - 1), natural*s)`: the pivot is the anchor scale grows +about and it *cancels at 100 %*, which is exactly the "can be got wrong +invisibly" the comment there warns about. Getting it wrong invisibly is what +happened. + +With `pos` as the top-left, base − focus is `(542,162) - (535,155)` = **(7,7)** +directly, on four of the five buttons. So P5 draws each focus element at its own +declared `pos` and needs no offset constant at all. Nothing to author. + +### The one real find: `ptbtn04` is 1 px off the grid on the disc + +The focus records sit on a clean 80 px pitch — 155, 235, 315, 395, 475. The +**base** records do not: 162, 242, 322, **401**, 482, i.e. spacings 80, 80, **79**, +**81**. So `ptbtn04`'s declared base→focus delta is **(7,6)**, while the art +itself aligns at (7,7) — the coverage scan puts `ptbtn04` at 100 % on (7,7) and +below 90 % on (7,6), the same as every other button. + +This is 1 px of authoring jitter on the disc, not a decode error, and it has one +consequence worth stating: **do not derive the focus placement from the base by a +constant.** Draw the focus record at its own declared `pos`. A port that +"simplified" this to base + (7,7) would put `ptbtn04`'s focus art 1 px off, and +would look right on the other four. + +### Verified against a capture, not against our other renderer + +Diffing two oracle frames isolates what focus changes without any instrument in +the path: `live-main-menu.png` vs `live-main-menu-options-focused.png` differ in +one tight cluster of 6 338 px at **x 506..702, y 398..445**. `ptbtn04` is the +`OPTIONS` button, and the union of its focus record under the `pos`-as-top-left +reading — ring `ptbtneff01` at (500,396) 42×46 plus label `ptbtn04f` at (535,395) +172×56 — is **x 500..706, y 395..451**. Those agree on all four edges to within a +few px of near-transparent sprite border. + +Under the `pos - pivot` reading the same record predicts x 433..604, y 367..422, +which matches nothing in the capture — and *no* button matches that cluster. The +capture, not our renderer, is what settles it. + +### An instrument that failed its own control, and was therefore discarded + +To locate the buttons independently I wrote a masked normalised-cross-correlation +template matcher and ran it as PROTOCOL requires — **through a control first**: +match each *base* sprite against the *plain* capture, where the declared position +is known and the answer must be a (0,0) delta. + +It returned deltas of (13,5), (6,−19), (12,21), (−16,22), (6,8) at NCC +**0.096–0.206** — noise, with three of five pinned to the ±22 search boundary. +The control fails, so the instrument is dead rather than tuneable, and **none of +its output is used above.** The button art is dark, low-contrast and further +crushed by the capture's γ ≈ 1.49 ramp, which is the likely cause; a matcher for +this corpus would have to work on gradients rather than luminance. Filed so the +next iteration does not rebuild the same broken tool. + +### What P5 still needs, and has not got + +* **Initial focus is not stable across boots** (Q5: 2× `TUTORIAL`, 2× `NEW GAME`). + That is a value to author, with a `why` naming Q5 — it is not written yet. +* **The ring's own animation is unread.** `ptbtneff01`'s two keyframes go + `rotation_deg` 0 → **360** at t=120 with no second timed keyframe, i.e. a full + turn. Whether it spins continuously while focused, or turns once and holds, is + the group-loop question again — answered "groups hold" for build-in animations, + but a 360° hold and a 0° hold are the same pose, so *this* group cannot be told + apart by its rest pose. Not guessed; raised below.