Expand hardcore interaction pools

This commit is contained in:
2026-06-26 12:54:35 +02:00
parent 4f0203fc3d
commit a392ab9779
6 changed files with 1203 additions and 11 deletions
+265 -6
View File
@@ -300,6 +300,8 @@ HARDCORE_POSITION_FAMILY_CHOICES = [
"any",
"penetrative",
"foreplay",
"interaction",
"manual",
"oral",
"outercourse",
"anal",
@@ -311,6 +313,8 @@ HARDCORE_POSITION_FOCUS_CHOICES = [
"keep_pool",
"penetration_only",
"foreplay_only",
"interaction_only",
"manual_only",
"oral_only",
"outercourse_only",
"anal_only",
@@ -341,6 +345,22 @@ HARDCORE_POSITION_KEY_CHOICES = [
"breast_touch",
"face_touch",
"undressing",
"body_worship",
"nipple_play",
"ass_grab",
"thigh_kissing",
"hair_holding",
"wrist_pinning",
"dirty_talk",
"position_transition",
"guided_positioning",
"camera_showing",
"watching",
"aftercare",
"cleanup",
"fingering",
"clit_rubbing",
"mutual_masturbation",
"boobjob",
"testicle_sucking",
"penis_licking",
@@ -353,17 +373,34 @@ HARDCORE_POSITION_FAMILY_SUBCATEGORIES = {
"any": [
"penetrative_sex",
"foreplay_teasing",
"body_worship_touching",
"clothing_position_transitions",
"dominant_guidance",
"camera_performance",
"manual_stimulation",
"oral_sex",
"outercourse_sex",
"anal_double_penetration",
"threesomes",
"group_coordination",
"group_sex_orgy",
"cumshot_climax",
"aftercare_cleanup",
],
"penetrative": ["penetrative_sex"],
"foreplay": ["foreplay_teasing"],
"interaction": [
"foreplay_teasing",
"body_worship_touching",
"clothing_position_transitions",
"dominant_guidance",
"camera_performance",
"group_coordination",
"aftercare_cleanup",
],
"manual": ["manual_stimulation"],
"oral": ["oral_sex"],
"outercourse": ["outercourse_sex"],
"outercourse": ["outercourse_sex", "manual_stimulation"],
"anal": ["anal_double_penetration"],
"climax": ["cumshot_climax"],
"threesome": ["threesomes"],
@@ -392,6 +429,22 @@ HARDCORE_POSITION_KEY_MATCHES = {
"breast_touch": ("breast", "breasts", "nipple", "cupping breasts", "touching breasts"),
"face_touch": ("face", "cheek", "jaw", "chin", "hand on the cheek", "fingers under the chin"),
"undressing": ("undressing", "removing clothing", "removing clothes", "pulling clothing", "sliding straps", "unbuttoning"),
"body_worship": ("body worship", "worship", "kissing down", "mouth on skin", "kissing the body"),
"nipple_play": ("nipple", "nipples", "licking nipples", "sucking nipples", "nipple play"),
"ass_grab": ("ass grab", "ass-grab", "ass grabbing", "hand on the ass", "squeezing the ass"),
"thigh_kissing": ("thigh kiss", "thigh kissing", "kissing thighs", "mouth on inner thighs"),
"hair_holding": ("hair holding", "hair held", "holding hair", "hair pulled back"),
"wrist_pinning": ("wrist", "wrists", "pinning wrists", "wrists pinned", "hands pinned"),
"dirty_talk": ("dirty talk", "whispering", "mouth near the ear", "telling", "verbal teasing"),
"position_transition": ("transition", "turning around", "pulling onto the bed", "moving into position", "position change"),
"guided_positioning": ("guiding", "guided", "guides", "lifting legs", "spreading thighs", "pulling hips", "turning the body"),
"camera_showing": ("camera", "showing to camera", "presenting to camera", "spread open for camera", "creator-shot"),
"watching": ("watching", "voyeur", "waiting turn", "partner watches", "onlooker"),
"aftercare": ("aftercare", "cuddling", "kissing after", "holding close", "post-sex"),
"cleanup": ("cleanup", "wiping", "cleaning", "towel", "wet cloth"),
"fingering": ("fingering", "fingers inside", "fingers in pussy", "finger stimulation"),
"clit_rubbing": ("clit", "clitoris", "clit rubbing", "rubbing the clit", "fingers on clit"),
"mutual_masturbation": ("mutual masturbation", "both touching themselves", "masturbating together", "hands on their own bodies"),
"boobjob": ("boobjob", "titjob", "breast-sex", "breast sex"),
"testicle_sucking": ("testicle", "balls-licking", "balls licking", "balls and mouth"),
"penis_licking": ("penis-licking", "penis licking", "tongue along", "tongue licking"),
@@ -400,7 +453,23 @@ HARDCORE_POSITION_KEY_MATCHES = {
"open_thighs": ("thighs open", "legs spread", "open thighs", "legs open", "reclining with thighs open"),
"front_back": ("front-and-back", "front and back", "one behind and one in front", "between two partners"),
}
HARDCORE_POSITION_AXIS_KEYS = {"position", "body_position", "body_arrangement", "arrangement"}
HARDCORE_POSITION_AXIS_KEYS = {
"position",
"body_position",
"body_arrangement",
"arrangement",
"tease_act",
"touch_detail",
"manual_act",
"manual_detail",
"worship_act",
"transition_act",
"control_act",
"performance_act",
"coordination_act",
"aftercare_act",
"cleanup_detail",
}
CAMERA_ORBIT_FRAMING_CHOICES = [
"from_zoom",
"wide",
@@ -2347,6 +2416,8 @@ def _empty_hardcore_position_config() -> dict[str, Any]:
"allow_double": True,
"allow_penetration": True,
"allow_foreplay": True,
"allow_interaction": True,
"allow_manual": True,
"allow_oral": True,
"allow_outercourse": True,
"allow_anal": True,
@@ -2376,6 +2447,8 @@ def _parse_hardcore_position_config(value: str | dict[str, Any] | None) -> dict[
"allow_double",
"allow_penetration",
"allow_foreplay",
"allow_interaction",
"allow_manual",
"allow_oral",
"allow_outercourse",
"allow_anal",
@@ -2401,6 +2474,8 @@ def _hardcore_position_summary(config: dict[str, Any]) -> str:
("allow_double", "double"),
("allow_penetration", "penetration"),
("allow_foreplay", "foreplay"),
("allow_interaction", "interaction"),
("allow_manual", "manual"),
("allow_oral", "oral"),
("allow_outercourse", "outercourse"),
("allow_anal", "anal"),
@@ -2446,6 +2521,8 @@ def build_hardcore_action_filter_json(
allow_double: bool = False,
allow_penetration: bool = True,
allow_foreplay: bool = True,
allow_interaction: bool = True,
allow_manual: bool = True,
allow_oral: bool = True,
allow_outercourse: bool = True,
allow_anal: bool = True,
@@ -2457,6 +2534,8 @@ def build_hardcore_action_filter_json(
focus_family = {
"penetration_only": "penetrative",
"foreplay_only": "foreplay",
"interaction_only": "interaction",
"manual_only": "manual",
"oral_only": "oral",
"outercourse_only": "outercourse",
"anal_only": "anal",
@@ -2470,6 +2549,8 @@ def build_hardcore_action_filter_json(
config["allow_double"] = bool(allow_double)
config["allow_penetration"] = bool(allow_penetration)
config["allow_foreplay"] = bool(allow_foreplay)
config["allow_interaction"] = bool(allow_interaction)
config["allow_manual"] = bool(allow_manual)
config["allow_oral"] = bool(allow_oral)
config["allow_outercourse"] = bool(allow_outercourse)
config["allow_anal"] = bool(allow_anal)
@@ -2481,6 +2562,8 @@ def build_hardcore_action_filter_json(
for enabled, family in (
(config["allow_penetration"], "penetrative"),
(config["allow_foreplay"], "foreplay"),
(config["allow_interaction"], "interaction"),
(config["allow_manual"], "manual"),
(config["allow_oral"], "oral"),
(config["allow_outercourse"], "outercourse"),
(config["allow_anal"], "anal"),
@@ -2493,6 +2576,12 @@ def build_hardcore_action_filter_json(
if focus == "foreplay_only":
config["allow_foreplay"] = True
config["allow_interaction"] = True
elif focus == "interaction_only":
config["allow_interaction"] = True
config["allow_foreplay"] = True
elif focus == "manual_only":
config["allow_manual"] = True
elif focus == "oral_only":
config["allow_oral"] = True
config["allow_penetration"] = False
@@ -2530,6 +2619,20 @@ def _hardcore_allowed_subcategory_slugs(config: dict[str, Any]) -> set[str]:
allowed.difference_update({"penetrative_sex", "anal_double_penetration", "threesomes", "group_sex_orgy"})
if not config.get("allow_foreplay", True):
allowed.discard("foreplay_teasing")
if not config.get("allow_interaction", True):
allowed.difference_update(
{
"foreplay_teasing",
"body_worship_touching",
"clothing_position_transitions",
"dominant_guidance",
"camera_performance",
"group_coordination",
"aftercare_cleanup",
}
)
if not config.get("allow_manual", True):
allowed.discard("manual_stimulation")
if not config.get("allow_oral", True):
allowed.discard("oral_sex")
if not config.get("allow_outercourse", True):
@@ -2582,7 +2685,7 @@ def _hardcore_text_blocked_by_action(text: str, axis_name: str, config: dict[str
return True
if not config.get("allow_anal", True) and (
axis_name == "anal_act"
or any(term in text for term in (" anal", "ass", "rear-entry anal"))
or any(term in text for term in (" anal", "anal sex", "anal penetration", "anus", "rear-entry anal", "penis entering ass", "thrusts into her ass", "thrusts into his ass"))
):
return True
if not config.get("allow_oral", True) and (
@@ -2626,6 +2729,68 @@ def _hardcore_text_blocked_by_action(text: str, axis_name: str, config: dict[str
)
):
return True
if not config.get("allow_interaction", True) and (
axis_name
in (
"tease_act",
"touch_detail",
"clothing_detail",
"foreplay_detail",
"face_detail",
"body_contact",
"mood_detail",
"worship_act",
"transition_act",
"control_act",
"performance_act",
"coordination_act",
"aftercare_act",
"cleanup_detail",
)
or any(
term in text
for term in (
"kiss",
"kissing",
"caress",
"body worship",
"nipple",
"ass grab",
"thigh",
"hair holding",
"wrists",
"dirty talk",
"whispering",
"undressing",
"position transition",
"guided",
"camera",
"watching",
"aftercare",
"cleanup",
"wiping",
)
)
):
return True
if not config.get("allow_manual", True) and (
axis_name in ("manual_act", "manual_detail")
or any(
term in text
for term in (
"fingering",
"fingers inside",
"clit",
"clitoris",
"manual stimulation",
"mutual masturbation",
"masturbating together",
"fingers on pussy",
"fingers on clit",
)
)
):
return True
if not config.get("allow_climax", True) and (
axis_name in ("climax_act", "climax_hint", "climax_detail", "fluid_detail", "fluid_location")
or any(term in text for term in ("climax", "cum", "semen", "ejaculat", "creampie", "post-orgasm", "post-penetration"))
@@ -6067,6 +6232,74 @@ def _role_graph(
f"hands caressing skin while clothing is pulled aside."
)
def interaction_text() -> str:
return " ".join(
str(part or "").lower()
for part in (
item_text,
*((item_axis_values or {}).values()),
)
)
def manual_position_graph(primary: str, partner: str = "") -> str:
text = interaction_text()
if not partner:
if "mutual" in text:
return f"{primary} faces the camera with thighs open, both hands on her body for solo mutual-style masturbation framing."
return f"{primary} reclines with thighs open, one hand between her legs and fingers visibly stimulating her pussy."
if "mutual" in text:
return f"{primary} and {partner} sit close facing each other, both touching themselves while keeping hands, faces, and bodies visible."
if "clit" in text or "clitoris" in text:
return f"{primary} reclines with thighs open while {partner}'s hand is between her legs, fingers rubbing her clit as her hips tilt toward the touch."
if "toy" in text or "vibrator" in text:
return f"{primary} reclines with thighs open while {partner} holds a vibrator or toy against her clit, one hand keeping her thigh open."
return f"{primary} reclines with thighs open while {partner}'s hand is between her legs, fingers visibly stimulating her pussy."
def interaction_position_graph(primary: str, partner: str, third: str = "") -> str:
text = interaction_text()
if "aftercare" in slug or any(term in text for term in ("aftercare", "cleanup", "wiping", "towel", "post-sex", "cuddle")):
if "cleanup" in text or "wiping" in text or "towel" in text:
return f"{primary} reclines after sex while {partner} kneels close and wipes her skin with a towel, hands and relaxed body contact visible."
return f"{primary} and {partner} lie close together after sex, bodies relaxed and hands resting on skin in a post-sex cuddle."
if "camera_performance" in slug or any(term in text for term in ("camera", "presenting", "showing", "viewer", "creator-shot")):
if third:
return f"{primary} faces the camera while {partner} and {third} hold and present her body, hands framing the exposed skin for the viewer."
return f"{primary} faces the camera and presents her body while {partner}'s hands hold her hips or thighs open for a clear creator-shot reveal."
if "body_worship" in slug or any(term in text for term in ("body worship", "nipple", "thigh", "mouth on skin", "kissing down", "ass grabbing")):
if "ass" in text:
return f"{primary} stands or kneels with hips angled back while {partner}'s hands grip her ass, fingers pressing into skin."
if "thigh" in text:
return f"{primary} reclines with thighs open while {partner} kneels close and kisses along her inner thighs, hands holding her legs in place."
if "nipple" in text or "breast" in text:
return f"{primary} arches toward {partner} while {partner}'s mouth is on her breast and one hand cups or squeezes the other breast."
return f"{primary} reclines or leans back while {partner} kisses down her body, hands tracing breasts, waist, hips, and thighs."
if "clothing_position" in slug or any(term in text for term in ("transition", "turning", "pulling onto", "lifting", "guided backward", "clothing", "garment")):
if "turn" in text or "rear-facing" in text:
return f"{partner}'s hands turn {primary} around by the hips, clothing partly moved aside as her body rotates into the next pose."
if "legs" in text or "thigh" in text:
return f"{primary} lies back while {partner} lifts and spreads her legs into position, hands and clothing movement clearly visible."
return f"{primary} and {partner} are mid-transition, with {partner}'s hands moving clothing aside and guiding {primary}'s hips toward the next pose."
if "dominant" in slug or any(term in text for term in ("hair", "wrist", "wrists", "jaw", "chin", "guided", "dominant", "control", "dirty talk", "whisper", "mouth near the ear", "verbal teasing")):
if "dirty talk" in text or "whisper" in text or "mouth near the ear" in text or "verbal teasing" in text:
return f"{partner} leans close to {primary}'s ear for dirty talk while holding her waist and keeping their bodies pressed close."
if "wrist" in text or "wrists" in text:
return f"{primary} lies back while {partner} pins her wrists above her head, both bodies close and the consensual control gesture clearly visible."
if "hair" in text:
return f"{partner} holds {primary}'s hair back while guiding her body closer, face and hair-hold gesture visible."
if "thigh" in text or "spread" in text:
return f"{primary} reclines with thighs open while {partner}'s hands spread her legs and hold the position for the camera."
return f"{partner} guides {primary}'s body with hands on her jaw, waist, and hips, keeping the consensual control gesture readable."
return foreplay_position_graph(primary, partner)
def group_coordination_graph(primary: str, partner: str, third: str) -> str:
observer = third or any_person({primary, partner})
text = interaction_text()
if "camera" in text or "hold" in text or "present" in text:
return f"{primary} is centered while {partner} and {observer} hold and present the body for the camera, each role clearly visible."
if "watch" in text or "waiting" in text:
return f"{primary} is centered while {partner} touches her body and {observer} watches close beside them, hands and faces readable."
return f"{primary} is centered while {partner} touches her body and {observer} stays close as the watching or guiding partner."
def mentions_ass(text: str) -> bool:
return bool(
re.search(
@@ -6346,6 +6579,10 @@ def _role_graph(
if people_count == 1:
solo = people[0]
if women_count == 1:
if "manual_stimulation" in slug:
return manual_position_graph(solo)
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."
@@ -6357,7 +6594,16 @@ def _role_graph(
a, b = _pick_distinct(rng, women, 2)
c = any_woman({a, b}) if len(women) >= 3 else ""
used = {a, b}
if "foreplay" in slug:
if "manual_stimulation" in slug:
graph = manual_position_graph(a, b)
elif "group_coordination" in slug and c:
graph = group_coordination_graph(a, b, c)
used.add(c)
elif any(token in slug for token in ("foreplay", "body_worship", "clothing_position", "dominant_guidance", "camera_performance", "aftercare")):
graph = interaction_position_graph(a, b, c)
if c and "camera_performance" in slug:
used.add(c)
elif "foreplay" in slug:
graph = foreplay_position_graph(a, b)
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."
@@ -6379,7 +6625,14 @@ def _role_graph(
a, b = _pick_distinct(rng, men, 2)
c = any_man({a, b}) if len(men) >= 3 else ""
used = {a, b}
if "foreplay" in slug:
if "manual_stimulation" in slug:
graph = f"{a} and {b} sit or recline close together with hands visibly stimulating bodies in a manual sex setup."
elif "group_coordination" in slug and c:
graph = group_coordination_graph(a, b, c)
used.add(c)
elif any(token in slug for token in ("foreplay", "body_worship", "clothing_position", "dominant_guidance", "camera_performance", "aftercare")):
graph = f"{a} and {b} press close together, kissing and caressing skin while clothing is pulled aside."
elif "foreplay" in slug:
graph = f"{a} and {b} press close together, kissing and caressing skin while clothing is pulled aside."
elif "outercourse" in slug:
graph = f"{a} and {b} keep explicit non-penetrative penis contact visible with hands, mouth, or feet."
@@ -6401,7 +6654,13 @@ def _role_graph(
woman = any_woman()
man = any_man()
third = any_person({woman, man}) if people_count >= 3 else ""
if "foreplay" in slug:
if "manual_stimulation" in slug:
graph = manual_position_graph(woman, man)
elif "group_coordination" in slug:
graph = group_coordination_graph(woman, man, third)
elif any(token in slug for token in ("foreplay", "body_worship", "clothing_position", "dominant_guidance", "camera_performance", "aftercare")):
graph = interaction_position_graph(woman, man, third)
elif "foreplay" in slug:
graph = foreplay_position_graph(woman, man)
elif "outercourse" in slug:
graph = outercourse_position_graph(woman, man)