port: P5 end to end -- and the port's title never says PRESS (A)

The P5 gate walk starts on a screen. This runs the whole objective instead, and
it is the only thing that would have found what it found:

  xvfb-run -a godot --path port -- --boot --play \
    --script=accept,down,down,down,down,accept,cancel,cancel --shots=/tmp/e2e

publisher wordmark -> developer logos -> ADV (151.9 s) -> title -> (A) -> main
menu -> navigate -> (A) -> EXTRAS -> (B) with focus restored to ptbtn05 -> (B)
-> title. 166.76 s, exit 0, nine frames. Shared as 1788003274-e68367e787d5.

THE PORT'S TITLE DOES NOT TELL THE PLAYER TO PRESS (A). The boot's last step is
`title` = GP_TITLE build 4, and build 4 has NO `PRESS (A) BUTTON` plate. P5 has
just made (A) the only way off that screen.

Not a guess about the art -- both states are captured off the running game and
differ by exactly that plate (live-title-build4-no-plate.png vs
live-title-press-a.png), and the plate is ALREADY EXPORTED as `press_start`,
build 2, sitting in export/screens/title/ unused by anything.

RECORDED, NOT FIXED, and the distinction is the point. This is P3's gate that
P5 exposed, and fixing it needs two things the port does not have:

  * WHICH state an idle post-boot title shows -- build 4 alone, build 4 with the
    plate over it, or build 4 THEN the plate after a delay -- is BEHAVIOURAL.
    The game demonstrably has both states and nothing says which follows the
    intro. The port has no oracle for a sequence; that is the Decoder's.
  * showing it means DRAWING TWO BUILDS AT ONCE, which this port has never done
    -- every mode loads exactly one screen. That is a change to ScreenView, not
    a line in flow.json, and it is not being smuggled in under a navigation
    milestone on the strength of "it looks more right".

Filed in BLOCKED.md. P5's gate is (A) into a submenu and (B) back; both work.

Two smaller things the same run found, both fixed:

  * the boot step's `why` still said "nothing takes the title's place until P5
    gives it somewhere to go". P5 has. Now says what is true: `--boot` STOPS on
    the title (a boot that ends by fading to black looks like a crash) and
    `--play` HANDS THE HELD TITLE OVER -- the stop is not a bug and the handover
    is not another boot step.
  * an empty focus printed as a line that trailed off, reading like a value had
    gone missing rather than like there is none. The title is a screen with no
    `buttons` that still takes (A), so it now prints
    "(none -- this screen has no focusable item)".

Also confirmed: entering a submenu directly (`--menu=extras`) and pressing (B)
enters the parent at its AUTHORED initial focus, not a restored one. There is no
history to restore and MenuFlow.cancel only claims a restored focus when the
stack agrees about where it is going.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CtmUw5N5LJaMW1Njb8Ziey
This commit is contained in:
Sylpheed port agent
2026-08-29 11:34:52 +00:00
parent 1defbe0a32
commit 76979c799c
4 changed files with 95 additions and 5 deletions

View File

@@ -342,7 +342,7 @@ func _menu_enter(name: String, fresh: bool) -> void:
_menu.enter(name, view.screen.get("buttons", []))
view.focused_id = _menu.focus()
view.queue_redraw()
print(" menu on %s, focus %s" % [name, view.focused_id])
print(" menu on %s, focus %s" % [name, _focus_label(view.focused_id)])
if not _script.is_empty() and not _script_started:
_script_started = true
_run_script()
@@ -366,7 +366,7 @@ func _menu_arrive() -> void:
_menu.stack[_menu.stack.size() - 1]["focus"] = String(action["restore_focus"])
view.focused_id = _menu.focus()
view.queue_redraw()
print(" menu on %s, focus restored to %s" % [name, view.focused_id])
print(" menu on %s, focus restored to %s" % [name, _focus_label(view.focused_id)])
else:
_menu_enter(name, true)
@@ -393,6 +393,14 @@ func _has_display(flag: String) -> bool:
return false
## How a focus reads in the log. The title has no focusable item at all -- it is
## a screen with no `buttons` that still takes (A) -- and an empty string there
## printed as a line that trailed off, which reads like the value went missing
## rather than like there is none.
static func _focus_label(id: String) -> String:
return id if id != "" else "(none -- this screen has no focusable item)"
func _capture(path: String) -> void:
# Two frames: the first is the one this callback is still inside of.
await RenderingServer.frame_post_draw
@@ -483,7 +491,7 @@ func _run_script() -> void:
return
await _shoot("%02d_%s" % [i + 1, token])
print("script complete after %.2f s on %s, focus %s"
% [_elapsed, _menu.current(), view.focused_id])
% [_elapsed, _menu.current(), _focus_label(view.focused_id)])
get_tree().quit(0)
@@ -530,4 +538,4 @@ func _shoot(label: String) -> void:
push_error("cannot write %s" % tmp)
return
DirAccess.rename_absolute(tmp, path)
print(" shot %s (%s, focus %s)" % [path, _menu.current(), view.focused_id])
print(" shot %s (%s, focus %s)" % [path, _menu.current(), _focus_label(view.focused_id)])