diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d18565..3449138 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,21 @@ All notable changes to **History** are listed here, newest first. +## v0.40.0 — 2026-10-16 07:14 +- **#67: Modular LLM-ability system — the 8 per-feature LLM abilities are extracted from `server/engine.ts` into an ordered `ABILITIES` registry (`server/abilities/`).** A pure, **behavior-preserving** refactor: each per-ability concern (validate / schema-line / constraint / payload / meaningfulness / apply / stub-default / durable-state-field) moves into one file per ability, assembled into a single ordered registry that the engine iterates uniformly. **Nothing observable changes** — the system prompt, user prompt, `applyTurnResult` world-state mutations, and the deterministic stub are all byte-for-byte identical to v0.39.0 (pinned by new golden fixtures + registry tests), and `server/engine.ts` re-exports every name its existing callers import, so `server/index.ts` and all tests work with zero edits. + - **New `server/abilities/` package.** + - `types.ts` — the `Ability` interface (a self-contained per-field module: `field`, `default()`, `validate()`, `apply()`, `schemaLine()`, `constraint()`, `isMeaningful()`, `stub()`, `payload()`, optional `stateFields`) plus the `AbilityField` union (the closed set of the 8 fields), `AbilityCtx`, and `UserPromptCtx` type surfaces. + - `shared.ts` — the cross-ability pure helpers (`resolveNationRef`, `resolveRegionKey`, `freshNationId`, `freshWarId`, `NEW_NATION_STATS`). + - `apply-helpers.ts` — the war-relation sync helpers (`activeWarPairs`, `atWar`, `syncWarRelations`). + - One file per ability — `takeovers.ts`, `wars.ts`, `hybrid.ts`, `economy.ts`, `industry.ts`, `nuclear.ts`, `leaders.ts`, `ideology.ts` — each owning its `Ability` const (validator, applier, schema line, constraint bullet, meaningfulness predicate, inert stub default, user-payload keys, and — for wars / economy / industry — the `GameState` fields it owns). + - `index.ts` — assembles the ordered `ABILITIES` registry (`takeovers → wars → hybrid → economy → industry → nuclear → leaders → ideology`) and re-exports every public name the engine and its tests need. + - **Engine (`server/engine.ts`).** Every per-ability code path now iterates `ABILITIES` instead of hand-enumerating the eight fields: `buildSystemPrompt` (schema lines + constraint bullets), `buildUserPrompt` (payload keys at their exact historical positions), `validateTurnResult` (per-field validate), `applyTurnResult` (the ordered apply block), `isMeaningful`, and the `runTurn` "unclear" fallback. The ~1,249 lines of moved ability bodies are deleted; the 21 names its callers import are re-exported from `./abilities` (5 shared primitives + 16 ability-module exports). + - **Stub (`server/stub.ts`).** `deterministicTurn` fills the 8 ability fields from the registry (each contributes its inert default) between the core fields and `markers` — the JSON output stays byte-identical to v0.39.0 (verified against `origin/master`). + - **Persistence (`server/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. + - **Tests.** New `server/abilities/registry.test.ts` (11 tests): the committed **golden prompts** (`server/abilities/goldens/` — system no-tool / system tool / user) are asserted **byte-identical** to the pre-refactor engine's output (a `REGEN_GOLDENS=1` mode regenerates them after an intentional prompt change — verified a no-op); the registry is the canonical 8-ability order with 8 distinct `TurnResult` fields; each `default()` equals the canonical `TurnResult` default and is a **fresh** value (never a shared reference); the default is never "meaningful" while a populated value is; the registry declares exactly the durable `GameState` fields; the registry composes (a hypothetical 9th ability appends cleanly to every uniform loop); and the engine still re-exports every name its callers import. + - **Guarantees.** No client (`src/`) changes, no new runtime deps, no `@turf`/`@types/geojson` in any `.ts`, and `shared/` keeps zero runtime deps. All 522 unit tests green; e2e (CI, `LLM_STUB=1`) unaffected. +- **Version** bumped `0.39.0 → 0.40.0`. + ## v0.39.0 — 2026-10-15 20:51 - **#62: Ideology / form of government — a per-nation `ideology` + a `TurnResult.ideology` lever (coups / revolutions / reforms / constitutional changes) with a deterministic −5 stability dip and political narration.** Each nation may carry a free-form `ideology` label — its form of government ("democracy", "fascism", "military junta", …). The model changes it each turn via `ideology.changes`; each change names a target nation (id or name) and the new government type. The engine sets the label and applies a **deterministic** stability dip (a regime shift always unsettles a nation) — **no hidden randomness**, **additive** (no `db.ts` change — `ideology` is an optional `Nation` field; a pre-v0.39 row loads with it absent, i.e. "no recorded government"). It is a third, independent axis next to `leader` (who is in charge) and the v0.32.0 takeover (which renames the nation): the id, name, territory, relations, and the leader all stay valid — only the form of government and stability move. - **Types (`shared/types.ts`).** `Nation.ideology?: string` (optional; `undefined`/absent = no recorded government). diff --git a/package.json b/package.json index b8573d0..7095d3f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "history", - "version": "0.39.0", + "version": "0.40.0", "private": true, "type": "module", "description": "AI-driven alternate-history simulator — the LLM is the world engine (Pax Historia style).",