test live sortie full log parity

This commit is contained in:
2026-07-26 13:13:15 +00:00
parent 6dec59430a
commit 6d642bb759
4 changed files with 50 additions and 7 deletions
@@ -7,6 +7,7 @@ export interface TurnSnapshotSelector {
logAfterId?: number; logAfterId?: number;
messageAfterId?: number; messageAfterId?: number;
includeNationHistoryLogs?: boolean; includeNationHistoryLogs?: boolean;
includeGlobalHistoryLogs?: boolean;
} }
export interface CanonicalTurnSnapshot { export interface CanonicalTurnSnapshot {
@@ -24,6 +25,7 @@ export interface CanonicalTurnSnapshot {
messages: Array<Record<string, unknown>>; messages: Array<Record<string, unknown>>;
watermarks: { watermarks: {
logId: number; logId: number;
historyLogId: number;
messageId: number; messageId: number;
}; };
} }
@@ -259,6 +261,7 @@ export const projectCoreDatabaseSnapshot = (rows: {
messages: [], messages: [],
watermarks: { watermarks: {
logId: logs.reduce((max, row) => Math.max(max, Number(row.id) || 0), 0), logId: logs.reduce((max, row) => Math.max(max, Number(row.id) || 0), 0),
historyLogId: logs.reduce((max, row) => Math.max(max, Number(row.id) || 0), 0),
messageId: 0, messageId: 0,
}, },
}; };
@@ -68,6 +68,7 @@ export interface TurnCommandFixtureRequest {
logAfterId?: number; logAfterId?: number;
messageAfterId?: number; messageAfterId?: number;
includeNationHistoryLogs?: boolean; includeNationHistoryLogs?: boolean;
includeGlobalHistoryLogs?: boolean;
generalCooldowns?: GeneralCooldownSelector[]; generalCooldowns?: GeneralCooldownSelector[];
nationCooldowns?: NationCooldownSelector[]; nationCooldowns?: NationCooldownSelector[];
diplomacyPairs?: Array<{ fromNationId: number; toNationId: number }>; diplomacyPairs?: Array<{ fromNationId: number; toNationId: number }>;
@@ -469,7 +470,7 @@ const buildWorldInput = (
lastTurnTime: turnTime, lastTurnTime: turnTime,
meta: { meta: {
hiddenSeed: request.setup?.world?.hiddenSeed ?? 'turn-command-differential-seed', hiddenSeed: request.setup?.world?.hiddenSeed ?? 'turn-command-differential-seed',
killturn: 24, killturn: readNumber(referenceBefore.world, 'killTurn', 24),
isUnited: readNumber(referenceBefore.world, 'isUnited'), isUnited: readNumber(referenceBefore.world, 'isUnited'),
scenarioId: readNumber(referenceBefore.world, 'scenarioId'), scenarioId: readNumber(referenceBefore.world, 'scenarioId'),
initYear: readNumber( initYear: readNumber(
@@ -661,7 +662,7 @@ const projectWorld = (
), ),
logs, logs,
messages, messages,
watermarks: { logId: logs.length, messageId: messages.length }, watermarks: { logId: logs.length, historyLogId: logs.length, messageId: messages.length },
}; };
}; };
@@ -785,8 +786,8 @@ export const runCoreTurnCommandTrace = async (
id: index + 1, id: index + 1,
scope: log.scope, scope: log.scope,
category: log.category, category: log.category,
generalId: log.generalId ?? actor.id, generalId: log.generalId ?? (log.scope === 'GENERAL' ? actor.id : undefined),
nationId: log.nationId ?? actor.nationId, nationId: log.nationId ?? (log.scope === 'NATION' ? actor.nationId : undefined),
year: state.currentYear, year: state.currentYear,
month: state.currentMonth, month: state.currentMonth,
text: log.text, text: log.text,
@@ -20,11 +20,30 @@ const ignoredLifecyclePaths = [
/^logs/, /^logs/,
/^messages/, /^messages/,
/^world\.turnTime$/, /^world\.turnTime$/,
/^generals\[[^\]]+\]\.(?:turnTime|recentWarTime|lastTurn|killTurn|mySet)(?:\.|$)/, /^generals\[[^\]]+\]\.(?:turnTime|recentWarTime|lastTurn|mySet)(?:\.|$)/,
/^generals\[[^\]]+\]\.meta(?:\.|$)/, /^generals\[[^\]]+\]\.meta(?:\.|$)/,
/^nations\[[^\]]+\]\.meta(?:\.|$)/, /^nations\[[^\]]+\]\.meta(?:\.|$)/,
]; ];
const normalizeStoredLogText = (value: unknown): string =>
String(value)
.replace(/^(?:<C>●<\/>|<S>◆<\/>|<R>★<\/>)(?:(?:\d+년 )?\d+월:|\d+년:)?/, '')
.replace(/<span class='hidden_but_copyable'>(.*?)<\/span>/g, '$1')
.replace(/ <1>\d{2}:\d{2}<\/>$/, '');
const semanticLogSignatures = (logs: Array<Record<string, unknown>>): string[] =>
logs
.map((entry) =>
JSON.stringify({
scope: String(entry.scope).toLowerCase(),
category: String(entry.category).toLowerCase(),
generalId: Number(entry.generalId) || null,
nationId: Number(entry.nationId) || null,
text: normalizeStoredLogText(entry.text),
})
)
.sort();
const readFixture = (relativePath: string): TurnCommandFixtureRequest => { const readFixture = (relativePath: string): TurnCommandFixtureRequest => {
const stackRoot = path.join(workspaceRoot!, 'docker_compose_files/reference'); const stackRoot = path.join(workspaceRoot!, 'docker_compose_files/reference');
const fixture = JSON.parse( const fixture = JSON.parse(
@@ -34,6 +53,7 @@ const readFixture = (relativePath: string): TurnCommandFixtureRequest => {
...fixture, ...fixture,
setup: { setup: {
...fixture.setup, ...fixture.setup,
isolateWorld: true,
world: { world: {
...fixture.setup?.world, ...fixture.setup?.world,
hiddenSeed: 'turn-command-differential-seed', hiddenSeed: 'turn-command-differential-seed',
@@ -60,6 +80,10 @@ integration('core ↔ legacy command-boundary differential', () => {
'%s matches command RNG and canonical state delta', '%s matches command RNG and canonical state delta',
async (_label, fixturePath) => { async (_label, fixturePath) => {
const request = readFixture(fixturePath); const request = readFixture(fixturePath);
if (request.action === 'che_출병') {
request.observe!.includeNationHistoryLogs = true;
request.observe!.includeGlobalHistoryLogs = true;
}
const reference = runReferenceTurnCommandTraceRequest( const reference = runReferenceTurnCommandTraceRequest(
workspaceRoot!, workspaceRoot!,
request as unknown as Record<string, unknown> request as unknown as Record<string, unknown>
@@ -73,9 +97,24 @@ integration('core ↔ legacy command-boundary differential', () => {
}); });
expect(reference.execution.outcome).toMatchObject({ completed: true }); expect(reference.execution.outcome).toMatchObject({ completed: true });
expect(core.rng).toEqual(reference.rng); expect(core.rng).toEqual(reference.rng);
if (request.action === 'che_출병') {
const generalLogWatermark = reference.before.watermarks.logId;
const historyLogWatermark = reference.before.watermarks.historyLogId;
const referenceAddedLogs = reference.after.logs.filter((entry) =>
String(entry.scope).toLowerCase() === 'nation' ||
(String(entry.scope).toLowerCase() === 'system' &&
String(entry.category).toLowerCase() === 'history')
? (Number(entry.id) || 0) > historyLogWatermark
: (Number(entry.id) || 0) > generalLogWatermark
);
expect(semanticLogSignatures(core.after.logs)).toEqual(semanticLogSignatures(referenceAddedLogs));
}
expect( expect(
compareTurnSnapshotDeltas(reference.before, reference.after, core.before, core.after, { compareTurnSnapshotDeltas(reference.before, reference.after, core.before, core.after, {
ignoredPathPatterns: ignoredLifecyclePaths, ignoredPathPatterns:
request.action === 'che_출병'
? ignoredLifecyclePaths
: [...ignoredLifecyclePaths, /^generals\[[^\]]+\]\.killTurn(?:\.|$)/],
}) })
).toEqual([]); ).toEqual([]);
}, },
@@ -23,7 +23,7 @@ const snapshot = (
nationTurns: [], nationTurns: [],
logs: [], logs: [],
messages: [], messages: [],
watermarks: { logId: 0, messageId: 0 }, watermarks: { logId: 0, historyLogId: 0, messageId: 0 },
...overrides, ...overrides,
}); });