From d9d2e41eb41603fe316da27d04cf1f05c7949b32 Mon Sep 17 00:00:00 2001 From: MarcelineVQ Date: Mon, 16 Mar 2026 15:37:28 -0700 Subject: [PATCH] bone_sse: return InterpResult in registers, eliminate store-forward latency findInterpIdx now returns {idx0, idx1, t} as a struct instead of writing all three to the output buffer. Only output[0] is written for next-frame cache persistence. All 29 call sites updated to use returned values. 3574 cycles (-14% vs 4176 baseline), was 3841 (-8%). Parity PASS. --- src/transform44/bone_sse.zig | 253 ++++++++++++++++------------------- 1 file changed, 118 insertions(+), 135 deletions(-) diff --git a/src/transform44/bone_sse.zig b/src/transform44/bone_sse.zig index eb5779f..dc438db 100644 --- a/src/transform44/bone_sse.zig +++ b/src/transform44/bone_sse.zig @@ -471,10 +471,17 @@ fn isParticleBufferNotEmpty(ptr: u32) bool { return false; } +const InterpResult = struct { + idx0: u32, + idx1: u32, + t: f32, +}; + /// findInterpIdx: temporal-coherence keyframe search. /// Reimplementation of game function at 0x713D50 (334 bytes). /// Assembly-verified against t44_helpers_asm.txt. -inline fn findInterpIdx(this: u32, search_value: u32, track_index: u32, anim_data: u32, output: u32) void { +/// Returns indices and t in registers; only writes output[0] for next-frame cache persistence. +inline fn findInterpIdx(this: u32, search_value: u32, track_index: u32, anim_data: u32, output: u32) InterpResult { const n_ranges = ru32(anim_data + AD.track_count_flag); // Range selection: [start, last] not [start, count] @@ -491,9 +498,7 @@ inline fn findInterpIdx(this: u32, search_value: u32, track_index: u32, anim_dat if (range_start >= range_last) { wu32(output, range_start); - wu32(output + 4, range_start); - wu32(output + 8, 0); - return; + return .{ .idx0 = range_start, .idx1 = range_start, .t = 0.0 }; } // Global sequence override: CMP AX,0xFFFF @@ -570,9 +575,7 @@ inline fn findInterpIdx(this: u32, search_value: u32, track_index: u32, anim_dat if (next >= kf_count) { wu32(output, result); - wu32(output + 4, result); - wu32(output + 8, 0); - return; + return .{ .idx0 = result, .idx1 = result, .t = 0.0 }; } // Interpolation factor: FILD qword / FIDIV dword @@ -583,8 +586,7 @@ inline fn findInterpIdx(this: u32, search_value: u32, track_index: u32, anim_dat const t: f32 = @as(f32, @floatFromInt(numer)) / @as(f32, @floatFromInt(@as(i32, @bitCast(denom)))); wu32(output, result); - wu32(output + 4, next); - wu32(output + 8, @bitCast(t)); + return .{ .idx0 = result, .idx1 = next, .t = t }; } // ============================================================================= @@ -598,12 +600,12 @@ inline fn findInterpIdx(this: u32, search_value: u32, track_index: u32, anim_dat /// Quaternion keyframe interpolation — replaces game's 0x713EA0. /// Assembly-verified: stride 16 (SHL EAX,4), values are 4×float, not CompQuat. inline fn interpAnimKF(this: u32, bone_rt: u32, anim_data: u32, output: u32) void { - findInterpIdx(this, ru32(bone_rt + BR.prim_time), ru32(bone_rt + BR.prim_track), anim_data, output); + const r = findInterpIdx(this, ru32(bone_rt + BR.prim_time), ru32(bone_rt + BR.prim_track), anim_data, output); const mode = ri16(anim_data + AD.interp_mode); const kf_base = ru32(anim_data + AD.keyframe_base); if (mode == 0) { - const src = kf_base + ru32(output) * 16; + const src = kf_base + r.idx0 * 16; wu32(output + 0x0C, ru32(src)); wu32(output + 0x10, ru32(src + 4)); wu32(output + 0x14, ru32(src + 8)); @@ -611,27 +613,25 @@ inline fn interpAnimKF(this: u32, bone_rt: u32, anim_data: u32, output: u32) voi return; } - const t = ufloat(ru32(output + 8)); - const src0 = kf_base + ru32(output) * 16; - const src1 = kf_base + ru32(output + 4) * 16; + const src0 = kf_base + r.idx0 * 16; + const src1 = kf_base + r.idx1 * 16; inline for (0..4) |i| { const off: u32 = @intCast(i * 4); const a = rf32(src0 + off); const b = rf32(src1 + off); - wf32(output + 0x0C + off, @mulAdd(f32, b - a, t, a)); + wf32(output + 0x0C + off, @mulAdd(f32, b - a, r.t, a)); } // Crossfade if (rf32(bone_rt + BR.blend_weight) != 0.0 and ri16(anim_data + AD.time_index) == -1) { - findInterpIdx(this, ru32(bone_rt + BR.sec_time), ru32(bone_rt + BR.sec_track), anim_data, output + 0x1C); - const st = ufloat(ru32(output + 0x24)); - const ssrc0 = kf_base + ru32(output + 0x1C) * 16; - const ssrc1 = kf_base + ru32(output + 0x20) * 16; + const sr = findInterpIdx(this, ru32(bone_rt + BR.sec_time), ru32(bone_rt + BR.sec_track), anim_data, output + 0x1C); + const ssrc0 = kf_base + sr.idx0 * 16; + const ssrc1 = kf_base + sr.idx1 * 16; inline for (0..4) |i| { const off: u32 = @intCast(i * 4); const a = rf32(ssrc0 + off); const b = rf32(ssrc1 + off); - wf32(output + 0x28 + off, @mulAdd(f32, b - a, st, a)); + wf32(output + 0x28 + off, @mulAdd(f32, b - a, sr.t, a)); } // Blend: primary = primary + (secondary - primary) * weight const bw = rf32(bone_rt + BR.blend_weight); @@ -681,35 +681,33 @@ inline fn interpVec3Track( output: u32, blend_weight: f32, ) void { - findInterpIdx(this, ru32(bone_rt + BR.prim_time), ru32(bone_rt + BR.prim_track), anim_data, output); + const r = findInterpIdx(this, ru32(bone_rt + BR.prim_time), ru32(bone_rt + BR.prim_track), anim_data, output); const interp_mode = ri16(anim_data + AD.interp_mode); const kf_base = ru32(anim_data + AD.keyframe_base); if (interp_mode == 0) { // No interpolation — copy keyframe directly - const src = kf_base + ru32(output) * 0xC; + const src = kf_base + r.idx0 * 0xC; wu32(output + 0x0C, ru32(src)); wu32(output + 0x10, ru32(src + 4)); wu32(output + 0x14, ru32(src + 8)); return; } - const t = ufloat(ru32(output + 8)); - const a = kf_base + ru32(output) * 0xC; - const b = kf_base + ru32(output + 4) * 0xC; - const result = lerpVec3(a, b, t); + const a = kf_base + r.idx0 * 0xC; + const b = kf_base + r.idx1 * 0xC; + const result = lerpVec3(a, b, r.t); wu32(output + 0x0C, fbits(result[0])); wu32(output + 0x10, fbits(result[1])); wu32(output + 0x14, fbits(result[2])); // Crossfade blend if (blend_weight != 0.0 and ri16(anim_data + AD.time_index) == -1) { - findInterpIdx(this, ru32(bone_rt + BR.sec_time), ru32(bone_rt + BR.sec_track), anim_data, output + 0x18); - const st = ufloat(ru32(output + 0x20)); - const sa = kf_base + ru32(output + 0x18) * 0xC; - const sb = kf_base + ru32(output + 0x1C) * 0xC; - const sec = lerpVec3(sa, sb, st); + const sr = findInterpIdx(this, ru32(bone_rt + BR.sec_time), ru32(bone_rt + BR.sec_track), anim_data, output + 0x18); + const sa = kf_base + sr.idx0 * 0xC; + const sb = kf_base + sr.idx1 * 0xC; + const sec = lerpVec3(sa, sb, sr.t); wu32(output + 0x24, fbits(sec[0])); wu32(output + 0x28, fbits(sec[1])); wu32(output + 0x2C, fbits(sec[2])); @@ -733,28 +731,26 @@ inline fn interpFloatTrack( output: u32, blend_weight: f32, ) void { - findInterpIdx(this, ru32(bone_rt + BR.prim_time), ru32(bone_rt + BR.prim_track), anim_data, output); + const r = findInterpIdx(this, ru32(bone_rt + BR.prim_time), ru32(bone_rt + BR.prim_track), anim_data, output); const interp_mode = ri16(anim_data + AD.interp_mode); const kf_base = ru32(anim_data + AD.keyframe_base); if (interp_mode == 0) { - wu32(output + 0x0C, ru32(kf_base + ru32(output) * 4)); + wu32(output + 0x0C, ru32(kf_base + r.idx0 * 4)); return; } - const t = ufloat(ru32(output + 8)); - const a = rf32(kf_base + ru32(output) * 4); - const b = rf32(kf_base + ru32(output + 4) * 4); - wf32(output + 0x0C, @mulAdd(f32, b - a, t, a)); + const a = rf32(kf_base + r.idx0 * 4); + const b = rf32(kf_base + r.idx1 * 4); + wf32(output + 0x0C, @mulAdd(f32, b - a, r.t, a)); // Crossfade — only for bone loop callers (particles pass 0.0) if (blend_weight != 0.0 and ri16(anim_data + AD.time_index) == -1) { - findInterpIdx(this, ru32(bone_rt + BR.sec_time), ru32(bone_rt + BR.sec_track), anim_data, output + 0x10); - const st = ufloat(ru32(output + 0x18)); - const sa = rf32(kf_base + ru32(output + 0x10) * 4); - const sb = rf32(kf_base + ru32(output + 0x14) * 4); - const sec = (sb - sa) * st + sa; + const sr = findInterpIdx(this, ru32(bone_rt + BR.sec_time), ru32(bone_rt + BR.sec_track), anim_data, output + 0x10); + const sa = rf32(kf_base + sr.idx0 * 4); + const sb = rf32(kf_base + sr.idx1 * 4); + const sec = (sb - sa) * sr.t + sa; wu32(output + 0x1C, fbits(sec)); const pri = ufloat(ru32(output + 0x0C)); wf32(output + 0x0C, @mulAdd(f32, sec - pri, blend_weight, pri)); @@ -789,37 +785,36 @@ inline fn bezierBasis(t: f32) struct { b0: f32, b1: f32, b2: f32, b3: f32 } { } inline fn interpVec3Track36(this: u32, bone_rt_base: u32, anim_data: u32, output: u32) void { - findInterpIdx(this, ru32(bone_rt_base + 0x98), ru32(bone_rt_base + 0x9C), anim_data, output); + const r = findInterpIdx(this, ru32(bone_rt_base + 0x98), ru32(bone_rt_base + 0x9C), anim_data, output); const mode = ri16(anim_data + AD.interp_mode); const kf_base = ru32(anim_data + AD.keyframe_base); if (mode == 0) { - const src = kf_base + ru32(output) * 36; + const src = kf_base + r.idx0 * 36; wu32(output + 0x0C, ru32(src)); wu32(output + 0x10, ru32(src + 4)); wu32(output + 0x14, ru32(src + 8)); return; } - const t = ufloat(ru32(output + 8)); - const kf_a = kf_base + ru32(output) * 36; - const kf_b = kf_base + ru32(output + 4) * 36; + const kf_a = kf_base + r.idx0 * 36; + const kf_b = kf_base + r.idx1 * 36; if (mode == 1) { - const result = lerpVec3(kf_a, kf_b, t); + const result = lerpVec3(kf_a, kf_b, r.t); wu32(output + 0x0C, fbits(result[0])); wu32(output + 0x10, fbits(result[1])); wu32(output + 0x14, fbits(result[2])); } else if (mode == 3) { - const h = hermiteBasis(t); + const h = hermiteBasis(r.t); var i: u32 = 0; while (i < 3) : (i += 1) { const off = i * 4; wf32(output + 0x0C + off, h.h1 * rf32(kf_a + off) + h.h2 * rf32(kf_a + 0x18 + off) + h.h3 * rf32(kf_b + off) + h.h4 * rf32(kf_b + 0x0C + off)); } } else if (mode == 2) { - const b = bezierBasis(t); + const b = bezierBasis(r.t); var i: u32 = 0; while (i < 3) : (i += 1) { const off = i * 4; @@ -829,27 +824,26 @@ inline fn interpVec3Track36(this: u32, bone_rt_base: u32, anim_data: u32, output const blend = rf32(bone_rt_base + BR.blend_weight); if (blend != 0.0 and ri16(anim_data + AD.time_index) == -1) { - findInterpIdx(this, ru32(bone_rt_base + BR.sec_time), ru32(bone_rt_base + BR.sec_track), anim_data, output + 0x18); + const sr = findInterpIdx(this, ru32(bone_rt_base + BR.sec_time), ru32(bone_rt_base + BR.sec_track), anim_data, output + 0x18); - const st = ufloat(ru32(output + 0x20)); - const skf_a = kf_base + ru32(output + 0x18) * 36; - const skf_b = kf_base + ru32(output + 0x1C) * 36; + const skf_a = kf_base + sr.idx0 * 36; + const skf_b = kf_base + sr.idx1 * 36; const smode = ri16(anim_data + AD.interp_mode); if (smode == 1) { - const sec = lerpVec3(skf_a, skf_b, st); + const sec = lerpVec3(skf_a, skf_b, sr.t); wu32(output + 0x24, fbits(sec[0])); wu32(output + 0x28, fbits(sec[1])); wu32(output + 0x2C, fbits(sec[2])); } else if (smode == 3) { - const h = hermiteBasis(st); + const h = hermiteBasis(sr.t); var i: u32 = 0; while (i < 3) : (i += 1) { const off = i * 4; wf32(output + 0x24 + off, h.h1 * rf32(skf_a + off) + h.h2 * rf32(skf_a + 0x18 + off) + h.h3 * rf32(skf_b + off) + h.h4 * rf32(skf_b + 0x0C + off)); } } else if (smode == 2) { - const b = bezierBasis(st); + const b = bezierBasis(sr.t); var i: u32 = 0; while (i < 3) : (i += 1) { const off = i * 4; @@ -872,49 +866,47 @@ inline fn interpVec3Track36(this: u32, bone_rt_base: u32, anim_data: u32, output } inline fn interpFloatTrack12(this: u32, bone_rt_base: u32, anim_data: u32, output: u32) void { - findInterpIdx(this, ru32(bone_rt_base + 0x98), ru32(bone_rt_base + 0x9C), anim_data, output); + const r = findInterpIdx(this, ru32(bone_rt_base + 0x98), ru32(bone_rt_base + 0x9C), anim_data, output); const mode = ri16(anim_data + AD.interp_mode); const kf_base = ru32(anim_data + AD.keyframe_base); if (mode == 0) { - wu32(output + 0x0C, ru32(kf_base + ru32(output) * 12)); + wu32(output + 0x0C, ru32(kf_base + r.idx0 * 12)); return; } - const t = ufloat(ru32(output + 8)); - const kf_a = kf_base + ru32(output) * 12; - const kf_b = kf_base + ru32(output + 4) * 12; + const kf_a = kf_base + r.idx0 * 12; + const kf_b = kf_base + r.idx1 * 12; if (mode == 1) { const a = rf32(kf_a); const b = rf32(kf_b); - wf32(output + 0x0C, @mulAdd(f32, b - a, t, a)); + wf32(output + 0x0C, @mulAdd(f32, b - a, r.t, a)); } else if (mode == 3) { - const h = hermiteBasis(t); + const h = hermiteBasis(r.t); wf32(output + 0x0C, h.h1 * rf32(kf_a) + h.h2 * rf32(kf_a + 0x08) + h.h3 * rf32(kf_b) + h.h4 * rf32(kf_b + 0x04)); } else if (mode == 2) { - const b = bezierBasis(t); + const b = bezierBasis(r.t); wf32(output + 0x0C, b.b0 * rf32(kf_a) + b.b1 * rf32(kf_a + 0x08) + b.b2 * rf32(kf_b + 0x04) + b.b3 * rf32(kf_b)); } else {} // Unknown mode: skip primary interp, fall through to crossfade const blend = rf32(bone_rt_base + BR.blend_weight); if (blend != 0.0 and ri16(anim_data + AD.time_index) == -1) { - findInterpIdx(this, ru32(bone_rt_base + BR.sec_time), ru32(bone_rt_base + BR.sec_track), anim_data, output + 0x10); + const sr = findInterpIdx(this, ru32(bone_rt_base + BR.sec_time), ru32(bone_rt_base + BR.sec_track), anim_data, output + 0x10); - const st = ufloat(ru32(output + 0x18)); - const skf_a = kf_base + ru32(output + 0x10) * 12; - const skf_b = kf_base + ru32(output + 0x14) * 12; + const skf_a = kf_base + sr.idx0 * 12; + const skf_b = kf_base + sr.idx1 * 12; const smode = ri16(anim_data + AD.interp_mode); var sec: f32 = undefined; if (smode == 1) { - sec = (rf32(skf_b) - rf32(skf_a)) * st + rf32(skf_a); + sec = (rf32(skf_b) - rf32(skf_a)) * sr.t + rf32(skf_a); } else if (smode == 3) { - const h = hermiteBasis(st); + const h = hermiteBasis(sr.t); sec = h.h1 * rf32(skf_a) + h.h2 * rf32(skf_a + 0x08) + h.h3 * rf32(skf_b) + h.h4 * rf32(skf_b + 0x04); } else if (smode == 2) { - const bz = bezierBasis(st); + const bz = bezierBasis(sr.t); sec = bz.b0 * rf32(skf_a) + bz.b1 * rf32(skf_a + 0x08) + bz.b2 * rf32(skf_b + 0x04) + bz.b3 * rf32(skf_b); } else { sec = rf32(skf_a); @@ -931,28 +923,26 @@ inline fn interpFloatTrack12(this: u32, bone_rt_base: u32, anim_data: u32, outpu // ============================================================================= inline fn getInterpolatedFloat(this: u32, bone_rt_addr: u32, anim_data_short_ptr: u32, output: u32) void { - findInterpIdx(this, ru32(bone_rt_addr + 0x98), ru32(bone_rt_addr + 0x9C), anim_data_short_ptr, output); + const r = findInterpIdx(this, ru32(bone_rt_addr + 0x98), ru32(bone_rt_addr + 0x9C), anim_data_short_ptr, output); const interp_mode = ri16(anim_data_short_ptr); const kf_base = ru32(anim_data_short_ptr + 0x18); if (interp_mode == 0) { - wu32(output + 0x0C, ru32(kf_base + ru32(output) * 4)); + wu32(output + 0x0C, ru32(kf_base + r.idx0 * 4)); return; } - const t = ufloat(ru32(output + 8)); - const a = rf32(kf_base + ru32(output) * 4); - const b = rf32(kf_base + ru32(output + 4) * 4); - wf32(output + 0x0C, @mulAdd(f32, b - a, t, a)); + const a = rf32(kf_base + r.idx0 * 4); + const b = rf32(kf_base + r.idx1 * 4); + wf32(output + 0x0C, @mulAdd(f32, b - a, r.t, a)); const blend = rf32(bone_rt_addr + 0x10C); if (blend != 0.0 and ri16(anim_data_short_ptr + 2) == -1) { - findInterpIdx(this, ru32(bone_rt_addr + 0xC4), ru32(bone_rt_addr + 0xC8), anim_data_short_ptr, output + 0x10); - const st = ufloat(ru32(output + 0x18)); - const sa = rf32(kf_base + ru32(output + 0x10) * 4); - const sb = rf32(kf_base + ru32(output + 0x14) * 4); - const sec = (sb - sa) * st + sa; + const sr = findInterpIdx(this, ru32(bone_rt_addr + 0xC4), ru32(bone_rt_addr + 0xC8), anim_data_short_ptr, output + 0x10); + const sa = rf32(kf_base + sr.idx0 * 4); + const sb = rf32(kf_base + sr.idx1 * 4); + const sec = (sb - sa) * sr.t + sa; wu32(output + 0x1C, fbits(sec)); const pri = ufloat(ru32(output + 0x0C)); wf32(output + 0x0C, (sec - pri) * blend + pri); @@ -1777,25 +1767,24 @@ fn texAnimLoop(this: u32, model_hdr: u32, frame_ctr: u32) void { const alpha_anim = anim_data + 0x1C; // ESI = output + 0x30 in original (alpha output area) const alpha_out = output + 0x30; - findInterpIdx(this, ru32(bone_rt_base + BR.prim_time), ru32(bone_rt_base + BR.prim_track), alpha_anim, alpha_out); + const ar = findInterpIdx(this, ru32(bone_rt_base + BR.prim_time), ru32(bone_rt_base + BR.prim_track), alpha_anim, alpha_out); const mode = ri16(alpha_anim); if (mode == 0) { // Mode 0: direct short→float copy. Assembly JMPs past crossfade. const kf_data = ru32(alpha_anim + 0x18); - const idx = ru32(alpha_out); - const sv = @as(f32, @floatFromInt(@as(i32, @as(*align(1) const i16, @ptrFromInt(kf_data + idx * 2)).*))); + const sv = @as(f32, @floatFromInt(@as(i32, @as(*align(1) const i16, @ptrFromInt(kf_data + ar.idx0 * 2)).*))); wf32(alpha_out + 0x0C, sv * getShortToFloat()); } else { // Mode != 0: lerp + crossfade - const primary = shortInterpToFloat(alpha_anim, alpha_out); + const primary = shortInterpToFloat(alpha_anim, ar); wf32(alpha_out + 0x0C, primary); // Crossfade (assembly 0x715BAF-0x715C5E) // Only runs for mode != 0 — mode 0 JMPs past this const bw = rf32(bone_rt_base + BR.blend_weight); if (bw != 0.0 and ri16(alpha_anim + 0x02) == -1) { - findInterpIdx(this, ru32(bone_rt_base + BR.sec_time), ru32(bone_rt_base + BR.sec_track), alpha_anim, alpha_out + 0x10); - const secondary = shortInterpToFloat(alpha_anim, alpha_out + 0x10); + const asr = findInterpIdx(this, ru32(bone_rt_base + BR.sec_time), ru32(bone_rt_base + BR.sec_track), alpha_anim, alpha_out + 0x10); + const secondary = shortInterpToFloat(alpha_anim, asr); wf32(alpha_out + 0x1C, secondary); wf32(alpha_out + 0x0C, @mulAdd(f32, secondary - primary, bw, primary)); } @@ -1804,18 +1793,17 @@ fn texAnimLoop(this: u32, model_hdr: u32, frame_ctr: u32) void { } } -/// Short-value interpolation: reads indices from output, looks up short values, interpolates. +/// Short-value interpolation: uses InterpResult indices, looks up short values, interpolates. /// Shared by texAnimLoop alpha, colorAnimLoop, and word animation crossfade. -inline fn shortInterpToFloat(anim_data: u32, output: u32) f32 { +inline fn shortInterpToFloat(anim_data: u32, r: InterpResult) f32 { const mode = ri16(anim_data); const table = anim_data + AD.nvalues; if (mode == 0) { - return @as(f32, @floatFromInt(@as(i32, readShortViaGame(table, ru32(output))))) * getShortToFloat(); + return @as(f32, @floatFromInt(@as(i32, readShortViaGame(table, r.idx0)))) * getShortToFloat(); } else { - const t = ufloat(ru32(output + 8)); - const v1 = @as(f32, @floatFromInt(@as(i32, readShortViaGame(table, ru32(output + 4))))); - const v0 = @as(f32, @floatFromInt(@as(i32, readShortViaGame(table, ru32(output))))); - return (v1 * getShortToFloat() - v0 * getShortToFloat()) * t + v0 * getShortToFloat(); + const v1 = @as(f32, @floatFromInt(@as(i32, readShortViaGame(table, r.idx1)))); + const v0 = @as(f32, @floatFromInt(@as(i32, readShortViaGame(table, r.idx0)))); + return (v1 * getShortToFloat() - v0 * getShortToFloat()) * r.t + v0 * getShortToFloat(); } } @@ -1840,24 +1828,23 @@ fn colorAnimLoop(this: u32, model_hdr: u32, frame_ctr: u32) void { const output = out_base + out_off; // Gate: anim_data+0x0C (kf count) > anim_frame_ctr if (frame_ctr < ru32(anim_data + 0x0C)) { - findInterpIdx(this, ru32(bone_rt_base + BR.prim_time), ru32(bone_rt_base + BR.prim_track), anim_data, output); + const cr = findInterpIdx(this, ru32(bone_rt_base + BR.prim_time), ru32(bone_rt_base + BR.prim_track), anim_data, output); const mode = ri16(anim_data); if (mode == 0) { // Mode 0: direct short→float. Assembly JMPs past crossfade (0x715D05). const kf_data = ru32(anim_data + 0x18); - const idx = ru32(output); - const sv = @as(f32, @floatFromInt(@as(i32, @as(*align(1) const i16, @ptrFromInt(kf_data + idx * 2)).*))); + const sv = @as(f32, @floatFromInt(@as(i32, @as(*align(1) const i16, @ptrFromInt(kf_data + cr.idx0 * 2)).*))); wf32(output + 0x0C, sv * getShortToFloat()); } else { // Mode != 0: lerp + crossfade - const primary = shortInterpToFloat(anim_data, output); + const primary = shortInterpToFloat(anim_data, cr); wf32(output + 0x0C, primary); // Crossfade (assembly 0x715D6B-0x715E1B) const bw = rf32(bone_rt_base + BR.blend_weight); if (bw != 0.0 and ri16(anim_data + 0x02) == -1) { - findInterpIdx(this, ru32(bone_rt_base + BR.sec_time), ru32(bone_rt_base + BR.sec_track), anim_data, output + 0x10); - const secondary = shortInterpToFloat(anim_data, output + 0x10); + const csr = findInterpIdx(this, ru32(bone_rt_base + BR.sec_time), ru32(bone_rt_base + BR.sec_track), anim_data, output + 0x10); + const secondary = shortInterpToFloat(anim_data, csr); wf32(output + 0x1C, secondary); wf32(output + 0x0C, @mulAdd(f32, secondary - primary, bw, primary)); } @@ -1888,12 +1875,11 @@ fn wordAnimLoop(this: u32, model_hdr: u32, frame_ctr: u32) void { const anim_data = data_base + data_off; const output = out_base + out_off; if (frame_ctr < ru32(anim_data + 0x0C)) { - findInterpIdx(this, ru32(bone_rt_base + BR.prim_time), ru32(bone_rt_base + BR.prim_track), anim_data, output); + const wr = findInterpIdx(this, ru32(bone_rt_base + BR.prim_time), ru32(bone_rt_base + BR.prim_track), anim_data, output); // Word copy: read word from keyframe data via direct indexing // Assembly (0x715EA3): MOV AX,[kf_data+idx*2]; MOV [output+0x0C],AX const kf_data = ru32(anim_data + 0x18); - const idx = ru32(output); - wu16(output + 0x0C, ru16(kf_data + idx * 2)); + wu16(output + 0x0C, ru16(kf_data + wr.idx0 * 2)); // Crossfade (assembly 0x715EB4-0x715EFA) // Original: JZ skip if mode==0, then check blend_weight > 0, then time_index == -1 @@ -1902,9 +1888,8 @@ fn wordAnimLoop(this: u32, model_hdr: u32, frame_ctr: u32) void { } else { const bw = rf32(bone_rt_base + BR.blend_weight); if (bw != 0.0 and ri16(anim_data + 0x02) == -1) { - findInterpIdx(this, ru32(bone_rt_base + BR.sec_time), ru32(bone_rt_base + BR.sec_track), anim_data, output + 0x10); - const sec_idx = ru32(output + 0x10); - wu16(output + 0x1C, ru16(kf_data + sec_idx * 2)); + const wsr = findInterpIdx(this, ru32(bone_rt_base + BR.sec_time), ru32(bone_rt_base + BR.sec_track), anim_data, output + 0x10); + wu16(output + 0x1C, ru16(kf_data + wsr.idx0 * 2)); } } } @@ -2006,14 +1991,13 @@ fn ribbonEmitterLoop(this: u32, model_hdr: u32, frame_ctr: u32) void { // ---- 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 vr = findInterpIdx(this, ru32(bone_rt + BR.prim_time), ru32(bone_rt + BR.prim_track), entry + 0xB8, output + 0xE0); const vis_values = ru32(entry + 0xD0); // entry+0xB8+0x18 = AD.keyframe_base - wu8(output + 0xEC, ru8(vis_values + vis_idx0)); + wu8(output + 0xEC, ru8(vis_values + vr.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))); + const vsr = findInterpIdx(this, ru32(bone_rt + BR.sec_time), ru32(bone_rt + BR.sec_track), entry + 0xB8, output + 0xF0); + wu8(output + 0xFC, ru8(vis_values + vsr.idx0)); } } } @@ -2126,16 +2110,16 @@ fn additionalParticleLoops(this: u32, model_hdr: u32, frame_ctr: u32) void { const bone_idx = @as(u32, ru16(entry + 0x04)); const bone_rt = bone_rt_base + bone_idx * 0x118; // Visibility byte animation at entry+0xC0 - findInterpIdx(this, ru32(bone_rt + 0x98), ru32(bone_rt + 0x9C), entry + 0xC0, output + 0xB0); + const pvr = findInterpIdx(this, ru32(bone_rt + 0x98), ru32(bone_rt + 0x9C), entry + 0xC0, output + 0xB0); const vis_mode = ri16(entry + 0xC0); if (vis_mode == 0) { - wu8(output + 0xBC, ru8(ru32(entry + 0xC0 + 0x18) + ru32(output + 0xB0))); + wu8(output + 0xBC, ru8(ru32(entry + 0xC0 + 0x18) + pvr.idx0)); } else { - wu8(output + 0xBC, ru8(ru32(output + 0xB0) + ru32(entry + 0xD8))); + wu8(output + 0xBC, ru8(pvr.idx0 + ru32(entry + 0xD8))); // Crossfade blend for visibility if needed if (rf32(bone_rt + 0x10C) != 0.0 and ri16(entry + 0xC2) == -1) { - findInterpIdx(this, ru32(bone_rt + 0xC4), ru32(bone_rt + 0xC8), entry + 0xC0, output + 0xC0); - wu8(output + 0xCC, ru8(ru32(output + 0xC0) + ru32(entry + 0xD8))); + const pvsr = findInterpIdx(this, ru32(bone_rt + 0xC4), ru32(bone_rt + 0xC8), entry + 0xC0, output + 0xC0); + wu8(output + 0xCC, ru8(pvsr.idx0 + ru32(entry + 0xD8))); } } } @@ -2152,17 +2136,16 @@ fn additionalParticleLoops(this: u32, model_hdr: u32, frame_ctr: u32) void { if (frame_ctr < ru32(entry + 0x4C)) { const bone_idx = @as(u32, ru16(entry + 0x04)); const bone_rt = bone_rt_base + bone_idx * 0x118; - findInterpIdx(this, ru32(bone_rt + 0x98), ru32(bone_rt + 0x9C), entry + 0x40, output + 0x30); + const par = findInterpIdx(this, ru32(bone_rt + 0x98), ru32(bone_rt + 0x9C), entry + 0x40, output + 0x30); const alpha_mode = ri16(entry + 0x40); const table = entry + 0x40 + AD.nvalues; if (alpha_mode == 0) { - const sv = @as(f32, @floatFromInt(@as(i32, readShortViaGame(table, ru32(output + 0x30))))); + const sv = @as(f32, @floatFromInt(@as(i32, readShortViaGame(table, par.idx0)))); wf32(output + 0x3C, sv * getShortToFloat()); } else { - const t = ufloat(ru32(output + 0x38)); - const v1 = @as(f32, @floatFromInt(@as(i32, readShortViaGame(table, ru32(output + 0x34))))); - const v0 = @as(f32, @floatFromInt(@as(i32, readShortViaGame(table, ru32(output + 0x30))))); - wf32(output + 0x3C, (v1 * getShortToFloat() - v0 * getShortToFloat()) * t + v0 * getShortToFloat()); + const v1 = @as(f32, @floatFromInt(@as(i32, readShortViaGame(table, par.idx1)))); + const v0 = @as(f32, @floatFromInt(@as(i32, readShortViaGame(table, par.idx0)))); + wf32(output + 0x3C, (v1 * getShortToFloat() - v0 * getShortToFloat()) * par.t + v0 * getShortToFloat()); } } @@ -2185,13 +2168,13 @@ fn additionalParticleLoops(this: u32, model_hdr: u32, frame_ctr: u32) void { if (frame_ctr < ru32(entry + 0xB0)) { const bone_idx = @as(u32, ru16(entry + 0x04)); const bone_rt = bone_rt_base + bone_idx * 0x118; - findInterpIdx(this, ru32(bone_rt + 0x98), ru32(bone_rt + 0x9C), entry + 0xA4, output + 0x90); + const scr = findInterpIdx(this, ru32(bone_rt + 0x98), ru32(bone_rt + 0x9C), entry + 0xA4, output + 0x90); const scale_values = ru32(entry + 0xA4 + AD.keyframe_base); - wu16(output + 0x9C, ru16(scale_values + ru32(output + 0x90) * 2)); + wu16(output + 0x9C, ru16(scale_values + scr.idx0 * 2)); if (ri16(entry + 0xA4) != 0) { if (rf32(bone_rt + 0x10C) != 0.0 and ri16(entry + 0xA6) == -1) { - findInterpIdx(this, ru32(bone_rt + 0xC4), ru32(bone_rt + 0xC8), entry + 0xA4, output + 0xA0); - wu16(output + 0xAC, ru16(scale_values + ru32(output + 0xA0) * 2)); + const scsr = findInterpIdx(this, ru32(bone_rt + 0xC4), ru32(bone_rt + 0xC8), entry + 0xA4, output + 0xA0); + wu16(output + 0xAC, ru16(scale_values + scsr.idx0 * 2)); } } } @@ -2231,14 +2214,14 @@ fn additionalParticleLoops(this: u32, model_hdr: u32, frame_ctr: u32) void { // Visibility: gate=entry+0x1E8, AnimData=entry+0x1DC, output=output+0x140 if (frame_ctr < ru32(entry + 0x1E8)) { - findInterpIdx(this, ru32(bone_rt + 0x98), ru32(bone_rt + 0x9C), entry + 0x1DC, output + 0x140); + const lvr = findInterpIdx(this, ru32(bone_rt + 0x98), ru32(bone_rt + 0x9C), entry + 0x1DC, output + 0x140); if (ri16(entry + 0x1DC) == 0) { - wu8(output + 0x14C, ru8(ru32(entry + 0x1F4) + ru32(output + 0x140))); + wu8(output + 0x14C, ru8(ru32(entry + 0x1F4) + lvr.idx0)); } else { - wu8(output + 0x14C, ru8(ru32(output + 0x140) + ru32(entry + 0x1F4))); + wu8(output + 0x14C, ru8(lvr.idx0 + ru32(entry + 0x1F4))); if (rf32(bone_rt + 0x10C) != 0.0 and ri16(entry + 0x1DE) == -1) { - findInterpIdx(this, ru32(bone_rt + 0xC4), ru32(bone_rt + 0xC8), entry + 0x1DC, output + 0x150); - wu8(output + 0x15C, ru8(ru32(entry + 0x1F4) + ru32(output + 0x150))); + const lvsr = findInterpIdx(this, ru32(bone_rt + 0xC4), ru32(bone_rt + 0xC8), entry + 0x1DC, output + 0x150); + wu8(output + 0x15C, ru8(ru32(entry + 0x1F4) + lvsr.idx0)); } } } @@ -2324,8 +2307,8 @@ fn attachmentRecursion(this: u32, model_hdr: u32, bone_out_base: u32, frame_ctr: const bone_rt = ru32(this + SO.bone_rt_base) + bone_idx * 0x118; const anim_data = att_entry + 0x14; const att_output = hierarchy + att_i * 0x20; - findInterpIdx(this, ru32(bone_rt + 0x98), ru32(bone_rt + 0x9C), anim_data, att_output); - wu8(att_output + 0x0C, ru8(ru32(anim_data + AD.keyframe_base) + ru32(att_output))); + const atr = findInterpIdx(this, ru32(bone_rt + 0x98), ru32(bone_rt + 0x9C), anim_data, att_output); + wu8(att_output + 0x0C, ru8(ru32(anim_data + AD.keyframe_base) + atr.idx0)); } }