feat: guide early stays and remember player choices

This commit is contained in:
2026-07-27 19:41:10 +09:00
parent eec4896002
commit c22f77b4e1
11 changed files with 2121 additions and 93 deletions

View File

@@ -91,8 +91,70 @@ try {
);
await page.evaluate(async () => {
await window.__HEROS_DEBUG__.goToCampVisitExploration();
await window.__HEROS_DEBUG__.goToCamp();
});
await page.waitForFunction(
({ expectedVisitId }) => {
const debug = window.__HEROS_DEBUG__;
const camp = debug?.camp?.();
const reward =
camp?.victoryRewardAcknowledgement;
const guided = reward?.actions?.find(
(action) => action.id === 'guided'
);
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 &&
camp?.sortieCommand?.guidedKind === 'visit' &&
camp.sortieCommand.targetId === expectedVisitId &&
camp.sortieCommand.label === '정찰막 둘러보기' &&
camp.sortieCommand.bypass?.label === '바로 출진' &&
camp.sortieCommand.bypass?.interactive === true
);
},
{ expectedVisitId: visitId },
{ timeout: 90000 }
);
const guidedCamp = await page.evaluate(() =>
window.__HEROS_DEBUG__?.camp?.()
);
const guidedReward =
guidedCamp.victoryRewardAcknowledgement;
const guidedAction = guidedReward.actions.find(
(action) => action.id === 'guided'
);
assert.equal(
guidedReward.actions.filter((action) => action.primary)
.length,
1
);
assert.equal(
guidedReward.actions.find(
(action) => action.id === 'sortie'
)?.label,
'바로 출진'
);
assertBoundsInsideViewport(
guidedAction.bounds,
'first-camp guided reward action'
);
await captureStableScreenshot(
page,
'dist/verification-first-pursuit-camp-guided-reward.png'
);
await clickNamedSceneBounds(
page,
'CampScene',
guidedAction.bounds
);
await waitForExplorationReady(page);
await page.waitForTimeout(380);
@@ -269,7 +331,7 @@ try {
);
},
{ key: sceneKey, actorId: 'jian-yong' },
{ timeout: 12000 }
{ timeout: 30000 }
);
exploration = await readExploration(page);
assert(
@@ -502,7 +564,7 @@ try {
);
},
{ key: sceneKey, exitId: 'camp-exit' },
{ timeout: 12000 }
{ timeout: 30000 }
);
await page.keyboard.press('e');
await waitForCampReturn(page);
@@ -517,6 +579,16 @@ try {
camp.firstPursuitScoutMemory.activeForNextSortie,
true
);
assert.notEqual(
camp.sortieCommand.guidedKind,
'visit',
'Completing the guided scout visit must advance the primary camp action.'
);
assert.equal(
camp.sortieCommand.bypass,
null,
'The direct-sortie bypass must collapse once the seed has no pending guided step.'
);
assertRelevantProgressEqual(
savesAfterChoice,
await readCampaignSaves(page),
@@ -540,7 +612,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, ` +
'distance-gated interaction, face portraits, two real choices, one-time resonance/item rewards, duplicate protection, ' +
'first-victory guided reward 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 {
@@ -801,6 +873,37 @@ async function clickSceneBounds(page, bounds) {
);
}
async function clickNamedSceneBounds(page, requestedSceneKey, bounds) {
const point = await page.evaluate(
({ key, requestedBounds }) => {
const scene = window.__HEROS_DEBUG__?.scene(key);
const canvas = document.querySelector('canvas');
const canvasBounds = canvas?.getBoundingClientRect();
if (!scene || !canvasBounds) {
return null;
}
return {
x:
canvasBounds.left +
(requestedBounds.x + requestedBounds.width / 2) *
canvasBounds.width /
scene.scale.width,
y:
canvasBounds.top +
(requestedBounds.y + requestedBounds.height / 2) *
canvasBounds.height /
scene.scale.height
};
},
{ key: requestedSceneKey, requestedBounds: bounds }
);
assert(
point && Number.isFinite(point.x) && Number.isFinite(point.y),
`Unable to map ${requestedSceneKey} bounds ${JSON.stringify(bounds)}.`
);
await page.mouse.click(point.x, point.y);
}
async function readCampaignSaves(page) {
return page.evaluate(() => {
const parse = (key) => {