Improve hardcore prompt wording QA
This commit is contained in:
@@ -344,7 +344,7 @@
|
||||
"pussy licking with thighs spread",
|
||||
"penis sucking with visible saliva",
|
||||
"oral sex with tongue and fingers",
|
||||
"mouth on genitals with explicit contact"
|
||||
"oral contact with mouth on the visible genitals"
|
||||
],
|
||||
"position": [
|
||||
"kneeling oral position",
|
||||
@@ -381,7 +381,7 @@
|
||||
"visibility": [
|
||||
"mouth and genitals clearly visible",
|
||||
"tongue contact clearly visible",
|
||||
"penis and lips visible",
|
||||
"mouth and contact point visible",
|
||||
"pussy and tongue visible",
|
||||
"saliva and arousal visible",
|
||||
"face pressed to genitals",
|
||||
@@ -435,7 +435,7 @@
|
||||
"knees braced close to the body",
|
||||
"torso leaning forward into the contact",
|
||||
"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",
|
||||
"lower body held still while the upper body works",
|
||||
"close body alignment around the penis"
|
||||
|
||||
+109
-15
@@ -292,9 +292,24 @@ def _natural_label_text(text: Any, labels: list[str]) -> str:
|
||||
text = re.sub(r"\bWoman A\b", "the woman", text)
|
||||
elif labels == ["Man A"]:
|
||||
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
|
||||
|
||||
|
||||
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(
|
||||
text: str,
|
||||
central_label: str = "Woman A",
|
||||
@@ -446,10 +461,40 @@ def _pov_hardcore_pose_sentence(
|
||||
action_lower = action_text.lower()
|
||||
if not context:
|
||||
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 = (
|
||||
"penetrat",
|
||||
"thrust",
|
||||
"penis",
|
||||
"anal",
|
||||
"cowgirl",
|
||||
"missionary",
|
||||
@@ -472,12 +517,6 @@ def _pov_hardcore_pose_sentence(
|
||||
|
||||
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:
|
||||
return sentence(
|
||||
"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
|
||||
|
||||
|
||||
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)
|
||||
if not text:
|
||||
return ""
|
||||
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)
|
||||
if body_exposure:
|
||||
return _clean(body_exposure.group(1)).rstrip(".")
|
||||
@@ -672,9 +731,9 @@ def _natural_clothing_state(text: Any) -> str:
|
||||
if "nude-adjacent" in state:
|
||||
return f"{owner.capitalize()}'s body is partly exposed"
|
||||
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:
|
||||
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 = text.replace("softcore 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:
|
||||
text = " ".join(_clean(part).lower() for part in parts if _clean(part))
|
||||
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()
|
||||
if not text:
|
||||
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 "face-down ass-up" in text or "face-down" in text:
|
||||
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"
|
||||
if "group" in text or "orgy" in text:
|
||||
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 ""
|
||||
|
||||
@@ -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"
|
||||
if "group" in text or "orgy" in text:
|
||||
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 ""
|
||||
|
||||
@@ -1158,7 +1252,7 @@ def _hardcore_item_detail(hard_item: str) -> str:
|
||||
r"face-sitting cunnilingus",
|
||||
r"pussy licking with thighs spread",
|
||||
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",
|
||||
)
|
||||
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):
|
||||
anchor_phrase = f"{anchor_phrase} {arrangement}"
|
||||
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:
|
||||
sentence = role_graph
|
||||
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,
|
||||
_pov_camera_phrase(pov_labels),
|
||||
_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_cast_prose,
|
||||
|
||||
+145
-22
@@ -5104,7 +5104,7 @@ def _role_graph(
|
||||
"hands pressing both breasts together around the shaft while the glans stays near her mouth."
|
||||
)
|
||||
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."
|
||||
)
|
||||
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 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 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:
|
||||
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."
|
||||
@@ -6143,8 +6143,8 @@ INSTA_OF_PLATFORM_STYLES = {
|
||||
|
||||
INSTA_OF_HARDCORE_CLOTHING_CONTINUITY = {
|
||||
"none": "",
|
||||
"same_outfit": "Woman A keeps her teaser outfit on, with sexual contact still clearly visible",
|
||||
"partially_removed": "Woman A's teaser outfit is pushed aside and partly removed, exposing the sexual contact clearly",
|
||||
"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 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",
|
||||
"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":
|
||||
return ["partly nude, body exposed"]
|
||||
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":
|
||||
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":
|
||||
return _normalize_characteristic_values(custom_clothing, None, allow_free_text=True)
|
||||
return []
|
||||
@@ -6559,7 +6559,7 @@ def _insta_of_softcore_pose(rng: random.Random, level: str) -> str:
|
||||
return g.choose(rng, pool)
|
||||
|
||||
|
||||
PENETRATION_LOWER_ACCESS_TERMS = (
|
||||
WOMAN_LOWER_ACCESS_TERMS = (
|
||||
"penetrat",
|
||||
"thrust",
|
||||
"vaginal",
|
||||
@@ -6577,10 +6577,59 @@ PENETRATION_LOWER_ACCESS_TERMS = (
|
||||
"penis into",
|
||||
"penis inside",
|
||||
"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",
|
||||
"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 = (
|
||||
"panty",
|
||||
"panties",
|
||||
@@ -6603,15 +6652,38 @@ LOWER_BODY_CLOTHING_TERMS = (
|
||||
"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 = [
|
||||
"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",
|
||||
"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 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",
|
||||
"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 = [
|
||||
@@ -6619,28 +6691,38 @@ INSTA_OF_HARDCORE_MEN_CLOTHING_VISIBLE = [
|
||||
"wears a fitted tee with pants opened at the waist",
|
||||
"keeps a dark shirt on with trousers loosened",
|
||||
"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 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_text = " ".join(str(value) for value in axis_values.values()) if isinstance(axis_values, dict) else ""
|
||||
text = " ".join(
|
||||
role_text = " ".join(
|
||||
str(part or "")
|
||||
for part in (
|
||||
row.get("source_role_graph"),
|
||||
row.get("role_graph"),
|
||||
)
|
||||
).lower()
|
||||
detail_text = " ".join(
|
||||
str(part or "")
|
||||
for part in (
|
||||
row.get("item"),
|
||||
row.get("source_composition"),
|
||||
row.get("composition"),
|
||||
axis_text,
|
||||
)
|
||||
).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:
|
||||
@@ -6654,6 +6736,7 @@ def _outfit_without_lower_body_blockers(outfit: str) -> str:
|
||||
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()
|
||||
@@ -6672,7 +6755,36 @@ def _outfit_without_lower_body_blockers(outfit: str) -> str:
|
||||
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"
|
||||
outfit = str(softcore_outfit or "").strip()
|
||||
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}."
|
||||
if mode == "implied_nude":
|
||||
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)
|
||||
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:
|
||||
return f"Clothing state: {base}; visible remaining styling: {detail}."
|
||||
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}."
|
||||
|
||||
|
||||
@@ -6995,19 +7117,20 @@ def build_insta_of_pair(
|
||||
pov_character_labels,
|
||||
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(
|
||||
hard_men_count,
|
||||
pov_character_labels,
|
||||
character_hardcore_clothing_entries,
|
||||
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)
|
||||
fallback_hard_clothing_state = "" if has_primary_hardcore_clothing else _insta_of_hardcore_clothing_state(
|
||||
options["hardcore_clothing_continuity"],
|
||||
soft_row["item"],
|
||||
needs_lower_access=needs_lower_access,
|
||||
woman_access=woman_access,
|
||||
)
|
||||
hard_clothing_parts = [
|
||||
part.strip().rstrip(".")
|
||||
|
||||
Reference in New Issue
Block a user