Commit Graph
100 Commits
Author SHA1 Message Date
KoalaTrapLord f24bd05be8 feat: collection stats dashboard (CMC curve, color/type/rarity/value) 2026-03-25 07:51:09 -04:00
KoalaTrapLord e7ffccf3e8 feat: persist coach sessions to SQLite with disk fallback 2026-03-24 23:44:20 -04:00
KoalaTrapLord 64a65ca2f6 feat: auto-fill deck name with Auto - {CommanderName} on select 2026-03-24 23:19:17 -04:00
KoalaTrapLord 3ba44f68d9 feat: oracle-text keyword overlap scoring in _generate_deck 2026-03-24 22:56:30 -04:00
KoalaTrapLord f851df6dbf fix: add getBaseUrl(), PolicyDecision record, and decide(String,int) to PolicyClient
GameSession.java references these APIs which were missing from
PolicyClient, causing 3 compilation errors. Added:
- getBaseUrl() getter
- PolicyDecision record (action, confidence)
- decide(String, int) overload that delegates to decide(JsonObject)
2026-03-24 22:46:45 -04:00
KoalaTrapLord ded79bfde1 feat: add omit_cards chips styles to deck generator CSS 2026-03-24 22:20:10 -04:00
KoalaTrapLord 411e40e46e feat: add omit_cards chips panel to deck generator HTML 2026-03-24 22:17:25 -04:00
KoalaTrapLord a4e49daf49 feat: add omit_cards chips/tags input to deck generator JS 2026-03-24 22:11:36 -04:00
KoalaTrapLord 675c64029b feat: add progress bar + ETA to deck generation UI 2026-03-24 21:20:47 -04:00
KoalaTrapLord 4c47b9d5e2 fix: add V3 result cache to avoid re-generating deck on export 2026-03-24 21:07:59 -04:00
KoalaTrapLord 8903432d67 fix: add missing export filter params (rarity, setCode, power, toughness, keyword, edhrec, qty) 2026-03-24 21:01:35 -04:00
KoalaTrapLord b1e473397f fix: shopping list field name + scan-add refresh function name 2026-03-24 20:53:19 -04:00
KoalaTrapLord 07b2c9d5a9 fix: add missing LLM_TIMEOUT constant in coach.py
LLM_TIMEOUT was referenced but never defined, causing a NameError
in the Ollama streaming SSE code path when COACH_PROVIDER != 'perplexity'.
2026-03-24 20:43:36 -04:00
KoalaTrapLord 58bbb514ad fix: skip ML decision flush when brain not connected (heuristic fallback guard) 2026-03-24 20:27:02 -04:00
KoalaTrapLord d70d74d753 feat: add Scryfall on-the-fly embedding fallback for unknown cards 2026-03-24 20:09:27 -04:00
KoalaTrapLord 475849e477 fix: prevent double-flush of ML decisions in deepseek sim thread
_run_sim_thread_deepseek was manually calling engine.flush_ml_decisions()
and writing the .jsonl, then passing engine=engine to _finish_sim which
called flush_ml_decisions() a second time — returning an empty list and
producing empty training data files.

Fix: pass engine=None to _finish_sim when the deepseek thread has already
flushed manually, so _finish_sim skips the second flush entirely.
2026-03-24 19:19:45 -04:00
KoalaTrapLord b2d1078ca9 fix: disable quiet mode when ML logging is enabled in createWorker
DecisionExtractor requires verbose Forge output to reconstruct board
states. When quiet=true, Forge runs with -q which strips the verbose
log lines, causing 0 decisions to be extracted. This change passes
quiet=false to BatchRunner when mlLogEnabled is true.
2026-03-24 18:45:36 -04:00
KoalaTrapLord 9bce919690 fix: replace non-existent ForgeEpisodeGenerator import with actual exports 2026-03-24 13:45:48 -04:00
KoalaTrapLord 41b711c17e fix: normalize deck report slug matching (spaces/hyphens/underscores) 2026-03-24 13:31:52 -04:00
KoalaTrapLord a6940ca61a fix: update get_status() to check Perplexity connection when COACH_PROVIDER=perplexity 2026-03-24 13:29:16 -04:00
KoalaTrapLord a65daaf801 fix: update Deck Coach status label from LM Studio to Perplexity 2026-03-24 13:26:41 -04:00
KoalaTrapLord c475f5998b docs: add COACH_PROVIDER to .env.example 2026-03-24 12:54:30 -04:00
KoalaTrapLord 456ef927f9 feat: add Perplexity deep research support to coach chat endpoint 2026-03-24 12:53:42 -04:00
KoalaTrapLord a515049542 feat: add COACH_PROVIDER config for Perplexity-powered coach chat 2026-03-24 12:50:21 -04:00
KoalaTrapLord 457e6f91c8 docs: add PPLX_MODEL to .env.example 2026-03-24 12:47:58 -04:00
KoalaTrapLord 977dbd96d7 feat: use PPLX_MODEL config instead of hardcoded 'sonar' in deckgen 2026-03-24 12:47:09 -04:00
KoalaTrapLord 5a499442e6 feat: add PPLX_MODEL env var for configurable Perplexity model 2026-03-24 12:45:40 -04:00
KoalaTrapLord 1f3b17d173 fix: remove ForgeEpisodeQueue import from training __init__
ForgeEpisodeQueue does not exist in rollout_buffer.py yet.
Removing premature import to unblock supervised training pipeline.
2026-03-24 12:26:06 -04:00
KoalaTrapLord f51ff61568 feat(83): Phase 3 — Performance & Scale (#83) (#101)
* feat(83): Phase 3.1 — persistent JVM worker pool in MultiThreadBatchRunner (#83)

* feat(83): Phase 3.2 — async Forge episode generator with producer-consumer pipeline (#83)

* feat(83): Phase 3.3 — deprecate self_play.py synthetic mode for training (#83)

Mark self_play.py synthetic training as deprecated per Phase 3.3:
- Add deprecation notice to module docstring
- Add import warnings and _TRAINING_DEPRECATION_MSG constant
- Add DeprecationWarning in collect_rollouts()
- Synthetic sim retained for local testing/unit tests only
- scope.py already defaults forge_weight=1.0, ppo_weight=0.0

* feat(83): Phase 3.3 — update ml/training/__init__.py exports (#83)
2026-03-24 10:43:51 -04:00
KoalaTrapLord 39bc36afef rename: update JsonExporter.java javadoc example from batch- to ml-decision- 2026-03-23 23:10:44 -04:00
KoalaTrapLord dffd89a01c rename: update LabCli.java default output from batch-latest to ml-decision-latest 2026-03-23 23:09:51 -04:00
KoalaTrapLord 015b9da1e9 rename: update forge_runner.py file prefix from batch- to ml-decision- 2026-03-23 23:06:33 -04:00
KoalaTrapLord f9007effa3 rename: update report_generator.py glob from batch- to ml-decision- 2026-03-23 23:05:25 -04:00
KoalaTrapLord 29ad996966 rename: batch-sim- to ml-decision-sim- prefix in deepseek.py 2026-03-23 23:04:13 -04:00
KoalaTrapLord 51b4e9b5ff rename: batch result files from batch- to ml-decision- prefix in lab.py 2026-03-23 23:00:24 -04:00
KoalaTrapLord 573a271561 fix: use correct query param format for batch status endpoint in overnight-batch.py
The /api/lab/status endpoint uses ?batchId= query param, not path param /{batch_id}
2026-03-23 22:19:21 -04:00
KoalaTrapLord 411fa41063 feat(83): Phase 2 — Live Forge-to-Policy-Server IPC (#100)
* feat(83): add PolicyClient for live Forge-to-policy-server IPC (#83)

Phase 2.1: Bridge between Forge decision points and Python policy
server for online training. Calls /api/policy/decide endpoint,
collects (state, action, reward) tuples in real-time, and flushes
them to the server for online PPO updates.

Falls back to Forge built-in AI if server is unavailable.

* feat(83): add /api/policy/* routes for live Forge IPC (#83)

Phase 2.2: Policy decision routes for real-time Forge IPC.

- POST /api/policy/decide — full game state inference with log_prob/value
- POST /api/policy/collect — online learning tuple batch collection
- POST /api/policy/reward — end-of-game reward propagation
- GET /api/policy/health — readiness check for Java PolicyClient
- GET /api/policy/stats — session statistics

Includes Forge phase mapping and PlayerZoneState with full zone contents.

* feat(83): integrate PolicyClient into GameSession for bidirectional sync (#83)
2026-03-23 22:08:37 -04:00
87404f3702 refactor: move logging out of routes/shared.py into services/logging.py (#99)
Create services/logging.py as the canonical home for setup_logging(),
all log format constants, named loggers, and _ml_logging_enabled.

Update all route modules and lab_api.py to import logging symbols from
services.logging and all other symbols from their canonical sources
(models.state, models.requests, models.responses, services.database,
services.deck_service, services.scryfall, services.import_helpers,
services.import_service, services.precon_service, services.forge_runner).

Delete routes/shared.py — the re-export hub is no longer needed.

Closes #61

Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-23 21:04:25 -04:00
KoalaTrapLordandClaude Code f0dfb41f2f fix: default Ollama endpoints to localhost instead of 192.168.0.240 (#98)
The hardcoded 192.168.0.240 IP was a developer-specific LAN address
that breaks out-of-the-box for anyone running Ollama locally or via
WSL2 on Windows. Switch every default and docstring reference to
localhost so the project works without manual configuration.

Files changed:
- coach/config.py: LLM_URL and DECK_GEN_BASE_URL defaults
- ui/simulator.html: API endpoint input value and placeholder
- lab_api.py: remove LAN IP from default CORS origins
- routes/coach.py: warning message text
- src/commander_ai_lab/sim/deepseek_brain.py: docstrings
- src/commander_ai_lab/sim/deepseek_engine.py: docstring example
- src/commander_ai_lab/sim/validator_brain.py: docstring example

Co-authored-by: Claude Code <noreply@anthropic.com>
2026-03-23 21:03:17 -04:00
56e5bff496 fix: correct indentation of last_err in DeepSeekBrain.choose_action() (#97)
`last_err = None` was at module-level indentation (4 spaces) instead of
method-body indentation (8 spaces), causing an IndentationError on import.

Co-authored-by: Claude Agent <claude-agent@anthropic.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-23 20:01:11 -04:00
KoalaTrapLord 62463dfa4a fix: correct indentation in deepseek_brain.py (choose_action + _parse_action) 2026-03-23 19:34:14 -04:00
KoalaTrapLord 7e0aba78ba fix: add ML logging support to MultiThreadBatchRunner 2026-03-23 19:01:43 -04:00
KoalaTrapLord 7036c9568f fix: enable ML decision logging in multi-thread batch path 2026-03-23 18:58:14 -04:00
KoalaTrapLord b37968e3c2 fix: always pass --ml-log flag to Forge Java batch runner 2026-03-23 17:22:58 -04:00
KoalaTrapLord d424ccb74c feat(86): add combat priority checkpoints with CombatState and split engine methods (#96) 2026-03-23 16:52:19 -04:00
KoalaTrapLord 1cf2439a5a fix(#95): correct validator defaults to deepseek-r1:8b and 300s timeout
_get_validator_brain() defaulted VALIDATOR_MODEL to deepseek-r1:14b
and VALIDATOR_TIMEOUT to 120.0, conflicting with the brain config.
Changed to deepseek-r1:8b and 300.0 to match. Fixes issue #95 item 4.
2026-03-23 14:31:59 -04:00
KoalaTrapLord f3bd188a8b fix(#95): implement retry loop and bump max_tokens/max_retries defaults
max_retries was configured but never used — choose_action() made
exactly one attempt. Now wraps _call_llm() in a retry loop.
Bumped max_retries default to 2 and max_tokens to 4096 (R1 needs
room for <think> blocks + JSON). Fixes issue #95 item 3.
2026-03-23 14:29:22 -04:00
KoalaTrapLord c1ad9e953c fix(#95): replace regex JSON extraction with outermost-brace approach
_parse_action() used r'\{[^{}]*\}' regex which fails on nested
JSON (e.g. oracle text with braces). Now uses text.find/rfind to
extract the outermost { } block, handling nested structures.
Fixes issue #95 item 2.
2026-03-23 14:24:57 -04:00
KoalaTrapLord 25e064d265 fix(#95): prevent check_connection from overwriting configured model
check_connection() was auto-overwriting self.config.model with the
first model returned by /v1/models, causing deepseek-r1:8b to be
replaced by deepseek-r1:14b (or whatever Ollama listed first).

Now logs available models without changing the configured model.
Fixes issue #95 item 1.
2026-03-23 10:37:39 -04:00
2b6d4dcdb1 fix: deduplicate LLM duplicate cards in _check_ownership (#78) (#79)
Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-23 09:42:33 -04:00
eee773b6f9 Forge Card Enrichment — Full Implementation (Issue #80) (#81)
* feat(issue-80): Phase 1 — add services/forge_card_loader.py

* feat(issue-80): Phase 2 — add 5 Forge fields to Card dataclass

* feat(issue-80): Phase 3+4 — wire Forge into enrich_card/score_card

* feat(issue-80): Phase 5 — add tests/sim/test_forge_enrichment.py

* fix(issue-80): resolve PR #81 blocking issues — import paths, indentation, partial Forge fallback

- Move forge_card_loader.py from services/ into src/commander_ai_lab/sim/
  so the import in rules.py uses a proper intra-package path instead of
  crossing package boundaries via services.forge_card_loader
- Fix indentation of Forge enrichment comment in models.py (8→4 spaces)
- Fix enrich_card() early return on partial Forge data: re-check
  has_real_data after _apply_forge_data so cards with incomplete Forge
  entries still fall through to known-cards / default heuristics
- Fix flawed test assertion in test_etb_via_forge_mode_equals_text_detection:
  oracle text "draw a card" was adding a +3 spell_draw bonus, making the
  score comparison invalid; replaced with side-effect-free ETB text

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Code <claude-code@anthropic.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-23 09:37:05 -04:00
2eae7dcbd5 fix(ci): remove frontend job that references non-existent directory (#94)
The CI workflow had a `frontend` job that tried to run npm ci/build/test
in a `frontend/` directory which does not exist in this repo. The actual
UI lives in `ui/` as static HTML/CSS/JS (no Node.js toolchain). This
caused every CI run to fail on an otherwise valid codebase.

Co-authored-by: Claude Agent <claude-agent@anthropic.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-22 23:54:16 -04:00
0f8d1382e0 feat: explicit turn-phase structure with Phase enum and main2 support (#86) (#93)
Refactor the headless engine to walk through all 12 MTG turn phases in
order (untap → upkeep → draw → main1 → begin_combat → declare_attackers
→ declare_blockers → combat_damage → end_combat → main2 → end_step →
cleanup) instead of the previous flat per-player block.

Key changes:
- Add Phase str enum and PHASE_ORDER/SORCERY_PHASES constants to models
- Track current_phase and active_player_index on SimState
- Sync sim.turn with the loop turn counter (fixes latent summoning-
  sickness bug where turn_played was always compared against 0)
- Add second main phase (main2) with shared 2-spell budget across
  main1 + main2
- _play_spells now accepts max_spells param and returns count played
- Unify turn_manager.py PHASES/ACTION_PHASES/RESPONSE_PHASES to derive
  from the canonical Phase enum in models.py
- 33 regression tests covering phase enum, phase progression, spell
  budget, game log structure, backward compat, and turn manager
  constant consistency

Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-22 23:46:09 -04:00
KoalaTrapLord b72d5fba83 fix(#92): bump READ_TIMEOUT_MS from 5s to 300s in PolicyServerClient.java 2026-03-22 23:44:47 -04:00
KoalaTrapLord 5d34d07bb8 fix(#92): bump validator_brain.py timeouts from 60s to 300s 2026-03-22 23:43:30 -04:00
KoalaTrapLord 5e9685ba70 fix(#92): read BRAIN_TIMEOUT env for ai_decision_timeout + add import os in turn_manager.py 2026-03-22 23:42:28 -04:00
KoalaTrapLord 49fc817187 fix(#92): bump BRAIN_TIMEOUT fallback from 120s to 300s in forge_runner.py 2026-03-22 23:40:28 -04:00
KoalaTrapLord c2b3d937a4 fix(#92): read BRAIN_TIMEOUT env for request_timeout in deepseek_brain.py 2026-03-22 23:39:33 -04:00
5e9c568bcc feat: implement London mulligan and opening-hand validation (#86) (#91) (#91)
Add London mulligan rule to the headless game engine:

- GameEngine now accepts mulligan_rule param ("london" default, "none" for legacy)
- AI heuristic: mulligan 0-1 land or 6-7 land hands; relax after 2+ mulligans
- After keeping, bottom N lowest-scored cards (London mulligan rule)
- Cap at MAX_MULLIGANS=4 to prevent infinite loops on degenerate decks
- Track mulligans and opening_hand_lands in PlayerStats
- Serialize both fields in to_dict() for batch-result schema compatibility
- 28 targeted regression tests covering unit helpers, integration,
  N-player, commander interaction, serialization, and legacy mode

Co-authored-by: Claude Agent <claude-agent@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-22 23:28:01 -04:00
KoalaTrapLordandClaude Code 85aa15445b feat: route direct-damage spells through centralized deal_damage() (#86) (#90)
Add centralized deal_damage() method to GameEngine that handles life
reduction, stat tracking, commander damage (both per-seat and per-card),
and elimination checks. Route both burn spells and combat damage through
this unified path, building on the dual-dict commander damage model
merged in PR #89.

- Add is_direct_damage / direct_damage_amount fields to Card
- Add burn spell detection via known-card table + oracle regex fallback
- Route burn spells in _play_spells() through deal_damage()
- Refactor _resolve_combat() to route per-attacker damage through deal_damage()
- Add 38 regression tests covering deal_damage(), burn detection, spell routing,
  combat routing, and full-game integration

Co-authored-by: Claude Code <noreply@anthropic.com>
2026-03-22 22:27:37 -04:00
KoalaTrapLordandClaude e265b1e15b feat: commander damage source identity and per-card tracking (#86) (#89)
Track commander damage at combat-damage time by checking `is_commander`
on each attacking creature.  Damage from commander creatures is now
recorded in both a per-seat aggregate (`commander_damage_received`) and
a per-card breakdown (`commander_damage_by_card`), enabling correct
21-damage lethal checks for partner/multi-commander games.

Changes:
- engine._resolve_combat: identify commander attackers, accumulate their
  damage (including trample overflow and double strike), write to both
  tracking dicts, and check commander-damage elimination after combat.
- models.Player: add commander_damage_received, commander_damage_by_card,
  and is_dead_to_commander_damage() so the headless engine can track
  commander damage without the full CommanderGameState wrapper.
- game_state.CommanderPlayer: replace the separate
  commander_damage_received field with a property delegating to
  base.Player to avoid split-brain state between engine and wrapper.
- game_state.CommanderGameState.deal_commander_damage: accept optional
  commander_name for per-card tracking.
- 22 new regression tests covering unblocked, blocked, trample, double
  strike, lifelink, partner, cumulative, and lethal scenarios.

Partial fix for Issue #86 item 2.

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-22 22:04:19 -04:00
KoalaTrapLord ae8d14c19e chore: point deepseek brain at deepseek-r1:8b on localhost 2026-03-22 21:49:45 -04:00
KoalaTrapLord f090a36f79 chore: switch deck builder default model to qwen2.5:7b 2026-03-22 21:40:26 -04: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
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