- Art pipeline recipes: the material foundation (palette atlas + 3 master shaders Lit-Palette/Emissive-Gloam/Skinned-Palette), statics recipe, skinned recipe (proven Rukhanka HUMANOID path), the 3 rig families (Drifter/Crawler/Strider), CreatureKit (A2) framing. Built on DR-022/023 + the proven Blender recipe; every carry-over gotcha flagged. - Currency re-mean: Ore/Aether/Biomass/Charge -> Flame/Lumen/Gloam/Loudness/Salvage/Chum/ Tallow with byte-id assignments; flags the fresh-saves-vs-migrate fork (Charge byte reuse). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
10 KiB
title, date, tags, status, permalink
| title | date | tags | status | permalink | |||||||
|---|---|---|---|---|---|---|---|---|---|---|---|
| LANTERN — Art Pipeline Recipes (Workstream A0 material + A1 statics/skinned) | 2026-07-13 |
|
draft-recipe | gamevault/06-roadmap/lantern-art-pipeline-recipes |
LANTERN — Art Pipeline Recipes
The operating manual the Art_Direction_Lantern 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)
Lit-Palette— statics. Samples the atlas by UV; vertex-colour tint; flat/near-flat lit. Cheap, batches.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.Skinned-Palette— deformation-aware (Rukhanka-skinned creatures/suits). This is the role the currentAnimatedLitShaderfills — a multi-target ShaderGraph withUniversalTarget+ 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:2on_BaseColor. A stock Unity-Per-Material graph makes anURPMaterialPropertyBaseColoroverride silently no-op (material-property-body-flash-rukhanka) — the DOTS-authoredAnimatedLitShaderhonours 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)
- Model in Blender to budget (props ≤ ~800 tris); place UVs onto atlas bands (no unwrap).
- 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).
- Collider policy: cosmetic decor → strip colliders, no
GhostAuthoring(classic-URP colliders are inert to the DOTS PhysicsWorld anyway). Gameplay-blocking geometry → anEnvironment-layer collider baked into the gameplay subscene (EG renders only baked entities; static collider, no Rigidbody → deterministic, no replication). ⚠ AMeshColliderbakes ONLY if the mesh has Read/Write enabled — else anInvalidOperationExceptionper bake and NO baked shape (the classic view lies; only a baked-CollisionWorldprobe catches it). PreferColliderFitTools(audit/apply) to fit convex hulls to the visual meshes. - Assign
Lit-Palette(orEmissive-Gloamfor glow props); one material, atlas-driven. - 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):
- Rig in Blender on a shared family armature (see §3). The rig is Humanoid.
- 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).
- Import
CreateFromThisModel— ⚠CopyFromOtherFAILS on Blender's extraArmaturenode. Optimize Game Objects OFF. - Muscle retarget the clips onto the player + creature rigs (humanoid muscle space).
- Rig bakes on the SAME entity holding the gameplay components the drive job reads —
Animator+RigDefinitionAuthoringon the root, SMRs flattened under it. ⚠ Skeleton-root = walk up from a bone to the model's direct child, NOTSkinnedMeshRenderer.rootBone(the bounds root — e.g. a head SMR's isSpine_03, which destroys the lower skeleton). - Material =
Skinned-Palette(deformation-aware, atlas-driven). ⚠ First Rukhanka bake is ~60 s, main- thread-synchronous (editor freezes — not a hang; blob caches after). - Prefab via
EnemyRigTools(GUID-preservingCopyAsset+ marker authoring — ⚠DeleteAsset+CopyAssetorphans subscene refs). Enemies = ownerless interpolated ghosts driven client-side byEnemyAnimationDriveSystem(remote-path velocity fromLocalTransformdelta). ⚠ The server runs Rukhanka unless stripped —ServerStripAnimationSystemdisables everyRukhanka.Runtimesystem 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-Palettematerial), - 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-Gloamparams (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