# 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.