diff --git a/crates/xenia-cpu/src/interpreter.rs b/crates/xenia-cpu/src/interpreter.rs index 051aa4f..082e029 100644 --- a/crates/xenia-cpu/src/interpreter.rs +++ b/crates/xenia-cpu/src/interpreter.rs @@ -2039,7 +2039,8 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) - let ai = vmx::flush_denorm(a[i]); let bi = vmx::flush_denorm(b[i]); let ci = vmx::flush_denorm(c[i]); - r[i] = vmx::flush_denorm(bi - ai * ci); + // PPCBUG-426: single FMA rounding instead of two-step (b - a*c). + r[i] = vmx::flush_denorm(-ai.mul_add(ci, -bi)); } ctx.vr[instr.rd()] = xenia_types::Vec128::from_f32x4_array(r); ctx.pc += 4; @@ -2056,7 +2057,8 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) - let ai = vmx::flush_denorm(a[i]); let bi = vmx::flush_denorm(b[i]); let di = vmx::flush_denorm(d[i]); - r[i] = vmx::flush_denorm(di - ai * bi); + // PPCBUG-427: single FMA rounding. + r[i] = vmx::flush_denorm(-ai.mul_add(bi, -di)); } ctx.vr[instr.vd128()] = xenia_types::Vec128::from_f32x4_array(r); ctx.pc += 4; diff --git a/crates/xenia-cpu/src/vmx.rs b/crates/xenia-cpu/src/vmx.rs index 5650a34..4be636f 100644 --- a/crates/xenia-cpu/src/vmx.rs +++ b/crates/xenia-cpu/src/vmx.rs @@ -214,7 +214,9 @@ pub fn flush_denorm(x: f32) -> f32 { // // vctsxs / vctuxs flush denormal inputs to 0 before scaling, per Altivec. #[inline] pub fn cvt_f32_to_i32_sat(x: f32, scale_bits: u32) -> (i32, bool) { - if x.is_nan() { return (0, true); } + // PPCBUG-433: AltiVec ISA saturates NaN to INT_MIN (0x80000000), not 0. + // (vctuxs's NaN→0 is correct per AltiVec ISA — see PPCBUG-434.) + if x.is_nan() { return (i32::MIN, true); } let x = flush_denorm(x); let scaled = (x as f64) * ((1u64 << scale_bits) as f64); if scaled >= i32::MAX as f64 { return (i32::MAX, true); }