fix #270: settle sweep at spawn placement - contact from the compressed first gravity frame
The [SPAWN-PLACE] probe showed placements succeeding with contact=False: find_placement_pos validates the spot but the sphere sits a few cm above the floor with no touch. Retail gains spawn contact from the FIRST GRAVITY FRAME (every CPhysicsObj simulates, falls, touches); our stationary remotes never run a physics frame. The seed now compresses that settle: a short downward ResolveWithTransition from the server position snaps the body onto the floor, and its touch grants the contact plane + CONTACT/ON_WALKABLE via the verbatim commit. No floor within reach = stays airborne, exactly like retail's fall. The retry predicate now watches the CONTACT transient (the flag contact_allows_move reads) instead of ContactPlaneValid - a DR-tick writeback can set plane DATA from last-known state without real contact, which is why the heavy attackers in the retry-session log got exactly one placement attempt and then 250+ refused Falling dispatches. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
807fdb5f7f
commit
21b3a3f3d3
1 changed files with 43 additions and 33 deletions
|
|
@ -178,43 +178,51 @@ internal sealed class LiveEntityNetworkUpdateController
|
|||
height = 1.835f;
|
||||
}
|
||||
|
||||
ResolveResult placement = _physicsEngine.ResolvePlacement(
|
||||
var moverFlags = IsPlayerGuid(serverGuid)
|
||||
? AcDream.Core.Physics.ObjectInfoState.IsPlayer
|
||||
| AcDream.Core.Physics.ObjectInfoState.EdgeSlide
|
||||
: AcDream.Core.Physics.ObjectInfoState.EdgeSlide;
|
||||
|
||||
// Retail's spawn contact comes from the FIRST GRAVITY FRAME, not the
|
||||
// placement itself: every retail CPhysicsObj simulates, so a freshly
|
||||
// placed creature falls the few centimetres onto the floor and the
|
||||
// transition's touch grants the contact plane. Our stationary remotes
|
||||
// never run a physics frame (the DR tick resolves only movers), so
|
||||
// the settle is compressed here: a short downward sweep from the
|
||||
// server position. Its touch handler produces exactly the state
|
||||
// retail's first frame would (position snapped onto the floor,
|
||||
// contact plane + CONTACT/ON_WALKABLE committed below). A sweep that
|
||||
// finds no floor (true airborne spawn) leaves the body airborne.
|
||||
AcDream.Core.Physics.ResolveResult settle = _physicsEngine.ResolveWithTransition(
|
||||
worldPos,
|
||||
worldPos + new System.Numerics.Vector3(0f, 0f, -0.5f),
|
||||
cellId,
|
||||
radius,
|
||||
height,
|
||||
sphereRadius: radius,
|
||||
sphereHeight: height,
|
||||
stepUpHeight: 0.4f,
|
||||
stepDownHeight: 0.4f,
|
||||
moverFlags: IsPlayerGuid(serverGuid)
|
||||
? AcDream.Core.Physics.ObjectInfoState.IsPlayer
|
||||
| AcDream.Core.Physics.ObjectInfoState.EdgeSlide
|
||||
: AcDream.Core.Physics.ObjectInfoState.EdgeSlide,
|
||||
isOnGround: false,
|
||||
body: remote.Body,
|
||||
moverFlags: moverFlags,
|
||||
movingEntityId: entity.Id);
|
||||
if (AcDream.Core.Physics.PhysicsDiagnostics.DumpMotionEnabled)
|
||||
{
|
||||
Console.WriteLine(
|
||||
$"[SPAWN-PLACE] guid={serverGuid:X8} cell=0x{cellId:X8} ok={placement.Ok} "
|
||||
+ $"contact={placement.InContact} walkable={placement.OnWalkable}");
|
||||
$"[SPAWN-PLACE] guid={serverGuid:X8} cell=0x{cellId:X8} ok={settle.Ok} "
|
||||
+ $"contact={settle.InContact} walkable={settle.OnWalkable} "
|
||||
+ $"z={worldPos.Z:F3}->{settle.Position.Z:F3}");
|
||||
}
|
||||
if (!placement.Ok)
|
||||
return; // unplaceable — stays airborne, like a failed retail placement
|
||||
if (!settle.Ok || !settle.InContact)
|
||||
return; // no floor within reach — stays airborne like retail's fall
|
||||
|
||||
remote.Body.Position = placement.Position;
|
||||
remote.Body.ContactPlaneValid = placement.InContact;
|
||||
if (placement.InContact)
|
||||
{
|
||||
remote.Body.ContactPlane = placement.ContactPlane;
|
||||
remote.Body.ContactPlaneCellId = placement.ContactPlaneCellId;
|
||||
remote.Body.ContactPlaneIsWater = placement.ContactPlaneIsWater;
|
||||
remote.Body.GroundNormal = placement.ContactPlane.Normal;
|
||||
}
|
||||
remote.Body.Position = settle.Position;
|
||||
|
||||
AcDream.Core.Physics.PhysicsObjUpdate.CommitSetPositionTransition(
|
||||
remote.Body,
|
||||
placement.InContact,
|
||||
placement.OnWalkable,
|
||||
placement.CollisionNormalValid,
|
||||
placement.CollisionNormal,
|
||||
settle.InContact,
|
||||
settle.OnWalkable,
|
||||
settle.CollisionNormalValid,
|
||||
settle.CollisionNormal,
|
||||
previousContact: false,
|
||||
previousOnWalkable: false,
|
||||
remote.Movement.HitGround,
|
||||
|
|
@ -727,17 +735,19 @@ internal sealed class LiveEntityNetworkUpdateController
|
|||
remote.Body.Orientation = entity.Rotation;
|
||||
remote.Body.Position = entity.Position;
|
||||
}
|
||||
// #270: run the retail spawn placement so the body has real ground
|
||||
// #270: run the retail spawn settle so the body has real ground
|
||||
// contact BEFORE the funnel below dispatches this packet's actions —
|
||||
// an attack swing needs contact_allows_move true to animate. Retried
|
||||
// (not creation-only) while the body has never been successfully
|
||||
// placed: creation during the login flood can precede streaming
|
||||
// residency or the entity's cell hydration, and retail's own answer
|
||||
// to "object addressed before its cell exists" is the
|
||||
// CObjectMaint lost-cell list — park it, re-place when the cell is
|
||||
// available. The no-contact-and-no-plane predicate is exactly
|
||||
// "never placed"; one success ends the retries.
|
||||
if (!remote.Body.InContact && !remote.Body.ContactPlaneValid)
|
||||
// (not creation-only) while the body lacks the CONTACT transient
|
||||
// (the flag contact_allows_move reads — NOT ContactPlaneValid, which
|
||||
// a DR writeback can set from last-known plane data without real
|
||||
// contact): creation during the login flood can precede streaming
|
||||
// residency or cell hydration, and retail's own answer to "object
|
||||
// addressed before its cell exists" is the CObjectMaint lost-cell
|
||||
// list — park it, re-place when the cell is available. One grounded
|
||||
// settle ends the retries; a genuinely airborne remote (mid-jump
|
||||
// player) fails the floor probe harmlessly until it lands.
|
||||
if (!remote.Body.InContact)
|
||||
{
|
||||
SeedRemoteSpawnPlacement(
|
||||
remote,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue