refactor(#74) P4: wire engine/stub/db through the ABILITIES registry
Behavior-preserving rewire: every per-ability code path now iterates the ordered ABILITIES registry instead of hand-enumerating the eight fields. - engine.ts: buildSystemPrompt (schema lines + constraint bullets), buildUserPrompt (payload keys at their historical positions), validateTurnResult (per-field validate), applyTurnResult (ordered apply block), isMeaningful, and the runTurn "unclear" fallback all drive off ABILITIES. The eight moved ability bodies (~1,249 lines) are deleted; the names the engine and its tests import are re-exported from ./abilities. - abilities/types.ts: Ability.stub is a zero-arg method (the stub has no GameState/AbilityCtx to pass; it needs none) — all eight implementations were already arg-less. - stub.ts: deterministicTurn fills the eight ability fields from the registry (each contributes its inert default), between the core fields and markers — JSON output stays byte-identical (verified against origin/master). - db.ts: the legacy-row migration for ability-owned GameState fields (wars/trades/embargoes/industry) iterates the registry's stateFields, only-if-undefined, instead of a hardcoded field list. No client changes, no new deps, no shared/ runtime deps. typecheck + lint + 511 unit tests green; stub JSON byte-identical to origin/master. Fixes #74
This commit is contained in:
+29
-9
@@ -4,6 +4,7 @@
|
||||
import { createLlmClient, type ChatMessage, type Transport } from './llm';
|
||||
import { addDays, clampStat, type TerritoryChange, type TurnEventSpec, type TurnResult } from '../shared/protocol';
|
||||
import type { RelationStatus, RoundItem } from '../shared/types';
|
||||
import { ABILITIES, type AbilityField } from './abilities';
|
||||
|
||||
export const ADVISOR_MARKER = '<<ADVISOR>>';
|
||||
|
||||
@@ -229,7 +230,18 @@ export function deterministicTurn(p: StubPayload): TurnResult {
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
// Core (non-ability) turn fields, in their historical position order.
|
||||
const pre: Pick<
|
||||
TurnResult,
|
||||
| 'date'
|
||||
| 'events'
|
||||
| 'stateChanges'
|
||||
| 'regionChanges'
|
||||
| 'relations'
|
||||
| 'territoryChanges'
|
||||
| 'unowned'
|
||||
| 'nationOps'
|
||||
> = {
|
||||
date: addDays(state?.date ?? '1939-09-01', 2),
|
||||
events,
|
||||
stateChanges: [{ nation: pid, stat: 'military', op: 'set', value: mil }],
|
||||
@@ -238,16 +250,24 @@ export function deterministicTurn(p: StubPayload): TurnResult {
|
||||
territoryChanges,
|
||||
unowned: [], // the stub never creates no man's land (see doc comment)
|
||||
nationOps: [], // the stub never proposes nation ops (see doc comment)
|
||||
takeovers: [], // the stub never performs a takeover (see doc comment)
|
||||
wars: { declare: [], end: [] }, // the stub never declares/ends wars (see doc comment)
|
||||
hybrid: { ops: [] }, // the stub never runs hybrid/covert ops (see doc comment)
|
||||
economy: { trade: [], embargo: [] }, // the stub never touches trades/embargoes (see doc comment)
|
||||
industry: { projects: [] }, // the stub never declares industry projects (see doc comment)
|
||||
nuclear: { arm: [], strikes: [] }, // the stub never arms or strikes (see doc comment)
|
||||
leaders: { changes: [] }, // the stub never changes leaders (see doc comment)
|
||||
ideology: { changes: [] }, // the stub never changes ideologies (see doc comment)
|
||||
};
|
||||
|
||||
// The 8 ability fields, filled by the registry in its canonical order. The
|
||||
// stub is ability-inert: every ability contributes its own no-op default
|
||||
// (each ability's `stub()` — see the per-ability doc comments above), so the
|
||||
// e2e assertions are unaffected. The insertion order here (ABILITIES) is the
|
||||
// historical key order pinned by the stub's deterministic output
|
||||
// (takeovers → wars → hybrid → economy → industry → nuclear → leaders →
|
||||
// ideology), which must stay byte-identical.
|
||||
const abilities = {} as Record<AbilityField, unknown>;
|
||||
for (const a of ABILITIES) abilities[a.field] = a.stub();
|
||||
|
||||
// The single field positioned after the abilities (markers last, as before).
|
||||
const post: Pick<TurnResult, 'markers'> = {
|
||||
markers: { add: [], remove: [] }, // the stub never touches markers (see doc comment)
|
||||
};
|
||||
|
||||
return { ...pre, ...abilities, ...post } as TurnResult;
|
||||
}
|
||||
|
||||
const CANNED_ADVISOR: string[] = [
|
||||
|
||||
Reference in New Issue
Block a user