diff --git a/authored/rendering.json b/authored/rendering.json index 55684693..a8f03a9d 100644 --- a/authored/rendering.json +++ b/authored/rendering.json @@ -1,172 +1,200 @@ { - "format": "sylpheed.rendering/1", - "_": [ - "WHICH decoded rules the runtime applies where. AUTHORED because it is a", - "choice about the REACH of somebody else's decode, not about the disc.", - "Delete an entry the day the decode covers the case outright.", - "", - "The exporter flags `leaf_carries_geometry` on 15 elements -- those whose", - "nested `.rat` leaf declares a scale or rotation the parent does not. That", - "flag is a CENSUS FACT and it is emitted for all 15. What is DECODED is", - "narrower: the Decoder fitted the game's own composed alpha (per-draw vertex", - "colours C3FFFFFF / B6FFFFFF = 195 and 182) against the ptloop leaves and got", - "one consistent time, then PREDICTED the quad centres to ~11 px. That covers", - "`ptloop01` and `ptloop02` and nothing else." - ], - "draw_leaf_for": [ - "ptloop01", - "ptloop02" - ], - "draw_leaf_why": [ - "The two the decode covers. `docs/re/structures/ui-leaf-vs-parent-alpha.md`.", - "", - "NOT DRAWN, though the exporter flags them and ships their data:", - "", - " `title_jp/ptlogo_eff2` -- OUT OF SCOPE, which is a better reason than", - " the caution this entry first gave. MISSION section 7 scopes out", - " 'localisation beyond English', and this element exists only on the", - " Japanese title. So it is not a thing the menu port has to answer, and the", - " parked Japanese-locale capture does not need reviving on its account --", - " that is the human's call and not something either agent widens quietly.", - "", - " It is ALSO undecidable here even if it were in scope. Its 125% is a POP,", - " not a steady scale: scale-0 -> 125% -> scale-0 between t=50 and t=107,", - " about 0.95 s. The leaf draws at 100%, as two superimposed copies at alpha", - " 160 and 80, each rotating 360 degrees over 960 units -- 16 s a turn. If", - " parent scale gates the leaf it is a 0.95 s flash; if the leaf runs free it", - " spins for 16 s. Nothing on the disc chooses and title_jp has no oracle", - " capture.", - " `build_12,15/pgloading_loop5` -- STILL NOT DRAWN, but the reason given here", - " was WRONG and is replaced. It read \"leaf scale (0,0). A zero scale is one of", - " the three historical failures this corpus names\" -- which describes t=0 and", - " t=30 and nothing after them.", - "", - " What the leaf actually holds, read out of the export: ONE element,", - " `pgloading_ring`, with a sprite, whose scale ramps 0 -> 250 -> 800 -> 1000", - " while its alpha rises to full at t=55 and falls to nothing by t=130. An", - " expanding, fading ring -- a loading pulse, not a degenerate record.", - "", - " 🔴 And it is VISIBLE at the instant this port poses. `build_12`'s settle", - " window is [40, 48], so the pose lands near t=44, where the ring interpolates", - " to scale 140 at alpha 143. So withholding it is not declining to draw", - " nothing; it is declining to draw something, and the old reason hid that.", - "", - " It stays withheld on the reason below, which is the one that always applied:", - " there is no way to adjudicate it here. The loading screens have no oracle", - " capture -- the RE agent records them as not reachable from the title path --", - " and `verify-screen` compares against a renderer that draws no leaves at all.", - " Drawing it would put unadjudicable content on a screen, which is the same", - " test `ptlogo_eff2` fails.", - "", - "AND THERE IS NO WAY TO ADJUDICATE EITHER HERE. `title_jp` has no oracle", - "capture, and `verify-screen` compares against `sylpheed-cli`, which does not", - "draw leaves at all -- so ANY leaf drawing increases that divergence whether", - "it is right or wrong. Its max went 155 -> 232 when they were drawn, and that", - "number is not evidence in either direction.", - "", - "What deletes this list: a decode covering those cases, or an oracle capture", - "of title_jp." - ], - "draw_leaf_kind": "decoded", - "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.", - "", - "🔴 RE-MEASURED 2026-08-31, BECAUSE THE EVIDENCE ABOVE WAS TAKEN WITH THE WRONG", - "BLEND. The phase sweep that produced '0.061 % off-screen, 0.183 % mid-screen'", - "drew the sweeps ALPHA-OVER. They are additive -- measured off the running game", - "the same day (`additive_elements`) -- so an on-screen sweep composited the wrong", - "way was being scored against the capture, and 'mid-screen is worse' could have", - "been an artefact of my own compositing rather than of the sweeps being absent.", - "", - "Re-run with additive sweeps and looping switched on for the menu, against", - "`live-main-menu.png`:", - "", - " phase 0 0.0208 % sweeps paint 0 px -- off screen", - " phase 150 0.0851 % sweeps paint 58 027 px, bbox 884x720", - " phase 300 0.0205 % sweeps paint 0 px -- off screen", - " phase 75 / 225 / 375 / 450 / 525: 0.086..0.122 %", - " run-once-and-park, which is what the port ships: 0.0208 %", - "", - "✅ THE CONCLUSION HELD AND GOT STRONGER. The ratio was 3x with the wrong blend", - "and is 4-6x with the right one, and the absolute numbers improved everywhere.", - "The capture still matches best with the sweeps NOT VISIBLE. So this entry stays", - "scoped to the title, and the correction is recorded rather than the scoping", - "changed.", - "", - "⚠️ It is still one capture and 'best match' is still a weak instrument for an", - "absence -- that caveat is not repaired by fixing the blend, only cleared of one", - "confound.", - "", - "📌 AND THE NEW DRAW LOG DOES NOT SETTLE IT EITHER, though it looks like it", - "should. `docs/re/captures/ui-draws/blend-main-menu-2026-08-31.log` shows both", - "sweep strips SUBMITTED on the main menu, in every frame group. That is not", - "evidence they animate there: a quad parked off-screen at x=1521 is still a draw", - "call. A DRAW IS NOT A VISIBLE ELEMENT, and reading that log as 'the sweeps run", - "on the menu' would have contradicted the pixels for no reason." - ], - "loop_leaf_kind": "measured", - "additive_elements_deleted_why": [ - "✅ DELETED 2026-09-01, and the deletion is the point.", - "", - "This held `additive_elements`, a per-screen list of element ids transcribed", - "from the Decoder's per-draw RB_BLENDCONTROL0 log. PORT-MISSION section 3: 'When", - "the RE agent later decodes something you had authored, delete the authored", - "entry and let the exporter emit it. That deletion is the measure of progress.'", - "", - "The blend is now DECODED -- `T8aD +0x04` bit 0x02, docs/re/structures/", - "ui-blend-mode-decoded.md -- and reachable since formats-pin-2026-09-01 exposed", - "`ui_layout::sprite_blend_additive` and `blend_additive_by_name`. The exporter", - "emits `blend_additive` per element and per nested focus/leaf element, and", - "ScreenView reads it there.", - "", - "🔴 CHECKED BEFORE THE SWAP, and the map turned out to be a SUBSET rather than", - "the answer. Over main_menu, extras, press_start and title:", - "", - " 15 the map called additive AND the disc agrees", - " 0 the map called additive and the disc does not <- no contradictions", - " 17 the disc calls additive and the map did not", - "", - "So nothing transcribed was wrong; it was incomplete, and was being read as", - "complete. The 17 include `pteff03`/`pteff03a` -- the sweep LEAVES, which are", - "what `draw_leaf_for` actually puts on screen while the map listed their parents", - "`ptloop01`/`ptloop02` -- and TWELVE on `title`, where this map was deliberately", - "empty and the port therefore drew every title effect alpha-over.", - "", - "A name-keyed map can only answer for a screen somebody drove the game to. That", - "is what made the Japanese menus an open question (BLOCKED.md H6): the port drew", - "main_menu additive and main_menu_jp alpha-over, asserting by omission that the", - "JP build blends differently. The bit is on the disc for every screen at once, so", - "that asymmetry is now answered statically and H6 needs no capture." - ] + "format": "sylpheed.rendering/1", + "_": [ + "WHICH decoded rules the runtime applies where. AUTHORED because it is a", + "choice about the REACH of somebody else's decode, not about the disc.", + "Delete an entry the day the decode covers the case outright.", + "", + "The exporter flags `leaf_carries_geometry` on 15 elements -- those whose", + "nested `.rat` leaf declares a scale or rotation the parent does not. That", + "flag is a CENSUS FACT and it is emitted for all 15. What is DECODED is", + "narrower: the Decoder fitted the game's own composed alpha (per-draw vertex", + "colours C3FFFFFF / B6FFFFFF = 195 and 182) against the ptloop leaves and got", + "one consistent time, then PREDICTED the quad centres to ~11 px. That covers", + "`ptloop01` and `ptloop02` and nothing else." + ], + "draw_leaf_for": [ + "ptloop01", + "ptloop02" + ], + "draw_leaf_why": [ + "The two the decode covers. `docs/re/structures/ui-leaf-vs-parent-alpha.md`.", + "", + "NOT DRAWN, though the exporter flags them and ships their data:", + "", + " `title_jp/ptlogo_eff2` -- OUT OF SCOPE, which is a better reason than", + " the caution this entry first gave. MISSION section 7 scopes out", + " 'localisation beyond English', and this element exists only on the", + " Japanese title. So it is not a thing the menu port has to answer, and the", + " parked Japanese-locale capture does not need reviving on its account --", + " that is the human's call and not something either agent widens quietly.", + "", + " It is ALSO undecidable here even if it were in scope. Its 125% is a POP,", + " not a steady scale: scale-0 -> 125% -> scale-0 between t=50 and t=107,", + " about 0.95 s. The leaf draws at 100%, as two superimposed copies at alpha", + " 160 and 80, each rotating 360 degrees over 960 units -- 16 s a turn. If", + " parent scale gates the leaf it is a 0.95 s flash; if the leaf runs free it", + " spins for 16 s. Nothing on the disc chooses and title_jp has no oracle", + " capture.", + " `build_12,15/pgloading_loop5` -- STILL NOT DRAWN, but the reason given here", + " was WRONG and is replaced. It read \"leaf scale (0,0). A zero scale is one of", + " the three historical failures this corpus names\" -- which describes t=0 and", + " t=30 and nothing after them.", + "", + " What the leaf actually holds, read out of the export: ONE element,", + " `pgloading_ring`, with a sprite, whose scale ramps 0 -> 250 -> 800 -> 1000", + " while its alpha rises to full at t=55 and falls to nothing by t=130. An", + " expanding, fading ring -- a loading pulse, not a degenerate record.", + "", + " \ud83d\udd34 And it is VISIBLE at the instant this port poses. `build_12`'s settle", + " window is [40, 48], so the pose lands near t=44, where the ring interpolates", + " to scale 140 at alpha 143. So withholding it is not declining to draw", + " nothing; it is declining to draw something, and the old reason hid that.", + "", + " It stays withheld on the reason below, which is the one that always applied:", + " there is no way to adjudicate it here. The loading screens have no oracle", + " capture -- the RE agent records them as not reachable from the title path --", + " and `verify-screen` compares against a renderer that draws no leaves at all.", + " Drawing it would put unadjudicable content on a screen, which is the same", + " test `ptlogo_eff2` fails.", + "", + "AND THERE IS NO WAY TO ADJUDICATE EITHER HERE. `title_jp` has no oracle", + "capture, and `verify-screen` compares against `sylpheed-cli`, which does not", + "draw leaves at all -- so ANY leaf drawing increases that divergence whether", + "it is right or wrong. Its max went 155 -> 232 when they were drawn, and that", + "number is not evidence in either direction.", + "", + "What deletes this list: a decode covering those cases, or an oracle capture", + "of title_jp." + ], + "draw_leaf_kind": "decoded", + "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.", + "", + "\ud83d\udd34 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.", + "", + "\u26a0\ufe0f 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.", + "", + "\ud83d\udd34 RE-MEASURED 2026-08-31, BECAUSE THE EVIDENCE ABOVE WAS TAKEN WITH THE WRONG", + "BLEND. The phase sweep that produced '0.061 % off-screen, 0.183 % mid-screen'", + "drew the sweeps ALPHA-OVER. They are additive -- measured off the running game", + "the same day (`additive_elements`) -- so an on-screen sweep composited the wrong", + "way was being scored against the capture, and 'mid-screen is worse' could have", + "been an artefact of my own compositing rather than of the sweeps being absent.", + "", + "Re-run with additive sweeps and looping switched on for the menu, against", + "`live-main-menu.png`:", + "", + " phase 0 0.0208 % sweeps paint 0 px -- off screen", + " phase 150 0.0851 % sweeps paint 58 027 px, bbox 884x720", + " phase 300 0.0205 % sweeps paint 0 px -- off screen", + " phase 75 / 225 / 375 / 450 / 525: 0.086..0.122 %", + " run-once-and-park, which is what the port ships: 0.0208 %", + "", + "\u2705 THE CONCLUSION HELD AND GOT STRONGER. The ratio was 3x with the wrong blend", + "and is 4-6x with the right one, and the absolute numbers improved everywhere.", + "The capture still matches best with the sweeps NOT VISIBLE. So this entry stays", + "scoped to the title, and the correction is recorded rather than the scoping", + "changed.", + "", + "\u26a0\ufe0f It is still one capture and 'best match' is still a weak instrument for an", + "absence -- that caveat is not repaired by fixing the blend, only cleared of one", + "confound.", + "", + "\ud83d\udccc AND THE NEW DRAW LOG DOES NOT SETTLE IT EITHER, though it looks like it", + "should. `docs/re/captures/ui-draws/blend-main-menu-2026-08-31.log` shows both", + "sweep strips SUBMITTED on the main menu, in every frame group. That is not", + "evidence they animate there: a quad parked off-screen at x=1521 is still a draw", + "call. A DRAW IS NOT A VISIBLE ELEMENT, and reading that log as 'the sweeps run", + "on the menu' would have contradicted the pixels for no reason." + ], + "loop_leaf_kind": "measured", + "additive_elements_deleted_why": [ + "\u2705 DELETED 2026-09-01, and the deletion is the point.", + "", + "This held `additive_elements`, a per-screen list of element ids transcribed", + "from the Decoder's per-draw RB_BLENDCONTROL0 log. PORT-MISSION section 3: 'When", + "the RE agent later decodes something you had authored, delete the authored", + "entry and let the exporter emit it. That deletion is the measure of progress.'", + "", + "The blend is now DECODED -- `T8aD +0x04` bit 0x02, docs/re/structures/", + "ui-blend-mode-decoded.md -- and reachable since formats-pin-2026-09-01 exposed", + "`ui_layout::sprite_blend_additive` and `blend_additive_by_name`. The exporter", + "emits `blend_additive` per element and per nested focus/leaf element, and", + "ScreenView reads it there.", + "", + "\ud83d\udd34 CHECKED BEFORE THE SWAP, and the map turned out to be a SUBSET rather than", + "the answer. Over main_menu, extras, press_start and title:", + "", + " 15 the map called additive AND the disc agrees", + " 0 the map called additive and the disc does not <- no contradictions", + " 17 the disc calls additive and the map did not", + "", + "So nothing transcribed was wrong; it was incomplete, and was being read as", + "complete. The 17 include `pteff03`/`pteff03a` -- the sweep LEAVES, which are", + "what `draw_leaf_for` actually puts on screen while the map listed their parents", + "`ptloop01`/`ptloop02` -- and TWELVE on `title`, where this map was deliberately", + "empty and the port therefore drew every title effect alpha-over.", + "", + "A name-keyed map can only answer for a screen somebody drove the game to. That", + "is what made the Japanese menus an open question (BLOCKED.md H6): the port drew", + "main_menu additive and main_menu_jp alpha-over, asserting by omission that the", + "JP build blends differently. The bit is on the disc for every screen at once, so", + "that asymmetry is now answered statically and H6 needs no capture." + ], + "leaf_clock": { + "title": { + "start_units": null, + "rate": null + } + }, + "leaf_clock_why": [ + "The leaf's clock relative to its screen's, as an origin and a rate.", + "", + "\ud83d\udd34 BOTH null ON PURPOSE. null means the leaf runs on the screen clock --", + "which is what the port has always done, and which is ITSELF an unmeasured", + "assumption (offset 0, rate 1) that the Decoder's capture contradicts.", + "", + "F6: a human reports the title's light sweep starts earlier in the port than", + "in the game, and separately that the port may be running it FASTER --", + "flagged by them as an impression, not a judgement to lean on. The capture", + "points the same way from a different direction: the game's leaf t=0 is its", + "first drawn frame, 40 frames after the title's first element, and its rate", + "measures well below the title's.", + "", + "\u26a0\ufe0f NOT FILLED IN WITH AN ESTIMATE. F1 established the rule the hard way: an", + "invented constant here is indistinguishable from a measured one later. The", + "figures come from the Decoder expressed in TITLE units, with the title's own", + "rate stated rather than assumed -- their unit 4 flags that rate as an", + "assumption, and it is load-bearing for both numbers.", + "", + "To adopt: set start_units and rate. Nothing else changes." + ] } diff --git a/port/scripts/boot.gd b/port/scripts/boot.gd index f15e02a3..3b31a37d 100644 --- a/port/scripts/boot.gd +++ b/port/scripts/boot.gd @@ -305,6 +305,10 @@ func _ready() -> void: 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", []) + # F6: the leaf's clock relative to its screen's. Absent or null -> the leaf + # runs on the screen clock, which is what this port has always done and is + # itself an unmeasured assumption (offset 0, rate 1). See ScreenView.leaf_clock. + _leaf_clock = {} if rendering == null else rendering.get("leaf_clock", {}) # `additive_elements` is DELETED from authored/rendering.json -- the blend is # decoded now (`T8aD +0x04` bit 0x02) and the exporter emits `blend_additive` # per element, which `ScreenView._draw` reads directly. Nothing to assign, @@ -337,6 +341,9 @@ func _ready() -> void: view.loop_phase_units = _loop_phase view.draw_leaf_for = _draw_leaf_for view.loop_leaf = _loop_leaf_screens.has(name) + var lc: Dictionary = _leaf_clock.get(name, {}) + view.leaf_start_units = float(lc.get("start_units", -1.0)) if lc.get("start_units") != null else -1.0 + view.leaf_rate = float(lc.get("rate", -1.0)) if lc.get("rate") != null else -1.0 view.focused_id = _force_focus if not view.load_screen(export_tree, name): push_error(export_tree.error) @@ -463,6 +470,9 @@ var _looping: Dictionary = {} ## `authored/rendering.json` `draw_leaf_for`. var _draw_leaf_for: Array = [] ## `authored/rendering.json` `additive_elements` -- measured off the running game. +## `authored/rendering.json` `leaf_clock`: screen -> {start_units, rate}. +var _leaf_clock: Dictionary = {} + ## `authored/rendering.json` `loop_leaf_on_screens`. var _loop_leaf_screens: Array = [] var _script: PackedStringArray = PackedStringArray() @@ -701,6 +711,9 @@ func _advance() -> void: view.loop_phase_units = _loop_phase view.draw_leaf_for = _draw_leaf_for view.loop_leaf = _loop_leaf_screens.has(name) + var lc: Dictionary = _leaf_clock.get(name, {}) + view.leaf_start_units = float(lc.get("start_units", -1.0)) if lc.get("start_units") != null else -1.0 + view.leaf_rate = float(lc.get("rate", -1.0)) if lc.get("rate") != null else -1.0 if not view.load_screen(view.tree, name): push_error(view.tree.error) get_tree().quit(2) @@ -1100,6 +1113,9 @@ func _menu_arrive() -> void: view.loop_phase_units = _loop_phase view.draw_leaf_for = _draw_leaf_for view.loop_leaf = _loop_leaf_screens.has(name) + var lc: Dictionary = _leaf_clock.get(name, {}) + view.leaf_start_units = float(lc.get("start_units", -1.0)) if lc.get("start_units") != null else -1.0 + view.leaf_rate = float(lc.get("rate", -1.0)) if lc.get("rate") != null else -1.0 if not view.load_screen(view.tree, name): push_error(view.tree.error) get_tree().quit(2) diff --git a/port/scripts/screen_view.gd b/port/scripts/screen_view.gd index 5ec2c657..cb48f97f 100644 --- a/port/scripts/screen_view.gd +++ b/port/scripts/screen_view.gd @@ -184,6 +184,39 @@ var loop_leaf := false ## pose, the leaf is placed at whatever phase is being tested. var leaf_time_units: float = -1.0 +## The leaf's clock relative to the SCREEN's, as an origin and a rate. +## +## 🔴 **BOTH UNSET (-1.0) AND THE LEAF THEREFORE RUNS ON THE SCREEN CLOCK, +## EXACTLY AS BEFORE.** F6: the human reports the title's light sweep starts +## earlier here than in the game, and the Decoder's capture puts the game's leaf +## `t=0` at its first drawn frame, 40 frames after the title's first element, at a +## rate that is not the title's. The port has neither an origin nor a rate for the +## leaf -- it simply passes `time_units` through, which is an assumption (offset 0, +## rate 1) that nobody measured and that the capture now contradicts. +## +## ⚠️ **No placeholder.** F1 established the rule the hard way: an invented +## constant here is indistinguishable from a measured one later. These stay unset +## until the Decoder's figures land, and `authored/rendering.json` carries them as +## data with their provenance when they do. +## +## `leaf_time_units` above is a different thing -- an ABSOLUTE override for the +## `--leaf-time` diagnostic, which pins a pose and ignores both of these. +var leaf_start_units: float = -1.0 +var leaf_rate: float = -1.0 + + +## Screen units -> leaf units. Identity while unmeasured. +## +## The clamp at 0 matters and is not arbitrary: at leaf `t=0` the quad's rotated +## AABB only just touches the viewport -- 886 px wide against a 399 px sprite at +## 30 deg, its right edge about 6 px inside the frame -- so parking at 0 before the +## start is visually indistinguishable from not drawing, without disturbing the +## parent-fallback path that `_draw_leaf` returns into. +func leaf_clock(screen_units: float) -> float: + if leaf_start_units < 0.0 or leaf_rate <= 0.0: + return screen_units + return maxf(0.0, (screen_units - leaf_start_units) * leaf_rate) + ## 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 @@ -681,7 +714,7 @@ func _draw_leaf(element: Dictionary) -> bool: # cannot tell "loops at 600" from "runs once for 600 and stops". var was := holding holding = false - var t := leaf_time_units if leaf_time_units >= 0.0 else time_units + var t := leaf_time_units if leaf_time_units >= 0.0 else leaf_clock(time_units) if loop_leaf: var span := 0.0 for k: Dictionary in fe.get("keyframes", []):