From f43a1db136af48c9c2ca05dedeb77c715588777c Mon Sep 17 00:00:00 2001 From: hided62 Date: Fri, 28 Aug 2026 06:20:13 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20=EC=9E=90=EB=8F=99=20=EC=B0=B8=EB=AA=A8?= =?UTF-8?q?=20=EA=B5=90=EC=B2=B4=20=EC=8B=9C=20=EA=B8=B0=EC=A1=B4=20?= =?UTF-8?q?=EC=B0=B8=EB=AA=A8=EB=A5=BC=20=ED=95=B4=EC=9E=84=ED=95=9C?= =?UTF-8?q?=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/game-engine/src/turn/ai/generalAi/core.ts | 7 +++ .../generalAiLegacyDecisionParity.test.ts | 47 +++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/app/game-engine/src/turn/ai/generalAi/core.ts b/app/game-engine/src/turn/ai/generalAi/core.ts index 78fb59ed..8f99f495 100644 --- a/app/game-engine/src/turn/ai/generalAi/core.ts +++ b/app/game-engine/src/turn/ai/generalAi/core.ts @@ -1198,6 +1198,13 @@ export class GeneralAI { ) { continue; } + const oldChief = this.chiefGenerals[11]; + if (oldChief && oldChief.id !== candidate.id) { + oldChief.officerLevel = 1; + oldChief.meta = { ...oldChief.meta, officer_city: 0 }; + this.promotionPatches.push({ generalId: oldChief.id, officerLevel: 1, officerCity: 0 }); + effectiveOfficerLevel.set(oldChief.id, 1); + } const permission = penalty.noAmbassador === true ? undefined : 'ambassador'; candidate.officerLevel = 11; candidate.meta = { diff --git a/app/game-engine/test/generalAiLegacyDecisionParity.test.ts b/app/game-engine/test/generalAiLegacyDecisionParity.test.ts index 3a182c01..f9e3ccf3 100644 --- a/app/game-engine/test/generalAiLegacyDecisionParity.test.ts +++ b/app/game-engine/test/generalAiLegacyDecisionParity.test.ts @@ -568,6 +568,53 @@ describe('legacy NPC user-chief promotion parity', () => { }); }); + it.each([ + { label: 'inactive user', npcState: 0, killturn: 70, permission: 'auditor' }, + { label: 'NPC', npcState: 3, killturn: 100, permission: undefined }, + ])('demotes the existing $label advisor before forcing an active user into the seat', (oldChiefInput) => { + const ruler = makePromotionGeneral({ + id: 1, + officerLevel: 12, + npcState: 0, + meta: { killturn: 100, belong: 2, use_auto_nation_promotion: 1 }, + }); + const oldChief = makePromotionGeneral({ + id: 2, + name: '기존참모', + officerLevel: 11, + npcState: oldChiefInput.npcState, + meta: { + killturn: oldChiefInput.killturn, + belong: 2, + ...(oldChiefInput.permission ? { permission: oldChiefInput.permission } : {}), + }, + }); + const candidate = makePromotionGeneral({ + id: 3, + name: '신규참모', + npcState: 0, + stats: { leadership: 80, strength: 40, intelligence: 40 }, + meta: { killturn: 100, belong: 1 }, + }); + const ai = makePromotionAi({ + ruler, + generals: [ruler, oldChief, candidate], + userGenerals: oldChief.npcState < 2 ? [oldChief, candidate] : [candidate], + chiefGenerals: [ruler, oldChief], + }); + + chooseNpcPromotion(ai); + + expect(ai.consumePromotionPatches()).toEqual({ + generals: [ + { generalId: oldChief.id, officerLevel: 1, officerCity: 0 }, + { generalId: candidate.id, officerLevel: 11, officerCity: 0, permission: 'ambassador' }, + ], + nationMeta: expect.objectContaining({ chief_set: 1 << 11 }), + }); + expect(oldChief).toMatchObject({ officerLevel: 1, meta: expect.objectContaining({ officer_city: 0 }) }); + }); + it('waits for belong 3 before forcing a user over a stronger NPC under an established NPC ruler', () => { const run = (belong: number) => { const ruler = makePromotionGeneral({