Two results, one of which corrects me. 1. The R1-re-opened 'declared keyframe timeline reproduces the captured splash' resolves in FAVOUR of the declared timeline. Instrument is a disc keyframe table against the guest's vertex stream -- no renderer in the chain, which is what the 'our-reader' tag demanded. Calibration-free test: is each captured alpha an exact member of the declared piecewise-linear value set at integer t? 39/50 exact under truncation, 30/50 under rounding, and the worst error under either rule is ONE alpha level in 255. All 11 non-exact samples are low by exactly 1 and all are on falling segments. The old refutation rested on 'still at a=255 nine frames after its declared a=32' -- under the fixed layout a=32 is at t=206, four units from the end of a 210-unit timeline. That was the off-by-one association. 2. The unit->seconds rate is PER-GAMEPART, which corrects what I told the port an hour ago. Title ~57 units/guest-second; splash ~35-40. The splash figure is confirmed two ways that share no algebra: a 15-unit ramp (T read off the disc, not borrowed) and a 160-unit HOLD, which is a declared duration with no T, no alpha slope and no interpolation in it. And my 'the borrowed T=15 does not apply' was wrong in the other direction: the disc says T=15 plainly. What failed was the premise of a single global rate, not the T. Noted at the foot of that page rather than by editing it. The trap worth recording: all four elements give 650-679 alpha/s, agreeing to 2%, which reads exactly like one clock. It is a coincidence -- T differs 22 vs 15 and the rates differ 57 vs 37, and the ratios nearly cancel. A quantity that looks constant across screens is not evidence of one clock when the thing that would vary is inside it. The hold breaks the tie. For the port: a single keyframe_units_per_second cannot be right, and a splash played at 60 runs 1.5-1.7x too fast -- shorter, sharper fades than the game's, which is the direction the play-test reported.
238 lines
12 KiB
Markdown
238 lines
12 KiB
Markdown
# Units per second — the clock is **time-integrated**, and the rate is **56.8 units per guest second**
|
||
|
||
**Status: ✅ measured.** Both pre-registered predictions hold and the control
|
||
passes at 1.15 %. ⚠️ **Read this page in order.** The first half was written while
|
||
the capture was still running and reports the rate prediction as FAILED at ~30;
|
||
the capture then landed and the second half resolves it. The failure and its
|
||
cause are kept because the cause — a borrowed `T` — is the lesson. 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 55–65 | **❌ FAILED.** Six elapsed-ratio estimates give a median of **29.9**, range 25.2–36.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 55–65** | ✅ **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.
|
||
|
||
---
|
||
|
||
# 🔴 CORRECTED the next iteration — the rate is **per-GamePart**, and this page's `T` reasoning was backwards
|
||
|
||
[`splash-declared-vs-captured.md`](splash-declared-vs-captured.md) reads the
|
||
splash's declared `T` **off the disc** instead of inferring it:
|
||
|
||
* `palogo_gamearts` ramps `t=15 → t=30`. **`T = 15`, plainly.** This page's
|
||
*"the splash elements' implied `T` is 23–34, none of them 15"* is **wrong** —
|
||
it was derived by assuming one global rate, which is the premise that failed,
|
||
not the `T`.
|
||
* Measured on the splash: **~35–40 units/guest-second**, against the title's
|
||
56.8. Confirmed two ways that share no algebra — a 15-unit ramp *and* a
|
||
**160-unit hold**, which contains no `T` at all.
|
||
|
||
**So `56.8` is the title's rate, not the game's.** Everything on this page about
|
||
*method* stands — the clock is time-integrated, the clamped final step must be
|
||
dropped, the guest timebase is the right instrument. The **number** has a
|
||
narrower reach than this page claims.
|
||
|
||
⚠️ And the near-equality of α/s across all four elements (650–679, ±2 %) is a
|
||
**coincidence** that reads exactly like one clock: `T` differs 22 vs 15 and the
|
||
rates differ 57 vs 37, and the two ratios nearly cancel.
|
||
|