"NPC steer loop freezes forever against a wall"
▸ SYMPTOM
A trace-swept NPC in Chase or Follow state stops moving and stands at byte-identical coordinates indefinitely (4+ minutes observed). The NPC is in an active pursuit state, the target is reachable, but the NPC never moves again. The position is constant to 4+ decimal places.
▸ CAUSE
"Wall ahead -> hold position this frame" is a permanent freeze whenever the desired direction is constant. The NPC's body trace hits a wall, the steer logic says "can't go forward, hold this frame," and next frame the identical trace hits the identical wall -- "this frame" becomes forever.
A StartedSolid push-through branch does NOT cover this case: the NPC is wedged against the obstacle, which is a Hit, not StartedSolid. The body is next to the wall, not inside it.
▸ FIX
On Hit, project the step onto the wall plane and sweep the slide direction instead:
if (tr.Hit)
{
var slide = dir - tr.Normal * dir.Dot(tr.Normal);
slide = slide.WithZ(0).Normal;
if (slide.LengthSquared > 0.01f)
{
// Sweep the slide direction instead
var slideTr = Scene.Trace
.Ray(pos, pos + slide * stepDist)
.Size(bodySize)
.Run();
if (!slideTr.Hit)
pos = slideTr.EndPosition;
}
// else: dead-on hit with nowhere to slide -- hold is correct here
}Hold position only when the slide vector is near zero (dead-on hit with no lateral escape). Any non-zero slide means the NPC can wall-slide around the obstacle and continue pursuit.
▸ WHY IT WORKS
The wall-plane projection gives the NPC a lateral escape path along the obstacle's surface, the same way player movement handles wall collisions. A constant desired direction that hits a wall at an angle always produces a non-zero slide vector, breaking the freeze loop. Only a perfectly head-on collision (where the desired direction is parallel to the wall normal) results in a zero slide -- and that's the one case where holding genuinely makes sense until the target moves.