Tier 2 · DOTS full stack
Latios, Kinemation & skinned animation
Skeletons, deform shaders and bone masks under Latios, and why units freeze in bind pose.
Skeletons, deform shaders and bone masks under Latios, and why units freeze in bind pose.
Skinned animation under Latios: skeletons, deform shaders, bone masks, and the several ways a unit ends up a bind-pose statue with a completely clean console.
-
Kinemation skin instances: bake a REAL entity prefab via a baker
GetEntity(prefabGameObject)reference — never Prefab-tag a live skeleton and clone it; and under NetCode don'tParenta skeleton root to a ghost. Two paired landmines from wiring per-unit Latios skins: (1) manually addingPrefabto a baked live skeleton (or promoting one at runtime) clones WITHOUT its LinkedEntityGroup/bind state — the instance holdsDependentSkinnedMeshetc. but renders nothing and Kinemation jobs throw (Index -1 out of range); the only reliable source is a SubScene authoring MB whose Baker storesGetEntity(authoring.skinPrefab, Dynamic)(LEG + binding baked prefab-side, Instantiate just works). (2) AUnity.Transforms.Parentadded to a skeleton-root skin gets stripped the same frame by the NetCode/Latios stack (no PreviousParent left behind) — drive the skin with a per-tick follow system copying the root's position/rotation (and destroy the skin when its root is gone, or they leak per respawn). (Seen in a survival RTS: unit skins.) -
Kinemation skin prefab ASSETS stay FBX prefab VARIANTS, and runtime skeleton spawners pin ordering against the buffer-init workaround. Two more skin landmines (same day, same stack): (1) building a skin prefab by UNPACKING the FBX instance and deleting unwanted kit-part nodes breaks Kinemation's optimized-rig mapping — socket bone indices bake against one hierarchy while the runtime
OptimizedBoneTransformbuffer is another length (UpdateSocketsSystemIndexOutOfRange flood). Keep the prefab a VARIANT of the FBX and trim kit parts withSetActive(false)overrides (inactive nodes bakeDisabled, don't render, rig identical). (2) On Latios 0.15.x with a PreBounds/buffer-expansion workaround system, ANY system thatInstantiates optimized skeletons at runtime must declare[UpdateBefore(<init system>)]— unpinned, the implicit sort reshuffles the day another system is added, fresh skeletons reach TransformSystemGroup with bind-pose-length buffers, and every spawn frame throws (classic unpinned read-after-write). Also: a follow job that writesLocalTransformby iteration while reading it via lookup needs[NativeDisableContainerSafetyRestriction]on the read lookup when the sets are provably disjoint. -
Latios deform clothing lives on
_Albedo_Map, not_BaseMap. GeneratedM_Deform_*variants keep the generic deform base on_BaseMapand the pack/clothing atlas on_Albedo_Map. Comparing or swapping only_BaseMap(URP Lit habit) silently no-ops in-match rematerial — units keep the pack look. Read_Albedo_Mapfirst; never assign a URP Lit kit material onto a Kinemation skin (T-pose). Mounts (horse/wolf) are a second deform mat — skip them or every cavalry rematerial paints the animal. -
Kinemation's deform path requires TANGENTS — vendor FBX packs that ship without them bake clean-looking skins whose subscene bake floods
InvalidOperationException: Mesh data does not have Tangent vertex componentfrom import workers. The skin-prefab build itself succeeds (the exceptions land seconds LATER during entity-scene bake/warmup, easily blamed on the wrong step), and Unity's importer defaultimportTangents: Importsilently yields NONE when the FBX carries no tangents (ImportFBX Warnings: Can't import tangents and binormalsis the only breadcrumb). Before wiring a new model pack into a Latios/Entities-Graphics pipeline, audit every mesh withmesh.HasVertexAttribute(VertexAttribute.Tangent)— read the imported MESH back, never trust importer settings (same class as texturetextureCompressionlying) — and setModelImporterTangents.CalculateMikk+SaveAndReimporton the misses. Packs are inconsistent even within one vendor line: in one four-pack drop exactly one race pack (all 22 FBXs) lacked tangents while 143 sibling models were fine. (Seen in a mobile strategy game: wiring the Dwarf unit pack; SW3 water has the same tangent requirement on meshes — see unity.md.) -
In-place AnimatorController regeneration does NOT rebake dependent Mecanim data — force reimport controllers + skin prefabs + subscene, and let the bake FINISH before entering play. Editing a controller asset in place (GUID kept, per ASSET-REGEN-GUID) leaves already-baked skin-prefab
MecanimControllerblobs andMecanimParameterbuffers stale/mismatched against the new parameter list — name-resolved parameter writes land in wrong slots and NO transition ever fires (every skin frozen in its default state while Speed still drives; reads as "walk cycle during attacks / no death anim"). After any generator run:ImportAsset(...ForceUpdate)the controllers, the prefabs referencing them, AND the baked subscene — then WAIT for the import/bake to go idle; entering play mid-bake loads the stale artifacts again. (Seen in practice: twice same day on SC unit controllers.) -
MecanimV2: freezing a final pose = a hold state pinned by a TIME parameter —
state.speed ≈ 0+cycleOffsetdoes NOT freeze. MecanimV2 samples non-looping clips throughLoopToClipTime(unconditional fmod), so a finished one-shot state visibly REPLAYS its clip forever, and the tempting speed≈0/cycleOffset≈1 "park" state doesn't hold the pose either. The working mechanism (PT AC_Pawn DeathHold, re-proven on SC generated controllers): a float parameter defaulting to ~0.99, and a hold state withtimeParameterActive = true; timeParameter = "<param>"playing the SAME clip — normalized time is pinned by the parameter, nothing ever advances. Chain: one-shot state → (exit time 0.9-0.95, NEVER exactly 1.0 — that transition never fires in MecanimV2) → pinned hold with zero outgoing transitions. Works fromAnimatorControllergeneration code (UnityEditor.Animations.AnimatorState .timeParameterActive/.timeParameter). -
Never InstallKinemation / Mecanim on a world that ticks before match start. Menu
CreateLocalWorldand the Editor dummy are not presentation worlds — installing skins there Burst-JITs BatchSkinning in the Editor while the player AOT path stays ~1 GB. Do notGetAll(Default)on those worlds either (Default = Client+Server). Client world at match launch only. Rule owner:BOOT-LOCAL-IS-NOT-THE-MATCHin the Netcode overview page. -
SIM-WINDOW-ANIM-FIT — every one-shot anim state tied to a sim window is speed-scaled to that window (
speedParameter, driver setsclipSeconds / windowSeconds, clamp ≥1) — for EVERY window kind, not just the one that burned first. The server paces by AUTHORED seconds (attack cooldown, cast wind-up root, channel time); the client plays a fixed-length pack clip. Any window shorter than the clip leaves the tail animating over resumed behavior — reads as foot-slide the moment movement returns (the unit "casts while walking"). The trap is partial application: the attack swing gets the fit rule ("one swing fits one cooldown", PT bow lesson) and the NEXT one-shot state added (Cast, channel, emote) ships without it because nothing fails loudly — the slide is cosmetic and only developer eyes catch it. When adding any one-shot state driven by a sim window: add the speed param in the controller generator, set it on the trigger edge from the ghosted window seconds, and scale the FALLBACK path too (a caster without a Cast state plays its attack clip — same fit, same window). (a mobile strategy game: Blood Rage 0.7s cast root vs the 1.33s_11_castpack clip — orcs slid the last ~0.6s of every cast; Attack had the fit for months, Cast never did.) -
MECANIMV2-OPTIMIZED-SKELETON-ONLY (HARD) - Latios MecanimV2 drives OPTIMIZED skeletons exclusively; a skin rig imported with
optimizeGameObjects=falsebakes an EXPOSED skeleton whose Mecanim pose is silently never applied - a bind-pose statue with zero errors. The addon's own feature table says "Exposed Skeletons: Not Supported" (V1 supported them - the V1 habit is the trap). Parameters tick, blobs bake, transitions fire; nothing ever writes the bones, so every asset-side audit (clips pose the rig when sampled, controllers correct, registry wired, materials fine) comes back clean because nothing IS broken except the import flag. The diagnostic tell is elimination symmetry: Humanoid pack skins (optimized) animate while Generic authored-AC skins (exposed) freeze. Fix:optimizeGameObjects=trueon every skin source FBX. Companion trap: vendor COLOR PREFABS (pack _Rd/_Pe colorway variants) are often plain UNPACKED copies, not prefab variants - they freeze the pre-optimization hierarchy, so a skin built FROM the color prefab stays exposed even after its FBX is optimized. Build the skin from the FBX and transfer the colorway's materials by renderer-node name. (a mobile strategy game: six air flyers + nine vendor god/hero replacements all bind-pose statues in match; an earlier lane had even play-claimed "wing-animating".)
Kinemation deform and masks (a project, both invisible-with-no-error)
-
A skinned mesh needs a DEFORM-AWARE shader — Kinemation ships a Shader Graph NODE, not a shader. The bake classifies deform by scanning the material's shader for
_latiosCurrentDeformBase/_latiosCurrentVertexSkinningMatrixBase/_latiosCurrentVertexSkinningDqsBase; with none present it REFUSES the deform blob and the mesh renders bind-pose. Duplicate the vendor shader and add theLatios Deformnode (SG browser: Input → Mesh Deformation, object space, Vertex block only) — never edit the vendor shader in place. Vendor environment/prop shaders carry no skinning nodes and will silently T-pose anything skinned that uses them. (Extends LATIOS-DEFORM-ALBEDO-MAP with the why and the fix.) -
Layer bone masks need a TRANSFORM AvatarMask. Build them with
SetTransformPath/SetTransformActivefrom the rig MODEL'sModelImporter.transformPaths. A mask authored viaSetHumanoidBodyPartActivehastransformCount == 0and bakes a mask with ZERO bits — the layer looks completely healthy (weight 1, transitions fire, states advance) while writing its pose to no bones. The rig being humanoid is a red herring; Latios works at bone-transform level. Never enumerate the instantiated prefab hierarchy — an optimized skeleton has no bone Transforms there. -
Drain skinned-skeleton spawns at a few per frame (~4), not
EntityManager.Instantiate(prefab, N, …). At N ≳ 10 the simultaneous skeleton allocations plus first compute-skin dispatches stall the GPU past Windows' ~2 s TDR, which presents as a graphics-driver crash and gets blamed on hardware. Player-spawn paths (1–2 per connection) are exempt. -
MECANIMV2-TRIGGERS-NEVER-RESET — MecanimV2 0.1.6 (source drop) never auto-resets triggers, although its docs promise it: transitions loop. It is a vendored-framework bug, patched in
MecanimUpdater.Update; the patch must survive every addon re-drop, and "clear triggers every frame in game code" was the misdiagnosis that preceded it. (a projectdocs/pitfalls/pawn-animation-latios-mecanimv2.md:48.)