using UnityEngine; namespace ProjectM.Client { /// /// Pure HUD presentation math (unit-tested, no ECS / no UnityEngine.Object). Drives the low-health screen /// vignette + the transient hurt-flash so the damage feedback is deterministic and testable. Used by the /// client-only observe-only in PresentationSystemGroup; the flash decay runs on the /// wall-frame SystemAPI.Time.DeltaTime, which is correct in a presentation system (the dt-trap only /// applies to plain simulation systems). /// public static class HudVisualMath { /// Below this health fraction the low-health vignette starts to ramp in. public const float LowHealthThreshold = 0.35f; /// Maximum steady vignette opacity at 0 HP. public const float MaxVignetteOpacity = 0.55f; /// Opacity boost added by a fresh hit (a hurt flash), decaying back to the steady vignette. public const float HurtFlashKick = 0.40f; /// Seconds for a full hurt-flash kick to decay to zero. public const float HurtFlashDuration = 0.40f; /// 0 at/above the low-health threshold, ramping to 1 as the health fraction falls to 0. public static float VignetteIntensity(float healthFrac) { float f = Mathf.Clamp01(healthFrac); if (f >= LowHealthThreshold) return 0f; return Mathf.Clamp01((LowHealthThreshold - f) / LowHealthThreshold); } /// Steady low-health vignette opacity (no flash) from the current health fraction. public static float VignetteOpacity(float healthFrac) => VignetteIntensity(healthFrac) * MaxVignetteOpacity; /// Decay an active hurt-flash boost toward 0 over seconds (clamped, never negative). public static float DecayFlash(float flash, float dt) => Mathf.Max(0f, flash - (HurtFlashKick / HurtFlashDuration) * Mathf.Max(0f, dt)); /// Final overlay opacity: the steady low-health vignette plus the current flash boost, clamped to 1. public static float CombinedOpacity(float healthFrac, float flash) => Mathf.Clamp01(VignetteOpacity(healthFrac) + Mathf.Clamp01(flash)); } }