Files
Sylpheed/docs/agents/TEMPORAL-VERIFICATION.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

6.6 KiB
Raw Permalink Blame History

Verifying things that MOVE

Both agents read this. Set by the human on 2026-09-01, after a play-test found the splash fade and the PRESS Ⓐ plate visibly wrong while every check either agent had was green.

The diagnosis, in one sentence

We have been trying to photograph the game at time t, and t is never the same twice.

Every temporal claim in this corpus rests on grabbing a frame at a wall-clock instant and comparing it to something. That instant drifts — emulator speed varies with host load, Canary presents at ~28.1 fps rather than 30, the capture path costs a variable 0.110.8 s, and a long-lived x11grab stream degrades and then freezes. So the comparison is between our render at the time we meant and the game at some other time, and the difference between those two things is being read as a difference in the content.

The register already carries four separate refutations of this exact shape:

  • "a latency read off a classified x11grab stream is a duration". At 1503 ms per classification against an 8 fps stream the consumer ran at 0.64 fps; four "durations" died. A screen transition, a button press and a plate fade all came out at ~2025 s, which is the tell.
  • "2 391 frames over 600 s, max glyph 0, therefore the title never appears" — withdrawn: the instrument stalls, repeating one stale frame, reading surface mean 5.21 where import read 125.65 at the same moment.
  • "the boot harness fails because its polling loop samples every ~41 s" — the defect was real and fixing it (13.7×) did not change the answer.
  • "the in-box capture noise of 0.32 between sessions" — it was not noise, it was the trigger: gating on the plate pulse phase-locks the shutter, so 0.32 is a lower bound produced by the instrument. At an arbitrary phase the honest figure is 11.9.

That last one is the important one, and it cuts both ways: gating hides variance, and not gating produces it. Neither is a measurement of the game.

What to do instead

The rule is simple and it removes the whole class:

Never compare at an absolute time. Record a SEQUENCE, and align it by CONTENT.

1. Capture a film, not a photograph

Record a continuous run of frames with an index and a timestamp each, spanning the whole animation with margin at both ends. One frame is a sample of a distribution you have not characterised; a film is the distribution.

State the achieved rate against the requested rate, every time. A capture that asked for 4 fps and delivered 1.6 is not a slow capture, it is a different capture, and it has already produced two withdrawn findings here. An instrument that cannot report its own completeness may not be trusted (R3).

2. Align by content, then measure

Find the offset that best matches, rather than assuming offset zero:

  • reduce each frame to a scalar or a small vector — mean of a region, an element's alpha, a per-tile amplitude;
  • do the same for the prediction;
  • search the lag that maximises agreement, and report both the lag and the agreement at it.

The lag is not an error to be minimised away — it is a measurement. A consistent lag across runs is a real offset in our model. A lag that varies run to run is the harness, and says so.

3. Prefer quantities that have no phase

Ranked by how much they survive a drifting clock:

quantity survives drift?
ordering — A finishes before B starts completely
counts — 83 frames at full alpha (given a known, reported rate)
durations and ratios — ramp is 2× the hold
shape — monotone, eased, stepped, its inflections
a value at a named event — alpha when the plate first appears 🟡 needs the event found, not the time
a value at wall-clock t this is the thing that has been failing

The two strongest existing results in the corpus are both of this kind: the hold duration (83 frames of full alpha) is called calibration-free in ui-keyframe-time-unit.md and decided the question; and the _eff glows' exact steps of 34 are a shape, not a sample.

4. Anchor on an event

Quote everything relative to a frame you can find rather than a time you requested: the first frame an element is non-black, the frame the plate first appears, the last frame of the previous screen. Then a drifting start costs nothing, because every number is a difference.

5. Say what you expected before you look (R2)

"This ramp declares 80 units, so at 30 fps I expect ~80 frames and I will accept 7486." Written first, it makes a near-miss legible as a near-miss instead of something to rationalise. Written after, any number can be explained.

6. Convert units deliberately

⚠️ Canary presents at ~28.1 fps, so a wall-clock duration off this emulator is ~6 % long. A measured interval landing near a round number of keyframe units probably is that number of units — that is how 2.13 s turned out to be 120 units = 2.000 s. Quote the unit count, then the seconds, then the fps you divided by. Never the seconds alone.

The other half: stop reproducing by eye

The play-test's verdict on the splashes was "close, but not quite right", and that is the signature of matching appearance instead of deriving mechanism. A ramp tuned until it looks right will be wrong in a way nobody can name, and "looks right" has no reach — it does not tell you what the next screen will do.

So for anything visual that is still not exact, the question is not "what curve fits?" but "what is the game actually doing?":

  • Is there a post-process pass at all — a blur, a bloom, a fade quad, a tone curve? That is a GPU-state question with a yes/no answer.
  • If yes: how many passes, what render targets, what blend, what shader, and where do its parameters come from — immediate constants, a table in a pak, a computed ramp?
  • Only then, what curve.

A mechanism found this way is decoded, generalises to every screen, and cannot be "close". A curve fitted by eye is none of those things.

What this does not license

Doing more of this is not a reason to stop shipping. A measurement that would take an hour is not blocked on building the perfect harness first — take the cheap phase-invariant version (an ordering, a count) and say what its reach is.

And an instrument that cannot pass a control is not a starting point. A filter that fails its own known-positive is dead, not tuneable; a lag search that cannot recover a synthetic 30-frame offset cannot measure an unknown one. Run the control first, and record it.