mechanism · git:20261025.66a67b5 · 2026-10-25 · sha256 76462b1418150f67
mechanism git:20261025.66a67b5A
Immutable. This exact content never changes and is served at /api/v1/blob/76462b1418150f67.
---
name: mechanism
description: Build an inspectable model of a real object from the evidence (spec sheets, teardowns, official photos, published test vectors), take it apart, and render a film with designed sound, an interactive viewer and an STL/GLB. Every number is tagged SPEC, MEASURED, DERIVED or ESTIMATED, and a spec whose numbers do not add up is not rendered. Use for exploded views, teardowns, comparisons and explainers of real products or machines (gear trains, key switches, cipher machines, phones, boards). Triggers on "exploded view of X", "model X exactly", "compare X and Y", "make a video of how X works", "/mechanism".
---
# Mechanism
Model a real object (hardware or software) from evidence, check the model against what is published, and film it. The point is not a pretty render: it is that every number says where it came from and that a model whose numbers do not close is not rendered.
## Quick start (the eight steps)
1. **Research.** Open the maker's sheet, a teardown, a standard, photos. Keep every URL and the date you opened it. Note what a page refused to show.
2. **Start the object in the user's project** (never in this folder): `node scripts/new.mjs <name> --dir <work> --title "..." --fig "Fig N"` (for example `--dir ./mechanism-work`).
3. **Write `<work>/<name>/spec.js`.** Copy the example closest to the object (see `references/examples.md`; `examples/hub`, `switch`, `lock`, `rtx4090`, `winrar`) and tag every number SPEC, MEASURED, DERIVED or ESTIMATED.
4. **Gate:** `node scripts/check-spec.mjs <work>/<name>/spec.js` until it says PASSES and the verdict says VERIFIED. Prefer checks that measure the drawn model or cross two sources; a unit conversion is reported but does not count (see Verdicts).
5. **Lint the film, then look:** `node scripts/lint-film.mjs <work>/<name>/index.html` renders one draft frame per phase and per tour stop and says EMPTY (nothing in the shot: a see-through plate, an orbiting part, an object off frame; exit code 1), LOOK (little in the middle: open the PNG it names and judge it) or SMALL (raise `director.zoom`). Fix every EMPTY and judge every LOOK, then open `?explore`. A tour stop aims where the part was assembled, so a part that orbits or moves (a planet gear, a lifted pin) needs `tourDim` or no stop. Then run the preflight on both cuts: `node scripts/preflight.mjs <work>/<name>/index.html --length short` and again without `--length`. It steps through the whole timeline without rendering an MP4 and reports geometry and text: FAIL for a camera inside a part, WARN for geometry inside the camera's near plane, a tour part or the object mostly out of the frame or spilling past both sides, and texts that overlap or run past the edge. Fix every FAIL. For each WARN open the frame (`node scripts/render.mjs <page> --length short --still <t> --out frame.png`) and decide: a real defect (fix it), a deliberate choice (a close-up, a part that flies out, a panel that slides in) or a false positive. Keep the ones you leave and say so in the final report. It exits 0 unless `--strict`; do not tune the spec only to silence a warning. Fixing a defect you can see in the frame (a dark-on-dark part you cannot read, a window that is not cut) is not tuning: say what you saw and fix it. The gate checks numbers, not shape; the preflight also checks that flat parts (`plate`, `profile`) cover their outline minus their holes (`GEOMETRY_AREA_MISMATCH`: FAIL above 5 %), but a mesh can be wrong in ways no check sees, so open a frame of every part you cut holes in. A part with `cutaway` fades while the mechanism runs: anything that sits on it (a dial, a pointer, a label) needs the same `cutaway` or it floats alone in the air when the part fades; no check can tell that from a part that is meant to stay, so look at a frame from the phase where the shell fades.
6. **Recognisable?** Run the stranger test (see Recognisable) and record the answer, even if it fails. If the judges do not name the object, the film does not explain it: see "Explain what it is first".
7. **Render:** the short cut first (`--length short`: a 30 s film that takes 1.5 to 4 minutes of wall time on a laptop GPU; run it in the background), then the normal one, then `--export glb`. Put everything next to the spec.
8. **Report** what you did in the format under "Final report". Say what is estimated and what failed.
## Setup
This skill is a toolkit as well as instructions, so **run everything from the folder that contains this SKILL.md**: `cd` to it first (cloned as a skill, that is `~/.claude/skills/mechanism`; installed as a plugin, it is `${CLAUDE_PLUGIN_ROOT}`). It needs Node 22+, Edge or Chrome, and `ffmpeg` (plus `uv` only to measure photos). There is no install step. **Work in the user's project, never inside this folder** (an agent cannot write under `.claude/skills/`): pick a folder such as `./mechanism-work` (call it `<work>`). A new object starts with `node scripts/new.mjs <name> --dir <work> --title "..." --fig "Fig N"`, which creates `<work>/<name>/` (page, `run.js` and a starter `spec.js` that FAILS the gate until every TODO is replaced). Renders go next to it, for example `--out <work>/<name>/<name>.mp4`. The scripts themselves are run from this folder.
Output for one subject: an MP4 (4:5, 9:16 or 1:1, 1080 wide, stereo, in the `dossier` look), as a short cut of about 30 s (share this one), a normal cut of 90 to 140 s and an extended one; an interactive `index.html?explore`, an STL/GLB of the model, and the data files that justify every number. A normal cut renders in about 2 to 3 seconds per second of film on a machine with a discrete GPU (the scripts ask for it); the short cut takes about a minute.
Two routes. **The declarative route is the default**: write one `spec.js` (parts with real dimensions, evidence, mechanism) and the engine does the scene, the kinematics, the camera, the sound and the film. Use the hand-built route (`examples/phones/scene.js`) only for products that need bespoke look, such as phone back covers with measured camera islands.
## Core rules
1. **Search before you model. Never invent a shape you could have looked up.** The user will notice a generic model immediately. Two phones that share one template are a failure.
2. **Look at the pictures.** `WebFetch` returns text only, but you can `curl` image files and open them with `Read`. Official sources worth checking, in this order:
- the maker's spare-parts site (Xiaomi: `spare-parts.mi.com/products/<model>-<part>`) for photos of the real back cover, display module with frame, boards, cameras, speakers;
- GSMArena `pictures` pages (`fdn2.gsmarena.com/vv/pics/<brand>/<slug>-N.jpg`: front, back and side views aligned) and review photos (`fdn.gsmarena.com/imgroot/reviews/...`);
- teardown articles and videos' hero photos, the maker's spec page and FAQ (battery cells, construction), phone-case fit guides.
3. **Measure, do not eyeball.** Use the published body size as the scale (`mm per px = published size / silhouette size`), check that x and y scales agree (within ~2%), then measure with OpenCV: silhouette, corner radius fit, Hough circles for lenses, rectangles for boards. Scripts live in `data/phones/ref/measure*.py`, run with `uv run --with opencv-python-headless --with numpy python ...`.
4. **Cross-check every derived number against a second published one.** Examples that already work: display area from diagonal + resolution aspect, checked against the published area and the ppi; sensor size from MP x pixel pitch, checked against the optical format (16 mm per inch-type for large sensors, 18 mm for small ones); entrance pupil = real focal length / f-number, real focal = 35 mm equivalent x sensor diagonal / 43.27.
5. **Tag everything.** SPEC (published), MEASURED (measured on an official photo, say the error), DERIVED (formula plus cross-check), ESTIMATED (nobody publishes it: draw it dashed and say so). Never present an estimate as fact. Say when sources conflict and which one you used.
6. **When two objects are compared, model each one as itself.** Corner radius, frame profile, back material, camera island, buttons, ports, display technology and interior all come from that object's own evidence. Same scale, different design.
7. **Say what is missing.** If there is no interior photo, say so in the video and keep that interior estimated. Ask the user for a photo only after you have searched.
## The declarative route (spec -> film)
1. **Research first, into the spec.** Search, download and look at pictures, measure (rules above). Then write `<work>/<name>/spec.js` as `window.SPEC = {...}`; copy `examples/hub/spec.js` (a three-speed planetary hub) as the template. Fields: `research.sources` (title, url, fetched date, note about what the page refused), `research.claims` (text, `conf`, `src`, and `cross` for DERIVED, `error` for MEASURED, `why` for ESTIMATED), `parts` (kind, real dimensions in mm, material, position, explode vector, `body`, `orbit`, `claims`, `info`, `cutaway`), `mechanism` (links between bodies, `drive.modes` with the ratio the maker publishes), `director` (optional tour order, durations, views).
2. **Gate: `node scripts/check-spec.mjs <work>/<name>/spec.js`.** It fails (exit 1) when a number has no source, a DERIVED claim has no cross-check, a MEASURED one no error, an ESTIMATED one no reason; when two meshing gears do not sit at the centre distance their module and teeth demand; when the teeth do not interleave; or when a mode of operation does not give the ratio the maker publishes. Fix the model or the claim, never the check. Do not render a spec that does not pass.
3. **Build the page**: `node scripts/new.mjs <name> --dir <work>` creates `<work>/<name>/` (page, `run.js`, starter `spec.js`); the script list is fixed and `spec.js` is the only thing you edit.
4. **Look before rendering**: `node scripts/probe.mjs "<work>/<name>/index.html?explore" <work>/<name>/probe` (mouse simulation) and `node scripts/render.mjs <page> --still 9 --blur 1` at the assembly, power-path, tour and panel times (`window.__specInfo().phases` lists them).
5. **Render**: `node scripts/render.mjs <work>/<name>/index.html --blur 3 --out <work>/<name>/<name>.mp4` (duration comes from the spec). Variants: `--theme night`, `--aspect 9:16`, a clip with `--from 13 --to 21`, and the model with `--export stl --e 1` or `--export glb` (`--e` is the explode factor, 0..1).
## Running, judging and reporting
**Processes.** A render takes minutes: start it with your tool's own background mode (not `nohup ... &` inside a one-shot shell: on Windows it can die with the shell) and poll its log. **Never stop processes by name** (`taskkill /IM node.exe`, `pkill node`, `killall`): the user's machine runs other Node programs (dev servers, MCP servers) and that kills them all. Keep the PID of what you started and stop only that one (`taskkill /PID <pid> /T`). Do not start a second render of the same file while the first is running; wait for it or stop that PID. A foreground call is cut off after about 10 minutes and the render then looks frozen, so start every render in the background from the beginning. The last line of a render says both numbers: `done: <file> (film 28.5 s, rendered in 217 s of wall time)`; the second one is the time it took, not the length of the film.
**Trust.** A spec is code. `check-spec.mjs`, `export-svg.mjs` and `registry.mjs` contain it with Node's permission model (never add `--trusted` to a spec you did not write); a render does not, so never render a spec taken from the web or a pull request without reading it; write only your own specs. Keep `--out` paths inside your work folder. See SECURITY.md.
**Recognisable.** A correct model nobody recognises is not finished. Add `recognize: { object, iconic: [{ text, parts | partsPrefix }] }` (2 or more features, each built from parts that exist) and, before delivering, run `node scripts/stranger.mjs <work>/<name>/index.html` (for an example of this repo, its name), which saves a label-free still under a neutral name in a temporary folder, and show that still, giving the judge only its path and never the object's name, to a model with no context ("what object is this?") and record the answer in `recognize.blind: { answer, judge, date, ok }`. If it is not recognised, fix the model (the iconic shapes, the livery, the proportions), not the caption. For software, research the real thing on the web first (logo, icon, UI) and keep the local tool as optional evidence.
**Verdicts.** The gate prints VERIFIED only if a published figure was compared, none is pending and at least one of the figures comes from a source independent of the model's inputs; CONSISTENT if everything matched but every figure shares its source with the inputs; NOTHING CHECKED / INCOMPLETE / DOES NOT PASS otherwise. **A check that only converts units** (inches to mm, pounds to kg, anything times 25.4) cannot fail, so it is reported but not counted: a spec with nothing but conversions says ONLY CONVERSIONS. Mark a real conversion `kind: 'convert'` (the gate also recognises the usual unit factors). **VERIFIED does not mean the source is right.** If the figure a model is compared with comes from the same source that fed the model, the check shows only that the two agree, so the gate classifies every published-figure check as independent, same source, or with no sourced inputs (the `provenance:` line) and the state is CONSISTENT, not VERIFIED, when none is independent. Prefer a published figure from another source than the inputs; when a source copies another (a wiki quoting the maker), say so with `derives: ['<source id>']` on it and it counts as the same. Numbers you type in a part card or the lead text of the film (a decimal, or a number with a unit) must be traceable to a claim, a dimension or a published value, or the gate warns that they can drift from the model: put the derivation in a claim. Strong checks are: measuring the drawn model (a spacing from the drawn pins, a displacement from the drawn bore), crossing two sources (a diagonal against the area and the resolution), an identity the sheet states in another way, or rebuilding and testing the real thing. In `mechanism.calc` expressions you can use `sqrt`, `abs`, `min`, `max`, `pow`, `round`, `hypot`, `PI` and the trigonometric functions without the `Math.` prefix. Templates from `scripts/new.mjs` are drafts that fail until every TODO is replaced. Never put a placeholder source, a calc variable without a claim or an ESTIMATED part without a reason in a spec. `node --test tests/gate.test.mjs` must stay green.
**Final report.** End the job with a short report the user can trust: (1) what you modelled and which variant (the sheet may cover several: say which one); (2) the sources you used and the ones that failed (403, empty page, a mirror instead of the maker's copy); (3) what the gate checked against what, in plain words, and which checks are conversions; (4) what is ESTIMATED (list it, it is drawn dashed); (5) the stranger test: what the judge said, which model judged, and whether it read text on the model instead of recognising the shape; (6) the lengths and the files (film, short cut, viewer, STL, GLB); (7) anything you did not check, including how the film plays beyond the stills you looked at. Do not round a weak result up.
## References (open when the step needs them)
- `references/spec-and-film.md`: the parts library and gear-train rules, what the director builds, looks, engines (Studio, Workflow, Paper), lengths, software objects, provenance per dimension, scanned sources, encoders, brand identity. **Read it while writing `spec.js` and before the first render.**
- `references/examples.md`: which shipped example to copy for which kind of object (step 3).
- `references/hand-built.md`: the hand-built route on `kit/figure.js`, the engine modules and the look, for objects a spec cannot express.
- `SECURITY.md`: what the gate contains and what it does not.
## Requirements
Node 22+, Edge or Chrome, `ffmpeg`, `uv` (Python tools for measuring photos). No npm install is needed to render; `npm install` in `tools/vendor-build` only to rebuild the three.js bundle.
## Limits
For toolkit changes, run `npm test` and `node scripts/visual-regression.mjs`. Golden frames are sampled regression evidence, not approval of every frame. Regenerate explicitly with `node scripts/visual-regression.mjs --update` only after reviewing the change; never regenerate to make a defect disappear. Stage initialization uses seeded denoiser noise. Full-resolution GPU edge samples can retain tiny bounded rounding differences even when geometry and time are identical. See `docs/perf-quality.md` for measurements and limits.
- It cannot get millimetres from a single photo without a scale: use a published size, a coin or ruler in the picture, or the datasheet. A catalog collage scales every piece to fit its slot, so even ratios between objects are not real.
- Interiors need photos. If none exist, the interior is estimated and says so.
- Rendering is heavy: about 2 to 3 seconds of wall time per second of film on a discrete GPU, much more on an integrated one (the scripts force the high-performance GPU; check `UNMASKED_RENDERER_WEBGL` if it is slow). Also checked on Ubuntu 24.04 (install, gates, stills, lint); a machine with no GPU renders a frame in about 7 s, so there use stills and the lint, not a film. Not tried on macOS.