using Unity.Mathematics; namespace ProjectM.Simulation { /// 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); 0 = IMMEDIATE (legacy same-tick resolve; the mechanics tests pin 0). /// COUPLING (documented, not clamped): keep contact(step3) < CastFacingTicks (26) and < 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). public static class MeleeTiming { /// 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). 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)); } } }