Docs: DR-052 SoD facing + underwater feel — build spec, session log, gotchas archive 07-16, skinned-kit cookbook recipe, CLAUDE.md facing contract (net-zero condensations)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -267,3 +267,27 @@ The direction that landed (operator-approved): **the interest lives in the LIGHT
|
||||
### Posed STATIC hero bake (style-proof placement without the Rukhanka pipeline)
|
||||
To drop a posed suit/creature into the staging scene as a static mesh (skinned Rukhanka bake is separate `/dots-dev` work): in Blender — **apply the `Armature` modifier** on each skinned mesh (bakes the current pose into geometry), **`visual_transform_apply` + clear constraints** on bone-parented kit (eyes/lamp Child-Of), then export a static glb (meshes only, no armature). ⚠ both `modifier_apply` and `export_scene.gltf` need the **window `temp_override`** after `open_mainfile` (context.active_object). Import via `import_model_file`, `InstantiatePrefab` at the slot — glTFast carries the embedded materials (Synty atlas + emission).
|
||||
```
|
||||
|
||||
## 12. Skinned attachment kit → existing Unity rig (proven 07-16, Bathynaut dome/tank/lamp)
|
||||
|
||||
Rigid accessories that must RIDE an already-in-engine Rukhanka rig (helmet, packs, lamps). Full failure-chain + Unity-side detail: gotchas archive 2026-07-16 + [[DR-052_SoD_Facing_Underwater_Feel]].
|
||||
|
||||
```python
|
||||
# 1. BIND (non-destructive, saved into the master): per piece — one vgroup named for the target
|
||||
# bone, ALL verts weight 1.0, + an Armature modifier. Pieces stay editable.
|
||||
o.vertex_groups.new(name="Head").add(range(len(o.data.vertices)), 1.0, 'REPLACE')
|
||||
o.modifiers.new("Armature", 'ARMATURE').object = ARM
|
||||
# 2. JOIN copies per SHADER ROLE (palette brass vs emissive glow) -> 2 export meshes, vgroups merge by name.
|
||||
# 3. EXPORT — ★ UNHIDE THE ARMATURE FIRST: a hidden armature can't be selected and the FBX
|
||||
# exports SILENTLY SKINLESS (static meshes, no vgroups). Restore hidden after.
|
||||
ARM.hide_set(False)
|
||||
bpy.ops.export_scene.fbx(filepath=out, use_selection=True, object_types={'ARMATURE','MESH'},
|
||||
apply_scale_options='FBX_SCALE_UNITS', apply_unit_scale=True, add_leaf_bones=False, bake_anim=False)
|
||||
```
|
||||
|
||||
Unity side (`PlayerRigTools.AttachBathynautKit` / `GraftSmr` is the reference implementation):
|
||||
- Rebind `smr.bones` by NAME onto the target skeleton (Blender dedup suffixes `.001` → strip to base name; safe when the kit only weights unambiguous bones).
|
||||
- **REBASE, never reuse bindposes**: a Blender FBX roundtrip imports cm bones under a 0.01 armature (regardless of scale option) while Synty-native rigs are meter-scale → raw bindpose reuse renders ×100 off. Bake verts to rest-world; bindposes = `Matrix4x4.TRS(m.GetColumn(3), m.rotation, Vector3.one).inverse` (RIGID, scale-stripped).
|
||||
- **`mesh.RecalculateTangents()` is mandatory** — a tangent-less procedural skinned mesh fails Rukhanka/BRG registration (`BatchMeshID not present`) and the WHOLE rig disappears.
|
||||
- Persist rebased meshes as `Rebased_*.asset` (Clear+refill an existing asset = GUID-stable re-runs).
|
||||
- Emissive pieces: `ProjectM/EmissiveGloamSkinned` (hand-written HLSL + Rukhanka `ComputeDeformedVertex`; the DOTS-instanced `_DeformedMeshIndex` block must be declared BEFORE the include, and the property must ALSO be in the Properties block for the baker's `HasProperty` validation). BRG-only: invisible in plain classic scenes, correct in the baked ECS world.
|
||||
|
||||
@@ -9,7 +9,7 @@ Multiplayer game on **Unity DOTS (Entities) + Netcode for Entities** — server-
|
||||
- **Size check** — bash: `wc -c CLAUDE.md` · PowerShell: `(Get-Item CLAUDE.md).Length`. Must be `< 40960`.
|
||||
- **Archive, don't delete.** When trimming, append the verbose / least-hot detail to the obsidian reference note `Docs/Vault/_Meta/CLAUDE_Build_Gotchas_Archive.md` under a **new dated heading** (never overwrite an older snapshot), and leave a one-line pointer + the relevant `[[DR-###]]` link here.
|
||||
- **Net-zero rule:** every addition is paid for by a condensation elsewhere. Keep only the hottest, highest-recurrence operational rules inline (flag them **★**); depth lives in the archive + DRs.
|
||||
- Condensation history (detail → archive dated headings): 06-17 · 07-04 · 07-04b · 07-04c · 07-06 · 07-07b (pays the attribute-swallow gotcha) · 07-07c (game-state narrative → invariants+DR pointers; ~3 KB freed, operator-ordered) · 07-13 (co-op-Hades core-loop bullet → LANTERN direction pointer; [[DR-048_Lantern_Adoption_Full_Pivot]]) · 07-15 (siege-era/automation/GoalProgress bullets retired with the DR-051 purge).
|
||||
- Condensation history → archive dated headings: 06-17 · 07-04(a–c) · 07-06 · 07-07(b,c) · 07-13 · 07-15 · 07-16 (what each paid is noted in the archive).
|
||||
|
||||
## Stack — Unity 6.5.1 (`6000.5.1f1`, stable) as of 2026-06-27
|
||||
|
||||
@@ -26,7 +26,7 @@ Multiplayer game on **Unity DOTS (Entities) + Netcode for Entities** — server-
|
||||
| `com.unity.mathematics` | 1.4.0 | (transitive) |
|
||||
| `com.rukhanka.animation` | **2.9.0** | Local pkg (`Packages/com.rukhanka.animation`). ECS skeletal animation (Burst CPU/GPU skinning). Resolves on 6.5.0 via SemVer floor. Netcode replication **OFF** → client-derived. See [[DR-022_Animation_Pipeline_Rukhanka_Synty]]. |
|
||||
|
||||
Values match `packages-lock.json` (reconciled 2026-06-17; URP 17.5.0, test-framework 1.7.0, ugui 2.5.0, multiplayer.center 1.0.1; `com.unity.ai.assistant` REMOVED 07-04 — console noise). **History:** 6.6.0a6 transport bug — [[DR-002_Unity66_Alpha_Netcode_Transport]] + archive.
|
||||
Values match `packages-lock.json` (reconciled 06-17; URP 17.5.0; `ai.assistant` removed 07-04). **History:** 6.6.0a6 transport bug — [[DR-002_Unity66_Alpha_Netcode_Transport]] + archive.
|
||||
|
||||
## Namespaces & assembly split
|
||||
|
||||
@@ -94,7 +94,7 @@ Long-form originals + the milestone each came from: `Docs/Vault/_Meta/CLAUDE_Bui
|
||||
|
||||
### Presentation / juice / VFX
|
||||
- **All juice/HUD = client-only observe-only `SystemBase` in `PresentationSystemGroup`** (once/frame, no rollback double-fire), never mutates the sim. Read ECS via `SystemAPI.Query` + `EntityManager.CompleteDependencyBeforeRO<T>()` — NOT MonoBehaviour `LateUpdate` (job-safety throw). `Entity` = a stable client dict key per ghost lifetime — **prune the cache each frame** (a pruned ghost = a kill/loss → death VFX); **never `DestroyEntity` a ghost client-side** (`GhostDespawnSystem` owns despawn). Hit-stop = camera punch, **never `Time.timeScale`**.
|
||||
- **Asset-free presentation:** procedural `AudioClip.Create` SFX; runtime `ParticleSystem` pool (Sprites/Default + HDR start color); code-built **UI Toolkit** HUD/menus. Edit a prefab asset's component in code via `PrefabUtility.LoadPrefabContents` → modify → **`SaveAsPrefabAsset(root, path)`** → `UnloadPrefabContents`. Watch **shared-material bleed** when re-tinting. ACES tonemapping needs URP color grading mode = HDR (`m_ColorGradingMode=1`).
|
||||
- **Asset-free presentation:** procedural `AudioClip.Create` SFX; runtime `ParticleSystem` pool; code-built **UI Toolkit**. Prefab-asset edits: `LoadPrefabContents`→modify→`SaveAsPrefabAsset`→`Unload`. Watch shared-material bleed on re-tint; ACES needs URP grading mode HDR. Detail → archive 07-16.
|
||||
- **Prototype glue lives in `ProjectM.Client` as MonoBehaviours:** `PrototypeCameraRig` (player-following ARPG cam), `VFXConfig` (static `Instance` + prefab fields bridging authored VFX to `CombatFeedbackSystem`; keep a procedural fallback). A **static presentation bridge must reset on play-enter** via `[RuntimeInitializeOnLoadMethod(SubsystemRegistration)]` (statics survive fast-enter-playmode reloads → stale flash).
|
||||
- **UITK HUD + menus ★:** `MenuUi` owns the palette/factories/`PanelSettings`/`EventSystem` plumbing; `HudSystem` = a `PresentationSystemGroup` observe-only `SystemBase` owning a runtime `UIDocument` (`sortingOrder 50`, root `pickingMode = Ignore`, tree built once `rootVisualElement != null`). **Runtime UITK needs `PanelSettings` WITH a `themeStyleSheet` AND an `EventSystem` + `InputSystemUIInputModule`** or buttons are silently dead. The build palette (lazy from the client `StructureCatalog`) drives click-to-place: green/red `BuildPreviewMath` ghost → `BuildPlaceRequest` RPC, right-click/Esc cancel, `[`/`]`/R rotate. See [[DR-021_HUD_UITK_BuildPalette]].
|
||||
- **HUD skin = build-safe `HudTheme` SO of serialized sprite refs** (runtime `Resources.Load` by name is build-stripped); tint MULTIPLIES, never set `unitySlice*` on 9-slices → archive 2026-07-06 + [[DR-024_HUD_Synty_Skin_Theme]].
|
||||
@@ -102,15 +102,15 @@ Long-form originals + the milestone each came from: `Docs/Vault/_Meta/CLAUDE_Bui
|
||||
### Art import (HDRP store packs → URP)
|
||||
- BefourStudios HDRP art: convert via `EnvArtTools.cs`, never switch pipelines (HDRP breaks EG); Synty = URP-native → archive 2026-07-06.
|
||||
- **World = the LANTERN murk ★ (DR-051; Synty biomes deleted):** ONE look — `PostFX_Lantern.asset` (ACES; needs URP HDR grading) + `Env_SeabedKit.prefab` (ArtStaging-sourced) + unified RenderSettings (NO skybox; Exp² teal fog {0.02,0.10,0.12}; flat ambient {0.03,0.055,0.08}; density knob 0.035 play / 0.075 staging; camera clearFlags **SolidColor deep-water** — else no-skybox corners bleed blue). `ScenePolicy.IsGameplayScene()` gates the dynamic-look systems — never re-add `scene.name` string checks. `WorldAtmosphereSystem` = water-column murk.
|
||||
- **A dark-lit screenshot MASKS material bugs — verify material *values*.** `shader.GetPropertyType(idx)`-guard before `GetColor`/`GetFloat`/`GetTexture` (`S_General`'s `_BaseColorMultiply` is a float → `GetColor` returns black). Gate emission on the `_Emissive` flag + a fixture name; keep converted env metallic low (0.1–0.2).
|
||||
- **An EG per-instance tint (`URPMaterialPropertyBaseColor`) darkens a ghost ONLY if the shader's `_BaseColor` is Hybrid-Per-Instance** (ShaderGraph `overrideHLSLDeclaration:true`+`hlslDeclarationOverride:2`). Enemies flash because `AnimatedLitShader` is DOTS-authored; a **stock Synty prop graph (`Generic_Basic`) is Unity-Per-Material → the override RENDERS but silently no-ops.** Check the graph before planning a per-instance tint on a baked/prop mesh; else use procedural decal quads (07-12 cover damage-cracks, Part P).
|
||||
- **A dark-lit screenshot MASKS material bugs — verify material *values*** (`GetPropertyType`-guard before `GetColor`/`GetFloat`; detail → archive 07-16).
|
||||
- **EG per-instance tint (`URPMaterialPropertyBaseColor`) works ONLY on a Hybrid-Per-Instance `_BaseColor` graph** (AnimatedLitShader yes; stock Synty `Generic_Basic` = Unity-Per-Material → renders but silently no-ops) — check the graph first; else procedural decals. Detail → archive 07-16.
|
||||
- **VolumeProfile.Add persistence + the URP `m_AssetVersion` build blocker** → archive 2026-07-06 (+ native memory `urp-global-settings-version-blocks-build`).
|
||||
- **`LocalTransform.FromPosition()` resets Scale=1** — server spawners read the prefab's baked `LocalTransform`, override only Position (Scale is a `[GhostField]` → consistent-but-wrong).
|
||||
- **Static decor → gameplay subscene** (EG renders only baked entities); **strip colliders from cosmetic props** + no `GhostAuthoring` on scenery (classic-URP colliders are inert to the DOTS PhysicsWorld). **World collision = subscene-only ★:** `Environment`-layer boundary ring + landmark colliders (player blocked via the layer matrix); enemies slide via a server `CollisionWorld.SphereCast` in `EnemyAISystem`. **★ enemy slide has NO pathfinding — a near-vertical wall normal or an embedded spawn FROZE Husks on cover rocks (soft-locks a room on one leftover); fixed 07-07 via `EnemyMoveUtil.Depenetrate` + tangent-slide + an `EnemyNavState` nudge backstop. Re-validate movers aren't frozen when adding Environment cover. 07-10: the nudge is COVER-AWARE — a live destructible-cover ghost (`BlightClutter` carrier) SUPPRESSES the phase-through (breakable ⇒ no soft-lock); static wedges still nudge.** Boundary = `SM_Env_Rock_Cliff` rim. See [[2026-06-08_World_Collision_HUD_Scaling]].
|
||||
- **A GA "projectile" prefab self-propels** — strip to particles before `Start` (`CombatFeedbackSystem.StripCosmetic`); verify *components*, not the name.
|
||||
|
||||
### Aim controls
|
||||
- Client-derived aim on `PlayerInput.Aim`; scheme byte KBM=0/Gamepad=1; reticle re-raycasts inside `AimReticleSystem` → full detail: archive 2026-07-06 section + the source files.
|
||||
### Aim / facing (SoD model — DR-052) ★
|
||||
- **`PlayerFacing` is body-yaw ONLY** (moves→face movement; cast window→turn to aim; idle→hold; never passively track the cursor). **Every gameplay direction reads `FacingMath.ResolveAim(PlayerInput.Aim, facing)`** (all AbilityFireSystem archetypes + assist seed + melee cleave) and aim-readout presentation reads the SAME resolver; Movement-archetype sockets never open a cast window (`TickWindowMath`); PlayerAimSystem stays UN-gated (integrator over the snapshot-restored [GhostField]). Scheme byte KBM=0/Gamepad=1; KBM reticle re-raycasts. [[DR-052_SoD_Facing_Underwater_Feel]] + archive 2026-07-06.
|
||||
|
||||
### Animation (Rukhanka) ★
|
||||
Full rationale: [[DR-022_Animation_Pipeline_Rukhanka_Synty]] · [[DR-023_Enemy_Animation_MonsterMash]] · [[Synty_Asset_Inventory]]. Skeletal animation = **Rukhanka 2.9** (the only maintained Entities-native option on 6.4). **Netcode replication OFF** (`RUKHANKA_WITH_NETCODE` undefined) → **client-derived**: `PlayerAnimationDriveSystem` (client-only `SystemBase`, `[WorldSystemFilter(LocalSimulation|ClientSimulation)]` + `[UpdateBefore(RukhankaAnimationSystemGroup)]`) reads replicated state and writes params via `AnimatorParametersAspect`/`FastAnimatorParameter`. No new `[GhostField]`s; no `DefaultVariant` strip (define off → ghost hash unchanged).
|
||||
@@ -121,6 +121,7 @@ Full rationale: [[DR-022_Animation_Pipeline_Rukhanka_Synty]] · [[DR-023_Enemy_A
|
||||
- **The server runs Rukhanka unless you strip it** — its **deformation** systems are `[WorldSystemFilter(Default)]` (⊇ ServerSimulation). **`ServerStripAnimationSystem`** (server-only one-shot) disables every `Rukhanka.Runtime` system on the server (group-disable cascades; matched by assembly name → no type ref). *Only Play-validation caught this.*
|
||||
- **Build the controller via the `AnimatorController` API** (`manage_animation` drops enum/Vector blend-tree fields). **Skeleton-root = walk up from a bone to the soldier's direct child**, NOT `SkinnedMeshRenderer.rootBone` (the *bounds* root — head SMR's is `Spine_03` → destroys the lower skeleton).
|
||||
- **The rig pipeline is HUMANOID** (muscle clips retarget onto player + monster rigs; the old "Generic" note was WRONG). **Blender clips:** per-action FBX (Key All Bones + Force Start/End + FBX Units Scale); import `CreateFromThisModel` (**CopyFromOther FAILS on Blender's extra `Armature` node**); bake root motion into pose. Optimize Game Objects **OFF**; root motion **OFF** (CC owns the transform).
|
||||
- **★ Skinned ATTACHMENTS onto an existing rig (07-16 suit-kit recipe):** Blender rigid-skin (vgroup w=1 + armature modifier; **UNHIDE the armature before selection-export or the FBX is silently skinless**) → `PlayerRigTools.GraftSmr` REBASES on rebind (verts→rest-world; bindposes = inverse of **rigid scale-stripped** rest matrices — a Blender roundtrip imports cm bones/0.01 armature, raw bindpose reuse explodes ×100; **`RecalculateTangents()` or Rukhanka/BRG registration fails and the whole rig vanishes**). Hand-written deformation shaders: DOTS-instanced `_DeformedMeshIndex` block **BEFORE** the `ComputeDeformedVertex.hlsl` include + the property ALSO in the Properties block (the baker validates `HasProperty`). [[DR-052_SoD_Facing_Underwater_Feel]].
|
||||
- **ENEMIES reuse the player pipeline** — a Husk = ownerless interpolated ghost = a remote player, so `EnemyAnimationDriveSystem` mirrors the REMOTE path (`LocalTransform` delta velocity + prevPos cache; `IsAttacking = AttackWindup != 0`). **Drop `[RequireMatchingQueriesForUpdate]`** so the prune runs every frame (else a cache entry leaks per kill). Build enemy prefabs via **`EnemyRigTools`**, GUID-preserving (`DeleteAsset+CopyAsset` orphans subscene refs); `WaveSystem` uses `baked.WithPosition` (not `FromPosition` → Scale reset). See [[DR-023_Enemy_Animation_MonsterMash]].
|
||||
|
||||
### MCP / editor workflow ★
|
||||
@@ -128,7 +129,7 @@ Full rationale: [[DR-022_Animation_Pipeline_Rukhanka_Synty]] · [[DR-023_Enemy_A
|
||||
- **`apply_text_edits` with MULTIPLE non-adjacent edits in one call can MISALIGN** — one edit per call (or strict bottom-first), always with `precondition_sha256` (it returns the current SHA on mismatch). **★ One edit can SWALLOW an adjacent attribute/comment line** (07-06 `_portalMat` NRE · 07-07 `[RuntimeInitializeOnLoadMethod]` off `WorldFeelConfig.ResetDefaults` → feedback slice silently dead) — re-read neighbors after editing beside attributes; silent presentation slice → probe its config's `Enabled` in-play. **`create_script` won't overwrite**; full-file rewrites = whole-span `apply_text_edits` (its brace-balance validator guards botched spans) or `manage_script delete`+`create_script` (NON-GUID-referenced files only — systems/tests, never authoring MonoBehaviours). `script_apply_edits replace_method` is safe for class methods but **can't target a `struct : ISystem`**. [[DR-017_Persistent_Base_Player_Driven_Pacing]]
|
||||
- **`execute_code` runs as a method body** — no `using` directives (parsed as statements); fully-qualify every type. Identify worlds by `world.Name == "ServerWorld"/"ClientWorld"` (flags overlap a shared `Game` bit).
|
||||
- **`manage_gameobject create` / `manage_prefabs modify_contents` `component_properties` SILENTLY DROP enum + Vector3 fields** — set those via a follow-up `manage_components set_property` and VERIFY through `mcpforunity://scene/gameobject/{id}/component/{Type}` (or read the baked component in `execute_code` after Play). `manage_material set_renderer_color` uses a runtime PropertyBlock that does NOT persist into Play — create + assign a material asset instead.
|
||||
- **New ghost prefab recipe:** `manage_asset duplicate` an existing correctly-configured ghost (e.g. `UpgradePickup.prefab`) → `manage_prefabs modify_contents` to swap the authoring MonoBehaviour (strip MeshFilter+MeshRenderer for an invisible state-holder) — its ownerless/interpolated `GhostAuthoringComponent` + `LinkedEntityGroupAuthoring` come free. **Runtime-spawn shared ghosts** via a one-shot server spawner (dodges the prespawn handshake); wire a baked spawner into the subscene via `manage_scene load additive` → `set_active_scene Gameplay` → create+verify → `save` → `close_scene`.
|
||||
- **New ghost prefab recipe:** `manage_asset duplicate` a correctly-configured ghost (`UpgradePickup.prefab`) → swap the authoring MB (ownerless/interpolated `GhostAuthoring` + LEG come free). **Runtime-spawn shared ghosts** via a one-shot server spawner (dodges the prespawn handshake); wire baked spawners via `manage_scene load additive`→`set_active`→create→`save`→`close_scene`. Detail → archive 07-16.
|
||||
- **An UNFOCUSED editor throttles Edit mode to near-idle** (MCP pings time out, bridge looks hung — it still queues; `telemetry_ping` succeeds) and stalls EditMode test INIT (pass `run_tests(init_timeout=120000)`, retry). `Application.runInBackground` only helps in **Play** mode. Prefer `refresh_unity scope=scripts` for code-only changes. Ask the operator to **focus Unity** for heavy build/test/Burst sessions.
|
||||
- **Run an adversarial design-review Workflow (netcode/relevancy · determinism/prediction · reuse/scope → synthesize) BEFORE coding a netcode-heavy slice** — it has pre-caught relevancy traps, singleton collisions, dt-traps, double-destroys.
|
||||
|
||||
@@ -165,7 +166,7 @@ Full rules: `.claude/skills/dots-dev/references/dots-conventions.md` (in-repo; t
|
||||
|
||||
## Memory — three layers (which tool when)
|
||||
|
||||
Full protocol + per-layer detail: [[Documentation_Protocol]] (`Docs/Vault/_Meta/Documentation_Protocol.md`). The three layers: **in-repo vault** `Docs/Vault/` (design docs, DRs, session logs — committed) · **basic-memory** MCP (semantic/wikilink recall over the vault) · **native Claude memory** (`memory/`, `MEMORY.md` — machine-local). (serena REMOVED 2026-07-07 — unused in practice + flaky on Unity.)
|
||||
Full protocol + per-layer detail: [[Documentation_Protocol]] (`Docs/Vault/_Meta/Documentation_Protocol.md`). The three layers: **in-repo vault** `Docs/Vault/` (design docs, DRs, session logs — committed) · **basic-memory** MCP (semantic/wikilink recall over the vault) · **native Claude memory** (`memory/`, `MEMORY.md` — machine-local). (serena removed 07-07.)
|
||||
|
||||
- Where is X / who calls it → **Grep/Glob**. What did we decide / how does Z work → **basic-memory** → read the vault note. Current DOTS API → **context7**. Conventions → this file. Long-form build lessons → the gotchas archive.
|
||||
- **Cross-machine rule:** durable truth → the **vault** or **this file** (both committed); native `memory/` is local-only, never the sole home of a decision.
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
---
|
||||
title: Facing & Underwater Feel — Build Spec (SoD facing + heavier movement + suit kit)
|
||||
date: 2026-07-15
|
||||
links: "[[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)
|
||||
|
||||
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).
|
||||
@@ -0,0 +1,79 @@
|
||||
---
|
||||
title: 2026-07-16 — SoD facing, underwater feel, Bathynaut kit in-engine
|
||||
date: 2026-07-16
|
||||
links: "[[DR-052_SoD_Facing_Underwater_Feel]], [[Facing_Underwater_Feel_Build_Spec]], [[DR-051_Lantern_Realignment_Purge]], [[Art_Direction_Lantern]]"
|
||||
---
|
||||
|
||||
# 2026-07-16 — SoD facing, underwater feel, Bathynaut kit (movement/animation tuning session 1)
|
||||
|
||||
Operator brief (07-15, /art-dev + /dots-dev): get the lamp/helmet/etc. in-engine on the character; kill rotate-to-cursor — facing "exactly like Shape of Dreams"; heavier underwater movement; heavier animation + footstep sounds; gap-analyze the movement+animation combo.
|
||||
|
||||
Forks locked by operator: facing model (a) move-facing + cast-turn + idle-hold · suit = dome+tank+shoulder lamp, **no chest beacon** · **keep cursor-dash** (stationary dash/blink fallback Move→Aim→facing) · **dome + bare male head** (Synty sci-fi helmet removed).
|
||||
|
||||
## Shipped (working tree, uncommitted — 22 modified + new files)
|
||||
|
||||
### B — Shape-of-Dreams facing (the rework, as corrected by the pre-code review)
|
||||
- **Pre-code design review `wf_a13d4395-1c7`** (3 lenses → 25 findings → 21 confirmed / 3 refuted / 1 critic died on session-limit; the dead critic's ordering claim was independently refuted by a sibling with full code evidence). **Blocking catch: fire directions did NOT read raw Aim — every archetype + the melee cleave aimed from `PlayerFacing`**; under move-facing every skillshot would have fired along the travel direction. The Build Spec ([[Facing_Underwater_Feel_Build_Spec]]) records all verdicts.
|
||||
- `FacingMath` (new, Simulation): `ResolveAim` (THE fire-direction resolver: Aim→facing→+Z), `SelectTarget` (shared Aim→Move→hold cascade; castActive grants Aim *priority* only — a resting gamepad stick mid-cast falls to Move), `RotateToward` (verbatim extraction).
|
||||
- `TickWindowMath` (new, Simulation/Combat): `SwingActive`/`FireActive`/`SocketFireAndCone` moved verbatim from PlayerAnimationDriveSystem (window length stays a caller param) + **Movement-archetype skip** — a blink is a dodge, never a cast; also kills the latent cooldown-tail phantom `IsFiring` pulse. Anim path now shares the same code.
|
||||
- `PlayerAimSystem`: castActive = melee swing ∪ non-Movement socket windows; locomotion rate = stat (360°/s), cast rate = const 1080°/s, both knob-overridable; **no `IsFirstTimeFullyPredictingTick` gate** (incremental integrator over the snapshot-restored [GhostField] — documented in header); `[UpdateAfter(MeleeComboSystem)]` hygiene pin (cycle-free, verified; sorter tie-breaks are deterministic cross-world so this pins semantics, not a divergence fix).
|
||||
- **Fire-direction decoupling**: AbilityFireSystem (cone :174 / aoe :209 / hitscan :242 / projectile+assist-seed :279) + MeleeComboSystem cleave (:163) → `FacingMath.ResolveAim(input.Aim, facing)`. PlayerFacing = body yaw only. Windup keeps current-tick Aim (existing contract).
|
||||
- Presentation coupled to the damage source: AimReticleSystem gamepad ring + lock-on tether, CombatFeedbackSystem local socket cue + melee arc (remotes stay facing-based — Aim isn't replicated to non-owners, documented asymmetry).
|
||||
- DashSystem/BlinkSystem stationary fallback → Move→**Aim**→facing (cursor-dash kept).
|
||||
|
||||
### C — Heavier underwater movement
|
||||
- `DefaultGroundedSharpness` 15→6 — **all coupled sites together** (const + Player.prefab serialized value + 4 DashSystemTests assertions → const refs; the review confirmed DashSystem's every-tick restore stomps a prefab-only edit within one tick).
|
||||
- `Character_Default.asset` TurnRateDegreesPerSec 720→360 (the asset, not just the initializer).
|
||||
- TuningKnob 26/27/28 (`TurnRateDeg`/`CastTurnRateDeg`/`MoveSharpness`), Count=29, **0 = no-override sentinel** (explicit ClampKnob case ≥0 — the default ≥1 branch would force a 1-unit override); all six maps + `DebugTuningReport` fields extended (**dev-protocol bump**: a stale standalone dev build will refuse the handshake vs a fresh peer — expected, rebuild both) + DebugOverlay rows + TuningConfigTests pin. Sharpness knob honored at all 4 restore sites (dash tail/idle, blink, death).
|
||||
|
||||
### D — Heavier animation + footsteps
|
||||
- Stride-distance footsteps (`FootstepStrideMeters` 1.5m; accumulate planar travel → cadence tracks the drifting velocity), 3 deep-thud clip variants (95–120Hz noise sweeps, decay 7) + ±12% volume jitter + a dark silt puff at the feet (new `SiltPuff` pool emitter). Old fixed-interval timer + `FootstepIntervalSec` deleted.
|
||||
- `RetimeLocomotionGait` (PlayerRigTools): Locomotion blend-state speed 0.85, idle⇄locomotion blends 0.25s (AnimatorController API).
|
||||
|
||||
### A — Bathynaut kit in-engine (art track)
|
||||
- Blender: 33 kitbash pieces rigid-skinned to Head/Spine_03/Clavicle_L (vgroup w=1 + armature modifier, saved in the master), joined per shader role → `SM_Suit_KitBrass` (940 tris) + `SM_Suit_KitGlow` (104: porthole glass + lamp lens), FBX to `ArtSource/Blender/`. ChestLamp + LampRim excluded per fork.
|
||||
- Unity: `PlayerRigTools.AttachBathynautKit` — grafts the kit SMRs + the bare male head onto Player.prefab's flattened skeleton by bone name (in place, GUID preserved, ghost surface unchanged); removes the Synty sci-fi helmet. **GraftSmr REBASES**: verts baked to rest-world space, bindposes = inverse of **rigid (scale-stripped)** rest matrices, `RecalculateTangents()` — persisted as `Rebased_*.asset` meshes (GUID-stable Clear+refill).
|
||||
- New `ProjectM/EmissiveGloamSkinned` shader (hand-written HLSL + Rukhanka `ComputeDeformedVertex`, in-place skinning) on `M_EmissiveGloam_WarmSkinned` (steady warm gold — true light): porthole + lamp lens glow and DEFORM.
|
||||
- Player.prefab children now: Root skeleton · body · armour · KitBrass · KitGlow · bare head. Suit tris ~8.9k (body 7.9k + kit 1k) — still over the ≤6k hero budget; decimate pass stays parked (07-14).
|
||||
|
||||
## Validation
|
||||
|
||||
- L1: console clean (only known tick-batching noise from execute_code stalls).
|
||||
- L2: **409/409 EditMode green** (+19: FacingMathTests 11, TickWindowMathTests 7 incl. the Movement-skip + resting-stick-cast cases, TuningConfigTests sentinel pin; SystemOrderingCycleTests now co-registers PlayerAim/PlayerControl/Blink/PlayerDeathState).
|
||||
- Play smoke (DevSandbox, client+server): **server==client facing under injected input**; move-east → facing (1,0) both worlds; **idle + aim-north holds facing** (no passive cursor tracking); cast opens → facing swings fully north at cast rate and returns; blink socket does NOT open a cast window; **projectile fired while moving east flies (0.00, 1.00) = raw Aim** — the SoD manual-aim contract, live. World creation clean (ordering pin acyclic).
|
||||
- L3: screenshots (`Assets/Screenshots/suitkit_front_glow.png`, `suitkit_back_rigid.png`) — brass dome + glowing amber porthole behind the grille + shoulder lamp from the front; dome crown + tank pack silhouette from the back/game angle. Kit deforms with the rig.
|
||||
- Post-impl diff review `wf_9a8d6162-e72` (20/20 agents, no failures): **17 findings → 15 confirmed (0 blocking: 3 should-fix + 12 nits) / 2 refuted.** All triaged and closed same-session:
|
||||
- **Fixed in code**: blink no longer fires the muzzle/fire cue (the third SocketCooldown consumer got the Movement skip); footsteps got a 0.18s cadence floor (a dash could machine-gun 2–4 thuds); `GraftSmr` now validates the bone rebind BEFORE destroying the old child (a mismatch used to silently strip the piece) + a rigid-skinning guard (`weight0<0.999` aborts) + `rebase:false` for the meter-scale blend-skinned head (tool now reproduces the committed prefab); orphaned doc-blocks deleted from PlayerAnimationDriveSystem; `k_AttackAnimTicks` now structurally = `PlayerAimSystem.CastFacingTicks`; dead ResolveAim re-guard removed from AimReticleSystem; remote-arc asymmetry documented at UpdateRemoteSwings; wrap (0-sentinel) + no-blob tests added to TickWindowMathTests. **Suite 411/411 green after fixes.**
|
||||
- **Docs amended**: Build Spec B.6 (local FX = ResolveAim, deliberately better than the draft cascade — cue==damage in every case) + Part D (pitch jitter shipped as 3 clip variants + volume jitter).
|
||||
- **Accepted**: the Move→Aim→facing cascade is duplicated verbatim in DashSystem/BlinkSystem (byte-identical, commented; a `ResolveDashDir` helper is available cleanup if a third site ever appears).
|
||||
|
||||
## Gotchas learned (new, durable)
|
||||
|
||||
1. **Blender skinned-kit export: a HIDDEN armature can't be selected → `use_selection` FBX export silently drops the skeleton + all vgroups** (the mesh imports as static MeshRenderers). `hide_set(False)` before selecting; restore after.
|
||||
2. **Blender→Unity skinned roundtrip imports cm bones under a 0.01 armature** (regardless of FBX_SCALE_UNITS vs FBX_SCALE_ALL) while Synty-native skeletons are meter-scale. **Rebind by bindpose reuse EXPLODES (×100)**. The fix that works: rebase at graft time — bake verts to rest-world, bindposes = inverse of **rigid (scale-stripped)** rest matrices (`Matrix4x4.TRS(pos, m.rotation, one).inverse`).
|
||||
3. **A procedural skinned Mesh asset without TANGENTS fails Rukhanka/BRG registration** (`BatchMeshID not present` + assertion spam, whole rig vanishes) — `RecalculateTangents()` mandatory.
|
||||
4. **Hand-written Rukhanka deformation shaders**: the `UNITY_DOTS_INSTANCING_START` block with `_DeformedMeshIndex` must be declared **BEFORE** including `ComputeDeformedVertex.hlsl` (macro expands at include time → else "undeclared identifier ..._DOTSInstancingOverrideMode" only in the DOTS_INSTANCING_ON variant = magenta), AND `_DeformedMeshIndex` must ALSO be a Properties-block entry — Rukhanka's SkinnedMeshBaker validates `material.HasProperty`.
|
||||
5. Synty variant containers hold BOTH genders' heads — match grafts by exact name (`Contains("Head")` grabbed the female head first).
|
||||
6. `manage_asset rename` can half-fail (moved the asset to `Assets/` root, extensionless, while reporting an error) — verify on disk; a plain file copy + `refresh_unity force` is the reliable fallback.
|
||||
|
||||
## Follow-ups (surfaced, not silent)
|
||||
|
||||
- `Ability_Blink.asset` CooldownTicks=1 vs BlinkSystem's 150-tick stamp (HUD bar + window-math hygiene) — align to 150 later.
|
||||
- Bathynaut game-ready decimate pass (~8.9k tris vs ≤6k hero budget) — parked since 07-14.
|
||||
- EmissiveGloamSkinned is BRG-only by design (invisible in plain classic scenes — same class as all EG-skinned materials).
|
||||
- Operator eyes-on tuning pass: knobs live in the DevSandbox overlay (Turn rate deg / Cast turn deg / Move sharp + the melee/dash rows); FeelConfig footstep values compile-time for now.
|
||||
|
||||
## Part E — gap analysis (the operator deliverable)
|
||||
|
||||
What the movement+animation combo still lacks for the underwater-heavy feel, in recommended order:
|
||||
1. **Idle sway/breathing** — the suit stands statue-still; a subtle full-body idle drift (Blender clip on the humanoid pipeline) would sell suspension-in-water more than any tuning knob.
|
||||
2. **Start/stop weight reads** — sharpness 6 gives the velocity drift, but there's no lean-in/lean-back pose; a 2-frame lean overlay driven off acceleration (AnimParamMath already computes the basis) is the cheap version.
|
||||
3. **Bubble exhaust** — periodic bubble trickle from the dome (pool emitter, warm-neutral, rises) synced loosely to the footstep cadence; strongest cheap underwater cue after the silt puffs.
|
||||
4. **Turn lean/banking** — body roll proportional to the facing turn rate (client-only, presentation quaternion tweak).
|
||||
5. **Camera weight** — PrototypeCameraRig positional lag/damping scaled up slightly so the camera "drags" through water with the player.
|
||||
6. **Underwater ambience loop** — procedural low rumble + occasional distant groans; the soundscape is currently just SFX.
|
||||
7. **Walk-cycle authoring** — the retimed Synty walk still reads "land walk slowed down"; a Blender heavy-trudge clip (lead with the chest, delayed foot plants) is the real fix per the humanoid clip pipeline.
|
||||
|
||||
## Next session
|
||||
|
||||
Operator eyes-on feel pass with the live knobs (turn rates, sharpness, footstep values), then pick from the gap list (recommend 1+3 first). Post-impl review findings (wf_9a8d6162-e72) to triage if any confirmed.
|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
title: DR-052 — Shape-of-Dreams facing model + underwater movement feel
|
||||
date: 2026-07-16
|
||||
status: locked
|
||||
links: "[[2026-07-16_Facing_Underwater_Feel_Suit_Kit]], [[Facing_Underwater_Feel_Build_Spec]], [[DR-048_Lantern_Adoption_Full_Pivot]], [[DR-051_Lantern_Realignment_Purge]]"
|
||||
---
|
||||
|
||||
# DR-052 — Shape-of-Dreams facing model + underwater movement feel
|
||||
|
||||
## Decisions (operator-locked 07-15/16)
|
||||
|
||||
1. **The facing model is SoD option (a)**: the body turns toward the MOVEMENT direction while moving; turns toward the AIM only during a cast window (melee swing / non-Movement socket fire, `CastFacingTicks`=13 ≈ the anim pulse); holds the last facing when idle. **The cursor is never passively tracked.** Twin-stick cursor-facing is dead.
|
||||
2. **`PlayerFacing` is body-yaw/presentation ONLY.** Every gameplay direction (all four AbilityFireSystem archetypes, the auto-target seed, the melee cleave) reads `FacingMath.ResolveAim(PlayerInput.Aim, facing)` — raw replicated Aim, facing fallback for a resting gamepad stick, +Z last. Aim-consuming presentation (reticle ring, tether axis, local slash/cone cues) reads the SAME resolver so sim and FX cannot diverge; remote players' cues stay facing-based (Aim isn't replicated to non-owners).
|
||||
3. **A Movement-archetype socket (blink) never counts as a cast** — `TickWindowMath.SocketFireAndCone` skips it outright (facing AND animation paths; this also killed the latent cooldown-tail phantom IsFiring).
|
||||
4. **Stationary dash/blink keep going toward the cursor** (fallback Move→Aim→facing) — cursor-dodge control kept deliberately over SoD purity.
|
||||
5. **Underwater weight defaults**: `GroundedMovementSharpness` 6 (was 15), locomotion turn 360°/s (was 720), cast turn 1080°/s const; MoveSpeed unchanged (6). Locomotion gait ×0.85, idle⇄locomotion blends 0.25s. Footsteps are stride-distance (1.5m) deep thuds + silt puffs.
|
||||
6. **Live tuning rides TuningKnob 26–28 with a 0 = NO-OVERRIDE sentinel** (0 falls back to stat/const/authored; explicit ClampKnob ≥0 case). Extending `DebugTuningReport` is an accepted **dev-protocol bump** (stale standalone dev build ⇒ handshake refusal ⇒ rebuild both).
|
||||
|
||||
## Determinism/netcode contract (from review wf_a13d4395-1c7, confirmed)
|
||||
|
||||
- Wire-neutral apart from the dev report bump: no `[GhostField]`/RPC/input-struct changes; PlayerFacing stays the only replicated facing state.
|
||||
- PlayerAimSystem stays **un-gated** (no `IsFirstTimeFullyPredictingTick`) — the rate-limited turn is an incremental integrator over the snapshot-restored ghost field and must re-integrate every predicted pass.
|
||||
- `[UpdateAfter(MeleeComboSystem)]` on PlayerAimSystem is a **hygiene pin**, not a divergence fix — the sorter's tie-break is deterministic and cross-world-identical (confirmed by critic evidence; the "cross-world sort divergence" claim was refuted). Socket windows open 1 tick late by construction — cosmetic-only since damage is Aim-sourced.
|
||||
|
||||
## Suit attachment pipeline (the reusable recipe)
|
||||
|
||||
Blender kitbash attachments → rigid-skin (vgroup w=1 + armature modifier) → join per shader role → FBX (UNHIDE the armature first — hidden = silently skinless) → `PlayerRigTools.AttachBathynautKit`: bone-name rebind + **rebase** (verts to rest-world; bindposes = inverse of RIGID scale-stripped rest matrices; `RecalculateTangents`) → `Rebased_*.asset` meshes. Glow pieces ride `ProjectM/EmissiveGloamSkinned` (Rukhanka ComputeDeformedVertex; DOTS-instanced block BEFORE the include; `_DeformedMeshIndex` in Properties for the baker's validation).
|
||||
@@ -482,3 +482,23 @@ The surviving invariants (ledger spend-in-place soft-fail; snapshot-above-early-
|
||||
- **Automation (server-only):** catch-up `ProductionMath.CyclesDue` **lower-bound 0**; `RuntimePlacedTag`=player-built. [[DR-020_M7_Automation_Production_Chains]].
|
||||
- (Persistence bullet, pre-v7 form): **born-correct load** — `CycleDirectorSpawnSystem` stages `PendingSave` AT SPAWN; `BaseRestoreSystem` replays structures charge-free + REMAINING-tick cooldowns + HP. `SaveService.Load` = additive floor `[MinLoadableVersion=2, Current]` (old saves load; missing field 0-defaults).
|
||||
- (Netcode bullet, pre-purge form): **Shared GLOBAL state (cycle phase, resource ledger, goal meter) rides an UNTAGGED ghost**, never a region-tagged one (`SetIsIrrelevant` would hide it cross-region). Resolve the ledger via its DISTINCT `ResourceLedger` tag (the multi-`StorageEntry` "multiple instances" rule — EB-2 line).
|
||||
|
||||
## 2026-07-16 — facing/underwater-feel session condensations (pays the SoD facing contract + the skinned-attachment recipe)
|
||||
|
||||
Condensed from CLAUDE.md 07-16; the inline bullets keep the operational core, verbose forms preserved here.
|
||||
|
||||
- **Old "Aim controls" bullet (pre-DR-052, superseded):** Client-derived aim on `PlayerInput.Aim`; scheme byte KBM=0/Gamepad=1; reticle re-raycasts inside `AimReticleSystem` → full detail: archive 2026-07-06 section + the source files. *(Superseded by the SoD facing model — [[DR-052_SoD_Facing_Underwater_Feel]]: PlayerFacing = body-yaw only; fire directions read `FacingMath.ResolveAim`.)*
|
||||
- **Dark-lit screenshot / material values (full form):** `shader.GetPropertyType(idx)`-guard before `GetColor`/`GetFloat`/`GetTexture` (`S_General`'s `_BaseColorMultiply` is a float → `GetColor` returns black). Gate emission on the `_Emissive` flag + a fixture name; keep converted env metallic low (0.1–0.2).
|
||||
- **EG per-instance tint (full form):** works only if the graph's `_BaseColor` is Hybrid-Per-Instance (ShaderGraph `overrideHLSLDeclaration:true`+`hlslDeclarationOverride:2`). `AnimatedLitShader` is DOTS-authored so enemies flash; a stock Synty prop graph (`Generic_Basic`) is Unity-Per-Material → the override renders but silently no-ops. Fallback = procedural decal quads (07-12 cover damage-cracks, Part P).
|
||||
- **Asset-free presentation (full form):** runtime `ParticleSystem` pool uses Sprites/Default + HDR start color; ACES tonemapping needs URP color grading mode = HDR (`m_ColorGradingMode=1`).
|
||||
- **New ghost prefab recipe (full form):** `manage_prefabs modify_contents` swaps the authoring MonoBehaviour (strip MeshFilter+MeshRenderer for an invisible state-holder); subscene wiring sequence `manage_scene load additive` → `set_active_scene Gameplay` → create+verify → `save` → `close_scene`.
|
||||
- **Old condensation-history parentheticals:** 07-07b paid the attribute-swallow gotcha · 07-07c = game-state narrative → invariants+DR pointers (~3 KB freed, operator-ordered) · 07-13 = co-op-Hades core-loop bullet → LANTERN direction pointer ([[DR-048_Lantern_Adoption_Full_Pivot]]) · 07-15 = siege-era/automation/GoalProgress bullets retired with the DR-051 purge · 07-16 = this heading.
|
||||
- **Stack-table trivia:** packages-lock reconcile 2026-06-17 pinned test-framework 1.7.0, ugui 2.5.0, multiplayer.center 1.0.1; `com.unity.ai.assistant` was removed 07-04 for console noise. serena MCP was removed 2026-07-07 (unused in practice + flaky on Unity).
|
||||
|
||||
### The 07-16 skinned-attachment session's full failure chain (for the record)
|
||||
1. Hidden Blender armature → `select_set` no-op → FBX exported skinless (meshes imported as static MeshRenderers, no vgroups). Fix: `hide_set(False)` before selection-export.
|
||||
2. Blender FBX roundtrip (both FBX_SCALE_UNITS and FBX_SCALE_ALL) imports cm bones under a 0.01-scale armature; Synty-native skeletons are meter-scale. Raw bindpose reuse on rebind → skin matrices ×100 (kit rendered 140 m in the air). Fix: rebase — verts baked to rest-world, bindposes = inverse of RIGID (scale-stripped) rest matrices.
|
||||
3. Procedural skinned Mesh asset without tangents → Rukhanka/BRG `BatchMeshID not present` + assertion spam, the whole rig invisible. Fix: `RecalculateTangents()`.
|
||||
4. Hand-written Rukhanka deformation shader: `UNITY_DOTS_INSTANCING_START` block with `_DeformedMeshIndex` must precede the `ComputeDeformedVertex.hlsl` include (macro expands at include time; else "undeclared identifier `_DeformedMeshIndex_DOTSInstancingOverrideMode`" in the DOTS_INSTANCING_ON variant = magenta), and `_DeformedMeshIndex` must also exist in the Properties block (SkinnedMeshBaker validates `material.HasProperty`).
|
||||
5. Synty variant containers hold both genders' heads — graft by exact name, purge wrong-variant children for idempotency.
|
||||
6. `manage_asset rename` half-failed (moved the asset to `Assets/` root, extensionless, while reporting an error) — verify on disk; plain file copy + `refresh_unity force` is the reliable fallback.
|
||||
|
||||
Reference in New Issue
Block a user