Crusader_Decomp/docs/remorse-class-candidate-inventory.md
2026-04-12 14:45:08 +02:00

16 KiB

Remorse Class Candidate Inventory

Purpose

This note is the working inventory for the first C++-oriented class lift in Remorse.

It is intentionally narrower than a full source plan. The goal here is to identify the object families that already have enough verified evidence to justify explicit class modeling in Ghidra later, even before the MCP layer grows class-authoring endpoints.

Until MCP can create namespaces, move methods, and build typed vtable/struct layouts directly, this note should act as the canonical queue for manual class-upgrade work.

Selection Rules

A family belongs here only if at least some of the following are already true in the current notes:

  • a constructor-style allocator/init path exists
  • a destructor, teardown, or release path exists
  • one or more stable vtable roots or slot dispatches are known
  • instance fields have repeatable meanings across multiple methods
  • the family has enough caller context to separate instance methods from generic helpers

Families that are only callback-shaped or object-like but still lack a safe subsystem label can stay here at lower confidence. They are still useful because they tell us where later class tooling must preserve ambiguity.

Confidence Scale

  • High: enough evidence to start class namespaces, instance structs, and method ownership now
  • Medium: class-like enough to inventory and maybe type, but higher-level naming or ownership is still partially open
  • Low: strong object mechanics, but subsystem naming or field ownership is still too ambiguous for broad lifting

Inventory

Candidate family Confidence Current best class-level read Core evidence Ctor / create evidence Dtor / release evidence Vtable evidence Size / layout evidence Immediate lift value
EntityDispatchEntryBase High Base dispatch-entry object used by scheduler/event and transition/runtime-state families entity_dispatch_entry_init, repeated field writes, list ownership, flag/state helpers 0008:ba00 entity_dispatch_entry_init alloc/init path for 0x32 bytes; variant allocators in 0004:ea00, 0008:cefb, 0008:d214 0008:dbec entity_word_list_destroy; 000d:8078 entity_dispatch_entry_release_runtime_state for runtime-state flavor base/root vtables 0x3b06, 0x2d10, 0x3afe; derived vtables 0x3ad2, 0x3aa6; slot dispatch at +0x14, +0x28 stable fields at +0x02, +0x04, +0x06, +0x08, +0x0a..+0x18, +0x32..+0x3e, plus runtime-state tail Strongest pilot family for the first full class-model pass
EntityDispatchEntryRuntimeState High Runtime-state / palette-backed derived dispatch entry with owned buffers and hold-state propagation runtime-state init/release pair, palette-entry emission families, owner-byte propagation through g_active_dispatch_entry_farptr 000d:7e00 entity_dispatch_entry_init_runtime_state; several dispatch_entry_create_*_palette_state* helpers build this family 000d:8078 entity_dispatch_entry_release_runtime_state frees owned buffers and updates hold-state propagation inherits dispatch-entry behavior; runtime-state users dispatch through vtable slot +0x08 after waits owned fields +0x41/+0x42/+0x44, paired buffers at +0x46/+0x48 and +0x4a/+0x4c Excellent second step after the base dispatch-entry type
SpriteNode High Tree-based UI/render node with child links, dirty-state propagation, event dispatch, and destructor ownership recursive tree helpers, event switch dispatch, dirty/update family, destructor constructor not yet explicitly named in current notes, but object-init helper and repeated sprite-node ownership patterns are strong 000b:326e sprite_node_destroy releases child nodes and frees self event dispatch through vtable slots +0x14/+0x18/+0x20/+0x24; many default vtable stubs identified in seg091 repeated fields at +0x19/+0x1b, +0x21, +0x23, +0x29; global focus pointer interaction at 0x4fd0:0x4fd2 Strong candidate because virtual surface is already visible and bounded
Entity High Base gameplay entity / actor-like object with multiple derived vtables for generic, shot, corpse, and debris behaviors stable NE entity layout, repeated lifecycle helpers, projectile/debris subfamilies 0007:3f2f entity_spawn; 0007:435e shot_entity_alloc; 0007:7490 debris_spawn; 0007:2c92 dialog_spawn is adjacent but likely separate family 0007:40d4 entity_remove; 0007:5092 entity_deactivate; 0007:44a9 shot_entity_free for projectile flavor vtables 0x29aa, 0x297e, 0x2a1a, 0x2a33, 0x2a57, plus registry vtable 0x2969; multiple virtual-slot helper wrappers in raw notes strong instance layout from +0x00 through +0xbd in ne-segment1.md Central long-term class family, but should probably be split into base + derived subfamilies
DialogMenuObject Medium Small UI/dialog object family sharing one vtable and event-notify wrappers dialog_spawn, menu/cursor event notify wrappers, UI update callback 0007:2c92 dialog_spawn allocates object and stamps vtable 0x28b5 no standalone destructor named yet in current notes vtable 0x28b5; cursor_event_notify_*, menu_event_notify_*, and ui_update_callback all behave like method wrappers enough to establish family ownership, but layout is still thin Good compact pilot if a smaller UI-oriented family is preferred
WatchEntityController Medium Global controller/watch/camera object with explicit virtual dispatch and startup/display involvement global object at 0x2bd8, dispatch wrapper, create-global path, startup/display callsites 0007:ba00 watch_entity_controller_create_global delegates to 0007:ba45 watch_entity_controller_create, stamps type 0x2c2b, stores global object no direct destructor identified in current notes repeated dispatch through vtable slots +0x24, +0x2c, and +0x30 global-object ownership clearer than field layout; seed row at 0x2be4 into callback table Worth inventorying now because it will benefit immediately from namespace/method grouping
EntityVmRuntime High Main VM runtime object that owns owner-resource helper, cached slot/value state, and category-base setup creation/load path is structurally stable and repeatedly cross-checked against extracted usecode evidence 000d:44df entity_vm_runtime_init_from_path_if_configured; 000d:4c99 entity_vm_runtime_create destroy path not fully named in the snippets here, but owner-resource destroy is known and runtime state/save-load consumers are well constrained not a classic gameplay vtable family in the current notes, but method-style ownership and object fields are stable object size and field zones strongly implied by +0x10c/+0x10e, +0x117/+0x119, +0x1315/+0x1317 and related runtime state Major lift target because VM readability is a blocker for recompilable source
EntityVmOwnerResource High File-backed helper owned by VM runtime that indexes source tables and materializes owner rows helper object shape and per-entry loader contract are already tight 000d:7000 entity_vm_runtime_owner_resource_create allocates helper/object tables paired destroy helper 000d:70fd entity_vm_runtime_owner_resource_destroy is documented in related notes helper method-table uses slots +0x04 size-query and +0x0c materialization callback helper-owned count +0x14, far-pointer table +0x10, paired word table +0x18, owner rows stride 0x0d One of the cleanest non-gameplay object families for typed struct work
NPCActionProcess family High Bounded NPC AI process family with a shared base shell and derived stand/pace/surrender/guard/loiter policies live process-table ownership now grounds the shared slot-1 destroy path, shared slot-10 no-op, and the bounded per-family create/run/destroy entries in seg033 1100:0000 NPCActionProcess_Create, 1100:02ed StandProcess_CreateProcess, 1100:0383 SurrenderProcess_CreateProcess, 1100:0693 PaceProcess_CreateProcess, 1100:0984 GuardProcess_CreateProcess, 1100:0afb LoiterProcess_CreateProcess 1100:1089 NPCActionProcess_Destroy, 1100:1036 StandProcess_Destroy, 1100:0437 SurrenderProcess_Destroy, 1100:0fe8 PaceProcess_Destroy, 1100:0f95 GuardProcess_Destroy, 1100:0f47 LoiterProcess_Destroy shared slot-10 base no-op at 1100:0fe3 plus loiter-only slot-10 override at 1100:0d3e; broader slot semantics still open current layout evidence is still thin, but SurrenderProcess_Destroy already proves family-local state plus two embedded dispatch-entry children High navigation value for gameplay/NPC AI work even before a safe datatype pass
EntityVmContext Medium Per-slot/per-entity VM context object built from runtime and owner-resource data create/setup/load helpers already have clear ownership, but broader dispatch semantics are still active work 000d:46ec entity_vm_context_create_from_slot_index and related masked-create wrappers no single destroy method is highlighted in the current note set used here context-side dispatch and busy-state updates through virtual or callback-like method surface at least on the context object stable fields include +0x32/+0x34, +0xd6/+0xd8, +0x102, +0x10c/+0x10e, +0x11b/+0x11d, +0x123 Important for VM readability, but should follow runtime and owner-resource typing
CacheBackendObject Medium Small backend/cache loader object with DOS file-handle state and method table constructor and callback roles are already explicit 0009:5600 cache_backend_object_init allocates 0x20 bytes and seeds method-table state no explicit destructor named in current note slice backend callback roles at +0x34 and +0x0c are verified in cache lookup/load path concrete 0x20-byte size; fields at +0x08, +0x0c, +0x10, +0x14, +0x16, +0x18, +0x1c Good contained family for early datatype work
PresentationCallbackBroker Low Video/presentation-state callback broker rooted at 0x4588 init/teardown/callback slot evidence is real, but subsystem naming remains intentionally conservative runtime_callback_object_init_once family is documented, but not all constructor details are fully promoted here runtime_callback_object_teardown_once and finalize path are explicit vtable slots +0x04, +0x08, +0x0c all have live evidence global state at 0x4588/0x458c/0x4590/0x4594/0x4595/0x45a6; payload fields from caller objects at +0x12d/+0x12f, +0x74f/+0x751 Useful as a typed broker object later, but not a good first namespace/class pilot
UsecodeDebuggerBreakState Medium Dormant debugger-state object retained in retail binary clear constructor and method expectations, but retail instantiation path is missing 1408:0000 usecode_debugger_break_state_create allocates and initializes the object no direct destroy path highlighted in current notes breakpoint gate callbacks through object vtable during 1408:0053 flow internal tables and state exist, but field map is not yet summarized into one layout note Good archival class candidate even if not a current gameplay priority

If the goal is to make later class-authoring work fast and low-risk, the best order is:

  1. EntityDispatchEntryBase
  2. EntityDispatchEntryRuntimeState
  3. SpriteNode
  4. NPCActionProcess family
  5. EntityVmOwnerResource
  6. CacheBackendObject
  7. WatchEntityController
  8. Entity
  9. EntityVmRuntime
  10. EntityVmContext
  11. PresentationCallbackBroker

This order prioritizes bounded families with visible constructors, derived variants, or explicit method tables before the larger gameplay and VM surfaces.

Broad Sweep Priorities

The earlier class-lift lane focused on proving a few pilot families end to end. After the recent live authoring batches, the next useful broader sweep is smaller and more structured than the raw inventory table alone suggests.

Tier 1: Ready Or Already Started

These families now have enough evidence or live foothold that they can be treated as the main near-term queue rather than as speculative future ideas.

  1. EntityVmOwnerResource
  • Why it is next-ready: bounded helper object, explicit create/destroy pair, stable callback slots at +0x04 and +0x0c, and already-strong loader/table layout evidence.
  • Why it broadens coverage well: it adds a non-UI, non-dispatch, non-debugger object family with a compact file-backed contract.
  1. CacheBackendObject
  • Why it stays in Tier 1: it is still a compact object with a concrete 0x20 size and explicit method-table state.
  • Current live status: the constructor shell is now started in CRUSADER.EXE as Remorse::CacheBackendObject::Create at live 1250:0000, backed by the decompiler's explicit old 0009:5600 segment metadata.
  1. SpriteNode
  • Why it stays active even in a broader sweep: its core live surface is now materially landed, but the constructor-wrapper split, deeper slot semantics, and subtype boundary questions remain open.
  • Current live status: class owner plus first method batch and first datatypes already exist in-session, so future broader work can deepen it selectively instead of rediscovering it.

Tier 2: Good Broader Identification Targets

These families are strong enough to be promoted intentionally in a wider identification pass, but they still benefit from one more bounded evidence sweep before heavy datatype work.

  1. WatchEntityController
  • Why it stands out: global object ownership is clear, create-global and direct create paths are already named, and vtable dispatch at +0x24, +0x2c, and +0x30 is explicit.
  • Main current caution: the wider subsystem label is still weaker than the local object mechanics.
  1. DialogMenuObject
  • Why it stands out: small UI/dialog object with one stable vtable (0x28b5) and multiple event-notify wrappers that already behave like owned methods.
  • Main current caution: destructor and richer layout evidence still trail the create/event surface.
  1. PresentationCallbackBroker
  • Why it still belongs in the broader class map: object lifecycle and vtable-slot surface are real, and it gives the broader sweep one presentation-side callback family.
  • Main current caution: subsystem naming is intentionally conservative and should probably remain that way until installation provenance tightens further.

Tier 3: High-Value But Bigger Families

These are absolutely class-worthy, but they are better treated as longer arcs than as quick additions to a broad identification pass.

  1. Entity
  2. EntityVmRuntime
  3. EntityVmContext

The common pattern here is that they are already clearly object-shaped, but each carries enough subtype, caller, or schema breadth that broad identification alone is not the main problem anymore.

First Pilot Candidates

Best pilot: EntityDispatchEntryBase

Why it is the safest first full class lift:

  • constructor variants are already named
  • derived vtable variants are known
  • multiple field groups have stable semantics
  • destroy/release behavior is present
  • it directly exercises the exact MCP features later needed: namespace creation, struct typing, method ownership, and vtable slot labeling

Smallest bounded pilot: CacheBackendObject

Why it is attractive:

  • explicit 0x20 size
  • explicit create path
  • explicit method-table callback roles
  • smaller ambiguity surface than gameplay entities or the VM runtime

Best UI-oriented pilot: SpriteNode

Why it is valuable:

  • clear virtual event surface
  • tree ownership and destructor logic already exist
  • likely to benefit quickly from readable class/derived-method naming

Per-Family Documentation Tasks

For each inventory entry, later class-upgrade work should produce the same minimum artifacts:

  1. candidate namespace/class name
  2. constructor and destructor list
  3. instance-size estimate or known size
  4. vtable root(s) and known slots
  5. field map grouped by confidence
  6. caller ownership notes
  7. explicit keep as free function list for helpers that should not become methods

Immediate Follow-Up Notes Worth Writing Later

  • dedicated EntityDispatchEntry class-layout note with base/derived split
  • dedicated SpriteNode virtual-slot table note
  • dedicated EntityVmRuntime / EntityVmOwnerResource layout note
  • dedicated Entity family split note covering base entity, projectile, debris, and corpse variants

Current Rule Until MCP Catches Up

Do not rely on automatic class listings from the live MCP tools as the class-recovery source of truth. The current output is still noisy and does not reflect the actual game object families well enough for disciplined C++ lifting.

Use this inventory plus the linked evidence notes as the authoritative queue for future class-authoring work.