Files
Sylpheed/docs/agents/PLAYTEST-2026-09-01.md
MechaCat02 1ad519d3ba agents: the splashes exactly, and stop photographing a moving thing
A human played the port on real hardware for the first time (2026-09-01) and
found four things. Two were port defects, fixed. Two are open and are now both
agents' focus: the PRESS (A) plate arrives late, and the splash fade/blur is
weaker than the game's.

Their verdict on method is the reason this is a brief change and not a ticket:

  "the agents were essentially guessing and trying to copy what one would see,
   but while they did get close it still is not quite right"

Close-but-not-right is the signature of reproducing APPEARANCE instead of
deriving MECHANISM. So the Decoder's focus block asks, in order: is there a
post-process pass at all, what is it, where do its parameters come from -- and
only then what curve. Both routes, dynamic (GPU state, shader constants, render
targets; add logging to Canary, it is theirs read-write) and static (.pe, the
DB, the paks), with each fact labelled by which produced it.

TEMPORAL-VERIFICATION.md is the other half, and it generalises past the
splashes. We have been photographing the game at time t, and t is never the
same twice: emulator speed varies with host load, Canary presents at ~28.1 fps,
the capture path costs a variable 0.1-10.8 s, and a long-lived x11grab stream
degrades and then freezes. The register already carries FOUR refutations of
exactly this shape. The replacement rule: record a film, not a photograph;
align by CONTENT, not by clock, and report the lag as a measurement rather than
minimising it away; prefer ordering, counts, durations and shape over any value
at a wall-clock instant; anchor on an event; report achieved fps against
requested fps.

Also into both briefs: the input set. The port had no joypad binding for (A) or
(B) and nobody noticed for a whole milestone, because --script sends
InputEventAction, which BYPASSES the input map -- so every check asserted the
code below the map and nothing about the map. The Decoder is asked to DECODE
the full set the game reads rather than discover it by pressing buttons; the
Port is told input is verified at the device level or not at all.

And both briefs now point at the R1 register reclassification, because two of
the ten re-opened entries land on this focus: "the declared keyframe timeline
reproduces the captured splash" is 🟡 our-reader, and the rest() pair is open
in BOTH directions -- while the two splashes are the only screens that reach
that fallback.
2026-09-01 17:59:17 +02:00

5.2 KiB
Raw Permalink Blame History

Play-test, 2026-09-01 — a human, a real controller, the port

The first time a person played this port on real hardware. It found four things. Two were fixed on the spot by the human; two are open and are the current focus of both agents.

⚠️ This page is a record of observations, not a mission change. PORT-MISSION.md and the loop briefs carry the objective.

What was found

# finding status
1 Ⓐ and Ⓑ did nothing on the pad. Could not skip the intro, could not open a submenu. fixed by the human — port/scripts/gamepad.gd
2 The left stick moved the cursor far too fast. fixed by the human — latched to one step per deflection
3 The PRESS Ⓐ plate appears too late. 🔴 OPEN
4 The splash fade/blur is wrong — the game's is more pronounced. 🔴 OPEN

1 & 2 — why no check caught them, which matters more than the fixes

--script sends InputEventAction, which BYPASSES the input map.

Every check the port had asserted the code below the input map and nothing about the map itself. The map turned out to have no joypad binding for ui_accept or ui_cancel at all — measured on Godot 4.7.2, not remembered, because the remembered answer was wrong:

ui_accept   key:Enter, key:Kp Enter, key:Space     <- no joypad button at all
ui_cancel   key:Escape                             <- no joypad button at all
ui_up       key:Up, JOYBTN:11, JOYAXIS:1-          <- d-pad AND left stick
ui_down     key:Down, JOYBTN:12, JOYAXIS:1+

Four actions reached the pad and two did not. Ⓐ was dead for the whole of P5 while the unattended walk passed on every iteration.

The same blind spot hid finding 2: an InputEventAction is not an analog axis, so nothing could observe that a stick held at deflection emits an event per jitter, each reporting the action as pressed — one cursor step per jitter.

Now asserted by tools/port/verify-input, with a control that removes each check's own subject. (Its first version inverted all nine assertions when only two depended on the fix, and reported seven correct checks as broken. Three rows now say plainly they are not controllable — they assert Godot's own bindings — and one is a negative carrying a positive control rather than a faked inversion.)

The standing rule that follows

Synthetic input is not a test of input. Anything injected below the input map is evidence about the code above it and nothing else. A test of input must go in at the device level — InputEventJoypadButton, InputEventJoypadMotion, InputEventKey — or must assert the map directly.

3 — the plate is late

The port raises the plate at t=236, 3.93 s after the shared clock starts, which it derives as 238 118 = 120 units = 2.000 s after the title's build-in ends. A human watching both says it is late.

This lands in a spot the corpus already knows is soft. All of the following are live:

  • REFUTED.md: "a screen has SETTLED at its rest.t" — believing rest.t had already put a port's plate 3.97 s late once.
  • REFUTED.md: the 2.13 s figure was "a wall-clock reading stretched by Canary presenting at ~28.1 fps", corrected to 120 units. So the conversion between units and seconds is load-bearing here and is exactly what TEMPORAL-VERIFICATION.md says not to trust from a wall clock.
  • The keyframe time-unit shift is unresolved (ui-keyframe-time-unit.md).
  • 🔴 After the 2026-09-01 R1 pass, "the declared keyframe timeline reproduces the captured splash" is 🟡 ⟨our-reader⟩, not — the record-layout fix re-times a group's final pose and the entry was never re-derived under it.

Candidate causes, none established: the unit→seconds constant; the clock origin (do both builds really start together?); rest.t again; the record layout. Settle it by ordering and counts, not by a stopwatch.

4 — the splash fade/blur

The port applies no blur at all. It draws declared keyframe alphas. So "more pronounced in the game" is consistent with a post-process the export does not describe, a different ramp shape, or both — and nothing in the export can distinguish those.

🔴 And the two splashes are the ONLY screens that reach rest()'s plateau-less fallback — title, main menu and EXTRAS reach it zero times. So finding 4 lands precisely where our resting-pose heuristic is least trustworthy, and the R1 pass just re-opened that question in both directions (see the rest() pair in REFUTED.md). That is not a coincidence to step around.

The human's verdict on method

"It seems the agents were essentially guessing and trying to copy what one would see, but while they did get close it still is not quite right."

Close-but-not-right is the signature of reproducing appearance instead of deriving mechanism. A ramp tuned until it looks right is wrong in a way nobody can name and has no reach to the next screen.

The instruction that follows: for the splashes, find out what the game is doing — is there a post-process pass, how many, what shader, what blend, what render targets, and where do its parameters come from — before proposing any curve. See the Decoder's brief.