using UnityEngine; namespace ProjectM.Client { /// /// Live-tunable knobs for the client-only COMBAT-FEEL slice (Stage E). A static bridge — mirrors /// — so values can be poked at runtime via MCP execute_code /// (e.g. ProjectM.Client.FeelConfig.HitShakeLocal = 0.4f;) WITHOUT a recompile, for interactive /// tuning. Read ONLY by client-presentation systems (, /// ) and the MonoBehaviour — all non-Burst, /// main-thread. NEVER read these from a [BurstCompile] system (managed-static + Color/enum-in-Burst /// hazards); they are presentation-only and never touch the deterministic simulation. /// /// Defaults match the values previously hardcoded in CombatFeedbackSystem so behaviour is byte-identical /// until a knob is poked. re-stamps every field on play-enter via /// [RuntimeInitializeOnLoadMethod(SubsystemRegistration)] because statics survive fast-enter-playmode /// domain reloads — without it a poked value would leak across play-enters and flash stale feel (the exact /// bug 's reset prevents). /// /// public static class FeelConfig { // ---- Feature 1: hit camera punch + (camera-only) hit-stop ---- /// Camera shake when the LOCAL player is hit (fed to PrototypeCameraRig.AddShake, clamp 0.8). public static float HitShakeLocal; /// Camera shake when a remote player / Husk is hit. public static float HitShakeRemote; /// Hit-spark particle burst count (procedural fallback path). public static int HitBurstCount; /// Hit SFX volume. public static float HitSfxVolume; /// Degrees of transient FOV "kick" on a LOCAL hit — the netcode-safe hit-stop (NEVER Time.timeScale). 0 = off. public static float HitStopFovKick; /// Milliseconds the FOV kick eases back to base. public static float HitStopDurationMs; // ---- MC-3: player-dealt-hit punch (magnitude-scaled) + deferred enemy flash ---- /// Min FOV kick (deg) when the LOCAL player lands a hit on an enemy (chip damage). public static float HitStopFovKickMin; /// Max FOV kick (deg) on a heavy player-dealt hit (delta >= HitStopRefDamage). public static float HitStopFovKickMax; /// Reserved cap for the (deferred) true freeze-frame, in fixed frames. public static int HitStopMaxFrames; /// Damage delta that saturates the player-dealt punch to HitStopFovKickMax. public static float HitStopRefDamage; /// Tint for the (deferred) enemy material hit-flash — exposed now, wired in the ShaderGraph slice. public static Color HitFlashColor; /// Master gate for the (deferred) true freeze-frame hit-stop. FALSE for v1 (camera-punch only). public static bool HitStopFreezeEnabled; // ---- Feature 1/2: death camera punch ---- /// Camera shake on LOCAL player death (loudest event by design). public static float PlayerDeathShake; /// Camera shake on a remote player's death. public static float RemotePlayerDeathShake; /// Base death-burst particle count (player death + Husk-death base). public static int DeathBurstCount; // ---- Feature 2: kill-shot fanfare (Husk death) ---- /// Camera shake on a Husk kill (nudged above a glancing hit, kept under PlayerDeathShake). public static float KillShake; /// Multiplier on DeathBurstCount for a Husk kill (result clamped by MaxActiveVfx). public static float KillBurstScale; /// Optional FOV kick on a kill (degrees). 0 = off. public static float KillFovKick; /// Husk-death SFX volume. public static float KillSfxVolume; // ---- Feature 3: respawn shimmer / fade-in (local player recovery) ---- /// Master gate for the local-player respawn shimmer. public static bool RespawnShimmerEnabled; /// Particle burst count for the recovery shimmer. public static int RespawnShimmerBurst; /// Light camera punch on recovery so respawn reads as "reinforcing". public static float RespawnShimmerShake; // ---- Feature 4: reticle lock-on tether (cosmetic aim HINT) ---- /// Master gate for the lock-on tether. public static bool LockOnEnabled; /// Show the tether only on the Gamepad scheme (mirrors the server's gamepad-only auto-target assist). public static bool LockOnGamepadOnly; /// Max world distance from the player to a tethered Husk. public static float LockOnRange; /// Forward half-arc (degrees) around PlayerFacing within which a Husk is eligible. public static float LockOnArcDegrees; /// Tether line tint (subtle highlight, not a laser). public static Color LockOnLineColor; /// Tether line width (world units). public static float LockOnLineWidth; // ---- Feature B: enemy health bars (Slice 1) ---- /// Squared world-distance beyond which a bar is hidden when the pool cap is reached (default 400 = 20m). public static float HealthBarMaxDistSq; // ---- Feature 5 (MC-1): dash juice ---- /// Camera shake on the local player's dash start. public static float DashShake; /// Transient FOV punch (degrees) on dash start — the "lurch" read (camera punch, never Time.timeScale). public static float DashFovKick; /// Afterimage/whoosh particle burst count at dash start. public static int DashBurstCount; /// Dash whoosh SFX volume. public static float DashSfxVolume; /// Particles emitted per frame while the local i-frame window is active (the shimmer trail). public static int DashShimmerPerFrame; /// Suppress local hit-feedback during the local i-frame window (masks the prediction-reconciliation /// Health flicker on a clean dodge — the documented acceptable-not-a-bug interaction). public static bool DashHitSuppress; // ---- Combat feel pass (2026-06): connect cue, hit-flash, kill pop, footsteps, rumble, telegraph beep ---- /// Hit-flash puff density (a colored particle burst in HitFlashColor on an enemy damage edge). public static int HitFlashBurstCount; /// Extra FOV punch (deg) when the LOCAL melee cleave is confirmed to connect (vs a whiff). public static float MeleeConnectFovKick; /// Volume of the meaty melee connect "thunk". public static float MeleeConnectVolume; /// Kill-pop colored flash density on an enemy death. public static int KillFlashBurstCount; /// Footstep SFX volume (07-15: deep muffled underwater thud). public static float FootstepVolume; /// Planar meters travelled per footstep — stride-DISTANCE stepping, so cadence tracks the /// real (drifting) velocity instead of a fixed timer (07-15 underwater feel). public static float FootstepStrideMeters; /// Local player speed (u/s) above which footsteps play. public static float FootstepMinSpeed; /// Per-step random volume jitter (±fraction) — breaks the metronome read. public static float FootstepJitter; /// Silt-puff particles per footstep (0 = off). public static int FootstepPuffCount; // ---- 07-16 underwater feel (gap-list): bubbles, ambience, camera weight, banking ---- /// Seconds between dome bubble-exhaust puffs (0 = off). public static float BubbleIntervalSec; /// Bubbles per exhaust puff. public static int BubbleBurstCount; /// Underwater ambience bed volume (Music bus). public static float AmbienceVolume; /// Mean seconds between distant groans (0 = off; ±40% jitter). public static float GroanIntervalSec; /// Distant-groan volume (Music bus). public static float GroanVolume; /// Camera follow-sharpness multiplier (<1 = the camera drags through the water). public static float CameraDragMult; /// The facing turn rate (deg/s) at which the additive body bank reaches full (±1). public static float BankFullTurnDegPerSec; /// The WALK ring's natural travel speed (u/s; measured off the pack's root-motion variant). /// StrideScale fine-corrects playback against the walk→run blended natural (07-16b/e). public static float TrudgeNaturalSpeed; /// The RUN ring's natural travel speed (u/s; measured). public static float RunNaturalSpeed; /// Shoulder-lamp spot intensity (0 = lamp off). LANTERN: light is territory — the suit lamp CASTS. public static float ShoulderLampIntensity; /// Shoulder-lamp beam range (m). public static float ShoulderLampRange; /// Seconds the Menacing01 combat idle (CombatIdle state) holds after the last melee swing (0 = off). public static float CombatIdleHoldSec; /// Melee slash-arc brightness multiplier (07-19 LANTERN retone: the WEAPON is the read, the arc is a water wake). public static float MeleeArcIntensity; /// Bubbles shed along a melee cleave (0 = off). public static int MeleeArcBubbles; /// Camera hold frames when the melee FINISHER lands (07-20 G5 impact ladder; light hits use HitStopMaxFrames). public static int FinisherHoldFrames; /// Master gate for gamepad rumble (no-op on KBM). public static bool RumbleEnabled; /// Rumble strength on a local hit taken / dealt. public static float RumbleHit; /// Rumble strength on a kill. public static float RumbleKill; /// Seconds a rumble pulse lasts before it auto-stops. public static float RumbleDurationSec; // ---- Deferred-items pass (2026-06): true body hit-flash, remote co-op swings, near-impact strike beep ---- /// Master gate for the enemy material BODY hit-flash (drives URPMaterialPropertyBaseColor on render children). public static bool BodyFlashEnabled; /// Peak _BaseColor the enemy body flashes to on a hit (HDR; lerps from the baked white base and decays back). public static Color BodyFlashColor; /// C2: player-body HURT flash tint (hot red-orange) — distinct from the enemy white overdrive so /// 'you got hit' reads differently from 'you hit them'. Multiplies the Synty atlas base map like BodyFlashColor. public static Color PlayerHurtFlashColor; /// Seconds the body flash decays from peak back to the baked white base. public static float BodyFlashDurationSec; /// Master gate for rendering REMOTE teammates' melee cleave arcs (co-op readability). public static bool RemoteSwingEnabled; /// Tint of a remote teammate's slash arc (cooler/friendlier than the local warm arc). public static Color RemoteSlashColor; /// Master gate for the near-impact \"dodge NOW\" strike beep on a winding-up enemy. public static bool StrikeBeepEnabled; /// Volume of the near-impact strike beep. public static float StrikeBeepVolume; /// Ticks before the strike lands that the beep fires (the dodge-reaction lead). public static int StrikeBeepLeadTicks; /// Squared world-distance from the local player beyond which the strike beep is suppressed (avoids a distant cacophony). public static float StrikeBeepMaxDistSq; // ---- Enemy visibility markers (EnemyMarkerSystem: off-screen edge arrows + on-screen overhead pips) ---- /// Master gate for the enemy visibility markers. public static bool EnemyMarkerEnabled; /// Only mark living enemies within this world distance of the local player (scopes to the current room; avoids cross-region arrow spam). public static float EnemyMarkerRange; /// On-screen enemy count above which the overhead pips fade toward EnemyMarkerMinAlpha (declutter a swarm; off-screen arrows stay full). public static int EnemyMarkerFadeStartCount; /// Faded pip alpha once the on-screen count is well past EnemyMarkerFadeStartCount. public static float EnemyMarkerMinAlpha; /// Marker tint (pip + off-screen arrow). public static Color EnemyMarkerColor; /// Marker glyph font size (px). public static float EnemyMarkerSize; [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] public static void ResetDefaults() { // Feature 1 HitShakeLocal = 0.32f; HitShakeRemote = 0.10f; HitBurstCount = 10; HitSfxVolume = 0.70f; HitStopFovKick = 1.5f; HitStopDurationMs = 90f; HitStopFovKickMin = 0.6f; HitStopFovKickMax = 2.2f; HitStopMaxFrames = 2; // C4: a 2-frame (~33ms) camera hold reads as crunch, not a lag-y stutter HitStopRefDamage = 30f; HitFlashColor = new Color(1f, 0.85f, 0.55f, 1f); HitStopFreezeEnabled = true; // C4: enable the finisher hit-stop hold (presentation-only camera freeze, bounded below) // Feature 1/2 death PlayerDeathShake = 0.50f; RemotePlayerDeathShake = 0.25f; DeathBurstCount = 28; // Feature 2 kill-shot KillShake = 0.20f; KillBurstScale = 1.5f; KillFovKick = 1.0f; KillSfxVolume = 0.75f; // Feature 3 respawn RespawnShimmerEnabled = true; RespawnShimmerBurst = 24; RespawnShimmerShake = 0.12f; // Feature 4 tether LockOnEnabled = true; LockOnGamepadOnly = true; LockOnRange = 9.0f; LockOnArcDegrees = 60f; LockOnLineColor = new Color(0.55f, 0.9f, 1f, 0.35f); LockOnLineWidth = 0.05f; // Feature B health bars (Slice 1) HealthBarMaxDistSq = 400f; // 20 m radius // Feature 5 dash (MC-1) DashShake = 0.18f; DashFovKick = 1.2f; DashBurstCount = 14; DashSfxVolume = 0.55f; DashShimmerPerFrame = 2; DashHitSuppress = true; // Combat feel pass (2026-06) HitFlashBurstCount = 16; MeleeConnectFovKick = 0.8f; MeleeConnectVolume = 0.55f; KillFlashBurstCount = 20; FootstepVolume = 0.30f; // 07-15: heavier, more present underwater thud FootstepStrideMeters = 1.5f; // one step per ~1.5m of planar travel FootstepMinSpeed = 1.0f; FootstepJitter = 0.12f; FootstepPuffCount = 5; BubbleIntervalSec = 1.6f; // 07-16: dome bubble exhaust cadence BubbleBurstCount = 2; AmbienceVolume = 0.14f; // underwater bed (Music bus) GroanIntervalSec = 24f; // distant groans, ±40% jitter GroanVolume = 0.35f; CameraDragMult = 0.8f; // heavier camera through the water (multiplies FollowSharpness) BankFullTurnDegPerSec = 270f; TrudgeNaturalSpeed = 1.46f; // measured: A_Walk_FwdStrafeF_RootMotion_Masc averageSpeed (walk ring) RunNaturalSpeed = 2.6f; // measured: A_Run_FwdStrafeF_RootMotion_Masc averageSpeed (run ring) ShoulderLampIntensity = 8f; // 07-16e: the suit lamp actually lights (3 got lost in the murk ambient) ShoulderLampRange = 12f; CombatIdleHoldSec = 4f; // 07-18: Menacing01 combat-idle hold after the last swing MeleeArcIntensity = 1f; // 07-19: arc colors are pre-toned dim; this scales from there MeleeArcBubbles = 6; FinisherHoldFrames = 5; // 07-20 G5: the finisher's heavy beat (~83ms), capped well under the 8-frame ladder ceiling RumbleEnabled = true; RumbleHit = 0.25f; RumbleKill = 0.45f; RumbleDurationSec = 0.12f; // Deferred-items pass (2026-06) BodyFlashEnabled = true; BodyFlashColor = new Color(3.2f, 2.8f, 2.2f, 1f); // hot near-white overdrive (multiplies the Synty atlas base map) PlayerHurtFlashColor = new Color(2.6f, 0.55f, 0.4f, 1f); // C2: hot red-orange body flash when the player is hit BodyFlashDurationSec = 0.16f; RemoteSwingEnabled = true; RemoteSlashColor = new Color(0.7f, 1.15f, 1.3f, 1f); // cool teammate arc (07-19 retone: dim bioluminescent wake) StrikeBeepEnabled = true; StrikeBeepVolume = 0.40f; StrikeBeepLeadTicks = 8; StrikeBeepMaxDistSq = 225f; // 15 m // Enemy visibility markers (EnemyMarkerSystem) EnemyMarkerEnabled = true; EnemyMarkerRange = 140f; EnemyMarkerFadeStartCount = 6; EnemyMarkerMinAlpha = 0.28f; EnemyMarkerColor = new Color(1f, 0.86f, 0.3f, 1f); // warm amber, reads over the cool world EnemyMarkerSize = 24f; } } }