# 🔴 WITHDRAWN — "the guest presents at 30 fps, so 60 units/s" **Status: ❌ withdrawn by its own author, 2026-09-01, the same day it was published.** The measurement was real; the **inference from it was not established**, and the flaw is one I named in my own pre-registration and then failed to apply when the data came back clean. Supersedes the verdict in [`guest-frame-rate-measured.md`](guest-frame-rate-measured.md). The data in that page stands; its conclusion does not. --- ## What I claimed That counting presented frames per decoded `ADV.wmv` movie frame gives `guest_fps / 30`, that it measured **1.0000**, and therefore that the guest presents at 30 fps and the UI clock runs at **60 units/s**. I told the port "keep your 60, change nothing." ## Why it does not hold My own pre-registration listed three ways the instrument could lie and guarded two. The third was: > *"**The movie is not playing at its declared rate** because the guest is > frame-locked to its own presentation instead of to the movie clock. This is the > assumption the whole method rests on and it is **not** guarded."* **That is the one that occurred, and a perfect 1.0000 is exactly what it produces.** A guest that advances the movie's buffer once per present — whether or not a new frame was decoded — yields run-length 1 at *any* presentation rate. So the cleanliness I read as confirmation is equally the signature of the failure mode, and the measurement cannot separate: * a 30 fps guest decoding one movie frame per present, from * a 60 fps guest rotating a triple buffer once per present. 🔴 **The methodological error is the part worth keeping.** I wrote the guard down, saw a result so clean it had no tail at all, and treated the cleanness as strength. **A clean result on an instrument whose key assumption is unguarded is not confirmation — the cleanness may be the failure mode's own signature.** The guards I *did* build (buffer-cycle shape, run-length distribution) both tested the reading of the buffer, and neither tested whether a buffer change means a decode. ## The evidence that surfaced it The draw log carries a per-frame `gtick`/`gfreq` marker I had not noticed. It is **not** guest-intended time — Canary's `Clock::QueryGuestTickCount()` is `host_tick_count * guest_tick_ratio` with the scalar at 1.0, i.e. host time rescaled — so it is a wall clock and cannot be read as a rate. But its **shape** has no phase in it. Xenia locks vblank to 60 Hz when vsync is on and `framerate_limit` is 0 (`graphics_system.cc`: *"If VSYNC is enabled, but frames are not limited, lock framerate at default value of 60"*). So: ``` interval between presents, in units of one 60 Hz vblank 1 vblank ( 16.67 ms) : 426 71.7% 2 vblanks( 33.33 ms) : 146 24.6% 3+ : 22 3.7% ``` [`data/present-interval-vs-vblank.txt`](data/present-interval-vs-vblank.txt) **A guest hard-locked to 30 fps presents every SECOND vblank and would put the mass at 2. It is at 1.** The tail at 2+ is dropped frames, which is the only direction a slow emulator can push: it cannot make an interval *shorter* than the guest asked for. ## 🔴 So what is the answer? OPEN — and I am not replacing one over-claim with another The vblank cadence favours **60 fps ⇒ 120 units/s**, which would put the plate at **1.97 s** and make the port ~2 s late — matching the play-test. But it does not settle it, because a **third** measurement disagrees with both: | route | says | |---|---| | movie cadence (withdrawn above) | 60 units/s | | present interval vs vblank (this page) | ~120 units/s | | `title-plate-delay-measured.md` — 120 declared units in **2.13 s**, twice, agreeing to 6 ms | **~56 units/s** | The third is a wall-clock reading, but two runs agreeing to 6 ms is not nothing, and 56 is a factor of **2.13** from 120. **Two of these three must be wrong** and I do not know which. Publishing 120 now would repeat exactly the mistake this page withdraws. ## The experiment that would settle it Distinguish "the buffer rotated" from "a new frame was decoded": **hash the movie luma plane's contents per present**, not its base address. Identical content on consecutive presents ⇒ rotation without decode ⇒ 60 fps guest. Changing content every present ⇒ genuine 30 fps decode ⇒ the movie ruler was valid after all. The draw logger dumps texture *bases*, not contents, so this needs the logger extended — `/canary` is read-write and the change is small. Second, independent, and cheaper: the **UI clock's own advance per present** is already measured at 2 units. What is unmeasured is whether the *animation* ticks once per present or once per two. The splash alpha data needed for that is in the `vb=` vertex dump this same log carries and which I have not yet parsed. ## What the port should do **Nothing yet, and I have told them so.** Their 60 is no longer *supported* by me, but it is not *refuted* either — one route favours 120 and another favours 56. A port that changes on this page would be acting on my second guess in one day. ## Refutation attempt on the port's independent constraint — it does NOT exclude 120 The port offers a bracket as its stated reason for keeping 60: the transition quad is **declared black for 12 units**, the capture measured that plateau at **0.14–0.30 s**, so `12 / 0.30 … 12 / 0.14` = **40–86 units/s** — 60 inside, 120 outside. They call it frame-free and note it survives everything retired here. **Frame-free it is. Independent it is not, and the exclusion does not hold. Two reasons, either one sufficient.** ### 1 — the low end sits ON the instrument's floor `title-plate-delay-measured.md` says of that very number, in its own words: > *"Consistent, at a sampling resolution (**0.125 s**) that cannot do better."* A 0.125 s sampler cannot report an event shorter than about one sample. **0.14 s is one sample.** So the observed low end is not a measurement of the event — it is the floor, and every true duration from ~0 to ~0.14 s produces it. At 120 units/s, 12 units is **0.100 s**. Sampled at 0.125 s that is observed as one sample and reported as ≈0.14 s — **exactly the low end that was read as excluding it.** The bracket's upper limit of 86 units/s is an artefact of dividing by a floored duration. ### 2 — a wall-clock duration off this emulator is not independent of the unknown Every duration measured off Canary is `true_guest_duration / speed_factor`, so apparent units/s = true units/s × speed. **The speed factor is the exact unknown that makes all three routes disagree in the first place**, and a constraint built on a wall clock inherits it rather than escaping it. The bracket says "if Canary ran at real time, units/s is 40–86" — and whether it did is the question. ### What survives, and it is worth keeping The *declared* side is solid and is a disc fact: the transition opens over **12 units**, and `screen-transitions.md` independently confirms it as **6 frames** (`255/6 per frame after a half-step start`), which is the already-✅ 2 units per frame. **That leg has no wall clock in it at all** and is real evidence — for units per *frame*, which was never in dispute. It says nothing about units per second. ⚠️ **So the port is keeping 60 for a reason that does not support it.** I have told them so. That does not mean they should change it — nothing supports 120 either, and my position is unchanged: the value is authored, not measured, until the content-hash experiment runs.