Commit Graph
408 Commits
Author SHA1 Message Date
Claude CodeandClaude Opus 4.6 a88b371889 feat: centralized combat damage routing with commander tracking (#86)
Wire all combat damage through a single _apply_damage() method that
handles commander damage tracking (MTG rule 903.10a) and elimination
checks. Each attacker's damage is individually attributed so commander
creatures correctly accumulate toward the 21-damage lethal threshold.

Changes:
- Add Player.commander_damage_received dict on the base model so the
  engine can track commander damage without needing CommanderGameState
- Add GameEngine._apply_damage() static method: reduces life, tracks
  commander damage when source is a commander, checks both life-lethal
  and commander-damage-lethal elimination
- Refactor _resolve_combat() to route per-attacker player damage through
  _apply_damage() instead of bulk life subtraction
- Change CommanderPlayer.commander_damage_received from own field to
  property delegating to base Player, keeping both layers in sync
- Add 24 targeted regression tests covering: _apply_damage unit tests,
  combat integration (unblocked/blocked/trample/double-strike/lifelink/
  mixed attackers/cumulative kills), and GameState compat

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-22 23:03:12 +00:00
cf4e2981e6 fix: resolve 78 test failures via shared stub cleanup and dependent fixes (#85) (#88)
Centralize and enhance test stubs to implement the full CommanderGameState
interface (battlefield(), living_players(), stack_is_empty(), sim_state,
active_player(), priority_player(), to_dict()). Fix all dependent test
failures across 12 files:

- conftest.py: Enhanced StubCard/StubPlayer/StubGameState with full API
  compatibility; added basic_creature fixture; fixed fresh_app singleton leak
- test_phase3.py: Added battlefield(), active_player(), prompt_builder compat
- test_integration.py: Fixed ThreatAssessor import (class→function), added
  threat_fn dict wrapper, sim_state delegate
- test_phase6.py/test_phase8.py: Replaced deprecated get_event_loop() with
  asyncio.run()
- test_benchmark.py: Same async fix + ThreatAssessor functional API
- sim/test_engine.py: Removed nonexistent seed= param, use run() method
- sim/test_models.py: basic_creature fixture now in conftest
- deck_builder/test_models.py: Updated ValidationError regex match
- state_logger.py: Added monotonic counter for unique snapshot filenames
- ai_opponent.py: Moved _CANNED dict to module-level constant (dataclass fix)
- web/app.py: Mount lobby router (fixes 6 LobbyAPI test failures)
- test_phase5.py: SessionStore + api._store reset fixture

Result: 76 failures + 2 errors → 0 failures, 0 errors (331 passed, 30 skipped)

Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-22 17:32:30 -04:00
d795e1a8fd feat: Phase 5 architecture follow-up — zone boundary, WAL store, Forge-only defaults (#87)
Implements the three approved Phase 5 architecture decisions:

1. Zone embedding boundary: add explicit documentation in StateDimensions
   and StateEncoder._encode_zones making the 6144-dim zero-padded zone
   contract visible to future maintainers.

2. SQLite WAL online learning store: new OnlineLearningStore class backed
   by WAL-mode SQLite for concurrent read/write on single-machine baseline.
   New /api/policy/decide endpoint collects decisions during inference.
   Stats endpoint at /api/policy/decide/stats.

3. Forge-only default weights: change ppo_weight default from 0.5 to 0.0
   across scope.py, DistillationConfig, DatasetConfig, and CLI defaults.
   Add MIXED_MODE_PRESETS dict with forge_only, forge_90_10, forge_80_20
   presets. Distill/start endpoint now accepts a "preset" parameter.

27 new tests covering all three decisions (all passing).

Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-22 14:41:33 -04:00
KoalaTrapLord 01d49e78a5 Fix coach cards-like: replace missing image_url column with scryfall_id URL builder 2026-03-22 13:06:36 -04:00
KoalaTrapLord c528fdb0b1 feat: add Scryfall card image hover preview on coach suggested cuts/adds 2026-03-22 12:30:00 -04:00
KoalaTrapLord e4bc3c5ce6 fix: use project root not routes/ for results_dir and reports_dir in coach 2026-03-22 12:07:56 -04:00
KoalaTrapLord d3c9e293d8 fix: mutate COMMANDER_META in-place so cross-module refs stay synced 2026-03-22 12:01:53 -04:00
ClaudeandClaude Opus 4.6 978fb12969 fix: early return in _load_deck_cards_by_name when deck not found in DB
Fixes UnboundLocalError crash when deck_row is None by returning an
empty list instead of falling through to a query with an unbound deck_id.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-22 07:25:39 +00:00
KoalaTrapLord 8c2a8cf1c1 fix: align indentation on line 108 to match surrounding block 2026-03-22 02:38:28 -04:00
KoalaTrapLord fd18d98b9b fix: correct indentation on line 108 in routes/lab.py 2026-03-22 02:35:30 -04:00
KoalaTrapLord 5c21564c78 fix: correct indentation on line 786 in deckbuilder.py 2026-03-22 02:02:22 -04:00
KoalaTrapLord e35df6fe36 fix: auto-export deckbuilder decks to .dck before batch simulation 2026-03-22 01:41:55 -04:00
KoalaTrapLord 7760c589b9 fix: add Deck Type=Commander metadata to .dck export 2026-03-22 01:39:17 -04:00
KoalaTrapLord 7fa262d963 fix(ui): account for nav-height in collection max-height calcs 2026-03-22 00:52:54 -04:00
KoalaTrapLord cffd8d8109 fix: add creature damage accumulation to combat engine
Previously _resolve_combat() used a binary power-vs-toughness kill check:
creatures only died if a single source had power >= their toughness in one
swing. This meant a 2/2 wouldn't die to two 1/1 tokens blocking it, and
repeated smaller hits across combats never accumulated.

Changes:
- models.py: add damage_marked field to Card (default 0)
- engine.py: accumulate damage via damage_marked += power instead of
  binary a_pow >= toughness checks in _resolve_combat()
- engine.py: clear damage_marked = 0 in untap phase (MTG rule 514.2)

This enables multi-blocker lethality, incremental chip damage within a
combat step, and proper creature death-by-accumulated-damage interactions.
2026-03-22 04:52:28 +00:00
KoalaTrapLord 473c453abf fix(ui): add #coll-card-preview CSS for hover image preview 2026-03-22 00:34:54 -04:00
KoalaTrapLord 54080eec37 Merge pull request #77 from KoalaTrapLord/feat/overnight-batch-runner
feat: overnight batch sim runner for ML data generation
2026-03-21 22:31:14 -04:00
rgriffith 5cb4bc3316 feat: add overnight batch sim runner for ML data generation
Script to run sequential batch sims across 10 precon decks with
rotating playstyles (midrange, aggro, control, combo). Generates
ML decision data for training.

- 210 unique 4-player matchups from 10 precon decks
- Cycles through playstyles each batch
- Polls for completion, handles errors gracefully
- Ensures ML logging is enabled before starting
- Prints summary with new decision count at end

Usage: python scripts/overnight-batch.py --hours 8
2026-03-22 02:31:05 +00:00
KoalaTrapLord 099e40d7c5 fix(ui): rewrite CollCardPreview multi-line, use imageUrl instead of scryfallId 2026-03-21 22:19:51 -04:00
KoalaTrapLord 57c4d6a26c Merge pull request #76 from KoalaTrapLord/fix/distillation-results-dir
fix: distillation loop uses wrong results_dir path
2026-03-21 22:18:34 -04:00
rgriffith ea3d488a41 fix: distillation loop uses wrong results_dir path
The distillation pipeline was looking for JSONL decision files in
data/results/ but they are stored in results/ (matching CFG.results_dir).

This caused all generations to fail at Step 1 with:
  'No ml-decisions-*.jsonl files found in data/results'

Fixed:
- routes/ml.py: results_dir and history_dir now use 'results/' not 'data/results/'
- ml/training/distillation_loop.py: updated DistillationConfig defaults to match
- History endpoint paths updated to match
2026-03-22 02:18:23 +00:00
KoalaTrapLord 83ee5ba9cc feat(ui): enable card image hover preview on collection table 2026-03-21 22:10:28 -04:00
KoalaTrapLord 5b532787af feat(ui): add card hover preview element to collection page 2026-03-21 22:05:00 -04:00
KoalaTrapLord 1b88cd362f tweak: reduce card preview hover delay to 225ms 2026-03-21 22:01:28 -04:00
KoalaTrapLord 834f8d9aad fix: Meta Commander picker — lazy-load, live search, debounce
- showMetaPicker() now guards for no backend with clear message,
  and lazy-loads commanders on first open (fixes race condition
  with Promise.all where loadMetaCommanders ran before detectBackend)
- filterMetaCommanders() hits live /api/lab/meta/search endpoint
  when backend is connected, falls back to local array filter
- Search input debounced at 250ms to avoid hammering API
2026-03-22 01:57:12 +00:00
KoalaTrapLord a7bc98c227 feat(ui): add 450ms hover delay to card preview popup
- Add _hoverTimer and HOVER_DELAY (450ms) properties to CardPreview
- mouseenter starts a delay timer before showing the popup
- mousemove only repositions if popup is already visible
- mouseleave clears the timer and hides immediately
- hide() now also clears any pending timer
2026-03-21 21:52:11 -04:00
KoalaTrapLord 3e08d2bed1 feat(ui): add card image hover preview element to deckbuilder
Add #deck-card-preview div with img element that the existing
CardPreview JS object references. This was missing from the HTML.
2026-03-21 21:46:47 -04:00
KoalaTrapLord e4730ad8c7 Merge pull request #74 from KoalaTrapLord/phase6-elo-tracking
Phase 6: ELO Tracking & Model Tournament
2026-03-21 20:31:22 -04:00
rgriffith e843528d8b Phase 6: ELO Tracking & Model Tournament (#70)
- Create ml/eval/elo_tracker.py:
  - EloTracker class with standard ELO formula (K=32, start=1200)
  - EloResult dataclass for tournament output
  - EloHistory class for JSON persistence (data/elo_history.json)
  - run_elo_tournament(): round-robin ELO-rated matches using
    existing run_match() from tournament.py
  - run_generation_tournament(): discovers gen-*/best_policy.pt
    checkpoints and runs cross-generation ELO tournament
  - CLI entry point: python -m ml.eval.elo_tracker

- Add ELO API endpoints to routes/ml.py:
  - POST /api/ml/elo/tournament — start background ELO tournament
  - GET  /api/ml/elo/tournament/status — poll progress
  - GET  /api/ml/elo/history — load ELO history from disk

- Integrate ELO convergence in distillation_loop.py:
  - New config fields: elo_convergence_enabled (default False),
    elo_convergence_threshold (20.0), elo_tournament_episodes (30)
  - New field: GenerationRecord.elo_rating
  - _run_elo_mini_tournament() runs after quality gate pass
  - _check_convergence() checks ELO delta when enabled

- Add ELO UI to distillation page:
  - ELO Ratings panel with canvas multi-line chart showing per-model
    ELO progression across generations
  - Run Tournament button with progress indicator
  - ELO leaderboard table with rank, rating, and visual bars
  - Responsive chart resize handling

Closes #70
2026-03-22 00:31:02 +00:00
KoalaTrapLord dd2e945214 Merge pull request #73 from KoalaTrapLord/phase5-distillation-ui
Phase 5: Distillation UI Integration & Monitoring
2026-03-21 20:19:04 -04:00
rgriffith 106ec716a5 Phase 5: Distillation UI Integration & Monitoring (#69)
- Add distillation API endpoints to routes/ml.py:
  - POST /api/ml/distill/start — start closed-loop distillation pipeline
  - GET  /api/ml/distill/status — poll loop status and generation data
  - POST /api/ml/distill/stop — graceful stop after current generation
  - GET  /api/ml/distill/history — load persisted generation history

- Add 'Distill' nav entry in ui/nav.js with loop icon

- Create ui/distillation.html:
  - Status overview cards (status, generation, best win rate, gate passes)
  - Control panel with hyperparameter inputs for supervised, PPO, and
    quality gate settings
  - Start/Stop buttons with progress bar and phase badge
  - Win rate trend chart (canvas-based, no dependencies)
  - Data composition stacked bar chart (Forge vs PPO per generation)
  - Generation history table with status, metrics, and gate results

- Create ui/distillation.js:
  - Polling loop for real-time status updates during distillation
  - Canvas chart rendering for win rate trends and data composition
  - History table rendering with color-coded status tags
  - Responsive resize handling for charts

- Create ui/distillation.css:
  - Page-specific styles following training.css patterns
  - Chart container, history table, and responsive grid styles

Closes #69
2026-03-22 00:18:43 +00:00
KoalaTrapLord 9bec84e997 Merge pull request #72 from KoalaTrapLord/phase4-distillation-loop
feat: Phase 4 — Distillation Loop Orchestrator
2026-03-21 20:11:31 -04:00
rgriffith ae00dd47e2 feat: Phase 4 — Distillation Loop Orchestrator (#68)
Implements the closed-loop distillation pipeline that automates:
  1. Supervised train on current dataset (Forge + PPO data)
  2. PPO self-play using supervised model as starting policy
  3. Export winning PPO decisions to JSONL
  4. Quality gate check (win rate threshold)
  5. If passed, merge into dataset and repeat
  6. If failed, discard batch, adjust hyperparameters, retry

New files:
  - ml/training/distillation_loop.py — DistillationLoop orchestrator,
    DistillationConfig, GenerationRecord, CLI entry point

Updated files:
  - ml/config/scope.py — DistillationDefaults dataclass with all
    default hyperparameters for the pipeline

Features:
  - Generation tagging: data and models tagged with gen-NNN
  - Convergence detection: stops when win rate plateaus over N gens
  - Automatic retry with LR reduction + entropy boost on gate failure
  - Per-generation history saved to JSON for observability
  - Full CLI with all tunable params
  - progress_callback for UI integration (Phase 5 ready)

Closes #68
2026-03-22 00:11:14 +00:00
KoalaTrapLord dc66ed78e5 fix(lab): remove shadowed _ml_logging_enabled redeclaration (closes #64) 2026-03-22 00:10:12 +00:00
KoalaTrapLord 0f6faa5fe4 feat(ml): integrate DecisionExporter into self-play rollouts (#65) 2026-03-22 00:06:55 +00:00
KoalaTrapLord 1164285a5b docs: add Phase 1 self-play integration checklist (#65) 2026-03-21 19:51:27 -04:00
KoalaTrapLord d9ee09b848 docs: update .env.example to match dual-model config (gpt-oss:20b + deepseek-r1:14b) 2026-03-21 19:47:17 -04:00
KoalaTrapLord d5c6746503 docs: add BRAIN_* and VALIDATOR_* vars to .env.example (#71) 2026-03-21 19:41:47 -04:00
KoalaTrapLord 1b95659f14 fix: add LAN origins (192.168.0.240) to default CORS allowlist (#71) 2026-03-21 19:41:05 -04:00
KoalaTrapLord 871934d101 fix: raise VALIDATOR_TIMEOUT default from 60s to 120s (#71) 2026-03-21 19:40:19 -04:00
KoalaTrapLord 11978736a1 fix: raise BRAIN_TIMEOUT default from 30s to 120s (#71) 2026-03-21 19:39:29 -04:00
KoalaTrapLord 8ed3fbc627 feat(ml): add quality gate & validation module (#67)
Phase 3 of closed-loop distillation pipeline.

- QualityGateConfig with configurable thresholds
- create_forge_holdout() for Forge-only eval split (12% by game_id)
- check_accuracy_gate() rejects PPO batch if Forge eval accuracy drops
- check_win_rate_gate() blocks export below configurable win rate
- compute_quality_metrics() logs action entropy, reward distribution
- QualityGate orchestrator class with evaluate_ppo_batch()
2026-03-21 19:34:37 -04:00
KoalaTrapLord 4d50449145 feat(ml): add source awareness to dataset builder (#66)
Phase 2 of closed-loop distillation pipeline.

- Scan both ml-decisions-sim-*.jsonl (Forge) and ml-decisions-ppo-*.jsonl (PPO)
- Add source_weights config for configurable sampling ratios
- Add min_reward_threshold filter for PPO data quality
- Tag each sample with source for downstream analysis
- New CLI args: --forge-weight, --ppo-weight, --min-reward
- NPZ output now includes 'sources' array
2026-03-21 19:28:22 -04:00
KoalaTrapLord f986d236ab Reduce coach prompt limits and raise LLM timeout for R1 compatibility 2026-03-21 19:08:24 -04:00
KoalaTrapLord 3957285a2e feat(ml): add PPO DecisionExporter for closed-loop distillation (#65)
Phase 1 of closed-loop distillation pipeline.

Adds DecisionExporter class that captures PPO self-play decisions
as JSONL files compatible with the Forge DecisionSnapshot schema.
Includes PPO-specific metadata fields (source, model_version,
reward, episode_return) and configurable win-only filtering.
2026-03-21 18:06:45 -04:00
KoalaTrapLord 3cca49c5a9 docs: add closed-loop iterative distillation implementation roadmap 2026-03-21 17:55:18 -04:00
KoalaTrapLord f290523ce1 fix(ml): replace undefined 'log' with 'logger' in labeler.py 2026-03-21 15:47:47 -04:00
KoalaTrapLord 448cec128c feat(ui): add 12 and 16 thread options to batch runner dropdown 2026-03-21 15:22:20 -04:00
KoalaTrapLord 6612f3d213 fix: use correct key best_val_acc in training completion message 2026-03-21 15:21:35 -04:00
KoalaTrapLord 06f007e605 fix: invert guard in _load_deck_cards_by_name — was if not deck_row (crash on None[0])
The guard condition was inverted: `if not deck_row:` entered the query
block when no deck was found, causing `deck_row[0]` to crash with
'NoneType' object is not subscriptable. Flipped to `if deck_row:`
so the query runs only when the deck exists.
2026-03-21 15:11:08 -04:00