WIP: bone_sse SSE reimplementation of transformMatrix4x4 + OnWorldUpdate frame counter

bone_sse.zig: Full standalone SSE reimplementation of the 17703-byte bone
transform engine (0x714260). All helper functions reimplemented inline
(findInterpolationIndices, interpolateAnimationKeyframes, scaleMatrix3x3,
ApplyTranslation, rotateByQuaternion). Currently disabled (A/B dispatch
commented out) due to NULL ofsValues crash under investigation.

transform44.zig: Add OnWorldUpdate (0x482EA0) hook for true per-frame
counting. Previous frame counter used executeSceneRenderPass which fires
multiple times per frame (shadows, reflections, spell effects), causing
A/B periods to be as short as 0.5s during combat instead of ~30s.

SCENEOBJECT_OFFSETS.md: Complete assembly-verified field offset map
(51 offsets) extracted from [EBX+N] patterns in transformMatrix4x4.
Corrects bone_runtime_base from +0x80 to +0x090.
This commit is contained in:
MarcelineVQ
2026-03-14 00:22:36 -07:00
parent 55735d7b9e
commit 0833d23e28
3 changed files with 1758 additions and 60 deletions
+112
View File
@@ -0,0 +1,112 @@
# SceneObject Field Offsets -- Assembly-Verified
Extracted from `[EBX+N]` patterns in transformMatrix4x4 (0x714260, 17703 bytes).
EBX = this pointer (set at prologue: `MOV EBX, ECX`).
**WARNING**: Ghidra struct field names like `field_0x184` do NOT encode the actual
byte offset. The Ghidra struct has been modified over time and field names are
inconsistent with their positions. ONLY assembly `[EBX+N]` is authoritative.
**WARNING**: RESEARCH.md previously claimed bone_runtime_base at +0x80. Assembly
confirms it is actually at +0x090.
## Complete Offset Map (51 unique offsets)
| Offset | Type | Semantic Name | Evidence |
|--------|------|---------------|----------|
| +0x010 | ptr | model_data_ptr | NULL check for early bail |
| +0x02C | ptr | animation_context_ptr | +0xC=timestamp, +0x10=sync_value |
| +0x030 | ptr | model_container_ptr | +0x130=M2 header. Ghidra SWAPS label as "ptr_at_2c" |
| +0x040 | u32 | transform_sync_value | Compared with *(anim_ctx+0x10) |
| +0x04C | u32 | search_data_base_ptr | Previous timestamp for delta computation |
| +0x050 | u32 | emitter_enable_flag | Written 0 or 1 |
| +0x064 | ptr | search_data_count / gs_values_ptr | Pointer to global sequence value array |
| +0x068 | u32 | gs_time_base / position_x | Subtracted from timestamp for GS modulo |
| +0x084 | u32 | child_objects_padding | len_sq of world_transform translation row |
| +0x08C | u32 | animation_frame_counter | CMP against bone def limits for skip checks |
| +0x090 | ptr | bone_runtime_base | Array of 0x118-byte per-bone runtime structs |
| +0x094 | ptr | bone_out_ptr / transform_vec2_x | Output bone matrices (bone_idx * 0x40) |
| +0x0A0 | ptr | tex_anim_out / transform_vec2_y | Texture animation output |
| +0x0A8 | ptr | color_anim_out / transform_vec2_z | Color animation output |
| +0x0AC | f32 | scale_factor_1 | |
| +0x0B0 | f32 | scale_factor_2 | |
| +0x0B4 | f32 | scale_factor_3 | |
| +0x0BC | ptr | particle_shader_arg1 | LEA, passed to initParticlePixelShaderGeneration |
| +0x0FC | f32 | billboard_row0[0] / model_attachment_list1 | Camera forward direction X |
| +0x100 | f32 | billboard_row0[1] / unknown_0xe4 | Camera forward Y |
| +0x104 | f32 | billboard_row0[2] / attachment_list1 | Camera forward Z |
| +0x108 | f32 | billboard_row0[3] | |
| +0x10C | f32[16] | world_transform_matrix | Billboard/camera reference matrix rows 1-3 |
| +0x17C | u32 | field_17c | Copied from emitter_ctx+0x17C |
| +0x180 | f32 | field_180 | Scale multiplier for render_scale_z |
| +0x184 | f32 | field_184 | Per-axis scale factor (X) |
| +0x188 | f32 | field_188 | Per-axis scale factor (Y) / emitter intensity |
| +0x18C | f32 | global_scale_factor | Per-axis scale factor (Z) |
| +0x190 | f32 | field_190 | Used in offset computation (FADD with param_4[0]) |
| +0x194 | f32 | render_scale_x | Added to param_4[1] |
| +0x198 | f32 | render_scale_y | Added to param_4[2] |
| +0x19C | f32 | render_scale_z | Computed: param_5 * field_180 |
| +0x1A0 | f32[3] | world_position | Vec3, passed as param_3 to child recursive calls |
| +0x1AC | f32[3] | render_priority | Vec3, passed as param_4 to child recursive calls |
| +0x1C8 | ptr | hierarchy_ptr / final_world_pos_z | Attachment hierarchy data |
| +0x1CC | ptr | emitter_context_ptr | Emitter/particle context |
| +0x1D8 | u32 | field_1d8 | Read in emitter setup |
| +0x1DC | u32 | hierarchy_idx | Linked list of child SceneObjects |
| +0x200 | ptr | ribbon_emitter_out | Ribbon emitter output base |
| +0x3C4 | ptr | particle_data1 | Particle emitter data array 1 |
| +0x3C8 | ptr | particle_data2 | Particle emitter data array 2 |
| +0x3D0 | ptr | particle_data3 | Particle emitter data array 3 |
| +0x3D4 | ptr | particle_data4 | Particle emitter data array 4 |
| +0x3D8 | u32 | additional_remaining | Written 0, then OR'd with particle active flags |
## world_transform_matrix Detail (+0x10C, float[16])
Row-major 4x4. Only 3x3 rotation + translation accessed (columns 3 are 0):
| Index | Offset | Used for |
|-------|--------|----------|
| [0] | +0x10C | Row 1 col 0 (billboard) |
| [1] | +0x110 | Row 1 col 1 |
| [2] | +0x114 | Row 1 col 2 |
| [4] | +0x11C | Row 2 col 0 |
| [5] | +0x120 | Row 2 col 1 |
| [6] | +0x124 | Row 2 col 2 |
| [8] | +0x12C | Translation X (also used for child_padding len_sq) |
| [9] | +0x130 | Translation Y |
| [10]| +0x134 | Translation Z |
## Model Header Offsets (from *(*(this+0x30) + 0x130))
| Offset | Field |
|--------|-------|
| +0x14 | Global sequence count |
| +0x18 | Global sequence durations array ptr |
| +0x20 | Animation lookup table |
| +0x34 | Bone count |
| +0x38 | Bone definition array ptr |
| +0x54 | Texture animation count |
| +0x58 | Texture animation data ptr |
| +0x64 | Color animation count |
| +0x68 | Color animation data ptr |
| +0x6C | Bone keyframe count |
| +0x70 | Bone keyframe data ptr |
| +0x74 | Bone keyframe count 2 |
| +0x78 | Bone keyframe data ptr 2 |
| +0x104 | Attachment count |
| +0x108 | Attachment data ptr |
| +0x11C | Ribbon emitter count |
| +0x120 | Ribbon emitter data ptr |
| +0x124 | Particle emitter count |
| +0x128 | Particle emitter data ptr |
| +0x134 | Additional particle count |
| +0x138 | Additional particle data ptr |
| +0x13C | Final particle section count |
| +0x140 | Final particle section data ptr |
## Method
1. Ghidra headless script extracted all `[EBX + offset]` from function instructions
2. Cross-referenced with decompilation flow to assign semantic meaning
3. Assembly disassembly at key addresses confirmed field mapping
4. Ghidra struct definition found to be INCONSISTENT with assembly (field names
shifted from their actual byte positions) -- do not use Ghidra struct for offsets