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:
parent
4da25a442b
commit
807fdb5f7f
3 changed files with 35 additions and 8 deletions
|
|
@ -45,6 +45,8 @@ internal sealed class LiveEntityMotionRuntimeController
|
|||
rm.Sink = new AcDream.Core.Physics.Motion.MotionTableDispatchSink(sequencer);
|
||||
rm.Motion.DefaultSink = rm.Sink;
|
||||
}
|
||||
// #270 probe identity — strip with the probes.
|
||||
rm.Motion.DiagnosticGuid = serverGuid;
|
||||
// #174 (2026-07-05): the RemoveLinkAnimations seam is retail
|
||||
// CPhysicsObj::RemoveLinkAnimations 0x0050fe20 — a TAILCALL to
|
||||
// CPartArray::HandleEnterWorld 0x00517d70 →
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -678,6 +678,14 @@ public sealed class MotionInterpreter : IMotionDoneSink
|
|||
/// </summary>
|
||||
public Action? RemoveLinkAnimations { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// #270 diagnostic identity (2026-07-30): the owning entity's server
|
||||
/// guid, stamped by the binding layer so [UM-ACT]/[MT-FAIL] probe lines
|
||||
/// are attributable (probe-identity-attribution lesson). 0 = unset.
|
||||
/// Strip with the #270 probes.
|
||||
/// </summary>
|
||||
public uint DiagnosticGuid { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// R3-W4 no-op seam standing in for retail
|
||||
/// <c>CPhysicsObj::InitializeMotionTables</c>, called from
|
||||
|
|
@ -2779,7 +2787,7 @@ public sealed class MotionInterpreter : IMotionDoneSink
|
|||
if (PhysicsDiagnostics.DumpMotionEnabled)
|
||||
{
|
||||
Console.WriteLine(
|
||||
$"[UM-ACT] cmd=0x{a.Command:X8} stamp={incoming} stored={stored} "
|
||||
$"[UM-ACT] guid={DiagnosticGuid:X8} cmd=0x{a.Command:X8} stamp={incoming} stored={stored} "
|
||||
+ $"newer={newer} auton={a.Autonomous} localSkip={IsLocalPlayer && a.Autonomous}");
|
||||
}
|
||||
|
||||
|
|
@ -3038,8 +3046,10 @@ public sealed class MotionInterpreter : IMotionDoneSink
|
|||
if (!dispatchOk && PhysicsDiagnostics.DumpMotionEnabled)
|
||||
{
|
||||
Console.WriteLine(
|
||||
$"[MT-FAIL] motion=0x{motion:X8} speed={p.Speed:F2} "
|
||||
+ $"style=0x{InterpretedState.CurrentStyle:X8} substate=0x{InterpretedState.ForwardCommand:X8}");
|
||||
$"[MT-FAIL] guid={DiagnosticGuid:X8} motion=0x{motion:X8} speed={p.Speed:F2} "
|
||||
+ $"style=0x{InterpretedState.CurrentStyle:X8} substate=0x{InterpretedState.ForwardCommand:X8} "
|
||||
+ $"contact={PhysicsObj.TransientState.HasFlag(TransientStateFlags.Contact)} "
|
||||
+ $"walkable={PhysicsObj.TransientState.HasFlag(TransientStateFlags.OnWalkable)}");
|
||||
}
|
||||
|
||||
if (!dispatchOk)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue