bone_sse: add ribbon visibility gate, fix Track A type, add post-processing

Ribbon emitter section (0x7163FC-0x716AA9) — assembly-verified:
- Add visibility byte animation (entry+0xB8 → output+0xE0/0xEC) with crossfade
- Add visibility gate: skip tracks unless (output+0x100 && output+0xEC) or frame 0
- Fix Track A from interpVec3Track to interpFloatTrack (assembly uses *4 not *12)
- Add post-processing 1: output+0x134 = TrackB_Vec3 * (TrackA_float * render_scale_z)
- Add post-processing 2: output+0x140 = TrackD_Vec3 * (TrackC_float * render_scale_z)

Applied to both bone_sse.zig and bone_sse_reference.zig.
This commit is contained in:
MarcelineVQ
2026-03-15 03:01:39 -07:00
parent d6f1904050
commit f97d27f614
2 changed files with 111 additions and 34 deletions
+62 -17
View File
@@ -1726,34 +1726,79 @@ fn ribbonEmitterLoop(this: u32, model_hdr: u32) void {
const data_base = ru32(model_hdr + 0x120);
const out_base = ru32(this + SO.field_200);
const bone_rt_base = ru32(this + SO.bone_rt_base);
const frame_ctr = ru32(this + SO.anim_frame_ctr);
var i: u32 = 0;
while (i < count) : (i += 1) {
const entry = data_base + i * 0xD4; // assembly 0x716ABC: ADD EDI, 0xD4
const output = out_base + i * 0x170; // assembly 0x716AC2: ADD ESI, 0x170
const entry = data_base + i * 0xD4; // asm 0x716ABC: ADD EDI, 0xD4
const output = out_base + i * 0x170; // asm 0x716AC2: ADD ESI, 0x170
const bone_idx = @as(u32, ru16(entry + 2));
const bone_rt = bone_rt_base + bone_idx * 0x118;
// Ribbon emitter tracks from assembly (0x716402-0x716AA9):
// Track 1 (Vec3): gate=entry+0x1C, AnimData=entry+0x10, output=output+0x00
// Assembly: 0x71660D CMP [EDX+0x1C]; 0x716624 LEA ESI,[EAX+0x10]; PUSH EDI
if (ru32(this + SO.anim_frame_ctr) < ru32(entry + 0x1C)) {
// ---- Visibility byte animation (asm 0x7163FC-0x7164F2) ----
// First check: output+0x100 flag gates visibility animation
if (ru32(output + 0x100) != 0) {
// Visibility animation gate: entry+0xC4 (asm 0x71640D)
if (ru32(entry + 0xC4) != 0) {
// findInterpIdx for visibility byte: AD=entry+0xB8, output=output+0xE0
findInterpIdx(this, ru32(bone_rt + BR.prim_time), ru32(bone_rt + BR.prim_track), entry + 0xB8, output + 0xE0);
const vis_idx0 = ru32(output + 0xE0);
const vis_values = ru32(entry + 0xD0); // entry+0xB8+0x18 = AD.keyframe_base
// Both mode 0 and mode != 0 store byte at ofsValues[idx0]
wu8(output + 0xEC, ru8(vis_values + vis_idx0));
// Crossfade only in interp mode (asm 0x71649B-0x7164EC)
if (ri16(entry + 0xB8) != 0) {
if (rf32(bone_rt + BR.blend_weight) != 0.0 and ri16(entry + 0xBA) == -1) {
findInterpIdx(this, ru32(bone_rt + BR.sec_time), ru32(bone_rt + BR.sec_track), entry + 0xB8, output + 0xF0);
wu8(output + 0xFC, ru8(vis_values + ru32(output + 0xF0)));
}
}
}
}
// ---- Visibility gate (asm 0x7164F2-0x716514) ----
// Process tracks if: (output+0x100 set AND visible) OR first frame
const should_process = blk: {
if (ru32(output + 0x100) != 0 and ru8(output + 0xEC) != 0) break :blk true;
if (frame_ctr == 0) break :blk true;
break :blk false;
};
if (!should_process) continue;
// ---- Track A (float): gate=entry+0x38, AD=entry+0x2C, output+0x30 ----
// Assembly: 0x716514 CMP [EDX+0x38], [EBX+0x8C]; uses *4 addressing (float, not Vec3)
if (frame_ctr < ru32(entry + 0x38)) {
interpFloatTrack(this, bone_rt, entry + 0x2C, output + 0x30);
}
// ---- Track B (Vec3): gate=entry+0x1C, AD=entry+0x10, output+0x00 ----
// Assembly: 0x71660D CMP [EDX+0x1C]; uses *12 addressing (Vec3)
if (frame_ctr < ru32(entry + 0x1C)) {
interpVec3Track(this, bone_rt, entry + 0x10, output, ufloat(ru32(bone_rt + BR.blend_weight)));
// Post-processing 1 (asm 0x71678A-0x7167CE):
// output+0x134 = Track_B_Vec3 * (Track_A_float * render_scale_z)
const scale1 = rf32(output + 0x3C) * rf32(this + SO.render_scale_z);
wf32(output + 0x134, rf32(output + 0x0C) * scale1);
wf32(output + 0x138, rf32(output + 0x10) * scale1);
wf32(output + 0x13C, rf32(output + 0x14) * scale1);
}
// Track 2 (Vec3): gate=entry+0x38, AnimData=entry+0x2C, output=output+0x30
// Assembly: 0x716517 CMP [EDX+0x38]; 0x716530 LEA ECX,[EDX+0x2C]; 0x716539 LEA EAX,[EDI+0x30]
if (ru32(this + SO.anim_frame_ctr) < ru32(entry + 0x38)) {
interpVec3Track(this, bone_rt, entry + 0x2C, output + 0x30, ufloat(ru32(bone_rt + BR.blend_weight)));
}
// Track 3 (float): gate=entry+0x70, AnimData=entry+0x64, output=output+0x80
// Assembly: 0x7167D4 CMP [EAX+0x70]; 0x7167F0 LEA ECX,[EDX+0x64]; 0x7167F9 LEA EAX,[EDI+0x80]
if (ru32(this + SO.anim_frame_ctr) < ru32(entry + 0x70)) {
// ---- Track C (float): gate=entry+0x70, AD=entry+0x64, output+0x80 ----
// Assembly: 0x7167D4 CMP [EAX+0x70]
if (frame_ctr < ru32(entry + 0x70)) {
interpFloatTrack(this, bone_rt, entry + 0x64, output + 0x80);
}
// Track 4 (Vec3): gate=entry+0x54, AnimData=entry+0x48, output=output+0x50
// Assembly: 0x7168EE CMP [EDX+0x54]; 0x716905 LEA ECX,[EAX+0x48]; 0x716912 LEA ESI,[EDI+0x50]
if (ru32(this + SO.anim_frame_ctr) < ru32(entry + 0x54)) {
// ---- Track D (Vec3): gate=entry+0x54, AD=entry+0x48, output+0x50 ----
// Assembly: 0x7168EE CMP [EDX+0x54]; uses *12 addressing (Vec3)
if (frame_ctr < ru32(entry + 0x54)) {
interpVec3Track(this, bone_rt, entry + 0x48, output + 0x50, ufloat(ru32(bone_rt + BR.blend_weight)));
// Post-processing 2 (asm 0x716A67-0x716AA6):
// output+0x140 = Track_D_Vec3 * (Track_C_float * render_scale_z)
const scale2 = rf32(output + 0x8C) * rf32(this + SO.render_scale_z);
wf32(output + 0x140, rf32(output + 0x5C) * scale2);
wf32(output + 0x144, rf32(output + 0x60) * scale2);
wf32(output + 0x148, rf32(output + 0x64) * scale2);
}
}
}
+49 -17
View File
@@ -1695,35 +1695,67 @@ fn ribbonEmitterLoop(this: u32, model_hdr: u32) void {
const data_base = ru32(model_hdr + 0x120);
const out_base = ru32(this + SO.field_200);
const bone_rt_base = ru32(this + SO.bone_rt_base);
const frame_ctr = ru32(this + SO.anim_frame_ctr);
var i: u32 = 0;
while (i < count) : (i += 1) {
const entry = data_base + i * 0xD4; // assembly 0x716ABC: ADD EDI, 0xD4
const output = out_base + i * 0x170; // assembly 0x716AC2: ADD ESI, 0x170
const entry = data_base + i * 0xD4; // asm 0x716ABC: ADD EDI, 0xD4
const output = out_base + i * 0x170; // asm 0x716AC2: ADD ESI, 0x170
const bone_idx = @as(u32, ru16(entry + 2));
const bone_rt = bone_rt_base + bone_idx * 0x118;
// Ribbon emitter tracks from assembly (0x716402-0x716AA9):
// ---- Visibility byte animation (asm 0x7163FC-0x7164F2) ----
if (ru32(output + 0x100) != 0) {
if (ru32(entry + 0xC4) != 0) {
findInterpIdx(this, ru32(bone_rt + BR.prim_time), ru32(bone_rt + BR.prim_track), entry + 0xB8, output + 0xE0);
const vis_idx0 = ru32(output + 0xE0);
const vis_values = ru32(entry + 0xD0); // entry+0xB8+0x18 = AD.keyframe_base
wu8(output + 0xEC, ru8(vis_values + vis_idx0));
if (ri16(entry + 0xB8) != 0) {
if (rf32(bone_rt + BR.blend_weight) != 0.0 and ri16(entry + 0xBA) == -1) {
findInterpIdx(this, ru32(bone_rt + BR.sec_time), ru32(bone_rt + BR.sec_track), entry + 0xB8, output + 0xF0);
wu8(output + 0xFC, ru8(vis_values + ru32(output + 0xF0)));
}
}
}
}
// Track 1 (Vec3): gate=entry+0x1C, AnimData=entry+0x10, output=output+0x00
// Assembly: 0x71660D CMP [EDX+0x1C]; 0x716624 LEA ESI,[EAX+0x10]; PUSH EDI
if (ru32(this + SO.anim_frame_ctr) < ru32(entry + 0x1C)) {
// ---- Visibility gate (asm 0x7164F2-0x716514) ----
const should_process = blk: {
if (ru32(output + 0x100) != 0 and ru8(output + 0xEC) != 0) break :blk true;
if (frame_ctr == 0) break :blk true;
break :blk false;
};
if (!should_process) continue;
// ---- Track A (float): gate=entry+0x38, AD=entry+0x2C, output+0x30 ----
if (frame_ctr < ru32(entry + 0x38)) {
interpFloatTrack(this, bone_rt, entry + 0x2C, output + 0x30);
}
// ---- Track B (Vec3): gate=entry+0x1C, AD=entry+0x10, output+0x00 ----
if (frame_ctr < ru32(entry + 0x1C)) {
interpVec3Track(this, bone_rt, entry + 0x10, output, ufloat(ru32(bone_rt + BR.blend_weight)));
// Post-processing 1 (asm 0x71678A-0x7167CE)
const scale1 = rf32(output + 0x3C) * rf32(this + SO.render_scale_z);
wf32(output + 0x134, rf32(output + 0x0C) * scale1);
wf32(output + 0x138, rf32(output + 0x10) * scale1);
wf32(output + 0x13C, rf32(output + 0x14) * scale1);
}
// Track 2 (Vec3): gate=entry+0x38, AnimData=entry+0x2C, output=output+0x30
// Assembly: 0x716517 CMP [EDX+0x38]; 0x716530 LEA ECX,[EDX+0x2C]; 0x716539 LEA EAX,[EDI+0x30]
if (ru32(this + SO.anim_frame_ctr) < ru32(entry + 0x38)) {
interpVec3Track(this, bone_rt, entry + 0x2C, output + 0x30, ufloat(ru32(bone_rt + BR.blend_weight)));
}
// Track 3 (float): gate=entry+0x70, AnimData=entry+0x64, output=output+0x80
// Assembly: 0x7167D4 CMP [EAX+0x70]; 0x7167F0 LEA ECX,[EDX+0x64]; 0x7167F9 LEA EAX,[EDI+0x80]
if (ru32(this + SO.anim_frame_ctr) < ru32(entry + 0x70)) {
// ---- Track C (float): gate=entry+0x70, AD=entry+0x64, output+0x80 ----
if (frame_ctr < ru32(entry + 0x70)) {
interpFloatTrack(this, bone_rt, entry + 0x64, output + 0x80);
}
// Track 4 (Vec3): gate=entry+0x54, AnimData=entry+0x48, output=output+0x50
// Assembly: 0x7168EE CMP [EDX+0x54]; 0x716905 LEA ECX,[EAX+0x48]; 0x716912 LEA ESI,[EDI+0x50]
if (ru32(this + SO.anim_frame_ctr) < ru32(entry + 0x54)) {
// ---- Track D (Vec3): gate=entry+0x54, AD=entry+0x48, output+0x50 ----
if (frame_ctr < ru32(entry + 0x54)) {
interpVec3Track(this, bone_rt, entry + 0x48, output + 0x50, ufloat(ru32(bone_rt + BR.blend_weight)));
// Post-processing 2 (asm 0x716A67-0x716AA6)
const scale2 = rf32(output + 0x8C) * rf32(this + SO.render_scale_z);
wf32(output + 0x140, rf32(output + 0x5C) * scale2);
wf32(output + 0x144, rf32(output + 0x60) * scale2);
wf32(output + 0x148, rf32(output + 0x64) * scale2);
}
}
}