Port the authored Link Status, Vitae, and Mini Game detail roots and register every indicator page with retail's one-active gmPanelUI owner. Helpful/Harmful and the new pages now replace Inventory, Character, or Magic at one canonical window position while preserving the DAT restore-previous flag. Correct the retail ping wire to its payload-free request/response, publish measured RTT, and port Vitae recovery XP from the live modifier and player properties. Keep transport packet-loss averaging and mini-game gameplay explicitly tracked under AP-110. Release build and all 5,814 tests pass with five intentional skips. Connected visual gate pending. Co-authored-by: OpenAI Codex <codex@openai.com>
84 lines
2.7 KiB
C#
84 lines
2.7 KiB
C#
using AcDream.Core.Net;
|
|
using AcDream.Core.Net.Messages;
|
|
using System.Net;
|
|
using Xunit;
|
|
|
|
namespace AcDream.Core.Net.Tests;
|
|
|
|
public sealed class WorldSessionLinkStatusTests
|
|
{
|
|
[Theory]
|
|
[InlineData(WorldSession.State.Disconnected)]
|
|
[InlineData(WorldSession.State.Failed)]
|
|
public void DisconnectedStates_DoNotPublishPacketAge(WorldSession.State state)
|
|
{
|
|
LinkStatusSnapshot snapshot = WorldSession.BuildLinkStatus(
|
|
state, lastInboundPacketTicks: 100, nowTicks: 900, frequency: 100);
|
|
|
|
Assert.False(snapshot.Connected);
|
|
Assert.Equal(0d, snapshot.SecondsSinceLastPacket);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(WorldSession.State.Handshaking)]
|
|
[InlineData(WorldSession.State.InCharacterSelect)]
|
|
[InlineData(WorldSession.State.EnteringWorld)]
|
|
[InlineData(WorldSession.State.InWorld)]
|
|
public void ConnectedStates_PublishMonotonicPacketAge(WorldSession.State state)
|
|
{
|
|
LinkStatusSnapshot snapshot = WorldSession.BuildLinkStatus(
|
|
state, lastInboundPacketTicks: 100, nowTicks: 850, frequency: 100);
|
|
|
|
Assert.True(snapshot.Connected);
|
|
Assert.Equal(7.5d, snapshot.SecondsSinceLastPacket);
|
|
}
|
|
|
|
[Fact]
|
|
public void ClockRegression_ClampsPacketAgeToZero()
|
|
{
|
|
LinkStatusSnapshot snapshot = WorldSession.BuildLinkStatus(
|
|
WorldSession.State.InWorld,
|
|
lastInboundPacketTicks: 200,
|
|
nowTicks: 100,
|
|
frequency: 100);
|
|
|
|
Assert.True(snapshot.Connected);
|
|
Assert.Equal(0d, snapshot.SecondsSinceLastPacket);
|
|
}
|
|
|
|
[Fact]
|
|
public void ConnectedSnapshot_CarriesPacketLossAndRoundTripTelemetry()
|
|
{
|
|
LinkStatusSnapshot snapshot = WorldSession.BuildLinkStatus(
|
|
WorldSession.State.InWorld,
|
|
lastInboundPacketTicks: 100,
|
|
nowTicks: 200,
|
|
frequency: 100,
|
|
roundTripSeconds: 0.125d,
|
|
packetLossPercentage: 2.5d);
|
|
|
|
Assert.Equal(2.5d, snapshot.PacketLossPercentage);
|
|
Assert.Equal(0.125d, snapshot.RoundTripSeconds);
|
|
}
|
|
|
|
[Fact]
|
|
public void RequestAndEmptyPingResponse_MeasureMonotonicRoundTrip()
|
|
{
|
|
using var session = new WorldSession(
|
|
new IPEndPoint(IPAddress.Loopback, 65000));
|
|
byte[]? action = null;
|
|
session.GameActionCapture = bytes => action = bytes;
|
|
|
|
session.RequestLinkStatusPing();
|
|
session.GameEvents.Dispatch(new GameEventEnvelope(
|
|
PlayerGuid: 0u,
|
|
Sequence: 1u,
|
|
EventType: GameEventType.PingResponse,
|
|
Payload: ReadOnlyMemory<byte>.Empty));
|
|
|
|
Assert.NotNull(action);
|
|
Assert.Equal(12, action.Length);
|
|
Assert.NotNull(session.PingRoundTripSeconds);
|
|
Assert.True(session.PingRoundTripSeconds >= 0d);
|
|
}
|
|
}
|