12 KiB
EntityDispatchEntry Class Layout
Purpose
This note is the first focused class-layout working paper for the Remorse C++ lift.
It takes the broad EntityDispatchEntry* inventory entry and narrows it into a base/derived object model that can later be pushed into Ghidra as class namespaces, instance structs, vtable structs, and method ownership.
The goal is not to claim a final C++ API. The goal is to lock down the pieces that are already stable enough to support later implementation work.
Why This Family Goes First
EntityDispatchEntry is the strongest current pilot family because it already has:
- a clear constructor-style base init path
- multiple derived constructor variants
- explicit owned state and word-list teardown
- stable field groups with known offsets
- repeated virtual-slot dispatch through known offsets
- strong caller evidence across scheduler, runtime-state, palette, and startup/display lanes
That makes it the best place to prototype the full later workflow:
- class namespace creation
- method ownership
- instance-struct typing
- vtable typing
- base/derived split
- later C++ skeleton emission
Candidate High-Level Model
Current best working split:
EntityDispatchEntryBaseEntityDispatchEntryTimedorEntityDispatchEntryPeriodicfor the0x3aa6timing/period variantEntityDispatchEntryRuntimeStatefor the later000d:7e00/8078runtime-state owned-buffer family
This should stay a working model, not a hard rename, until the class work lands in Ghidra.
Base Constructor Surface
0008:ba00 entity_dispatch_entry_init
Current best read:
- optional allocate/init path for a
0x32-byte base object - stamps base vtable/list-link state using
0x3b06,0x2d10, and0x3afe - zeroes core state fields
- seeds the group/layer byte through
entity_set_group_id
This is the strongest current candidate for the base constructor-style init method.
Derived constructor variants
0008:cefb entity_dispatch_entry_ctor_vtbl_3ad2
- allocates if null
- reinitializes through
entity_dispatch_entry_init - sets vtable
0x3ad2 - sets flag
0x100at+0x16 - zeroes extension words
+0x32/+0x34
0008:d214 entity_dispatch_entry_ctor_vtbl_3aa6
- allocates
0x40bytes if null - reuses
0008:cefb - sets vtable
0x3aa6 - sets flag
0x200at+0x16 - zeroes fields
+0x38..+0x3e
Related alloc/init helpers
0004:ea00 entity_dispatch_entry_alloc_type_0f5e0004:eb1f entity_dispatch_entry_ctor_0f3a_with_cache_reset
These look more like subtype-specific factory/create helpers than pure base constructors, but they still belong in the family map.
Destroy / Release Surface
Base-owned word-list destruction
0008:dbec entity_word_list_destroy
- resets vtable to
0x2d10 - frees list storage if present
- optionally frees object when destroy flag bit
1is set
This is the clearest current destructor-style path on the base object.
Runtime-state release
000d:8078 entity_dispatch_entry_release_runtime_state
- frees paired owned buffers
- updates shared hold/owner propagation through
g_active_dispatch_entry_farptr - destroys embedded word-list members
This reads as the release/destructor path for the runtime-state derived family rather than for the whole base type.
Current Base Layout
This table is a working layout, not a finished header.
| Offset | Current name | Confidence | Current meaning |
|---|---|---|---|
+0x00 |
type_or_kind |
Medium | Constructor/factory helpers stamp type words such as 0x0f3a, 0x0f5e, or 0x051e here in some subfamilies. Base-vtable interpretation remains separate. |
+0x02 |
slot_index_or_count |
Medium | Used as entry slot/index in several wrappers; also used as count in the base word-list family, so exact role may vary by subtype or overlay. |
+0x04 |
source_type |
High | Written by entity_set_source_type. |
+0x06 |
event_type_or_list_ptr_lo |
Medium | Written by entity_set_event_type_checked, but also participates in word-list storage in the list-owning variant. This is likely one of the current overlay collisions to resolve later. |
+0x08 |
group_id_byte |
High | Low 5-bit group/layer value managed by entity_set_group_id. |
+0x0a/+0x0c/+0x0e/+0x10 |
link_or_state_words |
High | Cleared by entity_dispatch_entry_unlink; belong to link/extent/target/reset state. |
+0x12/+0x14 |
target_farptr |
High | Managed by entity_flag20_*_target helpers. |
+0x16 |
flags1 |
High | Holds bits 0x10, 0x20, 0x100, 0x200, 0x4000, and other subtype/state gates. |
+0x18 |
flags2 |
High | Holds bits 0x40, 0x80, 0x100, 0x400, 0x1000; used by unlink, periodic, and refresh paths. |
+0x1e/+0x28 |
embedded_dispatch_or_word_list_members |
Medium | Many callsites treat these as subobject or vtable-dispatch bases. Exact split still needs a dedicated subobject note. |
+0x24/+0x26, +0x2e/+0x30 |
optional_member_ptrs |
Medium | Checked before freeing both embedded word-list members. |
+0x32/+0x34 |
extension_words_a |
High | Zeroed by the 0x3ad2 constructor variant; also used by later runtime/VM helper flows. |
+0x36/+0x38/+0x3a |
period_or_schedule_words |
Medium | Written by entity_set_update_period_and_reschedule; clearly timing-related in the periodic variant. |
+0x3c/+0x3e |
accumulator_words |
High | Used by entity_periodic_accumulate_and_dispatch. |
+0x40 |
hold_token |
High | Shared/borrowed hold byte in startup/display and runtime-state families. |
+0x41/+0x42/+0x44 |
runtime_state_flags |
High | Initialized by entity_dispatch_entry_init_runtime_state. |
+0x46/+0x48 |
owned_buffer_a |
High | Runtime-state owned work/palette-like buffer. |
+0x4a/+0x4c |
owned_buffer_b |
High | Second runtime-state owned buffer. |
+0x49 |
file_family_selector |
High for the seg126 subtype | Local selector state in startup/display transition family. Likely subtype-specific, not general base meaning. |
+0x5b |
state_flags |
High for the seg126 subtype | State-machine bits in the 000c startup/display lane. Likely subtype-specific overlay. |
+0x520 |
selected_resource |
Medium | Loaded file/resource object in the transition-file-family subtype. |
Important Layout Caveat
This family is almost certainly not one flat struct with universally stable semantics at every offset. Current evidence already shows subtype overlays:
- base scheduler/dispatch-entry state
- word-list-owning variants
- periodic/timer variants
- startup/display transition variants
- runtime-state/palette-backed variants
So the safest future Ghidra modeling strategy is:
- create a minimal
EntityDispatchEntryBase - create derived or overlay structs for subtype-specific tails
- avoid prematurely forcing every offset into one monolithic universal class layout
Candidate Method Map
Strong base methods
| Address | Current function | Candidate method role |
|---|---|---|
0008:ba00 |
entity_dispatch_entry_init |
InitBase() |
0008:bbb6 |
entity_set_source_type |
SetSourceType() |
0008:bc27 |
entity_set_event_type_checked |
SetEventTypeChecked() |
0008:bca8 |
entity_set_group_id |
SetGroupId() |
0008:bd53 |
entity_dispatch_entry_unlink |
Unlink() |
0008:be05 |
entity_increment_group_id |
IncrementGroupId() |
0008:c01d |
entity_refresh_dispatch_state |
RefreshDispatchState() |
0008:bfb2 |
entity_clear_status_bits_from_flags |
ClearStatusBitsFromFlags() |
0008:bf8e |
entity_call_update_vfunc14 |
CallUpdateSlot14() |
0008:beee |
entity_run_flagged_handlers |
RunFlaggedHandlers() |
Pair/link/target helpers
| Address | Current function | Candidate method role |
|---|---|---|
0008:c7f1 |
entity_pair_update_link_slot_a |
UpdateLinkSlotA() |
0008:c890 |
entity_pair_update_link_slot_b |
UpdateLinkSlotB() |
0008:c92f |
entity_pair_sync_a |
PairSyncA() |
0008:ca18 |
entity_pair_sync_b |
PairSyncB() |
0008:c9ee |
entity_pair_mark_and_sync_a |
MarkAndPairSyncA() |
0008:cad7 |
entity_pair_mark_and_sync_b |
MarkAndPairSyncB() |
0008:cb2c |
entity_flag20_clear_and_update_target |
ClearFlag20AndUpdateTarget() |
0008:cb5c |
entity_flag20_set_and_init_target |
SetFlag20AndInitTarget() |
Periodic/timed subtype methods
| Address | Current function | Candidate method role |
|---|---|---|
0008:cefb |
entity_dispatch_entry_ctor_vtbl_3ad2 |
ConstructVtable3AD2() |
0008:d214 |
entity_dispatch_entry_ctor_vtbl_3aa6 |
ConstructVtable3AA6() |
0008:d313 |
entity_periodic_accumulate_and_dispatch |
TickPeriodic() |
0008:d3e6 |
entity_set_flag2000_and_update_active_counters |
EnableActiveCounters() |
0008:d433 |
entity_clear_flag2000_and_update_active_counters |
DisableActiveCounters() |
0008:d27e |
entity_set_update_period_and_reschedule |
SetUpdatePeriodAndReschedule() |
Word-list-owning subtype methods
| Address | Current function | Candidate method role |
|---|---|---|
0008:da00 |
entity_word_list_set_0408_terminated |
SetWordList0408Terminated() |
0008:dba3 |
entity_word_list_free_existing |
FreeWordList() |
0008:dbec |
entity_word_list_destroy |
Destroy() |
0008:dc38 |
entity_word_list_ensure_contains |
EnsureWordListContains() |
0008:dcab |
entity_word_list_append_unique |
AppendUniqueWord() |
0008:ddaf |
entity_word_list_remove_value |
RemoveWordValue() |
0008:deea |
entity_word_list_get_at |
GetWordAt() |
0008:df1b |
entity_word_list_set_at |
SetWordAt() |
0008:dfa1 |
entity_word_list_find_unflagged_by_id10 |
FindUnflaggedWordById10() |
Runtime-state subtype methods
| Address | Current function | Candidate method role |
|---|---|---|
000d:7e00 |
entity_dispatch_entry_init_runtime_state |
InitRuntimeState() |
000d:8078 |
entity_dispatch_entry_release_runtime_state |
ReleaseRuntimeState() |
Candidate Virtual Surface
The current evidence does not justify a fully named vtable yet, but some slot use is already real:
+0x14= update callback slot used byentity_call_update_vfunc14+0x28= callback slot used by the periodic and proximity-style dispatch helpers- embedded subobject/member surfaces at
+0x1eand+0x28are also dispatched through helper wrappers infar-call-targets.md
Recommended future vtable note shape:
| Slot offset | Current best role | Evidence quality |
|---|---|---|
+0x14 |
update/refresh callback | High |
+0x28 |
periodic/dispatch callback | High |
| others | unknown/default stubs | Low |
Safe Future Ghidra Modeling Steps
When manual class work starts, the safest order for this family is:
- create class namespace
EntityDispatchEntry - move only the strong base methods first
- create minimal
EntityDispatchEntryBasestruct with the stable fields through+0x18 - create subtype overlay structs for word-list, timed, and runtime-state tails
- create a small provisional vtable for only the verified slots
Do not start by forcing one complete 0x520-byte monolithic class.
Questions To Close Later
- whether
+0x00should be modeled as a literalkindfield in all variants or only in some factory-built subtypes - exact ownership split between the base object and the embedded surfaces at
+0x1eand+0x28 - whether the seg126 startup/display subtype is truly part of the same inheritance family or only shares a lower-level dispatch-entry substrate
- final base-size versus subtype-size boundaries once class namespaces exist in Ghidra
Immediate Next Documentation Value
The next best companion note after this one is a slot-focused SpriteNode virtual table note, because that gives a second family with a cleaner explicit virtual surface and helps calibrate how aggressive the first Ghidra class conversion should be.