feat: add second-victory relief exploration

This commit is contained in:
2026-07-27 10:53:20 +09:00
parent e9fcd27611
commit f07a45c62b
24 changed files with 4583 additions and 282 deletions

View File

@@ -80,7 +80,8 @@ try {
verifyExplorationLayout(city, xuzhouCase.cityStayId);
await captureStableScreenshot(page, 'dist/verification-city-xuzhou-map.png');
await verifyKeyboardMovement(page);
const movementProbe = await verifyKeyboardMovement(page);
await verifyPointerMovement(page, movementProbe);
await verifyDistantInteractionIsBlocked(page);
const informationResult = await verifyInformationReward(
@@ -126,7 +127,7 @@ try {
console.log(
`Verified direct inter-battle city exploration at ${desktopBrowserViewport.width}x${desktopBrowserViewport.height}, ` +
`DPR ${desktopBrowserDeviceScaleFactor}: Camp gateway entry, real keyboard movement, distance-gated interaction, ` +
`DPR ${desktopBrowserDeviceScaleFactor}: Camp gateway entry, real keyboard and world-pointer movement, distance-gated interaction, ` +
'one-time information rewards, exact market purchases, saved resonance choices, reload/Continue restoration, ' +
'optional-objective exit, next-battle briefing integration, and Xuzhou/Xinye/Chengdu map and modal layouts.'
);
@@ -442,6 +443,42 @@ async function verifyKeyboardMovement(page) {
})}`
);
assert.equal(after.player.moving, false);
return {
start: { x: before.player.x, y: before.player.y },
end: { x: after.player.x, y: after.player.y }
};
}
async function verifyPointerMovement(page, movementProbe) {
await clickScenePoint(page, 'CityStayScene', movementProbe.start);
await waitForPlayerAt(page, movementProbe.start);
const returned = await readCity(page);
assert(
returned.player.x - movementProbe.end.x >= 85,
`Expected world-pointer movement to return Liu Bei right: ${JSON.stringify({
movementProbe,
returned: returned.player
})}`
);
assert(
Math.abs(returned.player.y - movementProbe.start.y) <= 3,
`Expected world-pointer movement to retain Y: ${JSON.stringify({
movementProbe,
returned: returned.player
})}`
);
await clickScenePoint(page, 'CityStayScene', movementProbe.end);
await waitForPlayerAt(page, movementProbe.end);
const restored = await readCity(page);
assert(
Math.abs(restored.player.x - movementProbe.end.x) <= 9 &&
Math.abs(restored.player.y - movementProbe.end.y) <= 9,
`Expected the pointer probe to restore the distant interaction position: ${JSON.stringify({
expected: movementProbe.end,
restored: restored.player
})}`
);
}
async function verifyDistantInteractionIsBlocked(page) {
@@ -1028,6 +1065,40 @@ async function clickSceneBounds(page, sceneKey, bounds) {
await page.mouse.click(point.x, point.y);
}
async function clickScenePoint(page, sceneKey, scenePoint) {
const point = await page.evaluate(({ requestedSceneKey, requestedPoint }) => {
const scene = window.__HEROS_GAME__?.scene.getScene(requestedSceneKey);
const canvas = document.querySelector('canvas');
const canvasBounds = canvas?.getBoundingClientRect();
if (!scene || !canvasBounds || !requestedPoint) {
return null;
}
return {
x: canvasBounds.left +
requestedPoint.x * canvasBounds.width / scene.scale.width,
y: canvasBounds.top +
requestedPoint.y * canvasBounds.height / scene.scale.height
};
}, {
requestedSceneKey: sceneKey,
requestedPoint: scenePoint
});
assert(point && Number.isFinite(point.x) && Number.isFinite(point.y));
await page.mouse.click(point.x, point.y);
}
async function waitForPlayerAt(page, target) {
await page.waitForFunction(({ x, y }) => {
const player = window.__HEROS_DEBUG__?.cityStay?.()?.player;
return Boolean(
player &&
Math.abs(player.x - x) <= 9 &&
Math.abs(player.y - y) <= 9 &&
player.moving === false
);
}, target);
}
async function captureStableScreenshot(page, path) {
await page.waitForTimeout(260);
const loopSlept = await page.evaluate(() => {