CB-WP-0045: the table names what it shows
Some checks failed
ci / check (push) Failing after 4s

Three reports from the 2026-08-08 sessions. H2 lands ("that seemed more
interesting"), two legibility gaps do not.

The number was only a FALLBACK for a missing title, so it showed exactly
when it was least useful. That was survivable while Problems sat in one
ordered row -- position WAS the number. CB-WP-0043 scattered them by
scope and took the implicit index with it, with nothing going red because
no test named the number.

DARVO.csv's mandatory_effect and Actions.csv's GROUND rules_text were
both vendored, both used only as tripwires, and neither ever reached the
player -- F18's shape again. The modes now explain themselves at the
point of choice, and NOT when no mode is on offer.

Tests assert the edition's own words verbatim (ADR-0015), the number over
every key in view.problems, and both halves of the mode explanation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
tegwick 2026-08-08 18:38:09 +02:00
parent d9b88d3140
commit fd76db4f71
7 changed files with 809 additions and 0 deletions

View file

@ -401,6 +401,9 @@ fn problem_svg(out: &mut String, priority: u32, p: &ProblemView, x: i32) {
out,
"<g data-drop=\"problem-{priority}\"><rect x=\"{x}\" y=\"10\" width=\"120\" height=\"78\" rx=\"8\" \
fill=\"{fill}\" stroke=\"#5a6b7a\"/>\
<circle cx=\"{bx}\" cy=\"24\" r=\"11\" fill=\"#1b1e26\" stroke=\"#5a6b7a\"/>\
<text x=\"{bx}\" y=\"29\" fill=\"#9cf\" font-size=\"13\" text-anchor=\"middle\">\
{priority}</text>\
<text x=\"{tx}\" y=\"36\" fill=\"#dde\" font-size=\"13\">{label}</text>\
<text x=\"{tx}\" y=\"56\" fill=\"#89a\" font-size=\"11\">{name}</text>\
<text x=\"{tx}\" y=\"74\" fill=\"#fc9\" font-size=\"11\">{sub}</text></g>",
@ -410,6 +413,14 @@ fn problem_svg(out: &mut String, priority: u32, p: &ProblemView, x: i32) {
// not remove the only identifier the player had.
name = esc(&problem_title(priority).unwrap_or_else(|| format!("priority {priority}"))),
tx = x + 10,
// CB-WP-0045: the number, on the card.
//
// Every move label says "Problem 7" and nothing on the table said
// which card that was. In the old row the position was a hint;
// once Problems are placed by scope the row is gone and the hint
// with it. The number was only ever a FALLBACK for a missing
// title — visible exactly when it was least useful.
bx = x + 104,
label = esc(&label),
sub = esc(&sub),
);
@ -941,6 +952,21 @@ fn player_card(out: &mut String, id: PlayerId, p: &PlayerView, view: &GroundView
//
// The `support` line is the one the maintainer reported: it read
// `support AcceptBond`, which names the answer and not the person.
// CB-WP-0045: **what this DARVO stage will do**, in the edition's own
// words. `DARVO.csv` carries `mandatory_effect` per stage; we vendored
// it for tripwires and never showed it, so the page said "DARVO
// Reverse" and left the player to guess. Reported as *"the GROUND and
// DARVO logic is far from self explanatory"*.
if p.darvo != games_ground::DarvoStage::Off {
if let Some(text) = darvo_stage_text(p.darvo) {
let _ = write!(
out,
"<details><summary>what {:?} does</summary>{}</details>",
p.darvo,
esc(&text)
);
}
}
if let Some(m) = view.ground_modes.get(&id) {
let _ = write!(
out,
@ -1432,6 +1458,38 @@ fn support_words(r: &games_ground::SupportResponse, who: Option<PlayerId>) -> St
}
}
/// The edition's own words for what a DARVO stage does (CB-WP-0045).
///
/// From `DARVO.csv`'s `mandatory_effect`. **Not our paraphrase**: the game
/// says what its stages do and ADR-0015's discipline is to use that.
fn darvo_stage_text(stage: games_ground::DarvoStage) -> Option<String> {
use games_ground::DarvoStage;
let want = match stage {
DarvoStage::Off => return None,
DarvoStage::Deny => "DENY",
DarvoStage::Attack => "ATTACK",
DarvoStage::Reverse => "REVERSE",
};
games_ground::edition::darvo_stages()
.ok()?
.into_iter()
.find(|s| s.stage == want)
.map(|s| s.mandatory_effect)
}
/// The GROUND card's own account of its three modes (CB-WP-0045).
///
/// Shown where the mode is chosen. The card explains GR, OU and ND in one
/// passage; by the Reveal step the action card is no longer on screen, so
/// the explanation has to travel to the decision.
fn ground_modes_text() -> Option<String> {
games_ground::edition::actions()
.ok()?
.into_iter()
.find(|c| c.id.to_uppercase().contains("GROUND"))
.map(|c| c.rules_text)
}
/// 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;
@ -1605,6 +1663,23 @@ fn move_section(
);
}
}
// CB-WP-0045: when a GROUND mode is on offer, the card's own
// account of the three modes travels with the decision. By the
// Reveal step the action card has left the screen, so the player
// was choosing GR / OU / ND from three names alone.
if legal
.iter()
.any(|c| matches!(c, games_ground::GroundCommand::ChooseGroundMode { .. }))
{
if let Some(text) = ground_modes_text() {
let _ = write!(
s,
"<details class=\"card\" open><summary>what the GROUND modes do\
</summary>{}</details>",
esc(&text)
);
}
}
s.push_str("</div><div class=\"row\">");
for (i, c) in legal.iter().enumerate() {
let spatial = seat.is_some_and(|seat| crate::input::affordance(c, seat).is_some());

View file

@ -697,6 +697,114 @@ mod gamelog {
}
}
/// **The number is on the card** (CB-WP-0045).
///
/// Every move label says "Problem 7"; nothing on the table said which
/// card that was. In the old row, position was a hint — once
/// Problems are placed by scope the row is gone and the hint with it.
/// The number was only ever a fallback for a missing title, so it
/// appeared exactly when it was least useful.
#[test]
fn every_problem_card_shows_its_number() {
let v = crate::testfix::view(Some(PlayerId(0)));
let text = crate::text_of(&crate::doc::document(
&v,
&[],
"/c",
Some(PlayerId(0)),
false,
));
for key in v.problems.keys() {
assert!(
text.contains(&format!("{key}")),
"Problem {key} is on the table with no number, and the move \
labels call it by one"
);
}
}
/// **The game's own words for a DARVO stage** (CB-WP-0045).
///
/// Reported as *"the GROUND and DARVO logic is far from self
/// explanatory"*. `DARVO.csv` carries `mandatory_effect` per stage —
/// vendored for tripwires and never shown, so the page said "DARVO
/// Reverse" and left the player to guess. F18's shape again: the data
/// was present and could not fire.
#[test]
fn a_darvo_stage_says_what_it_does() {
let v = crate::testfix::view(Some(PlayerId(0)));
let text = crate::text_of(&crate::doc::document(
&v,
&[],
"/c",
Some(PlayerId(0)),
false,
));
// The fixture has a seat at Reverse.
assert!(
text.contains("what Reverse does"),
"a seat in a DARVO stage does not say what the stage does"
);
// And it is the EDITION's sentence, not a paraphrase of ours.
let edition = games_ground::edition::darvo_stages()
.expect("DARVO.csv")
.into_iter()
.find(|s| s.stage == "REVERSE")
.expect("REVERSE");
let head: String = edition.mandatory_effect.chars().take(40).collect();
assert!(
text.contains(&head),
"the stage text is not the edition's own words: {head:?}"
);
}
/// **The GROUND modes explain themselves at the decision** (CB-WP-0045).
///
/// By the Reveal step the action card has left the screen, so the
/// player was choosing GR / OU / ND from three names alone.
#[test]
fn the_ground_modes_explain_themselves_where_they_are_chosen() {
use games_ground::{GroundCommand, GroundMode};
let v = crate::testfix::view(Some(PlayerId(0)));
let legal = vec![GroundCommand::ChooseGroundMode {
mode: GroundMode::Gr,
choice: None,
}];
let with = crate::text_of(&document_with_log(
&v,
&legal,
crate::TEST_ENDPOINTS,
Some(PlayerId(0)),
false,
Account::of(&[]),
&[],
));
assert!(
with.contains("what the GROUND modes do"),
"the mode choice is offered with no account of the modes"
);
assert!(
with.contains("Ground & Restate"),
"the explanation is not the card's own text"
);
// Absent when no mode is on offer — an always-on wall of rules is
// how a player stops reading them.
let without = crate::text_of(&document_with_log(
&v,
&[],
crate::TEST_ENDPOINTS,
Some(PlayerId(0)),
false,
Account::of(&[]),
&[],
));
assert!(
!without.contains("what the GROUND modes do"),
"the mode explanation shows when no mode is being chosen"
);
}
/// **A Problem is drawn where its Stress lands** (CB-WP-0043).
///
/// H2 scopes End-of-Round Stress, so a row across the middle — true

View file

@ -0,0 +1,158 @@
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: 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: SOLVE
problem: 2
- actor: P2
cmd: select_action
args:
action: INVESTIGATE
problem: 3
- 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: SUPPORT
target: P2
- 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: P2
cmd: respond_to_support
args:
response: accept_bond
- 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: INVESTIGATE
problem: 4
- actor: P3
cmd: select_action
args:
action: INVESTIGATE
problem: 4
- 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: P3
- actor: P2
cmd: select_action
args:
action: SOLVE
problem: 4
- actor: P3
cmd: select_action
args:
action: GROUND
- actor: SYSTEM
cmd: reveal
args: {}
- actor: P3
cmd: choose_ground_mode
args:
mode: GR
- actor: P3
cmd: respond_to_support
args:
response: accept_bond
- actor: SYSTEM
cmd: resolve
args: {}
- actor: SYSTEM
cmd: end_round
args: {}
expect:
events: []
state: {}
rejects: []
state_hash: ff50bedd79bef9ac80f3481d56a3b50b1f3cdef7d265b831ca17cdadbdf49e86

View file

@ -0,0 +1,187 @@
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:
choice: protect_problem
mode: OU
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: {}
- 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: 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: {}
- 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: P1
cmd: choose_darvo_target
args:
problem: 2
- 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: 1c5d6828619d00851bfd6048082074044a7343d606df4062f11d696f05fbc988

19
trials/2026-08-08-1818.md Normal file
View file

@ -0,0 +1,19 @@
# Trial log
What the player said while playing, bound to the position they said it at
(CB-WP-0027, ADR-0014). The recording beside this file is the position;
`state_hash` is what reaches it.
**These are raw notes and they stay here.** Nothing in this file travels to
`ground-game` (ADR-0014 D4) — a note reaches them only by being promoted to a
register finding, by a human, with the wording chosen then.
<!-- trial-log:begin -->
| n | game | after | round | step | state_hash | comment |
|---|---|---|---|---|---|---|
| 1 | 1 | 36 | 5 | after the end | 73661a4ab998 | Ok, that seemed more interesting and we lost. I am not sure how ground actually works. lets try again. |
| 2 | 2 | 35 | 5 | after the end | ff50bedd79be | We did it this time. |
| 3 | 3 | 44 | 5 | after the end | 1c5d6828619d | The GROUND and DARVO logic is far from self explanatory. That needs to be improved. |
<!-- trial-log:end -->

160
trials/2026-08-08-1818.yaml Normal file
View file

@ -0,0 +1,160 @@
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: INVESTIGATE
problem: 4
- 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: 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: SUPPORT
target: P1
- actor: P3
cmd: select_action
args:
action: SOLVE
problem: 2
- actor: SYSTEM
cmd: reveal
args: {}
- actor: P1
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: SOLVE
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: 73661a4ab99852cd098bed5fd34ad08b6999195a8b0fe0f8a0f695e5cac1c1b4

View file

@ -0,0 +1,102 @@
---
id: CB-WP-0045
kind: product
title: "The table names what it shows"
status: done
---
# Purpose
```
structural tier S (rendering only -- no rule, no dependency, no budget,
no artifact contract moved)
declared tier S
```
First declaration after [ADR-0021](../decisions/ADR-0021-the-chaos-roll-is-retired.md)
retired the chaos roll.
## The reports
Three sessions, `make trials` (notes now bind per game — ADR-0019 holds:
`g1+0`, `g2+35`, `g3+44`):
> *"Ok, that seemed more interesting and we lost."* / *"We did it this
> time."* — 2026-08-08 1818
The H2 dynamics land. Two things do not:
> *"The GROUND and DARVO logic is far from self explanatory. That needs to
> be improved."* — 2026-08-08 1818
> *"As the cards are now located differently on the table i cant tell which
> is problem 1, which 2 and so on."*
## The numbering defect [CB-WP-0043] caused
Every move label says **"Problem 7"**. The number was rendered on the card
only as a **fallback for a missing title** — so it appeared exactly when
it carried least information, and was absent whenever the card was
identifiable enough to be worth pointing at.
That was survivable while Problems sat in one ordered row: **position was
the number**. [CB-WP-0043](CB-WP-0043-a-problem-sits-where-its-stress-lands.md)
scattered them by scope, deleted the row, and took the implicit index with
it — without anything going red, because no test named the number.
**A rendering change removed a fact the page had been carrying
positionally.** Not the ADR-0018 family (nothing computes the wrong
subject); the nearer relative is CB-WP-0024 — a fact held by presentation
alone does not survive the presentation changing.
## The legibility defect: vendored and never shown
`DARVO.csv` carries `mandatory_effect` per stage. `Actions.csv` carries
`rules_text` for the three GROUND modes. **Both were vendored, both were
used only as tripwires, and neither ever reached the player.** The seat
panel said `DARVO Reverse`; the move buttons offered `GR` / `OU` / `ND`.
**F18's shape once more**: the edition's own words were present in the
repo and could not fire. The player was asked to choose between three
modes at the point where the action card has already left the screen.
## Task: put the number and the words on the table
```task
id: CB-WP-0045-T01
status: done
priority: high
```
**Controls:**
- **every Problem on the table shows its number** — asserted over
`view.problems.keys()`, so a card that renders without one goes red
whatever the fixture holds;
- **the explanation is the edition's sentence, not our paraphrase**
the DARVO test re-reads `DARVO.csv` and asserts a prefix of
`mandatory_effect` appears verbatim (ADR-0015);
- **the GROUND modes explain themselves where they are chosen** — present
when `ChooseGroundMode` is legal, and **absent when it is not**. An
always-on wall of rules is how a player stops reading them, so the
negative half is asserted too;
- both explanations are `<details>`, so the account can be closed.
**Done 2026-08-08.** 78 tests pass; `make all` green; verified against a
live `make ground VARIANT=h2` page — badges `1 2 3 4 …` and the header
reading `rules h2-scoped-problem-stress`.
## Not done here
- **Nothing explains what a *scope does*.** H2 is the variation under
study, the marker now says *whose* Problem it is and where it sits — and
no text on the page says that a scoped Problem presses only the owner's
bond network. The player can see the placement and still not know the
rule it encodes. This is the largest remaining gap and 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 trial log header does not record the variant.** A note from an H2
session and a note from a baseline session are indistinguishable in the
log, which is a real problem now that both are being played.
- **The `<details>` are closed-by-default for DARVO, open for GROUND.**
Chosen, not measured; whether a player opens the closed one is untested.