fix: 커맨드 차등 생명주기와 로그 그래프를 보강

장수 55종과 수뇌 35종의 상태, 로그, 메시지, 예약 턴 비교를 닫습니다.

실제 시나리오 프로필과 대표 PostgreSQL 수명주기, 즉시 외교와 출병 회귀를 추가하고 발견된 Ref 로그 및 생성 장수 저장 차이를 교정합니다.
This commit is contained in:
2026-08-23 21:48:29 +00:00
parent 85591c68ad
commit c63a49bd07
128 changed files with 8615 additions and 848 deletions
@@ -0,0 +1,124 @@
import { describe, expect, it } from 'vitest';
import { orderedSemanticLogStreams } from '../src/turn-differential/logProjection.js';
const actionLog = (id: number, text: string, overrides: Record<string, unknown> = {}): Record<string, unknown> => ({
id,
scope: 'general',
category: 'action',
generalId: 1,
nationId: null,
year: 190,
month: 1,
format: 4,
text,
...overrides,
});
const historyLog = (id: number, text: string, overrides: Record<string, unknown> = {}): Record<string, unknown> => ({
id,
scope: 'nation',
category: 'history',
generalId: null,
nationId: 1,
year: 190,
month: 1,
format: 2,
text,
...overrides,
});
const generalHistoryLog = (
id: number,
text: string,
overrides: Record<string, unknown> = {}
): Record<string, unknown> => ({
id,
scope: 'general',
category: 'history',
generalId: 1,
nationId: null,
year: 190,
month: 1,
format: 2,
text,
...overrides,
});
describe('orderedSemanticLogStreams', () => {
it('preserves observable ordering inside general_record', () => {
const expected = orderedSemanticLogStreams([actionLog(1, '명령'), actionLog(2, '능력 상승')]);
const reversed = orderedSemanticLogStreams([actionLog(2, '명령'), actionLog(1, '능력 상승')]);
expect(reversed).not.toEqual(expected);
});
it('keeps general history in general_record so an action/history order mutant fails', () => {
const expected = orderedSemanticLogStreams([generalHistoryLog(1, '장수 역사'), actionLog(2, '장수 행동')]);
const reversed = orderedSemanticLogStreams([actionLog(1, '장수 행동'), generalHistoryLog(2, '장수 역사')]);
expect(reversed).not.toEqual(expected);
});
it('orders each Ref table by its own id and does not invent a cross-table order', () => {
const left = orderedSemanticLogStreams([
historyLog(4, '국가 기록 둘'),
historyLog(3, '국가 기록 하나'),
actionLog(8, '장수 기록 둘'),
generalHistoryLog(6, '장수 역사'),
actionLog(7, '장수 기록 하나'),
]);
const right = orderedSemanticLogStreams([
generalHistoryLog(6, '장수 역사'),
actionLog(7, '장수 기록 하나'),
actionLog(8, '장수 기록 둘'),
historyLog(3, '국가 기록 하나'),
historyLog(4, '국가 기록 둘'),
]);
expect(left).toEqual(right);
});
it('can omit the lifecycle rest log without omitting other ordered entries', () => {
expect(
orderedSemanticLogStreams([actionLog(1, '아무것도 실행하지 않았습니다.'), actionLog(2, '명령')], {
omitRest: true,
})
).toEqual(orderedSemanticLogStreams([actionLog(2, '명령')]));
});
it('maps a Core draft format to the same semantic persisted prefix as Ref', () => {
const core = actionLog(1, '명령');
const reference = actionLog(1, '<C>●</>1월:명령');
delete reference.format;
expect(orderedSemanticLogStreams([core])).toEqual(orderedSemanticLogStreams([reference]));
});
it('normalizes the hidden battle seed span with either HTML quote style', () => {
const singleQuoted = actionLog(1, `진격<span class='hidden_but_copyable'>(전투시드: abc)</span>`);
const doubleQuoted = actionLog(1, `진격<span class="hidden_but_copyable">(전투시드: abc)</span>`);
expect(orderedSemanticLogStreams([doubleQuoted])).toEqual(orderedSemanticLogStreams([singleQuoted]));
expect(
orderedSemanticLogStreams([actionLog(1, `진격<span class="visible">(전투시드: abc)</span>`)])
).not.toEqual(orderedSemanticLogStreams([singleQuoted]));
});
it.each([
['year', { year: 191 }],
['month', { month: 2 }],
['format', { format: 1 }],
])('keeps a %s mutation visible', (_field, overrides) => {
expect(orderedSemanticLogStreams([actionLog(1, '명령', overrides)])).not.toEqual(
orderedSemanticLogStreams([actionLog(1, '명령')])
);
});
it('rejects a persisted prefix whose calendar disagrees with its row', () => {
const malformed = actionLog(1, '<C>●</>2월:명령');
delete malformed.format;
expect(() => orderedSemanticLogStreams([malformed])).toThrow('stored log month 2 does not match row month 1');
});
});