diff --git a/movie_editor/frontend/detail_verdict.test.js b/movie_editor/frontend/detail_verdict.test.js new file mode 100644 index 00000000..845d5ce7 --- /dev/null +++ b/movie_editor/frontend/detail_verdict.test.js @@ -0,0 +1,57 @@ +// The settings panel's verdict logic, exercised rather than syntax-checked. +// +// It shipped a temporal-dead-zone throw ("can't access lexical declaration 'moved' before +// initialization") that `node --check` passed happily, because a `const` used above its own +// declaration is valid syntax and only fails when the branch actually runs. So these RUN +// every branch. +const test = require("node:test"); +const assert = require("node:assert"); +const fs = require("node:fs"); + +const src = fs.readFileSync(__dirname + "/settings_window.js", "utf8"); +const grab = (name) => src.match(new RegExp(`function ${name}[\\s\\S]*?\\n \\}\\n`))[0]; +const { dpVerdict, dpNoiseFloor } = new Function( + grab("dpVerdict") + grab("dpNoiseFloor") + "; return { dpVerdict, dpNoiseFloor };")(); + +const row = (o) => Object.assign( + { changed: {}, same_seed: true, structure: 1, detail: 1, edge_aligned: 0, cond_shift: 0 }, o); + +test("every branch runs without throwing", () => { + const cases = [ + row({}), + row({ structure: 0.42, changed: { h3_repr_steering: [true, false] } }), + row({ changed: { conditioning: [null, 1] }, cond_shift: 1.0, structure: 0.5 }), + row({ changed: { h3_block_repeat: ["", "40"] }, structure: 0.95, detail: 1.3, edge_aligned: 0.7 }), + row({ same_seed: false, changed: { seed: [1, 2] }, structure: 0.3 }), + row({ structure: 0.95, detail: 0.8, changed: { cfg: [1, 2] } }), + ]; + cases.forEach((r) => [null, 0.1].forEach((f) => assert.equal(typeof dpVerdict(r, f), "string"))); +}); + +test("a pair with nothing changed reports the noise floor", () => { + const v = dpVerdict(row({ structure: 0.9 }), 0.1); + assert.match(v, /noise floor: two identical runs differ by 10%/); +}); + +test("movement is expressed against the measured floor", () => { + const v = dpVerdict(row({ structure: 0.42, changed: { h3_repr_steering: [true, false] } }), 0.1); + assert.match(v, /5\.8x your noise floor/); +}); + +test("a real prompt change voids the comparison, a small drift does not", () => { + assert.match(dpVerdict(row({ changed: { conditioning: [null, 1] }, cond_shift: 1.0, structure: 0.5 }), 0.1), + /prompt or scenes changed/); + const drift = dpVerdict(row({ changed: { conditioning: [null, 0.02] }, cond_shift: 0.02, + structure: 0.95, detail: 1.3, edge_aligned: 0.7 }), 0.1); + assert.match(drift, /sharper/); +}); + +test("the floor is the median of the quiet pairs, so one outlier cannot set it", () => { + const rows = [row({ structure: 0.9 }), row({ structure: 0.91 }), row({ structure: 0.2 }), + row({ structure: 0.5, changed: { cfg: [1, 2] } })]; + assert.ok(Math.abs(dpNoiseFloor(rows) - 0.1) < 0.02); +}); + +test("no quiet pairs means no floor rather than a made-up one", () => { + assert.equal(dpNoiseFloor([row({ changed: { cfg: [1, 2] } })]), null); +}); diff --git a/movie_editor/frontend/settings_window.js b/movie_editor/frontend/settings_window.js index a201eb0e..08ed09ff 100644 --- a/movie_editor/frontend/settings_window.js +++ b/movie_editor/frontend/settings_window.js @@ -421,6 +421,12 @@ } function dpVerdict(r, floor) { + // Declared FIRST: every branch below can reach for these, and a `const` used above its + // own declaration is a temporal-dead-zone throw at runtime that a syntax check happily + // passes ("can't access lexical declaration 'moved' before initialization"). + const moved = 1 - r.structure; + const vsFloor = floor && floor > 1e-6 ? ` — ${(moved / floor).toFixed(1)}x your noise floor` + : ""; // A low `structure` has two very different causes and the numbers cannot separate them, // so the seed decides. Different seeds = two different generations and the comparison // never was an A/B (a reference pins the subject, not the sample). Same seed = the @@ -437,9 +443,6 @@ return `noise floor: two identical runs differ by ${(moved * 100).toFixed(0)}% ` + `(detail x${r.detail.toFixed(2)}). Anything smaller than this is not a result.`; } - const moved = 1 - r.structure; - const vsFloor = floor && floor > 1e-6 ? ` — ${(moved / floor).toFixed(1)}x your noise floor` - : ""; if (r.structure < 0.85) { return r.same_seed ? `the shot moved ${(moved * 100).toFixed(0)}%${vsFloor} — this changed the picture, ` @@ -642,9 +645,14 @@ row.append(lbl); rows.append(row); - rows.append(infoRow("Rated generations recorded (this key)", + // The count is HISTORY. Turning recording off does not clear it, and a bare number + // beside a green dot reads as "still recording" — say which it is. + const recording = !!(biState && biState.enabled); + rows.append(infoRow( + recording ? "Rated generations recorded (this key)" + : "Recorded earlier (not recording now)", biState ? `${runs} (${biState.n_liked} liked / ${biState.n_disliked} disliked)` - : "0", runs > 0 ? true : null)); + : "0", recording && runs > 0 ? true : null)); rows.append(actionRow("Save this key's measurement", "Downloads every recorded profile as one file. A rental gets replaced and " + "refinements/ is not in git, so without this the count restarts on the next box.",