fix #270: retry spawn placement until first success (lost-cell recovery analog) + attributed probes

The creation-only spawn placement could no-op or fail during the login
flood (cell id not yet hydrated / streaming collision not resident) with
nothing retrying - monsters created in that window stayed
airborne-flagged forever and their action animations remained refused.
Retail's answer to 'object addressed before its cell exists' is the
CObjectMaint lost-cell list (GotoLostCell): park, re-place when the cell
is available. The UM dispatch path now retries SeedRemoteSpawnPlacement
while the body has never been successfully placed (no contact AND no
stored plane); one success ends the retries.

Probes: [SPAWN-PLACE] logs each placement outcome (guid/cell/ok/
contact/walkable); [UM-ACT]/[MT-FAIL] now carry the owning guid via
MotionInterpreter.DiagnosticGuid (probe-identity-attribution lesson)
plus the body's live contact/walkable flags.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-07-30 23:57:38 +02:00
parent 4da25a442b
commit 807fdb5f7f
3 changed files with 35 additions and 8 deletions

View file

@ -190,6 +190,12 @@ internal sealed class LiveEntityNetworkUpdateController
| AcDream.Core.Physics.ObjectInfoState.EdgeSlide
: AcDream.Core.Physics.ObjectInfoState.EdgeSlide,
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}");
}
if (!placement.Ok)
return; // unplaceable — stays airborne, like a failed retail placement
@ -720,15 +726,24 @@ internal sealed class LiveEntityNetworkUpdateController
update.Guid);
remote.Body.Orientation = entity.Rotation;
remote.Body.Position = entity.Position;
// #270: run the retail spawn placement so the fresh body has
// real ground contact BEFORE the funnel below dispatches this
// packet's actions — a first-ever-UM attack swing needs
// contact_allows_move true to animate.
}
// #270: run the retail spawn placement 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)
{
SeedRemoteSpawnPlacement(
remote,
update.Guid,
entity,
entity.Position,
remote.Body.Position,
// Interior live entities carry ParentCellId; outdoor live
// entities carry the outdoor landcell in EffectCellId (see
// WorldEntity's cell-field docs). 0 → helper no-ops.