fix: deck loading — wire startGameWithDecks, fetch .dck content, parse [sections]

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.
This commit is contained in:
Commander AI Lab committed 2026-04-03 13:45:15 +00:00
1 parent fa58463cde
commit 7750b43494
3 files changed
+90 -14

No files matched your search

+45 -4
View File
@@ -10877,16 +10877,20 @@ function parseDeckList(text) {
let line = raw.trim();
if (!line) continue;
// Section headers
if (/^\/\/\s*/i.test(line) || /^(COMMANDER|COMPANION|SIDEBOARD|MAYBEBOARD|MAINBOARD|DECK|LAND)[:\s]*$/i.test(line)) {
const cleaned = line.replace(/^\/\/\s*/, '').replace(/:$/, '').trim().toLowerCase();
// Section headers — supports "// Commander", "Commander:", "[Commander]", "[Main]", "[metadata]"
if (/^\/\/\s*/i.test(line) || /^\[?(COMMANDER|COMPANION|SIDEBOARD|MAYBEBOARD|MAINBOARD|MAIN|DECK|LAND|METADATA)\]?[:\s]*$/i.test(line)) {
const cleaned = line.replace(/^\/\/\s*/, '').replace(/[\[\]:]/g, '').trim().toLowerCase();
if (/commander/i.test(cleaned)) currentSection = 'commander';
else if (/companion/i.test(cleaned)) currentSection = 'companion';
else if (/sideboard/i.test(cleaned)) currentSection = 'sideboard';
else if (/metadata/i.test(cleaned)) currentSection = 'metadata';
else currentSection = 'mainboard';
continue;
}
// Skip metadata lines like "Name=Elven Empire"
if (currentSection === 'metadata') continue;
// Skip comment lines
if (/^#/.test(line) || /^\*/.test(line)) continue;
@@ -12719,7 +12723,44 @@ async function loadPreconForPlayer(playerIdx, preconId) {
var player = gameState.players[playerIdx];
if (!player) return;
var parsedCards = parseDeckList(precon.decklist);
// Try to get decklist text: embedded first, then fetch from backend .dck file
var decklistText = precon.decklist || '';
if (!decklistText && precon.fileName) {
// Fetch .dck file from backend if available
if (typeof AIBridge !== 'undefined' && AIBridge.connector.connected) {
try {
var deckData = await AIBridge.deckSource.fetchPreconDeck(precon.fileName);
if (deckData && deckData.decklist) {
decklistText = deckData.decklist;
} else if (deckData && typeof deckData === 'string') {
decklistText = deckData;
}
} catch (e) {
console.warn('[loadPreconForPlayer] Backend deck fetch failed:', e);
}
}
// Fallback: try to fetch .dck file directly from precon-decks/ (same origin)
if (!decklistText) {
try {
var resp = await fetch('precon-decks/' + precon.fileName);
if (resp.ok) {
decklistText = await resp.text();
}
} catch (e) {
// Fallback: try relative to backend
try {
var resp2 = await fetch('/precon-decks/' + precon.fileName);
if (resp2.ok) {
decklistText = await resp2.text();
}
} catch (e2) {
console.warn('[loadPreconForPlayer] Could not fetch .dck file:', precon.fileName);
}
}
}
}
var parsedCards = parseDeckList(decklistText);
if (!parsedCards.length) return;
var allCardNames = [];