Files
ComfyUI-Ethanfel-Prompt-Bui…/tools/prompt_smoke.py
T
2026-06-27 11:37:02 +02:00

5349 lines
246 KiB
Python

#!/usr/bin/env python3
"""Smoke-test core prompt routes without importing ComfyUI.
The checks here are intentionally lightweight invariants, not golden prompt
snapshots. They prove that representative rows still carry structured metadata
and that the Krea2, SDXL, and caption formatter paths consume metadata instead
of silently falling back to raw text parsing.
"""
from __future__ import annotations
import argparse
import json
import random
import re
import sys
import tempfile
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, Callable
ROOT = Path(__file__).resolve().parents[1]
if str(ROOT) not in sys.path:
sys.path.insert(0, str(ROOT))
import caption_naturalizer # noqa: E402
import caption_metadata_routes # noqa: E402
import caption_policy # noqa: E402
import cast_context # noqa: E402
import category_extensions # noqa: E402
import category_template_metadata # noqa: E402
import character_appearance # noqa: E402
import character_config # noqa: E402
import character_profile # noqa: E402
import character_slot # noqa: E402
import category_cast_config # noqa: E402
import category_library # noqa: E402
import filter_config # noqa: E402
import formatter_input # noqa: E402
import hardcore_position_config # noqa: E402
import __init__ as sxcp_nodes # noqa: E402
import generation_profile_config # noqa: E402
import index_switch_policy # noqa: E402
import node_tooltips # noqa: E402
import krea_cast # noqa: E402
import krea_configured_cast_formatter # noqa: E402
import krea_formatter # noqa: E402
import krea_normal_formatter # noqa: E402
import krea_pair_formatter # noqa: E402
import location_config # noqa: E402
import loop_nodes # noqa: E402
import pair_builder # noqa: E402
import pair_camera # noqa: E402
import pair_cast # noqa: E402
import pair_clothing # noqa: E402
import pair_rows # noqa: E402
import prompt_builder as pb # noqa: E402
import pov_policy # noqa: E402
import row_normalization # noqa: E402
import row_assembly # noqa: E402
import route_metadata # noqa: E402
import row_camera # noqa: E402
import row_category_route # noqa: E402
import row_expression # noqa: E402
import row_generation # noqa: E402
import row_item # noqa: E402
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
import sdxl_formatter # noqa: E402
import sdxl_presets # noqa: E402
import sdxl_tag_routes # noqa: E402
import seed_config # noqa: E402
import krea_pov # noqa: E402
import subject_context # noqa: E402
Trigger = "sxcppnl7"
SdxlTrigger = "mythp0rt"
@dataclass
class SmokeReport:
passed: list[str] = field(default_factory=list)
failed: list[str] = field(default_factory=list)
def ok(self, name: str) -> None:
self.passed.append(name)
print(f"PASS {name}")
def fail(self, name: str, message: str) -> None:
detail = f"{name}: {message}"
self.failed.append(detail)
print(f"FAIL {detail}")
def _clean_key(value: str) -> str:
return re.sub(r"[^a-z0-9]+", " ", str(value or "").lower()).strip()
def _json(value: Any) -> str:
return json.dumps(value, ensure_ascii=True, sort_keys=True)
def _expect(condition: bool, message: str) -> None:
if not condition:
raise AssertionError(message)
def _expect_text(name: str, value: Any, min_len: int = 8) -> str:
text = str(value or "").strip()
_expect(len(text) >= min_len, f"{name} is empty or too short")
_expect("None" not in text, f"{name} leaked None")
_expect(" " not in text, f"{name} has repeated spaces")
_expect(" ," not in text and " ." not in text, f"{name} has bad punctuation spacing")
return text
def _expect_no_duplicate_comma_items(name: str, value: Any) -> None:
items = [_clean_key(part) for part in str(value or "").split(",")]
items = [part for part in items if part]
duplicates = sorted({part for part in items if items.count(part) > 1})
_expect(not duplicates, f"{name} has duplicate comma items: {duplicates[:5]}")
def _trigger_count(text: str, trigger: str) -> int:
return len(re.findall(rf"(?<![a-z0-9_]){re.escape(trigger)}(?![a-z0-9_])", text, flags=re.IGNORECASE))
def _expect_trigger_once(name: str, value: Any, trigger: str) -> None:
text = str(value or "")
count = _trigger_count(text, trigger)
_expect(count == 1, f"{name} should contain trigger {trigger!r} exactly once, got {count}")
def _expect_row_base(row: dict[str, Any], name: str) -> None:
_expect(isinstance(row, dict), f"{name} did not return a metadata row")
_expect_text(f"{name}.prompt", row.get("prompt"), 20)
_expect_text(f"{name}.negative_prompt", row.get("negative_prompt"), 8)
_expect_no_duplicate_comma_items(f"{name}.negative_prompt", row.get("negative_prompt"))
_expect(json.loads(_json(row)) == row, f"{name} is not JSON-stable")
def _expect_custom_row(row: dict[str, Any], name: str) -> None:
_expect_row_base(row, name)
_expect(row.get("source") == "json_category", f"{name}.source should be json_category")
_expect_text(f"{name}.item", row.get("item"), 8)
_expect_text(f"{name}.scene_text", row.get("scene_text"), 8)
_expect_text(f"{name}.composition", row.get("composition"), 8)
_expect_text(f"{name}.role_graph", row.get("source_role_graph") or row.get("role_graph"), 8)
_expect(isinstance(row.get("item_axis_values"), dict), f"{name}.item_axis_values missing")
_expect(isinstance(row.get("formatter_hints"), dict), f"{name}.formatter_hints missing")
def _expect_formatter_outputs(row: dict[str, Any], name: str, *, target: str = "auto") -> None:
metadata = _json(row)
krea = krea_formatter.format_krea2_prompt("", metadata_json=metadata, target=target)
_expect("metadata" in krea.get("method", ""), f"{name}.krea did not use metadata: {krea.get('method')}")
_expect_text(f"{name}.krea_prompt", krea.get("krea_prompt"), 20)
_expect_no_duplicate_comma_items(f"{name}.krea_negative", krea.get("negative_prompt"))
sdxl = sdxl_formatter.format_sdxl_prompt(
"",
metadata_json=metadata,
target=target,
trigger=SdxlTrigger,
prepend_trigger=True,
)
_expect("metadata" in sdxl.get("method", ""), f"{name}.sdxl did not use metadata: {sdxl.get('method')}")
_expect_text(f"{name}.sdxl_prompt", sdxl.get("sdxl_prompt"), 20)
_expect_trigger_once(f"{name}.sdxl_prompt", sdxl.get("sdxl_prompt"), SdxlTrigger)
_expect_no_duplicate_comma_items(f"{name}.sdxl_negative", sdxl.get("negative_prompt"))
caption, method = caption_naturalizer.naturalize_caption(
"",
metadata_json=metadata,
trigger=Trigger,
include_trigger=True,
)
_expect("metadata" in method, f"{name}.caption did not use metadata: {method}")
_expect_text(f"{name}.caption", caption, 20)
_expect_trigger_once(f"{name}.caption", caption, Trigger)
def _expect_krea_normal_route_parity(row: dict[str, Any], name: str, method: str) -> None:
typed_route = krea_normal_formatter.format_normal_row_result(
krea_formatter._krea_normal_row_request_from_row(row, "balanced", "preserve"),
krea_formatter._krea_normal_row_dependencies(),
)
legacy_route = krea_formatter._normal_row_to_krea(row, "balanced", "preserve")
_expect(
typed_route.as_tuple() == legacy_route,
f"{name} typed Krea normal formatter route should match legacy wrapper output",
)
_expect(typed_route.method == method, f"{name} typed Krea normal formatter method changed")
_expect_text(f"{name}.typed_krea_prompt", typed_route.prompt, 20)
def _character_cast(*, pov_man: bool = False) -> str:
cast = pb.build_character_slot_json(
subject_type="woman",
label="A",
age="25-year-old adult",
ethnicity="western_european",
figure="balanced",
body="slim",
descriptor_detail="full",
expression_intensity=0.65,
softcore_expression_intensity=0.45,
hardcore_expression_intensity=0.85,
)["character_cast"]
return pb.build_character_slot_json(
subject_type="man",
label="A",
age="40-year-old adult",
ethnicity="western_european",
figure="balanced",
body="average",
descriptor_detail="compact",
expression_intensity=0.55,
softcore_expression_intensity=0.35,
hardcore_expression_intensity=0.75,
presence_mode="pov" if pov_man else "visible",
character_cast=cast,
)["character_cast"]
def _character_cast_two_men(*, pov_first_man: bool = False) -> str:
cast = _character_cast(pov_man=pov_first_man)
return pb.build_character_slot_json(
subject_type="man",
label="B",
age="41-year-old adult",
ethnicity="western_european",
figure="balanced",
body="average",
descriptor_detail="compact",
expression_intensity=0.55,
softcore_expression_intensity=0.35,
hardcore_expression_intensity=0.75,
character_cast=cast,
)["character_cast"]
def _character_cast_subjects(subjects: list[str] | tuple[str, ...]) -> str:
cast = ""
counts = {"woman": 0, "man": 0}
for subject in subjects:
subject = str(subject)
counts[subject] += 1
label = chr(ord("A") + counts[subject] - 1)
cast = pb.build_character_slot_json(
subject_type=subject,
label=label,
age="25-year-old adult" if subject == "woman" else "40-year-old adult",
ethnicity="western_european",
figure="balanced",
body="slim" if subject == "woman" else "average",
descriptor_detail="compact",
expression_intensity=0.55,
softcore_expression_intensity=0.35,
hardcore_expression_intensity=0.75,
character_cast=cast,
)["character_cast"]
return cast
def _action_filter(focus: str, hardcore_position_config: str | dict[str, Any] | None = "") -> str:
kwargs = {
"allow_toys": False,
"allow_double": False,
"allow_penetration": focus in ("penetration_only", "keep_pool"),
"allow_foreplay": focus in ("foreplay_only", "keep_pool"),
"allow_interaction": focus in ("interaction_only", "keep_pool"),
"allow_manual": focus in ("manual_only", "keep_pool"),
"allow_oral": focus in ("oral_only", "keep_pool"),
"allow_outercourse": focus in ("outercourse_only", "keep_pool"),
"allow_anal": focus in ("anal_only", "keep_pool"),
"allow_climax": focus in ("climax_only", "keep_pool"),
}
return pb.build_hardcore_action_filter_json(
hardcore_position_config=hardcore_position_config,
focus=focus,
**kwargs,
)
def _position_filter(focus: str, family: str, positions: list[str] | tuple[str, ...] | str) -> str:
position_config = pb.build_hardcore_position_pool_json(
combine_mode="replace",
family=family,
selected_positions=positions,
)
return _action_filter(focus, position_config)
def _anal_double_filter(positions: list[str] | tuple[str, ...] | str) -> str:
position_config = pb.build_hardcore_position_pool_json(
combine_mode="replace",
family="anal",
selected_positions=positions,
)
return pb.build_hardcore_action_filter_json(
hardcore_position_config=position_config,
focus="anal_only",
allow_toys=True,
allow_double=True,
allow_penetration=True,
allow_foreplay=False,
allow_interaction=False,
allow_manual=False,
allow_oral=False,
allow_outercourse=False,
allow_anal=True,
allow_climax=False,
)
def _coworking_location_config() -> str:
return pb.build_location_pool_json(
enabled=True,
combine_mode="replace",
preset="custom_only",
custom_locations=(
"coworking_smoke: coworking lounge with tall windows, warm desks, "
"laptop tables, glass partition seams, repeated desk rows, plants, "
"and soft shared-office depth"
),
)
def _classical_library_theme_configs() -> tuple[str, str]:
location_config, composition_config, _summary = pb.build_thematic_location_json(
enabled=True,
combine_mode="replace",
theme="classical_library",
)
return location_config, composition_config
def _orbit_camera(
*,
horizontal_angle: int,
vertical_angle: int,
zoom: float,
subject_focus: str = "auto",
camera_detail: str = "compact",
) -> str:
return pb.build_camera_orbit_config_json(
enabled=True,
camera_mode="standard",
horizontal_angle=horizontal_angle,
vertical_angle=vertical_angle,
zoom=zoom,
framing="from_zoom",
subject_focus=subject_focus,
lens="auto",
orientation="auto",
phone_visibility="auto",
priority="strong",
camera_detail=camera_detail,
include_degrees=True,
)
def _prompt_row(
*,
name: str,
category: str,
subcategory: str,
seed: int,
character_cast: str = "",
women_count: int = 1,
men_count: int = 1,
hardcore_position_config: str = "",
camera_config: str | dict[str, Any] | None = "",
location_config: str | dict[str, Any] | None = "",
composition_config: str | dict[str, Any] | None = "",
) -> dict[str, Any]:
row = pb.build_prompt(
category=category,
subcategory=subcategory,
row_number=1,
start_index=1,
seed=seed,
clothing="random",
ethnicity="any",
poses="random",
backside_bias=0.35,
figure="random",
no_plus_women=False,
no_black=False,
minimal_clothing_ratio=0.5,
standard_pose_ratio=0.5,
trigger=Trigger,
prepend_trigger_to_prompt=True,
extra_positive="",
extra_negative="",
character_cast=character_cast,
women_count=women_count,
men_count=men_count,
expression_enabled=True,
expression_intensity=0.6,
hardcore_position_config=hardcore_position_config,
camera_config=camera_config,
location_config=location_config,
composition_config=composition_config,
)
_expect_row_base(row, name)
return row
def _fixture_hardcore_row(**overrides: Any) -> dict[str, Any]:
row: dict[str, Any] = {
"source": "json_category",
"prompt": "Fixture explicit adult prompt for metadata route.",
"caption": "fixture caption",
"negative_prompt": "low quality, bad anatomy",
"main_category": "Hardcore sexual poses",
"subcategory": "Penetrative sex",
"category_slug": "hardcore_sexual_poses",
"subcategory_slug": "penetrative_sex",
"subject_type": "configured_cast",
"subject_phrase": "1 adult woman and 1 adult man",
"cast_summary": "1 woman, 1 man",
"cast_descriptor_text": (
"Woman A: 25-year-old adult woman, slim figure, fair skin, blonde hair, blue eyes; "
"Man A: 40-year-old adult man, average figure, tan skin, dark hair"
),
"cast_descriptors": [
"Woman A: 25-year-old adult woman, slim figure, fair skin, blonde hair, blue eyes",
"Man A: 40-year-old adult man, average figure, tan skin, dark hair",
],
"women_count": 1,
"men_count": 1,
"person_count": 2,
"item": (
"missionary position while full-body penetrative sex, hands gripping the ass, "
"mouth close to the ear, and explicit genital contact visible"
),
"custom_item": "Penetrative sex",
"item_label": "Sexual pose",
"item_axis_values": {
"position": "missionary position",
"penetration_act": "full-body penetrative sex",
"mouth_detail": "mouth close to the ear",
},
"item_template_metadata": {},
"formatter_hints": {},
"scene_text": "private studio room with warm light",
"scene_kind": "explicit adult sex scene",
"pose": "configured explicit pose",
"composition": "front-facing full-body frame",
"source_composition": "front-facing full-body frame",
"role_graph": (
"Woman A lies on her back with legs open around Man A's hips while Man A is above her between her thighs; "
"Man A's hips press close and Man A's penis thrusts into her pussy."
),
"source_role_graph": (
"Woman A lies on her back with legs open around Man A's hips while Man A is above her between her thighs; "
"Man A's hips press close and Man A's penis thrusts into her pussy."
),
"expression": "focused adult expression",
"action_family": "penetration",
"position_family": "penetrative",
"position_key": "missionary",
"position_keys": ["missionary"],
}
row.update(overrides)
return row
def smoke_builtin_single() -> None:
row = _prompt_row(name="builtin_single_woman", category="woman", subcategory="random", seed=1001, men_count=0)
_expect(row.get("source") == "built_in_generator", "builtin row should come from built-in generator")
_expect_trigger_once("builtin_single_woman.prompt", row.get("prompt"), Trigger)
_expect_formatter_outputs(row, "builtin_single_woman", target="single")
def smoke_camera_scene_single() -> None:
row = _prompt_row(
name="camera_scene_single",
category="woman",
subcategory="random",
seed=1051,
men_count=0,
camera_config=_orbit_camera(
horizontal_angle=45,
vertical_angle=-30,
zoom=5.0,
subject_focus="environment",
),
location_config=_coworking_location_config(),
)
scene_directive = _expect_text("camera_scene_single.camera_scene_directive", row.get("camera_scene_directive"), 40)
camera_directive = _expect_text("camera_scene_single.camera_directive", row.get("camera_directive"), 20)
_expect("Coworking camera layout" in scene_directive, "single camera-scene adapter did not identify coworking layout")
_expect("front-right quarter view" in scene_directive, "single camera scene missed orbit direction")
_expect("low-angle shot" in scene_directive, "single camera scene missed orbit elevation")
_expect("45-degree front-right quarter view" in camera_directive, "single camera directive missed custom orbit prompt")
krea = krea_formatter.format_krea2_prompt("", metadata_json=_json(row), target="single")
prompt = krea.get("krea_prompt") or ""
_expect("Coworking camera layout" in prompt, "Krea single prompt lost camera-scene directive")
_expect("45-degree front-right quarter view" in prompt, "Krea single prompt lost camera directive")
_expect_formatter_outputs(row, "camera_scene_single", target="single")
def smoke_row_camera_policy() -> None:
row = {
"prompt": "A generated adult prompt. Composition: vertical office-lobby walking composition. Avoid: low quality.",
"caption": "sxcppnl7, generated adult prompt, office-lobby walking composition, illustration",
"scene_text": "coworking lounge with tall windows, warm desks, and a polished outfit-check angle",
"composition": "office-lobby walking composition",
"subject_type": "configured_cast",
"women_count": 1,
"men_count": 1,
"pov_character_labels": ["Man A"],
}
updated = row_camera.apply_camera_config(
row,
_orbit_camera(horizontal_angle=45, vertical_angle=0, zoom=5.5),
compact_labels=pb.CAMERA_COMPACT_LABELS,
)
_expect(updated.get("camera_directive") == "", "POV row camera policy should suppress normal camera directive")
scene_directive = _expect_text("row_camera_policy.camera_scene_directive", updated.get("camera_scene_directive"), 40)
_expect("Coworking camera layout from POV" in scene_directive, "row camera policy missed POV coworking layout")
_expect("first-person spatial geometry" in scene_directive, "row camera policy lost POV geometry instruction")
_expect("Camera:" not in updated.get("prompt", ""), "row camera policy should not add normal Camera label")
_expect("45-degree front-right quarter view" not in updated.get("caption", ""), "POV row camera policy should not append camera caption")
_expect(
"coworking lounge frame with the couple near a desk edge" in updated.get("composition", ""),
"row camera policy did not adapt coworking composition for couple rows",
)
def smoke_config_route_location_theme() -> None:
location_config, composition_config = _classical_library_theme_configs()
row = pb.build_prompt_from_configs(
row_number=1,
start_index=1,
seed=3301,
category_config=pb.build_category_config_json("hardcore_pose", "Foreplay and teasing"),
cast_config=pb.build_cast_config_json("mixed_couple"),
generation_profile=pb.build_generation_profile_json(
profile="hardcore_intense",
trigger_policy="prepend_trigger",
),
filter_config=pb.build_ethnicity_list_json(
include_french_european=True,
strict_excludes=True,
)["filter_config"],
seed_config=pb.build_seed_lock_config_json(
base_seed=3301,
reroll_axis="pose",
reroll_seed=3302,
),
camera_config=_orbit_camera(
horizontal_angle=315,
vertical_angle=0,
zoom=5.0,
subject_focus="action",
),
character_cast=_character_cast(),
hardcore_position_config=_action_filter("foreplay_only"),
location_config=location_config,
composition_config=composition_config,
)
_expect_custom_row(row, "config_route_location_theme")
_expect(row.get("subcategory") == "Foreplay and teasing", "config route did not preserve requested subcategory")
_expect(row.get("subject_type") == "configured_cast", "config route did not apply character cast")
scene = _expect_text("config_route_location_theme.scene_text", row.get("scene_text"), 20)
composition = _expect_text("config_route_location_theme.composition", row.get("composition"), 10)
camera = _expect_text("config_route_location_theme.camera_directive", row.get("camera_directive"), 20)
_expect("library" in scene.lower() or "bookshelves" in scene.lower(), "location theme did not drive scene")
_expect("books" in composition.lower() or "shelf" in composition.lower() or "library" in composition.lower(), "location theme did not drive composition")
_expect("315-degree front-left quarter view" in camera, "config route did not preserve orbit camera directive")
seed_config = row.get("seed_config") if isinstance(row.get("seed_config"), dict) else {}
_expect(seed_config.get("pose_seed") == 3302, "seed lock did not reroll pose axis")
_expect(seed_config.get("role_seed") == 3302, "seed lock did not reroll role axis")
_expect(row.get("trigger") == "sxcpinup_coloredpencil", "generation profile trigger did not apply")
_expect_trigger_once("config_route_location_theme.prompt", row.get("prompt"), "sxcpinup_coloredpencil")
typed_route = krea_configured_cast_formatter.format_configured_cast_result(
krea_formatter._krea_configured_cast_request_from_row(row, "balanced", "preserve"),
krea_formatter._krea_configured_cast_dependencies(),
)
legacy_route = krea_formatter._normal_row_to_krea(row, "balanced", "preserve")
_expect(
typed_route.as_tuple() == legacy_route,
"Typed Krea configured-cast formatter route should match legacy wrapper output",
)
krea = krea_formatter.format_krea2_prompt("", metadata_json=_json(row), target="single")
prompt = krea.get("krea_prompt") or ""
_expect("library" in prompt.lower() or "bookshelves" in prompt.lower(), "Krea config route lost theme scene")
_expect("315-degree front-left quarter view" in prompt, "Krea config route lost camera directive")
_expect_formatter_outputs(row, "config_route_location_theme", target="single")
def smoke_krea_normal_row_routes() -> None:
single = {
"subject_type": "woman",
"primary_subject": "woman",
"age_band": "25-year-old adult",
"body_phrase": "slim figure",
"skin": "fair skin",
"hair": "long blonde hair",
"eyes": "blue eyes",
"item": "silk dress",
"pose": "standing beside a window",
"scene_text": "quiet studio with warm daylight",
"expression": "soft smile",
"composition": "vertical centered portrait",
"camera_directive": "Camera: eye-level medium shot",
"style": "realistic creator-shot photography",
}
_expect_krea_normal_route_parity(single, "krea_normal_single", "metadata(single)")
couple = {
"subject_type": "couple",
"primary_subject": "a woman and a man",
"subject_phrase": "woman and man",
"age": "25-year-old adult and 40-year-old adult",
"body": "slim and average builds",
"item": "Partner A wears black dress; Partner B wears dark shirt",
"pose": "standing close together",
"scene_text": "private lounge with soft lamps",
"expression": "shared confident gaze",
"composition": "two-person editorial frame",
"camera_directive": "Camera: front view, medium shot",
"style": "realistic social photo",
}
_expect_krea_normal_route_parity(couple, "krea_normal_couple", "metadata(couple)")
generic = {
"subject_type": "location",
"primary_subject": "adult editorial scene",
"item": "polished lounge styling",
"scene_text": "hotel hallway with warm wall sconces",
"expression": "quiet atmosphere",
"composition": "wide establishing frame",
"camera_directive": "Camera: wide shot",
"style": "clean photographic realism",
}
_expect_krea_normal_route_parity(generic, "krea_normal_generic", "metadata(generic)")
def smoke_location_config_policy() -> None:
_expect(pb.LOCATION_POOL_PRESETS is location_config.LOCATION_POOL_PRESETS, "Prompt builder location presets are not delegated")
_expect(pb.COMPOSITION_POOL_PRESETS is location_config.COMPOSITION_POOL_PRESETS, "Prompt builder composition presets are not delegated")
_expect("classical_library" in location_config.location_theme_choices(), "Location themes lost classical_library")
custom = json.loads(
pb.build_location_pool_json(
enabled=True,
combine_mode="replace",
preset="custom_only",
custom_locations="custom_room: a quiet room with warm lamps",
)
)
_expect(custom.get("enabled") is True, "Custom location config should be active")
_expect(custom.get("apply_mode") == "replace", "Custom location config lost replace mode")
_expect(custom.get("scene_entries", [{}])[0].get("slug") == "custom_room", "Custom location slug parser changed")
added = json.loads(
location_config.build_location_pool_json(
enabled=True,
combine_mode="add",
preset="custom_only",
custom_locations="second_room: another quiet room",
location_config=custom,
)
)
_expect(added.get("apply_mode") == "replace", "Location add merge should preserve incoming apply_mode")
_expect(len(added.get("scene_entries") or []) == 2, "Location add merge did not keep both custom locations")
composition = json.loads(
pb.build_composition_pool_json(
enabled=True,
combine_mode="replace",
preset="no_outfit_check",
custom_compositions="manual frame through foreground bookshelves",
)
)
_expect(composition.get("enabled") is True, "Composition config should be active")
_expect(
any("outfit-check" in str(entry) for entry in composition.get("composition_entries") or []),
"Composition inline preset no_outfit_check was not applied",
)
parsed = pb._parse_location_config({"enabled": True, "pool_names": [], "scene_entries": custom["scene_entries"]})
_expect(pb._location_config_active(parsed), "Prompt builder location parser wrapper is inactive")
themed_location, themed_composition, theme_summary = pb.build_thematic_location_json(
enabled=True,
combine_mode="replace",
theme="classical_library",
)
_expect("classical_library" in theme_summary, "Themed location summary lost theme name")
_expect(json.loads(themed_location).get("scene_entries"), "Themed location did not output locations")
_expect(json.loads(themed_composition).get("composition_entries"), "Themed location did not output compositions")
def smoke_row_location_policy() -> None:
location = json.loads(
location_config.build_location_pool_json(
combine_mode="replace",
custom_locations="archive_corner: hidden archive corner with repeated shelves and warm table lamps",
)
)
composition = json.loads(
location_config.build_composition_pool_json(
combine_mode="replace",
custom_compositions="long archive aisle composition",
)
)
row = {
"source": "built_in_generator",
"primary_subject": "adult woman",
"scene": "unknown_old_scene",
"composition": "old frame",
"prompt": "A generated adult prompt. Scene: old room. Pose: standing. Composition: vertical old frame. Avoid: low quality.",
"caption": "sxcppnl7, generated adult prompt, old room, old frame, illustration",
}
updated = row_location.apply_location_config_to_legacy_row(dict(row), location, {}, 123, 1)
updated = row_location.apply_composition_config_to_legacy_row(updated, composition, {}, 123, 1)
_expect(updated.get("scene") == "archive_corner", "Row location policy did not select forced custom scene slug")
_expect(
updated.get("scene_text") == "hidden archive corner with repeated shelves and warm table lamps",
"Row location policy did not apply forced custom scene text",
)
_expect(updated.get("source_scene") == "unknown_old_scene", "Row location policy lost source scene slug")
_expect(
"Scene: hidden archive corner with repeated shelves and warm table lamps. Pose:" in updated.get("prompt", ""),
"Row location policy did not rewrite prompt scene",
)
_expect(updated.get("composition") == "long archive aisle composition", "Row location policy did not apply forced composition")
_expect(
updated.get("composition_prompt") == "vertical long archive aisle composition",
"Row location policy did not compute composition prompt",
)
_expect(
"Composition: vertical long archive aisle composition." in updated.get("prompt", ""),
"Row location policy did not rewrite prompt composition",
)
_expect(", long archive aisle composition," in updated.get("caption", ""), "Row location policy did not rewrite caption composition")
def smoke_row_expression_policy() -> None:
entries = [
{"text": "quiet calm focus", "weight": 2.0},
{"text": "heated smirk", "weight": 3.0},
{"text": "dazed overwhelmed look", "weight": 1.0},
]
_expect(
pb._expression_entries_for_intensity(entries, 0.25) == row_expression.expression_entries_for_intensity(entries, 0.25),
"Prompt builder expression intensity wrapper should delegate to row_expression",
)
weighted = row_expression.expression_entries_for_intensity(entries, 0.25)
_expect(weighted[0]["weight"] == 8.0, "Row expression low-intensity weighting changed")
_expect(weighted[2]["weight"] == 0.05, "Row expression distant-intensity weighting changed")
row = {
"prompt": "Person with quiet focus, in room. Facial expression: quiet focus. Keep this.",
"caption": "sxcppnl7, person with quiet focus, in room",
"expression": "quiet focus",
"shared_expression": "quiet focus",
"character_expressions": ["Woman A has quiet focus"],
"character_expression_text": "Woman A has quiet focus",
}
_expect(
pb._disable_row_expression(dict(row), "test") == row_expression.disable_row_expression(dict(row), "test"),
"Prompt builder expression disable wrapper should delegate to row_expression",
)
disabled = row_expression.disable_row_expression(dict(row), "test")
_expect(disabled.get("expression_disabled") is True, "Row expression disable did not set disabled metadata")
_expect("quiet focus" not in disabled.get("prompt", ""), "Row expression disable did not strip prompt expression text")
woman_slot = character_slot.normalize_character_slot(
{
"subject_type": "woman",
"label": "A",
"expression_intensity": 0.8,
"softcore_expression_intensity": 0.2,
}
)
pov_man_slot = character_slot.normalize_character_slot(
{
"subject_type": "man",
"label": "A",
"presence_mode": "pov",
"expression_intensity": 1.0,
}
)
label_map = {"Woman A": woman_slot, "Man A": pov_man_slot}
_expect(
pb._cast_expression_intensity_override(0.5, label_map, 1, 1, "softcore")
== row_expression.cast_expression_intensity_override(0.5, label_map, 1, 1, "softcore"),
"Prompt builder cast expression override wrapper should delegate to row_expression",
)
_expect(
row_expression.cast_expression_intensity_override(0.5, label_map, 1, 1, "softcore")
== (0.2, "character_slot:Woman A"),
"Row expression cast override did not prefer visible slot phase intensity",
)
_expect(
pb._resolve_expression_route(
expression_enabled=True,
expression_intensity=0.5,
expression_intensity_source="input",
subject_type="woman",
applied_slot=woman_slot,
women_count=1,
men_count=0,
expression_phase="softcore",
)
== row_expression.resolve_expression_route(
expression_enabled=True,
expression_intensity=0.5,
expression_intensity_source="input",
subject_type="woman",
applied_slot=woman_slot,
women_count=1,
men_count=0,
expression_phase="softcore",
),
"Prompt builder expression route wrapper should delegate to row_expression",
)
route = row_expression.resolve_expression_route(
expression_enabled=True,
expression_intensity=0.5,
expression_intensity_source="input",
subject_type="woman",
applied_slot=woman_slot,
women_count=1,
men_count=0,
expression_phase="softcore",
)
_expect(route.expression_intensity == 0.2, "Expression route did not apply phase-specific slot intensity")
_expect(route.expression_intensity_source == "character_slot:Woman A", "Expression route lost slot source")
_expect(
pb._character_expression_entries(random.Random(22), entries, 0.5, label_map, 1, 1, "softcore")
== row_expression.character_expression_entries(random.Random(22), entries, 0.5, label_map, 1, 1, "softcore"),
"Prompt builder character expression wrapper should delegate to row_expression",
)
disabled_slot = character_slot.normalize_character_slot(
{"subject_type": "woman", "label": "A", "expression_enabled": False}
)
_expect(
row_expression.cast_expression_intensity_override(0.5, {"Woman A": disabled_slot}, 1, 0, "hardcore")
== (None, "character_slots:disabled"),
"Row expression cast override did not honor all-slot expression disable",
)
global_disabled = row_expression.resolve_expression_route(
expression_enabled=False,
expression_intensity=0.8,
expression_intensity_source="input",
subject_type="woman",
applied_slot=woman_slot,
)
_expect(global_disabled.expression_disabled is True, "Expression route did not honor global disabled state")
_expect(global_disabled.expression_intensity == 0.8, "Expression route changed disabled fallback intensity too early")
_expect(global_disabled.expression_intensity_source == "disabled", "Expression route did not mark global disabled source")
slot_disabled = row_expression.resolve_expression_route(
expression_enabled=True,
expression_intensity=0.5,
expression_intensity_source="input",
subject_type="woman",
applied_slot=disabled_slot,
)
_expect(slot_disabled.expression_disabled is True, "Expression route did not honor single-slot disable")
_expect(
slot_disabled.expression_intensity_source == "character_slot:Woman A:disabled",
"Expression route lost single-slot disabled source",
)
cast_disabled = row_expression.resolve_expression_route(
expression_enabled=True,
expression_intensity=0.5,
expression_intensity_source="input",
subject_type="configured_cast",
character_slots=[disabled_slot],
character_slot_map={"Woman A": disabled_slot},
women_count=1,
men_count=0,
expression_phase="hardcore",
)
_expect(cast_disabled.expression_disabled is True, "Expression route did not honor all-slot cast disable")
_expect(cast_disabled.expression_intensity is None, "Expression route did not clear all-slot disabled intensity")
_expect(
cast_disabled.expression_intensity_source == "character_slots:disabled",
"Expression route lost all-slot disabled source",
)
expression_text = "Woman A has steady focus; Man A has parted lips with saliva"
context_role = "Woman A performs a handjob while Man A stands close"
_expect(
pb._sanitize_character_expression_text_for_action(expression_text, context_role, "", {})
== row_expression.sanitize_character_expression_text_for_action(expression_text, context_role, "", {})
== "Woman A has steady focus",
"Row expression action sanitizer did not remove incompatible partner expression",
)
reverse_context = "Man A has mouth pressed to her pussy while Woman A lies back"
_expect(
row_expression.sanitize_character_expression_text_for_action(
"Woman A has tongue out; Man A has focused mouth contact",
reverse_context,
"",
{},
)
== "Man A has focused mouth contact",
"Row expression action sanitizer did not remove incompatible visible-subject expression",
)
def smoke_row_prompt_axes_policy() -> None:
category = {
"name": "Axis Test",
"slug": "axis_test",
"scenes": [{"slug": "studio", "prompt": "quiet studio with repeatable anchors"}],
"poses": ["standing fallback pose"],
"expressions": ["quiet focus", "heated smirk"],
"compositions": ["all participants visible centered frame"],
}
subcategory = {"name": "Axis Sub", "slug": "axis_sub", "items": ["axis item"]}
item = "axis item"
base_kwargs = {
"category": category,
"subcategory": subcategory,
"item": item,
"subject_type": "woman",
"context": {"fallback_pose": ""},
"poses": "standard",
"women_count": 1,
"men_count": 0,
"expression_disabled": True,
"expression_intensity": 0.5,
"is_pose_category": False,
"location_config": {},
"composition_config": {},
}
route = row_prompt_axes.resolve_prompt_axes(
**base_kwargs,
scene_rng=random.Random(1),
pose_rng=random.Random(2),
expression_rng=random.Random(3),
composition_rng=random.Random(4),
)
route_result = row_prompt_axes.resolve_prompt_axes_result(
**base_kwargs,
scene_rng=random.Random(1),
pose_rng=random.Random(2),
expression_rng=random.Random(3),
composition_rng=random.Random(4),
)
delegated = pb._prompt_axes_route(
**base_kwargs,
scene_rng=random.Random(1),
pose_rng=random.Random(2),
expression_rng=random.Random(3),
composition_rng=random.Random(4),
)
typed_delegated = pb._prompt_axes_route_result(
**base_kwargs,
scene_rng=random.Random(1),
pose_rng=random.Random(2),
expression_rng=random.Random(3),
composition_rng=random.Random(4),
)
_expect(delegated == route, "Prompt builder prompt-axes route should delegate to row_prompt_axes")
_expect(route_result.as_dict() == route, "Typed prompt axes route should match legacy dict route")
_expect(typed_delegated == route_result, "Prompt builder typed prompt-axes route should delegate")
_expect(route_result.scene_slug == "studio", "Typed prompt axes route lost selected scene slug")
_expect(route["scene_slug"] == "studio", "Prompt axes route lost selected scene slug")
_expect(route["scene"] == "quiet studio with repeatable anchors", "Prompt axes route lost selected scene text")
_expect(route["pose"] == "standing fallback pose", "Prompt axes route lost selected fallback pose")
_expect(route["expression"] == "", "Prompt axes route should omit expression when disabled")
_expect(route["shared_expression"] == "", "Prompt axes route should omit shared expression when disabled")
_expect(route["source_composition"] == "all participants visible centered frame", "Prompt axes route lost source composition")
pov_route = row_prompt_axes.resolve_prompt_axes(
**{**base_kwargs, "expression_disabled": True},
scene_rng=random.Random(1),
pose_rng=random.Random(2),
expression_rng=random.Random(3),
composition_rng=random.Random(4),
pov_character_labels=["Man A"],
)
_expect("first-person POV" in pov_route["composition"], "Prompt axes route did not adapt composition for POV")
woman_slot = character_slot.normalize_character_slot(
{"subject_type": "woman", "label": "A", "expression_intensity": 0.25}
)
configured_route = row_prompt_axes.resolve_prompt_axes(
**{
**base_kwargs,
"subject_type": "configured_cast",
"context": {"subject_type": "configured_cast"},
"expression_disabled": False,
"character_slots": [woman_slot],
"character_slot_map": {"Woman A": woman_slot},
},
scene_rng=random.Random(1),
pose_rng=random.Random(2),
expression_rng=random.Random(3),
composition_rng=random.Random(4),
)
_expect(
configured_route["character_expression_text"].startswith("Woman A has "),
"Prompt axes route lost configured-cast character expression",
)
_expect(
configured_route["expression"] == configured_route["character_expression_text"],
"Prompt axes route did not promote character expression text to row expression",
)
def smoke_row_item_policy() -> None:
weighted_entries = [
{"text": "first", "weight": 0.0},
{"text": "second", "weight": 4.0},
{"text": "third", "weight": 1.0},
]
_expect(
pb._weighted_choice(random.Random(44), weighted_entries) == row_item.weighted_choice(random.Random(44), weighted_entries),
"Prompt builder weighted item choice should delegate to row_item",
)
_expect(
pb._pair_from({"name": "Library Corner", "description": "carved shelves and warm lamps"})
== row_item.pair_from({"name": "Library Corner", "description": "carved shelves and warm lamps"}),
"Prompt builder pair parser should delegate to row_item",
)
oral_values = [
"fellatio with penis in mouth",
"cunnilingus with tongue on pussy",
"sixty-nine mutual oral",
]
_expect(
pb._oral_acts_for_position(oral_values, "kneeling oral position")
== row_item.oral_acts_for_position(oral_values, "kneeling oral position")
== ["fellatio with penis in mouth"],
"Row item oral act filtering changed for kneeling oral",
)
outer_values = [
"titjob with compressed breasts",
"handjob with palm around shaft",
"footjob with soles and toes",
]
_expect(
pb._outercourse_acts_for_position(outer_values, "footjob position")
== row_item.outercourse_acts_for_position(outer_values, "footjob position")
== ["footjob with soles and toes"],
"Row item outercourse act filtering changed for footjob",
)
texture_values = [
"fingers pressing around shaft",
"soles pressing around shaft",
"tongue wet against shaft",
]
_expect(
row_item.outercourse_axis_values_for_position(texture_values, "footjob position", "texture_detail")
== ["soles pressing around shaft"],
"Row item outercourse texture axis should prefer footjob-compatible details",
)
category = {}
subcategory = {
"name": "Oral sex",
"slug": "oral_sex",
"item_templates": [
{
"template": "{oral_act}; {hand_detail}",
"action_family": "oral",
"position_family": "oral",
}
],
"item_axes": {
"position": ["kneeling oral position"],
"oral_act": oral_values,
"hand_detail": ["hands on hips", "spreading thighs"],
},
}
item = "Oral sex"
builder_result = pb._compose_item(random.Random(7), category, subcategory, item, women_count=1, men_count=1)
policy_result = row_item.compose_item(random.Random(7), category, subcategory, item, women_count=1, men_count=1)
_expect(builder_result == policy_result, "Prompt builder compose item should delegate to row_item")
item_text, item_name, axis_values, metadata = policy_result
_expect(item_name == "Oral sex", "Row item compose lost item name")
_expect("fellatio" in item_text, "Row item compose did not apply oral act compatibility filter")
_expect(axis_values.get("hand_detail") == "hands on hips", "Row item compose did not apply oral detail filter")
_expect(metadata.get("action_family") == "oral", "Row item compose lost template metadata")
def smoke_row_category_route_policy() -> None:
hard_config = hardcore_position_config.parse_hardcore_position_config(_position_filter("oral_only", "oral", ["kneeling"]))
seed_cfg = seed_config.parse_seed_config({})
route = row_category_route.select_category_item_route(
category_choice="custom_random",
subcategory_choice="Hardcore sexual poses / Oral sex",
seed_config=seed_cfg,
seed=2301,
row_number=1,
women_count=1,
men_count=1,
hardcore_position_config=hard_config,
)
route_result = row_category_route.select_category_item_route_result(
category_choice="custom_random",
subcategory_choice="Hardcore sexual poses / Oral sex",
seed_config=seed_cfg,
seed=2301,
row_number=1,
women_count=1,
men_count=1,
hardcore_position_config=hard_config,
)
delegated = pb._select_category_item_route(
category_choice="custom_random",
subcategory_choice="Hardcore sexual poses / Oral sex",
seed_config=seed_cfg,
seed=2301,
row_number=1,
women_count=1,
men_count=1,
hardcore_position_config=hard_config,
)
typed_delegated = pb._select_category_item_route_result(
category_choice="custom_random",
subcategory_choice="Hardcore sexual poses / Oral sex",
seed_config=seed_cfg,
seed=2301,
row_number=1,
women_count=1,
men_count=1,
hardcore_position_config=hard_config,
)
_expect(delegated == route, "Prompt builder category/item route should delegate to row_category_route")
_expect(route_result.as_dict() == route, "Typed category/item route should match legacy dict route")
_expect(typed_delegated == route_result, "Prompt builder typed category/item route should delegate")
_expect(route_result.content_axis == "pose", "Typed category/item route lost content seed axis")
_expect(route["category"]["slug"] == "hardcore_sexual_poses", "Row category route selected wrong hardcore category")
_expect(route["subcategory"]["slug"] == "oral_sex", "Row category route selected wrong hardcore subcategory")
_expect(route["content_axis"] == "pose", "Hardcore pose category should use pose seed axis")
_expect(route["is_pose_category"] is True, "Hardcore pose category should be marked as pose content")
_expect(isinstance(route["item_axis_values"], dict), "Row category route lost item axis metadata")
_expect(isinstance(route["formatter_hints"], dict), "Row category route lost formatter hint metadata")
_expect(
pb._is_pose_content_category(route["category"], route["subcategory"])
== row_category_route.is_pose_content_category(route["category"], route["subcategory"]),
"Prompt builder pose-content wrapper should delegate",
)
casual_route = row_category_route.select_category_item_route(
category_choice="custom_random",
subcategory_choice="Casual clothes / Streetwear",
seed_config=seed_cfg,
seed=2301,
row_number=1,
women_count=1,
men_count=0,
hardcore_position_config={},
)
_expect(casual_route["category"]["slug"] == "casual_clothes", "Row category route selected wrong casual category")
_expect(casual_route["content_axis"] == "content", "Non-pose category should use content seed axis")
_expect(casual_route["is_pose_category"] is False, "Non-pose category should not be marked as pose content")
def smoke_row_generation_policy() -> None:
_expect(pb._ratio_or_none(-1) is None, "Prompt builder ratio helper should treat negative as unset")
_expect(pb._ratio_or_none(1.5) == row_generation.ratio_or_none(1.5) == 1.0, "Row generation ratio clamp changed")
_expect(pb._clamped_float("bad", 0.4) == row_generation.clamped_float("bad", 0.4) == 0.4, "Row generation float default changed")
_expect(
pb._pick_clothing_mode(random.Random(1), "random", None)
== row_generation.pick_clothing_mode(random.Random(1), "random", None),
"Prompt builder clothing mode picker should delegate to row_generation",
)
_expect(
row_generation.pick_pose_mode(random.Random(2), "evocative", 1.0) == "standard",
"Row generation standard pose ratio override changed",
)
_expect(
pb._pick_figure_bias(random.Random(3), "random") == row_generation.pick_figure_bias(random.Random(3), "random"),
"Prompt builder figure picker should delegate to row_generation",
)
_expect(
row_generation.pick_expression_intensity(random.Random(4), -1) == (0.24, "random"),
"Row generation random expression intensity changed",
)
_expect(
pb._pick_expression_intensity(random.Random(4), 2.0) == row_generation.pick_expression_intensity(random.Random(4), 2.0) == (1.0, "input"),
"Prompt builder expression intensity picker should delegate to row_generation",
)
direct_args = dict(
category="woman",
row_number=3,
start_index=41,
clothing="full",
ethnicity="any",
poses="standard",
backside_bias=0.25,
figure="curvy",
no_plus_women=False,
no_black=False,
minimal_clothing_ratio=None,
standard_pose_ratio=None,
seed=5050,
)
_expect(
pb._build_direct_builtin_row(**direct_args) == row_generation.build_direct_builtin_row(**direct_args),
"Prompt builder direct built-in row should delegate to row_generation",
)
auto_args = dict(
row_number=2,
start_index=41,
clothing="minimal",
ethnicity="any",
poses="evocative",
backside_bias=0.0,
figure="balanced",
no_plus_women=False,
no_black=False,
minimal_clothing_ratio=None,
standard_pose_ratio=None,
seed=6060,
)
auto_row = row_generation.build_auto_weighted_row(**auto_args)
_expect(pb._build_auto_weighted_row(**auto_args) == auto_row, "Prompt builder auto-weighted row should delegate to row_generation")
_expect(auto_row.get("source") == "built_in_generator", "Row generation auto-weighted row lost source metadata")
seed_cfg = seed_config.parse_seed_config({"category_seed": 123})
_expect(
pb._auto_full_choice(seed_cfg, 7070, 1) == row_generation.auto_full_choice(seed_cfg, 7070, 1),
"Prompt builder auto-full choice should delegate to row_generation",
)
def smoke_category_extensions_policy() -> None:
_expect(pb.BUILTIN_CATEGORIES is category_extensions.BUILTIN_CATEGORIES, "Prompt builder built-in categories should come from category_extensions")
targets = category_extensions.extension_targets()
_expect(
pb._extension_targets().keys() == targets.keys(),
"Prompt builder extension targets should delegate to category_extensions",
)
_expect(targets.get("group_scenes", (None, None))[1] is True, "Group scene extension target should expect scene pairs")
sample = [{"slug": "a", "prompt": "one"}]
category_extensions.unique_extend(sample, [{"slug": "a", "prompt": "one"}, {"slug": "b", "prompt": "two"}])
_expect(len(sample) == 2 and sample[1]["slug"] == "b", "Category extension unique_extend changed")
pb.apply_pool_extensions()
category_extensions.apply_pool_extensions()
_expect(pb.category_choices() == category_extensions.category_choices(), "Prompt builder category choices should delegate")
_expect(pb.subcategory_choices() == category_extensions.subcategory_choices(), "Prompt builder subcategory choices should delegate")
_expect("Hardcore sexual poses" in pb.category_choices(), "Category choices lost hardcore JSON category")
_expect(
any(slug == "private_suite_group_party" for slug, _prompt in targets["group_scenes"][0]),
"JSON pool_extensions did not reach legacy group scenes",
)
def smoke_category_cast_config_policy() -> None:
_expect(pb.CATEGORY_PRESETS is category_cast_config.CATEGORY_PRESETS, "Prompt builder category presets are not delegated")
_expect(pb.CAST_PRESETS is category_cast_config.CAST_PRESETS, "Prompt builder cast presets are not delegated")
_expect("hardcore_pose" in category_cast_config.category_preset_choices(), "Category preset choices lost hardcore_pose")
_expect("custom_counts" in category_cast_config.cast_preset_choices(), "Cast preset choices lost custom_counts")
_expect(
pb._count_phrase(2, "adult woman", "adult women") == cast_context.count_phrase(2, "adult woman", "adult women"),
"Prompt builder count phrase should delegate to cast_context",
)
configured = cast_context.configured_cast_context(1, 2)
_expect(configured.get("subject_phrase") == "one adult woman and two adult men", "Configured cast subject phrase changed")
_expect(configured.get("cast_summary") == "1 woman, 2 men, 3 total adults", "Configured cast summary changed")
_expect(configured.get("scene_kind") == "adult threesome sex scene", "Configured cast scene kind changed")
_expect(
pb._configured_cast_context(1, 2) == configured,
"Prompt builder configured cast context should delegate to cast_context",
)
_expect(
cast_context.couple_type_from_counts(
random.Random(1),
2,
0,
choose=lambda _rng, pool: pool[0],
couple_types=[("woman and man", "a woman and a man", "fallback pose")],
)
== ("two women", "two women", "close affectionate couple pose", 2, 0),
"Couple type count override for two women changed",
)
_expect(
pb._subject_context(random.Random(5), "couple", "any", "curvy", False, False, 1, 1)
== subject_context.subject_context(random.Random(5), "couple", "any", "curvy", False, False, 1, 1),
"Prompt builder subject context should delegate to subject_context",
)
_expect(
subject_context.subject_context(random.Random(1), "configured_cast", "any", "curvy", False, False, 2, 1).get("cast_summary")
== "2 women, 1 man, 3 total adults",
"Configured cast subject context changed",
)
group = subject_context.subject_context(random.Random(2), "group", "asian", "curvy", False, False)
_expect(group.get("subject") == "mixed Asian adult group", "Group subject ethnicity wording changed")
_expect(
subject_context.subject_context(random.Random(3), "layout", "any", "curvy", False, False).get("subject")
== "layout scene",
"Layout subject context fallback changed",
)
label_slots = [
{"subject_type": "woman", "label": "auto_chain", "name": "older auto"},
{"subject_type": "woman", "label": "auto_chain", "name": "newer auto"},
{"subject_type": "man", "label": "B", "name": "explicit man"},
]
label_map = cast_context.character_slot_label_map(label_slots)
_expect(
label_map.get("Woman A", {}).get("name") == "newer auto"
and label_map.get("Woman B", {}).get("name") == "older auto",
"Character slot auto-chain label order changed",
)
_expect(
label_map.get("Man B", {}).get("name") == "explicit man",
"Character slot explicit label mapping changed",
)
_expect(
pb._character_slot_label_map(label_slots) == label_map,
"Prompt builder character slot label map should delegate to cast_context",
)
category_config = json.loads(pb.build_category_config_json("hardcore_pose", "Foreplay and teasing"))
_expect(category_config.get("category") == "Hardcore sexual poses", "Category config lost hardcore category mapping")
_expect(category_config.get("subcategory") == "Foreplay and teasing", "Category config lost explicit subcategory")
_expect(pb._parse_category_config(category_config) == ("Hardcore sexual poses", "Foreplay and teasing"), "Category parser wrapper drifted")
fallback_config = json.loads(category_cast_config.build_category_config_json("unknown", "random"))
_expect(fallback_config.get("preset") == "auto_weighted", "Unknown category preset did not fall back")
_expect(pb._parse_category_config({"preset": "unknown"}) == ("auto_weighted", "random"), "Unknown category parser fallback changed")
cast_config = json.loads(pb.build_cast_config_json("mixed_couple", 9, 9))
_expect((cast_config.get("women_count"), cast_config.get("men_count")) == (1, 1), "Cast preset did not override manual counts")
custom_cast = json.loads(category_cast_config.build_cast_config_json("custom_counts", -5, 99))
_expect((custom_cast.get("women_count"), custom_cast.get("men_count")) == (0, 12), "Custom cast counts were not clamped")
empty_cast = pb._parse_cast_config({"cast_mode": "custom_counts", "women_count": 0, "men_count": 0})
_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:
_expect(
pb.GENERATION_PROFILE_PRESETS is generation_profile_config.GENERATION_PROFILE_PRESETS,
"Prompt builder generation profile presets are not delegated",
)
_expect("krea2_friendly" in generation_profile_config.generation_profile_choices(), "Generation profile choices lost krea2_friendly")
profile = json.loads(
pb.build_generation_profile_json(
profile="krea2_friendly",
clothing_override="minimal",
poses_override="random",
expression_enabled=False,
expression_intensity_mode="random",
expression_intensity=0.8,
backside_bias=2,
minimal_clothing_ratio=0.25,
standard_pose_ratio=0.75,
trigger_policy="prepend_trigger",
)
)
_expect(profile.get("profile") == "krea2_friendly", "Generation profile output lost selected profile")
_expect(profile.get("clothing") == "minimal", "Generation profile clothing override failed")
_expect(profile.get("poses") == "random", "Generation profile poses override failed")
_expect(profile.get("expression_enabled") is False, "Generation profile expression disable failed")
_expect(profile.get("expression_intensity") == -1.0, "Generation profile random expression marker changed")
_expect(profile.get("backside_bias") == 1.0, "Generation profile backside bias clamp changed")
_expect(profile.get("prepend_trigger_to_prompt") is True, "Generation profile trigger override failed")
parsed = pb._parse_generation_profile(profile)
_expect(parsed.get("clothing") == "minimal", "Generation profile parser wrapper lost clothing")
_expect(parsed.get("expression_enabled") is False, "Generation profile parser wrapper lost expression disable")
_expect(parsed.get("minimal_clothing_ratio") == 0.25, "Generation profile parser wrapper lost minimal clothing ratio")
fallback = generation_profile_config.parse_generation_profile({"profile": "unknown", "clothing": "bad", "poses": "bad"})
_expect(fallback.get("profile") == "unknown", "Generation profile parser should preserve raw profile label")
_expect(fallback.get("clothing") == "full", "Generation profile parser did not normalize invalid clothing")
_expect(fallback.get("poses") == "standard", "Generation profile parser did not normalize invalid poses")
_expect(fallback.get("trigger") == "sxcpinup_coloredpencil", "Generation profile parser lost default trigger")
def smoke_filter_config_policy() -> None:
_expect(pb.ETHNICITY_FILTER_CHOICES is filter_config.ETHNICITY_FILTER_CHOICES, "Prompt builder ethnicity choices are not delegated")
_expect("french_european" in filter_config.ETHNICITY_LIST_KEYS, "Ethnicity list keys lost regional choices")
advanced = json.loads(
pb.build_filter_config_json(
include_european=True,
include_mediterranean_mena=False,
include_latina=False,
include_east_asian=False,
include_southeast_asian=False,
include_south_asian=False,
include_black_african=True,
include_indigenous=False,
include_mixed=False,
include_plus_size=False,
figure="bad",
)
)
_expect(advanced.get("ethnicity_includes") == ["european", "black_african"], "Advanced filter selected ethnicity list changed")
_expect("exclude_latina" in advanced.get("ethnicity", ""), "Advanced filter ethnicity excludes changed")
_expect(advanced.get("figure") == "curvy", "Advanced filter invalid figure fallback changed")
_expect(advanced.get("no_plus_women") is True, "Advanced filter plus-size exclusion changed")
ethnicity_list = pb.build_ethnicity_list_json(include_french_european=True, include_asian=True, strict_excludes=True)
_expect("french_european" in ethnicity_list["ethnicity"], "Ethnicity list lost regional include")
_expect("asian" in ethnicity_list["ethnicity"], "Ethnicity list lost umbrella Asian include")
_expect("exclude_european" not in ethnicity_list["ethnicity"], "Ethnicity list should protect European when regional Europe is selected")
_expect("exclude_east_asian" not in ethnicity_list["ethnicity"], "Ethnicity list should protect East Asian when Asian is selected")
_expect("filter_config" in ethnicity_list, "Ethnicity list lost filter_config output")
parsed_text = pb._parse_filter_config("french_european")
_expect(parsed_text.get("ethnicity") == "french_european", "Filter parser text shortcut changed")
parsed_bad = filter_config.parse_filter_config({"ethnicity": "bad", "figure": "bad"})
_expect(parsed_bad.get("ethnicity") == "any", "Filter parser invalid ethnicity fallback changed")
_expect(parsed_bad.get("figure") == "curvy", "Filter parser invalid figure fallback changed")
_expect(pb.normalize_ethnicity_filter("random", "any", allow_random=True) == "random", "Ethnicity random normalization changed")
_expect(pb.normalize_ethnicity_filter("random", "any", allow_random=False) == "any", "Ethnicity default normalization changed")
def smoke_character_config_policy() -> None:
_expect(pb.CHARACTER_LABEL_CHOICES is character_config.CHARACTER_LABEL_CHOICES, "Prompt builder character choices are not delegated")
_expect("21-year-old adult" in character_config.character_age_choices(), "Character age choices lost adult ages")
_expect("fat" in character_config.character_man_body_choices(), "Man body pool lost fat option")
_expect(pb.character_figure_choices() == character_config.character_figure_choices(), "Character figure choices should delegate")
_expect("platinum_blonde" in character_config.character_hair_color_choices(), "Hair color choices lost platinum blonde")
traits = json.loads(
pb.build_characteristics_config_json(
axis="bodies",
selected_values=["slim", "bad value", "slim", "fat"],
combine_mode="replace_axis",
)
)
_expect(traits.get("bodies") == ["slim", "fat"], "Character body trait normalization changed")
merged_traits = json.loads(
character_config.build_characteristics_config_json(
characteristics=traits,
axis="eyes",
selected_values=["blue", "gray-brown", "blue"],
combine_mode="add_to_axis",
)
)
_expect(merged_traits.get("bodies") == ["slim", "fat"], "Character trait merge lost existing axis")
_expect(merged_traits.get("eyes") == ["blue", "gray_brown"], "Character eye trait normalization changed")
_expect(pb._characteristic_choice({"ages": ["21-year-old adult"]}, "ages", random.Random(1)) == "21-year-old adult", "Trait choice changed")
hair = json.loads(
pb.build_hair_config_json(
axis="color",
selected_values=["platinum blonde", "bad", "dark-brown"],
combine_mode="replace_axis",
)
)
_expect(hair.get("colors") == ["platinum_blonde", "dark_brown"], "Hair color normalization changed")
hair = json.loads(
character_config.build_hair_config_json(
hair_config=hair,
axis="style",
selected_values=["messy bun", "straight"],
combine_mode="add_to_axis",
)
)
_expect(hair.get("styles") == ["messy_bun", "straight"], "Hair style config merge changed")
_expect(pb._hair_phrase_from_parts("platinum_blonde", "long", "messy_bun") == "long platinum-blonde hair in a messy bun", "Hair phrase helper changed")
_expect(character_config.normalize_presence_mode("pov", "woman") == "visible", "POV presence should stay man-only")
pov_slot = {"subject_type": "man", "presence_mode": "pov"}
visible_slot = {"subject_type": "man", "presence_mode": "visible"}
_expect(pb._slot_is_pov(pov_slot) is True, "Prompt builder POV slot helper should delegate to POV policy")
_expect(pov_policy.slot_is_pov(visible_slot) is False, "Visible man slot should not be POV")
_expect(
pb._pov_character_labels({"Man A": pov_slot, "Man B": visible_slot}, 2) == ["Man A"],
"POV label selection should keep only POV men in count order",
)
_expect(
pb._pov_role_graph_prompt("Man A is positioned behind Woman A", ["Man A"])
== "First-person POV from Man A; the POV camera is positioned behind Woman A",
"Builder POV role graph prompt should use shared viewer replacement",
)
_expect(
pb._pov_composition_prompt("wide group-sex composition with all bodies visible", ["Man A"])
== "first-person group-sex POV composition with visible partners readable",
"Builder POV composition prompt should use shared POV composition replacements",
)
_expect(
krea_pov.pov_composition_text(
"wide group-sex composition with all bodies visible, adapted for first-person POV with the POV participant kept off-camera",
["Man A"],
)
== "first-person group-sex POV composition with visible partners readable",
"Krea POV composition cleanup should delegate shared replacements and strip builder annotation",
)
_expect(character_config.normalize_slot_seed(0xFFFFFFFF + 99) == 0xFFFFFFFF, "Slot seed clamp changed")
slot_result = character_slot.build_character_slot_json(
subject_type="man",
label="Man B",
slot_seed=123,
age="manual",
manual_age="44-year-old adult",
ethnicity="western_european",
figure="balanced",
body="manual",
manual_body="stocky",
descriptor_detail="compact",
expression_intensity=1.5,
softcore_expression_intensity=0.25,
hardcore_expression_intensity=-1,
presence_mode="pov",
hair_color="dark brown",
hair_length="short",
hair_style="straight",
softcore_outfit="buttoned shirt",
hardcore_clothing="shirt pushed open",
)
_expect(
pb.build_character_slot_json(
subject_type="man",
label="Man B",
slot_seed=123,
age="manual",
manual_age="44-year-old adult",
ethnicity="western_european",
figure="balanced",
body="manual",
manual_body="stocky",
descriptor_detail="compact",
expression_intensity=1.5,
softcore_expression_intensity=0.25,
hardcore_expression_intensity=-1,
presence_mode="pov",
hair_color="dark brown",
hair_length="short",
hair_style="straight",
softcore_outfit="buttoned shirt",
hardcore_clothing="shirt pushed open",
)
== slot_result,
"Prompt builder character slot JSON should delegate to character_slot",
)
slot = json.loads(slot_result["character_slot"])
_expect(slot.get("age") == "44-year-old adult", "Character slot manual age normalization changed")
_expect(slot.get("body") == "stocky", "Character slot manual body normalization changed")
_expect(slot.get("presence_mode") == "pov", "Character slot POV presence normalization changed")
_expect(slot.get("expression_intensity") == 1.0, "Character slot expression intensity clamp changed")
_expect(
character_slot.slot_expression_intensity_for_phase(slot, "softcore") == 0.25
and character_slot.slot_expression_intensity_for_phase(slot, "hardcore") == 1.0,
"Character slot phase expression fallback changed",
)
_expect(
pb._slot_effective_figure({"slot_seed": 123, "figure": "random"}, "woman", "curvy")
== character_slot.slot_effective_figure({"slot_seed": 123, "figure": "random"}, "woman", "curvy"),
"Prompt builder seeded slot figure should delegate to character_slot",
)
_expect(
pb._appearance_for_subject(random.Random(9), "woman", "western_european", "balanced", False, False)
== character_appearance.appearance_for_subject(random.Random(9), "woman", "western_european", "balanced", False, False),
"Prompt builder appearance selection should delegate to character_appearance",
)
_expect(
pb._context_from_character_slot(random.Random(11), slot, "man", "any", "curvy", False, False)
== character_appearance.context_from_character_slot(random.Random(11), slot, "man", "any", "curvy", False, False),
"Prompt builder slot context should delegate to character_appearance",
)
_expect(
pb._row_from_character_slot(slot_result["character_slot"])
== character_appearance.row_from_character_slot(slot_result["character_slot"]),
"Prompt builder slot row conversion should delegate to character_appearance",
)
row = character_appearance.apply_character_context_to_row({}, {"age": "44-year-old adult", "body": "stocky"})
_expect(row.get("age_band") == "44-year-old adult" and row.get("body") == "stocky", "Character context row application changed")
def smoke_character_profile_policy() -> None:
_expect(pb.CHARACTER_MANUAL_FIELDS is character_profile.CHARACTER_MANUAL_FIELDS, "Prompt builder manual fields are not delegated")
_expect(pb.PROFILE_DIR == character_profile.PROFILE_DIR, "Prompt builder profile dir is not delegated")
_expect(pb._body_phrase("curvy", "hourglass figure") == "curvy build and hourglass figure", "Body phrase helper changed")
_expect(pb._safe_profile_name("bad name!*") == "bad_name", "Profile name sanitizer changed")
manual = json.loads(
pb.build_character_manual_config_json(
combine_mode="merge_nonempty",
manual_age="31-year-old adult",
body_phrase="custom body",
skin="warm skin",
softcore_outfit="red dress",
)
)
_expect(manual.get("manual_age") == "31-year-old adult", "Manual config lost age")
_expect(manual.get("softcore_outfit") == "red dress", "Manual config lost outfit")
_expect("manual_age=31-year-old adult" in manual.get("summary", ""), "Manual config summary changed")
metadata_row = {
"subject_type": "woman",
"age": "28-year-old adult",
"body": "curvy",
"body_phrase": "curvy figure with full hips",
"skin": "warm skin",
"hair": "long black hair",
"eyes": "brown eyes",
"figure": "balanced",
"descriptor_detail": "medium",
}
profile_result = character_profile.build_character_profile_json(
profile_name="smoke profile",
source="metadata_json",
metadata_json=metadata_row,
)
profile = json.loads(profile_result["profile_json"])
_expect(profile.get("profile_name") == "smoke_profile", "Profile name normalization changed")
_expect(profile.get("age") == "28-year-old adult", "Profile metadata extraction lost age")
_expect("long black hair" in profile_result["descriptor"], "Profile descriptor lost hair at medium detail")
loaded = pb.load_character_profile_json(
profile_name="manual",
fallback_profile_json=profile_result["profile_json"],
override_age="35-year-old adult",
override_descriptor_detail="compact",
)
loaded_profile = json.loads(loaded["profile_json"])
_expect(loaded.get("status") == "fallback", "Profile fallback load status changed")
_expect(loaded_profile.get("age") == "35-year-old adult", "Profile override age did not apply")
_expect(loaded_profile.get("descriptor_detail") == "compact", "Profile override descriptor detail did not apply")
context = {"subject_type": "woman", "subject": "woman", "subject_phrase": "woman", "age": "21-year-old adult"}
applied_context, applied_profile, status = pb._apply_character_profile_to_context(context, loaded_profile)
_expect(status == "applied", "Profile context application changed")
_expect(applied_context.get("age") == "35-year-old adult", "Profile context application lost age")
_expect(applied_profile.get("profile_type") == "character", "Profile context returned wrong profile")
def smoke_row_normalization_policy() -> None:
_expect(
pb._prepend_trigger("base prompt", Trigger, True) == row_normalization.prepend_trigger("base prompt", Trigger, True),
"Prompt builder trigger helper should delegate to row normalization policy",
)
_expect(
pb._combined_negative("bad anatomy", "low quality") == row_normalization.combined_negative("bad anatomy", "low quality"),
"Prompt builder negative helper should delegate to row normalization policy",
)
row = row_normalization.normalize_prompt_row(
{
"prompt": f"{Trigger}, {Trigger}, base prompt.",
"caption": f"{Trigger}, {Trigger}, base caption.",
"negative_prompt": "bad anatomy, bad anatomy",
},
active_trigger=Trigger,
prepend_trigger_to_prompt=True,
extra_positive="extra detail",
extra_negative="low quality, bad anatomy",
default_negative="bad anatomy",
)
_expect_trigger_once("row_normalization.prompt", row.get("prompt"), Trigger)
_expect_trigger_once("row_normalization.caption", row.get("caption"), Trigger)
_expect("extra detail" in row.get("prompt", ""), "Row normalization lost extra positive text")
_expect(row.get("trigger") == Trigger, "Row normalization lost active trigger")
_expect_no_duplicate_comma_items("row_normalization.negative", row.get("negative_prompt"))
outputs = row_normalization.normalize_pair_text_outputs(
active_trigger=Trigger,
prepend_trigger_to_prompt=True,
extra_positive="pair extra",
extra_negative="low quality, bad anatomy",
soft_prompt="soft prompt.",
hard_prompt="hard prompt.",
soft_negative_base="bad anatomy, bad anatomy",
hard_negative_base="bad anatomy, low quality",
soft_caption_parts=[Trigger, "soft caption"],
hard_caption_parts=[Trigger, "hard caption"],
)
_expect_trigger_once("row_normalization.soft_prompt", outputs.get("soft_prompt"), Trigger)
_expect_trigger_once("row_normalization.hard_prompt", outputs.get("hard_prompt"), Trigger)
_expect_trigger_once("row_normalization.soft_caption", outputs.get("soft_caption"), Trigger)
_expect_trigger_once("row_normalization.hard_caption", outputs.get("hard_caption"), Trigger)
_expect_no_duplicate_comma_items("row_normalization.soft_negative", outputs.get("soft_negative"))
_expect_no_duplicate_comma_items("row_normalization.hard_negative", outputs.get("hard_negative"))
pair = row_normalization.normalize_pair_metadata(
{
"softcore_prompt": f"{Trigger}, {Trigger}, soft pair.",
"hardcore_prompt": f"{Trigger}, {Trigger}, hard pair.",
"softcore_caption": f"{Trigger}, {Trigger}, soft caption.",
"hardcore_caption": f"{Trigger}, {Trigger}, hard caption.",
"softcore_negative_prompt": "bad anatomy, bad anatomy",
"hardcore_negative_prompt": "bad anatomy, low quality, bad anatomy",
"softcore_partner_styling": {"outfits": ["partner outfit"], "pose": "partner pose"},
"hardcore_clothing_state": "structured hard clothing state",
"character_hardcore_clothing": ["Woman A custom hard clothing"],
"default_man_hardcore_clothing": ["Man A default hard clothing"],
"hardcore_detail_density": "dense",
"hardcore_position_config": {"family": "oral"},
"softcore_row": {
"prompt": f"{Trigger}, {Trigger}, embedded soft.",
"caption": f"{Trigger}, {Trigger}, embedded soft caption.",
"negative_prompt": "bad anatomy, bad anatomy",
},
"hardcore_row": {
"prompt": f"{Trigger}, {Trigger}, embedded hard.",
"caption": f"{Trigger}, {Trigger}, embedded hard caption.",
"negative_prompt": "low quality, bad anatomy, low quality",
},
},
active_trigger=Trigger,
)
_expect_trigger_once("row_normalization.pair.softcore_prompt", pair.get("softcore_prompt"), Trigger)
_expect_trigger_once("row_normalization.pair.hardcore_prompt", pair.get("hardcore_prompt"), Trigger)
_expect_trigger_once("row_normalization.pair.softcore_row.prompt", pair["softcore_row"].get("prompt"), Trigger)
_expect_trigger_once("row_normalization.pair.hardcore_row.caption", pair["hardcore_row"].get("caption"), Trigger)
_expect(
pair["softcore_row"].get("prompt") == pair.get("softcore_prompt"),
"Pair normalization left stale soft row prompt text",
)
_expect(
pair["hardcore_row"].get("caption") == pair.get("hardcore_caption"),
"Pair normalization left stale hard row caption text",
)
_expect(
pair["softcore_row"].get("softcore_partner_styling") == pair.get("softcore_partner_styling"),
"Pair normalization left stale soft side metadata",
)
_expect(
pair["hardcore_row"].get("hardcore_clothing_state") == pair.get("hardcore_clothing_state"),
"Pair normalization left stale hard clothing metadata",
)
_expect(
pair["hardcore_row"].get("default_man_hardcore_clothing") == pair.get("default_man_hardcore_clothing"),
"Pair normalization left stale hard default clothing metadata",
)
_expect_no_duplicate_comma_items("row_normalization.pair.soft_negative", pair.get("softcore_negative_prompt"))
_expect_no_duplicate_comma_items("row_normalization.pair.hard_row_negative", pair["hardcore_row"].get("negative_prompt"))
def smoke_row_rendering_policy() -> None:
_expect(pb.SINGLE_TEMPLATE == row_rendering.SINGLE_TEMPLATE, "Prompt builder single template should delegate to row_rendering")
_expect(
pb._format("Known {known}, missing {missing}", {"known": 7})
== row_rendering.format_template("Known {known}, missing {missing}", {"known": 7}),
"Prompt builder safe formatter should delegate to row_rendering",
)
_expect(
row_rendering.format_template("Known {known}, missing {missing}", {"known": 7}) == "Known 7, missing {missing}",
"Row rendering changed missing-field preservation",
)
_expect(
row_rendering.prompt_template_for({}, {}, {}, "woman") == row_rendering.SINGLE_TEMPLATE,
"Row rendering default woman template changed",
)
_expect(
row_rendering.prompt_template_for({}, {}, {}, "group") == row_rendering.GROUP_TEMPLATE,
"Row rendering default group template changed",
)
category_text = {
"name": "Category Label",
"negative_prompt": "category negative",
"positive_suffix": "category suffix",
"style": "category style",
"item_label": "Category Item",
}
subcategory_text = {
"negative_prompt": "subcategory negative",
"positive_suffix": "subcategory suffix",
"style": "subcategory style",
}
item_text = {
"negative_prompt": "item negative",
"style": "item style",
}
_expect(
pb._row_text_fields(category_text, subcategory_text, item_text)
== row_rendering.resolve_row_text_fields(category_text, subcategory_text, item_text),
"Prompt builder row text field wrapper should delegate to row_rendering",
)
text_fields = row_rendering.resolve_row_text_fields(category_text, subcategory_text, item_text)
_expect(text_fields.negative_prompt == "item negative", "Row text fields did not prefer item negative prompt")
_expect(text_fields.positive_suffix == "subcategory suffix", "Row text fields did not prefer subcategory suffix")
_expect(text_fields.style == "item style", "Row text fields did not prefer item style")
_expect(text_fields.item_label == "Category Item", "Row text fields did not fall back to category item label")
default_text_fields = row_rendering.resolve_row_text_fields({"name": "Default Category"}, {}, {})
_expect(default_text_fields.negative_prompt, "Row text fields lost default negative prompt")
_expect(default_text_fields.positive_suffix == row_rendering.GENERIC_POSITIVE_SUFFIX, "Row text fields lost suffix default")
_expect(default_text_fields.style == row_rendering.DEFAULT_STYLE, "Row text fields lost style default")
_expect(default_text_fields.item_label == "Default Category", "Row text fields lost category-name label default")
context = {
"trigger": Trigger,
"subject": "configured cast",
"subject_phrase": "configured adult cast",
"age": "adult",
"body": "varied",
"body_phrase": "varied",
"skin": "",
"hair": "",
"eyes": "",
"item_label": "Scene",
"item": "shared action",
"scene": "warm room",
"pose": "standing close",
"expression": "focused look",
"composition": "centered frame",
"composition_prompt": "vertical centered frame",
"positive_suffix": "clear readable bodies.",
"negative_prompt": "bad anatomy",
"cast_descriptors": "Woman A: adult woman; Man A: adult man",
}
rendered = row_rendering.render_prompt_caption(
item={},
subcategory={
"prompt_template": "Scene: {item}. Composition: {composition_prompt}. Avoid: {negative_prompt}.",
"caption_template": "{trigger}, {item}, {scene}",
},
category={},
subject_type="configured_cast",
context=context,
cast_descriptor_text="Woman A: adult woman; Man A: adult man",
pov_prompt_directive="First-person POV from Man A.",
)
prompt = rendered["prompt"]
caption = rendered["caption"]
_expect("Characters: Woman A: adult woman; Man A: adult man." in prompt, "Row rendering lost configured-cast descriptors")
_expect("First-person POV from Man A." in prompt, "Row rendering lost configured-cast POV directive")
_expect(
prompt.index("Characters:") < prompt.index("First-person POV") < prompt.index("Avoid:"),
"Row rendering did not insert configured-cast directives before negative prompt",
)
_expect(
caption.endswith("Woman A: adult woman; Man A: adult man"),
"Row rendering did not append descriptors to captions without descriptor placeholders",
)
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",
"subject_phrase": "configured adult cast",
"age": "21+ adults",
"body": "varied adult builds",
"body_phrase": "varied adult builds",
"figure": "balanced cast",
"skin": "warm skin tones",
"hair": "dark hair",
"eyes": "brown eyes",
"cast_summary": "one woman and one man",
"scene_kind": "configured_cast",
"women_count": "1",
"men_count": "1",
"person_count": "2",
}
count_adjustment = {"requested_women_count": 1, "requested_men_count": 1}
kwargs = {
"row_number": 2,
"start_index": 10,
"category": {"name": "Axis Test", "slug": "axis_test"},
"subcategory": {
"name": "Custom Scene",
"slug": "custom_scene",
"prompt_template": "Scene: {item}. Composition: {composition_prompt}. Avoid: {negative_prompt}.",
"caption_template": "{trigger}, {item}, {scene}",
},
"item": {"text": "shared structured action"},
"context": context,
"subject_type": "configured_cast",
"item_text": "shared structured action",
"item_name": "shared_action",
"item_axis_values": {"action_family": "test_action"},
"item_template_metadata": {"position_key": "test_position"},
"formatter_hints": {"krea": ["test_hint"]},
"item_label": "Scene",
"style": "clean test style",
"positive_suffix": "clear readable composition.",
"negative_prompt": "bad anatomy",
"scene_slug": "test_room",
"scene": "warm test room",
"pose": "standing close",
"expression": "focused look",
"shared_expression": "focused look",
"character_expressions": ["Woman A has focused look"],
"character_expression_text": "Woman A has focused look",
"expression_disabled": True,
"expression_intensity": 0.7,
"expression_intensity_source": "disabled",
"composition": "centered frame",
"source_composition": "centered frame",
"role_graph": "the visible partner stays centered",
"source_role_graph": "Man A stays centered",
"action_family": "test_action",
"position_family": "standing",
"position_key": "test_position",
"position_keys": ["test_position"],
"pov_character_labels": ["Man A"],
"cast_descriptors": ["Woman A: adult woman", "Man A: adult man"],
"cast_descriptor_text": "Woman A: adult woman; Man A: adult man",
"seed_config": {"content_seed": 123},
"hardcore_position_config": {"family": "standing"},
"location_config": {"location": "test_room"},
"composition_config": {"composition": "centered"},
"content_seed_axis": "pose",
"count_adjustment": count_adjustment,
"applied_profile": {"name": "profile_a"},
"profile_status": "applied",
"applied_slot": {"label": "Woman A"},
"slot_status": "applied",
"character_slots": [{"label": "Woman A"}, {"label": "Man A"}],
}
request = row_assembly.CustomRowAssemblyRequest(**kwargs)
row = row_assembly.assemble_custom_row(request)
delegated = pb._assemble_custom_row(request)
_expect(row == delegated, "Prompt builder row assembly wrapper should delegate without changing output")
_expect(request.content_seed_axis == "pose", "Row assembly request lost seed-axis metadata")
_expect(row["id"] == "sxcp_0011", "Row assembly changed row indexing")
_expect(row["batch"] == "batch_001", "Row assembly changed batch calculation")
_expect(row["source"] == "json_category", "Row assembly lost source marker")
_expect(row["figure"] == "balanced cast", "Row assembly lost figure metadata")
_expect(row["formatter_hints"] == {"krea": ["test_hint"]}, "Row assembly lost formatter hints")
_expect(row["cast_count_adjustment"] == count_adjustment, "Row assembly lost configured-cast count adjustment")
_expect(row["content_seed_axis"] == "pose", "Row assembly lost content seed axis")
_expect("POV participant: Man A" in row["prompt"], "Row assembly lost POV prompt directive")
_expect("Characters: Woman A: adult woman; Man A: adult man." in row["prompt"], "Row assembly lost cast descriptor insertion")
_expect(row["caption"].endswith("Woman A: adult woman; Man A: adult man"), "Row assembly lost caption descriptor append")
_expect(row["expression"] == "", "Disabled expression should clear row expression")
_expect(row["expression_enabled"] is False, "Disabled expression should mark expression disabled")
_expect(row["expression_intensity"] is None, "Disabled expression should clear intensity")
_expect(row["expression_intensity_source"] == "disabled", "Disabled expression should preserve disabled source")
def smoke_formatter_input_policy() -> None:
source_row = {
"prompt": "A simple adult portrait. Setting: quiet studio. Pose: standing calmly. Avoid: low quality.",
"caption": "adult portrait, quiet studio",
"negative_prompt": "low quality",
"subject_type": "woman",
"primary_subject": "woman",
"age": "25-year-old adult",
"body_phrase": "average figure",
"skin": "warm skin",
"hair": "dark hair",
"eyes": "brown eyes",
"item": "black dress",
"scene_text": "quiet studio",
"pose": "standing calmly",
"composition": "centered portrait",
"trigger": Trigger,
}
source_json = _json(source_row)
row, method = formatter_input.row_from_inputs(source_json, "", "auto")
_expect(method == "source_json", "Formatter input parser should read source JSON when metadata is empty")
_expect(row == source_row, "Formatter input parser changed parsed JSON row")
_expect(formatter_input.split_avoid("Prompt body. Avoid: blur, watermark") == ("Prompt body", "blur, watermark"), "Avoid split changed")
_expect(
formatter_input.prompt_field(source_row["prompt"], "Setting") == "quiet studio",
"Prompt field extraction changed",
)
_expect(
formatter_input.row_value({"prompt": source_row["prompt"]}, "scene_text", ("Setting",)) == "quiet studio",
"Row value prompt fallback changed",
)
_expect(
krea_formatter.PROMPT_FIELD_LABELS is formatter_input.DEFAULT_PROMPT_FIELD_LABELS,
"Krea formatter field-label inventory should delegate to formatter_input",
)
_expect(
sdxl_formatter.PROMPT_FIELD_LABELS is formatter_input.DEFAULT_PROMPT_FIELD_LABELS,
"SDXL formatter field-label inventory should delegate to formatter_input",
)
_expect(
caption_naturalizer.PROMPT_FIELD_LABELS is formatter_input.DEFAULT_PROMPT_FIELD_LABELS,
"Caption formatter field-label inventory should delegate to formatter_input",
)
labeled_prompt = "Sexual scene: close-contact action. Camera control: side view. Composition: centered frame."
_expect(
formatter_input.prompt_field(labeled_prompt, "Sexual scene") == "close-contact action",
"Shared formatter field-label inventory lost Sexual scene parsing",
)
_expect(
formatter_input.prompt_field(labeled_prompt, "Camera control") == "side view",
"Shared formatter field-label inventory lost Camera control parsing",
)
stripped_labels = formatter_input.strip_prompt_field_labels(
"Characters: woman. Erotic outfit: sheer dress. Camera: side view."
)
_expect("Characters:" not in stripped_labels, "Shared label stripper did not remove Characters label")
_expect("Erotic outfit:" not in stripped_labels, "Shared label stripper did not remove Erotic outfit label")
_expect("Camera:" not in stripped_labels, "Shared label stripper did not remove Camera label")
_expect(krea_formatter._clean("a b , c") == formatter_input.clean_text("a b , c"), "Krea clean helper is not delegated")
_expect(sdxl_formatter._clean("a b , c") == formatter_input.clean_text("a b , c"), "SDXL clean helper is not delegated")
_expect(
sdxl_formatter._strip_prompt_field_labels("Characters: woman. Camera: side view.") == "woman. side view.",
"SDXL label stripper should delegate to formatter_input",
)
_expect(caption_naturalizer._clean_text("a b , c") == formatter_input.clean_text("a b , c"), "Caption clean helper is not delegated")
_expect(krea_formatter._strip_trigger(f"{Trigger}, prompt text", False) == "prompt text", "Krea trigger stripping changed")
_expect(sdxl_formatter._strip_trigger(f"{SdxlTrigger}, prompt text", False) == "prompt text", "SDXL trigger stripping changed")
_expect(caption_naturalizer._remove_trigger(Trigger, Trigger) == "", "Caption exact-trigger removal changed")
krea = krea_formatter.format_krea2_prompt(source_json, input_hint="auto")
sdxl = sdxl_formatter.format_sdxl_prompt(source_json, input_hint="auto", trigger=SdxlTrigger, prepend_trigger=True)
caption, caption_method = caption_naturalizer.naturalize_caption(source_json, input_hint="auto", trigger=Trigger)
_expect(krea.get("method", "").startswith("source_json:krea2("), "Krea formatter did not use shared source JSON parsing")
_expect(sdxl.get("method", "").startswith("source_json:sdxl("), "SDXL formatter did not use shared source JSON parsing")
_expect(caption_method.startswith("source_json:metadata("), "Caption naturalizer did not use shared source JSON parsing")
_expect_text("formatter_input.krea_prompt", krea.get("krea_prompt"), 20)
_expect_text("formatter_input.sdxl_prompt", sdxl.get("sdxl_prompt"), 20)
_expect_text("formatter_input.caption", caption, 20)
fallback_sdxl = sdxl_formatter.format_sdxl_prompt(
"Characters: woman. Erotic outfit: sheer dress. Camera: side view. Avoid: blur",
input_hint="prompt",
style_preset="none",
quality_preset="none",
trigger=SdxlTrigger,
prepend_trigger=False,
)
fallback_prompt = fallback_sdxl.get("sdxl_prompt", "")
_expect("Characters:" not in fallback_prompt, "SDXL fallback leaked Characters label")
_expect("Erotic outfit:" not in fallback_prompt, "SDXL fallback leaked Erotic outfit label")
_expect("Camera:" not in fallback_prompt, "SDXL fallback leaked Camera label")
_expect("blur" in fallback_sdxl.get("negative_prompt", ""), "SDXL fallback lost Avoid negative text")
def smoke_formatter_cast_policy() -> None:
descriptor = (
"Woman A / primary creator: 25-year-old adult woman, average figure, warm skin, dark hair; "
"Man A: 40-year-old adult man, average figure, tan skin, short dark hair"
)
entries = [
("Woman A", "25-year-old adult woman, average figure, warm skin, dark hair"),
("Man A", "40-year-old adult man, average figure, tan skin, short dark hair"),
]
_expect(krea_cast.cast_entries(descriptor) == entries, "Shared cast entry parser changed")
_expect(caption_naturalizer._cast_entries(descriptor) == entries, "Caption cast parser should delegate to shared cast policy")
_expect(krea_cast.cast_labels(descriptor) == ["Woman A", "Man A"], "Shared cast label parser changed")
_expect(
caption_naturalizer._cast_labels(descriptor) == krea_cast.cast_labels(descriptor),
"Caption cast labels should delegate to shared cast policy",
)
natural = krea_cast.natural_cast_descriptor_text(descriptor)
_expect(natural.startswith("A 25-year-old adult woman"), "Shared natural cast descriptor text changed")
_expect(caption_naturalizer._natural_cast_descriptor_text(descriptor) == natural, "Caption cast descriptor text should delegate")
_expect(
krea_cast.natural_label_text("Woman A faces Man A.", ["Woman A", "Man A"]) == "The woman faces the man.",
"Krea natural label text should keep sentence capitalization",
)
_expect(
caption_naturalizer._natural_label_text("Woman A faces Man A.", ["Woman A", "Man A"]) == "the woman faces the man.",
"Caption natural label text should preserve previous lowercase inline behavior",
)
def smoke_caption_policy() -> None:
_expect(
caption_naturalizer.STYLE_TAILS is caption_policy.STYLE_TAILS,
"Caption naturalizer style tails should delegate to caption_policy",
)
_expect(
caption_naturalizer.ITEM_LABELS is caption_policy.ITEM_LABELS,
"Caption naturalizer item labels should delegate to caption_policy",
)
_expect(
caption_naturalizer.ACTION_FAMILY_CAPTION_LABELS is caption_policy.ACTION_FAMILY_CAPTION_LABELS,
"Caption naturalizer action labels should delegate to caption_policy",
)
_expect(caption_policy.normalize_detail_level("bad") == "balanced", "Caption invalid detail fallback changed")
_expect(caption_policy.keep_style_terms("keep_style_terms") is True, "Caption style policy keep flag changed")
_expect(caption_policy.detail_allows("concise") is False, "Caption concise detail gate changed")
_expect(caption_policy.detail_allows("dense", dense_only=True) is True, "Caption dense-only gate changed")
_expect("training_concise" in caption_policy.caption_profile_choices(), "Caption profile choices lost training_concise")
_expect(
caption_policy.normalize_caption_profile("bad") == caption_policy.CAPTION_PROFILE_DEFAULT,
"Caption invalid profile fallback changed",
)
_expect(
caption_policy.apply_caption_profile(
"training_dense",
detail_level="concise",
style_policy="keep_style_terms",
include_trigger=False,
)
== ("dense", "drop_style_tail", True),
"Caption training_dense profile overrides changed",
)
_expect(
caption_policy.apply_caption_profile(
"manual_controls",
detail_level="concise",
style_policy="keep_style_terms",
include_trigger=False,
)
== ("concise", "keep_style_terms", False),
"Caption manual profile should preserve explicit controls",
)
style_tail = caption_policy.STYLE_TAILS[0]
_expect(
caption_policy.strip_style_tail(f"caption body{style_tail}") == "caption body",
"Caption style-tail stripping changed",
)
_expect(
caption_naturalizer._strip_style_tail(f"caption body{style_tail}") == "caption body",
"Caption naturalizer style-tail wrapper should delegate",
)
_expect(
caption_policy.normalize_composition("vertical centered body frame") == "centered body frame",
"Caption composition normalization changed",
)
_expect(
caption_policy.clean_clothing("silk dress, fashion editorial styling") == "silk dress",
"Caption clothing cleanup changed",
)
row = {"action_family": "oral", "position_family": ""}
_expect(caption_policy.metadata_action_label(row) == "oral action", "Caption action-family label changed")
row = {"action_family": "oral", "position_family": "Anal"}
_expect(caption_naturalizer._metadata_action_label(row) == "anal action", "Caption position-family label priority changed")
browsing_caption, browsing_method = caption_naturalizer.naturalize_caption(
"woman, red dress, studio",
caption_profile="browsing",
include_trigger=True,
)
_expect(not browsing_caption.startswith(Trigger), "Caption browsing profile should disable trigger by default")
_expect(browsing_method == "text(fallback)", "Caption browsing profile changed fallback method")
def _expect_caption_route_parity(
name: str,
row: dict[str, Any],
route_builder: Callable[
[caption_metadata_routes.CaptionMetadataRouteRequest, caption_metadata_routes.CaptionMetadataRouteDependencies],
caption_metadata_routes.CaptionMetadataRoute | None,
],
wrapper: Callable[[dict[str, Any], str, bool], tuple[str, str] | None],
expected_method: str,
) -> None:
request = caption_naturalizer._caption_metadata_route_request(row, "balanced", False)
deps = caption_naturalizer._caption_metadata_route_dependencies()
typed_route = route_builder(request, deps)
legacy_route = wrapper(row, "balanced", False)
_expect(typed_route is not None, f"{name} typed caption metadata route did not match")
assert typed_route is not None
_expect(
typed_route.as_tuple() == legacy_route,
f"{name} typed caption metadata route should match legacy wrapper output",
)
_expect(typed_route.method == expected_method, f"{name} caption route method changed")
_expect_text(f"{name}.caption_route", typed_route.prose, 20)
def smoke_caption_metadata_routes() -> None:
single = {
"primary_subject": "woman",
"age_band": "25-year-old adult",
"body_phrase": "slim figure",
"skin": "fair skin",
"hair": "long blonde hair",
"eyes": "blue eyes",
"item": "silk dress",
"pose": "standing beside a window",
"scene_text": "quiet studio with warm daylight",
"expression": "soft smile",
"composition": "vertical centered portrait",
}
_expect_caption_route_parity(
"caption_route_single",
single,
caption_metadata_routes.single_from_row_result,
caption_naturalizer._single_from_row,
"metadata(single)",
)
couple = {
"primary_subject": "a woman and a man",
"subject_phrase": "woman and man",
"age": "25-year-old adult and 40-year-old adult",
"body": "slim and average builds",
"item": "Partner A wears black dress; Partner B wears dark shirt",
"pose": "standing close together",
"scene_text": "private lounge with soft lamps",
"expression": "shared confident gaze",
"composition": "two-person editorial frame",
}
_expect_caption_route_parity(
"caption_route_couple",
couple,
caption_metadata_routes.couple_from_row_result,
caption_naturalizer._couple_from_row,
"metadata(couple)",
)
configured = _fixture_hardcore_row()
_expect_caption_route_parity(
"caption_route_configured_cast",
configured,
caption_metadata_routes.configured_cast_from_row_result,
caption_naturalizer._configured_cast_from_row,
"metadata(configured_cast)",
)
group = {
"primary_subject": "group scene",
"subject_phrase": "three adult friends",
"age": "late 20s adults",
"item": "coordinated evening outfits",
"scene_text": "rooftop lounge with city lights",
"expression": "relaxed shared smiles",
"composition": "wide group frame",
}
_expect_caption_route_parity(
"caption_route_group",
group,
caption_metadata_routes.group_or_layout_from_row_result,
caption_naturalizer._group_or_layout_from_row,
"metadata(group_layout)",
)
pair = pb.build_insta_of_pair(
row_number=1,
start_index=1,
seed=3511,
ethnicity="any",
figure="random",
no_plus_women=False,
no_black=False,
trigger=Trigger,
prepend_trigger_to_prompt=True,
options_json=_insta_options(hardcore_clothing_continuity="partially_removed"),
character_cast=_character_cast(),
hardcore_position_config=_action_filter("penetration_only"),
)
_expect_pair(pair, "caption_route_pair")
_expect_caption_route_parity(
"caption_route_insta_pair",
pair,
caption_metadata_routes.insta_of_pair_from_row_result,
caption_naturalizer._insta_of_pair_from_row,
"metadata(insta_of_pair)",
)
def smoke_sdxl_presets_policy() -> None:
_expect(
sdxl_formatter.SDXL_STYLE_PRESETS is sdxl_presets.SDXL_STYLE_PRESETS,
"SDXL formatter style presets should delegate to sdxl_presets",
)
_expect(
sdxl_formatter.SDXL_QUALITY_PRESETS is sdxl_presets.SDXL_QUALITY_PRESETS,
"SDXL formatter quality presets should delegate to sdxl_presets",
)
_expect(
sdxl_formatter.SDXL_FORMATTER_PROFILES is sdxl_presets.SDXL_FORMATTER_PROFILES,
"SDXL formatter profiles should delegate to sdxl_presets",
)
_expect(
sdxl_formatter.SDXL_ACTION_FAMILY_TAGS is sdxl_presets.SDXL_ACTION_FAMILY_TAGS,
"SDXL formatter action-family tags should delegate to sdxl_presets",
)
_expect("sdxl_photo" in sdxl_presets.sdxl_formatter_profile_choices(), "SDXL profile choices lost sdxl_photo")
_expect("flat_vector_pony" in sdxl_presets.sdxl_style_preset_choices(), "SDXL style preset choices lost default")
_expect("pony_high" in sdxl_presets.sdxl_quality_preset_choices(), "SDXL quality preset choices lost default")
_expect(
sdxl_presets.normalize_formatter_profile("bad") == sdxl_presets.DEFAULT_FORMATTER_PROFILE,
"SDXL invalid profile fallback changed",
)
_expect(sdxl_presets.normalize_style_preset("bad") == sdxl_presets.DEFAULT_STYLE_PRESET, "SDXL invalid style fallback changed")
_expect(sdxl_presets.normalize_quality_preset("bad") == sdxl_presets.DEFAULT_QUALITY_PRESET, "SDXL invalid quality fallback changed")
_expect(
sdxl_presets.apply_formatter_profile(
"sdxl_photo",
style_preset="flat_vector_pony",
quality_preset="pony_high",
)
== ("photographic", "sdxl_high"),
"SDXL photo profile overrides changed",
)
_expect(
sdxl_presets.apply_formatter_profile(
"manual_controls",
style_preset="flat_vector",
quality_preset="none",
)
== ("flat_vector", "none"),
"SDXL manual profile should preserve explicit controls",
)
row = _fixture_hardcore_row(
action_family="oral",
position_family="oral",
position_key="kneeling_oral",
position_keys=["kneeling_oral"],
)
tags = sdxl_formatter._metadata_family_tags(row)
_expect("oral sex" in tags, "SDXL metadata family tags lost oral family tag")
_expect("kneeling oral" in tags, "SDXL metadata family tags lost position key tag")
formatted = sdxl_formatter.format_sdxl_prompt(
_json(row),
input_hint="auto",
style_preset="bad",
quality_preset="bad",
trigger=SdxlTrigger,
prepend_trigger=True,
)
_expect_trigger_once("sdxl_presets.formatted_prompt", formatted.get("sdxl_prompt"), SdxlTrigger)
_expect("Flat vector" in formatted.get("sdxl_prompt", ""), "SDXL invalid style did not fall back to default preset")
_expect("score_9" in formatted.get("sdxl_prompt", ""), "SDXL invalid quality did not fall back to default preset")
profiled = sdxl_formatter.format_sdxl_prompt(
_json(row),
input_hint="auto",
formatter_profile="sdxl_photo",
style_preset="flat_vector_pony",
quality_preset="pony_high",
trigger=SdxlTrigger,
prepend_trigger=True,
)
profiled_prompt = profiled.get("sdxl_prompt", "")
_expect("realistic photo" in profiled_prompt, "SDXL photo profile did not apply photographic style")
_expect("score_9" not in profiled_prompt, "SDXL photo profile should switch away from Pony score quality tail")
def smoke_sdxl_tag_routes() -> None:
row = _fixture_hardcore_row(
formatter_hints={
"all": ["shared route anchor"],
"sdxl": ["sdxl route tag"],
}
)
deps = sdxl_formatter._sdxl_tag_route_dependencies()
typed_row = sdxl_tag_routes.row_core_tags_result(
sdxl_tag_routes.SDXLRowTagRequest(row, 1.29),
deps,
)
_expect(
typed_row.tags == sdxl_formatter._row_core_tags(row, 1.29),
"Typed SDXL row tag route should match legacy wrapper output",
)
_expect("sdxl route tag" in typed_row.as_text(), "Typed SDXL row tag route lost route-specific formatter hint")
pair = pb.build_insta_of_pair(
row_number=1,
start_index=1,
seed=3511,
ethnicity="any",
figure="random",
no_plus_women=False,
no_black=False,
trigger=Trigger,
prepend_trigger_to_prompt=True,
options_json=_insta_options(hardcore_clothing_continuity="partially_removed"),
character_cast=_character_cast(),
hardcore_position_config=_action_filter("penetration_only"),
)
_expect_pair(pair, "sdxl_tag_routes_pair")
soft_row = pair.get("softcore_row") if isinstance(pair.get("softcore_row"), dict) else {}
hard_row = pair.get("hardcore_row") if isinstance(pair.get("hardcore_row"), dict) else {}
typed_soft = sdxl_tag_routes.soft_tags_result(
sdxl_tag_routes.SDXLPairTagRequest(soft_row, pair, 1.29),
deps,
)
typed_hard = sdxl_tag_routes.hard_tags_result(
sdxl_tag_routes.SDXLPairTagRequest(hard_row, pair, 1.29),
deps,
)
_expect(
typed_soft.as_text() == sdxl_formatter._soft_tags(soft_row, pair, 1.29),
"Typed SDXL pair soft tag route should match legacy wrapper output",
)
_expect(
typed_hard.as_text() == sdxl_formatter._hard_tags(hard_row, pair, 1.29),
"Typed SDXL pair hard tag route should match legacy wrapper output",
)
formatted = sdxl_formatter.format_sdxl_prompt(
"",
metadata_json=_json(pair),
target="hardcore",
trigger=SdxlTrigger,
prepend_trigger=True,
)
_expect("sdxl(insta_of_pair)" in formatted.get("method", ""), "SDXL pair formatter route changed method")
def smoke_hardcore_position_config_policy() -> None:
_expect(
pb.HARDCORE_POSITION_FAMILY_CHOICES is hardcore_position_config.HARDCORE_POSITION_FAMILY_CHOICES,
"Prompt builder hardcore position family choices are not delegated",
)
_expect("outercourse_only" in hardcore_position_config.hardcore_position_focus_choices(), "Hardcore focus choices lost outercourse_only")
_expect("boobjob" in hardcore_position_config.hardcore_position_key_choices(), "Hardcore position keys lost boobjob")
base = json.loads(
pb.build_hardcore_position_pool_json(
combine_mode="replace",
family="oral",
selected_positions=["standing", "bad value", "standing"],
)
)
_expect(base.get("enabled") is True, "Hardcore position pool should enable config")
_expect(base.get("family") == "oral", "Hardcore position pool lost family")
_expect(base.get("positions") == ["standing"], "Hardcore position normalization changed")
_expect(base.get("require_position") is True, "Hardcore position pool should require selected position")
added = json.loads(
hardcore_position_config.build_hardcore_position_pool_json(
hardcore_position_config=base,
combine_mode="add",
family="any",
selected_positions=["kneeling", "standing"],
)
)
_expect(added.get("positions") == ["standing", "kneeling"], "Hardcore position add merge changed")
filtered = json.loads(
pb.build_hardcore_action_filter_json(
hardcore_position_config=added,
focus="outercourse_only",
allow_toys=False,
allow_double=False,
allow_penetration=True,
allow_foreplay=True,
allow_interaction=True,
allow_manual=True,
allow_oral=True,
allow_outercourse=True,
allow_anal=True,
allow_climax=True,
)
)
_expect(filtered.get("family") == "outercourse", "Hardcore action focus did not set outercourse family")
_expect(filtered.get("allow_oral") is False, "Hardcore outercourse focus should disable oral")
_expect(filtered.get("allow_penetration") is False, "Hardcore outercourse focus should disable penetration")
_expect("outercourse_sex" in hardcore_position_config.hardcore_allowed_subcategory_slugs(filtered), "Allowed subcategories lost outercourse")
_expect("oral_sex" not in hardcore_position_config.hardcore_allowed_subcategory_slugs(filtered), "Allowed subcategories should exclude oral")
action_only = json.loads(
hardcore_position_config.build_hardcore_action_filter_json(
focus="outercourse_only",
allow_toys=False,
allow_double=False,
allow_penetration=True,
allow_foreplay=True,
allow_interaction=True,
allow_manual=True,
allow_oral=True,
allow_outercourse=True,
allow_anal=True,
allow_climax=True,
)
)
action_axis = hardcore_position_config.filter_hardcore_axis(
"outer_act",
["boobjob body contact", "blowjob oral sex", "vaginal penetration"],
action_only,
)
_expect(action_axis == ["boobjob body contact"], "Hardcore action filter policy did not block disabled oral/penetration text")
position_filtered = hardcore_position_config.apply_hardcore_position_config_to_subcategory(
{
"slug": "oral_sex",
"item_templates": [
{"template": "oral contact in {position}"},
{"template": "oral sex without a position axis"},
{"template": "unsupported static template"},
],
"item_axes": {
"position": ["standing oral position", "kneeling oral position"],
"oral_act": ["blowjob", "cunnilingus"],
},
},
base,
)
_expect(
position_filtered["item_templates"] == [{"template": "oral contact in {position}"}],
"Hardcore position policy did not filter templates by selected position requirements",
)
_expect(
position_filtered["item_axes"]["position"] == ["standing oral position"],
"Hardcore position policy did not filter position axes by selected keys",
)
filtered_categories = hardcore_position_config.filter_hardcore_categories_for_position(
[
{
"name": "Hardcore sexual poses",
"slug": "hardcore_sexual_poses",
"subcategories": [{"slug": "oral_sex"}, {"slug": "outercourse_sex"}],
},
{"name": "Casual clothes", "slug": "casual_clothes", "subcategories": [{"slug": "tops"}]},
],
filtered,
1,
1,
lambda _entry, _women, _men: True,
)
_expect(
[entry["slug"] for entry in filtered_categories[0]["subcategories"]] == ["outercourse_sex"],
"Hardcore category filter policy did not remove disallowed subcategories",
)
_expect(filtered_categories[1]["slug"] == "casual_clothes", "Hardcore category filter should preserve non-hardcore categories")
keys = pb._hardcore_position_keys("woman on all fours from behind", axis_values={"position": "doggy"})
_expect(keys == ["doggy"], "Hardcore position key detection changed")
source_family = hardcore_position_config.hardcore_source_position_family({"slug": "manual_stimulation"}, filtered)
_expect(source_family == "manual", "Hardcore source family lookup changed")
item_text, item_name, axis_values, template_metadata = pb._compose_item(
random.Random(42),
{},
{
"name": "Template metadata route",
"item_templates": [
{
"template": "{act} in {position}",
"action_family": "oral",
"position_family": "oral",
"position_keys": ["kneeling", "open_thighs"],
"formatter_hint": {
"krea2": "keep mouth contact readable",
"sdxl": ["oral contact", "kneeling oral"],
"training_caption": "oral contact caption detail",
},
}
],
"item_axes": {
"act": ["mouth contact"],
"position": ["kneeling oral position"],
},
},
"Template metadata route",
women_count=1,
men_count=1,
)
_expect(item_text == "mouth contact in kneeling oral position", "Template metadata route changed composed item text")
_expect(item_name == "Template metadata route", "Template metadata route changed item name")
_expect(axis_values == {"act": "mouth contact", "position": "kneeling oral position"}, "Template metadata route lost axis values")
_expect(template_metadata.get("action_family") == "oral", "Template metadata route lost action family")
_expect(pb._template_position_family(template_metadata) == "oral", "Template metadata route lost position family")
_expect(pb._template_position_keys(template_metadata) == ["kneeling", "open_thighs"], "Template metadata route lost position keys")
_expect(pb._template_action_family(template_metadata) == "oral", "Template metadata route lost normalized action family")
formatter_hints = pb._template_formatter_hints(template_metadata)
_expect(formatter_hints.get("krea") == ["keep mouth contact readable"], "Template metadata route lost Krea formatter hint")
_expect(formatter_hints.get("sdxl") == ["oral contact", "kneeling oral"], "Template metadata route lost SDXL formatter hints")
_expect(formatter_hints.get("caption") == ["oral contact caption detail"], "Template metadata route lost caption formatter hint")
route_row = {
"action_family": "penetrative",
"position_family": "Oral",
"position_keys": ["spread leg oral", "bad key"],
"position_key": "open thighs",
"formatter_hints": {"all": ["shared formatter cue"], "training_caption": ["caption formatter cue"]},
}
_expect(route_metadata.row_action_family(route_row) == "penetration", "Route metadata action normalization changed")
_expect(route_metadata.row_position_family(route_row) == "oral", "Route metadata position-family normalization changed")
_expect(
route_metadata.row_position_keys(route_row) == ["spread_leg_oral", "open_thighs"],
"Route metadata position-key normalization changed",
)
_expect(
route_metadata.row_position_keys({"position_keys": ["kneeling_oral"]}, include_unknown=True) == ["kneeling_oral"],
"Route metadata legacy position-key passthrough changed",
)
_expect(
route_metadata.row_formatter_hints(route_row, "caption") == ["shared formatter cue", "caption formatter cue"],
"Route metadata formatter hint routing changed",
)
route_hints = category_template_metadata.formatter_hints_for_route(
{"formatter_hints": {"all": ["shared formatter cue"], "krea2": ["krea formatter cue"]}},
"krea2",
)
_expect(route_hints == ["shared formatter cue", "krea formatter cue"], "Formatter hint route resolver changed")
_expect(
pb._template_action_family(template_metadata) == category_template_metadata.template_action_family(template_metadata),
"Prompt builder template action policy should delegate",
)
_expect(
category_template_metadata.template_metadata_errors(template_metadata) == [],
"Valid template metadata should not report audit errors",
)
invalid_metadata = {
"action_family": "bad_action",
"position_family": "bad_family",
"position_keys": ["kneeling", "bad_position"],
"formatter_hint": {"bad_route": 9, "sdxl": ["ok", ""]},
}
invalid_errors = category_template_metadata.template_metadata_errors(invalid_metadata)
_expect(any("bad_action" in error for error in invalid_errors), "Template metadata validation missed bad action")
_expect(any("bad_family" in error for error in invalid_errors), "Template metadata validation missed bad family")
_expect(any("bad_position" in error for error in invalid_errors), "Template metadata validation missed bad position key")
_expect(any("bad_route" in error for error in invalid_errors), "Template metadata validation missed bad formatter route")
_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")
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(
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")
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(
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")
_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:
categories = category_library.load_category_library()
_expect(len(categories) >= 3, "category library should load JSON categories")
category, subcategory, women_count, men_count = category_library.find_subcategory(
categories,
"custom_random",
"Hardcore sexual poses / Oral sex",
random.Random(101),
random.Random(102),
women_count=1,
men_count=1,
)
_expect(category.get("slug") == "hardcore_sexual_poses", "exact category lookup selected wrong category")
_expect(subcategory.get("slug") == "oral_sex", "exact subcategory lookup selected wrong subcategory")
_expect((women_count, men_count) == (1, 1), "exact subcategory lookup changed compatible cast counts")
item = category_library.compatible_entries(list(subcategory.get("items") or []), women_count, men_count)[0]
scenes = category_library.configured_pool(
category,
subcategory,
item,
"scenes",
"scene_pools",
category_library.load_scene_pool_library(),
"inherit_scenes",
)
expressions = category_library.configured_pool(
category,
subcategory,
item,
"expressions",
"expression_pools",
category_library.load_expression_pool_library(),
"inherit_expressions",
)
compositions = category_library.configured_pool(
category,
subcategory,
item,
"compositions",
"composition_pools",
category_library.load_composition_pool_library(),
"inherit_compositions",
)
_expect(scenes, "category inheritance did not resolve scenes")
_expect(expressions, "category inheritance did not resolve expressions")
_expect(compositions, "category inheritance did not resolve compositions")
_expect(any("oral" in _clean_key(entry.get("prompt") if isinstance(entry, dict) else entry) for entry in scenes), "oral scene pool did not contribute")
location_override = {"enabled": True, "apply_mode": "replace", "scene_entries": ["custom scene"]}
composition_override = {"enabled": True, "apply_mode": "replace", "composition_entries": ["custom composition"]}
_expect(
pb._scene_pool(category, subcategory, item, "configured_cast", location_override)
== row_pools.scene_pool(category, subcategory, item, "configured_cast", location_override),
"Prompt builder scene pool should delegate to row_pools",
)
_expect(
pb._expression_pool(category, subcategory, item) == row_pools.expression_pool(category, subcategory, item),
"Prompt builder expression pool should delegate to row_pools",
)
_expect(
pb._pose_pool(category, subcategory, item, "couple", "standard") == row_pools.pose_pool(category, subcategory, item, "couple", "standard"),
"Prompt builder pose pool should delegate to row_pools",
)
_expect(
pb._composition_pool(category, subcategory, item, "configured_cast", composition_override)
== row_pools.composition_pool(category, subcategory, item, "configured_cast", composition_override),
"Prompt builder composition pool should delegate to row_pools",
)
def smoke_hardcore_category_routes() -> None:
cast = _character_cast()
cases = [
("hardcore_penetration", "Penetrative sex", "penetration_only", "penetrative", {"penetration", "default"}, "penetrative sex", "penetrative action"),
("hardcore_oral", "Oral sex", "oral_only", "oral", {"oral"}, "oral sex", "oral action"),
("hardcore_manual", "Manual stimulation", "manual_only", "manual", {"foreplay", "outercourse"}, "manual stimulation", "manual action"),
("hardcore_outercourse", "Outercourse and genital teasing", "outercourse_only", "outercourse", {"outercourse"}, "outercourse", "non-penetrative action"),
("hardcore_foreplay", "Foreplay and teasing", "foreplay_only", "foreplay", {"foreplay"}, "foreplay", "foreplay action"),
("hardcore_aftercare", "Aftercare and cleanup", "interaction_only", "interaction", {"foreplay"}, "interaction", "interaction beat"),
]
for index, (name, subcategory, focus, position_family, action_families, sdxl_tag, caption_label) in enumerate(cases, start=1101):
row = _prompt_row(
name=name,
category="Hardcore sexual poses",
subcategory=subcategory,
seed=index,
character_cast=cast,
women_count=1,
men_count=1,
hardcore_position_config=_action_filter(focus),
)
_expect_custom_row(row, name)
_expect(row.get("subject_type") == "configured_cast", f"{name} should use configured cast")
_expect(row.get("position_family") == position_family, f"{name} position_family mismatch: {row.get('position_family')}")
_expect(row.get("action_family") in action_families, f"{name} action_family mismatch: {row.get('action_family')}")
_expect(isinstance(row.get("position_keys"), list), f"{name} position_keys missing")
_expect_formatter_outputs(row, name, target="single")
sdxl = sdxl_formatter.format_sdxl_prompt("", metadata_json=_json(row), target="single", trigger=SdxlTrigger, prepend_trigger=True)
_expect(sdxl_tag in (sdxl.get("sdxl_prompt") or "").lower(), f"{name} SDXL prompt did not include family tag {sdxl_tag!r}")
caption, _method = caption_naturalizer.naturalize_caption("", metadata_json=_json(row), trigger=Trigger, include_trigger=True)
_expect(caption_label in caption.lower(), f"{name} caption did not include family label {caption_label!r}")
annotated_row = None
for seed in range(1801, 1841):
row = _prompt_row(
name="hardcore_annotated_template",
category="Hardcore sexual poses",
subcategory="Oral sex",
seed=seed,
character_cast=cast,
women_count=1,
men_count=1,
hardcore_position_config=_action_filter("oral_only"),
)
if row.get("item_template_metadata"):
annotated_row = row
break
_expect(annotated_row is not None, "No annotated item template reached generated row in deterministic seed window")
if annotated_row is not None:
_expect(annotated_row.get("action_family") == "oral", "Annotated item template action_family did not reach row")
_expect(annotated_row.get("position_family") == "oral", "Annotated item template position_family did not reach row")
_expect(annotated_row.get("item_template_metadata", {}).get("action_family") == "oral", "Annotated item metadata missing in row")
def smoke_krea_close_foreplay_route() -> None:
row = _prompt_row(
name="krea_close_foreplay_route",
category="Hardcore sexual poses",
subcategory="Foreplay and teasing",
seed=3401,
character_cast=_character_cast(),
women_count=1,
men_count=1,
hardcore_position_config=_action_filter("foreplay_only"),
)
_expect_custom_row(row, "krea_close_foreplay_route")
krea = krea_formatter.format_krea2_prompt("", metadata_json=_json(row), target="single")
prompt = _expect_text("krea_close_foreplay_route.krea_prompt", krea.get("krea_prompt"), 40)
lower = prompt.lower()
_expect("metadata" in krea.get("method", ""), "close foreplay route did not use metadata")
_expect("role graph:" not in lower, "close foreplay leaked raw role label")
_expect("foreplay action:" not in lower, "close foreplay leaked raw item label")
_expect("on against" not in lower, "close foreplay kept invalid surface grammar")
_expect(
any(term in lower for term in ("clothing", "hands", "kiss", "bodies press", "body contact")),
"close foreplay lost close-contact action wording",
)
_expect_formatter_outputs(row, "krea_close_foreplay_route", target="single")
def _insta_options(**overrides: Any) -> str:
options = pb.build_insta_of_options_json(
softcore_cast="same_as_hardcore",
hardcore_cast="couple",
hardcore_women_count=1,
hardcore_men_count=1,
softcore_level="lingerie_tease",
hardcore_level="hardcore",
platform_style="hybrid",
continuity="same_creator_same_room",
hardcore_clothing_continuity="explicit_nude",
softcore_camera_mode="standard",
hardcore_camera_mode="standard",
camera_detail="compact",
hardcore_detail_density="balanced",
)
data = json.loads(options)
data.update(overrides)
return _json(data)
def smoke_pair_options_policy() -> None:
_expect(
pb.INSTA_OF_SOFTCORE_OUTFITS is pb.pair_options.INSTA_OF_SOFTCORE_OUTFITS,
"prompt_builder should delegate Insta/OF softcore outfit policy to pair_options",
)
_expect(
pb.HARDCORE_DETAIL_DENSITY_CHOICES is pb.pair_options.HARDCORE_DETAIL_DENSITY_CHOICES,
"prompt_builder should delegate hardcore detail density choices to pair_options",
)
_expect(
pb.pair_options.hardcore_detail_directive("compact").startswith("Use one compact"),
"compact hardcore detail density should have a compact directive",
)
_expect(
pb.pair_options.hardcore_detail_directive("dense").startswith("Use dense"),
"dense hardcore detail density should have a dense directive",
)
_expect(
pb.pair_options.hardcore_detail_directive("balanced") == "",
"balanced hardcore detail density should not add a directive",
)
_expect(
pb.pair_options.hardcore_detail_directive("bad") == "",
"invalid hardcore detail density directive should be empty",
)
_expect(
"scattered clothes" not in pair_clothing.body_exposure_scene_text(
"mirror corner, scattered clothes, outfit-check framing"
),
"Pair clothing body exposure scene cleanup should remove clothing clutter",
)
_expect(
"creator-shot" in pair_clothing.body_exposure_scene_text("outfit-check framing"),
"Pair clothing body exposure scene cleanup should replace outfit-check wording",
)
_expect(
pair_clothing.softcore_outfit_sentence("Man A", "wears hoodie and joggers")
== "Man A wears hoodie and joggers",
"Pair clothing softcore outfit sentence formatting changed",
)
_expect(
pair_clothing.hardcore_clothing_sentence("Woman A", "fully nude")
== "Woman A's body is fully exposed, bare skin unobstructed",
"Pair clothing hardcore fully nude sentence formatting changed",
)
_expect(
pair_clothing.character_hardcore_clothing_entries(
{
"Woman A": {"hardcore_clothing": "fully nude"},
"Man A": {"hardcore_clothing": "wears jeans"},
},
1,
1,
["Man A"],
random.Random(1),
lambda slot, _rng: str((slot or {}).get("hardcore_clothing") or ""),
)
== ["Woman A's body is fully exposed, bare skin unobstructed"],
"Pair clothing character entries should skip POV labels",
)
_expect(
pair_cast.cast_summary_phrase(2, 1) == "2 women, 1 man, 3 total adults",
"Pair cast summary phrase should live in pair_cast",
)
descriptor_row = {
"age": "25-year-old adult",
"body_phrase": "curvy build",
"skin": "warm skin",
"hair": "dark hair",
"eyes": "brown eyes",
}
_expect(
pb._insta_of_descriptor(descriptor_row) == pair_cast.insta_descriptor_from_row(descriptor_row),
"Prompt builder Insta descriptor should delegate to pair_cast",
)
_expect(
pair_cast.insta_descriptor_from_context(
{"subject_type": "man", "age": "40-year-old adult", "body_phrase": "stocky figure"}
)
== "40-year-old adult man, stocky figure",
"Pair cast context descriptor formatting changed",
)
_expect(
pair_cast.prompt_cast_descriptors("Woman A / primary creator: descriptor") == "Woman A: descriptor",
"Pair cast prompt descriptor label cleanup changed",
)
def _fake_character_context(
label: str,
label_map: dict[str, dict[str, Any]],
_rng: random.Random,
_ethnicity: str,
_figure: str,
_no_plus_women: bool,
_no_black: bool,
) -> tuple[dict[str, Any], dict[str, Any] | None]:
subject = "man" if label.startswith("Man ") else "woman"
age = "40-year-old adult" if subject == "man" else "30-year-old adult"
return {"subject_type": subject, "age": age, "body_phrase": f"{label} body"}, label_map.get(label)
descriptor_entries, descriptor_slots = pair_cast.cast_descriptor_entries_from_slots(
seed_config={},
seed=1,
row_number=1,
ethnicity="any",
figure="any",
no_plus_women=False,
no_black=False,
women_count=2,
men_count=1,
character_slots=[{"subject_type": "man", "presence_mode": "pov"}],
character_slot_map={"Man A": {"subject_type": "man", "presence_mode": "pov"}},
primary_descriptor="primary descriptor",
axis_rng=lambda _config, _axis, seed_value, row_value: random.Random(seed_value + row_value),
character_context_for_label=_fake_character_context,
slot_is_pov=lambda slot: bool(slot and slot.get("presence_mode") == "pov"),
)
_expect(
descriptor_entries
== [
"Woman A / primary creator: primary descriptor",
"Woman B: 30-year-old adult woman, Woman B body",
],
"Pair cast descriptor entries should keep primary label and skip POV men",
)
_expect(
descriptor_slots == [{"subject_type": "man", "presence_mode": "pov"}],
"Pair cast descriptor entries should return the source slots",
)
partner_styling = pair_cast.softcore_partner_styling(
seed_config={},
seed=1,
row_number=1,
women_count=2,
men_count=1,
pov_labels=["Man A"],
label_map={"Woman B": {"softcore_outfit": "custom satin dress"}, "Man A": {"softcore_outfit": "hidden"}},
axis_rng=lambda _config, _axis, seed_value, row_value: random.Random(seed_value + row_value),
choose=lambda _rng, pool: pool[0],
slot_softcore_outfit=lambda slot, _rng: str((slot or {}).get("softcore_outfit") or ""),
)
_expect(
partner_styling["outfits"] == ["Woman B wears custom satin dress"],
"Pair cast partner styling should use configured partner outfit and skip POV men",
)
_expect_text("pair_cast.partner_pose", partner_styling.get("pose"), 12)
options = json.loads(
pb.build_insta_of_options_json(
softcore_expression_enabled="false",
hardcore_expression_enabled="0",
softcore_expression_intensity=1.4,
hardcore_expression_intensity=-0.4,
hardcore_detail_density="invalid",
)
)
_expect(options["softcore_expression_enabled"] is False, "softcore expression enabled should normalize false strings")
_expect(options["hardcore_expression_enabled"] is False, "hardcore expression enabled should normalize false strings")
_expect(options["softcore_expression_intensity"] == 1.0, "softcore expression intensity should clamp high values")
_expect(options["hardcore_expression_intensity"] == 0.0, "hardcore expression intensity should clamp low values")
_expect(options["hardcore_detail_density"] == "balanced", "invalid hardcore detail density should fallback")
parsed = pb._parse_insta_of_options(
{
"softcore_cast": "bad",
"hardcore_cast": "bad",
"softcore_camera_mode": "bad",
"hardcore_camera_mode": "bad",
"camera_detail": "bad",
"hardcore_detail_density": "bad",
"hardcore_women_count": "20",
"hardcore_men_count": "-3",
}
)
_expect(parsed["softcore_cast"] == "solo", "invalid softcore cast should fallback")
_expect(parsed["hardcore_cast"] == "use_counts", "invalid hardcore cast should fallback")
_expect(parsed["softcore_camera_mode"] == "handheld_selfie", "invalid softcore camera should fallback")
_expect(parsed["hardcore_camera_mode"] == "from_camera_config", "invalid hardcore camera should fallback")
_expect(parsed["camera_detail"] == "from_camera_config", "invalid camera detail should fallback")
_expect(parsed["hardcore_detail_density"] == "balanced", "invalid hardcore density should fallback on parse")
_expect(parsed["hardcore_women_count"] == 12, "women count should clamp to max")
_expect(parsed["hardcore_men_count"] == 0, "men count should clamp to min")
_expect(pb.character_softcore_outfit_values("partner_man"), "partner man softcore outfit pool should not be empty")
_expect(
pb.character_softcore_outfit_values("custom", "one; two\nthree") == ["one", "two", "three"],
"custom softcore outfits should split stable free-text lists",
)
_expect("fully nude" in pb.character_hardcore_clothing_values("fully_nude"), "fully nude clothing state should be exposed")
_expect(
pb.character_hardcore_clothing_values("custom", "bare; outfit pushed aside") == ["bare", "outfit pushed aside"],
"custom hardcore clothing should split stable free-text lists",
)
_expect(pb._insta_of_hardcore_counts({"hardcore_cast": "threesome"}) == (2, 1), "threesome count policy changed")
_expect(pb._insta_of_softcore_category("social_tease") == ("Casual clothes", "Casual clothes / Smart casual"), "softcore category mapping changed")
def smoke_pair_route_policy() -> None:
def _fake_build_prompt(**kwargs: Any) -> dict[str, Any]:
is_hard = kwargs.get("category") == "Hardcore sexual poses"
return {
"category": kwargs.get("category"),
"subcategory": kwargs.get("subcategory"),
"scene_text": "shared test room",
"source_scene_text": "",
"composition": "centered test composition",
"source_composition": "",
"expression": "steady look",
"role_graph": "test role graph",
"item": "test item",
"positive_suffix": "test positive suffix",
"negative_prompt": "test negative",
"prompt": "test hard prompt" if is_hard else "test soft prompt",
"caption": "test hard caption" if is_hard else "test soft caption",
}
pair_options_data = {
"softcore_cast": "solo",
"softcore_expression_enabled": True,
"softcore_expression_intensity": 0.45,
"hardcore_expression_enabled": True,
"hardcore_expression_intensity": 0.85,
"hardcore_detail_density": "balanced",
}
pair_rows_kwargs: dict[str, Any] = {
"row_number": 1,
"start_index": 1,
"seed": 10,
"active_trigger": Trigger,
"parsed_seed_config": {},
"options": pair_options_data,
"ethnicity": "any",
"figure": "random",
"no_plus_women": False,
"no_black": False,
"character_profile": "",
"character_cast": "",
"character_slot_map": {},
"pov_character_labels": [],
"hard_women_count": 1,
"hard_men_count": 1,
"soft_category": "Casual clothes",
"soft_subcategory": "Casual clothes / Smart casual",
"softcore_level_key": "social_tease",
"hardcore_random_subcategory": pb.RANDOM_SUBCATEGORY,
"hardcore_position_config": "",
"location_config": "",
"composition_config": "",
"build_prompt": _fake_build_prompt,
"axis_rng": lambda _config, _axis, seed_value, row_value: random.Random(seed_value + row_value),
"cast_expression_intensity_override": lambda value, _slots, _women, _men, _phase: (value, "input"),
"context_from_character_slot": lambda *_args, **_kwargs: {},
"apply_character_context_to_row": lambda row, context: {**row, **context},
"disable_row_expression": lambda row, source: {**row, "expression_disabled": True, "expression_intensity_source": source},
"slot_softcore_outfit": lambda _slot, _rng: "",
"softcore_outfit": lambda _rng, _level: "test soft outfit",
"softcore_pose": lambda _rng, _level: "test soft pose",
"softcore_item_prompt_label": lambda _level: "Softcore test outfit",
"pov_prompt_directive": lambda labels: "POV directive" if labels else "",
"pov_composition_prompt": lambda composition, labels: f"{composition} for {','.join(labels)}" if labels else str(composition),
}
rows_route = pair_rows.build_insta_pair_rows_result(**pair_rows_kwargs)
rows_legacy = pair_rows.build_insta_pair_rows(**pair_rows_kwargs)
_expect(rows_route.soft_row == rows_legacy["soft_row"], "Typed pair row route should match legacy soft row")
_expect(rows_route.hard_row == rows_legacy["hard_row"], "Typed pair row route should match legacy hard row")
_expect(
rows_route.hard_content_rng.getstate() == rows_legacy["hard_content_rng"].getstate(),
"Typed pair row route should match legacy hard content RNG state",
)
_expect(rows_route.soft_row["item"] == "test soft outfit", "Typed pair row route lost soft outfit override")
_expect(rows_route.hard_row["hardcore_detail_density"] == "balanced", "Typed pair row route lost hard density")
camera_options = {
"hardcore_camera_mode": "same_as_softcore",
"softcore_camera_mode": "standard",
"camera_detail": "compact",
"softcore_cast": "same_as_hardcore",
"continuity": "same_creator_same_room",
}
def _camera_config_with_mode(source: Any, mode: str) -> dict[str, Any]:
parsed = dict(source or {})
parsed["camera_mode"] = mode
return parsed
def _camera_directive(config: dict[str, Any]) -> tuple[str, dict[str, Any]]:
return f"{config.get('camera_mode')} camera", config
def _camera_rows() -> tuple[dict[str, Any], dict[str, Any]]:
return (
{"scene_text": "soft room", "composition": "soft composition"},
{"scene_text": "hard room", "composition": "hard composition"},
)
camera_common = {
"options": camera_options,
"camera_config": {"base": "camera"},
"softcore_camera_config": None,
"hardcore_camera_config": None,
"hard_women_count": 1,
"hard_men_count": 1,
"pov_character_labels": [],
"camera_detail_choices": ("compact",),
"camera_config_with_mode": _camera_config_with_mode,
"camera_directive": _camera_directive,
"apply_contextual_composition": lambda row, subject_kind: {**row, "subject_kind": subject_kind},
"contextual_composition_prompt": lambda scene, composition, subject_kind: f"{subject_kind}: {scene}: {composition}",
"composition_prompt": lambda composition: f"Framed as {composition}",
"camera_scene_directive_for_context": lambda _scene, _composition, config, _pov, subject: (f"{subject} scene directive", config),
}
soft_row, hard_row = _camera_rows()
camera_route = pair_camera.resolve_insta_pair_camera_result(soft_row=soft_row, hard_row=hard_row, **camera_common)
soft_row, hard_row = _camera_rows()
camera_legacy = pair_camera.resolve_insta_pair_camera(soft_row=soft_row, hard_row=hard_row, **camera_common)
_expect(camera_route.as_dict() == camera_legacy, "Typed pair camera route should match legacy dict route")
_expect(camera_route.hard_scene == "soft room", "Typed pair camera route lost same-room continuity")
_expect(camera_route.hard_camera_sentence.startswith("Camera control:"), "Typed pair camera route lost hard camera sentence")
clothing_common = {
"hard_row": {
"role_graph": "the man thrusts his penis into the woman",
"item": "penetrative test item",
},
"mode": "explicit_nude",
"softcore_outfit": "test lingerie",
"character_hardcore_clothing_entries": [],
"men_count": 0,
"pov_labels": [],
"rng": random.Random(1),
"continuity_map": pb.INSTA_OF_HARDCORE_CLOTHING_CONTINUITY,
"choose": lambda _rng, pool: pool[0],
}
clothing_route = pair_clothing.resolve_hardcore_pair_clothing_result(**clothing_common)
clothing_legacy = pair_clothing.resolve_hardcore_pair_clothing(**clothing_common)
_expect(clothing_route.as_dict() == clothing_legacy, "Typed pair clothing route should match legacy dict route")
_expect(clothing_route.woman_access == "lower", "Typed pair clothing route lost lower-access detection")
_expect(clothing_route.requires_body_exposure_scene is True, "Typed pair clothing route lost exposure-scene flag")
def smoke_pair_builder_policy() -> None:
request = pair_builder.InstaPairBuildRequest(
row_number=1,
start_index=1,
seed=2101,
ethnicity="any",
figure="random",
no_plus_women=False,
no_black=False,
trigger=Trigger,
prepend_trigger_to_prompt=True,
options_json=_insta_options(),
character_cast=_character_cast(),
hardcore_position_config=_action_filter("penetration_only"),
)
built = pair_builder.build_insta_of_pair(request, pb._insta_pair_build_dependencies())
delegated = pb.build_insta_of_pair(
row_number=request.row_number,
start_index=request.start_index,
seed=request.seed,
ethnicity=request.ethnicity,
figure=request.figure,
no_plus_women=request.no_plus_women,
no_black=request.no_black,
trigger=request.trigger,
prepend_trigger_to_prompt=request.prepend_trigger_to_prompt,
seed_config=request.seed_config,
options_json=request.options_json,
filter_config=request.filter_config,
camera_config=request.camera_config,
softcore_camera_config=request.softcore_camera_config,
hardcore_camera_config=request.hardcore_camera_config,
character_profile=request.character_profile,
character_cast=request.character_cast,
hardcore_position_config=request.hardcore_position_config,
location_config=request.location_config,
composition_config=request.composition_config,
extra_positive=request.extra_positive,
extra_negative=request.extra_negative,
)
_expect(built == delegated, "Prompt builder Insta/OF wrapper should delegate to pair_builder without output drift")
_expect_pair(built, "pair_builder_policy")
def _expect_pair(pair: dict[str, Any], name: str) -> None:
_expect(pair.get("mode") == "Insta/OF", f"{name}.mode should be Insta/OF")
_expect_row_base(pair.get("softcore_row") or {}, f"{name}.softcore_row")
_expect_custom_row(pair.get("hardcore_row") or {}, f"{name}.hardcore_row")
_expect_text(f"{name}.shared_descriptor", pair.get("shared_descriptor"), 12)
_expect(pair.get("shared_cast_descriptors"), f"{name}.shared_cast_descriptors should not be empty")
_expect_text(f"{name}.softcore_prompt", pair.get("softcore_prompt"), 20)
_expect_text(f"{name}.hardcore_prompt", pair.get("hardcore_prompt"), 20)
_expect_trigger_once(f"{name}.softcore_prompt", pair.get("softcore_prompt"), Trigger)
_expect_trigger_once(f"{name}.hardcore_prompt", pair.get("hardcore_prompt"), Trigger)
_expect_trigger_once(f"{name}.softcore_caption", pair.get("softcore_caption"), Trigger)
_expect_trigger_once(f"{name}.hardcore_caption", pair.get("hardcore_caption"), Trigger)
_expect(pair["softcore_row"].get("prompt") == pair.get("softcore_prompt"), f"{name}.softcore_row prompt drifted from pair prompt")
_expect(pair["hardcore_row"].get("prompt") == pair.get("hardcore_prompt"), f"{name}.hardcore_row prompt drifted from pair prompt")
_expect(pair["softcore_row"].get("caption") == pair.get("softcore_caption"), f"{name}.softcore_row caption drifted from pair caption")
_expect(pair["hardcore_row"].get("caption") == pair.get("hardcore_caption"), f"{name}.hardcore_row caption drifted from pair caption")
_expect(
pair["softcore_row"].get("negative_prompt") == pair.get("softcore_negative_prompt"),
f"{name}.softcore_row negative drifted from pair negative",
)
_expect(
pair["hardcore_row"].get("negative_prompt") == pair.get("hardcore_negative_prompt"),
f"{name}.hardcore_row negative drifted from pair negative",
)
if "softcore_partner_styling" in pair:
_expect(
pair["softcore_row"].get("softcore_partner_styling") == pair.get("softcore_partner_styling"),
f"{name}.softcore_row partner styling drifted from pair root",
)
for key in (
"hardcore_clothing_state",
"character_hardcore_clothing",
"default_man_hardcore_clothing",
"hardcore_detail_density",
"hardcore_position_config",
):
if key in pair:
_expect(pair["hardcore_row"].get(key) == pair.get(key), f"{name}.hardcore_row {key} drifted from pair root")
_expect_no_duplicate_comma_items(f"{name}.softcore_negative", pair.get("softcore_negative_prompt"))
_expect_no_duplicate_comma_items(f"{name}.hardcore_negative", pair.get("hardcore_negative_prompt"))
_expect_formatter_outputs(pair, name, target="softcore")
_expect_formatter_outputs(pair, f"{name}.hardcore", target="hardcore")
def smoke_insta_pair() -> None:
pair = pb.build_insta_of_pair(
row_number=1,
start_index=1,
seed=2101,
ethnicity="any",
figure="random",
no_plus_women=False,
no_black=False,
trigger=Trigger,
prepend_trigger_to_prompt=True,
options_json=_insta_options(),
character_cast=_character_cast(),
hardcore_position_config=_action_filter("penetration_only"),
)
_expect_pair(pair, "insta_pair_same_cast")
_expect(pair["softcore_row"].get("scene_text") == pair["hardcore_row"].get("scene_text"), "pair scene continuity broke")
clothing_state = _clean_key(pair.get("hardcore_clothing_state"))
_expect("body is fully exposed" in clothing_state, "explicit nude pair should keep body exposure state")
_expect("teaser outfit detail" not in clothing_state, "explicit nude pair should not repeat softcore outfit detail")
partner_styling = pair.get("softcore_partner_styling") or {}
_expect(partner_styling.get("outfits"), "same-cast pair should keep partner softcore outfit styling")
_expect_text("insta_pair_same_cast.partner_pose", partner_styling.get("pose"), 12)
def smoke_krea_pair_clothing_state() -> None:
pair = pb.build_insta_of_pair(
row_number=1,
start_index=1,
seed=3511,
ethnicity="any",
figure="random",
no_plus_women=False,
no_black=False,
trigger=Trigger,
prepend_trigger_to_prompt=True,
options_json=_insta_options(hardcore_clothing_continuity="partially_removed"),
character_cast=_character_cast(),
hardcore_position_config=_action_filter("penetration_only"),
)
_expect_pair(pair, "krea_pair_clothing_state")
typed_route = krea_pair_formatter.format_insta_pair_result(
krea_pair_formatter.KreaPairFormatRequest(pair, "balanced", "preserve"),
krea_formatter._krea_pair_format_dependencies(),
)
legacy_route = krea_formatter._insta_pair_to_krea(pair, "balanced", "preserve")
_expect(
typed_route.as_tuple() == legacy_route,
"Typed Krea pair formatter route should match legacy wrapper output",
)
krea = krea_formatter.format_krea2_prompt("", metadata_json=_json(pair), target="hardcore")
prompt = _expect_text("krea_pair_clothing_state.krea_prompt", krea.get("krea_prompt"), 60)
lower = prompt.lower()
root_clothing = _clean_key(pair.get("hardcore_clothing_state"))
_expect("lower body is clear" in root_clothing, "pair root clothing state lost lower-body access wording")
_expect(pair.get("default_man_hardcore_clothing"), "pair root default man hardcore clothing is missing")
_expect("metadata" in krea.get("method", ""), "pair clothing route did not use metadata")
_expect("clothing state:" not in lower, "Krea clothing route leaked raw clothing label")
_expect("visual clothing state" not in lower, "Krea clothing route fell back to visual clothing state label")
_expect("softcore outfit" not in lower and "teaser outfit" not in lower, "Krea clothing route leaked softcore outfit label")
_expect("lower body is clear" in lower, "Krea clothing route lost generated clothing continuity")
_expect("the man keeps" in lower, "Krea clothing route lost partner clothing continuity")
def smoke_insta_pair_pov() -> None:
pair = pb.build_insta_of_pair(
row_number=1,
start_index=1,
seed=2201,
ethnicity="any",
figure="random",
no_plus_women=False,
no_black=False,
trigger=Trigger,
prepend_trigger_to_prompt=True,
options_json=_insta_options(),
character_cast=_character_cast(pov_man=True),
hardcore_position_config=_action_filter("oral_only"),
)
_expect_pair(pair, "insta_pair_pov_man")
pov_labels = pair.get("pov_character_labels") or []
_expect("Man A" in pov_labels, "pair POV labels should include Man A")
hard_row = pair.get("hardcore_row") or {}
_expect("Man A" in (hard_row.get("pov_character_labels") or []), "hard row POV labels should include Man A")
krea = krea_formatter.format_krea2_prompt("", metadata_json=_json(pair), target="hardcore")
prompt = krea.get("krea_prompt") or ""
_expect("viewer" in prompt.lower(), "POV Krea prompt should mention viewer perspective")
def smoke_insta_pair_camera_split() -> None:
soft_camera = _orbit_camera(
horizontal_angle=45,
vertical_angle=-30,
zoom=5.0,
subject_focus="environment",
)
hard_camera = _orbit_camera(
horizontal_angle=135,
vertical_angle=30,
zoom=8.0,
subject_focus="action",
)
pair = pb.build_insta_of_pair(
row_number=1,
start_index=1,
seed=2251,
ethnicity="any",
figure="random",
no_plus_women=False,
no_black=False,
trigger=Trigger,
prepend_trigger_to_prompt=True,
options_json=_insta_options(
softcore_camera_mode="from_camera_config",
hardcore_camera_mode="from_camera_config",
camera_detail="compact",
),
character_cast=_character_cast(),
hardcore_position_config=_action_filter("penetration_only"),
location_config=_coworking_location_config(),
softcore_camera_config=soft_camera,
hardcore_camera_config=hard_camera,
)
_expect_pair(pair, "insta_pair_camera_split")
soft_scene = _expect_text("insta_pair_camera_split.soft_camera_scene", pair.get("softcore_camera_scene_directive"), 40)
hard_scene = _expect_text("insta_pair_camera_split.hard_camera_scene", pair.get("hardcore_camera_scene_directive"), 40)
_expect("front-right quarter view" in soft_scene, "soft camera scene missed soft orbit direction")
_expect("back-right quarter view" in hard_scene, "hard camera scene missed hard orbit direction")
_expect("low-angle shot" in soft_scene, "soft camera scene missed soft elevation")
_expect("elevated shot" in hard_scene, "hard camera scene missed hard elevation")
_expect("front-right quarter view" in str(pair.get("softcore_camera_directive")), "soft pair camera directive was not preserved")
_expect("back-right quarter view" in str(pair.get("hardcore_camera_directive")), "hard pair camera directive was not preserved")
soft_row = pair.get("softcore_row") or {}
hard_row = pair.get("hardcore_row") or {}
_expect(pair.get("softcore_camera_config") == soft_row.get("camera_config"), "soft pair camera config drifted from soft row")
_expect(pair.get("hardcore_camera_config") == hard_row.get("camera_config"), "hard pair camera config drifted from hard row")
_expect(pair.get("softcore_camera_directive") == soft_row.get("camera_directive"), "soft pair camera directive drifted from soft row")
_expect(pair.get("hardcore_camera_directive") == hard_row.get("camera_directive"), "hard pair camera directive drifted from hard row")
_expect(pair.get("softcore_camera_scene_directive") == soft_row.get("camera_scene_directive"), "soft pair camera scene drifted from soft row")
_expect(pair.get("hardcore_camera_scene_directive") == hard_row.get("camera_scene_directive"), "hard pair camera scene drifted from hard row")
krea = krea_formatter.format_krea2_prompt("", metadata_json=_json(pair), target="auto")
_expect("front-right quarter view" in (krea.get("krea_softcore_prompt") or ""), "Krea soft pair lost soft camera geometry")
_expect("back-right quarter view" in (krea.get("krea_hardcore_prompt") or ""), "Krea hard pair lost hard camera geometry")
def smoke_pov_camera_scene() -> None:
pair = pb.build_insta_of_pair(
row_number=1,
start_index=1,
seed=2261,
ethnicity="any",
figure="random",
no_plus_women=False,
no_black=False,
trigger=Trigger,
prepend_trigger_to_prompt=True,
options_json=_insta_options(
softcore_camera_mode="from_camera_config",
hardcore_camera_mode="from_camera_config",
camera_detail="compact",
),
character_cast=_character_cast(pov_man=True),
hardcore_position_config=_action_filter("oral_only"),
location_config=_coworking_location_config(),
hardcore_camera_config=_orbit_camera(
horizontal_angle=135,
vertical_angle=30,
zoom=8.0,
subject_focus="action",
),
)
_expect_pair(pair, "pov_camera_scene")
hard_row = pair.get("hardcore_row") or {}
_expect(not hard_row.get("camera_directive"), "POV hard row should suppress normal camera directive")
scene_directive = _expect_text("pov_camera_scene.hard_camera_scene", hard_row.get("camera_scene_directive"), 40)
_expect("from POV" in scene_directive, "POV camera scene should be marked as first-person")
_expect("not in the lower foreground" in scene_directive, "POV camera scene should keep location anchors out of lower foreground")
krea = krea_formatter.format_krea2_prompt("", metadata_json=_json(pair), target="hardcore")
prompt = krea.get("krea_prompt") or ""
_expect("from POV" in prompt, "Krea POV prompt lost camera-scene directive")
_expect("Camera:" not in prompt, "Krea POV prompt should not emit normal third-person camera directive")
def smoke_krea_pov_penetration_route() -> None:
pair = pb.build_insta_of_pair(
row_number=1,
start_index=1,
seed=3411,
ethnicity="any",
figure="random",
no_plus_women=False,
no_black=False,
trigger=Trigger,
prepend_trigger_to_prompt=True,
options_json=_insta_options(
softcore_camera_mode="from_camera_config",
hardcore_camera_mode="from_camera_config",
camera_detail="compact",
),
character_cast=_character_cast(pov_man=True),
hardcore_position_config=_action_filter("penetration_only"),
location_config=_coworking_location_config(),
hardcore_camera_config=_orbit_camera(
horizontal_angle=45,
vertical_angle=0,
zoom=5.5,
subject_focus="action",
),
)
_expect_pair(pair, "krea_pov_penetration_route")
hard_row = pair.get("hardcore_row") or {}
_expect("Man A" in (hard_row.get("pov_character_labels") or []), "POV penetration hard row lost Man A POV label")
_expect(not hard_row.get("camera_directive"), "POV penetration should suppress normal camera directive")
krea = krea_formatter.format_krea2_prompt("", metadata_json=_json(pair), target="hardcore")
prompt = _expect_text("krea_pov_penetration_route.krea_prompt", krea.get("krea_prompt"), 60)
lower = prompt.lower()
_expect("metadata" in krea.get("method", ""), "POV penetration route did not use metadata")
_expect("viewer" in lower and "first-person" in lower, "POV penetration lost first-person wording")
_expect("penetrates" in lower or "penetration" in lower, "POV penetration lost penetration action wording")
_expect("woman" in lower and "thigh" in lower, "POV penetration lost body-position anchors")
_expect("camera:" not in prompt, "POV penetration emitted normal third-person camera directive")
_expect("role graph:" not in lower and "sexual scene:" not in lower, "POV penetration leaked raw prompt labels")
_expect("composition. explicit" in lower, "POV penetration composition sentence should keep punctuation before style suffix")
def smoke_pov_outercourse_position_routes() -> None:
cases = [
(
"pov_outercourse_boobjob",
"boobjob",
("breasts tightly around", "glans sits just below"),
("press her breasts tightly around", "glans just below", "penis shaft"),
),
(
"pov_outercourse_testicle",
"testicle_sucking",
("mouth and tongue on the pov viewer's balls", "penis points upward"),
("mouth and tongue licking", "balls", "penis points upward"),
),
(
"pov_outercourse_penis_licking",
"penis_licking",
("head low under the pov viewer's penis", "tongue running along"),
("tongue runs along", "penis shaft", "glans"),
),
(
"pov_outercourse_handjob",
"handjob",
("one hand wrapped around the pov viewer's penis", "strokes toward the glans"),
("one hand wraps around", "penis shaft", "strokes toward the glans"),
),
(
"pov_outercourse_footjob",
"footjob",
("both soles wrapped around the pov viewer's penis", "lower foreground"),
("soles wrap around", "penis shaft", "lower foreground"),
),
]
for offset, (name, position_key, role_terms, krea_terms) in enumerate(cases, start=3601):
pair = pb.build_insta_of_pair(
row_number=1,
start_index=1,
seed=offset,
ethnicity="any",
figure="random",
no_plus_women=False,
no_black=False,
trigger=Trigger,
prepend_trigger_to_prompt=True,
options_json=_insta_options(
softcore_camera_mode="from_camera_config",
hardcore_camera_mode="from_camera_config",
camera_detail="compact",
),
character_cast=_character_cast(pov_man=True),
hardcore_position_config=_position_filter("outercourse_only", "outercourse", [position_key]),
location_config=_coworking_location_config(),
hardcore_camera_config=_orbit_camera(
horizontal_angle=45,
vertical_angle=0,
zoom=7.5,
subject_focus="action",
),
)
_expect_pair(pair, name)
hard_row = pair.get("hardcore_row") or {}
_expect(hard_row.get("action_family") == "outercourse", f"{name} action_family should be outercourse")
_expect(hard_row.get("position_family") == "outercourse", f"{name} position_family should be outercourse")
_expect(position_key in (hard_row.get("position_keys") or []), f"{name} lost position key {position_key!r}")
role_graph = _expect_text(f"{name}.source_role_graph", hard_row.get("source_role_graph"), 40).lower()
for term in role_terms:
_expect(term in role_graph, f"{name} role graph missing {term!r}: {role_graph}")
krea = krea_formatter.format_krea2_prompt("", metadata_json=_json(pair), target="hardcore")
prompt = _expect_text(f"{name}.krea_prompt", krea.get("krea_prompt"), 60).lower()
_expect("metadata" in krea.get("method", ""), f"{name}.krea did not use metadata")
_expect("viewer" in prompt and "first-person" in prompt, f"{name} Krea prompt lost POV wording")
_expect("camera:" not in krea.get("krea_prompt", ""), f"{name} Krea prompt emitted normal third-person camera directive")
for term in krea_terms:
_expect(term in prompt, f"{name} Krea prompt missing {term!r}: {prompt}")
def smoke_pov_oral_position_routes() -> None:
cases = [
(
"pov_oral_kneeling",
"kneeling",
("viewer's penis", "takes the viewer's penis in her mouth"),
("takes the viewer's penis in her mouth", "viewer stands over her"),
),
(
"pov_oral_face_sitting",
"face_sitting",
("straddling the viewer's face", "pussy directly over the viewer's mouth"),
("straddling the viewer's face", "tongue contact visible"),
),
(
"pov_oral_sixty_nine",
"sixty_nine",
("head-to-hips", "viewer's mouth on woman a's pussy"),
("head-to-hips", "viewer's mouth on the woman's pussy"),
),
(
"pov_oral_edge_supported",
"edge_supported",
("raised edge with thighs open", "viewer kneels between her legs"),
("raised edge with thighs open", "viewer kneels between her legs"),
),
(
"pov_oral_side_lying",
"side_lying",
("woman a lies on her side", "viewer lies beside her hips"),
("woman lies on her side", "viewer lies beside her hips"),
),
(
"pov_oral_chair",
"chair_oral",
("viewer sits in a chair", "kneels between his thighs"),
("viewer sits in a chair", "kneels between the viewer's thighs"),
),
]
for offset, (name, position_key, role_terms, krea_terms) in enumerate(cases, start=3701):
pair = pb.build_insta_of_pair(
row_number=1,
start_index=1,
seed=offset,
ethnicity="any",
figure="random",
no_plus_women=False,
no_black=False,
trigger=Trigger,
prepend_trigger_to_prompt=True,
options_json=_insta_options(
softcore_camera_mode="from_camera_config",
hardcore_camera_mode="from_camera_config",
camera_detail="compact",
),
character_cast=_character_cast(pov_man=True),
hardcore_position_config=_position_filter("oral_only", "oral", [position_key]),
location_config=_coworking_location_config(),
hardcore_camera_config=_orbit_camera(
horizontal_angle=45,
vertical_angle=0,
zoom=7.5,
subject_focus="action",
),
)
_expect_pair(pair, name)
hard_row = pair.get("hardcore_row") or {}
_expect(hard_row.get("action_family") == "oral", f"{name} action_family should be oral")
_expect(hard_row.get("position_family") == "oral", f"{name} position_family should be oral")
_expect(position_key in (hard_row.get("position_keys") or []), f"{name} lost position key {position_key!r}")
role_graph = _expect_text(f"{name}.source_role_graph", hard_row.get("source_role_graph"), 40).lower()
for term in role_terms:
_expect(term in role_graph, f"{name} role graph missing {term!r}: {role_graph}")
krea = krea_formatter.format_krea2_prompt("", metadata_json=_json(pair), target="hardcore")
prompt = _expect_text(f"{name}.krea_prompt", krea.get("krea_prompt"), 60).lower()
_expect("metadata" in krea.get("method", ""), f"{name}.krea did not use metadata")
_expect("viewer" in prompt and "first-person" in prompt, f"{name} Krea prompt lost POV wording")
_expect("viewer lies on the viewer" not in prompt, f"{name} Krea prompt kept recursive POV wording: {prompt}")
_expect("camera:" not in krea.get("krea_prompt", ""), f"{name} Krea prompt emitted normal third-person camera directive")
for term in krea_terms:
_expect(term in prompt, f"{name} Krea prompt missing {term!r}: {prompt}")
def smoke_pov_penetration_position_routes() -> None:
cases = [
(
"pov_penetration_missionary",
"missionary",
("woman a lies on her back", "man a is above her between her thighs"),
("pov missionary position", "viewer is above her", "penetrates her pussy"),
),
(
"pov_penetration_cowgirl",
"cowgirl",
("woman a straddles man a's hips facing him", "man a lies under her"),
("pov cowgirl position", "viewer lies on his back", "woman straddles his hips"),
),
(
"pov_penetration_reverse_cowgirl",
"reverse_cowgirl",
("woman a straddles man a's hips facing away", "man a lies under her"),
("pov reverse cowgirl position", "facing away", "viewer lies on his back"),
),
(
"pov_penetration_doggy",
"doggy",
("woman a is on all fours", "man a is positioned behind her"),
("ass raised toward the pov viewer", "on all fours", "penetrates her pussy"),
),
(
"pov_penetration_edge_supported",
"edge_supported",
("raised edge", "man a kneels between her thighs"),
("pov raised-edge penetration position", "viewer kneels between her legs", "penetrates her pussy"),
),
(
"pov_penetration_lotus",
"lotus_lap",
("woman a sits in man a's lap", "legs around his hips"),
("pov lotus position", "woman sits in his lap", "penetrates her pussy"),
),
]
for offset, (name, position_key, role_terms, krea_terms) in enumerate(cases, start=3801):
pair = pb.build_insta_of_pair(
row_number=1,
start_index=1,
seed=offset,
ethnicity="any",
figure="random",
no_plus_women=False,
no_black=False,
trigger=Trigger,
prepend_trigger_to_prompt=True,
options_json=_insta_options(
softcore_camera_mode="from_camera_config",
hardcore_camera_mode="from_camera_config",
camera_detail="compact",
),
character_cast=_character_cast(pov_man=True),
hardcore_position_config=_position_filter("penetration_only", "penetrative", [position_key]),
location_config=_coworking_location_config(),
hardcore_camera_config=_orbit_camera(
horizontal_angle=45,
vertical_angle=0,
zoom=7.5,
subject_focus="action",
),
)
_expect_pair(pair, name)
hard_row = pair.get("hardcore_row") or {}
_expect(hard_row.get("action_family") == "penetration", f"{name} action_family should be penetration")
_expect(hard_row.get("position_family") == "penetrative", f"{name} position_family should be penetrative")
_expect(position_key in (hard_row.get("position_keys") or []), f"{name} lost position key {position_key!r}")
role_graph = _expect_text(f"{name}.source_role_graph", hard_row.get("source_role_graph"), 40).lower()
for term in role_terms:
_expect(term in role_graph, f"{name} role graph missing {term!r}: {role_graph}")
krea = krea_formatter.format_krea2_prompt("", metadata_json=_json(pair), target="hardcore")
prompt = _expect_text(f"{name}.krea_prompt", krea.get("krea_prompt"), 60).lower()
_expect("metadata" in krea.get("method", ""), f"{name}.krea did not use metadata")
_expect("viewer" in prompt and "pov" in prompt, f"{name} Krea prompt lost POV wording")
_expect("camera:" not in krea.get("krea_prompt", ""), f"{name} Krea prompt emitted normal third-person camera directive")
for term in krea_terms:
_expect(term in prompt, f"{name} Krea prompt missing {term!r}: {prompt}")
def smoke_pov_anal_position_routes() -> None:
cases = [
(
"pov_anal_doggy",
"doggy",
("on all fours", "positioned behind her"),
("on all fours directly in front", "penetrates her ass"),
),
(
"pov_anal_bent_over",
"bent_over",
("bent forward", "stands behind her"),
("bent forward at the waist", "penetrates her ass"),
),
(
"pov_anal_face_down",
"face_down_ass_up",
("lies face-down", "ass raised"),
("lying face-down", "penetrates her ass"),
),
(
"pov_anal_standing",
"standing",
("stands braced", "stands behind her"),
("pov standing rear-entry position", "viewer stands behind her"),
),
(
"pov_anal_side_lying",
"side_lying",
("lies on her side", "presses behind her"),
("pov side-lying sex position", "viewer is behind her"),
),
(
"pov_anal_edge_supported",
"edge_supported",
("raised edge", "kneels behind her"),
("pov raised-edge penetration position", "viewer kneels between her legs"),
),
(
"pov_anal_kneeling",
"kneeling",
("kneels forward", "kneels behind her"),
("pov kneeling rear-entry position", "viewer kneels behind her"),
),
]
for offset, (name, position_key, role_terms, krea_terms) in enumerate(cases, start=3901):
pair = pb.build_insta_of_pair(
row_number=1,
start_index=1,
seed=offset,
ethnicity="any",
figure="random",
no_plus_women=False,
no_black=False,
trigger=Trigger,
prepend_trigger_to_prompt=True,
options_json=_insta_options(
softcore_camera_mode="from_camera_config",
hardcore_camera_mode="from_camera_config",
camera_detail="compact",
),
character_cast=_character_cast(pov_man=True),
hardcore_position_config=_position_filter("anal_only", "anal", [position_key]),
location_config=_coworking_location_config(),
hardcore_camera_config=_orbit_camera(
horizontal_angle=45,
vertical_angle=0,
zoom=7.5,
subject_focus="action",
),
)
_expect_pair(pair, name)
hard_row = pair.get("hardcore_row") or {}
_expect(hard_row.get("position_family") == "anal", f"{name} position_family should be anal")
_expect(position_key in (hard_row.get("position_keys") or []), f"{name} lost position key {position_key!r}")
role_graph = _expect_text(f"{name}.source_role_graph", hard_row.get("source_role_graph"), 40).lower()
for term in role_terms:
_expect(term in role_graph, f"{name} role graph missing {term!r}: {role_graph}")
krea = krea_formatter.format_krea2_prompt("", metadata_json=_json(pair), target="hardcore")
prompt = _expect_text(f"{name}.krea_prompt", krea.get("krea_prompt"), 60).lower()
_expect("metadata" in krea.get("method", ""), f"{name}.krea did not use metadata")
_expect("viewer" in prompt and "first-person" in prompt, f"{name} Krea prompt lost POV wording")
_expect("camera:" not in krea.get("krea_prompt", ""), f"{name} Krea prompt emitted normal third-person camera directive")
for term in krea_terms:
_expect(term in prompt, f"{name} Krea prompt missing {term!r}: {prompt}")
def smoke_double_front_back_route() -> None:
pair = pb.build_insta_of_pair(
row_number=1,
start_index=1,
seed=3911,
ethnicity="any",
figure="random",
no_plus_women=False,
no_black=False,
trigger=Trigger,
prepend_trigger_to_prompt=True,
options_json=_insta_options(
hardcore_cast="mixed_group",
hardcore_men_count=2,
softcore_camera_mode="from_camera_config",
hardcore_camera_mode="from_camera_config",
camera_detail="compact",
),
character_cast=_character_cast_two_men(),
hardcore_position_config=_anal_double_filter(["front_back"]),
location_config=_coworking_location_config(),
hardcore_camera_config=_orbit_camera(
horizontal_angle=45,
vertical_angle=0,
zoom=7.5,
subject_focus="action",
),
)
_expect_pair(pair, "double_front_back_route")
hard_row = pair.get("hardcore_row") or {}
_expect(hard_row.get("position_family") == "anal", "double route position_family should be anal")
_expect("front_back" in (hard_row.get("position_keys") or []), "double route lost front_back key")
role_graph = _expect_text("double_front_back_route.source_role_graph", hard_row.get("source_role_graph"), 40).lower()
_expect("second penetration point from the front" in role_graph, f"double route role graph lost front/back placement: {role_graph}")
krea = krea_formatter.format_krea2_prompt("", metadata_json=_json(pair), target="hardcore")
prompt = _expect_text("double_front_back_route.krea_prompt", krea.get("krea_prompt"), 60).lower()
_expect("metadata" in krea.get("method", ""), "double route Krea did not use metadata")
_expect("front-and-back" in prompt, "double route Krea lost front/back position wording")
_expect("second penetration point" in prompt, "double route Krea lost second-contact wording")
_expect("role graph:" not in prompt and "sexual scene:" not in prompt, "double route Krea leaked raw labels")
def smoke_climax_position_routes() -> None:
cases = [
(
"climax_face_down",
"face_down_ass_up",
4001,
_character_cast(),
1,
1,
("lies face-down", "lower back and ass"),
("face-down", "lower back and ass"),
),
(
"climax_side_lying",
"side_lying",
4042,
_character_cast(),
1,
1,
("lies on her side", "thighs and pussy"),
("lies on her side", "thighs and pussy"),
),
(
"climax_lotus_lap",
"lotus_lap",
4001,
_character_cast(),
1,
1,
("sits in man a's lap", "legs wrapped"),
("sits in the man's lap", "legs wrapped"),
),
(
"climax_open_thighs",
"open_thighs",
4001,
_character_cast(),
1,
1,
("lies on her back", "thighs open"),
("lies on her back", "thighs open"),
),
(
"climax_front_back",
"front_back",
4090,
_character_cast_two_men(),
1,
2,
("lies between man a and man b", "man a under her hips"),
("lies between man a and man b", "visible semen lands"),
),
]
for name, position_key, seed, cast, women_count, men_count, role_terms, krea_terms in cases:
row = _prompt_row(
name=name,
category="Hardcore sexual poses",
subcategory="Cumshot and climax",
seed=seed,
character_cast=cast,
women_count=women_count,
men_count=men_count,
hardcore_position_config=_position_filter("climax_only", "climax", [position_key]),
)
_expect_custom_row(row, name)
_expect(row.get("action_family") == "climax", f"{name} action_family should be climax")
_expect(row.get("position_family") == "climax", f"{name} position_family should be climax")
_expect(position_key in (row.get("position_keys") or []), f"{name} lost position key {position_key!r}")
role_graph = _expect_text(f"{name}.source_role_graph", row.get("source_role_graph"), 40).lower()
for term in role_terms:
_expect(term in role_graph, f"{name} role graph missing {term!r}: {role_graph}")
krea = krea_formatter.format_krea2_prompt("", metadata_json=_json(row), target="single")
prompt = _expect_text(f"{name}.krea_prompt", krea.get("krea_prompt"), 60).lower()
_expect("metadata" in krea.get("method", ""), f"{name}.krea did not use metadata")
_expect("role graph:" not in prompt and "sexual scene:" not in prompt, f"{name} Krea leaked raw labels")
for term in krea_terms:
_expect(term in prompt, f"{name} Krea prompt missing {term!r}: {prompt}")
if position_key == "side_lying":
_expect("lower back and ass" not in prompt, f"{name} Krea kept conflicting rear-entry fluid location: {prompt}")
_expect_formatter_outputs(row, name, target="single")
def smoke_interaction_role_graph_routes() -> None:
cases = [
(
"interaction_manual",
"Manual stimulation",
"manual_only",
"manual",
"fingering",
4301,
_character_cast(),
1,
1,
("reclines with thighs open", "fingers visibly stimulating"),
("fingers visibly stimulating", "between her legs"),
),
(
"interaction_clothing_transition",
"Clothing and position transitions",
"interaction_only",
"interaction",
"position_transition",
4302,
_character_cast(),
1,
1,
("mid-transition", "moving clothing aside"),
("mid-transition", "guiding the woman's hips"),
),
(
"interaction_body_worship",
"Body worship and touching",
"interaction_only",
"interaction",
"body_worship",
4301,
_character_cast(),
1,
1,
("kisses down her body", "hands tracing"),
("kisses down her body", "hands tracing"),
),
(
"interaction_camera_performance",
"Camera performance",
"interaction_only",
"interaction",
"camera_showing",
4301,
_character_cast(),
1,
1,
("faces the camera", "creator-shot reveal"),
("faces the camera", "creator-shot reveal"),
),
(
"interaction_aftercare",
"Aftercare and cleanup",
"interaction_only",
"interaction",
"aftercare",
4301,
_character_cast(),
1,
1,
("lie close together after sex", "post-sex cuddle"),
("lie close together after sex", "post-sex cuddle"),
),
(
"interaction_group_coordination",
"Group coordination",
"interaction_only",
"interaction",
"watching",
4301,
_character_cast_two_men(),
1,
2,
("is centered", "hold and present"),
("is centered", "each role clearly visible"),
),
]
for name, subcategory, focus, family, position_key, seed, cast, women_count, men_count, role_terms, krea_terms in cases:
row = _prompt_row(
name=name,
category="Hardcore sexual poses",
subcategory=subcategory,
seed=seed,
character_cast=cast,
women_count=women_count,
men_count=men_count,
hardcore_position_config=_position_filter(focus, family, [position_key]),
)
_expect_custom_row(row, name)
_expect(row.get("action_family") == "foreplay", f"{name} action_family should stay formatter foreplay")
_expect(row.get("position_family") == family, f"{name} position_family mismatch: {row.get('position_family')}")
_expect(position_key in (row.get("position_keys") or []), f"{name} lost position key {position_key!r}")
role_graph = _expect_text(f"{name}.source_role_graph", row.get("source_role_graph"), 40).lower()
for term in role_terms:
_expect(term in role_graph, f"{name} role graph missing {term!r}: {role_graph}")
krea = krea_formatter.format_krea2_prompt("", metadata_json=_json(row), target="single")
prompt = _expect_text(f"{name}.krea_prompt", krea.get("krea_prompt"), 60).lower()
_expect("metadata" in krea.get("method", ""), f"{name}.krea did not use metadata")
_expect("role graph:" not in prompt and "sexual scene:" not in prompt, f"{name} Krea leaked raw labels")
for term in krea_terms:
_expect(term in prompt, f"{name} Krea prompt missing {term!r}: {prompt}")
_expect_formatter_outputs(row, name, target="single")
def smoke_fallback_role_graph_routes() -> None:
cases = [
(
"fallback_solo_woman_manual",
("woman",),
"Manual stimulation",
"manual_only",
"manual",
"fingering",
4401,
1,
0,
("reclines with thighs open", "one hand between her legs"),
("one hand between her legs", "fingers visibly stimulating"),
),
(
"fallback_solo_man_climax",
("man",),
"Cumshot and climax",
"climax_only",
"climax",
"open_thighs",
4401,
0,
1,
("solo visible ejaculation pose", "semen visible"),
("solo visible ejaculation pose", "visible semen on skin"),
),
(
"fallback_women_only_oral",
("woman", "woman"),
"Oral sex",
"oral_only",
"oral",
"reclining_oral",
4401,
2,
0,
("woman a kneels between woman b", "uses tongue and fingers"),
("woman a kneels between woman b", "uses tongue and fingers"),
),
(
"fallback_women_only_threesome",
("woman", "woman", "woman"),
"Threesomes",
"threesome_only",
"threesome",
"front_back",
4401,
3,
0,
("uses a strap-on", "gives oral contact"),
("uses a strap-on", "gives oral contact"),
),
(
"fallback_men_only_oral",
("man", "man"),
"Oral sex",
"oral_only",
"oral",
"kneeling",
4401,
0,
2,
("man a kneels", "takes man b's penis"),
("man a kneels", "takes man b's penis"),
),
(
"fallback_men_only_threesome",
("man", "man", "man"),
"Threesomes",
"threesome_only",
"threesome",
"front_back",
4401,
0,
3,
("penetrates man b anally", "gives oral contact"),
("penetrates man b anally", "gives oral contact"),
),
(
"fallback_mixed_threesome",
("woman", "man", "man"),
"Threesomes",
"threesome_only",
"threesome",
"front_back",
4401,
1,
2,
("thrusts his penis into woman a", "uses mouth and hands"),
("thrusts his penis into woman a", "uses mouth and hands"),
),
]
for name, subjects, subcategory, focus, family, position_key, seed, women_count, men_count, role_terms, krea_terms in cases:
row = _prompt_row(
name=name,
category="Hardcore sexual poses",
subcategory=subcategory,
seed=seed,
character_cast=_character_cast_subjects(subjects),
women_count=women_count,
men_count=men_count,
hardcore_position_config=_position_filter(focus, family, [position_key]),
)
_expect_custom_row(row, name)
_expect(row.get("position_family") == family, f"{name} position_family mismatch: {row.get('position_family')}")
_expect(position_key in (row.get("position_keys") or []), f"{name} lost position key {position_key!r}")
role_graph = _expect_text(f"{name}.source_role_graph", row.get("source_role_graph"), 30).lower()
for term in role_terms:
_expect(term in role_graph, f"{name} role graph missing {term!r}: {role_graph}")
krea = krea_formatter.format_krea2_prompt("", metadata_json=_json(row), target="single")
prompt = _expect_text(f"{name}.krea_prompt", krea.get("krea_prompt"), 60).lower()
_expect("metadata" in krea.get("method", ""), f"{name}.krea did not use metadata")
_expect("role graph:" not in prompt and "sexual scene:" not in prompt, f"{name} Krea leaked raw labels")
for term in krea_terms:
_expect(term in prompt, f"{name} Krea prompt missing {term!r}: {prompt}")
if name == "fallback_solo_man_climax":
_expect("lower back and ass" not in prompt, f"{name} Krea kept conflicting solo male climax detail: {prompt}")
_expect_formatter_outputs(row, name, target="single")
def smoke_no_expression_fallback() -> None:
cast = pb.build_character_slot_json(
subject_type="woman",
label="A",
age="25-year-old adult",
ethnicity="western_european",
body="slim",
descriptor_detail="full",
expression_enabled=False,
)["character_cast"]
row = _prompt_row(
name="hardcore_expression_disabled",
category="Hardcore sexual poses",
subcategory="Penetrative sex",
seed=2301,
character_cast=cast,
women_count=1,
men_count=1,
hardcore_position_config=_action_filter("penetration_only"),
)
_expect_custom_row(row, "hardcore_expression_disabled")
_expect(not row.get("expression"), "expression should stay disabled without fallback")
_expect("Facial expressions:" not in row.get("prompt", ""), "disabled expression leaked into prompt")
_expect_formatter_outputs(row, "hardcore_expression_disabled", target="single")
def smoke_formatter_metadata_fixtures() -> None:
cases = [
{
"name": "fixture_penetration_text_noise",
"row": _fixture_hardcore_row(),
"krea_terms": ("penis thrusts",),
"sdxl_terms": ("penetrative sex", "missionary"),
"caption_terms": ("penetrative action",),
},
{
"name": "fixture_manual_source_family",
"row": _fixture_hardcore_row(
subcategory="Manual stimulation",
subcategory_slug="manual_stimulation",
item="wet fingers moving between the thighs, one hand braced on the hip, wet shine on fingers and inner thighs",
custom_item="Manual stimulation",
item_axis_values={
"position": "kneeling hand-between-thighs position",
"manual_act": "wet fingers moving between the thighs",
},
composition="close crop on hands and face",
source_composition="close crop on hands and face",
role_graph=(
"Woman A reclines with thighs open while Man A's hand is between her legs, "
"fingers visibly stimulating her pussy."
),
source_role_graph=(
"Woman A reclines with thighs open while Man A's hand is between her legs, "
"fingers visibly stimulating her pussy."
),
action_family="foreplay",
position_family="manual",
position_key="fingering",
position_keys=["fingering", "open_thighs"],
),
"krea_terms": ("fingers visibly stimulating",),
"sdxl_terms": ("manual stimulation", "fingering"),
"caption_terms": ("manual action",),
},
{
"name": "fixture_climax_family",
"row": _fixture_hardcore_row(
subcategory="Cumshot and climax",
subcategory_slug="cumshot_climax",
item="external cumshot with cum on lower back and ass, explicit semen aftermath visible",
custom_item="Cumshot and climax",
item_axis_values={"position": "on all fours with hips raised"},
composition="low-angle post-orgasm frame",
source_composition="low-angle post-orgasm frame",
role_graph=(
"Woman A is on all fours with hips raised while Man A is positioned behind her "
"and ejaculates semen across her ass, thighs, and lower back."
),
source_role_graph=(
"Woman A is on all fours with hips raised while Man A is positioned behind her "
"and ejaculates semen across her ass, thighs, and lower back."
),
action_family="climax",
position_family="climax",
position_key="doggy",
position_keys=["doggy"],
),
"krea_terms": ("ejaculates semen",),
"sdxl_terms": ("climax", "semen"),
"caption_terms": ("climax action",),
},
]
for case in cases:
name = case["name"]
row = case["row"]
_expect_custom_row(row, name)
metadata = _json(row)
krea = krea_formatter.format_krea2_prompt("", metadata_json=metadata, target="single")
krea_prompt = _expect_text(f"{name}.krea_prompt", krea.get("krea_prompt"), 40).lower()
_expect("metadata" in krea.get("method", ""), f"{name}.krea did not use metadata")
_expect("role graph:" not in krea_prompt and "sexual pose:" not in krea_prompt, f"{name}.krea leaked raw labels")
for term in case["krea_terms"]:
_expect(term in krea_prompt, f"{name}.krea missing {term!r}")
sdxl = sdxl_formatter.format_sdxl_prompt("", metadata_json=metadata, target="single", trigger=SdxlTrigger, prepend_trigger=True)
sdxl_prompt = _expect_text(f"{name}.sdxl_prompt", sdxl.get("sdxl_prompt"), 40).lower()
_expect("metadata" in sdxl.get("method", ""), f"{name}.sdxl did not use metadata")
for term in case["sdxl_terms"]:
_expect(term in sdxl_prompt, f"{name}.sdxl missing {term!r}")
caption, method = caption_naturalizer.naturalize_caption("", metadata_json=metadata, trigger=Trigger, include_trigger=True)
caption_text = _expect_text(f"{name}.caption", caption, 40).lower()
_expect("metadata" in method, f"{name}.caption did not use metadata")
for term in case["caption_terms"]:
_expect(term in caption_text, f"{name}.caption missing {term!r}")
route_row = _fixture_hardcore_row(
formatter_hints={
"all": ["shared route anchor"],
"krea": ["krea readable anchor"],
"sdxl": ["sdxl route tag"],
"caption": ["caption route phrase"],
}
)
_expect_custom_row(route_row, "fixture_formatter_hints")
metadata = _json(route_row)
krea = krea_formatter.format_krea2_prompt("", metadata_json=metadata, target="single")
krea_prompt = _expect_text("fixture_formatter_hints.krea_prompt", krea.get("krea_prompt"), 40).lower()
_expect("shared route anchor" in krea_prompt, "Krea formatter missed shared formatter hint")
_expect("krea readable anchor" in krea_prompt, "Krea formatter missed Krea formatter hint")
_expect("sdxl route tag" not in krea_prompt, "Krea formatter leaked SDXL formatter hint")
_expect("caption route phrase" not in krea_prompt, "Krea formatter leaked caption formatter hint")
sdxl = sdxl_formatter.format_sdxl_prompt("", metadata_json=metadata, target="single", trigger=SdxlTrigger, prepend_trigger=True)
sdxl_prompt = _expect_text("fixture_formatter_hints.sdxl_prompt", sdxl.get("sdxl_prompt"), 40).lower()
_expect("shared route anchor" in sdxl_prompt, "SDXL formatter missed shared formatter hint")
_expect("sdxl route tag" in sdxl_prompt, "SDXL formatter missed SDXL formatter hint")
_expect("krea readable anchor" not in sdxl_prompt, "SDXL formatter leaked Krea formatter hint")
_expect("caption route phrase" not in sdxl_prompt, "SDXL formatter leaked caption formatter hint")
caption, method = caption_naturalizer.naturalize_caption("", metadata_json=metadata, trigger=Trigger, include_trigger=True)
caption_text = _expect_text("fixture_formatter_hints.caption", caption, 40).lower()
_expect("metadata" in method, "Caption formatter hints fixture did not use metadata")
_expect("shared route anchor" in caption_text, "Caption naturalizer missed shared formatter hint")
_expect("caption route phrase" in caption_text, "Caption naturalizer missed caption formatter hint")
_expect("krea readable anchor" not in caption_text, "Caption naturalizer leaked Krea formatter hint")
_expect("sdxl route tag" not in caption_text, "Caption naturalizer leaked SDXL formatter hint")
def smoke_node_utility_registration() -> None:
required_nodes = [
"SxCPGlobalSeed",
"SxCPSeedControl",
"SxCPSeedLocker",
"SxCPSDXLBucketSize",
"SxCPKrea2ResolutionSelector",
]
for node_name in required_nodes:
_expect(node_name in sxcp_nodes.NODE_CLASS_MAPPINGS, f"{node_name} missing from node registry")
_expect(node_name in sxcp_nodes.NODE_DISPLAY_NAME_MAPPINGS, f"{node_name} missing from display registry")
seed_control = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPSeedControl"]
seed_inputs = seed_control.INPUT_TYPES().get("required") or {}
_expect("category_seed_mode" in seed_inputs, "Seed Control lost category seed mode input")
_expect("tooltip" in seed_inputs["category_seed_mode"][1], "Seed Control tooltip injection missing")
_expect(
node_tooltips._tooltip_for_input("SxCPSeedControl", "category_seed_mode")
== "auto/follow_main follows the main seed; fixed uses category_seed; random rerolls this axis each queue.",
"Node tooltip policy lost Seed Control override",
)
_expect(
"Autoscaling switch input" in node_tooltips._tooltip_for_input("SxCPIndexSwitch", "input_12"),
"Node tooltip policy lost autoscaling input fallback",
)
seed, seed_config, summary = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPGlobalSeed"]().build(12345)
parsed_seed = json.loads(seed_config)
_expect(seed == 12345, "Global Seed did not return the clamped seed")
_expect(parsed_seed, "Global Seed config should not be empty")
_expect(all(int(value) == 12345 for value in parsed_seed.values()), "Global Seed config did not lock every axis")
_expect("all axes locked" in summary, "Global Seed summary changed unexpectedly")
locker_config, locker_summary = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPSeedLocker"]().build(12345, "pose", 999)
parsed_locker = json.loads(locker_config)
_expect(parsed_locker.get("pose_seed") == 999, "Seed Locker did not apply pose reroll seed")
_expect("reroll pose" in locker_summary, "Seed Locker summary lost reroll axis")
bucket_node = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPSDXLBucketSize"]()
bucket_a = bucket_node.build("portrait", 77, 3, 0)
bucket_b = bucket_node.build("portrait", 77, 3, 0)
_expect(bucket_a == bucket_b, "SDXL bucket should be deterministic for fixed seed and row")
_expect(bucket_a[3] == "portrait", "SDXL bucket ignored orientation filter")
krea_node = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPKrea2ResolutionSelector"]()
krea_width, krea_height, _resolution, aspect_ratio, api_aspect, _api_resolution, *_rest = krea_node.select("1.0MP", "9:16")
krea_config = json.loads(_rest[-1])
_expect(krea_height > krea_width, "Krea2 9:16 selector should return portrait dimensions")
_expect(aspect_ratio == "9:16", "Krea2 selector lost requested aspect ratio")
_expect(api_aspect == "9:16", "Krea2 selector lost API aspect mapping")
_expect(krea_config.get("width") == krea_width and krea_config.get("height") == krea_height, "Krea2 config_json dimensions mismatch")
def smoke_server_route_payload_policy() -> None:
requested, selected, available = index_switch_policy.input_selection(
0,
"zero_based",
"fallback",
{"input_1": "first"},
)
_expect((requested, selected, available) == (1, 1, [1]), "Index switch policy zero-based selection changed")
_expect(
index_switch_policy.route_selection(65, "one_based", "wrap") == (65, 1),
"Index switch policy wrap routing changed",
)
_expect(
index_switch_policy.lazy_inputs(2, "pick_input", "one_based", "fallback", {"input_2": "second"}) == ["input_2"],
"Index switch policy lazy input selection changed",
)
switch = loop_nodes.SxCPIndexSwitch()
picked = switch.switch(
2,
"pick_input",
"one_based",
"fallback",
input_1="first",
input_2="second",
fallback="fallback",
)
_expect(picked[0] == "second", "Index Switch pick_input did not select the requested input")
_expect(picked[1] == 2, "Index Switch pick_input selected_index changed")
_expect("selected=input_2" in picked[2], "Index Switch pick_input status lost selected input")
routed = switch.switch(3, "route_output", "one_based", "fallback", route_value="routed")
_expect(routed[0] == "routed", "Index Switch route_output primary value changed")
_expect(routed[1] == 3, "Index Switch route_output selected_index changed")
_expect(routed[5] == "routed", "Index Switch route_output did not route value to output_3")
key = "smoke_route_payload"
loop_nodes._ACCUMULATOR_STORES[key] = [
{"id": "first", "value": "alpha", "_sxcp_preview_key": "first-key"},
{"id": "second", "value": "beta", "_sxcp_preview_key": "second-key"},
]
try:
listed = server_routes.accumulator_list_payload({"store_key": key, "preview_limit": "0"})
_expect(listed.get("count") == 2, "Accumulator list payload lost stored entries")
_expect(listed["entries"][0].get("value") == "alpha", "Accumulator list payload lost value summary")
moved = server_routes.accumulator_move_payload({"store_key": key, "entry_id": "second", "target_index": "1"})
_expect(moved.get("moved") is True, "Accumulator move payload did not report movement")
_expect(moved.get("from_index") == 2 and moved.get("to_index") == 1, "Accumulator move payload changed indices")
_expect(moved["entries"][0].get("id") == "second", "Accumulator move payload did not reorder entries")
deleted = server_routes.accumulator_delete_payload({"store_key": key, "preview_key": "first-key"})
_expect(deleted.get("removed") == 1, "Accumulator delete payload did not remove by preview key")
_expect(deleted.get("count") == 1, "Accumulator delete payload count changed")
cleared = server_routes.accumulator_delete_payload({"store_key": key, "clear": True})
_expect(cleared.get("removed") == 1 and cleared.get("count") == 0, "Accumulator clear payload changed")
finally:
loop_nodes._ACCUMULATOR_STORES.pop(key, None)
with tempfile.TemporaryDirectory() as tmpdir:
previous_profile_dir = character_profile.PROFILE_DIR
character_profile.PROFILE_DIR = Path(tmpdir)
try:
profile = character_profile.build_character_profile_json(
profile_name="route source",
source="manual",
subject_type="woman",
age="28-year-old adult",
body="slim",
hair="long black hair",
save_now=False,
)
saved = server_routes.profile_save_cached_payload(
{"profile_name": "Route Save!*", "profile_json": profile["profile_json"]}
)
saved_path = Path(saved.get("saved_path") or "")
_expect(saved.get("status") == "saved", "Profile save payload did not save")
_expect(saved.get("profile_name") == "Route_Save", "Profile save payload did not sanitize requested name")
_expect(saved_path.exists(), "Profile save payload did not write profile file")
finally:
character_profile.PROFILE_DIR = previous_profile_dir
def smoke_seed_config_policy() -> None:
_expect(pb.SEED_AXIS_SALTS is seed_config.SEED_AXIS_SALTS, "prompt_builder seed salts should delegate to seed_config")
_expect(pb.seed_mode_choices() == seed_config.seed_mode_choices(), "seed mode choices drifted from seed_config")
fixed_config = json.loads(
pb.build_seed_config_json(
category_seed=-1,
content_seed=123,
pose_seed=456,
role_seed=789,
category_seed_mode="fixed",
content_seed_mode="fixed",
pose_seed_mode="follow_main",
role_seed_mode="auto",
)
)
_expect(fixed_config["category_seed"] == 0, "fixed seed mode should clamp negative seeds to zero")
_expect(fixed_config["content_seed"] == 123, "fixed seed mode should preserve positive seed")
_expect(fixed_config["pose_seed"] == -1, "follow_main seed mode should emit unlocked axis")
_expect(fixed_config["role_seed"] == 789, "auto seed mode should preserve numeric seed")
parsed = pb._parse_seed_config({"item_seed": "44", "pose_seed": "55", "bad": "nope"})
_expect(parsed == {"item_seed": 44, "pose_seed": 55}, "seed parser should keep integer-like values only")
_expect(pb._configured_axis_seed(parsed, "content") == 44, "content axis should honor item_seed alias")
_expect(pb._configured_axis_seed(parsed, "role") == 55, "role axis should honor pose seed alias")
locked = json.loads(pb.build_seed_lock_config_json(base_seed=100, reroll_axis="content_pose", reroll_seed=999))
_expect(locked["content_seed"] == 999, "content_pose reroll should alter content seed")
_expect(locked["pose_seed"] == 999 and locked["role_seed"] == 999, "content_pose reroll should alter pose and role seeds")
_expect(locked["scene_seed"] == 100, "content_pose reroll should leave scene locked")
rng_a = pb._axis_rng({"content_seed": 123}, "content", 999, 7)
rng_b = seed_config.axis_rng({"content_seed": 123}, "content", 999, 7)
_expect(rng_a.random() == rng_b.random(), "prompt_builder axis RNG should delegate to seed_config")
_expect(pb._row_seed(123, 7, 41) == seed_config.row_seed(123, 7, 41), "row seed wrapper drifted from seed_config")
def smoke_node_camera_registration() -> None:
required_nodes = [
"SxCPCameraControl",
"SxCPCameraOrbitControl",
"SxCPQwenCameraTranslator",
]
for node_name in required_nodes:
_expect(node_name in sxcp_nodes.NODE_CLASS_MAPPINGS, f"{node_name} missing from node registry")
_expect(node_name in sxcp_nodes.NODE_DISPLAY_NAME_MAPPINGS, f"{node_name} missing from display registry")
camera_control = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPCameraControl"]
camera_inputs = camera_control.INPUT_TYPES().get("required") or {}
_expect("camera_mode" in camera_inputs, "Camera Control lost camera_mode input")
_expect("tooltip" in camera_inputs["camera_mode"][1], "Camera Control tooltip injection missing")
camera_config = camera_control().build(
"handheld_selfie",
"three_quarter_body",
"high_angle",
"smartphone_wide",
"arm_length",
"vertical_story",
"phone_visible",
"locked",
"compact",
)[0]
parsed_camera = json.loads(camera_config)
_expect(parsed_camera.get("camera_mode") == "handheld_selfie", "Camera Control lost camera_mode")
_expect(parsed_camera.get("phone_visibility") == "phone_visible", "Camera Control lost phone visibility")
orbit_config, orbit_prompt, orbit_info = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPCameraOrbitControl"]().build(
True,
"standard",
45,
0,
5.5,
"from_zoom",
"auto",
"auto",
"auto",
"auto",
"soft_hint",
"compact",
True,
)
parsed_orbit = json.loads(orbit_config)
_expect(parsed_orbit.get("camera_source") == "orbit", "Orbit camera lost source metadata")
_expect("front-right quarter view" in orbit_prompt, "Orbit camera prompt lost direction")
_expect(json.loads(orbit_info).get("orbit_azimuth") == 45, "Orbit info JSON lost azimuth")
qwen_node = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPQwenCameraTranslator"]
qwen_inputs = qwen_node.INPUT_TYPES()
_expect("camera_info" in (qwen_inputs.get("optional") or {}), "Qwen translator lost camera_info optional input")
qwen_config, qwen_prompt, qwen_info = qwen_node().build(
"<sks> front-right quarter view eye-level shot medium shot",
True,
"standard",
"auto",
"auto",
"auto",
"auto",
"soft_hint",
"compact",
False,
True,
)
parsed_qwen = json.loads(qwen_config)
_expect(parsed_qwen.get("camera_source") == "qwen_multiangle_prompt", "Qwen translator lost source metadata")
_expect(parsed_qwen.get("phone_visibility") == "auto", "Qwen translator should suppress phone visibility by default")
_expect("front-right quarter view" in qwen_prompt, "Qwen camera prompt lost direction")
_expect(json.loads(qwen_info).get("qwen_prompt", "").startswith("<sks>"), "Qwen info JSON lost original prompt")
def smoke_node_route_config_registration() -> None:
required_nodes = [
"SxCPCategoryPreset",
"SxCPLocationPool",
"SxCPCompositionPool",
"SxCPLocationTheme",
"SxCPCastControl",
"SxCPCastBias",
]
for node_name in required_nodes:
_expect(node_name in sxcp_nodes.NODE_CLASS_MAPPINGS, f"{node_name} missing from node registry")
_expect(node_name in sxcp_nodes.NODE_DISPLAY_NAME_MAPPINGS, f"{node_name} missing from display registry")
category_node = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPCategoryPreset"]
category_inputs = category_node.INPUT_TYPES().get("required") or {}
_expect("preset" in category_inputs, "Category Preset lost preset input")
_expect("tooltip" in category_inputs["preset"][1], "Category Preset tooltip injection missing")
category_config, category, subcategory = category_node().build("auto_weighted", "random")
parsed_category = json.loads(category_config)
_expect(category == parsed_category.get("category") == "auto_weighted", "Category Preset output category mismatch")
_expect(subcategory == "random", "Category Preset output subcategory mismatch")
location_config, location_summary = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPLocationPool"]().build(
True,
"replace",
"custom_only",
"classical library stacks with brass lamps",
)
parsed_location = json.loads(location_config)
_expect(parsed_location.get("scene_entries"), "Location Pool did not keep custom location")
_expect("locations=1" in location_summary, "Location Pool summary lost custom count")
composition_config, composition_summary = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPCompositionPool"]().build(
True,
"replace",
"no_outfit_check",
"long aisle composition with shelves repeating behind the subject",
)
parsed_composition = json.loads(composition_config)
_expect(parsed_composition.get("composition_entries"), "Composition Pool did not keep composition entries")
_expect("compositions=" in composition_summary, "Composition Pool summary lost composition count")
theme_location, theme_composition, theme_summary = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPLocationTheme"]().build(
True,
"replace",
"semi_public_affair",
"",
"",
)
_expect(json.loads(theme_location).get("scene_entries"), "Location Theme did not output locations")
_expect(json.loads(theme_composition).get("composition_entries"), "Location Theme did not output compositions")
_expect("semi_public_affair" in theme_summary, "Location Theme summary lost theme name")
cast_config, women_count, men_count, cast_summary = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPCastControl"]().build(
"mixed_couple",
1,
1,
)
parsed_cast = json.loads(cast_config)
_expect((women_count, men_count) == (parsed_cast.get("women_count"), parsed_cast.get("men_count")), "Cast Control count outputs mismatch")
_expect("1 women, 1 men" in cast_summary, "Cast Control summary changed unexpectedly")
cast_bias = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPCastBias"]()
bias_a = cast_bias.build(123, 2, "0.7,0.3", 1, "0.4,0.6", 0, "force_one_woman")
bias_b = cast_bias.build(123, 2, "0.7,0.3", 1, "0.4,0.6", 0, "force_one_woman")
_expect(bias_a == bias_b, "Cast Bias should be deterministic for fixed seed and row")
_expect(bias_a[1] + bias_a[2] >= 1, "Cast Bias empty behavior allowed empty cast")
_expect("weighted cast:" in bias_a[3], "Cast Bias summary lost weighted cast label")
def smoke_node_character_registration() -> None:
required_nodes = [
"SxCPHairLength",
"SxCPHairColor",
"SxCPHairStyle",
"SxCPCharacterAgeRange",
"SxCPCharacterBodyPool",
"SxCPWomanBodyPool",
"SxCPManBodyPool",
"SxCPEyeColorPool",
"SxCPCharacterClothing",
"SxCPCharacterManualDetails",
"SxCPWomanSlot",
"SxCPManSlot",
"SxCPCharacterSlot",
"SxCPCharacterProfileSave",
"SxCPCharacterProfileLoad",
]
for node_name in required_nodes:
_expect(node_name in sxcp_nodes.NODE_CLASS_MAPPINGS, f"{node_name} missing from node registry")
_expect(node_name in sxcp_nodes.NODE_DISPLAY_NAME_MAPPINGS, f"{node_name} missing from display registry")
woman_slot_node = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPWomanSlot"]
woman_slot_inputs = woman_slot_node.INPUT_TYPES().get("required") or {}
_expect("slot_seed" in woman_slot_inputs, "Woman Slot lost slot_seed input")
_expect("tooltip" in woman_slot_inputs["slot_seed"][1], "Woman Slot tooltip injection missing")
hair_config, hair_summary = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPHairColor"]().build(
"replace_axis",
"",
include_blonde=True,
)
parsed_hair = json.loads(hair_config)
_expect(parsed_hair.get("colors") == ["blonde"], "Hair Color did not output selected blonde pool")
_expect("colors=blonde" in hair_summary, "Hair Color summary changed unexpectedly")
age_config, age_summary = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPCharacterAgeRange"]().build(
"replace_axis",
25,
27,
)
parsed_age = json.loads(age_config)
_expect(parsed_age.get("ages") == ["25-year-old adult", "26-year-old adult", "27-year-old adult"], "Age Range output changed")
_expect("25-year-old adult" in age_summary, "Age Range summary lost selected ages")
body_config, _body_summary = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPWomanBodyPool"]().build(
"replace_axis",
"",
include_curvy=True,
)
_expect(json.loads(body_config).get("bodies") == ["curvy"], "Woman Body Pool did not output selected body")
eye_config, _eye_summary = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPEyeColorPool"]().build(
"replace_axis",
"",
include_blue=True,
)
_expect(json.loads(eye_config).get("eyes") == ["blue"], "Eye Color Pool did not output selected eye color")
clothing_config, clothing_summary = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPCharacterClothing"]().build(
"replace_axis",
"lingerie_tease",
"fully_nude",
"",
"",
)
parsed_clothing = json.loads(clothing_config)
_expect(parsed_clothing.get("softcore_outfits"), "Character Clothing lost softcore outfit pool")
_expect(parsed_clothing.get("hardcore_clothing") == ["fully nude"], "Character Clothing lost hardcore clothing state")
_expect("soft_outfits=" in clothing_summary, "Character Clothing summary lost outfit count")
manual_config, manual_summary = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPCharacterManualDetails"]().build(
"merge_nonempty",
"31-year-old adult",
"curvy",
"custom body phrase",
"warm skin",
"short blonde hair",
"blue eyes",
"red dress",
"fully nude",
)
parsed_manual = json.loads(manual_config)
_expect(parsed_manual.get("manual_age") == "31-year-old adult", "Manual Details lost manual_age")
_expect(parsed_manual.get("softcore_outfit") == "red dress", "Manual Details lost softcore outfit")
_expect("manual_age=31-year-old adult" in manual_summary, "Manual Details summary changed unexpectedly")
cast, slot, slot_summary, slot_status = woman_slot_node().build(
True,
"A",
123,
"25-year-old adult",
"western_european",
"balanced",
"curvy",
"full",
True,
0.5,
0.4,
0.8,
"",
"",
"",
hair_config,
"",
)
parsed_slot = json.loads(slot)
_expect(parsed_slot.get("subject_type") == "woman", "Woman Slot output lost subject type")
_expect(parsed_slot.get("slot_seed") == 123, "Woman Slot output lost slot seed")
_expect("Woman A" in slot_summary, "Woman Slot summary lost label")
_expect("1 slot(s)" in slot_status, "Woman Slot status lost cast count")
_expect(json.loads(cast).get("slots"), "Woman Slot did not output chained cast JSON")
man_cast, man_slot, _man_summary, _man_status = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPManSlot"]().build(
True,
"A",
124,
"40-year-old adult",
"western_european",
"average",
"compact",
True,
0.3,
"pov",
0.2,
0.7,
cast,
"",
"",
"",
"",
)
_expect(json.loads(man_slot).get("presence_mode") == "pov", "Man Slot output lost POV presence mode")
_expect(len(json.loads(man_cast).get("slots") or []) == 2, "Man Slot did not append to incoming cast")
save_result = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPCharacterProfileSave"]().build(
"smoke_profile",
"character_slot",
"woman",
"",
"",
"",
"",
"",
"",
"",
False,
character_slot=slot,
)
saved_profile = save_result["result"][0]
_expect(save_result["result"][2] == "smoke_profile", "Profile Save lost profile name")
_expect(save_result["result"][4] == "not_saved", "Profile Save should not write when save_now is false")
loaded_profile = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPCharacterProfileLoad"]().build(
True,
"manual",
"",
False,
False,
fallback_profile_json=saved_profile,
)
_expect(loaded_profile[4] == "fallback", "Profile Load should consume fallback profile JSON")
_expect(json.loads(loaded_profile[0]).get("profile_type") == "character", "Profile Load returned wrong profile type")
def smoke_node_hardcore_position_registration() -> None:
required_nodes = [
"SxCPHardcorePositionPool",
"SxCPHardcoreActionFilter",
]
for node_name in required_nodes:
_expect(node_name in sxcp_nodes.NODE_CLASS_MAPPINGS, f"{node_name} missing from node registry")
_expect(node_name in sxcp_nodes.NODE_DISPLAY_NAME_MAPPINGS, f"{node_name} missing from display registry")
position_node = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPHardcorePositionPool"]
position_inputs = position_node.INPUT_TYPES().get("required") or {}
_expect("family" in position_inputs, "Hardcore Position Pool lost family input")
_expect("tooltip" in position_inputs["family"][1], "Hardcore Position Pool tooltip injection missing")
pool_config, pool_summary = position_node().build(
"replace",
"oral",
"",
include_boobjob=True,
include_handjob=True,
)
parsed_pool = json.loads(pool_config)
_expect(parsed_pool.get("family") == "oral", "Hardcore Position Pool lost selected family")
_expect(parsed_pool.get("positions") == ["boobjob", "handjob"], "Hardcore Position Pool lost selected positions")
_expect("positions=boobjob,handjob" in pool_summary, "Hardcore Position Pool summary changed unexpectedly")
filter_config, filter_summary = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPHardcoreActionFilter"]().build(
"outercourse_only",
False,
False,
False,
True,
True,
True,
False,
True,
False,
False,
pool_config,
)
parsed_filter = json.loads(filter_config)
_expect(parsed_filter.get("family") == "outercourse", "Hardcore Action Filter did not apply focus family")
_expect(parsed_filter.get("positions") == ["boobjob", "handjob"], "Hardcore Action Filter lost incoming positions")
_expect(parsed_filter.get("allow_penetration") is False, "Hardcore Action Filter did not block penetration")
_expect(parsed_filter.get("allow_outercourse") is True, "Hardcore Action Filter should allow outercourse")
_expect("blocked=" in filter_summary, "Hardcore Action Filter summary lost blocked-gate details")
def smoke_node_formatter_registration() -> None:
required_nodes = [
"SxCPCaptionNaturalizer",
"SxCPKrea2Formatter",
"SxCPSDXLFormatter",
]
for node_name in required_nodes:
_expect(node_name in sxcp_nodes.NODE_CLASS_MAPPINGS, f"{node_name} missing from node registry")
_expect(node_name in sxcp_nodes.NODE_DISPLAY_NAME_MAPPINGS, f"{node_name} missing from display registry")
krea_node = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPKrea2Formatter"]
krea_inputs = krea_node.INPUT_TYPES().get("required") or {}
_expect("source_text" in krea_inputs, "Krea2 Formatter lost source_text input")
_expect("tooltip" in krea_inputs["source_text"][1], "Krea2 Formatter tooltip injection missing")
caption, caption_method = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPCaptionNaturalizer"]().build(
"A woman standing by a window, best quality",
"caption_or_prompt",
"manual_controls",
"concise",
"drop_style_tail",
"sxcppnl7",
True,
)
_expect_text("node_formatter.caption", caption, 20)
_expect(caption.startswith("sxcppnl7"), "Caption Naturalizer did not prepend trigger")
_expect("text(" in caption_method, "Caption Naturalizer method changed unexpectedly")
caption_inputs = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPCaptionNaturalizer"].INPUT_TYPES().get("required") or {}
_expect("caption_profile" in caption_inputs, "Caption Naturalizer lost caption_profile input")
_expect("tooltip" in caption_inputs["caption_profile"][1], "Caption profile tooltip injection missing")
krea_output = krea_node().build(
"sxcppnl7 A woman standing by a window",
"prompt",
"single",
"concise",
"preserve",
True,
)
_expect_text("node_formatter.krea_prompt", krea_output[0], 20)
_expect("sxcppnl7" in krea_output[0], "Krea2 Formatter did not preserve trigger when enabled")
_expect(krea_output[6].startswith("text("), "Krea2 Formatter method changed unexpectedly")
sdxl_output = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPSDXLFormatter"]().build(
"A woman standing by a window",
"prompt",
"single",
"manual_controls",
"flat_vector_pony",
"pony_high",
"mythp0rt",
True,
False,
1.29,
)
_expect_text("node_formatter.sdxl_prompt", sdxl_output[0], 40)
_expect_trigger_once("node_formatter.sdxl_prompt", sdxl_output[0], "mythp0rt")
_expect_text("node_formatter.sdxl_negative", sdxl_output[1], 20)
_expect(sdxl_output[6].startswith("text("), "SDXL Formatter method changed unexpectedly")
sdxl_inputs = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPSDXLFormatter"].INPUT_TYPES().get("required") or {}
_expect("formatter_profile" in sdxl_inputs, "SDXL Formatter lost formatter_profile input")
_expect("tooltip" in sdxl_inputs["formatter_profile"][1], "SDXL formatter_profile tooltip injection missing")
def smoke_node_insta_registration() -> None:
required_nodes = [
"SxCPInstaOFOptions",
"SxCPInstaOFPromptPair",
]
for node_name in required_nodes:
_expect(node_name in sxcp_nodes.NODE_CLASS_MAPPINGS, f"{node_name} missing from node registry")
_expect(node_name in sxcp_nodes.NODE_DISPLAY_NAME_MAPPINGS, f"{node_name} missing from display registry")
options_node = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPInstaOFOptions"]
options_inputs = options_node.INPUT_TYPES().get("required") or {}
_expect("hardcore_detail_density" in options_inputs, "Insta/OF Options lost hardcore_detail_density input")
_expect("tooltip" in options_inputs["hardcore_detail_density"][1], "Insta/OF Options tooltip injection missing")
options_json = options_node().build(
"same_as_hardcore",
"couple",
1,
1,
"lingerie_tease",
"hardcore",
True,
True,
0.45,
0.85,
"hybrid",
"same_creator_same_room",
"explicit_nude",
"standard",
"standard",
"compact",
"balanced",
)[0]
parsed_options = json.loads(options_json)
_expect(parsed_options.get("softcore_cast") == "same_as_hardcore", "Insta/OF Options lost softcore cast")
_expect(parsed_options.get("hardcore_cast") == "couple", "Insta/OF Options lost hardcore cast")
_expect(parsed_options.get("hardcore_clothing_continuity") == "explicit_nude", "Insta/OF Options lost clothing continuity")
pair_output = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPInstaOFPromptPair"]().build(
1,
41,
123,
"any",
"random",
Trigger,
True,
options_json=options_json,
)
_expect_text("node_insta.softcore_prompt", pair_output[0], 20)
_expect_text("node_insta.hardcore_prompt", pair_output[1], 20)
pair = json.loads(pair_output[7])
_expect_pair(pair, "node_insta_pair")
_expect(pair.get("options", {}).get("hardcore_cast") == "couple", "Insta/OF Prompt Pair lost options metadata")
def smoke_node_builder_registration() -> None:
required_nodes = [
"SxCPPromptBuilder",
"SxCPPromptBuilderFromConfigs",
]
for node_name in required_nodes:
_expect(node_name in sxcp_nodes.NODE_CLASS_MAPPINGS, f"{node_name} missing from node registry")
_expect(node_name in sxcp_nodes.NODE_DISPLAY_NAME_MAPPINGS, f"{node_name} missing from display registry")
builder_node = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPPromptBuilder"]
builder_inputs = builder_node.INPUT_TYPES().get("required") or {}
_expect("category" in builder_inputs, "Prompt Builder lost category input")
_expect("tooltip" in builder_inputs["category"][1], "Prompt Builder tooltip injection missing")
direct_output = builder_node().build(
"woman",
"random",
1,
41,
123,
"full",
"any",
"standard",
True,
0.5,
0.0,
"random",
1,
0,
-1,
-1,
Trigger,
True,
)
direct_row = json.loads(direct_output[3])
_expect_row_base(direct_row, "node_builder.direct_row")
_expect(direct_output[0] == direct_row.get("prompt"), "Prompt Builder prompt output drifted from metadata")
_expect(direct_output[4] == direct_row.get("main_category"), "Prompt Builder category output drifted from metadata")
_expect_trigger_once("node_builder.direct_prompt", direct_output[0], Trigger)
config_node = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPPromptBuilderFromConfigs"]
config_inputs = config_node.INPUT_TYPES()
_expect("category_config" in (config_inputs.get("optional") or {}), "Prompt Builder From Configs lost category_config input")
config_output = config_node().build(
1,
41,
123,
category_config=pb.build_category_config_json("woman", "random"),
cast_config=pb.build_cast_config_json("solo_woman", 1, 0),
generation_profile=pb.build_generation_profile_json(profile="balanced"),
)
config_row = json.loads(config_output[3])
_expect_row_base(config_row, "node_builder.config_row")
_expect(config_output[0] == config_row.get("prompt"), "Prompt Builder From Configs prompt output drifted from metadata")
_expect(config_output[4] == config_row.get("main_category"), "Prompt Builder From Configs category output drifted from metadata")
_expect_text("node_builder.config_caption", config_output[2], 20)
def smoke_node_profile_filter_registration() -> None:
required_nodes = [
"SxCPGenerationProfile",
"SxCPAdvancedFilters",
"SxCPEthnicityList",
]
for node_name in required_nodes:
_expect(node_name in sxcp_nodes.NODE_CLASS_MAPPINGS, f"{node_name} missing from node registry")
_expect(node_name in sxcp_nodes.NODE_DISPLAY_NAME_MAPPINGS, f"{node_name} missing from display registry")
profile_node = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPGenerationProfile"]
profile_inputs = profile_node.INPUT_TYPES().get("required") or {}
_expect("profile" in profile_inputs, "Generation Profile lost profile input")
_expect("tooltip" in profile_inputs["profile"][1], "Generation Profile tooltip injection missing")
profile_config, profile_summary = profile_node().build(
profile="balanced",
clothing_override="profile_default",
poses_override="profile_default",
expression_enabled=True,
expression_intensity_mode="fixed",
expression_intensity=0.5,
backside_bias=-1,
minimal_clothing_ratio=-1,
standard_pose_ratio=-1,
trigger_policy="profile_default",
)
parsed_profile = json.loads(profile_config)
_expect(parsed_profile.get("profile") == "balanced", "Generation Profile output lost profile")
_expect(parsed_profile.get("expression_intensity") == 0.5, "Generation Profile output lost fixed expression intensity")
_expect("balanced:" in profile_summary, "Generation Profile summary changed unexpectedly")
filter_config = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPAdvancedFilters"]().build(
include_european=True,
include_mediterranean_mena=False,
include_latina=False,
include_east_asian=False,
include_southeast_asian=False,
include_south_asian=False,
include_black_african=True,
include_indigenous=False,
include_mixed=False,
include_plus_size=False,
figure="curvy",
)[0]
parsed_filter = json.loads(filter_config)
_expect(parsed_filter.get("figure") == "curvy", "Advanced Filters lost figure")
_expect(parsed_filter.get("ethnicity_includes") == ["european", "black_african"], "Advanced Filters ethnicity includes changed")
_expect(parsed_filter.get("no_plus_women") is True, "Advanced Filters should set no_plus_women when plus size is excluded")
ethnicity, ethnicity_filter_config, ethnicity_summary = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPEthnicityList"]().build(
include_european=False,
include_mediterranean_mena=False,
include_latina=False,
include_east_asian=False,
include_southeast_asian=False,
include_south_asian=False,
include_black_african=False,
include_indigenous=False,
include_mixed=False,
include_asian=False,
include_white_asian=False,
include_western_european=False,
include_french_european=True,
include_germanic_european=False,
include_nordic_european=False,
include_celtic_european=False,
include_slavic_european=False,
include_baltic_european=False,
include_alpine_european=False,
include_balkan_european=False,
include_greek_mediterranean=False,
include_italian_mediterranean=False,
include_iberian_mediterranean=False,
strict_excludes=True,
)
parsed_ethnicity_filter = json.loads(ethnicity_filter_config)
_expect("french_european" in ethnicity, "Ethnicity List output lost selected regional ethnicity")
_expect(parsed_ethnicity_filter.get("ethnicity_includes") == ["french_european"], "Ethnicity List filter output changed")
_expect("ethnicity list:" in ethnicity_summary, "Ethnicity List summary changed unexpectedly")
SMOKE_CASES: list[tuple[str, Callable[[], None]]] = [
("builtin_single_woman", smoke_builtin_single),
("camera_scene_single", smoke_camera_scene_single),
("row_camera_policy", smoke_row_camera_policy),
("config_route_location_theme", smoke_config_route_location_theme),
("krea_normal_row_routes", smoke_krea_normal_row_routes),
("location_config_policy", smoke_location_config_policy),
("row_location_policy", smoke_row_location_policy),
("row_expression_policy", smoke_row_expression_policy),
("row_prompt_axes_policy", smoke_row_prompt_axes_policy),
("row_item_policy", smoke_row_item_policy),
("row_category_route_policy", smoke_row_category_route_policy),
("row_generation_policy", smoke_row_generation_policy),
("category_extensions_policy", smoke_category_extensions_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),
("filter_config_policy", smoke_filter_config_policy),
("character_config_policy", smoke_character_config_policy),
("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),
("caption_policy", smoke_caption_policy),
("caption_metadata_routes", smoke_caption_metadata_routes),
("sdxl_presets_policy", smoke_sdxl_presets_policy),
("sdxl_tag_routes", smoke_sdxl_tag_routes),
("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),
("hardcore_category_routes", smoke_hardcore_category_routes),
("krea_close_foreplay_route", smoke_krea_close_foreplay_route),
("pair_options_policy", smoke_pair_options_policy),
("pair_route_policy", smoke_pair_route_policy),
("pair_builder_policy", smoke_pair_builder_policy),
("insta_pair_same_cast", smoke_insta_pair),
("krea_pair_clothing_state", smoke_krea_pair_clothing_state),
("insta_pair_pov_man", smoke_insta_pair_pov),
("insta_pair_camera_split", smoke_insta_pair_camera_split),
("pov_camera_scene", smoke_pov_camera_scene),
("krea_pov_penetration_route", smoke_krea_pov_penetration_route),
("pov_outercourse_position_routes", smoke_pov_outercourse_position_routes),
("pov_oral_position_routes", smoke_pov_oral_position_routes),
("pov_penetration_position_routes", smoke_pov_penetration_position_routes),
("pov_anal_position_routes", smoke_pov_anal_position_routes),
("double_front_back_route", smoke_double_front_back_route),
("climax_position_routes", smoke_climax_position_routes),
("interaction_role_graph_routes", smoke_interaction_role_graph_routes),
("fallback_role_graph_routes", smoke_fallback_role_graph_routes),
("expression_disabled", smoke_no_expression_fallback),
("formatter_metadata_fixtures", smoke_formatter_metadata_fixtures),
("node_utility_registration", smoke_node_utility_registration),
("server_route_payload_policy", smoke_server_route_payload_policy),
("seed_config_policy", smoke_seed_config_policy),
("node_camera_registration", smoke_node_camera_registration),
("node_route_config_registration", smoke_node_route_config_registration),
("node_character_registration", smoke_node_character_registration),
("node_hardcore_position_registration", smoke_node_hardcore_position_registration),
("node_formatter_registration", smoke_node_formatter_registration),
("node_insta_registration", smoke_node_insta_registration),
("node_builder_registration", smoke_node_builder_registration),
("node_profile_filter_registration", smoke_node_profile_filter_registration),
]
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--case",
choices=[name for name, _func in SMOKE_CASES],
action="append",
help="Run only the named smoke case. Can be passed multiple times.",
)
args = parser.parse_args(argv)
selected = set(args.case or [])
report = SmokeReport()
for name, func in SMOKE_CASES:
if selected and name not in selected:
continue
try:
func()
except Exception as exc: # noqa: BLE001 - report all smoke failures uniformly.
report.fail(name, str(exc))
else:
report.ok(name)
print(f"\nSummary: {len(report.passed)} passed, {len(report.failed)} failed")
if report.failed:
return 1
return 0
if __name__ == "__main__":
raise SystemExit(main())