diff --git a/.agents/skills/game-analysis/SKILL.md b/.agents/skills/game-analysis/SKILL.md new file mode 100644 index 0000000..c8539b3 --- /dev/null +++ b/.agents/skills/game-analysis/SKILL.md @@ -0,0 +1,29 @@ +--- +name: game-analysis +description: This skill should be used when analysing Tractor game-simulation logs with BigQuery — e.g. "upload game logs to BigQuery", "run the game analysis", "generate the KPI/performance report", "set up the BigQuery analysis infrastructure", or questions about the game's JSON log-event schema. Wraps the repo-root `analysis/` pipeline (uv-run Python) and documents the log-event shapes its queries consume. +version: 1.0.0 +license: UNLICENSED +--- + +## Overview + +Analyse Tractor game-simulation logs in Google Cloud BigQuery. The pipeline lives at the repo root in `analysis/` (kept there so it stays runnable as a CLI); this skill is how to drive it, plus the reference for the log-event shapes it reads. + +## Running the pipeline + +Run from the repo root: + +```bash +uv run analysis/bigquery_main.py setup # Create the BigQuery dataset / tables / transfer +uv run analysis/bigquery_main.py upload # Upload local game logs +uv run analysis/bigquery_main.py analyse # Run analysis and generate the performance report +``` + +- **Config**: `analysis/config.py` (project / dataset settings). +- **Queries**: `analysis/analysis_query.sql`. +- **Reports**: generated into `analysis/reports/`. +- **Producing logs**: `run_simulations.sh` (repo root) generates the log files that `upload` consumes — see the `simulation-testing` skill. + +## Log event reference + +The analysis reads newline-delimited JSON log events. See [reference/log-events.md](reference/log-events.md) for the common envelope and the key event shapes. The authoritative field set for any event is always its `gameLogger` call site in `src/`. diff --git a/.agents/skills/game-analysis/reference/log-events.md b/.agents/skills/game-analysis/reference/log-events.md new file mode 100644 index 0000000..b4fa483 --- /dev/null +++ b/.agents/skills/game-analysis/reference/log-events.md @@ -0,0 +1,52 @@ +# Log Event Reference + +Game logs are newline-delimited JSON, one object per event. **The authoritative, current +field set for any event is its `gameLogger` call site in `src/`** — this is an +analysis-oriented index, not an exhaustive schema. JSON keys are camelCase. + +## Common envelope + +Every entry has: `timestamp`, `level` (INFO / DEBUG / WARN / ERROR), `event`, +`sequenceNumber`, `appVersion`, `gameId`, `message`, and an event-specific `data` object. + +Cards appear in display notation: `A♠`, `10♦`, `K♥`, and `BJ` / `SJ` for the jokers. + +## Round lifecycle + +- **`game_initialized`** (once, round 1) and **`round_start`** (every round): `roundNumber`, + `defendingTeam`, `attackingTeam`, `roundStartingPlayer`, `trumpRank`, `teamRanks` + (`game_initialized` also carries `deckSize`). `round_preparation_start` / + `round_preparation_completed` bracket the per-round preparation step. +- **`trump_finalized`**: `finalDeclaration`, `trumpInfo`. **`trump_declared`**: `playerId` + (plus declaration detail). +- **`round_end`**: `roundNumber`, `attackingTeamWon`, `finalPoints`, `gameOver`, + `gameWinner`, `rankChanges`, `defendingTeam`, `attackingTeam`, `teamPointsAfter`. +- **`game_over`**: `winner`, `roundNumber`, `finalPoints`, `trickPoints`, `kittyBonus`, + `winCondition`. + +## Play + +- **`card_play`**: `playerId`, `isHuman`, `cardsPlayed`, `cardsPlayedCount`, + `handSizeBefore`, `handSizeAfter`, `trickNumber`, `roundNumber`, `currentTrickState`. +- **`trick_completed`**: `trickNumber`, `winningPlayer`, `trickPoints`, `isFinalTrick`, + `allPlays` (`[{ playerId, cards }]`), `roundNumber`. +- **`kitty_pickup`** / **`kitty_swap_completed`** (DEBUG): `kittyPoints` / + `selectedCardPoints` (the latter only when player hands are included in logs). + +## AI decisions + +- **`ai_leading_decision`** / **`ai_following_decision`** (DEBUG): `decisionPoint`, + `player`, `decision`. Async variants (`*_async`) carry the same fields. Richer strategic + context is emitted as separate DEBUG events by the strategy modules. + +## LLM layer (only when the optional LLM players are enabled) + +- **`llm_decision_success`**: `playerId`, `reasoning`, `play`, `attempts`. +- **`llm_adaptive_shortcut_*`** (`lead_ace`, `lead_unbeatable`, `lead_single_candidate`, + `follow_multi_combo`, `follow_hand_size`, `follow_forced_suit`, `follow_single_combo`): + `playerId`, `play` — a forced/obvious play made without calling the model. +- **Retry / rejection** (WARN): `llm_json_parse_failed`, `llm_invalid_format_keys`, + `llm_card_mapping_failed`, `llm_decision_invalid_rule`. +- **Fallback** (ERROR): `llm_retries_exhausted`, `llm_fallback_triggered`. +- **API transport**: `llm_api_call_start` / `llm_api_call_success` (INFO), plus the + `llm_api_*` error events (ERROR). diff --git a/.gitignore b/.gitignore index f71cf38..39d7afa 100644 --- a/.gitignore +++ b/.gitignore @@ -58,3 +58,4 @@ logs/* !**/.gitkeep debug/ .env +.vscode/ \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md index 539251c..90f00d1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -19,7 +19,6 @@ This file provides guidance to coding agents when working with this repository. - [docs/AI_SYSTEM.md](docs/AI_SYSTEM.md) - AI intelligence system documentation - [docs/GAME_RULES.md](docs/GAME_RULES.md) - Complete game rules and strategy - [docs/MULTI_COMBO.md](docs/MULTI_COMBO.md) - Multi-combo implementation guide -- [docs/LOG_EVENT_SCHEMA.md](docs/LOG_EVENT_SCHEMA.md) - Log event structure - [docs/VERSIONING_STRATEGY.md](docs/VERSIONING_STRATEGY.md) - Versioning strategy and OTA compatibility ## Setup Commands @@ -53,13 +52,13 @@ uv run analysis/bigquery_main.py analyse # Run analysis ### Key Enums ```typescript enum PlayerId { Human = 'human', Bot1 = 'bot1', Bot2 = 'bot2', Bot3 = 'bot3' } -enum GamePhase { Dealing = 'dealing', Playing = 'playing', Scoring = 'scoring' } +enum GamePhase { Dealing = 'dealing', KittySwap = 'kittySwap', Playing = 'playing', Scoring = 'scoring', RoundEnd = 'roundEnd', GameOver = 'gameOver' } enum TrickPosition { First = 'first', Second = 'second', Third = 'third', Fourth = 'fourth' } enum PointPressure { LOW = 'low', MEDIUM = 'medium', HIGH = 'high' } ``` ### File Organization -- `src/ai/` - AI strategic decision-making (following/, leading/, kittySwap/, trumpDeclaration/ sub-dirs) +- `src/ai/` - AI strategic decision-making (following/, leading/, kittySwap/, trumpDeclaration/, llm/ sub-dirs) - `src/game/` - Core game logic and rules - `src/types/` - Type definitions with clean re-exports - `src/utils/` - Shared utilities (game-agnostic helpers, sharedStyles, gameLogger, persistence) diff --git a/README.md b/README.md index 5bf15aa..405803f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Tractor (升级) -A React Native implementation of the classic Chinese card game **Tractor** (also known as Shengji/升级), featuring intelligent AI opponents and authentic gameplay mechanics. +A React Native implementation of the classic Chinese card game **Tractor** (also known as Shengji/升级), featuring algorithm-based AI players (with an optional LLM mode) and authentic gameplay mechanics.   @@ -23,7 +23,7 @@ A React Native implementation of the classic Chinese card game **Tractor** (also **Core Gameplay:** - **Team-based**: You + Bot 2 vs Bot 1 + Bot 3 in strategic cooperation -- **Rank Progression**: Start at rank 2, advance through ranks by collecting 80+ points per round +- **Rank Progression**: Start at rank 2; the attacking team advances by capturing 80+ points, the defenders by holding them under 80 - **Trump Declaration**: Players can declare trump during progressive dealing using pairs or jokers - **Card Combinations**: Play singles, pairs, or tractors (consecutive pairs) with complex following rules - **Victory Condition**: First team to advance to Ace rank wins the game @@ -31,19 +31,18 @@ A React Native implementation of the classic Chinese card game **Tractor** (also **Unique Features:** - **Progressive Dealing**: Cards dealt one-by-one with real-time trump declaration opportunities -- **Kitty Management**: Winner receives 8 hidden cards that can provide massive endgame bonuses +- **Kitty Management**: The round starter picks up and swaps 8 hidden cards at the deal; winning the final trick turns the kitty into a bonus point swing - **Complex Trump Hierarchy**: Big Joker > Small Joker > Trump rank cards > Trump suit cards *Complete rules and quick start guide in **[Game Rules](docs/GAME_RULES.md)*** ## Key Features -- **🧠 4-Phase AI Intelligence**: Memory-enhanced decision trees with strategic point management and trump conservation -- **🃏 Smart Card Auto-Selection**: Intelligent pair/tractor detection with tap-to-toggle controls -- **⚡ Progressive Dealing System**: Real-time trump declarations with sophisticated AI strategy -- **🎯 Advanced Kitty Management**: Strategic suit elimination and endgame bonus multipliers -- **💾 Automatic Game Persistence**: Seamless save/restore with auto-recovery on app restart -- **📊 BigQuery Analysis System**: Enterprise-scale game data analysis with Google Cloud BigQuery for massive datasets +- **🧠 Algorithm-Based AI Engine**: Memory-enhanced decision modules — scoring-based leading, scenario-based following, strategic point management and trump conservation +- **🤖 Optional LLM Players**: OpenRouter-backed language-model play — for your AI teammate and opponents alike — layered on the rule-based engine for ambiguous trick decisions (off by default; bring your own API key) +- **🃏 Card Auto-Selection**: Pair/tractor detection with tap-to-toggle controls +- **💾 Automatic Game Persistence**: Save/restore with auto-recovery on app restart +- **📊 BigQuery Analysis Pipeline**: Game-log analysis on Google Cloud BigQuery for large simulation datasets - **🌍 Multilingual Support**: Full English and Chinese localization with automatic language detection - **📱 Touch-Optimized UI**: Smooth animations, team color coding, and dark theme support - **🏆 Authentic Gameplay**: Complete Shengji/Tractor rules with proper combination following @@ -52,9 +51,9 @@ A React Native implementation of the classic Chinese card game **Tractor** (also **Built with React Native + Expo** for cross-platform mobile development: -- **React Native 0.81+** - Cross-platform mobile framework -- **Expo SDK 55** - Development tools and native API access -- **TypeScript 5.9+** - Strict type safety and enhanced developer experience +- **React Native** - Cross-platform mobile framework +- **Expo** - Development tooling and native API access +- **TypeScript** (strict) - Type safety and enhanced developer experience - **React i18next** - Type-safe internationalization with automatic language detection - **Jest** - Comprehensive tests with React Testing Library - **ESLint** - Code quality with React Native specific rules @@ -63,7 +62,7 @@ A React Native implementation of the classic Chinese card game **Tractor** (also **Architectural Highlights:** - **Modular Game System**: Focused game modules with direct imports (no re-export hub) -- **Modular AI System**: 20 focused modules with memory-enhanced intelligence organized by functional domain +- **Modular AI System**: Memory-enhanced decision modules organized by functional domain, plus an optional LLM layer - **Unified Trick Structure**: Streamlined game state with plays array for consistent data flow - **Consolidated Hook Architecture**: Single-responsibility hooks with minimal interdependencies - **Progressive Dealing System**: Unified dealing and trump declaration management @@ -74,39 +73,28 @@ A React Native implementation of the classic Chinese card game **Tractor** (also ## AI Intelligence -**Sophisticated Memory-Enhanced AI system** with comprehensive card tracking, strategic point management, and intelligent trump conservation. +The AI runs on an always-on **algorithm-based engine** — modular, memory-enhanced decision logic — with an **optional LLM layer** on top. -**Core AI Capabilities:** +**Algorithm-based engine:** -- **🧠 Memory-Enhanced Strategy**: Card tracking with guaranteed winner identification -- **🎯 4-Priority Decision Chain**: Team coordination → Opponent blocking → Trick contention → Strategic disposal -- **🃏 Streamlined Kitty Management**: Rule-based exclusion strategy with intelligent suit elimination -- **⚡ Real-Time Trump Declarations**: Hand quality analysis with timing optimization -- **🛡️ Strategic Conservation**: Trump hierarchy preservation and point card management +- **Memory & card tracking** — played-card memory identifies guaranteed winners and confirmed voids, and drives void exploitation (teammate vs opponent) +- **Scoring-based leading** — every candidate lead scored for control, points, and trump conservation +- **Scenario-based following** — a priority chain (team coordination → opponent blocking → trick contention → disposal), position-aware for all four seats +- **Trump conservation** — hierarchical values protect jokers, trump-rank cards, and high pairs +- **Declaration & kitty strategy** — hand-quality-driven trump declaration timing and rule-based kitty swaps -**Complete Memory-Enhanced Intelligence:** +**Optional LLM trick-play layer:** -- **Foundation Layer** - Perfect rule compliance and basic strategic logic -- **Strategy Layer** - Point optimization, team coordination, position-based play -- **Memory Layer** - Card tracking, probability analysis, guaranteed winner detection -- **Advanced Features** - Void exploitation, point timing optimization, trump conservation - -**Memory System Highlights:** - -- **Guaranteed Winner Detection**: Uses card tracking to identify cards certain to win -- **Smart Void Exploitation**: Differentiates teammate vs opponent void strategies for optimal point collection -- **Trump Exhaustion Tracking**: Dynamic trump conservation based on opponent depletion analysis -- **Context-Aware Memory**: Unified memory analysis integrated across all trick positions -- **Point Timing Optimization**: Memory-enhanced analysis for optimal point card collection timing +Beyond the always-on engine, an optional LLM layer (via OpenRouter, disabled by default) can make the trick-play decision at genuinely ambiguous lead/follow moments. It does not replace the engine: the rule-based AI still generates the candidate plays, scores, seat guidance, and win-security signals fed into the prompt, short-circuits forced or obvious plays before any API call, and serves as the fallback whenever the model's output is invalid or the API is unavailable. Configure it in-app with your own API key. *Complete AI documentation in **[AI System Guide](docs/AI_SYSTEM.md)*** ## Reporting & Analysis -**Unified BigQuery Analysis System** for comprehensive game data insights: +**BigQuery analysis pipeline** for game-log insights: -- **🌐 BigQuery Analysis**: Enterprise-scale processing for datasets from small tests to 50GB+ production data -- **📈 KPI Reports**: Comprehensive reports with AI decision analysis, performance metrics, and gameplay statistics +- **🌐 BigQuery Analysis**: Scales from small test runs to large simulation datasets +- **📈 KPI Reports**: AI decision analysis, performance metrics, and gameplay statistics - **🎨 Data Visualizations**: Rich charts and graphs showing AI behavior, game patterns, and strategic insights - **⚡ Automated Workflows**: Complete BigQuery pipeline with Data Transfer jobs and automated uploads @@ -118,20 +106,10 @@ A React Native implementation of the classic Chinese card game **Tractor** (also ## Built with AI -This project is developed with assistance from AI coding partners. - -- **Claude** ([claude.ai](https://claude.ai/code)) - Initial project scaffolding and early feature development. -- **Gemini** ([gemini.google.com](https://gemini.google.com)) - Primary development assistant for new features, refactoring, and test generation. +This project is developed with the assistance of various AI coding tools and models. Development guidelines are documented in [AGENTS.md](AGENTS.md). -## Future Enhancements - -- **Multi-game Learning** - Cross-game strategy improvement -- **Difficulty Scaling** - Adaptive challenge based on player skill -- **Advanced Psychology** - Deep behavioral modeling -- **iOS Testing** - Comprehensive iOS platform validation - ## More Screenshots
diff --git a/docs/AI_SYSTEM.md b/docs/AI_SYSTEM.md
index ddfa890..1d6d238 100644
--- a/docs/AI_SYSTEM.md
+++ b/docs/AI_SYSTEM.md
@@ -6,7 +6,7 @@
## Overview
-The Tractor AI system implements **sophisticated strategic decision-making** with memory-enhanced analysis, opponent modeling, and adaptive learning capabilities. The AI delivers challenging yet fair gameplay through intelligent card play, team coordination, and predictive strategy.
+The Tractor AI system implements **memory-enhanced strategic decision-making** with card tracking and opponent modelling. It delivers challenging yet fair gameplay through strong card play, team coordination, and predictive analysis.
**Core Intelligence Features:**
- **Memory-Enhanced Strategy** - Card tracking with guaranteed winner identification
@@ -15,6 +15,8 @@ The Tractor AI system implements **sophisticated strategic decision-making** wit
- **Strategic Team Coordination** - Optimal cooperation with human teammates
- **Advanced Trump Management** - Hierarchical conservation and strategic deployment
+> **Optional LLM layer:** beyond the always-on rule-based engine documented here, an optional language-model layer (`src/ai/llm/`, disabled by default) can make trick-play decisions at genuinely ambiguous moments — see **[LLM Trick-Play Layer](#llm-trick-play-layer-optional)**. The rule-based engine remains the foundation and the fallback.
+
---
## Modular AI Architecture
@@ -51,8 +53,19 @@ src/ai/
│ │ └── kittySwapStrategy.ts # Rule-based exclusion and suit elimination
│ └── trumpDeclaration/
│ └── trumpDeclarationStrategy.ts # Sophisticated declaration timing
+└── LLM Layer (6 modules, OPTIONAL — off by default)
+ └── llm/
+ ├── llmAIStrategy.ts # Decision orchestration: prompt → validate → retry → fallback
+ ├── llmGamePrompt.ts # Builds the dynamic user prompt from engine analysis
+ ├── llmPromptTemplates.ts # Static rules system prompt + user-prompt template
+ ├── llmAIClient.ts # OpenRouter HTTP transport (fetch / node-https)
+ ├── llmConfig.ts # Config load/persist + enable check
+ └── llmModels.ts # Available models + default
```
+The 20 rule-based modules are always active; the LLM layer is a separate, optional
+addition described in its own section below.
+
### **Modular Benefits**
**Functional Coherence:**
@@ -105,90 +118,99 @@ The V2 following system implements a **scenario-based approach** with strict gam
---
+## LLM Trick-Play Layer (Optional)
+
+Everything above describes the **rule-based engine**, which is always on. Layered on top
+is an **optional LLM layer** (`src/ai/llm/`) that, when enabled, makes the trick-play
+*judgement* at genuinely ambiguous lead/follow decisions for its **LLM players**. It is
+**disabled by default** and requires an OpenRouter API key; the rule-based engine remains
+the foundation and the fallback.
+
+### What the LLM decides — and what it doesn't
+
+- **In scope**: lead and follow choices during the **Playing** phase, for the configured
+ **LLM players** (default `bot1`, `bot2`, `bot3` — note `bot2` is your teammate).
+- **Out of scope**: kitty swap and trump declaration stay fully rule-based.
+- **Forced or obvious plays are short-circuited before any API call** (see Adaptive
+ Shortcuts below) — the LLM is consulted only where there is a real choice to make.
+
+### The engine feeds the prompt
+
+The LLM does not reason from raw state. The same rule-based modules that drive autonomous
+play compute the signals injected into the prompt:
+
+- **Leading** → candidate leads from `candidateLeadDetection.ts`, each carrying a **Rule
+ Score** from `leadingScoring.ts`.
+- **Following** → the suit-availability scenario (`valid_combos` / `enough_remaining` /
+ `insufficient` / `void`), a **Trick Win Security** verdict (SECURED / LIKELY /
+ UNCERTAIN), confirmed voids from the memory context, unseen off-suit points, and a
+ per-seat **GUIDANCE FOR THIS SEAT** bullet that applies the following rules to the exact
+ situation.
+
+The model's job is judgement among already-legal options, not legality or arithmetic.
+
+### Prompt structure
+
+- **System prompt** = `STATIC_LLM_GAME_RULES` (`llmPromptTemplates.ts`): the standing
+ trick-play strategy guide (card strength, combos, following/ruffing/leading order,
+ position cues, conservation).
+- **User prompt** = `buildUserPromptTemplate`, filled by `buildLLMUserPrompt` in
+ `llmGamePrompt.ts` with the per-decision state above.
+- **Card notation**: cards are named exactly as displayed — `10♦`, `K♥`, `A♠`, plus
+ `BJ` / `SJ` for the jokers; the hand groups duplicates with a `×N` count tag. The model
+ replies with JSON: `{"reasoning":"…","play":["…"]}`.
+
+### Validation, retry, and fallback
+
+`llmAIStrategy.ts` orchestrates each decision:
+
+1. Call the model (`llmAIClient.ts` → OpenRouter, `temperature: 0.1`, default 15s timeout).
+2. Parse JSON → map each notation to a held card → validate against the rules
+ (`getPlayValidationError`).
+3. On any failure, append a specific corrective hint and retry. **Max 2 attempts** (one
+ retry).
+4. If still invalid, or on API error/timeout, **fall back to the rule-based pick** for
+ that turn.
+
+### Adaptive shortcuts (skip the LLM)
+
+When the play is forced or strategically obvious the engine plays it directly and logs an
+`llm_adaptive_shortcut_*` event instead of calling the API:
+
+- **Leading**: round-start ace/king lead (`lead_ace`), unbeatable combo
+ (`lead_unbeatable`), only one legal candidate (`lead_single_candidate`).
+- **Following**: multi-combo follow (`follow_multi_combo`, delegated to the deterministic
+ algorithm), must play the whole hand (`follow_hand_size`), forced to play all remaining
+ cards of the led suit (`follow_forced_suit`), only one valid combo
+ (`follow_single_combo`).
+
+### Execution path & configuration
+
+- **Async path**: `getAIMoveWithErrorHandlingAsync` → `makeAIPlayAsync` →
+ `selectLeadingPlayAsync` / `selectFollowingPlayAsync`, awaited by the `useAITurns` hook.
+ The synchronous `getAIMove` stays rule-based (used by tests and simulation).
+- **Config** (`llmConfig.ts`): persisted in `localStorage` under `tractor_llm_config` (no
+ env vars). Fields: `enabled`, `apiKey`, `model`, `apiUrl`, `timeoutMs`,
+ `applyToPlayers`. `isLLMEnabled()` requires `enabled && apiKey`.
+- **Models** (`llmModels.ts`): default `google/gemini-2.5-flash-lite`; DeepSeek V3,
+ Qwen3 Next 80B, and Llama 3.3 70B also selectable.
+
+> Future work on enriching the signals fed to this layer is captured in
+> **[proposals/LLM_GAME_STATE_SIGNALS.md](proposals/LLM_GAME_STATE_SIGNALS.md)**.
+
+---
+
## Decision Framework
The AI follows a **modular decision framework** with specialized modules handling each strategic component. The decision process is split into two main pathways:
### **Leading Strategy Framework**
-When the AI must lead a trick, it follows this strategic decision flow:
-
-```mermaid
-flowchart TD
- StartLead([🎯 AI Leading Turn]) --> LogicLead[🎮 aiLogic.ts
Public API & Rule Compliance]
- LogicLead --> StrategyLead[🧠 aiStrategy.ts
Core Decision Coordination]
- StrategyLead --> ContextLead[📊 aiGameContext.ts
Game State Analysis]
- ContextLead --> MemoryLead[💾 aiCardMemory.ts
Memory System]
-
- MemoryLead --> LeadingMods[🎯 Scoring-Based Leading Strategy]
-
- LeadingMods --> CandidateDetection[🔍 STEP 1: Candidate Detection
candidateLeadDetection.ts
Find ALL possible leads]
- LeadingMods --> ContextCollection[📊 STEP 2: Context Collection
leadingContext.ts
Team & void analysis]
- LeadingMods --> ScoringEvaluation[🎯 STEP 3: Scoring Evaluation
leadingScoring.ts
Comprehensive scoring]
- LeadingMods --> LeadStrategy[✅ STEP 4: Selection & Execution
leadingStrategy.ts
Best score selection]
-
- CandidateDetection --> ContextCollection
- ContextCollection --> ScoringEvaluation
- ScoringEvaluation --> LeadStrategy
- LeadStrategy --> ExecuteLead[✅ Execute Leading Move]
-```
+Leading flows through the core pipeline (`aiLogic` → `aiStrategy` → `aiGameContext` → `aiCardMemory`) into the scoring-based leading modules: candidate detection (`candidateLeadDetection.ts`) → context collection (`leadingContext.ts`) → scoring (`leadingScoring.ts`) → selection (`leadingStrategy.ts`).
### **Following Strategy Framework**
-When the AI must follow a trick, it uses the enhanced V2 following system with scenario-based routing:
-
-```mermaid
-flowchart TD
- StartFollow([🎯 AI Following Turn]) --> LogicFollow[🎮 aiLogic.ts
Public API & Rule Compliance]
- LogicFollow --> StrategyFollow[🧠 aiStrategy.ts
Core Decision Coordination]
- StrategyFollow --> ContextFollow[📊 aiGameContext.ts
Game State Analysis]
- ContextFollow --> MemoryFollow[💾 aiCardMemory.ts
Memory System]
-
- MemoryFollow --> MultiCheck{🔍 Multi-Combo Lead?}
- MultiCheck -->|Yes| MultiComboFollow[multiComboFollowingStrategy.ts
Multi-Combo Algorithm]
- MultiCheck -->|No| SuitAnalysis[suitAvailabilityAnalysis.ts
Scenario Classification]
-
- SuitAnalysis --> RoutingLogic[routingLogic.ts
Decision Routing]
-
- RoutingLogic --> ValidCombos{✅ valid_combos?}
- RoutingLogic --> EnoughRemaining{📏 enough_remaining?}
- RoutingLogic --> Insufficient{⚠️ insufficient?}
- RoutingLogic --> VoidScenario{🚫 void?}
-
- ValidCombos --> ValidDecision[validCombosDecision.ts
Strategic Combo Selection]
- EnoughRemaining --> SameSuitDecision[sameSuitDecision.ts
Disposal/Contribution Logic]
- Insufficient --> CrossSuitDecision[crossSuitDecision.ts
Cross-Suit Fill]
- VoidScenario --> VoidDecision[voidDecision.ts
Trump/Cross-Suit Choice]
-
- ValidDecision --> PriorityChain[🎯 4-Priority Decision Chain]
- PriorityChain --> P1{🤝 PRIORITY 1
Teammate Winning?}
-
- P1 -->|Yes| TeamSupport[🎁 Team Coordination
Support Teammate]
- P1 -->|No| P2{⚔️ PRIORITY 2
Opponent Winning?}
- P2 -->|Yes| OpponentBlock[🛡️ Strategic Opposition
Block Opponent]
- P2 -->|No| P3{💰 PRIORITY 3
Can Win ≥5 Points?}
- P3 -->|Yes| TrickContest[⚡ Contest Trick
Point Collection]
- P3 -->|No| StrategicDisp[🗑️ Hierarchical Disposal
Conservation Values]
-
- ValidDecision --> PositionSpecific[🎯 Position-Specific Logic]
- PositionSpecific --> SecondPlayer[2nd Player: Early Influence
Strategic Setup]
- PositionSpecific --> ThirdPlayer[3rd Player: Takeover Analysis
Risk Assessment]
- PositionSpecific --> FourthPlayer[4th Player: Perfect Information
Optimal Decisions]
-
- TeamSupport --> ExecuteFollow[✅ Execute Following Move]
- OpponentBlock --> ExecuteFollow
- TrickContest --> ExecuteFollow
- StrategicDisp --> ExecuteFollow
- ValidDecision --> ExecuteFollow
- SameSuitDecision --> ExecuteFollow
- CrossSuitDecision --> ExecuteFollow
- VoidDecision --> ExecuteFollow
- MultiComboFollow --> ExecuteFollow
-
- MemoryFollow --> AnalysisFollow[🔍 Analysis Support]
- AnalysisFollow --> TeammateAnalysis[teammateAnalysis.ts
Teammate Evaluation]
- AnalysisFollow --> StrategicSelection[strategicSelection.ts
Pair-Preserving Selection]
-```
+Following runs the same core pipeline, then checks for a multi-combo lead (handled by `multiComboFollowingStrategy.ts`). Otherwise `suitAvailabilityAnalysis.ts` classifies the scenario (`valid_combos` / `enough_remaining` / `insufficient` / `void`) and `routingLogic.ts` routes to the matching decision module (`validCombosDecision` / `sameSuitDecision` / `crossSuitDecision` / `voidDecision`); the priority chain (teammate support → opponent blocking → trick contention → disposal) and position-specific logic then choose the card.
### **Priority Levels**
@@ -204,7 +226,7 @@ flowchart TD
## Scoring-Based Leading Strategy
-The AI implements a **unified scoring-based leading strategy** that evaluates all possible candidate leads and selects the highest scoring option. This replaces the previous complex priority chain system with a transparent, maintainable scoring approach.
+The AI implements a **unified scoring-based leading strategy**: it evaluates every candidate lead with a single scoring function and plays the highest-scoring option, keeping leading decisions transparent and debuggable.
### **Core Architecture**
@@ -222,12 +244,13 @@ The AI implements a **unified scoring-based leading strategy** that evaluates al
- **Point pressure**: Calculated based on current team progress
- **Game phase**: Early, mid, or endgame strategic context
-3. **Scoring Evaluation (`leadingScoring.ts`)**: Comprehensive candidate scoring
- - **Base card values**: Uses actual card rank values (Ace=14, King=13, etc.)
- - **Pair bonuses**: +20 for non-trump pairs, +30 for trump pairs
- - **Unbeatable bonus**: +50 for guaranteed winners
- - **Trump penalties**: -rankValue per trump card to encourage conservation
- - **Void suit bonuses**: +10 for weak combos in void suits (strategic opportunities)
+3. **Scoring Evaluation (`leadingScoring.ts`)**: scores each candidate by combining
+ - **Base card values** — higher ranks score higher
+ - **Pair bonuses** — pairs beat singles; trump pairs weighted above non-trump
+ - **Unbeatable bonus** — guaranteed winners score highest
+ - **Trump penalty** — trump cards are penalised to encourage conservation
+ - **Void-suit adjustment** — bonus for leading a teammate-void suit, penalty for an opponent-void suit
+ - *(exact weights live in `leadingScoring.ts`)*
4. **Selection & Execution (`leadingStrategy.ts`)**: Chooses best option
- **Score ranking**: Sorts all candidates by total score
@@ -235,37 +258,9 @@ The AI implements a **unified scoring-based leading strategy** that evaluates al
- **Fallback handling**: Graceful degradation when no candidates found
- **Comprehensive logging**: Detailed reasoning for analysis
-### **Scoring Algorithm**
-
-#### **Base Scoring Components**
-
-```typescript
-// 1. Card rank values (base score)
-const baseScore = candidate.cards.reduce((sum, card) => sum + getCardRankValue(card), 0);
+### **Strategic Priorities**
-// 2. Pair bonuses
-const pairBonus = candidate.metadata.totalPairs * (candidate.metadata.isTrump ? 30 : 20);
-
-// 3. Unbeatable bonus
-const unbeatableBonus = candidate.metadata.isUnbeatable ? 50 : 0;
-
-// 4. Trump penalties (conservation)
-const trumpPenalty = candidate.metadata.isTrump ?
- -candidate.cards.reduce((sum, card) => sum + getTrumpPenalty(card), 0) : 0;
-
-// 5. Void suit bonus (strategic opportunities)
-const voidBonus = (isVoidSuit && isWeakCombo) ? 10 : 0;
-
-const totalScore = baseScore + pairBonus + unbeatableBonus + trumpPenalty + voidBonus;
-```
-
-#### **Strategic Priorities**
-
-1. **Unbeatable leads** (score +50): Guaranteed winners receive highest priority
-2. **High-value pairs** (score +20/+30): Pairs get significant bonuses over singles
-3. **Card strength** (score +2 to +14): Higher ranked cards preferred
-4. **Trump conservation** (negative scores): Trump cards penalized to encourage saving
-5. **Void exploitation** (score +10): Weak combos in void suits get bonus
+In effect the scoring ranks leads roughly as: guaranteed-unbeatable leads first, then high-value pairs over singles, then stronger cards over weaker — while penalising trump (to conserve it) and rewarding a lead into a teammate's void. See `leadingScoring.ts` for the exact formula and weights.
### **Decision Process**
@@ -331,14 +326,7 @@ The AI identifies cards that are certain to win based on comprehensive memory an
#### Teammate Void Strategy (Smart Point Collection)
-```typescript
-// NEW: Smart teammate void analysis
-if (opponentPoints >= 15 && teammateCanTrump) {
- strategy = "lead_for_points" // 🎯 Lead to collect points for team
-} else {
- strategy = "avoid_leading" // 🛡️ Protect teammate from forced trump
-}
-```
+When a teammate is void in a suit, lead it **for points** if the teammate can ruff and there are meaningful points to win; otherwise **avoid leading it** so the teammate isn't forced to spend trump on a low-value trick.
#### Opponent Void Strategy (Aggressive Exploitation)
@@ -356,10 +344,7 @@ if (opponentPoints >= 15 && teammateCanTrump) {
#### Conservation Hierarchy
-```text
-Big Joker (100) > Small Joker (90) > Trump Rank in Trump Suit (80) >
-Trump Rank in Off-Suits (70) > Trump Suit Cards (A♠:60 → 3♠:5)
-```
+Trump is conserved by a fixed value ordering (jokers > trump-rank cards > trump-suit regulars) — see [Trump Management](#trump-management) for the full hierarchy.
### **Unified Memory Integration**
@@ -381,7 +366,7 @@ The memory system is seamlessly integrated across all AI decision modules:
#### Intelligence Enhancement
-- **15-25% Decision Quality Improvement** through comprehensive card tracking
+- **Improved decision quality** through comprehensive card tracking
- **Strategic Depth**: Complex void exploitation and point timing optimization
- **Predictive Analysis**: Anticipate opponent capabilities based on memory
- **Team Coordination**: Smart teammate strategies that maximize point collection
@@ -414,13 +399,8 @@ export function createMemoryContext(gameState: GameState): MemoryContext
- **Trump Management** - Conservation decisions based on trump depletion
**Performance Characteristics:**
-- **Efficient Processing** - Optimized for real-time decision making
-- **Accurate Analysis** - 100% consistency with game state
-- **Memory Efficiency** - Clean data structures without redundancy
-
-## Advanced Strategic Features
-
-The AI implements sophisticated game strategies through its modular architecture:
+- **Efficient Processing** - designed for real-time decision making
+- **Memory Efficiency** - clean data structures without redundancy
---
@@ -463,7 +443,7 @@ All following positions use the same priority framework but with position-specif
#### **Enhanced 3rd Player Takeover System**
-The AI now uses sophisticated strategic value analysis to determine when to take over from a teammate:
+The AI uses strategic value analysis to determine when to take over from a teammate:
**Strategic Value Thresholds:**
- **Strong** (≥170): Jokers, trump rank cards → Support teammate
@@ -605,19 +585,12 @@ When the AI cannot win a trick, it follows a sophisticated disposal system:
## Performance & User Experience
-### **Intelligence Benchmarks**
-
-**Decision Quality:**
-- **Rule Compliance** - Perfect adherence to complex Tractor/Shengji rules
-- **Strategic Optimization** - 20-30% improvement over basic AI play
-- **Memory Enhancement** - 15-25% improvement through card tracking
-- **Position-Based Logic** - Specialized decisions for all 4 trick positions
+### **Decision Quality**
-**Response Times:**
-- **Standard Decisions** - ~300ms for most scenarios
-- **Full Analysis** - <400ms with complete intelligence active
-- **Minimal Overhead** - Memory analysis adds only ~30ms when utilized
-- **Modular Efficiency** - Focused modules eliminate redundant calculations and improve decision speed
+- **Rule Compliance** - complete adherence to complex Tractor/Shengji rules
+- **Strategic Optimisation** - card-tracking and memory inform stronger play than naive heuristics
+- **Position-Based Logic** - specialised decisions for all 4 trick positions
+- **Modular Efficiency** - focused modules avoid redundant calculation
### **Strategic Effectiveness**
@@ -636,42 +609,9 @@ When the AI cannot win a trick, it follows a sophisticated disposal system:
---
-## Future Enhancement Roadmap
-
-### **Potential Enhancements**
-
-**Future Capabilities:**
-- **Advanced Pattern Recognition** - Enhanced memory analysis across multiple games
-- **Dynamic Difficulty Scaling** - AI intelligence adapts to player skill progression
-- **Enhanced Team Coordination** - Improved teammate support strategies
-- **Strategic Learning** - Continuous improvement through gameplay analysis
-
-**Foundation Ready:**
-- **Existing Infrastructure** - Current memory system provides solid foundation
-- **Clean Integration** - Modular architecture ready for extensions
-- **Natural Evolution** - Minimal architectural changes needed
-
-### **Recent Architecture Improvements (2025)**
-
-**Code Modernization and Simplification:**
-- **Type System Consolidation** - Merged `combinations.ts` into `card.ts` for better organization
-- **Function Naming Clarity** - Renamed `createCardMemory` to `createMemoryContext` for accuracy
-- **Documentation Unification** - Merged multi-combo documentation into single comprehensive guide
-- **Analysis System Migration** - Transitioned from BigQuery to local analysis using DuckDB
-- **Module Structure Cleanup** - Focused AI modules with clear separation of concerns
-
-**Technical Benefits:**
-- **Reduced Complexity** - Simplified type structure and cleaner imports
-- **Improved Maintainability** - Unified architecture with clearer responsibilities
-- **Enhanced Documentation** - Consolidated guides matching current implementation
-- **Local Analysis** - Streamlined reporting without external dependencies
-- **Better Testing** - Focused on core strategic decisions with comprehensive coverage
-
----
-
## Summary
-The Tractor AI system delivers **sophisticated strategic gameplay** through comprehensive intelligence, modular architecture, and adaptive learning:
+The Tractor AI system delivers strong strategic gameplay through memory-enhanced intelligence and a modular architecture:
### **Core Capabilities**
@@ -683,7 +623,7 @@ The Tractor AI system delivers **sophisticated strategic gameplay** through comp
- **Multi-Combo Integration** - Complete support for complex multi-combo scenarios
**Modular Architecture:**
-- **20 Focused Modules** - Streamlined architecture with clear separation of concerns (4 core + 10 following + 4 leading + 2 specialized)
+- **Modular Architecture** - 20 rule-based modules (4 core + 10 following + 4 leading + 2 specialised) plus an optional 6-module LLM layer
- **Unified Leading Strategy** - Scoring-based system with transparent decision making
- **4-Priority Following Chain** - Conflict-free strategic decision making for following scenarios
- **Domain Separation** - Clean separation between following, leading, and specialized systems
@@ -697,7 +637,7 @@ The Tractor AI system delivers **sophisticated strategic gameplay** through comp
- **Strategic Disposal** - Multi-level card safety prioritization with conservation values
**Performance & Maintainability:**
-- **Fast Response Times** - <400ms decision time with full analysis
+- **Responsive** - synchronous rule-based decisions suitable for real-time play
- **Modular Efficiency** - Specialized modules eliminate redundant calculations
- **Easy Testing** - Modular structure enables comprehensive unit testing
- **Rapid Development** - Clean architecture supports quick feature additions and bug fixes
diff --git a/docs/GAME_RULES.md b/docs/GAME_RULES.md
index e47ced1..858d647 100644
--- a/docs/GAME_RULES.md
+++ b/docs/GAME_RULES.md
@@ -171,15 +171,17 @@ Work with your AI teammate to collect 80+ points per round and advance through c
#### **3. Scoring Rules**
- **Hidden during play**: Kitty cards not revealed
- **Critical endgame rule**: Only scored if attacking team wins FINAL trick
-- **Multiplier system**:
- - **Singles final trick**: Kitty points × 2
- - **Pairs/Tractors final trick**: Kitty points × 4
+- **Multiplier system**: the final trick multiplies the kitty points by **2^(pairs + 1)**, where *pairs* is the number of pairs in that trick's leading combo:
+ - **Singles (0 pairs)**: × 2
+ - **One pair**: × 4
+ - **Two pairs**: × 8
+ - **Tractor of N pairs**: × 2^(N + 1)
#### **Kitty Bonus Examples**
- **Scenario**: Kitty contains 2 Kings + 1 Five = 25 points
- **Singles final win**: 25 × 2 = **+50 bonus points**
-- **Pair final win**: 25 × 4 = **+100 bonus points**
-- **Can change 80 points → 180 points** (massive swing!)
+- **One-pair final win**: 25 × 4 = **+100 bonus points**
+- **Two-pair final win**: 25 × 8 = **+200 bonus points** — a large swing
---
@@ -493,13 +495,13 @@ Teams collect points from tricks they win:
### **Round Advancement**
Based on attacking team's total points:
-| **Points Collected** | **Result** |
+| **Points Collected (attacking team)** | **Result** |
|---------------------|------------|
-| **160+ points** | Attacking team advances 2 ranks |
-| **120-159 points** | Attacking team advances 1 rank |
-| **80-119 points** | No advancement, teams switch roles |
+| **120+ points** | Attacking team advances `floor((points − 80) / 40)` ranks and becomes defending (120–159 → +1, 160–199 → +2, 200 → +3) |
+| **80-119 points** | No rank change; attacking team becomes defending |
| **40-79 points** | Defending team advances 1 rank |
-| **0-39 points** | Defending team advances 2 ranks |
+| **1-39 points** | Defending team advances 2 ranks |
+| **0 points (shutout)** | Defending team advances 3 ranks |
### **Team Role Changes**
- **80+ points**: Attacking team becomes defending team next round
diff --git a/docs/LOG_EVENT_SCHEMA.md b/docs/LOG_EVENT_SCHEMA.md
deleted file mode 100644
index 29a8376..0000000
--- a/docs/LOG_EVENT_SCHEMA.md
+++ /dev/null
@@ -1,141 +0,0 @@
-# Log Event Schema Documentation
-
-This document outlines the structure of various event types logged in the AI simulation, detailing the JSON paths and expected data types for key fields within the `data` payload of each event. This schema serves as a reference for accurate data parsing and analysis in the local analysis pipeline.
-
-## Common Log Entry Fields
-
-All log entries share these top-level fields:
-
-- `timestamp` (string): ISO 8601 formatted timestamp of the log entry.
-- `level` (string): The log level (e.g., "INFO", "DEBUG", "WARN", "ERROR").
-- `event` (string): A descriptive name for the event being logged.
-- `sequenceNumber` (integer): The sequential number of the log entry within its file.
-- `appVersion` (string): The version of the application that generated the log.
-- `gameId` (string): A unique identifier for the game session.
-- `message` (string): A human-readable summary of the event.
-- `data` (JSON object): The event-specific payload, whose structure varies by `event` type.
-
----
-
-## Event-Specific Data Payloads
-
-### 1. `game_initialized`
-
-**Purpose**: Marks the start of a new game round.
-
-**`data` fields**:
-- `round_number` (integer): `$.roundNumber`
-- `defending_team` (string): `$.defendingTeam`
-- `attacking_team` (string): `$.attackingTeam`
-- `round_starting_player` (string): `$.roundStartingPlayer`
-- `trump_rank` (string): `$.trumpRank`
-- `team_ranks` (JSON array): `$.teamRanks`
-- `deck_size` (integer): `$.deckSize`
-
-### 2. `trump_finalized`
-
-**Purpose**: Records the final trump suit and rank for a round.
-
-**`data` fields**:
-- `trump_suit` (string): `$.trumpInfo.trumpSuit`
-
-### 3. `game_over`
-
-**Purpose**: Indicates the end of a game.
-
-**`data` fields**:
-- `winner` (string): `$.winner` (Team ID)
-
-### 4. `trick_completed`
-
-**Purpose**: Records the outcome of a completed trick.
-
-**`data` fields**:
-- `winning_player` (string): `$.winningPlayer` (Player ID)
-- `trick_points` (integer): `$.trickPoints`
-- `all_plays` (JSON array): `$.allPlays`
- - Each element in `all_plays` is a JSON object representing a player's play, containing:
- - `player_id` (string): `$.playerId`
- - `cards` (JSON array): `$.cards`
- - Each element in `cards` is a JSON string representing a card (e.g., `"S-A"` for Ace of Spades).
-- `trick_type` (string): `$.trickType` (e.g., "single", "pair", "tractor", "multi-combo")
-
-### 5. `trump_declared`
-
-**Purpose**: Captures details when a player declares trump during dealing.
-
-**`data` fields**:
-- `player_id` (string): `$.playerId` (Player ID of the declarer)
-
-### 6. `kitty_pickup`
-
-**Purpose**: Records the points in the kitty when it's picked up.
-
-**`data` fields**:
-- `kitty_points` (integer): `$.kittyPoints`
-
-### 7. `kitty_swap_completed`
-
-**Purpose**: Records the points of cards put back into the kitty after a swap.
-
-**`data` fields**:
-- `selected_card_points` (integer): `$.selectedCardPoints`
-
-### 8. `round_preparation_start`
-
-**Purpose**: Marks the beginning of round preparation.
-
-**`data` fields**:
-- `current_team_ranks` (JSON array): `$.currentTeamRanks`
- - Each element in `current_team_ranks` is a JSON object representing a team's rank, containing:
- - `is_defending` (boolean): `$.isDefending`
- - `team_id` (string): `$.teamId`
-
-### 9. `ai_leading_decision`
-
-**Purpose**: Captures the AI's strategic decision when leading a trick.
-
-**`data` fields**:
-- `decision_point` (string): `$.decisionPoint` (specific AI strategy, e.g., "lead_multi_combo", "lead_early_game_ace", "lead_void_exploitation", "lead_point_timing", "lead_guaranteed_winner", "lead_historical_insights")
-- `player` (string): `$.player` (AI Player ID making the decision)
-- `decision` (JSON array): `$.decision` (cards selected for the leading play)
-- `context` (JSON object): `$.context` (strategic context)
- - `trick_position` (string): `$.trickPosition`
- - `point_pressure` (string): `$.pointPressure`
- - `play_style` (string): `$.playStyle`
- - `trump_exhaustion` (float): `$.memoryContext.trumpExhaustion` (when available)
- - `uncertainty_level` (float): `$.memoryContext.uncertaintyLevel` (when available)
-
-### 10. `ai_following_decision`
-
-**Purpose**: Captures the AI's strategic decision when following in a trick.
-
-**`data` fields**:
-- `decision_point` (string): `$.decisionPoint` (specific AI strategy, e.g., "analysis_start", "follow_multi_combo", "follow_teammate_winning", "follow_suit_establishment", "follow_opponent_blocking", "follow_trick_contention", "follow_strategic_disposal", "teammate_winning_second_player", "teammate_winning_third_player", "second_player_same_suit", "second_player_trump", "strategic_disposal_start")
-- `player` (string): `$.player` (AI Player ID making the decision)
-- `decision` (JSON array): `$.decision` (cards selected for the following play)
-- `context` (JSON object): `$.context` (strategic context)
- - `trick_position` (string): `$.trickPosition`
- - `is_teammate_winning` (boolean): `$.isTeammateWinning`
- - `is_opponent_winning` (boolean): `$.isOpponentWinning`
- - `can_beat_current_winner` (boolean): `$.canBeatCurrentWinner`
- - `trick_points` (integer): `$.trickPoints`
-- Additional context-specific fields based on `decision_point`:
- - For "second_player_trump": `strategy` (string), `point_potential` (integer)
- - For "teammate_winning_third_player": `should_contribute` (boolean)
- - For "strategic_disposal_start": `combo_count` (integer), `cards_remaining` (integer)
-
-### 11. `round_complete`
-
-**Purpose**: Indicates the completion of a game round.
-
-**`data` fields**:
-- `winning_team_id` (string): `$.winningTeamId`
-- `attacking_team_points` (integer): `$.attackingTeamPoints`
-- `defending_team_points` (integer): `$.defendingTeamPoints`
-
----
-
-## Future Updates
-
-This schema will be updated as new event types are introduced or existing event structures evolve.
\ No newline at end of file
diff --git a/docs/MULTI_COMBO.md b/docs/MULTI_COMBO.md
index 0bf3a57..0e9fb0c 100644
--- a/docs/MULTI_COMBO.md
+++ b/docs/MULTI_COMBO.md
@@ -21,164 +21,31 @@ In Tractor/Shengji, a **multi-combo** is multiple combos from the same suit play
---
-## Core Implementation
+## Implementation
-### Leading Multi-Combos
+Multi-combo handling spans a few modules. This is the conceptual map — see the named source files for the exact signatures and logic.
-**Human Validation** (`playValidation.ts`):
-```typescript
-// Simple validation: same suit + unbeatable check
-if (isMultiCombo(cards)) {
- return validateMultiComboLead(cards, gameState, playerId);
-}
-```
+**Detection & structure** (`multiComboAnalysis.ts`): a play is a multi-combo when it contains more than one combo from a single suit (it has no single overall combo type). Detection summarises the play's *structure* — total length, number of pairs, and the tractors and their sizes — which is what a following play must match.
-**AI Selection** (`candidateLeadDetection.ts`):
-```typescript
-// AI finds unbeatable combos and bundles them
-const unbeatableCombos = findUnbeatableCombos(suit, gameState);
-if (unbeatableCombos.length >= 2) {
- return createMultiComboLead(unbeatableCombos);
-}
-```
+**Leading** — validity is checked in `playValidation.ts` (human plays) and proposed by `candidateLeadDetection.ts` (AI). A multi-combo lead is legal only when **every component is unbeatable** by the three other players' unseen cards, **or** all three other players are **void** in the suit. The AI proposes such a lead only when it can find two or more unbeatable components in one suit.
-### Following Multi-Combos
+**Unbeatable check** (`isComboUnbeatable` in `multiComboValidation.ts`): a component is unbeatable when no stronger same-suit combo can still exist in the unseen cards — derived from the memory of played cards, the player's own hand, and (for the round starter) the visible kitty.
-**Current AI Algorithm** (`multiComboFollowingStrategy.ts`):
-```typescript
-// Simplified 3-step algorithm
-1. Same-suit following: Match structure if possible
-2. Trump following: Use trump with matching structure
-3. Cross-suit disposal: Play any cards when exhausted
-```
+**Following** (`multiComboFollowingStrategy.ts`): match the led structure in the same suit if possible; otherwise beat it with a matching trump structure; otherwise dispose. If the player runs out of the led suit mid-play, the **exhaustion rule** makes any remaining cards legal.
-**Validation** (`playValidation.ts`):
-```typescript
-// Exhaustion rule: If void after play → always valid
-if (isVoidAfterPlay(cards, hand, leadingSuit)) {
- return true;
-}
-// Otherwise: structure matching validation
-```
-
----
-
-## Data Structures
-
-### Core Types
-
-```typescript
-// Multi-combo structure analysis
-interface MultiComboStructure {
- suit: Suit;
- components: MultiComboComponents;
- isLeading: boolean;
-}
-
-interface MultiComboComponents {
- totalLength: number; // Total cards
- totalPairs: number; // All pairs (including tractor pairs)
- tractors: number; // Number of tractors
- tractorSizes: number[]; // Length of each tractor
-}
-```
-
-### Detection Results
-
-```typescript
-interface MultiComboDetection {
- isMultiCombo: boolean;
- structure?: MultiComboStructure;
- components?: Combo[];
- validation?: MultiComboValidation;
-}
-```
-
----
-
-## Key Algorithms
-
-### 1. Unbeatable Detection
-
-**Core Logic** (`multiComboValidation.ts`):
-```typescript
-function isComboUnbeatable(combo: Combo, suit: Suit, gameState: GameState): boolean {
- const availableToOthers = getTotalCards(108)
- - getPlayedCards(gameState)
- - getCurrentPlayerHand(gameState);
-
- // Check if any stronger combo exists in availableToOthers
- return !canBeBeaten(combo, availableToOthers, suit);
-}
-```
-
-### 2. Structure Matching
-
-**Following Validation** (`multiComboAnalysis.ts`):
-```typescript
-function matchesRequiredStructure(
- playedCards: Card[],
- requiredStructure: MultiComboComponents
-): boolean {
- const playedStructure = analyzeComboStructure(playedCards);
-
- return playedStructure.totalLength === requiredStructure.totalLength &&
- playedStructure.totalPairs >= requiredStructure.totalPairs &&
- playedStructure.tractors >= requiredStructure.tractors;
-}
-```
-
-### 3. Trump Multi-Combo Comparison
-
-**Strength Evaluation** (`cardComparison.ts`):
-```typescript
-function compareTrumpMultiCombos(
- combo1: Card[],
- combo2: Card[],
- trumpInfo: TrumpInfo
-): number {
- // Compare by highest combo type: tractors > pairs > singles
- const highestType1 = getHighestComboType(combo1);
- const highestType2 = getHighestComboType(combo2);
-
- if (highestType1 !== highestType2) {
- return compareComboTypes(highestType1, highestType2);
- }
-
- // Same type: compare strength within that type
- return compareCardStrength(
- getHighestCardOfType(combo1, highestType1),
- getHighestCardOfType(combo2, highestType2),
- trumpInfo
- );
-}
-```
+**Trump-vs-trump** (`cardComparison.ts`): when two trump responses compete, the higher *highest-component type* wins (tractor > pair > single), with ties broken on card strength.
---
## AI Integration
-### Leading Strategy
-
-**Priority 1**: Multi-combo detection in `leadingStrategy.ts`
-```typescript
-// Find unbeatable multi-combos as highest priority
-const multiComboLead = detectUnbeatableMultiCombos(hand, gameState);
-if (multiComboLead) {
- return multiComboLead;
-}
-```
+**Leading**: the leading strategy treats an unbeatable multi-combo as a top-priority lead (`leadingStrategy.ts` / `candidateLeadDetection.ts`).
-### Following Strategy
+**Following**: when the led play is a multi-combo, `followingStrategy.ts` routes to `executeMultiComboFollowingAlgorithm` instead of the regular following logic.
-**Routing Logic** in `followingStrategy.ts`:
-```typescript
-// Check if leading is multi-combo
-if (getComboType(leadingCards) === ComboType.Invalid) {
- return executeMultiComboFollowingAlgorithm(/* params */);
-}
-// Otherwise use regular following logic
-```
+> On the optional LLM path, multi-combo following is short-circuited to this same
+> `executeMultiComboFollowingAlgorithm` (logged as `llm_adaptive_shortcut_follow_multi_combo`)
+> — the model is not consulted, since the response is deterministic.
---
@@ -241,16 +108,9 @@ Result: ✅ Valid by exhaustion rule
---
-## Performance Notes
-
-The multi-combo system has been **optimized for simplicity**:
-
-✅ **Unified detection**: Single `analyzeComboStructure()` function
-✅ **Simplified AI**: Clear priority-based routing
-✅ **Efficient validation**: Early exhaustion rule check
-✅ **Memory integration**: Uses existing card tracking
+## Design Notes
-The implementation focuses on **correctness and maintainability** over complex optimizations, making it easier to debug and extend.
+The implementation favours correctness and maintainability: one structure analysis drives detection, following is priority-based routing, and the exhaustion rule is checked early.
---
diff --git a/docs/VERSIONING_STRATEGY.md b/docs/VERSIONING_STRATEGY.md
index 18ea342..60f257a 100644
--- a/docs/VERSIONING_STRATEGY.md
+++ b/docs/VERSIONING_STRATEGY.md
@@ -21,7 +21,7 @@ Tractor uses a triple-version system with explicit runtime version control for p
- **Alpha**: `v{major}.{next-minor}.0-alpha.{count}+{git-hash}` (isolated per build to guarantee feature branch safety)
### Full Version (`expo.extra.version`)
-**Purpose**: Complete tracking with git hash for debugging
+**Purpose**: Complete tracking with git hash for debugging (injected into the build on CI; not stored in `app.json`)
- **Production**: `v{major}.{minor}.{patch}+{git-hash}` (actual tag + hash)
- **Beta**: `v{major}.{next-minor}.0-beta.{count}+{git-hash}` (complete tracking)
- **Alpha**: `v{major}.{next-minor}.0-alpha.{count}+{git-hash}` (complete tracking)
@@ -59,7 +59,7 @@ Tractor uses a triple-version system with explicit runtime version control for p
To prevent development builds from overwriting your stable production build on your mobile device, Tractor dynamically overrides the package configuration on the GitHub Actions runner before compiling the Development Client APK:
* **Standard / Production builds**: Compiles with the static production details (`name: "Tractor"`, `package: "com.cardgame.tractor"`).
-* **Development Client builds**: The [**`eas-build-dev.yml`**](file:///home/eric/repos/Tractor/.github/workflows/eas-build-dev.yml) workflow uses `jq` to statically mutate `app.json` on the runner before uploading (`name: "Tractor (Dev)"`, `package: "com.cardgame.tractor.dev"`, `updates.requestHeaders["expo-channel-name"] = "preview"`).
+* **Development Client builds**: The [**`eas-build-dev.yml`**](../.github/workflows/eas-build-dev.yml) workflow rewrites `app.json` on the runner before building — distinct app name, a `.dev` Android package / iOS bundle id, and the build's EAS update channel — so the Dev Client installs alongside production. (See the workflow for the exact `jq` mutations.)
This allows developers to keep the official production app installed while concurrently running and testing with the custom Development Client shell.
diff --git a/docs/proposals/LLM_AI_INTEGRATION.md b/docs/proposals/LLM_AI_INTEGRATION.md
deleted file mode 100644
index 3ffafc8..0000000
--- a/docs/proposals/LLM_AI_INTEGRATION.md
+++ /dev/null
@@ -1,141 +0,0 @@
-# LLM-Driven AI Decisions via OpenRouter
-
-Integrate OpenRouter LLM API calls to make card play decisions for AI players during the `Playing` phase, while retaining rule-based fallback mechanisms and preserving the existing synchronous game engine and test suites.
-
-This plan includes a beautiful, premium configuration UI modal accessible via a hidden tap gesture, keeping the interface completely pristine.
-
----
-
-## Architecture & Design Decisions
-
-We have aligned on the following key architectural and user-experience branches:
-
-1. **Test Suite Compatibility**: We will preserve `getAIMoveWithErrorHandling` as a synchronous function to keep all 12+ existing test suites and simulation scripts fully functional and fast. We will introduce a new asynchronous counterpart, `getAIMoveWithErrorHandlingAsync`, for use by the `useAITurns` React hook.
-2. **Card Selection Representation**: The LLM will see its sorted hand with unique IDs (e.g. `c1: A♠`, `c2: 8♠`) and select cards by returning a JSON array of card IDs (e.g. `["c1", "c2"]`). Real-time validation is performed on this selection using the rule engine.
-3. **Reasoning & Output Format**: The LLM will output a JSON object containing both its strategic `"reasoning"` in English and the selected `"play"` card IDs array. The reasoning will be saved in our developer logs and BQ analytics.
-4. **Self-Correction & Feedback Loop**: We will create a detailed `getPlayValidationError` helper in `src/game/playValidation.ts` that identifies the exact Shengji rule violated and returns a highly specific error message. The LLM gets up to 2 retries to correct its selection using this feedback.
-5. **State Recovery & Session History**: Chat sessions will be reconstructed dynamically from the `gameState.tricks` array of the current round. This is 100% self-healing, stateless, and fully compatible with game save/load and app restarts.
-6. **API Credentials & Security**: API credentials will be supplied via standard Expo `EXPO_PUBLIC_` environment variables (`EXPO_PUBLIC_OPENROUTER_API_KEY`) as defaults, and can be overridden by users in the UI settings, stored securely in local SQLite storage via `localStorage`.
-
----
-
-## Configuration Settings UI Design
-
-We will build a high-fidelity config modal accessible only through a hidden developer/power-user trigger, keeping the main interface completely pristine and clean.
-
-### 1. Pristine UI Trigger Gesture (`src/components/GameStatus.tsx`)
-Instead of displaying a settings gear icon, we will implement a symmetrical hidden trigger:
-- **5 quick taps on the "Round X" text** in `GameStatus.tsx` will trigger a callback to open the LLM Configuration Modal.
-- This mirrors the existing 5 quick taps on the "Trump" display used to restart the game, creating a clean, logical, and fully hidden gesture pattern for developers/power users.
-
-### 2. Premium Settings Modal (`src/components/LLMConfigModal.tsx`)
-A new high-fidelity Modal overlay styled with clean container card layouts, modern typography, glassmorphism accents, and interactive visual feedback:
-- **Toggle switch**: Instantly enable or disable the LLM AI decision-making engine.
-- **TextInput (API Token)**: Secure text entry for the OpenRouter API Key. Includes a hide/reveal toggle (eye icon) and visual status border.
-- **Model selector (Radio list or dropdown)**: Clean, styled selectors showing the top supported models with brief taglines:
- - `deepseek/deepseek-chat` (DeepSeek-V3 - default, recommended for lowest latency & cost)
- - `google/gemini-2.5-flash` (Gemini 2.5 Flash - extremely fast)
- - `anthropic/claude-3.5-haiku` (Claude 3.5 Haiku - highly analytical)
- - `meta-llama/llama-3.3-70b-instruct` (Llama 3.3 70B - strong open-weights)
-- **Difficulty selector**: Choose between **Easy**, **Medium**, and **Hard** which dynamically maps to `reasoning_effort` ('low', 'medium', 'high') for supported reasoning-capable models.
-- **Interactive "Test Connection" Button**: Triggers a fast verification call to OpenRouter's API using a 4-second AbortController. Displays a loading indicator, followed by a green success toast or red error explanation.
-- **Save & Cancel Buttons**: Animates and saves configurations, updating active game controllers immediately.
-
-### 3. Local Storage Persistence (`src/ai/llmConfig.ts`)
-LLM configuration will be read from `localStorage` under the key `tractor_llm_config`. On startup:
-- First, check `localStorage` for user-saved configs.
-- Fall back to environment variables (`EXPO_PUBLIC_LLM_ENABLED`, `EXPO_PUBLIC_OPENROUTER_API_KEY`, etc.) if no local config exists.
-- Expose a `saveLLMConfig` utility that serializes the configuration back to `localStorage`.
-
----
-
-## Proposed Changes
-
-### 1. Persistent Settings (`src/ai/llmConfig.ts`) [NEW]
-
-Provide type definitions and persistence wrappers:
-
-```typescript
-export interface LLMConfig {
- enabled: boolean;
- apiKey: string;
- model: string;
- timeoutMs: number;
- difficulty: 'easy' | 'medium' | 'hard';
- applyToPlayers: string[]; // ['bot1', 'bot2', 'bot3']
-}
-
-export const DEFAULT_LLM_CONFIG: LLMConfig = {
- enabled: false,
- apiKey: '',
- model: 'deepseek/deepseek-chat',
- timeoutMs: 15000,
- difficulty: 'medium',
- applyToPlayers: ['bot1', 'bot2', 'bot3'],
-};
-
-export function getLLMConfig(): LLMConfig;
-export function saveLLMConfig(config: LLMConfig): void;
-```
-
-### 2. Connection Tester (`src/ai/llmAIClient.ts`) [NEW]
-
-Implement API calls and a connectivity testing utility:
-
-```typescript
-// Calls OpenRouter and returns assistant string response
-export async function callOpenRouter(...);
-
-// Fast check to see if OpenRouter key is active
-export async function testOpenRouterConnection(apiKey: string): Promise<{ success: boolean; message: string }>
-```
-
-### 3. Configuration UI Component (`src/components/LLMConfigModal.tsx`) [NEW]
-
-Create a beautiful component overlay containing all selectors, text inputs, error tooltips, hide/reveal keys, and save behaviors.
-
-### 4. Integration into Screen Controller (`src/screens/GameScreenController.tsx`) [MODIFY]
-
-Manage modal visibility state, load the LLM configuration on startup, pass handlers to trigger settings modals, and update hooks when settings are saved.
-
-### 5. Integration into Header View (`src/screens/GameScreenView.tsx` & `src/components/GameStatus.tsx`) [MODIFY]
-
-- Add an `onOpenSettings` prop to `GameStatus` and wrap the Round Text in a `TouchableWithoutFeedback` to detect the 5-tap gesture.
-- Pass this trigger up through `GameScreenView` to `GameScreenController`.
-- Render `