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