Extract role graph route policy
This commit is contained in:
@@ -199,6 +199,10 @@ Already isolated:
|
||||
safe formatting, configured-cast descriptor insertion, and POV directive
|
||||
insertion live in `row_rendering.py`; `prompt_builder.py` keeps public
|
||||
delegate wrappers.
|
||||
- row role-graph route sequencing lives in `row_role_graph.py`, covering
|
||||
hardcore source role graph construction, pose-category environment-anchor
|
||||
cleanup, and POV role-graph rewriting before prompt axes and formatter
|
||||
metadata consume the graph.
|
||||
- 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
|
||||
@@ -209,8 +213,8 @@ Already isolated:
|
||||
policy, position-key detection, category filtering, and item-template/axis
|
||||
filtering live in `hardcore_position_config.py`.
|
||||
- hardcore configured-cast role graph generation lives in
|
||||
`hardcore_role_graphs.py`; `prompt_builder.py` selects item/axis metadata and
|
||||
then asks that module for the source role graph.
|
||||
`hardcore_role_graphs.py`; row generation reaches it through
|
||||
`row_role_graph.py` after item/axis metadata is selected.
|
||||
- fallback role graph wording lives in `hardcore_role_fallback.py`, covering
|
||||
solo rows, women-only rows, men-only rows, mixed group fallbacks, and support
|
||||
partner sentences.
|
||||
|
||||
@@ -73,6 +73,7 @@ Core helper ownership:
|
||||
| `row_item.py` | Row item selection, weighted item/pair choice, item-template axis filling, and oral/outercourse axis compatibility filters. |
|
||||
| `row_category_route.py` | Row category/subcategory/item route resolution, hardcore position-category filtering, cast-count adjustment, pose-vs-content seed-axis choice, item metadata collection, and pose-category item sanitizing. |
|
||||
| `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_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_generation.py` | Built-in legacy row generation, auto-weighted/auto-full selection, row mode randomization, ratio clamps, and expression-intensity randomization. |
|
||||
@@ -100,7 +101,7 @@ Core helper ownership:
|
||||
| `pair_camera.py` | Insta/OF soft/hard camera route resolution, same-as-softcore camera mode, camera-detail override, camera-aware composition mutation, POV camera suppression, and synchronized row/root camera metadata. |
|
||||
| `pair_clothing.py` | Insta/OF clothing sentence formatting, body-exposure scene cleanup, hardcore clothing continuity, action-aware body-access flags, conflicting outfit-piece cleanup, configured/default visible-person clothing, and final root clothing-state assembly. |
|
||||
| `pair_output.py` | Insta/OF final pair prompts, trigger preservation, negative prompts, captions, and root pair metadata assembly. |
|
||||
| `hardcore_role_graphs.py` | Source role graph construction for hardcore configured-cast rows, including POV-aware interaction geometry. |
|
||||
| `hardcore_role_graphs.py` | Source role graph construction for hardcore configured-cast rows, including POV-aware interaction geometry, called through `row_role_graph.py` for row generation. |
|
||||
| `hardcore_role_fallback.py` | Solo, same-sex, mixed group fallback, and support-partner role graph wording for configured casts. |
|
||||
| `hardcore_role_interaction.py` | Foreplay, manual stimulation, body worship, clothing transition, dominant guidance, camera performance, aftercare, and group coordination role graph wording. |
|
||||
| `hardcore_role_oral.py` | Oral-sex role graph wording for kneeling, face-sitting, sixty-nine, edge-supported, side-lying, chair, standing, and reclining oral geometry. |
|
||||
|
||||
+31
-6
@@ -44,6 +44,7 @@ try:
|
||||
from . import row_prompt_axes as row_prompt_axes_policy
|
||||
from . import row_pools as row_pool_policy
|
||||
from . import row_rendering as row_rendering_policy
|
||||
from . import row_role_graph as row_role_graph_policy
|
||||
from . import row_route_metadata as row_route_policy
|
||||
from . import row_subject_route as row_subject_route_policy
|
||||
from . import seed_config as seed_policy
|
||||
@@ -52,7 +53,6 @@ try:
|
||||
sanitize_hardcore_axis_values as _sanitize_hardcore_axis_values,
|
||||
sanitize_hardcore_environment_anchors as _sanitize_hardcore_environment_anchors,
|
||||
)
|
||||
from .hardcore_role_graphs import build_hardcore_role_graph
|
||||
except ImportError: # Allows local smoke tests with `python -c`.
|
||||
from category_library import (
|
||||
compatible_entries as _compatible_entries,
|
||||
@@ -93,6 +93,7 @@ except ImportError: # Allows local smoke tests with `python -c`.
|
||||
import row_prompt_axes as row_prompt_axes_policy
|
||||
import row_pools as row_pool_policy
|
||||
import row_rendering as row_rendering_policy
|
||||
import row_role_graph as row_role_graph_policy
|
||||
import row_route_metadata as row_route_policy
|
||||
import row_subject_route as row_subject_route_policy
|
||||
import seed_config as seed_policy
|
||||
@@ -101,7 +102,6 @@ except ImportError: # Allows local smoke tests with `python -c`.
|
||||
sanitize_hardcore_axis_values as _sanitize_hardcore_axis_values,
|
||||
sanitize_hardcore_environment_anchors as _sanitize_hardcore_environment_anchors,
|
||||
)
|
||||
from hardcore_role_graphs import build_hardcore_role_graph
|
||||
|
||||
|
||||
ROOT_DIR = Path(__file__).resolve().parent
|
||||
@@ -2121,6 +2121,25 @@ def _prompt_axes_route(
|
||||
)
|
||||
|
||||
|
||||
def _role_graph_route(
|
||||
*,
|
||||
rng: random.Random,
|
||||
subcategory: dict[str, Any],
|
||||
context: dict[str, Any],
|
||||
item_axis_values: dict[str, Any],
|
||||
pov_character_labels: list[str],
|
||||
is_pose_category: bool,
|
||||
) -> row_role_graph_policy.RoleGraphRoute:
|
||||
return row_role_graph_policy.resolve_role_graph_route(
|
||||
rng=rng,
|
||||
subcategory=subcategory,
|
||||
context=context,
|
||||
item_axis_values=item_axis_values,
|
||||
pov_character_labels=pov_character_labels,
|
||||
is_pose_category=is_pose_category,
|
||||
)
|
||||
|
||||
|
||||
def _assemble_custom_row(request: row_assembly_policy.CustomRowAssemblyRequest) -> dict[str, Any]:
|
||||
return row_assembly_policy.assemble_custom_row(request)
|
||||
|
||||
@@ -2207,10 +2226,16 @@ def _build_custom_row(
|
||||
pov_character_labels = list(subject_route.get("pov_character_labels") or [])
|
||||
cast_descriptors = list(subject_route.get("cast_descriptors") or [])
|
||||
cast_descriptor_text = str(subject_route.get("cast_descriptor_text") or "")
|
||||
source_role_graph = build_hardcore_role_graph(role_rng, subcategory, context, item_axis_values, pov_character_labels)
|
||||
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)
|
||||
role_graph_route = _role_graph_route(
|
||||
rng=role_rng,
|
||||
subcategory=subcategory,
|
||||
context=context,
|
||||
item_axis_values=item_axis_values,
|
||||
pov_character_labels=pov_character_labels,
|
||||
is_pose_category=is_pose_category,
|
||||
)
|
||||
source_role_graph = role_graph_route.source_role_graph
|
||||
role_graph = role_graph_route.role_graph
|
||||
expression_route = _resolve_expression_route(
|
||||
expression_enabled=expression_enabled,
|
||||
expression_intensity=expression_intensity,
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import random
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
try:
|
||||
from . import hardcore_role_graphs
|
||||
from . import hardcore_text_cleanup
|
||||
from . import pov_policy
|
||||
except ImportError: # Allows local smoke tests from the repository root.
|
||||
import hardcore_role_graphs
|
||||
import hardcore_text_cleanup
|
||||
import pov_policy
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class RoleGraphRoute:
|
||||
source_role_graph: str
|
||||
role_graph: str
|
||||
|
||||
|
||||
def resolve_role_graph_route(
|
||||
*,
|
||||
rng: random.Random,
|
||||
subcategory: dict[str, Any],
|
||||
context: dict[str, Any],
|
||||
item_axis_values: dict[str, Any],
|
||||
pov_character_labels: list[str],
|
||||
is_pose_category: bool,
|
||||
) -> RoleGraphRoute:
|
||||
source_role_graph = hardcore_role_graphs.build_hardcore_role_graph(
|
||||
rng,
|
||||
subcategory,
|
||||
context,
|
||||
item_axis_values,
|
||||
pov_character_labels,
|
||||
)
|
||||
if is_pose_category:
|
||||
source_role_graph = hardcore_text_cleanup.sanitize_hardcore_environment_anchors(source_role_graph)
|
||||
role_graph = pov_policy.pov_role_graph_prompt(source_role_graph, pov_character_labels)
|
||||
return RoleGraphRoute(source_role_graph=source_role_graph, role_graph=role_graph)
|
||||
@@ -61,6 +61,7 @@ import row_location # noqa: E402
|
||||
import row_pools # noqa: E402
|
||||
import row_prompt_axes # noqa: E402
|
||||
import row_rendering # noqa: E402
|
||||
import row_role_graph # noqa: E402
|
||||
import row_route_metadata # noqa: E402
|
||||
import row_subject_route # noqa: E402
|
||||
import server_routes # noqa: E402
|
||||
@@ -1793,6 +1794,60 @@ def smoke_row_rendering_policy() -> None:
|
||||
)
|
||||
|
||||
|
||||
def smoke_row_role_graph_policy() -> None:
|
||||
empty_route = row_role_graph.resolve_role_graph_route(
|
||||
rng=random.Random(51),
|
||||
subcategory={"slug": "penetration"},
|
||||
context={"subject_type": "woman"},
|
||||
item_axis_values={"position": "missionary"},
|
||||
pov_character_labels=[],
|
||||
is_pose_category=True,
|
||||
)
|
||||
_expect(empty_route == row_role_graph.RoleGraphRoute("", ""), "Role graph route should stay empty outside configured cast")
|
||||
|
||||
context = {
|
||||
"subject_type": "configured_cast",
|
||||
"women_count": "1",
|
||||
"men_count": "1",
|
||||
}
|
||||
subcategory = {"slug": "cumshot_climax", "name": "Cumshot and climax"}
|
||||
axis_values = {"position": "lying at the bed edge with thighs open"}
|
||||
route = row_role_graph.resolve_role_graph_route(
|
||||
rng=random.Random(52),
|
||||
subcategory=subcategory,
|
||||
context=context,
|
||||
item_axis_values=axis_values,
|
||||
pov_character_labels=[],
|
||||
is_pose_category=True,
|
||||
)
|
||||
delegated = pb._role_graph_route(
|
||||
rng=random.Random(52),
|
||||
subcategory=subcategory,
|
||||
context=context,
|
||||
item_axis_values=axis_values,
|
||||
pov_character_labels=[],
|
||||
is_pose_category=True,
|
||||
)
|
||||
_expect(route == delegated, "Prompt builder role graph route wrapper should delegate to row_role_graph")
|
||||
_expect("raised edge" in route.source_role_graph, "Role graph route did not sanitize bed-edge environment anchor")
|
||||
_expect("bed edge" not in route.source_role_graph.lower(), "Role graph route leaked bed-edge environment anchor")
|
||||
_expect(route.role_graph == route.source_role_graph, "Role graph route changed non-POV role graph text")
|
||||
|
||||
pov_route = row_role_graph.resolve_role_graph_route(
|
||||
rng=random.Random(53),
|
||||
subcategory={"slug": "oral", "name": "Oral"},
|
||||
context=context,
|
||||
item_axis_values={"position": "standing oral", "act": "blowjob"},
|
||||
pov_character_labels=["Man A"],
|
||||
is_pose_category=False,
|
||||
)
|
||||
_expect(pov_route.source_role_graph, "Role graph route lost POV source role graph")
|
||||
_expect(
|
||||
pov_route.role_graph.startswith("First-person POV from Man A;"),
|
||||
"Role graph route did not prepend POV role graph directive",
|
||||
)
|
||||
|
||||
|
||||
def smoke_row_assembly_policy() -> None:
|
||||
context = {
|
||||
"subject": "configured cast",
|
||||
@@ -4687,6 +4742,7 @@ SMOKE_CASES: list[tuple[str, Callable[[], None]]] = [
|
||||
("character_profile_policy", smoke_character_profile_policy),
|
||||
("row_normalization_policy", smoke_row_normalization_policy),
|
||||
("row_rendering_policy", smoke_row_rendering_policy),
|
||||
("row_role_graph_policy", smoke_row_role_graph_policy),
|
||||
("row_assembly_policy", smoke_row_assembly_policy),
|
||||
("formatter_input_policy", smoke_formatter_input_policy),
|
||||
("formatter_cast_policy", smoke_formatter_cast_policy),
|
||||
|
||||
Reference in New Issue
Block a user