port: fix two capture bugs, and the second was hiding a missing PRESS (A) plate

1. --capture with --script photographed before the first press (t=0.133 s, 10 of
   16 elements transparent). Two runs differing by two presses came out
   bit-identical and I read it as 'runtime focus never changes'. Deferred to the
   end of the script; verified max 235 and t=82 units.

2. --boot --capture= wrote NO FILE: _finish_boot() is reachable only from the
   overlay-quit branch, but line 412 quit first because _overlay_spec is cleared
   when the overlay is raised. Pre-existing, confirmed by stashing. Fixed by also
   requiring _overlay_quit_at < 0.0.

3. The artifact that now exists shows the boot's end frame is bit-identical to
   the title alone -- no plate. ptbtn00 is opaque for 8 units (236-244) and the
   boot captures at 246.54, because it waits for build 4 to finish fading at
   t=261. Both halves of that are sound and they are incompatible. NOT changed;
   filed, since what settles it is what the game does after t=244.

Defect 3 was invisible while defect 2 existed: a capture flag that writes nothing
cannot show a missing element.

Also records that runtime focus is FINE -- my contrary reading came from 410
files whose names did not match the flag I passed.

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-29 23:59:54 +00:00
parent fce921d5c9
commit 09188ccbb2
3 changed files with 127 additions and 5 deletions

View File

@@ -512,3 +512,30 @@ shipped game would do. That reasoning is what makes an invented answer feel safe
⚠️ Related but separate, and already authored with a `why`: the bed **loops by
restart**, seam and all (`authored/audio.json`, `loop_why`). That is a known-ugly
deliberate choice pending a loop point, not this question.
---
## Does the `PRESS Ⓐ` plate stay up, or blink once and go?
*Derived from HANDOFF `9ca1eb5`. Raised 2026-08-29 by the port.*
`ptbtn00`'s group makes the plate opaque for **8 units only** — alpha 0 until
t=214, 255 at t=236 and t=238, back to 0 by t=244 — and its group then ends. So
in the port the plate flashes for 0.133 s and is gone, and the boot's end-state
capture (taken at t=246.54, once build 4 has finished fading up at t=261) contains
**no plate at all**.
🔴 The two states cannot share a frame: the plate's window closes 17 units before
the title stops presenting. The port captures the title, by an earlier and sound
decision it is not reversing.
What would settle it: **a capture of the real title held for several seconds after
the plate first appears.** Three outcomes, all decisive — the plate stays up; it
pulses on a period (`authored/timing.json` already carries a *speculative*
`looping_focus_records` entry for `press_start/ptbtn00` at 120 units, which is
what a pulse would need); or it genuinely blinks once and vanishes, in which case
the port is right and this closes ✅.
⚠️ Note the port already draws a looping focus record for this element. If the
answer is "blinks once", **that entry is wrong and should be deleted**, not kept
as harmless — it is the only thing making the plate reappear at all.

View File

@@ -5754,3 +5754,77 @@ silently ran 39 % long would have put every such observation at the wrong instan
and the bed-loop result above would have been the first casualty. It survived only
because the seam was read off the **recording's** clock, which the bed's own known
length calibrates.
## Two harness bugs, and the defect the second one was hiding
### 1. `--capture` with `--script` photographed the frame *before* the script
`--capture` fired in `_ready` and quit. With `--script` that is **before the
first press**: at t=0.133 s, with 10 of 16 elements still transparent. Two runs
differing by two `down` presses came out **bit-identical**, and I read that as
*"runtime focus never changes"* — a confident wrong finding that `--shots`
contradicted within a minute.
Fixed: with a script, the capture defers to the end of the run, through the same
`_capture_to` member the boot path already used. Verified — the two runs now
differ at max 235, and the capture lands at t=82 units instead of t=8.
### 2. `--boot --capture=` wrote no file at all
`_finish_boot()` is reachable only from the overlay-quit branch, but the boot
quit first: line 412 exits when `_film == "" and _overlay_spec.is_empty()`, and
`_overlay_spec` is **cleared when the overlay is raised**. So a plain `--boot`
ended at 10.99 s, 1.2 s before its own scheduled 12.21 s, and the capture never
happened.
Confirmed pre-existing by stashing my changes and re-running. Fixed by also
requiring `_overlay_quit_at < 0.0`. The boot now runs to 12.19 s, prints *"boot
ends on title + press_start"*, and writes the file.
⚠️ The flag has a doc comment explaining that it exists so the boot has an
artifact of its own instead of a 600-PNG filmstrip. **It has been producing
nothing.** A flag that silently writes no file is the same failure as
`ScreenView.skipped` and `stop_bed`: provided, plausible, and never exercised.
### 3. 🔴 And the artifact it now produces shows the plate is missing
The boot's end frame is **bit-identical** to `--screen=title` at the same instant
— max difference 0. The `PRESS Ⓐ` plate is not in the port's end state.
`ptbtn00`'s own fade explains it exactly:
| t | 0 | 214 | **236** | **238** | 244 |
|---|---|---|---|---|---|
| alpha | 0 | 0 | **255** | **255** | 0 |
The plate is visible for **8 units — 0.133 s** — and the boot captures at
**t=246.54**, two and a half units after it has gone.
That is not an accident of frame timing; the code chose it. `_overlay_quit_at`
takes `max(view.settle_time(), overlay.settle_time())`, and its comment says why:
*"the plate arrives at t=238 and build 4 is still fading up from black until
t=261 … quitting when the plate lands photographs a title that has not finished
presenting."* Both halves are true, and together they mean **the two states
cannot both be in one frame.** The port picked the title, and the consequence —
that the plate is in no artifact at all — was never written down.
⚠️ I am **not** moving the trigger. The earlier reasoning is sound and the
measurement it protects is real; picking the other instant would trade a missing
plate for a visibly dark title, which is the swap that was already made once and
regretted. What settles it is what the *game* does with the plate after t=244 —
filed.
📌 Worth naming: defect 3 was **invisible while defect 2 existed**. A capture flag
that writes nothing cannot show you a missing element. The broken tool was hiding
the thing the tool was built to find, and neither was noticed because the absence
of a file looks exactly like not having run the command.
### What was *not* wrong, and how I nearly recorded that it was
Runtime focus works. Per-step `--shots` across `down,down` differ at max 232233,
with the differing boxes tracking down the button column. My contrary reading came
from analysing 410 `f_NNN` files after asking for `--shots=…/s` — **the filenames
did not match the flag I passed, and that was visible in my first `ls`.** A
verbatim re-run produces six correctly-named per-step shots. I do not know where
those 410 files came from, which is itself the point: I drew a conclusion from a
file set whose provenance I never checked.

View File

@@ -274,11 +274,28 @@ func _ready() -> void:
overlay.time_units = float(args["time"]) * overlay.units_per_second
overlay.queue_redraw()
# `--boot --capture=` is deferred to the end of the sequence (`_finish_boot`);
# in every other mode the frame worth having is this one.
# `--boot --capture=` is deferred to the end of the sequence (`_finish_boot`),
# and so is `--script --capture=`.
#
# 🔴 The second half of that was missing, and the comment here used to assert
# the opposite -- "in every other mode the frame worth having is this one".
# With `--script` it is emphatically not: the capture fired **before the
# first press**, at t=0.133 s, with 10 of 16 elements still transparent, and
# then quit. Two runs differing by two `down` presses came out BIT-IDENTICAL,
# because neither had run its script when it was photographed.
#
# That is not a harmless default. It is a well-formed answer to a different
# question, and it produced a confident wrong finding -- "runtime focus never
# changes" -- that `--shots` immediately contradicted. A flag combination
# that silently photographs the wrong instant is worse than one that errors.
if args.has("capture") and _capture_to == "":
await _capture(args["capture"])
get_tree().quit(0)
if not _script.is_empty():
# Defer to the end of the script, through the SAME member the boot
# path already uses, rather than adding a second mechanism.
_capture_to = String(args["capture"])
else:
await _capture(args["capture"])
get_tree().quit(0)
var _frozen := false
@@ -392,7 +409,7 @@ func _process(delta: float) -> void:
# than one that exits.
if _play:
_menu_enter(String(_sequence[_step].get("screen", "")), true)
elif _film == "" and _overlay_spec.is_empty():
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:
# 🔴 THE BLACK HOLD, which this port had never implemented. A transition
@@ -836,6 +853,10 @@ func _run_script() -> void:
await _shoot("%02d_%s" % [i + 1, token])
print("script complete after %.2f s on %s, focus %s"
% [_elapsed, _menu.current(), _focus_label(view.focused_id)])
# The frame worth having from a scripted run is the one the script ARRIVED
# at, not the one it started from. See the note beside the early capture.
if _capture_to != "":
await _capture(_capture_to)
get_tree().quit(0)