Commit Graph

3 Commits

Author SHA1 Message Date
thejayman77 eefcd5966c Fix UI-boundary defects; run tests on the Android variant too
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>
2026-07-25 15:33:17 -04:00
thejayman77 7f82251d86 Android target, suspending agents, and observable table snapshots
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>
2026-07-25 10:55:28 -04:00
thejayman77 479be1f6b9 Initial commit: Hold'em engine, bots, and simulation harness
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>
2026-07-25 04:36:03 -04:00