Fixes 404 errors from lan-client ai-bridge.js which calls these routes
to populate the deck selector. Routes were missing from lab.py — only
precon_router (routes/precon.py) existed but served different paths.
Also added PRECON_DECKS_DIR env var alias in lab_api.py _parse_args()
to match the .env variable name users commonly set.
- Mount lan-client/ as static files at /lan with html=True
- Mount precon-decks/ at /precon-decks/ for direct .dck access
- ai-bridge.js: use same-origin (empty URLs, auto-detect from window.location)
- engine.js: simplify deck fetch to use /api/lab/precons/deck endpoint + /precon-decks/ static fallback
- Eliminates cross-origin issues by serving everything from localhost:8080
Access the LAN battlefield at http://localhost:8080/lan/
Three fixes that prevented precon decks from loading into actual gameplay:
1. ui.js: window.startGame() now calls startGameWithDecks() when
setupDeckChoices has entries (the 3-tab selector stores choices there).
Previously it called initGame() which looked for old deck-sel-{i}
dropdowns that no longer exist.
2. engine.js: loadPreconForPlayer() now fetches .dck file content from
the backend (/api/lab/precons/deck?fileName=...) when precon.decklist
is empty. Falls back to fetching precon-decks/{file} directly.
PRECON_DATA entries only have metadata (name, commander, set) — the
actual card list lives in the .dck files.
3. engine.js: parseDeckList() now handles .dck bracket-style section
headers like [Commander], [Main], [metadata]. Previously only matched
bare text like 'COMMANDER:' without brackets. Also skips [metadata]
lines (e.g. 'Name=Elven Empire').
4. routes/precon.py: Added GET /api/lab/precons/deck?fileName=... endpoint
to serve raw .dck file content for the LAN client.
- Add max-height: 92vh and overflow-y: auto to .setup-card
- Widen .setup-card to 860px to fit 2-column deck grid
- Change .deck-assignment-grid from vertical stack to 2-column grid
so 4 player panels render in 2 rows instead of 4
- Tighten .deck-tab-content (170px) and .deck-precon-list (140px)
- Make top gradient bar sticky on scroll
- Responsive fallback to single column below 640px
- Generate lan-client/precons.js from precon-decks/precon-index.json so
PRECON_DATA is always available for local/offline mode (163 precons)
- Add scripts/generate-precons-js.js for regeneration when precons update
- Fix _loadBackendPrecons() to re-render the precon tab UI when backend
precons load successfully (was only updating status indicator text,
not populating the dropdown)
Without precons.js, PRECON_DATA was undefined and the dropdown showed
'No precon data loaded' even though 163 .dck files exist in the repo.
* feat: LAN Battlefield App integration with Commander-AI-Lab AI pipeline
Add the LAN Battlefield App as a browser client (lan-client/) that
connects to the Commander-AI-Lab backend for ML-powered AI opponents,
3-category deck selection, and enhanced player panels.
Phase 1 - Foundation:
- Remove floating mana pool display (keep internal tracking)
- Add energy, experience, rad, emblems fields to game state
- No-op renderManaPool(), showManaPool(), setupManaPoolDrag()
Phase 2 - Backend Connection (ai-bridge.js):
- BackendConnector: REST + WebSocket connectivity with retry/timeout
- GameStateBridge: Serializes engine.js state for /api/policy/decide
- MacroActionMapper: Translates 8 ML macro-actions to engine.js calls
- DeckSourceClient: Fetches precon/EDHREC decks from backend API
Phase 3 - Deck System:
- 3-tab deck selector (Precons / EDHREC / My Decks) replaces dropdown
- Precon grouping by set with collapsible sections and filtering
- EDHREC search integration (stub, ready for backend implementation)
- My Decks textarea import placeholder
Phase 4 - AI Integration:
- runAiTurn() priority chain: ML Pipeline > Forge > Local Heuristic
- _runMlAiTurn() async turn loop with multi-phase decision requests
- Backend session initialization on game start
- Automatic fallback to local AI on any ML pipeline error
Phase 5 - Enhanced Player Panels (player-panels.js):
- Collapsible panels with localStorage persistence
- Commander damage progress bars with warning/critical thresholds
- Poison, energy, experience, rad counter widgets
- Emblem tracking with add/remove
Phase 6 - Polish:
- Script loading order: precons > engine > ai-bridge > panels > ui
- Backend auto-detection on page load with status indicator
- Connection status display on setup screen
Closes#184, Closes#185, Closes#186, Closes#187, Closes#188, Closes#189
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: refine LAN battlefield integration with full JSDoc, proper phase IDs
- Add comprehensive JSDoc comments to all ai-bridge.js classes
- Use optional chaining for PHASES lookup in GameStateBridge
- Direct addLogEntry calls in player-panels.js (loaded after engine.js)
- Remove leftover mana pool CSS that was missed in initial pass
- Minor style.css cleanup for consistency
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
When running plain `python script.py` or bare uvicorn, .env is never
loaded automatically. Added a dotenv bootstrap block at the top of
state.py that finds and loads the nearest .env file (walks up from
models/ to the project root). Uses load_dotenv with override=False so
explicitly set environment variables always win over .env values.
Gracefully skips if python-dotenv is not installed.
CFG.forge_dir, precon_dir, forge_decks_dir, forge_jar, and lab_jar
were only set via CLI args. When the server starts via uvicorn directly
(no CLI), all paths stayed empty and forge_card_loader could not find
the cardsfolder, causing 0 lands in every deck.
Fix: read FORGE_DIR, FORGE_JAR, PRECON_DIR, PRECON_DECKS_DIR,
FORGE_DECKS_DIR, and LAB_JAR from environment at import time, mirroring
the existing ANTHROPIC_API_KEY / XIMILAR_API_KEY pattern.
_find_dck_file only searched CFG.precon_dir / CFG.forge_decks_dir, both
of which are empty when the server is started without CLI args. This
caused all precon deck lookups to return 0 cards.
Fix: add _REPO_ROOT and _BUILTIN_DECK_DIRS constants, then append
precon-decks/, sample-decks/, imported-decks/ to the search list
whenever they exist on disk, regardless of whether CFG paths are set.
_load_deck_cards_from_dck was hardcoding type_line="" for every card,
causing Card.is_land() to return False for all lands when a deck is
loaded via the .dck file path (i.e. not in the SQLite DB). This meant
the engine never recognised lands as lands, breaking mana production.
Fix: call lookup_forge_card() per card and populate type_line,
oracle_text, cmc, mana_cost, power, toughness, and keywords from the
Forge cardsfolder cache. The cache is lazy-loaded and module-level so
there is no per-game overhead after the first deck load.
Precon decks (Elven_Empire, Blood_Rites, etc.) are stored as .dck files
on disk but not inserted into the SQLite decks/deck_cards tables.
The DeepSeek batch engine calls _load_deck_cards_by_name() which
previously returned [] for any deck not in the DB, causing
'No decks could be loaded.' errors.
Adds _find_dck_file() and _load_deck_cards_from_dck() helpers.
_find_dck_file searches CFG.precon_dir, CFG.forge_decks_dir, and
FORGE_DIR/res/decks/commander with exact + fuzzy matching.
_load_deck_cards_by_name now tries DB first, then .dck fallback.
- start-lab.bat: Set FORGE_DECKS_DIR to D:\ForgeCommander\commander-ai-lab\precon-decks
- BatchRunner.java: resolveForgeDecksPath() now checks PRECON_DECKS_DIR env var
first, then falls back to the hardcoded precon-decks path, before trying
%APPDATA%\Forge\decks\commander. Also pass full absolute path to Forge -d flag.
- .env.example: document PRECON_DECKS_DIR variable
Captures life-at-timeout snapshots for every draw game via [DRAW] [PARSE-SUMMARY] log lines. Adds services/draw_game_analyzer.py with DrawGameSnapshot + summarize_draw_games(). Wires drawGames summary into GET /api/lab/result response. Lowers batch clock default from 6000 to 3000 (30s per-game). Closes#176.
- Add validate_card_names() helper: checks collection_entries DB first,
then falls back to Scryfall /cards/named?fuzzy= for unknowns.
Drops any name that cannot be resolved (hallucinated cards).
- Inject validation in Step 4 immediately after each suggest_cards()
call so hallucinated names never reach ratio enforcement or assembly.
- Log dropped hallucinations at WARNING level with count + examples.
- Resolves the 'Sonic Hedgehog Charger' / 'Sonic Boom' class of
LLM-invented card names that were silently entering the deck.
- Add _parse_card_list_fallback() to recover plain-text CSV output from
the LLM when json_mode fails to enforce JSON (e.g. qwen2.5:7b drift)
- Harden suggest_cards system prompt with a one-shot JSON example so
small models are less likely to return prose in the first place
- Log recovered names at WARNING level so the degradation is visible
without being silent (previously returned [] with no recovery attempt)
- No behaviour changes to any other function
dict() on a types.SimpleNamespace raises TypeError: object is not iterable.
vars() correctly extracts the __dict__ attribute map. Fixes
TestBuildCollectionFailureTracking::test_high_failure_rate_raises_runtime_error.
The three split-combat helpers were accidentally defined as nested
functions inside _resolve_combat(), making them unreachable as
GameEngine.assign_attackers() etc. from tests and turn_manager.
Move them out to class-level methods with correct 4-space indentation.
_resolve_combat() now remains as the backward-compatible atomic path for
run_n(); the split methods are available for turn_manager priority
windows and direct testing.
begin_combat/end_combat are dispatched as named priority windows inside
_phase_combat(), not as entries in RESPONSE_PHASES. The constant is
correctly {"upkeep", "end_step"} — test was asserting stale intent.
Delta matches \bdelta\b in the nonbasic land guard regex, causing enrich_card
to classify it as a land (cmc=0). Zeta and Theta have no land keyword matches.
Surfaces previously-computed-but-unused stats into the Claude prompt:
1. USER_PROMPT_TEMPLATE gains a new '### Sim Insights' section that shows:
- Deck-level opening hand quality: avg keptInOpeningHandRate across all
cards, plus the 3 worst offenders with their actual rates
- Curve velocity: avg avgTurnCast weighted by castRate, flagging cards
cast on avg turn > 6 as 'late'
- Synergy outliers: bottom-3 synergyScore cards flagged as 'isolated'
- Archetype weak spots: archetypes with winRate < 40% flagged explicitly
2. _format_archetypes() now annotates weak spots with [WEAK SPOT] and
strong matchups with [STRENGTH] so Claude sees them immediately.
3. _format_top_cards() adds keptInOpeningHandRate to each line so
the 'good' cards also show their hand-keepability signal.
4. New helper _format_sim_insights() computes all deck-level aggregates
from CardPerformance list — no new fields on DeckMeta needed.
No schema changes. Fully backward-compatible with existing DeckReport.
New test file covering:
- TestGlobalFeatureDimensions: output shape (29,), all values in [0,1]
- TestTotalPowerRealValue: real total_power used at index 7 when present
- TestTotalPowerHeuristic: creatures*3 fallback when total_power absent/zero
- TestLandCount: real lands used at index 13 when present; defaults to 0
- TestCreatureCount: real creatures used at index 6; fallback to bf len
- TestManaFeature: mana normalised and clamped to [0,1]
- TestPhaseOneHot: exactly one hot in indices 9-12 per player block
- TestIsActivePlayer: binary 0/1 at index 8
- TestLifeNormalisation: 40 life → 1.0, 0 life → 0.0
- TestTurnFeature: turn clamped to [0,1]
- TestFullEncode: total state vector shape (6177,), dtype float32
- TestEmptyPlayer: _empty_player() has all Step 4 keys
- TestLegacySnapshot: old JSONL snapshot without Step 4 keys still encodes"