using UnityEngine; namespace SmartAction.Patch; /// /// Verification instrumentation for the movement patches. /// /// These postfixes run on every movement query for every entity in the raid, so nothing here /// may allocate or log on the hot path. Counters are plain increments; the periodic flush is /// gated behind a call counter so Time.unscaledTime is read once per ~4096 calls rather than /// per call. Disable via the "Diagnostics" config entry once the design is trusted. /// internal static class Diagnostics { internal static bool Enabled; private const int TickGate = 4096; private const float FlushIntervalSeconds = 30f; // Guard outcomes — the multiplayer-safety evidence. internal static long LocalCalls; internal static long NonLocalCalls; internal static long NullPlayerCalls; // Patch outcomes. internal static long WalkLifted; internal static long WalkHeldByObstacle; private static int _tick; private static float _nextFlush; // One-shot announcements, so the first occurrence of each interesting event is visible // immediately instead of waiting up to 30s for a flush. private static bool _saidWalkLift; private static bool _saidWalkObstacle; private static bool _saidNullPlayer; internal static void OnceWalkLifted() { if (_saidWalkLift) return; _saidWalkLift = true; Plugin.Log.LogInfo("[diag] CanWalk LIFTED (HealingLegs) — first occurrence"); } internal static void OnceWalkHeldByObstacle() { if (_saidWalkObstacle) return; _saidWalkObstacle = true; Plugin.Log.LogInfo("[diag] CanWalk HELD by obstacle check — first occurrence " + "(this is the CanMove() guard upstream's transpiler dropped)"); } internal static void OnceNullPlayer() { if (_saidNullPlayer) return; _saidNullPlayer = true; Plugin.Log.LogWarning("[diag] MovementContext._player was NULL — guard handled it without throwing. " + "This is the predicted NRE case; it is being absorbed correctly."); } /// Called from the guard. Cheap on all but 1-in-4096 calls. internal static void Tick() { if (++_tick < TickGate) return; _tick = 0; var now = Time.unscaledTime; if (now < _nextFlush) return; _nextFlush = now + FlushIntervalSeconds; Flush(); } internal static void Flush() { Plugin.Log.LogInfo( $"[diag] guard: local={LocalCalls} nonLocal={NonLocalCalls} nullPlayer={NullPlayerCalls} | " + $"walk: lifted={WalkLifted} heldByObstacle={WalkHeldByObstacle}"); if (NonLocalCalls == 0) { Plugin.Log.LogWarning("[diag] nonLocal=0 — no bot/remote MovementContext has been seen yet. " + "Either you are not in a raid with AI, or the postfixes are not firing " + "for other entities. The guard is unproven until this is non-zero."); } } }