210 lines
8.2 KiB
Markdown
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.
|