diff --git a/js/app.js b/js/app.js index a213979..673a56f 100644 --- a/js/app.js +++ b/js/app.js @@ -47,7 +47,7 @@ const opts = () => ({ tongueReject: +el('tongueReject').value / 100, blobGrow: +el('blobGrow').value, topBias: +el('topBias').value / 100, - verts: +el('teethVerts').value, + teethVerts: +el('teethVerts').value, smoothWin: +el('smoothWin').value, contourSmooth: +el('contourSmooth').value, apertureThresh: +el('apertureThresh').value / 1000, @@ -146,7 +146,7 @@ function measureAll(images, dense, o) { // Extraction keys on every knob that changes the pixels examined, so the cache // is keyed on exactly those and a change to anything else stays instant. const extractKey = (o) => - [o.cavityErode, o.tongueReject, o.blobGrow, o.topBias, o.verts].join('|'); + [o.cavityErode, o.tongueReject, o.blobGrow, o.topBias, o.teethVerts].join('|'); /* ---------- build ---------- */ diff --git a/js/interior.js b/js/interior.js index f3d7053..226475a 100644 --- a/js/interior.js +++ b/js/interior.js @@ -191,7 +191,7 @@ export function extractTeeth(img, innerNorm, ctx, o, wantDebug = false) { for (const i of comp.px) { only[i] = 1; cx += i % pw; cy += (i / pw) | 0; } cx /= comp.px.length; cy /= comp.px.length; - const local = radialContour(only, pw, ph, cx, cy, o.verts ?? 10); + const local = radialContour(only, pw, ph, cx, cy, o.teethVerts ?? 10); const contour = local.map((p) => ({ x: (p.x + px0) / W, y: (p.y + py0) / H })); return { diff --git a/js/selftest.js b/js/selftest.js index 24cae67..82abc7f 100644 --- a/js/selftest.js +++ b/js/selftest.js @@ -63,6 +63,19 @@ export async function runWiring() { if (list) { for (const m of list[1].matchAll(/'([\w]+)'/g)) { ids.add(m[1]); ids.add(m[1] + 'v'); } } + // Duplicate keys in an object literal are silent in JS - the last one wins. + // In opts() that meant the teeth vertex slider was quietly driving the lip + // vertex count while the lip slider did nothing at all. + const lit = app.match(/const opts = \(\) => \(\{([\s\S]*?)\n\}\);/); + if (lit) { + const keys = [...lit[1].matchAll(/^\s*([A-Za-z_$][\w$]*)\s*:/gm)].map((m) => m[1]); + const dupes = keys.filter((k, i) => keys.indexOf(k) !== i); + out.push({ name: `opts() has no duplicate keys (${keys.length} checked)`, + pass: dupes.length === 0, detail: [...new Set(dupes)].join(', ') }); + } else { + out.push({ name: 'opts() literal found for duplicate-key check', pass: false, detail: '' }); + } + const have = new Set([...html.matchAll(/id="([\w-]+)"/g)].map((m) => m[1])); const missing = [...ids].filter((i) => !have.has(i)); out.push({ name: `every el() id exists in index.html (${ids.size} checked)`,