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
+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
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:
return sum(values) / len(values)
@@ -1915,6 +1926,7 @@ def _cast_expression_intensity_override(
label_map: dict[str, dict[str, Any]],
women_count: int,
men_count: int,
expression_phase: str = "",
) -> tuple[float | None, str]:
groups: list[tuple[str, list[str]]] = [
("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
if slot:
matching_slots.append(slot)
value = _slot_expression_intensity(slot)
value = _slot_expression_intensity_for_phase(slot, expression_phase)
if value is not None:
values.append(value)
value_labels.append(label)
@@ -1954,6 +1966,7 @@ def _character_expression_entries(
label_map: dict[str, dict[str, Any]],
women_count: int,
men_count: int,
expression_phase: str = "",
) -> list[str]:
labels = [
*[f"Woman {chr(ord('A') + index)}" for index in range(max(0, women_count))],
@@ -1969,7 +1982,7 @@ def _character_expression_entries(
continue
if not _slot_expression_enabled(slot):
continue
intensity = _slot_expression_intensity(slot)
intensity = _slot_expression_intensity_for_phase(slot, expression_phase)
if intensity is None:
intensity = fallback_intensity
entries = _compatible_entries(
@@ -2075,8 +2088,12 @@ def _normalize_character_slot(slot: dict[str, Any]) -> dict[str, Any]:
"eyes": _slot_value(slot.get("eyes")),
"descriptor_detail": _normalize_descriptor_detail(slot.get("descriptor_detail")),
"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_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)
return normalized
@@ -2129,6 +2146,16 @@ def _character_slot_summary(slot: dict[str, Any]) -> str:
expression_intensity = _slot_expression_intensity(slot)
if expression_intensity is not None:
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"):
value = slot.get(key)
if value:
@@ -2155,6 +2182,10 @@ def build_character_slot_json(
enabled: bool = True,
character_cast: str | dict[str, Any] | list[Any] | None = "",
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]:
existing_slots = _parse_character_cast(character_cast)
slot = _normalize_character_slot(
@@ -2173,8 +2204,12 @@ def build_character_slot_json(
"eyes": eyes,
"descriptor_detail": descriptor_detail,
"presence_mode": presence_mode,
"softcore_outfit": softcore_outfit,
"hardcore_clothing": hardcore_clothing,
"expression_enabled": expression_enabled,
"expression_intensity": expression_intensity,
"softcore_expression_intensity": softcore_expression_intensity,
"hardcore_expression_intensity": hardcore_expression_intensity,
}
)
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)
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(
rng: random.Random,
slot: dict[str, Any],
@@ -3303,6 +3390,7 @@ def _build_custom_row(
expression_intensity: float,
character_profile: str | dict[str, Any] | None = None,
character_cast: str | dict[str, Any] | list[Any] | None = None,
expression_phase: str = "",
) -> dict[str, Any]:
categories = load_category_library()
category_rng = _axis_rng(seed_config, "category", seed, row_number)
@@ -3382,7 +3470,7 @@ def _build_custom_row(
expression_disabled = True
expression_intensity_source = f"character_slot:{slot_label}:disabled"
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:
expression_intensity = slot_expression_intensity
expression_intensity_source = f"character_slot:{slot_label}"
@@ -3392,6 +3480,7 @@ def _build_custom_row(
character_slot_map,
women_count,
men_count,
expression_phase,
)
if expression_intensity is None:
expression_disabled = True
@@ -3439,6 +3528,7 @@ def _build_custom_row(
character_slot_map,
women_count,
men_count,
expression_phase,
)
character_expression_text = "; ".join(character_expressions)
if character_expression_text:
@@ -3616,6 +3706,7 @@ def build_prompt(
character_profile: str | dict[str, Any] | None = None,
character_cast: str | dict[str, Any] | list[Any] | None = None,
expression_enabled: bool = True,
expression_phase: str = "",
) -> dict[str, Any]:
apply_pool_extensions()
row_number = max(1, int(row_number))
@@ -3683,6 +3774,7 @@ def build_prompt(
expression_intensity,
character_profile,
character_cast,
expression_phase,
)
if not expression_enabled:
@@ -4155,6 +4247,7 @@ def _insta_of_partner_styling(
women_count: int,
men_count: int,
pov_labels: list[str] | None = None,
label_map: dict[str, dict[str, Any]] | None = None,
) -> dict[str, Any]:
content_rng = _axis_rng(seed_config, "content", 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] = []
for index in range(max(0, women_count - 1)):
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)):
label = chr(ord("A") + index)
if f"Man {label}" in pov_set:
full_label = f"Man {label}"
if full_label in pov_set:
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 {
"outfits": outfits,
"pose": g.choose(pose_rng, SOFTCORE_CAST_POSES),
@@ -4225,6 +4326,7 @@ def build_insta_of_pair(
character_slot_map,
soft_expression_women_count,
soft_expression_men_count,
"softcore",
)
if soft_expression_intensity is None:
soft_expression_enabled = False
@@ -4277,11 +4379,12 @@ def build_insta_of_pair(
soft_row["character_slot_status"] = "applied:Woman A"
if not soft_expression_enabled:
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["item_label"] = "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"] = (
pov_character_labels
if options["softcore_cast"] == "same_as_hardcore"
@@ -4319,6 +4422,7 @@ def build_insta_of_pair(
expression_enabled=options["hardcore_expression_enabled"],
expression_intensity=options["hardcore_expression_intensity"],
character_cast=character_cast or "",
expression_phase="hardcore",
)
hard_row["hardcore_detail_density"] = options["hardcore_detail_density"]
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_men_count if options["softcore_cast"] == "same_as_hardcore" else 0,
pov_character_labels if options["softcore_cast"] == "same_as_hardcore" else [],
character_slot_map,
)
if options["softcore_cast"] != "same_as_hardcore":
soft_partner_styling = {"outfits": [], "pose": ""}
@@ -4394,10 +4499,19 @@ def build_insta_of_pair(
else ""
)
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"],
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_directive = {
"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_prompt_directive": pov_directive,
"softcore_partner_styling": soft_partner_styling,
"character_hardcore_clothing": character_hardcore_clothing_entries,
"hardcore_clothing_state": hard_clothing_state,
"hardcore_detail_density": hard_detail_density,
"softcore_prompt": soft_prompt,