Improve POV outercourse positioning

This commit is contained in:
2026-06-25 16:17:19 +02:00
parent 186f225778
commit cd076b3c02
3 changed files with 84 additions and 32 deletions
+13 -3
View File
@@ -448,7 +448,9 @@
"toes curled around the shaft", "toes curled around the shaft",
"balls held gently against the mouth", "balls held gently against the mouth",
"skin visibly compressed under fingers", "skin visibly compressed under fingers",
"wet contact visible on the penis" "wet contact visible on the penis",
"hand wrapped around the shaft with the glans visible",
"fingers and palm visibly stroking the penis"
], ],
"expression_detail": [ "expression_detail": [
"focused downward gaze", "focused downward gaze",
@@ -468,7 +470,9 @@
"hands holding the ankles in place", "hands holding the ankles in place",
"one hand holding the base of the penis", "one hand holding the base of the penis",
"hands cupping balls close to the mouth", "hands cupping balls close to the mouth",
"fingers spreading the breasts around the shaft" "fingers spreading the breasts around the shaft",
"one hand wrapped around the shaft",
"both hands stroking the penis in the foreground"
], ],
"outer_act": [ "outer_act": [
"boobjob with the penis squeezed between both breasts", "boobjob with the penis squeezed between both breasts",
@@ -478,6 +482,8 @@
"balls licking with tongue contact under the shaft", "balls licking with tongue contact under the shaft",
"penis licking with tongue along the shaft and glans", "penis licking with tongue along the shaft and glans",
"slow tongue licking on the underside of the penis", "slow tongue licking on the underside of the penis",
"handjob with one hand wrapped around the penis",
"two-handed handjob with the shaft centered in frame",
"footjob with both soles wrapped around the penis", "footjob with both soles wrapped around the penis",
"footjob with toes curled around the shaft", "footjob with toes curled around the shaft",
"feet stroking the penis while the legs frame the contact" "feet stroking the penis while the legs frame the contact"
@@ -490,9 +496,11 @@
"low-angle balls-licking position", "low-angle balls-licking position",
"kneeling penis-licking position", "kneeling penis-licking position",
"side-profile penis-licking position", "side-profile penis-licking position",
"kneeling handjob position",
"close-up handjob position",
"reclining footjob position", "reclining footjob position",
"seated footjob position", "seated footjob position",
"POV footjob position" "close-up footjob position"
], ],
"surface": [ "surface": [
"rumpled bed sheets", "rumpled bed sheets",
@@ -511,6 +519,7 @@
"wet lips and tongue contact", "wet lips and tongue contact",
"slight saliva shine on skin", "slight saliva shine on skin",
"toes pressing into the shaft", "toes pressing into the shaft",
"fingers sliding along the shaft",
"natural asymmetry and soft tissue movement", "natural asymmetry and soft tissue movement",
"visible pressure marks from fingers or soles" "visible pressure marks from fingers or soles"
], ],
@@ -520,6 +529,7 @@
"glans and lips visible at the same contact point", "glans and lips visible at the same contact point",
"balls and mouth contact visible", "balls and mouth contact visible",
"tongue contact on the penis clearly visible", "tongue contact on the penis clearly visible",
"hand and penis centered in frame",
"feet and penis centered in frame", "feet and penis centered in frame",
"soles and shaft contact clearly visible", "soles and shaft contact clearly visible",
"explicit non-penetrative genital contact visible" "explicit non-penetrative genital contact visible"
+30 -15
View File
@@ -468,29 +468,37 @@ def _pov_hardcore_pose_sentence(
details = _pov_clean_detail(action_text.split(";", 1)[1], f"{context} {base}", detail_density) details = _pov_clean_detail(action_text.split(";", 1)[1], f"{context} {base}", detail_density)
return f"{base}; {details}" if details else base return f"{base}; {details}" if details else base
def outercourse_sentence(base: str) -> str:
return _clean(base).rstrip(".")
if _is_outercourse_text(context, action_lower): if _is_outercourse_text(context, action_lower):
if any(term in context for term in ("boobjob", "titjob", "breast sex", "breast-sex")): if any(term in context for term in ("boobjob", "titjob", "breast sex", "breast-sex")):
return sentence( return outercourse_sentence(
"POV breast-sex position: the viewer reclines with his hips in the foreground while the woman kneels between his thighs; " "The woman kneels low between the viewer's thighs, chest directly over his pelvis; "
"her hands press both breasts around the viewer's penis with the glans near her mouth" "both hands press her breasts tightly around the viewer's penis, with the shaft vertical in the lower foreground and the glans just below her lips"
) )
if any(term in context for term in ("testicle", "balls licking", "balls-licking", "balls and mouth")): if any(term in context for term in ("testicle", "balls licking", "balls-licking", "balls and mouth")):
return sentence( return outercourse_sentence(
"POV testicle-sucking position: the viewer reclines with thighs open while the woman kneels low between his legs; " "The woman kneels very low between the viewer's thighs, face below the shaft at testicle height; "
"her mouth and tongue stay at the viewer's balls with his penis visible above her face" "her mouth and tongue contact the viewer's balls while his penis points upward above her forehead in the lower foreground"
) )
if any(term in context for term in ("penis licking", "penis-licking", "tongue along", "tongue licking")): if any(term in context for term in ("penis licking", "penis-licking", "tongue along", "tongue licking")):
return sentence( return outercourse_sentence(
"POV penis-licking position: the viewer's hips and penis are in the foreground while the woman kneels close; " "The woman kneels close at the viewer's pelvis, face just above his hips; "
"her tongue moves along the shaft and glans with her hands steadying the base" "her tongue runs along the underside of the penis from shaft to glans while one hand steadies the base in the lower foreground"
)
if any(term in context for term in ("handjob", "hand job", "hand wrapped", "hand stroking", "manual stimulation")):
return outercourse_sentence(
"The woman kneels close at the viewer's pelvis with her face behind the shaft; "
"one hand wraps around the penis and the other steadies the base as she strokes toward the glans in the lower foreground"
) )
if any(term in context for term in ("footjob", "soles", "toes curled", "feet stroking")): if any(term in context for term in ("footjob", "soles", "toes curled", "feet stroking")):
return sentence( return outercourse_sentence(
"POV footjob position: the viewer reclines with his penis in the foreground while the woman faces him; " "The woman faces the viewer with both legs raised toward the camera; "
"her soles wrap around the shaft and stroke it with the contact point centered" "her soles wrap around the penis in the lower foreground, toes curled around the shaft with her face visible beyond the contact"
) )
return sentence( return outercourse_sentence(
"POV non-penetrative outercourse position: the viewer's hips stay in the foreground while the woman keeps the visible contact point centered" "The woman stays close to the viewer's pelvis, keeping the non-penetrative contact centered in the lower foreground with her face visible behind the contact"
) )
penetrative_tokens = ( penetrative_tokens = (
"penetrat", "penetrat",
@@ -687,7 +695,7 @@ def _clothing_access_phrase(action_text: Any) -> str:
text = _clean(action_text).lower() text = _clean(action_text).lower()
if any(term in text for term in ("cumshot", "ejaculat", "semen", "cum on", "cum across", "post-orgasm", "aftermath")): 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" 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")): if any(term in text for term in ("boobjob", "titjob", "breast sex", "handjob", "hand job", "footjob", "testicle", "balls", "penis licking", "non-penetrative")):
return "leaving the contact point unobstructed" return "leaving the contact point unobstructed"
if any(term in text for term in ("oral", "blowjob", "fellatio", "mouth", "tongue")): if any(term in text for term in ("oral", "blowjob", "fellatio", "mouth", "tongue")):
return "leaving the oral contact unobstructed" return "leaving the oral contact unobstructed"
@@ -791,6 +799,11 @@ def _is_outercourse_text(*parts: Any) -> bool:
"penis licking", "penis licking",
"penis-licking", "penis-licking",
"tongue along", "tongue along",
"handjob",
"hand job",
"hand wrapped",
"hand stroking",
"manual stimulation",
"footjob", "footjob",
"soles", "soles",
"toes curled", "toes curled",
@@ -849,6 +862,8 @@ def _hardcore_pose_anchor(role_graph: str, hard_item: str, composition: str = ""
return "testicle-sucking outercourse pose" return "testicle-sucking outercourse pose"
if any(term in text for term in ("penis licking", "penis-licking", "tongue along", "tongue licking")): if any(term in text for term in ("penis licking", "penis-licking", "tongue along", "tongue licking")):
return "penis-licking outercourse pose" return "penis-licking outercourse pose"
if any(term in text for term in ("handjob", "hand job", "hand wrapped", "hand stroking", "manual stimulation")):
return "handjob outercourse pose"
if any(term in text for term in ("footjob", "soles", "toes curled", "feet stroking")): if any(term in text for term in ("footjob", "soles", "toes curled", "feet stroking")):
return "footjob outercourse pose" return "footjob outercourse pose"
return "non-penetrative outercourse pose" return "non-penetrative outercourse pose"
+41 -14
View File
@@ -336,6 +336,7 @@ HARDCORE_POSITION_KEY_CHOICES = [
"boobjob", "boobjob",
"testicle_sucking", "testicle_sucking",
"penis_licking", "penis_licking",
"handjob",
"footjob", "footjob",
"open_thighs", "open_thighs",
"front_back", "front_back",
@@ -379,6 +380,7 @@ HARDCORE_POSITION_KEY_MATCHES = {
"boobjob": ("boobjob", "titjob", "breast-sex", "breast sex"), "boobjob": ("boobjob", "titjob", "breast-sex", "breast sex"),
"testicle_sucking": ("testicle", "balls-licking", "balls licking", "balls and mouth"), "testicle_sucking": ("testicle", "balls-licking", "balls licking", "balls and mouth"),
"penis_licking": ("penis-licking", "penis licking", "tongue along", "tongue licking"), "penis_licking": ("penis-licking", "penis licking", "tongue along", "tongue licking"),
"handjob": ("handjob", "hand job", "stroking the penis", "hand stroking", "manual stimulation"),
"footjob": ("footjob", "soles", "toes curled", "feet stroking"), "footjob": ("footjob", "soles", "toes curled", "feet stroking"),
"open_thighs": ("thighs open", "legs spread", "open thighs", "legs open", "reclining with thighs open"), "open_thighs": ("thighs open", "legs spread", "open thighs", "legs open", "reclining with thighs open"),
"front_back": ("front-and-back", "front and back", "one behind and one in front", "between two partners"), "front_back": ("front-and-back", "front and back", "one behind and one in front", "between two partners"),
@@ -1023,6 +1025,8 @@ def _outercourse_acts_for_position(values: list[Any], position: str) -> list[Any
return filtered(lambda text: any(term in text for term in ("testicle", "balls"))) return filtered(lambda text: any(term in text for term in ("testicle", "balls")))
if "penis-licking" in position_text or "penis licking" in position_text: if "penis-licking" in position_text or "penis licking" in position_text:
return filtered(lambda text: "licking" in text or "tongue" in text) return filtered(lambda text: "licking" in text or "tongue" in text)
if "handjob" in position_text or "hand job" in position_text:
return filtered(lambda text: any(term in text for term in ("handjob", "hand job", "hand wrapped", "two-handed")))
if "footjob" in position_text: if "footjob" in position_text:
return filtered(lambda text: any(term in text for term in ("footjob", "feet", "soles", "toes"))) return filtered(lambda text: any(term in text for term in ("footjob", "feet", "soles", "toes")))
return values return values
@@ -1070,6 +1074,15 @@ def _outercourse_axis_values_for_position(values: list[Any], position: str, axis
"body_contact": ("hips", "body angled", "lower body"), "body_contact": ("hips", "body angled", "lower body"),
} }
return filtered(by_axis.get(axis_name, ("tongue", "glans", "shaft"))) return filtered(by_axis.get(axis_name, ("tongue", "glans", "shaft")))
if "handjob" in position_text or "hand job" in position_text:
by_axis = {
"contact_detail": ("hand", "fingers", "palm", "shaft", "glans"),
"hand_detail": ("hand", "hands", "shaft", "penis"),
"texture_detail": ("fingers", "pressure", "skin", "shaft"),
"visibility": ("hand", "penis", "shaft", "glans"),
"body_contact": ("hips", "knees", "body angle"),
}
return filtered(by_axis.get(axis_name, ("hand", "penis", "shaft")))
if "footjob" in position_text: if "footjob" in position_text:
by_axis = { by_axis = {
"contact_detail": ("soles", "toes", "shaft"), "contact_detail": ("soles", "toes", "shaft"),
@@ -5100,38 +5113,48 @@ def _role_graph(
if any(term in text for term in ("boobjob", "titjob", "breast-sex", "breast sex")): if any(term in text for term in ("boobjob", "titjob", "breast-sex", "breast sex")):
if man_is_pov: if man_is_pov:
return ( return (
f"{woman} kneels between the POV viewer's thighs with her breasts squeezed around the POV viewer's penis, " f"{woman} kneels low between the POV viewer's thighs, chest directly over his pelvis, "
"hands pressing both breasts together around the shaft while the glans stays near her mouth." "both hands pressing her breasts tightly around the POV viewer's penis while the glans sits just below her lips."
) )
return ( return (
f"{man} sits with legs apart while {woman} kneels between his thighs, squeezing her breasts " f"{man} sits with legs apart while {woman} kneels low between his thighs, chest over his pelvis, "
f"around {man}'s penis with both hands while the glans stays near her mouth." f"both hands pressing her breasts tightly around {man}'s penis 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:
return ( return (
f"{woman} kneels between the POV viewer's thighs with her mouth at the POV viewer's balls, " f"{woman} kneels very low between the POV viewer's thighs, face below the shaft at testicle height, "
"lips and tongue making visible contact while the POV viewer's penis remains above her face." "mouth and tongue on the POV viewer's balls while his penis points upward above her forehead."
) )
return ( return (
f"{man} sits with legs apart while {woman} kneels low between his thighs, sucking and licking his balls " f"{man} sits with legs apart while {woman} kneels very low between his thighs, face below the shaft at testicle height, "
f"with {man}'s penis visible above her face." f"mouth and tongue on his balls while {man}'s penis points upward above her forehead."
) )
if "penis-licking" in position_text or "penis licking" in text or "tongue along" in text or "tongue licking" in text: if "penis-licking" in position_text or "penis licking" in text or "tongue along" in text or "tongue licking" in text:
if man_is_pov: if man_is_pov:
return ( return (
f"{woman} kneels at the POV viewer's hips with her tongue along the POV viewer's penis, " f"{woman} kneels close at the POV viewer's pelvis, face just above his hips, "
"face close to the shaft and glans while her hands steady the base." "tongue running along the underside of the POV viewer's penis from shaft to glans while one hand steadies the base."
) )
return ( return (
f"{woman} kneels at {man}'s hips with her tongue along {man}'s penis, face close to the shaft and glans " f"{woman} kneels close at {man}'s pelvis, face just above his hips, "
"while her hands steady the base." f"tongue running along the underside of {man}'s penis from shaft to glans while one hand steadies the base."
)
if "handjob" in position_text or "handjob" in text or "hand job" in text or "hand wrapped" in text:
if man_is_pov:
return (
f"{woman} kneels close at the POV viewer's pelvis with her face behind the shaft, "
"one hand wrapped around the POV viewer's penis and the other hand steadying the base as she strokes toward the glans."
)
return (
f"{woman} kneels close at {man}'s pelvis with her face behind the shaft, "
f"one hand wrapped around {man}'s penis and the other hand steadying the base as she strokes toward the glans."
) )
if "footjob" in text or "soles" in text or "toes curled" in text or "feet stroking" in text: if "footjob" in text or "soles" in text or "toes curled" in text or "feet stroking" in text:
if man_is_pov: if man_is_pov:
return ( return (
f"{woman} faces the POV viewer while the POV viewer reclines, wrapping both soles around the POV viewer's penis " f"{woman} faces the POV viewer with legs raised toward the camera, both soles wrapped around the POV viewer's penis "
"and stroking the shaft with her feet from a clear first-person view." "in the lower foreground and toes curled around the shaft."
) )
return ( return (
f"{man} reclines with hips forward while {woman} faces him and wraps both soles around {man}'s penis, " f"{man} reclines with hips forward while {woman} faces him and wraps both soles around {man}'s penis, "
@@ -6622,6 +6645,10 @@ MAN_LOWER_ACCESS_TERMS = (
"titjob", "titjob",
"breast sex", "breast sex",
"footjob", "footjob",
"handjob",
"hand job",
"hand wrapped",
"hand stroking",
"blowjob", "blowjob",
"fellatio", "fellatio",
"penis sucking", "penis sucking",