feat(phys L.2g slice 1): WorldSession dispatches SetState (0xF74B) + hex probe
Three changes folded into one commit: 1. New public StateUpdated event on WorldSession + dispatcher branch for op == SetState.Opcode. Mirrors the VectorUpdated / MotionUpdated event pattern. GameWindow will subscribe in the next commit and feed the parsed (guid, newState) pair to ShadowObjectRegistry.UpdatePhysicsState. 2. One-shot probe-gated hex-dump (ACDREAM_PROBE_BUILDING) emits the first inbound SetState message's body bytes. Originally planned as a separate slice 1.5 confidence-check on holtburger's claimed 12-byte payload vs ACE's GameMessageSetState.cs. Folded into the dispatcher to avoid re-touching the same branch. The new _setStateHexDumped guard keeps the log clean — auto-close every 30s would otherwise produce noise. 3. Doc-comment polish on SetState.cs requested by Task 1's code review: remove false uncertainty about ACE's sequence-field width (ACE's UShortSequence.CurrentBytes provably writes 2 bytes via BitConverter), and align the 'total body size' phrasing with VectorUpdate.cs's convention. Folded here to avoid churning the file twice this slice. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d53891557d
commit
536a608093
2 changed files with 49 additions and 8 deletions
|
|
@ -24,16 +24,18 @@ namespace AcDream.Core.Net.Messages;
|
||||||
/// </list>
|
/// </list>
|
||||||
///
|
///
|
||||||
/// <para>
|
/// <para>
|
||||||
/// Total body size: 16 bytes from start (opcode + 12-byte payload).
|
/// Total body size: 16 bytes (4-byte opcode + 12-byte payload).
|
||||||
/// </para>
|
/// </para>
|
||||||
///
|
///
|
||||||
/// <para>
|
/// <para>
|
||||||
/// Server-side reference:
|
/// Server-side reference:
|
||||||
/// <c>references/ACE/Source/ACE.Server/Network/GameMessages/Messages/GameMessageSetState.cs:8-15</c>
|
/// <c>references/ACE/Source/ACE.Server/Network/GameMessages/Messages/GameMessageSetState.cs:8-15</c>
|
||||||
/// (ACE writes the same field order but appears to use <c>uint</c> for the
|
/// — ACE writes the same field order using its <c>UShortSequence.CurrentBytes</c>
|
||||||
/// sequence fields; verified against retail format by hex-dump probe in
|
/// helper (which calls <c>BitConverter.GetBytes((ushort)value)</c> = 2 bytes per
|
||||||
/// Task 5). Holtburger has been validated against a retail-format server,
|
/// sequence field), so its wire output matches holtburger's 12-byte payload.
|
||||||
/// so its 12-byte payload is the trusted spec.
|
/// A one-shot <c>ACDREAM_PROBE_BUILDING</c> hex-dump in
|
||||||
|
/// <see cref="WorldSession"/>'s dispatcher (added in the same commit) emits
|
||||||
|
/// the first SetState body bytes for runtime confirmation.
|
||||||
/// </para>
|
/// </para>
|
||||||
///
|
///
|
||||||
/// <para>
|
/// <para>
|
||||||
|
|
@ -69,9 +71,9 @@ public static class SetState
|
||||||
uint opcode = BinaryPrimitives.ReadUInt32LittleEndian(body.Slice(0, 4));
|
uint opcode = BinaryPrimitives.ReadUInt32LittleEndian(body.Slice(0, 4));
|
||||||
if (opcode != Opcode) return null;
|
if (opcode != Opcode) return null;
|
||||||
|
|
||||||
uint guid = BinaryPrimitives.ReadUInt32LittleEndian(body.Slice(4, 4));
|
uint guid = BinaryPrimitives.ReadUInt32LittleEndian(body.Slice(4, 4));
|
||||||
uint state = BinaryPrimitives.ReadUInt32LittleEndian(body.Slice(8, 4));
|
uint state = BinaryPrimitives.ReadUInt32LittleEndian(body.Slice(8, 4));
|
||||||
ushort instSeq = BinaryPrimitives.ReadUInt16LittleEndian(body.Slice(12, 2));
|
ushort instSeq = BinaryPrimitives.ReadUInt16LittleEndian(body.Slice(12, 2));
|
||||||
ushort stateSeq = BinaryPrimitives.ReadUInt16LittleEndian(body.Slice(14, 2));
|
ushort stateSeq = BinaryPrimitives.ReadUInt16LittleEndian(body.Slice(14, 2));
|
||||||
|
|
||||||
return new Parsed(guid, state, instSeq, stateSeq);
|
return new Parsed(guid, state, instSeq, stateSeq);
|
||||||
|
|
|
||||||
|
|
@ -129,6 +129,19 @@ public sealed class WorldSession : IDisposable
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event Action<VectorUpdate.Parsed>? VectorUpdated;
|
public event Action<VectorUpdate.Parsed>? VectorUpdated;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fires when the server broadcasts a <c>SetState (0xF74B)</c> game
|
||||||
|
/// message — a previously-spawned entity's <c>PhysicsState</c>
|
||||||
|
/// bitmask changed post-CreateObject. Chiefly doors flipping
|
||||||
|
/// <c>ETHEREAL_PS = 0x4</c> on Use (see ACE
|
||||||
|
/// <c>WorldObjects/Door.cs:127</c>, <c>WorldObject.cs:640-660</c>).
|
||||||
|
/// Subscribers route the new state into
|
||||||
|
/// <see cref="ShadowObjectRegistry.UpdatePhysicsState"/> so the
|
||||||
|
/// existing collision-exemption short-circuit honors the flip on the
|
||||||
|
/// next resolver tick.
|
||||||
|
/// </summary>
|
||||||
|
public event Action<SetState.Parsed>? StateUpdated;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fires when the server sends a PlayerTeleport (0xF751) game message,
|
/// Fires when the server sends a PlayerTeleport (0xF751) game message,
|
||||||
/// signalling that the player is entering portal space. The uint payload
|
/// signalling that the player is entering portal space. The uint payload
|
||||||
|
|
@ -375,6 +388,10 @@ public sealed class WorldSession : IDisposable
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private bool _loginCompleteSent;
|
private bool _loginCompleteSent;
|
||||||
|
|
||||||
|
/// <summary>L.2g slice 1: one-shot guard so the [setstate-hex] probe
|
||||||
|
/// emits the first SetState's body bytes only, not 5–10/sec.</summary>
|
||||||
|
private bool _setStateHexDumped;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Phase B.2: per-session game-action sequence counter. Monotonically
|
/// Phase B.2: per-session game-action sequence counter. Monotonically
|
||||||
/// incremented by <see cref="NextGameActionSequence"/> and embedded in
|
/// incremented by <see cref="NextGameActionSequence"/> and embedded in
|
||||||
|
|
@ -750,6 +767,28 @@ public sealed class WorldSession : IDisposable
|
||||||
if (parsed is not null)
|
if (parsed is not null)
|
||||||
VectorUpdated?.Invoke(parsed.Value);
|
VectorUpdated?.Invoke(parsed.Value);
|
||||||
}
|
}
|
||||||
|
else if (op == SetState.Opcode)
|
||||||
|
{
|
||||||
|
// L.2g slice 1 (2026-05-12): server broadcasts SetState
|
||||||
|
// (0xF74B) when an entity's PhysicsState changes
|
||||||
|
// post-spawn — chiefly doors flipping ETHEREAL on Use.
|
||||||
|
// Holtburger validated wire format = 16 bytes (opcode +
|
||||||
|
// guid + state + 2×u16 sequence). One-shot probe-gated
|
||||||
|
// hex-dump (ACDREAM_PROBE_BUILDING) captures the wire
|
||||||
|
// bytes for confidence before declaring slice 1 done.
|
||||||
|
if (AcDream.Core.Physics.PhysicsDiagnostics.ProbeBuildingEnabled
|
||||||
|
&& !_setStateHexDumped)
|
||||||
|
{
|
||||||
|
_setStateHexDumped = true;
|
||||||
|
var hex = string.Join(" ", body.Take(Math.Min(body.Length, 32))
|
||||||
|
.Select(b => b.ToString("X2")));
|
||||||
|
Console.WriteLine($"[setstate-hex] body.len={body.Length} first-{Math.Min(body.Length, 32)}-bytes: {hex}");
|
||||||
|
}
|
||||||
|
|
||||||
|
var parsed = SetState.TryParse(body);
|
||||||
|
if (parsed is not null)
|
||||||
|
StateUpdated?.Invoke(parsed.Value);
|
||||||
|
}
|
||||||
else if (op == HearSpeech.LocalOpcode || op == HearSpeech.RangedOpcode)
|
else if (op == HearSpeech.LocalOpcode || op == HearSpeech.RangedOpcode)
|
||||||
{
|
{
|
||||||
// Phase H.1: local/ranged chat. Standalone GameMessage
|
// Phase H.1: local/ranged chat. Standalone GameMessage
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue