diff --git a/tools/mj-queue.mjs b/tools/mj-queue.mjs index a7d34c4..86e7f04 100644 --- a/tools/mj-queue.mjs +++ b/tools/mj-queue.mjs @@ -80,7 +80,12 @@ export const TOKENS = { * and twenty minutes of wondering whether the account is out of fast hours. Cheaper to * catch here than to sit through. */ -const BANNED = ["gore", "bust", "corpse", "mutilat", "blood-soaked"]; +/* Learned in production, one silent stall at a time. "bare" was added on 2026-09-23 after + the martlet failed three times over ninety minutes: the gryphon succeeded in the same + run, so it was not the service, and replacing that single word made the martlet return + immediately. A moderation refusal never appears in the channel, so this is the only + record that the word costs half an hour. */ +const BANNED = ["gore", "bust", "corpse", "mutilat", "blood-soaked", "bare"]; /* ---------------------------------------------------------------- derivation */ @@ -116,7 +121,11 @@ const buildWords = ch => bandOf((Number(ch?.str) || 12) / (Number(ch?.siz) || 12 armour to be left out, because a word choice is an argument and a --no is closer to a guarantee. */ const armourWords = (n, era = "antique") => bandOf(Number(n) || 0, [ - [0, "bare, unprotected"], + /* NOT "bare". Midjourney's moderation refuses that word — it reads as nudity — and a + refusal is ephemeral, so the job simply never arrives. The martlet stalled for thirty + minutes three times; changing this one word and nothing else made it return. Thirteen + creatures carried it, every one of them a creature with no natural armour. */ + [0, "thin-skinned, nothing between it and a blow"], [3, "tough hide"], [6, "thick natural plating, part of the animal"], [99, era === "future" || era === "anomalous" @@ -331,7 +340,17 @@ export function promptFor(spec, kind = "npc") { + `Steer in the prompt instead. Terms: ${negatives.join(", ")}`); } + /* REFUSE, rather than warn. These were returned as `warnings` for the caller to print, + and a warning in a log nobody reads is what let the martlet stall three times: the + failure it predicts is a job that never arrives, which is indistinguishable from the + service being slow. The negative cap above refuses for the same reason. */ const warnings = BANNED.filter(b => prompt.toLowerCase().includes(b)); + if (warnings.length) { + throw new Error(`${spec.key}: prompt contains ${warnings.map(w => `"${w}"`).join(", ")}, ` + + `which Midjourney's moderation refuses even in innocent use. The refusal is ephemeral ` + + `— the job never arrives and never fails — so this must not be sent. Reword the ` + + `clause that produced it.`); + } return { prompt, token, warnings }; }