From d0c700e2ab8730f3f5d77941f1ae080360774087 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 17 Oct 2026 00:13:34 +0000 Subject: [PATCH] ventures: refresh havre from havre-next --- ventures/havre/abilities.js | 18 ++++++++++++------ ventures/havre/skill.md | 10 +++++++--- ventures/havre/tests/abilities.test.mjs | 22 +++++++++++++--------- ventures/havre/tests/smoke.mjs | 13 ++++++++----- 4 files changed, 40 insertions(+), 23 deletions(-) diff --git a/ventures/havre/abilities.js b/ventures/havre/abilities.js index b168c09..0af9113 100644 --- a/ventures/havre/abilities.js +++ b/ventures/havre/abilities.js @@ -89,10 +89,11 @@ export const hearingPerMinute = (model) => (model && model.perSecond != null ? m const SAFE = "Keep it safe for work: no nudity, nothing sexual, no gore. Everyone shown is an adult. Never show a real, " + "identifiable person: no celebrities, no public figures, no one the conversation says is real; if one is asked for, show " + "the character alone. No words, logos or watermarks in the picture."; -// for an adult character's photos and clips (adultCheck): the same rules, less the one about work, and a hard one first. -// It says who is shown, not who isn't: the words for that are the ones filters look for (and Havre's own check, underage) -export const ADULT_ONLY = "Hard rule, 18+ only: everyone shown is an adult, 18 or older, with a grown adult face and body."; -const ADULT = ADULT_ONLY + " If the conversation asks for anyone younger, or for anything that makes someone look younger, " + +// for an adult character's photos and clips (adultCheck): the same rules, less the one about work, and a hard one first, +// as the shot writer (a text model) reads it: it can be told the number. It says who is shown, not who isn't: the words +// for that are the ones filters look for (and Havre's own check, underage) +export const EIGHTEEN = "Hard rule, 18+ only: everyone shown is an adult, 18 or older, with a grown adult face and body."; +const ADULT = EIGHTEEN + " If the conversation asks for anyone younger, or for anything that makes someone look younger, " + "leave it out and show only adults. Never show a real, identifiable person: no celebrities, no public figures, no one the " + "conversation says is real; if one is asked for, show the character alone. No gore. No words, logos or watermarks in the " + "picture."; @@ -164,8 +165,13 @@ export function cleanText(text, max = 900) { // the rules ride at the front of every picture and clip, where a long shot or a long look can't push them out. They // say what's wanted: an image model reads the words, not the "no" in front of them, and so does a content filter const KEEP = "Safe for work: everyone in it is an adult, fully clothed."; -// an adult character's: the 18+ rule is the very first thing the model reads -const KEEP_ADULT = ADULT_ONLY + " Never a real, identifiable person."; +// an adult character's: the 18+ rule is the very first thing the picture model reads, in words a content filter +// doesn't take for an age or for anyone it guards. The number, "body", "hard rule" and "identifiable person" stay +// with the shot writer (EIGHTEEN, ADULT), whose instructions never reach a picture filter: a picture prompt with safe +// mode off is read the hardest, and a from-scratch adult character's photo was turned down on every model, with the +// plainest shot and look, while the same words went out before every one of them. +export const ADULT_ONLY = "Everyone shown is a grown adult."; +const KEEP_ADULT = ADULT_ONLY; /** how their card says they look, on one line, without a full stop at the end ("" when it doesn't say) */ const lookWords = (card) => str(card && card.look && card.look.description, 4000).replace(/\s+/g, " ").trim().replace(/[.!?…]+$/, ""); diff --git a/ventures/havre/skill.md b/ventures/havre/skill.md index 3e1e370..fd6d140 100644 --- a/ventures/havre/skill.md +++ b/ventures/havre/skill.md @@ -164,9 +164,13 @@ it didn't come. The one exception is an `adult` character, once the person has said they're 18 or older: when every picture on their card was made with AI (never one that was uploaded, which could be of anyone), their photos and clips can be adult. -`A.adultCheck(card, { of18, made, shot, said })` decides; they go to Venice's uncensored models with safe mode off, -and every prompt opens with the 18+ rule (`A.ADULT_ONLY`). Never when the card, the shot or the chat mentions anyone -under 18 (`A.underage`): then it's safe for work, as usual. The pictures made for their card go without safe mode +`A.adultCheck(card, { of18, made, shot, said })` decides; they go to Venice's uncensored models with safe mode off. +The shot is written to the 18+ rule with the number in it (`A.EIGHTEEN`, in the shot writer's instructions, which +no picture filter reads), and every picture and clip prompt opens with the same rule in words a filter doesn't take +for an age or for anyone it guards (`A.ADULT_ONLY`, "Everyone shown is a grown adult."): a picture prompt sent with +safe mode off is read the hardest, and "18+", "body", "hard rule" or "identifiable person" in it got the plainest +photo turned down on every model. Never when the card, the shot or the chat mentions anyone under 18 +(`A.underage`): then it's safe for work, as usual. The pictures made for their card go without safe mode too: a portrait drawn from words (`A.portraitPrompt(card, limit, { adult: true })`, when `A.adultCheck(card, { of18, drawn: true })` says so), and one made from their pictures when every one of those was made with AI. diff --git a/ventures/havre/tests/abilities.test.mjs b/ventures/havre/tests/abilities.test.mjs index 8074b18..cf540d4 100644 --- a/ventures/havre/tests/abilities.test.mjs +++ b/ventures/havre/tests/abilities.test.mjs @@ -131,7 +131,7 @@ test("pictures made from their pictures keep their face and follow how their car const drawn = A.portraitPrompt(long); assert.ok(drawn.length <= 1500); assert.match(drawn, /… Safe for work: everyone in it is an adult, fully clothed\. Head and shoulders, soft light, plain background, no text\.$/); - assert.match(A.portraitPrompt(long, 1500, { adult: true }), /^Hard rule.*Portrait photo of Ines Corbel.*… Head and shoulders, soft light, plain background, no text\.$/); + assert.match(A.portraitPrompt(long, 1500, { adult: true }), /^Everyone shown is a grown adult\. Portrait photo of Ines Corbel.*… Head and shoulders, soft light, plain background, no text\.$/); assert.match(A.portraitPrompt({ ...bare, tagline: "Ferry pilot." }), /an original adult character: Ferry pilot\. Safe for work/); assert.match(A.portraitPrompt({ ...bare, tagline: "" }), /an original adult character: an original character\. Safe for work/); }); @@ -141,12 +141,15 @@ const mara = C.normalizeCard({ name: "Mara Vell", persona: "You are Mara Vell, a portrait: "data:image/webp;base64,AAAA", look: { references: ["data:image/webp;base64,BBBB"] } }); test("adult characters: the 18+ rule is the first thing every photo and clip prompt says, and the shot's", () => { - assert.equal(A.ADULT_ONLY, "Hard rule, 18+ only: everyone shown is an adult, 18 or older, with a grown adult face and body."); + // the shot writer, a text model, is told the number; the picture model reads the same rule in words no filter takes + // for an age (or for anyone it guards): a picture prompt with safe mode off is read the hardest + assert.equal(A.EIGHTEEN, "Hard rule, 18+ only: everyone shown is an adult, 18 or older, with a grown adult face and body."); + assert.equal(A.ADULT_ONLY, "Everyone shown is a grown adult."); for (const p of [A.photoPrompt(mara, "On the stage.", 1, 1500, { adult: true }), A.photoPrompt(mara, "On the stage.", 2, 1500, { adult: true }), A.clipPrompt(mara, "She bows.", 2500, { adult: true })]) { - assert.ok(p.startsWith(A.ADULT_ONLY), p); + assert.ok(p.startsWith(A.ADULT_ONLY + " The reference picture"), p); assert.ok(!/Safe for work/.test(p)); - assert.match(p, /Never a real, identifiable person\./); + assert.ok(!/\d|identifiable|hard rule|body/i.test(p), "no age, no one it guards, nothing for a filter to read: " + p); } assert.ok(A.photoPrompt(mara, "x ".repeat(3000), 1, 400, { adult: true }).startsWith(A.ADULT_ONLY), "a long shot can't push it out"); assert.ok(A.clipPrompt(mara, "x ".repeat(3000), 400, { adult: true }).startsWith(A.ADULT_ONLY)); @@ -156,7 +159,7 @@ test("adult characters: the 18+ rule is the first thing every photo and clip pro assert.equal(A.shotMessages(mara, [], "", "clip", { adult: true })[0].content, A.CLIP_SYSTEM_ADULT); assert.equal(A.moveMessages(mara, [], { adult: true })[0].content, A.MOVE_SYSTEM_ADULT); for (const s of [A.PHOTO_SYSTEM_ADULT, A.CLIP_SYSTEM_ADULT, A.MOVE_SYSTEM_ADULT]) { - assert.ok(s.includes(A.ADULT_ONLY), "the shot is written to the same rule"); + assert.ok(s.includes(A.EIGHTEEN), "the shot is written to the 18+ rule, number and all"); assert.ok(!/safe for work/i.test(s)); assert.match(s, /Never show a real, identifiable person/); } @@ -173,7 +176,7 @@ test("the words Havre puts in a picture or a clip are none a content filter turn A.clipPrompt(bare, "She waves.", 2500, { adult }), A.portraitPrompt(who, 1500, { adult }), A.portraitPrompt({ ...bare, tagline: "" }, 1500, { adult })]) { assert.ok(!TRIPS.test(p), `“${(p.match(TRIPS) || [])[0]}” in: ${p}`); assert.equal(A.underage(p), "", p); - if (adult) assert.ok(p.startsWith("Hard rule, 18+ only: everyone shown is an adult, 18 or older, with a grown adult face and body."), p); + if (adult) assert.ok(p.startsWith("Everyone shown is a grown adult. "), p); else assert.match(p, /Safe for work: everyone in it is an adult, fully clothed\./); } } @@ -190,8 +193,8 @@ test("the words Havre puts in a picture or a clip are none a content filter turn test("adult characters: a portrait for their card opens with the 18+ rule too, and is drawn whatever pictures they have", () => { const p = A.portraitPrompt(mara, 1500, { adult: true }); assert.ok(p.startsWith(A.ADULT_ONLY), p); - assert.match(p, /Never a real, identifiable person\. Portrait photo of Mara Vell, an original adult character: /); - assert.ok(!/Safe for work/.test(p)); + assert.match(p, /^Everyone shown is a grown adult\. Portrait photo of Mara Vell, an original adult character: /); + assert.ok(!/Safe for work|\d|identifiable/.test(p)); assert.ok(A.portraitPrompt({ ...mara, look: { description: "x ".repeat(3000) } }, 400, { adult: true }).startsWith(A.ADULT_ONLY), "a long look can't push it out"); assert.match(A.portraitPrompt(mara), /Safe for work/, "without adult, as before"); // drawn from words, it's made from none of the pictures on their card: those don't count, everything else does @@ -462,7 +465,8 @@ test("venice: an error carries Venice's answer as it came, and a picture or clip }); test("the words in a prompt a content filter may weigh, for what venice saw", () => { - assert.deepEqual(A.filterWords(A.photoPrompt(mara, "On the stage, in a navy coat.", 1, 1500, { adult: true })), ["18"], "the 18+ rule's own age, once"); + assert.deepEqual(A.filterWords(A.photoPrompt(mara, "On the stage, in a navy coat.", 1, 1500, { adult: true })), [], "an adult photo's prompt: none"); + assert.deepEqual(A.filterWords(A.EIGHTEEN), ["18"], "the number stays with the shot writer"); assert.deepEqual(A.filterWords(A.photoPrompt(ines, "On deck.", 1, 1500)), [], "a safe-for-work photo: none"); assert.deepEqual(A.filterWords("A young woman in her early twenties, a schoolgirl's satchel, 21 years old, with her little sister, a girl of nine, and their son."), ["young", "schoolgirl", "21", "girl", "son"]); diff --git a/ventures/havre/tests/smoke.mjs b/ventures/havre/tests/smoke.mjs index 8f23b55..4ec2280 100644 --- a/ventures/havre/tests/smoke.mjs +++ b/ventures/havre/tests/smoke.mjs @@ -120,6 +120,8 @@ const handWritten = (o) => "Sure! Here it is:\n```json\n" + JSON.stringify(o, nu const talks = () => seen.chats.filter((b) => !/^You (decide whether|keep the memory)/.test(String((b.messages[0] || {}).content || ""))); /** words a content filter turns a prompt down for: none of them are Havre's own (abilities.test.mjs has the rest) */ const TRIPS = /\b(?:sex\w*|nud\w*|naked|gor[ey]|minors?|child\w*|kids?|school\w*|teen\w*|under[\s-]?(?:18|eighteen|age\w*)|young\w*)\b/i; +// and in a picture prompt sent with safe mode off, which a filter reads the hardest: no age, nothing it guards +const ADULT_TRIPS = /\d|identifiable|hard rule|\bbody\b/i; const multipart = (req) => (req.headers()["content-type"] || "").startsWith("multipart/form-data"); const CHARACTERS = [ { slug: "sable-penrose", name: "Sable Penrose", description: "Your new neighbor needs help moving in.", author: "btrzm", @@ -913,8 +915,8 @@ async function main() { const adultEdit = seen.edits.at(-1).body; assert.equal(adultEdit.modelId || adultEdit.model, "qwen-edit-uncensored"); assert.equal(adultEdit.safe_mode, false, "safe mode off, for her"); - assert.ok(adultEdit.prompt.startsWith("Hard rule, 18+ only: everyone shown is an adult, 18 or older"), adultEdit.prompt); - assert.ok(!TRIPS.test(adultEdit.prompt), "nothing in it for a content filter to turn down: " + adultEdit.prompt); + assert.ok(adultEdit.prompt.startsWith("Everyone shown is a grown adult. The reference picture"), adultEdit.prompt); + assert.ok(!TRIPS.test(adultEdit.prompt) && !ADULT_TRIPS.test(adultEdit.prompt), "nothing in it for a content filter to turn down: " + adultEdit.prompt); await has(page, "#log .line.media .cap", /photo · Qwen Edit Uncensored · \$0\.04 · 18\+ · save/); // a clip from her: an uncensored model, priced first, its shot written to the 18+ rule too await page.getByRole("button", { name: "ask for a clip" }).click(); @@ -952,8 +954,8 @@ async function main() { assert.equal(seen.generated.length, drawnBefore + 1); assert.equal(adultDrawn.model, "krea-2-turbo"); assert.equal(adultDrawn.safe_mode, false, "no safe mode for an adult character's portrait"); - assert.ok(adultDrawn.prompt.startsWith("Hard rule, 18+ only: everyone shown is an adult, 18 or older"), adultDrawn.prompt); - assert.ok(!TRIPS.test(adultDrawn.prompt), adultDrawn.prompt); + assert.ok(adultDrawn.prompt.startsWith("Everyone shown is a grown adult. Portrait photo of Ines Corbel"), adultDrawn.prompt); + assert.ok(!TRIPS.test(adultDrawn.prompt) && !ADULT_TRIPS.test(adultDrawn.prompt), adultDrawn.prompt); assert.equal(blurNext, true, "so nothing was blurred"); blurNext = false; await has(page, "#adult-note", /^their photos and clips can be adult/); @@ -965,7 +967,8 @@ async function main() { assert.equal(seen.edits.length, editedBefore + 1); assert.equal(sheetEdit.modelId || sheetEdit.model, "qwen-edit-uncensored"); assert.equal(sheetEdit.safe_mode, false); - assert.ok(sheetEdit.prompt.startsWith("Hard rule, 18+ only: everyone shown is an adult, 18 or older"), sheetEdit.prompt); + assert.ok(sheetEdit.prompt.startsWith("Everyone shown is a grown adult. The reference picture"), sheetEdit.prompt); + assert.ok(!ADULT_TRIPS.test(sheetEdit.prompt), sheetEdit.prompt); await has(page, "#adult-note", /^their photos and clips can be adult/); // with adult pictures switched off, safe mode is back on, and a blur says why await page.getByRole("button", { name: "abilities" }).click();