port: P7 -- the new-game intro plays, and the two screens it skips are named out loud
S00A has been exported since P4; what P7 needed was something to play it and a
defined place to land. Both are here, and the interesting part is the gap.
The real chain is NEW GAME -> DIFFICULTY -> SELECT DATA -> (A) on a save slot ->
~4.5 s -> S00A. DIFFICULTY and SELECT DATA are MEASURED destinations that are not
GP_TITLE builds, so no screen file exists to go to. The port jumps from NEW GAME
to the one thing in that chain it has -- and the whole design is about not
letting that read as a sequence:
* MenuFlow.accept returns a new kind, `video`, rather than folding this into
`blocked`, because the caller has to announce the skip and a distinct kind is
what forces it to;
* the runtime prints the skipped screens by name on every run;
* flow.json carries `skipped_chain` as DATA, so what is missing lives beside
the decision instead of inside a GDScript string.
After the movie the port returns to the title. Authored, and it has to be: the
game goes into mission 1 and gameplay is out of scope. The ~4.5 s before the
movie is left EMPTY on purpose -- GP_TITLE does carry a loading screen and 4.5 s
is about the right shape for one, which is exactly why that belongs in BLOCKED.md
and not in flow.json.
`--script`'s 20 s per-step timeout would have killed every movie run at step 1.
Raising the constant would have been wrong the other way: a movie stuck at frame
0 would then hang the job, and a job that waits is worse than one that fails. The
test is now LIVENESS -- while get_stream_position() advances the deadline moves
with it, and a stalled movie still trips the same 20 s.
Found while looking: GP_TITLE's four unnamed builds (entries 0, 1, 12, 15) are
LOADING screens -- every element in all four is pgloading_*, and LOADING is one of
the three names the decoder read out of the title part's state function. NOT
renamed here: which member of each pair is which locale is an inference, and a
name stops being questioned once written. Handed over.
One of them is a second casualty of the rest.t problem, and a worse one:
pgloading_eff00.prm rests OPAQUE BLACK at t=38, so anything drawing that screen
at its declared rest paints a black rectangle over all of it. The title's case
only dimmed a frame.
REFUTATION, attempted and SURVIVED: HANDOFF says "exactly the six screen builds
carry the black .prm quad while the six overlays do not". Counting bundles with a
full-screen black primitive gives 8 and 4 -- build_12/15 carry one too. But
theirs runs black -> held -> clear where the transition quad runs black -> clear
-> black, so read strictly as "the quad whose group is the transition" the claim
holds. Recorded anyway: there are two kinds, and the naive census over-counts.
Gate: NEW GAME -> S00A plays 93.75 s against a declared 93.9 -> title, with
98.453 s recorded off the Master bus. What that does NOT show is that S00A's own
audio is in the mix -- bed and movie were not separated in this run, and the
write-up says so.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WM5XL4HfrHuxz8RiMWdCMC
This commit is contained in:
@@ -369,9 +369,27 @@ func _video_finished() -> void:
|
||||
print(" video ended at %.2f s" % _elapsed)
|
||||
_player.queue_free()
|
||||
_player = null
|
||||
# A movie the MENU started (P7) returns to an authored screen; a movie the
|
||||
# BOOT started advances the sequence. Two different owners, and conflating
|
||||
# them walked the boot sequencer off the end of its own array.
|
||||
if not _video_then.is_empty():
|
||||
var after := _video_then
|
||||
_video_then = {}
|
||||
var goto := String(after.get("goto", ""))
|
||||
print(" -> %s (authored: %s)" % [goto, String(after.get("kind", "authored"))])
|
||||
_menu_activate({"kind": "enter", "goto": goto, "label": "after the movie"})
|
||||
# `_menu_activate` only arms the transition; the screen it is leaving has
|
||||
# already gone, so arrive immediately rather than fading out a movie.
|
||||
if _pending != null:
|
||||
_menu_arrive()
|
||||
return
|
||||
_advance()
|
||||
|
||||
|
||||
## Where a menu-started movie goes when it ends. Empty for a boot-started one.
|
||||
var _video_then: Dictionary = {}
|
||||
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
# HANDOFF Q9, measured: one (A) press skips a movie -- the title was reached
|
||||
# at 57 s against a 193 s baseline.
|
||||
@@ -433,6 +451,17 @@ func _menu_activate(action: Dictionary, cue: String = "") -> void:
|
||||
print(" (%s) -> %s" % [action.get("label", ""), action["goto"]])
|
||||
_pending = action
|
||||
view.holding = false
|
||||
"video":
|
||||
# P7. Announce the gap before opening it. `skipped` names the
|
||||
# MEASURED screens this export does not carry, and printing them is
|
||||
# not a nicety: the port is about to show a sequence the game does
|
||||
# not have, and the only thing that keeps that honest is saying so.
|
||||
var skipped: Array = action.get("skipped", [])
|
||||
if not skipped.is_empty():
|
||||
print(" (%s) -> the real chain is %s, then the movie. Neither screen is in this export."
|
||||
% [action.get("label", ""), " -> ".join(PackedStringArray(skipped))])
|
||||
_video_then = action.get("after", {})
|
||||
_play_video(String(action["video"]), bool(action.get("skippable", false)))
|
||||
"blocked":
|
||||
# A real, measured destination that is not in this export. Say which
|
||||
# -- silence here would read as a dead button.
|
||||
@@ -634,8 +663,21 @@ func _press(action: String) -> void:
|
||||
## reached its own hold. Shooting before that would photograph a fade.
|
||||
func _script_settled(what: String) -> bool:
|
||||
var deadline := _elapsed + SCRIPT_STEP_TIMEOUT
|
||||
var playhead := -1.0
|
||||
while _pending != null or _player != null or not view.holding \
|
||||
or view.time_units < view.settle_time():
|
||||
# A movie is not a screen that failed to settle: `S00A` runs 93.9 s and
|
||||
# would trip a 20 s timeout every time (P7). But "wait as long as it
|
||||
# takes" would turn a movie stuck at frame 0 into a job that hangs
|
||||
# forever, which is the worse failure -- it does not fail, it waits.
|
||||
#
|
||||
# So the test is LIVENESS, not duration: while the playhead advances the
|
||||
# deadline moves with it, and a stalled movie still trips the same 20 s.
|
||||
if _player != null:
|
||||
var now := _player.get_stream_position()
|
||||
if now > playhead:
|
||||
playhead = now
|
||||
deadline = _elapsed + SCRIPT_STEP_TIMEOUT
|
||||
if _elapsed > deadline:
|
||||
# Stop the run. Carrying on would write a whole filmstrip of the
|
||||
# screen that got stuck and call it a walk through the menus.
|
||||
|
||||
Reference in New Issue
Block a user