feat(L.2g-S2a): CMotionInterp inbound funnel in Core + live-retail conformance harness (DEV-1 core)

Port the inbound funnel verbatim into MotionInterpreter:
- MoveToInterpretedState (0x005289c0): raw-state style adopt, FLAT
  copy_movement_from overwrite, apply, then action replay under the
  15-bit server_action_stamp wraparound gate (local player skips its
  own autonomous echoes).
- ApplyInterpretedMovement (0x00528600): my_run_rate cache from
  RunForward speed, then retail dispatch order style -> forward-or-
  Falling -> sidestep(-stop) -> turn(-stop), turn early-return.
- DispatchInterpretedMotion (0x00528360): contact-gated sink dispatch
  (IInterpretedMotionSink = the GetObjectSequence backend the App
  implements); blocked non-action motions take the apply-only path —
  retail's real mechanism behind K-fix17's 'airborne remotes keep
  their cycle' empirical guard.
- contact_allows_move REWRITTEN VERBATIM from the real 0x00528240
  (pseudo-C 305471): Falling/0x40000011 + turns always allowed,
  non-creature weenies + no-gravity bypass, else Contact+OnWalkable.
  The previous body conflated jump_charge_is_allowed (0x00527a50)
  posture checks — the 2026-06-04 deep-dive divergence, now retired.
  Six tests that pinned the misattributed behavior corrected
  (grounded posture does NOT block motion; airborne accepts
  Falling/turns only).
- IWeenieObject.IsCreature (default true) for the gate's non-creature
  bypass.

Conformance harness (the user-requested 'prove it equals retail'
apparatus, layer 1): RetailObserverTraceConformanceTests parses the
LIVE cdb trace of a retail observer (Fixtures/l2g-observer-trace.log,
captured via tools/cdb/l2g-observer.cdb) into 183 golden cases —
each [MTIS] input state replayed through our funnel must produce
retail's exact [DIM] dispatch sequence. 183/183 conformant, including
the airborne jump case (replayed contact-free; pre-gate vs post-gate
accounting + HitGround second-pass truncation documented in-test).
13 synthetic funnel tests cover the branches the trace missed
(actions, stamps, longjump, sidestep).

GameWindow integration (S2b) follows; funnel not yet wired.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-07-02 18:50:09 +02:00
parent 97e098bf91
commit 7b0cbbda2c
6 changed files with 3693 additions and 55 deletions

View file

@ -0,0 +1,74 @@
using System.Collections.Generic;
namespace AcDream.Core.Physics;
/// <summary>
/// L.2g S2 — supporting types for the inbound CMotionInterp funnel
/// (deviation DEV-1). Spec:
/// docs/research/2026-07-02-s2-inbound-funnel-pseudocode.md; decomp:
/// MovementManager::unpack_movement 0x00524440,
/// CMotionInterp::move_to_interpreted_state 0x005289c0,
/// apply_interpreted_movement 0x00528600, DoInterpretedMotion 0x00528360 —
/// dispatch order validated against a LIVE retail-observer cdb trace
/// (tools/cdb/l2g-observer.cdb).
/// </summary>
public interface IInterpretedMotionSink
{
/// <summary>
/// The <c>CPhysicsObj::DoInterpretedMotion → … →
/// CMotionTable::GetObjectSequence</c> backend — called for every motion
/// that passes <c>contact_allows_move</c>, in retail dispatch order
/// (style → forward-or-Falling → sidestep → turn → actions). The App
/// implementation decides how the axes map onto the visible
/// <c>AnimationSequencer</c> cycle (retaining the HasCycle fallback,
/// overlay routing, etc.).
/// </summary>
void ApplyMotion(uint motion, float speed);
/// <summary>
/// <c>StopInterpretedMotion</c> notification for an axis the incoming
/// state cleared (sidestep 0x6500000F / turn 0x6500000D). Unconditional
/// (no contact gate) per apply_interpreted_movement.
/// </summary>
void StopMotion(uint motion);
}
/// <summary>
/// One entry of the inbound action list (retail <c>MotionItem</c> /
/// <c>InterpretedMotionState.actions</c>): a one-shot command with the
/// 15-bit server action stamp + autonomy bit
/// (<c>u16 ((stamp &amp; 0x7FFF) | (autonomous ? 0x8000 : 0))</c>).
/// </summary>
public readonly record struct InboundMotionAction(
uint Command, int Stamp, bool Autonomous, float Speed);
/// <summary>
/// A fully-decoded inbound <c>InterpretedMotionState</c>: wire fields where
/// present, retail UnPack defaults where absent
/// (<c>InterpretedMotionState::UnPack</c> 0x0051f400 /
/// ctor 0x0051e8d0). A flags=0 "empty" UM decodes to exactly
/// <see cref="Default"/> — a retail-verbatim full stop.
/// </summary>
public struct InboundInterpretedState
{
public uint CurrentStyle;
public uint ForwardCommand;
public float ForwardSpeed;
public uint SideStepCommand;
public float SideStepSpeed;
public uint TurnCommand;
public float TurnSpeed;
public IReadOnlyList<InboundMotionAction>? Actions;
public static InboundInterpretedState Default() => new()
{
CurrentStyle = 0x8000003Du,
ForwardCommand = 0x41000003u,
ForwardSpeed = 1.0f,
SideStepCommand = 0u,
SideStepSpeed = 1.0f,
TurnCommand = 0u,
TurnSpeed = 1.0f,
Actions = null,
};
}