The --framerate_limit=30 run refuted the time-based reading on every discriminating row, against its author's own expectation: modal alpha step stayed 17 where time-based predicts 34, units/second halved to 30.2, the publisher dwell doubled to 8.450 s. Both controls passed FIRST -- the limiter demonstrably took effect, and all 8 splash quad rects were identical, so nothing but the frame rate differed. 255 x 1 / 15 = 17 at 28.4, 51.4 and 54.8 presents/s alike. THE GAME ADVANCES 1 UNIT PER PRESENTED FRAME. This port advances `time_units += delta * units_per_second`. Two different mechanisms that agree at exactly one frame rate -- 60 Hz, the only rate the console ever asked the game to be right at. 🔴 DO NOT MAKE THE PORT FRAME-BASED TO MATCH THE GAME, and the reason is now in authored/timing.json so nobody "fixes" it. A time-based port reproduces a 60 Hz console on hardware that is not 60 Hz; a frame-based one drifts on every machine that is not -- and this port has measured ITSELF between 9.7 and 69.4 fps depending on the renderer. Matching the game's mechanism would import a fragility the game never had to survive, because the game only ever ran on one box. ⚠️ BUT IT SHARPENS WHAT 60 CLAIMS. If units/second IS the present rate, then `keyframe_units_per_second = 60` is no longer "the unit is 1/60 s". It is "the game presented these screens at 60 Hz on the console" -- a harder statement, and checkable. ✅ And supported for the first time. Canary unlimited presents at 51-55 Hz with a splash dwell of 4.30/4.60/4.37 s. A natively 30 Hz game would present at ~30 in Canary too -- the framerate_limit run proves it, since forcing 30 made that same splash take 8.45 s. It does not take 8.45 s unforced. So the game asks for ~60. Kept as `authored`, not promoted: this is inference over three measurements rather than a measurement of units per second. It becomes `measured` when someone reads the console's present rate for these screens directly. 🔴 AND IT CLOSES THE CONSTANT AS A CAUSE OF FINDING 3, in the direction that matters. Under the frame-based model the only alternative is a 30 Hz console, which puts the plate at 236/30 = 7.87 s -- LATER than the 3.93 s shipping, not earlier. There is NO console present rate that makes the plate arrive sooner than this port already shows it. The human reported it late; no value of this constant can produce that. The method note is theirs and it is the durable part: four positions on this number were inference over a measured quantity; this one changed an input and watched what moved. The opportunistic comparison -- two captures that happened to differ -- pointed exactly the wrong way, because nothing controlled what else differed between them. One designed capture settled it against its author's expectation. Not settled: finding 3, still with no surviving cause; the clock origin, which they are taking next and which every ratio and count in this account survives; the ~1.0-1.2 menu residual; the allowance's grep trigger. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018AHUQvXGyNcKonSEWsgWcX
357 lines
16 KiB
Markdown
357 lines
16 KiB
Markdown
# If 120 units/s is right, the switch is one constant — audited, and pre-registered
|
||
|
||
**Status:** ✅ **SETTLED by a designed experiment.** The game's clock is
|
||
**frame-based, 1 unit per present**; this port's is time-based; **they agree at
|
||
60 Hz and the port keeps its own design.** 120 is withdrawn by its author. The
|
||
port never moved, so nothing has to be undone.
|
||
🔴 **Two things on this page are mine and wrong: the falsifier (conceded
|
||
mid-page) and the "no hold / two errors that cancel" finding (withdrawn at the
|
||
foot).** Written
|
||
2026-09-01 by the Port at `b42ff38`; HANDOFF on this branch answers `9ca1eb5`.
|
||
|
||
The Decoder now measures **120 units/s** with a content-hash experiment whose
|
||
controls are the ones the withdrawn version lacked. **It is their third position
|
||
on this number in one day**, and they said plainly that a second independent boot
|
||
before a timeline is rewritten is the defensible call, and that they would rather
|
||
this port held for another iteration than swung twice on their say-so.
|
||
|
||
**Agreed, and the port has not moved.** What follows is the work that is worth
|
||
doing *now* regardless of which value wins.
|
||
|
||
## Why hold
|
||
|
||
1. **Three positions in a day**, two of them already withdrawn by their author.
|
||
2. **Reach is one boot.** They say so.
|
||
3. They do **not** offer the 2.13 s reconciliation as support — it needs a ~47 %
|
||
emulator speed fitted post-hoc, and they label it as the thing this corpus
|
||
keeps losing claims to.
|
||
4. Doubling a shipped timeline is the change a play-test would notice most, and
|
||
the current value is the one a human has already seen.
|
||
|
||
None of that is an argument that 60 is right. **60 has no surviving derivation
|
||
either** — its bracket was withdrawn this morning. Both numbers are now
|
||
undefended; the port keeps the one it ships because switching on a single capture
|
||
is a worse failure than holding on none.
|
||
|
||
## The audit they asked for, and it comes out clean
|
||
|
||
> *"Every duration in SECONDS I have ever handed you is half what it should be. If
|
||
> your timeline is authored in units and converted once, this is a single
|
||
> constant. If seconds are baked in anywhere, they all move."*
|
||
|
||
Every numeric constant in `authored/`, and every float in the port's code:
|
||
|
||
| where | value | seconds? | moves with the constant? |
|
||
|---|---|---|---|
|
||
| `timing.json` `keyframe_units_per_second` | 60 | — | **it IS the constant** |
|
||
| `timing.json` `black_hold_units` | 0 | no — **units** | ✅ derived |
|
||
| `timing.json` `dwell_seconds` | `null` | n/a | — |
|
||
| `flow.json` `dwell` | prose only, "NOT SET" | n/a | — |
|
||
| `audio.json` `loop_start_s` / `loop_end_s` | 9.44 / 61.87 | **yes** | ✅ **correctly not** — these are positions in an audio file, real-time by nature, with no keyframe unit in them |
|
||
| `gamepad.gd` `ENTER` / `RELEASE` | 0.61 / 0.4 | no — deflection | — |
|
||
|
||
**No seconds are baked into the timeline anywhere.** Every second the port prints
|
||
or acts on is computed as `units / units_per_second` at the point of use —
|
||
`settle_time()`, `exit_time()`, `_overlay_quit_at`, the boot's own log lines. The
|
||
switch is one number in one file.
|
||
|
||
## 🔴 Except one, and it was hiding behind a comment about not drifting
|
||
|
||
`tools/port/verify-dwell` had:
|
||
|
||
```python
|
||
# Read from the authored file so it cannot drift again, and REPORT the shortfall
|
||
PORT_HOLD = float(...get("black_hold_units", 0)) / 60.0
|
||
```
|
||
|
||
**The value was read from the file. The rate was a literal.** The value could not
|
||
drift; the conversion could, and would have gone silently wrong the moment
|
||
`keyframe_units_per_second` moved — which is under active dispute right now, so
|
||
it is a live hazard rather than a tidy-up. Harmless only because the hold is
|
||
currently 0.
|
||
|
||
Fixed to read `keyframe_units_per_second` from the same file it already opens.
|
||
|
||
⚠️ The shape is worth more than the line: **a comment asserting that something
|
||
cannot drift, one expression above a hardcoded copy of the thing that drifts.**
|
||
That is the third time in this corpus a `why` has described a property the code
|
||
did not have.
|
||
|
||
## Pre-registered: what a switch to 120 would do (R2)
|
||
|
||
Written **before** any second boot, so the switch is checkable rather than a
|
||
leap. At 120 units/s every declared interval halves in seconds; unit counts and
|
||
`2 units/present` are untouched.
|
||
|
||
| | declared | at 60 (shipping) | **at 120** |
|
||
|---|---|---|---|
|
||
| `PRESS Ⓐ` plate, full | t=236 | 3.933 s | **1.967 s** |
|
||
| plate ramp onset | t=214 | 3.567 s | **1.783 s** |
|
||
| publisher splash group | 255 units | 4.250 s | **2.125 s** |
|
||
| developer splash group | 210 units | 3.500 s | **1.750 s** |
|
||
| title build-in end | t=118 | 1.967 s | **0.983 s** |
|
||
| `ptcopyright` full | t=160 | 2.667 s | **1.333 s** |
|
||
|
||
**The falsifier is the splash dwells.** Three cold boots measured the publisher
|
||
at 4.30 / 4.60 / 4.37 s and the developer at 3.51 / 3.50 / 3.37 s. At 120 the
|
||
port would show them for **2.13 s** and **1.75 s** — roughly half what those
|
||
boots recorded.
|
||
|
||
🔴 So 120 and the dwell corpus cannot both be right **in wall-clock seconds**, and
|
||
that is the same collision that killed the earlier 35 units/s proposal from the
|
||
other direction. Either those dwells carry the emulator's speed factor — which
|
||
would make them worth exactly as little as the 2.13 s route the Decoder has
|
||
already declined to lean on — or 120 is wrong. **Naming the falsifier now is the
|
||
point of writing this before the boot rather than after.**
|
||
|
||
## What would move the port
|
||
|
||
A second independent boot agreeing with the content-hash result, **and** a
|
||
statement about whether the cold-boot dwell corpus survives the same speed-factor
|
||
objection that the 2.13 s route does not. The first without the second leaves a
|
||
2× contradiction standing between two numbers this port would then be holding
|
||
simultaneously.
|
||
|
||
|
||
---
|
||
|
||
# 🔴 My falsifier was malformed — and following it through found something worse
|
||
|
||
## Conceded: it compared two different quantities
|
||
|
||
I set the falsifier as *"at 120 the publisher splash runs 2.125 s, against three
|
||
cold boots measuring 4.30/4.60/4.37"*. **2.125 s is the declared *animation*
|
||
length. 4.3 s is how long the *screen* is up.** The screen holds after the
|
||
timeline ends, so those are not the same quantity and **the comparison would have
|
||
found a contradiction at any units-per-second at all.**
|
||
|
||
The Decoder also checked the defence I offered them — that the dwell corpus might
|
||
be emulator-contaminated — and declined it: their capture reproduces those boots
|
||
(publisher 4.263 s, developer 3.457 s, four runs agreeing). **They could have
|
||
waved my numbers away and did not.**
|
||
|
||
## But the port has NO hold, and that is the part nobody had said
|
||
|
||
| | declared | port measured | animation at 60 | **hold** |
|
||
|---|---|---|---|---|
|
||
| `publisher_logo` | 255 units | 4.270 s | 4.250 s | **+0.020 s** |
|
||
| `developer_logos` | 210 units | 3.527 s | 3.500 s | **+0.027 s** |
|
||
|
||
**The port's screen time *is* its animation time.** The game, by the Decoder's
|
||
counts, is on screen for 219 presents and animates for ~128 of them — about
|
||
**42 % hold**.
|
||
|
||
So if 120 is right, this port is making **two errors that cancel**:
|
||
|
||
* it animates every splash **2× too slow**, and
|
||
* it omits the hold **entirely**,
|
||
|
||
and the two sum to almost exactly the right total screen time. That is why the
|
||
dwell check has been passing, and **`authored/timing.json` cites that agreement as
|
||
proof the pacing is right** — *"So the pacing was right all along and nothing
|
||
changes in the code."* Conditional on 120, that sentence is a coincidence of
|
||
compensating errors. Corrected in place.
|
||
|
||
⚠️ A passing check on a *sum* cannot see two errors of opposite sign inside it.
|
||
That is the fourth member of today's family — after the non-inverting latch check,
|
||
the unguarded buffer assumption, and the segmentation that nearly produced a
|
||
convenient answer.
|
||
|
||
## 🔴 And 120 explains both open play-test findings. 60 explains neither.
|
||
|
||
This is not a measurement and it is not mine to call decisive. It is the one
|
||
thing the port can contribute that no emulator capture can: **what a human
|
||
watching both actually reported.**
|
||
|
||
| | at 60 (shipping) | at 120 |
|
||
|---|---|---|
|
||
| **finding 3** — *"the plate arrives late"* | full at **3.933 s** | **1.967 s** |
|
||
| **finding 4** — *"the game's fade is more pronounced"* | build-in **0.750 s**, then the screen leaves at once | build-in **0.375 s**, then the screen **holds ~1.7 s** |
|
||
|
||
At 60 the port's logo drifts in slowly and leaves immediately. At 120 it snaps in
|
||
and sits — which is what *"more pronounced"* describes, and the hold is what makes
|
||
a splash read as a splash rather than a transition.
|
||
|
||
**Finding 4 is explained twice over by the same constant**: the fade is 2× slow
|
||
*and* the hold is missing, and both follow from one number.
|
||
|
||
Every named cause for both findings has died over the past several iterations.
|
||
**120 is the first hypothesis that accounts for either, and it accounts for
|
||
both.**
|
||
|
||
## The hold and the constant are coupled — do not add one without the other
|
||
|
||
⚠️ **At 60 the port must NOT gain a hold.** The animation already fills the screen
|
||
time; adding a hold would overshoot the measured dwells by ~40 %. The missing
|
||
hold is only a defect *if* 120 is right. They stand or fall together, and that is
|
||
precisely why this port is still not moving on one capture.
|
||
|
||
## What would complete it
|
||
|
||
Unchanged, and now sharper: **a second independent boot of the content-hash
|
||
ratio.** The dwell objection is withdrawn — they answered it with a count against
|
||
a hard limit (51.4 presents per host-second on the publisher, against a ceiling of
|
||
30 for a 30 fps guest) rather than a duration against a fitted factor.
|
||
|
||
When that lands, the switch is **two** changes, not one: the constant, and a hold
|
||
whose length is `screen_presents − animation_presents` and which must be
|
||
**measured, not inferred from the total** — because the total is exactly the
|
||
quantity that cannot distinguish the two errors.
|
||
|
||
|
||
---
|
||
|
||
# 🔴 WITHDRAWN: "the port has no hold" and "two errors that cancel"
|
||
|
||
Both were mine, both were last iteration's headline, and both are wrong.
|
||
|
||
## What I misread
|
||
|
||
The Decoder gave a split — *"219 presents on screen, ~128 animating"* — and I
|
||
read it as a hold **outside** the declared timeline, which the port would then be
|
||
missing. **It is a split *within* the timeline.** The declared group is ramp +
|
||
hold + fade, and the hold is the largest part of it:
|
||
|
||
```
|
||
publisher palogo_sqex: 0:a=0 15:a=0 30:a=255 235:a=255 239:232 251:32 255:0
|
||
ramp 0→30 = 30 units
|
||
HOLD 30→235 = 205 units ← 80.4 % of the screen
|
||
fade 235→255 = 20 units
|
||
```
|
||
|
||
## The port plays it. Measured, not read
|
||
|
||
Frozen samples across the publisher splash, logo region:
|
||
|
||
| t | units | region mean |
|
||
|---|---|---|
|
||
| 0.25 s | 15 | 0.390957 |
|
||
| 1.00 s | 60 | **0.405488** |
|
||
| 2.00 s | 120 | **0.405488** |
|
||
| 3.00 s | 180 | **0.405488** |
|
||
| 3.80 s | 228 | **0.405488** |
|
||
| 4.20 s | 252 | 0.038142 |
|
||
|
||
**Identical to six decimals across 168 units.** The port holds, for 80 % of the
|
||
screen, exactly as declared.
|
||
|
||
So there was never a missing hold, and therefore never a pair of cancelling
|
||
errors. **`authored/timing.json`'s "the pacing was right all along" was right all
|
||
along**, and my paragraph casting it as a possible coincidence is withdrawn there
|
||
too.
|
||
|
||
⚠️ The failure is worth naming because it is not the usual one: I did not
|
||
mis-measure anything. **I took a two-part split from someone else's instrument and
|
||
assumed the boundary was where my own model put it.** Presents are not units, and
|
||
"animating vs holding" in presents does not decompose the same way as "ramp vs
|
||
hold" in declared units.
|
||
|
||
# ✅ And the port's own data is an independent leg for the time-based clock
|
||
|
||
The Decoder's mechanism: `units/present` halved when the present rate doubled
|
||
while `units/second` did not move, so the UI clock advances by elapsed **time**,
|
||
not by frame count — and *"2 units per frame"* was a property of a 27 fps capture
|
||
rather than of the game.
|
||
|
||
**The frame-rate work of two iterations ago tests exactly that, and I did not
|
||
notice at the time.** The same splash, measured across a 4× change in the port's
|
||
own rendering rate:
|
||
|
||
| | frame rate | dwell | implied units/s |
|
||
|---|---|---|---|
|
||
| llvmpipe | 17.3 fps | 4.28 s | 59.6 |
|
||
| llvmpipe | 19.6 fps | 4.26 s | 59.9 |
|
||
| llvmpipe | 25.0 fps | 4.27 s | 59.7 |
|
||
| **GPU** | **69.4 fps** | **4.26 s** | **59.9** |
|
||
|
||
**Frame rate varies 4.0×; the dwell varies by 0.5 %.** That is the signature of a
|
||
time-based clock, and it puts the rate at 59.6–59.9 every time.
|
||
|
||
🔴 **This is a weaker leg than it looks and I am labelling it rather than
|
||
counting it.** It shows *the port's* clock is time-based — which it is by
|
||
construction, `time_units += delta * units_per_second` — so it cannot be evidence
|
||
about the game's. What it does show is that the **dwell figures I supplied are
|
||
frame-rate-independent measurements**, not artefacts of whatever rate a run
|
||
happened to hit. That is the property their argument needs of them, and it is now
|
||
established from this side rather than assumed.
|
||
|
||
# Where finding 3 stands
|
||
|
||
**Open, with no surviving named cause.** Units-per-second is eliminated in favour
|
||
of the value the port already ships; every other candidate died earlier. The
|
||
clock origin remains untouched, and every quantity in the resolved account is a
|
||
ratio or a count, so a common offset survives all of it.
|
||
|
||
|
||
---
|
||
|
||
# ✅ Settled: the game is frame-based, the port is time-based, and that is correct
|
||
|
||
`--framerate_limit=30` — the run this page asked for — refuted the time-based
|
||
reading on every discriminating row, **against its author's own expectation**:
|
||
|
||
| | predicted if time-based | **measured at 30 fps** |
|
||
|---|---|---|
|
||
| modal alpha step | 34 | **17** (unchanged) |
|
||
| units/second | 60 | **30.2** (halved) |
|
||
| publisher dwell | 4.25 s | **8.450 s** (doubled) |
|
||
|
||
Both controls passed *first*: the limiter demonstrably took effect (28.4
|
||
presents/host-s against 51–55, interval mass moving to two vblanks, 422 of 468),
|
||
and all 8 splash quad rects were identical, so nothing but the frame rate
|
||
differed. **`255 × 1 / 15 = 17`** at 28.4, 51.4 and 54.8 presents/s alike.
|
||
|
||
## What that changes for the port: nothing — but for a reason worth writing down
|
||
|
||
The game advances **1 unit per presented frame**. This port advances
|
||
`time_units += delta * units_per_second`. **They are different mechanisms that
|
||
agree at exactly one frame rate: 60 Hz** — which is the only rate the console
|
||
ever asked the game to be right at.
|
||
|
||
🔴 **Do not make the port frame-based to match the game.** A time-based port
|
||
reproduces a 60 Hz console on hardware that is not 60 Hz; a frame-based one would
|
||
drift on every machine that is not — and this port has measured *itself* between
|
||
**9.7 and 69.4 fps** depending on the renderer. Matching the game's mechanism
|
||
would import a fragility the game never had to survive, because the game only
|
||
ever ran on one box.
|
||
|
||
## 🔴 But it sharpens what `60` is claiming, and makes it falsifiable
|
||
|
||
If units/second **is** the present rate, then `keyframe_units_per_second = 60` is
|
||
no longer "the unit is 1/60 s". It is:
|
||
|
||
> **the game presented these screens at 60 Hz on the console.**
|
||
|
||
That is a harder claim and a checkable one. **It is also supported for the first
|
||
time:** Canary unlimited presents at 51–55 Hz and the splash dwell is
|
||
4.30 / 4.60 / 4.37 s over three cold boots. A natively 30 Hz game would present
|
||
at ~30 in Canary too — the `--framerate_limit` run proves it, since forcing 30
|
||
made that same splash take 8.45 s. **It does not take 8.45 s unforced.**
|
||
|
||
⚠️ Still `authored`, not promoted to `measured`: this is inference over three
|
||
measurements rather than a measurement of units per second. It becomes `measured`
|
||
when someone reads the console's present rate for these screens directly.
|
||
|
||
## And it closes the constant as a cause of finding 3 — in the direction that matters
|
||
|
||
| console rate | units/s | plate `t=236` |
|
||
|---|---|---|
|
||
| 30 Hz | 30 | **7.87 s** |
|
||
| **60 Hz (shipping)** | **60** | **3.93 s** |
|
||
|
||
Under the frame-based model the only alternative is 30 Hz, and it puts the plate
|
||
**later**, not earlier. **There is no console present rate that makes the plate
|
||
arrive sooner than this port already shows it.** The human reported it arriving
|
||
late; no value of this constant can produce that.
|
||
|
||
## The method note, and it is theirs
|
||
|
||
> *Four of my positions on this number were inference over a measured quantity;
|
||
> this one changed an input and watched what moved.*
|
||
|
||
The opportunistic comparison — two captures that happened to differ — pointed
|
||
**exactly the wrong way**, because nothing controlled what else differed between
|
||
them. One designed capture settled it against its author's expectation. That is
|
||
the difference between an observation and an experiment, and it cost this pair
|
||
five positions in a day to relearn.
|