diff --git a/scripts/verify-flow.mjs b/scripts/verify-flow.mjs
index eff2abc..e37777b 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 !== 64 ||
+ progressTabState.campaignProgress?.totalKnown !== 65 ||
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 !== 64 ||
+ postBowangProgressState.campaignProgress?.totalKnown !== 65 ||
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 !== 64 ||
+ postChangbanProgressState.campaignProgress?.totalKnown !== 65 ||
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 !== 64 ||
+ postJiangdongProgressState.campaignProgress?.totalKnown !== 65 ||
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 !== 64 ||
+ postRedCliffsProgressState.campaignProgress?.totalKnown !== 65 ||
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 !== 64 ||
+ postFireAttackProgressState.campaignProgress?.totalKnown !== 65 ||
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 !== 64 ||
+ postJingzhouProgressState.campaignProgress?.totalKnown !== 65 ||
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 !== 64 ||
+ postGuiyangProgressState.campaignProgress?.totalKnown !== 65 ||
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 !== 64 ||
+ postWulingProgressState.campaignProgress?.totalKnown !== 65 ||
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 !== 64 ||
+ postChangshaProgressState.campaignProgress?.totalKnown !== 65 ||
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 !== 64 ||
+ postYizhouProgressState.campaignProgress?.totalKnown !== 65 ||
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 !== 64 ||
+ postFuPassProgressState.campaignProgress?.totalKnown !== 65 ||
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 !== 64 ||
+ postLuoProgressState.campaignProgress?.totalKnown !== 65 ||
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 !== 64 ||
+ postLuofengProgressState.campaignProgress?.totalKnown !== 65 ||
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 !== 64 ||
+ postLuoMainProgressState.campaignProgress?.totalKnown !== 65 ||
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 !== 64 ||
+ postMianzhuProgressState.campaignProgress?.totalKnown !== 65 ||
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 !== 64 ||
+ postChengduProgressState.campaignProgress?.totalKnown !== 65 ||
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 !== 64 ||
+ postJiamengProgressState.campaignProgress?.totalKnown !== 65 ||
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 !== 64 ||
+ postYangpingProgressState.campaignProgress?.totalKnown !== 65 ||
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 !== 64 ||
+ postDingjunProgressState.campaignProgress?.totalKnown !== 65 ||
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 !== 64 ||
+ postHanzhongProgressState.campaignProgress?.totalKnown !== 65 ||
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 !== 64 ||
+ postKingCouncilProgressState.campaignProgress?.totalKnown !== 65 ||
postKingCouncilProgressState.campaignProgress?.latestBattleTitle !== '한중 결전' ||
postKingCouncilProgressState.campaignProgress?.nextBattleTitle !== '촉한 건국 선포'
) {
@@ -4810,7 +4810,7 @@ try {
if (
postFoundationProgressState?.activeTab !== 'progress' ||
postFoundationProgressState.campaignProgress?.completedKnown !== 37 ||
- postFoundationProgressState.campaignProgress?.totalKnown !== 64 ||
+ postFoundationProgressState.campaignProgress?.totalKnown !== 65 ||
postFoundationProgressState.campaignProgress?.latestBattleTitle !== '한중 결전' ||
postFoundationProgressState.campaignProgress?.nextBattleTitle !== '형주 방위 전초전'
) {
@@ -4958,7 +4958,7 @@ try {
if (
postJingDefenseProgressState?.activeTab !== 'progress' ||
postJingDefenseProgressState.campaignProgress?.completedKnown !== 38 ||
- postJingDefenseProgressState.campaignProgress?.totalKnown !== 64 ||
+ postJingDefenseProgressState.campaignProgress?.totalKnown !== 65 ||
postJingDefenseProgressState.campaignProgress?.latestBattleTitle !== '형주 방위 전초전' ||
postJingDefenseProgressState.campaignProgress?.nextBattleTitle !== '번성 외곽 압박전'
) {
@@ -5108,7 +5108,7 @@ try {
if (
postFanCastleProgressState?.activeTab !== 'progress' ||
postFanCastleProgressState.campaignProgress?.completedKnown !== 39 ||
- postFanCastleProgressState.campaignProgress?.totalKnown !== 64 ||
+ postFanCastleProgressState.campaignProgress?.totalKnown !== 65 ||
postFanCastleProgressState.campaignProgress?.latestBattleTitle !== '번성 외곽 압박전' ||
postFanCastleProgressState.campaignProgress?.nextBattleTitle !== '한수 수공'
) {
@@ -5259,7 +5259,7 @@ try {
if (
postHanRiverProgressState?.activeTab !== 'progress' ||
postHanRiverProgressState.campaignProgress?.completedKnown !== 40 ||
- postHanRiverProgressState.campaignProgress?.totalKnown !== 64 ||
+ postHanRiverProgressState.campaignProgress?.totalKnown !== 65 ||
postHanRiverProgressState.campaignProgress?.latestBattleTitle !== '한수 수공' ||
postHanRiverProgressState.campaignProgress?.nextBattleTitle !== '번성 공성전'
) {
@@ -5410,7 +5410,7 @@ try {
if (
postFanSiegeProgressState?.activeTab !== 'progress' ||
postFanSiegeProgressState.campaignProgress?.completedKnown !== 41 ||
- postFanSiegeProgressState.campaignProgress?.totalKnown !== 64 ||
+ postFanSiegeProgressState.campaignProgress?.totalKnown !== 65 ||
postFanSiegeProgressState.campaignProgress?.latestBattleTitle !== '번성 공성전' ||
postFanSiegeProgressState.campaignProgress?.nextBattleTitle !== '강릉 나루 경계전'
) {
@@ -5569,7 +5569,7 @@ try {
if (
postJingRearProgressState?.activeTab !== 'progress' ||
postJingRearProgressState.campaignProgress?.completedKnown !== 42 ||
- postJingRearProgressState.campaignProgress?.totalKnown !== 64 ||
+ postJingRearProgressState.campaignProgress?.totalKnown !== 65 ||
postJingRearProgressState.campaignProgress?.latestBattleTitle !== '강릉 나루 경계전' ||
postJingRearProgressState.campaignProgress?.nextBattleTitle !== '공안 성문 변고'
) {
@@ -5721,7 +5721,7 @@ try {
if (
postGonganProgressState?.activeTab !== 'progress' ||
postGonganProgressState.campaignProgress?.completedKnown !== 43 ||
- postGonganProgressState.campaignProgress?.totalKnown !== 64 ||
+ postGonganProgressState.campaignProgress?.totalKnown !== 65 ||
postGonganProgressState.campaignProgress?.latestBattleTitle !== '공안 성문 변고' ||
postGonganProgressState.campaignProgress?.nextBattleTitle !== '맥성 고립전'
) {
@@ -5876,7 +5876,7 @@ try {
if (
postMaichengProgressState?.activeTab !== 'progress' ||
postMaichengProgressState.campaignProgress?.completedKnown !== 44 ||
- postMaichengProgressState.campaignProgress?.totalKnown !== 64 ||
+ postMaichengProgressState.campaignProgress?.totalKnown !== 65 ||
postMaichengProgressState.campaignProgress?.latestBattleTitle !== '맥성 고립전' ||
postMaichengProgressState.campaignProgress?.nextBattleTitle !== '이릉 진격로'
) {
@@ -6030,7 +6030,7 @@ try {
if (
postYilingProgressState?.activeTab !== 'progress' ||
postYilingProgressState.campaignProgress?.completedKnown !== 45 ||
- postYilingProgressState.campaignProgress?.totalKnown !== 64 ||
+ postYilingProgressState.campaignProgress?.totalKnown !== 65 ||
postYilingProgressState.campaignProgress?.latestBattleTitle !== '이릉 진격로' ||
postYilingProgressState.campaignProgress?.nextBattleTitle !== '이릉 화공전'
) {
@@ -6185,7 +6185,7 @@ try {
if (
postYilingFireProgressState?.activeTab !== 'progress' ||
postYilingFireProgressState.campaignProgress?.completedKnown !== 46 ||
- postYilingFireProgressState.campaignProgress?.totalKnown !== 64 ||
+ postYilingFireProgressState.campaignProgress?.totalKnown !== 65 ||
postYilingFireProgressState.campaignProgress?.latestBattleTitle !== '이릉 화공전' ||
postYilingFireProgressState.campaignProgress?.nextBattleTitle !== '백제성 유탁'
) {
@@ -6248,7 +6248,7 @@ try {
if (
postBaidiProgressState?.activeTab !== 'progress' ||
postBaidiProgressState.campaignProgress?.completedKnown !== 46 ||
- postBaidiProgressState.campaignProgress?.totalKnown !== 64 ||
+ postBaidiProgressState.campaignProgress?.totalKnown !== 65 ||
postBaidiProgressState.campaignProgress?.latestBattleTitle !== '이릉 화공전' ||
postBaidiProgressState.campaignProgress?.nextBattleTitle !== '남중 안정로'
) {
@@ -6406,7 +6406,7 @@ try {
if (
postNanzhongProgressState?.activeTab !== 'progress' ||
postNanzhongProgressState.campaignProgress?.completedKnown !== 47 ||
- postNanzhongProgressState.campaignProgress?.totalKnown !== 64 ||
+ postNanzhongProgressState.campaignProgress?.totalKnown !== 65 ||
postNanzhongProgressState.campaignProgress?.latestBattleTitle !== '남중 안정로' ||
postNanzhongProgressState.campaignProgress?.nextBattleTitle !== '맹획 본대전'
) {
@@ -6565,7 +6565,7 @@ try {
if (
postMengHuoProgressState?.activeTab !== 'progress' ||
postMengHuoProgressState.campaignProgress?.completedKnown !== 48 ||
- postMengHuoProgressState.campaignProgress?.totalKnown !== 64 ||
+ postMengHuoProgressState.campaignProgress?.totalKnown !== 65 ||
postMengHuoProgressState.campaignProgress?.latestBattleTitle !== '맹획 본대전' ||
postMengHuoProgressState.campaignProgress?.nextBattleTitle !== '칠종칠금 2차전'
) {
@@ -6724,7 +6724,7 @@ try {
if (
postSecondCaptureProgressState?.activeTab !== 'progress' ||
postSecondCaptureProgressState.campaignProgress?.completedKnown !== 49 ||
- postSecondCaptureProgressState.campaignProgress?.totalKnown !== 64 ||
+ postSecondCaptureProgressState.campaignProgress?.totalKnown !== 65 ||
postSecondCaptureProgressState.campaignProgress?.latestBattleTitle !== '칠종칠금 2차전' ||
postSecondCaptureProgressState.campaignProgress?.nextBattleTitle !== '칠종칠금 3차전'
) {
@@ -6886,7 +6886,7 @@ try {
if (
postThirdCaptureProgressState?.activeTab !== 'progress' ||
postThirdCaptureProgressState.campaignProgress?.completedKnown !== 50 ||
- postThirdCaptureProgressState.campaignProgress?.totalKnown !== 64 ||
+ postThirdCaptureProgressState.campaignProgress?.totalKnown !== 65 ||
postThirdCaptureProgressState.campaignProgress?.latestBattleTitle !== '칠종칠금 3차전' ||
postThirdCaptureProgressState.campaignProgress?.nextBattleTitle !== '칠종칠금 4차전'
) {
@@ -7049,7 +7049,7 @@ try {
if (
postFourthCaptureProgressState?.activeTab !== 'progress' ||
postFourthCaptureProgressState.campaignProgress?.completedKnown !== 51 ||
- postFourthCaptureProgressState.campaignProgress?.totalKnown !== 64 ||
+ postFourthCaptureProgressState.campaignProgress?.totalKnown !== 65 ||
postFourthCaptureProgressState.campaignProgress?.latestBattleTitle !== '칠종칠금 4차전' ||
postFourthCaptureProgressState.campaignProgress?.nextBattleTitle !== '칠종칠금 5차전'
) {
@@ -7212,7 +7212,7 @@ try {
if (
postFifthCaptureProgressState?.activeTab !== 'progress' ||
postFifthCaptureProgressState.campaignProgress?.completedKnown !== 52 ||
- postFifthCaptureProgressState.campaignProgress?.totalKnown !== 64 ||
+ postFifthCaptureProgressState.campaignProgress?.totalKnown !== 65 ||
postFifthCaptureProgressState.campaignProgress?.latestBattleTitle !== '칠종칠금 5차전' ||
postFifthCaptureProgressState.campaignProgress?.nextBattleTitle !== '칠종칠금 6차전'
) {
@@ -7374,7 +7374,7 @@ try {
if (
postSixthCaptureProgressState?.activeTab !== 'progress' ||
postSixthCaptureProgressState.campaignProgress?.completedKnown !== 53 ||
- postSixthCaptureProgressState.campaignProgress?.totalKnown !== 64 ||
+ postSixthCaptureProgressState.campaignProgress?.totalKnown !== 65 ||
postSixthCaptureProgressState.campaignProgress?.latestBattleTitle !== '칠종칠금 6차전' ||
postSixthCaptureProgressState.campaignProgress?.nextBattleTitle !== '칠종칠금 7차전'
) {
@@ -7537,7 +7537,7 @@ try {
if (
postFinalCaptureProgressState?.activeTab !== 'progress' ||
postFinalCaptureProgressState.campaignProgress?.completedKnown !== 54 ||
- postFinalCaptureProgressState.campaignProgress?.totalKnown !== 64 ||
+ postFinalCaptureProgressState.campaignProgress?.totalKnown !== 65 ||
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 !== 64 ||
+ postNorthernPrepProgressState.campaignProgress?.totalKnown !== 65 ||
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 !== 64 ||
+ postFiftyFifthProgressState.campaignProgress?.totalKnown !== 65 ||
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 !== 64 ||
+ postFiftySixthProgressState.campaignProgress?.totalKnown !== 65 ||
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 !== 64 ||
+ postFiftySeventhProgressState.campaignProgress?.totalKnown !== 65 ||
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 !== 64 ||
+ postFiftyEighthProgressState.campaignProgress?.totalKnown !== 65 ||
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 !== 64 ||
+ postFiftyNinthProgressState.campaignProgress?.totalKnown !== 65 ||
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 !== 64 ||
+ postSixtiethProgressState.campaignProgress?.totalKnown !== 65 ||
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 !== 64 ||
+ postSixtyFirstProgressState.campaignProgress?.totalKnown !== 65 ||
postSixtyFirstProgressState.campaignProgress?.activeChapter?.id !== 'northern-campaign' ||
postSixtyFirstProgressState.campaignProgress?.latestBattleTitle !== '한중 우로 방어전' ||
postSixtyFirstProgressState.campaignProgress?.nextBattleTitle !== '기산 재출정전'
@@ -8820,7 +8820,7 @@ try {
if (
postSixtySecondProgressState?.activeTab !== 'progress' ||
postSixtySecondProgressState.campaignProgress?.completedKnown !== 62 ||
- postSixtySecondProgressState.campaignProgress?.totalKnown !== 64 ||
+ postSixtySecondProgressState.campaignProgress?.totalKnown !== 65 ||
postSixtySecondProgressState.campaignProgress?.activeChapter?.id !== 'northern-campaign' ||
postSixtySecondProgressState.campaignProgress?.latestBattleTitle !== '기산 재출정전' ||
postSixtySecondProgressState.campaignProgress?.nextBattleTitle !== '노성 추격전'
@@ -8999,7 +8999,7 @@ try {
if (
postSixtyThirdProgressState?.activeTab !== 'progress' ||
postSixtyThirdProgressState.campaignProgress?.completedKnown !== 63 ||
- postSixtyThirdProgressState.campaignProgress?.totalKnown !== 64 ||
+ postSixtyThirdProgressState.campaignProgress?.totalKnown !== 65 ||
postSixtyThirdProgressState.campaignProgress?.activeChapter?.id !== 'northern-campaign' ||
postSixtyThirdProgressState.campaignProgress?.latestBattleTitle !== '노성 추격전' ||
postSixtyThirdProgressState.campaignProgress?.nextBattleTitle !== '위수 진영전'
@@ -9180,15 +9180,196 @@ try {
if (
postSixtyFourthProgressState?.activeTab !== 'progress' ||
postSixtyFourthProgressState.campaignProgress?.completedKnown !== 64 ||
- postSixtyFourthProgressState.campaignProgress?.totalKnown !== 64 ||
+ postSixtyFourthProgressState.campaignProgress?.totalKnown !== 65 ||
postSixtyFourthProgressState.campaignProgress?.activeChapter?.id !== 'northern-campaign' ||
postSixtyFourthProgressState.campaignProgress?.latestBattleTitle !== '위수 진영전' ||
- postSixtyFourthProgressState.campaignProgress?.nextBattleTitle !== '준비 중'
+ postSixtyFourthProgressState.campaignProgress?.nextBattleTitle !== '위수 북안 압박전'
) {
- throw new Error(`Expected post-sixty-fourth progress tab to complete Weishui camps and leave the next northern step pending: ${JSON.stringify(postSixtyFourthProgressState?.campaignProgress)}`);
+ throw new Error(`Expected post-sixty-fourth progress tab to complete Weishui camps and point toward Weishui north bank pressure: ${JSON.stringify(postSixtyFourthProgressState?.campaignProgress)}`);
}
await page.screenshot({ path: 'dist/verification-post-sixty-fourth-progress.png', fullPage: true });
+ await page.mouse.click(1120, 38);
+ await page.waitForTimeout(180);
+ const northbankSortieState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp());
+ if (
+ !northbankSortieState?.sortieVisible ||
+ northbankSortieState.campaign?.step !== 'sixty-fourth-camp' ||
+ northbankSortieState.campTitle !== '위수 진영전 후 군영' ||
+ !String(northbankSortieState.sortiePlan?.objectiveLine ?? '').includes('위수 북안 압박전') ||
+ !String(northbankSortieState.sortiePlan?.objectiveLine ?? '').includes('재추격 저지') ||
+ northbankSortieState.sortiePlan?.maxCount !== 7 ||
+ northbankSortieState.sortiePlan?.recommendedTotal !== 9 ||
+ !northbankSortieState.sortieRoster?.some((unit) => unit.id === 'zhuge-liang' && unit.selected && unit.required) ||
+ !northbankSortieState.sortieRoster?.some((unit) => unit.id === 'jiang-wei' && unit.recommended) ||
+ !northbankSortieState.sortieRoster?.some((unit) => unit.id === 'ma-dai' && unit.recommended) ||
+ !northbankSortieState.sortieRoster?.some((unit) => unit.id === 'wang-ping' && unit.recommended) ||
+ !northbankSortieState.sortieRoster?.some((unit) => unit.id === 'huang-quan' && unit.recommended) ||
+ !northbankSortieState.sortieRoster?.some((unit) => unit.id === 'li-yan' && unit.recommended) ||
+ !northbankSortieState.sortieRoster?.some((unit) => unit.id === 'wei-yan' && unit.recommended) ||
+ !northbankSortieState.sortieRoster?.some((unit) => unit.id === 'zhao-yun' && unit.recommended) ||
+ !northbankSortieState.sortieRoster?.some((unit) => unit.id === 'ma-liang' && unit.recommended) ||
+ northbankSortieState.sortieRoster?.some((unit) => unit.id === 'liu-bei')
+ ) {
+ throw new Error(`Expected Weishui north bank sortie to require Zhuge Liang, exclude Liu Bei, and recommend nine low-road/flank/supply roles: ${JSON.stringify(northbankSortieState)}`);
+ }
+ assertSortieTacticalRoster(northbankSortieState, [
+ '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 northbankPriorityUnits = ['jiang-wei', 'ma-dai', 'wang-ping', 'huang-quan', 'li-yan', 'wei-yan'];
+ for (const unitId of northbankPriorityUnits) {
+ 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 && !northbankPriorityUnits.includes(unit.id)
+ );
+ if (removable) {
+ await clickSortieRosterUnit(page, removable.id);
+ }
+ await clickSortieRosterUnit(page, unitId);
+ }
+ }
+
+ const northbankSortieReadyState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp());
+ if (
+ !northbankPriorityUnits.every((unitId) =>
+ northbankSortieReadyState.sortieRoster?.some((unit) => unit.id === unitId && unit.selected)
+ ) ||
+ !northbankSortieReadyState.sortieRoster?.some((unit) => unit.id === 'zhuge-liang' && unit.selected && unit.required) ||
+ northbankSortieReadyState.sortieRoster?.some((unit) => unit.id === 'liu-bei') ||
+ northbankSortieReadyState.sortieRoster?.some((unit) => unit.id === 'zhao-yun' && unit.selected) ||
+ northbankSortieReadyState.sortieRoster?.some((unit) => unit.id === 'ma-liang' && unit.selected) ||
+ northbankSortieReadyState.sortiePlan?.selectedCount !== 7 ||
+ northbankSortieReadyState.sortiePlan?.recommendedSelectedCount < 7 ||
+ northbankSortieReadyState.sortiePlan?.recommendedTotal !== 9
+ ) {
+ throw new Error(`Expected Weishui north bank sortie to deploy Zhuge Liang plus six low-road/flank/supply officers while benching Zhao Yun and Ma Liang: ${JSON.stringify(northbankSortieReadyState)}`);
+ }
+ await page.screenshot({ path: 'dist/verification-sixty-fifth-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-weishui-northbank') {
+ break;
+ }
+ await page.keyboard.press('Space');
+ await page.waitForTimeout(320);
+ }
+ const northbankStoryState = await page.evaluate(() =>
+ window.__HEROS_GAME__?.scene.getScene('StoryScene')?.getDebugState?.()
+ );
+ if (
+ northbankStoryState?.background !== 'story-weishui-northbank' ||
+ northbankStoryState?.backgroundReady !== true ||
+ northbankStoryState?.portraitKey !== 'zhugeLiang' ||
+ northbankStoryState?.portraitTextureKey !== 'portrait-zhuge-liang'
+ ) {
+ throw new Error(`Expected Weishui north bank story to lazy-load its new generated background and show Zhuge Liang portrait by speaker fallback: ${JSON.stringify(northbankStoryState)}`);
+ }
+ await page.screenshot({ path: 'dist/verification-sixty-fifth-story.png', fullPage: true });
+
+ for (let i = 0; i < 50; i += 1) {
+ const enteredSixtyFifthBattle = await page.evaluate(() => {
+ const state = window.__HEROS_DEBUG__?.battle();
+ return state?.scene === 'BattleScene' && state?.battleId === 'sixty-fifth-battle-weishui-northbank';
+ });
+ if (enteredSixtyFifthBattle) {
+ 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-fifth-battle-weishui-northbank' && state?.battleOutcome === null && state?.phase === 'idle';
+ });
+ await page.screenshot({ path: 'dist/verification-sixty-fifth-battle.png', fullPage: true });
+
+ const sixtyFifthBattleState = await page.evaluate(() => window.__HEROS_DEBUG__?.battle());
+ const sixtyFifthEnemies = sixtyFifthBattleState.units.filter((unit) => unit.faction === 'enemy');
+ const sixtyFifthAllies = sixtyFifthBattleState.units.filter((unit) => unit.faction === 'ally');
+ const sixtyFifthEnemyBehaviors = new Set(sixtyFifthEnemies.map((unit) => unit.ai));
+ if (
+ sixtyFifthBattleState.camera?.mapWidth !== 130 ||
+ sixtyFifthBattleState.camera?.mapHeight !== 112 ||
+ sixtyFifthBattleState.mapTextureKey !== 'battle-map-sixty-fifth' ||
+ sixtyFifthBattleState.mapTextureReady !== true ||
+ sixtyFifthBattleState.mapBackgroundReady !== true ||
+ sixtyFifthBattleState.victoryConditionLabel !== '위수 북안 전진 진영 압박과 재추격 저지' ||
+ !sixtyFifthBattleState.objectives?.some((objective) => objective.id === 'break-sima-yi-northbank-delay-line' && objective.label === '사마의 북안 지연선 압박') ||
+ !sixtyFifthBattleState.objectives?.some((objective) => objective.id === 'stop-zhang-he-rearguard-raid' && objective.label === '장합 재추격대 저지') ||
+ !sixtyFifthBattleState.objectives?.some((objective) => objective.id === 'silence-guo-huai-northbank-beacons' && objective.label === '곽회 북안 봉화 차단') ||
+ !sixtyFifthBattleState.objectives?.some((objective) => objective.id === 'hold-northbank-forward-camps' && objective.label === '북안 전진 진영 확보') ||
+ !sixtyFifthBattleState.objectives?.some((objective) => objective.id === 'reassure-northbank-villages' && objective.label === '강북 마을 안심') ||
+ sixtyFifthEnemies.length < 42 ||
+ !sixtyFifthEnemyBehaviors.has('aggressive') ||
+ !sixtyFifthEnemyBehaviors.has('guard') ||
+ !sixtyFifthEnemyBehaviors.has('hold') ||
+ !sixtyFifthEnemies.some((unit) => unit.id === 'northern-eleventh-leader-sima-yi' && unit.combatPortraitKey === 'portrait-sima-yi' && unit.textureBase === 'unit-sima-yi') ||
+ !sixtyFifthEnemies.some((unit) => unit.id === 'northern-eleventh-officer-zhang-he') ||
+ !sixtyFifthEnemies.some((unit) => unit.id === 'northern-eleventh-officer-guo-huai') ||
+ !sixtyFifthAllies.some((unit) => unit.id === 'zhuge-liang' && unit.combatPortraitKey === 'portrait-zhuge-liang' && unit.textureBase === 'unit-zhuge-liang') ||
+ !sixtyFifthAllies.some((unit) => unit.id === 'jiang-wei' && unit.combatPortraitKey === 'portrait-jiang-wei' && unit.textureBase === 'unit-jiang-wei') ||
+ !sixtyFifthAllies.some((unit) => unit.id === 'ma-dai') ||
+ !sixtyFifthAllies.some((unit) => unit.id === 'wang-ping') ||
+ !sixtyFifthAllies.some((unit) => unit.id === 'huang-quan') ||
+ !sixtyFifthAllies.some((unit) => unit.id === 'li-yan') ||
+ !sixtyFifthAllies.some((unit) => unit.id === 'wei-yan') ||
+ sixtyFifthAllies.some((unit) => unit.id === 'zhao-yun') ||
+ sixtyFifthAllies.some((unit) => unit.id === 'ma-liang') ||
+ sixtyFifthAllies.some((unit) => unit.id === 'liu-bei')
+ ) {
+ throw new Error(`Expected sixty-fifth battle to use the Weishui north bank map, Sima Yi/Zhang He/Guo Huai objectives, selected low-road/flank/supply officers, and mixed Wei AI: ${JSON.stringify(sixtyFifthBattleState)}`);
+ }
+
+ 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 sixtyFifthCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp());
+ if (
+ sixtyFifthCampState?.campBattleId !== 'sixty-fifth-battle-weishui-northbank' ||
+ sixtyFifthCampState.campaign?.step !== 'sixty-fifth-camp' ||
+ sixtyFifthCampState.campTitle !== '위수 북안 압박 후 군영' ||
+ sixtyFifthCampState.availableDialogueIds?.length !== 2 ||
+ !sixtyFifthCampState.availableDialogueIds.every((id) => id.startsWith('northern-eleventh-')) ||
+ sixtyFifthCampState.availableVisitIds?.length !== 1 ||
+ !sixtyFifthCampState.availableVisitIds.every((id) => id.startsWith('northern-eleventh-')) ||
+ sixtyFifthCampState.rosterCollection?.total < 22 ||
+ !sixtyFifthCampState.report?.bonds?.some((bond) => bond.id === 'zhuge-liang__li-yan_northern-eleventh')
+ ) {
+ throw new Error(`Expected Weishui north bank aftermath camp to expose eleventh northern campaign events and bonds: ${JSON.stringify(sixtyFifthCampState)}`);
+ }
+ await page.screenshot({ path: 'dist/verification-sixty-fifth-camp.png', fullPage: true });
+
+ await page.mouse.click(966, 38);
+ await page.waitForTimeout(180);
+ const postSixtyFifthProgressState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp());
+ if (
+ postSixtyFifthProgressState?.activeTab !== 'progress' ||
+ postSixtyFifthProgressState.campaignProgress?.completedKnown !== 65 ||
+ postSixtyFifthProgressState.campaignProgress?.totalKnown !== 65 ||
+ postSixtyFifthProgressState.campaignProgress?.activeChapter?.id !== 'northern-campaign' ||
+ postSixtyFifthProgressState.campaignProgress?.latestBattleTitle !== '위수 북안 압박전' ||
+ postSixtyFifthProgressState.campaignProgress?.nextBattleTitle !== '준비 중'
+ ) {
+ throw new Error(`Expected post-sixty-fifth progress tab to complete Weishui north bank pressure and leave the next northern step pending: ${JSON.stringify(postSixtyFifthProgressState?.campaignProgress)}`);
+ }
+ await page.screenshot({ path: 'dist/verification-post-sixty-fifth-progress.png', fullPage: true });
+
await page.evaluate(() => {
const game = window.__HEROS_GAME__;
game?.scene.stop('CampScene');
@@ -9217,12 +9398,12 @@ try {
}
}
for (let attempt = 0; attempt < 24; attempt += 1) {
- const continuedToWeishuiCampsCamp = await page.evaluate(() => {
+ const continuedToNorthbankCamp = await page.evaluate(() => {
const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? [];
const camp = window.__HEROS_DEBUG__?.camp?.() ?? null;
- return activeScenes.includes('CampScene') && camp?.campaign?.step === 'sixty-fourth-camp';
+ return activeScenes.includes('CampScene') && camp?.campaign?.step === 'sixty-fifth-camp';
});
- if (continuedToWeishuiCampsCamp) {
+ if (continuedToNorthbankCamp) {
break;
}
await page.keyboard.press('Space');
@@ -9231,17 +9412,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-fourth-camp';
+ return activeScenes.includes('CampScene') && camp?.campaign?.step === 'sixty-fifth-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-fourth-camp') {
- throw new Error(`Expected title continue to reopen the Weishui camps aftermath camp: ${JSON.stringify(titleContinueState)}`);
+ if (!titleContinueState.activeScenes.includes('CampScene') || titleContinueState.camp?.campaign?.step !== 'sixty-fifth-camp') {
+ throw new Error(`Expected title continue to reopen the Weishui north bank aftermath camp: ${JSON.stringify(titleContinueState)}`);
}
- console.log(`Verified title-to-Weishui-camps 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, Weishui camps, and pending next northern step state at ${targetUrl}`);
+ console.log(`Verified title-to-Weishui-northbank 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, Weishui camps, Weishui north bank pressure, and pending next northern step state at ${targetUrl}`);
} finally {
await browser?.close();
if (serverProcess && !serverProcess.killed) {
@@ -9422,3 +9603,4 @@ async function clickReserveTrainingFocus(page, focusId) {
await page.mouse.click(selectorX + 12 + index * (buttonWidth + 8) + buttonWidth / 2, 655);
await page.waitForTimeout(120);
}
+
diff --git a/src/assets/images/battle/sixty-fifth-battle-map.svg b/src/assets/images/battle/sixty-fifth-battle-map.svg
new file mode 100644
index 0000000..8c1b03f
--- /dev/null
+++ b/src/assets/images/battle/sixty-fifth-battle-map.svg
@@ -0,0 +1,98 @@
+
diff --git a/src/assets/images/story/24-weishui-northbank.png b/src/assets/images/story/24-weishui-northbank.png
new file mode 100644
index 0000000..51c34b7
Binary files /dev/null and b/src/assets/images/story/24-weishui-northbank.png differ
diff --git a/src/game/data/battleMapAssets.ts b/src/game/data/battleMapAssets.ts
index 5d513b0..461283a 100644
--- a/src/game/data/battleMapAssets.ts
+++ b/src/game/data/battleMapAssets.ts
@@ -14,6 +14,7 @@ import sixtyFirstBattleMapUrl from '../../assets/images/battle/sixty-first-battl
import sixtySecondBattleMapUrl from '../../assets/images/battle/sixty-second-battle-map.svg';
import sixtyThirdBattleMapUrl from '../../assets/images/battle/sixty-third-battle-map.svg';
import sixtyFourthBattleMapUrl from '../../assets/images/battle/sixty-fourth-battle-map.svg';
+import sixtyFifthBattleMapUrl from '../../assets/images/battle/sixty-fifth-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';
@@ -126,6 +127,7 @@ export const battleMapAssets: Record = {
'battle-map-sixty-second': sixtySecondBattleMapUrl,
'battle-map-sixty-third': sixtyThirdBattleMapUrl,
'battle-map-sixty-fourth': sixtyFourthBattleMapUrl,
+ 'battle-map-sixty-fifth': sixtyFifthBattleMapUrl,
'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 0a06e4a..5632c59 100644
--- a/src/game/data/battles.ts
+++ b/src/game/data/battles.ts
@@ -55,6 +55,10 @@ import {
sixtyFourthBattleMap,
sixtyFourthBattleUnits,
sixtyFourthBattleVictoryPages,
+ sixtyFifthBattleBonds,
+ sixtyFifthBattleMap,
+ sixtyFifthBattleUnits,
+ sixtyFifthBattleVictoryPages,
fiftySeventhBattleBonds,
fiftySeventhBattleMap,
fiftySeventhBattleUnits,
@@ -325,7 +329,8 @@ export type BattleScenarioId =
| 'sixty-first-battle-hanzhong-rain-defense'
| 'sixty-second-battle-qishan-renewed-offensive'
| 'sixty-third-battle-lucheng-pursuit'
- | 'sixty-fourth-battle-weishui-camps';
+ | 'sixty-fourth-battle-weishui-camps'
+ | 'sixty-fifth-battle-weishui-northbank';
export type BattleObjectiveKind = 'defeat-leader' | 'pacify-leader' | 'keep-unit-alive' | 'secure-terrain' | 'quick-victory';
@@ -4335,6 +4340,73 @@ export const sixtyFourthBattleScenario: BattleScenarioDefinition = {
nextCampScene: 'CampScene'
};
+export const sixtyFifthBattleScenario: BattleScenarioDefinition = {
+ id: 'sixty-fifth-battle-weishui-northbank',
+ title: '위수 북안 압박전',
+ victoryConditionLabel: '위수 북안 전진 진영 압박과 재추격 저지',
+ defeatConditionLabel: '제갈량 퇴각 또는 보급 수레 붕괴',
+ openingObjectiveLines: [
+ '위수 남안 진영을 얻었지만 사마의는 북안에 더 촘촘한 지연선을 세웠습니다. 이번 출전은 강북을 끝내는 결전이 아니라, 전진 진영과 봉화선을 눌러 다음 북방 공세의 숨을 만드는 싸움입니다.',
+ '강유와 마대는 낮은 물목과 측면 기병을 읽고, 왕평과 황권은 정지선과 장부를 맞춥니다. 제갈량과 이엄은 진영을 얻은 뒤에도 수레가 끊기지 않을 군령을 세워야 합니다.',
+ '사마의의 북안 지연선, 장합의 재추격대, 곽회의 북안 봉화를 동시에 늦추십시오. 강북 마을과 전진 진영을 지키면 다음 북방 선택지가 더 선명해집니다.'
+ ],
+ map: sixtyFifthBattleMap,
+ units: sixtyFifthBattleUnits,
+ bonds: sixtyFifthBattleBonds,
+ mapTextureKey: 'battle-map-sixty-fifth',
+ leaderUnitId: 'northern-eleventh-leader-sima-yi',
+ quickVictoryTurnLimit: 130,
+ baseVictoryGold: 21240,
+ objectives: [
+ {
+ id: 'break-sima-yi-northbank-delay-line',
+ kind: 'defeat-leader',
+ label: '사마의 북안 지연선 압박',
+ rewardGold: 15100,
+ unitId: 'northern-eleventh-leader-sima-yi'
+ },
+ {
+ id: 'stop-zhang-he-rearguard-raid',
+ kind: 'defeat-leader',
+ label: '장합 재추격대 저지',
+ rewardGold: 9160,
+ unitId: 'northern-eleventh-officer-zhang-he'
+ },
+ {
+ id: 'silence-guo-huai-northbank-beacons',
+ kind: 'defeat-leader',
+ label: '곽회 북안 봉화 차단',
+ rewardGold: 8520,
+ unitId: 'northern-eleventh-officer-guo-huai'
+ },
+ {
+ id: 'hold-northbank-forward-camps',
+ kind: 'secure-terrain',
+ label: '북안 전진 진영 확보',
+ rewardGold: 7100,
+ terrain: 'camp'
+ },
+ {
+ id: 'reassure-northbank-villages',
+ kind: 'secure-terrain',
+ label: '강북 마을 안심',
+ rewardGold: 6900,
+ terrain: 'village'
+ },
+ {
+ id: 'quick',
+ kind: 'quick-victory',
+ label: '130턴 이내 북안 압박',
+ rewardGold: 4720,
+ maxTurn: 130
+ }
+ ],
+ defeatConditions: [{ kind: 'unit-defeated', unitId: 'zhuge-liang' }],
+ itemRewards: ['금 144', '상처약 110', '군주 54', '북안 전진 장부 1', '위수 재추격 기록 1'],
+ victoryPages: sixtyFifthBattleVictoryPages,
+ nextCampScene: 'CampScene'
+};
+
export const defaultBattleScenarioId: BattleScenarioId = firstBattleScenario.id;
export const battleScenarios: Record = {
@@ -4401,7 +4473,8 @@ export const battleScenarios: Record
'sixty-first-battle-hanzhong-rain-defense': sixtyFirstBattleScenario,
'sixty-second-battle-qishan-renewed-offensive': sixtySecondBattleScenario,
'sixty-third-battle-lucheng-pursuit': sixtyThirdBattleScenario,
- 'sixty-fourth-battle-weishui-camps': sixtyFourthBattleScenario
+ 'sixty-fourth-battle-weishui-camps': sixtyFourthBattleScenario,
+ 'sixty-fifth-battle-weishui-northbank': sixtyFifthBattleScenario
};
export const defaultBattleScenario = battleScenarios[defaultBattleScenarioId];
diff --git a/src/game/data/campaignFlow.ts b/src/game/data/campaignFlow.ts
index 682f95b..a18c543 100644
--- a/src/game/data/campaignFlow.ts
+++ b/src/game/data/campaignFlow.ts
@@ -19,6 +19,7 @@ import {
sixtySecondBattleScenario,
sixtyThirdBattleScenario,
sixtyFourthBattleScenario,
+ sixtyFifthBattleScenario,
fortiethBattleScenario,
fortyFirstBattleScenario,
fortySecondBattleScenario,
@@ -90,6 +91,8 @@ import {
sixtyThirdBattleVictoryPages,
sixtyFourthBattleIntroPages,
sixtyFourthBattleVictoryPages,
+ sixtyFifthBattleIntroPages,
+ sixtyFifthBattleVictoryPages,
fiftyFirstBattleIntroPages,
fiftyFirstBattleVictoryPages,
fiftySecondBattleIntroPages,
@@ -939,13 +942,24 @@ const sortieFlows: Record = {
},
[sixtyFourthBattleScenario.id]: {
afterBattleId: sixtyFourthBattleScenario.id,
- eyebrow: '다음 장 준비',
- title: '위수 진영 이후',
+ eyebrow: '다음 전장',
+ title: sixtyFifthBattleScenario.title,
description:
- '위수 남안 진영과 봉화선은 흔들었지만, 북방의 길은 아직 끝나지 않았습니다. 다음 계산에서는 강가 장기전의 보급, 사마의 본영 압박, 장합의 재추격을 다시 나누어야 합니다.',
- rewardHint: '다음 목표: 위수 전선 재정비 / 다음 북방 공세 검토',
- pages: sixtyFourthBattleVictoryPages,
- unavailableNotice: '위수 진영 이후의 다음 북방 전투는 이어지는 작업에서 확장합니다. 현재는 위수 진영 확보, 장합 물목 추격 저지, 곽회 봉화 차단과 강가 보급 정비를 확인할 수 있습니다.'
+ '위수 남안 진영과 봉화선은 흔들었지만, 사마의의 북안 지연선은 아직 시간을 벌고 있습니다. 강을 끼고 오래 싸울 사람과 더 밀고 들어갈 사람을 다시 나누어 북안 전진 진영을 압박해야 합니다.',
+ rewardHint: `예상 보상: ${sixtyFifthBattleScenario.title} 개방 / 북안 전진 장부`,
+ nextBattleId: sixtyFifthBattleScenario.id,
+ campaignStep: 'sixty-fifth-battle',
+ pages: [...sixtyFourthBattleVictoryPages, ...sixtyFifthBattleIntroPages]
+ },
+ [sixtyFifthBattleScenario.id]: {
+ afterBattleId: sixtyFifthBattleScenario.id,
+ eyebrow: '다음 장 준비',
+ title: '위수 북안 이후',
+ description:
+ '위수 북안 지연선은 눌렸지만, 북방의 길은 아직 끝나지 않았습니다. 다음 계산에서는 오래 붙들 진영, 물릴 진영, 더 깊이 들어갈 길을 다시 나누어야 합니다.',
+ rewardHint: '다음 목표: 북안 전선 재정비 / 다음 북방 공세 검토',
+ pages: sixtyFifthBattleVictoryPages,
+ unavailableNotice: '위수 북안 이후의 다음 북방 전투는 이어지는 작업에서 확장합니다. 현재는 북안 전진 진영 압박, 장합 재추격 저지, 곽회 봉화 차단과 강북 보급 정비를 확인할 수 있습니다.'
},
'northern-campaign-prep-camp-legacy': {
afterBattleId: fiftyFourthBattleScenario.id,
diff --git a/src/game/data/scenario.ts b/src/game/data/scenario.ts
index 76b6965..dca15af 100644
--- a/src/game/data/scenario.ts
+++ b/src/game/data/scenario.ts
@@ -4066,6 +4066,59 @@ export const sixtyFourthBattleVictoryPages: StoryPage[] = [
}
];
+export const sixtyFifthBattleIntroPages: StoryPage[] = [
+ {
+ id: 'sixty-fifth-northbank-council',
+ bgm: 'story-dark',
+ chapter: '위수 북안',
+ background: 'story-weishui-northbank',
+ speaker: '제갈량',
+ text: '위수 남안의 진영은 확보했지만, 사마의는 북안의 진문과 봉화대를 더 촘촘히 묶었습니다. 이번에는 강을 건너 끝장을 보려는 싸움이 아니라, 북안 지연선을 눌러 다음 출정의 숨을 확보하는 싸움입니다.'
+ },
+ {
+ id: 'sixty-fifth-forward-ledger',
+ bgm: 'battle-prep',
+ chapter: '전진 장부',
+ background: 'story-weishui-northbank',
+ speaker: '황권',
+ text: '강을 건너면 이긴 곳보다 버릴 수 없는 곳이 더 빨리 늘어납니다. 전진 진영과 회수선, 마을 군량을 한 장부에 묶지 않으면 승리한 길이 곧 굶주린 길이 됩니다.'
+ },
+ {
+ id: 'sixty-fifth-rearguard-threat',
+ bgm: 'story-dark',
+ chapter: '재추격의 그림자',
+ background: 'story-weishui-northbank',
+ speaker: '강유',
+ text: '장합은 물러나는 척하며 다시 뒤를 물 것이고, 곽회는 봉화와 우회 기병으로 사마의의 시간을 벌 것입니다. 북안으로 나아가되, 돌아올 표식과 남길 진영을 동시에 지켜야 합니다.'
+ }
+];
+
+export const sixtyFifthBattleVictoryPages: StoryPage[] = [
+ {
+ id: 'sixty-fifth-victory-delay-line-pressed',
+ bgm: 'militia-theme',
+ chapter: '눌린 지연선',
+ background: 'story-weishui-northbank',
+ text: '촉한군은 위수 북안의 전진 진영을 압박하고 장합의 재추격을 늦췄습니다. 사마의의 본영은 아직 물러서지 않았지만, 그의 시간을 버는 선이 처음으로 흔들렸습니다.'
+ },
+ {
+ id: 'sixty-fifth-victory-northbank-ledger',
+ bgm: 'story-dark',
+ chapter: '강북 장부',
+ background: 'story-weishui-northbank',
+ speaker: '황권',
+ text: '전진 진영과 마을 군량, 회수 깃발이 한 장부에 묶였습니다. 제갈량은 그 장부 위에 더 깊은 북방 길을 그리되, 아직 끝이라는 말을 적지 않았습니다.'
+ },
+ {
+ id: 'sixty-fifth-next-front-choice',
+ bgm: 'story-dark',
+ chapter: '다음 선택',
+ background: 'story-weishui-northbank',
+ speaker: '제갈량',
+ text: '위수 북안이 열렸다고 북벌이 끝난 것은 아닙니다. 오래 붙들 진영, 버릴 진영, 다시 밀고 들어갈 길을 나누어야만 다음 북방 공세가 병사의 숨을 해치지 않습니다.'
+ }
+];
+
export const firstBattleMap: BattleMap = {
width: 20,
height: 18,
@@ -4595,6 +4648,12 @@ export const sixtyFourthBattleMap: BattleMap = {
terrain: createSixtyFourthBattleTerrain()
};
+export const sixtyFifthBattleMap: BattleMap = {
+ width: 130,
+ height: 112,
+ terrain: createSixtyFifthBattleTerrain()
+};
+
export const firstBattleUnits: UnitData[] = [
{
id: 'liu-bei',
@@ -18188,6 +18247,103 @@ export const sixtyFourthBattleUnits: UnitData[] = [
...sixtyFourthEnemySpecs.map((spec) => createThirtyFirstEnemyUnit(...spec))
];
+const sixtyFifthBattleAllyPositions: Record = {
+ 'guan-yu': { x: 18, y: 100 },
+ 'zhang-fei': { x: 26, y: 103 },
+ 'jian-yong': { x: 9, y: 108 },
+ 'mi-zhu': { x: 15, y: 109 },
+ 'sun-qian': { x: 21, y: 109 },
+ 'zhao-yun': { x: 33, y: 98 },
+ 'zhuge-liang': { x: 20, y: 95 },
+ 'ma-liang': { x: 27, y: 92 },
+ 'yi-ji': { x: 9, y: 110 },
+ 'gong-zhi': { x: 31, y: 107 },
+ 'huang-zhong': { x: 41, y: 96 },
+ 'wei-yan': { x: 48, y: 96 },
+ 'pang-tong': { x: 17, y: 91 },
+ 'fa-zheng': { x: 31, y: 90 },
+ 'wu-yi': { x: 50, y: 105 },
+ 'yan-yan': { x: 54, y: 98 },
+ 'li-yan': { x: 36, y: 89 },
+ 'huang-quan': { x: 14, y: 94 },
+ 'ma-chao': { x: 57, y: 94 },
+ 'ma-dai': { x: 63, y: 101 },
+ 'wang-ping': { x: 43, y: 89 },
+ 'jiang-wei': { x: 37, y: 92 }
+};
+
+const sixtyFifthEnemySpecs: ReadonlyArray<
+ readonly [string, string, string, UnitClassKey, number, number, number, number, number]
+> = [
+ ['northern-eleventh-scout-a', '북안 서쪽 척후병', '서쪽 강북 정찰', 'bandit', 241, 1160, 314, 52, 92],
+ ['northern-eleventh-scout-b', '회수선 척후병', '회수선 정찰', 'bandit', 241, 1160, 314, 66, 88],
+ ['northern-eleventh-scout-c', '북안 물목 척후병', '중앙 물목 정찰', 'bandit', 242, 1172, 316, 80, 82],
+ ['northern-eleventh-scout-d', '봉화 척후병', '동쪽 봉화 정찰', 'bandit', 242, 1172, 316, 94, 74],
+ ['northern-eleventh-scout-e', '곽회 전초 척후병', '차단로 정찰', 'bandit', 243, 1184, 318, 110, 62],
+ ['northern-eleventh-scout-f', '북안 뒤 척후병', '북쪽 진문 정찰', 'bandit', 243, 1184, 318, 128, 40],
+ ['northern-eleventh-infantry-a', '위군 북안 보병', '서쪽 강북 보병', 'yellowTurban', 242, 1780, 420, 59, 87],
+ ['northern-eleventh-infantry-b', '위군 회수선 보병', '회수선 보병', 'yellowTurban', 242, 1780, 420, 72, 84],
+ ['northern-eleventh-infantry-c', '위군 물목 보병', '중앙 물목 보병', 'yellowTurban', 243, 1800, 424, 86, 77],
+ ['northern-eleventh-infantry-d', '위군 봉화 보병', '봉화대 보병', 'yellowTurban', 243, 1800, 424, 101, 70],
+ ['northern-eleventh-infantry-e', '곽회 차단 보병', '차단로 보병', 'yellowTurban', 244, 1820, 428, 114, 57],
+ ['northern-eleventh-infantry-f', '사마의 북안 보병', '북안 본영 보병', 'yellowTurban', 244, 1820, 428, 125, 47],
+ ['northern-eleventh-infantry-g', '수레 차단병', '강북 수레 차단 보병', 'yellowTurban', 245, 1840, 432, 97, 93],
+ ['northern-eleventh-infantry-h', '북쪽 진문 보병', '진문 보병', 'yellowTurban', 245, 1840, 432, 128, 29],
+ ['northern-eleventh-archer-a', '북안 서쪽 궁병', '서쪽 능선 궁병', 'archer', 242, 1290, 346, 68, 79],
+ ['northern-eleventh-archer-b', '회수선 궁병', '회수선 궁병', 'archer', 242, 1290, 346, 82, 73],
+ ['northern-eleventh-archer-c', '물목 궁병', '중앙 물목 궁병', 'archer', 243, 1302, 348, 96, 65],
+ ['northern-eleventh-archer-d', '봉화대 궁병', '봉화 궁병', 'archer', 244, 1314, 350, 110, 54],
+ ['northern-eleventh-archer-e', '북안 본영 궁병', '본영 궁병', 'archer', 244, 1314, 350, 122, 37],
+ ['northern-eleventh-archer-f', '북쪽 절벽 궁병', '절벽 궁병', 'archer', 245, 1326, 352, 128, 21],
+ ['northern-eleventh-cavalry-a', '장합 재추격 기병', '서쪽 재추격 기병', 'cavalry', 243, 1860, 444, 54, 80],
+ ['northern-eleventh-cavalry-b', '장합 재추격 기병', '회수선 재추격 기병', 'cavalry', 243, 1860, 444, 68, 77],
+ ['northern-eleventh-cavalry-c', '장합 재추격 기병', '중앙 재추격 기병', 'cavalry', 244, 1882, 448, 84, 69],
+ ['northern-eleventh-cavalry-d', '곽회 차단 기병', '동쪽 차단 기병', 'cavalry', 245, 1904, 452, 101, 61],
+ ['northern-eleventh-cavalry-e', '사마의 예비 기병', '본영 예비 기병', 'cavalry', 245, 1904, 452, 117, 47],
+ ['northern-eleventh-cavalry-f', '북쪽 절벽 기병', '절벽 차단 기병', 'cavalry', 246, 1926, 456, 128, 32],
+ ['northern-eleventh-camp-guard-a', '북안 서쪽 수비대', '서쪽 전진 진영 수비', 'yellowTurban', 243, 1800, 426, 46, 66],
+ ['northern-eleventh-camp-guard-b', '회수 표식 수비대', '표식 수비', 'yellowTurban', 243, 1800, 426, 64, 62],
+ ['northern-eleventh-camp-guard-c', '물목 수비대', '중앙 물목 수비', 'yellowTurban', 244, 1820, 430, 82, 55],
+ ['northern-eleventh-camp-guard-d', '봉화 길 수비대', '봉화 길 수비', 'yellowTurban', 244, 1820, 430, 100, 46],
+ ['northern-eleventh-camp-guard-e', '사마의 북안 수비대', '본영 수비', 'yellowTurban', 245, 1840, 434, 116, 38],
+ ['northern-eleventh-camp-guard-f', '북쪽 절벽 수비대', '절벽 수비', 'yellowTurban', 245, 1840, 434, 128, 25],
+ ['northern-eleventh-strategist-a', '위군 북안 군리', '강북 장부 군리', 'strategist', 243, 1380, 366, 84, 62],
+ ['northern-eleventh-strategist-b', '위군 보급 군리', '수레 차단 군리', 'strategist', 244, 1392, 368, 100, 50],
+ ['northern-eleventh-strategist-c', '사마의 지연 군리', '지연선 장부 군리', 'strategist', 244, 1392, 368, 116, 40],
+ ['northern-eleventh-strategist-d', '곽회 봉화 군리', '봉화 장부 군리', 'strategist', 245, 1404, 370, 124, 70],
+ ['northern-eleventh-officer-northbank', '북안 전진장', '북안 전진 진영 장수', 'rebelLeader', 247, 1920, 456, 90, 84],
+ ['northern-eleventh-officer-supply', '강북 수레 차단장', '수레 차단 장수', 'rebelLeader', 247, 1920, 456, 111, 76],
+ ['northern-eleventh-officer-guo-huai', '곽회', '북안 봉화 장수', 'rebelLeader', 246, 1900, 452, 121, 74],
+ ['northern-eleventh-officer-relay', '봉화 연락장', '북안 뒤 봉화 장수', 'rebelLeader', 246, 1900, 452, 125, 56],
+ ['northern-eleventh-officer-zhang-he', '장합', '재추격 대장', 'rebelLeader', 248, 1960, 462, 126, 35],
+ ['northern-eleventh-leader-sima-yi', '사마의', '북안 지연 총지휘', 'rebelLeader', 249, 2000, 468, 122, 47]
+];
+
+export const sixtyFifthBattleUnits: 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, sixtyFifthBattleAllyPositions[unit.id] ?? { x: unit.x, y: unit.y })),
+ ...sixtyFifthEnemySpecs.map((spec) => createThirtyFirstEnemyUnit(...spec))
+];
+
export const firstBattleBonds: BattleBond[] = [
{
id: 'liu-bei__guan-yu',
@@ -19665,6 +19821,46 @@ export const sixtyFourthBattleBonds: BattleBond[] = [
...northernTenthBattleBonds
].map(cloneBattleBondForScenario);
+export const northernEleventhBattleBonds: BattleBond[] = [
+ {
+ id: 'jiang-wei__ma-dai_northern-eleventh',
+ unitIds: ['jiang-wei', 'ma-dai'],
+ title: '낮은 길과 측면',
+ level: 60,
+ exp: 0,
+ description: '강유가 북안 낮은 길을 읽고 마대가 측면 기병을 묶으면, 장합의 재추격을 먼저 꺾을 수 있습니다.'
+ },
+ {
+ id: 'zhuge-liang__li-yan_northern-eleventh',
+ unitIds: ['zhuge-liang', 'li-yan'],
+ title: '군령과 수레',
+ level: 58,
+ exp: 0,
+ description: '제갈량의 전진 명령과 이엄의 수레 배정이 맞물리면, 강북 진영을 얻고도 후방 숨을 잃지 않습니다.'
+ },
+ {
+ id: 'wang-ping__huang-quan_northern-eleventh',
+ unitIds: ['wang-ping', 'huang-quan'],
+ title: '정지선과 장부',
+ level: 56,
+ exp: 0,
+ description: '왕평의 멈출 선과 황권의 소모 장부가 함께 움직이면, 북안 압박 뒤에도 지켜야 할 진영을 구분할 수 있습니다.'
+ },
+ {
+ id: 'wei-yan__zhao-yun_northern-eleventh',
+ unitIds: ['wei-yan', 'zhao-yun'],
+ title: '돌파와 회수',
+ level: 54,
+ exp: 0,
+ description: '위연이 북안 진영 사이를 흔들고 조운이 예비대를 붙들면, 돌파가 고립으로 바뀌기 전에 군을 되돌릴 수 있습니다.'
+ }
+];
+
+export const sixtyFifthBattleBonds: BattleBond[] = [
+ ...sixtyFourthBattleBonds,
+ ...northernEleventhBattleBonds
+].map(cloneBattleBondForScenario);
+
function createEighthBattleTerrain(): TerrainType[][] {
return Array.from({ length: 22 }, (_, y) =>
Array.from({ length: 26 }, (_, x): TerrainType => {
@@ -23304,6 +23500,88 @@ function createSixtyFourthBattleTerrain(): TerrainType[][] {
);
}
+function createSixtyFifthBattleTerrain(): TerrainType[][] {
+ return Array.from({ length: 112 }, (_, y) =>
+ Array.from({ length: 130 }, (_, x): TerrainType => {
+ if (
+ (x >= 2 && x <= 128 && y >= 27 && y <= 38) ||
+ (x >= 3 && x <= 128 && y >= 42 && y <= 52)
+ ) {
+ return 'river';
+ }
+ if (
+ (x >= 7 && x <= 92 && y >= 97 && y <= 104) ||
+ (x >= 25 && x <= 111 && y >= 85 && y <= 94) ||
+ (x >= 50 && x <= 119 && y >= 73 && y <= 83) ||
+ (x >= 80 && x <= 126 && y >= 60 && y <= 72) ||
+ (x >= 101 && x <= 129 && y >= 47 && y <= 59) ||
+ (x >= 34 && x <= 45 && y >= 77 && y <= 107) ||
+ (x >= 60 && x <= 71 && y >= 63 && y <= 98) ||
+ (x >= 90 && x <= 101 && y >= 49 && y <= 88) ||
+ (x >= 104 && x <= 115 && y >= 32 && y <= 74) ||
+ (x >= 44 && x <= 90 && y >= 89 && y <= 95)
+ ) {
+ return 'road';
+ }
+ if (
+ (x >= 0 && x <= 42 && y >= 68 && y <= 101) ||
+ (x >= 45 && x <= 88 && y >= 82 && y <= 103) ||
+ (x >= 94 && x <= 129 && y >= 76 && y <= 102) ||
+ (x >= 16 && x <= 47 && y >= 52 && y <= 72) ||
+ (x >= 104 && x <= 129 && y >= 60 && y <= 80) ||
+ (x >= 120 && x <= 129 && y >= 34 && y <= 56)
+ ) {
+ return 'forest';
+ }
+ if (
+ (x >= 0 && x <= 56 && y <= 25) ||
+ (x >= 48 && x <= 100 && y <= 26) ||
+ (x >= 92 && x <= 129 && y <= 32) ||
+ (x >= 102 && x <= 129 && y >= 53 && y <= 80)
+ ) {
+ return 'hill';
+ }
+ if (
+ (x <= 2 && y <= 103) ||
+ (x >= 127 && y <= 96) ||
+ (x >= 42 && x <= 49 && y >= 0 && y <= 73) ||
+ (x >= 82 && x <= 89 && y >= 0 && y <= 56) ||
+ (x >= 66 && x <= 75 && y >= 92 && y <= 111)
+ ) {
+ return 'cliff';
+ }
+ if (
+ (x >= 14 && x <= 35 && y >= 93 && y <= 104) ||
+ (x >= 46 && x <= 70 && y >= 80 && y <= 92) ||
+ (x >= 86 && x <= 108 && y >= 66 && y <= 78) ||
+ (x >= 108 && x <= 127 && y >= 50 && y <= 64) ||
+ (x >= 115 && x <= 129 && y >= 34 && y <= 44)
+ ) {
+ return 'village';
+ }
+ if (
+ (x >= 23 && x <= 54 && y >= 87 && y <= 101) ||
+ (x >= 58 && x <= 88 && y >= 74 && y <= 88) ||
+ (x >= 94 && x <= 121 && y >= 60 && y <= 74) ||
+ (x >= 103 && x <= 124 && y >= 32 && y <= 48)
+ ) {
+ return 'camp';
+ }
+ if (
+ (x >= 52 && x <= 76 && y >= 54 && y <= 70) ||
+ (x >= 77 && x <= 106 && y >= 44 && y <= 60) ||
+ (x >= 99 && x <= 126 && y >= 53 && y <= 69) ||
+ (x >= 108 && x <= 129 && y >= 18 && y <= 43) ||
+ (x >= 114 && x <= 129 && y >= 72 && y <= 90) ||
+ (x >= 118 && x <= 129 && y >= 5 && y <= 18)
+ ) {
+ 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 666bd3e..4688e09 100644
--- a/src/game/data/storyAssets.ts
+++ b/src/game/data/storyAssets.ts
@@ -21,6 +21,7 @@ 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';
import storyWeishuiCampsUrl from '../../assets/images/story/23-weishui-camps.png';
+import storyWeishuiNorthbankUrl from '../../assets/images/story/24-weishui-northbank.png';
export const storyBackgroundAssets: Record = {
'story-chaos': storyChaosUrl,
@@ -46,5 +47,6 @@ export const storyBackgroundAssets: Record = {
'story-hanzhong-rain': storyHanzhongRainDefenseUrl,
'story-lucheng-pursuit': storyLuchengPursuitUrl,
'story-weishui-camps': storyWeishuiCampsUrl,
+ 'story-weishui-northbank': storyWeishuiNorthbankUrl,
'story-resolve': storyLiuBeiUrl
};
diff --git a/src/game/scenes/CampScene.ts b/src/game/scenes/CampScene.ts
index d0db9f2..13c4870 100644
--- a/src/game/scenes/CampScene.ts
+++ b/src/game/scenes/CampScene.ts
@@ -56,6 +56,7 @@ import {
northernFourthBattleBonds,
northernFirstBattleBonds,
northernNinthBattleBonds,
+ northernEleventhBattleBonds,
northernSecondBattleBonds,
northernSeventhBattleBonds,
northernSixthBattleBonds,
@@ -460,9 +461,10 @@ const campaignTimelineChapters: CampaignTimelineChapter[] = [
'sixty-first-battle-hanzhong-rain-defense',
'sixty-second-battle-qishan-renewed-offensive',
'sixty-third-battle-lucheng-pursuit',
- 'sixty-fourth-battle-weishui-camps'
+ 'sixty-fourth-battle-weishui-camps',
+ 'sixty-fifth-battle-weishui-northbank'
],
- nextHints: ['한중 창고 정비', '출전 무장 재편', '기산 출진로', '천수·가정 방면', '강유 합류', '진창 공성 재계산', '무도·음평 확보', '한중 우로 방어', '기산 재출정', '노성 추격', '위수 진영']
+ nextHints: ['한중 창고 정비', '출전 무장 재편', '기산 출진로', '천수·가정 방면', '강유 합류', '진창 공성 재계산', '무도·음평 확보', '한중 우로 방어', '기산 재출정', '노성 추격', '위수 진영', '위수 북안']
}
];
@@ -530,7 +532,8 @@ const campBattleIds = {
sixtyFirst: 'sixty-first-battle-hanzhong-rain-defense',
sixtySecond: 'sixty-second-battle-qishan-renewed-offensive',
sixtyThird: 'sixty-third-battle-lucheng-pursuit',
- sixtyFourth: 'sixty-fourth-battle-weishui-camps'
+ sixtyFourth: 'sixty-fourth-battle-weishui-camps',
+ sixtyFifth: 'sixty-fifth-battle-weishui-northbank'
} as const;
const defaultRequiredSortieUnitIds = ['liu-bei'];
@@ -1348,6 +1351,23 @@ const sortieRulesByBattleId: Partial