Move action expression sanitizer
This commit is contained in:
@@ -179,8 +179,8 @@ Already isolated:
|
|||||||
wrappers.
|
wrappers.
|
||||||
- row expression text cleanup, expression intensity weighting,
|
- row expression text cleanup, expression intensity weighting,
|
||||||
character-slot/cast expression override resolution, and per-character
|
character-slot/cast expression override resolution, and per-character
|
||||||
expression picking live in `row_expression.py`; `prompt_builder.py` keeps
|
expression picking plus action-aware character-expression sanitizing live in
|
||||||
public delegate wrappers.
|
`row_expression.py`; `prompt_builder.py` keeps public delegate wrappers.
|
||||||
- hardcore position/action-filter choices, selected-position normalization,
|
- hardcore position/action-filter choices, selected-position normalization,
|
||||||
config JSON builders/parsers, focus-policy toggles, subcategory allow-list
|
config JSON builders/parsers, focus-policy toggles, subcategory allow-list
|
||||||
policy, position-key detection, category filtering, and item-template/axis
|
policy, position-key detection, category filtering, and item-template/axis
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ Core helper ownership:
|
|||||||
| `subject_context.py` | Row subject-context routing for single, couple, configured-cast, group, and layout subjects, combining appearance policy, cast metadata, and generator subject pools. |
|
| `subject_context.py` | Row subject-context routing for single, couple, configured-cast, group, and layout subjects, combining appearance policy, cast metadata, and generator subject pools. |
|
||||||
| `location_config.py` | Location/composition preset schemas, themed location packs, custom location/composition parsing, pool merge behavior, and location/composition config parsing. |
|
| `location_config.py` | Location/composition preset schemas, themed location packs, custom location/composition parsing, pool merge behavior, and location/composition config parsing. |
|
||||||
| `row_location.py` | Built-in row location/composition config application, deterministic scene/composition choice, source metadata, and legacy prompt/caption rewrites. |
|
| `row_location.py` | Built-in row location/composition config application, deterministic scene/composition choice, source metadata, and legacy prompt/caption rewrites. |
|
||||||
| `row_expression.py` | Row expression cleanup, expression intensity weighting, character-slot/cast expression override resolution, and per-character expression selection. |
|
| `row_expression.py` | Row expression cleanup, expression intensity weighting, character-slot/cast expression override resolution, per-character expression selection, and action-aware character-expression sanitizing. |
|
||||||
| `row_pools.py` | Row scene/expression/pose/composition pool routing, category inheritance handling, runtime location/composition pool overrides, and generator fallback pools. |
|
| `row_pools.py` | Row scene/expression/pose/composition pool routing, category inheritance handling, runtime location/composition pool overrides, and generator fallback pools. |
|
||||||
| `hardcore_position_config.py` | Hardcore position/action-filter choices, selected-position normalization, config JSON builders/parsers, focus-policy toggles, subcategory allow-list policy, position-key detection, and category/template/axis filtering. |
|
| `hardcore_position_config.py` | Hardcore position/action-filter choices, selected-position normalization, config JSON builders/parsers, focus-policy toggles, subcategory allow-list policy, position-key detection, and category/template/axis filtering. |
|
||||||
| `pair_options.py` | Insta/OF option schema/defaults, softcore category/outfit/pose pools, partner outfit pools, clothing-continuity labels, negatives, hardcore cast count policy, and hardcore detail-density directives. |
|
| `pair_options.py` | Insta/OF option schema/defaults, softcore category/outfit/pose pools, partner outfit pools, clothing-continuity labels, negatives, hardcore cast count policy, and hardcore detail-density directives. |
|
||||||
@@ -846,7 +846,7 @@ pair metadata through the core Python APIs, then verifies:
|
|||||||
| Wrong location | `categories/location_pools.json`, category `scene_pool`, `_scene_pool`. |
|
| Wrong location | `categories/location_pools.json`, category `scene_pool`, `_scene_pool`. |
|
||||||
| Location good but camera/location layout wrong | `_camera_scene_directive_for_context`, coworking adapter functions. |
|
| Location good but camera/location layout wrong | `_camera_scene_directive_for_context`, coworking adapter functions. |
|
||||||
| Repeated desk/anchor in POV foreground | Coworking direction/distance/elevation helpers. |
|
| Repeated desk/anchor in POV foreground | Coworking direction/distance/elevation helpers. |
|
||||||
| Wrong expression intensity | Character slot expression settings, `row_expression.py`, expression pools. |
|
| Wrong expression intensity or action-incompatible character expression | Character slot expression settings, `row_expression.py`, expression pools. |
|
||||||
| Expression appears when disabled | `row_expression.disable_row_expression`, formatter expression extraction. |
|
| Expression appears when disabled | `row_expression.disable_row_expression`, formatter expression extraction. |
|
||||||
| Same hardcore action repeats | Hardcore filter config, `sexual_poses.json` weights, `hardcore_position_config.apply_hardcore_position_config_to_subcategory`. |
|
| Same hardcore action repeats | Hardcore filter config, `sexual_poses.json` weights, `hardcore_position_config.apply_hardcore_position_config_to_subcategory`. |
|
||||||
| Hardcore interaction beat falls back to penetration/oral | `sexual_poses.json` interaction subcategory, `_role_graph`, and `krea_action_context.is_foreplay_text` / `krea_action_positions.hardcore_pose_anchor`. |
|
| Hardcore interaction beat falls back to penetration/oral | `sexual_poses.json` interaction subcategory, `_role_graph`, and `krea_action_context.is_foreplay_text` / `krea_action_positions.hardcore_pose_anchor`. |
|
||||||
|
|||||||
+3
-84
@@ -1454,93 +1454,12 @@ def _sanitize_character_expression_text_for_action(
|
|||||||
item: Any,
|
item: Any,
|
||||||
axis_values: Any = None,
|
axis_values: Any = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
text = str(expression_text or "").strip()
|
return row_expression_policy.sanitize_character_expression_text_for_action(
|
||||||
if not text:
|
expression_text,
|
||||||
return ""
|
|
||||||
context = " ".join(
|
|
||||||
str(part or "").lower()
|
|
||||||
for part in (
|
|
||||||
role_graph,
|
role_graph,
|
||||||
item,
|
item,
|
||||||
*((axis_values or {}).values() if isinstance(axis_values, dict) else ()),
|
axis_values,
|
||||||
)
|
)
|
||||||
)
|
|
||||||
woman_active_outercourse = (
|
|
||||||
re.search(r"\bwoman [a-z]\b", context)
|
|
||||||
and re.search(r"\bman [a-z]\b", context)
|
|
||||||
and any(
|
|
||||||
term in context
|
|
||||||
for term in (
|
|
||||||
"boobjob",
|
|
||||||
"titjob",
|
|
||||||
"breast sex",
|
|
||||||
"breasts tightly",
|
|
||||||
"testicle",
|
|
||||||
"balls-licking",
|
|
||||||
"balls licking",
|
|
||||||
"penis-licking",
|
|
||||||
"penis licking",
|
|
||||||
"handjob",
|
|
||||||
"hand job",
|
|
||||||
"footjob",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
woman_gives_oral = (
|
|
||||||
re.search(r"\bwoman [a-z]\b", context)
|
|
||||||
and re.search(r"\bman [a-z]\b", context)
|
|
||||||
and any(
|
|
||||||
term in context
|
|
||||||
for term in (
|
|
||||||
"takes man",
|
|
||||||
"penis in her mouth",
|
|
||||||
"mouth at penis level",
|
|
||||||
"fellatio",
|
|
||||||
"blowjob",
|
|
||||||
"deepthroat",
|
|
||||||
"penis sucking",
|
|
||||||
"lips wrapped",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
man_gives_oral = (
|
|
||||||
re.search(r"\bwoman [a-z]\b", context)
|
|
||||||
and re.search(r"\bman [a-z]\b", context)
|
|
||||||
and any(
|
|
||||||
term in context
|
|
||||||
for term in (
|
|
||||||
"mouth on her pussy",
|
|
||||||
"mouth on woman",
|
|
||||||
"mouth pressed to her pussy",
|
|
||||||
"cunnilingus",
|
|
||||||
"pussy licking",
|
|
||||||
"tongue on pussy",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
mouth_expression_terms = ("mouth", "oral", "tongue", "lips", "gagging", "saliva")
|
|
||||||
clauses = [clause.strip() for clause in text.split(";") if clause.strip()]
|
|
||||||
if woman_active_outercourse:
|
|
||||||
clauses = [clause for clause in clauses if not re.match(r"^Man [A-Z] has\b", clause)]
|
|
||||||
if woman_gives_oral:
|
|
||||||
clauses = [
|
|
||||||
clause
|
|
||||||
for clause in clauses
|
|
||||||
if not (
|
|
||||||
re.match(r"^Man [A-Z] has\b", clause)
|
|
||||||
and any(term in clause.lower() for term in mouth_expression_terms)
|
|
||||||
)
|
|
||||||
]
|
|
||||||
if man_gives_oral:
|
|
||||||
clauses = [
|
|
||||||
clause
|
|
||||||
for clause in clauses
|
|
||||||
if not (
|
|
||||||
re.match(r"^Woman [A-Z] has\b", clause)
|
|
||||||
and any(term in clause.lower() for term in mouth_expression_terms)
|
|
||||||
)
|
|
||||||
]
|
|
||||||
return "; ".join(clauses)
|
|
||||||
|
|
||||||
|
|
||||||
def _descriptor_detail_for_subject(subject: Any, descriptor_detail: Any) -> str:
|
def _descriptor_detail_for_subject(subject: Any, descriptor_detail: Any) -> str:
|
||||||
|
|||||||
@@ -302,3 +302,98 @@ def character_expression_entries(
|
|||||||
used.add(choice)
|
used.add(choice)
|
||||||
expressions.append(f"{label} has {choice}")
|
expressions.append(f"{label} has {choice}")
|
||||||
return expressions
|
return expressions
|
||||||
|
|
||||||
|
|
||||||
|
def sanitize_character_expression_text_for_action(
|
||||||
|
expression_text: str,
|
||||||
|
role_graph: Any,
|
||||||
|
item: Any,
|
||||||
|
axis_values: Any = None,
|
||||||
|
) -> str:
|
||||||
|
text = str(expression_text or "").strip()
|
||||||
|
if not text:
|
||||||
|
return ""
|
||||||
|
context = " ".join(
|
||||||
|
str(part or "").lower()
|
||||||
|
for part in (
|
||||||
|
role_graph,
|
||||||
|
item,
|
||||||
|
*((axis_values or {}).values() if isinstance(axis_values, dict) else ()),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
woman_active_outercourse = (
|
||||||
|
re.search(r"\bwoman [a-z]\b", context)
|
||||||
|
and re.search(r"\bman [a-z]\b", context)
|
||||||
|
and any(
|
||||||
|
term in context
|
||||||
|
for term in (
|
||||||
|
"boobjob",
|
||||||
|
"titjob",
|
||||||
|
"breast sex",
|
||||||
|
"breasts tightly",
|
||||||
|
"testicle",
|
||||||
|
"balls-licking",
|
||||||
|
"balls licking",
|
||||||
|
"penis-licking",
|
||||||
|
"penis licking",
|
||||||
|
"handjob",
|
||||||
|
"hand job",
|
||||||
|
"footjob",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
woman_gives_oral = (
|
||||||
|
re.search(r"\bwoman [a-z]\b", context)
|
||||||
|
and re.search(r"\bman [a-z]\b", context)
|
||||||
|
and any(
|
||||||
|
term in context
|
||||||
|
for term in (
|
||||||
|
"takes man",
|
||||||
|
"penis in her mouth",
|
||||||
|
"mouth at penis level",
|
||||||
|
"fellatio",
|
||||||
|
"blowjob",
|
||||||
|
"deepthroat",
|
||||||
|
"penis sucking",
|
||||||
|
"lips wrapped",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
man_gives_oral = (
|
||||||
|
re.search(r"\bwoman [a-z]\b", context)
|
||||||
|
and re.search(r"\bman [a-z]\b", context)
|
||||||
|
and any(
|
||||||
|
term in context
|
||||||
|
for term in (
|
||||||
|
"mouth on her pussy",
|
||||||
|
"mouth on woman",
|
||||||
|
"mouth pressed to her pussy",
|
||||||
|
"cunnilingus",
|
||||||
|
"pussy licking",
|
||||||
|
"tongue on pussy",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
mouth_expression_terms = ("mouth", "oral", "tongue", "lips", "gagging", "saliva")
|
||||||
|
clauses = [clause.strip() for clause in text.split(";") if clause.strip()]
|
||||||
|
if woman_active_outercourse:
|
||||||
|
clauses = [clause for clause in clauses if not re.match(r"^Man [A-Z] has\b", clause)]
|
||||||
|
if woman_gives_oral:
|
||||||
|
clauses = [
|
||||||
|
clause
|
||||||
|
for clause in clauses
|
||||||
|
if not (
|
||||||
|
re.match(r"^Man [A-Z] has\b", clause)
|
||||||
|
and any(term in clause.lower() for term in mouth_expression_terms)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
if man_gives_oral:
|
||||||
|
clauses = [
|
||||||
|
clause
|
||||||
|
for clause in clauses
|
||||||
|
if not (
|
||||||
|
re.match(r"^Woman [A-Z] has\b", clause)
|
||||||
|
and any(term in clause.lower() for term in mouth_expression_terms)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
return "; ".join(clauses)
|
||||||
|
|||||||
@@ -733,6 +733,25 @@ def smoke_row_expression_policy() -> None:
|
|||||||
== (None, "character_slots:disabled"),
|
== (None, "character_slots:disabled"),
|
||||||
"Row expression cast override did not honor all-slot expression disable",
|
"Row expression cast override did not honor all-slot expression disable",
|
||||||
)
|
)
|
||||||
|
expression_text = "Woman A has steady focus; Man A has parted lips with saliva"
|
||||||
|
context_role = "Woman A performs a handjob while Man A stands close"
|
||||||
|
_expect(
|
||||||
|
pb._sanitize_character_expression_text_for_action(expression_text, context_role, "", {})
|
||||||
|
== row_expression.sanitize_character_expression_text_for_action(expression_text, context_role, "", {})
|
||||||
|
== "Woman A has steady focus",
|
||||||
|
"Row expression action sanitizer did not remove incompatible partner expression",
|
||||||
|
)
|
||||||
|
reverse_context = "Man A has mouth pressed to her pussy while Woman A lies back"
|
||||||
|
_expect(
|
||||||
|
row_expression.sanitize_character_expression_text_for_action(
|
||||||
|
"Woman A has tongue out; Man A has focused mouth contact",
|
||||||
|
reverse_context,
|
||||||
|
"",
|
||||||
|
{},
|
||||||
|
)
|
||||||
|
== "Man A has focused mouth contact",
|
||||||
|
"Row expression action sanitizer did not remove incompatible visible-subject expression",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def smoke_row_item_policy() -> None:
|
def smoke_row_item_policy() -> None:
|
||||||
|
|||||||
Reference in New Issue
Block a user