feat(physics): port retail complete object frame pipeline
Restore the named-retail object update order across local, remote, static, projectile, animation, shadow, teleport, and effect lifetimes. Separate authoritative root commits from spatial rebucketing, preserve per-owner hook/FIFO ordering, and remove update-path allocations with exact lifecycle and residency gates. Add deterministic conformance, adversarial lifetime, GUID-reuse, pending-cell, quaternion, timestamp, and allocation coverage. Release build is warning-free and all 6,446 tests pass with five intentional skips; retail, architecture, and adversarial reviews are clean. Co-authored-by: OpenAI Codex <codex@openai.com>
This commit is contained in:
parent
31a0889f08
commit
f961d70023
77 changed files with 12513 additions and 1871 deletions
|
|
@ -11,15 +11,11 @@ namespace AcDream.App.Input;
|
|||
/// </summary>
|
||||
public sealed class LocalPlayerOutboundController
|
||||
{
|
||||
private readonly Func<float, Quaternion> _toWireRotation;
|
||||
private readonly Action<string, uint, MovementResult, Vector3, uint, byte> _diagnostic;
|
||||
|
||||
public LocalPlayerOutboundController(
|
||||
Func<float, Quaternion> toWireRotation,
|
||||
Action<string, uint, MovementResult, Vector3, uint, byte> diagnostic)
|
||||
{
|
||||
_toWireRotation = toWireRotation
|
||||
?? throw new ArgumentNullException(nameof(toWireRotation));
|
||||
_diagnostic = diagnostic
|
||||
?? throw new ArgumentNullException(nameof(diagnostic));
|
||||
}
|
||||
|
|
@ -38,14 +34,13 @@ public sealed class LocalPlayerOutboundController
|
|||
if (session is null || hidden)
|
||||
return;
|
||||
|
||||
Quaternion wireRotation = _toWireRotation(controller.Yaw);
|
||||
if (!controller.TryGetOutboundPosition(
|
||||
wireRotation,
|
||||
out uint wireCellId,
|
||||
out Vector3 wirePosition))
|
||||
if (!controller.TryGetOutboundPosition(out Position outboundPosition))
|
||||
{
|
||||
return;
|
||||
}
|
||||
uint wireCellId = outboundPosition.ObjCellId;
|
||||
Vector3 wirePosition = outboundPosition.Frame.Origin;
|
||||
Quaternion wireRotation = outboundPosition.Frame.Orientation;
|
||||
|
||||
if (movement.ShouldSendMovementEvent)
|
||||
TrySendMovement(session, controller, movement);
|
||||
|
|
@ -82,16 +77,14 @@ public sealed class LocalPlayerOutboundController
|
|||
if (session is null || hidden)
|
||||
return;
|
||||
|
||||
Quaternion wireRotation = _toWireRotation(controller.Yaw);
|
||||
if (!controller.TryGetOutboundPosition(
|
||||
wireRotation,
|
||||
out uint wireCellId,
|
||||
out Vector3 wirePosition))
|
||||
if (!controller.TryGetOutboundPosition(out Position position))
|
||||
{
|
||||
return;
|
||||
}
|
||||
uint wireCellId = position.ObjCellId;
|
||||
Vector3 wirePosition = position.Frame.Origin;
|
||||
Quaternion wireRotation = position.Frame.Orientation;
|
||||
|
||||
var position = new Position(wireCellId, wirePosition, wireRotation);
|
||||
if (!controller.ShouldSendPositionEvent(
|
||||
position,
|
||||
controller.ContactPlane,
|
||||
|
|
@ -136,14 +129,13 @@ public sealed class LocalPlayerOutboundController
|
|||
if (session is null || controller is null)
|
||||
return false;
|
||||
|
||||
Quaternion wireRotation = _toWireRotation(controller.Yaw);
|
||||
if (!controller.TryGetOutboundPosition(
|
||||
wireRotation,
|
||||
out uint wireCellId,
|
||||
out Vector3 wirePosition))
|
||||
if (!controller.TryGetOutboundPosition(out Position outboundPosition))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
uint wireCellId = outboundPosition.ObjCellId;
|
||||
Vector3 wirePosition = outboundPosition.Frame.Origin;
|
||||
Quaternion wireRotation = outboundPosition.Frame.Orientation;
|
||||
|
||||
byte contactByte = movement.IsOnGround ? (byte)1 : (byte)0;
|
||||
RawMotionState rawMotionState = BuildRawMotionState(movement);
|
||||
|
|
|
|||
|
|
@ -13,19 +13,27 @@ public sealed class LocalPlayerProjectionController
|
|||
private readonly Func<int> _liveCenterY;
|
||||
private readonly Action<WorldEntity, uint> _syncShadow;
|
||||
private readonly Action<uint, uint> _rebucket;
|
||||
private readonly Func<WorldEntity, bool> _isCurrentVisibleProjection;
|
||||
private readonly Action<WorldEntity> _suspendShadow;
|
||||
|
||||
public LocalPlayerProjectionController(
|
||||
Func<WorldEntity?> resolveEntity,
|
||||
Func<int> liveCenterX,
|
||||
Func<int> liveCenterY,
|
||||
Action<WorldEntity, uint> syncShadow,
|
||||
Action<uint, uint> rebucket)
|
||||
Action<uint, uint> rebucket,
|
||||
Func<WorldEntity, bool> isCurrentVisibleProjection,
|
||||
Action<WorldEntity> suspendShadow)
|
||||
{
|
||||
_resolveEntity = resolveEntity ?? throw new ArgumentNullException(nameof(resolveEntity));
|
||||
_liveCenterX = liveCenterX ?? throw new ArgumentNullException(nameof(liveCenterX));
|
||||
_liveCenterY = liveCenterY ?? throw new ArgumentNullException(nameof(liveCenterY));
|
||||
_syncShadow = syncShadow ?? throw new ArgumentNullException(nameof(syncShadow));
|
||||
_rebucket = rebucket ?? throw new ArgumentNullException(nameof(rebucket));
|
||||
_isCurrentVisibleProjection = isCurrentVisibleProjection
|
||||
?? throw new ArgumentNullException(nameof(isCurrentVisibleProjection));
|
||||
_suspendShadow = suspendShadow
|
||||
?? throw new ArgumentNullException(nameof(suspendShadow));
|
||||
}
|
||||
|
||||
public void Project(
|
||||
|
|
@ -39,11 +47,10 @@ public sealed class LocalPlayerProjectionController
|
|||
|
||||
entity.SetPosition(movement.RenderPosition);
|
||||
entity.ParentCellId = movement.CellId;
|
||||
entity.Rotation = System.Numerics.Quaternion.CreateFromAxisAngle(
|
||||
System.Numerics.Vector3.UnitZ,
|
||||
controller.Yaw - MathF.PI / 2f);
|
||||
if (!hidden)
|
||||
_syncShadow(entity, movement.CellId);
|
||||
// Retail's root is a complete Frame. Project the authoritative body
|
||||
// quaternion directly so pitch/roll from a server correction or DAT
|
||||
// root frame cannot be flattened by the presentation layer.
|
||||
entity.Rotation = controller.BodyOrientation;
|
||||
|
||||
uint currentLandblock;
|
||||
if (movement.CellId != 0 && (movement.CellId & 0xFFFFu) >= 0x0100u)
|
||||
|
|
@ -62,7 +69,22 @@ public sealed class LocalPlayerProjectionController
|
|||
|
||||
// The teleport owner alone projects the destination while the local
|
||||
// controller deliberately retains its frozen source cell.
|
||||
if (controller.State != PlayerState.PortalSpace)
|
||||
_rebucket(entity.ServerGuid, currentLandblock);
|
||||
if (controller.State == PlayerState.PortalSpace)
|
||||
return;
|
||||
|
||||
// SetPositionInternal commits the complete root before changing cell
|
||||
// membership, then replaces collision rows only while the object still
|
||||
// owns a loaded cell. Rebucket is a synchronous callback boundary: it
|
||||
// can move this incarnation to pending or replace the GUID entirely.
|
||||
_rebucket(entity.ServerGuid, currentLandblock);
|
||||
if (hidden
|
||||
|| movement.CellId == 0
|
||||
|| !_isCurrentVisibleProjection(entity))
|
||||
{
|
||||
_suspendShadow(entity);
|
||||
return;
|
||||
}
|
||||
|
||||
_syncShadow(entity, movement.CellId);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -21,18 +21,33 @@ public sealed class RetailLocalPlayerFrameController
|
|||
private readonly Action<PlayerMovementController, MovementResult, bool> _project;
|
||||
private readonly Action<PlayerMovementController, MovementResult, bool> _sendPreNetwork;
|
||||
private readonly Action<PlayerMovementController, bool> _sendPostNetwork;
|
||||
private readonly Func<AcDream.Core.Physics.RetailObjectClockDisposition>
|
||||
_objectClockDisposition;
|
||||
|
||||
private AdvancedFrame? _advancedFrame;
|
||||
|
||||
private readonly record struct AdvancedFrame(
|
||||
PlayerMovementController Controller,
|
||||
MovementResult Movement);
|
||||
MovementResult Movement,
|
||||
bool ObjectAdvanced,
|
||||
bool Hidden,
|
||||
bool ObjectQuantumAdvanced);
|
||||
|
||||
public readonly record struct PresentationFrame(
|
||||
MovementResult Movement,
|
||||
bool Hidden,
|
||||
bool AdvancedBeforeNetwork);
|
||||
|
||||
/// <summary>
|
||||
/// Whether the pre-network Hidden player update admitted a complete object
|
||||
/// quantum whose retained CPartArray pose must be sampled this frame.
|
||||
/// </summary>
|
||||
internal bool HiddenPartPoseDirty => _advancedFrame is
|
||||
{
|
||||
Hidden: true,
|
||||
ObjectQuantumAdvanced: true,
|
||||
};
|
||||
|
||||
public RetailLocalPlayerFrameController(
|
||||
Func<bool> canPresentPlayer,
|
||||
Func<PlayerMovementController?> getController,
|
||||
|
|
@ -42,7 +57,9 @@ public sealed class RetailLocalPlayerFrameController
|
|||
Func<bool> isHidden,
|
||||
Action<PlayerMovementController, MovementResult, bool> project,
|
||||
Action<PlayerMovementController, MovementResult, bool> sendPreNetwork,
|
||||
Action<PlayerMovementController, bool> sendPostNetwork)
|
||||
Action<PlayerMovementController, bool> sendPostNetwork,
|
||||
Func<AcDream.Core.Physics.RetailObjectClockDisposition>?
|
||||
objectClockDisposition = null)
|
||||
{
|
||||
_canPresentPlayer = canPresentPlayer
|
||||
?? throw new ArgumentNullException(nameof(canPresentPlayer));
|
||||
|
|
@ -62,6 +79,8 @@ public sealed class RetailLocalPlayerFrameController
|
|||
?? throw new ArgumentNullException(nameof(sendPreNetwork));
|
||||
_sendPostNetwork = sendPostNetwork
|
||||
?? throw new ArgumentNullException(nameof(sendPostNetwork));
|
||||
_objectClockDisposition = objectClockDisposition
|
||||
?? (() => AcDream.Core.Physics.RetailObjectClockDisposition.Advance);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -75,16 +94,54 @@ public sealed class RetailLocalPlayerFrameController
|
|||
if (!_canPresentPlayer() || controller is null)
|
||||
return;
|
||||
|
||||
if (!float.IsFinite(deltaSeconds) || deltaSeconds <= 0f)
|
||||
{
|
||||
bool rejectedHidden = _isHidden();
|
||||
MovementResult rejected = controller.CapturePresentationResult();
|
||||
_project(controller, rejected, rejectedHidden);
|
||||
_advancedFrame = new AdvancedFrame(
|
||||
controller,
|
||||
rejected,
|
||||
ObjectAdvanced: false,
|
||||
Hidden: rejectedHidden,
|
||||
ObjectQuantumAdvanced: false);
|
||||
return;
|
||||
}
|
||||
|
||||
controller.LocalEntityId = _resolveLocalEntityId();
|
||||
|
||||
bool hidden = _isHidden();
|
||||
if (_objectClockDisposition()
|
||||
is AcDream.Core.Physics.RetailObjectClockDisposition.Suspend)
|
||||
{
|
||||
// CPhysicsObj::update_object returns before advancing time for a
|
||||
// Frozen or cell-less object. Keep the retained projection live,
|
||||
// but emit neither input actions nor AutonomousPosition from a
|
||||
// physics tick that retail did not run.
|
||||
controller.SuspendObjectUpdate(deltaSeconds);
|
||||
MovementResult paused = controller.CapturePresentationResult();
|
||||
_project(controller, paused, hidden);
|
||||
_advancedFrame = new AdvancedFrame(
|
||||
controller,
|
||||
paused,
|
||||
ObjectAdvanced: false,
|
||||
Hidden: hidden,
|
||||
ObjectQuantumAdvanced: false);
|
||||
return;
|
||||
}
|
||||
|
||||
MovementResult movement = hidden
|
||||
? controller.TickHidden(deltaSeconds, _handleTargeting)
|
||||
: controller.Update(deltaSeconds, _captureInput(), _handleTargeting);
|
||||
|
||||
_project(controller, movement, hidden);
|
||||
_sendPreNetwork(controller, movement, hidden);
|
||||
_advancedFrame = new AdvancedFrame(controller, movement);
|
||||
_advancedFrame = new AdvancedFrame(
|
||||
controller,
|
||||
movement,
|
||||
ObjectAdvanced: true,
|
||||
Hidden: hidden,
|
||||
ObjectQuantumAdvanced: controller.AdvancedObjectQuantumLastTick);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -106,7 +163,15 @@ public sealed class RetailLocalPlayerFrameController
|
|||
advanced.Movement,
|
||||
controller);
|
||||
_project(controller, movement, hidden);
|
||||
_advancedFrame = new AdvancedFrame(controller, movement);
|
||||
_advancedFrame = new AdvancedFrame(
|
||||
controller,
|
||||
movement,
|
||||
advanced.ObjectAdvanced,
|
||||
advanced.Hidden,
|
||||
advanced.ObjectQuantumAdvanced);
|
||||
|
||||
if (!advanced.ObjectAdvanced)
|
||||
return;
|
||||
}
|
||||
|
||||
_sendPostNetwork(controller, hidden);
|
||||
|
|
@ -131,7 +196,10 @@ public sealed class RetailLocalPlayerFrameController
|
|||
MovementResult movement = RefreshSpatialFields(
|
||||
advanced.Movement,
|
||||
controller);
|
||||
frame = new PresentationFrame(movement, hidden, AdvancedBeforeNetwork: true);
|
||||
frame = new PresentationFrame(
|
||||
movement,
|
||||
hidden,
|
||||
AdvancedBeforeNetwork: advanced.ObjectAdvanced);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue