Spadecraft
Handed the worst legal card in a position, this review flags it 20 times as often as it flags a competent one. That number is the product.
Most Spades apps deal you a hand and keep score. This one reviews the hand afterwards, card by card, and says what each decision cost in points — and then publishes what that review is worth, with the sample size and the error bar beside it.
20.0×
more often the review flags the worst legal card than a competent one — both graded in the same positions
+10.34
points a hand over a frozen copy of the first version, ± 0.79 over 6,000 hands
49.4%
the control: two identical sides over 2,000 games. A number printed without one is not a result
Your partner already had this trick with Q♥. Overtaking with K♥ spends a winner on a trick you had; 4♥ keeps it in hand.
This is one of them. Every card you play gets one, and the sentence is written from the position rather than pulled from a library of tips.
The hard part of a review is not writing the sentence. It is being right often enough that the sentence is worth reading — and that is measurable, so we measure it.
Take a hand played out by a frozen reference policy. At every decision, substitute a different card into that same position and grade it three ways: the card a competent player chose, a random legal card, and the card the engine ranks last. The positions are identical, so the only thing varying is the card.
| card played | n | mean loss | flagged mistake+ | accuracy |
|---|---|---|---|---|
| a competent card | 100 | 0.62 | 1% | 96.3% |
| a random legal card | 100 | 4.95 | 9% | 58.3% |
| the worst-ranked card | 100 | 8.57 | 20% | 33.3% |
A corpus of 10 hands played end to end by the frozen policy, which never moves — so a change to the bot cannot quietly change the test set. dart run eval/run.dart discrimination
A decision is scored by what it cost against the best card available in that exact position — in points, over the rest of the hand.
The harsh labels start at 8 on purpose: that is the threshold the table above is measured at. Below it the engine cannot separate a bad card from an unlucky one, so it says nothing rather than guessing at you.
Strength is measured against a frozen snapshot of the first version, which shares the rules and none of the policy. Self-play was tried and it measures nothing: it reported half a point for a change worth five, because improving the bot improves its opponents in the same breath.
| hero's card play | net points a hand | set rate |
|---|---|---|
| the rules layer | −0.5 | 5.9% |
| always the highest card | −22.5 | 16.0% |
| always the lowest card | −20.5 | 17.1% |
| a random legal card | −23.8 | 18.9% |
Every row plays the same 1,500 deals with the same bids against the shipping engine, so the only thing that varies is the hero's card play. The weak rows are not competitors — they are the floor the rows above them are measured against. dart run eval/run.dart strength
Above the rules sits a search that samples the hands it cannot see and plays every legal card out. Two seats searching at 160 sampled deals net +7.10 ± 3.43 points a hand over two rule-layer seats, across 300 hands dealt both ways round — a figure about a side, not a seat. Played to 500 instead, the searching side won 95.8% of 24 games: a small sample, printed as one. dart run eval/run.dart searchgain
And it is beatable, which is a brag rather than a hedge, because we can put a number on that too. A side −10.9 points a hand worse than its opponents still takes better than a third of the games off them: the stronger side wins 63.1% over 2,000 games, and no more. A game to 500 is about eleven hands and a noisy unit, which is why a form here is a band rather than a number, and why a single hand gets a verdict rather than a percentage.
The engine charges 100 points for the tenth bag, and a seat holding nine of them was bidding exactly as it would with none. Letting the bid see the bag column is worth 9.2 points a game ± 7.2 over 3,200 games, and it shipped.
Five rules built on the rest of the match score were measured the same way — 16,000 games each, every game paired against a control on the same deal — and they are all switched off:
| rule | points a game | games won |
|---|---|---|
| shade the bid to the winning number | −3.37 ± 0.46 | −1.1% |
| shade, keeping a trick in hand | −1.16 ± 0.33 | −0.4% |
| stretch up to the winning number | −0.24 ± 0.45 | 0.0% |
| take every viable nil when far behind | +0.41 ± 0.22 | 0.0% |
| chase the set that ends the game | −0.56 ± 0.28 | 0.0% |
The one positive figure moves no games at all, so it is off with the others. The first estimate of the bag column, at 80 games, read 41.2 ± 45.1 and would have been a wrong answer confidently given; it settled only past a thousand games. Read off the tool on 8 and 9 September 2026; it is 16,000 games a variant, so no build re-runs it. dart run tool/score_value.dart
Not good and bad. A review that cannot say which kind of error you made cannot pick you a drill or describe an opponent. The categories came out of a census of played hands — 29,640 decisions reviewed over 19 seats, 1,726 of them graded a mistake or worse — and not out of a guess about what beginners do.
Six of the seventeen — the five commonest, and the one that costs the most — with each one's share of the graded mistakes. Anything below the mistake band is excluded from the census, because counting it would be counting noise. Read off the tool on 24 September 2026; it reviews every decision it counts and takes about twelve minutes, so no build re-runs it. The corpus is bots, and a bot is wrong in different ways than a person is. dart run tool/mistake_census.dart
The ladder is not a difficulty slider. Every room is a table of players who hold one specific wrong idea, and you move up by learning to punish it. Each idea has a price, measured the same way as everything else.
Nil means a bad hand — the idea the second room is built on — costs the seat holding it −26.41 points a hand, and the side it sits on −60.60. Those are different questions and the figures are never mixed up. A seat believing nothing false, measured over the same 1,200 deals, reads +1.47 ± 1.67 — the zero this scale hangs from. dart run eval/run.dart misconceptions
Before a card exists, the app publishes a commitment to the deal. Your device then mixes in a seed of its own, so neither side picked the hand alone. After the hand, the server seed is revealed and anyone can recompute the whole thing.
The formula above is the whole scheme, so a hand record carries everything a check needs: the commitment, both seeds and the deal they produce. Export one from the review screen — it is plain text, and the two lines above are enough to recompute it by hand or by script.
What is not true yet: the specification and the verifier that reads these records are written but not published, so checking a deal today means implementing those two lines rather than running ours. Ask at support@spadecraft.app for the specification in the meantime.