feat(physics): P5 commit 2 - arm the ConstraintManager leash on accepted positions (#167)
Wire ConstraintManager.ConstrainTo at every current acdream inbound-position acceptance seam, matching retail SmartBox::HandleReceivedPosition (0x00453fd0): - Remote (player + NPC): LiveEntityNetworkUpdateController arms right after the hard-teleport branch (remotePlacementRequired) returns - reaching that point already means MoveOrTeleport did NOT hard-place - anchored to the object's own live IPhysicsObjHost.Position. - Local player teleport: PlayerMovementController.SetPositionCore now runs UnConstrain (retail teleport_hook 0x00514ed0, previously a no-op because nothing armed the leash) then re-arms anchored to the just-snapped position, composing with the existing StopCompletelyAtPhysicsObjectBoundary velocity zero rather than duplicating it. CommitPreparedPosition mirrors the same pair for the deferred player-mode-entry commit path. - Local player ForcePosition: PlayerMovementController.BlipPosition arms with NO preceding UnConstrain (retail BlipPlayer/SetPositionSimple survives motion/velocity/stick, and the leash is no different). Push PhysicsBody.IsFullyConstrained from PositionManager.IsFullyConstrained at the SAME per-tick chokepoint each pump already runs AdjustOffset (PlayerMovementController.Update, RuntimeRemotePhysicsUpdater.Tick/TickHidden) so TS-35's read gate in jump_is_allowed sees live state instead of a stub that is never written. Tests: local-player arm/teardown/rearm/taper/jump-refusal (Runtime.Tests, PlayerMovementControllerTests), remote-tick IsFullyConstrained push (Runtime.Tests, RuntimePhysicsStateTests). Full Core/Runtime/App suites green with no regressions.
This commit is contained in:
parent
378d0b6ca0
commit
7719d25bc5
5 changed files with 330 additions and 3 deletions
|
|
@ -564,6 +564,82 @@ public sealed class RuntimePhysicsStateTests
|
|||
Assert.Equal(Vector3.Zero, cycle);
|
||||
}
|
||||
|
||||
// Campaign P Slice P5 (2026-07-30, #167): the remote per-tick pump pushes
|
||||
// ConstraintManager.IsFullyConstrained onto PhysicsBody.IsFullyConstrained
|
||||
// exactly like the local player's per-tick pump
|
||||
// (PlayerMovementControllerTests). The App-layer arm site
|
||||
// (LiveEntityNetworkUpdateController's remote UpdatePosition acceptance)
|
||||
// isn't reachable from here, so this test arms the leash directly through
|
||||
// the bound host's PositionManager — exactly the call shape that site
|
||||
// makes — and proves RuntimeRemotePhysicsUpdater.Tick's push keeps
|
||||
// Body.IsFullyConstrained current.
|
||||
[Fact]
|
||||
public void RemotePhysicsTickPushesIsFullyConstrainedFromTheArmedLeash()
|
||||
{
|
||||
using var lifetime = new RuntimeEntityObjectLifetime();
|
||||
RuntimeEntityRecord record =
|
||||
lifetime.Entities.AddActive(Spawn(0x70000027u, 1));
|
||||
var remote = new RemoteMotion();
|
||||
remote.Body.Position = new Vector3(10f, 20f, 5f);
|
||||
remote.Body.Orientation = Quaternion.Identity;
|
||||
remote.Body.TransientState = TransientStateFlags.Active
|
||||
| TransientStateFlags.Contact
|
||||
| TransientStateFlags.OnWalkable;
|
||||
// SetRemoteMotion binds the reader that resolves record.PhysicsHost —
|
||||
// do not also call BindCanonicalRuntime here, it is already bound.
|
||||
lifetime.Physics.SetRemoteMotion(record, remote);
|
||||
lifetime.Physics.AcknowledgeSpatialProjection(record, spatial: true);
|
||||
|
||||
EntityPhysicsHost host = new(
|
||||
record.ServerGuid,
|
||||
getPosition: () => new Position(
|
||||
record.FullCellId, remote.Body.Position, remote.Body.Orientation),
|
||||
getVelocity: () => remote.Body.Velocity,
|
||||
getRadius: () => 0.48f,
|
||||
inContact: () => remote.Body.InContact,
|
||||
minterpMaxSpeed: () => null,
|
||||
curTime: () => 0d,
|
||||
physicsTimerTime: () => 0d,
|
||||
getObjectA: _ => null,
|
||||
handleUpdateTarget: _ => { },
|
||||
interruptCurrentMovement: () => { });
|
||||
// Mirrors EntityPhysicsHostComposition.InstallOrRebind + the
|
||||
// production MarkFullPhysicsHostBound call
|
||||
// (LiveEntityMotionRuntimeController.EnsureRemoteMotionBindings).
|
||||
lifetime.Physics.InstallOrRebindPhysicsHost(record, host);
|
||||
remote.MarkFullPhysicsHostBound();
|
||||
Assert.Same(host, remote.Host);
|
||||
|
||||
// Same call shape as the LiveEntityNetworkUpdateController arm site
|
||||
// this slice adds: anchored to the object's own position, tight
|
||||
// synthetic band so a single tick's raw root-motion pass-through (the
|
||||
// interp queue is empty, so RemoteMotionCombiner.ComposeOffset leaves
|
||||
// it unmodified) overshoots 90% of max.
|
||||
host.PositionManager.ConstrainTo(host.Position, startDistance: 1f, maxDistance: 2f);
|
||||
Assert.False(remote.Body.IsFullyConstrained); // stub default, not yet pushed
|
||||
|
||||
var updater = new RuntimeRemotePhysicsUpdater(lifetime.Physics);
|
||||
Assert.True(updater.Tick(
|
||||
record,
|
||||
remote,
|
||||
objectScale: 1f,
|
||||
sequencer: null,
|
||||
dt: 0.1f,
|
||||
objectClockEpoch: record.ObjectClockEpoch,
|
||||
new MotionDeltaFrame
|
||||
{
|
||||
Origin = new Vector3(10f, 0f, 0f), // one huge tick, past max
|
||||
Orientation = Quaternion.Identity,
|
||||
},
|
||||
radius: 0.48f,
|
||||
height: 1.835f,
|
||||
liveCenterX: 1,
|
||||
liveCenterY: 1));
|
||||
|
||||
Assert.True(host.PositionManager.IsFullyConstrained());
|
||||
Assert.True(remote.Body.IsFullyConstrained);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PhysicsBodyAcquisitionIsCanonicalAndRejectsGuidReuse()
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue