Two defects found reviewing the UI boundary before Compose work:
1. HumanAgent.awaiting was a plain mutable property written by the game
coroutine and read by the UI — a data race, and invisible to Compose. It also
exposed DecisionContext, which holds a live Seat whose fields mutate as the
hand proceeds, so even a safe read could observe torn state. Replaced with an
immutable DecisionOffer published through a StateFlow. A test mutates the live
seat after publication and asserts the offer does not change.
2. STREET_COMPLETE was emitted after dealing the new street but before the round
state was reset, so a flop snapshot carried pre-flop currentBet and
committedThisRound — the UI would have painted last street's chips in front of
every player alongside the new board. The reset is now prepareRound(), called
before publishing. Verified: with the ordering reverted the new test fails
with currentBet 10 on the flop.
Also:
- HumanAgent.cancel() is now covered directly; the previous test only cancelled
the coroutine running act(). cancel() and submit() both report whether anything
was actually pending.
- Android host tests enabled via withHostTestBuilder, so the shared suite runs
against the Android variant instead of the AAR merely compiling.
No librsvg needed for card assets: sips rasterizes the SVGs directly at exact
2:3 dimensions, court cards and patterned backs included.
Tests: 45 -> 48, now green on both jvmTest and testAndroidHostTest.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Build:
- :engine now uses com.android.kotlin.multiplatform.library (AGP 9.2.1), the
modern KMP Android integration, rather than plain androidTarget(). Produces
engine.aar alongside the JVM target; compileAndroidMain verified.
- Version catalog added; SDK levels match the other JSJ apps (compileSdk 37,
minSdk 26).
Engine:
- PlayerAgent.act() and Table.playHand() are now suspend, so a human player can
wait for input without blocking a thread. Bots are unaffected; the simulator
wraps in runBlocking.
- TableSnapshot/SeatSnapshot published after the deal, before and after every
action, and at the finish. Immutable, aliasing no live Seat state, giving
animation, hand history, saving, and replay one boundary to work against.
- Snapshots carry the whole truth; maskedFor(viewer) is an explicit step that
hides hole cards the viewer is not entitled to. Showdown reveals contenders;
folded hands never are.
- Terminal snapshots report the contested pot rather than 0. settle() zeroes
contributions when awarding, so the naive value was empty at exactly the
moment the UI needs to show what was won. Caught by a new test.
- HumanAgent suspends on a CompletableDeferred and clears its pending state in a
finally block, so cancelling an abandoned hand releases the wait instead of
stranding it. Re-usable afterwards; a stale submit returns false.
Tests: 37 -> 45. Chips still conserved, skill gradient still monotonic.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Kotlin Multiplatform engine (JVM target only for now; androidTarget and
iosArm64 slot in without touching commonMain).
Core:
- HandEvaluator: single-pass 5-7 card evaluation, ~24M evals/sec. Verified
exhaustively against published frequencies for all 2,598,960 five-card hands.
- Equity: Monte Carlo with ties split. PreflopChart ranks the 169 starting
hands using all-in equity plus an explicit playability adjustment, so
looseness means "plays the top N%".
- Table: no-limit betting rounds, side pots, odd-chip splits, uncalled-bet
refunds, and incomplete (short all-in) raises that correctly do not reopen
betting.
Bots:
- SkillLevel and PlayStyle are orthogonal axes. Skill drives decision quality
(rollout accuracy, pot-odds discipline, position awareness, error rate);
style drives bluffing, sandbagging, aggression, tightness.
- BotMood gives tilt that persists between hands and decays.
- OpponentModel lets Advanced/Expert exploit habitual bettors.
- MathBot emits a DecisionTrace of the numbers behind each decision, which the
coach will later hand to an LLM to narrate. The LLM never does poker maths.
Simulator:
- 2,200-3,400 hands/sec. Deck RNG is separate from bot RNGs so rollout counts
cannot shift the deal.
- Controlled skill-ladder test asserts the difficulty gradient is monotonic:
73.9 / 53.9 / 27.6 / -155.4 bb/100 over 50k hands.
Assets: 52 CC0 English-pattern card faces plus generated backs.
Tests: 30 passing (evaluator, table rules, pre-flop chart).
Known open: win-rate magnitudes ~10x realistic and several profiles looser
than their labels. Tuning, not correctness.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>