Crusader_Decomp/docs/sprite-node-class-layout.md
2026-04-05 18:27:09 +02:00

7.1 KiB

SpriteNode Class Layout

Purpose

This note captures the current class-level read of the SpriteNode family so later Ghidra class work can move quickly and conservatively.

Compared with EntityDispatchEntry, this family has a cleaner explicit virtual/event-dispatch surface and a more bounded ownership model. That makes it an excellent second pilot for class lifting.

Current Best Class-Level Read

SpriteNode is a tree-based render/UI node family with:

  • child-chain ownership
  • accumulated position and bounds propagation
  • dirty-state tracking
  • central event dispatch through a small virtual surface
  • destructor ownership over child nodes and global focus state

This already looks much closer to an ordinary C++ object family than many gameplay-side structures.

Strongest Evidence Anchors

Destructor

000b:326e sprite_node_destroy

Current best read:

  • destructor-style path
  • sets vtable ptr to 0x501a
  • clears global focus pointer [0x4fd0:0x4fd2] if self
  • releases child nodes
  • frees object memory through mem_free

This is the strongest current single proof that SpriteNode should be lifted as an owned object family.

Event dispatch

000b:3ab2 sprite_node_dispatch_event

Current best read:

  • large event dispatch switch
  • checks event types 2/4/8/0x100
  • updates global focus pointer at [0x4fd0:0x4fd2]
  • dispatches through virtual slots +0x14, +0x18, +0x20, +0x24

This is the strongest current proof of a stable virtual method surface.

Dirty/update family

  • 000b:3380 sprite_node_is_dirty
  • 000b:33a6 sprite_node_mark_dirty
  • 000b:40ee sprite_node_update_and_dispatch

These show that node state changes and redraw/update dispatch are methods on the same family, not just free helper functions wandering across unrelated data.

Recursive tree helpers

  • 000a:b988 sprite_node_get_or_traverse
  • 000b:358d sprite_tree_accumulate_pos
  • 000b:3a00 sprite_tree_sum_x_offset
  • 000b:3a35 sprite_tree_sum_y_offset

These are strong evidence for a child-linked tree object with inherited coordinate accumulation.

Candidate Layout

This layout is intentionally conservative.

Offset Current name Confidence Current meaning
+0x19/+0x1b child_or_next_ptr High Child-chain pointer pair used by recursive traversal and offset accumulation.
+0x21 local_x_offset High Summed by sprite_tree_sum_x_offset / accumulate helpers.
+0x23 local_y_offset High Summed by sprite_tree_sum_y_offset / accumulate helpers.
+0x29 dirty_flags High Checked by sprite_node_is_dirty; manipulated by mark/update paths.
+0x17e redraw_flag Medium Cleared by sprite_clear_redraw_flag; likely a subtype or larger-object field tied to one SpriteNode family variant.

Layout Caveat

The current notes likely mix a compact core SpriteNode with one or more larger derived UI/display objects. The evidence for +0x17e strongly suggests there are bigger family members or wrapper objects in the same virtual ecosystem.

So the safe future modeling strategy is:

  • define a small SpriteNodeBase
  • keep larger UI/display fields in derived or sibling structs until more offsets are closed

Candidate Method Map

Strong instance methods

Address Current function Candidate method role
000b:326e sprite_node_destroy Destroy()
000b:3380 sprite_node_is_dirty IsDirty()
000b:33a6 sprite_node_mark_dirty MarkDirty()
000b:3ab2 sprite_node_dispatch_event DispatchEvent()
000b:40ee sprite_node_update_and_dispatch UpdateAndDispatch()
000a:b988 sprite_node_get_or_traverse GetOrTraverse()

Strong family-local helpers that may remain free/static

Address Current function Why it may stay non-method
000b:3a00 sprite_tree_sum_x_offset Pure recursive accumulation helper; method status depends on later decompile readability.
000b:3a35 sprite_tree_sum_y_offset Same as above.
000b:330c sprite_tree_dispatch_wrapper Looks like a pure thunk wrapper rather than a meaningful source-level method.
000b:3362 sprite_tree_unwind_check Stack-segment guard helper; probably not worth presenting as a class method.

Candidate Virtual Slot Map

The currently verified slots are already good enough for a first typed vtable.

Slot offset Current best role Evidence
+0x14 event handler A sprite_node_dispatch_event dispatches here for one event class
+0x18 event handler B same dispatcher
+0x20 event handler C same dispatcher
+0x24 event handler D same dispatcher

The seg091 default-slot helpers are also useful evidence:

  • 000a:7b44, 000a:7b49, 000a:7b53, 000a:7b4e, 000a:7b78, 000a:7b7d, 000a:7b30, 000a:7b3f, 000a:7b35, 000a:7b3a
  • 000a:7b58 returns zero and behaves like a default no-op boolean slot
  • 000a:7b5f is a forwarding trampoline slot

These likely belong to one or more shared/default node vtables and should be preserved as vtable evidence even if they never become pretty source-level methods.

Ownership And Global State

Focus/global state

Global focus pointer [0x4fd0:0x4fd2] is updated in the dispatch family and cleared in the destructor.

That gives the family a real interaction with global UI focus/state, but the key point for class work is simpler:

  • focus ownership is tied to the node family itself
  • this is not just an arbitrary free helper changing global UI state

Child ownership

The destructor and recursive sum/traverse helpers strongly suggest real child ownership or at least managed child linkage.

That means later class modeling should preserve a node/tree mental model rather than flattening everything into stand-alone display items.

Candidate Ghidra Modeling Plan

When class authoring begins, the safest sequence for this family is:

  1. create class namespace SpriteNode
  2. move Destroy, IsDirty, MarkDirty, DispatchEvent, UpdateAndDispatch, and GetOrTraverse first
  3. create minimal SpriteNodeBase struct with the stable offsets around +0x19, +0x21, +0x23, and +0x29
  4. create provisional vtable with slots +0x14, +0x18, +0x20, +0x24
  5. keep recursive tree helpers outside the class until decompiler output shows they benefit from becoming methods

Open Questions

  • exact root vtable address or addresses for the main SpriteNode family
  • whether the +0x17e redraw flag belongs to a derived display node rather than the compact base node
  • which event-code cases map to which slot semantically beyond the current A/B/C/D placeholder naming
  • whether sprite_tree_accumulate_pos should become a class method, a static helper, or a separate geometry utility

Immediate Follow-Up Value

The most useful next companion work after this note is not more sprite detail by itself. It is the rebuild-ABI note, because once the first few class families are documented this well, the next real risk is drifting away from the original memory and calling-convention model before any code is emitted.