# ✅ 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.