Clean up outercourse prompt wording
This commit is contained in:
@@ -235,6 +235,20 @@
|
|||||||
"wet lips pressed around a partner",
|
"wet lips pressed around a partner",
|
||||||
"shameless oral-service stare"
|
"shameless oral-service stare"
|
||||||
],
|
],
|
||||||
|
"hardcore_outercourse_expressions": [
|
||||||
|
"focused downward gaze at the contact point",
|
||||||
|
"eyes looking up from the contact point",
|
||||||
|
"mouth open in focused concentration",
|
||||||
|
"heavy-lidded teasing eye contact",
|
||||||
|
"controlled hungry expression",
|
||||||
|
"soft flushed concentration",
|
||||||
|
"messy lips with steady eye contact",
|
||||||
|
"breathless focused pleasure face",
|
||||||
|
"intense close-camera stare",
|
||||||
|
"wet lips close to the contact point",
|
||||||
|
"calm concentrated pleasure expression",
|
||||||
|
"playful shameless eye contact"
|
||||||
|
],
|
||||||
"hardcore_anal_dp_expressions": [
|
"hardcore_anal_dp_expressions": [
|
||||||
"controlled braced expression during anal sex",
|
"controlled braced expression during anal sex",
|
||||||
"focused eye contact while hips are held open",
|
"focused eye contact while hips are held open",
|
||||||
|
|||||||
@@ -400,12 +400,12 @@
|
|||||||
"inherit_compositions": false,
|
"inherit_compositions": false,
|
||||||
"weight": 1.0,
|
"weight": 1.0,
|
||||||
"scene_pools": ["hardcore_private_scenes", "hardcore_bed_scenes", "hardcore_mirror_scenes"],
|
"scene_pools": ["hardcore_private_scenes", "hardcore_bed_scenes", "hardcore_mirror_scenes"],
|
||||||
"expression_pools": ["hardcore_oral_expressions", "hardcore_messy_expressions"],
|
"expression_pools": ["hardcore_outercourse_expressions"],
|
||||||
"compositions": [
|
"compositions": [
|
||||||
{"text": "tight contact close-up with penis, hands, and face readable", "min_people": 2, "max_people": 3},
|
{"text": "tight contact close-up with penis, hands, and face readable", "min_people": 2, "max_people": 3},
|
||||||
{"text": "side-profile non-penetrative contact frame with body geometry clear", "min_people": 2, "max_people": 3},
|
{"text": "side-profile non-penetrative contact frame with body geometry clear", "min_people": 2, "max_people": 3},
|
||||||
{"text": "POV-aligned close-up from the man's hips with the visible partner centered", "min_people": 2, "max_people": 2},
|
{"text": "POV-aligned close-up from the man's hips with the visible partner centered", "min_people": 2, "max_people": 2},
|
||||||
{"text": "low-angle close crop on breasts or feet around the penis", "min_people": 2, "max_people": 3},
|
{"text": "low-angle close crop around the penis with the active contact readable", "min_people": 2, "max_people": 3},
|
||||||
{"text": "front-facing kneeling contact composition with eyes, mouth, and hands visible", "min_people": 2, "max_people": 3},
|
{"text": "front-facing kneeling contact composition with eyes, mouth, and hands visible", "min_people": 2, "max_people": 3},
|
||||||
{"text": "mirror-reflected non-penetrative sex composition with contact point readable", "min_people": 2, "max_people": 3},
|
{"text": "mirror-reflected non-penetrative sex composition with contact point readable", "min_people": 2, "max_people": 3},
|
||||||
{"text": "waist-level explicit contact frame with natural skin compression visible", "min_people": 2, "max_people": 3},
|
{"text": "waist-level explicit contact frame with natural skin compression visible", "min_people": 2, "max_people": 3},
|
||||||
@@ -413,7 +413,7 @@
|
|||||||
],
|
],
|
||||||
"item_templates": [
|
"item_templates": [
|
||||||
"{outer_act} in {position}, with {contact_detail}, {hand_detail}, and {visibility}",
|
"{outer_act} in {position}, with {contact_detail}, {hand_detail}, and {visibility}",
|
||||||
"{position} featuring {outer_act}, {body_contact}, {texture_detail}, and {angle}",
|
"{position} featuring {outer_act}, {body_contact}, {texture_detail}, seen from a {angle} view",
|
||||||
"{angle} view of {outer_act}, with {visibility}, {contact_detail}, and {expression_detail}",
|
"{angle} view of {outer_act}, with {visibility}, {contact_detail}, and {expression_detail}",
|
||||||
"explicit non-penetrative sex pose: {outer_act}, {position}, {contact_detail}, and {visibility}",
|
"explicit non-penetrative sex pose: {outer_act}, {position}, {contact_detail}, and {visibility}",
|
||||||
"{outer_act} on {surface}, with {hand_detail}, {body_contact}, and {texture_detail}",
|
"{outer_act} on {surface}, with {hand_detail}, {body_contact}, and {texture_detail}",
|
||||||
|
|||||||
@@ -1404,6 +1404,46 @@ def _dedupe_toy_double_detail(detail: str) -> str:
|
|||||||
return _clean(detail).strip(" ,;")
|
return _clean(detail).strip(" ,;")
|
||||||
|
|
||||||
|
|
||||||
|
def _dedupe_outercourse_detail(detail: str, role_graph: str, hard_item: str = "", axis_values: Any = None) -> str:
|
||||||
|
detail = _clean(detail)
|
||||||
|
if not detail:
|
||||||
|
return ""
|
||||||
|
context = _position_context_text(role_graph, hard_item, "", axis_values)
|
||||||
|
context_lower = context.lower()
|
||||||
|
breast_sex = any(term in context_lower for term in ("boobjob", "titjob", "breast sex", "breast-sex"))
|
||||||
|
clauses: list[str] = []
|
||||||
|
for clause in _detail_clauses(detail):
|
||||||
|
lower = clause.lower()
|
||||||
|
if breast_sex:
|
||||||
|
if lower in ("penis", "breasts", "mouth clearly visible"):
|
||||||
|
continue
|
||||||
|
if any(
|
||||||
|
term in lower
|
||||||
|
for term in (
|
||||||
|
"boobjob",
|
||||||
|
"titjob",
|
||||||
|
"breast-sex",
|
||||||
|
"breast sex",
|
||||||
|
"seated titjob position",
|
||||||
|
"kneeling boobjob position",
|
||||||
|
"tight close-up breast-sex position",
|
||||||
|
"penis shaft compressed between breasts",
|
||||||
|
"penis squeezed between both breasts",
|
||||||
|
"hands pressing the breasts tightly",
|
||||||
|
"hands pressing breasts firmly together",
|
||||||
|
"fingers spreading the breasts around the penis shaft",
|
||||||
|
"soft flesh squeezed around the penis shaft",
|
||||||
|
"hand wrapped around the penis shaft",
|
||||||
|
"glans near the mouth",
|
||||||
|
"glans visible",
|
||||||
|
"penis, breasts, and mouth clearly visible",
|
||||||
|
)
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
clauses.append(clause)
|
||||||
|
return _join_detail_clauses(clauses)
|
||||||
|
|
||||||
|
|
||||||
def _detail_clauses(detail: str) -> list[str]:
|
def _detail_clauses(detail: str) -> list[str]:
|
||||||
return [part.strip(" ,;") for part in re.split(r",\s*(?:and\s+)?", _clean(detail)) if part.strip(" ,;")]
|
return [part.strip(" ,;") for part in re.split(r",\s*(?:and\s+)?", _clean(detail)) if part.strip(" ,;")]
|
||||||
|
|
||||||
@@ -1727,6 +1767,7 @@ def _hardcore_action_sentence(
|
|||||||
role_graph = _climax_role_graph(role_graph, hard_item, axis_values)
|
role_graph = _climax_role_graph(role_graph, hard_item, axis_values)
|
||||||
detail = _hardcore_item_detail(hard_item)
|
detail = _hardcore_item_detail(hard_item)
|
||||||
anchor = _hardcore_pose_anchor(role_graph, hard_item, composition, axis_values)
|
anchor = _hardcore_pose_anchor(role_graph, hard_item, composition, axis_values)
|
||||||
|
is_outercourse = _is_outercourse_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 _is_toy_assisted_double_text(role_graph, hard_item, composition, _axis_values_text(axis_values)):
|
||||||
role_graph = re.sub(
|
role_graph = re.sub(
|
||||||
r"\s+while a toy adds (?:the|a) second penetration point\b",
|
r"\s+while a toy adds (?:the|a) second penetration point\b",
|
||||||
@@ -1737,6 +1778,10 @@ def _hardcore_action_sentence(
|
|||||||
if is_climax:
|
if is_climax:
|
||||||
anchor = ""
|
anchor = ""
|
||||||
detail = _dedupe_climax_detail(detail, role_graph, detail_density)
|
detail = _dedupe_climax_detail(detail, role_graph, detail_density)
|
||||||
|
elif is_outercourse:
|
||||||
|
anchor = ""
|
||||||
|
detail = _dedupe_outercourse_detail(detail, role_graph, hard_item, axis_values)
|
||||||
|
detail = _limit_detail_for_density(detail, detail_density, False)
|
||||||
else:
|
else:
|
||||||
detail = _dedupe_hardcore_detail(detail, anchor) if anchor else detail
|
detail = _dedupe_hardcore_detail(detail, anchor) if anchor else detail
|
||||||
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)):
|
||||||
|
|||||||
+72
-6
@@ -1044,19 +1044,34 @@ def _outercourse_axis_values_for_position(values: list[Any], position: str, axis
|
|||||||
def value_text(value: Any) -> str:
|
def value_text(value: Any) -> str:
|
||||||
return _entry_text(value).lower()
|
return _entry_text(value).lower()
|
||||||
|
|
||||||
def filtered(terms: tuple[str, ...]) -> list[Any]:
|
def filtered(terms: tuple[str, ...], excluded_terms: tuple[str, ...] = ()) -> list[Any]:
|
||||||
matches = [value for value in values if any(term in value_text(value) for term in terms)]
|
matches = [
|
||||||
|
value
|
||||||
|
for value in values
|
||||||
|
if any(term in value_text(value) for term in terms)
|
||||||
|
and not any(term in value_text(value) for term in excluded_terms)
|
||||||
|
]
|
||||||
return matches or values
|
return matches or values
|
||||||
|
|
||||||
if any(term in position_text for term in ("boobjob", "titjob", "breast-sex", "breast sex")):
|
if any(term in position_text for term in ("boobjob", "titjob", "breast-sex", "breast sex")):
|
||||||
by_axis = {
|
by_axis = {
|
||||||
"contact_detail": ("compressed", "glans", "shaft", "skin", "fingers"),
|
"contact_detail": ("compressed", "glans", "breast", "breasts", "soft tissue", "skin visibly"),
|
||||||
"hand_detail": ("breast", "breasts", "fingers"),
|
"hand_detail": ("breast", "breasts", "fingers"),
|
||||||
"texture_detail": ("compression", "soft flesh", "skin", "flesh", "asymmetry"),
|
"texture_detail": ("compression", "soft flesh", "skin", "flesh", "asymmetry"),
|
||||||
"visibility": ("breast", "breasts", "glans", "shaft"),
|
"visibility": ("breast", "breasts", "glans", "shaft"),
|
||||||
"body_contact": ("torso", "body angled", "shoulders", "hips"),
|
"body_contact": ("torso", "body angled", "shoulders", "hips"),
|
||||||
}
|
}
|
||||||
return filtered(by_axis.get(axis_name, ("breast", "breasts", "shaft")))
|
excluded_by_axis = {
|
||||||
|
"contact_detail": ("hand wrapped", "fingers and palm", "soles", "toes", "balls", "tongue"),
|
||||||
|
"hand_detail": ("base of the penis", "penis shaft", "balls", "thigh", "ankles", "stroking"),
|
||||||
|
"texture_detail": ("toes", "soles", "tongue"),
|
||||||
|
"visibility": ("balls", "soles", "toes", "hand"),
|
||||||
|
"body_contact": ("head tucked", "face directly", "base of the penis"),
|
||||||
|
}
|
||||||
|
return filtered(
|
||||||
|
by_axis.get(axis_name, ("breast", "breasts", "shaft")),
|
||||||
|
excluded_by_axis.get(axis_name, ()),
|
||||||
|
)
|
||||||
if any(term in position_text for term in ("testicle", "balls")):
|
if any(term in position_text for term in ("testicle", "balls")):
|
||||||
by_axis = {
|
by_axis = {
|
||||||
"contact_detail": ("balls", "lips", "tongue", "wet"),
|
"contact_detail": ("balls", "lips", "tongue", "wet"),
|
||||||
@@ -3581,6 +3596,50 @@ def _character_expression_entries(
|
|||||||
return expressions
|
return expressions
|
||||||
|
|
||||||
|
|
||||||
|
def _sanitize_character_expression_text_for_action(
|
||||||
|
expression_text: str,
|
||||||
|
role_graph: Any,
|
||||||
|
item: Any,
|
||||||
|
axis_values: Any = None,
|
||||||
|
) -> str:
|
||||||
|
text = str(expression_text or "").strip()
|
||||||
|
if not text:
|
||||||
|
return ""
|
||||||
|
context = " ".join(
|
||||||
|
str(part or "").lower()
|
||||||
|
for part in (
|
||||||
|
role_graph,
|
||||||
|
item,
|
||||||
|
*((axis_values or {}).values() if isinstance(axis_values, dict) else ()),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
woman_active_outercourse = (
|
||||||
|
re.search(r"\bwoman [a-z]\b", context)
|
||||||
|
and re.search(r"\bman [a-z]\b", context)
|
||||||
|
and any(
|
||||||
|
term in context
|
||||||
|
for term in (
|
||||||
|
"boobjob",
|
||||||
|
"titjob",
|
||||||
|
"breast sex",
|
||||||
|
"breasts tightly",
|
||||||
|
"testicle",
|
||||||
|
"balls-licking",
|
||||||
|
"balls licking",
|
||||||
|
"penis-licking",
|
||||||
|
"penis licking",
|
||||||
|
"handjob",
|
||||||
|
"hand job",
|
||||||
|
"footjob",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
clauses = [clause.strip() for clause in text.split(";") if clause.strip()]
|
||||||
|
if woman_active_outercourse:
|
||||||
|
clauses = [clause for clause in clauses if not re.match(r"^Man [A-Z] has\b", clause)]
|
||||||
|
return "; ".join(clauses)
|
||||||
|
|
||||||
|
|
||||||
def _descriptor_detail_for_subject(subject: Any, descriptor_detail: Any) -> str:
|
def _descriptor_detail_for_subject(subject: Any, descriptor_detail: Any) -> str:
|
||||||
detail = _normalize_descriptor_detail(descriptor_detail)
|
detail = _normalize_descriptor_detail(descriptor_detail)
|
||||||
if detail != "auto":
|
if detail != "auto":
|
||||||
@@ -5136,8 +5195,8 @@ def _role_graph(
|
|||||||
"both hands lifting and pressing her breasts tightly around the POV viewer's penis shaft while the glans sits just below her lips."
|
"both hands lifting and pressing her breasts tightly around the POV viewer's penis shaft while the glans sits just below her lips."
|
||||||
)
|
)
|
||||||
return (
|
return (
|
||||||
f"{man} sits with legs apart while {woman} kneels between his open thighs with her torso bent forward over his pelvis and shoulders low, "
|
f"{woman} kneels between {man}'s open thighs with her torso bent forward over his pelvis and shoulders low while {man} sits with legs apart, "
|
||||||
f"both hands lifting and pressing her breasts tightly around {man}'s penis shaft while the glans sits just below her lips."
|
f"{woman}'s hands lifting and pressing her breasts tightly around {man}'s penis shaft while the glans sits just below her lips."
|
||||||
)
|
)
|
||||||
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")):
|
||||||
if man_is_pov:
|
if man_is_pov:
|
||||||
@@ -5840,6 +5899,13 @@ def _build_custom_row(
|
|||||||
expression_phase,
|
expression_phase,
|
||||||
)
|
)
|
||||||
character_expression_text = "; ".join(character_expressions)
|
character_expression_text = "; ".join(character_expressions)
|
||||||
|
character_expression_text = _sanitize_character_expression_text_for_action(
|
||||||
|
character_expression_text,
|
||||||
|
source_role_graph,
|
||||||
|
item,
|
||||||
|
item_axis_values,
|
||||||
|
)
|
||||||
|
character_expressions = [part.strip() for part in character_expression_text.split(";") if part.strip()]
|
||||||
if character_expression_text:
|
if character_expression_text:
|
||||||
expression = character_expression_text
|
expression = character_expression_text
|
||||||
source_composition = _choose_text(
|
source_composition = _choose_text(
|
||||||
|
|||||||
Reference in New Issue
Block a user