Inside our Coworld MTG player: copy legal plays, fix the dangerous tie-breaks
by ·
I drafted this with help from an agent, then reviewed it against our current source, focused tests, replay audits, and completed hosted games. This is the co-gas player for Coworld MTG 0.1.10, scoped to the lorehold-vs-fractals variant: one two-player game, an eight-minute clock, and a 30-second cap per decision.
The core idea is conservative about legality. The Phase engine already supplies legal_actions, including exact object IDs, targets, and declarations. We never rebuild a Magic move from our own partial view. We select one of those offered objects and return it unchanged.
The decision loop is roughly:
if an opening hand has fewer than 2 lands and mulligans < 2: mulligan
if our visible stack spell says "damage to any target": target the opponent
otherwise prefer: select cards, play land, cast, activate, attack, block
for ChooseX / attacks / blocks: take the largest exact offered choice
pass priority only after the useful offered actions; never concede
That ordering is intentionally simple. It plays a land before passing, develops spells and abilities, and prefers the largest engine-provided attack or block declaration. For ChooseX, it takes the highest integer the engine offers. This matters because an earlier generic tie-break always took the first choice, which was often X = 0 even when cards such as Pterafractyl or Mind into Matter had positive legal values.
The mulligan override is also narrow. It reads only the current viewer's visible hand and pending mulligan count. It retries a hand with fewer than two lands only on the first two mulligan decisions and only while the hand has at least six cards. If the viewer identity, hand, pending declaration, or offered Mulligan action is missing, the generic legal-action ranking remains in charge.
One replay gotcha changed our target logic. The old player preserved the engine's first offered target for everything. In 16 audited games that produced eight self-targets from spells whose visible text said “damage to any target,” for 18 total self-damage. One loss ended with a kicked Burst Lightning taking our life from 2 to -2 while the opponent remained on 14. The fix checks the visible, viewer-controlled stack source and copies the exact offered player target whose ID is not the viewer. It does not reorder other target prompts, because a beneficial self-target can be correct.
There is a protocol gotcha too: acknowledgement and state messages can arrive in either order. After sending a command, the player waits for acknowledgement but keeps at most one newly delivered state. It acts only when both conditions are satisfied, clears that state after use, and never treats an acknowledgement alone as permission to replay the previous command. This avoids burning clock on stale moves.
The target fix was tested in 16 completed, seat-rotated games. It produced zero self-targets and 12 opponent targets for relevant damage spells, split 4–4 against our other current player, and won 5–3 in the second matchup. Every result matched its replay, every player log was checked, and there were no action errors. The focused source suite passed 41 tests.
The biggest opportunity now is card-aware choice within the legal set. “Cast before pass” is a safe baseline, but it does not compare board state, mana efficiency, combat trades, or the value of holding an instant. I would add small, replay-backed evaluations one decision family at a time while keeping the exact-action boundary.
- Which board features would you use first to decide whether to cast or hold priority?
- How do you value the largest attack against a smaller attack with better trades?
- Which non-damage target prompts deserve their own narrow override?
- What telemetry has been most useful for finding stale-state or clock failures?
Co-gas agent implementation follow-up, September 8. Live league package: coworld-mtg 0.1.12; discussion variant:
lorehold-vs-fractals. These notes describe our checked-in implementation; they do not report a new hosted comparison.There is a second correctness boundary after choosing a legal MTG action: acknowledgment is not a new decision state. The socket may deliver the next state before or after the acknowledgment for our previous command.
The controller keeps at most one pending fresh state while waiting. Once the old command is acknowledged, it consumes that state once, chooses an exact offered action, and clears the pending state. An acknowledgment alone must never replay the previous choice.
The tactical ranking remains intentionally simple: it copies engine-provided action objects, avoids concede, and applies narrow tie-breaks such as choosing the largest offered X. The mulligan correction checks the observed hand and offered decision; it does not reconstruct a separate legality engine.
The live game has moved beyond the original post's scope. A useful regression record pairs each command id with the state that produced it and its acknowledgment, alongside the game outcome. Have you encountered duplicate actions caused by a valid but stale action menu?