Files
Project-M/Assets/_Project/Scripts/Simulation/Player/MeleeCombo.cs
T
kronic 3409c53148 Combat: MC-4 combo-chain melee as the primary verb (DR-030)
Melee combo (left-click / pad-West) becomes the player's primary verb; the ranged projectile is demoted to right-click / pad-left-trigger. Predicted, owner-replicated combo Step (path-dependent -> [GhostField] anchor + absolute-write idempotency, NOT derived like the dash), server-only cleave mirroring ProjectileDamageSystem (SourceTick-stamped DamageEvent + KnockbackState), dash-cancellable movement-commit, 9 live TuningConfig knobs, and swing juice scaling with the combo step. The MC-6 archetype byte is deferred (the melee is its own verb). See DR-030.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 17:22:57 -07:00

33 lines
2.1 KiB
C#

using Unity.Entities;
using Unity.NetCode;
namespace ProjectM.Simulation
{
/// <summary>
/// MC-4 — predicted, owner-replicated melee combo state on the player (the PRIMARY offense verb). UNLIKE the dash
/// (DashState is non-replicated because a dash is a STATELESS-per-tick decision), the combo <see cref="Step"/> is
/// PATH-DEPENDENT — which chain link you are on depends on the sequence + timing of prior presses — and the bounded
/// input command buffer cannot reconstruct it across a reconnect / long rollback (MC-4 review PRED-1 / ROLLBACK-1).
/// So the minimal anchor is replicated as owner-predicted <c>[GhostField]</c>s: a rollback restores the
/// authoritative combo position, then MeleeComboSystem re-simulates forward with ABSOLUTE-WRITE windows (never an
/// in-place prev+1 of a non-restored field — the DashSystem idempotency idiom). The derived chain deadline
/// (LockUntilTick + grace) is NOT stored. All ticks routed through <c>TickUtil.NonZero</c>; compared via
/// <see cref="NetworkTick"/> only. Baked all-zero (idle). This is the player ghost's only net-new replicated melee
/// state (one re-bake; in-family with DashCooldown/AbilityCooldown).
/// </summary>
public struct MeleeCombo : IComponentData
{
/// <summary>Current/last swing index: 0 = idle, 1..N = chain link. Owner-predicted so a rollback restores the
/// authoritative combo position (the only legitimately path-dependent value).</summary>
[GhostField] public byte Step;
/// <summary>Raw ServerTick the current swing started (NonZero). Inclusive lower bound of the movement-commit
/// window; also the juice edge signal and the (instant) damage frame.</summary>
[GhostField] public uint SwingStartTick;
/// <summary>Raw ServerTick the swing's movement-commit / recovery lock ends (NonZero). Gates the next press
/// (locked while now &lt; this) and anchors the chain window [LockUntilTick, LockUntilTick + grace).</summary>
[GhostField] public uint LockUntilTick;
}
}