Files
Sylpheed/docs/re/units-per-second-preregistration.md
sylph-decoder 5436ba2039 re: the game blends in the ENCODED space -- no gamma render target, anywhere
The Port asks whether the blend is evaluated on sRGB-encoded values or
linearised and re-encoded. It is answered by one register field I already
log, and the answer is the same on every draw of two full captures.

RB_COLOR_INFO.color_format is k_8_8_8_8 (0) on 2402/2402 splash draws and
33779/33791 draws of the boot-through-title capture; the other 12 are
k_32_FLOAT and k_16_16_FLOAT and are not colour passes. k_8_8_8_8_GAMMA (1)
appears ZERO times, and color_exp_bias is 0 everywhere so nothing stands in
for a gamma either.

The mechanism is Canary's own source, not our inference: k_8_8_8_8_GAMMA is
the ONLY colour format around which a piecewise-linear gamma<->linear
conversion is applied (spirv_shader_translator.h:510 PWLGammaToLinear /
LinearToPWLGamma, render_target_cache.h:720, dxbc_shader_translator_om.cc).
With k_8_8_8_8 there is none, so the blender operates on the stored values
as they are.

So a renderer that linearises before blending and re-encodes after is doing
a different operation -- and the difference is gamma-shaped and exactly zero
on unblended pixels, which is the divergence signature the port reports.

Also renamed my units-per-second pre-registration off the 'h4' prefix: the
Port's BLOCKED.md numbers this blend-space question H4 and two different H4s
in one corpus is how a citation goes wrong.
2026-09-01 17:01:11 +00:00

81 lines
4.2 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: is the animation clock frame-counted or time-integrated?
**Pre-registration. Written 2026-09-01 BEFORE the capture was taken**, per
[`../agents/TEMPORAL-VERIFICATION.md`](../agents/TEMPORAL-VERIFICATION.md).
Committed first so the prediction cannot be edited afterwards.
## The question, and why the last answer did not close it
[`h3-units-per-frame-measured.md`](h3-units-per-frame-measured.md) pinned **2
declared units per guest frame**. The port needs **units per second**, and
`units/s = (units/frame) × (frames/s)`. That is only a valid decomposition if the
game's animation clock counts **frames**. If it integrates **elapsed time**, then
units/second is the invariant, units/frame is an artefact of whatever rate the
host managed, and multiplying by an assumed 30 fps is meaningless.
There is already evidence it is time-integrated, and it is the anomaly the last
page had to quote as a caveat: across the empty label 5376 the plate's alpha
moved **+82** where three adjacent labels each moved **+23**. `82 / 23 = 3.57`
**not an integer number of ticks**. A fixed per-frame increment cannot produce a
fractional multiple of itself. That is suggestive and it is one observation.
## The instrument, and why it is not a wall clock
The draw logger now stamps every frame boundary with
`Clock::QueryGuestTickCount()` and `Clock::guest_tick_frequency()` — the timebase
the **guest** reads, 50 MHz (`emulator.cc:225`), with `guest_time_scalar_ = 1.0`
(`clock.cc:37`, unchanged by any flag this harness passes). A duration computed
from those is the duration **the game experienced**, which is the only one its own
integrator could have used. No host wall clock enters the calculation.
⟨canary-source⟩ for the scalar and the frequency; ⟨capture⟩ for everything
measured with them.
## The discriminator — within one run, no second capture needed
For every consecutive pair of frame labels on `ptbtn00`'s ramp, compute the
label's **guest duration** and the **alpha step** across it. Then:
| if the clock is | Δα vs guest duration | what it means |
|---|---|---|
| **frame-counted** | **flat** — Δα ≈ 23 whatever the frame took | units/frame is the invariant; the port multiplies by an assumed fps |
| **time-integrated** | **proportional, through the origin** | units/second is the invariant; **units/frame is not a property of the game at all** |
The slope of that line, if it is one, is the answer: `slope × 22 / 255` =
**units per guest second**.
Frame durations under this emulator vary by a factor of several — the previous
capture's labels ran from nominal to 3.5× nominal — so the regressor has real
dynamic range and this is not a null test.
## The predictions, stated before the run
1. **The clock is time-integrated.** Δα correlates with guest frame duration,
r > 0.9.
2. **The rate is 60 units per guest second.** I accept **5565**. This is *not*
independent: it is what the previous capture's run-average implies (27.2
labels/s × 2.02.25 units/label ≈ 5461), so it is a consistency prediction,
not a blind one, and I say so. What is new is that this measurement is
per-label rather than a run average, in guest time rather than host time, and
carries a shape test the average cannot.
3. **120 units/s and 30 units/s are both excluded** if 1 and 2 hold.
**If prediction 1 fails** — Δα flat against duration — then the clock *is*
frame-counted, `units/s` genuinely depends on the console's frame rate, and the
+82 anomaly needs another explanation. That outcome is reportable and I will
report it.
## The control that removes the test's own subject
The plate's ramp is short. The same regression is run **independently** on
`ptcopyright`'s ramp in the same capture — a different element, a different
declared segment, a different part of the screen. Two elements must give the
**same** units/second under the time-integrated model and need not under any
other. Disagreement between them fails the test regardless of either slope.
## What this still will not answer
The **console's** frame rate. Even a confirmed 60 units/s says nothing about
whether the retail game ran at 30 or 60 fps; it says the port should not need to
know, because it should drive its clock in seconds and not in frames.