# 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.