# Model Studio

Model Studio is the shell's generic viewer for portable object packages. It starts with the same authored people used at the beach. It does not contain a hard-coded scene for each model.

## Use the viewer

1. Open `shell/?scene=studio` over local HTTP.
2. Choose an **Available model**, then **Add** or **Replace selected**.
3. Select an instance in the viewport or **Selected object** list. Use **Frame selected** to centre it. The Camera panel selects orbit, pan or free look; scroll zooms. See CAMERA_AND_CONTROLLER.md for mouse, keyboard and Xbox mappings.
4. The inspector contains scale, rotation, material roughness and the selected model's own properties/actions. The side panel scrolls; model-specific controls are below the shared controls.
5. **Remove** releases the selected instance. An empty studio is supported. **Reset** returns to the starting person. Objects and edited values are session state, not a saved scene file.

Different objects keep their real sizes. The viewer frames the selected object automatically and places added models beside existing ones. The engine remains independent of this UI.

## Load a model made later

Copy the entire compatible package into `objects/my-model/` beside the other packages. In Studio, expand **Load another package**, enter `my-model`, and click **Load package**. Its models appear in the library; select one and click **Add** or **Replace selected**. No Studio source edits or catalogue changes are needed. A direct application-local path such as `../objects/my-model/object.json` also works.

The browser reads files served by the local HTTP server. This is a package loader, not a desktop file manager: it does not copy folders into the project or import arbitrary mesh formats. Future packages follow the contract below. Package registration lasts for the current page session; load a custom package again after a full reload. Built-in packages are registered automatically.

The developer example at `tests/fixtures/studio-object/` is a complete tiny package absent from the built-in catalogue. Load `../tests/fixtures/studio-object/object.json` to verify the extension path. It exposes a color property and quarter-turn action.

## Object-owned Studio contract, version 1

The existing `object.json` keeps its runtime entry and portable object protocol. Add:

```json
"studio": { "entry": "studio.js", "protocol": 1 }
```

`studio.js` exports a `models` array. Each definition has:

| Field | Meaning |
| --- | --- |
| `id`, `label` | Stable package-local identifier and user-facing model name |
| `create(api, context)` | Return a portable object, or a promise for it |
| `properties` | Optional array of editable property definitions |
| `actions` | Optional array of labelled actions |

The factory receives the engine API namespace. `context` contains an abort `signal`, `quality`, shared `wind`, flat `terrain`, `camera` and `cameraPosition`. Use local package imports and asset URLs relative to `import.meta.url`; do not import an engine path or shell file. This preserves independent object-folder portability.

The returned object supplies `root` (a Three.js Object3D), `properties`, `update(dt, time, context)`, `getBounds()` and `dispose()`. The usual portable-object serialize/restore methods remain available to other consumers. `getBounds()` returns finite world bounds. The factory must release allocations if it fails before returning. The viewer owns successfully returned objects, disposes replaced/removed instances, and disposes a late return after scene cancellation. Object disposal must also cover resources not attached to its visible mesh.

A property specifies `id`, `label`, `type`, `get(object)` and `set(object, value)`. Types are `number`, `boolean`, `select`, `color`. Numbers may declare `min`, `max`, `step`. Select options are strings or `{value, label}` entries; typed values such as booleans are preserved. Color values passed to setters are six-digit CSS hex strings. Setters must update the actual object through its API, not merely edit an unrelated metadata field. The UI validates numeric ranges and choices before invoking a setter.

An action specifies `id`, `label`, `run(object)`. Setters/actions may return promises. Parameterized methods can be exposed through properties plus an action, keeping their object-specific logic in the package. Engine time drives animation; mechanisms such as the jet canopy need animation resumed to travel to their commanded positions.

Example from the fixture:

```js
import {ExampleBlock} from './index.js';
export const models = [{
  id: 'block', label: 'Example block',
  create: api => new ExampleBlock(api),
  properties: [{
    id: 'color', label: 'Color', type: 'color',
    get: object => object.properties.color,
    set: (object, value) => object.setColor(value)
  }],
  actions: [{
    id: 'turn', label: 'Quarter turn',
    run: object => { object.root.rotation.y += Math.PI / 2; }
  }]
}];
```

## Loading and verification boundaries

Packages are trusted local application code. The loader accepts manifests within the current application folder and origin, rejects manifest redirects and escaping module entry paths, and requires protocol 1. It does not sandbox the implementation of a package the application owner supplies. Built-in package modules and dependencies remain subject to the existing folder-closure checks.

`shell/lib/model-library.js` is the single deliberate dynamic-import exception in `tools/verify.mjs`, needed to load future packages. The exception is limited to its named import boundary. Other computed imports remain audit failures. Five Node checks cover validation/registration; `tests/studio.html` exercises real WebGL loading, actions, replacement, failures and late cancellation, including the uncatalogued fixture.

Camera pan, fixed-position free look and standard Xbox-controller navigation are available in 0.2.2. Model-library state remains independent of camera mode.

## Hair color

Select a Person, then scroll to **Model Properties & Actions**. **Hair color** is directly below **Hairstyle**. The color picker updates the hair and eyebrows together. It retains the chosen style and all other person settings. The starting person now has brown hair; gray, black, blond and other colors are selectable. Bald remains hairless while its eyebrow color can change.
