diff --git a/authored/rendering.json b/authored/rendering.json index 84ae7abc..a4cea797 100644 --- a/authored/rendering.json +++ b/authored/rendering.json @@ -48,5 +48,39 @@ "", "What deletes this list: a decode covering those cases, or an oracle capture", "of title_jp." + ], + "loop_leaf_on_screens": [ + "title" + ], + "loop_leaf_why": [ + "WHICH screens replay a leaf's group instead of letting it run once and park.", + "MEASURED on the title, UNRESOLVED on the menus, so it is scoped to the title.", + "", + "The disc gives one pass: ptloop01's leaf runs t=0..600 and ptloop02's t=0..720,", + "each ending parked off-screen at x=1521 / -839. The port ran them once.", + "", + "THE ORACLE SAYS THEY LOOP ON THE TITLE. Across two title dwells the sweep quad", + "oscillates over its whole x range and resets hard to the same start value --", + "one reset inside the first dwell, two inside the second. A run-once-and-park", + "shows one traverse and then a constant x.", + "", + "🔴 THE LOOP-LENGTH FIELD CANNOT SETTLE THIS, and I had hoped it would.", + "`ptloop01` declares 600 with keyframes to exactly 600; `ptloop02` declares 720", + "to 720. SLACK ZERO -- and 'loops at 600' and 'runs once for 600 and stops'", + "write the identical header. 92.3% of records on the disc are in that state, so", + "the field discriminates loop length only where there IS slack, as the plate's", + "105-in-120 had.", + "", + "⚠️ THE MENUS ARE NOT COVERED, on purpose. Both declare the same 600/720, so", + "nothing on the disc distinguishes them -- but the oracle measurement is of the", + "title, and my own weak evidence points the other way for the menu: sweeping the", + "phase against live-main-menu.png, the port matches best with the sweeps", + "OFF-SCREEN (0.061%) and three times worse mid-screen (0.183%). If they looped", + "with a 600-unit period the sweep is on screen for roughly 73% of the cycle, so", + "a capture showing none is not nothing -- but it is one capture, and 'best", + "match' is a weak instrument for an absence. Two weak signals in opposite", + "directions is a reason to scope, not to pick.", + "", + "What settles the menu: a direct capture of it, which the Decoder has offered." ] } diff --git a/authored/timing.json b/authored/timing.json index c6f40ed4..a8bd6eca 100644 --- a/authored/timing.json +++ b/authored/timing.json @@ -129,8 +129,39 @@ "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_units": 9, "black_hold_why": [ + "MEASURED IN THE DRAW STREAM, 9 units. Not on the disc for these screens, so", + "it is authored with a why.", + "", + "The Decoder counted submitted quads rather than luminance, which is the better", + "instrument here: luminance cannot separate the outgoing fade's tail from true", + "black, and the draw stream can. Frames 21-125 submit `palogo_sqex` fading to", + "alpha 7; frames 126-129 submit NO SPRITE QUAD AT ALL; frames 130-153 fade the", + "developer splash in from alpha 34. Four presented frames, the only such run in", + "the sequence.", + "", + "Converted with the disc as its own clock, because that run's frame rate is", + "useless (13.1 fps against 28 elsewhere): `palogo_sqex` declares alpha >= 1 for", + "239.8 units and is drawn in 105 frames -> 2.284 units/frame, corroborated", + "independently at 2.231 by the title capture. Four frames = 9.1 units = 0.152 s,", + "with a +/-1 frame range of 6.9-11.4 units.", + "", + "⚠️ IT DISAGREES WITH THE LUMINANCE FIGURE AND THE DISAGREEMENT IS THE POINT.", + "HANDOFF Q7's pure-black plateau was measured at 0.17-0.23 s and I authored 12", + "units (0.200 s) from it. The draw-stream range is 0.114-0.190 s, overlapping", + "only at the top. And the true black is SHORTER than 9 even so: both boundary", + "frames still carry picture, at alpha 7 and 34, and are counted as black.", + "", + "12 was also supported by the menus' transition quad declaring black for 12", + "units -- but that is a different screen's quad on a different path, and the", + "boot splashes carry no such quad at all (`palogo_eff0` is one static", + "keyframe). A number that fits by analogy loses to one measured in place.", + "", + "🔴 It had never been implemented at ALL until 2026-08-29 -- see the entry", + "below, kept because the way it stayed hidden matters more than the value.", + "", + "PREVIOUS ENTRY, kept:", "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", diff --git a/docs/port/DECISIONS.md b/docs/port/DECISIONS.md index 984bca1b..c49d87d0 100644 --- a/docs/port/DECISIONS.md +++ b/docs/port/DECISIONS.md @@ -4678,3 +4678,83 @@ off-screen**, which is evidence they run once and park. — but it is the first evidence either way, and it points against looping. The loop-length field the Decoder decoded (`+0x08` of a nested record's header) would settle it outright; it is not in the pinned tag. + +## Refuted — "the developer splash is one composited quad, the bounding box of the three logos" + +The Decoder observed the game submitting **one 525×259 quad at (378, 155)** on +the developer splash and read it as the bounding box of the three logos, warning +that drawing three sprites there draws something the console does not. **The +arithmetic does not support it**, and the port keeps drawing three. + +| | bounding box | +|---|---| +| the **three logos** | **500×421 at (390, 164)** | +| `gamearts_eff` + `seta_eff` | **521×261 at (379, 154)** | +| the observed quad | **525×259 at (378, 155)** | + +**A 259-tall quad cannot contain the three logos**, which span y 164…585: +`palogo_anima` alone starts at y = 449, thirty-five pixels below that quad's +bottom edge. The observed quad matches the union of the two `_eff` **glows** to +about four pixels in every dimension. + +⚠️ And those two are **transients** — my own census flagged them, dark again by +t=45 — so a frame containing that quad is a **build-in** frame, not the settled +screen. Consistent with a draw capture that starts early, which is exactly what +theirs does. + +I cannot see their draw stream, so I have sent the arithmetic rather than a +verdict. What I will not do is stop drawing an element on a claim whose stated +identification excludes that element from its own bounding box. + +## The black hold is 9 units, not 12 — measured in draws rather than luminance + +I authored 12 from HANDOFF Q7's luminance plateau of 0.17–0.23 s, supported by +the menus' transition quad declaring black for 12. The Decoder counted **submitted +quads** instead, which is the better instrument: luminance cannot separate the +outgoing fade's tail from true black. + +Frames 21–125 submit `palogo_sqex` fading to alpha 7; **frames 126–129 submit no +sprite quad at all**; 130–153 fade the developer splash in from alpha 34. Four +presented frames at 2.284 units/frame — a rate derived from the **disc as its own +clock**, because that run ran at 13.1 fps against 28 elsewhere — gives **9.1 +units = 0.152 s**, ±1 frame 6.9–11.4. + +⚠️ **It disagrees with the luminance figure and the disagreement is the point.** +0.114–0.190 s against 0.17–0.23 s overlaps only at the top, and the true black is +**shorter** than 9 even so: both boundary frames still carry picture. My 12 was +also supported *by analogy* — a different screen's quad on a different path, +where the boot splashes carry no quad at all. **A number that fits by analogy +loses to one measured in place.** + +`verify-dwell`'s bound moved with it. Both screens still agree: publisher 4.42 s, +developer 3.78 s. + +## The title's sweeps loop — measured, and the field could not have told us + +The disc gives one pass (`ptloop01` t=0…600, `ptloop02` t=0…720), each ending +parked off-screen, and the port ran them once. **The oracle says they loop**: +across two title dwells the sweep quad oscillates over its whole x range and +resets hard to the same start — one reset in the first dwell, two in the second. + +🔴 **And the loop-length field could not have settled it, which corrects a hope I +had stated.** Both records declare exactly their last keyframe time — **slack +zero** — and *"loops at 600"* and *"runs once for 600 and stops"* write the +identical header. 92.3 % of records on the disc are in that state, so the field +discriminates only where there **is** slack, as the plate's 105-in-120 had. + +Implemented and verified on the two sweeps' **least common multiple**, since they +have different periods: 600 and 720 realign at **3600 units = 60 s**. + +| separation | mean difference | +|---|---| +| **+3600 units (LCM)** | **0** | +| +1800 units | 0.438 | +| +600 units (`ptloop01` only) | 0.100 | + +⚠️ **Scoped to the title**, because that is where it is measured. The menus +declare the same 600/720 and nothing on the disc distinguishes them, but my own +weak evidence points the other way there — sweeping the phase against +`live-main-menu.png`, the port matches best with the sweeps **off-screen** +(0.061 %) and three times worse mid-screen (0.183 %), and if they looped the +sweep is on screen for roughly 73 % of the cycle. **Two weak signals in opposite +directions is a reason to scope, not to pick.** diff --git a/port/scripts/boot.gd b/port/scripts/boot.gd index 3734db4e..831ae700 100644 --- a/port/scripts/boot.gd +++ b/port/scripts/boot.gd @@ -205,6 +205,7 @@ func _ready() -> void: # Which decoded rules apply where. See `authored/rendering.json`. var rendering: Variant = export_tree.authored("rendering.json") _draw_leaf_for = [] if rendering == null else rendering.get("draw_leaf_for", []) + _loop_leaf_screens = [] if rendering == null else rendering.get("loop_leaf_on_screens", []) # `--no-hold` plays a screen's groups PAST their rest instead of clamping each # element at its own `rest.t`. A diagnostic, not a mode: `rest.t` is the last # HOLD keyframe before the exit, not the settled state, and the only way to @@ -221,6 +222,7 @@ func _ready() -> void: view.looping_focus = _looping_for(name) view.draw_leaf_for = _draw_leaf_for + view.loop_leaf = _loop_leaf_screens.has(name) view.focused_id = _force_focus if not view.load_screen(export_tree, name): push_error(export_tree.error) @@ -287,6 +289,8 @@ var _pending: Variant = null var _looping: Dictionary = {} ## `authored/rendering.json` `draw_leaf_for`. var _draw_leaf_for: Array = [] +## `authored/rendering.json` `loop_leaf_on_screens`. +var _loop_leaf_screens: Array = [] var _script: PackedStringArray = PackedStringArray() var _shots := "" ## `--skip-at=SECONDS`: when to send a synthetic (A) during a movie, or 0. @@ -404,6 +408,7 @@ func _advance() -> void: view.time_units = 0.0 view.looping_focus = _looping_for(name) view.draw_leaf_for = _draw_leaf_for + view.loop_leaf = _loop_leaf_screens.has(name) if not view.load_screen(view.tree, name): push_error(view.tree.error) get_tree().quit(2) @@ -607,6 +612,7 @@ func _menu_arrive() -> void: view.time_units = 0.0 view.looping_focus = _looping_for(name) view.draw_leaf_for = _draw_leaf_for + view.loop_leaf = _loop_leaf_screens.has(name) if not view.load_screen(view.tree, name): push_error(view.tree.error) get_tree().quit(2) @@ -922,6 +928,7 @@ func _raise_overlay(name: String) -> void: overlay.exit_ramp_units = view.exit_ramp_units overlay.looping_focus = _looping_for(name) overlay.draw_leaf_for = _draw_leaf_for + overlay.loop_leaf = _loop_leaf_screens.has(name) overlay.holding = true overlay.time_units = 0.0 if not overlay.load_screen(view.tree, name): diff --git a/port/scripts/screen_view.gd b/port/scripts/screen_view.gd index 24d22e8c..dfdc7745 100644 --- a/port/scripts/screen_view.gd +++ b/port/scripts/screen_view.gd @@ -90,6 +90,9 @@ var looping_focus: Dictionary = {} ## consistency harness compares against a renderer that draws no leaves at all. var draw_leaf_for: Array = [] +## Whether this screen replays a leaf's group. See `authored/rendering.json`. +var loop_leaf := false + ## While true the screen holds at `rest` and never plays its exit. The ## sequencer clears it to send the screen away. var holding: bool = true @@ -464,9 +467,22 @@ func _draw_leaf(element: Dictionary) -> bool: # `ORACLE-CAPTURES.md` says these two "move continuously". Held at its own # `rest.t` the leaf sits at x=1521 -- entirely off the right edge -- so # `holding` would delete the sweeps rather than settle them. + # A leaf replays its own group where the oracle has measured that it does + # -- `authored/rendering.json` `loop_leaf_on_screens`. The period is the + # leaf's own last keyframe time, which IS its declared length: these + # records carry zero slack, which is also why the loop-length field + # cannot tell "loops at 600" from "runs once for 600 and stops". var was := holding holding = false - var pose := pose_at(fe, time_units) + var t := time_units + if loop_leaf: + var span := 0.0 + for k: Dictionary in fe.get("keyframes", []): + if k.has("t"): + span = maxf(span, float(k["t"])) + if span > 0.0: + t = fposmod(time_units, span) + var pose := pose_at(fe, t) holding = was # 🔴 A SCALE-0 LEAF MUST NOT CLAIM THE DRAW. The Decoder hit this in its own # renderer: its leaf branch marked the element drawn unconditionally, but diff --git a/tools/port/verify-dwell b/tools/port/verify-dwell index 6f74db2e..f43525f4 100755 --- a/tools/port/verify-dwell +++ b/tools/port/verify-dwell @@ -45,7 +45,12 @@ INTERVAL="$INTERVAL" python3 - "$OUT" <<'PYEOF' import glob, os, re, subprocess, sys out = sys.argv[1] INTERVAL = float(os.environ.get("INTERVAL", "0.25")) -HOLD_LO, HOLD_HI = 0.17, 0.23 # HANDOFF Q7, measured +# The boot's black gap, measured in the DRAW STREAM (4 presented frames at +# 2.284 units/frame = 9.1 units), not from luminance -- luminance cannot separate +# the outgoing fade's tail from true black. The +/-1 frame range is 6.9-11.4 +# units = 0.114-0.190 s. HANDOFF Q7's luminance figure of 0.17-0.23 s overlaps +# only at the top, and the draw-stream number is the one to use. +HOLD_LO, HOLD_HI = 0.114, 0.190 marks = [] for line in open(os.path.join(out, "boot.log"), errors="replace"):