fix(dynasty): normalize archived nation records

This commit is contained in:
2026-07-31 15:28:04 +00:00
parent 6e8f048876
commit 8a17412fcd
9 changed files with 266 additions and 56 deletions
+9 -26
View File
@@ -35,6 +35,7 @@ import { calculateNationBettingRewards } from '../betting/nationBettingSettlemen
import type { NationBettingCandidate, PendingNationBettingFinish, PendingNationBettingOpen } from './types.js';
import { buildPersistedRankRows } from './rankData.js';
import { persistUnificationFinalization } from './unificationPersistence.js';
import { buildOldNationArchiveData } from './oldNationArchive.js';
import { persistYearbookSnapshot } from './yearbookPersistence.js';
export interface DatabaseTurnHooks {
@@ -696,40 +697,22 @@ export const createDatabaseTurnHooks = async (
},
},
update: {
data: {
nation: snapshot.nation.id,
name: snapshot.nation.name,
color: snapshot.nation.color,
type: snapshot.nation.typeCode,
level: snapshot.nation.level,
gold: snapshot.nation.gold,
rice: snapshot.nation.rice,
power: snapshot.nation.power,
capitalCityId: snapshot.nation.capitalCityId,
generals: snapshot.generalIds,
data: buildOldNationArchiveData({
nation: snapshot.nation,
generalIds: snapshot.generalIds,
history: historyMap.get(snapshot.nation.id) ?? [],
meta: snapshot.nation.meta ?? {},
},
}) as InputJsonValue,
date: snapshot.removedAt,
},
create: {
serverId,
nation: snapshot.nation.id,
sourceId: 0,
data: {
nation: snapshot.nation.id,
name: snapshot.nation.name,
color: snapshot.nation.color,
type: snapshot.nation.typeCode,
level: snapshot.nation.level,
gold: snapshot.nation.gold,
rice: snapshot.nation.rice,
power: snapshot.nation.power,
capitalCityId: snapshot.nation.capitalCityId,
generals: snapshot.generalIds,
data: buildOldNationArchiveData({
nation: snapshot.nation,
generalIds: snapshot.generalIds,
history: historyMap.get(snapshot.nation.id) ?? [],
meta: snapshot.nation.meta ?? {},
},
}) as InputJsonValue,
date: snapshot.removedAt,
},
})
@@ -0,0 +1,38 @@
import { asRecord } from '@sammo-ts/common';
import type { Nation } from '@sammo-ts/logic';
const readNumber = (value: unknown): number => {
const parsed = typeof value === 'string' ? Number(value) : value;
return typeof parsed === 'number' && Number.isFinite(parsed) ? parsed : 0;
};
const readTextArray = (value: unknown): string[] =>
Array.isArray(value) ? value.filter((entry): entry is string => typeof entry === 'string') : [];
export const buildOldNationArchiveData = (options: {
nation: Nation;
generalIds: readonly number[];
history: readonly string[];
}): Record<string, unknown> => {
const { nation } = options;
const meta = asRecord(nation.meta);
const maxPower = asRecord(meta.max_power);
const aux = {
...asRecord(meta.aux),
...maxPower,
};
const maxCities = readTextArray(maxPower.maxCities);
return {
...nation,
nation: nation.id,
type: nation.typeCode,
tech: readNumber(meta.tech),
maxPower: readNumber(maxPower.maxPower),
maxCrew: readNumber(maxPower.maxCrew),
maxCities,
aux,
generals: [...options.generalIds],
history: [...options.history],
};
};
@@ -4,6 +4,7 @@ import { LogCategory, LogScope, sendMessage, type MessageDraft, type MessageReco
import type { InMemoryTurnWorld } from './inMemoryWorld.js';
import { ALL_MERGED_INHERITANCE_KEYS, computeActiveInheritancePoint } from './inheritancePointCalculation.js';
import { buildOldNationArchiveData } from './oldNationArchive.js';
import type { PendingUnificationAuctionCancellation, TurnGeneral } from './types.js';
const UNIFIER_POINT = 2000;
@@ -490,16 +491,13 @@ export const persistUnificationFinalization = async (
const totalMaxPop = cities.reduce((sum, city) => sum + city.populationMax, 0);
const winnerMeta = asRecord(winner.meta);
const winnerData = {
...winner,
tech: readInteger(winnerMeta.tech),
aux: {
...asRecord(winnerMeta.aux),
...asRecord(winnerMeta.max_power),
},
...buildOldNationArchiveData({
nation: winner,
generalIds: winnerGenerals.map((general) => general.id),
history: nationHistory,
}),
msg: String(asRecord(winnerMeta.nationNotice).msg ?? winnerMeta.msg ?? ''),
scout_msg: String(winnerMeta.scout_msg ?? ''),
generals: winnerGenerals.map((general) => general.id),
history: nationHistory,
generationKey: input.generationKey,
};
await transaction.oldNation.upsert({
@@ -0,0 +1,41 @@
import { describe, expect, it } from 'vitest';
import type { Nation } from '@sammo-ts/logic';
import { buildOldNationArchiveData } from '../src/turn/oldNationArchive.js';
describe('old nation archive data', () => {
it('writes one canonical public shape for winner and deleted-nation readers', () => {
const nation: Nation = {
id: 2,
name: '위',
color: '#0000ff',
capitalCityId: 3,
chiefGeneralId: 7,
gold: 1_000,
rice: 2_000,
power: 8_000,
level: 5,
typeCode: 'che_법가',
meta: {
tech: 3_000,
aux: { legacy: 'preserved' },
max_power: { maxPower: 20_000, maxCrew: 80_000, maxCities: ['허창'] },
},
};
expect(buildOldNationArchiveData({ nation, generalIds: [7, 8], history: ['위가 멸망'] })).toMatchObject({
nation: 2,
name: '위',
type: 'che_법가',
typeCode: 'che_법가',
tech: 3_000,
power: 8_000,
maxPower: 20_000,
maxCrew: 80_000,
maxCities: ['허창'],
aux: { legacy: 'preserved', maxPower: 20_000, maxCrew: 80_000, maxCities: ['허창'] },
generals: [7, 8],
history: ['위가 멸망'],
});
});
});
@@ -330,6 +330,26 @@ integration('unification finalization transaction', () => {
expect(await db.inheritanceResult.count({ where: { serverId } })).toBe(1);
expect(await db.oldGeneral.count({ where: { serverId } })).toBe(1);
expect(await db.oldNation.count({ where: { serverId } })).toBe(2);
expect(
(
await db.oldNation.findUniqueOrThrow({
where: {
serverId_nation_sourceId: { serverId, nation: fixtureId, sourceId: 0 },
},
})
).data
).toMatchObject({
nation: fixtureId,
name: '원자통일국',
type: 'che_중립',
typeCode: 'che_중립',
tech: 123,
maxPower: 3_500,
maxCrew: 400,
maxCities: ['원자도시'],
aux: { maxPower: 3_500, maxCrew: 400, maxCities: ['원자도시'] },
generals: [fixtureId],
});
expect(await db.emperor.count({ where: { serverId } })).toBe(1);
expect(
(await db.inheritancePoint.findUniqueOrThrow({ where: { userId_key: { userId, key: 'previous' } } }))