Files
Sylpheed/docs/re/units-per-second-measured.md
sylph-decoder d67aaaf30b re: the rate is 56.8 units per guest second -- both predictions hold, control passes
The capture reached the title AFTER the harness stopped classifying, so the
plate's ramp was in the log with tick stamps after all.

ptbtn00: alpha 11->231 over 334.4 guest ms = 657.9 alpha/s. With its
independently attested T=22 that is 56.8 units per guest second, inside the
pre-registered 55-65 band. The final step of every ramp is excluded because
it clamps at 255 and reports more elapsed time than it consumed -- including
it drags the plate to 633.0 alpha/s, a 4% error entirely inside the clamp.

Control passes at 1.15%: ptcopyright gives 650.4 alpha/s, and at one shared
clock that makes its own segment T=22.25. Two elements agreeing on a rate
AND independently landing on a round declared length is stronger than either.

Why my earlier 29.9 was wrong, and it is the failure the page predicted: it
used T=15 borrowed from a row that is about A element with a 15-unit fade,
generalised to splash B's quads, which is not what that row says. At 56.8 the
splash elements' implied T is 23-34, none of them 15. And the step quantum
does not rescue 15: splash steps are multiples of 17 = 255/15, which is what
made 15 look confirmed, but at T=28.5 a step of 17 is simply TWO units of
8.9. A quantum fixes T only if you already know the step is one unit.

60 is NOT refuted -- 5.6% away against ~5% quantisation resolution -- so the
port keeps it. But this DOES eliminate the unit constant as a cause of a late
plate: at 56.8 units/s t=236 lands at 4.15 s against the port's 3.93 s, so
the port is fractionally early.
2026-09-01 17:11:19 +00:00

210 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Units per second — the clock is **time-integrated**, and the rate is **not yet a number**
**Status: 🟡 half-answered.** One pre-registered prediction **held**, one
**failed**, and the failure is informative. Instrument: ⟨capture⟩ with
⟨canary-source⟩ for the clock. 2026-09-01.
Against [`units-per-second-preregistration.md`](units-per-second-preregistration.md),
committed before the capture was taken.
---
## What was predicted, and what happened
| # | prediction | outcome |
|---|---|---|
| 1 | the clock is **time-integrated**, Δα correlates with guest frame duration, **r > 0.9** | **🟡 direction held, threshold missed.** r = **0.8396** over 19 rising steps spanning a **12.5× duration range** (16.4 → 204.6 ms). Positive and structured, but not r > 0.9 |
| 2 | the rate is **60 units per guest second**, accept 5565 | **❌ FAILED.** Six elapsed-ratio estimates give a median of **29.9**, range 25.236.6 |
| 3 | 120 and 30 are both excluded | **❌ FAILED in the worst way** — 30 is what came out |
**A failed prediction is the result.** It is written down before the explanation,
because the explanation below is new and untested and the number is not.
## ✅ The part that IS settled: the clock is not frame-counted
Prediction 1's threshold was missed but its *subject* is decided, and by a
cleaner argument than the regression:
**The same animation takes a different number of frames in two captures.**
| element | capture A (2026-09-01, first) | capture B (with tick stamps) |
|---|---|---|
| splash A's logo `Q0` rising steps | `+136, +34` | `+17, +51, +34, +34, +17, +17` |
| splash B's logo trio, labels present | `127…147` (21) | `115…147` (33) |
A fixed per-frame increment cannot do that. The steps are always integer
multiples of **17** (= 255/15, one time unit), so the clock advances in **whole
units**, but *how many* per frame is whatever that frame took.
📌 **This retires "2 units per submitted frame" as a description of the
mechanism.** The [H3 measurement](h3-units-per-frame-measured.md) is not wrong —
three consecutive plate steps really were exactly 23 = 2 units — but 2 was a
property of *that run's frame pacing*, not of the game. Anything the port
computes as `units = 2 × frames` is computing an emulator artefact.
⚠️ **This is my own ✅ row weakened, from `ui-keyframe-time-unit.md` and from my
own page of two hours ago.** Recording it here rather than editing either, and
proposing rather than enacting a change to the register.
## ❌ Why the rate is not a number yet, and it is a `T` problem
```
units per second = (Δα / Δt) × T / 255
```
`Δα/Δt` is measured, cleanly, six ways:
| quad | Δα | guest s | α/s | units/s **if T = 15** |
|---|---|---|---|---|
| splash A logo | 170 | 0.273 | 622.5 | 36.6 |
| splash B logo ×3 | 187 | 0.368 | 508.7 | 29.9 |
| splash B companion ×2 | 51 | 0.119 | 428.2 | 25.2 |
**`T` is the load-bearing term and I have not read it off the disc myself.**
`T = 15` comes from `ui-keyframe-time-unit.md`'s ✅ row, and the way I used it
here is circular: that row's *shape* result (the ramp is linear, `round(255·k/15)`
fits) is independent, but a step of 34 per frame only implies `T = 15` **given**
2 units/frame — which is exactly the thing this page has just retired.
If `T = 30` for these elements the rate is ~60. If `T = 15` it is ~30. **The
factor between the two answers is the same factor as the unknown**, so no amount
of re-measuring alpha settles it.
## What settles it, and why the plate is the right element
**`ptbtn00`.** Its `T = 22` is attested independently of any clock: the four
`(time, pose)` pairs `214/236/238/244` are read the same way by two different
readers — my own `screen info` dump on a different branch and the port's
exporter — differing only in the record association, which is ✅ decoded in
[`ui-keyframe-record-layout.md`](ui-keyframe-record-layout.md). Nothing in that
chain uses a clock.
So: capture the plate's ramp **with the guest tick stamps**, take the elapsed
ratio over it, and `rate = (Δα/Δt) × 22 / 255` is the answer with no circularity.
🔴 **That capture did not complete this iteration.** The run reached 531 s of
attract loop without presenting the title, against 243 s in the previous run —
which is the variable-attract-loop behaviour
[`capture-harness-status.md`](capture-harness-status.md) already documents at up
to 604 s. The instrument is built and verified; what is missing is one run that
gets there.
## The instrument, and its control
The draw logger now stamps every frame boundary with the **guest** timebase —
`Clock::QueryGuestTickCount()` at `guest_tick_frequency()` — so no host wall
clock enters any number above. `emulator.cc:225` sets that frequency to
**50 MHz** and `clock.cc:37` leaves `guest_time_scalar_` at **1.0**
(⟨canary-source⟩).
**Control, run before trusting it:** the stamps span **123.24 guest seconds**
across a capture that had been running ~118 wall seconds at the time of reading.
Guest time tracks real time, as the source says it should. An instrument that
disagreed with its own source here would be dead.
📌 And the guest frame rate is wildly non-uniform — **16.4 ms to 204.6 ms per
frame in one splash**. That is 12.5×, in a stretch a wall-clock instrument would
have averaged into a single meaningless "fps". It is also why prediction 1's
regression is honest but noisy: the steps are quantised to 17 and the residual
structure is real, with the long frames advancing **less** than a constant rate
predicts.
🟡 **That residual is unexplained and is a candidate finding in itself** — a
clamped `dt`, a capped number of logic steps per frame, or a decoupled logic
tick would all produce it. Not tested.
## What the port should do with this today
**Nothing yet.** The 60 units/s constant is neither confirmed nor refuted:
this page's ~30 rests on a `T` I have not verified, and the argument that
retires `2 × frames` does not by itself supply a replacement. Changing it on
the strength of a failed prediction would be worse than leaving it.
## Reach
Two captures, the boot splashes in both, one title capture without stamps. The
"not frame-counted" conclusion rests on a **comparison between captures** and is
as strong as the two captures being of the same animation, which their element
rects and declared ramps make certain. The rate has no reach at all yet.
---
# 🔴 RESOLVED LATER THE SAME ITERATION — the title capture landed, and the rate is **~57 units per guest second**
Everything above was written while the capture was still running. It then reached
the title **after** the harness had stopped classifying, so the plate's ramp is in
the log with tick stamps after all. Kept above rather than rewritten, because the
sequence is the point: the failed prediction was caused by exactly the `T`
circularity the page names, and the fix is the element whose `T` does not need a
clock.
## The measurement
[`data/units-per-second-rate.txt`](data/units-per-second-rate.txt)
```
ptbtn00 (the plate) α 11 → 231 over 334.4 guest ms 657.9 α/s
ptcopyright α 34 → 231 over 302.9 guest ms 650.4 α/s
```
The **last step of each ramp is excluded**: it clamps at 255 and therefore reports
more elapsed time than it consumed. Including it drags the plate from 657.9 to
633.0 α/s — a 4 % error entirely inside the clamp.
**With `ptbtn00`'s independently attested `T = 22`:**
> ## **56.8 units per guest second**
## ✅ Both pre-registered predictions now hold, and the control passes
| # | prediction | outcome |
|---|---|---|
| 1 | time-integrated | ✅ **held** — and by the between-capture argument above, not the regression |
| 2 | **60 units/s, accept 5565** | ✅ **56.8 — inside the band** |
| 3 | 120 and 30 excluded | ✅ **both excluded.** 30 would need `T = 11.6` for the plate; 120 would need `T = 46.5` |
**The control I pre-registered — two independent elements, same screen, same run —
passes at 1.15 %.** `ptcopyright` gives 650.4 α/s against the plate's 657.9. At
one shared clock that makes `ptcopyright`'s own segment **`T = 22.25`**, i.e. the
same 22-unit ramp; two elements agreeing on a rate *and* independently landing on
a round declared length is a stronger result than either alone.
## Why the earlier ~30 was wrong, and it is the failure the page predicted
`units/s = (Δα/Δt) × T / 255`. The splash estimate used **`T = 15`**, borrowed
from `ui-keyframe-time-unit.md`. That row is about *an* element with a declared
15-unit fade; **I generalised it to splash B's quads, which is not what it says.**
At the measured 56.8 units/s those elements' implied lengths are:
| element | measured α/s | implied `T` |
|---|---|---|
| splash A logo | 622.5 | **23.3** |
| splash B logos ×3 | 508.7 | **28.5** |
| splash B companions ×2 | 428.2 | **33.8** |
None is 15. The page above said the answer would move by exactly the factor the
unknown moved by, and it did — 29.9 × (28.5/15) = 56.8.
⚠️ **The step-quantum argument does not rescue `T = 15` either.** Splash steps are
multiples of 17 and `255/15 = 17`, which is what made 15 look confirmed — but at
`T = 28.5` a step of 17 is simply **two** units of 8.9. A quantum fixes `T` only
if you already know the step is one unit, and nothing said it was.
## Resolution and reach
**Classified: measured.** ⟨capture⟩, guest timebase, control passed.
* **`56.8` is not `60`, and `60` is not refuted.** The span is 19 units at ~11.6 α
per unit, so one unit of quantisation is ~5 %; 60 sits 5.6 % away, at the edge
of this measurement's resolution. **The port keeps 60.**
* **It does eliminate the unit constant as the cause of a late plate.** At 56.8
units/s the plate's `t = 236` lands at **4.15 s** after clock zero against the
port's 3.93 s — the port is fractionally *early*, not late. Whatever the human
saw, this is not it.
* 🟡 **Reach is the title.** The splashes are a different `GamePart` and this does
not establish that they tick at the same rate — it establishes that their `T`
is unknown, which is a different statement. Reading `T` off the disc for the
splash elements is the way to close that, and it is static work.
* 🟡 The **structured residual** — long frames advancing less than a constant rate
predicts — is untouched and still unexplained.