Is it possible to reliably extract the same quaternion coordinates from the data that is still reported?
SDL might need to be patched back to handle the quaternion reports, seeing as
https://github.com/libsdl-org/SDL/blob/634dff3725b8419902b832d1c84363da211a3596/src/joystick/hidapi/SDL_hidapi_steam_triton.c#L526 falls through to the BLE report case.
In any case, I probably won't be updating the firmware on my controller until this is resolved.
I accumulate drift when doing so - I was not accumulating drift before.
I've tried using something like
if ts_delta == 0 {
continue;
}
rot += (input_packet.imu_gyro_z as f32 / 32768. * (ts_delta / 4011) as f32);
in a loop, and it seems to accumulate drift as I turn the controller back and forth. (the 4011 is based on my own controller's numbers, you can also just multiply by the actual value, and ignoring the delta doesn't really seem to help either - though you wanna drop the value if the ts doesn't change)
In regards to the linked code, it never used the quaternion - it truncated the struct if you passed in the full one, SDL never passed the quaternion back to user code in the case of triton.
SDL has code for calculating quaternions and calibrating controllers in https://github.com/libsdl-org/SDL/blob/main/test/testcontroller.c . I wonder if that gets you decent quality orientation data from the speeds.
What would concern me the most if the controller measures at a different rate internally to calculate the quaternion or the quaternion is still readable from steam input but not for third party tools.
If there's anything you would like me to try let me know, my controller still reports id 0x42 (66) since I am on an older firmware version.
@Kuratius If you can try just verifying that I haven't misunderstood the quaternion that's derived (because I hadn't tested it as thoroughly at the time, I had waited for this update due to another issue around LFO tones that could cause physical damage to the hardware which has since been fixed - not possible with any of the normal operating commands steam sends, nor the HID rumble commands, so you aren't likely to trigger it) - if you can try checking the quaternion homes correctly if:
You can see the drift accumulate via steam's UI if you go to the gyro calibration screen and watch the icons rotating, as they use the rotation in deg/sec method as I mentioned above, not the actual quaternion, even if the controller is sending the quaternion.
I don't remember any drift, though I wasn't taking as close notes when I did so, but as far as I could tell there wasn't any. Would be good to sanity check.
Do this in the Y axis (so if sitting on a desk, just spin it) - if it homes consistently, then the data likely does differ. Do this ideally by reading the raw HID events, not via any SDL builtins.
Modified the switch statement in SDL to print the packet contents, then logged them. This was captured while connected via the puck and without steam running.
https://gist.github.com/Kuratius/47b7498678a37224e91678aea024e009
https://gist.github.com/Kuratius/6ec7aba94f1b5702254ec429a75dc451
Also wrote a parser https://gist.github.com/Kuratius/50d8cd3581488b330b6894256c455cda
So, with more time on this than I'd like to admit, I've figured out what's going on. My assumptions are partially wrong.
The original quaternion has drift - it always likely did, I don't even need to decode the data. The (new) steam controller has a 6dof IMU - no magnometer - so any drift that is seen will accumulate regardless. The only potential difference (which I haven't confirmed) is the sampling rate between the controller's own internal hardware and HID, but that's unlikely to actually be a huge amount of drift given we get an IMU update rate of just under 250hz or so over HID (on average, with a small amount of deviation).
The actual data we are given is somewhat similar to the MPU6050 here.
The exact units we are given is a fixed point value where the range [-1.0, 1.0) maps to [-2000, 2000) degrees/second. To turn this into a quaternion you'd use something like this (after converting to radians in the same way SDL does)
// Source - https://stackoverflow.com/a/24201879
// Posted by minorlogic, modified by community. See post 'Timeline' for change history
// Retrieved 2026-05-31, License - CC BY-SA 4.0
Quaternion deltaRotation(const Vector3& angularVelocity, double deltaTime)
{
Vector3 ha = angularVelocity * (deltaTime * 0.5); // vector of half angle
double l = ha.norm(); // magnitude
if (l > 0) {
ha *= sin(l) / l; //sine cardinal
}
return Quaternion(cos(l), ha.x(), ha.y(), ha.z());
}
and that would give us the same result anyway.
While the quaternion over HID is definitely more convenient, it's actually unlikely to be any more accurate, and the lower processing overhead on the controller itself (due to not needing to perform trigonometry ops) is probably worth it.
Also @Kuratius I'd update your controller - some of the changes in the updates, both publicised and not, are generally going to give you a better experience. I haven't tested as hard but some of the more basic ops that crashed my controller firmware in past no longer do.
There is at least 1 scenario where having the controller compute the quaternion is preferable. A spotty connection would mean dropped packets that the computer can't recreate. Logically one should let the application choose which type of report it wants. Even the performance issues for sin/cos that might be a concern aren't actually significant if you use CORDIC with a correction step for small angles (To explain, cordic usually leaves a remainder that needs to be handled, since you want to do as few iterations as possible). I've implemented this before, an accurate sin/cos only takes a few dozen cycles if you avoid the <math.h> functions that rely on a 10th degree polynomial using floats. A LUT would be even faster but the accuracy (and maybe memory usage) probably wouldn't be acceptable.
For future reference, the following is a cordic implementation with very few steps and good accuracy:
https://github.com/KeithDobbelaere/GeometryVibes3D-PicoCalc/issues/7#issuecomment-4148542242
@Kuratius I generally agree with the idea there, and have seen missed events due to the reported IMU timestamp, would be cool to have. Might just be a wrong packet type honestly, I dunno, the packet type definitely doesn't make sense to me at least. Might reopen it.
Nothing extracted yet.
Your system information
Please describe your issue in as much detail as possible:
As of the most recent update, we no longer get the raw quaternion from the controller. The problem is that when using gyro + timestamp, I can't get a consistent reading of the controller's actual rotation - it always drifts forwards or backwards a little, whether or not I take the timestamp into account.
Previously, I was using the quaternion to read the rotation, which kept its rotation far more consistently, which was coming out over report ID 0x42. Now I'm getting 0x45 only, even when wired, and no quaternion data.
Steps for reproducing this issue:
0x30(SETTING_IMU_MODE) to0x0018(raw gyro + raw accel)TritonMTUNoQuat_tinstead ofTritonMTUFull_t