Research
This commit is contained in:
parent
28cbbe3470
commit
a9153546ae
56 changed files with 6731 additions and 258 deletions
112
docs/npc-action-process-class-layout.md
Normal file
112
docs/npc-action-process-class-layout.md
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
# 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
|
||||
Loading…
Add table
Add a link
Reference in a new issue