re: the transition is overlap, not ramp-then-hold -- and the fade-in was 5x wrong
screen-transitions.md carried a 14-unit "black hold" that the page itself flagged as arithmetic rather than measurement. Measured it against the running game; the guess was wrong, and finding the instrument to measure it turned up a second, larger error in the same page. 1. fade_quads.py was STALE. It read each pose's time from blk+36 -- the next record's time word -- the association the keyframe record-layout fix retired in the crate. sylpheed-cli was rebuilt at the time; the Python helper was never swept with it. Signature: it cannot time a group's last pose, so it printed a trailing `t=-`. Fixed, controlled against the rebuilt `screen info` ([0 12 70 80] for build 5's pteff00.prm). 2. Through it, the page labelled the quad's CLEAR-hold as its fade-in and published 0.87 s / 0.97 s / 4.08 s for a ramp that is 0.20 s / 0.20 s / 0.27 s. A port pacing its menu fade-in off that would run it 5x too slow. 3. The measurement. fade_decompose.sh boots to the main menu, arms the UI draw capture there, then presses (B), so one 260-frame window holds the whole screen change. The fade quad is identified rather than guessed: a .prm carries no tex[base=] and paints last, so it is the last full-screen untextured quad of a frame. Control first -- the quad's ramp is decoded at 10 units = 5 frames, and measures 4 submitted-frame steps with one unlogged frame in the span. Result: content elements begin fading at frame 34; the black quad first appears at 40 and is opaque by 43; the menu's last frame is 45; frame 46 has 6 draws against 12. So the ~14 extra units are the content's own fade-outs OVERLAPPING the quad's ramp, not a hold after it, and the inter-screen black is one frame. Refutation attempted: sylpheed-port's entries 13/14 twins. Re-derived off the disc -- 3.06 / 4.33 / 47.91, identical to two decimals. Recorded as confirming their addressing and arithmetic, NOT as independent support: same renderer, same disc, which is their own rule. Reach: one transition, one run; the frame axis has gaps (232 headers over frames 3..260), so every span is +-1 frame. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
This commit is contained in:
@@ -23,6 +23,80 @@ There is no fourth kind. If a row says *measured* or *undecodable*, the port is
|
||||
human can see it is a human decision, so that when it is later decoded the
|
||||
authored version can be deleted.
|
||||
|
||||
## 🔴 2026-08-30 — the transition is OVERLAP, not ramp-then-hold. And your menu fade-in is 5× too slow.
|
||||
|
||||
Two corrections and one measurement, all on
|
||||
[`screen-transitions.md`](../re/screen-transitions.md). **The fade-in one changes a
|
||||
number you are probably already using.**
|
||||
|
||||
### 🔴 1. The fade-in is 0.20 s, not 0.97 s
|
||||
|
||||
That page told you the screen "holds black for 0.20 s, then fades in over 0.87 s
|
||||
(`EXTRAS`), 0.97 s (main menu) or 4.08 s (the title)". **Those are not fade-ins.**
|
||||
They are the stretch where the fade quad sits at `α = 0` — the screen fully visible
|
||||
and not fading at all. Read correctly:
|
||||
|
||||
```
|
||||
build 4 (title) pteff00.prm t= 0 α=255 t= 16 α=0 t=261 α=0 t=269 α=255
|
||||
build 5 (main menu) pteff00.prm t= 0 α=255 t= 12 α=0 t= 70 α=0 t= 80 α=255
|
||||
build 6 (EXTRAS) pteff00.prm t= 0 α=255 t= 12 α=0 t= 64 α=0 t= 74 α=255
|
||||
```
|
||||
|
||||
**Fade-in = 12 units (0.20 s) on the menu and `EXTRAS`, 16 units (0.27 s) on the
|
||||
title.** Fade-out = 10 units, 10 units, and **8** on the title.
|
||||
|
||||
**Cause:** `tools/re-capture/fade_quads.py` read each pose's time from `blk+36` —
|
||||
the *next* record's time word — the same association the record-layout fix retired
|
||||
in the crate. `sylpheed-cli` was rebuilt then; the Python helper was not swept with
|
||||
it. Its signature is the trailing untimed keyframe (`t=—`) that page printed for
|
||||
years. Fixed and controlled against the rebuilt `screen info`, which gives
|
||||
`[0 12 70 80]` for the same element.
|
||||
|
||||
### ✅ 2. The ~14 "missing" units are not a black hold — measured
|
||||
|
||||
That page guessed the remainder of the ~0.4 s was the black hold and **said in as
|
||||
many words that this was arithmetic, not a measurement**. I measured it. It is
|
||||
wrong.
|
||||
|
||||
`fade_decompose.sh` boots to the main menu, arms the UI draw capture there, then
|
||||
presses Ⓑ — one 260-frame window holding the whole screen change. The fade quad is
|
||||
*identified*, not guessed: a `.prm` carries no `tex[base=…]` and paints last, so it
|
||||
is the last full-screen **untextured** quad of a frame.
|
||||
|
||||
**Control first:** the quad's ramp is decoded at 10 units = 5 frames. Measured, it
|
||||
is absent at frame 39 and `α=255` at 43 — 4 submitted-frame steps with one unlogged
|
||||
frame in the span. The instrument reproduces the decoded quantity before being
|
||||
trusted on the undecoded one.
|
||||
|
||||
```
|
||||
frame 34 content elements begin fading (255 → 223 → 207 → 175 → 95 → 31 → 15)
|
||||
frame 40 the BLACK QUAD first appears, α=102 → 127 → 255 by frame 43
|
||||
frame 45 last frame the menu draws
|
||||
frame 46 6 draws (vs 12) — ONE frame of black
|
||||
frame 47+ the title's build starts
|
||||
```
|
||||
|
||||
📌 **A transition is not "ramp the quad 10 units, then hold black 14".** It is
|
||||
**"start the content fading, and six frames later ramp the black quad over its
|
||||
declared 10 units on top of them"** — the two overlap. Total blackout is frame
|
||||
34→43 = **9 frames ≈ 0.30 s**, and the gap between screens is **one frame**.
|
||||
Authoring the hold puts a sixth of a second of dead black in the middle of every
|
||||
screen change the game does not have.
|
||||
|
||||
⚠️ **Reach:** one transition, one run. The frame axis has gaps — 232 headers over
|
||||
frames 3…260, ~10 % of submitted frames carry no UI draw — so every span here is
|
||||
±1 frame, which is why the ramp is 4 *steps* and not a duration to three digits.
|
||||
Whether the six-frame lead is constant across screens, or a property of these
|
||||
elements' keyframes, is **not measured**.
|
||||
|
||||
### Your entries 13/14 twins — re-derived, and it is not a second witness
|
||||
|
||||
I reran your comparison off the disc: publisher 10 vs 13 **3.06**, developer 11 vs
|
||||
14 **4.33**, control 10 vs 11 **47.91**. Identical to two decimals. ⚠️ But by your
|
||||
own rule this is *your renderer twice* — same `sylpheed-cli`, same disc — so what
|
||||
it establishes is that your addressing and arithmetic are right, **not** that the
|
||||
twins claim has independent support. I am recording it as the former.
|
||||
|
||||
## ✅ 2026-08-30 — I swept the whole disc for the ordinal foot-gun. Your screens are the exposed ones.
|
||||
|
||||
Last iteration I retracted three claims because `--build 10/11` on `GP_TITLE` are
|
||||
@@ -969,7 +1043,7 @@ discovered it after re-exporting. ❔ The sweeps may simply be a small term, and
|
||||
| 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, ⚠️ **per clause** | 🔴 **This row used to open with a single `**measured**` covering six clauses of different strength, and the port's `authored/flow.json` copied that word into a `MEASURED` provenance stamp for a clause whose evidence cell reads `none`. A bundled label is exactly as strong as its weakest cell.** Split: ✅ **measured** — initial focus varies boot to boot (2× `TUTORIAL`, 2× `NEW GAME`); ⬆⬇ move **one item per press** (indirect: the 4-press wrap count only works if each press moves one) and **wrap both ends**; ⬅➡ do nothing; Ⓑ on a submenu returns to the parent **with focus restored** (4/4); Ⓑ on the main menu → **title**, ≤ 0.4 s, no loading screen (2026-08-30). ✅ **AND BOTH WEAK CLAUSES ARE NOW MEASURED (2026-08-30, later)** — you can stamp them: **no auto-repeat** (a 2.0 s held ⬇ moves the cursor exactly **once**; the counter passes its control, a single tap giving exactly 1 spike) and **Ⓑ on the title → nothing** (20 s after a delivery-confirmed Ⓑ the screen is still the title with `PRESS Ⓐ BUTTON` up — and that run waited for the **plate pulse**, the title's own settled signature, which is what the confounded earlier attempt did not). ✅ The plate **is** re-drawn after Ⓑ from the menu, ~7 s later — [`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) |
|
||||
| Q7 | transitions | ✅ answered, **two numbers changed 2026-08-30** | a **fade through black**, drawn by the screen's own last-painting `.prm` quad. 🔴 **Fade-in is 12 units (0.20 s) on the menu/`EXTRAS` and 16 (0.27 s) on the title — this row's source used to say 0.87–4.08 s, which is the quad's CLEAR-hold, not its ramp** (a stale Python reader that shifted every keyframe time by one slot). Fade-out **is** on the disc: 10/10/8 units. ✅ **And the transition is OVERLAP, not ramp-then-hold, measured from the running game**: content elements start fading ~6 frames before the black quad's ramp begins, total blackout 9 frames ≈ 0.30 s, and the gap between screens is **one frame**. The "~14 units of black hold" this page used to carry was arithmetic and is **withdrawn** — [`screen-transitions.md`](../re/screen-transitions.md) |
|
||||
| Q8 | menu audio bindings | ✅ answered | cue vocabulary + bank **decoded**; event binding is a **name match** (the authors' own event names). ✅ **You CAN have the SE audio** — ⚠️ an earlier version of this row said it was "undecodable from the disc"; that was **retracted** and the row was stale. Three cues are located in `Static.slb` and **decode to PCM**: d-pad move `0x1ec0` (4 packets), Ⓑ back `0x0ec0` (2), Ⓐ confirm `0x5d6c0` (6), all mono 48 kHz. The bank is a packed run of XMA waves with no delimiter, so a wave is only (offset, packet count) — and ⚠️ the file order is **not** cue-id order, so the index cannot be counted out — [`menu-audio-cues.md`](../re/menu-audio-cues.md) |
|
||||
| Q9 | video binding + playback rules | ✅ answered | **decoded** from the movie manifest: `ADVERTISE_MOVIE`→`ADV.wmv` (boot intro *and* attract are one asset), `MS00A`→`S00A.wmv` is the new-game intro, `STAFF_ROLL`→the credits reel. ✅ **one Ⓐ skips a movie** (title at 57 s vs a 193 s baseline) — [`movie-binding.md`](../re/movie-binding.md) |
|
||||
| Q10 | music-bank sub-wave roles (intro+loop?) | ✅ answered | **two stems of one performance, played together** — sample-synchronous, equal duration, 32/32 banks. **Concatenating is wrong.** Not a seamless loop either — [`structures/bgm-two-stems.md`](../re/structures/bgm-two-stems.md). ⚠️ **Our reader said three until 2026-08-29** — the extra one was the **bank header**, emitted by `to_xma_riffs`; fixed, with a 28/28 disc-wide check and two regression tests — [`structures/slb-bank-header-not-a-wave.md`](../re/structures/slb-bank-header-not-a-wave.md) |
|
||||
|
||||
@@ -189,6 +189,20 @@ agent's loop prompt, i.e. nowhere durable. See [`README.md`](README.md) for the
|
||||
luck in one archive, not a property of the format, and it does not extend to the
|
||||
screens the port has left to do.
|
||||
|
||||
* **A layout fix has to be swept across every READER of that layout, not just the
|
||||
crate.** The keyframe record-layout fix (a pose's time precedes it) landed in
|
||||
`ui_layout.rs`, and `sylpheed-cli` was found stale and rebuilt. **Two more
|
||||
readers survived it**: `tools/re-capture/fade_quads.py`, which read each pose's
|
||||
time from `blk+36` — the *next* record's time word — and therefore printed a
|
||||
trailing untimed keyframe; and, through it,
|
||||
[`screen-transitions.md`](screen-transitions.md), which labelled the quad's
|
||||
**clear-hold** as its *fade-in* and published 0.87–4.08 s for a ramp that is
|
||||
0.20–0.27 s. Both looked right: a shifted time series is still monotone,
|
||||
plausible, and internally consistent. The tell is structural, not numeric — the
|
||||
stale reader **cannot time the last pose**, so any output with a trailing `t=—`
|
||||
or `-` is that bug's signature. Grep the corpus for readers of a structure
|
||||
before calling its fix done.
|
||||
|
||||
## Runtime / emulator
|
||||
|
||||
* **Look at the PNG** — and check its dimensions.
|
||||
|
||||
43
docs/re/data/fade-envelope-menu-to-title.txt
Normal file
43
docs/re/data/fade-envelope-menu-to-title.txt
Normal file
@@ -0,0 +1,43 @@
|
||||
# Menu -> title transition, per-frame, from the running game.
|
||||
# tools/re-capture/fade_decompose.sh -> log_ui_draws capture (260 frames)
|
||||
# tools/re-capture/fade_envelope.py (the fade quad = last FULL-SCREEN
|
||||
# UNTEXTURED quad in a frame: a .prm carries no tex[base=], and the fade
|
||||
# quad paints last).
|
||||
# Xenia VdSwap frame numbers. 2026-08-30.
|
||||
#
|
||||
# TIMING CHECK: F10 armed the capture at frame 1; the script sent (B) 1.2 s
|
||||
# later. 1.2 s at 30 Hz is frame ~36. The fade begins at 34. The press and
|
||||
# the transition agree without either being used to place the other.
|
||||
#
|
||||
# frame untextured full-screen quad alphas, in submission order
|
||||
28 [[64]] draws= 12 tex= 10
|
||||
29 [[64]] draws= 12 tex= 10
|
||||
30 [[64]] draws= 12 tex= 10
|
||||
31 [[64]] draws= 12 tex= 10
|
||||
32 [[64]] draws= 12 tex= 10
|
||||
34 [[64, 255], [64]] draws= 15 tex= 12 <- content starts fading
|
||||
35 [[64, 223], [64]] draws= 15 tex= 12
|
||||
36 [[64, 207], [64]] draws= 15 tex= 12
|
||||
37 [[64, 175], [64]] draws= 14 tex= 11
|
||||
39 [[64, 95], [64]] draws= 11 tex= 8
|
||||
40 [[64, 31], [64], [102]] draws= 12 tex= 6 <- BLACK QUAD APPEARS
|
||||
41 [[64, 15], [64], [127]] draws= 12 tex= 6
|
||||
43 [[64], [64], [255]] draws= 12 tex= 6 <- fully black
|
||||
44 [[64], [64], [255]] draws= 12 tex= 6
|
||||
45 [[64], [64], [255]] draws= 12 tex= 6
|
||||
46 [[64]] draws= 6 tex= 2 <- menu stops drawing (6 draws vs 12); ONE frame of black
|
||||
47 [[64]] draws= 8 tex= 6
|
||||
49 [[64]] draws= 8 tex= 6
|
||||
50 [[64]] draws= 8 tex= 6
|
||||
51 [[64]] draws= 8 tex= 6
|
||||
52 [[64]] draws= 8 tex= 6
|
||||
53 [[64]] draws= 8 tex= 6
|
||||
54 [[64]] draws= 8 tex= 6
|
||||
55 [[64]] draws= 8 tex= 6
|
||||
56 [[73]] draws= 8 tex= 6
|
||||
57 [[93]] draws= 8 tex= 6
|
||||
58 [[113]] draws= 9 tex= 7
|
||||
59 [[152]] draws= 12 tex= 10
|
||||
60 [[172]] draws= 12 tex= 10
|
||||
|
||||
missing submitted frames in this span: 33->34, 37->39, 41->43, 47->49
|
||||
@@ -27,16 +27,33 @@ The group is always four blocks, and always this shape:
|
||||
Read with the corpus's rule that a keyframe is the *start* of a ramp
|
||||
([`structures/ui-resting-pose.md`](structures/ui-resting-pose.md)).
|
||||
|
||||
🔴 **The table this section used to print was taken with a STALE READER, and both
|
||||
its numbers were wrong (corrected 2026-08-30).** `fade_quads.py` read each pose's
|
||||
time from `blk+36` — the *next* record's time word — so every time was shifted one
|
||||
slot and the last pose came out untimed (`t=—`). That is the same association the
|
||||
[record-layout fix](ui-keyframe-record-layout.md) retired in the crate; the Python
|
||||
helper was never swept with it. Fixed, and controlled against the rebuilt
|
||||
`screen info`, which prints `[0 12 70 80]` for the same element:
|
||||
|
||||
```
|
||||
$ tools/re-capture/fade_quads.py 4 5 6 # GP_TITLE
|
||||
build 4 (title) pteff00.prm t=16 α=255 t=261 α=0 t=269 α=0 t=— α=255
|
||||
build 5 (main menu) pteff00.prm t=12 α=255 t= 70 α=0 t= 80 α=0 t=— α=255
|
||||
build 6 (EXTRAS) pteff00.prm t=12 α=255 t= 64 α=0 t= 74 α=0 t=— α=255
|
||||
build 4 (title) pteff00.prm t= 0 α=255 t= 16 α=0 t=261 α=0 t=269 α=255
|
||||
build 5 (main menu) pteff00.prm t= 0 α=255 t= 12 α=0 t= 70 α=0 t= 80 α=255
|
||||
build 6 (EXTRAS) pteff00.prm t= 0 α=255 t= 12 α=0 t= 64 α=0 t= 74 α=255
|
||||
```
|
||||
|
||||
Under [Q1](ui-keyframe-time-unit.md)'s `1 unit = 1/60 s`: the screen holds black
|
||||
for **0.20 s**, then fades in over **0.87 s** (`EXTRAS`), **0.97 s** (main menu)
|
||||
or **4.08 s** (the title).
|
||||
**Every pose is timed. There is no untimed keyframe**, and the fade-out ramp is on
|
||||
the disc after all: `70 → 80` = 10 units for the main menu, `64 → 74` = 10 for
|
||||
`EXTRAS`, `261 → 269` = **8** for the title.
|
||||
|
||||
⚠️ **And the fade-IN was mislabelled by the same shift.** This page used to say the
|
||||
screen "holds black for **0.20 s**, then fades in over **0.87 s** (`EXTRAS`),
|
||||
**0.97 s** (main menu) or **4.08 s** (the title)". Those spans are `T2 − T1` under
|
||||
the stale pairing — the stretch where the quad sits at **α = 0**, i.e. the screen
|
||||
fully visible and *not* fading at all. Read correctly the screen starts black at
|
||||
`t = 0` and fades in over **12 units (0.20 s)** on the menu and `EXTRAS`, **16
|
||||
units (0.27 s)** on the title. A port pacing its menu fade-in off the old number
|
||||
would have run it **5× too slow**.
|
||||
|
||||
### ❔ The fade-OUT duration is not in this field
|
||||
|
||||
@@ -148,11 +165,65 @@ units ≈ 0.167 s**, not the ~24 units this page authored. That confirms the por
|
||||
agent's reading; I tried to refute it against the bytes and could not.
|
||||
|
||||
🔴 **So the measured ~0.4 s is NOT the ramp alone** — 0.4 s is ~24 units against a
|
||||
decoded 10. Something else occupies the other ~14 units. 🟡 That the remainder is
|
||||
exactly the black hold is *arithmetic that fits* (14 units = 0.233 s, inside this
|
||||
corpus's own 0.17–0.23 s plateau), **not a measurement** — and a fit that closes a
|
||||
question without evidence is what this pair of agents has been catching all week.
|
||||
The decomposition stays open.
|
||||
decoded 10. Something else occupies the other ~14 units.
|
||||
|
||||
## ✅ What the other ~14 units are — MEASURED 2026-08-30, and it is not a hold
|
||||
|
||||
This page previously guessed: *"that the remainder is exactly the black hold is
|
||||
arithmetic that fits (14 units = 0.233 s), **not a measurement**"*. It has now been
|
||||
measured, and **the guess was wrong**. There is no black hold inside the fade-out.
|
||||
|
||||
**Instrument.** [`fade_decompose.sh`](../../tools/re-capture/fade_decompose.sh)
|
||||
boots to the main menu, arms the UI draw capture there, then presses Ⓑ, so one
|
||||
260-frame window contains the whole screen change.
|
||||
[`fade_envelope.py`](../../tools/re-capture/fade_envelope.py) reads the fade quad's
|
||||
alpha per submitted frame. The quad is **identified, not guessed at**: a `.prm`
|
||||
primitive carries no `tex[base=…]`, and this element paints last
|
||||
([paint-order key](structures/ui-paint-order-key.md)), so it is the last
|
||||
full-screen *untextured* quad of a frame. Taking merely the last full-screen quad
|
||||
picks up textured backdrops and gives a different answer.
|
||||
|
||||
**Control.** The quad's ramp is *decoded* (10 units), so the instrument can be
|
||||
checked before it is believed. At Q1's 2 units per rendered frame, 10 units is 5
|
||||
frames. Measured: the quad is absent at frame 39 and `α=255` at frame 43 — **4
|
||||
submitted-frame steps**, with one unlogged frame inside the span. Agreement to
|
||||
within that one frame. An instrument that could not reproduce the decoded ramp
|
||||
could not be trusted on the undecoded remainder.
|
||||
|
||||
**The measurement** ([`data/fade-envelope-menu-to-title.txt`](data/fade-envelope-menu-to-title.txt)):
|
||||
|
||||
```
|
||||
frame 34 content elements begin fading (a 255 quad appears and decays)
|
||||
35..41 255 223 207 175 95 31 15 the content fade
|
||||
frame 40 the BLACK QUAD first appears, α=102
|
||||
41 α=127
|
||||
43 α=255 fully black
|
||||
45 last frame the menu draws
|
||||
frame 46 6 draws (vs 12) — ONE frame of black
|
||||
47+ the title's build starts
|
||||
```
|
||||
|
||||
✅ **The ~14 extra units are the content elements' own fade-outs, which start six
|
||||
frames BEFORE the quad's ramp and overlap it.** The blackout runs frame 34 → 43 =
|
||||
**9 submitted frames ≈ 0.30 s at 30 Hz**, of which the quad's ramp is the last 4.
|
||||
That is the same quantity the filmstrip measured as 0.367–0.400 s with coarser
|
||||
timing, and it decomposes as **overlap, not sequence**.
|
||||
|
||||
✅ **The inter-screen black is ONE frame** (46), not ~14 units. The draw count
|
||||
collapses from 12 to 6 for exactly one frame and the incoming build starts at 47.
|
||||
|
||||
📌 **For the port:** a transition is *not* "ramp the black quad for 10 units, then
|
||||
hold black for 14". It is "start the content elements fading, and 6 frames later
|
||||
ramp the black quad over its declared 10 units on top of them". Authoring it as a
|
||||
hold puts a sixth of a second of dead black in the middle of every screen change
|
||||
that the game does not have.
|
||||
|
||||
⚠️ **Reach.** One transition (main menu → title, via Ⓑ), one run. The frame axis
|
||||
has gaps — 232 `--- frame` headers over frames 3…260, so ~10 % of submitted frames
|
||||
carry no UI draw — which is ±1 frame on any span quoted here and is why the ramp is
|
||||
given as 4 steps rather than a duration to three digits. Whether the six-frame lead
|
||||
is constant across screens, or is a property of *these* elements' keyframes, is
|
||||
**not** measured.
|
||||
|
||||
| elements | final untimed block | what it does |
|
||||
|---|---|---|
|
||||
|
||||
78
tools/re-capture/fade_decompose.sh
Executable file
78
tools/re-capture/fade_decompose.sh
Executable file
@@ -0,0 +1,78 @@
|
||||
#!/usr/bin/env bash
|
||||
# Decompose a screen transition's ~0.4 s fade-out into RAMP + HOLD, at the
|
||||
# emulator's own frame granularity.
|
||||
#
|
||||
# docs/re/screen-transitions.md measures the fade-out as ~0.4 s (~24 units) while
|
||||
# `pteff00.prm`'s final declared ramp is 70->80 = 10 units. The remainder is
|
||||
# currently ARITHMETIC THAT FITS -- "the other 14 units must be the black hold"
|
||||
# -- and that page says so itself. This measures it instead.
|
||||
#
|
||||
# The design point: arm the UI draw capture ON THE MAIN MENU, then press (B).
|
||||
# One capture window then contains
|
||||
# * the menu's fade-OUT -- the unknown, and
|
||||
# * the title's fade-IN -- whose ramp IS decoded from the file (build 4's
|
||||
# pteff00.prm, t=16 a=255 -> t=261 a=0, 245 units),
|
||||
# so the run carries its own control: an instrument that cannot reproduce the
|
||||
# known fade-in cannot be trusted on the unknown fade-out.
|
||||
#
|
||||
# Traps inherited from menu_draw_capture.sh, both already paid for:
|
||||
# * F10 arms the capture AND opens the emulator menu bar; any Xenia UI makes
|
||||
# IsUIActive() true and every later guest keystroke is swallowed. Click the
|
||||
# game surface to dismiss before touching the pad.
|
||||
# * a 0.12 s tap gets missed; hold (B) 0.5 s and confirm [RE-INPUT] delivery.
|
||||
#
|
||||
# Usage: fade_decompose.sh [out_dir]
|
||||
set -u
|
||||
export HOME=/sylph-home/re SDL_AUDIODRIVER=dummy DISPLAY=:98
|
||||
SD="$(cd "$(dirname "$0")" && pwd)"
|
||||
OUT="${1:-/sylph-home/re/fadecap}"
|
||||
mkdir -p "$OUT"; rm -f "$OUT"/xenia_re_ui_draws_*.log
|
||||
alive(){ ps -o pid=,stat= -C xenia_canary 2>/dev/null | awk '$2 !~ /^Z/ {print $1}'; }
|
||||
shot(){ screenshot "$1" >/dev/null 2>&1; }
|
||||
screen(){ shot /tmp/fdc.png; python3 "$SD/screen_id.py" /tmp/fdc.png | awk '{print $1}'; }
|
||||
|
||||
( cd "$OUT" && nohup run-canary --mem_watch=false --log_ui_draws=true \
|
||||
--ui_draw_capture_frames="${FRAMES:-260}" \
|
||||
--ui_draw_capture_max="${MAXDRAWS:-400000}" \
|
||||
--logged_profile_slot_0_xuid=B13EBABEBABEBABE \
|
||||
>"$OUT/canary.stdout" 2>"$OUT/canary.stderr" & )
|
||||
sleep 8
|
||||
until xdotool search --name "Xenia-canary" >/dev/null 2>&1; do
|
||||
[ -n "$(alive)" ] || { echo "EMULATOR GONE"; exit 4; }; sleep 1
|
||||
done
|
||||
win="$(xdotool search --name "Xenia-canary" | tail -1)"
|
||||
|
||||
# 1. wait for the boot title; do not tap through the intro (a run that tapped
|
||||
# every 4 s delivered 88 presses and ended on a black screen).
|
||||
deadline=$(( SECONDS + 420 )); s=""
|
||||
while [ $SECONDS -lt $deadline ]; do
|
||||
s="$(screen)"; echo "t=${SECONDS}s $s"
|
||||
[ "$s" = "title" ] && break
|
||||
sleep 4
|
||||
done
|
||||
[ "$s" = "title" ] || { echo "NEVER REACHED THE TITLE"; exit 1; }
|
||||
|
||||
# 2. one (A) on the boot title -> main menu
|
||||
python3 "$SD/pad.py" tap A 0.5
|
||||
for _ in 1 2 3 4 5 6; do
|
||||
sleep 4; s="$(screen)"; echo " after A: $s"
|
||||
[ "$s" = "menu" ] && break
|
||||
done
|
||||
[ "$s" = "menu" ] || { echo "NO MENU (screen=$s)"; exit 2; }
|
||||
shot "$OUT/menu.png"
|
||||
|
||||
# 3. arm the capture, dismiss the menu bar F10 opened, then press (B).
|
||||
# Everything between F10 and (B) is spent inside the capture window, so keep
|
||||
# it short: the window is FRAMES submitted frames, not seconds.
|
||||
xdotool windowactivate --sync "$win"; sleep 1
|
||||
xdotool key F10; sleep 0.6
|
||||
xdotool mousemove 900 400 click 1; sleep 0.6
|
||||
echo "-- (B) at $(date +%S.%N) --"
|
||||
python3 "$SD/pad.py" tap B 0.5
|
||||
sleep 12
|
||||
shot "$OUT/after-b.png"; echo "screen after B: $(screen)"
|
||||
|
||||
grep -c "RE-INPUT" "$OUT/canary.stdout" 2>/dev/null | sed 's/^/[RE-INPUT] lines: /'
|
||||
ls -l "$OUT"/xenia_re_ui_draws_*.log 2>/dev/null || echo "NO CAPTURE LOG"
|
||||
grep -i "UI-CAP" "$OUT/canary.stdout" | tail -3
|
||||
echo "FADE CAPTURE DONE (emulator left running)"
|
||||
71
tools/re-capture/fade_envelope.py
Executable file
71
tools/re-capture/fade_envelope.py
Executable file
@@ -0,0 +1,71 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Per-frame alpha of the fade quad (`pteff00.prm`), from a `log_ui_draws` capture.
|
||||
|
||||
`screen-transitions.md` measures a screen change's fade-out as a ~0.4 s lump and
|
||||
then SPLITS it by arithmetic -- the declared ramp is 10 units, 0.4 s is ~24, "so
|
||||
the other ~14 must be the black hold". That page flags the split as a fit, not a
|
||||
measurement. This measures it.
|
||||
|
||||
Identifying the quad, rather than guessing at it: the fade quad is a `.prm`
|
||||
PRIMITIVE, so its draw carries NO `tex[base=...]`, and it is its screen's
|
||||
last-painting element (structures/ui-paint-order-key.md). So: per frame, the LAST
|
||||
full-screen draw with no bound texture. Taking merely the last full-screen quad
|
||||
picks up textured backdrops and gets a different answer.
|
||||
|
||||
⚠️ The frame axis has gaps. A 260-frame window produced 232 `--- frame` headers,
|
||||
so ~10 % of submitted frames carry no UI draw at all. A duration in frames is
|
||||
therefore +-1 frame per gap it spans, and this prints the gaps so a reader can
|
||||
see which spans are affected.
|
||||
|
||||
fade_envelope.py <capture.log>
|
||||
"""
|
||||
import re
|
||||
import sys
|
||||
sys.path.insert(0, __file__.rsplit("/", 1)[0])
|
||||
|
||||
W, H = 1280, 720
|
||||
VERT = re.compile(r"col=([0-9A-F]{8})")
|
||||
|
||||
|
||||
def envelope(log):
|
||||
"""Yield (frame, alpha|None) -- alpha of the last untextured full-screen quad."""
|
||||
frame, pending_untex = None, None
|
||||
last = {}
|
||||
seen = []
|
||||
for line in open(log):
|
||||
if line.startswith("--- frame"):
|
||||
if frame is not None:
|
||||
seen.append(frame)
|
||||
frame = int(line.split()[2])
|
||||
continue
|
||||
m = re.match(r"\s*(\d+) prim=(\d+) indices=(\d+)", line)
|
||||
if m:
|
||||
# a primitive draw has no bound texture
|
||||
pending_untex = ("tex[base=" not in line) and m.group(2) == "13"
|
||||
continue
|
||||
if "vb=0x" in line and pending_untex:
|
||||
cols = VERT.findall(line)
|
||||
# full-screen NDC quad: every vertex at +-1
|
||||
if cols and line.count("[-1.00,1.00,") >= 1:
|
||||
last[frame] = int(cols[-1][:2], 16)
|
||||
pending_untex = None
|
||||
if frame is not None:
|
||||
seen.append(frame)
|
||||
return last, seen
|
||||
|
||||
|
||||
def main():
|
||||
last, seen = envelope(sys.argv[1])
|
||||
gaps = [(a, b) for a, b in zip(seen, seen[1:]) if b != a + 1]
|
||||
print(f"# {len(seen)} frame headers, {seen[0]}..{seen[-1]}; "
|
||||
f"{sum(b-a-1 for a,b in gaps)} submitted frames carry no UI draw")
|
||||
print("# gaps: " + " ".join(f"{a}->{b}" for a, b in gaps))
|
||||
print("frame alpha")
|
||||
for f in seen:
|
||||
a = last.get(f)
|
||||
print(f"{f:6d} {'-' if a is None else a:>5}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -34,7 +34,13 @@ def parse(bundle):
|
||||
if blk+36 > len(bundle) or blk+36 > end: break
|
||||
g.append(dict(fade=be32(bundle,blk), sx=be32(bundle,blk+16), sy=be32(bundle,blk+20),
|
||||
x=struct.unpack_from(">i",bundle,blk+28)[0], y=struct.unpack_from(">i",bundle,blk+32)[0],
|
||||
t=(be32(bundle,blk+36) if blk+40<=end else None)))
|
||||
# A POSE'S TIME PRECEDES IT (ui-keyframe-record-layout.md).
|
||||
# This read `blk+36` -- the NEXT record's time word --
|
||||
# which shifted every time by one slot and left the
|
||||
# last pose untimed. That stale association is what
|
||||
# made screen-transitions.md print a 0.87-4.08 s
|
||||
# fade-in and an untimed fade-out. Corrected 2026-08-30.
|
||||
t=be32(bundle,blk-4)))
|
||||
groups[idx]=g; pos=end
|
||||
return names, groups
|
||||
|
||||
|
||||
Reference in New Issue
Block a user