Extract row route metadata policy
This commit is contained in:
@@ -131,6 +131,9 @@ Already isolated:
|
|||||||
- row item selection, weighted item/pair choice, item-template axis filling,
|
- row item selection, weighted item/pair choice, item-template axis filling,
|
||||||
and oral/outercourse axis compatibility filters live in `row_item.py`;
|
and oral/outercourse axis compatibility filters live in `row_item.py`;
|
||||||
`prompt_builder.py` keeps public delegate wrappers.
|
`prompt_builder.py` keeps public delegate wrappers.
|
||||||
|
- row action/position route metadata resolution, template metadata precedence,
|
||||||
|
inferred position-key merging, and source action-family fallback live in
|
||||||
|
`row_route_metadata.py`; `prompt_builder.py` keeps a public delegate wrapper.
|
||||||
- built-in legacy row generation, auto-weighted/auto-full selection, row mode
|
- built-in legacy row generation, auto-weighted/auto-full selection, row mode
|
||||||
randomization, ratio clamps, and expression-intensity randomization live in
|
randomization, ratio clamps, and expression-intensity randomization live in
|
||||||
`row_generation.py`; `prompt_builder.py` keeps public delegate wrappers.
|
`row_generation.py`; `prompt_builder.py` keeps public delegate wrappers.
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ Core helper ownership:
|
|||||||
| `category_extensions.py` | JSON `pool_extensions`, legacy pool patching, built-in category choice lists, and category/subcategory UI choices. |
|
| `category_extensions.py` | JSON `pool_extensions`, legacy pool patching, built-in category choice lists, and category/subcategory UI choices. |
|
||||||
| `category_template_metadata.py` | Object-style item-template metadata extraction, action/position family normalization, position-key normalization, key merging, and audit validation errors. |
|
| `category_template_metadata.py` | Object-style item-template metadata extraction, action/position family normalization, position-key normalization, key merging, and audit validation errors. |
|
||||||
| `row_item.py` | Row item selection, weighted item/pair choice, item-template axis filling, and oral/outercourse axis compatibility filters. |
|
| `row_item.py` | Row item selection, weighted item/pair choice, item-template axis filling, and oral/outercourse axis compatibility filters. |
|
||||||
|
| `row_route_metadata.py` | Row action/position route metadata resolution, template metadata precedence, inferred position-key merging, and source action-family fallback. |
|
||||||
| `row_generation.py` | Built-in legacy row generation, auto-weighted/auto-full selection, row mode randomization, ratio clamps, and expression-intensity randomization. |
|
| `row_generation.py` | Built-in legacy row generation, auto-weighted/auto-full selection, row mode randomization, ratio clamps, and expression-intensity randomization. |
|
||||||
| `category_cast_config.py` | Category preset and cast preset schemas, category/cast config JSON builders, choice lists, and config parsers used by route nodes. |
|
| `category_cast_config.py` | Category preset and cast preset schemas, category/cast config JSON builders, choice lists, and config parsers used by route nodes. |
|
||||||
| `cast_context.py` | Generation-time cast count phrases, configured-cast context metadata, character-slot label assignment, cast-summary wording, scene-kind labels, and couple count normalization. |
|
| `cast_context.py` | Generation-time cast count phrases, configured-cast context metadata, character-slot label assignment, cast-summary wording, scene-kind labels, and couple count normalization. |
|
||||||
@@ -473,9 +474,9 @@ plain prompt text. When debugging, inspect these fields before editing pools.
|
|||||||
| `item_axis_values` | `row_item.compose_item` | Krea hardcore rewrite, SDXL tags | Filled template axes such as position/action/detail values. |
|
| `item_axis_values` | `row_item.compose_item` | Krea hardcore rewrite, SDXL tags | Filled template axes such as position/action/detail values. |
|
||||||
| `item_template_metadata` | `row_item.compose_item` | Debug, Krea/SDXL/Naturalizer route metadata | Optional metadata from object-style item templates; currently used to prefer explicit action/position families and keys before inference. |
|
| `item_template_metadata` | `row_item.compose_item` | Debug, Krea/SDXL/Naturalizer route metadata | Optional metadata from object-style item templates; currently used to prefer explicit action/position families and keys before inference. |
|
||||||
| `formatter_hints` | `category_template_metadata.formatter_hints` | Krea/SDXL/Naturalizer route specialization, debug | Normalized route-specific hints from object-style item templates, keyed by `all`, `krea`, `sdxl`, or `caption`; each formatter consumes `all` plus its own route only. |
|
| `formatter_hints` | `category_template_metadata.formatter_hints` | Krea/SDXL/Naturalizer route specialization, debug | Normalized route-specific hints from object-style item templates, keyed by `all`, `krea`, `sdxl`, or `caption`; each formatter consumes `all` plus its own route only. |
|
||||||
| `action_family` | `item_template_metadata` or `hardcore_action_metadata.source_hardcore_action_family` | Krea hardcore rewrite, SDXL tags, natural captions, debug | Source-aware formatter semantic family such as `foreplay`, `outercourse`, `oral`, `penetration`, `toy_double`, or `climax`. |
|
| `action_family` | `row_route_metadata.resolve_action_position_route` | Krea hardcore rewrite, SDXL tags, natural captions, debug | Source-aware formatter semantic family such as `foreplay`, `outercourse`, `oral`, `penetration`, `toy_double`, or `climax`. |
|
||||||
| `position_family` | `item_template_metadata` or `_hardcore_source_position_family` | Debug/filtering | Source/UI hardcore family selected by template metadata or subcategory, such as `manual`, `interaction`, `oral`, `anal`, or `climax`. |
|
| `position_family` | `row_route_metadata.resolve_action_position_route` | Debug/filtering | Source/UI hardcore family selected by template metadata or subcategory, such as `manual`, `interaction`, `oral`, `anal`, or `climax`. |
|
||||||
| `position_key`, `position_keys` | `item_template_metadata` plus `_hardcore_position_keys` | Debug/future filters | Concrete position tokens from object-template metadata and inferred axes/role text, such as `kneeling`, `doggy`, `boobjob`, or `open_thighs`. |
|
| `position_key`, `position_keys` | `row_route_metadata.resolve_action_position_route` | Debug/future filters | Concrete position tokens from object-template metadata and inferred axes/role text, such as `kneeling`, `doggy`, `boobjob`, or `open_thighs`. |
|
||||||
| `custom_item`, `item_label` | Category/pair route | Formatters and debug | Label/name for item route. |
|
| `custom_item`, `item_label` | Category/pair route | Formatters and debug | Label/name for item route. |
|
||||||
| `role_graph` | `_role_graph`, POV adapter | Krea/Naturalizer | Choreography/action relationship text after POV adaptation. |
|
| `role_graph` | `_role_graph`, POV adapter | Krea/Naturalizer | Choreography/action relationship text after POV adaptation. |
|
||||||
| `source_role_graph` | `_role_graph` before POV rewrite | Krea hardcore rewrite | Raw action graph used to infer position and contact. |
|
| `source_role_graph` | `_role_graph` before POV rewrite | Krea hardcore rewrite | Raw action graph used to infer position and contact. |
|
||||||
|
|||||||
+42
-30
@@ -42,13 +42,13 @@ try:
|
|||||||
from . import row_item as row_item_policy
|
from . import row_item as row_item_policy
|
||||||
from . import row_location as row_location_policy
|
from . import row_location as row_location_policy
|
||||||
from . import row_pools as row_pool_policy
|
from . import row_pools as row_pool_policy
|
||||||
|
from . import row_route_metadata as row_route_policy
|
||||||
from . import seed_config as seed_policy
|
from . import seed_config as seed_policy
|
||||||
from . import subject_context as subject_context_policy
|
from . import subject_context as subject_context_policy
|
||||||
from .hardcore_text_cleanup import (
|
from .hardcore_text_cleanup import (
|
||||||
sanitize_hardcore_axis_values as _sanitize_hardcore_axis_values,
|
sanitize_hardcore_axis_values as _sanitize_hardcore_axis_values,
|
||||||
sanitize_hardcore_environment_anchors as _sanitize_hardcore_environment_anchors,
|
sanitize_hardcore_environment_anchors as _sanitize_hardcore_environment_anchors,
|
||||||
)
|
)
|
||||||
from .hardcore_action_metadata import source_hardcore_action_family
|
|
||||||
from .hardcore_role_graphs import build_hardcore_role_graph
|
from .hardcore_role_graphs import build_hardcore_role_graph
|
||||||
except ImportError: # Allows local smoke tests with `python -c`.
|
except ImportError: # Allows local smoke tests with `python -c`.
|
||||||
from category_library import (
|
from category_library import (
|
||||||
@@ -86,13 +86,13 @@ except ImportError: # Allows local smoke tests with `python -c`.
|
|||||||
import row_item as row_item_policy
|
import row_item as row_item_policy
|
||||||
import row_location as row_location_policy
|
import row_location as row_location_policy
|
||||||
import row_pools as row_pool_policy
|
import row_pools as row_pool_policy
|
||||||
|
import row_route_metadata as row_route_policy
|
||||||
import seed_config as seed_policy
|
import seed_config as seed_policy
|
||||||
import subject_context as subject_context_policy
|
import subject_context as subject_context_policy
|
||||||
from hardcore_text_cleanup import (
|
from hardcore_text_cleanup import (
|
||||||
sanitize_hardcore_axis_values as _sanitize_hardcore_axis_values,
|
sanitize_hardcore_axis_values as _sanitize_hardcore_axis_values,
|
||||||
sanitize_hardcore_environment_anchors as _sanitize_hardcore_environment_anchors,
|
sanitize_hardcore_environment_anchors as _sanitize_hardcore_environment_anchors,
|
||||||
)
|
)
|
||||||
from hardcore_action_metadata import source_hardcore_action_family
|
|
||||||
from hardcore_role_graphs import build_hardcore_role_graph
|
from hardcore_role_graphs import build_hardcore_role_graph
|
||||||
|
|
||||||
|
|
||||||
@@ -259,6 +259,31 @@ def _merge_position_keys(primary: list[str], fallback: list[str]) -> list[str]:
|
|||||||
return item_template_policy.merge_position_keys(primary, fallback)
|
return item_template_policy.merge_position_keys(primary, fallback)
|
||||||
|
|
||||||
|
|
||||||
|
def _action_position_route_metadata(
|
||||||
|
*,
|
||||||
|
is_pose_category: bool,
|
||||||
|
subcategory: dict[str, Any],
|
||||||
|
hardcore_position_config: dict[str, Any] | None,
|
||||||
|
item_template_metadata: dict[str, Any] | None,
|
||||||
|
item_text: Any,
|
||||||
|
source_role_graph: Any,
|
||||||
|
source_composition: Any,
|
||||||
|
pose: Any,
|
||||||
|
item_axis_values: dict[str, Any] | None = None,
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
return row_route_policy.resolve_action_position_route(
|
||||||
|
is_pose_category=is_pose_category,
|
||||||
|
subcategory=subcategory,
|
||||||
|
hardcore_position_config=hardcore_position_config,
|
||||||
|
item_template_metadata=item_template_metadata,
|
||||||
|
item_text=item_text,
|
||||||
|
source_role_graph=source_role_graph,
|
||||||
|
source_composition=source_composition,
|
||||||
|
pose=pose,
|
||||||
|
item_axis_values=item_axis_values,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _oral_acts_for_position(values: list[Any], position: str) -> list[Any]:
|
def _oral_acts_for_position(values: list[Any], position: str) -> list[Any]:
|
||||||
return row_item_policy.oral_acts_for_position(values, position)
|
return row_item_policy.oral_acts_for_position(values, position)
|
||||||
|
|
||||||
@@ -2201,34 +2226,21 @@ def _build_custom_row(
|
|||||||
if is_pose_category:
|
if is_pose_category:
|
||||||
source_composition = _sanitize_hardcore_environment_anchors(source_composition)
|
source_composition = _sanitize_hardcore_environment_anchors(source_composition)
|
||||||
composition = _pov_composition_prompt(source_composition, pov_character_labels)
|
composition = _pov_composition_prompt(source_composition, pov_character_labels)
|
||||||
position_family = ""
|
action_route = _action_position_route_metadata(
|
||||||
position_keys: list[str] = []
|
is_pose_category=is_pose_category,
|
||||||
position_key = ""
|
subcategory=subcategory,
|
||||||
action_family = ""
|
hardcore_position_config=parsed_hardcore_position_config,
|
||||||
if is_pose_category:
|
item_template_metadata=item_template_metadata,
|
||||||
template_position_family = _template_position_family(item_template_metadata)
|
item_text=item_text,
|
||||||
position_family = template_position_family or _hardcore_source_position_family(
|
source_role_graph=source_role_graph,
|
||||||
subcategory,
|
source_composition=source_composition,
|
||||||
parsed_hardcore_position_config,
|
pose=pose,
|
||||||
)
|
item_axis_values=item_axis_values,
|
||||||
inferred_position_keys = _hardcore_position_keys(
|
)
|
||||||
item_text,
|
position_family = str(action_route.get("position_family") or "")
|
||||||
source_role_graph,
|
position_keys = list(action_route.get("position_keys") or [])
|
||||||
source_composition,
|
position_key = str(action_route.get("position_key") or "")
|
||||||
pose,
|
action_family = str(action_route.get("action_family") or "")
|
||||||
axis_values=item_axis_values,
|
|
||||||
)
|
|
||||||
position_keys = _merge_position_keys(_template_position_keys(item_template_metadata), inferred_position_keys)
|
|
||||||
position_key = position_keys[0] if position_keys else ""
|
|
||||||
action_family = _template_action_family(item_template_metadata)
|
|
||||||
if not action_family:
|
|
||||||
action_family = source_hardcore_action_family(
|
|
||||||
position_family,
|
|
||||||
source_role_graph,
|
|
||||||
item_text,
|
|
||||||
source_composition,
|
|
||||||
item_axis_values,
|
|
||||||
)
|
|
||||||
|
|
||||||
negative_prompt = str(_merged_field(category, subcategory, item, "negative_prompt", g.NEGATIVE_PROMPT))
|
negative_prompt = str(_merged_field(category, subcategory, item, "negative_prompt", g.NEGATIVE_PROMPT))
|
||||||
positive_suffix = str(_merged_field(category, subcategory, item, "positive_suffix", GENERIC_POSITIVE_SUFFIX))
|
positive_suffix = str(_merged_field(category, subcategory, item, "positive_suffix", GENERIC_POSITIVE_SUFFIX))
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
try:
|
||||||
|
from . import category_template_metadata as template_policy
|
||||||
|
from . import hardcore_position_config as hardcore_position_policy
|
||||||
|
from .hardcore_action_metadata import source_hardcore_action_family
|
||||||
|
except ImportError: # Allows local smoke tests from the repository root.
|
||||||
|
import category_template_metadata as template_policy
|
||||||
|
import hardcore_position_config as hardcore_position_policy
|
||||||
|
from hardcore_action_metadata import source_hardcore_action_family
|
||||||
|
|
||||||
|
|
||||||
|
EMPTY_ACTION_POSITION_ROUTE = {
|
||||||
|
"position_family": "",
|
||||||
|
"position_keys": [],
|
||||||
|
"position_key": "",
|
||||||
|
"action_family": "",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def empty_action_position_route() -> dict[str, Any]:
|
||||||
|
return {
|
||||||
|
"position_family": "",
|
||||||
|
"position_keys": [],
|
||||||
|
"position_key": "",
|
||||||
|
"action_family": "",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_action_position_route(
|
||||||
|
*,
|
||||||
|
is_pose_category: bool,
|
||||||
|
subcategory: dict[str, Any],
|
||||||
|
hardcore_position_config: dict[str, Any] | None,
|
||||||
|
item_template_metadata: dict[str, Any] | None,
|
||||||
|
item_text: Any,
|
||||||
|
source_role_graph: Any,
|
||||||
|
source_composition: Any,
|
||||||
|
pose: Any,
|
||||||
|
item_axis_values: dict[str, Any] | None = None,
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
if not is_pose_category:
|
||||||
|
return empty_action_position_route()
|
||||||
|
|
||||||
|
metadata = item_template_metadata or {}
|
||||||
|
position_family = template_policy.template_position_family(
|
||||||
|
metadata
|
||||||
|
) or hardcore_position_policy.hardcore_source_position_family(
|
||||||
|
subcategory,
|
||||||
|
hardcore_position_config,
|
||||||
|
)
|
||||||
|
inferred_position_keys = hardcore_position_policy.hardcore_position_keys(
|
||||||
|
item_text,
|
||||||
|
source_role_graph,
|
||||||
|
source_composition,
|
||||||
|
pose,
|
||||||
|
axis_values=item_axis_values,
|
||||||
|
)
|
||||||
|
position_keys = template_policy.merge_position_keys(
|
||||||
|
template_policy.template_position_keys(metadata),
|
||||||
|
inferred_position_keys,
|
||||||
|
)
|
||||||
|
action_family = template_policy.template_action_family(metadata)
|
||||||
|
if not action_family:
|
||||||
|
action_family = source_hardcore_action_family(
|
||||||
|
position_family,
|
||||||
|
source_role_graph,
|
||||||
|
item_text,
|
||||||
|
source_composition,
|
||||||
|
item_axis_values,
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"position_family": position_family,
|
||||||
|
"position_keys": position_keys,
|
||||||
|
"position_key": position_keys[0] if position_keys else "",
|
||||||
|
"action_family": action_family,
|
||||||
|
}
|
||||||
@@ -57,6 +57,7 @@ import row_generation # noqa: E402
|
|||||||
import row_item # noqa: E402
|
import row_item # noqa: E402
|
||||||
import row_location # noqa: E402
|
import row_location # noqa: E402
|
||||||
import row_pools # noqa: E402
|
import row_pools # noqa: E402
|
||||||
|
import row_route_metadata # noqa: E402
|
||||||
import server_routes # noqa: E402
|
import server_routes # noqa: E402
|
||||||
import sdxl_formatter # noqa: E402
|
import sdxl_formatter # noqa: E402
|
||||||
import sdxl_presets # noqa: E402
|
import sdxl_presets # noqa: E402
|
||||||
@@ -1882,6 +1883,69 @@ def smoke_hardcore_position_config_policy() -> None:
|
|||||||
_expect(any("invalid formatter_hint" in error for error in invalid_errors), "Template metadata validation missed bad formatter hint value")
|
_expect(any("invalid formatter_hint" in error for error in invalid_errors), "Template metadata validation missed bad formatter hint value")
|
||||||
|
|
||||||
|
|
||||||
|
def smoke_row_route_metadata_policy() -> None:
|
||||||
|
template_metadata = {
|
||||||
|
"action_family": "oral",
|
||||||
|
"position_family": "oral",
|
||||||
|
"position_keys": ["kneeling", "open_thighs"],
|
||||||
|
}
|
||||||
|
route = row_route_metadata.resolve_action_position_route(
|
||||||
|
is_pose_category=True,
|
||||||
|
subcategory={"slug": "oral_sex"},
|
||||||
|
hardcore_position_config={},
|
||||||
|
item_template_metadata=template_metadata,
|
||||||
|
item_text="mouth contact in kneeling oral position",
|
||||||
|
source_role_graph="the woman kneels in front of the man",
|
||||||
|
source_composition="close kneeling oral composition",
|
||||||
|
pose="kneeling pose",
|
||||||
|
item_axis_values={"position": "kneeling oral position"},
|
||||||
|
)
|
||||||
|
_expect(route["action_family"] == "oral", "Route policy lost template action family")
|
||||||
|
_expect(route["position_family"] == "oral", "Route policy lost template position family")
|
||||||
|
_expect(route["position_key"] == "kneeling", "Route policy did not preserve first template position key")
|
||||||
|
_expect(route["position_keys"] == ["kneeling", "open_thighs"], "Route policy changed template position-key precedence")
|
||||||
|
|
||||||
|
delegated = pb._action_position_route_metadata(
|
||||||
|
is_pose_category=True,
|
||||||
|
subcategory={"slug": "oral_sex"},
|
||||||
|
hardcore_position_config={},
|
||||||
|
item_template_metadata=template_metadata,
|
||||||
|
item_text="mouth contact in kneeling oral position",
|
||||||
|
source_role_graph="the woman kneels in front of the man",
|
||||||
|
source_composition="close kneeling oral composition",
|
||||||
|
pose="kneeling pose",
|
||||||
|
item_axis_values={"position": "kneeling oral position"},
|
||||||
|
)
|
||||||
|
_expect(delegated == route, "Prompt builder route wrapper should delegate to row_route_metadata")
|
||||||
|
|
||||||
|
fallback = row_route_metadata.resolve_action_position_route(
|
||||||
|
is_pose_category=True,
|
||||||
|
subcategory={"slug": "manual_stimulation"},
|
||||||
|
hardcore_position_config={},
|
||||||
|
item_template_metadata={},
|
||||||
|
item_text="manual stimulation while kneeling",
|
||||||
|
source_role_graph="the woman kneels close and uses her hand",
|
||||||
|
source_composition="kneeling manual composition",
|
||||||
|
pose="kneeling pose",
|
||||||
|
item_axis_values={"position": "kneeling manual position"},
|
||||||
|
)
|
||||||
|
_expect(fallback["position_family"] == "manual", "Route policy lost source position-family fallback")
|
||||||
|
_expect(fallback["action_family"] == "foreplay", "Route policy lost source action-family fallback")
|
||||||
|
_expect("kneeling" in fallback["position_keys"], "Route policy lost inferred position key")
|
||||||
|
|
||||||
|
empty = row_route_metadata.resolve_action_position_route(
|
||||||
|
is_pose_category=False,
|
||||||
|
subcategory={"slug": "casual_clothes"},
|
||||||
|
hardcore_position_config={},
|
||||||
|
item_template_metadata=template_metadata,
|
||||||
|
item_text="casual outfit",
|
||||||
|
source_role_graph="",
|
||||||
|
source_composition="",
|
||||||
|
pose="standing pose",
|
||||||
|
)
|
||||||
|
_expect(empty == row_route_metadata.empty_action_position_route(), "Non-pose route should return empty route metadata")
|
||||||
|
|
||||||
|
|
||||||
def smoke_category_library_route() -> None:
|
def smoke_category_library_route() -> None:
|
||||||
categories = category_library.load_category_library()
|
categories = category_library.load_category_library()
|
||||||
_expect(len(categories) >= 3, "category library should load JSON categories")
|
_expect(len(categories) >= 3, "category library should load JSON categories")
|
||||||
@@ -4138,6 +4202,7 @@ SMOKE_CASES: list[tuple[str, Callable[[], None]]] = [
|
|||||||
("caption_policy", smoke_caption_policy),
|
("caption_policy", smoke_caption_policy),
|
||||||
("sdxl_presets_policy", smoke_sdxl_presets_policy),
|
("sdxl_presets_policy", smoke_sdxl_presets_policy),
|
||||||
("hardcore_position_config_policy", smoke_hardcore_position_config_policy),
|
("hardcore_position_config_policy", smoke_hardcore_position_config_policy),
|
||||||
|
("row_route_metadata_policy", smoke_row_route_metadata_policy),
|
||||||
("category_library_route", smoke_category_library_route),
|
("category_library_route", smoke_category_library_route),
|
||||||
("hardcore_category_routes", smoke_hardcore_category_routes),
|
("hardcore_category_routes", smoke_hardcore_category_routes),
|
||||||
("krea_close_foreplay_route", smoke_krea_close_foreplay_route),
|
("krea_close_foreplay_route", smoke_krea_close_foreplay_route),
|
||||||
|
|||||||
Reference in New Issue
Block a user