CB-WP-0046: the rule the placement encodes, and a log that names its rules
Some checks failed
ci / check (push) Failing after 4s

CB-WP-0045 left "nothing explains what a scope does" open and gave a
FALSE reason: that scoping is our Variant so no printed sentence exists.
The H2 package ships Rules_Text.csv -- twenty-two passages of
player-facing rules -- and nothing in clay-borg had ever read the file.
A wrong reason for an open item is worse than an open item; it retires
the question. Filed as F26 against ground-game: a package that adds a
FILE is invisible where one that adds a column is not.

The scope rule now renders under the table in the edition's own words,
only when a non-global scope is in play, matched by heading rather than
row number, and absent (never paraphrased) if the edition drops it.

The trial log stamps its variant on the begin marker -- a session
property, not an eighth column -- read off state.variant rather than the
--variant flag, because a bare `state.variant = v` leaves H2 inert and a
flag-stamped log would put false provenance on real player words. An
unstamped log reports `unrecorded`, never `ground-darvo-r0`.

Six mutations, six red. The sixth is the finding: every trials.py fixture
built its marker out of BEGIN, so nine checks followed BEGIN away from
what hotseat.rs writes and stayed green while real logs broke. A fixture
built from the constant under test cannot test the constant -- the
control is now a literal, asserted from both sides.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
tegwick 2026-08-08 19:15:52 +02:00
parent df65e2dee1
commit de25c4cae0
10 changed files with 986 additions and 12 deletions

View file

@ -1306,6 +1306,25 @@ fn body(s: &mut String, view: &GroundView) {
s.push_str("<div class=\"card\">no problems in play</div>");
}
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,
"<details class=\"card\"><summary>what a Problem\u{2019}s scope does\
</summary>{}</details>",
esc(&rule)
);
}
}
s.push_str("<h2>seats</h2><div class=\"row\">");
for (id, p) in &view.players {
@ -1490,6 +1509,20 @@ fn ground_modes_text() -> Option<String> {
.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<String> {
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;