# Canonical 3D contract

AsukaDesigner now has one 3D state format.

## Scene

```ts
interface Asuka3DSceneState {
  products: Asuka3DProduct[];
  selectedProductId?: string | null;
  selectedPartId?: string | null;
  camera?: Asuka3DCameraState;
  materials?: Record<string, Asuka3DMaterialState>;
  decorations?: Asuka3DDecoration[];
  printAreas?: Asuka3DPrintArea[];
  activePrintAreaId?: string | null;
  selectedObjectId?: string | null;
  selectedMaterialId?: string | null;
}
```

## Product

```ts
interface Asuka3DProduct {
  id: string;
  catalogId?: string;
  name?: string;
  model?: { url: string; format?: 'glb' | 'gltf' | string };
  parts?: Asuka3DPart[];
  variationId?: string;
  variationName?: string;
  variationPath?: Array<{ id?: string; name?: string }>;
  variationPathLabel?: string;
  partVariationIds?: Record<string, string>;
  transform?: {
    position?: { x: number; y: number; z: number };
    rotation?: { x: number; y: number; z: number };
    scale?: { x: number; y: number; z: number };
  };
  meshIds?: string[];
  metadata?: Record<string, unknown>;
}
```

## Rules

- The scene product collection is always `products`.
- A placed product id is always `product.id`.
- A catalog reference is always `product.catalogId`.
- A source model is always `product.model` in scene state and `element.product3d.model` in catalog state.
- The active product is always `selectedProductId`.
- Selecting a product as a transformable object uses `selectedObjectId: "product:<id>"`.
- `selectProduct(id, { selectObject: false })` changes only the active product context and clears object selection.
- Part selection is always `selectedPartId`.
- Per-part variations are always `product.partVariationIds`.
- Print-area modes are only `texture` and `surface`.
- Engine configuration is only `engines['3d']`.
- A view may provide only `view.scene3d`.
- History tracks own their `viewId`; scene snapshots do not.
- Removed names are rejected. No migration or fallback is performed.

The runtime helpers `assertCanonical3DSceneState()`, `create3DSceneState()`, `select3DProduct()`, `select3DPart()`, and `select3DObject()` enforce this contract.
