Extract anal role graph wording

This commit is contained in:
2026-06-26 17:32:04 +02:00
parent 04ee754f68
commit ee62e2215d
6 changed files with 290 additions and 75 deletions
+8 -2
View File
@@ -111,6 +111,9 @@ Already isolated:
- penetration-specific role graph wording lives in - penetration-specific role graph wording lives in
`hardcore_role_penetration.py`, covering the main vaginal penetration `hardcore_role_penetration.py`, covering the main vaginal penetration
position families while Krea POV rewriting keeps first-person geometry stable. position families while Krea POV rewriting keeps first-person geometry stable.
- anal/double-contact role graph wording lives in `hardcore_role_anal.py`,
covering rear-entry anal variants and front/back double-contact source
geometry.
- 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.
@@ -177,8 +180,9 @@ Already isolated:
- `krea_action_dispatch.py` owns non-POV role normalization, action-family - `krea_action_dispatch.py` owns non-POV role normalization, action-family
classification, and family-specific detail cleanup. classification, and family-specific detail cleanup.
- `krea_actions.py` owns final non-POV hardcore action sentence assembly. - `krea_actions.py` owns final non-POV hardcore action sentence assembly.
- `krea_pov_actions.py` owns POV hardcore action sentence rewriting and - `krea_pov_actions.py` owns POV hardcore action sentence rewriting,
first-person body geometry. first-person body geometry, and selected-position-axis priority before loose
context fallback.
Improve later: Improve later:
@@ -307,6 +311,8 @@ Near-term:
- Cover close foreplay and POV penetration Krea routes so raw labels, invalid - Cover close foreplay and POV penetration Krea routes so raw labels, invalid
surface grammar, normal third-person camera text, and composition punctuation surface grammar, normal third-person camera text, and composition punctuation
drift are caught. drift are caught.
- Cover POV outercourse, oral, penetration, anal, and front/back double-contact
Krea routes so selected position geometry stays synchronized with metadata.
Medium-term: Medium-term:
+8
View File
@@ -68,6 +68,7 @@ Core helper ownership:
| `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_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_role_anal.py` | Anal and double-contact role graph wording for rear-entry, raised-edge, kneeling, side-lying, and front/back double-position 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. |
@@ -481,6 +482,8 @@ What each part owns:
- `sexual_poses.json`: available positions, families, action templates, role - `sexual_poses.json`: available positions, families, action templates, role
graph templates, interaction templates, and action-specific pool references. graph templates, interaction templates, and action-specific pool references.
- `prompt_builder.py`: filters which templates/axes remain available. - `prompt_builder.py`: filters which templates/axes remain available.
- `hardcore_role_graphs.py` and action-family helper modules: turn selected
item axes into source role graphs before formatter-specific rewrites.
- `krea_formatter.py`: orchestrates the selected action rewrite into - `krea_formatter.py`: orchestrates the selected action rewrite into
model-readable prose. model-readable prose.
- `krea_action_positions.py`: resolves non-POV pose anchors, body-arrangement - `krea_action_positions.py`: resolves non-POV pose anchors, body-arrangement
@@ -740,6 +743,11 @@ pair metadata through the core Python APIs, then verifies:
without recursive viewer wording; without recursive viewer wording;
- POV penetration routes keep constrained missionary, cowgirl, reverse-cowgirl, - POV penetration routes keep constrained missionary, cowgirl, reverse-cowgirl,
doggy, raised-edge, and lotus geometry through Krea formatting; doggy, raised-edge, and lotus geometry through Krea formatting;
- POV anal routes keep constrained doggy, bent-over, face-down, standing,
side-lying, raised-edge, and kneeling rear-entry geometry through Krea
formatting;
- front/back double-contact routes keep source role graph metadata and Krea
front/back position wording synchronized;
- 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
+61
View File
@@ -0,0 +1,61 @@
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 _anal_position_graph(woman: str, man: str, context: str) -> str:
if "bent-over" in context or "bent over" in context:
return f"{woman} is bent forward with hips raised while {man} stands behind her and thrusts his penis into her ass."
if "face-down" in context:
return f"{woman} lies face-down with ass raised while {man} is positioned behind her and thrusts his penis into her ass."
if "doggy" in context or "rear-entry" in context:
return f"{woman} is on all fours with hips raised while {man} is positioned behind her and thrusts his penis into her ass."
if "standing" in context:
return f"{woman} stands braced with hips angled back while {man} stands behind her and thrusts his penis into her ass."
if "spooning" in context or "side-lying" in context:
return f"{woman} lies on her side with thighs parted while {man} presses behind her and thrusts his penis into her ass."
if "edge-of-bed" in context or "edge of bed" in context or "bed edge" in context or "edge-supported" in context:
return f"{woman} lies near a raised edge with hips exposed while {man} kneels behind her and thrusts his penis into her ass."
if "kneeling" in context:
return f"{woman} kneels forward with hips raised while {man} kneels behind her and thrusts his penis into her ass."
return f"{woman} is on all fours with hips raised while {man} is positioned behind her and thrusts his penis into her ass."
def _two_person_double_graph(woman: str, man: str, context: str) -> str:
if "bent-over" in context or "bent over" in context:
return f"{woman} is bent forward with hips raised while {man} is positioned behind her and thrusts his penis into her ass."
if "face-down" in context:
return f"{woman} lies face-down with hips raised while {man} is positioned behind her and thrusts his penis into her ass."
if "standing" in context:
return f"{woman} stands braced with hips raised while {man} is positioned behind her and thrusts his penis into her ass."
if "kneeling" in context:
return f"{woman} kneels forward with hips raised while {man} is positioned behind her and thrusts his penis into her ass."
return f"{woman} is on all fours with hips raised while {man} is positioned behind her and thrusts his penis into her ass."
def build_anal_or_double_role_graph(
woman: str,
man: str,
third: str,
people_count: int,
item_text: str,
item_axis_values: dict[str, Any] | None = None,
) -> str:
context = _context_text(item_text, item_axis_values)
if "double" in context or "toy" in context:
if people_count >= 3 and third:
return f"{man} thrusts his penis into {woman} while {third} adds a second penetration point from the front."
return _two_person_double_graph(woman, man, context)
if people_count >= 3 and third:
return f"{man} thrusts his penis into {woman} while {third} gives oral contact from the front."
return _anal_position_graph(woman, man, context)
+3 -42
View File
@@ -5,10 +5,12 @@ import re
from typing import Any from typing import Any
try: try:
from .hardcore_role_anal import build_anal_or_double_role_graph
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 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_anal import build_anal_or_double_role_graph
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 from hardcore_role_penetration import build_penetration_role_graph
@@ -240,30 +242,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 anal_position_graph(woman: str, man: str) -> str:
text = " ".join(
str(part or "").lower()
for part in (
item_text,
*((item_axis_values or {}).values()),
)
)
if "bent-over" in text or "bent over" in text:
return f"{woman} is bent forward with hips raised while {man} stands behind her and thrusts his penis into her ass."
if "face-down" in text:
return f"{woman} lies face-down with ass raised while {man} is positioned behind her and thrusts his penis into her ass."
if "doggy" in text or "rear-entry" in text:
return f"{woman} is on all fours with hips raised while {man} is positioned behind her and thrusts his penis into her ass."
if "standing" in text:
return f"{woman} stands braced with hips angled back while {man} stands behind her and thrusts his penis into her ass."
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 thrusts his penis into her ass."
if "edge-of-bed" in text or "edge of bed" in text or "bed edge" in text:
return f"{woman} lies near a raised edge with hips exposed while {man} kneels behind her and thrusts his penis into her ass."
if "kneeling" in text:
return f"{woman} kneels forward with hips raised while {man} kneels behind her and thrusts his penis into her ass."
return f"{woman} is on all fours with hips raised while {man} is positioned behind her and thrusts his penis into her ass."
if people_count == 1: if people_count == 1:
solo = people[0] solo = people[0]
if women_count == 1: if women_count == 1:
@@ -354,24 +332,7 @@ def build_hardcore_role_graph(
elif "oral" in slug: elif "oral" in slug:
graph = build_oral_role_graph(woman, man, item_text, item_axis_values, pov_labels) graph = build_oral_role_graph(woman, man, item_text, item_axis_values, pov_labels)
elif "anal" in slug or "double" in slug: elif "anal" in slug or "double" in slug:
if "double" in item_text or "toy" in item_text: graph = build_anal_or_double_role_graph(woman, man, third, people_count, item_text, item_axis_values)
if people_count >= 3:
graph = f"{man} thrusts his penis into {woman} while {third} adds a second penetration point from the front."
else:
if "bent-over" in item_text or "bent over" in item_text:
graph = f"{woman} is bent forward with hips raised while {man} is positioned behind her and thrusts his penis into her ass."
elif "face-down" in item_text:
graph = f"{woman} lies face-down with hips raised while {man} is positioned behind her and thrusts his penis into her ass."
elif "standing" in item_text:
graph = f"{woman} stands braced with hips raised while {man} is positioned behind her and thrusts his penis into her ass."
elif "kneeling" in item_text:
graph = f"{woman} kneels forward with hips raised while {man} is positioned behind her and thrusts his penis into her ass."
else:
graph = f"{woman} is on all fours with hips raised while {man} is positioned behind her and thrusts his penis into her ass."
elif people_count >= 3:
graph = f"{man} thrusts his penis into {woman} while {third} gives oral contact from the front."
else:
graph = anal_position_graph(woman, man)
elif "threesome" in slug: elif "threesome" in slug:
graph = f"{man} thrusts his penis into {woman} while {third or any_person({woman, man})} uses mouth and hands on the exposed body." graph = f"{man} thrusts his penis into {woman} while {third or any_person({woman, man})} uses mouth and hands on the exposed body."
elif "group" in slug or "orgy" in slug: elif "group" in slug or "orgy" in slug:
+40 -31
View File
@@ -138,6 +138,10 @@ def pov_hardcore_pose_sentence(
action_lower = action_text.lower() action_lower = action_text.lower()
if not context: if not context:
context = action_lower context = action_lower
position_text = ""
if isinstance(axis_values, dict):
position_text = _clean(axis_values.get("position", "")).lower()
position_context = position_text or context
def sentence(base: str) -> str: def sentence(base: str) -> str:
details = "" details = ""
@@ -212,63 +216,68 @@ def pov_hardcore_pose_sentence(
contact = pov_contact_clause(action, role_graph, hard_item, axis_values, context) contact = pov_contact_clause(action, role_graph, hard_item, axis_values, context)
if "reverse cowgirl" in context: if "reverse cowgirl" in position_context:
return sentence( return sentence(
"POV reverse cowgirl position: the viewer lies on his back while the woman straddles his hips facing away; " "POV reverse cowgirl position: the viewer lies on his back while the woman straddles his hips facing away; "
f"her back, ass, thighs, and the viewer's foreground legs are visible {contact}" f"her back, ass, thighs, and the viewer's foreground legs are visible {contact}"
) )
if "cowgirl" in context or "straddling a partner" in context or "squatting on top" in context: if "cowgirl" in position_context or "straddling a partner" in position_context or "squatting on top" in position_context:
return sentence( return sentence(
"POV cowgirl position: the viewer lies on his back while the woman straddles his hips facing him; " "POV cowgirl position: the viewer lies on his back while the woman straddles his hips facing him; "
f"her torso, hips, and open thighs fill the frame from below {contact}" f"her torso, hips, and open thighs fill the frame from below {contact}"
) )
if "lotus" in context or "seated in a partner's lap" in context: if "lotus" in position_context or "seated in a partner's lap" in position_context:
return sentence( return sentence(
"POV lotus position: the viewer sits upright while the woman sits in his lap facing him with her legs around his hips; " "POV lotus position: the viewer sits upright while the woman sits in his lap facing him with her legs around his hips; "
f"her torso and hips stay close to the viewer {contact}" f"her torso and hips stay close to the viewer {contact}"
) )
if "kneeling straddle" in context: if "kneeling straddle" in position_context:
return sentence( return sentence(
"POV kneeling straddle position: the viewer kneels upright while the woman straddles his hips facing him; " "POV kneeling straddle position: the viewer kneels upright while the woman straddles his hips facing him; "
f"both torsos are upright and her hips press directly against him {contact}" f"both torsos are upright and her hips press directly against him {contact}"
) )
if "face-down" in context or "face down" in context: if "face-down" in position_context or "face down" in position_context:
return sentence( return sentence(
"The woman is seen from behind with her ass raised toward the POV viewer, lying face-down with hips lifted; " "The woman is seen from behind with her ass raised toward the POV viewer, lying face-down with hips lifted; "
f"the viewer looks down at her raised ass with foreground hands on her hips {contact}" f"the viewer looks down at her raised ass with foreground hands on her hips {contact}"
) )
if "bent-over" in context or "bent over" in context or "bent forward" in context:
return sentence(
"The woman is seen from behind with her ass raised toward the POV viewer, bent forward at the waist with hips lifted and head turned back; "
f"the viewer looks down at her raised ass from behind with foreground hands near her hips {contact}"
)
if "doggy" in context or "all fours" in context or "rear-entry" in context:
return sentence(
"The woman is seen from behind with her ass raised toward the POV viewer, on all fours directly in front of him with hips high and back arched; "
f"the viewer looks down at her raised ass with his hands on her hips in the foreground {contact}"
)
if "standing" in context:
return sentence(
"POV standing rear-entry position: the woman stands braced in front of the viewer with hips angled back and legs steady; "
f"the viewer stands behind her at hip level {contact}"
)
if "spooning" in context or "side-lying" in context or "lies on her side" in context:
return sentence(
"POV side-lying sex position: the woman lies on her side in front of the viewer with thighs parted; "
f"the viewer is behind her along the same body line {contact}"
)
if ( if (
"edge-supported" in context "edge-supported" in position_context
or "raised edge" in context or "raised edge" in position_context
or "edge of bed" in context or "edge of bed" in position_context
or "bed edge" in context or "bed edge" in position_context
or "kneels between her legs" in context or (not position_text and "kneels between her legs" in context)
): ):
return sentence( return sentence(
"POV raised-edge penetration position: the woman reclines at the raised edge with thighs open toward the viewer; " "POV raised-edge penetration position: the woman reclines at the raised edge with thighs open toward the viewer; "
f"the viewer kneels between her legs with his hands near her hips {contact}" f"the viewer kneels between her legs with his hands near her hips {contact}"
) )
if "missionary" in context or ("lies on her back" in context and ("legs open" in context or "thighs open" in context)): if "standing" in position_context:
return sentence(
"POV standing rear-entry position: the woman stands braced in front of the viewer with hips angled back and legs steady; "
f"the viewer stands behind her at hip level {contact}"
)
if "spooning" in position_context or "side-lying" in position_context or "lies on her side" in position_context:
return sentence(
"POV side-lying sex position: the woman lies on her side in front of the viewer with thighs parted; "
f"the viewer is behind her along the same body line {contact}"
)
if "doggy" in position_context or "all fours" in position_context or "rear-entry" in position_context:
return sentence(
"The woman is seen from behind with her ass raised toward the POV viewer, on all fours directly in front of him with hips high and back arched; "
f"the viewer looks down at her raised ass with his hands on her hips in the foreground {contact}"
)
if "kneeling" in position_context:
return sentence(
"POV kneeling rear-entry position: the woman kneels forward in front of the viewer with hips raised and thighs apart; "
f"the viewer kneels behind her at hip level with foreground hands near her waist {contact}"
)
if "bent-over" in position_context or "bent over" in position_context or "bent forward" in position_context:
return sentence(
"The woman is seen from behind with her ass raised toward the POV viewer, bent forward at the waist with hips lifted and head turned back; "
f"the viewer looks down at her raised ass from behind with foreground hands near her hips {contact}"
)
if "missionary" in position_context or (not position_text and "lies on her back" in context and ("legs open" in context or "thighs open" in context)):
return sentence( return sentence(
"POV missionary position: the woman lies on her back with legs open around the viewer's hips; " "POV missionary position: the woman lies on her back with legs open around the viewer's hips; "
f"the viewer is above her with foreground arms braced beside her body {contact}" f"the viewer is above her with foreground arms braced beside her body {contact}"
+170
View File
@@ -163,6 +163,23 @@ def _character_cast(*, pov_man: bool = False) -> str:
)["character_cast"] )["character_cast"]
def _character_cast_two_men(*, pov_first_man: bool = False) -> str:
cast = _character_cast(pov_man=pov_first_man)
return pb.build_character_slot_json(
subject_type="man",
label="B",
age="41-year-old adult",
ethnicity="western_european",
figure="balanced",
body="average",
descriptor_detail="compact",
expression_intensity=0.55,
softcore_expression_intensity=0.35,
hardcore_expression_intensity=0.75,
character_cast=cast,
)["character_cast"]
def _action_filter(focus: str, hardcore_position_config: str | dict[str, Any] | None = "") -> str: def _action_filter(focus: str, hardcore_position_config: str | dict[str, Any] | None = "") -> str:
kwargs = { kwargs = {
"allow_toys": False, "allow_toys": False,
@@ -192,6 +209,28 @@ def _position_filter(focus: str, family: str, positions: list[str] | tuple[str,
return _action_filter(focus, position_config) return _action_filter(focus, position_config)
def _anal_double_filter(positions: list[str] | tuple[str, ...] | str) -> str:
position_config = pb.build_hardcore_position_pool_json(
combine_mode="replace",
family="anal",
selected_positions=positions,
)
return pb.build_hardcore_action_filter_json(
hardcore_position_config=position_config,
focus="anal_only",
allow_toys=True,
allow_double=True,
allow_penetration=True,
allow_foreplay=False,
allow_interaction=False,
allow_manual=False,
allow_oral=False,
allow_outercourse=False,
allow_anal=True,
allow_climax=False,
)
def _coworking_location_config() -> str: def _coworking_location_config() -> str:
return pb.build_location_pool_json( return pb.build_location_pool_json(
enabled=True, enabled=True,
@@ -967,6 +1006,135 @@ def smoke_pov_penetration_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_anal_position_routes() -> None:
cases = [
(
"pov_anal_doggy",
"doggy",
("on all fours", "positioned behind her"),
("on all fours directly in front", "penetrates her ass"),
),
(
"pov_anal_bent_over",
"bent_over",
("bent forward", "stands behind her"),
("bent forward at the waist", "penetrates her ass"),
),
(
"pov_anal_face_down",
"face_down_ass_up",
("lies face-down", "ass raised"),
("lying face-down", "penetrates her ass"),
),
(
"pov_anal_standing",
"standing",
("stands braced", "stands behind her"),
("pov standing rear-entry position", "viewer stands behind her"),
),
(
"pov_anal_side_lying",
"side_lying",
("lies on her side", "presses behind her"),
("pov side-lying sex position", "viewer is behind her"),
),
(
"pov_anal_edge_supported",
"edge_supported",
("raised edge", "kneels behind her"),
("pov raised-edge penetration position", "viewer kneels between her legs"),
),
(
"pov_anal_kneeling",
"kneeling",
("kneels forward", "kneels behind her"),
("pov kneeling rear-entry position", "viewer kneels behind her"),
),
]
for offset, (name, position_key, role_terms, krea_terms) in enumerate(cases, start=3901):
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("anal_only", "anal", [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("position_family") == "anal", f"{name} position_family should be anal")
_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 "first-person" 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_double_front_back_route() -> None:
pair = pb.build_insta_of_pair(
row_number=1,
start_index=1,
seed=3911,
ethnicity="any",
figure="random",
no_plus_women=False,
no_black=False,
trigger=Trigger,
prepend_trigger_to_prompt=True,
options_json=_insta_options(
hardcore_cast="mixed_group",
hardcore_men_count=2,
softcore_camera_mode="from_camera_config",
hardcore_camera_mode="from_camera_config",
camera_detail="compact",
),
character_cast=_character_cast_two_men(),
hardcore_position_config=_anal_double_filter(["front_back"]),
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, "double_front_back_route")
hard_row = pair.get("hardcore_row") or {}
_expect(hard_row.get("position_family") == "anal", "double route position_family should be anal")
_expect("front_back" in (hard_row.get("position_keys") or []), "double route lost front_back key")
role_graph = _expect_text("double_front_back_route.source_role_graph", hard_row.get("source_role_graph"), 40).lower()
_expect("second penetration point from the front" in role_graph, f"double route role graph lost front/back placement: {role_graph}")
krea = krea_formatter.format_krea2_prompt("", metadata_json=_json(pair), target="hardcore")
prompt = _expect_text("double_front_back_route.krea_prompt", krea.get("krea_prompt"), 60).lower()
_expect("metadata" in krea.get("method", ""), "double route Krea did not use metadata")
_expect("front-and-back" in prompt, "double route Krea lost front/back position wording")
_expect("second penetration point" in prompt, "double route Krea lost second-contact wording")
_expect("role graph:" not in prompt and "sexual scene:" not in prompt, "double route Krea leaked raw labels")
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",
@@ -1101,6 +1269,8 @@ SMOKE_CASES: list[tuple[str, Callable[[], None]]] = [
("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), ("pov_penetration_position_routes", smoke_pov_penetration_position_routes),
("pov_anal_position_routes", smoke_pov_anal_position_routes),
("double_front_back_route", smoke_double_front_back_route),
("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),
] ]