Clean up outercourse prompt wording
This commit is contained in:
@@ -235,6 +235,20 @@
|
||||
"wet lips pressed around a partner",
|
||||
"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": [
|
||||
"controlled braced expression during anal sex",
|
||||
"focused eye contact while hips are held open",
|
||||
|
||||
@@ -400,12 +400,12 @@
|
||||
"inherit_compositions": false,
|
||||
"weight": 1.0,
|
||||
"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": [
|
||||
{"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": "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": "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},
|
||||
@@ -413,7 +413,7 @@
|
||||
],
|
||||
"item_templates": [
|
||||
"{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}",
|
||||
"explicit non-penetrative sex pose: {outer_act}, {position}, {contact_detail}, and {visibility}",
|
||||
"{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(" ,;")
|
||||
|
||||
|
||||
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]:
|
||||
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)
|
||||
detail = _hardcore_item_detail(hard_item)
|
||||
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)):
|
||||
role_graph = re.sub(
|
||||
r"\s+while a toy adds (?:the|a) second penetration point\b",
|
||||
@@ -1737,6 +1778,10 @@ def _hardcore_action_sentence(
|
||||
if is_climax:
|
||||
anchor = ""
|
||||
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:
|
||||
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)):
|
||||
|
||||
+72
-6
@@ -1044,19 +1044,34 @@ def _outercourse_axis_values_for_position(values: list[Any], position: str, axis
|
||||
def value_text(value: Any) -> str:
|
||||
return _entry_text(value).lower()
|
||||
|
||||
def filtered(terms: tuple[str, ...]) -> list[Any]:
|
||||
matches = [value for value in values if any(term in value_text(value) for term in terms)]
|
||||
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)
|
||||
and not any(term in value_text(value) for term in excluded_terms)
|
||||
]
|
||||
return matches or values
|
||||
|
||||
if any(term in position_text for term in ("boobjob", "titjob", "breast-sex", "breast sex")):
|
||||
by_axis = {
|
||||
"contact_detail": ("compressed", "glans", "shaft", "skin", "fingers"),
|
||||
"contact_detail": ("compressed", "glans", "breast", "breasts", "soft tissue", "skin visibly"),
|
||||
"hand_detail": ("breast", "breasts", "fingers"),
|
||||
"texture_detail": ("compression", "soft flesh", "skin", "flesh", "asymmetry"),
|
||||
"visibility": ("breast", "breasts", "glans", "shaft"),
|
||||
"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")):
|
||||
by_axis = {
|
||||
"contact_detail": ("balls", "lips", "tongue", "wet"),
|
||||
@@ -3581,6 +3596,50 @@ def _character_expression_entries(
|
||||
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:
|
||||
detail = _normalize_descriptor_detail(descriptor_detail)
|
||||
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."
|
||||
)
|
||||
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"both hands lifting and pressing her breasts tightly around {man}'s penis shaft while the glans sits just below her lips."
|
||||
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"{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 man_is_pov:
|
||||
@@ -5840,6 +5899,13 @@ def _build_custom_row(
|
||||
expression_phase,
|
||||
)
|
||||
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:
|
||||
expression = character_expression_text
|
||||
source_composition = _choose_text(
|
||||
|
||||
Reference in New Issue
Block a user