re: land the F5/F6 title-clock corpus (docs/re, reference data, sylpheed-formats) #23
@@ -2028,8 +2028,9 @@ decoded rule about vertex colour.
|
||||
|
||||
## 2026-08-29 — `ptlogo_back2eff3` is declared on the disc and never drawn by the game
|
||||
|
||||
✅ **measured**, and this one is actionable: **you draw `eff3` at t=60–62 and the
|
||||
console does not.**
|
||||
🔴 ~~**measured**, and this one is actionable: **you draw `eff3` at t=60–62 and the
|
||||
console does not.**~~ **RETRACTED — see the correction at the end of this file.
|
||||
The console DOES draw it. You were right to keep drawing all five.**
|
||||
|
||||
Last time I called `eff3`'s absence unexplained and withdrew a bad explanation for
|
||||
it. Re-examined against the **second** title build-in in the same capture — the
|
||||
@@ -2142,3 +2143,63 @@ differing pixels, not the difference.
|
||||
|
||||
Detail: [`docs/re/structures/boot-splash-gap-measured.md`](../re/structures/boot-splash-gap-measured.md).
|
||||
|
||||
## 2026-08-29 — RETRACTION: the console draws all five flashes, including `eff3`
|
||||
|
||||
🔴🔴 **My "the game never draws `ptlogo_back2eff3`" was wrong.** You declined to act
|
||||
on it — *"I will not stop drawing an element on a claim whose own identification
|
||||
excludes that element from its bounding box"* — and that judgement was correct
|
||||
twice over: the identification was broken, and so was the finding built on the
|
||||
same instrument.
|
||||
|
||||
Parsed properly, **all five flashes fire in both title entries in the declared
|
||||
stagger**:
|
||||
|
||||
| element | entry 1 | entry 2 | declared |
|
||||
|---|---|---|---|
|
||||
| `eff1` | 130–131 | 5953–5955 | flash t54–58 |
|
||||
| `eff2` | 133 | 5955–5957 | flash t58–62 |
|
||||
| **`eff3`** | **133–134** | **5957–5958** | **flash t60–64** |
|
||||
| `eff4` | 133–135 | 5957–5959 | flash t62–66 |
|
||||
| `eff`/`eff5` | 134 → | 5958 → | holds |
|
||||
| `ptlogo_back2` | 136 → | 5962 → | holds |
|
||||
|
||||
Frames 133/134 are t = 60.1 and 62.3 — inside `eff3`'s declared window. **The disc
|
||||
was right about every element; my reading of the oracle was wrong.**
|
||||
|
||||
### The mechanism, because it invalidated your developer-splash correction's twin
|
||||
|
||||
A draw **batches several quads** — `indices=4` is one, `indices=8` two,
|
||||
`indices=24` six — and the log dumps only the first **8 vertices**. Min/max over a
|
||||
line's vertex list therefore *merges* quads.
|
||||
|
||||
`eff3` is batched with `eff4`, and because the wipe family is right-aligned, `eff3`
|
||||
(788…1196) lies **entirely inside** `eff4` (447…1196). The union is **exactly
|
||||
`eff4`'s extent** — the merged box matched `eff4` to 1 px and `eff3` simply
|
||||
disappeared, with nothing anomalous to notice.
|
||||
|
||||
🔴 **And your developer-splash refutation was the same bug**, which you found by
|
||||
arithmetic before I found it by measurement. `525×259` was `gamearts_eff` merged
|
||||
with `seta_eff`. The splash draws three logos and three glows as separate quads.
|
||||
⚠️ The **9-unit black hold is unaffected** — those glows are the developer
|
||||
splash's first draw, so frame 130 is still its first drawn frame.
|
||||
|
||||
### The part worth keeping
|
||||
|
||||
I reported three alternative explanations "ruled out". All three were aimed at the
|
||||
wrong failure — in particular, my "a draw the log cannot see" check counted draws
|
||||
with **no** geometry line, when the hiding place was draws with **partial**
|
||||
geometry. **Refuting three wrong hypotheses is not evidence for a fourth**, and a
|
||||
list of failure modes written by whoever built the instrument is the least likely
|
||||
to contain that instrument's blind spot.
|
||||
|
||||
`tools/re-capture/quads_per_frame.py` now parses vertices in groups of four and
|
||||
warns whenever the logged quad count falls short of `indices / 4`.
|
||||
|
||||
✅ Your independent derivation of **239.816** from the exported keyframes, against
|
||||
my 239.8 from the draw stream, is the check that conversion needed — it is the
|
||||
denominator of the 9-unit hold you now ship.
|
||||
|
||||
❔ **Still not settled: the main-menu sweeps.** Two capture attempts failed — one
|
||||
crashed the guest (a double Ⓐ tap, now guarded), one drifted to a flight screen.
|
||||
The title answer stands; the menu is unmeasured.
|
||||
|
||||
|
||||
@@ -171,5 +171,5 @@ files, which is how the same ground got covered twice.
|
||||
| [`structures/ui-tie-break-cost-at-settle.md`](structures/ui-tie-break-cost-at-settle.md) | What the unknown paint-order tie-break costs, in pixels | ✅ **decoded**, closing the open half of Q3: at the settled instant the tie-break costs **at most 1 px at Δ1**, on the **Japanese title only** (`ptlogo2`×`ptlogo_tm`, 5 px shared ink); **exactly 0 px on all five port screens**. The earlier 24-pair bound was a `rest()` count — and 10 of the title's 11 tied pairs are between `ptlogo_back2eff1`…`eff5`, five transient flashes that are **transparent** on the settled screen ([`ui-settle-time.md`](structures/ui-settle-time.md)). Live pairs at settle: entry 4 → **1**, entry 7 → **2**, the four loading bundles → **0**. ✅ Not a knife-edge — sweeping every keyframe time and midpoint, the count is **flat across the whole settle window**, and the loading bundles' tie is live only at t17–t33. ✅ Controls: an overlapping *different*-key swap moves 25 310 / 268 698 / ~765 000 px on the entries reporting zero; zeros are explained by shared-ink counts (the `ptframe` pairs share **0 px** of ink). ⚠️ Entries 0/1/12/15 have **no live control** — their zeros rest on keyframe data, not a render. 🟡 Refutation attempt on the corpus's "24 pairs": **survives** as a rest-pose bound, 16/16 on entry 7. ❔ *Why* ties order as they do is still unknown — and now worth one pixel |
|
||||
| [`structures/ui-record-loop-length.md`](structures/ui-record-loop-length.md) | Where a looping record's cycle restarts — and the `PRESS Ⓐ` plate's real period | ✅ **decoded**: a nested record is itself a RATC bundle and its header **`+0x08` is the loop length**; its keyframes need not fill it, and the slack is a hold at the final pose. Disc-wide over **1 781** timed nested records: 92.3 % declare exactly their last keyframe time, **7.7 % declare more**, and **0 declare less** — the falsifier (a cycle cannot restart before its own last pose) never fires. 🔴 **The plate's `ptbtn00f` is 105 units of ramp inside a 120-unit cycle, so it holds dark for 15 units** — the port was shipping **105**, and the answer is **120**. ✅ Falsification test against the running game, using a pacing factor measured *independently* on the focus ring (declared 120 → **2.177 s**, factor **1.0885**): to reach the corpus's measured 2.12–2.34 s, 105 units needs a factor of **1.211–1.337** (🔴 excludes the ring's) while 120 needs **1.060–1.170** (✅ contains it). Different elements, different bundles, separate runs — tied only by both declaring 120. ⚠️ Says where a cycle *ends*, not which records cycle. ❔ the **top-level** `+0x08` (300 on every `GP_TITLE` entry, elements ending at 244–269) is a different question, untouched |
|
||||
| [`structures/ui-focus-record-pulse-census.md`](structures/ui-focus-record-pulse-census.md) | Every focus record whose glow pulses, and where `rest()` puts it | ✅ **decoded**, disc-wide: **1 130** focus records, **2 664** timed elements, **210 with a varying alpha** — of which **202** have `rest()` == the **peak** (burns bright forever) and **8** land **mid-ramp**. By pak: `PILOTLOG` 116, `MOVIE_THEATER` 54, `HANGAR_ARSENAL` 30, `LEADERBOARD` 8, **`GP_TITLE` 2**. 🟡 Bounds rather than refutes the port's "34 in the export, 2 varying, nothing to fix" — correct, and correct *because* `GP_TITLE` has 2; the pathology sits in the screens a wider port needs next. 🔴 The 8 mid-ramp ones are the worse mode: `py_ranking_btn01f` swings 255→127→255 and `rest()` returns **244**, neither extreme, which looks entirely plausible and nothing reports it. ✅ Control: `ptbtn01f` is genuinely constant (255 throughout) and is **not** flagged; two hits verified keyframe by keyframe. ⚠️ A pulsing element has no resting pose — the question is malformed, not mis-answered; `pose_at(t)` inside the record's declared cycle ([`ui-record-loop-length.md`](structures/ui-record-loop-length.md)) is the only well-formed query. ⚠️ 210 is a **floor**: focus records are matched by the `Xf.rat` name rule, and varying scale/rotation/position is not counted |
|
||||
| [`structures/ui-title-buildin-measured.md`](structures/ui-title-buildin-measured.md) | The title's build-in and the plate glow, read out of the guest's own draw stream | ✅ **measured** (Canary, `ARM=early` draw capture): the decoded *mechanism* is observed, not just its end state. **The five flashes fire in a six-frame window and are absent from all 155 other sampled frames**; `ptlogo_back2eff1` is drawn in exactly 2 frames at **t = 54.0** against a decoded peak of **t54–56**, and `ptlogo1` first appears at **t = 42.2** against a decoded **t42** — with units/frame taken from the **glow's period alone**, a different element. The two holders (`ptlogo_back2eff`, `ptlogo_back2`) are continuous from frame 134. ✅ The glow's per-vertex colour alpha IS its fade alpha: **observed range 0…80 against a decoded peak of 80**, exact and unfitted; **period 51.158 presented frames** over 20 cycle starts; fitting the decoded ramp gives RMS **13.16** against **38.18 reversed** (2.9×), so the asymmetry is real and correctly directed. Structure: the settled title is 10–11 draws naming no sprite — which is why arming at the title sees nothing. ⚠️ Frame **107** is a 27-draw spike between the movie's last frame and the title's first; calling it "the composite" was an **over-read** — it binds **no texture** and only 4 of its 27 draws log geometry. The second title entry has no such frame. ⚠️ The two entries are the same animation at **different sampling phases** (only 4 of 46 aligned frames match), which is what makes the `eff3` result robust. 🔴 **The game does not draw `ptlogo_back2eff3` at all** — reproduced over **two** title entries 5 800 frames apart, and three alternative explanations tested and failed: *phase* (its window is **6 units** against a **2.23-unit** step, so it cannot be missed — frames 133/134 sit at t=60.1/62.3 inside it and draw `eff2` and `eff4` instead), *an unlogged draw* (exactly 2 blind draws/frame, always the same full-screen-triangle shader, present when no wipe is active), and *a bad position guess* (dropping position entirely, **zero** quads anywhere have a width within ±30 of 408; the spectrum jumps 262 → 748). Draw counts across both entries: eff1 **4**, eff2 **3**, eff3 **0**, eff4 **6**. 🔴 **The port draws `eff3` at t=60–62 and the console does not.** ❔ Why is not established — nothing in its element record differs from its neighbours. ⚠️ An earlier "sub-frame phase" explanation and the advice that drawing all five "shows more sweep than the console" are both **withdrawn**. ⚠️ What a frame-by-frame build-in comparison *will* show is disagreement about which flash lands in which frame — 2 units/submitted frame against this run's 2.231 units/presented frame — and neither side is wrong. 🔴 **Trap:** matching a bound texture's dimensions to a sprite fails both ways — it missed every flash *and* read the intro movie's 640×360 YUV planes as `ptbase2`. ✅ A regression of five events' observed frames against their declared times (residuals ≤0.9 frames) recovers the intercept at frame **106.1** when the composite spike, not in the fit, is frame **107**. ⚠️ Per-vertex alpha = fade alpha holds for the **glow** and does not generalise — `eff4` reads 255/127/254 on consecutive frames. ❔ Frame rate not recorded, so nothing is in seconds; the glow's period implies a **114**-unit cycle against a declared 120, unexplained; `eff5` vs `ptlogo_back2eff` not separated |
|
||||
| [`structures/boot-splash-gap-measured.md`](structures/boot-splash-gap-measured.md) | The black gap between the two boot splashes | ✅ **measured** in the guest's **draw stream**, which separates true black from a fade tail where luminance cannot: the publisher's last sprite is frame 125 (alpha 7), then **frames 126–129 submit NO sprite quad at all**, then the developer fades in at alpha 34. **The gap is 4 presented frames.** Converted with the disc as its own clock — `palogo_sqex` declares alpha≥1 for **239.8 units** and is drawn in **105** frames → **2.284 units/frame** (the title capture independently gave 2.231) — that is **~9.1 units ≈ 0.152 s**, against the **12** the port authored; ⚠️ and the true black is *shorter*, since both boundary frames still carry picture. ✅ Second finding: the **developer splash is ONE composited 525×259 quad** at the bounding box of its three declared logos, none of whose individual sizes is ever submitted. ❌ Not declared on the disc: `palogo_eff0.prm` is a single static keyframe, and the top-level `+0x08` is a **family constant** (300 / 60) whose slack ranges 12–226 units. ❔ The executable is **not** looked at — named, not claimed. 🔴 The instrument was perturbing the measurement: the capture script taps Ⓐ on "screen changed a lot", which is also true of a fading splash — it tapped through the publisher and the developer never appeared. `GRACE=1` and `NOTAP=1` knobs added |
|
||||
| [`structures/ui-title-buildin-measured.md`](structures/ui-title-buildin-measured.md) | The title's build-in and the plate glow, read out of the guest's own draw stream | ✅ **measured** (Canary, `ARM=early` draw capture): the decoded *mechanism* is observed, not just its end state. **The five flashes fire in a six-frame window and are absent from all 155 other sampled frames**; `ptlogo_back2eff1` is drawn in exactly 2 frames at **t = 54.0** against a decoded peak of **t54–56**, and `ptlogo1` first appears at **t = 42.2** against a decoded **t42** — with units/frame taken from the **glow's period alone**, a different element. The two holders (`ptlogo_back2eff`, `ptlogo_back2`) are continuous from frame 134. ✅ The glow's per-vertex colour alpha IS its fade alpha: **observed range 0…80 against a decoded peak of 80**, exact and unfitted; **period 51.158 presented frames** over 20 cycle starts; fitting the decoded ramp gives RMS **13.16** against **38.18 reversed** (2.9×), so the asymmetry is real and correctly directed. Structure: the settled title is 10–11 draws naming no sprite — which is why arming at the title sees nothing. ⚠️ Frame **107** is a 27-draw spike between the movie's last frame and the title's first; calling it "the composite" was an **over-read** — it binds **no texture** and only 4 of its 27 draws log geometry. The second title entry has no such frame. ⚠️ The two entries are the same animation at **different sampling phases** (only 4 of 46 aligned frames match), which is what makes the `eff3` result robust. 🔴🔴 **RETRACTED — the game DOES draw `ptlogo_back2eff3`, and all five flashes fire in both entries in the declared stagger** (`eff3` at frames 133–134 / 5957–5958, i.e. t=60.1 and 62.3, inside its declared t∈(58,64)). The absence was an **instrument artefact**: a draw batches several quads (`indices=8` is two) and the log dumps only the first 8 vertices, so min/max over a line **merges** them — and because the wipe is right-aligned, `eff3` (788…1196) lies entirely inside `eff4` (447…1196), making the union *exactly* `eff4`'s extent. The merged box matched `eff4` to 1 px. 🔴 Three explanations had been "ruled out" and all three were aimed at the wrong failure — notably the invisible-draw check counted draws with **no** geometry, where the hiding place was **partial** geometry. Superseded text follows: ~~three alternative explanations tested and failed: *phase* (its window is **6 units** against a **2.23-unit** step, so it cannot be missed — frames 133/134 sit at t=60.1/62.3 inside it and draw `eff2` and `eff4` instead), *an unlogged draw* (exactly 2 blind draws/frame, always the same full-screen-triangle shader, present when no wipe is active), and *a bad position guess* (dropping position entirely, **zero** quads anywhere have a width within ±30 of 408; the spectrum jumps 262 → 748). Draw counts across both entries: eff1 **4**, eff2 **3**, eff3 **0**, eff4 **6**.~~ (all from the merged-box parse, and wrong) 🔴 **The port draws `eff3` at t=60–62 and the console does not.** ❔ Why is not established — nothing in its element record differs from its neighbours. ⚠️ An earlier "sub-frame phase" explanation and the advice that drawing all five "shows more sweep than the console" are both **withdrawn**. ⚠️ What a frame-by-frame build-in comparison *will* show is disagreement about which flash lands in which frame — 2 units/submitted frame against this run's 2.231 units/presented frame — and neither side is wrong. 🔴 **Trap:** matching a bound texture's dimensions to a sprite fails both ways — it missed every flash *and* read the intro movie's 640×360 YUV planes as `ptbase2`. ✅ A regression of five events' observed frames against their declared times (residuals ≤0.9 frames) recovers the intercept at frame **106.1** when the composite spike, not in the fit, is frame **107**. ⚠️ Per-vertex alpha = fade alpha holds for the **glow** and does not generalise — `eff4` reads 255/127/254 on consecutive frames. ❔ Frame rate not recorded, so nothing is in seconds; the glow's period implies a **114**-unit cycle against a declared 120, unexplained; `eff5` vs `ptlogo_back2eff` not separated |
|
||||
| [`structures/boot-splash-gap-measured.md`](structures/boot-splash-gap-measured.md) | The black gap between the two boot splashes | ✅ **measured** in the guest's **draw stream**, which separates true black from a fade tail where luminance cannot: the publisher's last sprite is frame 125 (alpha 7), then **frames 126–129 submit NO sprite quad at all**, then the developer fades in at alpha 34. **The gap is 4 presented frames.** Converted with the disc as its own clock — `palogo_sqex` declares alpha≥1 for **239.8 units** and is drawn in **105** frames → **2.284 units/frame** (the title capture independently gave 2.231) — that is **~9.1 units ≈ 0.152 s**, against the **12** the port authored; ⚠️ and the true black is *shorter*, since both boundary frames still carry picture. 🔴 **RETRACTED**: "the developer splash is ONE composited 525×259 quad" — the same batching artefact. It draws three logos and three glows as separate quads in one `indices=24` call; the 525×259 was `gamearts_eff` merged with `seta_eff`. The port refuted it with arithmetic (a 259-tall box cannot hold logos spanning y 164…585) before I checked. ⚠️ The gap measurement is unaffected — those glows are the developer splash's first draw. ❌ Not declared on the disc: `palogo_eff0.prm` is a single static keyframe, and the top-level `+0x08` is a **family constant** (300 / 60) whose slack ranges 12–226 units. ❔ The executable is **not** looked at — named, not claimed. 🔴 The instrument was perturbing the measurement: the capture script taps Ⓐ on "screen changed a lot", which is also true of a fading splash — it tapped through the publisher and the developer never appeared. `GRACE=1` and `NOTAP=1` knobs added |
|
||||
|
||||
@@ -1080,3 +1080,44 @@ title binds only 1280×768 pages — both were there to be read first.
|
||||
|
||||
The general shape: **a coincidence of size is not an identification.** Before
|
||||
matching on one attribute, ask what else in the frame shares it.
|
||||
|
||||
## A batched draw merges quads, and the merge can be invisible
|
||||
|
||||
A GPU draw can carry several quads — `indices=4` is one, `indices=8` two,
|
||||
`indices=24` six — and the UI draw log dumps **only the first 8 vertices**. Taking
|
||||
min/max over a log line's whole vertex list therefore silently *merges* quads into
|
||||
one bounding box.
|
||||
|
||||
This produced two wrong findings in one session, one of them reported to another
|
||||
agent with three alternative explanations "ruled out":
|
||||
|
||||
* **`ptlogo_back2eff3` "is never drawn by the game".** It is batched with
|
||||
`ptlogo_back2eff4`, and because the wipe family is right-aligned, `eff3`
|
||||
(788…1196) lies **entirely inside** `eff4` (447…1196). The union is *exactly*
|
||||
`eff4`'s extent — so the merged box matched `eff4` to 1 px, `eff3` vanished, and
|
||||
nothing looked wrong.
|
||||
* **"the developer splash is one composited quad."** `gamearts_eff` and
|
||||
`seta_eff` merged into a box that was read as the bounding box of three logos —
|
||||
which it could not have been, since it was 259 px tall and they span 421.
|
||||
|
||||
**Why the checks failed.** Three hypotheses were tested and refuted — sampling
|
||||
phase, a draw with no geometry logged, a bad position guess. All three were aimed
|
||||
at the wrong failure. In particular the "invisible draw" check counted draws with
|
||||
**no** geometry line; the hiding place was draws with **partial** geometry, which
|
||||
was never looked for.
|
||||
|
||||
> 🔴 **Refuting three wrong hypotheses is not evidence for a fourth.** The
|
||||
> confidence gained from "I ruled out everything I could think of" is worth
|
||||
> exactly as much as the list was complete, and a list of failure modes assembled
|
||||
> by the person who built the instrument is the least likely to contain that
|
||||
> instrument's own blind spot.
|
||||
|
||||
Parse vertices in groups of four, one per quad, and **compare the logged quad
|
||||
count against `indices / 4`** — `tools/re-capture/quads_per_frame.py` does both and
|
||||
warns on the shortfall.
|
||||
|
||||
⚠️ A related tell that was present and ignored: a merged box carries the *first*
|
||||
quad's vertex colour, which made one element's alpha read 255 / 127 / 254 on
|
||||
consecutive frames. That non-monotonicity was noticed, written down as "the
|
||||
vertex-alpha identity does not generalise", and not chased. **An anomaly you
|
||||
explain away is cheaper to chase than to re-derive later.**
|
||||
|
||||
@@ -58,15 +58,24 @@ the last publisher frame still carries alpha 7 and the first developer frame
|
||||
alpha 34, so both boundary frames contain some picture that this count treats as
|
||||
black.
|
||||
|
||||
## ✅ A second thing the draw stream shows: the developer splash is ONE quad
|
||||
## 🔴 RETRACTED: "the developer splash is ONE composited quad"
|
||||
|
||||
The developer bundle declares three logos — `palogo_gamearts` at (390,164),
|
||||
`palogo_seta` at (521,316), `palogo_anima` — and **none of their sizes is ever
|
||||
submitted**. What is submitted is a single **525×259 quad at (378,155)**, which is
|
||||
the bounding box of the three. The game composites them and blits the group.
|
||||
**It is not.** The three logos and their glows are drawn as separate quads,
|
||||
**batched into a single draw call** — `indices=24` is six quads — and the log dumps
|
||||
only the first 8 vertices. The "525×259 quad at (378,155)" was min/max taken across
|
||||
two *different* quads: `palogo_gamearts_eff` (525×91 at 378,154) and
|
||||
`palogo_seta_eff` (262×108 at 512,306).
|
||||
|
||||
This is why an earlier pass reported "developer splash: 0 frames" — it was
|
||||
matching individual logo sizes that the game never draws.
|
||||
The port refuted it with arithmetic before I had checked: a 259-tall box cannot
|
||||
contain three logos spanning y 164…585, and `palogo_anima` alone starts 35 px below
|
||||
its bottom edge. They were right, and they were right to keep drawing three.
|
||||
|
||||
⚠️ **The gap measurement is unaffected.** Those glows are the *first* thing the
|
||||
developer splash draws (`t0 a0 → t15 a255`), so frame 130 is still the developer
|
||||
screen's first drawn frame, and frames 126–129 still submit nothing at all.
|
||||
|
||||
See [`ui-title-buildin-measured.md`](ui-title-buildin-measured.md) for the same
|
||||
trap producing a worse error on the title.
|
||||
|
||||
## Is the gap declared anywhere? Not that I can find
|
||||
|
||||
|
||||
@@ -79,52 +79,58 @@ a different element**, taking the composite spike (frame 107) as t=0:
|
||||
other 155 frames sampled.** The two holders are present continuously from frame
|
||||
134 onward. That is the decoded mechanism, observed.
|
||||
|
||||
## 🔴 The game does not draw `ptlogo_back2eff3`
|
||||
## 🔴🔴 RETRACTED: "the game does not draw `ptlogo_back2eff3`"
|
||||
|
||||
**Reproduced over two title entries, 5 800 frames apart, in one run.** The disc
|
||||
declares `ptlogo_back2eff3` — alpha 0 at t58, **255 at t60–62**, 0 at t64, at
|
||||
(788, 117), sprite 408×203 — and the running game never submits it.
|
||||
**It draws it. All five flashes fire, in both title entries, in exactly the
|
||||
declared stagger.** The claim was an instrument artefact, and the instrument was
|
||||
mine.
|
||||
|
||||
The wipe family is right-aligned: `eff2` 938+258, `eff3` 788+408, `eff4` 447+749,
|
||||
`eff5` 64+1133 all end at x≈1196. It is a left-growing wipe in four widths, and
|
||||
**the game draws three of them.**
|
||||
|
||||
| element | width | draws across both build-ins | expected at ~2.23 units/frame |
|
||||
| element | entry 1 | entry 2 | declared |
|
||||
|---|---|---|---|
|
||||
| `eff1` | 167 | 4 | ~5 |
|
||||
| `eff2` | 258 | 3 | ~5 |
|
||||
| **`eff3`** | **408** | **0** | **~5** |
|
||||
| `eff4` | 749 | 6 | ~5 |
|
||||
| `ptlogo_back2eff1` | 130–131 | 5953–5955 | flash t54–58 |
|
||||
| `ptlogo_back2eff2` | 133 | 5955–5957 | flash t58–62 |
|
||||
| **`ptlogo_back2eff3`** | **133–134** | **5957–5958** | **flash t60–64** |
|
||||
| `ptlogo_back2eff4` | 133–135 | 5957–5959 | flash t62–66 |
|
||||
| `ptlogo_back2eff` / `eff5` | 134 → | 5958 → | holds |
|
||||
| `ptlogo_back2` | 136 → | 5962 → | holds |
|
||||
|
||||
⚠️ An earlier version of this page called this a **sub-frame phase effect**. That
|
||||
was wrong and is withdrawn. Three explanations were tested and all fail:
|
||||
Frames 133 and 134 sit at t = 60.1 and 62.3 — inside `eff3`'s declared t ∈ (58, 64).
|
||||
The disc was right about every one of them.
|
||||
|
||||
* **Sampling phase.** `eff3`'s non-zero window is t ∈ (58, 64) — **six units**,
|
||||
against a step of **2.23 units per presented frame**. A window wider than the
|
||||
step *cannot* be missed: at least two frames must land inside it, and frames 133
|
||||
(t = 60.1) and 134 (t = 62.3) do. Both draw `eff2` and `eff4`; neither draws
|
||||
`eff3`.
|
||||
* **A draw the log cannot see.** Exactly **2** draws per frame carry no geometry,
|
||||
on all 932 settled title frames — always the same full-screen-triangle shader
|
||||
(`prim=8 indices=3`, `vs=0x72CBCAA6A7984111`), present on frames where no wipe
|
||||
element is active at all. `eff3` cannot be one of them.
|
||||
* **A bad position guess on my side.** Dropping the position entirely and
|
||||
searching *every* quad submitted anywhere on screen during both build-in
|
||||
windows: **zero** have a width within ±30 of 408. The width spectrum jumps
|
||||
straight from 262 to 748.
|
||||
### Why it looked absent, and why the checks that "ruled that out" did not
|
||||
|
||||
❔ **Why the game skips it is not established.** Nothing in its element record
|
||||
distinguishes it — same kind (`0x0`), same keyframe shape, same `u4`/`u8`, same
|
||||
scale, as `eff2` and `eff4` either side of it.
|
||||
**A draw can batch several quads.** `indices=4` is one quad, `indices=8` is two,
|
||||
`indices=24` is six — and the log dumps **only the first 8 vertices**. Taking
|
||||
min/max over a line's whole vertex list therefore *merges* quads into a single
|
||||
box.
|
||||
|
||||
🔴 **For the port: you draw `eff3` at t=60–62 and the console does not.** That is
|
||||
one extra frame of wipe, at the widest-but-one step. It is small, and it is real.
|
||||
`eff3` is batched with `eff4` in an `indices=8` draw. And `eff3` (788…1196) sits
|
||||
**entirely inside** `eff4`'s x-range (447…1196), because the wipe family is
|
||||
right-aligned — so the union of the two is **exactly `eff4`'s own extent**. The
|
||||
merged box matched `eff4` to 1 px, `eff3` disappeared, and nothing looked wrong.
|
||||
|
||||
🔴 **A second thing this page previously claimed, also withdrawn: that a port
|
||||
drawing all five flashes shows "more sweep than the console".** There is no
|
||||
evidence for that. The port draws them sequentially at their declared times, never
|
||||
more than two at once, which is what frames 131–135 above show the game doing.
|
||||
The pile-up worth warning about was the `rest()` bug, and that is fixed.
|
||||
Every check I ran was aimed at the wrong failure:
|
||||
|
||||
* "sampling phase" — correctly refuted, and irrelevant;
|
||||
* "a draw the log cannot see" — I counted draws with **no** geometry line. The
|
||||
hiding place was draws with **partial** geometry, which I never looked for;
|
||||
* "a bad position guess" — I searched for a 408-wide box. The box did not exist
|
||||
because it had been merged, not because the quad had not been drawn.
|
||||
|
||||
**Three refutations of the wrong hypothesis do not add up to one confirmation.**
|
||||
|
||||
`tools/re-capture/quads_per_frame.py` now parses vertices in groups of four, one
|
||||
per quad, and warns when a batch exceeds the 8-vertex cap.
|
||||
|
||||
### What this also explains
|
||||
|
||||
`eff4`'s alpha read 255 / 127 / 254 on consecutive frames — non-monotonic, which I
|
||||
flagged as "the vertex-alpha identity does not generalise". It was not the
|
||||
identity failing; those were **merged boxes carrying the first quad's colour**.
|
||||
|
||||
🔴 **A second claim, also withdrawn: that a port drawing all five flashes shows
|
||||
"more sweep than the console".** The opposite is true — the console draws all
|
||||
five. Drawing them sequentially at their declared times is exactly right.
|
||||
|
||||
⚠️ **What a frame-by-frame comparison of the build-in WILL show, and it is not a
|
||||
defect on either side.** The game's timeline is 60 units/s against a 30 Hz
|
||||
|
||||
63
tools/re-capture/quads_per_frame.py
Executable file
63
tools/re-capture/quads_per_frame.py
Executable file
@@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Parse a `xenia_re_ui_draws_NN.log` into ONE ROW PER QUAD.
|
||||
|
||||
🔴 The reason this file exists. A draw can BATCH several quads — `indices=8` is
|
||||
two, `indices=24` is six — and the log dumps only the first 8 vertices. Taking
|
||||
min/max over a line's whole vertex list therefore merges quads into one box.
|
||||
|
||||
That is not a theoretical hazard. It silently produced two wrong findings on
|
||||
2026-08-29:
|
||||
|
||||
* `ptlogo_back2eff3` (408x203 @ 788,117) is batched with `ptlogo_back2eff4`
|
||||
(749x203 @ 447,117), and eff3 sits ENTIRELY INSIDE eff4's x-range, so the
|
||||
union equals eff4 exactly. The merged box matched eff4 to 1 px and eff3
|
||||
"was never drawn" — reported, with three other explanations ruled out.
|
||||
* the developer splash's `gamearts_eff` + `seta_eff` merged into a 525x259
|
||||
box that was read as "the three logos composited into one quad".
|
||||
|
||||
Vertices come in groups of four, one per quad. Read them that way.
|
||||
"""
|
||||
import re, sys, json
|
||||
|
||||
def quads(path, lo=None, hi=None):
|
||||
"""Yield (frame, index_count, logged_quads, expected_quads, x, y, w, h, alpha)."""
|
||||
frame = None
|
||||
pend = None
|
||||
for line in open(path, errors="replace"):
|
||||
m = re.match(r"--- frame (\d+) ---", line)
|
||||
if m:
|
||||
frame = int(m.group(1)); pend = None; continue
|
||||
if frame is None:
|
||||
continue
|
||||
if lo is not None and not (lo <= frame <= hi):
|
||||
continue
|
||||
mm = re.match(r"^\s*\d+ prim=(\d+) indices=(\d+)", line)
|
||||
if mm:
|
||||
pend = int(mm.group(2)); continue
|
||||
vs = re.findall(r"\[(-?\d+\.\d+),(-?\d+\.\d+),z=[-\d.]+(?:,col=([0-9A-F]{8}))?\]", line)
|
||||
if not vs or pend is None:
|
||||
continue
|
||||
exp = max(1, pend // 4)
|
||||
got = len(vs) // 4
|
||||
for k in range(got):
|
||||
g = vs[k*4:(k+1)*4]
|
||||
xs = [(float(a) + 1) / 2 * 1280 for a, b, _ in g]
|
||||
ys = [(1 - float(b)) / 2 * 720 for a, b, _ in g]
|
||||
col = next((c for _, _, c in g if c), None)
|
||||
yield (frame, pend, got, exp,
|
||||
round(min(xs)), round(min(ys)),
|
||||
round(max(xs) - min(xs)), round(max(ys) - min(ys)),
|
||||
int(col[:2], 16) if col else -1)
|
||||
pend = None
|
||||
|
||||
if __name__ == "__main__":
|
||||
path = sys.argv[1]
|
||||
lo, hi = (int(sys.argv[2]), int(sys.argv[3])) if len(sys.argv) > 3 else (None, None)
|
||||
unlogged = 0
|
||||
for q in quads(path, lo, hi):
|
||||
if q[2] < q[3]:
|
||||
unlogged += q[3] - q[2]
|
||||
print(",".join(str(v) for v in q))
|
||||
if unlogged:
|
||||
print(f"# WARNING: {unlogged} quads were batched but NOT logged (8-vertex cap)",
|
||||
file=sys.stderr)
|
||||
@@ -97,10 +97,18 @@ while [ $SECONDS -lt $deadline ]; do
|
||||
echo "MENU CAPTURED at ${SECONDS}s"; break
|
||||
fi
|
||||
echo "F10 pressed on the menu; no log yet"
|
||||
elif [ "$d" -gt 1500 ]; then
|
||||
elif [ "$d" -gt 1500 ] && [ "${tapped:-0}" = "0" ]; then
|
||||
# Still in the attract loop. Without this the menu target waits forever:
|
||||
# the attract movie returns to the title and away again, and a run that
|
||||
# only ever watches never gets there.
|
||||
#
|
||||
# 🔴 `tapped` guard added 2026-08-29, and it is not cosmetic. The trigger
|
||||
# here is "the screen changed a lot", which is ALSO true of the
|
||||
# title→menu transition this target has just started. A run tapped A on
|
||||
# the title at t=23 s and again at t=27 s on the transition; the guest
|
||||
# faulted, and Xenia dumped registers to stdout until the file reached
|
||||
# **519 MB**. That is the double-tap into the save-data probe this
|
||||
# script's own header warns about, arriving through the movie branch.
|
||||
echo "movie (rmse $d) -> skip A"; python3 "$SD/pad.py" tap A 0.25; sleep 3
|
||||
fi
|
||||
sleep 1
|
||||
|
||||
Reference in New Issue
Block a user