port: pin formats-pin-2026-08-29c -- the knob I tested last iteration was retired

I tested the wrong switch. SYLPHEED_KF_TIME_SHIFT is a superseded partial fix: it
got the association right but LEFT POSE 0 UNTIMED, which is exactly why the
untimed keyframe appeared to move from last to first. The real correction is the
DEFAULT in the tagged crate, with the old reading behind SYLPHEED_KF_TIME_LEGACY.
So last iteration's five rows measured a mismatch against a knob nobody should
use -- I suspected they were not decisive, I did not suspect the knob was retired.

THE CONSEQUENCE IS MUCH SMALLER THAN I BUDGETED. A placement group is an 8-byte
header then frames x {u32 time; 36-byte pose}, so pose 0's time is the group's
lead-in word and every pose is timed. Measured on the re-export: 866 keyframes,
0 untimed. `pose_at`'s "the final keyframe carries no t, so give it a synthetic
time" premise does not invert, it DISAPPEARS -- dead code rather than wrong code,
which is why nothing needed re-deriving. And the leaf now reads t=0 x=-639,
t=150 x=-39, t=540 x=1521, giving x=781 at t=355: the Decoder's predicted
top-left, and the 1300 px discrepancy is gone.

Pinned by tag, which is what MISSION section 2's tagging rule is for. BLOCKED was
wrong in both directions -- "cannot be taken yet" AND "only when that branch lands
on main". It arrives when the tag is pinned.

COST STATED: sylpheed-cli builds from the workspace crate, so until this reaches
main the exporter and the reference renderer read different decoders and
verify-screen compares two eras. verify-capture is unaffected -- it compares
against oracle captures and never touches the CLI. Revert to the path dependency
when the tag is an ancestor of main.

Oracle: publisher_logo 1.00% -> 0.75%, developer_logos 0.39% -> 0.33%, and
extras' differing region COLLAPSING from 736x525 to 398x295 at the sweep position
-- the residual localised onto the one element still in question. title unchanged
at 1.82%, now posed at t=355, the Decoder's FITTED sweep time. t=390 measures
1.65% and picking it would be fitting the pose to the score.

REFUTED, MINE: "ptlogo_eff2 is the single drawn element at a scale that is not a
whole multiple of 100%". That census was parents-only; the 45 leaves hold
thirteen distinct non-whole-multiple scales and 125% is among the rarest at two.
The claim's real content was "the only one the port draws" -- about my element
set, not the disc.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N7FiFFFwbvG2uxdcEh8HyF
This commit is contained in:
Sylpheed port agent
2026-08-29 18:20:41 +00:00
parent 1380bbdcff
commit e5be03709a
5 changed files with 147 additions and 7 deletions

View File

@@ -128,7 +128,7 @@ HANDOFF.
| ~~P6 — runtime headroom~~ | ~~the Master bus clips~~ | — | 🟢 **withdrawn by the port, 2026-08-29 — it was my own overstatement.** Filed 🔴 twice on a peak reading of 0.0 dBFS. Measured properly: **43 samples at full scale in 5.9 s and 24 in 98.5 s, longest clamped run 0.25 ms** — the disc's own `confirm` cue touching the ceiling on a transient, possibly only in the recording's 16-bit conversion since Godot mixes in float. Not a defect, and nothing is changed: attenuating to buy headroom would be an unmeasured level decision of exactly the kind this port refused for the BGM loop point. **A peak reading is not a clipping measurement** — one sample at 0 dBFS and two seconds of square wave give the same number. |
| Modding — rule 4 | ~~base-and-overrides is unimplemented~~ | — | ✅ **implemented 2026-08-29, and it was not blocked on anybody.** `MODDING.md` calls it a constraint on the exporter *today*; nothing read `data/mods/` for eight milestones. `ExportTree.resolve` now shadows by path for every asset kind, each replacement is logged as it is read, and `.gitignore` excludes the directory's contents — a mod is usually an edited game asset, and that directory was the one place git would have taken one. ⚠️ The `export/` vs `data/base/` naming split between `PORT-MISSION.md` §3 and `MODDING.md` is **raised, not resolved**: only the human changes a mission. |
| P1P7 — the keyframe record layout | **adopt the corrected pose/time pairing** | — | 🟡 **AVAILABLE TODAY — this row's "cannot be taken yet" was WRONG.** It said the change reaches the port only when the Decoder's branch lands on `main`. It does not: `ui_layout.rs` in this checkout carries the corrected association behind **`SYLPHEED_KF_TIME_SHIFT=1`**, and has all along. I read that file twice in one session without noticing the switch. **Measured** by re-exporting under it and asking the oracle: `publisher_logo` 1.00 %**0.75 %**, `developer_logos` 0.39 %**0.33 %**, `title` RMSE 21.07 → **20.41** — but `main_menu` 0.06 % → 0.10 % and `extras` 0.20 % → 0.24 % **worse**. ⚠️ **Not decisive, and the reason matters**: under the shift the untimed keyframe moves from **last to first**, while `pose_at` is written around *"the final keyframe carries no `t`"*. So the numbers measure a renderer/association mismatch, not the association. Adopting it means re-deriving `exit_ramp_units`, `settle_units`, `spin_period_units` and the plate against a layout where the untimed frame is first — real work, not a flag flip. Export left on the default. |
| ~~P1P7 — 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`. |
## Answered since this file was last written — no longer blocking

View File

@@ -4170,3 +4170,90 @@ same run with one input changed.
cues reach the Master bus at the right moments; it does not show they are the
cues the game plays — that is HANDOFF Q8, and `authored/audio.json` still carries
the offsets as measured rather than decoded.
## Pinned `formats-pin-2026-08-29c` — and the knob I tested last iteration was retired
🔴 **I tested the wrong switch.** `SYLPHEED_KF_TIME_SHIFT` is a **superseded
partial fix**: it got the association right but **left pose 0 untimed**, which is
exactly why the untimed keyframe appeared to "move from last to first". It does
not exist in the current parser. The real correction is the **default** in the
tagged crate, with the old reading behind `SYLPHEED_KF_TIME_LEGACY=1` — the
opposite polarity from what I had.
So last iteration's five rows measured a renderer/association mismatch **against
a knob nobody should use**. I suspected they were not decisive; I did not suspect
the knob itself was retired.
### The consequence is smaller than I budgeted for: there is no untimed keyframe
A placement group is an 8-byte header then `frames` × `{u32 time; 36-byte pose}`,
so pose 0's time is the group's lead-in word and **every pose is timed, including
the last.** Measured on the re-export: **866 keyframes, 0 untimed.**
`pose_at`'s premise — *"the final keyframe carries no `t`, so it is given a
synthetic time `exit_ramp_units` after the last timed frame"* — does not invert,
it **disappears**. The branch is now dead code rather than wrong code, which is
why nothing needed re-deriving to adopt this.
And the leaf reads as the Decoder's table says: t=0 x=639, t=150 x=39, t=540
x=1521. At t=355 that interpolates to **x = 781** — the top-left it predicted, and
the 1 300 px discrepancy is gone.
### Pinned by tag, which is what MISSION §2 is for
> *"The RE agent tags when it lands something you need and tells you over the
> message channel — that is how you stay current without floating."*
That is precisely what happened, so `crates/sylpheed-export/Cargo.toml` now pins
`formats-pin-2026-08-29c` by tag. ⚠️ **`BLOCKED.md` was wrong in both
directions** — it said the change "cannot be taken yet" *and* that it arrives
only when the branch lands on `main`. It arrives when the tag is pinned.
🔴 **The cost, stated rather than discovered later:** `sylpheed-cli` builds from
the **workspace** crate, so until this reaches `main` the exporter and the
reference renderer read **different decoders**, and `verify-screen` is comparing
two eras rather than detecting drift. `verify-capture` is unaffected — it
compares the port against oracle **captures** and never touches the CLI — and it
is the check that matters. Revert to the path dependency the day the tag is an
ancestor of `main`.
### What the oracle says
| screen | before | after |
|---|---|---|
| `publisher_logo` | 1.00 % | **0.75 %** |
| `developer_logos` | 0.39 % | **0.33 %** |
| `extras` | 0.20 %, region **736×525** | 0.19 %, region **398×295 at (441,230)** |
| `main_menu` | 0.06 % | 0.06 % |
| `title` | 1.82 % | 1.82 % |
The splashes improve outright. ⚠️ **`extras` is the interesting row**: the *area*
barely moved but its differing region **collapsed onto the sweep position**
(441, 270) — the residual is now localised to the one element still in question
rather than spread over the screen.
The title does not move. Its row is now posed at **t=355**, the Decoder's fitted
sweep time, because the leaf group ends at t=600 with the quads parked off-screen
at x=1521 — posing at the settle simply omits them. ⚠️ **t=355 is not the time
that minimises the difference**: t=390 measures **1.65 %**. Picking that would be
fitting the pose to the score, which is what this harness exists not to do.
## Refuted — my own "the single non-whole-multiple scale in the export"
`DECISIONS` has said since P1 that `ptlogo_eff2` is *"the single drawn element in
the whole export at a scale that is not a whole multiple of 100 % (125 %)"*.
**That census was parents-only.** Opening the 45 leaves finds **thirteen** distinct
non-whole-multiple scales — 75, 96, 99, 101, 103, 112, 125, 150, 204×208,
210×220, 250 — and 125 % is among the *rarest* at two occurrences.
`ptlogo1`/`ptlogo2` carry 101/103/112 on the **English** title.
The claim's real content was *"the only one **the port draws**"* — a fact about my
element set, not about the disc. Corrected.
🔴 **And `ptlogo_eff2` stays withheld, now for a stronger reason than caution.**
Its 125 % is a **pop**, not a steady scale: scale-0 → 125 % → scale-0 between
t=50 and t=107, ≈0.95 s. The leaf draws at 100 %, as **two superimposed copies**
at alpha 160 and 80, each rotating 360° over 960 units — 16 s per revolution.
**If parent scale gates the leaf it is a 0.95 s flash; if the leaf runs free it
spins for 16 s.** Nothing on the disc chooses, `title_jp` has no oracle capture,
and the Japanese-locale capture MISSION has parked is what would settle it.