feat(R3-W4): ground transitions + lifecycle verbatim; K-fix18 DELETED (closes J8, J10, J11-shape, J12, J13, J18, J19)
Core (dedicated agent, independently reviewed): HitGround 0x00528ac0 / LeaveGround 0x00528b00 verbatim (creature+gravity gates, the RemoveLinkAnimations seam — K-fix18's retail mechanism — velocity via GetLeaveGroundVelocity with the autonomous flag, jump-state resets, apply_current_movement re-sync); enter_default_state 0x00528c80 per A8 (fresh states, InitializeMotionTables seam, sentinel APPENDED without draining pending_motions — pinned, Initted=1, LeaveGround tail); Initted gates; the A3 IsThePlayer dual dispatch in apply_current_movement / ReportExhaustion / SetWeenieObject / SetPhysicsObject (a remote player routes INTERPRETED — the ACE-divergence pin); set_hold_run 0x00528b70 + SetHoldKey 0x00528bb0 (XOR guard, None-only-from-Run); adjust_motion creature guard wired (TS-34 retired); PhysicsBody.LastMoveWasAutonomous + set_local_velocity(autonomous). Port discovery: retail's apply_raw_movement 0x005287e0 / apply_interpreted_movement 0x00528600 ARE the already-shipped D6.2a/funnel functions — the dual dispatch composes them instead of duplicating. App cutover (orchestrator): the skipTransitionLink flag + both K-fix18 call sites DELETED (AP-74 retired). MotionInterpreter.DefaultSink routes apply_current_movement's interpreted branch through the REAL funnel dispatch when a sink is bound — so a remote's LeaveGround engages Falling via the contact-gated funnel, replacing the forced SetCycle (J19); the per-remote MotionTableDispatchSink is now PERSISTENT (EnsureRemoteMotionBindings: DefaultSink + RemoveLinkAnimations + InitializeMotionTables seams, idempotent from both the UM and VectorUpdate paths; wire velocity re-applied after LeaveGround so it stays authoritative). Player: seams bound to the player sequencer; the controller's grounded→airborne EDGE now fires LeaveGround (jump() clears OnWalkable and the same frame's transition detection fires it — retail's order; walk-off-a-ledge gets the momentum fallback + link strip it never had); the manual jump-block LeaveGround deleted; LastMoveWasAutonomous set at the controller chokepoint (W6 refines). Trace S8 re-expressed as the retail mechanism (Falling dispatch + RemoveAllLinkAnimations = same final state the flag produced). 43 new lifecycle tests. Registers: TS-34 + AP-74 retired; TS-38, AP-77, AP-78 added. Full suite: 3,665 passed. Live smoke: in-world clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
af4764443f
commit
e214acdf23
8 changed files with 1575 additions and 124 deletions
|
|
@ -412,6 +412,11 @@ public sealed class GameWindow : IDisposable
|
|||
{
|
||||
public AcDream.Core.Physics.PhysicsBody Body;
|
||||
public AcDream.Core.Physics.MotionInterpreter Motion;
|
||||
/// <summary>R3-W4: the persistent per-remote dispatch sink (created
|
||||
/// once by EnsureRemoteMotionBindings; also bound as
|
||||
/// Motion.DefaultSink so LeaveGround/HitGround re-applies dispatch
|
||||
/// cycles through the motion-table backend).</summary>
|
||||
public AcDream.Core.Physics.Motion.MotionTableDispatchSink? Sink;
|
||||
/// <summary>Last UpdatePosition timestamp — drives body.update_object sub-stepping.</summary>
|
||||
public double LastServerPosTime;
|
||||
/// <summary>Last known server position — kept for diagnostics / HUD.</summary>
|
||||
|
|
@ -4176,6 +4181,45 @@ public sealed class GameWindow : IDisposable
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// R3-W4: one-time per-remote wiring of the animation-dispatch stack —
|
||||
/// the persistent <see cref="RemoteMotion.Sink"/> (ObservedOmega turn
|
||||
/// callbacks), <c>Motion.DefaultSink</c> (so
|
||||
/// <c>apply_current_movement</c>'s interpreted branch dispatches cycles
|
||||
/// — the retail mechanism behind the deleted K-fix18 forced-Falling),
|
||||
/// and the <c>RemoveLinkAnimations</c>/<c>InitializeMotionTables</c>
|
||||
/// seams (retail CPhysicsObj::RemoveLinkAnimations 0x0050fe20 /
|
||||
/// InitializeMotionTables). Idempotent; safe from both the UM path and
|
||||
/// the VectorUpdate path regardless of arrival order.
|
||||
/// </summary>
|
||||
private AcDream.Core.Physics.Motion.MotionTableDispatchSink? EnsureRemoteMotionBindings(
|
||||
RemoteMotion rm, AnimatedEntity ae)
|
||||
{
|
||||
if (ae.Sequencer is null)
|
||||
return rm.Sink;
|
||||
if (rm.Sink is not null)
|
||||
return rm.Sink;
|
||||
|
||||
var rmForSink = rm;
|
||||
rm.Sink = new AcDream.Core.Physics.Motion.MotionTableDispatchSink(ae.Sequencer)
|
||||
{
|
||||
TurnApplied = (turnMotion, turnSpeed) =>
|
||||
{
|
||||
float signed = (turnMotion & 0xFFu) == 0x0E
|
||||
? -System.MathF.Abs(turnSpeed)
|
||||
: turnSpeed;
|
||||
rmForSink.ObservedOmega = new System.Numerics.Vector3(
|
||||
0f, 0f, -(System.MathF.PI / 2f) * signed);
|
||||
},
|
||||
TurnStopped = () =>
|
||||
rmForSink.ObservedOmega = System.Numerics.Vector3.Zero,
|
||||
};
|
||||
rm.Motion.DefaultSink = rm.Sink;
|
||||
rm.Motion.RemoveLinkAnimations = ae.Sequencer.RemoveAllLinkAnimations;
|
||||
rm.Motion.InitializeMotionTables = () => ae.Sequencer.Manager.InitializeState();
|
||||
return rm.Sink;
|
||||
}
|
||||
|
||||
private bool RemoveLiveEntityByServerGuid(uint serverGuid)
|
||||
{
|
||||
if (!_entitiesByServerGuid.TryGetValue(serverGuid, out var existingEntity))
|
||||
|
|
@ -4623,31 +4667,13 @@ public sealed class GameWindow : IDisposable
|
|||
ims.Actions = actionList;
|
||||
}
|
||||
|
||||
// R2-Q5: funnel dispatches go STRAIGHT into the motion-
|
||||
// table stack (GetObjectSequence + is_allowed decide) —
|
||||
// RemoteMotionSink's single-cycle pick + fallback chains
|
||||
// are deleted (AP-73 retired). The callbacks are the
|
||||
// ObservedOmega seam: seed remote rotation from the wire
|
||||
// turn this tick (retail rotates from sequence omega in
|
||||
// the per-tick apply_physics chain — R6; register row).
|
||||
AcDream.Core.Physics.Motion.MotionTableDispatchSink? sink = null;
|
||||
if (ae.Sequencer is not null)
|
||||
{
|
||||
var rmForSink = remoteMot;
|
||||
sink = new AcDream.Core.Physics.Motion.MotionTableDispatchSink(ae.Sequencer)
|
||||
{
|
||||
TurnApplied = (turnMotion, turnSpeed) =>
|
||||
{
|
||||
float signed = (turnMotion & 0xFFu) == 0x0E
|
||||
? -System.MathF.Abs(turnSpeed)
|
||||
: turnSpeed;
|
||||
rmForSink.ObservedOmega = new System.Numerics.Vector3(
|
||||
0f, 0f, -(System.MathF.PI / 2f) * signed);
|
||||
},
|
||||
TurnStopped = () =>
|
||||
rmForSink.ObservedOmega = System.Numerics.Vector3.Zero,
|
||||
};
|
||||
}
|
||||
// R2-Q5/R3-W4: funnel dispatches go STRAIGHT into the
|
||||
// motion-table stack (GetObjectSequence + is_allowed
|
||||
// decide). The sink is PERSISTENT per remote and also
|
||||
// bound as Motion.DefaultSink + the RemoveLink/InitTables
|
||||
// seams (EnsureRemoteMotionBindings) so LeaveGround/
|
||||
// HitGround dispatch through the same backend.
|
||||
var sink = EnsureRemoteMotionBindings(remoteMot, ae);
|
||||
remoteMot.Motion.MoveToInterpretedState(ims, sink);
|
||||
}
|
||||
}
|
||||
|
|
@ -4813,26 +4839,24 @@ public sealed class GameWindow : IDisposable
|
|||
| AcDream.Core.Physics.TransientStateFlags.OnWalkable);
|
||||
rm.Body.State |= AcDream.Core.Physics.PhysicsStateFlags.Gravity;
|
||||
|
||||
// K-fix10 (2026-04-26): force the Falling animation cycle on
|
||||
// the remote so the legs match the arc. Mirrors the local
|
||||
// player's UpdatePlayerAnimation path which sets
|
||||
// animCommand = Falling whenever !IsOnGround.
|
||||
//
|
||||
// K-fix18 (2026-04-26): pass skipTransitionLink:true so the
|
||||
// RunForward→Falling transition frames don't play first.
|
||||
// Without that flag the remote stood still for ~100 ms at
|
||||
// the start of the jump while the link drained, then
|
||||
// folded into Falling. Skipping the link makes the pose
|
||||
// engage immediately on jump start.
|
||||
// R3-W4 (J19 — K-fix10/K-fix18 DELETED): the retail mechanism.
|
||||
// The remote's ground departure fires LeaveGround (0x00528b00):
|
||||
// strips pending transition links (the RemoveLinkAnimations
|
||||
// seam) + re-applies movement through DefaultSink, whose
|
||||
// contact-gated funnel dispatch engages Falling — no forced
|
||||
// SetCycle, no skip flag. The wire velocity/omega are re-applied
|
||||
// AFTER so they stay authoritative over LeaveGround's
|
||||
// state-derived velocity write (adaptation note: retail's
|
||||
// equivalence comes from the per-tick transition-sweep order —
|
||||
// R6 scope).
|
||||
if (_entitiesByServerGuid.TryGetValue(update.Guid, out var ent)
|
||||
&& _animatedEntities.TryGetValue(ent.Id, out var ae)
|
||||
&& ae.Sequencer is not null)
|
||||
{
|
||||
uint style = ae.Sequencer.CurrentStyle != 0
|
||||
? ae.Sequencer.CurrentStyle
|
||||
: 0x8000003Du; // NonCombat default
|
||||
ae.Sequencer.SetCycle(style, AcDream.Core.Physics.MotionCommand.Falling, 1.0f,
|
||||
skipTransitionLink: true);
|
||||
EnsureRemoteMotionBindings(rm, ae);
|
||||
rm.Motion.LeaveGround();
|
||||
rm.Body.Velocity = update.Velocity;
|
||||
rm.Body.Omega = update.Omega;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -10241,14 +10265,11 @@ public sealed class GameWindow : IDisposable
|
|||
{
|
||||
animScale = s;
|
||||
}
|
||||
// K-fix18 (2026-04-26): when transitioning into Falling
|
||||
// (jump start), skip the link so the legs engage Falling
|
||||
// immediately. Without this the local player visibly
|
||||
// stood still for ~100 ms at the start of every jump
|
||||
// while the RunForward→Falling transition link drained.
|
||||
// For everything else (Walk → Run, Run → Ready, etc.) we
|
||||
// keep the link so transitions stay smooth.
|
||||
bool skipLink = animCommand == AcDream.Core.Physics.MotionCommand.Falling;
|
||||
// R3-W4: the K-fix18 skipLink flag is DELETED — the instant
|
||||
// Falling engage comes from the controller's grounded→airborne
|
||||
// edge firing MotionInterpreter.LeaveGround, whose
|
||||
// RemoveLinkAnimations seam (bound at sequencer creation)
|
||||
// strips pending links exactly where retail does.
|
||||
|
||||
// #45 (2026-05-06): scale sidestep speedMod to match ACE's
|
||||
// wire formula. PlayerMovementController hands us a raw
|
||||
|
|
@ -10270,8 +10291,7 @@ public sealed class GameWindow : IDisposable
|
|||
* 0.5f;
|
||||
}
|
||||
|
||||
ae.Sequencer.SetCycle(fullStyle, animCommand, animSpeed * animScale,
|
||||
skipTransitionLink: skipLink);
|
||||
ae.Sequencer.SetCycle(fullStyle, animCommand, animSpeed * animScale);
|
||||
}
|
||||
|
||||
// Legacy path: update the manual slerp fields (for entities without sequencer)
|
||||
|
|
@ -12965,6 +12985,13 @@ public sealed class GameWindow : IDisposable
|
|||
&& playerAE.Sequencer is { } playerSeq)
|
||||
{
|
||||
_playerController.AttachCycleVelocityAccessor(() => playerSeq.CurrentVelocity);
|
||||
// R3-W4: bind the player interp's retail seams to the player
|
||||
// sequencer — LeaveGround/HitGround strip transition links here
|
||||
// (the K-fix18 replacement); DefaultSink stays null until R3-W6
|
||||
// (UpdatePlayerAnimation still drives the player's cycles).
|
||||
_playerController.Motion.RemoveLinkAnimations = playerSeq.RemoveAllLinkAnimations;
|
||||
_playerController.Motion.InitializeMotionTables =
|
||||
() => playerSeq.Manager.InitializeState();
|
||||
}
|
||||
|
||||
var q = playerEntity.Rotation;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue