port: delete exit_ramp_units, invert the format's own rule, and guard a scale-0 leaf
FOUR THINGS, and the first is what MISSION section 3 calls the measure of
progress.
DELETED `exit_ramp_units` AND `exit_ramp_seconds`. They were authored because the
disc had no time slot on a group's final keyframe, so the ramp into it was the
one unknown duration per screen. Under the corrected record layout that keyframe
does not exist -- a group is an 8-byte header then frames x {u32 time; 36-byte
pose} and every pose is timed. VERIFIED DEAD BEFORE DELETING: setting it to 9999
(166 s) moved the boot's transitions by 0.04 s, which is wall-clock jitter, and
both uses in ScreenView are gated on a condition that no longer fires on any of
the export's 866 keyframes.
INVERTED THE FORMAT'S OWN RULE. `check.rs` enforced "the final keyframe has no
`t`; the disc has no time slot there" and FORMAT.md stated it. Both are now
backwards, and the validator fired 150 times on a re-export. I had not run
`check` between pinning the tag and measuring against the oracle -- the pixel
harness was green while the format validator was failing on every screen with a
multi-keyframe group. A correctness harness does not replace a format one; they
fail at different layers.
GUARDED A SCALE-0 LEAF, which the Decoder hit in its own renderer: its leaf
branch marked the element drawn unconditionally while the blit returned early on
zero scale, so a scale-0 leaf suppressed its parent and blanked the element --
live on all four loading screens. This port did not have the bug only because
authored/rendering.json happens not to list pgloading_loop5. That is an accident
of a gate written for another reason, not a defence, so `_draw_leaf` now reports
whether it drew and `_draw` falls back to the parent.
ISOLATED THE PACING QUESTION rather than leaving it as a suspected regression.
Legacy association: publisher 4.70 agrees, developer 3.92 DIFFERS. Corrected:
publisher 4.26 DIFFERS, developer 3.62 agrees. Both misses are ~0.03 s outside a
composite bound. The association traded which screen is marginally out; it did
not regress the pacing.
Bumped the pin c -> d for the parser and audio changes. Its headline renderer
change does not reach this port: sylpheed-cli builds from the workspace crate.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N7FiFFFwbvG2uxdcEh8HyF
This commit is contained in:
@@ -117,6 +117,13 @@ func _ready() -> void:
|
||||
if args.has("boot"):
|
||||
_capture_to = args.get("capture", "")
|
||||
_film = args.get("film", "")
|
||||
# `--film-interval=` in seconds. Configurable because the fixed 0.25 s could
|
||||
# not resolve the boot's own black hold: the transition's pure-black plateau
|
||||
# is MEASURED at 0.17-0.23 s (authored/timing.json, HANDOFF Q7), which is
|
||||
# shorter than the cadence that was meant to observe it. `verify-dwell` duly
|
||||
# reported two screens as one 93 s span and called it a regression, when the
|
||||
# black frame had simply fallen between samples.
|
||||
_film_interval = maxf(0.01, float(args.get("film-interval", "0.25")))
|
||||
_shots = args.get("shots", "")
|
||||
if args.has("script"):
|
||||
_script = args["script"].split(",", false)
|
||||
@@ -177,7 +184,12 @@ func _ready() -> void:
|
||||
view.units_per_second = float(timing["keyframe_units_per_second"])
|
||||
# The one unknown duration per screen: the ramp into the final untimed
|
||||
# keyframe. Authored, because the disc has no time slot there.
|
||||
view.exit_ramp_units = float(timing["exit_ramp_units"])
|
||||
# `exit_ramp_units` is DELETED from authored/timing.json -- under the corrected
|
||||
# record layout every pose is timed, so there is no untimed final keyframe to
|
||||
# give a synthetic time to. The default below is now unreachable rather than
|
||||
# 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))
|
||||
# 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.
|
||||
@@ -278,6 +290,8 @@ var _step := 0
|
||||
var _film := ""
|
||||
var _film_frame := 0
|
||||
var _film_next := 0.0
|
||||
## Seconds between `--film` frames. See `--film-interval`.
|
||||
var _film_interval := 0.25
|
||||
var _elapsed := 0.0
|
||||
var _boot_done := false
|
||||
|
||||
@@ -652,7 +666,7 @@ func _film_capture() -> void:
|
||||
var img := viewport.get_texture().get_image()
|
||||
img.save_png("%s_%03d.png" % [_film, _film_frame])
|
||||
_film_frame += 1
|
||||
_film_next += 0.25
|
||||
_film_next += _film_interval
|
||||
|
||||
|
||||
# Godot passes everything after `--` through untouched; take `--key=value`.
|
||||
|
||||
@@ -404,7 +404,9 @@ static func _rot_of(pose: Dictionary) -> float:
|
||||
## ❔ Every observation behind this has parent alpha 0, so "the leaf wins" and
|
||||
## "the parent is ignored because it draws nothing" are NOT separated. A capture
|
||||
## during t=100...238 would separate them.
|
||||
func _draw_leaf(element: Dictionary) -> void:
|
||||
## Returns whether anything was actually drawn, so the caller can fall back.
|
||||
func _draw_leaf(element: Dictionary) -> bool:
|
||||
var any_drawn := false
|
||||
for fe: Dictionary in element.get("leaf", {}).get("elements", []):
|
||||
var rel: String = fe.get("sprite", "")
|
||||
if rel == "":
|
||||
@@ -422,10 +424,27 @@ func _draw_leaf(element: Dictionary) -> void:
|
||||
holding = false
|
||||
var pose := pose_at(fe, time_units)
|
||||
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
|
||||
# the blit returns early on zero scale, so a scale-0 leaf suppressed its
|
||||
# parent and BLANKED the element -- live on all four loading screens via
|
||||
# `pgloading_loop5`, whose leaf is scale (0, 0).
|
||||
#
|
||||
# ⚠️ This port did not have the bug only because `authored/rendering.json`
|
||||
# happens not to list `pgloading_loop5`. That is an accident of a gate
|
||||
# written for a different reason, not a defence, so the guard is here: a
|
||||
# leaf that would draw nothing reports so, and `_draw` falls back to the
|
||||
# parent rather than losing the element.
|
||||
var scale: Array = pose.get("scale", [100, 100])
|
||||
if int(scale[0]) == 0 or int(scale[1]) == 0:
|
||||
skipped.append("%s (leaf scale 0 -- parent drawn instead)" % fe.get("id", ""))
|
||||
continue
|
||||
var pivot := _vec(fe.get("pivot", [0, 0]))
|
||||
_draw_quad(tex, placement(pose, pivot, tex.get_size()), modulate_of(pose),
|
||||
pivot, _vec(pose.get("pos", [0, 0])), _rot_of(pose))
|
||||
drawn.append(fe.get("id", ""))
|
||||
any_drawn = true
|
||||
return any_drawn
|
||||
|
||||
|
||||
func _draw_focus(element: Dictionary) -> void:
|
||||
@@ -508,8 +527,8 @@ func _draw() -> void:
|
||||
# itself: the parent is a container whose own record has identity scale
|
||||
# and rotation. See `_draw_leaf`.
|
||||
if element.get("leaf_carries_geometry", false) \
|
||||
and draw_leaf_for.has(String(element.get("id", ""))):
|
||||
_draw_leaf(element)
|
||||
and draw_leaf_for.has(String(element.get("id", ""))) \
|
||||
and _draw_leaf(element):
|
||||
continue
|
||||
# A focused button draws its own record instead of its base sprite -- and
|
||||
# so does an element the authored table says always shows it, which is
|
||||
|
||||
Reference in New Issue
Block a user