diff --git a/docs/port/HANDOFF.md b/docs/port/HANDOFF.md index 161b4867..7c399e6d 100644 --- a/docs/port/HANDOFF.md +++ b/docs/port/HANDOFF.md @@ -348,7 +348,7 @@ tool `cargo run -p sylpheed-formats --example tie_break_pixel_cost -- dat/GP_TIT | 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 ✅ **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) | +| Q4 | button → GamePart | ✅ answered | **measured** which screen all **5** buttons open, by pressing each one and reading the screen's own title off the framebuffer. ✅ **In the form you need it: exactly ONE main-menu button opens a `GP_TITLE` entry.** `EXTRAS` → **entry 6** (EN) / **9** (JP). The other four leave the archive: `NEW GAME` → `DIFFICULTY` → `SELECT DATA`; `LOAD GAME` → the save-slot list; `TUTORIAL` → the lesson list; `OPTIONS` → GAME/CONTROL/SOUND/SCREEN SETTINGS. None of those four is a `GP_TITLE` build — so a menu→submenu→back cycle inside this archive is `main menu ↔ EXTRAS` and nothing else. The **GamePart id is still a name match**, not a measurement, and 🔴 the "cheap way to measure it" this page used to point at is a dead route (the guest words are monotonic counters, not a screen id) — [`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) | | Q7 | transitions | ✅ answered | a **fade through black**, drawn by the screen's own last-painting `.prm` quad. Fade-in ramp is **decoded** from its keyframes; the ~0.4 s fade-out is **measured** (not in the file) — [`screen-transitions.md`](../re/screen-transitions.md) | diff --git a/docs/re/menu-navigation-semantics.md b/docs/re/menu-navigation-semantics.md index 7b3b7046..68babcbc 100644 --- a/docs/re/menu-navigation-semantics.md +++ b/docs/re/menu-navigation-semantics.md @@ -66,15 +66,33 @@ black-screen hang that ends the run ([`ui-paint-order-third-permutation.md`](ui-paint-order-third-permutation.md)), and this iteration needed the session. It is the one destination still unmeasured. -### The cheap way to finish this, and to make it a measurement +### 🔴 The "cheap way to finish this" was a dead route — WITHDRAWN 2026-08-29 -`0x828A690C` holds a **live screen id** — `1` title, `3` main menu, `4` extras — -and `0x828F38AC` the cursor ([`menu-state-in-memory.md`](menu-state-in-memory.md)). -Reading those while pressing Ⓐ turns "the screen said OPTIONS" into a measured -transition, and works under `--gpu=null` with no screenshots at all. ⚠️ Note that -those values are **not** GamePart ids — `GP_TITLE` is GamePart 0 and `GP_EXTRAS` -is 5, but the word reads 1 and 4 — so it is a third enumeration and mapping it to -the id table is itself unfinished work. +This section used to say: *`0x828A690C` holds a live screen id (1 title, 3 main +menu, 4 extras) and `0x828F38AC` the cursor; read them while pressing Ⓐ and the +transition becomes a measurement, under `--gpu=null` with no screenshots.* + +**Do not do that. Those words are monotonic counters, not state.** +[`menu-state-in-memory.md`](menu-state-in-memory.md) withdrew that identity **on +the same day it was published** — driving deep and then pressing Ⓑ three times +sends the "cursor" `36 → 38 → 40 → 41`, and a cursor returns when you go back. +The values match across runs because the same key sequence produces the same +count, not because `3` *means* main menu. With the sign-in fix the sequence runs +`3 → 5`, skipping `4` entirely, so "4 = extras" was never a screen at all. + +This page kept recommending the route for three days after the page it cited had +killed it. ⚠️ **A cross-reference is not a citation unless you re-read the target** +— the two documents disagreed in the corpus and either would have been believed +on its own. + +✅ **What those words are still good for**: all three advance if and only if the +game responds to input, and are stable when it does not. Read them as *"did the +game react?"*, never as *"which screen is this?"*. Screen identity still has to +come off the framebuffer. + +❔ **So a measured button→GamePart-id binding remains unfinished**, and for the +reason it always was: no screen/state enum has been located in guest memory. The +ids in the table below stay a name match. ## ✅ `NEW GAME` — measured 2026-08-28, and it is not a hang