문서 업데이트: 레거시 엔진 아키텍처 및 명령 카탈로그 추가

This commit is contained in:
2025-12-26 19:06:33 +00:00
parent 475772240c
commit f41c50b9c1
7 changed files with 580 additions and 0 deletions
+136
View File
@@ -0,0 +1,136 @@
# Legacy Command Catalog
This file lists the command keys available under `legacy/hwe/sammo/Command/`.
Command keys match class names and file names; they are the values stored in
`general_turn.action` and `nation_turn.action`.
## Command Resolution
- General: `buildGeneralCommandClass($type)` in `legacy/hwe/func_converter.php`
- Nation: `buildNationCommandClass($type)` in `legacy/hwe/func_converter.php`
- `null` or empty `type` defaults to `휴식`
- Post-req turn cooldowns are stored in:
- `next_execute` KV (per general)
- `nation_env` KV (per nation)
## Execution Semantics (BaseCommand)
- Conditions are checked via constraint sets (permission/min/full).
- `getPreReqTurn()` is enforced by `LastTurn` term stacking.
- `getPostReqTurn()` is enforced by `next_execute` (cooldown).
- If `run()` returns `false`, `getAlternativeCommand()` may be used.
## Command Prefixes and Scenarios
Command keys are scenario-driven. The prefixes (e.g. `che_`, `cr_`, `event_`)
identify the rule set or scenario extension, and the active scenario chooses
which commands are available via `GameConst` overrides. See
`docs/architecture/legacy-scenarios.md` for details.
## General Commands (`legacy/hwe/sammo/Command/General/`)
| Key | Display name (actionName) |
| --- | --- |
| `che_NPC능동` | `NPC능동` |
| `che_강행` | `강행` |
| `che_거병` | `거병` |
| `che_건국` | `건국` |
| `che_견문` | `견문` |
| `che_군량매매` | `군량매매` |
| `che_귀환` | `귀환` |
| `che_기술연구` | `기술 연구` |
| `che_내정특기초기화` | `내정 특기 초기화` |
| `che_농지개간` | `농지 개간` |
| `che_단련` | `단련` |
| `che_등용` | `등용` |
| `che_등용수락` | `등용 수락` |
| `che_랜덤임관` | `무작위 국가로 임관` |
| `che_모반시도` | `모반시도` |
| `che_모병` | `모병` |
| `che_무작위건국` | `무작위 도시 건국` |
| `che_물자조달` | `물자조달` |
| `che_방랑` | `방랑` |
| `che_사기진작` | `사기진작` |
| `che_상업투자` | `상업 투자` |
| `che_선동` | `선동` |
| `che_선양` | `선양` |
| `che_성벽보수` | `성벽 보수` |
| `che_소집해제` | `소집해제` |
| `che_수비강화` | `수비 강화` |
| `che_숙련전환` | `숙련전환` |
| `che_요양` | `요양` |
| `che_은퇴` | `은퇴` |
| `che_이동` | `이동` |
| `che_인재탐색` | `인재탐색` |
| `che_임관` | `임관` |
| `che_장비매매` | `장비매매` |
| `che_장수대상임관` | `장수를 따라 임관` |
| `che_전투태세` | `전투태세` |
| `che_전투특기초기화` | `전투 특기 초기화` |
| `che_접경귀환` | `접경귀환` |
| `che_정착장려` | `정착 장려` |
| `che_주민선정` | `주민 선정` |
| `che_증여` | `증여` |
| `che_집합` | `집합` |
| `che_징병` | `징병` |
| `che_첩보` | `첩보` |
| `che_출병` | `출병` |
| `che_치안강화` | `치안 강화` |
| `che_탈취` | `탈취` |
| `che_파괴` | `파괴` |
| `che_하야` | `하야` |
| `che_해산` | `해산` |
| `che_헌납` | `헌납` |
| `che_화계` | `화계` |
| `che_훈련` | `훈련` |
| `cr_건국` | `건국` |
| `cr_맹훈련` | `맹훈련` |
| `휴식` | `휴식` |
## Nation Commands (`legacy/hwe/sammo/Command/Nation/`)
| Key | Display name (actionName) |
| --- | --- |
| `che_감축` | `감축` |
| `che_국기변경` | `국기변경` |
| `che_국호변경` | `국호변경` |
| `che_급습` | `급습` |
| `che_몰수` | `몰수` |
| `che_무작위수도이전` | `무작위 수도 이전` |
| `che_물자원조` | `원조` |
| `che_발령` | `발령` |
| `che_백성동원` | `백성동원` |
| `che_부대탈퇴지시` | `부대 탈퇴 지시` |
| `che_불가침수락` | `불가침 수락` |
| `che_불가침제의` | `불가침 제의` |
| `che_불가침파기수락` | `불가침 파기 수락` |
| `che_불가침파기제의` | `불가침 파기 제의` |
| `che_선전포고` | `선전포고` |
| `che_수몰` | `수몰` |
| `che_의병모집` | `의병모집` |
| `che_이호경식` | `이호경식` |
| `che_종전수락` | `종전 수락` |
| `che_종전제의` | `종전 제의` |
| `che_증축` | `증축` |
| `che_천도` | `천도` |
| `che_초토화` | `초토화` |
| `che_포상` | `포상` |
| `che_피장파장` | `피장파장` |
| `che_필사즉생` | `필사즉생` |
| `che_허보` | `허보` |
| `cr_인구이동` | `인구이동` |
| `event_극병연구` | `극병 연구` |
| `event_대검병연구` | `대검병 연구` |
| `event_무희연구` | `무희 연구` |
| `event_산저병연구` | `산저병 연구` |
| `event_상병연구` | `상병 연구` |
| `event_원융노병연구` | `원융노병 연구` |
| `event_음귀병연구` | `음귀병 연구` |
| `event_화륜차연구` | `화륜차 연구` |
| `event_화시병연구` | `화시병 연구` |
| `휴식` | `휴식` |
## Open Questions / Follow-ups
- Prefix semantics (`che_`, `cr_`, `event_`) are not documented here and may be
scenario- or mode-specific.
@@ -0,0 +1,121 @@
# Legacy Engine Execution Flow
This document summarizes how the legacy engine advances turns and executes
commands. It focuses on the execution pipeline, RNG seeding, and state writes
based on `legacy/hwe/sammo/API/Global/ExecuteEngine.php`,
`legacy/hwe/sammo/TurnExecutionHelper.php`, and `legacy/hwe/sammo/Command/BaseCommand.php`.
## Entry Point: ExecuteEngine API
- Endpoint: `legacy/hwe/sammo/API/Global/ExecuteEngine.php`
- Session: no session required (`NO_SESSION`)
- Optional `serverID` guard via `UniqueConst::$serverID`
- Calls `TurnExecutionHelper::executeAllCommand($updated, $locked)`
- Returns `{ updated, locked, lastExecuted }`
## Global Turn Loop (`TurnExecutionHelper::executeAllCommand`)
1. **Time gate**
- If `now < game_env.turntime`, return without executing.
2. **Locking**
- `tryLock()` on `plock` row (`type='GAME'`).
- If locked or `isunited` in `2|3`, return (frozen state).
3. **Pre-turn maintenance**
- Cache game env, `checkDelay()`, `updateOnline()`, `CheckOverhead()`.
4. **Catch-up monthly loop**
- Compute `prevTurn = cutTurn(turntime, turnterm)` and `nextTurn = addTurn(prevTurn)`.
- While `nextTurn <= now`:
- `executeGeneralCommandUntil(nextTurn, limitActionTime, year, month)`
- `updateTraffic()`
- Monthly pipeline:
- `runEventHandler(PreMonth)`
- `preUpdateMonthly()`
- `turnDate(nextTurn)`
- `checkStatistic()` (only if `month == 1`)
- `runEventHandler(Month)`
- `postUpdateMonthly($monthlyRng)`
- Advance `prevTurn`, `nextTurn`, update `game_env.turntime`.
5. **Current partial month**
- `turnDate(prevTurn)`
- `executeGeneralCommandUntil(now, limitActionTime, year, month)`
6. **Post-turn maintenance**
- `processTournament()`
- `processAuction()`
- Reset cache, `unlock()`.
`limitActionTime` is derived from `max_execution_time` (roughly 2/3 of the
configured limit). If time runs out mid-loop, it returns early and keeps
`game_env.turntime` at the most recent executed value.
## Per-General Execution (`executeGeneralCommandUntil`)
For each general with `turntime < targetDate` (ordered by `turntime, no`):
1. **Load state**
- `General::createObjFromDB()` and `KVStorage` for `game_env`.
2. **Nation command (if officer_level >= 5)**
- Pull `nation_turn` row for `turn_idx = 0`.
- Build `NationCommand` with `LastTurn` stored in `nation_env`.
3. **AI/autorun**
- NPCs (`npc >= 2`) or players past `autorun_limit` use `GeneralAI`.
- AI can replace both nation and general commands.
4. **Preprocess triggers**
- RNG seed: `hiddenSeed + 'preprocess' + year + month + generalID`.
- `preprocessCommand()` merges action triggers + `che_부상경감`, `che_병력군량소모`.
5. **Blocked users**
- `processBlocked()` consumes `killturn` and logs if `block >= 2`.
6. **Execute nation command**
- RNG seed: `hiddenSeed + 'nationCommand' + year + month + generalID + commandKey`.
- `processNationCommand()` checks conditions, term stack, `run()`.
- If `run()` fails and `getAlternativeCommand()` exists, it retries.
- Updates `nation_env` with `LastTurn` result.
7. **Execute general command**
- Load from `general_turn` (`turn_idx = 0`), default to `휴식`.
- RNG seed: `hiddenSeed + 'generalCommand' + year + month + generalID + commandKey`.
- `processCommand()` checks conditions, term stack, `run()`.
- Updates `killturn` based on NPC status, autorun, or `휴식`.
8. **Queue maintenance & turntime**
- `pullNationCommand()` / `pullGeneralCommand()` advance queues.
- `updateTurnTime()` handles deletion/retirement and sets next `turntime`.
- Persist via `General::applyDB()`.
## Command Semantics (BaseCommand)
- **Constraints**: permission/min/full conditions are evaluated via
`Constraint::testAll()` with `general/city/nation/dest*` context.
- **Pre-turn requirement**: `getPreReqTurn()` uses `LastTurn` to accumulate
term stack (`addTermStack()`), logging "수행중" until the term completes.
- **Post-turn requirement**: `getPostReqTurn()` uses `next_execute` storage
(general: `next_execute`, nation: `nation_env`) to block early execution.
- **Run flow**: `run($rng)` returns `true` when completed; `false` can chain to
`getAlternativeCommand()`.
## Turntime & Lifecycle (`updateTurnTime`)
- **Inactivity**: if `killturn <= 0`:
- NPC-owned characters can detach (owner -> NPC) if `deadyear` not reached.
- Otherwise the general is deleted (`kill()`).
- **Retirement**: if `age >= retirementYear` and not NPC, `CheckHall()` and
`rebirth()` are invoked.
- **Scheduling**: next `turntime` is `addTurn(current, turnterm)` with
optional `nextTurnTimeBase` override (aux var).
## Deterministic RNG Seeds
RNG uses `LiteHashDRBG` with `UniqueConst::$hiddenSeed`:
- Preprocess: `('preprocess', year, month, generalID)`
- Nation command: `('nationCommand', year, month, generalID, commandKey)`
- General command: `('generalCommand', year, month, generalID, commandKey)`
- Monthly: `('monthly', year, month)`
These seeds make command outcomes and monthly updates reproducible for
validation.
## Open Questions / Follow-ups
- `preUpdateMonthly()`, `postUpdateMonthly()`, `turnDate()`, `checkStatistic()`
are defined outside this flow; see `legacy/hwe/func_time_event.php` for
economic updates and population calculations.
- `updateTraffic()`, `checkDelay()`, `updateOnline()` logic lives in
`legacy/hwe/func.php` and related utility files.
@@ -0,0 +1,91 @@
# Legacy General Model and Action Stack
This document captures how `General` aggregates modifiers, computes stats, and
exposes trigger hooks. The main reference is `legacy/hwe/sammo/General.php`.
## Construction and Core State
- `General` builds from raw DB rows (general, city, nation, rank, access log).
- `last_turn` is parsed into `LastTurn`, and a separate `resultTurn` is
maintained for updates.
- `penalty` JSON is decoded into `PenaltyKey -> value` map.
- Rank data is tracked with:
- `rankVarRead` (current read view)
- `rankVarIncrease` (increment queue)
- `rankVarSet` (explicit set queue)
## Action Objects (`iAction`)
`General` composes action sources into a single list:
1. Nation type (`buildNationTypeClass`)
2. Officer level (`TriggerOfficerLevel`)
3. Domestic special (`buildGeneralSpecialDomesticClass`)
4. War special (`buildGeneralSpecialWarClass`)
5. Personality (`buildPersonalityClass`)
6. Crew type (`GameUnitConst::byID`)
7. Inheritance buff (`TriggerInheritBuff`)
8. Scenario effect (`buildScenarioEffectClass`)
9. Items: horse / weapon / book / item (`buildItemClass`)
`getActionList()` returns this merged list and is the core entry point for
modifiers and triggers.
## Stat Computation
`getStatValue()` applies layered modifiers:
- Base stat from raw value
- Injury multiplier
- Cross-stat adjust (strength/intel + 1/4 of the other stat)
- Clamp to `GameConst::$maxLevel`
- `onCalcStat()` from every action object
- Cached by `(statName, injury, action, adjust)` tuple
Convenience wrappers:
- `getLeadership()`, `getStrength()`, `getIntel()`
## Turn Reservation and LastTurn
- `getReservedTurn(turnIdx)` reads from `general_turn`.
- When no entry exists, `buildGeneralCommandClass(null)` creates `휴식`.
- `LastTurn` holds `{ command, arg, term, seq }` and is used by
`BaseCommand::addTermStack()` to advance multi-turn commands.
- `applyDB()` persists updated fields and writes `last_turn` if it changed.
## Trigger and Modifier Hooks
`General` delegates calculations and trigger lists through action objects:
- Economy/strategy: `onCalcDomestic`, `onCalcStrategic`,
`onCalcNationalIncome`
- Combat modifiers: `onCalcStat`, `onCalcOpposeStat`, `getWarPowerMultiplier`
- Arbitrary actions: `onArbitraryAction` (used in city conquest)
Turn-prep triggers:
- `getPreTurnExecuteTriggerList()` merges trigger lists from every action
object. `TurnExecutionHelper::preprocessCommand()` adds
`che_부상경감` + `che_병력군량소모` on top.
Battle triggers:
- `getBattleInitSkillTriggerList()` merges per-action battle-init triggers.
- `getBattlePhaseSkillTriggerList()` includes base phase triggers:
- `che_필살시도`, `che_필살발동`
- `che_회피시도`, `che_회피발동`
- `che_계략시도`, `che_계략발동`, `che_계략실패`
- plus any action-specific additions.
## Rank and Access Log Updates
- `increaseRankVar()` / `setRankVar()` queue updates until `applyDB()`.
- `applyDB()` updates `general` and `rank_data`, then flushes logs.
- `checkStatChange()` uses `*_exp` thresholds to increase/decrease stats and
logs level changes.
## Open Questions / Follow-ups
- `PenaltyKey` effects and how penalties are applied are outside this file.
- `GeneralBase` and `LazyVarAndAuxUpdater` may contain additional state
conventions for new code to mirror.
+109
View File
@@ -0,0 +1,109 @@
# Legacy Battle and War Resolution
This document summarizes the legacy battle pipeline centered on
`legacy/hwe/process_war.php`, `legacy/hwe/sammo/WarUnit.php`,
`legacy/hwe/sammo/WarUnitGeneral.php`, and `legacy/hwe/sammo/WarUnitCity.php`.
## Entry Point: `processWar`
Inputs:
- `warSeed` (string): battle RNG seed
- `attackerGeneral`: `General`
- `rawAttackerNation`: nation row (snapshot)
- `rawDefenderCity`: city row (snapshot)
Setup steps:
1. Initialize RNG with `warSeed` (`LiteHashDRBG`).
2. Load defender nation row (or default neutral nation if `nation = 0`).
3. Build `WarUnitGeneral` (attacker) and `WarUnitCity` (defender city).
4. Collect defender generals in the city, filter with `extractBattleOrder()`.
5. Optionally append city as defender if city order > 0.
6. Sort defenders by battle order, iterate via `getNextDefender()`.
7. Run `processWar_NG()`; update DB and nation/city stats.
## Battle Order (`extractBattleOrder`)
For defender generals:
- Must have crew > 0
- Must have rice > crew/100
- `train` and `atmos` must meet `defence_train`
- Battle order uses:
- `totalStat = (realStat + fullStat) / 2`
- `totalCrew = crew / 1_000_000 * (train * atmos) ^ 1.5`
- `totalStat + totalCrew / 100`
For cities:
- Uses attacker `onCalcOpposeStat('cityBattleOrder', -1)`.
## Battle Loop (`processWar_NG`)
1. Log start, include seed in battle logs.
2. If no defenders remain, set defender = city and switch to siege.
3. **Initial engagement**
- `setOppose()` for attacker/defender
- `addTrain(1)` for both
- Fire battle-init triggers
4. **Per phase**
- `beginPhase()` computes war power
- Fire battle-phase triggers
- `calcDamage()` on both sides
- Clamp damage if it exceeds HP ratios
- Apply damage, increase killed/dead counters
- Log phase results
5. **Continuation checks**
- `continueWar()` fails on no rice or HP <= 0
- On retreat/defeat: log, apply win/lose, try wound
- If defender removed, move to next defender (or city siege)
6. **Finish**
- `logBattleResult()` for last phase if needed
- `finishBattle()` for attacker/defender
- City conflict tracking and history logs
## Post-Battle Updates (`processWar`)
After `processWar_NG()`:
- Apply attacker DB updates
- Update nation rice (supply and siege rules)
- Distribute city `dead` counts (40% attacker city, 60% defender city)
- Increase nation tech based on killed/dead and nation size adjustments
- Update `diplomacy.dead` for both sides
- If city conquered: call `ConquerCity()`
## `WarUnitGeneral` Highlights
- Train/atmos bonuses depend on city level and attacker/defender role.
- War power:
- Base attack/defence from crew type + tech
- Adjusted by train/atmos, dex (`getDex()`), crew type coefficients
- Experience level scales war power and counter-scales opponent
- `General::getWarPowerMultiplier()` applies special multipliers
- Rice consumption on kills: proportional to damage, tech cost, unit rice
- Wound chance: 5% unless `부상무효` / `퇴각부상무효` triggered
- `finishBattle()` updates rank stats, rounds values, and checks stat changes
## `WarUnitCity` Highlights
- Uses `DummyGeneral` with `CREWTYPE_CASTLE`
- HP = `def * 10`
- Computed attack/defence = `(def + wall * 9) / 500 + 200`
- City train/atmos scales with elapsed years since `startYear`
- Siege state:
- Non-siege battle ends after one exchange
- Siege continues until HP <= 0
- `heavyDecreaseWealth()` halves `agri/comm/secu` on supply-based rout
- `addConflict()` records contribution in `city.conflict` JSON
## Deterministic RNG
- Main battle uses `warSeed` directly.
- City conquest uses `hiddenSeed + 'ConquerCity' + year + month + nationID + generalID + cityID`.
## Open Questions / Follow-ups
- Detailed conquest outcomes (nation collapse, officer handling) extend beyond
the summary here; see `ConquerCity()` in `legacy/hwe/process_war.php`.
+8
View File
@@ -34,3 +34,11 @@ organization rather than endpoint-first routing.
- "attempt" then "execute" phases
- Common trigger categories (traits, specials, scenario effects)
- How triggers combine when multiple sources apply
## Detailed Notes
- Turn execution pipeline: `docs/architecture/legacy-engine-execution.md`
- General model and action stack: `docs/architecture/legacy-engine-general.md`
- Battle and war resolution: `docs/architecture/legacy-engine-war.md`
- Command catalog: `docs/architecture/legacy-commands.md`
- Scenario system and rule sets: `docs/architecture/legacy-scenarios.md`
+109
View File
@@ -0,0 +1,109 @@
# Legacy Scenario System
This document explains how scenarios are loaded and how they define the active
rule set, commands, and effects. Core references include
`legacy/hwe/sammo/Scenario.php`, `legacy/hwe/sammo/ResetHelper.php`, and
`legacy/hwe/sammo/GameConstBase.php`.
## Scenario Loading Flow
1. Server reset/init calls `ResetHelper::buildScenario()`.
2. `Scenario` loads `scenario_{id}.json` and merges defaults (`default.json`).
3. `Scenario::buildConf()` generates runtime constants:
- `d_setting/GameConst.php` from `GameConstBase + scenario.const/map/stat`
- `d_setting/CityConst.php` from `scenario/map/{mapName}.php`
- `d_setting/GameUnitConst.php` from `scenario/unit/{unitSet}.php`
4. `Scenario::build()` inserts nations, generals, and events into DB and runs
`initialEvents` immediately.
`Scenario::getAllScenarios()` is used for listing scenarios without fully
building them (lazy init).
## Scenario JSON Structure (Observed)
Common top-level keys (see `legacy/hwe/scenario/frame.json` and actual
`scenario_*.json` files):
- `title`, `startYear`, `history`, `iconPath`
- `stat`: default stat totals and bounds
- `map`: `mapName`, `unitSet`, `scenarioEffect`
- `const`: overrides for `GameConst` (commands, items, limits, etc.)
- `nation`, `diplomacy`
- `general`, `general_ex`, `general_neutral`
- `events`, `initialEvents`
- `ignoreDefaultEvents` (skip `GameConst::$defaultInitialEvents/$defaultEvents`)
Notes:
- A few files still use `initialActions` or `defaultInitialEvents` keys. The
engine currently reads `initialEvents` only.
- `general` rows use the tuple format from `Scenario::generateGeneral()`:
`affinity, name, picture, nationName, city, leadership, strength, intel,
officerLevel, birth, death, ego, char, text`.
## How Scenario Chooses Commands and Effects
Scenario config influences runtime rules via `GameConst` and `ScenarioEffect`:
- `const.availableGeneralCommand` / `const.availableChiefCommand` define the
commands that appear in UI and can be executed.
- `const.availableSpecialDomestic/War`, `const.availablePersonality`,
`const.allItems`, `const.availableNationType` control selectable traits/items.
- `map.scenarioEffect` or `const.scenarioEffect` sets
`GameConst::$scenarioEffect`, which is injected into each `General` as an
`iAction` (`General::getActionList()`).
- `const.availableInstantAction` merges into
`GameConst::$availableInstantAction`.
Because `GameConst` is generated from scenario data, a scenario can swap
available commands or replace the action pool entirely.
## Command Prefix Conventions
Prefixes are used to separate rule packs and assets:
- `che_`: default rule set (base commands, specials, items, nation types).
- `cr_`: alternate rule set used by specific scenarios (e.g. `scenario_910`).
- `event_`: scenario-specific extensions (research, extra unit sets, or special
effects).
Example: `scenario_910.json` uses `mapName=cr` and `unitSet=cr` and overrides
`availableGeneralCommand/availableChiefCommand` to include `cr_건국`,
`cr_맹훈련`, and `cr_인구이동` alongside `che_` commands.
## Scenario Environment Variants (Current Repo)
These are the map/unit/effect variants referenced by existing scenario files.
Defaults are `mapName=che` and `unitSet=che` when not specified.
Map sets (`scenario/map/*.php`):
- `che` (default)
- `miniche`, `miniche_b`, `miniche_clean`
- `cr`
- `chess`
- `pokemon_v1`
- `ludo_rathowm`
Unit sets (`scenario/unit/*.php`):
- `che` (default)
- `che_except_siege`
- `cr`
- `basic`
- `siegetank`
- `event_more_crewtype`
- `ludo_rathowm`
Scenario effects (`sammo/ActionScenarioEffect/*`):
- `event_StrongAttacker`
- `event_UnlimitedDefenceThresholdChange`
- `event_MoreEffect`
## Event Targets
Scenario events are stored in the `event` table and executed via
`TurnExecutionHelper::runEventHandler()` using `EventTarget` values:
`PRE_MONTH`, `MONTH`, `OCCUPY_CITY`, `DESTROY_NATION`, `UNITED`.
Most scenario JSON uses lowercase targets (e.g. `"month"`). The DB enum uses
uppercase values but is case-insensitive, so lowercase targets still match.
+6
View File
@@ -28,3 +28,9 @@ Move items into the main docs once they are finalized.
- "Next-turn intent" (예턴) data schema and lifecycle
- Profile selection workflow and deployment mapping
## Legacy Engine Docs
- [AI suggestion] Expand monthly pipeline details (`preUpdateMonthly`, `postUpdateMonthly`, `turnDate`, `checkStatistic`) with concrete side effects and tables touched.
- [AI suggestion] Document `ConquerCity()` resolution paths (nation collapse, officer handling, reward/penalty rules).
- [AI suggestion] Clarify command prefix semantics (`che_`, `cr_`, `event_`) and add per-command effect summaries.