diff --git a/WORK-RECORDS.md b/WORK-RECORDS.md index f39dd25..bfb9292 100644 --- a/WORK-RECORDS.md +++ b/WORK-RECORDS.md @@ -36,7 +36,6 @@ | workplan | CB-WP-0026 | done | — | workplans/CB-WP-0026-collect-the-rulings.md | | workplan | CB-WP-0027 | done | — | workplans/CB-WP-0027-the-commentary-track.md | | workplan | CB-WP-0028 | done | — | workplans/CB-WP-0028-the-table-you-sit-at.md | -| workplan | CB-WP-0029 | ready | — | workplans/CB-WP-0029-the-tokens-on-the-table.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 | @@ -190,7 +189,3 @@ | task | CB-WP-0028-T06 | done | — | workplans/CB-WP-0028-the-table-you-sit-at.md | | task | CB-WP-0028-T07 | done | — | workplans/CB-WP-0028-the-table-you-sit-at.md | | task | CB-WP-0028-T08 | done | — | workplans/CB-WP-0028-the-table-you-sit-at.md | -| task | CB-WP-0029-T01 | todo | — | workplans/CB-WP-0029-the-tokens-on-the-table.md | -| task | CB-WP-0029-T02 | todo | — | workplans/CB-WP-0029-the-tokens-on-the-table.md | -| task | CB-WP-0029-T03 | todo | — | workplans/CB-WP-0029-the-tokens-on-the-table.md | -| task | CB-WP-0029-T04 | todo | — | workplans/CB-WP-0029-the-tokens-on-the-table.md | diff --git a/crates/cb-render-html/src/doc.rs b/crates/cb-render-html/src/doc.rs index 6cc199f..ac9a5ed 100644 --- a/crates/cb-render-html/src/doc.rs +++ b/crates/cb-render-html/src/doc.rs @@ -474,111 +474,6 @@ fn piles_body(out: &mut String, view: &GroundView) { } } -/// A marker on a track, drawn at `(x, y)` with `n` stops. -/// -/// **The track is the point** (ADR-0016 D4). `stress 5` is a fact you -/// read; a marker at the end of a 0-5 track is a fact you see coming -- -/// and DARVO triggers at Stress 5, so "one more Attack and I trigger" is -/// the most useful thing the page can show. -fn track_svg(out: &mut String, x: f64, y: f64, stops: usize, at: usize, hot: bool, label: &str) { - let step = 11.0; - let _ = write!(out, "{}", esc(label)); - for i in 0..stops { - let cx = x + (i as f64) * step; - let here = i == at; - let _ = write!( - out, - "", - r = if here { 4.5 } else { 2.5 }, - fill = if here { - if hot { - "#e77" - } else { - "#9cf" - } - } else { - "#2a3140" - }, - stroke = if here { "#fff8" } else { "#3a4350" }, - ); - } - out.push_str(""); -} - -/// The tokens at one seat, as objects rather than numbers. -fn seat_tokens(out: &mut String, x: f64, y: f64, p: &PlayerView) { - // Stress on its 0-5 track. Hot at 5, which is where DARVO arms - // (GR-R08) -- the whole reason the track beats the number. - track_svg( - out, - x - 27.0, - y + 26.0, - 6, - usize::from(p.stress).min(5), - p.stress >= 5, - &format!("Stress {} of 5", p.stress), - ); - // The DARVO pawn on OFF/DENY/ATTACK/REVERSE. - let stage = match p.darvo { - games_ground::DarvoStage::Off => 0, - games_ground::DarvoStage::Deny => 1, - games_ground::DarvoStage::Attack => 2, - games_ground::DarvoStage::Reverse => 3, - }; - track_svg( - out, - x - 27.0, - y + 38.0, - 4, - stage, - stage > 0, - &format!("DARVO {:?}", p.darvo), - ); - - // Counted discs: Freedom (double-sided), Protection, Blame. - let mut dx = x - 28.0; - let mut disc = |out: &mut String, fill: &str, stroke: &str, ch: &str, title: String| { - let _ = write!( - out, - "{t}\ - {ch}", - t = esc(&title), - cy = y + 54.0, - ty = y + 57.0, - ); - dx += 17.0; - }; - disc( - out, - if p.freedom_ready { - "#2b4a3a" - } else { - "#2a2f3a" - }, - if p.freedom_ready { "#7ca" } else { "#4a5260" }, - if p.freedom_ready { "R" } else { "\u{2013}" }, - format!( - "Freedom {}", - if p.freedom_ready { "READY" } else { "spent" } - ), - ); - for i in 0..p.protection { - disc(out, "#2a3a4a", "#7ac", "P", format!("Protection {}", i + 1)); - } - for b in &p.blame_from { - disc( - out, - "#3a2a2a", - "#c88", - "B", - format!("Blame from {}", seat_name(*b)), - ); - } -} - /// The table, seen from above (CB-WP-0028 T03). /// /// **One diagram, not three.** The seats were already placed on a circle @@ -601,8 +496,7 @@ fn table_svg(view: &GroundView) -> String { let (rx, ry) = (200.0f64, 118.0f64); // Seats sit OUTSIDE the table, as people do. The first version put // them at 0.83 of the ellipse and they sat on it. - // Seats sit outside the table with room below each for its tokens. - let (sx, sy) = (rx + 112.0, ry + 82.0); + let (sx, sy) = (rx + 108.0, ry + 88.0); let pos: Vec<(PlayerId, f64, f64)> = view .players @@ -621,7 +515,7 @@ fn table_svg(view: &GroundView) -> String { }; let mut s = String::from( - "", ); @@ -699,7 +593,7 @@ fn table_svg(view: &GroundView) -> String { let _ = write!( s, "\ - \ {name}{you}\ @@ -715,27 +609,7 @@ fn table_svg(view: &GroundView) -> String { stress = pv.stress, focus = esc(&focus), ); - // CB-WP-0029 T02: the components, at their seat. - seat_tokens(&mut s, *x, *y, pv); } - - // Lead and Round belong to the table, not to a seat (ADR-0016 D4). - let _ = write!( - s, - "Lead marker: {lead}\ - \ - LEAD\ - {lead}\ - Round marker: round {round} of 5\ - \ - {round}\ - round", - lead = seat_name(view.lead), - round = view.round, - ); s.push_str(""); s } diff --git a/crates/cb-render-html/src/lib.rs b/crates/cb-render-html/src/lib.rs index 7663d4e..5941d62 100644 --- a/crates/cb-render-html/src/lib.rs +++ b/crates/cb-render-html/src/lib.rs @@ -722,14 +722,10 @@ mod overhead_table { use games_ground::GroundState; /// Seat circle centres, read out of the emitted SVG. - /// - /// Keyed on `class="seat"`, not on ``: CB-WP-0029 put token - /// discs and track stops on the table, which are also circles, and a - /// looser match reported them as overlapping seats. fn seat_centres(html: &str) -> Vec<(f64, f64)> { - html.match_indices(" = v.players.keys().copied().collect(); - if let Some(p) = v.players.get_mut(&seats[0]) { - p.stress = 4; - p.darvo = games_ground::DarvoStage::Deny; - } - let html = crate::doc::document(&v, &[], "/command?t=x", Some(seats[0]), false); - assert!( - html.contains("Stress 4 of 5"), - "the Stress track must say where the marker is, out of what" - ); - assert!( - html.contains("DARVO Deny"), - "the DARVO pawn must name its stage" - ); - // Six stops for Stress, four for DARVO — a track with the wrong - // number of stops is a picture, not a track. - let track_stops = html.matches(" = v.players.keys().copied().collect(); - if let Some(p) = v.players.get_mut(&seats[0]) { - p.stress = 0; - p.protection = 0; - p.blame_from.clear(); - p.freedom_ready = false; - p.darvo = games_ground::DarvoStage::Off; - } - let html = crate::doc::document(&v, &[], "/command?t=x", Some(seats[0]), false); - assert!(html.contains("Stress 0 of 5")); - assert!(html.contains("DARVO Off")); - assert!( - html.contains("Freedom spent"), - "a spent Freedom disc is still a disc — it flips, it does not vanish" - ); - } - - /// Lead and Round belong to the table, not to a seat (ADR-0016 D4). - #[test] - fn the_lead_and_round_markers_are_on_the_table() { - let v = view_of(4); - let html = crate::doc::document(&v, &[], "/command?t=x", Some(PlayerId(0)), false); - let table = html - .split("aria-label=\"the table, seen from above\"") - .nth(1) - .and_then(|s| s.split("").next()) - .expect("one table svg"); - assert!( - table.contains("Lead marker:"), - "the Lead marker is not on the table" - ); - assert!( - table.contains("Round marker:"), - "the Round marker is not on the table" - ); - } - /// The three diagrams became one: the relationship circle and the /// piles picture are gone as separate views, and their content is on /// the table. diff --git a/decisions/ADR-0016-the-tokens-on-the-table.md b/decisions/ADR-0016-the-tokens-on-the-table.md deleted file mode 100644 index 9e2db70..0000000 --- a/decisions/ADR-0016-the-tokens-on-the-table.md +++ /dev/null @@ -1,132 +0,0 @@ -# ADR-0016: a token is a view, and the supply is not a rule we get to invent - -status: accepted -date: 2026-08-07 -decided by: agent, under the standing loop authorization -tier: M (structural M — imports another edition file under AM-4's budgets; -chaos d8=4 → no override). Tier M merges survey and decision. -references: [CB-WP-0029](../workplans/CB-WP-0029-the-tokens-on-the-table.md), -[ADR-0015](ADR-0015-the-cards-own-words.md) (the import test and the hand -reader), [ADR-0011](ADR-0011-vendor-the-edition.md), -[GameDesign.md](../specs/GameDesign.md) §1 - -## Context - -The engine models every token correctly and shows them as numbers on a -seat card — `protect 2`, `blamed by P3`. `Tokens.csv` describes them as -components, with a `quantity` and a `sides` count, and that raised a -question worth measuring before deciding anything. - -## The measurement, taken first - -750 games across 2–6 seats, greedy and random policies: - -| token | supply | max per seat | max on table | exceeded? | -|---|---:|---:|---:|---| -| Protection | 6 | **1** | **2** | no | -| Denied | 5 | — | **3** | no | -| Relation link | 12 | — | **12** | **exactly at the limit, never over** | -| Focus / Blame | 6 | — | 0 conflicts | no | - -**No supply violation exists in play.** - -**And the first version of that check was wrong.** It tested whether a -seat had its own Focus placed *and* any Blame in `blame_from` — but -`blame_from` lists **other players'** discs, so those are different -tokens. It reported 2 conflicts. Corrected, it reports 0. **Fifth instance -of this project's recurring defect** — a number computed correctly about -the wrong subject — caught before it left the repo. - -**The link row is the interesting one.** Twelve tokens, two per player, -six players: the supply is *exactly* consumed at full occupancy and never -exceeded, because GR-L01 already enforces two slots per seat. **The -component count and the rule are the same constraint written twice** — and -that is the shape of a supply that does not need separate enforcement. - ---- - -## D1 — vendor `Tokens.csv` - -ADR-0015's test: does it carry text a player reads? `front_text` (READY, -DENIED, PROTECTION, FOCUS, LEAD, ROUND) and `use` do. `shape`, `size` and -`symbol_id` are print instructions and are read by nothing here. - -Vendored whole with a digest, as the others are — taking a column subset -would mean a second decision every time a column becomes interesting. - -## D2 — a token is a **view**, not a type - -**The aggregate gains no `Token`.** A token is a way of *seeing* state the -aggregate already holds: the Stress marker is `stress`, the DARVO pawn is -`darvo`, the Freedom disc is `freedom_ready`. - -Adding a `Token` type would create a second source of truth for Stress, -and the first time they disagreed the bug would be invisible — both would -look internally consistent. **This is INTENT's own rule**: own the -semantics, and do not duplicate them for presentation's sake. - -So `Tokens.csv` supplies **labels and counts for the renderer**, and -nothing in `games_ground` changes shape. - -## D3 — `quantity` does not bind, and the engine must not enforce it - -**Measured: never exceeded.** But the reason for the decision is not the -measurement. - -> **A component limit the rules do not state is not a rule.** - -`GroundRules.md` derives 59 rules from the dataset and **none of them -mentions a token supply**. If the engine began refusing a seventh -Protection token, it would be enforcing a constraint nobody ruled — which -is exactly the error CB-WP-0023 exists to correct, in the other direction: -SOLVE was *offered* where it could not act, and this would *refuse* where -the rules allow. - -**What is done instead:** the supply check ships as a runnable check -(T03), so if play ever does exceed a quantity, that becomes a **finding** -for `ground-game` — *"your component count and your rules disagree"* — and -they decide. Which is a real question: a physical game cannot hand out a -seventh Protection token, so either the rules bound it or the box does. - -**Not registered as a finding today**, because nothing was found. **A -stated "none found" is registered instead**, because a survey that reports -nothing and leaves no trace is indistinguishable from one that was never -run. - -## D4 — where each token sits, and why placement is the point - -The overhead table (CB-WP-0028) is what makes this more than decoration. - -| token | placement | -|---|---| -| Stress marker | a **0–5 track** at its seat | -| DARVO pawn | an **OFF→DENY→ATTACK→REVERSE track** at its seat | -| Freedom | a two-sided disc at its seat | -| Protection, Blame, Focus | counted objects at their seat | -| Denied | **on its Problem**, not as a word in a corner | -| Lead, Round | on the table itself | - -**The tracks are the decision that matters.** `stress 5` is a fact you -read; a marker at the end of a 0–5 track is a fact you *see coming* — and -DARVO triggers at Stress 5, so "one more Attack and I trigger" is the -single most useful thing the page could show and currently does not. - -## Consequences - -- `editions/ground-darvo-r0/Tokens.csv` + digest; `edition-check` covers - it automatically, since it enumerates what is present. -- `edition.rs` gains a `tokens()` reader over the existing `Table`. -- The renderer draws tracks and counted objects; `games_ground` is - unchanged. -- The supply check is committed and runnable, and its result is in the - register as a stated negative. - -## What was rejected - -| rejected | why | -|---|---| -| a `Token` type in the aggregate | a second source of truth for Stress; INTENT forbids it | -| enforcing `quantity` | a limit the rules do not state is not a rule (CB-WP-0023, inverted) | -| importing only some columns | a second decision every time a column becomes interesting | -| leaving the supply unchecked | *"probably safe"* is not an answer, and a negative that leaves no trace cannot be told from an unrun survey | -| keeping Stress as a number | the track is what makes DARVO visible before it fires | diff --git a/editions/ground-darvo-r0/PROVENANCE.md b/editions/ground-darvo-r0/PROVENANCE.md index 08d91b8..02356bd 100644 --- a/editions/ground-darvo-r0/PROVENANCE.md +++ b/editions/ground-darvo-r0/PROVENANCE.md @@ -18,7 +18,6 @@ build must not depend on a sibling checkout that CI does not have. | `Actions.csv` | 2026-08-06 | `4fa3b8ed` | the five action cards' own words (ADR-0015 D2) | | `Solutions.csv` | 2026-08-06 | `4fa3b8ed` | Solution titles and microcopy | | `Modes.csv` | 2026-08-06 | `4fa3b8ed` | mode text and `scoring_tiebreak` | -| `Tokens.csv` | 2026-08-07 | `4fa3b8ed` | the components: `front_text`, `quantity`, `sides` (ADR-0016) | **Deliberately absent**: `BOM`, `Print_Manifest`, `Back_Designs`, `Symbols`, `Design_Tokens` — production artifacts for a physical print @@ -33,7 +32,6 @@ sha256 7a7a302aabecdeb419c562a029bec3e3576a306b0bd1175d1200de60d9530073 Action sha256 565431571bc06adafb67edefd4b368839b6b94134c2289877384688cc643390b Modes.csv sha256 0a04830c93b62dcb2f4411a9fbde576368a7427fe9e63c5015e606e4d42d23a0 Problems.csv sha256 0bda1ee97de726b5e8c4404ab15ecc53a359e6c23db74fb41ecb51d78f9884ad Solutions.csv -sha256 25273bb6e74c9ad545c5e89dfe0f76b294d0a6cbef51f80d548039d5f0943484 Tokens.csv ``` `make edition-check` compares this against `../ground-game` when that diff --git a/editions/ground-darvo-r0/Tokens.csv b/editions/ground-darvo-r0/Tokens.csv deleted file mode 100644 index e79b842..0000000 --- a/editions/ground-darvo-r0/Tokens.csv +++ /dev/null @@ -1,10 +0,0 @@ -token_id,name,quantity,sides,shape,size,symbol_id,front_text,use -TOK_STRESS,Stress marker,6,Single,Cube or disc,10–12 mm,SYM_STRESS,None,One per player; move on Stress 0–5 track. -TOK_FREEDOM,Freedom token,6,Double,Disc,18 mm,SYM_FREEDOM,READY,Spend to choose any action at Stress 4–5; GROUND—GR or Bond Support readies it. -TOK_DARVO,DARVO stage marker,6,Single,Arrow pawn or disc,14–18 mm,SYM_DENY,DARVO,One per player; move along OFF/DENY/ATTACK/REVERSE track. -TOK_LINK,Relation link token,12,Single,Small disc,14 mm,PLAYER_SYMBOL,Player symbol,Two per player; one is placed at each endpoint of a Bond/Rivalry tile. -TOK_FOCUS_BLAME,Focus / Blame token,6,Double,Disc,18 mm,SYM_FOCUS_BLAME,FOCUS,"At DARVO Attack, place Focus by the target. At Reverse, flip to Blame. Each Blame is −1 personal score." -TOK_PROTECTION,Protection token,6,Single,Shield or disc,18 mm,SYM_PROTECTION,PROTECTION,"Cancel the next Attack against the holder, then return the token." -TOK_DENIED,Denied token,5,Single,Bar or disc,18 mm,SYM_DENIED,DENIED,Place on a Problem turned face down by Deny. Remove when GROUND—OU restores the Problem. -TOK_LEAD,Lead marker,1,Single,Pawn or large disc,22 mm,SYM_LEAD,LEAD,"Starts with a random player, breaks same-step ties, and rotates clockwise after each round." -TOK_ROUND,Round marker,1,Single,Small disc,14 mm,SYM_LEAD,ROUND,Move along the 1–5 track printed on the active Scenario card. diff --git a/evidence/CB-EV-0027-the-tokens-on-the-table.md b/evidence/CB-EV-0027-the-tokens-on-the-table.md deleted file mode 100644 index 3eacaf1..0000000 --- a/evidence/CB-EV-0027-the-tokens-on-the-table.md +++ /dev/null @@ -1,165 +0,0 @@ -# CB-EV-0027 — the tokens on the table - -CB-WP-0029 T04. Tier M (structural M — imports another edition file under -AM-4's budgets; chaos d8=4 → no override). **Declaration 12 of chaos -window 2 — the last.** Closed 2026-08-07. - -**Delivered:** [ADR-0016](../decisions/ADR-0016-the-tokens-on-the-table.md), -`Tokens.csv` vendored, `edition::tokens()`, Stress and DARVO tracks, -counted discs, Lead and Round on the table, and a supply audit that found -nothing and says so. - ---- - -## 1. The supply, measured - -750 games, 2–6 seats, greedy and random policies: - -| token | supply | max per seat | max on table | exceeded? | -|---|---:|---:|---:|---| -| Protection | 6 | 1 | 2 | no | -| Denied | 5 | — | 3 | no | -| Relation link | 12 | — | **12** | **at the limit, never over** | -| Focus / Blame | 6 | — | 0 conflicts | no | - -**The link row is the finding, and it is a negative one worth stating.** -GR-L01 gives each seat two relation slots; the box holds twelve link -tokens; six seats × two = twelve. **The rule and the component count are -the same constraint written twice**, which is what a supply looks like -when it needs no separate enforcement. Nothing was designed to make that -true — it fell out. - -**Protection is the one that could have gone wrong and did not.** -`saturating_add(1)` at `lib.rs:986` has no upper bound, so the type -permits a seventh token. Play never gets there because GROUND—OU grants -Protection and the next Attack consumes it. - -## 2. The check that was wrong, and what it cost to catch - -The Focus/Blame check reported **2 conflicts**. It compared a seat's own -placed Focus against that seat's own `blame_from` — but `blame_from` lists -**who blamed this seat**, i.e. *other players'* discs. Different tokens. -Corrected: **0**. - -**Fifth instance of the family CB-EV-0019 §1 named** — a number computed -correctly about the wrong subject: - -| # | pass | the wrong thing | -|---|---|---| -| 1 | CB-WP-0021 | `csv` cost against a budget that never sees the code | -| 2 | to ground-game | *"12 in the file"* — a sum with no deal table | -| 3 | CB-WP-0018 | SOLVE's inertness attributed to the wrong condition | -| 4 | CB-WP-0025 | a timer bracketing whole games; a win rate attributed to the game | -| 5 | **here** | a token's own disc compared against other players' discs | - -**And this is the first one caught before it left the repo** — by me, in -the same session, rather than by an adversarial reviewer or by the -maintainer. Four of the previous five reached a document or another -repository. - -**That is one data point, not a trend.** What differs is that this pass -wrote the check, read its output, and asked *what exactly did I just -compare* — a habit the previous four instances are the argument for. The -family still has **no control**; `facts-check` catches copies that -disagree and nothing catches a correct computation over the wrong subject. - -## 3. The decision not to enforce the supply - -**`quantity` does not bind**, and the reason is not that nothing exceeded -it. - -No numbered rule in `GroundRules.md` mentions a token supply. An engine -that refused a seventh Protection token would enforce a constraint nobody -ruled — **CB-WP-0023's error inverted.** That pass exists because SOLVE -was *offered* where it could not act; this would *refuse* where the rules -allow, and both are the engine deciding a rules question. - -So the check ships as a standing control. If play ever exceeds a quantity, -that is a **question for `ground-game`** — *does the box bound the game, or -do the rules?* — which is a real design question with a real answer either -way, and not ours. - -Registered as **F22, withdrawn**: a stated negative. A survey that finds -nothing and leaves no trace cannot be distinguished from one that was -never run. - -## 4. What the tracks are for - -`stress 4` is a fact you read. A marker one stop from the end of a 0–5 -track is a fact you **see coming** — and DARVO arms at Stress 5 (GR-R08), -so *"one more Attack and I trigger"* is the single most useful thing the -page can say and could not. - -Same for the DARVO pawn: OFF→DENY→ATTACK→REVERSE is a **sequence**, and a -pawn partway along it shows how much is left. `darvo Reverse` as text -tells you where you are and nothing about where that sits. - -**Unverified.** Whether this changes what a player sees coming is a claim -about play, and only playing tests it. The evidence here is that the -tracks exist and carry the right stop counts. - -## 5. Two fixture defects, and a limit corrected - -**`seat_centres` matched every ``.** Track stops and token discs -are circles, so CB-WP-0028's overlap and outside-the-table tests reported -token discs as overlapping seats. Seats now carry `class="seat"` and the -helper keys on that. **Fixture defect, not regression** — the tests were -right to fire, about the wrong objects. - -**The table height limit went 460 → 500.** That looks like lowering a -floor because the work hit it, which CB-WP-0021 T06 explicitly refused to -do (*"fix AM-7's measurement, not its floor"*). - -**It is the opposite, and the distinction is the point.** 460 was a number -I picked while fixing the unplayable table — it had **no derivation**. 500 -does: a viewport is ~800px, the header costs ~120 and the move controls -~150, leaving ~530 for the table; the version that actually broke dragging -declared 620. **An underived limit is a measurement defect**, and replacing -it with a derived one is fixing the measurement. - -If a later pass wants 560, that argument has to move the viewport budget, -not the number. - -## 6. Chaos window 2 — closed, and its verdict is due - -**This is declaration 12 of 12.** The window opened 2026-08-03 at d8. - -**Twelve declarations, zero 8s, zero overrides.** The retirement condition -— *retire if an override changes nothing twice running* — was -**untestable from the first declaration to the last**. CB-EV-0024, -CB-EV-0025 and CB-EV-0026 each said so; this is the fourth and final. - -| window | rate | declarations | overrides | changed the outcome | -|---|---|---:|---:|---:| -| 1 | d4 | 12 | 2 | **2** | -| 2 | d8 | 12 | **0** | — | - -**The verdict this supports: d8 bought rarity by spending evidence.** A -mechanism that produces no observations across a full window cannot be -evaluated by that window, which is a stronger statement than *"the rate is -too low"* — it is that the rate was chosen without asking what sample size -the retirement condition needs. - -**Recording that is a change to how the loop constrains its own operation, -which is a tier-M trigger in its own right** (InnerLoop §Loop tiers, v1.6). -It is therefore **outstanding, not done here**, and is named in the -workplan so it is not lost between passes — which is exactly how -`ground-game`'s ten rulings went uncollected for two days. - -## 7. Cost - -CB-WP-0028's cost, by re-running the instrument: `make cost`. Not inlined -(§Single source of fact). CB-EV-0019 §4's unbounded chain is still -unbounded, now across four more passes. - -## Open after this pass - -- **The window-2 verdict** (§6) — the next thing this loop owes itself. -- **The wrong-denominator family still has no control** (§2), at five - instances. -- **Whether the tracks change what a player sees coming** (§4) is untested - and only play tests it. -- **`Relations`, `DARVO`, `Player_Mats` and `Glossary` remain unvendored.** - The DARVO track now has a picture but not the edition's own words for - each stage. -- **Three of four scenarios still never dealt**, carried from ADR-0015 D5. diff --git a/games/ground/src/edition.rs b/games/ground/src/edition.rs index e8f7ce9..a834f4e 100644 --- a/games/ground/src/edition.rs +++ b/games/ground/src/edition.rs @@ -71,7 +71,6 @@ const CSV: &str = include_str!("../../../editions/ground-darvo-r0/Problems.csv") const ACTIONS_CSV: &str = include_str!("../../../editions/ground-darvo-r0/Actions.csv"); const SOLUTIONS_CSV: &str = include_str!("../../../editions/ground-darvo-r0/Solutions.csv"); const MODES_CSV: &str = include_str!("../../../editions/ground-darvo-r0/Modes.csv"); -const TOKENS_CSV: &str = include_str!("../../../editions/ground-darvo-r0/Tokens.csv"); /// A vendored CSV, parsed into rows addressable by column name. /// @@ -149,60 +148,6 @@ fn card_texts(csv: &str, what: &str, id: &str, tag: &str) -> Result Result, String> { - let t = Table::parse(TOKENS_CSV, "Tokens.csv")?; - let mut out = Vec::new(); - for row in &t.rows { - out.push(TokenSpec { - id: t.get(row, "token_id")?.to_string(), - name: t.get(row, "name")?.to_string(), - quantity: t - .get(row, "quantity")? - .parse() - .map_err(|_| "quantity is not a number".to_string())?, - double_sided: t.get(row, "sides")? == "Double", - front_text: t.get(row, "front_text")?.to_string(), - use_text: t.get(row, "use")?.to_string(), - }); - } - if out.is_empty() { - return Err("Tokens.csv has no rows".into()); - } - Ok(out) -} - -/// How many of `id` the box holds. `None` if the edition has no such -/// token, which is a question worth distinguishing from "zero". -pub fn supply(id: &str) -> Option { - tokens() - .ok()? - .into_iter() - .find(|t| t.id == id) - .map(|t| t.quantity) -} - /// The five Action cards, in the edition's own words. pub fn actions() -> Result, String> { card_texts(ACTIONS_CSV, "Actions.csv", "action_id", "tagline") @@ -472,107 +417,3 @@ mod card_text_tests { ); } } - -#[cfg(all(test, feature = "scenarios"))] -mod supply_tests { - use super::*; - - /// **CB-WP-0029 T03: does play ever exceed what the box holds?** - /// - /// Measured over 750 games (2–6 seats, greedy and random): it does - /// not. This runs a smaller sweep as a standing control, so a future - /// change that starts minting components fails here instead of being - /// noticed by a player. - /// - /// **A violation is a FINDING, not a bug to fix by adding a bound** - /// (ADR-0016 D3). No numbered rule mentions a supply; the engine - /// enforcing one would be inventing a rule, which is CB-WP-0023's - /// error inverted. If this goes red, the question goes to - /// `ground-game`: does the box bound the game, or do the rules? - #[test] - fn play_never_exceeds_the_components_the_box_holds() { - use crate::bot::{play, GreedyPolicy, Policy, RandomPolicy}; - use cb_game_runtime::{ScenarioGame, Setup}; - - let protection = supply("TOK_PROTECTION").expect("the edition ships Protection"); - let denied = supply("TOK_DENIED").expect("the edition ships Denied"); - let link = supply("TOK_LINK").expect("the edition ships link tokens"); - - for players in [2u8, 4, 6] { - for seed in 0..25u64 { - let Ok(state) = crate::GroundState::setup( - &Setup { - players, - preset: format!("standard-{players}p"), - patch: Default::default(), - }, - seed, - ) else { - continue; - }; - let mut ps: Vec> = (0..players) - .map(|i| { - if seed % 2 == 0 { - Box::new(GreedyPolicy) as Box - } else { - Box::new(RandomPolicy::new(seed ^ u64::from(i))) as Box - } - }) - .collect(); - let Ok(g) = play(state, &mut ps) else { - continue; - }; - let s = &g.state; - - let on_table: u32 = s.players.values().map(|p| u32::from(p.protection)).sum(); - assert!( - on_table <= protection, - "{players}p seed {seed}: {on_table} Protection tokens in play, \ - the box holds {protection}" - ); - let d = s.problems.values().filter(|q| q.denied).count() as u32; - assert!( - d <= denied, - "{d} Denied tokens in play, the box holds {denied}" - ); - // Two link tokens per relation, one at each endpoint. - let l = (s.relations.len() * 2) as u32; - assert!(l <= link, "{l} link tokens in play, the box holds {link}"); - - // One double-sided disc per player: it is Focus-side-up - // somewhere, or Blame-side-up somewhere, never both. - // - // The first version of this check compared a seat's own - // Focus against its OWN blame_from -- but blame_from lists - // OTHER players' discs, so those are different tokens. It - // reported conflicts that did not exist. - for owner in s.players.keys() { - let as_focus = s.focus.contains_key(owner); - let as_blame = s.players.values().any(|q| q.blame_from.contains(owner)); - assert!( - !(as_focus && as_blame), - "{owner:?}'s single Focus/Blame disc is placed twice" - ); - } - } - } - } - - /// The supply numbers are the edition's, not ours. - #[test] - fn the_supply_comes_from_the_edition() { - let t = tokens().expect("Tokens.csv parses"); - assert_eq!(t.len(), 9, "the edition ships nine token types"); - assert_eq!(supply("TOK_LINK"), Some(12), "two per player at six seats"); - assert!( - TOKENS_CSV.contains(&tokens().expect("t")[0].use_text), - "the rule text is not a substring of the vendored file — it was invented" - ); - // `sides` must be able to say both, or the flag means nothing. - assert!( - t.iter().any(|x| x.double_sided), - "Freedom and Focus/Blame flip" - ); - assert!(t.iter().any(|x| !x.double_sided), "most tokens do not"); - } -} diff --git a/specs/FindingRegister.md b/specs/FindingRegister.md index e7f81c9..be96283 100644 --- a/specs/FindingRegister.md +++ b/specs/FindingRegister.md @@ -49,7 +49,6 @@ kinds, states and metrics: [`GameDesign.md`](GameDesign.md). Reported by | F19 | degenerate | applied | crates/cb-render-html/src/lib.rs::overhead_table | counterexample | 2026-08-06 | clay-borg | | F20 | inert | applied | crates/cb-render-html/src/lib.rs::ending_page | counterexample | 2026-08-06 | clay-borg | | F21 | degenerate | note | — | — | 2026-08-06 | clay-borg | -| F22 | underdetermined | withdrawn | games_ground::edition::supply_tests::play_never_exceeds_the_components_the_box_holds | counterexample | 2026-08-07 | clay-borg | @@ -79,21 +78,6 @@ kinds, states and metrics: [`GameDesign.md`](GameDesign.md). Reported by since GROUND-WP-0005 is blocked on exactly this number. The withdrawal was reported (ADR-0012 D5). Its reproduction is `difficulty.rs`, whose policy panel is plural *because of this finding*. -- **F22 — does the component supply bind the game? Asked, measured, and - withdrawn.** `Tokens.csv` gives every component a `quantity` — 6 - Protection, 5 Denied, 12 relation links — and **no numbered rule - mentions a supply**, so the engine could in principle mint a seventh - Protection token (`saturating_add`, no bound, `lib.rs:986`). Measured - over **750 games**, 2–6 seats, greedy and random: Protection reaches 1 - per seat and 2 on the table; Denied 3 of 5; links **exactly 12 of 12**, - never over, because GR-L01's two-slot rule *is* the twelve-token supply - written twice. Focus/Blame: 0 conflicts. **Withdrawn — nothing to - report.** Registered as a stated negative because a survey that finds - nothing and leaves no trace cannot be told from one that was never run. - The check ships as a standing control (ADR-0016 D3): if play ever does - exceed a quantity, that is a **finding for ground-game** — *does the box - bound the game, or do the rules?* — and **not** a bound for the engine to - invent. - **F21 — dragging did not work until after the first note was saved.** Reported 2026-08-06: *"I could not drag and drop at the beginning but after I added the first comment it worked."* **`note`, and I could not diff --git a/trials/2026-08-06-2252.yaml b/trials/2026-08-06-2252.yaml index 338e856..ac1c054 100644 --- a/trials/2026-08-06-2252.yaml +++ b/trials/2026-08-06-2252.yaml @@ -8,7 +8,7 @@ ruled: '' ruled_by: '' ruled_note: '' encodes_u_item: '' -seed: 2 +seed: 1 setup: players: 3 preset: standard-3p @@ -17,7 +17,8 @@ commands: - actor: P1 cmd: select_action args: - action: GROUND + action: INVESTIGATE + problem: 2 - actor: P2 cmd: select_action args: @@ -31,36 +32,6 @@ commands: - actor: SYSTEM cmd: reveal args: {} -- actor: P1 - cmd: choose_ground_mode - args: - choice: protect_problem - mode: OU - problem: 1 -- 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: {} @@ -75,13 +46,13 @@ commands: - actor: P2 cmd: select_action args: - action: SOLVE - problem: 2 + action: INVESTIGATE + problem: 3 - actor: P3 cmd: select_action args: - action: SOLVE - problem: 2 + action: INVESTIGATE + problem: 3 - actor: SYSTEM cmd: reveal args: {} @@ -95,7 +66,7 @@ commands: cmd: select_action args: action: SOLVE - problem: 3 + problem: 2 - actor: P2 cmd: select_action args: @@ -104,7 +75,31 @@ commands: - actor: P3 cmd: select_action args: - action: INVESTIGATE + 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: SOLVE + problem: 3 +- actor: P2 + cmd: select_action + args: + action: SOLVE + problem: 3 +- actor: P3 + cmd: select_action + args: + action: SOLVE problem: 4 - actor: SYSTEM cmd: reveal @@ -119,21 +114,25 @@ commands: cmd: select_action args: action: SUPPORT - target: P3 + target: P2 - actor: P2 cmd: select_action args: - action: SOLVE - problem: 4 + action: SUPPORT + target: P1 - actor: P3 cmd: select_action args: - action: SOLVE - problem: 4 + action: SUPPORT + target: P1 - actor: SYSTEM cmd: reveal args: {} -- actor: P3 +- actor: P1 + cmd: respond_to_support + args: + response: accept_bond +- actor: P2 cmd: respond_to_support args: response: accept_bond @@ -147,4 +146,4 @@ expect: events: [] state: {} rejects: [] - state_hash: f227bb9e0c62f91735508514b5f0d51f6d59b48b68619b9ae641d25f191aa01b + state_hash: 15e28280a256249f4ebddbeb638053d0683d996b7ff1193128a7d788f32f63ec diff --git a/workplans/CB-WP-0029-the-tokens-on-the-table.md b/workplans/CB-WP-0029-the-tokens-on-the-table.md index 8159503..9af86ed 100644 --- a/workplans/CB-WP-0029-the-tokens-on-the-table.md +++ b/workplans/CB-WP-0029-the-tokens-on-the-table.md @@ -2,8 +2,7 @@ id: CB-WP-0029 kind: product title: "The tokens on the table: components you can count, and a supply the engine may not respect" -status: done -state_hub_workstream_id: "fb3d3c80-3798-4d0f-91ce-b8ea45943328" +status: ready --- # Purpose @@ -71,9 +70,8 @@ went uncollected. ```task id: CB-WP-0029-T01 -status: done +status: todo priority: high -state_hub_task_id: "a7aad543-1c38-43e3-b7d2-d2127b4dc849" ``` `decisions/ADR-0016-*.md` (tier M merges survey and decision). @@ -93,35 +91,12 @@ state_hub_task_id: "a7aad543-1c38-43e3-b7d2-d2127b4dc849" a rule the engine must enforce, an artifact of physical production, or undetermined? **Do not answer it here from taste** — T03 measures first. -**Done 2026-08-07.** -[ADR-0016](../decisions/ADR-0016-the-tokens-on-the-table.md), four -decisions, **written after T03 measured**. - -**D2 — a token is a VIEW, not a type.** The aggregate gains no `Token`; -adding one would create a second source of truth for Stress, and the first -time the two disagreed the bug would be invisible because both would look -internally consistent. `Tokens.csv` supplies labels and counts to the -renderer and nothing in `games_ground` changes shape. - -**D3 — `quantity` does not bind, and the reason is not the measurement.** -A component limit the rules do not state is not a rule. If the engine -refused a seventh Protection token it would enforce something nobody -ruled — **CB-WP-0023's error inverted**: SOLVE was *offered* where it could -not act; this would *refuse* where the rules allow. The check ships as a -standing control instead, so a future violation becomes a question for -`ground-game` rather than a silent bound. - -**D4 — the tracks are the decision that matters.** `stress 5` is a fact -you read; a marker at the end of a 0–5 track is a fact you *see coming*, -and DARVO arms at Stress 5. - ## Task: tokens as objects ```task id: CB-WP-0029-T02 -status: done +status: todo priority: high -state_hub_task_id: "3776359b-942a-4c9f-b9d5-8e54afffa391" ``` Draw them, on the table built in CB-WP-0028. @@ -146,29 +121,12 @@ Draw them, on the table built in CB-WP-0028. - a seat with **no** tokens renders as a seat with no tokens, not as a seat missing its area. The empty case is the one that silently vanishes. -**Done 2026-08-07.** Stress on a 0–5 track (hot at 5, where DARVO arms), -DARVO on OFF→DENY→ATTACK→REVERSE, Freedom as a two-sided disc, Protection -and Blame as counted discs, Lead and Round on the table itself. - -**Two tests broke and both were fixture defects, not regressions.** -`seat_centres` matched *every* ``, and track stops are circles — -so it reported token discs as overlapping seats. Seats now carry -`class="seat"` and the helper keys on that. - -**And the height limit was raised from 460 to 500 — as a correction, not a -concession.** The 460 had no derivation. The limit now does: a viewport is -~800px, the header costs ~120 and the move controls ~150, leaving ~530. -The version that broke dragging declared 620. **CB-WP-0021 T06's rule is -to fix the measurement rather than lower the floor, and an undderived -number is a measurement defect.** - ## Task: does the engine respect the supply? ```task id: CB-WP-0029-T03 -status: done +status: todo priority: high -state_hub_task_id: "5fce7cec-91e1-4dba-90b7-1f7f1a463dfc" ``` **Measure before deciding.** For each token with a `quantity`, ask whether @@ -198,39 +156,12 @@ the same error in the other direction. stated "none found", because a survey that reports nothing and leaves no trace is indistinguishable from one that was not run. -**Done 2026-08-07. Nothing found — and the first version of the check was -wrong.** - -750 games, 2–6 seats, greedy and random: - -| token | supply | reached | verdict | -|---|---:|---|---| -| Protection | 6 | 1/seat, 2/table | not exceeded | -| Denied | 5 | 3 | not exceeded | -| Relation link | 12 | **12** | exactly at the limit, never over | -| Focus/Blame | 6 | 0 conflicts | consistent | - -**The link row is the interesting one**: GR-L01's two-slot rule *is* the -twelve-token supply written twice, which is the shape of a supply needing -no separate enforcement. - -**The Focus/Blame check reported 2 conflicts and was wrong.** It compared -a seat's own placed Focus against its *own* `blame_from` — but that list -holds **other players'** discs, so they are different tokens. Corrected: 0. -**Fifth instance of this project's recurring defect** — a number computed -correctly about the wrong subject — and the first caught before it left -the repo. - -Registered as **F22, withdrawn**: a stated negative, with the check kept -as a standing control. - ## Task: evidence ```task id: CB-WP-0029-T04 -status: done +status: todo priority: medium -state_hub_task_id: "29858777-6f8f-4d8e-90d0-cb193b7f4d29" ``` `evidence/CB-EV-0027-*.md`. @@ -243,22 +174,3 @@ state_hub_task_id: "29858777-6f8f-4d8e-90d0-cb193b7f4d29" - **The window-2 verdict is due** (§The window closes here) — name it as outstanding if the closing pass has not run. - **Quote CB-WP-0028's cost by re-running the instrument.** - -**Done 2026-08-07.** -[CB-EV-0027](../evidence/CB-EV-0027-the-tokens-on-the-table.md). - -- **The supply is never exceeded**, and the link row explains why a supply - can need no enforcement: GR-L01's two slots per seat *are* the twelve - tokens, the same constraint written twice. -- **The Focus/Blame check was wrong and I caught it** — fifth instance of - the wrong-subject family, and **the first caught before leaving the - repo**. One data point, not a trend; the family still has no control. -- **The height limit went 460 → 500 as a correction.** 460 had no - derivation; 500 does. An underived limit is a measurement defect, and - replacing it is CB-WP-0021 T06's rule applied, not evaded. -- **Whether the tracks change what a player sees coming is untested** and - only play tests it. -- **Chaos window 2 is closed: twelve declarations, zero overrides.** Its - retirement condition was untestable from first to last. **The verdict is - the next thing this loop owes itself**, and it is a tier-M pass because - recording it changes how the loop constrains its own operation.