Invariants are properties that must always hold true for valid game states. These are checked in property-based tests to ensure correctness.
Invariant: All players have non-negative chip amounts.
∀ player in state.seats:
player.stack >= 0
player.committed_street >= 0
player.committed_total >= 0Violation: System error - indicates bug in reducer or event handler.
Invariant: Total chips on table remain constant (no rake).
initial_total = sum(player.stack + player.committed_total for all players)
current_total = sum(player.stack + player.committed_total for all players)
initial_total == current_total # Always trueNote: In production, rake would be subtracted, but for this engine we assume rake = 0.
Invariant: At terminal state (SHOWDOWN/COMPLETE), pots match committed chips.
if state.street in (SHOWDOWN, COMPLETE):
total_committed = sum(player.committed_total for active players)
total_pots = sum(pot.amount for pot in state.pots)
total_committed == total_potsViolation: Indicates bug in side pot calculation or event handling.
Invariant: Betting state values are non-negative.
state.current_bet >= 0
state.min_raise >= 0Invariant: Player status matches their chip state.
if player.stack == 0 and player.committed_total > 0:
player.status == ALL_IN # Not ACTIVEInvariant: Replaying events produces identical state.
events = event_store.get_events(hand_id)
replay_state = fold(events, initial_state)
reducer_state = # state from reducer
replay_state == reducer_state # Must be identicalAll invariants are tested using Hypothesis property-based testing:
- Random valid game states generated
- Random valid action sequences executed
- Invariants checked after each action
- Deterministic replay verified
The engine/rules/invariants.py module provides:
check_all_invariants(): Comprehensive checkcheck_chip_conservation(): Chip conservationcheck_no_negative_stacks(): Stack validationcheck_pot_correctness(): Pot validation
If an invariant is violated:
- Development: Property tests will fail, indicating bug
- Production: Should log error and reject state transition
- Recovery: Replay events from last known good state
- Unit Tests: Test individual invariant checks
- Property Tests: Generate random scenarios, verify invariants
- Integration Tests: Test invariants across full hand flow
- Replay Tests: Verify deterministic replay invariant