← 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
A scripts/gemini-39309-booze-joints.jsM scripts/generate-cactus.js
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 →