# NPC Action Process Family Layout ## Purpose This note captures the current safest class-lift state for the bounded seg033 NPC AI process family in live `CRUSADER.EXE`. The goal is not to force a complete inheritance or process-layout model yet. The current goal is narrower: - preserve the now-verified class ownership in Ghidra - record which methods are strong enough to live under class owners today - keep the remaining uncertainty explicit so later vtable and datatype work can resume without rediscovery ## Current Live Class Owners The following class owners now exist live in `CRUSADER.EXE` under the `Remorse` namespace: - `Remorse::NPCActionProcess` - `Remorse::StandProcess` - `Remorse::PaceProcess` - `Remorse::SurrenderProcess` - `Remorse::GuardProcess` - `Remorse::LoiterProcess` This is an owner-first lift only. No full instance struct, base-process overlay, or vtable datatype has been forced yet. ## Landed Method Batch ### Base family | Live address | Current name | Why it is safe | |---|---|---| | `1100:0000` | `Remorse::NPCActionProcess::Create` | Shared family create entry already named and bounded in the local process lane. | | `1100:1084` | `Remorse::NPCActionProcess::RunNoop` | Base run body is an intentional no-op and does not need wider semantics yet. | | `1100:1089` | `Remorse::NPCActionProcess::Destroy` | Shared slot-1 destroy body is grounded by live `g_*ProcessFnPtr` ownership across the NPC action family. | | `1100:0fe3` | `Remorse::NPCActionProcess::VtableSlot10Noop` | Shared slot-10 no-op body is structurally bounded even though final slot semantics remain open. | ### Derived families | Live address | Current name | Why it is safe | |---|---|---| | `1100:02ed` | `Remorse::StandProcess::CreateProcess` | Direct family create wrapper in the same bounded seg033 lane. | | `1100:0359` | `Remorse::StandProcess::Run` | Direct owned run body for the stand policy. | | `1100:1036` | `Remorse::StandProcess::Destroy` | Slot-1 destroy entry grounded by live process-function-table ownership. | | `1100:0383` | `Remorse::SurrenderProcess::CreateProcess` | Direct family create wrapper in the same bounded seg033 lane. | | `1100:04ab` | `Remorse::SurrenderProcess::Run` | Direct owned run body for surrender behavior. | | `1100:0437` | `Remorse::SurrenderProcess::Destroy` | Clear family destroy path; resets the surrender vtable root, clears the local NPC flag bit, and destroys two embedded dispatch-entry children. | | `1100:0693` | `Remorse::PaceProcess::CreateProcess` | Direct family create wrapper in the same bounded seg033 lane. | | `1100:0708` | `Remorse::PaceProcess::Run` | Direct owned run body for the pace policy. | | `1100:0fe8` | `Remorse::PaceProcess::Destroy` | Slot-1 destroy entry grounded by live process-function-table ownership. | | `1100:0984` | `Remorse::GuardProcess::CreateProcess` | Direct family create wrapper in the same bounded seg033 lane. | | `1100:0a0e` | `Remorse::GuardProcess::Run` | Direct owned run body for the guard policy. | | `1100:0f95` | `Remorse::GuardProcess::Destroy` | Slot-1 destroy entry grounded by live process-function-table ownership. | | `1100:0afb` | `Remorse::LoiterProcess::CreateProcess` | Direct family create wrapper in the same bounded seg033 lane. | | `1100:0bfa` | `Remorse::LoiterProcess::Run` | Direct owned run body for the loiter policy. | | `1100:0d3e` | `Remorse::LoiterProcess::VtableSlot10DispatchByShapeIfAlive` | Loiter-only slot-10 override is behaviorally distinct enough to separate from the shared base slot-10 no-op. | | `1100:0f47` | `Remorse::LoiterProcess::Destroy` | Slot-1 destroy entry grounded by live process-function-table ownership. | ## Shared Helper That Still Stays Free `1100:0913` remains `NPC_DoRandomIdleAnimTwiceIfNotBusy` instead of being forced under one class owner. Current evidence supports a helper role more strongly than a specific class-method role: - the live caller map still shows only `Remorse::GuardProcess::Run` and `Remorse::LoiterProcess::Run` - it gates on `NPC_IsBusy` - it chooses between two idle-animation paths and dispatches `NPC_DoAnim` twice That makes it strong enough to name and comment, but still weak for ownership under exactly one class. ## Ghidra Documentation Landed In-Session The live database now also carries short decompiler comments on the main ownership and evidence anchors: - `NPCActionProcess::Create` - `NPCActionProcess::Destroy` - `SurrenderProcess::CreateProcess` - `SurrenderProcess::Destroy` - `GuardProcess::CreateProcess` - `GuardProcess::Run` - `LoiterProcess::CreateProcess` - `LoiterProcess::Run` - `LoiterProcess::VtableSlot10DispatchByShapeIfAlive` - `NPC_DoRandomIdleAnimTwiceIfNotBusy` Those comments preserve the current rationale without pretending that the datatype and inheritance story is already closed. ## Current Safest Structural Read The family now reads as: 1. one bounded `NPCActionProcess` base with a shared create entry, shared destroy entry, and at least one shared no-op virtual slot 2. multiple policy-specific derived families (`Stand`, `Pace`, `Surrender`, `Guard`, `Loiter`) 3. one shared guard/loiter-side idle helper that still belongs outside the current class owners That is enough for owner-first navigation in Ghidra, but not yet enough for a full ABI-clean C++ inheritance model. ## Things Still Open The main remaining gaps are now narrower than basic object identity: - what slot `10` and the adjacent slot `11` mean semantically across the broader process family - whether the current `NPCActionProcess::Create` should later split into a thinner base initializer plus a more explicit allocator/factory wrapper - whether the shared slot-1 destroy body can support a first provisional base-process overlay without destabilizing other process families - whether `StandProcess` and `PaceProcess` have any equally strong family-local helper methods in the same seg033 window that should move next ## Recommended Next Pass The next pass on this family should stay conservative: 1. recover the process-function-table roots for the seg033 family explicitly enough to document slot order 2. inspect local caller/callee structure around `StandProcess::Run` and `PaceProcess::Run` for family-local helpers comparable to the guard/loiter idle helper 3. only then decide whether a provisional `/Remorse/NPCActionProcess` datatype is safe, or whether the family should remain owner-only for now