Add character-level InstaOF overrides

This commit is contained in:
2026-06-24 18:19:20 +02:00
parent 89e499537e
commit e042960466
4 changed files with 194 additions and 15 deletions
+18 -5
View File
@@ -113,6 +113,17 @@ character-driven. For configured casts, matching enabled slots emit
per-character expression text such as `Woman A has ...; Man A has ...`; Krea per-character expression text such as `Woman A has ...; Man A has ...`; Krea
formatting naturalizes those labels in pair prompts. formatting naturalizes those labels in pair prompts.
For Insta/OF pairs, slots also expose character-level overrides:
- `softcore_expression_intensity` and `hardcore_expression_intensity`: override
the option-node expression fallback for that character and that output half.
- `softcore_outfit`: overrides the character's softcore clothing. For `Woman A`
this replaces the generated teaser outfit; for partners it replaces random
partner styling.
- `hardcore_clothing`: adds direct character clothing/nudity wording in the
hardcore output. A `Woman A` hardcore clothing override replaces the global
`hardcore_clothing_continuity` text to avoid contradictory clothing prompts.
Slots are chainable through the `character_cast` input/output. In automatic Slots are chainable through the `character_cast` input/output. In automatic
label mode, the slot closest to the final generator becomes `A` for its gender, label mode, the slot closest to the final generator becomes `A` for its gender,
the next upstream slot becomes `B`, then `C`, and so on. Example: the next upstream slot becomes `B`, then `C`, and so on. Example:
@@ -311,11 +322,12 @@ Options:
microwear, or shirtless partner styling. `explicit_nude` is available when microwear, or shirtless partner styling. `explicit_nude` is available when
you want visible nude creator-shot framing without a sex act. you want visible nude creator-shot framing without a sex act.
- `hardcore_level`: `explicit` or `hardcore`. - `hardcore_level`: `explicit` or `hardcore`.
- `softcore_expression_intensity`: `0.0` is mild/controlled, `0.5` is sensual, - `softcore_expression_intensity`: fallback when no connected character slot
sets `softcore_expression_intensity`. `0.0` is mild/controlled, `0.5` is sensual,
`1.0` strongly favors more heated softcore faces. `1.0` strongly favors more heated softcore faces.
- `hardcore_expression_intensity`: `0.0` is controlled, `0.5` is balanced - `hardcore_expression_intensity`: fallback when no connected character slot
hardcore, `1.0` strongly favors ahegao-style, drooling, fucked-out, climax, sets `hardcore_expression_intensity`. `0.0` is controlled, `0.5` is balanced
and messy orgasm expressions. hardcore, `1.0` strongly favors stronger hardcore expressions.
- `softcore_expression_enabled` and `hardcore_expression_enabled`: disable the - `softcore_expression_enabled` and `hardcore_expression_enabled`: disable the
expression sentence for that half of the Insta/OF pair. The intensity values expression sentence for that half of the Insta/OF pair. The intensity values
are fallbacks; `SxCP Woman Slot` / `SxCP Man Slot` `expression_intensity` are fallbacks; `SxCP Woman Slot` / `SxCP Man Slot` `expression_intensity`
@@ -328,7 +340,8 @@ Options:
- `hardcore_clothing_continuity`: `none`, `same_outfit`, `partially_removed`, - `hardcore_clothing_continuity`: `none`, `same_outfit`, `partially_removed`,
`implied_nude`, or `explicit_nude`. This controls whether the hardcore prompt `implied_nude`, or `explicit_nude`. This controls whether the hardcore prompt
references the softcore outfit, uses it displaced/removed, or makes Woman A references the softcore outfit, uses it displaced/removed, or makes Woman A
explicitly nude. explicitly nude. It is a fallback for Woman A; `hardcore_clothing` on
`SxCP Woman Slot` or `SxCP Man Slot` takes priority for that character.
- `softcore_camera_mode`: base camera mode for the softcore output. - `softcore_camera_mode`: base camera mode for the softcore output.
- `hardcore_camera_mode`: `from_camera_config`, `same_as_softcore`, or a - `hardcore_camera_mode`: `from_camera_config`, `same_as_softcore`, or a
separate base camera mode for the hardcore output. `from_camera_config` is separate base camera mode for the hardcore output. `from_camera_config` is
+36
View File
@@ -614,6 +614,10 @@ class SxCPCharacterSlot:
"expression_enabled": ("BOOLEAN", {"default": True}), "expression_enabled": ("BOOLEAN", {"default": True}),
"expression_intensity": ("FLOAT", {"default": -1.0, "min": -1.0, "max": 1.0, "step": 0.01}), "expression_intensity": ("FLOAT", {"default": -1.0, "min": -1.0, "max": 1.0, "step": 0.01}),
"presence_mode": (character_presence_choices(), {"default": "visible"}), "presence_mode": (character_presence_choices(), {"default": "visible"}),
"softcore_expression_intensity": ("FLOAT", {"default": -1.0, "min": -1.0, "max": 1.0, "step": 0.01}),
"hardcore_expression_intensity": ("FLOAT", {"default": -1.0, "min": -1.0, "max": 1.0, "step": 0.01}),
"softcore_outfit": ("STRING", {"default": ""}),
"hardcore_clothing": ("STRING", {"default": ""}),
}, },
"optional": { "optional": {
"character_cast": ("STRING", {"default": "", "multiline": True}), "character_cast": ("STRING", {"default": "", "multiline": True}),
@@ -644,6 +648,10 @@ class SxCPCharacterSlot:
expression_enabled=True, expression_enabled=True,
expression_intensity=-1.0, expression_intensity=-1.0,
presence_mode="visible", presence_mode="visible",
softcore_expression_intensity=-1.0,
hardcore_expression_intensity=-1.0,
softcore_outfit="",
hardcore_clothing="",
character_cast="", character_cast="",
): ):
result = build_character_slot_json( result = build_character_slot_json(
@@ -663,6 +671,10 @@ class SxCPCharacterSlot:
expression_enabled=expression_enabled, expression_enabled=expression_enabled,
expression_intensity=expression_intensity, expression_intensity=expression_intensity,
presence_mode=presence_mode, presence_mode=presence_mode,
softcore_expression_intensity=softcore_expression_intensity,
hardcore_expression_intensity=hardcore_expression_intensity,
softcore_outfit=softcore_outfit,
hardcore_clothing=hardcore_clothing,
enabled=enabled, enabled=enabled,
character_cast=character_cast or "", character_cast=character_cast or "",
) )
@@ -689,6 +701,10 @@ class SxCPWomanSlot:
"descriptor_detail": (character_descriptor_detail_choices(), {"default": "auto"}), "descriptor_detail": (character_descriptor_detail_choices(), {"default": "auto"}),
"expression_enabled": ("BOOLEAN", {"default": True}), "expression_enabled": ("BOOLEAN", {"default": True}),
"expression_intensity": ("FLOAT", {"default": -1.0, "min": -1.0, "max": 1.0, "step": 0.01}), "expression_intensity": ("FLOAT", {"default": -1.0, "min": -1.0, "max": 1.0, "step": 0.01}),
"softcore_expression_intensity": ("FLOAT", {"default": -1.0, "min": -1.0, "max": 1.0, "step": 0.01}),
"hardcore_expression_intensity": ("FLOAT", {"default": -1.0, "min": -1.0, "max": 1.0, "step": 0.01}),
"softcore_outfit": ("STRING", {"default": ""}),
"hardcore_clothing": ("STRING", {"default": ""}),
}, },
"optional": { "optional": {
"character_cast": ("STRING", {"default": "", "multiline": True}), "character_cast": ("STRING", {"default": "", "multiline": True}),
@@ -717,6 +733,10 @@ class SxCPWomanSlot:
descriptor_detail="auto", descriptor_detail="auto",
expression_enabled=True, expression_enabled=True,
expression_intensity=-1.0, expression_intensity=-1.0,
softcore_expression_intensity=-1.0,
hardcore_expression_intensity=-1.0,
softcore_outfit="",
hardcore_clothing="",
character_cast="", character_cast="",
): ):
result = build_character_slot_json( result = build_character_slot_json(
@@ -735,6 +755,10 @@ class SxCPWomanSlot:
descriptor_detail=descriptor_detail, descriptor_detail=descriptor_detail,
expression_enabled=expression_enabled, expression_enabled=expression_enabled,
expression_intensity=expression_intensity, expression_intensity=expression_intensity,
softcore_expression_intensity=softcore_expression_intensity,
hardcore_expression_intensity=hardcore_expression_intensity,
softcore_outfit=softcore_outfit,
hardcore_clothing=hardcore_clothing,
enabled=enabled, enabled=enabled,
character_cast=character_cast or "", character_cast=character_cast or "",
) )
@@ -761,6 +785,10 @@ class SxCPManSlot:
"expression_enabled": ("BOOLEAN", {"default": True}), "expression_enabled": ("BOOLEAN", {"default": True}),
"expression_intensity": ("FLOAT", {"default": -1.0, "min": -1.0, "max": 1.0, "step": 0.01}), "expression_intensity": ("FLOAT", {"default": -1.0, "min": -1.0, "max": 1.0, "step": 0.01}),
"presence_mode": (character_presence_choices(), {"default": "visible"}), "presence_mode": (character_presence_choices(), {"default": "visible"}),
"softcore_expression_intensity": ("FLOAT", {"default": -1.0, "min": -1.0, "max": 1.0, "step": 0.01}),
"hardcore_expression_intensity": ("FLOAT", {"default": -1.0, "min": -1.0, "max": 1.0, "step": 0.01}),
"softcore_outfit": ("STRING", {"default": ""}),
"hardcore_clothing": ("STRING", {"default": ""}),
}, },
"optional": { "optional": {
"character_cast": ("STRING", {"default": "", "multiline": True}), "character_cast": ("STRING", {"default": "", "multiline": True}),
@@ -789,6 +817,10 @@ class SxCPManSlot:
expression_enabled=True, expression_enabled=True,
expression_intensity=-1.0, expression_intensity=-1.0,
presence_mode="visible", presence_mode="visible",
softcore_expression_intensity=-1.0,
hardcore_expression_intensity=-1.0,
softcore_outfit="",
hardcore_clothing="",
character_cast="", character_cast="",
): ):
result = build_character_slot_json( result = build_character_slot_json(
@@ -808,6 +840,10 @@ class SxCPManSlot:
expression_enabled=expression_enabled, expression_enabled=expression_enabled,
expression_intensity=expression_intensity, expression_intensity=expression_intensity,
presence_mode=presence_mode, presence_mode=presence_mode,
softcore_expression_intensity=softcore_expression_intensity,
hardcore_expression_intensity=hardcore_expression_intensity,
softcore_outfit=softcore_outfit,
hardcore_clothing=hardcore_clothing,
enabled=enabled, enabled=enabled,
character_cast=character_cast or "", character_cast=character_cast or "",
) )
+16 -1
View File
@@ -307,6 +307,18 @@ def _pov_action_phrase(action: Any, pov_labels: list[str]) -> str:
rendered = re.sub(r"\bhe\b", "the POV viewer", rendered, flags=re.IGNORECASE) rendered = re.sub(r"\bhe\b", "the POV viewer", rendered, flags=re.IGNORECASE)
rendered = re.sub(r"\bhim\b", "the POV viewer", rendered, flags=re.IGNORECASE) rendered = re.sub(r"\bhim\b", "the POV viewer", rendered, flags=re.IGNORECASE)
rendered = re.sub(r"\bhis\b", "the POV viewer's", rendered, flags=re.IGNORECASE) rendered = re.sub(r"\bhis\b", "the POV viewer's", rendered, flags=re.IGNORECASE)
rendered = re.sub(
r"\bthe POV viewer lies on the POV viewer's back under her\b",
"the POV viewer reclines underneath her",
rendered,
flags=re.IGNORECASE,
)
rendered = re.sub(
r"\bthe POV viewer lies on the POV viewer's back\b",
"the POV viewer reclines",
rendered,
flags=re.IGNORECASE,
)
rendered = re.sub(r"\bthe POV viewer is positioned\b", "the POV camera is positioned", rendered, flags=re.IGNORECASE) rendered = re.sub(r"\bthe POV viewer is positioned\b", "the POV camera is positioned", rendered, flags=re.IGNORECASE)
return rendered return rendered
@@ -1595,7 +1607,10 @@ def _insta_pair_to_krea(row: dict[str, Any], detail_level: str, style_mode: str)
hard_parts = [ hard_parts = [
hard_action, hard_action,
_pov_camera_phrase(pov_labels), _pov_camera_phrase(pov_labels),
_natural_clothing_state(row.get("hardcore_clothing_state")), _natural_label_text(
_filter_pov_labeled_clauses(_natural_clothing_state(row.get("hardcore_clothing_state")), pov_labels),
hard_labels,
),
hard_cast_prose, hard_cast_prose,
f"set in {hard_scene}" if hard_scene else "", f"set in {hard_scene}" if hard_scene else "",
_expression_phrase(hard_expression), _expression_phrase(hard_expression),
+124 -9
View File
@@ -1906,6 +1906,17 @@ def _slot_expression_intensity(slot: dict[str, Any] | None) -> float | None:
return intensity if intensity >= 0 else None return intensity if intensity >= 0 else None
def _slot_expression_intensity_for_phase(slot: dict[str, Any] | None, phase: str = "") -> float | None:
if not slot or not _slot_expression_enabled(slot):
return None
phase_key = f"{phase}_expression_intensity" if phase in ("softcore", "hardcore") else ""
if phase_key:
intensity = _normalize_slot_expression_intensity(slot.get(phase_key))
if intensity >= 0:
return intensity
return _slot_expression_intensity(slot)
def _mean(values: list[float]) -> float: def _mean(values: list[float]) -> float:
return sum(values) / len(values) return sum(values) / len(values)
@@ -1915,6 +1926,7 @@ def _cast_expression_intensity_override(
label_map: dict[str, dict[str, Any]], label_map: dict[str, dict[str, Any]],
women_count: int, women_count: int,
men_count: int, men_count: int,
expression_phase: str = "",
) -> tuple[float | None, str]: ) -> tuple[float | None, str]:
groups: list[tuple[str, list[str]]] = [ groups: list[tuple[str, list[str]]] = [
("women", [f"Woman {chr(ord('A') + index)}" for index in range(max(0, women_count))]), ("women", [f"Woman {chr(ord('A') + index)}" for index in range(max(0, women_count))]),
@@ -1931,7 +1943,7 @@ def _cast_expression_intensity_override(
continue continue
if slot: if slot:
matching_slots.append(slot) matching_slots.append(slot)
value = _slot_expression_intensity(slot) value = _slot_expression_intensity_for_phase(slot, expression_phase)
if value is not None: if value is not None:
values.append(value) values.append(value)
value_labels.append(label) value_labels.append(label)
@@ -1954,6 +1966,7 @@ def _character_expression_entries(
label_map: dict[str, dict[str, Any]], label_map: dict[str, dict[str, Any]],
women_count: int, women_count: int,
men_count: int, men_count: int,
expression_phase: str = "",
) -> list[str]: ) -> list[str]:
labels = [ labels = [
*[f"Woman {chr(ord('A') + index)}" for index in range(max(0, women_count))], *[f"Woman {chr(ord('A') + index)}" for index in range(max(0, women_count))],
@@ -1969,7 +1982,7 @@ def _character_expression_entries(
continue continue
if not _slot_expression_enabled(slot): if not _slot_expression_enabled(slot):
continue continue
intensity = _slot_expression_intensity(slot) intensity = _slot_expression_intensity_for_phase(slot, expression_phase)
if intensity is None: if intensity is None:
intensity = fallback_intensity intensity = fallback_intensity
entries = _compatible_entries( entries = _compatible_entries(
@@ -2075,8 +2088,12 @@ def _normalize_character_slot(slot: dict[str, Any]) -> dict[str, Any]:
"eyes": _slot_value(slot.get("eyes")), "eyes": _slot_value(slot.get("eyes")),
"descriptor_detail": _normalize_descriptor_detail(slot.get("descriptor_detail")), "descriptor_detail": _normalize_descriptor_detail(slot.get("descriptor_detail")),
"presence_mode": _normalize_presence_mode(slot.get("presence_mode"), subject_type), "presence_mode": _normalize_presence_mode(slot.get("presence_mode"), subject_type),
"softcore_outfit": _slot_value(slot.get("softcore_outfit")),
"hardcore_clothing": _slot_value(slot.get("hardcore_clothing") or slot.get("hardcore_outfit")),
"expression_enabled": not _is_false(slot.get("expression_enabled", True)), "expression_enabled": not _is_false(slot.get("expression_enabled", True)),
"expression_intensity": _normalize_slot_expression_intensity(slot.get("expression_intensity")), "expression_intensity": _normalize_slot_expression_intensity(slot.get("expression_intensity")),
"softcore_expression_intensity": _normalize_slot_expression_intensity(slot.get("softcore_expression_intensity")),
"hardcore_expression_intensity": _normalize_slot_expression_intensity(slot.get("hardcore_expression_intensity")),
} }
normalized["summary"] = _character_slot_summary(normalized) normalized["summary"] = _character_slot_summary(normalized)
return normalized return normalized
@@ -2129,6 +2146,16 @@ def _character_slot_summary(slot: dict[str, Any]) -> str:
expression_intensity = _slot_expression_intensity(slot) expression_intensity = _slot_expression_intensity(slot)
if expression_intensity is not None: if expression_intensity is not None:
parts.append(f"expression={expression_intensity:.2f}") parts.append(f"expression={expression_intensity:.2f}")
softcore_expression_intensity = _slot_expression_intensity_for_phase(slot, "softcore")
hardcore_expression_intensity = _slot_expression_intensity_for_phase(slot, "hardcore")
if softcore_expression_intensity is not None and softcore_expression_intensity != expression_intensity:
parts.append(f"soft_expr={softcore_expression_intensity:.2f}")
if hardcore_expression_intensity is not None and hardcore_expression_intensity != expression_intensity:
parts.append(f"hard_expr={hardcore_expression_intensity:.2f}")
if slot.get("softcore_outfit"):
parts.append(f"soft_outfit={slot['softcore_outfit']}")
if slot.get("hardcore_clothing"):
parts.append(f"hard_clothing={slot['hardcore_clothing']}")
for key in ("body_phrase", "skin", "hair", "eyes"): for key in ("body_phrase", "skin", "hair", "eyes"):
value = slot.get(key) value = slot.get(key)
if value: if value:
@@ -2155,6 +2182,10 @@ def build_character_slot_json(
enabled: bool = True, enabled: bool = True,
character_cast: str | dict[str, Any] | list[Any] | None = "", character_cast: str | dict[str, Any] | list[Any] | None = "",
presence_mode: str = "visible", presence_mode: str = "visible",
softcore_expression_intensity: float = -1.0,
hardcore_expression_intensity: float = -1.0,
softcore_outfit: str = "",
hardcore_clothing: str = "",
) -> dict[str, str]: ) -> dict[str, str]:
existing_slots = _parse_character_cast(character_cast) existing_slots = _parse_character_cast(character_cast)
slot = _normalize_character_slot( slot = _normalize_character_slot(
@@ -2173,8 +2204,12 @@ def build_character_slot_json(
"eyes": eyes, "eyes": eyes,
"descriptor_detail": descriptor_detail, "descriptor_detail": descriptor_detail,
"presence_mode": presence_mode, "presence_mode": presence_mode,
"softcore_outfit": softcore_outfit,
"hardcore_clothing": hardcore_clothing,
"expression_enabled": expression_enabled, "expression_enabled": expression_enabled,
"expression_intensity": expression_intensity, "expression_intensity": expression_intensity,
"softcore_expression_intensity": softcore_expression_intensity,
"hardcore_expression_intensity": hardcore_expression_intensity,
} }
) )
slots = existing_slots + ([slot] if enabled else []) slots = existing_slots + ([slot] if enabled else [])
@@ -2271,6 +2306,58 @@ def _pov_composition_prompt(composition: Any, pov_labels: list[str]) -> str:
return _clean_prompt_punctuation(text) return _clean_prompt_punctuation(text)
def _slot_softcore_outfit(slot: dict[str, Any] | None) -> str:
return _slot_value(slot.get("softcore_outfit")) if slot else ""
def _slot_hardcore_clothing(slot: dict[str, Any] | None) -> str:
return _slot_value(slot.get("hardcore_clothing")) if slot else ""
def _softcore_outfit_sentence(label: str, outfit: str) -> str:
outfit = str(outfit or "").strip()
if not outfit:
return ""
lower = outfit.lower()
if lower.startswith(("wears ", "wearing ", "in ")):
return f"{label} {outfit}"
return f"{label} wears {outfit}"
def _hardcore_clothing_sentence(label: str, clothing: str) -> str:
clothing = str(clothing or "").strip().rstrip(".")
if not clothing:
return ""
lower = clothing.lower()
if lower.startswith(("is ", "wears ", "wearing ", "keeps ", "has ", "with ")):
return f"{label} {clothing}"
if lower.startswith(("fully nude", "nude", "partly nude")):
return f"{label} is {clothing}"
return f"{label}'s clothing: {clothing}"
def _character_hardcore_clothing_entries(
label_map: dict[str, dict[str, Any]],
women_count: int,
men_count: int,
pov_labels: list[str] | None = None,
) -> list[str]:
pov_set = set(pov_labels or [])
labels = [
*[f"Woman {chr(ord('A') + index)}" for index in range(max(0, women_count))],
*[f"Man {chr(ord('A') + index)}" for index in range(max(0, men_count))],
]
entries: list[str] = []
for label in labels:
if label in pov_set:
continue
clothing = _slot_hardcore_clothing(label_map.get(label))
sentence = _hardcore_clothing_sentence(label, clothing)
if sentence:
entries.append(sentence)
return entries
def _context_from_character_slot( def _context_from_character_slot(
rng: random.Random, rng: random.Random,
slot: dict[str, Any], slot: dict[str, Any],
@@ -3303,6 +3390,7 @@ def _build_custom_row(
expression_intensity: float, expression_intensity: float,
character_profile: str | dict[str, Any] | None = None, character_profile: str | dict[str, Any] | None = None,
character_cast: str | dict[str, Any] | list[Any] | None = None, character_cast: str | dict[str, Any] | list[Any] | None = None,
expression_phase: str = "",
) -> dict[str, Any]: ) -> dict[str, Any]:
categories = load_category_library() categories = load_category_library()
category_rng = _axis_rng(seed_config, "category", seed, row_number) category_rng = _axis_rng(seed_config, "category", seed, row_number)
@@ -3382,7 +3470,7 @@ def _build_custom_row(
expression_disabled = True expression_disabled = True
expression_intensity_source = f"character_slot:{slot_label}:disabled" expression_intensity_source = f"character_slot:{slot_label}:disabled"
else: else:
slot_expression_intensity = _slot_expression_intensity(applied_slot) slot_expression_intensity = _slot_expression_intensity_for_phase(applied_slot, expression_phase)
if slot_expression_intensity is not None: if slot_expression_intensity is not None:
expression_intensity = slot_expression_intensity expression_intensity = slot_expression_intensity
expression_intensity_source = f"character_slot:{slot_label}" expression_intensity_source = f"character_slot:{slot_label}"
@@ -3392,6 +3480,7 @@ def _build_custom_row(
character_slot_map, character_slot_map,
women_count, women_count,
men_count, men_count,
expression_phase,
) )
if expression_intensity is None: if expression_intensity is None:
expression_disabled = True expression_disabled = True
@@ -3439,6 +3528,7 @@ def _build_custom_row(
character_slot_map, character_slot_map,
women_count, women_count,
men_count, men_count,
expression_phase,
) )
character_expression_text = "; ".join(character_expressions) character_expression_text = "; ".join(character_expressions)
if character_expression_text: if character_expression_text:
@@ -3616,6 +3706,7 @@ def build_prompt(
character_profile: str | dict[str, Any] | None = None, character_profile: str | dict[str, Any] | None = None,
character_cast: str | dict[str, Any] | list[Any] | None = None, character_cast: str | dict[str, Any] | list[Any] | None = None,
expression_enabled: bool = True, expression_enabled: bool = True,
expression_phase: str = "",
) -> dict[str, Any]: ) -> dict[str, Any]:
apply_pool_extensions() apply_pool_extensions()
row_number = max(1, int(row_number)) row_number = max(1, int(row_number))
@@ -3683,6 +3774,7 @@ def build_prompt(
expression_intensity, expression_intensity,
character_profile, character_profile,
character_cast, character_cast,
expression_phase,
) )
if not expression_enabled: if not expression_enabled:
@@ -4155,6 +4247,7 @@ def _insta_of_partner_styling(
women_count: int, women_count: int,
men_count: int, men_count: int,
pov_labels: list[str] | None = None, pov_labels: list[str] | None = None,
label_map: dict[str, dict[str, Any]] | None = None,
) -> dict[str, Any]: ) -> dict[str, Any]:
content_rng = _axis_rng(seed_config, "content", seed, row_number + 421) content_rng = _axis_rng(seed_config, "content", seed, row_number + 421)
pose_rng = _axis_rng(seed_config, "pose", seed, row_number + 421) pose_rng = _axis_rng(seed_config, "pose", seed, row_number + 421)
@@ -4162,12 +4255,20 @@ def _insta_of_partner_styling(
outfits: list[str] = [] outfits: list[str] = []
for index in range(max(0, women_count - 1)): for index in range(max(0, women_count - 1)):
label = chr(ord("B") + index) label = chr(ord("B") + index)
outfits.append(f"Woman {label} wears {g.choose(content_rng, INSTA_OF_SOFTCORE_PARTNER_WOMEN_OUTFITS)}") full_label = f"Woman {label}"
outfit = _slot_softcore_outfit((label_map or {}).get(full_label)) or g.choose(content_rng, INSTA_OF_SOFTCORE_PARTNER_WOMEN_OUTFITS)
sentence = _softcore_outfit_sentence(full_label, outfit)
if sentence:
outfits.append(sentence)
for index in range(max(0, men_count)): for index in range(max(0, men_count)):
label = chr(ord("A") + index) label = chr(ord("A") + index)
if f"Man {label}" in pov_set: full_label = f"Man {label}"
if full_label in pov_set:
continue continue
outfits.append(f"Man {label} wears {g.choose(content_rng, INSTA_OF_SOFTCORE_PARTNER_MEN_OUTFITS)}") outfit = _slot_softcore_outfit((label_map or {}).get(full_label)) or g.choose(content_rng, INSTA_OF_SOFTCORE_PARTNER_MEN_OUTFITS)
sentence = _softcore_outfit_sentence(full_label, outfit)
if sentence:
outfits.append(sentence)
return { return {
"outfits": outfits, "outfits": outfits,
"pose": g.choose(pose_rng, SOFTCORE_CAST_POSES), "pose": g.choose(pose_rng, SOFTCORE_CAST_POSES),
@@ -4225,6 +4326,7 @@ def build_insta_of_pair(
character_slot_map, character_slot_map,
soft_expression_women_count, soft_expression_women_count,
soft_expression_men_count, soft_expression_men_count,
"softcore",
) )
if soft_expression_intensity is None: if soft_expression_intensity is None:
soft_expression_enabled = False soft_expression_enabled = False
@@ -4277,11 +4379,12 @@ def build_insta_of_pair(
soft_row["character_slot_status"] = "applied:Woman A" soft_row["character_slot_status"] = "applied:Woman A"
if not soft_expression_enabled: if not soft_expression_enabled:
soft_row = _disable_row_expression(soft_row, soft_expression_intensity_source) soft_row = _disable_row_expression(soft_row, soft_expression_intensity_source)
soft_row["item"] = _insta_of_softcore_outfit(soft_content_rng, softcore_level_key) primary_softcore_outfit = _slot_softcore_outfit(primary_slot)
soft_row["item"] = primary_softcore_outfit or _insta_of_softcore_outfit(soft_content_rng, softcore_level_key)
soft_row["pose"] = _insta_of_softcore_pose(soft_content_rng, softcore_level_key) soft_row["pose"] = _insta_of_softcore_pose(soft_content_rng, softcore_level_key)
soft_row["item_label"] = "Insta/OF softcore outfit" soft_row["item_label"] = "Insta/OF softcore outfit"
soft_row["custom_item"] = "insta_of_softcore_outfit" soft_row["custom_item"] = "insta_of_softcore_outfit"
soft_row["softcore_outfit_policy"] = "insta_of_safe_softcore" soft_row["softcore_outfit_policy"] = "character_slot:Woman A" if primary_softcore_outfit else "insta_of_safe_softcore"
soft_row["pov_character_labels"] = ( soft_row["pov_character_labels"] = (
pov_character_labels pov_character_labels
if options["softcore_cast"] == "same_as_hardcore" if options["softcore_cast"] == "same_as_hardcore"
@@ -4319,6 +4422,7 @@ def build_insta_of_pair(
expression_enabled=options["hardcore_expression_enabled"], expression_enabled=options["hardcore_expression_enabled"],
expression_intensity=options["hardcore_expression_intensity"], expression_intensity=options["hardcore_expression_intensity"],
character_cast=character_cast or "", character_cast=character_cast or "",
expression_phase="hardcore",
) )
hard_row["hardcore_detail_density"] = options["hardcore_detail_density"] hard_row["hardcore_detail_density"] = options["hardcore_detail_density"]
hard_row["pov_character_labels"] = pov_character_labels hard_row["pov_character_labels"] = pov_character_labels
@@ -4351,6 +4455,7 @@ def build_insta_of_pair(
hard_women_count if options["softcore_cast"] == "same_as_hardcore" else 1, hard_women_count if options["softcore_cast"] == "same_as_hardcore" else 1,
hard_men_count if options["softcore_cast"] == "same_as_hardcore" else 0, hard_men_count if options["softcore_cast"] == "same_as_hardcore" else 0,
pov_character_labels if options["softcore_cast"] == "same_as_hardcore" else [], pov_character_labels if options["softcore_cast"] == "same_as_hardcore" else [],
character_slot_map,
) )
if options["softcore_cast"] != "same_as_hardcore": if options["softcore_cast"] != "same_as_hardcore":
soft_partner_styling = {"outfits": [], "pose": ""} soft_partner_styling = {"outfits": [], "pose": ""}
@@ -4394,10 +4499,19 @@ def build_insta_of_pair(
else "" else ""
) )
hard_cast = _insta_of_cast_phrase(hard_women_count, hard_men_count) hard_cast = _insta_of_cast_phrase(hard_women_count, hard_men_count)
hard_clothing_state = _insta_of_hardcore_clothing_state( character_hardcore_clothing_entries = _character_hardcore_clothing_entries(
character_slot_map,
hard_women_count,
hard_men_count,
pov_character_labels,
)
has_primary_hardcore_clothing = any(entry.startswith("Woman A") for entry in character_hardcore_clothing_entries)
fallback_hard_clothing_state = "" if has_primary_hardcore_clothing else _insta_of_hardcore_clothing_state(
options["hardcore_clothing_continuity"], options["hardcore_clothing_continuity"],
soft_row["item"], soft_row["item"],
) )
hard_clothing_parts = [part for part in (fallback_hard_clothing_state, *character_hardcore_clothing_entries) if part]
hard_clothing_state = " ".join(hard_clothing_parts)
hard_detail_density = options["hardcore_detail_density"] hard_detail_density = options["hardcore_detail_density"]
hard_detail_directive = { hard_detail_directive = {
"compact": "Use one compact position-first sexual action sentence; avoid repeated aftermath wording. ", "compact": "Use one compact position-first sexual action sentence; avoid repeated aftermath wording. ",
@@ -4482,6 +4596,7 @@ def build_insta_of_pair(
"pov_character_labels": pov_character_labels, "pov_character_labels": pov_character_labels,
"pov_prompt_directive": pov_directive, "pov_prompt_directive": pov_directive,
"softcore_partner_styling": soft_partner_styling, "softcore_partner_styling": soft_partner_styling,
"character_hardcore_clothing": character_hardcore_clothing_entries,
"hardcore_clothing_state": hard_clothing_state, "hardcore_clothing_state": hard_clothing_state,
"hardcore_detail_density": hard_detail_density, "hardcore_detail_density": hard_detail_density,
"softcore_prompt": soft_prompt, "softcore_prompt": soft_prompt,