Commit Graph

4 Commits

Author SHA1 Message Date
Sylpheed port agent
ca7dd583d5 port: the counter-example I kept asking for was in a file I wrote
For several iterations I said the MISSION-SELECT-versus-top-item ambiguity needed
a screen whose opening item is not its first, and that none was known. The Decoder
found one and reported it had been sitting unconnected in their corpus.

It is in mine too, and I authored it. authored/flow.json under
main_menu/buttons/ptbtn01 has read since eef45ec on 2026-08-29: 'MEASURED
destination (EASY/NORMAL/HARD/BACK, opening on NORMAL, then SELECT DATA)'.
DIFFICULTY opens on the second of four. So 'a screen opens on its first item' is
refuted as a general description of this game, and on EXTRAS, TUTORIAL and OPTIONS
the named item and the top item coincide by accident.

Worse than an index failing to amplify: my extras/initial_focus_why framed the
ambiguity as conditional -- 'it matters IF another screen is ever authored' -- in
the same file that already recorded such a screen. Future tense over a fact twelve
keys away. Corrected to name DIFFICULTY concretely.

MenuFlow.initial_focus's buttons[0] fallback is now documented as a repair for
broken data rather than a default, and that is measured rather than fastidious: if
a screen reaches that line silently the port shows a top-item default for a game
that does not always have one. No authored value moves -- DIFFICULTY is not a
GP_TITLE build and EXTRAS keeps ptbtn11, correct under either reading. Walk re-run
unchanged.

It does not settle the question, which is about reset rather than opening. That
needs the cursor moved inside DIFFICULTY, left and re-entered, and its forward
path crashes the guest at SELECT DATA so the run must go back rather than on.

No checker either of us has built would have caught this. Every instrument here
verifies that a claim matches a value; nothing detects that an answer already
written down is not being connected to the question it answers -- and mine had
both halves in one file.

It also makes the previous iteration's restraint look better: declining to promote
'4/4 submenus reset' to a rule was argued from the principle that a generalisation
should not pre-decide the next screen, and the next screen turns out to be one the
generalisation would have got wrong.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N7FiFFFwbvG2uxdcEh8HyF
2026-08-31 01:37:30 +00:00
Sylpheed port agent
6e3347a338 port: the main menu remembers its cursor -- a measured P5 defect, fixed and scoped
Measured by the Decoder today: (B) from the menu to the title and (A) back
returns to the item you left, not to a default; their control passed first, two
delivery-confirmed DOWNs moving the cursor exactly two items before the round
trip. The port reset to initial_focus on every entry, so a player who moved to
EXTRAS, pressed (B) then (A) landed back on NEW GAME. MenuFlow.enter() now
consults opening_focus(), and a new set_focus() writes the memory.

set_focus() exists because two call sites set focus -- a cursor move and (B)'s
restore -- and a memory updated at only one of them is right until the player
uses the other.

focus_persists is true on main_menu and nowhere else, and the scope is the
authored part. wrap generalised because it was measured on two screens; this was
measured on one. Here that is stronger than a preference: extras opens on MISSION
SELECT as a MEASURED initial focus, so a menu-wide memory would have silently
replaced a measured value with a derived one.

Both halves are in one artifact, because a one-sided test passes a port that
quietly generalised: the menu returns to ptbtn05 after the round trip, and extras
opens on ptbtn11 both times despite being left on ptbtn12. contract-check asserts
the pair -- on where measured, off elsewhere -- and fails its known negative.
Eleven checks.

Not assumed: whether the memory survives a reboot, or whether any other screen
has it. Their reach is one boot, one round trip, one direction. The finding also
reframes this morning's initial-focus warning without settling it -- if focus
persists, a reading not taken on a fresh boot's first entry is measuring history.
NEW GAME stays authored, on its own reasoning.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N7FiFFFwbvG2uxdcEh8HyF
2026-08-30 21:36:43 +00:00
Sylpheed port agent
d45da73bba 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
2026-08-29 13:12:41 +00:00
Sylpheed port agent
eef45ecfd6 port: P5 -- the menus navigate, and the focus ring is drawn wrong on purpose
P5's gate is "a human clicks through it". The artifact is a scripted walk that
proves the wiring rather than the intent -- up (wraps 01->05), five down, (A)
into EXTRAS, down, (B) back, landing on the main menu with focus RESTORED to
EXTRAS, ten PNGs one per settled step:

  xvfb-run -a godot --path port -- --menu \
    --script=up,down,down,down,down,down,accept,down,cancel --shots=/tmp/p5

--script posts InputEventAction through Input.parse_input_event so the presses
arrive at _unhandled_input exactly as a d-pad's would. Calling MenuFlow directly
would have been shorter and would have proved nothing: the wiring between a
press and the cursor is the part most likely to be broken, and a direct call is
exactly the part that skips it.

Derived vs authored, which P5 is the easiest place to blur:

  * DERIVED -- the ORDER of the items, from each screen file's `buttons`, which
    the exporter already fills from button-role elements sorted by resting Y.
  * AUTHORED -- destinations, initial focus, what (B) does, and left/right being
    a no-op. All measured off the running game (HANDOFF Q4/Q5) or chosen, none
    on the disc, all in authored/flow.json with a why.

Four of five main-menu destinations are `goto: null` with a `blocked` note. That
is a MILESTONE BOUNDARY, not an unknown -- DIFFICULTY, the save list, the lesson
list and OPTIONS were all measured and live in archives this export does not
carry. `blocked` and `none` are kept apart so nobody later "discovers" the gap.

--headless CANNOT DRAW, and the port hung instead of saying so.

Measured, not assumed: under --headless Godot's dummy renderer never emits
RenderingServer.frame_post_draw, so every capture path awaited it forever --
--capture since P1, --film since P3, --shots as of now. With stdout block-
buffered the observable behaviour was SILENCE, FOREVER, which in a loop reads as
a job still working. Isolated by `--quit` (prints, exits 0) vs `--capture` (zero
bytes, killed at 40 s). Now those three flags refuse at STARTUP naming the
xvfb-run line that works, and --script no longer waits for a frame it is not
going to photograph -- so headless walks the menus in 4.5 s as a cheap
regression check needing no X server.

REFUTATION ATTEMPT, against the Decoder's 7eeae30 point 2 ("the oracle confirms
the game renders the ring's rotation"). Aimed there because PROTOCOL says to aim
at a claim the port is about to build on that rests on an estimator whose own
control the Decoder reported as +/-19.8 deg. IT SURVIVES, more strongly than
claimed.

Both captures draw the SAME sprite (ptbtneff01) 240 px apart, so "is it drawn
rotated" becomes "are these two crops one image at a different angle" -- no crop
offset needed and no reference to our own renderer. 360-bin angular luminance
profile over the annulus, circularly cross-correlated. Two controls first: known
rotations 0/30/90/150/210/270/330 recovered with 0 deg error, and a ring-free
patch of the same capture peaks at 0.369, so the estimator does not manufacture
matches. Then: A vs B 134 deg (corr 0.968), sprite vs A 76 deg, sprite vs B
210 deg -- and 210-76 = 134, which nothing in the method forced.

So 0 deg is NOT A POSE THE GAME SHOWS, and screen_view.gd draws the ring at
0 deg. That is now stated in the code as known-wrong rather than suspected. The
port did NOT start spinning it: the period has two unknowns and both are the
Decoder's -- the second keyframe is untimed, and "groups hold" predicts a stop
at 360 = 0 which contradicts both captures. Two frames of one focused button a
known time apart settle it. Filed in BLOCKED.md and asked over the channel.

BLOCKED.md's staleness check was half a check. It tested whether that page is
stale relative to HANDOFF; it cannot see the other direction, and the other
direction is what happened -- 7eeae30 lands 27 minutes AFTER HANDOFF was last
written and answers a question HANDOFF still lists as open. Added the missing
half: `git log --oneline 9ca1eb5..HEAD -- docs/re/`.

Also recorded, since the two were nearly confused: the ring's annulus centroid
lands within ~0.4 px of its design position under a ZERO crop offset, which
corroborates ORACLE-CAPTURES' "1279x675, top-left aligned" on a feature nobody
chose for the purpose. The earlier "text bands at design y + 23" is an offset
WITHIN the button sprite, not a crop offset.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CtmUw5N5LJaMW1Njb8Ziey
2026-08-29 11:27:05 +00:00