← back to Wallco Ai
T6: gen-luxe --verify-subjects flag (off by default, ~$0.0005/check)
fbe6091f24c1ce4f396cf6f028b8046d10ac434d · 2026-05-25 04:29:14 -0700 · Steve Abrams
Adds an optional post-gate inside the existing composition retry loop:
once a candidate image passes composition, if --verify-subjects is set,
call lib/subject-detector.gateSubjectPresence with subjects auto-extracted
from the motif via extractSubjectsFromPrompt. Primary subject (first
extracted, the animal noun) MUST be visibly present or the attempt
retries — catches the bullfrog-flowers / parrot-bottles failure class.
Design choices:
- SUBJECTS extracted ONCE before the loop (regex only, no LLM cost).
- Only the PRIMARY subject blocks; missing secondary subjects log but
don't retry. Mirrors the scan-subject-mismatch.js primary-only flag
semantics so retry pressure matches diagnostic flag pressure.
- Subject-verify ERR is non-fatal — composition-passing candidate is
accepted with a warning. Keeps gen-luxe robust to vision transient
failures.
- lastSubjectResult is recorded into the prompt-metadata column of the
inserted spoon_all_designs row so the verify status is queryable
later ("Subject-verify: primary=bullfrog present=YES ...").
- OFF by default — adds ~$0.0005/attempt × up to 3 attempts per
variant = up to ~$0.0015/regen extra. Enable for high-stakes
individual regens via the ↻ button (future enhancement) or for
categories with known drift (e.g. add to scripts/queue-luxe-curator
via --variants-verify-subjects flag if Steve wants).
Pairs with [[feedback_bulk_transforms_need_heterogeneous_sample]] and
the subject-detector + scan-subject-mismatch pipeline shipped earlier
in this overnight loop (caabe86, e7bc7e1).
Files touched
Diff
commit fbe6091f24c1ce4f396cf6f028b8046d10ac434d
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Mon May 25 04:29:14 2026 -0700
T6: gen-luxe --verify-subjects flag (off by default, ~$0.0005/check)
Adds an optional post-gate inside the existing composition retry loop:
once a candidate image passes composition, if --verify-subjects is set,
call lib/subject-detector.gateSubjectPresence with subjects auto-extracted
from the motif via extractSubjectsFromPrompt. Primary subject (first
extracted, the animal noun) MUST be visibly present or the attempt
retries — catches the bullfrog-flowers / parrot-bottles failure class.
Design choices:
- SUBJECTS extracted ONCE before the loop (regex only, no LLM cost).
- Only the PRIMARY subject blocks; missing secondary subjects log but
don't retry. Mirrors the scan-subject-mismatch.js primary-only flag
semantics so retry pressure matches diagnostic flag pressure.
- Subject-verify ERR is non-fatal — composition-passing candidate is
accepted with a warning. Keeps gen-luxe robust to vision transient
failures.
- lastSubjectResult is recorded into the prompt-metadata column of the
inserted spoon_all_designs row so the verify status is queryable
later ("Subject-verify: primary=bullfrog present=YES ...").
- OFF by default — adds ~$0.0005/attempt × up to 3 attempts per
variant = up to ~$0.0015/regen extra. Enable for high-stakes
individual regens via the ↻ button (future enhancement) or for
categories with known drift (e.g. add to scripts/queue-luxe-curator
via --variants-verify-subjects flag if Steve wants).
Pairs with [[feedback_bulk_transforms_need_heterogeneous_sample]] and
the subject-detector + scan-subject-mismatch pipeline shipped earlier
in this overnight loop (caabe86, e7bc7e1).
---
scripts/gen-luxe.js | 45 +++++++++++++++++++++++++++++++++++++++++----
1 file changed, 41 insertions(+), 4 deletions(-)
diff --git a/scripts/gen-luxe.js b/scripts/gen-luxe.js
index 5d09bc2..6a492bf 100644
--- a/scripts/gen-luxe.js
+++ b/scripts/gen-luxe.js
@@ -21,6 +21,7 @@ const fs = require('fs');
const path = require('path');
const { spawnSync } = require('child_process');
const { gateComposition } = require('../lib/composition-detector');
+const { gateSubjectPresence, extractSubjectsFromPrompt } = require('../lib/subject-detector');
const SRC_ID = parseInt(process.argv[2], 10);
const MOTIF = process.argv[3];
@@ -31,8 +32,15 @@ const VARIANT = ((process.argv.find(a => a.startsWith('--variant=')) || '').spli
// the source. For the hot-or-not curator workflow where Steve picks the
// keeper from a 4-up before anything ships.
const CURATOR = process.argv.includes('--curator-mode');
+// --verify-subjects runs lib/subject-detector (gemini vision) AFTER the
+// composition gate. Both must pass for the attempt to break the loop —
+// catches the "background only, subject dropped" failure class
+// (bullfrog → flowers, parrots → bottles). Adds ~$0.0005 per attempt.
+// OFF by default to keep batch runs cheap; turn on for higher-stakes
+// individual regens or when you've seen subject drift in a category.
+const VERIFY_SUBJECTS = process.argv.includes('--verify-subjects');
if (!Number.isFinite(SRC_ID) || !MOTIF) {
- console.error('usage: <src_id> "<motif>" [--texture-cat=<grasscloth|linen|cork|silk|mica|...>] [--variant=<A|B|C|D|E>] [--curator-mode]');
+ console.error('usage: <src_id> "<motif>" [--texture-cat=<grasscloth|linen|cork|silk|mica|...>] [--variant=<A|B|C|D|E>] [--curator-mode] [--verify-subjects]');
process.exit(2);
}
@@ -162,6 +170,13 @@ function pickTextureBrief(category) {
const VARIANT_IDX = { A: 0, B: 1, C: 2, D: 3, E: 4 };
const startAttempt = VARIANT in VARIANT_IDX ? VARIANT_IDX[VARIANT] : 0;
const MAX = VARIANT ? 3 : 5;
+ // Extract subjects from motif ONCE (regex-only, no LLM cost) — reused
+ // across attempts if --verify-subjects is on.
+ const SUBJECTS = VERIFY_SUBJECTS ? extractSubjectsFromPrompt(MOTIF, { max: 2 }) : [];
+ if (VERIFY_SUBJECTS) {
+ console.log(`[gen-luxe] verify-subjects=ON · checking visibility of: [${SUBJECTS.join(', ') || '(none extractable, skipping verify)'}]`);
+ }
+ let lastSubjectResult = null; // recorded into PG metadata at the end
while (attempt < MAX) {
// When locked to a variant, pass startAttempt every time (no cycling).
const variantIdx = VARIANT ? startAttempt : (startAttempt + attempt);
@@ -173,8 +188,30 @@ function pickTextureBrief(category) {
gate = await gateComposition(b64, { minInstances: 3 });
console.log(` composition: ${gate.ok ? 'PASS' : 'FAIL'} · n=${gate.result.instance_count} hero=${gate.result.hero_centered}`);
console.log(` saw: ${gate.result.what_you_see}`);
- if (gate.ok) { candidate = b64; break; }
- attempt++;
+ if (!gate.ok) { attempt++; continue; }
+
+ // Composition passed. Subject post-gate (optional).
+ if (VERIFY_SUBJECTS && SUBJECTS.length) {
+ try {
+ const subjResult = await gateSubjectPresence(b64, { subjects: SUBJECTS });
+ lastSubjectResult = subjResult.result;
+ const primary = SUBJECTS[0];
+ const primaryPresent = subjResult.result.present.includes(primary);
+ console.log(` subject-verify: primary "${primary}" ${primaryPresent ? 'PRESENT' : 'MISSING'} · present=[${subjResult.result.present.join(',')}] missing=[${subjResult.result.missing.join(',')}]`);
+ if (!primaryPresent) {
+ // Primary subject dropped — retry. This is the bullfrog-flowers
+ // failure class we built the detector to catch.
+ attempt++;
+ continue;
+ }
+ } catch (e) {
+ // Verify endpoint failed — don't block on it, log + accept the
+ // composition-passing candidate
+ console.log(` subject-verify ERR (non-fatal): ${e.message}`);
+ }
+ }
+ candidate = b64;
+ break;
}
if (!candidate) {
console.log(`[gen-luxe] gate failed all ${MAX}, accepting last`);
@@ -194,7 +231,7 @@ INSERT INTO spoon_all_designs
VALUES
('seamless_tile', 'wallco.ai', 24, 24,
'gemini-2.5-flash-image-luxe-v2',
- ${esc('Luxe regen of #' + SRC_ID + ' anchored to de Gournay / House of Hackney / 1838-flock / Cole & Son / Brunschwig & Fils.' + (textureBrief ? ' Ground: ' + textureBrief + '.' : '') + ' Motif: ' + MOTIF)},
+ ${esc('Luxe regen of #' + SRC_ID + ' anchored to de Gournay / House of Hackney / 1838-flock / Cole & Son / Brunschwig & Fils.' + (textureBrief ? ' Ground: ' + textureBrief + '.' : '') + (lastSubjectResult ? ' Subject-verify: primary=' + (SUBJECTS[0]||'?') + ' present=' + (lastSubjectResult.present.includes(SUBJECTS[0]) ? 'YES' : 'NO') + ' all=[' + lastSubjectResult.present.join(',') + ']/missing=[' + lastSubjectResult.missing.join(',') + '].' : '') + ' Motif: ' + MOTIF)},
${newSeed},
'/designs/img/by-id/__NEW__',
${esc(outPath)},
← c2332ef T4: keyboard R = regen focused row; visual focus ring; K-key
·
back to Wallco Ai
·
T7: batch-quality audit — 99.3% ok, no variant/ground bias 929d53b →