Skip to content

Epic 27 & 28#94

Merged
folkengine merged 9 commits into
mainfrom
epic-27
May 2, 2026
Merged

Epic 27 & 28#94
folkengine merged 9 commits into
mainfrom
epic-27

Conversation

@folkengine
Copy link
Copy Markdown
Collaborator

Features

  • src/bot/exploit.rs — ExploitConfig, scale_percentage, largest_active_opponent, adjust_profile with 8 deviation rules (fold-to-cbet, calling-station,
    loose-passive sizing, nit, aggro calldown, WTSD, 3-bet%)
  • src/bot/exploitative_decider.rs — ExploitativeDecider wrapper with wrap / wrap_with_config constructors
  • src/bot/sim.rs — SimTable::new_with_registry constructor enabling any Box with full stats ingestion
  • src/bot/table_snapshot.rs — SeatInfo gains pub id: Uuid from player.id
  • src/analysis/player_stats.rs — StatsRegistry::insert_for_test helper (#[cfg(test)])
  • src/bot/training/encoding.rs — ExploitConfig ↔ [f64; 16] encoding with per-dimension bounds
  • src/bot/training/evaluator.rs — evaluate / default_field / run_session for BB/100 fitness
  • src/bot/training/trainer.rs — ExploitTrainer (1+λ)-ES with Box-Muller Gaussian mutation and 1/5 success rule
  • src/bot/training/mod.rs — TrainingConfig, TrainingResult, GenerationRecord re-exports
  • src/prelude.rs — re-exports for ExploitConfig, ExploitativeDecider, training types under feature gates

Config/Docs

  • Cargo.toml — bot-training feature gate; [[example]] and [[test]] entries for training
  • src/bot/mod.rs — registers exploit, exploitative_decider, training modules under feature gates
  • src/bot/exploit.rs — #[cfg_attr(feature = "bot-training", derive(Serialize, Deserialize))] on ExploitConfig
  • ROADMAP.md — EPIC-27 and EPIC-28 rows added to Epics table
  • docs/EPIC-27_Exploitative_Decider.md — full planning doc, all 14 status rows ✅
  • docs/EPIC-28_Profile_Training.md — full planning doc, all status rows ✅
  • docs/TUTORIAL_EPIC28_ES_Math.md — 473-line math tutorial (ES algorithm, 1/5 rule, Box-Muller, BB/100 noise, CMA-ES relation, references)
  • data/exploit_configs/tag_trained.yaml — 100-generation trained config artifact
  • data/exploit_configs/.gitkeep — directory placeholder

Tests/Examples

  • examples/exploitative_play.rs — TAG-exploit vs LP/TP/Maniac, 1,000 hands, per-rule firing counters via Arc + SharedTelemetry
    newtype
  • examples/train_exploit_config.rs — end-to-end training demo with --generations, --hands, --output flags and YAML save
  • tests/exploitative_play_smoke.rs — 3 integration tests: chip conservation, stats accumulation, profile deviation firing
  • tests/training_integration.rs — smoke test (fast) + 200-generation validation test (#[ignore])

folkengine and others added 9 commits May 2, 2026 06:38
  - src/bot/exploit.rs — new: ExploitConfig, scale_percentage, largest_active_opponent, adjust_profile with 8 deviation rules (fold-to-cbet, calling
  station, loose-passive sizing, nit, aggro calldown, high-WTSD, 3-bet%)
  - src/bot/exploitative_decider.rs — new: ExploitativeDecider<D: BotDecider> wrapper with wrap / wrap_with_config constructors and BotDecider impl
  - src/bot/table_snapshot.rs — SeatInfo gains pub id: Uuid populated from player.id

  Tests
  - src/bot/exploit.rs — 5 unit tests: identity (no stats), high fold-to-cbet fires, calling-station reduces bluff, thin sample no-op, clamp to valid
  percentage
  - src/bot/exploitative_decider.rs — 2 unit tests: empty-registry parity with bare decider, hot-stats diverge in ≥1 seed
  - src/analysis/player_stats.rs — StatsRegistry::insert_for_test helper under #[cfg(test)]

  Config/Docs
  - src/bot/mod.rs — wired exploit and exploitative_decider under #[cfg(feature = "player-stats")]
  - src/prelude.rs — re-exported ExploitConfig and ExploitativeDecider under player-stats gate
  - docs/EPIC-27_Exploitative_Decider.md — status rows flipped to ✅ for phases 0–2 and regression check; phases 3–4 remain pending
  - data/hands/pkarena0-session_neverends.yaml — hand history data file added (committed)
…ecider::wrap(RuleBasedDecider(TAG)) vs RuleBasedDecider(LP) integration guard

  using SimTable::run_n_hands.
…Maniac, 1,000 hands each. Per-rule firing counters via Arc<TelemetryDecider> +

  SharedTelemetry newtype (orphan rule workaround). Prints chip deltas and a rule-activity table per matchup — the output shows different rules firing
  for each opponent archetype (fold-to-cbet + nit vs LP, nit-only vs TP, AF + 3-bet rules vs Maniac).
  New feature gate — bot-training in Cargo.toml (requires player-stats + bot-profiles, adds serde_yaml_bw for YAML output, no external optimizer
  dependency).

  src/bot/training/ module — 4 files:
  - encoding.rs — encode/decode between ExploitConfig and [f64; 16], bounds constants, 5 unit tests
  - evaluator.rs — evaluate runs SimTable::new_with_registry sessions and returns mean BB/100; default_field() returns all 8 archetypes; 3 unit tests
  - trainer.rs — ExploitTrainer with (1+λ)-ES (isotropic Gaussian mutation, 1/5 success rule sigma adaptation, Box-Muller N(0,1));
  TrainingConfig/TrainingResult/GenerationRecord; 4 unit tests
  - mod.rs — clean re-exports

  Optimizer choice — (1+λ)-ES implemented internally (~60 lines). No external nalgebra/cmaes dependency. Fully deterministic via seeded SmallRng.
  Pluggable if a proper CMA-ES is wanted later.

  examples/train_exploit_config.rs — end-to-end demo with --output, --generations, --hands flags; prints per-generation table; saves trained config as
  YAML.

  tests/training_integration.rs — smoke test (fast) + 200-generation validation test (marked #[ignore]).
…14 total headings. The EPIC-28 doc now has a callout block at the top linking to

  it.

  Here's what the tutorial covers:

  ┌─────────┬───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  │ Section │                                                              Content                                                              │
  ├─────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
  │ §1      │ Problem formulation — what we're optimising, why it's black-box and bounded                                                       │
  ├─────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
  │ §2      │ ES overview — μ/λ notation, (1+λ) vs (μ,λ), history                                                                               │
  ├─────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
  │ §3      │ The (1+λ)-ES algorithm — pseudocode matched line-for-line to trainer.rs                                                           │
  ├─────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
  │ §4      │ 1/5 success rule — Rechenberg's derivation, self-consistency condition, the constants 1.22/0.90                                   │
  ├─────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
  │ §5      │ Box-Muller transform — proof sketch, edge-case handling, Ziggurat alternative                                                     │
  ├─────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
  │ §6      │ Bounds and encoding — why range-scaling makes σ dimensionless, continuous relaxation of integer gates                             │
  ├─────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
  │ §7      │ BB/100 — definition, why 1B-chip stacks were wrong, bounded-stack design                                                          │
  ├─────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
  │ §8      │ Noisy optimisation — why variance is a fundamental challenge, paired comparisons, replicates                                      │
  ├─────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
  │ §9      │ CMA-ES relation — what it adds (covariance matrix, evolution paths), when isotropic is sufficient, how to plug in the cmaes crate │
  ├─────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
  │ §10     │ References — 8 primary sources (Rechenberg 1973, Box-Muller 1958, Hansen 2016, etc.) + accessible web links                       │
  └─────────┴───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  - [[example]] exploitative_play — required-features = ["bot-profiles", "player-stats"]
  - [[test]] exploitative_play_smoke — required-features = ["bot-profiles", "player-stats"]

  Both were being compiled unconditionally by Cargo (no entry = attempt with whatever features are active), which caused the gated imports to fail under
   --no-default-features. The required-features field tells Cargo to skip them entirely when the listed features aren't enabled, which is the correct
  behavior for the CI no-default-features job.
@folkengine folkengine merged commit 6eeec09 into main May 2, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant