mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-11 22:34:02 +01:00
The wiki's dice-effect row for Balatro's die is `{{Dice effect|d|d|d|d|d|d}}`
— six *unfilled placeholder* cells that render as "d%". A previous pass read
those six `d`s as six wildcard faces, which made the die resolve every throw in
the player's favour: EV 8000, bust 0%, and the solver forced it into every
loadout it could. Only one face is the joker (Jimbo's grinning face, visible on
the item icon), and no source publishes the die's side weights at all.
The two joker faces also do not score alike, and the scorer was treating them as
one thing. Per the wiki, a Devil's head "matches any combination but never
scores on its own", while Balatro's joker counts as a 1 when picked alone. So
there are now two substitute slots: a Devil's head is spent inside the
combination it completes rather than resolved to a plain face, which is what
stops it being held as a lone 1. Hands with no substitute keep the original
single-pass code path.
Balatro's die: EV 8000 -> 771.2, bust 0% -> 3.09%; a lone devil's head scores
100 -> 0 and is no longer a legal hold. The two dice now tie exactly on a single
throw of six and differ only mid-turn (813.7 vs 808.3 points per turn), which is
where "may be held alone" actually pays.
Which pip the joker replaces is unpublished; the 1 is assumed, matching the
Devil's head die the game compares it to, and marked UNVERIFIED in the data.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
91 lines
4.6 KiB
Markdown
91 lines
4.6 KiB
Markdown
# KCD2 Dice Solver
|
||
|
||
Pick which six dice and which badge to bring to a game of dice in
|
||
_Kingdom Come: Deliverance II_. Enter what you own; the solver evaluates every
|
||
loadout your inventory allows and reports the best one with the numbers behind
|
||
it.
|
||
|
||
```bash
|
||
pnpm install
|
||
pnpm dev # http://localhost:5174
|
||
pnpm test # full suite
|
||
pnpm coverage # 100% statements / branches / functions / lines
|
||
pnpm lint # tsc --noEmit + eslint (strictTypeChecked)
|
||
pnpm build
|
||
```
|
||
|
||
## Entering an inventory
|
||
|
||
Three ways, all wired to the same state:
|
||
|
||
- **Click** a die's row to add one.
|
||
- **Scroll** the mouse wheel over its counter to step it up or down.
|
||
- **Search** by name — a fuzzy match, so `wei` finds _Weighted die_ and
|
||
`paintb` finds _Painter's die B_. The same box filters the badge list.
|
||
|
||
The inventory is saved to `localStorage`, so it survives a reload.
|
||
|
||
## How the recommendation is computed
|
||
|
||
**Scoring** (`src/core/scoring.ts`) is the wiki's table: 100 per one, 50 per
|
||
five, `1000` for three ones and `100 × face` otherwise, doubled for each die
|
||
beyond three, and 500/750/1500 for the three straights. A roll scores the best
|
||
_partition_ of its dice, which is a small memoised search.
|
||
|
||
**Expected value** (`src/core/evaluate.ts`) is exact, not sampled. Scoring
|
||
depends only on the multiset of faces, so the six dice are convolved into a
|
||
distribution over count vectors — at most 924 of them, against 6⁶ = 46,656
|
||
ordered outcomes. Those vectors stay packed as integers end to end, so the
|
||
scorer's memo is keyed on them directly. `evaluate.test.ts` checks the result
|
||
against a naive full enumeration that shares none of that machinery.
|
||
|
||
**The search** (`src/core/search.ts`) evaluates whole sets, because a Farkle
|
||
set's value is not the sum of its dice — ranking dice individually and taking
|
||
the top six is simply wrong. Dice with identical distributions are pooled
|
||
first (nine of the game's dice are plain uniform dice under different names).
|
||
Small inventories are enumerated exhaustively and reported as _provably
|
||
optimal_; larger ones use a multi-start steepest-ascent local search and are
|
||
reported as _not proven_.
|
||
|
||
**Turn value** (`src/core/simulate.ts`) is a Monte Carlo simulation, and unlike
|
||
the above it depends on how you play. The policy is stated explicitly in that
|
||
file and its knobs are exposed rather than buried: hold the subset maximising
|
||
`points + 60 × dice-left`, bank at 300.
|
||
|
||
**Badges** (`src/core/badgeValue.ts`) split in two. The five that change the
|
||
scoring table are valued on the exact EV delta after re-running the whole dice
|
||
search with the rule on — so equipping one can change which dice you should
|
||
bring. The rest are simulated one charge at a time, with each turn drawing from
|
||
its own seeded stream so the comparison is paired and the measured difference is
|
||
the badge rather than a diverged random sequence.
|
||
|
||
Nothing uses `Math.random`; every random source is a seeded `mulberry32`, so
|
||
every number here is reproducible.
|
||
|
||
## What the numbers are not
|
||
|
||
- **Balatro's die** publishes no face probabilities: the wiki's dice-effect row
|
||
for it is six unfilled `d` placeholders, and Inara does not list the die at
|
||
all. Exactly **one** of its faces is the joker (Jimbo's grinning face), so it
|
||
is modelled as a fair die whose 1 is replaced by that joker. Which pip the
|
||
joker replaces is unverified — the 1 is assumed, matching the Devil's head die
|
||
the game compares it to, and matching "picking it alone will count as if you
|
||
threw 1".
|
||
- **The two joker faces score differently**, and the scorer keeps them apart. A
|
||
Devil's head "matches any combination but never scores on its own", so it can
|
||
never be the lone 1 or 5 that lets you hold a die; Balatro's joker can. Both
|
||
are assumed able to fill in for any face of a combination, including one made
|
||
entirely of jokers — the wiki's examples only ever pair a joker with natural
|
||
dice, so that part is an assumption.
|
||
- **The three "Advantage" formations** (Carpenter's Cut, Executioner's Gallows,
|
||
Priest's Eye) and the **Headstart** point leads have no published values. The
|
||
constants used are marked `UNVERIFIED` in `src/data/badges.ts`. Correct them
|
||
from the game's help screen if you have it open.
|
||
- **Badge point-per-game figures** are estimates for ranking badges against each
|
||
other, not a prediction of a scoreline.
|
||
- The wiki and [Inara](https://inara.cz/kingdom-come-2/items-dice/) **disagree**
|
||
on about six dice (Ci/Fer/Lu, Painted, Trinity, Holy Trinity, Devil's head).
|
||
The [fandom wiki](https://kingdom-come-deliverance.fandom.com/wiki/Dice/KCD2)
|
||
is treated as canonical; the Inara figures sit in a comment beside each
|
||
conflicting entry.
|