re: the animation clock is TIME-INTEGRATED -- one prediction held, one failed

Reported against the pre-registration, failure first.

HELD, and by a cleaner argument than the regression: the clock is NOT
frame-counted. The same animation takes 21 frame labels in one capture and
33 in another, and splash A's logo steps +136,+34 in one and
+17,+51,+34,+34,+17,+17 in the other. A fixed per-frame increment cannot do
that. Steps are always integer multiples of 17 (255/15 = one unit), so the
clock advances in whole units at a rate set by how long the frame took.

That retires '2 units per submitted frame' as a MECHANISM -- including my own
page from two hours ago. The measurement stands; 2 was that run's frame
pacing. units = 2 x frames computes an emulator artefact. Recorded as a
proposal against the register, not enacted, and noted at the head of the H3
page rather than by editing it.

FAILED: predicted 60 units per guest second, accept 55-65. Measured median
29.9 over six elapsed-ratio estimates (25.2-36.6). Prediction 3 said 30 was
excluded and 30 is what came out.

Why the rate is not a number yet: units/s = (d_alpha/dt) * T/255, and T is
the load-bearing term. d_alpha/dt is measured cleanly six ways. T=15 for
these elements comes from a corpus row whose usual derivation is circular
with the thing just retired -- a step of 34/frame implies T=15 only GIVEN 2
units/frame. T=30 would give ~60. No amount of re-measuring alpha settles it.

Settles with ptbtn00, whose T=22 is attested by two independent readers with
no clock anywhere in the chain. That capture did not reach the title in 531 s
of attract loop against 243 s in the previous run -- the variable attract
loop capture-harness-status.md already documents at up to 604 s. Instrument
built and control-passed; one run is missing.

Instrument: guest timebase, 50 MHz per emulator.cc:225, scalar 1.0 per
clock.cc:37, so no host wall clock enters any number. Control: 123.24 guest
seconds across a capture running ~118 wall seconds.

Told the port to change NOTHING today: ~30 rests on an unverified T, and
retiring 2 x frames does not by itself supply a replacement.
This commit is contained in:
sylph-decoder
2026-09-01 17:04:23 +00:00
parent 60b05bcd90
commit 781a70af9a
3 changed files with 149 additions and 0 deletions

View File

@@ -137,6 +137,7 @@ files, which is how the same ground got covered twice.
| [`structures/stage-mission-tables.md`](structures/stage-mission-tables.md) | The stage table set — phases, routes, sub-objectives and AI parameters | ✅ the table set and how the stage record reaches it, validated across; **`AIParams` disc-wide: 23 objects, one shared 34-profile roster (782 records), loader `sub_8233C368`; `Type`→field-count holds except the two `_Test` templates** |
| [`structures/texture-color-k8888.md`](structures/texture-color-k8888.md) | Texture colour interpretation — `k_8_8_8_8` (32bpp UI/HUD textures) | — |
| [`ui-keyframe-time-unit.md`](ui-keyframe-time-unit.md) | What a keyframe time is worth, and what shape the ramp has | ✅ CONFIRMED from the running game's own draw stream — the ramp is **linear** (a declared 15-unit fade lands on `round(255·k/15)` for all seven samples) and the animation clock advances **2 time units per submitted frame**. 🟡 the seconds conversion (`1 unit = 1/60 s`) rests on a measured 27.6 present-frames/second |
| [`units-per-second-measured.md`](units-per-second-measured.md) + [pre-reg](units-per-second-preregistration.md) | Is the animation clock frame-counted or time-integrated, and at what rate | 🟡 **half-answered, 2026-09-01, one prediction HELD and one FAILED.****The clock is NOT frame-counted**: the same animation takes 21 labels in one capture and 33 in another, and splash A's logo steps `+136,+34` in one and `+17,+51,+34,+34,+17,+17` in the other. Steps are always integer multiples of **17** (255/15 = one unit), so the clock advances in whole units at a rate set by frame duration. 📌 **This retires "2 units per submitted frame" as a MECHANISM** — 2 was that run's frame pacing, so `units = 2 × frames` computes an emulator artefact. ❌ **The rate FAILED its prediction**: predicted 60 units/guest-second, measured **median 29.9** (25.236.6) — but that rests on `T=15`, which I have not read off the disc and whose usual derivation is circular with the thing just retired. **Settles with the plate (`T=22`, attested by two readers with no clock in the chain) captured with tick stamps; that run did not reach the title in 531 s.** Instrument: guest timebase (50 MHz, scalar 1.0), control passed — 123.24 guest s over ~118 wall s. |
| [`h3-units-per-frame-measured.md`](h3-units-per-frame-measured.md) | How many keyframe units elapse per guest frame, and which declared time the title's settle anchor is | ✅ **measured against a pre-registration, 2026-09-01. 2 units per guest frame**, on `ptbtn00`'s own declared ramp (`t=214→236`, T=22): three consecutive gap-free steps of **exactly 23** = 255×2/22, ramp span 10 labels against a predicted 11 (±1). The Port's inferred **5 is excluded by >2×**. 🔴 Why 5 appeared: **an alpha step is not a clock rate**`Δα/frame = 255·(units/frame)/T`, so splash B's 34-with-T=15 and the plate's 23-with-T=22 are ONE clock. Also: the **title settle anchor is t≈160**, not t=118 — it is `ptcopyright`, the last build-in element and the only glyph one, full at label 5350 → t≈168176. 🔴 **units/SECOND is NOT settled** and two of my own captures disagree ~2.9× on it; the gap is in frames→seconds, not units→frames. |
| [`h3-units-per-frame-preregistration.md`](h3-units-per-frame-preregistration.md) | The prediction, committed before the capture was read | ✅ kept as the control on the row above |
| [`input-pad-read-path.md`](input-pad-read-path.md) | What the game asks the console for, on the pad | ✅ **decoded from the image, 2026-09-01.** `sub_82457038` is the only function that reads controller *data*, and it compares **every** field of `XINPUT_GAMEPAD``wButtons` (full 16 bits), both triggers, all four stick axes — against the previous state. **14/14** loads verified byte-for-byte against `/image/sylpheed.pe`, database used as an index only. **Two input paths**: the polled state and an 8-byte-record `XamInputGetKeystrokeEx` ring. ⚠️ This is the **superset the game can see, not the per-screen set** — reading a field is not acting on it. ❔ Which bits each screen tests is open; footholds are the image's own `C_PAD_DECODER` / `C_PAD_RINGBUF` trace strings (`sub_8220B610` / `sub_821A6470`). |

View File

@@ -7,6 +7,26 @@ Canary, per-draw. 2026-09-01.
Answers the Port's `BLOCKED.md` H3 (`auto/port-p6-audio` at `6eccfa8`), both
halves. Neither answer rests on a renderer of ours.
> ## 🟡 WEAKENED THE SAME DAY, by my own next experiment — read this first
>
> **"2 units per guest frame" is a correct measurement and a wrong mechanism.**
> [`units-per-second-measured.md`](units-per-second-measured.md) shows the clock
> is **time-integrated, not frame-counted**: the same splash animation occupies
> **21 labels in one capture and 33 in another**, and splash A's logo steps
> `+136, +34` in one and `+17, +51, +34, +34, +17, +17` in the other. The three
> consecutive plate steps of exactly 23 below are real; **2** was a property of
> that run's frame pacing.
>
> **What still stands unchanged:** everything on this page that is a *ratio* or a
> *count* — the T-vs-step arithmetic that explains the Port's factor of 2.7, and
> the settle anchor at t≈160, which is derived from label counts *calibrated on
> the plate's own ramp within the same run* and so does not depend on the rate
> being constant between runs.
>
> **What must not be used:** `units = 2 × frames` as a conversion. It computes an
> emulator artefact.
---
## The prediction, and what happened

View File

@@ -0,0 +1,128 @@
# 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.