The hands' sway takes the short way round when the yaw wraps

LocalPlayer's yaw runs on without wrapping, so yRot - yBob is always the short way. Ours is kept
in -180..180 and a boat turns its rider through the wrap: the difference jumped by 360 degrees
for a tick, swinging the hands 36 degrees (a tenth of it) and then easing back by halves, like
jelly. The difference is wrapped, both when yBob eases and when the sway is read.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
This commit is contained in:
Federico TaglianiandClaude Sonnet 5.5 committed 2026-11-01 22:13:54 +01:00
1 parent 885f47a49a
commit 6aec3da419
1 file changed
+36 -2
+36 -2
View File
@@ -520,6 +520,17 @@ pub fn equip_displacement(height: f32, swap_animation_scale: f32) -> f32 {
swap_animation_scale * (1.0 - height)
}
/// `Mth.wrapDegrees`: an angle difference in -180..=180.
fn wrap_degrees(d: f32) -> f32 {
let d = d % 360.0;
if d >= 180.0 {
d - 360.0
} else if d < -180.0 {
d + 360.0
} else {
d
}
}
/// `LocalPlayer.xBob` and `yBob`: the hands lag behind the view when it
/// turns. `LocalPlayer.applyInput` eases them halfway to the rotation every
/// tick; `FirstPersonHandsAndItemsRenderer` turns the hands by a tenth of
@@ -537,7 +548,9 @@ impl HandSway {
self.y_bob_o = self.y_bob;
self.x_bob_o = self.x_bob;
self.x_bob += (x_rot - self.x_bob) * 0.5;
self.y_bob += (y_rot - self.y_bob) * 0.5;
// Vanilla's yaw runs on without wrapping, so `yRot - yBob` is the short way round. Ours
// is kept in -180..180 (a boat turns its rider through it), so take the short way here.
self.y_bob += wrap_degrees(y_rot - self.y_bob) * 0.5;
}
/// The degrees the hands turn about X, then Y, before either is drawn:
/// `(viewXRot - xBob) * 0.1` and `(viewYRot - yBob) * 0.1`, the bobs
@@ -545,7 +558,10 @@ impl HandSway {
pub fn rotation(&self, partial: f32, view_x_rot: f32, view_y_rot: f32) -> [f32; 2] {
let x_bob = crate::camera::lerp(partial, self.x_bob_o, self.x_bob);
let y_bob = crate::camera::lerp(partial, self.y_bob_o, self.y_bob);
[(view_x_rot - x_bob) * 0.1, (view_y_rot - y_bob) * 0.1]
[
(view_x_rot - x_bob) * 0.1,
wrap_degrees(view_y_rot - y_bob) * 0.1,
]
}
/// The hands' view space to camera-relative world space, turned by the
/// sway. The hand pass draws with the camera's view, whose rotation a
@@ -1384,4 +1400,22 @@ mod action_result_tests {
);
}
}
/// The hands' sway turns the short way round when the yaw wraps from 179 to -179 degrees (a
/// boat turning its rider), instead of swinging them 36 degrees the long way.
#[test]
fn hand_sway_takes_the_short_way_over_the_yaw_wrap() {
let mut sway = HandSway::default();
sway.tick(0.0, 179.0);
for _ in 0..20 {
sway.tick(0.0, 179.0);
}
// The view is still at 179; one tick later it reads -179 (2 degrees on).
sway.tick(0.0, -179.0);
let [_, y] = sway.rotation(1.0, 0.0, -179.0);
assert!(
y.abs() < 0.5,
"a 2 degree turn sways the hands by under half a degree: {y}"
);
}
}