From 99843135c95d84e25bcad7bab0e1c4051afa3b4c Mon Sep 17 00:00:00 2001 From: Wickedness Date: Sun, 28 Jun 2026 13:24:28 +0900 Subject: [PATCH] Polish release candidate QA flow --- package.json | 1 + scripts/verify-flow.mjs | 366 ++++++----------------- scripts/verify-release-candidate.mjs | 418 +++++++++++++++++++++++++++ src/game/scenes/BattleScene.ts | 45 ++- src/game/scenes/CampScene.ts | 6 +- vite.config.ts | 19 +- 6 files changed, 554 insertions(+), 301 deletions(-) create mode 100644 scripts/verify-release-candidate.mjs diff --git a/package.json b/package.json index cd7f79a..df2c948 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "measure:performance": "node scripts/measure-performance.mjs", "verify:flow": "node scripts/verify-flow.mjs", "verify:save-flow": "node scripts/verify-save-retry-flow.mjs", + "verify:release": "node scripts/verify-release-candidate.mjs", "qa:representative": "node scripts/qa-representative-battles.mjs" }, "dependencies": { diff --git a/scripts/verify-flow.mjs b/scripts/verify-flow.mjs index be3a070..be26839 100644 --- a/scripts/verify-flow.mjs +++ b/scripts/verify-flow.mjs @@ -404,10 +404,7 @@ try { await page.screenshot({ path: 'dist/verification-battle-result-victory.png', fullPage: true }); await page.mouse.click(738, 642); - await page.waitForFunction(() => { - const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - return activeScenes.includes('CampScene'); - }); + await waitForCampReady(page); await page.screenshot({ path: 'dist/verification-camp.png', fullPage: true }); const campState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); @@ -561,10 +558,7 @@ try { }); await page.mouse.click(738, 642); - await page.waitForFunction(() => { - const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - return activeScenes.includes('CampScene'); - }); + await waitForCampReady(page); const campaignSaveAfterSecondBattle = await page.evaluate(() => { const raw = window.localStorage.getItem('heros-web:campaign-state'); @@ -631,10 +625,7 @@ try { }); await page.mouse.click(738, 642); - await page.waitForFunction(() => { - const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - return activeScenes.includes('CampScene'); - }); + await waitForCampReady(page); const campaignSaveAfterThirdBattle = await page.evaluate(() => { const raw = window.localStorage.getItem('heros-web:campaign-state'); @@ -700,10 +691,7 @@ try { }); await page.mouse.click(738, 642); - await page.waitForFunction(() => { - const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - return activeScenes.includes('CampScene'); - }); + await waitForCampReady(page); const campaignSaveAfterFourthBattle = await page.evaluate(() => { const raw = window.localStorage.getItem('heros-web:campaign-state'); @@ -769,10 +757,7 @@ try { }); await page.mouse.click(738, 642); - await page.waitForFunction(() => { - const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - return activeScenes.includes('CampScene'); - }); + await waitForCampReady(page); const campaignSaveAfterFifthBattle = await page.evaluate(() => { const raw = window.localStorage.getItem('heros-web:campaign-state'); @@ -848,10 +833,7 @@ try { }); await page.mouse.click(738, 642); - await page.waitForFunction(() => { - const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - return activeScenes.includes('CampScene'); - }); + await waitForCampReady(page); const campaignSaveAfterSixthBattle = await page.evaluate(() => { const raw = window.localStorage.getItem('heros-web:campaign-state'); @@ -947,10 +929,7 @@ try { }); await page.mouse.click(738, 642); - await page.waitForFunction(() => { - const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - return activeScenes.includes('CampScene'); - }); + await waitForCampReady(page); const campaignSaveAfterSeventhBattle = await page.evaluate(() => { const raw = window.localStorage.getItem('heros-web:campaign-state'); @@ -1040,10 +1019,7 @@ try { }); await page.mouse.click(738, 642); - await page.waitForFunction(() => { - const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - return activeScenes.includes('CampScene'); - }); + await waitForCampReady(page); const eighthCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); if ( @@ -1121,10 +1097,7 @@ try { }); await page.mouse.click(738, 642); - await page.waitForFunction(() => { - const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - return activeScenes.includes('CampScene'); - }); + await waitForCampReady(page); const ninthCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); if ( @@ -1202,10 +1175,7 @@ try { }); await page.mouse.click(738, 642); - await page.waitForFunction(() => { - const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - return activeScenes.includes('CampScene'); - }); + await waitForCampReady(page); const tenthCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); if ( @@ -1276,10 +1246,7 @@ try { }); await page.mouse.click(738, 642); - await page.waitForFunction(() => { - const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - return activeScenes.includes('CampScene'); - }); + await waitForCampReady(page); const eleventhCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); if ( @@ -1351,10 +1318,7 @@ try { }); await page.mouse.click(738, 642); - await page.waitForFunction(() => { - const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - return activeScenes.includes('CampScene'); - }); + await waitForCampReady(page); const twelfthCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); if ( @@ -1435,10 +1399,7 @@ try { }); await page.mouse.click(738, 642); - await page.waitForFunction(() => { - const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - return activeScenes.includes('CampScene'); - }); + await waitForCampReady(page); const thirteenthCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); if ( @@ -1518,10 +1479,7 @@ try { }); await page.mouse.click(738, 642); - await page.waitForFunction(() => { - const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - return activeScenes.includes('CampScene'); - }); + await waitForCampReady(page); const fourteenthCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); if ( @@ -1593,10 +1551,7 @@ try { }); await page.mouse.click(738, 642); - await page.waitForFunction(() => { - const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - return activeScenes.includes('CampScene'); - }); + await waitForCampReady(page); const fifteenthCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); if ( @@ -1695,10 +1650,7 @@ try { }); await page.mouse.click(738, 642); - await page.waitForFunction(() => { - const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - return activeScenes.includes('CampScene'); - }); + await waitForCampReady(page); const sixteenthCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); if ( @@ -1806,10 +1758,7 @@ try { }); await page.mouse.click(738, 642); - await page.waitForFunction(() => { - const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - return activeScenes.includes('CampScene'); - }); + await waitForCampReady(page); const seventeenthCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); if ( @@ -1934,10 +1883,7 @@ try { }); await page.mouse.click(738, 642); - await page.waitForFunction(() => { - const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - return activeScenes.includes('CampScene'); - }); + await waitForCampReady(page); const eighteenthCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); if ( @@ -2087,10 +2033,7 @@ try { }); await page.mouse.click(738, 642); - await page.waitForFunction(() => { - const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - return activeScenes.includes('CampScene'); - }); + await waitForCampReady(page); const nineteenthCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); if ( @@ -2222,10 +2165,7 @@ try { }); await page.mouse.click(738, 642); - await page.waitForFunction(() => { - const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - return activeScenes.includes('CampScene'); - }); + await waitForCampReady(page); const twentiethCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); if ( @@ -2359,10 +2299,7 @@ try { }); await page.mouse.click(738, 642); - await page.waitForFunction(() => { - const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - return activeScenes.includes('CampScene'); - }); + await waitForCampReady(page); const twentyFirstCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); if ( @@ -2496,10 +2433,7 @@ try { }); await page.mouse.click(738, 642); - await page.waitForFunction(() => { - const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - return activeScenes.includes('CampScene'); - }); + await waitForCampReady(page); const twentySecondCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); if ( @@ -2636,10 +2570,7 @@ try { }); await page.mouse.click(738, 642); - await page.waitForFunction(() => { - const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - return activeScenes.includes('CampScene'); - }); + await waitForCampReady(page); const twentyThirdCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); if ( @@ -2777,10 +2708,7 @@ try { }); await page.mouse.click(738, 642); - await page.waitForFunction(() => { - const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - return activeScenes.includes('CampScene'); - }); + await waitForCampReady(page); const twentyFourthCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); if ( @@ -2919,10 +2847,7 @@ try { }); await page.mouse.click(738, 642); - await page.waitForFunction(() => { - const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - return activeScenes.includes('CampScene'); - }); + await waitForCampReady(page); const twentyFifthCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); if ( @@ -3067,10 +2992,7 @@ try { }); await page.mouse.click(738, 642); - await page.waitForFunction(() => { - const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - return activeScenes.includes('CampScene'); - }); + await waitForCampReady(page); const twentySixthCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); if ( @@ -3211,10 +3133,7 @@ try { }); await page.mouse.click(738, 642); - await page.waitForFunction(() => { - const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - return activeScenes.includes('CampScene'); - }); + await waitForCampReady(page); const twentySeventhCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); if ( @@ -3355,10 +3274,7 @@ try { }); await page.mouse.click(738, 642); - await page.waitForFunction(() => { - const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - return activeScenes.includes('CampScene'); - }); + await waitForCampReady(page); const twentyEighthCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); if ( @@ -3505,10 +3421,7 @@ try { }); await page.mouse.click(738, 642); - await page.waitForFunction(() => { - const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - return activeScenes.includes('CampScene'); - }); + await waitForCampReady(page); const twentyNinthCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); if ( @@ -3649,10 +3562,7 @@ try { }); await page.mouse.click(738, 642); - await page.waitForFunction(() => { - const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - return activeScenes.includes('CampScene'); - }); + await waitForCampReady(page); const thirtiethCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); if ( @@ -3802,10 +3712,7 @@ try { }); await page.mouse.click(738, 642); - await page.waitForFunction(() => { - const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - return activeScenes.includes('CampScene'); - }); + await waitForCampReady(page); const thirtyFirstCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); if ( @@ -3952,10 +3859,7 @@ try { }); await page.mouse.click(738, 642); - await page.waitForFunction(() => { - const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - return activeScenes.includes('CampScene'); - }); + await waitForCampReady(page); const thirtySecondCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); if ( @@ -4103,10 +4007,7 @@ try { }); await page.mouse.click(738, 642); - await page.waitForFunction(() => { - const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - return activeScenes.includes('CampScene'); - }); + await waitForCampReady(page); const thirtyThirdCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); if ( @@ -4259,10 +4160,7 @@ try { }); await page.mouse.click(738, 642); - await page.waitForFunction(() => { - const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - return activeScenes.includes('CampScene'); - }); + await waitForCampReady(page); const thirtyFourthCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); if ( @@ -4412,10 +4310,7 @@ try { }); await page.mouse.click(738, 642); - await page.waitForFunction(() => { - const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - return activeScenes.includes('CampScene'); - }); + await waitForCampReady(page); const thirtyFifthCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); if ( @@ -4571,10 +4466,7 @@ try { }); await page.mouse.click(738, 642); - await page.waitForFunction(() => { - const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - return activeScenes.includes('CampScene'); - }); + await waitForCampReady(page); const thirtySixthCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); if ( @@ -4722,10 +4614,7 @@ try { }); await page.mouse.click(738, 642); - await page.waitForFunction(() => { - const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - return activeScenes.includes('CampScene'); - }); + await waitForCampReady(page); const thirtySeventhCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); if ( @@ -4782,10 +4671,7 @@ try { await page.keyboard.press('Space'); await page.waitForTimeout(320); } - await page.waitForFunction(() => { - const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - return activeScenes.includes('CampScene') && !activeScenes.includes('StoryScene'); - }); + await waitForCampReady(page, { withoutStory: true }); const hanzhongKingCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); if ( @@ -4839,10 +4725,7 @@ try { await page.keyboard.press('Space'); await page.waitForTimeout(320); } - await page.waitForFunction(() => { - const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - return activeScenes.includes('CampScene') && !activeScenes.includes('StoryScene'); - }); + await waitForCampReady(page, { withoutStory: true }); const shuHanFoundationCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); if ( @@ -4986,10 +4869,7 @@ try { }); await page.mouse.click(738, 642); - await page.waitForFunction(() => { - const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - return activeScenes.includes('CampScene'); - }); + await waitForCampReady(page); const thirtyEighthCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); if ( @@ -5136,10 +5016,7 @@ try { }); await page.mouse.click(738, 642); - await page.waitForFunction(() => { - const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - return activeScenes.includes('CampScene'); - }); + await waitForCampReady(page); const thirtyNinthCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); if ( @@ -5286,10 +5163,7 @@ try { }); await page.mouse.click(738, 642); - await page.waitForFunction(() => { - const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - return activeScenes.includes('CampScene'); - }); + await waitForCampReady(page); const fortiethCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); if ( @@ -5437,10 +5311,7 @@ try { }); await page.mouse.click(738, 642); - await page.waitForFunction(() => { - const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - return activeScenes.includes('CampScene'); - }); + await waitForCampReady(page); const fortyFirstCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); if ( @@ -5592,10 +5463,7 @@ try { }); await page.mouse.click(738, 642); - await page.waitForFunction(() => { - const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - return activeScenes.includes('CampScene'); - }); + await waitForCampReady(page); const fortySecondCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); if ( @@ -5748,10 +5616,7 @@ try { }); await page.mouse.click(738, 642); - await page.waitForFunction(() => { - const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - return activeScenes.includes('CampScene'); - }); + await waitForCampReady(page); const fortyThirdCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); if ( @@ -5903,10 +5768,7 @@ try { }); await page.mouse.click(738, 642); - await page.waitForFunction(() => { - const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - return activeScenes.includes('CampScene'); - }); + await waitForCampReady(page); const fortyFourthCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); if ( @@ -6057,10 +5919,7 @@ try { }); await page.mouse.click(738, 642); - await page.waitForFunction(() => { - const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - return activeScenes.includes('CampScene'); - }); + await waitForCampReady(page); const fortyFifthCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); if ( @@ -6212,10 +6071,7 @@ try { }); await page.mouse.click(738, 642); - await page.waitForFunction(() => { - const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - return activeScenes.includes('CampScene'); - }); + await waitForCampReady(page); const fortySixthCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); if ( @@ -6273,11 +6129,7 @@ try { await page.keyboard.press('Space'); await page.waitForTimeout(320); } - await page.waitForFunction(() => { - const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - const camp = window.__HEROS_DEBUG__?.camp?.(); - return activeScenes.includes('CampScene') && camp?.campaign?.step === 'baidi-entrustment-camp'; - }); + await waitForCampReady(page, { step: 'baidi-entrustment-camp' }); const baidiCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); if ( @@ -6433,10 +6285,7 @@ try { }); await page.mouse.click(738, 642); - await page.waitForFunction(() => { - const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - return activeScenes.includes('CampScene'); - }); + await waitForCampReady(page); const fortySeventhCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); if ( @@ -6592,10 +6441,7 @@ try { }); await page.mouse.click(738, 642); - await page.waitForFunction(() => { - const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - return activeScenes.includes('CampScene'); - }); + await waitForCampReady(page); const fortyEighthCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); if ( @@ -6751,10 +6597,7 @@ try { }); await page.mouse.click(738, 642); - await page.waitForFunction(() => { - const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - return activeScenes.includes('CampScene'); - }); + await waitForCampReady(page); const fortyNinthCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); if ( @@ -6913,10 +6756,7 @@ try { }); await page.mouse.click(738, 642); - await page.waitForFunction(() => { - const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - return activeScenes.includes('CampScene'); - }); + await waitForCampReady(page); const fiftiethCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); if ( @@ -7076,10 +6916,7 @@ try { }); await page.mouse.click(738, 642); - await page.waitForFunction(() => { - const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - return activeScenes.includes('CampScene'); - }); + await waitForCampReady(page); const fiftyFirstCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); if ( @@ -7239,10 +7076,7 @@ try { }); await page.mouse.click(738, 642); - await page.waitForFunction(() => { - const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - return activeScenes.includes('CampScene'); - }); + await waitForCampReady(page); const fiftySecondCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); if ( @@ -7401,10 +7235,7 @@ try { }); await page.mouse.click(738, 642); - await page.waitForFunction(() => { - const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - return activeScenes.includes('CampScene'); - }); + await waitForCampReady(page); const fiftyThirdCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); if ( @@ -7563,10 +7394,7 @@ try { }); await page.mouse.click(738, 642); - await page.waitForFunction(() => { - const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - return activeScenes.includes('CampScene'); - }); + await waitForCampReady(page); const fiftyFourthCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); if ( @@ -7633,10 +7461,7 @@ try { await page.keyboard.press('Space'); await page.waitForTimeout(320); } - await page.waitForFunction(() => { - const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - return activeScenes.includes('CampScene') && !activeScenes.includes('StoryScene'); - }); + await waitForCampReady(page, { withoutStory: true }); const northernPrepCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); if ( @@ -7780,10 +7605,7 @@ try { }); await page.mouse.click(738, 642); - await page.waitForFunction(() => { - const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - return activeScenes.includes('CampScene'); - }); + await waitForCampReady(page); const fiftyFifthCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); if ( @@ -7927,10 +7749,7 @@ try { }); await page.mouse.click(738, 642); - await page.waitForFunction(() => { - const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - return activeScenes.includes('CampScene'); - }); + await waitForCampReady(page); const fiftySixthCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); if ( @@ -8078,10 +7897,7 @@ try { }); await page.mouse.click(738, 642); - await page.waitForFunction(() => { - const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - return activeScenes.includes('CampScene'); - }); + await waitForCampReady(page); const fiftySeventhCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); if ( @@ -8226,10 +8042,7 @@ try { }); await page.mouse.click(738, 642); - await page.waitForFunction(() => { - const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - return activeScenes.includes('CampScene'); - }); + await waitForCampReady(page); const fiftyEighthCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); if ( @@ -8374,10 +8187,7 @@ try { }); await page.mouse.click(738, 642); - await page.waitForFunction(() => { - const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - return activeScenes.includes('CampScene'); - }); + await waitForCampReady(page); const fiftyNinthCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); if ( @@ -8528,10 +8338,7 @@ try { }); await page.mouse.click(738, 642); - await page.waitForFunction(() => { - const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - return activeScenes.includes('CampScene'); - }); + await waitForCampReady(page); const sixtiethCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); if ( @@ -8685,10 +8492,7 @@ try { }); await page.mouse.click(738, 642); - await page.waitForFunction(() => { - const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - return activeScenes.includes('CampScene'); - }); + await waitForCampReady(page); const sixtyFirstCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); if ( @@ -8845,10 +8649,7 @@ try { }); await page.mouse.click(738, 642); - await page.waitForFunction(() => { - const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - return activeScenes.includes('CampScene'); - }); + await waitForCampReady(page); const sixtySecondCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); if ( @@ -9028,10 +8829,7 @@ try { }); await page.mouse.click(738, 642); - await page.waitForFunction(() => { - const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - return activeScenes.includes('CampScene'); - }); + await waitForCampReady(page); const sixtyThirdCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); if ( @@ -9213,10 +9011,7 @@ try { }); await page.mouse.click(738, 642); - await page.waitForFunction(() => { - const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - return activeScenes.includes('CampScene'); - }); + await waitForCampReady(page); const sixtyFourthCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); if ( @@ -9410,10 +9205,7 @@ try { }); await page.mouse.click(738, 642); - await page.waitForFunction(() => { - const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - return activeScenes.includes('CampScene'); - }); + await waitForCampReady(page); const sixtyFifthCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); if ( @@ -9541,10 +9333,7 @@ try { }); await page.mouse.click(738, 642); - await page.waitForFunction(() => { - const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - return activeScenes.includes('CampScene'); - }); + await waitForCampReady(page); const sixtySixthCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); if ( @@ -9766,6 +9555,21 @@ async function waitForStoryReady(page) { }, undefined, { timeout: 90000 }); } +async function waitForCampReady(page, options = {}) { + await page.waitForFunction((expected) => { + const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; + const campScene = window.__HEROS_DEBUG__?.scene('CampScene'); + const campState = window.__HEROS_DEBUG__?.camp(); + return ( + activeScenes.includes('CampScene') && + (!expected.withoutStory || !activeScenes.includes('StoryScene')) && + campState?.scene === 'CampScene' && + (campScene?.contentObjects?.length ?? 0) > 0 && + (!expected.step || campState?.campaign?.step === expected.step) + ); + }, options, { timeout: 90000 }); +} + function delay(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } diff --git a/scripts/verify-release-candidate.mjs b/scripts/verify-release-candidate.mjs new file mode 100644 index 0000000..34a1850 --- /dev/null +++ b/scripts/verify-release-candidate.mjs @@ -0,0 +1,418 @@ +import { spawn } from 'node:child_process'; +import { mkdirSync } from 'node:fs'; +import { chromium } from 'playwright'; + +const targetUrl = withDebugParam(process.env.RELEASE_QA_URL ?? 'http://127.0.0.1:4173/'); +const screenshotDir = 'dist'; + +let serverProcess; +let browser; + +try { + mkdirSync(screenshotDir, { recursive: true }); + serverProcess = await ensurePreviewServer(targetUrl); + + browser = await chromium.launch({ headless: true }); + const page = await browser.newPage({ viewport: { width: 1280, height: 720 } }); + const consoleMessages = []; + page.on('console', (message) => { + consoleMessages.push({ type: message.type(), text: message.text() }); + }); + + await page.goto(targetUrl, { waitUntil: 'domcontentloaded' }); + await page.evaluate(() => window.localStorage.clear()); + await page.reload({ waitUntil: 'domcontentloaded' }); + await waitForTitle(page); + await page.screenshot({ path: `${screenshotDir}/rc-title-first-run.png`, fullPage: true }); + + const emptySave = await readCampaignSave(page); + assert(!emptySave.current && !emptySave.slot1, `Expected first run without campaign save: ${JSON.stringify(emptySave)}`); + + await page.mouse.click(962, 240); + await waitForStoryReady(page); + await page.screenshot({ path: `${screenshotDir}/rc-new-game-story.png`, fullPage: true }); + await advanceUntilBattle(page, 'first-battle-zhuo-commandery'); + await page.screenshot({ path: `${screenshotDir}/rc-first-battle.png`, fullPage: true }); + + const firstBattleProbe = await page.evaluate(() => { + const scene = window.__HEROS_GAME__?.scene.getScene('BattleScene'); + const state = window.__HEROS_DEBUG__?.battle(); + const sideTexts = textValues(scene?.sidePanelObjects); + return { + battleId: state?.battleId, + objectiveText: scene?.objectiveTrackerText?.text, + objectiveSubText: scene?.objectiveTrackerSubText?.text, + sideTexts + }; + + function textValues(objects = []) { + return objects.filter((object) => object?.type === 'Text').map((object) => object.text); + } + }); + assert(firstBattleProbe.battleId === 'first-battle-zhuo-commandery', `Expected first battle: ${JSON.stringify(firstBattleProbe)}`); + assert(firstBattleProbe.objectiveText?.startsWith('승리 목표:'), `Expected clear victory objective label: ${JSON.stringify(firstBattleProbe)}`); + assert(firstBattleProbe.objectiveSubText?.includes('패배 조건:'), `Expected clear defeat condition label: ${JSON.stringify(firstBattleProbe)}`); + assert( + firstBattleProbe.sideTexts.some((text) => text.includes('황건적의 전열이 무너집니다.')) && + !firstBattleProbe.sideTexts.some((text) => text.includes('...')), + `Expected opening objective text without ASCII truncation: ${JSON.stringify(firstBattleProbe.sideTexts)}` + ); + + await page.evaluate(() => window.__HEROS_DEBUG__?.forceBattleOutcome('victory')); + await waitForBattleOutcome(page, 'victory'); + await page.screenshot({ path: `${screenshotDir}/rc-first-battle-result.png`, fullPage: true }); + + const resultProbe = await page.evaluate(() => { + const scene = window.__HEROS_GAME__?.scene.getScene('BattleScene'); + const state = window.__HEROS_DEBUG__?.battle(); + return { + outcome: state?.battleOutcome, + resultTexts: textValues(scene?.resultObjects) + }; + + function textValues(objects = []) { + return objects.filter((object) => object?.type === 'Text').map((object) => object.text); + } + }); + assert(resultProbe.outcome === 'victory', `Expected forced victory result: ${JSON.stringify(resultProbe)}`); + assert(resultProbe.resultTexts.includes('목표 정산'), `Expected result objective settlement title: ${JSON.stringify(resultProbe.resultTexts)}`); + assert(resultProbe.resultTexts.some((text) => text.includes('목표 보상')), `Expected reward panel to name objective rewards: ${JSON.stringify(resultProbe.resultTexts)}`); + assert(!resultProbe.resultTexts.some((text) => text.includes('미달')), `Expected result screen to avoid harsh optional-goal wording: ${JSON.stringify(resultProbe.resultTexts)}`); + + await page.mouse.click(738, 642); + await waitForCamp(page); + await page.screenshot({ path: `${screenshotDir}/rc-first-camp.png`, fullPage: true }); + + const firstCampProbe = await page.evaluate(() => { + const scene = window.__HEROS_GAME__?.scene.getScene('CampScene'); + return { + state: window.__HEROS_DEBUG__?.camp(), + summary: scene?.reportSummary?.(), + texts: textValues(scene?.contentObjects) + }; + + function textValues(objects = []) { + return objects.filter((object) => object?.type === 'Text').map((object) => object.text); + } + }); + assert(firstCampProbe.state?.campaign?.step === 'first-camp', `Expected victory to return to first camp: ${JSON.stringify(firstCampProbe.state)}`); + assert(firstCampProbe.summary?.includes('보유 군자금'), `Expected camp summary to show held gold: ${JSON.stringify(firstCampProbe)}`); + assert(firstCampProbe.texts.some((text) => text.includes('전투 보상')), `Expected camp report to show battle reward: ${JSON.stringify(firstCampProbe.texts)}`); + + await seedCampaignSave(page, { + battleId: 'fifty-eighth-battle-qishan-retreat', + battleTitle: '기산 후퇴로', + step: 'fifty-eighth-camp', + gold: 7400, + turnNumber: 13, + defeatedEnemies: 16, + selectedSortieUnitIds: ['zhuge-liang', 'jiang-wei', 'wang-ping', 'zhao-yun', 'huang-quan', 'ma-liang', 'ma-dai'] + }); + await page.reload({ waitUntil: 'domcontentloaded' }); + await waitForTitle(page); + await page.mouse.click(962, 310); + await waitForCamp(page); + await page.screenshot({ path: `${screenshotDir}/rc-mid-campaign-continue.png`, fullPage: true }); + + const midCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); + assert(midCampState?.campaign?.step === 'fifty-eighth-camp', `Expected mid-campaign save to continue into camp: ${JSON.stringify(midCampState)}`); + assert(midCampState.campBattleId === 'fifty-eighth-battle-qishan-retreat', `Expected mid-campaign battle id: ${JSON.stringify(midCampState)}`); + assert(midCampState.sortiePlan?.selectedCount > 0, `Expected mid-campaign formation state: ${JSON.stringify(midCampState?.sortiePlan)}`); + + await seedCampaignSave(page, { + battleId: 'sixty-sixth-battle-wuzhang-final', + battleTitle: '오장원 최종전', + step: 'sixty-sixth-camp', + gold: 9800, + turnNumber: 18, + defeatedEnemies: 20, + selectedSortieUnitIds: ['zhuge-liang', 'jiang-wei', 'wang-ping', 'ma-dai', 'huang-quan', 'li-yan', 'wei-yan'] + }); + await page.reload({ waitUntil: 'domcontentloaded' }); + await waitForTitle(page); + await page.mouse.click(962, 310); + await waitForCamp(page); + const finalCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()); + assert(finalCampState?.campaign?.step === 'sixty-sixth-camp', `Expected final camp save to continue: ${JSON.stringify(finalCampState)}`); + await page.screenshot({ path: `${screenshotDir}/rc-final-camp.png`, fullPage: true }); + + await page.mouse.click(1160, 38); + const finalTransitionScenes = await waitForStoryOrEnding(page); + if (finalTransitionScenes.includes('StoryScene')) { + await waitForStoryReady(page); + await advanceStoryUntilEnding(page); + } + await waitForEnding(page); + await page.screenshot({ path: `${screenshotDir}/rc-ending.png`, fullPage: true }); + + const endingState = await page.evaluate(() => window.__HEROS_DEBUG__?.scene('EndingScene')?.getDebugState?.()); + assert(endingState?.campaignStep === 'ending-complete', `Expected ending-complete state: ${JSON.stringify(endingState)}`); + assert(endingState.latestBattleId === 'sixty-sixth-battle-wuzhang-final', `Expected Wuzhang final as latest battle: ${JSON.stringify(endingState)}`); + + await page.keyboard.press('Enter'); + await waitForTitle(page); + await page.mouse.click(962, 310); + await waitForEnding(page); + await page.screenshot({ path: `${screenshotDir}/rc-ending-continue.png`, fullPage: true }); + + const endingContinueState = await page.evaluate(() => ({ + activeScenes: window.__HEROS_DEBUG__?.activeScenes() ?? [], + ending: window.__HEROS_DEBUG__?.scene('EndingScene')?.getDebugState?.() + })); + assert( + endingContinueState.activeScenes.includes('EndingScene') && endingContinueState.ending?.campaignStep === 'ending-complete', + `Expected title continue to reopen ending: ${JSON.stringify(endingContinueState)}` + ); + + const errors = consoleMessages.filter((message) => message.type === 'error'); + assert(errors.length === 0, `Unexpected browser console errors: ${JSON.stringify(errors)}`); + + console.log(`Verified release-candidate user flow at ${targetUrl}`); +} finally { + await browser?.close(); + if (serverProcess && !serverProcess.killed) { + serverProcess.kill(); + } +} + +function withDebugParam(url) { + const parsed = new URL(url); + parsed.searchParams.set('debug', '1'); + parsed.searchParams.set('renderer', 'canvas'); + return parsed.toString(); +} + +async function waitForTitle(page) { + await page.waitForFunction(() => document.querySelector('canvas') !== null, undefined, { timeout: 90000 }); + await page.waitForFunction(() => window.__HEROS_DEBUG__ !== undefined, undefined, { timeout: 90000 }); + await page.waitForFunction(() => { + const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; + return activeScenes.includes('TitleScene'); + }, undefined, { timeout: 90000 }); +} + +async function waitForStoryReady(page) { + await page.waitForFunction(() => { + const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; + const story = window.__HEROS_DEBUG__?.scene('StoryScene')?.getDebugState?.(); + return activeScenes.includes('StoryScene') && story?.ready === true; + }, undefined, { timeout: 90000 }); +} + +async function waitForStoryOrEnding(page) { + await page.waitForFunction(() => { + const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; + return activeScenes.includes('StoryScene') || activeScenes.includes('EndingScene'); + }, undefined, { timeout: 30000 }); + return page.evaluate(() => window.__HEROS_DEBUG__?.activeScenes() ?? []); +} + +async function advanceUntilBattle(page, battleId) { + for (let i = 0; i < 48; i += 1) { + const ready = await page.evaluate((expectedBattleId) => { + const state = window.__HEROS_DEBUG__?.battle(); + return state?.scene === 'BattleScene' && state?.battleId === expectedBattleId && state?.phase === 'idle' && state?.mapBackgroundReady === true; + }, battleId); + + if (ready) { + return; + } + + await page.keyboard.press('Space'); + await page.waitForTimeout(220); + } + + await waitForBattleReady(page, battleId); +} + +async function waitForBattleReady(page, battleId) { + await page.waitForFunction((expectedBattleId) => { + const state = window.__HEROS_DEBUG__?.battle(); + return state?.scene === 'BattleScene' && state?.battleId === expectedBattleId && state?.phase === 'idle' && state?.mapBackgroundReady === true; + }, battleId, { timeout: 90000 }); +} + +async function waitForBattleOutcome(page, outcome) { + await page.waitForFunction((expectedOutcome) => { + const state = window.__HEROS_DEBUG__?.battle(); + return state?.battleOutcome === expectedOutcome && state?.phase === 'resolved' && state?.resultVisible === true; + }, outcome, { timeout: 90000 }); +} + +async function waitForCamp(page) { + await page.waitForFunction(() => { + const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; + const scene = window.__HEROS_DEBUG__?.scene('CampScene'); + return ( + activeScenes.includes('CampScene') && + window.__HEROS_DEBUG__?.camp()?.scene === 'CampScene' && + (scene?.contentObjects?.length ?? 0) > 0 + ); + }, undefined, { timeout: 90000 }); +} + +async function advanceStoryUntilEnding(page) { + for (let i = 0; i < 48; i += 1) { + const activeScenes = await page.evaluate(() => window.__HEROS_DEBUG__?.activeScenes() ?? []); + if (activeScenes.includes('EndingScene')) { + return; + } + + await page.keyboard.press('Space'); + await page.waitForTimeout(240); + } +} + +async function waitForEnding(page) { + await page.waitForFunction(() => { + const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; + const ending = window.__HEROS_DEBUG__?.scene('EndingScene')?.getDebugState?.(); + return activeScenes.includes('EndingScene') && ending?.ready === true && ending?.campaignStep === 'ending-complete'; + }, undefined, { timeout: 90000 }); +} + +async function readCampaignSave(page) { + return page.evaluate(() => { + const read = (key) => { + const raw = window.localStorage.getItem(key); + return raw ? JSON.parse(raw) : undefined; + }; + return { + current: read('heros-web:campaign-state'), + slot1: read('heros-web:campaign-state:slot-1') + }; + }); +} + +async function seedCampaignSave(page, options) { + await page.evaluate((seed) => { + const storageKey = 'heros-web:campaign-state'; + const current = JSON.parse(window.localStorage.getItem(storageKey) ?? 'null'); + if (!current?.firstBattleReport) { + throw new Error('Expected an existing campaign report before seeding RC save.'); + } + + const now = new Date().toISOString(); + const objectives = [ + { id: 'leader', label: '지휘관 격파', achieved: true, status: 'done', detail: '전장 목표 완료', rewardGold: 420 }, + { id: 'survive', label: '본대 보존', achieved: true, status: 'done', detail: '핵심 장수 생존', rewardGold: 260 }, + { id: 'supply', label: '보급선 유지', achieved: true, status: 'done', detail: '수레와 군량 확보', rewardGold: 220 }, + { id: 'quick', label: '빠른 정리', achieved: false, status: 'failed', detail: `${seed.turnNumber}/12턴`, rewardGold: 180 } + ]; + const units = current.roster?.length ? current.roster : current.firstBattleReport.units; + const bonds = current.bonds?.length ? current.bonds : current.firstBattleReport.bonds; + const report = { + ...current.firstBattleReport, + battleId: seed.battleId, + battleTitle: seed.battleTitle, + outcome: 'victory', + turnNumber: seed.turnNumber, + rewardGold: 1080, + defeatedEnemies: seed.defeatedEnemies, + totalEnemies: seed.defeatedEnemies, + objectives, + units, + bonds, + mvp: current.firstBattleReport.mvp ?? { unitId: 'zhuge-liang', name: '제갈량', damageDealt: 320, defeats: 2 }, + itemRewards: ['상처약 2', '탁주 1'], + completedCampDialogues: current.completedCampDialogues ?? [], + completedCampVisits: current.completedCampVisits ?? [], + createdAt: now + }; + const settlement = { + battleId: report.battleId, + battleTitle: report.battleTitle, + outcome: 'victory', + rewardGold: report.rewardGold, + itemRewards: report.itemRewards, + objectives, + units: units.map((unit) => ({ + unitId: unit.id, + name: unit.name, + level: unit.level ?? 1, + exp: unit.exp ?? 0, + hp: unit.hp ?? unit.maxHp ?? 1, + maxHp: unit.maxHp ?? 1, + equipment: unit.equipment + })), + bonds: bonds.map((bond) => ({ + id: bond.id, + title: bond.title, + level: bond.level ?? 1, + exp: bond.exp ?? 0, + battleExp: bond.battleExp ?? 0 + })), + reserveTraining: [], + completedAt: now + }; + const next = { + ...current, + updatedAt: now, + step: seed.step, + activeSaveSlot: 1, + gold: seed.gold, + inventory: { ...(current.inventory ?? {}), 콩: 12, 상처약: 6, 탁주: 4 }, + selectedSortieUnitIds: seed.selectedSortieUnitIds, + latestBattleId: seed.battleId, + firstBattleReport: report, + battleHistory: { + ...(current.battleHistory ?? {}), + [seed.battleId]: settlement + } + }; + + window.localStorage.setItem(storageKey, JSON.stringify(next)); + window.localStorage.setItem(`${storageKey}:slot-1`, JSON.stringify(next)); + }, options); +} + +async function ensurePreviewServer(url) { + if (await canReach(url)) { + return undefined; + } + + const parsed = new URL(url); + const isLocal = ['localhost', '127.0.0.1', '0.0.0.0'].includes(parsed.hostname); + if (!isLocal) { + throw new Error(`No server responded at ${url}`); + } + + const stderr = []; + const child = spawn(process.execPath, ['node_modules/vite/bin/vite.js', 'preview', '--host', '127.0.0.1', '--port', parsed.port || '4173'], { + cwd: process.cwd(), + env: process.env, + stdio: ['ignore', 'pipe', 'pipe'] + }); + + child.stderr.on('data', (chunk) => stderr.push(chunk.toString())); + child.stdout.on('data', () => {}); + + for (let i = 0; i < 120; i += 1) { + if (await canReach(url)) { + return child; + } + await delay(250); + } + + child.kill(); + throw new Error(`Vite preview did not start at ${url}\n${stderr.join('')}`); +} + +async function canReach(url) { + try { + const response = await fetch(url, { signal: AbortSignal.timeout(1000) }); + return response.ok; + } catch { + return false; + } +} + +function delay(ms) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +function assert(condition, message) { + if (!condition) { + throw new Error(message); + } +} diff --git a/src/game/scenes/BattleScene.ts b/src/game/scenes/BattleScene.ts index 08d9a9b..7139a43 100644 --- a/src/game/scenes/BattleScene.ts +++ b/src/game/scenes/BattleScene.ts @@ -1126,6 +1126,7 @@ type BattleObjectiveResult = { id: string; label: string; achieved: boolean; + status: 'active' | 'done' | 'failed'; detail: string; rewardGold: number; }; @@ -4597,6 +4598,7 @@ export class BattleScene extends Phaser.Scene { id: objective.id, label: objective.label, achieved: objective.achieved, + status: objective.status, detail: objective.detail, rewardGold: objective.rewardGold })); @@ -4630,7 +4632,7 @@ export class BattleScene extends Phaser.Scene { id: objective.id, label: objective.label, achieved: alive, - status: alive ? 'active' : 'failed', + status: alive ? (outcome === 'victory' ? 'done' : 'active') : 'failed', detail: alive ? `${this.unitName(targetId)} 생존` : `${this.unitName(targetId)} 퇴각`, rewardGold: objective.rewardGold }; @@ -4662,7 +4664,7 @@ export class BattleScene extends Phaser.Scene { label: objective.label, achieved, status: achieved ? (outcome === 'victory' ? 'done' : 'active') : 'failed', - detail: inTime ? `${this.turnNumber}/${maxTurn}턴 진행 중` : `${this.turnNumber}/${maxTurn}턴 초과`, + detail: inTime ? (outcome === 'victory' ? `${this.turnNumber}/${maxTurn}턴 달성` : `${this.turnNumber}/${maxTurn}턴 진행 중`) : `${this.turnNumber}/${maxTurn}턴 초과`, rewardGold: objective.rewardGold }; } @@ -4728,7 +4730,7 @@ export class BattleScene extends Phaser.Scene { bg.setDepth(depth); bg.setStrokeStyle(1, palette.blue, 0.56); - const title = this.trackResultObject(this.add.text(x + 14, y + 10, '전투 목표', { + const title = this.trackResultObject(this.add.text(x + 14, y + 10, '목표 정산', { fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif', fontSize: '19px', color: '#f2e3bf', @@ -4738,12 +4740,13 @@ export class BattleScene extends Phaser.Scene { objectives.forEach((objective, index) => { const rowY = y + 42 + index * 21; - const mark = objective.achieved ? '완료' : '미달'; + const mark = objective.achieved ? '완료' : objective.status === 'failed' ? '미획득' : '보너스'; const color = objective.achieved ? '#a8ffd0' : '#aeb7c2'; - const line = this.trackResultObject(this.add.text(x + 16, rowY, `${mark} ${objective.label} ${objective.detail}`, { + const line = this.trackResultObject(this.add.text(x + 16, rowY, `${mark} ${objective.label} · ${objective.detail}`, { fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif', fontSize: '13px', - color + color, + wordWrap: { width: width - 98, useAdvancedWrap: true } })); line.setDepth(depth + 1); @@ -4780,7 +4783,7 @@ export class BattleScene extends Phaser.Scene { .join(' '); const achievedCount = objectives.filter((objective) => objective.achieved).length; const lines = [ - outcome === 'victory' ? `군자금 ${this.resultGold(outcome)} 목표 ${achievedCount}/${objectives.length}` : '패배: 보상 없음', + outcome === 'victory' ? `군자금 ${this.resultGold(outcome)} 목표 보상 ${achievedCount}/${objectives.length}` : '패배: 보상 없음', mvp ? `MVP ${mvp.unit.name}: 피해 ${mvp.stats.damageDealt}, 격파 ${mvp.stats.defeats}` : 'MVP 없음', bondSummary ? `공명 성장 ${bondSummary}` : '공명 성장 없음', outcome === 'victory' ? `전리품: ${battleScenario.itemRewards.join(', ')}` : '재도전하면 목표 보상을 다시 노릴 수 있습니다.' @@ -8953,8 +8956,8 @@ export class BattleScene extends Phaser.Scene { private compactSituationMessage(message: string) { return message .split('\n') - .slice(0, 2) - .map((line) => (line.length > 18 ? `${line.slice(0, 17)}...` : line)) + .slice(0, 3) + .map((line) => (line.length > 30 ? `${line.slice(0, 29)}…` : line)) .join('\n'); } @@ -8966,15 +8969,15 @@ export class BattleScene extends Phaser.Scene { const leader = battleUnits.find((unit) => unit.id === leaderUnitId); const leaderProgress = leader && leader.hp > 0 ? `${leader.hp}/${leader.maxHp}` : '완료'; const quickObjective = this.objectiveStates(this.battleOutcome).find((objective) => objective.id === 'quick'); - const quickText = quickObjective ? ` · ${quickObjective.label} ${this.objectiveStatusText(quickObjective)}` : ''; + const quickText = quickObjective ? ` · 보너스: ${quickObjective.label} ${this.objectiveStatusText(quickObjective)}` : ''; const victoryText = this.battleOutcome === 'victory' - ? `승리: ${battleScenario.victoryConditionLabel} 완료` - : `승리: ${battleScenario.victoryConditionLabel} (${leaderProgress})`; + ? `승리 목표 완료: ${battleScenario.victoryConditionLabel}` + : `승리 목표: ${battleScenario.victoryConditionLabel} · ${this.unitName(leaderUnitId)} ${leaderProgress}`; const defeatText = this.battleOutcome === 'defeat' - ? `패배: ${battleScenario.defeatConditionLabel} 발생` - : `패배: ${battleScenario.defeatConditionLabel}`; + ? `패배 조건 발생: ${battleScenario.defeatConditionLabel}` + : `패배 조건: ${battleScenario.defeatConditionLabel}`; this.objectiveTrackerText.setText(victoryText); this.objectiveTrackerSubText.setText(`${defeatText}${quickText}`); @@ -9697,7 +9700,7 @@ export class BattleScene extends Phaser.Scene { } debugForceBattleOutcome(outcome: BattleOutcome = 'victory') { - if (!import.meta.env.DEV || this.battleOutcome) { + if (!this.debugToolsEnabled() || this.battleOutcome) { return; } @@ -9740,7 +9743,7 @@ export class BattleScene extends Phaser.Scene { } private installDebugHotkeys() { - if (!import.meta.env.DEV) { + if (!this.debugToolsEnabled()) { return; } @@ -9756,6 +9759,16 @@ export class BattleScene extends Phaser.Scene { }); } + private debugToolsEnabled() { + if (import.meta.env.DEV) { + return true; + } + if (typeof window === 'undefined') { + return false; + } + return new URLSearchParams(window.location.search).has('debug'); + } + private updateDebugOverlay() { if (!this.debugOverlay) { return; diff --git a/src/game/scenes/CampScene.ts b/src/game/scenes/CampScene.ts index 1bf6d28..0f57128 100644 --- a/src/game/scenes/CampScene.ts +++ b/src/game/scenes/CampScene.ts @@ -10064,7 +10064,7 @@ export class CampScene extends Phaser.Scene { return; } - const loadingText = this.add.text(this.scale.width / 2, this.scale.height / 2, 'Camp loading...', { + const loadingText = this.add.text(this.scale.width / 2, this.scale.height / 2, '군영을 정비하는 중...', { fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif', fontSize: '22px', color: '#f5e6b8', @@ -10836,7 +10836,7 @@ export class CampScene extends Phaser.Scene { const achieved = this.report.objectives.filter((objective) => objective.achieved).length; const gold = this.campaign?.gold ?? this.report.rewardGold; - return `${this.report.battleTitle} ${this.report.turnNumber}턴 승리 · 군자금 ${gold} · 목표 ${achieved}/${this.report.objectives.length} · 격파 ${this.report.defeatedEnemies}/${this.report.totalEnemies}`; + return `${this.report.battleTitle} ${this.report.turnNumber}턴 승리 · 보유 군자금 ${gold} · 목표 보상 ${achieved}/${this.report.objectives.length} · 격파 ${this.report.defeatedEnemies}/${this.report.totalEnemies}`; } private currentCampTitle() { @@ -12217,7 +12217,7 @@ export class CampScene extends Phaser.Scene { this.add.text( x + 18, y + 43, - `MVP ${report.mvp?.name ?? '-'} · 보상 ${report.rewardGold} · 전리품 ${report.itemRewards.join(', ') || '없음'}`, + `MVP ${report.mvp?.name ?? '-'} · 전투 보상 ${report.rewardGold} · 전리품 ${report.itemRewards.join(', ') || '없음'}`, this.textStyle(15, '#d4dce6') ) ); diff --git a/vite.config.ts b/vite.config.ts index cf9b173..d96c30f 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -6,6 +6,23 @@ export default defineConfig({ port: 5173 }, build: { - sourcemap: process.env.VITE_BUILD_SOURCEMAP === 'true' + sourcemap: process.env.VITE_BUILD_SOURCEMAP === 'true', + chunkSizeWarningLimit: 1600, + rollupOptions: { + output: { + manualChunks(id) { + const normalizedId = id.replace(/\\/g, '/'); + if (normalizedId.includes('/node_modules/phaser/')) { + return 'vendor-phaser'; + } + if (normalizedId.endsWith('/src/game/data/scenario.ts')) { + return 'campaign-scenario'; + } + if (normalizedId.endsWith('/src/game/scenes/CampScene.ts')) { + return 'scene-camp'; + } + } + } + } } });