diff --git a/src/app/calibration-ui.ts b/src/app/calibration-ui.ts
index 12b99af..68ed23a 100644
--- a/src/app/calibration-ui.ts
+++ b/src/app/calibration-ui.ts
@@ -58,7 +58,7 @@ export function mountCalibration(deps: CalibrationDeps): void {
const renderList = (errs?: number[]) => {
list.innerHTML = points
- .map((p, i) => `
(${p.machine[0]}, ${p.machine[1]}) → img(${p.image[0].toFixed(3)}, ${p.image[1].toFixed(3)})${errs ? ` err ${(errs[i]! * overlay.width).toFixed(1)}px` : ''}`)
+ .map((p, i) => `(${p.machine[0]}, ${p.machine[1]}) → img(${p.image[0].toFixed(3)}, ${p.image[1].toFixed(3)})${errs ? ` err ${errs[i]!.toFixed(1)}px` : ''}`)
.join('');
};
@@ -100,8 +100,12 @@ export function mountCalibration(deps: CalibrationDeps): void {
const machine = points.map((p) => p.machine);
const image = points.map((p) => p.image);
const H = estimateHomography(machine, image);
- const errs = residuals(H, machine, image);
- renderList(errs);
+ // Per-axis pixel residual (normalized error scaled by canvas width/height) — used only for misclick display.
+ const errsPx = machine.map((m, i) => {
+ const p = applyHomography(H, m);
+ return Math.hypot((p[0] - image[i]![0]) * overlay.width, (p[1] - image[i]![1]) * overlay.height);
+ });
+ renderList(errsPx);
jsonEl.value = JSON.stringify(
{ imagePoints: image, machinePoints: machine, homography: H },
null,
diff --git a/src/main.ts b/src/main.ts
index 36f6f2c..ed4472b 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -47,8 +47,6 @@ fileInput.addEventListener('change', async () => {
render();
});
-// expose for the UI modules added in later tasks
-export const app = { state, render, resize, overlay, stage, stream };
mountCalibration({
panel: document.getElementById('calib-panel') as HTMLElement,
diff --git a/src/styles.css b/src/styles.css
index 672d4e6..47f062a 100644
--- a/src/styles.css
+++ b/src/styles.css
@@ -2,8 +2,8 @@
body { margin: 0; display: flex; height: 100vh; font-family: system-ui, sans-serif; background: #111; color: #eee; }
#stage { position: relative; flex: 1; overflow: hidden; background: #000; }
#stream { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; }
-#overlay { position: absolute; pointer-events: none; }
-#overlay.interactive { pointer-events: auto; cursor: crosshair; }
+#overlay { position: absolute; pointer-events: auto; cursor: default; }
+#overlay.interactive { cursor: crosshair; }
#panel { width: 320px; padding: 16px; overflow-y: auto; background: #1b1b1b; }
#panel h1 { font-size: 16px; }
.filebtn { display: inline-block; padding: 8px 12px; background: #2962ff; border-radius: 4px; cursor: pointer; }