using System.Collections.Generic; namespace AcDream.Core.Physics; /// /// 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). /// public interface IInterpretedMotionSink { /// /// The CPhysicsObj::DoInterpretedMotion → … → /// CMotionTable::GetObjectSequence backend — called for every motion /// that passes contact_allows_move, in retail dispatch order /// (style → forward-or-Falling → sidestep → turn → actions). R2-Q5: the /// production implementation is Motion.MotionTableDispatchSink, /// which dispatches straight into the entity's motion-table stack /// (PerformMovement → GetObjectSequence + is_allowed decide) — /// no sink-side axis pick or fallback chain. /// /// /// R3-W5: retail's own CPhysicsObj::DoInterpretedMotion RETURNS a /// result (result == 0 = the motion table found a cycle for this /// id; nonzero = MotionTableManagerError.MotionFailed/no-table) — /// CMotionInterp::DoInterpretedMotion's caller (raw 305591-305610) /// gates BOTH the add_to_queue call AND the /// InterpretedMotionState::ApplyMotion state-write on that result. /// This matters concretely for the very first dispatch of every /// apply_interpreted_movement call — the STYLE/stance id /// (current_style, always >= 0x80000000) has no /// locomotion MotionData entry in the dat, so /// CMotionTable::DoObjectMotion genuinely fails for it; if that /// failure isn't propagated, the state-write's negative-motion branch /// (: arg2 < 0 → /// forward_command = 0x41000003) clobbers ForwardCommand /// BEFORE the very next line reads it for the real forward dispatch — /// confirmed regression against the 183-case live retail-observer trace /// (RetailObserverTraceConformanceTests) when this was void. /// Return true when the underlying PerformMovement call /// succeeded (MotionTableManagerError.Success). /// bool ApplyMotion(uint motion, float speed); /// /// StopInterpretedMotion notification for an axis the incoming /// state cleared (sidestep 0x6500000F / turn 0x6500000D). Unconditional /// (no contact gate) per apply_interpreted_movement. /// /// Same result-propagation rationale as /// — retail's CPhysicsObj::StopInterpretedMotion also returns a /// result that gates the post-stop add_to_queue + state-removal. bool StopMotion(uint motion); /// /// R4-V5 wedge fix — retail CPhysicsObj::StopCompletely_Internal /// (0x0050ead0) → CPartArray::StopCompletelyInternal (0x00518890) /// → MotionTableManager::PerformMovement(MovementStruct{type=5}): /// the ANIMATION-side full stop dispatched from the middle of /// CMotionInterp::StopCompletely (raw @00527e90). The manager /// queues its pending_animations entry UNCONDITIONALLY for type 5, and /// that entry's AnimationDone → MotionDone is the matched pop for the A9 /// pending_motions node StopCompletely enqueues right after this /// call — without it the A9 node is an orphan and MotionsPending /// never drains at idle (the 2026-07-03 moveto wedge). Default /// implementation returns true (the null-sink "no animation /// backend" posture — bare tests and sink-less interps are unaffected). /// bool StopCompletely() => true; } /// /// One entry of the inbound action list (retail MotionItem / /// InterpretedMotionState.actions): a one-shot command with the /// 15-bit server action stamp + autonomy bit /// (u16 ((stamp & 0x7FFF) | (autonomous ? 0x8000 : 0))). /// public readonly record struct InboundMotionAction( uint Command, int Stamp, bool Autonomous, float Speed); /// /// A fully-decoded inbound InterpretedMotionState: wire fields where /// present, retail UnPack defaults where absent /// (InterpretedMotionState::UnPack 0x0051f400 / /// ctor 0x0051e8d0). A flags=0 "empty" UM decodes to exactly /// — a retail-verbatim full stop. /// 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? 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, }; }