231 lines
10 KiB
Markdown
231 lines
10 KiB
Markdown
|
|
# 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 **30–50× too high** (a timer bracketing whole
|
|||
|
|
games). Corrected: ~3–4 µ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 2–4 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
|
|||
|
|
0–100% 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-(6−K)", 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.0–4.1 µs**, the reviewer **15.6–20.4 µs**, by
|
|||
|
|
different isolations. Both agree the published 112–161 µs was wrong by
|
|||
|
|
1–2 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 5–6
|
|||
|
|
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 0–100% 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) |
|