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
8.7 KiB
✅ 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,
committed before the capture was taken.
Data: 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 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, nott = 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.