fix(cli): fold canvas pixel hashes into the frozen-sweep fingerprint

This commit is contained in:
Vance Ingalls
2026-07-11 14:42:43 -07:00
parent 1dfb016f3b
commit 3338a6f350
@@ -895,6 +895,31 @@
// actually moved anything and the whole audit run is unreliable. Deliberately
// a single opaque string (not a structured array) since Node only ever needs
// equality, not per-element diffing.
// Pixel-only canvas motion (a 2D/WebGL canvas repainting without any element
// moving) is invisible to a geometry+opacity fingerprint and false-positived
// sweep_static (wild report: 4K WebGL comp needed a transparent moving DOM
// sentinel to pass). Downsample each visible canvas to 8x8 and fold its
// pixels into the fingerprint. Tainted/zero-sized/unreadable canvases hash
// to a constant — no worse than today, never a new false NEGATIVE for
// DOM-motion comps.
function canvasPixelHash(canvas) {
try {
if (!canvas.width || !canvas.height) return "x";
const off = document.createElement("canvas");
off.width = 8;
off.height = 8;
const ctx = off.getContext("2d");
if (!ctx) return "x";
ctx.drawImage(canvas, 0, 0, 8, 8);
const data = ctx.getImageData(0, 0, 8, 8).data;
let hash = 0;
for (let i = 0; i < data.length; i++) hash = (hash * 31 + data[i]) >>> 0;
return String(hash);
} catch {
return "x";
}
}
window.__hyperframesLayoutGeometry = function collectLayoutGeometry() {
const root =
document.querySelector("[data-composition-id][data-width][data-height]") ||
@@ -903,12 +928,15 @@
const elements = Array.from(root.querySelectorAll("*")).filter((element) =>
isVisibleElement(element),
);
return elements
.map((element) => {
const rect = toRect(element.getBoundingClientRect());
const opacity = round(opacityChain(element));
return `${rect.left},${rect.top},${rect.width},${rect.height},${opacity}`;
})
.join("|");
const parts = elements.map((element) => {
const rect = toRect(element.getBoundingClientRect());
const opacity = round(opacityChain(element));
return `${rect.left},${rect.top},${rect.width},${rect.height},${opacity}`;
});
for (const canvas of root.querySelectorAll("canvas")) {
if (!isVisibleElement(canvas)) continue;
parts.push(`c:${canvasPixelHash(canvas)}`);
}
return parts.join("|");
};
})();