From 0cc50bcd668fe56fd378976efe5149e2935c214d Mon Sep 17 00:00:00 2001 From: hided62 Date: Sat, 22 Aug 2026 04:51:06 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20=EC=B2=AB=20=EB=AA=A8=EB=B3=91=20?= =?UTF-8?q?=EC=98=88=EC=95=BD=EB=8F=84=20=ED=9B=84=EB=B0=A9=20=EB=B0=9C?= =?UTF-8?q?=EB=A0=B9=EC=97=90=20=EB=B0=98=EC=98=81=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ref의 che_모병 extends che_징병 상속 의미를 예약턴 문자열 경계에서 복원하고 두 명령을 함께 회귀한다. --- app/game-engine/src/turn/ai/generalAi/core.ts | 8 ++++++++ .../generalAi/nation/assignments/userAssignments.ts | 3 +-- .../test/generalAiLegacyDecisionParity.test.ts | 12 +++++++----- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/app/game-engine/src/turn/ai/generalAi/core.ts b/app/game-engine/src/turn/ai/generalAi/core.ts index 0d1968c9..9c30d58c 100644 --- a/app/game-engine/src/turn/ai/generalAi/core.ts +++ b/app/game-engine/src/turn/ai/generalAi/core.ts @@ -677,6 +677,14 @@ export class GeneralAI { return this.reservedTurnProvider.getGeneralTurn(generalId, 0); } + hasFirstReservedGeneralRecruitmentTurn(generalId: number): boolean { + const action = this.getFirstReservedGeneralTurn(generalId).action; + // Ref checks `instanceof che_징병`; che_모병 inherits che_징병 there. + // Core persists the concrete command key, so preserve that subtype + // relationship explicitly at the serialized reserved-turn boundary. + return action === 'che_징병' || action === 'che_모병'; + } + resolveGeneralAiStats(general: TurnGeneral): ReturnType { if (this.commandEnv.generalActionModules) { return resolveLegacyAiStatsWithModules( diff --git a/app/game-engine/src/turn/ai/generalAi/nation/assignments/userAssignments.ts b/app/game-engine/src/turn/ai/generalAi/nation/assignments/userAssignments.ts index ad4e1b01..dd2827ee 100644 --- a/app/game-engine/src/turn/ai/generalAi/nation/assignments/userAssignments.ts +++ b/app/game-engine/src/turn/ai/generalAi/nation/assignments/userAssignments.ts @@ -55,8 +55,7 @@ export const do부대유저장후방발령 = (ai: GeneralAI) => { if (!isGeneralTurnBefore(general, troopLeader)) { return false; } - const reserved = ai.getFirstReservedGeneralTurn(general.id); - if (reserved.action !== 'che_징병') { + if (!ai.hasFirstReservedGeneralRecruitmentTurn(general.id)) { return false; } return true; diff --git a/app/game-engine/test/generalAiLegacyDecisionParity.test.ts b/app/game-engine/test/generalAiLegacyDecisionParity.test.ts index 8f9ed7e8..0a017430 100644 --- a/app/game-engine/test/generalAiLegacyDecisionParity.test.ts +++ b/app/game-engine/test/generalAiLegacyDecisionParity.test.ts @@ -1435,11 +1435,13 @@ describe('legacy NPC AI final-decision parity', () => { return { result: do부대유저장후방발령(ai), rng }; }; - expect(run({ reservedAction: 'che_징병', userTurnTick: 100, leaderTurnTick: 200 }).result).toMatchObject({ - action: 'che_발령', - args: { destGeneralId: 2, destCityId: 3 }, - }); - expect(run({ reservedAction: 'che_모병', userTurnTick: 100, leaderTurnTick: 200 }).result).toBeNull(); + for (const reservedAction of ['che_징병', 'che_모병']) { + expect(run({ reservedAction, userTurnTick: 100, leaderTurnTick: 200 }).result).toMatchObject({ + action: 'che_발령', + args: { destGeneralId: 2, destCityId: 3 }, + }); + } + expect(run({ reservedAction: 'che_훈련', userTurnTick: 100, leaderTurnTick: 200 }).result).toBeNull(); expect(run({ reservedAction: 'che_징병', userTurnTick: 200, leaderTurnTick: 100 }).result).toBeNull(); expect( run({ reservedAction: 'che_징병', userTurnTick: 100, leaderTurnTick: 200, recruitmentScore: 0 }).result