From b533725ae4c0acbed9372da1180ba77f6fcdda40 Mon Sep 17 00:00:00 2001 From: arcattack Date: Tue, 14 Jul 2026 08:09:16 -0500 Subject: [PATCH] Radar: FIX heading flop every 180deg -- YawPitchRoll not EulerAngles (user-reported) The radar map derived its rotation from EulerAngles(Quaternion), whose decomposition is AMBIGUOUS for a yawing mech: as yaw sweeps past +/-pi the quaternion double-cover flips it onto the pitch=roll=pi branch, so euler.yaw REVERSES -- the whole radar counter-rotates the wrong way past 180deg (user: 'flops every 180 degrees'). Switched both radar sites (view-matrix build + blip delta rotation) to YawPitchRoll, which applies yaw first and yields a clean continuous 360deg heading -- the SAME fix the compass HeadingPointer already uses (btl4gaug.cpp:2185). A/B verified live (BT_RADAR_LOG, spinning madcat): euler.yaw folds (-0.30 vs true -2.84 rad) while ypr.yaw tracks clean. Co-Authored-By: Claude Fable 5 --- game/reconstructed/btl4rdr.cpp | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/game/reconstructed/btl4rdr.cpp b/game/reconstructed/btl4rdr.cpp index f6d4faf..4d2c360 100644 --- a/game/reconstructed/btl4rdr.cpp +++ b/game/reconstructed/btl4rdr.cpp @@ -697,9 +697,16 @@ void // Top-down: keep heading only. Extract the yaw, build a // yaw-only quaternion, SET the rotation block from it. // - EulerAngles euler; - euler = *currentAngularPointer; // FUN_0040954c - Scalar heading = euler.yaw * HeadingHalf; // * 0.5f + YawPitchRoll euler; // FIX: YawPitchRoll (yaw-first), + euler = *currentAngularPointer; // not EulerAngles -- the latter folds + Scalar heading = (Scalar)euler.yaw * HeadingHalf; // at +/-pi under yaw. * 0.5f half-angle + if (getenv("BT_RADAR_LOG")) { + EulerAngles oldE; oldE = *currentAngularPointer; // old buggy path, A/B + static int s_rv = 0; + if ((s_rv++ % 20) == 0) + DEBUG_STREAM << "[radar-view] euler.yaw=" << (Scalar)oldE.yaw + << " ypr.yaw=" << (Scalar)euler.yaw << " (rad)\n" << std::flush; + } SinCosPair sc; sc = Radian(heading); // FUN_00408328 Quaternion yaw(0.0f, sc.sine, 0.0f, sc.cosine); // FUN_00409948 @@ -965,12 +972,25 @@ void Scalar sinH = 0.0f, cosH = 1.0f; if (currentAngularPointer != NULL) { - EulerAngles e; - e = *currentAngularPointer; // Quaternion -> EulerAngles (operator=) + // FIX (user-reported: radar rotation flops every 180deg): the MUNGA + // EulerAngles(Quaternion) decomposition is AMBIGUOUS for a yawing mech -- + // as yaw sweeps past +/-pi the quaternion double-cover flips it onto a + // pitch=roll=pi branch, so euler.yaw REVERSES (the radar spins back). + // YawPitchRoll applies yaw FIRST -> a clean continuous 360deg heading. + // Same fix the compass HeadingPointer already uses (btl4gaug.cpp:2185). + YawPitchRoll ypr; + ypr = *currentAngularPointer; SinCosPair scH; - scH = Radian(e.yaw); // FUN_00408328 + scH = Radian((Scalar)ypr.yaw); sinH = scH.sine; cosH = scH.cosine; + if (getenv("BT_RADAR_LOG")) { + EulerAngles e; e = *currentAngularPointer; // old (buggy) path, for A/B + static int s_rn = 0; + if ((s_rn++ % 20) == 0) + DEBUG_STREAM << "[radar] euler.yaw=" << (Scalar)e.yaw + << " ypr.yaw=" << (Scalar)ypr.yaw << " (rad)\n" << std::flush; + } } Scalar rx = delta.x * cosH + delta.z * sinH; Scalar rz = -delta.x * sinH + delta.z * cosH;