feat(ui): port retail gameplay indicator bar

Promote all seven LayoutDesc 0x21000071 controls to retained buttons, drive link quality, effects, Vitae, and burden from live state, and route Character Information plus end-session confirmation through the shared UI owners. Keep network timing in WorldSession and pin retail thresholds, flash cadence, authored states, and action routing with focused conformance tests.

Release build and all 5,807 tests pass with five intentional skips. Connected visual gate pending.

Co-authored-by: OpenAI Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-17 10:02:44 +02:00
parent 06016014bc
commit 52c529be86
15 changed files with 857 additions and 204 deletions

View file

@ -519,6 +519,32 @@ public sealed class WorldSession : IDisposable
public State CurrentState { get; private set; } = State.Disconnected;
/// <summary>
/// Network-owned source for retail
/// <c>LinkStatusHolder::GetConnectionStatus @ 0x00411380</c>. The age is
/// measured from the last successfully decoded server datagram using the
/// monotonic Stopwatch clock; presentation thresholds remain in the UI.
/// </summary>
public LinkStatusSnapshot LinkStatus => BuildLinkStatus(
CurrentState,
Volatile.Read(ref _lastInboundPacketTicks),
Stopwatch.GetTimestamp(),
Stopwatch.Frequency);
internal static LinkStatusSnapshot BuildLinkStatus(
State state,
long lastInboundPacketTicks,
long nowTicks,
long frequency)
{
bool connected = state is not State.Disconnected and not State.Failed;
if (!connected || frequency <= 0)
return LinkStatusSnapshot.Disconnected;
long elapsed = Math.Max(0, nowTicks - lastInboundPacketTicks);
return new LinkStatusSnapshot(true, elapsed / (double)frequency);
}
/// <summary>Movement sequence counters for outbound MoveToState/AutonomousPosition.</summary>
public ushort InstanceSequence => _instanceSequence;
public ushort ServerControlSequence => _serverControlSequence;
@ -547,6 +573,7 @@ public sealed class WorldSession : IDisposable
public CharacterList.Parsed? Characters { get; private set; }
private readonly NetClient _net;
private long _lastInboundPacketTicks = Stopwatch.GetTimestamp();
private readonly IPEndPoint _loginEndpoint;
private readonly IPEndPoint _connectEndpoint;
private readonly FragmentAssembler _assembler = new();
@ -852,6 +879,11 @@ public sealed class WorldSession : IDisposable
var dec = PacketCodec.TryDecode(bytes, _inboundIsaac);
if (!dec.IsOk) return;
// Retail LinkStatusHolder::OnHeartbeat @ 0x004113D0 updates its
// last-heard clock only for valid server traffic. Record at decode
// acceptance, before any heavy render-thread message handling.
Volatile.Write(ref _lastInboundPacketTicks, Stopwatch.GetTimestamp());
// Phase 4.9: send an ACK_SEQUENCE control packet for every received
// server packet with sequence > 0 and no ACK flag of its own. This
// is the proper holtburger pattern (every received packet gets an