feat: add selectable core resonance pairs
This commit is contained in:
@@ -2343,6 +2343,8 @@ try {
|
||||
title: '도원결의',
|
||||
level: 72,
|
||||
chainRate: 18,
|
||||
baseChainRate: 18,
|
||||
core: false,
|
||||
damageBonus: 9
|
||||
},
|
||||
{
|
||||
@@ -2352,6 +2354,8 @@ try {
|
||||
title: '의형제',
|
||||
level: 68,
|
||||
chainRate: 8,
|
||||
baseChainRate: 8,
|
||||
core: false,
|
||||
damageBonus: 9
|
||||
},
|
||||
{
|
||||
@@ -2361,6 +2365,8 @@ try {
|
||||
title: '맹장 공명',
|
||||
level: 64,
|
||||
chainRate: 8,
|
||||
baseChainRate: 8,
|
||||
core: false,
|
||||
damageBonus: 9
|
||||
}
|
||||
];
|
||||
@@ -2368,29 +2374,150 @@ try {
|
||||
const logicalViewportBounds = { x: 0, y: 0, width: 1280, height: 720 };
|
||||
assert(
|
||||
sameJsonValue(firstSortiePursuit?.activePairs, expectedFirstSortiePursuitPairs) &&
|
||||
firstSortiePursuit?.selectableOpportunities?.length === 0,
|
||||
firstSortiePursuit?.selectableOpportunities?.length === 0 &&
|
||||
firstSortiePursuit?.selectedCoreBondId === null &&
|
||||
firstSortiePursuit?.selectionBattleId === null &&
|
||||
sameJsonValue(
|
||||
firstSortiePursuit?.coreCandidates?.map((candidate) => ({
|
||||
id: candidate.id,
|
||||
level: candidate.level,
|
||||
baseChainRate: candidate.baseChainRate,
|
||||
coreChainRate: candidate.coreChainRate,
|
||||
selected: candidate.selected
|
||||
})),
|
||||
[
|
||||
{ id: 'liu-bei__guan-yu', level: 72, baseChainRate: 18, coreChainRate: 28, selected: false },
|
||||
{ id: 'liu-bei__zhang-fei', level: 68, baseChainRate: 8, coreChainRate: 18, selected: false },
|
||||
{ id: 'guan-yu__zhang-fei', level: 64, baseChainRate: 8, coreChainRate: 18, selected: false }
|
||||
]
|
||||
),
|
||||
`Expected the selected founding trio to expose ordered 18/8/8 resonance-pursuit pairs without reserve opportunities: ${JSON.stringify(firstSortiePursuit)}`
|
||||
);
|
||||
assert(
|
||||
firstSortiePursuit?.summaryText === '조합 시너지 · 추격 후보 3조 · 역할 3/3' &&
|
||||
firstSortiePursuit?.ruleText === '같은 적을 먼저 친 공명 장수가 지원 거리 안에서 표시 확률로 추격합니다.' &&
|
||||
firstSortiePursuit?.summaryText === '핵심 공명조 · 미지정 · 후보 3조' &&
|
||||
firstSortiePursuit?.ruleText === 'Lv30+ 한 조 · 핵심 추격 8/18/28% · 재클릭 해제' &&
|
||||
isFiniteBounds(firstSortiePursuit.panelBounds) &&
|
||||
isFiniteBounds(firstSortiePursuit.ruleBounds) &&
|
||||
firstSortiePursuit.page === 0 &&
|
||||
firstSortiePursuit.pageCount === 1 &&
|
||||
firstSortiePursuit.pageText === '1/1' &&
|
||||
firstSortiePursuit.prevEnabled === false &&
|
||||
firstSortiePursuit.nextEnabled === false &&
|
||||
isFiniteBounds(firstSortiePursuit.prevBounds) &&
|
||||
isFiniteBounds(firstSortiePursuit.nextBounds) &&
|
||||
firstSortiePursuit.prevClickBounds === null &&
|
||||
firstSortiePursuit.nextClickBounds === null &&
|
||||
boundsInside(firstSortiePursuit.prevBounds, firstSortiePursuit.panelBounds) &&
|
||||
boundsInside(firstSortiePursuit.nextBounds, firstSortiePursuit.panelBounds) &&
|
||||
boundsInside(firstSortiePursuit.panelBounds, logicalViewportBounds) &&
|
||||
boundsInside(firstSortiePursuit.ruleBounds, firstSortiePursuit.panelBounds) &&
|
||||
sameJsonValue(firstSortiePursuit.rows?.map((row) => ({ state: row.state, text: row.text })), [
|
||||
{ state: 'candidate', text: '18% 유비↔관우' },
|
||||
{ state: 'candidate', text: '8% 유비↔장비' },
|
||||
{ state: 'candidate', text: '8% 관우↔장비' }
|
||||
sameJsonValue(firstSortiePursuit.rows?.map((row) => ({ bondId: row.bondId, state: row.state, selected: row.selected, chainRate: row.chainRate, text: row.text })), [
|
||||
{ bondId: 'liu-bei__guan-yu', state: 'candidate', selected: false, chainRate: 28, text: '지정 28% 유비↔관우' },
|
||||
{ bondId: 'liu-bei__zhang-fei', state: 'candidate', selected: false, chainRate: 18, text: '지정 18% 유비↔장비' },
|
||||
{ bondId: 'guan-yu__zhang-fei', state: 'candidate', selected: false, chainRate: 18, text: '지정 18% 관우↔장비' }
|
||||
]) &&
|
||||
firstSortiePursuit.rows.every(
|
||||
(row) =>
|
||||
isFiniteBounds(row.bounds) &&
|
||||
isFiniteBounds(row.chipBounds) &&
|
||||
isFiniteBounds(row.textBounds) &&
|
||||
sameJsonValue(row.bounds, row.chipBounds) &&
|
||||
boundsInside(row.bounds, firstSortiePursuit.panelBounds) &&
|
||||
boundsInside(row.textBounds, row.bounds, 2)
|
||||
),
|
||||
`Expected all three pursuit chips and their labels to remain visible inside the first-sortie panel: ${JSON.stringify(firstSortiePursuit)}`
|
||||
`Expected all three interactive core-resonance chips and their labels to remain visible inside the first-sortie panel: ${JSON.stringify(firstSortiePursuit)}`
|
||||
);
|
||||
|
||||
const coreResonanceToggleTarget = firstSortiePursuit.rows.find(
|
||||
(row) => row.bondId === 'liu-bei__zhang-fei'
|
||||
);
|
||||
assert(
|
||||
isFiniteBounds(coreResonanceToggleTarget?.chipBounds),
|
||||
`Expected the lower-level brother pair to expose an interactive core-resonance chip: ${JSON.stringify(coreResonanceToggleTarget)}`
|
||||
);
|
||||
await page.mouse.click(
|
||||
coreResonanceToggleTarget.chipBounds.x + coreResonanceToggleTarget.chipBounds.width / 2,
|
||||
coreResonanceToggleTarget.chipBounds.y + coreResonanceToggleTarget.chipBounds.height / 2
|
||||
);
|
||||
await page.waitForFunction(() => {
|
||||
const pursuit = window.__HEROS_DEBUG__?.camp()?.sortiePursuitPreview;
|
||||
return pursuit?.selectedCoreBondId === 'liu-bei__zhang-fei' &&
|
||||
pursuit?.rows?.some((row) => row.bondId === 'liu-bei__zhang-fei' && row.selected === true);
|
||||
}, undefined, { timeout: 30000 });
|
||||
const selectedCoreResonance = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()?.sortiePursuitPreview);
|
||||
const selectedCoreResonanceSave = await readCampaignSave(page);
|
||||
await page.screenshot({ path: `${screenshotDir}/rc-first-camp-core-resonance-selected.png`, fullPage: true });
|
||||
assert(
|
||||
selectedCoreResonance?.selectionBattleId === 'second-battle-yellow-turban-pursuit' &&
|
||||
selectedCoreResonance?.selectedCoreBondId === 'liu-bei__zhang-fei' &&
|
||||
selectedCoreResonance?.summaryText === '핵심 공명조 · 지정 · 후보 3조' &&
|
||||
selectedCoreResonance?.activePairs?.[0]?.id === 'liu-bei__zhang-fei' &&
|
||||
selectedCoreResonance.activePairs[0].core === true &&
|
||||
selectedCoreResonance.activePairs[0].baseChainRate === 8 &&
|
||||
selectedCoreResonance.activePairs[0].chainRate === 18 &&
|
||||
selectedCoreResonance?.coreCandidates?.[0]?.id === 'liu-bei__zhang-fei' &&
|
||||
selectedCoreResonance.coreCandidates[0].selected === true &&
|
||||
selectedCoreResonance?.rows?.[0]?.bondId === 'liu-bei__zhang-fei' &&
|
||||
selectedCoreResonance.rows[0].state === 'selected' &&
|
||||
selectedCoreResonance.rows[0].text === '핵심 18% 유비↔장비' &&
|
||||
selectedCoreResonanceSave.current?.sortieResonanceSelection?.battleId === 'second-battle-yellow-turban-pursuit' &&
|
||||
selectedCoreResonanceSave.current.sortieResonanceSelection.bondId === 'liu-bei__zhang-fei',
|
||||
`Expected one clicked pair to become the persisted, prioritized 18% core resonance: ${JSON.stringify({ selectedCoreResonance, save: selectedCoreResonanceSave.current?.sortieResonanceSelection })}`
|
||||
);
|
||||
|
||||
const coreResonanceAutoClearFixture = await page.evaluate(() => {
|
||||
const scene = window.__HEROS_GAME__?.scene.getScene('CampScene');
|
||||
if (!scene || typeof scene.toggleSortieUnit !== 'function') {
|
||||
return { ready: false };
|
||||
}
|
||||
const before = {
|
||||
selectedUnitIds: [...scene.selectedSortieUnitIds],
|
||||
formationAssignments: { ...scene.sortieFormationAssignments },
|
||||
itemAssignments: structuredClone(scene.sortieItemAssignments ?? {}),
|
||||
focusedUnitId: scene.sortieFocusedUnitId ?? null,
|
||||
planFeedback: scene.sortiePlanFeedback,
|
||||
rosterScroll: scene.sortieRosterScroll
|
||||
};
|
||||
scene.toggleSortieUnit('zhang-fei');
|
||||
return { ready: true, before };
|
||||
});
|
||||
await page.waitForFunction(
|
||||
() => {
|
||||
const state = window.__HEROS_DEBUG__?.camp();
|
||||
return state?.sortiePursuitPreview?.selectedCoreBondId === null &&
|
||||
!state?.selectedSortieUnitIds?.includes('zhang-fei');
|
||||
},
|
||||
undefined,
|
||||
{ timeout: 30000 }
|
||||
);
|
||||
const clearedCoreResonance = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()?.sortiePursuitPreview);
|
||||
const clearedCoreCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp());
|
||||
const clearedCoreResonanceSave = await readCampaignSave(page);
|
||||
assert(
|
||||
coreResonanceAutoClearFixture.ready === true &&
|
||||
clearedCoreResonance?.selectedCoreBondId === null &&
|
||||
clearedCoreResonance?.activePairs?.every((pair) => pair.core === false) &&
|
||||
clearedCoreResonance?.rows?.every((row) => row.selected === false && row.state === 'candidate') &&
|
||||
!clearedCoreCampState?.selectedSortieUnitIds?.includes('zhang-fei') &&
|
||||
clearedCoreCampState?.sortiePlanFeedback?.includes('핵심 공명조 자동 해제') &&
|
||||
clearedCoreResonanceSave.current?.sortieResonanceSelection === undefined,
|
||||
`Expected removing a designated member to clear the persisted core pair and retain ordinary pursuit behavior: ${JSON.stringify({ clearedCoreCampState, clearedCoreResonance, save: clearedCoreResonanceSave.current?.sortieResonanceSelection })}`
|
||||
);
|
||||
await page.evaluate((before) => {
|
||||
const scene = window.__HEROS_GAME__?.scene.getScene('CampScene');
|
||||
scene.selectedSortieUnitIds = [...before.selectedUnitIds];
|
||||
scene.sortieFormationAssignments = { ...before.formationAssignments };
|
||||
scene.sortieItemAssignments = structuredClone(before.itemAssignments);
|
||||
scene.sortieFocusedUnitId = before.focusedUnitId ?? undefined;
|
||||
scene.sortiePlanFeedback = before.planFeedback;
|
||||
scene.sortieRosterScroll = before.rosterScroll;
|
||||
scene.persistSortieSelection();
|
||||
scene.showSortiePrep();
|
||||
}, coreResonanceAutoClearFixture.before);
|
||||
await page.waitForFunction(
|
||||
() => window.__HEROS_DEBUG__?.camp()?.selectedSortieUnitIds?.includes('zhang-fei') === true,
|
||||
undefined,
|
||||
{ timeout: 30000 }
|
||||
);
|
||||
|
||||
const firstSortiePursuitSaveBefore = await readCampaignSave(page);
|
||||
@@ -2598,9 +2725,33 @@ try {
|
||||
sameJsonValue(
|
||||
firstSortiePursuitSaveRestored.slot1?.sortieItemAssignments,
|
||||
firstSortiePursuitSaveBefore.slot1?.sortieItemAssignments
|
||||
),
|
||||
),
|
||||
`Expected the pursuit RC fixture to restore current and slot-1 sortie saves before launch: ${JSON.stringify(firstSortiePursuitSaveRestored)}`
|
||||
);
|
||||
|
||||
const launchCoreResonanceRow = firstSortiePursuitRestored.pursuit?.rows?.find(
|
||||
(row) => row.bondId === 'liu-bei__zhang-fei'
|
||||
);
|
||||
assert(
|
||||
isFiniteBounds(launchCoreResonanceRow?.chipBounds),
|
||||
`Expected the restored formation to keep the Liu Bei/Zhang Fei core-resonance control available: ${JSON.stringify(firstSortiePursuitRestored.pursuit)}`
|
||||
);
|
||||
await page.mouse.click(
|
||||
launchCoreResonanceRow.chipBounds.x + launchCoreResonanceRow.chipBounds.width / 2,
|
||||
launchCoreResonanceRow.chipBounds.y + launchCoreResonanceRow.chipBounds.height / 2
|
||||
);
|
||||
await page.waitForFunction(
|
||||
() => window.__HEROS_DEBUG__?.camp()?.sortiePursuitPreview?.selectedCoreBondId === 'liu-bei__zhang-fei',
|
||||
undefined,
|
||||
{ timeout: 30000 }
|
||||
);
|
||||
const launchCoreResonance = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()?.sortiePursuitPreview);
|
||||
assert(
|
||||
launchCoreResonance?.activePairs?.[0]?.id === 'liu-bei__zhang-fei' &&
|
||||
launchCoreResonance.activePairs[0].core === true &&
|
||||
launchCoreResonance.activePairs[0].chainRate === 18,
|
||||
`Expected the lower legacy-rate pair to be promoted and prioritized before battle launch: ${JSON.stringify(launchCoreResonance)}`
|
||||
);
|
||||
await advanceSortiePrepStep(page, 'loadout');
|
||||
await page.mouse.click(1116, 656);
|
||||
await advanceUntilBattle(page, 'second-battle-yellow-turban-pursuit');
|
||||
@@ -2646,6 +2797,120 @@ try {
|
||||
secondBattleProbe.sortieDoctrine?.activeBondCount === 3,
|
||||
`Expected the second battle doctrine to expose one officer per role and all three active bonds: ${JSON.stringify(secondBattleProbe.sortieDoctrine)}`
|
||||
);
|
||||
assert(
|
||||
secondBattleProbe.coreSortieResonance?.selected === true &&
|
||||
secondBattleProbe.coreSortieResonance.selectedBondId === 'liu-bei__zhang-fei' &&
|
||||
secondBattleProbe.coreSortieResonance.valid === true &&
|
||||
secondBattleProbe.coreSortieResonance.active === true &&
|
||||
secondBattleProbe.coreSortieResonance.level === 68 &&
|
||||
secondBattleProbe.coreSortieResonance.rate === 18 &&
|
||||
sameJsonValue(secondBattleProbe.coreSortieResonance.unitIds, ['liu-bei', 'zhang-fei']) &&
|
||||
sameJsonValue(secondBattleProbe.coreSortieResonance.stats, {
|
||||
attempts: 0,
|
||||
successes: 0,
|
||||
failures: 0,
|
||||
additionalDamage: 0
|
||||
}) &&
|
||||
sameJsonValue(secondBattleProbe.sortieResonance, secondBattleProbe.coreSortieResonance),
|
||||
`Expected the camp designation to reach battle as one active 18% core resonance with clean statistics: ${JSON.stringify(secondBattleProbe.coreSortieResonance)}`
|
||||
);
|
||||
const secondBattleCorePriority = await page.evaluate(() => {
|
||||
const scene = window.__HEROS_GAME__?.scene.getScene('BattleScene');
|
||||
const attacker = scene?.debugUnitById('liu-bei');
|
||||
const corePartner = scene?.debugUnitById('zhang-fei');
|
||||
const ordinaryPartner = scene?.debugUnitById('guan-yu');
|
||||
const targetState = window.__HEROS_DEBUG__?.battle()?.units?.find((unit) => unit.faction === 'enemy' && unit.hp > 0);
|
||||
const target = targetState ? scene?.debugUnitById(targetState.id) : undefined;
|
||||
if (!scene || !attacker || !corePartner || !ordinaryPartner || !target) {
|
||||
return { ready: false };
|
||||
}
|
||||
|
||||
const touchedUnits = [attacker, corePartner, ordinaryPartner, target];
|
||||
const original = {
|
||||
units: touchedUnits.map((unit) => ({ unit, x: unit.x, y: unit.y })),
|
||||
attackIntents: scene.attackIntents.map((intent) => ({ ...intent })),
|
||||
actedUnitIds: new Set(scene.actedUnitIds),
|
||||
targetHp: target.hp,
|
||||
battleStats: new Map(
|
||||
[...scene.battleStats.entries()].map(([unitId, stats]) => [unitId, structuredClone(stats)])
|
||||
)
|
||||
};
|
||||
try {
|
||||
Object.assign(attacker, { x: 5, y: 5 });
|
||||
Object.assign(corePartner, { x: 5, y: 6 });
|
||||
Object.assign(ordinaryPartner, { x: 4, y: 5 });
|
||||
Object.assign(target, { x: 6, y: 5 });
|
||||
scene.attackIntents = [
|
||||
{ attackerId: corePartner.id, targetId: target.id },
|
||||
{ attackerId: ordinaryPartner.id, targetId: target.id }
|
||||
];
|
||||
scene.actedUnitIds = new Set([...original.actedUnitIds, corePartner.id, ordinaryPartner.id]);
|
||||
const candidate = scene.activeBondChainCandidate(attacker, target, 'attack');
|
||||
const preview = scene.combatPreview(attacker, target, 'attack');
|
||||
const failedResolution = scene.resolveBondChainFollowUp(preview, true, false);
|
||||
const successfulResolution = scene.resolveBondChainFollowUp(preview, true, true);
|
||||
const coreStats = window.__HEROS_DEBUG__?.battle()?.coreSortieResonance?.stats ?? null;
|
||||
return {
|
||||
ready: true,
|
||||
candidate: candidate ? {
|
||||
bondId: candidate.bondId,
|
||||
partnerId: candidate.partner.id,
|
||||
rate: candidate.rate,
|
||||
coreResonance: candidate.coreResonance,
|
||||
label: candidate.label
|
||||
} : null,
|
||||
preview: {
|
||||
bondId: preview.bondChainBondId ?? null,
|
||||
partnerId: preview.bondChainPartnerId ?? null,
|
||||
rate: preview.bondChainRate,
|
||||
coreResonance: preview.bondChainCoreResonance,
|
||||
label: preview.bondChainLabel ?? null
|
||||
},
|
||||
failedResolution: failedResolution ? {
|
||||
succeeded: failedResolution.attempt.succeeded,
|
||||
coreResonance: failedResolution.attempt.coreResonance,
|
||||
hasResult: Boolean(failedResolution.result)
|
||||
} : null,
|
||||
successfulResolution: successfulResolution ? {
|
||||
succeeded: successfulResolution.attempt.succeeded,
|
||||
coreResonance: successfulResolution.attempt.coreResonance,
|
||||
damage: successfulResolution.result?.damage ?? 0
|
||||
} : null,
|
||||
coreStats
|
||||
};
|
||||
} finally {
|
||||
original.units.forEach(({ unit, x, y }) => Object.assign(unit, { x, y }));
|
||||
target.hp = original.targetHp;
|
||||
scene.battleStats = original.battleStats;
|
||||
scene.attackIntents = original.attackIntents;
|
||||
scene.actedUnitIds = original.actedUnitIds;
|
||||
}
|
||||
});
|
||||
assert(
|
||||
secondBattleCorePriority.ready === true &&
|
||||
secondBattleCorePriority.candidate?.bondId === 'liu-bei__zhang-fei' &&
|
||||
secondBattleCorePriority.candidate.partnerId === 'zhang-fei' &&
|
||||
secondBattleCorePriority.candidate.rate === 18 &&
|
||||
secondBattleCorePriority.candidate.coreResonance === true &&
|
||||
secondBattleCorePriority.candidate.label.includes('핵심 공명') &&
|
||||
secondBattleCorePriority.preview?.bondId === 'liu-bei__zhang-fei' &&
|
||||
secondBattleCorePriority.preview.partnerId === 'zhang-fei' &&
|
||||
secondBattleCorePriority.preview.rate === 18 &&
|
||||
secondBattleCorePriority.preview.coreResonance === true &&
|
||||
secondBattleCorePriority.failedResolution?.succeeded === false &&
|
||||
secondBattleCorePriority.failedResolution.coreResonance === true &&
|
||||
secondBattleCorePriority.failedResolution.hasResult === false &&
|
||||
secondBattleCorePriority.successfulResolution?.succeeded === true &&
|
||||
secondBattleCorePriority.successfulResolution.coreResonance === true &&
|
||||
secondBattleCorePriority.successfulResolution.damage > 0 &&
|
||||
sameJsonValue(secondBattleCorePriority.coreStats, {
|
||||
attempts: 2,
|
||||
successes: 1,
|
||||
failures: 1,
|
||||
additionalDamage: secondBattleCorePriority.successfulResolution.damage
|
||||
}),
|
||||
`Expected the designated Lv68 pair to outrank Liu Bei's ordinary Lv72 pursuit candidate: ${JSON.stringify(secondBattleCorePriority)}`
|
||||
);
|
||||
assert(
|
||||
secondBattleProbe.openingBannerTexts?.some((text) => text.includes('세 형제 생존 중 공명 지원 거리 2칸')) &&
|
||||
secondBattleProbe.openingBannerTexts?.some((text) => text.includes('전술 명령')),
|
||||
@@ -2679,7 +2944,13 @@ try {
|
||||
const loadAssignments = (savedCampaignRaw, assignments) => {
|
||||
const campaign = JSON.parse(savedCampaignRaw);
|
||||
campaign.sortieFormationAssignments = assignments;
|
||||
delete campaign.sortieResonanceSelection;
|
||||
window.localStorage.setItem(campaignSlotKey, JSON.stringify(campaign));
|
||||
scene.launchSortieResonanceBondId = undefined;
|
||||
scene.coreResonancePursuitAttempts = 0;
|
||||
scene.coreResonancePursuitSuccesses = 0;
|
||||
scene.coreResonancePursuitFailures = 0;
|
||||
scene.coreResonancePursuitDamage = 0;
|
||||
scene.loadBattleState?.(1);
|
||||
};
|
||||
|
||||
@@ -2691,6 +2962,9 @@ try {
|
||||
|
||||
loadAssignments(savedCampaignRaw, { 'liu-bei': 'front', 'guan-yu': 'support', 'zhang-fei': 'reserve' });
|
||||
const changed = captureUnits();
|
||||
const changedCoreResonance = structuredClone(
|
||||
window.__HEROS_DEBUG__?.battle()?.coreSortieResonance ?? null
|
||||
);
|
||||
|
||||
loadAssignments(savedCampaignRaw, { 'liu-bei': 'reserve', 'guan-yu': 'reserve', 'zhang-fei': 'reserve' });
|
||||
scene.hideBattleEventBanner?.();
|
||||
@@ -2706,8 +2980,16 @@ try {
|
||||
scene.hideBattleEventBanner?.();
|
||||
|
||||
window.localStorage.setItem(campaignSlotKey, savedCampaignRaw);
|
||||
scene.launchSortieResonanceBondId = undefined;
|
||||
scene.coreResonancePursuitAttempts = 0;
|
||||
scene.coreResonancePursuitSuccesses = 0;
|
||||
scene.coreResonancePursuitFailures = 0;
|
||||
scene.coreResonancePursuitDamage = 0;
|
||||
scene.loadBattleState?.(1);
|
||||
const restored = captureUnits();
|
||||
const restoredCoreResonance = structuredClone(
|
||||
window.__HEROS_DEBUG__?.battle()?.coreSortieResonance ?? null
|
||||
);
|
||||
storageSnapshot.forEach((value, key) => {
|
||||
if (value === null) {
|
||||
window.localStorage.removeItem(key);
|
||||
@@ -2715,7 +2997,7 @@ try {
|
||||
window.localStorage.setItem(key, value);
|
||||
}
|
||||
});
|
||||
return { changed, allReserve, restored };
|
||||
return { changed, changedCoreResonance, allReserve, restored, restoredCoreResonance };
|
||||
});
|
||||
assert(
|
||||
JSON.stringify(roleSealResyncProbe?.changed) === JSON.stringify([
|
||||
@@ -2725,6 +3007,17 @@ try {
|
||||
]),
|
||||
`Expected role seals to resync when a loaded formation changes roles: ${JSON.stringify(roleSealResyncProbe)}`
|
||||
);
|
||||
assert(
|
||||
roleSealResyncProbe?.changedCoreResonance?.selectedBondId === 'liu-bei__zhang-fei' &&
|
||||
roleSealResyncProbe.changedCoreResonance.active === true &&
|
||||
sameJsonValue(roleSealResyncProbe.changedCoreResonance.stats, {
|
||||
attempts: 2,
|
||||
successes: 1,
|
||||
failures: 1,
|
||||
additionalDamage: secondBattleCorePriority.successfulResolution.damage
|
||||
}),
|
||||
`Expected battle save data—not the cleared in-memory or campaign fallback—to restore the core designation and totals: ${JSON.stringify(roleSealResyncProbe?.changedCoreResonance)}`
|
||||
);
|
||||
assert(
|
||||
roleSealResyncProbe?.allReserve?.doctrineActive === true &&
|
||||
roleSealResyncProbe?.allReserve?.coveredRoleCount === 0 &&
|
||||
@@ -2739,6 +3032,45 @@ try {
|
||||
]),
|
||||
`Expected loaded role seals to restore the original formation cleanly: ${JSON.stringify(roleSealResyncProbe?.restored)}`
|
||||
);
|
||||
assert(
|
||||
roleSealResyncProbe?.restoredCoreResonance?.selectedBondId === 'liu-bei__zhang-fei' &&
|
||||
roleSealResyncProbe.restoredCoreResonance.active === true &&
|
||||
roleSealResyncProbe.restoredCoreResonance.rate === 18 &&
|
||||
sameJsonValue(roleSealResyncProbe.restoredCoreResonance.stats, {
|
||||
attempts: 2,
|
||||
successes: 1,
|
||||
failures: 1,
|
||||
additionalDamage: secondBattleCorePriority.successfulResolution.damage
|
||||
}),
|
||||
`Expected battle-slot reloads to preserve the selected core pair and its accumulated pursuit statistics: ${JSON.stringify(roleSealResyncProbe?.restoredCoreResonance)}`
|
||||
);
|
||||
|
||||
await page.evaluate(() => window.__HEROS_DEBUG__?.forceBattleOutcome('victory'));
|
||||
await page.waitForFunction(() => window.__HEROS_DEBUG__?.battle()?.resultVisible === true, undefined, { timeout: 30000 });
|
||||
const secondBattleCoreResult = await page.evaluate(() => {
|
||||
const scene = window.__HEROS_GAME__?.scene.getScene('BattleScene');
|
||||
const resonance = window.__HEROS_DEBUG__?.battle()?.coreSortieResonance;
|
||||
const texts = (scene?.resultObjects ?? [])
|
||||
.filter((object) => object?.type === 'Text')
|
||||
.map((object) => object.text);
|
||||
return {
|
||||
stats: resonance?.stats ?? null,
|
||||
resultSummary: resonance?.resultSummary ?? null,
|
||||
summary: texts.find((text) => text.startsWith('핵심 공명 · 시도')) ?? null
|
||||
};
|
||||
});
|
||||
await page.screenshot({ path: `${screenshotDir}/rc-second-battle-core-resonance-result.png`, fullPage: true });
|
||||
assert(
|
||||
secondBattleCoreResult.summary ===
|
||||
`핵심 공명 · 시도 2 · 성공 1 · 추가 피해 +${secondBattleCorePriority.successfulResolution.damage}` &&
|
||||
sameJsonValue(secondBattleCoreResult.stats, roleSealResyncProbe.restoredCoreResonance.stats) &&
|
||||
secondBattleCoreResult.resultSummary?.visible === true &&
|
||||
secondBattleCoreResult.resultSummary.text === secondBattleCoreResult.summary &&
|
||||
isFiniteBounds(secondBattleCoreResult.resultSummary.bounds) &&
|
||||
boundsInside(secondBattleCoreResult.resultSummary.bounds, logicalViewportBounds) &&
|
||||
secondBattleCoreResult.resultSummary.bounds.y + secondBattleCoreResult.resultSummary.bounds.height <= 416,
|
||||
`Expected the victory report to surface persisted core-resonance attempts, successes, and damage: ${JSON.stringify(secondBattleCoreResult)}`
|
||||
);
|
||||
|
||||
await seedCampaignSave(page, {
|
||||
battleId: 'fifty-eighth-battle-qishan-retreat',
|
||||
@@ -2846,6 +3178,107 @@ try {
|
||||
`Expected all 23 campaign officers to use loaded lightweight portrait cards: ${JSON.stringify(missingMidFormationPortraits)}`
|
||||
);
|
||||
|
||||
const midCoreSelectorToggle = midFormationPortraitState?.sortiePursuitPreview;
|
||||
assert(
|
||||
midCoreSelectorToggle?.selectorOpen === false &&
|
||||
midCoreSelectorToggle.toggleLabel === '핵심 공명조' &&
|
||||
isFiniteBounds(midCoreSelectorToggle.toggleBounds) &&
|
||||
isFiniteBounds(midCoreSelectorToggle.toggleClickBounds) &&
|
||||
boundsInside(midCoreSelectorToggle.toggleBounds, midCoreSelectorToggle.panelBounds),
|
||||
`Expected the generic formation comparison panel to expose an in-panel core-resonance mode switch: ${JSON.stringify(midCoreSelectorToggle)}`
|
||||
);
|
||||
await page.mouse.click(
|
||||
midCoreSelectorToggle.toggleClickBounds.x + midCoreSelectorToggle.toggleClickBounds.width / 2,
|
||||
midCoreSelectorToggle.toggleClickBounds.y + midCoreSelectorToggle.toggleClickBounds.height / 2
|
||||
);
|
||||
await page.waitForFunction(
|
||||
() => window.__HEROS_DEBUG__?.camp()?.sortiePursuitPreview?.selectorOpen === true,
|
||||
undefined,
|
||||
{ timeout: 30000 }
|
||||
);
|
||||
const midCoreSelectorState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp());
|
||||
const midCoreResonancePageOne = midCoreSelectorState?.sortiePursuitPreview;
|
||||
assert(
|
||||
midCoreResonancePageOne?.coreCandidates?.length > 3 &&
|
||||
midCoreResonancePageOne.selectorOpen === true &&
|
||||
midCoreResonancePageOne.toggleLabel === '편성 비교' &&
|
||||
midCoreResonancePageOne.page === 0 &&
|
||||
midCoreResonancePageOne.pageCount === Math.ceil(midCoreResonancePageOne.coreCandidates.length / 3) &&
|
||||
midCoreResonancePageOne.rows.length === 3 &&
|
||||
midCoreResonancePageOne.prevEnabled === false &&
|
||||
midCoreResonancePageOne.nextEnabled === true &&
|
||||
midCoreResonancePageOne.prevClickBounds === null &&
|
||||
isFiniteBounds(midCoreResonancePageOne.nextClickBounds) &&
|
||||
boundsInside(midCoreResonancePageOne.nextBounds, midCoreResonancePageOne.panelBounds),
|
||||
`Expected the mid-campaign formation to paginate more than three eligible core-resonance pairs: ${JSON.stringify(midCoreResonancePageOne)}`
|
||||
);
|
||||
const midCoreFirstPageIds = midCoreResonancePageOne.rows.map((row) => row.bondId);
|
||||
await page.mouse.click(
|
||||
midCoreResonancePageOne.nextClickBounds.x + midCoreResonancePageOne.nextClickBounds.width / 2,
|
||||
midCoreResonancePageOne.nextClickBounds.y + midCoreResonancePageOne.nextClickBounds.height / 2
|
||||
);
|
||||
await page.waitForFunction(
|
||||
() => window.__HEROS_DEBUG__?.camp()?.sortiePursuitPreview?.page === 1,
|
||||
undefined,
|
||||
{ timeout: 30000 }
|
||||
);
|
||||
const midCoreResonancePageTwo = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()?.sortiePursuitPreview);
|
||||
await page.screenshot({ path: `${screenshotDir}/rc-mid-camp-core-resonance-page-2.png`, fullPage: true });
|
||||
assert(
|
||||
midCoreResonancePageTwo?.page === 1 &&
|
||||
midCoreResonancePageTwo.pageText === `2/${midCoreResonancePageTwo.pageCount}` &&
|
||||
midCoreResonancePageTwo.prevEnabled === true &&
|
||||
isFiniteBounds(midCoreResonancePageTwo.prevClickBounds) &&
|
||||
midCoreResonancePageTwo.rows.length > 0 &&
|
||||
midCoreResonancePageTwo.rows.every(
|
||||
(row) => !midCoreFirstPageIds.includes(row.bondId) &&
|
||||
isFiniteBounds(row.chipBounds) &&
|
||||
boundsInside(row.chipBounds, midCoreResonancePageTwo.panelBounds)
|
||||
),
|
||||
`Expected the next-page control to expose later eligible resonance pairs inside the HD panel: ${JSON.stringify(midCoreResonancePageTwo)}`
|
||||
);
|
||||
const midCoreLaterCandidate = midCoreResonancePageTwo.rows[0];
|
||||
await page.mouse.click(
|
||||
midCoreLaterCandidate.chipBounds.x + midCoreLaterCandidate.chipBounds.width / 2,
|
||||
midCoreLaterCandidate.chipBounds.y + midCoreLaterCandidate.chipBounds.height / 2
|
||||
);
|
||||
await page.waitForFunction(
|
||||
(bondId) => {
|
||||
const pursuit = window.__HEROS_DEBUG__?.camp()?.sortiePursuitPreview;
|
||||
return pursuit?.selectedCoreBondId === bondId && pursuit?.page === 0 && pursuit?.rows?.[0]?.bondId === bondId;
|
||||
},
|
||||
midCoreLaterCandidate.bondId,
|
||||
{ timeout: 30000 }
|
||||
);
|
||||
const midCoreLaterSelected = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()?.sortiePursuitPreview);
|
||||
assert(
|
||||
midCoreLaterSelected?.selectedCoreBondId === midCoreLaterCandidate.bondId &&
|
||||
midCoreLaterSelected.page === 0 &&
|
||||
midCoreLaterSelected.rows[0]?.bondId === midCoreLaterCandidate.bondId &&
|
||||
midCoreLaterSelected.rows[0].selected === true &&
|
||||
midCoreLaterSelected.rows[0].state === 'selected',
|
||||
`Expected a pair reached through page two to become the visible persisted core designation: ${JSON.stringify(midCoreLaterSelected)}`
|
||||
);
|
||||
await page.mouse.click(
|
||||
midCoreLaterSelected.rows[0].chipBounds.x + midCoreLaterSelected.rows[0].chipBounds.width / 2,
|
||||
midCoreLaterSelected.rows[0].chipBounds.y + midCoreLaterSelected.rows[0].chipBounds.height / 2
|
||||
);
|
||||
await page.waitForFunction(
|
||||
() => window.__HEROS_DEBUG__?.camp()?.sortiePursuitPreview?.selectedCoreBondId === null,
|
||||
undefined,
|
||||
{ timeout: 30000 }
|
||||
);
|
||||
const midCoreSelectorAfterClear = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()?.sortiePursuitPreview);
|
||||
await page.mouse.click(
|
||||
midCoreSelectorAfterClear.toggleClickBounds.x + midCoreSelectorAfterClear.toggleClickBounds.width / 2,
|
||||
midCoreSelectorAfterClear.toggleClickBounds.y + midCoreSelectorAfterClear.toggleClickBounds.height / 2
|
||||
);
|
||||
await page.waitForFunction(
|
||||
() => window.__HEROS_DEBUG__?.camp()?.sortiePursuitPreview?.selectorOpen === false,
|
||||
undefined,
|
||||
{ timeout: 30000 }
|
||||
);
|
||||
|
||||
const midFormationSelectedBefore = [...(midFormationPortraitState?.selectedSortieUnitIds ?? [])];
|
||||
const midFormationCampaignSelectedBefore = [...(midFormationPortraitState?.campaign?.selectedSortieUnitIds ?? [])];
|
||||
const midFormationAssignmentsBefore = { ...(midFormationPortraitState?.sortieFormationAssignments ?? {}) };
|
||||
|
||||
Reference in New Issue
Block a user