Skip to content

tests: pin which head movement becomes which angle (pitch/roll had no coverage) - #19

Open
dkxmercury wants to merge 1 commit into
NicholasSlattery:mainfrom
dkxmercury:tests/pin-pitch-roll-convention
Open

tests: pin which head movement becomes which angle (pitch/roll had no coverage)#19
dkxmercury wants to merge 1 commit into
NicholasSlattery:mainfrom
dkxmercury:tests/pin-pitch-roll-convention

Conversation

@dkxmercury

@dkxmercury dkxmercury commented Jul 17, 2026

Copy link
Copy Markdown

Summary

The maths tests cover yaw but never pitch or roll, so nothing pins down which head movement is supposed to become which angle.

That gap is measurable rather than theoretical. If you swap the pitch and roll terms in quaternionToEulerDegrees:

-return {std::atan2(siny, cosy)*d, std::asin(sinp)*d, std::atan2(sinr, cosr)*d};
+return {std::atan2(siny, cosy)*d, std::atan2(sinr, cosr)*d, std::asin(sinp)*d};

all 8 existing tests still pass. identity_quaternion_is_zero_euler can't see it because every angle is zero, and ninety_degree_yaw_about_z can't see it because it only touches yaw. Given that pitch/roll confusion is a recurring report (#10), that seemed worth closing.

This PR adds three tests, no production changes:

  • thirty_degree_roll_about_x and thirty_degree_pitch_about_y cover the two untested angles at the maths level.
  • default_mapping_sends_each_raw_axis_to_one_head_angle goes end to end through remap -> rotationVectorToQuaternion -> quaternionToEulerDegrees with the default {1,0,2} / {-1,+1,-1} mapping, and checks that each raw device axis lands in exactly one head angle: a nod about raw X leaves as pitch, a tilt about raw Y as roll, a turn about raw Z as yaw.

The end-to-end one is the point of the change. The mapping is what actually decides which movement becomes which angle, and it's the piece that isn't obviously right by inspection, since the extraction itself uses the aerospace convention (roll about X, pitch about Y) while the raw device frame doesn't. It's correct today; this just keeps it that way.

To be clear about scope: this does not fix #10. That looks like the default mapping being tuned for the WH-1000XM5 rather than a maths bug, and I don't own a headset to confirm. What these tests do is make sure that when someone does adjust mapping for another model, the models that work today can't quietly break.

How tested

Built and ran the pure core with g++ 13.4.0 (-std=c++20 -Wall -Wextra, warning-clean), since I'm not on a Windows box with MSVC:

PASS  identity_quaternion_is_zero_euler
PASS  ninety_degree_yaw_about_z
PASS  thirty_degree_roll_about_x
PASS  thirty_degree_pitch_about_y
PASS  default_mapping_sends_each_raw_axis_to_one_head_angle
PASS  rotation_vector_round_trip
PASS  zero_rotation_vector_is_identity
PASS  remap_identity_is_noop
PASS  remap_default_yxz_with_inverted_x_and_z
PASS  quaternion_multiply_by_conjugate_is_identity
PASS  slerp_endpoints

11/11 test(s) passed, 0 assertion failure(s)

I also checked the new tests actually bite, rather than just passing:

  • Swapping pitch/roll in quaternionToEulerDegrees: all three new tests fail, the 8 existing ones still pass (8/11 passed).
  • Breaking the default mapping's source order ({1,0,2} -> {0,1,2}): the end-to-end test fails, everything else passes (10/11 passed).

No hardware involved, and no hardware needed for any of it.

One question while I was in there, happy to leave it alone: the comment above quaternionToEulerDegrees says "Android head axes: X=right, Y=forward, Z=up", but the function is called with the already-remapped vector, so its X isn't the device's right axis. Reading it, I first thought pitch and roll were swapped, and only the default mapping showed otherwise. Would a note that the input is post-remap be useful, or is it clear enough in context? Didn't want to touch a comment on a guess about intent.

Checklist

  • Builds warning-clean at /W3 with MSVC (build.cmd) - not verified, no MSVC here. Warning-clean under g++ -Wall -Wextra; CI on windows-latest will be the real check.
  • Stays within scope: no device spoofing, identity changes, or firmware edits
  • Updated docs/PROTOCOL.md if the UDP/JSON output changed - n/a, tests only
  • Bumped the JSON version if the change is not backward-compatible - n/a, no behaviour change
  • Added a line to the Unreleased section of CHANGELOG.md

The maths tests covered yaw only, so swapping the pitch and roll terms in
quaternionToEulerDegrees left all 8 of them passing.

Adds 30-degree rotations about X and Y at the maths level, plus an
end-to-end check through remap -> rotationVectorToQuaternion ->
quaternionToEulerDegrees with the default {1,0,2} / {-1,+1,-1} mapping,
asserting each raw device axis lands in exactly one head angle.

No production changes.
@GuikiPT

GuikiPT commented Jul 17, 2026

Copy link
Copy Markdown

Would this fix the issue I have created then @dkxmercury ? (nevermind I re-read the pr, saw that will not fix it)

In 3/4hrs I'm at home, so I can install VS Studio -> Windows Tools to build that pr to see if it fixes it for me. (or even use that test to see if I can create a custom mapping perhaps)

@dkxmercury

Copy link
Copy Markdown
Author

You've got it right, this PR only locks in that a nod stays on Pitch. It doesn't change any device's mapping, so it won't fix #10 on its own.

Good news though, you shouldn't need to build anything to fix it for your XM6. The app already exposes this in the GUI. There's an axis-map dropdown with six orderings, plus X/Y/Z invert checkboxes. Since your up-down currently shows up as roll, try the other entries in that dropdown until a nod moves Pitch instead of Roll, and flip the invert boxes if an axis goes the wrong way. That should get your XM6 tracking correctly live, no rebuild.

And if you land on the combo that works, that's gold for #10. The current default is tuned for the WH-1000XM5, and the XM6 clearly sits differently. Drop the working axis-map and invert settings into #10, and the maintainer has exactly what he needs to ship a correct default for the XM6. Happy to help read the numbers if you paste what you find.

GuikiPT added a commit to GuikiPT/sony-head-tracker that referenced this pull request Jul 28, 2026
…math tests

- Mark WF-1000XM6 as verified in the default axis mapping comment
- Add Unreleased changelog entries for the reference frame and mapping fixes
- Cherry-pick dkxmercury's pitch/roll math-level tests from PR NicholasSlattery#19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Sony WF-1000XM6 Pitch and Roll not working correctly.

2 participants