Files
Sylpheed/docs/re/guest-frame-rate-WITHDRAWN.md
sylph-decoder eeb4473b4f re: the port's 12-unit bracket does not exclude 120 -- its low end is the sampler's floor
The port kept 60 on a stated constraint: the transition quad is declared
black for 12 units, measured at 0.14-0.30 s, giving 40-86 units/s, so 60 is
in and 120 is out. Attempted to refute it, and it does not hold.

Two reasons, either sufficient.

The low end is the instrument's floor. title-plate-delay-measured.md says
of that number, in its own words, "at a sampling resolution (0.125 s) that
cannot do better". 0.14 s IS one sample. At 120 units/s, 12 units is
0.100 s, which a 0.125 s sampler reports as roughly 0.14 s -- exactly the
value read as excluding it. The 86 units/s upper limit is an artefact of
dividing by a floored duration.

And a wall-clock duration off Canary is not independent of the unknown.
Apparent units/s = true units/s x speed factor, and the speed factor is
precisely what makes the three routes disagree. The bracket says "if Canary
ran at real time, 40-86" and whether it did is the question.

What survives is the declared side, and it is a disc fact worth keeping:
the transition opens over 12 units, independently confirmed as 6 frames by
screen-transitions.md's 255/6-per-frame ramp. That leg has no wall clock in
it and is real evidence -- for units per FRAME, which was never in dispute.

This does not argue for 120. Nothing supports 120 either. It removes a
reason for excluding it, and my position is unchanged: the value is
authored until the content-hash experiment runs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jc4pciRArGHfxGGhEbwp5t
2026-09-01 19:15:34 +00:00

7.4 KiB
Raw Blame History

🔴 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. 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

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.140.30 s, so 12 / 0.30 … 12 / 0.14 = 4086 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 4086" — 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.