feat: carry sortie doctrine through campaign battles

This commit is contained in:
2026-07-10 14:14:54 +09:00
parent 3caf03fe2f
commit 4a5a73a220
8 changed files with 792 additions and 125 deletions

View File

@@ -1277,6 +1277,7 @@ async function setupBattle(page, battle) {
{ timeout: 90000 }
);
await startDeploymentIfNeeded(page, battle.id);
await verifySortieDoctrine(page, battle);
await normalizeRepresentativeBattleUnits(page, battle);
}
@@ -1414,6 +1415,7 @@ async function setupCumulativeBattle(page, battle, firstBattle) {
{ timeout: 90000 }
);
await startDeploymentIfNeeded(page, battle.id);
await verifySortieDoctrine(page, battle);
await normalizeRepresentativeBattleUnits(page, battle);
return preparation;
}
@@ -1520,6 +1522,48 @@ async function startDeploymentIfNeeded(page, battleId) {
);
}
async function verifySortieDoctrine(page, battle) {
const state = await page.evaluate(() => window.__HEROS_DEBUG__?.battle());
const doctrine = state?.sortieDoctrine;
if (!doctrine?.active || doctrine.battleId !== battle.id || doctrine.openingBannerShown !== true) {
throw new Error(`Sortie doctrine did not activate for ${battle.id}: ${JSON.stringify(doctrine)}`);
}
const expectedFirstPursuit = battle.id === 'second-battle-yellow-turban-pursuit';
if (
state.firstSortieRoleEffects?.active !== expectedFirstPursuit ||
state.tacticalInitiative?.active !== expectedFirstPursuit
) {
throw new Error(
`First-pursuit-only mechanics leaked for ${battle.id}: ${JSON.stringify({
firstSortieRoleEffects: state.firstSortieRoleEffects,
tacticalInitiative: state.tacticalInitiative
})}`
);
}
const roleGroups = new Map((doctrine.roles ?? []).map((group) => [group.role, new Set(group.unitIds ?? [])]));
const coreRoles = new Set(['front', 'flank', 'support']);
const sealByRole = { front: '전', flank: '돌', support: '후' };
const deployedAllies = (state.units ?? []).filter((unit) => unit.faction === 'ally');
for (const unit of deployedAllies) {
if (coreRoles.has(unit.formationRole)) {
if (
!unit.sortieRoleEffect ||
!unit.roleSealPresent ||
unit.roleSealText !== sealByRole[unit.formationRole] ||
!roleGroups.get(unit.formationRole)?.has(unit.id)
) {
throw new Error(`Missing doctrine effect or seal for ${battle.id}/${unit.id}: ${JSON.stringify(unit)}`);
}
continue;
}
if (unit.formationRole === 'reserve' && (unit.sortieRoleEffect || unit.roleSealPresent || unit.roleSealText !== null)) {
throw new Error(`Reserve unit received a doctrine effect for ${battle.id}/${unit.id}: ${JSON.stringify(unit)}`);
}
}
}
async function writeCampaignSnapshot(page, snapshotPath) {
const snapshot = await page.evaluate(
({ slotKey, storageKey }) => window.localStorage.getItem(slotKey) ?? window.localStorage.getItem(storageKey),