Extract row subject route policy
This commit is contained in:
@@ -155,6 +155,10 @@ Already isolated:
|
|||||||
- row subject-context routing for single, couple, configured-cast, group, and
|
- row subject-context routing for single, couple, configured-cast, group, and
|
||||||
layout subjects lives in `subject_context.py`; it combines appearance policy,
|
layout subjects lives in `subject_context.py`; it combines appearance policy,
|
||||||
cast metadata, and generator subject pools behind one row-facing entry point.
|
cast metadata, and generator subject pools behind one row-facing entry point.
|
||||||
|
- row subject route orchestration, character slot/profile precedence,
|
||||||
|
configured-cast POV labels, visible cast descriptor collection, and
|
||||||
|
descriptor prompt cleanup live in `row_subject_route.py`;
|
||||||
|
`prompt_builder.py` keeps a public delegate wrapper.
|
||||||
- ethnicity/filter choices, advanced filter JSON, ethnicity-list JSON, filter
|
- ethnicity/filter choices, advanced filter JSON, ethnicity-list JSON, filter
|
||||||
parsing, and ethnicity normalization live in `filter_config.py`; character
|
parsing, and ethnicity normalization live in `filter_config.py`; character
|
||||||
routes and builder filters use `prompt_builder.py` delegate wrappers.
|
routes and builder filters use `prompt_builder.py` delegate wrappers.
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ Core helper ownership:
|
|||||||
| `generation_profile_config.py` | Generation profile presets, profile option overrides, trigger policy, expression/pose/clothing config normalization, and profile config parsing. |
|
| `generation_profile_config.py` | Generation profile presets, profile option overrides, trigger policy, expression/pose/clothing config normalization, and profile config parsing. |
|
||||||
| `seed_config.py` | Seed axis salts/aliases, seed mode choices, global/axis lock JSON builders, seed config parsing, row seed math, and deterministic axis RNG construction. |
|
| `seed_config.py` | Seed axis salts/aliases, seed mode choices, global/axis lock JSON builders, seed config parsing, row seed math, and deterministic axis RNG construction. |
|
||||||
| `subject_context.py` | Row subject-context routing for single, couple, configured-cast, group, and layout subjects, combining appearance policy, cast metadata, and generator subject pools. |
|
| `subject_context.py` | Row subject-context routing for single, couple, configured-cast, group, and layout subjects, combining appearance policy, cast metadata, and generator subject pools. |
|
||||||
|
| `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. |
|
| `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_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 intensity weighting, character-slot/cast expression override resolution, per-character expression selection, and action-aware character-expression sanitizing. |
|
||||||
@@ -498,12 +499,12 @@ plain prompt text. When debugging, inspect these fields before editing pools.
|
|||||||
| `camera_config` | Camera nodes/parser | Krea/SDXL/debug | Structured camera settings. |
|
| `camera_config` | Camera nodes/parser | Krea/SDXL/debug | Structured camera settings. |
|
||||||
| `camera_directive` | `_camera_directive` | Krea/Naturalizer/prompt text | Human camera sentence. Suppressed for POV. |
|
| `camera_directive` | `_camera_directive` | Krea/Naturalizer/prompt text | Human camera sentence. Suppressed for POV. |
|
||||||
| `camera_scene_directive` | scene-camera adapter | Krea/Naturalizer/prompt text | Location-aware camera layout sentence. |
|
| `camera_scene_directive` | scene-camera adapter | Krea/Naturalizer/prompt text | Location-aware camera layout sentence. |
|
||||||
| `subject_type`, `subject_phrase` | Subject/context builder | Formatters | Single/couple/group/configured cast route. |
|
| `subject_type`, `subject_phrase` | `row_subject_route.resolve_subject_route` | Formatters | Single/couple/group/configured cast route. |
|
||||||
| `women_count`, `men_count`, `person_count` | Cast route | Pair/formatters/debug | Effective cast counts. |
|
| `women_count`, `men_count`, `person_count` | `row_subject_route.resolve_subject_route` | Pair/formatters/debug | Effective cast counts. |
|
||||||
| `cast_descriptors`, `cast_descriptor_text` | Character/cast route | Krea/SDXL/Naturalizer | Visible cast descriptors. |
|
| `cast_descriptors`, `cast_descriptor_text` | `row_subject_route.resolve_subject_route` | Krea/SDXL/Naturalizer | Visible cast descriptors. |
|
||||||
| `character_cast_slots` | Character slot chain | POV/camera/formatters | Raw configured slots. |
|
| `character_cast_slots` | `row_subject_route.resolve_subject_route` | POV/camera/formatters | Raw configured slots. |
|
||||||
| `character_slot_status`, `character_profile_status` | Character/profile application | Debug | Explains whether slot/profile was applied or skipped. |
|
| `character_slot_status`, `character_profile_status` | `row_subject_route.resolve_subject_route` | Debug | Explains whether slot/profile was applied or skipped. |
|
||||||
| `pov_character_labels` | Character slot presence mode | Krea/prompt/camera | Labels omitted from visible cast and rewritten as first-person POV. |
|
| `pov_character_labels` | `row_subject_route.resolve_subject_route` | Krea/prompt/camera | Labels omitted from visible cast and rewritten as first-person POV. |
|
||||||
| `hardcore_position_config` | Hardcore position/filter nodes | Debug | Active hardcore family/position/action/interaction constraints, including `interaction_only` and `manual_only`. |
|
| `hardcore_position_config` | Hardcore position/filter nodes | Debug | Active hardcore family/position/action/interaction constraints, including `interaction_only` and `manual_only`. |
|
||||||
| `negative_prompt` | Category/pair/default negative route | Formatter output | Base negative text before formatter extras. |
|
| `negative_prompt` | Category/pair/default negative route | Formatter output | Base negative text before formatter extras. |
|
||||||
| `trigger` | Builder input | Formatter/fallback/debug | Active trigger after fallback to default. |
|
| `trigger` | Builder input | Formatter/fallback/debug | Active trigger after fallback to default. |
|
||||||
|
|||||||
+57
-45
@@ -43,6 +43,7 @@ try:
|
|||||||
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_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 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 (
|
||||||
@@ -89,6 +90,7 @@ except ImportError: # Allows local smoke tests with `python -c`.
|
|||||||
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_route_metadata as row_route_policy
|
import row_route_metadata as row_route_policy
|
||||||
|
import row_subject_route as row_subject_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 (
|
||||||
@@ -1960,6 +1962,37 @@ def _subject_context(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _subject_route(
|
||||||
|
*,
|
||||||
|
subject_type: str,
|
||||||
|
seed_config: dict[str, int],
|
||||||
|
seed: int,
|
||||||
|
row_number: int,
|
||||||
|
ethnicity: str,
|
||||||
|
figure: str,
|
||||||
|
no_plus_women: bool,
|
||||||
|
no_black: bool,
|
||||||
|
women_count: int,
|
||||||
|
men_count: int,
|
||||||
|
character_profile: str | dict[str, Any] | None = None,
|
||||||
|
character_cast: str | dict[str, Any] | list[Any] | None = None,
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
return row_subject_route_policy.resolve_subject_route(
|
||||||
|
subject_type=subject_type,
|
||||||
|
seed_config=seed_config,
|
||||||
|
seed=seed,
|
||||||
|
row_number=row_number,
|
||||||
|
ethnicity=ethnicity,
|
||||||
|
figure=figure,
|
||||||
|
no_plus_women=no_plus_women,
|
||||||
|
no_black=no_black,
|
||||||
|
women_count=women_count,
|
||||||
|
men_count=men_count,
|
||||||
|
character_profile=character_profile,
|
||||||
|
character_cast=character_cast,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _scene_pool(
|
def _scene_pool(
|
||||||
category: dict[str, Any],
|
category: dict[str, Any],
|
||||||
subcategory: dict[str, Any],
|
subcategory: dict[str, Any],
|
||||||
@@ -2020,7 +2053,6 @@ def _build_custom_row(
|
|||||||
location_config: str | dict[str, Any] | None = None,
|
location_config: str | dict[str, Any] | None = None,
|
||||||
composition_config: str | dict[str, Any] | None = None,
|
composition_config: str | dict[str, Any] | None = None,
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
person_rng = _axis_rng(seed_config, "person", seed, row_number)
|
|
||||||
scene_rng = _axis_rng(seed_config, "scene", seed, row_number)
|
scene_rng = _axis_rng(seed_config, "scene", seed, row_number)
|
||||||
pose_rng = _axis_rng(seed_config, "pose", seed, row_number)
|
pose_rng = _axis_rng(seed_config, "pose", seed, row_number)
|
||||||
role_rng = _axis_rng(seed_config, "role", seed, row_number)
|
role_rng = _axis_rng(seed_config, "role", seed, row_number)
|
||||||
@@ -2054,41 +2086,35 @@ def _build_custom_row(
|
|||||||
item_formatter_hints = dict(category_route.get("formatter_hints") or {})
|
item_formatter_hints = dict(category_route.get("formatter_hints") or {})
|
||||||
is_pose_category = bool(category_route.get("is_pose_category"))
|
is_pose_category = bool(category_route.get("is_pose_category"))
|
||||||
subject_type = str(_merged_field(category, subcategory, item, "subject_type", "single_any"))
|
subject_type = str(_merged_field(category, subcategory, item, "subject_type", "single_any"))
|
||||||
context = _subject_context(person_rng, subject_type, ethnicity, figure, no_plus_women, no_black, women_count, men_count)
|
subject_route = _subject_route(
|
||||||
character_slots = _parse_character_cast(character_cast)
|
subject_type=subject_type,
|
||||||
character_slot_map = _character_slot_label_map(character_slots)
|
seed_config=seed_config,
|
||||||
applied_slot: dict[str, Any] = {}
|
seed=seed,
|
||||||
slot_status = "none"
|
row_number=row_number,
|
||||||
if context.get("subject_type") in ("woman", "man"):
|
ethnicity=ethnicity,
|
||||||
slot_label = "Woman A" if context["subject_type"] == "woman" else "Man A"
|
figure=figure,
|
||||||
if slot_label in character_slot_map:
|
no_plus_women=no_plus_women,
|
||||||
context, applied_slot = _character_context_for_label(
|
no_black=no_black,
|
||||||
slot_label,
|
women_count=women_count,
|
||||||
character_slot_map,
|
men_count=men_count,
|
||||||
person_rng,
|
character_profile=character_profile,
|
||||||
ethnicity,
|
character_cast=character_cast,
|
||||||
figure,
|
|
||||||
no_plus_women,
|
|
||||||
no_black,
|
|
||||||
)
|
|
||||||
slot_status = f"applied:{slot_label}"
|
|
||||||
applied_profile, profile_status = {}, "skipped_character_slot"
|
|
||||||
else:
|
|
||||||
context, applied_profile, profile_status = _apply_character_profile_to_context(context, character_profile)
|
|
||||||
else:
|
|
||||||
context, applied_profile, profile_status = _apply_character_profile_to_context(context, character_profile)
|
|
||||||
subject_type = context["subject_type"]
|
|
||||||
pov_character_labels = (
|
|
||||||
_pov_character_labels(character_slot_map, men_count)
|
|
||||||
if subject_type == "configured_cast"
|
|
||||||
else []
|
|
||||||
)
|
)
|
||||||
|
context = dict(subject_route["context"])
|
||||||
|
subject_type = str(subject_route.get("subject_type") or context.get("subject_type") or subject_type)
|
||||||
|
character_slots = list(subject_route.get("character_slots") or [])
|
||||||
|
character_slot_map = dict(subject_route.get("character_slot_map") or {})
|
||||||
|
applied_slot = dict(subject_route.get("applied_slot") or {})
|
||||||
|
slot_status = str(subject_route.get("character_slot_status") or "none")
|
||||||
|
applied_profile = dict(subject_route.get("applied_profile") or {})
|
||||||
|
profile_status = str(subject_route.get("character_profile_status") or "none")
|
||||||
|
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)
|
source_role_graph = build_hardcore_role_graph(role_rng, subcategory, context, item_axis_values, pov_character_labels)
|
||||||
if is_pose_category:
|
if is_pose_category:
|
||||||
source_role_graph = _sanitize_hardcore_environment_anchors(source_role_graph)
|
source_role_graph = _sanitize_hardcore_environment_anchors(source_role_graph)
|
||||||
role_graph = _pov_role_graph_prompt(source_role_graph, pov_character_labels)
|
role_graph = _pov_role_graph_prompt(source_role_graph, pov_character_labels)
|
||||||
cast_descriptors: list[str] = []
|
|
||||||
cast_descriptor_text = ""
|
|
||||||
expression_intensity_source = expression_intensity_source or "input"
|
expression_intensity_source = expression_intensity_source or "input"
|
||||||
expression_disabled = not bool(expression_enabled)
|
expression_disabled = not bool(expression_enabled)
|
||||||
if expression_disabled:
|
if expression_disabled:
|
||||||
@@ -2113,20 +2139,6 @@ def _build_custom_row(
|
|||||||
)
|
)
|
||||||
if expression_intensity is None:
|
if expression_intensity is None:
|
||||||
expression_disabled = True
|
expression_disabled = True
|
||||||
if subject_type == "configured_cast" and character_slots:
|
|
||||||
cast_descriptors, _descriptor_slots = _cast_descriptor_entries(
|
|
||||||
seed_config,
|
|
||||||
seed,
|
|
||||||
row_number,
|
|
||||||
ethnicity,
|
|
||||||
figure,
|
|
||||||
no_plus_women,
|
|
||||||
no_black,
|
|
||||||
women_count,
|
|
||||||
men_count,
|
|
||||||
character_slots,
|
|
||||||
)
|
|
||||||
cast_descriptor_text = _insta_of_prompt_cast_descriptors("; ".join(cast_descriptors))
|
|
||||||
|
|
||||||
scene_slug, scene = _choose_pair(
|
scene_slug, scene = _choose_pair(
|
||||||
scene_rng,
|
scene_rng,
|
||||||
|
|||||||
@@ -0,0 +1,120 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
try:
|
||||||
|
from . import cast_context as cast_context_policy
|
||||||
|
from . import character_appearance as character_appearance_policy
|
||||||
|
from . import character_profile as character_profile_policy
|
||||||
|
from . import character_slot as character_slot_policy
|
||||||
|
from . import pair_cast
|
||||||
|
from . import pov_policy
|
||||||
|
from . import seed_config as seed_policy
|
||||||
|
from . import subject_context as subject_context_policy
|
||||||
|
except ImportError: # Allows local smoke tests from the repository root.
|
||||||
|
import cast_context as cast_context_policy
|
||||||
|
import character_appearance as character_appearance_policy
|
||||||
|
import character_profile as character_profile_policy
|
||||||
|
import character_slot as character_slot_policy
|
||||||
|
import pair_cast
|
||||||
|
import pov_policy
|
||||||
|
import seed_config as seed_policy
|
||||||
|
import subject_context as subject_context_policy
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_subject_route(
|
||||||
|
*,
|
||||||
|
subject_type: str,
|
||||||
|
seed_config: dict[str, int],
|
||||||
|
seed: int,
|
||||||
|
row_number: int,
|
||||||
|
ethnicity: str,
|
||||||
|
figure: str,
|
||||||
|
no_plus_women: bool,
|
||||||
|
no_black: bool,
|
||||||
|
women_count: int,
|
||||||
|
men_count: int,
|
||||||
|
character_profile: str | dict[str, Any] | None = None,
|
||||||
|
character_cast: str | dict[str, Any] | list[Any] | None = None,
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
person_rng = seed_policy.axis_rng(seed_config, "person", seed, row_number)
|
||||||
|
context = subject_context_policy.subject_context(
|
||||||
|
person_rng,
|
||||||
|
subject_type,
|
||||||
|
ethnicity,
|
||||||
|
figure,
|
||||||
|
no_plus_women,
|
||||||
|
no_black,
|
||||||
|
women_count,
|
||||||
|
men_count,
|
||||||
|
)
|
||||||
|
character_slots = character_slot_policy.parse_character_cast(character_cast)
|
||||||
|
character_slot_map = cast_context_policy.character_slot_label_map(character_slots)
|
||||||
|
applied_slot: dict[str, Any] = {}
|
||||||
|
slot_status = "none"
|
||||||
|
if context.get("subject_type") in ("woman", "man"):
|
||||||
|
slot_label = "Woman A" if context["subject_type"] == "woman" else "Man A"
|
||||||
|
if slot_label in character_slot_map:
|
||||||
|
context, applied_slot = character_appearance_policy.character_context_for_label(
|
||||||
|
slot_label,
|
||||||
|
character_slot_map,
|
||||||
|
person_rng,
|
||||||
|
ethnicity,
|
||||||
|
figure,
|
||||||
|
no_plus_women,
|
||||||
|
no_black,
|
||||||
|
)
|
||||||
|
slot_status = f"applied:{slot_label}"
|
||||||
|
applied_profile, profile_status = {}, "skipped_character_slot"
|
||||||
|
else:
|
||||||
|
context, applied_profile, profile_status = character_profile_policy.apply_character_profile_to_context(
|
||||||
|
context,
|
||||||
|
character_profile,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
context, applied_profile, profile_status = character_profile_policy.apply_character_profile_to_context(
|
||||||
|
context,
|
||||||
|
character_profile,
|
||||||
|
)
|
||||||
|
|
||||||
|
resolved_subject_type = str(context.get("subject_type") or subject_type)
|
||||||
|
pov_character_labels = (
|
||||||
|
pov_policy.pov_character_labels(character_slot_map, men_count)
|
||||||
|
if resolved_subject_type == "configured_cast"
|
||||||
|
else []
|
||||||
|
)
|
||||||
|
cast_descriptors: list[str] = []
|
||||||
|
cast_descriptor_text = ""
|
||||||
|
if resolved_subject_type == "configured_cast" and character_slots:
|
||||||
|
cast_descriptors, _descriptor_slots = pair_cast.cast_descriptor_entries_from_slots(
|
||||||
|
seed_config=seed_config,
|
||||||
|
seed=seed,
|
||||||
|
row_number=row_number,
|
||||||
|
ethnicity=ethnicity,
|
||||||
|
figure=figure,
|
||||||
|
no_plus_women=no_plus_women,
|
||||||
|
no_black=no_black,
|
||||||
|
women_count=women_count,
|
||||||
|
men_count=men_count,
|
||||||
|
character_slots=character_slots,
|
||||||
|
character_slot_map=character_slot_map,
|
||||||
|
primary_descriptor="",
|
||||||
|
axis_rng=seed_policy.axis_rng,
|
||||||
|
character_context_for_label=character_appearance_policy.character_context_for_label,
|
||||||
|
slot_is_pov=pov_policy.slot_is_pov,
|
||||||
|
)
|
||||||
|
cast_descriptor_text = pair_cast.prompt_cast_descriptors("; ".join(cast_descriptors))
|
||||||
|
|
||||||
|
return {
|
||||||
|
"context": context,
|
||||||
|
"subject_type": resolved_subject_type,
|
||||||
|
"character_slots": character_slots,
|
||||||
|
"character_slot_map": character_slot_map,
|
||||||
|
"applied_slot": applied_slot or {},
|
||||||
|
"character_slot_status": slot_status,
|
||||||
|
"applied_profile": applied_profile or {},
|
||||||
|
"character_profile_status": profile_status,
|
||||||
|
"pov_character_labels": pov_character_labels,
|
||||||
|
"cast_descriptors": cast_descriptors,
|
||||||
|
"cast_descriptor_text": cast_descriptor_text,
|
||||||
|
}
|
||||||
@@ -60,6 +60,7 @@ import row_location # noqa: E402
|
|||||||
import row_pools # noqa: E402
|
import row_pools # noqa: E402
|
||||||
import row_rendering # noqa: E402
|
import row_rendering # noqa: E402
|
||||||
import row_route_metadata # noqa: E402
|
import row_route_metadata # noqa: E402
|
||||||
|
import row_subject_route # 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
|
||||||
@@ -1061,6 +1062,84 @@ def smoke_category_cast_config_policy() -> None:
|
|||||||
_expect((empty_cast.get("women_count"), empty_cast.get("men_count")) == (1, 0), "Empty custom cast was not corrected")
|
_expect((empty_cast.get("women_count"), empty_cast.get("men_count")) == (1, 0), "Empty custom cast was not corrected")
|
||||||
|
|
||||||
|
|
||||||
|
def smoke_row_subject_route_policy() -> None:
|
||||||
|
seed_cfg = seed_config.parse_seed_config({})
|
||||||
|
slot_cast = pb.build_character_slot_json(
|
||||||
|
subject_type="woman",
|
||||||
|
label="A",
|
||||||
|
age="32-year-old adult",
|
||||||
|
ethnicity="western_european",
|
||||||
|
figure="balanced",
|
||||||
|
body="slim",
|
||||||
|
hair="short silver bob",
|
||||||
|
eyes="gray eyes",
|
||||||
|
descriptor_detail="full",
|
||||||
|
)["character_cast"]
|
||||||
|
profile = {
|
||||||
|
"profile_type": "character",
|
||||||
|
"subject_type": "woman",
|
||||||
|
"age": "45-year-old adult",
|
||||||
|
"body": "average",
|
||||||
|
"body_phrase": "average figure",
|
||||||
|
"skin": "profile skin",
|
||||||
|
"hair": "profile hair",
|
||||||
|
"eyes": "profile eyes",
|
||||||
|
}
|
||||||
|
route = row_subject_route.resolve_subject_route(
|
||||||
|
subject_type="woman",
|
||||||
|
seed_config=seed_cfg,
|
||||||
|
seed=501,
|
||||||
|
row_number=1,
|
||||||
|
ethnicity="any",
|
||||||
|
figure="balanced",
|
||||||
|
no_plus_women=False,
|
||||||
|
no_black=False,
|
||||||
|
women_count=1,
|
||||||
|
men_count=0,
|
||||||
|
character_profile=profile,
|
||||||
|
character_cast=slot_cast,
|
||||||
|
)
|
||||||
|
delegated = pb._subject_route(
|
||||||
|
subject_type="woman",
|
||||||
|
seed_config=seed_cfg,
|
||||||
|
seed=501,
|
||||||
|
row_number=1,
|
||||||
|
ethnicity="any",
|
||||||
|
figure="balanced",
|
||||||
|
no_plus_women=False,
|
||||||
|
no_black=False,
|
||||||
|
women_count=1,
|
||||||
|
men_count=0,
|
||||||
|
character_profile=profile,
|
||||||
|
character_cast=slot_cast,
|
||||||
|
)
|
||||||
|
_expect(delegated == route, "Prompt builder subject route should delegate to row_subject_route")
|
||||||
|
_expect(route["subject_type"] == "woman", "Subject route changed single-woman subject type")
|
||||||
|
_expect(route["character_slot_status"] == "applied:Woman A", "Subject route did not apply matching Woman A slot")
|
||||||
|
_expect(route["character_profile_status"] == "skipped_character_slot", "Subject route should skip profile when slot applies")
|
||||||
|
_expect(route["context"].get("age") == "32-year-old adult", "Subject route lost slot age override")
|
||||||
|
_expect(route["context"].get("hair") == "short silver bob", "Subject route lost slot hair override")
|
||||||
|
_expect(route["applied_profile"] == {}, "Subject route should not apply profile over matching slot")
|
||||||
|
|
||||||
|
cast_route = row_subject_route.resolve_subject_route(
|
||||||
|
subject_type="configured_cast",
|
||||||
|
seed_config=seed_cfg,
|
||||||
|
seed=502,
|
||||||
|
row_number=1,
|
||||||
|
ethnicity="western_european",
|
||||||
|
figure="balanced",
|
||||||
|
no_plus_women=False,
|
||||||
|
no_black=False,
|
||||||
|
women_count=1,
|
||||||
|
men_count=1,
|
||||||
|
character_cast=_character_cast(pov_man=True),
|
||||||
|
)
|
||||||
|
_expect(cast_route["subject_type"] == "configured_cast", "Configured-cast subject route changed subject type")
|
||||||
|
_expect(cast_route["pov_character_labels"] == ["Man A"], "Subject route lost configured-cast POV man label")
|
||||||
|
_expect("Woman A:" in cast_route["cast_descriptor_text"], "Subject route lost visible woman descriptor")
|
||||||
|
_expect("Man A:" not in cast_route["cast_descriptor_text"], "Subject route should not describe POV man as visible cast")
|
||||||
|
|
||||||
|
|
||||||
def smoke_generation_profile_config_policy() -> None:
|
def smoke_generation_profile_config_policy() -> None:
|
||||||
_expect(
|
_expect(
|
||||||
pb.GENERATION_PROFILE_PRESETS is generation_profile_config.GENERATION_PROFILE_PRESETS,
|
pb.GENERATION_PROFILE_PRESETS is generation_profile_config.GENERATION_PROFILE_PRESETS,
|
||||||
@@ -4313,6 +4392,7 @@ SMOKE_CASES: list[tuple[str, Callable[[], None]]] = [
|
|||||||
("row_generation_policy", smoke_row_generation_policy),
|
("row_generation_policy", smoke_row_generation_policy),
|
||||||
("category_extensions_policy", smoke_category_extensions_policy),
|
("category_extensions_policy", smoke_category_extensions_policy),
|
||||||
("category_cast_config_policy", smoke_category_cast_config_policy),
|
("category_cast_config_policy", smoke_category_cast_config_policy),
|
||||||
|
("row_subject_route_policy", smoke_row_subject_route_policy),
|
||||||
("generation_profile_config_policy", smoke_generation_profile_config_policy),
|
("generation_profile_config_policy", smoke_generation_profile_config_policy),
|
||||||
("filter_config_policy", smoke_filter_config_policy),
|
("filter_config_policy", smoke_filter_config_policy),
|
||||||
("character_config_policy", smoke_character_config_policy),
|
("character_config_policy", smoke_character_config_policy),
|
||||||
|
|||||||
Reference in New Issue
Block a user