Improve hardcore prompt wording QA
This commit is contained in:
@@ -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
@@ -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
@@ -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(".")
|
||||||
|
|||||||
Reference in New Issue
Block a user