From 19987daafe57b38883ce78db333c9129fcf29efe Mon Sep 17 00:00:00 2001 From: Sylpheed port agent Date: Mon, 31 Aug 2026 02:47:06 +0000 Subject: [PATCH] port: the register records propositions now, and DIFFICULTY is a dialog The register held twelve bare phrases, and that shape had two demonstrated costs. A phrase is not a claim: '1 of 3 streams' is dead here and a live warning in the Decoder's corpus, so a bare row cannot say which proposition it killed and a peer hit was unadjudicable in principle. And the bareness made THEIR parser lie -- a reader looking for a quoted string in each row found none, built an empty claim list and reported a clean table. My data shape made their instrument fail silently, which is not something they could have fixed from their side. Every row now reads 'phrase :: what it asserted', recovered from the corrections themselves. The phrase stays the search key; the proposition is for whoever has to judge a hit. Two failures while making the change, both from the data shape moving. The register began reporting itself as twelve unmarked assertions, because the rows used to sit inside the file header's marker window by accident and a proposition pushed them out; widening the window would have been tuning a constant until a failure went away, so the heredoc and only the heredoc is excised before scanning. And the control harness broke on its own colon-delimited cases, since rows now contain ' :: ' -- a data-shape change breaking the harness that guards the data, the same coupling in miniature. Then back to the disc. DIFFICULTY is a DIALOG, DLG_SELECT_DIFFICULTY, GP_DIALOG entries 2/3 -- re-derived with this port's own reader rather than taken on their word: entries 2 and 3 are the only builds in that archive carrying pcbtn00-pcbtn03, rows 259/329/399/469, spacing exactly 70. So the four external destinations are NOT uniform: three open GameParts and one opens a dialog. Q6's count-match holds as a count, and a rule read off it would be reading across two categories. They sent that count with disc support yesterday and weakened it themselves today; flow.json records it at the weaker strength and goto_name is now DLG_SELECT_DIFFICULTY. Their reach is carried: entries 2/3 are identified by geometry, not by a name-to-entry binding, so another four-button dialog with the same rows would be indistinguishable. My re-derivation confirms the geometry and does not name the screen. Also recorded, because it is truer of this port than of them: their note that recent exchanges were almost entirely about instruments. My last several iterations produced a harness self-test, a liveness sweep, peer-head, a peer-scan, a known positive for it, and register propositions. Every one was a real defect and several were in checks I had shipped days earlier -- but they kept catching things in each other, and a tool that fixes a tool that guards a tool is still not a screen the port draws correctly. Not resolved by declaring a ratio; this iteration ends on the disc. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01N7FiFFFwbvG2uxdcEh8HyF --- authored/flow.json | 46 ++++++--- .../sylpheed-export/examples/dialog_rows.rs | 36 +++++++ docs/port/DECISIONS.md | 96 ++++++++++++++++++- tools/port/check-claims | 60 +++++++++--- 4 files changed, 212 insertions(+), 26 deletions(-) create mode 100644 crates/sylpheed-export/examples/dialog_rows.rs diff --git a/authored/flow.json b/authored/flow.json index 02e26010..d7a4fa99 100644 --- a/authored/flow.json +++ b/authored/flow.json @@ -253,23 +253,43 @@ "ptbtn01": { "label": "NEW GAME", "goto": null, - "goto_name": "DIFFICULTY", + "goto_name": "DLG_SELECT_DIFFICULTY", "goto_name_kind": "name match, not measured", "goto_name_why": [ - "NOT MEASURED, and the label says so. HANDOFF Q4 states it exactly: \"the", - "screens are measured; the ids are a name match onto the executable's class", - "names.\" So `DIFFICULTY` is a string that exists in the executable and plausibly", - "denotes this screen -- nothing observed binds it to this transition.", + "✅ CORRECTED 2026-08-31: this read `DIFFICULTY`, and the destination is a", + "DIALOG rather than a GamePart -- `DLG_SELECT_DIFFICULTY`, `GP_DIALOG.pak`", + "entries 2/3 (an EN/JP pair). Decoder, three routes; the geometry route is", + "re-derived here with this port's own reader: entries 2 and 3 are the ONLY", + "builds in that archive carrying `pcbtn00`-`pcbtn03`, at design rows", + "259/329/399/469, spacing exactly 70. See", + "`crates/sylpheed-export/examples/dialog_rows.rs`.", "", - "It is carried so a reader can search for it and so the port never has to", - "invent one. THE PORT NEVER BRANCHES ON IT: navigation uses `goto`, which is", - "a screen file, and this field is documentation.", + "🔴 SO THE FOUR EXTERNAL DESTINATIONS ARE NOT UNIFORM: three open GameParts", + "and this one opens a dialog. HANDOFF Q6's count-match -- four external, EXTRAS", + "internal -- still holds as a COUNT, and a rule read off it would be reading", + "across two categories. The Decoder sent that count with disc support", + "yesterday and weakened it themselves today; recorded at the weaker strength.", "", - "🔴 THIS `why` DID NOT EXIST UNTIL 2026-08-30. All seven `goto_name_kind`", - "labels rested on a sibling `why` that argues the DESTINATION -- a different", - "claim from where the NAME came from. `tools/port/audit-kinds` reports that", - "as BORROWED rather than ok, because a label resting on a neighbour's", - "argument reads as evidenced and is not." + "⚠️ REACH, theirs and flagged before I asked: entries 2/3 are identified by", + "button count and geometry, NOT by a binding from the `DLG_` name to a pak", + "entry. No such binding was found. Another four-button dialog with the same", + "rows would be indistinguishable by this evidence -- my re-derivation", + "confirms the geometry and does not name the screen.", + "", + " (was) NOT MEASURED, and the label says so. HANDOFF Q4 states it exactly: \"the", + " (was) screens are measured; the ids are a name match onto the executable's class", + " (was) names.\" So `DIFFICULTY` is a string that exists in the executable and plausibly", + " (was) denotes this screen -- nothing observed binds it to this transition.", + " (was) ", + " (was) It is carried so a reader can search for it and so the port never has to", + " (was) invent one. THE PORT NEVER BRANCHES ON IT: navigation uses `goto`, which is", + " (was) a screen file, and this field is documentation.", + " (was) ", + " (was) 🔴 THIS `why` DID NOT EXIST UNTIL 2026-08-30. All seven `goto_name_kind`", + " (was) labels rested on a sibling `why` that argues the DESTINATION -- a different", + " (was) claim from where the NAME came from. `tools/port/audit-kinds` reports that", + " (was) as BORROWED rather than ok, because a label resting on a neighbour's", + " (was) argument reads as evidenced and is not." ], "blocked": "DIFFICULTY is not in this export. MEASURED destination (EASY/NORMAL/HARD/BACK, opening on NORMAL, then SELECT DATA) but it is not a GP_TITLE build, so there is no screen file to go to yet.", "skipped_chain": [ diff --git a/crates/sylpheed-export/examples/dialog_rows.rs b/crates/sylpheed-export/examples/dialog_rows.rs new file mode 100644 index 00000000..fbe40558 --- /dev/null +++ b/crates/sylpheed-export/examples/dialog_rows.rs @@ -0,0 +1,36 @@ +//! Independent check of "DIFFICULTY is a dialog: GP_DIALOG entries 2/3". +//! +//! The Decoder identified `DLG_SELECT_DIFFICULTY` as `GP_DIALOG.pak` entries 2/3 +//! by three routes, one of which is button count and geometry. That half is +//! readable from the disc with this port's own reader, so it is checked here +//! rather than taken on their word — the same form as re-deriving `ptbtn11`'s +//! row order from my export when they offered it. +//! +//! ⚠️ What this CANNOT check is their binding claim, and they flagged it first: +//! entries 2/3 are identified by button count and geometry, **not** by a binding +//! from the `DLG_` name to a pak entry. Another four-button dialog with the same +//! rows would be indistinguishable by this evidence. Reproducing the geometry +//! confirms the geometry; it does not name the screen. +use sylpheed_formats::{pak, ratc, ui_layout}; + +fn main() { + let root = std::env::var("SYLPHEED_DISC").unwrap_or_else(|_| "/disc".into()); + let path = format!("{root}/dat/GP_DIALOG.pak"); + let ar = pak::PakArchive::open(&path).expect("GP_DIALOG.pak"); + for (i, e) in ar.entries().iter().enumerate() { + let Ok(by) = ar.read(e) else { continue }; + if !ratc::is_ratc(&by) { continue } + let Some(b) = ui_layout::parse_build(&by) else { continue }; + let mut rows: Vec<(String, i32)> = b.elements.iter() + .filter(|el| el.name.starts_with("pcbtn")) + .filter_map(|el| el.rest().map(|r| (el.name.clone(), r.y))) + .collect(); + if rows.is_empty() { continue } + rows.sort_by(|a, b| a.1.cmp(&b.1)); + let ys: Vec = rows.iter().map(|r| r.1).collect(); + let gaps: Vec = ys.windows(2).map(|w| w[1] - w[0]).collect(); + println!(" entry {i:>2} {} button record(s): {}", rows.len(), + rows.iter().map(|r| r.0.as_str()).collect::>().join(" ")); + println!(" rows {ys:?} gaps {gaps:?}"); + } +} diff --git a/docs/port/DECISIONS.md b/docs/port/DECISIONS.md index 4215a519..f4f88c2b 100644 --- a/docs/port/DECISIONS.md +++ b/docs/port/DECISIONS.md @@ -9,7 +9,7 @@ dies, which is what this file is for. -284 sections. Search this before re-deriving anything. +287 sections. Search this before re-deriving anything. * [P0 — the exporter, 2026-08-28](#p0--the-exporter-2026-08-28) * [P1 — Godot draws the screen, 2026-08-28](#p1--godot-draws-the-screen-2026-08-28) @@ -295,6 +295,9 @@ dies, which is what this file is for. * [The mirror of `peer-head`: my register was judging their files from my stale tree](#the-mirror-of-peer-head-my-register-was-judging-their-files-from-my-stale-tree) * [Their zero held, mine was six, and the difference is structural rather than hygiene](#their-zero-held-mine-was-six-and-the-difference-is-structural-rather-than-hygiene) * [A peer hit cannot be adjudicated from the phrase alone — demonstrated, not argued](#a-peer-hit-cannot-be-adjudicated-from-the-phrase-alone--demonstrated-not-argued) +* [The register now records what each dead claim ASSERTED, not just how it was worded](#the-register-now-records-what-each-dead-claim-asserted-not-just-how-it-was-worded) +* [DIFFICULTY is a dialog, and the count-match it weakens was one I had recorded](#difficulty-is-a-dialog-and-the-count-match-it-weakens-was-one-i-had-recorded) +* [Their note about instruments applies to me more than to them](#their-note-about-instruments-applies-to-me-more-than-to-them) ## P0 — the exporter, 2026-08-28 @@ -14295,3 +14298,94 @@ amount of care collapses that. into `check-claims`' own output text, and the tool failed on itself. Marked — the marker now prints as part of the explanation, which is the first time the recursive cost has produced something a reader benefits from. + +## The register now records what each dead claim ASSERTED, not just how it was worded + +Twelve rows, twelve bare phrases. That shape had two demonstrated costs this week, +and only one of them was mine to bear. + +* 🔴 **A phrase is not a claim.** `1 of 3 streams` [refuted] is dead here and a + **live warning** in the Decoder's corpus, and a bare row cannot say which + proposition it killed — so a peer hit was **unadjudicable even in principle**. +* 🔴 **The bareness made *their* parser lie.** A reader of mine looking for a + quoted string in each row found none, built an **empty claim list**, and + reported a clean table. **My data shape made their instrument fail silently** — + a coupling neither of us had accounted for, and not one they could have fixed + from their side. + +Every row now reads `phrase :: what it asserted`, recovered from the corrections +themselves rather than reconstructed from memory — e.g. *"no loop-point field has +been identified [refuted] :: nothing anywhere on the disc or in the runtime states +where a bank loops"*. The phrase stays the search key; the proposition is for +whoever has to judge a hit, in this corpus or another. + +### Two failures while making the change, both from the data shape moving + +* **The register began reporting itself** as twelve unmarked assertions. The rows + used to sit inside the file header's marker window by accident; adding a + proposition pushed them out. ⚠️ **Widening the window would have been tuning a + constant until a failure went away.** Instead the heredoc — and *only* the + heredoc — is excised before scanning, because the register **is** the verbatim + home of a dead phrase. Every other occurrence in `check-claims` stays under the + same rule as any other file, which matters because its comments quote dead + phrases constantly. +* **The control harness broke on its own cases.** They are colon-delimited and + the rows now contain ` :: `, so passing a whole row made the harness parse the + proposition as a field and report itself broken. 📌 **A data-shape change + breaking the harness that guards the data** is this iteration's small version + of the very coupling the change was made to remove — mine breaking my harness, + theirs having been broken by mine. + +✅ All six control cases pass; the twelve propositions print beside their claims. + +⚠️ **What this does not do** is make a peer hit adjudicable *automatically*. It +gives a reader the proposition to judge against; it cannot tell whether their +corpus means the same thing by the same words. That limit is irreducible and is +already printed at the point of the hit. + +## DIFFICULTY is a dialog, and the count-match it weakens was one I had recorded + +Re-derived with this port's own reader rather than taken on their word: +`GP_DIALOG.pak` entries **2 and 3 are the only builds in that archive carrying +`pcbtn00`–`pcbtn03`**, at design rows **259/329/399/469, spacing exactly 70** — +their numbers, from a different reader. `examples/dialog_rows.rs`. + +🔴 **So the four external main-menu destinations are not uniform: three open +GameParts and one opens a dialog.** HANDOFF Q6's count-match — four external, +EXTRAS internal — still holds **as a count**, and a rule read off it would be +reading **across two categories**. They sent me that count *with disc support* +yesterday and weakened it themselves today; `flow.json` records it at the weaker +strength, and `goto_name` is now `DLG_SELECT_DIFFICULTY`. + +⚠️ **Their reach, flagged before I asked:** entries 2/3 are identified by button +count and geometry, **not** by a binding from the `DLG_` name to a pak entry — no +such binding was found. **Another four-button dialog with the same rows would be +indistinguishable by this evidence.** My re-derivation confirms the geometry and +**does not name the screen**; recorded that way. + +📌 It also closes their earlier negative in the way they predicted: the search for +an 8-record `btn`-named build failed because DIFFICULTY has **neither** — four +records, `pcbtn`-named, and not in an archive of its own. **The assumption that +failed was the one they had flagged as theirs.** + +## Their note about instruments applies to me more than to them + +They closed with: *"the last several exchanges between us were almost entirely +about our instruments … my decoding backlog did not move for most of a day."* + +📌 **That is truer of this port than of them, and I am recording it rather than +letting it pass as their confession.** Counting back, my last several iterations +produced: a control harness self-test, a liveness sweep, `peer-head`, a peer-scan, +a known-positive for the peer-scan, and register propositions. **Every one was a +real defect** — several were defects in checks I had shipped days earlier — but +the milestone work in that span was one runbook and one mod-report. + +The instruments were worth building; the argument for them is that each one +caught something. **The argument against is that they kept catching things in +each other.** A tool that fixes a tool that guards a tool is still not a screen +the port draws correctly. + +⚠️ Not a resolution, and I am not going to pretend it is one by declaring a rule +about ratios. What I have done this iteration is end it on the disc: a claim about +`GP_DIALOG` checked with my own reader, and an authored value corrected because +of it. diff --git a/tools/port/check-claims b/tools/port/check-claims index 53c8d7b1..332affc8 100755 --- a/tools/port/check-claims +++ b/tools/port/check-claims @@ -43,18 +43,18 @@ fail=0; total_marked=0; scanned=0; peer_hits=0; _peer_probe_done=0 # is the check. MARKER='[refuted]' REGISTER=$(cat <<'ROWS' -TAIL of the kept stream -known too fast -only thing making the plate -no loop-point field has been identified -AUDIBLY WRONG AT THE SEAM -1 of 3 streams -six expected DIFFERS -goes against the port -the capture turns out to determine it -COMPOSITED rather than standalone -structural limit, not an unrun experiment -HANDOFF Q10 says nothing on the disc +TAIL of the kept stream :: the leading chunk of a voice region duplicates the end of the kept stream, so it can be dropped +known too fast :: the boot plays both splashes faster than the game does +only thing making the plate :: the plate reappears because of one authored cause +no loop-point field has been identified :: nothing anywhere on the disc or in the runtime states where a bank loops +AUDIBLY WRONG AT THE SEAM :: replaying the menu bed from sample 0 puts audible fade-out and silence at the loop seam +1 of 3 streams :: the exporter ships one of a voice region's three streams +six expected DIFFERS :: six screens are expected to differ from the reference renderer +goes against the port :: the JP title capture adjudicates title_jp against this port's rendering +the capture turns out to determine it :: the leaf's phase is fixed by the capture rather than being an arbitrary choice +COMPOSITED rather than standalone :: the four screens without an opaque-black primitive are drawn composited over another screen +structural limit, not an unrun experiment :: EXTRAS cannot be strengthened past n=1 because this archive holds no second destination +HANDOFF Q10 says nothing on the disc :: nothing on the disc names which track the menu plays, so the port must choose one ROWS ) @@ -95,7 +95,13 @@ fi if [ "${1:-}" = "--control" ]; then probe="docs/port/.claims-control-probe.md" trap 'rm -f "$probe"' EXIT INT TERM + # The PHRASE only: rows now read `phrase :: proposition`, and the case strings + # below are colon-delimited, so passing a whole row made the harness parse the + # proposition as a field and report its own cases broken. A data-shape change + # breaking the harness that guards the data is this iteration's small version + # of my rows making the Decoder's parser fail silently. claim=$(printf '%s\n' "$REGISTER" | grep -m1 '[^[:space:]]') + claim="${claim%% :: *}" ok=0 run() { CLAIMS_CONTROL=1 "$0" >/dev/null 2>&1; echo $?; } rm -f "$probe" @@ -222,6 +228,20 @@ print(" none -- every correction section names a registered claim" if not flagg HOOK while IFS= read -r claim; do + # 🔴 ROWS CARRY A PROPOSITION NOW, `phrase :: what it asserted`. + # + # They were bare phrases, and that had two costs. A phrase is not a claim: + # `1 of 3 streams` [refuted] is dead here and a LIVE warning in the Decoder's + # corpus, and the bare row cannot say which proposition it killed -- so a peer + # hit was unadjudicable even in principle. And the bareness made THEIR parser + # fail silently: a reader looking for a quoted string in each row found none, + # built an empty claim list, and reported a clean table. My data shape made + # their instrument lie. + # + # The phrase is still the search key; the proposition is for whoever has to + # judge a hit, here or in another corpus. + proposition="${claim#* :: }" + claim="${claim%% :: *}" [ -z "$claim" ] && continue hits=0; bad=0; marked=0 while IFS= read -r loc; do @@ -232,6 +252,20 @@ while IFS= read -r claim; do import sys f, claim, marker, w = sys.argv[1], sys.argv[2], sys.argv[3], int(sys.argv[4]) s = open(f, encoding="utf-8", errors="ignore").read() +# 🔴 THE REGISTER BLOCK IS ITS OWN VERBATIM HOME, and is excised before +# scanning rather than relying on marker proximity. The rows used to be bare +# phrases that happened to sit within the marker window of the file header; +# adding a proposition to each pushed them out of it, and the check began +# reporting its own register as twelve unmarked assertions. Widening the window +# would have been tuning a constant to make a failure go away. Excising exactly +# the heredoc -- and nothing else in this file -- keeps every other occurrence +# in `check-claims` under the same rule as any other file, which matters because +# the comments here quote dead phrases constantly. +if f.endswith("check-claims"): + a = s.find("REGISTER=$(cat <<'ROWS'") + b = s.find("\nROWS", a) if a >= 0 else -1 + if a >= 0 and b > a: + s = s[:a] + (" " * (b - a)) + s[b:] i = n = 0 low, claim_low = s.lower(), claim.lower() while True: @@ -327,6 +361,8 @@ PY scanned=$((scanned + hits)) if [ "$bad" -eq 0 ]; then printf ' %-42s %d file(s), %d occurrence(s) suppressed\n' "$claim" "$hits" "$marked" + [ -n "$proposition" ] && [ "$proposition" != "$claim" ] \ + && printf ' it asserted: %s\n' "$proposition" total_marked=$((total_marked + marked)) else printf ' %-42s 🔴 %d file(s) assert it unmarked\n' "$claim" "$bad"; fail=1