worldbox-observer-mod/IdleSpectator/StateProbe.cs
2026-07-14 14:17:42 -05:00

161 lines
4.7 KiB
C#

using NeoModLoader.services;
using UnityEngine;
namespace IdleSpectator;
/// <summary>
/// Samples focus / spectator / power-bar state and logs only on changes or faults.
/// Tail Player.log for <c>[IdleSpectator][STATE]</c> and <c>[IdleSpectator][BAD]</c>.
/// </summary>
public static class StateProbe
{
private static bool _prevIdle;
private static bool _prevFocus;
private static bool _prevVanillaSpec;
private static bool _prevBar;
private static string _prevUnit = "";
private static float _focusLostSince = -1f;
private static float _lastHeartbeatAt = -999f;
private static float _lastBadPowerBarAt = -999f;
private static float _lastBadGapAt = -999f;
private static bool _hadFocusWhileIdle;
private const float HeartbeatSeconds = 10f;
private const float FocusGapBadSeconds = 0.35f;
public static int BadEventCount { get; private set; }
public static int StateEventCount { get; private set; }
public static void ResetCounters()
{
BadEventCount = 0;
StateEventCount = 0;
_lastBadPowerBarAt = -999f;
_lastBadGapAt = -999f;
_focusLostSince = -1f;
_lastHeartbeatAt = -999f;
_hadFocusWhileIdle = false;
}
public static void Update()
{
if (!ModSettings.DebugStateProbe || !Config.game_loaded || World.world == null)
{
return;
}
bool idle = SpectatorMode.Active;
bool focus = MoveCamera.hasFocusUnit();
bool vanillaSpec = MoveCamera.inSpectatorMode();
bool bar = CanvasMain.isBottomBarShowing();
string unit = FocusUnitLabel();
bool changed = idle != _prevIdle
|| focus != _prevFocus
|| vanillaSpec != _prevVanillaSpec
|| bar != _prevBar
|| unit != _prevUnit;
if (changed)
{
StateEventCount++;
LogService.LogInfo(
$"[IdleSpectator][STATE] idle={idle} focus={focus} vanillaSpec={vanillaSpec} powerBar={bar} unit={unit}");
}
if (idle && focus)
{
_hadFocusWhileIdle = true;
}
if (idle && _prevFocus && !focus)
{
BadEventCount++;
LogService.LogInfo("[IdleSpectator][BAD] focus dropped while Idle Spectator is on");
_focusLostSince = Time.unscaledTime;
}
// Only treat prolonged no-focus as bad after we have successfully acquired a target.
// Fresh maps often have a short empty window before the first unit exists.
if (idle && !focus && _hadFocusWhileIdle)
{
if (_focusLostSince < 0f)
{
_focusLostSince = Time.unscaledTime;
}
else if (Time.unscaledTime - _focusLostSince >= FocusGapBadSeconds
&& Time.unscaledTime - _lastBadGapAt >= 1f)
{
_lastBadGapAt = Time.unscaledTime;
BadEventCount++;
LogService.LogInfo(
$"[IdleSpectator][BAD] no focus unit for {Time.unscaledTime - _focusLostSince:0.00}s while idle on (power bar risk)");
}
}
else if (focus)
{
_focusLostSince = -1f;
}
if (idle && bar && Time.unscaledTime - _lastBadPowerBarAt >= 1f)
{
_lastBadPowerBarAt = Time.unscaledTime;
BadEventCount++;
LogService.LogInfo("[IdleSpectator][BAD] power bar showing while Idle Spectator is on");
}
if (idle && Time.unscaledTime - _lastHeartbeatAt >= HeartbeatSeconds)
{
_lastHeartbeatAt = Time.unscaledTime;
StateEventCount++;
LogService.LogInfo(
$"[IdleSpectator][STATE] heartbeat idle={idle} focus={focus} vanillaSpec={vanillaSpec} powerBar={bar} unit={unit}");
}
_prevIdle = idle;
_prevFocus = focus;
_prevVanillaSpec = vanillaSpec;
_prevBar = bar;
_prevUnit = unit;
}
public static void Reset()
{
_prevIdle = false;
_prevFocus = false;
_prevVanillaSpec = false;
_prevBar = false;
_prevUnit = "";
_focusLostSince = -1f;
_lastHeartbeatAt = -999f;
ResetCounters();
}
private static string FocusUnitLabel()
{
if (!MoveCamera.hasFocusUnit())
{
return "-";
}
Actor unit = MoveCamera._focus_unit;
if (unit == null)
{
return "?";
}
try
{
string name = unit.getName();
if (!string.IsNullOrEmpty(name))
{
return name;
}
}
catch
{
// ignore
}
return unit.asset != null ? unit.asset.id : "unit";
}
}