Fix exact Krea2 POV oral routing

This commit is contained in:
2026-06-30 20:35:00 +02:00
parent 5f4dd7d77f
commit 3832044256
8 changed files with 182 additions and 12 deletions
+4
View File
@@ -51,10 +51,12 @@ HARDCORE_POSITION_KEY_CHOICES = [
"side_lying", "side_lying",
"edge_supported", "edge_supported",
"kneeling", "kneeling",
"top_down_oral",
"lotus_lap", "lotus_lap",
"face_sitting", "face_sitting",
"sixty_nine", "sixty_nine",
"reclining_oral", "reclining_oral",
"blowjob_sitting",
"straddled_oral", "straddled_oral",
"spread_leg_oral", "spread_leg_oral",
"chair_oral", "chair_oral",
@@ -138,10 +140,12 @@ HARDCORE_POSITION_KEY_MATCHES = {
"side_lying": ("side-lying", "side lying", "spooning", "on the side", "on her side"), "side_lying": ("side-lying", "side lying", "spooning", "on the side", "on her side"),
"edge_supported": ("edge-of-bed", "edge of bed", "bed edge", "raised edge", "edge-supported"), "edge_supported": ("edge-of-bed", "edge of bed", "bed edge", "raised edge", "edge-supported"),
"kneeling": ("kneeling", "kneels", "kneeling center"), "kneeling": ("kneeling", "kneels", "kneeling center"),
"top_down_oral": ("top-down oral", "top view oral", "top-view oral", "nadir-angle", "overhead oral"),
"lotus_lap": ("lotus", "lap", "seated in a partner's lap"), "lotus_lap": ("lotus", "lap", "seated in a partner's lap"),
"face_sitting": ("face-sitting", "face sitting"), "face_sitting": ("face-sitting", "face sitting"),
"sixty_nine": ("sixty-nine", "69"), "sixty_nine": ("sixty-nine", "69"),
"reclining_oral": ("reclining cunnilingus",), "reclining_oral": ("reclining cunnilingus",),
"blowjob_sitting": ("blowjob_sitting", "upright sitting oral", "sitting upright oral", "seated oral"),
"straddled_oral": ("straddled oral",), "straddled_oral": ("straddled oral",),
"spread_leg_oral": ("spread-leg", "spread leg", "reclining cunnilingus"), "spread_leg_oral": ("spread-leg", "spread leg", "reclining cunnilingus"),
"chair_oral": ("chair oral",), "chair_oral": ("chair oral",),
+27
View File
@@ -8,6 +8,9 @@ except ImportError: # Allows local smoke tests with top-level imports.
import item_axis_policy import item_axis_policy
SITTING_ORAL_VARIANT = "pov_blowjob_sitting_upright_oral"
def _context_text(item_text: str, item_axis_values: dict[str, Any] | None) -> str: def _context_text(item_text: str, item_axis_values: dict[str, Any] | None) -> str:
return item_axis_policy.context_text(item_text, axis_values=item_axis_values) return item_axis_policy.context_text(item_text, axis_values=item_axis_values)
@@ -46,6 +49,20 @@ def _oral_direction(text: str) -> tuple[bool, bool]:
return woman_gives, man_gives return woman_gives, man_gives
def _list_values(value: Any) -> list[str]:
if isinstance(value, list):
return [str(item) for item in value if str(item).strip()]
if isinstance(value, str) and value.strip():
return [part.strip().strip("[]'\" ") for part in value.split(",") if part.strip().strip("[]'\" ")]
return []
def _has_krea2_variant(axis_values: dict[str, Any] | None, key: str) -> bool:
if not isinstance(axis_values, dict):
return False
return key in _list_values(axis_values.get("krea2_variant_keys"))
def build_oral_role_graph( def build_oral_role_graph(
woman: str, woman: str,
man: str, man: str,
@@ -58,6 +75,16 @@ def build_oral_role_graph(
man_is_pov = man in set(pov_labels or []) man_is_pov = man in set(pov_labels or [])
woman_gives, man_gives = _oral_direction(text) woman_gives, man_gives = _oral_direction(text)
if _has_krea2_variant(item_axis_values, SITTING_ORAL_VARIANT):
if man_is_pov:
return (
f"The viewer reclines with open thighs in an upright sitting oral frame while {woman} sits low between his thighs; "
f"{woman}'s face lowers close to the centered shaft tip, her mouth on the viewer's penis, with both hands low at the base."
)
return (
f"{man} reclines with open thighs in an upright sitting oral frame while {woman} sits low between his thighs; "
f"{woman}'s face lowers close to the centered shaft tip, her mouth on his penis, with both hands low at the base."
)
if "sixty-nine" in position_text or ("sixty-nine" in text and not position_text): if "sixty-nine" in position_text or ("sixty-nine" in text and not position_text):
if man_is_pov: if man_is_pov:
return ( return (
+2 -2
View File
@@ -6,7 +6,7 @@ from typing import Any
PLACEHOLDER_VALUES = {"", "any", "auto", "random", "none", "null"} PLACEHOLDER_VALUES = {"", "any", "auto", "random", "none", "null"}
PREFERRED_VALUE_KEYS = ("text", "prompt", "template", "value", "name") PREFERRED_VALUE_KEYS = ("text", "prompt", "template", "value", "name")
METADATA_AXIS_KEYS = {"action_family", "position_family", "position_key", "position_keys"} METADATA_AXIS_KEYS = {"action_family", "position_family", "position_key", "position_keys", "krea2_variant_keys"}
ACTION_CONTEXT_PRIORITY = ( ACTION_CONTEXT_PRIORITY = (
"position", "position",
"body_position", "body_position",
@@ -110,7 +110,7 @@ def action_context_text(axis_values: Any) -> str:
def context_text(*parts: Any, axis_values: Any = None) -> str: def context_text(*parts: Any, axis_values: Any = None) -> str:
text_parts = [clean_text(part) for part in parts if clean_text(part)] text_parts = [clean_text(part) for part in parts if clean_text(part)]
text_parts.extend(axis_value_texts(axis_values)) text_parts.extend(axis_value_texts(axis_values, skip_keys=METADATA_AXIS_KEYS))
return " ".join(part.lower() for part in text_parts if part) return " ".join(part.lower() for part in text_parts if part)
+14 -2
View File
@@ -6,6 +6,14 @@ from typing import Any, Callable
MOUTH_EXPRESSION_TERMS = ("mouth", "oral", "tongue", "lips", "gagging", "saliva", "drool") MOUTH_EXPRESSION_TERMS = ("mouth", "oral", "tongue", "lips", "gagging", "saliva", "drool")
TOP_VIEW_ORAL_VARIANT = "pov_blowjob_top_down_vertical_shaft" TOP_VIEW_ORAL_VARIANT = "pov_blowjob_top_down_vertical_shaft"
ORAL_CONTACT_VARIANTS = frozenset(
(
TOP_VIEW_ORAL_VARIANT,
"pov_blowjob_side_profile_oral",
"pov_blowjob_laying_frontal_oral",
"pov_blowjob_sitting_upright_oral",
)
)
@dataclass(frozen=True) @dataclass(frozen=True)
@@ -98,8 +106,12 @@ def _has_krea2_variant(row: dict[str, Any], key: str) -> bool:
return key in _krea2_variant_keys(row) return key in _krea2_variant_keys(row)
def _has_krea2_oral_contact_variant(row: dict[str, Any]) -> bool:
return any(key in ORAL_CONTACT_VARIANTS for key in _krea2_variant_keys(row))
def _filter_expression_for_krea2_variant(row: dict[str, Any], expression: Any) -> Any: def _filter_expression_for_krea2_variant(row: dict[str, Any], expression: Any) -> Any:
if not _has_krea2_variant(row, TOP_VIEW_ORAL_VARIANT): if not _has_krea2_oral_contact_variant(row):
return expression return expression
clauses = [clause.strip() for clause in str(expression or "").split(";") if clause.strip()] clauses = [clause.strip() for clause in str(expression or "").split(";") if clause.strip()]
if not clauses: if not clauses:
@@ -114,7 +126,7 @@ def _filter_expression_for_krea2_variant(row: dict[str, Any], expression: Any) -
def _filter_camera_scene_for_krea2_variant(row: dict[str, Any], camera_scene: Any) -> str: def _filter_camera_scene_for_krea2_variant(row: dict[str, Any], camera_scene: Any) -> str:
text = str(camera_scene or "") text = str(camera_scene or "")
if _has_krea2_variant(row, TOP_VIEW_ORAL_VARIANT) and "eye-level" in text.lower(): if _has_krea2_oral_contact_variant(row) and "eye-level" in text.lower():
return "" return ""
return text return text
+14 -2
View File
@@ -6,6 +6,14 @@ from typing import Any, Callable
MOUTH_EXPRESSION_TERMS = ("mouth", "oral", "tongue", "lips", "gagging", "saliva", "drool") MOUTH_EXPRESSION_TERMS = ("mouth", "oral", "tongue", "lips", "gagging", "saliva", "drool")
TOP_VIEW_ORAL_VARIANT = "pov_blowjob_top_down_vertical_shaft" TOP_VIEW_ORAL_VARIANT = "pov_blowjob_top_down_vertical_shaft"
ORAL_CONTACT_VARIANTS = frozenset(
(
TOP_VIEW_ORAL_VARIANT,
"pov_blowjob_side_profile_oral",
"pov_blowjob_laying_frontal_oral",
"pov_blowjob_sitting_upright_oral",
)
)
@dataclass(frozen=True) @dataclass(frozen=True)
@@ -76,8 +84,12 @@ def _has_krea2_variant(row: dict[str, Any], key: str) -> bool:
return key in _krea2_variant_keys(row) return key in _krea2_variant_keys(row)
def _has_krea2_oral_contact_variant(row: dict[str, Any]) -> bool:
return any(key in ORAL_CONTACT_VARIANTS for key in _krea2_variant_keys(row))
def _filter_expression_for_krea2_variant(row: dict[str, Any], expression: Any) -> Any: def _filter_expression_for_krea2_variant(row: dict[str, Any], expression: Any) -> Any:
if not _has_krea2_variant(row, TOP_VIEW_ORAL_VARIANT): if not _has_krea2_oral_contact_variant(row):
return expression return expression
clauses = [clause.strip() for clause in str(expression or "").split(";") if clause.strip()] clauses = [clause.strip() for clause in str(expression or "").split(";") if clause.strip()]
if not clauses: if not clauses:
@@ -92,7 +104,7 @@ def _filter_expression_for_krea2_variant(row: dict[str, Any], expression: Any) -
def _filter_camera_scene_for_krea2_variant(row: dict[str, Any], camera_scene: Any) -> str: def _filter_camera_scene_for_krea2_variant(row: dict[str, Any], camera_scene: Any) -> str:
text = str(camera_scene or "") text = str(camera_scene or "")
if _has_krea2_variant(row, TOP_VIEW_ORAL_VARIANT) and "eye-level" in text.lower(): if _has_krea2_oral_contact_variant(row) and "eye-level" in text.lower():
return "" return ""
return text return text
+24
View File
@@ -27,6 +27,9 @@ except ImportError: # Allows local smoke tests with `python -c`.
from krea_detail import limit_detail_for_density from krea_detail import limit_detail_for_density
SITTING_ORAL_VARIANT = "pov_blowjob_sitting_upright_oral"
def _clean(value: Any) -> str: def _clean(value: Any) -> str:
text = "" if value is None else str(value) text = "" if value is None else str(value)
text = text.replace("\n", " ") text = text.replace("\n", " ")
@@ -35,6 +38,20 @@ def _clean(value: Any) -> str:
return text return text
def _list_values(value: Any) -> list[str]:
if isinstance(value, list):
return [str(item) for item in value if str(item).strip()]
if isinstance(value, str) and value.strip():
return [part.strip().strip("[]'\" ") for part in value.split(",") if part.strip().strip("[]'\" ")]
return []
def _has_krea2_variant(axis_values: Any, key: str) -> bool:
if not isinstance(axis_values, dict):
return False
return key in _list_values(axis_values.get("krea2_variant_keys"))
def pov_ejaculation_target(context: str) -> str: def pov_ejaculation_target(context: str) -> str:
if any( if any(
token in context token in context
@@ -384,6 +401,13 @@ def pov_hardcore_pose_sentence(
if is_oral_text(context, action_lower) and not has_penetrative_context: if is_oral_text(context, action_lower) and not has_penetrative_context:
woman_gives, man_gives = oral_direction() woman_gives, man_gives = oral_direction()
if _has_krea2_variant(axis_values, SITTING_ORAL_VARIANT):
return oral_sentence(
"POV upright sitting oral position: the viewer reclines with open thighs forming the lower V-frame and his lower abdomen anchoring the near edge; "
"the woman sits low between his open thighs with hips between his knees, torso upright behind the action, shoulders square to the camera, and face lowered close to the exact center contact point; "
"the vertical shaft rises from the exact lower center between the viewer thighs, her open mouth covers the tip at the centerline, lips wrapped around the glans, and mouth-to-shaft contact is the nearest facial detail; "
"both hands stay low at the base directly below her mouth, fingers wrapped around the shaft, while her eyes, face, shoulders, torso, hands, shaft, and the viewer thigh frame remain readable in one first-person seated frame"
)
if "sixty-nine" in position_context: if "sixty-nine" in position_context:
return oral_sentence( return oral_sentence(
"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; " "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; "
+5 -6
View File
@@ -2363,6 +2363,10 @@ def _build_custom_row(
item_text = category_route.item_text item_text = category_route.item_text
item_name = category_route.item_name item_name = category_route.item_name
item_axis_values = dict(category_route.item_axis_values) item_axis_values = dict(category_route.item_axis_values)
item_axis_values = _axis_values_with_krea2_variant_keys(
item_axis_values,
parsed_hardcore_position_config,
)
item_template_metadata = dict(category_route.item_template_metadata) item_template_metadata = dict(category_route.item_template_metadata)
item_formatter_hints = dict(category_route.formatter_hints) item_formatter_hints = dict(category_route.formatter_hints)
is_pose_category = category_route.is_pose_category is_pose_category = category_route.is_pose_category
@@ -2471,11 +2475,6 @@ def _build_custom_row(
action_family = action_route.action_family action_family = action_route.action_family
text_fields = _row_text_fields(category, subcategory, item, style_config) text_fields = _row_text_fields(category, subcategory, item, style_config)
formatter_axis_values = _axis_values_with_krea2_variant_keys(
item_axis_values,
parsed_hardcore_position_config,
)
assembly_request = row_assembly_policy.CustomRowAssemblyRequest( assembly_request = row_assembly_policy.CustomRowAssemblyRequest(
row_number=row_number, row_number=row_number,
start_index=start_index, start_index=start_index,
@@ -2486,7 +2485,7 @@ def _build_custom_row(
subject_type=subject_type, subject_type=subject_type,
item_text=item_text, item_text=item_text,
item_name=item_name, item_name=item_name,
item_axis_values=formatter_axis_values, item_axis_values=item_axis_values,
item_template_metadata=item_template_metadata, item_template_metadata=item_template_metadata,
formatter_hints=item_formatter_hints, formatter_hints=item_formatter_hints,
item_label=text_fields.item_label, item_label=text_fields.item_label,
+92
View File
@@ -8336,6 +8336,80 @@ def smoke_pov_oral_position_routes() -> None:
_expect("eye-level shot" not in top_prompt, f"Top-view variant prompt kept contradictory eye-level camera text: {top_prompt}") _expect("eye-level shot" not in top_prompt, f"Top-view variant prompt kept contradictory eye-level camera text: {top_prompt}")
_expect("tongue extended toward genitals" not in top_prompt, f"Top-view variant prompt kept contradictory tongue-extension expression: {top_prompt}") _expect("tongue extended toward genitals" not in top_prompt, f"Top-view variant prompt kept contradictory tongue-extension expression: {top_prompt}")
sitting_variant_config = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPKrea2POVOralFilter"]().build(
"replace",
"",
include_blowjob_sitting_upright_oral=True,
)[0]
sitting_variant_pair = pb.build_insta_of_pair(
row_number=1,
start_index=1,
seed=3831,
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=sitting_variant_config,
location_config=_coworking_location_config(),
hardcore_camera_config=_orbit_camera(
horizontal_angle=45,
vertical_angle=0,
zoom=7.5,
subject_focus="action",
),
)
_expect_pair(sitting_variant_pair, "pov_oral_sitting_variant_filter")
sitting_variant_row = sitting_variant_pair.get("hardcore_row") or {}
sitting_variant_config_row = sitting_variant_row.get("hardcore_position_config") or {}
_expect(
sitting_variant_config_row.get("krea2_variant_keys") == ["pov_blowjob_sitting_upright_oral"],
"Sitting oral POV filter lost exact variant key in row metadata",
)
_expect(
"blowjob_sitting" in (sitting_variant_config_row.get("positions") or []),
"Sitting oral POV filter lost exact sitting route position in row config",
)
sitting_axis_values = sitting_variant_row.get("item_axis_values") or {}
_expect(
sitting_axis_values.get("krea2_variant_keys") == ["pov_blowjob_sitting_upright_oral"],
"Sitting oral POV filter exact variant key did not reach formatter axis metadata",
)
sitting_variant_krea = krea_formatter.format_krea2_prompt("", metadata_json=_json(sitting_variant_pair), target="hardcore")
sitting_variant_prompt = _expect_text(
"pov_oral_sitting_variant_filter.krea_prompt",
sitting_variant_krea.get("krea_prompt"),
60,
).lower()
for term in (
"pov upright sitting oral position",
"woman sits low between his open thighs",
"face lowered close to the exact center contact point",
"open mouth covers the tip",
"hands stay low at the base",
):
_expect(term in sitting_variant_prompt, f"Sitting oral variant prompt missing {term!r}: {sitting_variant_prompt}")
_expect(
"pov open-thigh cunnilingus position" not in sitting_variant_prompt,
f"Sitting oral variant drifted into generic cunnilingus route: {sitting_variant_prompt}",
)
_expect(
"viewer kneels between her legs" not in sitting_variant_prompt,
f"Sitting oral variant kept generic kneeling-between-legs route: {sitting_variant_prompt}",
)
_expect(
"tongue extended toward genitals" not in sitting_variant_prompt,
f"Sitting oral variant kept contradictory tongue-extension expression: {sitting_variant_prompt}",
)
_expect("eye-level shot" not in sitting_variant_prompt, f"Sitting oral variant kept contradictory eye-level camera text: {sitting_variant_prompt}")
side_body_item = "side-lying oral position while blowjob with lips wrapped around the viewer's penis" side_body_item = "side-lying oral position while blowjob with lips wrapped around the viewer's penis"
side_body_axis = {"position": "side-lying oral position"} side_body_axis = {"position": "side-lying oral position"}
side_body_role_graph = hardcore_role_oral.build_oral_role_graph( side_body_role_graph = hardcore_role_oral.build_oral_role_graph(
@@ -11009,6 +11083,24 @@ def smoke_node_hardcore_position_registration() -> None:
) )
_expect("variants=pov_blowjob_top_down_vertical_shaft" in top_summary, "POV Oral Filter summary lost top-view variant key") _expect("variants=pov_blowjob_top_down_vertical_shaft" in top_summary, "POV Oral Filter summary lost top-view variant key")
sitting_config, sitting_keys, sitting_positions, _sitting_notes, sitting_summary, _sitting_variants_json = oral_filter().build(
"replace",
"",
include_blowjob_sitting_upright_oral=True,
)
parsed_sitting_config = json.loads(sitting_config)
_expect(sitting_keys == "pov_blowjob_sitting_upright_oral", "POV Oral Filter returned wrong exact sitting oral variant key")
_expect(
parsed_sitting_config.get("krea2_variant_keys") == ["pov_blowjob_sitting_upright_oral"],
"POV Oral Filter lost exact sitting oral variant metadata",
)
_expect(
"blowjob_sitting" in (parsed_sitting_config.get("positions") or []),
"POV Oral Filter dropped sitting oral route position from config",
)
_expect("blowjob_sitting" in sitting_positions.split(","), "POV Oral Filter dropped sitting oral route position summary")
_expect("variants=pov_blowjob_sitting_upright_oral" in sitting_summary, "POV Oral Filter summary lost sitting oral variant key")
doggy_config, doggy_keys, doggy_positions, doggy_notes, doggy_summary, doggy_variants_json = penetration_filter().build( doggy_config, doggy_keys, doggy_positions, doggy_notes, doggy_summary, doggy_variants_json = penetration_filter().build(
"replace", "replace",
"", "",