feat: preview sortie composition synergy

This commit is contained in:
2026-07-10 13:08:56 +09:00
parent 3d2c09c24e
commit 3caf03fe2f
8 changed files with 609 additions and 70 deletions

View File

@@ -170,6 +170,19 @@ try {
await waitForSortiePrep(page);
await page.screenshot({ path: `${screenshotDir}/rc-first-camp-sortie-prep.png`, fullPage: true });
await assertCanvasPainted(page, 'first camp sortie prep');
await advanceFirstSortiePrepStep(page, 'formation');
await page.screenshot({ path: `${screenshotDir}/rc-first-camp-sortie-formation.png`, fullPage: true });
await assertCanvasPainted(page, 'first camp sortie formation');
const firstSortieSynergy = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()?.sortieSynergyPreview);
assert(
firstSortieSynergy?.activeBondCount === 3 &&
firstSortieSynergy?.coveredRoleCount === 3 &&
firstSortieSynergy?.strongestBond?.damageBonus === 9 &&
firstSortieSynergy?.strongestBond?.chainRate === 18 &&
firstSortieSynergy?.firstPursuitTrinityConfigured === true,
`Expected first sortie to preview three bonds, the strongest effect, and trinity: ${JSON.stringify(firstSortieSynergy)}`
);
await advanceFirstSortiePrepStep(page, 'loadout');
await page.mouse.click(1116, 656);
await advanceUntilBattle(page, 'second-battle-yellow-turban-pursuit');
await page.screenshot({ path: `${screenshotDir}/rc-second-battle-from-first-camp.png`, fullPage: true });
@@ -235,13 +248,65 @@ try {
`Expected mid-campaign deployment preview to avoid duplicate tiles: ${JSON.stringify(midCampState?.sortieDeploymentPreview)}`
);
const midCampSelectedSortieUnitIds = midCampState.selectedSortieUnitIds ?? [];
const midCampDeploymentPreview = midCampState.sortieDeploymentPreview ?? [];
const midCampNextBattleId = midCampState.nextSortieBattleId;
await page.mouse.click(1160, 38);
await waitForSortiePrep(page);
await page.screenshot({ path: `${screenshotDir}/rc-mid-camp-sortie-prep.png`, fullPage: true });
await assertCanvasPainted(page, 'mid camp sortie prep');
const fullRosterCandidateProbe = await page.evaluate(() => {
const scene = window.__HEROS_GAME__?.scene.getScene('CampScene');
const state = window.__HEROS_DEBUG__?.camp();
const candidate = state?.sortieRoster?.find((unit) => !unit.selected && unit.available);
if (!candidate || typeof scene?.toggleSortieUnit !== 'function') {
return { candidateId: null, selectedBefore: state?.selectedSortieUnitIds ?? [] };
}
scene.toggleSortieUnit(candidate.id);
return { candidateId: candidate.id, selectedBefore: state.selectedSortieUnitIds ?? [] };
});
assert(fullRosterCandidateProbe.candidateId, `Expected a full-roster swap candidate: ${JSON.stringify(fullRosterCandidateProbe)}`);
await page.waitForFunction((probe) => {
const state = window.__HEROS_DEBUG__?.camp();
return state?.sortieFocusedUnitId === probe.candidateId &&
state?.sortieFocusedSynergyPreview?.mode === 'waiting' &&
state?.selectedSortieUnitIds?.length === probe.selectedBefore.length &&
!state.selectedSortieUnitIds.includes(probe.candidateId);
}, fullRosterCandidateProbe, { timeout: 30000 });
const underCapacityProbe = await page.evaluate(() => {
const scene = window.__HEROS_GAME__?.scene.getScene('CampScene');
const state = window.__HEROS_DEBUG__?.camp();
const removable = state?.sortieRoster?.find((unit) => unit.selected && !unit.required);
if (!removable || typeof scene?.toggleSortieUnit !== 'function') {
return { removedUnitId: null, selectedBefore: state?.selectedSortieUnitIds ?? [] };
}
scene.toggleSortieUnit(removable.id);
return { removedUnitId: removable.id, selectedBefore: state.selectedSortieUnitIds ?? [] };
});
assert(underCapacityProbe.removedUnitId, `Expected a removable mid-campaign officer: ${JSON.stringify(underCapacityProbe)}`);
await page.waitForFunction((probe) => {
const state = window.__HEROS_DEBUG__?.camp();
return state?.sortieVisible === true &&
state?.selectedSortieUnitIds?.length === probe.selectedBefore.length - 1 &&
!state.selectedSortieUnitIds.includes(probe.removedUnitId) &&
state?.sortieFocusedSynergyPreview?.mode === 'add';
}, underCapacityProbe, { timeout: 30000 });
await page.reload({ waitUntil: 'domcontentloaded' });
await waitForTitle(page);
await page.mouse.click(962, 310);
await waitForCamp(page);
const restoredUnderCapacityState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp());
assert(
restoredUnderCapacityState?.selectedSortieUnitIds?.length === underCapacityProbe.selectedBefore.length - 1 &&
!restoredUnderCapacityState.selectedSortieUnitIds.includes(underCapacityProbe.removedUnitId),
`Expected an under-capacity manual formation to survive scene recreation: ${JSON.stringify(restoredUnderCapacityState?.selectedSortieUnitIds)} / ${JSON.stringify(underCapacityProbe)}`
);
const midCampSelectedSortieUnitIds = restoredUnderCapacityState.selectedSortieUnitIds ?? [];
const midCampDeploymentPreview = restoredUnderCapacityState.sortieDeploymentPreview ?? [];
await page.mouse.click(1160, 38);
await waitForSortiePrep(page);
await page.mouse.click(1116, 656);
await advanceUntilBattle(page, midCampNextBattleId);
await page.screenshot({ path: `${screenshotDir}/rc-mid-camp-next-battle.png`, fullPage: true });
@@ -467,6 +532,14 @@ async function waitForSortiePrep(page) {
}, undefined, { timeout: 30000 });
}
async function advanceFirstSortiePrepStep(page, expectedStep) {
await page.mouse.click(1116, 656);
await page.waitForFunction((step) => {
const camp = window.__HEROS_DEBUG__?.camp?.();
return camp?.sortieVisible === true && camp?.sortiePrepStep === step;
}, expectedStep, { timeout: 30000 });
}
async function waitForCampAfterBattleResult(page) {
await page.waitForFunction(() => {
const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? [];