diff --git a/docs/port/HANDOFF.md b/docs/port/HANDOFF.md index 9b5c588a..161b4867 100644 --- a/docs/port/HANDOFF.md +++ b/docs/port/HANDOFF.md @@ -306,13 +306,48 @@ items MISSION parks as emulator-blocked. at **7.97 / 7.98 fps against a requested 8**, so there was no backlog to destroy them. +## ✅ 2026-08-29 — the paint-order tie-break costs your screens ZERO pixels (Q3 closed for you) + +You challenged the advertised blast radius of the unknown tie-break and were +right; the census that came back said **24 overlapping tied pairs** and admitted +nobody had measured how many of them *change a pixel*. Measured now. + +**Overlap was an upper bound and it was loose.** Each bundle was rendered twice, +once in the derived order and once with one tied pair swapped, and diffed — +elements sharing a layer key are contiguous in the derived order, so a swap +paints nothing else in between. Every entry ran a **control** first: a swap of an +overlapping pair with *different* keys, which must move pixels (it moved 36 305 +to 771 479 px, max Δ 254). Where no control was available the page says so. + +* ✅ **On `EXTRAS` (entries 6/9) and the main menu (5/8): 0 px.** The tied + `ptframe` pairs each ink ~3 600 px and **share 0**. The bounding-box overlap + was an artefact of approximating an element's rect as pivot × 2. Layers that + never touch the same pixel cannot be mis-ordered under *any* blend, so this + does not depend on our compositor being right. +* ✅ **The splashes have no ties; the title and developer splash use a measured + order.** So across all five screens the tie-break's cost is **zero**, not "one + drawable pair, consistent with a capture". +* 🔴 **A claim of ours is withdrawn.** This page's source said a wrong tie-break + "can be wrong by a whole layer", from the geometry that `ptlogo_back2eff5` + *fully contains* two other glows. Rendered, that swap moves 6 390 px by a + maximum of **Δ2 out of 255**. The glows are near-transparent; containment is + not occlusion. Nobody had rendered it before asserting it. +* 🟡 **Outside your set**, the worst tie-break cost anywhere in `GP_TITLE` is + **Δ3/255** (the Japanese title, entry 7). ⚠️ Those Δ figures *do* assume our + alpha blend — what they rule out is a structural error, a layer appearing or + vanishing, not a shading one. + +[`ui-paint-order-derived-check.md`](../re/structures/ui-paint-order-derived-check.md#-what-the-tie-break-actually-costs-in-pixels-2026-08-29) · +[data](../re/data/paint-order-tie-pixel-cost.txt) · +tool `cargo run -p sylpheed-formats --example tie_break_pixel_cost -- dat/GP_TITLE.pak` + ## Status | | Question | State | Answer / link | |---|---|---|---| | Q1 | keyframe time unit + ramp shape | ✅ answered | ramp is **linear**; **2 units per rendered frame**; **`1 unit = 1/60 s` — measured**, the idle title presents at 28.5 fps so the game is 30 Hz — [`ui-keyframe-time-unit.md`](../re/ui-keyframe-time-unit.md). ✅ **The group timeline is now DECODED too (2026-08-29) and the gap is closed**: a placement group is `frames` records of `{u32 time; 36-byte pose}` after an 8-byte header, so a pose's time is the word **before** it, pose 0's time is the group's lead-in word, and **every** pose is timed — including the last, which nothing could time before. Disc-wide over 13 991 groups with controls; the old reading makes **0 of 1 042** multi-segment alpha ramps constant-rate against 857 of 1 540. `SYLPHEED_KF_TIME_SHIFT` is retired (it had the association right but left pose 0 untimed, which is the whole reason it appeared to cost 13.1 % of build 7). Static renders are byte-identical — [`ui-keyframe-record-layout.md`](../re/ui-keyframe-record-layout.md) | -| Q2 | which build is which screen state | ✅ answered | `GP_TITLE` is **8 screens shipped twice, EN/JP**: 4/7 title art, 2/3 the `PRESS Ⓐ` plate, 5/8 main menu, 6/9 `EXTRAS`, and ✅ **0/1 and 10/11 are the LOADING screen** — two variants, plain and dressed, decoded from their `pgloading_*` element names (2026-08-29). 🟡 which of the two is `LOADING` vs `LOADING2` is undecided; 🟡 the English member of a pair is the one in the first half of the data segment — [`ui-title-build-map.md`](../re/ui-title-build-map.md) | -| Q3 | paint order for the six screens | ✅ answered, ❔ tie-break | **decoded**: a `u16` layer key at `+0x0A` of each `T8aD` sprite header, stable-sorted with declaration index; unkeyed elements get an implied key. Confirmed on 5 measured orders + `EXTRAS` vs a capture. One residual: the **tie-break** is unknown and bites on one element of the title — [`structures/ui-paint-order-key.md`](../re/structures/ui-paint-order-key.md). ⚠️ **The key does not fully order a screen**: elements sharing a key are tied, and the tie-break is ❔ **undecodable from the bundle** — declaration table, `T8aD` header (exhaustive: every offset 0x00–0x7f at u8/u16/u32, both directions, **0** fields match the measured order against **64** for the control) and the RATC child order all give the same order the game does *not* use. Your exposure is **2 overlapping tied pairs on `EXTRAS`** — [`structures/ui-paint-order-derived-check.md`](../re/structures/ui-paint-order-derived-check.md) | +| Q2 | which build is which screen state | ✅ answered | `GP_TITLE` is **8 screens shipped twice, EN/JP**: 4/7 title art, 2/3 the `PRESS Ⓐ` plate, 5/8 main menu, 6/9 `EXTRAS`, and ✅ **pak entries 0/1 and 12/15 are the LOADING screen** — two variants, plain and dressed, decoded from their `pgloading_*` element names (2026-08-29). ⚠️ **Read that in ENTRY space.** This row said "0/1 and 10/11" until 2026-08-29; that is true only of `screen list`'s ordinals, where 10→entry 12 and 11→entry 15. In entry space 10/11 are the **publisher and developer splashes** (`palogo_sqex`, `palogo_gamearts`). The port caught it; see [METHOD](../re/METHOD.md#mechanics-that-have-bitten). 🟡 which of the two is `LOADING` vs `LOADING2` is undecided; 🟡 the English member of a pair is the one in the first half of the data segment — [`ui-title-build-map.md`](../re/ui-title-build-map.md) | +| Q3 | paint order for the six screens | ✅ answered, ❔ tie-break | **decoded**: a `u16` layer key at `+0x0A` of each `T8aD` sprite header, stable-sorted with declaration index; unkeyed elements get an implied key. Confirmed on 5 measured orders + `EXTRAS` vs a capture. One residual: the **tie-break** is unknown and bites on one element of the title — [`structures/ui-paint-order-key.md`](../re/structures/ui-paint-order-key.md). ⚠️ **The key does not fully order a screen**: elements sharing a key are tied, and the tie-break is ❔ **undecodable from the bundle** — declaration table, `T8aD` header (exhaustive: every offset 0x00–0x7f at u8/u16/u32, both directions, **0** fields match the measured order against **64** for the control) and the RATC child order all give the same order the game does *not* use. ✅ **Your exposure is now measured at ZERO PIXELS (2026-08-29).** The 2 overlapping tied pairs on `EXTRAS` are `ptframe3`×`ptframe4` (the other is a `loop*` you never draw), and rendering the screen with that pair swapped changes **0 px** — because the two sprites put ink on ~3 600 pixels each and **share none of them**; the 102×132 "overlap" was a bounding-box artefact. Same on the main menu's `ptframe1`×`ptframe2`. This is blend-independent: layers that never touch the same pixel cannot be ordered wrongly. **Nothing about the tie-break can change a pixel on any of your five screens** — [`structures/ui-paint-order-derived-check.md`](../re/structures/ui-paint-order-derived-check.md) | | Q4 | button → GamePart | ✅ answered | **measured** which screen all **5** buttons open — `NEW GAME` → `DIFFICULTY` → `SELECT DATA`, not a hang. The **GamePart id is still a name match**, not a measurement — [`menu-navigation-semantics.md`](../re/menu-navigation-semantics.md) | | Q5 | navigation semantics | ✅ answered | **measured**: initial focus varies boot to boot (2× `TUTORIAL`, 2× `NEW GAME`); ⬆⬇ one step, **wraps both ends**; ⬅➡ do nothing; Ⓑ returns to the parent **with focus restored**; Ⓑ on the main menu → title; Ⓑ on the title → nothing — [`menu-navigation-semantics.md`](../re/menu-navigation-semantics.md) | | Q6 | boot sequence + what drives it | ✅ answered | sequence **measured** end to end; the driver is **code, not data** — four search spaces closed, so the port **authors** the sequence — [`boot-config-and-gamepart-registry.md`](../re/boot-config-and-gamepart-registry.md) | @@ -1180,7 +1215,7 @@ here until 2026-08-28 and is now settled.) |---|---|---| | 🟡 | **cue NAME → event binding** (Q8) | event→**wave** is measured for move/confirm/back; that the cursor's wave is the cue *named* `SE_UI_CURSOR` is still read off the authors' identifiers | | ❔ | **the other ~319 SE cues** (Q8) | located one at a time by triggering them; only the three the menu needs have been done | -| 🟡 | **the paint-order tie-break** (Q3) | eight candidates refuted. 🔴 **The cost was understated and is corrected 2026-08-29** — the port challenged it and was right. Over all 16 `GP_TITLE` entries: 5 use a **measured** order and carry no tie risk; the other 11 fall back to the derived order and **7 of them have overlapping ties, 24 pairs in total** — 16 of those on the **Japanese title** (entry 7) alone, 2 each on `EXTRAS` EN/JP, 1 each on the four loading bundles. ⚠️ Overlap bounds *where* a wrong tie-break could show; nobody has measured how many actually change a pixel — [`ui-paint-order-derived-check.md`](../re/structures/ui-paint-order-derived-check.md) · [census](../re/data/paint-order-ties-gp_title.txt) | +| ✅→🟡 | **the paint-order tie-break** (Q3) | ✅ **Its COST is now measured and it is zero on all five port screens** (2026-08-29) — the tied `ptframe` pairs share no ink, and the worst tie-break effect anywhere in `GP_TITLE` is Δ3/255. What stays open is only *why* the game orders ties as it does. Eight candidates refuted. 🔴 **The cost was understated and is corrected 2026-08-29** — the port challenged it and was right. Over all 16 `GP_TITLE` entries: 5 use a **measured** order and carry no tie risk; the other 11 fall back to the derived order and **7 of them have overlapping ties, 24 pairs in total** — 16 of those on the **Japanese title** (entry 7) alone, 2 each on `EXTRAS` EN/JP, 1 each on the four loading bundles. ⚠️ Overlap bounds *where* a wrong tie-break could show; nobody has measured how many actually change a pixel — [`ui-paint-order-derived-check.md`](../re/structures/ui-paint-order-derived-check.md) · [census](../re/data/paint-order-ties-gp_title.txt) | | 🟡 | **GamePart ids behind the buttons** (Q4) | the *screens* are measured; the ids are a name match onto the executable's class names | | 🟡 | **the boot transitions in code** (Q6) | both levels decoded — phase at `this+132` (`entry→2`, `2→0`, `2→3`, `3→4`, `4→2`) and state at `this+136` inside phase 4. Phase 0 = splash (`LOGO`), phase 2 = title + `PRESS Ⓐ`, phase 4 = menu. Unknown: what the event *numbers* mean | | 🟡 | **Ⓑ leaving the main menu** (Q5) | **upgraded 2026-08-29 (later).** The idle half of this objection is **refuted**: the main menu does not self-return for **≥ 60 s** untouched, and the ~8–10 s idle belongs to the **title**. Ⓑ is delivered (Canary logs `vk=5801`) and is the only input in ≥ 100 s before the return, so the **ordering is measured**; the latency is not (a backlogged probe void). The footer point stands — the main menu is still the only screen not advertising Ⓑ — [`menu-navigation-semantics.md`](../re/menu-navigation-semantics.md#-refutation-attempt-2026-08-29--the-main-menus-own-footer-does-not-advertise-ⓑ) | diff --git a/docs/re/METHOD.md b/docs/re/METHOD.md index d37e2767..408a317d 100644 --- a/docs/re/METHOD.md +++ b/docs/re/METHOD.md @@ -89,6 +89,18 @@ agent's loop prompt, i.e. nowhere durable. See [`README.md`](README.md) for the * **Raw grep cannot see inside compressed pak entries.** * **Commit messages go in a file** (`git commit -F`); a literal `|` in a table cell needs escaping; `git log --all -- ` can hang. +* **"Build 10" of a pak is ambiguous — always say which index space.** + `sylpheed-cli screen list GP_TITLE.pak` reports **12** builds and numbers them + 0–11; `screen list --all` reports **16** and numbers them 0–15. Only under + `--all` does the ordinal equal the pak entry. Without it, ordinal 10 is pak + entry **12** and ordinal 11 is entry **15** — so "builds 10/11 are the loading + screen" and "entries 12/15 are the loading screen" are the same true statement, + while "**entries** 10/11 are the loading screen" is false: those are the + publisher (`palogo_sqex`) and developer (`palogo_gamearts`/`seta`/`anima`) + splashes. This cost a wrong line in HANDOFF that the port caught, and it would + have validated silently because the port's `screen_names.json` is keyed by + entry. **Write `entry N`, not `build N`, whenever the number leaves this + repository.** ## Runtime / emulator