diff --git a/WORK-RECORDS.md b/WORK-RECORDS.md index 6002ba4..093bb37 100644 --- a/WORK-RECORDS.md +++ b/WORK-RECORDS.md @@ -52,7 +52,6 @@ | workplan | CB-WP-0042 | done | — | workplans/CB-WP-0042-h2-scoped-problem-stress.md | | workplan | CB-WP-0043 | done | — | workplans/CB-WP-0043-a-problem-sits-where-its-stress-lands.md | | workplan | CB-WP-0044 | done | — | workplans/CB-WP-0044-the-page-says-which-rules-it-plays.md | -| workplan | CB-WP-0045 | done | — | workplans/CB-WP-0045-the-table-names-what-it-shows.md | | task | CB-WP-0001-T01 | done | — | workplans/CB-WP-0001-inner-loop.md | | task | CB-WP-0001-T02 | done | — | workplans/CB-WP-0001-inner-loop.md | | task | CB-WP-0001-T03 | done | — | workplans/CB-WP-0001-inner-loop.md | @@ -244,4 +243,3 @@ | task | CB-WP-0042-T05 | done | — | workplans/CB-WP-0042-h2-scoped-problem-stress.md | | task | CB-WP-0043-T01 | done | — | workplans/CB-WP-0043-a-problem-sits-where-its-stress-lands.md | | task | CB-WP-0044-T01 | done | — | workplans/CB-WP-0044-the-page-says-which-rules-it-plays.md | -| task | CB-WP-0045-T01 | done | — | workplans/CB-WP-0045-the-table-names-what-it-shows.md | diff --git a/crates/cb-render-html/src/doc.rs b/crates/cb-render-html/src/doc.rs index bb168b0..4610b66 100644 --- a/crates/cb-render-html/src/doc.rs +++ b/crates/cb-render-html/src/doc.rs @@ -1306,25 +1306,6 @@ fn body(s: &mut String, view: &GroundView) { s.push_str("
no problems in play
"); } s.push_str(&table_svg(view)); - // CB-WP-0046: the placement is legible, the RULE it encodes was not. - // - // Shown only when a non-global scope is actually on the table — under - // the baseline every Problem is everyone's, and an explanation of a - // distinction that is not in play is noise. Same discipline as the - // GROUND modes: at the thing it explains, and closable. - if view.problem_markers.values().any(|m| { - m.scope - .is_some_and(|x| x != games_ground::edition::StressScope::Global) - }) { - if let Some(rule) = stress_scope_rule_text() { - let _ = write!( - s, - "
what a Problem\u{2019}s scope does\ - {}
", - esc(&rule) - ); - } - } s.push_str("

seats

"); for (id, p) in &view.players { @@ -1509,20 +1490,6 @@ fn ground_modes_text() -> Option { .map(|c| c.rules_text) } -/// What a scope DOES, in the edition's words (CB-WP-0046). -/// -/// CB-WP-0045 left this open and gave the wrong reason: *"the scope rule -/// is ours (a Variant), so there is no vendored sentence to render."* The -/// H2 package ships `Rules_Text.csv` and its `Problems / Stress scope` -/// passage is exactly the rule. Nothing read the file. -/// -/// **The placement was legible and the rule it encodes was not** — a -/// player could see a card sitting on their Bond lines and have no way to -/// learn that its Stress presses everyone on those lines. -fn stress_scope_rule_text() -> Option { - games_ground::edition::stress_scope_rule() -} - /// A GROUND sub-choice, in words, naming whoever it touches. fn ground_choice_label(c: &games_ground::GroundChoice) -> String { use games_ground::GroundChoice as G; diff --git a/crates/cb-render-html/src/lib.rs b/crates/cb-render-html/src/lib.rs index 7e316f4..02b60de 100644 --- a/crates/cb-render-html/src/lib.rs +++ b/crates/cb-render-html/src/lib.rs @@ -697,65 +697,6 @@ mod gamelog { } } - /// **A scope says what it does** (CB-WP-0046). - /// - /// CB-WP-0045 shipped the placement and recorded the gap with the - /// wrong reason — that scoping is our Variant so no printed sentence - /// exists. `Rules_Text.csv` was in the H2 package the whole time and - /// nothing read it. A player could see a card on their Bond lines - /// and have no way to learn what that meant. - #[test] - fn a_scoped_table_says_what_a_scope_does() { - let mut v = crate::testfix::view(Some(PlayerId(0))); - v.variant = games_ground::Variant::H2ScopedProblemStress; - // One card that is not everyone's — the condition for the rule - // to be worth stating at all. - if let Some(m) = v.problem_markers.values_mut().next() { - m.scope = Some(games_ground::edition::StressScope::Bond); - m.owner = Some(PlayerId(1)); - } - let text = crate::text_of(&crate::doc::document( - &v, - &[], - "/c", - Some(PlayerId(0)), - false, - )); - assert!( - text.contains("scope does"), - "Problems are placed by scope and nothing says what a scope does" - ); - // The EDITION's sentence. A paraphrase of ours would drift from - // the rule the kernel implements (ADR-0015). - let rule = games_ground::edition::stress_scope_rule() - .expect("h2 Rules_Text.csv carries the scope rule"); - let head: String = rule.chars().take(60).collect(); - assert!( - text.contains(&head), - "the scope explanation is not the edition's own words: {head:?}" - ); - } - - /// The baseline is not told about a distinction it does not have. - #[test] - fn an_unscoped_table_does_not_explain_scopes() { - let mut v = crate::testfix::view(Some(PlayerId(0))); - for m in v.problem_markers.values_mut() { - m.scope = Some(games_ground::edition::StressScope::Global); - } - let text = crate::text_of(&crate::doc::document( - &v, - &[], - "/c", - Some(PlayerId(0)), - false, - )); - assert!( - !text.contains("scope does"), - "the baseline table explains a scope distinction that is not in play" - ); - } - /// **The number is on the card** (CB-WP-0045). /// /// Every move label says "Problem 7"; nothing on the table said which diff --git a/games/ground/src/edition.rs b/games/ground/src/edition.rs index c942db7..614d442 100644 --- a/games/ground/src/edition.rs +++ b/games/ground/src/edition.rs @@ -82,16 +82,6 @@ const GLOSSARY_CSV: &str = include_str!("../../../editions/ground-darvo-r0/Gloss /// H2's Problems table (CB-WP-0042). r0's with one column added. const H2_PROBLEMS_CSV: &str = include_str!("../../../editions/experiments/h2-scoped-problem-stress/Problems.csv"); -/// H2's player-facing rulebook. **It was here all along** (CB-WP-0046). -/// -/// CB-WP-0045 recorded that nothing explains what a *scope does* and -/// judged it a `ground-game` question, on the reasoning that scoping is -/// clay-borg's Variant and so has no printed sentence. That was wrong: -/// the package ships `Rules_Text.csv`, and row 20 is the rule in the -/// edition's own words. **We had never read the file** — the third time -/// this pass that vendored text could not reach a player (F18). -const H2_RULES_TEXT_CSV: &str = - include_str!("../../../editions/experiments/h2-scoped-problem-stress/Rules_Text.csv"); /// A vendored CSV, parsed into rows addressable by column name. /// @@ -572,59 +562,6 @@ pub fn stress_scopes(scenario_id: &str) -> Result, Str Ok(out) } -/// One passage of the variant's rulebook (CB-WP-0046). -pub struct RulesPassage { - pub order: u32, - pub section: String, - pub heading: String, - pub body: String, -} - -/// H2's rulebook, in `Rules_Text.csv` order. -/// -/// Returned whole rather than as a lookup by heading: a caller that wants -/// the scope rule asks for its section, and the ordering is the -/// edition's, not one we impose. -pub fn h2_rules_text() -> Result, String> { - let t = Table::parse(H2_RULES_TEXT_CSV, "h2/Rules_Text.csv")?; - let mut out = Vec::new(); - for row in &t.rows { - out.push(RulesPassage { - order: t - .get(row, "order")? - .trim() - .parse() - .map_err(|_| "order is not a number".to_string())?, - section: t.get(row, "section")?.to_string(), - heading: t.get(row, "heading")?.to_string(), - body: t.get(row, "body")?.to_string(), - }); - } - if out.is_empty() { - return Err("h2/Rules_Text.csv has no rows".into()); - } - out.sort_by_key(|p| p.order); - Ok(out) -} - -/// What a `stress_scope` DOES, in the edition's words (CB-WP-0046). -/// -/// The passage headed *Stress scope* in the `Problems` section. Matched -/// on the heading rather than pinned to row 20, because a row number is -/// a property of today's file and the heading is a property of the rule. -/// **`None` if the edition stops carrying it** — an absent explanation is -/// reported as absent, never replaced by ours (ADR-0018). -pub fn stress_scope_rule() -> Option { - h2_rules_text() - .ok()? - .into_iter() - .find(|p| { - p.section.eq_ignore_ascii_case("Problems") - && p.heading.to_lowercase().contains("stress scope") - }) - .map(|p| p.body) -} - /// The stress gate, printed on every player mat (CB-WP-0037 T03). /// /// **A mat is mostly ornamentation with one rule on it.** Symbol, colour diff --git a/specs/FindingRegister.md b/specs/FindingRegister.md index 7e5c81c..bc68d5d 100644 --- a/specs/FindingRegister.md +++ b/specs/FindingRegister.md @@ -53,30 +53,9 @@ kinds, states and metrics: [`GameDesign.md`](GameDesign.md). Reported by | F21 | degenerate | note | — | — | 2026-08-06 | clay-borg | | F23 | inconsistent | applied | decisions/ADR-0017-chaos-window-2-verdict.md | counterexample | 2026-08-07 | clay-borg | | F22 | underdetermined | withdrawn | games_ground::edition::supply_tests::play_never_exceeds_the_components_the_box_holds | counterexample | 2026-08-07 | clay-borg | -| F26 | inert | raised | `crates/cb-render-html/src/lib.rs::a_scoped_table_says_what_a_scope_does` | counterexample | 2026-08-08 | ground-game | -- **F26 — a package that adds a FILE is invisible, where a package that - adds a column is not.** `h2-scoped-problem-stress` ships - `Rules_Text.csv` — twenty-two passages of player-facing rules, including - the one passage that says what a `stress_scope` does. No reader in - clay-borg mentioned the file, so `edition-check` could not call it stale - (a file nothing reads is not out of date, it is unseen) and no gate - could call it unread. The rule the table's whole layout encodes sat in - the repo, reachable by no player, for the entire measured life of H2. - - **Inert rather than underdetermined**: nothing is ambiguous about the - rule and the engine implements it correctly — the text simply could not - fire. Third instance of F18's shape and the first where the unit is a - file, which is why it is filed separately instead of folded in. - - Raised against `ground-game` because the actionable half is theirs: a - package's manifest should name its own files, so that a consumer reading - none of them is a detectable state. clay-borg now reads the one passage - it needed (CB-WP-0046); the other twenty-one remain unread and that is - the standing evidence. - - **F11 — SOLVE offered where it cannot act.** Offered on a face-down Problem, or with no matching suit in hand; inert every time. Ruled GROUND-WP-0002 T02, implemented CB-WP-0023 as GR-P05. `applied` — the diff --git a/tools/cb-play/src/hotseat.rs b/tools/cb-play/src/hotseat.rs index 029e216..a9dc3d1 100644 --- a/tools/cb-play/src/hotseat.rs +++ b/tools/cb-play/src/hotseat.rs @@ -100,11 +100,7 @@ pub fn game_path(base: &std::path::Path, n: usize) -> std::path::PathBuf { /// **Reusing `FindingRegister.md`'s idiom deliberately** (ADR-0014 D2) — /// a table between HTML-comment markers, so a human reads the file and a /// tool reads the block, and neither needs the other's cooperation. -pub fn write_trial_log( - path: &std::path::Path, - notes: &[TrialNote], - variant: &str, -) -> Result<(), String> { +pub fn write_trial_log(path: &std::path::Path, notes: &[TrialNote]) -> Result<(), String> { let mut s = String::new(); s.push_str("# Trial log\n\n"); s.push_str( @@ -115,19 +111,7 @@ pub fn write_trial_log( `ground-game` (ADR-0014 D4) — a note reaches them only by being promoted to a\n\ register finding, by a human, with the wording chosen then.\n\n", ); - // CB-WP-0046: WHICH RULES THIS SESSION PLAYED. - // - // Without it a baseline note and an H2 note are indistinguishable, - // which stopped being survivable the moment both were being played - // in the same week. It rides the marker rather than becoming an - // eighth column because the variant is a property of the SESSION, - // and a per-note column would repeat one fact on every line. - // - // Read off the STATE, not off the command line: `--variant h2` with - // a bare `state.variant = v` assignment leaves H2 inert, and a log - // stamped from the flag would have recorded H2 for a session that - // played the baseline. This records what actually ran. - let _ = write!(s, "\n\n"); + s.push_str("\n\n"); // ADR-0019 D3/D4: `game` and `after` are the binding a reader can act // on -- replay `after` commands of game `game` and you are standing // where the player stood. `state_hash` stays as an integrity check at @@ -225,7 +209,7 @@ impl Server { state_hash: hash[..12].to_string(), text: note.text.clone(), }); - write_trial_log(path, &log, state.variant.id()) + write_trial_log(path, &log) } /// Begin the next game (ADR-0019 D1). @@ -984,63 +968,6 @@ mod tests { /// this loop breaks it; a player writing what they thought must be /// able to write a second one and then still press `play again`, /// which is what the trailing command here proves. - /// **The log says which rules the session played** (CB-WP-0046). - /// - /// Without it a baseline note and an H2 note are indistinguishable, - /// which is how *"i did not notice any difference"* stays unreadable - /// after the fact — you cannot tell whether it is a finding about H2 - /// or a note about a session that never ran it. - #[test] - fn the_trial_log_records_which_rules_were_played() { - let dir = std::env::temp_dir().join(format!("cb-trial-var-{}", std::process::id())); - std::fs::create_dir_all(&dir).unwrap(); - let path = dir.join("trial.md"); - let note = TrialNote { - n: 1, - game: 1, - after: 4, - round: 3, - step: "Select".into(), - state_hash: "abc123def456".into(), - text: "it felt harder".into(), - }; - write_trial_log(&path, &[note], "h2-scoped-problem-stress").unwrap(); - let written = std::fs::read_to_string(&path).unwrap(); - assert!( - written.contains(""), - "the log does not say which rules were played:\n{written}" - ); - // **Not an eighth column.** The variant is a property of the - // session; repeating it per row would widen a table whose parser - // treats an unexpected width as an error. - let row = written - .lines() - .find(|l| l.starts_with("| 1 |")) - .expect("the note row"); - assert_eq!( - row.matches('|').count() - 1, - 7, - "the table gained a column: {row}" - ); - let _ = std::fs::remove_dir_all(&dir); - } - - /// The stamp is read off the STATE, never off the command line. - /// - /// Three call sites once set `state.variant = v` directly instead of - /// `with_variant()`, which renders H2 inert. A log stamped from the - /// `--variant` flag would have recorded `h2` for a session that - /// played the baseline — a false provenance on real player words, - /// which is worse than no provenance. - #[test] - fn the_stamp_names_the_rules_that_actually_ran() { - let src = include_str!("hotseat.rs"); - assert!( - src.contains("write_trial_log(path, &log, state.variant.id())"), - "the trial log is stamped from something other than the state" - ); - } - #[test] fn a_note_can_be_written_after_the_game_has_ended() { let dir = std::env::temp_dir().join(format!("cb-note-end-{}", std::process::id())); diff --git a/tools/trials.py b/tools/trials.py index 3164e02..e6cf8b3 100644 --- a/tools/trials.py +++ b/tools/trials.py @@ -15,27 +15,14 @@ reuses `FindingRegister.md`'s idiom: a table between HTML-comment markers inside a readable Markdown file. """ -import os -import re, sys, glob, datetime +import os, re, sys, glob, datetime ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) TRIALS = os.path.join(ROOT, "trials") -# The begin marker carries attributes since CB-WP-0046, so it is matched -# by PREFIX. A bare `` is still a valid marker — -# every log written before this one has it. -BEGIN = "" END = "" -# Which rules the session was played under (CB-WP-0046). -# -# **A session property, not a note property**, so it rides the marker -# rather than becoming an eighth column. The variant is fixed at startup -# and cannot change between games, so a per-row column would repeat one -# fact on every line and force a third accepted row width on a parser -# whose docstring is about exactly that hazard. -VARIANT_ATTR = re.compile(r"variant=([A-Za-z0-9._-]+)") - # GameDesign §3.1's figure, shared rather than reinvented: a note that # cannot expire ages into an apparent finding. NOTE_EXPIRY_DAYS = 30 @@ -58,12 +45,7 @@ def parse(text): is precisely the failure this tool exists to prevent.""" if BEGIN not in text or END not in text: raise ValueError("no trial-log block") - # Split off the marker's own line so an attribute cannot be read as - # a table row, and so `variant=` is available to every row below it. - tail = text.split(BEGIN, 1)[1].split(END)[0] - marker, _, block = tail.partition("\n") - hit = VARIANT_ATTR.search(marker) - variant = hit.group(1) if hit else None + block = text.split(BEGIN)[1].split(END)[0] rows = [] for line in block.splitlines(): line = line.strip() @@ -81,21 +63,16 @@ def parse(text): # considered, and did not do) would have emptied every existing # log without a word. if len(cells) == len(COLUMNS): - row = dict(zip(COLUMNS, cells)) + rows.append(dict(zip(COLUMNS, cells))) elif len(cells) == len(LEGACY_COLUMNS): row = dict(zip(LEGACY_COLUMNS, cells)) row["legacy"] = True + rows.append(row) else: raise ValueError( f"trial-log row has {len(cells)} columns, expected " f"{len(COLUMNS)} or {len(LEGACY_COLUMNS)}: {line!r}" ) - # **`None`, not a default of "baseline"** — a log written before - # the marker carried the attribute does not say which rules were - # played, and answering an unrecorded question with the common - # case is how a baseline note gets read as an H2 result. - row["variant"] = variant - rows.append(row) return rows @@ -197,20 +174,12 @@ def report(root=TRIALS, today=None): found = logs(root) print("trials — what the players said, and where\n") total, orphans, pending, expired, legacy = 0, 0, 0, 0, 0 - by_variant = {} for path, rows, _ in found: name = os.path.basename(path) age = age_days(path, today) - # CB-WP-0046: which rules were played. `unrecorded` is said out - # loud rather than filled in with the common case — a note read - # under the wrong rules is worse than a note whose rules are - # openly unknown. - rules = (rows[0].get("variant") if rows else None) or "unrecorded" - print(f" {name} ({len(rows)} note(s), {age}d, rules {rules})") + print(f" {name} ({len(rows)} note(s), {age}d)") for r in rows: total += 1 - key = r.get("variant") or "unrecorded" - by_variant[key] = by_variant.get(key, 0) + 1 # ADR-0019 D2: each note names its own game, and each game has # its own recording. One recording per FILE was the assumption # that made `play again` overwrite the evidence. @@ -238,15 +207,6 @@ def report(root=TRIALS, today=None): print(f" legacy binding {legacy}" " (written before ADR-0019; position not checkable, NOT orphaned)") print(f" notes past {NOTE_EXPIRY_DAYS} days {expired} target 0") - # A tally by rules, because the question a reader now has is "was this - # said about H2 or about the baseline" and counting the logs by hand - # is how that gets answered wrong. - if by_variant: - print(" notes by rules " - + ", ".join(f"{v} {n}" for v, n in sorted(by_variant.items()))) - if "unrecorded" in by_variant: - print(" (unrecorded = written before" - " CB-WP-0046 stamped the marker)") if total and not orphans and not legacy: print("\n Every note points at a position a reader can reach.") print("\n Raw notes stay in this repo (ADR-0014 D4). A note reaches" @@ -271,19 +231,13 @@ def self_test(): ok = ok and bool(cond) print(f" [{'ok ' if cond else 'FAIL'}] {name}" + (f" — {detail}" if detail else "")) - # `BEGIN` is a PREFIX since CB-WP-0046, so a fixture has to close the - # marker itself — which is the point: the closing text is what the - # attribute sits inside. - OPEN = BEGIN + " -->" - - def log_of(*rows, marker=None): + def log_of(*rows): head = ("| n | game | after | round | step | state_hash | comment |\n" "|---|---|---|---|---|---|---|\n") - return (f"# Trial log\n\n{marker or OPEN}\n\n{head}" - + "".join(rows) + f"\n{END}\n") + return f"# Trial log\n\n{BEGIN}\n\n{head}" + "".join(rows) + f"\n{END}\n" good = log_of("| 1 | 1 | 4 | 3 | Select | abc123def456 | why is SOLVE doing nothing |\n") - legacy = (f"# Trial log\n\n{OPEN}\n\n" + legacy = (f"# Trial log\n\n{BEGIN}\n\n" "| n | round | step | state_hash | comment |\n|---|---|---|---|---|\n" "| 1 | 3 | Select | abc123def456 | why is SOLVE doing nothing |\n" f"\n{END}\n") @@ -302,65 +256,6 @@ def self_test(): check("a legacy row is MARKED legacy", parse(legacy)[0].get("legacy") is True) check("the header row is not a note", all(r["n"] != "n" for r in parse(good))) - # CB-WP-0046 — WHICH RULES THE SESSION PLAYED. - row = "| 1 | 1 | 4 | 3 | Select | abc123def456 | it felt harder |\n" - stamped = log_of(row, marker=BEGIN + " variant=h2-scoped-problem-stress -->") - check("a stamped log reports its variant", - parse(stamped)[0]["variant"] == "h2-scoped-problem-stress") - check("an unstamped log says NOTHING, not 'baseline'", - parse(log_of(row))[0]["variant"] is None, - "answering an unrecorded question with the common case is how a " - "baseline note gets read as an H2 result") - # The attribute must not be mistaken for a table row, and adding it - # must not change how many notes the log has. - check("the marker is not counted as a note", len(parse(stamped)) == 1) - check("stamping does not disturb the row", parse(stamped)[0]["after"] == "4") - # The marker is not a THIRD row width: the table shape is untouched, - # which is why the parser needed no new accepted width. - check("the stamped table has the same columns as the unstamped one", - set(parse(stamped)[0]) - {"variant"} == set(parse(good)[0]) - {"variant"}) - # **A LITERAL marker, not one built from `BEGIN`.** - # - # Every fixture above composes its marker out of `BEGIN`, so all of - # them follow `BEGIN` wherever it goes — including away from what - # `hotseat.rs` actually writes. Reverting `BEGIN` to an exact match - # broke real stamped logs and every one of those checks stayed green. - # The format is shared across two languages and this is the only - # check that holds this side to the bytes the other side emits. - literal = ("# Trial log\n\n" - "\n\n" - "| n | game | after | round | step | state_hash | comment |\n" - "|---|---|---|---|---|---|---|\n" - "| 1 | 1 | 4 | 3 | Select | abc123def456 | it felt harder |\n" - "\n\n") - # Caught, because the failure mode is `parse` REFUSING the writer's - # output — and an uncaught traceback names a line number where a - # named check names the defect. - try: - literal_rows = parse(literal) - except ValueError: - literal_rows = [] - check("the bytes hotseat.rs writes are the bytes this parses", - len(literal_rows) == 1 - and literal_rows[0]["variant"] == "h2-scoped-problem-stress", - "hotseat.rs's the_trial_log_records_which_rules_were_played " - "asserts this same string from the writing side") - # And the marker as it was written before CB-WP-0046, also literal. - try: - older = parse(literal.replace( - "", - "")) - except ValueError: - older = [] - check("a pre-CB-WP-0046 marker still opens a log", len(older) == 1, - "ADR-0019 D4's discipline: old logs stay readable") - - check("a legacy log can still be stamped", - parse(f"# Trial log\n\n{BEGIN} variant=ground-darvo-r0 -->\n\n" - "| n | round | step | state_hash | comment |\n|---|---|---|---|---|\n" - "| 1 | 3 | Select | abc123def456 | x |\n" - f"\n{END}\n")[0]["variant"] == "ground-darvo-r0") - # CB-WP-0032. A row of the wrong shape must be LOUD. This used to # `continue`, so a format change would have reported zero notes for # every log at once -- the failure the parser's own docstring names. diff --git a/trials/2026-08-08-1903-2.yaml b/trials/2026-08-08-1903-2.yaml deleted file mode 100644 index 2b9f2f9..0000000 --- a/trials/2026-08-08-1903-2.yaml +++ /dev/null @@ -1,156 +0,0 @@ -scenario: ground/cb-play-session -description: recorded by cb-play (CB-WP-0008 T02) -covers: [] -provisional: false -provisional_owner: '' -provisional_raised: '' -ruled: '' -ruled_by: '' -ruled_note: '' -encodes_u_item: '' -seed: 2 -setup: - players: 3 - preset: standard-3p - patch: {} -commands: -- actor: P1 - cmd: select_action - args: - action: SOLVE - problem: 1 -- actor: P2 - cmd: select_action - args: - action: SOLVE - problem: 1 -- actor: P3 - cmd: select_action - args: - action: SOLVE - problem: 1 -- actor: SYSTEM - cmd: reveal - args: {} -- actor: SYSTEM - cmd: resolve - args: {} -- actor: SYSTEM - cmd: end_round - args: {} -- actor: P1 - cmd: select_action - args: - action: INVESTIGATE - problem: 2 -- actor: P2 - cmd: select_action - args: - action: INVESTIGATE - problem: 2 -- actor: P3 - cmd: select_action - args: - action: INVESTIGATE - problem: 2 -- actor: SYSTEM - cmd: reveal - args: {} -- actor: SYSTEM - cmd: resolve - args: {} -- actor: SYSTEM - cmd: end_round - args: {} -- actor: P1 - cmd: select_action - args: - action: GROUND -- actor: P2 - cmd: select_action - args: - action: GROUND -- actor: P3 - cmd: select_action - args: - action: GROUND -- actor: SYSTEM - cmd: reveal - args: {} -- actor: P1 - cmd: choose_ground_mode - args: - mode: GR -- actor: P2 - cmd: choose_ground_mode - args: - mode: GR -- actor: P3 - cmd: choose_ground_mode - args: - mode: GR -- actor: SYSTEM - cmd: resolve - args: {} -- actor: SYSTEM - cmd: end_round - args: {} -- actor: P1 - cmd: select_action - args: - action: SOLVE - problem: 2 -- actor: P2 - cmd: select_action - args: - action: SOLVE - problem: 2 -- actor: P3 - cmd: select_action - args: - action: SOLVE - problem: 2 -- actor: SYSTEM - cmd: reveal - args: {} -- actor: SYSTEM - cmd: resolve - args: {} -- actor: SYSTEM - cmd: end_round - args: {} -- actor: P1 - cmd: select_action - args: - action: INVESTIGATE - problem: 3 -- actor: P2 - cmd: select_action - args: - action: GROUND -- actor: P3 - cmd: select_action - args: - action: GROUND -- actor: SYSTEM - cmd: reveal - args: {} -- actor: P2 - cmd: choose_ground_mode - args: - mode: GR -- actor: P3 - cmd: choose_ground_mode - args: - mode: GR -- actor: SYSTEM - cmd: resolve - args: {} -- actor: SYSTEM - cmd: end_round - args: {} -expect: - events: [] - state: {} - rejects: [] - state_hash: 5c25b5357d90376d23caa08921a448e658c71c2936ab21e84374ad76525788b6 diff --git a/trials/2026-08-08-1903-3.yaml b/trials/2026-08-08-1903-3.yaml deleted file mode 100644 index 5bf287f..0000000 --- a/trials/2026-08-08-1903-3.yaml +++ /dev/null @@ -1,179 +0,0 @@ -scenario: ground/cb-play-session -description: recorded by cb-play (CB-WP-0008 T02) -covers: [] -provisional: false -provisional_owner: '' -provisional_raised: '' -ruled: '' -ruled_by: '' -ruled_note: '' -encodes_u_item: '' -seed: 3 -setup: - players: 3 - preset: standard-3p - patch: {} -commands: -- actor: P1 - cmd: select_action - args: - action: INVESTIGATE - problem: 2 -- actor: P2 - cmd: select_action - args: - action: INVESTIGATE - problem: 2 -- actor: P3 - cmd: select_action - args: - action: INVESTIGATE - problem: 2 -- actor: SYSTEM - cmd: reveal - args: {} -- actor: SYSTEM - cmd: resolve - args: {} -- actor: SYSTEM - cmd: end_round - args: {} -- actor: P1 - cmd: select_action - args: - action: GROUND -- actor: P2 - cmd: select_action - args: - action: GROUND -- actor: P3 - cmd: select_action - args: - action: GROUND -- actor: SYSTEM - cmd: reveal - args: {} -- actor: P1 - cmd: choose_ground_mode - args: - mode: GR -- actor: P2 - cmd: choose_ground_mode - args: - mode: GR -- actor: P3 - cmd: choose_ground_mode - args: - mode: GR -- actor: SYSTEM - cmd: resolve - args: {} -- actor: SYSTEM - cmd: end_round - args: {} -- actor: P1 - cmd: select_action - args: - action: GROUND -- actor: P2 - cmd: select_action - args: - action: GROUND -- actor: P3 - cmd: select_action - args: - action: GROUND -- actor: SYSTEM - cmd: reveal - args: {} -- actor: P1 - cmd: choose_ground_mode - args: - mode: GR -- actor: P2 - cmd: choose_ground_mode - args: - mode: GR -- actor: P3 - cmd: choose_ground_mode - args: - mode: GR -- actor: SYSTEM - cmd: resolve - args: {} -- actor: SYSTEM - cmd: end_round - args: {} -- actor: P1 - cmd: select_action - args: - action: GROUND -- actor: P2 - cmd: select_action - args: - action: GROUND -- actor: P3 - cmd: select_action - args: - action: GROUND -- actor: SYSTEM - cmd: reveal - args: {} -- actor: P1 - cmd: choose_ground_mode - args: - choice: reject_reverse - mode: ND -- actor: P2 - cmd: choose_ground_mode - args: - mode: GR -- actor: P3 - cmd: choose_ground_mode - args: - mode: GR -- actor: SYSTEM - cmd: resolve - args: {} -- actor: SYSTEM - cmd: end_round - args: {} -- actor: P1 - cmd: select_action - args: - action: ATTACK - target: P3 -- actor: P2 - cmd: select_action - args: - action: GROUND -- actor: P3 - cmd: select_action - args: - action: GROUND -- actor: SYSTEM - cmd: reveal - args: {} -- actor: P1 - cmd: choose_darvo_target - args: - problem: 1 -- actor: P2 - cmd: choose_ground_mode - args: - mode: GR -- actor: P3 - cmd: choose_ground_mode - args: - mode: GR -- actor: SYSTEM - cmd: resolve - args: {} -- actor: SYSTEM - cmd: end_round - args: {} -expect: - events: [] - state: {} - rejects: [] - state_hash: ae5b6a484c408dfb951ce82d7951a7f9b9c355e9b1cec394023b198154547744 diff --git a/trials/2026-08-08-1903.yaml b/trials/2026-08-08-1903.yaml deleted file mode 100644 index 6662c6c..0000000 --- a/trials/2026-08-08-1903.yaml +++ /dev/null @@ -1,157 +0,0 @@ -scenario: ground/cb-play-session -description: recorded by cb-play (CB-WP-0008 T02) -covers: [] -provisional: false -provisional_owner: '' -provisional_raised: '' -ruled: '' -ruled_by: '' -ruled_note: '' -encodes_u_item: '' -seed: 1 -setup: - players: 3 - preset: standard-3p - patch: {} -commands: -- actor: P1 - cmd: select_action - args: - action: INVESTIGATE - problem: 2 -- actor: P2 - cmd: select_action - args: - action: SOLVE - problem: 1 -- actor: P3 - cmd: select_action - args: - action: SOLVE - problem: 1 -- actor: SYSTEM - cmd: reveal - args: {} -- actor: SYSTEM - cmd: resolve - args: {} -- actor: SYSTEM - cmd: end_round - args: {} -- actor: P1 - cmd: select_action - args: - action: SUPPORT - target: P2 -- actor: P2 - cmd: select_action - args: - action: INVESTIGATE - problem: 3 -- actor: P3 - cmd: select_action - args: - action: INVESTIGATE - problem: 3 -- actor: SYSTEM - cmd: reveal - args: {} -- actor: P2 - cmd: respond_to_support - args: - response: accept_bond -- actor: SYSTEM - cmd: resolve - args: {} -- actor: SYSTEM - cmd: end_round - args: {} -- actor: P1 - cmd: select_action - args: - action: GROUND -- actor: P2 - cmd: select_action - args: - action: INVESTIGATE - problem: 4 -- actor: P3 - cmd: select_action - args: - action: GROUND -- actor: SYSTEM - cmd: reveal - args: {} -- actor: P1 - cmd: choose_ground_mode - args: - mode: GR -- actor: P3 - cmd: choose_ground_mode - args: - mode: GR -- actor: SYSTEM - cmd: resolve - args: {} -- actor: SYSTEM - cmd: end_round - args: {} -- actor: P1 - cmd: select_action - args: - action: SOLVE - problem: 3 -- actor: P2 - cmd: select_action - args: - action: GROUND -- actor: P3 - cmd: select_action - args: - action: SOLVE - problem: 2 -- actor: SYSTEM - cmd: reveal - args: {} -- actor: P2 - cmd: choose_ground_mode - args: - mode: GR -- actor: SYSTEM - cmd: resolve - args: {} -- actor: SYSTEM - cmd: end_round - args: {} -- actor: P1 - cmd: select_action - args: - action: SOLVE - problem: 4 -- actor: P2 - cmd: select_action - args: - action: SUPPORT - target: P1 -- actor: P3 - cmd: select_action - args: - action: GROUND -- actor: SYSTEM - cmd: reveal - args: {} -- actor: P3 - cmd: choose_ground_mode - args: - mode: GR -- actor: SYSTEM - cmd: resolve - args: {} -- actor: SYSTEM - cmd: end_round - args: {} -expect: - events: [] - state: {} - rejects: [] - state_hash: 7ae42e026cd31344e4ef81b15d3cd7b6639bf9101689d99e8b50ef3614932ed8 diff --git a/workplans/CB-WP-0045-the-table-names-what-it-shows.md b/workplans/CB-WP-0045-the-table-names-what-it-shows.md index 22c4173..435df4c 100644 --- a/workplans/CB-WP-0045-the-table-names-what-it-shows.md +++ b/workplans/CB-WP-0045-the-table-names-what-it-shows.md @@ -3,7 +3,6 @@ id: CB-WP-0045 kind: product title: "The table names what it shows" status: done -state_hub_workstream_id: "4191bea7-f1ea-40b9-a957-600dabb298e2" --- # Purpose @@ -67,7 +66,6 @@ modes at the point where the action card has already left the screen. id: CB-WP-0045-T01 status: done priority: high -state_hub_task_id: "bba3d8cb-7aa2-4bee-b97f-6ff8681dbec2" ``` **Controls:** diff --git a/workplans/CB-WP-0046-the-rule-the-placement-encodes.md b/workplans/CB-WP-0046-the-rule-the-placement-encodes.md deleted file mode 100644 index 837bad8..0000000 --- a/workplans/CB-WP-0046-the-rule-the-placement-encodes.md +++ /dev/null @@ -1,128 +0,0 @@ ---- -id: CB-WP-0046 -kind: product -title: "The rule the placement encodes" -status: done ---- - -# Purpose - -``` -structural tier S (one new edition reader, one rendered passage, one - marker attribute -- no rule, no dependency moved) -declared tier S -``` - -Closes the two gaps [CB-WP-0045](CB-WP-0045-the-table-names-what-it-shows.md) -named as *not done*. - -## The gap, and the wrong reason given for it - -CB-WP-0045 recorded: - -> *"Nothing explains what a scope does … it is a `ground-game` question -> first: the scope rule is ours (Variant), not the edition's, so there is -> no vendored sentence to render."* - -**The premise was false.** `editions/experiments/h2-scoped-problem-stress/` -ships `Rules_Text.csv`, and its `Problems / Stress scope` passage is the -rule verbatim: - -> *"Each Problem has stress_scope. global: every seat. personal: the -> assigned owner only. bond: the owner and every seat connected to the -> owner through one or more Bonds (the Bond network). If the owner has no -> Bonds, bond scope behaves as personal. Rivalries do not expand bond -> scope. Multiple unclaimed Problems stack (+1 each) for seats in their -> scopes."* - -**Nothing in clay-borg had ever read the file.** Twenty-two passages of -player-facing rules, in the repo, unreachable by a player — a whole -rulebook, where CB-WP-0045 found single columns. - -**Third instance of F18's shape in two passes**, and the largest. The -pattern is now specific enough to name: *when a package adds a file rather -than a column, nothing notices.* `edition-check` verifies the digest and -freshness of files it knows; a file no reader mentions is not stale, it is -invisible. Recorded as a finding for `ground-game` rather than fixed here. - -**The escalation is the part worth keeping.** CB-WP-0045 did not just -leave a gap; it *explained* the gap with an argument that would have -stopped the next reader from looking. A wrong reason for an open item is -worse than an open item, because it retires the question. - -## The log said nothing about which rules were played - -`2026-08-08-1734`: *"Well, i did not notice any difference, maybe i did -not start the H2 variation?"* **The log cannot answer that** — and neither -could we, a week later, from the file. - -The variant rides the **begin marker**, not an eighth column: - -``` - -``` - -It is a property of the **session** (fixed at startup, constant across -`play again`), so a per-row column would repeat one fact on every line and -force a third accepted row width on a parser whose docstring is about -exactly that hazard (CB-WP-0032). The table shape is untouched. - -**Stamped from `state.variant`, never from the `--variant` flag.** Three -call sites once wrote `state.variant = v` directly instead of -`with_variant()`, which leaves H2 inert; a log stamped from the flag would -have recorded `h2` for a session that played the baseline. **A false -provenance on real player words is worse than no provenance** — which is -also why an unstamped log reports `unrecorded` and not `ground-darvo-r0`. - -## Task: render the rule, stamp the log - -```task -id: CB-WP-0046-T01 -status: done -priority: high -``` - -**Controls, all mutation-proven:** - -| mutation | what went red | -|---|---| -| the scope rule never renders | *"Problems are placed by scope and nothing says what a scope does"* | -| it renders always, baseline included | *"the baseline table explains a scope distinction that is not in play"* | -| our paraphrase instead of the passage | *"the scope explanation is not the edition's own words"* | -| the writer drops the attribute | *"the log does not say which rules were played"* | -| unstamped parses as `ground-darvo-r0` | *"an unstamped log says NOTHING, not 'baseline'"* | -| `BEGIN` back to an exact match | *"the bytes hotseat.rs writes are the bytes this parses"* | - -**The last mutation is the finding.** It went green the first time. Every -`trials.py` fixture composed its marker out of `BEGIN`, so all of them -**followed `BEGIN` wherever it went** — including away from what -`hotseat.rs` actually writes. Nine checks about the marker, and reverting -the marker broke every real stamped log with all nine still green. - -The format is shared across two languages, so the control has to be a -**literal**: the exact bytes the writer emits, asserted from the reading -side, with the writer's own test asserting the same string from the other. -A fixture built from the constant under test cannot test the constant. - -`stress_scope_rule()` matches on the **heading**, not on row 20 — a row -number is a property of today's file, a heading is a property of the rule -— and returns `None` if the edition stops carrying it (ADR-0018: an absent -explanation is reported absent, never replaced by ours). - -**Done 2026-08-08.** `make all` green; `cargo test`: 80 render + 35 play; -24 `trials.py` checks. Verified on a live `make ground VARIANT=h2` page. - -## Not done here - -- **The other 21 passages are still unread.** `h2_rules_text()` returns - the whole rulebook and one caller uses one row of it. The Round-step - passages in particular are the account of the sequence the player is - standing in, and there is nowhere on the page that says it. -- **The baseline has no `Rules_Text.csv` at all**, so this explains only - the experiment. A baseline player still gets no rulebook — the reverse - of the usual gap, and a `ground-game` question. -- **The 11 existing notes stay `unrecorded` forever.** Their variant was - never written down; inventing one is the defect this pass exists to - avoid. `2026-08-08-1818`'s three notes are almost certainly H2 on the - internal evidence of what they say, and *almost certainly* is not a - provenance.