Files
core2026/docs/architecture/legacy-engine-ai.md
T

210 lines
8.2 KiB
Markdown

# Legacy General AI (GeneralAI)
This document summarizes how `legacy/hwe/sammo/GeneralAI.php` selects nation and
general commands, which data fields it relies on, and how policies shape NPC
behavior. It also outlines considerations for an in-memory rewrite of the AI
loop.
## Entry Points
- `chooseNationTurn(NationCommand $reservedCommand)`
- `chooseInstantNationTurn(NationCommand $reservedCommand)`
- `chooseGeneralTurn(GeneralCommand $reservedCommand)`
All three call `updateInstance()` first, which caches current state and derives
key AI decisions (diplomacy state, general type, policy setup).
## Deterministic RNG
`GeneralAI` uses `LiteHashDRBG` seeded with:
```
hiddenSeed + "GeneralAI" + year + month + generalID
```
This makes AI choices reproducible per turn. All random choices (`choice`,
`choiceUsingWeight`, `nextBool`) flow through this RNG.
## State Snapshot and Derived Fields
`updateInstance()` pulls and caches:
- `game_env` (via `KVStorage`): year, month, startyear, turnterm, develcost,
init_year/init_month, killturn, global NPC policy defaults.
- `city` (current city row, from `general` -> `city`).
- `nation` (nation row, or fallback for neutral).
- `nation_env` (KVStorage): npc policy overrides, prev income, last attackable.
- `nation['aux']` decoded JSON.
- `general` primary stats (leadership/strength/intel; both full and adjusted).
- `baseDevelCost` and `maxResourceActionAmount`.
- `dipState`, `attackable`, `warTargetNation`.
- `genType` (type flags derived from stats + policy thresholds).
Derived groupings are computed lazily:
- `categorizeNationCities()` => `nationCities`, `frontCities`, `supplyCities`,
`backupCities` with per-city `dev` and `important` scores.
- `categorizeNationGeneral()` => user/NPC buckets, war/civil buckets,
troop leaders, lost generals, chief list.
## Diplomacy State (`calcDiplomacyState`)
Diplomacy state controls war/peace behavior:
- `d평화`: no war declarations, no active war.
- `d선포`: declaration in progress, no active war.
- `d징병`: pre-war recruitment window.
- `d직전`: immediate pre-war.
- `d전쟁`: active war, or recently lost a front (grace period).
Key inputs:
- `diplomacy` rows (`state`, `term`) for current nation.
- `front` cities with `supply=1`.
- `year/month` relative to `startyear` (early-game war lockout).
`warTargetNation` tracks eligible enemies:
- `2` = currently at war.
- `1` = declaration in progress / pre-war.
- `0` is used as "any neighbor" fallback when no active target exists.
## General Type (`calcGenType`)
General type is a bitmask:
- `t무장` (strength-leaning)
- `t지장` (intelligence-leaning)
- `t통솔장` (leadership threshold for war-capable NPCs)
Logic:
- Strength vs intel decides initial bias.
- If the weaker stat is within 80%, a probabilistic hybrid type can be added.
- `t통솔장` is set when `leadership >= nationPolicy->minNPCWarLeadership`.
## Policy Inputs (Autorun)
`AutorunNationPolicy` and `AutorunGeneralPolicy` are built from:
- per-user autorun options (`env['autorun_user']['options']`)
- `nation_env` overrides (`npc_nation_policy`, `npc_general_policy`)
- global defaults (`env['npc_nation_policy']`, `env['npc_general_policy']`)
- live nation + env snapshot
Policies provide:
- `priority` action lists
- `can{Action}` toggles
- thresholds (min crew, safe population ratio, resource floors, etc.)
## Nation Turn Behavior (`chooseNationTurn`)
Nation-level choices run only for NPCs (`npc >= 2`) or for autorun users:
1. Apply quarterly promotions and tax/bill adjustments.
2. Honor reserved nation commands if valid.
3. Iterate policy `priority`, invoking matching `do{Action}`.
4. Fall back to neutral (empty) nation command if all fail.
### Major Action Groups
- **Troop movement**
- `do부대전방발령`, `do부대후방발령`, `do부대구출발령`
- user/NPC versions to move generals between front/back/supply cities
- checks `frontCities`, `supplyCities`, `last발령`, and war route.
- **Resource distribution**
- `do유저장포상`, `doNPC포상`, `doNPC몰수`
- uses resource floors (`reqNation*`, `reqNPC*`, `reqHuman*`)
- sorts by each general's gold/rice, excludes inactive (`killturn <= 5`)
targets, and preserves the legacy geometric-mean amounts and candidate
weights.
- **Diplomacy**
- `do불가침제의`: respond to assistance requests with NAP offer.
- `do선전포고`: probabilistic declaration when strong enough.
- **Capital relocation**
- `do천도`: moves capital based on population, dev, and connectivity.
## General Turn Behavior (`chooseGeneralTurn`)
General-level decisions are layered:
1. NPC message broadcast if `npcmsg` and RNG triggers.
2. Reserved command is honored if valid (unless `휴식`).
3. Immediate recovery if `injury > cureThreshold`.
4. Special cases:
- A nationless NPC troop leader shortens `killturn` and keeps its reserved
command; an affiliated type-5 leader refreshes `killturn` and uses `집합`.
- wandering lords decide on founding, one-edge movement toward a cached
target, or disbanding.
5. Iterate policy `priority`, invoking `do{Action}`.
6. Fallback to `do중립`.
### Major Action Groups
- **Domestic development**
- `do일반내정`, `do전쟁내정`, `do긴급내정`
- weighted by `city` dev rates and general type flags.
- **War preparation**
- `do징병`, `do전투준비`, `do출병`
- strict checks on crew, train, atmos, population and diplomacy state.
- **Mobility**
- `do전방워프`, `do후방워프`, `do내정워프`, `do귀환`
- uses `front/supply/backup` cities and population thresholds.
- **Resource handling**
- `do금쌀구매` (trade), `doNPC헌납` (donation).
- **Neutral behavior**
- `do중립` selects between `물자조달`, `인재탐색`, `견문`.
## Data Fields Accessed (Representative)
GeneralAI reads or writes:
- **Nation**: `nation`, `capital`, `gold`, `rice`, `tech`, `level`, `type`,
`chief_set`, `rate`, `bill`, `aux`.
- **City**: `city`, `nation`, `supply`, `front`, `pop`, `pop_max`, `trust`,
`agri/comm/secu/def/wall` and `*_max`, `trade`, `level`, `region`.
- **General**: `npc`, `officer_level`, `officer_city`, `killturn`, `injury`,
`troop`, `crew`, `train`, `atmos`, `defence_train`, `gold`, `rice`, `belong`,
`permission`, `npcmsg`, `dex1..dex5`, `armType`, `affinity`, `makelimit`.
- **KVStorage**: `npc_*_policy`, `prev_income_*`, `last_attackable`,
`last천도Trial`, `resp_assist*`.
## In-Memory Rewrite Considerations
To port the AI to an in-memory state model without behavior drift:
- **Snapshot-first**
- Build a per-turn `GameSnapshot` containing env, nation, cities, generals,
diplomacy, and nation_env. `GeneralAI` should read only from this snapshot.
- **Derived caches**
- Cache `DiplomacyState`, `CityBuckets`, `GeneralBuckets`, and `WarRoute`.
- Use lazy recalculation and invalidate only the affected region/city/general
after a command is applied.
- **Deterministic ordering**
- For candidate lists, sort by ID before weighted RNG to preserve parity.
- RNG seeding should keep the exact per-turn seed scheme to ensure replay.
- **Command evaluation**
- Keep `hasFullConditionMet()` semantics intact by providing the same
generalized context (`general`, `city`, `nation`, `dest*`).
- **Policy snapshots**
- Cache policy values per turn and avoid reading KVStorage per action.
- When policies change, treat it as an explicit state transition.
These guidelines mirror the current "derive once, then select via priority"
pattern and minimize resimulation deltas in the rewrite.
## Migrated decision-parity regression
`app/game-engine/test/generalAiLegacyDecisionParity.test.ts` records focused
final-command expectations extracted from `ref/sam` `ng_compare@fe9ae978`.
Its matrix varies diplomacy/war state, city development and population,
technology/year ceilings, general gold/rice and casualty ranks, stats and
affinity, reserved/special NPC state, nation treasury reserves, and command
availability. It also asserts RNG-sensitive candidate weights where consuming
the same random branch is part of the final decision.
This is compatibility evidence for the represented decision branches. The
long-running NPC scenario suites remain smoke tests and are not a substitute
for this branch-level matrix.