Improve hardcore prompt wording QA

This commit is contained in:
2026-06-25 15:07:42 +02:00
parent 9c86151b89
commit 186f225778
3 changed files with 257 additions and 40 deletions
+3 -3
View File
@@ -344,7 +344,7 @@
"pussy licking with thighs spread", "pussy licking with thighs spread",
"penis sucking with visible saliva", "penis sucking with visible saliva",
"oral sex with tongue and fingers", "oral sex with tongue and fingers",
"mouth on genitals with explicit contact" "oral contact with mouth on the visible genitals"
], ],
"position": [ "position": [
"kneeling oral position", "kneeling oral position",
@@ -381,7 +381,7 @@
"visibility": [ "visibility": [
"mouth and genitals clearly visible", "mouth and genitals clearly visible",
"tongue contact clearly visible", "tongue contact clearly visible",
"penis and lips visible", "mouth and contact point visible",
"pussy and tongue visible", "pussy and tongue visible",
"saliva and arousal visible", "saliva and arousal visible",
"face pressed to genitals", "face pressed to genitals",
@@ -435,7 +435,7 @@
"knees braced close to the body", "knees braced close to the body",
"torso leaning forward into the contact", "torso leaning forward into the contact",
"legs spread enough to keep the contact point visible", "legs spread enough to keep the contact point visible",
"body angled so the penis, hands, and face stay readable", "body angle keeps the penis and face readable",
"shoulders relaxed while the contact stays centered", "shoulders relaxed while the contact stays centered",
"lower body held still while the upper body works", "lower body held still while the upper body works",
"close body alignment around the penis" "close body alignment around the penis"
+109 -15
View File
@@ -292,9 +292,24 @@ def _natural_label_text(text: Any, labels: list[str]) -> str:
text = re.sub(r"\bWoman A\b", "the woman", text) text = re.sub(r"\bWoman A\b", "the woman", text)
elif labels == ["Man A"]: elif labels == ["Man A"]:
text = re.sub(r"\bMan A\b", "the man", text) text = re.sub(r"\bMan A\b", "the man", text)
text = re.sub(
r"(^|[.!?]\s+)(the woman|the man)\b",
lambda match: match.group(1) + match.group(2).capitalize(),
text,
flags=re.IGNORECASE,
)
return text return text
def _lowercase_for_inline_join(text: str) -> str:
return re.sub(
r"^(The woman|The man|The viewer|The named adults)\b",
lambda match: match.group(1).lower(),
_clean(text),
flags=re.IGNORECASE,
)
def _cast_prose( def _cast_prose(
text: str, text: str,
central_label: str = "Woman A", central_label: str = "Woman A",
@@ -446,10 +461,40 @@ def _pov_hardcore_pose_sentence(
action_lower = action_text.lower() action_lower = action_text.lower()
if not context: if not context:
context = action_lower context = action_lower
def sentence(base: str) -> str:
details = ""
if ";" in action_text:
details = _pov_clean_detail(action_text.split(";", 1)[1], f"{context} {base}", detail_density)
return f"{base}; {details}" if details else base
if _is_outercourse_text(context, action_lower):
if any(term in context for term in ("boobjob", "titjob", "breast sex", "breast-sex")):
return sentence(
"POV breast-sex position: the viewer reclines with his hips in the foreground while the woman kneels between his thighs; "
"her hands press both breasts around the viewer's penis with the glans near her mouth"
)
if any(term in context for term in ("testicle", "balls licking", "balls-licking", "balls and mouth")):
return sentence(
"POV testicle-sucking position: the viewer reclines with thighs open while the woman kneels low between his legs; "
"her mouth and tongue stay at the viewer's balls with his penis visible above her face"
)
if any(term in context for term in ("penis licking", "penis-licking", "tongue along", "tongue licking")):
return sentence(
"POV penis-licking position: the viewer's hips and penis are in the foreground while the woman kneels close; "
"her tongue moves along the shaft and glans with her hands steadying the base"
)
if any(term in context for term in ("footjob", "soles", "toes curled", "feet stroking")):
return sentence(
"POV footjob position: the viewer reclines with his penis in the foreground while the woman faces him; "
"her soles wrap around the shaft and stroke it with the contact point centered"
)
return sentence(
"POV non-penetrative outercourse position: the viewer's hips stay in the foreground while the woman keeps the visible contact point centered"
)
penetrative_tokens = ( penetrative_tokens = (
"penetrat", "penetrat",
"thrust", "thrust",
"penis",
"anal", "anal",
"cowgirl", "cowgirl",
"missionary", "missionary",
@@ -472,12 +517,6 @@ def _pov_hardcore_pose_sentence(
contact = _pov_contact_clause(action, role_graph, hard_item, axis_values, context) contact = _pov_contact_clause(action, role_graph, hard_item, axis_values, context)
def sentence(base: str) -> str:
details = ""
if ";" in action_text:
details = _pov_clean_detail(action_text.split(";", 1)[1], f"{context} {base}", detail_density)
return f"{base}; {details}" if details else base
if "reverse cowgirl" in context: if "reverse cowgirl" in context:
return sentence( return sentence(
"POV reverse cowgirl position: the viewer lies on his back while the woman straddles his hips facing away; " "POV reverse cowgirl position: the viewer lies on his back while the woman straddles his hips facing away; "
@@ -644,11 +683,31 @@ def _sanitize_scene_text_for_cast(text: Any, labels: list[str]) -> str:
return text return text
def _natural_clothing_state(text: Any) -> str: def _clothing_access_phrase(action_text: Any) -> str:
text = _clean(action_text).lower()
if any(term in text for term in ("cumshot", "ejaculat", "semen", "cum on", "cum across", "post-orgasm", "aftermath")):
return "leaving the body exposed for visible semen and aftermath"
if any(term in text for term in ("boobjob", "titjob", "breast sex", "footjob", "testicle", "balls", "penis licking", "non-penetrative")):
return "leaving the contact point unobstructed"
if any(term in text for term in ("oral", "blowjob", "fellatio", "mouth", "tongue")):
return "leaving the oral contact unobstructed"
if any(term in text for term in ("penetrat", "thrust", "penis entering", "vaginal", "anal")):
return "leaving the penetration point unobstructed"
return "leaving skin and body contact readable"
def _natural_clothing_state(text: Any, action_text: Any = "") -> str:
text = _clean(text) text = _clean(text)
if not text: if not text:
return "" return ""
text = re.sub(r"^Clothing state:\s*", "", text, flags=re.IGNORECASE) text = re.sub(r"^Clothing state:\s*", "", text, flags=re.IGNORECASE)
if re.search(r";\s*(?=(?:Woman|Man) [A-Z]\b)", text):
parts = [
_natural_clothing_state(part, action_text).rstrip(".")
for part in re.split(r";\s*(?=(?:Woman|Man) [A-Z]\b)", text)
if _clean(part)
]
return ". ".join(part for part in parts if part)
body_exposure = re.match(r"^Body exposure:\s*(.*?)\.?$", text, flags=re.IGNORECASE) body_exposure = re.match(r"^Body exposure:\s*(.*?)\.?$", text, flags=re.IGNORECASE)
if body_exposure: if body_exposure:
return _clean(body_exposure.group(1)).rstrip(".") return _clean(body_exposure.group(1)).rstrip(".")
@@ -672,9 +731,9 @@ def _natural_clothing_state(text: Any) -> str:
if "nude-adjacent" in state: if "nude-adjacent" in state:
return f"{owner.capitalize()}'s body is partly exposed" return f"{owner.capitalize()}'s body is partly exposed"
if "partially removed" in state or "pushed aside" in state: if "partially removed" in state or "pushed aside" in state:
return f"{owner.capitalize()}'s {outfit} is pushed aside and partly removed, exposing the sexual contact clearly" return f"{owner.capitalize()}'s {outfit} is pushed aside or partly removed where needed, {_clothing_access_phrase(action_text)}"
if "keeps" in state: if "keeps" in state:
return f"{owner.capitalize()} keeps the {outfit} on while the sexual contact stays visible" return f"{owner.capitalize()} keeps the {outfit} on while {_clothing_access_phrase(action_text)}"
text = re.sub(r";\s*(?:softcore visual reference|teaser outfit detail):\s*", ". Visual clothing state: ", text, flags=re.IGNORECASE) text = re.sub(r";\s*(?:softcore visual reference|teaser outfit detail):\s*", ". Visual clothing state: ", text, flags=re.IGNORECASE)
text = text.replace("softcore outfit", "outfit") text = text.replace("softcore outfit", "outfit")
text = text.replace("teaser outfit", "outfit") text = text.replace("teaser outfit", "outfit")
@@ -715,6 +774,31 @@ def _position_context_text(role_graph: str, hard_item: str, composition: str = "
) )
def _is_outercourse_text(*parts: Any) -> bool:
text = " ".join(_clean(part).lower() for part in parts if _clean(part))
return any(
term in text
for term in (
"outercourse",
"non-penetrative",
"boobjob",
"titjob",
"breast sex",
"breast-sex",
"testicle",
"balls licking",
"balls-licking",
"penis licking",
"penis-licking",
"tongue along",
"footjob",
"soles",
"toes curled",
"feet stroking",
)
)
def _is_toy_assisted_double_text(*parts: Any) -> bool: def _is_toy_assisted_double_text(*parts: Any) -> bool:
text = " ".join(_clean(part).lower() for part in parts if _clean(part)) text = " ".join(_clean(part).lower() for part in parts if _clean(part))
if "toy" not in text: if "toy" not in text:
@@ -758,6 +842,16 @@ def _hardcore_pose_anchor(role_graph: str, hard_item: str, composition: str = ""
position_text = _clean(axis_values.get("position", "")).lower() position_text = _clean(axis_values.get("position", "")).lower()
if not text: if not text:
return "" return ""
if _is_outercourse_text(role_graph, hard_item, composition, _axis_values_text(axis_values)):
if any(term in text for term in ("boobjob", "titjob", "breast sex", "breast-sex")):
return "breast-sex outercourse pose"
if any(term in text for term in ("testicle", "balls licking", "balls-licking", "balls and mouth")):
return "testicle-sucking outercourse pose"
if any(term in text for term in ("penis licking", "penis-licking", "tongue along", "tongue licking")):
return "penis-licking outercourse pose"
if any(term in text for term in ("footjob", "soles", "toes curled", "feet stroking")):
return "footjob outercourse pose"
return "non-penetrative outercourse pose"
if _is_toy_assisted_double_text(role_graph, hard_item, composition, _axis_values_text(axis_values)): if _is_toy_assisted_double_text(role_graph, hard_item, composition, _axis_values_text(axis_values)):
if "face-down ass-up" in text or "face-down" in text: if "face-down ass-up" in text or "face-down" in text:
return "toy-assisted face-down rear-entry double-penetration pose" return "toy-assisted face-down rear-entry double-penetration pose"
@@ -887,7 +981,7 @@ def _hardcore_pose_anchor(role_graph: str, hard_item: str, composition: str = ""
return "three-body explicit sex pose" return "three-body explicit sex pose"
if "group" in text or "orgy" in text: if "group" in text or "orgy" in text:
return "multi-body explicit sex pose" return "multi-body explicit sex pose"
if "penetrat" in text or "thrust" in text: if re.search(r"(?<!non-)penetrat|thrust", text):
return "hip-aligned penetrative sex pose" return "hip-aligned penetrative sex pose"
return "" return ""
@@ -1085,7 +1179,7 @@ def _hardcore_pose_arrangement(anchor: str, role_graph: str, hard_item: str, com
return "with all three adult bodies clearly placed around the central subject" return "with all three adult bodies clearly placed around the central subject"
if "group" in text or "orgy" in text: if "group" in text or "orgy" in text:
return "with each adult body readable in the shared sex act" return "with each adult body readable in the shared sex act"
if "penetrat" in text or "thrust" in text: if re.search(r"(?<!non-)penetrat|thrust", text):
return "with hips aligned and legs open around the contact point" return "with hips aligned and legs open around the contact point"
return "" return ""
@@ -1158,7 +1252,7 @@ def _hardcore_item_detail(hard_item: str) -> str:
r"face-sitting cunnilingus", r"face-sitting cunnilingus",
r"pussy licking with thighs spread", r"pussy licking with thighs spread",
r"oral sex with tongue and fingers", r"oral sex with tongue and fingers",
r"mouth on genitals with explicit contact", r"oral contact with mouth on the visible genitals",
r"sixty-nine oral sex", r"sixty-nine oral sex",
) )
act_pattern = "|".join(act_patterns) act_pattern = "|".join(act_patterns)
@@ -1621,7 +1715,7 @@ def _hardcore_action_sentence(
if arrangement and anchor_phrase and not _arrangement_duplicates_role(arrangement, role_graph): if arrangement and anchor_phrase and not _arrangement_duplicates_role(arrangement, role_graph):
anchor_phrase = f"{anchor_phrase} {arrangement}" anchor_phrase = f"{anchor_phrase} {arrangement}"
if role_graph and anchor_phrase: if role_graph and anchor_phrase:
sentence = f"In {anchor_phrase}, {role_graph}" sentence = f"In {anchor_phrase}, {_lowercase_for_inline_join(role_graph)}"
elif role_graph: elif role_graph:
sentence = role_graph sentence = role_graph
elif detail and anchor_phrase: elif detail and anchor_phrase:
@@ -2063,7 +2157,7 @@ def _insta_pair_to_krea(row: dict[str, Any], detail_level: str, style_mode: str)
hard_action, hard_action,
_pov_camera_phrase(pov_labels), _pov_camera_phrase(pov_labels),
_natural_label_text( _natural_label_text(
_filter_pov_labeled_clauses(_natural_clothing_state(row.get("hardcore_clothing_state")), pov_labels), _filter_pov_labeled_clauses(_natural_clothing_state(row.get("hardcore_clothing_state"), hard_action), pov_labels),
hard_labels, hard_labels,
), ),
hard_cast_prose, hard_cast_prose,
+145 -22
View File
@@ -5104,7 +5104,7 @@ def _role_graph(
"hands pressing both breasts together around the shaft while the glans stays near her mouth." "hands pressing both breasts together around the shaft while the glans stays near her mouth."
) )
return ( return (
f"{man} sits or reclines with legs apart while {woman} kneels between his thighs, squeezing her breasts " f"{man} sits with legs apart while {woman} kneels between his thighs, squeezing her breasts "
f"around {man}'s penis with both hands while the glans stays near her mouth." f"around {man}'s penis with both hands while the glans stays near her mouth."
) )
if any(term in text for term in ("testicle", "balls-licking", "balls licking", "balls and mouth", "balls held")): if any(term in text for term in ("testicle", "balls-licking", "balls licking", "balls and mouth", "balls held")):
@@ -5227,7 +5227,7 @@ def _role_graph(
if "kneeling oral" in position_text or ("kneeling oral" in text and not position_text): if "kneeling oral" in position_text or ("kneeling oral" in text and not position_text):
if man_gives and not woman_gives: if man_gives and not woman_gives:
return f"{woman} kneels with thighs parted and hips angled forward while {man} kneels in front of her with his mouth on her pussy." return f"{woman} kneels with thighs parted and hips angled forward while {man} kneels in front of her with his mouth on her pussy."
return f"{woman} kneels in front of {man}'s hips with her mouth at penis level while {man} stands or sits close above her." return f"{woman} kneels in front of {man}'s hips with her mouth at penis level while {man} stands close above her."
if man_gives and not woman_gives: if man_gives and not woman_gives:
return f"{woman} lies on her back with thighs open while {man} kneels between her legs with his mouth pressed to her pussy." return f"{woman} lies on her back with thighs open while {man} kneels between her legs with his mouth pressed to her pussy."
return f"{woman} kneels in front of {man}'s hips and takes his penis in her mouth while {man} keeps his hips aligned with her face." return f"{woman} kneels in front of {man}'s hips and takes his penis in her mouth while {man} keeps his hips aligned with her face."
@@ -6143,8 +6143,8 @@ INSTA_OF_PLATFORM_STYLES = {
INSTA_OF_HARDCORE_CLOTHING_CONTINUITY = { INSTA_OF_HARDCORE_CLOTHING_CONTINUITY = {
"none": "", "none": "",
"same_outfit": "Woman A keeps her teaser outfit on, with sexual contact still clearly visible", "same_outfit": "Woman A keeps her teaser outfit on with the body contact readable",
"partially_removed": "Woman A's teaser outfit is pushed aside and partly removed, exposing the sexual contact clearly", "partially_removed": "Woman A's teaser outfit is pushed aside and partly removed where needed, leaving body contact unobstructed",
"implied_nude": "Woman A's body is partly exposed, with fabric slipping off or covering only part of the body", "implied_nude": "Woman A's body is partly exposed, with fabric slipping off or covering only part of the body",
"explicit_nude": "Woman A's body is fully exposed, bare skin unobstructed", "explicit_nude": "Woman A's body is fully exposed, bare skin unobstructed",
} }
@@ -6301,9 +6301,9 @@ def character_hardcore_clothing_values(state: str, custom_clothing: str = "") ->
if state == "partly_exposed": if state == "partly_exposed":
return ["partly nude, body exposed"] return ["partly nude, body exposed"]
if state == "same_outfit": if state == "same_outfit":
return ["keeps the teaser outfit on, with sexual contact clearly visible"] return ["keeps the teaser outfit on with the body contact readable"]
if state == "partially_removed": if state == "partially_removed":
return ["teaser outfit is pushed aside and partly removed, exposing the sexual contact clearly"] return ["teaser outfit is pushed aside and partly removed where needed, leaving body contact unobstructed"]
if state == "custom": if state == "custom":
return _normalize_characteristic_values(custom_clothing, None, allow_free_text=True) return _normalize_characteristic_values(custom_clothing, None, allow_free_text=True)
return [] return []
@@ -6559,7 +6559,7 @@ def _insta_of_softcore_pose(rng: random.Random, level: str) -> str:
return g.choose(rng, pool) return g.choose(rng, pool)
PENETRATION_LOWER_ACCESS_TERMS = ( WOMAN_LOWER_ACCESS_TERMS = (
"penetrat", "penetrat",
"thrust", "thrust",
"vaginal", "vaginal",
@@ -6577,10 +6577,59 @@ PENETRATION_LOWER_ACCESS_TERMS = (
"penis into", "penis into",
"penis inside", "penis inside",
"penis entering", "penis entering",
"mouth on her pussy",
"mouth pressed to her pussy",
"pussy licking",
"cunnilingus",
"thighs spread",
"thighs open",
"legs spread",
"legs open",
"cum on pussy",
"cum across her pussy",
"cum dripping from pussy",
"cum dripping from ass",
"cum on belly",
"cum on thighs",
"cum across her ass",
"cum across her lower back",
"toy aligned", "toy aligned",
"second penetration point", "second penetration point",
) )
WOMAN_UPPER_ACCESS_TERMS = (
"boobjob",
"titjob",
"breast sex",
"breasts around",
"breasts tightly",
"hands pressing both breasts",
"breasts together",
"cum on breasts",
"cum across her breasts",
"cum on chest",
)
MAN_LOWER_ACCESS_TERMS = (
"penis",
"glans",
"testicle",
"balls",
"cumshot",
"ejaculat",
"semen",
"boobjob",
"titjob",
"breast sex",
"footjob",
"blowjob",
"fellatio",
"penis sucking",
"penis in mouth",
"mouth on penis",
"penis licking",
)
LOWER_BODY_CLOTHING_TERMS = ( LOWER_BODY_CLOTHING_TERMS = (
"panty", "panty",
"panties", "panties",
@@ -6603,15 +6652,38 @@ LOWER_BODY_CLOTHING_TERMS = (
"blanket", "blanket",
) )
UPPER_BODY_CLOTHING_TERMS = (
"bra",
"cup",
"cups",
"corset",
"bodysuit",
"bustier",
"top",
"camisole",
"shirt",
"blouse",
"bodice",
"dress",
"robe",
"jacket",
"sweater",
"harness",
"chest",
"cleavage",
"panel",
"panels",
)
INSTA_OF_HARDCORE_MEN_CLOTHING_LOWER_ACCESS = [ INSTA_OF_HARDCORE_MEN_CLOTHING_LOWER_ACCESS = [
"wears an open button shirt with jeans lowered below the hips for sexual contact", "wears an open button shirt with jeans lowered below the hips for genital access",
"wears a fitted tee pushed up with trousers lowered below the hips", "wears a fitted tee pushed up with trousers lowered below the hips",
"keeps a dark shirt on while pants and underwear are pulled down enough for penetration", "keeps a dark shirt on while pants and underwear are pulled down below the hips",
"wears an open overshirt with jeans pushed down at the thighs", "wears an open overshirt with jeans pushed down at the thighs",
"wears a hoodie lifted at the waist with sweatpants lowered below the hips", "wears a hoodie lifted at the waist with sweatpants lowered below the hips",
"wears gym shorts pulled down enough for sexual contact with his shirt still on", "wears gym shorts pulled down below the hips with his shirt still on",
"keeps a casual shirt on with belt open and pants lowered below the hips", "keeps a casual shirt on with belt open and pants lowered below the hips",
"wears a half-open shirt with lower garments pushed down for clear sexual contact", "wears a half-open shirt with lower garments pushed down below the hips",
] ]
INSTA_OF_HARDCORE_MEN_CLOTHING_VISIBLE = [ INSTA_OF_HARDCORE_MEN_CLOTHING_VISIBLE = [
@@ -6619,28 +6691,38 @@ INSTA_OF_HARDCORE_MEN_CLOTHING_VISIBLE = [
"wears a fitted tee with pants opened at the waist", "wears a fitted tee with pants opened at the waist",
"keeps a dark shirt on with trousers loosened", "keeps a dark shirt on with trousers loosened",
"wears an open overshirt with jeans partly lowered", "wears an open overshirt with jeans partly lowered",
"wears gym shorts lowered enough for sexual contact with a towel nearby", "wears gym shorts loose at the waist with a towel nearby",
"wears a hoodie lifted at the waist with sweatpants loosened", "wears a hoodie lifted at the waist with sweatpants loosened",
"wears a casual shirt with belt open and pants partly lowered", "wears a casual shirt with belt open and pants partly lowered",
"wears a half-open shirt with underwear waistband visible", "wears a half-open shirt with dark trousers",
] ]
def _hardcore_row_needs_lower_access(row: dict[str, Any]) -> bool: def _hardcore_row_access_flags(row: dict[str, Any]) -> dict[str, bool]:
axis_values = row.get("item_axis_values") axis_values = row.get("item_axis_values")
axis_text = " ".join(str(value) for value in axis_values.values()) if isinstance(axis_values, dict) else "" axis_text = " ".join(str(value) for value in axis_values.values()) if isinstance(axis_values, dict) else ""
text = " ".join( role_text = " ".join(
str(part or "") str(part or "")
for part in ( for part in (
row.get("source_role_graph"), row.get("source_role_graph"),
row.get("role_graph"), row.get("role_graph"),
)
).lower()
detail_text = " ".join(
str(part or "")
for part in (
row.get("item"), row.get("item"),
row.get("source_composition"), row.get("source_composition"),
row.get("composition"), row.get("composition"),
axis_text, axis_text,
) )
).lower() ).lower()
return any(term in text for term in PENETRATION_LOWER_ACCESS_TERMS) full_text = f"{role_text} {detail_text}"
return {
"woman_lower": any(term in role_text for term in WOMAN_LOWER_ACCESS_TERMS),
"woman_upper": any(term in full_text for term in WOMAN_UPPER_ACCESS_TERMS),
"man_lower": any(term in role_text for term in MAN_LOWER_ACCESS_TERMS),
}
def _outfit_without_lower_body_blockers(outfit: str) -> str: def _outfit_without_lower_body_blockers(outfit: str) -> str:
@@ -6654,6 +6736,7 @@ def _outfit_without_lower_body_blockers(outfit: str) -> str:
kept = [] kept = []
for fragment in fragments: for fragment in fragments:
fragment = fragment.strip(" ,.;") fragment = fragment.strip(" ,.;")
fragment = re.sub(r"^(?:and|with|under|over)\s+", "", fragment, flags=re.IGNORECASE)
if not fragment: if not fragment:
continue continue
lower = fragment.lower() lower = fragment.lower()
@@ -6672,7 +6755,36 @@ def _outfit_without_lower_body_blockers(outfit: str) -> str:
return ", ".join(deduped) return ", ".join(deduped)
def _insta_of_hardcore_clothing_state(mode: str, softcore_outfit: str, needs_lower_access: bool = False) -> str: def _outfit_without_upper_body_blockers(outfit: str) -> str:
text = str(outfit or "").strip()
if not text:
return ""
text = re.sub(r"\blingerie set\b", "lingerie styling", text, flags=re.IGNORECASE)
text = re.sub(r"\bbalconette bra and brief set\b", "briefs and garter styling", text, flags=re.IGNORECASE)
fragments = re.split(r"\s*,\s*|\s+\band\s+|\s+\bwith\s+|\s+\bunder\s+|\s+\bover\s+", text)
kept = []
for fragment in fragments:
fragment = fragment.strip(" ,.;")
fragment = re.sub(r"^(?:and|with|under|over)\s+", "", fragment, flags=re.IGNORECASE)
if not fragment:
continue
lower = fragment.lower()
if any(term in lower for term in UPPER_BODY_CLOTHING_TERMS):
continue
kept.append(fragment)
if not kept:
return ""
deduped = []
seen = set()
for fragment in kept:
key = re.sub(r"\W+", " ", fragment.lower()).strip()
if key and key not in seen:
deduped.append(fragment)
seen.add(key)
return ", ".join(deduped)
def _insta_of_hardcore_clothing_state(mode: str, softcore_outfit: str, woman_access: str = "") -> str:
mode = mode if mode in INSTA_OF_HARDCORE_CLOTHING_CONTINUITY else "none" mode = mode if mode in INSTA_OF_HARDCORE_CLOTHING_CONTINUITY else "none"
outfit = str(softcore_outfit or "").strip() outfit = str(softcore_outfit or "").strip()
if mode == "none" or not outfit: if mode == "none" or not outfit:
@@ -6682,14 +6794,24 @@ def _insta_of_hardcore_clothing_state(mode: str, softcore_outfit: str, needs_low
return f"Body exposure: {base}." return f"Body exposure: {base}."
if mode == "implied_nude": if mode == "implied_nude":
return f"Body exposure: {base}." return f"Body exposure: {base}."
if mode == "partially_removed" and needs_lower_access: if mode == "partially_removed" and woman_access == "lower":
detail = _outfit_without_lower_body_blockers(outfit) detail = _outfit_without_lower_body_blockers(outfit)
base = ( base = (
"Woman A's lower body is clear for penetration; any lower garment is pulled aside or removed below the hips" "Woman A's lower body is clear; any lower garment is pulled aside or removed below the hips"
) )
if detail: if detail:
return f"Clothing state: {base}; visible remaining styling: {detail}." return f"Clothing state: {base}; visible remaining styling: {detail}."
return f"Clothing state: {base}." return f"Clothing state: {base}."
if mode == "partially_removed" and woman_access == "upper":
detail = _outfit_without_upper_body_blockers(outfit)
base = (
"Woman A's breasts and upper body are clear; any bra cup, bodice, or top panel is pulled aside or removed"
)
if detail:
return f"Clothing state: {base}; visible remaining styling: {detail}."
return f"Clothing state: {base}."
if mode == "partially_removed":
return f"Clothing state: Woman A keeps the outfit mostly on; teaser outfit detail: {outfit}."
return f"Clothing state: {base}; teaser outfit detail: {outfit}." return f"Clothing state: {base}; teaser outfit detail: {outfit}."
@@ -6995,19 +7117,20 @@ def build_insta_of_pair(
pov_character_labels, pov_character_labels,
hard_content_rng, hard_content_rng,
) )
needs_lower_access = _hardcore_row_needs_lower_access(hard_row) access_flags = _hardcore_row_access_flags(hard_row)
woman_access = "lower" if access_flags["woman_lower"] else "upper" if access_flags["woman_upper"] else ""
default_man_hardcore_clothing_entries = _default_man_hardcore_clothing_entries( default_man_hardcore_clothing_entries = _default_man_hardcore_clothing_entries(
hard_men_count, hard_men_count,
pov_character_labels, pov_character_labels,
character_hardcore_clothing_entries, character_hardcore_clothing_entries,
hard_content_rng, hard_content_rng,
needs_lower_access, access_flags["man_lower"],
) )
has_primary_hardcore_clothing = any(entry.startswith("Woman A") for entry in character_hardcore_clothing_entries) has_primary_hardcore_clothing = any(entry.startswith("Woman A") for entry in character_hardcore_clothing_entries)
fallback_hard_clothing_state = "" if has_primary_hardcore_clothing else _insta_of_hardcore_clothing_state( fallback_hard_clothing_state = "" if has_primary_hardcore_clothing else _insta_of_hardcore_clothing_state(
options["hardcore_clothing_continuity"], options["hardcore_clothing_continuity"],
soft_row["item"], soft_row["item"],
needs_lower_access=needs_lower_access, woman_access=woman_access,
) )
hard_clothing_parts = [ hard_clothing_parts = [
part.strip().rstrip(".") part.strip().rstrip(".")