diff --git a/docs/prompt-architecture-improvement-plan.md b/docs/prompt-architecture-improvement-plan.md index d5c815b..01a94fd 100644 --- a/docs/prompt-architecture-improvement-plan.md +++ b/docs/prompt-architecture-improvement-plan.md @@ -57,7 +57,9 @@ It should only handle route-agnostic cleanup: It must not make semantic decisions such as sexual action positioning, POV geometry, clothing state, or model-specific tag weighting. Those stay in the -route-specific owner. +route-specific owner. It also preserves ordinary words such as `composition` +inside normal sentences; empty field-label cleanup is limited to standalone +labels. Current integration points: @@ -93,6 +95,8 @@ Already isolated: - camera-scene prose and coworking composition adaptation live in `scene_camera_adapters.py`; `prompt_builder.py` still owns camera config parsing and row mutation. +- shared hardcore environment-anchor cleanup normalizes malformed pool joins + such as `on against a wall` before metadata reaches formatter routes. ### Pair / Adapter Layer @@ -248,10 +252,12 @@ Near-term: formatting across built-in rows, hardcore rows, same-cast pairs, and POV pairs. - Cover camera-scene preservation through `tools/prompt_smoke.py` for single - rows, split soft/hard pair cameras, and POV camera-scene routing. Expand it + rows, split soft/hard pair cameras, and POV camera-scene routing. - Cover config-node routing through `tools/prompt_smoke.py` for category, cast, generation profile, seed lock, camera, location theme, and composition config. - Expand it next for close foreplay and POV penetration. +- Cover close foreplay and POV penetration Krea routes so raw labels, invalid + surface grammar, normal third-person camera text, and composition punctuation + drift are caught. Medium-term: @@ -310,8 +316,8 @@ Medium-term: ## Recommended Next Passes -1. Expand `tools/prompt_smoke.py` with close foreplay and POV penetration - fixtures. -2. Split Krea action/POV/clothing helpers into separate modules. -3. Split `__init__.py` node classes by family after behavior is covered by smoke +1. Split Krea action/POV/clothing helpers into separate modules. +2. Split `__init__.py` node classes by family after behavior is covered by smoke checks. +3. Add metadata fields such as `action_family` / `position_family` to reduce + keyword guessing in hardcore filters and formatter dispatch. diff --git a/docs/prompt-pool-routing-map.md b/docs/prompt-pool-routing-map.md index 54f7174..a5b1b33 100644 --- a/docs/prompt-pool-routing-map.md +++ b/docs/prompt-pool-routing-map.md @@ -687,6 +687,11 @@ pair metadata through the core Python APIs, then verifies: collapsing to the same camera phrase; - POV camera-scene directives suppress normal third-person camera text while preserving first-person spatial layout; +- Krea close-interaction routes keep rewritten action wording, avoid raw + builder labels, and catch invalid surface joins such as `on against a wall`; +- Krea POV penetration routes keep first-person position anchors, suppress + normal camera text, and preserve composition punctuation before the style + suffix; - expression-disabled rows do not fall back to generated expression text. ## Editing Cheatsheet diff --git a/krea_formatter.py b/krea_formatter.py index 3ab707b..3c3f10e 100644 --- a/krea_formatter.py +++ b/krea_formatter.py @@ -47,6 +47,7 @@ def _clean(value: Any) -> str: HARDCORE_ENVIRONMENT_ANCHOR_REPLACEMENTS = ( + (r"\bon against a wall\b", "against a wall"), (r"\bstacked bodies on the bed\b", "close body alignment"), (r"\bstacked bodies with close body alignment\b", "close body alignment"), (r"\boverhead tangled-body anal frame\b", "overhead rear-entry anal frame"), diff --git a/prompt_builder.py b/prompt_builder.py index f6be95e..a5f048b 100644 --- a/prompt_builder.py +++ b/prompt_builder.py @@ -966,6 +966,7 @@ def _heuristic_cast_compatible(text: str, women_count: int, men_count: int) -> b HARDCORE_ENVIRONMENT_ANCHOR_REPLACEMENTS = ( + (r"\bon against a wall\b", "against a wall"), (r"\bstacked bodies on the bed\b", "close body alignment"), (r"\bstacked bodies with close body alignment\b", "close body alignment"), (r"\boverhead tangled-body anal frame\b", "overhead rear-entry anal frame"), diff --git a/prompt_hygiene.py b/prompt_hygiene.py index 0f9d1e4..40770b5 100644 --- a/prompt_hygiene.py +++ b/prompt_hygiene.py @@ -51,7 +51,7 @@ def _strip_empty_fields(text: str) -> str: labels = "|".join(re.escape(label) for label in EMPTY_FIELD_LABELS) text = re.sub(rf"\b(?:{labels})\s*:\s*[.,;]", "", text, flags=re.IGNORECASE) text = re.sub(rf"\b(?:{labels}):\s*(?=\.|,|;|$)", "", text, flags=re.IGNORECASE) - text = re.sub(rf"\b(?:{labels})\.(?=\s|$)", "", text, flags=re.IGNORECASE) + text = re.sub(rf"(^|(?<=[.!?])\s+)(?:{labels})\.(?=\s|$)", r"\1", text, flags=re.IGNORECASE) text = re.sub(rf"\b(?:{labels}):\s*(?:none|null|n/a)\b[.,;]?", "", text, flags=re.IGNORECASE) return clean_spacing(text) diff --git a/tools/prompt_smoke.py b/tools/prompt_smoke.py index c149b5e..37ee4bc 100644 --- a/tools/prompt_smoke.py +++ b/tools/prompt_smoke.py @@ -387,6 +387,32 @@ def smoke_hardcore_category_routes() -> None: _expect_formatter_outputs(row, name, target="single") +def smoke_krea_close_foreplay_route() -> None: + row = _prompt_row( + name="krea_close_foreplay_route", + category="Hardcore sexual poses", + subcategory="Foreplay and teasing", + seed=3401, + character_cast=_character_cast(), + women_count=1, + men_count=1, + hardcore_position_config=_action_filter("foreplay_only"), + ) + _expect_custom_row(row, "krea_close_foreplay_route") + krea = krea_formatter.format_krea2_prompt("", metadata_json=_json(row), target="single") + prompt = _expect_text("krea_close_foreplay_route.krea_prompt", krea.get("krea_prompt"), 40) + lower = prompt.lower() + _expect("metadata" in krea.get("method", ""), "close foreplay route did not use metadata") + _expect("role graph:" not in lower, "close foreplay leaked raw role label") + _expect("foreplay action:" not in lower, "close foreplay leaked raw item label") + _expect("on against" not in lower, "close foreplay kept invalid surface grammar") + _expect( + any(term in lower for term in ("clothing", "hands", "kiss", "bodies press", "body contact")), + "close foreplay lost close-contact action wording", + ) + _expect_formatter_outputs(row, "krea_close_foreplay_route", target="single") + + def _insta_options(**overrides: Any) -> str: options = pb.build_insta_of_options_json( softcore_cast="same_as_hardcore", @@ -553,6 +579,48 @@ def smoke_pov_camera_scene() -> None: _expect("Camera:" not in prompt, "Krea POV prompt should not emit normal third-person camera directive") +def smoke_krea_pov_penetration_route() -> None: + pair = pb.build_insta_of_pair( + row_number=1, + start_index=1, + seed=3411, + 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=_action_filter("penetration_only"), + location_config=_coworking_location_config(), + hardcore_camera_config=_orbit_camera( + horizontal_angle=45, + vertical_angle=0, + zoom=5.5, + subject_focus="action", + ), + ) + _expect_pair(pair, "krea_pov_penetration_route") + hard_row = pair.get("hardcore_row") or {} + _expect("Man A" in (hard_row.get("pov_character_labels") or []), "POV penetration hard row lost Man A POV label") + _expect(not hard_row.get("camera_directive"), "POV penetration should suppress normal camera directive") + krea = krea_formatter.format_krea2_prompt("", metadata_json=_json(pair), target="hardcore") + prompt = _expect_text("krea_pov_penetration_route.krea_prompt", krea.get("krea_prompt"), 60) + lower = prompt.lower() + _expect("metadata" in krea.get("method", ""), "POV penetration route did not use metadata") + _expect("viewer" in lower and "first-person" in lower, "POV penetration lost first-person wording") + _expect("penetrates" in lower or "penetration" in lower, "POV penetration lost penetration action wording") + _expect("woman" in lower and "thigh" in lower, "POV penetration lost body-position anchors") + _expect("camera:" not in prompt, "POV penetration emitted normal third-person camera directive") + _expect("role graph:" not in lower and "sexual scene:" not in lower, "POV penetration leaked raw prompt labels") + _expect("composition. explicit" in lower, "POV penetration composition sentence should keep punctuation before style suffix") + + def smoke_no_expression_fallback() -> None: cast = pb.build_character_slot_json( subject_type="woman", @@ -584,10 +652,12 @@ SMOKE_CASES: list[tuple[str, Callable[[], None]]] = [ ("camera_scene_single", smoke_camera_scene_single), ("config_route_location_theme", smoke_config_route_location_theme), ("hardcore_category_routes", smoke_hardcore_category_routes), + ("krea_close_foreplay_route", smoke_krea_close_foreplay_route), ("insta_pair_same_cast", smoke_insta_pair), ("insta_pair_pov_man", smoke_insta_pair_pov), ("insta_pair_camera_split", smoke_insta_pair_camera_split), ("pov_camera_scene", smoke_pov_camera_scene), + ("krea_pov_penetration_route", smoke_krea_pov_penetration_route), ("expression_disabled", smoke_no_expression_fallback), ]