re: the declared timeline DOES reproduce the splash -- and the unit rate is per-GamePart

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.
This commit is contained in:
sylph-decoder
2026-09-01 17:19:59 +00:00
parent 4e0f7bf6cb
commit 4b5ac88f0a
4 changed files with 208 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 |
| [`splash-declared-vs-captured.md`](splash-declared-vs-captured.md) | Does the declared keyframe timeline reproduce the captured splash, and is the unit rate global? | ✅ **YES, to ONE alpha level, 2026-09-01** — settles the R1-re-opened 🟡 `⟨our-reader⟩` entry with a disc-table-vs-vertex-stream comparison, **no renderer in the chain**. 50 captured alphas: 39 exact under truncation (30 under rounding), **max error 1 level in 255 (0.39 %)**, and all 11 non-exact are low by exactly 1 on falling segments. The old ❌ rested on an `a=32` that the fixed record layout puts at t=206, four units from the end. 🔴 **AND: the unit→seconds rate is PER-GAMEPART.** Title ~**57** units/guest-second; splash **~3540**, confirmed two ways sharing no algebra — a 15-unit ramp *and* a **160-unit hold**, which has no `T` in it. The near-equal α/s across all four elements (650679) is a coincidence: `T` 22-vs-15 and rate 57-vs-37 nearly cancel. ⚠️ A single `keyframe_units_per_second` is wrong; a splash played at 60 runs **1.51.7× too fast**. Classified **measured** — nothing on the disc states a rate. |
| [`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. ✅ **RESOLVED later the same iteration: 56.8 units per guest second**, on `ptbtn00`'s ramp with the clamped final step excluded, `T=22` attested by two readers with no clock in the chain — **inside the pre-registered 5565 band; 30 and 120 both excluded**. Control passed at **1.15 %**: `ptcopyright` gives 650.4 α/s against the plate's 657.9, implying its own `T=22.25`. The earlier 29.9 was a borrowed `T=15` that does not apply to those elements (implied `T` there is 2334). ⚠️ `60` is **not** refuted — 5.6 % away against ~5 % quantisation — so the port keeps it; but the unit constant is eliminated as a cause of a late plate (t=236 → 4.15 s vs the port's 3.93 s, i.e. fractionally *early*). 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 |

View File

@@ -0,0 +1,57 @@
# The developer splash's DECLARED keyframe timeline, off the disc, under the FIXED
# record layout (ui-keyframe-record-layout.md). Reader: sylpheed-cli screen info
# --all --build 11 --geometry, built from this checkout.
build [11] 1280x720 7 elements 6 sprites
# element parent kind pivot kf rest / keyframes
0 palogo_eff0.prm - 0x10 (640,360) 1 (0,0)
1 palogo_gamearts.t32 - 0x0 (250,36) 7 rest (390,164) t=30 [0:390,164 15:390,164 30:390,164 190:390,164 194:390,164 206:390,164 210:390,164]
2 palogo_gamearts_eff.t32 - 0x0 (260,46) 4 rest (379,154) t=15 [0:379,154 15:379,154 30:379,154 45:379,154]
3 palogo_seta.t32 - 0x0 (120,44) 7 rest (521,316) t=30 [0:521,316 15:521,316 30:521,316 190:521,316 194:521,316 206:521,316 210:521,316]
4 palogo_seta_eff.t32 - 0x0 (130,55) 4 rest (511,305) t=15 [0:511,305 15:511,305 30:511,305 45:511,305]
5 palogo_anima.t32 - 0x0 (194,68) 7 rest (446,449) t=30 [0:446,449 15:446,449 30:446,449 190:446,449 194:446,449 206:446,449 210:446,449]
6 palogo_anima_eff.t32 - 0x0 (204,78) 4 rest (435,440) t=30 [0:435,440 15:435,440 30:435,440 45:435,440]
placement-region group order: [0, 1, 2, 3, 4, 5, 6] (== declaration order)
RATC child order: ["T8aD:palogo_gamearts.t32", "T8aD:palogo_gamearts_eff.t32", "T8aD:palogo_seta.t32", "T8aD:palogo_seta_eff.t32", "T8aD:palogo_anima.t32", "T8aD:palogo_anima_eff.t32"]
geometry — decoded sprite size vs the declared pivot, and every keyframe
# sprite decoded pivot*2 same keyframes t: x,y sx%,sy% a=alpha r=rot°
0 — — 1280x720 - 0: 0,0 100%,100% a=255
1 palogo_gamearts.t32 500x71 500x72 NO 0: 390,164 100%,100% a=0 15: 390,164 100%,100% a=0 30: 390,164 100%,100% a=255 190: 390,164 100%,100% a=255 194: 390,164 100%,100% a=232 206: 390,164 100%,100% a=32 210: 390,164 100%,100% a=0
2 palogo_gamearts_eff.t32 521x91 520x92 NO 0: 379,154 100%,100% a=0 15: 379,154 100%,100% a=255 30: 379,154 100%,100% a=255 45: 379,154 100%,100% a=0
3 palogo_seta.t32 240x89 240x88 NO 0: 521,316 100%,100% a=0 15: 521,316 100%,100% a=0 30: 521,316 100%,100% a=255 190: 521,316 100%,100% a=255 194: 521,316 100%,100% a=232 206: 521,316 100%,100% a=32 210: 521,316 100%,100% a=0
4 palogo_seta_eff.t32 261x110 260x110 NO 0: 511,305 100%,100% a=0 15: 511,305 100%,100% a=255 30: 511,305 100%,100% a=255 45: 511,305 100%,100% a=0
5 palogo_anima.t32 388x136 388x136 yes 0: 446,449 100%,100% a=0 15: 446,449 100%,100% a=0 30: 446,449 100%,100% a=255 190: 446,449 100%,100% a=255 194: 446,449 100%,100% a=232 206: 446,449 100%,100% a=32 210: 446,449 100%,100% a=0
6 palogo_anima_eff.t32 407x156 408x156 NO 0: 435,440 100%,100% a=0 15: 435,440 100%,100% a=255 30: 435,440 100%,100% a=212 45: 435,440 100%,100% a=0
# ---- and the PRESS (A) plate, build 2, for the T=22 the rate rests on ----
build [2] 1280x720 1 elements 2 sprites
# element parent kind pivot kf rest / keyframes
0 ptbtn00.rat - 0x73002 (256,25) 5 rest (383,550) t=236 [0:383,560 214:383,550 236:383,550 238:383,550 244:383,550]
→ focus ptbtn00f.rat
placement-region group order: [0] (== declaration order)
RATC child order: ["T8aD:ptbtn00.t32", "T8aD:ptbtn00f.t32", "RATC:ptbtn00.rat", "RATC:ptbtn00f.rat"]
geometry — decoded sprite size vs the declared pivot, and every keyframe
# sprite decoded pivot*2 same keyframes t: x,y sx%,sy% a=alpha r=rot°
0 ptbtn00.t32 513x50 512x50 NO 0: 383,560 100%,100% a=0 214: 383,550 100%,100% a=0 236: 383,550 100%,100% a=255 238: 383,550 100%,100% a=255 244: 383,550 100%,100% a=0
# ---- publisher splash, build 10 ----
build [10] 1280x720 3 elements 2 sprites
# element parent kind pivot kf rest / keyframes
0 palogo_eff0.prm - 0x10 (640,360) 1 (0,0)
1 palogo_sqex.t32 - 0x0 (330,30) 7 rest (309,330) t=30 [0:309,330 15:309,330 30:309,330 235:309,330 239:309,330 251:309,330 255:309,330]
2 palogo_sqex_eff.t32 1 0x1 (341,41) 4 rest (299,319) t=30 [0:299,319 15:299,319 30:299,319 45:299,319]
placement-region group order: [0, 1, 2] (== declaration order)
RATC child order: ["T8aD:palogo_sqex.t32", "T8aD:palogo_sqex_eff.t32"]
geometry — decoded sprite size vs the declared pivot, and every keyframe
# sprite decoded pivot*2 same keyframes t: x,y sx%,sy% a=alpha r=rot°
0 — — 1280x720 - 0: 0,0 100%,100% a=255
1 palogo_sqex.t32 666x68 660x60 NO 0: 309,330 100%,100% a=0 15: 309,330 100%,100% a=0 30: 309,330 100%,100% a=255 235: 309,330 100%,100% a=255 239: 309,330 100%,100% a=232 251: 309,330 100%,100% a=32 255: 309,330 100%,100% a=0
2 palogo_sqex_eff.t32 686x89 682x82 NO 0: 299,319 100%,100% a=0 15: 299,319 100%,100% a=255 30: 299,319 100%,100% a=212 45: 299,319 100%,100% a=0

View File

@@ -0,0 +1,125 @@
# The declared timeline **does** reproduce the captured splash — and the unit rate is **per-GamePart**
**Status: ✅ two results, one of which corrects me.** 2026-09-01.
Instrument: ⟨disc⟩ for the declared side, ⟨capture⟩ for the observed side.
**No renderer is anywhere in this comparison** — it is a keyframe table against a
vertex stream.
Settles the R1-re-opened entry *"the declared keyframe timeline reproduces the
captured splash"*, 🟡 `⟨our-reader⟩`, whose stated condition was *"re-derive the
declared timeline under the fixed record layout and re-compare against the same
frames."*
---
## 1 ✅ The declared timeline reproduces the capture to **one alpha level**
Declared, off the disc ([`data/splash-declared-timeline.txt`](data/splash-declared-timeline.txt)),
`GP_TITLE` build 11:
| family | declared keyframes |
|---|---|
| logos (`palogo_gamearts`, `_seta`, `_anima`) | `0:a=0 15:a=0 30:a=255 190:a=255 194:a=232 206:a=32 210:a=0` |
| glows (`_gamearts_eff`, `_seta_eff`) | `0:a=0 15:a=255 30:a=255 45:a=0` |
| `palogo_anima_eff` | `0:a=0 15:a=255 **30:a=212** 45:a=0` |
Captured: the per-quad vertex alphas from the draw stream
([`data/splash-quad-timeline.txt`](data/splash-quad-timeline.txt)).
**Test — deliberately calibration-free.** No fitting, no clock, no lag search:
*is each captured alpha an exact member of the declared value set*, i.e. equal to
the declared piecewise-linear α(t) at some integer t?
| | |
|---|---|
| samples | **50** |
| exact members under **truncation** | **39 / 50** |
| exact members under **rounding** | 30 / 50 |
| **worst error under either rule** | **1 alpha level out of 255 — 0.39 %** |
| samples off by more than one level | **0** |
**Every one of the 11 non-exact samples is low by exactly 1**, and every one of
them is on a *falling* segment. Truncation fits better than rounding by 9
samples, which is what an integer interpolator that floors rather than rounds
looks like.
📌 **So the entry resolves in favour of the declared timeline.** The old ❌ rested
on *"`palogo_gamearts` is still at `a=255` nine frames after its declared
`a=32`"* — under the fixed record layout `a=32` is at **t=206**, four units from
the end of a 210-unit timeline, not early. That was the off-by-one association,
exactly as the R1 note suspected.
🟡 **What is not settled:** *why* 11 samples floor one low while 39 do not. The
residual is one level and is named, not hidden. A candidate is that the clock is
fractional and α is floored, so a sample lands one low whenever the fraction is
small — untested.
## 2 🔴 The unit→seconds rate is **per-GamePart**, and I told the port otherwise
This is the correction, and it matters more than the first result.
An hour ago I reported **56.8 units per guest second** off the plate and wrote
that the reach was the title. The splash now has its own measurement, from the
disc-declared `T` rather than a borrowed one, and **it is a different number.**
| screen | element | declared | measured | **units / guest second** |
|---|---|---|---|---|
| title | `ptbtn00` ramp | `T = 22` (`t=214→236`, ⟨disc⟩) | 657.9 α/s | **56.8** |
| title | `ptcopyright` ramp | implied `T = 22.25` | 650.4 α/s | 56.8 (control, 1.15 %) |
| splash | `palogo_gamearts` ramp | `T = 15` (`t=15→30`, ⟨disc⟩) | 664.8 α/s | **39.1** |
| splash | publisher logo ramp | `T = 15` ⟨disc⟩ | 679.4 α/s | **40.0** |
| **splash** | **`palogo_gamearts` HOLD** | **160 units (`t=30→190`) ⟨disc⟩** | 4.514 guest s | **35.4** |
### The hold is what makes this safe to say
A ramp rate is `(Δα/Δt) × T/255` and therefore depends on `T`. **The hold does
not.** It is a declared *duration* — 160 units of plateau — measured directly in
guest seconds, with no alpha slope, no interpolation and no `T` in the
arithmetic. It gives **35.4 units/s** against the same screen's ramp at 39.1, and
the two share none of their algebra.
⚠️ The hold figure is a **lower bound on the rate**: it is measured between the
first and last labels at α=255, and the true plateau extends slightly past both.
So the splash sits at **~3540 units/s** and the title at **~57**.
### Why I nearly missed it — recorded because it is a good trap
All four elements give **650679 α/s**, agreeing to ±2 %, which reads exactly
like one clock. It is a coincidence: `T` differs 22 vs 15 (ratio 1.47) and the
rates differ 57 vs 37 (ratio 1.54), and those two ratios nearly cancel in
`Δα/Δt`. **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
because `T` is not in it.
### And my "borrowed `T=15` does not apply" was wrong
[`units-per-second-measured.md`](units-per-second-measured.md) says the splash
elements' implied `T` is 2334, *"none of them 15"*, derived by assuming one
rate. **The disc says `T = 15`, plainly.** The premise was the single rate, not
the `T`. Both halves of that page's arithmetic were right and its conclusion
about which term was unknown was wrong.
## What this means for the port
🔴 **A single `keyframe_units_per_second` cannot be right.** The two splashes and
the title run their timelines at rates ~1.5× apart, and the splashes are exactly
the screens the play-test called *"close but not right"*.
* title: **~57 units/s** — the port's 60 is 5 % away and stands.
* splashes: **~3540 units/s** — a port running them at 60 plays them **1.51.7×
too fast**, which would make every splash fade look shorter and sharper than
the game's. That is the direction of *"the fade is more pronounced in the
game"*.
**Classified: measured**, not decoded. Nothing on the disc has been found that
*states* a rate; these are two numbers off the running game. The port authors
them and must know it is authoring.
## Reach
Two `GamePart`s, one boot each. It establishes that the rate is **not global**,
which is a negative with wide reach — one counter-example is enough. It does
**not** establish the rate for the main menu, `EXTRAS`, or any submenu, and those
must not inherit either number. ❔ Where the rate comes from — a per-`GamePart`
field, a constant in the driver, or a frame-rate target — is **not decoded**, and
that is the next question.

View File

@@ -210,3 +210,28 @@ if you already know the step is one unit, and nothing said it was.
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 2334, 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: **~3540 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 (650679, ±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.