Move cast descriptor entry policy
This commit is contained in:
@@ -229,9 +229,9 @@ Already isolated:
|
|||||||
override resolution, Woman A slot context application, soft outfit/pose
|
override resolution, Woman A slot context application, soft outfit/pose
|
||||||
overrides, POV row fields, and hardcore row creation.
|
overrides, POV row fields, and hardcore row creation.
|
||||||
- pair-level cast/display context lives in `pair_cast.py`, including descriptor
|
- pair-level cast/display context lives in `pair_cast.py`, including descriptor
|
||||||
prose, shared descriptors, cast-label cleanup, same-cast softcore descriptor
|
prose, descriptor-entry assembly, shared descriptors, cast-label cleanup,
|
||||||
text, partner styling, platform and level labels, softcore cast presence text,
|
same-cast softcore descriptor text, partner styling, platform and level
|
||||||
and hard cast summary text.
|
labels, softcore cast presence text, and hard cast summary text.
|
||||||
- pair-level camera routing lives in `pair_camera.py`, including soft/hard
|
- pair-level camera routing lives in `pair_camera.py`, including soft/hard
|
||||||
camera config selection, same-as-softcore mode, camera-detail override,
|
camera config selection, same-as-softcore mode, camera-detail override,
|
||||||
same-room hard scene continuity, camera-aware composition mutation, POV camera
|
same-room hard scene continuity, camera-aware composition mutation, POV camera
|
||||||
|
|||||||
@@ -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. |
|
| `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_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_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 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_cast.py` | Insta/OF descriptor prose, descriptor-entry assembly, 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_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_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. |
|
| `pair_output.py` | Insta/OF final pair prompts, trigger preservation, negative prompts, captions, and root pair metadata assembly. |
|
||||||
@@ -390,7 +390,8 @@ Important behavior:
|
|||||||
Edit targets:
|
Edit targets:
|
||||||
|
|
||||||
- Appearance field generation: `_context_from_character_slot`,
|
- Appearance field generation: `_context_from_character_slot`,
|
||||||
`_character_context_for_label`, `_cast_descriptor_entries`.
|
`_character_context_for_label`; pair cast descriptor entry assembly:
|
||||||
|
`pair_cast.cast_descriptor_entries`.
|
||||||
- Profile save/load: `SxCPCharacterProfileSave`,
|
- Profile save/load: `SxCPCharacterProfileSave`,
|
||||||
`SxCPCharacterProfileLoad`, profile policy in `character_profile.py`, and
|
`SxCPCharacterProfileLoad`, profile policy in `character_profile.py`, and
|
||||||
`web/profile_buttons.js`.
|
`web/profile_buttons.js`.
|
||||||
|
|||||||
+117
-14
@@ -14,9 +14,15 @@ except ImportError: # Allows local smoke tests with top-level imports.
|
|||||||
import pair_options
|
import pair_options
|
||||||
|
|
||||||
|
|
||||||
CastDescriptors = Callable[..., list[str]]
|
|
||||||
AxisRng = Callable[[dict[str, int], str, int, int], Any]
|
AxisRng = Callable[[dict[str, int], str, int, int], Any]
|
||||||
Choose = Callable[[Any, list[str]], str]
|
Choose = Callable[[Any, list[str]], str]
|
||||||
|
CharacterContextForLabel = Callable[
|
||||||
|
[str, dict[str, dict[str, Any]], Any, str, str, bool, bool],
|
||||||
|
tuple[dict[str, Any], dict[str, Any] | None],
|
||||||
|
]
|
||||||
|
CharacterSlotLabelMap = Callable[[list[dict[str, Any]]], dict[str, dict[str, Any]]]
|
||||||
|
ParseCharacterCast = Callable[[str | dict[str, Any] | list[Any] | None], list[dict[str, Any]]]
|
||||||
|
SlotIsPov = Callable[[dict[str, Any] | None], bool]
|
||||||
SlotSoftcoreOutfit = Callable[[dict[str, Any] | None, Any], str]
|
SlotSoftcoreOutfit = Callable[[dict[str, Any] | None, Any], str]
|
||||||
|
|
||||||
|
|
||||||
@@ -53,6 +59,98 @@ def prompt_cast_descriptors(text: str) -> str:
|
|||||||
return str(text or "").replace("Woman A / primary creator:", "Woman A:")
|
return str(text or "").replace("Woman A / primary creator:", "Woman A:")
|
||||||
|
|
||||||
|
|
||||||
|
def cast_descriptor_entries_from_slots(
|
||||||
|
*,
|
||||||
|
seed_config: dict[str, int],
|
||||||
|
seed: int,
|
||||||
|
row_number: int,
|
||||||
|
ethnicity: str,
|
||||||
|
figure: str,
|
||||||
|
no_plus_women: bool,
|
||||||
|
no_black: bool,
|
||||||
|
women_count: int,
|
||||||
|
men_count: int,
|
||||||
|
character_slots: list[dict[str, Any]],
|
||||||
|
character_slot_map: dict[str, dict[str, Any]],
|
||||||
|
primary_descriptor: str = "",
|
||||||
|
axis_rng: AxisRng,
|
||||||
|
character_context_for_label: CharacterContextForLabel,
|
||||||
|
slot_is_pov: SlotIsPov,
|
||||||
|
) -> tuple[list[str], list[dict[str, Any]]]:
|
||||||
|
rng = axis_rng(seed_config, "person", seed, row_number + 997)
|
||||||
|
descriptors: list[str] = []
|
||||||
|
for index in range(max(0, women_count)):
|
||||||
|
label = f"Woman {chr(ord('A') + index)}"
|
||||||
|
if index == 0 and primary_descriptor:
|
||||||
|
descriptors.append(f"Woman A / primary creator: {primary_descriptor}")
|
||||||
|
continue
|
||||||
|
context, _slot = character_context_for_label(
|
||||||
|
label,
|
||||||
|
character_slot_map,
|
||||||
|
rng,
|
||||||
|
ethnicity,
|
||||||
|
figure,
|
||||||
|
no_plus_women,
|
||||||
|
no_black,
|
||||||
|
)
|
||||||
|
descriptors.append(f"{label}: {insta_descriptor_from_context(context)}")
|
||||||
|
for index in range(max(0, men_count)):
|
||||||
|
label = f"Man {chr(ord('A') + index)}"
|
||||||
|
if slot_is_pov(character_slot_map.get(label)):
|
||||||
|
continue
|
||||||
|
context, _slot = character_context_for_label(
|
||||||
|
label,
|
||||||
|
character_slot_map,
|
||||||
|
rng,
|
||||||
|
ethnicity,
|
||||||
|
figure,
|
||||||
|
no_plus_women,
|
||||||
|
no_black,
|
||||||
|
)
|
||||||
|
descriptors.append(f"{label}: {insta_descriptor_from_context(context)}")
|
||||||
|
return descriptors, character_slots
|
||||||
|
|
||||||
|
|
||||||
|
def cast_descriptor_entries(
|
||||||
|
*,
|
||||||
|
seed_config: dict[str, int],
|
||||||
|
seed: int,
|
||||||
|
row_number: int,
|
||||||
|
ethnicity: str,
|
||||||
|
figure: str,
|
||||||
|
no_plus_women: bool,
|
||||||
|
no_black: bool,
|
||||||
|
women_count: int,
|
||||||
|
men_count: int,
|
||||||
|
character_cast: str | dict[str, Any] | list[Any] | None = "",
|
||||||
|
primary_descriptor: str = "",
|
||||||
|
parse_character_cast: ParseCharacterCast,
|
||||||
|
character_slot_label_map: CharacterSlotLabelMap,
|
||||||
|
axis_rng: AxisRng,
|
||||||
|
character_context_for_label: CharacterContextForLabel,
|
||||||
|
slot_is_pov: SlotIsPov,
|
||||||
|
) -> tuple[list[str], list[dict[str, Any]]]:
|
||||||
|
slots = parse_character_cast(character_cast)
|
||||||
|
label_map = character_slot_label_map(slots)
|
||||||
|
return cast_descriptor_entries_from_slots(
|
||||||
|
seed_config=seed_config,
|
||||||
|
seed=seed,
|
||||||
|
row_number=row_number,
|
||||||
|
ethnicity=ethnicity,
|
||||||
|
figure=figure,
|
||||||
|
no_plus_women=no_plus_women,
|
||||||
|
no_black=no_black,
|
||||||
|
women_count=women_count,
|
||||||
|
men_count=men_count,
|
||||||
|
character_slots=slots,
|
||||||
|
character_slot_map=label_map,
|
||||||
|
primary_descriptor=primary_descriptor,
|
||||||
|
axis_rng=axis_rng,
|
||||||
|
character_context_for_label=character_context_for_label,
|
||||||
|
slot_is_pov=slot_is_pov,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def softcore_partner_styling(
|
def softcore_partner_styling(
|
||||||
*,
|
*,
|
||||||
seed_config: dict[str, int],
|
seed_config: dict[str, int],
|
||||||
@@ -117,24 +215,29 @@ def resolve_insta_pair_cast_context(
|
|||||||
platform_styles: dict[str, str],
|
platform_styles: dict[str, str],
|
||||||
soft_levels: dict[str, str],
|
soft_levels: dict[str, str],
|
||||||
hardcore_levels: dict[str, str],
|
hardcore_levels: dict[str, str],
|
||||||
build_cast_descriptors: CastDescriptors,
|
|
||||||
axis_rng: AxisRng,
|
axis_rng: AxisRng,
|
||||||
|
character_context_for_label: CharacterContextForLabel,
|
||||||
|
slot_is_pov: SlotIsPov,
|
||||||
choose: Choose,
|
choose: Choose,
|
||||||
slot_softcore_outfit: SlotSoftcoreOutfit,
|
slot_softcore_outfit: SlotSoftcoreOutfit,
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
descriptor = insta_descriptor_from_row(soft_row)
|
descriptor = insta_descriptor_from_row(soft_row)
|
||||||
cast_descriptors = build_cast_descriptors(
|
cast_descriptors, _descriptor_slots = cast_descriptor_entries_from_slots(
|
||||||
descriptor,
|
seed_config=parsed_seed_config,
|
||||||
parsed_seed_config,
|
seed=seed,
|
||||||
seed,
|
row_number=row_number,
|
||||||
row_number,
|
ethnicity=ethnicity,
|
||||||
ethnicity,
|
figure=figure,
|
||||||
figure,
|
no_plus_women=no_plus_women,
|
||||||
no_plus_women,
|
no_black=no_black,
|
||||||
no_black,
|
women_count=hard_women_count,
|
||||||
hard_women_count,
|
men_count=hard_men_count,
|
||||||
hard_men_count,
|
character_slots=character_slots,
|
||||||
character_slots,
|
character_slot_map=character_slot_map,
|
||||||
|
primary_descriptor=descriptor,
|
||||||
|
axis_rng=axis_rng,
|
||||||
|
character_context_for_label=character_context_for_label,
|
||||||
|
slot_is_pov=slot_is_pov,
|
||||||
)
|
)
|
||||||
cast_descriptor_text = prompt_cast_descriptors("; ".join(cast_descriptors))
|
cast_descriptor_text = prompt_cast_descriptors("; ".join(cast_descriptors))
|
||||||
same_softcore_cast = options["softcore_cast"] == "same_as_hardcore"
|
same_softcore_cast = options["softcore_cast"] == "same_as_hardcore"
|
||||||
|
|||||||
+20
-48
@@ -2621,24 +2621,24 @@ def _cast_descriptor_entries(
|
|||||||
character_cast: str | dict[str, Any] | list[Any] | None = "",
|
character_cast: str | dict[str, Any] | list[Any] | None = "",
|
||||||
primary_descriptor: str = "",
|
primary_descriptor: str = "",
|
||||||
) -> tuple[list[str], list[dict[str, Any]]]:
|
) -> tuple[list[str], list[dict[str, Any]]]:
|
||||||
slots = _parse_character_cast(character_cast)
|
return pair_cast.cast_descriptor_entries(
|
||||||
label_map = _character_slot_label_map(slots)
|
seed_config=seed_config,
|
||||||
rng = _axis_rng(seed_config, "person", seed, row_number + 997)
|
seed=seed,
|
||||||
descriptors: list[str] = []
|
row_number=row_number,
|
||||||
for index in range(max(0, women_count)):
|
ethnicity=ethnicity,
|
||||||
label = f"Woman {chr(ord('A') + index)}"
|
figure=figure,
|
||||||
if index == 0 and primary_descriptor:
|
no_plus_women=no_plus_women,
|
||||||
descriptors.append(f"Woman A / primary creator: {primary_descriptor}")
|
no_black=no_black,
|
||||||
continue
|
women_count=women_count,
|
||||||
context, _slot = _character_context_for_label(label, label_map, rng, ethnicity, figure, no_plus_women, no_black)
|
men_count=men_count,
|
||||||
descriptors.append(f"{label}: {_insta_of_descriptor_from_context(context)}")
|
character_cast=character_cast,
|
||||||
for index in range(max(0, men_count)):
|
primary_descriptor=primary_descriptor,
|
||||||
label = f"Man {chr(ord('A') + index)}"
|
parse_character_cast=_parse_character_cast,
|
||||||
if _slot_is_pov(label_map.get(label)):
|
character_slot_label_map=_character_slot_label_map,
|
||||||
continue
|
axis_rng=_axis_rng,
|
||||||
context, _slot = _character_context_for_label(label, label_map, rng, ethnicity, figure, no_plus_women, no_black)
|
character_context_for_label=_character_context_for_label,
|
||||||
descriptors.append(f"{label}: {_insta_of_descriptor_from_context(context)}")
|
slot_is_pov=_slot_is_pov,
|
||||||
return descriptors, slots
|
)
|
||||||
|
|
||||||
|
|
||||||
def _row_from_profile_metadata(metadata_json: str | dict[str, Any] | None) -> dict[str, Any]:
|
def _row_from_profile_metadata(metadata_json: str | dict[str, Any] | None) -> dict[str, Any]:
|
||||||
@@ -3826,35 +3826,6 @@ def _insta_of_descriptor_from_context(context: dict[str, Any]) -> str:
|
|||||||
return pair_cast.insta_descriptor_from_context(context)
|
return pair_cast.insta_descriptor_from_context(context)
|
||||||
|
|
||||||
|
|
||||||
def _insta_of_cast_descriptors(
|
|
||||||
primary_descriptor: str,
|
|
||||||
seed_config: dict[str, int],
|
|
||||||
seed: int,
|
|
||||||
row_number: int,
|
|
||||||
ethnicity: str,
|
|
||||||
figure: str,
|
|
||||||
no_plus_women: bool,
|
|
||||||
no_black: bool,
|
|
||||||
women_count: int,
|
|
||||||
men_count: int,
|
|
||||||
character_cast: str | dict[str, Any] | list[Any] | None = "",
|
|
||||||
) -> list[str]:
|
|
||||||
descriptors, _slots = _cast_descriptor_entries(
|
|
||||||
seed_config,
|
|
||||||
seed,
|
|
||||||
row_number,
|
|
||||||
ethnicity,
|
|
||||||
figure,
|
|
||||||
no_plus_women,
|
|
||||||
no_black,
|
|
||||||
women_count,
|
|
||||||
men_count,
|
|
||||||
character_cast,
|
|
||||||
primary_descriptor=primary_descriptor,
|
|
||||||
)
|
|
||||||
return descriptors
|
|
||||||
|
|
||||||
|
|
||||||
def _insta_of_prompt_cast_descriptors(text: str) -> str:
|
def _insta_of_prompt_cast_descriptors(text: str) -> str:
|
||||||
return pair_cast.prompt_cast_descriptors(text)
|
return pair_cast.prompt_cast_descriptors(text)
|
||||||
|
|
||||||
@@ -3973,8 +3944,9 @@ def build_insta_of_pair(
|
|||||||
platform_styles=INSTA_OF_PLATFORM_STYLES,
|
platform_styles=INSTA_OF_PLATFORM_STYLES,
|
||||||
soft_levels=INSTA_OF_SOFT_LEVELS,
|
soft_levels=INSTA_OF_SOFT_LEVELS,
|
||||||
hardcore_levels=INSTA_OF_HARDCORE_LEVELS,
|
hardcore_levels=INSTA_OF_HARDCORE_LEVELS,
|
||||||
build_cast_descriptors=_insta_of_cast_descriptors,
|
|
||||||
axis_rng=_axis_rng,
|
axis_rng=_axis_rng,
|
||||||
|
character_context_for_label=_character_context_for_label,
|
||||||
|
slot_is_pov=_slot_is_pov,
|
||||||
choose=g.choose,
|
choose=g.choose,
|
||||||
slot_softcore_outfit=_slot_softcore_outfit,
|
slot_softcore_outfit=_slot_softcore_outfit,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1732,6 +1732,49 @@ def smoke_pair_options_policy() -> None:
|
|||||||
pair_cast.prompt_cast_descriptors("Woman A / primary creator: descriptor") == "Woman A: descriptor",
|
pair_cast.prompt_cast_descriptors("Woman A / primary creator: descriptor") == "Woman A: descriptor",
|
||||||
"Pair cast prompt descriptor label cleanup changed",
|
"Pair cast prompt descriptor label cleanup changed",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _fake_character_context(
|
||||||
|
label: str,
|
||||||
|
label_map: dict[str, dict[str, Any]],
|
||||||
|
_rng: random.Random,
|
||||||
|
_ethnicity: str,
|
||||||
|
_figure: str,
|
||||||
|
_no_plus_women: bool,
|
||||||
|
_no_black: bool,
|
||||||
|
) -> tuple[dict[str, Any], dict[str, Any] | None]:
|
||||||
|
subject = "man" if label.startswith("Man ") else "woman"
|
||||||
|
age = "40-year-old adult" if subject == "man" else "30-year-old adult"
|
||||||
|
return {"subject_type": subject, "age": age, "body_phrase": f"{label} body"}, label_map.get(label)
|
||||||
|
|
||||||
|
descriptor_entries, descriptor_slots = pair_cast.cast_descriptor_entries_from_slots(
|
||||||
|
seed_config={},
|
||||||
|
seed=1,
|
||||||
|
row_number=1,
|
||||||
|
ethnicity="any",
|
||||||
|
figure="any",
|
||||||
|
no_plus_women=False,
|
||||||
|
no_black=False,
|
||||||
|
women_count=2,
|
||||||
|
men_count=1,
|
||||||
|
character_slots=[{"subject_type": "man", "presence_mode": "pov"}],
|
||||||
|
character_slot_map={"Man A": {"subject_type": "man", "presence_mode": "pov"}},
|
||||||
|
primary_descriptor="primary descriptor",
|
||||||
|
axis_rng=lambda _config, _axis, seed_value, row_value: random.Random(seed_value + row_value),
|
||||||
|
character_context_for_label=_fake_character_context,
|
||||||
|
slot_is_pov=lambda slot: bool(slot and slot.get("presence_mode") == "pov"),
|
||||||
|
)
|
||||||
|
_expect(
|
||||||
|
descriptor_entries
|
||||||
|
== [
|
||||||
|
"Woman A / primary creator: primary descriptor",
|
||||||
|
"Woman B: 30-year-old adult woman, Woman B body",
|
||||||
|
],
|
||||||
|
"Pair cast descriptor entries should keep primary label and skip POV men",
|
||||||
|
)
|
||||||
|
_expect(
|
||||||
|
descriptor_slots == [{"subject_type": "man", "presence_mode": "pov"}],
|
||||||
|
"Pair cast descriptor entries should return the source slots",
|
||||||
|
)
|
||||||
partner_styling = pair_cast.softcore_partner_styling(
|
partner_styling = pair_cast.softcore_partner_styling(
|
||||||
seed_config={},
|
seed_config={},
|
||||||
seed=1,
|
seed=1,
|
||||||
|
|||||||
Reference in New Issue
Block a user