diff --git a/authored/timing.json b/authored/timing.json index e19f31db..c6f40ed4 100644 --- a/authored/timing.json +++ b/authored/timing.json @@ -128,5 +128,31 @@ "`tools/port/verify-dwell` compares a transition INTERVAL against the oracle's", "visible SPAN plus that hold. What is deleted is the port's need to invent a", "duration the disc now states." + ], + "black_hold_units": 12, + "black_hold_why": [ + "MEASURED, not on the disc for these screens, so it is authored with a why.", + "", + "HANDOFF Q7: a screen transition is a fade THROUGH BLACK, and the pure-black", + "plateau between one screen leaving and the next arriving was measured at", + "0.17-0.23 s. The transition quad on the menus declares black for 12 units, and", + "12/60 = 0.200 s sits in the middle of that range -- so 12 units is the number,", + "and it is the disc's own where a screen carries such a quad.", + "", + "THE BOOT SPLASHES DO NOT CARRY ONE. `publisher_logo` and `developer_logos`", + "each hold a single `palogo_eff0`, a 1280x720 primitive with ONE keyframe at", + "t=0 -- static, not a transition ramp. So on the boot path the hold is", + "sequencer behaviour and there is nothing to read it from.", + "", + "🔴 IT HAD NEVER BEEN IMPLEMENTED, and this is what the publisher_logo residual", + "was. Filmed at 0.05 s, the port fades the publisher out to a mean of 0.317 at", + "t=4.20 s and the developer logos are already at 5.65 by t=4.25 -- NO BLACK", + "FRAME AT ALL. The oracle measures a 0.17-0.23 s plateau there. I had recorded", + "the miss as '0.03 s, probably an artefact of the bound I composed', and the", + "Decoder agreed; we were both wrong. The bound was fine and the port was", + "missing a fifth of a second of black.", + "", + "Delete this the day a screen's own transition quad supplies it on the boot", + "path too." ] } diff --git a/docs/port/DECISIONS.md b/docs/port/DECISIONS.md index f52ab86b..b86a97b3 100644 --- a/docs/port/DECISIONS.md +++ b/docs/port/DECISIONS.md @@ -4552,3 +4552,66 @@ sample large shared pages. Matching on texture dimensions told them first that n flash is ever drawn, then that `ptbase2` and `pteff04` are drawn in frames 75–105 — which are the intro movie, whose YUV planes are 640×360 targeting 1280×720. Two errors, opposite directions, one pass, neither loud. + +## ✅ The `publisher_logo` residual was a missing black hold, and we had both dismissed it + +I had carried this as *"0.03 s outside a composite bound, probably a property of +the bound rather than the game"*, and the Decoder agreed. **We were both wrong, +and the way it was settled is the point: I stopped reasoning about the bound and +filmed the transition.** + +At 0.05 s the port fell straight out of the publisher's fade into the developer +logos — mean 5.06 → 0.32 at t=4.20, then **5.65 at t=4.25**. There was **no black +frame at all**, where the oracle measures a 0.17–0.23 s pure-black plateau +(HANDOFF Q7). The bound was fine. The port was missing a fifth of a second of +black, and had been since P3. + +**Authored at 12 units**, because on the boot path there is nothing to read it +from: `publisher_logo` and `developer_logos` each carry a single `palogo_eff0` — +a 1280×720 primitive with **one keyframe at t=0**, static, not a transition ramp. +The menus' quad declares black for 12 units and 12/60 = **0.200 s** sits in the +middle of the measured range, so the number is the disc's where a screen has one. + +Filmed after: **t=4.25, 4.30, 4.35, 4.40 all at mean 0** — four black frames, +0.20 s — then the developer logos at 4.45. + +| | before | after | +|---|---|---| +| publisher interval | 4.26 s, **DIFFERS** | **4.47 s, agrees** | +| developer interval | 3.62 s, agrees | 3.73 s, agrees | + +The settled-frame comparisons are untouched, as they should be — this is pacing, +not pixels. + +⚠️ **The lesson is about the shape of the dismissal, not the number.** *"A 0.03 s +miss against a bound composed from two measured ranges plus jitter slack is more +likely a property of the bound"* is a **plausible** explanation, it was offered +and accepted by both of us, and it was wrong. The composite bound was the reason +the miss looked small — the underlying gap was 0.2 s — and a plausible +explanation for a small number is exactly how a real defect stays hidden. **The +film cost one command.** + +## `ptlogo_back2eff3` — recorded, deliberately not acted on + +The Decoder has reproduced across two independent build-ins that the console +**never draws `eff3`**: 0 draws against ~5 expected, while `eff1` gets 4, `eff2` +3 and `eff4` 6. Three explanations are ruled out — sampling phase (`eff3` is +non-zero over six units against a 2.23-unit step, and frames at t=60.1 and 62.3 +sit inside it drawing `eff2` and `eff4` instead), a draw the log cannot see, and +a bad position guess (no quad anywhere is within ±30 of the expected 408 width; +the spectrum jumps 262 → 748). + +❔ **But *why* is not established** — nothing in `eff3`'s record differs from its +neighbours: same kind `0x0`, same keyframe shape, same `u4`/`u8`, same scale. + +🔴 **So the port keeps drawing it, and that is a decision rather than an +oversight.** Dropping an element the disc declares, on a measurement with no +mechanism behind it, is authoring a behaviour neither agent can derive — and +**nothing this port gates on would notice either way**: the flashes live only in +the build-in, and `verify-capture` compares the settled frame at t=198 where none +of the five is drawn. Acting would buy no measurable fidelity and cost an +unexplained exception in `authored/`. + +**What would change it:** a mechanism in the record, or a gate that measures the +build-in against a capture. Until then the port is visibly wrong for two frames +during a build-in nobody compares, which is the cheaper of the two wrongs. diff --git a/port/scripts/boot.gd b/port/scripts/boot.gd index 7ebb6a2c..3734db4e 100644 --- a/port/scripts/boot.gd +++ b/port/scripts/boot.gd @@ -197,6 +197,7 @@ func _ready() -> void: # authored, and both of ScreenView's uses are dead branches kept only so an # older export still loads. view.exit_ramp_units = float(timing.get("exit_ramp_units", 24.0)) + _black_hold = float(timing.get("black_hold_units", 0.0)) # Which focus records draw unconditionally and loop. Kept out of ScreenView's # own logic on purpose -- see `looping_focus` there for the census that says # this cannot be a rule. @@ -290,6 +291,8 @@ var _script: PackedStringArray = PackedStringArray() var _shots := "" ## `--skip-at=SECONDS`: when to send a synthetic (A) during a movie, or 0. var _skip_at := 0.0 +## Units of pure black between one screen leaving and the next arriving. +var _black_hold := 0.0 ## `--focus=`: draw this element's focus record in a `--screen` run. var _force_focus := "" var _skip_sent := false @@ -374,7 +377,17 @@ func _process(delta: float) -> void: _menu_enter(String(_sequence[_step].get("screen", "")), true) elif _film == "" and _overlay_spec.is_empty(): get_tree().quit(0) - elif not view.holding and view.time_units >= view.exit_time(): + elif not view.holding and view.time_units >= view.exit_time() + _black_hold: + # 🔴 THE BLACK HOLD, which this port had never implemented. A transition + # is a fade THROUGH black (HANDOFF Q7), and the pure-black plateau + # between one screen leaving and the next arriving was measured at + # 0.17-0.23 s. Filmed at 0.05 s the port fell straight from the + # publisher's fade-out into the developer logos with NO BLACK FRAME. + # + # The menus' transition quad declares black for 12 units and 12/60 = + # 0.200 s sits in the middle of the measured range -- but the boot + # splashes carry no such quad (`palogo_eff0` is one static keyframe), so + # on this path it is authored. See `authored/timing.json`. _advance()