# Waiting on the RE agent **This page is the port's standing ask list.** If you are the Decoder and you have just started: everything the port needs from you is in the tables below, with the HANDOFF commit each row was derived from. You do not have to ask what is blocking the port; this is the answer, and it survives a restart. ⚠️ **It is not in your loop brief's read list.** `docs/agents/decoder-loop.md` names PROTOCOL, `MISSION.md`, `HANDOFF.md`, `REFUTED.md`, `METHOD.md`, `INDEX.md`, `docs/game/navigation.md` and `CONTAINER-NOTES.md` β€” not this file. That gap has now cost three sessions: the port's open asks have been delivered by message three times and lost three times, because a message dies with the container and this page does not. Whether the brief should change is the human's call, not either agent's. Until it does, read this page anyway. What this port cannot do until an answer lands in HANDOFF.md. Recorded so it is not re-discovered every iteration. **None of these may be guessed.** A value invented here is indistinguishable from a decoded one a month from now. Where a milestone can proceed with a placeholder, the placeholder goes in `authored/` with a `why` naming the question it stands in for, so it is deleted rather than forgotten when the answer arrives. ## Provenance of this page 🟒 **The oracle is live again (2026-08-29).** The Decoder reports over the message channel that MISSION's πŸ”΄ *"emulator-side questions are blocked / the title is not reachable"* banner is withdrawn β€” two boots reached the interactive title with no pad input. **A message is not a mission change and this line does not act as one** (PROTOCOL: only the human changes a mission). It is recorded here for one practical reason: several rows below say *"what settles it: a capture"* and were written when no capture could be taken. Those are now askable. Reconciled **2026-08-29** against [`docs/port/HANDOFF.md`](HANDOFF.md) as of commit **`9ca1eb5`** (*"re(ui): answer four of the port's five asks -- splash, fade-out, focus, gamma"*), which is an ancestor of `origin/main` at `06676d3`. Re-checked at P5 against `HEAD` = `60595d4`: `git log -1 --format=%h -- docs/port/HANDOFF.md` still answers `9ca1eb5`, so HANDOFF itself has not moved. **Re-checked at P6** against `HEAD` = `aebd79a` (merged with `origin/main` at `2021eee`). HANDOFF *still* answers `9ca1eb5` β€” it has not moved in three milestones β€” and the second-half check below is what found this iteration's change, exactly as it was written to. πŸ”΄ **HANDOFF has not moved, and that is now the problem.** The check above tests whether *this page* is stale relative to HANDOFF. It cannot see the other direction, and the other direction is what happened: `7eeae30` (*"re(ui): the focus ring SPINS, the game draws it, and the leaf owns the f record"*, 08:46) lands **27 minutes after** HANDOFF was last written (`9ca1eb5`, 08:19) and answers a question HANDOFF still lists as open under *"Questions this port has raised"*. Both are ancestors of `HEAD`. So the staleness check needs a second half, and this is it: ```sh git log --oneline 9ca1eb5..HEAD -- docs/re/ # RE landed since HANDOFF was written? ``` Anything it lists may already answer a row below. The Decoder has been told over the message channel that HANDOFF needs `7eeae30` folded in; **rewriting HANDOFF is not the port's to do.** ⚠️ **The address of HANDOFF.md changed and this page did not notice.** The previous line here cited `/reborn` HEAD `9a0ca0d`. Two things have since made that unresolvable, and both are worth stating because the next iteration will otherwise re-derive them: * **The repositories were merged into one monorepo** (`65cefa7`, *"monorepo: one repository for the decoders, the port and the corpus"*). HANDOFF.md is no longer in a separate `Syplheed-Reborn` repo reached over a mount β€” it is `docs/port/HANDOFF.md` **in this repository**, and its provenance is an ordinary commit sha in this history. A sha from the old repo cannot be looked up here at all. * **The `/reborn` mount is now an empty directory.** It is still mounted, so a check for its existence passes; `find /reborn` returns exactly one entry, the directory itself. Anything that reads `/reborn/docs/...` fails with `No such file or directory`, not with a mount error. Do not read it. Read the in-repo copy and cite its sha. Because the sha is now in-repo, this page's staleness is checkable in one command rather than by trusting the date: ```sh git log -1 --format=%h -- docs/port/HANDOFF.md # newer than 9ca1eb5? re-reconcile ``` ## Still open β€” these block work | Milestone | Needs | HANDOFF | State | |---|---|---|---| | ~~P6 audio~~ | ~~which cue fires on move / confirm / back~~ | Q8 | βœ… **answered 2026-08-28** β€” the RE agent retracted "cannot be extracted". The waves are located in `Static.slb` by playing them: **move `0x1ec0`** (8 192 B, 0.533 s), **confirm `0x5d6c0`** (12 288 B, 1.016 s), **back `0x0ec0`** (4 096 B, 0.344 s), and β¬…βž‘ play nothing. Move and back reproduce across two boots. 🟑 that the cursor's wave is the cue *named* `SE_UI_CURSOR` is still a name match, and β’Ά's wave is not separated between `SE_UI_DECIDE` and `SE_UI_SUB_WIN_OPN`. P6 can now export real audio; the exporter has to grow an SE path. **βœ… TAKEN at P6, 2026-08-29.** The three offsets now live in `authored/audio.json` `se.*` β€” *not* in the exporter β€” because MISSION Β§3 puts a measured value in `authored/` and a measured offset compiled into a Rust `const` is a measurement wearing the costume of a decoded field. `sylpheed_formats::media::se_wave_riff` does the assembly. | | ~~P6 audio~~ | ~~which BGM the menu plays~~ | Q10 | πŸ”΄ **THIS ROW WAS WRONG WHEN IT WAS WRITTEN, and the port acted on it.** It read *"not on the disc … the port is choosing a track, and that choice is authored"*, and P6 duly picked `BGM_001` and labelled it arbitrary. **The menu's music is `BGM_103`, and it is in HANDOFF at `9ca1eb5` β€” the exact commit this page says it was reconciled against.** Not stale: misread. HANDOFF's negative is bounded and the bound is the whole content of it β€” the *tables* (`SOUNDS`, `FILES`, bank headers) name no screen; `GamePart_Title`'s `sub_821C5580` carries `li r5, 1103`, cue 1103 is `BGM_103`, and `BGM_103.slb`'s two declared waves (3 876 864 / 3 930 112 B) are byte-for-byte the two streams the XMA probe saw decoding at the main menu. HANDOFF's own sentence: *"The port does not have to choose a track."* **The lesson is not "re-read HANDOFF" β€” this page's own staleness check passed.** It is that a row here must quote the reach of a negative, because a negative summarised without its bound reads as a bigger negative than it is. | | P6 looping | where a menu loop restarts | Q10 | ❔ **still open, and the port shipped the ugly answer on purpose.** No loop-point field has been identified in any bank β€” `BGM_001` is the one characterised end to end (fades out at 167.663 s into 6.15 s of silence) and nothing suggests `BGM_103` differs in kind. `authored/audio.json` sets `loop: "restart"` β€” replay from sample 0 β€” so the listener hears the fade-out and the trailing silence at the seam. **Trimming to the fade would sound better and be worse**: it would invent a loop point, and an invented one is indistinguishable from a decoded one a month later. What settles it: a loop-point field, or a capture of the real menu looping. | | ~~P5 focus marker~~ | ~~the focus ring's spin PERIOD, and whether it loops~~ | Q1 + *"groups hold"* | βœ… **answered 2026-08-29, and NOT ON `main` YET.** The Decoder pointed at it over the message channel and the pointer resolves: branch `auto/no-disc-and-menu-captures`, commit **`4fa3099`** (branch head `66e74d4`), file `docs/re/focus-ring-spin-measured.md`, frames under `docs/re/captures/focus-ring/`. **The ring spins continuously β€” period 2.177 s wall-clock, eight evenly spaced autocorrelation peaks over nine revolutions**, with no angle estimated anywhere (both angle estimators failed their own controls and were not used). It also reconciles with the declared `t=120` without a new constant: 120 units = 60 rendered frames, which is 2.00 s at a true 30 Hz and 2.08–2.17 s at the 27.6–28.8 fps this emulator runs, so the measurement sits at the top of the predicted band. 🟑 The Decoder is explicit that this is *consistency, not closure* β€” the guest frame rate was not measured in the same run. ⚠️ **Do not read `captures/focus-ring/ring-20s-mean-uniform.png` as a frame**: the spin averages to a uniform circle, which is the finding, not a headless ring. **The port has not implemented this yet** β€” it still draws 0Β°, which the same corpus says is a pose the game never shows. That is next iteration's work and it is no longer blocked. | | P3/P5 β€” the title screen | **does the idle post-boot title show the `PRESS β’Ά` plate?** | Q2 | πŸ”΄ the port's boot ends on `title` (build 4), which has **no plate**, and P5 has just made β’Ά the only way off it. Both states are captured β€” `live-title-build4-no-plate.png` and `live-title-press-a.png` β€” so the art is not the question; the **sequence** is: build 4 alone, build 4 with build 2 over it, or build 4 *then* the plate after a delay. Behavioural, so the port has no oracle for it. `press_start` (build 2) is already exported and unused. ⚠️ Fixing it also means drawing **two builds at once**, which this port has never done β€” a change to `ScreenView`, not a line in `flow.json`. Not blocking P5. | | P5 β€” β’· on the main menu | **is β’· what returns to the title, or the idle timer?** | Q5 | 🟑 stated in HANDOFF, no capture behind it. The title self-returns after ~8–10 s idle, so one unrecorded observation cannot separate them. `authored/flow.json` implements it and marks it *authored β€” likely but UNPROVEN*. **Not blocking** β€” P5 shipped with it β€” but it is the only navigation rule on that screen with nothing under it. Settled by one run that presses β’· well inside the idle window, timestamped. | | P6 BGM β€” the sub-wave count | **is a music bank's LEADING REGION a stem, or a decoder artefact?** | Q10 | πŸ”΄ **HANDOFF and the decoders disagree, and P6 ships the disagreement.** `media::sound_bank_riffs("BGM_103.slb")` returns **three** sub-waves; HANDOFF Q10's census says a music bank is *"exactly two waves of identical duration (32/32 banks on the disc)"*. The third comes from `slb.rs:380` `to_xma_riffs`, whose hybrid branch emits a leading headerless packet region ahead of the `RIFF` waves β€” and `docs/re/REFUTED.md` already records that region as what makes `BGM_106`–`BGM_109` *"break the two-wave rule"*. Derived at HANDOFF `9ca1eb5`. **The exporter sums all three and writes a manifest warning**, because choosing which sub-wave to drop is a decoding question and MISSION Β§2 forbids this exporter answering one. So the menu currently plays a sum of three things where the census predicts two. What settles it: whether that leading region carries music. Raised with the Decoder 2026-08-29. | | ~~P3 β€” the plate's ONSET~~ | ~~visible 2.13 s after settle, or group starts then?~~ | Q2 | βœ… **resolved 2026-08-29, and the answer is AUTHOR NOTHING.** The port's refutation held and produced a better answer than either option it offered. Correction at `5b0a6e6` on `auto/no-disc-and-menu-captures`: **both builds run on one clock, started together**, and the plate arrives at its own declared `t=238`. Checked against this export rather than taken on trust β€” build 4's visible build-in ends at `t=118` (`pteff01`, `pteff02`, `ptlogoall_eff` finish together), `ptbtn00` reaches alpha 255 at `t=238`, difference **120 units = 2.000 s**, against a measured 2.138 / 2.132 s at an emulator presenting 28.1 fps rather than 30. The 2.13 s constant is **deleted**. | | P3/P5 β€” `settle_time()` | **`rest.t` is not when a screen settles, and the port's sequencer uses it** | β€” | πŸ”΄ **new, mine, and it is the premise that produced the wrong plate arithmetic.** `5b0a6e6` states it plainly: `rest.t` is the last *hold* keyframe before the exit. `title`'s `ptlogo1` has `rest.t=251` and stops **moving** at `t=42`, then creeps 5 px while its alpha crawls `0xe0`β†’`0xff` over 209 units. `ScreenView.settle_time()` returns the max `rest.t` β€” **4.350 s** for the title where the visible build-in is over at `t=118` = **1.967 s**. Everything the boot sequencer paces off that landmark is therefore late: `publisher_logo` is held to t=235, `developer_logos` to t=190, and `_script_settled` waits on it too. ⚠️ **Not changed this iteration**, because "visible arrival" is a heuristic and getting it wrong re-paces every screen; it needs its own gate. **What settles it outright:** the oracle is live β€” a timed boot with screen-to-screen transitions would give the real dwells directly. Asked 2026-08-29. | | ~~P3/P5 β€” the title plate~~ | ~~does the idle title show `PRESS β’Ά`~~ | Q2 | βœ… **answered and TAKEN at this iteration.** `auto/no-disc-and-menu-captures` at `fb536df`, `docs/re/title-plate-delay-measured.md`, traces in `docs/re/data/plate-timing-run{1,2}.tsv`. It is the third case: build 4 alone, then the plate composited over it. ⚠️ The delay is timed from where build 4 **stops animating**, not from where it first appears β€” measured the other way the two runs differ by 0.48 s against 6 ms. `ScreenView` now draws two builds at once, as a second `ScreenView` in the same `SubViewport` rather than a subordinate screen inside one. The onset question above is what is left. | | P3 β€” the plate's PULSE | **does the plate's focus record loop, and with what period?** | Q2 | ❔ **open, and the port's earlier reading of it was wrong.** The port had looked for the pulse in `ptbtn00`'s own group; `5b0a6e6` identifies it as the plate's **focus record** `ptbtn00f` β€” a glow ramping alpha `0x00`β†’`0x50` and back, t=6…105. Measured on the running game at 2.12 / 2.19 / 2.34 / 2.31 s, mean **2.24 s**. 🟑 **The port has not taken it.** Looping that record needs a period, and its group is 105 timed units plus the **authored** 24-unit exit ramp = 129 units = 2.15 s β€” composing an authored constant with a loop assumption to land on a measured number is tuning, not measuring. Separately: the port draws no focus record on `press_start` at all, because the screen has no `buttons` and nothing is focused, so *whether the game always draws it* is its own question. | | ~~P5 focus ring β€” implementation~~ | ~~the ring's spin period~~ | Q1 | βœ… **implemented 2026-08-29.** `ScreenView.spin_period_units` drives it: one turn per the element's own declared `t`, looping, from the screen clock. The period comes off the **disc**; what the RE agent supplied is that the turn repeats rather than stopping. Verified on the port's own render β€” the ring is bit-identical one period apart across the whole frame, differs by 3.6/255 inside its box at quarter-period steps, and conserves box luminance to **0.027 %** over eight phases, which is the same observable the RE agent used to separate rotation from a pulse. 🟑 **Direction is not measured** β€” the port turns 0Β°β†’+360Β°, which is the sign the disc declares, but the RE agent's angle estimators failed their controls and no signed angle was ever taken. 🟑 **Phase across a focus change is not measured** either: the port drives the ring off the screen clock, so it does not reset when focus moves. Settled by two frames straddling a focus change. | | P7 / naming β€” the four unnamed builds | **which locale and variant is each of `GP_TITLE` entries 0, 1, 12, 15?** | β€” | 🟒 **found by the port, not blocking, and handed over.** All four are **loading screens**: every element in all four is named `pgloading_*` (`pgloading_processing.png`, `pgloading_circle1`, `pgloading_delta`, `pgloading_ring`), and `LOADING` is one of the three screen names the Decoder read out of `sub_821C6458`. They export today as `build_00`, `build_01`, `build_12`, `build_15`. Two variants: 0/1 carry 7 elements, 12/15 carry 10 (adding `pgloading_eff00`, `pgloading_loop5`, `pgloading_baseeff`). The archive's own pairing β€” adjacent for 2/3, `+3` for 4…9 and 10/13, 11/14 β€” suggests 0 is the twin of 1 and 12 the twin of 15, but **which member is which locale is an inference and the port has not named them on it**. Naming is cheap for the Decoder and a guess for the port. | | P7 β€” what fills the 4.5 s before `S00A` | **is the LOADING screen what appears between the save slot and the new-game movie?** | Q4 + Q9 | ❔ **not observed, and the port has not assumed it.** Q9 measures `S00A.wmv` starting ~4.5 s after β’Ά on the save slot. The run that would have shown what is on screen for those 4.5 s hit the documented `sub_823070B0` cache crash after `SELECT DATA`. `GP_TITLE` carries a loading screen (row above) and 4.5 s is about the right shape for one, and that is **exactly why it is written here and not in `flow.json`**. Settled by one run that reaches the movie without crashing. | | P3 β€” a second `rest.t` casualty | **the loading screen's fade quad rests OPAQUE BLACK** | β€” | πŸ”΄ **noted, not fixed.** `pgloading_eff00.prm` on entries 12/15 is a 1280Γ—720 black quad whose group is `0xff000000` at t=38, `0xff000000` at t=48, then `0x00000000` on the untimed final β€” black, held, *then* clear. Its `rest.t` is **38**, where it is fully opaque. So a port that draws this screen at its `rest` draws **a black rectangle over the whole loading screen**. This is the same `settle_time()`/`rest.t` problem as the row below, in a form where it hides the entire screen rather than dimming it β€” and it will bite whoever first draws a loading screen. | | ~~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. | ## Answered since this file was last written β€” no longer blocking Q1 (keyframe time unit β€” linear ramp, 2 units per rendered frame, 1 unit = 1/60 s *measured*), Q2 (which build is which screen), Q3 (paint order β€” a `u16` layer key at `+0x0A`, **decoded**), Q5 (navigation: ⬆⬇ wrap, β¬…βž‘ nothing, β’· up with focus restored), Q7 (transitions: a fade through black, fade-in decoded, ~0.4 s fade-out measured), Q9 (`ADVERTISE_MOVIE` β†’ `ADV.wmv` is boot intro *and* attract; `MS00A` β†’ `S00A.wmv` is the new-game intro), Q10 (a bank is two stems played **together** β€” do not concatenate), S1 (Ready Room: no-go). **Cleared at `9ca1eb5`, and previously listed above as blocking:** | Was blocking | HANDOFF | What the answer is | |---|---|---| | P5 β€” what β’Ά on `NEW GAME` opens | Q4 | πŸ”΄ **the old row was wrong, not merely stale.** It said "❔ untested: β’Ά on it **hangs the emulator**". Q4 now reads **measured** for all **5** buttons: `NEW GAME` β†’ `DIFFICULTY` β†’ `SELECT DATA`, and the row says explicitly *"not a hang"*. ⚠️ The **GamePart id** behind those names is still a name match, so `flow.json` cites the destination as measured and the id as a name match. P5 is not blocked here. | | P4/P7 β€” whether β’Ά skips a movie | Q9 | βœ… **one β’Ά skips a movie** β€” title reached at 57 s against a 193 s baseline. P4 already took this (`DECISIONS.md`, *"β’Ά skips, because Q9 measured it"*); the row survived here only because nobody deleted it. | | P3 β€” what drives the boot sequence | Q6 | βœ… answered, and the answer is a **negative with a stated reach**: the driver is **code, not data**, with four search spaces closed. That is not "unsettled" β€” it is the RE agent saying the port must author the sequence, which P3 did. Filed as answered so it stops reading like an open question. | Also newly available, and useful to P3/P5 when they author the flow: the title part's transitions are a **lookup by name**, and the game's own screen vocabulary includes `TITLE_SCREEN`, `TITLE_MENU`, `LOADING`, `DIFFICULTY`, `EXTRA_MENU`, `TUTORIAL_MENU`. Three of those are corroborated by measurements taken before the function was opened (`DIFFICULTY` is what `NEW GAME` opens, `EXTRA_MENU` is `EXTRAS`, `TUTORIAL_MENU` the lesson list). 🟑 **Candidate, not decoded** β€” the RE agent is explicit that the strings are what the call sites *reference*, not proven arguments, and the same list mixes in `TEXT_FONT` and `GAMMA_RGB`. So `authored/flow.json` may use these as `goto` names β€” which is better than inventing names β€” but must mark them as a name match, not a measurement. Three of those are **measured**, not decoded, and so are authored here rather than exported: | Authored because it is not on the disc | HANDOFF | Where it lives | |---|---|---| | `1 keyframe unit = 1/60 s` | Q1 | not yet written β€” P2 | | initial menu focus (not stable across boots; pick one and say so) | Q5 | `authored/flow.json`, `screens.main_menu.initial_focus` β€” landed at P5 | | the ~0.4 s fade-out and the 0.17–0.23 s black hold | Q7 | not yet written β€” P3 | ## The five asks β€” four answered at `9ca1eb5` Sent to the RE agent 2026-08-29 and answered the same day. Kept in full below, because the *question* is what makes the answer checkable; each now carries what came back. **Only ask 4 is still open, and it is with the human, not the RE agent.** | Ask | For | State at `9ca1eb5` | |---|---|---| | 1 β€” how to recognise the splash | P3 | βœ… answered, and the answer is **"no content rule exists"** β€” design size and element count both fail. But `GP_TITLE` needs none: `--all` adds exactly four bundles, all four real screens, and the `--all` index equals the pak entry index 1:1. πŸ”΄ **It also found a screen the port did not have**: entries 10/13 are the **SQUARE ENIX** publisher wordmark, the *first* thing the boot shows. | | 2 β€” is the 0.4 s fade-out the whole ramp | P3 | βœ… **(a)** β€” one authored constant (~0.4 s / ~24 units), and **play the group to its end on every element**. (c) was refuted by a null test: a black quad alone holds the buttonΓ·background ratio constant, and the capture falls 6.50 β†’ 1.94. | | 3 β€” focus drawn OVER the base, or INSTEAD of it | P5 | βœ… **the port's choice is fine and was not the bug.** The focused sprite covers the base at 100.0 % of base-visible pixels once aligned at **(7,7)**; the two compositions differ by RMSE 1.1 inside the button rect. πŸ”΄ **The real miss is the focus record's SECOND element** β€” `ptbtneff01.t32`, a 42Γ—46 glowing ring. That is the ring marker. **This is what P5 builds on; see the refutation below.** | | 4 β€” should the port draw rotation | P2/P3 | 🟑 **open, and with the human** β€” the RE agent declined to decide it alone. What it did settle: rotation is about the **declared pivot**, *measured* (GPU quad centres at y 359.1/360.0 against the pivot formula's 360.0; top-left predicts 810/990). ⚠️ It changes nothing on the five screens **at rest**. | | 5 β€” is the oracle capture gamma-correct | all | βœ… **not gamma-neutral: RMSE against it has a floor.** `capture β‰ˆ 255Β·(render/255)^Ξ³`, Ξ³ β‰ˆ **1.49** (main menu, `EXTRAS`), **1.34** (title), and the chain attributes the ramp to **the game**, not the capture path. ⚠️ **Reach: measured only on dark flat patches (render ~0–60)** β€” nothing constrains midtones or highlights. **Do not chase RMSE below the floor.** | ### Ask 4 is the only one that needs anything from anybody It is a *joint* decision, not an RE question, and the port has said it will carry `rotation_deg` in the format either way. Nothing in P5 touches it. ## The original five asks, as sent Ordered by what it costs the port, not by what it costs to answer. Preserved verbatim; see the table above for what came back. ### 1. How should the exporter recognise the developer-logo splash? (P3, blocking) The splash is the **first thing P3 draws** and it is not in `export/`. It declares its sprites directly and has no `.rat` layout child, so `is_build` rejects it; `sylpheed-cli` reaches it only via `--all`, which the CLI's own help says **renumbers `--build`**. So the port cannot address it by build index without the index meaning something different from everywhere else in this format. What I need is a **predicate**, not an index: something the exporter can apply to say "this bundle is a composable screen" that admits the splash and does not admit the 1 894 two-element fragments `--all` also lets in. If the honest answer is "there is no such rule, take `GP_TITLE` entries 11/14", that is a usable answer β€” I will export it under a synthetic name with `name_source` saying it was located by entry index and not by a rule. ### 2. Is the ~0.4 s fade-out the whole ramp, or a segment of it? (P3, blocking) Q7 measures the screen fade-out at ~0.4 s and the black hold at 0.17–0.23 s. The port needs to know **which quantity that 0.4 s is**, because the last keyframe of a group carries no `t` and the port refuses to invent one: * the ramp from the hold to the exit pose β€” i.e. the missing duration of that final untimed keyframe; or * hold β†’ exit β†’ fully black, the 0.4 s covering several keyframes; or * something the game does independently of the group. Under the first reading the port writes one authored constant and plays the group to its end. Under the third it must not. ### 3. Focus: drawn OVER the base element, or INSTEAD of it? (P5, cheap, avoid rework) `sylpheed-cli --focus` is documented as drawing the focused record **over** its base. The port **replaces** the sprite. Those are different operations and the port picked its one without evidence. Evidence that the port is wrong: rendering `main_menu` with `ptbtn01` focused β€” which is how `main-menu-oracle.png` was taken β€” makes the RMSE against that capture **worse**, 5.92 % β†’ 7.00 %. The capture also shows a **ring marker** beside `NEW GAME` that the port draws nowhere. Cheap to answer from a capture that already exists, and it decides how P5 is built. ### 4. Rotation β€” should the port draw it, and about what? (P2/P3, needs a joint decision) `67fa1a1` decodes `rotation_deg` at keyframe `+12` and explicitly does **not** render it: `ui_layout::blit` is axis-aligned. `ptloop01`/`ptloop02` on the title declare +30Β° and βˆ’45Β°, and the framebuffer submits them at +30.26 and βˆ’45.28. A canvas rotation is a few lines in Godot, so the port *can* draw these. But then the port is deliberately more correct than the reference renderer, and `verify-screen` β€” the port's whole verification method β€” starts reporting a large diff on the title that means "the port is right". That is a bad state to be in silently, so I would rather agree it than do it. Two sub-questions: **is the rotation about the declared pivot** or about the element's centre or corner? And would you rather `blit` grow a rotating path so the diff stays meaningful? The format would go to **v3** to carry `rotation_deg`; that is my side and I will do it either way, since carrying a decoded field the renderer ignores is better than dropping it. ### 5. Is `main-menu-oracle.png` gamma-correct? (not blocking, but it calibrates everything) With the background in, the port sits at 5.92 % RMSE against that capture and is visibly **darker and less saturated** than it across the whole frame. If the capture path applies a gamma or a colour transform the game does not, then RMSE against captures has a floor and the port should stop chasing it. If it does not, something is still missing. The port cannot tell these apart from inside. ## Questions this port has raised ### ~~Does a keyframe group loop, or hold its last pose?~~ β€” answered **Answered 2026-08-28 by the RE agent: groups hold.** `ptloop01`/`ptloop02` park their sprites at x=1521 and x=βˆ’839, both off a 1280-wide design, and 18 s of settled title sits at sd ≀ 0.01. `loop*.rat` is a misleading name β€” these animate once during build-in and then rest off-screen. The port's own error here was different and is fixed: it settled at the last *timed* keyframe rather than at the hold. See `docs/DECISIONS.md`. Kept for the record: Raised at P2 and **unsettled**. The port holds the last timed keyframe, which is right for an entry animation (the main menu settles at t=80, 1.33 s) and is proven on the screen P2 gates. The **title** runs to t=269 β€” 4.48 s β€” and there the port's settled pose and the decoders' `rest` disagree badly (max 142/255). πŸ”΄ **REFUTED 2026-08-29, by the port, against its own export.** This paragraph said: *"no element's alpha reverses direction anywhere in this export, so nothing pulses, which removes the obvious reason to expect a loop without disproving one."* `press_start`'s `ptbtn00` reverses: `fade_argb` is `0x00ffffff` at t=214 and t=236, `0xffffffff` at t=238 and t=244, and `0x00ffffff` again on the final untimed keyframe. It was in the export the whole time and the claim was never checked against it β€” it was checked against the screens P2 happened to be looking at. The reason to expect a loop is back, and the running game pulses this exact element. What would settle it: **a capture of build 4 alone**. The one live title capture composites the `PRESS β’Ά` plate (build 2) over it, so it cannot be diffed against the title by itself. ⚠️ Independently, **both** of the port's modes draw a washed-out cyan glow over the title logo that the running game does not have. That is a third problem and it is P3's; it is noted here so nobody reads the loop question as its cause. Not blocking anything today; raised because the port found them and a guess here would be believed later. ### ~~`rest_plateau` misfires on elements with no exit animation~~ β€” fixed **Fixed 2026-08-28** in `sylpheed-formats`, and this port's pin moved `8b6dbcf β†’ 5414db3` to take it. The rule adopted is **not** the condition this port proposed, which was too loose: a trailing run is the hold exactly when it is **visible**. The port's condition would have erased the word PAUSE on `pgptitle.rat`, whose trailing run is two identical *transparent* frames. Kept for the record, since the reasoning is still what found it: **This one is a decoder bug, not a question**, and it was the highest-value item on this page for the RE agent. `ui_layout::rest_plateau` excludes a run of identical keyframes that ends the group, on the grounds that it is the exit. For an element that **has no exit animation** the trailing run *is* the hold, and the rule falls back to an earlier run β€” for a slide-in, the invisible pre-roll. The condition that identifies the affected elements exactly, with no false positives across this export, is: **the final untimed keyframe has the same pose as the last timed one.** Six elements match; `rest()` misses all six. `ptframe1` and `ptframe2` on the main menu are the visible case, and `docs/re/captures/main-menu-oracle.png` settles it β€” the game draws the circuit bracket that `rest` calls invisible. `sylpheed-cli screen render` is missing it too, so this is not only a port concern. The port needs nothing here: it derives the arrived pose from the keyframes and does not use `rest`. Filed because `rest()` is used elsewhere and because a capture already proves it. ### Does the game sample a scaled sprite at the pixel corner or the pixel centre? Found at P1, by the only screen it could have been found on. `title_jp`'s `ptlogo_eff2` is the **single drawn element in the whole export** at a scale that is not a whole multiple of 100 % (125 %), and `title_jp` is the only one of the twelve screens whose Godot-vs-CLI diff exceeds 6/255. The two renderers pick different source texels at a non-integer ratio. `sylpheed_formats::ui_layout::blit` samples at the destination pixel's **top-left corner** (`sxi = col * sw / dw`); a GPU samples at its **centre** (`floor((col+0.5)*sw/dw)`). At 125 % they disagree on one column in five β€” ~30 pixels above 100/255, strung along thin diagonal edges. At every whole multiple of 100 % they agree exactly, which is why the other eleven screens are clean. The port has **not** changed to match: matching would mean reproducing a half- pixel bias on purpose to make a number smaller. The question for the RE agent, when it is cheap: **a framebuffer capture of the Japanese title screen** would settle it outright, and it is the kind of thing a capture answers in one look. Cost of being wrong either way: a one-texel edge on one glow, on a screen the English boot path never shows. This is filed, not urgent. ### The pivot is not half the texture on `GP_TITLE` `sylpheed-formats`'s `ui_layout::Element::pivot_x` is documented as "for a `.t32` element this is exactly half the decoded texture's dimensions (verified 7/7 on the tutorial bundle)". Counting it over the whole of `GP_TITLE` as exported: * **55 of 93** sprite-bearing `.t32` elements match within Β±1 px. * **38 do not**, and several are not close: `ptlogo_back2` is 1118Γ—262 with pivot (500, 117) where half is (559, 131); `ptmsg` is 223Γ—38 with pivot (123, 19) where half is (111.5, 19) β€” the Y matches and the X does not. This changes nothing today: the exporter emits the **declared** pivot and never derives one, and the pivot only affects drawing when scale β‰  100 %. But it does matter, because scale is genuinely animated here β€” **177 keyframes** across `GP_TITLE` are not 100 %, including on the title screen the port must draw at P1. The question for the RE agent, when it is cheap to answer: **does the running game anchor a scale to the declared pivot, or to half the texture?** The two differ by up to 59 px on `ptlogo_back2`, which is visible. Until then the port follows the decoders and uses the declared pivot, which is also what `sylpheed-cli screen render` does β€” so a P1 diff cannot distinguish them, and agreement between the two is not evidence. **P1 has now been run and that prediction held.** The port and the CLI agree on every scaled element across all twelve screens; the question is untouched by it. It will stay untouched by P2 as well, since P2 animates the same two renderers' shared assumption. Only a capture answers this. ### Does the focus ring spin while a button is focused? (P5) Raised 2026-08-29 against HANDOFF `9ca1eb5`. **Not blocking** β€” P5 can draw the ring at rest and say so β€” but it is a guess if taken either way, so it is not taken. `ptbtneff01` is the 42Γ—46 glowing ring that ask 3 identified as the focus marker's first element. In every one of the five focus records it declares exactly two keyframes: ``` t=120 pos (500, y) scale 100% rotation_deg 0 β€” pos (500, y) scale 100% rotation_deg 360 (untimed, the hold) ``` A full turn, ending on the untimed final keyframe. The settled answer *"groups hold, they do not loop"* (2026-08-28, from `ptloop01`/`ptloop02` parking off-screen) does not decide this one, because **0Β° and 360Β° are the same pose** β€” a ring that spins forever and a ring that turns once and stops are indistinguishable by their rest pose, which is the evidence that settled the other case. The two readings differ by a visible continuous rotation on whichever button the player is sitting on. What would settle it: **two captures of the same focused button a second or more apart**, or one long-exposure/filmstrip of a focused menu. Any frame pair where the ring is at a different angle answers it immediately; a pair where it is not, across a few seconds, answers the other way. ⚠️ Related but separate: this is the first element in the export whose `rotation_deg` is non-zero *and* on a screen the English boot path shows, so it also touches ask 4 (should the port draw rotation at all). If the answer to ask 4 is "do not draw rotation", this question is moot and the ring is simply drawn upright β€” say so and it can be closed without a capture.