Problem
The trick-winner rule is computed through three independent call paths that all delegate to the same core current_winner free function (packages/contrai-core/src/contrai_core/trick.py):
PlayState.trick_winners maps current_winner over completed_tricks (packages/contrai-core/src/contrai_core/play.py).
Round recomputes the winner on its mutable mirror Trick (packages/contrai-engine/src/contrai_engine/model/round/round.py, get_current_winner on current_trick) — this is what feeds team_tricks and last_trick_winner for scoring.
- The view builds a throwaway proxy
Trick and pushes directly onto proxy.plays to reuse the rule (packages/contrai-engine/src/contrai_engine/view/state_helpers.py, _current_winner).
They agree today because they share the underlying function, but three call sites independently asserting the same fact is redundancy waiting to drift — especially the view's proxy-object construction, which bypasses Trick's normal API.
Proposal
Unify on the core Play records / PlayState as the single source of truth:
Round reads winners from PlayState.trick_winners instead of recomputing on its mirror Trick.
- The view's
state_helpers._current_winner consumes the same plays-based rule directly (no proxy Trick).
- Longer term this folds into reducing the
Round mirror-state duplication (Trick holding (Player, Card) tuples vs core Play records).
Notes
- Pure refactor: no behaviour change; existing round-lifecycle and view tests must stay green.
- Good to schedule alongside or after the
Position migration since both touch Round/view seams, but not blocked by it.
Problem
The trick-winner rule is computed through three independent call paths that all delegate to the same core
current_winnerfree function (packages/contrai-core/src/contrai_core/trick.py):PlayState.trick_winnersmapscurrent_winnerovercompleted_tricks(packages/contrai-core/src/contrai_core/play.py).Roundrecomputes the winner on its mutable mirrorTrick(packages/contrai-engine/src/contrai_engine/model/round/round.py,get_current_winneroncurrent_trick) — this is what feedsteam_tricksandlast_trick_winnerfor scoring.Trickand pushes directly ontoproxy.playsto reuse the rule (packages/contrai-engine/src/contrai_engine/view/state_helpers.py,_current_winner).They agree today because they share the underlying function, but three call sites independently asserting the same fact is redundancy waiting to drift — especially the view's proxy-object construction, which bypasses
Trick's normal API.Proposal
Unify on the core
Playrecords /PlayStateas the single source of truth:Roundreads winners fromPlayState.trick_winnersinstead of recomputing on its mirrorTrick.state_helpers._current_winnerconsumes the same plays-based rule directly (no proxyTrick).Roundmirror-state duplication (Trickholding(Player, Card)tuples vs corePlayrecords).Notes
Positionmigration since both touchRound/view seams, but not blocked by it.