Combat feel pass: enemy hit-flash, melee connect cue, kill pop, gamepad rumble, footsteps, damage-number tiering

Implements the "what's missing" feel backlog from the combat overhaul investigation (wf_c6c87dc5-9c3). All
client-only, observe-only (PresentationSystemGroup), no sim/netcode change, rollback-safe; new FeelConfig knobs
default-stamped on play-enter.

- ENEMY HIT-FLASH (#1 missing): on the enemy Health-decrease edge, a bright body-scaled particle puff in the
  previously-unused FeelConfig.HitFlashColor (via a new EmitColored helper using per-emit startColor) -- the staple
  "I connected" read. (A true material body-flash needs an AnimatedLitShader emission property + Entities Graphics
  MaterialProperty; the puff is the asset-free version.)
- MELEE CONNECT-vs-WHIFF: on the local swing, a client-side MeleeConeMath.InCone overlap over the cached enemy
  snapshot -> immediate connect read (brightens the slash arc, hit-spark at the nearest enemy, a meaty low "thunk"
  SFX, extra FOV punch) before the authoritative server spark arrives. Whiffs stay dim.
- KILL POP: a colored flash burst + rumble on enemy death (on top of the existing burst/shake/FOV).
- GAMEPAD RUMBLE: new RumbleUtil (auto-stopping pulse, gamepad-only, stops on focus-loss, reset on play-enter)
  pulsed on local hit-taken / hit-dealt / kill, gated on AimPresentation.Scheme==Gamepad.
- FOOTSTEPS: edge-detect local locomotion from the position delta -> soft step SFX at a cadence while moving.
- DAMAGE-NUMBER TIERING: SpawnNumber scales font + life by hit magnitude (vs HitStopRefDamage) so heavy hits read.

390/390 EditMode, clean compile + Play (no exceptions; per-frame footstep/rumble-tick paths verified). On-screen
feel is the operator's eyes. Deferred (focused follow-ups): true material body hit-flash (ShaderGraph), co-op
remote-swing rendering, near-impact telegraph beep (clip reserved).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-26 00:09:07 -07:00
parent ca38c2b16d
commit c3b53cef28
4 changed files with 164 additions and 3 deletions
@@ -109,6 +109,32 @@ namespace ProjectM.Client
/// Health flicker on a clean dodge — the documented acceptable-not-a-bug interaction).</summary>
public static bool DashHitSuppress;
// ---- Combat feel pass (2026-06): connect cue, hit-flash, kill pop, footsteps, rumble, telegraph beep ----
/// <summary>Hit-flash puff density (a colored particle burst in HitFlashColor on an enemy damage edge).</summary>
public static int HitFlashBurstCount;
/// <summary>Extra FOV punch (deg) when the LOCAL melee cleave is confirmed to connect (vs a whiff).</summary>
public static float MeleeConnectFovKick;
/// <summary>Volume of the meaty melee connect "thunk".</summary>
public static float MeleeConnectVolume;
/// <summary>Kill-pop colored flash density on an enemy death.</summary>
public static int KillFlashBurstCount;
/// <summary>Soft footstep SFX volume.</summary>
public static float FootstepVolume;
/// <summary>Seconds between footsteps while the local player is moving.</summary>
public static float FootstepIntervalSec;
/// <summary>Local player speed (u/s) above which footsteps play.</summary>
public static float FootstepMinSpeed;
/// <summary>Master gate for gamepad rumble (no-op on KBM).</summary>
public static bool RumbleEnabled;
/// <summary>Rumble strength on a local hit taken / dealt.</summary>
public static float RumbleHit;
/// <summary>Rumble strength on a kill.</summary>
public static float RumbleKill;
/// <summary>Rumble strength on dash / local death (the heaviest).</summary>
public static float RumbleHeavy;
/// <summary>Seconds a rumble pulse lasts before it auto-stops.</summary>
public static float RumbleDurationSec;
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
public static void ResetDefaults()
{
@@ -161,6 +187,20 @@ namespace ProjectM.Client
DashSfxVolume = 0.55f;
DashShimmerPerFrame = 2;
DashHitSuppress = true;
// Combat feel pass (2026-06)
HitFlashBurstCount = 16;
MeleeConnectFovKick = 0.8f;
MeleeConnectVolume = 0.55f;
KillFlashBurstCount = 20;
FootstepVolume = 0.16f;
FootstepIntervalSec = 0.32f;
FootstepMinSpeed = 1.5f;
RumbleEnabled = true;
RumbleHit = 0.25f;
RumbleKill = 0.45f;
RumbleHeavy = 0.6f;
RumbleDurationSec = 0.12f;
}
}
}