Commit Graph
433 Commits
Author SHA1 Message Date
Claude bd94435993 cleanup: remove unused imports, merge duplicates, drop stale comment
- Remove unused _ml_logging_enabled import from routes/deepseek.py
- Remove dead _ml_logging_enabled top-level import and
  _ml_logging_enabled_local alias from routes/ml.py (endpoints already
  use the module-path form via import services.logging)
- Merge duplicate from services.precon_service imports in routes/precon.py
- Merge duplicate from models.requests imports in routes/deckgen.py
- Remove redundant inline comment in routes/precon.py

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-24 02:53:40 +00:00
ClaudeandClaude Opus 4.6 dff7ae2860 refactor: move logging out of routes/shared.py into services/logging.py
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 Opus 4.6 <noreply@anthropic.com>
2026-03-24 00:55:56 +00: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
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