Merge branch 'main' into claude/peaceful-visvesvaraya-e0a196
# Conflicts: # docs/ISSUES.md # docs/architecture/retail-divergence-register.md
This commit is contained in:
commit
217a4bad69
329 changed files with 81439 additions and 8499 deletions
|
|
@ -119,6 +119,9 @@ public static class CreateObject
|
|||
ushort TeleportSequence = 0,
|
||||
ushort ServerControlSequence = 0,
|
||||
ushort ForcePositionSequence = 0,
|
||||
// L.2g S1 (DEV-6): ObjectMovement stamp (timestamp block index 1)
|
||||
// seeds MotionSequenceGate's MOVEMENT_TS at spawn.
|
||||
ushort MovementSequence = 0,
|
||||
uint? PhysicsState = null,
|
||||
uint? ObjectDescriptionFlags = null,
|
||||
// L.3b (2026-04-30): per-object friction + elasticity from the
|
||||
|
|
@ -215,6 +218,21 @@ public static class CreateObject
|
|||
/// 0x40=TurnSpeed.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <param name="MoveToRunRate">
|
||||
/// R4-V3 deliverable D — the trailing <c>f32 runRate</c> on MoveToObject
|
||||
/// (6) / MoveToPosition (7) payloads. Retail's <c>unpack_movement</c>
|
||||
/// writes this straight onto <c>CMotionInterp::my_run_rate</c>
|
||||
/// (r4-moveto-decomp.md §2f: <c>this->motion_interpreter->my_run_rate =
|
||||
/// read_float()</c>, both @300603 case 6 and @300660 case 7 — SAME
|
||||
/// write for both types, immediately after <c>UnPackNet</c>). Today this
|
||||
/// field only seeds <c>PlanMoveToStart</c>'s local heuristic (plan M13);
|
||||
/// the interp's actual <see cref="AcDream.Core.Physics.MotionInterpreter.MyRunRate"/>
|
||||
/// field is a SEPARATE consumer write the V4/V5 MoveToManager cutover
|
||||
/// performs at the GameWindow mt 6-9 routing site (r4-port-plan.md §4,
|
||||
/// step 2: <c>Motion.MyRunRate = MoveToRunRate</c>) — this record is
|
||||
/// wire-primitive only; it does not write MotionInterpreter state
|
||||
/// itself.
|
||||
/// </param>
|
||||
public readonly record struct ServerMotionState(
|
||||
ushort Stance,
|
||||
ushort? ForwardCommand,
|
||||
|
|
@ -228,7 +246,30 @@ public static class CreateObject
|
|||
uint? MoveToParameters = null,
|
||||
float? MoveToSpeed = null,
|
||||
float? MoveToRunRate = null,
|
||||
MoveToPathData? MoveToPath = null)
|
||||
MoveToPathData? MoveToPath = null,
|
||||
// R4-V3 (closes M7): movement types 8 (TurnToObject) and 9
|
||||
// (TurnToHeading) — previously dropped end-to-end (UpdateMotion.cs
|
||||
// only branched on `movementType is 6 or 7`). Carries the DECODED
|
||||
// wire payload (guid + standalone wire_heading for type 8, plus the
|
||||
// shared 3-dword UnPackNet triple for both types) per V0-pins.md P6.
|
||||
TurnToPathData? TurnToPath = null,
|
||||
// R4-V3 (closes M14-wire-note): the 0xF74C motionFlags sticky-guid
|
||||
// trailer, mt=0 (Invalid) only — ACE MovementInvalid.Write gates the
|
||||
// trailing guid on MotionFlags.StickToObject (0x1); the decomp's
|
||||
// `unpack_movement` case 0 reads it right after
|
||||
// InterpretedMotionState::UnPack (r4-moveto-decomp.md §2f
|
||||
// @0052455d: `if (header & 0x100) sticky_object_guid = read_dword()`
|
||||
// — bit 0x100 of the combined header word is motionFlags byte1&0x1).
|
||||
// R5-V4 consumes it: the GameWindow mt-0 tail routes it into
|
||||
// CPhysicsObj::stick_to_object's port (target PartArray radii →
|
||||
// PositionManager.StickTo — decomp 0x005127e0, call @00524589).
|
||||
uint? StickyObjectGuid = null,
|
||||
// R5-V4 (closes the "documented but NOT consumed" note in
|
||||
// UpdateMotion.cs): motionFlags & 0x2 — retail `unpack_movement`
|
||||
// case 0 writes it onto `motion_interpreter->standing_longjump`
|
||||
// UNCONDITIONALLY (@0052458e: absent flag CLEARS it). Consumed at
|
||||
// the GameWindow mt-0 tails (remote + player).
|
||||
bool StandingLongJump = false)
|
||||
{
|
||||
/// <summary>
|
||||
/// ACE/retail movement types 6 and 7 are server-controlled
|
||||
|
|
@ -238,6 +279,13 @@ public static class CreateObject
|
|||
/// </summary>
|
||||
public bool IsServerControlledMoveTo => MovementType is 6 or 7;
|
||||
|
||||
/// <summary>
|
||||
/// R4-V3: movement types 8 (TurnToObject) and 9 (TurnToHeading) —
|
||||
/// the turn-only sibling of <see cref="IsServerControlledMoveTo"/>.
|
||||
/// Neither carries an InterpretedMotionState.ForwardCommand either.
|
||||
/// </summary>
|
||||
public bool IsServerControlledTurnTo => MovementType is 8 or 9;
|
||||
|
||||
public bool MoveToCanRun => !MoveToParameters.HasValue
|
||||
|| (MoveToParameters.Value & 0x2u) != 0;
|
||||
|
||||
|
|
@ -297,6 +345,51 @@ public static class CreateObject
|
|||
float MinDistance,
|
||||
float FailDistance,
|
||||
float WalkRunThreshold,
|
||||
float DesiredHeading,
|
||||
uint Bitfield = 0); // R4-V4: the raw UnPackNet flags dword, feeds MovementParameters.FromWire
|
||||
|
||||
/// <summary>
|
||||
/// R4-V3 (closes M7) — path-control payload of a server-controlled
|
||||
/// TurnTo packet (movementType 8 TurnToObject or 9 TurnToHeading).
|
||||
/// Sibling of <see cref="MoveToPathData"/>: kept as a SEPARATE record
|
||||
/// rather than widening <c>MoveToPathData</c> in place, because the two
|
||||
/// wire forms genuinely diverge (7-dword <c>UnPackNet</c> with an
|
||||
/// Origin+optional-guid head for move types vs. the 3-dword
|
||||
/// <c>UnPackNet</c> with a guid+standalone-heading head for turn types —
|
||||
/// V0-pins.md P6) and a single record would need every move-only field
|
||||
/// nullable for turn payloads (and vice versa) for no reader benefit —
|
||||
/// no code path ever needs "either a move or a turn path" polymorphically,
|
||||
/// every consumer already switches on <see cref="ServerMotionState.MovementType"/>
|
||||
/// first.
|
||||
///
|
||||
/// <list type="bullet">
|
||||
/// <item>type 8 (TurnToObject) only: u32 <c>TargetGuid</c>, f32
|
||||
/// <c>WireHeading</c> — the STANDALONE heading field (ACE
|
||||
/// <c>TurnToObject.DesiredHeading</c>, distinct from
|
||||
/// <see cref="DesiredHeading"/> below despite ACE always populating
|
||||
/// both from the same source; V0-pins.md P6's fixture caveat: never
|
||||
/// distinguish the two fields by value in a test, only by
|
||||
/// OFFSET). Consumed ONLY in retail's unresolvable-object fallback
|
||||
/// (decomp §2f case 8: <c>if (GetObjectA(object_id) == 0) {
|
||||
/// params.desired_heading = wire_heading; goto TurnToHeading; }</c>)
|
||||
/// — the resolved-object path never reads it.</item>
|
||||
/// <item>TurnToParameters (0xc bytes, exact retail order —
|
||||
/// <c>MovementParameters::UnPackNet</c> 3-dword TurnTo form, decomp
|
||||
/// §2g): u32 <c>Bitfield</c>, f32 <c>Speed</c>, f32
|
||||
/// <c>DesiredHeading</c>. Present for BOTH type 8 and type 9 (type 9
|
||||
/// has no guid/WireHeading head — <see cref="TargetGuid"/> and
|
||||
/// <see cref="WireHeading"/> are null).</item>
|
||||
/// </list>
|
||||
///
|
||||
/// Feeds <see cref="AcDream.Core.Physics.Motion.MovementParameters.FromWireTurnTo"/>
|
||||
/// at the (future) App-layer consumer — this record stays wire-primitive
|
||||
/// (no domain-object construction in the Net-layer parser).
|
||||
/// </summary>
|
||||
public readonly record struct TurnToPathData(
|
||||
uint? TargetGuid,
|
||||
float? WireHeading,
|
||||
uint Bitfield,
|
||||
float Speed,
|
||||
float DesiredHeading);
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -532,11 +625,13 @@ public static class CreateObject
|
|||
if ((physicsFlags & PhysicsDescriptionFlag.DefaultScriptIntensity) != 0) pos += 4;
|
||||
|
||||
// 9 sequence timestamps, always present at end of PhysicsData.
|
||||
// Indices per holtburger: 0=position, 4=teleport, 5=serverControl,
|
||||
// 6=forcePosition, 8=instance.
|
||||
// PhysicsTimeStamp enum order (acclient.h:6084; ACE
|
||||
// WorldObject_Networking.cs:411-420): 0=position, 1=movement,
|
||||
// 4=teleport, 5=serverControl, 6=forcePosition, 8=instance.
|
||||
if (body.Length - pos < 9 * 2) return PartialResult();
|
||||
var seqSpan = body.Slice(pos, 9 * 2);
|
||||
ushort instanceSeq = BinaryPrimitives.ReadUInt16LittleEndian(seqSpan.Slice(8 * 2));
|
||||
ushort movementSeq = BinaryPrimitives.ReadUInt16LittleEndian(seqSpan.Slice(1 * 2));
|
||||
ushort teleportSeq = BinaryPrimitives.ReadUInt16LittleEndian(seqSpan.Slice(4 * 2));
|
||||
ushort serverControlSeq = BinaryPrimitives.ReadUInt16LittleEndian(seqSpan.Slice(5 * 2));
|
||||
ushort forcePositionSeq = BinaryPrimitives.ReadUInt16LittleEndian(seqSpan.Slice(6 * 2));
|
||||
|
|
@ -849,6 +944,7 @@ public static class CreateObject
|
|||
return new Parsed(guid, position, setupTableId, animParts,
|
||||
textureChanges, subPalettes, basePaletteId, objScale, name, itemType, motionState, motionTableId,
|
||||
instanceSeq, teleportSeq, serverControlSeq, forcePositionSeq,
|
||||
movementSeq,
|
||||
physicsState, objectDescriptionFlags,
|
||||
friction, elasticity,
|
||||
IconId: iconId,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue