feat(sim): cash_out_threshold — rathole stack excess to bankroll
After each hand, stack above the threshold moves to the bankroll. Bounds effective depth while keeping natural short-stack dynamics (losses, rebuy cycles). Needed for realistic deep-stack-safe benchmarks: the oracle hybrid's EV collapses with stack depth (+6.9 at 75BB reset vs -33.8 natural), so unbounded natural-stack snowballs distort candidate evaluation.
This commit is contained in:
parent
a589acbbc7
commit
b9963bfbb1
2 changed files with 41 additions and 1 deletions
|
|
@ -354,6 +354,12 @@ pub struct GameConfig {
|
|||
#[serde(default)]
|
||||
pub bankroll_refill_threshold: ChipCount,
|
||||
|
||||
/// Cash-out (rathole) threshold: after each hand, stack excess above
|
||||
/// this amount is moved to the player's bankroll. 0 = disabled.
|
||||
/// Keeps natural stack dynamics (losses, rebuys) while bounding depth.
|
||||
#[serde(default)]
|
||||
pub cash_out_threshold: ChipCount,
|
||||
|
||||
/// If true, player seat assignments are randomized before each hand
|
||||
/// (cash game) or before tournament start.
|
||||
#[serde(default)]
|
||||
|
|
@ -430,6 +436,7 @@ impl Default for GameConfig {
|
|||
rake_cap: 0,
|
||||
reset_bankroll_each_hand: false,
|
||||
bankroll_refill_threshold: 0,
|
||||
cash_out_threshold: 0,
|
||||
randomize_seats: false,
|
||||
auto_rebuy: false,
|
||||
rebuy_threshold_percentage: default_rebuy_threshold(),
|
||||
|
|
@ -1069,6 +1076,7 @@ min_raise = 50
|
|||
rake_cap: 0,
|
||||
reset_bankroll_each_hand: false,
|
||||
bankroll_refill_threshold: 0,
|
||||
cash_out_threshold: 0,
|
||||
randomize_seats: false,
|
||||
auto_rebuy: false,
|
||||
rebuy_threshold_percentage: 25,
|
||||
|
|
@ -1158,6 +1166,7 @@ min_raise = 50
|
|||
rake_cap: 0,
|
||||
reset_bankroll_each_hand: false,
|
||||
bankroll_refill_threshold: 0,
|
||||
cash_out_threshold: 0,
|
||||
randomize_seats: false,
|
||||
auto_rebuy: false,
|
||||
rebuy_threshold_percentage: 25,
|
||||
|
|
@ -1197,6 +1206,7 @@ min_raise = 50
|
|||
rake_cap: 0,
|
||||
reset_bankroll_each_hand: false,
|
||||
bankroll_refill_threshold: 0,
|
||||
cash_out_threshold: 0,
|
||||
randomize_seats: false,
|
||||
auto_rebuy: false,
|
||||
rebuy_threshold_percentage: 25,
|
||||
|
|
@ -1252,6 +1262,7 @@ min_raise = 50
|
|||
rake_cap: 0,
|
||||
reset_bankroll_each_hand: false,
|
||||
bankroll_refill_threshold: 0,
|
||||
cash_out_threshold: 0,
|
||||
randomize_seats: false,
|
||||
auto_rebuy: false,
|
||||
rebuy_threshold_percentage: 25,
|
||||
|
|
@ -1309,6 +1320,7 @@ min_raise = 50
|
|||
rake_cap: 0,
|
||||
reset_bankroll_each_hand: false,
|
||||
bankroll_refill_threshold: 0,
|
||||
cash_out_threshold: 0,
|
||||
randomize_seats: false,
|
||||
auto_rebuy: false,
|
||||
rebuy_threshold_percentage: 25,
|
||||
|
|
@ -1360,6 +1372,7 @@ min_raise = 50
|
|||
rake_cap: 0,
|
||||
reset_bankroll_each_hand: false,
|
||||
bankroll_refill_threshold: 0,
|
||||
cash_out_threshold: 0,
|
||||
randomize_seats: false,
|
||||
auto_rebuy: false,
|
||||
rebuy_threshold_percentage: 25,
|
||||
|
|
@ -1422,6 +1435,7 @@ min_raise = 50
|
|||
rake_cap: 0,
|
||||
reset_bankroll_each_hand: false,
|
||||
bankroll_refill_threshold: 0,
|
||||
cash_out_threshold: 0,
|
||||
randomize_seats: false,
|
||||
auto_rebuy: false,
|
||||
rebuy_threshold_percentage: 25,
|
||||
|
|
@ -1475,6 +1489,7 @@ min_raise = 50
|
|||
rake_cap: 0,
|
||||
reset_bankroll_each_hand: false,
|
||||
bankroll_refill_threshold: 0,
|
||||
cash_out_threshold: 0,
|
||||
randomize_seats: false,
|
||||
auto_rebuy: false,
|
||||
rebuy_threshold_percentage: 25,
|
||||
|
|
@ -1594,6 +1609,7 @@ min_raise = 20
|
|||
rake_cap: 0,
|
||||
reset_bankroll_each_hand: false,
|
||||
bankroll_refill_threshold: 0,
|
||||
cash_out_threshold: 0,
|
||||
randomize_seats: false,
|
||||
auto_rebuy: false,
|
||||
rebuy_threshold_percentage: 25,
|
||||
|
|
|
|||
|
|
@ -128,6 +128,28 @@ pub fn run_cash_game(config: &GameConfig) -> Result<(SimulationStats, Table), Ga
|
|||
}
|
||||
}
|
||||
}
|
||||
// Cash-out (rathole): move stack excess above the threshold
|
||||
// into the bankroll after each hand. Bounds effective depth
|
||||
// while keeping natural short-stack dynamics intact.
|
||||
if config.cash_out_threshold > 0 {
|
||||
for slot in table.seats_mut().iter_mut() {
|
||||
if let Some(seat) = slot {
|
||||
if seat.stack > config.cash_out_threshold {
|
||||
let excess =
|
||||
seat.stack - config.cash_out_threshold;
|
||||
log::debug!(
|
||||
"Cash-out: {} ratholes {} (stack {} -> {})",
|
||||
seat.player.name(),
|
||||
excess,
|
||||
seat.stack,
|
||||
config.cash_out_threshold
|
||||
);
|
||||
seat.bankroll += excess as i64;
|
||||
seat.stack = config.cash_out_threshold;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(GameError::NotEnoughPlayers(count)) => {
|
||||
// Not enough active players to continue. This should only
|
||||
|
|
@ -175,7 +197,9 @@ pub fn run_cash_game(config: &GameConfig) -> Result<(SimulationStats, Table), Ga
|
|||
.map(|seat| (seat.player_id, seat.bankroll))
|
||||
})
|
||||
.collect();
|
||||
let bankroll_mode = config.reset_bankroll_each_hand || config.bankroll_refill_threshold > 0;
|
||||
let bankroll_mode = config.reset_bankroll_each_hand
|
||||
|| config.bankroll_refill_threshold > 0
|
||||
|| config.cash_out_threshold > 0;
|
||||
stats.record_final_stacks(&final_stacks, &starting_stacks, &bankrolls, bankroll_mode, config.auto_rebuy);
|
||||
|
||||
Ok((stats, table))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue