Files
scopone/docs/FINDINGS.md
2026-04-10 22:35:01 +02:00

9.8 KiB

Findings

Last Updated: 2026-04-10T20:33:00.000Z

Summary

Documentation refresh for the current repository state. docs/ARCHITECTURE.md and docs/FINDINGS.md were updated to remove stale workflow-specific notes and now reflect the live source tree directly. docs/CODE_STYLE.md remains valid without changes.

Codebase Observations

  • Primary gameplay code currently lives in 17 TypeScript source files under src/; the Android wrapper adds 3 Java files under android/app/src/.
  • 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.
  • A dedicated audio preference seam exists in src/game/preferences.ts, and it is consumed from both MenuScene and SettingsScene.
  • main.ts imports and registers SettingsScene directly in the Phaser scene list.
  • The AI transport layer is 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 base master search profile is 4300 ms / 8 samples / depth 5 / batch 2, with tighter endgame branches down to 3200 ms / 4 samples / exact remaining depth / batch 1 when 4 cards remain.
  • GameScene consumes AI progress callbacks to update an on-screen think bar while a worker request is running.
  • GameScene enforces AI_MIN_THINK_MS = 1000 and MOVE_OUTCOME_STATUS_MS = 2000 through timer-backed scene logic.
  • AIWorkerClient fails over pending work to in-thread chooseMove() if worker creation, posting, or deserialization fails.
  • MenuScene includes a compact-viewport layout path driven by calculated panel bounds and camera zoom.
  • The AI benchmark harness lives in source under src/game/ai-benchmark.ts and src/game/ai-benchmark-fixtures.ts, and package.json exposes it as npm run benchmark:ai-quality.
  • The current benchmark contract is iteration 5: 13 fixed fixtures, 6 critical concepts, and 48 self-play matches.
  • 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 was discovered in the source tree.
  • No ESLint or Prettier configuration was discovered.
  • No top-level tests/ directory was discovered during analysis.

Potential Improvement Areas

  • GameScene.ts still centralizes layout, turn flow, HUD updates, effects, audio, status messaging, and AI orchestration in one scene class.
  • ai.ts still combines heuristic tiers, inference helpers, determinization, move ordering, and alpha-beta evaluation in one module.
  • MenuScene.ts carries responsive layout and decorative rendering logic in the same scene that handles navigation and difficulty selection.
  • Worker transport is isolated cleanly, but progress rendering and fallback behavior remain coupled to scene-level UI concerns.
  • A 3.2 to 4.3 second master search window may still be noticeable on slower mobile devices even with yielding and minimum-think pacing already in place.
  • No dedicated automated rules test suite was discovered beyond type checking and the AI benchmark harness.
  • 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.
  • Scopa 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, denari pressure, and tracker-based probability estimates.
  • master orders legal moves with a quick evaluator, samples hidden hands, and scores them with alpha-beta search under a dynamic deadline.
  • Progress is reported through AIDecisionProgress so the scene can keep the think bar responsive.
  • CardTracker remains the inference surface for unseen-card reasoning across higher difficulties.

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.
  • main.ts registers BootScene, MenuScene, GameScene, and SettingsScene directly in the Phaser game config.
  • MenuScene exposes both difficulty selection and a dedicated entry point into SettingsScene, with a separate compact-layout branch for smaller viewports.
  • SettingsScene persists music and effects toggles immediately through saveAudioPreferences().
  • GameScene reads normalized audio preferences from scene data or persisted storage 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, procedural audio, and particle effects.
  • Round-end and match-end flows remain managed inside the scene instead of separate overlay components.

Benchmark snapshot

  • ai-benchmark.ts uses a simulated timing source for fixture and self-play evaluation rather than only wall-clock timing.
  • The benchmark summary records per-seed aggregates, dual-loss seeds, and a regression watchlist intersection.
  • The harness remains source-local under src/, so it is covered by the default TypeScript include set.

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-0010: UI, settings, and benchmark refresh notes (2026-04-09)

  • src/game/preferences.ts is the authoritative audio preference seam. It normalizes stored values and shields scenes from malformed storage state.
  • src/scenes/MenuScene.ts reads persisted audio preferences and exposes a dedicated settings entry point.
  • src/scenes/SettingsScene.ts exists as a real scene and persists music and effects toggles independently through saveAudioPreferences().
  • src/scenes/GameScene.ts contains the current pacing and status behavior: AI_MIN_THINK_MS = 1000, MOVE_OUTCOME_STATUS_MS = 2000, timer-backed status updates, and shutdown cleanup.
  • src/game/ai-benchmark.ts enforces an iteration 5 contract with simulated timing, cross-seed aggregation, dual-loss reporting, and a regression watchlist intersection.
  • src/main.ts imports and registers SettingsScene directly.