Extract Insta pair clothing routing
This commit is contained in:
@@ -154,8 +154,7 @@ Improve later:
|
|||||||
- make a single pair metadata sanitizer that normalizes `softcore_row`,
|
- make a single pair metadata sanitizer that normalizes `softcore_row`,
|
||||||
`hardcore_row`, pair prompts, negatives, captions, and camera fields;
|
`hardcore_row`, pair prompts, negatives, captions, and camera fields;
|
||||||
- split pair assembly into small functions by phase:
|
- split pair assembly into small functions by phase:
|
||||||
`build_soft_row`, `build_hard_row`, `resolve_pair_clothing`,
|
`build_soft_row`, `build_hard_row`, `assemble_pair_metadata`.
|
||||||
`assemble_pair_metadata`.
|
|
||||||
|
|
||||||
Already isolated:
|
Already isolated:
|
||||||
|
|
||||||
@@ -163,6 +162,10 @@ Already isolated:
|
|||||||
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
|
||||||
suppression, and row/root camera metadata synchronization.
|
suppression, and row/root camera metadata synchronization.
|
||||||
|
- pair-level hardcore clothing continuity lives in `pair_clothing.py`,
|
||||||
|
including action-aware body-access flags, conflicting outfit-piece cleanup,
|
||||||
|
default visible-men clothing, character-clothing override handling, and final
|
||||||
|
root clothing-state assembly.
|
||||||
|
|
||||||
### Krea2 Formatter Path
|
### Krea2 Formatter Path
|
||||||
|
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ Core helper ownership:
|
|||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `category_library.py` | JSON category loading, subcategory normalization, named scene/expression/composition pool loading, cast compatibility filtering, exact subcategory lookup, and inheritance-based pool merging. |
|
| `category_library.py` | JSON category loading, subcategory normalization, named scene/expression/composition pool loading, cast compatibility filtering, exact subcategory lookup, and inheritance-based pool merging. |
|
||||||
| `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 hardcore clothing continuity, action-aware body-access flags, conflicting outfit-piece cleanup, default visible-men clothing, and final root clothing-state assembly. |
|
||||||
| `hardcore_role_graphs.py` | Source role graph construction for hardcore configured-cast rows, including POV-aware interaction geometry. |
|
| `hardcore_role_graphs.py` | Source role graph construction for hardcore configured-cast rows, including POV-aware interaction geometry. |
|
||||||
| `hardcore_role_fallback.py` | Solo, same-sex, mixed group fallback, and support-partner role graph wording for configured casts. |
|
| `hardcore_role_fallback.py` | Solo, same-sex, mixed group fallback, and support-partner role graph wording for configured casts. |
|
||||||
| `hardcore_role_interaction.py` | Foreplay, manual stimulation, body worship, clothing transition, dominant guidance, camera performance, aftercare, and group coordination role graph wording. |
|
| `hardcore_role_interaction.py` | Foreplay, manual stimulation, body worship, clothing transition, dominant guidance, camera performance, aftercare, and group coordination role graph wording. |
|
||||||
@@ -842,8 +843,8 @@ Use these traces to narrow a problem in one pass.
|
|||||||
2. Check hard row `item` and `source_role_graph` for access flags.
|
2. Check hard row `item` and `source_role_graph` for access flags.
|
||||||
3. Character slot `hardcore_clothing` overrides pair fallback clothing.
|
3. Character slot `hardcore_clothing` overrides pair fallback clothing.
|
||||||
4. For Krea wording, inspect `krea_clothing.natural_clothing_state`.
|
4. For Krea wording, inspect `krea_clothing.natural_clothing_state`.
|
||||||
5. For generation wording, inspect `_insta_of_hardcore_clothing_state`,
|
5. For generation wording, inspect `pair_clothing.py` and
|
||||||
`_hardcore_row_access_flags`, and `character_hardcore_clothing_values`.
|
`character_hardcore_clothing_values`.
|
||||||
|
|
||||||
### Softcore contains strange no-contact or bed/action leakage
|
### Softcore contains strange no-contact or bed/action leakage
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,345 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import re
|
||||||
|
from typing import Any, Callable
|
||||||
|
|
||||||
|
|
||||||
|
WOMAN_LOWER_ACCESS_TERMS = (
|
||||||
|
"penetrat",
|
||||||
|
"thrust",
|
||||||
|
"vaginal",
|
||||||
|
"anal",
|
||||||
|
"rear-entry",
|
||||||
|
"rear entry",
|
||||||
|
"front-and-back",
|
||||||
|
"front and back",
|
||||||
|
"double",
|
||||||
|
"doggy",
|
||||||
|
"missionary",
|
||||||
|
"cowgirl",
|
||||||
|
"straddles",
|
||||||
|
"hips aligned",
|
||||||
|
"penis into",
|
||||||
|
"penis inside",
|
||||||
|
"penis entering",
|
||||||
|
"mouth on her pussy",
|
||||||
|
"mouth pressed to her pussy",
|
||||||
|
"pussy licking",
|
||||||
|
"cunnilingus",
|
||||||
|
"thighs spread",
|
||||||
|
"thighs open",
|
||||||
|
"legs spread",
|
||||||
|
"legs open",
|
||||||
|
"cum on pussy",
|
||||||
|
"cum across her pussy",
|
||||||
|
"cum dripping from pussy",
|
||||||
|
"cum dripping from ass",
|
||||||
|
"cum on belly",
|
||||||
|
"cum on thighs",
|
||||||
|
"cum across her ass",
|
||||||
|
"cum across her lower back",
|
||||||
|
"toy aligned",
|
||||||
|
"second penetration point",
|
||||||
|
)
|
||||||
|
|
||||||
|
WOMAN_UPPER_ACCESS_TERMS = (
|
||||||
|
"boobjob",
|
||||||
|
"titjob",
|
||||||
|
"breast sex",
|
||||||
|
"breasts around",
|
||||||
|
"breasts tightly",
|
||||||
|
"hands pressing both breasts",
|
||||||
|
"breasts together",
|
||||||
|
"cum on breasts",
|
||||||
|
"cum across her breasts",
|
||||||
|
"cum on chest",
|
||||||
|
)
|
||||||
|
|
||||||
|
MAN_LOWER_ACCESS_TERMS = (
|
||||||
|
"penis",
|
||||||
|
"glans",
|
||||||
|
"testicle",
|
||||||
|
"balls",
|
||||||
|
"cumshot",
|
||||||
|
"ejaculat",
|
||||||
|
"semen",
|
||||||
|
"boobjob",
|
||||||
|
"titjob",
|
||||||
|
"breast sex",
|
||||||
|
"footjob",
|
||||||
|
"handjob",
|
||||||
|
"hand job",
|
||||||
|
"hand wrapped",
|
||||||
|
"hand stroking",
|
||||||
|
"blowjob",
|
||||||
|
"fellatio",
|
||||||
|
"penis sucking",
|
||||||
|
"penis in mouth",
|
||||||
|
"mouth on penis",
|
||||||
|
"penis licking",
|
||||||
|
)
|
||||||
|
|
||||||
|
LOWER_BODY_CLOTHING_TERMS = (
|
||||||
|
"panty",
|
||||||
|
"panties",
|
||||||
|
"brief",
|
||||||
|
"briefs",
|
||||||
|
"thong",
|
||||||
|
"bottom",
|
||||||
|
"bottoms",
|
||||||
|
"bodysuit",
|
||||||
|
"teddy",
|
||||||
|
"dress",
|
||||||
|
"skirt",
|
||||||
|
"shorts",
|
||||||
|
"jeans",
|
||||||
|
"trousers",
|
||||||
|
"pants",
|
||||||
|
"bikini",
|
||||||
|
"towel",
|
||||||
|
"sheet",
|
||||||
|
"blanket",
|
||||||
|
)
|
||||||
|
|
||||||
|
UPPER_BODY_CLOTHING_TERMS = (
|
||||||
|
"bra",
|
||||||
|
"cup",
|
||||||
|
"cups",
|
||||||
|
"corset",
|
||||||
|
"bodysuit",
|
||||||
|
"bustier",
|
||||||
|
"top",
|
||||||
|
"camisole",
|
||||||
|
"shirt",
|
||||||
|
"blouse",
|
||||||
|
"bodice",
|
||||||
|
"dress",
|
||||||
|
"robe",
|
||||||
|
"jacket",
|
||||||
|
"sweater",
|
||||||
|
"harness",
|
||||||
|
"chest",
|
||||||
|
"cleavage",
|
||||||
|
"panel",
|
||||||
|
"panels",
|
||||||
|
)
|
||||||
|
|
||||||
|
INSTA_OF_HARDCORE_MEN_CLOTHING_LOWER_ACCESS = [
|
||||||
|
"wears an open button shirt with jeans lowered below the hips for genital access",
|
||||||
|
"wears a fitted tee pushed up with trousers lowered below the hips",
|
||||||
|
"keeps a dark shirt on while pants and underwear are pulled down below the hips",
|
||||||
|
"wears an open overshirt with jeans pushed down at the thighs",
|
||||||
|
"wears a hoodie lifted at the waist with sweatpants lowered below the hips",
|
||||||
|
"wears gym shorts pulled down below the hips with his shirt still on",
|
||||||
|
"keeps a casual shirt on with belt open and pants lowered below the hips",
|
||||||
|
"wears a half-open shirt with lower garments pushed down below the hips",
|
||||||
|
]
|
||||||
|
|
||||||
|
INSTA_OF_HARDCORE_MEN_CLOTHING_VISIBLE = [
|
||||||
|
"wears an open button shirt with jeans unfastened",
|
||||||
|
"wears a fitted tee with pants opened at the waist",
|
||||||
|
"keeps a dark shirt on with trousers loosened",
|
||||||
|
"wears an open overshirt with jeans partly lowered",
|
||||||
|
"wears gym shorts loose at the waist with a towel nearby",
|
||||||
|
"wears a hoodie lifted at the waist with sweatpants loosened",
|
||||||
|
"wears a casual shirt with belt open and pants partly lowered",
|
||||||
|
"wears a half-open shirt with dark trousers",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def hardcore_row_access_flags(row: dict[str, Any]) -> dict[str, bool]:
|
||||||
|
axis_values = row.get("item_axis_values")
|
||||||
|
axis_text = " ".join(str(value) for value in axis_values.values()) if isinstance(axis_values, dict) else ""
|
||||||
|
role_text = " ".join(
|
||||||
|
str(part or "")
|
||||||
|
for part in (
|
||||||
|
row.get("source_role_graph"),
|
||||||
|
row.get("role_graph"),
|
||||||
|
)
|
||||||
|
).lower()
|
||||||
|
detail_text = " ".join(
|
||||||
|
str(part or "")
|
||||||
|
for part in (
|
||||||
|
row.get("item"),
|
||||||
|
row.get("source_composition"),
|
||||||
|
row.get("composition"),
|
||||||
|
axis_text,
|
||||||
|
)
|
||||||
|
).lower()
|
||||||
|
full_text = f"{role_text} {detail_text}"
|
||||||
|
return {
|
||||||
|
"woman_lower": any(term in role_text for term in WOMAN_LOWER_ACCESS_TERMS),
|
||||||
|
"woman_upper": any(term in full_text for term in WOMAN_UPPER_ACCESS_TERMS),
|
||||||
|
"man_lower": any(term in role_text for term in MAN_LOWER_ACCESS_TERMS),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _outfit_without_lower_body_blockers(outfit: str) -> 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)
|
||||||
|
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:
|
||||||
|
fragment = fragment.strip(" ,.;")
|
||||||
|
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:
|
||||||
|
key = re.sub(r"\W+", " ", fragment.lower()).strip()
|
||||||
|
if key and key not in seen:
|
||||||
|
deduped.append(fragment)
|
||||||
|
seen.add(key)
|
||||||
|
return ", ".join(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
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
def hardcore_clothing_state(
|
||||||
|
mode: str,
|
||||||
|
softcore_outfit: str,
|
||||||
|
continuity_map: dict[str, str],
|
||||||
|
woman_access: str = "",
|
||||||
|
) -> str:
|
||||||
|
mode = mode if mode in continuity_map else "none"
|
||||||
|
outfit = str(softcore_outfit or "").strip()
|
||||||
|
if mode == "none" or not outfit:
|
||||||
|
return ""
|
||||||
|
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: {base}; teaser outfit detail: {outfit}."
|
||||||
|
|
||||||
|
|
||||||
|
def default_man_hardcore_clothing_entries(
|
||||||
|
men_count: int,
|
||||||
|
pov_labels: list[str] | None,
|
||||||
|
configured_entries: list[str],
|
||||||
|
rng: Any,
|
||||||
|
needs_lower_access: bool,
|
||||||
|
choose: Callable[[Any, list[str]], str],
|
||||||
|
sentence_builder: Callable[[str, str], str],
|
||||||
|
) -> list[str]:
|
||||||
|
pov_set = set(pov_labels or [])
|
||||||
|
configured_labels = {
|
||||||
|
match.group(1)
|
||||||
|
for entry in configured_entries
|
||||||
|
for match in [re.match(r"^\s*(Man [A-Z])\b", str(entry or ""))]
|
||||||
|
if match
|
||||||
|
}
|
||||||
|
pool = INSTA_OF_HARDCORE_MEN_CLOTHING_LOWER_ACCESS if needs_lower_access else INSTA_OF_HARDCORE_MEN_CLOTHING_VISIBLE
|
||||||
|
entries = []
|
||||||
|
for index in range(max(0, int(men_count))):
|
||||||
|
label = f"Man {chr(ord('A') + index)}"
|
||||||
|
if label in pov_set or label in configured_labels:
|
||||||
|
continue
|
||||||
|
entries.append(sentence_builder(label, choose(rng, pool)))
|
||||||
|
return entries
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_hardcore_pair_clothing(
|
||||||
|
*,
|
||||||
|
hard_row: dict[str, Any],
|
||||||
|
mode: str,
|
||||||
|
softcore_outfit: str,
|
||||||
|
character_hardcore_clothing_entries: list[str],
|
||||||
|
men_count: int,
|
||||||
|
pov_labels: list[str] | None,
|
||||||
|
rng: Any,
|
||||||
|
continuity_map: dict[str, str],
|
||||||
|
choose: Callable[[Any, list[str]], str],
|
||||||
|
sentence_builder: Callable[[str, str], str],
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
access_flags = hardcore_row_access_flags(hard_row)
|
||||||
|
woman_access = "lower" if access_flags["woman_lower"] else "upper" if access_flags["woman_upper"] else ""
|
||||||
|
default_man_entries = default_man_hardcore_clothing_entries(
|
||||||
|
men_count,
|
||||||
|
pov_labels,
|
||||||
|
character_hardcore_clothing_entries,
|
||||||
|
rng,
|
||||||
|
access_flags["man_lower"],
|
||||||
|
choose,
|
||||||
|
sentence_builder,
|
||||||
|
)
|
||||||
|
has_primary_hardcore_clothing = any(entry.startswith("Woman A") for entry in character_hardcore_clothing_entries)
|
||||||
|
fallback_state = "" if has_primary_hardcore_clothing else hardcore_clothing_state(
|
||||||
|
mode,
|
||||||
|
softcore_outfit,
|
||||||
|
continuity_map,
|
||||||
|
woman_access=woman_access,
|
||||||
|
)
|
||||||
|
hard_clothing_parts = [
|
||||||
|
part.strip().rstrip(".")
|
||||||
|
for part in (
|
||||||
|
fallback_state,
|
||||||
|
*character_hardcore_clothing_entries,
|
||||||
|
*default_man_entries,
|
||||||
|
)
|
||||||
|
if str(part or "").strip()
|
||||||
|
]
|
||||||
|
hard_clothing_state = "; ".join(hard_clothing_parts)
|
||||||
|
return {
|
||||||
|
"access_flags": access_flags,
|
||||||
|
"woman_access": woman_access,
|
||||||
|
"default_man_hardcore_clothing": default_man_entries,
|
||||||
|
"hardcore_clothing_state": hard_clothing_state,
|
||||||
|
"hardcore_clothing_sentence": f"{hard_clothing_state}. " if hard_clothing_state else "",
|
||||||
|
"requires_body_exposure_scene": (
|
||||||
|
"body is fully exposed" in hard_clothing_state.lower()
|
||||||
|
or "bare skin unobstructed" in hard_clothing_state.lower()
|
||||||
|
),
|
||||||
|
}
|
||||||
+17
-310
@@ -25,6 +25,7 @@ try:
|
|||||||
template_list as _template_list,
|
template_list as _template_list,
|
||||||
)
|
)
|
||||||
from . import generate_prompt_batches as g
|
from . import generate_prompt_batches as g
|
||||||
|
from . import pair_clothing
|
||||||
from . import pair_camera
|
from . import pair_camera
|
||||||
from . import scene_camera_adapters
|
from . import scene_camera_adapters
|
||||||
from .hardcore_text_cleanup import (
|
from .hardcore_text_cleanup import (
|
||||||
@@ -55,6 +56,7 @@ except ImportError: # Allows local smoke tests with `python -c`.
|
|||||||
template_list as _template_list,
|
template_list as _template_list,
|
||||||
)
|
)
|
||||||
import generate_prompt_batches as g
|
import generate_prompt_batches as g
|
||||||
|
import pair_clothing
|
||||||
import pair_camera
|
import pair_camera
|
||||||
import scene_camera_adapters
|
import scene_camera_adapters
|
||||||
from hardcore_text_cleanup import (
|
from hardcore_text_cleanup import (
|
||||||
@@ -6922,290 +6924,6 @@ def _insta_of_softcore_pose(rng: random.Random, level: str) -> str:
|
|||||||
return g.choose(rng, pool)
|
return g.choose(rng, pool)
|
||||||
|
|
||||||
|
|
||||||
WOMAN_LOWER_ACCESS_TERMS = (
|
|
||||||
"penetrat",
|
|
||||||
"thrust",
|
|
||||||
"vaginal",
|
|
||||||
"anal",
|
|
||||||
"rear-entry",
|
|
||||||
"rear entry",
|
|
||||||
"front-and-back",
|
|
||||||
"front and back",
|
|
||||||
"double",
|
|
||||||
"doggy",
|
|
||||||
"missionary",
|
|
||||||
"cowgirl",
|
|
||||||
"straddles",
|
|
||||||
"hips aligned",
|
|
||||||
"penis into",
|
|
||||||
"penis inside",
|
|
||||||
"penis entering",
|
|
||||||
"mouth on her pussy",
|
|
||||||
"mouth pressed to her pussy",
|
|
||||||
"pussy licking",
|
|
||||||
"cunnilingus",
|
|
||||||
"thighs spread",
|
|
||||||
"thighs open",
|
|
||||||
"legs spread",
|
|
||||||
"legs open",
|
|
||||||
"cum on pussy",
|
|
||||||
"cum across her pussy",
|
|
||||||
"cum dripping from pussy",
|
|
||||||
"cum dripping from ass",
|
|
||||||
"cum on belly",
|
|
||||||
"cum on thighs",
|
|
||||||
"cum across her ass",
|
|
||||||
"cum across her lower back",
|
|
||||||
"toy aligned",
|
|
||||||
"second penetration point",
|
|
||||||
)
|
|
||||||
|
|
||||||
WOMAN_UPPER_ACCESS_TERMS = (
|
|
||||||
"boobjob",
|
|
||||||
"titjob",
|
|
||||||
"breast sex",
|
|
||||||
"breasts around",
|
|
||||||
"breasts tightly",
|
|
||||||
"hands pressing both breasts",
|
|
||||||
"breasts together",
|
|
||||||
"cum on breasts",
|
|
||||||
"cum across her breasts",
|
|
||||||
"cum on chest",
|
|
||||||
)
|
|
||||||
|
|
||||||
MAN_LOWER_ACCESS_TERMS = (
|
|
||||||
"penis",
|
|
||||||
"glans",
|
|
||||||
"testicle",
|
|
||||||
"balls",
|
|
||||||
"cumshot",
|
|
||||||
"ejaculat",
|
|
||||||
"semen",
|
|
||||||
"boobjob",
|
|
||||||
"titjob",
|
|
||||||
"breast sex",
|
|
||||||
"footjob",
|
|
||||||
"handjob",
|
|
||||||
"hand job",
|
|
||||||
"hand wrapped",
|
|
||||||
"hand stroking",
|
|
||||||
"blowjob",
|
|
||||||
"fellatio",
|
|
||||||
"penis sucking",
|
|
||||||
"penis in mouth",
|
|
||||||
"mouth on penis",
|
|
||||||
"penis licking",
|
|
||||||
)
|
|
||||||
|
|
||||||
LOWER_BODY_CLOTHING_TERMS = (
|
|
||||||
"panty",
|
|
||||||
"panties",
|
|
||||||
"brief",
|
|
||||||
"briefs",
|
|
||||||
"thong",
|
|
||||||
"bottom",
|
|
||||||
"bottoms",
|
|
||||||
"bodysuit",
|
|
||||||
"teddy",
|
|
||||||
"dress",
|
|
||||||
"skirt",
|
|
||||||
"shorts",
|
|
||||||
"jeans",
|
|
||||||
"trousers",
|
|
||||||
"pants",
|
|
||||||
"bikini",
|
|
||||||
"towel",
|
|
||||||
"sheet",
|
|
||||||
"blanket",
|
|
||||||
)
|
|
||||||
|
|
||||||
UPPER_BODY_CLOTHING_TERMS = (
|
|
||||||
"bra",
|
|
||||||
"cup",
|
|
||||||
"cups",
|
|
||||||
"corset",
|
|
||||||
"bodysuit",
|
|
||||||
"bustier",
|
|
||||||
"top",
|
|
||||||
"camisole",
|
|
||||||
"shirt",
|
|
||||||
"blouse",
|
|
||||||
"bodice",
|
|
||||||
"dress",
|
|
||||||
"robe",
|
|
||||||
"jacket",
|
|
||||||
"sweater",
|
|
||||||
"harness",
|
|
||||||
"chest",
|
|
||||||
"cleavage",
|
|
||||||
"panel",
|
|
||||||
"panels",
|
|
||||||
)
|
|
||||||
|
|
||||||
INSTA_OF_HARDCORE_MEN_CLOTHING_LOWER_ACCESS = [
|
|
||||||
"wears an open button shirt with jeans lowered below the hips for genital access",
|
|
||||||
"wears a fitted tee pushed up with trousers lowered below the hips",
|
|
||||||
"keeps a dark shirt on while pants and underwear are pulled down below the hips",
|
|
||||||
"wears an open overshirt with jeans pushed down at the thighs",
|
|
||||||
"wears a hoodie lifted at the waist with sweatpants lowered below the hips",
|
|
||||||
"wears gym shorts pulled down below the hips with his shirt still on",
|
|
||||||
"keeps a casual shirt on with belt open and pants lowered below the hips",
|
|
||||||
"wears a half-open shirt with lower garments pushed down below the hips",
|
|
||||||
]
|
|
||||||
|
|
||||||
INSTA_OF_HARDCORE_MEN_CLOTHING_VISIBLE = [
|
|
||||||
"wears an open button shirt with jeans unfastened",
|
|
||||||
"wears a fitted tee with pants opened at the waist",
|
|
||||||
"keeps a dark shirt on with trousers loosened",
|
|
||||||
"wears an open overshirt with jeans partly lowered",
|
|
||||||
"wears gym shorts loose at the waist with a towel nearby",
|
|
||||||
"wears a hoodie lifted at the waist with sweatpants loosened",
|
|
||||||
"wears a casual shirt with belt open and pants partly lowered",
|
|
||||||
"wears a half-open shirt with dark trousers",
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def _hardcore_row_access_flags(row: dict[str, Any]) -> dict[str, bool]:
|
|
||||||
axis_values = row.get("item_axis_values")
|
|
||||||
axis_text = " ".join(str(value) for value in axis_values.values()) if isinstance(axis_values, dict) else ""
|
|
||||||
role_text = " ".join(
|
|
||||||
str(part or "")
|
|
||||||
for part in (
|
|
||||||
row.get("source_role_graph"),
|
|
||||||
row.get("role_graph"),
|
|
||||||
)
|
|
||||||
).lower()
|
|
||||||
detail_text = " ".join(
|
|
||||||
str(part or "")
|
|
||||||
for part in (
|
|
||||||
row.get("item"),
|
|
||||||
row.get("source_composition"),
|
|
||||||
row.get("composition"),
|
|
||||||
axis_text,
|
|
||||||
)
|
|
||||||
).lower()
|
|
||||||
full_text = f"{role_text} {detail_text}"
|
|
||||||
return {
|
|
||||||
"woman_lower": any(term in role_text for term in WOMAN_LOWER_ACCESS_TERMS),
|
|
||||||
"woman_upper": any(term in full_text for term in WOMAN_UPPER_ACCESS_TERMS),
|
|
||||||
"man_lower": any(term in role_text for term in MAN_LOWER_ACCESS_TERMS),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def _outfit_without_lower_body_blockers(outfit: str) -> 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)
|
|
||||||
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:
|
|
||||||
fragment = fragment.strip(" ,.;")
|
|
||||||
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:
|
|
||||||
key = re.sub(r"\W+", " ", fragment.lower()).strip()
|
|
||||||
if key and key not in seen:
|
|
||||||
deduped.append(fragment)
|
|
||||||
seen.add(key)
|
|
||||||
return ", ".join(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
|
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
def _insta_of_hardcore_clothing_state(mode: str, softcore_outfit: str, woman_access: str = "") -> str:
|
|
||||||
mode = mode if mode in INSTA_OF_HARDCORE_CLOTHING_CONTINUITY else "none"
|
|
||||||
outfit = str(softcore_outfit or "").strip()
|
|
||||||
if mode == "none" or not outfit:
|
|
||||||
return ""
|
|
||||||
base = INSTA_OF_HARDCORE_CLOTHING_CONTINUITY[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: {base}; teaser outfit detail: {outfit}."
|
|
||||||
|
|
||||||
|
|
||||||
def _default_man_hardcore_clothing_entries(
|
|
||||||
men_count: int,
|
|
||||||
pov_labels: list[str] | None,
|
|
||||||
configured_entries: list[str],
|
|
||||||
rng: random.Random,
|
|
||||||
needs_lower_access: bool,
|
|
||||||
) -> list[str]:
|
|
||||||
pov_set = set(pov_labels or [])
|
|
||||||
configured_labels = {
|
|
||||||
match.group(1)
|
|
||||||
for entry in configured_entries
|
|
||||||
for match in [re.match(r"^\s*(Man [A-Z])\b", str(entry or ""))]
|
|
||||||
if match
|
|
||||||
}
|
|
||||||
pool = INSTA_OF_HARDCORE_MEN_CLOTHING_LOWER_ACCESS if needs_lower_access else INSTA_OF_HARDCORE_MEN_CLOTHING_VISIBLE
|
|
||||||
entries = []
|
|
||||||
for index in range(max(0, int(men_count))):
|
|
||||||
label = f"Man {chr(ord('A') + index)}"
|
|
||||||
if label in pov_set or label in configured_labels:
|
|
||||||
continue
|
|
||||||
entries.append(_hardcore_clothing_sentence(label, g.choose(rng, pool)))
|
|
||||||
return entries
|
|
||||||
|
|
||||||
|
|
||||||
def _insta_of_partner_styling(
|
def _insta_of_partner_styling(
|
||||||
seed_config: dict[str, int],
|
seed_config: dict[str, int],
|
||||||
seed: int,
|
seed: int,
|
||||||
@@ -7506,33 +7224,22 @@ def build_insta_of_pair(
|
|||||||
pov_character_labels,
|
pov_character_labels,
|
||||||
hard_content_rng,
|
hard_content_rng,
|
||||||
)
|
)
|
||||||
access_flags = _hardcore_row_access_flags(hard_row)
|
clothing_route = pair_clothing.resolve_hardcore_pair_clothing(
|
||||||
woman_access = "lower" if access_flags["woman_lower"] else "upper" if access_flags["woman_upper"] else ""
|
hard_row=hard_row,
|
||||||
default_man_hardcore_clothing_entries = _default_man_hardcore_clothing_entries(
|
mode=options["hardcore_clothing_continuity"],
|
||||||
hard_men_count,
|
softcore_outfit=soft_row["item"],
|
||||||
pov_character_labels,
|
character_hardcore_clothing_entries=character_hardcore_clothing_entries,
|
||||||
character_hardcore_clothing_entries,
|
men_count=hard_men_count,
|
||||||
hard_content_rng,
|
pov_labels=pov_character_labels,
|
||||||
access_flags["man_lower"],
|
rng=hard_content_rng,
|
||||||
|
continuity_map=INSTA_OF_HARDCORE_CLOTHING_CONTINUITY,
|
||||||
|
choose=g.choose,
|
||||||
|
sentence_builder=_hardcore_clothing_sentence,
|
||||||
)
|
)
|
||||||
has_primary_hardcore_clothing = any(entry.startswith("Woman A") for entry in character_hardcore_clothing_entries)
|
default_man_hardcore_clothing_entries = clothing_route["default_man_hardcore_clothing"]
|
||||||
fallback_hard_clothing_state = "" if has_primary_hardcore_clothing else _insta_of_hardcore_clothing_state(
|
hard_clothing_state = clothing_route["hardcore_clothing_state"]
|
||||||
options["hardcore_clothing_continuity"],
|
hard_clothing_sentence = clothing_route["hardcore_clothing_sentence"]
|
||||||
soft_row["item"],
|
if clothing_route["requires_body_exposure_scene"]:
|
||||||
woman_access=woman_access,
|
|
||||||
)
|
|
||||||
hard_clothing_parts = [
|
|
||||||
part.strip().rstrip(".")
|
|
||||||
for part in (
|
|
||||||
fallback_hard_clothing_state,
|
|
||||||
*character_hardcore_clothing_entries,
|
|
||||||
*default_man_hardcore_clothing_entries,
|
|
||||||
)
|
|
||||||
if str(part or "").strip()
|
|
||||||
]
|
|
||||||
hard_clothing_state = "; ".join(hard_clothing_parts)
|
|
||||||
hard_clothing_sentence = f"{hard_clothing_state}. " if hard_clothing_state else ""
|
|
||||||
if "body is fully exposed" in hard_clothing_state.lower() or "bare skin unobstructed" in hard_clothing_state.lower():
|
|
||||||
hard_scene = _body_exposure_scene_text(hard_scene)
|
hard_scene = _body_exposure_scene_text(hard_scene)
|
||||||
hard_row["source_scene_text"] = hard_row.get("source_scene_text") or hard_row.get("scene_text", "")
|
hard_row["source_scene_text"] = hard_row.get("source_scene_text") or hard_row.get("scene_text", "")
|
||||||
hard_row["scene_text"] = hard_scene
|
hard_row["scene_text"] = hard_scene
|
||||||
|
|||||||
@@ -679,6 +679,9 @@ def smoke_krea_pair_clothing_state() -> None:
|
|||||||
krea = krea_formatter.format_krea2_prompt("", metadata_json=_json(pair), target="hardcore")
|
krea = krea_formatter.format_krea2_prompt("", metadata_json=_json(pair), target="hardcore")
|
||||||
prompt = _expect_text("krea_pair_clothing_state.krea_prompt", krea.get("krea_prompt"), 60)
|
prompt = _expect_text("krea_pair_clothing_state.krea_prompt", krea.get("krea_prompt"), 60)
|
||||||
lower = prompt.lower()
|
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(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("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("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("visual clothing state" not in lower, "Krea clothing route fell back to visual clothing state label")
|
||||||
|
|||||||
Reference in New Issue
Block a user