Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
7.4 KiB
title, date, links
| title | date | links |
|---|---|---|
| Facing & Underwater Feel — Build Spec (SoD facing + heavier movement + suit kit) | 2026-07-15 | DR-051_Lantern_Realignment_Purge, Roadmap_Lantern_Slice, Identity_Lantern |
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)
Simulation/Combat/TickWindowMath.cs(new):SwingActive/FireActive/SocketFireAndConemoved verbatim from PlayerAnimationDriveSystem (:118-162), window length stays a caller param. Add Movement-archetype skip (hoist the blob lookup,continueonAbilityArchetype.Movement) — used by BOTH the facing path and the anim path (kills the latent cooldown-tail phantomIsFiringfrom 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).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).- 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). NoIsFirstTimeFullyPredictingTickgate — 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. - DashSystem :67 + BlinkSystem :101: stationary fallback Move → Aim → facing (operator: keep cursor-dash). Update the :64-65 comment.
- AimReticleSystem: add
RefRO<PlayerInput>; gamepad ring + tether axis fromResolveAim(Aim, facing)(tether stays consistent with the server assist cone because the assist seed also moves to Aim). - 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.DefaultGroundedSharpnessconst ANDPlayer.prefabserializedGroundedMovementSharpness: 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.assetTurnRateDegreesPerSec(the asset — serialized wins) +CharacterStatsDefinition.cs:18initializer 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/DebugTuningReportfields (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 —PlayClipAtPointhas 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.assetCooldownTicks 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).