feat(#62): ideology / government-type change (v0.39.0)
CI / build + unit + e2e (pull_request) Successful in 2m33s

Add a per-nation free-form ideology (form of government) field and a
TurnResult.ideology lever (coups / revolutions / reforms / constitutional
changes) with a deterministic -5 stability dip and political narration.

- shared/types.ts: Nation.ideology?: string (optional/additive)
- shared/protocol.ts: IdeologyChange / IdeologyOps, IDEOLOGY_STABILITY_EFFECT
  = -5, TurnResult.ideology (required, between leaders and markers),
  nationForPrompt now carries ideology
- server/engine.ts: validateIdeologyOps (exported, never throws),
  applyIdeologyChange (resolve -> set field -> -5 dip mirrored to regions +
  re-derived -> political event), ideology block in applyTurnResult after
  leaders / before markers, isMeaningful + validateTurnResult + system prompt
  + runTurn unclear fallback
- server/stub.ts: StubPayload gains ideology; deterministicTurn returns
  ideology: { changes: [] } (never changes one, e2e unaffected)
- Tests: engine.test.ts (validate / isMeaningful / applyTurnResult /
  buildUserPrompt), protocol.test.ts (constant + nationForPrompt),
  db.test.ts (legacy load + round-trip); every TurnResult fixture updated
- package.json -> 0.39.0; CHANGELOG v0.39.0 entry
This commit is contained in:
2026-10-15 20:54:18 +00:00
parent 9d201135b3
commit 7866a1987d
9 changed files with 501 additions and 21 deletions
+22
View File
@@ -222,6 +222,28 @@ describe('openDb', () => {
db.close();
});
it('(v0.39.0 #62) a legacy row saved before the nation ideology field existed loads with it absent (no recorded government)', () => {
const db = openDb(':memory:');
// makeState has no nation.ideology field — a pre-v0.39 save. The field is
// optional and not re-stamped by load, so an absent ideology stays absent.
db.save(makeState('legacy'));
const loaded = db.load('legacy');
expect(loaded).not.toBeNull();
expect(loaded!.nations.uk.ideology).toBeUndefined();
db.close();
});
it('(v0.39.0 #62) a set nation ideology round-trips through save/load', () => {
const db = openDb(':memory:');
const s = makeState('ideo');
s.nations.uk.ideology = 'fascism';
db.save(s);
const loaded = db.load('ideo');
expect(loaded).not.toBeNull();
expect(loaded!.nations.uk.ideology).toBe('fascism');
db.close();
});
it('delete removes a game by id and reports absence otherwise', () => {
const db = openDb(':memory:');
db.save(makeState('a'));