From 7350e4dd638354e191390e8075a64595a95c747e Mon Sep 17 00:00:00 2001 From: Luis Gonzalez Date: Mon, 6 Jul 2026 20:49:28 -0700 Subject: [PATCH] Docs: Blender clip pipeline in session log; CLAUDE.md Humanoid correction (net-zero) The 'Synty share one Generic skeleton' note was WRONG - the pipeline is HUMANOID (CharactersAvatar isHuman, muscle-curve clips). Corrected + the Blender clip recipe added; paid for by archiving Aim-controls, the URP m_AssetVersion blocker, VolumeProfile persistence, HDRP-convert and the HudTheme bullets (verbatim in the gotchas archive, 2026-07-06 section). 39,959 bytes. Co-Authored-By: Claude Fable 5 --- CLAUDE.md | 13 ++++----- ...7-06_DevLoop_Iteration_CoopHades_Phase0.md | 29 +++++++++++++++---- .../_Meta/CLAUDE_Build_Gotchas_Archive.md | 15 ++++++++++ 3 files changed, 44 insertions(+), 13 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 9657c8b73..1fdf010b7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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. Design rationale already lives in the per-milestone DRs (`Docs/Vault/07_Sessions/_Decisions/DR-###`). - **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: 06-04 → 06-17 (M1–END-2 long-form + 6.5 stack swap → archive) · 07-04 (base-mining core-loop bullet + stack-history sentence → archive; DR-044 supersedes) · 07-04b (EB-1/2 · END-1/2 bullets → DR pointers, pays DR-045) · 07-04c (Rukhanka-samples/bake/history/enemy-anim bullets trimmed → archive, pays the DR-046 portal/class/prep additions + Health.Max fix). +- Condensation history: 06-04 → 06-17 (M1–END-2 long-form + 6.5 stack swap → archive) · 07-04 (base-mining core-loop bullet + stack-history sentence → archive; DR-044 supersedes) · 07-04b (EB-1/2 · END-1/2 bullets → DR pointers, pays DR-045) · 07-04c (Rukhanka-samples/bake/history/enemy-anim bullets trimmed → archive, pays the DR-046 portal/class/prep additions + Health.Max fix) · 07-06 (Aim-controls + URP-version + VolumeProfile + HDRP-convert → archive; pays the HUMANOID-pipeline correction — the "Generic skeleton" note was WRONG). ## Stack — Unity 6.5.1 (`6000.5.1f1`, stable) as of 2026-06-27 @@ -102,20 +102,19 @@ Long-form originals + the milestone each came from: `Docs/Vault/_Meta/CLAUDE_Bui - **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`). - **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]]. -- **Synty HUD skin via a build-safe `HudTheme` ★ (DR-024):** a runtime name-string `Resources.Load` of Synty sprites is **build-stripped** → use a curated `HudTheme : ScriptableObject` of serialized refs (`HudTheme.Get()` null-safe, flat fallback). `unityBackgroundImageTintColor` MULTIPLIES; don't set `unitySlice*` on 9-slice sprites (per-element ERROR); Synty sprites may import as **Multiple** → `LoadAssetAtPath` null. See [[DR-024_HUD_Synty_Skin_Theme]]. +- **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]]. ### Art import (HDRP store packs → URP) -- BefourStudios art is **HDRP-authored** → magenta under URP 17.4 + Entities Graphics. **Convert, don't switch pipelines** (HDRP breaks EG): re-author to stock URP/Lit via `EnvArtTools.cs` (menu `ProjectM/Art/1. Convert Curated Env Materials`). Synty art is **URP-native — no conversion**. +- BefourStudios HDRP art: convert via `EnvArtTools.cs`, never switch pipelines (HDRP breaks EG); Synty = URP-native → archive 2026-07-06. - **World = cosmetic Synty nature biomes ★ (DR-025):** `Game.unity` roots `BaseBiome`(Meadow_Forest)@origin + `ExpeditionBiome`(Arid_Desert)@+1000; ground = stock URP/Lit (NOT prop-atlas `S_General`). Per-region fog/ambient cross-fade via client `WorldAtmosphereSystem` (camera X>500). **PNB fog/cloud-ring prefabs = white torus — don't place.** See [[DR-025_World_Environment_Redo_Natural_Frontier]]. - **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). -- **`VolumeProfile.Add()` does NOT persist** (serializes `{fileID:0}`) — use `AssetDatabase.AddObjectToAsset(comp, profile)` + `SaveAssets`, verify on disk. -- **A reverted engine/URP upgrade can stamp `URPGlobalSettings.asset` `m_AssetVersion` AHEAD of the package's `k_LastVersion`** (11>10, from the reverted 6.6 alpha); URP migrates forward-only so `URPPreprocessBuild` rejects it (*"not at last version"*) — **blocks player builds, not editor Play**. Fix: reflection-set `m_AssetVersion` back to `k_LastVersion` + `SaveAssets`. +- **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 cosmetic colliders are **inert to the DOTS PhysicsWorld**). **World collision = subscene-only ★:** `Environment`-layer boundary ring + landmark box colliders (player blocked via the default layer matrix); enemies slide via a server `CollisionWorld.SphereCast` in `EnemyAISystem` (filter=`WorldCollisionConfig.EnvironmentMask`). Boundary = a height-gated `SM_Env_Rock_Cliff` bowl 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 rides the EXISTING `PlayerInput.Aim` `[GhostField]`** (`PlayerInputGatherSystem`, managed, `GhostInputSystemGroup`: cursor ray → `AimMath.PlanarAimFromRay`); only the direction crosses the wire. Scheme = last-meaningful-actuation-wins **`byte`** (`PlayerInput.Scheme`, KBM=0/Gamepad=1 — compared in Bursted `AbilityFireSystem`); server gates `AutoTarget` to gamepad only. `AimReticleSystem` (client, observe-only) RE-raycasts the KBM ground point INSIDE itself (runs after the follow-cam LateUpdate → latching from the gather drifts a frame); hardware cursor hidden while aiming+focused. +- 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. ### 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). @@ -125,7 +124,7 @@ Full rationale: [[DR-022_Animation_Pipeline_Rukhanka_Synty]] · [[DR-023_Enemy_A - **First Rukhanka bake is ~60 s, main-thread-synchronous** (editor freezes — not a hang; blob cached after → fast re-plays). - **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). -- **Synty Polygon characters share one Generic skeleton**; FBX needs **Optimize Game Objects OFF** (Rukhanka req). Entity origin = capsule center (~1 m up) → offset the un-keyed `Root` bone local Y (baked constant → persists through clips). Root motion **OFF** (CC owns the transform; blend tree velocity-driven). +- **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). - **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 ★ diff --git a/Docs/Vault/07_Sessions/2026/2026-07-06_DevLoop_Iteration_CoopHades_Phase0.md b/Docs/Vault/07_Sessions/2026/2026-07-06_DevLoop_Iteration_CoopHades_Phase0.md index d0fd828e9..0aec3711f 100644 --- a/Docs/Vault/07_Sessions/2026/2026-07-06_DevLoop_Iteration_CoopHades_Phase0.md +++ b/Docs/Vault/07_Sessions/2026/2026-07-06_DevLoop_Iteration_CoopHades_Phase0.md @@ -85,11 +85,28 @@ stale copy interpolates `Tuning.StartingOre`; expedition ground receded via proj fleet died mid-run ([[workflow-agent-quota-failures-look-clean]]) — salvaged the completed verdicts from `journal.jsonl` and hand-synthesized; the folded design is in [[Iteration_2026-07_CoopHades]]. +## Blender-authored animation clips — BUILT + Play-validated (same session, operator: "you blender author, do it fully") + +**The pipeline is HUMANOID, not Generic** — `CharactersAvatar.isHuman=true`, clips are muscle curves (CLAUDE.md +corrected; muscle retargeting plays one clip on the player AND every monster rig). Four clips authored +programmatically in Blender on `PolygonSyntyCharacter.fbx` (full-body keys per pose; world-space hip-drop helper — +the armature object carries a 90° X FBX rotation, so naive "down" goes backward; poses verified by EEVEE renders + +numeric bone world positions): **A_Death_FallBack** (0.90 s = exactly the 54-tick corpse window; recoil → fall → +flat-on-back → settle), **A_Fire_OneHand**, **A_Dash_Lean**, **A_HitReact**. Export: per-action FBX, Key All Bones + +Force Start/End + FBX Units Scale. Import: `CreateFromThisModel` (**CopyFromOther FAILS on Blender's extra `Armature` +node** — "Transform 'Armature' not found in HumanDescription"); all root motion baked into pose. Wiring: Death state +motion swapped on BOTH controllers (was a crouch idle); player gains `IsFiring`+Fire and `IsDashing`+Dash states +(AnimatorController API); `PlayerAnimationDriveSystem` routes `FireActive` to IsFiring (melee owns IsAttacking +again) + derives IsDashing from the local predicted `DashState` window (not replicated → remotes skip the lean). +A_HitReact imported, wiring deferred (needs a hit-tick source). **Play-validated**: forced `IsDead` (via the Rukhanka +parameter-hash buffer write, driver disabled) renders the soldier flat on the ground in-game — the +Blender→FBX→Humanoid→Rukhanka chain proven end-to-end. 456/456. + ## Next session -Phase 1 leftovers: real death/fire/dash clips (needs the Synty Sword/Bow Combat animation packs — operator decision -to buy, or Blender-author on the shared Generic skeleton), creature locomotion for monsters (PolygonDog re-rig for the -Swarmer is inventoried), remaining clarity swaps (distinct spit/pickup/harvest meshes, weapon meshes, projectile point -lights), knob rows for the new tunables in the overlay UI. Then the **Phase 1 gate: one honest solo campaign to -Victory + a LAN lap**. Phase 2 (hub-ification) requires the adversarial design review before code. Blender workstream -(portal gate ring, hub crystal, boss kitbash, decals) can start any time. +Phase 1 leftovers: HitReact wiring, creature locomotion for monsters (PolygonDog re-rig for the Swarmer is +inventoried), remaining clarity swaps (distinct spit/pickup/harvest meshes, weapon meshes, projectile point lights), +knob rows for the new tunables in the overlay UI. Then the **Phase 1 gate: one honest solo campaign to Victory + a +LAN lap** (now also gating: death-fall feel, fire thrust, dash lean, corpse-window timing). Phase 2 (hub-ification) +requires the adversarial design review before code. Blender hero-prop workstream (portal gate ring, hub crystal, boss +kitbash, decals) is next in the art track — the clip pipeline is proven. diff --git a/Docs/Vault/_Meta/CLAUDE_Build_Gotchas_Archive.md b/Docs/Vault/_Meta/CLAUDE_Build_Gotchas_Archive.md index 600f5f18f..a87a30d7b 100644 --- a/Docs/Vault/_Meta/CLAUDE_Build_Gotchas_Archive.md +++ b/Docs/Vault/_Meta/CLAUDE_Build_Gotchas_Archive.md @@ -422,3 +422,18 @@ longer earns inline space). Verbatim: The inline Rukhanka section keeps the pointer (`AnimatedLitShader` lives in `_Project/Shaders/`, GUID-preserved from the deleted samples tree) so the deformation-material rule is still self-contained. Full rationale: [[DR-022_Animation_Pipeline_Rukhanka_Synty]]. + +## 2026-07-06 — condensation (pays the Humanoid-pipeline correction in Animation) + +### Aim controls (verbatim from CLAUDE.md) +- **Client-derived aim rides the EXISTING `PlayerInput.Aim` `[GhostField]`** (`PlayerInputGatherSystem`, managed, `GhostInputSystemGroup`: cursor ray → `AimMath.PlanarAimFromRay`); only the direction crosses the wire. Scheme = last-meaningful-actuation-wins **`byte`** (`PlayerInput.Scheme`, KBM=0/Gamepad=1 — compared in Bursted `AbilityFireSystem`); server gates `AutoTarget` to gamepad only. `AimReticleSystem` (client, observe-only) RE-raycasts the KBM ground point INSIDE itself (runs after the follow-cam LateUpdate → latching from the gather drifts a frame); hardware cursor hidden while aiming+focused. + +### URP asset-version + VolumeProfile persistence (verbatim from CLAUDE.md) +- **`VolumeProfile.Add()` does NOT persist** (serializes `{fileID:0}`) — use `AssetDatabase.AddObjectToAsset(comp, profile)` + `SaveAssets`, verify on disk. +- **A reverted engine/URP upgrade can stamp `URPGlobalSettings.asset` `m_AssetVersion` AHEAD of the package's `k_LastVersion`** (11>10, from the reverted 6.6 alpha); URP migrates forward-only so `URPPreprocessBuild` rejects it (*"not at last version"*) — **blocks player builds, not editor Play**. Fix: reflection-set `m_AssetVersion` back to `k_LastVersion` + `SaveAssets`. + +### BefourStudios HDRP conversion (verbatim from CLAUDE.md) +- BefourStudios art is **HDRP-authored** → magenta under URP 17.4 + Entities Graphics. **Convert, don't switch pipelines** (HDRP breaks EG): re-author to stock URP/Lit via `EnvArtTools.cs` (menu `ProjectM/Art/1. Convert Curated Env Materials`). Synty art is **URP-native — no conversion**. + +### Synty HUD skin / HudTheme (verbatim from CLAUDE.md) +- **Synty HUD skin via a build-safe `HudTheme` ★ (DR-024):** a runtime name-string `Resources.Load` of Synty sprites is **build-stripped** → use a curated `HudTheme : ScriptableObject` of serialized refs (`HudTheme.Get()` null-safe, flat fallback). `unityBackgroundImageTintColor` MULTIPLIES; don't set `unitySlice*` on 9-slice sprites (per-element ERROR); Synty sprites may import as **Multiple** → `LoadAssetAtPath` null. See [[DR-024_HUD_Synty_Skin_Theme]].