LANTERN feel pass (DR-052 + gap list): SoD facing, underwater feel, Bathynaut kit, walk/run gait, suit lamp + new Synty anim packs

- SoD facing: PlayerFacing = body-yaw only (move-facing / cast-turn / idle-hold); every fire
  direction re-sourced to FacingMath.ResolveAim (pre-code review blocking catch); TickWindowMath
  shared windows with Movement-skip; reticle/FX coupled to the damage direction; cursor-dash kept.
- Underwater feel: sharpness 15->6, turn 720->360, MoveSpeed 6->4.2; TuningKnob 26-28
  (0 = no-override sentinels, dev-protocol bump on DebugTuningReport); stride footsteps + silt +
  cadence floor; bubbles; underwater ambience bed + distant groans; camera drag + dev scroll zoom.
- Bathynaut kit in-engine: dome/tank/shoulder-lamp + bare head grafted (GraftSmr rigid rebase,
  RecalculateTangents); EmissiveGloamSkinned shader (Rukhanka deformation); shoulder lamp CASTS
  (warm steady spot on body yaw).
- Gait: two-ring walk/run FreeformDirectional tree (walk @0.35, run @1.0) + blended-natural
  StrideScale; additive Posture(Bank) + Lead(chest-lead) layers; idle = AnimationIdles Base;
  banking driven from facing turn rate; flat terrain (Env_SeabedKit seabed squashed - CC is planar).
- New packs: Synty AnimationIdles + AnimationSwordCombat (combat pass queued) + SyntyPropBoneTool;
  four authored clips (sway/trudge/banks/lean) + Anim_Player_Underwater.blend + suit-kit FBX.
- Validation: 411/411 EditMode green; Play smokes (server==client facing, Aim-true projectile,
  bank/stride live-sampled, lamp beam verified); pre-code + post-impl adversarial reviews applied.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-16 13:56:02 -07:00
parent 385d0de08e
commit 7571091394
2637 changed files with 1810753 additions and 303 deletions
@@ -84,6 +84,36 @@ namespace ProjectM.Tests
Assert.AreEqual(0f, c.ChargerLungeSpeed, 1e-6f, "ChargerLungeSpeed floors at 0");
}
[Test]
public void FeelOverride_Knobs_Default_To_Zero_And_Allow_The_NoOverride_Sentinel()
{
// 07-15 facing/underwater feel: TurnRateDeg/CastTurnRateDeg/MoveSharpness use 0 = NO-OVERRIDE
// (consumers substitute the authored stat / cast const / sharpness const). They must default to 0,
// clamp negatives to 0, and round-trip 0 through Apply (the default >=1 tick-branch would silently
// floor them to 1 and force a permanent 1-unit override).
var c = TuningConfig.Defaults();
Assert.AreEqual(0f, c.TurnRateDeg, 1e-6f);
Assert.AreEqual(0f, c.CastTurnRateDeg, 1e-6f);
Assert.AreEqual(0f, c.MoveSharpness, 1e-6f);
TuningConfig.Apply(ref c, TuningKnob.TurnRateDeg, -5f);
Assert.AreEqual(0f, c.TurnRateDeg, 1e-6f, "negatives clamp to the sentinel, not 1");
TuningConfig.Apply(ref c, TuningKnob.CastTurnRateDeg, 720f);
Assert.AreEqual(720f, c.CastTurnRateDeg, 1e-6f);
TuningConfig.Apply(ref c, TuningKnob.CastTurnRateDeg, 0f);
Assert.AreEqual(0f, c.CastTurnRateDeg, 1e-6f, "0 must survive Apply (back to no-override)");
TuningConfig.Apply(ref c, TuningKnob.MoveSharpness, 9f);
Assert.AreEqual(9f, TuningConfig.Get(c, TuningKnob.MoveSharpness), 1e-6f);
// The new fields ride the report like every other knob (a missed FromReport field would zero them).
c.TurnRateDeg = 360f; c.CastTurnRateDeg = 1080f; c.MoveSharpness = 6f;
var r = TuningConfig.FromReport(TuningConfig.ToReport(c));
Assert.AreEqual(360f, r.TurnRateDeg, 1e-6f);
Assert.AreEqual(1080f, r.CastTurnRateDeg, 1e-6f);
Assert.AreEqual(6f, r.MoveSharpness, 1e-6f);
}
[Test]
public void Apply_Ignores_An_Out_Of_Range_Knob_Index()
{