Files
Project-M/Docs/Vault/06_Roadmap/Facing_Underwater_Feel_Build_Spec.md
T

7.5 KiB
Raw Blame History

title, date, links, permalink
title date links permalink
Facing & Underwater Feel — Build Spec (SoD facing + heavier movement + suit kit) 2026-07-15 DR-051_Lantern_Realignment_Purge, Roadmap_Lantern_Slice, Identity_Lantern gamevault/06-roadmap/facing-underwater-feel-build-spec

Facing & Underwater Feel — Build Spec

Pre-code design review run wf_a13d4395-1c7 (3 lenses, 25 findings → 21 confirmed / 3 refuted / 1 critic died on session-limit — its ordering claim was independently refuted by a sibling critic with full code evidence). This spec = the approved plan as corrected by the review. Operator forks locked 07-15: facing model (a) move-facing + cast-turn; suit = dome+tank+shoulder-lamp (no chest beacon); keep cursor-dash (Move→Aim→facing fallback); dome + bare head (sci-fi helmet removed).

The review's blocking correction ★

Fire directions do NOT read PlayerInput.Aim today — they read PlayerFacing (AbilityFireSystem cFace :174 / aFace :209 / hFace :242 / rawAim :279 + auto-target seed :286; MeleeComboSystem face :163). Under move-facing every skillshot/cleave would fire along the travel direction. Fix (wire-neutral, both queries already carry PlayerInput): re-source all five sites to FacingMath.ResolveAim(input.Aim, facing.Direction) = normalize(Aim) if lengthsq>1e-6 else facing else (0,1). PlayerFacing becomes body-yaw/presentation only. Windup path keeps current-tick Aim (existing contract).

Part B — facing rework (Simulation)

  1. Simulation/Combat/TickWindowMath.cs (new): SwingActive/FireActive/SocketFireAndCone moved verbatim from PlayerAnimationDriveSystem (:118-162), window length stays a caller param. Add Movement-archetype skip (hoist the blob lookup, continue on AbilityArchetype.Movement) — used by BOTH the facing path and the anim path (kills the latent cooldown-tail phantom IsFiring from the blink socket; anim behavior change is a bug fix). Blink socket must never count as a cast (review: blink stamps its cooldown row → facing would yank toward cursor at the cooldown tail).
  2. Simulation/FacingMath.cs (new): ResolveAim (above) + SelectTarget + RotateToward (verbatim extraction of PlayerAimSystem :39-59). One shared Aim→Move→hold cascade in BOTH branches — castActive selects turn RATE + Aim-priority, never bypasses the cascade (gamepad Aim is zeroed at rest → a resting-stick cast must fall through to Move).
  3. PlayerAimSystem: target = castActive(melee swing window non-Movement socket windows) ? cascade-with-Aim-first : (Move | hold). Locomotion rate = EffectiveCharacterStats.TurnRateRadiansPerSec (knob-overridable), cast rate = const 1080°/s (knob-overridable). No IsFirstTimeFullyPredictingTick gate — the rate-limited turn is an incremental integrator over the snapshot-restored [GhostField]; it must re-integrate on EVERY predicted pass (document in header, DashSystem wording). Add [UpdateAfter(typeof(MeleeComboSystem))] — optional hygiene pin (cycle-free, verified against the full predicted-group edge set; sorter tie-break is deterministic and cross-world-identical, so this pins same-tick cast-window semantics, not a divergence fix). Socket windows open 1 tick late by construction (AbilityFire stamps after PlayerAim) — acceptable, cosmetic-only once fire directions are decoupled.
  4. DashSystem :67 + BlinkSystem :101: stationary fallback Move → Aim → facing (operator: keep cursor-dash). Update the :64-65 comment.
  5. AimReticleSystem: add RefRO<PlayerInput>; gamepad ring + tether axis from ResolveAim(Aim, facing) (tether stays consistent with the server assist cone because the assist seed also moves to Aim).
  6. CombatFeedbackSystem: LOCAL socket cue (:330-335) + melee arc (:409-413) direction from FacingMath.ResolveAim(Aim, facing) — the SAME resolver as the sim fire sites, so cue == damage direction in every case (amended post-impl: the draft's literal Aim→Move→facing could diverge from damage on a moving resting-stick gamepad cast); remote arcs stay facing-based (Aim not replicated to non-owners; asymmetry documented at UpdateRemoteSwings).

Part C — heavier movement (all coupled sites together ★)

  • Sharpness 15→6: CharacterComponent.DefaultGroundedSharpness const AND Player.prefab serialized GroundedMovementSharpness: 15→6 AND DashSystemTests literal 15f assertions (:94/:113/:207/:230) → const reference. (DashSystem's non-dash branch restores to the const EVERY tick — a prefab-only edit is stomped within one tick.)
  • Turn rate 720→360: Character_Default.asset TurnRateDegreesPerSec (the asset — serialized wins) + CharacterStatsDefinition.cs:18 initializer hygiene. Re-bake accepted (DependsOn; no ghost-hash change).
  • Knobs (full checklist in one pass): TuningKnob.TurnRateDeg=26, CastTurnRateDeg=27, MoveSharpness=28, Count=29 (retired 20-23 untouched). Semantics: 0 = no override (use stat/const/authored). Extend Defaults(0)/explicit ClampKnob case (max(0f,v) — must NOT fall into the default ≥1 branch)/Apply/Get/ToReport/FromReport/DebugTuningReport fields (unconditional, no #if)/TuningConfigTests pin/DebugOverlay rows. Consumption: PlayerAimSystem rates; sharpness override at the restore sites (DashSystem :97/:101, BlinkSystem :127, PlayerDeathStateSystem :51) so the knob is live within a tick. Dev-protocol bump: DebugTuningReport layout changes → a stale standalone dev build refuses the handshake vs a fresh peer; expected, rebuild both.

Part D — footsteps + gait (client-only)

  • Stride-distance footsteps (accumulate planar distance, step every FootstepStrideMeters≈1.5, + a 0.18s cadence floor so a dash can't machine-gun thuds — post-impl review), heavier clip (~100Hz, longer decay, noise), jitter (shipped as 3 fixed pitch-variant clips 95/108/120Hz + ±12% volume jitter — PlayClipAtPoint has no pitch control), silt-puff particle per step. New FeelConfig fields.
  • Locomotion blend state speed ~0.85 + idle↔locomotion transitions ~0.25s via AnimatorController API (PlayerRigTools).

Part A — suit kit (art)

Blender export was missing the armature (FBX had no skin — cause under diagnosis; re-export with verified selection). Kit = SM_Suit_KitBrass (940 tris, Head/Spine_03/Clavicle_L rigid-skinned) + SM_Suit_KitGlow (104 tris) → FBX → AttachBathynautKit re-binds SMRs by bone name onto Player.prefab. Brass = M_Skinned_Palette; glow = new ProjectM/EmissiveGloamSkinned (hand-written HLSL + Rukhanka ComputeDeformedVertex, DOTS-instanced _DeformedMeshIndex) on M_EmissiveGloam_WarmSkinned (steady warm). Plus: remove SM_Chr_Attach_SpaceSoldier_Male_Helmet_01, graft the bare head (Bathynaut material).

Tests / validation

FacingMathTests (cascade incl. resting-stick cast; RotateToward step/snap), TickWindowMathTests (windows + Movement skip + wrap), DashSystemTests → const, SystemOrderingCycleTests + PlayerAimSystem (+PlayerControl/Blink) in the fixture, TuningConfigTests knob pins. L1 console clean · L2 suite green + Play smoke (server==client PlayerFacing under injected input) · L3 suit + steps screenshots. Post-impl diff review (same lenses).

Deferred (surfaced, not silent)

  • Ability_Blink.asset CooldownTicks 1 vs BlinkSystem's 150 stamp (HUD bar + window-math hygiene) — align later.
  • Skinned EG shader on non-DOTS/classic path renders undeformed (BRG-only by design, like all EG-skinned materials).
  • Bathynaut body still over hero tri budget (game-ready decimate pass parked since 07-14; kit adds ~1044).