feat: streamline victory debrief flow

This commit is contained in:
2026-07-28 06:48:05 +09:00
parent 5f6490b2ae
commit 7e06ffb9f5
14 changed files with 1224 additions and 439 deletions

View File

@@ -97,25 +97,25 @@ try {
({ expectedVisitId }) => {
const debug = window.__HEROS_DEBUG__;
const camp = debug?.camp?.();
const reward =
camp?.victoryRewardAcknowledgement;
const guided = reward?.actions?.find(
(action) => action.id === 'guided'
);
const arrival = camp?.victorySettlementArrival;
const reward = camp?.victoryRewardAcknowledgement;
return (
debug?.activeScenes?.().includes('CampScene') &&
camp?.campaign?.step === 'first-camp' &&
reward?.visible === true &&
reward.actions
.map((action) => action.id)
.join(',') ===
'equipment,supplies,guided,sortie,close' &&
guided?.primary === true &&
guided?.label === '북문 정찰막 둘러보기' &&
guided?.interactive === true &&
arrival?.battleId === 'first-battle-zhuo-commandery' &&
arrival.blocking === false &&
reward?.role === 'on-demand-ledger' &&
reward.available === true &&
reward.visible === false &&
reward.toggleInteractive === true &&
reward.toggleButtonBounds &&
reward.cards?.length === 0 &&
reward.actions?.length === 0 &&
camp?.sortieCommand?.guidedKind === 'visit' &&
camp.sortieCommand.targetId === expectedVisitId &&
camp.sortieCommand.label === '정찰막 둘러보기' &&
camp.sortieCommand.interactive === true &&
camp.sortieCommand.bounds &&
camp.sortieCommand.bypass?.label === '바로 출진' &&
camp.sortieCommand.bypass?.interactive === true
);
@@ -126,34 +126,155 @@ try {
const guidedCamp = await page.evaluate(() =>
window.__HEROS_DEBUG__?.camp?.()
);
const guidedReward =
guidedCamp.victoryRewardAcknowledgement;
const guidedAction = guidedReward.actions.find(
(action) => action.id === 'guided'
const settlementArrival = guidedCamp.victorySettlementArrival;
const closedRewardLedger = guidedCamp.victoryRewardAcknowledgement;
assert.deepEqual(
settlementArrival.pendingCategories.filter(
(category) => category === 'gold' || category === 'reputation'
),
[],
'Passive reward categories must be acknowledged on arrival without consuming destination-specific NEW categories.'
);
assert.equal(
guidedReward.actions.filter((action) => action.primary)
.length,
1
);
assert.equal(
guidedReward.actions.find(
(action) => action.id === 'sortie'
)?.label,
'바로 출진'
assert(
settlementArrival.passiveCategoriesAcknowledged.every(
(category) => category === 'gold' || category === 'reputation'
)
);
assert(settlementArrival.settledBattleIds.includes(sourceBattleId));
assertBoundsInsideViewport(
guidedAction.bounds,
'first-camp guided reward action'
);
await captureStableScreenshot(
page,
'dist/verification-first-pursuit-camp-guided-reward.png'
closedRewardLedger.toggleButtonBounds,
'first-camp reward ledger toggle'
);
await clickNamedSceneBounds(
page,
'CampScene',
guidedAction.bounds
closedRewardLedger.toggleButtonBounds
);
await page.waitForFunction(() => {
const reward =
window.__HEROS_DEBUG__?.camp?.()?.victoryRewardAcknowledgement;
const primaryActions =
reward?.actions?.filter((action) => action.primary) ?? [];
return (
reward?.role === 'on-demand-ledger' &&
reward?.visible === true &&
reward?.cards?.length === 6 &&
reward.cards.every(
(card) =>
card.bounds &&
(card.actionId === null || card.interactive === true)
) &&
reward?.actions
?.map((action) => action.id)
.join(',') === 'equipment,supplies,sortie,close' &&
reward.actions.every(
(action) => action.bounds && action.interactive === true
) &&
primaryActions.length === 1 &&
primaryActions[0]?.id === 'close'
);
});
const openLedgerCamp = await page.evaluate(() =>
window.__HEROS_DEBUG__?.camp?.()
);
const openRewardLedger =
openLedgerCamp.victoryRewardAcknowledgement;
const pendingCategorySet = new Set(
settlementArrival.pendingCategories
);
const newCardCategorySet = new Set(
openRewardLedger.cards
.filter((card) => card.new)
.map((card) => card.id)
);
assert(
[...pendingCategorySet].every((category) =>
newCardCategorySet.has(category)
),
`Pending reward categories must retain NEW ledger cards: ${JSON.stringify({
pendingCategories: [...pendingCategorySet],
newCards: [...newCardCategorySet]
})}`
);
assert.equal(
openRewardLedger.actions.filter((action) => action.primary)
.length,
1
);
assert.equal(
openRewardLedger.actions.find(
(action) => action.id === 'sortie'
)?.label,
'출진 준비'
);
const closeLedgerAction = openRewardLedger.actions.find(
(action) => action.id === 'close'
);
assertBoundsInsideViewport(
closeLedgerAction.bounds,
'first-camp reward ledger close action'
);
await captureStableScreenshot(
page,
'dist/verification-first-pursuit-camp-reward-ledger.png'
);
await clickNamedSceneBounds(
page,
'CampScene',
closeLedgerAction.bounds
);
await page.waitForFunction(() =>
window.__HEROS_DEBUG__?.camp?.()
?.victoryRewardAcknowledgement?.visible === false
);
let afterLedgerClose = await page.evaluate(() =>
window.__HEROS_DEBUG__?.camp?.()
);
await clickNamedSceneBounds(
page,
'CampScene',
afterLedgerClose.victoryRewardAcknowledgement.toggleButtonBounds
);
await page.waitForFunction(() =>
window.__HEROS_DEBUG__?.camp?.()
?.victoryRewardAcknowledgement?.visible === true
);
const reopenedRewardLedger = await page.evaluate(() =>
window.__HEROS_DEBUG__?.camp?.()
?.victoryRewardAcknowledgement
);
assert.deepEqual(
reopenedRewardLedger.cards
.filter((card) => card.new)
.map((card) => card.id),
openRewardLedger.cards
.filter((card) => card.new)
.map((card) => card.id),
'Closing the ledger must not consume destination-specific NEW categories.'
);
const reopenedCloseAction = reopenedRewardLedger.actions.find(
(action) => action.id === 'close'
);
await clickNamedSceneBounds(
page,
'CampScene',
reopenedCloseAction.bounds
);
await page.waitForFunction(() =>
window.__HEROS_DEBUG__?.camp?.()
?.victoryRewardAcknowledgement?.visible === false
);
afterLedgerClose = await page.evaluate(() =>
window.__HEROS_DEBUG__?.camp?.()
);
assertBoundsInsideViewport(
afterLedgerClose.sortieCommand.bounds,
'first-camp guided sortie command'
);
await clickNamedSceneBounds(
page,
'CampScene',
afterLedgerClose.sortieCommand.bounds
);
await waitForExplorationReady(page);
await page.waitForTimeout(380);
@@ -633,7 +754,7 @@ try {
console.log(
`Verified the first-pursuit camp exploration at ${desktopBrowserViewport.width}x${desktopBrowserViewport.height}, ` +
`DPR ${desktopBrowserDeviceScaleFactor}: dedicated Jian Yong SD art, fixed NPC positions, keyboard and pointer movement, ` +
'first-victory guided reward routing, distance-gated interaction, face portraits, two real choices, one-time resonance/item rewards, duplicate protection, ' +
'nonblocking victory settlement, on-demand reward ledger, guided camp routing, distance-gated interaction, face portraits, two real choices, one-time resonance/item rewards, duplicate protection, ' +
'CampScene return, and current/slot-1 save persistence.'
);
} finally {