Extract fallback role graph wording
This commit is contained in:
@@ -102,6 +102,9 @@ Already isolated:
|
|||||||
- hardcore configured-cast role graph generation lives in
|
- hardcore configured-cast role graph generation lives in
|
||||||
`hardcore_role_graphs.py`; `prompt_builder.py` selects item/axis metadata and
|
`hardcore_role_graphs.py`; `prompt_builder.py` selects item/axis metadata and
|
||||||
then asks that module for the source role graph.
|
then asks that module for the source role graph.
|
||||||
|
- fallback role graph wording lives in `hardcore_role_fallback.py`, covering
|
||||||
|
solo rows, women-only rows, men-only rows, mixed group fallbacks, and support
|
||||||
|
partner sentences.
|
||||||
- interaction-style role graph wording lives in `hardcore_role_interaction.py`,
|
- interaction-style role graph wording lives in `hardcore_role_interaction.py`,
|
||||||
covering foreplay, manual stimulation, body worship, clothing transitions,
|
covering foreplay, manual stimulation, body worship, clothing transitions,
|
||||||
dominant guidance, camera performance, aftercare, and group coordination.
|
dominant guidance, camera performance, aftercare, and group coordination.
|
||||||
@@ -323,6 +326,8 @@ Near-term:
|
|||||||
so source aftermath placement and formatter details cannot drift apart.
|
so source aftermath placement and formatter details cannot drift apart.
|
||||||
- Cover generated interaction routes through Krea, SDXL, and natural caption
|
- Cover generated interaction routes through Krea, SDXL, and natural caption
|
||||||
outputs so source contact/guidance/presentation wording stays metadata-driven.
|
outputs so source contact/guidance/presentation wording stays metadata-driven.
|
||||||
|
- Cover generated fallback role routes through Krea, SDXL, and natural caption
|
||||||
|
outputs so solo and same-sex paths do not remain untested edge behavior.
|
||||||
|
|
||||||
Medium-term:
|
Medium-term:
|
||||||
|
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ Core helper ownership:
|
|||||||
| Python module | What it owns |
|
| Python module | What it owns |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `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_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. |
|
||||||
| `hardcore_role_oral.py` | Oral-sex role graph wording for kneeling, face-sitting, sixty-nine, edge-supported, side-lying, chair, standing, and reclining oral geometry. |
|
| `hardcore_role_oral.py` | Oral-sex role graph wording for kneeling, face-sitting, sixty-nine, edge-supported, side-lying, chair, standing, and reclining oral geometry. |
|
||||||
| `hardcore_role_outercourse.py` | Outercourse role graph wording for boobjob, testicle-sucking, penis-licking, handjob, and footjob geometry. |
|
| `hardcore_role_outercourse.py` | Outercourse role graph wording for boobjob, testicle-sucking, penis-licking, handjob, and footjob geometry. |
|
||||||
@@ -756,6 +757,8 @@ pair metadata through the core Python APIs, then verifies:
|
|||||||
- interaction routes keep source role graphs, Krea prose, SDXL tags, and
|
- interaction routes keep source role graphs, Krea prose, SDXL tags, and
|
||||||
training captions synchronized for manual, clothing transition, body worship,
|
training captions synchronized for manual, clothing transition, body worship,
|
||||||
camera-performance, aftercare, and group-coordination rows;
|
camera-performance, aftercare, and group-coordination rows;
|
||||||
|
- fallback role routes keep solo, women-only, men-only, and mixed-threesome
|
||||||
|
source role graphs synchronized with Krea, SDXL, and training-caption outputs;
|
||||||
- expression-disabled rows do not fall back to generated expression text.
|
- expression-disabled rows do not fall back to generated expression text.
|
||||||
- static formatter metadata fixtures keep source-provided action families
|
- static formatter metadata fixtures keep source-provided action families
|
||||||
stable across Krea2 prose, SDXL tags, and natural captions even when raw item
|
stable across Krea2 prose, SDXL tags, and natural captions even when raw item
|
||||||
|
|||||||
@@ -0,0 +1,121 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import random
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
try:
|
||||||
|
from .hardcore_role_interaction import build_group_coordination_role_graph, build_manual_role_graph
|
||||||
|
except ImportError: # Allows local smoke tests with `python -c`.
|
||||||
|
from hardcore_role_interaction import build_group_coordination_role_graph, build_manual_role_graph
|
||||||
|
|
||||||
|
|
||||||
|
def build_support_sentence(rng: random.Random, people: list[str], exclude: set[str]) -> str:
|
||||||
|
extras = [person for person in people if person not in exclude]
|
||||||
|
if not extras:
|
||||||
|
return ""
|
||||||
|
extra = rng.choice(extras)
|
||||||
|
actions = [
|
||||||
|
"kisses and grips the nearest body",
|
||||||
|
"holds hips open for the camera",
|
||||||
|
"touches breasts, thighs, and stomach",
|
||||||
|
"keeps one hand on a partner's ass",
|
||||||
|
"watches close and joins the body contact",
|
||||||
|
"presses in from the side with hands on skin",
|
||||||
|
]
|
||||||
|
return f" {extra} {rng.choice(actions)}."
|
||||||
|
|
||||||
|
|
||||||
|
def build_solo_role_graph(
|
||||||
|
solo: str,
|
||||||
|
women_count: int,
|
||||||
|
slug: str,
|
||||||
|
item_text: str = "",
|
||||||
|
item_axis_values: dict[str, Any] | None = None,
|
||||||
|
) -> str:
|
||||||
|
if women_count == 1:
|
||||||
|
if "manual_stimulation" in slug:
|
||||||
|
return build_manual_role_graph(solo, item_text=item_text, item_axis_values=item_axis_values)
|
||||||
|
if "camera_performance" in slug:
|
||||||
|
return f"{solo} faces the camera and presents her body with hands framing the exposed skin in a solo creator-shot pose."
|
||||||
|
if "cumshot" in slug or "climax" in slug:
|
||||||
|
return f"{solo} is shown in a solo explicit orgasm pose with thighs open, one hand on her body, and visible arousal on skin and sheets."
|
||||||
|
return f"{solo} is shown in a solo explicit adult pose with self-touch, open body framing, and direct camera awareness."
|
||||||
|
if "cumshot" in slug or "climax" in slug:
|
||||||
|
return f"{solo} is shown in a solo visible ejaculation pose with one hand on his penis, body angled toward the camera, and semen visible."
|
||||||
|
return f"{solo} is shown in a solo explicit adult pose with direct camera awareness and clear body framing."
|
||||||
|
|
||||||
|
|
||||||
|
def build_women_only_role_graph(
|
||||||
|
slug: str,
|
||||||
|
a: str,
|
||||||
|
b: str,
|
||||||
|
c: str = "",
|
||||||
|
fallback_helper: str = "",
|
||||||
|
item_text: str = "",
|
||||||
|
item_axis_values: dict[str, Any] | None = None,
|
||||||
|
) -> tuple[str, set[str]]:
|
||||||
|
used = {a, b}
|
||||||
|
if "manual_stimulation" in slug:
|
||||||
|
return build_manual_role_graph(a, b, item_text, item_axis_values), used
|
||||||
|
if "group_coordination" in slug and c:
|
||||||
|
used.add(c)
|
||||||
|
return build_group_coordination_role_graph(a, b, c, item_text=item_text, item_axis_values=item_axis_values), used
|
||||||
|
if "outercourse" in slug:
|
||||||
|
return f"{a} kneels close to {b}'s body and uses mouth, hands, breasts, or feet for explicit non-penetrative contact.", used
|
||||||
|
if "oral" in slug:
|
||||||
|
return f"{a} kneels between {b}'s spread thighs and uses tongue and fingers on her pussy.", used
|
||||||
|
if "anal" in slug or "double" in slug:
|
||||||
|
return f"{a} uses a strap-on on {b} while keeping her hips held open.", used
|
||||||
|
if "threesome" in slug or "group" in slug or "orgy" in slug:
|
||||||
|
helper = c or fallback_helper or b
|
||||||
|
used.add(helper)
|
||||||
|
return f"{a} uses a strap-on on {b} while {helper} gives oral contact and touches both bodies.", used
|
||||||
|
if "cumshot" in slug or "climax" in slug:
|
||||||
|
return f"{a} brings {b} to orgasm with mouth and fingers while wetness is visible on thighs and sheets.", used
|
||||||
|
return f"{a} uses a strap-on on {b} while their bodies stay pressed together.", used
|
||||||
|
|
||||||
|
|
||||||
|
def build_men_only_role_graph(
|
||||||
|
slug: str,
|
||||||
|
a: str,
|
||||||
|
b: str,
|
||||||
|
c: str = "",
|
||||||
|
fallback_helper: str = "",
|
||||||
|
item_text: str = "",
|
||||||
|
item_axis_values: dict[str, Any] | None = None,
|
||||||
|
) -> tuple[str, set[str]]:
|
||||||
|
used = {a, b}
|
||||||
|
if "manual_stimulation" in slug:
|
||||||
|
return f"{a} and {b} sit or recline close together with hands visibly stimulating bodies in a manual sex setup.", used
|
||||||
|
if "group_coordination" in slug and c:
|
||||||
|
used.add(c)
|
||||||
|
return build_group_coordination_role_graph(a, b, c, item_text=item_text, item_axis_values=item_axis_values), used
|
||||||
|
if any(token in slug for token in ("foreplay", "body_worship", "clothing_position", "dominant_guidance", "camera_performance", "aftercare")):
|
||||||
|
return f"{a} and {b} press close together, kissing and caressing skin while clothing is pulled aside.", used
|
||||||
|
if "outercourse" in slug:
|
||||||
|
return f"{a} and {b} keep explicit non-penetrative penis contact visible with hands, mouth, or feet.", used
|
||||||
|
if "oral" in slug:
|
||||||
|
return f"{a} kneels and takes {b}'s penis in his mouth while holding his hips.", used
|
||||||
|
if "anal" in slug or "double" in slug or "penetrative" in slug:
|
||||||
|
return f"{a} penetrates {b} anally while {b}'s hips are held open.", used
|
||||||
|
if "threesome" in slug or "group" in slug or "orgy" in slug:
|
||||||
|
helper = c or fallback_helper or b
|
||||||
|
used.add(helper)
|
||||||
|
return f"{a} penetrates {b} anally while {helper} gives oral contact from the front.", used
|
||||||
|
if "cumshot" in slug or "climax" in slug:
|
||||||
|
return f"{a} ejaculates semen over {b}'s body while {b} keeps eye contact and one hand on his penis.", used
|
||||||
|
return f"{a} and {b} keep explicit penis and anal contact visible.", used
|
||||||
|
|
||||||
|
|
||||||
|
def build_mixed_group_fallback_role_graph(
|
||||||
|
woman: str,
|
||||||
|
man: str,
|
||||||
|
third: str,
|
||||||
|
helper: str,
|
||||||
|
slug: str,
|
||||||
|
) -> str:
|
||||||
|
if "threesome" in slug:
|
||||||
|
return f"{man} thrusts his penis into {woman} while {third or helper} uses mouth and hands on the exposed body."
|
||||||
|
if "group" in slug or "orgy" in slug:
|
||||||
|
return f"{man} thrusts his penis into {woman} while surrounding partners give oral contact and keep hands on hips, breasts, and thighs."
|
||||||
|
return ""
|
||||||
+39
-77
@@ -6,6 +6,13 @@ from typing import Any
|
|||||||
try:
|
try:
|
||||||
from .hardcore_role_anal import build_anal_or_double_role_graph
|
from .hardcore_role_anal import build_anal_or_double_role_graph
|
||||||
from .hardcore_role_climax import build_climax_role_graph
|
from .hardcore_role_climax import build_climax_role_graph
|
||||||
|
from .hardcore_role_fallback import (
|
||||||
|
build_men_only_role_graph,
|
||||||
|
build_mixed_group_fallback_role_graph,
|
||||||
|
build_solo_role_graph,
|
||||||
|
build_support_sentence,
|
||||||
|
build_women_only_role_graph,
|
||||||
|
)
|
||||||
from .hardcore_role_interaction import (
|
from .hardcore_role_interaction import (
|
||||||
build_foreplay_role_graph,
|
build_foreplay_role_graph,
|
||||||
build_group_coordination_role_graph,
|
build_group_coordination_role_graph,
|
||||||
@@ -18,6 +25,13 @@ try:
|
|||||||
except ImportError: # Allows local smoke tests with `python -c`.
|
except ImportError: # Allows local smoke tests with `python -c`.
|
||||||
from hardcore_role_anal import build_anal_or_double_role_graph
|
from hardcore_role_anal import build_anal_or_double_role_graph
|
||||||
from hardcore_role_climax import build_climax_role_graph
|
from hardcore_role_climax import build_climax_role_graph
|
||||||
|
from hardcore_role_fallback import (
|
||||||
|
build_men_only_role_graph,
|
||||||
|
build_mixed_group_fallback_role_graph,
|
||||||
|
build_solo_role_graph,
|
||||||
|
build_support_sentence,
|
||||||
|
build_women_only_role_graph,
|
||||||
|
)
|
||||||
from hardcore_role_interaction import (
|
from hardcore_role_interaction import (
|
||||||
build_foreplay_role_graph,
|
build_foreplay_role_graph,
|
||||||
build_group_coordination_role_graph,
|
build_group_coordination_role_graph,
|
||||||
@@ -88,94 +102,44 @@ def build_hardcore_role_graph(
|
|||||||
pool = [person for person in men if person not in exclude] or [person for person in people if person not in exclude] or people
|
pool = [person for person in men if person not in exclude] or [person for person in people if person not in exclude] or people
|
||||||
return rng.choice(pool)
|
return rng.choice(pool)
|
||||||
|
|
||||||
def support_sentence(exclude: set[str]) -> str:
|
|
||||||
extras = [person for person in people if person not in exclude]
|
|
||||||
if not extras:
|
|
||||||
return ""
|
|
||||||
extra = rng.choice(extras)
|
|
||||||
actions = [
|
|
||||||
"kisses and grips the nearest body",
|
|
||||||
"holds hips open for the camera",
|
|
||||||
"touches breasts, thighs, and stomach",
|
|
||||||
"keeps one hand on a partner's ass",
|
|
||||||
"watches close and joins the body contact",
|
|
||||||
"presses in from the side with hands on skin",
|
|
||||||
]
|
|
||||||
return f" {extra} {rng.choice(actions)}."
|
|
||||||
|
|
||||||
if people_count == 1:
|
if people_count == 1:
|
||||||
solo = people[0]
|
return build_solo_role_graph(people[0], women_count, slug, item_text, item_axis_values)
|
||||||
if women_count == 1:
|
|
||||||
if "manual_stimulation" in slug:
|
|
||||||
return build_manual_role_graph(solo, item_text=item_text, item_axis_values=item_axis_values)
|
|
||||||
if "camera_performance" in slug:
|
|
||||||
return f"{solo} faces the camera and presents her body with hands framing the exposed skin in a solo creator-shot pose."
|
|
||||||
if "cumshot" in slug or "climax" in slug:
|
|
||||||
return f"{solo} is shown in a solo explicit orgasm pose with thighs open, one hand on her body, and visible arousal on skin and sheets."
|
|
||||||
return f"{solo} is shown in a solo explicit adult pose with self-touch, open body framing, and direct camera awareness."
|
|
||||||
if "cumshot" in slug or "climax" in slug:
|
|
||||||
return f"{solo} is shown in a solo visible ejaculation pose with one hand on his penis, body angled toward the camera, and semen visible."
|
|
||||||
return f"{solo} is shown in a solo explicit adult pose with direct camera awareness and clear body framing."
|
|
||||||
|
|
||||||
if women_count > 0 and men_count == 0:
|
if women_count > 0 and men_count == 0:
|
||||||
a, b = _pick_distinct(rng, women, 2)
|
a, b = _pick_distinct(rng, women, 2)
|
||||||
c = any_woman({a, b}) if len(women) >= 3 else ""
|
c = any_woman({a, b}) if len(women) >= 3 else ""
|
||||||
used = {a, b}
|
used = {a, b}
|
||||||
if "manual_stimulation" in slug:
|
if any(token in slug for token in ("foreplay", "body_worship", "clothing_position", "dominant_guidance", "camera_performance", "aftercare")):
|
||||||
graph = build_manual_role_graph(a, b, item_text, item_axis_values)
|
|
||||||
elif "group_coordination" in slug and c:
|
|
||||||
graph = build_group_coordination_role_graph(a, b, c, item_text=item_text, item_axis_values=item_axis_values)
|
|
||||||
used.add(c)
|
|
||||||
elif any(token in slug for token in ("foreplay", "body_worship", "clothing_position", "dominant_guidance", "camera_performance", "aftercare")):
|
|
||||||
graph = build_interaction_role_graph(a, b, c, slug, item_text, item_axis_values)
|
graph = build_interaction_role_graph(a, b, c, slug, item_text, item_axis_values)
|
||||||
if c and "camera_performance" in slug:
|
if c and "camera_performance" in slug:
|
||||||
used.add(c)
|
used.add(c)
|
||||||
elif "foreplay" in slug:
|
elif "foreplay" in slug:
|
||||||
graph = build_foreplay_role_graph(a, b, item_text, item_axis_values)
|
graph = build_foreplay_role_graph(a, b, item_text, item_axis_values)
|
||||||
elif "outercourse" in slug:
|
|
||||||
graph = f"{a} kneels close to {b}'s body and uses mouth, hands, breasts, or feet for explicit non-penetrative contact."
|
|
||||||
elif "oral" in slug:
|
|
||||||
graph = f"{a} kneels between {b}'s spread thighs and uses tongue and fingers on her pussy."
|
|
||||||
elif "anal" in slug or "double" in slug:
|
|
||||||
graph = f"{a} uses a strap-on on {b} while keeping her hips held open."
|
|
||||||
elif "threesome" in slug or "group" in slug or "orgy" in slug:
|
|
||||||
helper = c or any_woman({a})
|
|
||||||
graph = f"{a} uses a strap-on on {b} while {helper} gives oral contact and touches both bodies."
|
|
||||||
used.add(helper)
|
|
||||||
elif "cumshot" in slug or "climax" in slug:
|
|
||||||
graph = f"{a} brings {b} to orgasm with mouth and fingers while wetness is visible on thighs and sheets."
|
|
||||||
else:
|
else:
|
||||||
graph = f"{a} uses a strap-on on {b} while their bodies stay pressed together."
|
graph, used = build_women_only_role_graph(
|
||||||
return graph + support_sentence(used)
|
slug,
|
||||||
|
a,
|
||||||
|
b,
|
||||||
|
c,
|
||||||
|
c or any_woman({a}),
|
||||||
|
item_text,
|
||||||
|
item_axis_values,
|
||||||
|
)
|
||||||
|
return graph + build_support_sentence(rng, people, used)
|
||||||
|
|
||||||
if men_count > 0 and women_count == 0:
|
if men_count > 0 and women_count == 0:
|
||||||
a, b = _pick_distinct(rng, men, 2)
|
a, b = _pick_distinct(rng, men, 2)
|
||||||
c = any_man({a, b}) if len(men) >= 3 else ""
|
c = any_man({a, b}) if len(men) >= 3 else ""
|
||||||
used = {a, b}
|
graph, used = build_men_only_role_graph(
|
||||||
if "manual_stimulation" in slug:
|
slug,
|
||||||
graph = f"{a} and {b} sit or recline close together with hands visibly stimulating bodies in a manual sex setup."
|
a,
|
||||||
elif "group_coordination" in slug and c:
|
b,
|
||||||
graph = build_group_coordination_role_graph(a, b, c, item_text=item_text, item_axis_values=item_axis_values)
|
c,
|
||||||
used.add(c)
|
c or any_man({a}),
|
||||||
elif any(token in slug for token in ("foreplay", "body_worship", "clothing_position", "dominant_guidance", "camera_performance", "aftercare")):
|
item_text,
|
||||||
graph = f"{a} and {b} press close together, kissing and caressing skin while clothing is pulled aside."
|
item_axis_values,
|
||||||
elif "foreplay" in slug:
|
)
|
||||||
graph = f"{a} and {b} press close together, kissing and caressing skin while clothing is pulled aside."
|
return graph + build_support_sentence(rng, people, used)
|
||||||
elif "outercourse" in slug:
|
|
||||||
graph = f"{a} and {b} keep explicit non-penetrative penis contact visible with hands, mouth, or feet."
|
|
||||||
elif "oral" in slug:
|
|
||||||
graph = f"{a} kneels and takes {b}'s penis in his mouth while holding his hips."
|
|
||||||
elif "anal" in slug or "double" in slug or "penetrative" in slug:
|
|
||||||
graph = f"{a} penetrates {b} anally while {b}'s hips are held open."
|
|
||||||
elif "threesome" in slug or "group" in slug or "orgy" in slug:
|
|
||||||
helper = c or any_man({a})
|
|
||||||
graph = f"{a} penetrates {b} anally while {helper} gives oral contact from the front."
|
|
||||||
used.add(helper)
|
|
||||||
elif "cumshot" in slug or "climax" in slug:
|
|
||||||
graph = f"{a} ejaculates semen over {b}'s body while {b} keeps eye contact and one hand on his penis."
|
|
||||||
else:
|
|
||||||
graph = f"{a} and {b} keep explicit penis and anal contact visible."
|
|
||||||
return graph + support_sentence(used)
|
|
||||||
|
|
||||||
woman = any_woman()
|
woman = any_woman()
|
||||||
man = any_man()
|
man = any_man()
|
||||||
@@ -201,12 +165,10 @@ def build_hardcore_role_graph(
|
|||||||
graph = build_oral_role_graph(woman, man, item_text, item_axis_values, pov_labels)
|
graph = build_oral_role_graph(woman, man, item_text, item_axis_values, pov_labels)
|
||||||
elif "anal" in slug or "double" in slug:
|
elif "anal" in slug or "double" in slug:
|
||||||
graph = build_anal_or_double_role_graph(woman, man, third, people_count, item_text, item_axis_values)
|
graph = build_anal_or_double_role_graph(woman, man, third, people_count, item_text, item_axis_values)
|
||||||
elif "threesome" in slug:
|
elif "threesome" in slug or "group" in slug or "orgy" in slug:
|
||||||
graph = f"{man} thrusts his penis into {woman} while {third or any_person({woman, man})} uses mouth and hands on the exposed body."
|
graph = build_mixed_group_fallback_role_graph(woman, man, third, any_person({woman, man}), slug)
|
||||||
elif "group" in slug or "orgy" in slug:
|
|
||||||
graph = f"{man} thrusts his penis into {woman} while surrounding partners give oral contact and keep hands on hips, breasts, and thighs."
|
|
||||||
elif "cumshot" in slug or "climax" in slug:
|
elif "cumshot" in slug or "climax" in slug:
|
||||||
graph = build_climax_role_graph(woman, man, third, item_text, item_axis_values)
|
graph = build_climax_role_graph(woman, man, third, item_text, item_axis_values)
|
||||||
else:
|
else:
|
||||||
graph = build_penetration_role_graph(woman, man, item_text, item_axis_values)
|
graph = build_penetration_role_graph(woman, man, item_text, item_axis_values)
|
||||||
return graph + support_sentence({woman, man, third} if third else {woman, man})
|
return graph + build_support_sentence(rng, people, {woman, man, third} if third else {woman, man})
|
||||||
|
|||||||
@@ -126,6 +126,9 @@ def dedupe_climax_detail(detail: str, role_graph: str, density: str = "balanced"
|
|||||||
detail = _clean(detail)
|
detail = _clean(detail)
|
||||||
lower = role_graph.lower()
|
lower = role_graph.lower()
|
||||||
patterns: list[str] = []
|
patterns: list[str] = []
|
||||||
|
if "solo visible ejaculation" in lower or "one hand on his penis" in lower:
|
||||||
|
detail = re.sub(r"\bcum on lower back and ass\b", "visible semen on skin", detail, flags=re.IGNORECASE)
|
||||||
|
detail = re.sub(r"\bcum (?:on|dripping from) ass\b", "visible semen on skin", detail, flags=re.IGNORECASE)
|
||||||
if "lies on her back" in lower:
|
if "lies on her back" in lower:
|
||||||
patterns.extend((r"lying on the back with legs spread and hips lifted", r"reclining with thighs open", r"lying on the back with legs spread"))
|
patterns.extend((r"lying on the back with legs spread and hips lifted", r"reclining with thighs open", r"lying on the back with legs spread"))
|
||||||
detail = re.sub(r"\bcum on lower back and ass\b", "cum across thighs and pussy", detail, flags=re.IGNORECASE)
|
detail = re.sub(r"\bcum on lower back and ass\b", "cum across thighs and pussy", detail, flags=re.IGNORECASE)
|
||||||
|
|||||||
@@ -180,6 +180,29 @@ def _character_cast_two_men(*, pov_first_man: bool = False) -> str:
|
|||||||
)["character_cast"]
|
)["character_cast"]
|
||||||
|
|
||||||
|
|
||||||
|
def _character_cast_subjects(subjects: list[str] | tuple[str, ...]) -> str:
|
||||||
|
cast = ""
|
||||||
|
counts = {"woman": 0, "man": 0}
|
||||||
|
for subject in subjects:
|
||||||
|
subject = str(subject)
|
||||||
|
counts[subject] += 1
|
||||||
|
label = chr(ord("A") + counts[subject] - 1)
|
||||||
|
cast = pb.build_character_slot_json(
|
||||||
|
subject_type=subject,
|
||||||
|
label=label,
|
||||||
|
age="25-year-old adult" if subject == "woman" else "40-year-old adult",
|
||||||
|
ethnicity="western_european",
|
||||||
|
figure="balanced",
|
||||||
|
body="slim" if subject == "woman" else "average",
|
||||||
|
descriptor_detail="compact",
|
||||||
|
expression_intensity=0.55,
|
||||||
|
softcore_expression_intensity=0.35,
|
||||||
|
hardcore_expression_intensity=0.75,
|
||||||
|
character_cast=cast,
|
||||||
|
)["character_cast"]
|
||||||
|
return cast
|
||||||
|
|
||||||
|
|
||||||
def _action_filter(focus: str, hardcore_position_config: str | dict[str, Any] | None = "") -> str:
|
def _action_filter(focus: str, hardcore_position_config: str | dict[str, Any] | None = "") -> str:
|
||||||
kwargs = {
|
kwargs = {
|
||||||
"allow_toys": False,
|
"allow_toys": False,
|
||||||
@@ -1325,6 +1348,128 @@ def smoke_interaction_role_graph_routes() -> None:
|
|||||||
_expect_formatter_outputs(row, name, target="single")
|
_expect_formatter_outputs(row, name, target="single")
|
||||||
|
|
||||||
|
|
||||||
|
def smoke_fallback_role_graph_routes() -> None:
|
||||||
|
cases = [
|
||||||
|
(
|
||||||
|
"fallback_solo_woman_manual",
|
||||||
|
("woman",),
|
||||||
|
"Manual stimulation",
|
||||||
|
"manual_only",
|
||||||
|
"manual",
|
||||||
|
"fingering",
|
||||||
|
4401,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
("reclines with thighs open", "one hand between her legs"),
|
||||||
|
("one hand between her legs", "fingers visibly stimulating"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"fallback_solo_man_climax",
|
||||||
|
("man",),
|
||||||
|
"Cumshot and climax",
|
||||||
|
"climax_only",
|
||||||
|
"climax",
|
||||||
|
"open_thighs",
|
||||||
|
4401,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
("solo visible ejaculation pose", "semen visible"),
|
||||||
|
("solo visible ejaculation pose", "visible semen on skin"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"fallback_women_only_oral",
|
||||||
|
("woman", "woman"),
|
||||||
|
"Oral sex",
|
||||||
|
"oral_only",
|
||||||
|
"oral",
|
||||||
|
"reclining_oral",
|
||||||
|
4401,
|
||||||
|
2,
|
||||||
|
0,
|
||||||
|
("woman a kneels between woman b", "uses tongue and fingers"),
|
||||||
|
("woman a kneels between woman b", "uses tongue and fingers"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"fallback_women_only_threesome",
|
||||||
|
("woman", "woman", "woman"),
|
||||||
|
"Threesomes",
|
||||||
|
"threesome_only",
|
||||||
|
"threesome",
|
||||||
|
"front_back",
|
||||||
|
4401,
|
||||||
|
3,
|
||||||
|
0,
|
||||||
|
("uses a strap-on", "gives oral contact"),
|
||||||
|
("uses a strap-on", "gives oral contact"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"fallback_men_only_oral",
|
||||||
|
("man", "man"),
|
||||||
|
"Oral sex",
|
||||||
|
"oral_only",
|
||||||
|
"oral",
|
||||||
|
"kneeling",
|
||||||
|
4401,
|
||||||
|
0,
|
||||||
|
2,
|
||||||
|
("man a kneels", "takes man b's penis"),
|
||||||
|
("man a kneels", "takes man b's penis"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"fallback_men_only_threesome",
|
||||||
|
("man", "man", "man"),
|
||||||
|
"Threesomes",
|
||||||
|
"threesome_only",
|
||||||
|
"threesome",
|
||||||
|
"front_back",
|
||||||
|
4401,
|
||||||
|
0,
|
||||||
|
3,
|
||||||
|
("penetrates man b anally", "gives oral contact"),
|
||||||
|
("penetrates man b anally", "gives oral contact"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"fallback_mixed_threesome",
|
||||||
|
("woman", "man", "man"),
|
||||||
|
"Threesomes",
|
||||||
|
"threesome_only",
|
||||||
|
"threesome",
|
||||||
|
"front_back",
|
||||||
|
4401,
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
("thrusts his penis into woman a", "uses mouth and hands"),
|
||||||
|
("thrusts his penis into woman a", "uses mouth and hands"),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
for name, subjects, subcategory, focus, family, position_key, seed, women_count, men_count, role_terms, krea_terms in cases:
|
||||||
|
row = _prompt_row(
|
||||||
|
name=name,
|
||||||
|
category="Hardcore sexual poses",
|
||||||
|
subcategory=subcategory,
|
||||||
|
seed=seed,
|
||||||
|
character_cast=_character_cast_subjects(subjects),
|
||||||
|
women_count=women_count,
|
||||||
|
men_count=men_count,
|
||||||
|
hardcore_position_config=_position_filter(focus, family, [position_key]),
|
||||||
|
)
|
||||||
|
_expect_custom_row(row, name)
|
||||||
|
_expect(row.get("position_family") == family, f"{name} position_family mismatch: {row.get('position_family')}")
|
||||||
|
_expect(position_key in (row.get("position_keys") or []), f"{name} lost position key {position_key!r}")
|
||||||
|
role_graph = _expect_text(f"{name}.source_role_graph", row.get("source_role_graph"), 30).lower()
|
||||||
|
for term in role_terms:
|
||||||
|
_expect(term in role_graph, f"{name} role graph missing {term!r}: {role_graph}")
|
||||||
|
krea = krea_formatter.format_krea2_prompt("", metadata_json=_json(row), target="single")
|
||||||
|
prompt = _expect_text(f"{name}.krea_prompt", krea.get("krea_prompt"), 60).lower()
|
||||||
|
_expect("metadata" in krea.get("method", ""), f"{name}.krea did not use metadata")
|
||||||
|
_expect("role graph:" not in prompt and "sexual scene:" not in prompt, f"{name} Krea leaked raw labels")
|
||||||
|
for term in krea_terms:
|
||||||
|
_expect(term in prompt, f"{name} Krea prompt missing {term!r}: {prompt}")
|
||||||
|
if name == "fallback_solo_man_climax":
|
||||||
|
_expect("lower back and ass" not in prompt, f"{name} Krea kept conflicting solo male climax detail: {prompt}")
|
||||||
|
_expect_formatter_outputs(row, name, target="single")
|
||||||
|
|
||||||
|
|
||||||
def smoke_no_expression_fallback() -> None:
|
def smoke_no_expression_fallback() -> None:
|
||||||
cast = pb.build_character_slot_json(
|
cast = pb.build_character_slot_json(
|
||||||
subject_type="woman",
|
subject_type="woman",
|
||||||
@@ -1463,6 +1608,7 @@ SMOKE_CASES: list[tuple[str, Callable[[], None]]] = [
|
|||||||
("double_front_back_route", smoke_double_front_back_route),
|
("double_front_back_route", smoke_double_front_back_route),
|
||||||
("climax_position_routes", smoke_climax_position_routes),
|
("climax_position_routes", smoke_climax_position_routes),
|
||||||
("interaction_role_graph_routes", smoke_interaction_role_graph_routes),
|
("interaction_role_graph_routes", smoke_interaction_role_graph_routes),
|
||||||
|
("fallback_role_graph_routes", smoke_fallback_role_graph_routes),
|
||||||
("expression_disabled", smoke_no_expression_fallback),
|
("expression_disabled", smoke_no_expression_fallback),
|
||||||
("formatter_metadata_fixtures", smoke_formatter_metadata_fixtures),
|
("formatter_metadata_fixtures", smoke_formatter_metadata_fixtures),
|
||||||
]
|
]
|
||||||
|
|||||||
Reference in New Issue
Block a user