Add the 10th modular LLM ability (v0.40.0/#67 system): a turn-transientTurnResult.migration lever letting the model declare population flows between nations.
What it does
Each op is { kind, from, to?, scale, detail? }:
kind: migration | refugees | diaspora | exodus (exodus has no to)
scale: small | moderate | large
Deterministic per-scale effects (no hidden randomness), clamped 0-100, mirrored to each nation's regions, aggregates re-derived:
Scale
Source loses (pop, eco)
Destination gains (pop, eco)
extra
small
-1, -1
+1, +1
—
moderate
-2, -2
+2, +2
—
large
-4, -3
+3, +3
destination stability -2
The from<->to relation then moves one step toward the positive end of the ally/neutral/rival/hostile ladder via shiftRelationTowardPositive (the mirror of the hybrid layer's shiftRelationTowardHostile): capped at ally for refugees (hosting refugees builds the strongest goodwill), neutral for migration/diaspora, and not at all for exodus; a war relation is never touched (the nudge neither fabricates nor escalates a war).
Properties
Turn-transient (like hybrid / disasters): no new GameState field, no db.ts change, no durable list.
Never moves territory — sole ownership is untouched (a no-man's-land re-claim stays the regionChanges lever's job).
Drop-first resolution: an unknown / ambiguous source, a missing / unknown / ambiguous destination, or a self-flow (from === to) drops the whole op (the source is untouched).
Registered last in ABILITIES (existing order untouched); the stub is migration-inert, so e2e (CI, LLM_STUB=1) is unaffected.
Tests
New server/abilities/migration.test.ts (38 tests); registry.test.ts updated to the 10-ability contract.
System goldens system-no-tool.txt / system-tool.txt re-baselined (each gains the schema line + constraint bullet); user.txt byte-identical (no new user-payload keys).
Local verification: typecheck + lint + 593/593 unit tests + build all green.
The Gitea #64 body sketched a simpler flows: { type: 'refugees' | 'settlers' } shape that included a settler re-claim of no-man's-land. The refined, approved design implemented here is the richer four-kind nation-to-nation stat layer that deliberately never moves territory.
Add the **10th** modular LLM ability (v0.40.0/#67 system): a **turn-transient** `TurnResult.migration` lever letting the model declare population flows between nations.
## What it does
Each op is `{ kind, from, to?, scale, detail? }`:
- **kind**: `migration` | `refugees` | `diaspora` | `exodus` (`exodus` has no `to`)
- **scale**: `small` | `moderate` | `large`
Deterministic per-scale effects (no hidden randomness), clamped 0-100, mirrored to each nation's regions, aggregates re-derived:
| Scale | Source **loses** (pop, eco) | Destination **gains** (pop, eco) | extra |
|-------|----------------------------|----------------------------------|-------|
| small | -1, -1 | +1, +1 | — |
| moderate | -2, -2 | +2, +2 | — |
| large | -4, -3 | +3, +3 | destination stability -2 |
The `from`<->`to` relation then moves **one step toward the positive** end of the ally/neutral/rival/hostile ladder via `shiftRelationTowardPositive` (the mirror of the hybrid layer's `shiftRelationTowardHostile`): capped at `ally` for `refugees` (hosting refugees builds the strongest goodwill), `neutral` for `migration`/`diaspora`, and not at all for `exodus`; a `war` relation is **never** touched (the nudge neither fabricates nor escalates a war).
## Properties
- **Turn-transient** (like `hybrid` / `disasters`): no new `GameState` field, no `db.ts` change, no durable list.
- **Never moves territory** — sole ownership is untouched (a no-man's-land re-claim stays the `regionChanges` lever's job).
- Drop-first resolution: an unknown / ambiguous source, a missing / unknown / ambiguous destination, or a **self-flow** (`from` === `to`) drops the whole op (the source is untouched).
- Registered **last** in `ABILITIES` (existing order untouched); the stub is migration-inert, so e2e (CI, `LLM_STUB=1`) is unaffected.
## Tests
- New `server/abilities/migration.test.ts` (**38 tests**); `registry.test.ts` updated to the **10-ability** contract.
- System goldens `system-no-tool.txt` / `system-tool.txt` re-baselined (each gains the schema line + constraint bullet); **`user.txt` byte-identical** (no new user-payload keys).
- Local verification: `typecheck` + `lint` + **593/593** unit tests + `build` all green.
## Design note (interpretation of #64)
The Gitea #64 body sketched a simpler `flows: { type: 'refugees' | 'settlers' }` shape that included a settler **re-claim of no-man's-land**. The refined, approved design implemented here is the richer four-kind nation-to-nation **stat** layer that deliberately never moves territory.
**Fixes #64**
Add the 10th modular LLM ability: a turn-transient TurnResult.migration
lever declaring population flows between nations (migration / refugees /
diaspora / exodus). The source loses a little population + economy, the
destination gains a little (a large influx also strains its stability),
and the from<->to relation is nudged one step toward the positive end of
the ally/neutral/rival/hostile ladder (capped by kind; a war is never
touched). Deterministic per-scale tables, no hidden randomness, clamped
0-100, mirrored to owned regions, re-derived aggregates; never moves
territory. Registered last in ABILITIES; user prompt unchanged
(user.txt golden byte-identical), system goldens re-baselined. Fixes#64.
kamilkrzywda
scheduled this pull request to auto merge when all checks succeed 2026-10-16 12:20:00 +02:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Add the 10th modular LLM ability (v0.40.0/#67 system): a turn-transient
TurnResult.migrationlever letting the model declare population flows between nations.What it does
Each op is
{ kind, from, to?, scale, detail? }:migration|refugees|diaspora|exodus(exodushas noto)small|moderate|largeDeterministic per-scale effects (no hidden randomness), clamped 0-100, mirrored to each nation's regions, aggregates re-derived:
The
from<->torelation then moves one step toward the positive end of the ally/neutral/rival/hostile ladder viashiftRelationTowardPositive(the mirror of the hybrid layer'sshiftRelationTowardHostile): capped atallyforrefugees(hosting refugees builds the strongest goodwill),neutralformigration/diaspora, and not at all forexodus; awarrelation is never touched (the nudge neither fabricates nor escalates a war).Properties
hybrid/disasters): no newGameStatefield, nodb.tschange, no durable list.regionChangeslever's job).from===to) drops the whole op (the source is untouched).ABILITIES(existing order untouched); the stub is migration-inert, so e2e (CI,LLM_STUB=1) is unaffected.Tests
server/abilities/migration.test.ts(38 tests);registry.test.tsupdated to the 10-ability contract.system-no-tool.txt/system-tool.txtre-baselined (each gains the schema line + constraint bullet);user.txtbyte-identical (no new user-payload keys).typecheck+lint+ 593/593 unit tests +buildall green.Design note (interpretation of #64)
The Gitea #64 body sketched a simpler
flows: { type: 'refugees' | 'settlers' }shape that included a settler re-claim of no-man's-land. The refined, approved design implemented here is the richer four-kind nation-to-nation stat layer that deliberately never moves territory.Fixes #64