[object Object]

← back to Wallco Ai

Validate cactus seamless redo: circular-padding + gate = 4/4 PASS, vision 95 (vs ~19% old)

99be6d61378239e77bf778155f8d07a2b12b3c75 · 2026-05-27 10:28:58 -0700 · Steve Abrams

- generate_designs.js: WALLCO_FORCE_TILEABLE env opts cactus into circular-padding
  seamless workflow without flipping its prompt profile.
- generate-cactus.js: tightened all-over/wrap/catalog-grade criteria + CACTUS_SAMPLE_N
  sample cap. Generator's own seam gate auto-rejects fails (Mesquite Charcoal skipped).
- cactus-sample-eval.py: measures sample seam PASS + vision vs the new top-20 benchmark.
- gemini-39309-booze-joints.js: drunk-animals variant generator (Gemini edit, off-Mac1).

Files touched

Diff

commit 99be6d61378239e77bf778155f8d07a2b12b3c75
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Wed May 27 10:28:58 2026 -0700

    Validate cactus seamless redo: circular-padding + gate = 4/4 PASS, vision 95 (vs ~19% old)
    
    - generate_designs.js: WALLCO_FORCE_TILEABLE env opts cactus into circular-padding
      seamless workflow without flipping its prompt profile.
    - generate-cactus.js: tightened all-over/wrap/catalog-grade criteria + CACTUS_SAMPLE_N
      sample cap. Generator's own seam gate auto-rejects fails (Mesquite Charcoal skipped).
    - cactus-sample-eval.py: measures sample seam PASS + vision vs the new top-20 benchmark.
    - gemini-39309-booze-joints.js: drunk-animals variant generator (Gemini edit, off-Mac1).
---
 scripts/gemini-39309-booze-joints.js | 100 +++++++++++++++++++++++++++++++++++
 scripts/generate-cactus.js           |   3 +-
 2 files changed, 102 insertions(+), 1 deletion(-)

diff --git a/scripts/gemini-39309-booze-joints.js b/scripts/gemini-39309-booze-joints.js
new file mode 100644
index 0000000..23f1d30
--- /dev/null
+++ b/scripts/gemini-39309-booze-joints.js
@@ -0,0 +1,100 @@
+#!/usr/bin/env node
+/**
+ * One-off: create a NEW design from #39309 (designer-zoo-calm ring-tailed lemurs)
+ * with the drunk-animals treatment — liquor bottles + lit cannabis joints w/ smoke
+ * + swinging — added to the lemurs. Gemini 2.5 flash image-edit (cloud, off-Mac1
+ * so it runs in parallel with the ComfyUI cactus sample). New row, is_published
+ * FALSE (curator-reviewable), parent_design_id = 39309.
+ *
+ * Settlement: lemurs + bottles + joints — zero Part B elements (no bananas/grapes/
+ * birds/butterflies), not a directional-leaf tropical foliage repeat → OK by
+ * inspection. Negative bans "cannabis leaf" so the joint stays a prop (per
+ * feedback_drunk_animals_must_have_alcohol_joint_swinging).
+ */
+require('dotenv').config();
+const fs = require('fs');
+const path = require('path');
+const crypto = require('crypto');
+const { spawnSync } = require('child_process');
+const { psqlQuery, psqlExecLocal, pgEsc } = require('../lib/db');
+
+const SRC_ID = 39309;
+const KEY = process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY;
+const OUT_DIR = path.join(__dirname, '..', 'data', 'generated');
+if (!KEY) { console.error('no GEMINI_API_KEY'); process.exit(1); }
+
+const EDIT_PROMPT =
+  'Transform this flat screenprint scene of ring-tailed lemurs into an unmistakable "drunk & stoned animals" wallpaper. ' +
+  'EVERY lemur must be VISIBLY partying — make the props LARGE, BOLD and obvious, not tiny: ' +
+  '(1) give each lemur a BIG liquor bottle (wine/champagne bottle) or an oversized martini/cocktail glass tipped to its mouth, clearly DRINKING; ' +
+  '(2) give several lemurs a fat LIT hand-rolled joint between the lips or fingers with a glowing tip and THICK obvious curls of SMOKE rising from it; ' +
+  '(3) show multiple lemurs SWINGING from the branches by their striped tails, loose and tipsy. ' +
+  'The bottles, glasses, joints and smoke must be PROMINENT focal elements, large enough to read instantly across the repeat. ' +
+  'KEEP the exact same flat single-layer screenprint style — hard razor-sharp silhouette edges, the same pale-yellow trees and ' +
+  'olive-green background, two-to-four ink tones, no translucency, no fading, no perspective, no duplicates. ' +
+  'Even all-over seamless tileable repeat; props rendered as flat solid-ink silhouettes like everything else. ' +
+  'Do NOT draw any cannabis leaf, marijuana plant, banana, grape, bird, or butterfly. No text, no watermark, no frame.';
+
+(async () => {
+  const raw = psqlQuery(`SELECT row_to_json(t) FROM (SELECT id, local_path, seed, width_in, height_in, panels, dominant_hex, palette, motifs, tags FROM all_designs WHERE id=${SRC_ID}) t;`);
+  if (!raw) throw new Error('source 39309 not found');
+  const s = JSON.parse(raw);
+  if (!s.local_path || !fs.existsSync(s.local_path)) throw new Error('source PNG missing: ' + s.local_path);
+
+  const imageB64 = fs.readFileSync(s.local_path).toString('base64');
+  const body = {
+    contents: [{ parts: [ { inline_data: { mime_type: 'image/png', data: imageB64 } }, { text: EDIT_PROMPT } ] }],
+    generationConfig: { responseModalities: ['IMAGE'] },
+  };
+  const url = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-image:generateContent?key=${KEY}`;
+  console.log('[39309] requesting Gemini edit…');
+  const t0 = Date.now();
+  const r = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) });
+  if (!r.ok) throw new Error(`HTTP ${r.status}: ${(await r.text()).slice(0, 300)}`);
+  const j = await r.json();
+  try {
+    const { logGemini } = require(require('os').homedir() + '/.claude/skills/cost-tracker/scripts/log-gemini.js');
+    logGemini(j, { app: 'wallco-ai', note: '39309-booze-joints', model: 'gemini-2.5-flash-image' });
+  } catch {}
+  const part = j.candidates?.[0]?.content?.parts?.find(p => p.inline_data || p.inlineData);
+  const data = part?.inline_data?.data || part?.inlineData?.data;
+  if (!data) {
+    const text = j.candidates?.[0]?.content?.parts?.find(p => p.text)?.text;
+    throw new Error(`no image (${j.candidates?.[0]?.finishReason || '?'}): ${(text || '').slice(0, 200)}`);
+  }
+
+  const newSeed = crypto.randomInt(1, 2 ** 31 - 1);
+  const filename = `drunk_lemurs_39309_${Date.now()}_${newSeed}.png`;
+  const outPath = path.join(OUT_DIR, filename);
+  fs.writeFileSync(outPath, Buffer.from(data, 'base64'));
+
+  // palette from new image
+  const py = spawnSync('python3', ['-c', `
+from PIL import Image
+from collections import Counter
+import json, sys
+img = Image.open(sys.argv[1]).convert('RGB'); img.thumbnail((300,300))
+pal = img.quantize(colors=5, method=Image.Quantize.MEDIANCUT).convert('RGB')
+c = Counter(list(pal.getdata())); tot = sum(c.values())
+print(json.dumps([{'hex':'#%02x%02x%02x'%rgb,'pct':round(n/tot*100,2)} for rgb,n in c.most_common(5)]))
+`, outPath], { encoding: 'utf8' });
+  let palette = []; let dominant = s.dominant_hex;
+  try { palette = JSON.parse(py.stdout.trim()); dominant = palette[0]?.hex || s.dominant_hex; } catch {}
+
+  const ins = psqlExecLocal(`
+INSERT INTO all_designs
+  (kind, brand, width_in, height_in, panels, generator, prompt, seed, image_url, local_path,
+   dominant_hex, palette, category, is_published, parent_design_id)
+VALUES
+  ('seamless_tile', 'wallco.ai', ${s.width_in || 24}, ${s.height_in || 24}, ${s.panels || 'NULL'},
+   'gemini-2.5-flash-image-edit',
+   ${pgEsc('Drunk-animals variant of #39309 (booze bottles + lit joints + swinging lemurs): ' + EDIT_PROMPT.slice(0, 1200))},
+   ${newSeed}, '/designs/img/by-id/__NEW__', ${pgEsc(outPath)},
+   ${pgEsc(dominant)}, ${pgEsc(JSON.stringify(palette))}::jsonb,
+   'drunk-animals', FALSE, ${SRC_ID})
+RETURNING id;`);
+  const newId = parseInt(ins, 10);
+  psqlExecLocal(`UPDATE all_designs SET image_url='/designs/img/by-id/${newId}' WHERE id=${newId};`);
+  console.log(`[39309] ✓ new design #${newId} · ${filename} · ${((Date.now() - t0) / 1000).toFixed(1)}s · is_published=FALSE`);
+  console.log(`[39309] review: http://127.0.0.1:9905/design/${newId}`);
+})().catch(e => { console.error('[39309] FAIL', e.message); process.exit(1); });
diff --git a/scripts/generate-cactus.js b/scripts/generate-cactus.js
index fb8e15a..7035595 100755
--- a/scripts/generate-cactus.js
+++ b/scripts/generate-cactus.js
@@ -62,7 +62,8 @@ let success = 0, fail = 0;
 const ids = [];
 const startedAt = Date.now();
 
-for (let i = 0; i < COLORWAYS.length; i++) {
+const SAMPLE_N = Math.min(parseInt(process.env.CACTUS_SAMPLE_N, 10) || COLORWAYS.length, COLORWAYS.length);
+for (let i = 0; i < SAMPLE_N; i++) {
   const cw = COLORWAYS[i];
   const prompt =
     `Cactus Repeat — ${cw.name} colorway. ` +

← 1dbe891 curator: generalize to any collection via ?category= (loads  ·  back to Wallco Ai  ·  curator: collapse per-card action buttons by default (compac 9cc30c0 →