diff --git a/.claude/skills/art-dev/SKILL.md b/.claude/skills/art-dev/SKILL.md index 46f2493f9..68e25999e 100644 --- a/.claude/skills/art-dev/SKILL.md +++ b/.claude/skills/art-dev/SKILL.md @@ -11,7 +11,7 @@ The spine: **size by complexity → ground → agree the change → setup scene ## Non-negotiables (every track) -1. **The material keystone ★ — no per-asset texturing, ever.** Every asset shares the ONE palette atlas (`Assets/_Project/Art/Palette/PaletteAtlas.png`) + emissive materials; color = *placing* a face's UV onto a colour band, never unwrap/paint. Three shader roles only: Lit-Palette (statics), Emissive-Gloam (glows — hue/flicker = gameplay info), Skinned-Palette (creatures/suits). This is the solo-dev keystone — deviating breaks batching, coherence, and the whole factory. Detail → [[Art_Direction_Lantern]] + `references/style-and-practices.md`. +1. **The material keystone ★ — no per-asset texturing, ever.** Every asset shares the ONE palette atlas (`Assets/_Project/Art/Palette/PaletteAtlas.png`) + emissive materials; color = *placing* a face's UV onto a colour band, never unwrap/paint. Three shader roles only: Lit-Palette (statics), Emissive-Gloam (glows — hue/flicker = gameplay info), Skinned-Palette (creatures/suits). This is the solo-dev keystone — deviating breaks batching, coherence, and the whole factory. **A kitbashed or AI-generated mesh arrives with its OWN baked texture — that clash IS the keystone violation; strip it and re-material to the shared palette + flat-shade before it ships** (§14). Detail → [[Art_Direction_Lantern]] + `references/style-and-practices.md`. 2. **Poly budgets + the readability law ★.** props ≤ ~800 tris · creatures ≤ ~3k · hero/boss/suit ≤ ~6k. Value-first (every warm/cold read carried by luminance + shape + flicker-cadence, not hue alone); reads at 200 px; silhouette-first. Report the tri count on every asset. 3. **The palette ontology ★.** warm gold/amber = true light (ours) · cold corpse-green/blue = gloam (counterfeit) · lure-red = bait · white-hot = conversion. Steady = true, flicker = false. Pick emissive hues to match what the asset *is*. 4. **Nudge-and-bake for precision ★.** Never script fine placement/pose/aesthetics blind (a script can't see painted/low-poly features + judges via a low-res grab). Stage + hand off to the operator's live GUI, then **bake the result back into a reproducible form** (bone-local offsets, saved transforms). See *The nudge-and-bake loop* below + [[blender-hybrid-nudge-and-bake]]. @@ -55,6 +55,7 @@ Establish the LANTERN working scene (snippets → `references/blender-cookbook.m ### Phase 4 — Build - **Model/kitbash (Auto):** primitives via `bmesh.ops`; faceted (flat shading); UVs *placed* on atlas cells (never unwrap); Synty base import via the **5.1 window-override + `ignore_leaf_bones=True, automatic_bone_orientation=False`** workaround; normalize to metres/feet-at-floor. Report tris vs budget. +- **AI-generate an iconic shape (Auto, last resort):** when scripted primitives can't nail an *iconic organic hard-surface* form (a Mark-V helmet, an ornate horn) and hand attempts read as golf-ball/egg/blob, **generate the base with Hyper3D Rodin** then **conform it** — it arrives as raw photoreal high-poly meter-scale clay that clashes with the flat low-poly body. The conform pass IS the work: clean-decimate to budget (watertight — weld+recalc FIRST, gentle collapse, 0 boundary edges), **drop the baked texture for the shared palette**, flat-shade, ontology-tie any emissive, and (rigid accessory) don't skin a generated mesh onto a cm-scale rig (~100× shrink → invisible). Full recipe → `references/blender-cookbook.md` §14. - **Precision / pose / silhouette (Guided) → the nudge-and-bake loop** (next section). - Iterate: for Guided, the operator can request modifications ("bigger", "move it", "add another there", "stage the vent for me") — treat each as a mini Auto or a new stage. Keep going until they're happy. diff --git a/.claude/skills/art-dev/references/blender-cookbook.md b/.claude/skills/art-dev/references/blender-cookbook.md index 26d28670e..bf291f299 100644 --- a/.claude/skills/art-dev/references/blender-cookbook.md +++ b/.claude/skills/art-dev/references/blender-cookbook.md @@ -268,7 +268,7 @@ The direction that landed (operator-approved): **the interest lives in the LIGHT To drop a posed suit/creature into the staging scene as a static mesh (skinned Rukhanka bake is separate `/dots-dev` work): in Blender — **apply the `Armature` modifier** on each skinned mesh (bakes the current pose into geometry), **`visual_transform_apply` + clear constraints** on bone-parented kit (eyes/lamp Child-Of), then export a static glb (meshes only, no armature). ⚠ both `modifier_apply` and `export_scene.gltf` need the **window `temp_override`** after `open_mainfile` (context.active_object). Import via `import_model_file`, `InstantiatePrefab` at the slot — glTFast carries the embedded materials (Synty atlas + emission). ``` -## 12. Skinned attachment kit → existing Unity rig (proven 07-16, Bathynaut dome/tank/lamp) +## 13. Skinned attachment kit → existing Unity rig (proven 07-16, Bathynaut dome/tank/lamp) Rigid accessories that must RIDE an already-in-engine Rukhanka rig (helmet, packs, lamps). Full failure-chain + Unity-side detail: gotchas archive 2026-07-16 + [[DR-052_SoD_Facing_Underwater_Feel]]. @@ -291,3 +291,47 @@ Unity side (`PlayerRigTools.AttachBathynautKit` / `GraftSmr` is the reference im - **`mesh.RecalculateTangents()` is mandatory** — a tangent-less procedural skinned mesh fails Rukhanka/BRG registration (`BatchMeshID not present`) and the WHOLE rig disappears. - Persist rebased meshes as `Rebased_*.asset` (Clear+refill an existing asset = GUID-stable re-runs). - Emissive pieces: `ProjectM/EmissiveGloamSkinned` (hand-written HLSL + Rukhanka `ComputeDeformedVertex`; the DOTS-instanced `_DeformedMeshIndex` block must be declared BEFORE the include, and the property must ALSO be in the Properties block for the baker's `HasProperty` validation). BRG-only: invisible in plain classic scenes, correct in the baked ECS world. + +## 14. AI-generated hero shape → conform to Synty (proven 07-24, Bathynaut Mark-V helmet) + +When scripted `bmesh` primitives can't nail an **iconic organic hard-surface shape** (a classic diving helmet, an ornate boss horn) — repeated hand attempts read as "golf-ball / egg / blocky robot" — **generate the base shape with Hyper3D Rodin** (`generate_hyper3d_model_via_text` → poll → `import_generated_asset`), then **conform it to the LANTERN/Synty style**. Generation is a valid *modeling* path (like a kitbash); the output is raw clay, NOT a finished asset — it arrives photoreal, high-poly, meter-scale, single-textured, and **clashes hard** dropped next to the flat-shaded low-poly body. The conform pass is the real work: + +**A. Watertight decimate — clean topology FIRST, then a GENTLE collapse.** A raw generated mesh has duplicate verts + loose geometry; an aggressive collapse (ratio ~0.10) on it **tears holes and shatters facets** (the operator will see it). Order matters: +```python +import bmesh +bm=bmesh.new(); bm.from_mesh(h.data) +bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.0006) # weld +bmesh.ops.recalc_face_normals(bm, faces=bm.faces) +loose=[v for v in bm.verts if not v.link_faces] +if loose: bmesh.ops.delete(bm, geom=loose, context='VERTS') +bm.to_mesh(h.data); bm.free() +d=h.modifiers.new("Dec","DECIMATE"); d.decimate_type='COLLAPSE'; d.ratio=0.26 # ~1k tris for a hero prop +dg=bpy.context.evaluated_depsgraph_get(); h.data=bpy.data.meshes.new_from_object(h.evaluated_get(dg)); h.modifiers.clear() +# HOLE DETECTOR: boundary edges (edges touching <2 faces). MUST be 0 = watertight. +bm=bmesh.new(); bm.from_mesh(h.data); holes=sum(1 for e in bm.edges if len(e.link_faces)<2); bm.free() +``` +23k→1.1k with 0 boundary edges reads as chunky Synty facets. If `holes>0`, the collapse was too aggressive for the cleanup — raise the ratio (gentler). Report tris vs budget. + +**B. Kill the photoreal texture — flat palette color is the biggest tell.** The single baked albedo/PBR map (painted weathering, smooth metal highlights) is what makes it look "AI dropped on low-poly." **Discard the imported material** and re-paint the mesh with the scene's **OWN shared palette materials** (assign by face region → guaranteed cohesion, literally the same material asset). Set **flat/faceted shading** (`for p in mesh.polygons: p.use_smooth=False`) so each plane reads as a facet. Region-assign by world-space geometry on the *decimated* mesh (fewer faces = cleaner colour blocks): +```python +h.data.materials.clear() +for m in (brass, gunmetal, glass): h.data.materials.append(m) # reuse the suit/scene's flat mats +mw=h.matrix_world; bm=bmesh.new(); bm.from_mesh(h.data); bm.faces.ensure_lookup_table(); bm.verts.ensure_lookup_table() +zs=[(mw@v.co).z for v in bm.verts]; zmin,zmax=min(zs),max(zs); zh=zmax-zmin +port=mathutils.Vector((0,-0.15,1.68)) # feature centre in WORLD space +for f in bm.faces: + c=mw@f.calc_center_median(); n=(mw.to_3x3()@f.normal).normalized(); vz=[(mw@v.co).z for v in f.verts] + s=0 # brass body (dominant) + if min(vz)>zmax-zh*0.085 or max(vz)