Game probes — findings & proposed fixes
The single evaluation document. Each famous game is authored as a
definition file (game-defs/), imported, and simulated;
everything it cannot express becomes a numbered finding with a
proposed fix. The Decision column is deliberately empty —
it is yours. Nothing here gets built until decided.
Method & where things stand
A probe is authored faithfully, including what we expect to
fail — recorded failures are the deliverable. The import prints
card-validation problems; setup prints engine warnings; simulation
shows emergent wrongness (games ending at birth, draws that should be
wins). Definition files live in game-defs/; import with
php8.2 game-tool.php import game-defs/<file>.json.
| # | Game | Genre stress | Status |
|---|---|---|---|
| 1 | Star Realms (as Starforge Rivals) | Combat deck-builder — the home genre | plays fully · families untested |
| 2 | Dominion | Supply piles, budgets, most-VP ending | plays · 5 of 14 findings shipped |
| 3 | Ascension | Center row, dual currency, defeat, shared pool | plays wrongly · 8 findings |
| 4 | 7 Wonders Duel (proposed) | Tableau/pyramid, computed costs, multi-win | forecast only |
| 5 | Clank! | Board movement fused with deck-building | declared out of reach |
| 6 | Slay the Spire | Solo vs scripted enemies, block/intents | stretch — not probed |
1 · Star Realms — via Starforge Rivals
Not a probe file: authored end-to-end through the real wizard, now the engine's reference game. Plays fully — 100% of greedy games end by the authored combat win. Remaining distance to real Star Realms:
| ID | Observed / missing | Proposed fix | Effort | Status | Decision |
|---|---|---|---|---|---|
| S1 | Faction ally bonuses (“if you control another Blob card…”) — families are an empty scaffold: no authoring UI, no AI awareness, no effect can target a family | Finish the families axis: authoring UI on Step 2, AI prompt context, and an effect condition “count cards of family F you control” (the evaluator's count_cards already filters by card fields — small) | ~1 day | open | |
| S2 | Bases with activated abilities (“tap for 2 Trade”) | Q1's wire-but-disabled plan: abilities[] on card types + use_ability composite + exhausted state |
~2 days | shipped (U3, 2026-08-06) — Salvage Outpost taps for 2 Trade | — |
| S3 | Scrapping (banish a card from hand/discard permanently) | Player-chosen selector for move_cards + a void-style zone (shares machinery with Ascension A1) | with A1 | open |
2 · Dominion — game-defs/dominion.json
The founder: 17 supply piles, Action/Buy/Coin budgets, most-VP ending. First import ended on turn 1 with zero actions; after G5/G6/G7/G8/G1 it plays 41-turn games that end by the authored “Province pile empty” condition. Findings:
| ID | Observed / missing | Proposed fix | Effort | Status | Decision |
|---|---|---|---|---|---|
| G1 | No card-facing draw effect existed in the catalog | Added draw_n_cards_to_self; engine branch already existed | done | shipped | — |
| G5 | Supply piles never filled; deck→zone authoring unread | Setup as the game's first automatic phase: fill_zone/draw steps, authored or compiled, by deck name | done | shipped | — |
| G6 | One bindable market zone; Dominion has 16 buyable piles | market_zones set: every visible shared zone that refills or was deck-filled | done | shipped | — |
| G7 | highest_tracker winner rule resolved by fallthrough | All winner rules implemented; ties are draws; unknown rules refuse to invent a winner | done | shipped | — |
| G8 | Ending true at setup ended the game on turn 1 | Arm endings after setup; true-at-birth = authoring warning, suppressed for the run | done | shipped | — |
| G12 | Games end 0–0: VP lives on cards (Estate 1, Province 6), nothing counts it | Score-from-cards: implement resolution.metric as a JSON Logic expression evaluated per player at game end (+ a sum_cards(zones, field) evaluator aggregate for clean authoring). Makes Dominion AND Ascension produce real winners | ~½–1 day | shipped | — |
| G9 | Playing an Action card should spend 1 Action; play is free | Per-play tracker cost as a binding parameter on play_card | ~1 day together | shipped (point 7) | — |
| G10 | Any card playable in any phase; Estates played as no-ops | Per-phase card-type filters + an unplayable-from-hand property | shipped (point 7) | — | |
| G11 | Buys unlimited; the Buys tracker is decorative (100-card end decks) | Per-turn action-use limit fed by a tracker (binding parameter) | shipped (point 7) | — | |
| G2 | Militia/Council Room: effects targeting the other player | Effect target scope each_opponent / all_players in the effect executor + catalog entries | ~½ day | open | |
| G4 | Gardens: VP computed from deck size | Derived trackers (model doc B.7) — or covered by G12's metric if expressed as JSON Logic | with G12 | shipped (with G12) | — |
| G13 | “Any 3 supply piles empty” inexpressible | Evaluator count_zones_where (its own TODO #5) | ~½ day | open | |
| G3 | Moat's Reaction (interrupt an attack) | Behaviour catalog entry is trivial; real interrupt semantics need the resolution stack | — | ceiling | |
| G14 | Phases reference cleanup helpers not in the enabled-action list (warning) | Harmless; authoring-side polish | — | cosmetic |
3 · Ascension — game-defs/ascension.json
Center-row deck-builder, dual currencies, defeat-to-void, shared honor pool. Structurally it nearly played on arrival (markets, refill, dual costs, constructs all worked — G5/G6/G1 paying off). Seed 1: p1 ends 88 honor to 53 — declared a draw. Findings:
| ID | Observed / missing | Proposed fix | Effort | Status | Decision |
|---|---|---|---|---|---|
| A5 | 88 vs 53 honor resolved as a draw at the turn limit (fallback compares the life tracker; Ascension has none) | Turn-limit fallback: life-role tracker, else score-role tracker, else draw | minutes | open — quick win | |
| A1 | Defeated monsters land in the buyer's discard and get drawn later; the Void stays empty | A second acquisition composite: defeat — pay a (different) tracker, card goes to a named zone (void), reward fires. Destination + currency become per-action bindings (D4) | ~1 day | open | |
| A2 | on_defeat triggers silently rewritten to on_play; validation runs post-normalisation and saw nothing | Coercion half FIXED by point 6: unknown triggers are preserved + flagged loudly, never rewritten. The defeat/acquire vocabulary itself still arrives with A1 — and can now be expressed as event patterns (card_moved to discard/trash) | with A1 | half-shipped | |
| A3 | Honor pool never drained; the authored ending was unreachable | Effect target scope shared (adjust a shared tracker; optionally paired transfer: player +N, pool −N) | ~½ day | open | |
| A4 | terminates_at: end_of_round ignored (would end mid-round) | Ending timing: immediate / end-of-turn / end-of-round in engineCheckEndings | ~½ day | open | |
| A6 | “Always available” Mystic/Heavy piles sold out at 20 copies | Unlimited-supply flag on a zone (refill from nothing) | small | open — minor | |
| A7 | Honor printed on owned cards uncounted | = G12 (score-from-cards) | with G12 | shipped (with G12) | — |
| A8 | Deliberate typo'd effect key was correctly flagged | — positive control: validation works | — | confirmed | — |
4–6 · The pending games
| Game | What probing it would test that nothing else has | Forecast |
|---|---|---|
| 7 Wonders Duel (proposed as probe #4) | The pyramid (positional availability — taking a card uncovers others), resource production vs pools, opponent-dependent trading costs, science set-collection win, three coexisting win conditions | ~Half maps (military track = shared tug tracker ✓, multi-endings ✓, age decks ✓); half fails newly: tableau tier, computed costs, set-membership counting. Best next probe precisely because it is not a deck-builder. |
| Clank! | Deck-building fused with board movement | Declared out of reach: spatial movement has no home in the model (the structured-tableau/board tier). Probe only if we want to write the spec for that tier. |
| Slay the Spire | Solo mode, scripted enemy AI (intents), block-vs-attack, energy | Energy/block are pure tracker work ✓; needs solo mode + scripted opponents (a bot that follows a card script — new but bounded) + card-targeted damage (P1). A plausible probe after P1 lands. |
The decision matrix — one row per buildable feature
Deduplicated across all probes, ordered by how many games each unlocks. My recommendation is marked; the Decision column is yours.
| Feature | Findings it closes | Games it unlocks | Effort | Recommendation | Decision |
|---|---|---|---|---|---|
| Turn-limit fallback uses score tracker | A5 | Ascension (real winners today) | minutes | do immediately | |
| Score-from-cards (resolution.metric + sum_cards) | G12, G4, A7 | Dominion + Ascension produce true results | ~1 day | shipped | — |
| Defeat/acquire transaction + trigger vocabulary + warn-on-unknown-trigger | A1, A2, S3 (partly) | Ascension correct; Star Realms scrapping | ~1 day | after G12 | |
| Shared-tracker effects (pools) | A3 | Ascension's real ending; 7WD military track later | ~½ day | with A1 batch | |
| Binding parameters: play costs, phase type filters, use limits | G9, G10, G11 | Dominion faithful; groundwork for the Rules step UI (P7) | ~1 day | when Rules step starts | |
| Opponent-targeted effects | G2 | Dominion attacks (Militia, Witch) | ~½ day | medium | |
| Families finished (UI + AI + effects) | S1 | Real Star Realms ally system | ~1 day | medium | |
| count_zones_where + ending timing | G13, A4 | Dominion's real end trigger; Ascension round-end | ~1 day | medium | |
| Activated abilities (Q1 wiring) | S2 | Star Realms bases; tap-style designs | ~2 days | later | |
| Unlimited-supply zones | A6 | Ascension polish | small | later | |
| Tableau/positional tier | 7WD pyramid, Clank! | Whole genres beyond card rows | big | only after a probe writes its spec | |
| Resolution stack (reactions/interrupts) | G3 | Dominion Moat; TCG-style play | very big | stays ceiling |
Verification probe — Dominion, kingdom 2 2026-08-05 · all green
Per the method note (verification probes verify fixes, they don't discover): dominion-verification.json swaps Woodcutter → Guild Broker ("this turn, every card you buy pays back 1 coin" — a point-6 pattern trigger) and Moat → Sacrifice ("trash 2 cards of your choice" — points 2+3 composed: chosen discard redirected to the trash). First game ever to import with a clean card validation (zero problems).
| Verified | Evidence |
|---|---|
| Point 6 through the full pipeline | import detects the pattern (hasPatternTriggers); micro-proof: with Broker in play, a 3-cost buy nets −2 coins (paid 3, rebated 1) |
| Points 2+3 in a real game | 184 Sacrifice plays across 10 seeds → 318 logged decisions, 348 cards in the trash; greedy trashes its deadest cards (deck thinning emerges) |
| Point 7 + G12 stack | 20 greedy games: 13–7 real winners, no draws among the 14 Province-depletion endings |
| Point 1 at 3 players | 3p × 10 seeds: 4/5/1 wins, expression piles at 12/20, games complete |
| Bot-quality signal intact | random bot: 20/20 hit the turn limit with expensive piles dead — the known "decisions don't matter for random" detector, now with scored results via A5 |
Still deliberately inexpressible (recorded, untouched by this probe): gain-from-supply (Witch/Workshop — A1), reactions (Moat — G3, waits for points 4+3), "any 3 piles empty" (G13), static modifiers (Merchant — point 5).
Probe: Jaipur 2026-08-06 · the first non-deck-builder
Goal-built to force user-definable actions: no deck-building, shared draw deck,
tokens-as-cards with descending values, one action per turn. The entire turn
structure is action_definitions — three preset instances + one
composer action (sell, needing three bonus tiers). Endings: G13
(count_zones_where three-piles-empty) + deck exhausted; scoring: G12
over won tokens.
| Result | Evidence |
|---|---|
| It plays | 40 games both bots: ~0 draws among proper endings, e.g. 43–64 on token points; all four actions used (26 takes / 21 sells / 19 trades / 8 sweeps, 85 decisions in one game); bonus tiers firing |
| Engine additions forced | shared-deck opening deal (draw_n_cards from a shared zone); greedy learning to pick custom actions (it passed forever — a bot gap, found in minutes) |
Approximations, recorded in the def (_approximations): market opens
unseeded (fills to 5 turn 1); precious goods may sell single; trade gives from
hand only (no camels); no hand limit, no herd bonus, single round. None move the
six metrics materially; all expressible later (min-count filters per action
binding, herd bonus as an ending-metric term).