Docs: DR-022 animation pipeline + /dots-dev commit phase
DR-022 + session log for the Rukhanka/Synty player-animation slice; CLAUDE.md stack row + Animation (Rukhanka) build gotchas + client asmdef refs; add Phase 10 (operator-approved commit) to the dots-dev skill. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -132,7 +132,18 @@ If the operator says "skip the protocol", still write a one-line stub log so the
|
||||
**Suggested next step**: <one concrete next action, from the session log's Next-session intent>.
|
||||
```
|
||||
|
||||
Stop. Do not auto-loop into a fresh session.
|
||||
Then **offer to commit** the changes (Phase 10) — propose how, but do not commit unless asked. Do not auto-loop into a fresh session.
|
||||
|
||||
## Phase 10 — Commit (only after explicit operator approval)
|
||||
|
||||
Committing is a state change, so it is **gated like the plan**: never auto-commit. In the Phase 9 report, **offer** to commit and propose the grouping; commit only when the operator explicitly says so (e.g. "commit", "commit it", "commit for me"). If they decline or don't mention it, **leave git untouched** and note the changes are uncommitted for their review.
|
||||
|
||||
When approved:
|
||||
1. **Review first.** `git status` + `git diff --stat` (and `git diff` on anything non-obvious) so the operator sees exactly what lands. **Stage explicit paths — never `git add -A` blindly.** Never commit generated/cache (`Library/`, `Temp/`, `obj/`, `Logs/`, `UserSettings/`), `.csproj`/`.sln`, or stray scratch files (debug screenshots, temp exports).
|
||||
2. **Group logically** — one commit per concern, not one mega-commit: typically (a) the feature's `Assets/` code + assets, (b) tests, (c) `Docs/Vault/` + `CLAUDE.md`. Always keep a `.cs`/prefab/material/controller **with its `.meta`** in the same commit.
|
||||
3. **Proper messages.** Read recent `git log --oneline` first and match the repo's style. Imperative subject (≤~72 chars) summarizing the change; a short body on the *what + why* with a back-reference to the DR / session log (e.g. "See DR-0NN"). End **every** message with the Co-Authored-By trailer the environment specifies.
|
||||
4. **Branch + push policy.** Match the repo's convention (the `git log` reveals it — many solo DOTS projects commit straight to the default branch; if the history is *not* solo-commits-on-default, branch first). **Never push** unless the operator explicitly asks.
|
||||
5. Report the commit hash(es) + a one-line summary of each, then stop.
|
||||
|
||||
## DOTS conventions (authoritative file)
|
||||
|
||||
@@ -200,7 +211,7 @@ Escalate only after 1–3 fail or step 1 returns `instance_count == 0`. Quote th
|
||||
- Write machine-specific absolute paths into the skill, code, docs, or `.mcp.json` (use `<repo>` / `${CLAUDE_PROJECT_DIR}`).
|
||||
- Install the DOTS stack / scaffold the project inline (that's the separate setup task).
|
||||
- Skip plan mode, start Phase 6 without `ExitPlanMode`, or skip the Phase 8 doc/memory update.
|
||||
- Auto-commit to git (the operator commits manually). Run more than one `/dots-dev` in parallel.
|
||||
- **Commit or push without explicit operator approval** — committing is gated like the plan (Phase 10): offer it in the report, commit only when the operator says so, group logically with proper messages, and **never push** unless asked. Run more than one `/dots-dev` in parallel.
|
||||
|
||||
## References
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ Multiplayer game on **Unity DOTS (Entities) + Netcode for Entities** — server-
|
||||
| `com.unity.transport` | 2.7.2 | (transitive) |
|
||||
| `com.unity.burst` | 1.8.29 | (transitive) |
|
||||
| `com.unity.mathematics` | 1.3.3 | (transitive) |
|
||||
| `com.rukhanka.animation` | **2.9.0** | Local pkg (`Packages/com.rukhanka.animation`). ECS skeletal animation (Burst CPU/GPU skinning). Declares entities.graphics 1.4.16 → resolves on 6.4.0 via SemVer floor. Netcode replication **OFF** → client-derived. See [[DR-022_Animation_Pipeline_Rukhanka_Synty]]. |
|
||||
|
||||
Values match `packages-lock.json` (reconciled 2026-06-02; URP 17.4.0, test-framework 1.6.0, ugui 2.0.0, multiplayer.center 1.0.1). **History:** briefly tried **6.6.0a6** (renumbers Netcode→6.6.0/Physics→6.5.0/Entities→6.5.0) but its Netcode/Transport runtime is a **confirmed engine bug** ("invalid wrapped network interface") → reverted. If returning to 6.6, expect the renumber + re-test runtime. See [[DR-002_Unity66_Alpha_Netcode_Transport]] and `Docs/Vault/_Meta/CLAUDE_Build_Gotchas_Archive.md`.
|
||||
|
||||
@@ -27,7 +28,7 @@ Root namespace: **`ProjectM`**. Code lives under `Assets/_Project/Scripts/` in f
|
||||
| Assembly | Namespace | Runs in | References |
|
||||
|---|---|---|---|
|
||||
| `ProjectM.Simulation` | `ProjectM.Simulation` | **client + server** worlds | Entities, **Unity.Transforms**, Collections, Mathematics, Burst, Unity.Physics, Unity.NetCode |
|
||||
| `ProjectM.Client` | `ProjectM.Client` | client world only | + Simulation, Unity.Entities.Graphics, **Unity.InputSystem**, Unity.Transforms |
|
||||
| `ProjectM.Client` | `ProjectM.Client` | client world only | + Simulation, Unity.Entities.Graphics, **Unity.InputSystem**, Unity.Transforms, Unity.NetCode, **Unity.Physics + Unity.CharacterController** (KinematicCharacterBody source-gen), **Rukhanka.Runtime** (animation) |
|
||||
| `ProjectM.Server` | `ProjectM.Server` | server world only | + Simulation, **Unity.Transforms**, Unity.NetCode |
|
||||
| `ProjectM.Authoring` | `ProjectM.Authoring` | bake time (+ scene runtime) | Simulation, Entities, **Unity.Entities.Hybrid**, Collections, Mathematics, Unity.NetCode |
|
||||
|
||||
@@ -104,6 +105,17 @@ Long-form originals + the milestone each came from: `Docs/Vault/_Meta/CLAUDE_Bui
|
||||
- **Active scheme = last-meaningful-actuation-wins, replicated as `byte`** (`PlayerInput.Scheme`, KBM=0/Gamepad=1 — byte because compared in Bursted `AbilityFireSystem`). Server gates the `AutoTarget` cone to gamepad only → precise mouse, gamepad-only assist.
|
||||
- **Cursor/reticle = client `PresentationSystemGroup` `SystemBase` (`AimReticleSystem`) that OBSERVES.** Re-raycast the KBM ground point INSIDE that system (PresentationSystemGroup runs after the follow-cam's LateUpdate) — latching from the gather drifts a frame behind. Hardware cursor hidden while aiming + focused, restored on focus-loss/`OnDestroy`.
|
||||
|
||||
### Animation (Rukhanka) ★
|
||||
Full rationale: [[DR-022_Animation_Pipeline_Rukhanka_Synty]]. Skeletal animation = **Rukhanka 2.9** (Entities-native; the only maintained option on the 6.4 stack — Latios/Kinemation isn't 6.4-compatible, Unity's official ECS-animation is vaporware). **Netcode replication OFF** (`RUKHANKA_WITH_NETCODE` undefined) → animation is **client-derived**: a client-only `SystemBase` (`PlayerAnimationDriveSystem`, `[WorldSystemFilter(LocalSimulation|ClientSimulation)]` + `[UpdateBefore(RukhankaAnimationSystemGroup)]`) reads replicated state and writes params via `AnimatorParametersAspect`/`FastAnimatorParameter`. No new `[GhostField]`s; no `DefaultVariant` strip (with the define off, no Rukhanka component is a ghost component → ghost hash/snapshot unchanged).
|
||||
- **The rig must bake on the SAME entity that holds the gameplay components the drive job reads.** Rukhanka puts the param buffer + index-table on the GO with `RigDefinitionAuthoring`, so put `Animator` + `RigDefinitionAuthoring` on the **player root** (not a child) and flatten the skeleton + SMRs under it — else the single-entity drive query matches nothing.
|
||||
- **CPU engine still skins via Entities-Graphics GPU deformation → needs a deformation-aware material** (`AnimatedLitShader`, a multi-target ShaderGraph that includes a `UniversalTarget`; Synty atlas → its `_BaseColorMap`). Stock URP/Lit renders **unskinned static** + a `"does not support skinning"` warning — NOT magenta (magenta = reusing an HDRP sample `.mat`).
|
||||
- **Importing the Rukhanka "Animation Samples"** (the only source of `AnimatedLitShader`) drags in 26 sample **subscenes** (one NRE's Rukhanka's **unguarded clip baker** — `AnimationClipBaker.ReadCurvesFromTransform` reads a null bone Transform on a missing-bone clip), sample **systems that run in your worlds**, and a conflicting **TextMesh Pro** folder. Fix: `MoveAsset` the 3 deformation ShaderGraphs to `_Project/Shaders/` (GUID-preserving → material ref intact), then delete the samples tree.
|
||||
- **First Rukhanka bake is ~60 s, synchronous on the main thread** (editor telemetry freezes → looks like a hang, isn't); the animation blob is cached after → fast re-plays.
|
||||
- **The server runs Rukhanka unless you strip it** — its **deformation** systems use `[WorldSystemFilter(Default)]` (Default ⊇ ServerSimulation) so they run on the server's baked bones/meshes (the animation group is created server-side too but left empty by the bootstrap). **`ServerStripAnimationSystem`** (server-only one-shot, `[WorldSystemFilter(ServerSimulation)]`) disables every `Rukhanka.Runtime` system on the server (disabling a group cascades to its children; matched by assembly name → no type ref). *Only Play-validation caught this — the static `WorldFlags` read said the server was clean; it wasn't.*
|
||||
- **Build the controller via the `AnimatorController` API** (`manage_animation` silently drops enum/Vector blend-tree fields). **Skeleton-root detection = walk up from a bone to the soldier's direct child**, NOT `SkinnedMeshRenderer.rootBone` (that's the *bounds* root — the head SMR's is `Spine_03`; using it destroys the lower skeleton).
|
||||
- **Synty Polygon characters share one Generic skeleton**; the FBX needs **Optimize Game Objects OFF** (Rukhanka requirement; SciFiSpace was already so). Entity origin = capsule **center** (~1 m up) → offset the **un-keyed `Root` bone** local Y (clips key no Root/Hips position → Rukhanka bakes the offset as a constant → it persists through animation). Root motion **OFF** (the CC owns the transform; the blend tree is velocity-driven).
|
||||
- **`Unity.Physics` must be a DIRECT asmdef ref** for any system whose source-gen touches `KinematicCharacterBody` (it nests `Unity.Physics.ColliderKey`) → else CS8377/CS0012 in `*.g.cs` (same class as the `Unity.Transforms` direct-ref rule).
|
||||
|
||||
### MCP / editor workflow ★
|
||||
- **Edit Assets `.cs` ONLY via MCP `apply_text_edits` / `create_script`** (Unity's scripting pipeline) — the raw `Write` tool does NOT reliably trigger a recompile on an unfocused editor → tests/`execute_code` run a **stale assembly**. A raw-`Write`-created NEW `.cs` is worse — it gets **no `.meta` / no test-discovery** until `refresh_unity scope=all mode=force` (it compiles, but EditMode silently won't run it). (`Write`/`Edit` are fine for non-asset files: this vault, asmdef JSON, etc.) For comment/string-precise edits to existing scripts, `script_apply_edits` **`anchor_replace`** (regex anchor) + **`delete_method`** work cleanly even on a `struct : ISystem` (unlike `replace_method`).
|
||||
- **`apply_text_edits` with MULTIPLE non-adjacent edits in one call can MISALIGN** (a paired replace+delete hit the line *above* the target). One edit per call (or strict bottom-first), always with `precondition_sha256` (it returns the current SHA on mismatch). **`create_script` won't overwrite** an existing path; 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 still **can't target a `struct : ISystem`** (use whole-span). [[DR-017_Persistent_Base_Player_Driven_Pacing]]
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
---
|
||||
date: 2026-06-06
|
||||
type: session
|
||||
tags: [session, animation, rukhanka, synty, netcode, presentation, dots, slice]
|
||||
---
|
||||
|
||||
# Session 2026-06-06 — Animation pipeline (Rukhanka + Synty), Slice 1: the player is alive
|
||||
|
||||
## Goal
|
||||
|
||||
Operator (via `/dots-dev`, ultracode): *"Explore animations for this DOTS project — Rukhanka 2 (netcode-aware), alternatives, how much I can wire via MCP. Build something scalable/stable/sustainable for a solo dev. The game feels dead — the player is a capsule with no movement. Make it feel alive."*
|
||||
|
||||
## Process
|
||||
|
||||
- **Research (adversarial Workflow):** verify exact Rukhanka 2.9 / Entities 6.4 APIs against the installed source + docs + context7 → synthesize a code-ready spec → 2 adversarial critics → finalize. Landscape: **Rukhanka is the only maintained Entities-native option on the 6.4 stack** (Latios/Kinemation explicitly not 6.4-compatible; Unity official = vaporware; DMotion dead). 3 parallel research agents up front (Rukhanka capability+netcode, alternatives, Synty rig/anim-pack compat).
|
||||
- **Clarifying gate (AskUserQuestion):** clip source = **Synty anim pack** (already imported: `AnimationBaseLocomotion`), first slice = **player only**, netcode = **advise-me** → chose **client-derived**, look = **SciFiSpace soldier**.
|
||||
- **Plan → approval → execution.** A permissions misfire (my subagent guardrail text "don't enter Play / use UnityMCP" was read by the auto-approver as the *user's* rule) blocked Play; operator reconnected MCP and cleared it.
|
||||
- **Execution serialized through the one live editor** (research fanned out in parallel; all Unity mutations done by me, validating each).
|
||||
|
||||
## Done
|
||||
|
||||
- **De-risk (task 1):** entered Play → Rukhanka 2.9 initializes clean on Entities 6.4 (zero Rukhanka errors). GO.
|
||||
- **FBX (task 2):** SciFiSpace `Characters.fbx` already Generic + Optimize-GO-OFF + Mikk tangents → verify-only.
|
||||
- **Controller (task 3):** authored `AC_PlayerTopDown.controller` via the AnimatorController API (robust vs the enum/Vector-drop MCP traps) — 4 params, Idle / 2D-Freeform-Directional strafe Locomotion (9 in-place Synty run clips) / Death; 0 missing clips.
|
||||
- **Drive system (task 5, done before assets to compile-validate the Rukhanka API):** `AnimParamMath` (pure, **10 EditMode tests**) + client-only `PlayerAnimationDriveSystem` (Local CC-velocity + Remote position-delta jobs; `[WithAll(GhostOwnerIsLocal)]` / `[WithDisabled]` / `[WithPresent(Dead)]`). Asmdef refs added; **also `Unity.Physics`** (source-gen needs it directly — `KinematicCharacterBody` nests `ColliderKey`; the verified spec missed it). EditMode **204/204**.
|
||||
- **Material + prefab (task 4):** imported Rukhanka samples for `AnimatedLitShader` (multi-target ShaderGraph w/ a UniversalTarget → URP-valid), built `M_SpaceSoldier_Animated` (Synty atlas → `_BaseColorMap`). Assembled `Player.prefab`: flattened the soldier skeleton + body/head SMRs onto the **player root** (so the rig co-locates with gameplay components — required for the drive query), `Animator`+`RigDefinitionAuthoring`(CPU, root-motion off) on the root, capsule visual removed / collider kept. **Recovered a broken first attempt** (used `rootBone`=Spine_03 → destroyed the lower skeleton) via `git checkout` + correct walk-up-to-skeleton-root detection.
|
||||
- **Samples cleanup:** moved the 3 deformation ShaderGraphs to `_Project/Shaders/RukhankaSampleShaders` (GUID-preserving) and **deleted the whole samples tree** — it had dragged in 26 sample subscenes (one NRE'd Rukhanka's unguarded clip baker), sample systems that run in our worlds, and a conflicting TextMesh Pro folder.
|
||||
- **Ground offset:** skeleton `Root` local Y = −0.9 (entity origin = capsule center ~1 m up). Clips key no Root/Hips position → Rukhanka bakes it as a constant → persists. Feet 0.90 → ≈0.00.
|
||||
|
||||
## Validation (runtime, focused editor, real Server+Client)
|
||||
|
||||
- ClientWorld player entity: `PlayerTag` + rig + 4 params + facing/stats/CC/`Dead` + **`GhostOwnerIsLocal` enabled** → drive job matches. **Idle and a forced run pose both deform the mesh** (distinct silhouettes, screenshots); **feet at y≈0**; capsule replaced by the textured soldier. Console clean (no NRE, no skinning warning).
|
||||
- ServerWorld: our drive system correctly **absent**.
|
||||
- **EditMode 204/204** (+10 `AnimParamMath`).
|
||||
|
||||
## Decisions
|
||||
|
||||
[[DR-022_Animation_Pipeline_Rukhanka_Synty]] — Rukhanka client-derived; Generic-rig + Synty clips; CPU engine + deformation material; slim top-down controller; **rig-on-root** for entity co-location; client-only two-path drive (local CC-velocity / remote position-delta).
|
||||
|
||||
**Runtime finding the adversarial spec got wrong:** `RukhankaAnimationSystemGroup` **is** created in `ServerWorld` (the spec read a `WorldFlags` gate and concluded otherwise). Harmless (drive is client-only; bones unreplicated; animation never feeds the sim) but wastes server CPU — only Play-validation caught it. Logged as a follow-up (server-strip).
|
||||
|
||||
## Next-session intent
|
||||
|
||||
- ~~Server-strip Rukhanka~~ **DONE (same session):** `ServerStripAnimationSystem` (server-only one-shot) disables all `Rukhanka.Runtime` systems on the server (validated: server enabled=0 / 4 disabled, client 8 running, console clean). Also added a **Phase 10 (Commit)** to the `/dots-dev` skill — offer + commit only on explicit operator approval, logical groups, proper messages, no push.
|
||||
- Template the pipeline to **enemies** (Husk/Brute/Swarmer) — same flow, swap mesh + reuse `AC_PlayerTopDown` (or per-enemy clips).
|
||||
- Source a **combat/hit-react/death** anim pack (Base Locomotion is locomotion-only; Death is a crouch placeholder; Fire has no clip yet).
|
||||
- **Aim-IK** upper body toward the cursor (twin-stick), then ragdoll-on-death, bone-socket weapons, GPU engine + VAT for crowds.
|
||||
- Operator live play-through to tune anim-speed scaling / blend thresholds / the −0.9 offset, and a 2-client MPPM check that remote players animate (the position-delta path).
|
||||
@@ -0,0 +1,52 @@
|
||||
---
|
||||
id: DR-022
|
||||
title: Player skeletal animation via Rukhanka (client-derived) on a Synty SciFiSpace soldier — Slice 1
|
||||
status: accepted
|
||||
date: 2026-06-06
|
||||
tags:
|
||||
- decision
|
||||
- animation
|
||||
- rukhanka
|
||||
- synty
|
||||
- netcode
|
||||
- presentation
|
||||
- dots
|
||||
permalink: gamevault/07-sessions/decisions/dr-022-animation-pipeline-rukhanka-synty
|
||||
---
|
||||
|
||||
# DR-022 — Skeletal animation pipeline (Rukhanka + Synty), client-derived — Slice 1 (player)
|
||||
|
||||
## Context
|
||||
|
||||
The player was a built-in **capsule** (`Player.prefab` = primitive mesh + CC + ghost); enemies/structures too. Operator: *"explore animations … build something scalable, stable, sustainable for a single developer … I want the game to feel alive. The current player capsule … feels really dead."* Owns **Rukhanka Animation System 2** (Entities-native, Burst/GPU skinning, netcode-aware) and a large **Synty Polygon** character library; **Synty Animation – Base Locomotion** was imported this session (721 clips + `AC_Polygon_*` controllers, the unified Synty skeleton).
|
||||
|
||||
An adversarial research **Workflow** (verify exact Rukhanka 2.9 / Entities 6.4 APIs → synthesize → 2 critics → finalize) produced a code-ready spec. Landscape verdict: **Rukhanka is the right (only) primary** on this stack — **Latios/Kinemation explicitly does not support Entities 6.4** today; Unity's official ECS-animation path is vaporware; DMotion is dead. VAT (crowds) + procedural (secondary motion) are complementary, later. Clarifying answers: **Synty anim pack** (already in project), **player-only** first slice, **advise-me** on netcode, **SciFiSpace soldier**.
|
||||
|
||||
## Decision
|
||||
|
||||
1. **Rukhanka 2.9.0; netcode replication OFF** (`RUKHANKA_WITH_NETCODE` undefined). Animation is **client-derived presentation**, never replicated — matches the project doctrine ("derive instead of replicate"; "all juice = client-only observe", see [[DR-017_Persistent_Base_Player_Driven_Pacing]] / [[DR-021_HUD_UITK_BuildPalette]]). Clients compute locomotion params from **already-replicated state** (velocity/facing/`Dead`). Zero added bandwidth, **no new `[GhostField]`s**. Flip netcode on only if the server must arbitrate frame-exact animation state.
|
||||
2. **Generic rig + the existing Synty `AC_Polygon` clips drive the soldier by bone-path** (one unified Synty skeleton spans the Polygon packs → no Mecanim Humanoid Avatar UI). The SciFiSpace `Characters.fbx` was already `animationType:3` (Generic) + **Optimize Game Objects OFF** (Rukhanka's hard requirement) + Mikk tangents → verify-only, no reimport.
|
||||
3. **CPU animation engine + a deformation-aware material.** CPU sampling still skins via Entities-Graphics GPU deformation, which needs a material exposing `_DeformedMeshIndex` — `AnimatedLitShader` (a **multi-target** ShaderGraph that includes a `UniversalTarget` → renders under URP 17.4; stock URP/Lit would render **unskinned static**, not magenta). Synty atlas → its `_BaseColorMap`.
|
||||
4. **Slim custom controller `AC_PlayerTopDown`** (not the 700-clip Synty graph): `Idle` / **2D Freeform-Directional** `Locomotion` (params `MoveX`/`MoveZ`, 9 nodes = idle-center + 8 in-place run strafes) / `Death`; params `MoveX,MoveZ,Speed,IsDead`. **Root motion OFF** (the DOTS Character Controller owns the transform; the blend tree is velocity-driven).
|
||||
5. **Rig on the PLAYER ROOT, not a child.** Rukhanka puts the animator param components (`AnimatorControllerParameterComponent` buffer + index table) on the GameObject holding `RigDefinitionAuthoring`. The client-only drive job needs those **and** the gameplay components (`PlayerFacing`/`EffectiveCharacterStats`/`KinematicCharacterBody`/`Dead`) on **one entity**, so the rig must bake onto the ghost entity itself — `Animator` + `RigDefinitionAuthoring` go on the player root; the soldier skeleton (`Root` + 49 bones) + body/head `SkinnedMeshRenderer`s are flattened as children; the capsule MeshRenderer/Filter are removed, the **CapsuleCollider kept**. (The first assembly attempt used `SkinnedMeshRenderer.rootBone` as the skeleton top — that's the *bounds* root (`Spine_03` for the head) and destroyed the lower skeleton; correct detection = walk up from any bone to the direct child of the soldier instance.)
|
||||
6. **`PlayerAnimationDriveSystem`** (client `SystemBase`, `[WorldSystemFilter(LocalSimulation|ClientSimulation)]`, `[UpdateBefore(RukhankaAnimationSystemGroup)]` → runs in `SimulationSystemGroup` before Rukhanka's same-frame controller eval — a documented exception to "all juice = PresentationSystemGroup", to avoid a 1-frame lag; still observe-only, never in the predicted loop). Two Bursted `IJobEntity` paths:
|
||||
- **Local** `[WithAll(GhostOwnerIsLocal)]` (enableable → only the owned player) → `KinematicCharacterBody.RelativeVelocity`.
|
||||
- **Remote** `[WithDisabled(GhostOwnerIsLocal)]` → `LocalTransform.Position` frame-delta (KinematicCharacterBody is baked **zero** on remotes — not a `[GhostField]`, owner-only-written), cached per-Entity + pruned each frame.
|
||||
- Both `[WithPresent(Dead)]` (`Dead` is baked **disabled** → without `WithPresent` an `EnabledRefRO<Dead>` query silently visits zero alive players). Pure mapping is `AnimParamMath.LocomotionParams` (world-vel → facing-frame strafe + normalized speed), **10 EditMode tests**.
|
||||
7. **Visual ground offset:** the entity origin is the capsule **center** (~1 m up), so feet-at-skeleton-origin float ~0.9 m. Fix = skeleton `Root` local **Y = −0.9**. The in-place clips key **no** `Root`/`Hips` position curves, so Rukhanka bakes the un-keyed `Root` at its authored value → the offset persists through animation. Verified: bodyFeetY went 0.90 → ≈0.00.
|
||||
|
||||
## Consequences (validated at runtime, Unity 6.4.7, real Server+Client worlds)
|
||||
|
||||
- ClientWorld player entity: `PlayerTag` + rig + **4 params** + facing/stats/CC/dead + **`GhostOwnerIsLocal` enabled** → the drive job matches. Idle pose **and** a forced run pose both deform the mesh (distinct silhouettes); feet on the ground; capsule gone. The soldier replaces the capsule in-game (screenshots `_anim_slice1_check`/`_run`, since deleted). **EditMode 194 → 204** (+10 `AnimParamMath`). Console clean (no NRE, no "does not support skinning").
|
||||
- **Asmdef:** `ProjectM.Client` += `Rukhanka.Runtime`, `Rukhanka.Toolbox`, `Unity.CharacterController`, **`Unity.Physics`**. The last is a source-gen requirement the verified spec missed: `KinematicCharacterBody` nests `Unity.Physics.ColliderKey`, so the generated `*.g.cs` needs `Unity.Physics` as a **direct** ref (CS8377/CS0012) — same class as the `Unity.Transforms` direct-ref rule.
|
||||
- **No new asmdef; no netcode surface change** — with the define off, no Rukhanka component is a ghost component (all its `[GhostField]`/variants are `#if RUKHANKA_WITH_NETCODE`), so the ghost hash + snapshot layout are unchanged and **no `DefaultVariantSystemBase` stripping** is needed. New files: `AnimParamMath`, `PlayerAnimationDriveSystem`, `AnimParamMathTests`, `AC_PlayerTopDown.controller`, `M_SpaceSoldier_Animated.mat`, `Shaders/RukhankaSampleShaders/*`, `ServerStripAnimationSystem` (server-only Rukhanka strip); modified `Player.prefab` + `ProjectM.Client.asmdef`.
|
||||
|
||||
## Findings / open / deferred
|
||||
|
||||
- **Server-side Rukhanka — STRIPPED (shipped this session).** Runtime showed Rukhanka systems on the `ServerWorld` (the adversarial spec concluded otherwise from a `WorldFlags` read — wrong; only Play-validation caught it). Real mechanism: the bootstrap creates `RukhankaAnimationSystemGroup` on the server but leaves it **empty** (it fills the update list only `if (isClient)`); the actual waste is the **deformation systems** — they use `[WorldSystemFilter(Default)]` and `Default` includes `ServerSimulation`, so a headless server runs skinned-mesh prep + mesh deformation on the player's baked bones/meshes nobody renders. **Fix:** `ServerStripAnimationSystem` (server-only one-shot, `[WorldSystemFilter(ServerSimulation)]`) disables every `Rukhanka.Runtime` system in the server world (disabling a group cascades to its managed + unmanaged children); matched by assembly name (no Rukhanka type ref → no asmdef change). **Validated:** server Rukhanka enabled **0** (4 disabled), client unaffected (8 running), console clean.
|
||||
- **Samples-pollution gotcha:** importing the Rukhanka "Animation Samples" (the only source of `AnimatedLitShader`) drags in **26 sample subscenes** (one NRE'd Rukhanka's **unguarded clip baker** — `AnimationClipBaker.ReadCurvesFromTransform` reads a null bone Transform when a clip references a missing bone), sample **systems that run in your worlds**, and a conflicting **TextMesh Pro** folder. Fix applied: move the 3 deformation ShaderGraphs to `Assets/_Project/Shaders/RukhankaSampleShaders` (GUID-preserving → material ref intact), then delete the entire samples tree.
|
||||
- **Rukhanka's first bake is heavy** (~60 s, synchronous on the main thread → editor telemetry freezes — looks like a hang, isn't) while it builds the animation **blob** for every clip; **cached afterwards** (re-plays are fast). Budget for it on first Play / clip changes.
|
||||
- **Death = crouch-pose placeholder** (Base Locomotion has no death/hit clips). Needs a combat/hit-react anim pack for real Fire/Death.
|
||||
- **Roadmap (reuses this exact template):** enemies (Husk/Brute/Swarmer) → **Aim-IK** upper body toward cursor → ragdoll on death → bone-socket weapons → GPU engine + **VAT** for large Husk crowds → procedural secondary motion.
|
||||
|
||||
Builds on [[DR-007_M5b_Character_Controller_Package]] (the CC the visual rides), [[DR-012_Aim_Controls_Cursor_Gamepad]] (`PlayerFacing`), [[DR-021_HUD_UITK_BuildPalette]] / [[DR-017_Persistent_Base_Player_Driven_Pacing]] (observe-replicated-state presentation doctrine). Serves the "feel alive" goal.
|
||||
Reference in New Issue
Block a user