@glissade/cli 0.37.0-pre.0 → 0.37.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 +20 -1
- package/dist/repin.js +34 -6
- package/package.json +10 -10
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
`gs` — headless rendering and the dev/capture loop. `gs render` evaluates every frame, rasterizes on Skia (no browser anywhere), writes a PNG sequence or muxes mp4/webm via FFmpeg with sample-accurate audio. v2: `gs dev --record` serves a scene with its state machines mounted and writes input-trace sidecars; `gs render --trace/--state` are the deterministic export routes for interactive scenes.
|
|
4
4
|
|
|
5
|
-
The full command set: `render`, `dev`, `import` (Lottie `.json` / `.svg`), `build`, `mcp` (an MCP server over the engine), `describe`, `migrate`, `narrate`, `sfx`, `prepare`, `measure-loudness`, `fonts`, `cache`, `diff`, and `verify-determinism`.
|
|
5
|
+
The full command set: `render`, `dev`, `import` (Lottie `.json` / `.svg`), `build`, `mcp` (an MCP server over the engine), `describe`, `migrate`, `repin` (narration-aware golden reviewer), `narrate`, `sfx`, `prepare`, `measure-loudness`, `fonts`, `cache`, `diff`, and `verify-determinism`. Run `gs` with no arguments for the full usage.
|
|
6
6
|
|
|
7
7
|
```sh
|
|
8
8
|
npm i -D @glissade/cli
|
|
@@ -14,6 +14,25 @@ gs dev scene.ts --record # capture a take
|
|
|
14
14
|
gs render scene.ts --trace scene.button.take1.trace.json --out take.mp4
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
+
## Reviewing goldens (`gs repin`)
|
|
18
|
+
|
|
19
|
+
When a re-narration re-flows a project's beats, every downstream golden goes
|
|
20
|
+
stale at once. `gs repin` renders the current scene vs the committed golden PNGs
|
|
21
|
+
and, per changed frame, reports a perceptual SSIM delta (mean + worst tile) plus a
|
|
22
|
+
one-line **cause** — it diffs the `*.narration.timing.json` sibling against a git
|
|
23
|
+
ref to attribute the edit site and trace the pushed beats back to it. Default is a
|
|
24
|
+
dry run; `--write` re-pins, and `--floor <ssim>` refuses a bigger-than-expected
|
|
25
|
+
drop until `--force` — the confidence gate before you bless a batch re-pin.
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
gs repin scene.ts --golden test/golden # dry-run: what changed & why
|
|
29
|
+
gs repin scene.ts --golden test/golden --write --floor 0.98 # re-pin, guarded
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The SSIM machinery is also exported from `@glissade/backend-skia`
|
|
33
|
+
(`ssim` / `ssimMap` / `heatmapRgba`) for your own golden tooling. Full guide:
|
|
34
|
+
[Reviewing goldens](https://github.com/tyevco/glissade/blob/main/docs/golden-review.md).
|
|
35
|
+
|
|
17
36
|
## Part of glissade
|
|
18
37
|
|
|
19
38
|
*(glide & slide)* — programmatic motion graphics for TypeScript: realtime-first in any web page, deterministic headless video export from the same code, a visual studio over the same document. No generator functions.
|
package/dist/repin.js
CHANGED
|
@@ -80,6 +80,7 @@ function diffTiming(a, b) {
|
|
|
80
80
|
id: s.id,
|
|
81
81
|
start: s.start,
|
|
82
82
|
deltaStart: prev ? s.start - prev.start : 0,
|
|
83
|
+
deltaDuration: prev ? s.duration - prev.duration : 0,
|
|
83
84
|
added: !prev,
|
|
84
85
|
removed: false
|
|
85
86
|
});
|
|
@@ -89,6 +90,7 @@ function diffTiming(a, b) {
|
|
|
89
90
|
id,
|
|
90
91
|
start: s.start,
|
|
91
92
|
deltaStart: 0,
|
|
93
|
+
deltaDuration: 0,
|
|
92
94
|
added: false,
|
|
93
95
|
removed: true
|
|
94
96
|
});
|
|
@@ -96,14 +98,32 @@ function diffTiming(a, b) {
|
|
|
96
98
|
}
|
|
97
99
|
const EPS = 1e-4;
|
|
98
100
|
const sign = (n) => `${n >= 0 ? "+" : ""}${n.toFixed(2)}s`;
|
|
99
|
-
/**
|
|
101
|
+
/** Is this shift a re-narration ROOT — a segment whose own content changed
|
|
102
|
+
* (duration moved) or that was newly added, i.e. an EDIT SITE that pushes
|
|
103
|
+
* everything downstream of it? */
|
|
104
|
+
function isRoot(s) {
|
|
105
|
+
return s.added || Math.abs(s.deltaDuration) > EPS;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* One-line cause for a frame at time `t`. A re-narration that re-records one line
|
|
109
|
+
* changes that segment's DURATION (not its start) and pushes every later segment's
|
|
110
|
+
* start by the same amount — so attribution has two jobs:
|
|
111
|
+
* • name the EDIT SITE by its duration change (its start doesn't move), and
|
|
112
|
+
* • trace a purely-shifted downstream beat back to the ROOT that pushed it,
|
|
113
|
+
* rather than letting it claim its own (derived) shift.
|
|
114
|
+
*/
|
|
100
115
|
function causeFor(t, shifts) {
|
|
101
116
|
const before = shifts.filter((s) => s.start <= t + EPS && !s.removed);
|
|
102
117
|
const active = before[before.length - 1];
|
|
103
|
-
if (active
|
|
104
|
-
if (active
|
|
105
|
-
|
|
106
|
-
|
|
118
|
+
if (!active) return void 0;
|
|
119
|
+
if (active.added) return `${active.id}: new segment`;
|
|
120
|
+
if (Math.abs(active.deltaDuration) > EPS) return `${active.id} re-narrated (${sign(active.deltaDuration)} duration): re-narration`;
|
|
121
|
+
const root = before.slice(0, -1).reverse().find(isRoot);
|
|
122
|
+
if (Math.abs(active.deltaStart) > EPS) {
|
|
123
|
+
if (root) return `downstream of ${root.id} (${sign(active.deltaStart)}): re-narration`;
|
|
124
|
+
return `${active.id} moved ${sign(active.deltaStart)}: re-narration`;
|
|
125
|
+
}
|
|
126
|
+
if (root) return `downstream of ${root.id} (${sign(root.deltaDuration)}): re-narration`;
|
|
107
127
|
}
|
|
108
128
|
async function decodePng(buf) {
|
|
109
129
|
const img = await loadImage(buf);
|
|
@@ -226,6 +246,13 @@ function formatReport(name, opts, r) {
|
|
|
226
246
|
lines.push(`gs repin '${name}' — ${r.frames.length} frames, ${r.changed} changed [${mode}]`);
|
|
227
247
|
if (r.causeSource) lines.push(` cause ← ${r.causeSource} vs ${opts.since ?? "HEAD"}`);
|
|
228
248
|
else lines.push(` (no narration timing sibling diffed — perceptual delta only)`);
|
|
249
|
+
let worstFrame = -1;
|
|
250
|
+
let worstSsim = Infinity;
|
|
251
|
+
for (const f of r.frames) if (f.status === "changed" && f.ssim !== void 0 && f.ssim < worstSsim) {
|
|
252
|
+
worstSsim = f.ssim;
|
|
253
|
+
worstFrame = f.frame;
|
|
254
|
+
}
|
|
255
|
+
const marked = r.changed > 1 && worstFrame >= 0;
|
|
229
256
|
for (const f of r.frames) {
|
|
230
257
|
if (f.status === "identical") continue;
|
|
231
258
|
const fno = `f${String(f.frame).padStart(4, "0")}`;
|
|
@@ -235,7 +262,8 @@ function formatReport(name, opts, r) {
|
|
|
235
262
|
}
|
|
236
263
|
const perc = f.ssim !== void 0 ? `ssim ${f.ssim.toFixed(4)} (min ${f.minSsim.toFixed(3)})` : `dimensions changed`;
|
|
237
264
|
const tail = f.blocked ? "⚠ BELOW FLOOR — refused (use --force)" : f.wrote ? "→ re-pinned" : "(dry-run)";
|
|
238
|
-
|
|
265
|
+
const editMark = marked && f.frame === worstFrame ? "◀ likely edit-site (lowest SSIM) " : "";
|
|
266
|
+
lines.push(` ${fno} ${perc} ${f.cause ? `— ${f.cause} ` : ""}${editMark}${tail}`);
|
|
239
267
|
}
|
|
240
268
|
if (!opts.write && r.changed > 0) lines.push(` ${r.changed} stale — re-run with --write to re-pin${opts.floor !== void 0 ? ` (floor ${opts.floor})` : ""}`);
|
|
241
269
|
if (r.blocked > 0) lines.push(` ${r.blocked} refused below floor — inspect the heat-map, then --force if intended`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glissade/cli",
|
|
3
|
-
"version": "0.37.0
|
|
3
|
+
"version": "0.37.0",
|
|
4
4
|
"description": "glissade CLI: headless rendering via backend-skia (+ FFmpeg mux).",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"engines": {
|
|
@@ -28,15 +28,15 @@
|
|
|
28
28
|
"@napi-rs/canvas": "^0.1.65",
|
|
29
29
|
"esbuild": "0.28.0",
|
|
30
30
|
"jiti": "^2.4.2",
|
|
31
|
-
"@glissade/backend-skia": "0.37.0
|
|
32
|
-
"@glissade/core": "0.37.0
|
|
33
|
-
"@glissade/interact": "0.37.0
|
|
34
|
-
"@glissade/lottie": "0.37.0
|
|
35
|
-
"@glissade/narrate": "0.37.0
|
|
36
|
-
"@glissade/player": "0.37.0
|
|
37
|
-
"@glissade/scene": "0.37.0
|
|
38
|
-
"@glissade/sfx": "0.37.0
|
|
39
|
-
"@glissade/svg": "0.37.0
|
|
31
|
+
"@glissade/backend-skia": "0.37.0",
|
|
32
|
+
"@glissade/core": "0.37.0",
|
|
33
|
+
"@glissade/interact": "0.37.0",
|
|
34
|
+
"@glissade/lottie": "0.37.0",
|
|
35
|
+
"@glissade/narrate": "0.37.0",
|
|
36
|
+
"@glissade/player": "0.37.0",
|
|
37
|
+
"@glissade/scene": "0.37.0",
|
|
38
|
+
"@glissade/sfx": "0.37.0",
|
|
39
|
+
"@glissade/svg": "0.37.0"
|
|
40
40
|
},
|
|
41
41
|
"repository": {
|
|
42
42
|
"type": "git",
|