← back to Wallco Ai
feat(generators): wire bad-aesthetic-patterns.jsonl into the tick loop
913798d218ea2f31b7f65a7eef2440d411e07cf7 · 2026-05-24 22:50:36 -0700 · Steve Abrams
Closes Steve's 'I dont want to waste time on bad patterns / learn that those
are bad' loop. The bulk-delete UI + learn-and-purge-flagged.js were already
writing prompt + motifs + category to data/bad-aesthetic-patterns.jsonl, but
no generator was reading it — so the 'learn' half was a no-op.
lib/bad-aesthetic.js — small new module:
- loadRows() / getCategoryFailureCount(cat)
- isCategoryCooldown(cat, threshold=50) — skip the whole tick if too many
prior failures in this category
- getAvoidanceAddendum(cat) — assembles a category-targeted 'AVOID prior
failure modes' suffix from the actual reasons logged in past rejections
scripts/drunk_animals_tick.js — pre-flight: count drunk-animals failures.
≥ DRUNK_ANIMALS_COOLDOWN (default 50) → skip tick. ≥ 1 → append learned
avoidance addendum to the freshPrompt.
scripts/generator_tick.js — same pattern, keyed off recipe.style_tags[0].
GENERATOR_COOLDOWN (default 50) env tunes the bar.
Local data file currently has 0 entries; prod has 4 cactus entries. Both
ticks no-op on missing file, so the wiring is safe to deploy today.
Files touched
A lib/bad-aesthetic.jsM scripts/drunk_animals_tick.jsM scripts/generator_tick.js
Diff
commit 913798d218ea2f31b7f65a7eef2440d411e07cf7
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Sun May 24 22:50:36 2026 -0700
feat(generators): wire bad-aesthetic-patterns.jsonl into the tick loop
Closes Steve's 'I dont want to waste time on bad patterns / learn that those
are bad' loop. The bulk-delete UI + learn-and-purge-flagged.js were already
writing prompt + motifs + category to data/bad-aesthetic-patterns.jsonl, but
no generator was reading it — so the 'learn' half was a no-op.
lib/bad-aesthetic.js — small new module:
- loadRows() / getCategoryFailureCount(cat)
- isCategoryCooldown(cat, threshold=50) — skip the whole tick if too many
prior failures in this category
- getAvoidanceAddendum(cat) — assembles a category-targeted 'AVOID prior
failure modes' suffix from the actual reasons logged in past rejections
scripts/drunk_animals_tick.js — pre-flight: count drunk-animals failures.
≥ DRUNK_ANIMALS_COOLDOWN (default 50) → skip tick. ≥ 1 → append learned
avoidance addendum to the freshPrompt.
scripts/generator_tick.js — same pattern, keyed off recipe.style_tags[0].
GENERATOR_COOLDOWN (default 50) env tunes the bar.
Local data file currently has 0 entries; prod has 4 cactus entries. Both
ticks no-op on missing file, so the wiring is safe to deploy today.
---
lib/bad-aesthetic.js | 72 +++++++++++++++++++++++++++++++++++++++++++
scripts/drunk_animals_tick.js | 22 ++++++++++++-
scripts/generator_tick.js | 24 ++++++++++++++-
3 files changed, 116 insertions(+), 2 deletions(-)
diff --git a/lib/bad-aesthetic.js b/lib/bad-aesthetic.js
new file mode 100644
index 0000000..702b1dd
--- /dev/null
+++ b/lib/bad-aesthetic.js
@@ -0,0 +1,72 @@
+// lib/bad-aesthetic.js — closes the marquee-Delete-and-learn loop.
+//
+// Reads data/bad-aesthetic-patterns.jsonl (written by both the Ghost Review UI's
+// bulk-delete endpoint and scripts/learn-and-purge-flagged.js) and exposes a
+// tiny API the generators can call before spending a Gemini/SDXL call on a
+// category that keeps producing defects.
+//
+// Schema per line:
+// { id, category, prompt, motifs, reason, ts }
+//
+// Behavior:
+// getCategoryFailureCount(category) → integer
+// isCategoryCooldown(category, n=50) → boolean ("skip this tick entirely")
+// getAvoidanceAddendum(category) → string to append to a fresh prompt
+// when the category has ≥1 prior failure
+//
+// File is reloaded on every call (small file, append-only — millisecond cost).
+// If the file is missing the lib returns the no-failures defaults.
+
+'use strict';
+
+const fs = require('fs');
+const path = require('path');
+
+const FILE = path.join(__dirname, '..', 'data', 'bad-aesthetic-patterns.jsonl');
+
+function loadRows() {
+ try {
+ const raw = fs.readFileSync(FILE, 'utf8');
+ const rows = [];
+ for (const line of raw.split('\n')) {
+ if (!line.trim()) continue;
+ try { rows.push(JSON.parse(line)); } catch {}
+ }
+ return rows;
+ } catch { return []; }
+}
+
+function getCategoryFailureCount(category) {
+ if (!category) return 0;
+ const want = String(category).toLowerCase();
+ return loadRows().filter(r => (r.category || '').toLowerCase() === want).length;
+}
+
+function isCategoryCooldown(category, threshold = 50) {
+ return getCategoryFailureCount(category) >= threshold;
+}
+
+// Build a category-targeted avoidance addendum from the reason strings actually
+// observed in past failures. Falls back to a generic anti-ghost addendum if the
+// category has prior failures but no parseable reasons.
+function getAvoidanceAddendum(category) {
+ const rows = loadRows().filter(r => (r.category || '').toLowerCase() === String(category || '').toLowerCase());
+ if (!rows.length) return '';
+ const reasons = [...new Set(rows.map(r => r.reason || '').filter(Boolean))].slice(0, 3);
+ const n = rows.length;
+ return (
+ `\n\n[LEARNED FROM PRIOR FAILURES — ${n} prior generation${n === 1 ? '' : 's'} in this ` +
+ `"${category}" category were rejected with these reasons: ${reasons.join(' · ')}. ` +
+ `Take EXTRA CARE to avoid: ghost-layer / faded duplicate motifs / atmospheric haze over ` +
+ `flat patterns / multi-opacity copies of the same motif / "halo" pale silhouettes echoing ` +
+ `sharp ones. Render as a SINGLE flat layer, hard razor-sharp silhouette edges, uniform ` +
+ `opacity across every motif, ground is one flat solid color edge-to-edge.]`
+ );
+}
+
+module.exports = {
+ loadRows,
+ getCategoryFailureCount,
+ isCategoryCooldown,
+ getAvoidanceAddendum,
+};
diff --git a/scripts/drunk_animals_tick.js b/scripts/drunk_animals_tick.js
index e578424..665c763 100644
--- a/scripts/drunk_animals_tick.js
+++ b/scripts/drunk_animals_tick.js
@@ -61,7 +61,27 @@ function freshPrompt() {
// Steve killed the 18:00→05:59 window on 2026-05-13.
const ROOT = path.join(__dirname, '..');
-const prompt = freshPrompt();
+const { isCategoryCooldown, getAvoidanceAddendum, getCategoryFailureCount } = require('../lib/bad-aesthetic');
+
+// ── Bad-aesthetic cooldown ───────────────────────────────────────────────
+// If the drunk-animals category has accumulated ≥ COOLDOWN_THRESHOLD bulk-
+// rejected designs in data/bad-aesthetic-patterns.jsonl, skip this tick to
+// stop burning Gemini calls on an aesthetic that keeps failing review. Steve
+// raises/lowers the bar by tuning DRUNK_ANIMALS_COOLDOWN env var.
+const COOLDOWN_THRESHOLD = parseInt(process.env.DRUNK_ANIMALS_COOLDOWN || '50', 10);
+const failureCount = getCategoryFailureCount('drunk-animals');
+if (isCategoryCooldown('drunk-animals', COOLDOWN_THRESHOLD)) {
+ console.log(`[tick ${new Date().toISOString()}] COOLDOWN — drunk-animals has ${failureCount} prior failures (threshold ${COOLDOWN_THRESHOLD}). Skipping.`);
+ process.exit(0);
+}
+
+let prompt = freshPrompt();
+// If there's any prior failure (even below cooldown threshold), append the
+// learned avoidance addendum so SDXL gets explicit "don't repeat the defect".
+if (failureCount > 0) {
+ prompt = prompt + getAvoidanceAddendum('drunk-animals');
+ console.log(`[tick ${new Date().toISOString()}] LEARNED-AVOID applied (${failureCount} prior failures).`);
+}
console.log(`[tick ${new Date().toISOString()}] ${prompt.slice(0, 100)}…`);
// ── Settlement pre-generation gate ───────────────────────────────────────
diff --git a/scripts/generator_tick.js b/scripts/generator_tick.js
index 16c411b..b773e50 100644
--- a/scripts/generator_tick.js
+++ b/scripts/generator_tick.js
@@ -112,8 +112,30 @@ function main() {
console.log(JSON.stringify({ event: 'skip', reason: 'no enabled recipe' }));
return 0;
}
+
+ // ── Bad-aesthetic cooldown ─────────────────────────────────────────────
+ // The category we'd ship to generate_designs.js below. If that category
+ // has accumulated ≥ COOLDOWN_THRESHOLD bulk-rejected designs in
+ // data/bad-aesthetic-patterns.jsonl (written by the Ghost Review UI's
+ // bulk-delete endpoint and learn-and-purge-flagged.js), skip the tick
+ // entirely. Stops Gemini/SDXL burn on an aesthetic that keeps failing.
+ const { isCategoryCooldown, getAvoidanceAddendum, getCategoryFailureCount } = require('../lib/bad-aesthetic');
+ const COOLDOWN_THRESHOLD = parseInt(process.env.GENERATOR_COOLDOWN || '50', 10);
+ const category = (recipe.style_tags && recipe.style_tags[0]) || 'mixed';
+ const failureCount = getCategoryFailureCount(category);
+ if (isCategoryCooldown(category, COOLDOWN_THRESHOLD)) {
+ console.log(JSON.stringify({
+ event: 'tick_skipped_cooldown', recipe_id: recipe.id,
+ recipe_name: recipe.name, category, failures: failureCount, threshold: COOLDOWN_THRESHOLD,
+ }));
+ return 0;
+ }
+
const seeds = recentPaletteSeeds(3);
- const prompt = buildPrompt(recipe, seeds);
+ let prompt = buildPrompt(recipe, seeds);
+ if (failureCount > 0) {
+ prompt = prompt + getAvoidanceAddendum(category);
+ }
// ── Settlement pre-generation gate ─────────────────────────────────────
// Hard rule: no pattern is generated until the gate returns OK. BLOCK and
← 1848657 fix(deploy): exclude ghost-scan + bad-aesthetic-patterns + g
·
back to Wallco Ai
·
ghost-detector: seam-frame lens — catches plates at the tile c85af21 →