From 39209eab055167132e0c91d3ce4e7b60a0d3a913 Mon Sep 17 00:00:00 2001 From: Sylpheed port agent Date: Sat, 29 Aug 2026 20:44:56 +0000 Subject: [PATCH] port: the title's sweeps loop, the black hold is 9 units, and one claim refuted THREE THINGS FROM THE DECODER, one of which I am not taking. REFUTED: "the developer splash is one composited quad, the bounding box of the three logos". The observed quad is 525x259 at (378,155). The three logos' bounding box is 500x421 at (390,164) -- a 259-tall quad CANNOT contain them, and palogo_anima alone starts at y=449, thirty-five pixels below that quad's bottom edge. The observed quad matches the union of gamearts_eff and seta_eff, 521x261 at (379,154), to about four pixels in every dimension -- and both of those are TRANSIENTS my own census flagged, dark by t=45, so a frame containing that quad is a build-in frame rather than the settled screen. I cannot see their draw stream, so I sent the arithmetic rather than a verdict, and the port keeps drawing three: I will not 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. I authored 12 from Q7's luminance plateau of 0.17-0.23 s, supported by the menus' transition quad. The Decoder counted SUBMITTED QUADS instead -- luminance cannot separate the outgoing fade's tail from true black. Four frames with no sprite quad at all, at 2.284 units/frame derived from the disc as its own clock, gives 9.1 units = 0.152 s (6.9-11.4). That overlaps the luminance figure only at the top, and the true black is SHORTER still since both boundary frames carry picture. My 12 was supported by analogy -- a different screen's quad on a different path -- and a number that fits by analogy loses to one measured in place. verify-dwell's bound moved with it; both screens still agree. THE TITLE'S SWEEPS LOOP. The oracle shows the quad oscillating over its whole x range and resetting hard, one reset in the first title dwell and two in the second. The loop-length field could NOT have settled it, correcting 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. Verified on the two sweeps' LCM, since their periods differ: 600 and 720 realign at 3600 units, mean diff 0, against 0.438 at half that. Scoped to the title. The menus declare the same lengths but the oracle measurement is of the title, and my own weak evidence points the other way there -- best match with the sweeps off-screen, three times worse mid-screen, against a 73% on-screen duty cycle if they looped. Two weak signals in opposite directions is a reason to scope, not to pick. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01N7FiFFFwbvG2uxdcEh8HyF --- authored/rendering.json | 34 ++++++++++++++++ authored/timing.json | 33 ++++++++++++++- docs/port/DECISIONS.md | 80 +++++++++++++++++++++++++++++++++++++ port/scripts/boot.gd | 7 ++++ port/scripts/screen_view.gd | 18 ++++++++- tools/port/verify-dwell | 7 +++- 6 files changed, 176 insertions(+), 3 deletions(-) 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"):