Commit Graph
100 Commits
Author SHA1 Message Date
KoalaTrapLord 968b68947e fix(#176): wire drawGames summary into get_result() response
Calls summarize_draw_games(state.draw_game_snapshots) after the existing
win-type injection and embeds the result under data['drawGames'] before
returning JSONResponse.  The likelyCause field immediately surfaces which
deck most often leads at timeout so the Dictate of Kruphix loop theory
can be confirmed from the dashboard without log-diving.

Part of fix #176 (companion to services/draw_game_analyzer.py).
2026-04-01 14:38:53 -04:00
KoalaTrapLord b6222cbc7c fix(#176): capture life-at-timeout for draw games + add DrawGameAnalyzer
- forge_runner._run_process_blocking: parse [DRAW] PARSE-SUMMARY lines
  and record life totals per seat into state.draw_game_snapshots
- routes/lab.py get_result(): attach draw_game_snapshots to result JSON
  under 'drawGames' so the dashboard and analysis scripts can query them
- New services/draw_game_analyzer.py: report which deck held the highest
  average life at timeout; used by /api/lab/result and scripts/
- Also lowers the default batch --clock from 6000 → 3000 per game
  (30s per-game wall limit for batch mode, down from 60s).
  Interactive games can override via req.clock which defaults stay at 6000.

Closes #176
2026-04-01 14:38:53 -04:00
KoalaTrapLord 5411fd6bf6 fix(#175): alternate win condition parser & winType tagging (#179)
Adds win_condition_parser.py, GameResult.win_type field, and winType stamping on both the Java/Forge and DeepSeek batch paths. Closes #175.
2026-04-01 14:28:10 -04:00
KoalaTrapLord 55fdc2ec4a feat: add CreateBattlegroundScene editor script to build full scene programmatically 2026-03-30 20:31:09 -04:00
KoalaTrapLord ddfe8e601e fix(eval): parse batch JSON output instead of JSONL for Forge game results 2026-03-30 16:12:20 -04:00
KoalaTrapLord 9e3cd45936 fix(eval): require >= 3 decks for Forge availability check 2026-03-30 15:45:20 -04:00
KoalaTrapLord c91f4c90bc fix(eval): discover 4 decks for 4-player Commander (Forge requires >= 3) 2026-03-30 15:43:27 -04:00
KoalaTrapLord 80f67dd0a0 fix(eval): use precon-decks/ for deck discovery instead of decks/ 2026-03-30 15:28:19 -04:00
KoalaTrapLord c62e75af52 fix(tests): update _build_document assertions to match NL sentence format 2026-03-30 12:36:00 -04:00
KoalaTrapLord 11940835fc fix: validate ollama suggest_cards names against DB + Scryfall before pipeline merge
- 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.
2026-03-30 12:33:28 -04:00
KoalaTrapLord d12b47f874 fix: robust suggest_cards parsing — CSV fallback + one-shot JSON prompt
- 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
2026-03-30 12:26:28 -04:00
KoalaTrapLord 5b5bac42fb fix: use vars(row) instead of dict(row) in _build_collection_from_bulk
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.
2026-03-30 10:39:49 -04:00
KoalaTrapLord 5554ef4dd7 fix(eval): call load() for embeddings before reload() for checkpoint 2026-03-30 09:53:56 -04:00
KoalaTrapLord cf2d7ed806 fix(eval): use reload() with explicit checkpoint path instead of load()
load() hardcodes best_policy.pt but PPO trainer saves as best_ppo.pt, causing the model to never load during eval. reload() accepts an explicit path.
2026-03-30 09:51:22 -04:00
KoalaTrapLord aec1896094 fix(engine): promote assign_attackers/assign_blockers/resolve_combat_damage to public methods (#86)
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.
2026-03-30 09:50:56 -04:00
KoalaTrapLord 75785ae918 fix(tests): update RESPONSE_PHASES assertion to match current turn_manager design
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.
2026-03-29 20:39:37 -04:00
KoalaTrapLord 252e46b288 fix(tests): swap Delta/Epsilon for Zeta/Theta to avoid _land_kw collision
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.
2026-03-29 20:37:04 -04:00
KoalaTrapLord e1bb2e7209 feat(coach): Phase 1 sim stats pass-through in prompt template (#108)
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.
2026-03-29 05:55:51 -04:00
KoalaTrapLord e77a945805 test(encoder): unit tests for StateEncoder global features + Step 4 total_power (#83 Step 4)
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"
2026-03-29 05:46:01 -04:00
KoalaTrapLord 3f5d6d12e8 feat: 4-player Commander Battleground UI (issues #138–#151)
* chore: add battleground UI task tracker (2026-03-28)

* feat(#142): add [NonSerialized] battleground runtime state to CardModel

* feat(#143): add LoadArtCrop and LoadCardRaw (RawImage) to ImageCache

* feat(#148): add GameStateManager singleton and GameState/SeatState POCOs

* feat(#138): add BattlegroundController scene orchestrator

* feat(#139): add LifeTrackerWidget, CommanderDamageRow, PoisonCounter

* feat(#140): add PhaseTrackerWidget and PhaseStepButton

* feat(#141): add TurnOrderBar and TurnOrderPill

* feat(#142): add CardZone and CardView battlefield zone components

* feat(#143): add CommanderZoneWidget with art crop, cast counter, and tax display

* feat(#144): add TokenModel POCO, TokenPanelController, and TokenRowView

* chore: mark issues 1-8 as complete in task tracker

* feat(#146): add StackZoneController, StackItemView, and ManaPoolDisplay

* feat(#147 #150 #151): add LobbySetupModal, ZoneViewerModal, EliminationHandler; add Play Live button to SimulationController

* feat(#151): add Play Live button to SimulationController

* feat(#149): add GameWebSocketClient with reconnect, ping, and main-thread dispatch

* fix: add PostGameAction to ApiClient + update TODO.md all issues complete

- ApiClient.cs: add PostGameAction() wrapper → POST /api/game/action
  (fixes compile error in HumanActionBar.cs)
- TODO.md: mark issues #145, #146, #147, #149, #150, #151 as ✅
  all 14 battleground issues now committed

* feat(game): add routes/game.py + wire lab_api + fix GameWebSocketClient game_id URL

- routes/game.py: POST /api/game/start, POST /api/game/action (new)
- lab_api.py: import + register game_router
- GameWebSocketClient.cs: read game_id from PlayerPrefs, build
  ws://{host}/ws/game/{game_id} — fixes URL mismatch with ws_game.py

* fix(lobby): wire OnConfirm to POST /api/game/start, save game_id to PlayerPrefs

- OnConfirm() now calls ApiClient.PostGameStart() with 4-seat config
- On success: saves BattleSeats + GameId + WsUrl to PlayerPrefs,
  loads Battleground scene
- On failure: shows inline error label (red), re-enables Confirm button
- Added StartGameRequest/Response POCOs matching routes/game.py schema
- Added _errorLabel [SerializeField] for user-facing error feedback
2026-03-29 05:35:29 -04:00
KoalaTrapLord 915c745911 fix(eval): remove Set-StrictMode, fix curly-quotes and -- in strings (issue #153) 2026-03-29 05:31:58 -04:00
KoalaTrapLord 00374c1f03 feat(eval): add run_benchmark.ps1 — full benchmark pipeline for issue #153 2026-03-29 05:13:21 -04:00
KoalaTrapLord 6e71ba88fd fix(encoder): read real total_power from snapshot dict at global index 7 (#83 Step 4)
Previously _encode_global() always computed:
  total_power = creatures * 3  (heuristic)

Now reads p.get('total_power', 0) first, falling back to the heuristic
only when the key is absent (legacy JSONL snapshots without Step 4 data).

Also wires total_toughness, artifacts, and enchantments into _empty_player()
so padding players have consistent keys.
2026-03-29 05:13:19 -04:00
KoalaTrapLord 73d8e8d220 test(policy): add Step 4 board-stats tests + fix schema alias (#83 Step 4)
- TestBoardStatsWiring: verifies creaturesOnField, landCount,
  totalPowerOnBoard, totalToughnessOnBoard, artifactsOnField,
  enchantmentsOnField are accepted without 422 and survive
  a round-trip through the fallback encoder path
- TestBoardStatsFallback: verifies resolver fallbacks when Forge
  fields are absent (creatures inferred from bf length,
  total_power falls back to creatures * 3 heuristic,
  lands defaults to 0)
- TestSchemaAlias: verifies snapshot_schema Field(alias='schema')
  still accepts JSON key 'schema' without 422
- Removes now-irrelevant UserWarning (schema shadow fixed)
2026-03-29 04:51:47 -04:00
KoalaTrapLord bff7b3a81e feat(eval): add forge_evaluator, eval_policy, compare_eval for issue #153
- ml/eval/forge_evaluator.py: reusable eval harness wrapping
  MultiThreadBatchRunner + PolicyInferenceService; collects win rate,
  avg turns, life differential, commander damage, illegal action rate,
  policy entropy per game; writes results/eval-{run_id}.json
- scripts/eval_policy.py: CLI to run N Forge eval games with any
  checkpoint vs forge-builtin or policy-server opponent; supports
  --threads, --clock, --seed, --greedy, --temperature
- scripts/compare_eval.py: loads two result JSONs, computes absolute
  win-rate delta + 95% CI, runs Fisher's exact test for significance,
  prints formatted comparison table and pass/fail verdict
2026-03-29 04:49:43 -04:00
KoalaTrapLord b1c17359f0 feat(policy): wire real Forge board stats into encoder snapshot (#83 Step 4)
Previously _build_encoder_snapshot() stubbed:
  - lands = 0  (always zero)
  - creatures = len(bf_names)  (name-list length, ignores tokens/etc)
  - mana = mana_available  (was already correct)
  - total_power / total_toughness / artifacts / enchantments = missing

Now reads real pre-computed values from PlayerZoneState:
  - creatures      ← creaturesOnField   (Forge board stat)
  - lands          ← landCount          (Forge board stat)
  - total_power    ← totalPowerOnBoard  (fed to global feature index 7)
  - total_toughness← totalToughnessOnBoard
  - artifacts      ← artifactsOnField
  - enchantments   ← enchantmentsOnField
  - mana           ← manaAvailable (unchanged)

Also replaces the `creatures * 3` proxy in _build_encoder_snapshot with
the real totalPowerOnBoard value so index-7 of the global feature vector
tracks actual power rather than a heuristic.

PlayerZoneState gains 5 new optional int fields (default 0) matching
DecisionSnapshot.PlayerSnapshot field names so JSON from both the live
IPC path and the JSONL training path deserialise without changes.
2026-03-29 04:49:40 -04:00
KoalaTrapLord 7440d0ce17 test(policy): add round-trip tests for /api/policy/decide state_vector path (#83 Step 2)
Covers:
  - health endpoint returns {ready, status, model_loaded}
  - precomputed fast path: state_vector float[29] → vector_source=precomputed
  - correct 6177-dim tensor shape reaches the mock model
  - all 8 MacroAction values are valid responses
  - encoder fallback: missing state_vector → vector_source=encoder
  - encoder fallback: wrong-length state_vector → vector_source=encoder
  - GameSession field name aliases (turnNumber, activePlayer,
    commanderTax, handCount, commandZone) are accepted without 422
  - 503 when model not loaded
  - /api/policy/stats tracks precomputed_vector_hits vs encoder_fallbacks
  - both phase strings (MAIN1, main_1) round-trip correctly

Uses a MockPolicyInferenceService that captures the tensor shape passed
to the model so we can assert on dim without needing a real checkpoint.
2026-03-29 04:37:09 -04:00
KoalaTrapLord 4d73f134c2 feat(policy): consume state_vector from GameSession snapshots (#83 Step 2)
DecideRequest now accepts an optional state_vector field (float[29]).
When present, /api/policy/decide uses it directly for the global scalar
block instead of re-deriving it from nested player fields.

StateEncoder gets a new encode_with_precomputed_global() path that:
  - Accepts the precomputed float[29] global block from Java
  - Still runs full zone embedding pooling (hand/battlefield/graveyard/
    command_zone) from the players[] zone contents
  - Appends the playstyle one-hot as normal
  - Returns the same 6177-dim tensor the policy network expects

Fallback: if state_vector is absent or wrong length, encode() is called
as before so all existing callers (batch predict, JSONL training) are
unaffected.

Also maps GameSession player field names to encoder field names:
  handCount  -> hand size proxy when hand[] is empty (AI seats)
  battlefield list -> creatures count proxy
  commanderTax -> cmdr_tax
  turnNumber  -> turn
  activePlayer -> active_seat
2026-03-29 04:32:52 -04:00
KoalaTrapLord 126684bb94 feat(bridge): Phase 2.3 — wire GameSession to real Forge IPC subprocess (issue #83)
Replaces synthetic stub loop with a live Forge process:
- Spawns Forge with --policy + --policyServer flags so Forge calls
  back to /api/policy/decide at each AI decision point
- Reads Forge stdout line-by-line and parses zone/phase/life updates
  via the same regex patterns used in BatchRunner
- Syncs full board state (battlefield, hand, graveyard, exile,
  command zone, stack) on every Forge state-change line
- Routes human input from humanActionQueue via a side-channel
  HTTP POST to /api/policy/override on the policy server
- Handles activePlayer + priorityPlayer correctly for 3-4 player pods
- Falls back to synthetic stub loop if Forge subprocess fails to start
- Watchdog thread kills stalled Forge process after 10 min
2026-03-29 04:31:49 -04:00
KoalaTrapLord 882e2370c1 fix(#127): persist built_at in ChromaDB collection metadata as sidecar fallback
- _write_build_meta: after writing build_meta.json, also stamps built_at
  onto the collection via col.modify() (best-effort, swallows exceptions)
- _collection_is_current: if sidecar built_at is missing/zero, falls back
  to collection.metadata["built_at"] so a deleted sidecar doesn't force
  an unnecessary full rebuild when the ChromaDB data is still valid
2026-03-29 04:29:35 -04:00
KoalaTrapLord bbca0b7985 fix(#126): add batch insert retry logic with exponential backoff
- Add BATCH_RETRY_LIMIT and BATCH_RETRY_DELAY constants after DEFAULT_BATCH_SIZE
- Add _insert_batch_with_retry() helper with exponential backoff
- Replace both bare try/except insert blocks in _build_collection_from_bulk
  with calls to the retry helper so transient ChromaDB errors are retried
  up to BATCH_RETRY_LIMIT times before a batch is counted as failed
2026-03-29 04:24:58 -04:00
KoalaTrapLord da8de77704 feat(bridge): add buildStateVector() emitting state_vector float[] in snapshots (#83 Step 1)
Adds a buildStateVector(int seat) method to GameSession that produces
a float[29] global scalar vector matching STATE_DIMS layout in
ml/config/scope.py:

  Per-player block (14 features × 2 players = 28):
    [0]  life_total           / 40.0
    [1]  commander_damage     / 21.0  (stub 0.0 until real Forge tracks it)
    [2]  mana_available       / 20.0  (stub 0.0 until mana pool wired)
    [3]  commander_tax        / 10.0
    [4]  cards_in_hand        / 15.0
    [5]  cards_in_graveyard   / 100.0
    [6]  creatures_on_battlefield / 30.0
    [7]  total_power_on_board / 100.0  (stub = creature count until P/T data)
    [8]  is_active_player     (0 or 1)
    [9]  phase one-hot[0]  main1
    [10] phase one-hot[1]  combat
    [11] phase one-hot[2]  main2
    [12] phase one-hot[3]  end
    [13] (padding / reserved)

  Global:
    [28] turn_number / max_turns (25)

buildStateSnapshot() now includes "state_vector": float[] so that
PolicyClient.decide(JsonObject) sends a machine-readable vector the
Python /api/policy/decide endpoint can pass directly to the encoder
without re-parsing the nested human-readable fields.

Stub fields (mana_available, commander_damage, total_power_on_board)
are zero until real Forge board state is wired in Step 4.
2026-03-29 04:24:55 -04:00
KoalaTrapLord 11990c2fd3 feat(ml): add --data-source forge flag to PPO trainer (issue #83 phase 1.3)
Wires forge_episode_generator into the PPO training loop:
- New --data-source flag: synthetic (default) | forge | mixed
- New --forge-results-dir, --forge-batch-id, --forge-weight flags
- ForgeDataSource class: runs run_forge_pipeline() in background thread,
  drains episodes into RolloutBuffer via episodes_to_buffer()
- Mixed mode: blends Forge episodes + synthetic self-play per iteration
  using --forge-weight ratio (0.0–1.0)
- Synthetic path unchanged — no regression for existing training runs
2026-03-29 03:02:30 -04:00
KoalaTrapLord aefd0cace6 feat(rag): Phase 1 — rewrite build_card_text to natural-language format (#137)
* feat(rag): Phase 1 — rewrite build_card_text to natural-language format

* feat(rag): Phase 2 — Forge-enriched ChromaDB RAG store (2a–2e)

Phase 2a: Forge lazy import guard (_try_import_forge_lookup)
Phase 2b: _build_document appends Forge trigger/replacement/static/keyword tags
Phase 2c: _build_metadata adds has_trigger, has_replacement, has_static, trigger_modes fields
Phase 2d: query_cards extended with require_trigger, require_replacement, require_static params + ChromaDB where clause support
Phase 2e: get_rag_stats reports forge_enriched and forge_coverage; _write_build_meta tracks coverage; build loop counts enriched cards

* feat(rag): Phase 3 — add mtgrules second ChromaDB collection

Adds build_rules_index() and query_rules() operating on a separate
'mtgrules' ChromaDB collection. Chunks MagicCompRules.txt by rule-number
boundaries, embeds each chunk, and exposes semantic search. Completely
independent from the mtg_oracle card collection.

* feat(coach): Phase 4 — behavior-aware RAG + rules context in coach_service

Phase 4a: run_coaching_session uses dual RAG query
  - Primary: commander synergy (n=10)
  - Secondary: require_replacement=True for graveyard/protection cards (n=5)
  - Deduplicates by name before merging
Phase 4b: query_rules() fetches 3 Commander rules chunks to ground the prompt
  - Passed as rules_context kwarg to build_user_prompt()

* feat(rag): Phase 5 — add /api/rag/build-rules and /api/rag/query-rules endpoints
2026-03-29 02:11:38 -04:00
KoalaTrapLord ac536999eb Update lab_api.pyfeat: wire validate_forge_config into lifespan startup 2026-03-28 14:48:43 -04:00
KoalaTrapLord 2e43511d87 feat: add validate_forge_config() startup check
Validates FORGE_DIR at startup with clear warning if cardsfolder not found.
Checks both forge-gui/res/cardsfolder and res/cardsfolder layouts.
2026-03-28 14:34:50 -04:00
KoalaTrapLord 1bebe38e66 fix: forge_card_loader path resolution + reset_forge_cache helper
- _get_cards_folder() now tries both forge-gui/res/cardsfolder and res/cardsfolder
  so FORGE_DIR can point to either the repo root or the forge-gui subdirectory
- Add reset_forge_cache() to allow hot-reloading without server restart
2026-03-28 14:31:42 -04:00
KoalaTrapLord eca1653024 fix: backfill basic lands on card_entries to guarantee 99 cards
The previous backfill operated on seen_names dict which silently
ignored basics already present (dict keys are unique). Now backfills
directly on card_entries list after Scryfall fetch, incrementing
quantity on existing basics or appending new CardEntry objects.
Distributes shortfall evenly across commander color identity basics.
2026-03-28 00:26:42 -04:00
KoalaTrapLord 08f1cb7581 fix(deckgen): backfill basic lands after cross-category dedup to reach 99 cards 2026-03-28 00:10:52 -04:00
KoalaTrapLord 9d9e8675a9 fix: restore missing # on line 27 section banner in rules.py (SyntaxError on import) 2026-03-27 23:36:02 -04:00
KoalaTrapLord 64526b7bee fix: exclude lands from cmc_buckets in _build_deck_report_from_db (matches deck_service behaviour) 2026-03-27 20:38:49 -04:00
KoalaTrapLord e9732d292f fix: expand _land_kw, hoist to module level, guard has_real_data for cmc=0 lands after Forge lookup 2026-03-27 20:36:22 -04:00
KoalaTrapLord fa5349ea7c fix: basic land name fallback in _build_deck_report_from_db when type_line is missing from collection join 2026-03-27 20:32:15 -04:00
KoalaTrapLord 1c491bc2b0 fix: add Land role detection to _detect_card_roles (fixes basics showing as Unknown) 2026-03-27 20:28:35 -04:00
KoalaTrapLord 81fe5108d2 fix: coach_apply rollback on partial write, 4-space reindent; ws_game hoist inline imports 2026-03-27 20:24:55 -04:00
KoalaTrapLord 61fa37dc48 fix: lifespan indent error, get_running_loop, remove double rag init, register policy routes 2026-03-27 20:22:42 -04:00
KoalaTrapLord 0118950939 fix: add anthropic_api_key to Config with env-var default (fixes AttributeError in ASGI mode) 2026-03-27 20:20:45 -04:00
KoalaTrapLord 22cf23b24d fix: correct query_cards kwarg names in coach_service (n= -> n_results=, colors= -> color_identity=) 2026-03-27 20:17:14 -04:00
KoalaTrapLord 0010b0f394 refactor: RAG improvements — shared card_text, Chroma fallback, staleness monitor (fixes #133, #134, #135) (#136)
* refactor: add shared build_card_text() utility

* refactor: unify _build_document via shared card_text + add is_chroma_available/check_staleness (fixes #133, #134, #135)

* refactor: delegate embeddings._build_card_text to shared utility

* feat: add background RAG staleness monitor to lab_api lifespan (fixes #135)
2026-03-27 16:46:33 -04:00
KoalaTrapLord d0cdafa66a feat(rag): Phase 3 — inject RAG cards into coach prompts (#132)
* feat(rag): add RAG card injection to prompt_template.py

* feat(rag): wire RAG query into coach_service.py

* test(rag): add Phase 3 RAG injection tests
2026-03-27 15:20:49 -04:00
KoalaTrapLord bcf558418a fix(rag): squash all RAG/ChromaDB bugs #123–#130
Fixes critical API contract mismatch, batch_size wiring, lifespan init, failure tracking, build_meta persistence, mana_cost in embeddings, pyproject.toml deps, and adds full test suite.

Closes #123, #124, #125, #126, #127, #128, #129, #130
2026-03-27 13:24:41 -04:00
KoalaTrapLord 61ac349445 fix: re-encode requirements.txt as UTF-8 (fixes pip install failure) 2026-03-27 11:14:34 -04:00
KoalaTrapLord 06ce5cc91a fix(rules): add nonbasic land guard before creature fallback (fixes #118) (#122) 2026-03-27 10:22:05 -04:00
KoalaTrapLord fc2e9d88d5 feat(rag): wire ensure_rag_store into main() startup (#121) 2026-03-27 09:13:03 -04:00
KoalaTrapLord 4b0cd34eda feat(rag): Phase 2 — ChromaDB vector index over bulk card DB (#120)
* feat(rag): Phase 2 — ChromaDB vector index over bulk card DB

* feat(rag): add routes/rag.py with build, search, status endpoints

* Update lab_api.pyfeat(rag): register rag_router in lab_api.py, remove old inline rag_status

* Update requirements.txtfeat(rag): add chromadb>=0.5.0 to requirements.txtfeat(rag): add chromadb to requirements.txt
2026-03-27 08:39:02 -04:00
KoalaTrapLord 83502ce726 feat: Phase 3/5 deep analysis + Quick Digest endpoint (issue #108) (#119)
* feat(coach): Phase 3/5 JSON schema + QUICK_DIGEST prompt (issue #108)

- Append removalCoverage, rampQuality, drawEngineProfile, winConditions,
  antiSynergyFlags, podPresence, tempoAssessment, metaMatchups to schema
- Add 8 new RULES for Phase 3/5 analysis
- Add QUICK_DIGEST_SYSTEM_PROMPT constant
- Add build_quick_system_prompt() function

* feat(coach): Phase 3/5 JSON parsing + run_quick_digest() (issue #108)

* feat(routes): add POST /api/coach/decks/{deck_id}/quick endpoint (issue #108)

* feat(coach): Phase 3/5 parsing + run_quick_digest() (issue #108)

* feat(coach): Phase 3/5 parsing + run_quick_digest() (issue #108)

- Import RemovalCoverage, RampQuality, DrawEngineProfile, WinCondition,
  AntiSynergyFlag, PodPresence, TempoAssessment, MetaMatchup
- Add Phase 3/5 blocks to _populate_session_from_json()
- Add run_quick_digest() method

* feat(ui): Top Priority card + Quick/Deep buttons in coach.html (issue #108)

* feat(coach): Phase 3/5 parsing + run_quick_digest() (issue #108)

* feat(ui): wire Quick Digest button + displayTopPriority() (issue #108)
2026-03-27 07:51:01 -04:00
KoalaTrapLord 8df5900ff9 feat(rag): Phase 1 — Scryfall bulk offline card database (SQLite FTS5) (#117)
* feat(rag): add scryfall_bulk.py — offline SQLite FTS5 card database (RAG Phase 1)

* feat(rag): wire ensure_bulk_db() into startup and add /api/rag/status endpoint (RAG Phase 1)

* fix: migrate on_event to lifespan handler, fix /api/rag/status route ordering
2026-03-27 07:16:55 -04:00
KoalaTrapLord 864cefb8d1 feat(coach): Phase 5 - add PodPresence, TempoAssessment, MetaMatchup models + isQuickDigest flag (#108) 2026-03-27 04:22:23 -04:00
KoalaTrapLord 6c03e56794 fix(#116): dual card_records fallback JOIN, card_type/card_category fields, simulator land detection
- deck_service._compute_deck_analysis: add LEFT JOIN card_records with COALESCE on all fields
- deck_service: add _classify_card_category() with oracle-text keyword matching
- deckbuilder.GET /api/decks/{id}/cards: add card_records fallback JOIN + COALESCE, emit card_type/card_category per card
- deepseek._fetch_deck_card_data: add card_records fallback JOIN so lands get correct type_line before enrich_card()

Closes #116
2026-03-27 04:10:17 -04:00
KoalaTrapLord 4b92a5f364 Update models.pyfeat(coach): Phase 3 - add RemovalCoverage, RampQuality, DrawEngineProfile, WinCondition, AntiSynergyFlag models (#108) 2026-03-27 04:01:25 -04:00
KoalaTrapLord da0193eb11 Update coach_service.pyfeat(coach): Phase 2 - parse upgradePriority, commanderDependency, mulliganAnalysis in coach_service (#108)feat(coach): Phase 2 - parse new fields in coach_service (#108) 2026-03-27 03:45:32 -04:00
KoalaTrapLord 71e0db4985 feat(coach): Phase 2 - add upgradePriority, commanderDependency, mulliganAnalysis to prompt schema (#108) 2026-03-27 03:32:03 -04:00
KoalaTrapLord 825946ae75 fix(deckgen): Phase 1 - non-destructive smart substitution + 99-card guarantee (#111)
- Add SubstitutionMethod enum to track how each card was resolved
- smart_substitute() now always returns a valid card string (never None/drops)
  fallback chain: exact -> fuzzy -> functional analog -> original kept -> basic land
- apply_substitutions() loop no longer silently drops cards on failure
- _pad_to_99() circuit breaker added before BuildResult validation
- Pre-validation count logging per category for fast regression diagnosis
- Startup assertion: sum(SLOT_BUDGET) == 99 raises on app boot if misconfigured
- Prompt injection: slot budget + exact card count requirement sent to LLM
- run_substitution() anthropic fallback failure now pads with basic land instead of leaving slot missing
- SubstitutionBatchResult gains padded_slots field (> 0 = circuit breaker fired)
2026-03-27 03:27:52 -04:00
KoalaTrapLord bd4df0c263 Update models.pyfeat(coach): Phase 2 - add UpgradePriority, CommanderDependency, MulliganAnalysis models (#108) 2026-03-27 02:52:01 -04:00
KoalaTrapLord 113b5c63f0 docs: rewrite README to reflect full current feature set (#115) 2026-03-27 02:51:12 -04:00
KoalaTrapLord 3f6d71ecbc fix(coach): switch all Anthropic calls to streaming to prevent 10-min timeout error (closes #109)
- anthropic_client.py: all 4 methods (chat_structured, chat_plain, achat_structured, achat_plain) now use .messages.stream() context manager
- coach_service.py: inline aclient.messages.create() replaced with async streaming via aclient.messages.stream()
- _parse_response() updated to accept pre-extracted text string (since stream gives us text directly)
- check_status() left as non-streaming (max_tokens=10, safe from the 10min rule)
2026-03-27 02:36:22 -04:00
KoalaTrapLord 4246555228 feat: auto-run update_weights.py after overnight sims (#114)
Completes the sim → weights → deck builder feedback loop.

- Add run_update_weights() to overnight-run.py
- Add auto_update_weights config key (default True)
- Add --no-update-weights, --weights-min-games, --weights-lr CLI flags
- Runs before neural net training so both pipelines get fresh data
2026-03-27 02:35:08 -04:00
KoalaTrapLord 8dd9623417 feat: inject simulation-learned weights into deck builder LLM prompts (#113)
Closes #112

- Add sim_insights.py to translate learned_weights.json into LLM prompt text
- Add sim_context param to suggest_cards() and enforce_deck_ratios() in ollama_client.py
- Wire get_sim_insights() into build_deck pipeline (Steps 4 & 6)
- Fully backwards compatible — no-op until real sim data exists
2026-03-27 02:27:42 -04:00
KoalaTrapLord 817b71f69d feat(ui): add tip-icon tooltips to distillation, simulator, coach, deckgenerator pages
- distillation.html: tooltips on all 10 params + ELO Ratings header
- simulator.html: tooltips on Opponent, Games, Record logs, API Endpoint, Model, Temp, Timeout
- coach.html: tooltips on Power Level, Meta Focus, Budget, Focus Areas, Similar Cards Results
- deckgenerator.html: tooltips on Target Bracket, Use my collection, Smart Substitution, AI Model
- All pages now share tooltip styles from global.css (no per-page duplication needed)
2026-03-27 02:06:58 -04:00
KoalaTrapLord e71cac9dd1 feat(ui): add tooltips to all pages — distillation, simulator, coach, deckgenerator
- Move .tip-icon tooltip CSS from training.css into global.css so all pages share it
- distillation.html: 8 param tooltips (Max Generations, Convergence Window, Supervised Epochs, Supervised LR, PPO Iterations, PPO Episodes/Iter, PPO LR, Min Win Rate Gate, Opponent, Playstyle) + ELO Ratings header
- simulator.html: tooltips on Opponent, Games, Record turn logs, API Endpoint, Model, Temp, Timeout
- coach.html: tooltips on Target Power Level, Meta Focus, Budget, Focus Areas, Find Similar Cards Results
- deckgenerator.html: tooltips on Target Bracket, Use my collection, Smart Substitution, AI Model
2026-03-27 01:57:37 -04:00
KoalaTrapLord cb38081a8b fix: add top_n cutoff to ELO tournament (default top 10 generations) 2026-03-27 01:48:10 -04:00
KoalaTrapLord cd9d2eebd8 feat(coach): Phase 1 - pass richer card stats to Claude prompt (#108)
- _format_underperformers: add synergyScore, clunkinessScore, avgTurnCast, keptInOpeningHandRate
- _format_top_cards: add synergyScore and avgTurnCast
- _format_matchups: append perArchetypeWinRates block from DeckMeta
- USER_PROMPT_TEMPLATE: add ### Archetype Win Rates section placeholder
2026-03-27 01:32:51 -04:00
KoalaTrapLord e6f18d4a61 feat(training): add intro banner and parameter tooltips to ML Training page 2026-03-27 00:18:21 -04:00
KoalaTrapLord 4e90337f54 feat: extend card hover preview to all card names in coach UI 2026-03-26 23:48:27 -04:00
KoalaTrapLord a01a70f725 Use DEFAULT_MAX_TOKENS config for Anthropic max_tokens 2026-03-26 22:50:10 -04:00
KoalaTrapLord 4da58681a4 Enhance coach prompt for more detailed reports 2026-03-26 22:41:23 -04:00
KoalaTrapLord 224b4b5d5b feat: increase coach detail limits (MAX_UNDERPERFORMERS=8, MAX_TOKENS=16384) 2026-03-26 22:33:18 -04:00
KoalaTrapLord 1a34f00574 Update .env.examplefix: correct model ID in .env.example (claude-opus-4-0 -> claude-opus-4) 2026-03-26 15:50:53 -04:00
KoalaTrapLord 8ec7db1612 fix: correct malformed string literals in config.py model defaults 2026-03-26 15:42:34 -04:00
KoalaTrapLord e0a75e2d04 fix: correct Anthropic model ID from claude-opus-4-0 to claude-opus-4 2026-03-26 15:36:23 -04:00
KoalaTrapLord 15bd6f42bb fix: resolve all ruff linting errors across coach module 2026-03-26 15:30:27 -04:00
KoalaTrapLord 0aa57ab7d2 fix: remove duplicate resp token on line 284 in run_coaching_session (#105) 2026-03-26 14:23:52 -04:00
KoalaTrapLord 9f5eb4a99f fix: add missing resp= assignment on line 284 (#105) 2026-03-26 14:11:35 -04:00
KoalaTrapLord a9f18f53da fix: correct indentation of try/except/else block in run_coaching_session (#105) 2026-03-26 14:06:27 -04:00
KoalaTrapLord c9e932ef43 fix: correct try: block indentation in run_coaching_session (#105) 2026-03-26 13:44:33 -04:00
KoalaTrapLord cb99388e3c fix: correct indentation of total_steps in collect_rollouts 2026-03-26 13:14:34 -04:00
KoalaTrapLord 2e316548e7 fix: repair resp= assignment and _re.sub typo in coach_service.py (#105) 2026-03-26 13:11:55 -04:00
KoalaTrapLord b46e1e0877 fix: remove stray string literal in init_coach_service (#105) 2026-03-26 12:31:52 -04:00
KoalaTrapLord e17b877dcc fix: repair syntax errors in coach_status and init_coach_service (#105) 2026-03-26 12:18:07 -04:00
KoalaTrapLord 6ce21908d3 Update coach.py 2026-03-26 11:53:39 -04:00
KoalaTrapLord 15495483f3 fix: remove _coach_init_error reference from status endpoint (#105) 2026-03-26 11:36:33 -04:00
KoalaTrapLord 540fbbf1b3 fix: remove broken _coach_init_error lines from coach.py (#105) 2026-03-26 10:52:41 -04:00
KoalaTrapLord a5cc7736b4 feat: update thread dropdown to 1,2,4,8,16,24,32 2026-03-25 23:15:38 -04:00
KoalaTrapLord 8c6248cf84 feat: increase max threads from 16 to 32 2026-03-25 21:37:21 -04:00
KoalaTrapLord 22d63fd15e Update coach.py 2026-03-25 21:29:24 -04:00
KoalaTrapLord 2dc0ba2d25 Update coach.py 2026-03-25 21:18:27 -04:00
KoalaTrapLord 8f180820f9 fix: add missing _DEFAULT_ORIGINS definition in lab_api.py (#105) 2026-03-25 21:00:34 -04:00
KoalaTrapLord b283418879 fix: correct indentation in _call_anthropic_api else-block (#105) 2026-03-25 20:43:11 -04:00
KoalaTrapLord 61be42ea6d fix: resolve IndentationError on line 882 in routes/deckgen.py (#105) 2026-03-25 20:18:19 -04:00
KoalaTrapLord 3906b93c6a feat: add WebSocket support for Unity game state streaming (#107)
* feat: add ws/ package for WebSocket game state streaming

* feat: add WebSocket ConnectionManager with per-game broadcasting

* feat: add GameStateTracker with deep-diff delta engine

* feat: add WebSocket route with delta push and REST bridge endpoints

* feat: register ws_game_router in lab_api.py
2026-03-25 19:51:15 -04:00