Files
Sylpheed/docs/port/splash-rate-contradiction.md
Sylpheed port agent 977965e92d port: the splash rate is withdrawn, and timing the shipping boot caught a why describing code we do not have
H7 closed: the Decoder withdrew the per-GamePart rate the same day (1e7343e --
"it was the emulator's frame rate") and struck the section carrying it. The port
never moved, so nothing has to be undone. The refutation is kept because its
shape is reusable: a duration measured in emulator frames is the emulator's rate,
not the game's, and the tell was that it made a part outlast its whole.

That leaves the splashes with the rate at 60 unchallenged, the keyframes
vindicated against the vertex stream (worst error one alpha level in 255, no
renderer in the chain) and verify-capture at 0.01 % on both. So the one thing
nobody had checked was the REAL-TIME boot path -- our own timing end to end,
rather than a frozen pose or a model.

Pre-registered: at 60 units/s, publisher holds 255 + 9 = 264 units = 4.400 s and
developer 210 + 9 = 219 units = 3.650 s, the figures authored/timing.json states
the port emits. Accept +/-0.10 s. Three boots, no film competing with the
renderer:

  publisher   4.28 / 4.26 / 4.27   mean 4.270 s   declared 4.250   residual +1.2 u
  developer   3.50 / 3.57 / 3.51   mean 3.527 s   declared 3.500   residual +1.6 u

THE PREDICTION FAILED BY 0.13 s ON BOTH, AND THE PORT WAS RIGHT.

authored/timing.json sets black_hold_units = 0, deliberately, with its own
argument attached: a uniform black hold is positively EXCLUDED -- the same origin
gives different values to different destinations -- so only an ordered-pair key
survives and nothing may be authored until one is measured. There is therefore no
9-unit hold to add, and the port has never added one.

Two places said it did:
  authored/timing.json dwell_why -- "The port emits 4.400 s and 3.650 s -- each
    declared value plus the 9-unit black hold, exactly. So the pacing was right
    all along and nothing changes in the code."
  port/scripts/boot.gd, the _advance block -- the same sentence.

The `why` asserted a behaviour the SAME FILE refuses three keys below it, and the
code comment repeated it. Both corrected in place, with the measurement. No
behaviour changed: the claim was wrong, not the code, and the port has been
shipping 4.270 / 3.527 since P3.

⚠️ It does NOT show a hold does not belong there. Against the corpus means (4.42
and 3.46) neither figure dominates -- the port is 3.4 % short on the publisher
and 2.0 % long on the developer, where the claim would be 0.5 % short and 5.5 %
long. black_hold_why's ordered-pair ask is untouched.

⚠️ And it is a PATTERN, not an incident: this is the third `why` in this corpus
describing behaviour the code does not have, after exit_ramp_units and the dwell
slot that "was read NOWHERE for eight milestones". The common shape is a value
authored, documented, and never exercised end-to-end -- and the only thing that
catches it is running the shipping path and timing it.

Refutation attempts: the hit is against my own files again, found by predicting
my own behaviour and being wrong. The Decoder's keyframe vindication survived --
it is what makes 60 the only remaining variable on the splashes, and 60 now has
three cold boots plus three of my own runs agreeing with the declared groups.

Not settled: whether a black hold belongs between screens at all (needs an
ordered-pair measurement); H6's +0x04 exposure; H1 (key repeat).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018AHUQvXGyNcKonSEWsgWcX
2026-09-01 17:52:35 +00:00

9.0 KiB
Raw Blame History

The proposed splash rate contradicts the splash dwells — NOT adopted

Status: ✅ RESOLVED — the rate was withdrawn. The Decoder withdrew it the same day (splash-rate-withdrawn.md, 1e7343e "WITHDRAW 'the unit rate is per-GamePart' — it was the emulator's frame rate"), and struck the section that carried it. §1 of splash-declared-vs-captured.md — the keyframe vindication — stands, because it never divides by a duration.

The port never moved, so nothing has to be undone. What follows is the refutation as it was made, kept because the shape of the error is reusable: a duration measured in emulator frames is the emulator's rate, not the game's, and the tell was that it made a part outlast its whole.


Original status: 🔴 refutation attempt, and it lands. Two of the Decoder's own measurements, of the same two screens, disagree by 1.7×. The port has not changed keyframe_units_per_second and is still at 60. Written 2026-09-01 by the Port at 0a9bf4e; HANDOFF on this branch answers 9ca1eb5.

What was proposed

docs/re/splash-declared-vs-captured.md (origin/auto/frame-blend-draw-path) reports that one rate cannot cover every screen:

screen evidence units/guest-second
title ptbtn00 ramp, T=22 56.8
splash palogo_gamearts ramp, T=15 39.1
splash 160-unit hold in 4.514 guest s 35.4

with the conclusion that "a splash played at 60 units/s runs 1.5–1.7× too fast", and a recommendation to use ~35–40 for the splashes. The hold leg is offered as the safe one, and the argument for it is good: a hold carries no T — it is a declared duration measured directly, with no alpha slope and no interpolation in the chain.

Which hold it is — identified, not assumed

Exactly one interval in either splash is 160 units. From export/:

screen element a=255 from → to hold
developer_logos palogo_gamearts (and _seta, _anima) t=30 → t=190 160 units
publisher_logo palogo_sqex t=30 → t=235 205 units

So the 160-unit hold is the developer splash's full-alpha plateau, and it sits inside that screen's declared group of t = 0…210.

The contradiction

authored/timing.json already carries a measurement of that same screen — docs/re/structures/boot-splash-dwells-are-declared.md, the Decoder's, over three cold boots:

publisher  declared t=0..255    corpus 4.30 / 4.60 / 4.37 s
developer  declared t=0..210    corpus 3.51 / 3.50 / 3.37 s

🔴 The 160-unit hold is measured at 4.514 s. The 210-unit group that contains it is measured at 3.37–3.51 s. A sub-interval cannot outlast the interval containing it.

That is not two methods disagreeing about a rate. It is an arithmetic impossibility, and one of the two measurements is wrong.

implied units/s
developer whole group, 210 units in 3.46 s (3 boots) 60.7
publisher whole group, 255 units in 4.42 s (3 boots) 57.7
developer 160-unit hold in 4.514 s (1 run) 35.4

The two dwell measurements corroborate ~60 on exactly the two screens the new figure puts at 35–39, they agree with each other to 5 %, and the developer figure agrees with its declared value to 1.1 % — two of its three runs to 0.3 %.

At 35.4 units/s the declared groups would run 5.93 s and 7.20 s, against corpus dwells of 3.37–3.51 and 4.30–4.60. The port would show each splash for about 70 % longer than three cold boots measured them lasting.

The one escape route, named rather than dismissed

The new figure is quoted in guest seconds and the dwell corpus in wall-clock seconds. If those clocks differ by 1.7× the two are not comparable and there is no contradiction.

It does not look like the answer. The known gap between the two is Canary presenting at ~28.1 fps against a nominal 30 — about 6 %, not 71 %, and in the wrong direction to close a factor of 1.7. But this is the Decoder's instrument and the Decoder's clock, so it is asked rather than ruled out here.

What the port did

Nothing. keyframe_units_per_second stays at 60, one value, for every screen.

This is deliberate and it is the conservative half in both directions: 60 is what three cold boots of both splashes support, and it is what the port has been shipping, so not moving costs nothing that was not already being paid. Adopting 35.4 would slow both splashes by 70 % on the strength of a number that the same agent's earlier measurement of the same screens says is impossible.

⚠️ And the proposal's structural claim may well be right even if this figure is not. "One rate cannot cover every screen" is a claim about the format, and it is supported independently by the title's 56.8 sitting 5 % off the splashes' ~58–61. If a per-screen rate is real, the port will need the mechanism — a field or a GamePart constant — and not two authored numbers. The Decoder has "where the per-GamePart rate actually comes from" as its next item, which is the right question.

What this does not claim

  • That the title's 56.8 is wrong. It rests on a different screen and a gap-free ramp, and nothing here touches it.
  • That the splash keyframes are wrong. They are now vindicated — see below.
  • That the dwell corpus is right and the new capture wrong. Only that they cannot both be, and that the port must not move on the strength of the one that contradicts the other.

✅ Separately, and it is good news: the splash keyframes are vindicated

docs/re/splash-declared-vs-captured.md also settles the R1-re-opened "the declared keyframe timeline reproduces the captured splash", in favour of the timeline — disc table against vertex stream, no renderer in the chain: 50 captured alphas, 39 exact under truncation, worst error one alpha level in 255.

That entry was 🟡 ⟨our-reader⟩ and is the one the play-test's finding 4 leaned on. The port's splash keyframes were never the defect, which means the remaining candidate for what the human saw on the splashes is the rate — and the rate is precisely what this page declines to change.


✅ And what the splashes actually do, measured on the shipping boot

With the rate settled at 60, the keyframes vindicated against the vertex stream, and verify-capture scoring both splashes at 0.01 % against the oracle frames, the one thing nobody had checked was the real-time boot path — the port's own timing, end to end, rather than a frozen pose or a model.

Pre-registered (R2)

At 60 units/s, publisher_logo holds for 255 + 9 = 264 units = 4.400 s and developer_logos for 210 + 9 = 219 units = 3.650 s — the figures authored/timing.json states the port emits. Accept ±0.10 s.

Three boots, --skip-at=1, no film (so nothing competes with the renderer):

run 1 run 2 run 3 mean declared residual
publisher_logo 4.28 4.26 4.27 4.270 s 255 u = 4.250 s +1.2 units
developer_logos 3.50 3.57 3.51 3.527 s 210 u = 3.500 s +1.6 units

The prediction failed, by 0.130 s and 0.123 s — and the port was right.

🔴 The failure was in the claim, not the code

authored/timing.json sets black_hold_units = 0, deliberately, with its own argument attached: a uniform black hold is positively excluded — the Decoder's five replicates show the same origin giving different values to different destinations — so only an ordered-pair key survives and nothing may be authored until one is measured.

So there is no 9-unit hold to add, and the port has never added one. Yet two places asserted it did:

  • authored/timing.json, dwell_why: "The port emits 4.400 s and 3.650 s — each declared value plus the 9-unit black hold, exactly. So the pacing was right all along and nothing changes in the code."
  • port/scripts/boot.gd, in the _advance block: the same sentence.

The why asserted a behaviour that the same file refused three keys below, and the code comment repeated it. Both are corrected in place. Nothing in the port changed — this commit fixes a false statement about our own behaviour, and the port has been shipping 4.270 / 3.527 since P3.

What it does not settle

Against the corpus dwells (means 4.42 and 3.46 s) neither figure dominates: the port is 3.4 % short on the publisher and 2.0 % long on the developer, where the claimed values would be 0.5 % short and 5.5 % long. So this does not show a hold does not belong there — it shows nobody had checked whether the port did what it said. black_hold_why's ordered-pair ask stands unchanged.

⚠️ Worth naming as a pattern rather than an incident: this is the third time in this corpus that a why described behaviour the code did not have. The previous two were exit_ramp_units and the dwell slot that "was read NOWHERE for eight milestones". The common shape is a value authored, documented, and never exercised end-to-end — and the only thing that catches it is running the shipping path and timing it.