diff --git a/docs/DECISIONS.md b/docs/DECISIONS.md index 11ae0af8..16d7a2b3 100644 --- a/docs/DECISIONS.md +++ b/docs/DECISIONS.md @@ -563,3 +563,122 @@ the capture shows a ring marker beside `NEW GAME` that the port does not draw. This is P5's, not P2's, and it is not being guessed at here. Raised in `docs/BLOCKED.md`. + +--- + +## P3 — splash → title, unattended, 2026-08-29 + +### The splash is located by entry index, because no rule can find it + +The RE agent looked for a content predicate and there is none: design size fails +(every extra composable bundle sampled is 1280×720, the same as every screen) and +element count fails (fragments run 2…15 elements in `GP_OPTIONS`/`GP_SAVE_LOAD` +while the splash halves are 3 and 7 — the ranges overlap). + +So `screen_builds` is now `is_build` **plus an authored allow-list of entry +indices**, in `authored/screen_names.json` under `also_export`, each with a `why` +that says it is a locator and not a claim. This is safe in `GP_TITLE` and would +not be in general: there, widening adds exactly four bundles and all four are +real screens with zero fragments. That is why it is an allow-list rather than a +loosened predicate. + +**There were two splash screens and the port had neither.** Entries 11/14 are the +developer logos (GAME ARTS / SETA / studio anima); entries **10/13 are the SQUARE +ENIX publisher wordmark, the first thing the boot shows**, and nothing in this +project had noticed them. Both pairs are region twins — ™ on 10, ® on 13 — and +the port shows one of each, not both. + +### `authored/screen_names.json` is now keyed by pak entry, not by ordinal + +Widening the enumeration renumbers the ordinals, and a name that moves when the +enumeration rule changes is not a name. The file had always called the entry +"the stronger locator"; it is now the only stable one. In `GP_TITLE` the two +coincide across all 16 entries, which is also the numbering `sylpheed-cli screen +--build N --all` takes — so `verify-screen` now passes `--all`, and without it +`--build 10` would have landed on entry 12. + +The two previously-unnamed plates therefore renamed `build_10`/`build_11` → +`build_12`/`build_15`. Their names were always locators; now they locate the +right thing. + +### The exit is the group playing itself out, not a black rect over a freeze + +HANDOFF's answer to ask 2 was (a), and it came with a test that discriminates +rather than a plausibility argument. Under "a black quad over a frozen screen" +every region is scaled by the same 1−α, so the button-region / background-region +brightness **ratio** stays constant through the fade. Measured, it falls +6.495 → 5.574 → 3.105 → 2.125 → 1.935 — a 3.4× monotonic drop. The screen plays +out: `pteff00.prm` ramps to opaque black while the labels, `ptmsg`, `pteff10` +and `pteff12` ramp to transparent, and `ptframe1`/`ptframe2` hold. + +Implemented by giving the final untimed keyframe a **synthetic time**, +`exit_ramp_units` after the last timed one, and then interpolating it like any +other. One code path: the difference between arriving and leaving is only how far +`t` is allowed to run, not a second kind of animation. + +`exit_ramp_units = 24` (~0.4 s) is authored, and `authored/timing.json` carries +the RE agent's own reach caveat rather than smoothing it: the filmstrip is +downsampled and the button region contains some background, so this pins the +**direction**, not 0.4 s to ±0.05 s, and it is one transition pair. + +### Nothing waits on a timer the disc does not carry + +`dwell` in `authored/flow.json` is deliberately empty. Each screen's dwell is its +own keyframe group — the publisher wordmark reaches its hold at t=235 (3.92 s), +the developer logos at t=190 (3.17 s), both read from the disc. Adding a hold on +top would be inventing a number nobody measured. The pacing you see is the +disc's own, and the file says where a measured number would go. + +### The last screen holds + +A screen plays itself out because something is taking its place. Nothing takes +the title's place yet, so the sequencer holds there. A boot that ends by fading +to black is a boot that looks like it crashed. P4 puts the intro video in front +of the title and P5 gives the title somewhere to go. + +### `flow.json` reproduces an observation and says so + +Q6 closed with a negative: the order is in none of the four places it could have +been, and a transition is a call with a name argument chosen by code. So this +file is authored and its header says plainly that it reproduces what was watched, +not what any file states. The intro video's place in the real boot is **named as +a gap** rather than the order being quietly rewritten to hide it. + +## P3 gate + +`godot --path port -- --boot --film=/tmp/boot` runs unattended: + +``` +publisher_logo → developer_logos at 4.65 s → title at 8.57 s +boot sequence complete after 13.05 s, holding on title +``` + +The filmstrip shows each screen fading in, holding, and fading through black into +the next, and the title staying up. `verify-screen` covers all **16** screens +now; the four new splash bundles come in at max 1–2/255 against the reference +renderer. The three known differences are unchanged: `title` 6 (paint-order tie), +`main_menu` 4, `title_jp` 155 (sampling phase at 125 % scale). + +## Answers taken from the RE agent without re-deriving them + +* **Focus stays "replace".** Over-vs-instead is unobservable: the focused sprite + covers the base at 100 % of base-visible pixels, and the two compositions + differ by RMSE 1.1 inside the button rect — under the gamma floor. The port's + guess was right for the wrong reason, and the actual gap is that + `ptbtn0Nf.rat` declares **two** sprites — `ptbtneff01.t32`, a glowing ring, and + then the bright label — where `ptbtn0N.rat` declares one. The ring is P5's, and + its placement inside the record is **not decoded**, so it will be authored from + the capture and marked as such. +* **RMSE against captures has a floor, so stop chasing it.** The capture is + `≈ 255·(render/255)^γ` with γ ≈ 1.49 on the menu and `EXTRAS`, 1.34 on the + title, and it is a ramp *the game installed* (`VdGetCurrentDisplayGamma` at + video init), not a capture-path artefact to subtract. Its reach is narrow — + the flat patches it was fitted on are almost all dark — so the port will not + extrapolate it across the range, and will not apply it to rendered output on + this evidence. It is a comparison constant, not a rendering one. +* **Rotation is escalated to a human and the port has not acted.** The RE half is + answered — rotate about the **declared pivot**, measured against the GPU + capture — and it has zero effect on the five screens at rest. The port will + carry `rotation_deg` in a future FORMAT v3 because carrying a decoded field the + renderer ignores beats dropping it, but it will not draw it until the + divergence question is settled. diff --git a/tools/verify-screen b/tools/verify-screen index 6ca45069..fc98aa6a 100755 --- a/tools/verify-screen +++ b/tools/verify-screen @@ -65,8 +65,12 @@ m=json.load(open("export/manifest.json")) f=next(s["file"] for s in m["screens"] if s["name"]==sys.argv[1]) print(json.load(open("export/"+f))["source"]["build"])' "$name") + # `--all` because the exporter now addresses by PAK ENTRY INDEX, which is the + # numbering `--all` uses; without it the CLI enumerates only the 12 bundles + # `is_build` accepts and `--build 10` would land on entry 12. `--all` widens + # the list, it does not change how any one bundle composites. "$CLI" screen render "$DISC/dat/GP_TITLE.pak" "$OUT/$name.ref.png" \ - --build "$build" --black --primitives --animated >/dev/null + --build "$build" --all --black --primitives --animated >/dev/null godot --path port --resolution 1280x720 -- \ "--screen=$name" --pose=rest "--capture=$OUT/$name.godot.png" >"$OUT/$name.log" 2>&1