# Findings > Last Updated: 2026-03-31T00:00:00.000Z ## Summary Initial analysis of the Scopone Scientifico Phaser 3 codebase. This document is populated by the Planner agent as research is performed. ## Codebase Observations - **Total source files**: 9 TypeScript (6 in `src/`), 3 Java (Capacitor boilerplate) - **Largest file**: `GameScene.ts` (~1340 lines) — rendering, input, effects, audio, UI - **Game logic is framework-independent**: `game/` modules have zero Phaser imports - **No test framework**: only `tsc --noEmit` for type-checking - **No linter/formatter**: code style enforced manually - **AI plays all 3 non-human seats** using the same heuristic - **Procedural audio**: all sound is Web Audio oscillators — no audio asset files ## Potential Improvement Areas - **AI cheats with perfect information**: `scoreDump()` and `opponentThreatScore()` in `ai.ts` iterate `opp.hand` directly — bots can see all opponent cards. Must be replaced with imperfect-information card tracking. - **No mastery/difficulty levels**: All 3 AI seats use the same heuristic at the same strength. - **No card tracking**: No module tracks which cards have been played or remains in the deck. - **No minimax**: Pure heuristic scoring, no look-ahead or game tree search. - **Allied bot is selfish**: Compagno (player 2) plays identically to opponents — no cooperative strategy. ## Research Performed ### Web Research: Scopone Scientifico Rules (2026-03-31) **Sources**: Wikipedia (*Scopa* article, Scopone section), Pagat.com (*Scopone* page by John McLeod) #### Core Rules (Scopone Scientifico variant) - 4 players, 2 fixed teams of 2 (sit opposite): Team A = players 0+2, Team B = players 1+3 - 40-card Napoletane deck: 4 suits (bastoni, coppe, denara, spade), values 1–10 - **All 40 cards dealt** (10 each), **no initial table cards** — the "scientifico" variant - Play passes around the table (counter-clockwise in Italian tradition; this game uses 0→1→2→3) - Each turn: play one card face-up to the table #### Capture Rules 1. If the played card's value **matches a table card**, the table card **must** be captured (single card, not a sum) 2. If **multiple table cards match** the played value, exactly one is captured (player chooses) 3. If **no direct match**, the player may capture a **subset of table cards summing** to the played value 4. If the played card matches both a single card and a sum, **the single card must be captured** (not the sum) 5. There is **no obligation to play a capturing card** — a player may choose to play a non-capturing card instead. But if the played card CAN capture, it MUST capture. 6. **Scopa**: capturing ALL remaining table cards awards +1 point (except on the very last card of the round) #### Scoring (per round, 4 fixed points + scope) | Category | Rule | |------------|----------------------------------------------------------| | **Carte** | Team with majority of captured cards (20+ of 40). Tie = no point. | | **Denari** | Team with majority of coins/denara suit cards (6+ of 10). Tie = no point. | | **Settebello** | Team capturing the 7 of denara. Always awarded. | | **Primiera** | Team with highest prime value. Prime = best card per suit using special scale. Tie = no point. Must have all 4 suits. | | **Scope** | +1 per scopa achieved during play. | #### Primiera Values (confirmed matching codebase) | Card value | Primiera value | |------------|---------------| | 7 | 21 | | 6 | 18 | | 1 (Ace) | 16 | | 5 | 15 | | 4 | 14 | | 3 | 13 | | 2 | 12 | | 8,9,10 | 10 | A team missing an entire suit **cannot win primiera** (even 3×21=63 loses to 21+16+16+16=69 with all 4 suits). #### Winning - First team to **11+ points** at the end of a round wins - If both reach 11 in the same round, higher total wins; if tied, play continues #### Strategy Notes (from Pagat.com) - **7 of coins (settebello)** is the single most valuable card — contributes to all 4 fixed scoring categories - **Avoid giving scope**: leave table total ≥ 11 when possible - **Anchor strategy**: leave a card on table that your team controls (you hold duplicates of that value) - **Whirlwind**: consecutive scope — clearing the table forces opponent to play, partner captures, repeat - **Sevens > sixes > aces** in priority for primiera control - **Paired/unpaired tracking**: if all captures are single-card matches, the last card matches the last table card. Sum captures disrupt this pattern, important for end-game planning. ### Codebase Capture Rule Validation The `findCaptures()` in `engine.ts` correctly implements: - Direct match priority over sum captures ✓ - Multiple direct matches: takes ALL matching cards (slight deviation — pagat.com says choose ONE, but Wikipedia says take all. The codebase takes all direct matches.) - Sum subsets via power set enumeration ✓ - `applyMove()` auto-captures when possible ✓ ### SCOPONE-0003: findCaptures() Change Analysis (2026-03-31) **Current behavior** (engine.ts lines 43-62): When direct matches exist, `findCaptures()` bundles ALL into one `Card[]` and returns immediately (`results.push([...directMatches]); return results`). Sum captures are never computed when direct matches exist. **Required behavior**: 1. Each direct match returned as a **separate** single-card option: `[match_a]`, `[match_b]`, etc. 2. Sum captures computed **alongside** direct matches (removing the early return). 3. Both direct-match and sum options presented to the player for choice. **Mathematical proof that sums never include direct-match cards**: A direct-match card has value V = played.value. Any subset of size ≥ 2 containing this card sums to at least V + 1 (minimum card value is 1). Since we're looking for subsets summing to exactly V, no valid sum can ever include a direct-match card. Therefore, computing sums from the entire table is safe — no filtering needed. **Callers impacted**: - `canCapture()` (engine.ts:77): Uses `.length > 0` — unaffected. - `applyMove()` (engine.ts:139): Uses `captures[0]` as default — now defaults to the first direct-match card (single card) instead of all bundled matches. Correct behavior. - `chooseMove()` (ai.ts:23): Iterates and scores all capture sets — now scores each direct match individually. **Improvement**: AI can now prefer the higher-value direct match (e.g., 7♦ over 7♣). - `opponentThreatScore()` (ai.ts:151): Uses `caps[0].length` — changes from N to 1 for multi-match scenarios. More accurate (opponent captures one card, not all). - `scoreDump()` (ai.ts:103): Uses `caps` for threat checking — unaffected. - `GameScene.onCardClick()`: Already routes `captures.length > 1` to `highlightMultipleCaptures()` — works correctly with new behavior. ### Minimax Feasibility Analysis - 10 cards per player × 4 players = 40 total moves per round - Full game tree: ~10^12 nodes — infeasible for exhaustive search - **Approach**: Depth-limited alpha-beta with determinization for imperfect information - Sample N possible opponent hand assignments consistent with card tracking - Run minimax on each sample to limited depth (4–6 plies) - Average/vote across samples for best move - Alpha-beta pruning reduces effective branching factor significantly - Depth 4 (one full rotation) with ~5 moves per player = ~625 nodes per sample — very manageable - 10–20 samples × 625 nodes = ~6,000–12,500 evaluations — runs in <100ms on modern hardware