rig-review 0.13.1 → 0.14.0
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 +39 -705
- package/docs/DESIGN.md +11 -0
- package/docs/PANELS.md +400 -0
- package/docs/REVIEW_PACKAGE.md +23 -0
- package/docs/WORKFLOWS.md +337 -0
- package/package.json +4 -1
- package/src/compare-session.ts +3 -1
- package/src/feedback-validation.ts +2 -0
- package/src/manifest.ts +2 -1
- package/src/session.ts +5 -3
- package/src/turn-pad.ts +60 -0
- package/web/compare.css +10 -0
- package/web/compare.ts +17 -5
- package/web/inspection-persistence.ts +4 -2
- package/web/main.ts +20 -7
- package/web/styles.css +10 -0
- package/web/turn-pad.ts +26 -0
package/README.md
CHANGED
|
@@ -34,718 +34,52 @@ the same CLI. `@rig-c/review` remains the canonical package.
|
|
|
34
34
|
|
|
35
35
|
The package is 0.x: the CLI is the supported surface, and no importable API is promised yet (`exports` exposes only `package.json`).
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
Example stdout from `open` (instance values vary):
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
```sh
|
|
42
|
-
bun run check -- /absolute/path/to/output
|
|
43
|
-
bun run review -- /absolute/path/to/output
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
The adapter recognizes root `skeleton.model.json` or the fixed rigc build layout `build/skeleton.model.json` (root model takes precedence), root `motion.json`, `report.json`, `skeleton.json`/`.skel`/`.atlas`, source videos (`.mp4`, `.webm`, `.mov`), image filenames containing `strip`, `contact`, `sheet`, `filmstrip` or `띠`, and canonical `*pose*.json` sequences. Model discovery checks only those two named paths; it never recursively searches directories. A `rig-parts build` output root (`out/`: regular `parts.json` and `rig/rig.json`, a real `check/` directory and `check/build/skeleton.model.json`) that has neither model path itself opens as its `check/` package — the same package, identity and session as opening `out/check` — so `rig-review open <out>` works on a fresh build without copying `check/build` anywhere. Model page images resolve relative to the selected model, while motion and references remain relative to the opened folder. It builds the normalized review manifest in memory and reads source files without changing them. Motion/report/skeleton evidence links are read only. A recognized `rig-pose-report/1` also supplies read-only event markers: frame-only events retain their producer interval and explicitly lack seek until a declared clock is present; explicit event seconds can seek. Empty reported categories show none, while missing fields remain missing. Report markers prefill known feedback coordinates and actual bone/slot names without creating a review decision or motion keys. It exposes each matching video/strip separately, without guessing synchronization or source-to-rig bindings.
|
|
47
|
-
|
|
48
|
-
When the report declares `fps` plus `frame0` (seconds offset) and a frame count, or exact `frameTimes[]`, the matching animation also shows a producer-frame readout and frame seek controls. Without a bounded declared clock it shows `rate unknown`; current unchanged v19 has no clock. Sample times come only from the report, and feedback context records the recalculated frame or null.
|
|
49
|
-
|
|
50
|
-
A raw `skeleton.json`/`.skel` can still be inspected as evidence, but cannot play through the compiled-model runtime. Playback requires an upstream gated rigc build's `skeleton.model.json` with portable local page images inside the opened folder. `check` validates the delivery contract and model envelope, not the upstream compiler gate or visual quality.
|
|
51
|
-
|
|
52
|
-
For a curated handoff, [REVIEW_PACKAGE.md](docs/REVIEW_PACKAGE.md) documents optional `review.json` with named checkpoints, references and angle axes:
|
|
53
|
-
|
|
54
|
-
```sh
|
|
55
|
-
bun run review -- /absolute/path/to/package/review.json --checkpoint silhouette
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
The server prints the exact link, for example `http://127.0.0.1:4317/?checkpoint=silhouette#cap=…`. The `#cap=` fragment is this server's write capability (see [Managed front and write capability](#managed-front-and-write-capability)). `--port 0` selects a free port. It binds only `127.0.0.1` and never opens a browser automatically. A folder containing `review.json` uses that explicit manifest.
|
|
59
|
-
|
|
60
|
-
## Compare two outputs
|
|
61
|
-
|
|
62
|
-
Open two compiled packages against one explicitly selected local reference video:
|
|
63
|
-
|
|
64
|
-
```sh
|
|
65
|
-
bun run review -- compare /absolute/output-a /absolute/output-b --reference /absolute/reference.mp4 --animation dance
|
|
66
|
-
bun run check -- compare /absolute/output-a /absolute/output-b --reference /absolute/reference.mp4 --animation dance
|
|
67
|
-
bun run feedback -- compare /absolute/output-a /absolute/output-b --reference /absolute/reference.mp4 --animation dance
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
Both models must contain the named animation. The video is a separate read-only CLI input, served only through a fixed endpoint; it is never inserted into either package. A separate compare page uses one video playhead to seek both rigs at the same time. Frame readouts use each side's explicit producer clock; no video FPS or missing clock is inferred.
|
|
71
|
-
|
|
72
|
-
Compare feedback requires side `a` or `b` and carries both package source hashes, both exact rig/motion identities, the shared video hash and animation. A changed input on either side or in the video refuses saved feedback until restart. State stays outside all source inputs. Single-package commands and sessions retain their existing contract. See [the comparison contract](docs/REVIEW_PACKAGE.md#two-package-comparison).
|
|
73
|
-
|
|
74
|
-
## Feedback ownership
|
|
75
|
-
|
|
76
|
-
Opening leaves status **pending**. The person explicitly saves **approved**, **changes-requested**, or **pending**, checkpoint outcomes/notes, and feedback containing an inclusive frame interval, a bone/slot/rig target, defect/quality category and one-line summary. Frame numbers are entered explicitly; the app does not invent a source FPS. **Write review**, at the top right, opens the review dialog: review status, reviewed source, selected checkpoint, structured feedback and the frame-strip export. Its **Submit review** saves that explicit status and feedback; closing the dialog keeps the unsubmitted draft. The page has no feedback export buttons: `rig-review feedback --format json` and `--format issue` (below) are the export path, and carry the original source binding and reproducible inspection context. Each submission records `reviewedSource`: package revision, actual viewer/runtime versions, and separate exact-byte SHA-256 plus path for the reviewed rig and motion; an absent artifact stays null. An active compiled model is the rig identity; raw skeleton JSON is evidence with playback unavailable.
|
|
77
|
-
|
|
78
|
-
New submissions validate bone/slot names against the exact loaded model; the target field offers those names. A rig target names the package id. Source-only packages cannot validate a bone/slot target and say so explicitly. When a matching animation has a declared producer clock, the inclusive interval cannot end beyond the last declared sample at or before the actual clip end. Unknown clocks keep manual frame intervals without inventing a limit. `context.interval` records the chosen interval; inspection `time`/`frame` remain separate. Older invalid feedback stays readable with per-row warnings and exports its original evidence, but cannot be resubmitted until corrected or removed.
|
|
79
|
-
|
|
80
|
-
Feedback lives under `~/.local/state/rig-c-review/`, separate from delivered files. Identity, revision and a streaming SHA-256 of the normalized manifest plus declared files/model pages bind each saved session. Changed content starts a new pending session, even without a revision bump. Restart the server after changing inputs. Saving or exporting feedback never modifies a model, authors correction keys, writes motion output or publishes an issue automatically.
|
|
81
|
-
|
|
82
|
-
After the person submits, the producing agent retrieves the stored review without needing the hashed state filename:
|
|
83
|
-
|
|
84
|
-
```sh
|
|
85
|
-
bun run feedback -- /absolute/path/to/output
|
|
86
|
-
# Or the same explicit review.json originally opened:
|
|
87
|
-
bun run feedback -- /absolute/path/to/package/review.json
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
This read-only command prints the session JSON, including reviewed artifact identity and feedback. The exact current-tool session wins when present. After a viewer/runtime upgrade, if that exact session is absent, the CLI can retrieve the latest saved submission over the same content with its original recorded versions and `retrieval: {kind:"prior-tools", toolsChangedSince:true, matchingSubmissions, currentTools}`. It does not migrate approval into the current UI or change saved bytes. Changed inputs still return a fresh pending session; a prior submission is never borrowed across content, revision or reviewed-artifact changes. The same lookup applies to `feedback compare`, preserving both recorded side bindings, reference identity and animation. See [historical retrieval](docs/REVIEW_PACKAGE.md#historical-feedback-retrieval) for selection and validation rules.
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
For grouped issue-ready text, use the same input/reference binding with `--format issue`:
|
|
94
|
-
|
|
95
|
-
```sh
|
|
96
|
-
rig-review feedback /absolute/output --reference /absolute/original.mp4 --format issue
|
|
97
|
-
rig-review feedback /absolute/a --compare /absolute/b --reference /absolute/original.mp4 --animation dance --format issue
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
The header counts feedback records by defect/quality and by build; blocks group by category, then exact target kind/name. Overlapping intervals and A/B sides remain distinct items. Each block retains its id, inclusive interval, one-line summary and selected/full paired provenance. Historical tool identities and validation warnings remain visible; output never migrates approval, writes state or publishes an issue. Default output and `--format json` remain the existing session JSON. Only feedback accepts `--format`, with `json` or `issue` as its value. These two formats are how submitted feedback leaves the tool; the browser page offers no JSON or Markdown download.
|
|
101
|
-
|
|
102
|
-
## Development
|
|
103
|
-
|
|
104
|
-
From a clone:
|
|
105
|
-
|
|
106
|
-
```sh
|
|
107
|
-
bun install
|
|
108
|
-
bun run demo
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
Open the printed loopback URL. The bundled original lantern study has source art, an explicitly synthetic canonical pose-sequence sample and an original synthetic chime. It has no compiled model: playback remains unavailable until a real model is supplied. Inside a clone, `bun run review -- <dir>`, `bun run check -- <dir>` and `bun src/cli.ts <command>` are the same CLI as `npx rig-review`.
|
|
112
|
-
|
|
113
|
-
```sh
|
|
114
|
-
bun run typecheck
|
|
115
|
-
bun test
|
|
116
|
-
bun run build
|
|
117
|
-
bun run smoke
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
`smoke` packs this tree, installs the tarball into an empty project, and runs the installed bin: `--help`, `open --help`, `check` on the bundled demo, and a served page whose browser bundle is built from the installed files. A file missing from `files` in `package.json` fails there, not after publishing.
|
|
121
|
-
|
|
122
|
-
The server bundles `web/main.ts` at startup using the pinned installed runtime, with no CDN. `build` verifies that browser bundle in memory. CI checks types, focused path/state/API tests, bundling and the demo package. Follow AGENTS.md for contributor verification policy. For a live run against the installed producers, [docs/LIVE_FIXTURE.md](docs/LIVE_FIXTURE.md) gives the public package, the producer bench and the open line that reaches `baseline: reproduced`. [DESIGN.md](docs/DESIGN.md) distinguishes the runnable foundation from future modeling touch-up and preserves the illustrative screen concept.
|
|
123
|
-
|
|
124
|
-
### Releasing
|
|
125
|
-
|
|
126
|
-
Releases are cut by `.github/workflows/release.yml`. On every push to main, release-please keeps a `release: vX.Y.Z` pull request whose version comes from the conventional-commit subjects since the last release. Merging it tags the version, creates the GitHub release, runs typecheck and the install smoke on the tagged tree, and publishes to npm through trusted publishing (OIDC) with provenance. No npm token is stored. Nothing is published until a release PR is merged. Before publishing, the push path resolves the cut tag's commit and requires it to equal the run's `GITHUB_SHA`; a re-run with an older SHA refuses `PROVENANCE_SHA_MISMATCH`, reports both SHAs and leaves the tag cut for recovery. Checkout uses the validated commit SHA. A manual dispatch only re-publishes an existing tag whose GitHub release release-please created, whose commit is on main, and whose version the registry does not serve yet. It must be dispatched on the tag itself (`gh workflow run release.yml --ref vX.Y.Z -f tag=vX.Y.Z`): npm provenance names the run's commit, so a run whose commit is not the tag's is refused `PROVENANCE_SHA_MISMATCH`. Only a tag cut after that check carries it.
|
|
127
|
-
|
|
128
|
-
## Frame strip export
|
|
129
|
-
|
|
130
|
-
The strip panel exports the explicitly entered inclusive feedback interval, or all current in-memory feedback intervals, as a PNG or JSON download. Batch selections include unsaved annotations; they do not submit or approve a review. Choose a declared reference video in single-package mode and an explicit clock basis A/B in compare mode. Every selected feedback interval must identify the selected animation. Separate intervals remain separate strip groups, including overlaps.
|
|
131
|
-
|
|
132
|
-
The same export is available without a browser:
|
|
133
|
-
|
|
134
|
-
```sh
|
|
135
|
-
bun src/cli.ts strip /absolute/package/review.json --reference source-video --animation dance --frames 176:184 --out /absolute/external/strip.png
|
|
136
|
-
bun src/cli.ts strip compare /absolute/a /absolute/b --reference /absolute/reference.mp4 --animation dance --basis b --frames 176:184 --out /absolute/external/compare.png
|
|
137
|
-
bun src/cli.ts strip /absolute/package/review.json --reference source-video --animation dance --all-feedback --json
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
Single `--reference` is a declared video **id**; compare uses the explicitly selected local video path. `--all-feedback` reads only the exact current saved session, and retains each item's side/interval. `--json` produces a selection/provenance plan without rendering; optionally add `--out`. PNG writes an additional `.png.json` provenance sidecar with the actual decoded samples and PNG hash. Destinations must be new files outside every input package, in existing directories without symlink parents. The server exposes download blobs, never a destination-path API.
|
|
141
|
-
|
|
142
|
-
Strip times require the selected report's exact `frameTimes` and matching animation. Missing or uniform-only clocks refuse PNG with `Report has no frameTimes — regenerate with pose 0.30.0 or later`; JSON retains requested frames with `time:null`. The other compare side may lack a clock: it can still be posed at the common seconds, while its frame label stays unknown. No frame rate is inferred from frame count, duration or video metadata.
|
|
143
|
-
|
|
144
|
-
PNG export uses the pinned published `@rig-c/play` canvas player and real Node Canvas2D, with `ffmpeg` (5.1 or later, for `-fps_mode`) and `ffprobe` available on PATH for reference decoding. Missing capabilities fail by name. Each requested sample chooses the nearest actual decoded reference PTS (ties earlier); both rigs seek that actual PTS using the viewer's fixed step. JSON distinguishes requested time, actual PTS, delta, decoded frame index, actual player times and independently known producer frames. Requests outside the common domain refuse instead of clamping. The PNG shows source rows, chronological frame columns, and full source hashes/revisions/tool and renderer versions. Batch groups stack vertically. Exports are bounded to64 intervals,256 total frames,64 megapixels and16384 pixels per dimension; larger selections must be split.
|
|
145
|
-
|
|
146
|
-
The footer fits its measured text while retaining full hashes. RGB/dark-tint features unsupported by the published Canvas2D path refuse with `STRIP_UNSUPPORTED_TINT`; they are not silently omitted.
|
|
147
|
-
|
|
148
|
-
## One-command owner handoff
|
|
149
|
-
|
|
150
|
-
`open` validates and hashes an existing folder, starts a background loopback
|
|
151
|
-
instance or reuses the exact matching one, prints its hash-bound URL, and exits.
|
|
152
|
-
By default the URL goes through the managed front at `127.0.0.1:4318`; `--direct`
|
|
153
|
-
prints the instance's own port instead (see [Managed front and write capability](#managed-front-and-write-capability)):
|
|
154
|
-
|
|
155
|
-
```sh
|
|
156
|
-
bun run open -- /absolute/output --reference /absolute/original.mp4
|
|
157
|
-
# Installed executable mapping:
|
|
158
|
-
rig-review open /absolute/output --reference /absolute/original.mp4 --animation dance
|
|
159
|
-
rig-review feedback /absolute/output --reference /absolute/original.mp4
|
|
160
|
-
rig-review open /absolute/a --compare /absolute/b --reference /absolute/original.mp4 --animation dance
|
|
161
|
-
rig-review feedback /absolute/a --compare /absolute/b --reference /absolute/original.mp4 --animation dance
|
|
162
|
-
# Producer handoff (single review only): the config, parts and inspected rig an Apply rebuilds from
|
|
163
|
-
rig-review open /absolute/output --config /absolute/output/rig/config.json [--parts /absolute/output/rig/out] [--source <png> --full <dir> [--head <dir>]] [--keypoints <file>]
|
|
164
|
-
rig-review feedback /absolute/output --config /absolute/output/rig/config.json
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
`--config`, `--parts`, `--source`, `--full`, `--head` and `--keypoints` declare the producer
|
|
168
|
-
handoff (see `docs/REVIEW_PACKAGE.md`, *Producer handoff*). They win over the
|
|
169
|
-
review.json `producer` block, which wins over discovery (`config.json`, then
|
|
170
|
-
`rig/config.json` inside the opened folder, then `../config.json` beside an opened
|
|
171
|
-
`out/`, named as discovered outside the folder). For the `check/` package of a
|
|
172
|
-
`rig-parts build` output the next places are the build's own config: `config.json`
|
|
173
|
-
in the build root, else beside it (`<out>/../config.json`, where `rig-parts build
|
|
174
|
-
--config <dir>/config.json --out <dir>/out` leaves it); the parts directory is the
|
|
175
|
-
build root. Declaring `--config` at exactly one of those two files is taken the same
|
|
176
|
-
way, and that file's folder is then the root for the other flags. Every other path must sit inside the
|
|
177
|
-
package root; a path outside it or a symlink that leaves it is refused by name.
|
|
178
|
-
The handoff files are hashed into the reviewed identity, so `feedback` and
|
|
179
|
-
`control` take the same flags to retrieve the same session, and changing the
|
|
180
|
-
config bytes starts a new pending review. `--keypoints` (#111) is the body-18 keypoint
|
|
181
|
-
file a cloth recipe Apply hands to `rig-parts propose`; it is hashed into the identity
|
|
182
|
-
only when declared, so a handoff without it keeps its identity. `--parts`,
|
|
183
|
-
`--source`/`--full`/`--head` and `--keypoints` need `--config`; `--source` and `--full` go together; compare takes none of them.
|
|
184
|
-
|
|
185
|
-
`open` chooses an explicit `--animation` first, then the normalized report's
|
|
186
|
-
`animation` after validating actual model membership, then a unique actual clip
|
|
187
|
-
(or unique common clip in compare). Thus `rig-review open /absolute/output
|
|
188
|
-
--reference /absolute/original.mp4` opens a report-bound `dance` without the flag
|
|
189
|
-
and prints `animation: dance (from report.json)` on stderr. Conflicting A/B reports,
|
|
190
|
-
a stale report name or multiple clips without a report refuse by name; no first
|
|
191
|
-
entry, filename or frame rate supplies a guess. Explicit invalid names never fall
|
|
192
|
-
back. The selected clip actually initializes single playback, while an explicit
|
|
193
|
-
checkpoint link or later manual choice takes priority. Incompatible automatic
|
|
194
|
-
checkpoints do not apply another clip's time or axis. Static/source-only packages
|
|
195
|
-
remain inspectable without claiming clip selection; explicit unvalidatable clips
|
|
196
|
-
refuse. Single review sessions remain package-wide; animation selection does not
|
|
197
|
-
create an animation-specific approval identity. Opening never
|
|
198
|
-
approves a review. The command prints the matching feedback retrieval command on
|
|
199
|
-
stderr; stdout contains the URL alone. A selected external video takes precedence
|
|
200
|
-
at automatic startup; an explicit checkpoint link or manual checkpoint selection
|
|
201
|
-
still chooses that checkpoint's reference.
|
|
202
|
-
|
|
203
|
-
The selected video is a separate CLI capability, not a manifest path escape or a
|
|
204
|
-
file copied into the source package. Its name and streaming byte hash, plus the
|
|
205
|
-
base package hash, appear as `referenceBinding` in single review responses,
|
|
206
|
-
sessions and feedback exports. Use the same `--reference` when retrieving that
|
|
207
|
-
submission; omitting it reads the separate reference-free identity. Changed
|
|
208
|
-
package or reference bytes start pending state. Existing historical retrieval
|
|
209
|
-
preserves earlier recorded tool versions over exactly the same reference content.
|
|
210
|
-
|
|
211
|
-
The single strip panel can export the selected external video using that same
|
|
212
|
-
binding. Headless equivalent (the original declared-reference form stays valid):
|
|
213
|
-
|
|
214
|
-
```sh
|
|
215
|
-
rig-review strip /absolute/output --reference-video /absolute/original.mp4 --animation dance --frames 176:184 --out /absolute/external/strip.png
|
|
216
|
-
```
|
|
217
|
-
|
|
218
|
-
Only tool-owned instances with matching content, canonical input locations, tool
|
|
219
|
-
fingerprint and a private capability handshake are reused. The URL hash is
|
|
220
|
-
checked before serving the app; a stale link never selects another instance's
|
|
221
|
-
package. Input hashes are freshly computed, not trusted from mtime caches. Idle
|
|
222
|
-
open-managed workers stop after 30 minutes. Their private registry, requests and
|
|
223
|
-
logs live beside feedback under `~/.local/state/rig-c-review/open/`; nothing is
|
|
224
|
-
written into supplied folders. `RIG_REVIEW_STATE_DIR` selects another state root
|
|
225
|
-
outside all input packages, judged by its canonical location (symlinks in the path
|
|
226
|
-
and its existing ancestors resolved). Existing foreground review commands still work.
|
|
227
|
-
|
|
228
|
-
No browser is opened, no LAN listener is added, and no misleading phone/QR URL is
|
|
229
|
-
printed: every current instance binds only `127.0.0.1`.
|
|
230
|
-
|
|
231
|
-
The printed feedback retrieval command uses POSIX shell quoting for literal path
|
|
232
|
-
arguments. It is not a PowerShell or cmd.exe command string.
|
|
233
|
-
|
|
234
|
-
### Launcher contract
|
|
235
|
-
|
|
236
|
-
A launcher may start `rig-review open` as a child process and rely on its stdout,
|
|
237
|
-
stderr and exit code alone:
|
|
238
|
-
|
|
239
|
-
1. On success stdout is exactly one line, the URL. `open` never writes anything
|
|
240
|
-
else to stdout.
|
|
241
|
-
2. On success stderr carries the human lines in this fixed order: for the `check/`
|
|
242
|
-
package of a `rig-parts build` output, `Package: rig-parts build check package <path>`
|
|
243
|
-
and the handoff line (`Handoff <how>: <config path> sha256 <hash> · parts <dir>`, or
|
|
244
|
-
`Handoff: <refusal>`); the animation diagnostic (when a clip is selected), the control notice (with `--control`),
|
|
245
|
-
then the copyable retrieval line `Review started|reused; feedback: rig-review feedback …`.
|
|
246
|
-
3. A refusal exits with code 1, writes nothing to stdout, and its first stderr line
|
|
247
|
-
starts with a name, followed by a colon and the explanation:
|
|
248
|
-
- `OPEN_INPUT_MISSING <path>`: the input or `--compare` path does not exist, or is neither a directory nor a regular file.
|
|
249
|
-
- `OPEN_INPUT_UNRECOGNIZED <path>`: the folder holds no recognized model, skeleton, motion, report, reference or pose sequence and is not a `rig-parts build` output root, or the file is not a JSON manifest.
|
|
250
|
-
- `OPEN_REFERENCE_REQUIRED`: `--compare` was given without an explicit `--reference`.
|
|
251
|
-
- `OPEN_ANIMATION_UNKNOWN <name>`: the explicit or report-named clip (JSON-quoted) is not in the compiled model.
|
|
252
|
-
- `OPEN_ANIMATION_AMBIGUOUS`: no flag or report selects one clip, or the A/B reports conflict.
|
|
253
|
-
- `OPEN_ANIMATION_UNVALIDATABLE`: a clip was requested but no compiled model can validate it.
|
|
254
|
-
- `OPEN_OPTION_INVALID <flag>`: an unknown, duplicate, empty or value-less option, a handoff flag without `--config`, `--source` without `--full` (or the reverse), or a handoff flag with `--compare`.
|
|
255
|
-
- `OPEN_STATE_INSIDE_PACKAGE <package | build root | handoff root>`: the state directory (`RIG_REVIEW_STATE_DIR` or the default), resolved through symlinks, lies inside an input package, the `rig-parts build` root a check package belongs to, or the producer handoff's root.
|
|
256
|
-
- `OPEN_HANDOFF_INVALID <flag or path>`: a declared or discovered producer handoff path is refused — `HANDOFF_PATH_OUTSIDE_ROOT`, `HANDOFF_SYMLINK_ESCAPE`, `HANDOFF_INPUT_MISSING`, `HANDOFF_INPUT_INVALID` or `HANDOFF_PATH_INVALID`, named after the colon.
|
|
257
|
-
4. Exit codes: 0 applied, 1 refused. (`control` keeps its own exit 2 for an
|
|
258
|
-
unapplied command.)
|
|
259
|
-
|
|
260
|
-
Package validation and worker startup refusals keep their existing messages; the
|
|
261
|
-
named refusals above are decided before any state is written.
|
|
262
|
-
|
|
263
|
-
### Managed front and write capability
|
|
264
|
-
|
|
265
|
-
`open` is **managed by default**. It ensures one detached loopback front at
|
|
266
|
-
`127.0.0.1:4318` (`RIG_REVIEW_FRONT_PORT` overrides) for the state directory and
|
|
267
|
-
prints `http://127.0.0.1:4318/instances/<key>/?input=…&handoff=…#cap=…`. Every
|
|
268
|
-
inspection therefore shares one stable browser origin, so Settings, panel sizes and
|
|
269
|
-
source-bound drafts survive a reopen. The front only routes: it takes the worker
|
|
270
|
-
address from that key's private open record, allows a closed per-method route
|
|
271
|
-
list, bounds bodies (16 KiB control, 1 MiB session/strip) and upstream time,
|
|
272
|
-
limits concurrent upstream streams, re-verifies the worker's nonce on every
|
|
273
|
-
request, and refuses an unknown, expired or replaced instance by name
|
|
274
|
-
(`FRONT_INSTANCE_UNKNOWN`, `FRONT_INSTANCE_EXPIRED`, `FRONT_INSTANCE_REPLACED`).
|
|
275
|
-
Worker error text is not passed through; failures become named `FRONT_*` codes.
|
|
276
|
-
Package assets keep their `sandbox` CSP through the front. It never reads packages.
|
|
277
|
-
Idle fronts exit after 30 minutes once no live instance remains.
|
|
278
|
-
|
|
279
|
-
If another listener holds the front port, `open` fails with
|
|
280
|
-
`FRONT_PORT_BUSY: 127.0.0.1:4318 is held by another listener; use --direct` and
|
|
281
|
-
does not fall back silently. `open --direct` keeps the per-instance port and
|
|
282
|
-
prints that worker's own URL.
|
|
283
|
-
|
|
284
|
-
One origin cannot isolate scripts that run on it, so writes are bound to a
|
|
285
|
-
per-instance capability rather than to the page's origin or the key in its path.
|
|
286
|
-
Each worker mints a 256-bit capability kept only in its private 0600 open record.
|
|
287
|
-
`open` (managed or direct) and the foreground `review`/`review compare` commands
|
|
288
|
-
print it in the `#cap=` fragment, which browsers never send to a server. That
|
|
289
|
-
printed link goes only to the terminal of the command that started the instance.
|
|
290
|
-
Output meant for a log does not carry it: the install smoke (`bun run smoke`, whose output CI publishes) prints the same
|
|
291
|
-
URL as `#cap=<redacted>` and fails if any capability value reaches its stdout. The page
|
|
292
|
-
moves it into memory and that tab's `sessionStorage`, then removes the fragment
|
|
293
|
-
from the address bar. Every mutation sends it as `X-Review-Capability`; the front
|
|
294
|
-
checks it before forwarding and the worker compares it again in constant time
|
|
295
|
-
before its existing same-origin check. No endpoint returns it, so a page opened
|
|
296
|
-
for one instance cannot submit to another. A copied view link (without the
|
|
297
|
-
fragment) opens read-only and says *Read-only view: reopen with rig-review open to
|
|
298
|
-
edit*; run `open` again for a writable link.
|
|
299
|
-
|
|
300
|
-
## Control the current inspection page
|
|
301
|
-
|
|
302
|
-
Opt in once when opening a fixed package, then keep that page open:
|
|
303
|
-
|
|
304
|
-
```sh
|
|
305
|
-
rig-review open /absolute/output --reference /absolute/original.mp4 --control
|
|
306
|
-
rig-review control /absolute/output --reference /absolute/original.mp4
|
|
307
|
-
```
|
|
308
|
-
|
|
309
|
-
The second command reads the connected page's actual inspection state and its
|
|
310
|
-
`pageId`/`revision`; it does not open a browser or start another worker. A page
|
|
311
|
-
learns from `GET /api/access` (`control: true|false`) whether its instance serves
|
|
312
|
-
control; an instance opened without `--control` is never asked for
|
|
313
|
-
`/api/control/config`. To change
|
|
314
|
-
that page, use the observed values and a closed inspection command:
|
|
315
|
-
|
|
316
|
-
```sh
|
|
317
|
-
rig-review control /absolute/output --reference /absolute/original.mp4 --page '<pageId>' --expected-revision 0 --request-id theme-1 --command '{"type":"theme","value":"light"}'
|
|
39
|
+
```text
|
|
40
|
+
http://127.0.0.1:4318/instances/<key>/?input=…&handoff=…#cap=…
|
|
318
41
|
```
|
|
319
42
|
|
|
320
|
-
|
|
321
|
-
pages require an explicit `--page`. The URL and source binding stay unchanged;
|
|
322
|
-
existing ordinary servers do not become controllable. Starting the opt-in worker
|
|
323
|
-
is a one-time handoff, not a new link for each parameter change.
|
|
324
|
-
|
|
325
|
-
Commands cover appearance, automatic layout, synchronized or independent pane
|
|
326
|
-
cameras, playback, single animation, time and actual inspection layers. Camera
|
|
327
|
-
pan is measured in viewport CSS pixels (right/down positive). New inspections
|
|
328
|
-
start with camera sync on: zoom relative to fit and pan normalized by each pane's
|
|
329
|
-
viewport move all visible measurable reference/rig panes together. Turn sync off
|
|
330
|
-
with `{"type":"camera-sync","value":false}` to inspect each pane independently;
|
|
331
|
-
turning it off preserves current views. Turning it on aligns the visible panes
|
|
332
|
-
to the last active pane. Camera/fit targets are `rig|reference` in single review
|
|
333
|
-
and `a|b|reference` in compare. For legacy clients, compare `target:"rig"` explicitly
|
|
334
|
-
moves both A/B even when sync is off; its CSS readback aliases A. Comparison retains its
|
|
335
|
-
fixed animation and actual video-master seek path. A single-video time request
|
|
336
|
-
uses the actual decoded timestamp for both reference and rig; the applied
|
|
337
|
-
acknowledgement reports that time, which may differ from the requested seconds.
|
|
338
|
-
Unsupported features, stale
|
|
339
|
-
page/source/revision, unsubmitted feedback drafts and out-of-range values refuse
|
|
340
|
-
by name. No command changes a package/reference, authors motion, or submits or
|
|
341
|
-
approves feedback. Local controls and remote readback use the same inspection
|
|
342
|
-
state. A live view-link/history change supersedes pending remote work, and
|
|
343
|
-
commands refuse while that view is being restored.
|
|
344
|
-
|
|
345
|
-
Remote inspection readback also includes versioned numeric `view` coordinates
|
|
346
|
-
when the page can measure them: rig/world center and zoom, reference/image center
|
|
347
|
-
and zoom, each panel's current CSS viewport size and visible rectangle. World
|
|
348
|
-
coordinates use Y up; reference image coordinates use Y down. Compare reports
|
|
349
|
-
actual A and B rectangles separately; unavailable measurements are null. The
|
|
350
|
-
canonical view also reports `cameraSync` and `activeCamera`. These
|
|
351
|
-
are the same current cameras used by the local controls, rather than a second
|
|
352
|
-
simulated view. Camera commands still use the documented viewport CSS pan.
|
|
353
|
-
|
|
354
|
-
The CLI waits up to10 seconds for the page's actual acknowledgment. `queued` or
|
|
355
|
-
`dispatched` is not success; non-applied commands exit2 and may carry
|
|
356
|
-
`timedOut:true`. Reuse the same `--request-id` and original request to obtain its
|
|
357
|
-
receipt without applying it twice. `--wait-ms 0` returns the queued receipt;
|
|
358
|
-
`--wait-ms` is bounded to30000. A timeout does not mean an already delivered action
|
|
359
|
-
was rolled back. Tokens stay in private tool state, not URLs or command output.
|
|
360
|
-
|
|
361
|
-
## Keep and share an inspection
|
|
362
|
-
|
|
363
|
-
The view link stores the current numeric inspection in a bounded, versioned
|
|
364
|
-
`#review=` fragment. It includes the exact loaded source/model binding, selected
|
|
365
|
-
animation/time and reference selection, appearance/layers, camera sync and the
|
|
366
|
-
active pane, and each canonical camera center with visible bounds. Reloading a matching link restores a paused view;
|
|
367
|
-
it does not restart an idle loop or submit a review. Coordinates adapt to the
|
|
368
|
-
current viewport instead of replaying another window's CSS pan offsets.
|
|
369
|
-
Historical links without a sync mode restore with sync off to preserve their
|
|
370
|
-
saved views; fresh inspections start with sync on. The page keeps the current
|
|
371
|
-
fragment in the address bar as you inspect, so copying the address copies the
|
|
372
|
-
view; there is no separate copy button. Legacy modeling/motion
|
|
373
|
-
workspace selections are accepted without restoring a top-level workspace switch. A link
|
|
374
|
-
for a different source binding is refused.
|
|
375
|
-
|
|
376
|
-
Ordinary form inputs, selected panels, view state and unsent feedback/checkpoint
|
|
377
|
-
choices are kept in browser local storage for the same source. The draft scope
|
|
378
|
-
includes the source hash (`single:<id>:<sourceHash>`; compare uses both sides'
|
|
379
|
-
id and hash), so two revisions of one package keep separate drafts. Opening a page
|
|
380
|
-
deletes no draft; saving evicts the least-recently-written drafts beyond 32, but
|
|
381
|
-
never one written within the last 24 hours. Browser
|
|
382
|
-
Settings (below) remain, while a source-bound appearance override does not
|
|
383
|
-
carry over. URL view state takes precedence over the locally saved view. Each
|
|
384
|
-
Inspector section's own scroll position is part of this source-bound draft.
|
|
385
|
-
|
|
386
|
-
Local recovery compares the original submitted server-session fingerprint,
|
|
387
|
-
including its update time. It cannot replace a newer server review: conflicting
|
|
388
|
-
or corrupt saved data remains available to download, with an explicit warning;
|
|
389
|
-
recovering a conflicting draft requires choosing a pending review. Storage
|
|
390
|
-
errors never count as a successful save. Nothing is written to the server or
|
|
391
|
-
input package merely by restoring a page. Browser storage is convenient local
|
|
392
|
-
recovery, not a durable backup; submit the review (and retrieve it with
|
|
393
|
-
`rig-review feedback`) when it must be kept.
|
|
394
|
-
Control capabilities are excluded from links and browser persistence.
|
|
395
|
-
|
|
396
|
-
## Workspace settings and panels
|
|
397
|
-
|
|
398
|
-
The review page has a left column (Parts explorer, with checkpoints and the
|
|
399
|
-
reference select folded above it), the stage, the Inspector below it with
|
|
400
|
-
**Setup** and **Animate** sections, and a status footer. The compare page
|
|
401
|
-
keeps sources on the left and the Parts explorer on the right. The page
|
|
402
|
-
header carries the **Setup** / **Animate** mode switch next to the package
|
|
403
|
-
title; it is the only way to change the Inspector's section. Setup is the
|
|
404
|
-
mesh-review composition (selected part, part presets, modeling-review
|
|
405
|
-
proposals) and holds nothing that seeks or plays; the angle inspection, the
|
|
406
|
-
transport and frame navigation are in Animate. **Write review** at the top
|
|
407
|
-
right opens the review dialog, a modal holding the review status, reviewed
|
|
408
|
-
source, selected checkpoint, structured feedback and frame-strip export, with
|
|
409
|
-
its own **Submit review**. Escape or Close dismisses it and returns focus to
|
|
410
|
-
**Write review**; the draft keeps saving whether the dialog is open or not,
|
|
411
|
-
and the footer and the dialog both show its state.
|
|
412
|
-
The left column's header (Parts title and search) stays fixed at the top;
|
|
413
|
-
the checkpoint strip, the part list and Settings scroll beneath it as one
|
|
414
|
-
body. A drawer keeps its title and close button in place while its body
|
|
415
|
-
scrolls. The per-inspection **Repeat idle** toggle sits in the Animate
|
|
416
|
-
transport row.
|
|
417
|
-
|
|
418
|
-
Animate's **Environment** block drives the player's physics while you
|
|
419
|
-
inspect: a **Wind** base (−100…100), **Breeze**, a **Gust** pulse
|
|
420
|
-
(peak 0…200, duration 0.1…20 s, **Repeat** every max(6, duration + 0.6) s)
|
|
421
|
-
and **Gravity** (0…4), applied through `player.environment` in the same frame
|
|
422
|
-
step that draws. The gust clock is the stepped player time, so pausing freezes
|
|
423
|
-
it. **Jolt** sends a translate-and-return impulse through `player.moved`;
|
|
424
|
-
with **Drag to trail** on, dragging the rig pane moves the rig's physics
|
|
425
|
-
instead of the camera (Space still pans). **Reset physics** returns the
|
|
426
|
-
runtime start values and re-poses from the current time, clearing the physics
|
|
427
|
-
history. Until a control is touched the player stays at the runtime start values. The
|
|
428
|
-
state is kept in the local draft and recorded in feedback as
|
|
429
|
-
`context.environment`; nothing is written to the model or motion. The compare
|
|
430
|
-
page does not have this block.
|
|
431
|
-
|
|
432
|
-
Below it, the **Physics tuning** panel (`#physics-panel`) edits the physics
|
|
433
|
-
constraints through the producer. Each row is a constraint of the inspected
|
|
434
|
-
model, matched to the handoff config's `constraints[]` entry of the same name
|
|
435
|
-
and kind; a row is editable only when every field the panel edits reads the
|
|
436
|
-
same on both sides. Rows the model has and the config does not declare (the
|
|
437
|
-
rig stage's `turn` twins), the config's transform constraints and any row that
|
|
438
|
-
does not map 1:1 are listed under **Not editable** with the reason. Picking a
|
|
439
|
-
row opens its editor: a slider and number field for each of `strength`,
|
|
440
|
-
`damping`, `inertia`, `mass`, `wind`, `gravity` and `mix` the config declares,
|
|
441
|
-
and toggles for the `x`, `y` and `rotate` components (0 is off). A **Chain**
|
|
442
|
-
(from bone parentage: links on parent bones that move the same components;
|
|
443
|
-
pick one from the list or with **Pick on stage**) takes a **Ramp**: one value
|
|
444
|
-
per link, linear from root to tip. Every edit is a config patch op at
|
|
445
|
-
`/constraints/<i>/<field>` that also records the constraint's `name`; the
|
|
446
|
-
pending ops are listed with a per-op **Revert** and kept in the local draft.
|
|
447
|
-
**Apply** sends them as one patch (`rig`, then `check`, about 20–50 s on a real
|
|
448
|
-
package); the scratch build reloads under the same wind, gust, jolt and gravity
|
|
449
|
-
state Apply was pressed in, and the readout shows the `ANIMATIONS_DO_NOT_FOLD`
|
|
450
|
-
and `TEXTURE_STRETCH` lines before and after. BEFORE for the inspected build
|
|
451
|
-
is its own check: once the baseline reproduces, review runs `rig-parts check`
|
|
452
|
-
on that baseline output in the background (about 16 s on a real package) and
|
|
453
|
-
stores the summary beside the baseline record, so a reload reads it; until it
|
|
454
|
-
finishes the line says "check running…", a producer refusal is shown verbatim,
|
|
455
|
-
and "not in check summary" remains only when the baseline did not reproduce. A
|
|
456
|
-
refusal is the producer's line verbatim (`CONFIG_CONSTRAINT_*`,
|
|
457
|
-
`CONFIG_NAME_RESOLVES`, rigc's `A23` for a mass of 0) and the stage is
|
|
458
|
-
unchanged. **Reset** returns to the inspected build and clears the pending ops.
|
|
459
|
-
Submit refuses a review written on an applied build whose check failed
|
|
460
|
-
(`EDIT_SUBMIT_UNCHECKED`).
|
|
461
|
-
|
|
462
|
-
With pinned published Play **1.14.0**, **Preview pending physics** previews
|
|
463
|
-
`strength`, `damping`, `inertia`, `wind`, `gravity` and `mix` through the live
|
|
464
|
-
player's public `setPhysicsParameters` / `readPhysicsParameters` /
|
|
465
|
-
`clearPhysicsParameters`. Slider input updates the standing patch while dragging;
|
|
466
|
-
the label separates the pending override from the last evaluated effective values.
|
|
467
|
-
The next evaluation uses it after authored keys and sliders, before physics. While
|
|
468
|
-
paused, the page explicitly re-poses the held time and draws; it never seeks or
|
|
469
|
-
resets history to preview. `mass` and `x` / `y` / `rotate` stay Apply-only.
|
|
470
|
-
**Clear preview** disables preview and removes its overrides, keeping the pending
|
|
471
|
-
ops and their Apply bytes. Authored values resume on the next evaluation; the
|
|
472
|
-
integrated history is retained, so clearing does not undo motion caused earlier.
|
|
473
|
-
Apply and a model replacement clear the preview; Reset clears both preview and
|
|
474
|
-
pending ops. Preview state is never recorded as applied or submitted.
|
|
475
|
-
|
|
476
|
-
Play retains the current standing patch across play and seek, with no history of
|
|
477
|
-
patch changes. A backward seek after an edit recomputes the past using the patch
|
|
478
|
-
standing now, rather than reproducing the old trajectory. A newly opened track
|
|
479
|
-
first evaluates its ordinary authored pose, then receives the patch as pending;
|
|
480
|
-
at seek time 0 its effective values can therefore still be authored until the next
|
|
481
|
-
evaluation. No live track means a named refusal; preview never creates one.
|
|
482
|
-
|
|
483
|
-
Below it, the **Idle tracks** panel (`#idle-tracks`) edits the idle through
|
|
484
|
-
the producer. It lists the handoff config's `motion.tracks`: a single track
|
|
485
|
-
opens amp, period, phase and base (base can be added or taken out); a chain
|
|
486
|
-
track opens one amp per link and lag, and draws a handle at each link on the
|
|
487
|
-
stage (drag up or down; arrow keys step 0.05°). Period is a select of the
|
|
488
|
-
divisors of `motion.duration` (`duration / 1 … 12`) — the only periods the
|
|
489
|
-
producer accepts — beside a field for typing one, which review's pre-check
|
|
490
|
-
refuses with the producer's own line (`CONFIG_PERIOD_DIVIDES_DURATION`). A
|
|
491
|
-
track is added by bone + property or by chain (it starts still: amplitude 0,
|
|
492
|
-
the whole idle's period) and removed with **Remove**; a second track on a
|
|
493
|
-
property already keyed (a chain link's `rotate`, a blink member) is refused
|
|
494
|
-
before Apply by the producer's `CONFIG_BONE_PROPERTY_KEYED_ONCE`. The blink's
|
|
495
|
-
`t` is a marker above the transport scrub, dragged along the timeline (arrow
|
|
496
|
-
keys step one 12 fps frame), with `squash` and `brow_drop` sliders. The
|
|
497
|
-
pending ops (`/motion/tracks/<i>/…`, `/motion/blink/…`) are listed with a
|
|
498
|
-
per-op **Revert** and kept in the local draft. While edits are pending the
|
|
499
|
-
**preview** drives the edited bones through the player's `adjust` with the
|
|
500
|
-
producer's sine on the bone the idle keys (`<bone>_ctl` under ctl keys, read
|
|
501
|
-
off the handoff rig). It evaluates from Play's `BoneAdjust` context's
|
|
502
|
-
`trackTime`, including seek replay, and the turn dial receives the same context.
|
|
503
|
-
It reads no displayed player clock and adds no compensating re-pose after seek.
|
|
504
|
-
With unchanged patch, animation, environment, reset and step partition, seek and
|
|
505
|
-
continuous evaluation agree bit for bit. It is labelled "preview — Apply decides"; it is never
|
|
506
|
-
recorded or submitted, and a property the inspected idle does not key yet is
|
|
507
|
-
not previewed (`IDLE_PREVIEW_TARGET_UNKNOWN`). **Apply** clears the preview and
|
|
508
|
-
sends one patch (`rig --idle-keys <the inspected rig's>`, then `check`, about
|
|
509
|
-
30 s on a real package); the built idle replaces the preview, and the
|
|
510
|
-
readout shows the `CHAIN_LAG`, `TIP_OVER_ROOT` and `BREATH_VISIBLE` lines and
|
|
511
|
-
check's `loop:` line before and after. **Reset** clears the preview and the
|
|
512
|
-
pending ops and returns to the inspected build.
|
|
513
|
-
|
|
514
|
-
Below that, the **Readings** block (`#readings`) runs the compiler's own
|
|
515
|
-
readings of the drawn animation: a **Folds** toggle, a **Pass-through**
|
|
516
|
-
cloth:leg pair and a **Seam** slot:slot pair (slot names suggested from the
|
|
517
|
-
model), an **Overlaps** toggle and an **Angle range** bone against a relative
|
|
518
|
-
bone. **Run readings** sends the chosen readings (names only) to
|
|
519
|
-
`POST /api/readings`; the server runs one `rigc render --animation <drawn>
|
|
520
|
-
--folds | --pass-through <cloth>:<leg> | --seam <a>:<b> | --overlaps |
|
|
521
|
-
--angle-range <bone>:<relative>` per reading on the inspected build and on the
|
|
522
|
-
latest scratch build of the session (if an Apply built one), into scratch
|
|
523
|
-
under the state directory (`readings/<package>/<build>/<key>/`; an identical
|
|
524
|
-
reading on the same build and compiler is reused; about 0.5–5 s each on a real
|
|
525
|
-
package). The frames each reading's JSON lists become ticks under the
|
|
526
|
-
transport scrub — inspected above, scratch below, in two colours — and
|
|
527
|
-
clicking a tick seeks to that frame's time (frame / fps). Each reading gets a
|
|
528
|
-
worst-frame line, `<build> · <reading> <pair>: worst at frame N — <figure> ·
|
|
529
|
-
<rigc's line>`, which also seeks; folds and overlaps list each folding mesh or
|
|
530
|
-
moving pair under it. `--angle-range` writes no file, so its ticks are the
|
|
531
|
-
lowest and highest frames its printed line names. An unknown slot or bone is
|
|
532
|
-
rigc's to refuse, and its line is shown verbatim. While the Environment
|
|
533
|
-
controls are applied, their current wind and gravity go along as a one-step
|
|
534
|
-
`--environment` schedule (only when the installed rigc's `render --help` lists
|
|
535
|
-
the flag). A reading is not a gate; nothing is written into the package. The
|
|
536
|
-
pairs of the last run are recorded in feedback as `context.readings`.
|
|
537
|
-
|
|
538
|
-
In the Environment block, **Use for readings** (issue #112) turns the controls
|
|
539
|
-
into a `rigc-environment/1` schedule from the clip's first frame — one step when
|
|
540
|
-
the wind is steady, else one step per 12 fps frame carrying the breeze and the
|
|
541
|
-
repeating gust (equal to the panel's wind at every frame time; between frames
|
|
542
|
-
rigc holds the frame's value, and the panel states how far that can be from the
|
|
543
|
-
live wind) — and sends it with readings and with Apply, whose check then runs
|
|
544
|
-
`rig-parts check --environment`. The wind clock restarts with the clip, so what
|
|
545
|
-
you watch is the world the readings and bars are taken in. **Export schedule**
|
|
546
|
-
downloads the same bytes; **Check under this world** runs `rig-parts check
|
|
547
|
-
--environment` on the build on the stage (needs `@rig-c/parts` 1.48.0) and shows
|
|
548
|
-
its `ANIMATIONS_DO_NOT_FOLD` and `TEXTURE_STRETCH` lines beside the same build's
|
|
549
|
-
default-world check. A refused schedule (`CHECK_ENVIRONMENT`) is shown verbatim.
|
|
550
|
-
Feedback records the schedule's bytes and sha256 as `context.environmentSchedule`;
|
|
551
|
-
nothing is authored into the rig.
|
|
552
|
-
|
|
553
|
-
Below Readings, the **Requirements** block (`#requirements`) declares what the
|
|
554
|
-
scene asks of the motion (issue #113): pick a kind (contact, follow, aim,
|
|
555
|
-
range, stretch, seam), an animation and the subject — the slot picked on the
|
|
556
|
-
stage pre-fills the slot, its mesh attachment and its bone — then set each
|
|
557
|
-
bar on a slider whose track marks the last measured value. **Check** sends the
|
|
558
|
-
typed requirements (names and numbers only) to `POST /api/requirements`;
|
|
559
|
-
review writes them as a `spine-parts-requirements/1` file (`spec`, `fps`,
|
|
560
|
-
`requirements`; never `may_fold`, scene targets or the part exception lists)
|
|
561
|
-
into scratch and runs the installed producer's `rig-parts check --rig <rig>
|
|
562
|
-
--out out --parts <handoff parts> --requirements requirements.json` on the
|
|
563
|
-
inspected rig spec or the latest scratch build. Each requirement gets one
|
|
564
|
-
line with its worst frame (click to seek); `REQUIREMENTS_*`,
|
|
565
|
-
`CHECK_REQUIREMENT_MET` and `CHECK_REQUIREMENT_MEASURABLE` lines are shown
|
|
566
|
-
verbatim. A kind the rig spec cannot offer (no mesh slot, no ik or transform
|
|
567
|
-
constraint, fewer than two slots) is disabled with the reason. The file rides
|
|
568
|
-
submitted feedback by hash and content as `context.requirements`.
|
|
569
|
-
|
|
570
|
-
The view bar is one row and keeps the layer toggles in reach in every section: **Images**
|
|
571
|
-
(`I`), **Mesh edges** (`M`) and **Bone points** (`B`). The keys do nothing while
|
|
572
|
-
a text field, textarea, select or editable text has focus, with Ctrl/Alt/Cmd
|
|
573
|
-
held, or on key repeat; Space stays pan and Enter stays frame entry. A disabled
|
|
574
|
-
toggle shows why as a tooltip, and the footer shows the same layer status. The
|
|
575
|
-
footer also gathers the message, player, layout, renderer, idle, remote control
|
|
576
|
-
and draft state; **View region** opens the numeric view. Side by side,
|
|
577
|
-
Reference and Rig are in the **View** menu at the right end of the view bar,
|
|
578
|
-
labelled with the current choice.
|
|
579
|
-
|
|
580
|
-
The side and bottom panels are resizable. The separators are thin (4px)
|
|
581
|
-
lines whose colour is never the scrollbar's; the grab area is wider than the
|
|
582
|
-
line. Drag a separator, or focus it
|
|
583
|
-
and use the arrow keys (Shift for larger steps) and Home/End. Escape, a
|
|
584
|
-
cancelled pointer or window blur during a drag returns the previous size. The
|
|
585
|
-
stage keeps a usable minimum. When the window is too narrow, the side columns
|
|
586
|
-
(left first on the compare page), and when it is too short, the Inspector, become explicit
|
|
587
|
-
drawers opened from buttons above the stage instead of shrinking to nothing. Responsive clamping
|
|
588
|
-
never overwrites the desired size, so a wider window restores it.
|
|
589
|
-
|
|
590
|
-
Settings, at the end of the left column's scrolling body, holds browser defaults: Theme, Sync cameras by
|
|
591
|
-
default, Repeat idle by default, and Reset layout. Defaults seed a new
|
|
592
|
-
inspection only. Theme is the page's only appearance control: it shows the
|
|
593
|
-
current inspection's appearance, and changing it applies it and saves it as the
|
|
594
|
-
default. A view link or source draft keeps its own appearance, camera
|
|
595
|
-
sync and idle choice, and restoring it neither changes the saved defaults nor
|
|
596
|
-
starts playback. Reset layout restores panel sizes and nothing else.
|
|
597
|
-
|
|
598
|
-
These defaults and desired panel sizes are one versioned, closed record in
|
|
599
|
-
browser `localStorage` (`rig-review-preferences/1`). It holds no source path,
|
|
600
|
-
hash, feedback, review decision or capability. The first load migrates the
|
|
601
|
-
earlier same-origin appearance preference. If storage is denied, unavailable
|
|
602
|
-
or the record is corrupt, the page uses defaults and every control keeps
|
|
603
|
-
working for that page; a corrupt record is refused rather than overwritten
|
|
604
|
-
until you change a setting. Several open tabs merge their writes per field, so
|
|
605
|
-
changing the theme in one tab does not undo a panel size changed in another.
|
|
606
|
-
Separate browser profiles keep separate preferences.
|
|
607
|
-
|
|
608
|
-
Browser storage is per origin. Managed opens share the front's one origin, so
|
|
609
|
-
Settings, panel sizes and drafts carry across reopens. `--direct` and foreground
|
|
610
|
-
serving use per-port origins and keep them separate.
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
## Inspect animation keys
|
|
614
|
-
|
|
615
|
-
Animate → **Transport** opens the read-only Timeline, Curves and Bone tree views of the build currently on stage. The timeline preserves every stored bone/slot track and key time; click a key to seek, or scrub the transport to move its playhead. Bone selection filters its direct tracks. Tree, Parts and stage selections stay synchronized; enable **Bone points** to pick a bone on stage. Selecting a bone highlights all directly attached Parts rows without inventing a selected slot.
|
|
616
|
-
|
|
617
|
-
Curves show the compiled skeleton’s exact linear, stepped or cubic Bézier segments and stored handles. The separately labeled **Runtime evaluation** cursor/readout uses Play’s ten-piece Float32 interpolation, which can differ from the cubic. Its local value includes setup rotation/translation and scale multiplication, in stored timeline order, before constraints and previews. Rotation is unwrapped local degrees. **Inspect time** accepts exact seconds; frame labels require the existing explicit clock. These views author no keys and change no package, motion, patch or review format. See [animation inspection](docs/ANIMATION_INSPECTION.md) for the numeric validation and reproduction commands.
|
|
618
|
-
|
|
619
|
-
## Inspect a part
|
|
620
|
-
|
|
621
|
-
The Parts explorer (left column; right on the compare page) selects an actual model slot on a specific single/A/B side. All rows follow the public held snapshot's exact draw-order z, including hidden/null slots. Search matches the exact source slot names: a row is shown when its name contains the typed text, case-sensitively, with no case folding or normalization. Arrow keys/Home/End move between result buttons, Enter/Space selects, and Escape returns to search. Whole rig restores full drawing. Selecting a drawn row fits its conservative world bounds within the existing camera zoom range; Sync cameras applies the same normalized camera action to every visible pane, while independent mode changes only that selected pane. Hidden/empty geometry reports `PART_FIT_UNAVAILABLE` and keeps the camera.
|
|
622
|
-
|
|
623
|
-
Solo preserves the full held pose's resolved clips and filters only drawing. In context, the whole rig remains visible with an amber dashed conservative selection box; this box is geometry/clip bounds, not exact source-alpha coverage. Selection changes no track, skin, set, motion, source asset or review decision. The selected slot's side/mode survives source-bound links and local drafts. The Setup section shows actual posed mesh counts or region corners, page/region, current skin/set context and declared attachment candidates. **Use slot for feedback** explicitly prefills the target; selecting a row alone submits nothing.
|
|
624
|
-
|
|
625
|
-
The pinned published `@rig-c/play`1.14.0 supplies `slotIdentity()` and frame generation. The explorer atomically retains the public identity snapshot with the same-generation held frame: exact attachment key, owning skin, active set/skin, full z order and explicit hidden/no-attachment/timeline-null/set-hide reasons. Hidden/null slots keep null keys with their reported reason; declarations and texture/UV names never replace that public result. An identity-known attachment need not have a visible primitive or positive fit bounds. The current UI follows its selected slot as animation changes, updating the displayed identity to the actual held frame.
|
|
626
|
-
|
|
627
|
-
Source-bound links/drafts and explicit feedback preserve that side/slot plus exact attachment/owning skin/set/active skin/null reason. A restored explicit target is checked after restoring its animation/time/set and drawing; `PART_IDENTITY_MISMATCH` rolls back rather than silently substituting another key. Older slot-only links remain readable and resolve from the current authoritative snapshot. Runtime generation is current observation/feedback evidence, never a persistent identity. Named vertex weights and original source alpha remain unavailable in this inspection; packed pages are not original part images.
|
|
628
|
-
|
|
629
|
-
This explorer is inspection. Producer-backed optimization and candidate Apply/undo/export remain separate work; the explorer does not generate mesh, rig or motion bytes.
|
|
630
|
-
|
|
631
|
-
## Chest regions (designate, rebuild, watch)
|
|
632
|
-
|
|
633
|
-
In **Setup**, the view bar's **Chest regions** button (pressed state; hidden in Animate) opens the chest-region editor bar directly under the view bar and enables editing on the rig pane. The bar has an **R** and an **L** group, each with **X**, **Y**, **R** (core radius, 6–120) and **Band** (4–120) in crop pixels, x screen-right and y down; **Mirror R → L** copies R's y, core radius and band to L; **Download JSON**; and **Apply · Rebuild**.
|
|
634
|
-
|
|
635
|
-
On the rig pane each region is drawn as two circles: the core (full weight) and a dashed outer circle at core + band, R in one colour and L in another (two theme tokens per side), with a centre handle, a core handle on the right edge, a band handle on the top edge and a label `R (x,y) r:… b:…` under the outer circle. Dragging the centre moves the region, dragging the core edge sets r and dragging the band handle sets the band, all in crop pixels through the current camera scale. Each drag and each committed bar value is clamped live with the public example rebuild script's non-overlap rule: integer values, both cores plus 8 px must fit the centre distance minus a 2 px margin (both shrink by one factor) and the bands share what remains. A side the rule changed flashes its label and the footer says `CHEST_REGIONS_CLAMPED`. A handle drag never moves the camera; a drag anywhere else, or with Space held, still pans, and the wheel still zooms.
|
|
43
|
+
## What you can do
|
|
636
44
|
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
Rig pixels are the whole figure's frame, which is the crop frame the model's stage fixes (below), so a click maps through the same crop → world mapping. A part's own image is that frame less the part's `parts.json` box less the producer's 4 px pad: rig = part image + (box.x − 4, box.y − 4) (`src/chest-landmarks.ts` `partToRig` / `rigToPart`).
|
|
640
|
-
|
|
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:
|
|
642
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
647
|
-
- **Watching.** The producer's lines stream into the editor bar (the latest line and a short log); a refusal shows the producer's first named line verbatim (for example `FAIL REMESH_REQUEST_HELD: …`) and the stage is unchanged. On success the compiled scratch build (served read-only under `/rebuild/<id>/`, its model and pages hashed) replaces the drawn rig in place with the same camera, animation, time and part set; playback resumes with physics when it was running. **Reset** returns the stage to the inspected build. **Undo last Apply** (the *Applied records* list at the top of the Inspector, its depth in the label) returns the stage to the previous applied build, or to the inspected build when none is left; a predecessor whose build was evicted re-runs its recorded step and must come back byte for byte, else `UNDO_PREDECESSOR_CHANGED: <what>` (docs/REVIEW_PACKAGE.md, *Undo across Applies*). A change to the handoff config ends the session's undo stack (reopen): Undo is then refused `FRONT_INSTANCE_EXPIRED`. The Cuts and Cloth blocks mark a pending op applied only while the record on the stage holds it, so after an undo they show the predecessor's ops as applied, and none on the inspected build. The mark shows what the build on the stage carries, not every op applied so far: while a cloth build is on the stage, the Cuts block marks nothing.
|
|
648
|
-
- **Record.** The last applied state (`rig-review-applied/1`: part, regions, for a Landmarks Apply also `bust` — the entry as sent, with `regions` its two derived circles — config hash, entry SHA-256, producer and compiler versions, the producer's line and the scratch model's SHA-256) stays in the inspection draft, and feedback added while that build is on the stage carries it as `context.applied`. The review's `reviewedSource` still names the inspected build.
|
|
649
|
-
- **Config patch.** The same loop takes any config edit as data: `POST /api/rebuild {patch}` with a `rig-review-config-patch/1` document (JSON Pointer ops over the handoff config, refused `EDIT_PATCH_STALE` when made against other config bytes) rebuilds the whole rig (`rig`) or one part (`remesh`) into scratch, compiles it and runs `rig-parts check` on it; the page's `window.rigReviewEditor.applyPatch(patch)` streams, reloads and resets like Apply, and the draft and `context.applied` carry `rig-review-applied/2` (the patch, its hash, the producer's line, check's summary and the diff of every document the producer writes, per file, with the images by hash). Schema and refusals: `docs/REVIEW_PACKAGE.md`, *Config patch and Apply*.
|
|
650
|
-
|
|
651
|
-
The editor's values are the fields of Setup's **Chest regions · detail** block (R is region 1, L is region 2), which also holds what the bar does not: per region a name, a bone chosen from the loaded model's actual bones, the two semi-axes, a spacing and an optional medial clip x, plus the contour settings (tolerance, margin, spacing, max edge, stray, bridges). Setting R in the bar writes both semi-axes. A region with equal semi-axes and no clip exports as the examples' circle, `{"name", "shape": "circle", "cx", "cy", "r", "bone", "spacing", "band"}`; unequal semi-axes or a clip export as the generator's 32-sample polygon on its 1/256-pixel grid and are drawn filled, without handles.
|
|
652
|
-
|
|
653
|
-
The overlay needs the loaded model to fix the crop: its declared stage must be the crop frame with the root at its bottom centre (`x = -width/2`, `y = 0`) and the drawn root bone must sit at the world origin, so crop `(x, y)` is world `(x - width/2, height - y)`. Otherwise the editor bar and the detail block show `CHEST_REGION_MAPPING_UNAVAILABLE: <what is missing>`, nothing is drawn, and the values still edit numerically and export from a crop size you enter.
|
|
654
|
-
|
|
655
|
-
**Download JSON** (and the detail block's **Download chest-regions.json**) saves two files in the browser: `chest-regions.json`, formatted exactly as the public example files, and `chest-regions.binding.json`, which names the package source hash, the rig and motion SHA-256 values, the export time, the viewer and runtime versions and the mapping used. An export holds 1–8 regions, the set Apply accepts (#118): a detail row without a centre x is not designated, so one row exports alone; two regions keep the public examples' text byte for byte. Export refuses `CHEST_REGIONS_COUNT`, `CHEST_REGION_NAME_DUPLICATE`, `CHEST_REGION_BONE_UNKNOWN <bone>`, `CHEST_REGION_OUT_OF_CROP <name>`, `CHEST_REGION_OFF_GRID` and `CHEST_REGIONS_OVERLAP <a> <b>` (every pair is tested). Two circles overlap when their centre distance minus both cores is less than the sum of their bands; any other pair is tested through polygons. The generator refuses per mesh vertex, so this check can refuse a pair it would accept, never the reverse.
|
|
656
|
-
|
|
657
|
-
Field values stay in the inspection's local draft. They never enter the review session's feedback, nothing is written into the package, and an export is review evidence, not an approval (see `docs/DESIGN.md`).
|
|
658
|
-
|
|
659
|
-
## Mesh panel (density, regions, segments → remesh)
|
|
660
|
-
|
|
661
|
-
In **Setup**, under Chest regions · detail, the **Mesh** panel (`#mesh-panel`, #106) edits the selected part's `meshes.<part>` entry from the producer handoff's config — the entry `rig-parts remesh --mesh` takes — and rebuilds that one part. Select a mesh part in the parts list; the panel starts from the config's own entry (or the draft's pending entry for that part).
|
|
662
|
-
|
|
663
|
-
- **Modes.** The two modes whose fields the panel draws: **contour** (sliders for `tolerance`, `margin`, `spacing`, and optional `maxEdge`, `budget`, `stray`, each with a checkbox that declares or removes it; `bridges` as a checkbox) and **grid** (`grid`, optional top-level `stray`). In both: `r`, optional `exponent`, `rule` (absent / `distance` / `heat`), and the weight cut `influences.maxInfluences` and `influences.floor`. Each slider's range is review's drawing range only; the number field beside it takes any value and the producer judges it. An `auto` entry is shown with `MESH_PANEL_MODE` and no controls; an `influence` entry is not offered either, because `remesh --help` names grid, contour and auto entries and not influence (that is the producer's to document). The mode itself is not switched here.
|
|
664
|
-
- **Regions** (contour). **Add circle** adds a circle region (drawn like the chest editor's circles, in the mesh-region colour); **Draw polygon** arms the stage: each click places a point in rig px (whole pixels, so on the producer's 1/256 grid), **Enter** (or the button again) closes it with three or more points and **Escape** drops it. **Drag on stage** lets a polygon vertex or a circle centre be dragged; a drag never pans the camera. Each region row has its name, a bone chosen from the loaded model's bones, `spacing` and `band` (and `cx`, `cy`, `r` for a circle) and **Remove**. A `contour.bust` in the entry stays as written (the chest editor's Landmarks mode edits it).
|
|
665
|
-
- **Segments.** The entry's `segments` as written, each removable; **Add** appends a bone picked from the loaded model's bones. In contour mode each string segment (a chain) has a **ribs stations** field (empty: no ribs); removing a segment drops its ribs.
|
|
666
|
-
- **Pending entry.** The diff against the config's entry, one line per JSON Pointer (`/meshes/<part>/contour/spacing 28 → 20`). Review pre-checks the pending entry with the config loader's rules and names (`CONFIG_MESH_MODE`, `CONFIG_FIELD_PRESENT`, `CONFIG_FIELD_TYPE`, `CONFIG_REGION_NAME_UNIQUE`, `CONFIG_NAME_RESOLVES`, `CONFIG_RIBS_MODE`, `CONFIG_STRAY_MODE`) and Apply is disabled while one stands. What the outline refuses at the rig stage — a spacing whose keep radius snaps to 0 (`CONTOUR_PARAMETER`), a bridge no quad makes (`CONTOUR_BRIDGE`), a heat source (`RIG_HEAT_*`), a remesh rule (`REMESH_*`) — is sent and comes back as the producer's line.
|
|
667
|
-
- **Apply.** The pending entry becomes one config patch, `replace /meshes/<part>` with the whole entry and nothing else, run by `remesh --part <part>` through the patch runner (*Config patch* below): remesh into scratch, compile, check. On a build the revision replaces the drawn rig in place (same camera, animation, time and world), the **Mesh edges** layer turns on so the new triangulation is drawn, and the panel shows the part's vertex / triangle / hull counts before and after from the revision's `remesh.json`, one line comparing `remesh.json`'s after-count with the revision model's triangles for the slot and the triangles the overlay draws (`MESH_COUNTS_AGREE` / `MESH_COUNTS_DIFFER`, every figure named), and `TEXTURE_STRETCH` before (the build that was on the stage; for the inspected build its background check) and after. **Folds reading** runs the #108 folds reading of the drawn animation on the inspected build and on this revision and puts one worst-frame line per build in the status. A refusal leaves the stage and the pending entry as they were and the status line is the producer's line verbatim (for example `FAIL REMESH_CANDIDATE_REFUSED: … CONTOUR_PARAMETER: contour mesh "<part>", spacing — is 0.003; its keep radius … snaps to 0 grid units of 1/256 px …`).
|
|
668
|
-
- **Reset** returns the pending entry to the config's and the stage to the inspected build.
|
|
669
|
-
- **Record.** The draft keeps `#mesh-pending` (`{schema:"rig-review-mesh-entry/1", part, entry}`, only while it differs from the config). A built Apply's `context.applied` is `rig-review-applied/2` with `mesh` added: the part and its counts before and after.
|
|
670
|
-
|
|
671
|
-
## Weights (setup pose, watch only)
|
|
672
|
-
|
|
673
|
-
In **Setup**, after the Mesh panel, the **Weights** block (`#weights`, #107; **Weights** in the block row) shows the selected slot's mesh weights as the compiled model document stores them. **Show weights** draws the slot's mesh at the setup pose over a veil that dims the rest of the rig: each vertex is a dot and each triangle is filled with the mean of its vertices' colours.
|
|
674
|
-
|
|
675
|
-
- **Bone weight.** Pick a bone (the slot's influencing bones first, then every other bone of the model): each vertex is coloured by that bone's stored weight on the weight ramp, 0 → 1 (`--rv-weight-0`, `--rv-weight-half`, `--rv-weight-1`); a bone that does not influence a vertex is 0 there. The legend shows the ramp and the bone.
|
|
676
|
-
- **Max influence.** Each vertex takes its dominant bone's colour (the largest stored weight; the first stored on a tie), from `--rv-weight-bone-1` … `-6` in the influencing bones' order; the legend names each bone with its colour.
|
|
677
|
-
- **Hover.** The vertex under the pointer (within 10 px) is ringed and labelled with its index, the bone and the weight drawn; the block's readout lists all of its stored influences. Values are printed as stored, never rounded or normalised.
|
|
678
|
-
- **Source.** The vertices and weights are the slot's attachment at the setup pose (`skins[].attachments.<slot>.<name>.vertices` of `rigc-compiled/3`: `{weighted:true, bindings}` — per vertex `{bone, x, y, weight}` — or `{weighted:false, xy}`). An unweighted mesh is its slot's bone at 1.0, named. A linked mesh reads its source mesh. A never-played player reads only the public setup identity, including any setup-slider attachment switch. Positions, triangles and hull come directly from published Play 1.14.0's `readSetupMesh`, which includes selected setup-slider deform; Review supplies no skinning formula. Its active skin selection (null for a fresh player) stays distinct from the supplying table used for stored metadata. Hidden/null identities keep their public reason and are never guessed from declarations. The stage player is not touched, so the drawing can sit apart from an animated pose on the stage.
|
|
679
|
-
- **Refusals.** A package without a compiled model document refuses `WEIGHTS_MODEL_UNAVAILABLE`; no slot `WEIGHTS_SELECT_SLOT`; a slot without a mesh `WEIGHTS_NO_MESH <slot>`; a malformed attachment `WEIGHTS_MODEL_INVALID`.
|
|
680
|
-
|
|
681
|
-
Nothing here edits, applies or records a weight: the block adds nothing to the draft or to feedback, and weight editing is out of scope. With **Edit mesh** on, the weights layer is not drawn.
|
|
682
|
-
|
|
683
|
-
## Bones (config anchors, check, rebuild)
|
|
684
|
-
|
|
685
|
-
In **Setup**, after Weights, the **Bones** block (`#bones-panel`, #109; **Bones** in the block row) edits where the producer config puts its bones. It reads the handoff config's `bones` as written (`GET /api/rebuild` returns them as `bones`): a single bone `{name, parent, at, tip?}` or a chain `{chain, parent, points, tip}` whose links are `<chain>0 … <chain>n` (rig-parts AUTHORING, `bones`).
|
|
686
|
-
|
|
687
|
-
- **Stage mode.** **Edit bones on stage** (`#bones-edit`, `aria-pressed`; it pauses playback and ends when Setup is left) draws every anchor on `#rig-overlay` at its config position: an origin (`at`) as a square, a chain point as a circle, a tip as a diamond, joined along the chain or from the bone to its tip. Positions are rig px, y down, mapped to the rig world through the same crop → world mapping the chest and weights views use (the model's stage). They are the config's setup positions; an animated stage pose can sit apart from them. Without that mapping no handle is drawn and the block says why.
|
|
688
|
-
- **Drag.** Drag a handle (within 9 px) to move it; the pointer's delta is snapped to whole rig px and added to the config's value, written with the decimals that value already has (an integer stays an integer). **Bone** (`#bones-select`) picks a bone, whose anchors can also be typed. Each move is one config patch op, `replace` at `/bones/<i>/at`, `/bones/<i>/points/<k>` or `/bones/<i>/tip`, recording the bone's name (a chain's `chain`) beside the index; the server refuses an index that no longer holds that name (`EDIT_PATCH_NAME_MISMATCH`). The pending ops are listed with their delta and a Revert each; the draft keeps them (`#bones-pending`, `{schema:"rig-review-bones-draft/1", edits}`, restored only while each anchor still holds the value it was made from).
|
|
689
|
-
- **Check.** `POST /api/bones-check` applies the ops to a scratch copy of the handoff config and runs the installed producer on it: `propose --from-config` prints its LINT lines, the LINT count line and one coverage line per bone (shown as printed; a LINT line blocks nothing — propose exits 1 on any, and Apply stays enabled), and `compare --left <handoff config> --right <patched>` gives each moved bone's origin (and tip) distance and the summary lines. `propose --from-config` reads the source painting: a handoff without one (declare it with `--source <png> --full <dir>`) shows `MODELING_INPUT_UNAVAILABLE: source painting — …` in place of the LINT lines, and compare still runs. Any change to the pending ops clears the last Check's lines.
|
|
690
|
-
- **Apply · Reset.** Apply sends the ops as one `rig` patch through the generic runner (`rig --config`, compile, `check`): a whole-rig op, so it runs only when the baseline reproduced the inspected rig (`EDIT_BASELINE_MISMATCH` otherwise). A producer or rigc refusal (`RIG_TIP_DISTINCT` for a tip dragged onto its origin, `CONFIG_NAME_RESOLVES`, rigc's `A51`, `RIG_HEAT_BONE_SOURCE`, …) is shown verbatim and leaves the stage as it was. On a build the re-weighted rig reloads in place, and the Weights block reads the revision. Reset returns to the inspected build and clears the pending ops.
|
|
691
|
-
|
|
692
|
-
Adding, removing or re-parenting bones is out of scope.
|
|
693
|
-
|
|
694
|
-
## Rig fields (regions, still row, stops, inherit, turn)
|
|
695
|
-
|
|
696
|
-
In **Setup**, after Bones, the **Rig fields** block (`#rig-fields`, #110; **Fields** in the block row) edits the producer config's small fields. `GET /api/rebuild` returns them as written in `fields` (`regions`, `motion.blink`, `stops`, `bones`, `turn`); the block's state line counts what the config states. Every edit is one config patch op at the field's own pointer, listed with a Revert; the draft keeps the pending ops (`#rig-fields-pending`, `{schema:"rig-review-rig-fields-draft/1", edits}`, restored only while each field still holds the value the edit was made from).
|
|
697
|
-
|
|
698
|
-
- **`regions.<part>`** — the bone a rigid part rides. Pick the part (selecting a region part on the stage picks it) and its bone among the bones the config declares (chain links expanded, and `root`): `replace /regions/<part>`. A meshed part is not offered.
|
|
699
|
-
- **`motion.blink.still.<part>`** — a lash's still row, `{row, bone}`: rows above `row` (rig px, y down) are drawn by `<part>_still` on `bone` and do not blink. The block offers the region parts on a bone the blink's `eyes` names; **Add still row** starts at the eye bone's origin row on its parent. The row is drawn across the part on the stage and dragged in whole rig px (its number and bone can also be typed): `add /motion/blink/still/<part>` (with `add /motion/blink/still {}` first when the config states no `still`), **Remove** sends `remove`. It sits in Setup, beside the part it cuts, rather than in Animate's blink controls.
|
|
700
|
-
- **`stops[i]`** — a stop's inward and outward bounds (degrees). Picking a stop draws its two arcs on the stage at the first stopped link's origin, around the rest aim at the touch point (`at` in `against`'s frame, as rig-c's `expandStops` aims it); dragging an arc end sets that bound in whole degrees (never below 0). The op replaces the whole entry, every other key as written, and names its `chain`: `replace /stops/<i>`. Adding a stop is out of scope.
|
|
701
|
-
- **Inherit** — a single bone's `inherit`, or a chain link's entry in `link_inherit`, picked among rig-c's five modes (`normal`, `onlyTranslation`, `noRotationOrReflection`, `noScale`, `noScaleOrReflection`; absent is `normal`): `add` (or `remove`) at `/bones/<i>/inherit` or `/bones/<i>/link_inherit`, naming the bone.
|
|
702
|
-
- **`turn`** — the pivot and a part's depth, each a slider with a number: `replace /turn/pivot`, `replace /turn/depths/<part>`. The **dial** holds the drawn build's `turn` bone at x rig px through play's `adjust` (shared with the idle preview), labelled *preview — Apply decides*: it shows the build on the stage, not the pending depths, and it is never recorded. Apply and Reset set it back to 0. `turn.deform` is out of scope.
|
|
703
|
-
|
|
704
|
-
**Apply** sends the pending ops (checked against the block's scope, `RIG_FIELDS_OP_OUT_OF_SCOPE` otherwise) as one `rig` patch through the generic runner (`rig --config`, compile, `check`) — a whole-rig op, so it runs only when the baseline reproduced the inspected rig. rig-parts has no `retune` op yet; when it has, stops and inherit can move to it. The producer's refusal (`CONFIG_STILL_OFF_THE_BLINK`, `RIG_STILL_ROW_CLEAR`, `CONFIG_BONE_INHERIT`, `CONFIG_TURN_PIVOT_BEHIND_NEAREST` and the other `CONFIG_TURN_*`, `CONFIG_NAME_RESOLVES`, rigc's `RIG_STOP_BOUNDS`, …) is shown as printed and leaves the stage as it was. On a build the revision reloads in place, the status line names the per-file diff, and the readout shows check's `BLINK_NO_HOLE` and `FEET_STAY_LEVEL` lines before (the build that was on the stage, or the inspected build's stored check) and after. **Angle range** runs the angle-range reading (#108) on the inspected and the latest scratch build with one click: the inherit bone against its parent, else a re-boned part's new bone against the bone it rode, else the part's bone against its parent. **Reset** returns to the inspected build, clears the pending ops and sets the dial to 0.
|
|
705
|
-
|
|
706
|
-
## Cuts (cut polygons, patch boxes, limb joints → rig-parts build)
|
|
707
|
-
|
|
708
|
-
In **Setup**, after Rig fields, the **Cuts** block (`#cuts-panel`, #115; **Cuts** in the block row) edits what the producer's assemble stage reads: the config's `assemble.cuts` (a plan part's layer cut by a polygon into a new part), `assemble.patches` (a part cut from the painting by a box) and `limbs` (a limb cut at its joints). `GET /api/rebuild` returns them as written in `cuts.config` (`assemble.plan`, `assemble.cuts`, `assemble.patches`, `limbs`) with `cuts.build`, the refusal that stops an Apply here (null when the handoff declares the painting and the runs). Everything is in rig px of the setup pose, the space of `parts.json`, mapped to the stage through the same crop → world mapping the chest, mesh and bones views use.
|
|
709
|
-
|
|
710
|
-
- **Stage mode.** **Edit cuts on stage** (`#cuts-edit`) draws each cut polygon and patch box with a handle on every vertex and corner, each limb joint (the lower segment's bone origin) as a diamond, and a declared `range` as an arc from the joint (min to max degrees from the lower bone's rest direction, counter-clockwise on screen, rigc's sense). Drag a handle (within 9 px); the pointer's delta snaps to whole rig px. **Entry** (`#cuts-select`) picks a cut, a patch or a joint, whose numbers can also be typed.
|
|
711
|
-
- **Ops.** A vertex is `replace /assemble/cuts/<i>/polygon/<k>` naming the cut by its `into`; a box is `replace /assemble/patches/<i>/box` naming the patch; a range is `add`, `replace` or `remove` at `/limbs/<limb>/segments/<k>/range` naming the segment's `part`; a joint is a bones op (`/bones/<i>/at` or `/bones/<i>/points/<k>`), since the joint is its bone's origin. The server checks the recorded name against the index (`EDIT_PATCH_NAME_MISMATCH`). **Place cut** / **Place patch** add a new part by clicking its polygon's points or its box's two corners: `add /assemble/cuts/-` (`{from, into, polygon, draw, overlap}`) or `add /assemble/patches/-` (`{name, box, alpha, draw}`), and `add /regions/<name>` with the bone typed beside it. Review refuses before sending only what is not the loader's type (`CUTS_VALUE_INVALID` for a negative or non-integer point, `CUTS_POLYGON_SHORT`); everything else is the producer's. The draft keeps the pending ops (`#cuts-pending`).
|
|
712
|
-
- **Apply · Reset.** Apply sends the ops as one `rig` patch. The server runs it as `rig-parts build` (*Config patch and Apply*, Routing) from the declared painting and runs into scratch — a whole-rig op behind the reproduced baseline — and the scratch build reloads in place. The producer's refusal is shown as printed (`ASSEMBLE_CUT_INSIDE`, `ASSEMBLE_CUT_OVERLAP`, `ASSEMBLE_CUT_PIXELS`, `ASSEMBLE_UNDER_SIZE`, `ASSEMBLE_UNDER_DRAW`, `CONFIG_LIMB_RANGE`, `CONFIG_LIMB_BONE`, `CONFIG_PART_UNIQUE`, …), and a check bar that is not met (`SEAM_OVER_RANGE`, `LIMB_ONE_BONE`, `CHECK_SEAM_WITHIN_BAR`) keeps the build with check's FAIL line. The readout shows the `cut:` / `limb cut:` / `limb cap:` lines, the recomposite figures before and after with the scratch build's `recomposite_error_rig.png`, the setup-pose `seam` and `seam pair` lines, which parts changed and the build's wall clock, which the applied record keeps. Reset returns to the inspected build and clears the pending ops.
|
|
713
|
-
|
|
714
|
-
Review paints no image: a new cut names no `under`, and a patch may only keep, move or drop an `under` or `cap` the config already names (`EDIT_PATCH_PATH_HELD`). Removing a cut or a patch is out of scope.
|
|
715
|
-
|
|
716
|
-
## Mesh edit (typed delta, preview only)
|
|
717
|
-
|
|
718
|
-
In **Setup**, the view bar's **Edit mesh** button (pressed state; hidden in Animate) edits the selected slot's actual mesh attachment **in setup space**: the attachment's entry in the compiled model document (`skeleton.model.json`), its full-double setup world coordinates, triangles and hull from published Play 1.14.0's `readSetupMesh(assets, {slot, attachment, skin})`, shared with Weights. A never-played player reads only the public setup identity, including any setup-slider attachment switch; the named geometry read owns setup-slider deform. Its active skin selection (null for a fresh player) is distinct from the supplying table returned as `skin`. Hidden/null identities remain unavailable with the public reason; Review never guesses a mesh from declarations. A package without the model document (a Spine export alone, or the bundled demo) refuses `MESH_EDIT_MODEL_UNAVAILABLE` and the editor does not open. With **Whole rig** selected it refuses `MESH_EDIT_SELECT_SLOT`; a slot whose setup attachment is not a mesh refuses `MESH_EDIT_NO_MESH <slot>`, and so does a linked mesh (edit its source mesh). Entering pauses playback (the stage keeps the pose it held, under the veil; the mesh is drawn at the setup pose with the stage's own camera), turns the chest-region editor off and opens the mesh editor bar under the view bar. The rig pane dims except the slot's own triangles; the mesh is drawn at full opacity with every vertex as a handle, hull edges, interior triangulation edges and added constraint edges each in their own theme token.
|
|
719
|
-
|
|
720
|
-
The bar has four sub-modes, one pressed at a time, with single keys that avoid the layer keys `I` / `M` / `B`:
|
|
721
|
-
|
|
722
|
-
| Key | Sub-mode | Pointer |
|
|
45
|
+
| Task | Command or panel | Destination |
|
|
723
46
|
| --- | --- | --- |
|
|
724
|
-
|
|
|
725
|
-
|
|
|
726
|
-
|
|
|
727
|
-
|
|
|
47
|
+
| **Hand off and compare** | | |
|
|
48
|
+
| <a id="one-command-owner-handoff"></a>Open an inspection | `open` | [Owner handoff — WORKFLOWS](docs/WORKFLOWS.md#one-command-owner-handoff) |
|
|
49
|
+
| <a id="existing-output-handoff"></a>Inspect an existing folder | `check`, `review` | [Existing output handoff — WORKFLOWS](docs/WORKFLOWS.md#existing-output-handoff) |
|
|
50
|
+
| <a id="compare-two-outputs"></a>Compare two builds against a video | `compare` | [Compare two outputs — WORKFLOWS](docs/WORKFLOWS.md#compare-two-outputs) |
|
|
51
|
+
| <a id="feedback-ownership"></a>Retrieve submitted feedback | `feedback` | [Feedback ownership — WORKFLOWS](docs/WORKFLOWS.md#feedback-ownership) |
|
|
52
|
+
| <a id="frame-strip-export"></a>Export feedback intervals as strips | `strip` | [Frame strip export — WORKFLOWS](docs/WORKFLOWS.md#frame-strip-export) |
|
|
53
|
+
| **Inspect** | | |
|
|
54
|
+
| <a id="workspace-settings-and-panels"></a>Navigate the workspace | Setup, Animate, Settings | [Workspace panels — PANELS](docs/PANELS.md#workspace-settings-and-panels) |
|
|
55
|
+
| <a id="inspect-animation-keys"></a>Inspect stored animation keys | Timeline, Curves, Bone tree | [Animation keys — PANELS](docs/PANELS.md#inspect-animation-keys) |
|
|
56
|
+
| <a id="inspect-a-part"></a>Select and fit a model slot | Parts explorer | [Part inspection — PANELS](docs/PANELS.md#inspect-a-part) |
|
|
57
|
+
| <a id="weights-setup-pose-watch-only"></a>Inspect setup-pose weights | Weights | [Weights — PANELS](docs/PANELS.md#weights-setup-pose-watch-only) |
|
|
58
|
+
| Steer model turn dials and compare at one angle | Turn pad | [Turn inspection — PANELS](docs/PANELS.md#turn-pad) |
|
|
59
|
+
| **Edit through the producer** | | |
|
|
60
|
+
| <a id="chest-regions-designate-rebuild-watch"></a>Designate chest regions and rebuild | Chest regions | [Chest regions — PANELS](docs/PANELS.md#chest-regions-designate-rebuild-watch) |
|
|
61
|
+
| <a id="mesh-panel-density-regions-segments--remesh"></a>Rebuild a part from mesh parameters | Mesh | [Mesh parameters — PANELS](docs/PANELS.md#mesh-panel-density-regions-segments--remesh) |
|
|
62
|
+
| <a id="bones-config-anchors-check-rebuild"></a>Check and rebuild config anchors | Bones | [Bones — PANELS](docs/PANELS.md#bones-config-anchors-check-rebuild) |
|
|
63
|
+
| <a id="rig-fields-regions-still-row-stops-inherit-turn"></a>Adjust producer config fields | Rig fields | [Rig fields — PANELS](docs/PANELS.md#rig-fields-regions-still-row-stops-inherit-turn) |
|
|
64
|
+
| <a id="cuts-cut-polygons-patch-boxes-limb-joints--rig-parts-build"></a>Designate cuts, patches and limb joints | Cuts | [Cuts — PANELS](docs/PANELS.md#cuts-cut-polygons-patch-boxes-limb-joints--rig-parts-build) |
|
|
65
|
+
| <a id="mesh-edit-typed-delta-preview-only"></a>Preview a setup-space delta and rebuild | Edit Mesh | [Mesh edit — PANELS](docs/PANELS.md#mesh-edit-typed-delta-preview-only) |
|
|
66
|
+
| <a id="retarget-map-flags--rig-pose-watch"></a>Retarget a declared pose sequence | Retarget | [Retarget — PANELS](docs/PANELS.md#retarget-map-flags--rig-pose-watch) |
|
|
67
|
+
| **Control and share** | | |
|
|
68
|
+
| <a id="control-the-current-inspection-page"></a>Control an opted-in inspection | `control` | [Inspection control — WORKFLOWS](docs/WORKFLOWS.md#control-the-current-inspection-page) |
|
|
69
|
+
| <a id="keep-and-share-an-inspection"></a>Restore a view link or local draft | View link, draft | [Keep and share — WORKFLOWS](docs/WORKFLOWS.md#keep-and-share-an-inspection) |
|
|
70
|
+
| <a id="launcher-contract"></a>Start or reuse the launcher | `open` | [Launcher contract — WORKFLOWS](docs/WORKFLOWS.md#launcher-contract) |
|
|
71
|
+
| <a id="managed-front-and-write-capability"></a>Use the managed front and write capability | `open` | [Managed front — WORKFLOWS](docs/WORKFLOWS.md#managed-front-and-write-capability) |
|
|
728
72
|
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
Everything is a typed delta over the loaded mesh, in setup-pose crop pixels of the rig canvas (x right, y down, the chest regions' crop mapping): `{schema:"rig-review-mesh-edit/1", slot, skin, attachment, source:{vertexCount, triangleCount, hullLength, hash}, units, basis:"setup", preview, ops}` with ordered ops `move {v, from, to}`, `add {at, onEdge?}`, `remove {v}`, `edge {a, b}` and `unedge {a, b}`. Source vertex ids are the attachment's indices; an added vertex takes the next id; a removed id is never reused. `hullLength` is the model's `hull` (the first `hull` vertices, in order, are the hull) and `hash` is the SHA-256 of `JSON.stringify` of the attachment's entry in the hash-verified model document. Source positions are not rounded (a `move`'s `from` is the vertex's exact setup position; `to` and `add.at` are rounded to 0.01 px). This is the input `rig-parts remesh --delta` reads as it is (see [docs/REVIEW_PACKAGE.md](docs/REVIEW_PACKAGE.md#mesh-edit-export)); the former last-drawn-pose basis is gone. The overlay previews moved, added and removed vertices and the constraint edges over the original triangulation; nothing is retriangulated in the browser (the status line says *preview only — the producer retriangulates*).
|
|
732
|
-
|
|
733
|
-
**Download JSON** saves `mesh-edit.json` and `mesh-edit.binding.json` (package source hash, rig and motion SHA-256, the attachment and its hash, export time, viewer and runtime versions and the mapping). **Apply · Rebuild** (#135) sends the delta to the installed producer as `POST /api/rebuild {part, delta, configHash}`: the server writes it as `mesh-edit.json` (the download's bytes) into the scratch directory and runs `rig-parts remesh --rig <handoff rig> --config <handoff config> --parts <handoff parts> --part <part> --delta mesh-edit.json --out rig`, then the shared tail (per-file diff, compile with remesh.json's pack flags, hash). The producer must read `--delta` (`@rig-c/parts` 1.43.0 or later, read from its own `package.json`), else `MODELING_PRODUCER_TOO_OLD <found> 1.43.0`. The delta's schema, basis, source counts and `source.hash` and every op are the producer's judgment: its `REMESH_DELTA_*` / `REMESH_*` line comes back verbatim and the stage keeps its model. A built revision loads in place and the editor re-reads that revision's setup mesh; `context.applied` becomes `rig-review-applied/3` (the delta by content, `deltaSha256` of its bytes, the producer's line, `op: "remesh"`, the per-file diff and the part's counts). While the revision is on the stage, Reset returns to the inspected build and the draft's delta is restored over it. A delta drawn over the revision's mesh is refused by the producer (`REMESH_DELTA_SOURCE`): its input is always the handoff rig. The delta rides in the inspection's local draft bound to the attachment hash (at most 300 ops per attachment); it never enters the review session and nothing is written into the package.
|
|
734
|
-
|
|
735
|
-
For inspection automation, `window.rigReviewEditor.meshEdit()` returns the open editor's source (setup-pose crop px per vertex, unrounded), its ops and each drawn vertex as page client pixels, or `null` when the editor is closed; it reads only.
|
|
736
|
-
|
|
737
|
-
## Retarget (map, flags → rig-pose, watch)
|
|
738
|
-
|
|
739
|
-
In **Animate**, the **Retarget** block (#114) puts a pose sequence the package declares onto the inspected rig through the installed `@rig-c/pose`. The sequence is a `rig-pose-sequence/1` pose reference the package declares (a `references` entry of type `pose` in `review.json`, or a `*pose*.json` in an opened folder); review takes no other file. The block's controls:
|
|
73
|
+
## License
|
|
740
74
|
|
|
741
|
-
|
|
742
|
-
- **Clean** — clean first (default; a sequence that already carries clean's record starts with it off), `--loop`, `--legs`, `--rest`, the stillness hold and `--hips-inferred`. With clean off the sequence goes to retarget as the package holds it, and an uncleaned one is refused by the producer (`POSE_NOT_CLEANED`).
|
|
743
|
-
- **Retarget** — the new animation's name (`--animation`), `--flip`, `--short`, `--min-score`, `--bends refuse|clamp`, `--bend-tolerance`, `--leg-scale min,max`, `--plant-feet`, `--stance`, `--raised-by-head`, `--carry-idle`, and the person when the sequence holds more than one.
|
|
75
|
+
MIT. See [LICENSE](LICENSE).
|
|
744
76
|
|
|
745
|
-
|
|
77
|
+
## Links
|
|
746
78
|
|
|
747
|
-
|
|
79
|
+
<a id="development"></a><a id="releasing"></a>
|
|
80
|
+
Development and releasing: [Contributing workflows](https://github.com/rig-c/review/blob/main/CONTRIBUTING.md#development).
|
|
748
81
|
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
82
|
+
- [Package contract](docs/REVIEW_PACKAGE.md)
|
|
83
|
+
- [Product design](docs/DESIGN.md)
|
|
84
|
+
- [Animation inspection](docs/ANIMATION_INSPECTION.md)
|
|
85
|
+
- [Installed-producer fixture](docs/LIVE_FIXTURE.md)
|