clay-borg/decisions/ADR-0013-could-we-have-won.md
tegwick 3a026b1e1f CB-WP-0025 T03: ADR-0013 -- one world after the game, and difficulty that
does not measure the bot

Seven decisions. Two are not what T03 expected, because the review moved
the ground under both.

D1: strategy fusion DOES NOT APPLY, and that is why the affordable option
is also the honest one. Fusion is a defect of aggregating over
determinizations to choose a move -- the search picking different actions
in states a player cannot distinguish. After the game there is ONE WORLD:
the deal is known, so a search over it yields a line executable in the
only world there is. The survey treated fusion as this pass's central
obstacle; it is an obstacle to a playing engine, which we are not
building. The tool answers "given the deal as it actually was, was there a
line that reached the threshold" and is labelled that way on screen --
never "how you should have played".

D4: difficulty is the WINNABLE FRACTION, not a bot's win rate. C4 killed
the bot rate -- two trivial policies span 0-100% on the same deals, and
improving the bot would make the game "easier" without a rule changing. A
measure that moves when the measurer improves is not measuring the thing.
The solver supplies the alternative: over N deals, in what proportion does
a winning line exist. That is a property of the deal distribution and the
threshold, which is what ground-game tunes and what GROUND-WP-0005 is
blocked on. Ships as a table -- winnable fraction, named reference policy,
skill gap -- never one number, with policy/N/seed-range/K in the number's
name. Stated as a LOWER BOUND, since a K-round search cannot see a line
that needed round 1.

D2: search GroundState. The survey's view-only structural boundary is not
implementable -- a view cannot fold events, and it said so in §6. The
guarantee moves to something checkable: every move in a witness is marked
visible or hidden, computed from project(). A witness reads "you could
have won, but two of these six moves needed a card you had no way to know
was coming" -- more useful than either extreme.

D3: bounded exhaustive over the last K rounds, table as one co-operative
agent, K=2 default. Affordable once C6 corrected the premise: joint
branching over the last two rounds is ~5x10^2 / 1.6x10^5 / 5.7x10^5 at
2/3/4 seats. Wording is normative: "no winning line found in the last K
rounds", never "unwinnable". PIMC and ISMCTS rejected -- they add strategy
fusion to a problem that does not have it.

D5: the harness becomes an instrument before any figure is quoted (C3) --
positive controls, --self-test in make self-tests, a make target, and a
PLURAL policy panel, because the spread between policies is what C4
exposed and hiding it would restore the error.

D6: no new crate and no port. The L declaration was an over-declaration
and is recorded as one. D7: the node cost is disputed 5x between author
and reviewer; T04 benchmarks it with criterion and neither existing figure
may be cited, including by this ADR.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 18:47:07 +02:00

230 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# ADR-0013: answer the retrospective question, and measure difficulty with the solver rather than with a bot
status: accepted
date: 2026-08-05
decided by: agent, under the standing loop authorization
tier: L (structural L — a new analysis capability whose numbers `ground-game`
will tune against; chaos d8=6 → no override). Tier L: separate survey,
adversarial review, then this document.
references: [CB-WP-0025](../workplans/CB-WP-0025-could-we-have-won.md),
[CB-RES-0008](../research/CB-RES-0008-could-we-have-won.md),
[challenge](../history/260805-could-we-have-won-challenge.md) /
[response](../history/260805-could-we-have-won-response.md),
[ADR-0012](ADR-0012-the-design-instrument.md) (admissibility),
[GameDesign.md](../specs/GameDesign.md),
GROUND-WP-0004 T02, GROUND-WP-0005 (blocked on D4)
## Context
The maintainer asked two things: *"we lost — could we have won, and how?"*
and *"do we have difficulty estimations?"*
**The survey answered the second and was wrong.** It measured
`GreedyPolicy` winning 200/200 at five and six seats and called the game
too easy there. A `FirstLegal` policy — `legal[0]`, no heuristic — scores
**0%** on the same deals. Two unsophisticated agents span the whole range,
so the measurement was about the policy.
That failure is not incidental to this ADR; **it determines D4.**
## The premise that changed, and it changes the algorithm
The survey said exhaustive search was impossible and reached for
determinized sampling, which carries strategy fusion. **Both halves were
wrong.**
- Its per-node cost was **3050× too high** (a timer bracketing whole
games). Corrected: ~34 µs per `legal_commands` call, with the exact
figure still disputed (§D7).
- Bounded exhaustive search is **affordable**: measured ~3 s over the last
two rounds at three seats.
Joint branching, treating the table as one co-operative agent — the
product over seats of the measured per-seat branching:
| seats | per-seat mean | joint per round | last 2 rounds |
|---|---:|---:|---:|
| 2 | 4.7 | ~22 | ~5×10² |
| 3 | 7.4 | ~405 | ~1.6×10⁵ |
| 4 | 9.1 | ~754 | ~5.7×10⁵ |
Against a ~10⁵10⁶ node budget, **the last two rounds are exhaustively
searchable at two, three and four seats.** Five rounds is not, at any seat
count.
---
## D1 — answer the *retrospective* question, and say so in those words
Three questions were on the table (CB-RES-0008 §3). The tool answers:
> **"Given the deal as it actually was, was there a line of play that
> reached the threshold — and here is one."**
**Strategy fusion does not apply to this question, and that is the whole
reason it is the affordable one.** Fusion is a defect of *aggregating over
determinizations to choose a move*: the search picks different actions in
states the player cannot distinguish. **After the game there is one
world.** The deck is known, the deal is known, and a search over that
single world produces a line that is executable in it — because it is the
only world there is.
The survey treated fusion as an obstacle to this pass. It is an obstacle
to a *playing* engine. We are not building one.
**What remains true is that the line may have been unfindable at the
time**, and D2 handles that by annotation rather than by refusing to
answer.
**On screen it is called** *"was this deal winnable?"* — never *"how you
should have played"*. The distinction is the honest content of the
feature, and a label that overclaims turns a true answer into a false
lesson.
## D2 — run on `GroundState`, and mark each move's information dependence
The survey's preferred guarantee was structural: search a `GroundView` so
the boundary cannot be crossed. **It is not implementable** — a view
cannot `fold` events, so a search needs a state it may not see. The survey
said so in §6 and was right to.
Decision: **search `GroundState`** — legitimate here, because post-game
the deal is public (`solution_discard` already is, and the game is over) —
and move the honesty guarantee to something checkable:
> **Every move in an emitted witness is marked `visible` or `hidden`.**
> A move is `visible` if, at the point it is played, everything it depends
> on was in the acting seat's projection: the target Problem face-up, the
> Solution in that seat's own hand. Otherwise `hidden`.
So a witness reads *"you could have won — but two of these six moves
needed a card you had no way to know was coming."* **That is a more useful
answer than either extreme**, and it is computed from `project()`, which
already exists and is already tested.
**Falsifier:** if a witness is emitted whose moves are all marked
`visible` but which no seat could actually have chosen, the marking is
wrong and D2 has failed. A test constructs exactly that case.
## D3 — bounded exhaustive over the endgame, `K` rounds, and honest wording
**Exhaustive search over the last `K` rounds**, with the table treated as
one co-operative agent choosing joint selections. `K = 2` by default,
which the measurements put inside budget at 24 seats.
- The bound is **rounds**, not nodes or seconds, because rounds are what a
player understands: *"winnable from round 4"* means something; *"winnable
within 100,000 nodes"* does not.
- A node budget is a **secondary** cut that aborts with a stated reason,
so a wide table cannot hang the page.
- **Wording is normative.** When no line is found the tool says
**"no winning line found in the last K rounds"** — never *"unwinnable"*.
A bounded search that claims unwinnability is lying, and this is the
sentence the maintainer will read.
**Not chosen: determinized sampling (PIMC), ISMCTS.** Both are for playing
under uncertainty. Here there is one world (D1), so they would add strategy
fusion to a problem that does not have it.
## D4 — difficulty is the **winnable fraction**, not any bot's win rate
**This is the decision the review forced, and it is the useful half of the
pass.**
A single-policy win rate cannot be a difficulty: two trivial policies span
0100% on the same deals. Worse, *improving the bot would make the game
"easier"* without a rule changing — a measure that moves when the
measurer improves is not measuring the thing.
The solver supplies a policy-independent alternative:
> **Winnable fraction** — over N deals at a seat count, the proportion in
> which the search finds *any* winning line within its bound.
That is a property of **the deal distribution and the threshold**, which
is exactly what `ground-game` tunes. It is the number GROUND-WP-0005 is
blocked on, and the bot rate never was.
Difficulty therefore ships as **a small table, never one number**:
| column | what it is |
|---|---|
| winnable fraction | can the deal be won at all (bounded, K stated) |
| reference-policy win rate | what a stated bot achieves — **named policy** |
| skill gap | the difference: how much play has to supply |
**Every rate carries its policy, its N, its seed range and its K in the
number's name**, not in a footnote. A figure that loses them is
inadmissible under GameDesign §1.2.
**Bounded-below caveat, stated because it will be quoted:** the winnable
fraction from a K-round search is a **lower bound** on true winnability —
a deal unwinnable in the last 2 rounds may have been winnable in round 1.
The report says "winnable-from-round-(6K)", never "winnable".
## D5 — the harness becomes an instrument before any figure is quoted
C3 established that `difficulty-baseline.rs` has no assertions, no
`--self-test` and no `make` target — nothing can turn it red. Under
CB-WP-0022 T05's own `role` distinction it is a `default` artifact wearing
a `counterexample` label, and **GameDesign §1.3 makes it inadmissible.**
Required before T06 reports anything:
- **positive controls** — a deal constructed to be unwinnable returns
none; a deal constructed to be winnable returns a witness that replays;
- **`--self-test`**, wired into `make self-tests` like every other
reporting tool;
- **`make difficulty`** (or equivalent), so the figure regenerates from
one command;
- the **policy panel is plural**: at least `greedy`, `random` and
`first-legal`, because the spread between them is what C4 exposed and
hiding it would restore the error.
## D6 — it lives in `games/ground`, not a new crate
The search needs `validate`, `fold`, `legal_commands` and `project`
all of `games_ground`. A separate crate would either re-export the
aggregate or take a dependency on it and add nothing.
**The tier was declared L on the assumption of a new capability port.
There is no port**, and that over-declaration is recorded rather than
hidden — it is a data point for the tier rules, and the L weight paid for
itself twice over regardless (§Consequences).
`cb-play` gains a mode to ask the question about a finished game; the
difficulty sweep is an example/binary, as the baseline is.
## D7 — the per-node cost is unsettled and T04 must benchmark it
The author measured **3.04.1 µs**, the reviewer **15.620.4 µs**, by
different isolations. Both agree the published 112161 µs was wrong by
12 orders; neither has established which is right.
**T04 benchmarks it with `criterion`** — already a dev-dependency, already
used by `benches/synthetic.rs` — and the spec quotes that number and no
other. **Neither figure above may be cited**, including by this ADR.
## Consequences
- `specs/` gains the witness contract and the difficulty table's shape
(T04), plus the benchmarked node cost.
- T05 builds the K-round search, the `visible`/`hidden` marking, and the
replay check.
- **T06's payload changes completely.** It reports a winnable fraction and
a policy panel to GROUND-WP-0005 — *not* the withdrawn "too easy at 56
seats". The withdrawal itself is reported, per ADR-0012 D5.
- The register gains the withdrawn finding as `inconsistent` /
`withdrawn`, so it is in the log rather than forgotten.
## What was rejected
| rejected | why |
|---|---|
| determinized sampling / PIMC / ISMCTS | strategy fusion, added to a question that has one world (D1) |
| a view-only search as a structural boundary | not implementable — a view cannot fold events |
| refusing to answer unless the line was findable at the time | throws away a true and useful answer; annotate instead (D2) |
| a single bot win rate as "difficulty" | two trivial policies span 0100% on the same deals (C4) |
| "unwinnable" as output wording | a bounded search cannot know it (D3) |
| a new crate | no port exists; it would re-export the aggregate (D6) |
| quoting either measured node cost | they disagree 5× and neither is established (D7) |