From e3b252143b513c31b283c90c9eb119cb9a1aa26b Mon Sep 17 00:00:00 2001 From: hided62 Date: Mon, 24 Aug 2026 01:57:15 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20=EC=8B=A0=EA=B7=9C=20=EC=9E=A5=EC=88=98?= =?UTF-8?q?=EC=9D=98=20=EB=B9=88=20=ED=8A=B9=EA=B8=B0=EB=A5=BC=20=EC=A6=89?= =?UTF-8?q?=EC=8B=9C=20=EC=A0=95=EA=B7=9C=ED=99=94=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../test/createGeneral.integration.test.ts | 8 ++++++++ .../src/turn/joinCreateGeneralService.ts | 19 +++++++++++++------ .../test/joinCreateGeneralService.test.ts | 8 ++++++++ 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/app/game-api/test/createGeneral.integration.test.ts b/app/game-api/test/createGeneral.integration.test.ts index 00464f1f..ad0c613f 100644 --- a/app/game-api/test/createGeneral.integration.test.ts +++ b/app/game-api/test/createGeneral.integration.test.ts @@ -299,6 +299,10 @@ integration('generic general creation through the durable turn daemon', () => { userId, name: created.name, cityId: city.id, + role: { + specialDomestic: null, + specialWar: null, + }, inheritancePoints: { previous: 7351, }, @@ -363,6 +367,10 @@ integration('generic general creation through the durable turn daemon', () => { userId, name: created.name, cityId: city.id, + role: { + specialDomestic: null, + specialWar: null, + }, inheritancePoints: { previous: 7351, }, diff --git a/app/game-engine/src/turn/joinCreateGeneralService.ts b/app/game-engine/src/turn/joinCreateGeneralService.ts index f3898a9f..99c8697d 100644 --- a/app/game-engine/src/turn/joinCreateGeneralService.ts +++ b/app/game-engine/src/turn/joinCreateGeneralService.ts @@ -90,6 +90,9 @@ const fail = (code: JoinCreateGeneralErrorCode, message: string): never => { const normalizeJoinName = (value: string): string => normalizeTroopName(value).replace(LEGACY_JOIN_REMOVED_CHARACTERS, ''); +export const normalizeJoinSpecialityCode = (value: unknown): string | null => + typeof value === 'string' && value !== '' && value !== 'None' ? value : null; + export const resolveLegacyPenalty = ( rawPenalty: Record | undefined, profileId: string, @@ -658,8 +661,8 @@ export const createGeneralFromJoin = async (options: { const scenarioId = Number(worldMeta.scenarioId ?? worldState.scenarioCode); let specialityDomesticAge = resolveSpecialityAge(retirementYear, age, relativeYear, 12); let specialityWarAge = resolveSpecialityAge(retirementYear, age, relativeYear, 6); - let specialWar = typeof configConst.defaultSpecialWar === 'string' ? configConst.defaultSpecialWar : 'None'; - let specialWarName = specialWar; + let specialWar = normalizeJoinSpecialityCode(configConst.defaultSpecialWar); + let specialWarName = specialWar ?? 'None'; if (genius) { specialityWarAge = age; if (input.inheritSpecial) { @@ -677,10 +680,10 @@ export const createGeneralFromJoin = async (options: { ) ?? specialWar; } const [trait] = await loadWarTraitModules( - [specialWar].filter((key) => isWarTraitKey(key)), + specialWar && isWarTraitKey(specialWar) ? [specialWar] : [], new WarTraitLoader() ); - specialWarName = trait?.name ?? specialWar; + specialWarName = trait?.name ?? specialWar ?? 'None'; } if (Number.isFinite(scenarioId) && scenarioId >= 1000) { specialityDomesticAge = age + 3; @@ -762,8 +765,12 @@ export const createGeneralFromJoin = async (options: { startAge: age, role: { personality, - specialDomestic: - typeof configConst.defaultSpecialDomestic === 'string' ? configConst.defaultSpecialDomestic : 'None', + // Ref persists an empty speciality as the sentinel `None`, while + // the Core in-memory domain uses null. Keeping the sentinel here + // makes a newly joined general differ from the same row after a + // daemon reload and prevents the monthly speciality handler from + // recognizing the empty slot until that reload. + specialDomestic: normalizeJoinSpecialityCode(configConst.defaultSpecialDomestic), specialWar, items: { horse: null, diff --git a/app/game-engine/test/joinCreateGeneralService.test.ts b/app/game-engine/test/joinCreateGeneralService.test.ts index 18d4ff3e..c5997376 100644 --- a/app/game-engine/test/joinCreateGeneralService.test.ts +++ b/app/game-engine/test/joinCreateGeneralService.test.ts @@ -4,6 +4,7 @@ import { buildJoinCreateGeneralSeed, cutJoinTurnTime, JOIN_WELCOME_MESSAGE, + normalizeJoinSpecialityCode, resolveJoinTurnTime, } from '../src/turn/joinCreateGeneralService.js'; @@ -53,4 +54,11 @@ describe('generic join legacy time contracts', () => { expect(JOIN_WELCOME_MESSAGE).toBe('삼국지 모의전투 HiDCHe의 세계에 오신 것을 환영합니다 ^o^'); expect(JOIN_WELCOME_MESSAGE).not.toContain('PHP'); }); + + it('normalizes the Ref empty-speciality sentinel at the join boundary', () => { + expect(normalizeJoinSpecialityCode(undefined)).toBeNull(); + expect(normalizeJoinSpecialityCode('')).toBeNull(); + expect(normalizeJoinSpecialityCode('None')).toBeNull(); + expect(normalizeJoinSpecialityCode('che_견고')).toBe('che_견고'); + }); });