Tuning Knobs

This commit is contained in:
2026-06-10 15:22:30 -07:00
parent da522efe7a
commit 08f16b689f
20 changed files with 11045 additions and 18 deletions
@@ -29,12 +29,9 @@ namespace ProjectM.Simulation
[BurstCompile]
public partial struct DashSystem : ISystem
{
// Baked-first feel knobs (MC-1; promote to a live TuningConfig later). Sim runs at 60 ticks/sec.
const float DashDistance = 4.0f; // world units covered during the i-frame window
const uint IFrameWindowTicks = 12; // ~0.20 s of i-frames
const uint RecoverTailTicks = 9; // ~0.15 s movement-locked tail (punishes spam)
const uint DashCooldownTicks = 45; // ~0.75 s
const float DashSharpness = 200f; // GroundedMovementSharpness during the dash -> blink
// Feel knobs are LIVE-tunable via the TuningConfig singleton (MC-0): OnUpdate reads it each tick and falls
// back to TuningConfig.Defaults() when absent (release builds / EditMode), so behaviour is identical to the
// old baked consts. DefaultSharpness (the restore target) + SimTickRate stay compile-time (not tuned).
const float DefaultSharpness = 15f; // CharacterComponent.GetDefault() base
const float SimTickRate = 60f;
@@ -45,7 +42,12 @@ namespace ProjectM.Simulation
return;
var serverTick = netTime.ServerTick;
uint now = serverTick.TickIndexForValidTick;
float dashSpeed = DashDistance / (IFrameWindowTicks / SimTickRate);
var t = SystemAPI.TryGetSingleton<TuningConfig>(out var tc) ? tc : TuningConfig.Defaults();
uint iFrameTicks = (uint)math.max(1f, t.IFrameWindowTicks);
uint recoverTicks = (uint)math.max(1f, t.RecoverTailTicks);
uint cooldownTicks = (uint)math.max(1f, t.DashCooldownTicks);
float dashSpeed = t.DashDistance / (iFrameTicks / SimTickRate); // iFrameTicks>=1 -> never div-by-0 (review F1)
float dashSharpness = t.DashSharpness;
foreach (var (ds, cd, control, character, input, facing) in
SystemAPI.Query<RefRW<DashState>, RefRW<DashCooldown>, RefRW<CharacterControl>,
@@ -64,9 +66,9 @@ namespace ProjectM.Simulation
dir = math.normalize(dir);
ds.ValueRW.Dir = dir;
ds.ValueRW.StartTick = TickUtil.NonZero(now);
ds.ValueRW.IFrameUntilTick = TickUtil.NonZero(now + IFrameWindowTicks);
ds.ValueRW.RecoverUntilTick = TickUtil.NonZero(now + IFrameWindowTicks + RecoverTailTicks);
cd.ValueRW.NextTick = TickUtil.NonZero(now + DashCooldownTicks);
ds.ValueRW.IFrameUntilTick = TickUtil.NonZero(now + iFrameTicks);
ds.ValueRW.RecoverUntilTick = TickUtil.NonZero(now + iFrameTicks + recoverTicks);
cd.ValueRW.NextTick = TickUtil.NonZero(now + cooldownTicks);
}
// --- OVERRIDE (runs every predicted pass so rollback re-simulation re-applies it) ---
@@ -84,7 +86,7 @@ namespace ProjectM.Simulation
{
float2 d = ds.ValueRO.Dir;
control.ValueRW.MoveVelocity = new float3(d.x, 0f, d.y) * dashSpeed;
character.ValueRW.GroundedMovementSharpness = DashSharpness;
character.ValueRW.GroundedMovementSharpness = dashSharpness;
}
else if (recoverActive)
{