Add Yi Province relief road chapter

This commit is contained in:
2026-06-23 08:55:39 +09:00
parent 2402e1c38f
commit 17cbaab55d
11 changed files with 1251 additions and 49 deletions

View File

@@ -1736,7 +1736,7 @@ try {
if (
progressTabState?.activeTab !== 'progress' ||
progressTabState.campaignProgress?.completedKnown !== 17 ||
progressTabState.campaignProgress?.totalKnown !== 26 ||
progressTabState.campaignProgress?.totalKnown !== 27 ||
progressTabState.campaignProgress?.activeChapter?.title !== '적벽대전' ||
progressTabState.campaignProgress?.latestBattleTitle !== '융중 방문로' ||
progressTabState.campaignProgress?.nextBattleTitle !== '박망파 매복전'
@@ -1888,7 +1888,7 @@ try {
if (
postBowangProgressState?.activeTab !== 'progress' ||
postBowangProgressState.campaignProgress?.completedKnown !== 18 ||
postBowangProgressState.campaignProgress?.totalKnown !== 26 ||
postBowangProgressState.campaignProgress?.totalKnown !== 27 ||
postBowangProgressState.campaignProgress?.activeChapter?.title !== '적벽대전' ||
postBowangProgressState.campaignProgress?.latestBattleTitle !== '박망파 매복전' ||
postBowangProgressState.campaignProgress?.nextBattleTitle !== '장판파 피난로'
@@ -2026,7 +2026,7 @@ try {
if (
postChangbanProgressState?.activeTab !== 'progress' ||
postChangbanProgressState.campaignProgress?.completedKnown !== 19 ||
postChangbanProgressState.campaignProgress?.totalKnown !== 26 ||
postChangbanProgressState.campaignProgress?.totalKnown !== 27 ||
postChangbanProgressState.campaignProgress?.activeChapter?.title !== '적벽대전' ||
postChangbanProgressState.campaignProgress?.latestBattleTitle !== '장판파 피난로' ||
postChangbanProgressState.campaignProgress?.nextBattleTitle !== '강동 사절로'
@@ -2164,7 +2164,7 @@ try {
if (
postJiangdongProgressState?.activeTab !== 'progress' ||
postJiangdongProgressState.campaignProgress?.completedKnown !== 20 ||
postJiangdongProgressState.campaignProgress?.totalKnown !== 26 ||
postJiangdongProgressState.campaignProgress?.totalKnown !== 27 ||
postJiangdongProgressState.campaignProgress?.activeChapter?.title !== '적벽대전' ||
postJiangdongProgressState.campaignProgress?.latestBattleTitle !== '강동 사절로' ||
postJiangdongProgressState.campaignProgress?.nextBattleTitle !== '적벽 전초전'
@@ -2304,7 +2304,7 @@ try {
if (
postRedCliffsProgressState?.activeTab !== 'progress' ||
postRedCliffsProgressState.campaignProgress?.completedKnown !== 21 ||
postRedCliffsProgressState.campaignProgress?.totalKnown !== 26 ||
postRedCliffsProgressState.campaignProgress?.totalKnown !== 27 ||
postRedCliffsProgressState.campaignProgress?.activeChapter?.title !== '적벽대전' ||
postRedCliffsProgressState.campaignProgress?.latestBattleTitle !== '적벽 전초전' ||
postRedCliffsProgressState.campaignProgress?.nextBattleTitle !== '적벽 화공전'
@@ -2444,7 +2444,7 @@ try {
if (
postFireAttackProgressState?.activeTab !== 'progress' ||
postFireAttackProgressState.campaignProgress?.completedKnown !== 22 ||
postFireAttackProgressState.campaignProgress?.totalKnown !== 26 ||
postFireAttackProgressState.campaignProgress?.totalKnown !== 27 ||
postFireAttackProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' ||
postFireAttackProgressState.campaignProgress?.latestBattleTitle !== '적벽 화공전' ||
postFireAttackProgressState.campaignProgress?.nextBattleTitle !== '형주 남부 진입전'
@@ -2587,7 +2587,7 @@ try {
if (
postJingzhouProgressState?.activeTab !== 'progress' ||
postJingzhouProgressState.campaignProgress?.completedKnown !== 23 ||
postJingzhouProgressState.campaignProgress?.totalKnown !== 26 ||
postJingzhouProgressState.campaignProgress?.totalKnown !== 27 ||
postJingzhouProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' ||
postJingzhouProgressState.campaignProgress?.latestBattleTitle !== '형주 남부 진입전' ||
postJingzhouProgressState.campaignProgress?.nextBattleTitle !== '계양 설득전'
@@ -2729,7 +2729,7 @@ try {
if (
postGuiyangProgressState?.activeTab !== 'progress' ||
postGuiyangProgressState.campaignProgress?.completedKnown !== 24 ||
postGuiyangProgressState.campaignProgress?.totalKnown !== 26 ||
postGuiyangProgressState.campaignProgress?.totalKnown !== 27 ||
postGuiyangProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' ||
postGuiyangProgressState.campaignProgress?.latestBattleTitle !== '계양 설득전' ||
postGuiyangProgressState.campaignProgress?.nextBattleTitle !== '무릉 산길 확보전'
@@ -2872,7 +2872,7 @@ try {
if (
postWulingProgressState?.activeTab !== 'progress' ||
postWulingProgressState.campaignProgress?.completedKnown !== 25 ||
postWulingProgressState.campaignProgress?.totalKnown !== 26 ||
postWulingProgressState.campaignProgress?.totalKnown !== 27 ||
postWulingProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' ||
postWulingProgressState.campaignProgress?.latestBattleTitle !== '무릉 산길 확보전' ||
postWulingProgressState.campaignProgress?.nextBattleTitle !== '장사 노장 대면전'
@@ -3021,15 +3021,160 @@ try {
if (
postChangshaProgressState?.activeTab !== 'progress' ||
postChangshaProgressState.campaignProgress?.completedKnown !== 26 ||
postChangshaProgressState.campaignProgress?.totalKnown !== 26 ||
postChangshaProgressState.campaignProgress?.totalKnown !== 27 ||
postChangshaProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' ||
postChangshaProgressState.campaignProgress?.latestBattleTitle !== '장사 노장 대면전' ||
postChangshaProgressState.campaignProgress?.nextBattleTitle !== '준비 중'
postChangshaProgressState.campaignProgress?.nextBattleTitle !== '익주 원군로'
) {
throw new Error(`Expected post-Changsha progress tab to complete the veteran-audience battle and pause on Yi Province preparation: ${JSON.stringify(postChangshaProgressState?.campaignProgress)}`);
throw new Error(`Expected post-Changsha progress tab to complete the veteran-audience battle and open Yi Province relief road: ${JSON.stringify(postChangshaProgressState?.campaignProgress)}`);
}
await page.screenshot({ path: 'dist/verification-post-changsha-progress.png', fullPage: true });
await page.mouse.click(1120, 38);
await page.waitForTimeout(180);
const yizhouSortieState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp());
if (
!yizhouSortieState?.sortieVisible ||
!yizhouSortieState.sortiePlan?.objectiveLine?.includes('익주 원군로') ||
!yizhouSortieState.sortieRoster?.some((unit) => unit.id === 'zhuge-liang' && unit.recruited && unit.recommended) ||
!yizhouSortieState.sortieRoster?.some((unit) => unit.id === 'huang-zhong' && unit.recruited && unit.recommended) ||
!yizhouSortieState.sortieRoster?.some((unit) => unit.id === 'wei-yan' && unit.recruited && unit.recommended) ||
!yizhouSortieState.sortieRoster?.some((unit) => unit.id === 'zhao-yun' && unit.recruited && unit.recommended) ||
!yizhouSortieState.sortieRoster?.some((unit) => unit.id === 'mi-zhu' && unit.recommended) ||
yizhouSortieState.sortieRoster?.some((unit) => unit.id === 'pang-tong') ||
yizhouSortieState.sortieRoster?.length < 13 ||
yizhouSortieState.sortiePlan?.maxCount !== 6
) {
throw new Error(`Expected twenty-sixth camp sortie prep to target Yi Province relief road with Huang Zhong/Wei Yan selectable and Pang Tong absent before victory: ${JSON.stringify(yizhouSortieState)}`);
}
assertSortieTacticalRoster(yizhouSortieState, [
'liu-bei',
'guan-yu',
'zhang-fei',
'jian-yong',
'mi-zhu',
'sun-qian',
'zhao-yun',
'zhuge-liang',
'ma-liang',
'yi-ji',
'gong-zhi',
'huang-zhong',
'wei-yan'
]);
const yizhouPriorityUnits = ['zhuge-liang', 'huang-zhong', 'wei-yan', 'zhao-yun', 'mi-zhu'];
for (const unitId of yizhouPriorityUnits) {
const currentSortieState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp());
if (!currentSortieState.sortieRoster?.some((unit) => unit.id === unitId && unit.selected)) {
const removable = currentSortieState.sortieRoster?.find(
(unit) => unit.selected && unit.id !== 'liu-bei' && !yizhouPriorityUnits.includes(unit.id)
);
if (removable) {
await clickSortieRosterUnit(page, removable.id);
}
await clickSortieRosterUnit(page, unitId);
}
}
const yizhouSortieReadyState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp());
if (
!yizhouPriorityUnits.every((unitId) =>
yizhouSortieReadyState.sortieRoster?.some((unit) => unit.id === unitId && unit.selected)
) ||
yizhouSortieReadyState.sortiePlan?.selectedCount !== 6 ||
yizhouSortieReadyState.sortiePlan?.recommendedSelectedCount < 6
) {
throw new Error(`Expected Yi Province sortie to deploy Zhuge Liang, Huang Zhong, Wei Yan, Zhao Yun, and Mi Zhu while preserving six-officer pressure: ${JSON.stringify(yizhouSortieReadyState)}`);
}
await page.screenshot({ path: 'dist/verification-yizhou-sortie.png', fullPage: true });
await page.mouse.click(1068, 646);
await page.waitForFunction(() => {
const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? [];
return activeScenes.includes('StoryScene');
});
await page.screenshot({ path: 'dist/verification-yizhou-story.png', fullPage: true });
for (let i = 0; i < 50; i += 1) {
const enteredTwentySeventhBattle = await page.evaluate(() => {
const state = window.__HEROS_DEBUG__?.battle();
return state?.scene === 'BattleScene' && state?.battleId === 'twenty-seventh-battle-yizhou-relief-road';
});
if (enteredTwentySeventhBattle) {
break;
}
await page.keyboard.press('Space');
await page.waitForTimeout(320);
}
await page.waitForFunction(() => {
const state = window.__HEROS_DEBUG__?.battle();
return state?.scene === 'BattleScene' && state?.battleId === 'twenty-seventh-battle-yizhou-relief-road' && state?.battleOutcome === null && state?.phase === 'idle';
});
await page.screenshot({ path: 'dist/verification-twenty-seventh-battle.png', fullPage: true });
const twentySeventhBattleState = await page.evaluate(() => window.__HEROS_DEBUG__?.battle());
const twentySeventhEnemies = twentySeventhBattleState.units.filter((unit) => unit.faction === 'enemy');
const twentySeventhAllies = twentySeventhBattleState.units.filter((unit) => unit.faction === 'ally');
const twentySeventhEnemyBehaviors = new Set(twentySeventhEnemies.map((unit) => unit.ai));
if (
twentySeventhBattleState.camera?.mapWidth !== 54 ||
twentySeventhBattleState.camera?.mapHeight !== 36 ||
twentySeventhBattleState.victoryConditionLabel !== '양회 퇴각' ||
twentySeventhEnemies.length < 19 ||
!twentySeventhEnemyBehaviors.has('aggressive') ||
!twentySeventhEnemyBehaviors.has('guard') ||
!twentySeventhEnemyBehaviors.has('hold') ||
!twentySeventhEnemies.some((unit) => unit.id === 'yizhou-leader-yang-huai') ||
!twentySeventhAllies.some((unit) => unit.id === 'huang-zhong') ||
!twentySeventhAllies.some((unit) => unit.id === 'wei-yan') ||
twentySeventhAllies.some((unit) => unit.id === 'pang-tong')
) {
throw new Error(`Expected twenty-seventh battle to use Yi Province map, Yang Huai objective, Huang Zhong/Wei Yan as selectable allies, mixed AI, and no Pang Tong before victory: ${JSON.stringify(twentySeventhBattleState)}`);
}
await page.evaluate(() => window.__HEROS_DEBUG__?.forceBattleOutcome('victory'));
await page.waitForFunction(() => {
const state = window.__HEROS_DEBUG__?.battle();
return state?.battleOutcome === 'victory' && state?.phase === 'resolved' && state?.resultVisible === true;
});
await page.mouse.click(738, 642);
await page.waitForFunction(() => {
const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? [];
return activeScenes.includes('CampScene');
});
const twentySeventhCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp());
if (
twentySeventhCampState?.campBattleId !== 'twenty-seventh-battle-yizhou-relief-road' ||
twentySeventhCampState.campTitle !== '익주 원군로 후 군영' ||
twentySeventhCampState.availableDialogueIds?.length !== 3 ||
!twentySeventhCampState.availableDialogueIds.every((id) => id.endsWith('yizhou')) ||
twentySeventhCampState.availableVisitIds?.length !== 2 ||
!twentySeventhCampState.campaign?.roster?.some((unit) => unit.id === 'pang-tong') ||
!twentySeventhCampState.sortieRoster?.some((unit) => unit.id === 'pang-tong' && unit.recruited) ||
twentySeventhCampState.rosterCollection?.total < 14
) {
throw new Error(`Expected twenty-seventh camp to recruit Pang Tong and expose Yi Province dialogue/visit sets: ${JSON.stringify(twentySeventhCampState)}`);
}
await page.screenshot({ path: 'dist/verification-yizhou-camp.png', fullPage: true });
await page.mouse.click(966, 38);
await page.waitForTimeout(180);
const postYizhouProgressState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp());
if (
postYizhouProgressState?.activeTab !== 'progress' ||
postYizhouProgressState.campaignProgress?.completedKnown !== 27 ||
postYizhouProgressState.campaignProgress?.totalKnown !== 27 ||
postYizhouProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' ||
postYizhouProgressState.campaignProgress?.latestBattleTitle !== '익주 원군로' ||
postYizhouProgressState.campaignProgress?.nextBattleTitle !== '준비 중'
) {
throw new Error(`Expected post-Yi Province progress tab to complete the relief-road battle and pause on Fu Pass preparation: ${JSON.stringify(postYizhouProgressState?.campaignProgress)}`);
}
await page.screenshot({ path: 'dist/verification-post-yizhou-progress.png', fullPage: true });
await page.evaluate(() => window.__HEROS_GAME__?.scene.start('TitleScene'));
await page.waitForFunction(() => {
const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? [];
@@ -3041,7 +3186,7 @@ try {
return activeScenes.includes('CampScene');
});
console.log(`Verified title-to-twenty-sixth-battle flow, recruited officer sortie selection, Ma Liang, Yi Ji, Gong Zhi, Huang Zhong, and Wei Yan joins, result states, and debug API at ${targetUrl}`);
console.log(`Verified title-to-twenty-seventh-battle flow, recruited officer sortie selection, Ma Liang, Yi Ji, Gong Zhi, Huang Zhong, Wei Yan, and Pang Tong joins, result states, and debug API at ${targetUrl}`);
} finally {
await browser?.close();
if (serverProcess && !serverProcess.killed) {
@@ -3157,7 +3302,8 @@ async function clickSortieRosterUnit(page, unitId) {
throw new Error(`Cannot click sortie unit ${unitId}; unit is missing from roster: ${JSON.stringify(state.sortieRoster)}`);
}
const rowGap = state.sortieRoster.length > 7 ? 30 : state.sortieRoster.length > 5 ? 38 : state.sortieRoster.length > 4 ? 43 : 48;
await page.mouse.click(254, 314 + 50 + index * rowGap);
const denseRoster = state.sortieRoster.length > 12;
const rowGap = denseRoster ? 17 : state.sortieRoster.length > 7 ? 30 : state.sortieRoster.length > 5 ? 38 : state.sortieRoster.length > 4 ? 43 : 48;
await page.mouse.click(254, 314 + (denseRoster ? 48 : 50) + index * rowGap);
await page.waitForTimeout(120);
}