Mirror softcore outfit in hard clothing state
This commit is contained in:
+104
-51
@@ -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
@@ -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"))
|
||||
|
||||
Reference in New Issue
Block a user