diff --git a/tools/mj-queue.mjs b/tools/mj-queue.mjs index 0bcf735..a7d34c4 100644 --- a/tools/mj-queue.mjs +++ b/tools/mj-queue.mjs @@ -134,12 +134,17 @@ const armourWords = (n, era = "antique") => bandOf(Number(n) || 0, [ function negativesFor(spec, era, isPerson) { if (isPerson) return []; const out = []; - if (era !== "future" && era !== "anomalous") { - out.push("worn armour", "plate armour", "armour plating", "machinery", "robot", "mecha"); - } + /* SHORT, deliberately. The base style already carries two negatives, and this project + has a recorded failure where prompts with ten or more --no terms never returned a + grid at all while the same prompt with four came back in two minutes — a stall that + is indistinguishable from a moderation refusal and so gets misdiagnosed every time. + The first version of this function pushed six armour terms and three weapon terms, + which took the gryphon to eight and into that band. Three terms and one; steer with + the prompt, which is what the armourWords rewrite above is for. */ + if (era !== "future" && era !== "anomalous") out.push("plate armour", "machinery", "robot"); // "no weapon — it uses its own limbs" is already in the prompt, positively. An animal // is still handed a spear often enough to be worth stating twice. - if (!(spec.weapons ?? []).length) out.push("weapons", "swords", "armour straps"); + if (!(spec.weapons ?? []).length) out.push("weapons"); return out; } @@ -311,6 +316,21 @@ export function promptFor(spec, kind = "npc") { const base = expand(clauses.join(", ").replace(/\s+/g, " ").trim()); const extra = negativesFor(spec, era, isPerson); const prompt = extra.length ? `${base}, ${extra.join(", ")}` : base; + + /* A prompt with too many --no terms does not fail, which is the problem: it never + returns a grid at all, and a job that never arrives looks exactly like a moderation + refusal — those are ephemeral and never appear in the channel either. The recorded + case cost twenty minutes of wondering whether the account was out of fast hours. + Refuse to build one, and say which terms, rather than letting it be diagnosed again + from scratch. Six is under the observed cliff with room to spare. */ + const NEGATIVE_CAP = 6; + const negatives = (prompt.split("--no ")[1] ?? "").split(",").map(x => x.trim()).filter(Boolean); + if (negatives.length > NEGATIVE_CAP) { + throw new Error(`${spec.key}: ${negatives.length} --no terms (cap ${NEGATIVE_CAP}) — ` + + `a long negative list stalls the job silently rather than failing it. ` + + `Steer in the prompt instead. Terms: ${negatives.join(", ")}`); + } + const warnings = BANNED.filter(b => prompt.toLowerCase().includes(b)); return { prompt, token, warnings }; }