Specialize Krea POV oral positioning

This commit is contained in:
2026-06-27 17:46:15 +02:00
parent 9668bd1709
commit 2b9e880b11
2 changed files with 202 additions and 24 deletions
+189 -17
View File
@@ -8,6 +8,7 @@ try:
from .krea_action_context import ( from .krea_action_context import (
axis_values_text, axis_values_text,
is_climax_text, is_climax_text,
is_oral_text,
is_outercourse_text, is_outercourse_text,
is_toy_assisted_double_text, is_toy_assisted_double_text,
position_context_text, position_context_text,
@@ -18,6 +19,7 @@ except ImportError: # Allows local smoke tests with `python -c`.
from krea_action_context import ( from krea_action_context import (
axis_values_text, axis_values_text,
is_climax_text, is_climax_text,
is_oral_text,
is_outercourse_text, is_outercourse_text,
is_toy_assisted_double_text, is_toy_assisted_double_text,
position_context_text, position_context_text,
@@ -76,12 +78,21 @@ def pov_clean_detail(detail: Any, context: str, detail_density: str) -> str:
detail = re.sub(r"\bthe POV viewer\b", "the viewer", detail, flags=re.IGNORECASE) detail = re.sub(r"\bthe POV viewer\b", "the viewer", detail, flags=re.IGNORECASE)
detail = re.sub(r"\bthe man's\b", "the viewer's", detail, flags=re.IGNORECASE) detail = re.sub(r"\bthe man's\b", "the viewer's", detail, flags=re.IGNORECASE)
detail = re.sub(r"\bthe man\b", "the viewer", detail, flags=re.IGNORECASE) detail = re.sub(r"\bthe man\b", "the viewer", detail, flags=re.IGNORECASE)
detail = re.sub(r"\bhe\b", "the viewer", detail, flags=re.IGNORECASE)
detail = re.sub(r"\bhis\b", "the viewer's", detail, flags=re.IGNORECASE)
detail = re.sub(r"\bhim\b", "the viewer", detail, flags=re.IGNORECASE)
detail = re.sub( detail = re.sub(
r"^(?:missionary|cowgirl|reverse cowgirl|doggy style|standing sex|spooning sex|edge-supported|edge-of-bed|raised edge|kneeling straddle|lotus sex|bent-over|face-down ass-up|side-lying|kneeling rear-entry)\s+(?:position|pose)\s+(?:featuring|with|while|,)?\s*", r"^(?:missionary|cowgirl|reverse cowgirl|doggy style|standing sex|spooning sex|edge-supported|edge-of-bed|raised edge|kneeling straddle|lotus sex|bent-over|face-down ass-up|side-lying|kneeling rear-entry)\s+(?:position|pose)\s+(?:featuring|with|while|,)?\s*",
"", "",
detail, detail,
flags=re.IGNORECASE, flags=re.IGNORECASE,
) )
detail = re.sub(
r"^(?:kneeling oral|standing oral|chair oral|side-lying oral|sixty-nine|edge-supported oral|edge-of-bed oral|reclining cunnilingus|straddled oral|spread-leg oral)\s+(?:position|pose),?\s*",
"",
detail,
flags=re.IGNORECASE,
)
detail = re.sub(r"^(?:featuring|with)\s+", "", detail, flags=re.IGNORECASE) detail = re.sub(r"^(?:featuring|with)\s+", "", detail, flags=re.IGNORECASE)
detail = re.sub( detail = re.sub(
r"^(?:full-body|explicit|close-contact|deep|hardcore|vaginal|anal)?\s*(?:penetrative sex|vaginal sex|anal sex|penetration with visible genital contact|hardcore vaginal thrusting|hardcore anal thrusting),?\s*", r"^(?:full-body|explicit|close-contact|deep|hardcore|vaginal|anal)?\s*(?:penetrative sex|vaginal sex|anal sex|penetration with visible genital contact|hardcore vaginal thrusting|hardcore anal thrusting),?\s*",
@@ -127,6 +138,26 @@ def pov_clean_detail(detail: Any, context: str, detail_density: str) -> str:
return limit_detail_for_density(detail, detail_density, is_climax_text(context, detail)) return limit_detail_for_density(detail, detail_density, is_climax_text(context, detail))
def pov_clean_oral_detail(detail: Any, context: str, detail_density: str) -> str:
detail = pov_clean_detail(detail, context, detail_density)
if not detail:
return ""
duplicate_patterns = (
r"\bthe woman takes the viewer's penis in her mouth with\s+",
r"\bthe woman takes the viewer's penis in her mouth\b,?\s*",
r"\bher mouth on the viewer's penis\b,?\s*",
r"\bthe viewer's mouth on the woman's pussy\b,?\s*",
r"\bmouth on the viewer's penis\b,?\s*",
r"\bmouth on the woman's pussy\b,?\s*",
)
for pattern in duplicate_patterns:
detail = re.sub(pattern, "", detail, flags=re.IGNORECASE)
detail = re.sub(r"\bwith\s+(?=[,;.]|$)", "", detail, flags=re.IGNORECASE)
detail = re.sub(r"\s*,\s*", ", ", detail)
detail = re.sub(r",\s*,", ",", detail)
return _clean(detail).strip(" ,;")
def pov_hardcore_pose_sentence( def pov_hardcore_pose_sentence(
action: Any, action: Any,
role_graph: Any, role_graph: Any,
@@ -154,6 +185,66 @@ def pov_hardcore_pose_sentence(
def outercourse_sentence(base: str) -> str: def outercourse_sentence(base: str) -> str:
return _clean(base).rstrip(".") return _clean(base).rstrip(".")
def oral_sentence(base: str) -> str:
details = ""
if ";" in action_text:
details = pov_clean_oral_detail(action_text.split(";", 1)[1], f"{context} {base}", detail_density)
return _clean(f"{base}; {details}" if details else base).rstrip(".")
def oral_direction() -> tuple[bool, bool]:
oral_context = f"{context} {action_lower}"
woman_gives = any(
token in oral_context
for token in (
"fellatio",
"blowjob",
"deepthroat",
"penis sucking",
"penis in her mouth",
"mouth on the viewer's penis",
"mouth on viewer's penis",
"takes the viewer's penis",
"takes the man's penis",
"mouth at penis level",
"lips wrapped",
)
)
man_gives = any(
token in oral_context
for token in (
"cunnilingus",
"pussy licking",
"mouth on the woman's pussy",
"mouth on her pussy",
"mouth pressed to her pussy",
"tongue on pussy",
"face-sitting",
"straddles his face",
"straddling the viewer's face",
)
)
if "sixty-nine" in oral_context:
return True, True
return woman_gives, man_gives
penetrative_tokens = (
"penetrat",
"thrust",
"anal",
"cowgirl",
"missionary",
"doggy",
"rear-entry",
"spooning",
"bent-over",
"face-down",
"ejaculat",
"semen",
"cumshot",
"climax",
)
has_penetrative_context = any(token in context or token in action_lower for token in penetrative_tokens)
if ( if (
"face-sitting" in context "face-sitting" in context
or "face sitting" in context or "face sitting" in context
@@ -199,24 +290,105 @@ def pov_hardcore_pose_sentence(
return outercourse_sentence( return outercourse_sentence(
"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" "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 = (
"penetrat", if is_oral_text(context, action_lower) and not has_penetrative_context:
"thrust", woman_gives, man_gives = oral_direction()
"anal", if "sixty-nine" in position_context:
"cowgirl", return oral_sentence(
"missionary", "POV sixty-nine oral position: the woman lies head-to-hips over the viewer, her pelvis close to his face and her head lowered toward his hips; "
"doggy", "her mouth on the viewer's penis and the viewer's mouth on the woman's pussy, with her torso, hips, mouth, and the viewer's lower-foreground body cues aligned in one first-person frame"
"rear-entry",
"spooning",
"side-lying",
"bent-over",
"face-down",
"ejaculat",
"semen",
"cumshot",
"climax",
) )
if not any(token in context or token in action_lower for token in penetrative_tokens): if "side-lying oral" in position_context or "side lying oral" in position_context:
if woman_gives and not man_gives:
return oral_sentence(
"POV side-lying oral position: the viewer lies on his side with hips angled toward the woman while she lies beside his thighs; "
"her head stays at penis height with her mouth on the viewer's penis, shoulders and hands close to his pelvis in the lower foreground"
)
return oral_sentence(
"POV side-lying cunnilingus position: the woman lies on her side with her top thigh lifted while the viewer lies beside her hips; "
"his face is at pussy height, with her thigh, hip, and torso forming a clear side-profile first-person frame"
)
if (
"edge-supported oral" in position_context
or "edge-of-bed oral" in position_context
or "edge of bed oral" in position_context
or "raised edge" in position_context
):
if woman_gives and not man_gives:
return oral_sentence(
"POV raised-edge oral position: the viewer sits at the raised edge with legs apart while the woman kneels directly between his thighs; "
"her head is at penis height, mouth on the viewer's penis, with his thighs framing her shoulders in the lower foreground"
)
return oral_sentence(
"POV raised-edge cunnilingus position: the woman reclines at the raised edge with thighs open toward the viewer; "
"the viewer kneels between her legs with his face at pussy height, her hips and open thighs framing the first-person view"
)
if "chair oral" in position_context:
if woman_gives and not man_gives:
return oral_sentence(
"POV chair oral position: the viewer sits in a chair with legs apart while the woman kneels between the viewer's thighs; "
"her head is low at his pelvis, mouth on the viewer's penis, with chair seat, thighs, and hands anchoring the lower foreground"
)
return oral_sentence(
"POV chair cunnilingus position: the woman sits in the chair with thighs open while the viewer kneels between her legs; "
"his face is at pussy height and her hips, knees, and chair seat define the first-person geometry"
)
if "standing oral" in position_context:
if man_gives and not woman_gives:
return oral_sentence(
"POV standing cunnilingus position: the woman stands braced with one thigh lifted while the viewer kneels in front of her; "
"his face is at pussy height, with her raised thigh, hips, and standing leg clearly framing the view"
)
return oral_sentence(
"POV standing oral position: the viewer stands over her with hips forward while the woman kneels directly in front of him at hip height; "
"her head is tilted up at penis level, mouth on the viewer's penis, with his thighs and hands in the lower foreground"
)
if (
"reclining cunnilingus" in position_context
or "spread-leg oral" in position_context
or "open-thigh" in position_context
or "open thigh" in position_context
):
if woman_gives and not man_gives:
return oral_sentence(
"POV reclining oral position: the viewer reclines with thighs apart while the woman kneels low between his legs; "
"her face stays at penis height with her mouth on the viewer's penis and his thighs framing the first-person view"
)
return oral_sentence(
"POV open-thigh cunnilingus position: the woman reclines on her back with thighs spread toward the viewer; "
"the viewer kneels between her legs with his face at pussy height, her knees, hips, and torso aligned toward the camera"
)
if "straddled oral" in position_context:
if woman_gives and not man_gives:
return oral_sentence(
"POV straddled oral position: the viewer leans forward near the woman's face while she kneels below his pelvis; "
"her mouth stays on the viewer's penis with her head tilted upward and his thighs framing the lower foreground"
)
return oral_sentence(
"POV straddled cunnilingus position: the woman straddles above the viewer's face with her thighs framing his head; "
"her pussy stays directly over the viewer's mouth in a close first-person oral frame"
)
if "kneeling oral" in position_context or "kneeling" in position_context:
if man_gives and not woman_gives:
return oral_sentence(
"POV kneeling cunnilingus position: the woman kneels with thighs parted and hips angled forward while the viewer kneels in front of her; "
"his face is at pussy height, with her knees, hips, and torso readable from the first-person angle"
)
return oral_sentence(
"POV kneeling oral position: the viewer stands over her with hips forward while the woman kneels directly in front of him; "
"her head is at penis height, mouth on the viewer's penis, shoulders below his hips and his thighs framing the lower foreground"
)
if man_gives and not woman_gives:
return oral_sentence(
"POV cunnilingus position: the woman lies back with thighs open toward the viewer while he kneels between her legs; "
"his face is at pussy height, with her knees, hips, and torso forming the first-person frame"
)
return oral_sentence(
"POV oral position: the woman kneels close at the viewer's pelvis with her head at penis height; "
"her mouth is on the viewer's penis, shoulders between his thighs, and the viewer's hands or thighs anchor the lower foreground"
)
if not has_penetrative_context:
return "" return ""
oral_only = any(token in context for token in ("oral", "blowjob", "cunnilingus", "mouth on", "penis in her mouth")) oral_only = any(token in context for token in ("oral", "blowjob", "cunnilingus", "mouth on", "penis in her mouth"))
if oral_only and not any(token in context for token in ("penetrat", "thrust", "anal", "ejaculat", "semen", "cumshot", "climax")): if oral_only and not any(token in context for token in ("penetrat", "thrust", "anal", "ejaculat", "semen", "cumshot", "climax")):
+12 -6
View File
@@ -6199,37 +6199,37 @@ def smoke_pov_oral_position_routes() -> None:
"pov_oral_kneeling", "pov_oral_kneeling",
"kneeling", "kneeling",
("viewer's penis", "takes the viewer's penis in her mouth"), ("viewer's penis", "takes the viewer's penis in her mouth"),
("takes the viewer's penis in her mouth", "viewer stands over her"), ("pov kneeling oral position", "viewer stands over her", "head is at penis height", "thighs framing"),
), ),
( (
"pov_oral_face_sitting", "pov_oral_face_sitting",
"face_sitting", "face_sitting",
("straddling the viewer's face", "pussy directly over the viewer's mouth"), ("straddling the viewer's face", "pussy directly over the viewer's mouth"),
("straddling the viewer's face", "tongue contact visible"), ("close first-person underview", "straddling the viewer's face", "tongue contact visible"),
), ),
( (
"pov_oral_sixty_nine", "pov_oral_sixty_nine",
"sixty_nine", "sixty_nine",
("head-to-hips", "viewer's mouth on woman a's pussy"), ("head-to-hips", "viewer's mouth on woman a's pussy"),
("head-to-hips", "viewer's mouth on the woman's pussy"), ("pov sixty-nine oral position", "head-to-hips", "viewer's mouth on the woman's pussy", "lower-foreground body cues aligned"),
), ),
( (
"pov_oral_edge_supported", "pov_oral_edge_supported",
"edge_supported", "edge_supported",
("raised edge with thighs open", "viewer kneels between her legs"), ("raised edge with thighs open", "viewer kneels between her legs"),
("raised edge with thighs open", "viewer kneels between her legs"), ("pov raised-edge cunnilingus position", "raised edge with thighs open", "face at pussy height"),
), ),
( (
"pov_oral_side_lying", "pov_oral_side_lying",
"side_lying", "side_lying",
("woman a lies on her side", "viewer lies beside her hips"), ("woman a lies on her side", "viewer lies beside her hips"),
("woman lies on her side", "viewer lies beside her hips"), ("pov side-lying cunnilingus position", "woman lies on her side", "top thigh lifted", "viewer lies beside her hips"),
), ),
( (
"pov_oral_chair", "pov_oral_chair",
"chair_oral", "chair_oral",
("viewer sits in a chair", "kneels between his thighs"), ("viewer sits in a chair", "kneels between his thighs"),
("viewer sits in a chair", "kneels between the viewer's thighs"), ("pov chair oral position", "viewer sits in a chair", "kneels between the viewer's thighs", "head is low at his pelvis"),
), ),
] ]
for offset, (name, position_key, role_terms, krea_terms) in enumerate(cases, start=3701): for offset, (name, position_key, role_terms, krea_terms) in enumerate(cases, start=3701):
@@ -6272,6 +6272,12 @@ def smoke_pov_oral_position_routes() -> None:
_expect("viewer" in prompt and "first-person" in prompt, f"{name} Krea prompt lost POV wording") _expect("viewer" in prompt and "first-person" in prompt, f"{name} Krea prompt lost POV wording")
_expect("viewer lies on the viewer" not in prompt, f"{name} Krea prompt kept recursive POV wording: {prompt}") _expect("viewer lies on the viewer" not in prompt, f"{name} Krea prompt kept recursive POV wording: {prompt}")
_expect("camera:" not in krea.get("krea_prompt", ""), f"{name} Krea prompt emitted normal third-person camera directive") _expect("camera:" not in krea.get("krea_prompt", ""), f"{name} Krea prompt emitted normal third-person camera directive")
_expect("as he looks down" not in prompt, f"{name} Krea prompt kept unresolved male pronoun: {prompt}")
_expect(
"the woman takes the viewer's penis in her mouth with" not in prompt,
f"{name} Krea prompt repeated oral contact detail after POV rewrite: {prompt}",
)
_expect("edge-supported oral position;" not in prompt, f"{name} Krea prompt kept source oral-position scaffold: {prompt}")
for term in krea_terms: for term in krea_terms:
_expect(term in prompt, f"{name} Krea prompt missing {term!r}: {prompt}") _expect(term in prompt, f"{name} Krea prompt missing {term!r}: {prompt}")