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.