feat(L.2b): outbound movement wire parity — RawMotionState default-difference, JumpPack, contact byte
Ports the three retail outbound-movement packers verbatim (decomp-derived golden bytes, confirmed via the Ghidra bridge, cross-checked vs holtburger): - D1 — RawMotionState::Pack (0x0051ed10): new AcDream.Core.Physics.RawMotionState data type (11 fields + actions, retail defaults) + RawMotionStatePacker that sets a flag bit only when the field DIFFERS from its default. MoveToState.Build now takes a RawMotionState instead of presence-based nullable params, so the over-sent forwardSpeed=1.0 / currentHoldKey=None / default per-axis holdkeys are no longer emitted. num_actions packs into bits 11-15 (not "bits 11-31"). - D3 — MoveToStatePack::Pack (0x005168f0) trailing byte = (standingLongjump ? 0x02 : 0) | (contact ? 0x01 : 0); explicit contact/ standingLongjump params (standingLongjump=false honestly until the feature lands). - D4 — JumpAction rewritten to retail JumpPack::Pack (0x00516d10): extent, velocity, full Position, four u16 timestamps, align. Removed the spurious objectGuid/spellId u32s; Position is now packed (it was absent). Body 56 bytes. - Position::Pack (0x005a9640) / Frame::Pack (0x00535130) verified already-correct (cellId, origin xyz, quaternion wxyz); locked with a golden test, no change. GameWindow callers adapted minimally: build the RawMotionState from the existing MovementResult values (behavior preserved except the intended D1 omissions) and pass cellId/position/rotation to the Jump send. Pre-existing MotionInterpreter placeholder struct RawMotionState renamed LegacyRawMotionState (D6/Phase-2 scope, pure rename) to free the name for the retail-faithful type. D5 audit: confirmed a real divergence — retail SendMovementEvent (0x006b4680) stamps only last_sent_position_time after an MTS while SendPositionEvent (0x006b4770) stamps all three; acdream's NotePositionSent stamps all three on both paths. Left unchanged (comments added at both call sites), recorded as register TS-33, deferred to a dedicated cadence-port slice. Tests: RawMotionStatePackTests / MoveToStateGoldenTests / JumpActionTests / PositionPackTests + updated MoveToStateTests / AutonomousPositionTests. Full suite green (Core.Net.Tests 372, full solution 3228 passed / 4 pre-existing skips). Register: TS-24/TS-25 refreshed (packer now supports actions/style; runtime emission still deferred), TS-33 added. Roadmap L.2b shipped note added. Spec: docs/superpowers/specs/2026-06-30-movement-wire-parity-design.md (2-6) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
2c8620ea94
commit
78e163a41e
15 changed files with 1173 additions and 212 deletions
|
|
@ -129,11 +129,23 @@ public enum WeenieError : uint
|
|||
// ── Motion state structs ───────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Raw (network-derived) motion state for the local player.
|
||||
/// Raw (network-derived) motion state for the local player, as consumed by
|
||||
/// the (still-approximate) <see cref="MotionInterpreter"/> input-state path.
|
||||
/// Struct layout in chunk_00520000 starts at offset +0x14 (struct field +0x20 =
|
||||
/// ForwardCommand, +0x28 = ForwardSpeed, etc.).
|
||||
///
|
||||
/// <para>
|
||||
/// Renamed from <c>RawMotionState</c> during the L.2b wire-parity slice
|
||||
/// (2026-06-30) to free that name for the retail-faithful, full-field
|
||||
/// <c>AcDream.Core.Physics.RawMotionState</c> (mirrors
|
||||
/// <c>acclient.h RawMotionState::PackBitfield</c>) used by the outbound
|
||||
/// wire packer. This partial type is D6/Phase-2 territory (the
|
||||
/// <c>adjust_motion</c>/<c>apply_raw_movement</c> input-state port) —
|
||||
/// out of scope for this slice. It is expected to be folded into or
|
||||
/// replaced by the retail-faithful type when D6 lands.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public struct RawMotionState
|
||||
public struct LegacyRawMotionState
|
||||
{
|
||||
/// <summary>Forward/backward motion command (offset +0x20).</summary>
|
||||
public uint ForwardCommand;
|
||||
|
|
@ -149,7 +161,7 @@ public struct RawMotionState
|
|||
public float TurnSpeed;
|
||||
|
||||
/// <summary>Initialize to the idle/ready state (1.0 speed, Ready command).</summary>
|
||||
public static RawMotionState Default() => new()
|
||||
public static LegacyRawMotionState Default() => new()
|
||||
{
|
||||
ForwardCommand = MotionCommand.Ready,
|
||||
ForwardSpeed = 1.0f,
|
||||
|
|
@ -261,7 +273,7 @@ public sealed class MotionInterpreter
|
|||
public IWeenieObject? WeenieObj { get; set; }
|
||||
|
||||
/// <summary>Raw (network-derived) motion state (struct offsets +0x14..+0x44).</summary>
|
||||
public RawMotionState RawState;
|
||||
public LegacyRawMotionState RawState;
|
||||
|
||||
/// <summary>Interpreted motion state derived from raw (struct offsets +0x44..+0x7C).</summary>
|
||||
public InterpretedMotionState InterpretedState;
|
||||
|
|
@ -318,7 +330,7 @@ public sealed class MotionInterpreter
|
|||
|
||||
public MotionInterpreter()
|
||||
{
|
||||
RawState = RawMotionState.Default();
|
||||
RawState = LegacyRawMotionState.Default();
|
||||
InterpretedState = InterpretedMotionState.Default();
|
||||
}
|
||||
|
||||
|
|
@ -326,7 +338,7 @@ public sealed class MotionInterpreter
|
|||
{
|
||||
PhysicsObj = physicsObj;
|
||||
WeenieObj = weenieObj;
|
||||
RawState = RawMotionState.Default();
|
||||
RawState = LegacyRawMotionState.Default();
|
||||
InterpretedState = InterpretedMotionState.Default();
|
||||
}
|
||||
|
||||
|
|
|
|||
71
src/AcDream.Core/Physics/RawMotionState.cs
Normal file
71
src/AcDream.Core/Physics/RawMotionState.cs
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
namespace AcDream.Core.Physics;
|
||||
|
||||
/// <summary>
|
||||
/// Retail hold-key enum (<c>acclient.h</c> <c>enum HoldKey</c>, line 3394).
|
||||
/// </summary>
|
||||
public enum HoldKey : uint
|
||||
{
|
||||
Invalid = 0x0,
|
||||
None = 0x1,
|
||||
Run = 0x2,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// One entry in <see cref="RawMotionState.Actions"/> — a discrete motion
|
||||
/// event (jump, attack, etc.) layered on top of the continuous
|
||||
/// forward/sidestep/turn axes.
|
||||
///
|
||||
/// Retail packs each action as <c>u16 command</c> then
|
||||
/// <c>u16 ((stamp & 0x7FFF) | (autonomous ? 0x8000 : 0))</c>
|
||||
/// (<c>RawMotionState::Pack</c>, 0x0051ed10, decomp lines ~293998-294010).
|
||||
/// </summary>
|
||||
public readonly record struct RawMotionAction(ushort Command, ushort Stamp, bool Autonomous);
|
||||
|
||||
/// <summary>
|
||||
/// Pure-data mirror of retail's <c>RawMotionState</c> struct
|
||||
/// (<c>acclient.h</c> <c>RawMotionState::PackBitfield</c>, line 46474;
|
||||
/// packer at <c>RawMotionState::Pack</c>, 0x0051ed10).
|
||||
///
|
||||
/// <para>
|
||||
/// This type carries the COMPLETE motion-state snapshot the way retail's
|
||||
/// <c>CPhysicsObj::InqRawMotionState()</c> would return it. The packer
|
||||
/// (<see cref="AcDream.Core.Net"/>) compares every field against
|
||||
/// <see cref="Default"/> and only emits the fields that differ — see
|
||||
/// <c>RawMotionState::Pack</c> for the exact default-difference logic.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// PURE DATA: no PacketWriter / GL / Net dependency. Lives in
|
||||
/// <c>AcDream.Core.Physics</c> so the (future) motion interpreter, which is
|
||||
/// also Core.Physics, can populate it without taking a Core.Net dependency
|
||||
/// (Code Structure Rule #2).
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public sealed class RawMotionState
|
||||
{
|
||||
public HoldKey CurrentHoldKey { get; init; } = HoldKey.None;
|
||||
public uint CurrentStyle { get; init; } = 0x8000003Du;
|
||||
public uint ForwardCommand { get; init; } = 0x41000003u;
|
||||
public HoldKey ForwardHoldKey { get; init; } = HoldKey.Invalid;
|
||||
public float ForwardSpeed { get; init; } = 1.0f;
|
||||
public uint SidestepCommand { get; init; } = 0u;
|
||||
public HoldKey SidestepHoldKey { get; init; } = HoldKey.Invalid;
|
||||
public float SidestepSpeed { get; init; } = 1.0f;
|
||||
public uint TurnCommand { get; init; } = 0u;
|
||||
public HoldKey TurnHoldKey { get; init; } = HoldKey.Invalid;
|
||||
public float TurnSpeed { get; init; } = 1.0f;
|
||||
|
||||
/// <summary>
|
||||
/// Discrete action list. Retail packs <c>num_actions</c> (count, not the
|
||||
/// values) into bits 11-15 of the flags dword, then emits each action's
|
||||
/// command+stamp pair unconditionally after the continuous-axis fields.
|
||||
/// </summary>
|
||||
public IReadOnlyList<RawMotionAction> Actions { get; init; } = Array.Empty<RawMotionAction>();
|
||||
|
||||
/// <summary>
|
||||
/// Retail defaults — a field bit is set in the packed flags dword ONLY
|
||||
/// when the live value differs from these (<c>RawMotionState::Pack</c>,
|
||||
/// 0x0051ed10).
|
||||
/// </summary>
|
||||
public static readonly RawMotionState Default = new();
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue