From 544168a76f894defdf2f92f26576ea8d2e7bcc52 Mon Sep 17 00:00:00 2001 From: hided62 Date: Wed, 5 Aug 2026 02:39:04 +0000 Subject: [PATCH] fix: preserve NPC name prefixes in scenario bootstrap --- packages/logic/src/world/bootstrap.ts | 21 +++++++++++-- packages/logic/test/worldBootstrap.test.ts | 35 ++++++++++++++++++++-- 2 files changed, 51 insertions(+), 5 deletions(-) diff --git a/packages/logic/src/world/bootstrap.ts b/packages/logic/src/world/bootstrap.ts index d469546..5b74996 100644 --- a/packages/logic/src/world/bootstrap.ts +++ b/packages/logic/src/world/bootstrap.ts @@ -97,6 +97,22 @@ const DEFAULT_CITY_TRADE = 100; const DEFAULT_CITY_SUPPLY_STATE = 1; const DEFAULT_CITY_FRONT_STATE = 0; +// Ref Scenario\GeneralBuilder::build() decorates the persisted name after +// setNPCType(); scenario JSON keeps the undecorated source name. +const LEGACY_GENERAL_NAME_PREFIX_BY_NPC_TYPE: Readonly> = { + 0: '', + 1: 'ⓝ', + 2: 'ⓝ', + 3: 'ⓜ', + 4: 'ⓖ', + 5: '㉥', + 6: 'ⓤ', + 9: 'ⓞ', +}; + +const decorateLegacyGeneralName = (name: string, npcType: number): string => + `${LEGACY_GENERAL_NAME_PREFIX_BY_NPC_TYPE[npcType] ?? 'ⓧ'}${name}`; + const canonicalizeDomesticTrait = (raw: string | null | undefined): string | null => { if (!raw || raw === 'None') { return null; @@ -484,6 +500,7 @@ const buildGeneralSeeds = ( for (const row of rows) { const id = nextId; nextId += 1; + const name = decorateLegacyGeneralName(row.name, npcType); const nationId = resolveNationId(row.nation, nationNameToId, warnings, row.name); let cityId = resolveCityId(row.city, cityByName, warnings, row.name); @@ -555,7 +572,7 @@ const buildGeneralSeeds = ( const seed: GeneralSeed = { id, - name: row.name, + name, nationId, cityId, stats, @@ -610,7 +627,7 @@ const buildGeneralSeeds = ( generals.push({ id, - name: row.name, + name, nationId, cityId, troopId: 0, diff --git a/packages/logic/test/worldBootstrap.test.ts b/packages/logic/test/worldBootstrap.test.ts index 3641c8a..e803f7e 100644 --- a/packages/logic/test/worldBootstrap.test.ts +++ b/packages/logic/test/worldBootstrap.test.ts @@ -106,7 +106,24 @@ describe('scenario bootstrap', () => { }, ], generalsEx: [], - generalsNeutral: [], + generalsNeutral: [ + { + affinity: 13, + name: 'NeutralGeneral', + picture: null, + nation: null, + city: 'Alpha', + leadership: 50, + strength: 50, + intelligence: 50, + officerLevel: 0, + birthYear: 180, + deathYear: 240, + personality: 'Calm', + special: '', + text: null, + }, + ], cities: [], events: [], initialEvents: [], @@ -159,6 +176,18 @@ describe('scenario bootstrap', () => { expect(result.seed.cities[0]?.nationId).toBe(1); expect(result.snapshot.generals[0]?.cityId).toBe(1); expect(result.snapshot.generals[0]?.crewTypeId).toBe(1200); + expect(result.seed.generals.map((general) => general.name)).toEqual([ + 'ⓝTestGeneral', + 'ⓝMedicalGeneral', + 'ⓝChargeGeneral', + 'ⓤNeutralGeneral', + ]); + expect(result.snapshot.generals.map((general) => general.name)).toEqual([ + 'ⓝTestGeneral', + 'ⓝMedicalGeneral', + 'ⓝChargeGeneral', + 'ⓤNeutralGeneral', + ]); expect(result.snapshot.generals[0]?.role.specialDomestic).toBe('Special'); expect(result.snapshot.generals[0]?.role.specialWar).toBeNull(); expect(result.snapshot.generals[1]?.role).toMatchObject({ @@ -442,8 +471,8 @@ describe('scenario bootstrap', () => { const result = buildScenarioBootstrap({ scenario, map }); - expect(result.seed.generals.map((row) => row.name)).toEqual(['현재']); - expect(result.snapshot.generals.map((row) => row.name)).toEqual(['현재']); + expect(result.seed.generals.map((row) => row.name)).toEqual(['ⓝ현재']); + expect(result.snapshot.generals.map((row) => row.name)).toEqual(['ⓝ현재']); expect(result.seed.events).toEqual([ ['Month', 500, ['Date', '>=', 200, 1], ['Existing']], [