CB-WP-0037 done: T03 classification and T04 evidence
Some checks failed
ci / check (push) Failing after 5s

Reading Player_Mats.csv and Glossary.csv — which O4 had forbidden
declaring about until they were read — found a rule. Player_Mats'
choice_rule column IS GR-R03: at Stress 4-5 choose ATTACK or GROUND unless
you spend a ready Freedom token. The engine's stress_gated and
allowed_under_stress_gate match it exactly, and now have a tripwire.

So Ornamentation.md gains §1.3: the unit is a column, not a file.
Classifying Player_Mats as ornamentation would have thrown a rule away
with the colour swatches. A mixed artifact is normal — a player mat is a
rule printed on a decorated card in the box too. O4 is rewritten and
re-grounded from provisional to rule accordingly.

O5, O6, O7 declared with falsifiers. O7's is not theoretical:
back_design_id is unread in four vendored files, and hidden information is
what card backs are for.

CB-EV-0029 answers T04's question per rule rather than as a count — yes,
every checkable clause — and states the reading that result does NOT
support: it shows GroundRules.md was a faithful secondary source for the
clauses with behaviour to check, not that the engine is correct.

10 of 19 files vendored. Nine still unread, and Rules_Text.csv is the one
that matters: every clause checked here was checked against our own
transcription of it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
tegwick 2026-08-08 00:33:24 +02:00
parent 87414d7c1d
commit 627992bc85
10 changed files with 497 additions and 12 deletions

View file

@ -75,6 +75,8 @@ const TOKENS_CSV: &str = include_str!("../../../editions/ground-darvo-r0/Tokens.
const DARVO_CSV: &str = include_str!("../../../editions/ground-darvo-r0/DARVO.csv");
const RELATIONS_CSV: &str = include_str!("../../../editions/ground-darvo-r0/Relations.csv");
const SCENARIOS_CSV: &str = include_str!("../../../editions/ground-darvo-r0/Scenarios.csv");
const PLAYER_MATS_CSV: &str = include_str!("../../../editions/ground-darvo-r0/Player_Mats.csv");
const GLOSSARY_CSV: &str = include_str!("../../../editions/ground-darvo-r0/Glossary.csv");
/// A vendored CSV, parsed into rows addressable by column name.
///
@ -502,6 +504,42 @@ pub fn scenarios() -> Result<Vec<ScenarioText>, String> {
Ok(out)
}
/// The stress gate, printed on every player mat (CB-WP-0037 T03).
///
/// **A mat is mostly ornamentation with one rule on it.** Symbol, colour
/// and title decorate; `choice_rule` is GR-R03. All six seats print the
/// same rule, so this returns it once and checks they agree.
pub fn choice_rule() -> Result<String, String> {
let t = Table::parse(PLAYER_MATS_CSV, "Player_Mats.csv")?;
let mut seen: Option<String> = None;
for row in &t.rows {
let rule = t.get(row, "choice_rule")?.to_string();
match &seen {
None => seen = Some(rule),
Some(first) if *first == rule => {}
Some(first) => {
return Err(format!(
"player mats disagree on the stress gate: {first:?} vs {rule:?}"
))
}
}
}
seen.ok_or_else(|| "Player_Mats.csv has no rows".to_string())
}
/// The game's own words for its own terms (CB-WP-0037 T03).
pub fn glossary() -> Result<Vec<(String, String)>, String> {
let t = Table::parse(GLOSSARY_CSV, "Glossary.csv")?;
let mut out = Vec::new();
for row in &t.rows {
out.push((
t.get(row, "term")?.to_string(),
t.get(row, "definition")?.to_string(),
));
}
Ok(out)
}
/// One relation side as the edition states it (CB-WP-0037 T02).
#[derive(Debug, Clone)]
pub struct RelationText {
@ -667,6 +705,8 @@ mod card_text_tests {
let _ = darvo_stages();
let _ = relations();
let _ = scenarios();
let _ = choice_rule();
let _ = glossary();
let files = [
("Problems.csv", CSV),
@ -677,6 +717,8 @@ mod card_text_tests {
("DARVO.csv", DARVO_CSV),
("Relations.csv", RELATIONS_CSV),
("Scenarios.csv", SCENARIOS_CSV),
("Player_Mats.csv", PLAYER_MATS_CSV),
("Glossary.csv", GLOSSARY_CSV),
];
let mut report = String::new();
let mut total_unread = 0usize;
@ -708,6 +750,52 @@ mod card_text_tests {
println!("{report}");
}
/// **The stress gate is printed on the mats** (CB-WP-0037 T03).
///
/// `Player_Mats.csv` looked like pure ornamentation — a symbol, a
/// colour, a title per seat — and one of its columns is GR-R03:
/// *"At Stress 0-3 choose any action. At Stress 4-5 choose ATTACK or
/// GROUND unless you spend a ready Freedom token."*
///
/// **This is why the classification unit is a column, not a file.**
/// Declaring the mats ornamental would have discarded a rule the
/// engine implements, alongside the colour swatches it sits next to.
///
/// A tripwire, like T02's: the engine's `stress_gated` and
/// `allowed_under_stress_gate` were written from `GroundRules.md`,
/// and this pins the edition sentence they answer to.
#[test]
fn the_mats_still_print_the_stress_gate_the_engine_enforces() {
let rule = choice_rule().expect("Player_Mats.csv");
// `stress_gated`: stress >= 4, unless Freedom lifted the gate.
assert!(
rule.contains("At Stress 4\u{2013}5") || rule.contains("At Stress 4-5"),
"the gate's threshold moved; `stress_gated` uses >= 4: {rule:?}"
);
// `allowed_under_stress_gate`: ATTACK and GROUND, and only those.
assert!(
rule.contains("ATTACK or GROUND"),
"the gate's admitted actions changed: {rule:?}"
);
assert!(
rule.contains("ready Freedom token"),
"the escape hatch changed; the engine spends Freedom: {rule:?}"
);
// GR-L01's two slots are restated in the glossary, and
// `has_free_slot` is written against that number.
let g = glossary().expect("Glossary.csv");
let slot = g
.iter()
.find(|(term, _)| term == "Relation slot")
.map(|(_, d)| d.clone())
.expect("Relation slot is defined");
assert!(
slot.contains("two"),
"the glossary no longer says two relation slots: {slot:?}"
);
}
/// **F25** — the engine hardcodes numbers the edition states.
///
/// `GroundState::threshold` is a `match` returning 5/7/9;