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,
};
}