feat(headless): complete deterministic bot command parity

This commit is contained in:
Erik 2026-07-27 08:23:36 +02:00
parent 7e8acb74dd
commit 38e83640d9
37 changed files with 2805 additions and 295 deletions

View file

@ -411,6 +411,7 @@ public sealed class PlayerMovementController
_animationRootMotionScratch = new();
private readonly AcDream.Core.Physics.Motion.MotionDeltaFrame
_positionManagerDeltaScratch = new();
private bool _externalMovementEventPending;
// ── R4-V5: the verbatim retail MoveToManager replaces B.6 auto-walk ──
// The B.6 DriveServerAutoWalk overlay (synthesized turn-first phase,
@ -856,6 +857,35 @@ public sealed class PlayerMovementController
return true;
}
/// <summary>
/// Applies a command-originated retail posture through the same
/// CPhysicsObj movement boundary used by keyboard input. The next admitted
/// object turn emits the resulting raw movement state exactly once.
/// </summary>
public bool RequestPosture(uint motion)
{
if (motion is not (
MotionCommand.Ready
or MotionCommand.Crouch
or MotionCommand.Sitting
or MotionCommand.Sleeping))
{
return false;
}
TakeControlFromServer();
var parameters =
new AcDream.Core.Physics.Motion.MovementParameters();
if (DoMotionAtPhysicsObjectBoundary(motion, parameters)
!= WeenieError.None)
{
return false;
}
_externalMovementEventPending = true;
return true;
}
public void SetCharacterSkills(int runSkill, int jumpSkill)
{
_weenie.SetSkills(runSkill, jumpSkill);
@ -1455,8 +1485,12 @@ public sealed class PlayerMovementController
_motion.set_hold_run(input.Run, interrupt: false);
}
bool externallyRequestedMovementEvent =
_externalMovementEventPending;
_externalMovementEventPending = false;
bool motionEdgeFired = false;
bool movementEventRequested = false;
bool movementEventRequested =
externallyRequestedMovementEvent;
{
bool userInputEdge = input.Run != _prevRunHeld
|| input.Forward != _prevForwardHeld
@ -1511,7 +1545,7 @@ public sealed class PlayerMovementController
// an immediate SendMovementEvent. Mouse-origin turn updates below
// deliberately do not: CameraSet reports them on its separate
// half-second cadence.
movementEventRequested = motionEdgeFired;
movementEventRequested |= motionEdgeFired;
// Turn channel. Retail CameraSet::Rotate (0x00458310) feeds mouse
// input through CommandInterpreter::MovePlayer as TurnLeft /
@ -1937,6 +1971,14 @@ public sealed class PlayerMovementController
outForwardCmd = MotionCommand.WalkBackward;
outForwardSpeed = 1.0f;
}
else if (_motion.RawState.ForwardCommand is (
MotionCommand.Crouch
or MotionCommand.Sitting
or MotionCommand.Sleeping))
{
outForwardCmd = _motion.RawState.ForwardCommand;
outForwardSpeed = _motion.RawState.ForwardSpeed;
}
// Source the outbound axis from the canonical input-owned raw channel.
// CameraInstantMouseLook maps keyboard TurnLeft/TurnRight into this
@ -1979,7 +2021,8 @@ public sealed class PlayerMovementController
|| outTurnCmd != _prevTurnCmd
|| !FloatsEqual(outForwardSpeed, _prevForwardSpeed)
|| runHold != _prevRunHold
|| motionEdgeFired;
|| motionEdgeFired
|| externallyRequestedMovementEvent;
bool mouseMovementEventDue = _mouseMovementEventPending
|| (_mouseMovementEventCandidate