clay-borg/history/260801-cb-wp-0008-log.md

103 lines
4.7 KiB
Markdown
Raw Normal View History

# 2026-08-01 — CB-WP-0008 delivery notes
Kept out of the workplan file so it stays loadable (`loop-lint`).
## T01 — bots
`games/ground/src/bot.rs`, 8 tests. `Policy` + `RandomPolicy` (seeded
`ChaChaRng`) + `GreedyPolicy` (stated heuristic, deterministic), a
`legal_commands` generator that filters candidates through `validate`, and
a driver that reaches `GameEnded`.
Measured, 3-player, seed 42, both policies to GR-R09's fifth round:
| policy | commands | events | claimed | total vs threshold |
|---|---|---|---|---|
| random | 36 | 81 | 1 | 2 / 7 |
| greedy | 30 | 51 | 2 | 3 / 7 |
**The module is not gated on `scenarios`; its tests are.** A bot needs
only the aggregate, but the setup presets live behind that feature. That
is a real seam and it is left visible rather than papered over with a
feature the bot does not need.
### What the second consumer found
INTENT's second-use rule is why T01 was worth doing before the CLI. Three
findings, none of them fixed here, because fixing a rule from inside a bot
is exactly the invention-in-isolation the rule guards against.
1. **GR-A13 admits SOLVE against an already-claimed Problem.** The rule
names "a face-up, non-Denied Problem"; `claimed_by` is not mentioned,
so `check_targeting` accepts it and resolution then does nothing. The
action is silently wasted. Greedy hit this immediately — it spent all
fifteen selections re-solving Problem 1 and scored 1. The **policy**
now avoids it; the **rule** is untouched and this is a U-item
candidate for GroundRules.
2. **The 3-player standard fixture cannot succeed.** GR-S01 gives 3
Problems at 3–4 players, the preset values them by priority (1, 2, 3 —
maximum 6), and GR-E01 sets the 3–4p threshold at **7**.
`group_success` is therefore unreachable at 3 players regardless of
play. No scenario noticed because no scenario plays to scoring with
maximal claiming. The fixture is documented as placeholder ("Problem
content is scenario data") — so this is evidence that the placeholder
is not neutral, not that GR-E01 is wrong.
3. **K13's `Project` trait has zero implementors.** `grep` finds no
`impl Project` anywhere in the tree. T02 is its first consumer, and
the projection will be written against a trait that has never been
exercised.
### Process
The acceptance tests were mutation-checked by hand (no `mutation-check`
row — that denominator is the GameKernel acceptance table, and adding a
game-level row would move a metric by changing its question).
**The first mutation was a no-op and SURVIVED**: it changed
`apply(...)?` to `let _ = apply(...)`, which still issues the command.
Removing the `Resolve` call outright turned three tests red for their
stated reason. Third recorded instance of the weak-mutation class — the
retrospective in `260801-instrument-the-table-retrospective.md` predicted
mutation strength would stay a standing maintenance cost, and this is
that cost arriving on the next pass.
## T02 — `cb-play`
`tools/cb-play` (`make play`), 6 tests. A human seat is a `Policy` like
any bot, so the CLI adds no second driver: `HumanPolicy` renders the
projection, lists the legal commands, and reads an index or `pass`.
- **K13's first implementor** is `games/ground/src/view.rs`. Hidden:
other seats' face-down selections until Reveal, hands (count only), the
undealt deck (count only), a face-down Problem's suit and value, and
**the seed** — not secret content, but a seat holding it can compute the
deck.
- **A played session becomes an artifact.** `--record FILE` writes it as a
scenario the runner executes; `--replay DIR` writes a `.cbreplay`
bundle. `games/ground/src/record.rs` is the inverse of
`parse_command`, and its warrant is a round-trip test over every
command shape — an encoder checked against hand-written expectations
only agrees with itself.
### Two vacuous tests, caught by counting
The acceptance clause *"a seat's projection never contains another seat's
hidden selection"* passed twice while asserting nothing.
1. The first version asserted the rendered text contained `face-down` —
which it does, from **Problems**, in every game ever rendered.
2. The counted version then reported **0 inspected entries**: seats are
asked in order, so a human at P1 is always prompted before anyone has
selected and never sees a hidden selection at all.
Seating the human at **P3** exercises the rule; the test now counts what
it inspected and requires at least four. Mutating the projection
(`revealed = true`) fails it for its stated reason.
This is the same class as the AM-2 `expect` that matched passing output —
an assertion that cannot distinguish the two worlds. The remedy that
worked both times was **counting what the harness examined**, not
strengthening the predicate.