@triscope/mcp 0.4.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/dist/refs.mjs ADDED
@@ -0,0 +1,396 @@
1
+ // src/refs.ts
2
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
3
+ import { dirname, join, resolve } from "path";
4
+ import { PNG } from "pngjs";
5
+ var stripPrefix = (s) => s.replace(/^data:image\/png;base64,/, "");
6
+ function refsRoot(cwd) {
7
+ return resolve(cwd, "refs");
8
+ }
9
+ function refsPath(cwd, element, camera) {
10
+ const safeCam = String(camera).replace(/[^A-Za-z0-9._-]/g, "_");
11
+ return join(refsRoot(cwd), element, `${safeCam}.png`);
12
+ }
13
+ function refsMotionPaths(cwd, element, camera) {
14
+ const safeCam = String(camera).replace(/[^A-Za-z0-9._-]/g, "_");
15
+ const base = join(refsRoot(cwd), element);
16
+ return {
17
+ filmstrip: join(base, `${safeCam}.motion.png`),
18
+ meta: join(base, `${safeCam}.motion.json`)
19
+ };
20
+ }
21
+ function setReference({
22
+ cwd,
23
+ element,
24
+ camera,
25
+ path,
26
+ base64
27
+ }) {
28
+ if (!element || !camera) throw new Error("element and camera are required");
29
+ let bytes;
30
+ if (path) {
31
+ if (!existsSync(path)) throw new Error(`reference file not found: ${path}`);
32
+ bytes = readFileSync(path);
33
+ } else if (base64) {
34
+ bytes = Buffer.from(stripPrefix(base64), "base64");
35
+ } else {
36
+ throw new Error("provide either path or base64");
37
+ }
38
+ const dest = refsPath(cwd, element, camera);
39
+ mkdirSync(dirname(dest), { recursive: true });
40
+ writeFileSync(dest, bytes);
41
+ return { path: dest, bytes: bytes.length };
42
+ }
43
+ function decodePng(buffer) {
44
+ return PNG.sync.read(buffer);
45
+ }
46
+ function nearestNeighborResize(src, targetW, targetH) {
47
+ if (src.width === targetW && src.height === targetH) return src;
48
+ const dst = new PNG({ width: targetW, height: targetH });
49
+ for (let y = 0; y < targetH; y++) {
50
+ const sy = Math.min(src.height - 1, Math.floor(y * src.height / targetH));
51
+ for (let x = 0; x < targetW; x++) {
52
+ const sx = Math.min(src.width - 1, Math.floor(x * src.width / targetW));
53
+ const si = (sy * src.width + sx) * 4;
54
+ const di = (y * targetW + x) * 4;
55
+ dst.data[di] = src.data[si];
56
+ dst.data[di + 1] = src.data[si + 1];
57
+ dst.data[di + 2] = src.data[si + 2];
58
+ dst.data[di + 3] = 255;
59
+ }
60
+ }
61
+ return dst;
62
+ }
63
+ function composeSideBySide(left, right) {
64
+ const h = Math.min(left.height, right.height);
65
+ const sep = 4;
66
+ const lw = Math.round(left.width * h / left.height);
67
+ const rw = Math.round(right.width * h / right.height);
68
+ const w = lw + sep + rw;
69
+ const L = nearestNeighborResize(left, lw, h);
70
+ const R = nearestNeighborResize(right, rw, h);
71
+ const out = new PNG({ width: w, height: h });
72
+ for (let i = 0; i < out.data.length; i += 4) out.data[i + 3] = 255;
73
+ for (let y = 0; y < h; y++) {
74
+ const orow = y * w * 4;
75
+ const lrow = y * lw * 4;
76
+ const rrow = y * rw * 4;
77
+ L.data.copy(out.data, orow, lrow, lrow + lw * 4);
78
+ R.data.copy(out.data, orow + (lw + sep) * 4, rrow, rrow + rw * 4);
79
+ }
80
+ return out;
81
+ }
82
+ function meanAbsDiff(a, b) {
83
+ const W = 256;
84
+ const H = 256;
85
+ const A = nearestNeighborResize(a, W, H);
86
+ const B = nearestNeighborResize(b, W, H);
87
+ let sum = 0;
88
+ const pixels = W * H * 3;
89
+ for (let i = 0; i < W * H; i++) {
90
+ const j = i * 4;
91
+ sum += Math.abs(A.data[j] - B.data[j]);
92
+ sum += Math.abs(A.data[j + 1] - B.data[j + 1]);
93
+ sum += Math.abs(A.data[j + 2] - B.data[j + 2]);
94
+ }
95
+ return +(sum / pixels).toFixed(2);
96
+ }
97
+ function ssim(a, b) {
98
+ const W = 256;
99
+ const H = 256;
100
+ const A = nearestNeighborResize(a, W, H);
101
+ const B = nearestNeighborResize(b, W, H);
102
+ const lumA = new Float32Array(W * H);
103
+ const lumB = new Float32Array(W * H);
104
+ for (let i = 0; i < W * H; i++) {
105
+ const j = i * 4;
106
+ lumA[i] = 0.2126 * A.data[j] + 0.7152 * A.data[j + 1] + 0.0722 * A.data[j + 2];
107
+ lumB[i] = 0.2126 * B.data[j] + 0.7152 * B.data[j + 1] + 0.0722 * B.data[j + 2];
108
+ }
109
+ const L = 255;
110
+ const C1 = (0.01 * L) ** 2;
111
+ const C2 = (0.03 * L) ** 2;
112
+ const WIN = 8;
113
+ let total = 0;
114
+ let count = 0;
115
+ for (let wy = 0; wy < H; wy += WIN) {
116
+ for (let wx = 0; wx < W; wx += WIN) {
117
+ let muA = 0, muB = 0;
118
+ for (let dy = 0; dy < WIN; dy++)
119
+ for (let dx = 0; dx < WIN; dx++) {
120
+ const i = (wy + dy) * W + (wx + dx);
121
+ muA += lumA[i];
122
+ muB += lumB[i];
123
+ }
124
+ muA /= WIN * WIN;
125
+ muB /= WIN * WIN;
126
+ let varA = 0, varB = 0, covAB = 0;
127
+ for (let dy = 0; dy < WIN; dy++)
128
+ for (let dx = 0; dx < WIN; dx++) {
129
+ const i = (wy + dy) * W + (wx + dx);
130
+ const da = lumA[i] - muA;
131
+ const db = lumB[i] - muB;
132
+ varA += da * da;
133
+ varB += db * db;
134
+ covAB += da * db;
135
+ }
136
+ varA /= WIN * WIN - 1;
137
+ varB /= WIN * WIN - 1;
138
+ covAB /= WIN * WIN - 1;
139
+ const num = (2 * muA * muB + C1) * (2 * covAB + C2);
140
+ const den = (muA * muA + muB * muB + C1) * (varA + varB + C2);
141
+ total += num / den;
142
+ count += 1;
143
+ }
144
+ }
145
+ return +(total / count).toFixed(4);
146
+ }
147
+ function ssimTileGrid(a, b, gridSize = 8) {
148
+ const W = 256;
149
+ const H = 256;
150
+ const A = nearestNeighborResize(a, W, H);
151
+ const B = nearestNeighborResize(b, W, H);
152
+ const lumA = new Float32Array(W * H);
153
+ const lumB = new Float32Array(W * H);
154
+ for (let i = 0; i < W * H; i++) {
155
+ const j = i * 4;
156
+ lumA[i] = 0.2126 * A.data[j] + 0.7152 * A.data[j + 1] + 0.0722 * A.data[j + 2];
157
+ lumB[i] = 0.2126 * B.data[j] + 0.7152 * B.data[j + 1] + 0.0722 * B.data[j + 2];
158
+ }
159
+ const L = 255;
160
+ const C1 = (0.01 * L) ** 2;
161
+ const C2 = (0.03 * L) ** 2;
162
+ const tile = Math.floor(W / gridSize);
163
+ const grid = [];
164
+ let worst = { row: 0, col: 0, ssim: Number.POSITIVE_INFINITY };
165
+ let min = Number.POSITIVE_INFINITY;
166
+ let max = Number.NEGATIVE_INFINITY;
167
+ let sum = 0;
168
+ let count = 0;
169
+ for (let r = 0; r < gridSize; r++) {
170
+ const rowArr = [];
171
+ for (let c = 0; c < gridSize; c++) {
172
+ const x0 = c * tile;
173
+ const y0 = r * tile;
174
+ const n = tile * tile;
175
+ let muA = 0;
176
+ let muB = 0;
177
+ for (let dy = 0; dy < tile; dy++)
178
+ for (let dx = 0; dx < tile; dx++) {
179
+ const i = (y0 + dy) * W + (x0 + dx);
180
+ muA += lumA[i];
181
+ muB += lumB[i];
182
+ }
183
+ muA /= n;
184
+ muB /= n;
185
+ let vA = 0;
186
+ let vB = 0;
187
+ let cov = 0;
188
+ for (let dy = 0; dy < tile; dy++)
189
+ for (let dx = 0; dx < tile; dx++) {
190
+ const i = (y0 + dy) * W + (x0 + dx);
191
+ const da = lumA[i] - muA;
192
+ const db = lumB[i] - muB;
193
+ vA += da * da;
194
+ vB += db * db;
195
+ cov += da * db;
196
+ }
197
+ vA /= n - 1;
198
+ vB /= n - 1;
199
+ cov /= n - 1;
200
+ const s = (2 * muA * muB + C1) * (2 * cov + C2) / ((muA * muA + muB * muB + C1) * (vA + vB + C2));
201
+ const sr = +s.toFixed(4);
202
+ rowArr.push(sr);
203
+ sum += sr;
204
+ count += 1;
205
+ if (sr < min) min = sr;
206
+ if (sr > max) max = sr;
207
+ if (sr < worst.ssim) worst = { row: r, col: c, ssim: sr };
208
+ }
209
+ grid.push(rowArr);
210
+ }
211
+ return {
212
+ grid,
213
+ worst,
214
+ min: +min.toFixed(4),
215
+ max: +max.toFixed(4),
216
+ mean: +(sum / count).toFixed(4),
217
+ gridSize
218
+ };
219
+ }
220
+ function heatColor(t) {
221
+ const x = Math.max(0, Math.min(1, t));
222
+ const stops = [
223
+ [0, [0, 0, 0]],
224
+ [0.33, [0, 0, 255]],
225
+ [0.66, [255, 255, 0]],
226
+ [1, [255, 0, 0]]
227
+ ];
228
+ for (let i = 1; i < stops.length; i++) {
229
+ if (x <= stops[i][0]) {
230
+ const [t0, c0] = stops[i - 1];
231
+ const [t1, c1] = stops[i];
232
+ const f = (x - t0) / (t1 - t0 || 1);
233
+ return [
234
+ Math.round(c0[0] + f * (c1[0] - c0[0])),
235
+ Math.round(c0[1] + f * (c1[1] - c0[1])),
236
+ Math.round(c0[2] + f * (c1[2] - c0[2]))
237
+ ];
238
+ }
239
+ }
240
+ return [255, 0, 0];
241
+ }
242
+ function composeDiffHeatmap(a, b, size = 256) {
243
+ const A = nearestNeighborResize(a, size, size);
244
+ const B = nearestNeighborResize(b, size, size);
245
+ const out = new PNG({ width: size, height: size });
246
+ for (let p = 0; p < size * size; p++) {
247
+ const j = p * 4;
248
+ const d = (Math.abs(A.data[j] - B.data[j]) + Math.abs(A.data[j + 1] - B.data[j + 1]) + Math.abs(A.data[j + 2] - B.data[j + 2])) / (3 * 255);
249
+ const [r, g, bl] = heatColor(d);
250
+ out.data[j] = r;
251
+ out.data[j + 1] = g;
252
+ out.data[j + 2] = bl;
253
+ out.data[j + 3] = 255;
254
+ }
255
+ return PNG.sync.write(out);
256
+ }
257
+ function composeFilmstrip(frameBase64s, opts = {}) {
258
+ if (!Array.isArray(frameBase64s) || frameBase64s.length === 0) {
259
+ throw new Error("composeFilmstrip: no frames");
260
+ }
261
+ const sep = opts.sep ?? 2;
262
+ const frames = frameBase64s.map((b) => decodePng(Buffer.from(stripPrefix(b), "base64")));
263
+ const h = Math.min(...frames.map((f) => f.height));
264
+ const resized = frames.map(
265
+ (f) => nearestNeighborResize(f, Math.round(f.width * h / f.height), h)
266
+ );
267
+ const totalW = resized.reduce((acc, f, i) => acc + f.width + (i > 0 ? sep : 0), 0);
268
+ const out = new PNG({ width: totalW, height: h });
269
+ for (let i = 0; i < out.data.length; i += 4) out.data[i + 3] = 255;
270
+ let x = 0;
271
+ for (let f = 0; f < resized.length; f++) {
272
+ const img = resized[f];
273
+ for (let y = 0; y < h; y++) {
274
+ const srcRow = y * img.width * 4;
275
+ const dstRow = (y * totalW + x) * 4;
276
+ img.data.copy(out.data, dstRow, srcRow, srcRow + img.width * 4);
277
+ }
278
+ x += img.width + sep;
279
+ }
280
+ return PNG.sync.write(out);
281
+ }
282
+ function motionMagnitudeFromFrames(frameBase64s) {
283
+ if (!Array.isArray(frameBase64s) || frameBase64s.length < 2) return 0;
284
+ const decoded = frameBase64s.map((b) => decodePng(Buffer.from(stripPrefix(b), "base64")));
285
+ let total = 0;
286
+ for (let i = 1; i < decoded.length; i++) {
287
+ total += meanAbsDiff(decoded[i - 1], decoded[i]);
288
+ }
289
+ return +(total / (decoded.length - 1)).toFixed(2);
290
+ }
291
+ function setReferenceMotion({ cwd, element, camera, frameBase64s, meta }) {
292
+ if (!Array.isArray(frameBase64s) || frameBase64s.length < 2) {
293
+ throw new Error("setReferenceMotion: need at least 2 frames");
294
+ }
295
+ const filmstrip = composeFilmstrip(frameBase64s);
296
+ const { filmstrip: fpath, meta: mpath } = refsMotionPaths(cwd, element, camera);
297
+ mkdirSync(dirname(fpath), { recursive: true });
298
+ writeFileSync(fpath, filmstrip);
299
+ writeFileSync(
300
+ mpath,
301
+ JSON.stringify(
302
+ {
303
+ frames: frameBase64s.length,
304
+ ...meta,
305
+ savedAt: (/* @__PURE__ */ new Date()).toISOString()
306
+ },
307
+ null,
308
+ 2
309
+ )
310
+ );
311
+ return { filmstripPath: fpath, metaPath: mpath, frames: frameBase64s.length };
312
+ }
313
+ function diffReferenceMotion({ cwd, element, camera, currentFrames }) {
314
+ const { filmstrip: fpath, meta: mpath } = refsMotionPaths(cwd, element, camera);
315
+ if (!existsSync(fpath)) {
316
+ throw new Error(`no motion reference at ${fpath} \u2014 call set_reference_motion first`);
317
+ }
318
+ if (!Array.isArray(currentFrames) || currentFrames.length === 0) {
319
+ throw new Error("currentFrames must be a non-empty array of base64 PNGs");
320
+ }
321
+ const refFilmstrip = decodePng(readFileSync(fpath));
322
+ const curFilmstrip = decodePng(composeFilmstrip(currentFrames));
323
+ const h = Math.min(refFilmstrip.height, curFilmstrip.height);
324
+ const lw = Math.round(refFilmstrip.width * h / refFilmstrip.height);
325
+ const rw = Math.round(curFilmstrip.width * h / curFilmstrip.height);
326
+ const w = Math.max(lw, rw);
327
+ const sep = 4;
328
+ const composite = new PNG({ width: w, height: h * 2 + sep });
329
+ for (let i = 0; i < composite.data.length; i += 4) composite.data[i + 3] = 255;
330
+ const refResized = nearestNeighborResize(refFilmstrip, w, h);
331
+ const curResized = nearestNeighborResize(curFilmstrip, w, h);
332
+ for (let y = 0; y < h; y++) {
333
+ refResized.data.copy(composite.data, y * w * 4, y * w * 4, (y + 1) * w * 4);
334
+ curResized.data.copy(composite.data, (y + h + sep) * w * 4, y * w * 4, (y + 1) * w * 4);
335
+ }
336
+ let meta = null;
337
+ try {
338
+ meta = existsSync(mpath) ? JSON.parse(readFileSync(mpath, "utf8")) : null;
339
+ } catch {
340
+ }
341
+ const motionDiff = meanAbsDiff(refFilmstrip, curFilmstrip);
342
+ return {
343
+ refFilmstripPath: fpath,
344
+ refMeta: meta,
345
+ motionDiff,
346
+ compositeBase64: PNG.sync.write(composite).toString("base64")
347
+ };
348
+ }
349
+ function diffReference({ cwd, element, camera, currentBase64 }) {
350
+ const refPath = refsPath(cwd, element, camera);
351
+ if (!existsSync(refPath)) {
352
+ throw new Error(`no reference at ${refPath} \u2014 call set_reference first`);
353
+ }
354
+ if (!currentBase64) throw new Error("currentBase64 is required");
355
+ const refPng = decodePng(readFileSync(refPath));
356
+ const curPng = decodePng(Buffer.from(stripPrefix(currentBase64), "base64"));
357
+ const composite = composeSideBySide(refPng, curPng);
358
+ const compositeBuf = PNG.sync.write(composite);
359
+ const meanAbs = meanAbsDiff(refPng, curPng);
360
+ const ssimScore = ssim(refPng, curPng);
361
+ const tiles = ssimTileGrid(refPng, curPng);
362
+ const heatmap = composeDiffHeatmap(refPng, curPng);
363
+ return {
364
+ camera,
365
+ refPath,
366
+ meanAbsDiff: meanAbs,
367
+ // 0 = identical, 255 = max difference
368
+ ssim: ssimScore,
369
+ // 1 = identical, lower = more different
370
+ // Spatial localization: 8×8 SSIM map + the single worst tile, so the agent
371
+ // knows WHERE the divergence is without re-reading the composite image.
372
+ tileGrid: tiles.grid,
373
+ worstTile: tiles.worst,
374
+ tileStats: { min: tiles.min, max: tiles.max, mean: tiles.mean },
375
+ compositeBase64: compositeBuf.toString("base64"),
376
+ heatmapBase64: heatmap.toString("base64")
377
+ };
378
+ }
379
+ export {
380
+ composeDiffHeatmap,
381
+ composeFilmstrip,
382
+ composeSideBySide,
383
+ decodePng,
384
+ diffReference,
385
+ diffReferenceMotion,
386
+ meanAbsDiff,
387
+ motionMagnitudeFromFrames,
388
+ nearestNeighborResize,
389
+ refsMotionPaths,
390
+ refsPath,
391
+ setReference,
392
+ setReferenceMotion,
393
+ ssim,
394
+ ssimTileGrid
395
+ };
396
+ //# sourceMappingURL=refs.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/refs.ts"],"sourcesContent":["// Reference photo primitives: save a reference PNG per (element, camera),\n// then compose a side-by-side diff against the current view + a scalar\n// mean-absolute-difference. Designed so the user can paste an image in chat\n// and have Claude pipe it straight into set_reference without an intermediate\n// file system dance — both `path` and `base64` inputs are accepted.\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, join, resolve } from 'node:path';\nimport { PNG } from 'pngjs';\n\nconst stripPrefix = (s) => s.replace(/^data:image\\/png;base64,/, '');\n\nfunction refsRoot(cwd) {\n return resolve(cwd, 'refs');\n}\n\nexport function refsPath(cwd, element, camera) {\n // Sanitize the camera name to a single path segment so any name the\n // element declares can become a filename safely.\n const safeCam = String(camera).replace(/[^A-Za-z0-9._-]/g, '_');\n return join(refsRoot(cwd), element, `${safeCam}.png`);\n}\n\nexport function refsMotionPaths(cwd, element, camera) {\n const safeCam = String(camera).replace(/[^A-Za-z0-9._-]/g, '_');\n const base = join(refsRoot(cwd), element);\n return {\n filmstrip: join(base, `${safeCam}.motion.png`),\n meta: join(base, `${safeCam}.motion.json`),\n };\n}\n\nexport function setReference({\n cwd,\n element,\n camera,\n path,\n base64,\n}: {\n cwd: string;\n element: string;\n camera: string;\n path?: string;\n base64?: string;\n}) {\n if (!element || !camera) throw new Error('element and camera are required');\n let bytes: Buffer;\n if (path) {\n if (!existsSync(path)) throw new Error(`reference file not found: ${path}`);\n bytes = readFileSync(path);\n } else if (base64) {\n bytes = Buffer.from(stripPrefix(base64), 'base64');\n } else {\n throw new Error('provide either path or base64');\n }\n const dest = refsPath(cwd, element, camera);\n mkdirSync(dirname(dest), { recursive: true });\n writeFileSync(dest, bytes);\n return { path: dest, bytes: bytes.length };\n}\n\nexport function decodePng(buffer) {\n return PNG.sync.read(buffer);\n}\n\nexport function nearestNeighborResize(src, targetW, targetH) {\n if (src.width === targetW && src.height === targetH) return src;\n const dst = new PNG({ width: targetW, height: targetH });\n for (let y = 0; y < targetH; y++) {\n const sy = Math.min(src.height - 1, Math.floor((y * src.height) / targetH));\n for (let x = 0; x < targetW; x++) {\n const sx = Math.min(src.width - 1, Math.floor((x * src.width) / targetW));\n const si = (sy * src.width + sx) * 4;\n const di = (y * targetW + x) * 4;\n dst.data[di] = src.data[si];\n dst.data[di + 1] = src.data[si + 1];\n dst.data[di + 2] = src.data[si + 2];\n dst.data[di + 3] = 255;\n }\n }\n return dst;\n}\n\nexport function composeSideBySide(left, right) {\n // Match heights to the smaller of the two so we don't grow the payload,\n // then concatenate horizontally with a 4-px black separator.\n const h = Math.min(left.height, right.height);\n const sep = 4;\n const lw = Math.round((left.width * h) / left.height);\n const rw = Math.round((right.width * h) / right.height);\n const w = lw + sep + rw;\n const L = nearestNeighborResize(left, lw, h);\n const R = nearestNeighborResize(right, rw, h);\n const out = new PNG({ width: w, height: h });\n // Fill background black (default is zeros — alpha would be 0; force 255).\n for (let i = 0; i < out.data.length; i += 4) out.data[i + 3] = 255;\n for (let y = 0; y < h; y++) {\n const orow = y * w * 4;\n const lrow = y * lw * 4;\n const rrow = y * rw * 4;\n L.data.copy(out.data, orow, lrow, lrow + lw * 4);\n R.data.copy(out.data, orow + (lw + sep) * 4, rrow, rrow + rw * 4);\n }\n return out;\n}\n\nexport function meanAbsDiff(a, b) {\n // Resize to a common 256x256 grid to keep the metric cheap and\n // resolution-independent.\n const W = 256;\n const H = 256;\n const A = nearestNeighborResize(a, W, H);\n const B = nearestNeighborResize(b, W, H);\n let sum = 0;\n const pixels = W * H * 3;\n for (let i = 0; i < W * H; i++) {\n const j = i * 4;\n sum += Math.abs(A.data[j] - B.data[j]);\n sum += Math.abs(A.data[j + 1] - B.data[j + 1]);\n sum += Math.abs(A.data[j + 2] - B.data[j + 2]);\n }\n return +(sum / pixels).toFixed(2);\n}\n\n/**\n * Mean Structural Similarity Index (SSIM) — perceptual metric that's\n * robust to mild brightness/contrast shifts and sensitive to structural\n * change. Returns a value in [-1, 1] where 1 = identical, 0 = no\n * correlation, negative = inverse. Most natural-image diffs land in\n * [0.5, 1.0]; below ~0.9 starts to look visibly different.\n *\n * We compute it over Rec.709 luminance on a 256×256 downsample (same as\n * meanAbsDiff for comparability), with non-overlapping 8×8 windows. The\n * three terms are luminance, contrast, and structure with the standard\n * stability constants K1=0.01, K2=0.03 against dynamic range L=255.\n *\n * This is the metric auto_tune optimizes against — meanAbsDiff is too\n * noisy for an optimizer (chases pixel-level glitter), SSIM tracks the\n * actual structure of what the human sees.\n */\nexport function ssim(a, b): number {\n const W = 256;\n const H = 256;\n const A = nearestNeighborResize(a, W, H);\n const B = nearestNeighborResize(b, W, H);\n const lumA = new Float32Array(W * H);\n const lumB = new Float32Array(W * H);\n for (let i = 0; i < W * H; i++) {\n const j = i * 4;\n lumA[i] = 0.2126 * A.data[j] + 0.7152 * A.data[j + 1] + 0.0722 * A.data[j + 2];\n lumB[i] = 0.2126 * B.data[j] + 0.7152 * B.data[j + 1] + 0.0722 * B.data[j + 2];\n }\n const L = 255;\n const C1 = (0.01 * L) ** 2;\n const C2 = (0.03 * L) ** 2;\n const WIN = 8;\n let total = 0;\n let count = 0;\n for (let wy = 0; wy < H; wy += WIN) {\n for (let wx = 0; wx < W; wx += WIN) {\n let muA = 0,\n muB = 0;\n for (let dy = 0; dy < WIN; dy++)\n for (let dx = 0; dx < WIN; dx++) {\n const i = (wy + dy) * W + (wx + dx);\n muA += lumA[i];\n muB += lumB[i];\n }\n muA /= WIN * WIN;\n muB /= WIN * WIN;\n let varA = 0,\n varB = 0,\n covAB = 0;\n for (let dy = 0; dy < WIN; dy++)\n for (let dx = 0; dx < WIN; dx++) {\n const i = (wy + dy) * W + (wx + dx);\n const da = lumA[i] - muA;\n const db = lumB[i] - muB;\n varA += da * da;\n varB += db * db;\n covAB += da * db;\n }\n varA /= WIN * WIN - 1;\n varB /= WIN * WIN - 1;\n covAB /= WIN * WIN - 1;\n const num = (2 * muA * muB + C1) * (2 * covAB + C2);\n const den = (muA * muA + muB * muB + C1) * (varA + varB + C2);\n total += num / den;\n count += 1;\n }\n }\n return +(total / count).toFixed(4);\n}\n\nexport interface TileGrid {\n /** SSIM per tile, grid[row][col], in [-1, 1] (1 = identical region). */\n grid: number[][];\n /** The single most-divergent tile — tells the agent WHERE frames differ. */\n worst: { row: number; col: number; ssim: number };\n min: number;\n max: number;\n mean: number;\n gridSize: number;\n}\n\n/**\n * Per-tile SSIM map. The scalar `ssim()` averages away the spatial detail it\n * computes per window; this surfaces a coarse `gridSize × gridSize` map (default\n * 8×8 over the 256×256 downsample, i.e. 32-px tiles) plus the worst tile, so an\n * agent can localize \"bow specular clipped, top-right\" instead of re-reading the\n * composite image. Same luminance + windowed-SSIM math as `ssim()`.\n */\nexport function ssimTileGrid(a, b, gridSize = 8): TileGrid {\n const W = 256;\n const H = 256;\n const A = nearestNeighborResize(a, W, H);\n const B = nearestNeighborResize(b, W, H);\n const lumA = new Float32Array(W * H);\n const lumB = new Float32Array(W * H);\n for (let i = 0; i < W * H; i++) {\n const j = i * 4;\n lumA[i] = 0.2126 * A.data[j] + 0.7152 * A.data[j + 1] + 0.0722 * A.data[j + 2];\n lumB[i] = 0.2126 * B.data[j] + 0.7152 * B.data[j + 1] + 0.0722 * B.data[j + 2];\n }\n const L = 255;\n const C1 = (0.01 * L) ** 2;\n const C2 = (0.03 * L) ** 2;\n const tile = Math.floor(W / gridSize);\n const grid: number[][] = [];\n let worst = { row: 0, col: 0, ssim: Number.POSITIVE_INFINITY };\n let min = Number.POSITIVE_INFINITY;\n let max = Number.NEGATIVE_INFINITY;\n let sum = 0;\n let count = 0;\n for (let r = 0; r < gridSize; r++) {\n const rowArr: number[] = [];\n for (let c = 0; c < gridSize; c++) {\n const x0 = c * tile;\n const y0 = r * tile;\n const n = tile * tile;\n let muA = 0;\n let muB = 0;\n for (let dy = 0; dy < tile; dy++)\n for (let dx = 0; dx < tile; dx++) {\n const i = (y0 + dy) * W + (x0 + dx);\n muA += lumA[i];\n muB += lumB[i];\n }\n muA /= n;\n muB /= n;\n let vA = 0;\n let vB = 0;\n let cov = 0;\n for (let dy = 0; dy < tile; dy++)\n for (let dx = 0; dx < tile; dx++) {\n const i = (y0 + dy) * W + (x0 + dx);\n const da = lumA[i] - muA;\n const db = lumB[i] - muB;\n vA += da * da;\n vB += db * db;\n cov += da * db;\n }\n vA /= n - 1;\n vB /= n - 1;\n cov /= n - 1;\n const s =\n ((2 * muA * muB + C1) * (2 * cov + C2)) / ((muA * muA + muB * muB + C1) * (vA + vB + C2));\n const sr = +s.toFixed(4);\n rowArr.push(sr);\n sum += sr;\n count += 1;\n if (sr < min) min = sr;\n if (sr > max) max = sr;\n if (sr < worst.ssim) worst = { row: r, col: c, ssim: sr };\n }\n grid.push(rowArr);\n }\n return {\n grid,\n worst,\n min: +min.toFixed(4),\n max: +max.toFixed(4),\n mean: +(sum / count).toFixed(4),\n gridSize,\n };\n}\n\nfunction heatColor(t: number): [number, number, number] {\n const x = Math.max(0, Math.min(1, t));\n // black → blue → yellow → red\n const stops: Array<[number, [number, number, number]]> = [\n [0, [0, 0, 0]],\n [0.33, [0, 0, 255]],\n [0.66, [255, 255, 0]],\n [1, [255, 0, 0]],\n ];\n for (let i = 1; i < stops.length; i++) {\n if (x <= stops[i][0]) {\n const [t0, c0] = stops[i - 1];\n const [t1, c1] = stops[i];\n const f = (x - t0) / (t1 - t0 || 1);\n return [\n Math.round(c0[0] + f * (c1[0] - c0[0])),\n Math.round(c0[1] + f * (c1[1] - c0[1])),\n Math.round(c0[2] + f * (c1[2] - c0[2])),\n ];\n }\n }\n return [255, 0, 0];\n}\n\n/**\n * Per-pixel difference heatmap (black=identical → blue → yellow → red=max\n * difference) on the 256×256 downsample. Returns a PNG Buffer the diff tool\n * ships as a second image block so the agent SEES where the frames diverge.\n */\nexport function composeDiffHeatmap(a, b, size = 256): Buffer {\n const A = nearestNeighborResize(a, size, size);\n const B = nearestNeighborResize(b, size, size);\n const out = new PNG({ width: size, height: size });\n for (let p = 0; p < size * size; p++) {\n const j = p * 4;\n const d =\n (Math.abs(A.data[j] - B.data[j]) +\n Math.abs(A.data[j + 1] - B.data[j + 1]) +\n Math.abs(A.data[j + 2] - B.data[j + 2])) /\n (3 * 255);\n const [r, g, bl] = heatColor(d);\n out.data[j] = r;\n out.data[j + 1] = g;\n out.data[j + 2] = bl;\n out.data[j + 3] = 255;\n }\n return PNG.sync.write(out);\n}\n\nexport function composeFilmstrip(frameBase64s: string[], opts: { sep?: number } = {}): Buffer {\n // Tile N frames horizontally with a 2-px black separator. Each frame is\n // resized to match the smallest source height (so payload stays bounded\n // even if frames are e.g. 1600x900 each). Returns a PNG Buffer.\n if (!Array.isArray(frameBase64s) || frameBase64s.length === 0) {\n throw new Error('composeFilmstrip: no frames');\n }\n const sep = opts.sep ?? 2;\n const frames = frameBase64s.map((b) => decodePng(Buffer.from(stripPrefix(b), 'base64')));\n const h = Math.min(...frames.map((f) => f.height));\n const resized = frames.map((f) =>\n nearestNeighborResize(f, Math.round((f.width * h) / f.height), h),\n );\n const totalW = resized.reduce((acc, f, i) => acc + f.width + (i > 0 ? sep : 0), 0);\n const out = new PNG({ width: totalW, height: h });\n for (let i = 0; i < out.data.length; i += 4) out.data[i + 3] = 255;\n let x = 0;\n for (let f = 0; f < resized.length; f++) {\n const img = resized[f];\n for (let y = 0; y < h; y++) {\n const srcRow = y * img.width * 4;\n const dstRow = (y * totalW + x) * 4;\n img.data.copy(out.data, dstRow, srcRow, srcRow + img.width * 4);\n }\n x += img.width + sep;\n }\n return PNG.sync.write(out);\n}\n\nexport function motionMagnitudeFromFrames(frameBase64s) {\n // Mean over consecutive-frame pairs of meanAbsDiff. 256x256 downscale.\n // 0 = no motion; >5 = visible; >20 = vigorous.\n if (!Array.isArray(frameBase64s) || frameBase64s.length < 2) return 0;\n const decoded = frameBase64s.map((b) => decodePng(Buffer.from(stripPrefix(b), 'base64')));\n let total = 0;\n for (let i = 1; i < decoded.length; i++) {\n total += meanAbsDiff(decoded[i - 1], decoded[i]);\n }\n return +(total / (decoded.length - 1)).toFixed(2);\n}\n\nexport function setReferenceMotion({ cwd, element, camera, frameBase64s, meta }) {\n if (!Array.isArray(frameBase64s) || frameBase64s.length < 2) {\n throw new Error('setReferenceMotion: need at least 2 frames');\n }\n const filmstrip = composeFilmstrip(frameBase64s);\n const { filmstrip: fpath, meta: mpath } = refsMotionPaths(cwd, element, camera);\n mkdirSync(dirname(fpath), { recursive: true });\n writeFileSync(fpath, filmstrip);\n writeFileSync(\n mpath,\n JSON.stringify(\n {\n frames: frameBase64s.length,\n ...meta,\n savedAt: new Date().toISOString(),\n },\n null,\n 2,\n ),\n );\n return { filmstripPath: fpath, metaPath: mpath, frames: frameBase64s.length };\n}\n\nexport function diffReferenceMotion({ cwd, element, camera, currentFrames }) {\n const { filmstrip: fpath, meta: mpath } = refsMotionPaths(cwd, element, camera);\n if (!existsSync(fpath)) {\n throw new Error(`no motion reference at ${fpath} — call set_reference_motion first`);\n }\n if (!Array.isArray(currentFrames) || currentFrames.length === 0) {\n throw new Error('currentFrames must be a non-empty array of base64 PNGs');\n }\n const refFilmstrip = decodePng(readFileSync(fpath));\n const curFilmstrip = decodePng(composeFilmstrip(currentFrames));\n // Stack vertically: reference on top, current on bottom, 4-px separator.\n const h = Math.min(refFilmstrip.height, curFilmstrip.height);\n const lw = Math.round((refFilmstrip.width * h) / refFilmstrip.height);\n const rw = Math.round((curFilmstrip.width * h) / curFilmstrip.height);\n const w = Math.max(lw, rw);\n const sep = 4;\n const composite = new PNG({ width: w, height: h * 2 + sep });\n for (let i = 0; i < composite.data.length; i += 4) composite.data[i + 3] = 255;\n const refResized = nearestNeighborResize(refFilmstrip, w, h);\n const curResized = nearestNeighborResize(curFilmstrip, w, h);\n for (let y = 0; y < h; y++) {\n refResized.data.copy(composite.data, y * w * 4, y * w * 4, (y + 1) * w * 4);\n curResized.data.copy(composite.data, (y + h + sep) * w * 4, y * w * 4, (y + 1) * w * 4);\n }\n // Per-frame mean abs diff if we have a saved frame count to align with.\n let meta = null;\n try {\n meta = existsSync(mpath) ? JSON.parse(readFileSync(mpath, 'utf8')) : null;\n } catch {\n /* tolerate corrupt meta */\n }\n const motionDiff = meanAbsDiff(refFilmstrip, curFilmstrip);\n return {\n refFilmstripPath: fpath,\n refMeta: meta,\n motionDiff,\n compositeBase64: PNG.sync.write(composite).toString('base64'),\n };\n}\n\nexport function diffReference({ cwd, element, camera, currentBase64 }) {\n const refPath = refsPath(cwd, element, camera);\n if (!existsSync(refPath)) {\n throw new Error(`no reference at ${refPath} — call set_reference first`);\n }\n if (!currentBase64) throw new Error('currentBase64 is required');\n const refPng = decodePng(readFileSync(refPath));\n const curPng = decodePng(Buffer.from(stripPrefix(currentBase64), 'base64'));\n const composite = composeSideBySide(refPng, curPng);\n const compositeBuf = PNG.sync.write(composite);\n const meanAbs = meanAbsDiff(refPng, curPng);\n const ssimScore = ssim(refPng, curPng);\n const tiles = ssimTileGrid(refPng, curPng);\n const heatmap = composeDiffHeatmap(refPng, curPng);\n return {\n camera,\n refPath,\n meanAbsDiff: meanAbs, // 0 = identical, 255 = max difference\n ssim: ssimScore, // 1 = identical, lower = more different\n // Spatial localization: 8×8 SSIM map + the single worst tile, so the agent\n // knows WHERE the divergence is without re-reading the composite image.\n tileGrid: tiles.grid,\n worstTile: tiles.worst,\n tileStats: { min: tiles.min, max: tiles.max, mean: tiles.mean },\n compositeBase64: compositeBuf.toString('base64'),\n heatmapBase64: heatmap.toString('base64'),\n };\n}\n"],"mappings":"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","names":[]}