Add typed action route metadata
This commit is contained in:
@@ -138,9 +138,11 @@ Already isolated:
|
|||||||
- row prompt/caption template selection, safe formatting, default prompt
|
- row prompt/caption template selection, safe formatting, default prompt
|
||||||
templates, configured-cast descriptor insertion, and POV directive insertion
|
templates, configured-cast descriptor insertion, and POV directive insertion
|
||||||
live in `row_rendering.py`; `prompt_builder.py` keeps compatibility aliases.
|
live in `row_rendering.py`; `prompt_builder.py` keeps compatibility aliases.
|
||||||
- row action/position route metadata resolution, template metadata precedence,
|
- row action/position route metadata resolution lives in
|
||||||
inferred position-key merging, and source action-family fallback live in
|
`row_route_metadata.py` behind `ActionPositionRoute`, covering template
|
||||||
`row_route_metadata.py`; `prompt_builder.py` keeps a public delegate wrapper.
|
metadata precedence, inferred position-key merging, legacy dict
|
||||||
|
compatibility, and source action-family fallback; `prompt_builder.py` keeps
|
||||||
|
public delegate wrappers.
|
||||||
- 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.
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ Core helper ownership:
|
|||||||
| `row_rendering.py` | Row prompt/caption text-field resolution, template selection, safe formatting, default prompt templates, configured-cast descriptor insertion, and POV directive insertion. |
|
| `row_rendering.py` | Row prompt/caption text-field resolution, template selection, safe formatting, default prompt templates, configured-cast descriptor insertion, and POV directive insertion. |
|
||||||
| `row_role_graph.py` | Row role-graph route sequencing, including hardcore source graph construction, pose-category environment-anchor cleanup, and POV role-graph rewriting. |
|
| `row_role_graph.py` | Row role-graph route sequencing, including hardcore source graph construction, pose-category environment-anchor cleanup, and POV role-graph rewriting. |
|
||||||
| `row_assembly.py` | Final custom-row dictionary assembly behind `CustomRowAssemblyRequest`, render-context metadata population, prompt/caption rendering delegation, row-base indexing, cast/profile/slot metadata copying, and disabled-expression cleanup. |
|
| `row_assembly.py` | Final custom-row dictionary assembly behind `CustomRowAssemblyRequest`, render-context metadata population, prompt/caption rendering delegation, row-base indexing, cast/profile/slot metadata copying, and disabled-expression cleanup. |
|
||||||
| `row_route_metadata.py` | Row action/position route metadata resolution, template metadata precedence, inferred position-key merging, and source action-family fallback. |
|
| `row_route_metadata.py` | Row action/position route metadata resolution behind `ActionPositionRoute`, template metadata precedence, inferred position-key merging, legacy dict compatibility, 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. |
|
||||||
|
|||||||
+30
-5
@@ -267,6 +267,31 @@ def _action_position_route_metadata(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _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,
|
||||||
|
) -> row_route_policy.ActionPositionRoute:
|
||||||
|
return row_route_policy.resolve_action_position_route_result(
|
||||||
|
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)
|
||||||
|
|
||||||
@@ -2286,7 +2311,7 @@ def _build_custom_row(
|
|||||||
character_expression_text = str(prompt_axes.get("character_expression_text") or "")
|
character_expression_text = str(prompt_axes.get("character_expression_text") or "")
|
||||||
source_composition = str(prompt_axes.get("source_composition") or "")
|
source_composition = str(prompt_axes.get("source_composition") or "")
|
||||||
composition = str(prompt_axes.get("composition") or "")
|
composition = str(prompt_axes.get("composition") or "")
|
||||||
action_route = _action_position_route_metadata(
|
action_route = _action_position_route(
|
||||||
is_pose_category=is_pose_category,
|
is_pose_category=is_pose_category,
|
||||||
subcategory=subcategory,
|
subcategory=subcategory,
|
||||||
hardcore_position_config=parsed_hardcore_position_config,
|
hardcore_position_config=parsed_hardcore_position_config,
|
||||||
@@ -2297,10 +2322,10 @@ def _build_custom_row(
|
|||||||
pose=pose,
|
pose=pose,
|
||||||
item_axis_values=item_axis_values,
|
item_axis_values=item_axis_values,
|
||||||
)
|
)
|
||||||
position_family = str(action_route.get("position_family") or "")
|
position_family = action_route.position_family
|
||||||
position_keys = list(action_route.get("position_keys") or [])
|
position_keys = list(action_route.position_keys)
|
||||||
position_key = str(action_route.get("position_key") or "")
|
position_key = action_route.position_key
|
||||||
action_family = str(action_route.get("action_family") or "")
|
action_family = action_route.action_family
|
||||||
|
|
||||||
text_fields = _row_text_fields(category, subcategory, item)
|
text_fields = _row_text_fields(category, subcategory, item)
|
||||||
|
|
||||||
|
|||||||
+60
-14
@@ -1,5 +1,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -20,16 +21,36 @@ EMPTY_ACTION_POSITION_ROUTE = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def empty_action_position_route() -> dict[str, Any]:
|
@dataclass(frozen=True)
|
||||||
|
class ActionPositionRoute:
|
||||||
|
position_family: str
|
||||||
|
position_keys: list[str]
|
||||||
|
position_key: str
|
||||||
|
action_family: str
|
||||||
|
|
||||||
|
def as_dict(self) -> dict[str, Any]:
|
||||||
return {
|
return {
|
||||||
"position_family": "",
|
"position_family": self.position_family,
|
||||||
"position_keys": [],
|
"position_keys": list(self.position_keys),
|
||||||
"position_key": "",
|
"position_key": self.position_key,
|
||||||
"action_family": "",
|
"action_family": self.action_family,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def resolve_action_position_route(
|
def empty_action_position_route_result() -> ActionPositionRoute:
|
||||||
|
return ActionPositionRoute(
|
||||||
|
position_family="",
|
||||||
|
position_keys=[],
|
||||||
|
position_key="",
|
||||||
|
action_family="",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def empty_action_position_route() -> dict[str, Any]:
|
||||||
|
return empty_action_position_route_result().as_dict()
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_action_position_route_result(
|
||||||
*,
|
*,
|
||||||
is_pose_category: bool,
|
is_pose_category: bool,
|
||||||
subcategory: dict[str, Any],
|
subcategory: dict[str, Any],
|
||||||
@@ -40,9 +61,9 @@ def resolve_action_position_route(
|
|||||||
source_composition: Any,
|
source_composition: Any,
|
||||||
pose: Any,
|
pose: Any,
|
||||||
item_axis_values: dict[str, Any] | None = None,
|
item_axis_values: dict[str, Any] | None = None,
|
||||||
) -> dict[str, Any]:
|
) -> ActionPositionRoute:
|
||||||
if not is_pose_category:
|
if not is_pose_category:
|
||||||
return empty_action_position_route()
|
return empty_action_position_route_result()
|
||||||
|
|
||||||
metadata = item_template_metadata or {}
|
metadata = item_template_metadata or {}
|
||||||
position_family = template_policy.template_position_family(
|
position_family = template_policy.template_position_family(
|
||||||
@@ -72,9 +93,34 @@ def resolve_action_position_route(
|
|||||||
item_axis_values,
|
item_axis_values,
|
||||||
)
|
)
|
||||||
|
|
||||||
return {
|
return ActionPositionRoute(
|
||||||
"position_family": position_family,
|
position_family=position_family,
|
||||||
"position_keys": position_keys,
|
position_keys=position_keys,
|
||||||
"position_key": position_keys[0] if position_keys else "",
|
position_key=position_keys[0] if position_keys else "",
|
||||||
"action_family": action_family,
|
action_family=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]:
|
||||||
|
return resolve_action_position_route_result(
|
||||||
|
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,
|
||||||
|
).as_dict()
|
||||||
|
|||||||
@@ -2445,6 +2445,19 @@ def smoke_row_route_metadata_policy() -> None:
|
|||||||
_expect(route["position_family"] == "oral", "Route policy lost template position 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_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")
|
_expect(route["position_keys"] == ["kneeling", "open_thighs"], "Route policy changed template position-key precedence")
|
||||||
|
route_result = row_route_metadata.resolve_action_position_route_result(
|
||||||
|
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_result.as_dict() == route, "Typed action/position route should match legacy dict route")
|
||||||
|
_expect(route_result.position_key == "kneeling", "Typed action/position route lost first position key")
|
||||||
|
|
||||||
delegated = pb._action_position_route_metadata(
|
delegated = pb._action_position_route_metadata(
|
||||||
is_pose_category=True,
|
is_pose_category=True,
|
||||||
@@ -2458,6 +2471,18 @@ def smoke_row_route_metadata_policy() -> None:
|
|||||||
item_axis_values={"position": "kneeling oral position"},
|
item_axis_values={"position": "kneeling oral position"},
|
||||||
)
|
)
|
||||||
_expect(delegated == route, "Prompt builder route wrapper should delegate to row_route_metadata")
|
_expect(delegated == route, "Prompt builder route wrapper should delegate to row_route_metadata")
|
||||||
|
typed_delegated = pb._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(typed_delegated == route_result, "Prompt builder typed route wrapper should delegate to row_route_metadata")
|
||||||
|
|
||||||
fallback = row_route_metadata.resolve_action_position_route(
|
fallback = row_route_metadata.resolve_action_position_route(
|
||||||
is_pose_category=True,
|
is_pose_category=True,
|
||||||
@@ -2485,6 +2510,10 @@ def smoke_row_route_metadata_policy() -> None:
|
|||||||
pose="standing pose",
|
pose="standing pose",
|
||||||
)
|
)
|
||||||
_expect(empty == row_route_metadata.empty_action_position_route(), "Non-pose route should return empty route metadata")
|
_expect(empty == row_route_metadata.empty_action_position_route(), "Non-pose route should return empty route metadata")
|
||||||
|
_expect(
|
||||||
|
row_route_metadata.empty_action_position_route_result().as_dict() == empty,
|
||||||
|
"Typed empty action/position route should match legacy dict route",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def smoke_category_library_route() -> None:
|
def smoke_category_library_route() -> None:
|
||||||
|
|||||||
Reference in New Issue
Block a user