Mirror softcore outfit in hard clothing state

This commit is contained in:
2026-06-28 10:14:51 +02:00
parent 5acda5227c
commit f681fe2949
2 changed files with 122 additions and 54 deletions
+104 -51
View File
@@ -269,12 +269,36 @@ def hardcore_row_access_flags(row: dict[str, Any]) -> dict[str, bool]:
def _outfit_without_lower_body_blockers(outfit: str) -> str:
_removed, remaining = _outfit_split_by_terms(
outfit,
LOWER_BODY_CLOTHING_TERMS,
replacements=(
(r"\blingerie set\b", "lingerie top details"),
(r"\bbrief set\b", "bra set"),
(r"\bbodysuit with\b", "upper bodysuit detail with"),
),
)
return remaining
def _outfit_without_upper_body_blockers(outfit: str) -> str:
_removed, remaining = _outfit_split_by_terms(
outfit,
UPPER_BODY_CLOTHING_TERMS,
replacements=(
(r"\blingerie set\b", "lingerie styling"),
(r"\bbalconette bra and brief set\b", "briefs and garter styling"),
),
)
return remaining
def _split_outfit_fragments(outfit: str, replacements: tuple[tuple[str, str], ...] = ()) -> list[str]:
text = str(outfit or "").strip()
if not text:
return ""
text = re.sub(r"\blingerie set\b", "lingerie top details", text, flags=re.IGNORECASE)
text = re.sub(r"\bbrief set\b", "bra set", text, flags=re.IGNORECASE)
text = re.sub(r"\bbodysuit with\b", "upper bodysuit detail with", text, flags=re.IGNORECASE)
return []
for pattern, replacement in replacements:
text = re.sub(pattern, replacement, text, flags=re.IGNORECASE)
fragments = re.split(r"\s*,\s*|\s+\band\b\s+|\s+\bwith\b\s+|\s+\bunder\b\s+|\s+\bover\b\s+", text)
kept = []
for fragment in fragments:
@@ -282,12 +306,7 @@ def _outfit_without_lower_body_blockers(outfit: str) -> str:
fragment = re.sub(r"^(?:and|with|under|over)\s+", "", fragment, flags=re.IGNORECASE)
if not fragment:
continue
lower = fragment.lower()
if any(term in lower for term in LOWER_BODY_CLOTHING_TERMS):
continue
kept.append(fragment)
if not kept:
return ""
deduped = []
seen = set()
for fragment in kept:
@@ -295,36 +314,82 @@ def _outfit_without_lower_body_blockers(outfit: str) -> str:
if key and key not in seen:
deduped.append(fragment)
seen.add(key)
return ", ".join(deduped)
return deduped
def _outfit_without_upper_body_blockers(outfit: str) -> str:
text = str(outfit or "").strip()
if not text:
return ""
text = re.sub(r"\blingerie set\b", "lingerie styling", text, flags=re.IGNORECASE)
text = re.sub(r"\bbalconette bra and brief set\b", "briefs and garter styling", text, flags=re.IGNORECASE)
fragments = re.split(r"\s*,\s*|\s+\band\s+|\s+\bwith\s+|\s+\bunder\s+|\s+\bover\s+", text)
kept = []
for fragment in fragments:
fragment = fragment.strip(" ,.;")
fragment = re.sub(r"^(?:and|with|under|over)\s+", "", fragment, flags=re.IGNORECASE)
if not fragment:
continue
def _join_fragments(fragments: list[str]) -> str:
return ", ".join(fragment for fragment in fragments if fragment)
def _outfit_split_by_terms(
outfit: str,
terms: tuple[str, ...],
replacements: tuple[tuple[str, str], ...] = (),
) -> tuple[str, str]:
removed: list[str] = []
remaining: list[str] = []
for fragment in _split_outfit_fragments(outfit, replacements):
lower = fragment.lower()
if any(term in lower for term in UPPER_BODY_CLOTHING_TERMS):
continue
kept.append(fragment)
if not kept:
return ""
deduped = []
seen = set()
for fragment in kept:
key = re.sub(r"\W+", " ", fragment.lower()).strip()
if key and key not in seen:
deduped.append(fragment)
seen.add(key)
return ", ".join(deduped)
if any(term in lower for term in terms):
removed.append(fragment)
else:
remaining.append(fragment)
return _join_fragments(removed), _join_fragments(remaining)
def _is_plural_clothing_phrase(text: str) -> bool:
lower = text.lower()
if "," in text or " and " in lower:
return True
return any(term in lower for term in ("briefs", "panties", "shorts", "jeans", "trousers", "pants", "stockings"))
def _partially_removed_outfit_state(outfit: str, woman_access: str, implied: bool = False) -> str:
outfit = str(outfit or "").strip()
if not outfit:
return "Woman A's body is partly exposed" if implied else "Woman A's outfit is pushed aside where needed"
if woman_access == "lower":
removed, remaining = _outfit_split_by_terms(
outfit,
LOWER_BODY_CLOTHING_TERMS,
replacements=(
(r"\blingerie set\b", "lingerie top details"),
(r"\bbrief set\b", "bra set"),
(r"\bbodysuit with\b", "upper bodysuit detail with"),
),
)
verb = "are" if _is_plural_clothing_phrase(removed) else "is"
lead = (
f"Woman A's {removed} {verb} pulled aside or removed below the hips"
if removed
else "Woman A's lower body is clear, with the outfit pulled aside below the hips"
)
if remaining:
remain_verb = "remain" if _is_plural_clothing_phrase(remaining) else "remains"
return f"{lead}; {remaining} {remain_verb} visible from the same outfit"
return lead
if woman_access == "upper":
removed, remaining = _outfit_split_by_terms(
outfit,
UPPER_BODY_CLOTHING_TERMS,
replacements=(
(r"\blingerie set\b", "lingerie styling"),
(r"\bbalconette bra and brief set\b", "briefs and garter styling"),
),
)
verb = "are" if _is_plural_clothing_phrase(removed) else "is"
lead = (
f"Woman A's {removed} {verb} pulled open or pushed aside from her breasts and chest"
if removed
else "Woman A's upper body is clear, with the outfit pulled open at the chest"
)
if remaining:
remain_verb = "remain" if _is_plural_clothing_phrase(remaining) else "remains"
return f"{lead}; {remaining} {remain_verb} visible from the same outfit"
return lead
if implied:
return f"Woman A's {outfit} is loosened and partly slipping off, leaving her body partly exposed"
return f"Woman A's {outfit} is pushed aside and partly removed where needed"
def hardcore_clothing_state(
@@ -340,22 +405,10 @@ def hardcore_clothing_state(
base = continuity_map[mode]
if mode == "explicit_nude":
return f"Body exposure: {base}."
if mode == "implied_nude":
return f"Body exposure: {base}."
if mode == "partially_removed" and woman_access == "lower":
detail = _outfit_without_lower_body_blockers(outfit)
base = "Woman A's lower body is clear; any lower garment is pulled aside or removed below the hips"
if detail:
return f"Clothing state: {base}; visible remaining styling: {detail}."
return f"Clothing state: {base}."
if mode == "partially_removed" and woman_access == "upper":
detail = _outfit_without_upper_body_blockers(outfit)
base = "Woman A's breasts and upper body are clear; any bra cup, bodice, or top panel is pulled aside or removed"
if detail:
return f"Clothing state: {base}; visible remaining styling: {detail}."
return f"Clothing state: {base}."
if mode == "partially_removed":
return f"Clothing state: Woman A keeps the outfit mostly on; teaser outfit detail: {outfit}."
return f"Clothing state: {_partially_removed_outfit_state(outfit, woman_access)}."
if mode == "implied_nude":
return f"Clothing state: {_partially_removed_outfit_state(outfit, woman_access, implied=True)}."
return f"Clothing state: {base}; teaser outfit detail: {outfit}."
+18 -3
View File
@@ -6297,12 +6297,24 @@ def smoke_pair_route_policy() -> None:
_expect(clothing_route.as_dict() == clothing_legacy, "Typed pair clothing route should match legacy dict route")
_expect(clothing_route.woman_access == "lower", "Typed pair clothing route lost lower-access detection")
_expect(clothing_route.requires_body_exposure_scene is True, "Typed pair clothing route lost exposure-scene flag")
partial_common = {**clothing_common, "mode": "partially_removed"}
continuity_outfit = "button-down shirt tied at the waist over a fitted bralette and denim shorts"
partial_common = {**clothing_common, "mode": "partially_removed", "softcore_outfit": continuity_outfit}
partial_route = pair_clothing.resolve_hardcore_pair_clothing_result(**partial_common)
_expect(partial_route.requires_body_exposure_scene is True, "Partial lower-access clothing should request scene cleanup")
partial_lower = partial_route.hardcore_clothing_state.lower()
_expect("denim shorts" in partial_lower, "Partial lower-access clothing should name removed lower softcore garment")
_expect("below the hips" in partial_lower, "Partial lower-access clothing should describe lower-garment removal")
_expect("button-down shirt" in partial_lower and "fitted bralette" in partial_lower, "Partial lower-access clothing lost remaining softcore outfit styling")
implied_route = pair_clothing.resolve_hardcore_pair_clothing_result(
**{**clothing_common, "mode": "implied_nude", "softcore_outfit": continuity_outfit}
)
implied_lower = implied_route.hardcore_clothing_state.lower()
_expect("fabric slipping off" not in implied_lower, "Implied nude clothing should not fall back to generic fabric slipping")
_expect("denim shorts" in implied_lower and "fitted bralette" in implied_lower, "Implied nude clothing should mirror softcore outfit pieces")
structured_axis_clothing = pair_clothing.resolve_hardcore_pair_clothing_result(
**{
**clothing_common,
"softcore_outfit": continuity_outfit,
"hard_row": {
"role_graph": "generic adult action",
"item": "generic configured action",
@@ -6495,13 +6507,16 @@ def smoke_krea_pair_clothing_state() -> None:
prompt = _expect_text("krea_pair_clothing_state.krea_prompt", krea.get("krea_prompt"), 60)
lower = prompt.lower()
root_clothing = _clean_key(pair.get("hardcore_clothing_state"))
_expect("lower body is clear" in root_clothing, "pair root clothing state lost lower-body access wording")
_expect(
"below the hips" in root_clothing,
"pair root clothing state lost lower-body removal wording",
)
_expect(pair.get("default_man_hardcore_clothing"), "pair root default man hardcore clothing is missing")
_expect("metadata" in krea.get("method", ""), "pair clothing route did not use metadata")
_expect("clothing state:" not in lower, "Krea clothing route leaked raw clothing label")
_expect("visual clothing state" not in lower, "Krea clothing route fell back to visual clothing state label")
_expect("softcore outfit" not in lower and "teaser outfit" not in lower, "Krea clothing route leaked softcore outfit label")
_expect("lower body is clear" in lower, "Krea clothing route lost generated clothing continuity")
_expect("below the hips" in lower, "Krea clothing route lost generated lower-body clothing continuity")
_expect("the man keeps" in lower, "Krea clothing route lost partner clothing continuity")
_expect("outfit racks" not in lower and "shoe shelves" not in lower, "Krea pair formatter leaked unsanitized hard scene")
hard_scene = _clean_key(pair["hardcore_row"].get("scene_text"))