Extract expression route resolution
This commit is contained in:
@@ -195,10 +195,11 @@ Already isolated:
|
||||
filtering, expression-disabled handling, per-character expression promotion,
|
||||
POV composition adaptation, and pose-category environment sanitizing live in
|
||||
`row_prompt_axes.py`; `prompt_builder.py` keeps a public delegate wrapper.
|
||||
- row expression text cleanup, expression intensity weighting,
|
||||
character-slot/cast expression override resolution, and per-character
|
||||
expression picking plus action-aware character-expression sanitizing live in
|
||||
`row_expression.py`; `prompt_builder.py` keeps public delegate wrappers.
|
||||
- row expression text cleanup, expression route resolution, expression
|
||||
intensity weighting, character-slot/cast expression override resolution, and
|
||||
per-character expression picking plus action-aware character-expression
|
||||
sanitizing live in `row_expression.py`; `prompt_builder.py` keeps public
|
||||
delegate wrappers.
|
||||
- hardcore position/action-filter choices, selected-position normalization,
|
||||
config JSON builders/parsers, focus-policy toggles, subcategory allow-list
|
||||
policy, position-key detection, category filtering, and item-template/axis
|
||||
|
||||
@@ -90,7 +90,7 @@ Core helper ownership:
|
||||
| `row_subject_route.py` | Row subject route orchestration, character slot/profile precedence, configured-cast POV labels, visible cast descriptor collection, and descriptor prompt cleanup. |
|
||||
| `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_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_expression.py` | Row expression cleanup, expression route resolution, 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_prompt_axes.py` | Row scene/pose/expression/composition axis selection, compatible-entry filtering, expression-disabled handling, per-character expression promotion, POV composition adaptation, and pose-category environment sanitizing. |
|
||||
| `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. |
|
||||
|
||||
+41
-23
@@ -1446,6 +1446,33 @@ def _cast_expression_intensity_override(
|
||||
)
|
||||
|
||||
|
||||
def _resolve_expression_route(
|
||||
*,
|
||||
expression_enabled: bool,
|
||||
expression_intensity: float,
|
||||
expression_intensity_source: str,
|
||||
subject_type: str,
|
||||
applied_slot: dict[str, Any] | None = None,
|
||||
character_slots: list[dict[str, Any]] | None = None,
|
||||
character_slot_map: dict[str, dict[str, Any]] | None = None,
|
||||
women_count: int = 1,
|
||||
men_count: int = 1,
|
||||
expression_phase: str = "",
|
||||
) -> row_expression_policy.ExpressionRoute:
|
||||
return row_expression_policy.resolve_expression_route(
|
||||
expression_enabled=expression_enabled,
|
||||
expression_intensity=expression_intensity,
|
||||
expression_intensity_source=expression_intensity_source,
|
||||
subject_type=subject_type,
|
||||
applied_slot=applied_slot,
|
||||
character_slots=character_slots,
|
||||
character_slot_map=character_slot_map,
|
||||
women_count=women_count,
|
||||
men_count=men_count,
|
||||
expression_phase=expression_phase,
|
||||
)
|
||||
|
||||
|
||||
def _character_expression_entries(
|
||||
rng: random.Random,
|
||||
expression_pool: list[Any],
|
||||
@@ -2176,30 +2203,21 @@ def _build_custom_row(
|
||||
if is_pose_category:
|
||||
source_role_graph = _sanitize_hardcore_environment_anchors(source_role_graph)
|
||||
role_graph = _pov_role_graph_prompt(source_role_graph, pov_character_labels)
|
||||
expression_intensity_source = expression_intensity_source or "input"
|
||||
expression_disabled = not bool(expression_enabled)
|
||||
if expression_disabled:
|
||||
expression_intensity_source = "disabled"
|
||||
elif subject_type in ("woman", "man") and applied_slot:
|
||||
slot_label = "Woman A" if subject_type == "woman" else "Man A"
|
||||
if not _slot_expression_enabled(applied_slot):
|
||||
expression_disabled = True
|
||||
expression_intensity_source = f"character_slot:{slot_label}:disabled"
|
||||
else:
|
||||
slot_expression_intensity = _slot_expression_intensity_for_phase(applied_slot, expression_phase)
|
||||
if slot_expression_intensity is not None:
|
||||
expression_intensity = slot_expression_intensity
|
||||
expression_intensity_source = f"character_slot:{slot_label}"
|
||||
elif subject_type == "configured_cast" and character_slots:
|
||||
expression_intensity, expression_intensity_source = _cast_expression_intensity_override(
|
||||
expression_intensity,
|
||||
character_slot_map,
|
||||
women_count,
|
||||
men_count,
|
||||
expression_phase,
|
||||
expression_route = _resolve_expression_route(
|
||||
expression_enabled=expression_enabled,
|
||||
expression_intensity=expression_intensity,
|
||||
expression_intensity_source=expression_intensity_source,
|
||||
subject_type=subject_type,
|
||||
applied_slot=applied_slot,
|
||||
character_slots=character_slots,
|
||||
character_slot_map=character_slot_map,
|
||||
women_count=women_count,
|
||||
men_count=men_count,
|
||||
expression_phase=expression_phase,
|
||||
)
|
||||
if expression_intensity is None:
|
||||
expression_disabled = True
|
||||
expression_disabled = expression_route.expression_disabled
|
||||
expression_intensity = expression_route.expression_intensity
|
||||
expression_intensity_source = expression_route.expression_intensity_source
|
||||
|
||||
prompt_axes = _prompt_axes_route(
|
||||
category=category,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
import random
|
||||
import re
|
||||
from typing import Any
|
||||
@@ -55,6 +56,61 @@ def disable_row_expression(row: dict[str, Any], source: str = "disabled") -> dic
|
||||
return row
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ExpressionRoute:
|
||||
expression_disabled: bool
|
||||
expression_intensity: float | None
|
||||
expression_intensity_source: str
|
||||
|
||||
|
||||
def resolve_expression_route(
|
||||
*,
|
||||
expression_enabled: bool,
|
||||
expression_intensity: float,
|
||||
expression_intensity_source: str,
|
||||
subject_type: str,
|
||||
applied_slot: dict[str, Any] | None = None,
|
||||
character_slots: list[dict[str, Any]] | None = None,
|
||||
character_slot_map: dict[str, dict[str, Any]] | None = None,
|
||||
women_count: int = 1,
|
||||
men_count: int = 1,
|
||||
expression_phase: str = "",
|
||||
) -> ExpressionRoute:
|
||||
source = expression_intensity_source or "input"
|
||||
disabled = not bool(expression_enabled)
|
||||
intensity: float | None = expression_intensity
|
||||
if disabled:
|
||||
source = "disabled"
|
||||
elif subject_type in ("woman", "man") and applied_slot:
|
||||
slot_label = "Woman A" if subject_type == "woman" else "Man A"
|
||||
if not character_slot_policy.slot_expression_enabled(applied_slot):
|
||||
disabled = True
|
||||
source = f"character_slot:{slot_label}:disabled"
|
||||
else:
|
||||
slot_expression_intensity = character_slot_policy.slot_expression_intensity_for_phase(
|
||||
applied_slot,
|
||||
expression_phase,
|
||||
)
|
||||
if slot_expression_intensity is not None:
|
||||
intensity = slot_expression_intensity
|
||||
source = f"character_slot:{slot_label}"
|
||||
elif subject_type == "configured_cast" and character_slots:
|
||||
intensity, source = cast_expression_intensity_override(
|
||||
expression_intensity,
|
||||
character_slot_map or {},
|
||||
women_count,
|
||||
men_count,
|
||||
expression_phase,
|
||||
)
|
||||
if intensity is None:
|
||||
disabled = True
|
||||
return ExpressionRoute(
|
||||
expression_disabled=disabled,
|
||||
expression_intensity=intensity,
|
||||
expression_intensity_source=source,
|
||||
)
|
||||
|
||||
|
||||
def _clamped_float(value: Any, default: float = 0.5, min_value: float = 0.0, max_value: float = 1.0) -> float:
|
||||
try:
|
||||
number = float(value)
|
||||
|
||||
@@ -725,6 +725,41 @@ def smoke_row_expression_policy() -> None:
|
||||
== (0.2, "character_slot:Woman A"),
|
||||
"Row expression cast override did not prefer visible slot phase intensity",
|
||||
)
|
||||
_expect(
|
||||
pb._resolve_expression_route(
|
||||
expression_enabled=True,
|
||||
expression_intensity=0.5,
|
||||
expression_intensity_source="input",
|
||||
subject_type="woman",
|
||||
applied_slot=woman_slot,
|
||||
women_count=1,
|
||||
men_count=0,
|
||||
expression_phase="softcore",
|
||||
)
|
||||
== row_expression.resolve_expression_route(
|
||||
expression_enabled=True,
|
||||
expression_intensity=0.5,
|
||||
expression_intensity_source="input",
|
||||
subject_type="woman",
|
||||
applied_slot=woman_slot,
|
||||
women_count=1,
|
||||
men_count=0,
|
||||
expression_phase="softcore",
|
||||
),
|
||||
"Prompt builder expression route wrapper should delegate to row_expression",
|
||||
)
|
||||
route = row_expression.resolve_expression_route(
|
||||
expression_enabled=True,
|
||||
expression_intensity=0.5,
|
||||
expression_intensity_source="input",
|
||||
subject_type="woman",
|
||||
applied_slot=woman_slot,
|
||||
women_count=1,
|
||||
men_count=0,
|
||||
expression_phase="softcore",
|
||||
)
|
||||
_expect(route.expression_intensity == 0.2, "Expression route did not apply phase-specific slot intensity")
|
||||
_expect(route.expression_intensity_source == "character_slot:Woman A", "Expression route lost slot source")
|
||||
_expect(
|
||||
pb._character_expression_entries(random.Random(22), entries, 0.5, label_map, 1, 1, "softcore")
|
||||
== row_expression.character_expression_entries(random.Random(22), entries, 0.5, label_map, 1, 1, "softcore"),
|
||||
@@ -739,6 +774,45 @@ def smoke_row_expression_policy() -> None:
|
||||
== (None, "character_slots:disabled"),
|
||||
"Row expression cast override did not honor all-slot expression disable",
|
||||
)
|
||||
global_disabled = row_expression.resolve_expression_route(
|
||||
expression_enabled=False,
|
||||
expression_intensity=0.8,
|
||||
expression_intensity_source="input",
|
||||
subject_type="woman",
|
||||
applied_slot=woman_slot,
|
||||
)
|
||||
_expect(global_disabled.expression_disabled is True, "Expression route did not honor global disabled state")
|
||||
_expect(global_disabled.expression_intensity == 0.8, "Expression route changed disabled fallback intensity too early")
|
||||
_expect(global_disabled.expression_intensity_source == "disabled", "Expression route did not mark global disabled source")
|
||||
slot_disabled = row_expression.resolve_expression_route(
|
||||
expression_enabled=True,
|
||||
expression_intensity=0.5,
|
||||
expression_intensity_source="input",
|
||||
subject_type="woman",
|
||||
applied_slot=disabled_slot,
|
||||
)
|
||||
_expect(slot_disabled.expression_disabled is True, "Expression route did not honor single-slot disable")
|
||||
_expect(
|
||||
slot_disabled.expression_intensity_source == "character_slot:Woman A:disabled",
|
||||
"Expression route lost single-slot disabled source",
|
||||
)
|
||||
cast_disabled = row_expression.resolve_expression_route(
|
||||
expression_enabled=True,
|
||||
expression_intensity=0.5,
|
||||
expression_intensity_source="input",
|
||||
subject_type="configured_cast",
|
||||
character_slots=[disabled_slot],
|
||||
character_slot_map={"Woman A": disabled_slot},
|
||||
women_count=1,
|
||||
men_count=0,
|
||||
expression_phase="hardcore",
|
||||
)
|
||||
_expect(cast_disabled.expression_disabled is True, "Expression route did not honor all-slot cast disable")
|
||||
_expect(cast_disabled.expression_intensity is None, "Expression route did not clear all-slot disabled intensity")
|
||||
_expect(
|
||||
cast_disabled.expression_intensity_source == "character_slots:disabled",
|
||||
"Expression route lost all-slot disabled source",
|
||||
)
|
||||
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(
|
||||
|
||||
Reference in New Issue
Block a user