--- title: LANTERN — Art Pipeline Recipes (Workstream A0 material + A1 statics/skinned) date: 2026-07-13 tags: [roadmap, lantern, art, blender, pipeline, workstream-a, recipes] status: draft-recipe permalink: gamevault/06-roadmap/lantern-art-pipeline-recipes --- # LANTERN — Art Pipeline Recipes > The **operating manual** the [[Art_Direction_Lantern|A0 constitution]] hands off to. Workstream A of > [[Roadmap_Lantern_Slice]]: the material decision made concrete (A0) + the statics/skinned recipes (A1) + the > three rig families + the CreatureKit framing (A2). Built on the project's **proven** animation pipeline > ([[DR-022_Animation_Pipeline_Rukhanka_Synty]] / [[DR-023_Enemy_Animation_MonsterMash]] + the machine-local > Blender recipe). The product isn't assets — it's a **repeatable factory** where creature N+1 is a sitting. > > **Checklist-grade by design** — this project runs on distilled gotchas; the art pipeline gets the same > treatment. Every ⚠ is a carry-over hazard already paid for in blood. ## 0. The material foundation (A0 keystone — no per-asset texturing, ever) **One palette atlas + vertex colours under three master shaders. Every asset shares ~3 materials.** ### The palette atlas - A single small texture (e.g. 256×256) of flat colour **bands** — the whole game's palette. UVs are *placed* onto a band (island = a quad snapped to a colour cell), **never unwrapped/painted** (Imphenzia method). - Encode the ontology as bands: warm gold/amber (light/ours), cold corpse-green/blue (gloam), lure-red (bait), white-hot (conversion), plus neutral greys for hard-surface. - ⚠ **Import the atlas LINEAR, not sRGB** — the ground bundle learned this: an sRGB-imported detail/colour map used in a multiply darkens ~2.3×. Palette atlas = colour source → sRGB is usually right for a base-colour map, but any map used as a MULTIPLY/detail must be LINEAR. Verify the actual look in Play, not the importer. - Generatable procedurally (deterministic, no artist eyes needed for v1): a script paints N bands into a PNG. ### The three master shaders (author in ShaderGraph, multi-target `UniversalTarget`) 1. **`Lit-Palette`** — statics. Samples the atlas by UV; vertex-colour tint; flat/near-flat lit. Cheap, batches. 2. **`Emissive-Gloam`** — the palette rule as a shader. Parameterized **hue · flicker-cadence · intensity** emissive over the atlas base. One shader is the whole "true vs false light" language: steady = true, flicker = false (the load-bearing readability channel). Every glow in the game is this shader. 3. **`Skinned-Palette`** — deformation-aware (Rukhanka-skinned creatures/suits). This is the role the current `AnimatedLitShader` fills — a multi-target ShaderGraph with `UniversalTarget` + the deformation nodes so Entities-Graphics GPU skinning works. ⚠ Stock URP/Lit renders a skinned mesh **unskinned static** + a "does not support skinning" warning — the deformation-aware graph is mandatory for anything Rukhanka drives. - ⚠ **Author all three Hybrid-Per-Instance** if we want per-instance gloam-tint variants (the free variant axis): ShaderGraph `overrideHLSLDeclaration:true` + `hlslDeclarationOverride:2` on `_BaseColor`. A stock Unity-Per-Material graph makes an `URPMaterialPropertyBaseColor` override **silently no-op** ([[material-property-body-flash-rukhanka]]) — the DOTS-authored `AnimatedLitShader` honours it; a stock Synty prop graph does not. - ⚠ Keep the three deformation/palette graphs in `_Project/Shaders/` (GUID-preserved), not in any imported sample tree (importing Rukhanka "Animation Samples" drags in 26 subscenes + world-running sample systems + a TMP conflict — the archive documents the extraction). ## 1. Statics recipe (target: minutes per prop) 1. **Model** in Blender to budget (props ≤ ~800 tris); place UVs onto atlas bands (no unwrap). 2. **Export** FBX (skinned-capable) or glb via glTFast (statics). ⚠ **Apply Scalings = FBX Units Scale**; realize any geometry-node instances before export (geo-nodes don't survive the exporter). 3. **Collider policy:** cosmetic decor → **strip colliders**, no `GhostAuthoring` (classic-URP colliders are inert to the DOTS PhysicsWorld anyway). Gameplay-blocking geometry → an `Environment`-layer collider baked into the gameplay **subscene** (EG renders only baked entities; static collider, no Rigidbody → deterministic, no replication). ⚠ A `MeshCollider` bakes ONLY if the mesh has Read/Write enabled — else an `InvalidOperationException` per bake and NO baked shape (the classic view lies; only a baked-`CollisionWorld` probe catches it). Prefer `ColliderFitTools` (audit/apply) to fit convex hulls to the visual meshes. 4. **Assign `Lit-Palette`** (or `Emissive-Gloam` for glow props); one material, atlas-driven. 5. **Bake** into the subscene; verify in the baked CollisionWorld + a Play screenshot (⚠ a dark render masks material bugs — check shader property *values*, `GetPropertyType`-guarded). ## 2. Skinned recipe (creatures + suit-frames) The proven Rukhanka path — **HUMANOID rig pipeline** (muscle clips retarget across rigs; the old "Generic" note was wrong): 1. **Rig** in Blender on a shared family armature (see §3). The rig is **Humanoid**. 2. **Per-action clips** — one FBX per action. ⚠ **Key All Bones + Force Start/End keys + FBX Units Scale**; **root motion baked into the pose** (CC owns the transform → root motion OFF on import). ⚠ Base any overlay/ partial anim on a **full-body pose** — a clip keying only a bone-subset collapses the un-keyed bones (mesh sinks) ([[rukhanka-partial-clip-collapse]]). 3. **Import** `CreateFromThisModel` — ⚠ **`CopyFromOther` FAILS** on Blender's extra `Armature` node. Optimize Game Objects **OFF**. 4. **Muscle retarget** the clips onto the player + creature rigs (humanoid muscle space). 5. **Rig bakes on the SAME entity holding the gameplay components** the drive job reads — `Animator` + `RigDefinitionAuthoring` on the **root**, SMRs flattened under it. ⚠ **Skeleton-root = walk up from a bone to the model's direct child**, NOT `SkinnedMeshRenderer.rootBone` (the bounds root — e.g. a head SMR's is `Spine_03`, which destroys the lower skeleton). 6. **Material = `Skinned-Palette`** (deformation-aware, atlas-driven). ⚠ First Rukhanka bake is **~60 s, main- thread-synchronous** (editor freezes — not a hang; blob caches after). 7. **Prefab** via `EnemyRigTools` (GUID-preserving `CopyAsset` + marker authoring — ⚠ `DeleteAsset+CopyAsset` orphans subscene refs). Enemies = ownerless interpolated ghosts driven client-side by `EnemyAnimationDriveSystem` (remote-path velocity from `LocalTransform` delta). ⚠ The **server runs Rukhanka unless stripped** — `ServerStripAnimationSystem` disables every `Rukhanka.Runtime` system server-side (matched by assembly name). ## 3. The three rig families (the amortization decision — clips per FAMILY, not per creature) Every slice creature maps onto one shared armature so **clips are made once per family**: | Family | Body plan | Slice members | Clip budget (4–5) | |---|---|---|---| | **Drifter** | spine-chain swimmer | Wisp-Choir motes, lure-fish (later: serpents/rays) | swim-idle · dart · attack · death-sink | | **Crawler** | legged/scuttling floor unit | Grindylow (later: kappa-kin, Tenders) | skitter · leap · attack · death | | **Strider** | biped-ish | Drowner, Wrecker, **and the suit-frames** | walk · windup · attack · death | Per-creature **attack accents only** where identity demands (a Wrecker's cast, a boss phase). Suit-frames: A1 pass = **kitbash on the existing humanoid (Strider) rig** (helmet/tank/plate swaps over the proven player rig); the custom Wick silhouette sculpt is an A3 upgrade, not a Phase-1 blocker. ## 4. Procedural assists (geometry nodes + blendermcp scripting) - **Geometry nodes** for glow-gardens / coral / tube-worm / wreck families — parametric families, not hand- modeled instances. ⚠ Realize instances before export. - **blendermcp scripts** for the repetitive steps (scripted posing → per-action FBX export; batch import/ material-assign). The Blender MCP is live this machine; the proven combat-clip recipe is scripted end-to-end. ## 5. CreatureKit (A2 — the factory tool; spec, built in Phase 2) **One editor tool or hard checklist that takes a Blender export → a play-ready enemy.** The measure: adding enemy **N+1 touches zero undocumented steps.** It must yield, from one export: - the prefab (rig + SMRs on root, `Skinned-Palette` material), - ghost/network authoring (ownerless interpolated `GhostAuthoringComponent` + `LinkedEntityGroup`), - `Environment`/hit colliders, - a tuning-table row (Health/speed/windup/attack-range — bytes, not enums, for Burst), - spawn-table + bestiary hooks (the kind-indexed director roster — ⚠ directors index the prefab buffer by **Kind**, not round-robin; a stale round-robin pool silently mis-maps — [[enemy-director-kind-indexed-roster]]). - **Variant policy (cheapest content first):** palette-shift / gloam-tint / scale / part-swap of an existing mesh BEFORE any new mesh. One Drowner mesh → 3 visual tiers via the `Emissive-Gloam` params (which are *also* gameplay information). **VFX-first identity:** emissive + a dynamic light + silhouette + animation carry a creature in the dark; mesh detail is the last 20%. ## Throughput rules (solo sanity) Timebox per class — a prop is a morning, a creature is days-not-weeks, the boss is the exception. The done-bar is *"reads at gameplay zoom, under murk, in motion"* — never render-portfolio quality. When stuck, ship the placeholder and log the upgrade. ⚠ For skinned-mesh visual bugs, **use the operator's eyes** — `WorldRenderBounds`/ bone reads return the bind AABB, not the deformed pose ([[rukhanka-partial-clip-collapse]]). ## Related - [[Art_Direction_Lantern]] — the A0 constitution (this is its operating manual) - [[Roadmap_Lantern_Slice]] — Workstream A gates (A0→A1→A2→A3) - [[DR-022_Animation_Pipeline_Rukhanka_Synty]] · [[DR-023_Enemy_Animation_MonsterMash]] — the proven pipeline - [[Synty_Asset_Inventory]] — placeholder quarry during the transition