# Machine Authoring Playbook

This guide describes how a contributor adds a new machine to Mechanica without touching catalogue, navigation, or core viewer code.

## 1. Choose stable identifiers

Use a lowercase kebab-case machine ID:

```ts
id: 'radial-engine'
```

Part IDs must also be kebab-case and unique within the machine:

```ts
parts: [
  { id: 'crankcase', ... },
  { id: 'master-rod', ... },
  { id: 'cylinder-bank', ... },
]
```

These IDs are part of shareable URLs, localStorage preferences, guided tours, animation bindings, and scene hit-testing. Do not rename an ID after release; add aliases in URL parsing if a rename is unavoidable.

## 2. Add data in the registry data layer

Core machines live in:

```text
src/modules/machines/data/coreMachines.ts
```

Stretch machines should be added in a sibling file when their milestone begins:

```text
src/modules/machines/data/stretchMachines.ts
```

Then export them through `src/modules/machines/data/index.ts` and merge them in `src/modules/machines/registry.ts`.

A complete machine entry includes:

- `id`, `slug`, `title`, `subtitle`, `description`
- `category`, `difficulty`, `complexity`, `releaseOrder`
- `tags`, `keywords`
- `facts`
- `parts`
- `guidedTour`
- `related`
- `animation`
- `asset`
- `learningObjectives`
- `seo`

## 3. Define parts for every selectable component

Every visible/selectable engineering component should have a part definition:

```ts
part(
  'sun-gear',
  'Sun gear',
  'Central external gear meshing with all planets.',
  'rotating',
  [0, 0, 0.35],
)
```

Part definitions drive:

- left sidebar component lists
- hover labels
- click selection detail drawer
- opacity and visibility controls
- exploded-view offsets
- guided tour focus
- animation target bindings
- future GLB node mapping

Guidelines:

1. Keep part IDs stable.
2. Use engineering names, not mesh names.
3. Put manufacturing or material details in `material` and `specs`.
4. Use `explode` as a semantic separation direction, not a final transform.
5. Mark non-physical overlays as `sensor` or `annotation` roles.

## 4. Add animation metadata first

Even before the animation module is implemented, define the descriptor:

```ts
animation: {
  modulePath: 'src/animations/machines/radialEngine.ts',
  defaultRpm: 900,
  minRpm: 200,
  maxRpm: 2400,
  cycleSeconds: 0.1333,
  cycleType: 'hybrid',
  loopMode: 'seamless',
  stepCount: 9,
  drivenParts: ['crankshaft', 'master-rod', 'piston-set'],
  notes: ['Master rod controls articulated rod motion.'],
}
```

The animation module must later satisfy `MachineAnimationModule` from `src/animations/types.ts`. The core player will call only the standard controller methods:

- `update(clock)`
- `seek(cycleProgress)`
- `step(direction)`
- `reset()`
- `dispose()`

## 5. Add scene implementation without coupling the viewer

A machine scene adapter must satisfy `MachineSceneAdapter` from `src/three/sceneContracts.ts`.

The adapter receives:

- the machine definition
- quality tier
- display state
- camera state
- reduced-motion preference

The adapter must not own global UI state. It should expose part bindings so the viewer can map pointer hits back to registry part IDs.

## 6. Prepare assets through the asset descriptor

During early implementation, use:

```ts
asset: {
  strategy: 'procedural',
  status: 'procedural-placeholder',
  ...
}
```

When a production GLB is ready, update to:

```ts
asset: {
  strategy: 'gltf',
  status: 'shipped',
  url: '/assets/machines/radial-engine/radial-engine.glb',
  draco: true,
  ktx2: true,
  ...
}
```

Required asset handoff contents:

```text
public/assets/machines/<machine-id>/
├── <machine-id>.glb
├── manifest.json
├── preview.webp
└── source/
    ├── README.md
    └── attribution.md
```

The GLB node naming convention should include part IDs where practical:

```text
part__crankshaft
part__piston-set__instance_001
annotation__tdc-marker
```

## 7. Add guided tour steps

Guided tours are content, not imperative code. Each step should focus existing part IDs:

```ts
{
  id: 'compression',
  title: 'Compression stroke',
  body: 'The piston rises with both valves closed, raising charge pressure and temperature.',
  focusPartIds: ['piston', 'intake-valve', 'exhaust-valve'],
  cameraPreset: 'front',
  durationSeconds: 6,
}
```

Tour rules:

1. Focus one to four parts per step.
2. Keep captions under two short sentences.
3. Use camera presets instead of hardcoded camera vectors unless a custom camera move is required.
4. Pair tour steps with animation cues only when the animation module supports them.

## 8. Update related-machine links

Related machines should form useful learning paths:

- same principle, different implementation
- upstream/downstream system relationship
- simpler prerequisite mechanism
- advanced follow-up machine

Example:

```ts
related: ['slider-crank', 'four-stroke-petrol-engine', 'piston-pump']
```

## 9. Validate registry integrity

Run the validation utility during development or add it to a future test script:

```ts
import { assertValidMachineRegistry } from './src/modules/machines/validation';

assertValidMachineRegistry();
```

The validator checks:

- duplicate machine IDs
- invalid categories and difficulty values
- invalid part opacity
- duplicate part IDs
- animation references to missing parts
- guided tour references to missing parts
- self-referential related links
- shipped assets without URLs

## 10. Authoring review checklist

Before opening a pull request:

- [ ] Machine ID is stable, lowercase, and kebab-case.
- [ ] Category and difficulty are from the schema constants.
- [ ] At least four engineering facts are present.
- [ ] Every important component has a part entry.
- [ ] Part descriptions are educational, not placeholder text.
- [ ] Guided tour focus IDs all exist.
- [ ] Animation `drivenParts` all exist.
- [ ] Related machine IDs are valid.
- [ ] Asset descriptor accurately states procedural vs GLTF status.
- [ ] New files do not import undeclared dependencies.
- [ ] Catalogue UI works without changes to core components.
