Melee feel overhaul: SwordCombat anims at heavy cadence, salvage axe, damage-at-contact + buffer + dash law (guidelines forks 1-6)

07-18/19/20 arc: LightCombo01/HeavyCombo01 clips onto Swing1-3/Slam at
~natural speed (26-tick window, 30-tick recover, DPS-held damage retune),
Menacing01 combat idle (InCombat), SM_Wep_Axe_Large_01 rigid-skinned to
Hand_R at x1.25, LANTERN FX retone + blade-smear ribbon. Forks 1-6 per
Combat_Attack_Feel_Guidelines (design review wf_000bc247): cleave resolves
at the CONTACT tick (MeleeCleavePending schedule-and-consume, knob 31,
0=legacy; connect cues moved to contact), MeleeRange 2.2 + reach-only
finisher mult 1.25 (knob 30), BufferedAttackTick GhostField input buffer
(knob 29, unlock-edge validity), dash refused pre-contact (lookup-based).
Tests: MeleeComboTests pin legacy knobs; +3 fork tests (414 green); live
server proof: HP drop exactly at swing+16 under tick batching.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-20 21:03:18 -07:00
parent 2c187a17a9
commit 7b65912f30
22 changed files with 1327 additions and 112 deletions
@@ -0,0 +1,28 @@
using Unity.Mathematics;
namespace ProjectM.Simulation
{
/// <summary>07-20 melee feel forks (guidelines G2.1): the per-step CONTACT tick — how many ticks after
/// SwingStartTick the blade actually lands. The server cleave resolves THEN (not at swing start), so the
/// damage cone is a true record of the visible hit. Fractions derive from the AC_PlayerTopDown clip contact
/// seconds at the wired state speeds (PlayerRigTools swaps table: A hit@0.267s, B hit@0.167s, C hit@0.333s
/// @60Hz): step2 = x0.625 (10t at the default), step3+ (finisher) = x1.25 (20t). Knob = TuningConfig
/// .MeleeContactTicks (default 16); <b>0 = IMMEDIATE</b> (legacy same-tick resolve; the mechanics tests pin 0).
/// COUPLING (documented, not clamped): keep contact(step3) &lt; CastFacingTicks (26) and &lt; MeleeRecoverTicks —
/// the cast-turn/anim window and chain cadence assume the hit lands inside both. The cleave itself is safe at
/// ANY knob value (MeleeComboSystem flushes a still-armed pending cleave before overwriting the swing).</summary>
public static class MeleeTiming
{
/// <summary>Server tick-batching slack: the buffered-attack validity window past the unlock edge must
/// cover Netcode's default MaxSimulationStepBatchSize (4) or an edge press drops under load (design
/// review wf_000bc247 C1).</summary>
public const uint BatchSlackTicks = 4;
public static uint ContactTicks(byte step, float contactKnob)
{
if (contactKnob <= 0f) return 0u; // 0 = immediate (legacy same-tick resolve)
float f = step == 2 ? 0.625f : step >= 3 ? 1.25f : 1f;
return (uint)math.max(0f, math.round(contactKnob * f));
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 4354c9c60079e0a44a0985ef7065d574