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