# Shareable Animation State Milestone 4 now includes a deterministic URL-state codec for animation and procedural-machine viewer state. The goal is to make links safe to open, stable to diff in tests, and compact enough for “copy link” UI without trusting arbitrary query-string values. ## Import ```ts import { createShareableAnimationSearchParams, decodeShareableAnimationState, encodeShareableAnimationState, } from '../src/animations/shareableAnimationState'; ``` ## Encoded fields | Field | URL key | Notes | | --- | --- | --- | | Schema version | `v` | Current version is `1`. Future versions are decoded conservatively with a warning. | | Machine | `m` | Procedural machine id such as `wankel-rotary-engine`. | | Animation | `a` | Optional animation module id when a machine exposes multiple animations. | | Time | `t` | Animation time in seconds, clamped to a safe non-negative range. | | RPM | `rpm` | Clamped by configurable limits; default max is `12000`. | | Time scale | `s` | Default safe range is `0.05–4`. | | Playback | `play` | `1` for playing, `0` for paused. | | Loop | `loop` | Boolean. | | Step-through mode | `step` | Boolean. | | Reduced motion | `rm` | Boolean user preference/state override. | | Exploded view | `exp` | Default normalized range is `0–1`. | | Render mode | `mode` | `solid`, `wireframe`, `xray`, or `cross-section`. | | Selected component | `part` | Component id for details/highlight panels. | | Guided tour | `tour`, `tourStep` | Tour id and zero-based step index. | | Camera preset/vector | `cam`, `pos`, `target`, `zoom` | `pos` and `target` are comma-separated xyz triples. | | Hidden components | `hide` | URI-escaped list. Duplicates are removed and ids are sorted. | | Isolated components | `iso` | URI-escaped list. Isolation wins over hidden-state conflicts. | | Component opacity | `op` | URI-escaped `componentId:opacity` entries. Opacity is clamped to `0–1`. | | Labels | `labels` | Boolean label visibility. | | Cross-section cut | `cut` | Encoded as `axis:offset:inverted`, for example `z:0.15:1`. | ## Basic usage ```ts const query = encodeShareableAnimationState({ machineId: 'planetary-gearbox', rpm: 120, playing: false, exploded: 0.35, renderMode: 'xray', camera: { preset: 'cutaway', }, hiddenComponents: ['carrier', 'ring-gear'], }); // Example output: // v=1&m=planetary-gearbox&rpm=120&play=0&exp=0.35&mode=xray&cam=cutaway&hide=carrier%2Cring-gear ``` To read a link: ```ts const decoded = decodeShareableAnimationState(window.location.href, { knownMachineIds: ['four-stroke-petrol-engine', 'planetary-gearbox'], }); if (decoded.warnings.length > 0) { console.info('Some shared viewer-state fields were ignored or clamped', decoded.warnings); } const state = decoded.state; ``` The decoder accepts plain query strings, full URLs, `URLSearchParams`, record objects, and hash-routed SPA links such as: ```txt /#/procedural-demo?m=geneva-drive&rpm=60&step=1 ``` ## Updating an existing URL Use `createShareableAnimationSearchParams` when a route already has unrelated campaign, lesson, or navigation parameters. It removes previous animation-state keys and preserves everything else. ```ts const params = createShareableAnimationSearchParams(window.location.search, { machineId: 'centrifugal-pump', rpm: 850, playing: true, }); window.history.replaceState(null, '', `${window.location.pathname}?${params.toString()}`); ``` ## Safety behavior The codec is intentionally forgiving: - non-finite numbers are ignored; - extreme values are clamped to safe limits; - unknown ids can be rejected with `knownMachineIds`, `knownComponentIds`, `knownTourIds`, and related allow-lists; - duplicate component ids are deduplicated; - a component listed as both hidden and isolated is kept isolated and removed from the hidden list; - reserved object keys such as `__proto__` are rejected before opacity maps are materialized. Warnings are returned as structured objects so UI can show a non-blocking “link partially restored” message without crashing the viewer.