diff --git a/docs/architecture/legacy-engine-ai.md b/docs/architecture/legacy-engine-ai.md new file mode 100644 index 0000000..ab8af7f --- /dev/null +++ b/docs/architecture/legacy-engine-ai.md @@ -0,0 +1,191 @@ +# 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*`) + - weighted by target general's deficit and recent activity. +- **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: + - NPC troop leaders (type 5) always `집합`. + - wanderers decide on founding / moving / 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. diff --git a/docs/architecture/legacy-engine.md b/docs/architecture/legacy-engine.md index 6e2529e..400d964 100644 --- a/docs/architecture/legacy-engine.md +++ b/docs/architecture/legacy-engine.md @@ -39,6 +39,7 @@ organization rather than endpoint-first routing. - Turn execution pipeline: `docs/architecture/legacy-engine-execution.md` - General model and action stack: `docs/architecture/legacy-engine-general.md` +- General AI behavior and policy model: `docs/architecture/legacy-engine-ai.md` - Trigger system (iAction + trigger callers): `docs/architecture/legacy-engine-triggers.md` - Battle and war resolution: `docs/architecture/legacy-engine-war.md` - Command catalog: `docs/architecture/legacy-commands.md` diff --git a/docs/architecture/todo.md b/docs/architecture/todo.md index 99c6e24..32e1496 100644 --- a/docs/architecture/todo.md +++ b/docs/architecture/todo.md @@ -10,6 +10,7 @@ Move items into the main docs once they are finalized. - In-memory state lifecycle and DBMS flush checkpoints - Recovery behavior after partial flush or crash - Observability: metrics, logs, and alerts for turn processing +- [AI suggestion] Define a stable in-memory AI state contract (snapshot + delta invalidation rules) aligned with `GeneralAI` inputs. ## Game Logic and Testing