Files
Sylpheed/docs/agents/RETRO-2026-08-31-agreed.md
Sylpheed port agent 50be9578c5 recover: the F5/F6 port work from the deleted auto/port-p6-audio
A snapshot of the non-game files as of 0148cb8 ("port: F5/F6 hand-off --
one-minute human checks, and a refutation attempt that survived",
2026-09-04), the tip of auto/port-p6-audio. The branch was deleted from the
server on 2026-09-17 during the consolidation cleanup; issue #7 asks for
this work as a reviewable PR, so it is recovered here before the commits
are garbage collected.

Contents: the 84 files the branch changed relative to its fork point
b305aa4, which is this commit's parent. The tree is therefore 0148cb8's
tree with the 854 exported game assets left out -- export-probe/,
export-probe2/, three .wav renders of game audio and adv-v2-screenlog.tsv.
Game data stays out of git; the exporter regenerates those from the disc.
docs/port/DECISIONS.md still refers to them by name.

Not recovered: the branch's own 366 commits. Keeping them would make those
assets reachable again, so this is one snapshot instead. The original
commits stay unreferenced in the server's object store, and in this clone
under the local branch archive/port-p6-audio, until either is garbage
collected.

Refs #7. The OPTIONS work that issue #6 asks for is a subset of this
branch, also recovered as recover/options-menu.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 20:48:47 +02:00

8.6 KiB

Agreed retro — Port and Decoder, 2026-08-31

Two self-reviews, one round of mutual attack, and the result both agents accept.

  • Decoder's own review: docs/agents/RETRO-2026-08-31.md on auto/frame-blend-draw-path.
  • Port's failures and the original eight proposals: this file's §1, and docs/port/DECISIONS.md.

⚠️ Nothing here is applied to PROTOCOL.md. The parts that change the shared refuted-claim register are presented for the human, not enacted by two agents agreeing with each other. Both agents remain paused.

1. The single most expensive thing we did

We let claims that rest on our own renderer sit in the register as settled refutations.

REFUTED.md killed "T8aD +0x04 bit 0x02 selects an additive blend" with the reason "blending those sprites additively worsens every measure against the capture" — a statement about our renderer, made while that renderer had a stale keyframe association, no leaf geometry and no rotation. The field is real. It sat dead for weeks, and the cost was: a published "the blend is not on the disc", an authored table built on it, three rounds of per-element transcription, and one agent steering a search deliberately around the entry.

The Port paid the mirror of it: a phase sweep that "refuted" menu-looping was measuring the Port's own sweeps, not the game's, and was re-run and reported as strengthened one iteration before the oracle contradicted it.

📌 Neither of us was careless. The rule was in PROTOCOL.md the whole time"anything derived from our own renderer rather than a capture" is named as a prime refutation target. What was missing is that nothing re-opens a claim when the instrument that killed it improves.

2. The gap underneath our controls

PROTOCOL.md already requires running an instrument through a control. We both did, and it did not help, because:

Our controls verified capability, not configuration.

  • The Port's additive material passed every control — they tested whether the method detects a blend difference, not whether this run had blend_mode set. It was left at Godot's default, MIX. The change predicted a large move and delivered 0.03, and would have been publishable as a careful negative.
  • The Decoder's vertex dump passed every control — they tested whether NDC→pixel conversion is right, not whether the dump captured all six quads. It captured two, with a well-formed line and no ellipsis, and four elements therefore appeared in no draw on any screen.

3. The gap neither of us had noticed

We have never given a NEGATIVE a positive control.

Every "undecodable, with reach" page lists where we looked. Not one shows that the search method can find a property that is there. "Absent" and "my search does not work" are indistinguishable in all of them — and "the blend is not on the disc" is exactly that failure, published.

4. The rules we agree to work by

rule replaces / from
R1 A refutation whose instrument is one of our renderers is not a refutation. It is "our renderer disagrees"🟡, not . Each register entry names its instrument:, and a --stale <instrument> mode lists everything that instrument killed, for re-opening when it changes. Port P2, strengthened by Decoder
R2 State the expected number before you read the actual one — the effect size for a change, the count for a parse. "This draw declares 24 indices, so I expect 6 quads." Port P3+P4, merged by Decoder
R3 Instruments print their own completeness: n resolved of n declared, and refuse to be trusted otherwise. Port P4
R4 A negative carries a positive control. Before publishing "no field encodes X", show the same search finding a field known to exist. Decoder D1 — neither agent had this
R5 Label provenance is part of the artefact. A field hunt states where its ground truth came from, and renderer-derived labels are disqualified for disc-side questions. replaces Port P1, which had no teeth — the question was asked and answered wrongly
R6 Suppression localises disagreement; only the oracle labels it. It is two renders of ours: it found the frames, it could not have said additive. Decoder's correction of Port P5
R7 Coverage is computed against a declared denominator"35 of the 41 elements entry 6 declares", never "everything is covered". Port P6 + Decoder
R8 Hold the role line even when the answer looks obvious. The asymmetry is the argument: refusing to infer ptframe4 cost one message; inferring "frame-shaped and mostly transparent ⇒ additive" would have cost a wrong renderer until the title was captured — and the title capture killed that exact rule. Port P7, agreed
R9 The message carries the delta and names the file and section; it does not summarise it. Short messages are safe only when the pointer is precise. Port P8 + Decoder's caveat
R10 A disagreement is evidence about the CHAIN — disc → decode → render → capture — not about a link. A chain-level residual gets a named owner and a next experiment, or is recorded as unowned. Decoder F
R11 A cross-agent pointer must fail loudly when it goes stale. Every staleness incident here was silent. Port, new
R12 Each iteration names the gate it moved, or says plainly that it moved none. Port, new — see §5

5. The efficiency finding neither review led with

The record has grown faster than the artifact. DECISIONS.md is past 13 000 lines. This session produced twelve Port commits of genuine measurement — and the milestone gate did not move, because P5's gate has needed a human, not code, the whole time. Writing more is not free, and a capability that lives only in the record is, to the person who needs it, absent.

R12 exists so that a run of iterations that moves no gate says so, rather than reading as progress because each entry is individually rigorous.

6. What each agent changes, without a human

  • Decoder: a standing pointer at the top of HANDOFF.md — which their brief already forces them to read every iteration, and which is theirs to write — to git show origin/auto/port-p6-audio:docs/port/BLOCKED.md. One line in a file they own, routing the Port's standing asks into a file they must already open. This closes a gap BLOCKED.md records as having cost three sessions.
  • Port: instrument: provenance and --stale in check-claims; completeness lines (R3) and predicted counts (R2) in the port's tools; a loud staleness failure for peer pointers (R11).

7. What needs the human

  1. The register re-classification (R1) — DONE 2026-09-01, by the human, on docs/re/REFUTED.md at the Decoder's tip. All 222 entries now carry an ⟨instrument⟩; the file opens with a reading guide naming which instruments are ours; R1 is now standing text in PROTOCOL.md; and tools/stale-instrument is the --stale query — run it whenever you improve a renderer, a reader or the harness, and it lists what that instrument killed.

    Ten entries moved 🟡, each naming what would settle it: eight render-vs-capture, one our-reader, one harness.

    Three things the pass turned up that neither self-review had:

    • The rest() question is open, and had been reading as settled in both directions. "rest = last keyframe" was refuted by the sibling argument; that refutation was then refuted by correlating our render against captures. Both legs run through our renderer, so under R1 neither survives — and which one you believed depended on which entry you found first. 🔴 This one is load-bearing for the port: rest() decides the pose every plateau-less element is drawn at.
    • A withdrawal never reached its sibling. "2 391 frames, max glyph 0" was withdrawn because a long-lived x11grab stream degrades and then repeats a stale frame. The 1 674-sample negative three lines above it — same probe, same instrument, comparable duration — was left standing as a reinstated measurement. §1's lesson, inside the register itself.
    • 83 of 222 entries — 37 % — record no instrument at all. Not disputed, not safe: unauditable. stale-instrument unrecorded is the backfill queue, and it is larger than every other group combined.
  2. P5's gate — a person clicking through the port. Unchanged, and it is the only thing standing between the milestone and done.