Files
core2026/tools/integration-tests/src/turn-differential/fullLifecycleFixture.ts
T
Hide_D c63a49bd07 fix: 커맨드 차등 생명주기와 로그 그래프를 보강
장수 55종과 수뇌 35종의 상태, 로그, 메시지, 예약 턴 비교를 닫습니다.

실제 시나리오 프로필과 대표 PostgreSQL 수명주기, 즉시 외교와 출병 회귀를 추가하고 발견된 Ref 로그 및 생성 장수 저장 차이를 교정합니다.
2026-08-23 21:48:29 +00:00

214 lines
7.2 KiB
TypeScript

import type { CanonicalTurnSnapshot, TurnSnapshotSelector } from './canonical.js';
import type { TurnCommandFixtureRequest } from './coreCommandTrace.js';
const asRecord = (value: unknown): Record<string, unknown> =>
typeof value === 'object' && value !== null && !Array.isArray(value) ? (value as Record<string, unknown>) : {};
const semanticTimestamp = (value: unknown): number => {
const raw = String(value);
const normalized = raw.includes('T') ? raw : `${raw.replace(' ', 'T').replace(/\.(\d{3})\d*$/, '.$1')}Z`;
return new Date(normalized).getTime();
};
const semanticTurnArgs = (value: unknown): unknown => (Array.isArray(value) && value.length === 0 ? {} : value);
export const fullLifecycleGeneralTurns = Array.from({ length: 30 }, (_, turnIndex) => ({
generalId: 1,
turnIndex,
action: turnIndex === 0 ? 'che_훈련' : '휴식',
args: {},
}));
export const fullLifecycleNationTurns = Array.from({ length: 12 }, (_, turnIndex) => ({
nationId: 1,
officerLevel: 12,
turnIndex,
action: turnIndex === 0 ? 'che_국호변경' : '휴식',
args: turnIndex === 0 ? { nationName: '수명주기국' } : {},
}));
export const fullLifecycleSnapshotSelector: TurnSnapshotSelector = {
generalIds: [1],
cityIds: [3],
nationIds: [1],
allGenerals: true,
allCities: true,
allNations: true,
allTroops: true,
includeRankMirrors: true,
logAfterId: 0,
messageAfterId: 0,
includeNationHistoryLogs: true,
includeGlobalHistoryLogs: true,
};
export const fullLifecycleTurnCommandRequest: TurnCommandFixtureRequest = {
kind: 'general',
actorGeneralId: 1,
action: 'che_훈련',
args: {},
includeLifecycle: true,
setup: {
isolateWorld: true,
world: {
startYear: 180,
year: 190,
month: 1,
hiddenSeed: 'turn-command-full-lifecycle-v1',
freezeClock: true,
},
nations: [
{
id: 1,
name: '아국',
color: '#777777',
capitalCityId: 3,
gold: 1_000_000,
rice: 1_000_000,
tech: 1_000,
level: 1,
typeCode: 'che_명가',
generalCount: 1,
meta: { can_국호변경: 1 },
},
],
cities: [
{
id: 3,
nationId: 1,
level: 5,
population: 100_000,
populationMax: 200_000,
agriculture: 1_000,
agricultureMax: 2_000,
commerce: 1_000,
commerceMax: 2_000,
security: 1_000,
securityMax: 2_000,
supplyState: 1,
frontState: 0,
defence: 1_000,
defenceMax: 2_000,
wall: 1_000,
wallMax: 2_000,
state: 0,
term: 0,
trust: 80,
trade: 100,
},
],
generals: [
{
id: 1,
name: '수명주기장수',
nationId: 1,
cityId: 3,
troopId: 0,
leadership: 90,
strength: 80,
intelligence: 70,
leadershipExp: 0,
strengthExp: 0,
intelExp: 0,
experience: 1_000,
dedication: 1_000,
expLevel: 0,
officerLevel: 12,
officerCityId: 3,
belong: 10,
permission: 'normal',
injury: 0,
age: 30,
gold: 100_000,
rice: 100_000,
crew: 1_000,
crewTypeId: 1_100,
train: 50,
atmos: 50,
killTurn: 24,
npcState: 0,
blockState: 0,
personality: 'None',
specialDomestic: 'None',
specialWar: 'None',
itemHorse: 'None',
itemWeapon: 'None',
itemBook: 'None',
itemExtra: 'None',
meta: {},
},
],
generalTurns: fullLifecycleGeneralTurns,
nationTurns: fullLifecycleNationTurns,
},
observe: fullLifecycleSnapshotSelector,
};
export const projectFullLifecycleSnapshotGraph = (snapshot: CanonicalTurnSnapshot): Record<string, unknown> => {
const general = snapshot.generals.find((entry) => entry.id === 1);
const nation = snapshot.nations.find((entry) => entry.id === 1);
return {
actor: general
? {
nationId: general.nationId,
cityId: general.cityId,
train: general.train,
atmos: general.atmos,
experience: general.experience,
dedication: general.dedication,
leadershipExp: general.leadershipExp,
expLevel: general.expLevel,
dedLevel: general.dedLevel,
killTurn: general.killTurn,
mySet: general.mySet,
turnTime: semanticTimestamp(general.turnTime),
lastTurn: general.lastTurn,
}
: null,
nation: nation
? {
name: nation.name,
gold: nation.gold,
rice: nation.rice,
canRename: asRecord(nation.meta).can_국호변경 ?? 0,
}
: null,
actorRankData: snapshot.rankData
.filter((row) => row.generalId === 1)
.sort((left, right) => String(left.type).localeCompare(String(right.type)))
.map((row) => ({
nationId: row.nationId,
type: row.type,
value: row.value,
})),
generalTurns: snapshot.generalTurns
.filter((turn) => turn.generalId === 1)
.sort((left, right) => Number(left.turnIndex) - Number(right.turnIndex))
.map((turn) => ({
turnIndex: turn.turnIndex,
action: turn.action,
args: semanticTurnArgs(turn.args),
})),
nationTurns: snapshot.nationTurns
.filter((turn) => turn.nationId === 1 && turn.officerLevel === 12)
.sort((left, right) => Number(left.turnIndex) - Number(right.turnIndex))
.map((turn) => ({
turnIndex: turn.turnIndex,
action: turn.action,
args: semanticTurnArgs(turn.args),
})),
};
};
export const addedFullLifecycleReferenceLogs = (
before: CanonicalTurnSnapshot,
after: CanonicalTurnSnapshot
): Array<Record<string, unknown>> =>
after.logs.filter((entry) => {
const scope = String(entry.scope).toLowerCase();
const category = String(entry.category).toLowerCase();
const usesWorldHistory = scope === 'nation' || (scope === 'system' && category === 'history');
const watermark = usesWorldHistory ? before.watermarks.historyLogId : before.watermarks.logId;
return Number(entry.id) > watermark;
});