diff --git a/solitaire_server/e2e/scripts/cycle_metrics.js b/solitaire_server/e2e/scripts/cycle_metrics.js index 71149a5..40d2156 100644 --- a/solitaire_server/e2e/scripts/cycle_metrics.js +++ b/solitaire_server/e2e/scripts/cycle_metrics.js @@ -142,10 +142,32 @@ async function main() { const page = await context.newPage(); const results = []; + // Load the page once, then reset each game in place via the bridge's + // newGame(). A fresh page.goto() per game (hundreds of navigations in a + // single browser context) accumulates resources and eventually makes + // waitForFunction time out around game ~100. One load stays fast. + await page.goto(`${baseUrl}/${route}`, { waitUntil: "domcontentloaded" }); + if (route === "play-classic") { + const resumeVisible = await page + .locator("#resume-overlay:not(.hidden)") + .isVisible() + .catch(() => false); + if (resumeVisible) { + await page.evaluate(() => localStorage.removeItem("fs_game_save")); + await page.reload({ waitUntil: "domcontentloaded" }); + } + } + await page.waitForFunction( + () => + typeof window.__FERROUS_DEBUG__ === "object" && + typeof window.__FERROUS_DEBUG__.newGame === "function", + null, + { timeout: 30_000 } + ); + for (let i = 0; i < games; i++) { const seed = i; const draw3 = i % 2 === 1; - const suffix = draw3 ? "&draw3=" : ""; const pageErrors = []; const consoleErrors = []; @@ -158,27 +180,10 @@ async function main() { } }); - await page.goto(`${baseUrl}/${route}?seed=${seed}${suffix}`, { - waitUntil: "domcontentloaded", - }); - - if (route === "play-classic") { - const resumeVisible = await page - .locator("#resume-overlay:not(.hidden)") - .isVisible() - .catch(() => false); - if (resumeVisible) { - await page.evaluate(() => localStorage.removeItem("fs_game_save")); - await page.reload({ waitUntil: "domcontentloaded" }); - } - } - - await page.waitForFunction( - () => - typeof window.__FERROUS_DEBUG__ === "object" && - window.__FERROUS_DEBUG__.seed() !== null, - null, - { timeout: 30_000 } + // Reset to a fresh seeded game without navigating. + await page.evaluate( + ({ seed, draw3 }) => window.__FERROUS_DEBUG__.newGame(seed, draw3), + { seed, draw3 } ); const run = await page.evaluate(({ stepCap, policyName, maxVisits }) => { diff --git a/solitaire_server/web/game.js b/solitaire_server/web/game.js index 7270052..9a24aee 100644 --- a/solitaire_server/web/game.js +++ b/solitaire_server/web/game.js @@ -994,6 +994,14 @@ window.__FERROUS_DEBUG__ = { serialize() { return game ? game.serialize() : null; }, + // Reset to a fresh seeded game in place (no page reload). Lets the cycle + // regression harness reuse one page across hundreds of games instead of + // navigating per game. + newGame(seed, drawThreeMode) { + drawThree = !!drawThreeMode; + startGame(seed ?? randomSeed()); + return game ? game.state() : null; + }, applyLegalMove(index) { if (!game) return { ok: false, error: "game_not_ready" }; const result = game.debug_apply_legal_move(index);