Skip to content

engine: resolve the seven open TODO markers in contrai-engine #9

Description

@valmathieu

Inventory of the TODO markers living in the two first-party game packages.
contrai-core is clean — zero markers. All four remaining sit in
contrai-engine. No markers exist in contrai-analyzer or contrai-scraper
either, so the list below is the complete inventory for the whole workspace.

Line numbers are as of fix/engine-open-todos @ 120e6e9.

The original inventory listed seven markers. Three are resolved and one moved to
its own issue — see Resolved at the bottom.


1. Seat configuration is hardcoded (2 markers)

  • packages/contrai-engine/src/contrai_engine/cli.py:29
  • packages/contrai-engine/src/contrai_engine/view/screens/landing.py:135
# cli.py
# TODO: replace with a seat picker on the landing screen. For now the
# layout matches the design handoff exactly: South is the human, the
# other three seats are AI (expert — the default strategies) — unless
# ``--autoplay`` is set, in which case South is an AI too (see
# ``_build_game``).
HUMAN_SEAT = Position.SOUTH
# landing.py — _panel_players()
TODO: replace with a configurable seat picker when we expose
difficulty / player config on the landing screen.

Two halves of the same feature: _build_game() always seats the human at South
with three AiPlayers, and _panel_players() renders that same layout from its
own literal roles map. The two are only consistent by hand — nothing forces the
landing panel to describe the game the CLI actually builds. Both already handle
the --autoplay case separately, which is two places encoding one fact.

Resolving this means a seat/difficulty picker on the landing screen feeding a
single setup description that both the view and _build_game() read.

MVC note: the picker is user input, so it belongs on the view/controller side;
the resulting configuration should reach Game as data, not as view state.

Scheduling: this overlaps the Contrée-variants roadmap's step 4
(feat/engine-ruleset-ux), whose scope is a game-setup screen listing presets
with a resolved-knob summary and a persisted last-used choice. Building a seat
picker first means building a setup screen that step 4 then rebuilds — the seat
and difficulty pickers are better folded into that screen as one piece of work.

2. Solo Slam is gated exactly like Slam (1 marker)

  • packages/contrai-engine/src/contrai_engine/model/player/rule_based/bidding.py:64
(SLAM_NUMERIC, {}, 0, 0, 8, False),  # Slam — only the trick estimator gates it.
# TODO: tune SoloSlam gate — currently shares Slam's gate. A
# stricter rule (e.g. holds the 8 top trumps in trump-led play,
# or all aces + trump master) would make this conservative.
(SOLO_SLAM_NUMERIC, {}, 0, 0, 8, False),  # Solo Slam — same gate as Slam for now.

Both all-tricks rows in BIDDING_TABLE carry an identical gate (tricks_min=8,
no trump/ace requirement), but the two bids are not equivalent: Slam only asks
that the team take all eight tricks, Solo Slam that the bidder personally
does. A hand strong enough for Slam with partner support is routinely not strong
enough for Solo Slam, so the AI over-bids 500 whenever it reaches 250.

The comment already sketches candidate rules (8 top trumps in trump-led play, or
all aces + trump master). Deciding between them needs a hand-strength argument
rather than a code change — see contree-domain.md for what Solo Slam actually
commits the bidder to.

Scheduling: the gate fires rarely (the trick estimator has to reach 8), so
the over-bid is a low-frequency correctness issue rather than a live drag on
strength. Two roadmap steps touch the same rows — step 1 adds an
allow_solo_slam_bid knob, step 2 rewrites the per-mode bid ceilings — so the
rule is worth deciding now and implementing against whichever lands first.
Kept open here deliberately: it is a strategy decision, not deferred work.


Resolved

Closed by fix/engine-open-todos:

  • Leading-card strategy doesn't exploit a known trump-void table
    (card_play.py) — fixed. _play_leading_card hoisted the trump-void check out
    of the pull guard and restricts the ace/master search to plain suits when the
    opponents are proven void, so a trump ace is no longer cashed where a plain one
    wins the same trick. The original framing understated it: the fall-through was
    reached as a defender, with no contract, or holding no trump — not only in the
    "opponents are out of trump" case it claimed — and the inference had never
    reached defenders at all, because the only call sat behind an "our side
    declared" guard.
  • _is_master_card takes a suit where a boolean would do
    (card_play.py) — resolved, though not as originally specified. The premise
    died with the TrumpRules seam: _get_higher_ranks no longer exists, so
    trump_suit had stopped being a bare equality operand and "replace it with a
    boolean" became the wrong move. The helper now takes the round's TrumpRules
    directly — every call site already holds one, and it was previously re-resolved
    once per candidate card inside list comprehensions — with the missing type
    hints added.
  • Game.__init__ requires every player to carry a position (game.py) —
    implemented. BasePlayer.position is optional and Game([p1, p2, p3, p4])
    seats an unseated roster in list order against list(Position). The
    auto-assignment is deterministic; randomised seating stays a caller-side
    shuffle, which keeps the RNG where a self-play harness or a test can seed it. A
    half-seated list raises rather than being completed. (The Position enum this
    was waiting on had already landed by the time the work started.)

Moved out:

Two claims in the original inventory were stale and have been dropped: the 4-AI
simulation mode does have a CLI entry point (--autoplay), and the Position
enum was described as "in flight" when it had already merged.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions