diff --git a/scripts/verify-flow.mjs b/scripts/verify-flow.mjs
index 452c8c4..ae94224 100644
--- a/scripts/verify-flow.mjs
+++ b/scripts/verify-flow.mjs
@@ -1804,7 +1804,7 @@ try {
if (
progressTabState?.activeTab !== 'progress' ||
progressTabState.campaignProgress?.completedKnown !== 17 ||
- progressTabState.campaignProgress?.totalKnown !== 62 ||
+ progressTabState.campaignProgress?.totalKnown !== 63 ||
progressTabState.campaignProgress?.activeChapter?.title !== '적벽대전' ||
progressTabState.campaignProgress?.latestBattleTitle !== '융중 방문로' ||
progressTabState.campaignProgress?.nextBattleTitle !== '박망파 매복전'
@@ -1952,7 +1952,7 @@ try {
if (
postBowangProgressState?.activeTab !== 'progress' ||
postBowangProgressState.campaignProgress?.completedKnown !== 18 ||
- postBowangProgressState.campaignProgress?.totalKnown !== 62 ||
+ postBowangProgressState.campaignProgress?.totalKnown !== 63 ||
postBowangProgressState.campaignProgress?.activeChapter?.title !== '적벽대전' ||
postBowangProgressState.campaignProgress?.latestBattleTitle !== '박망파 매복전' ||
postBowangProgressState.campaignProgress?.nextBattleTitle !== '장판파 피난로'
@@ -2087,7 +2087,7 @@ try {
if (
postChangbanProgressState?.activeTab !== 'progress' ||
postChangbanProgressState.campaignProgress?.completedKnown !== 19 ||
- postChangbanProgressState.campaignProgress?.totalKnown !== 62 ||
+ postChangbanProgressState.campaignProgress?.totalKnown !== 63 ||
postChangbanProgressState.campaignProgress?.activeChapter?.title !== '적벽대전' ||
postChangbanProgressState.campaignProgress?.latestBattleTitle !== '장판파 피난로' ||
postChangbanProgressState.campaignProgress?.nextBattleTitle !== '강동 사절로'
@@ -2222,7 +2222,7 @@ try {
if (
postJiangdongProgressState?.activeTab !== 'progress' ||
postJiangdongProgressState.campaignProgress?.completedKnown !== 20 ||
- postJiangdongProgressState.campaignProgress?.totalKnown !== 62 ||
+ postJiangdongProgressState.campaignProgress?.totalKnown !== 63 ||
postJiangdongProgressState.campaignProgress?.activeChapter?.title !== '적벽대전' ||
postJiangdongProgressState.campaignProgress?.latestBattleTitle !== '강동 사절로' ||
postJiangdongProgressState.campaignProgress?.nextBattleTitle !== '적벽 전초전'
@@ -2359,7 +2359,7 @@ try {
if (
postRedCliffsProgressState?.activeTab !== 'progress' ||
postRedCliffsProgressState.campaignProgress?.completedKnown !== 21 ||
- postRedCliffsProgressState.campaignProgress?.totalKnown !== 62 ||
+ postRedCliffsProgressState.campaignProgress?.totalKnown !== 63 ||
postRedCliffsProgressState.campaignProgress?.activeChapter?.title !== '적벽대전' ||
postRedCliffsProgressState.campaignProgress?.latestBattleTitle !== '적벽 전초전' ||
postRedCliffsProgressState.campaignProgress?.nextBattleTitle !== '적벽 화공전'
@@ -2496,7 +2496,7 @@ try {
if (
postFireAttackProgressState?.activeTab !== 'progress' ||
postFireAttackProgressState.campaignProgress?.completedKnown !== 22 ||
- postFireAttackProgressState.campaignProgress?.totalKnown !== 62 ||
+ postFireAttackProgressState.campaignProgress?.totalKnown !== 63 ||
postFireAttackProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' ||
postFireAttackProgressState.campaignProgress?.latestBattleTitle !== '적벽 화공전' ||
postFireAttackProgressState.campaignProgress?.nextBattleTitle !== '형주 남부 진입전'
@@ -2636,7 +2636,7 @@ try {
if (
postJingzhouProgressState?.activeTab !== 'progress' ||
postJingzhouProgressState.campaignProgress?.completedKnown !== 23 ||
- postJingzhouProgressState.campaignProgress?.totalKnown !== 62 ||
+ postJingzhouProgressState.campaignProgress?.totalKnown !== 63 ||
postJingzhouProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' ||
postJingzhouProgressState.campaignProgress?.latestBattleTitle !== '형주 남부 진입전' ||
postJingzhouProgressState.campaignProgress?.nextBattleTitle !== '계양 설득전'
@@ -2775,7 +2775,7 @@ try {
if (
postGuiyangProgressState?.activeTab !== 'progress' ||
postGuiyangProgressState.campaignProgress?.completedKnown !== 24 ||
- postGuiyangProgressState.campaignProgress?.totalKnown !== 62 ||
+ postGuiyangProgressState.campaignProgress?.totalKnown !== 63 ||
postGuiyangProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' ||
postGuiyangProgressState.campaignProgress?.latestBattleTitle !== '계양 설득전' ||
postGuiyangProgressState.campaignProgress?.nextBattleTitle !== '무릉 산길 확보전'
@@ -2915,7 +2915,7 @@ try {
if (
postWulingProgressState?.activeTab !== 'progress' ||
postWulingProgressState.campaignProgress?.completedKnown !== 25 ||
- postWulingProgressState.campaignProgress?.totalKnown !== 62 ||
+ postWulingProgressState.campaignProgress?.totalKnown !== 63 ||
postWulingProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' ||
postWulingProgressState.campaignProgress?.latestBattleTitle !== '무릉 산길 확보전' ||
postWulingProgressState.campaignProgress?.nextBattleTitle !== '장사 노장 대면전'
@@ -3061,7 +3061,7 @@ try {
if (
postChangshaProgressState?.activeTab !== 'progress' ||
postChangshaProgressState.campaignProgress?.completedKnown !== 26 ||
- postChangshaProgressState.campaignProgress?.totalKnown !== 62 ||
+ postChangshaProgressState.campaignProgress?.totalKnown !== 63 ||
postChangshaProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' ||
postChangshaProgressState.campaignProgress?.latestBattleTitle !== '장사 노장 대면전' ||
postChangshaProgressState.campaignProgress?.nextBattleTitle !== '익주 원군로'
@@ -3203,7 +3203,7 @@ try {
if (
postYizhouProgressState?.activeTab !== 'progress' ||
postYizhouProgressState.campaignProgress?.completedKnown !== 27 ||
- postYizhouProgressState.campaignProgress?.totalKnown !== 62 ||
+ postYizhouProgressState.campaignProgress?.totalKnown !== 63 ||
postYizhouProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' ||
postYizhouProgressState.campaignProgress?.latestBattleTitle !== '익주 원군로' ||
postYizhouProgressState.campaignProgress?.nextBattleTitle !== '부수관 진입전'
@@ -3347,7 +3347,7 @@ try {
if (
postFuPassProgressState?.activeTab !== 'progress' ||
postFuPassProgressState.campaignProgress?.completedKnown !== 28 ||
- postFuPassProgressState.campaignProgress?.totalKnown !== 62 ||
+ postFuPassProgressState.campaignProgress?.totalKnown !== 63 ||
postFuPassProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' ||
postFuPassProgressState.campaignProgress?.latestBattleTitle !== '부수관 진입전' ||
postFuPassProgressState.campaignProgress?.nextBattleTitle !== '낙성 외곽전'
@@ -3493,7 +3493,7 @@ try {
if (
postLuoProgressState?.activeTab !== 'progress' ||
postLuoProgressState.campaignProgress?.completedKnown !== 29 ||
- postLuoProgressState.campaignProgress?.totalKnown !== 62 ||
+ postLuoProgressState.campaignProgress?.totalKnown !== 63 ||
postLuoProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' ||
postLuoProgressState.campaignProgress?.latestBattleTitle !== '낙성 외곽전' ||
postLuoProgressState.campaignProgress?.nextBattleTitle !== '낙봉파 매복전'
@@ -3636,7 +3636,7 @@ try {
if (
postLuofengProgressState?.activeTab !== 'progress' ||
postLuofengProgressState.campaignProgress?.completedKnown !== 30 ||
- postLuofengProgressState.campaignProgress?.totalKnown !== 62 ||
+ postLuofengProgressState.campaignProgress?.totalKnown !== 63 ||
postLuofengProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' ||
postLuofengProgressState.campaignProgress?.latestBattleTitle !== '낙봉파 매복전' ||
postLuofengProgressState.campaignProgress?.nextBattleTitle !== '낙성 본성 공략'
@@ -3785,7 +3785,7 @@ try {
if (
postLuoMainProgressState?.activeTab !== 'progress' ||
postLuoMainProgressState.campaignProgress?.completedKnown !== 31 ||
- postLuoMainProgressState.campaignProgress?.totalKnown !== 62 ||
+ postLuoMainProgressState.campaignProgress?.totalKnown !== 63 ||
postLuoMainProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' ||
postLuoMainProgressState.campaignProgress?.latestBattleTitle !== '낙성 본성 공략' ||
postLuoMainProgressState.campaignProgress?.nextBattleTitle !== '면죽관 압박전'
@@ -3935,7 +3935,7 @@ try {
if (
postMianzhuProgressState?.activeTab !== 'progress' ||
postMianzhuProgressState.campaignProgress?.completedKnown !== 32 ||
- postMianzhuProgressState.campaignProgress?.totalKnown !== 62 ||
+ postMianzhuProgressState.campaignProgress?.totalKnown !== 63 ||
postMianzhuProgressState.campaignProgress?.activeChapter?.title !== '형주·익주 확보' ||
postMianzhuProgressState.campaignProgress?.latestBattleTitle !== '면죽관 압박전' ||
postMianzhuProgressState.campaignProgress?.nextBattleTitle !== '성도 항복 권고전'
@@ -4086,7 +4086,7 @@ try {
if (
postChengduProgressState?.activeTab !== 'progress' ||
postChengduProgressState.campaignProgress?.completedKnown !== 33 ||
- postChengduProgressState.campaignProgress?.totalKnown !== 62 ||
+ postChengduProgressState.campaignProgress?.totalKnown !== 63 ||
postChengduProgressState.campaignProgress?.activeChapter?.title !== '촉한 건국' ||
postChengduProgressState.campaignProgress?.latestBattleTitle !== '성도 항복 권고전' ||
postChengduProgressState.campaignProgress?.nextBattleTitle !== '가맹관 마초 대면전'
@@ -4238,7 +4238,7 @@ try {
if (
postJiamengProgressState?.activeTab !== 'progress' ||
postJiamengProgressState.campaignProgress?.completedKnown !== 34 ||
- postJiamengProgressState.campaignProgress?.totalKnown !== 62 ||
+ postJiamengProgressState.campaignProgress?.totalKnown !== 63 ||
postJiamengProgressState.campaignProgress?.activeChapter?.title !== '촉한 건국' ||
postJiamengProgressState.campaignProgress?.latestBattleTitle !== '가맹관 마초 대면전' ||
postJiamengProgressState.campaignProgress?.nextBattleTitle !== '양평관 정찰전'
@@ -4391,7 +4391,7 @@ try {
if (
postYangpingProgressState?.activeTab !== 'progress' ||
postYangpingProgressState.campaignProgress?.completedKnown !== 35 ||
- postYangpingProgressState.campaignProgress?.totalKnown !== 62 ||
+ postYangpingProgressState.campaignProgress?.totalKnown !== 63 ||
postYangpingProgressState.campaignProgress?.activeChapter?.title !== '촉한 건국' ||
postYangpingProgressState.campaignProgress?.latestBattleTitle !== '양평관 정찰전' ||
postYangpingProgressState.campaignProgress?.nextBattleTitle !== '정군산 전초전'
@@ -4545,7 +4545,7 @@ try {
if (
postDingjunProgressState?.activeTab !== 'progress' ||
postDingjunProgressState.campaignProgress?.completedKnown !== 36 ||
- postDingjunProgressState.campaignProgress?.totalKnown !== 62 ||
+ postDingjunProgressState.campaignProgress?.totalKnown !== 63 ||
postDingjunProgressState.campaignProgress?.activeChapter?.title !== '촉한 건국' ||
postDingjunProgressState.campaignProgress?.latestBattleTitle !== '정군산 전초전' ||
postDingjunProgressState.campaignProgress?.nextBattleTitle !== '한중 결전'
@@ -4695,7 +4695,7 @@ try {
if (
postHanzhongProgressState?.activeTab !== 'progress' ||
postHanzhongProgressState.campaignProgress?.completedKnown !== 37 ||
- postHanzhongProgressState.campaignProgress?.totalKnown !== 62 ||
+ postHanzhongProgressState.campaignProgress?.totalKnown !== 63 ||
postHanzhongProgressState.campaignProgress?.activeChapter?.title !== '촉한 건국' ||
postHanzhongProgressState.campaignProgress?.latestBattleTitle !== '한중 결전' ||
postHanzhongProgressState.campaignProgress?.nextBattleTitle !== '한중왕 즉위 준비'
@@ -4753,7 +4753,7 @@ try {
if (
postKingCouncilProgressState?.activeTab !== 'progress' ||
postKingCouncilProgressState.campaignProgress?.completedKnown !== 37 ||
- postKingCouncilProgressState.campaignProgress?.totalKnown !== 62 ||
+ postKingCouncilProgressState.campaignProgress?.totalKnown !== 63 ||
postKingCouncilProgressState.campaignProgress?.latestBattleTitle !== '한중 결전' ||
postKingCouncilProgressState.campaignProgress?.nextBattleTitle !== '촉한 건국 선포'
) {
@@ -4810,7 +4810,7 @@ try {
if (
postFoundationProgressState?.activeTab !== 'progress' ||
postFoundationProgressState.campaignProgress?.completedKnown !== 37 ||
- postFoundationProgressState.campaignProgress?.totalKnown !== 62 ||
+ postFoundationProgressState.campaignProgress?.totalKnown !== 63 ||
postFoundationProgressState.campaignProgress?.latestBattleTitle !== '한중 결전' ||
postFoundationProgressState.campaignProgress?.nextBattleTitle !== '형주 방위 전초전'
) {
@@ -4958,7 +4958,7 @@ try {
if (
postJingDefenseProgressState?.activeTab !== 'progress' ||
postJingDefenseProgressState.campaignProgress?.completedKnown !== 38 ||
- postJingDefenseProgressState.campaignProgress?.totalKnown !== 62 ||
+ postJingDefenseProgressState.campaignProgress?.totalKnown !== 63 ||
postJingDefenseProgressState.campaignProgress?.latestBattleTitle !== '형주 방위 전초전' ||
postJingDefenseProgressState.campaignProgress?.nextBattleTitle !== '번성 외곽 압박전'
) {
@@ -5108,7 +5108,7 @@ try {
if (
postFanCastleProgressState?.activeTab !== 'progress' ||
postFanCastleProgressState.campaignProgress?.completedKnown !== 39 ||
- postFanCastleProgressState.campaignProgress?.totalKnown !== 62 ||
+ postFanCastleProgressState.campaignProgress?.totalKnown !== 63 ||
postFanCastleProgressState.campaignProgress?.latestBattleTitle !== '번성 외곽 압박전' ||
postFanCastleProgressState.campaignProgress?.nextBattleTitle !== '한수 수공'
) {
@@ -5259,7 +5259,7 @@ try {
if (
postHanRiverProgressState?.activeTab !== 'progress' ||
postHanRiverProgressState.campaignProgress?.completedKnown !== 40 ||
- postHanRiverProgressState.campaignProgress?.totalKnown !== 62 ||
+ postHanRiverProgressState.campaignProgress?.totalKnown !== 63 ||
postHanRiverProgressState.campaignProgress?.latestBattleTitle !== '한수 수공' ||
postHanRiverProgressState.campaignProgress?.nextBattleTitle !== '번성 공성전'
) {
@@ -5410,7 +5410,7 @@ try {
if (
postFanSiegeProgressState?.activeTab !== 'progress' ||
postFanSiegeProgressState.campaignProgress?.completedKnown !== 41 ||
- postFanSiegeProgressState.campaignProgress?.totalKnown !== 62 ||
+ postFanSiegeProgressState.campaignProgress?.totalKnown !== 63 ||
postFanSiegeProgressState.campaignProgress?.latestBattleTitle !== '번성 공성전' ||
postFanSiegeProgressState.campaignProgress?.nextBattleTitle !== '강릉 나루 경계전'
) {
@@ -5569,7 +5569,7 @@ try {
if (
postJingRearProgressState?.activeTab !== 'progress' ||
postJingRearProgressState.campaignProgress?.completedKnown !== 42 ||
- postJingRearProgressState.campaignProgress?.totalKnown !== 62 ||
+ postJingRearProgressState.campaignProgress?.totalKnown !== 63 ||
postJingRearProgressState.campaignProgress?.latestBattleTitle !== '강릉 나루 경계전' ||
postJingRearProgressState.campaignProgress?.nextBattleTitle !== '공안 성문 변고'
) {
@@ -5721,7 +5721,7 @@ try {
if (
postGonganProgressState?.activeTab !== 'progress' ||
postGonganProgressState.campaignProgress?.completedKnown !== 43 ||
- postGonganProgressState.campaignProgress?.totalKnown !== 62 ||
+ postGonganProgressState.campaignProgress?.totalKnown !== 63 ||
postGonganProgressState.campaignProgress?.latestBattleTitle !== '공안 성문 변고' ||
postGonganProgressState.campaignProgress?.nextBattleTitle !== '맥성 고립전'
) {
@@ -5876,7 +5876,7 @@ try {
if (
postMaichengProgressState?.activeTab !== 'progress' ||
postMaichengProgressState.campaignProgress?.completedKnown !== 44 ||
- postMaichengProgressState.campaignProgress?.totalKnown !== 62 ||
+ postMaichengProgressState.campaignProgress?.totalKnown !== 63 ||
postMaichengProgressState.campaignProgress?.latestBattleTitle !== '맥성 고립전' ||
postMaichengProgressState.campaignProgress?.nextBattleTitle !== '이릉 진격로'
) {
@@ -6030,7 +6030,7 @@ try {
if (
postYilingProgressState?.activeTab !== 'progress' ||
postYilingProgressState.campaignProgress?.completedKnown !== 45 ||
- postYilingProgressState.campaignProgress?.totalKnown !== 62 ||
+ postYilingProgressState.campaignProgress?.totalKnown !== 63 ||
postYilingProgressState.campaignProgress?.latestBattleTitle !== '이릉 진격로' ||
postYilingProgressState.campaignProgress?.nextBattleTitle !== '이릉 화공전'
) {
@@ -6185,7 +6185,7 @@ try {
if (
postYilingFireProgressState?.activeTab !== 'progress' ||
postYilingFireProgressState.campaignProgress?.completedKnown !== 46 ||
- postYilingFireProgressState.campaignProgress?.totalKnown !== 62 ||
+ postYilingFireProgressState.campaignProgress?.totalKnown !== 63 ||
postYilingFireProgressState.campaignProgress?.latestBattleTitle !== '이릉 화공전' ||
postYilingFireProgressState.campaignProgress?.nextBattleTitle !== '백제성 유탁'
) {
@@ -6248,7 +6248,7 @@ try {
if (
postBaidiProgressState?.activeTab !== 'progress' ||
postBaidiProgressState.campaignProgress?.completedKnown !== 46 ||
- postBaidiProgressState.campaignProgress?.totalKnown !== 62 ||
+ postBaidiProgressState.campaignProgress?.totalKnown !== 63 ||
postBaidiProgressState.campaignProgress?.latestBattleTitle !== '이릉 화공전' ||
postBaidiProgressState.campaignProgress?.nextBattleTitle !== '남중 안정로'
) {
@@ -6406,7 +6406,7 @@ try {
if (
postNanzhongProgressState?.activeTab !== 'progress' ||
postNanzhongProgressState.campaignProgress?.completedKnown !== 47 ||
- postNanzhongProgressState.campaignProgress?.totalKnown !== 62 ||
+ postNanzhongProgressState.campaignProgress?.totalKnown !== 63 ||
postNanzhongProgressState.campaignProgress?.latestBattleTitle !== '남중 안정로' ||
postNanzhongProgressState.campaignProgress?.nextBattleTitle !== '맹획 본대전'
) {
@@ -6565,7 +6565,7 @@ try {
if (
postMengHuoProgressState?.activeTab !== 'progress' ||
postMengHuoProgressState.campaignProgress?.completedKnown !== 48 ||
- postMengHuoProgressState.campaignProgress?.totalKnown !== 62 ||
+ postMengHuoProgressState.campaignProgress?.totalKnown !== 63 ||
postMengHuoProgressState.campaignProgress?.latestBattleTitle !== '맹획 본대전' ||
postMengHuoProgressState.campaignProgress?.nextBattleTitle !== '칠종칠금 2차전'
) {
@@ -6724,7 +6724,7 @@ try {
if (
postSecondCaptureProgressState?.activeTab !== 'progress' ||
postSecondCaptureProgressState.campaignProgress?.completedKnown !== 49 ||
- postSecondCaptureProgressState.campaignProgress?.totalKnown !== 62 ||
+ postSecondCaptureProgressState.campaignProgress?.totalKnown !== 63 ||
postSecondCaptureProgressState.campaignProgress?.latestBattleTitle !== '칠종칠금 2차전' ||
postSecondCaptureProgressState.campaignProgress?.nextBattleTitle !== '칠종칠금 3차전'
) {
@@ -6886,7 +6886,7 @@ try {
if (
postThirdCaptureProgressState?.activeTab !== 'progress' ||
postThirdCaptureProgressState.campaignProgress?.completedKnown !== 50 ||
- postThirdCaptureProgressState.campaignProgress?.totalKnown !== 62 ||
+ postThirdCaptureProgressState.campaignProgress?.totalKnown !== 63 ||
postThirdCaptureProgressState.campaignProgress?.latestBattleTitle !== '칠종칠금 3차전' ||
postThirdCaptureProgressState.campaignProgress?.nextBattleTitle !== '칠종칠금 4차전'
) {
@@ -7049,7 +7049,7 @@ try {
if (
postFourthCaptureProgressState?.activeTab !== 'progress' ||
postFourthCaptureProgressState.campaignProgress?.completedKnown !== 51 ||
- postFourthCaptureProgressState.campaignProgress?.totalKnown !== 62 ||
+ postFourthCaptureProgressState.campaignProgress?.totalKnown !== 63 ||
postFourthCaptureProgressState.campaignProgress?.latestBattleTitle !== '칠종칠금 4차전' ||
postFourthCaptureProgressState.campaignProgress?.nextBattleTitle !== '칠종칠금 5차전'
) {
@@ -7212,7 +7212,7 @@ try {
if (
postFifthCaptureProgressState?.activeTab !== 'progress' ||
postFifthCaptureProgressState.campaignProgress?.completedKnown !== 52 ||
- postFifthCaptureProgressState.campaignProgress?.totalKnown !== 62 ||
+ postFifthCaptureProgressState.campaignProgress?.totalKnown !== 63 ||
postFifthCaptureProgressState.campaignProgress?.latestBattleTitle !== '칠종칠금 5차전' ||
postFifthCaptureProgressState.campaignProgress?.nextBattleTitle !== '칠종칠금 6차전'
) {
@@ -7374,7 +7374,7 @@ try {
if (
postSixthCaptureProgressState?.activeTab !== 'progress' ||
postSixthCaptureProgressState.campaignProgress?.completedKnown !== 53 ||
- postSixthCaptureProgressState.campaignProgress?.totalKnown !== 62 ||
+ postSixthCaptureProgressState.campaignProgress?.totalKnown !== 63 ||
postSixthCaptureProgressState.campaignProgress?.latestBattleTitle !== '칠종칠금 6차전' ||
postSixthCaptureProgressState.campaignProgress?.nextBattleTitle !== '칠종칠금 7차전'
) {
@@ -7537,7 +7537,7 @@ try {
if (
postFinalCaptureProgressState?.activeTab !== 'progress' ||
postFinalCaptureProgressState.campaignProgress?.completedKnown !== 54 ||
- postFinalCaptureProgressState.campaignProgress?.totalKnown !== 62 ||
+ postFinalCaptureProgressState.campaignProgress?.totalKnown !== 63 ||
postFinalCaptureProgressState.campaignProgress?.activeChapter?.id !== 'northern-campaign' ||
postFinalCaptureProgressState.campaignProgress?.latestBattleTitle !== '칠종칠금 7차전' ||
!String(postFinalCaptureProgressState.campaignProgress?.nextBattleTitle ?? '').includes('북벌 준비')
@@ -7607,7 +7607,7 @@ try {
if (
postNorthernPrepProgressState?.activeTab !== 'progress' ||
postNorthernPrepProgressState.campaignProgress?.completedKnown !== 54 ||
- postNorthernPrepProgressState.campaignProgress?.totalKnown !== 62 ||
+ postNorthernPrepProgressState.campaignProgress?.totalKnown !== 63 ||
postNorthernPrepProgressState.campaignProgress?.activeChapter?.id !== 'northern-campaign' ||
postNorthernPrepProgressState.campaignProgress?.latestBattleTitle !== '칠종칠금 7차전' ||
postNorthernPrepProgressState.campaignProgress?.nextBattleTitle !== '제1차 북벌 출진로'
@@ -7754,7 +7754,7 @@ try {
if (
postFiftyFifthProgressState?.activeTab !== 'progress' ||
postFiftyFifthProgressState.campaignProgress?.completedKnown !== 55 ||
- postFiftyFifthProgressState.campaignProgress?.totalKnown !== 62 ||
+ postFiftyFifthProgressState.campaignProgress?.totalKnown !== 63 ||
postFiftyFifthProgressState.campaignProgress?.activeChapter?.id !== 'northern-campaign' ||
postFiftyFifthProgressState.campaignProgress?.latestBattleTitle !== '제1차 북벌 출진로' ||
postFiftyFifthProgressState.campaignProgress?.nextBattleTitle !== '천수 진군로'
@@ -7901,7 +7901,7 @@ try {
if (
postFiftySixthProgressState?.activeTab !== 'progress' ||
postFiftySixthProgressState.campaignProgress?.completedKnown !== 56 ||
- postFiftySixthProgressState.campaignProgress?.totalKnown !== 62 ||
+ postFiftySixthProgressState.campaignProgress?.totalKnown !== 63 ||
postFiftySixthProgressState.campaignProgress?.activeChapter?.id !== 'northern-campaign' ||
postFiftySixthProgressState.campaignProgress?.latestBattleTitle !== '천수 진군로' ||
postFiftySixthProgressState.campaignProgress?.nextBattleTitle !== '가정 배치 위기'
@@ -8053,7 +8053,7 @@ try {
if (
postFiftySeventhProgressState?.activeTab !== 'progress' ||
postFiftySeventhProgressState.campaignProgress?.completedKnown !== 57 ||
- postFiftySeventhProgressState.campaignProgress?.totalKnown !== 62 ||
+ postFiftySeventhProgressState.campaignProgress?.totalKnown !== 63 ||
postFiftySeventhProgressState.campaignProgress?.activeChapter?.id !== 'northern-campaign' ||
postFiftySeventhProgressState.campaignProgress?.latestBattleTitle !== '가정 배치 위기' ||
postFiftySeventhProgressState.campaignProgress?.nextBattleTitle !== '기산 후퇴로'
@@ -8201,7 +8201,7 @@ try {
if (
postFiftyEighthProgressState?.activeTab !== 'progress' ||
postFiftyEighthProgressState.campaignProgress?.completedKnown !== 58 ||
- postFiftyEighthProgressState.campaignProgress?.totalKnown !== 62 ||
+ postFiftyEighthProgressState.campaignProgress?.totalKnown !== 63 ||
postFiftyEighthProgressState.campaignProgress?.activeChapter?.id !== 'northern-campaign' ||
postFiftyEighthProgressState.campaignProgress?.latestBattleTitle !== '기산 후퇴로' ||
postFiftyEighthProgressState.campaignProgress?.nextBattleTitle !== '진창 공성전'
@@ -8349,7 +8349,7 @@ try {
if (
postFiftyNinthProgressState?.activeTab !== 'progress' ||
postFiftyNinthProgressState.campaignProgress?.completedKnown !== 59 ||
- postFiftyNinthProgressState.campaignProgress?.totalKnown !== 62 ||
+ postFiftyNinthProgressState.campaignProgress?.totalKnown !== 63 ||
postFiftyNinthProgressState.campaignProgress?.activeChapter?.id !== 'northern-campaign' ||
postFiftyNinthProgressState.campaignProgress?.latestBattleTitle !== '진창 공성전' ||
postFiftyNinthProgressState.campaignProgress?.nextBattleTitle !== '무도·음평 확보전'
@@ -8503,7 +8503,7 @@ try {
if (
postSixtiethProgressState?.activeTab !== 'progress' ||
postSixtiethProgressState.campaignProgress?.completedKnown !== 60 ||
- postSixtiethProgressState.campaignProgress?.totalKnown !== 62 ||
+ postSixtiethProgressState.campaignProgress?.totalKnown !== 63 ||
postSixtiethProgressState.campaignProgress?.activeChapter?.id !== 'northern-campaign' ||
postSixtiethProgressState.campaignProgress?.latestBattleTitle !== '무도·음평 확보전' ||
postSixtiethProgressState.campaignProgress?.nextBattleTitle !== '한중 우로 방어전'
@@ -8660,7 +8660,7 @@ try {
if (
postSixtyFirstProgressState?.activeTab !== 'progress' ||
postSixtyFirstProgressState.campaignProgress?.completedKnown !== 61 ||
- postSixtyFirstProgressState.campaignProgress?.totalKnown !== 62 ||
+ postSixtyFirstProgressState.campaignProgress?.totalKnown !== 63 ||
postSixtyFirstProgressState.campaignProgress?.activeChapter?.id !== 'northern-campaign' ||
postSixtyFirstProgressState.campaignProgress?.latestBattleTitle !== '한중 우로 방어전' ||
postSixtyFirstProgressState.campaignProgress?.nextBattleTitle !== '기산 재출정전'
@@ -8820,15 +8820,194 @@ try {
if (
postSixtySecondProgressState?.activeTab !== 'progress' ||
postSixtySecondProgressState.campaignProgress?.completedKnown !== 62 ||
- postSixtySecondProgressState.campaignProgress?.totalKnown !== 62 ||
+ postSixtySecondProgressState.campaignProgress?.totalKnown !== 63 ||
postSixtySecondProgressState.campaignProgress?.activeChapter?.id !== 'northern-campaign' ||
postSixtySecondProgressState.campaignProgress?.latestBattleTitle !== '기산 재출정전' ||
- postSixtySecondProgressState.campaignProgress?.nextBattleTitle !== '준비 중'
+ postSixtySecondProgressState.campaignProgress?.nextBattleTitle !== '노성 추격전'
) {
- throw new Error(`Expected post-sixty-second progress tab to complete Qishan renewed offensive and leave the next northern step pending: ${JSON.stringify(postSixtySecondProgressState?.campaignProgress)}`);
+ throw new Error(`Expected post-sixty-second progress tab to complete Qishan renewed offensive and point toward Lucheng pursuit: ${JSON.stringify(postSixtySecondProgressState?.campaignProgress)}`);
}
await page.screenshot({ path: 'dist/verification-post-sixty-second-progress.png', fullPage: true });
+ await page.mouse.click(1120, 38);
+ await page.waitForTimeout(180);
+ const luchengPursuitSortieState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp());
+ if (
+ !luchengPursuitSortieState?.sortieVisible ||
+ luchengPursuitSortieState.campaign?.step !== 'sixty-second-camp' ||
+ luchengPursuitSortieState.campTitle !== '기산 재출정 후 군영' ||
+ !String(luchengPursuitSortieState.sortiePlan?.objectiveLine ?? '').includes('노성 추격전') ||
+ !String(luchengPursuitSortieState.sortiePlan?.objectiveLine ?? '').includes('회수 표식') ||
+ luchengPursuitSortieState.sortiePlan?.maxCount !== 7 ||
+ luchengPursuitSortieState.sortiePlan?.recommendedTotal !== 9 ||
+ !luchengPursuitSortieState.sortieRoster?.some((unit) => unit.id === 'zhuge-liang' && unit.selected && unit.required) ||
+ !luchengPursuitSortieState.sortieRoster?.some((unit) => unit.id === 'jiang-wei' && unit.recommended) ||
+ !luchengPursuitSortieState.sortieRoster?.some((unit) => unit.id === 'zhao-yun' && unit.recommended) ||
+ !luchengPursuitSortieState.sortieRoster?.some((unit) => unit.id === 'wei-yan' && unit.recommended) ||
+ !luchengPursuitSortieState.sortieRoster?.some((unit) => unit.id === 'wang-ping' && unit.recommended) ||
+ !luchengPursuitSortieState.sortieRoster?.some((unit) => unit.id === 'huang-quan' && unit.recommended) ||
+ !luchengPursuitSortieState.sortieRoster?.some((unit) => unit.id === 'li-yan' && unit.recommended) ||
+ !luchengPursuitSortieState.sortieRoster?.some((unit) => unit.id === 'ma-liang' && unit.recommended) ||
+ !luchengPursuitSortieState.sortieRoster?.some((unit) => unit.id === 'ma-dai' && unit.recommended) ||
+ luchengPursuitSortieState.sortieRoster?.some((unit) => unit.id === 'liu-bei')
+ ) {
+ throw new Error(`Expected Lucheng pursuit sortie to require Zhuge Liang, exclude Liu Bei, and recommend nine competing pursuit/return/supply roles: ${JSON.stringify(luchengPursuitSortieState)}`);
+ }
+ assertSortieTacticalRoster(luchengPursuitSortieState, [
+ 'guan-yu', 'zhang-fei', 'jian-yong', 'mi-zhu', 'sun-qian', 'zhao-yun', 'zhuge-liang', 'ma-liang', 'yi-ji', 'gong-zhi', 'huang-zhong', 'wei-yan', 'pang-tong', 'fa-zheng', 'wu-yi', 'yan-yan', 'li-yan', 'huang-quan', 'ma-chao', 'ma-dai', 'wang-ping', 'jiang-wei'
+ ]);
+
+ const luchengPursuitPriorityUnits = ['jiang-wei', 'zhao-yun', 'wei-yan', 'wang-ping', 'huang-quan', 'li-yan'];
+ for (const unitId of luchengPursuitPriorityUnits) {
+ 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.required && !luchengPursuitPriorityUnits.includes(unit.id)
+ );
+ if (removable) {
+ await clickSortieRosterUnit(page, removable.id);
+ }
+ await clickSortieRosterUnit(page, unitId);
+ }
+ }
+
+ const luchengPursuitSortieReadyState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp());
+ if (
+ !luchengPursuitPriorityUnits.every((unitId) =>
+ luchengPursuitSortieReadyState.sortieRoster?.some((unit) => unit.id === unitId && unit.selected)
+ ) ||
+ !luchengPursuitSortieReadyState.sortieRoster?.some((unit) => unit.id === 'zhuge-liang' && unit.selected && unit.required) ||
+ luchengPursuitSortieReadyState.sortieRoster?.some((unit) => unit.id === 'liu-bei') ||
+ luchengPursuitSortieReadyState.sortieRoster?.some((unit) => unit.id === 'ma-liang' && unit.selected) ||
+ luchengPursuitSortieReadyState.sortieRoster?.some((unit) => unit.id === 'ma-dai' && unit.selected) ||
+ luchengPursuitSortieReadyState.sortiePlan?.selectedCount !== 7 ||
+ luchengPursuitSortieReadyState.sortiePlan?.recommendedSelectedCount < 7 ||
+ luchengPursuitSortieReadyState.sortiePlan?.recommendedTotal !== 9
+ ) {
+ throw new Error(`Expected Lucheng pursuit sortie to deploy Zhuge Liang plus six pursuit/return/supply officers while benching Ma Liang and Ma Dai: ${JSON.stringify(luchengPursuitSortieReadyState)}`);
+ }
+ await page.screenshot({ path: 'dist/verification-sixty-third-sortie.png', fullPage: true });
+
+ await page.mouse.click(1068, 646);
+ await waitForStoryReady(page);
+ for (let i = 0; i < 12; i += 1) {
+ const storyState = await page.evaluate(() =>
+ window.__HEROS_GAME__?.scene.getScene('StoryScene')?.getDebugState?.()
+ );
+ if (storyState?.background === 'story-lucheng-pursuit') {
+ break;
+ }
+ await page.keyboard.press('Space');
+ await page.waitForTimeout(320);
+ }
+ const luchengPursuitStoryState = await page.evaluate(() =>
+ window.__HEROS_GAME__?.scene.getScene('StoryScene')?.getDebugState?.()
+ );
+ if (
+ luchengPursuitStoryState?.background !== 'story-lucheng-pursuit' ||
+ luchengPursuitStoryState?.backgroundReady !== true
+ ) {
+ throw new Error(`Expected Lucheng pursuit story to lazy-load its new generated background: ${JSON.stringify(luchengPursuitStoryState)}`);
+ }
+ await page.screenshot({ path: 'dist/verification-sixty-third-story.png', fullPage: true });
+
+ for (let i = 0; i < 50; i += 1) {
+ const enteredSixtyThirdBattle = await page.evaluate(() => {
+ const state = window.__HEROS_DEBUG__?.battle();
+ return state?.scene === 'BattleScene' && state?.battleId === 'sixty-third-battle-lucheng-pursuit';
+ });
+ if (enteredSixtyThirdBattle) {
+ 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 === 'sixty-third-battle-lucheng-pursuit' && state?.battleOutcome === null && state?.phase === 'idle';
+ });
+ await page.screenshot({ path: 'dist/verification-sixty-third-battle.png', fullPage: true });
+
+ const sixtyThirdBattleState = await page.evaluate(() => window.__HEROS_DEBUG__?.battle());
+ const sixtyThirdEnemies = sixtyThirdBattleState.units.filter((unit) => unit.faction === 'enemy');
+ const sixtyThirdAllies = sixtyThirdBattleState.units.filter((unit) => unit.faction === 'ally');
+ const sixtyThirdEnemyBehaviors = new Set(sixtyThirdEnemies.map((unit) => unit.ai));
+ if (
+ sixtyThirdBattleState.camera?.mapWidth !== 126 ||
+ sixtyThirdBattleState.camera?.mapHeight !== 108 ||
+ sixtyThirdBattleState.mapTextureKey !== 'battle-map-sixty-third' ||
+ sixtyThirdBattleState.mapTextureReady !== true ||
+ sixtyThirdBattleState.mapBackgroundReady !== true ||
+ sixtyThirdBattleState.victoryConditionLabel !== '노성 추격로 확보와 회수 표식 유지' ||
+ !sixtyThirdBattleState.objectives?.some((objective) => objective.id === 'break-sima-yi-lucheng-defense' && objective.label === '사마의 노성 방어 돌파') ||
+ !sixtyThirdBattleState.objectives?.some((objective) => objective.id === 'stop-zhang-he-return-raid' && objective.label === '장합 회수선 추격 저지') ||
+ !sixtyThirdBattleState.objectives?.some((objective) => objective.id === 'silence-guo-huai-beacons' && objective.label === '곽회 봉화 차단') ||
+ !sixtyThirdBattleState.objectives?.some((objective) => objective.id === 'hold-return-supply-camps' && objective.label === '회수 보급 진영 유지') ||
+ !sixtyThirdBattleState.objectives?.some((objective) => objective.id === 'reassure-lucheng-villages' && objective.label === '노성 주변 마을 안심') ||
+ sixtyThirdEnemies.length < 42 ||
+ !sixtyThirdEnemyBehaviors.has('aggressive') ||
+ !sixtyThirdEnemyBehaviors.has('guard') ||
+ !sixtyThirdEnemyBehaviors.has('hold') ||
+ !sixtyThirdEnemies.some((unit) => unit.id === 'northern-ninth-leader-sima-yi') ||
+ !sixtyThirdEnemies.some((unit) => unit.id === 'northern-ninth-officer-zhang-he') ||
+ !sixtyThirdEnemies.some((unit) => unit.id === 'northern-ninth-officer-guo-huai') ||
+ !sixtyThirdAllies.some((unit) => unit.id === 'zhuge-liang') ||
+ !sixtyThirdAllies.some((unit) => unit.id === 'jiang-wei') ||
+ !sixtyThirdAllies.some((unit) => unit.id === 'zhao-yun') ||
+ !sixtyThirdAllies.some((unit) => unit.id === 'wei-yan') ||
+ !sixtyThirdAllies.some((unit) => unit.id === 'wang-ping') ||
+ !sixtyThirdAllies.some((unit) => unit.id === 'huang-quan') ||
+ !sixtyThirdAllies.some((unit) => unit.id === 'li-yan') ||
+ sixtyThirdAllies.some((unit) => unit.id === 'ma-liang') ||
+ sixtyThirdAllies.some((unit) => unit.id === 'ma-dai') ||
+ sixtyThirdAllies.some((unit) => unit.id === 'liu-bei')
+ ) {
+ throw new Error(`Expected sixty-third battle to use the Lucheng pursuit map, Sima Yi/Zhang He/Guo Huai objectives, selected pursuit/return/supply officers, and mixed Wei AI: ${JSON.stringify(sixtyThirdBattleState)}`);
+ }
+
+ 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 sixtyThirdCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp());
+ if (
+ sixtyThirdCampState?.campBattleId !== 'sixty-third-battle-lucheng-pursuit' ||
+ sixtyThirdCampState.campaign?.step !== 'sixty-third-camp' ||
+ sixtyThirdCampState.campTitle !== '노성 추격 후 군영' ||
+ sixtyThirdCampState.availableDialogueIds?.length !== 3 ||
+ !sixtyThirdCampState.availableDialogueIds.every((id) => id.startsWith('northern-ninth-')) ||
+ sixtyThirdCampState.availableVisitIds?.length !== 1 ||
+ !sixtyThirdCampState.availableVisitIds.every((id) => id.startsWith('northern-ninth-')) ||
+ sixtyThirdCampState.rosterCollection?.total < 22 ||
+ !sixtyThirdCampState.report?.bonds?.some((bond) => bond.id === 'jiang-wei__zhao-yun_northern-ninth')
+ ) {
+ throw new Error(`Expected Lucheng pursuit aftermath camp to expose ninth northern campaign events and bonds: ${JSON.stringify(sixtyThirdCampState)}`);
+ }
+ await page.screenshot({ path: 'dist/verification-sixty-third-camp.png', fullPage: true });
+
+ await page.mouse.click(966, 38);
+ await page.waitForTimeout(180);
+ const postSixtyThirdProgressState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp());
+ if (
+ postSixtyThirdProgressState?.activeTab !== 'progress' ||
+ postSixtyThirdProgressState.campaignProgress?.completedKnown !== 63 ||
+ postSixtyThirdProgressState.campaignProgress?.totalKnown !== 63 ||
+ postSixtyThirdProgressState.campaignProgress?.activeChapter?.id !== 'northern-campaign' ||
+ postSixtyThirdProgressState.campaignProgress?.latestBattleTitle !== '노성 추격전' ||
+ postSixtyThirdProgressState.campaignProgress?.nextBattleTitle !== '준비 중'
+ ) {
+ throw new Error(`Expected post-sixty-third progress tab to complete Lucheng pursuit and leave the next northern step pending: ${JSON.stringify(postSixtyThirdProgressState?.campaignProgress)}`);
+ }
+ await page.screenshot({ path: 'dist/verification-post-sixty-third-progress.png', fullPage: true });
+
await page.evaluate(() => {
const game = window.__HEROS_GAME__;
game?.scene.stop('CampScene');
@@ -8857,12 +9036,12 @@ try {
}
}
for (let attempt = 0; attempt < 24; attempt += 1) {
- const continuedToQishanRenewedCamp = await page.evaluate(() => {
+ const continuedToLuchengPursuitCamp = await page.evaluate(() => {
const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? [];
const camp = window.__HEROS_DEBUG__?.camp?.() ?? null;
- return activeScenes.includes('CampScene') && camp?.campaign?.step === 'sixty-second-camp';
+ return activeScenes.includes('CampScene') && camp?.campaign?.step === 'sixty-third-camp';
});
- if (continuedToQishanRenewedCamp) {
+ if (continuedToLuchengPursuitCamp) {
break;
}
await page.keyboard.press('Space');
@@ -8871,17 +9050,17 @@ try {
await page.waitForFunction(() => {
const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? [];
const camp = window.__HEROS_DEBUG__?.camp?.() ?? null;
- return activeScenes.includes('CampScene') && camp?.campaign?.step === 'sixty-second-camp';
+ return activeScenes.includes('CampScene') && camp?.campaign?.step === 'sixty-third-camp';
});
const titleContinueState = await page.evaluate(() => ({
activeScenes: window.__HEROS_DEBUG__?.activeScenes() ?? [],
camp: window.__HEROS_DEBUG__?.camp?.() ?? null
}));
- if (!titleContinueState.activeScenes.includes('CampScene') || titleContinueState.camp?.campaign?.step !== 'sixty-second-camp') {
- throw new Error(`Expected title continue to reopen the Qishan renewed-offensive aftermath camp: ${JSON.stringify(titleContinueState)}`);
+ if (!titleContinueState.activeScenes.includes('CampScene') || titleContinueState.camp?.campaign?.step !== 'sixty-third-camp') {
+ throw new Error(`Expected title continue to reopen the Lucheng pursuit aftermath camp: ${JSON.stringify(titleContinueState)}`);
}
- console.log(`Verified title-to-Qishan-renewed-offensive flow, recruited officer sortie selection, Ma Liang, Yi Ji, Gong Zhi, Huang Zhong, Wei Yan, Pang Tong, Fa Zheng, Wu Yi, Yan Yan, Li Yan, Huang Quan, Ma Chao, Ma Dai, Wang Ping joins, Luofeng, Luo main gate, Mianzhu, Chengdu surrender, Jiameng, Yangping, Dingjun, Hanzhong decisive battle, King of Hanzhong council, Shu-Han foundation state, Jing Province defense vanguard, Fan Castle vanguard, Han River flood, Fan siege, Jing rear crisis, Gongan collapse, Maicheng isolation, Yiling vanguard, Yiling fire attack, Baidi entrustment, Nanzhong stabilization, Meng Huo pacification, second capture, third capture, fourth capture, fifth capture, sixth capture, final capture, northern campaign preparation, Qishan road first northern expedition, Tianshui advance, Jieting crisis, Jiang Wei joining, Qishan retreat, Chencang siege, Wudu/Yinping commanderies, Hanzhong rainy-pass defense, Qishan renewed offensive, and pending next northern step state at ${targetUrl}`);
+ console.log(`Verified title-to-Lucheng-pursuit flow, recruited officer sortie selection, Ma Liang, Yi Ji, Gong Zhi, Huang Zhong, Wei Yan, Pang Tong, Fa Zheng, Wu Yi, Yan Yan, Li Yan, Huang Quan, Ma Chao, Ma Dai, Wang Ping joins, Luofeng, Luo main gate, Mianzhu, Chengdu surrender, Jiameng, Yangping, Dingjun, Hanzhong decisive battle, King of Hanzhong council, Shu-Han foundation state, Jing Province defense vanguard, Fan Castle vanguard, Han River flood, Fan siege, Jing rear crisis, Gongan collapse, Maicheng isolation, Yiling vanguard, Yiling fire attack, Baidi entrustment, Nanzhong stabilization, Meng Huo pacification, second capture, third capture, fourth capture, fifth capture, sixth capture, final capture, northern campaign preparation, Qishan road first northern expedition, Tianshui advance, Jieting crisis, Jiang Wei joining, Qishan retreat, Chencang siege, Wudu/Yinping commanderies, Hanzhong rainy-pass defense, Qishan renewed offensive, Lucheng pursuit, and pending next northern step state at ${targetUrl}`);
} finally {
await browser?.close();
if (serverProcess && !serverProcess.killed) {
diff --git a/src/assets/images/battle/sixty-third-battle-map.svg b/src/assets/images/battle/sixty-third-battle-map.svg
new file mode 100644
index 0000000..8d7e3ed
--- /dev/null
+++ b/src/assets/images/battle/sixty-third-battle-map.svg
@@ -0,0 +1,97 @@
+
diff --git a/src/assets/images/story/22-lucheng-pursuit.png b/src/assets/images/story/22-lucheng-pursuit.png
new file mode 100644
index 0000000..3ccea31
Binary files /dev/null and b/src/assets/images/story/22-lucheng-pursuit.png differ
diff --git a/src/game/data/battleMapAssets.ts b/src/game/data/battleMapAssets.ts
index 345e82d..edc9d08 100644
--- a/src/game/data/battleMapAssets.ts
+++ b/src/game/data/battleMapAssets.ts
@@ -12,6 +12,7 @@ import fiftyNinthBattleMapUrl from '../../assets/images/battle/fifty-ninth-battl
import sixtiethBattleMapUrl from '../../assets/images/battle/sixtieth-battle-map.svg';
import sixtyFirstBattleMapUrl from '../../assets/images/battle/sixty-first-battle-map.svg';
import sixtySecondBattleMapUrl from '../../assets/images/battle/sixty-second-battle-map.svg';
+import sixtyThirdBattleMapUrl from '../../assets/images/battle/sixty-third-battle-map.svg';
import fiftySeventhBattleMapUrl from '../../assets/images/battle/fifty-seventh-battle-map.svg';
import fiftySixthBattleMapUrl from '../../assets/images/battle/fifty-sixth-battle-map.svg';
import fifthBattleMapUrl from '../../assets/images/battle/fifth-battle-map.svg';
@@ -122,6 +123,7 @@ export const battleMapAssets: Record = {
'battle-map-sixtieth': sixtiethBattleMapUrl,
'battle-map-sixty-first': sixtyFirstBattleMapUrl,
'battle-map-sixty-second': sixtySecondBattleMapUrl,
+ 'battle-map-sixty-third': sixtyThirdBattleMapUrl,
'battle-map-fifty-seventh': fiftySeventhBattleMapUrl,
'battle-map-fifty-sixth': fiftySixthBattleMapUrl
};
diff --git a/src/game/data/battles.ts b/src/game/data/battles.ts
index 2cc5570..d61241d 100644
--- a/src/game/data/battles.ts
+++ b/src/game/data/battles.ts
@@ -47,6 +47,10 @@ import {
sixtySecondBattleMap,
sixtySecondBattleUnits,
sixtySecondBattleVictoryPages,
+ sixtyThirdBattleBonds,
+ sixtyThirdBattleMap,
+ sixtyThirdBattleUnits,
+ sixtyThirdBattleVictoryPages,
fiftySeventhBattleBonds,
fiftySeventhBattleMap,
fiftySeventhBattleUnits,
@@ -315,7 +319,8 @@ export type BattleScenarioId =
| 'fifty-ninth-battle-chencang-siege'
| 'sixtieth-battle-wudu-yinping'
| 'sixty-first-battle-hanzhong-rain-defense'
- | 'sixty-second-battle-qishan-renewed-offensive';
+ | 'sixty-second-battle-qishan-renewed-offensive'
+ | 'sixty-third-battle-lucheng-pursuit';
export type BattleObjectiveKind = 'defeat-leader' | 'pacify-leader' | 'keep-unit-alive' | 'secure-terrain' | 'quick-victory';
@@ -4191,6 +4196,73 @@ export const sixtySecondBattleScenario: BattleScenarioDefinition = {
nextCampScene: 'CampScene'
};
+export const sixtyThirdBattleScenario: BattleScenarioDefinition = {
+ id: 'sixty-third-battle-lucheng-pursuit',
+ title: '노성 추격전',
+ victoryConditionLabel: '노성 추격로 확보와 회수 표식 유지',
+ defeatConditionLabel: '제갈량 퇴각 또는 보급 수레 붕괴',
+ openingObjectiveLines: [
+ '기산 재출정으로 노성 앞 지연선을 밀어 낸 촉한군은 더 깊은 추격로를 열려 합니다. 그러나 사마의는 물러나는 척하며 회수 표식과 보급 수레를 끊으려 합니다.',
+ '강유와 위연은 낮은 길과 돌파선을 나누고, 조운과 왕평은 장합의 기병이 뒤를 흔들기 전에 예비대와 표식을 이어야 합니다.',
+ '사마의의 노성 방어 본영, 장합의 회수선 추격대, 곽회의 봉화 차단로를 동시에 끊으십시오. 추격로를 얻어도 수레와 마을을 잃으면 다음 북벌은 깊어질 수 없습니다.'
+ ],
+ map: sixtyThirdBattleMap,
+ units: sixtyThirdBattleUnits,
+ bonds: sixtyThirdBattleBonds,
+ mapTextureKey: 'battle-map-sixty-third',
+ leaderUnitId: 'northern-ninth-leader-sima-yi',
+ quickVictoryTurnLimit: 126,
+ baseVictoryGold: 19880,
+ objectives: [
+ {
+ id: 'break-sima-yi-lucheng-defense',
+ kind: 'defeat-leader',
+ label: '사마의 노성 방어 돌파',
+ rewardGold: 14100,
+ unitId: 'northern-ninth-leader-sima-yi'
+ },
+ {
+ id: 'stop-zhang-he-return-raid',
+ kind: 'defeat-leader',
+ label: '장합 회수선 추격 저지',
+ rewardGold: 8500,
+ unitId: 'northern-ninth-officer-zhang-he'
+ },
+ {
+ id: 'silence-guo-huai-beacons',
+ kind: 'defeat-leader',
+ label: '곽회 봉화 차단',
+ rewardGold: 7900,
+ unitId: 'northern-ninth-officer-guo-huai'
+ },
+ {
+ id: 'hold-return-supply-camps',
+ kind: 'secure-terrain',
+ label: '회수 보급 진영 유지',
+ rewardGold: 6640,
+ terrain: 'camp'
+ },
+ {
+ id: 'reassure-lucheng-villages',
+ kind: 'secure-terrain',
+ label: '노성 주변 마을 안심',
+ rewardGold: 6440,
+ terrain: 'village'
+ },
+ {
+ id: 'quick',
+ kind: 'quick-victory',
+ label: '126턴 이내 추격로 확보',
+ rewardGold: 4360,
+ maxTurn: 126
+ }
+ ],
+ defeatConditions: [{ kind: 'unit-defeated', unitId: 'zhuge-liang' }],
+ itemRewards: ['금 132', '상처약 102', '군주 50', '노성 추격 장부 1', '회수 표식 기록 1'],
+ victoryPages: sixtyThirdBattleVictoryPages,
+ nextCampScene: 'CampScene'
+};
+
export const defaultBattleScenarioId: BattleScenarioId = firstBattleScenario.id;
export const battleScenarios: Record = {
@@ -4255,7 +4327,8 @@ export const battleScenarios: Record
'fifty-ninth-battle-chencang-siege': fiftyNinthBattleScenario,
'sixtieth-battle-wudu-yinping': sixtiethBattleScenario,
'sixty-first-battle-hanzhong-rain-defense': sixtyFirstBattleScenario,
- 'sixty-second-battle-qishan-renewed-offensive': sixtySecondBattleScenario
+ 'sixty-second-battle-qishan-renewed-offensive': sixtySecondBattleScenario,
+ 'sixty-third-battle-lucheng-pursuit': sixtyThirdBattleScenario
};
export const defaultBattleScenario = battleScenarios[defaultBattleScenarioId];
diff --git a/src/game/data/campaignFlow.ts b/src/game/data/campaignFlow.ts
index eb723eb..0fe4792 100644
--- a/src/game/data/campaignFlow.ts
+++ b/src/game/data/campaignFlow.ts
@@ -17,6 +17,7 @@ import {
sixtiethBattleScenario,
sixtyFirstBattleScenario,
sixtySecondBattleScenario,
+ sixtyThirdBattleScenario,
fortiethBattleScenario,
fortyFirstBattleScenario,
fortySecondBattleScenario,
@@ -84,6 +85,8 @@ import {
sixtyFirstBattleVictoryPages,
sixtySecondBattleIntroPages,
sixtySecondBattleVictoryPages,
+ sixtyThirdBattleIntroPages,
+ sixtyThirdBattleVictoryPages,
fiftyFirstBattleIntroPages,
fiftyFirstBattleVictoryPages,
fiftySecondBattleIntroPages,
@@ -911,13 +914,24 @@ const sortieFlows: Record = {
},
[sixtySecondBattleScenario.id]: {
afterBattleId: sixtySecondBattleScenario.id,
- eyebrow: '다음 장 준비',
- title: '기산 재출정 이후',
+ eyebrow: '다음 전장',
+ title: sixtyThirdBattleScenario.title,
description:
- '사마의의 지연선을 밀어 냈지만 북방 전선은 더 깊어졌습니다. 다음 계산에서는 노성 앞에서 확보한 길을 어디까지 밀고, 누가 돌아올 표식과 군량 장부를 맡을지 다시 정해야 합니다.',
- rewardHint: '다음 목표: 노성 전선 재정비 / 더 깊은 북벌 공격로 검토',
- pages: sixtySecondBattleVictoryPages,
- unavailableNotice: '기산 재출정 이후의 다음 북벌 전투는 이어지는 작업에서 확장합니다. 현재는 사마의 지연선 돌파, 회수선 표식, 기산 보급로 전진을 확인할 수 있습니다.'
+ '사마의의 지연선을 밀어 냈지만 노성 뒤의 전선은 아직 닫히지 않았습니다. 장합의 회수선 추격, 곽회의 봉화 차단, 수레와 마을의 부담을 상대하려면 7명 안에서 전진과 회수를 다시 나누어야 합니다.',
+ rewardHint: `예상 보상: ${sixtyThirdBattleScenario.title} 개방 / 노성 추격 장부`,
+ nextBattleId: sixtyThirdBattleScenario.id,
+ campaignStep: 'sixty-third-battle',
+ pages: [...sixtySecondBattleVictoryPages, ...sixtyThirdBattleIntroPages]
+ },
+ [sixtyThirdBattleScenario.id]: {
+ afterBattleId: sixtyThirdBattleScenario.id,
+ eyebrow: '다음 장 준비',
+ title: '노성 추격 이후',
+ description:
+ '노성 추격로와 회수 표식은 지켜 냈지만 북방 전선은 여전히 길고 무겁습니다. 다음 계산에서는 얻은 길을 지키는 장부와 더 깊은 북벌 공세를 다시 맞춰야 합니다.',
+ rewardHint: '다음 목표: 북방 전선 재정비 / 다음 북벌 공격로 검토',
+ pages: sixtyThirdBattleVictoryPages,
+ unavailableNotice: '노성 추격 이후의 다음 북벌 전투는 이어지는 작업에서 확장합니다. 현재는 회수 표식, 장합 추격 저지, 노성 주변 보급과 민심 정비를 확인할 수 있습니다.'
},
'northern-campaign-prep-camp-legacy': {
afterBattleId: fiftyFourthBattleScenario.id,
diff --git a/src/game/data/scenario.ts b/src/game/data/scenario.ts
index 1ac7874..cc17953 100644
--- a/src/game/data/scenario.ts
+++ b/src/game/data/scenario.ts
@@ -3960,6 +3960,59 @@ export const sixtySecondBattleVictoryPages: StoryPage[] = [
}
];
+export const sixtyThirdBattleIntroPages: StoryPage[] = [
+ {
+ id: 'sixty-third-lucheng-pursuit-council',
+ bgm: 'story-dark',
+ chapter: '노성 추격',
+ background: 'story-lucheng-pursuit',
+ speaker: '제갈량',
+ text: '사마의가 노성 뒤로 물러났다고 하여 길이 끝난 것은 아닙니다. 물러난 군은 추격당하는 척하며 돌아갈 길과 보급 수레를 끊으려 합니다.'
+ },
+ {
+ id: 'sixty-third-zhanghe-rear-raid',
+ bgm: 'battle-prep',
+ chapter: '장합의 뒷길',
+ background: 'story-lucheng-pursuit',
+ speaker: '조운',
+ text: '장합의 기병은 앞의 성보다 뒤의 표식을 노릴 것입니다. 전진하는 부대가 승리를 보고 달릴수록, 돌아오는 병사는 어둠 속에서 길을 잃습니다.'
+ },
+ {
+ id: 'sixty-third-supply-and-villages',
+ bgm: 'story-dark',
+ chapter: '이긴 길을 지키다',
+ background: 'story-lucheng-pursuit',
+ speaker: '황권',
+ text: '노성으로 더 깊이 들어가려면 수레와 고시문이 군기보다 먼저 도착해야 합니다. 이번 출전에서는 추격과 회수, 장부와 민심을 한 전장 안에서 다시 나누어야 합니다.'
+ }
+];
+
+export const sixtyThirdBattleVictoryPages: StoryPage[] = [
+ {
+ id: 'sixty-third-victory-return-road-held',
+ bgm: 'militia-theme',
+ chapter: '돌아오는 길',
+ background: 'story-lucheng-pursuit',
+ text: '촉한군은 노성 추격로를 열었고, 장합의 기병이 회수 표식을 흔들기 전에 예비대를 움직였습니다. 전진한 병사들은 처음으로 깊이 들어간 뒤에도 돌아올 길을 보았습니다.'
+ },
+ {
+ id: 'sixty-third-victory-ledger-and-signal',
+ bgm: 'story-dark',
+ chapter: '봉화와 장부',
+ background: 'story-lucheng-pursuit',
+ speaker: '강유',
+ text: '곽회의 봉화가 끊기자 노성 앞 마을과 수레 진영이 한 줄로 이어졌습니다. 강유는 낮은 길을 새 지도에 옮겼고, 왕평은 그 길마다 멈출 표식을 남겼습니다.'
+ },
+ {
+ id: 'sixty-third-next-northern-plan',
+ bgm: 'story-dark',
+ chapter: '다음 북방의 무게',
+ background: 'story-lucheng-pursuit',
+ speaker: '제갈량',
+ text: '오늘의 추격은 길을 얻었을 뿐, 아직 전쟁의 끝은 아닙니다. 더 깊은 북방으로 가려면 누가 길을 열고 누가 나라의 숨을 아낄지 다시 정해야 합니다.'
+ }
+];
+
export const firstBattleMap: BattleMap = {
width: 20,
height: 18,
@@ -4477,6 +4530,12 @@ export const sixtySecondBattleMap: BattleMap = {
terrain: createSixtySecondBattleTerrain()
};
+export const sixtyThirdBattleMap: BattleMap = {
+ width: 126,
+ height: 108,
+ terrain: createSixtyThirdBattleTerrain()
+};
+
export const firstBattleUnits: UnitData[] = [
{
id: 'liu-bei',
@@ -17876,6 +17935,103 @@ export const sixtySecondBattleUnits: UnitData[] = [
createThirtyFirstEnemyUnit('northern-eighth-leader-sima-yi', '사마의', '위군 지연 총지휘', 'rebelLeader', 222, 1740, 410, 116, 40)
];
+const sixtyThirdBattleAllyPositions: Record = {
+ 'guan-yu': { x: 16, y: 96 },
+ 'zhang-fei': { x: 24, y: 99 },
+ 'jian-yong': { x: 8, y: 104 },
+ 'mi-zhu': { x: 13, y: 105 },
+ 'sun-qian': { x: 19, y: 105 },
+ 'zhao-yun': { x: 31, y: 94 },
+ 'zhuge-liang': { x: 18, y: 91 },
+ 'ma-liang': { x: 25, y: 88 },
+ 'yi-ji': { x: 8, y: 106 },
+ 'gong-zhi': { x: 29, y: 103 },
+ 'huang-zhong': { x: 39, y: 92 },
+ 'wei-yan': { x: 45, y: 93 },
+ 'pang-tong': { x: 15, y: 87 },
+ 'fa-zheng': { x: 29, y: 86 },
+ 'wu-yi': { x: 48, y: 101 },
+ 'yan-yan': { x: 52, y: 94 },
+ 'li-yan': { x: 34, y: 85 },
+ 'huang-quan': { x: 12, y: 90 },
+ 'ma-chao': { x: 55, y: 90 },
+ 'ma-dai': { x: 60, y: 97 },
+ 'wang-ping': { x: 41, y: 85 },
+ 'jiang-wei': { x: 35, y: 88 }
+};
+
+const sixtyThirdEnemySpecs: ReadonlyArray<
+ readonly [string, string, string, UnitClassKey, number, number, number, number, number]
+> = [
+ ['northern-ninth-scout-a', '노성 추격 척후병', '서쪽 추격 정찰', 'bandit', 223, 1090, 300, 48, 88],
+ ['northern-ninth-scout-b', '회수선 척후병', '회수선 정찰', 'bandit', 223, 1090, 300, 61, 84],
+ ['northern-ninth-scout-c', '낮은 길 척후병', '중앙 고개 정찰', 'bandit', 224, 1102, 302, 74, 78],
+ ['northern-ninth-scout-d', '동쪽 봉화 척후병', '동쪽 봉화 정찰', 'bandit', 224, 1102, 302, 88, 70],
+ ['northern-ninth-scout-e', '곽회 전초 척후병', '차단로 정찰', 'bandit', 225, 1114, 304, 103, 58],
+ ['northern-ninth-scout-f', '노성 뒤 척후병', '북쪽 절벽 정찰', 'bandit', 225, 1114, 304, 122, 36],
+ ['northern-ninth-infantry-a', '위군 추격 보병', '서쪽 추격 보병', 'yellowTurban', 224, 1620, 386, 55, 83],
+ ['northern-ninth-infantry-b', '위군 회수선 보병', '회수선 보병', 'yellowTurban', 224, 1620, 386, 68, 80],
+ ['northern-ninth-infantry-c', '위군 낮은 길 보병', '중앙 고개 보병', 'yellowTurban', 225, 1640, 390, 82, 73],
+ ['northern-ninth-infantry-d', '위군 봉화 보병', '봉화대 보병', 'yellowTurban', 225, 1640, 390, 96, 66],
+ ['northern-ninth-infantry-e', '곽회 차단 보병', '차단로 보병', 'yellowTurban', 226, 1660, 394, 108, 53],
+ ['northern-ninth-infantry-f', '노성 본영 보병', '본영 보병', 'yellowTurban', 226, 1660, 394, 118, 43],
+ ['northern-ninth-infantry-g', '수레길 차단병', '수레길 차단 보병', 'yellowTurban', 227, 1680, 398, 92, 88],
+ ['northern-ninth-infantry-h', '북쪽 문 보병', '북문 보병', 'yellowTurban', 227, 1680, 398, 124, 25],
+ ['northern-ninth-archer-a', '회수선 궁병', '서쪽 능선 궁병', 'archer', 224, 1180, 322, 64, 75],
+ ['northern-ninth-archer-b', '낮은 길 궁병', '중앙 능선 궁병', 'archer', 224, 1180, 322, 77, 69],
+ ['northern-ninth-archer-c', '봉화대 궁병', '봉화 능선 궁병', 'archer', 225, 1192, 324, 91, 60],
+ ['northern-ninth-archer-d', '차단로 궁병', '차단로 궁병', 'archer', 226, 1204, 326, 104, 49],
+ ['northern-ninth-archer-e', '본영 궁병', '노성 본영 궁병', 'archer', 226, 1204, 326, 116, 33],
+ ['northern-ninth-archer-f', '북쪽 절벽 궁병', '절벽 궁병', 'archer', 227, 1216, 328, 124, 17],
+ ['northern-ninth-cavalry-a', '장합 추격 기병', '서쪽 추격 기병', 'cavalry', 225, 1700, 410, 50, 76],
+ ['northern-ninth-cavalry-b', '장합 추격 기병', '회수선 추격 기병', 'cavalry', 225, 1700, 410, 63, 73],
+ ['northern-ninth-cavalry-c', '장합 추격 기병', '중앙 추격 기병', 'cavalry', 226, 1722, 414, 79, 65],
+ ['northern-ninth-cavalry-d', '곽회 차단 기병', '동쪽 차단 기병', 'cavalry', 227, 1744, 418, 96, 57],
+ ['northern-ninth-cavalry-e', '사마의 예비 기병', '본영 예비 기병', 'cavalry', 227, 1744, 418, 111, 43],
+ ['northern-ninth-cavalry-f', '북쪽 절벽 기병', '절벽 차단 기병', 'cavalry', 228, 1766, 422, 122, 28],
+ ['northern-ninth-ridge-guard-a', '노성 서로 수비대', '서쪽 고개 수비', 'yellowTurban', 225, 1640, 392, 42, 62],
+ ['northern-ninth-ridge-guard-b', '회수 표식 수비대', '표식 수비', 'yellowTurban', 225, 1640, 392, 59, 58],
+ ['northern-ninth-ridge-guard-c', '낮은 길 수비대', '중앙 고개 수비', 'yellowTurban', 226, 1660, 396, 76, 51],
+ ['northern-ninth-ridge-guard-d', '봉화 길 수비대', '봉화 길 수비', 'yellowTurban', 226, 1660, 396, 94, 42],
+ ['northern-ninth-ridge-guard-e', '사마의 본영 수비대', '본영 수비', 'yellowTurban', 227, 1680, 400, 110, 34],
+ ['northern-ninth-ridge-guard-f', '북쪽 절벽 수비대', '북쪽 절벽 수비', 'yellowTurban', 227, 1680, 400, 124, 22],
+ ['northern-ninth-strategist-a', '위군 추격 군리', '추격 장부 군리', 'strategist', 225, 1260, 342, 80, 58],
+ ['northern-ninth-strategist-b', '위군 보급 군리', '수레 차단 군리', 'strategist', 226, 1272, 344, 96, 46],
+ ['northern-ninth-strategist-c', '사마의 본영 군리', '본영 장부 군리', 'strategist', 226, 1272, 344, 112, 36],
+ ['northern-ninth-strategist-d', '곽회 봉화 군리', '봉화 장부 군리', 'strategist', 227, 1284, 346, 119, 66],
+ ['northern-ninth-officer-lucheng', '노성 추격장', '노성 추격 장수', 'rebelLeader', 229, 1760, 424, 84, 80],
+ ['northern-ninth-officer-supply', '수레 차단장', '수레 차단 장수', 'rebelLeader', 229, 1760, 424, 104, 72],
+ ['northern-ninth-officer-guo-huai', '곽회', '봉화 차단 장수', 'rebelLeader', 228, 1740, 420, 114, 70],
+ ['northern-ninth-officer-relay', '봉화 연락장', '노성 뒤 봉화 장수', 'rebelLeader', 228, 1740, 420, 118, 52],
+ ['northern-ninth-officer-zhang-he', '장합', '회수선 추격대장', 'rebelLeader', 230, 1800, 430, 120, 31],
+ ['northern-ninth-leader-sima-yi', '사마의', '노성 방어 총지휘', 'rebelLeader', 231, 1840, 436, 116, 43]
+];
+
+export const sixtyThirdBattleUnits: UnitData[] = [
+ ...[
+ ...firstBattleUnits.filter((unit) => unit.faction === 'ally' && unit.id !== 'liu-bei'),
+ ...xuzhouRecruitUnits,
+ ...caoBreakRecruitUnits,
+ ...liuBiaoRecruitUnits,
+ ...zhugeRecruitUnits,
+ ...jingzhouRecruitUnits,
+ ...guiyangRecruitUnits,
+ ...wulingRecruitUnits,
+ ...changshaRecruitUnits,
+ ...yizhouRecruitUnits,
+ ...fuPassRecruitUnits,
+ ...luoCastleRecruitUnits,
+ ...luoCastleProperRecruitUnits,
+ ...chengduPressureRecruitUnits,
+ ...chengduSurrenderRecruitUnits,
+ ...hanzhongOpeningRecruitUnits,
+ ...hanzhongScoutRecruitUnits,
+ ...hanzhongMainRecruitUnits,
+ ...jiangWeiRecruitUnits
+ ].map((unit) => placeScenarioUnit(unit, sixtyThirdBattleAllyPositions[unit.id] ?? { x: unit.x, y: unit.y })),
+ ...sixtyThirdEnemySpecs.map((spec) => createThirtyFirstEnemyUnit(...spec))
+];
+
export const firstBattleBonds: BattleBond[] = [
{
id: 'liu-bei__guan-yu',
@@ -19273,6 +19429,46 @@ export const sixtySecondBattleBonds: BattleBond[] = [
...northernEighthBattleBonds
].map(cloneBattleBondForScenario);
+export const northernNinthBattleBonds: BattleBond[] = [
+ {
+ id: 'jiang-wei__zhao-yun_northern-ninth',
+ unitIds: ['jiang-wei', 'zhao-yun'],
+ title: '길잡이와 회수기',
+ level: 56,
+ exp: 0,
+ description: '강유가 낮은 길을 읽고 조운이 예비대를 회수하면, 추격전에서도 앞선 병사가 길을 잃지 않습니다.'
+ },
+ {
+ id: 'wei-yan__wang-ping_northern-ninth',
+ unitIds: ['wei-yan', 'wang-ping'],
+ title: '칼끝과 정지선',
+ level: 54,
+ exp: 0,
+ description: '위연의 돌파와 왕평의 정지선이 함께 움직이면, 노성 추격로를 밀어붙이면서도 돌아올 표식을 남길 수 있습니다.'
+ },
+ {
+ id: 'huang-quan__li-yan_northern-ninth',
+ unitIds: ['huang-quan', 'li-yan'],
+ title: '장부와 수레',
+ level: 50,
+ exp: 0,
+ description: '황권의 군량 계산과 이엄의 운송 기준이 맞물리면, 더 깊은 추격도 한중 창고의 숨을 끊지 않습니다.'
+ },
+ {
+ id: 'ma-liang__ma-dai_northern-ninth',
+ unitIds: ['ma-liang', 'ma-dai'],
+ title: '고시문과 측면',
+ level: 46,
+ exp: 0,
+ description: '마량의 민심 문서와 마대의 측면 기동은 노성 앞 마을을 안심시키면서 곽회의 우회로를 견제합니다.'
+ }
+];
+
+export const sixtyThirdBattleBonds: BattleBond[] = [
+ ...sixtySecondBattleBonds,
+ ...northernNinthBattleBonds
+].map(cloneBattleBondForScenario);
+
function createEighthBattleTerrain(): TerrainType[][] {
return Array.from({ length: 22 }, (_, y) =>
Array.from({ length: 26 }, (_, x): TerrainType => {
@@ -22749,6 +22945,88 @@ function createSixtySecondBattleTerrain(): TerrainType[][] {
);
}
+function createSixtyThirdBattleTerrain(): TerrainType[][] {
+ return Array.from({ length: 108 }, (_, y) =>
+ Array.from({ length: 126 }, (_, x): TerrainType => {
+ if (
+ (x >= 6 && x <= 13 && y >= 0 && y <= 107) ||
+ (x >= 66 && x <= 73 && y >= 10 && y <= 107) ||
+ (x >= 0 && x <= 125 && y >= 100)
+ ) {
+ return 'river';
+ }
+ if (
+ (x >= 6 && x <= 86 && y >= 92 && y <= 98) ||
+ (x >= 24 && x <= 106 && y >= 80 && y <= 88) ||
+ (x >= 48 && x <= 114 && y >= 68 && y <= 78) ||
+ (x >= 78 && x <= 122 && y >= 54 && y <= 66) ||
+ (x >= 96 && x <= 125 && y >= 38 && y <= 50) ||
+ (x >= 31 && x <= 41 && y >= 72 && y <= 102) ||
+ (x >= 57 && x <= 66 && y >= 58 && y <= 94) ||
+ (x >= 87 && x <= 97 && y >= 44 && y <= 84) ||
+ (x >= 100 && x <= 110 && y >= 24 && y <= 70) ||
+ (x >= 42 && x <= 86 && y >= 84 && y <= 90)
+ ) {
+ return 'road';
+ }
+ if (
+ (x >= 0 && x <= 39 && y >= 62 && y <= 96) ||
+ (x >= 43 && x <= 84 && y >= 77 && y <= 99) ||
+ (x >= 90 && x <= 125 && y >= 71 && y <= 97) ||
+ (x >= 15 && x <= 44 && y >= 45 && y <= 66) ||
+ (x >= 100 && x <= 125 && y >= 54 && y <= 76) ||
+ (x >= 117 && x <= 125 && y >= 27 && y <= 50)
+ ) {
+ return 'forest';
+ }
+ if (
+ (x >= 0 && x <= 54 && y <= 42) ||
+ (x >= 47 && x <= 96 && y <= 36) ||
+ (x >= 88 && x <= 125 && y <= 55) ||
+ (x >= 99 && x <= 125 && y >= 47 && y <= 76)
+ ) {
+ return 'hill';
+ }
+ if (
+ (x <= 2 && y <= 98) ||
+ (x >= 123 && y <= 92) ||
+ (x >= 40 && x <= 47 && y >= 0 && y <= 68) ||
+ (x >= 79 && x <= 85 && y >= 0 && y <= 50) ||
+ (x >= 62 && x <= 70 && y >= 88 && y <= 107)
+ ) {
+ return 'cliff';
+ }
+ if (
+ (x >= 12 && x <= 31 && y >= 88 && y <= 98) ||
+ (x >= 44 && x <= 66 && y >= 75 && y <= 87) ||
+ (x >= 82 && x <= 102 && y >= 61 && y <= 73) ||
+ (x >= 104 && x <= 122 && y >= 45 && y <= 59) ||
+ (x >= 112 && x <= 124 && y >= 28 && y <= 38)
+ ) {
+ return 'village';
+ }
+ if (
+ (x >= 21 && x <= 50 && y >= 82 && y <= 96) ||
+ (x >= 56 && x <= 84 && y >= 69 && y <= 82) ||
+ (x >= 91 && x <= 116 && y >= 55 && y <= 68) ||
+ (x >= 100 && x <= 120 && y >= 25 && y <= 41)
+ ) {
+ return 'camp';
+ }
+ if (
+ (x >= 50 && x <= 72 && y >= 50 && y <= 66) ||
+ (x >= 74 && x <= 102 && y >= 40 && y <= 55) ||
+ (x >= 96 && x <= 122 && y >= 49 && y <= 65) ||
+ (x >= 105 && x <= 125 && y >= 15 && y <= 38) ||
+ (x >= 112 && x <= 125 && y >= 68 && y <= 86)
+ ) {
+ return 'fort';
+ }
+ return 'plain';
+ })
+ );
+}
+
function placeScenarioUnit(unit: UnitData, position: { x: number; y: number }): UnitData {
return {
...cloneUnitForScenario(unit),
diff --git a/src/game/data/storyAssets.ts b/src/game/data/storyAssets.ts
index 41c45f3..c0090b1 100644
--- a/src/game/data/storyAssets.ts
+++ b/src/game/data/storyAssets.ts
@@ -19,6 +19,7 @@ import storyJietingCrisisUrl from '../../assets/images/story/18-jieting-crisis.p
import storyChencangSiegeUrl from '../../assets/images/story/19-chencang-siege.png';
import storyWuduYinpingUrl from '../../assets/images/story/20-wudu-yinping.png';
import storyHanzhongRainDefenseUrl from '../../assets/images/story/21-hanzhong-rain-defense.png';
+import storyLuchengPursuitUrl from '../../assets/images/story/22-lucheng-pursuit.png';
export const storyBackgroundAssets: Record = {
'story-chaos': storyChaosUrl,
@@ -42,5 +43,6 @@ export const storyBackgroundAssets: Record = {
'story-chencang-siege': storyChencangSiegeUrl,
'story-wudu-yinping': storyWuduYinpingUrl,
'story-hanzhong-rain': storyHanzhongRainDefenseUrl,
+ 'story-lucheng-pursuit': storyLuchengPursuitUrl,
'story-resolve': storyLiuBeiUrl
};
diff --git a/src/game/scenes/BattleScene.ts b/src/game/scenes/BattleScene.ts
index 90708e6..8120929 100644
--- a/src/game/scenes/BattleScene.ts
+++ b/src/game/scenes/BattleScene.ts
@@ -6571,13 +6571,15 @@ export class BattleScene extends Phaser.Scene {
'northern-fifth',
'northern-sixth',
'northern-seventh',
- 'northern-eighth'
+ 'northern-eighth',
+ 'northern-ninth'
];
if (lateNorthernPrefixes.some((prefix) => enemy.id.startsWith(prefix))) {
if (
enemy.id.includes('-scout') ||
enemy.id.includes('-cavalry') ||
- enemy.id === 'northern-eighth-officer-zhang-he'
+ enemy.id === 'northern-eighth-officer-zhang-he' ||
+ enemy.id === 'northern-ninth-officer-zhang-he'
) {
return 'aggressive';
}
diff --git a/src/game/scenes/CampScene.ts b/src/game/scenes/CampScene.ts
index 5819c0d..b75fa37 100644
--- a/src/game/scenes/CampScene.ts
+++ b/src/game/scenes/CampScene.ts
@@ -55,6 +55,7 @@ import {
northernFifthBattleBonds,
northernFourthBattleBonds,
northernFirstBattleBonds,
+ northernNinthBattleBonds,
northernSecondBattleBonds,
northernSeventhBattleBonds,
northernSixthBattleBonds,
@@ -449,9 +450,10 @@ const campaignTimelineChapters: CampaignTimelineChapter[] = [
'fifty-ninth-battle-chencang-siege',
'sixtieth-battle-wudu-yinping',
'sixty-first-battle-hanzhong-rain-defense',
- 'sixty-second-battle-qishan-renewed-offensive'
+ 'sixty-second-battle-qishan-renewed-offensive',
+ 'sixty-third-battle-lucheng-pursuit'
],
- nextHints: ['한중 창고 정비', '출전 무장 재편', '기산 출진로', '천수·가정 방면', '강유 합류', '진창 공성 재계산', '무도·음평 확보', '한중 우로 방어', '기산 재출정']
+ nextHints: ['한중 창고 정비', '출전 무장 재편', '기산 출진로', '천수·가정 방면', '강유 합류', '진창 공성 재계산', '무도·음평 확보', '한중 우로 방어', '기산 재출정', '노성 추격']
}
];
@@ -517,7 +519,8 @@ const campBattleIds = {
fiftyNinth: 'fifty-ninth-battle-chencang-siege',
sixtieth: 'sixtieth-battle-wudu-yinping',
sixtyFirst: 'sixty-first-battle-hanzhong-rain-defense',
- sixtySecond: 'sixty-second-battle-qishan-renewed-offensive'
+ sixtySecond: 'sixty-second-battle-qishan-renewed-offensive',
+ sixtyThird: 'sixty-third-battle-lucheng-pursuit'
} as const;
const defaultRequiredSortieUnitIds = ['liu-bei'];
@@ -1301,6 +1304,23 @@ const sortieRulesByBattleId: Partial