merge: 신규 장수 특기 지급 지연 수정을 main에 반영한다

This commit is contained in:
2026-08-24 01:58:58 +00:00
3 changed files with 29 additions and 6 deletions
@@ -299,6 +299,10 @@ integration('generic general creation through the durable turn daemon', () => {
userId, userId,
name: created.name, name: created.name,
cityId: city.id, cityId: city.id,
role: {
specialDomestic: null,
specialWar: null,
},
inheritancePoints: { inheritancePoints: {
previous: 7351, previous: 7351,
}, },
@@ -363,6 +367,10 @@ integration('generic general creation through the durable turn daemon', () => {
userId, userId,
name: created.name, name: created.name,
cityId: city.id, cityId: city.id,
role: {
specialDomestic: null,
specialWar: null,
},
inheritancePoints: { inheritancePoints: {
previous: 7351, previous: 7351,
}, },
@@ -90,6 +90,9 @@ const fail = (code: JoinCreateGeneralErrorCode, message: string): never => {
const normalizeJoinName = (value: string): string => const normalizeJoinName = (value: string): string =>
normalizeTroopName(value).replace(LEGACY_JOIN_REMOVED_CHARACTERS, ''); 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 = ( export const resolveLegacyPenalty = (
rawPenalty: Record<string, unknown> | undefined, rawPenalty: Record<string, unknown> | undefined,
profileId: string, profileId: string,
@@ -658,8 +661,8 @@ export const createGeneralFromJoin = async (options: {
const scenarioId = Number(worldMeta.scenarioId ?? worldState.scenarioCode); const scenarioId = Number(worldMeta.scenarioId ?? worldState.scenarioCode);
let specialityDomesticAge = resolveSpecialityAge(retirementYear, age, relativeYear, 12); let specialityDomesticAge = resolveSpecialityAge(retirementYear, age, relativeYear, 12);
let specialityWarAge = resolveSpecialityAge(retirementYear, age, relativeYear, 6); let specialityWarAge = resolveSpecialityAge(retirementYear, age, relativeYear, 6);
let specialWar = typeof configConst.defaultSpecialWar === 'string' ? configConst.defaultSpecialWar : 'None'; let specialWar = normalizeJoinSpecialityCode(configConst.defaultSpecialWar);
let specialWarName = specialWar; let specialWarName = specialWar ?? 'None';
if (genius) { if (genius) {
specialityWarAge = age; specialityWarAge = age;
if (input.inheritSpecial) { if (input.inheritSpecial) {
@@ -677,10 +680,10 @@ export const createGeneralFromJoin = async (options: {
) ?? specialWar; ) ?? specialWar;
} }
const [trait] = await loadWarTraitModules( const [trait] = await loadWarTraitModules(
[specialWar].filter((key) => isWarTraitKey(key)), specialWar && isWarTraitKey(specialWar) ? [specialWar] : [],
new WarTraitLoader() new WarTraitLoader()
); );
specialWarName = trait?.name ?? specialWar; specialWarName = trait?.name ?? specialWar ?? 'None';
} }
if (Number.isFinite(scenarioId) && scenarioId >= 1000) { if (Number.isFinite(scenarioId) && scenarioId >= 1000) {
specialityDomesticAge = age + 3; specialityDomesticAge = age + 3;
@@ -762,8 +765,12 @@ export const createGeneralFromJoin = async (options: {
startAge: age, startAge: age,
role: { role: {
personality, personality,
specialDomestic: // Ref persists an empty speciality as the sentinel `None`, while
typeof configConst.defaultSpecialDomestic === 'string' ? configConst.defaultSpecialDomestic : 'None', // 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, specialWar,
items: { items: {
horse: null, horse: null,
@@ -4,6 +4,7 @@ import {
buildJoinCreateGeneralSeed, buildJoinCreateGeneralSeed,
cutJoinTurnTime, cutJoinTurnTime,
JOIN_WELCOME_MESSAGE, JOIN_WELCOME_MESSAGE,
normalizeJoinSpecialityCode,
resolveJoinTurnTime, resolveJoinTurnTime,
} from '../src/turn/joinCreateGeneralService.js'; } 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).toBe('삼국지 모의전투 HiDCHe의 세계에 오신 것을 환영합니다 ^o^');
expect(JOIN_WELCOME_MESSAGE).not.toContain('PHP'); 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_견고');
});
}); });