"Hand-rolled visual smoother fights engine FixedUpdateInterpolation — 50 Hz model flicker"
▸ SYMPTOM
Third-person character model flickers visibly on stepped or voxel terrain (coarse-collision block risers), but appears smooth on continuous/lowpoly terrain. The flicker is a 50 Hz sawtooth — the model judders up and down each physics tick. Toggling the hand-rolled visual smoother off immediately resolves it.
▸ CAUSE
FixedUpdateInterpolation is true by default in the engine (GameTransform.cs). A GameObject whose WorldPosition is written inside OnFixedUpdate is already interpolated per render frame — ModelRenderer renders Transform.InterpolatedWorld, and a child composes parent.InterpolatedWorld * childLocal.
A hand-rolled per-frame visual smoother that eases a child's LocalPosition.z toward the body each render frame — subtracting the raw, un-interpolated fixed-tick z while the parent renders at the interpolated z — creates a disagreement. The composed child world-z becomes parentInterp_z + (visZ - feetZ[t]): the parent term sweeps between tick positions across each 20 ms interval while the offset pins to the discrete tick value, producing a per-tick 50 Hz sawtooth whose amplitude equals the z-delta per step.
On stepped terrain (voxel, coarse collision) that delta is large (~0.9 m per block riser), so the flicker is obvious. On continuous terrain the per-tick z-delta approaches zero, so the sawtooth vanishes. That terrain discriminator is the diagnostic signature.
▸ FIX
Delete the manual smoother when engine interpolation is active. The visual child sits at its natural LocalPosition and engine interpolation carries the step motion.
Before hand-smoothing any fixed-tick transform, check whether engine interpolation is already active:
- No
NoInterpolationflag set - Not
IsStatic - Position written in a fixed-update context
If all three hold, a second per-frame smoother will double-smooth and fight it.
If manual smoothing is genuinely needed on an interpolated root, either:
- Compute the offset against the interpolated parent state, not raw fixed-tick state
- Feed the smoothed value back through the fixed-update write so interpolation smooths the smoothed value
▸ WHY IT WORKS
Engine interpolation already blends between discrete physics-tick positions to produce smooth render-frame positions. Adding a second smoothing layer that reads the raw (un-blended) tick state creates two competing interpolations that disagree by the per-tick delta — visible as a sawtooth at the physics rate. Removing the redundant layer leaves only the engine's interpolation, which handles the step motion correctly.