From 9ca2320df287313bc20a60f42e542d87ee8533a1 Mon Sep 17 00:00:00 2001 From: Ethanfel Date: Sat, 27 Jun 2026 03:15:49 +0200 Subject: [PATCH] Move pair detail density policy --- docs/prompt-architecture-improvement-plan.md | 5 +++-- docs/prompt-pool-routing-map.md | 6 +++--- pair_options.py | 20 ++++++++++++++++++-- prompt_builder.py | 10 +++------- tools/prompt_smoke.py | 20 ++++++++++++++++++++ 5 files changed, 47 insertions(+), 14 deletions(-) diff --git a/docs/prompt-architecture-improvement-plan.md b/docs/prompt-architecture-improvement-plan.md index c1a3b80..c798ab5 100644 --- a/docs/prompt-architecture-improvement-plan.md +++ b/docs/prompt-architecture-improvement-plan.md @@ -214,8 +214,9 @@ Already isolated: - Insta/OF option normalization, softcore category/outfit/pose pools, partner outfit pools, clothing-continuity labels, negatives, and hardcore cast count - policy live in `pair_options.py`; `prompt_builder.py` keeps public delegate - wrappers for existing nodes and tests. + policy, plus hardcore detail-density directive text, live in + `pair_options.py`; `prompt_builder.py` keeps public delegate wrappers for + existing nodes and tests. - soft/hard row creation lives in `pair_rows.py`, including softcore expression override resolution, Woman A slot context application, soft outfit/pose overrides, POV row fields, and hardcore row creation. diff --git a/docs/prompt-pool-routing-map.md b/docs/prompt-pool-routing-map.md index 84d0cec..2053260 100644 --- a/docs/prompt-pool-routing-map.md +++ b/docs/prompt-pool-routing-map.md @@ -79,7 +79,7 @@ Core helper ownership: | `location_config.py` | Location/composition preset schemas, themed location packs, custom location/composition parsing, pool merge behavior, and location/composition config parsing. | | `row_location.py` | Built-in row location/composition config application, deterministic scene/composition choice, source metadata, and legacy prompt/caption rewrites. | | `hardcore_position_config.py` | Hardcore position/action-filter choices, selected-position normalization, config JSON builders/parsers, focus-policy toggles, subcategory allow-list policy, position-key detection, and category/template/axis filtering. | -| `pair_options.py` | Insta/OF option schema/defaults, softcore category/outfit/pose pools, partner outfit pools, clothing-continuity labels, negatives, and hardcore cast count policy. | +| `pair_options.py` | Insta/OF option schema/defaults, softcore category/outfit/pose pools, partner outfit pools, clothing-continuity labels, negatives, hardcore cast count policy, and hardcore detail-density directives. | | `pair_rows.py` | Insta/OF soft/hard row creation, softcore expression override resolution, Woman A slot context application, soft outfit/pose overrides, and POV row fields. | | `pair_camera.py` | Insta/OF soft/hard camera route resolution, same-as-softcore camera mode, camera-detail override, camera-aware composition mutation, POV camera suppression, and synchronized row/root camera metadata. | | `pair_clothing.py` | Insta/OF hardcore clothing continuity, action-aware body-access flags, conflicting outfit-piece cleanup, default visible-men clothing, and final root clothing-state assembly. | @@ -261,7 +261,7 @@ This table is the first stop when the selected content is wrong. | `sexual_poses.json` foreplay/interaction/manual/oral/outercourse/penetration/etc. | Hardcore action and porn-scene interaction templates, role graphs, axis values, hardcore pool references | `pose` for pose-content route, also `role`; sometimes `content` aliases matter | High because Krea2 rewrites action and POV position text | | `location_pools.json` | Reusable scene pools and legacy scene extensions | `scene` | Medium when a camera-aware adapter changes scene/composition wording | | `expression_composition_pools.json` | Reusable expressions and framing/composition pools | `expression`, `composition` | Medium because formatter may label or suppress expressions | -| `pair_options.py` | Insta/OF option defaults, softcore level-to-category mapping, creator outfit/pose pools, partner outfit pools, negatives, and hard cast count policy | Options node plus `content`/`pose` axes inside pair route | Medium because pair route pools must remain consistent with Krea/SDXL pair formatting | +| `pair_options.py` | Insta/OF option defaults, softcore level-to-category mapping, creator outfit/pose pools, partner outfit pools, negatives, hard cast count policy, and hardcore detail-density directives | Options node plus `content`/`pose` axes inside pair route | Medium because pair route pools must remain consistent with Krea/SDXL pair formatting | | `generate_prompt_batches.py` legacy pools | Built-in generator clothing, pose, expression, scene, composition lists | Main row seed plus axis config through legacy adapter | Medium because legacy prompt format is field-label heavy | When adding a new pool, choose JSON when the change is pure selectable wording. @@ -503,7 +503,7 @@ plain prompt text. When debugging, inspect these fields before editing pools. | `character_hardcore_clothing` | Character slots | Krea pair branch | Explicit per-character hardcore clothing state. | | `default_man_hardcore_clothing` | Pair fallback | Krea pair branch | Auto clothing for visible men without configured clothing. | | `hardcore_clothing_state` | Pair clothing continuity | Krea/SDXL pair branch | Final hard clothing/body exposure sentence before Krea cleanup. | -| `hardcore_detail_density` | Insta/OF options | Krea hardcore action rewrite | Controls compact/balanced/dense action detail. | +| `hardcore_detail_density` | Insta/OF options via `pair_options.py` | Krea hardcore action rewrite | Controls compact/balanced/dense action detail directives. | | `softcore_camera_config`, `hardcore_camera_config` | Pair camera route | Krea/SDXL pair branch | Separate camera configs after option mode resolution. | | `softcore_camera_directive`, `hardcore_camera_directive` | Pair camera route | Krea pair branch | Separate plain camera sentences, suppressed for POV. | | `softcore_camera_scene_directive`, `hardcore_camera_scene_directive` | Scene-camera adapter | Krea/Naturalizer pair branch | Separate location-aware camera layout text. | diff --git a/pair_options.py b/pair_options.py index be95403..c261f48 100644 --- a/pair_options.py +++ b/pair_options.py @@ -32,6 +32,14 @@ INSTA_OF_HARDCORE_CLOTHING_CONTINUITY = { "explicit_nude": "Woman A's body is fully exposed, bare skin unobstructed", } +HARDCORE_DETAIL_DENSITY_CHOICES = ["compact", "balanced", "dense"] + +HARDCORE_DETAIL_DIRECTIVES = { + "compact": "Use one compact position-first sexual action sentence; avoid repeated aftermath wording. ", + "balanced": "", + "dense": "Use dense but coherent motion, contact, and aftermath detail while keeping one readable body position. ", +} + INSTA_OF_NEGATIVE = ( "minors, childlike appearance, teen, underage, schoolgirl, non-consensual, coercion, rape, " "violence, injury, blood, gore, incest, bestiality, watermark, logo, readable username, social media UI" @@ -218,6 +226,14 @@ def character_softcore_outfit_values(source: str, custom_outfits: str = "") -> l return [] +def hardcore_detail_density_choices() -> list[str]: + return list(HARDCORE_DETAIL_DENSITY_CHOICES) + + +def hardcore_detail_directive(density: Any) -> str: + return HARDCORE_DETAIL_DIRECTIVES.get(str(density or "balanced"), "") + + def character_hardcore_clothing_values(state: str, custom_clothing: str = "") -> list[str]: state = str(state or "no_change").strip() if state == "fully_nude": @@ -251,7 +267,7 @@ def build_insta_of_options_json( softcore_expression_enabled: bool = True, hardcore_expression_enabled: bool = True, hardcore_detail_density: str = "balanced", - hardcore_detail_density_choices: list[str] | tuple[str, ...] = ("compact", "balanced", "dense"), + hardcore_detail_density_choices: list[str] | tuple[str, ...] = tuple(HARDCORE_DETAIL_DENSITY_CHOICES), ) -> str: hardcore_detail_density = ( hardcore_detail_density if hardcore_detail_density in hardcore_detail_density_choices else "balanced" @@ -286,7 +302,7 @@ def parse_insta_of_options( *, camera_mode_choices: dict[str, str] | list[str] | tuple[str, ...], camera_detail_choices: list[str] | tuple[str, ...], - hardcore_detail_density_choices: list[str] | tuple[str, ...], + hardcore_detail_density_choices: list[str] | tuple[str, ...] = tuple(HARDCORE_DETAIL_DENSITY_CHOICES), ) -> dict[str, Any]: defaults = { "softcore_cast": "solo", diff --git a/prompt_builder.py b/prompt_builder.py index 87ade41..36b9356 100644 --- a/prompt_builder.py +++ b/prompt_builder.py @@ -132,7 +132,7 @@ CHARACTER_HAIR_STYLE_CHOICES = character_policy.CHARACTER_HAIR_STYLE_CHOICES CHARACTER_EYE_COLOR_CHOICES = character_policy.CHARACTER_EYE_COLOR_CHOICES CAMERA_DETAIL_CHOICES = camera_policy.CAMERA_DETAIL_CHOICES -HARDCORE_DETAIL_DENSITY_CHOICES = ["compact", "balanced", "dense"] +HARDCORE_DETAIL_DENSITY_CHOICES = pair_options.HARDCORE_DETAIL_DENSITY_CHOICES HARDCORE_POSITION_FAMILY_CHOICES = hardcore_position_policy.HARDCORE_POSITION_FAMILY_CHOICES HARDCORE_POSITION_FOCUS_CHOICES = hardcore_position_policy.HARDCORE_POSITION_FOCUS_CHOICES HARDCORE_POSITION_KEY_CHOICES = hardcore_position_policy.HARDCORE_POSITION_KEY_CHOICES @@ -1244,7 +1244,7 @@ def camera_detail_choices() -> list[str]: def hardcore_detail_density_choices() -> list[str]: - return list(HARDCORE_DETAIL_DENSITY_CHOICES) + return pair_options.hardcore_detail_density_choices() def hardcore_position_family_choices() -> list[str]: @@ -4279,11 +4279,7 @@ def build_insta_of_pair( hard_row["source_scene_text"] = hard_row.get("source_scene_text") or hard_row.get("scene_text", "") hard_row["scene_text"] = hard_scene hard_detail_density = options["hardcore_detail_density"] - hard_detail_directive = { - "compact": "Use one compact position-first sexual action sentence; avoid repeated aftermath wording. ", - "balanced": "", - "dense": "Use dense but coherent motion, contact, and aftermath detail while keeping one readable body position. ", - }[hard_detail_density] + hard_detail_directive = pair_options.hardcore_detail_directive(hard_detail_density) pov_directive = _pov_prompt_directive(pov_character_labels) soft_descriptor_sentence = cast_context["soft_descriptor_sentence"] diff --git a/tools/prompt_smoke.py b/tools/prompt_smoke.py index 9304a29..7c4c764 100644 --- a/tools/prompt_smoke.py +++ b/tools/prompt_smoke.py @@ -1597,6 +1597,26 @@ def smoke_pair_options_policy() -> None: pb.INSTA_OF_SOFTCORE_OUTFITS is pb.pair_options.INSTA_OF_SOFTCORE_OUTFITS, "prompt_builder should delegate Insta/OF softcore outfit policy to pair_options", ) + _expect( + pb.HARDCORE_DETAIL_DENSITY_CHOICES is pb.pair_options.HARDCORE_DETAIL_DENSITY_CHOICES, + "prompt_builder should delegate hardcore detail density choices to pair_options", + ) + _expect( + pb.pair_options.hardcore_detail_directive("compact").startswith("Use one compact"), + "compact hardcore detail density should have a compact directive", + ) + _expect( + pb.pair_options.hardcore_detail_directive("dense").startswith("Use dense"), + "dense hardcore detail density should have a dense directive", + ) + _expect( + pb.pair_options.hardcore_detail_directive("balanced") == "", + "balanced hardcore detail density should not add a directive", + ) + _expect( + pb.pair_options.hardcore_detail_directive("bad") == "", + "invalid hardcore detail density directive should be empty", + ) options = json.loads( pb.build_insta_of_options_json( softcore_expression_enabled="false",