feat(paint-slots): authored item materials + unified slot painting

Phase 1 + paint unification of the paint-slots plan.

- core: scene-material data layer (materials map mirroring collections,
  undo/partialize/setScene full-graph support), SceneMaterial schema,
  scene:/library: MaterialRef helpers + parseMaterialRef, slot id helpers
  (deriveSlotId/slotLabelFromId), slots map on ItemNode, hitObject on
  PaintResolveArgs, optional PaintCapability.commit.
- viewer: resolveMaterialRef (library:/scene: -> three material, null on
  dangling).
- nodes(item): renderer keeps authored GLB materials for slot-authored
  assets and applies per-slot overrides per-instance (never mutates the
  shared cached GLB); textures-off still collapses to furnishing role;
  non-authored items unchanged. Item paint capability + registration.
- editor: item joins the unified (nodeId, slotId) paint dispatch; item
  paint target + slot reset.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Wassim SAMAD
2026-06-15 10:01:08 -04:00
co-authored by Claude Fable 5
parent cf24b62c44
commit 7afb286e47
16 changed files with 627 additions and 78 deletions
+10
View File
@@ -1126,6 +1126,14 @@ export type PaintCapability = {
* `role`. Returned partial is merged into the node by the editor.
*/
buildPatch: (args: PaintPatchArgs) => Partial<AnyNode>
/**
* Optional: fully own the click-commit instead of the default
* `updateNode(node.id, buildPatch(...))`. Kinds whose commit has a side
* effect (items create a scene material for one-off colours, then store a
* `scene:<id>` ref) implement this; kinds that just patch the node omit it.
* Must perform its mutations as a single undo step.
*/
commit?: (args: PaintPatchArgs) => void
/**
* Apply a preview to the kind's registered mesh subtree at
* `role`. The kind builds whatever preview material(s) it needs
@@ -1169,6 +1177,8 @@ export type PaintResolveArgs = {
localPosition?: readonly [number, number, number]
/** Optional: name of the three.js object that received the hit. Stair uses this. */
hitObjectName?: string
/** Optional: the three.js object that received the pointer hit. Items read userData.slotId off it. */
hitObject?: Object3D
}
export type PaintPatchArgs = {