전투 차등 통합 테스트를 일괄 실행한다

This commit is contained in:
2026-09-04 05:53:08 +00:00
parent 746bbe7e7d
commit 01fdb51b6e
2 changed files with 64 additions and 3 deletions
+6
View File
@@ -134,3 +134,9 @@ PostgreSQL·Redis 경계를 증명하지 않습니다. Full suite 실패는 변
Ref 호환성 판정은 [차등 검증](architecture/turn-state-differential-testing.md),
UI는 [프론트엔드 호환 검증](frontend-legacy-parity.md)을 함께 사용합니다.
전투 아이템 전체 차등 검증은 공격자·방어자 각각 145개 fixture를 모두 Core와
Ref에서 실행하고 event, RNG, outcome, 전체 log bucket을 항목별로 비교합니다.
Ref harness는 fixture마다 새 PHP process를 띄우지 않고 `--jsonl` 입력을 한
process에서 일괄 처리합니다. `ITEM_PARITY_FILTER`는 특정 항목을 진단할 때만
사용하며, 전체 검증의 fixture 수나 assertion을 줄이는 최적화로 사용하지 않습니다.
@@ -582,7 +582,28 @@ const runReferenceTraceBatch = (workspaceRoot: string, fixtureLines: string[]):
}
const compareSourceRoot = process.env.REF_COMPARE_SOURCE_ROOT;
if (!compareSourceRoot) {
throw new Error('BATTLE_CORPUS_PATH requires REF_COMPARE_SOURCE_ROOT with the JSONL-capable ref harness.');
const stdout = execFileSync(
'docker',
['compose', 'exec', '-T', 'php', 'php', '/var/www/html/hwe/compare/battle_trace.php', '--jsonl'],
{
cwd: path.join(workspaceRoot, 'docker_compose_files/reference'),
input: normalizeJsonlForManifest(fixtureLines),
encoding: 'utf8',
stdio: ['pipe', 'pipe', 'pipe'],
maxBuffer: 512 * 1024 * 1024,
}
);
const traces = stdout
.split(/\r?\n/u)
.filter(Boolean)
.map((line) => JSON.parse(line) as ReferenceTrace);
if (traces.length !== fixtureLines.length) {
throw new Error(`ref batch returned ${traces.length} traces for ${fixtureLines.length} fixtures`);
}
traces.forEach((trace, index) =>
assertReferenceFixtureIdentity(trace, fixtureLines[index]!, `compose trace[${index}]`)
);
return traces;
}
const stdout = runReferenceSourceScript({
workspaceRoot,
@@ -2370,6 +2391,14 @@ describeWithReference('ref ↔ core2026 battle differential', () => {
},
};
const failures: string[] = [];
const cases: Array<{
itemKey: string;
base: BattleSimRequestPayload & { startYear: number };
coreEvents: WarBattleTraceEvent[];
coreLogs: CoreLogCapture;
coreRng: TracingRng | null;
coreOutcome: WarBattleOutcome | null;
}> = [];
const itemFilter = process.env['ITEM_PARITY_FILTER'];
for (const [itemKey, slot] of [...itemSlots].sort(([lhs], [rhs]) => lhs.localeCompare(rhs))) {
@@ -2423,7 +2452,16 @@ describeWithReference('ref ↔ core2026 battle differential', () => {
return coreRng.createRandUtil();
},
});
const reference = runReferenceTrace(workspaceRoot!, JSON.stringify(base));
cases.push({ itemKey, base, coreEvents, coreLogs, coreRng, coreOutcome });
}
const references = runReferenceTraceBatch(
workspaceRoot!,
cases.map(({ base }) => JSON.stringify(base))
);
for (let index = 0; index < cases.length; index += 1) {
const { itemKey, base, coreEvents, coreLogs, coreRng, coreOutcome } = cases[index]!;
const reference = references[index]!;
try {
assertTraceParity(coreEvents, reference, coreRng, coreOutcome);
assertAllLogBucketsParity(coreLogs, reference, base, `item.attacker.${itemKey}`);
@@ -2487,6 +2525,14 @@ describeWithReference('ref ↔ core2026 battle differential', () => {
},
};
const failures: string[] = [];
const cases: Array<{
itemKey: string;
base: BattleSimRequestPayload & { startYear: number };
coreEvents: WarBattleTraceEvent[];
coreLogs: CoreLogCapture;
coreRng: TracingRng | null;
coreOutcome: WarBattleOutcome | null;
}> = [];
const itemFilter = process.env['ITEM_PARITY_FILTER'];
for (const [itemKey, slot] of [...itemSlots].sort(([lhs], [rhs]) => lhs.localeCompare(rhs))) {
@@ -2538,7 +2584,16 @@ describeWithReference('ref ↔ core2026 battle differential', () => {
return coreRng.createRandUtil();
},
});
const reference = runReferenceTrace(workspaceRoot!, JSON.stringify(base));
cases.push({ itemKey, base, coreEvents, coreLogs, coreRng, coreOutcome });
}
const references = runReferenceTraceBatch(
workspaceRoot!,
cases.map(({ base }) => JSON.stringify(base))
);
for (let index = 0; index < cases.length; index += 1) {
const { itemKey, base, coreEvents, coreLogs, coreRng, coreOutcome } = cases[index]!;
const reference = references[index]!;
try {
assertTraceParity(coreEvents, reference, coreRng, coreOutcome);
assertAllLogBucketsParity(coreLogs, reference, base, `item.defender.${itemKey}`);