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;
}
}
}