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) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Evuhbt8pxKJEUvwfniwYkU
This commit is contained in:
Sylpheed port agent
2026-08-29 10:46:05 +00:00
parent d090c9c75c
commit 3e280d63de

View File

@@ -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 (P0P4 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 7884 %)". 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 7884 % 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.0960.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.