A small sensor strapped near the shaft, a TV box rendering the shot, and a player who expects the ball to go where they aimed. Six months of that taught us that the hard part is never the fusion math — it is agreeing on what "zero" means.

Mounting beats math

The same firmware, the same swing, two different mounts: 11 degrees of error in club face. You can spend weeks improving a Kalman filter and lose to a bracket that shifts 2 mm under load.

What we ended up doing:

  • A hard mechanical key. The bracket has a flat reference face that seats against a flat on the sensor housing. If it only fits one way, it only reads one way.
  • Mount ID in the payload. Every report carries which bracket revision it came from, so field data can be bucketed instead of silently averaged together.
  • Re-zero on every session start, not on a button. Players will not press "calibrate". They will take a practice swing. Treat the first quiet second as the reference.

The gyro bias drift nobody warns you about

Cheap MEMS gyros have a bias that moves with temperature. A sensor that sat in a cold car reads a few degrees per second of phantom rotation until it warms up. At 100 mph of club head speed that is not noise, that is a wrong shot shape.

Our fix is unglamorous: estimate bias during the quiet window, then keep re-estimating any time the device reports "not moving" for more than a second. The state machine matters more than the filter.

Latency is a product feature

The gap between impact and the screen updating is what players judge the whole product by. Our budget: 120 ms sensor to render. That forces decisions:

  • Do the minimum viable integration on the sensor, ship raw-ish quaternions, and do the heavy work on the box.
  • Never block the render on persistence. Write the shot to storage after the animation starts.
  • Preload every asset the shot animation needs. A single network fetch in the hot path blows the budget.

What we stopped measuring

We removed several "nice" metrics because they were unstable across mounts and made the app feel random:

  • Swing plane angle — too sensitive to the bracket's roll. Replaced with a simpler face-to-path number that players can actually act on.
  • Tempo ratios — precise on a lab bench, jittery in a living room. Kept as a coarse band instead of a number.

The lesson generalizes: if a metric changes when you re-mount the sensor, it is a property of your fixture, not of the player.