Files
scopone/docs/FINDINGS.md
2026-04-09 22:30:27 +02:00

13 KiB

Findings

Last Updated: 2026-04-09T00:00:00.000Z

Summary

Initializer refresh for SCOPONE-0009. The cached findings were stale relative to the live source tree, so the observations below reflect the current Phaser, worker, and AI implementation.

Codebase Observations

  • Primary gameplay code currently lives in 10 TypeScript source files under src/; the Android wrapper adds 3 Java files.
  • The project is structurally split between framework-free gameplay modules in src/game/ and Phaser scene code in src/scenes/.
  • src/scenes/GameScene.ts and src/game/ai.ts remain the two largest concentrations of application logic.
  • The AI transport layer is now a stable three-file path: ai-worker-protocol.ts, ai-worker-client.ts, and ai.worker.ts.
  • The AI exposes three difficulty levels: beginner, advanced, and master.
  • advanced and master both use CardTracker to reason about unseen cards without directly reading hidden hands.
  • The current master search profile is timeBudgetMs: 4600, sampleCount: 10, maxDepth: 6, batchSize: 2.
  • GameScene consumes AI progress callbacks to update an on-screen think bar while a worker request is running.
  • AIWorkerClient fails over pending work to in-thread chooseMove() if worker creation, posting, or deserialization fails.
  • The Android wrapper targets SDK 36 with minSdkVersion 24 and applies immersive mode from the native activity.
  • Audio remains procedural via Web Audio; no dedicated audio asset pipeline is present in the source tree.
  • No ESLint or Prettier configuration is present.
  • The only repository-wide verification command supplied is npx tsc --noEmit.

Potential Improvement Areas

  • GameScene.ts still centralizes layout, turn flow, HUD updates, effects, and audio in one scene class, which raises maintenance cost.
  • ai.ts still combines heuristic tiers, inference helpers, determinization, and alpha-beta evaluation in one module.
  • Worker transport is isolated cleanly, but progress rendering remains coupled to scene-level UI concerns.
  • A 4600 ms master search budget may still be noticeable on slower mobile devices even with batch yielding.
  • There is no dedicated automated rules or AI test suite beyond type-checking.
  • Formatting and style are enforced socially rather than by automated linting or formatting tools.

Current Rule / Implementation Notes

Capture behavior in engine.ts

  • Direct-match capture has priority over subset-sum capture.
  • When multiple direct matches exist, findCaptures() returns one single-card option per matching card.
  • Subset-sum captures are considered only when no direct match exists.
  • applyMove() defaults to the first legal capture if no explicit capture choice is supplied.
  • Scope is awarded only when a capture clears the table before the final play of the round.

AI implementation snapshot

  • beginner uses a simpler heuristic with noise to remain beatable.
  • advanced adds race awareness, anti-scopa logic, partner setup, anchor play, and tracker-based probability estimates.
  • master orders legal moves with a quick evaluator, samples hidden hands, and scores them with alpha-beta search under the active deadline.
  • Progress is reported through AIDecisionProgress so the scene can keep the think bar responsive.

Worker execution snapshot

  • GameScene creates AIWorkerClient during create() and disposes it on both shutdown and destroy.
  • AIWorkerClient serializes CardTracker state through toSnapshot() instead of attempting to transfer the class instance.
  • ai.worker.ts rebuilds tracker state with CardTracker.fromSnapshot() before calling chooseMove().
  • Progress, result, and serialized error payloads all travel through ai-worker-protocol.ts.
  • If worker execution becomes unavailable, pending requests are rerun with the in-thread AI path rather than being dropped.

Scene / UI implementation snapshot

  • BootScene loads atlas assets and presents a simple loading bar.
  • MenuScene exposes difficulty selection before match start.
  • GameScene tracks played and captured cards in CardTracker as the round evolves.
  • The scene owns score HUD rendering, player labels, status text, think-bar rendering, and procedural particle effects.
  • Round-end and match-end flows remain managed inside the scene instead of separate overlay components.

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 are dealt at the start of the round; the table begins empty.
  • Turns advance in the implementation as 0 -> 1 -> 2 -> 3.

Capture Rules

  1. If the played card matches one or more table cards by value, a direct match must be taken.
  2. If multiple direct matches exist, one matching table card is chosen.
  3. If no direct match exists, a subset of table cards may be captured when their values sum to the played value.
  4. A direct match has priority over any possible sum capture.
  5. Scopa awards a point only when the table is cleared before the final play of the round.

Scoring

Category Rule
Carte Majority of captured cards
Denari Majority of denara suit cards
Settebello Team that captures the 7 of denara
Primiera Highest best-of-each-suit prime value
Scope One point per scopa

Primiera values used in code

Card value Primiera value
7 21
6 18
1 16
5 15
4 14
3 13
2 12
8, 9, 10 10

SCOPONE-0008: AI progress rendering notes (2026-04-02)

  • The current implementation does not use Phaser TimerEvent progress helpers.
  • Instead, chooseMove() emits its own normalized progress payload through AIDecisionProgress.
  • GameScene.updateThinkBar() renders remaining time from that callback.
  • The yielding behavior in the master search path is necessary so the browser can repaint while search batches continue.

SCOPONE-0009: Phaser scene lifecycle notes (2026-04-08)

  • Source: Context7 /websites/phaser_io_api-documentation, query Phaser 3.87 Scene lifecycle create restart shutdown destroy event listeners scene restart preserving external state.
  • Phaser dispatches shutdown when a scene stops being active but may be re-used later; resource cleanup that should also cover final teardown can additionally listen to destroy.
  • The current GameScene pattern of registering one-shot shutdown and destroy handlers is aligned with Phaser guidance for worker disposal and UI cleanup.
  • Dealer rotation and next-round state changes can stay inside the existing in-scene orchestration without requiring a different Phaser lifecycle primitive.

SCOPONE-0009: Iteration 3 strength-planning notes (2026-04-08)

  • src/game/ai.ts currently generates master determinization samples by uniformly shuffling all unseen cards and slicing them into opponents' hidden hands; it does not yet bias assignments by dealer role, parity residue, or observed capture semantics.
  • The transposition-table key in src/game/ai.ts includes the exact sampled hidden hands, so reuse is effective within a determinized sample but does not merge equivalent uncertainty classes across different sample assignments.
  • No executable benchmark harness or AI quality test module exists under src/; the current timing evidence lives only in prompt artifacts such as prompts/SCOPONE-0009/iteration_2/benchmark_summary.md.
  • tsconfig.json includes only src, so any automated quality or self-play harness that should be typechecked by the default npx tsc --noEmit command needs to live under src/ unless the project configuration changes.

SCOPONE-0009: Iteration 3 continuation notes (2026-04-09)

  • The accepted iteration 3 benchmark work is now present in source: src/game/ai-benchmark.ts and src/game/ai-benchmark-fixtures.ts exist under src/, package.json exposes benchmark:ai-quality, and the harness already measures fixed fixtures, self-play, and production-master timing.
  • The live production master budgets in src/game/ai.ts are already below the requested five-second ceiling in every shipped branch: base 4300, <= 20 cards 4350, <= 12 cards 4200, <= 8 cards 3900, <= 6 cards 3600, and <= 4 cards 3200 milliseconds.
  • src/scenes/GameScene.ts still executes AI turns immediately after await aiClient.chooseMove(...) resolves in doAIMove(); there is currently no scene-level minimum think-time floor.
  • src/scenes/GameScene.ts still uses a bare setStatus(msg) helper that only calls this.statusText.setText(msg); there is no timed persistence policy, no cancellation of prior status timers, and no dedicated post-move outcome message path.
  • Phaser 3.87 scene timers can be cancelled with TimerEvent.remove() and their references cleaned with TimerEvent.destroy(); the current scene already listens to shutdown and destroy, so timed status cleanup belongs in the existing handleSceneShutdown() path.

SCOPONE-0009: Iteration 3 refresh notes (2026-04-09)

  • The current src/game/ai.ts heuristic does not reason about numeric even/odd card values; it already computes the unseen copy count for each rank and stores whether the remaining copies for that rank are in a singleton residue or a paired residue, but the internal names still use oddResidue, evenResidue, and scoreParityTableState, which can mislead future work.
  • The live tactical seam that needs refresh is therefore naming and policy framing, not a wholesale replacement of the underlying signal: the AI should explicitly treat apparigliare / sparigliare as preserving or breaking same-rank copy residues and connect that to table control, scopa prevention, and forced replies.
  • The accepted benchmark harness in src/game/ai-benchmark.ts still measures runtime with performance.now() and therefore depends on wall-clock search time. It does not yet use an injected or simulated search clock for fast validation runs.
  • src/scenes/GameScene.ts already contains the previously planned pacing and status work: AI_MIN_THINK_MS = 1000, MOVE_OUTCOME_STATUS_MS = 2000, a timer-backed setStatus(...), and handleSceneShutdown() timer cleanup are all present in source and should be preserved rather than re-planned.
  • src/game/ai-benchmark-fixtures.ts still contains one fixture and tag using the stale label dealer-parity-preserve-pair / critical-dealer-parity; if benchmark files are reopened for simulated timing, that terminology should be refreshed to rank-residue wording at the same time.

SCOPONE-0009: Iteration 5 planning notes (2026-04-09)

  • The live AI quality harness in src/game/ai-benchmark.ts still hard-codes an iteration: 4 quality gate with targets of 12 fixed fixtures, 4 critical concepts, and 48 self-play matches requiring >= 30 wins and <= 12 losses; the readable summary does not yet surface cross-seed aggregation such as the recurring dual-loss seeds from the latest rejected run.
  • src/game/ai-benchmark-fixtures.ts currently covers settebello-capture, anti-scopa-defense, dealer-rank-residue-preservation, and exact-endgame-resolution as critical concepts, but it does not yet encode an explicit critical fixture for partner invitation / partner scopa setup and does not yet make fare scopa itself a critical concept despite the user's new ordering.
  • Non-critical fixtures already exist for denari pressure, late denari shielding, and seven pressure, so the benchmark seam for iteration 5 is to rebalance critical-vs-fixed coverage and ordering expectations rather than to introduce a second harness.
  • Cross-tier heuristic priorities are concentrated in src/game/ai.ts: beginner logic in scoreCaptureBeginner() / scoreDumpBeginner(), advanced logic in scoreCaptureAdv() / scoreDumpAdv(), and master root/search logic in quickEval(), orderSearchMoves(), generateSamples(), and evaluateFast().
  • Partner-aware logic already exists in all three tiers, but it is currently additive and distributed across multiple heuristics; there is no single explicit priority ladder that guarantees partner setup outranks seven denial, denari denial, and generic material capture across the whole file.
  • Anti-scopa prevention is already strong enough to pass the fixed tactical fixtures, but the rejected iteration 4 result (18 wins, 30 losses over 48 seeded self-play matches) indicates that full-game strength is still limited by strategic continuity across seed-intrinsic lines rather than by isolated tactical blindness.