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>
- 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
- 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
- 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
- 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
- Add full CSS for precon grid: responsive card grid, hover effects,
install button states, color pips, dark theme styling
- Build commander art_crop image URLs from set_code/number during
precon download (Scryfall direct image redirect, no API call needed)
- Batch-fetch color_identity from Scryfall /cards/collection endpoint
(2 requests for ~145 unique commanders) and store in precon index
- Widen precon modal to 900px for grid layout
- Auto-invalidate precon cache when enrichment fields are missing
- JS renderer now shows commander art crop + card body layout
- Bump cache-busting version to v=4
Browser was serving the old Base64-encoded ai-lab.js from cache.
Adding ?v=3 query parameter forces the browser to fetch the new
decoded JavaScript file.
The entire ai-lab.js file (139KB) was stored as a single Base64-encoded
string instead of actual JavaScript. The browser loaded it as a script
but couldn't parse it — AiLab was never defined, so the batch sim panel
never rendered.
Decoded the file back to its proper 2531-line JavaScript source. The
file already includes auto-initialization at the bottom:
if (document.readyState === 'loading')
document.addEventListener('DOMContentLoaded', AiLab.init);
else AiLab.init();
Also removed the now-redundant DOMContentLoaded listener from
index.html since the JS file handles init itself.
The ai-lab.js IIFE defines the AiLab global with an init() method that
calls renderPanel(), detectBackend(), loadAvailableDecks(),
loadMetaCommanders(), loadPrecons(), and bindEvents() — but nothing
ever invoked it. Added a DOMContentLoaded listener after the script tag
in index.html to trigger initialization.
- Added _fill_basic_lands() post-processing for V1 and V3 generators
- Strips LLM-provided basics, then distributes evenly across commander colors
- Always hits exactly 100 cards regardless of LLM output
- Frontend group counts now sum card.count instead of entry count
- Cards with count > 1 show '5x Forest' style prefix
- Colorless commanders get Wastes
Previously the coach required running simulations first to generate a
deck-reports/*.json file. Now if no simulation report is found, the
endpoint builds a lightweight DeckReport on the fly from the deck_cards
table — pulling card names, types, CMC, and color identity directly from
the DB. The LLM can still analyze composition and give advice, just
without simulation-derived performance metrics (impactScore, etc.).
Also removes the '[no report]' tag from the dropdown since all decks
can now be coached.
Backend returns [{deck_id, deck_name, commander, has_report, ...}] directly,
not {decks: [...]}. Frontend was reading data.decks which was always undefined.
Now properly unwraps the array, shows deck name + commander, slugifies name
for coach session deck_id, and marks decks without reports.