[object Object]

← back to Wallco Ai

la-toile: fix prompt — gate off conflicting tone-on-tone palette suffix (BH colorway now wins → multi-color), remove palms from positive prompt (palm-free, matches negative+header)

dcdb6796c49e5ad9d23cc31b557209fddc8e27b3 · 2026-06-01 22:14:01 -0700 · Steve Abrams

Files touched

Diff

commit dcdb6796c49e5ad9d23cc31b557209fddc8e27b3
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Mon Jun 1 22:14:01 2026 -0700

    la-toile: fix prompt — gate off conflicting tone-on-tone palette suffix (BH colorway now wins → multi-color), remove palms from positive prompt (palm-free, matches negative+header)
---
 scripts/generate-la-toile-clean.js | 216 +++++++++++++++++++++++++++++++++++++
 scripts/generate_designs.js        |   8 +-
 2 files changed, 223 insertions(+), 1 deletion(-)

diff --git a/scripts/generate-la-toile-clean.js b/scripts/generate-la-toile-clean.js
new file mode 100644
index 0000000..e6ca81d
--- /dev/null
+++ b/scripts/generate-la-toile-clean.js
@@ -0,0 +1,216 @@
+#!/usr/bin/env node
+// Toile de Jouy of Los Angeles — regen-until-clean driver (30 scenes).
+// Steve 2026-06-01: monochrome LA toiles for asseeninla.com/catalog, keep birds.
+//
+// Per scene: generate (seamless_tile force-routes to ComfyUI for true seamless)
+// → 3 gates (seam PASS≤SEAM_MAX · flat-color ≤COLOR_CAP buckets · Gemini
+// concept-vision "is this a monochrome LA toile") → re-roll failures until clean
+// (cap ROLLS). Losing rolls marked user_removed=TRUE. Winners → /tmp/la-toile-winners.json.
+// Resumable: scenes with a recorded winner are skipped.
+//
+// Monochrome (single ink) ⇒ settlement A3=false ⇒ never a violation even with birds.
+
+const { spawnSync, execSync } = require('child_process');
+const fs = require('fs');
+const path = require('path');
+const ROOT = path.join(__dirname, '..');
+const PY = path.join(ROOT, 'scripts/vectorize/.venv/bin/python3');
+const WINNERS = '/tmp/la-toile-winners.json';
+
+const ROLLS = parseInt(process.env.ROLLS || '7', 10);
+const SEAM_MAX = parseFloat(process.env.SEAM_MAX || '6');
+const COLOR_CAP = parseInt(process.env.COLOR_CAP || '9', 10); // up to 6 BH colors + linen ground + shading ≈ 7-8 buckets (Steve: ok up to 6 colors)
+const COLOR_PCT = process.env.COLOR_PCT || '0.03';
+// Concept gate — multi-color BH scenic toile (NOT abstract). Calibrated loose so
+// it passes genuine scenic toiles and rejects abstract/geometric blobs.
+const CONCEPT_Q = process.env.CONCEPT_Q ||
+  "Is this a scenic toile-style wallpaper showing engraved pictorial scenes of buildings, villas, trees or gardens (a Beverly Hills / Los Angeles garden scenic) — and NOT an abstract or geometric shape pattern?";
+// MANDATORY settlement gate. Steve 2026-06-01: STAY AWAY FROM PALM TREES — no
+// palm fronds means Part A1 is false, so Part A is never fully satisfied and the
+// multi-color line is bulletproof. Reject any tile showing palms OR any Part-B
+// element (bird/butterfly/banana/grape).
+// Steve 2026-06-01: palms ALLOWED (a palm alone is not a violation — needs Part B too).
+// This gate now blocks ONLY Part-B elements; that is the sole settlement guard, so keep it strict.
+const SETTLEMENT_Q = process.env.SETTLEMENT_Q ||
+  "Does this image contain any bird, butterfly, banana, banana plant or leaf, or bunch of grapes? Answer yes only if one of those is clearly visible.";
+
+// Beverly Hills style-guide colorways (see ~/Projects/dw-style-guides/index.html).
+// Each toile is MONOCHROME single-ink (settlement A3=false → bulletproof). The ink
+// is a legible deep BH accent; the ground is a BH neutral / sunshine-pastel tint.
+// Pastels & pinks appear as grounds + the dusty-rose/terracotta inks.
+// Beverly Hills style-guide COLORWAYS (see ~/Projects/dw-style-guides/index.html) —
+// soft MULTI-COLOR scenic toile (Steve 2026-06-01), each on a REAL natural-LINEN
+// ground (weave + slubs show through — wallco standing rule). 3-4 aged BH pigments
+// per colorway incl. the sunshine pastels/pinks. Multi-color satisfies settlement
+// Part A, so a MANDATORY per-tile Part-B vision gate rejects any bird/butterfly/
+// banana/grape (see SETTLEMENT_Q / evalRoll).
+// Beverly Hills style-guide COLORWAYS — up to 6 BH colors each (Steve 2026-06-01:
+// "use the beverly hills style guide colors!! ok to use up to 6 colors"). Exact
+// hexes from ~/Projects/dw-style-guides/index.html, each on a real linen ground.
+const INKS = [
+  { name: 'garden',        palette: 'hedge green #3E4A38, olive leaf #8C9472, sunset terracotta #B0512F, dusty rose #9F5664 and brass #B08D3F', ground: '#EFE7D6', ground_tone: 'natural flax linen' },
+  { name: 'jacaranda spring', palette: 'jacaranda violet #7C6FA6, hedge green #3E4A38, olive #8C9472, blush stucco #E8B7AC and sand #C9B79C', ground: '#F1EADA', ground_tone: 'warm ivory linen' },
+  { name: 'bougainvillea', palette: 'bougainvillea magenta #C2417B, cypress #2C3A30, olive leaf #8C9472 and brass #B08D3F', ground: '#EFE7D6', ground_tone: 'natural flax linen' },
+  { name: 'pacific',       palette: 'pacific blue #4E7C93, sky #A9C6D4, sage #8C9472, sand #E4D6BF and charcoal #3A352E', ground: '#E7ECE2', ground_tone: 'pale flax linen' },
+  { name: 'sunset',        palette: 'sunset coral #E3795F, terracotta #B0512F, apricot #E8A06A, olive #8C9472 and brass #B08D3F', ground: '#F2E6D6', ground_tone: 'peach-dyed linen' },
+  { name: 'blush sunshine', palette: 'dusty rose #9F5664, blush #E8B7AC, apricot #E8A06A, soft sage #A9B6A0 and champagne gold #CBB179 (sunshine pastels & pinks)', ground: '#F2E1DD', ground_tone: 'blush-dyed linen' },
+  { name: 'brass & green', palette: 'brass #B08D3F, champagne gold #CBB179, hedge green #3E4A38, olive #8C9472 and noir #15140F', ground: '#EFE7D6', ground_tone: 'natural flax linen' },
+  { name: 'noir & rose',   palette: 'black-tie noir #15140F, charcoal #3A352E, dusty rose #9F5664, sage #8C9472 and brass #B08D3F', ground: '#F2EEE6', ground_tone: 'marble-tone linen' },
+];
+const SCENES = [
+  'the Hollywood Sign on a chaparral hillside with Griffith Observatory domes below',
+  'the Venice Beach boardwalk with the fishing pier, beach cruisers and rolling surf',
+  'the downtown Los Angeles skyline with City Hall tower and Art-Deco high-rises',
+  'a jacaranda-lined Sunset Boulevard with vintage streetlamps and stucco storefronts',
+  'Beverly Hills Spanish-revival bungalows with tiled fountains and clipped hedges',
+  'the Santa Monica Pier Ferris wheel, arcade and ocean waves',
+  'the Chinatown paifang gate with hanging lanterns and pagoda rooflines',
+  'Echo Park Lake with paddle boats, lotus blossoms and the downtown skyline beyond',
+  'mid-century Case Study glass houses cantilevered over the canyon hills',
+  'vintage 1960s convertibles cruising the curves of Mulholland Drive',
+  'the Watts Towers mosaic spires rising over small craftsman rooftops',
+  'the Los Angeles Aqueduct cascade against the San Gabriel mountains',
+  'Olvera Street market stalls with woven baskets, pottery and string-light arcades',
+  'Griffith Park trails winding through oak groves with a grazing mule deer',
+  'the Malibu coastline with longboard surfers, sea cliffs and a lighthouse',
+  'Art-Deco movie palaces with marquees, ticket booths and klieg-light beams',
+  'old-Los-Angeles citrus groves with orange crates, packing sheds and wagons',
+  'Union Station archways with a vintage streamliner train and a magnolia courtyard',
+  'a golden-age studio backlot with sound stages, klieg lights and camera cranes',
+  'Pasadena Rose Parade floats with marching bands and ranunculus garlands',
+  'the Angels Flight funicular railway climbing Bunker Hill brownstones',
+  'the La Brea Tar Pits with woolly mammoths and sabertooth silhouettes',
+  'the Bradbury Building wrought-iron atrium stairs and birdcage elevators',
+  'a Mulholland overlook at dusk with a lone coyote and city lights below',
+  'Silver Lake reservoir with joggers, eucalyptus trees and modernist homes',
+  'the Original Farmers Market clock tower with the Grove trolley and produce carts',
+  'Topanga Canyon cabins tucked among coast live oaks and boulders',
+  'the Googie LAX Theme Building arches with finned cars and jet contrails',
+  'Dodger Stadium grandstands ringed by eucalyptus and the Elysian hills',
+  'jacaranda-lined residential streets in full bloom with craftsman porches',
+];
+const NEGATIVE =
+  'photoreal, photograph, photo, watercolour wash, oil painting, painterly, 3d render, abstract shapes, cut-paper collage, geometric blobs, ' +
+  'panel border, postcard panels, framed vignette, white panel margins, rectangular frames, picture frame, cartouche, gilded frame, grid of framed scenes, ' +
+  'visible seam, edge mismatch, abrupt cutoff at tile borders, horizontal band, border band, sparse, sparse composition, empty background, large empty ground, single centered hero motif, one big central scene, isolated motif on blank background, ' +
+  'ghost layer, ghosted faded copies, semi-transparent layer, ' +
+  'palm tree, palm trees, palm fronds, palms, date palm, fan palm, coconut palm, ' +
+  'birds, bird, butterfly, butterflies, banana, bananas, banana leaf, banana plant, grapes, grape bunch, monstera, philodendron, ' +
+  'room mockup, perspective, furniture, watermark, signature, text, logo, neon, fluorescent, day-glo, rainbow, primary saturated colors.';
+
+function composeToilePrompt(scene, ink) {
+  const g = ink.ground || '#EFE7D6', gt = ink.ground_tone || 'natural flax linen';
+  return (
+    `A lush Los Angeles & Beverly Hills garden Toile de Jouy with Italian cypress, coast live oak, olive, jacaranda, magnolia and eucalyptus. ` +
+    `Scene: ${scene}. ` +
+    `A TRUE engraved scenic toile in the heritage manner of Zuber, Pierre Frey and Brunschwig & Fils — REPRESENTATIONAL pictorial vignettes ` +
+    `(recognizable buildings, villas, formal gardens, cypress and olive trees, figures, carriages, fountains) in fine copperplate line work with hatching and stippling. ` +
+    `Beverly Hills colorway — use ONLY these exact Beverly Hills style-guide colors: ${ink.palette}. A rich MULTI-COLOR scenic toile (up to 6 of these colors) in aged, sun-bleached pigments — never neon, never saturated, no colors outside this palette. ` +
+    `DENSE ALL-OVER TOILE — the scenic vignettes are SMALL and REPEAT in stacked rows that completely fill the tile from top edge to bottom edge: little villas, trees, figures and fountains packed close together covering the WHOLE sheet like an antique toile document. ` +
+    `NO open sky, NO empty horizon band, NO large blank ground area, NO single big landscape — the upper half is just as full of small repeating motifs as the lower half. Motifs nearly touch and continue uninterrupted across all four edges, tiling infinitely. ` +
+    `NO panel borders, NO framed rectangles, NO postcard panels, NO white margins between scenes — one continuous toile field. ` +
+    `Printed on a REAL ${gt} ground (${g}); the natural linen weave and slubs show through. ` +
+    `NOT abstract, NOT geometric, NOT cut-paper — every motif is a literal engraved scene like an antique toile document. Museum-grade archival designer scenic toile. ` +
+    `Trees include Italian cypress, coast live oak, jacaranda, magnolia, olive and eucalyptus (NO palm trees). Villas, fountains and gardens fill the scene.`
+  );
+  // NB: palm/bird/banana/text bans go to the NEGATIVE conditioning via
+  // WALLCO_EXTRA_NEGATIVE (set when spawning generate_designs.js) — NOT appended
+  // here, because naming them in the positive prompt makes SDXL draw them.
+}
+// Routed to the negative CLIP node (not the positive prompt).
+const EXTRA_NEGATIVE = 'bird, birds, butterfly, butterflies, banana, banana leaf, banana plant, grapes, grape bunch, monstera, text, lettering, words, caption, label, signage, gibberish text, panel border, postcard panel, framed vignette';
+
+const DENSITY_MAX = parseFloat(process.env.DENSITY_MAX || '0.52'); // reject sparse: top colour must cover ≤52% (dense all-over toile, not empty-sky landscape)
+const SEAM_PASS = parseFloat(process.env.SEAM_PASS || '8');       // accept roll directly if seam ≤ this
+const SEAM_HEAL_MAX = parseFloat(process.env.SEAM_HEAL_MAX || '11'); // accept HEALED roll if seam ≤ this
+function sql(q) { return execSync(`psql -d dw_unified -tAc ${JSON.stringify(q)}`, { encoding: 'utf8' }).trim(); }
+function seamOf(id) {
+  try {
+    const s = execSync(`${PY} -c "import json,subprocess;o=subprocess.run(['${PY}','scripts/seam-defect-boxes.py','--id','${id}'],capture_output=True,text=True);j=json.loads(o.stdout);print(round(j['scores']['overall_max'],2))"`, { cwd: ROOT, encoding: 'utf8' }).trim();
+    const v = parseFloat(s); return Number.isFinite(v) ? v : null;
+  } catch { return null; }
+}
+function colorsOf(lp) {
+  try { return parseInt(execSync(`${PY} -c "import numpy as np;from PIL import Image;im=np.asarray(Image.open('${lp}').convert('RGB'));b=(im>>4).reshape(-1,3);_,c=np.unique(b,axis=0,return_counts=True);print(int((c>=c.sum()*${COLOR_PCT}).sum()))"`, { cwd: ROOT, encoding: 'utf8' }).trim(), 10); }
+  catch { return null; }
+}
+// Largest single colour's fraction of the tile — a proxy for emptiness. A sparse
+// horizon/sky landscape has one ground colour dominating; a dense all-over toile
+// spreads the field. Reject when the top colour exceeds DENSITY_MAX.
+function bgFracOf(lp) {
+  try { return parseFloat(execSync(`${PY} -c "import numpy as np;from PIL import Image;im=np.asarray(Image.open('${lp}').convert('RGB'));b=(im>>3).reshape(-1,3);_,c=np.unique(b,axis=0,return_counts=True);print(round(float(c.max())/float(c.sum()),3))"`, { cwd: ROOT, encoding: 'utf8' }).trim()); }
+  catch { return null; }
+}
+function visionAsk(lp, q) {
+  try { return execSync(`${PY} scripts/vision-contains.py ${JSON.stringify(lp)} ${JSON.stringify(q)}`, { cwd: ROOT, encoding: 'utf8' }).trim(); }
+  catch { return 'err'; }
+}
+// Evaluate a fresh roll: BH multi-color scenic toile + SETTLEMENT (no Part-B) +
+// seam. Returns aesthetic-ok (color+concept+settlement) and seam for heal-tracking.
+function evalRoll(id) {
+  const lp = sql(`SELECT local_path FROM all_designs WHERE id=${id}`);
+  if (!lp) return { aesthetic: false, om: null, why: 'no local_path' };
+  const major = colorsOf(lp);
+  if (major === null || major > COLOR_CAP) return { aesthetic: false, om: null, why: `colors ${major}` };
+  const bg = bgFracOf(lp);   // density gate — reject empty-sky/sparse landscapes
+  if (bg !== null && bg > DENSITY_MAX) return { aesthetic: false, om: null, why: `too sparse (bg ${bg})` };
+  const concept = visionAsk(lp, CONCEPT_Q);
+  if (concept !== 'yes') return { aesthetic: false, om: null, why: `concept=${concept}` };
+  // MANDATORY settlement Part-B gate: any bird/butterfly/banana/grape => violation => reject.
+  const partB = visionAsk(lp, SETTLEMENT_Q);
+  if (partB === 'yes') return { aesthetic: false, om: null, why: 'SETTLEMENT: part-B element present' };
+  const om = seamOf(id);
+  return { aesthetic: true, om, why: `colors ${major} concept yes settlement OK seam ${om}` };
+}
+// Heal the seam boxes of a concept-clean roll → new healed id + post-heal seam.
+function healSeam(id) {
+  try {
+    const out = execSync(`${PY} scripts/heal-seam-region.py --id ${id}`, { cwd: ROOT, encoding: 'utf8' }).trim();
+    const j = JSON.parse(out);
+    return { id: j.new_id, om: j.scan_after && j.scan_after.scores ? +(j.scan_after.scores.overall_max.toFixed(2)) : null };
+  } catch { return { id: null, om: null }; }
+}
+
+let winners = {};
+try { winners = JSON.parse(fs.readFileSync(WINNERS, 'utf8')); } catch {}
+
+function remove(id) { try { sql(`UPDATE all_designs SET user_removed=TRUE WHERE id=${id}`); } catch {} }
+
+const LIMIT = parseInt(process.env.LIMIT || String(SCENES.length), 10);
+console.log(`LA Toile (BH/linen) — scenes 1..${Math.min(LIMIT, SCENES.length)} · ROLLS≤${ROLLS}/scene · seam accept≤${SEAM_PASS}, heal≤${SEAM_HEAL_MAX} · colors≤${COLOR_CAP}`);
+for (let i = 0; i < Math.min(SCENES.length, LIMIT); i++) {
+  const slug = 'la-toile-' + String(i + 1).padStart(2, '0');
+  if (winners[slug]) { console.log(`[${i + 1}/30] ${slug} — already won #${winners[slug]}, skip`); continue; }
+  const ink = INKS[i % INKS.length];
+  const prompt = composeToilePrompt(SCENES[i], ink);
+  let won = null;
+  let best = { id: null, om: Infinity };   // lowest-seam concept-clean roll, kept for healing
+  for (let r = 1; r <= ROLLS && !won; r++) {
+    const t0 = Date.now();
+    const g = spawnSync('node', [path.join(__dirname, 'generate_designs.js'),
+      '--n', '1', '--kind', 'seamless_tile', '--category', 'la-toile', '--prompts', prompt],
+      { cwd: ROOT, encoding: 'utf8', timeout: 360000,
+        env: { ...process.env, WALLCO_SEAMLESS_GATE: '0', WALLCO_FRAME_GATE: '0', WALLCO_GHOST_GATE: '0',
+          WALLCO_NO_PALETTE_SUFFIX: '1', WALLCO_EXTRA_NEGATIVE: EXTRA_NEGATIVE } });
+    const m = (g.stdout || '').match(/#(\d+)\s*·/);
+    if (!m) { console.log(`  ${slug} roll ${r}: GEN FAIL`); continue; }
+    const id = parseInt(m[1], 10);
+    const res = evalRoll(id);
+    const dt = ((Date.now() - t0) / 1000).toFixed(0);
+    console.log(`  ${slug} roll ${r}: #${id} ${res.aesthetic ? 'aesthetic-ok' : 'reject'} — ${res.why} (${dt}s)`);
+    if (res.aesthetic && res.om !== null && res.om <= SEAM_PASS) { won = id; if (best.id) remove(best.id); break; }
+    if (res.aesthetic && res.om !== null && res.om < best.om) { if (best.id) remove(best.id); best = { id, om: res.om }; }
+    else remove(id);
+  }
+  // No directly-clean roll → heal the best concept-clean candidate's seam
+  if (!won && best.id) {
+    const h = healSeam(best.id);
+    console.log(`  ${slug} HEAL #${best.id} (seam ${best.om}) → #${h.id} seam ${h.om}`);
+    if (h.id && h.om !== null && h.om <= SEAM_HEAL_MAX) { won = h.id; remove(best.id); }
+    else { won = best.id; if (h.id) remove(h.id); console.log(`  ${slug} heal insufficient — keeping best #${best.id} (seam ${best.om})`); }
+  }
+  if (won) { winners[slug] = won; fs.writeFileSync(WINNERS, JSON.stringify(winners, null, 2)); console.log(`[${i + 1}/30] ${slug} WON #${won}`); }
+  else console.log(`[${i + 1}/30] ${slug} — NO usable roll in ${ROLLS} (left for manual)`);
+}
+console.log('\nWinners: ' + JSON.stringify(winners));
+console.log(`${Object.keys(winners).length}/30 scenes have a clean toile.`);
diff --git a/scripts/generate_designs.js b/scripts/generate_designs.js
index 74fa54c..955df34 100644
--- a/scripts/generate_designs.js
+++ b/scripts/generate_designs.js
@@ -749,8 +749,14 @@ async function main() {
   // batch runners, settlement regen), still append the tone-on-tone suffix
   // so the aesthetic gate applies at the entrypoint level — no caller can
   // bypass it without explicitly stripping the suffix.
+  // WALLCO_NO_PALETTE_SUFFIX=1 lets a caller that supplies its OWN complete,
+  // self-contained palette (e.g. la-toile's Beverly Hills colorway) opt out of the
+  // generic tone-on-tone learned-palette suffix, which otherwise CONFLICTS with it
+  // ("use ONLY these BH colors" vs "use ONLY this ivory/cream learned palette") and
+  // steamrolls the design into pale monochrome.
+  const skipSuffix = process.env.WALLCO_NO_PALETTE_SUFFIX === '1';
   const prompts = opt.prompts
-    ? opt.prompts.map(p => /tone-on-tone/i.test(p) ? p : `${p}. ${TONE_ON_TONE_SUFFIX}`)
+    ? opt.prompts.map(p => (skipSuffix || /tone-on-tone/i.test(p)) ? p : `${p}. ${TONE_ON_TONE_SUFFIX}`)
     : pickPrompts(opt.category, opt.n);
   const created = [];
   for (let i = 0; i < opt.n; i++) {

← aa2d6d6 PDP theme switcher: admin-only — hide .switcher bar from sho  ·  back to Wallco Ai  ·  la-toile: density gate 0.52->0.58 (stop over-rejecting class 0e62d01 →