Pin POV outercourse position routing
This commit is contained in:
@@ -730,6 +730,8 @@ pair metadata through the core Python APIs, then verifies:
|
|||||||
- Krea POV penetration routes keep first-person position anchors, suppress
|
- Krea POV penetration routes keep first-person position anchors, suppress
|
||||||
normal camera text, and preserve composition punctuation before the style
|
normal camera text, and preserve composition punctuation before the style
|
||||||
suffix;
|
suffix;
|
||||||
|
- POV outercourse routes keep constrained boobjob, testicle-sucking,
|
||||||
|
penis-licking, handjob, and footjob 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
|
||||||
|
|||||||
+10
-10
@@ -333,16 +333,6 @@ def build_hardcore_role_graph(
|
|||||||
f"{woman} bends forward between {man}'s open thighs, head low under {man}'s penis with her face directly under the penis, "
|
f"{woman} bends forward between {man}'s open thighs, head low under {man}'s penis with her face directly under the penis, "
|
||||||
f"tongue running along the underside from the penis shaft to the glans while one hand steadies the base of the penis."
|
f"tongue running along the underside from the penis shaft to the glans while one hand steadies the base of the penis."
|
||||||
)
|
)
|
||||||
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 between the POV viewer's open thighs with her torso leaning forward and face visible behind the penis shaft, "
|
|
||||||
"one hand wrapped around the POV viewer's penis shaft while the other hand steadies the base of the penis as she strokes toward the glans."
|
|
||||||
)
|
|
||||||
return (
|
|
||||||
f"{woman} kneels between {man}'s open thighs with her torso leaning forward and face visible behind the penis shaft, "
|
|
||||||
f"one hand wrapped around {man}'s penis shaft while the other hand steadies the base of the penis 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 (
|
||||||
@@ -353,6 +343,16 @@ def build_hardcore_role_graph(
|
|||||||
f"{man} reclines with hips forward while {woman} faces him with her hips back and both knees bent open, "
|
f"{man} reclines with hips forward while {woman} faces him with her hips back and both knees bent open, "
|
||||||
f"wrapping both soles around {man}'s penis shaft while the contact stays centered."
|
f"wrapping both soles around {man}'s penis shaft while the contact stays centered."
|
||||||
)
|
)
|
||||||
|
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 between the POV viewer's open thighs with her torso leaning forward and face visible behind the penis shaft, "
|
||||||
|
"one hand wrapped around the POV viewer's penis shaft while the other hand steadies the base of the penis as she strokes toward the glans."
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
f"{woman} kneels between {man}'s open thighs with her torso leaning forward and face visible behind the penis shaft, "
|
||||||
|
f"one hand wrapped around {man}'s penis shaft while the other hand steadies the base of the penis as she strokes toward the glans."
|
||||||
|
)
|
||||||
if man_is_pov:
|
if man_is_pov:
|
||||||
return (
|
return (
|
||||||
f"{woman} kneels close to the POV viewer's hips and keeps the POV viewer's penis centered in clear non-penetrative contact, "
|
f"{woman} kneels close to the POV viewer's hips and keeps the POV viewer's penis centered in clear non-penetrative contact, "
|
||||||
|
|||||||
+10
-2
@@ -1246,13 +1246,21 @@ def _outercourse_axis_values_for_position(values: list[Any], position: str, axis
|
|||||||
return filtered(by_axis.get(axis_name, ("hand", "penis", "shaft")))
|
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"),
|
||||||
"hand_detail": ("ankles", "thighs"),
|
"hand_detail": ("ankles", "thighs"),
|
||||||
"texture_detail": ("toes", "soles", "pressure"),
|
"texture_detail": ("toes", "soles", "pressure"),
|
||||||
"visibility": ("feet", "soles"),
|
"visibility": ("feet", "soles"),
|
||||||
"body_contact": ("legs", "knees", "body angled"),
|
"body_contact": ("legs", "knees", "body angled"),
|
||||||
}
|
}
|
||||||
return filtered(by_axis.get(axis_name, ("feet", "soles", "toes")))
|
excluded_by_axis = {
|
||||||
|
"contact_detail": ("hand", "finger", "palm", "balls", "tongue", "breast"),
|
||||||
|
"texture_detail": ("fingers", "tongue", "breast"),
|
||||||
|
"visibility": ("hand", "balls", "breast"),
|
||||||
|
}
|
||||||
|
return filtered(
|
||||||
|
by_axis.get(axis_name, ("feet", "soles", "toes")),
|
||||||
|
excluded_by_axis.get(axis_name, ()),
|
||||||
|
)
|
||||||
return values
|
return values
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+92
-2
@@ -163,7 +163,7 @@ def _character_cast(*, pov_man: bool = False) -> str:
|
|||||||
)["character_cast"]
|
)["character_cast"]
|
||||||
|
|
||||||
|
|
||||||
def _action_filter(focus: str) -> str:
|
def _action_filter(focus: str, hardcore_position_config: str | dict[str, Any] | None = "") -> str:
|
||||||
kwargs = {
|
kwargs = {
|
||||||
"allow_toys": False,
|
"allow_toys": False,
|
||||||
"allow_double": False,
|
"allow_double": False,
|
||||||
@@ -176,7 +176,20 @@ def _action_filter(focus: str) -> str:
|
|||||||
"allow_anal": focus in ("anal_only", "keep_pool"),
|
"allow_anal": focus in ("anal_only", "keep_pool"),
|
||||||
"allow_climax": focus in ("climax_only", "keep_pool"),
|
"allow_climax": focus in ("climax_only", "keep_pool"),
|
||||||
}
|
}
|
||||||
return pb.build_hardcore_action_filter_json(focus=focus, **kwargs)
|
return pb.build_hardcore_action_filter_json(
|
||||||
|
hardcore_position_config=hardcore_position_config,
|
||||||
|
focus=focus,
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _position_filter(focus: str, family: str, positions: list[str] | tuple[str, ...] | str) -> str:
|
||||||
|
position_config = pb.build_hardcore_position_pool_json(
|
||||||
|
combine_mode="replace",
|
||||||
|
family=family,
|
||||||
|
selected_positions=positions,
|
||||||
|
)
|
||||||
|
return _action_filter(focus, position_config)
|
||||||
|
|
||||||
|
|
||||||
def _coworking_location_config() -> str:
|
def _coworking_location_config() -> str:
|
||||||
@@ -713,6 +726,82 @@ def smoke_krea_pov_penetration_route() -> None:
|
|||||||
_expect("composition. explicit" in lower, "POV penetration composition sentence should keep punctuation before style suffix")
|
_expect("composition. explicit" in lower, "POV penetration composition sentence should keep punctuation before style suffix")
|
||||||
|
|
||||||
|
|
||||||
|
def smoke_pov_outercourse_position_routes() -> None:
|
||||||
|
cases = [
|
||||||
|
(
|
||||||
|
"pov_outercourse_boobjob",
|
||||||
|
"boobjob",
|
||||||
|
("breasts tightly around", "glans sits just below"),
|
||||||
|
("press her breasts tightly around", "glans just below", "penis shaft"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"pov_outercourse_testicle",
|
||||||
|
"testicle_sucking",
|
||||||
|
("mouth and tongue on the pov viewer's balls", "penis points upward"),
|
||||||
|
("mouth and tongue licking", "balls", "penis points upward"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"pov_outercourse_penis_licking",
|
||||||
|
"penis_licking",
|
||||||
|
("head low under the pov viewer's penis", "tongue running along"),
|
||||||
|
("tongue runs along", "penis shaft", "glans"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"pov_outercourse_handjob",
|
||||||
|
"handjob",
|
||||||
|
("one hand wrapped around the pov viewer's penis", "strokes toward the glans"),
|
||||||
|
("one hand wraps around", "penis shaft", "strokes toward the glans"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"pov_outercourse_footjob",
|
||||||
|
"footjob",
|
||||||
|
("both soles wrapped around the pov viewer's penis", "lower foreground"),
|
||||||
|
("soles wrap around", "penis shaft", "lower foreground"),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
for offset, (name, position_key, role_terms, krea_terms) in enumerate(cases, start=3601):
|
||||||
|
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("outercourse_only", "outercourse", [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") == "outercourse", f"{name} action_family should be outercourse")
|
||||||
|
_expect(hard_row.get("position_family") == "outercourse", f"{name} position_family should be outercourse")
|
||||||
|
_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_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",
|
||||||
@@ -844,6 +933,7 @@ SMOKE_CASES: list[tuple[str, Callable[[], None]]] = [
|
|||||||
("insta_pair_camera_split", smoke_insta_pair_camera_split),
|
("insta_pair_camera_split", smoke_insta_pair_camera_split),
|
||||||
("pov_camera_scene", smoke_pov_camera_scene),
|
("pov_camera_scene", smoke_pov_camera_scene),
|
||||||
("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),
|
||||||
("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),
|
||||||
]
|
]
|
||||||
|
|||||||
Reference in New Issue
Block a user