rig-review 0.13.0 → 0.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -14,8 +14,9 @@ Review is the human inspection and source-bound feedback gate in this delivery
14
14
  flow. It inspects the upstream build without replacing Core's compiler gate or
15
15
  authoring motion. Review itself uses the published Play runtime for previews;
16
16
  the chain describes delivery and feedback responsibilities, not runtime import
17
- order. Adapter denotes downstream consumer integration, without promising an
18
- unreleased adapter API or changing existing compilation/playback dependencies.
17
+ order. Adapter denotes downstream consumer integration, without depending on the
18
+ published adapters (`@rig-c/pixi`, `@rig-c/phaser`, `@rig-c/three`) or changing
19
+ existing compilation/playback dependencies.
19
20
 
20
21
  ## Start
21
22
 
@@ -26,9 +27,12 @@ npm i -D @rig-c/review
26
27
  npx rig-review open /absolute/path/to/output
27
28
  ```
28
29
 
30
+ `rig-review` is also the convenience package name: `npm i -D rig-review` installs
31
+ the same CLI. `@rig-c/review` remains the canonical package.
32
+
29
33
  `rig-review` runs under **Bun 1.3.0 or later**, which must be on `PATH`; CI runs the installed package on 1.3.0 and on the latest Bun. `open` prints a loopback URL and exits; see [One-command owner handoff](#one-command-owner-handoff). `npx rig-review --help` lists every command.
30
34
 
31
- The package is 0.1.x: the CLI is the supported surface, and no importable API is promised yet (`exports` exposes only `package.json`).
35
+ The package is 0.x: the CLI is the supported surface, and no importable API is promised yet (`exports` exposes only `package.json`).
32
36
 
33
37
  ## Existing output handoff
34
38
 
@@ -636,7 +640,7 @@ Rig pixels are the whole figure's frame, which is the crop frame the model's sta
636
640
 
637
641
  **Apply · Rebuild** runs the installed producer on the designated regions and reloads the result on the stage, so the loop is point → rebuild → watch → repeat → submit. Review builds no mesh itself and never runs a manifest command:
638
642
 
639
- - **Producer.** `@rig-c/parts`' `rig-parts`, resolved the way `rigc review` resolves `rig-review`: `node_modules/@rig-c/parts/package.json` in the nearest directory walking up from the package, then from the directory `rig-review open` was run in; its `bin` entry is read from that manifest, never from `PATH`. The compiler is the `rig-c` that `rig-parts` itself finds walking up from its own package. Absent: `MODELING_PRODUCER_NOT_INSTALLED: … — bun add -d @rig-c/parts`; older than the first release with `remesh` (1.40.0): `MODELING_PRODUCER_TOO_OLD <found> 1.40.0`. Review never installs anything.
643
+ - **Producer.** `@rig-c/parts`' `rig-parts`, resolved the way `rigc review` resolves `rig-review`: `node_modules/@rig-c/parts/package.json` in the nearest directory walking up from the package, then from the directory `rig-review open` was run in; its `bin` entry is read from that manifest, never from `PATH`. The compiler is resolved walking up from `rig-parts`' own package: `@rig-c/core` first, then legacy `rig-c` only when the scoped package is absent. The selected package name and version are reported as `compiler`. Absent: `MODELING_PRODUCER_NOT_INSTALLED: … — bun add -d @rig-c/parts`; older than the first release with `remesh` (1.40.0): `MODELING_PRODUCER_TOO_OLD <found> 1.40.0`. Review never installs anything.
640
644
  - **Inputs.** The producer handoff (`docs/REVIEW_PACKAGE.md`, *Producer handoff*): `open --config` (and `--parts`), else the review.json `producer` block, else `config.json`, then `rig/config.json` inside the opened folder, then `../config.json` beside an opened `out/`. The parts directory defaults to `out/` beside the config and holds `parts.json`, `parts/` and the inspected `rig/`. The editor bar shows, before any Apply, how the config was found (declared, discovered, or discovered outside the opened folder), its path and SHA-256, and the baseline. Without a handoff the bar shows `MODELING_INPUT_UNAVAILABLE: config.json — looked at …` and Apply refuses `MODELING_HANDOFF_MISSING`, naming `--config`. The request carries only `{part, regions, configHash}`; the server reads the config, replaces `meshes.<part>.contour.regions` with the designated regions (every other field as written) and runs `rig-parts remesh --rig <out>/rig --config <config> --parts <out> --part <part> --mesh mesh.json --out revision`, then `rigc build --profile rig-play --pack` with the revision's own `--page-edges` / `--pack-shape`, both with the scratch directory as working directory. A region row with equal semi-axes and no clip is sent as the config's circle, any other as the 32-sample polygon. An untouched editor starts from the config's own circle regions and contour numbers.
641
645
  - **Baseline.** Once per session, when the handoff and the producer both resolve, review runs `rig-parts rig --config <config> --parts <parts> --out <state>/baseline/<key>/baseline` and compares every file it writes (rig.json, motion.json, mesh_report.json, sets.json, images/*) with the inspected rig directory: `baseline: reproduced — rig.json <hash> = inspected <hash>`, or `baseline: not reproduced — <first differing path> · <what Apply can still do here>`. The key is the package hash, the handoff record and the producer versions (no clock), so a reopen reuses the result. Ops that rebuild the whole rig refuse `EDIT_BASELINE_MISMATCH <path>` unless the baseline reproduced; `remesh` keeps its own `REMESH_BASELINE_MISMATCH`. A build the config does not reproduce — a hand repair — is not open to every Apply (#148): retune carries a **mesh** repair over but holds every image, so a repair that redrew an image refuses every physics and idle edit (`RETUNE_REQUEST_HELD: images/<part>.png`); remesh from a config entry refuses the repaired parts and re-fits the others; a mesh edit is held to the inspected mesh, not the config; and remesh, its mesh edit and retune all refuse an inspected `rig.json` (or `mesh_report.json`, `motion.json`) that is not the writer's text (`…_INPUT_WRITTEN` — re-serialise it as rig writes it, JSON with one space of indent and a closing newline, in a copy). The baseline line and `EDIT_BASELINE_MISMATCH` name which of these hold here, and a producer refusal review can explain carries `review: …` after the producer's own line (docs/REVIEW_PACKAGE.md, *Baseline reach*). Once it reproduces, `rig-parts check` runs on the baseline output in the background (into `<state>/baseline/<key>/inspected-check/`, with Apply's default `--page-edges free --pack-shape polygon`) and its summary is stored beside the baseline record as `inspected-check.json`; that is the physics readout's BEFORE for the inspected build, and a reopen reads it instead of re-running.
642
646
  - **Scratch.** `<state>/rebuild/<package source hash>/<designation hash>/` under `RIG_REVIEW_STATE_DIR` (refused, by its canonical location, inside the package, its build root or the handoff root), named from the package, the config bytes, the part, the entry and both tool versions — no clock. The same designation again reuses its build and says so; a new one replaces the previous scratch build on the stage (the earlier applied records' builds are kept beside the scratch root for undo, at most eight per package). The inspected package's files are only read.
@@ -27,15 +27,24 @@ Review runs the installed producers and never installs them. Keep one folder tha
27
27
  ```sh
28
28
  mkdir "$BASE/review-producer" && cd "$BASE/review-producer"
29
29
  npm init -y
30
- npm i -D @rig-c/review@0.8.0 @rig-c/parts@1.49.1 rig-c@2.64.1 @rig-c/pose@0.37.0
30
+ npm i -D @rig-c/review@0.13.0 @rig-c/parts@1.78.0 @rig-c/core@2.81.0 @rig-c/pose@1.5.3
31
31
  cd "$BASE"
32
32
  ```
33
33
 
34
+ **Scoped compiler prerequisite (#221).** Published Review 0.13.0 still resolves
35
+ only `rig-c`; the scoped-only bench above needs the fixed Review checkout
36
+ containing #221, or a later release that includes it. Until that release, run
37
+ `bun /absolute/path/to/review/src/cli.ts open …` from the bench in step 3 instead
38
+ of `npx rig-review open …` (install that checkout's dependencies first). After
39
+ release, replace the Review pin with that released version. The producer pins
40
+ above were checked against npm on 2026-10-30; both parts and pose depend on
41
+ `@rig-c/core`.
42
+
34
43
  Bump a version here when a card needs a newer producer. Review refuses an older one by name where a flag needs it, for example `MODELING_PRODUCER_TOO_OLD` for `check --environment`.
35
44
 
36
45
  ## 2. The package, built in scratch
37
46
 
38
- The example's heavy inputs live in rig-c/parts-examples. A parts clone copies them in with `bun run fetch-examples`:
47
+ The pinned parts checkout fetches the example's heavy inputs with `bun run fetch-examples`:
39
48
 
40
49
  ```sh
41
50
  git clone --branch v1.49.1 https://github.com/rig-c/parts.git "$BASE/parts"
@@ -62,7 +71,10 @@ npx rig-parts build --config "$P/config.json" --source "$P/inputs/painting.png"
62
71
  cp -R "$P/out/check/build" "$P/build"
63
72
  ```
64
73
 
65
- The build takes about two minutes and ends with `build: PASS`. `out/parts.json`, `out/rig/motion.json` and `out/rig/mesh_report.json` are byte-equal to `expected/`.
74
+ The original measurement with parts 1.49.1 and rig-c 2.64.1 took about two
75
+ minutes and ended with `build: PASS`; `out/parts.json`, `out/rig/motion.json` and
76
+ `out/rig/mesh_report.json` were byte-equal to `expected/`. That timing and byte
77
+ comparison have not been re-measured for the current producer pins.
66
78
 
67
79
  ## 3. Open it with the handoff
68
80
 
@@ -75,7 +87,8 @@ npx rig-review open "$P" --config "$P/config.json" --parts "$P/out" \
75
87
  --keypoints "$P/keypoints.json" --animation idle
76
88
  ```
77
89
 
78
- Inside a review clone, `bun src/cli.ts open …` is the same command. Once the session baseline settles (about two minutes), the editor bar reads:
90
+ Inside a review clone, `bun src/cli.ts open …` is the same command. In the original measurement, once the session baseline settled (about two
91
+ minutes), the editor bar read (re-measure with the current pins):
79
92
 
80
93
  ```
81
94
  baseline: reproduced — rig.json d19892a32cec… = inspected d19892a32cec…
@@ -5,8 +5,9 @@ Adapter**. Review supplies human inspection and feedback bound to the exact
5
5
  reviewed sources; it neither replaces Core's build validation nor authors motion.
6
6
  The app uses the published `@rig-c/play` loader/player to preview artifacts during
7
7
  that inspection. This conceptual delivery order is not an import/dependency
8
- order. Adapter means downstream consumer integration, not a newly introduced or
9
- unreleased adapter contract. Existing producer formats and playback dependencies
8
+ order. Adapter means downstream consumer integration through the published
9
+ `@rig-c/pixi`, `@rig-c/phaser` and `@rig-c/three` packages; Review does not depend
10
+ on them. Existing producer formats and playback dependencies
10
11
  remain authoritative.
11
12
 
12
13
  The CLI also accepts an existing output directory. It reads a root `review.json` when present. Otherwise it constructs this contract in memory: optional root `skeleton.model.json`, otherwise the fixed rigc build path `build/skeleton.model.json`; read-only `skeleton.*`, `motion.json` and `report.json` links; root videos (`mp4`, `webm`, `mov`); image filenames containing strip/contact/sheet/filmstrip/띠; and canonical `*pose*.json` references. The root compiled model takes precedence over the build path. These are the only two automatically recognized model paths; discovery never searches recursively. One fixed layout redirects instead (#149): a folder with neither model path that is a `rig-parts build` output root — regular `parts.json` and `rig/rig.json`, a real (not symlinked) `check/` directory, and an existing `check/build/skeleton.model.json` entry — opens as its `check/` package. The package root is then `check/`, exactly as if `check/` had been opened: the same manifest, source hash, `reviewedSource`, session and open key. A folder missing any of those files is not a build root and is refused or adapted as before; a build root that also holds a model of its own opens as itself. The opened directory remains the package root, so root motion/references accompany the build model and its model-relative pages. An existing selected model with an invalid type, dangling symlink, escaping symlink or invalid model envelope is refused rather than skipped. Each reference is shown separately; no source relationship, FPS or video synchronization is inferred. It never inserts or rewrites a manifest or motion file in the source folder. Raw Spine JSON/skel remains read-only evidence with playback unavailable. The folder revision is `folder-adapter/1`; actual changed file bytes are distinguished by the source hash.
@@ -176,7 +177,7 @@ Every edit card states its edit as one config patch; review applies it to a scra
176
177
  - `rig-review-applied/2` (patch): `{schema, buildId, patch, patchSha256, configHash, producer:{package,version}, compiler:{package,version}, line, check, diff, modelHash}` — the exact patch (whose `config_sha256` equals `configHash`), its hash, the producer's last line, check's summary and the rig.json diff. Since #136 the record adds `op`, the producer op that ran: `"rig"` or `"retune"` for a `rig` patch, `"remesh"` for a remesh patch (anything else is refused); the job carries the same `op` and its lines name the command. Records without `op` stay valid. Since #111 `op` may be `"cloth"` for a `rig` patch whose ops all lie under `/cloth`, and exactly then the record adds `cloth: {route:"build"|"rig", keypoints:{path, sha256}, written:{count, paths[≤200]}}` — how the config propose wrote was built, the keypoint file it read and where the written config differs from the patched one — and, when the route is `build`, `assembled` as below; the export's `Applied:` line names the written diff and the route. Since #115 `op` may be `"build"` for a `rig` patch, and exactly then the record adds `assembled: {wallMs, recomposite:{before, after}, partsChanged[], seamPairs[≤64]}` — the build's wall clock for that Apply, the recomposite figures, the parts whose PNG changed against the handoff's and the `seam pair` lines; the issue export's `Applied:` line names the wall clock (`· build 118.0 s · parts changed: bottomwear, ribbon_r · recomposite error px 8194 → 8194`). A remesh patch from the Mesh panel (#106) adds `mesh: {part, before:{vertices, triangles, hull}, after:{vertices, triangles, hull}}` — the revision's `remesh.json` counts; `part` must be the part the patch's `remesh --part` names. Since #125 the record adds `files: {documents:[{file, count, paths}], images:{changed, unchanged, names}}` (or `null`) — the scratch rig against the inspected rig, every document the producer writes: `documents` in the order `rig.json`, `motion.json`, `sets.json`, `mesh_report.json` (each `file` at most once; a document neither side has is left out), `count` the number of RFC 6901 pointers at which it differs and `paths` the first ≤ 200 of them (`""` — the whole document — when only one side has it or a side is not JSON, unless the bytes are equal); `images` compares `images/*` by SHA-256 only: `changed` (including an image only one side has, its `images/<name>` in `names`, ≤ 200) and `unchanged`. `diff` stays rig.json's entry as before. Records without `files` (written before #125) stay valid. The list of documents is `PRODUCER_DOCUMENTS` in `src/producer-files.ts`, the same list the baseline compares. The Apply status line and the export state it per file: `motion.json differs at 2 path(s) · rig.json, sets.json, mesh_report.json unchanged · 31 images unchanged`.
177
178
  - `rig-review-applied/3` (#135, Edit Mesh's delta): `{schema, buildId, part, delta, deltaSha256, configHash, producer, compiler, line, op, files, mesh?, modelHash}` — the `rig-review-mesh-edit/1` document as sent (basis `"setup"`, `slot` the part, at most 300 ops), the SHA-256 of its bytes, the producer's last line, `op` always `"remesh"`, the per-file diff as above (or `null`) and the part's `remesh.json` counts (`mesh.part` must be `part`). No `check`: `rig-parts remesh` holds check on the revision itself (`REMESH_CHECK_HELD`). See *Mesh edit export*.
178
179
 
179
- **Readings (#108).** `POST /api/readings` (single review; same-origin, capability, 64 KiB) takes exactly `{animation, readings:[1–8], environment}`: each reading is `{kind:"folds"}`, `{kind:"overlaps"}`, `{kind:"pass-through", cloth, leg}`, `{kind:"seam", a, b}` or `{kind:"angle-range", bone, relative}`, every name a slot or bone name without `:` or `,` (whether the rig has it is rigc's to say), no reading twice; `environment` is `null`, `{wind:[x,y], gravity:[x,y]}` (x right, y down) or, with *Use for readings* on (#112), the exported `rigc-environment/1` schedule as data. Malformed: HTTP 400 `READINGS_REQUEST_INVALID`. Outcomes (HTTP 200 `{status:"refused", error}`): `READINGS_COMPILER_NOT_INSTALLED` (rig-c beside `@rig-c/parts`, else beside the package or review), `READINGS_COMPILER_TOO_OLD <found> 2.63.0`, `READINGS_NO_MODEL`, `READINGS_STATE_INSIDE_PACKAGE`, `READINGS_BUSY`. The job (`GET /api/readings/<id>`, the id hashed from the package, the request, the inspected model's bytes, the scratch build id, the compiler and whether the schedule was passed): `{schema:"rig-review-readings/1", id, status:"running"|"done"|"failed", error, request, environment:{requested, flag, passed}, compiler, runs:[{build:"inspected"|"scratch", id, modelHash, results[]}]}`. Each result is `{reading, label, status:"read"|"refused"|"failed", fps, frames[] (every frame the reading's JSON lists: folds' meshes' frames, pass-through's frames, seam's opens, overlaps' moving pairs' runs; angle-range's lowest and highest frame from its printed line), source:"json"|"line", worst:{frame, figure, line}|null, items[] (per mesh or pair), lines[] (rigc's printed rows for the reading), refusal (rigc's `rigc: …` line verbatim), exitCode, wallMs}`. The inspected build is `--candidate <the directory of the manifest's model>`; the scratch build is the session's latest built Apply. Each reading runs as `rigc render --candidate <build> --animation <animation> [--environment environment.json] <flag> --out render` in `<state>/readings/<package hash>/<inspected-<model sha256>|scratch-<build id>>/<key>/`, keeps `result.json`, the reading's JSON and `frames.json`, and removes the rendered frames. The schedule `{spec:"rigc-environment/1", steps:[{t:0, wind, gravity}]}` is passed only when `rigc render --help` lists `--environment`. A schedule sent in the request is written as sent (re-serialized, one-space JSON and a newline).
180
+ **Readings (#108).** `POST /api/readings` (single review; same-origin, capability, 64 KiB) takes exactly `{animation, readings:[1–8], environment}`: each reading is `{kind:"folds"}`, `{kind:"overlaps"}`, `{kind:"pass-through", cloth, leg}`, `{kind:"seam", a, b}` or `{kind:"angle-range", bone, relative}`, every name a slot or bone name without `:` or `,` (whether the rig has it is rigc's to say), no reading twice; `environment` is `null`, `{wind:[x,y], gravity:[x,y]}` (x right, y down) or, with *Use for readings* on (#112), the exported `rigc-environment/1` schedule as data. Malformed: HTTP 400 `READINGS_REQUEST_INVALID`. Outcomes (HTTP 200 `{status:"refused", error}`): `READINGS_COMPILER_NOT_INSTALLED` (`@rig-c/core` first, legacy `rig-c` only when absent, beside `@rig-c/parts`, else beside the package or review), `READINGS_COMPILER_TOO_OLD <found> 2.63.0`, `READINGS_NO_MODEL`, `READINGS_STATE_INSIDE_PACKAGE`, `READINGS_BUSY`. The job (`GET /api/readings/<id>`, the id hashed from the package, the request, the inspected model's bytes, the scratch build id, the compiler and whether the schedule was passed): `{schema:"rig-review-readings/1", id, status:"running"|"done"|"failed", error, request, environment:{requested, flag, passed}, compiler, runs:[{build:"inspected"|"scratch", id, modelHash, results[]}]}`. Each result is `{reading, label, status:"read"|"refused"|"failed", fps, frames[] (every frame the reading's JSON lists: folds' meshes' frames, pass-through's frames, seam's opens, overlaps' moving pairs' runs; angle-range's lowest and highest frame from its printed line), source:"json"|"line", worst:{frame, figure, line}|null, items[] (per mesh or pair), lines[] (rigc's printed rows for the reading), refusal (rigc's `rigc: …` line verbatim), exitCode, wallMs}`. The inspected build is `--candidate <the directory of the manifest's model>`; the scratch build is the session's latest built Apply. Each reading runs as `rigc render --candidate <build> --animation <animation> [--environment environment.json] <flag> --out render` in `<state>/readings/<package hash>/<inspected-<model sha256>|scratch-<build id>>/<key>/`, keeps `result.json`, the reading's JSON and `frames.json`, and removes the rendered frames. The schedule `{spec:"rigc-environment/1", steps:[{t:0, wind, gravity}]}` is passed only when `rigc render --help` lists `--environment`. A schedule sent in the request is written as sent (re-serialized, one-space JSON and a newline).
180
181
 
181
182
  **Environment schedule (#112).** The Animate Environment block exports its controls as a `rigc-environment/1` schedule (`src/environment-schedule.ts`): `{spec:"rigc-environment/1", steps:[{t, wind?:[x,y], gravity?:[x,y]}]}`, x right, y down, t = 0 at the clip's first frame, written as one-space JSON and a newline (the bytes downloaded, sent and hashed). With neither breeze nor a repeating gust it is the step form, one step `{t:0, wind:[base,0], gravity:[0,g]}` (#108's standing schedule) — exact. With breeze or repeat on it is the sampled form: one step per frame at 12 fps (rigc render's default and check's IDLE_FPS) from 0 to the clip's last frame (`round(duration·12)`; rigc refuses a step after it), each the panel's wind at that time from a fresh clock (a repeating gust starts at 0, every `max(6, duration + 0.6)` s, turned when the base is negative), a step equal to the one before left out, gravity on the first step. rigc sets a step before the physics step that ends on its frame, as the page sets the wind before the player's step, so the schedule equals the panel's wind at every frame time; between frames rigc holds the frame's value, within `max|dw/dt| / 12` of the panel's (`(breeze ? 8.48 : 0) + (repeat ? peak·π/duration : 0)`, over 12). A one-off Gust (Repeat off) is not in the schedule. Refused by name: `ENVIRONMENT_NOT_APPLIED` (the controls are not engaged; check and render without `--environment` already read the runtime's world), `ENVIRONMENT_SCHEDULE_NO_CLIP`, `ENVIRONMENT_SCHEDULE_TOO_LONG` (more than 600 steps). *Use for readings* sends the drawn clip's schedule with readings and the checked clip's (`idle` when the rig has one) with Apply, and restarts the wind clock with the clip (and at each idle loop) so the person watches the world rigc reads. *Export schedule* downloads `rigc-environment.json`.
182
183
 
@@ -190,7 +191,7 @@ Every edit card states its edit as one config patch; review applies it to a scra
190
191
 
191
192
  **Self-targeted requirements (#142).** The panel refuses before Add, as `REQUIREMENTS_SELF_TARGET`, a `contact` whose target is its own bone and point and an `aim` whose target bone is the aimed bone: both measure 0 in every frame and the producer reports them as PASS (its own refusal is rig-c/parts#540). A bone's tip against its own origin is not refused; it measures the bone's length. A fresh contact or aim form proposes a target bone other than the subject (the next bone in rig order).
192
193
 
193
- **Retarget (#114).** `GET /api/rebuild` adds `retarget: {available, reason, producer, compiler, rig, sequences:[{id, label, path, cleaned, frames, people[]}], bones[], animations[]}`: the installed `@rig-c/pose` (found as `@rig-c/parts` is: the nearest `node_modules` from the package, then from review's working directory; `RETARGET_PRODUCER_NOT_INSTALLED` when absent, never installed) and the `rig-c` beside it (`RETARGET_COMPILER_NOT_INSTALLED`), the package's declared `rig-pose-sequence/1` pose references, and the handoff rig's bone and animation names (`RETARGET_SEQUENCE_UNAVAILABLE` when the package declares no sequence). `POST /api/rebuild` with exactly `{retarget: {sequence, map, flags, configHash}}` (same-origin, capability, 64 KiB): `sequence` a declared pose reference id; `map` a `rig-pose-map/1` document of at most 32 KiB whose chains each hold a name and 1–32 bones and joints (everything else is the producer's to judge); `flags` exactly `{animation, person, clean, minScore, flip, short, bends, bendTolerance, legScale, plantFeet, stance, raisedByHead, carryIdle}`, `clean` null or exactly `{loop, legs, rest, hold, hipsInferred}` (windows 1, or odd and 5–61). Malformed: HTTP 400 `RETARGET_REQUEST_INVALID`. Refused before anything runs: `REBUILD_CONFIG_STALE`, `RETARGET_SEQUENCE_UNKNOWN`, `RETARGET_SEQUENCE_UNSUPPORTED` (a static pose), the handoff and state-directory refusals, `REBUILD_BUSY`. The server maps the flags to argv (`op.args` for retarget, `op.clean` for clean, only what the flags state) and runs, in `<state>/rebuild/<package hash>/<retarget id>/`, `rig-pose clean --pose pose.json --out pose.clean.json <op.clean>` (unless `clean` is null) and `rig-pose retarget --rig <handoff rig> --map map.json --sequence <pose.clean.json | pose.json> --out motion.json <op.args>`, copies the handoff rig's regular files (motion.json excepted) and `images/` to `rig/` with `rig/motion.json` the retarget's, then the shared tail: the per-file diff and `rigc build --rig rig/rig.json --motion rig/motion.json --out build --profile rig-play --pack --page-edges free --pack-shape polygon`. The retarget id hashes the package, the handoff config, the record's bytes and both tool versions (no clock); an identical retarget reuses its build. The job (`GET /api/rebuild/<id>`): `{schema:"rig-review-rebuild/1", kind:"retarget", op:"retarget", id, status, reused, request:{record, recordSha256, configHash}, producer, compiler, lines[], producerLine, refusals[] (every FAIL line of the step that refused, verbatim), exitCode, wallMs:{clean, retarget, compile}, files, motion:{animation, animations[], kept, changed[], animationSha256, motionSha256}, readings:[{code, line, frame, time}], build}`. `readings` are the retarget's FEET, STANCE, FLIP, SHORT, END-ON, LEG-SCALE and REACH lines (at most 200), each with the first frame it names and that frame's key time from the run's report `frameTimes` (else the line's `t=`). The record, `rig-review-retarget/1`: `{schema, config_sha256, rig:{path, sha256 (rig.json)}, sequence:{reference, path, sha256}, map, map_sha256 (the bytes of map.json as written: one-space JSON and a newline), flags, producer:{name, version}, op:{cmd:"retarget", args, clean}}`; a reader checks that `op` is its flags' own argv. The applied context of a built retarget is `rig-review-applied/4`: `{schema, buildId, retarget, recordSha256, configHash, producer, compiler, line, motion, files, readings, modelHash}`.
194
+ **Retarget (#114).** `GET /api/rebuild` adds `retarget: {available, reason, producer, compiler, rig, sequences:[{id, label, path, cleaned, frames, people[]}], bones[], animations[]}`: the installed `@rig-c/pose` (found as `@rig-c/parts` is: the nearest `node_modules` from the package, then from review's working directory; `RETARGET_PRODUCER_NOT_INSTALLED` when absent, never installed) and the `@rig-c/core` beside it (legacy `rig-c` only when the scoped package is absent; `RETARGET_COMPILER_NOT_INSTALLED`). The `compiler` record identifies the selected package and version; the remaining inputs are the package's declared `rig-pose-sequence/1` pose references, and the handoff rig's bone and animation names (`RETARGET_SEQUENCE_UNAVAILABLE` when the package declares no sequence). `POST /api/rebuild` with exactly `{retarget: {sequence, map, flags, configHash}}` (same-origin, capability, 64 KiB): `sequence` a declared pose reference id; `map` a `rig-pose-map/1` document of at most 32 KiB whose chains each hold a name and 1–32 bones and joints (everything else is the producer's to judge); `flags` exactly `{animation, person, clean, minScore, flip, short, bends, bendTolerance, legScale, plantFeet, stance, raisedByHead, carryIdle}`, `clean` null or exactly `{loop, legs, rest, hold, hipsInferred}` (windows 1, or odd and 5–61). Malformed: HTTP 400 `RETARGET_REQUEST_INVALID`. Refused before anything runs: `REBUILD_CONFIG_STALE`, `RETARGET_SEQUENCE_UNKNOWN`, `RETARGET_SEQUENCE_UNSUPPORTED` (a static pose), the handoff and state-directory refusals, `REBUILD_BUSY`. The server maps the flags to argv (`op.args` for retarget, `op.clean` for clean, only what the flags state) and runs, in `<state>/rebuild/<package hash>/<retarget id>/`, `rig-pose clean --pose pose.json --out pose.clean.json <op.clean>` (unless `clean` is null) and `rig-pose retarget --rig <handoff rig> --map map.json --sequence <pose.clean.json | pose.json> --out motion.json <op.args>`, copies the handoff rig's regular files (motion.json excepted) and `images/` to `rig/` with `rig/motion.json` the retarget's, then the shared tail: the per-file diff and `rigc build --rig rig/rig.json --motion rig/motion.json --out build --profile rig-play --pack --page-edges free --pack-shape polygon`. The retarget id hashes the package, the handoff config, the record's bytes and both tool versions (no clock); an identical retarget reuses its build. The job (`GET /api/rebuild/<id>`): `{schema:"rig-review-rebuild/1", kind:"retarget", op:"retarget", id, status, reused, request:{record, recordSha256, configHash}, producer, compiler, lines[], producerLine, refusals[] (every FAIL line of the step that refused, verbatim), exitCode, wallMs:{clean, retarget, compile}, files, motion:{animation, animations[], kept, changed[], animationSha256, motionSha256}, readings:[{code, line, frame, time}], build}`. `readings` are the retarget's FEET, STANCE, FLIP, SHORT, END-ON, LEG-SCALE and REACH lines (at most 200), each with the first frame it names and that frame's key time from the run's report `frameTimes` (else the line's `t=`). The record, `rig-review-retarget/1`: `{schema, config_sha256, rig:{path, sha256 (rig.json)}, sequence:{reference, path, sha256}, map, map_sha256 (the bytes of map.json as written: one-space JSON and a newline), flags, producer:{name, version}, op:{cmd:"retarget", args, clean}}`; a reader checks that `op` is its flags' own argv. The applied context of a built retarget is `rig-review-applied/4`: `{schema, buildId, retarget, recordSha256, configHash, producer, compiler, line, motion, files, readings, modelHash}`.
194
195
 
195
196
  **`context.environmentSchedule`.** Feedback written while the Environment controls are applied carries the drawn clip's exported schedule by hash and content: `{schema:"rig-review-environment-schedule/1", sha256, size, text, usedForReadings}` — `text` is exactly the exported bytes (a `rigc-environment/1` schedule), `sha256` and `size` are recomputed over it on save, `usedForReadings` says whether readings and Apply read it. Absent and `null` stay valid. The issue export adds an `Environment schedule:` line.
196
197
 
@@ -563,7 +564,7 @@ Browser players retain play's default DOM mode and default mesh backend. Rigid
563
564
  parts remain DOM images; a WebGL mesh label does not claim full-frame GL drawing.
564
565
  WebGL is requested by the published default, with Canvas2D as the supported mesh
565
566
  fallback. Accurate public actual-flush telemetry requires play 1.11.1 or later;
566
- this checkout consumes pinned 1.11.2. The consumer does not probe GL or inspect
567
+ this checkout consumes pinned 1.14.0. The consumer does not probe GL or inspect
567
568
  private renderer state. The separate Node strip exporter remains Canvas2D.
568
569
 
569
570
  The backend badge is sampled only after successful draw/frame calls. Fresh mesh
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rig-review",
3
- "version": "0.13.0",
3
+ "version": "0.13.1",
4
4
  "type": "module",
5
5
  "description": "Local human rig inspection and structured feedback export",
6
6
  "license": "MIT",
package/src/producer.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * The installed producer (`@rig-c/parts`' `rig-parts`) and the compiler it gates with (`rig-c`'s `rigc`), resolved the
2
+ * The installed producer (`@rig-c/parts`' `rig-parts`) and the compiler it gates with (`@rig-c/core`'s `rigc`, or legacy `rig-c`), resolved the
3
3
  * way `rigc review` resolves `rig-review`: the package's own `package.json` under the nearest `node_modules` walking
4
4
  * up from each start directory, its `bin` entry read from that manifest. No PATH lookup, no install, no prompt.
5
5
  * Review builds nothing itself: it runs these binaries as child processes with arguments it composes from a typed
@@ -10,7 +10,8 @@ import { dirname, join, resolve } from 'node:path';
10
10
 
11
11
  export const PRODUCER_PACKAGE = '@rig-c/parts';
12
12
  export const PRODUCER_BIN = 'rig-parts';
13
- export const COMPILER_PACKAGE = 'rig-c';
13
+ export const COMPILER_PACKAGE = '@rig-c/core';
14
+ const LEGACY_COMPILER_PACKAGE = 'rig-c';
14
15
  export const COMPILER_BIN = 'rigc';
15
16
  /** The first `@rig-c/parts` release with `rig-parts remesh` (its CHANGELOG: 1.40.0, "remesh: re-fit one part of a gated rig"). */
16
17
  export const REMESH_SINCE = '1.40.0';
@@ -39,6 +40,11 @@ export function findPackage(name: string, starts: readonly string[], exists: (pa
39
40
  return null;
40
41
  }
41
42
 
43
+ /** Prefer the canonical package across the search roots; use the legacy name only when it is absent. */
44
+ function findCompiler(starts: readonly string[], exists: (path: string) => boolean): string | null {
45
+ return findPackage(COMPILER_PACKAGE, starts, exists) ?? findPackage(LEGACY_COMPILER_PACKAGE, starts, exists);
46
+ }
47
+
42
48
  /** `X.Y.Z` → [X, Y, Z]; anything else is null (a pre-release tag is not compared, it is refused as unread). */
43
49
  export function parseVersion(text: unknown): [number, number, number] | null {
44
50
  const match = typeof text === 'string' ? /^(\d+)\.(\d+)\.(\d+)$/.exec(text.trim()) : null;
@@ -72,7 +78,7 @@ export function resolveProducer(starts: readonly string[], exists: (path: string
72
78
  const producer = readTool(manifest, PRODUCER_BIN);
73
79
  if ('error' in producer) return { ok: false, error: `${MODELING_PRODUCER_NOT_INSTALLED} (${producer.error})` };
74
80
  if (!versionAtLeast(producer.version, REMESH_SINCE)) return { ok: false, error: modelingProducerTooOld(producer.version || 'unknown', REMESH_SINCE) };
75
- const compilerManifest = findPackage(COMPILER_PACKAGE, [producer.root], exists);
81
+ const compilerManifest = findCompiler([producer.root], exists);
76
82
  if (!compilerManifest) return { ok: false, error: MODELING_COMPILER_NOT_INSTALLED };
77
83
  const compiler = readTool(compilerManifest, COMPILER_BIN);
78
84
  if ('error' in compiler) return { ok: false, error: `${MODELING_COMPILER_NOT_INSTALLED} (${compiler.error})` };
@@ -129,7 +135,7 @@ export const readingsCompilerTooOld = (found: string, required: string) => `READ
129
135
  export function resolveCompiler(starts: readonly string[], since: string, exists: (path: string) => boolean = existsSync): { ok: true; compiler: InstalledTool } | { ok: false; error: string } {
130
136
  const pair = resolveProducer(starts, exists);
131
137
  let compiler: InstalledTool | { error: string } | null = pair.ok ? pair.compiler : null;
132
- if (!compiler) { const manifest = findPackage(COMPILER_PACKAGE, starts, exists); compiler = manifest ? readTool(manifest, COMPILER_BIN) : null; }
138
+ if (!compiler) { const manifest = findCompiler(starts, exists); compiler = manifest ? readTool(manifest, COMPILER_BIN) : null; }
133
139
  if (!compiler) return { ok: false, error: READINGS_COMPILER_NOT_INSTALLED };
134
140
  if ('error' in compiler) return { ok: false, error: `${READINGS_COMPILER_NOT_INSTALLED} (${compiler.error})` };
135
141
  if (!versionAtLeast(compiler.version, since)) return { ok: false, error: readingsCompilerTooOld(compiler.version || 'unknown', since) };
@@ -150,7 +156,7 @@ export function resolvePose(starts: readonly string[], exists: (path: string) =>
150
156
  if (!manifest) return { ok: false, error: RETARGET_PRODUCER_NOT_INSTALLED };
151
157
  const producer = readTool(manifest, POSE_BIN);
152
158
  if ('error' in producer) return { ok: false, error: `${RETARGET_PRODUCER_NOT_INSTALLED} (${producer.error})` };
153
- const compilerManifest = findPackage(COMPILER_PACKAGE, [producer.root], exists);
159
+ const compilerManifest = findCompiler([producer.root], exists);
154
160
  if (!compilerManifest) return { ok: false, error: RETARGET_COMPILER_NOT_INSTALLED };
155
161
  const compiler = readTool(compilerManifest, COMPILER_BIN);
156
162
  if ('error' in compiler) return { ok: false, error: `${RETARGET_COMPILER_NOT_INSTALLED} (${compiler.error})` };