The port isolated main_menu's 10.88 -> 13.02 regression to pteff10 and asked whether the per-draw log shows it alpha-over, which would be a counter-example to ui-blend-mode-decoded.md. It does not. blend-bit-vs-oracle.txt has pteff10 ADDITIVE on entry 5 (main menu) AND entry 6 (extras), both labels read out of the guest command stream, and HANDOFF states it from three independent menu sessions, every frame. The premise came from a stale coverage table of mine that the same HANDOFF entry had already corrected in place. Recording that: a correction which leaves the wrong table visible upstream of it has not landed. And the regression was predicted on this exact element before adoption -- HANDOFF flagged pteff10 as the one row the port's renderer would not corroborate, because for a dim wholly-semi-transparent glow (max alpha 130) over a dark background additive and alpha-over nearly coincide. A renderer metric getting worse while the render gets more correct is the standing rule with a number on it. Separately, the port answered my finding-3 candidate: a frozen sweep of the plate region rises monotonically across the declared 214->236, so the port fades rather than pops and its t=236 is a completion. That kills the onset branch. Two of the play-test's four named candidates for finding 3 are now dead -- units/s (measured at 60) and the ramp. Finding 3 has no surviving named cause. The clock origin is the one to take next, because every measurement so far is a DIFFERENCE between two events and a difference cannot detect a common offset. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jc4pciRArGHfxGGhEbwp5t
185 lines
8.7 KiB
Markdown
185 lines
8.7 KiB
Markdown
# ✅ The guest presents at **30 fps**, so the UI clock runs at **60 units/s** — the port's value stands
|
||
|
||
**Status: ✅ measured.** Instrument: ⟨capture⟩ — the real game in Xenia Canary,
|
||
per-draw, one boot, 2026-09-01. Ruler: ⟨disc⟩ — `ADV.wmv`'s own ASF header.
|
||
Answered against
|
||
[`guest-frame-rate-preregistration.md`](guest-frame-rate-preregistration.md),
|
||
committed **before** the capture was taken.
|
||
Data: [`data/guest-frame-rate-cadence.txt`](data/guest-frame-rate-cadence.txt).
|
||
|
||
---
|
||
|
||
## The result
|
||
|
||
| | predicted | measured |
|
||
|---|---|---|
|
||
| **H_A** guest 30 fps ⇒ **60 units/s** | 1.0 labels per movie frame | — |
|
||
| **H_B** guest 60 fps ⇒ 120 units/s | 2.0 | — |
|
||
| | | **1.0000** |
|
||
|
||
**H_A, and not marginally.** The plate's `t = 236` is **3.93 s**.
|
||
|
||
🔴 **This closes the question the port was blocked on, and the answer is that the
|
||
port was already right.** `HANDOFF.md` §H3 said *"units per second is still open,
|
||
and it is now the only place the disagreement lives"*, and warned *"do not change
|
||
your 60 on my account yet."* Good: 60 is correct and nothing should change.
|
||
|
||
⚠️ **So the play-test's "the plate arrives late" is NOT a units-per-second
|
||
error.** The leading candidate is eliminated. See "what this re-opens" below.
|
||
|
||
## Why this measurement does not have the defect the previous two had
|
||
|
||
Canary presents at ~27–28 fps and runs the guest slower than real time by an
|
||
unknown factor, so *a 30 Hz guest at full speed* and *a 60 Hz guest at half speed*
|
||
produce the identical wall-clock observation. Both earlier readings were wall
|
||
clock, and they disagree by **2.9×** (2.13 s vs 0.73 s for the same interval).
|
||
|
||
The ruler here is not a clock at all. `ADV.wmv` — the boot intro and the attract
|
||
movie, the same asset — declares its video rate **in the shipped file**:
|
||
|
||
```
|
||
ExtendedStreamProperties stream #2 avgTimePerFrame = 333333 x100ns -> 30.0000 fps
|
||
```
|
||
|
||
⚠️ Stream **#1** is the audio and its `avgTimePerFrame` is 3 276 559, which reads
|
||
as 3.05 fps. Reading the wrong stream is the obvious way to get this wrong, so it
|
||
is recorded rather than silently avoided.
|
||
|
||
A decoded movie frame is therefore a tick that the emulator's speed cannot
|
||
stretch: however slowly Canary runs, the guest still decodes 30 movie frames per
|
||
second **of movie time**. Counting presented frames per decoded movie frame gives
|
||
`guest_fps / 30` with **no wall clock anywhere in the chain**.
|
||
|
||
## The measurement, and both guards
|
||
|
||
One presented frame = one `RESOLVE` to `dest=0x14570000`. There are **595** of
|
||
them against the logger's own `FRAMES=600` budget, so the two notions of "frame"
|
||
agree and the count is not the harness's.
|
||
|
||
```
|
||
movie luma draws : 156
|
||
movie spans frames : 439..594 (156 frames)
|
||
presented frames per movie fr : 1.0000
|
||
```
|
||
|
||
**Guard 2 — is it really a buffer cycle?** Yes, and stronger than asked:
|
||
|
||
```
|
||
distinct 1280x720 luma bases : 3 0x11590000 0x11720000 0x118B0000
|
||
uses of each : 52, 52, 52 (156/3 exactly)
|
||
perfect repeating 3-cycle : True (not merely 3 distinct)
|
||
chroma planes per luma draw : 2, on 156 of 156 (YUV420)
|
||
```
|
||
|
||
**Guard 1 — a spike, not a smear.** The pre-registration said H_A is a spike at
|
||
1, H_B a spike at 2, and frame-dropping a smear with a tail:
|
||
|
||
```
|
||
run lengths (frames holding one luma base):
|
||
1 frame(s): 156
|
||
```
|
||
|
||
**156 runs, all of length 1, nothing else.** No tail, no smear, no dropped movie
|
||
frames. The bias that would have pushed the answer toward H_B is measurably
|
||
absent rather than argued away.
|
||
|
||
## 🔴 The control was NOT the one I pre-registered, and that is stated plainly
|
||
|
||
The pre-registered control was *"the splashes' established +34/frame alpha step
|
||
must reproduce in the same log."* **It could not be run**: this logger build emits
|
||
vertex-buffer *addresses* (`vb=0x14CD00BC`), not vertex contents, so there is no
|
||
alpha to extract. I did not discover that until the log was in hand.
|
||
|
||
Substituted, and weaker in a way worth naming: the three splash pixel shaders and
|
||
their blend states must reproduce against the committed census from an
|
||
**independent boot**.
|
||
|
||
| | committed census | this capture |
|
||
|---|---|---|
|
||
| sprite shader | `0xE59B2B3DA4AA9008` `0x07010701` | ✅ ×446 |
|
||
| the clear | `0x2E372EA28CC404B7` `0x00010001` | ✅ ×223 |
|
||
| black backdrop | `0x5773DC18083C4C20` `0x07010701` | ✅ ×223 |
|
||
|
||
This validates the log's **structure** — frame delimitation, shader and blend
|
||
fields — which is what the cadence measurement actually uses. It does **not**
|
||
validate alpha extraction, and the cadence measurement does not use alpha. That
|
||
is why the substitution is acceptable here and would not have been for a claim
|
||
about a ramp.
|
||
|
||
## Independent corroboration, from data already committed
|
||
|
||
[`data/attract-frame-match.txt`](data/attract-frame-match.txt) matched captured
|
||
attract frames to timestamps inside `ADV.wmv`. Over its fourteen high-confidence
|
||
`ADV` matches, movie time advances **≈ 5.77 movie-seconds per capture step**
|
||
against a harness that sleeps 5 s plus grab overhead — i.e. the movie plays at
|
||
roughly **real time**. A movie playing at real time, with one present per movie
|
||
frame, is a guest presenting at ≈ 30 fps. Consistent, from a different capture,
|
||
a different instrument, and a measurement taken for a different purpose.
|
||
|
||
## 🔴 What this re-opens: finding 3 still has no cause
|
||
|
||
Units per second was the leading candidate for *"the `PRESS Ⓐ` plate arrives
|
||
late"* and it is now **eliminated**. The play-test's remaining candidates were:
|
||
the clock origin, `rest.t`, and the record layout. One of them is now the most
|
||
likely, and it is **decoded**, not speculative:
|
||
|
||
> **The plate's declared onset is `t = 214`, not `t = 236`.**
|
||
|
||
```
|
||
$ sylpheed-cli screen info --build 2 --geometry $SYLPHEED_DISC/dat/GP_TITLE.pak
|
||
ptbtn00.t32 0: a=0 214: a=0 236: a=255 238: a=255 244: a=0
|
||
```
|
||
|
||
A keyframe is the **start of a ramp** (an established ✅ law), so the plate fades
|
||
in across `214 → 236` — a **22-unit ramp**, which is exactly the `T = 22` the
|
||
oracle independently confirmed by measuring **+23 alpha per presented frame** on
|
||
this element (`255 × 2 / 22 = 23.18`).
|
||
|
||
**At 60 units/s the plate begins appearing at 3.57 s and is full at 3.93 s.** A
|
||
port that shows nothing until `t = 236` is 22 units — **0.367 s** — late at
|
||
onset, and replaces a 22-unit fade with a pop. A human watching judges a fade by
|
||
when it *starts*.
|
||
|
||
⚠️ **I am not claiming that is what the port does.** The play-test reports the
|
||
port raising the plate at `t = 236`; whether that is its onset or its completion
|
||
is the port's to check. What is decoded here is the disc fact and the arithmetic.
|
||
|
||
### 🔴 ANSWERED same day, and this branch is dead too
|
||
|
||
The port checked and reports a frozen sweep of the plate region across the
|
||
declared ramp — `210u → 0.1457`, `216u → 0.1573`, `222u → 0.1727`, `228u →
|
||
0.1900`, `236u → 0.2142`. **A clean monotone rise across `214 → 236`: the port
|
||
fades, it does not pop**, and its `t = 236` is its completion, not its onset.
|
||
|
||
So the onset branch is eliminated as well. Of the four candidates the play-test
|
||
named for finding 3 — the unit→seconds constant, the clock origin, `rest.t`, the
|
||
record layout — **two are now dead**: units/s is measured at 60 here, and the
|
||
plate's ramp is being drawn. **Finding 3 has no surviving named cause.**
|
||
|
||
The remaining two are the **clock origin** (does the port's shared clock start
|
||
when the game's does?) and **`rest.t`**, and the origin is the one worth taking
|
||
first: everything measured so far is a *difference* between two events, and a
|
||
difference is exactly what cannot detect a common offset. `REFUTED.md` already
|
||
records that believing `rest.t` put a port's plate **3.97 s late once**.
|
||
|
||
## Reach
|
||
|
||
⟨capture⟩ over **one** boot, English locale, one machine, and the movie region of
|
||
one log. The ratio is exact and both guards pass, but a second independent boot
|
||
would make it ⟨capture⟩×2 — worth having before anything irreversible rests on
|
||
it, though nothing needs to, because the answer is "keep 60".
|
||
The 30.000 fps ruler is ⟨disc⟩ and generalises.
|
||
|
||
## Refutation attempt, recorded per the adversarial duty
|
||
|
||
**Target:** my predecessor's own H3 note that *"`2 × 60 fps` is 120 units/s, which
|
||
puts the plate at 1.97 s instead of 3.93 s — and 'about two seconds early' is the
|
||
size of what the human reported."* That is a live hypothesis with a plausible
|
||
motive, and it is the one I expected to confirm.
|
||
|
||
**Result: REFUTED.** The guest presents one frame per decoded movie frame, on a
|
||
perfect 3-buffer cycle with no dropped frames. 120 units/s requires two presents
|
||
per movie frame and there are none. The hypothesis was well-formed and wrong, and
|
||
the reason it was attractive — it would have explained the play-test — is exactly
|
||
why it needed a ruler that is not a clock.
|