port: wire flow.json's dwell, assert the three authored values the port hardcodes

Applying the prior from six prior findings to authored/ itself: five keys had no
reader. dwell, ramp, left_right, input_during_transition, stems.

dwell is the one that mattered. Its own text says a measured hold goes there and
a number placed there did nothing -- and two iterations ago I asked the Decoder
for measurements destined for that slot. Wired now, and it stays EMPTY: the
splash dwells are declared on the disc and measured to agree.

I wired it to the wrong branch first and it did nothing, silently -- holding
longer after settle is absorbed because the screen still leaves at exit_time +
black_hold. A dwell must delay the departure. Caught only by testing the control:
+120 units moves the transition 4.46 -> 6.43 s.

ramp, left_right and input_during_transition describe hardcoded behaviour and are
written like switches. Rather than invent the missing implementations, they are
now asserted against the value the port was built for, naming the file -- which
is the distinction left_right's own why claims to make and was not making. The
validator is called, not merely defined.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N7FiFFFwbvG2uxdcEh8HyF
This commit is contained in:
Sylpheed port agent
2026-08-30 03:16:35 +00:00
parent c4d7553e59
commit 141cef4047
2 changed files with 130 additions and 1 deletions

View File

@@ -202,6 +202,9 @@ func _ready() -> void:
# own logic on purpose -- see `looping_focus` there for the census that says
# this cannot be a rule.
_looping = timing.get("looping_focus_records", {})
# Called, not merely defined. A validator nobody invokes is the same defect
# it exists to catch.
_check_authored_invariants(timing)
# 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", [])
@@ -391,6 +394,25 @@ func _process(delta: float) -> void:
#
# `_advance` is CAUSED by the next screen arriving, never scheduled off a
# timer, which is what the draw stream says the game does.
# `authored/flow.json` `dwell` is applied at the EXIT below, not here.
#
# 🔴 I wired it here first and it did nothing, silently -- which is the
# defect it exists to remove, reproduced while removing it. Holding longer
# after settle changes nothing, because the screen still leaves when
# `exit_time() + black_hold` arrives and the extra hold is absorbed. A dwell
# has to delay the DEPARTURE.
#
# It was read NOWHERE for eight milestones. The
# block's own text says "when a capture times the real boot, the extra hold
# per screen goes here" -- and a number placed there did nothing at all. Two
# iterations ago I asked the Decoder for measurements destined for that slot;
# had they arrived, they would have been filed into a value with no reader
# and the boot would have been unchanged, silently.
#
# It stays EMPTY. Nothing is authored into it, because the splash dwells are
# declared on the disc and measured to agree. This wires the slot so the day
# a number belongs there it has an effect, which is the opposite of adopting
# one now.
if view.holding and view.time_units >= view.settle_time():
# The LAST screen in the sequence keeps holding. A screen plays itself
# out because something is taking its place; nothing is taking the
@@ -420,7 +442,8 @@ func _process(delta: float) -> void:
_menu_enter(String(_sequence[_step].get("screen", "")), true)
elif _film == "" and _overlay_spec.is_empty() and _overlay_quit_at < 0.0:
get_tree().quit(0)
elif not view.holding and view.time_units >= view.exit_time() + _black_hold:
elif not view.holding and view.time_units >= view.exit_time() + _black_hold \
+ _dwell_for(String(_sequence[_step].get("screen", ""))):
# 🔴 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
@@ -1165,3 +1188,45 @@ func _looping_for(screen_name: String) -> Dictionary:
if parts.size() == 2 and parts[0] == screen_name:
out[parts[1]] = _looping[key]
return out
## Extra hold for one screen, in units, from `authored/flow.json` `dwell`.
##
## Zero unless a measurement is authored. A value here is an ADDITION to the
## screen's own declared group, not a replacement for it.
func _dwell_for(screen_name: String) -> float:
if _flow == null or not (_flow is Dictionary):
return 0.0
var table: Variant = (_flow as Dictionary).get("dwell", {})
if not (table is Dictionary):
return 0.0
var v: Variant = (table as Dictionary).get(screen_name, 0.0)
return float(v) if (v is float or v is int) else 0.0
## Check the authored values this port CANNOT act on, and fail loudly if one
## changes.
##
## `left_right`, `input_during_transition` and `ramp` are authored with reasons
## and read by nothing -- the behaviour they describe is hardcoded. That is
## defensible for a record and dangerous for a switch, and they are written like
## switches: someone setting `left_right` to "move" would change nothing and get
## no warning.
##
## So rather than invent the missing implementations, the port ASSERTS the value
## it was built against. Changing one now produces an error naming the file
## instead of silence, which is the distinction the `why` for `left_right`
## claims to be making -- "the game ignores it" and "we never wired it" are
## different lines of code -- and which was not actually being made.
func _check_authored_invariants(timing: Dictionary) -> void:
var nav: Variant = (_flow as Dictionary).get("navigation", {}) if _flow is Dictionary else {}
if nav is Dictionary:
var lr := String((nav as Dictionary).get("left_right", "nothing"))
if lr != "nothing":
push_error("authored/flow.json navigation.left_right is \"%s\"; this port implements only \"nothing\"" % lr)
var idt := String((nav as Dictionary).get("input_during_transition", "ignored"))
if idt != "ignored":
push_error("authored/flow.json navigation.input_during_transition is \"%s\"; this port implements only \"ignored\"" % idt)
var ramp := String(timing.get("ramp", "linear"))
if ramp != "linear":
push_error("authored/timing.json ramp is \"%s\"; ScreenView interpolates linearly and has no other mode" % ramp)