[object Object]

← 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 →