From 20c69b6feb1b73f0af7078ed73a874eeb6aef4e7 Mon Sep 17 00:00:00 2001 From: Ethanfel Date: Sat, 27 Jun 2026 08:13:05 +0200 Subject: [PATCH] Move pair descriptor policy --- docs/prompt-architecture-improvement-plan.md | 7 ++-- docs/prompt-pool-routing-map.md | 2 +- pair_cast.py | 36 +++++++++++++++++--- prompt_builder.py | 25 ++------------ tools/prompt_smoke.py | 22 ++++++++++++ 5 files changed, 62 insertions(+), 30 deletions(-) diff --git a/docs/prompt-architecture-improvement-plan.md b/docs/prompt-architecture-improvement-plan.md index 44c8964..8334936 100644 --- a/docs/prompt-architecture-improvement-plan.md +++ b/docs/prompt-architecture-improvement-plan.md @@ -228,9 +228,10 @@ Already isolated: - 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. -- pair-level cast/display context lives in `pair_cast.py`, including shared - descriptors, same-cast softcore descriptor text, partner styling, platform and - level labels, softcore cast presence text, and hard cast summary text. +- pair-level cast/display context lives in `pair_cast.py`, including descriptor + prose, shared descriptors, cast-label cleanup, same-cast softcore descriptor + text, partner styling, platform and level labels, softcore cast presence text, + and hard cast summary text. - pair-level camera routing lives in `pair_camera.py`, including soft/hard camera config selection, same-as-softcore mode, camera-detail override, same-room hard scene continuity, camera-aware composition mutation, POV camera diff --git a/docs/prompt-pool-routing-map.md b/docs/prompt-pool-routing-map.md index a713139..9d93b5c 100644 --- a/docs/prompt-pool-routing-map.md +++ b/docs/prompt-pool-routing-map.md @@ -82,7 +82,7 @@ Core helper ownership: | `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, 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_cast.py` | Insta/OF shared descriptors, same-cast softcore descriptor text, partner styling selection, cast-summary wording, platform/level labels, softcore cast presence text, and hard cast summary text. | +| `pair_cast.py` | Insta/OF descriptor prose, shared descriptors, cast-label cleanup, same-cast softcore descriptor text, partner styling selection, cast-summary wording, platform/level labels, softcore cast presence text, and hard cast summary text. | | `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 clothing sentence formatting, body-exposure scene cleanup, hardcore clothing continuity, action-aware body-access flags, conflicting outfit-piece cleanup, configured/default visible-person clothing, and final root clothing-state assembly. | | `pair_output.py` | Insta/OF final pair prompts, trigger preservation, negative prompts, captions, and root pair metadata assembly. | diff --git a/pair_cast.py b/pair_cast.py index 6dc8914..8ab64a3 100644 --- a/pair_cast.py +++ b/pair_cast.py @@ -4,15 +4,16 @@ from typing import Any, Callable try: from . import cast_context as cast_context_policy + from . import character_profile as character_profile_policy from . import pair_clothing from . import pair_options except ImportError: # Allows local smoke tests with top-level imports. import cast_context as cast_context_policy + import character_profile as character_profile_policy import pair_clothing import pair_options -DescriptorFromRow = Callable[[dict[str, Any]], str] CastDescriptors = Callable[..., list[str]] AxisRng = Callable[[dict[str, int], str, int, int], Any] Choose = Callable[[Any, list[str]], str] @@ -23,6 +24,35 @@ def cast_summary_phrase(women_count: int, men_count: int) -> str: return cast_context_policy.cast_summary_phrase(women_count, men_count) +def insta_descriptor_from_row(row: dict[str, Any]) -> str: + return character_profile_policy.descriptor_from_parts( + "woman", + row.get("age_band") or row.get("age"), + row.get("body_phrase"), + row.get("skin"), + row.get("hair"), + row.get("eyes"), + row.get("descriptor_detail"), + ) + + +def insta_descriptor_from_context(context: dict[str, Any]) -> str: + subject = str(context.get("subject") or context.get("subject_type") or "person").strip() + return character_profile_policy.descriptor_from_parts( + subject, + context.get("age"), + context.get("body_phrase"), + context.get("skin"), + context.get("hair"), + context.get("eyes"), + context.get("descriptor_detail"), + ) + + +def prompt_cast_descriptors(text: str) -> str: + return str(text or "").replace("Woman A / primary creator:", "Woman A:") + + def softcore_partner_styling( *, seed_config: dict[str, int], @@ -87,14 +117,12 @@ def resolve_insta_pair_cast_context( platform_styles: dict[str, str], soft_levels: dict[str, str], hardcore_levels: dict[str, str], - descriptor_from_row: DescriptorFromRow, build_cast_descriptors: CastDescriptors, - prompt_cast_descriptors: Callable[[str], str], axis_rng: AxisRng, choose: Choose, slot_softcore_outfit: SlotSoftcoreOutfit, ) -> dict[str, Any]: - descriptor = descriptor_from_row(soft_row) + descriptor = insta_descriptor_from_row(soft_row) cast_descriptors = build_cast_descriptors( descriptor, parsed_seed_config, diff --git a/prompt_builder.py b/prompt_builder.py index 1ae46b4..80449ce 100644 --- a/prompt_builder.py +++ b/prompt_builder.py @@ -3819,28 +3819,11 @@ def _insta_of_hardcore_counts(options: dict[str, Any]) -> tuple[int, int]: def _insta_of_descriptor(row: dict[str, Any]) -> str: - return _descriptor_from_parts( - "woman", - row.get("age_band") or row.get("age"), - row.get("body_phrase"), - row.get("skin"), - row.get("hair"), - row.get("eyes"), - row.get("descriptor_detail"), - ) + return pair_cast.insta_descriptor_from_row(row) def _insta_of_descriptor_from_context(context: dict[str, Any]) -> str: - subject = str(context.get("subject") or context.get("subject_type") or "person").strip() - return _descriptor_from_parts( - subject, - context.get("age"), - context.get("body_phrase"), - context.get("skin"), - context.get("hair"), - context.get("eyes"), - context.get("descriptor_detail"), - ) + return pair_cast.insta_descriptor_from_context(context) def _insta_of_cast_descriptors( @@ -3873,7 +3856,7 @@ def _insta_of_cast_descriptors( def _insta_of_prompt_cast_descriptors(text: str) -> str: - return str(text or "").replace("Woman A / primary creator:", "Woman A:") + return pair_cast.prompt_cast_descriptors(text) def _insta_of_softcore_category(level: str) -> tuple[str, str]: @@ -3990,9 +3973,7 @@ def build_insta_of_pair( platform_styles=INSTA_OF_PLATFORM_STYLES, soft_levels=INSTA_OF_SOFT_LEVELS, hardcore_levels=INSTA_OF_HARDCORE_LEVELS, - descriptor_from_row=_insta_of_descriptor, build_cast_descriptors=_insta_of_cast_descriptors, - prompt_cast_descriptors=_insta_of_prompt_cast_descriptors, axis_rng=_axis_rng, choose=g.choose, slot_softcore_outfit=_slot_softcore_outfit, diff --git a/tools/prompt_smoke.py b/tools/prompt_smoke.py index 2f50755..971cb47 100644 --- a/tools/prompt_smoke.py +++ b/tools/prompt_smoke.py @@ -1710,6 +1710,28 @@ def smoke_pair_options_policy() -> None: pair_cast.cast_summary_phrase(2, 1) == "2 women, 1 man, 3 total adults", "Pair cast summary phrase should live in pair_cast", ) + descriptor_row = { + "age": "25-year-old adult", + "body_phrase": "curvy build", + "skin": "warm skin", + "hair": "dark hair", + "eyes": "brown eyes", + } + _expect( + pb._insta_of_descriptor(descriptor_row) == pair_cast.insta_descriptor_from_row(descriptor_row), + "Prompt builder Insta descriptor should delegate to pair_cast", + ) + _expect( + pair_cast.insta_descriptor_from_context( + {"subject_type": "man", "age": "40-year-old adult", "body_phrase": "stocky figure"} + ) + == "40-year-old adult man, stocky figure", + "Pair cast context descriptor formatting changed", + ) + _expect( + pair_cast.prompt_cast_descriptors("Woman A / primary creator: descriptor") == "Woman A: descriptor", + "Pair cast prompt descriptor label cleanup changed", + ) partner_styling = pair_cast.softcore_partner_styling( seed_config={}, seed=1,