#nullable enable
using IGP.UnitySDK.Models;
using IGP.UnitySDK;
using System;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Security.Cryptography;
using System.Text;
using IGP.Multiplayer.Models;
using IGP.Multiplayer.Network;
using IGP.Multiplayer.Protocol;
using IGP.Multiplayer.ThirdParty.Kcp2k;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using UnityEngine;

namespace IGP.Multiplayer.Core
{
    /// <summary>
    /// Unity 版本 KCP 客户端（lowlevel IGP.Multiplayer.ThirdParty.Kcp2k.Kcp + UDP Socket）。
    ///
    /// 注意：本实现使用 length-prefixed JSON：
    /// [len:4 bytes big-endian] + [json bytes]
    /// 并通过 WS 获取 token 后在 KCP 上发送 kcp_handshake。
    /// </summary>
    internal class IGPKcpClient : IDisposable
    {
        private const string LogScope = "kcp";
        private const int HeaderLength = 4;
        private const int MaxDatagramSize = 1500;
        private const float BusyStateLogIntervalSeconds = 30f;
        private const float DebugStateLogIntervalSeconds = 5f;
        private const double TickGapLogThresholdSeconds = 0.5;
        private const float TransportRateSampleIntervalSeconds = 0.5f;
        private const int QueueWarningThreshold = 256;
        private const int QueueCriticalThreshold = 1024;
        private const int SendQueueHardFaultThreshold = 2048;
        private const int ReceiveHardDatagramLimit = 2048;
        private const double ReceiveExtraBudgetSeconds = 0.0015;
        public const int DefaultMaxDatagramsPerTick = 128;
        public const int DefaultWindowSize = 256;
        public const int MinWindowSize = 128;
        public const int MaxWindowSize = 1024;
        private const double SendSuspectAfterSeconds = 3.0;
        private const double SendFaultAfterSeconds = 15.0;
        private const double IdleProbeIntervalSeconds = 1.0;
        private const double IdleProbeResponseWarningSeconds = 3.0;
        private const double IdleProbeExpirationSeconds = 4.0;
        private const int MaxPendingIdleProbes = 4;
        private const int PendingSendGroupLimit = 256;
        private const int PendingSendByteLimit = 4 * 1024 * 1024;
        private const int MaxNewSegmentsPerTick = 64;
        private const int MaxSendBytesPerTick = 128 * 1024;
        private const int ControlReserveGroups = 16;
        private const int ControlReserveBytes = 256 * 1024;

        private readonly object stateLock = new object();
        private readonly IGPNetworkAnomalyLogLimiter anomalyLogLimiter = new IGPNetworkAnomalyLogLimiter();
        private IGPReliableTransportOptions transportOptions = IGPReliableTransportOptions.Default;
        private IGPKcpFrameDecoder frameDecoder;
        private Socket? socket;
        private EndPoint? remoteEndPoint;
        private Kcp? kcp;
        private uint conversationId;
        private bool isDisposed = false;

        private readonly byte[] datagramBuffer = new byte[MaxDatagramSize];
        private byte[] kcpReceiveBuffer;

        private bool authenticated;
        private uint nextUpdate;

        // Idle probe and application RTT state.
        private IGPRTTStats rttStats = new IGPRTTStats();
        private Dictionary<int, PendingIdleProbe> pendingIdleProbes = new Dictionary<int, PendingIdleProbe>();
        private int idleProbeSequence;
        private double lastKcpActivityAt;
        private double lastIdleProbeAttemptAt;
        private double lastIdleProbePongAt;
        private long totalIdleProbeAttempts;
        private long totalIdleProbesAccepted;
        private long totalIdleProbePongs;
        private long totalIdleProbeEvicted;
        private long totalIdleProbeAdmissionBlocked;
        private long sampleIdleProbeAttempts;
        private long sampleIdleProbesAccepted;
        private long sampleIdleProbePongs;
        private long sampleIdleProbeEvicted;
        private long sampleIdleProbeAdmissionBlocked;
        private bool handshakeDatagramLogged;
        private string handshakeTarget = string.Empty;
        private float connectedAt;
        private float lastStateLogAt;
        private float lastDebugStateLogAt;
        private int lastStateLogQueueTotal;
        private int totalDatagramsReceived;
        private int totalDatagramsSent;
        private long totalDatagramBytesReceived;
        private long totalDatagramBytesSent;
        private int totalFramesSent;
        private int totalFramesReceived;
        private int totalPayloadsReceived;
        private int totalReceiveErrors;
        private int totalSendErrors;
        private int totalDroppedSends;
        private long totalOutputAttempts;
        private long totalSocketAccepted;
        private long totalSocketWouldBlock;
        private long totalSocketFatal;
        private long totalKcpInputErrors;
        private bool hasTransportRateSample;
        private float lastTransportRateSampleTime;
        private int lastRateDatagramsReceived;
        private int lastRateDatagramsSent;
        private long lastRateDatagramBytesReceived;
        private long lastRateDatagramBytesSent;
        private float datagramsReceivedPerSecond;
        private float datagramsSentPerSecond;
        private float datagramBytesReceivedPerSecond;
        private float datagramBytesSentPerSecond;
        private int maxDatagramsPerTick = DefaultMaxDatagramsPerTick;
        private int sendWindowSize = DefaultWindowSize;
        private int receiveWindowSize = DefaultWindowSize;
        private IGPKcpHealthState healthState = IGPKcpHealthState.Disconnected;
        private IGPKcpFaultReason lastFaultReason = IGPKcpFaultReason.None;
        private IGPKcpFaultReason pendingFaultReason = IGPKcpFaultReason.None;
        private bool faultDispatched;
        private bool acceptingBusinessSends;
        private bool sendBackpressured;
        private bool sendProgressTrackingActive;
        private bool sendSuspectActive;
        private double lastSendProgressAt;
        private double lastReceiveProgressAt;
        private double lastTickStartedAt;
        private double lastTickGapSeconds;
        private double maxTickGapSinceSendProgressSeconds;
        private double sendSuspectStartedAt;
        private int progressAnchorDatagramsReceived;
        private int progressAnchorDatagramsSent;
        private long progressAnchorSocketWouldBlock;
        private long progressAnchorKcpInputErrors;
        private uint progressAnchorSndUna;
        private int progressAnchorWaitSnd;
        private uint observedRcvNxt;
        private readonly Queue<QueuedSendGroup> pendingSendGroups = new Queue<QueuedSendGroup>();
        private int pendingSendBytes;
        private long totalRejectedSendGroups;
        private int sampleDatagramsReceived;
        private int sampleDatagramsSent;
        private long sampleDatagramBytesReceived;
        private long sampleDatagramBytesSent;
        private int sampleFramesReceived;
        private int sampleFramesSent;
        private long sampleSocketWouldBlock;
        private long sampleKcpInputErrors;
        private uint sampleSndUna;
        private uint sampleSndNxt;
        private uint sampleRcvNxt;
        private double sampleMaxTickGapSeconds;
        private long applicationFramePayloadsReceived;
        private long applicationFramesCreated;
        private long applicationFramesCompressed;
        private long applicationFrameRawBytes;
        private long applicationFrameWireBytes;
        private int applicationFrameMaxQueueMessages;
        private int applicationFrameMaxQueueBytes;
        private double applicationFrameMaxQueueAgeSeconds;
        private double applicationFrameMaxSchedulerGapSeconds;

        private sealed class QueuedSendGroup
        {
            public readonly List<byte[]> Frames;
            public readonly bool IsControl;
            public readonly string Detail;
            public readonly double EnqueuedAt;
            public int NextFrameIndex;

            public QueuedSendGroup(List<byte[]> frames, bool isControl, string detail, double enqueuedAt)
            {
                Frames = frames;
                IsControl = isControl;
                Detail = detail;
                EnqueuedAt = enqueuedAt;
            }
        }

        private readonly struct PendingIdleProbe
        {
            public readonly long ClientTimestampUnixMs;
            public readonly double SentAt;

            public PendingIdleProbe(long clientTimestampUnixMs, double sentAt)
            {
                ClientTimestampUnixMs = clientTimestampUnixMs;
                SentAt = sentAt;
            }
        }

        public string? RoomId { get; private set; }
        public string? PlayerId { get; private set; }
        public bool IsConnected => socket != null && kcp != null && authenticated;
        public IGPLogLevel LogLevel { get; set; }
        public string DiagnosticsSummary => BuildDiagnosticsSummary();
        public IGPKcpHealthState HealthState => healthState;
        public IGPKcpFaultReason LastFaultReason => lastFaultReason;
        public bool IsWritable => IsConnected && authenticated && acceptingBusinessSends &&
            !sendBackpressured && !sendSuspectActive && pendingFaultReason == IGPKcpFaultReason.None &&
            pendingSendGroups.Count < BusinessPendingGroupLimit && pendingSendBytes < BusinessPendingByteLimit;

        /// <summary>
        /// 每次 Tick 最多读取的 UDP datagram 数，用于控制突发包的单帧处理预算。
        /// </summary>
        public int MaxDatagramsPerTick
        {
            get => maxDatagramsPerTick;
            set => maxDatagramsPerTick = Math.Max(1, value);
        }

        public int SendWindowSize
        {
            get => sendWindowSize;
            set
            {
                sendWindowSize = NormalizeWindowSize(value);
                ApplyWindowSize();
            }
        }

        public int ReceiveWindowSize
        {
            get => receiveWindowSize;
            set
            {
                receiveWindowSize = NormalizeWindowSize(value);
                ApplyWindowSize();
            }
        }

        /// <summary>
        /// Compatibility alias for app-level idle probe RTT stats.
        /// </summary>
        public IGPRTTStats RTTStats => rttStats;

        /// <summary>
        /// App-level idle probe ping/pong RTT stats.
        /// </summary>
        public IGPRTTStats AppRTTStats => rttStats;

        /// <summary>
        /// Snapshot of KCP ACK timing and queue state.
        /// </summary>
        public IGPKcpTransportStats TransportStats => BuildTransportStats();
        
        /// <summary>
        /// 连接是否保持可用且未进入发送停滞状态。
        /// </summary>
        public bool IsAlive => IsConnected &&
            pendingFaultReason == IGPKcpFaultReason.None &&
            healthState != IGPKcpHealthState.Stalled &&
            !sendSuspectActive;
        
        internal event Action<IGPTransportMessageReceived>? MessageReceived;
        internal event Action<IGPTransportConnectionChanged>? ConnectionChanged;
        internal event Action<IGPTransportFault>? Faulted;

        internal void ObserveApplicationFrameCreated(int payloadCount, int rawBytes, int wireBytes, bool compressed)
        {
            applicationFramesCreated += 1;
            applicationFrameRawBytes += rawBytes;
            applicationFrameWireBytes += wireBytes;
            if (compressed)
            {
                applicationFramesCompressed += 1;
            }
        }

        internal void ObserveApplicationFrameReceived(int payloadCount)
        {
            applicationFramePayloadsReceived += Math.Max(0, payloadCount);
        }

        internal void ObserveApplicationFrameQueue(int messages, int bytes, double oldestAgeSeconds)
        {
            applicationFrameMaxQueueMessages = Math.Max(applicationFrameMaxQueueMessages, messages);
            applicationFrameMaxQueueBytes = Math.Max(applicationFrameMaxQueueBytes, bytes);
            applicationFrameMaxQueueAgeSeconds = Math.Max(applicationFrameMaxQueueAgeSeconds, oldestAgeSeconds);
        }

        internal void ObserveApplicationFrameSchedulerGap(double gapSeconds)
        {
            applicationFrameMaxSchedulerGapSeconds = Math.Max(applicationFrameMaxSchedulerGapSeconds, gapSeconds);
        }

        public IGPKcpClient()
        {
            frameDecoder = new IGPKcpFrameDecoder(transportOptions);
            kcpReceiveBuffer = new byte[transportOptions.KcpFrameMaxBytes + HeaderLength];
        }

        internal void ApplyTransportOptions(IGPReliableTransportOptions options)
        {
            if (options == null)
            {
                throw new ArgumentNullException(nameof(options));
            }

            lock (stateLock)
            {
                transportOptions = options;
                frameDecoder = new IGPKcpFrameDecoder(transportOptions);
                kcpReceiveBuffer = new byte[transportOptions.KcpFrameMaxBytes + HeaderLength];
            }

            LogLifecycle(
                "options-applied",
                $"reliableMaxBytes={options.ReliableMessageMaxBytes} reliableChunkBytes={options.ReliableChunkMaxBytes} " +
                $"kcpPayloadMaxBytes={options.KcpDataPlanePayloadMaxBytes} kcpFrameMaxBytes={options.KcpFrameMaxBytes}");
        }

        public void Connect(string host, int port, string roomId, string playerId, string token)
        {
            if (socket != null && kcp != null)
            {
                if (ShouldLog(IGPLogLevel.Info))
                {
                    LogLifecycle("connect", $"event=skipped reason=already-created {BuildDiagnosticsSummary()}");
                }

                return;
            }

            DisconnectInternal(
                suppressDisposedGuard: false,
                notifyConnectionStateChanged: false);

            RoomId = roomId;
            PlayerId = playerId;
            authenticated = false;
            frameDecoder.Reset();
            
            // Reset idle probe and RTT state for the new connection.
            rttStats = new IGPRTTStats();
            pendingIdleProbes = new Dictionary<int, PendingIdleProbe>();
            idleProbeSequence = 0;
            lastKcpActivityAt = Time.realtimeSinceStartupAsDouble;
            lastIdleProbeAttemptAt = lastKcpActivityAt;
            lastIdleProbePongAt = 0;
            totalIdleProbeAttempts = 0;
            totalIdleProbesAccepted = 0;
            totalIdleProbePongs = 0;
            totalIdleProbeEvicted = 0;
            totalIdleProbeAdmissionBlocked = 0;
            sampleIdleProbeAttempts = 0;
            sampleIdleProbesAccepted = 0;
            sampleIdleProbePongs = 0;
            sampleIdleProbeEvicted = 0;
            sampleIdleProbeAdmissionBlocked = 0;
            handshakeDatagramLogged = false;
            handshakeTarget = string.Empty;
            connectedAt = 0f;
            lastStateLogAt = 0f;
            lastDebugStateLogAt = 0f;
            lastStateLogQueueTotal = 0;
            totalDatagramsReceived = 0;
            totalDatagramsSent = 0;
            totalDatagramBytesReceived = 0;
            totalDatagramBytesSent = 0;
            totalFramesSent = 0;
            totalFramesReceived = 0;
            totalPayloadsReceived = 0;
            totalReceiveErrors = 0;
            totalSendErrors = 0;
            totalDroppedSends = 0;
            totalOutputAttempts = 0;
            totalSocketAccepted = 0;
            totalSocketWouldBlock = 0;
            totalSocketFatal = 0;
            totalKcpInputErrors = 0;
            healthState = IGPKcpHealthState.Handshaking;
            lastFaultReason = IGPKcpFaultReason.None;
            pendingFaultReason = IGPKcpFaultReason.None;
            faultDispatched = false;
            acceptingBusinessSends = true;
            sendBackpressured = false;
            sendProgressTrackingActive = false;
            sendSuspectActive = false;
            lastSendProgressAt = Time.realtimeSinceStartupAsDouble;
            lastReceiveProgressAt = lastSendProgressAt;
            lastTickStartedAt = 0;
            lastTickGapSeconds = 0;
            maxTickGapSinceSendProgressSeconds = 0;
            sendSuspectStartedAt = 0;
            progressAnchorDatagramsReceived = 0;
            progressAnchorDatagramsSent = 0;
            progressAnchorSocketWouldBlock = 0;
            progressAnchorKcpInputErrors = 0;
            progressAnchorSndUna = 0;
            progressAnchorWaitSnd = 0;
            observedRcvNxt = 0;
            pendingSendGroups.Clear();
            pendingSendBytes = 0;
            totalRejectedSendGroups = 0;
            sampleDatagramsReceived = 0;
            sampleDatagramsSent = 0;
            sampleDatagramBytesReceived = 0;
            sampleDatagramBytesSent = 0;
            sampleFramesReceived = 0;
            sampleFramesSent = 0;
            sampleSocketWouldBlock = 0;
            sampleKcpInputErrors = 0;
            sampleSndUna = 0;
            sampleSndNxt = 0;
            sampleRcvNxt = 0;
            sampleMaxTickGapSeconds = 0;
            applicationFramePayloadsReceived = 0;
            applicationFramesCreated = 0;
            applicationFramesCompressed = 0;
            applicationFrameRawBytes = 0;
            applicationFrameWireBytes = 0;
            applicationFrameMaxQueueMessages = 0;
            applicationFrameMaxQueueBytes = 0;
            applicationFrameMaxQueueAgeSeconds = 0;
            applicationFrameMaxSchedulerGapSeconds = 0;
            ResetTransportRates();

            try
            {
                var ip = ResolveIP(host);
                remoteEndPoint = new IPEndPoint(ip, port);
                handshakeTarget = remoteEndPoint.ToString() ?? $"{host}:{port}";
                LogLifecycle(
                    "connect",
                    $"event=start targetHost={host} resolvedIP={ip} port={port} endpoint={handshakeTarget} " +
                    $"roomId={roomId} localPlayerId={playerId}");

                socket = new Socket(ip.AddressFamily, SocketType.Dgram, ProtocolType.Udp)
                {
                    Blocking = false
                };
                socket.Connect(remoteEndPoint);

                uint conv = CreateRandomConv();
                conversationId = conv;
                kcp = new Kcp(conv, (buffer, size) =>
                {
                    totalOutputAttempts += 1;
                    try
                    {
                        var sent = socket?.Send(buffer, 0, size, SocketFlags.None) ?? 0;
                        totalDatagramsSent += sent > 0 ? 1 : 0;
                        totalDatagramBytesSent += sent > 0 ? sent : 0;
                        totalSocketAccepted += sent > 0 ? 1 : 0;
                        if (!authenticated && !handshakeDatagramLogged)
                        {
                            handshakeDatagramLogged = true;
                            if (ShouldLog(IGPLogLevel.Info))
                            {
                                LogLifecycle(
                                    "handshake",
                                    $"event=datagram-sent endpoint={handshakeTarget} bytes={sent} " +
                                    $"roomId={roomId} localPlayerId={playerId} " +
                                    BuildKcpStateSummary());
                            }
                        }
                    }
                    catch (SocketException ex) when (IsWouldBlock(ex.SocketErrorCode))
                    {
                        totalSocketWouldBlock += 1;
                        totalSendErrors += 1;
                        if (ShouldLog(IGPLogLevel.Warning))
                        {
                            anomalyLogLimiter.ObserveEvent(
                                "udp-send-would-block",
                                Time.realtimeSinceStartup,
                                () => $"socketError={ex.SocketErrorCode} total={totalSocketWouldBlock} {BuildKcpStateSummary()}",
                                message => LogWarning("anomaly", message));
                        }
                    }
                    catch (SocketException ex)
                    {
                        totalSocketFatal += 1;
                        totalSendErrors += 1;
                        if (!QueueFault(IGPKcpFaultReason.SocketFailure))
                        {
                            return;
                        }
                        if (ShouldLog(IGPLogLevel.Warning))
                        {
                            LogWarning("send", $"event=udp-send-failed bytes={size} socketError={ex.SocketErrorCode} error={ex.Message} {BuildKcpStateSummary()}");
                        }
                    }
                    catch (Exception ex)
                    {
                        totalSocketFatal += 1;
                        totalSendErrors += 1;
                        if (QueueFault(IGPKcpFaultReason.SocketFailure) && ShouldLog(IGPLogLevel.Warning))
                        {
                            LogWarning("send", $"event=udp-send-failed bytes={size} error={ex.Message} {BuildKcpStateSummary()}");
                        }
                    }
                });

                kcp.SetNoDelay(1, 10, 2, nocwnd: true);
                kcp.SetMtu(1200);
                ApplyWindowSize();
                kcp.SetInterval(10);

                var now = NowMs();
                nextUpdate = now;

                // kcp handshake (framed)
                var handshake = new
                {
                    type = "kcp_handshake",
                    token = token
                };
                if (ShouldLog(IGPLogLevel.Info))
                {
                    LogLifecycle(
                        "handshake",
                        $"event=queued endpoint={handshakeTarget} roomId={roomId} localPlayerId={playerId} token={FormatTokenForLog(token)} " +
                        $"config={BuildTransportOptionsSummary()} {BuildKcpStateSummary()}");
                }

                SendRawJson(handshake);
                LogStateIfNeeded(force: true, reason: "connect-started");
            }
            catch (Exception ex)
            {
                totalSendErrors += 1;
                if (ShouldLog(IGPLogLevel.Error))
                {
                    LogError(
                        "connect",
                        $"event=connect-failed targetHost={host} port={port} roomId={roomId} localPlayerId={playerId} " +
                        $"error={ex.Message} {BuildKcpStateSummary()}");
                }

                Faulted?.Invoke(new IGPTransportFault(IGPRealtimeTransport.Kcp, "KCP_CONNECT_FAILED", $"KCP connect failed: {ex.Message}", true, ex));
                Disconnect();
            }
        }

        public void Disconnect()
        {
            DisconnectInternal(suppressDisposedGuard: false);
        }

        private void DisconnectInternal(
            bool suppressDisposedGuard,
            bool notifyConnectionStateChanged = true)
        {
            string summary;
            bool hadState;
            bool hadConnectionAttempt;
            lock (stateLock)
            {
                if (isDisposed && !suppressDisposedGuard) return;

                hadConnectionAttempt = socket != null ||
                                       kcp != null ||
                                       authenticated ||
                                       !string.IsNullOrEmpty(RoomId) ||
                                       !string.IsNullOrEmpty(PlayerId);
                hadState = hadConnectionAttempt ||
                           totalDatagramsReceived > 0 ||
                           totalDatagramsSent > 0 ||
                           totalFramesReceived > 0 ||
                           totalFramesSent > 0;
                summary = ShouldLog(IGPLogLevel.Info) ? BuildDiagnosticsSummary() : string.Empty;
                authenticated = false;
                acceptingBusinessSends = false;
                sendBackpressured = false;
                sendProgressTrackingActive = false;
                sendSuspectActive = false;
                healthState = IGPKcpHealthState.Disconnected;
                RoomId = null;
                PlayerId = null;
                frameDecoder.Reset();
                pendingIdleProbes.Clear();
                pendingSendGroups.Clear();
                pendingSendBytes = 0;
                handshakeDatagramLogged = false;
                handshakeTarget = string.Empty;

                try
                {
                    socket?.Close(0);
                }
                catch
                {
                    // ignore
                }

                socket = null;
                remoteEndPoint = null;
                kcp = null;
                conversationId = 0;
            }

            if (hadState && ShouldLog(IGPLogLevel.Info))
            {
                LogLifecycle("disconnect", $"event=complete {summary}");
            }

            if (notifyConnectionStateChanged && hadConnectionAttempt)
            {
                ConnectionChanged?.Invoke(new IGPTransportConnectionChanged(IGPRealtimeTransport.Kcp, false, "disconnected"));
            }
        }

        public void Dispose()
        {
            lock (stateLock)
            {
                if (isDisposed) return;
                isDisposed = true;
            }

            DisconnectInternal(suppressDisposedGuard: true);
        }

        /// <summary>
        /// 由 IGPMultiplayerRuntime 在 Update() 中驱动。
        /// </summary>
        public void Tick()
        {
            if (socket == null || kcp == null)
            {
                healthState = IGPKcpHealthState.Disconnected;
                return;
            }

            ObserveTickGap(Time.realtimeSinceStartupAsDouble);
            uint now = NowMs();
            int datagramsThisTick = 0;
            bool receiveBudgetExhausted = false;
            double receiveStartedAt = Time.realtimeSinceStartupAsDouble;
            int waitSndBeforeInput = kcp.WaitSnd;
            uint sndUnaBeforeInput = kcp.snd_una;

            try
            {
                int softBudget = Math.Min(MaxDatagramsPerTick, ReceiveHardDatagramLimit);
                double extraBudgetStartedAt = 0;
                bool extraBudgetStarted = false;
                while (socket.Poll(0, SelectMode.SelectRead))
                {
                    if (datagramsThisTick >= ReceiveHardDatagramLimit)
                    {
                        receiveBudgetExhausted = true;
                        break;
                    }
                    if (datagramsThisTick >= softBudget)
                    {
                        if (!extraBudgetStarted)
                        {
                            extraBudgetStartedAt = Time.realtimeSinceStartupAsDouble;
                            extraBudgetStarted = true;
                        }
                        else if (Time.realtimeSinceStartupAsDouble - extraBudgetStartedAt >= ReceiveExtraBudgetSeconds)
                        {
                            receiveBudgetExhausted = true;
                            break;
                        }
                    }

                    int received = socket.Receive(datagramBuffer, 0, datagramBuffer.Length, SocketFlags.None);
                    if (received > 0)
                    {
                        datagramsThisTick += 1;
                        totalDatagramsReceived += 1;
                        totalDatagramBytesReceived += received;
                        int inputResult = kcp.Input(datagramBuffer, 0, received);
                        if (inputResult != 0)
                        {
                            totalKcpInputErrors += 1;
                            totalReceiveErrors += 1;
                            if (ShouldLog(IGPLogLevel.Warning))
                            {
                                LogWarning(
                                    "recv",
                                    $"event=kcp-input-failed result={inputResult} datagramBytes={received} " +
                                    $"{BuildKcpStateSummary()}");
                            }
                        }

                    }
                    else
                    {
                        break;
                    }
                }

                if (!receiveBudgetExhausted && socket.Poll(0, SelectMode.SelectRead))
                {
                    receiveBudgetExhausted = true;
                }
                if (receiveBudgetExhausted)
                {
                    if (ShouldLog(IGPLogLevel.Warning))
                    {
                        anomalyLogLimiter.ObserveEvent(
                            "udp-read-budget-exhausted",
                            Time.realtimeSinceStartup,
                            () => $"datagrams={datagramsThisTick} elapsedMs={(Time.realtimeSinceStartupAsDouble - receiveStartedAt) * 1000.0:F2} " +
                                  $"softLimit={softBudget} hardLimit={ReceiveHardDatagramLimit} socketStillReadable=true {BuildKcpStateSummary()}",
                            message => LogWarning("anomaly", message));
                    }
                }
            }
            catch (SocketException ex) when (IsWouldBlock(ex.SocketErrorCode))
            {
                totalSocketWouldBlock += 1;
                totalReceiveErrors += 1;
                if (ShouldLog(IGPLogLevel.Warning))
                {
                    anomalyLogLimiter.ObserveEvent(
                        "udp-receive-would-block",
                        Time.realtimeSinceStartup,
                        () => $"socketError={ex.SocketErrorCode} total={totalSocketWouldBlock} {BuildKcpStateSummary()}",
                        message => LogWarning("anomaly", message));
                }
            }
            catch (SocketException ex)
            {
                totalSocketFatal += 1;
                totalReceiveErrors += 1;
                if (QueueFault(IGPKcpFaultReason.SocketFailure) && ShouldLog(IGPLogLevel.Warning))
                {
                    LogWarning("recv", $"event=udp-receive-failed socketError={ex.SocketErrorCode} error={ex.Message} {BuildKcpStateSummary()}");
                }
            }
            catch (Exception ex)
            {
                totalReceiveErrors += 1;
                if (ShouldLog(IGPLogLevel.Warning))
                {
                    LogWarning("recv", $"event=udp-receive-failed error={ex.Message} {BuildKcpStateSummary()}");
                }

                Faulted?.Invoke(new IGPTransportFault(IGPRealtimeTransport.Kcp, "KCP_RECEIVE_FAILED", $"KCP receive failed: {ex.Message}", true, ex));
            }

            if (UpdateTransportRates())
            {
                EvaluateNetworkAnomalyWarnings();
            }

            try
            {
                DrainKcpReceive();
                ObserveKcpInputProgress(sndUnaBeforeInput, waitSndBeforeInput);
                double currentTime = Time.realtimeSinceStartupAsDouble;
                EvaluateIdleProbeResponses(currentTime);
                DriveIdleProbe(currentTime);

                DrainPendingSends();
                if (now >= nextUpdate)
                {
                    kcp.Update(now);
                    nextUpdate = kcp.Check(now);
                }
                LogStateIfNeeded(force: false, reason: "post-update");
            }
            catch (Exception ex)
            {
                totalReceiveErrors += 1;
                if (ShouldLog(IGPLogLevel.Error))
                {
                    LogError("tick", $"event=failed error={ex.Message} {BuildKcpStateSummary()}");
                }

                Faulted?.Invoke(new IGPTransportFault(IGPRealtimeTransport.Kcp, "KCP_TICK_FAILED", $"KCP tick failed: {ex.Message}", true, ex));
            }

            EvaluateTransportHealth(receiveBudgetExhausted);
            LogDebugStateIfNeeded("post-health");
            DispatchPendingFault();
        }

        private void DrainKcpReceive()
        {
            if (kcp == null) return;

            int payloads = 0;
            while (true)
            {
                int n = kcp.Receive(kcpReceiveBuffer, kcpReceiveBuffer.Length);
                if (n <= 0)
                {
                    break;
                }

                var frames = frameDecoder.Append(kcpReceiveBuffer, 0, n);
                payloads += 1;
                foreach (var frame in frames)
                {
                    totalFramesReceived += 1;
                    HandlePayload(frame);
                }
            }

            if (payloads > 0)
            {
                totalPayloadsReceived += payloads;
            }
        }

        public void SendMessage(Message message)
        {
            if (!TrySendMessage(message))
            {
                totalDroppedSends += 1;
            }
        }

        public bool TrySendMessage(Message message)
        {
            return TrySendMessages(new[] { message }, isControl: false) == IGPTransportSendResult.Accepted;
        }

        internal IGPTransportSendResult TrySendMessages(IReadOnlyList<Message> messages, bool isControl)
        {
            if (messages == null || messages.Count == 0)
            {
                return IGPTransportSendResult.InvalidPayload;
            }
            if (pendingFaultReason != IGPKcpFaultReason.None || faultDispatched)
            {
                return RejectSendGroup(IGPTransportSendResult.Faulted, "transport-faulted", messages.Count);
            }
            if (!IsConnected || !authenticated || !acceptingBusinessSends ||
                string.IsNullOrEmpty(RoomId) || string.IsNullOrEmpty(PlayerId))
            {
                return RejectSendGroup(IGPTransportSendResult.Unavailable, "not-ready", messages.Count);
            }

            if (!RefreshSendBackpressure())
            {
                return RejectSendGroup(IGPTransportSendResult.Faulted, "send-queue-overflow", messages.Count);
            }
            if (sendBackpressured || sendSuspectActive)
            {
                return RejectSendGroup(
                    IGPTransportSendResult.WouldBlock,
                    sendSuspectActive ? "send-suspect" : "kcp-backpressure",
                    messages.Count);
            }

            var frames = new List<byte[]>(messages.Count);
            int encodedBytes = 0;
            try
            {
                for (int i = 0; i < messages.Count; i++)
                {
                    Message message = messages[i];
                    if (message == null)
                    {
                        return RejectSendGroup(IGPTransportSendResult.InvalidPayload, "null-message", messages.Count);
                    }

                    message.roomId ??= RoomId;
                    message.playerId ??= PlayerId;
                    byte[] payload;
                    if (!TryEncodeBinaryDataPlaneMessage(message, out payload))
                    {
                        payload = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(message));
                    }

                    byte[] frame = IGPKcpFrameCodec.EncodeFrame(payload, transportOptions);
                    frames.Add(frame);
                    encodedBytes = checked(encodedBytes + frame.Length);
                }
            }
            catch (Exception ex)
            {
                if (ShouldLog(IGPLogLevel.Warning))
                {
                    LogWarning("send", $"event=send-rejected reason=invalid-payload error={ex.Message}");
                }
                return RejectSendGroup(IGPTransportSendResult.InvalidPayload, "encode-failed", messages.Count);
            }

            int groupLimit = isControl ? PendingSendGroupLimit : BusinessPendingGroupLimit;
            int byteLimit = isControl ? PendingSendByteLimit : BusinessPendingByteLimit;
            if (encodedBytes > PendingSendByteLimit)
            {
                return RejectSendGroup(IGPTransportSendResult.InvalidPayload, "group-exceeds-hard-limit", messages.Count);
            }
            if (pendingSendGroups.Count + 1 > groupLimit || pendingSendBytes + encodedBytes > byteLimit)
            {
                return RejectSendGroup(IGPTransportSendResult.WouldBlock, "pending-queue-full", messages.Count);
            }

            string detail = $"messages={messages.Count} control={isControl}";
            pendingSendGroups.Enqueue(new QueuedSendGroup(
                frames,
                isControl,
                detail,
                Time.realtimeSinceStartupAsDouble));
            pendingSendBytes += encodedBytes;
            return IGPTransportSendResult.Accepted;
        }

        private void DriveIdleProbe(double now)
        {
            if (!authenticated || kcp == null || !acceptingBusinessSends || sendSuspectActive ||
                pendingFaultReason != IGPKcpFaultReason.None || faultDispatched ||
                kcp.WaitSnd > 0 || pendingSendGroups.Count > 0 ||
                now - lastKcpActivityAt < IdleProbeIntervalSeconds ||
                now - lastIdleProbeAttemptAt < IdleProbeIntervalSeconds)
            {
                return;
            }

            lastIdleProbeAttemptAt = now;
            totalIdleProbeAttempts += 1;
            while (pendingIdleProbes.Count >= MaxPendingIdleProbes)
            {
                EvictOldestIdleProbe();
            }

            long clientTimestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
            int sequence = idleProbeSequence++;
            var probe = new Message
            {
                type = "ping",
                roomId = RoomId ?? string.Empty,
                playerId = PlayerId ?? string.Empty,
                reliable = true,
                content = new Dictionary<string, object>
                {
                    ["clientTimestamp"] = clientTimestamp,
                    ["seq"] = sequence,
                }
            };
            IGPTransportSendResult result = TrySendMessages(new[] { probe }, isControl: true);
            if (result == IGPTransportSendResult.Accepted)
            {
                pendingIdleProbes[sequence] = new PendingIdleProbe(clientTimestamp, now);
                totalIdleProbesAccepted += 1;
                return;
            }

            if (result != IGPTransportSendResult.Faulted)
            {
                totalIdleProbeAdmissionBlocked += 1;
                if (ShouldLog(IGPLogLevel.Warning))
                {
                    anomalyLogLimiter.ObserveEvent(
                        "idle-probe-admission-blocked",
                        (float)now,
                        () => $"result={result} retryAfterMs={IdleProbeIntervalSeconds * 1000:F0} " +
                              $"pendingGroups={pendingSendGroups.Count} pendingBytes={pendingSendBytes} " +
                              $"{BuildPeerContext()} {BuildKcpStateSummary()}",
                        message => LogWarning("idle-probe", message));
                }
            }
        }

        private void EvaluateIdleProbeResponses(double now)
        {
            double oldestAge = GetOldestIdleProbeAgeSeconds(now);
            bool transportAcked = kcp != null && kcp.WaitSnd == 0 && !sendProgressTrackingActive;
            bool delayed = transportAcked && oldestAge >= IdleProbeResponseWarningSeconds;
            bool recovered = pendingIdleProbes.Count == 0 ||
                             (transportAcked && oldestAge < IdleProbeResponseWarningSeconds);
            if (ShouldLog(IGPLogLevel.Warning))
            {
                anomalyLogLimiter.ObserveContinuous(
                    "arena-pong-response-delayed",
                    delayed,
                    recovered,
                    (float)now,
                    () => $"oldestProbeAgeMs={oldestAge * 1000:F0} pendingProbes={pendingIdleProbes.Count} " +
                          $"kcpAckProgressing={transportAcked} {BuildPeerContext()} {BuildKcpStateSummary()}",
                    message => LogWarning("idle-probe", message),
                    message => LogLifecycle("idle-probe", message));
            }

            while (GetOldestIdleProbeAgeSeconds(now) >= IdleProbeExpirationSeconds)
            {
                EvictOldestIdleProbe();
            }
        }

        private void EvictOldestIdleProbe()
        {
            int oldestSequence = 0;
            double oldestSentAt = double.MaxValue;
            bool found = false;
            foreach (KeyValuePair<int, PendingIdleProbe> entry in pendingIdleProbes)
            {
                if (!found || entry.Value.SentAt < oldestSentAt)
                {
                    found = true;
                    oldestSequence = entry.Key;
                    oldestSentAt = entry.Value.SentAt;
                }
            }

            if (found && pendingIdleProbes.Remove(oldestSequence))
            {
                totalIdleProbeEvicted += 1;
            }
        }

        private void SendRawJson(object obj)
        {
            var json = JsonConvert.SerializeObject(obj);
            _ = SendFrameImmediate(
                Encoding.UTF8.GetBytes(json),
                $"type={obj.GetType().Name} encoding=json-raw");
        }

        private bool SendFrameImmediate(byte[] payload, string detail)
        {
            if (kcp == null)
            {
                totalDroppedSends += 1;
                LogWarning("send", $"event=send-dropped reason=no-kcp {detail}");
                return false;
            }

            var frame = IGPKcpFrameCodec.EncodeFrame(payload, transportOptions);
            int result = kcp.Send(frame, 0, frame.Length);
            if (result == 0)
            {
                totalFramesSent += 1;
                FlushKcpOutput("handshake");
                LogStateIfNeeded(force: false, reason: "send");
            }
            else
            {
                totalDroppedSends += 1;
                if (ShouldLog(IGPLogLevel.Warning))
                {
                    LogWarning(
                        "send",
                        $"event=send-rejected {detail} payloadBytes={payload.Length} frameBytes={frame.Length} " +
                        $"result={result} {BuildKcpStateSummary()}");
                }
            }

            return result == 0;
        }

        private IGPTransportSendResult RejectSendGroup(IGPTransportSendResult result, string reason, int messageCount)
        {
            totalRejectedSendGroups += 1;
            if (ShouldLog(IGPLogLevel.Warning))
            {
                anomalyLogLimiter.ObserveEvent(
                    $"send-rejected-{reason}",
                    Time.realtimeSinceStartup,
                    () => $"result={result} messages={messageCount} reason={reason} pendingGroups={pendingSendGroups.Count} " +
                          $"pendingBytes={pendingSendBytes} {BuildKcpStateSummary()}",
                    message => LogWarning("send", message));
            }
            return result;
        }

        private void FlushKcpOutput(string reason)
        {
            if (kcp == null)
            {
                return;
            }

            try
            {
                uint now = NowMs();
                kcp.Update(now);
                kcp.Flush();
                nextUpdate = kcp.Check(now);
            }
            catch (Exception ex)
            {
                totalSendErrors += 1;
                if (ShouldLog(IGPLogLevel.Warning))
                {
                    LogWarning(
                        "send",
                        $"event=flush-failed reason={reason} error={ex.Message} {BuildKcpStateSummary()}");
                }

                Faulted?.Invoke(new IGPTransportFault(IGPRealtimeTransport.Kcp, "KCP_FLUSH_FAILED", $"KCP flush failed: {ex.Message}", true, ex));
            }
        }

        private void ApplyWindowSize()
        {
            if (kcp == null)
            {
                return;
            }

            kcp.SetWindowSize((uint)sendWindowSize, (uint)receiveWindowSize);
        }

        private void HandlePayload(byte[] payload)
        {
            if (TryHandleBinaryEnvelope(payload))
            {
                return;
            }

            var json = Encoding.UTF8.GetString(payload);
            if (string.IsNullOrWhiteSpace(json))
            {
                return;
            }

            try
            {
                var obj = JObject.Parse(json);
                var t = obj.Value<string>("type");
                
                if (t == "kcp_handshake_ack")
                {
                    if (!authenticated)
                    {
                        authenticated = true;
                        connectedAt = Time.realtimeSinceStartup;
                        acceptingBusinessSends = true;
                        lastSendProgressAt = Time.realtimeSinceStartupAsDouble;
                        lastReceiveProgressAt = lastSendProgressAt;
                        lastKcpActivityAt = lastSendProgressAt;
                        lastIdleProbeAttemptAt = lastKcpActivityAt;
                        observedRcvNxt = kcp?.rcv_nxt ?? 0;
                        TransitionHealth(IGPKcpHealthState.Healthy, "handshake-ack");
                        if (ShouldLog(IGPLogLevel.Info))
                        {
                            LogLifecycle("handshake", $"event=ack {BuildKcpStateSummary()}");
                            LogLifecycle(
                                "idle-probe",
                                $"event=enabled idleThresholdMs={IdleProbeIntervalSeconds * 1000:F0} " +
                                $"maxPending={MaxPendingIdleProbes} {BuildPeerContext()} {BuildKcpStateSummary()}");
                        }

                        LogStateIfNeeded(force: true, reason: "handshake-ack");
                        ConnectionChanged?.Invoke(new IGPTransportConnectionChanged(IGPRealtimeTransport.Kcp, true, "handshake-ack"));
                    }
                }
                else if (t == "pong")
                {
                    // 处理pong响应，计算RTT
                    HandlePong(obj);
                }
            }
            catch
            {
                // ignore type check errors
            }

            try
            {
                var message = JsonConvert.DeserializeObject<Message>(json);
                if (message != null)
                {
                    MessageReceived?.Invoke(new IGPTransportMessageReceived(IGPRealtimeTransport.Kcp, message));
                }
            }
            catch (Exception ex)
            {
                totalReceiveErrors += 1;
                if (ShouldLog(IGPLogLevel.Warning))
                {
                    LogWarning("recv", $"event=payload-parse-failed error={ex.Message} bytes={payload.Length} {BuildKcpStateSummary()}");
                }

                Faulted?.Invoke(new IGPTransportFault(IGPRealtimeTransport.Kcp, "KCP_MESSAGE_PARSE_FAILED", $"Failed to parse KCP message: {ex.Message}", false, ex));
            }
        }

        private bool TryEncodeBinaryDataPlaneMessage(Message message, out byte[] payload)
        {
            payload = Array.Empty<byte>();
            if (!string.Equals(message.type, "p2p_data", StringComparison.Ordinal) || message.content == null)
            {
                return false;
            }

            P2PMessagePayload? dataPayload;
            if (message.content is P2PMessagePayload typedPayload)
            {
                dataPayload = typedPayload;
            }
            else if (message.content is JObject jobj)
            {
                dataPayload = jobj.ToObject<P2PMessagePayload>();
            }
            else
            {
                dataPayload = JsonConvert.DeserializeObject<P2PMessagePayload>(
                    JsonConvert.SerializeObject(message.content));
            }

            if (dataPayload == null ||
                string.IsNullOrWhiteSpace(dataPayload.data))
            {
                return false;
            }

            bool hasReliableChunkMetadata = HasReliableChunkMetadata(dataPayload);
            string transportChannel = IGPP2PTransportChannels.Resolve(
                dataPayload.transportChannel,
                hasReliableChunkMetadata);
            if (dataPayload.transportSequence.HasValue ||
                !string.Equals(transportChannel, IGPP2PTransportChannels.Data, StringComparison.Ordinal))
            {
                return false;
            }

            byte[] rawPayload = Convert.FromBase64String(dataPayload.data);
            string senderPlayerId = !string.IsNullOrWhiteSpace(dataPayload.senderId)
                ? dataPayload.senderId
                : message.playerId ?? PlayerId ?? string.Empty;
            string targetPlayerId = dataPayload.targetId ?? message.targetPlayerId ?? string.Empty;
            var targetKind = string.IsNullOrWhiteSpace(targetPlayerId)
                ? IGPKcpTargetKind.Broadcast
                : IGPKcpTargetKind.Player;

            IGPKcpBinaryEnvelope envelope;
            if (hasReliableChunkMetadata)
            {
                if (!HasCompleteReliableChunkMetadata(dataPayload))
                {
                    return false;
                }

                envelope = new IGPKcpBinaryEnvelope(
                    version: IGPKcpBinaryEnvelopeCodec.Version2,
                    flags: IGPKcpBinaryEnvelopeCodec.ReliableChunkFlag,
                    messageType: dataPayload.messageType,
                    targetKind: targetKind,
                    senderPlayerId: senderPlayerId,
                    targetPlayerId: targetPlayerId,
                    payload: rawPayload,
                    reliableMessageId: dataPayload.reliableMessageId,
                    reliableChunkIndex: dataPayload.reliableChunkIndex,
                    reliableChunkCount: dataPayload.reliableChunkCount,
                    reliableTotalBytes: dataPayload.reliableTotalBytes,
                    reliableMessageType: dataPayload.reliableMessageType);
            }
            else
            {
                envelope = new IGPKcpBinaryEnvelope(
                    version: IGPKcpBinaryEnvelopeCodec.Version1,
                    flags: 0,
                    messageType: dataPayload.messageType,
                    targetKind: targetKind,
                    senderPlayerId: senderPlayerId,
                    targetPlayerId: targetPlayerId,
                    payload: rawPayload);
            }

            try
            {
                payload = IGPKcpBinaryEnvelopeCodec.Encode(envelope, transportOptions);
                return true;
            }
            catch (Exception ex)
            {
                if (ShouldLog(IGPLogLevel.Warning))
                {
                    anomalyLogLimiter.ObserveEvent(
                        "binary-envelope-encode-failed",
                        Time.realtimeSinceStartup,
                        () => $"reliableChunk={hasReliableChunkMetadata} error={ex.Message} {BuildKcpStateSummary()}",
                        message => LogWarning("anomaly", message));
                }

                payload = Array.Empty<byte>();
                return false;
            }
        }

        private bool TryHandleBinaryEnvelope(byte[] payload)
        {
            if (payload == null ||
                payload.Length == 0 ||
                (payload[0] != IGPKcpBinaryEnvelopeCodec.Version1 &&
                 payload[0] != IGPKcpBinaryEnvelopeCodec.Version2))
            {
                return false;
            }

            try
            {
                var envelope = IGPKcpBinaryEnvelopeCodec.Decode(payload);
                var p2pPayload = new P2PMessagePayload
                {
                    senderId = envelope.SenderPlayerId,
                    targetId = envelope.TargetPlayerId,
                    data = Convert.ToBase64String(envelope.Payload),
                    messageType = envelope.MessageType,
                    transportChannel = IGPP2PTransportChannels.Data,
                    reliable = true
                };
                if (envelope.IsReliableChunk)
                {
                    p2pPayload.reliableMessageId = envelope.ReliableMessageId;
                    p2pPayload.reliableChunkIndex = envelope.ReliableChunkIndex;
                    p2pPayload.reliableChunkCount = envelope.ReliableChunkCount;
                    p2pPayload.reliableTotalBytes = envelope.ReliableTotalBytes;
                    p2pPayload.reliableMessageType = envelope.ReliableMessageType;
                }

                MessageReceived?.Invoke(new IGPTransportMessageReceived(IGPRealtimeTransport.Kcp, new Message
                {
                    type = "p2p_data",
                    roomId = RoomId ?? string.Empty,
                    playerId = envelope.SenderPlayerId,
                    targetPlayerId = envelope.TargetPlayerId,
                    reliable = true,
                    content = p2pPayload
                }));

                return true;
            }
            catch (Exception ex)
            {
                totalReceiveErrors += 1;
                if (ShouldLog(IGPLogLevel.Warning))
                {
                    LogWarning("recv", $"event=binary-envelope-parse-failed error={ex.Message} bytes={payload.Length} {BuildKcpStateSummary()}");
                }

                Faulted?.Invoke(new IGPTransportFault(IGPRealtimeTransport.Kcp, "KCP_BINARY_PARSE_FAILED", $"Failed to parse KCP binary envelope: {ex.Message}", false, ex));
                return true;
            }
        }

        private static bool HasReliableChunkMetadata(P2PMessagePayload dataPayload)
        {
            return !string.IsNullOrWhiteSpace(dataPayload.reliableMessageId) ||
                   dataPayload.reliableChunkIndex.HasValue ||
                   dataPayload.reliableChunkCount.HasValue ||
                   dataPayload.reliableTotalBytes.HasValue ||
                   dataPayload.reliableMessageType.HasValue;
        }

        private static bool HasCompleteReliableChunkMetadata(P2PMessagePayload dataPayload)
        {
            return !string.IsNullOrWhiteSpace(dataPayload.reliableMessageId) &&
                   dataPayload.reliableChunkIndex.HasValue &&
                   dataPayload.reliableChunkCount.HasValue &&
                   dataPayload.reliableTotalBytes.HasValue &&
                   dataPayload.reliableMessageType.HasValue;
        }

        /// <summary>
        /// 处理Pong响应，计算RTT
        /// </summary>
        private void HandlePong(JObject pongMessage)
        {
            try
            {
                var content = pongMessage["content"] as JObject;
                if (content == null || content.Count == 0)
                {
                    LogInvalidIdleProbePong("missing-content");
                    return;
                }
                
                // 从content中获取seq和clientTimestamp
                var seqToken = content["seq"];
                var timestampToken = content["clientTimestamp"];
                
                if (seqToken == null || timestampToken == null)
                {
                    LogInvalidIdleProbePong("missing-sequence-or-timestamp");
                    return;
                }
                
                int seq = seqToken.Type == JTokenType.Float 
                    ? (int)seqToken.Value<double>() 
                    : seqToken.Value<int>();

                lastIdleProbePongAt = Time.realtimeSinceStartupAsDouble;
                lastKcpActivityAt = lastIdleProbePongAt;
                totalIdleProbePongs += 1;
                if (!pendingIdleProbes.TryGetValue(seq, out PendingIdleProbe probe))
                {
                    return;
                }

                pendingIdleProbes.Remove(seq);

                // 计算RTT（使用本地记录的客户端时间戳，避免被响应内容污染）
                long now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
                float rtt = Math.Max(0, now - probe.ClientTimestampUnixMs) / 1000f;
                rttStats.AddSample(rtt);
            }
            catch (Exception ex)
            {
                totalReceiveErrors += 1;
                if (ShouldLog(IGPLogLevel.Warning))
                {
                    anomalyLogLimiter.ObserveEvent(
                        "idle-probe-pong-invalid",
                        Time.realtimeSinceStartup,
                        () => $"reason=parse-failed error={ex.Message} {BuildKcpStateSummary()}",
                        message => LogWarning("idle-probe", message));
                }
            }
        }

        private void LogInvalidIdleProbePong(string reason)
        {
            if (!ShouldLog(IGPLogLevel.Warning))
            {
                return;
            }

            anomalyLogLimiter.ObserveEvent(
                "idle-probe-pong-invalid",
                Time.realtimeSinceStartup,
                () => $"reason={reason} {BuildKcpStateSummary()}",
                message => LogWarning("idle-probe", message));
        }

        private int SendHighWatermark => Math.Min(1024, 2 * SendWindowSize);
        private int SendLowWatermark => SendHighWatermark / 2;
        private const int BusinessPendingGroupLimit = PendingSendGroupLimit - ControlReserveGroups;
        private const int BusinessPendingByteLimit = PendingSendByteLimit - ControlReserveBytes;

        private void DrainPendingSends()
        {
            if (kcp == null || !acceptingBusinessSends || sendSuspectActive ||
                pendingFaultReason != IGPKcpFaultReason.None)
            {
                return;
            }

            if (!RefreshSendBackpressure() || sendBackpressured)
            {
                return;
            }

            int segmentsThisTick = 0;
            int bytesThisTick = 0;
            int framesThisTick = 0;
            while (pendingSendGroups.Count > 0)
            {
                var group = pendingSendGroups.Peek();
                byte[] frame = group.Frames[group.NextFrameIndex];
                int estimatedSegments = Math.Max(1, (frame.Length + (int)kcp.mss - 1) / (int)kcp.mss);
                if (framesThisTick > 0 &&
                    (segmentsThisTick + estimatedSegments > MaxNewSegmentsPerTick ||
                     bytesThisTick + frame.Length > MaxSendBytesPerTick))
                {
                    break;
                }

                int result = kcp.Send(frame, 0, frame.Length);
                if (result != 0)
                {
                    totalDroppedSends += group.Frames.Count - group.NextFrameIndex;
                    if (ShouldLog(IGPLogLevel.Error))
                    {
                        LogError(
                            "send",
                            $"event=kcp-send-failed result={result} frameBytes={frame.Length} {group.Detail} {BuildKcpStateSummary()}");
                    }
                    QueueFault(IGPKcpFaultReason.SendQueueOverflow);
                    break;
                }

                group.NextFrameIndex += 1;
                pendingSendBytes = Math.Max(0, pendingSendBytes - frame.Length);
                totalFramesSent += 1;
                framesThisTick += 1;
                segmentsThisTick += estimatedSegments;
                bytesThisTick += frame.Length;

                if (!sendProgressTrackingActive && kcp.WaitSnd > 0)
                {
                    sendProgressTrackingActive = true;
                    lastSendProgressAt = Time.realtimeSinceStartupAsDouble;
                }

                if (group.NextFrameIndex >= group.Frames.Count)
                {
                    pendingSendGroups.Dequeue();
                }

                if (!RefreshSendBackpressure() || sendBackpressured)
                {
                    break;
                }
            }

        }

        private bool RefreshSendBackpressure()
        {
            if (kcp == null)
            {
                sendBackpressured = false;
                return false;
            }
            if (kcp.WaitSnd >= SendQueueHardFaultThreshold)
            {
                QueueFault(IGPKcpFaultReason.SendQueueOverflow);
                return false;
            }

            if (!sendBackpressured && kcp.WaitSnd >= SendHighWatermark)
            {
                sendBackpressured = true;
                LogBackpressureTransition("entered");
            }
            else if (sendBackpressured && kcp.WaitSnd <= SendLowWatermark)
            {
                sendBackpressured = false;
                LogBackpressureTransition("recovered");
            }

            return pendingFaultReason == IGPKcpFaultReason.None;
        }

        private void LogBackpressureTransition(string transition)
        {
            if (ShouldLog(IGPLogLevel.Info))
            {
                LogLifecycle(
                    "backpressure",
                    $"event={transition} waitSnd={kcp?.WaitSnd ?? 0} high={SendHighWatermark} low={SendLowWatermark} " +
                    $"pendingGroups={pendingSendGroups.Count} pendingBytes={pendingSendBytes} {BuildKcpStateSummary()}");
            }
        }

        private void ObserveKcpInputProgress(uint sndUnaBeforeInput, int waitSndBeforeInput)
        {
            if (kcp == null)
            {
                return;
            }

            double now = Time.realtimeSinceStartupAsDouble;
            bool sendProgressed = sendProgressTrackingActive &&
                                  (kcp.snd_una != sndUnaBeforeInput || kcp.WaitSnd < waitSndBeforeInput);
            if (sendProgressed)
            {
                lastSendProgressAt = now;
                if (!sendSuspectActive)
                {
                    CaptureSendProgressAnchor();
                }
            }
            if (kcp.WaitSnd == 0)
            {
                sendProgressTrackingActive = false;
                SetSendSuspect(false, 0);
            }
            else if (!sendProgressTrackingActive)
            {
                sendProgressTrackingActive = true;
                lastSendProgressAt = now;
                CaptureSendProgressAnchor();
            }

            bool receiveProgressed = kcp.rcv_nxt != observedRcvNxt;
            if (receiveProgressed)
            {
                lastReceiveProgressAt = now;
            }
            if (sendProgressed || receiveProgressed)
            {
                lastKcpActivityAt = now;
            }

            observedRcvNxt = kcp.rcv_nxt;
        }

        private void EvaluateTransportHealth(bool receiveBudgetExhausted)
        {
            if (kcp == null)
            {
                TransitionHealth(IGPKcpHealthState.Disconnected, "no-kcp");
                return;
            }

            if (kcp.state < 0)
            {
                QueueFault(IGPKcpFaultReason.KcpDeadLink);
                return;
            }
            if (!RefreshSendBackpressure())
            {
                return;
            }

            double now = Time.realtimeSinceStartupAsDouble;
            if (!authenticated)
            {
                TransitionHealth(IGPKcpHealthState.Handshaking, "awaiting-handshake");
                return;
            }

            double sendProgressAge = sendProgressTrackingActive
                ? Math.Max(0, now - lastSendProgressAt)
                : 0;
            if (sendProgressTrackingActive && sendProgressAge >= SendFaultAfterSeconds)
            {
                QueueFault(IGPKcpFaultReason.SendProgressTimeout);
                return;
            }

            bool nextSendSuspect = sendProgressTrackingActive && sendProgressAge >= SendSuspectAfterSeconds;
            SetSendSuspect(nextSendSuspect, sendProgressAge);

            if (receiveBudgetExhausted || sendSuspectActive)
            {
                TransitionHealth(IGPKcpHealthState.Suspect, receiveBudgetExhausted
                    ? "receive-budget-exhausted"
                    : "send-progress-delayed");
                return;
            }

            TransitionHealth(IGPKcpHealthState.Healthy, "within-progress-window");
        }

        private void SetSendSuspect(bool suspect, double sendProgressAge)
        {
            if (sendSuspectActive == suspect)
            {
                return;
            }

            double now = Time.realtimeSinceStartupAsDouble;
            if (suspect)
            {
                sendSuspectStartedAt = lastSendProgressAt > 0 ? lastSendProgressAt : now;
            }
            double suspectDuration = suspect || sendSuspectStartedAt <= 0
                ? sendProgressAge
                : Math.Max(0, now - sendSuspectStartedAt);
            sendSuspectActive = suspect;
            string transitionMessage =
                $"event=send-suspect-{(suspect ? "entered" : "recovered")} reason=kcp-no-ack " +
                $"sendProgressAgeMs={sendProgressAge * 1000:F0} {BuildPeerContext()} {BuildKcpStateSummary()}";
            if (suspect && ShouldLog(IGPLogLevel.Warning))
            {
                LogWarning("health", transitionMessage);
            }
            else if (!suspect && ShouldLog(IGPLogLevel.Info))
            {
                LogLifecycle("health", transitionMessage);
            }
            if (ShouldLog(IGPLogLevel.Debug))
            {
                LogDebug(
                    "progress",
                    $"event=send-progress-stall phase={(suspect ? "started" : "recovered")} " +
                    $"durationMs={suspectDuration * 1000:F0} {BuildSendProgressDiagnosticSummary()}");
            }
            if (!suspect)
            {
                sendSuspectStartedAt = 0;
                CaptureSendProgressAnchor();
            }
        }

        private bool QueueFault(IGPKcpFaultReason reason)
        {
            if (reason == IGPKcpFaultReason.None || faultDispatched || pendingFaultReason != IGPKcpFaultReason.None)
            {
                return false;
            }

            pendingFaultReason = reason;
            acceptingBusinessSends = false;
            pendingSendGroups.Clear();
            pendingSendBytes = 0;
            pendingIdleProbes.Clear();
            TransitionHealth(IGPKcpHealthState.Stalled, $"fault-{reason}");
            return true;
        }

        private void DispatchPendingFault()
        {
            if (pendingFaultReason == IGPKcpFaultReason.None || faultDispatched)
            {
                return;
            }

            faultDispatched = true;
            lastFaultReason = pendingFaultReason;
            if (ShouldLog(IGPLogLevel.Error))
            {
                LogError(
                    "health",
                    $"event=transport-fault reason={lastFaultReason} source={(lastFaultReason == IGPKcpFaultReason.SendProgressTimeout ? "kcp-no-ack" : "transport")} " +
                    $"sendProgressAgeMs={GetSendProgressAgeSeconds() * 1000:F0} {BuildPeerContext()} " +
                    BuildKcpStateSummary());
            }
            Faulted?.Invoke(new IGPTransportFault(IGPRealtimeTransport.Kcp, lastFaultReason.ToString(), $"KCP transport faulted: {lastFaultReason}", true, lastFaultReason));
        }

        private void TransitionHealth(IGPKcpHealthState next, string reason)
        {
            if (healthState == next)
            {
                return;
            }

            IGPKcpHealthState previous = healthState;
            healthState = next;
            if (ShouldLog(IGPLogLevel.Info))
            {
                LogLifecycle(
                    "health",
                    $"event=transition previous={previous} current={next} reason={reason} " +
                    $"sendProgressAgeMs={GetSendProgressAgeSeconds() * 1000:F0} " +
                    $"receiveProgressAgeMs={GetReceiveProgressAgeSeconds() * 1000:F0} {BuildKcpStateSummary()}");
            }
        }

        private double GetSendProgressAgeSeconds() => sendProgressTrackingActive
            ? Math.Max(0, Time.realtimeSinceStartupAsDouble - lastSendProgressAt)
            : 0;

        private double GetReceiveProgressAgeSeconds() => Math.Max(
            0,
            Time.realtimeSinceStartupAsDouble - lastReceiveProgressAt);

        private double GetKcpIdleAgeSeconds(double now) => Math.Max(0, now - lastKcpActivityAt);

        private double GetLastIdleProbeAttemptAgeSeconds(double now) => totalIdleProbeAttempts == 0
            ? 0
            : Math.Max(0, now - lastIdleProbeAttemptAt);

        private double GetLastIdleProbePongAgeSeconds(double now) => lastIdleProbePongAt <= 0
            ? 0
            : Math.Max(0, now - lastIdleProbePongAt);

        private double GetOldestIdleProbeAgeSeconds(double now)
        {
            double oldestSentAt = double.MaxValue;
            foreach (PendingIdleProbe probe in pendingIdleProbes.Values)
            {
                oldestSentAt = Math.Min(oldestSentAt, probe.SentAt);
            }

            return oldestSentAt == double.MaxValue ? 0 : Math.Max(0, now - oldestSentAt);
        }

        private double GetOldestPendingSendAgeSeconds() => pendingSendGroups.Count == 0
            ? 0
            : Math.Max(0, Time.realtimeSinceStartupAsDouble - pendingSendGroups.Peek().EnqueuedAt);

        private static bool IsWouldBlock(SocketError error) =>
            error == SocketError.WouldBlock || error == SocketError.IOPending;

        private uint NowMs()
        {
            return (uint)(Time.realtimeSinceStartupAsDouble * 1000.0);
        }

        private static IPAddress ResolveIP(string host)
        {
            if (IPAddress.TryParse(host, out var ip))
            {
                return ip;
            }

            var addresses = Dns.GetHostAddresses(host);
            if (addresses.Length == 0)
            {
                throw new InvalidOperationException($"Cannot resolve host: {host}");
            }

            foreach (var addr in addresses)
            {
                if (addr.AddressFamily == AddressFamily.InterNetwork)
                {
                    return addr;
                }
            }

            return addresses[0];
        }

        private static uint CreateRandomConv()
        {
            Span<byte> b = stackalloc byte[4];
            RandomNumberGenerator.Fill(b);
            return BinaryPrimitives.ReadUInt32LittleEndian(b);
        }

        private static string FormatTokenForLog(string token)
        {
            if (string.IsNullOrWhiteSpace(token))
            {
                return "<empty>";
            }

            if (token.Length <= 8)
            {
                return token;
            }

            return $"{token[..4]}...{token[^4..]}";
        }

        private void LogLifecycle(string path, string message)
        {
            IGPLog.Info(LogScope, path, message);
        }

        private void LogDebug(string path, string message)
        {
            IGPLog.Debug(LogScope, path, message);
        }

        private void LogWarning(string path, string message)
        {
            IGPLog.Warning(LogScope, path, message);
        }

        private void LogError(string path, string message)
        {
            IGPLog.Error(LogScope, path, message);
        }

        private bool ShouldLog(IGPLogLevel messageLevel)
        {
            return IGPLog.ShouldLog(messageLevel);
        }

        private void ObserveTickGap(double now)
        {
            if (lastTickStartedAt <= 0)
            {
                lastTickStartedAt = now;
                return;
            }

            lastTickGapSeconds = Math.Max(0, now - lastTickStartedAt);
            lastTickStartedAt = now;
            maxTickGapSinceSendProgressSeconds = Math.Max(
                maxTickGapSinceSendProgressSeconds,
                lastTickGapSeconds);
            sampleMaxTickGapSeconds = Math.Max(sampleMaxTickGapSeconds, lastTickGapSeconds);
            if (!authenticated || lastTickGapSeconds < TickGapLogThresholdSeconds ||
                !ShouldLog(IGPLogLevel.Debug))
            {
                return;
            }

            anomalyLogLimiter.ObserveEvent(
                "unity-tick-gap",
                (float)now,
                () => $"tickGapMs={lastTickGapSeconds * 1000:F0} thresholdMs={TickGapLogThresholdSeconds * 1000:F0} " +
                      $"{BuildSendProgressDiagnosticSummary()} {BuildKcpStateSummary()}",
                message => LogDebug("progress", message));
        }

        private void CaptureSendProgressAnchor()
        {
            progressAnchorDatagramsReceived = totalDatagramsReceived;
            progressAnchorDatagramsSent = totalDatagramsSent;
            progressAnchorSocketWouldBlock = totalSocketWouldBlock;
            progressAnchorKcpInputErrors = totalKcpInputErrors;
            progressAnchorSndUna = kcp?.snd_una ?? 0;
            progressAnchorWaitSnd = kcp?.WaitSnd ?? 0;
            maxTickGapSinceSendProgressSeconds = 0;
        }

        private string BuildSendProgressDiagnosticSummary()
        {
            return
                $"progress(sendAgeMs={GetSendProgressAgeSeconds() * 1000:F0},receiveAgeMs={GetReceiveProgressAgeSeconds() * 1000:F0}," +
                $"anchorSndUna={progressAnchorSndUna},sndUna={kcp?.snd_una ?? 0}," +
                $"anchorWaitSnd={progressAnchorWaitSnd},waitSnd={kcp?.WaitSnd ?? 0}) " +
                $"sinceSendProgress(datagramsIn={Math.Max(0, totalDatagramsReceived - progressAnchorDatagramsReceived)}," +
                $"datagramsOut={Math.Max(0, totalDatagramsSent - progressAnchorDatagramsSent)}," +
                $"socketWouldBlock={Math.Max(0, totalSocketWouldBlock - progressAnchorSocketWouldBlock)}," +
                $"kcpInputErrors={Math.Max(0, totalKcpInputErrors - progressAnchorKcpInputErrors)}) " +
                $"tick(lastGapMs={lastTickGapSeconds * 1000:F0},maxGapSinceProgressMs={maxTickGapSinceSendProgressSeconds * 1000:F0})";
        }

        private void LogDebugStateIfNeeded(string reason)
        {
            if (!authenticated || kcp == null)
            {
                return;
            }

            float now = Time.realtimeSinceStartup;
            if (lastDebugStateLogAt > 0 && now - lastDebugStateLogAt < DebugStateLogIntervalSeconds)
            {
                return;
            }

            lastDebugStateLogAt = now;
            int datagramsIn = Math.Max(0, totalDatagramsReceived - sampleDatagramsReceived);
            int datagramsOut = Math.Max(0, totalDatagramsSent - sampleDatagramsSent);
            long datagramBytesIn = Math.Max(0, totalDatagramBytesReceived - sampleDatagramBytesReceived);
            long datagramBytesOut = Math.Max(0, totalDatagramBytesSent - sampleDatagramBytesSent);
            int framesIn = Math.Max(0, totalFramesReceived - sampleFramesReceived);
            int framesOut = Math.Max(0, totalFramesSent - sampleFramesSent);
            long socketWouldBlock = Math.Max(0, totalSocketWouldBlock - sampleSocketWouldBlock);
            long inputErrors = Math.Max(0, totalKcpInputErrors - sampleKcpInputErrors);
            long idleProbeAttempts = Math.Max(0, totalIdleProbeAttempts - sampleIdleProbeAttempts);
            long idleProbesAccepted = Math.Max(0, totalIdleProbesAccepted - sampleIdleProbesAccepted);
            long idleProbePongs = Math.Max(0, totalIdleProbePongs - sampleIdleProbePongs);
            long idleProbeEvicted = Math.Max(0, totalIdleProbeEvicted - sampleIdleProbeEvicted);
            long idleProbeAdmissionBlocked = Math.Max(
                0,
                totalIdleProbeAdmissionBlocked - sampleIdleProbeAdmissionBlocked);
            uint sndUnaDelta = unchecked(kcp.snd_una - sampleSndUna);
            uint sndNxtDelta = unchecked(kcp.snd_nxt - sampleSndNxt);
            uint rcvNxtDelta = unchecked(kcp.rcv_nxt - sampleRcvNxt);
            long appPayloadsReceived = applicationFramePayloadsReceived;
            long appFramesCreated = applicationFramesCreated;
            long appFramesCompressed = applicationFramesCompressed;
            long appRawBytes = applicationFrameRawBytes;
            long appWireBytes = applicationFrameWireBytes;
            int appMaxQueueMessages = applicationFrameMaxQueueMessages;
            int appMaxQueueBytes = applicationFrameMaxQueueBytes;
            double appMaxQueueAgeSeconds = applicationFrameMaxQueueAgeSeconds;
            double appMaxSchedulerGapSeconds = applicationFrameMaxSchedulerGapSeconds;
            double maxTickGapSeconds = sampleMaxTickGapSeconds;
            double sampleNow = Time.realtimeSinceStartupAsDouble;
            double kcpIdleAgeSeconds = GetKcpIdleAgeSeconds(sampleNow);
            double oldestIdleProbeAgeSeconds = GetOldestIdleProbeAgeSeconds(sampleNow);
            double lastIdleProbeAttemptAgeSeconds = GetLastIdleProbeAttemptAgeSeconds(sampleNow);
            double lastIdleProbePongAgeSeconds = GetLastIdleProbePongAgeSeconds(sampleNow);

            sampleDatagramsReceived = totalDatagramsReceived;
            sampleDatagramsSent = totalDatagramsSent;
            sampleDatagramBytesReceived = totalDatagramBytesReceived;
            sampleDatagramBytesSent = totalDatagramBytesSent;
            sampleFramesReceived = totalFramesReceived;
            sampleFramesSent = totalFramesSent;
            sampleSocketWouldBlock = totalSocketWouldBlock;
            sampleKcpInputErrors = totalKcpInputErrors;
            sampleIdleProbeAttempts = totalIdleProbeAttempts;
            sampleIdleProbesAccepted = totalIdleProbesAccepted;
            sampleIdleProbePongs = totalIdleProbePongs;
            sampleIdleProbeEvicted = totalIdleProbeEvicted;
            sampleIdleProbeAdmissionBlocked = totalIdleProbeAdmissionBlocked;
            sampleSndUna = kcp.snd_una;
            sampleSndNxt = kcp.snd_nxt;
            sampleRcvNxt = kcp.rcv_nxt;
            sampleMaxTickGapSeconds = 0;
            applicationFramePayloadsReceived = 0;
            applicationFramesCreated = 0;
            applicationFramesCompressed = 0;
            applicationFrameRawBytes = 0;
            applicationFrameWireBytes = 0;
            applicationFrameMaxQueueMessages = 0;
            applicationFrameMaxQueueBytes = 0;
            applicationFrameMaxQueueAgeSeconds = 0;
            applicationFrameMaxSchedulerGapSeconds = 0;

            if (!ShouldLog(IGPLogLevel.Debug))
            {
                return;
            }

            LogDebug(
                "sample",
                $"event=sample windowSeconds={DebugStateLogIntervalSeconds:F0} reason={reason} " +
                $"roomId={RoomId ?? string.Empty} playerId={PlayerId ?? string.Empty} conv={conversationId} health={healthState} " +
                $"traffic(datagramsIn={datagramsIn},datagramsOut={datagramsOut},bytesIn={datagramBytesIn},bytesOut={datagramBytesOut}," +
                $"framesIn={framesIn},framesOut={framesOut}) " +
                $"sequence(sndUna={kcp.snd_una},sndUnaDelta={sndUnaDelta},sndNxt={kcp.snd_nxt},sndNxtDelta={sndNxtDelta}," +
                $"rcvNxt={kcp.rcv_nxt},rcvNxtDelta={rcvNxtDelta}) " +
                $"queues(waitSnd={kcp.WaitSnd},sndQueue={kcp.snd_queue.Count},sndBuf={kcp.snd_buf.Count}," +
                $"rcvQueue={kcp.rcv_queue.Count},rcvBuf={kcp.rcv_buf.Count},pendingGroups={pendingSendGroups.Count},pendingBytes={pendingSendBytes}) " +
                $"timing(srttMs={kcp.rx_srtt},rtoMs={kcp.rx_rto},maxUnityTickGapMs={maxTickGapSeconds * 1000.0:F1}) " +
                $"errors(socketWouldBlock={socketWouldBlock},kcpInput={inputErrors}) " +
                $"idleProbe(idleAgeMs={kcpIdleAgeSeconds * 1000.0:F1},attempts={idleProbeAttempts},accepted={idleProbesAccepted}," +
                $"pongs={idleProbePongs},evicted={idleProbeEvicted},admissionBlocked={idleProbeAdmissionBlocked}," +
                $"pending={pendingIdleProbes.Count},oldestPendingMs={oldestIdleProbeAgeSeconds * 1000.0:F1}," +
                $"lastAttemptAgeMs={lastIdleProbeAttemptAgeSeconds * 1000.0:F1},lastPongAgeMs={lastIdleProbePongAgeSeconds * 1000.0:F1}) " +
                $"application(receivedPayloads={appPayloadsReceived},createdFrames={appFramesCreated},compressedFrames={appFramesCompressed}," +
                $"rawBytes={appRawBytes},wireBytes={appWireBytes},maxQueueMessages={appMaxQueueMessages}," +
                $"maxQueueBytes={appMaxQueueBytes},maxQueueAgeMs={appMaxQueueAgeSeconds * 1000.0:F1}," +
                $"maxSchedulerGapMs={appMaxSchedulerGapSeconds * 1000.0:F1})");
        }

        private void LogStateIfNeeded(bool force, string reason)
        {
            float now = Time.realtimeSinceStartup;
            int queueTotal = GetKcpQueueTotal();
            bool busy = queueTotal >= QueueWarningThreshold;
            bool critical = queueTotal >= QueueCriticalThreshold;
            if ((!busy && !critical) || !ShouldLog(IGPLogLevel.Warning))
            {
                return;
            }

            if (!force &&
                now - lastStateLogAt < BusyStateLogIntervalSeconds &&
                Math.Abs(queueTotal - lastStateLogQueueTotal) < QueueWarningThreshold)
            {
                return;
            }

            lastStateLogAt = now;
            lastStateLogQueueTotal = queueTotal;

            string message = $"event=state reason={reason} {BuildDiagnosticsSummary()}";
            if (critical)
            {
                LogWarning("state", $"level=critical action=observe-only {message}");
            }
            else if (busy)
            {
                LogWarning("state", $"level=busy action=observe-only {message}");
            }
        }

        private int GetKcpQueueTotal()
        {
            if (kcp == null)
            {
                return 0;
            }

            return kcp.snd_queue.Count +
                   kcp.snd_buf.Count +
                   kcp.rcv_queue.Count +
                   kcp.rcv_buf.Count;
        }

        private string BuildDiagnosticsSummary()
        {
            string endpoint = remoteEndPoint?.ToString() ?? handshakeTarget;
            string uptime = connectedAt > 0f
                ? $"{Math.Max(0f, Time.realtimeSinceStartup - connectedAt):F1}s"
                : "0.0s";
            double now = Time.realtimeSinceStartupAsDouble;

            return
                $"connected={IsConnected} authenticated={authenticated} alive={IsAlive} health={healthState} fault={lastFaultReason} " +
                $"idleProbe(intervalMs={IdleProbeIntervalSeconds * 1000:F0},idleAgeMs={GetKcpIdleAgeSeconds(now) * 1000:F0}," +
                $"pending={pendingIdleProbes.Count},oldestPendingMs={GetOldestIdleProbeAgeSeconds(now) * 1000:F0}," +
                $"attempts={totalIdleProbeAttempts},accepted={totalIdleProbesAccepted},pongs={totalIdleProbePongs}," +
                $"evicted={totalIdleProbeEvicted},admissionBlocked={totalIdleProbeAdmissionBlocked}) " +
                $"maxDatagramsPerTick={MaxDatagramsPerTick} sendWindow={SendWindowSize} receiveWindow={ReceiveWindowSize} " +
                $"sendHighWatermark={SendHighWatermark} sendLowWatermark={SendLowWatermark} sendBackpressured={sendBackpressured} " +
                $"pendingSendGroups={pendingSendGroups.Count} pendingSendBytes={pendingSendBytes} " +
                $"pendingHeadControl={(pendingSendGroups.Count > 0 && pendingSendGroups.Peek().IsControl)} " +
                $"oldestPendingSendAgeMs={GetOldestPendingSendAgeSeconds() * 1000:F0} " +
                $"sendProgressAgeMs={GetSendProgressAgeSeconds() * 1000:F0} receiveProgressAgeMs={GetReceiveProgressAgeSeconds() * 1000:F0} " +
                $"roomId={RoomId ?? string.Empty} localPlayerId={PlayerId ?? string.Empty} endpoint={endpoint} uptime={uptime} " +
                $"{BuildTransportOptionsSummary()} {BuildRttSummary()} {BuildKcpStateSummary()} " +
                $"totals(datagramsIn={totalDatagramsReceived},datagramsOut={totalDatagramsSent}," +
                $"datagramBytesIn={totalDatagramBytesReceived},datagramBytesOut={totalDatagramBytesSent},framesIn={totalFramesReceived}," +
                $"framesOut={totalFramesSent},payloadsIn={totalPayloadsReceived},sendErrors={totalSendErrors}," +
                $"receiveErrors={totalReceiveErrors},droppedSends={totalDroppedSends},outputAttempted={totalOutputAttempts}," +
                $"socketAccepted={totalSocketAccepted},socketWouldBlock={totalSocketWouldBlock},socketFatal={totalSocketFatal}," +
                $"kcpInputErrors={totalKcpInputErrors})";
        }

        private string BuildTransportOptionsSummary()
        {
            return
                $"limits(payloadMax={transportOptions.KcpDataPlanePayloadMaxBytes},frameMax={transportOptions.KcpFrameMaxBytes}," +
                $"reliableMax={transportOptions.ReliableMessageMaxBytes},chunkMax={transportOptions.ReliableChunkMaxBytes})";
        }

        private string BuildRttSummary()
        {
            if (rttStats.SampleCount == 0)
            {
                return "appRtt(samples=0)";
            }

            return
                $"appRtt(samples={rttStats.SampleCount},lastMs={rttStats.LastRTT * 1000f:F1}," +
                $"avgMs={rttStats.AvgRTT * 1000f:F1},minMs={rttStats.MinRTT * 1000f:F1}," +
                $"maxMs={rttStats.MaxRTT * 1000f:F1})";
        }

        private IGPKcpTransportStats BuildTransportStats()
        {
            if (kcp == null)
            {
                return IGPKcpTransportStats.Unavailable;
            }

            return new IGPKcpTransportStats(
                isAvailable: true,
                smoothedRttMs: kcp.rx_srtt,
                rtoMs: kcp.rx_rto,
                rttVarMs: kcp.rx_rttval,
                waitSnd: kcp.WaitSnd,
                sndQueue: kcp.snd_queue.Count,
                sndBuf: kcp.snd_buf.Count,
                rcvQueue: kcp.rcv_queue.Count,
                rcvBuf: kcp.rcv_buf.Count,
                ackList: kcp.acklist.Count,
                cwnd: kcp.cwnd,
                remoteWindow: kcp.rmt_wnd,
                datagramsIn: totalDatagramsReceived,
                datagramsOut: totalDatagramsSent,
                datagramsInPerSecond: datagramsReceivedPerSecond,
                datagramsOutPerSecond: datagramsSentPerSecond,
                datagramBytesIn: totalDatagramBytesReceived,
                datagramBytesOut: totalDatagramBytesSent,
                pendingSendGroups: pendingSendGroups.Count,
                pendingSendBytes: pendingSendBytes,
                sendBackpressured: sendBackpressured,
                sendProgressAgeSeconds: GetSendProgressAgeSeconds(),
                receiveProgressAgeSeconds: GetReceiveProgressAgeSeconds(),
                rejectedSendGroups: totalRejectedSendGroups,
                oldestPendingSendAgeSeconds: GetOldestPendingSendAgeSeconds());
        }

        private void ResetTransportRates()
        {
            hasTransportRateSample = false;
            lastTransportRateSampleTime = 0f;
            lastRateDatagramsReceived = totalDatagramsReceived;
            lastRateDatagramsSent = totalDatagramsSent;
            lastRateDatagramBytesReceived = totalDatagramBytesReceived;
            lastRateDatagramBytesSent = totalDatagramBytesSent;
            datagramsReceivedPerSecond = 0f;
            datagramsSentPerSecond = 0f;
            datagramBytesReceivedPerSecond = 0f;
            datagramBytesSentPerSecond = 0f;
            anomalyLogLimiter.Reset();
        }

        private bool UpdateTransportRates()
        {
            float now = Time.realtimeSinceStartup;
            if (!hasTransportRateSample ||
                totalDatagramsReceived < lastRateDatagramsReceived ||
                totalDatagramsSent < lastRateDatagramsSent)
            {
                hasTransportRateSample = true;
                lastTransportRateSampleTime = now;
                lastRateDatagramsReceived = totalDatagramsReceived;
                lastRateDatagramsSent = totalDatagramsSent;
                lastRateDatagramBytesReceived = totalDatagramBytesReceived;
                lastRateDatagramBytesSent = totalDatagramBytesSent;
                datagramsReceivedPerSecond = 0f;
                datagramsSentPerSecond = 0f;
                datagramBytesReceivedPerSecond = 0f;
                datagramBytesSentPerSecond = 0f;
                return false;
            }

            float elapsed = now - lastTransportRateSampleTime;
            if (elapsed < TransportRateSampleIntervalSeconds)
            {
                return false;
            }

            datagramsReceivedPerSecond = (totalDatagramsReceived - lastRateDatagramsReceived) / elapsed;
            datagramsSentPerSecond = (totalDatagramsSent - lastRateDatagramsSent) / elapsed;
            datagramBytesReceivedPerSecond = (totalDatagramBytesReceived - lastRateDatagramBytesReceived) / elapsed;
            datagramBytesSentPerSecond = (totalDatagramBytesSent - lastRateDatagramBytesSent) / elapsed;
            lastTransportRateSampleTime = now;
            lastRateDatagramsReceived = totalDatagramsReceived;
            lastRateDatagramsSent = totalDatagramsSent;
            lastRateDatagramBytesReceived = totalDatagramBytesReceived;
            lastRateDatagramBytesSent = totalDatagramBytesSent;
            return true;
        }

        private void EvaluateNetworkAnomalyWarnings()
        {
            if (!ShouldLog(IGPLogLevel.Warning) || kcp == null)
            {
                anomalyLogLimiter.Reset();
                return;
            }

            float now = Time.realtimeSinceStartup;
            float appLastRttMs = rttStats.SampleCount > 0 ? rttStats.LastRTT * 1000f : 0f;
            int kcpSmoothedRttMs = kcp.rx_srtt;
            bool highLatency = appLastRttMs >= IGPNetworkAnomalyThresholds.HighLatencyMs ||
                               kcpSmoothedRttMs >= IGPNetworkAnomalyThresholds.HighLatencyMs;
            bool latencyRecovered = appLastRttMs <= IGPNetworkAnomalyThresholds.HighLatencyRecoveryMs &&
                                    kcpSmoothedRttMs <= IGPNetworkAnomalyThresholds.HighLatencyRecoveryMs;
            anomalyLogLimiter.ObserveContinuous(
                "high-latency",
                highLatency,
                latencyRecovered,
                now,
                () => $"appLastRttMs={appLastRttMs:F1} appAvgRttMs={rttStats.AvgRTT * 1000f:F1} " +
                      $"kcpSrttMs={kcpSmoothedRttMs} kcpRtoMs={kcp.rx_rto} thresholdMs={IGPNetworkAnomalyThresholds.HighLatencyMs} " +
                      BuildKcpStateSummary(),
                message => LogWarning("anomaly", message),
                message => LogLifecycle("anomaly", message));

            bool highTraffic = datagramsReceivedPerSecond >= IGPNetworkAnomalyThresholds.HighDatagramsPerSecond ||
                               datagramsSentPerSecond >= IGPNetworkAnomalyThresholds.HighDatagramsPerSecond ||
                               datagramBytesReceivedPerSecond >= IGPNetworkAnomalyThresholds.HighDatagramBytesPerSecond ||
                               datagramBytesSentPerSecond >= IGPNetworkAnomalyThresholds.HighDatagramBytesPerSecond;
            bool trafficRecovered = datagramsReceivedPerSecond <= IGPNetworkAnomalyThresholds.HighDatagramsPerSecondRecovery &&
                                    datagramsSentPerSecond <= IGPNetworkAnomalyThresholds.HighDatagramsPerSecondRecovery &&
                                    datagramBytesReceivedPerSecond <= IGPNetworkAnomalyThresholds.HighDatagramBytesPerSecondRecovery &&
                                    datagramBytesSentPerSecond <= IGPNetworkAnomalyThresholds.HighDatagramBytesPerSecondRecovery;
            anomalyLogLimiter.ObserveContinuous(
                "high-traffic",
                highTraffic,
                trafficRecovered,
                now,
                () => $"datagramsInPerSecond={datagramsReceivedPerSecond:F0} datagramsOutPerSecond={datagramsSentPerSecond:F0} " +
                      $"bytesInPerSecond={datagramBytesReceivedPerSecond:F0} bytesOutPerSecond={datagramBytesSentPerSecond:F0} " +
                      $"datagramThresholdPerSecond={IGPNetworkAnomalyThresholds.HighDatagramsPerSecond:F0} " +
                      $"byteThresholdPerSecond={IGPNetworkAnomalyThresholds.HighDatagramBytesPerSecond:F0} " +
                      BuildKcpStateSummary(),
                message => LogWarning("anomaly", message),
                message => LogLifecycle("anomaly", message));
        }

        private string BuildKcpStateSummary()
        {
            if (kcp == null)
            {
                return "kcp(null)";
            }

            return
                $"kcp(conv={conversationId},waitSnd={kcp.WaitSnd},sndQueue={kcp.snd_queue.Count},sndBuf={kcp.snd_buf.Count}," +
                $"rcvQueue={kcp.rcv_queue.Count},rcvBuf={kcp.rcv_buf.Count},ackList={kcp.acklist.Count}," +
                $"sndUna={kcp.snd_una},sndNxt={kcp.snd_nxt},rcvNxt={kcp.rcv_nxt}," +
                $"sndWnd={kcp.snd_wnd},rcvWnd={kcp.rcv_wnd},remoteWnd={kcp.rmt_wnd},cwnd={kcp.cwnd}," +
                $"mtu={kcp.mtu},mss={kcp.mss},rto={kcp.rx_rto},srtt={kcp.rx_srtt},rttVar={kcp.rx_rttval}," +
                $"intervalMs={kcp.interval},nextUpdateInMs={GetNextUpdateDelayMs()},state={kcp.state})";
        }

        private string BuildPeerContext() =>
            $"roomId={RoomId ?? string.Empty} playerId={PlayerId ?? string.Empty}";

        private int GetNextUpdateDelayMs()
        {
            if (kcp == null)
            {
                return 0;
            }

            return Math.Max(0, unchecked((int)(nextUpdate - NowMs())));
        }

        private static int NormalizeWindowSize(int value)
        {
            return Math.Min(MaxWindowSize, Math.Max(MinWindowSize, value));
        }

    }
}
