The expert AI never redoubles. The Redouble plumbing is complete and tested;
the strategy behind it is a permanent False.
Split out of #9 (item 6), where it sat as one of seven TODO markers, and removed
from the Contrée-variants roadmap's step 5 agenda: the gap is a classic-ruleset
one — the AI never redoubles under any preset — and resolving the variant space
is not a prerequisite for fixing it.
Line numbers as of fix/engine-open-todos @ 120e6e9.
What exists
packages/contrai-engine/src/contrai_engine/model/player/rule_based/bidding.py
choose_bid resolves the frozen-auction states before the bidding table runs: a
standing Redouble leaves nothing legal but Pass, and a standing Double routes to
_choose_under_double, which builds a RedoubleBid, confirms
auction.is_legal(...), and asks the strategy:
contract_bid = auction.last_contract_bid
if contract_bid is not None and contract_bid.player.team is self.team:
redouble = RedoubleBid(self._player)
if auction.is_legal(redouble) and self._should_redouble():
return redouble
return PassBid(self._player)
The stub
@staticmethod
def _should_redouble():
"""Determine if we should redouble after being doubled."""
# TODO: Implement a redouble strategy
return False
Two consequences:
-
The AI declines every surcoinche, so the branch above is dead in play. The
tests that cover the redouble path reach it by patching the method
(tests/test_model/test_rule_based_bidding.py — ai_player.bidding._should_redouble = lambda: True),
which means the plumbing is pinned but the decision is not.
-
It takes no arguments, so it cannot see what it would need to decide. Its
sibling _should_double receives the standing bid and reasons from it:
strength = self._estimate_tricks(suit) * 20 # each expected trick worth 20
return strength > 162 - value
What implementing it involves
- Give it the inputs. At minimum the standing
ContractBid (its value and
trump suit) so the seat can estimate tricks in the agreed trump the way
_should_double does. Note the asymmetry: _should_double estimates
against a suit the opponents chose, while a redouble estimate is for a
contract our own side named — usually our partner's, so the hand doing the
estimating is not the hand that valued the contract in the first place.
- Decide the confidence bar. A redouble doubles the stake in both
directions; the mirror of _should_double's rule is a starting point, but the
threshold should be strictly higher than "we expect to make it" — the downside
is symmetric and the upside only materialises on a contract we were already
committed to.
- Reckon with partner's opening. The declaring side has two hands' worth of
information by then: our own strength plus whatever partner's bid implied.
Whether to model that, or to redouble on our own hand alone, is the main
design question.
Notes
- Behaviour change to the expert bidding strategy, so per
CLAUDE.md §9 it ships
with pytest coverage — cases pinning both a redouble and a declined one, and
the existing patched-stub tests should then assert against the real method
rather than a lambda.
- Worth revisiting once the variants roadmap's step 3 lands
at_stake_mode:
under FIXED stakes the payout grid changes enough to move the threshold. That
is a follow-up tuning pass, not a blocker.
The expert AI never redoubles. The Redouble plumbing is complete and tested;
the strategy behind it is a permanent
False.Split out of #9 (item 6), where it sat as one of seven TODO markers, and removed
from the Contrée-variants roadmap's step 5 agenda: the gap is a classic-ruleset
one — the AI never redoubles under any preset — and resolving the variant space
is not a prerequisite for fixing it.
Line numbers as of
fix/engine-open-todos@120e6e9.What exists
packages/contrai-engine/src/contrai_engine/model/player/rule_based/bidding.pychoose_bidresolves the frozen-auction states before the bidding table runs: astanding Redouble leaves nothing legal but Pass, and a standing Double routes to
_choose_under_double, which builds aRedoubleBid, confirmsauction.is_legal(...), and asks the strategy:The stub
Two consequences:
The AI declines every surcoinche, so the branch above is dead in play. The
tests that cover the redouble path reach it by patching the method
(
tests/test_model/test_rule_based_bidding.py—ai_player.bidding._should_redouble = lambda: True),which means the plumbing is pinned but the decision is not.
It takes no arguments, so it cannot see what it would need to decide. Its
sibling
_should_doublereceives the standing bid and reasons from it:What implementing it involves
ContractBid(its value andtrump suit) so the seat can estimate tricks in the agreed trump the way
_should_doubledoes. Note the asymmetry:_should_doubleestimatesagainst a suit the opponents chose, while a redouble estimate is for a
contract our own side named — usually our partner's, so the hand doing the
estimating is not the hand that valued the contract in the first place.
directions; the mirror of
_should_double's rule is a starting point, but thethreshold should be strictly higher than "we expect to make it" — the downside
is symmetric and the upside only materialises on a contract we were already
committed to.
information by then: our own strength plus whatever partner's bid implied.
Whether to model that, or to redouble on our own hand alone, is the main
design question.
Notes
CLAUDE.md§9 it shipswith
pytestcoverage — cases pinning both a redouble and a declined one, andthe existing patched-stub tests should then assert against the real method
rather than a lambda.
at_stake_mode:under
FIXEDstakes the payout grid changes enough to move the threshold. Thatis a follow-up tuning pass, not a blocker.