Extract penetration role graph wording

This commit is contained in:
2026-06-26 17:20:01 +02:00
parent 86a8f6167a
commit 04ee754f68
5 changed files with 142 additions and 38 deletions
@@ -108,6 +108,9 @@ Already isolated:
- oral-specific role graph wording lives in `hardcore_role_oral.py`, including - oral-specific role graph wording lives in `hardcore_role_oral.py`, including
direct POV viewer phrasing for kneeling, face-sitting, sixty-nine, direct POV viewer phrasing for kneeling, face-sitting, sixty-nine,
edge-supported, side-lying, chair, standing, and reclining oral positions. edge-supported, side-lying, chair, standing, and reclining oral positions.
- penetration-specific role graph wording lives in
`hardcore_role_penetration.py`, covering the main vaginal penetration
position families while Krea POV rewriting keeps first-person geometry stable.
- camera-scene prose and coworking composition adaptation live in - camera-scene prose and coworking composition adaptation live in
`scene_camera_adapters.py`; `prompt_builder.py` still owns camera config `scene_camera_adapters.py`; `prompt_builder.py` still owns camera config
parsing and row mutation. parsing and row mutation.
+3
View File
@@ -67,6 +67,7 @@ Core helper ownership:
| `hardcore_role_graphs.py` | Source role graph construction for hardcore configured-cast rows, including POV-aware interaction geometry. | | `hardcore_role_graphs.py` | Source role graph construction for hardcore configured-cast rows, including POV-aware interaction geometry. |
| `hardcore_role_oral.py` | Oral-sex role graph wording for kneeling, face-sitting, sixty-nine, edge-supported, side-lying, chair, standing, and reclining oral geometry. | | `hardcore_role_oral.py` | Oral-sex role graph wording for kneeling, face-sitting, sixty-nine, edge-supported, side-lying, chair, standing, and reclining oral geometry. |
| `hardcore_role_outercourse.py` | Outercourse role graph wording for boobjob, testicle-sucking, penis-licking, handjob, and footjob geometry. | | `hardcore_role_outercourse.py` | Outercourse role graph wording for boobjob, testicle-sucking, penis-licking, handjob, and footjob geometry. |
| `hardcore_role_penetration.py` | Penetrative-sex role graph wording for missionary, cowgirl, reverse-cowgirl, doggy, standing, side-lying, raised-edge, kneeling-straddle, and lotus geometry. |
| `hardcore_action_metadata.py` | Source action-family and position-family metadata used by Krea2, SDXL, and caption routes. | | `hardcore_action_metadata.py` | Source action-family and position-family metadata used by Krea2, SDXL, and caption routes. |
| `scene_camera_adapters.py` | Location-aware camera/scene prose such as coworking lounge camera layout. | | `scene_camera_adapters.py` | Location-aware camera/scene prose such as coworking lounge camera layout. |
| `prompt_hygiene.py` | Generic prompt, caption, and negative-prompt cleanup. | | `prompt_hygiene.py` | Generic prompt, caption, and negative-prompt cleanup. |
@@ -737,6 +738,8 @@ pair metadata through the core Python APIs, then verifies:
- POV oral routes keep constrained kneeling, face-sitting, sixty-nine, - POV oral routes keep constrained kneeling, face-sitting, sixty-nine,
edge-supported, side-lying, and chair oral geometry through Krea formatting edge-supported, side-lying, and chair oral geometry through Krea formatting
without recursive viewer wording; without recursive viewer wording;
- POV penetration routes keep constrained missionary, cowgirl, reverse-cowgirl,
doggy, raised-edge, and lotus geometry through Krea formatting;
- expression-disabled rows do not fall back to generated expression text. - expression-disabled rows do not fall back to generated expression text.
- static formatter metadata fixtures keep source-provided action families - static formatter metadata fixtures keep source-provided action families
stable across Krea2 prose, SDXL tags, and natural captions even when raw item stable across Krea2 prose, SDXL tags, and natural captions even when raw item
+3 -38
View File
@@ -7,9 +7,11 @@ from typing import Any
try: try:
from .hardcore_role_oral import build_oral_role_graph from .hardcore_role_oral import build_oral_role_graph
from .hardcore_role_outercourse import build_outercourse_role_graph from .hardcore_role_outercourse import build_outercourse_role_graph
from .hardcore_role_penetration import build_penetration_role_graph
except ImportError: # Allows local smoke tests with `python -c`. except ImportError: # Allows local smoke tests with `python -c`.
from hardcore_role_oral import build_oral_role_graph from hardcore_role_oral import build_oral_role_graph
from hardcore_role_outercourse import build_outercourse_role_graph from hardcore_role_outercourse import build_outercourse_role_graph
from hardcore_role_penetration import build_penetration_role_graph
def _lettered(prefix: str, count: int) -> list[str]: def _lettered(prefix: str, count: int) -> list[str]:
@@ -238,43 +240,6 @@ def build_hardcore_role_graph(
return f"{woman} kneels in front of {man} at hip height while {man} ejaculates semen onto her face, lips, and chest." return f"{woman} kneels in front of {man} at hip height while {man} ejaculates semen onto her face, lips, and chest."
return f"{woman} lies on her back with thighs open while {man} kneels between her legs and ejaculates semen onto her body." return f"{woman} lies on her back with thighs open while {man} kneels between her legs and ejaculates semen onto her body."
def penetration_position_graph(woman: str, man: str) -> str:
text = " ".join(
str(part or "").lower()
for part in (
item_text,
*((item_axis_values or {}).values()),
)
)
if "missionary" in text:
return (
f"{woman} lies on her back with legs open around {man}'s hips while {man} is above her between her thighs; "
f"{man}'s hips press close and {man}'s penis thrusts into her pussy."
)
if "reverse cowgirl" in text:
return f"{woman} straddles {man}'s hips facing away while {man} lies under her and {man}'s penis thrusts into her pussy."
if "cowgirl" in text or "straddling" in text:
return f"{woman} straddles {man}'s hips facing him while {man} lies under her and {man}'s penis thrusts into her pussy."
if "doggy" in text or "rear-entry" in text or "bent-over" in text or "bent over" in text:
return f"{woman} is on all fours with hips raised while {man} is positioned behind her and {man}'s penis thrusts into her pussy."
if "standing" in text:
return f"{woman} stands braced with hips angled back while {man} stands behind her and {man}'s penis thrusts into her pussy."
if "spooning" in text or "side-lying" in text:
return f"{woman} lies on her side with thighs parted while {man} presses behind her and {man}'s penis thrusts into her pussy."
if "edge-of-bed" in text or "edge of bed" in text or "bed edge" in text or "edge-supported" in text or "raised edge" in text:
return (
f"{woman} lies back at a raised edge with hips at the edge and legs open while {man} kneels between her thighs; "
f"{man}'s hips press close and {man}'s penis thrusts into her pussy."
)
if "kneeling straddle" in text:
return f"{woman} kneels straddling {man}'s hips while {man} supports her waist and {man}'s penis thrusts into her pussy."
if "lotus" in text:
return f"{woman} sits in {man}'s lap facing him with legs around his hips while {man}'s penis thrusts into her pussy."
return (
f"{woman} lies on her back with legs spread wide and knees bent outward while {man} kneels between her open thighs facing her; "
f"{man}'s hips are pressed between her legs and {man}'s penis thrusts into her pussy."
)
def anal_position_graph(woman: str, man: str) -> str: def anal_position_graph(woman: str, man: str) -> str:
text = " ".join( text = " ".join(
str(part or "").lower() str(part or "").lower()
@@ -414,5 +379,5 @@ def build_hardcore_role_graph(
elif "cumshot" in slug or "climax" in slug: elif "cumshot" in slug or "climax" in slug:
graph = climax_position_graph(woman, man, third) graph = climax_position_graph(woman, man, third)
else: else:
graph = penetration_position_graph(woman, man) graph = build_penetration_role_graph(woman, man, item_text, item_axis_values)
return graph + support_sentence({woman, man, third} if third else {woman, man}) return graph + support_sentence({woman, man, third} if third else {woman, man})
+50
View File
@@ -0,0 +1,50 @@
from __future__ import annotations
from typing import Any
def _context_text(item_text: str, item_axis_values: dict[str, Any] | None) -> str:
return " ".join(
str(part or "").lower()
for part in (
item_text,
*((item_axis_values or {}).values()),
)
)
def build_penetration_role_graph(
woman: str,
man: str,
item_text: str,
item_axis_values: dict[str, Any] | None = None,
) -> str:
text = _context_text(item_text, item_axis_values)
if "missionary" in text:
return (
f"{woman} lies on her back with legs open around {man}'s hips while {man} is above her between her thighs; "
f"{man}'s hips press close and {man}'s penis thrusts into her pussy."
)
if "reverse cowgirl" in text:
return f"{woman} straddles {man}'s hips facing away while {man} lies under her and {man}'s penis thrusts into her pussy."
if "cowgirl" in text or "straddling" in text:
return f"{woman} straddles {man}'s hips facing him while {man} lies under her and {man}'s penis thrusts into her pussy."
if "doggy" in text or "rear-entry" in text or "bent-over" in text or "bent over" in text:
return f"{woman} is on all fours with hips raised while {man} is positioned behind her and {man}'s penis thrusts into her pussy."
if "standing" in text:
return f"{woman} stands braced with hips angled back while {man} stands behind her and {man}'s penis thrusts into her pussy."
if "spooning" in text or "side-lying" in text:
return f"{woman} lies on her side with thighs parted while {man} presses behind her and {man}'s penis thrusts into her pussy."
if "edge-of-bed" in text or "edge of bed" in text or "bed edge" in text or "edge-supported" in text or "raised edge" in text:
return (
f"{woman} lies back at a raised edge with hips at the edge and legs open while {man} kneels between her thighs; "
f"{man}'s hips press close and {man}'s penis thrusts into her pussy."
)
if "kneeling straddle" in text:
return f"{woman} kneels straddling {man}'s hips while {man} supports her waist and {man}'s penis thrusts into her pussy."
if "lotus" in text:
return f"{woman} sits in {man}'s lap facing him with legs around his hips while {man}'s penis thrusts into her pussy."
return (
f"{woman} lies on her back with legs spread wide and knees bent outward while {man} kneels between her open thighs facing her; "
f"{man}'s hips are pressed between her legs and {man}'s penis thrusts into her pussy."
)
+83
View File
@@ -885,6 +885,88 @@ def smoke_pov_oral_position_routes() -> None:
_expect(term in prompt, f"{name} Krea prompt missing {term!r}: {prompt}") _expect(term in prompt, f"{name} Krea prompt missing {term!r}: {prompt}")
def smoke_pov_penetration_position_routes() -> None:
cases = [
(
"pov_penetration_missionary",
"missionary",
("woman a lies on her back", "man a is above her between her thighs"),
("pov missionary position", "viewer is above her", "penetrates her pussy"),
),
(
"pov_penetration_cowgirl",
"cowgirl",
("woman a straddles man a's hips facing him", "man a lies under her"),
("pov cowgirl position", "viewer lies on his back", "woman straddles his hips"),
),
(
"pov_penetration_reverse_cowgirl",
"reverse_cowgirl",
("woman a straddles man a's hips facing away", "man a lies under her"),
("pov reverse cowgirl position", "facing away", "viewer lies on his back"),
),
(
"pov_penetration_doggy",
"doggy",
("woman a is on all fours", "man a is positioned behind her"),
("ass raised toward the pov viewer", "on all fours", "penetrates her pussy"),
),
(
"pov_penetration_edge_supported",
"edge_supported",
("raised edge", "man a kneels between her thighs"),
("pov raised-edge penetration position", "viewer kneels between her legs", "penetrates her pussy"),
),
(
"pov_penetration_lotus",
"lotus_lap",
("woman a sits in man a's lap", "legs around his hips"),
("pov lotus position", "woman sits in his lap", "penetrates her pussy"),
),
]
for offset, (name, position_key, role_terms, krea_terms) in enumerate(cases, start=3801):
pair = pb.build_insta_of_pair(
row_number=1,
start_index=1,
seed=offset,
ethnicity="any",
figure="random",
no_plus_women=False,
no_black=False,
trigger=Trigger,
prepend_trigger_to_prompt=True,
options_json=_insta_options(
softcore_camera_mode="from_camera_config",
hardcore_camera_mode="from_camera_config",
camera_detail="compact",
),
character_cast=_character_cast(pov_man=True),
hardcore_position_config=_position_filter("penetration_only", "penetrative", [position_key]),
location_config=_coworking_location_config(),
hardcore_camera_config=_orbit_camera(
horizontal_angle=45,
vertical_angle=0,
zoom=7.5,
subject_focus="action",
),
)
_expect_pair(pair, name)
hard_row = pair.get("hardcore_row") or {}
_expect(hard_row.get("action_family") == "penetration", f"{name} action_family should be penetration")
_expect(hard_row.get("position_family") == "penetrative", f"{name} position_family should be penetrative")
_expect(position_key in (hard_row.get("position_keys") or []), f"{name} lost position key {position_key!r}")
role_graph = _expect_text(f"{name}.source_role_graph", hard_row.get("source_role_graph"), 40).lower()
for term in role_terms:
_expect(term in role_graph, f"{name} role graph missing {term!r}: {role_graph}")
krea = krea_formatter.format_krea2_prompt("", metadata_json=_json(pair), target="hardcore")
prompt = _expect_text(f"{name}.krea_prompt", krea.get("krea_prompt"), 60).lower()
_expect("metadata" in krea.get("method", ""), f"{name}.krea did not use metadata")
_expect("viewer" in prompt and "pov" in prompt, f"{name} Krea prompt lost POV wording")
_expect("camera:" not in krea.get("krea_prompt", ""), f"{name} Krea prompt emitted normal third-person camera directive")
for term in krea_terms:
_expect(term in prompt, f"{name} Krea prompt missing {term!r}: {prompt}")
def smoke_no_expression_fallback() -> None: def smoke_no_expression_fallback() -> None:
cast = pb.build_character_slot_json( cast = pb.build_character_slot_json(
subject_type="woman", subject_type="woman",
@@ -1018,6 +1100,7 @@ SMOKE_CASES: list[tuple[str, Callable[[], None]]] = [
("krea_pov_penetration_route", smoke_krea_pov_penetration_route), ("krea_pov_penetration_route", smoke_krea_pov_penetration_route),
("pov_outercourse_position_routes", smoke_pov_outercourse_position_routes), ("pov_outercourse_position_routes", smoke_pov_outercourse_position_routes),
("pov_oral_position_routes", smoke_pov_oral_position_routes), ("pov_oral_position_routes", smoke_pov_oral_position_routes),
("pov_penetration_position_routes", smoke_pov_penetration_position_routes),
("expression_disabled", smoke_no_expression_fallback), ("expression_disabled", smoke_no_expression_fallback),
("formatter_metadata_fixtures", smoke_formatter_metadata_fixtures), ("formatter_metadata_fixtures", smoke_formatter_metadata_fixtures),
] ]