Add general constraint fallback parity

This commit is contained in:
2026-07-26 03:37:48 +00:00
parent 2de999d1b0
commit 4b9ddb3e25
3 changed files with 91 additions and 6 deletions
@@ -8,12 +8,15 @@
- 범위: 명령 결과, RNG 소비, 로그, 예약 턴 lifecycle, DB 영속화 - 범위: 명령 결과, RNG 소비, 로그, 예약 턴 lifecycle, DB 영속화
현재 ref MariaDB와 core memory를 잇는 공통 runner, 성공 경로 55개, 현재 ref MariaDB와 core memory를 잇는 공통 runner, 성공 경로 55개,
실행 중 확률 실패 9개가 구현됐다. 실패 9개는 내정 critical 실행 중 확률 실패 9개와 full constraint fallback 7개가 구현됐다.
실패 9개는 내정 critical
`주민선정/정착장려/상업투자/기술연구/물자조달`과 모략 `주민선정/정착장려/상업투자/기술연구/물자조달`과 모략
`화계/선동/파괴/탈취`이며 RNG 전체 trace, semantic state delta와 실패 `화계/선동/파괴/탈취`이며 RNG 전체 trace, semantic state delta와 실패
로그 본문을 비교한다. 나머지 제약 실패·값 경계·alternative와 전체 로그 본문을 비교한다. 제약 7개는 무소속, 방랑국, 타국 도시, 보급 단절,
core PostgreSQL 재조회가 완료 기준을 통과하기 전까지 55개 명령 전체의 금·쌀 부족과 민심 상한을 대표하며 휴식 fallback과 RNG/state delta를
동적 호환 상태를 `확인`으로 올리지 않는다. 비교한다. 나머지 명령별 제약 실패·값 경계·alternative와 전체 core
PostgreSQL 재조회가 완료 기준을 통과하기 전까지 55개 명령 전체의 동적
호환 상태를 `확인`으로 올리지 않는다.
## 결정 요약 ## 결정 요약
@@ -179,8 +179,12 @@ responses pass a separate reference trace and core resolver/API boundary.
Nine general in-action failure cases now also pass the common differential: Nine general in-action failure cases now also pass the common differential:
five domestic critical failures and four sabotage failures. They compare the five domestic critical failures and four sabotage failures. They compare the
full command RNG trace, semantic state delta and the exact action-log body. full command RNG trace, semantic state delta and the exact action-log body.
This is not yet a claim that the remaining constraint, clamp, alternative and Seven full-constraint cases cover neutral status, wandering nation, city
persistence boundaries have been dynamically compared. ownership, supply, gold, rice and trust-cap rejection. Both engines replace
the denied command with rest, consume the same RNG and produce the same
semantic delta. This is not yet a claim that every command-specific
constraint, clamp, alternative and persistence boundary has been dynamically
compared.
The fixture runner also reports whether the requested legacy command reached The fixture runner also reports whether the requested legacy command reached
its completed execution path. For multi-turn commands this is derived from the its completed execution path. For multi-turn commands this is derived from the
@@ -484,3 +484,81 @@ integration('general command in-action failure matrix', () => {
120_000 120_000
); );
}); });
type GeneralConstraintCase = {
name: string;
action: string;
args?: Record<string, unknown>;
actorPatch?: Record<string, unknown>;
fixturePatches?: FixturePatches;
};
const constraintCases: GeneralConstraintCase[] = [
{
name: 'neutral general',
action: 'che_훈련',
actorPatch: { nationId: 0, officerLevel: 0 },
},
{
name: 'wandering nation',
action: 'che_농지개간',
fixturePatches: {
nations: { 1: { level: 0, capitalCityId: 0, typeCode: 'None' } },
},
},
{
name: 'city not occupied by actor nation',
action: 'che_농지개간',
fixturePatches: { cities: { 3: { nationId: 2 } } },
},
{
name: 'unsupplied city',
action: 'che_농지개간',
fixturePatches: { cities: { 3: { supplyState: 0 } } },
},
{
name: 'insufficient gold',
action: 'che_상업투자',
actorPatch: { gold: 0 },
},
{
name: 'insufficient rice',
action: 'che_주민선정',
actorPatch: { rice: 0 },
},
{
name: 'maximum city trust',
action: 'che_주민선정',
fixturePatches: { cities: { 3: { trust: 100 } } },
},
];
integration('general command full-constraint fallback matrix', () => {
it.each(constraintCases)(
'$name: $action falls back exactly like legacy',
async ({ action, args, actorPatch, fixturePatches }) => {
const request = buildRequest(action, args, actorPatch, fixturePatches);
request.setup!.world!.hiddenSeed = `general-constraint-${action}`;
const reference = runReferenceTurnCommandTraceRequest(
workspaceRoot!,
request as unknown as Record<string, unknown>
);
const core = await runCoreTurnCommandTrace(request, reference.before);
expect(reference.execution.outcome).toMatchObject({ completed: false });
expect(core.execution.outcome).toMatchObject({
requestedAction: action,
actionKey: '휴식',
usedFallback: true,
});
expect(core.execution.outcome).toHaveProperty('blockedReason');
expect(core.rng).toEqual(reference.rng);
expect(
compareTurnSnapshotDeltas(reference.before, reference.after, core.before, core.after, {
ignoredPathPatterns: ignoredLifecyclePaths,
})
).toEqual([]);
},
120_000
);
});