fix(L.2g-S2b): turn/sidestep axes checked BEFORE overlay classification in RemoteMotionSink

The first S2b cut classified turn/sidestep dispatches (Modifier-class
0x65xxxxxx) into the generic overlay router before the axis checks ran —
turns never reached the cycle pick OR the ObservedOmega seed. S2 smoke
symptoms: turn-in-place showed no animation (orientation snapping only)
and running-in-circles had no client-side rotation between UPs.
Log evidence: 30 [TURN_WIRE] events, zero turn [SETCYCLE]s
(launch-s2-smoke.log).

Harness gap noted: the conformance suite stops at the funnel->sink
boundary; extending golden coverage through the sink (SETCYCLE-level
fixtures) is scheduled with the R-phase rewrite.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-07-02 19:19:46 +02:00
parent 67506ce988
commit 4bfcd2735e

View file

@ -61,24 +61,33 @@ internal sealed class RemoteMotionSink : IInterpretedMotionSink
return;
}
var route = AnimationCommandRouter.Classify(motion);
if (route is AnimationCommandRouteKind.Action
or AnimationCommandRouteKind.Modifier
or AnimationCommandRouteKind.ChatEmote)
{
// Overlay (attack swings, emotes): append over the current
// cyclic tail immediately — the substate cycle is unaffected
// (retail Action branch of GetObjectSequence). The funnel's
// contact gate already filtered airborne cases.
AnimationCommandRouter.RouteFullCommand(
_sequencer, _style, motion, speed <= 0f ? 1f : speed);
return;
}
// Turn/sidestep FIRST: they are Modifier-class (0x65xxxxxx), so the
// generic overlay classification below would swallow them — that
// exact ordering bug shipped in the first S2b cut and killed
// turn-in-place animation + the ObservedOmega seed (S2 smoke,
// 2026-07-02: 30 [TURN_WIRE] events, zero turn SetCycles). They are
// cycle-axis candidates for the single-cycle pick, not overlays.
uint low = motion & 0xFFu;
bool isTurn = low is 0x0D or 0x0E && (motion & 0xFF000000u) == 0x65000000u;
bool isSidestep = low is 0x0F or 0x10 && (motion & 0xFF000000u) == 0x65000000u;
if (!isTurn && !isSidestep)
{
var route = AnimationCommandRouter.Classify(motion);
if (route is AnimationCommandRouteKind.Action
or AnimationCommandRouteKind.Modifier
or AnimationCommandRouteKind.ChatEmote)
{
// Overlay (attack swings, emotes): append over the current
// cyclic tail immediately — the substate cycle is unaffected
// (retail Action branch of GetObjectSequence). The funnel's
// contact gate already filtered airborne cases.
AnimationCommandRouter.RouteFullCommand(
_sequencer, _style, motion, speed <= 0f ? 1f : speed);
return;
}
}
if (isTurn)
{
_turn = motion;