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;
|
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,
|
||||||
|
worldPos + new System.Numerics.Vector3(0f, 0f, -0.5f),
|
||||||
cellId,
|
cellId,
|
||||||
radius,
|
sphereRadius: radius,
|
||||||
height,
|
sphereHeight: height,
|
||||||
stepUpHeight: 0.4f,
|
stepUpHeight: 0.4f,
|
||||||
stepDownHeight: 0.4f,
|
stepDownHeight: 0.4f,
|
||||||
moverFlags: IsPlayerGuid(serverGuid)
|
isOnGround: false,
|
||||||
? AcDream.Core.Physics.ObjectInfoState.IsPlayer
|
body: remote.Body,
|
||||||
| AcDream.Core.Physics.ObjectInfoState.EdgeSlide
|
moverFlags: moverFlags,
|
||||||
: AcDream.Core.Physics.ObjectInfoState.EdgeSlide,
|
|
||||||
movingEntityId: entity.Id);
|
movingEntityId: entity.Id);
|
||||||
if (AcDream.Core.Physics.PhysicsDiagnostics.DumpMotionEnabled)
|
if (AcDream.Core.Physics.PhysicsDiagnostics.DumpMotionEnabled)
|
||||||
{
|
{
|
||||||
Console.WriteLine(
|
Console.WriteLine(
|
||||||
$"[SPAWN-PLACE] guid={serverGuid:X8} cell=0x{cellId:X8} ok={placement.Ok} "
|
$"[SPAWN-PLACE] guid={serverGuid:X8} cell=0x{cellId:X8} ok={settle.Ok} "
|
||||||
+ $"contact={placement.InContact} walkable={placement.OnWalkable}");
|
+ $"contact={settle.InContact} walkable={settle.OnWalkable} "
|
||||||
|
+ $"z={worldPos.Z:F3}->{settle.Position.Z:F3}");
|
||||||
}
|
}
|
||||||
if (!placement.Ok)
|
if (!settle.Ok || !settle.InContact)
|
||||||
return; // unplaceable — stays airborne, like a failed retail placement
|
return; // no floor within reach — stays airborne like retail's fall
|
||||||
|
|
||||||
remote.Body.Position = placement.Position;
|
remote.Body.Position = settle.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;
|
|
||||||
}
|
|
||||||
|
|
||||||
AcDream.Core.Physics.PhysicsObjUpdate.CommitSetPositionTransition(
|
AcDream.Core.Physics.PhysicsObjUpdate.CommitSetPositionTransition(
|
||||||
remote.Body,
|
remote.Body,
|
||||||
placement.InContact,
|
settle.InContact,
|
||||||
placement.OnWalkable,
|
settle.OnWalkable,
|
||||||
placement.CollisionNormalValid,
|
settle.CollisionNormalValid,
|
||||||
placement.CollisionNormal,
|
settle.CollisionNormal,
|
||||||
previousContact: false,
|
previousContact: false,
|
||||||
previousOnWalkable: false,
|
previousOnWalkable: false,
|
||||||
remote.Movement.HitGround,
|
remote.Movement.HitGround,
|
||||||
|
|
@ -727,17 +735,19 @@ internal sealed class LiveEntityNetworkUpdateController
|
||||||
remote.Body.Orientation = entity.Rotation;
|
remote.Body.Orientation = entity.Rotation;
|
||||||
remote.Body.Position = entity.Position;
|
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 —
|
// contact BEFORE the funnel below dispatches this packet's actions —
|
||||||
// an attack swing needs contact_allows_move true to animate. Retried
|
// an attack swing needs contact_allows_move true to animate. Retried
|
||||||
// (not creation-only) while the body has never been successfully
|
// (not creation-only) while the body lacks the CONTACT transient
|
||||||
// placed: creation during the login flood can precede streaming
|
// (the flag contact_allows_move reads — NOT ContactPlaneValid, which
|
||||||
// residency or the entity's cell hydration, and retail's own answer
|
// a DR writeback can set from last-known plane data without real
|
||||||
// to "object addressed before its cell exists" is the
|
// contact): creation during the login flood can precede streaming
|
||||||
// CObjectMaint lost-cell list — park it, re-place when the cell is
|
// residency or cell hydration, and retail's own answer to "object
|
||||||
// available. The no-contact-and-no-plane predicate is exactly
|
// addressed before its cell exists" is the CObjectMaint lost-cell
|
||||||
// "never placed"; one success ends the retries.
|
// list — park it, re-place when the cell is available. One grounded
|
||||||
if (!remote.Body.InContact && !remote.Body.ContactPlaneValid)
|
// settle ends the retries; a genuinely airborne remote (mid-jump
|
||||||
|
// player) fails the floor probe harmlessly until it lands.
|
||||||
|
if (!remote.Body.InContact)
|
||||||
{
|
{
|
||||||
SeedRemoteSpawnPlacement(
|
SeedRemoteSpawnPlacement(
|
||||||
remote,
|
remote,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue