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
|
safe formatting, configured-cast descriptor insertion, and POV directive
|
||||||
insertion live in `row_rendering.py`; `prompt_builder.py` keeps public
|
insertion live in `row_rendering.py`; `prompt_builder.py` keeps public
|
||||||
delegate wrappers.
|
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
|
- row expression text cleanup, expression route resolution, expression
|
||||||
intensity weighting, character-slot/cast expression override resolution, and
|
intensity weighting, character-slot/cast expression override resolution, and
|
||||||
per-character expression picking plus action-aware character-expression
|
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
|
policy, position-key detection, category filtering, and item-template/axis
|
||||||
filtering live in `hardcore_position_config.py`.
|
filtering live in `hardcore_position_config.py`.
|
||||||
- hardcore configured-cast role graph generation lives in
|
- hardcore configured-cast role graph generation lives in
|
||||||
`hardcore_role_graphs.py`; `prompt_builder.py` selects item/axis metadata and
|
`hardcore_role_graphs.py`; row generation reaches it through
|
||||||
then asks that module for the source role graph.
|
`row_role_graph.py` after item/axis metadata is selected.
|
||||||
- fallback role graph wording lives in `hardcore_role_fallback.py`, covering
|
- fallback role graph wording lives in `hardcore_role_fallback.py`, covering
|
||||||
solo rows, women-only rows, men-only rows, mixed group fallbacks, and support
|
solo rows, women-only rows, men-only rows, mixed group fallbacks, and support
|
||||||
partner sentences.
|
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_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_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_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_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, 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. |
|
||||||
@@ -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_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_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. |
|
| `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_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_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. |
|
| `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_prompt_axes as row_prompt_axes_policy
|
||||||
from . import row_pools as row_pool_policy
|
from . import row_pools as row_pool_policy
|
||||||
from . import row_rendering as row_rendering_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_route_metadata as row_route_policy
|
||||||
from . import row_subject_route as row_subject_route_policy
|
from . import row_subject_route as row_subject_route_policy
|
||||||
from . import seed_config as seed_policy
|
from . import seed_config as seed_policy
|
||||||
@@ -52,7 +53,6 @@ try:
|
|||||||
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_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 (
|
||||||
compatible_entries as _compatible_entries,
|
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_prompt_axes as row_prompt_axes_policy
|
||||||
import row_pools as row_pool_policy
|
import row_pools as row_pool_policy
|
||||||
import row_rendering as row_rendering_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_route_metadata as row_route_policy
|
||||||
import row_subject_route as row_subject_route_policy
|
import row_subject_route as row_subject_route_policy
|
||||||
import seed_config as seed_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_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_role_graphs import build_hardcore_role_graph
|
|
||||||
|
|
||||||
|
|
||||||
ROOT_DIR = Path(__file__).resolve().parent
|
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]:
|
def _assemble_custom_row(request: row_assembly_policy.CustomRowAssemblyRequest) -> dict[str, Any]:
|
||||||
return row_assembly_policy.assemble_custom_row(request)
|
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 [])
|
pov_character_labels = list(subject_route.get("pov_character_labels") or [])
|
||||||
cast_descriptors = list(subject_route.get("cast_descriptors") or [])
|
cast_descriptors = list(subject_route.get("cast_descriptors") or [])
|
||||||
cast_descriptor_text = str(subject_route.get("cast_descriptor_text") 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)
|
role_graph_route = _role_graph_route(
|
||||||
if is_pose_category:
|
rng=role_rng,
|
||||||
source_role_graph = _sanitize_hardcore_environment_anchors(source_role_graph)
|
subcategory=subcategory,
|
||||||
role_graph = _pov_role_graph_prompt(source_role_graph, pov_character_labels)
|
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_route = _resolve_expression_route(
|
||||||
expression_enabled=expression_enabled,
|
expression_enabled=expression_enabled,
|
||||||
expression_intensity=expression_intensity,
|
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_pools # noqa: E402
|
||||||
import row_prompt_axes # noqa: E402
|
import row_prompt_axes # noqa: E402
|
||||||
import row_rendering # noqa: E402
|
import row_rendering # noqa: E402
|
||||||
|
import row_role_graph # noqa: E402
|
||||||
import row_route_metadata # noqa: E402
|
import row_route_metadata # noqa: E402
|
||||||
import row_subject_route # noqa: E402
|
import row_subject_route # noqa: E402
|
||||||
import server_routes # 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:
|
def smoke_row_assembly_policy() -> None:
|
||||||
context = {
|
context = {
|
||||||
"subject": "configured cast",
|
"subject": "configured cast",
|
||||||
@@ -4687,6 +4742,7 @@ SMOKE_CASES: list[tuple[str, Callable[[], None]]] = [
|
|||||||
("character_profile_policy", smoke_character_profile_policy),
|
("character_profile_policy", smoke_character_profile_policy),
|
||||||
("row_normalization_policy", smoke_row_normalization_policy),
|
("row_normalization_policy", smoke_row_normalization_policy),
|
||||||
("row_rendering_policy", smoke_row_rendering_policy),
|
("row_rendering_policy", smoke_row_rendering_policy),
|
||||||
|
("row_role_graph_policy", smoke_row_role_graph_policy),
|
||||||
("row_assembly_policy", smoke_row_assembly_policy),
|
("row_assembly_policy", smoke_row_assembly_policy),
|
||||||
("formatter_input_policy", smoke_formatter_input_policy),
|
("formatter_input_policy", smoke_formatter_input_policy),
|
||||||
("formatter_cast_policy", smoke_formatter_cast_policy),
|
("formatter_cast_policy", smoke_formatter_cast_policy),
|
||||||
|
|||||||
Reference in New Issue
Block a user