16792 lines
859 KiB
Python
16792 lines
859 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 subprocess
|
|
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) in sys.path:
|
|
sys.path.remove(str(ROOT))
|
|
sys.path.insert(0, str(ROOT))
|
|
|
|
import caption_naturalizer # noqa: E402
|
|
import caption_format_route # noqa: E402
|
|
import caption_metadata_routes # noqa: E402
|
|
import caption_policy # noqa: E402
|
|
import caption_text_policy # noqa: E402
|
|
import builder_config_route # noqa: E402
|
|
import builder_prompt_route # 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_detail # noqa: E402
|
|
import formatter_input # noqa: E402
|
|
import formatter_target # noqa: E402
|
|
import hardcore_action_metadata # noqa: E402
|
|
import hardcore_position_config # noqa: E402
|
|
import __init__ as sxcp_nodes # noqa: E402
|
|
import generation_profile_config # noqa: E402
|
|
import hardcore_role_interaction # noqa: E402
|
|
import hardcore_role_oral # noqa: E402
|
|
import hardcore_role_outercourse # noqa: E402
|
|
import index_switch_policy # noqa: E402
|
|
import item_axis_policy # noqa: E402
|
|
import node_tooltips # noqa: E402
|
|
import krea_cast # noqa: E402
|
|
import krea_action_details # noqa: E402
|
|
import krea_action_context # noqa: E402
|
|
import krea_pov_actions # noqa: E402
|
|
import krea_configured_cast_formatter # noqa: E402
|
|
import krea_format_route # noqa: E402
|
|
import krea_formatter # noqa: E402
|
|
import krea_normal_formatter # noqa: E402
|
|
import krea_pair_formatter # noqa: E402
|
|
import krea2_eval_log # noqa: E402
|
|
import krea2_pose_variant_catalog # noqa: E402
|
|
import krea2_tuning_report # noqa: E402
|
|
import krea_row_fields # 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_hygiene # noqa: E402
|
|
import prompt_builder as pb # noqa: E402
|
|
import outercourse_action_policy # 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 scene_camera_adapters # noqa: E402
|
|
import server_routes # noqa: E402
|
|
import sdxl_formatter # noqa: E402
|
|
import sdxl_format_route # noqa: E402
|
|
import sdxl_presets # noqa: E402
|
|
import sdxl_tag_policy # noqa: E402
|
|
import sdxl_tag_routes # noqa: E402
|
|
import seed_config # noqa: E402
|
|
import krea_pov # noqa: E402
|
|
import subject_context # noqa: E402
|
|
from tools import prompt_route_simulation # noqa: E402
|
|
from tools import sxcp_prompt_batch # noqa: E402
|
|
|
|
|
|
Trigger = "sxcppnl7"
|
|
SdxlTrigger = "mythp0rt"
|
|
|
|
|
|
@dataclass
|
|
class SmokeReport:
|
|
verbose: bool = True
|
|
passed: list[str] = field(default_factory=list)
|
|
failed: list[str] = field(default_factory=list)
|
|
|
|
def ok(self, name: str) -> None:
|
|
self.passed.append(name)
|
|
if self.verbose:
|
|
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 _expect_no_softcore_noise(name: str, value: Any) -> None:
|
|
text = str(value or "").lower()
|
|
noisy = (
|
|
"the image focuses",
|
|
"softcore version",
|
|
"non-explicit teaser setup",
|
|
"no sex act",
|
|
"genital contact",
|
|
"keep the softcore version",
|
|
"focused on woman a alone",
|
|
)
|
|
found = [phrase for phrase in noisy if phrase in text]
|
|
_expect(not found, f"{name} has softcore prompt noise: {found}")
|
|
|
|
|
|
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,
|
|
target=target,
|
|
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 _exact_subcategory_selector(category: dict[str, Any], subcategory: dict[str, Any]) -> str:
|
|
return category_library.exact_subcategory_selector(category, subcategory)
|
|
|
|
|
|
def _matrix_cast_for_route(category: dict[str, Any], subcategory: dict[str, Any]) -> tuple[int, int, str]:
|
|
subject_type = str(subcategory.get("subject_type") or category.get("subject_type") or "woman")
|
|
if subject_type == "woman":
|
|
return 1, 0, ""
|
|
if subject_type == "man":
|
|
return 0, 1, ""
|
|
if subject_type == "couple":
|
|
return 1, 1, ""
|
|
|
|
min_people = int(subcategory.get("min_people") or 0)
|
|
women_count = int(subcategory.get("min_women") or 0)
|
|
men_count = int(subcategory.get("min_men") or 0)
|
|
if min_people <= 1 and not women_count and not men_count:
|
|
women_count = 1
|
|
elif min_people >= 4:
|
|
women_count = max(women_count, 2)
|
|
men_count = max(men_count, min_people - women_count)
|
|
elif min_people >= 3:
|
|
women_count = max(women_count, 1)
|
|
men_count = max(men_count, min_people - women_count)
|
|
elif min_people >= 2:
|
|
women_count = max(women_count, 1)
|
|
men_count = max(men_count, min_people - women_count)
|
|
required_total = max(1, min_people)
|
|
if women_count + men_count < required_total:
|
|
men_count += required_total - (women_count + men_count)
|
|
if women_count == 0 and men_count == 0:
|
|
women_count = 1
|
|
|
|
cast = _character_cast_subjects(["woman"] * women_count + ["man"] * men_count)
|
|
return women_count, men_count, 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 _broad_hardcore_filter() -> str:
|
|
return pb.build_hardcore_action_filter_json(
|
|
focus="keep_pool",
|
|
allow_toys=True,
|
|
allow_double=True,
|
|
allow_penetration=True,
|
|
allow_foreplay=True,
|
|
allow_interaction=True,
|
|
allow_manual=True,
|
|
allow_oral=True,
|
|
allow_outercourse=True,
|
|
allow_anal=True,
|
|
allow_climax=True,
|
|
)
|
|
|
|
|
|
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]:
|
|
return _thematic_location_configs("classical_library")
|
|
|
|
|
|
def _thematic_location_configs(theme: str) -> tuple[str, str]:
|
|
location_config, composition_config, _summary = pb.build_thematic_location_json(
|
|
enabled=True,
|
|
combine_mode="replace",
|
|
theme=theme,
|
|
)
|
|
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,
|
|
seed_config: str | dict[str, Any] | None = None,
|
|
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 = "",
|
|
style_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="",
|
|
seed_config=seed_config,
|
|
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,
|
|
style_config=style_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(row.get("subject_type") == "woman", "builtin single row lost normalized subject_type")
|
|
_expect(row.get("subject_phrase") == "woman", "builtin single row lost normalized subject_phrase")
|
|
_expect(row.get("women_count") == 1 and row.get("men_count") == 0, "builtin single row lost normalized cast counts")
|
|
_expect(row.get("person_count") == 1, "builtin single row lost normalized person count")
|
|
_expect_text("builtin_single_woman.scene_text", row.get("scene_text"), 12)
|
|
_expect(row.get("scene_slug") == row.get("scene"), "builtin single row lost legacy scene slug metadata")
|
|
_expect(row.get("scene_entry", {}).get("slug") == row.get("scene"), "builtin single row lost scene_entry slug")
|
|
item = _expect_text("builtin_single_woman.item", row.get("item"), 8)
|
|
pose = _expect_text("builtin_single_woman.pose", row.get("pose"), 8)
|
|
body_phrase = _expect_text("builtin_single_woman.body_phrase", row.get("body_phrase"), 8)
|
|
skin = _expect_text("builtin_single_woman.skin", row.get("skin"), 8)
|
|
hair = _expect_text("builtin_single_woman.hair", row.get("hair"), 5)
|
|
eyes = _expect_text("builtin_single_woman.eyes", row.get("eyes"), 4)
|
|
_expect(row.get("item_label") == "Clothing", "builtin single row lost item label")
|
|
_expect(row.get("clothing") == item, "builtin single row did not mirror clothing into item metadata")
|
|
_expect("fashion editorial styling" not in item.lower(), "builtin single item kept generic styling suffix")
|
|
_expect("cast_summary" not in row, "builtin single row should not masquerade as configured cast")
|
|
_expect_trigger_once("builtin_single_woman.prompt", row.get("prompt"), Trigger)
|
|
krea = krea_formatter.format_krea2_prompt("", metadata_json=_json(row), target="single")
|
|
_expect(row.get("scene_text") in str(krea.get("krea_prompt", "")), "builtin single Krea route used scene slug instead of scene text")
|
|
sdxl = sdxl_formatter.format_sdxl_prompt("", metadata_json=_json(row), target="single", trigger=SdxlTrigger, prepend_trigger=True)
|
|
_expect("1woman" in str(sdxl.get("sdxl_prompt", "")).lower(), "builtin single SDXL route lost normalized woman count")
|
|
sdxl_scene_text = str(sdxl.get("sdxl_prompt", "")).lower()
|
|
scene_words = re.findall(r"[a-z0-9]+", str(row.get("scene_text", "")).split(",", 1)[0].lower())
|
|
scene_anchor = " ".join(scene_words[:2])
|
|
_expect(scene_anchor in sdxl_scene_text, "builtin single SDXL route lost readable scene text")
|
|
_expect(str(row.get("scene_slug", "")).lower() not in sdxl_scene_text, "builtin single SDXL route leaked raw scene slug")
|
|
caption, caption_method = caption_naturalizer.naturalize_caption("", metadata_json=_json(row), target="single", trigger=Trigger, include_trigger=True)
|
|
_expect(caption_method.endswith("metadata(single)"), "builtin single caption route did not use single metadata branch")
|
|
_expect("woman" in caption.lower(), "builtin single caption route lost normalized subject")
|
|
_expect(row.get("scene_text") in caption, "builtin single caption route used scene slug instead of scene text")
|
|
|
|
metadata_only = dict(row)
|
|
metadata_only["prompt"] = ""
|
|
metadata_only["caption"] = ""
|
|
krea_metadata = krea_formatter.format_krea2_prompt("", metadata_json=_json(metadata_only), target="single")
|
|
sdxl_metadata = sdxl_formatter.format_sdxl_prompt("", metadata_json=_json(metadata_only), target="single", trigger=SdxlTrigger, prepend_trigger=True)
|
|
caption_metadata, caption_metadata_method = caption_naturalizer.naturalize_caption(
|
|
"",
|
|
metadata_json=_json(metadata_only),
|
|
target="single",
|
|
trigger=Trigger,
|
|
include_trigger=True,
|
|
)
|
|
_expect(item in str(krea_metadata.get("krea_prompt", "")), "Krea metadata-only built-in route lost explicit item")
|
|
_expect(pose in str(krea_metadata.get("krea_prompt", "")), "Krea metadata-only built-in route lost explicit pose")
|
|
krea_metadata_prompt = str(krea_metadata.get("krea_prompt", ""))
|
|
if re.search(r"\b(?:body|build|figure|frame|physique|silhouette)\s+with\b", body_phrase, flags=re.IGNORECASE):
|
|
body_head, body_detail = body_phrase.split(" with ", 1)
|
|
_expect(
|
|
body_head in krea_metadata_prompt and body_detail in krea_metadata_prompt,
|
|
"Krea metadata-only built-in route lost normalized body phrase",
|
|
)
|
|
else:
|
|
_expect(body_phrase in krea_metadata_prompt, "Krea metadata-only built-in route lost body phrase")
|
|
_expect(skin in str(krea_metadata.get("krea_prompt", "")), "Krea metadata-only built-in route lost skin")
|
|
_expect(hair in str(krea_metadata.get("krea_prompt", "")), "Krea metadata-only built-in route lost hair")
|
|
_expect(eyes in str(krea_metadata.get("krea_prompt", "")), "Krea metadata-only built-in route lost eyes")
|
|
item_anchor = " ".join(re.findall(r"[a-z0-9]+", item.lower())[:3])
|
|
pose_anchor = " ".join(re.findall(r"[a-z0-9]+", pose.lower())[:4])
|
|
sdxl_metadata_prompt = str(sdxl_metadata.get("sdxl_prompt", "")).lower()
|
|
_expect(item_anchor in sdxl_metadata_prompt, "SDXL metadata-only built-in route lost explicit item")
|
|
_expect(pose_anchor in sdxl_metadata_prompt, "SDXL metadata-only built-in route lost explicit pose")
|
|
for body_tag in sdxl_tag_policy.split_tag_text(body_phrase):
|
|
_expect(body_tag.lower() in sdxl_metadata_prompt, f"SDXL metadata-only built-in route lost body tag: {body_tag}")
|
|
_expect(skin.lower() in sdxl_metadata_prompt, "SDXL metadata-only built-in route lost skin")
|
|
_expect(hair.lower() in sdxl_metadata_prompt, "SDXL metadata-only built-in route lost hair")
|
|
_expect(eyes.lower() in sdxl_metadata_prompt, "SDXL metadata-only built-in route lost eyes")
|
|
_expect(caption_metadata_method.endswith("metadata(single)"), "Caption metadata-only built-in route did not use single metadata branch")
|
|
_expect(item in caption_metadata and pose in caption_metadata, "Caption metadata-only built-in route lost explicit item or pose")
|
|
_expect(body_phrase in caption_metadata and skin in caption_metadata, "Caption metadata-only built-in route lost appearance")
|
|
_expect(hair in caption_metadata and eyes in caption_metadata, "Caption metadata-only built-in route lost hair or eyes")
|
|
_expect_formatter_outputs(row, "builtin_single_woman", target="single")
|
|
|
|
|
|
def smoke_builtin_couple_metadata() -> None:
|
|
row = _prompt_row(name="builtin_couple", category="couple", subcategory="random", seed=1003, men_count=0)
|
|
_expect(row.get("source") == "built_in_generator", "builtin couple row should come from built-in generator")
|
|
_expect(row.get("subject_type") == "couple", "builtin couple row lost normalized subject_type")
|
|
_expect(row.get("women_count") + row.get("men_count") == row.get("person_count"), "builtin couple row lost count consistency")
|
|
_expect(row.get("person_count") == 2, "builtin couple row lost normalized person count")
|
|
subject = _expect_text("builtin_couple.subject_phrase", row.get("subject_phrase"), 5)
|
|
age = _expect_text("builtin_couple.age_band", row.get("age_band"), 8)
|
|
body = _expect_text("builtin_couple.body_type", row.get("body_type"), 5)
|
|
scene = _expect_text("builtin_couple.scene_text", row.get("scene_text"), 12)
|
|
item = _expect_text("builtin_couple.item", row.get("item"), 8)
|
|
pose = _expect_text("builtin_couple.pose", row.get("pose"), 8)
|
|
expression = _expect_text("builtin_couple.expression", row.get("expression"), 8)
|
|
composition = _expect_text("builtin_couple.composition", row.get("composition"), 8)
|
|
|
|
metadata_only = dict(row)
|
|
metadata_only["prompt"] = ""
|
|
metadata_only["caption"] = ""
|
|
metadata = _json(metadata_only)
|
|
krea = krea_formatter.format_krea2_prompt("", metadata_json=metadata, target="auto")
|
|
sdxl = sdxl_formatter.format_sdxl_prompt(
|
|
"",
|
|
metadata_json=metadata,
|
|
target="auto",
|
|
trigger=SdxlTrigger,
|
|
prepend_trigger=True,
|
|
)
|
|
caption, caption_method = caption_naturalizer.naturalize_caption(
|
|
"",
|
|
metadata_json=metadata,
|
|
target="auto",
|
|
trigger=Trigger,
|
|
include_trigger=True,
|
|
)
|
|
|
|
clean_age = caption_text_policy.clean_age_phrase(age)
|
|
krea_prompt = str(krea.get("krea_prompt", ""))
|
|
sdxl_prompt = str(sdxl.get("sdxl_prompt", "")).lower()
|
|
_expect("metadata(couple)" in krea.get("method", ""), "Krea built-in couple route did not use metadata")
|
|
_expect(subject.capitalize() in krea_prompt and clean_age in krea_prompt, "Krea built-in couple route lost subject or age")
|
|
_expect("all visibly adult" not in krea_prompt.lower(), "Krea built-in couple route kept redundant adult noise")
|
|
_expect("Age detail:" not in krea_prompt, "Krea built-in couple route kept age helper label")
|
|
for value in (body, scene, item, pose, expression, composition):
|
|
_expect(value in krea_prompt, f"Krea built-in couple route lost metadata value: {value}")
|
|
|
|
_expect("2women" in sdxl_prompt or "1woman, 1man" in sdxl_prompt or "2men" in sdxl_prompt, "SDXL built-in couple route lost count tags")
|
|
for value in (body, scene, item, pose, expression, composition):
|
|
for tag in sdxl_tag_policy.split_tag_text(value):
|
|
_expect(tag.lower() in sdxl_prompt, f"SDXL built-in couple route lost metadata tag: {tag}")
|
|
|
|
_expect(caption_method.endswith("metadata(couple)"), "Caption built-in couple route did not use metadata")
|
|
_expect(clean_age in caption and scene in caption, "Caption built-in couple route lost age or scene")
|
|
_expect("The age detail is" not in caption, "Caption built-in couple route kept age helper sentence")
|
|
for value in (body, item, pose, expression, composition):
|
|
_expect(value in caption, f"Caption built-in couple route lost metadata value: {value}")
|
|
|
|
|
|
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")
|
|
|
|
custom_location = pb.build_location_pool_json(
|
|
enabled=True,
|
|
combine_mode="replace",
|
|
preset="custom_only",
|
|
custom_locations=json.dumps(
|
|
{
|
|
"slug": "greenhouse_suite",
|
|
"prompt": "private room with soft daylight",
|
|
"camera_profile": {
|
|
"key": "glass_conservatory",
|
|
"family": "greenhouse",
|
|
"layout_label": "Glass conservatory camera layout",
|
|
"place": "glass conservatory",
|
|
"foreground": "plant shelf edge, fern leaves, and iron table corner",
|
|
"midground": "glass panes, iron ribs, and potted palms",
|
|
"background": "hanging vines, greenhouse windows, and layered plant depth",
|
|
"detail_label": "conservatory details",
|
|
"composition": {
|
|
"woman": "glass conservatory frame with the woman beside fern leaves and greenhouse depth behind her",
|
|
"default": "glass conservatory frame with the subjects beside fern leaves and greenhouse depth behind them",
|
|
},
|
|
},
|
|
},
|
|
sort_keys=True,
|
|
),
|
|
)
|
|
custom_composition = pb.build_composition_pool_json(
|
|
enabled=True,
|
|
combine_mode="replace",
|
|
preset="custom_only",
|
|
custom_compositions=json.dumps({"prompt": "polished mirror view with bag and shoes visible"}, sort_keys=True),
|
|
)
|
|
custom_row = _prompt_row(
|
|
name="camera_scene_custom_inline_profile",
|
|
category="woman",
|
|
subcategory="random",
|
|
seed=1061,
|
|
men_count=0,
|
|
camera_config=_orbit_camera(
|
|
horizontal_angle=45,
|
|
vertical_angle=30,
|
|
zoom=5.0,
|
|
subject_focus="environment",
|
|
),
|
|
location_config=custom_location,
|
|
composition_config=custom_composition,
|
|
)
|
|
custom_scene = _expect_text(
|
|
"camera_scene_custom_inline_profile.camera_scene_directive",
|
|
custom_row.get("camera_scene_directive"),
|
|
40,
|
|
)
|
|
custom_composition_text = _expect_text(
|
|
"camera_scene_custom_inline_profile.composition",
|
|
custom_row.get("composition"),
|
|
20,
|
|
)
|
|
_expect("Glass conservatory camera layout" in custom_scene, "custom Location Pool JSON camera profile did not drive scene layout")
|
|
_expect(custom_row.get("scene_camera_profile_key") == "glass_conservatory", "custom Location Pool JSON profile key was not exposed")
|
|
_expect(custom_row.get("scene_entry", {}).get("camera_profile", {}).get("family") == "greenhouse", "custom Location Pool JSON profile metadata was not preserved")
|
|
_expect("glass conservatory" in custom_composition_text.lower(), "custom Location Pool JSON profile did not clean composition")
|
|
_expect("bag" not in custom_composition_text.lower() and "shoes" not in custom_composition_text.lower(), "custom inline profile composition leaked unrelated props")
|
|
|
|
|
|
def smoke_scene_camera_adapter_pov_profile_policy() -> None:
|
|
parsed_camera = {
|
|
"camera_mode": "standard",
|
|
"camera_detail": "compact",
|
|
"orbit_direction": "front-right quarter view",
|
|
"orbit_elevation_label": "elevated shot",
|
|
"orbit_distance_label": "medium shot",
|
|
"custom_camera_prompt": "front-right quarter view, elevated shot, medium shot",
|
|
}
|
|
for profile in scene_camera_adapters.SCENE_CAMERA_PROFILES:
|
|
key = str(profile.get("key") or "")
|
|
foreground = str(profile.get("foreground") or "")
|
|
non_pov = _expect_text(
|
|
f"scene_camera_adapter_pov_profile_policy.{key}.non_pov",
|
|
scene_camera_adapters.scene_camera_directive(
|
|
"",
|
|
parsed_camera,
|
|
pov_labels=[],
|
|
subject_kind="couple",
|
|
profile_key=key,
|
|
),
|
|
40,
|
|
)
|
|
pov = _expect_text(
|
|
f"scene_camera_adapter_pov_profile_policy.{key}.pov",
|
|
scene_camera_adapters.scene_camera_directive(
|
|
"",
|
|
parsed_camera,
|
|
pov_labels=["Man A"],
|
|
subject_kind="couple",
|
|
profile_key=key,
|
|
),
|
|
40,
|
|
)
|
|
_expect(foreground and foreground in non_pov, f"{key} non-POV scene directive lost profile foreground anchor")
|
|
_expect(foreground not in pov, f"{key} POV scene directive reused profile foreground anchor as viewer-side text")
|
|
_expect("from POV" in pov, f"{key} POV scene directive lost POV marker")
|
|
_expect(
|
|
"POV body or hand cues stay in the lower foreground" in pov,
|
|
f"{key} POV scene directive lost lower-foreground body-cue reservation",
|
|
)
|
|
plural_directive = scene_camera_adapters.scene_camera_directive(
|
|
"",
|
|
parsed_camera,
|
|
pov_labels=[],
|
|
subject_kind="subjects",
|
|
profile_key="coworking_lounge",
|
|
)
|
|
_expect(
|
|
"the subjects are placed" in plural_directive,
|
|
"scene camera adapter used singular grammar for plural subjects",
|
|
)
|
|
_expect("the subjects is" not in plural_directive, "scene camera adapter leaked 'the subjects is'")
|
|
|
|
|
|
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("POV body or hand cues stay in the lower foreground" in scene_directive, "row camera policy lost POV foreground cue")
|
|
_expect("use the multiangle camera" not in scene_directive, "row camera policy leaked internal multiangle 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",
|
|
)
|
|
pov_action_row = dict(row)
|
|
pov_action_row["composition"] = (
|
|
"first-person rear-view frame looking down at the woman's raised ass, "
|
|
"with foreground hands and rear-entry contact readable"
|
|
)
|
|
pov_action_row["prompt"] = (
|
|
"A generated adult prompt. Framed as first-person rear-view frame looking down at the woman's raised ass, "
|
|
"with foreground hands and rear-entry contact readable. Avoid: low quality."
|
|
)
|
|
updated_pov_action = row_camera.apply_camera_config(
|
|
pov_action_row,
|
|
_orbit_camera(horizontal_angle=180, vertical_angle=-30, zoom=7.5),
|
|
compact_labels=pb.CAMERA_COMPACT_LABELS,
|
|
)
|
|
_expect(
|
|
"first-person rear-view frame" in updated_pov_action.get("composition", ""),
|
|
"row camera policy replaced explicit POV action composition with generic location composition",
|
|
)
|
|
_expect(
|
|
"couple's raised ass" not in updated_pov_action.get("composition", ""),
|
|
"row camera policy rewrote explicit female action anatomy as couple anatomy",
|
|
)
|
|
_expect(
|
|
"coworking lounge frame with the couple near a desk edge" not in updated_pov_action.get("composition", ""),
|
|
"row camera policy leaked generic coworking composition into explicit POV action frame",
|
|
)
|
|
_expect(
|
|
str(updated_pov_action.get("camera_scene_directive", "")).count(";") <= 3,
|
|
"POV camera scene directive became too noisy",
|
|
)
|
|
already_matching_row = dict(row)
|
|
already_matching_row["pov_character_labels"] = []
|
|
already_matching_row["composition"] = "coworking lounge frame with the subjects near a desk edge and tall-window depth behind them"
|
|
already_matching_row["prompt"] = (
|
|
"A generated adult prompt. Framed as coworking lounge frame with the subjects near a desk edge and tall-window depth behind them. "
|
|
"Avoid: low quality."
|
|
)
|
|
updated_matching = row_camera.apply_camera_config(
|
|
already_matching_row,
|
|
_orbit_camera(horizontal_angle=45, vertical_angle=0, zoom=5.5),
|
|
compact_labels=pb.CAMERA_COMPACT_LABELS,
|
|
)
|
|
_expect(
|
|
"the couple near a desk edge" in str(updated_matching.get("composition", "")),
|
|
"row camera policy did not adapt generic matching composition subject wording",
|
|
)
|
|
_expect(
|
|
"the subjects near a desk edge" not in str(updated_matching.get("prompt", "")),
|
|
"row camera policy left generic matching composition subject wording in prompt",
|
|
)
|
|
pre_normalized_row = dict(row)
|
|
pre_normalized_row["pov_character_labels"] = []
|
|
pre_normalized_row["composition"] = "coworking lounge frame with the woman near a desk edge and tall-window depth behind them"
|
|
pre_normalized_row["prompt"] = (
|
|
"A generated adult prompt. Framed as coworking lounge frame with the woman near a desk edge and tall-window depth behind them. "
|
|
"Avoid: low quality."
|
|
)
|
|
updated_pre_normalized = row_camera.apply_camera_config(
|
|
pre_normalized_row,
|
|
_orbit_camera(horizontal_angle=45, vertical_angle=0, zoom=5.5),
|
|
compact_labels=pb.CAMERA_COMPACT_LABELS,
|
|
)
|
|
_expect(
|
|
"the couple near a desk edge" in str(updated_pre_normalized.get("composition", "")),
|
|
"row camera policy did not adapt pre-normalized woman composition for couple rows",
|
|
)
|
|
_expect(
|
|
"the woman near a desk edge" not in str(updated_pre_normalized.get("prompt", "")),
|
|
"row camera policy left pre-normalized woman composition wording in prompt",
|
|
)
|
|
stale_internal_row = dict(row)
|
|
stale_internal_row["pov_character_labels"] = []
|
|
stale_internal_row["composition"] = "camera-aware coworking lounge frame with subjects near a desk edge"
|
|
stale_internal_row["prompt"] = (
|
|
"A generated adult prompt. Framed as camera-aware coworking lounge frame with subjects near a desk edge. "
|
|
"Avoid: low quality."
|
|
)
|
|
updated_stale_internal = row_camera.apply_camera_config(
|
|
stale_internal_row,
|
|
_orbit_camera(horizontal_angle=45, vertical_angle=0, zoom=5.5),
|
|
compact_labels=pb.CAMERA_COMPACT_LABELS,
|
|
)
|
|
_expect(
|
|
"camera-aware" not in str(updated_stale_internal.get("composition", "")).lower(),
|
|
"row camera policy leaked internal camera-aware composition wording",
|
|
)
|
|
_expect(
|
|
"camera-aware" not in str(updated_stale_internal.get("prompt", "")).lower(),
|
|
"row camera policy left internal camera-aware wording in prompt",
|
|
)
|
|
library_row = {
|
|
"prompt": "A generated adult prompt. Composition: vertical polished mirror view with bag and shoes visible. Avoid: low quality.",
|
|
"caption": "sxcppnl7, generated adult prompt, polished mirror view with bag and shoes visible, illustration",
|
|
"scene_text": "grand classical library hall with towering dark-wood bookshelves, carved columns, rolling ladders, marble floor, and warm brass lamps",
|
|
"composition": "polished mirror view with bag and shoes visible",
|
|
"subject_type": "woman",
|
|
"women_count": 1,
|
|
"men_count": 0,
|
|
}
|
|
updated_library = row_camera.apply_camera_config(
|
|
library_row,
|
|
_orbit_camera(horizontal_angle=315, vertical_angle=0, zoom=5.0),
|
|
compact_labels=pb.CAMERA_COMPACT_LABELS,
|
|
)
|
|
library_scene = _expect_text("row_camera_policy.library_scene", updated_library.get("camera_scene_directive"), 40)
|
|
library_composition = _expect_text("row_camera_policy.library_composition", updated_library.get("composition"), 20)
|
|
_expect("Library camera layout" in library_scene, "row camera policy missed library layout")
|
|
_expect("front-left quarter view" in library_scene, "row camera library layout missed orbit direction")
|
|
_expect("bookshelf" in library_scene.lower() or "bookshelves" in library_scene.lower(), "row camera library layout missed shelf anchors")
|
|
_expect("bag" not in library_composition.lower(), "row camera library composition leaked bag wording")
|
|
_expect("shoes" not in library_composition.lower(), "row camera library composition leaked shoes wording")
|
|
_expect("library" in library_composition.lower(), "row camera library composition did not become location-aware")
|
|
semi_public_row = {
|
|
"prompt": "A generated adult prompt. Composition: vertical polished mirror view with bag and shoes visible. Avoid: low quality.",
|
|
"caption": "sxcppnl7, generated adult prompt, polished mirror view with bag and shoes visible, illustration",
|
|
"scene_text": "upscale hotel corridor with repeating numbered doors, patterned carpet, brass wall lamps, luggage carts, and a secluded corner near a service alcove",
|
|
"scene_entry": {
|
|
"slug": "hotel_corridor_affair",
|
|
"prompt": "upscale hotel corridor with repeating numbered doors, patterned carpet, brass wall lamps, luggage carts, and a secluded corner near a service alcove",
|
|
"theme": "semi_public_affair",
|
|
},
|
|
"scene_theme": "semi_public_affair",
|
|
"composition": "polished mirror view with bag and shoes visible",
|
|
"subject_type": "configured_cast",
|
|
"women_count": 1,
|
|
"men_count": 1,
|
|
"pov_character_labels": ["Man A"],
|
|
}
|
|
updated_semi_public = row_camera.apply_camera_config(
|
|
semi_public_row,
|
|
_orbit_camera(horizontal_angle=180, vertical_angle=30, zoom=7.5),
|
|
compact_labels=pb.CAMERA_COMPACT_LABELS,
|
|
)
|
|
semi_public_scene = _expect_text("row_camera_policy.semi_public_scene", updated_semi_public.get("camera_scene_directive"), 40)
|
|
semi_public_composition = _expect_text(
|
|
"row_camera_policy.semi_public_composition",
|
|
updated_semi_public.get("composition"),
|
|
20,
|
|
)
|
|
_expect("Hotel corridor camera layout from POV" in semi_public_scene, "row camera semi-public scene did not use hotel corridor profile")
|
|
_expect("back view" not in semi_public_scene, "POV camera scene should not inject orbit back view as body orientation")
|
|
_expect("partner's back" not in semi_public_scene, "POV camera scene should not force the visible partner's back")
|
|
_expect("POV body or hand cues stay in the lower foreground" in semi_public_scene, "row camera semi-public POV scene lost foreground cue")
|
|
_expect(updated_semi_public.get("scene_camera_profile_key") == "hotel_corridor", "row camera semi-public scene did not expose text-matched profile key")
|
|
_expect("hotel corridor" in semi_public_composition.lower(), "row camera semi-public composition did not become location-aware")
|
|
_expect("bag" not in semi_public_composition.lower() and "shoes" not in semi_public_composition.lower(), "row camera semi-public composition leaked outfit-check props")
|
|
metadata_profile_row = {
|
|
"prompt": "A generated adult prompt. Composition: vertical polished mirror view with bag and shoes visible. Avoid: low quality.",
|
|
"caption": "sxcppnl7, generated adult prompt, polished mirror view with bag and shoes visible, illustration",
|
|
"scene_text": "private themed room with neutral walls and warm lamps",
|
|
"scene_entry": {
|
|
"slug": "library_by_metadata",
|
|
"prompt": "private themed room with neutral walls and warm lamps",
|
|
"theme": "classical_library",
|
|
},
|
|
"scene_theme": "classical_library",
|
|
"composition": "polished mirror view with bag and shoes visible",
|
|
"subject_type": "woman",
|
|
"women_count": 1,
|
|
"men_count": 0,
|
|
}
|
|
updated_metadata_profile = row_camera.apply_camera_config(
|
|
metadata_profile_row,
|
|
_orbit_camera(horizontal_angle=315, vertical_angle=0, zoom=5.0),
|
|
compact_labels=pb.CAMERA_COMPACT_LABELS,
|
|
)
|
|
metadata_scene = _expect_text(
|
|
"row_camera_policy.metadata_scene",
|
|
updated_metadata_profile.get("camera_scene_directive"),
|
|
40,
|
|
)
|
|
_expect("Library camera layout" in metadata_scene, "row camera should prefer scene theme metadata over generic scene text")
|
|
_expect(
|
|
updated_metadata_profile.get("scene_camera_profile_key") == "classical_library",
|
|
"row camera should expose metadata-selected profile key",
|
|
)
|
|
_expect(
|
|
"library" in str(updated_metadata_profile.get("composition", "")).lower(),
|
|
"row camera metadata-selected profile did not clean composition",
|
|
)
|
|
explicit_profile_row = {
|
|
"prompt": "A generated adult prompt. Composition: vertical polished mirror view with bag and shoes visible. Avoid: low quality.",
|
|
"caption": "sxcppnl7, generated adult prompt, polished mirror view with bag and shoes visible, illustration",
|
|
"scene_text": "coworking lounge with tall windows, warm desks, and glass partitions",
|
|
"scene_camera_profile_key": "classical_library",
|
|
"composition": "polished mirror view with bag and shoes visible",
|
|
"subject_type": "woman",
|
|
"women_count": 1,
|
|
"men_count": 0,
|
|
}
|
|
updated_explicit_profile = row_camera.apply_camera_config(
|
|
explicit_profile_row,
|
|
_orbit_camera(horizontal_angle=315, vertical_angle=0, zoom=5.0),
|
|
compact_labels=pb.CAMERA_COMPACT_LABELS,
|
|
)
|
|
_expect(
|
|
"Library camera layout" in str(updated_explicit_profile.get("camera_scene_directive", "")),
|
|
"explicit scene_camera_profile_key should override text-matched scene profile",
|
|
)
|
|
inline_profile_row = {
|
|
"prompt": "A generated adult prompt. Composition: vertical polished mirror view with bag and shoes visible. Avoid: low quality.",
|
|
"caption": "sxcppnl7, generated adult prompt, polished mirror view with bag and shoes visible, illustration",
|
|
"scene_text": "private room with soft daylight",
|
|
"scene_entry": {
|
|
"slug": "greenhouse_room",
|
|
"prompt": "private room with soft daylight",
|
|
"camera_profile": {
|
|
"key": "glass_conservatory",
|
|
"family": "greenhouse",
|
|
"layout_label": "Glass conservatory camera layout",
|
|
"place": "glass conservatory",
|
|
"foreground": "plant shelf edge, fern leaves, and iron table corner",
|
|
"midground": "glass panes, iron ribs, and potted palms",
|
|
"background": "hanging vines, greenhouse windows, and layered plant depth",
|
|
"detail_label": "conservatory details",
|
|
"composition": {
|
|
"woman": "glass conservatory frame with the woman beside fern leaves and greenhouse depth behind her",
|
|
"default": "glass conservatory frame with the subjects beside fern leaves and greenhouse depth behind them",
|
|
},
|
|
},
|
|
},
|
|
"composition": "polished mirror view with bag and shoes visible",
|
|
"subject_type": "woman",
|
|
"women_count": 1,
|
|
"men_count": 0,
|
|
}
|
|
updated_inline_profile = row_camera.apply_camera_config(
|
|
inline_profile_row,
|
|
_orbit_camera(horizontal_angle=45, vertical_angle=30, zoom=5.0),
|
|
compact_labels=pb.CAMERA_COMPACT_LABELS,
|
|
)
|
|
inline_scene = _expect_text(
|
|
"row_camera_policy.inline_profile_scene",
|
|
updated_inline_profile.get("camera_scene_directive"),
|
|
40,
|
|
)
|
|
inline_composition = _expect_text(
|
|
"row_camera_policy.inline_profile_composition",
|
|
updated_inline_profile.get("composition"),
|
|
20,
|
|
)
|
|
inline_profile = updated_inline_profile.get("scene_camera_profile") if isinstance(updated_inline_profile.get("scene_camera_profile"), dict) else {}
|
|
_expect("Glass conservatory camera layout" in inline_scene, "inline scene camera profile did not drive camera layout")
|
|
_expect(updated_inline_profile.get("scene_camera_profile_key") == "glass_conservatory", "inline profile key was not exposed")
|
|
_expect(inline_profile.get("family") == "greenhouse", "inline profile family was not exposed")
|
|
_expect("glass conservatory" in inline_composition.lower(), "inline profile did not drive composition cleanup")
|
|
_expect("bag" not in inline_composition.lower() and "shoes" not in inline_composition.lower(), "inline profile composition leaked unrelated props")
|
|
|
|
beach_profile = scene_camera_adapters.scene_camera_profile(
|
|
"beach cafe table with woven chairs, linen shade, and ocean light in the background",
|
|
scene_entry={"slug": "beach_cafe_table"},
|
|
)
|
|
subway_profile = scene_camera_adapters.scene_camera_profile(
|
|
"clean subway platform with tiled walls, overhead lights, and a quiet selfie corner",
|
|
scene_entry={"slug": "subway_tile_selfie_corner"},
|
|
)
|
|
apartment_profile = scene_camera_adapters.scene_camera_profile(
|
|
"sunny apartment corner with bookshelves, a warm rug, and a phone on a small tripod",
|
|
scene_entry={"slug": "sunny_apartment_phone_tripod"},
|
|
)
|
|
_expect(not beach_profile, "scene camera resolver should not classify beach cafe as business cafe")
|
|
_expect(not subway_profile, "scene camera resolver should not classify subway tile as station lockers")
|
|
_expect(not apartment_profile, "scene camera resolver should not classify apartment bookshelves as classical library")
|
|
|
|
mirror_profile = scene_camera_adapters.scene_camera_profile(
|
|
"large bedroom mirror with the phone visible, bed behind the subject, and warm side lamps",
|
|
scene_entry={"slug": "large_bedroom_mirror_selfie"},
|
|
)
|
|
studio_profile = scene_camera_adapters.scene_camera_profile(
|
|
"dark private studio with glossy black floor reflections, rim light, and a phone tripod",
|
|
scene_entry={"slug": "black_latex_studio_floor"},
|
|
)
|
|
_expect(mirror_profile.get("key") == "mirror_room", "scene slug resolver missed mirror-room profile")
|
|
_expect(studio_profile.get("key") == "private_studio", "scene slug resolver missed private-studio profile")
|
|
|
|
|
|
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)
|
|
scene_directive = _expect_text("config_route_location_theme.camera_scene_directive", row.get("camera_scene_directive"), 40)
|
|
scene_profile = row.get("scene_camera_profile") if isinstance(row.get("scene_camera_profile"), dict) else {}
|
|
_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(row.get("location_theme") == "classical_library", "location theme did not survive into row metadata")
|
|
_expect(row.get("scene_theme") == "classical_library", "selected scene theme did not survive into row metadata")
|
|
_expect(row.get("composition_theme") == "classical_library", "composition theme did not survive into row metadata")
|
|
_expect(row.get("scene_entry", {}).get("theme") == "classical_library", "selected scene entry lost theme metadata")
|
|
_expect("Library camera layout" in scene_directive, "location theme did not drive library camera-scene adapter")
|
|
_expect(row.get("scene_camera_profile_key") == "classical_library", "row lost scene camera profile key")
|
|
_expect(scene_profile.get("family") == "library", "row lost scene camera profile family")
|
|
_expect("front-left quarter view" in scene_directive, "library camera-scene adapter missed orbit direction")
|
|
_expect("bag" not in composition.lower() and "shoes" not in composition.lower(), "location theme composition leaked outfit-check props")
|
|
_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("Library camera layout" in prompt, "Krea config route lost library camera-scene directive")
|
|
_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")
|
|
|
|
parking_location_config, parking_composition_config = _thematic_location_configs("parking_garage")
|
|
parking_row = pb.build_prompt_from_configs(
|
|
row_number=1,
|
|
start_index=1,
|
|
seed=3311,
|
|
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"),
|
|
camera_config=_orbit_camera(
|
|
horizontal_angle=135,
|
|
vertical_angle=-30,
|
|
zoom=4.0,
|
|
subject_focus="environment",
|
|
),
|
|
location_config=parking_location_config,
|
|
composition_config=parking_composition_config,
|
|
)
|
|
_expect_row_base(parking_row, "config_route_location_theme.parking")
|
|
parking_scene = _expect_text("config_route_location_theme.parking_scene", parking_row.get("scene_text"), 20)
|
|
parking_composition = _expect_text("config_route_location_theme.parking_composition", parking_row.get("composition"), 10)
|
|
parking_directive = _expect_text(
|
|
"config_route_location_theme.parking_camera_scene_directive",
|
|
parking_row.get("camera_scene_directive"),
|
|
40,
|
|
)
|
|
parking_profile = parking_row.get("scene_camera_profile") if isinstance(parking_row.get("scene_camera_profile"), dict) else {}
|
|
_expect("parking" in parking_scene.lower() or "garage" in parking_scene.lower(), "parking theme did not drive scene")
|
|
_expect("parking" in parking_composition.lower() or "garage" in parking_composition.lower() or "pillar" in parking_composition.lower(), "parking theme did not drive composition")
|
|
_expect(parking_row.get("location_theme") == "parking_garage", "parking location theme did not survive")
|
|
_expect(parking_row.get("scene_theme") == "parking_garage", "parking scene theme did not survive")
|
|
_expect(parking_row.get("main_category") == "woman", "parking built-in woman preset fell back to another category")
|
|
_expect(parking_row.get("primary_subject") == "woman", "parking built-in woman preset did not generate a woman")
|
|
_expect(parking_row.get("scene_camera_profile_key") == "parking_garage", "parking theme did not expose camera profile key")
|
|
_expect(parking_profile.get("family") == "semi_public", "parking camera profile family should be semi_public")
|
|
_expect("Parking garage camera layout" in parking_directive, "parking theme did not drive camera-scene adapter")
|
|
_expect("back-right quarter view" in parking_directive, "parking camera-scene adapter missed orbit direction")
|
|
_expect("low-angle shot" in parking_directive, "parking camera-scene adapter missed elevation")
|
|
|
|
creator_location_config, creator_composition_config = _thematic_location_configs("creator_bedroom")
|
|
creator_row = pb.build_prompt_from_configs(
|
|
row_number=1,
|
|
start_index=1,
|
|
seed=3321,
|
|
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"),
|
|
camera_config=_orbit_camera(
|
|
horizontal_angle=45,
|
|
vertical_angle=0,
|
|
zoom=5.5,
|
|
subject_focus="environment",
|
|
),
|
|
location_config=creator_location_config,
|
|
composition_config=creator_composition_config,
|
|
)
|
|
_expect_row_base(creator_row, "config_route_location_theme.creator")
|
|
creator_scene = _expect_text("config_route_location_theme.creator_scene", creator_row.get("scene_text"), 20)
|
|
creator_composition = _expect_text("config_route_location_theme.creator_composition", creator_row.get("composition"), 10)
|
|
creator_directive = _expect_text(
|
|
"config_route_location_theme.creator_camera_scene_directive",
|
|
creator_row.get("camera_scene_directive"),
|
|
40,
|
|
)
|
|
creator_profile = creator_row.get("scene_camera_profile") if isinstance(creator_row.get("scene_camera_profile"), dict) else {}
|
|
_expect("creator" in creator_scene.lower() or "phone" in creator_scene.lower(), "creator theme did not drive scene")
|
|
_expect(
|
|
any(token in creator_composition.lower() for token in ("creator", "tripod", "phone", "bed")),
|
|
"creator theme did not drive composition",
|
|
)
|
|
_expect(creator_row.get("location_theme") == "creator_bedroom", "creator location theme did not survive")
|
|
_expect(creator_row.get("scene_theme") == "creator_bedroom", "creator scene theme did not survive")
|
|
_expect(creator_row.get("main_category") == "woman", "creator built-in woman preset fell back to another category")
|
|
_expect(creator_row.get("primary_subject") == "woman", "creator built-in woman preset did not generate a woman")
|
|
_expect(creator_row.get("scene_camera_profile_key") == "creator_bedroom", "creator theme did not expose camera profile key")
|
|
_expect(creator_profile.get("family") == "private_creator", "creator camera profile family should be private_creator")
|
|
_expect("Creator room camera layout" in creator_directive, "creator theme did not drive camera-scene adapter")
|
|
_expect("front-right quarter view" in creator_directive, "creator camera-scene adapter missed orbit direction")
|
|
creator_krea = krea_formatter.format_krea2_prompt("", metadata_json=_json(creator_row), target="single")
|
|
creator_prompt = creator_krea.get("krea_prompt") or ""
|
|
_expect("Creator room camera layout" in creator_prompt, "Krea config route lost creator camera-scene directive")
|
|
|
|
|
|
def smoke_builder_prompt_route_policy() -> None:
|
|
def legacy_from_request(request: builder_prompt_route.PromptBuildRequest) -> dict[str, Any]:
|
|
return pb.build_prompt(
|
|
category=request.category,
|
|
subcategory=request.subcategory,
|
|
row_number=request.row_number,
|
|
start_index=request.start_index,
|
|
seed=request.seed,
|
|
clothing=request.clothing,
|
|
ethnicity=request.ethnicity,
|
|
poses=request.poses,
|
|
backside_bias=request.backside_bias,
|
|
figure=request.figure,
|
|
no_plus_women=request.no_plus_women,
|
|
no_black=request.no_black,
|
|
minimal_clothing_ratio=request.minimal_clothing_ratio,
|
|
standard_pose_ratio=request.standard_pose_ratio,
|
|
trigger=request.trigger,
|
|
prepend_trigger_to_prompt=request.prepend_trigger_to_prompt,
|
|
extra_positive=request.extra_positive,
|
|
extra_negative=request.extra_negative,
|
|
seed_config=request.seed_config,
|
|
women_count=request.women_count,
|
|
men_count=request.men_count,
|
|
camera_config=request.camera_config,
|
|
expression_intensity=request.expression_intensity,
|
|
character_profile=request.character_profile,
|
|
character_cast=request.character_cast,
|
|
expression_enabled=request.expression_enabled,
|
|
expression_phase=request.expression_phase,
|
|
hardcore_position_config=request.hardcore_position_config,
|
|
location_config=request.location_config,
|
|
composition_config=request.composition_config,
|
|
)
|
|
|
|
seed_config_json = pb.build_seed_lock_config_json(base_seed=3501, reroll_axis="content", reroll_seed=3502)
|
|
request = builder_prompt_route.PromptBuildRequest(
|
|
category="Casual clothes",
|
|
subcategory="Casual clothes / Smart casual",
|
|
row_number=3,
|
|
start_index=8,
|
|
seed=3501,
|
|
clothing="random",
|
|
ethnicity="french_european",
|
|
poses="random",
|
|
backside_bias=0.2,
|
|
figure="random",
|
|
no_plus_women=False,
|
|
no_black=False,
|
|
minimal_clothing_ratio=0.3,
|
|
standard_pose_ratio=0.4,
|
|
trigger="sxcpinup_coloredpencil",
|
|
prepend_trigger_to_prompt=True,
|
|
extra_positive="typed builder route marker",
|
|
extra_negative="typed builder negative marker",
|
|
seed_config=seed_config_json,
|
|
women_count=1,
|
|
men_count=0,
|
|
camera_config=_orbit_camera(horizontal_angle=45, vertical_angle=0, zoom=5.5),
|
|
expression_intensity=0.6,
|
|
expression_enabled=True,
|
|
)
|
|
typed_route = builder_prompt_route.build_prompt_result(request, pb._prompt_build_dependencies())
|
|
legacy_row = legacy_from_request(request)
|
|
_expect(typed_route.row == legacy_row, "Typed builder prompt route should match public wrapper output")
|
|
_expect(typed_route.category == "Casual clothes", "Builder prompt route changed category")
|
|
_expect(typed_route.subcategory == "Casual clothes / Smart casual", "Builder prompt route changed subcategory")
|
|
_expect(typed_route.branch == "custom", "Builder prompt route should use custom branch for category JSON route")
|
|
_expect(typed_route.parsed_seed_config.get("content_seed") == 3502, "Builder prompt route lost seed config")
|
|
custom_trace = typed_route.row.get("generation_trace")
|
|
_expect(isinstance(custom_trace, dict), "Builder custom route lost generation_trace")
|
|
_expect(custom_trace.get("branch") == "custom", "Builder custom generation_trace lost branch")
|
|
_expect(custom_trace.get("source") == "json_category", "Builder custom generation_trace lost source")
|
|
_expect(custom_trace.get("category_slug") == "casual_clothes", "Builder custom generation_trace lost category slug")
|
|
_expect(custom_trace.get("content_seed_axis") == "content", "Builder custom generation_trace lost content axis")
|
|
_expect(custom_trace.get("seed_axes", {}).get("content", {}).get("source") == "configured", "Builder custom generation_trace lost configured content seed")
|
|
_expect(custom_trace.get("seed_axes", {}).get("content", {}).get("seed") == 3502, "Builder custom generation_trace lost content seed value")
|
|
_expect("typed builder route marker" in typed_route.row.get("prompt", ""), "Builder prompt route lost extra positive")
|
|
_expect("typed builder negative marker" in typed_route.row.get("negative_prompt", ""), "Builder prompt route lost extra negative")
|
|
_expect(
|
|
"45-degree front-right quarter view" in typed_route.row.get("camera_directive", ""),
|
|
"Builder prompt route lost camera config",
|
|
)
|
|
_expect_trigger_once("builder_prompt_route_policy.prompt", typed_route.row.get("prompt"), "sxcpinup_coloredpencil")
|
|
|
|
built_in_request = builder_prompt_route.PromptBuildRequest(
|
|
category="woman",
|
|
subcategory="random",
|
|
row_number=1,
|
|
start_index=1,
|
|
seed=3503,
|
|
clothing="full",
|
|
ethnicity="any",
|
|
poses="standard",
|
|
backside_bias=0.0,
|
|
figure="curvy",
|
|
no_plus_women=False,
|
|
no_black=False,
|
|
minimal_clothing_ratio=0.0,
|
|
standard_pose_ratio=1.0,
|
|
trigger=Trigger,
|
|
prepend_trigger_to_prompt=True,
|
|
extra_positive="built-in route marker",
|
|
extra_negative="built-in route negative",
|
|
expression_intensity=0.5,
|
|
expression_enabled=False,
|
|
)
|
|
built_in_route = builder_prompt_route.build_prompt_result(built_in_request, pb._prompt_build_dependencies())
|
|
_expect(built_in_route.row == legacy_from_request(built_in_request), "Builder built-in route should match public wrapper")
|
|
_expect(built_in_route.branch == "built_in", "Builder prompt route lost built-in branch")
|
|
_expect(built_in_route.row.get("source") == "built_in_generator", "Builder built-in branch changed source")
|
|
built_in_trace = built_in_route.row.get("generation_trace")
|
|
_expect(isinstance(built_in_trace, dict), "Builder built-in route lost generation_trace")
|
|
_expect(built_in_trace.get("branch") == "built_in", "Builder built-in generation_trace lost branch")
|
|
_expect(built_in_trace.get("source") == "built_in_generator", "Builder built-in generation_trace lost source")
|
|
_expect(built_in_trace.get("seed_axes", {}).get("person", {}).get("source") == "main", "Builder built-in generation_trace should follow main seed")
|
|
_expect(built_in_route.row.get("expression_disabled") is True, "Builder built-in branch lost expression disable")
|
|
_expect("built-in route marker" in built_in_route.row.get("prompt", ""), "Builder built-in branch lost extra positive")
|
|
|
|
auto_weighted_request = builder_prompt_route.PromptBuildRequest(
|
|
category="auto_weighted",
|
|
subcategory="random",
|
|
row_number=2,
|
|
start_index=10,
|
|
seed=3504,
|
|
clothing="random",
|
|
ethnicity="any",
|
|
poses="random",
|
|
backside_bias=0.35,
|
|
figure="random",
|
|
no_plus_women=False,
|
|
no_black=False,
|
|
minimal_clothing_ratio=0.4,
|
|
standard_pose_ratio=0.6,
|
|
trigger=Trigger,
|
|
prepend_trigger_to_prompt=True,
|
|
extra_positive="auto route marker",
|
|
extra_negative="auto route negative",
|
|
seed_config=pb.build_seed_lock_config_json(base_seed=3504, reroll_axis="person", reroll_seed=3505),
|
|
expression_intensity=0.7,
|
|
expression_enabled=True,
|
|
)
|
|
auto_route = builder_prompt_route.build_prompt_result(auto_weighted_request, pb._prompt_build_dependencies())
|
|
_expect(auto_route.row == legacy_from_request(auto_weighted_request), "Builder auto-weighted route should match public wrapper")
|
|
_expect(auto_route.branch == "auto_weighted", "Builder prompt route lost auto-weighted branch")
|
|
_expect(auto_route.parsed_seed_config.get("person_seed") == 3505, "Builder auto-weighted branch lost person seed lock")
|
|
auto_trace = auto_route.row.get("generation_trace")
|
|
_expect(isinstance(auto_trace, dict), "Builder auto-weighted route lost generation_trace")
|
|
_expect(auto_trace.get("branch") == "auto_weighted", "Builder auto-weighted generation_trace lost branch")
|
|
_expect(auto_trace.get("seed_axes", {}).get("person", {}).get("source") == "configured", "Builder auto-weighted trace lost configured person seed")
|
|
_expect(auto_trace.get("seed_axes", {}).get("person", {}).get("seed") == 3505, "Builder auto-weighted trace lost person seed")
|
|
_expect("auto route marker" in auto_route.row.get("prompt", ""), "Builder auto-weighted branch lost extra positive")
|
|
|
|
|
|
def smoke_builder_config_route_policy() -> None:
|
|
category_config = pb.build_category_config_json("women_casual", "Casual clothes / Smart casual")
|
|
cast_config = pb.build_cast_config_json("solo_woman")
|
|
generation_profile = pb.build_generation_profile_json(
|
|
profile="casual_clean",
|
|
trigger_policy="prepend_trigger",
|
|
)
|
|
filter_config = pb.build_filter_config_json(
|
|
ethnicity="french_european",
|
|
figure="balanced",
|
|
)
|
|
seed_config_json = pb.build_seed_lock_config_json(base_seed=3401, reroll_axis="scene", reroll_seed=3402)
|
|
request = builder_config_route.PromptFromConfigsRequest(
|
|
row_number=2,
|
|
start_index=5,
|
|
seed=3401,
|
|
category_config=category_config,
|
|
cast_config=cast_config,
|
|
generation_profile=generation_profile,
|
|
filter_config=filter_config,
|
|
seed_config=seed_config_json,
|
|
extra_positive="clean route marker",
|
|
extra_negative="bad route marker",
|
|
)
|
|
typed_route = builder_config_route.build_prompt_from_configs_result(
|
|
request,
|
|
pb._prompt_from_configs_dependencies(),
|
|
)
|
|
legacy_row = pb.build_prompt_from_configs(
|
|
row_number=request.row_number,
|
|
start_index=request.start_index,
|
|
seed=request.seed,
|
|
category_config=category_config,
|
|
cast_config=cast_config,
|
|
generation_profile=generation_profile,
|
|
filter_config=filter_config,
|
|
seed_config=seed_config_json,
|
|
extra_positive=request.extra_positive,
|
|
extra_negative=request.extra_negative,
|
|
)
|
|
_expect(typed_route.row == legacy_row, "Prompt Builder From Configs route should match public wrapper output")
|
|
_expect(typed_route.category == "Casual clothes", "Config route lost category preset")
|
|
_expect(typed_route.subcategory == "Casual clothes / Smart casual", "Config route lost requested subcategory")
|
|
_expect(typed_route.cast["women_count"] == 1 and typed_route.cast["men_count"] == 0, "Config route lost cast preset")
|
|
_expect(typed_route.profile["trigger"] == "sxcpinup_coloredpencil", "Config route lost generation profile trigger")
|
|
_expect(typed_route.filters["ethnicity"] == "french_european", "Config route lost filter ethnicity")
|
|
config_trace = typed_route.row.get("generation_trace")
|
|
_expect(isinstance(config_trace, dict), "Config route row lost generation_trace")
|
|
_expect(config_trace.get("branch") == "custom", "Config route generation_trace lost builder branch")
|
|
_expect(config_trace.get("seed_axes", {}).get("scene", {}).get("source") == "configured", "Config route generation_trace lost scene seed lock")
|
|
_expect(config_trace.get("seed_axes", {}).get("scene", {}).get("seed") == 3402, "Config route generation_trace lost scene reroll seed")
|
|
kwargs = typed_route.build_kwargs
|
|
_expect(kwargs["category"] == typed_route.category, "Config route build kwargs category drifted")
|
|
_expect(kwargs["subcategory"] == typed_route.subcategory, "Config route build kwargs subcategory drifted")
|
|
_expect(kwargs["women_count"] == 1 and kwargs["men_count"] == 0, "Config route build kwargs cast counts drifted")
|
|
_expect(kwargs["seed_config"] == seed_config_json, "Config route build kwargs seed config drifted")
|
|
_expect(kwargs["extra_positive"] == "clean route marker", "Config route build kwargs extra positive drifted")
|
|
_expect("clean route marker" in typed_route.row.get("prompt", ""), "Config route row lost extra positive")
|
|
_expect("bad route marker" in typed_route.row.get("negative_prompt", ""), "Config route row lost extra negative")
|
|
|
|
|
|
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",
|
|
"camera_scene_directive": "Camera-aware studio layout with window depth.",
|
|
"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)")
|
|
single_figure_note = dict(
|
|
single,
|
|
body_phrase="slim busty figure with soft, natural fullness up top and a small waist",
|
|
)
|
|
figure_note_prompt, figure_note_method = krea_formatter._normal_row_to_krea(
|
|
single_figure_note,
|
|
"balanced",
|
|
"preserve",
|
|
)
|
|
_expect(figure_note_method == "metadata(single)", "Krea single figure-note route changed method")
|
|
_expect(
|
|
"adult woman, with slim busty figure with" not in figure_note_prompt,
|
|
"Krea single route kept old comma-with figure grammar",
|
|
)
|
|
_expect(
|
|
"adult woman with a slim busty figure defined by soft" in figure_note_prompt,
|
|
"Krea single route did not attach figure-note appearance to the subject",
|
|
)
|
|
_expect(
|
|
"adult woman with a slim busty figure with" not in figure_note_prompt,
|
|
"Krea single route kept nested figure-with wording",
|
|
)
|
|
_expect(
|
|
"adult woman, showing slim busty figure with" not in figure_note_prompt,
|
|
"Krea single route kept awkward showing figure-note grammar",
|
|
)
|
|
_expect(
|
|
"warm daylight, Camera-aware studio layout" not in figure_note_prompt,
|
|
"Krea single route joined camera-scene directive to scene with a comma",
|
|
)
|
|
_expect(
|
|
"warm daylight. Camera-aware studio layout" in figure_note_prompt,
|
|
"Krea single route did not split camera-scene directive into its own sentence",
|
|
)
|
|
style_metadata = dict(
|
|
single,
|
|
style="metadata style phrase",
|
|
positive_suffix="metadata suffix phrase",
|
|
prompt="Use: stale prompt suffix phrase.",
|
|
)
|
|
style_prompt, style_method = krea_formatter._normal_row_to_krea(style_metadata, "balanced", "preserve")
|
|
_expect(style_method == "metadata(single)", "Krea style metadata route changed method")
|
|
style_prompt_lower = style_prompt.lower()
|
|
_expect("metadata style phrase" in style_prompt_lower, "Krea metadata route lost structured style")
|
|
_expect("metadata suffix phrase" in style_prompt_lower, "Krea metadata route lost structured positive suffix")
|
|
_expect("stale prompt suffix" not in style_prompt_lower, "Krea metadata route parsed stale Use prompt text")
|
|
|
|
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)")
|
|
|
|
configured_with_descriptor = _fixture_hardcore_row(
|
|
prompt="Characters: stale prompt subject, stale body, stale skin, stale hair, stale eyes.",
|
|
cast_descriptor_text=(
|
|
"Woman A: 30-year-old adult woman, toned figure, fair skin, red hair, gray eyes; "
|
|
"Man A: 45-year-old adult man, average figure, tan skin, dark hair"
|
|
),
|
|
)
|
|
descriptor_prompt, descriptor_method = krea_formatter._normal_row_to_krea(
|
|
configured_with_descriptor,
|
|
"balanced",
|
|
"preserve",
|
|
)
|
|
_expect(descriptor_method == "metadata(configured_cast)", "Krea configured-cast route changed method")
|
|
_expect("30-year-old adult woman" in descriptor_prompt, "Krea configured-cast route lost descriptor metadata")
|
|
_expect("toned figure" in descriptor_prompt, "Krea configured-cast route lost descriptor body metadata")
|
|
_expect("stale" not in descriptor_prompt, "Krea configured-cast route parsed stale prompt character labels")
|
|
|
|
configured_without_descriptor = _fixture_hardcore_row(
|
|
prompt="Characters: stale prompt subject, stale body, stale skin, stale hair, stale eyes.",
|
|
cast_descriptor_text="",
|
|
cast_descriptors=[],
|
|
)
|
|
no_descriptor_prompt, no_descriptor_method = krea_formatter._normal_row_to_krea(
|
|
configured_without_descriptor,
|
|
"balanced",
|
|
"preserve",
|
|
)
|
|
_expect(no_descriptor_method == "metadata(configured_cast)", "Krea configured-cast no-descriptor route changed method")
|
|
_expect("stale" not in no_descriptor_prompt, "Krea configured-cast route should not parse prompt labels without metadata")
|
|
_expect("private studio room with warm light" in no_descriptor_prompt, "Krea configured-cast route lost structured scene")
|
|
|
|
|
|
def smoke_krea_action_details_policy() -> None:
|
|
_expect(
|
|
krea_action_details.strip_redundant_position_detail(
|
|
"kneeling penis-licking position while slow tongue licking on the underside of the penis"
|
|
)
|
|
== "slow tongue licking on the underside of the penis",
|
|
"Krea action detail cleanup should remove leading position-while scaffolding",
|
|
)
|
|
_expect(
|
|
krea_action_details.strip_redundant_position_detail(
|
|
"raised edge fingering position featuring mutual masturbation with both bodies touching themselves"
|
|
)
|
|
== "mutual masturbation with both bodies touching themselves",
|
|
"Krea action detail cleanup should remove leading position-featuring scaffolding",
|
|
)
|
|
_expect(
|
|
krea_action_details.strip_redundant_position_detail(
|
|
"footjob with toes curled around the penis shaft in seated footjob position"
|
|
)
|
|
== "footjob with toes curled around the penis shaft",
|
|
"Krea action detail cleanup should remove trailing in-position scaffolding",
|
|
)
|
|
_expect(
|
|
"position while"
|
|
not in krea_action_details.dedupe_outercourse_detail(
|
|
"kneeling penis-licking position while slow tongue licking on the underside of the penis",
|
|
"the woman bends forward between the man's open thighs",
|
|
"penis licking",
|
|
{"position": "kneeling penis-licking position"},
|
|
).lower(),
|
|
"Krea outercourse detail cleanup leaked position-while scaffolding",
|
|
)
|
|
_expect(
|
|
"position featuring"
|
|
not in krea_action_details.sanitize_foreplay_detail(
|
|
"raised edge fingering position featuring mutual masturbation with both bodies touching themselves",
|
|
"the woman and man sit close facing each other",
|
|
).lower(),
|
|
"Krea foreplay/manual detail cleanup leaked position-featuring scaffolding",
|
|
)
|
|
_expect(
|
|
krea_action_details.dedupe_anchor_detail(
|
|
"side-lying anal position, one leg lifted high",
|
|
"side-lying rear-entry anal pose",
|
|
)
|
|
== "one leg lifted high",
|
|
"Krea anchored detail cleanup should remove repeated anal position prefix",
|
|
)
|
|
|
|
|
|
def smoke_outercourse_action_policy() -> None:
|
|
_expect(
|
|
outercourse_action_policy.infer_outercourse_action_kind("kneeling boobjob position")
|
|
== outercourse_action_policy.OUTERCOURSE_BOOBJOB,
|
|
"Outercourse classifier lost boobjob position detection",
|
|
)
|
|
_expect(
|
|
outercourse_action_policy.infer_outercourse_action_kind("bent-over balls-licking position")
|
|
== outercourse_action_policy.OUTERCOURSE_TESTICLE,
|
|
"Outercourse classifier lost testicle/balls position detection",
|
|
)
|
|
_expect(
|
|
outercourse_action_policy.infer_outercourse_action_kind("tongue runs along the underside of the penis")
|
|
== outercourse_action_policy.OUTERCOURSE_PENIS_LICKING,
|
|
"Outercourse classifier lost penis-licking detail detection",
|
|
)
|
|
_expect(
|
|
outercourse_action_policy.infer_outercourse_action_kind("two-handed handjob with thumb and fingers around the penis")
|
|
== outercourse_action_policy.OUTERCOURSE_HANDJOB,
|
|
"Outercourse classifier lost handjob detection",
|
|
)
|
|
_expect(
|
|
outercourse_action_policy.infer_outercourse_action_kind("footjob with toes curled around the penis")
|
|
== outercourse_action_policy.OUTERCOURSE_FOOTJOB,
|
|
"Outercourse classifier lost footjob detection",
|
|
)
|
|
axis_text = krea_action_context.axis_values_text(
|
|
{
|
|
"outer_act": "handjob with one hand wrapped around the penis",
|
|
"contact_detail": "hand wrapped around the penis shaft with the glans visible",
|
|
"visibility": "hand and penis centered in frame",
|
|
}
|
|
)
|
|
_expect("handjob" in axis_text and "hand and penis" in axis_text, "Krea action context lost outercourse axes")
|
|
role_graph = hardcore_role_outercourse.build_outercourse_role_graph(
|
|
"Woman A",
|
|
"Man A",
|
|
"testicle sucking with lips around the balls",
|
|
{"position": "bent-over testicle-sucking position"},
|
|
["Man A"],
|
|
)
|
|
lower_role = role_graph.lower()
|
|
_expect("side-pelvis pov" in lower_role, "POV testicle role graph lost side-low geometry")
|
|
_expect("cheek against the pov viewer's inner thigh" in lower_role, "POV testicle role graph lost cheek/thigh geometry")
|
|
_expect("scrotum is the mouth surface" in lower_role, "POV testicle role graph lost scrotum-mouth surface target")
|
|
_expect("scrotal skin is the nearest mouth surface" in lower_role, "POV testicle role graph lost scrotal-skin nearest-surface target")
|
|
_expect("testicles resting across her open lips" in lower_role, "POV testicle role graph lost open-lips testicle target")
|
|
_expect("both testicles rest against her tongue from below" in lower_role, "POV testicle role graph lost tongue-from-below target")
|
|
deduped = krea_action_details.dedupe_outercourse_detail(
|
|
"testicle sucking with lips around the balls, balls and mouth contact visible, wet lips and tongue contact",
|
|
role_graph,
|
|
"testicle sucking with lips around the balls",
|
|
{"position": "bent-over testicle-sucking position"},
|
|
).lower()
|
|
_expect("testicle" not in deduped and "balls and mouth" not in deduped, "Krea outercourse dedupe kept redundant testicle clauses")
|
|
_expect("wet lips" in deduped, "Krea outercourse dedupe removed useful texture clause")
|
|
|
|
|
|
def smoke_item_axis_policy() -> None:
|
|
axis_values = {
|
|
"ignored": "random",
|
|
"position": "kneeling oral position",
|
|
"contact_detail": {"text": "mouth contact at hip height"},
|
|
"nested": {"unused": "fallback body detail"},
|
|
"list_detail": ["hands on hips", "auto"],
|
|
"unprioritized_detail": "extra unprioritized cue",
|
|
}
|
|
texts = item_axis_policy.axis_value_texts(axis_values)
|
|
_expect("kneeling oral position" in texts, "Item axis policy lost position value")
|
|
_expect("mouth contact at hip height" in texts, "Item axis policy lost preferred dict text")
|
|
_expect("fallback body detail" in texts, "Item axis policy lost nested fallback text")
|
|
_expect("hands on hips" in texts, "Item axis policy lost list text")
|
|
_expect("random" not in texts and "auto" not in texts, "Item axis policy leaked placeholder values")
|
|
_expect(
|
|
item_axis_policy.axis_value_texts(axis_values, existing_text="kneeling oral position already present")[0]
|
|
== "mouth contact at hip height",
|
|
"Item axis policy should skip details already present in existing text",
|
|
)
|
|
context_text = item_axis_policy.action_context_text(axis_values)
|
|
_expect("kneeling oral position" in context_text, "Item axis policy context lost priority position")
|
|
_expect("mouth contact at hip height" in context_text, "Item axis policy context lost priority contact")
|
|
_expect("extra unprioritized cue" not in context_text, "Item axis policy context should ignore unprioritized values")
|
|
_expect(
|
|
krea_action_context.axis_values_text(axis_values) == context_text,
|
|
"Krea action context should delegate to shared item axis policy",
|
|
)
|
|
|
|
|
|
def smoke_krea_row_fields_policy() -> None:
|
|
row = {
|
|
"subject_type": "configured_cast",
|
|
"primary_subject": "woman",
|
|
"cast_summary": "1 woman, 1 man",
|
|
"item": "lace bodysuit fashion editorial styling",
|
|
"pose": "standing close together",
|
|
"scene_text": "private room with warm lamps",
|
|
"expression": "soft smile",
|
|
"expression_enabled": False,
|
|
"composition": "vertical tight two-person frame",
|
|
"source_composition": "vertical source action frame",
|
|
"camera_directive": "Camera: eye-level close-up",
|
|
"camera_scene_directive": "Camera-aware scene layout.",
|
|
"style": "realistic social photo",
|
|
}
|
|
fields = krea_row_fields.extract_krea_row_fields(
|
|
row,
|
|
"preserve",
|
|
krea_formatter._krea_row_field_dependencies(),
|
|
)
|
|
normal_request = krea_formatter._krea_normal_row_request_from_row(row, "balanced", "preserve")
|
|
cast_request = krea_formatter._krea_configured_cast_request_from_row(row, "balanced", "preserve")
|
|
_expect(fields.item == "lace bodysuit", "Krea row fields did not strip generic styling suffix")
|
|
_expect(fields.expression == "", "Krea row fields ignored expression disabled flag")
|
|
_expect(fields.composition == "tight two-person frame", "Krea row fields did not normalize composition prefix")
|
|
_expect(fields.source_composition == "source action frame", "Krea row fields did not normalize source composition")
|
|
_expect(normal_request.item == fields.item, "Normal Krea route item extraction drifted")
|
|
_expect(cast_request.item == fields.item, "Configured-cast Krea route item extraction drifted")
|
|
_expect(normal_request.expression == cast_request.expression == fields.expression, "Krea route expression extraction drifted")
|
|
_expect(normal_request.camera == cast_request.camera == fields.camera, "Krea route camera extraction drifted")
|
|
_expect(cast_request.source_composition == fields.source_composition, "Configured-cast source composition drifted")
|
|
|
|
|
|
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")
|
|
_expect("creator_bedroom" in location_config.location_theme_choices(), "Location themes lost creator_bedroom")
|
|
_expect("mirror_room" in location_config.location_theme_choices(), "Location themes lost mirror_room")
|
|
_expect("workspace_lounge" in location_config.location_theme_choices(), "Location themes lost workspace_lounge")
|
|
_expect("fetish_studio" in location_config.location_theme_choices(), "Location themes lost fetish_studio")
|
|
|
|
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")
|
|
structured_custom = json.loads(
|
|
pb.build_location_pool_json(
|
|
enabled=True,
|
|
combine_mode="replace",
|
|
preset="custom_only",
|
|
custom_locations=json.dumps(
|
|
{
|
|
"slug": "structured_room",
|
|
"text": "structured room with preserved metadata",
|
|
"camera_profile": {
|
|
"key": "structured_camera_profile",
|
|
"foreground": "foreground test anchor",
|
|
"midground": "middle test anchor",
|
|
"background": "background test anchor",
|
|
},
|
|
},
|
|
sort_keys=True,
|
|
),
|
|
)
|
|
)
|
|
structured_entry = structured_custom.get("scene_entries", [{}])[0]
|
|
_expect(structured_entry.get("slug") == "structured_room", "Structured custom location lost slug")
|
|
_expect(structured_entry.get("prompt") == "structured room with preserved metadata", "Structured custom location did not normalize prompt text")
|
|
_expect(structured_entry.get("camera_profile", {}).get("key") == "structured_camera_profile", "Structured custom location lost camera profile metadata")
|
|
|
|
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",
|
|
)
|
|
structured_composition = json.loads(
|
|
pb.build_composition_pool_json(
|
|
enabled=True,
|
|
combine_mode="replace",
|
|
preset="custom_only",
|
|
custom_compositions=json.dumps({"text": "structured composition frame", "source": "json_line"}, sort_keys=True),
|
|
)
|
|
)
|
|
structured_composition_entry = structured_composition.get("composition_entries", [{}])[0]
|
|
_expect(structured_composition_entry.get("prompt") == "structured composition frame", "Structured custom composition did not normalize prompt text")
|
|
_expect(structured_composition_entry.get("source") == "json_line", "Structured custom composition lost metadata")
|
|
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")
|
|
themed_location_payload = json.loads(themed_location)
|
|
themed_composition_payload = json.loads(themed_composition)
|
|
_expect(themed_location_payload.get("scene_entries"), "Themed location did not output locations")
|
|
_expect(themed_location_payload.get("theme") == "classical_library", "Themed location config lost theme metadata")
|
|
_expect(
|
|
all(
|
|
not isinstance(entry, dict) or entry.get("theme") == "classical_library"
|
|
for entry in themed_location_payload.get("scene_entries") or []
|
|
),
|
|
"Themed location entries lost theme metadata",
|
|
)
|
|
_expect(themed_composition_payload.get("composition_entries"), "Themed location did not output compositions")
|
|
_expect(themed_composition_payload.get("theme") == "classical_library", "Themed composition config lost theme metadata")
|
|
parsed_themed = pb._parse_location_config(themed_location_payload)
|
|
_expect(parsed_themed.get("theme") == "classical_library", "Location parser lost theme metadata")
|
|
workspace_location, workspace_composition, workspace_summary = pb.build_thematic_location_json(
|
|
enabled=True,
|
|
combine_mode="replace",
|
|
theme="workspace_lounge",
|
|
)
|
|
workspace_location_payload = json.loads(workspace_location)
|
|
workspace_composition_payload = json.loads(workspace_composition)
|
|
_expect("workspace_lounge" in workspace_summary, "Workspace theme summary lost theme name")
|
|
_expect(
|
|
workspace_location_payload.get("scene_entries", [{}])[0].get("slug") == "coworking_lounge_window",
|
|
"Workspace theme lost coworking lounge location slug",
|
|
)
|
|
_expect(workspace_composition_payload.get("composition_entries"), "Workspace theme did not output compositions")
|
|
workspace_compositions_text = " ".join(str(entry) for entry in workspace_composition_payload.get("composition_entries") or [])
|
|
_expect("camera-aware" not in workspace_compositions_text.lower(), "Workspace theme leaked internal camera-aware wording")
|
|
replaced_after_theme = json.loads(
|
|
location_config.build_location_pool_json(
|
|
enabled=True,
|
|
combine_mode="replace",
|
|
preset="custom_only",
|
|
custom_locations="plain_room: plain room after theme",
|
|
location_config=themed_location_payload,
|
|
)
|
|
)
|
|
_expect(replaced_after_theme.get("theme") == "", "Location replace mode should not inherit upstream theme metadata")
|
|
replaced_composition_after_theme = json.loads(
|
|
location_config.build_composition_pool_json(
|
|
enabled=True,
|
|
combine_mode="replace",
|
|
preset="custom_only",
|
|
custom_compositions="plain composition after theme",
|
|
composition_config=themed_composition_payload,
|
|
)
|
|
)
|
|
_expect(
|
|
replaced_composition_after_theme.get("theme") == "",
|
|
"Composition replace mode should not inherit upstream theme metadata",
|
|
)
|
|
|
|
|
|
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(updated.get("scene_entry", {}).get("slug") == "archive_corner", "Row location policy lost selected scene entry")
|
|
_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_entry", {}).get("prompt") == "long archive aisle composition",
|
|
"Row location policy lost selected composition entry",
|
|
)
|
|
_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["scene_entry"].get("slug") == "studio", "Prompt axes route lost selected scene entry slug")
|
|
_expect(
|
|
route["scene_entry"].get("prompt") == "quiet studio with repeatable anchors",
|
|
"Prompt axes route lost selected scene entry prompt",
|
|
)
|
|
_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")
|
|
_expect(
|
|
route["composition_entry"].get("prompt") == "all participants visible centered frame",
|
|
"Prompt axes route lost selected composition entry",
|
|
)
|
|
|
|
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",
|
|
)
|
|
anal_leg_values = [
|
|
"standing with legs braced",
|
|
"one leg lifted high",
|
|
"kneeling with thighs apart",
|
|
"knees pressed to chest",
|
|
]
|
|
_expect(
|
|
pb._anal_axis_values_for_position(anal_leg_values, "side-lying anal position", "leg_detail")
|
|
== row_item.anal_axis_values_for_position(anal_leg_values, "side-lying anal position", "leg_detail")
|
|
== ["one leg lifted high"],
|
|
"Row item anal leg-detail filtering changed for side-lying anal",
|
|
)
|
|
|
|
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")
|
|
|
|
anal_subcategory = {
|
|
"name": "Anal and double penetration",
|
|
"slug": "anal_double_penetration",
|
|
"item_templates": [
|
|
{
|
|
"template": "{anal_act} in {position}, with {leg_detail}",
|
|
"action_family": "default",
|
|
"position_family": "anal",
|
|
}
|
|
],
|
|
"item_axes": {
|
|
"position": ["side-lying anal position"],
|
|
"anal_act": ["penis entering ass"],
|
|
"leg_detail": anal_leg_values,
|
|
},
|
|
}
|
|
anal_text, _anal_name, anal_axis_values, _anal_metadata = row_item.compose_item(
|
|
random.Random(5),
|
|
{},
|
|
anal_subcategory,
|
|
"Anal and double penetration",
|
|
women_count=1,
|
|
men_count=1,
|
|
)
|
|
_expect("standing with legs braced" not in anal_text, "Row item compose leaked standing legs into side-lying anal")
|
|
_expect(anal_axis_values.get("leg_detail") == "one leg lifted high", "Row item compose did not apply anal leg-detail filter")
|
|
|
|
|
|
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")
|
|
|
|
exposed_route = row_category_route.select_category_item_route(
|
|
category_choice="custom_random",
|
|
subcategory_choice="Provocative erotic clothes / Sheer exposed",
|
|
seed_config=seed_cfg,
|
|
seed=2302,
|
|
row_number=1,
|
|
women_count=1,
|
|
men_count=0,
|
|
hardcore_position_config={},
|
|
)
|
|
_expect(exposed_route["subcategory"]["slug"] == "sheer_exposed", "Row category route selected wrong exposed category")
|
|
_expect(exposed_route["content_axis"] == "content", "Exposed clothing slug should not be treated as pose content")
|
|
_expect(exposed_route["is_pose_category"] is False, "Exposed clothing slug 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",
|
|
)
|
|
|
|
previous_category_dir = category_library.CATEGORY_DIR
|
|
previous_extensions_applied = category_extensions._EXTENSIONS_APPLIED
|
|
try:
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
category_library.CATEGORY_DIR = Path(temp_dir)
|
|
category_extensions._EXTENSIONS_APPLIED = False
|
|
Path(temp_dir, "custom_category.json").write_text(
|
|
json.dumps(
|
|
{
|
|
"categories": {
|
|
"Dev Test Wear": {
|
|
"prompt_template": (
|
|
"{subject_phrase}: clean test style. {item_label}: {item}. "
|
|
"Scene: {scene}. Pose: {pose}. Facial expression: {expression}. "
|
|
"Composition: {composition}."
|
|
),
|
|
"caption_template": "{subject_phrase}, {item}, {scene}, {composition}",
|
|
"subcategories": {
|
|
"Layered Office": {
|
|
"items": ["structured test blazer over dark trousers"],
|
|
"scenes": ["temporary showroom with modular shelves"],
|
|
"poses": ["standing by a rail"],
|
|
"expressions": ["focused calm look"],
|
|
"compositions": ["centered catalog frame"],
|
|
}
|
|
},
|
|
},
|
|
"Dev / Test Wear": {
|
|
"prompt_template": (
|
|
"{subject_phrase}: slash-name test style. {item_label}: {item}. "
|
|
"Scene: {scene}. Pose: {pose}. Facial expression: {expression}. "
|
|
"Composition: {composition}."
|
|
),
|
|
"caption_template": "{subject_phrase}, {item}, {scene}, {composition}",
|
|
"subcategories": {
|
|
"Layered / Office": {
|
|
"items": ["slash-safe structured blazer over tailored trousers"],
|
|
"scenes": ["slash-safe showroom with glass shelving"],
|
|
"poses": ["standing beside a mirrored divider"],
|
|
"expressions": ["focused exact-route look"],
|
|
"compositions": ["slash-safe centered catalog frame"],
|
|
}
|
|
},
|
|
},
|
|
}
|
|
},
|
|
ensure_ascii=True,
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
_expect("Dev Test Wear" in pb.category_choices(), "User-added JSON category did not reach category choices")
|
|
_expect(
|
|
"Dev Test Wear / Layered Office" in pb.subcategory_choices(),
|
|
"User-added JSON subcategory did not reach subcategory choices",
|
|
)
|
|
slash_selector = "Dev / Test Wear / Layered / Office"
|
|
_expect("Dev / Test Wear" in pb.category_choices(), "Slash-bearing user category did not reach category choices")
|
|
_expect(
|
|
slash_selector in pb.subcategory_choices(),
|
|
"Slash-bearing user subcategory did not reach subcategory choices",
|
|
)
|
|
row = pb.build_prompt(
|
|
category="Dev Test Wear",
|
|
subcategory="Dev Test Wear / Layered Office",
|
|
row_number=1,
|
|
start_index=1,
|
|
seed=4109,
|
|
clothing="random",
|
|
ethnicity="any",
|
|
poses="random",
|
|
backside_bias=0.0,
|
|
figure="random",
|
|
no_plus_women=False,
|
|
no_black=False,
|
|
minimal_clothing_ratio=0.0,
|
|
standard_pose_ratio=1.0,
|
|
trigger=Trigger,
|
|
prepend_trigger_to_prompt=True,
|
|
extra_positive="",
|
|
extra_negative="",
|
|
women_count=1,
|
|
men_count=0,
|
|
)
|
|
_expect_row_base(row, "category_extensions_user_added_json")
|
|
_expect(row.get("source") == "json_category", "User-added JSON category did not use custom row route")
|
|
_expect(row.get("main_category") == "Dev Test Wear", "User-added JSON category name drifted")
|
|
_expect(row.get("subcategory") == "Layered Office", "User-added JSON subcategory name drifted")
|
|
_expect(row.get("item") == "structured test blazer over dark trousers", "User-added JSON item did not generate")
|
|
_expect(row.get("scene_text") == "temporary showroom with modular shelves", "User-added JSON scene did not generate")
|
|
_expect(row.get("pose") == "standing by a rail", "User-added JSON pose did not generate")
|
|
_expect(row.get("expression") == "focused calm look", "User-added JSON expression did not generate")
|
|
_expect(row.get("composition") == "centered catalog frame", "User-added JSON composition did not generate")
|
|
_expect_formatter_outputs(row, "category_extensions_user_added_json", target="single")
|
|
slash_row = pb.build_prompt(
|
|
category="Dev / Test Wear",
|
|
subcategory=slash_selector,
|
|
row_number=1,
|
|
start_index=1,
|
|
seed=4110,
|
|
clothing="random",
|
|
ethnicity="any",
|
|
poses="random",
|
|
backside_bias=0.0,
|
|
figure="random",
|
|
no_plus_women=False,
|
|
no_black=False,
|
|
minimal_clothing_ratio=0.0,
|
|
standard_pose_ratio=1.0,
|
|
trigger=Trigger,
|
|
prepend_trigger_to_prompt=True,
|
|
extra_positive="",
|
|
extra_negative="",
|
|
women_count=1,
|
|
men_count=0,
|
|
)
|
|
_expect_row_base(slash_row, "category_extensions_slash_named_json")
|
|
_expect(slash_row.get("main_category") == "Dev / Test Wear", "Slash-bearing JSON category name drifted")
|
|
_expect(slash_row.get("subcategory") == "Layered / Office", "Slash-bearing JSON subcategory name drifted")
|
|
_expect(
|
|
slash_row.get("item") == "slash-safe structured blazer over tailored trousers",
|
|
"Slash-bearing JSON exact subcategory did not generate the intended item",
|
|
)
|
|
_expect_formatter_outputs(slash_row, "category_extensions_slash_named_json", target="single")
|
|
finally:
|
|
category_library.CATEGORY_DIR = previous_category_dir
|
|
category_extensions._EXTENSIONS_APPLIED = previous_extensions_applied
|
|
|
|
|
|
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("woman" in category_cast_config.category_preset_choices(), "Category preset choices lost built-in woman")
|
|
_expect("man" in category_cast_config.category_preset_choices(), "Category preset choices lost built-in man")
|
|
_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")
|
|
builtin_woman_config = json.loads(pb.build_category_config_json("woman", "random"))
|
|
_expect(builtin_woman_config.get("preset") == "woman", "Built-in woman category preset fell back")
|
|
_expect(builtin_woman_config.get("category") == "woman", "Built-in woman category config lost direct category")
|
|
_expect(pb._parse_category_config(builtin_woman_config) == ("woman", "random"), "Built-in woman category parser 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"))
|
|
|
|
legacy_couple = row_normalization.normalize_prompt_row(
|
|
{
|
|
"source": "built_in_generator",
|
|
"primary_subject": "two women",
|
|
"scene": "office",
|
|
"prompt": (
|
|
"Two adults in a clean legacy prompt. Scene: old room. "
|
|
"Pose: standing close, affectionate and flirtatious but non-explicit. "
|
|
"Facial expressions: one with a calm smile, the other with a side glance. "
|
|
"Clothing: coordinated satin outfits, resort styling. Composition: vertical old frame."
|
|
),
|
|
"caption": "legacy couple caption",
|
|
"negative_prompt": "bad anatomy",
|
|
},
|
|
active_trigger=Trigger,
|
|
prepend_trigger_to_prompt=False,
|
|
)
|
|
_expect(legacy_couple.get("subject_type") == "couple", "Legacy couple row lost normalized subject_type")
|
|
_expect(legacy_couple.get("women_count") == 2 and legacy_couple.get("men_count") == 0, "Legacy couple row lost normalized counts")
|
|
_expect(legacy_couple.get("person_count") == 2, "Legacy couple row lost normalized person count")
|
|
_expect(legacy_couple.get("scene_slug") == "office", "Legacy couple row lost scene slug")
|
|
_expect("cozy office desk" in str(legacy_couple.get("scene_text", "")), "Legacy couple row lost readable scene text")
|
|
_expect(legacy_couple.get("scene_entry", {}).get("slug") == "office", "Legacy couple row lost scene entry")
|
|
_expect(legacy_couple.get("pose") == "standing close", "Legacy couple row did not clean pose suffix")
|
|
_expect(legacy_couple.get("item") == "coordinated satin outfits", "Legacy couple row did not clean clothing suffix")
|
|
_expect(legacy_couple.get("item_label") == "Clothing", "Legacy couple row lost item label")
|
|
_expect("calm smile" in str(legacy_couple.get("expression", "")), "Legacy couple row lost expression metadata")
|
|
_expect("cast_summary" not in legacy_couple, "Legacy couple row should not gain configured-cast summary")
|
|
|
|
legacy_single = row_normalization.normalize_prompt_row(
|
|
{
|
|
"source": "built_in_generator",
|
|
"primary_subject": "woman",
|
|
"scene": "studio",
|
|
"age_band": "25-year-old adult",
|
|
"body_type": "curvy",
|
|
"figure": "soft curves, defined waist",
|
|
"caption": (
|
|
f"{Trigger}, woman, 25-year-old adult, curvy figure with soft curves, defined waist, "
|
|
"warm skin, short blonde hair, blue eyes, pose, expression, clothing, scene, composition"
|
|
),
|
|
"prompt": (
|
|
"A woman. Scene: old studio. Pose: standing calmly. "
|
|
"Facial expression: direct look. Clothing: fitted dress, fashion editorial styling. "
|
|
"Composition: vertical portrait."
|
|
),
|
|
"negative_prompt": "bad anatomy",
|
|
},
|
|
active_trigger=Trigger,
|
|
prepend_trigger_to_prompt=False,
|
|
)
|
|
_expect(legacy_single.get("body_phrase") == "curvy figure with soft curves, defined waist", "Legacy single row lost body phrase")
|
|
_expect(legacy_single.get("skin") == "warm skin", "Legacy single row lost skin metadata")
|
|
_expect(legacy_single.get("hair") == "short blonde hair", "Legacy single row lost hair metadata")
|
|
_expect(legacy_single.get("eyes") == "blue eyes", "Legacy single row lost eye metadata")
|
|
|
|
legacy_group = row_normalization.normalize_prompt_row(
|
|
{
|
|
"source": "built_in_generator",
|
|
"primary_subject": "mixed adult group",
|
|
"prompt": "Group legacy prompt.",
|
|
"caption": "legacy group caption",
|
|
"negative_prompt": "bad anatomy",
|
|
},
|
|
active_trigger=Trigger,
|
|
prepend_trigger_to_prompt=False,
|
|
)
|
|
_expect(legacy_group.get("subject_type") == "group", "Legacy group row lost normalized subject_type")
|
|
_expect(legacy_group.get("women_count") == 2 and legacy_group.get("men_count") == 2, "Legacy group row lost fallback counts")
|
|
_expect(legacy_group.get("person_count") == 4, "Legacy group row lost normalized person count")
|
|
|
|
legacy_layout = row_normalization.normalize_prompt_row(
|
|
{
|
|
"source": "built_in_generator",
|
|
"primary_subject": "layout scene",
|
|
"prompt": "Layout legacy prompt.",
|
|
"caption": "legacy layout caption",
|
|
"negative_prompt": "bad anatomy",
|
|
},
|
|
active_trigger=Trigger,
|
|
prepend_trigger_to_prompt=False,
|
|
)
|
|
_expect(legacy_layout.get("subject_type") == "layout", "Legacy layout row lost normalized subject_type")
|
|
_expect("women_count" not in legacy_layout and "men_count" not in legacy_layout, "Legacy layout row should not invent cast counts")
|
|
|
|
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_camera_config": {"camera_mode": "standard", "camera_detail": "compact"},
|
|
"hardcore_camera_config": {"camera_mode": "pov", "camera_detail": "dense"},
|
|
"softcore_camera_directive": "Camera: soft front view.",
|
|
"hardcore_camera_directive": "Camera: hard side view.",
|
|
"softcore_camera_scene_directive": "Soft camera-aware scene layout.",
|
|
"hardcore_camera_scene_directive": "Hard camera-aware scene layout.",
|
|
"softcore_row": {
|
|
"prompt": f"{Trigger}, {Trigger}, embedded soft.",
|
|
"caption": f"{Trigger}, {Trigger}, embedded soft caption.",
|
|
"negative_prompt": "bad anatomy, bad anatomy",
|
|
"camera_directive": "stale soft camera",
|
|
},
|
|
"hardcore_row": {
|
|
"prompt": f"{Trigger}, {Trigger}, embedded hard.",
|
|
"caption": f"{Trigger}, {Trigger}, embedded hard caption.",
|
|
"negative_prompt": "low quality, bad anatomy, low quality",
|
|
"camera_scene_directive": "stale hard camera scene",
|
|
},
|
|
},
|
|
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(
|
|
pair["softcore_row"].get("camera_config") == pair.get("softcore_camera_config"),
|
|
"Pair normalization left stale soft camera config",
|
|
)
|
|
_expect(
|
|
pair["hardcore_row"].get("camera_config") == pair.get("hardcore_camera_config"),
|
|
"Pair normalization left stale hard camera config",
|
|
)
|
|
_expect(
|
|
pair["softcore_row"].get("camera_directive") == pair.get("softcore_camera_directive"),
|
|
"Pair normalization left stale soft camera directive",
|
|
)
|
|
_expect(
|
|
pair["hardcore_row"].get("camera_scene_directive") == pair.get("hardcore_camera_scene_directive"),
|
|
"Pair normalization left stale hard camera scene directive",
|
|
)
|
|
_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"))
|
|
|
|
reverse_pair = row_normalization.normalize_pair_metadata(
|
|
{
|
|
"softcore_row": {
|
|
"prompt": f"{Trigger}, {Trigger}, embedded-only soft.",
|
|
"caption": f"{Trigger}, {Trigger}, embedded-only soft caption.",
|
|
"negative_prompt": "bad anatomy, bad anatomy",
|
|
"softcore_partner_styling": {"outfits": ["row partner outfit"], "pose": "row partner pose"},
|
|
"camera_config": {"camera_mode": "standard"},
|
|
"camera_directive": "Camera: row soft front view.",
|
|
"camera_scene_directive": "Row soft scene camera layout.",
|
|
},
|
|
"hardcore_row": {
|
|
"prompt": f"{Trigger}, {Trigger}, embedded-only hard.",
|
|
"caption": f"{Trigger}, {Trigger}, embedded-only hard caption.",
|
|
"negative_prompt": "low quality, low quality",
|
|
"hardcore_clothing_state": "row hard clothing state",
|
|
"character_hardcore_clothing": ["Woman A row hard clothing"],
|
|
"default_man_hardcore_clothing": ["Man A row default hard clothing"],
|
|
"hardcore_detail_density": "concise",
|
|
"hardcore_position_config": {"family": "outercourse"},
|
|
"camera_config": {"camera_mode": "pov"},
|
|
"camera_directive": "Camera: row hard side view.",
|
|
"camera_scene_directive": "Row hard scene camera layout.",
|
|
},
|
|
},
|
|
active_trigger=Trigger,
|
|
)
|
|
_expect_trigger_once("row_normalization.reverse.soft_prompt", reverse_pair.get("softcore_prompt"), Trigger)
|
|
_expect_trigger_once("row_normalization.reverse.hard_caption", reverse_pair.get("hardcore_caption"), Trigger)
|
|
_expect(
|
|
reverse_pair.get("softcore_partner_styling") == reverse_pair["softcore_row"].get("softcore_partner_styling"),
|
|
"Pair normalization did not lift soft side metadata from embedded row",
|
|
)
|
|
_expect(
|
|
reverse_pair.get("hardcore_clothing_state") == reverse_pair["hardcore_row"].get("hardcore_clothing_state"),
|
|
"Pair normalization did not lift hard side metadata from embedded row",
|
|
)
|
|
_expect(
|
|
reverse_pair.get("softcore_camera_config") == reverse_pair["softcore_row"].get("camera_config"),
|
|
"Pair normalization did not lift soft camera config from embedded row",
|
|
)
|
|
_expect(
|
|
reverse_pair.get("hardcore_camera_scene_directive") == reverse_pair["hardcore_row"].get("camera_scene_directive"),
|
|
"Pair normalization did not lift hard camera scene from embedded row",
|
|
)
|
|
_expect_no_duplicate_comma_items("row_normalization.reverse.hard_negative", reverse_pair.get("hardcore_negative_prompt"))
|
|
|
|
|
|
def smoke_prompt_hygiene_policy() -> None:
|
|
merged = prompt_hygiene.combine_negative_text(
|
|
"bad anatomy, bad anatomy",
|
|
"low quality",
|
|
"bad anatomy",
|
|
"",
|
|
)
|
|
_expect(merged == "bad anatomy, low quality", "Prompt hygiene negative merge/dedupe changed")
|
|
_expect(
|
|
row_normalization.combined_negative("bad anatomy, bad anatomy", "low quality, bad anatomy") == merged,
|
|
"Row normalization negative merge should delegate to prompt hygiene",
|
|
)
|
|
_expect(
|
|
krea_formatter._combine_negative("bad anatomy, bad anatomy", "low quality", "bad anatomy") == merged,
|
|
"Krea negative merge should delegate to prompt hygiene",
|
|
)
|
|
_expect(
|
|
prompt_hygiene.sanitize_prose_text("Scene: . A sentence. A sentence.") == "A sentence.",
|
|
"Prompt hygiene prose cleanup changed",
|
|
)
|
|
|
|
|
|
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")
|
|
style_config = pb.build_style_config_json(preset="comic_pinup_colored_pencil")
|
|
styled_fields = row_rendering.resolve_row_text_fields(category_text, subcategory_text, item_text, style_config)
|
|
_expect("comic pin-up" in styled_fields.style, "Style Pool did not override row style")
|
|
_expect("comic linework" in styled_fields.positive_suffix, "Style Pool did not override row style suffix")
|
|
negative_style_config = pb.build_style_config_json(
|
|
preset="realistic_photo",
|
|
custom_negative="flat vector, comic paper texture",
|
|
)
|
|
negative_fields = row_rendering.resolve_row_text_fields(category_text, subcategory_text, item_text, negative_style_config)
|
|
_expect("comic paper texture" in negative_fields.negative_prompt, "Style Pool did not merge style negatives")
|
|
|
|
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",
|
|
)
|
|
structured_axis_route = row_role_graph.resolve_role_graph_route(
|
|
rng=random.Random(54),
|
|
subcategory={"slug": "oral", "name": "Oral"},
|
|
context=context,
|
|
item_axis_values={
|
|
"position": {"text": "standing oral position"},
|
|
"oral_act": ["blowjob", "auto"],
|
|
"contact_detail": {"unused": "mouth contact at hip height"},
|
|
},
|
|
pov_character_labels=[],
|
|
is_pose_category=False,
|
|
)
|
|
_expect(
|
|
"kneels in front" in structured_axis_route.source_role_graph,
|
|
"Role graph route should read structured/list axis values through item_axis_policy",
|
|
)
|
|
|
|
|
|
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",
|
|
"scene_entry": {"slug": "test_room", "prompt": "warm test room", "theme": "fixture_theme"},
|
|
"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",
|
|
"composition_entry": {"prompt": "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", "theme": "fixture_theme", "apply_mode": "replace"},
|
|
"composition_config": {"composition": "centered", "theme": "fixture_theme"},
|
|
"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["scene_entry"].get("slug") == "test_room", "Row assembly lost selected scene entry")
|
|
_expect(row["location_theme"] == "fixture_theme", "Row assembly lost location theme")
|
|
_expect(row["scene_theme"] == "fixture_theme", "Row assembly lost selected scene theme")
|
|
_expect(row["composition_entry"].get("prompt") == "centered frame", "Row assembly lost selected composition entry")
|
|
_expect(row["composition_theme"] == "fixture_theme", "Row assembly lost composition theme")
|
|
_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)
|
|
|
|
_expect(
|
|
formatter_input.input_hint_choices(text_hint=formatter_input.INPUT_HINT_PROMPT) == ["auto", "metadata_json", "prompt"],
|
|
"Formatter prompt input-hint choices changed",
|
|
)
|
|
_expect(
|
|
formatter_input.input_hint_choices(text_hint=formatter_input.INPUT_HINT_CAPTION_OR_PROMPT)
|
|
== ["auto", "metadata_json", "caption_or_prompt"],
|
|
"Formatter caption input-hint choices changed",
|
|
)
|
|
_expect(
|
|
formatter_input.normalize_input_hint("bad_hint") == "auto",
|
|
"Formatter input-hint policy should normalize invalid values to auto",
|
|
)
|
|
_expect(
|
|
formatter_input.normalize_input_hint("caption", text_hint=formatter_input.INPUT_HINT_CAPTION_OR_PROMPT)
|
|
== "caption_or_prompt",
|
|
"Formatter input-hint policy lost caption alias",
|
|
)
|
|
_expect(
|
|
formatter_input.normalize_input_hint("caption_or_prompt", text_hint=formatter_input.INPUT_HINT_PROMPT) == "prompt",
|
|
"Formatter input-hint policy should map text hints to the route's text mode",
|
|
)
|
|
|
|
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")
|
|
row, method = formatter_input.row_from_inputs(source_json, "", "bad_hint")
|
|
_expect(method == "source_json" and row == source_row, "Formatter input parser should treat invalid hints as auto")
|
|
row, method = formatter_input.row_from_inputs(source_json, "", "prompt")
|
|
_expect(row is None and method == "text", "Formatter input parser should not parse source JSON in explicit prompt mode")
|
|
pair_metadata = {
|
|
"trigger": Trigger,
|
|
"softcore_row": {
|
|
"prompt": f"{Trigger}, {Trigger}, embedded-only soft.",
|
|
"caption": f"{Trigger}, {Trigger}, embedded-only soft caption.",
|
|
"negative_prompt": "bad anatomy, bad anatomy",
|
|
"softcore_partner_styling": {"outfits": ["row partner outfit"], "pose": "row partner pose"},
|
|
"camera_config": {"camera_mode": "standard"},
|
|
"camera_directive": "Camera: row soft front view.",
|
|
"camera_scene_directive": "Row soft scene camera layout.",
|
|
},
|
|
"hardcore_row": {
|
|
"prompt": f"{Trigger}, {Trigger}, embedded-only hard.",
|
|
"caption": f"{Trigger}, {Trigger}, embedded-only hard caption.",
|
|
"negative_prompt": "low quality, low quality",
|
|
"hardcore_clothing_state": "row hard clothing state",
|
|
"camera_config": {"camera_mode": "pov"},
|
|
"camera_scene_directive": "Row hard scene camera layout.",
|
|
},
|
|
}
|
|
_expect(formatter_input.is_pair_metadata(pair_metadata), "Formatter input policy should detect structural pair metadata")
|
|
parsed_pair, pair_method = formatter_input.row_from_inputs("", _json(pair_metadata), "metadata_json")
|
|
_expect(pair_method == "metadata_json", "Formatter input parser should read pair metadata JSON")
|
|
_expect(formatter_input.is_pair_metadata(parsed_pair), "Formatter input parser should preserve structural pair metadata")
|
|
_expect_trigger_once("formatter_input.pair.soft_prompt", parsed_pair.get("softcore_prompt"), Trigger)
|
|
_expect(
|
|
parsed_pair.get("softcore_partner_styling") == parsed_pair["softcore_row"].get("softcore_partner_styling"),
|
|
"Formatter input parser did not normalize pair soft side metadata",
|
|
)
|
|
_expect(
|
|
parsed_pair.get("hardcore_clothing_state") == parsed_pair["hardcore_row"].get("hardcore_clothing_state"),
|
|
"Formatter input parser did not normalize pair hard side metadata",
|
|
)
|
|
_expect(
|
|
parsed_pair.get("softcore_camera_config") == parsed_pair["softcore_row"].get("camera_config"),
|
|
"Formatter input parser did not normalize pair camera metadata",
|
|
)
|
|
_expect_no_duplicate_comma_items("formatter_input.pair.hard_negative", parsed_pair.get("hardcore_negative_prompt"))
|
|
_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)
|
|
|
|
bad_hint_krea = krea_formatter.format_krea2_prompt(source_json, input_hint="bad_hint")
|
|
bad_hint_sdxl = sdxl_formatter.format_sdxl_prompt(
|
|
source_json,
|
|
input_hint="bad_hint",
|
|
trigger=SdxlTrigger,
|
|
prepend_trigger=True,
|
|
)
|
|
bad_hint_caption, bad_hint_caption_method = caption_naturalizer.naturalize_caption(
|
|
source_json,
|
|
input_hint="bad_hint",
|
|
trigger=Trigger,
|
|
)
|
|
_expect(
|
|
bad_hint_krea.get("method", "").startswith("source_json:krea2("),
|
|
"Krea formatter did not normalize bad input hint to auto",
|
|
)
|
|
_expect(
|
|
bad_hint_sdxl.get("method", "").startswith("source_json:sdxl("),
|
|
"SDXL formatter did not normalize bad input hint to auto",
|
|
)
|
|
_expect(
|
|
bad_hint_caption_method.startswith("source_json:metadata("),
|
|
"Caption formatter did not normalize bad input hint to auto",
|
|
)
|
|
|
|
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_target_policy() -> None:
|
|
_expect(
|
|
formatter_target.target_choices() == ["auto", "single", "softcore", "hardcore"],
|
|
"Formatter target choices changed",
|
|
)
|
|
_expect(formatter_target.normalize_target("single") == "single", "Formatter target lost single")
|
|
_expect(formatter_target.normalize_target("Hard-Core") == "hardcore", "Formatter target alias lost hardcore")
|
|
_expect(formatter_target.normalize_target("soft") == "softcore", "Formatter target alias lost softcore")
|
|
_expect(formatter_target.normalize_target("bad target") == "auto", "Formatter target should normalize invalid values")
|
|
|
|
auto_pair = formatter_target.pair_policy("auto")
|
|
_expect(auto_pair.target == "auto", "Pair target policy lost normalized auto target")
|
|
_expect(auto_pair.pair_target == "auto", "Pair target policy lost auto pair target")
|
|
_expect(auto_pair.selected_side == "softcore", "Pair auto should select softcore side for single-output formatters")
|
|
_expect(auto_pair.include_softcore and auto_pair.include_hardcore, "Pair auto should include both sides for combined captions")
|
|
|
|
single_pair = formatter_target.pair_policy("single")
|
|
_expect(single_pair.target == "single", "Pair target policy lost normalized single target")
|
|
_expect(single_pair.pair_target == "auto", "Pair single should map to auto for pair inclusion")
|
|
_expect(single_pair.selected_side == "softcore", "Pair single should select softcore side by default")
|
|
_expect(single_pair.include_softcore and single_pair.include_hardcore, "Pair single should include both sides when treated as auto")
|
|
|
|
hard_pair = formatter_target.pair_policy("hard")
|
|
_expect(hard_pair.target == "hardcore", "Pair target policy lost hard alias")
|
|
_expect(hard_pair.pair_target == "hardcore", "Pair hard alias should become hardcore pair target")
|
|
_expect(hard_pair.selected_side == "hardcore", "Pair hardcore should select hardcore side")
|
|
_expect(not hard_pair.include_softcore and hard_pair.include_hardcore, "Pair hardcore should include only hard side")
|
|
|
|
|
|
def smoke_formatter_detail_policy() -> None:
|
|
_expect(
|
|
formatter_detail.detail_level_choices() == ["balanced", "concise", "dense"],
|
|
"Formatter detail choices changed",
|
|
)
|
|
_expect(formatter_detail.normalize_detail_level("dense") == "dense", "Formatter detail lost dense")
|
|
_expect(formatter_detail.normalize_detail_level("BAD") == "balanced", "Formatter detail should normalize invalid values")
|
|
_expect(formatter_detail.detail_allows("concise") is False, "Formatter detail concise gate changed")
|
|
_expect(formatter_detail.detail_allows("dense", dense_only=True) is True, "Formatter detail dense-only gate changed")
|
|
_expect(caption_policy.DETAIL_LEVELS is formatter_detail.DETAIL_LEVELS, "Caption detail levels should delegate")
|
|
_expect(
|
|
caption_policy.normalize_detail_level("bad") == formatter_detail.normalize_detail_level("bad"),
|
|
"Caption detail normalization should delegate",
|
|
)
|
|
_expect(
|
|
caption_policy.detail_allows("dense", dense_only=True) == formatter_detail.detail_allows("dense", dense_only=True),
|
|
"Caption detail gate should delegate",
|
|
)
|
|
|
|
|
|
def smoke_krea_format_route_policy() -> None:
|
|
row = _prompt_row(
|
|
name="krea_format_route_single",
|
|
category="woman",
|
|
subcategory="random",
|
|
seed=3601,
|
|
men_count=0,
|
|
camera_config=_orbit_camera(horizontal_angle=45, vertical_angle=0, zoom=5.5),
|
|
)
|
|
single_request = krea_format_route.KreaFormatRequest(
|
|
source_text="",
|
|
metadata_json=_json(row),
|
|
target="single",
|
|
detail_level="dense",
|
|
style_mode="photographic",
|
|
extra_positive="krea route marker",
|
|
extra_negative="krea route negative",
|
|
)
|
|
typed_single = krea_format_route.format_krea2_prompt_result(
|
|
single_request,
|
|
krea_formatter._krea_format_dependencies(),
|
|
)
|
|
public_single = krea_formatter.format_krea2_prompt(
|
|
"",
|
|
metadata_json=single_request.metadata_json,
|
|
target=single_request.target,
|
|
detail_level=single_request.detail_level,
|
|
style_mode=single_request.style_mode,
|
|
extra_positive=single_request.extra_positive,
|
|
extra_negative=single_request.extra_negative,
|
|
)
|
|
_expect(typed_single.output == public_single, "Typed Krea format route should match public single formatter output")
|
|
_expect(typed_single.branch == "metadata(single)", "Typed Krea format route changed single branch")
|
|
_expect(typed_single.target == "single", "Typed Krea format route lost target normalization")
|
|
single_trace = json.loads(_expect_text("krea_format_route_policy.single_trace", typed_single.output.get("route_trace_json"), 20))
|
|
_expect(single_trace.get("formatter") == "krea2", "Typed Krea single trace lost formatter")
|
|
_expect(single_trace.get("branch") == "metadata(single)", "Typed Krea single trace lost branch")
|
|
_expect(single_trace.get("target") == "single", "Typed Krea single trace lost target")
|
|
_expect("krea route marker" in typed_single.output.get("krea_prompt", ""), "Typed Krea route lost extra positive")
|
|
_expect("krea route negative" in typed_single.output.get("negative_prompt", ""), "Typed Krea route lost extra negative")
|
|
|
|
pair = pb.build_insta_of_pair(
|
|
row_number=1,
|
|
start_index=1,
|
|
seed=3602,
|
|
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"),
|
|
)
|
|
pair_request = krea_format_route.KreaFormatRequest(
|
|
source_text="",
|
|
metadata_json=_json(pair),
|
|
target="hardcore",
|
|
detail_level="balanced",
|
|
style_mode="preserve",
|
|
extra_positive="pair route marker",
|
|
extra_negative="pair route negative",
|
|
)
|
|
typed_pair = krea_format_route.format_krea2_prompt_result(
|
|
pair_request,
|
|
krea_formatter._krea_format_dependencies(),
|
|
)
|
|
public_pair = krea_formatter.format_krea2_prompt(
|
|
"",
|
|
metadata_json=pair_request.metadata_json,
|
|
target=pair_request.target,
|
|
detail_level=pair_request.detail_level,
|
|
style_mode=pair_request.style_mode,
|
|
extra_positive=pair_request.extra_positive,
|
|
extra_negative=pair_request.extra_negative,
|
|
)
|
|
_expect(typed_pair.output == public_pair, "Typed Krea format route should match public pair formatter output")
|
|
_expect(typed_pair.branch == "insta_of_pair", "Typed Krea format route changed pair branch")
|
|
pair_trace = json.loads(_expect_text("krea_format_route_policy.pair_trace", typed_pair.output.get("route_trace_json"), 20))
|
|
_expect(pair_trace.get("branch") == "insta_of_pair", "Typed Krea pair trace lost branch")
|
|
_expect(pair_trace.get("selected_side") == "hardcore", "Typed Krea pair trace lost selected side")
|
|
_expect_text("krea_format_route_policy.hard_prompt", typed_pair.output.get("krea_hardcore_prompt"), 40)
|
|
_expect("pair route marker" in typed_pair.output.get("krea_prompt", ""), "Typed Krea pair route lost extra positive")
|
|
|
|
fallback_request = krea_format_route.KreaFormatRequest(
|
|
source_text="Scene: quiet studio. Pose: seated portrait. Avoid: blur",
|
|
input_hint="prompt",
|
|
target="weird",
|
|
detail_level="verbose",
|
|
style_mode="invalid",
|
|
preserve_trigger=False,
|
|
)
|
|
typed_fallback = krea_format_route.format_krea2_prompt_result(
|
|
fallback_request,
|
|
krea_formatter._krea_format_dependencies(),
|
|
)
|
|
public_fallback = krea_formatter.format_krea2_prompt(
|
|
fallback_request.source_text,
|
|
input_hint=fallback_request.input_hint,
|
|
target=fallback_request.target,
|
|
detail_level=fallback_request.detail_level,
|
|
style_mode=fallback_request.style_mode,
|
|
preserve_trigger=fallback_request.preserve_trigger,
|
|
)
|
|
_expect(typed_fallback.output == public_fallback, "Typed Krea format route should match public fallback output")
|
|
_expect(typed_fallback.branch == "fallback", "Typed Krea format route changed fallback branch")
|
|
_expect(typed_fallback.target == "auto", "Typed Krea format route should normalize invalid target")
|
|
fallback_trace = json.loads(_expect_text("krea_format_route_policy.fallback_trace", typed_fallback.output.get("route_trace_json"), 20))
|
|
_expect(fallback_trace.get("branch") == "fallback", "Typed Krea fallback trace lost branch")
|
|
_expect(fallback_trace.get("input_hint") == "prompt", "Typed Krea fallback trace lost input hint")
|
|
_expect(typed_fallback.detail_level == "balanced", "Typed Krea format route should normalize invalid detail level")
|
|
_expect(typed_fallback.style_mode == "preserve", "Typed Krea format route should normalize invalid style mode")
|
|
_expect(krea_format_route.style_mode_choices() == ["preserve", "photographic", "minimal"], "Krea style mode choices changed")
|
|
_expect(krea_format_route.normalize_style_mode("photographic") == "photographic", "Krea style mode lost photographic")
|
|
_expect(krea_format_route.normalize_style_mode("bad") == "preserve", "Krea style mode invalid fallback changed")
|
|
_expect("blur" in typed_fallback.output.get("negative_prompt", ""), "Typed Krea fallback route lost Avoid negative")
|
|
|
|
|
|
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.normalize_style_policy("bad") == "drop_style_tail", "Caption invalid style fallback changed")
|
|
_expect(
|
|
caption_policy.normalize_style_policy("Keep Style Terms") == "keep_style_terms",
|
|
"Caption style policy should normalize spaces/case",
|
|
)
|
|
_expect(caption_policy.style_policy_choices() == ["drop_style_tail", "keep_style_terms"], "Caption style policy choices 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.normalize_caption_profile("training-dense") == "training_dense",
|
|
"Caption profile should normalize hyphen spelling",
|
|
)
|
|
_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.normalize_composition("vertical centered body frame composition") == "centered body frame",
|
|
"Caption composition should drop trailing composition label",
|
|
)
|
|
_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": "anal", "position_family": ""}
|
|
_expect(caption_policy.metadata_action_label(row) == "anal action", "Caption anal action-family label changed")
|
|
row = {"action_family": "manual", "position_family": ""}
|
|
_expect(caption_policy.metadata_action_label(row) == "manual action", "Caption manual action-family label changed")
|
|
row = {"action_family": "threesome", "position_family": ""}
|
|
_expect(caption_policy.metadata_action_label(row) == "three-person action", "Caption threesome action-family label changed")
|
|
row = {"action_family": "group", "position_family": ""}
|
|
_expect(caption_policy.metadata_action_label(row) == "group action", "Caption group 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 smoke_caption_format_route_policy() -> None:
|
|
row = _prompt_row(
|
|
name="caption_format_route_single",
|
|
category="woman",
|
|
subcategory="random",
|
|
seed=3801,
|
|
men_count=0,
|
|
)
|
|
metadata_request = caption_format_route.CaptionFormatRequest(
|
|
source_text="",
|
|
metadata_json=_json(row),
|
|
input_hint="metadata_json",
|
|
target="single",
|
|
trigger=Trigger,
|
|
include_trigger=False,
|
|
detail_level="concise",
|
|
style_policy="keep_style_terms",
|
|
caption_profile="training_dense",
|
|
)
|
|
typed_metadata = caption_format_route.naturalize_caption_result(
|
|
metadata_request,
|
|
caption_naturalizer._caption_format_dependencies(),
|
|
)
|
|
public_metadata = caption_naturalizer.naturalize_caption(
|
|
"",
|
|
metadata_json=metadata_request.metadata_json,
|
|
input_hint=metadata_request.input_hint,
|
|
target=metadata_request.target,
|
|
trigger=metadata_request.trigger,
|
|
include_trigger=metadata_request.include_trigger,
|
|
detail_level=metadata_request.detail_level,
|
|
style_policy=metadata_request.style_policy,
|
|
caption_profile=metadata_request.caption_profile,
|
|
)
|
|
_expect(typed_metadata.as_tuple() == public_metadata, "Typed caption format route should match public metadata output")
|
|
_expect(typed_metadata.branch == "metadata", "Typed caption format route changed metadata branch")
|
|
_expect(typed_metadata.input_hint == "metadata_json", "Typed caption route lost input hint")
|
|
_expect(typed_metadata.target == "single", "Typed caption route lost target normalization")
|
|
_expect(typed_metadata.detail_level == "dense", "Typed caption route lost training_dense detail override")
|
|
_expect(typed_metadata.style_policy == "drop_style_tail", "Typed caption route lost training_dense style override")
|
|
_expect(typed_metadata.include_trigger is True, "Typed caption route lost training_dense trigger override")
|
|
metadata_trace = json.loads(_expect_text("caption_format_route_policy.metadata_trace", typed_metadata.route_trace_json, 20))
|
|
_expect(metadata_trace.get("formatter") == "caption", "Typed caption metadata trace lost formatter")
|
|
_expect(metadata_trace.get("branch") == "metadata", "Typed caption metadata trace lost branch")
|
|
_expect(metadata_trace.get("target") == "single", "Typed caption metadata trace lost target")
|
|
traced_public_metadata = caption_naturalizer.naturalize_caption_with_trace(
|
|
"",
|
|
metadata_json=metadata_request.metadata_json,
|
|
input_hint=metadata_request.input_hint,
|
|
target=metadata_request.target,
|
|
trigger=metadata_request.trigger,
|
|
include_trigger=metadata_request.include_trigger,
|
|
detail_level=metadata_request.detail_level,
|
|
style_policy=metadata_request.style_policy,
|
|
caption_profile=metadata_request.caption_profile,
|
|
)
|
|
_expect(traced_public_metadata == typed_metadata.as_trace_tuple(), "Caption trace wrapper drifted from typed route")
|
|
_expect(typed_metadata.caption.startswith(Trigger), "Typed caption metadata route should prepend training trigger")
|
|
|
|
fallback_request = caption_format_route.CaptionFormatRequest(
|
|
source_text="woman, red dress, studio, coloured pencil comic illustration",
|
|
input_hint="bad_hint",
|
|
target="weird",
|
|
trigger=Trigger,
|
|
include_trigger=True,
|
|
detail_level="dense",
|
|
style_policy="drop_style_tail",
|
|
caption_profile="browsing",
|
|
)
|
|
typed_fallback = caption_format_route.naturalize_caption_result(
|
|
fallback_request,
|
|
caption_naturalizer._caption_format_dependencies(),
|
|
)
|
|
public_fallback = caption_naturalizer.naturalize_caption(
|
|
fallback_request.source_text,
|
|
input_hint=fallback_request.input_hint,
|
|
target=fallback_request.target,
|
|
trigger=fallback_request.trigger,
|
|
include_trigger=fallback_request.include_trigger,
|
|
detail_level=fallback_request.detail_level,
|
|
style_policy=fallback_request.style_policy,
|
|
caption_profile=fallback_request.caption_profile,
|
|
)
|
|
_expect(typed_fallback.as_tuple() == public_fallback, "Typed caption format route should match public fallback output")
|
|
_expect(typed_fallback.branch == "text", "Typed caption format route changed fallback branch")
|
|
_expect(typed_fallback.input_hint == "auto", "Typed caption route should normalize invalid input hint")
|
|
_expect(typed_fallback.target == "auto", "Typed caption route should normalize invalid target")
|
|
_expect(typed_fallback.include_trigger is False, "Typed caption browsing profile should disable trigger")
|
|
fallback_trace = json.loads(_expect_text("caption_format_route_policy.fallback_trace", typed_fallback.route_trace_json, 20))
|
|
_expect(fallback_trace.get("branch") == "text", "Typed caption fallback trace lost branch")
|
|
_expect(fallback_trace.get("input_hint") == "auto", "Typed caption fallback trace lost input hint")
|
|
_expect(typed_fallback.keep_style is True, "Typed caption browsing profile should keep style terms")
|
|
_expect(not typed_fallback.caption.startswith(Trigger), "Typed caption fallback route should not prepend browsing trigger")
|
|
_expect(typed_fallback.method == "text(fallback)", "Typed caption fallback method changed")
|
|
|
|
|
|
def smoke_caption_text_policy() -> None:
|
|
row = {
|
|
"primary_subject": "woman",
|
|
"age_band": "25-year-old adult",
|
|
"body_phrase": "slim figure",
|
|
"caption": f"{Trigger}, woman, 25-year-old adult, slim figure, fair skin, blonde hair, blue eyes, studio",
|
|
"formatter_hints": {"caption": ["caption policy hint"]},
|
|
}
|
|
_expect(
|
|
caption_naturalizer._body_phrase("slim", "balanced figure") == caption_text_policy.body_phrase("slim", "balanced figure"),
|
|
"Caption body phrase wrapper should delegate to caption_text_policy",
|
|
)
|
|
_expect(
|
|
caption_naturalizer._single_caption_front(row) == caption_text_policy.single_caption_front(row),
|
|
"Caption front parser wrapper should delegate to caption_text_policy",
|
|
)
|
|
_expect(
|
|
caption_naturalizer._formatter_hint_parts(row) == caption_text_policy.formatter_hint_parts(row),
|
|
"Caption formatter hint wrapper should delegate to caption_text_policy",
|
|
)
|
|
_expect(
|
|
caption_naturalizer._append_formatter_hints("Base sentence.", row)
|
|
== caption_text_policy.append_formatter_hints("Base sentence.", row),
|
|
"Caption formatter hint append wrapper should delegate to caption_text_policy",
|
|
)
|
|
_expect(
|
|
caption_naturalizer._with_trigger("A caption body", Trigger, True)
|
|
== caption_text_policy.with_trigger("A caption body", Trigger, True),
|
|
"Caption trigger wrapper should delegate to caption_text_policy",
|
|
)
|
|
axis_detail_row = {
|
|
"item_axis_values": {
|
|
"position": "standing oral position",
|
|
"contact_detail": "mouth contact at hip height",
|
|
"duplicate": "standing oral position",
|
|
"ignored": "random",
|
|
}
|
|
}
|
|
_expect(
|
|
caption_text_policy.item_axis_detail_text(axis_detail_row, "generic action")
|
|
== "standing oral position and mouth contact at hip height",
|
|
"Caption axis detail text should flatten selected item axes",
|
|
)
|
|
_expect(
|
|
caption_text_policy.item_axis_detail_text(axis_detail_row, "standing oral position already appears")
|
|
== "mouth contact at hip height",
|
|
"Caption axis detail text should skip details already present in item prose",
|
|
)
|
|
deps = caption_naturalizer._caption_metadata_route_dependencies()
|
|
_expect(deps.clean_text is caption_text_policy.clean_text, "Caption route deps lost clean text policy")
|
|
_expect(deps.field_row_value is caption_text_policy.field_row_value, "Caption route deps lost field row-value policy")
|
|
_expect(deps.expression_disabled is caption_text_policy.expression_disabled, "Caption route deps lost expression policy")
|
|
_expect(deps.single_caption_front is caption_text_policy.single_caption_front, "Caption route deps lost front parser")
|
|
_expect(deps.item_axis_detail_text is caption_text_policy.item_axis_detail_text, "Caption route deps lost item-axis detail policy")
|
|
_expect(deps.metadata_to_prose is caption_naturalizer._metadata_to_prose, "Caption route deps lost metadata recursion callback")
|
|
|
|
|
|
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)",
|
|
)
|
|
configured_character_expression = _fixture_hardcore_row(
|
|
character_expression_text="Woman A has flushed focus; Man A has concentrated stare",
|
|
)
|
|
character_expression_route = caption_metadata_routes.configured_cast_from_row_result(
|
|
caption_naturalizer._caption_metadata_route_request(configured_character_expression, "balanced", False),
|
|
caption_naturalizer._caption_metadata_route_dependencies(),
|
|
)
|
|
_expect(character_expression_route is not None, "Caption configured-cast character expression row did not match")
|
|
assert character_expression_route is not None
|
|
_expect(
|
|
"with Woman A has" not in character_expression_route.prose,
|
|
"Caption configured-cast prose kept old character-expression grammar",
|
|
)
|
|
_expect(
|
|
"Woman A with flushed focus" in character_expression_route.prose,
|
|
"Caption configured-cast prose did not naturalize Woman A expression",
|
|
)
|
|
_expect(
|
|
"Man A with concentrated stare" in character_expression_route.prose,
|
|
"Caption configured-cast prose did not naturalize Man A expression",
|
|
)
|
|
configured_axis_only = _fixture_hardcore_row(
|
|
item="generic configured adult action",
|
|
role_graph="",
|
|
source_role_graph="",
|
|
item_axis_values={
|
|
"position": "standing oral position",
|
|
"contact_detail": "mouth contact at hip height, hands on hips",
|
|
},
|
|
action_family="oral",
|
|
position_family="oral",
|
|
position_key="standing",
|
|
position_keys=["standing"],
|
|
)
|
|
axis_route = caption_metadata_routes.configured_cast_from_row_result(
|
|
caption_naturalizer._caption_metadata_route_request(configured_axis_only, "balanced", False),
|
|
caption_naturalizer._caption_metadata_route_dependencies(),
|
|
)
|
|
_expect(axis_route is not None, "Caption configured-cast axis-only row did not match")
|
|
assert axis_route is not None
|
|
_expect("Selected action details include" in axis_route.prose, "Caption route did not emit selected axis details")
|
|
_expect("standing oral position" in axis_route.prose, "Caption route lost item-axis position detail")
|
|
_expect("mouth contact at hip height" in axis_route.prose, "Caption route lost item-axis contact detail")
|
|
_expect("hands on hips" in axis_route.prose, "Caption route lost item-axis split detail")
|
|
|
|
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)",
|
|
)
|
|
deps = caption_naturalizer._caption_metadata_route_dependencies()
|
|
soft_route = caption_metadata_routes.insta_of_pair_from_row_result(
|
|
caption_naturalizer._caption_metadata_route_request(pair, "balanced", False, target="softcore"),
|
|
deps,
|
|
)
|
|
hard_route = caption_metadata_routes.insta_of_pair_from_row_result(
|
|
caption_naturalizer._caption_metadata_route_request(pair, "balanced", False, target="hardcore"),
|
|
deps,
|
|
)
|
|
_expect(soft_route is not None, "Caption pair softcore target did not match")
|
|
_expect(hard_route is not None, "Caption pair hardcore target did not match")
|
|
assert soft_route is not None
|
|
assert hard_route is not None
|
|
_expect("Softcore side:" not in soft_route.prose, "Caption softcore target should not keep combined pair labels")
|
|
_expect("Hardcore side:" not in soft_route.prose, "Caption softcore target should not include hard label")
|
|
_expect("Softcore side:" not in hard_route.prose, "Caption hardcore target should not include soft label")
|
|
_expect("Hardcore side:" not in hard_route.prose, "Caption hardcore target should not keep combined pair labels")
|
|
_expect(soft_route.prose != hard_route.prose, "Caption pair soft/hard targets should produce distinct prose")
|
|
shared_cast = pair.get("shared_cast_descriptors")
|
|
if isinstance(shared_cast, list):
|
|
shared_cast_text = "; ".join(str(item or "").strip() for item in shared_cast if str(item or "").strip())
|
|
else:
|
|
shared_cast_text = str(shared_cast or "").strip()
|
|
shared_cast_caption = caption_naturalizer._natural_cast_descriptor_text(shared_cast_text)
|
|
if shared_cast_caption:
|
|
_expect(
|
|
hard_route.prose.count(shared_cast_caption) <= 1,
|
|
"Caption hardcore target repeated shared cast descriptors",
|
|
)
|
|
public_hard, public_hard_method = caption_naturalizer.naturalize_caption(
|
|
"",
|
|
metadata_json=_json(pair),
|
|
input_hint="metadata_json",
|
|
target="hardcore",
|
|
trigger=Trigger,
|
|
include_trigger=False,
|
|
)
|
|
_expect(public_hard == hard_route.prose, "Public caption hardcore target drifted from typed route")
|
|
_expect("metadata(insta_of_pair)" in public_hard_method, "Public caption hardcore target lost pair method")
|
|
|
|
|
|
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_formatter_profile("SDXL Photo") == "sdxl_photo", "SDXL profile should normalize spaces/case")
|
|
_expect(sdxl_presets.normalize_style_preset("bad") == sdxl_presets.DEFAULT_STYLE_PRESET, "SDXL invalid style fallback changed")
|
|
_expect(sdxl_presets.normalize_style_preset("flat-vector-pony") == "flat_vector_pony", "SDXL style should normalize hyphens")
|
|
_expect(sdxl_presets.normalize_quality_preset("bad") == sdxl_presets.DEFAULT_QUALITY_PRESET, "SDXL invalid quality fallback changed")
|
|
_expect(sdxl_presets.normalize_quality_preset("Pony High") == "pony_high", "SDXL quality should normalize spaces/case")
|
|
_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_format_route_policy() -> None:
|
|
row = _prompt_row(
|
|
name="sdxl_format_route_single",
|
|
category="woman",
|
|
subcategory="random",
|
|
seed=3701,
|
|
men_count=0,
|
|
camera_config=_orbit_camera(horizontal_angle=45, vertical_angle=0, zoom=5.5),
|
|
)
|
|
single_request = sdxl_format_route.SDXLFormatRequest(
|
|
source_text="",
|
|
metadata_json=_json(row),
|
|
target="single",
|
|
style_preset="flat_vector_pony",
|
|
quality_preset="pony_high",
|
|
trigger=SdxlTrigger,
|
|
prepend_trigger=True,
|
|
nude_weight=9.0,
|
|
extra_positive="sdxl route marker",
|
|
extra_negative="sdxl route negative",
|
|
formatter_profile="sdxl_photo",
|
|
)
|
|
typed_single = sdxl_format_route.format_sdxl_prompt_result(
|
|
single_request,
|
|
sdxl_formatter._sdxl_format_dependencies(),
|
|
)
|
|
public_single = sdxl_formatter.format_sdxl_prompt(
|
|
"",
|
|
metadata_json=single_request.metadata_json,
|
|
target=single_request.target,
|
|
style_preset=single_request.style_preset,
|
|
quality_preset=single_request.quality_preset,
|
|
trigger=single_request.trigger,
|
|
prepend_trigger=single_request.prepend_trigger,
|
|
nude_weight=single_request.nude_weight,
|
|
extra_positive=single_request.extra_positive,
|
|
extra_negative=single_request.extra_negative,
|
|
formatter_profile=single_request.formatter_profile,
|
|
)
|
|
_expect(typed_single.output == public_single, "Typed SDXL format route should match public single formatter output")
|
|
_expect(typed_single.branch == "metadata", "Typed SDXL format route changed single branch")
|
|
_expect(typed_single.target == "single", "Typed SDXL format route lost target normalization")
|
|
_expect(typed_single.nude_weight == 3.0, "Typed SDXL format route should clamp high nude weight")
|
|
_expect(typed_single.style_preset == "photographic", "Typed SDXL format route lost profile style override")
|
|
single_trace = json.loads(_expect_text("sdxl_format_route_policy.single_trace", typed_single.output.get("route_trace_json"), 20))
|
|
_expect(single_trace.get("formatter") == "sdxl", "Typed SDXL single trace lost formatter")
|
|
_expect(single_trace.get("branch") == "metadata", "Typed SDXL single trace lost branch")
|
|
_expect(single_trace.get("target") == "single", "Typed SDXL single trace lost target")
|
|
_expect(single_trace.get("nude_weight") == 3.0, "Typed SDXL single trace lost clamped nude weight")
|
|
_expect("sdxl route marker" in typed_single.output.get("sdxl_prompt", ""), "Typed SDXL route lost extra positive")
|
|
_expect("sdxl route negative" in typed_single.output.get("negative_prompt", ""), "Typed SDXL route lost extra negative")
|
|
|
|
pair = pb.build_insta_of_pair(
|
|
row_number=1,
|
|
start_index=1,
|
|
seed=3702,
|
|
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"),
|
|
)
|
|
pair_request = sdxl_format_route.SDXLFormatRequest(
|
|
source_text="",
|
|
metadata_json=_json(pair),
|
|
target="hardcore",
|
|
trigger=SdxlTrigger,
|
|
prepend_trigger=True,
|
|
extra_positive="pair sdxl route marker",
|
|
extra_negative="pair sdxl route negative",
|
|
)
|
|
typed_pair = sdxl_format_route.format_sdxl_prompt_result(
|
|
pair_request,
|
|
sdxl_formatter._sdxl_format_dependencies(),
|
|
)
|
|
public_pair = sdxl_formatter.format_sdxl_prompt(
|
|
"",
|
|
metadata_json=pair_request.metadata_json,
|
|
target=pair_request.target,
|
|
trigger=pair_request.trigger,
|
|
prepend_trigger=pair_request.prepend_trigger,
|
|
extra_positive=pair_request.extra_positive,
|
|
extra_negative=pair_request.extra_negative,
|
|
)
|
|
_expect(typed_pair.output == public_pair, "Typed SDXL format route should match public pair formatter output")
|
|
_expect(typed_pair.branch == "insta_of_pair", "Typed SDXL format route changed pair branch")
|
|
pair_trace = json.loads(_expect_text("sdxl_format_route_policy.pair_trace", typed_pair.output.get("route_trace_json"), 20))
|
|
_expect(pair_trace.get("branch") == "insta_of_pair", "Typed SDXL pair trace lost branch")
|
|
_expect(pair_trace.get("selected_side") == "hardcore", "Typed SDXL pair trace lost selected side")
|
|
_expect_text("sdxl_format_route_policy.hard_prompt", typed_pair.output.get("sdxl_hardcore_prompt"), 40)
|
|
_expect("pair sdxl route marker" in typed_pair.output.get("sdxl_prompt", ""), "Typed SDXL pair route lost extra positive")
|
|
|
|
fallback_request = sdxl_format_route.SDXLFormatRequest(
|
|
source_text="Characters: woman. Erotic outfit: sheer dress. Camera: side view. Avoid: blur",
|
|
input_hint="prompt",
|
|
target="weird",
|
|
trigger=SdxlTrigger,
|
|
prepend_trigger=False,
|
|
nude_weight=0.01,
|
|
style_preset="none",
|
|
quality_preset="none",
|
|
)
|
|
typed_fallback = sdxl_format_route.format_sdxl_prompt_result(
|
|
fallback_request,
|
|
sdxl_formatter._sdxl_format_dependencies(),
|
|
)
|
|
public_fallback = sdxl_formatter.format_sdxl_prompt(
|
|
fallback_request.source_text,
|
|
input_hint=fallback_request.input_hint,
|
|
target=fallback_request.target,
|
|
trigger=fallback_request.trigger,
|
|
prepend_trigger=fallback_request.prepend_trigger,
|
|
nude_weight=fallback_request.nude_weight,
|
|
style_preset=fallback_request.style_preset,
|
|
quality_preset=fallback_request.quality_preset,
|
|
)
|
|
_expect(typed_fallback.output == public_fallback, "Typed SDXL format route should match public fallback output")
|
|
_expect(typed_fallback.branch == "fallback", "Typed SDXL format route changed fallback branch")
|
|
_expect(typed_fallback.target == "auto", "Typed SDXL format route should normalize invalid target")
|
|
_expect(typed_fallback.nude_weight == 0.1, "Typed SDXL format route should clamp low nude weight")
|
|
fallback_trace = json.loads(_expect_text("sdxl_format_route_policy.fallback_trace", typed_fallback.output.get("route_trace_json"), 20))
|
|
_expect(fallback_trace.get("branch") == "fallback", "Typed SDXL fallback trace lost branch")
|
|
_expect(fallback_trace.get("input_hint") == "prompt", "Typed SDXL fallback trace lost input hint")
|
|
_expect("Characters:" not in typed_fallback.output.get("sdxl_prompt", ""), "Typed SDXL fallback leaked Characters label")
|
|
_expect("blur" in typed_fallback.output.get("negative_prompt", ""), "Typed SDXL fallback route lost Avoid negative")
|
|
|
|
|
|
def smoke_sdxl_tag_policy() -> None:
|
|
row = _fixture_hardcore_row(
|
|
action_family="oral",
|
|
position_family="oral",
|
|
position_key="kneeling_oral",
|
|
position_keys=["kneeling_oral"],
|
|
formatter_hints={"sdxl": ["policy route tag"]},
|
|
)
|
|
_expect(
|
|
sdxl_formatter._split_tag_text("Woman A with camera, Man A")
|
|
== sdxl_tag_policy.split_tag_text("Woman A with camera, Man A"),
|
|
"SDXL formatter split helper should delegate to sdxl_tag_policy",
|
|
)
|
|
_expect(
|
|
sdxl_formatter._metadata_family_tags(row) == sdxl_tag_policy.metadata_family_tags(row),
|
|
"SDXL formatter metadata-family helper should delegate to sdxl_tag_policy",
|
|
)
|
|
axis_row = {
|
|
"item_axis_values": {
|
|
"position": "edge-supported kneeling pose",
|
|
"contact_detail": "hands braced on thighs, close body alignment",
|
|
"ignored": "random",
|
|
}
|
|
}
|
|
axis_tags = sdxl_tag_policy.axis_value_tags(axis_row)
|
|
_expect("edge-supported kneeling pose" in axis_tags, "SDXL axis tags lost selected position axis")
|
|
_expect("hands braced on thighs" in axis_tags, "SDXL axis tags lost selected detail axis")
|
|
_expect("close body alignment" in axis_tags, "SDXL axis tags lost split detail axis")
|
|
_expect("random" not in axis_tags, "SDXL axis tags should ignore random placeholders")
|
|
hyphenated_tags = sdxl_tag_policy.split_tag_text("front-and-back penetration with hands on hips")
|
|
_expect("front-and-back penetration" in hyphenated_tags, "SDXL tag splitter broke hyphenated and compound")
|
|
_expect("front-" not in hyphenated_tags and "-back penetration" not in hyphenated_tags, "SDXL tag splitter emitted broken hyphen fragments")
|
|
_expect("hands on hips" in hyphenated_tags, "SDXL tag splitter stopped splitting non-hyphenated with connector")
|
|
subject_pair_tags = sdxl_tag_policy.split_tag_text("Woman A, Man A are mid-transition with hands on hips")
|
|
_expect("woman and man are mid-transition" in subject_pair_tags, "SDXL tag splitter broke paired character clause")
|
|
_expect("woman" not in subject_pair_tags and "man are mid-transition" not in subject_pair_tags, "SDXL tag splitter emitted broken paired character fragments")
|
|
sentence_boundary_tags = sdxl_tag_policy.split_tag_text("keep hands on hips, breasts, thighs. Man watches close")
|
|
sentence_boundary_tags_lower = [tag.lower() for tag in sentence_boundary_tags]
|
|
_expect("hands on hips" in sentence_boundary_tags_lower, "SDXL tag splitter did not clean leading keep imperative")
|
|
_expect("keep hands on hips" not in sentence_boundary_tags_lower, "SDXL tag splitter kept leading keep imperative")
|
|
_expect("thighs" in sentence_boundary_tags, "SDXL tag splitter lost pre-period tag")
|
|
_expect("man watches close" in sentence_boundary_tags_lower, "SDXL tag splitter did not split sentence-boundary tag")
|
|
_expect(
|
|
"thighs. man watches close" not in sentence_boundary_tags_lower,
|
|
"SDXL tag splitter kept sentence-boundary tag fragment",
|
|
)
|
|
_expect(
|
|
sdxl_formatter._camera_tags(row) == sdxl_tag_policy.camera_tags(row),
|
|
"SDXL formatter camera helper should delegate to sdxl_tag_policy",
|
|
)
|
|
_expect(
|
|
sdxl_formatter._combine_tags("a, b", "a", "c")
|
|
== sdxl_tag_policy.combine_tags("a, b", "a", "c")
|
|
== "a, b, c",
|
|
"SDXL tag combining changed",
|
|
)
|
|
deps = sdxl_formatter._sdxl_tag_route_dependencies()
|
|
_expect(deps.tag_key is sdxl_tag_policy.tag_key, "SDXL route deps lost policy tag_key")
|
|
_expect(deps.normal_character_tags is sdxl_tag_policy.normal_character_tags, "SDXL route deps lost character tag policy")
|
|
_expect(deps.metadata_family_tags is sdxl_tag_policy.metadata_family_tags, "SDXL route deps lost metadata family policy")
|
|
_expect(deps.axis_value_tags is sdxl_tag_policy.axis_value_tags, "SDXL route deps lost axis-value tag policy")
|
|
_expect(deps.camera_tags is sdxl_tag_policy.camera_tags, "SDXL route deps lost camera tag policy")
|
|
_expect(deps.explicit_tags is sdxl_tag_policy.explicit_tags, "SDXL route deps lost explicit tag policy")
|
|
_expect(
|
|
deps.filter_incompatible_route_tags is sdxl_tag_policy.filter_incompatible_route_tags,
|
|
"SDXL route deps lost route-family tag filter",
|
|
)
|
|
_expect(deps.softcore_pair_tags is sdxl_tag_policy.softcore_pair_tags, "SDXL route deps lost softcore pair tag policy")
|
|
mouth_nearby_tags = sdxl_tag_policy.explicit_tags(
|
|
"missionary penetration with mouth close to the ear",
|
|
1.29,
|
|
)
|
|
_expect("penetration" in mouth_nearby_tags, "SDXL explicit tags lost penetration signal")
|
|
_expect("oral sex" not in mouth_nearby_tags, "SDXL explicit tags should not treat nearby mouth wording as oral")
|
|
outercourse_filtered_tags = sdxl_tag_policy.filter_incompatible_route_tags(
|
|
["outercourse", "penis licking", "oral sex", "penetration"],
|
|
_fixture_hardcore_row(
|
|
action_family="outercourse",
|
|
position_family="outercourse",
|
|
position_key="penis_licking",
|
|
position_keys=["penis_licking"],
|
|
),
|
|
)
|
|
_expect("outercourse" in outercourse_filtered_tags, "SDXL route filter removed matching outercourse tag")
|
|
_expect("penis licking" in outercourse_filtered_tags, "SDXL route filter removed specific outercourse key")
|
|
_expect("oral sex" not in outercourse_filtered_tags, "SDXL route filter kept incompatible oral tag")
|
|
_expect("penetration" not in outercourse_filtered_tags, "SDXL route filter kept incompatible penetration tag")
|
|
|
|
stale_character_row = {
|
|
"prompt": "Characters: 99-year-old adult man, stale body, stale skin, stale hair, stale eyes.",
|
|
"age_band": "27-year-old adult",
|
|
"subject_phrase": "woman",
|
|
"body_phrase": "athletic figure",
|
|
"skin": "warm olive skin",
|
|
"hair": "short black hair",
|
|
"eyes": "green eyes",
|
|
}
|
|
stale_character_tags = sdxl_tag_policy.normal_character_tags(stale_character_row)
|
|
_expect("27-year-old adult" in stale_character_tags, "SDXL character tags lost structured age")
|
|
_expect("warm olive skin" in stale_character_tags, "SDXL character tags lost structured skin")
|
|
_expect(
|
|
all("stale" not in tag for tag in stale_character_tags),
|
|
"SDXL character tags should not parse stale raw prompt character labels",
|
|
)
|
|
descriptor_tags = sdxl_tag_policy.normal_character_tags(
|
|
{
|
|
"prompt": "Characters: stale prompt descriptor.",
|
|
"cast_descriptor_text": "Woman A: 30-year-old adult woman, toned figure, fair skin, red hair, gray eyes",
|
|
}
|
|
)
|
|
_expect("30-year-old adult woman" in descriptor_tags, "SDXL character tags lost cast descriptor metadata")
|
|
_expect("toned build" in descriptor_tags, "SDXL character tags did not normalize descriptor figure tag")
|
|
_expect(all("stale" not in tag for tag in descriptor_tags), "SDXL cast descriptor metadata should beat stale prompt labels")
|
|
|
|
|
|
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")
|
|
axis_only_row = _fixture_hardcore_row(
|
|
item="generic configured adult action",
|
|
pose="configured explicit pose",
|
|
role_graph="",
|
|
source_role_graph="",
|
|
item_axis_values={
|
|
"position": "standing oral position",
|
|
"contact_detail": "mouth contact at hip height, hands on hips",
|
|
},
|
|
action_family="oral",
|
|
position_family="oral",
|
|
position_key="standing",
|
|
position_keys=["standing"],
|
|
)
|
|
axis_only_tags = sdxl_tag_routes.row_core_tags_result(
|
|
sdxl_tag_routes.SDXLRowTagRequest(axis_only_row, 1.29),
|
|
deps,
|
|
).as_text()
|
|
_expect("standing oral position" in axis_only_tags, "SDXL row route lost item axis position tag")
|
|
_expect("mouth contact at hip height" in axis_only_tags, "SDXL row route lost item axis contact tag")
|
|
_expect("hands on hips" in axis_only_tags, "SDXL row route lost split item axis detail tag")
|
|
stale_character_route_row = _fixture_hardcore_row(
|
|
prompt="Characters: stale prompt subject, stale body, stale skin, stale hair, stale eyes.",
|
|
cast_descriptor_text="",
|
|
cast_descriptors=[],
|
|
subject_type="woman",
|
|
subject_phrase="woman",
|
|
primary_subject="woman",
|
|
age_band="27-year-old adult",
|
|
body_phrase="athletic figure",
|
|
skin="warm olive skin",
|
|
hair="short black hair",
|
|
eyes="green eyes",
|
|
women_count=1,
|
|
men_count=0,
|
|
person_count=1,
|
|
)
|
|
stale_character_route_tags = sdxl_tag_routes.row_core_tags_result(
|
|
sdxl_tag_routes.SDXLRowTagRequest(stale_character_route_row, 1.29),
|
|
deps,
|
|
).as_text()
|
|
_expect("27-year-old adult" in stale_character_route_tags, "SDXL route lost structured character age")
|
|
_expect("warm olive skin" in stale_character_route_tags, "SDXL route lost structured character skin")
|
|
_expect("stale" not in stale_character_route_tags, "SDXL route should not parse stale prompt character labels")
|
|
composition_label_row = _fixture_hardcore_row(
|
|
composition="coworking lounge frame with tall-window depth behind them composition",
|
|
)
|
|
composition_label_tags = sdxl_tag_routes.row_core_tags_result(
|
|
sdxl_tag_routes.SDXLRowTagRequest(composition_label_row, 1.29),
|
|
deps,
|
|
).as_text()
|
|
_expect("tall-window depth behind them composition" not in composition_label_tags, "SDXL route kept raw composition label tag")
|
|
_expect("tall-window depth behind them" in composition_label_tags, "SDXL route lost composition detail while removing label")
|
|
expression_label_row = _fixture_hardcore_row(
|
|
character_expression_text="Woman A has focused gaze; Man A has steady expression",
|
|
expression="stale expression",
|
|
)
|
|
expression_label_tags = sdxl_tag_routes.row_core_tags_result(
|
|
sdxl_tag_routes.SDXLRowTagRequest(expression_label_row, 1.29),
|
|
deps,
|
|
).as_text()
|
|
_expect("woman has" not in expression_label_tags.lower(), "SDXL route kept woman-has expression label")
|
|
_expect("man has" not in expression_label_tags.lower(), "SDXL route kept man-has expression label")
|
|
_expect("focused gaze" in expression_label_tags, "SDXL route lost cleaned woman expression")
|
|
_expect("steady expression" in expression_label_tags, "SDXL route lost cleaned man expression")
|
|
stale_prompt_row = _fixture_hardcore_row(
|
|
prompt="stale raw prompt mentions fully nude naked pussy penis oral anal semen penetration",
|
|
item="standing portrait setup",
|
|
pose="standing pose",
|
|
role_graph="",
|
|
source_role_graph="",
|
|
expression="neutral expression",
|
|
action_family="",
|
|
position_family="",
|
|
position_key="",
|
|
position_keys=[],
|
|
)
|
|
stale_tags = sdxl_tag_routes.row_core_tags_result(
|
|
sdxl_tag_routes.SDXLRowTagRequest(stale_prompt_row, 1.29),
|
|
deps,
|
|
).as_text()
|
|
for forbidden in ("(naked:", "pussy", "penis", "oral sex", "anal sex", "semen", "penetration"):
|
|
_expect(forbidden not in stale_tags, f"SDXL row tags should not infer explicit tag from stale prompt: {forbidden}")
|
|
|
|
metadata_explicit_row = _fixture_hardcore_row(
|
|
prompt="stale raw prompt without explicit tag anchors",
|
|
item="kneeling pose with visible penis and pussy contact",
|
|
pose="penetration pose",
|
|
role_graph="penis thrusts into pussy",
|
|
source_role_graph="penis thrusts into pussy",
|
|
hardcore_clothing_state="fully nude body, bare skin unobstructed",
|
|
action_family="penetration",
|
|
position_family="penetrative",
|
|
position_key="kneeling",
|
|
position_keys=["kneeling"],
|
|
)
|
|
metadata_tags = sdxl_tag_routes.row_core_tags_result(
|
|
sdxl_tag_routes.SDXLRowTagRequest(metadata_explicit_row, 1.29),
|
|
deps,
|
|
).as_text()
|
|
for required in ("(naked:1.29)", "pussy", "penis", "penetration"):
|
|
_expect(required in metadata_tags, f"SDXL row tags lost structured explicit metadata tag: {required}")
|
|
outercourse_noise_row = _fixture_hardcore_row(
|
|
item="penis-licking outercourse position with tongue along the penis shaft",
|
|
pose="configured outercourse pose",
|
|
role_graph="Woman A bends low while her tongue runs along Man A's penis shaft.",
|
|
source_role_graph="Woman A bends low while her tongue runs along Man A's penis shaft.",
|
|
item_axis_values={
|
|
"position": "penis-licking outercourse position",
|
|
"outer_act": "tongue along the penis shaft",
|
|
},
|
|
action_family="outercourse",
|
|
position_family="outercourse",
|
|
position_key="penis_licking",
|
|
position_keys=["penis_licking"],
|
|
)
|
|
outercourse_noise_tags = sdxl_tag_routes.row_core_tags_result(
|
|
sdxl_tag_routes.SDXLRowTagRequest(outercourse_noise_row, 1.29),
|
|
deps,
|
|
).as_text()
|
|
_expect("outercourse" in outercourse_noise_tags, "SDXL outercourse row lost matching family tag")
|
|
_expect("penis licking" in outercourse_noise_tags, "SDXL outercourse row lost specific position key")
|
|
_expect("oral sex" not in outercourse_noise_tags, "SDXL outercourse row kept incompatible oral tag")
|
|
_expect("penetration" not in outercourse_noise_tags, "SDXL outercourse row kept incompatible penetration tag")
|
|
stale_hardcore_pose_row = _fixture_hardcore_row(
|
|
item="oral contact with mouth on the visible genitals in side-lying oral position",
|
|
pose="kneeling and balancing a cucumber upright on an open palm held overhead",
|
|
role_graph="Woman A lies on her side while Man A's mouth is pressed to her pussy.",
|
|
source_role_graph="Woman A lies on her side while Man A's mouth is pressed to her pussy.",
|
|
item_axis_values={
|
|
"position": "side-lying oral position",
|
|
"oral_act": "oral contact with mouth on the visible genitals",
|
|
},
|
|
action_family="oral",
|
|
position_family="oral",
|
|
position_key="side_lying",
|
|
position_keys=["side_lying"],
|
|
)
|
|
stale_hardcore_pose_tags = sdxl_tag_routes.row_core_tags_result(
|
|
sdxl_tag_routes.SDXLRowTagRequest(stale_hardcore_pose_row, 1.29),
|
|
deps,
|
|
).as_text()
|
|
_expect("oral sex" in stale_hardcore_pose_tags, "SDXL hardcore route lost oral family tag")
|
|
_expect("side lying" in stale_hardcore_pose_tags, "SDXL hardcore route lost structured position key")
|
|
_expect("cucumber" not in stale_hardcore_pose_tags, "SDXL hardcore route leaked generic stale pose text")
|
|
|
|
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",
|
|
)
|
|
pair_stale_hard_row = _fixture_hardcore_row(
|
|
prompt="",
|
|
item="standing portrait setup",
|
|
pose="standing pose",
|
|
role_graph="",
|
|
source_role_graph="",
|
|
expression="neutral expression",
|
|
composition="standing portrait frame",
|
|
action_family="",
|
|
position_family="",
|
|
position_key="",
|
|
position_keys=[],
|
|
)
|
|
pair_stale_root = {
|
|
"hardcore_prompt": "stale pair prompt says fully nude naked pussy penis oral anal semen penetration",
|
|
"hardcore_women_count": 1,
|
|
"hardcore_men_count": 1,
|
|
}
|
|
pair_stale_tags = sdxl_tag_routes.hard_tags_result(
|
|
sdxl_tag_routes.SDXLPairTagRequest(pair_stale_hard_row, pair_stale_root, 1.29),
|
|
deps,
|
|
).as_text()
|
|
for forbidden in ("(naked:", "pussy", "penis", "oral sex", "anal sex", "semen", "penetration"):
|
|
_expect(forbidden not in pair_stale_tags, f"SDXL pair tags should not infer explicit tag from stale prompt: {forbidden}")
|
|
|
|
pair_metadata_root = {
|
|
"hardcore_prompt": "stale pair prompt without explicit tag anchors",
|
|
"hardcore_women_count": 1,
|
|
"hardcore_men_count": 1,
|
|
"hardcore_clothing_state": "fully nude body, bare skin unobstructed",
|
|
}
|
|
pair_metadata_hard_row = _fixture_hardcore_row(
|
|
prompt="",
|
|
item="kneeling pose with visible penis and pussy contact",
|
|
role_graph="penis thrusts into pussy",
|
|
source_role_graph="penis thrusts into pussy",
|
|
action_family="penetration",
|
|
position_family="penetrative",
|
|
position_key="kneeling",
|
|
position_keys=["kneeling"],
|
|
)
|
|
pair_metadata_tags = sdxl_tag_routes.hard_tags_result(
|
|
sdxl_tag_routes.SDXLPairTagRequest(pair_metadata_hard_row, pair_metadata_root, 1.29),
|
|
deps,
|
|
).as_text()
|
|
for required in ("(naked:1.29)", "pussy", "penis", "penetration"):
|
|
_expect(required in pair_metadata_tags, f"SDXL pair tags lost structured explicit metadata tag: {required}")
|
|
pair_axis_tags = sdxl_tag_routes.hard_tags_result(
|
|
sdxl_tag_routes.SDXLPairTagRequest(axis_only_row, pair_metadata_root, 1.29),
|
|
deps,
|
|
).as_text()
|
|
_expect("standing oral position" in pair_axis_tags, "SDXL pair hard route lost item axis position tag")
|
|
_expect("mouth contact at hip height" in pair_axis_tags, "SDXL pair hard route lost item axis contact tag")
|
|
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")
|
|
_expect(
|
|
category_template_metadata.template_action_family({"action_family": "toy double"}) == "toy_double",
|
|
"Template action-family normalizer should accept spaced aliases",
|
|
)
|
|
_expect(
|
|
category_template_metadata.template_action_family({"action_family": "manual stimulation"}) == "manual",
|
|
"Template action-family normalizer should accept subcategory-style aliases",
|
|
)
|
|
_expect(
|
|
category_template_metadata.template_action_family({"action_family": "anal sex"}) == "anal",
|
|
"Template action-family normalizer should accept anal aliases",
|
|
)
|
|
_expect(
|
|
category_template_metadata.template_action_family({"action_family": "three way"}) == "threesome",
|
|
"Template action-family normalizer should accept threesome aliases",
|
|
)
|
|
_expect(
|
|
category_template_metadata.template_action_family({"action_family": "group sex"}) == "group",
|
|
"Template action-family normalizer should accept group aliases",
|
|
)
|
|
_expect(
|
|
category_template_metadata.template_position_family({"position_family": "penetration"}) == "penetrative",
|
|
"Template position-family normalizer should accept action-style aliases",
|
|
)
|
|
_expect(
|
|
category_template_metadata.template_position_family({"position_family": "outer-course"}) == "outercourse",
|
|
"Template position-family normalizer should accept hyphenated aliases",
|
|
)
|
|
|
|
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")
|
|
strict_threesome = json.loads(
|
|
pb.build_hardcore_action_filter_json(
|
|
hardcore_position_config=pb.build_hardcore_position_pool_json(family="threesome"),
|
|
focus="threesome_only",
|
|
allow_toys=False,
|
|
allow_double=False,
|
|
allow_penetration=False,
|
|
allow_foreplay=False,
|
|
allow_interaction=False,
|
|
allow_manual=False,
|
|
allow_oral=False,
|
|
allow_outercourse=False,
|
|
allow_anal=False,
|
|
allow_climax=False,
|
|
)
|
|
)
|
|
_expect(
|
|
hardcore_position_config.hardcore_allowed_subcategory_slugs(strict_threesome) == {"threesomes"},
|
|
"Specific hardcore family filter should not widen to the full pool when boolean filters empty it",
|
|
)
|
|
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")
|
|
action_axis_metadata = hardcore_position_config.filter_hardcore_axis(
|
|
"outer_act",
|
|
[
|
|
{"text": "generic contact route", "action_family": "outercourse", "position_family": "outercourse"},
|
|
{"text": "generic contact route", "action_family": "oral", "position_family": "oral"},
|
|
{"text": "generic contact route", "action_family": "penetration", "position_family": "penetrative"},
|
|
],
|
|
action_only,
|
|
)
|
|
_expect(
|
|
action_axis_metadata == [{"text": "generic contact route", "action_family": "outercourse", "position_family": "outercourse"}],
|
|
"Hardcore action filter policy did not honor structured action metadata",
|
|
)
|
|
position_filtered = hardcore_position_config.apply_hardcore_position_config_to_subcategory(
|
|
{
|
|
"slug": "oral_sex",
|
|
"item_templates": [
|
|
{"template": "oral contact in {position}"},
|
|
{"template": "metadata-specific oral contact", "position_key": "standing", "action_family": "oral"},
|
|
{"template": "oral sex without a position axis"},
|
|
{"template": "unsupported static template"},
|
|
],
|
|
"item_axes": {
|
|
"position": [
|
|
"standing oral position",
|
|
"kneeling oral position",
|
|
{"text": "generic standing pose", "position_key": "standing"},
|
|
{"text": "generic kneeling pose", "position_key": "kneeling"},
|
|
],
|
|
"oral_act": ["blowjob", "cunnilingus"],
|
|
},
|
|
},
|
|
base,
|
|
)
|
|
_expect(
|
|
position_filtered["item_templates"]
|
|
== [
|
|
{"template": "oral contact in {position}"},
|
|
{"template": "metadata-specific oral contact", "position_key": "standing", "action_family": "oral"},
|
|
],
|
|
"Hardcore position policy did not filter templates by selected position requirements or metadata",
|
|
)
|
|
_expect(
|
|
position_filtered["item_axes"]["position"] == ["standing oral position", {"text": "generic standing pose", "position_key": "standing"}],
|
|
"Hardcore position policy did not filter position axes by selected keys or metadata",
|
|
)
|
|
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")
|
|
source_action_family = hardcore_action_metadata.source_hardcore_action_family(
|
|
"outer-course",
|
|
"",
|
|
"generic contact",
|
|
)
|
|
_expect(source_action_family == "outercourse", "Source action-family fallback should accept hyphenated source aliases")
|
|
_expect(
|
|
hardcore_action_metadata.source_hardcore_action_family("threesome", "", "three-body contact") == "threesome",
|
|
"Source action-family fallback should accept threesome source family",
|
|
)
|
|
_expect(
|
|
hardcore_action_metadata.source_hardcore_action_family("group", "", "group sex contact") == "group",
|
|
"Source action-family fallback should accept group source family",
|
|
)
|
|
default_action_route = row_route_metadata.resolve_action_position_route(
|
|
is_pose_category=True,
|
|
subcategory={"slug": "anal_double_penetration"},
|
|
hardcore_position_config=None,
|
|
item_template_metadata={"action_family": "default", "position_family": "anal"},
|
|
item_text="toy-assisted double penetration with front-and-back contact",
|
|
source_role_graph="one partner between two bodies",
|
|
source_composition="",
|
|
pose="",
|
|
item_axis_values={"double_act": "toy-assisted double penetration"},
|
|
)
|
|
_expect(default_action_route.get("position_family") == "anal", "Default-action metadata should preserve position family")
|
|
_expect(default_action_route.get("action_family") == "toy_double", "Default-action metadata should still allow action inference")
|
|
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")
|
|
inherited_text, _inherited_name, inherited_axis_values, inherited_metadata = pb._compose_item(
|
|
random.Random(42),
|
|
{},
|
|
{
|
|
"name": "Inherited metadata route",
|
|
"item_template_metadata": {
|
|
"action_family": "manual",
|
|
"position_family": "manual",
|
|
"position_keys": ["kneeling"],
|
|
"formatter_hint": {"caption": "inherited caption cue"},
|
|
},
|
|
"item_templates": ["{act} in {position}"],
|
|
"item_axes": {
|
|
"act": ["hand stimulation"],
|
|
"position": ["kneeling manual position"],
|
|
},
|
|
},
|
|
"Inherited metadata route",
|
|
women_count=1,
|
|
men_count=1,
|
|
)
|
|
_expect(inherited_text == "hand stimulation in kneeling manual position", "Inherited template metadata changed item text")
|
|
_expect(inherited_axis_values == {"act": "hand stimulation", "position": "kneeling manual position"}, "Inherited template metadata lost axis values")
|
|
_expect(inherited_metadata.get("action_family") == "manual", "String template did not inherit action family")
|
|
_expect(inherited_metadata.get("position_family") == "manual", "String template did not inherit position family")
|
|
_expect(pb._template_position_keys(inherited_metadata) == ["kneeling"], "String template did not inherit position keys")
|
|
_expect(
|
|
route_metadata.row_formatter_hints({"item_template_metadata": inherited_metadata}, "caption") == ["inherited caption cue"],
|
|
"String template did not inherit formatter hints",
|
|
)
|
|
override_text, _override_name, _override_axis_values, override_metadata = pb._compose_item(
|
|
random.Random(42),
|
|
{},
|
|
{
|
|
"name": "Override metadata route",
|
|
"item_template_metadata": {
|
|
"action_family": "manual",
|
|
"position_family": "manual",
|
|
"formatter_hint": {"all": "inherited shared cue"},
|
|
},
|
|
"item_templates": [
|
|
{
|
|
"template": "{act} in {position}",
|
|
"action_family": "oral",
|
|
"formatter_hint": {"krea2": "override krea cue"},
|
|
}
|
|
],
|
|
"item_axes": {
|
|
"act": ["mouth contact"],
|
|
"position": ["kneeling oral position"],
|
|
},
|
|
},
|
|
"Override metadata route",
|
|
women_count=1,
|
|
men_count=1,
|
|
)
|
|
_expect(override_text == "mouth contact in kneeling oral position", "Override template metadata changed item text")
|
|
_expect(override_metadata.get("action_family") == "oral", "Template object did not override inherited action family")
|
|
_expect(override_metadata.get("position_family") == "manual", "Template object should keep inherited position family when absent")
|
|
_expect(
|
|
route_metadata.row_formatter_hints({"item_template_metadata": override_metadata}, "krea")
|
|
== ["inherited shared cue", "override krea cue"],
|
|
"Template metadata did not merge inherited and template formatter hints",
|
|
)
|
|
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",
|
|
)
|
|
nested_route_row = {
|
|
"item_template_metadata": {
|
|
"action_family": "oral",
|
|
"position_family": "oral",
|
|
"position_keys": ["kneeling", "open_thighs"],
|
|
"formatter_hint": {"krea2": "nested krea cue", "sdxl": "nested sdxl cue", "training_caption": "nested caption cue"},
|
|
}
|
|
}
|
|
_expect(
|
|
route_metadata.row_action_family(nested_route_row) == "oral",
|
|
"Route metadata should fall back to nested template action family",
|
|
)
|
|
_expect(
|
|
route_metadata.row_position_family(nested_route_row) == "oral",
|
|
"Route metadata should fall back to nested template position family",
|
|
)
|
|
_expect(
|
|
route_metadata.row_position_keys(nested_route_row) == ["kneeling", "open_thighs"],
|
|
"Route metadata should fall back to nested template position keys",
|
|
)
|
|
_expect(
|
|
route_metadata.row_formatter_hints(nested_route_row, "krea") == ["nested krea cue"],
|
|
"Route metadata should fall back to nested Krea formatter hints",
|
|
)
|
|
merged_route_row = {
|
|
"position_key": "standing",
|
|
"formatter_hints": {"all": ["shared cue"]},
|
|
"item_template_metadata": {
|
|
"position_keys": ["kneeling"],
|
|
"formatter_hint": {"caption": "nested caption cue"},
|
|
},
|
|
}
|
|
_expect(
|
|
route_metadata.row_position_keys(merged_route_row) == ["standing", "kneeling"],
|
|
"Route metadata should merge top-level and nested position keys",
|
|
)
|
|
_expect(
|
|
route_metadata.row_formatter_hints(merged_route_row, "caption") == ["shared cue", "nested caption cue"],
|
|
"Route metadata should merge top-level and nested formatter hints",
|
|
)
|
|
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")
|
|
raw_route_hints = category_template_metadata.formatter_hints_for_route(
|
|
{"all": ["raw shared cue"], "caption": ["raw caption cue"]},
|
|
"caption",
|
|
)
|
|
_expect(raw_route_hints == ["raw shared cue", "raw caption cue"], "Raw formatter route-map resolver changed")
|
|
_expect(
|
|
category_template_metadata.formatter_hints_for_route({"caption": "row caption should not be a hint", "prompt": "row prompt"}, "caption")
|
|
== [],
|
|
"Formatter hint route resolver treated an arbitrary metadata row as route hints",
|
|
)
|
|
_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"] == "manual", "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")
|
|
|
|
slash_categories = [
|
|
{"name": "Dev", "slug": "dev", "subcategories": [{"name": "Wrong Route", "slug": "wrong_route", "items": ["wrong item"]}]},
|
|
{
|
|
"name": "Dev / Test Wear",
|
|
"slug": "dev_test_wear",
|
|
"subcategories": [{"name": "Layered / Office", "slug": "layered_office", "items": ["structured test item"]}],
|
|
},
|
|
]
|
|
slash_selector = category_library.exact_subcategory_selector(
|
|
slash_categories[1],
|
|
slash_categories[1]["subcategories"][0],
|
|
)
|
|
slash_choice = category_library.split_exact_subcategory_choice(slash_categories, slash_selector)
|
|
_expect(slash_choice is not None, "Exact selector parser did not accept slash-bearing category/subcategory names")
|
|
if slash_choice is not None:
|
|
_expect(slash_choice[0].get("slug") == "dev_test_wear", "Exact selector parser did not prefer longest category prefix")
|
|
_expect(slash_choice[1] == "Layered / Office", "Exact selector parser trimmed slash-bearing subcategory incorrectly")
|
|
slash_category, slash_subcategory, _slash_women, _slash_men = category_library.find_subcategory(
|
|
slash_categories,
|
|
"custom_random",
|
|
slash_selector,
|
|
random.Random(201),
|
|
random.Random(202),
|
|
women_count=1,
|
|
men_count=0,
|
|
)
|
|
_expect(slash_category.get("slug") == "dev_test_wear", "Exact subcategory lookup failed slash-bearing category")
|
|
_expect(slash_subcategory.get("slug") == "layered_office", "Exact subcategory lookup failed slash-bearing subcategory")
|
|
|
|
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_category_subcategory_matrix() -> None:
|
|
categories = category_library.load_category_library()
|
|
cases: list[tuple[dict[str, Any], dict[str, Any]]] = []
|
|
for category in categories:
|
|
for subcategory in category.get("subcategories") or []:
|
|
if subcategory.get("item_templates") or subcategory.get("items"):
|
|
cases.append((category, subcategory))
|
|
_expect(len(cases) >= 30, "category matrix should cover all configured JSON subcategories")
|
|
|
|
hardcore_filter = _broad_hardcore_filter()
|
|
for index, (category, subcategory) in enumerate(cases, start=6101):
|
|
category_slug = str(category.get("slug") or "")
|
|
subcategory_slug = str(subcategory.get("slug") or "")
|
|
name = f"category_matrix.{category_slug}.{subcategory_slug}"
|
|
women_count, men_count, cast = _matrix_cast_for_route(category, subcategory)
|
|
row = _prompt_row(
|
|
name=name,
|
|
category=str(category.get("name") or ""),
|
|
subcategory=_exact_subcategory_selector(category, subcategory),
|
|
seed=index,
|
|
character_cast=cast,
|
|
women_count=women_count,
|
|
men_count=men_count,
|
|
hardcore_position_config=hardcore_filter if category_slug == "hardcore_sexual_poses" else "",
|
|
)
|
|
|
|
_expect(row.get("source") == "json_category", f"{name}.source should be json_category")
|
|
_expect(row.get("category_slug") == category_slug, f"{name}.category_slug drifted to {row.get('category_slug')}")
|
|
_expect(
|
|
row.get("subcategory_slug") == subcategory_slug,
|
|
f"{name}.subcategory_slug drifted to {row.get('subcategory_slug')}",
|
|
)
|
|
_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(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")
|
|
|
|
if category_slug == "hardcore_sexual_poses":
|
|
_expect(row.get("content_seed_axis") == "pose", f"{name}.content_seed_axis should be pose")
|
|
_expect_text(f"{name}.source_role_graph", row.get("source_role_graph") or row.get("role_graph"), 20)
|
|
_expect_text(f"{name}.action_family", row.get("action_family"), 3)
|
|
_expect_text(f"{name}.position_family", row.get("position_family"), 3)
|
|
_expect(isinstance(row.get("position_keys"), list), f"{name}.position_keys missing")
|
|
_expect(isinstance(row.get("item_template_metadata"), dict), f"{name}.item_template_metadata missing")
|
|
_expect(row.get("item_template_metadata"), f"{name}.item_template_metadata should not be empty")
|
|
else:
|
|
_expect(row.get("content_seed_axis") == "content", f"{name}.content_seed_axis should be content")
|
|
|
|
_expect_formatter_outputs(row, name, target="single")
|
|
|
|
|
|
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", {"manual"}, "manual stimulation", "manual action"),
|
|
("hardcore_anal", "Anal and double penetration", "anal_only", "anal", {"anal", "toy_double"}, "anal sex", "anal 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}")
|
|
styled_row = _prompt_row(
|
|
name="hardcore_style_pool_override",
|
|
category="Hardcore sexual poses",
|
|
subcategory="Penetrative sex",
|
|
seed=1181,
|
|
character_cast=cast,
|
|
women_count=1,
|
|
men_count=1,
|
|
hardcore_position_config=_action_filter("penetration_only"),
|
|
style_config=pb.build_style_config_json(preset="comic_pinup_colored_pencil"),
|
|
)
|
|
_expect("comic pin-up" in styled_row.get("style", ""), "Style Pool did not reach generated hardcore row style")
|
|
_expect("comic linework" in styled_row.get("positive_suffix", ""), "Style Pool did not reach generated hardcore row suffix")
|
|
_expect("comic pin-up" in styled_row.get("prompt", ""), "Style Pool style was not rendered into hardcore prompt")
|
|
multi_cases = [
|
|
("hardcore_threesome", "Threesomes", "threesome_only", "threesome", {"threesome", "toy_double"}, "threesome", "three-person action", 1, 2),
|
|
("hardcore_group", "Group sex and orgy", "group_only", "group", {"group", "toy_double"}, "group sex", "group action", 2, 2),
|
|
]
|
|
for index, (name, subcategory, focus, position_family, action_families, sdxl_tag, caption_label, women_count, men_count) in enumerate(multi_cases, start=1151):
|
|
subjects = ["woman"] * women_count + ["man"] * men_count
|
|
row = _prompt_row(
|
|
name=name,
|
|
category="Hardcore sexual poses",
|
|
subcategory=subcategory,
|
|
seed=index,
|
|
character_cast=_character_cast_subjects(subjects),
|
|
women_count=women_count,
|
|
men_count=men_count,
|
|
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_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",
|
|
)
|
|
sanitized_closet_scene = pair_clothing.body_exposure_scene_text(
|
|
"full-length closet mirror with outfit racks, shoe shelves, and soft boutique lighting"
|
|
).lower()
|
|
_expect("outfit racks" not in sanitized_closet_scene, "Pair clothing body exposure scene cleanup should remove outfit racks")
|
|
_expect("shoe shelves" not in sanitized_closet_scene, "Pair clothing body exposure scene cleanup should remove shoe shelves")
|
|
_expect("mirror shelves" in sanitized_closet_scene, "Pair clothing body exposure scene cleanup should keep neutral mirror detail")
|
|
_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")
|
|
_expect(camera_route.soft_row.get("subject_kind") == "couple", "Same-cast softcore camera route should use couple subject kind")
|
|
_expect("couple scene directive" in camera_route.soft_camera_scene_directive, "Same-cast softcore camera directive should use couple wording")
|
|
|
|
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")
|
|
continuity_outfit = "button-down shirt tied at the waist over a fitted bralette and denim shorts"
|
|
partial_common = {**clothing_common, "mode": "partially_removed", "softcore_outfit": continuity_outfit}
|
|
partial_route = pair_clothing.resolve_hardcore_pair_clothing_result(**partial_common)
|
|
_expect(partial_route.requires_body_exposure_scene is True, "Partial lower-access clothing should request scene cleanup")
|
|
partial_lower = partial_route.hardcore_clothing_state.lower()
|
|
_expect("denim shorts" in partial_lower, "Partial lower-access clothing should name removed lower softcore garment")
|
|
_expect("below the hips" in partial_lower, "Partial lower-access clothing should describe lower-garment removal")
|
|
_expect("button-down shirt" in partial_lower and "fitted bralette" in partial_lower, "Partial lower-access clothing lost remaining softcore outfit styling")
|
|
implied_route = pair_clothing.resolve_hardcore_pair_clothing_result(
|
|
**{**clothing_common, "mode": "implied_nude", "softcore_outfit": continuity_outfit}
|
|
)
|
|
implied_lower = implied_route.hardcore_clothing_state.lower()
|
|
_expect("fabric slipping off" not in implied_lower, "Implied nude clothing should not fall back to generic fabric slipping")
|
|
_expect("denim shorts" in implied_lower and "fitted bralette" in implied_lower, "Implied nude clothing should mirror softcore outfit pieces")
|
|
pov_clothing_route = pair_clothing.resolve_hardcore_pair_clothing_result(
|
|
**{
|
|
**clothing_common,
|
|
"men_count": 1,
|
|
"pov_labels": ["Man A"],
|
|
"label_map": {"Man A": {"hardcore_clothing": "open shirt with jeans lowered below the hips"}},
|
|
"slot_hardcore_clothing": lambda slot, _rng: str((slot or {}).get("hardcore_clothing") or ""),
|
|
}
|
|
)
|
|
pov_clothing_lower = pov_clothing_route.hardcore_clothing_state.lower()
|
|
_expect("pov foreground clothing cue" in pov_clothing_lower, "POV man clothing should become a foreground cue")
|
|
_expect("jeans lowered below the hips" in pov_clothing_lower, "POV lower-access clothing lost configured lower garment state")
|
|
_expect("man a wears" not in pov_clothing_lower, "POV clothing should not describe the POV man as a visible partner")
|
|
_expect(not pov_clothing_route.default_man_hardcore_clothing, "POV man should not also receive default visible-man clothing")
|
|
structured_axis_clothing = pair_clothing.resolve_hardcore_pair_clothing_result(
|
|
**{
|
|
**clothing_common,
|
|
"softcore_outfit": continuity_outfit,
|
|
"hard_row": {
|
|
"role_graph": "generic adult action",
|
|
"item": "generic configured action",
|
|
"item_axis_values": {
|
|
"position": {"text": "edge-supported penetration"},
|
|
"leg_detail": ["thighs open", "auto"],
|
|
},
|
|
},
|
|
"mode": "partially_removed",
|
|
}
|
|
)
|
|
_expect(
|
|
structured_axis_clothing.woman_access == "lower",
|
|
"Pair clothing should read structured/list axis values for lower-access detection",
|
|
)
|
|
oral_common = {
|
|
**clothing_common,
|
|
"hard_row": {
|
|
"role_graph": "the woman takes the man's penis in her mouth",
|
|
"item": "standing oral test item",
|
|
},
|
|
"mode": "partially_removed",
|
|
}
|
|
oral_route = pair_clothing.resolve_hardcore_pair_clothing_result(**oral_common)
|
|
_expect(oral_route.requires_body_exposure_scene is True, "Man lower-access clothing should request scene cleanup")
|
|
|
|
|
|
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")
|
|
shared_cast_text = "; ".join(str(item).strip() for item in pair.get("shared_cast_descriptors") or [] if str(item).strip())
|
|
_expect(
|
|
pair["hardcore_row"].get("cast_descriptor_text") == shared_cast_text,
|
|
f"{name}.hardcore_row cast descriptors drifted from pair root",
|
|
)
|
|
if pair.get("options", {}).get("softcore_cast") == "same_as_hardcore":
|
|
_expect(
|
|
pair["softcore_row"].get("cast_descriptor_text") == shared_cast_text,
|
|
f"{name}.softcore_row cast descriptors drifted from same-cast pair root",
|
|
)
|
|
_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")
|
|
_expect_no_softcore_noise("insta_pair_same_cast.softcore_prompt", pair.get("softcore_prompt"))
|
|
_expect("styled creator-teaser frame" in str(pair.get("softcore_prompt", "")).lower(), "pair softcore prompt lost clean cast-presence wording")
|
|
krea_soft = krea_formatter.format_krea2_prompt("", metadata_json=_json(pair), target="softcore")
|
|
krea_soft_prompt = _expect_text("insta_pair_same_cast.krea_soft_prompt", krea_soft.get("krea_prompt"), 40)
|
|
_expect_no_softcore_noise("insta_pair_same_cast.krea_soft_prompt", krea_soft_prompt)
|
|
_expect("styled creator-teaser frame" in krea_soft_prompt.lower(), "Krea softcore prompt lost clean same-cast wording")
|
|
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:
|
|
forced_location = pb.build_location_pool_json(
|
|
enabled=True,
|
|
combine_mode="replace",
|
|
preset="custom_only",
|
|
custom_locations="closet_scene: full-length closet mirror with outfit racks, shoe shelves, and soft boutique lighting",
|
|
)
|
|
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"),
|
|
location_config=forced_location,
|
|
)
|
|
_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(
|
|
"below the hips" in root_clothing,
|
|
"pair root clothing state lost lower-body removal 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("below the hips" in lower, "Krea clothing route lost generated lower-body clothing continuity")
|
|
_expect("the man keeps" in lower, "Krea clothing route lost partner clothing continuity")
|
|
_expect("outfit racks" not in lower and "shoe shelves" not in lower, "Krea pair formatter leaked unsanitized hard scene")
|
|
hard_scene = _clean_key(pair["hardcore_row"].get("scene_text"))
|
|
_expect("outfit racks" not in hard_scene and "shoe shelves" not in hard_scene, "Pair builder leaked outfit-check scene clutter into hardcore row")
|
|
_expect("mirror shelves" in hard_scene, "Pair builder lost neutralized scene anchor")
|
|
caption_text, _caption_method = caption_naturalizer.naturalize_caption("", metadata_json=_json(pair), target="hardcore")
|
|
caption_lower = caption_text.lower()
|
|
_expect("outfit racks" not in caption_lower and "shoe shelves" not in caption_lower, "Caption pair formatter leaked unsanitized hard scene")
|
|
sdxl = sdxl_formatter.format_sdxl_prompt("", metadata_json=_json(pair), target="hardcore")
|
|
sdxl_lower = sdxl.get("sdxl_prompt", "").lower()
|
|
_expect("outfit racks" not in sdxl_lower and "shoe shelves" not in sdxl_lower, "SDXL pair formatter leaked unsanitized hard scene")
|
|
|
|
|
|
def smoke_krea_anal_axis_compatibility() -> None:
|
|
pair = pb.build_insta_of_pair(
|
|
row_number=1,
|
|
start_index=1,
|
|
seed=6252,
|
|
ethnicity="french_european",
|
|
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", camera_detail="off"),
|
|
character_cast=_character_cast(),
|
|
hardcore_position_config=_action_filter(
|
|
"anal_only",
|
|
pb.build_hardcore_position_pool_json(family="anal"),
|
|
),
|
|
seed_config=pb.build_seed_lock_config_json(base_seed=6252, reroll_axis="pose", reroll_seed=6352),
|
|
)
|
|
hard_row = pair["hardcore_row"]
|
|
axis_values = hard_row.get("item_axis_values") or {}
|
|
position = str(axis_values.get("position") or "").lower()
|
|
leg_detail = str(axis_values.get("leg_detail") or "").lower()
|
|
if "side-lying" in position:
|
|
_expect("standing" not in leg_detail, "Generated side-lying anal row leaked standing leg detail")
|
|
krea = krea_formatter.format_krea2_prompt("", metadata_json=_json(pair), target="hardcore")
|
|
prompt = _expect_text("krea_anal_axis_compatibility.krea_prompt", krea.get("krea_prompt"), 60)
|
|
lower = prompt.lower()
|
|
_expect(
|
|
"side-lying rear-entry anal pose" not in lower or "stands braced" not in lower,
|
|
"Krea anal formatter mixed side-lying anchor with standing role graph",
|
|
)
|
|
_expect("side-lying anal position, standing with legs braced" not in lower, "Krea anal formatter leaked contradictory axis detail")
|
|
|
|
|
|
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")
|
|
pov_clothing = " ".join(pair.get("pov_hardcore_clothing") or []).lower()
|
|
_expect("pov foreground clothing cue" in pov_clothing, "pair POV man should get foreground clothing metadata")
|
|
_expect("viewer" in pov_clothing, "POV clothing metadata should be phrased through the viewer")
|
|
_expect("man a wears" not in (pair.get("hardcore_clothing_state") or "").lower(), "POV clothing state should not describe Man A as visible")
|
|
krea = krea_formatter.format_krea2_prompt("", metadata_json=_json(pair), target="hardcore")
|
|
prompt = krea.get("krea_prompt") or ""
|
|
prompt_lower = prompt.lower()
|
|
_expect("viewer" in prompt_lower, "POV Krea prompt should mention viewer perspective")
|
|
_expect("pov foreground clothing cue" in prompt_lower, "POV Krea prompt lost foreground clothing cue")
|
|
|
|
|
|
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("near desk edge" in soft_scene, "non-POV camera scene lost coworking foreground anchor")
|
|
_expect("laptop corner" in soft_scene, "non-POV camera scene lost coworking foreground detail")
|
|
_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(
|
|
"context stays beside or behind the bodies and along the side/background edges" in scene_directive,
|
|
"POV camera scene should place location anchors with positive wording",
|
|
)
|
|
_expect("not in the lower foreground" not in scene_directive, "POV camera scene should avoid negative foreground wording")
|
|
_expect(
|
|
"near desk edge" not in scene_directive and "laptop corner" not in scene_directive and "chair back" not in scene_directive,
|
|
"POV camera scene should not reuse coworking foreground anchors as viewer-side objects",
|
|
)
|
|
_expect(
|
|
"POV body or hand cues stay in the lower foreground" in scene_directive,
|
|
"POV camera scene should keep first-person body cues in the lower foreground",
|
|
)
|
|
_expect("use the multiangle camera" not in scene_directive, "POV camera scene leaked internal multiangle instruction")
|
|
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("laptop corner" not in prompt, "Krea POV prompt leaked foreground desk anchor into camera scene")
|
|
_expect("POV body or hand cues stay in the lower foreground" in prompt, "Krea POV prompt lost POV foreground cue")
|
|
_expect("use the multiangle camera" not in prompt, "Krea POV prompt leaked internal multiangle instruction")
|
|
_expect("Camera:" not in prompt, "Krea POV prompt should not emit normal third-person camera directive")
|
|
|
|
|
|
def smoke_krea2_pov_pose_variant_catalog() -> None:
|
|
catalog_path = ROOT / "categories" / "krea2_pov_pose_variants.json"
|
|
_expect(catalog_path.is_file(), "Krea2 POV pose-variant catalog is missing")
|
|
catalog = json.loads(catalog_path.read_text(encoding="utf-8"))
|
|
_expect(catalog.get("version") == 1, "Krea2 POV pose-variant catalog version changed unexpectedly")
|
|
atlas_root = Path(_expect_text("krea2_pov_pose_variant_catalog.atlas_root", catalog.get("atlas_root"), 20))
|
|
variants = catalog.get("variants")
|
|
_expect(isinstance(variants, list) and variants, "Krea2 POV pose-variant catalog has no variants")
|
|
statuses = set((catalog.get("status_values") or {}).keys())
|
|
_expect({"proven", "candidate", "unstable"}.issubset(statuses), "Krea2 POV pose-variant statuses are incomplete")
|
|
seen_keys: set[str] = set()
|
|
required_keys = {
|
|
"pov_doggy_top_down_rear_entry",
|
|
"pov_boobjob_upright_cleavage",
|
|
"pov_handjob_upright_centered",
|
|
}
|
|
for variant in variants:
|
|
key = _expect_text("krea2_pov_pose_variant_catalog.key", variant.get("key"), 8)
|
|
_expect(key not in seen_keys, f"Krea2 POV pose-variant catalog has duplicate key {key!r}")
|
|
seen_keys.add(key)
|
|
_expect(variant.get("status") in statuses, f"{key} has unknown status {variant.get('status')!r}")
|
|
_expect_text(f"{key}.family", variant.get("family"), 3)
|
|
_expect_text(f"{key}.canonical_geometry", variant.get("canonical_geometry"), 80)
|
|
prompt_cues = variant.get("prompt_cues")
|
|
avoid_cues = variant.get("avoid_cues")
|
|
refs = variant.get("reference_images")
|
|
_expect(isinstance(prompt_cues, list) and prompt_cues, f"{key} has no prompt cues")
|
|
_expect(isinstance(avoid_cues, list) and avoid_cues, f"{key} has no avoid cues")
|
|
prompt_variant_cues = variant.get("prompt_variant_cues", [])
|
|
_expect(isinstance(prompt_variant_cues, list), f"{key} prompt variant cue sets should be a list when present")
|
|
all_cue_sets = [prompt_cues, *prompt_variant_cues]
|
|
for cue_set_index, cue_set in enumerate(all_cue_sets):
|
|
_expect(isinstance(cue_set, list) and cue_set, f"{key} prompt cue set {cue_set_index} is empty")
|
|
for cue in cue_set:
|
|
cue_text = _expect_text(f"{key}.prompt_cue[{cue_set_index}]", cue, 8)
|
|
_expect(
|
|
not re.search(r"\b(?:may|optionally|either|or)\b", cue_text, flags=re.IGNORECASE),
|
|
f"{key} prompt cue should be a direct model instruction, not an option list: {cue_text!r}",
|
|
)
|
|
_expect(isinstance(refs, list) and refs, f"{key} has no reference images")
|
|
hook = variant.get("generator_hook") or {}
|
|
_expect_text(f"{key}.generator_hook.module", hook.get("module"), 6)
|
|
_expect(isinstance(hook.get("route_terms"), list) and hook["route_terms"], f"{key} has no route terms")
|
|
for folder in variant.get("atlas_folders") or []:
|
|
_expect("bg" not in str(folder).lower(), f"{key} should not use background-only folder {folder!r}")
|
|
for ref in refs:
|
|
ref_text = _expect_text(f"{key}.reference_image", ref, 8)
|
|
_expect(".." not in Path(ref_text).parts, f"{key} reference escapes atlas root: {ref_text!r}")
|
|
_expect("bg" not in ref_text.lower(), f"{key} should not use background-only reference {ref_text!r}")
|
|
if atlas_root.exists():
|
|
_expect((atlas_root / ref_text).is_file(), f"{key} reference image is missing: {atlas_root / ref_text}")
|
|
if variant.get("status") == "proven":
|
|
evidence = variant.get("evidence") or {}
|
|
_expect(evidence.get("fixed_seed_tests"), f"{key} is proven but has no fixed-seed evidence")
|
|
_expect_text(f"{key}.guide_section", evidence.get("guide_section"), 10)
|
|
_expect(required_keys.issubset(seen_keys), "Krea2 POV pose-variant catalog lost a proven starter variant")
|
|
|
|
|
|
def _atlas_variant_include_key(variant_key: str) -> str:
|
|
key = "".join(char if char.isalnum() else "_" for char in str(variant_key).lower().removeprefix("pov_")).strip("_")
|
|
while "__" in key:
|
|
key = key.replace("__", "_")
|
|
return f"include_{key}"
|
|
|
|
|
|
def smoke_krea2_pov_atlas_variant_prompt_routes() -> None:
|
|
filter_by_action_family = {
|
|
"penetration": "SxCPKrea2POVPenetrationFilter",
|
|
"oral": "SxCPKrea2POVOralFilter",
|
|
"outercourse": "SxCPKrea2POVOutercourseFilter",
|
|
"manual": "SxCPKrea2POVManualFilter",
|
|
"toy": "SxCPKrea2POVToyFilter",
|
|
"climax": "SxCPKrea2POVClimaxFilter",
|
|
"interaction": "SxCPKrea2POVInteractionFilter",
|
|
}
|
|
variants = krea2_pose_variant_catalog.variants()
|
|
_expect(variants, "Krea2 POV atlas prompt route smoke found no variants")
|
|
for offset, variant in enumerate(variants, start=4510):
|
|
key = _expect_text("krea2_pov_atlas_variant_prompt_routes.key", variant.get("key"), 8)
|
|
action_family = _expect_text(f"{key}.action_family", variant.get("action_family"), 3)
|
|
node_name = filter_by_action_family.get(action_family)
|
|
_expect(node_name in sxcp_nodes.NODE_CLASS_MAPPINGS, f"{key} has no Krea2 POV filter node for action family {action_family!r}")
|
|
include_key = _atlas_variant_include_key(key)
|
|
node_cls = sxcp_nodes.NODE_CLASS_MAPPINGS[node_name]
|
|
_expect(include_key in (node_cls.INPUT_TYPES().get("required") or {}), f"{node_name} does not expose {include_key}")
|
|
variant_config = node_cls().build("replace", "", **{include_key: True})[0]
|
|
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=variant_config,
|
|
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, f"krea2_pov_atlas_variant_prompt_routes.{key}")
|
|
hard_row = pair.get("hardcore_row") or {}
|
|
variant_keys = (hard_row.get("hardcore_position_config") or {}).get("krea2_variant_keys") or []
|
|
_expect(variant_keys == [key], f"{key} row lost exact variant metadata: {variant_keys}")
|
|
krea = krea_formatter.format_krea2_prompt("", metadata_json=_json(pair), target="hardcore")
|
|
prompt = _expect_text(f"{key}.krea_prompt", krea.get("krea_prompt"), 80).lower()
|
|
hard_axis_values = hard_row.get("item_axis_values") if isinstance(hard_row.get("item_axis_values"), dict) else {}
|
|
variant_indices = hard_axis_values.get("krea2_prompt_variant_indices") if isinstance(hard_axis_values, dict) else {}
|
|
selected_index = 0
|
|
if isinstance(variant_indices, dict):
|
|
try:
|
|
selected_index = int(variant_indices.get(key, 0))
|
|
except (TypeError, ValueError):
|
|
selected_index = 0
|
|
cue_sets = krea2_pose_variant_catalog.prompt_cue_sets(variant)
|
|
_expect(0 <= selected_index < len(cue_sets), f"{key} selected invalid prompt variant index {selected_index}")
|
|
for cue in cue_sets[selected_index]:
|
|
cue_text = _expect_text(f"{key}.prompt_cue", cue, 8).lower()
|
|
_expect(cue_text in prompt, f"{key} final Krea prompt lost atlas cue {cue_text!r}: {prompt}")
|
|
atlas_action_prompt = prompt.split(" camera is ", 1)[0]
|
|
_expect(
|
|
not re.search(r"\b(?:may|optionally|either|or)\b", atlas_action_prompt, flags=re.IGNORECASE),
|
|
f"{key} final Krea prompt kept optional atlas action wording: {prompt}",
|
|
)
|
|
_expect(
|
|
"; " not in atlas_action_prompt,
|
|
f"{key} final Krea prompt kept semicolon-delimited atlas cue formatting: {prompt}",
|
|
)
|
|
_expect(
|
|
"camera layout" not in prompt
|
|
and "keep the visible partner" not in prompt
|
|
and "context stays beside" not in prompt
|
|
and "pov body or hand cues stay" not in prompt,
|
|
f"{key} final Krea prompt kept generic camera-scene layout prose: {prompt}",
|
|
)
|
|
_expect(
|
|
"hands or body cues" not in prompt and "perspective or foreground cues" not in prompt,
|
|
f"{key} final Krea prompt kept option wording in POV camera phrase: {prompt}",
|
|
)
|
|
_expect(
|
|
"framed as " not in prompt and "the image is framed as " not in prompt,
|
|
f"{key} final Krea prompt kept generic composition text after atlas route: {prompt}",
|
|
)
|
|
for forbidden in (
|
|
"pov foreground clothing cue",
|
|
"body is fully exposed",
|
|
"explicit consensual",
|
|
"use clear adult anatomy",
|
|
):
|
|
_expect(forbidden not in prompt, f"{key} final Krea prompt kept generic formatter layer {forbidden!r}: {prompt}")
|
|
atlas_geometry = " ".join([str(variant.get("canonical_geometry") or ""), *[str(cue) for cue in variant.get("prompt_cues") or []]]).lower()
|
|
if any(term in atlas_geometry for term in ("top-down", "top view", "top-view", "nadir", "overhead")):
|
|
_expect("eye-level shot" not in prompt, f"{key} final Krea prompt kept contradictory eye-level camera text: {prompt}")
|
|
if key == "pov_blowjob_top_down_vertical_shaft":
|
|
_expect(
|
|
prompt.count("nadir-angle standing male pov top-view oral") == 1,
|
|
f"{key} final Krea prompt repeated top-view atlas header instead of staying compact: {prompt}",
|
|
)
|
|
for forbidden in (
|
|
"the woman takes the viewer's penis",
|
|
"visible saliva",
|
|
"cum on",
|
|
"hands holding hips",
|
|
"eyes looking up",
|
|
):
|
|
_expect(forbidden not in prompt, f"{key} final Krea prompt kept generic detail fragment {forbidden!r}: {prompt}")
|
|
for required in (
|
|
"the woman kneels directly below the viewer between his feet",
|
|
"mouth seals around the centered shaft",
|
|
"one hand wraps the base",
|
|
):
|
|
_expect(required in prompt, f"{key} final Krea prompt lost compact contact cue {required!r}: {prompt}")
|
|
_expect("one woman" not in prompt, f"{key} final Krea prompt split the subject with 'one woman': {prompt}")
|
|
for avoid in variant.get("avoid_cues") or []:
|
|
avoid_text = _expect_text(f"{key}.avoid_cue", avoid, 4).lower()
|
|
_expect(avoid_text not in prompt, f"{key} final Krea prompt leaked avoid cue {avoid_text!r}: {prompt}")
|
|
|
|
|
|
def smoke_krea2_pose_variant_catalog_policy() -> None:
|
|
catalog = krea2_pose_variant_catalog.load_catalog()
|
|
_expect(catalog.get("version") == 1, "Krea2 pose-variant loader returned wrong catalog")
|
|
synthetic_variant = {
|
|
"key": "pov_synthetic_seeded_variant",
|
|
"prompt_cues": ["synthetic baseline atlas cue", "synthetic baseline contact anchor"],
|
|
"prompt_variant_cues": [
|
|
["synthetic alternate atlas cue", "synthetic alternate contact anchor"],
|
|
{"prompt_cues": ["synthetic second alternate cue", "synthetic second contact anchor"]},
|
|
],
|
|
}
|
|
_expect(
|
|
krea2_pose_variant_catalog.prompt_cue_sets(synthetic_variant) == [
|
|
["synthetic baseline atlas cue", "synthetic baseline contact anchor"],
|
|
["synthetic alternate atlas cue", "synthetic alternate contact anchor"],
|
|
["synthetic second alternate cue", "synthetic second contact anchor"],
|
|
],
|
|
"Krea2 pose-variant catalog should expose baseline and optional prompt variant cue sets",
|
|
)
|
|
original_get_variant = krea2_pose_variant_catalog.get_variant
|
|
try:
|
|
def fake_get_variant(key: str, **kwargs):
|
|
if key == "pov_synthetic_seeded_variant":
|
|
return synthetic_variant
|
|
return original_get_variant(key, **kwargs)
|
|
|
|
krea2_pose_variant_catalog.get_variant = fake_get_variant
|
|
baseline_sentence = krea_pov_actions._krea2_atlas_variant_sentence(
|
|
{
|
|
"krea2_variant_keys": ["pov_synthetic_seeded_variant"],
|
|
"krea2_prompt_variant_indices": {"pov_synthetic_seeded_variant": 0},
|
|
}
|
|
)
|
|
alternate_sentence = krea_pov_actions._krea2_atlas_variant_sentence(
|
|
{
|
|
"krea2_variant_keys": ["pov_synthetic_seeded_variant"],
|
|
"krea2_prompt_variant_indices": {"pov_synthetic_seeded_variant": 1},
|
|
}
|
|
)
|
|
_expect("synthetic baseline atlas cue" in baseline_sentence, "Krea2 atlas sentence lost baseline prompt cue set")
|
|
_expect("synthetic alternate atlas cue" in alternate_sentence, "Krea2 atlas sentence lost selected prompt variant cue set")
|
|
selected_indices: set[int] = set()
|
|
for seed in range(4510, 4560):
|
|
axis_values = pb._axis_values_with_krea2_prompt_variant_indices(
|
|
{"krea2_variant_keys": ["pov_synthetic_seeded_variant"]},
|
|
seed_config={},
|
|
seed=seed,
|
|
row_number=1,
|
|
)
|
|
selected_indices.add(int(axis_values.get("krea2_prompt_variant_indices", {}).get("pov_synthetic_seeded_variant", 0)))
|
|
_expect(
|
|
len(selected_indices) >= 2,
|
|
f"Krea2 prompt variant selector should vary across pose seeds, got {sorted(selected_indices)}",
|
|
)
|
|
preselected_axis_values = pb._axis_values_with_krea2_variant_keys(
|
|
{},
|
|
{
|
|
"krea2_variant_keys": ["pov_synthetic_seeded_variant"],
|
|
"krea2_prompt_variant_indices": {"pov_synthetic_seeded_variant": 1},
|
|
},
|
|
)
|
|
_expect(
|
|
preselected_axis_values.get("krea2_prompt_variant_indices") == {"pov_synthetic_seeded_variant": 1},
|
|
"Krea2 variant-key merge should preserve node-selected prompt variant indices",
|
|
)
|
|
generated_axis_values = pb._axis_values_with_krea2_prompt_variant_indices(
|
|
preselected_axis_values,
|
|
seed_config={},
|
|
seed=4510,
|
|
row_number=1,
|
|
)
|
|
_expect(
|
|
generated_axis_values.get("krea2_prompt_variant_indices") == {"pov_synthetic_seeded_variant": 1},
|
|
"Pose-seed prompt variant selector should not overwrite node-selected atlas cue indices",
|
|
)
|
|
finally:
|
|
krea2_pose_variant_catalog.get_variant = original_get_variant
|
|
|
|
proven = krea2_pose_variant_catalog.variant_keys(status="proven")
|
|
_expect(
|
|
proven == [
|
|
"pov_doggy_top_down_rear_entry",
|
|
"pov_boobjob_upright_cleavage",
|
|
"pov_handjob_upright_centered",
|
|
"pov_footjob_frontal_sole_stroke",
|
|
"pov_wand_foreground_tool_contact",
|
|
"pov_blowjob_side_profile_oral",
|
|
"pov_cowgirl_frontal_straddle_penetration",
|
|
],
|
|
f"Krea2 pose-variant proven keys changed unexpectedly: {proven}",
|
|
)
|
|
penetration = krea2_pose_variant_catalog.variant_keys(action_family="penetration")
|
|
_expect(
|
|
penetration == [
|
|
"pov_doggy_top_down_rear_entry",
|
|
"pov_missionary_open_leg_penetration",
|
|
"pov_missionary_folded_high_leg_penetration",
|
|
"pov_cowgirl_frontal_straddle_penetration",
|
|
"pov_cowgirl_alt_low_squat_penetration",
|
|
"pov_reverse_cowgirl_back_facing_penetration",
|
|
"pov_reverse_cowgirl_alt_upright_back_facing_penetration",
|
|
],
|
|
f"Krea2 pose-variant penetration filtering changed unexpectedly: {penetration}",
|
|
)
|
|
outercourse = krea2_pose_variant_catalog.variant_keys(action_family="outercourse")
|
|
_expect(
|
|
outercourse == [
|
|
"pov_boobjob_upright_cleavage",
|
|
"pov_handjob_upright_centered",
|
|
"pov_ballsucking_low_head",
|
|
"pov_footjob_frontal_sole_stroke",
|
|
],
|
|
f"Krea2 pose-variant outercourse filtering changed unexpectedly: {outercourse}",
|
|
)
|
|
manual = krea2_pose_variant_catalog.variant_keys(action_family="manual")
|
|
_expect(
|
|
manual == ["pov_fingering_reclined_open_thighs"],
|
|
f"Krea2 pose-variant manual filtering changed unexpectedly: {manual}",
|
|
)
|
|
toy = krea2_pose_variant_catalog.variant_keys(action_family="toy")
|
|
_expect(
|
|
toy == ["pov_wand_foreground_tool_contact"],
|
|
f"Krea2 pose-variant toy filtering changed unexpectedly: {toy}",
|
|
)
|
|
climax = krea2_pose_variant_catalog.variant_keys(action_family="climax")
|
|
_expect(
|
|
climax == ["pov_ejaculation_aftermath_open_thigh_candidate"],
|
|
f"Krea2 pose-variant climax filtering changed unexpectedly: {climax}",
|
|
)
|
|
interaction = krea2_pose_variant_catalog.variant_keys(action_family="interaction")
|
|
_expect(
|
|
interaction == ["pov_spread_open_thigh_presentation"],
|
|
f"Krea2 pose-variant interaction filtering changed unexpectedly: {interaction}",
|
|
)
|
|
oral = krea2_pose_variant_catalog.variant_keys(action_family="oral")
|
|
_expect(
|
|
oral == [
|
|
"pov_sixty_nine_close_reversed_oral",
|
|
"pov_blowjob_top_down_vertical_shaft",
|
|
"pov_blowjob_side_profile_oral",
|
|
"pov_blowjob_laying_frontal_oral",
|
|
"pov_blowjob_sitting_upright_oral",
|
|
],
|
|
f"Krea2 pose-variant oral filtering changed unexpectedly: {oral}",
|
|
)
|
|
handjob = krea2_pose_variant_catalog.get_variant("pov_handjob_upright_centered")
|
|
_expect(
|
|
any("woman's right hand wraps" in str(cue) for cue in handjob.get("prompt_cues", [])),
|
|
"Handjob variant lost hand ownership cue",
|
|
)
|
|
handjob["prompt_cues"].append("mutation should not leak")
|
|
clean_handjob = krea2_pose_variant_catalog.get_variant("pov_handjob_upright_centered")
|
|
_expect("mutation should not leak" not in clean_handjob.get("prompt_cues", []), "Catalog loader leaked caller mutation")
|
|
ballsucking = krea2_pose_variant_catalog.get_variant("pov_ballsucking_low_head")
|
|
_expect(
|
|
any("scrotum is the mouth surface" in str(cue) for cue in ballsucking.get("prompt_cues", [])),
|
|
"Ballsucking variant lost open-lips scrotum-surface cue",
|
|
)
|
|
_expect(
|
|
any("scrotal skin is the nearest mouth surface" in str(cue) for cue in ballsucking.get("prompt_cues", [])),
|
|
"Ballsucking variant lost scrotal-skin nearest-surface cue",
|
|
)
|
|
ballsucking_evidence = ballsucking.get("evidence") or {}
|
|
_expect(
|
|
ballsucking_evidence.get("fixed_seed_tests") == [
|
|
"238365845574312",
|
|
"1212121212",
|
|
"5757575757",
|
|
"6262626262",
|
|
"9797979797",
|
|
"9898989898",
|
|
"5959595959",
|
|
"6060606060",
|
|
"6161616161",
|
|
"7171717171",
|
|
"7272727272",
|
|
],
|
|
"Ballsucking variant lost fresh-seed target-object evidence",
|
|
)
|
|
footjob = krea2_pose_variant_catalog.get_variant("pov_footjob_frontal_sole_stroke")
|
|
_expect(footjob.get("status") == "proven", "Footjob variant should be proven after fresh-seed generated-route repeat evidence")
|
|
_expect(
|
|
any("two large overlapping soles dominate the lower center foreground" in str(cue) for cue in footjob.get("prompt_cues", [])),
|
|
"Footjob variant lost sole-contact cue",
|
|
)
|
|
_expect(
|
|
any("narrow visible strip of shaft and glans rises between the compressed feet" in str(cue) for cue in footjob.get("prompt_cues", [])),
|
|
"Footjob variant lost visible-glans contact cue",
|
|
)
|
|
fingering = krea2_pose_variant_catalog.get_variant("pov_fingering_reclined_open_thighs")
|
|
_expect(fingering.get("status") == "candidate", "Fingering variant should remain a candidate until fixed-seed evidence exists")
|
|
_expect(
|
|
any("viewer hand enters from the foreground" in str(cue) for cue in fingering.get("prompt_cues", [])),
|
|
"Fingering variant lost foreground-hand cue",
|
|
)
|
|
wand = krea2_pose_variant_catalog.get_variant("pov_wand_foreground_tool_contact")
|
|
_expect(wand.get("status") == "proven", "Wand variant should be proven after repeated generated-route evidence")
|
|
_expect(
|
|
any("viewer hand holds a wand-style toy from the foreground" in str(cue) for cue in wand.get("prompt_cues", [])),
|
|
"Wand variant lost foreground tool-hold cue",
|
|
)
|
|
ready = krea2_pose_variant_catalog.get_variant("pov_ejaculation_aftermath_open_thigh_candidate")
|
|
_expect(ready.get("status") == "candidate", "Ready aftermath variant should remain a candidate until fixed-seed evidence exists")
|
|
_expect(
|
|
any("thick semen and clear fluid are visible around the exposed pussy and anal opening" in str(cue) for cue in ready.get("prompt_cues", [])),
|
|
"Ready aftermath variant lost explicit post-ejaculation fluid/opening cue",
|
|
)
|
|
_expect(
|
|
any("generic ready/setup pose before sex" in str(cue) for cue in ready.get("avoid_cues", [])),
|
|
"Ready aftermath variant should reject generic ready/setup wording",
|
|
)
|
|
sixty_nine = krea2_pose_variant_catalog.get_variant("pov_sixty_nine_close_reversed_oral")
|
|
_expect(sixty_nine.get("status") == "unstable", "Sixty-nine route should stay unstable until text-only evidence improves")
|
|
_expect(sixty_nine.get("difficulty") == "hardest", "Sixty-nine route should be marked as the hardest atlas route")
|
|
_expect(sixty_nine.get("priority") == "low", "Sixty-nine route should be marked low priority")
|
|
_expect(
|
|
sixty_nine.get("control_requirement") == "pose_or_image_guidance_first",
|
|
"Sixty-nine route should require pose/image guidance before prompt-only tuning",
|
|
)
|
|
_expect(
|
|
any("visible partner is reversed over the viewer with hips closest" in str(cue) for cue in sixty_nine.get("prompt_cues", [])),
|
|
"Sixty-nine variant lost reversed-over-viewer cue",
|
|
)
|
|
spread = krea2_pose_variant_catalog.get_variant("pov_spread_open_thigh_presentation")
|
|
_expect(spread.get("status") == "candidate", "Spread variant should remain a candidate until fixed-seed evidence exists")
|
|
_expect(
|
|
any("legs raised and knees held wide" in str(cue) for cue in spread.get("prompt_cues", [])),
|
|
"Spread variant lost open-thigh cue",
|
|
)
|
|
oral_top = krea2_pose_variant_catalog.get_variant("pov_blowjob_top_down_vertical_shaft")
|
|
_expect(oral_top.get("status") == "candidate", "Blowjob top-view variant should remain a candidate until fixed-seed evidence exists")
|
|
_expect(
|
|
any("nadir-angle standing male POV top-view oral position" in str(cue) for cue in oral_top.get("prompt_cues", [])),
|
|
"Blowjob top-view variant lost nadir-angle cue",
|
|
)
|
|
_expect("kneeling" in (oral_top.get("position_keys") or []), "Blowjob top-view variant lost kneeling route key")
|
|
oral_top_evidence = oral_top.get("evidence") or {}
|
|
_expect(
|
|
oral_top_evidence.get("fixed_seed_tests") == ["4242424242"],
|
|
"Blowjob top-view variant lost fixed-seed evidence",
|
|
)
|
|
_expect(
|
|
"blowjob-top-view--overhead-vertical-shaft" in str(oral_top_evidence.get("guide_section") or ""),
|
|
"Blowjob top-view variant lost guide-section link",
|
|
)
|
|
_expect(
|
|
"nadir-angle standing male POV" in str(oral_top_evidence.get("notes") or ""),
|
|
"Blowjob top-view variant lost nadir-angle evidence note",
|
|
)
|
|
oral_side = krea2_pose_variant_catalog.get_variant("pov_blowjob_side_profile_oral")
|
|
_expect(oral_side.get("status") == "proven", "Blowjob side variant should be proven after fresh-seed three-woman generated-route repeat evidence")
|
|
_expect(
|
|
any("woman enters laterally from the left edge beside his hip" in str(cue) for cue in oral_side.get("prompt_cues", [])),
|
|
"Blowjob side variant lost lateral-edge body-line cue",
|
|
)
|
|
_expect(
|
|
any("mouth-to-shaft contact is the nearest facial detail" in str(cue) for cue in oral_side.get("prompt_cues", [])),
|
|
"Blowjob side variant lost generated-route contact-priority cue",
|
|
)
|
|
_expect(
|
|
any("adult male viewer's own torso starts at the lower edge" in str(cue) for cue in oral_side.get("prompt_cues", [])),
|
|
"Blowjob side variant lost lower-right self-torso anchor cue",
|
|
)
|
|
oral_side_evidence = oral_side.get("evidence") or {}
|
|
_expect(
|
|
oral_side_evidence.get("fixed_seed_tests") == ["5656565656", "9753197531", "9595959595", "9696969696", "5858585858"],
|
|
"Blowjob side variant lost fixed-seed evidence",
|
|
)
|
|
_expect(
|
|
"blowjob-side-profile--side-phone-weak-case" in str(oral_side_evidence.get("guide_section") or ""),
|
|
"Blowjob side variant lost weak-case guide-section link",
|
|
)
|
|
_expect(
|
|
"explicit adult-male foreground ownership" in str(oral_side_evidence.get("notes") or ""),
|
|
"Blowjob side variant lost male-body ownership evidence note",
|
|
)
|
|
_expect(
|
|
"transferring the central body surface to the woman" in str(oral_side_evidence.get("notes") or ""),
|
|
"Blowjob side variant lost source-47 body-axis failure note",
|
|
)
|
|
_expect(
|
|
"9753197531" in str(oral_side_evidence.get("notes") or "") and "lateral-edge body-line" in str(oral_side_evidence.get("notes") or ""),
|
|
"Blowjob side variant lost repeated lateral-edge evidence note",
|
|
)
|
|
_expect(
|
|
"turn 207" in str(oral_side_evidence.get("notes") or "") and "mouth-to-shaft-contact priority" in str(oral_side_evidence.get("notes") or ""),
|
|
"Blowjob side variant lost generated-route contact-priority note",
|
|
)
|
|
_expect(
|
|
"9595959595" in str(oral_side_evidence.get("notes") or "") and "lower-right torso anchor" in str(oral_side_evidence.get("notes") or ""),
|
|
"Blowjob side variant lost lower-right torso repeat evidence note",
|
|
)
|
|
_expect(
|
|
"9696969696" in str(oral_side_evidence.get("notes") or "") and "generated-route validation" in str(oral_side_evidence.get("notes") or ""),
|
|
"Blowjob side variant lost patched generated-route validation note",
|
|
)
|
|
_expect(
|
|
"5858585858" in str(oral_side_evidence.get("notes") or "") and "three-woman generated-route repeat" in str(oral_side_evidence.get("notes") or ""),
|
|
"Blowjob side variant lost fresh-seed promotion note",
|
|
)
|
|
oral_laying = krea2_pose_variant_catalog.get_variant("pov_blowjob_laying_frontal_oral")
|
|
_expect(oral_laying.get("status") == "candidate", "Blowjob laying variant should remain a candidate until repeated evidence exists")
|
|
_expect(
|
|
any("woman lies belly-down between the viewer's open thighs" in str(cue) for cue in oral_laying.get("prompt_cues", [])),
|
|
"Blowjob laying variant lost prone frontal cue",
|
|
)
|
|
oral_laying_evidence = oral_laying.get("evidence") or {}
|
|
_expect(
|
|
oral_laying_evidence.get("fixed_seed_tests") == ["6767676767"],
|
|
"Blowjob laying variant lost fixed-seed evidence",
|
|
)
|
|
_expect(
|
|
"blowjob-laying-frontal--wide-v-frame" in str(oral_laying_evidence.get("guide_section") or ""),
|
|
"Blowjob laying variant lost guide-section link",
|
|
)
|
|
_expect(
|
|
"wide symmetrical V-frame" in str(oral_laying_evidence.get("notes") or ""),
|
|
"Blowjob laying variant lost V-frame evidence note",
|
|
)
|
|
oral_sitting = krea2_pose_variant_catalog.get_variant("pov_blowjob_sitting_upright_oral")
|
|
_expect(oral_sitting.get("status") == "candidate", "Blowjob sitting variant should remain a candidate until fixed-seed evidence exists")
|
|
_expect(
|
|
any("woman sits low between the viewer's open thighs" in str(cue) for cue in oral_sitting.get("prompt_cues", [])),
|
|
"Blowjob sitting variant lost low seated cue",
|
|
)
|
|
_expect("blowjob_sitting" in (oral_sitting.get("position_keys") or []), "Blowjob sitting variant lost route key")
|
|
oral_sitting_evidence = oral_sitting.get("evidence") or {}
|
|
_expect(
|
|
oral_sitting_evidence.get("fixed_seed_tests") == ["7878787878"],
|
|
"Blowjob sitting variant lost fixed-seed evidence",
|
|
)
|
|
_expect(
|
|
"blowjob-sitting-upright--low-mouth-contact" in str(oral_sitting_evidence.get("guide_section") or ""),
|
|
"Blowjob sitting variant lost guide-section link",
|
|
)
|
|
_expect(
|
|
"low-mouth seated hierarchy" in str(oral_sitting_evidence.get("notes") or ""),
|
|
"Blowjob sitting variant lost low-mouth evidence note",
|
|
)
|
|
missionary = krea2_pose_variant_catalog.get_variant("pov_missionary_open_leg_penetration")
|
|
_expect(missionary.get("status") == "candidate", "Missionary variant should remain a candidate until fixed-seed evidence exists")
|
|
_expect(
|
|
any("woman reclines on her back with knees open toward the viewer" in str(cue) for cue in missionary.get("prompt_cues", [])),
|
|
"Missionary variant lost open-leg reclined cue",
|
|
)
|
|
_expect(
|
|
any("viewer braced at the foot edge" in str(cue) for cue in missionary.get("prompt_cues", [])),
|
|
"Missionary variant lost elevated-support edge cue",
|
|
)
|
|
missionary_evidence = missionary.get("evidence") or {}
|
|
_expect(
|
|
missionary_evidence.get("fixed_seed_tests") == ["8989898989"],
|
|
"Missionary variant lost fixed-seed evidence",
|
|
)
|
|
_expect(
|
|
"edge-supported route" in str(missionary_evidence.get("notes") or ""),
|
|
"Missionary variant lost edge-supported patch boundary",
|
|
)
|
|
missionary_folded = krea2_pose_variant_catalog.get_variant("pov_missionary_folded_high_leg_penetration")
|
|
_expect(missionary_folded.get("status") == "candidate", "Folded missionary variant should remain a candidate until repeated evidence exists")
|
|
_expect(
|
|
any("woman reclines on her back with knees folded high toward her chest" in str(cue) for cue in missionary_folded.get("prompt_cues", [])),
|
|
"Folded missionary variant lost high-leg folded cue",
|
|
)
|
|
_expect(
|
|
any("large centered shaft" in str(cue) for cue in missionary_folded.get("prompt_cues", [])),
|
|
"Folded missionary variant lost contact-first shaft cue",
|
|
)
|
|
missionary_folded_evidence = missionary_folded.get("evidence") or {}
|
|
_expect(
|
|
missionary_folded_evidence.get("fixed_seed_tests") == ["8989898989"],
|
|
"Folded missionary variant lost fixed-seed evidence",
|
|
)
|
|
_expect(
|
|
"contact before the compact folded-knee block" in str(missionary_folded_evidence.get("notes") or ""),
|
|
"Folded missionary variant lost contact-first evidence note",
|
|
)
|
|
cowgirl = krea2_pose_variant_catalog.get_variant("pov_cowgirl_frontal_straddle_penetration")
|
|
_expect(cowgirl.get("status") == "proven", "Cowgirl variant should be proven after fresh-seed generated-route repeat evidence")
|
|
_expect(
|
|
any("woman straddles the viewer facing him" in str(cue) for cue in cowgirl.get("prompt_cues", [])),
|
|
"Cowgirl variant lost frontal straddle cue",
|
|
)
|
|
_expect(
|
|
any("wide horizontal thigh bridge" in str(cue) for cue in cowgirl.get("prompt_cues", [])),
|
|
"Cowgirl variant lost wide thigh bridge cue",
|
|
)
|
|
cowgirl_evidence = cowgirl.get("evidence") or {}
|
|
_expect(
|
|
cowgirl_evidence.get("fixed_seed_tests") == ["8989898989", "2828282828", "9191919191"],
|
|
"Cowgirl variant lost fixed-seed evidence",
|
|
)
|
|
_expect(
|
|
"baseline already valid" in str(cowgirl_evidence.get("notes") or ""),
|
|
"Cowgirl variant lost baseline-valid evidence note",
|
|
)
|
|
_expect(
|
|
"9191919191" in str(cowgirl_evidence.get("notes") or "") and "turns 242" in str(cowgirl_evidence.get("notes") or ""),
|
|
"Cowgirl variant lost fresh-seed proven evidence note",
|
|
)
|
|
cowgirl_alt = krea2_pose_variant_catalog.get_variant("pov_cowgirl_alt_low_squat_penetration")
|
|
_expect(cowgirl_alt.get("status") == "candidate", "Cowgirl alt variant should remain a candidate until repeated evidence exists")
|
|
_expect(
|
|
any("low seated squat over the viewer's pelvis" in str(cue) for cue in cowgirl_alt.get("prompt_cues", [])),
|
|
"Cowgirl alt variant lost low seated squat cue",
|
|
)
|
|
_expect(
|
|
any("viewer lies flat on his back" in str(cue) for cue in cowgirl_alt.get("prompt_cues", [])),
|
|
"Cowgirl alt variant lost flat-supine viewer cue",
|
|
)
|
|
cowgirl_alt_evidence = cowgirl_alt.get("evidence") or {}
|
|
_expect(
|
|
cowgirl_alt_evidence.get("fixed_seed_tests") == ["8989898989"],
|
|
"Cowgirl alt variant lost fixed-seed evidence",
|
|
)
|
|
_expect(
|
|
"ceiling and upper glass" in str(cowgirl_alt_evidence.get("notes") or ""),
|
|
"Cowgirl alt variant lost spatial-orientation evidence note",
|
|
)
|
|
reverse_cowgirl = krea2_pose_variant_catalog.get_variant("pov_reverse_cowgirl_back_facing_penetration")
|
|
_expect(reverse_cowgirl.get("status") == "candidate", "Reverse cowgirl variant should remain a candidate until fixed-seed evidence exists")
|
|
_expect(
|
|
any("woman faces away from the viewer in a back-facing straddle" in str(cue) for cue in reverse_cowgirl.get("prompt_cues", [])),
|
|
"Reverse cowgirl variant lost back-facing straddle cue",
|
|
)
|
|
reverse_cowgirl_alt = krea2_pose_variant_catalog.get_variant("pov_reverse_cowgirl_alt_upright_back_facing_penetration")
|
|
_expect(reverse_cowgirl_alt.get("status") == "candidate", "Reverse cowgirl alt variant should remain a candidate until fixed-seed evidence exists")
|
|
_expect(
|
|
any("woman sits upright facing away from the viewer in a back-facing straddle" in str(cue) for cue in reverse_cowgirl_alt.get("prompt_cues", [])),
|
|
"Reverse cowgirl alt variant lost upright back-facing cue",
|
|
)
|
|
refs = krea2_pose_variant_catalog.reference_paths("pov_boobjob_upright_cleavage")
|
|
_expect(refs and all(path.name.endswith(".png") for path in refs), "Boobjob reference paths are not image paths")
|
|
_expect(all("bg" not in str(path).lower() for path in refs), "Reference paths should not include background-only atlas images")
|
|
missing = krea2_pose_variant_catalog.get_variant("missing_pose_variant")
|
|
_expect(missing == {}, "Missing pose variant should return an empty mapping")
|
|
|
|
|
|
def smoke_krea2_eval_log_policy() -> None:
|
|
log = krea2_eval_log.load_eval_log()
|
|
_expect(log.get("version") == 1, "Krea2 eval log version changed unexpectedly")
|
|
_expect("external ComfyUI artifacts" in str(log.get("artifact_policy") or ""), "Krea2 eval log should document external artifact policy")
|
|
entries = krea2_eval_log.entries()
|
|
_expect(entries, "Krea2 eval log has no entries")
|
|
catalog_keys = set(krea2_pose_variant_catalog.variant_keys())
|
|
proven_keys = set(krea2_pose_variant_catalog.variant_keys(status="proven"))
|
|
accepted_keys = set(krea2_eval_log.variant_keys(result="accepted"))
|
|
_expect(proven_keys.issubset(accepted_keys), "Krea2 eval log does not cover every proven pose variant")
|
|
seen_ids: set[str] = set()
|
|
for entry in entries:
|
|
entry_id = _expect_text("krea2_eval_log.entry.id", entry.get("id"), 6)
|
|
_expect(entry_id not in seen_ids, f"Krea2 eval log has duplicate entry id {entry_id!r}")
|
|
seen_ids.add(entry_id)
|
|
variant_key = _expect_text(f"{entry_id}.variant_key", entry.get("variant_key"), 8)
|
|
_expect(variant_key in catalog_keys, f"{entry_id} references unknown variant {variant_key!r}")
|
|
_expect(isinstance(entry.get("seed"), int), f"{entry_id} has no integer fixed seed")
|
|
_expect(entry.get("result") in {"accepted", "rejected", "inconclusive"}, f"{entry_id} has unknown result")
|
|
_expect(
|
|
entry.get("decision") in {
|
|
"generator_patch",
|
|
"provisional_generator_patch",
|
|
"proven_with_evidence",
|
|
"prompt_guide_rule",
|
|
"prompt_only_retry",
|
|
"needs_more_tests",
|
|
},
|
|
f"{entry_id} has unknown decision",
|
|
)
|
|
_expect_text(f"{entry_id}.baseline_prompt_summary", entry.get("baseline_prompt_summary"), 20)
|
|
_expect_text(f"{entry_id}.candidate_prompt_summary", entry.get("candidate_prompt_summary"), 20)
|
|
_expect_text(f"{entry_id}.observation", entry.get("observation"), 30)
|
|
for image_key in ("baseline_image", "candidate_image"):
|
|
image_path = str(entry.get(image_key) or "")
|
|
if image_path:
|
|
_expect(Path(image_path).is_absolute(), f"{entry_id}.{image_key} should be absolute when present")
|
|
_expect(Path(image_path).suffix.lower() == ".png", f"{entry_id}.{image_key} should reference a PNG artifact")
|
|
boobjob_entries = krea2_eval_log.entries_for_variant("pov_boobjob_upright_cleavage", result="accepted")
|
|
_expect(boobjob_entries and boobjob_entries[0].get("seed") == 7302, "Boobjob accepted eval evidence changed")
|
|
mutation = krea2_eval_log.entries_for_variant("pov_handjob_upright_centered")[0]
|
|
mutation["observation"] = "mutation should not leak"
|
|
clean = krea2_eval_log.entries_for_variant("pov_handjob_upright_centered")[0]
|
|
_expect(clean.get("observation") != "mutation should not leak", "Krea2 eval log leaked caller mutation")
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
temp_log_path = Path(tmpdir) / "krea2-eval-log.json"
|
|
temp_log_path.write_text(json.dumps(log, ensure_ascii=True, indent=2) + "\n", encoding="utf-8")
|
|
smoke_entry = {
|
|
"id": "ballsucking-9001-low-head-smoke",
|
|
"date": "2026-06-29",
|
|
"variant_key": "pov_ballsucking_low_head",
|
|
"seed": 9001,
|
|
"generator_seed": 4101,
|
|
"source": "smoke",
|
|
"result": "inconclusive",
|
|
"decision": "needs_more_tests",
|
|
"baseline_prompt_summary": "Baseline prompt kept the head too high for the atlas low-head target.",
|
|
"candidate_prompt_summary": "Candidate prompt moved the head below the shaft at testicle height.",
|
|
"observation": "Smoke entry validates the durable fixed-seed record path without changing the real eval log.",
|
|
"baseline_image": "/tmp/krea2_baseline.png",
|
|
"candidate_image": "",
|
|
"commit": "smoke",
|
|
}
|
|
errors = krea2_eval_log.validate_entry(
|
|
smoke_entry,
|
|
existing_entries=log.get("entries") or [],
|
|
catalog_keys=set(krea2_pose_variant_catalog.variant_keys()),
|
|
)
|
|
_expect(errors == [], f"Valid Krea2 eval entry failed validation: {errors}")
|
|
provisional_entry = dict(smoke_entry)
|
|
provisional_entry["id"] = "ballsucking-9002-provisional-generator-smoke"
|
|
provisional_entry["seed"] = 9002
|
|
provisional_entry["result"] = "accepted"
|
|
provisional_entry["decision"] = "provisional_generator_patch"
|
|
provisional_errors = krea2_eval_log.validate_entry(
|
|
provisional_entry,
|
|
existing_entries=log.get("entries") or [],
|
|
catalog_keys=set(krea2_pose_variant_catalog.variant_keys()),
|
|
)
|
|
_expect(provisional_errors == [], f"Provisional generator eval entry failed validation: {provisional_errors}")
|
|
appended_log = krea2_eval_log.append_entry(smoke_entry, path=temp_log_path)
|
|
_expect(len(appended_log.get("entries") or []) == len(entries) + 1, "Krea2 eval append did not add one entry")
|
|
appended_entries = krea2_eval_log.entries_for_variant("pov_ballsucking_low_head", path=temp_log_path)
|
|
_expect(appended_entries and appended_entries[-1].get("seed") == 9001, "Krea2 eval append did not persist temp entry")
|
|
duplicate_errors = krea2_eval_log.validate_entry(
|
|
smoke_entry,
|
|
existing_entries=appended_log.get("entries") or [],
|
|
catalog_keys=set(krea2_pose_variant_catalog.variant_keys()),
|
|
)
|
|
_expect(any("duplicate id" in error for error in duplicate_errors), "Krea2 eval validation should reject duplicate ids")
|
|
bad_variant = dict(smoke_entry)
|
|
bad_variant["id"] = "missing-variant-9001"
|
|
bad_variant["variant_key"] = "missing_variant"
|
|
bad_variant_errors = krea2_eval_log.validate_entry(
|
|
bad_variant,
|
|
existing_entries=appended_log.get("entries") or [],
|
|
catalog_keys=set(krea2_pose_variant_catalog.variant_keys()),
|
|
)
|
|
_expect(any("unknown variant" in error for error in bad_variant_errors), "Krea2 eval validation should reject unknown variants")
|
|
bad_generator_seed = dict(smoke_entry)
|
|
bad_generator_seed["id"] = "bad-generator-seed-9001"
|
|
bad_generator_seed["generator_seed"] = "4101"
|
|
bad_generator_seed_errors = krea2_eval_log.validate_entry(
|
|
bad_generator_seed,
|
|
existing_entries=appended_log.get("entries") or [],
|
|
catalog_keys=set(krea2_pose_variant_catalog.variant_keys()),
|
|
)
|
|
_expect(
|
|
any("generator_seed must be an integer" in error for error in bad_generator_seed_errors),
|
|
"Krea2 eval validation should reject non-integer generator_seed",
|
|
)
|
|
template = krea2_eval_log.entry_template(
|
|
"pov_footjob_frontal_sole_stroke",
|
|
seed=9102,
|
|
generator_seed=9104,
|
|
source="smoke",
|
|
date="2026-06-29",
|
|
)
|
|
_expect(template.get("variant_key") == "pov_footjob_frontal_sole_stroke", "Krea2 eval template lost variant key")
|
|
_expect(template.get("seed") == 9102, "Krea2 eval template lost fixed seed")
|
|
_expect(template.get("generator_seed") == 9104, "Krea2 eval template lost generator seed")
|
|
_expect(template.get("result") == "inconclusive", "Krea2 eval template should default to inconclusive")
|
|
_expect(template.get("decision") == "needs_more_tests", "Krea2 eval template should default to needs_more_tests")
|
|
_expect("footjob" in str(template.get("id") or ""), "Krea2 eval template id should include variant family")
|
|
template_errors = krea2_eval_log.validate_entry(
|
|
template,
|
|
existing_entries=appended_log.get("entries") or [],
|
|
catalog_keys=set(krea2_pose_variant_catalog.variant_keys()),
|
|
)
|
|
_expect(template_errors == [], f"Krea2 eval template should validate immediately: {template_errors}")
|
|
cli_result = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
str(ROOT / "tools" / "krea2_record_eval.py"),
|
|
"--print-template",
|
|
"--variant-key",
|
|
"pov_fingering_reclined_open_thighs",
|
|
"--seed",
|
|
"9103",
|
|
"--generator-seed",
|
|
"9105",
|
|
"--source",
|
|
"smoke",
|
|
"--date",
|
|
"2026-06-29",
|
|
],
|
|
cwd=str(ROOT),
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
_expect(cli_result.returncode == 0, f"Krea2 eval template CLI failed: {cli_result.stderr}")
|
|
cli_template = json.loads(cli_result.stdout)
|
|
_expect(cli_template.get("variant_key") == "pov_fingering_reclined_open_thighs", "Krea2 eval template CLI lost variant")
|
|
_expect(cli_template.get("seed") == 9103, "Krea2 eval template CLI lost seed")
|
|
_expect(cli_template.get("generator_seed") == 9105, "Krea2 eval template CLI lost generator seed")
|
|
|
|
|
|
def smoke_krea2_prompt_guide_policy() -> None:
|
|
guide = (ROOT / "docs" / "krea2-prompt-guide.md").read_text(encoding="utf-8")
|
|
_expect("## Climax / Ejaculation Wording" in guide, "Krea2 prompt guide lost climax wording section")
|
|
_expect("ejaculates semen" in guide, "Krea2 prompt guide lost explicit semen wording rule")
|
|
_expect("### Ballsucking / Testicle Sucking" in guide, "Krea2 prompt guide lost ballsucking section")
|
|
_expect("chest low over the viewer's pelvis" in guide, "Krea2 prompt guide lost low-body ballsucking cue")
|
|
_expect("balls fill the exact center" in guide, "Krea2 prompt guide lost balls-first scrotum framing rule")
|
|
_expect("shaft is cropped to the side" in guide, "Krea2 prompt guide lost side-cropped shaft rule")
|
|
_expect("Fresh-seed partial update: sampler seed `6262626262`" in guide, "Krea2 prompt guide lost ballsucking fresh-seed partial update")
|
|
_expect("Generated-route validation: turns `262` and `263`" in guide, "Krea2 prompt guide lost ballsucking generated-route validation update")
|
|
_expect("Fresh-seed target-object repeat: sampler seed `9797979797`" in guide, "Krea2 prompt guide lost ballsucking target-object repeat update")
|
|
_expect("scrotal skin is the nearest mouth surface" in guide and "turns `288` and `293`" in guide, "Krea2 prompt guide lost ballsucking scrotal-skin target evidence")
|
|
_expect("Generated-route validation: sampler seed `9898989898`" in guide, "Krea2 prompt guide lost ballsucking scrotal-skin generated-route validation")
|
|
_expect("turns `296` and `297`" in guide and "side-low cheek/thigh geometry" in guide, "Krea2 prompt guide lost ballsucking validation turn evidence")
|
|
_expect("Fresh-seed weak case: sampler seed `5959595959`" in guide, "Krea2 prompt guide lost ballsucking fresh-seed weak-case update")
|
|
_expect("lip-oval" in guide and "sideways mouth pocket" in guide and "chin-pelvis upward seal" in guide, "Krea2 prompt guide lost ballsucking weak-case wording axes")
|
|
_expect("Fresh-seed occlusion weak case: sampler seed `6060606060`" in guide, "Krea2 prompt guide lost ballsucking occlusion weak-case update")
|
|
_expect("scrotum foreground occlusion" in guide and "under-scrotum tongue shelf" in guide and "hand-guided scrotum" in guide, "Krea2 prompt guide lost ballsucking occlusion weak-case wording axes")
|
|
_expect("Fresh-seed mouth-axis mixed case: sampler seed `6161616161`" in guide, "Krea2 prompt guide lost ballsucking mouth-axis mixed-case update")
|
|
_expect("exact mouth-sucking" in guide and "single-testicle" in guide and "hanging balls below shaft" in guide, "Krea2 prompt guide lost ballsucking mouth-axis wording axes")
|
|
_expect("turns `331` and `337`" in guide and "turn `348`" in guide, "Krea2 prompt guide lost ballsucking mouth-axis turn evidence")
|
|
_expect("Fresh-seed pelvis-valley weak case: sampler seed `7171717171`" in guide, "Krea2 prompt guide lost ballsucking pelvis-valley weak-case update")
|
|
_expect("flat pelvis-valley" in guide and "thigh tunnel" in guide and "pelvis-edge target-first" in guide, "Krea2 prompt guide lost ballsucking pelvis-valley wording axes")
|
|
_expect("turns `350`, `356`, and `362`" in guide and "body-plane correction" in guide, "Krea2 prompt guide lost ballsucking pelvis-valley turn evidence")
|
|
_expect("Fresh-seed flat-target hybrid weak case: sampler seed `7272727272`" in guide, "Krea2 prompt guide lost ballsucking flat-target hybrid weak-case update")
|
|
_expect("flat-valley scrotal-skin target" in guide and "upper-frame shaft lower-scrotum" in guide and "side-low flat-valley hybrid" in guide, "Krea2 prompt guide lost ballsucking flat-target hybrid wording axes")
|
|
_expect("turns `368`, `374`, and `380`" in guide and "shaft-centered" in guide, "Krea2 prompt guide lost ballsucking flat-target hybrid turn evidence")
|
|
_expect("### Footjob" in guide, "Krea2 prompt guide lost footjob section")
|
|
_expect("large overlapping soles" in guide, "Krea2 prompt guide lost overlapping-sole footjob rule")
|
|
_expect("shaft trapped between the two soles" in guide, "Krea2 prompt guide lost trapped-shaft footjob rule")
|
|
_expect("glans rises between the compressed feet" in guide, "Krea2 prompt guide lost visible-glans footjob correction rule")
|
|
_expect("Fresh-seed promotion: sampler seed `7373737373`" in guide, "Krea2 prompt guide lost footjob fresh-seed promotion note")
|
|
_expect("### Fingering / Manual Stimulation" in guide, "Krea2 prompt guide lost fingering section")
|
|
_expect("single foreground hand is the largest lower-frame object" in guide, "Krea2 prompt guide lost hand-first fingering rule")
|
|
_expect("office-chair support and coworking depth fourth" in guide, "Krea2 prompt guide lost coworking-safe fingering hierarchy")
|
|
_expect("provisional improvement over baseline" in guide, "Krea2 prompt guide lost provisional fingering generator note")
|
|
_expect("### Wand Toy Contact" in guide, "Krea2 prompt guide lost wand section")
|
|
_expect("single continuous teal wand-style massager" in guide, "Krea2 prompt guide lost wand single-device rule")
|
|
_expect("Krea2 splitting the cue into a contact toy plus a second wand-like foreground object" in guide, "Krea2 prompt guide lost wand split-device failure mode")
|
|
_expect("### Ready / Post-Ejaculation Open-Thigh Display" in guide, "Krea2 prompt guide lost ready aftermath section")
|
|
_expect("the wet aftermath detail is the exact center" in guide, "Krea2 prompt guide lost ready wet-aftermath hierarchy")
|
|
_expect("category-exit rule" in guide, "Krea2 prompt guide lost ready provisional generator note")
|
|
_expect("### Spread / Open-Thigh Presentation" in guide, "Krea2 prompt guide lost spread section")
|
|
_expect("thighs form a broad V-frame" in guide, "Krea2 prompt guide lost spread V-frame rule")
|
|
_expect("hands hold the knees or thighs" in guide, "Krea2 prompt guide lost spread hand-anchor rule")
|
|
_expect("### Blowjob Top View / Overhead Vertical Shaft" in guide, "Krea2 prompt guide lost blowjob top-view section")
|
|
_expect("nadir-angle standing male POV top-view oral position" in guide, "Krea2 prompt guide lost top-view nadir-angle rule")
|
|
_expect("nearby carpet/floor plane dominates the image" in guide, "Krea2 prompt guide lost top-view floor-plane rule")
|
|
_expect("shaft appears as a short centered vertical column" in guide, "Krea2 prompt guide lost top-view vertical-column rule")
|
|
_expect("hair crown, forehead, shoulders, hands, and knees are visible from above" in guide, "Krea2 prompt guide lost top-view from-above body-surface rule")
|
|
_expect("plumb-line" in guide and "literal drawn artifacts" in guide, "Krea2 prompt guide lost top-view literalization warning")
|
|
_expect("### Blowjob Side Profile / Side-Phone Weak Case" in guide, "Krea2 prompt guide lost blowjob side weak-case section")
|
|
_expect("side-phone" in guide, "Krea2 prompt guide lost side-phone oral weak-case wording")
|
|
_expect("not valid POV evidence" in guide, "Krea2 prompt guide lost side-profile non-POV boundary")
|
|
_expect("visible adult male chest, navel, abdomen hair" in guide, "Krea2 prompt guide lost side-profile male-body ownership cue")
|
|
_expect("accepted single-source evidence" in guide, "Krea2 prompt guide lost side-profile single-source evidence boundary")
|
|
_expect("Krea2 transferred the body-axis cue" in guide, "Krea2 prompt guide lost side-profile body-axis transfer failure")
|
|
_expect("lateral_edge_entry" in guide and "male viewer's abdomen" in guide and "near thigh create a broad horizontal foreground body" in guide, "Krea2 prompt guide lost side-profile lateral-edge matrix update")
|
|
_expect("turn `206`" in guide and "mouth-to-shaft contact is the nearest facial detail" in guide, "Krea2 prompt guide lost side-profile generated-route contact update")
|
|
_expect("Fresh-seed weak case: sampler seed `8484848484`" in guide, "Krea2 prompt guide lost side-profile fresh-seed weak-case update")
|
|
_expect("Fresh-seed lower-right torso repeat: sampler seed `9595959595`" in guide, "Krea2 prompt guide lost side-profile lower-right torso repeat")
|
|
_expect("turns `279` and `283`" in guide and "adult male viewer's own torso starts at the lower edge" in guide, "Krea2 prompt guide lost side-profile lower-right self-torso evidence")
|
|
_expect("Generated-route validation: sampler seed `9696969696`" in guide, "Krea2 prompt guide lost side-profile generated-route validation")
|
|
_expect("turns `284` and `285`" in guide and "lower-right own-body foreground" in guide, "Krea2 prompt guide lost side-profile generated-route validation turn evidence")
|
|
_expect("Fresh-seed promotion: sampler seed `5858585858`" in guide, "Krea2 prompt guide lost side-profile fresh-seed promotion")
|
|
_expect("turns `298`, `301`, and `304`" in guide and "side-camera-style self-body crop" in guide, "Krea2 prompt guide lost side-profile three-woman promotion evidence")
|
|
_expect("### Blowjob Laying Frontal / Wide V-Frame" in guide, "Krea2 prompt guide lost blowjob laying frontal section")
|
|
_expect("wide symmetrical V-frame" in guide, "Krea2 prompt guide lost blowjob laying V-frame rule")
|
|
_expect("torso stretched low and horizontal" in guide, "Krea2 prompt guide lost blowjob laying low-horizontal rule")
|
|
_expect("centered mouth-to-shaft contact" in guide, "Krea2 prompt guide lost blowjob laying centered-contact rule")
|
|
_expect("### Blowjob Sitting Upright / Low Mouth Contact" in guide, "Krea2 prompt guide lost blowjob sitting section")
|
|
_expect("face lowered close to the exact center contact point" in guide, "Krea2 prompt guide lost blowjob sitting low-face rule")
|
|
_expect("open mouth covers the tip at the centerline" in guide, "Krea2 prompt guide lost blowjob sitting mouth-tip rule")
|
|
_expect("both hands stay low at the base directly below her mouth" in guide, "Krea2 prompt guide lost blowjob sitting hand-base rule")
|
|
_expect("### Missionary Open-Leg / Seated-Lounge Drift" in guide, "Krea2 prompt guide lost missionary weak-case section")
|
|
_expect("Follow-up prompt-axis probes on the same sampler seed" in guide, "Krea2 prompt guide lost missionary prompt-axis follow-up")
|
|
_expect("pose-first probes omitted the original subject/look" in guide and "block" in guide, "Krea2 prompt guide lost missionary prompt-order caveat")
|
|
_expect("Patch only the raised-edge or" in guide and "edge-supported route" in guide, "Krea2 prompt guide lost missionary edge-supported patch boundary")
|
|
_expect("keep generic" in guide and "valid angled missionary views" in guide, "Krea2 prompt guide lost generic missionary validity boundary")
|
|
_expect("office-lounge support words" in guide, "Krea2 prompt guide lost missionary lounge-drift warning")
|
|
_expect("flat elevated-support atlas" in guide, "Krea2 prompt guide lost flat elevated-support axis")
|
|
_expect("### Cowgirl Frontal / Wide Thigh Bridge" in guide, "Krea2 prompt guide lost cowgirl wide-thigh section")
|
|
_expect("Matrix update: sampler seed `2828282828`" in guide, "Krea2 prompt guide lost cowgirl repeat matrix update")
|
|
_expect("Mirror the wide-thigh bridge hierarchy into the normal cowgirl" in guide, "Krea2 prompt guide lost cowgirl provisional generator note")
|
|
_expect("Generated-route validation: turn `216`" in guide, "Krea2 prompt guide lost cowgirl generated-route validation note")
|
|
_expect("Fresh-seed promotion: sampler seed `9191919191`" in guide, "Krea2 prompt guide lost cowgirl fresh-seed promotion note")
|
|
_expect("## Evidence Promotion Threshold" in guide, "Krea2 prompt guide lost evidence promotion threshold")
|
|
_expect("Do not promote a single-character, single-location prompt hack" in guide, "Krea2 prompt guide lost single-scene overfit guard")
|
|
_expect("Scene repair words must match the selected location" in guide, "Krea2 prompt guide lost location-consistency guard")
|
|
methodology = (ROOT / "docs" / "krea2-ab-methodology.md").read_text(encoding="utf-8")
|
|
_expect("2026-06-30-generated-route-validation-positive-channel-cleanup" in methodology, "Krea2 A/B methodology memory lost current method version")
|
|
_expect("Update it whenever the testing method improves" in methodology, "Krea2 A/B methodology memory lost update contract")
|
|
_expect("Use location-compatible anchors only" in methodology, "Krea2 A/B methodology memory lost anchor rule")
|
|
_expect("## Generator Mirroring" in methodology, "Krea2 A/B methodology memory lost generator mirroring section")
|
|
_expect("final formatter output" in methodology, "Krea2 A/B methodology memory lost formatter-output regression rule")
|
|
_expect("## Generator-Patch Evidence Matrix" in methodology, "Krea2 A/B methodology memory lost generator-patch matrix")
|
|
_expect("at least three distinct source cases" in methodology, "Krea2 A/B methodology memory lost source-case threshold")
|
|
_expect("at least two sampler seeds" in methodology, "Krea2 A/B methodology memory lost sampler-seed threshold")
|
|
_expect("provisional_generator_patch" in methodology, "Krea2 A/B methodology memory lost provisional generator rule")
|
|
_expect("same-seed evidence shows it improves over baseline" in methodology, "Krea2 A/B methodology memory lost category-exit progress rule")
|
|
_expect("two source subjects improved on the same sampler seed" in methodology, "Krea2 A/B methodology memory lost spread two-source update")
|
|
_expect("baseline is already usable" in methodology, "Krea2 A/B methodology memory lost narrow-improvement update")
|
|
_expect("attractive side-camera result" in methodology, "Krea2 A/B methodology memory lost non-target-viewpoint weak-case rule")
|
|
_expect(
|
|
"/media/p5/miniforge3/bin/python tools/sxcp_mcp_client.py call-tool comfy_push" in methodology,
|
|
"Krea2 A/B methodology memory lost approved MCP push helper command",
|
|
)
|
|
_expect("source `46`" in methodology and "improved with explicit adult-male foreground ownership" in methodology, "Krea2 A/B methodology memory lost side-profile ownership update")
|
|
_expect("source `47`" in methodology and "rejected a related `body-axis` cue" in methodology, "Krea2 A/B methodology memory lost body-axis rejection update")
|
|
_expect("9753197531" in methodology and "lateral-edge body-line axis" in methodology, "Krea2 A/B methodology memory lost side-profile lateral-edge update")
|
|
_expect("turn `206`" in methodology and "mouth-to-shaft-contact priority" in methodology, "Krea2 A/B methodology memory lost side-profile generated-route contact update")
|
|
_expect("9595959595" in methodology and "lower-right torso anchor" in methodology, "Krea2 A/B methodology memory lost side-profile lower-right anchor update")
|
|
_expect("9696969696" in methodology and "generated-route validation" in methodology, "Krea2 A/B methodology memory lost side-profile generated-route validation update")
|
|
_expect("5858585858" in methodology and "three-woman generated-route repeat" in methodology, "Krea2 A/B methodology memory lost side-profile fresh-seed promotion update")
|
|
_expect("5959595959" in methodology and "lip-oval" in methodology and "shaft/glans collapse" in methodology, "Krea2 A/B methodology memory lost ballsucking fresh weak-case update")
|
|
_expect("6060606060" in methodology and "foreground occlusion" in methodology and "hand-guided" in methodology, "Krea2 A/B methodology memory lost ballsucking occlusion weak-case update")
|
|
_expect("6161616161" in methodology and "exact mouth-sucking" in methodology and "generated-route controls" in methodology, "Krea2 A/B methodology memory lost ballsucking mouth-axis mixed-case update")
|
|
_expect("7171717171" in methodology and "flat pelvis-valley" in methodology and "body-plane correction" in methodology, "Krea2 A/B methodology memory lost ballsucking pelvis-valley weak-case update")
|
|
_expect("7272727272" in methodology and "flat-valley scrotal-skin" in methodology and "shaft-centered" in methodology, "Krea2 A/B methodology memory lost ballsucking flat-target hybrid weak-case update")
|
|
_expect("2828282828" in methodology and "wide-thigh bridge axis" in methodology, "Krea2 A/B methodology memory lost cowgirl provisional patch update")
|
|
_expect("blowjob laying frontal" in methodology, "Krea2 A/B methodology memory lost blowjob laying update")
|
|
_expect("wide V-frame and low-horizontal torso" in methodology, "Krea2 A/B methodology memory lost blowjob laying hierarchy update")
|
|
_expect("blowjob sitting upright" in methodology, "Krea2 A/B methodology memory lost blowjob sitting update")
|
|
_expect("low-mouth seated hierarchy" in methodology, "Krea2 A/B methodology memory lost blowjob sitting hierarchy update")
|
|
_expect("Corrected blowjob top-view criteria" in methodology, "Krea2 A/B methodology memory lost top-view correction update")
|
|
_expect("Refined blowjob top-view prompt-axis search" in methodology, "Krea2 A/B methodology memory lost top-view axis-search update")
|
|
_expect("Avoid `plumb-line` and `map` in generator prompts" in methodology, "Krea2 A/B methodology memory lost top-view literalization warning")
|
|
_expect("batched prompt-probe" in methodology and "loop before analysis-heavy iteration" in methodology, "Krea2 A/B methodology memory lost batched prompt-probe loop")
|
|
_expect("pull only until each new" in methodology and "`sxcp_eval_in` turn and image path exists" in methodology, "Krea2 A/B methodology memory lost image-presence probe rule")
|
|
_expect("Preserve prompt-order controls" in methodology, "Krea2 A/B methodology memory lost prompt-order control rule")
|
|
_expect("subject/look description first" in methodology, "Krea2 A/B methodology memory lost subject-look-first rule")
|
|
_expect("geometry-only probes" in methodology, "Krea2 A/B methodology memory lost geometry-only probe caveat")
|
|
_expect("vertical shaft alignment alone" in methodology, "Krea2 A/B methodology memory lost vertical-shaft insufficiency rule")
|
|
_expect(
|
|
"Never put negative-conditioning phrases inside the positive Krea2 prompt" in guide,
|
|
"Krea2 prompt guide lost positive-only conditioning rule",
|
|
)
|
|
_expect(
|
|
"no active negative-output contract" in guide,
|
|
"Krea2 prompt guide lost no-negative-channel workflow rule",
|
|
)
|
|
_expect("## Stronger-Control / Low-Priority Cases" in guide, "Krea2 prompt guide lost stronger-control section")
|
|
_expect("pov_sixty_nine_close_reversed_oral" in guide, "Krea2 prompt guide lost sixty-nine unstable route")
|
|
_expect("hardest" in guide and "low-priority" in guide, "Krea2 prompt guide lost hardest low-priority wording")
|
|
_expect("not a normal prompt-only fixed-seed candidate" in guide, "Krea2 prompt guide should not queue sixty-nine as normal prompt tuning")
|
|
_expect("pose/control image" in guide or "image-guided" in guide, "Krea2 prompt guide lost control-first guidance")
|
|
|
|
|
|
def smoke_krea2_tuning_report_policy() -> None:
|
|
rows = krea2_tuning_report.coverage_rows()
|
|
catalog_keys = krea2_pose_variant_catalog.variant_keys()
|
|
_expect([row.get("key") for row in rows] == catalog_keys, "Krea2 tuning report row order should follow catalog order")
|
|
by_key = {row.get("key"): row for row in rows}
|
|
boobjob = by_key.get("pov_boobjob_upright_cleavage") or {}
|
|
_expect(boobjob.get("coverage_state") == "proven_with_evidence", "Boobjob report should be proven with evidence")
|
|
_expect(boobjob.get("accepted_evidence_count", 0) >= 1, "Boobjob report lost accepted evidence count")
|
|
boobjob_latest = boobjob.get("latest_evidence") or {}
|
|
_expect(boobjob_latest.get("id") == "boobjob-7302-upright-cleavage", "Boobjob report lost latest evidence id")
|
|
_expect(boobjob_latest.get("seed") == 7302, "Boobjob report lost latest fixed seed")
|
|
_expect(boobjob_latest.get("result") == "accepted", "Boobjob report lost latest evidence result")
|
|
_expect(boobjob_latest.get("decision") == "generator_patch", "Boobjob report lost latest evidence decision")
|
|
_expect("upright frontal boobjob geometry" in str(boobjob_latest.get("candidate_prompt_summary") or ""), "Boobjob report lost latest candidate summary")
|
|
ballsucking = by_key.get("pov_ballsucking_low_head") or {}
|
|
_expect(ballsucking.get("coverage_state") == "tracked", "Ballsucking report should track accepted prompt-guide evidence without proving the candidate")
|
|
_expect(ballsucking.get("accepted_evidence_count") == 6, "Ballsucking report lost fresh generated-route validation evidence")
|
|
_expect(ballsucking.get("total_evidence_count") == 14, "Ballsucking report lost fresh flat-target hybrid weak-case history")
|
|
ballsucking_latest = ballsucking.get("latest_evidence") or {}
|
|
_expect(
|
|
ballsucking_latest.get("id") == "ballsucking-7272727272-flat-target-hybrid-weak-case",
|
|
"Ballsucking report lost latest fresh flat-target hybrid weak-case evidence id",
|
|
)
|
|
_expect(ballsucking_latest.get("seed") == 7272727272, "Ballsucking report lost fresh flat-target hybrid weak-case sampler seed evidence")
|
|
_expect(ballsucking_latest.get("result") == "inconclusive", "Ballsucking latest weak-case evidence should be inconclusive")
|
|
_expect(ballsucking_latest.get("decision") == "needs_more_tests", "Ballsucking latest weak-case evidence should keep route queued")
|
|
_expect(
|
|
"flat-valley scrotal-skin" in str(ballsucking_latest.get("candidate_prompt_summary") or "") and "side-low flat-valley" in str(ballsucking_latest.get("candidate_prompt_summary") or ""),
|
|
"Ballsucking report lost fresh flat-target hybrid wording axes",
|
|
)
|
|
_expect(
|
|
"turns 367-384" in str(ballsucking_latest.get("observation") or "") and "shaft-centered" in str(ballsucking_latest.get("observation") or ""),
|
|
"Ballsucking report lost fresh flat-target hybrid weak-case turn observation",
|
|
)
|
|
ballsucking_latest_accepted = ballsucking.get("latest_accepted_evidence") or {}
|
|
_expect(
|
|
ballsucking_latest_accepted.get("id") == "ballsucking-9898989898-scrotal-skin-route-validation",
|
|
"Ballsucking report lost latest accepted validation evidence id",
|
|
)
|
|
footjob = by_key.get("pov_footjob_frontal_sole_stroke") or {}
|
|
_expect(footjob.get("coverage_state") == "proven_with_evidence", "Footjob report should be proven after fresh-seed generated-route repeat evidence")
|
|
_expect(footjob.get("accepted_evidence_count") == 5, "Footjob report lost fresh-seed proven evidence")
|
|
_expect(footjob.get("total_evidence_count") == 5, "Footjob report lost accepted A/B history")
|
|
footjob_latest = footjob.get("latest_evidence") or {}
|
|
_expect(
|
|
footjob_latest.get("id") == "footjob-7373737373-generated-route-repeat-proven",
|
|
"Footjob report lost latest fresh-seed proven evidence id",
|
|
)
|
|
_expect(footjob_latest.get("seed") == 7373737373, "Footjob report lost fresh sampler seed evidence")
|
|
_expect(footjob_latest.get("result") == "accepted", "Footjob report lost accepted evidence result")
|
|
_expect(footjob_latest.get("decision") == "proven_with_evidence", "Footjob report lost proven evidence decision")
|
|
_expect(
|
|
"narrow visible strip of shaft and glans" in str(footjob_latest.get("candidate_prompt_summary") or ""),
|
|
"Footjob report lost narrow-strip visible-contact correction rule",
|
|
)
|
|
fingering = by_key.get("pov_fingering_reclined_open_thighs") or {}
|
|
_expect(fingering.get("coverage_state") == "tracked", "Fingering report should track accepted prompt-guide evidence without proving the candidate")
|
|
_expect(fingering.get("accepted_evidence_count") == 2, "Fingering report lost repeated prompt-guide evidence")
|
|
_expect(fingering.get("total_evidence_count") == 5, "Fingering report should retain inconclusive/prompt-guide/generator/matrix/weak-case A/B history")
|
|
fingering_latest = fingering.get("latest_evidence") or {}
|
|
_expect(
|
|
fingering_latest.get("id") == "fingering-1357913579-source52-own-hand-weak-case",
|
|
"Fingering report lost latest source-52 weak-case evidence id",
|
|
)
|
|
_expect(fingering_latest.get("seed") == 1357913579, "Fingering report lost source-52 sampler seed evidence")
|
|
_expect(fingering_latest.get("result") == "inconclusive", "Fingering source-52 weak case should stay inconclusive")
|
|
_expect(fingering_latest.get("decision") == "needs_more_tests", "Fingering source-52 weak case should need more tests")
|
|
_expect(
|
|
"changed hand ownership" in str(fingering_latest.get("observation") or ""),
|
|
"Fingering report lost hand-ownership weak-case note",
|
|
)
|
|
fingering_latest_accepted = fingering.get("latest_accepted_evidence") or {}
|
|
_expect(
|
|
fingering_latest_accepted.get("id") == "fingering-987654321-source50-office-chair-hand-hierarchy",
|
|
"Fingering report lost latest accepted prompt-guide evidence id",
|
|
)
|
|
_expect(
|
|
fingering_latest_accepted.get("decision") == "provisional_generator_patch",
|
|
"Fingering latest accepted evidence should be a provisional generator patch",
|
|
)
|
|
wand = by_key.get("pov_wand_foreground_tool_contact") or {}
|
|
_expect(wand.get("coverage_state") == "proven_with_evidence", "Wand report should be proven after repeated generated-route evidence")
|
|
_expect(wand.get("accepted_evidence_count") == 3, "Wand report lost accepted evidence")
|
|
_expect(wand.get("total_evidence_count") == 3, "Wand report lost accepted A/B history")
|
|
wand_latest = wand.get("latest_evidence") or {}
|
|
_expect(
|
|
wand_latest.get("id") == "wand-7979797979-two-woman-teal-repeat-proven",
|
|
"Wand report lost latest accepted evidence id",
|
|
)
|
|
_expect(wand_latest.get("seed") == 7979797979, "Wand report lost sampler seed evidence")
|
|
_expect(wand_latest.get("decision") == "generator_patch", "Wand latest evidence should finalize the generator default")
|
|
_expect(
|
|
"white upper-left branch" in str(wand_latest.get("candidate_prompt_summary") or ""),
|
|
"Wand report lost upper-left alternate evidence",
|
|
)
|
|
ready = by_key.get("pov_ejaculation_aftermath_open_thigh_candidate") or {}
|
|
_expect(ready.get("coverage_state") == "tracked", "Ready aftermath report should track first accepted evidence")
|
|
_expect(ready.get("accepted_evidence_count") == 1, "Ready aftermath report lost first accepted evidence")
|
|
_expect(ready.get("total_evidence_count") == 1, "Ready aftermath report should have one recorded A/B result")
|
|
ready_latest = ready.get("latest_evidence") or {}
|
|
_expect(
|
|
ready_latest.get("id") == "ready-1123581321-source52-wet-aftermath-hierarchy",
|
|
"Ready aftermath report lost latest accepted evidence id",
|
|
)
|
|
_expect(ready_latest.get("seed") == 1123581321, "Ready aftermath report lost sampler seed evidence")
|
|
_expect(ready_latest.get("decision") == "provisional_generator_patch", "Ready first evidence should be a provisional generator patch")
|
|
_expect(
|
|
"wet aftermath hierarchy" in str(ready_latest.get("candidate_prompt_summary") or ""),
|
|
"Ready aftermath report lost wet hierarchy evidence",
|
|
)
|
|
sixty_nine = by_key.get("pov_sixty_nine_close_reversed_oral") or {}
|
|
_expect(sixty_nine.get("coverage_state") == "needs_stronger_control", "Sixty-nine report should require stronger control")
|
|
_expect(sixty_nine.get("accepted_evidence_count") == 0, "Sixty-nine report should not have accepted evidence yet")
|
|
_expect(sixty_nine.get("difficulty") == "hardest", "Sixty-nine report lost hardest-route marker")
|
|
_expect(sixty_nine.get("priority") == "low", "Sixty-nine report lost low-priority marker")
|
|
_expect(
|
|
sixty_nine.get("control_requirement") == "pose_or_image_guidance_first",
|
|
"Sixty-nine report lost control-first marker",
|
|
)
|
|
spread = by_key.get("pov_spread_open_thigh_presentation") or {}
|
|
_expect(spread.get("coverage_state") == "tracked", "Spread report should track provisional generator evidence")
|
|
_expect(spread.get("accepted_evidence_count") == 1, "Spread report lost first accepted evidence")
|
|
_expect(spread.get("total_evidence_count") == 1, "Spread report should have one recorded A/B result")
|
|
spread_latest = spread.get("latest_evidence") or {}
|
|
_expect(
|
|
spread_latest.get("id") == "spread-3141592653-source50-47-raised-knee-v-frame",
|
|
"Spread report lost latest accepted evidence id",
|
|
)
|
|
_expect(spread_latest.get("seed") == 3141592653, "Spread report lost sampler seed evidence")
|
|
_expect(spread_latest.get("decision") == "provisional_generator_patch", "Spread first evidence should be a provisional generator patch")
|
|
_expect(
|
|
"atlas spread hierarchy" in str(spread_latest.get("candidate_prompt_summary") or ""),
|
|
"Spread report lost atlas hierarchy evidence",
|
|
)
|
|
oral_top = by_key.get("pov_blowjob_top_down_vertical_shaft") or {}
|
|
_expect(oral_top.get("coverage_state") == "tracked", "Blowjob top-view report should track provisional generator evidence")
|
|
_expect(oral_top.get("accepted_evidence_count") == 2, "Blowjob top-view report lost accepted correction evidence")
|
|
_expect(oral_top.get("total_evidence_count") == 3, "Blowjob top-view report should have three recorded A/B results")
|
|
oral_top_latest = oral_top.get("latest_evidence") or {}
|
|
_expect(
|
|
oral_top_latest.get("id") == "blowjob-top-4242424242-turn67-70-nadir-floor-plane-axis",
|
|
"Blowjob top-view report lost latest nadir-angle correction evidence id",
|
|
)
|
|
_expect(oral_top_latest.get("seed") == 4242424242, "Blowjob top-view report lost sampler seed evidence")
|
|
_expect(
|
|
oral_top_latest.get("decision") == "provisional_generator_patch",
|
|
"Blowjob top-view first evidence should be a provisional generator patch",
|
|
)
|
|
_expect(
|
|
"nadir-angle" in str(oral_top_latest.get("candidate_prompt_summary") or ""),
|
|
"Blowjob top-view report lost nadir-angle hierarchy evidence",
|
|
)
|
|
oral_side = by_key.get("pov_blowjob_side_profile_oral") or {}
|
|
_expect(oral_side.get("coverage_state") == "proven_with_evidence", "Blowjob side report should be proven after three-woman generated-route repeat evidence")
|
|
_expect(oral_side.get("accepted_evidence_count") == 6, "Blowjob side report should count male-body-axis, lateral-edge, generated-route, lower-right torso, validation, and promotion evidence")
|
|
_expect(oral_side.get("total_evidence_count") == 8, "Blowjob side report should retain fresh weak-case and promotion evidence")
|
|
oral_side_latest = oral_side.get("latest_evidence") or {}
|
|
_expect(
|
|
oral_side_latest.get("id") == "blowjob-side-5858585858-three-woman-generated-route-proven",
|
|
"Blowjob side report lost latest three-woman generated-route evidence id",
|
|
)
|
|
_expect(oral_side_latest.get("seed") == 5858585858, "Blowjob side report lost three-woman generated-route sampler seed evidence")
|
|
_expect(oral_side_latest.get("result") == "accepted", "Blowjob side three-woman generated-route evidence should be accepted")
|
|
_expect(oral_side_latest.get("decision") == "proven_with_evidence", "Blowjob side three-woman generated-route evidence should promote the route")
|
|
_expect(oral_side_latest.get("needs_expansion") is False, "Blowjob side three-woman generated-route evidence should leave the expansion queue")
|
|
_expect(
|
|
"three-woman generated-route repeat" in str(oral_side_latest.get("observation") or ""),
|
|
"Blowjob side report lost three-woman generated-route observation",
|
|
)
|
|
_expect(
|
|
"turns 298, 301, and 304" in str(oral_side_latest.get("observation") or ""),
|
|
"Blowjob side report lost three-woman generated-route turn evidence",
|
|
)
|
|
oral_laying = by_key.get("pov_blowjob_laying_frontal_oral") or {}
|
|
_expect(oral_laying.get("coverage_state") == "tracked", "Blowjob laying report should track provisional generator evidence")
|
|
_expect(oral_laying.get("accepted_evidence_count") == 1, "Blowjob laying report lost first accepted evidence")
|
|
_expect(oral_laying.get("total_evidence_count") == 1, "Blowjob laying report should have one recorded A/B result")
|
|
oral_laying_latest = oral_laying.get("latest_evidence") or {}
|
|
_expect(
|
|
oral_laying_latest.get("id") == "blowjob-laying-6767676767-source46-50-wide-v-frame",
|
|
"Blowjob laying report lost latest wide-V evidence id",
|
|
)
|
|
_expect(oral_laying_latest.get("seed") == 6767676767, "Blowjob laying report lost sampler seed evidence")
|
|
_expect(oral_laying_latest.get("decision") == "provisional_generator_patch", "Blowjob laying evidence should be provisional generator patch")
|
|
_expect(
|
|
"wide V-frame and low-horizontal torso hierarchy" in str(oral_laying_latest.get("observation") or ""),
|
|
"Blowjob laying report lost low-horizontal hierarchy observation",
|
|
)
|
|
oral_sitting = by_key.get("pov_blowjob_sitting_upright_oral") or {}
|
|
_expect(oral_sitting.get("coverage_state") == "tracked", "Blowjob sitting report should track provisional generator evidence")
|
|
_expect(oral_sitting.get("accepted_evidence_count") == 1, "Blowjob sitting report lost first accepted evidence")
|
|
_expect(oral_sitting.get("total_evidence_count") == 1, "Blowjob sitting report should have one recorded A/B result")
|
|
oral_sitting_latest = oral_sitting.get("latest_evidence") or {}
|
|
_expect(
|
|
oral_sitting_latest.get("id") == "blowjob-sitting-7878787878-source46-50-low-mouth-contact",
|
|
"Blowjob sitting report lost latest low-mouth evidence id",
|
|
)
|
|
_expect(oral_sitting_latest.get("seed") == 7878787878, "Blowjob sitting report lost sampler seed evidence")
|
|
_expect(oral_sitting_latest.get("decision") == "provisional_generator_patch", "Blowjob sitting evidence should be provisional generator patch")
|
|
_expect(
|
|
"low-mouth hierarchy" in str(oral_sitting_latest.get("observation") or ""),
|
|
"Blowjob sitting report lost low-mouth hierarchy observation",
|
|
)
|
|
missionary = by_key.get("pov_missionary_open_leg_penetration") or {}
|
|
_expect(missionary.get("coverage_state") == "tracked", "Missionary report should track accepted elevated-edge evidence")
|
|
_expect(missionary.get("accepted_evidence_count") == 1, "Missionary report lost accepted elevated-edge evidence")
|
|
_expect(missionary.get("total_evidence_count") == 4, "Missionary report should retain mixed, prompt-axis, subject-first, and elevated-edge evidence entries")
|
|
missionary_latest = missionary.get("latest_evidence") or {}
|
|
_expect(
|
|
missionary_latest.get("id") == "missionary-open-8989898989-turn81-84-elevated-edge-support",
|
|
"Missionary report lost latest elevated-edge support evidence id",
|
|
)
|
|
_expect(missionary_latest.get("result") == "accepted", "Missionary latest evidence should be accepted")
|
|
_expect(missionary_latest.get("decision") == "provisional_generator_patch", "Missionary latest evidence should be a provisional generator patch")
|
|
missionary_latest_text = f"{missionary_latest.get('candidate_prompt_summary') or ''} {missionary_latest.get('observation') or ''}"
|
|
_expect("elevated support" in missionary_latest_text and "keep generic missionary" in missionary_latest_text, "Missionary latest evidence lost elevated-support/generic-route boundary")
|
|
missionary_folded = by_key.get("pov_missionary_folded_high_leg_penetration") or {}
|
|
_expect(missionary_folded.get("coverage_state") == "tracked", "Folded missionary report should track accepted contact-first evidence")
|
|
_expect(missionary_folded.get("accepted_evidence_count") == 1, "Folded missionary report lost accepted contact-first evidence")
|
|
_expect(missionary_folded.get("total_evidence_count") == 1, "Folded missionary report should have one durable evidence entry")
|
|
missionary_folded_latest = missionary_folded.get("latest_evidence") or {}
|
|
_expect(
|
|
missionary_folded_latest.get("id") == "missionary-folded-8989898989-turn85-92-contact-first-knee-block",
|
|
"Folded missionary report lost latest contact-first evidence id",
|
|
)
|
|
_expect(missionary_folded_latest.get("decision") == "provisional_generator_patch", "Folded missionary latest evidence should be a provisional generator patch")
|
|
cowgirl = by_key.get("pov_cowgirl_frontal_straddle_penetration") or {}
|
|
_expect(cowgirl.get("coverage_state") == "proven_with_evidence", "Cowgirl report should be proven after fresh-seed wide-thigh evidence")
|
|
_expect(cowgirl.get("accepted_evidence_count") == 4, "Cowgirl report lost fresh-seed accepted wide-thigh evidence")
|
|
_expect(cowgirl.get("total_evidence_count") == 4, "Cowgirl report should have four durable evidence entries")
|
|
cowgirl_latest = cowgirl.get("latest_evidence") or {}
|
|
_expect(
|
|
cowgirl_latest.get("id") == "cowgirl-frontal-9191919191-fresh-seed-wide-thigh-proven",
|
|
"Cowgirl report lost latest fresh-seed wide-thigh evidence id",
|
|
)
|
|
_expect(cowgirl_latest.get("decision") == "proven_with_evidence", "Cowgirl latest evidence should promote the route")
|
|
_expect(cowgirl_latest.get("needs_expansion") is False, "Cowgirl proven evidence should leave the expansion queue")
|
|
_expect(
|
|
"Fresh seed" in str(cowgirl_latest.get("observation") or ""),
|
|
"Cowgirl latest evidence lost fresh-seed observation",
|
|
)
|
|
cowgirl_alt = by_key.get("pov_cowgirl_alt_low_squat_penetration") or {}
|
|
_expect(cowgirl_alt.get("coverage_state") == "tracked", "Cowgirl alt report should track accepted flat-supine evidence")
|
|
_expect(cowgirl_alt.get("accepted_evidence_count") == 1, "Cowgirl alt report lost accepted flat-supine evidence")
|
|
_expect(cowgirl_alt.get("total_evidence_count") == 1, "Cowgirl alt report should have one durable evidence entry")
|
|
cowgirl_alt_latest = cowgirl_alt.get("latest_evidence") or {}
|
|
_expect(
|
|
cowgirl_alt_latest.get("id") == "cowgirl-alt-8989898989-turn97-104-flat-supine-low-angle",
|
|
"Cowgirl alt report lost latest flat-supine evidence id",
|
|
)
|
|
_expect(cowgirl_alt_latest.get("decision") == "provisional_generator_patch", "Cowgirl alt latest evidence should be a provisional generator patch")
|
|
reverse_cowgirl = by_key.get("pov_reverse_cowgirl_back_facing_penetration") or {}
|
|
_expect(reverse_cowgirl.get("coverage_state") == "tracked", "Reverse cowgirl report should track accepted close-back evidence")
|
|
_expect(reverse_cowgirl.get("accepted_evidence_count") == 1, "Reverse cowgirl report lost accepted close-back evidence")
|
|
_expect(reverse_cowgirl.get("total_evidence_count") == 1, "Reverse cowgirl report should have one durable evidence entry")
|
|
reverse_cowgirl_latest = reverse_cowgirl.get("latest_evidence") or {}
|
|
_expect(
|
|
reverse_cowgirl_latest.get("id") == "reverse-cowgirl-8989898989-turn105-108-close-back-hip-dominant",
|
|
"Reverse cowgirl report lost latest close-back evidence id",
|
|
)
|
|
_expect(
|
|
reverse_cowgirl_latest.get("decision") == "provisional_generator_patch",
|
|
"Reverse cowgirl latest evidence should be a provisional generator patch",
|
|
)
|
|
reverse_cowgirl_alt = by_key.get("pov_reverse_cowgirl_alt_upright_back_facing_penetration") or {}
|
|
_expect(reverse_cowgirl_alt.get("coverage_state") == "tracked", "Reverse cowgirl alt report should track accepted upright evidence")
|
|
_expect(reverse_cowgirl_alt.get("accepted_evidence_count") == 1, "Reverse cowgirl alt report lost accepted upright evidence")
|
|
_expect(reverse_cowgirl_alt.get("total_evidence_count") == 1, "Reverse cowgirl alt report should have one durable evidence entry")
|
|
reverse_cowgirl_alt_latest = reverse_cowgirl_alt.get("latest_evidence") or {}
|
|
_expect(
|
|
reverse_cowgirl_alt_latest.get("id") == "reverse-cowgirl-alt-8989898989-turn109-112-upright-hands-on-hips",
|
|
"Reverse cowgirl alt report lost latest upright evidence id",
|
|
)
|
|
_expect(
|
|
reverse_cowgirl_alt_latest.get("decision") == "provisional_generator_patch",
|
|
"Reverse cowgirl alt latest evidence should be a provisional generator patch",
|
|
)
|
|
summary = krea2_tuning_report.coverage_summary()
|
|
_expect(summary.get("status_counts", {}).get("proven") == 7, "Krea2 tuning report proven count changed")
|
|
_expect(summary.get("status_counts", {}).get("candidate") == 12, "Krea2 tuning report candidate count changed")
|
|
_expect(summary.get("status_counts", {}).get("unstable") == 1, "Krea2 tuning report unstable count changed")
|
|
_expect(
|
|
summary.get("variants_without_accepted_evidence") == [
|
|
"pov_sixty_nine_close_reversed_oral",
|
|
],
|
|
f"Krea2 tuning report missing-evidence set changed: {summary.get('variants_without_accepted_evidence')}",
|
|
)
|
|
_expect(
|
|
summary.get("stronger_control_cases") == ["pov_sixty_nine_close_reversed_oral"],
|
|
f"Krea2 tuning report stronger-control set changed: {summary.get('stronger_control_cases')}",
|
|
)
|
|
expansion_plans = krea2_tuning_report.guide_expansion_plans()
|
|
_expect(
|
|
[plan.get("key") for plan in expansion_plans]
|
|
== [
|
|
"pov_ballsucking_low_head",
|
|
],
|
|
f"Krea2 guide expansion queue changed: {[plan.get('key') for plan in expansion_plans]}",
|
|
)
|
|
ballsucking_expansion = expansion_plans[0]
|
|
_expect(
|
|
ballsucking_expansion.get("latest_accepted_decision") == "provisional_generator_patch",
|
|
"Ballsucking expansion plan should identify the provisional generator evidence",
|
|
)
|
|
_expect(
|
|
ballsucking_expansion.get("target") == "multi_seed_multi_woman_matrix",
|
|
"Guide expansion plan should target multi-seed/multi-woman evidence",
|
|
)
|
|
_expect(
|
|
"pov_cowgirl_frontal_straddle_penetration" not in [plan.get("key") for plan in expansion_plans],
|
|
"Cowgirl should leave the expansion queue after fresh-seed promotion",
|
|
)
|
|
_expect(
|
|
"pov_blowjob_side_profile_oral" not in [plan.get("key") for plan in expansion_plans],
|
|
"Blowjob side should leave the expansion queue after fresh-seed three-woman promotion",
|
|
)
|
|
plans = krea2_tuning_report.next_test_plans()
|
|
_expect(plans == [], f"Krea2 tuning report should have no normal next-test plans after reverse-alt evidence: {plans}")
|
|
_expect(
|
|
"pov_sixty_nine_close_reversed_oral" not in [plan.get("key") for plan in plans],
|
|
"Unstable sixty-nine route should not be queued as a normal fixed-seed candidate",
|
|
)
|
|
template_commands = krea2_tuning_report.next_eval_template_commands(seed_token="$SEED")
|
|
_expect(template_commands == [], f"Krea2 eval template commands should be empty when no normal next-test plans remain: {template_commands}")
|
|
_expect(
|
|
[command.get("key") for command in template_commands]
|
|
== [plan.get("key") for plan in plans],
|
|
"Krea2 eval template commands should follow normal next-test candidates",
|
|
)
|
|
_expect(
|
|
"pov_sixty_nine_close_reversed_oral" not in " ".join(str(command.get("command") or "") for command in template_commands),
|
|
"Krea2 eval template commands should exclude low-priority stronger-control routes",
|
|
)
|
|
_expect([plan.get("key") for plan in plans] == [], "Krea2 tuning report next plans changed")
|
|
plan_by_key = {plan.get("key"): plan for plan in plans}
|
|
_expect(
|
|
"pov_ballsucking_low_head" not in plan_by_key,
|
|
"Ballsucking should leave the normal next-test queue after accepted prompt-guide evidence",
|
|
)
|
|
_expect(
|
|
"pov_footjob_frontal_sole_stroke" not in plan_by_key,
|
|
"Footjob should leave the normal next-test queue after accepted prompt-guide evidence",
|
|
)
|
|
_expect(
|
|
"pov_fingering_reclined_open_thighs" not in plan_by_key,
|
|
"Fingering should leave the normal next-test queue after accepted prompt-guide evidence",
|
|
)
|
|
_expect(
|
|
"pov_wand_foreground_tool_contact" not in plan_by_key,
|
|
"Wand should leave the normal next-test queue after accepted prompt-guide evidence",
|
|
)
|
|
_expect(
|
|
"pov_ejaculation_aftermath_open_thigh_candidate" not in plan_by_key,
|
|
"Ready aftermath should leave the normal next-test queue after provisional generator evidence",
|
|
)
|
|
_expect(
|
|
"pov_spread_open_thigh_presentation" not in plan_by_key,
|
|
"Spread should leave the normal next-test queue after provisional generator evidence",
|
|
)
|
|
_expect(
|
|
"pov_blowjob_top_down_vertical_shaft" not in plan_by_key,
|
|
"Blowjob top-view should leave the normal next-test queue after provisional generator evidence",
|
|
)
|
|
_expect(
|
|
"pov_blowjob_side_profile_oral" not in plan_by_key,
|
|
"Blowjob side-profile should leave the normal next-test queue after accepted fragile evidence",
|
|
)
|
|
_expect(
|
|
"pov_blowjob_laying_frontal_oral" not in plan_by_key,
|
|
"Blowjob laying should leave the normal next-test queue after provisional generator evidence",
|
|
)
|
|
_expect(
|
|
"pov_blowjob_sitting_upright_oral" not in plan_by_key,
|
|
"Blowjob sitting should leave the normal next-test queue after provisional generator evidence",
|
|
)
|
|
_expect(
|
|
"pov_missionary_open_leg_penetration" not in plan_by_key,
|
|
"Missionary should leave the normal next-test queue after accepted elevated-edge evidence",
|
|
)
|
|
_expect(
|
|
"pov_missionary_folded_high_leg_penetration" not in plan_by_key,
|
|
"Folded missionary should leave the normal next-test queue after accepted contact-first evidence",
|
|
)
|
|
_expect(
|
|
"pov_cowgirl_frontal_straddle_penetration" not in plan_by_key,
|
|
"Cowgirl should leave the normal next-test queue after accepted wide-thigh evidence",
|
|
)
|
|
_expect(
|
|
"pov_cowgirl_alt_low_squat_penetration" not in plan_by_key,
|
|
"Cowgirl alt should leave the normal next-test queue after accepted flat-supine evidence",
|
|
)
|
|
_expect(
|
|
"pov_reverse_cowgirl_back_facing_penetration" not in plan_by_key,
|
|
"Reverse cowgirl should leave the normal next-test queue after accepted close-back evidence",
|
|
)
|
|
_expect(
|
|
"pov_reverse_cowgirl_alt_upright_back_facing_penetration" not in plan_by_key,
|
|
"Reverse cowgirl alt should leave the normal next-test queue after accepted upright evidence",
|
|
)
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
atlas_root = Path(tmpdir)
|
|
for folder in ("doggy", "doggy_control", "custom_pose", "custom_pose_control", "ready", "ready_control", "69", "69_control", "bg", "woman", "doggy_bg"):
|
|
folder_path = atlas_root / folder
|
|
folder_path.mkdir()
|
|
(folder_path / f"{folder}_sample.png").write_bytes(b"")
|
|
(atlas_root / "custom_pose" / "custom_pose_b.png").write_bytes(b"")
|
|
(atlas_root / "custom_pose_control" / "custom_pose_control_b.png").write_bytes(b"")
|
|
(atlas_root / "ready" / "ready_b.png").write_bytes(b"")
|
|
(atlas_root / "ready_control" / "ready_control_b.png").write_bytes(b"")
|
|
(atlas_root / "69" / "69_b.png").write_bytes(b"")
|
|
(atlas_root / "69_control" / "69_control_b.png").write_bytes(b"")
|
|
atlas_rows = krea2_tuning_report.atlas_folder_rows(atlas_root=atlas_root)
|
|
atlas_by_folder = {row.get("folder"): row for row in atlas_rows}
|
|
_expect(atlas_by_folder.get("doggy", {}).get("mapped") is True, "Atlas report should mark catalog folders as mapped")
|
|
_expect(atlas_by_folder.get("custom_pose", {}).get("mapped") is False, "Atlas report should expose unmapped pose folders")
|
|
_expect(atlas_by_folder.get("ready", {}).get("mapped") is True, "Atlas report should mark ready as mapped once cataloged")
|
|
_expect(atlas_by_folder.get("69", {}).get("mapped") is True, "Atlas report should mark sixty-nine as mapped once cataloged")
|
|
_expect("doggy_control" not in atlas_by_folder, "Atlas report should exclude control folders")
|
|
_expect("doggy_bg" not in atlas_by_folder, "Atlas report should exclude background folders")
|
|
_expect("bg" not in atlas_by_folder, "Atlas report should exclude shared bg folder")
|
|
_expect("woman" not in atlas_by_folder, "Atlas report should exclude non-pose woman folder")
|
|
atlas_summary = krea2_tuning_report.atlas_coverage_summary(atlas_root=atlas_root)
|
|
_expect(atlas_summary.get("pose_folder_count") == 4, "Atlas report should count only pose folders")
|
|
_expect(atlas_summary.get("mapped_folder_count") == 3, "Atlas report should count mapped pose folders")
|
|
_expect(atlas_summary.get("unmapped_folders") == ["custom_pose"], "Atlas report should identify unmapped pose folders")
|
|
gap_plans = krea2_tuning_report.atlas_gap_plans(atlas_root=atlas_root, sample_limit=2)
|
|
_expect([plan.get("folder") for plan in gap_plans] == ["custom_pose"], "Atlas gap plans should follow unmapped folders")
|
|
gap_by_folder = {plan.get("folder"): plan for plan in gap_plans}
|
|
custom_gap = gap_by_folder["custom_pose"]
|
|
_expect(custom_gap.get("suggested_variant_key") == "pov_custom_pose_candidate", "Atlas gap plan should suggest stable variant key")
|
|
_expect(len(custom_gap.get("sample_images") or []) == 2, "Atlas gap plan should include deterministic sample images")
|
|
_expect(len(custom_gap.get("control_images") or []) == 2, "Atlas gap plan should include deterministic control images")
|
|
_expect(
|
|
krea2_tuning_report._suggested_variant_key("ready") == "pov_ejaculation_aftermath_open_thigh_candidate",
|
|
"Atlas gap plan should not treat ready as a neutral setup pose",
|
|
)
|
|
atlas_markdown = krea2_tuning_report.markdown_report(atlas_root=atlas_root)
|
|
_expect("Atlas Folder Coverage" in atlas_markdown, "Krea2 tuning report markdown lost atlas coverage section")
|
|
_expect("custom_pose" in atlas_markdown, "Krea2 tuning report markdown lost unmapped atlas folder")
|
|
_expect("pov_custom_pose_candidate" in atlas_markdown, "Krea2 tuning report markdown lost suggested gap key")
|
|
markdown = krea2_tuning_report.markdown_report()
|
|
_expect("## Latest Evidence" in markdown, "Krea2 tuning report markdown lost latest evidence section")
|
|
_expect("boobjob-7302-upright-cleavage" in markdown, "Krea2 tuning report markdown lost boobjob evidence id")
|
|
_expect("seed 7302" in markdown, "Krea2 tuning report markdown lost evidence seed")
|
|
_expect("generator_patch" in markdown, "Krea2 tuning report markdown lost evidence decision")
|
|
_expect("upright frontal boobjob geometry" in markdown, "Krea2 tuning report markdown lost evidence prompt summary")
|
|
_expect("ballsucking-7272727272-flat-target-hybrid-weak-case" in markdown, "Krea2 tuning report markdown lost ballsucking fresh flat-target hybrid weak-case evidence id")
|
|
_expect("flat-valley scrotal-skin" in markdown and "shaft-centered" in markdown, "Krea2 tuning report markdown lost ballsucking fresh flat-target hybrid weak-case evidence")
|
|
_expect("footjob-7373737373-generated-route-repeat-proven" in markdown, "Krea2 tuning report markdown lost footjob fresh-seed proven evidence id")
|
|
_expect("two large overlapping soles" in markdown, "Krea2 tuning report markdown lost overlapping-sole footjob evidence")
|
|
_expect("narrow visible strip of shaft and glans" in markdown, "Krea2 tuning report markdown lost visible-strip footjob evidence")
|
|
_expect("fingering-1357913579-source52-own-hand-weak-case" in markdown, "Krea2 tuning report markdown lost fingering source-52 weak-case evidence id")
|
|
_expect("changed hand ownership" in markdown, "Krea2 tuning report markdown lost fingering hand-ownership weak-case evidence")
|
|
_expect("provisional_generator_patch" in markdown, "Krea2 tuning report markdown lost provisional generator decision")
|
|
_expect("wand-7979797979-two-woman-teal-repeat-proven" in markdown, "Krea2 tuning report markdown lost wand evidence id")
|
|
_expect("single continuous teal wand" in markdown, "Krea2 tuning report markdown lost wand single-device evidence")
|
|
_expect("ready-1123581321-source52-wet-aftermath-hierarchy" in markdown, "Krea2 tuning report markdown lost ready aftermath evidence id")
|
|
_expect("wet aftermath hierarchy" in markdown, "Krea2 tuning report markdown lost ready wet hierarchy evidence")
|
|
_expect("spread-3141592653-source50-47-raised-knee-v-frame" in markdown, "Krea2 tuning report markdown lost spread evidence id")
|
|
_expect("atlas spread hierarchy" in markdown, "Krea2 tuning report markdown lost spread atlas hierarchy evidence")
|
|
_expect("blowjob-top-4242424242-turn67-70-nadir-floor-plane-axis" in markdown, "Krea2 tuning report markdown lost blowjob top-view nadir evidence id")
|
|
_expect("nadir-angle" in markdown, "Krea2 tuning report markdown lost blowjob top-view nadir evidence")
|
|
_expect("blowjob-side-5858585858-three-woman-generated-route-proven" in markdown, "Krea2 tuning report markdown lost blowjob side three-woman generated-route evidence id")
|
|
_expect("adult male viewer's own torso starts at the lower edge" in markdown, "Krea2 tuning report markdown lost side-profile self-torso ownership wording")
|
|
_expect("three-woman generated-route repeat" in markdown, "Krea2 tuning report markdown lost side-profile promotion observation")
|
|
_expect("lower-right torso anchor" in markdown, "Krea2 tuning report markdown lost side-profile lower-right torso wording")
|
|
_expect("blowjob-laying-6767676767-source46-50-wide-v-frame" in markdown, "Krea2 tuning report markdown lost blowjob laying evidence id")
|
|
_expect("wide V-frame and low-horizontal torso hierarchy" in markdown, "Krea2 tuning report markdown lost blowjob laying hierarchy wording")
|
|
_expect("blowjob-sitting-7878787878-source46-50-low-mouth-contact" in markdown, "Krea2 tuning report markdown lost blowjob sitting evidence id")
|
|
_expect("low-mouth seated hierarchy" in markdown, "Krea2 tuning report markdown lost blowjob sitting hierarchy wording")
|
|
_expect("missionary-open-8989898989-turn81-84-elevated-edge-support" in markdown, "Krea2 tuning report markdown lost missionary elevated-edge evidence id")
|
|
_expect("flat elevated-support" in markdown, "Krea2 tuning report markdown lost missionary elevated-support evidence")
|
|
_expect("## Stronger Control Cases" in markdown, "Krea2 tuning report markdown lost stronger-control section")
|
|
_expect("hardest" in markdown, "Krea2 tuning report markdown lost hardest-route marker")
|
|
_expect("low priority" in markdown, "Krea2 tuning report markdown lost low-priority marker")
|
|
_expect("pose_or_image_guidance_first" in markdown, "Krea2 tuning report markdown lost control-first marker")
|
|
_expect("## Guide/Fragile Evidence Expansion" in markdown, "Krea2 tuning report markdown lost guide expansion section")
|
|
_expect("multi_seed_multi_woman_matrix" in markdown, "Krea2 tuning report markdown lost expansion target")
|
|
_expect("## Eval Entry Template Commands" not in markdown, "Krea2 tuning report should omit eval template commands when no normal next tests remain")
|
|
_expect(
|
|
"python tools/krea2_record_eval.py --print-template" not in markdown,
|
|
"Krea2 tuning report should omit recorder template commands when no normal next tests remain",
|
|
)
|
|
_expect("--seed <fixed_seed>" not in markdown, "Krea2 tuning report should omit fixed-seed placeholder when no normal next tests remain")
|
|
_expect("pov_ballsucking_low_head" in markdown, "Krea2 tuning report markdown lost candidate variant")
|
|
_expect("pov_footjob_frontal_sole_stroke" in markdown, "Krea2 tuning report markdown lost footjob candidate variant")
|
|
_expect("pov_wand_foreground_tool_contact" in markdown, "Krea2 tuning report markdown lost wand candidate variant")
|
|
_expect("pov_ejaculation_aftermath_open_thigh_candidate" in markdown, "Krea2 tuning report markdown lost ready aftermath candidate variant")
|
|
_expect("pov_sixty_nine_close_reversed_oral" in markdown, "Krea2 tuning report markdown lost sixty-nine unstable variant")
|
|
_expect("needs_stronger_control" in markdown, "Krea2 tuning report markdown lost stronger-control coverage state")
|
|
_expect("pov_spread_open_thigh_presentation" in markdown, "Krea2 tuning report markdown lost spread candidate variant")
|
|
_expect("pov_blowjob_top_down_vertical_shaft" in markdown, "Krea2 tuning report markdown lost blowjob top-view candidate variant")
|
|
_expect("pov_blowjob_side_profile_oral" in markdown, "Krea2 tuning report markdown lost blowjob side candidate variant")
|
|
_expect("pov_blowjob_laying_frontal_oral" in markdown, "Krea2 tuning report markdown lost blowjob laying candidate variant")
|
|
_expect("pov_blowjob_sitting_upright_oral" in markdown, "Krea2 tuning report markdown lost blowjob sitting candidate variant")
|
|
_expect("pov_missionary_open_leg_penetration" in markdown, "Krea2 tuning report markdown lost missionary candidate variant")
|
|
cli_result = subprocess.run(
|
|
[sys.executable, str(ROOT / "krea2_tuning_report.py")],
|
|
cwd=ROOT,
|
|
capture_output=True,
|
|
text=True,
|
|
check=True,
|
|
)
|
|
_expect("# Krea2 Pose Variant Coverage" in cli_result.stdout, "Krea2 tuning report CLI should print markdown coverage")
|
|
_expect("## Next Fixed-Seed Tests" not in cli_result.stdout, "Krea2 tuning report CLI should omit next fixed-seed tests when none remain")
|
|
_expect("pov_missionary_folded_high_leg_penetration" in markdown, "Krea2 tuning report markdown lost folded missionary candidate variant")
|
|
_expect("pov_cowgirl_frontal_straddle_penetration" in markdown, "Krea2 tuning report markdown lost cowgirl candidate variant")
|
|
_expect("cowgirl-frontal-9191919191-fresh-seed-wide-thigh-proven" in markdown, "Krea2 tuning report markdown lost cowgirl fresh-seed promotion evidence id")
|
|
_expect("pov_cowgirl_alt_low_squat_penetration" in markdown, "Krea2 tuning report markdown lost cowgirl alt candidate variant")
|
|
_expect("pov_reverse_cowgirl_back_facing_penetration" in markdown, "Krea2 tuning report markdown lost reverse cowgirl candidate variant")
|
|
_expect("pov_reverse_cowgirl_alt_upright_back_facing_penetration" in markdown, "Krea2 tuning report markdown lost reverse cowgirl alt candidate variant")
|
|
_expect("needs_fixed_seed_tests" not in markdown, "Krea2 tuning report should not list normal fixed-seed gaps after reverse-alt evidence")
|
|
_expect("Prompt cues" not in markdown, "Krea2 tuning report should omit next-test cue section when no normal tests remain")
|
|
_expect("Avoid cues" not in markdown, "Krea2 tuning report should omit next-test avoid section when no normal tests remain")
|
|
|
|
|
|
def smoke_krea2_atlas_refine_manifest_policy() -> None:
|
|
import importlib
|
|
|
|
manifest_module = importlib.import_module("krea2_atlas_refine_manifest")
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
root = Path(tmpdir)
|
|
known_prompt = root / "pov_footjob_frontal_sole_stroke_00001_.txt"
|
|
known_image = root / "pov_footjob_frontal_sole_stroke_00001_.png"
|
|
known_sidecar = root / "pov_footjob_frontal_sole_stroke_00001_.json"
|
|
baseline_only_prompt = root / "pov_handjob_upright_centered_00001_.txt"
|
|
baseline_only_image = root / "pov_handjob_upright_centered_00001_.png"
|
|
unknown_prompt = root / "pov_unknown_pose_candidate_00001_.txt"
|
|
unknown_image = root / "pov_unknown_pose_candidate_00001_.png"
|
|
orphan_prompt = root / "pov_blowjob_side_profile_oral_00002_.txt"
|
|
known_prompt.write_text(
|
|
"A controlled same-subject footjob reference prompt with foreground soles.",
|
|
encoding="utf-8",
|
|
)
|
|
known_image.write_bytes(b"fake-png")
|
|
known_sidecar.write_text(
|
|
json.dumps(
|
|
{
|
|
"seed_metadata": {
|
|
"sampler_seed": 101,
|
|
"atlas_cue_seed": 202,
|
|
"micro_position_seed": 303,
|
|
"workspace_seed": 404,
|
|
},
|
|
"cue_axes": {
|
|
"foot_position": "soles_more_forward",
|
|
"workspace_surface": "floor_between_desks",
|
|
},
|
|
"score": {
|
|
"atlas_pose_match": "partial",
|
|
"workspace_continuity": "pass",
|
|
"subject_identity": "pass",
|
|
},
|
|
"prompt_variants": [
|
|
{
|
|
"id": "soles_more_forward",
|
|
"append_cues": [
|
|
"the woman's soles press farther forward along the same contact line"
|
|
],
|
|
"reference_images": [
|
|
"blowjob_top_view/22_blowjob_top_view.png"
|
|
],
|
|
"cue_axes": {
|
|
"foot_position": "soles_more_forward",
|
|
"contact_depth": "contact_line_farther_forward",
|
|
},
|
|
"seed_metadata": {
|
|
"atlas_cue_seed": 202,
|
|
"micro_position_seed": 303,
|
|
},
|
|
"notes": "explicit sidecar cue, not invented by the batch builder",
|
|
}
|
|
],
|
|
"notes": "same-subject seedable foot placement frame",
|
|
},
|
|
ensure_ascii=True,
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
baseline_only_prompt.write_text(
|
|
"A controlled same-subject handjob reference prompt awaiting prompt variants.",
|
|
encoding="utf-8",
|
|
)
|
|
baseline_only_image.write_bytes(b"fake-png")
|
|
unknown_prompt.write_text("A controlled same-subject unknown pose reference prompt.", encoding="utf-8")
|
|
unknown_image.write_bytes(b"fake-png")
|
|
orphan_prompt.write_text("A prompt without a matching image should be reported.", encoding="utf-8")
|
|
|
|
manifest = manifest_module.build_manifest(root, subject_id="same_woman_001")
|
|
with tempfile.TemporaryDirectory() as duplicate_tmpdir:
|
|
duplicate_root = Path(duplicate_tmpdir)
|
|
duplicate_prompt = duplicate_root / "pov_footjob_frontal_sole_stroke_00001_.txt"
|
|
duplicate_image = duplicate_root / "pov_footjob_frontal_sole_stroke_00001_.png"
|
|
duplicate_sidecar = duplicate_root / "pov_footjob_frontal_sole_stroke_00001_.json"
|
|
duplicate_prompt.write_text(
|
|
"A controlled same-subject footjob reference prompt with foreground soles.",
|
|
encoding="utf-8",
|
|
)
|
|
duplicate_image.write_bytes(b"fake-png")
|
|
duplicate_sidecar.write_text(
|
|
json.dumps(
|
|
{
|
|
"seed_metadata": {"sampler_seed": 101},
|
|
"prompt_variants": [
|
|
{
|
|
"id": "duplicate_axis",
|
|
"append_cues": ["first duplicate cue"],
|
|
},
|
|
{
|
|
"id": "duplicate_axis",
|
|
"append_cues": ["second duplicate cue"],
|
|
},
|
|
],
|
|
},
|
|
ensure_ascii=True,
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
try:
|
|
manifest_module.build_manifest(duplicate_root, subject_id="same_woman_001")
|
|
except ValueError as exc:
|
|
_expect(
|
|
"duplicate_axis" in str(exc) and "duplicated" in str(exc),
|
|
f"Atlas refine manifest duplicate prompt-variant error should identify the duplicated id: {exc}",
|
|
)
|
|
else:
|
|
raise AssertionError("Atlas refine manifest should reject duplicate sidecar prompt_variant ids")
|
|
with tempfile.TemporaryDirectory() as source_mismatch_tmpdir:
|
|
source_mismatch_root = Path(source_mismatch_tmpdir)
|
|
source_mismatch_prompt = source_mismatch_root / "pov_footjob_frontal_sole_stroke_00001_.txt"
|
|
source_mismatch_image = source_mismatch_root / "pov_footjob_frontal_sole_stroke_00001_.png"
|
|
source_mismatch_sidecar = source_mismatch_root / "pov_footjob_frontal_sole_stroke_00001_.json"
|
|
source_mismatch_prompt.write_text(
|
|
"A controlled same-subject footjob reference prompt with foreground soles.",
|
|
encoding="utf-8",
|
|
)
|
|
source_mismatch_image.write_bytes(b"fake-png")
|
|
source_mismatch_sidecar.write_text(
|
|
json.dumps(
|
|
{
|
|
"seed_metadata": {"sampler_seed": 101},
|
|
"prompt_variants": [
|
|
{
|
|
"id": "soles_more_forward",
|
|
"append_cues": [
|
|
"the woman's soles press farther forward along the same contact line"
|
|
],
|
|
"prompt_source": {
|
|
"kind": "append_cues",
|
|
"prompt_variant_id": "other_axis",
|
|
"append_cues": [
|
|
"the woman's soles press farther forward along the same contact line"
|
|
],
|
|
},
|
|
},
|
|
],
|
|
},
|
|
ensure_ascii=True,
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
try:
|
|
manifest_module.build_manifest(source_mismatch_root, subject_id="same_woman_001")
|
|
except ValueError as exc:
|
|
_expect(
|
|
"prompt_source.prompt_variant_id" in str(exc)
|
|
and "soles_more_forward" in str(exc)
|
|
and "other_axis" in str(exc),
|
|
f"Atlas refine manifest prompt-source mismatch error should name both ids: {exc}",
|
|
)
|
|
else:
|
|
raise AssertionError("Atlas refine manifest should reject mismatched prompt_source prompt_variant_id")
|
|
with tempfile.TemporaryDirectory() as missing_reference_tmpdir:
|
|
missing_reference_root = Path(missing_reference_tmpdir)
|
|
missing_reference_prompt = missing_reference_root / "pov_footjob_frontal_sole_stroke_00001_.txt"
|
|
missing_reference_image = missing_reference_root / "pov_footjob_frontal_sole_stroke_00001_.png"
|
|
missing_reference_sidecar = missing_reference_root / "pov_footjob_frontal_sole_stroke_00001_.json"
|
|
missing_reference_prompt.write_text(
|
|
"A controlled same-subject footjob reference prompt with foreground soles.",
|
|
encoding="utf-8",
|
|
)
|
|
missing_reference_image.write_bytes(b"fake-png")
|
|
missing_reference_sidecar.write_text(
|
|
json.dumps(
|
|
{
|
|
"seed_metadata": {"sampler_seed": 101},
|
|
"prompt_variants": [
|
|
{
|
|
"id": "missing_atlas_reference",
|
|
"append_cues": [
|
|
"the woman's soles press farther forward along the same contact line"
|
|
],
|
|
"reference_images": [
|
|
"blowjob_top_view/does_not_exist.png"
|
|
],
|
|
}
|
|
],
|
|
},
|
|
ensure_ascii=True,
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
try:
|
|
manifest_module.build_manifest(missing_reference_root, subject_id="same_woman_001")
|
|
except ValueError as exc:
|
|
_expect(
|
|
"reference_images" in str(exc) and "does_not_exist.png" in str(exc),
|
|
f"Atlas refine manifest missing reference-image error should identify the stale atlas path: {exc}",
|
|
)
|
|
else:
|
|
raise AssertionError("Atlas refine manifest should reject missing atlas reference_images when atlas root exists")
|
|
cli_result = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
str(ROOT / "tools" / "krea2_atlas_refine_manifest.py"),
|
|
"--folder",
|
|
str(root),
|
|
"--subject-id",
|
|
"same_woman_001",
|
|
],
|
|
cwd=ROOT,
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
cli_stdout = cli_result.stdout
|
|
_expect(cli_result.returncode == 0, f"Atlas refine manifest CLI failed: {cli_result.stderr}")
|
|
cli_manifest = json.loads(cli_stdout)
|
|
explicit_manifest_cli_result = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
str(ROOT / "tools" / "krea2_atlas_refine_manifest.py"),
|
|
"--folder",
|
|
str(root),
|
|
"--subject-id",
|
|
"same_woman_001",
|
|
"--print-manifest",
|
|
],
|
|
cwd=ROOT,
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
_expect(
|
|
explicit_manifest_cli_result.returncode == 0,
|
|
f"Atlas refine explicit manifest CLI failed: {explicit_manifest_cli_result.stderr}",
|
|
)
|
|
explicit_cli_manifest = json.loads(explicit_manifest_cli_result.stdout)
|
|
_expect(
|
|
explicit_cli_manifest == cli_manifest,
|
|
"Atlas refine --print-manifest should match the default manifest CLI output",
|
|
)
|
|
scaffold_cli_result = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
str(ROOT / "tools" / "krea2_atlas_refine_manifest.py"),
|
|
"--folder",
|
|
str(root),
|
|
"--subject-id",
|
|
"same_woman_001",
|
|
"--print-sidecar-scaffold",
|
|
"--variant-key",
|
|
"pov_handjob_upright_centered",
|
|
],
|
|
cwd=ROOT,
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
scaffold_cli_returncode = scaffold_cli_result.returncode
|
|
scaffold_cli_stdout = scaffold_cli_result.stdout
|
|
scaffold_cli_stderr = scaffold_cli_result.stderr
|
|
baseline_score_cli_result = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
str(ROOT / "tools" / "krea2_atlas_refine_manifest.py"),
|
|
"--folder",
|
|
str(root),
|
|
"--subject-id",
|
|
"same_woman_001",
|
|
"--print-baseline-score-sheet",
|
|
"--variant-key",
|
|
"pov_handjob_upright_centered",
|
|
],
|
|
cwd=ROOT,
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
baseline_score_cli_returncode = baseline_score_cli_result.returncode
|
|
baseline_score_cli_stdout = baseline_score_cli_result.stdout
|
|
baseline_score_cli_stderr = baseline_score_cli_result.stderr
|
|
prompt_noise_cli_result = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
str(ROOT / "tools" / "krea2_atlas_refine_manifest.py"),
|
|
"--folder",
|
|
str(root),
|
|
"--subject-id",
|
|
"same_woman_001",
|
|
"--print-prompt-noise-report",
|
|
],
|
|
cwd=ROOT,
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
prompt_noise_cli_returncode = prompt_noise_cli_result.returncode
|
|
prompt_noise_cli_stdout = prompt_noise_cli_result.stdout
|
|
prompt_noise_cli_stderr = prompt_noise_cli_result.stderr
|
|
prompt_cleanup_cli_result = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
str(ROOT / "tools" / "krea2_atlas_refine_manifest.py"),
|
|
"--folder",
|
|
str(root),
|
|
"--subject-id",
|
|
"same_woman_001",
|
|
"--print-prompt-cleanup-sheet",
|
|
],
|
|
cwd=ROOT,
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
prompt_cleanup_cli_returncode = prompt_cleanup_cli_result.returncode
|
|
prompt_cleanup_cli_stdout = prompt_cleanup_cli_result.stdout
|
|
prompt_cleanup_cli_stderr = prompt_cleanup_cli_result.stderr
|
|
|
|
_expect(manifest.get("schema") == "sxcp_krea2_atlas_refine_manifest_v1", "Atlas refine manifest lost schema")
|
|
_expect(manifest.get("subject_id") == "same_woman_001", "Atlas refine manifest lost subject id")
|
|
_expect(manifest.get("entry_count") == 3, f"Atlas refine manifest should include paired prompt/image entries: {manifest}")
|
|
_expect(manifest.get("missing_pair_count") == 1, "Atlas refine manifest should report orphan prompt/image files")
|
|
_expect(manifest.get("unknown_variant_count") == 1, "Atlas refine manifest should count unknown variant keys")
|
|
entries = manifest.get("entries") or []
|
|
by_variant = {entry.get("variant_key"): entry for entry in entries}
|
|
footjob = by_variant.get("pov_footjob_frontal_sole_stroke") or {}
|
|
_expect(footjob.get("known_variant") is True, "Atlas refine manifest should validate known variant keys")
|
|
_expect(footjob.get("prompt_text", "").startswith("A controlled same-subject footjob"), "Atlas refine manifest lost prompt text")
|
|
_expect(footjob.get("prompt_sha256"), "Atlas refine manifest should include a prompt hash for drift checks")
|
|
_expect(footjob.get("image_size_bytes") == len(b"fake-png"), "Atlas refine manifest should record image byte size")
|
|
seed_metadata = footjob.get("seed_metadata") or {}
|
|
_expect(seed_metadata.get("sampler_seed") == 101, "Atlas refine manifest should merge sampler seed metadata")
|
|
_expect(seed_metadata.get("atlas_cue_seed") == 202, "Atlas refine manifest should merge atlas cue seed metadata")
|
|
_expect(seed_metadata.get("micro_position_seed") == 303, "Atlas refine manifest should merge micro-position seed metadata")
|
|
_expect(seed_metadata.get("workspace_seed") == 404, "Atlas refine manifest should merge workspace seed metadata")
|
|
_expect(seed_metadata.get("generator_seed") is None, "Atlas refine manifest should keep missing generator seed explicit")
|
|
cue_axes = footjob.get("cue_axes") or {}
|
|
for key in (
|
|
"contact_depth",
|
|
"hand_position",
|
|
"foot_position",
|
|
"body_angle",
|
|
"camera_height",
|
|
"workspace_surface",
|
|
"clothing_visibility",
|
|
"expression_eye_detail",
|
|
"anatomy_shape_detail",
|
|
):
|
|
_expect(key in cue_axes, f"Atlas refine manifest missing cue-axis slot {key}")
|
|
_expect(cue_axes.get("foot_position") == "soles_more_forward", "Atlas refine manifest should merge cue-axis metadata")
|
|
_expect(cue_axes.get("workspace_surface") == "floor_between_desks", "Atlas refine manifest should merge workspace cue axis")
|
|
score = footjob.get("score") or {}
|
|
for key in (
|
|
"atlas_pose_match",
|
|
"contact_match",
|
|
"pose_ownership",
|
|
"workspace_continuity",
|
|
"clothing_visibility",
|
|
"subject_identity",
|
|
"expression_eye_control",
|
|
"anatomy_proportion",
|
|
"prompt_noise",
|
|
):
|
|
_expect(key in score, f"Atlas refine manifest missing score slot {key}")
|
|
_expect(score.get("atlas_pose_match") == "partial", "Atlas refine manifest should merge atlas pose score")
|
|
_expect(score.get("workspace_continuity") == "pass", "Atlas refine manifest should merge workspace continuity score")
|
|
_expect(score.get("subject_identity") == "pass", "Atlas refine manifest should merge subject identity score")
|
|
_expect(footjob.get("notes") == "same-subject seedable foot placement frame", "Atlas refine manifest should merge notes")
|
|
prompt_variants = footjob.get("prompt_variants") or []
|
|
_expect(len(prompt_variants) == 1, f"Atlas refine manifest should keep sidecar prompt variants: {prompt_variants}")
|
|
_expect(prompt_variants[0].get("id") == "soles_more_forward", "Atlas refine manifest lost prompt variant id")
|
|
_expect(
|
|
prompt_variants[0].get("reference_images") == ["blowjob_top_view/22_blowjob_top_view.png"],
|
|
"Atlas refine manifest should keep nearest atlas reference-image provenance on prompt variants",
|
|
)
|
|
baseline_score_sheet = manifest_module.build_baseline_score_sheet(manifest)
|
|
_expect(
|
|
baseline_score_sheet.get("schema") == "sxcp_atlas_refine_baseline_score_sheet_v1",
|
|
"Atlas refine baseline score sheet lost schema",
|
|
)
|
|
_expect(
|
|
baseline_score_sheet.get("entry_count") == 3,
|
|
"Atlas refine baseline score sheet should include every paired manifest entry",
|
|
)
|
|
_expect(
|
|
baseline_score_sheet.get("unscored_count") == 2,
|
|
"Atlas refine baseline score sheet should count unscored baselines",
|
|
)
|
|
_expect(
|
|
baseline_score_sheet.get("partially_scored_count") == 1,
|
|
"Atlas refine baseline score sheet should count partially scored baselines separately",
|
|
)
|
|
baseline_score_entries = {
|
|
entry.get("variant_key"): entry for entry in baseline_score_sheet.get("entries") or []
|
|
}
|
|
footjob_baseline_score = baseline_score_entries.get("pov_footjob_frontal_sole_stroke") or {}
|
|
_expect(
|
|
footjob_baseline_score.get("score", {}).get("atlas_pose_match") == "partial",
|
|
"Atlas refine baseline score sheet should preserve existing sidecar baseline scores",
|
|
)
|
|
_expect(
|
|
footjob_baseline_score.get("score_state") == "partially_scored",
|
|
"Atlas refine baseline score sheet should flag incomplete baseline scores as partial",
|
|
)
|
|
handjob_baseline_score = baseline_score_entries.get("pov_handjob_upright_centered") or {}
|
|
_expect(
|
|
handjob_baseline_score.get("score_state") == "needs_visual_score",
|
|
"Atlas refine baseline score sheet should flag unscored baseline-only known entries",
|
|
)
|
|
_expect(
|
|
baseline_score_cli_returncode == 0,
|
|
f"Atlas refine baseline score sheet CLI failed: {baseline_score_cli_stderr}",
|
|
)
|
|
cli_baseline_score = json.loads(baseline_score_cli_stdout)
|
|
_expect(
|
|
cli_baseline_score.get("entry_count") == 1,
|
|
"Atlas refine baseline score sheet CLI should honor variant-key filtering",
|
|
)
|
|
scored_baseline_sheet = json.loads(json.dumps(baseline_score_sheet))
|
|
scored_baseline_entries = {
|
|
entry.get("variant_key"): entry for entry in scored_baseline_sheet.get("entries") or []
|
|
}
|
|
scored_footjob_baseline = scored_baseline_entries.get("pov_footjob_frontal_sole_stroke") or {}
|
|
scored_footjob_baseline["analysis_notes"] = "Partial baseline is still useful as scored evidence."
|
|
scored_handjob_baseline = scored_baseline_entries.get("pov_handjob_upright_centered") or {}
|
|
scored_handjob_baseline["cue_axes"]["hand_position"] = "centered_upright_base_grip"
|
|
scored_handjob_baseline["score"].update(
|
|
{
|
|
"atlas_pose_match": "pass",
|
|
"contact_match": "pass",
|
|
"pose_ownership": "pass",
|
|
"workspace_continuity": "pass",
|
|
"clothing_visibility": "pass",
|
|
"subject_identity": "pass",
|
|
"expression_eye_control": "pass",
|
|
"anatomy_proportion": "pass",
|
|
"prompt_noise": "pass",
|
|
}
|
|
)
|
|
scored_handjob_baseline["analysis_notes"] = "Baseline handjob frame preserves subject, pose, and workspace."
|
|
baseline_score_update_draft = manifest_module.build_baseline_score_update_draft(scored_baseline_sheet)
|
|
_expect(
|
|
baseline_score_update_draft.get("schema") == "sxcp_atlas_refine_baseline_score_update_draft_v1",
|
|
"Atlas refine baseline score update draft lost schema",
|
|
)
|
|
_expect(
|
|
baseline_score_update_draft.get("update_count") == 2,
|
|
f"Atlas refine baseline score update draft should include scored and partially scored baselines: {baseline_score_update_draft}",
|
|
)
|
|
baseline_score_updates = {
|
|
update.get("variant_key"): update for update in baseline_score_update_draft.get("updates") or []
|
|
}
|
|
footjob_baseline_update = baseline_score_updates.get("pov_footjob_frontal_sole_stroke") or {}
|
|
_expect(
|
|
footjob_baseline_update.get("score_state") == "partially_scored",
|
|
"Atlas refine baseline score update draft should preserve partial baseline scoring",
|
|
)
|
|
_expect(
|
|
"prompt_variants" not in footjob_baseline_update,
|
|
"Atlas refine baseline score update draft must not carry prompt variants",
|
|
)
|
|
handjob_baseline_update = baseline_score_updates.get("pov_handjob_upright_centered") or {}
|
|
_expect(
|
|
handjob_baseline_update.get("score_state") == "scored_pass",
|
|
"Atlas refine baseline score update draft should recalculate filled baseline scores",
|
|
)
|
|
_expect(
|
|
handjob_baseline_update.get("score", {}).get("pose_ownership") == "pass",
|
|
"Atlas refine baseline score update draft should keep manual baseline score values",
|
|
)
|
|
_expect(
|
|
handjob_baseline_update.get("cue_axes", {}).get("hand_position") == "centered_upright_base_grip",
|
|
"Atlas refine baseline score update draft should keep manually reviewed cue axes",
|
|
)
|
|
baseline_score_update_validation = manifest_module.validate_baseline_score_update_draft(baseline_score_update_draft)
|
|
_expect(
|
|
baseline_score_update_validation.get("schema") == "sxcp_atlas_refine_baseline_score_update_validation_v1",
|
|
"Atlas refine baseline score update validation lost schema",
|
|
)
|
|
_expect(
|
|
baseline_score_update_validation.get("valid") is True,
|
|
f"Atlas refine baseline score update draft should validate: {baseline_score_update_validation}",
|
|
)
|
|
_expect(
|
|
baseline_score_update_validation.get("warning_count", 0) >= 1,
|
|
"Atlas refine baseline score update validation should warn about partial baseline scores",
|
|
)
|
|
invalid_baseline_score_update = json.loads(json.dumps(baseline_score_update_draft))
|
|
invalid_baseline_score_update["updates"][0]["prompt_variants"] = []
|
|
invalid_baseline_score_update["updates"][1]["sidecar_filename"] = "wrong.json"
|
|
invalid_baseline_score_validation = manifest_module.validate_baseline_score_update_draft(invalid_baseline_score_update)
|
|
_expect(
|
|
invalid_baseline_score_validation.get("valid") is False,
|
|
"Atlas refine baseline score update validation should reject contaminated baseline updates",
|
|
)
|
|
_expect(
|
|
any("prompt_variants" in error for error in invalid_baseline_score_validation.get("errors", [])),
|
|
f"Atlas refine baseline score update validation should reject prompt variant fields: {invalid_baseline_score_validation}",
|
|
)
|
|
_expect(
|
|
any("sidecar_filename" in error for error in invalid_baseline_score_validation.get("errors", [])),
|
|
f"Atlas refine baseline score update validation should reject sidecar filename drift: {invalid_baseline_score_validation}",
|
|
)
|
|
with tempfile.NamedTemporaryFile("w", suffix=".json", delete=False, encoding="utf-8") as baseline_sheet_handle:
|
|
json.dump(scored_baseline_sheet, baseline_sheet_handle)
|
|
cli_baseline_sheet_path = Path(baseline_sheet_handle.name)
|
|
try:
|
|
baseline_update_cli_result = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
str(ROOT / "tools" / "krea2_atlas_refine_manifest.py"),
|
|
"--print-baseline-score-update-draft",
|
|
"--baseline-score-sheet-json",
|
|
str(cli_baseline_sheet_path),
|
|
],
|
|
cwd=ROOT,
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
finally:
|
|
cli_baseline_sheet_path.unlink(missing_ok=True)
|
|
_expect(
|
|
baseline_update_cli_result.returncode == 0,
|
|
f"Atlas refine baseline score update draft CLI failed: {baseline_update_cli_result.stderr}",
|
|
)
|
|
cli_baseline_update_draft = json.loads(baseline_update_cli_result.stdout)
|
|
_expect(
|
|
cli_baseline_update_draft.get("update_count") == 2,
|
|
"Atlas refine baseline score update draft CLI should keep scored baseline updates",
|
|
)
|
|
with tempfile.NamedTemporaryFile("w", suffix=".json", delete=False, encoding="utf-8") as baseline_update_handle:
|
|
json.dump(baseline_score_update_draft, baseline_update_handle)
|
|
cli_baseline_update_path = Path(baseline_update_handle.name)
|
|
try:
|
|
baseline_update_validation_cli_result = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
str(ROOT / "tools" / "krea2_atlas_refine_manifest.py"),
|
|
"--validate-baseline-score-update-draft",
|
|
"--baseline-score-update-draft-json",
|
|
str(cli_baseline_update_path),
|
|
],
|
|
cwd=ROOT,
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
finally:
|
|
cli_baseline_update_path.unlink(missing_ok=True)
|
|
_expect(
|
|
baseline_update_validation_cli_result.returncode == 0,
|
|
f"Atlas refine baseline score update validation CLI failed: {baseline_update_validation_cli_result.stderr}",
|
|
)
|
|
cli_baseline_update_validation = json.loads(baseline_update_validation_cli_result.stdout)
|
|
_expect(
|
|
cli_baseline_update_validation.get("valid") is True,
|
|
"Atlas refine baseline score update validation CLI should validate the draft",
|
|
)
|
|
with tempfile.TemporaryDirectory() as baseline_apply_tmpdir:
|
|
baseline_apply_root = Path(baseline_apply_tmpdir)
|
|
baseline_footjob_sidecar_path = baseline_apply_root / "pov_footjob_frontal_sole_stroke_00001_.json"
|
|
baseline_footjob_sidecar_path.write_text(
|
|
json.dumps(
|
|
{
|
|
"notes": "preserve prompt variant sidecar notes",
|
|
"prompt_variants": [
|
|
{
|
|
"id": "existing_axis",
|
|
"text": "Existing tested prompt variant.",
|
|
"cue_axes": {"foot_position": "existing"},
|
|
}
|
|
],
|
|
},
|
|
ensure_ascii=True,
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
baseline_apply_report = manifest_module.apply_baseline_score_update_draft(
|
|
baseline_score_update_draft,
|
|
baseline_apply_root,
|
|
)
|
|
_expect(
|
|
baseline_apply_report.get("schema") == "sxcp_atlas_refine_baseline_score_apply_report_v1",
|
|
"Atlas refine baseline score apply report lost schema",
|
|
)
|
|
_expect(baseline_apply_report.get("applied") is True, "Atlas refine baseline score apply should mark applied")
|
|
_expect(
|
|
baseline_apply_report.get("updated_file_count") == 2,
|
|
"Atlas refine baseline score apply should update each scored baseline sidecar",
|
|
)
|
|
applied_footjob_baseline_sidecar = json.loads(baseline_footjob_sidecar_path.read_text(encoding="utf-8"))
|
|
_expect(
|
|
[variant.get("id") for variant in applied_footjob_baseline_sidecar.get("prompt_variants", [])]
|
|
== ["existing_axis"],
|
|
"Atlas refine baseline score apply must preserve existing prompt variants without adding new ones",
|
|
)
|
|
_expect(
|
|
applied_footjob_baseline_sidecar.get("score", {}).get("atlas_pose_match") == "partial",
|
|
"Atlas refine baseline score apply should write partial baseline score metadata",
|
|
)
|
|
_expect(
|
|
applied_footjob_baseline_sidecar.get("baseline_analysis_notes") == "Partial baseline is still useful as scored evidence.",
|
|
"Atlas refine baseline score apply should keep manual baseline analysis notes",
|
|
)
|
|
baseline_handjob_sidecar_path = baseline_apply_root / "pov_handjob_upright_centered_00001_.json"
|
|
applied_handjob_baseline_sidecar = json.loads(baseline_handjob_sidecar_path.read_text(encoding="utf-8"))
|
|
_expect(
|
|
applied_handjob_baseline_sidecar.get("score", {}).get("pose_ownership") == "pass",
|
|
"Atlas refine baseline score apply should write passing baseline score metadata",
|
|
)
|
|
_expect(
|
|
applied_handjob_baseline_sidecar.get("cue_axes", {}).get("hand_position") == "centered_upright_base_grip",
|
|
"Atlas refine baseline score apply should write manually reviewed cue axes",
|
|
)
|
|
with tempfile.NamedTemporaryFile("w", suffix=".json", delete=False, encoding="utf-8") as baseline_apply_handle:
|
|
json.dump(baseline_score_update_draft, baseline_apply_handle)
|
|
cli_baseline_apply_path = Path(baseline_apply_handle.name)
|
|
try:
|
|
baseline_apply_cli_result = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
str(ROOT / "tools" / "krea2_atlas_refine_manifest.py"),
|
|
"--apply-baseline-score-update-draft",
|
|
"--baseline-score-update-draft-json",
|
|
str(cli_baseline_apply_path),
|
|
"--folder",
|
|
str(baseline_apply_root),
|
|
],
|
|
cwd=ROOT,
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
finally:
|
|
cli_baseline_apply_path.unlink(missing_ok=True)
|
|
_expect(
|
|
baseline_apply_cli_result.returncode == 0,
|
|
f"Atlas refine baseline score apply CLI failed: {baseline_apply_cli_result.stderr}",
|
|
)
|
|
cli_baseline_apply_report = json.loads(baseline_apply_cli_result.stdout)
|
|
_expect(
|
|
cli_baseline_apply_report.get("applied") is True,
|
|
"Atlas refine baseline score apply CLI should report applied",
|
|
)
|
|
(baseline_apply_root / "pov_footjob_frontal_sole_stroke_00001_.txt").write_text(
|
|
footjob.get("prompt_text", ""),
|
|
encoding="utf-8",
|
|
)
|
|
(baseline_apply_root / "pov_footjob_frontal_sole_stroke_00001_.png").write_bytes(b"fake-png")
|
|
(baseline_apply_root / "pov_handjob_upright_centered_00001_.txt").write_text(
|
|
handjob_baseline_score.get("prompt_text", ""),
|
|
encoding="utf-8",
|
|
)
|
|
(baseline_apply_root / "pov_handjob_upright_centered_00001_.png").write_bytes(b"fake-png")
|
|
baseline_applied_manifest = manifest_module.build_manifest(baseline_apply_root, subject_id="same_woman_001")
|
|
baseline_applied_sheet = manifest_module.build_baseline_score_sheet(baseline_applied_manifest)
|
|
baseline_applied_entries = {
|
|
entry.get("variant_key"): entry for entry in baseline_applied_sheet.get("entries") or []
|
|
}
|
|
_expect(
|
|
baseline_applied_entries.get("pov_footjob_frontal_sole_stroke", {}).get("score_state")
|
|
== "partially_scored",
|
|
"Atlas refine baseline score apply should rescan partial baseline score state",
|
|
)
|
|
_expect(
|
|
baseline_applied_entries.get("pov_handjob_upright_centered", {}).get("score_state") == "scored_pass",
|
|
"Atlas refine baseline score apply should rescan passing baseline score state",
|
|
)
|
|
clean_noise_report = manifest_module.build_prompt_noise_report(manifest)
|
|
_expect(
|
|
clean_noise_report.get("schema") == "sxcp_atlas_refine_prompt_noise_report_v1",
|
|
"Atlas refine prompt-noise report lost schema",
|
|
)
|
|
_expect(
|
|
clean_noise_report.get("issue_count") == 0,
|
|
f"Atlas refine prompt-noise report should not flag clean direct atlas prompts: {clean_noise_report}",
|
|
)
|
|
_expect(
|
|
prompt_noise_cli_returncode == 0,
|
|
f"Atlas refine prompt-noise report CLI failed: {prompt_noise_cli_stderr}",
|
|
)
|
|
cli_prompt_noise_report = json.loads(prompt_noise_cli_stdout)
|
|
_expect(
|
|
cli_prompt_noise_report.get("issue_count") == 0,
|
|
"Atlas refine prompt-noise report CLI should preserve clean issue count",
|
|
)
|
|
clean_cleanup_sheet = manifest_module.build_prompt_cleanup_sheet(manifest)
|
|
_expect(
|
|
clean_cleanup_sheet.get("schema") == "sxcp_atlas_refine_prompt_cleanup_sheet_v1",
|
|
"Atlas refine prompt-cleanup sheet lost schema",
|
|
)
|
|
_expect(
|
|
clean_cleanup_sheet.get("cleanup_item_count") == 0,
|
|
f"Atlas refine prompt-cleanup sheet should not emit cleanup items for clean prompts: {clean_cleanup_sheet}",
|
|
)
|
|
_expect(
|
|
prompt_cleanup_cli_returncode == 0,
|
|
f"Atlas refine prompt-cleanup sheet CLI failed: {prompt_cleanup_cli_stderr}",
|
|
)
|
|
cli_prompt_cleanup_sheet = json.loads(prompt_cleanup_cli_stdout)
|
|
_expect(
|
|
cli_prompt_cleanup_sheet.get("cleanup_item_count") == 0,
|
|
"Atlas refine prompt-cleanup sheet CLI should preserve clean cleanup count",
|
|
)
|
|
noisy_manifest = json.loads(json.dumps(manifest))
|
|
noisy_entries = {
|
|
entry.get("variant_key"): entry for entry in noisy_manifest.get("entries") or []
|
|
}
|
|
noisy_handjob = noisy_entries.get("pov_handjob_upright_centered") or {}
|
|
noisy_handjob["prompt_text"] += (
|
|
" Keep the visible partner and the action primary; context stays beside or behind the bodies."
|
|
" The viewer looks straight down from his torso."
|
|
" The viewer looks straight down from his torso."
|
|
)
|
|
noisy_handjob["prompt_sha256"] = manifest_module._sha256_text(noisy_handjob["prompt_text"])
|
|
noisy_footjob = noisy_entries.get("pov_footjob_frontal_sole_stroke") or {}
|
|
noisy_footjob.setdefault("prompt_variants", []).append(
|
|
{
|
|
"id": "noisy_option_wording",
|
|
"append_cues": [
|
|
"either hand or foot may move optionally while the POV foreground clothing cue stays visible"
|
|
],
|
|
"cue_axes": {"foot_position": "ambiguous_option_axis"},
|
|
}
|
|
)
|
|
noisy_footjob.setdefault("prompt_variants", []).append(
|
|
{
|
|
"id": "noisy_negative_text",
|
|
"text": "No lower torso should appear; keep the visible partner primary.",
|
|
"cue_axes": {"body_angle": "negative_meta_text"},
|
|
}
|
|
)
|
|
noisy_noise_report = manifest_module.build_prompt_noise_report(noisy_manifest)
|
|
_expect(
|
|
noisy_noise_report.get("issue_count", 0) >= 6,
|
|
f"Atlas refine prompt-noise report should flag option/meta/negative prompt noise: {noisy_noise_report}",
|
|
)
|
|
noisy_issue_codes = [
|
|
issue.get("code")
|
|
for entry in noisy_noise_report.get("entries") or []
|
|
for issue in entry.get("issues") or []
|
|
]
|
|
_expect(
|
|
"meta_instruction" in noisy_issue_codes,
|
|
f"Atlas refine prompt-noise report should flag meta instructions: {noisy_noise_report}",
|
|
)
|
|
_expect(
|
|
"option_word" in noisy_issue_codes,
|
|
f"Atlas refine prompt-noise report should flag option-list wording: {noisy_noise_report}",
|
|
)
|
|
_expect(
|
|
"negative_conditioning" in noisy_issue_codes,
|
|
f"Atlas refine prompt-noise report should flag negative positive-channel wording: {noisy_noise_report}",
|
|
)
|
|
_expect(
|
|
"duplicate_phrase" in noisy_issue_codes,
|
|
f"Atlas refine prompt-noise report should flag repeated prompt phrases: {noisy_noise_report}",
|
|
)
|
|
_expect(
|
|
any(issue.get("context") == "prompt_variant_append_cue" for entry in noisy_noise_report.get("entries") or [] for issue in entry.get("issues") or []),
|
|
f"Atlas refine prompt-noise report should localize append-cue noise: {noisy_noise_report}",
|
|
)
|
|
noisy_coverage_report = manifest_module.build_coverage_report(noisy_manifest)
|
|
_expect(
|
|
noisy_coverage_report.get("needs_prompt_cleanup_count") == 2,
|
|
f"Atlas refine coverage should count noisy entries as cleanup-needed: {noisy_coverage_report}",
|
|
)
|
|
_expect(
|
|
noisy_coverage_report.get("prompt_noise_issue_count", 0) >= 6,
|
|
f"Atlas refine coverage should carry prompt-noise issue totals: {noisy_coverage_report}",
|
|
)
|
|
noisy_coverage_by_variant = {
|
|
entry.get("variant_key"): entry for entry in noisy_coverage_report.get("entries") or []
|
|
}
|
|
noisy_handjob_coverage = noisy_coverage_by_variant.get("pov_handjob_upright_centered") or {}
|
|
_expect(
|
|
noisy_handjob_coverage.get("state") == "needs_prompt_cleanup",
|
|
f"Atlas refine coverage should block noisy baseline-only entries before scoring: {noisy_handjob_coverage}",
|
|
)
|
|
noisy_footjob_coverage = noisy_coverage_by_variant.get("pov_footjob_frontal_sole_stroke") or {}
|
|
_expect(
|
|
noisy_footjob_coverage.get("state") == "needs_prompt_cleanup",
|
|
f"Atlas refine coverage should block noisy sidecar variants before seed selection: {noisy_footjob_coverage}",
|
|
)
|
|
_expect(
|
|
noisy_footjob_coverage.get("prompt_noise_issue_count", 0) >= 4,
|
|
f"Atlas refine coverage should localize sidecar prompt-noise counts: {noisy_footjob_coverage}",
|
|
)
|
|
noisy_cleanup_sheet = manifest_module.build_prompt_cleanup_sheet(noisy_manifest)
|
|
_expect(
|
|
noisy_cleanup_sheet.get("schema") == "sxcp_atlas_refine_prompt_cleanup_sheet_v1",
|
|
"Atlas refine prompt-cleanup sheet lost schema for noisy prompts",
|
|
)
|
|
_expect(
|
|
noisy_cleanup_sheet.get("cleanup_item_count") == 3,
|
|
f"Atlas refine prompt-cleanup sheet should group issues by editable source text: {noisy_cleanup_sheet}",
|
|
)
|
|
_expect(
|
|
noisy_cleanup_sheet.get("issue_count", 0) >= 6,
|
|
f"Atlas refine prompt-cleanup sheet should carry all issue counts: {noisy_cleanup_sheet}",
|
|
)
|
|
cleanup_items = {
|
|
(item.get("variant_key"), item.get("context"), item.get("prompt_variant_id")): item
|
|
for item in noisy_cleanup_sheet.get("cleanup_items") or []
|
|
}
|
|
handjob_cleanup = cleanup_items.get(("pov_handjob_upright_centered", "baseline_prompt", "")) or {}
|
|
_expect(
|
|
handjob_cleanup.get("source_type") == "prompt_file",
|
|
f"Atlas refine prompt-cleanup sheet should point baseline cleanup at prompt files: {handjob_cleanup}",
|
|
)
|
|
_expect(
|
|
handjob_cleanup.get("source_path") == handjob_baseline_score.get("prompt_path"),
|
|
f"Atlas refine prompt-cleanup sheet should keep exact prompt path: {handjob_cleanup}",
|
|
)
|
|
_expect(
|
|
handjob_cleanup.get("source_prompt_sha256") == noisy_handjob.get("prompt_sha256"),
|
|
f"Atlas refine prompt-cleanup sheet should preserve baseline source prompt hash: {handjob_cleanup}",
|
|
)
|
|
_expect(
|
|
"Keep the visible partner" in handjob_cleanup.get("current_text", ""),
|
|
"Atlas refine prompt-cleanup sheet should preserve current noisy baseline text",
|
|
)
|
|
_expect(
|
|
handjob_cleanup.get("current_text_sha256") == manifest_module._sha256_text(handjob_cleanup.get("current_text", "")),
|
|
f"Atlas refine prompt-cleanup sheet should hash current baseline text for drift checks: {handjob_cleanup}",
|
|
)
|
|
_expect(
|
|
handjob_cleanup.get("replacement_text") == "",
|
|
"Atlas refine prompt-cleanup sheet should leave replacement text blank for manual cleanup",
|
|
)
|
|
append_cleanup = cleanup_items.get(("pov_footjob_frontal_sole_stroke", "prompt_variant_append_cue", "noisy_option_wording")) or {}
|
|
_expect(
|
|
append_cleanup.get("source_type") == "sidecar_prompt_variant_append_cue",
|
|
f"Atlas refine prompt-cleanup sheet should point append-cue cleanup at sidecar variants: {append_cleanup}",
|
|
)
|
|
_expect(
|
|
append_cleanup.get("cue_index") == 0,
|
|
"Atlas refine prompt-cleanup sheet should preserve append-cue index",
|
|
)
|
|
_expect(
|
|
append_cleanup.get("sidecar_filename") == "pov_footjob_frontal_sole_stroke_00001_.json",
|
|
f"Atlas refine prompt-cleanup sheet should preserve same-stem sidecar filename: {append_cleanup}",
|
|
)
|
|
_expect(
|
|
append_cleanup.get("source_prompt_sha256") == noisy_footjob.get("prompt_sha256"),
|
|
f"Atlas refine prompt-cleanup sheet should preserve sidecar baseline source prompt hash: {append_cleanup}",
|
|
)
|
|
_expect(
|
|
append_cleanup.get("current_text_sha256") == manifest_module._sha256_text(append_cleanup.get("current_text", "")),
|
|
f"Atlas refine prompt-cleanup sheet should hash current append-cue text for drift checks: {append_cleanup}",
|
|
)
|
|
invalid_cleanup_sheet = json.loads(json.dumps(noisy_cleanup_sheet))
|
|
invalid_cleanup_sheet["cleanup_items"][0]["replacement_text"] = ""
|
|
invalid_cleanup_sheet["cleanup_items"][1]["replacement_text"] = "either noisy option remains"
|
|
invalid_cleanup_sheet["cleanup_items"][2]["current_text_sha256"] = "stale-hash"
|
|
invalid_cleanup_sheet["cleanup_items"][2]["source_prompt_sha256"] = ""
|
|
invalid_cleanup_validation = manifest_module.validate_prompt_cleanup_sheet(invalid_cleanup_sheet)
|
|
_expect(
|
|
invalid_cleanup_validation.get("schema") == "sxcp_atlas_refine_prompt_cleanup_validation_v1",
|
|
"Atlas refine prompt-cleanup validation lost schema",
|
|
)
|
|
_expect(
|
|
invalid_cleanup_validation.get("valid") is False,
|
|
f"Atlas refine prompt-cleanup validation should reject blank or noisy replacements: {invalid_cleanup_validation}",
|
|
)
|
|
_expect(
|
|
any("replacement_text is required" in error for error in invalid_cleanup_validation.get("errors", [])),
|
|
f"Atlas refine prompt-cleanup validation should reject blank replacements: {invalid_cleanup_validation}",
|
|
)
|
|
_expect(
|
|
any("replacement_text still has prompt-noise issues" in error for error in invalid_cleanup_validation.get("errors", [])),
|
|
f"Atlas refine prompt-cleanup validation should reject noisy replacements: {invalid_cleanup_validation}",
|
|
)
|
|
_expect(
|
|
any("current_text_sha256" in error for error in invalid_cleanup_validation.get("errors", [])),
|
|
f"Atlas refine prompt-cleanup validation should reject stale current-text hashes: {invalid_cleanup_validation}",
|
|
)
|
|
_expect(
|
|
any("source_prompt_sha256" in error for error in invalid_cleanup_validation.get("errors", [])),
|
|
f"Atlas refine prompt-cleanup validation should reject missing source prompt hashes: {invalid_cleanup_validation}",
|
|
)
|
|
cleanup_sheet_for_apply = json.loads(json.dumps(noisy_cleanup_sheet))
|
|
cleanup_sheet_for_apply["cleanup_items"] = [
|
|
json.loads(json.dumps(handjob_cleanup)),
|
|
json.loads(json.dumps(append_cleanup)),
|
|
]
|
|
cleanup_sheet_for_apply["cleanup_items"][0]["replacement_text"] = (
|
|
"A controlled same-subject handjob reference prompt with direct centered upright contact."
|
|
)
|
|
cleanup_sheet_for_apply["cleanup_items"][0]["cleanup_notes"] = "manual direct rewrite"
|
|
cleanup_sheet_for_apply["cleanup_items"][1]["replacement_text"] = (
|
|
"the woman's foot presses forward along the same contact line"
|
|
)
|
|
cleanup_validation = manifest_module.validate_prompt_cleanup_sheet(cleanup_sheet_for_apply)
|
|
_expect(
|
|
cleanup_validation.get("valid") is True,
|
|
f"Atlas refine prompt-cleanup validation should accept clean manual replacements: {cleanup_validation}",
|
|
)
|
|
stale_source_hash_cleanup_sheet = json.loads(json.dumps(cleanup_sheet_for_apply))
|
|
stale_source_hash_cleanup_sheet["cleanup_items"][0]["source_prompt_sha256"] = "stale-source-hash"
|
|
stale_source_hash_validation = manifest_module.validate_prompt_cleanup_sheet(stale_source_hash_cleanup_sheet)
|
|
_expect(
|
|
stale_source_hash_validation.get("valid") is False
|
|
and any(
|
|
"source_prompt_sha256 must match current_text_sha256"
|
|
in error
|
|
for error in stale_source_hash_validation.get("errors", [])
|
|
),
|
|
f"Atlas refine prompt-cleanup validation should reject stale baseline source prompt hashes: {stale_source_hash_validation}",
|
|
)
|
|
with tempfile.TemporaryDirectory() as cleanup_tmpdir:
|
|
cleanup_root = Path(cleanup_tmpdir)
|
|
cleanup_handjob_prompt = cleanup_root / "pov_handjob_upright_centered_00001_.txt"
|
|
cleanup_handjob_image = cleanup_root / "pov_handjob_upright_centered_00001_.png"
|
|
cleanup_footjob_prompt = cleanup_root / "pov_footjob_frontal_sole_stroke_00001_.txt"
|
|
cleanup_footjob_image = cleanup_root / "pov_footjob_frontal_sole_stroke_00001_.png"
|
|
cleanup_footjob_sidecar = cleanup_root / "pov_footjob_frontal_sole_stroke_00001_.json"
|
|
cleanup_handjob_prompt.write_text(handjob_cleanup.get("current_text", ""), encoding="utf-8")
|
|
cleanup_handjob_image.write_bytes(b"fake-png")
|
|
cleanup_footjob_prompt.write_text(noisy_footjob.get("prompt_text", ""), encoding="utf-8")
|
|
cleanup_footjob_image.write_bytes(b"fake-png")
|
|
cleanup_footjob_sidecar.write_text(
|
|
json.dumps(
|
|
{
|
|
"notes": "preserve cleanup sidecar notes",
|
|
"prompt_variants": [
|
|
{
|
|
"id": "noisy_option_wording",
|
|
"append_cues": [
|
|
append_cleanup.get("current_text", ""),
|
|
"existing second cue stays",
|
|
],
|
|
"cue_axes": {"foot_position": "ambiguous_option_axis"},
|
|
}
|
|
],
|
|
},
|
|
ensure_ascii=True,
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
cleanup_sheet_for_apply["cleanup_items"][0]["source_path"] = str(cleanup_handjob_prompt)
|
|
cleanup_sheet_for_apply["cleanup_items"][1]["source_path"] = str(cleanup_footjob_sidecar)
|
|
cleanup_apply_report = manifest_module.apply_prompt_cleanup_sheet(cleanup_sheet_for_apply, cleanup_root)
|
|
_expect(
|
|
cleanup_apply_report.get("schema") == "sxcp_atlas_refine_prompt_cleanup_apply_report_v1",
|
|
"Atlas refine prompt-cleanup apply report lost schema",
|
|
)
|
|
_expect(
|
|
cleanup_apply_report.get("applied") is True,
|
|
f"Atlas refine prompt-cleanup apply should accept clean manual replacements: {cleanup_apply_report}",
|
|
)
|
|
_expect(
|
|
cleanup_apply_report.get("updated_file_count") == 2,
|
|
"Atlas refine prompt-cleanup apply should update prompt file and sidecar file",
|
|
)
|
|
_expect(
|
|
cleanup_handjob_prompt.read_text(encoding="utf-8")
|
|
== "A controlled same-subject handjob reference prompt with direct centered upright contact.",
|
|
"Atlas refine prompt-cleanup apply should rewrite the prompt file with manual replacement",
|
|
)
|
|
applied_cleanup_sidecar = json.loads(cleanup_footjob_sidecar.read_text(encoding="utf-8"))
|
|
applied_cleanup_variant = (applied_cleanup_sidecar.get("prompt_variants") or [{}])[0]
|
|
_expect(
|
|
applied_cleanup_sidecar.get("notes") == "preserve cleanup sidecar notes",
|
|
"Atlas refine prompt-cleanup apply should preserve unrelated sidecar metadata",
|
|
)
|
|
_expect(
|
|
applied_cleanup_variant.get("append_cues", [])[0]
|
|
== "the woman's foot presses forward along the same contact line",
|
|
"Atlas refine prompt-cleanup apply should replace the targeted append cue",
|
|
)
|
|
_expect(
|
|
applied_cleanup_variant.get("append_cues", [])[1] == "existing second cue stays",
|
|
"Atlas refine prompt-cleanup apply should preserve untargeted append cues",
|
|
)
|
|
cleanup_manifest = manifest_module.build_manifest(cleanup_root, subject_id="same_woman_001")
|
|
cleanup_noise_report = manifest_module.build_prompt_noise_report(cleanup_manifest)
|
|
_expect(
|
|
cleanup_noise_report.get("issue_count") == 0,
|
|
f"Atlas refine prompt-cleanup apply should remove prompt-noise issues from the cleaned temp deck: {cleanup_noise_report}",
|
|
)
|
|
with tempfile.NamedTemporaryFile("w", suffix=".json", delete=False, encoding="utf-8") as cleanup_sheet_handle:
|
|
json.dump(cleanup_sheet_for_apply, cleanup_sheet_handle)
|
|
cli_cleanup_sheet_path = Path(cleanup_sheet_handle.name)
|
|
try:
|
|
cleanup_validation_cli_result = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
str(ROOT / "tools" / "krea2_atlas_refine_manifest.py"),
|
|
"--validate-prompt-cleanup-sheet",
|
|
"--prompt-cleanup-sheet-json",
|
|
str(cli_cleanup_sheet_path),
|
|
],
|
|
cwd=ROOT,
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
finally:
|
|
cli_cleanup_sheet_path.unlink(missing_ok=True)
|
|
_expect(
|
|
cleanup_validation_cli_result.returncode == 0,
|
|
f"Atlas refine prompt-cleanup validation CLI failed: {cleanup_validation_cli_result.stderr}",
|
|
)
|
|
with tempfile.NamedTemporaryFile("w", suffix=".json", delete=False, encoding="utf-8") as cleanup_apply_sheet_handle:
|
|
json.dump(cleanup_sheet_for_apply, cleanup_apply_sheet_handle)
|
|
cli_cleanup_apply_sheet_path = Path(cleanup_apply_sheet_handle.name)
|
|
try:
|
|
cleanup_apply_cli_result = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
str(ROOT / "tools" / "krea2_atlas_refine_manifest.py"),
|
|
"--apply-prompt-cleanup-sheet",
|
|
"--prompt-cleanup-sheet-json",
|
|
str(cli_cleanup_apply_sheet_path),
|
|
"--folder",
|
|
str(cleanup_root),
|
|
],
|
|
cwd=ROOT,
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
finally:
|
|
cli_cleanup_apply_sheet_path.unlink(missing_ok=True)
|
|
_expect(
|
|
cleanup_apply_cli_result.returncode == 0,
|
|
f"Atlas refine prompt-cleanup apply CLI failed: {cleanup_apply_cli_result.stderr}",
|
|
)
|
|
cli_cleanup_apply = json.loads(cleanup_apply_cli_result.stdout)
|
|
_expect(
|
|
cli_cleanup_apply.get("applied") is True,
|
|
"Atlas refine prompt-cleanup apply CLI should report applied",
|
|
)
|
|
coverage_report = manifest_module.build_coverage_report(manifest)
|
|
_expect(
|
|
coverage_report.get("schema") == "sxcp_atlas_refine_coverage_report_v1",
|
|
"Atlas refine coverage report lost schema",
|
|
)
|
|
_expect(
|
|
coverage_report.get("entry_count") == 3,
|
|
"Atlas refine coverage report should keep manifest entry count",
|
|
)
|
|
coverage_by_variant = {entry.get("variant_key"): entry for entry in coverage_report.get("entries") or []}
|
|
footjob_coverage = coverage_by_variant.get("pov_footjob_frontal_sole_stroke") or {}
|
|
_expect(
|
|
footjob_coverage.get("state") == "needs_visual_score",
|
|
f"Atlas refine coverage should flag unscored prompt variants: {footjob_coverage}",
|
|
)
|
|
_expect(
|
|
footjob_coverage.get("prompt_variant_count") == 1 and footjob_coverage.get("unscored_variant_count") == 1,
|
|
"Atlas refine coverage should count unscored prompt variants",
|
|
)
|
|
unknown_coverage = coverage_by_variant.get("pov_unknown_pose_candidate") or {}
|
|
_expect(
|
|
unknown_coverage.get("state") == "unknown_variant",
|
|
"Atlas refine coverage should flag unknown variant entries before seed testing",
|
|
)
|
|
handjob_coverage = coverage_by_variant.get("pov_handjob_upright_centered") or {}
|
|
_expect(
|
|
handjob_coverage.get("state") == "baseline_only",
|
|
"Atlas refine coverage should flag known entries with no sidecar variants as baseline-only",
|
|
)
|
|
reference_pool_report = manifest_module.build_reference_pool_report(
|
|
"pov_blowjob_top_down_vertical_shaft",
|
|
supplemental_folders=["1.original/blowjob_top_view_1024"],
|
|
)
|
|
_expect(
|
|
reference_pool_report.get("schema") == "sxcp_atlas_reference_pool_report_v1",
|
|
"Atlas reference pool report lost schema",
|
|
)
|
|
_expect(
|
|
reference_pool_report.get("variant_key") == "pov_blowjob_top_down_vertical_shaft",
|
|
"Atlas reference pool report lost variant key",
|
|
)
|
|
_expect(
|
|
reference_pool_report.get("canonical_image_count") == 17,
|
|
f"Atlas reference pool report should count curated top-view references: {reference_pool_report}",
|
|
)
|
|
_expect(
|
|
reference_pool_report.get("supplemental_image_count") == 27,
|
|
f"Atlas reference pool report should count supplemental raw top-view references: {reference_pool_report}",
|
|
)
|
|
_expect(
|
|
reference_pool_report.get("matched_image_count") == 17,
|
|
f"Atlas reference pool report should match curated refs to raw counterparts by image id: {reference_pool_report}",
|
|
)
|
|
_expect(
|
|
reference_pool_report.get("supplemental_extra_count") == 10,
|
|
f"Atlas reference pool report should surface raw-only cue-expansion images: {reference_pool_report}",
|
|
)
|
|
_expect(
|
|
"1.original/blowjob_top_view_1024/16.png" in reference_pool_report.get("supplemental_extra_images", []),
|
|
f"Atlas reference pool report should include raw-only image ids for cue mining: {reference_pool_report}",
|
|
)
|
|
_expect(
|
|
"blowjob_top_view/22_blowjob_top_view.png" in reference_pool_report.get("catalog_reference_images", []),
|
|
f"Atlas reference pool report should keep curated catalog reference images: {reference_pool_report}",
|
|
)
|
|
reference_pool_cli_result = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
str(ROOT / "tools" / "krea2_atlas_refine_manifest.py"),
|
|
"--print-reference-pool-report",
|
|
"--variant-key",
|
|
"pov_blowjob_top_down_vertical_shaft",
|
|
"--reference-pool-folder",
|
|
"1.original/blowjob_top_view_1024",
|
|
],
|
|
cwd=ROOT,
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
_expect(
|
|
reference_pool_cli_result.returncode == 0,
|
|
f"Atlas reference pool report CLI failed: {reference_pool_cli_result.stderr}",
|
|
)
|
|
cli_reference_pool_report = json.loads(reference_pool_cli_result.stdout)
|
|
_expect(
|
|
cli_reference_pool_report.get("supplemental_extra_count") == 10,
|
|
"Atlas reference pool report CLI should keep supplemental raw-only count",
|
|
)
|
|
reference_cue_review_sheet = manifest_module.build_reference_cue_review_sheet(
|
|
"pov_blowjob_top_down_vertical_shaft",
|
|
supplemental_folders=["1.original/blowjob_top_view_1024"],
|
|
)
|
|
_expect(
|
|
reference_cue_review_sheet.get("schema") == "sxcp_atlas_reference_cue_review_sheet_v1",
|
|
"Atlas reference cue-review sheet lost schema",
|
|
)
|
|
_expect(
|
|
reference_cue_review_sheet.get("review_item_count") == 27,
|
|
f"Atlas reference cue-review sheet should cover curated refs plus raw-only extras: {reference_cue_review_sheet}",
|
|
)
|
|
review_items_by_image = {
|
|
item.get("canonical_image") or item.get("supplemental_image"): item
|
|
for item in reference_cue_review_sheet.get("review_items") or []
|
|
}
|
|
catalog_reference_item = review_items_by_image.get("blowjob_top_view/22_blowjob_top_view.png") or {}
|
|
_expect(
|
|
catalog_reference_item.get("role") == "catalog_reference",
|
|
f"Atlas reference cue-review sheet should mark curated catalog anchors: {catalog_reference_item}",
|
|
)
|
|
_expect(
|
|
catalog_reference_item.get("supplemental_image") == "1.original/blowjob_top_view_1024/22.png",
|
|
f"Atlas reference cue-review sheet should keep matched raw counterpart provenance: {catalog_reference_item}",
|
|
)
|
|
_expect(
|
|
catalog_reference_item.get("reference_images_template") == ["blowjob_top_view/22_blowjob_top_view.png"],
|
|
f"Atlas reference cue-review sheet should provide canonical sidecar reference template: {catalog_reference_item}",
|
|
)
|
|
_expect(
|
|
catalog_reference_item.get("observed_positive_cues") == [],
|
|
"Atlas reference cue-review sheet must leave positive cue extraction blank for manual review",
|
|
)
|
|
raw_extra_item = review_items_by_image.get("1.original/blowjob_top_view_1024/16.png") or {}
|
|
_expect(
|
|
raw_extra_item.get("role") == "supplemental_extra",
|
|
f"Atlas reference cue-review sheet should mark raw-only extras separately: {raw_extra_item}",
|
|
)
|
|
_expect(
|
|
raw_extra_item.get("reference_images_template") == [],
|
|
f"Atlas reference cue-review sheet should not auto-promote raw-only extras into sidecar reference templates: {raw_extra_item}",
|
|
)
|
|
_expect(
|
|
all(value == "" for value in (raw_extra_item.get("cue_axes") or {}).values()),
|
|
f"Atlas reference cue-review sheet should leave cue axes blank for human labeling: {raw_extra_item}",
|
|
)
|
|
reference_cue_review_cli_result = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
str(ROOT / "tools" / "krea2_atlas_refine_manifest.py"),
|
|
"--print-reference-cue-review-sheet",
|
|
"--variant-key",
|
|
"pov_blowjob_top_down_vertical_shaft",
|
|
"--reference-pool-folder",
|
|
"1.original/blowjob_top_view_1024",
|
|
],
|
|
cwd=ROOT,
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
_expect(
|
|
reference_cue_review_cli_result.returncode == 0,
|
|
f"Atlas reference cue-review sheet CLI failed: {reference_cue_review_cli_result.stderr}",
|
|
)
|
|
cli_reference_cue_review_sheet = json.loads(reference_cue_review_cli_result.stdout)
|
|
_expect(
|
|
cli_reference_cue_review_sheet.get("review_item_count") == 27,
|
|
"Atlas reference cue-review sheet CLI should keep review item count",
|
|
)
|
|
filled_reference_cue_review_sheet = json.loads(json.dumps(reference_cue_review_sheet))
|
|
filled_review_items_by_image = {
|
|
item.get("canonical_image") or item.get("supplemental_image"): item
|
|
for item in filled_reference_cue_review_sheet.get("review_items") or []
|
|
}
|
|
filled_catalog_item = filled_review_items_by_image["blowjob_top_view/22_blowjob_top_view.png"]
|
|
filled_catalog_item["prompt_variant_template"]["id"] = "atlas22_upper_body_stack"
|
|
filled_catalog_item["observed_positive_cues"] = [
|
|
"face, hair crown, shoulders, upper chest, and one hand form the primary visible partner stack"
|
|
]
|
|
filled_catalog_item["cue_axes"]["camera_height"] = "near_vertical_overhead"
|
|
filled_catalog_item["cue_axes"]["workspace_surface"] = "flat_floor_plane"
|
|
filled_catalog_item["cue_axes"]["hand_position"] = "one_hand_base_contact"
|
|
filled_catalog_item["review_notes"] = "canonical row reviewed from atlas image 22"
|
|
filled_exact_text_item = filled_review_items_by_image["blowjob_top_view/27_blowjob_top_view.png"]
|
|
filled_exact_text_item["prompt_variant_template"]["id"] = "atlas27_shaft_first_exact_text"
|
|
filled_exact_text_item["prompt_variant_template"]["text"] = (
|
|
"A controlled same-subject prompt. Straight-down male POV oral close-up. "
|
|
"the centered shaft and mouth contact form the main vertical axis from the lower foreground to the woman's face. "
|
|
"the woman's face, hair crown, shoulders, upper chest, and one hand stack around the shaft-contact axis. "
|
|
"a flat floor plane fills the background as shallow overhead room evidence."
|
|
)
|
|
filled_exact_text_item["observed_positive_cues"] = [
|
|
"the centered shaft and mouth contact form the main vertical axis from the lower foreground to the woman's face"
|
|
]
|
|
filled_exact_text_item["cue_axes"]["camera_height"] = "straight_down_overhead_shaft_first"
|
|
filled_exact_text_item["cue_axes"]["contact_depth"] = "shaft_contact_axis_centered_from_lower_foreground_to_mouth"
|
|
filled_exact_text_item["review_notes"] = "canonical row reviewed as exact-text shaft-first ordering"
|
|
filled_noisy_item = filled_review_items_by_image["blowjob_top_view/106_blowjob_top_view.png"]
|
|
filled_noisy_item["prompt_variant_template"]["id"] = "noisy_option_axis"
|
|
filled_noisy_item["observed_positive_cues"] = [
|
|
"either a high top-down view or a flat top-down view"
|
|
]
|
|
filled_raw_extra_item = filled_review_items_by_image["1.original/blowjob_top_view_1024/16.png"]
|
|
filled_raw_extra_item["prompt_variant_template"]["id"] = "raw16_floor_plane_axis"
|
|
filled_raw_extra_item["observed_positive_cues"] = [
|
|
"carpet tile seams fill the support plane"
|
|
]
|
|
filled_raw_extra_item["cue_axes"]["workspace_surface"] = "carpet_tile_floor_plane"
|
|
reference_cue_candidate_draft = manifest_module.build_reference_cue_candidate_draft(
|
|
filled_reference_cue_review_sheet
|
|
)
|
|
_expect(
|
|
reference_cue_candidate_draft.get("schema") == "sxcp_atlas_reference_cue_candidate_draft_v1",
|
|
"Atlas reference cue candidate draft lost schema",
|
|
)
|
|
_expect(
|
|
reference_cue_candidate_draft.get("ready_candidate_count") == 2,
|
|
f"Atlas reference cue candidate draft should only promote canonical filled cues: {reference_cue_candidate_draft}",
|
|
)
|
|
candidates_by_id = {
|
|
candidate.get("prompt_variant_id"): candidate
|
|
for candidate in reference_cue_candidate_draft.get("candidates") or []
|
|
}
|
|
candidate = candidates_by_id.get("atlas22_upper_body_stack") or {}
|
|
_expect(
|
|
candidate.get("reference_images") == ["blowjob_top_view/22_blowjob_top_view.png"],
|
|
f"Atlas reference cue candidate draft should keep canonical reference image provenance: {candidate}",
|
|
)
|
|
_expect(
|
|
candidate.get("prompt_variant", {}).get("append_cues") == [
|
|
"face, hair crown, shoulders, upper chest, and one hand form the primary visible partner stack"
|
|
],
|
|
f"Atlas reference cue candidate draft should use reviewed positive cues as append cues: {candidate}",
|
|
)
|
|
_expect(
|
|
candidate.get("prompt_variant", {}).get("cue_axes", {}).get("camera_height") == "near_vertical_overhead",
|
|
f"Atlas reference cue candidate draft should carry reviewed cue axes: {candidate}",
|
|
)
|
|
exact_text_candidate = candidates_by_id.get("atlas27_shaft_first_exact_text") or {}
|
|
_expect(
|
|
"text" in (exact_text_candidate.get("prompt_variant") or {})
|
|
and "append_cues" not in (exact_text_candidate.get("prompt_variant") or {}),
|
|
f"Atlas reference cue candidate draft should preserve reviewed exact text instead of append cues: {exact_text_candidate}",
|
|
)
|
|
_expect(
|
|
(exact_text_candidate.get("prompt_variant") or {}).get("text", "").startswith(
|
|
"A controlled same-subject prompt. Straight-down male POV oral close-up."
|
|
),
|
|
f"Atlas reference cue candidate draft should keep exact-text wording order: {exact_text_candidate}",
|
|
)
|
|
skipped_reasons = {
|
|
(item.get("id"), item.get("reason"))
|
|
for item in reference_cue_candidate_draft.get("skipped") or []
|
|
}
|
|
_expect(
|
|
("106", "prompt_noise_issue") in skipped_reasons,
|
|
f"Atlas reference cue candidate draft should skip noisy reviewed cues: {reference_cue_candidate_draft}",
|
|
)
|
|
_expect(
|
|
("16", "supplemental_extra_needs_canonical_reference") in skipped_reasons,
|
|
f"Atlas reference cue candidate draft should not auto-promote raw-only extras: {reference_cue_candidate_draft}",
|
|
)
|
|
with tempfile.NamedTemporaryFile("w", suffix=".json", delete=False, encoding="utf-8") as cue_candidate_sheet_handle:
|
|
json.dump(filled_reference_cue_review_sheet, cue_candidate_sheet_handle)
|
|
cue_candidate_sheet_path = Path(cue_candidate_sheet_handle.name)
|
|
try:
|
|
reference_cue_candidate_cli_result = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
str(ROOT / "tools" / "krea2_atlas_refine_manifest.py"),
|
|
"--print-reference-cue-candidate-draft",
|
|
"--reference-cue-review-sheet-json",
|
|
str(cue_candidate_sheet_path),
|
|
],
|
|
cwd=ROOT,
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
finally:
|
|
cue_candidate_sheet_path.unlink(missing_ok=True)
|
|
_expect(
|
|
reference_cue_candidate_cli_result.returncode == 0,
|
|
f"Atlas reference cue candidate draft CLI failed: {reference_cue_candidate_cli_result.stderr}",
|
|
)
|
|
cli_reference_cue_candidate_draft = json.loads(reference_cue_candidate_cli_result.stdout)
|
|
_expect(
|
|
cli_reference_cue_candidate_draft.get("ready_candidate_count") == 2,
|
|
"Atlas reference cue candidate draft CLI should keep ready candidate count",
|
|
)
|
|
with tempfile.TemporaryDirectory() as reference_author_tmpdir:
|
|
reference_author_root = Path(reference_author_tmpdir)
|
|
reference_author_prompt = reference_author_root / "pov_blowjob_top_down_vertical_shaft_00001_.txt"
|
|
reference_author_image = reference_author_root / "pov_blowjob_top_down_vertical_shaft_00001_.png"
|
|
reference_author_prompt.write_text(
|
|
"A controlled same-subject top-view oral baseline prompt awaiting atlas cue candidates.",
|
|
encoding="utf-8",
|
|
)
|
|
reference_author_image.write_bytes(b"fake-png")
|
|
reference_author_manifest = manifest_module.build_manifest(
|
|
reference_author_root,
|
|
subject_id="same_woman_001",
|
|
)
|
|
reference_author_draft = manifest_module.build_reference_cue_sidecar_author_draft(
|
|
reference_author_manifest,
|
|
reference_cue_candidate_draft,
|
|
variant_key="pov_blowjob_top_down_vertical_shaft",
|
|
)
|
|
_expect(
|
|
reference_author_draft.get("schema") == "sxcp_atlas_reference_cue_sidecar_author_draft_v1",
|
|
"Atlas reference sidecar author draft lost schema",
|
|
)
|
|
_expect(
|
|
reference_author_draft.get("update_count") == 1,
|
|
f"Atlas reference sidecar author draft should target the same-stem baseline entry: {reference_author_draft}",
|
|
)
|
|
reference_author_update = (reference_author_draft.get("updates") or [{}])[0]
|
|
_expect(
|
|
reference_author_update.get("sidecar_filename") == "pov_blowjob_top_down_vertical_shaft_00001_.json",
|
|
f"Atlas reference sidecar author draft should use same-stem sidecar filenames: {reference_author_update}",
|
|
)
|
|
_expect(
|
|
[variant.get("id") for variant in reference_author_update.get("prompt_variants") or []]
|
|
== ["atlas22_upper_body_stack", "atlas27_shaft_first_exact_text"],
|
|
f"Atlas reference sidecar author draft should carry reviewed prompt variants: {reference_author_update}",
|
|
)
|
|
reference_author_variants_by_id = {
|
|
variant.get("id"): variant
|
|
for variant in reference_author_update.get("prompt_variants") or []
|
|
}
|
|
_expect(
|
|
(reference_author_variants_by_id.get("atlas27_shaft_first_exact_text") or {})
|
|
.get("prompt_source", {})
|
|
.get("kind")
|
|
== "text",
|
|
f"Atlas reference sidecar author draft should mark exact-text provenance: {reference_author_update}",
|
|
)
|
|
reference_author_validation = manifest_module.validate_reference_cue_sidecar_author_draft(reference_author_draft)
|
|
_expect(
|
|
reference_author_validation.get("valid") is True,
|
|
f"Atlas reference sidecar author draft should validate before apply: {reference_author_validation}",
|
|
)
|
|
invalid_reference_author_draft = json.loads(json.dumps(reference_author_draft))
|
|
invalid_reference_author_draft["updates"][0]["prompt_variants"][0]["append_cues"][0] = (
|
|
"either the reviewed cue or another cue"
|
|
)
|
|
invalid_reference_author_validation = manifest_module.validate_reference_cue_sidecar_author_draft(
|
|
invalid_reference_author_draft
|
|
)
|
|
_expect(
|
|
invalid_reference_author_validation.get("valid") is False
|
|
and any("prompt_noise" in error for error in invalid_reference_author_validation.get("errors", [])),
|
|
f"Atlas reference sidecar author draft validation should reject noisy append cues: {invalid_reference_author_validation}",
|
|
)
|
|
reference_author_apply_report = manifest_module.apply_reference_cue_sidecar_author_draft(
|
|
reference_author_draft,
|
|
reference_author_root,
|
|
)
|
|
_expect(
|
|
reference_author_apply_report.get("schema") == "sxcp_atlas_reference_cue_sidecar_author_apply_report_v1",
|
|
"Atlas reference sidecar author apply lost schema",
|
|
)
|
|
_expect(
|
|
reference_author_apply_report.get("applied") is True
|
|
and reference_author_apply_report.get("updated_file_count") == 1,
|
|
f"Atlas reference sidecar author apply should write one sidecar: {reference_author_apply_report}",
|
|
)
|
|
authored_sidecar = json.loads(
|
|
(reference_author_root / "pov_blowjob_top_down_vertical_shaft_00001_.json").read_text(encoding="utf-8")
|
|
)
|
|
_expect(
|
|
authored_sidecar.get("prompt_variants", [{}])[0].get("reference_images") == [
|
|
"blowjob_top_view/22_blowjob_top_view.png"
|
|
],
|
|
f"Atlas reference sidecar author apply should preserve reference provenance: {authored_sidecar}",
|
|
)
|
|
rescanned_reference_author_manifest = manifest_module.build_manifest(
|
|
reference_author_root,
|
|
subject_id="same_woman_001",
|
|
)
|
|
reference_author_batch = manifest_module.build_prompt_batch(
|
|
rescanned_reference_author_manifest,
|
|
"pov_blowjob_top_down_vertical_shaft",
|
|
sampler_seed=101,
|
|
)
|
|
reference_author_batch_probes = reference_author_batch.get("probes") or []
|
|
_expect(
|
|
len(reference_author_batch_probes) == 3
|
|
and reference_author_batch_probes[1].get("reference_images") == [
|
|
"blowjob_top_view/22_blowjob_top_view.png"
|
|
],
|
|
f"Atlas reference sidecar author apply should rescan into testable prompt batches: {reference_author_batch}",
|
|
)
|
|
exact_text_probe = reference_author_batch_probes[2]
|
|
_expect(
|
|
exact_text_probe.get("text", "").startswith(
|
|
"A controlled same-subject prompt. Straight-down male POV oral close-up."
|
|
)
|
|
and "awaiting atlas cue candidates" not in exact_text_probe.get("text", ""),
|
|
f"Atlas exact-text sidecar variants should replace the baseline prompt in prompt batches: {exact_text_probe}",
|
|
)
|
|
with tempfile.NamedTemporaryFile("w", suffix=".json", delete=False, encoding="utf-8") as candidate_draft_handle:
|
|
json.dump(reference_cue_candidate_draft, candidate_draft_handle)
|
|
candidate_draft_path = Path(candidate_draft_handle.name)
|
|
with tempfile.NamedTemporaryFile("w", suffix=".json", delete=False, encoding="utf-8") as author_draft_handle:
|
|
json.dump(reference_author_draft, author_draft_handle)
|
|
author_draft_path = Path(author_draft_handle.name)
|
|
try:
|
|
reference_author_cli_result = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
str(ROOT / "tools" / "krea2_atlas_refine_manifest.py"),
|
|
"--folder",
|
|
str(reference_author_root),
|
|
"--subject-id",
|
|
"same_woman_001",
|
|
"--print-reference-cue-sidecar-author-draft",
|
|
"--reference-cue-candidate-draft-json",
|
|
str(candidate_draft_path),
|
|
"--variant-key",
|
|
"pov_blowjob_top_down_vertical_shaft",
|
|
],
|
|
cwd=ROOT,
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
reference_author_validate_cli_result = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
str(ROOT / "tools" / "krea2_atlas_refine_manifest.py"),
|
|
"--validate-reference-cue-sidecar-author-draft",
|
|
"--reference-cue-sidecar-author-draft-json",
|
|
str(author_draft_path),
|
|
],
|
|
cwd=ROOT,
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
reference_author_apply_cli_result = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
str(ROOT / "tools" / "krea2_atlas_refine_manifest.py"),
|
|
"--apply-reference-cue-sidecar-author-draft",
|
|
"--reference-cue-sidecar-author-draft-json",
|
|
str(author_draft_path),
|
|
"--folder",
|
|
str(reference_author_root),
|
|
],
|
|
cwd=ROOT,
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
finally:
|
|
candidate_draft_path.unlink(missing_ok=True)
|
|
author_draft_path.unlink(missing_ok=True)
|
|
_expect(
|
|
reference_author_cli_result.returncode == 0,
|
|
f"Atlas reference sidecar author draft CLI failed: {reference_author_cli_result.stderr}",
|
|
)
|
|
cli_reference_author_draft = json.loads(reference_author_cli_result.stdout)
|
|
_expect(
|
|
cli_reference_author_draft.get("update_count") == 1,
|
|
"Atlas reference sidecar author draft CLI should keep update count",
|
|
)
|
|
_expect(
|
|
reference_author_validate_cli_result.returncode == 0,
|
|
f"Atlas reference sidecar author validation CLI failed: {reference_author_validate_cli_result.stderr}",
|
|
)
|
|
_expect(
|
|
reference_author_apply_cli_result.returncode == 0,
|
|
f"Atlas reference sidecar author apply CLI failed: {reference_author_apply_cli_result.stderr}",
|
|
)
|
|
cli_reference_author_apply_report = json.loads(reference_author_apply_cli_result.stdout)
|
|
_expect(
|
|
cli_reference_author_apply_report.get("applied") is True,
|
|
"Atlas reference sidecar author apply CLI should report applied",
|
|
)
|
|
sidecar_scaffold = manifest_module.build_sidecar_scaffold(manifest)
|
|
_expect(
|
|
sidecar_scaffold.get("schema") == "sxcp_atlas_refine_sidecar_scaffold_v1",
|
|
"Atlas refine sidecar scaffold lost schema",
|
|
)
|
|
_expect(
|
|
sidecar_scaffold.get("scaffold_count") == 1,
|
|
f"Atlas refine sidecar scaffold should include only known baseline-only entries: {sidecar_scaffold}",
|
|
)
|
|
scaffold_entry = (sidecar_scaffold.get("scaffolds") or [{}])[0]
|
|
_expect(
|
|
scaffold_entry.get("variant_key") == "pov_handjob_upright_centered",
|
|
"Atlas refine sidecar scaffold should target the baseline-only known variant",
|
|
)
|
|
_expect(
|
|
scaffold_entry.get("sidecar_filename") == "pov_handjob_upright_centered_00001_.json",
|
|
"Atlas refine sidecar scaffold should use the same-stem sidecar filename",
|
|
)
|
|
_expect(
|
|
scaffold_entry.get("sidecar_json", {}).get("prompt_variants") == [],
|
|
"Atlas refine sidecar scaffold should not invent prompt variants",
|
|
)
|
|
_expect(
|
|
scaffold_entry.get("prompt_variant_template", {}).get("append_cues") == [],
|
|
"Atlas refine sidecar scaffold should leave append cues blank for user-authored variants",
|
|
)
|
|
_expect(
|
|
scaffold_entry.get("prompt_variant_template", {}).get("reference_images") == [],
|
|
"Atlas refine sidecar scaffold should include a blank nearest-reference image list",
|
|
)
|
|
_expect(scaffold_cli_returncode == 0, f"Atlas refine sidecar scaffold CLI failed: {scaffold_cli_stderr}")
|
|
cli_scaffold = json.loads(scaffold_cli_stdout)
|
|
_expect(
|
|
cli_scaffold.get("scaffold_count") == 1,
|
|
"Atlas refine sidecar scaffold CLI should keep scaffold count",
|
|
)
|
|
batch = manifest_module.build_prompt_batch(manifest, "pov_footjob_frontal_sole_stroke")
|
|
_expect(batch.get("schema") == "sxcp_atlas_refine_prompt_batch_v1", "Atlas refine batch lost schema")
|
|
_expect(batch.get("seed") == 101, "Atlas refine batch should use the entry sampler seed")
|
|
_expect(batch.get("variant_key") == "pov_footjob_frontal_sole_stroke", "Atlas refine batch lost variant key")
|
|
batch_probes = batch.get("probes") or []
|
|
_expect([probe.get("id") for probe in batch_probes] == [
|
|
"pov_footjob_frontal_sole_stroke_00001__baseline",
|
|
"pov_footjob_frontal_sole_stroke_00001__soles_more_forward",
|
|
], f"Atlas refine batch should include baseline then sidecar variant probes: {batch_probes}")
|
|
_expect(
|
|
batch_probes[0].get("text") == footjob.get("prompt_text"),
|
|
"Atlas refine batch baseline should preserve the exact source prompt",
|
|
)
|
|
_expect(
|
|
"the woman's soles press farther forward along the same contact line" in batch_probes[1].get("text", ""),
|
|
"Atlas refine batch should append explicit sidecar cue text",
|
|
)
|
|
_expect(
|
|
batch_probes[1].get("prompt_source", {}).get("kind") == "append_cues",
|
|
"Atlas refine batch should preserve that the candidate came from append_cues",
|
|
)
|
|
_expect(
|
|
batch_probes[1].get("prompt_source", {}).get("append_cues") == [
|
|
"the woman's soles press farther forward along the same contact line"
|
|
],
|
|
"Atlas refine batch should preserve append-cue deltas for later catalog review",
|
|
)
|
|
_expect(
|
|
batch_probes[1].get("cue_axes", {}).get("contact_depth") == "contact_line_farther_forward",
|
|
"Atlas refine batch should preserve variant cue-axis metadata",
|
|
)
|
|
_expect(
|
|
batch_probes[1].get("reference_images") == ["blowjob_top_view/22_blowjob_top_view.png"],
|
|
"Atlas refine batch should preserve prompt-variant reference-image provenance",
|
|
)
|
|
_expect(
|
|
batch_probes[1].get("seed_metadata", {}).get("micro_position_seed") == 303,
|
|
"Atlas refine batch should preserve variant micro-position seed metadata",
|
|
)
|
|
override_batch = manifest_module.build_prompt_batch(
|
|
manifest,
|
|
"pov_footjob_frontal_sole_stroke",
|
|
sampler_seed=909,
|
|
)
|
|
override_batch_probes = override_batch.get("probes") or []
|
|
_expect(
|
|
override_batch.get("seed") == 909,
|
|
"Atlas refine batch should use explicit sampler seed overrides",
|
|
)
|
|
_expect(
|
|
override_batch_probes
|
|
and all(probe.get("seed_metadata", {}).get("sampler_seed") == 909 for probe in override_batch_probes),
|
|
f"Atlas refine batch probe metadata should reflect the actual sampler seed override: {override_batch_probes}",
|
|
)
|
|
with tempfile.NamedTemporaryFile("w", suffix=".json", delete=False, encoding="utf-8") as handle:
|
|
json.dump(batch, handle)
|
|
batch_path = Path(handle.name)
|
|
try:
|
|
loaded_batch = sxcp_prompt_batch.load_batch(batch_path)
|
|
finally:
|
|
batch_path.unlink(missing_ok=True)
|
|
_expect(loaded_batch.get("seed") == 101, "Atlas refine batch should load through sxcp prompt batch helper")
|
|
_expect(len(loaded_batch.get("probes") or []) == 2, "Atlas refine batch should keep both probes through batch loader")
|
|
_expect(
|
|
(loaded_batch.get("probes") or [])[1].get("reference_images") == ["blowjob_top_view/22_blowjob_top_view.png"],
|
|
"SxCP prompt batch loader should preserve reference-image provenance metadata",
|
|
)
|
|
results = {
|
|
"seed": 101,
|
|
"channel_in": "sxcp_eval_in",
|
|
"probes": [
|
|
{
|
|
"id": "pov_footjob_frontal_sole_stroke_00001__baseline",
|
|
"prompt_order": "subject_first",
|
|
"turn": 11,
|
|
"image_path": "/tmp/pov_footjob_baseline.png",
|
|
"returned_seed": 101,
|
|
},
|
|
{
|
|
"id": "pov_footjob_frontal_sole_stroke_00001__soles_more_forward",
|
|
"prompt_order": "subject_first",
|
|
"turn": 12,
|
|
"image_path": "/tmp/pov_footjob_soles_more_forward.png",
|
|
"returned_seed": 101,
|
|
},
|
|
],
|
|
}
|
|
result_sheet = manifest_module.build_result_sheet(batch, results, notes="visual scoring pending")
|
|
_expect(result_sheet.get("schema") == "sxcp_atlas_refine_result_sheet_v1", "Atlas refine result sheet lost schema")
|
|
_expect(result_sheet.get("seed") == 101, "Atlas refine result sheet should keep the fixed sampler seed")
|
|
_expect(
|
|
result_sheet.get("variant_key") == "pov_footjob_frontal_sole_stroke",
|
|
"Atlas refine result sheet lost variant key",
|
|
)
|
|
sheet_probes = result_sheet.get("probes") or []
|
|
_expect([probe.get("id") for probe in sheet_probes] == [
|
|
"pov_footjob_frontal_sole_stroke_00001__baseline",
|
|
"pov_footjob_frontal_sole_stroke_00001__soles_more_forward",
|
|
], "Atlas refine result sheet should preserve batch/result probe order")
|
|
_expect(
|
|
sheet_probes[0].get("image_path") == "/tmp/pov_footjob_baseline.png",
|
|
"Atlas refine result sheet should keep baseline image path",
|
|
)
|
|
_expect(
|
|
sheet_probes[1].get("turn") == 12,
|
|
"Atlas refine result sheet should keep returned turn numbers",
|
|
)
|
|
_expect(
|
|
sheet_probes[1].get("text") == batch_probes[1].get("text"),
|
|
"Atlas refine result sheet should keep the exact candidate prompt text",
|
|
)
|
|
_expect(
|
|
sheet_probes[1].get("cue_axes", {}).get("contact_depth") == "contact_line_farther_forward",
|
|
"Atlas refine result sheet should keep candidate cue axes",
|
|
)
|
|
_expect(
|
|
sheet_probes[1].get("prompt_source", {}).get("kind") == "append_cues",
|
|
"Atlas refine result sheet should keep append-cue provenance",
|
|
)
|
|
_expect(
|
|
sheet_probes[1].get("reference_images") == ["blowjob_top_view/22_blowjob_top_view.png"],
|
|
"Atlas refine result sheet should preserve nearest atlas reference-image provenance",
|
|
)
|
|
_expect(
|
|
sheet_probes[1].get("score", {}).get("subject_identity") is None,
|
|
"Atlas refine result sheet should leave subject-identity score unfilled for visual analysis",
|
|
)
|
|
for score_key in (
|
|
"atlas_pose_match",
|
|
"contact_match",
|
|
"pose_ownership",
|
|
"workspace_continuity",
|
|
"clothing_visibility",
|
|
"subject_identity",
|
|
"expression_eye_control",
|
|
"anatomy_proportion",
|
|
"prompt_noise",
|
|
):
|
|
_expect(score_key in sheet_probes[1].get("score", {}), f"Atlas refine result sheet missing score slot {score_key}")
|
|
with tempfile.NamedTemporaryFile("w", suffix=".json", delete=False, encoding="utf-8") as batch_handle:
|
|
json.dump(batch, batch_handle)
|
|
cli_batch_path = Path(batch_handle.name)
|
|
with tempfile.NamedTemporaryFile("w", suffix=".json", delete=False, encoding="utf-8") as result_handle:
|
|
json.dump(results, result_handle)
|
|
cli_result_path = Path(result_handle.name)
|
|
try:
|
|
sheet_cli_result = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
str(ROOT / "tools" / "krea2_atlas_refine_manifest.py"),
|
|
"--print-result-sheet",
|
|
"--batch-json",
|
|
str(cli_batch_path),
|
|
"--result-json",
|
|
str(cli_result_path),
|
|
"--notes",
|
|
"visual scoring pending",
|
|
],
|
|
cwd=ROOT,
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
finally:
|
|
cli_batch_path.unlink(missing_ok=True)
|
|
cli_result_path.unlink(missing_ok=True)
|
|
_expect(sheet_cli_result.returncode == 0, f"Atlas refine result-sheet CLI failed: {sheet_cli_result.stderr}")
|
|
cli_sheet = json.loads(sheet_cli_result.stdout)
|
|
_expect(
|
|
cli_sheet.get("schema") == "sxcp_atlas_refine_result_sheet_v1",
|
|
"Atlas refine result-sheet CLI lost schema",
|
|
)
|
|
_expect(
|
|
len(cli_sheet.get("probes") or []) == 2,
|
|
"Atlas refine result-sheet CLI should keep both probes",
|
|
)
|
|
scored_sheet = json.loads(json.dumps(result_sheet))
|
|
scored_sheet["probes"][0]["score"].update(
|
|
{
|
|
"atlas_pose_match": "baseline",
|
|
"contact_match": "baseline",
|
|
"pose_ownership": "pass",
|
|
"workspace_continuity": "pass",
|
|
"clothing_visibility": "pass",
|
|
"subject_identity": "pass",
|
|
"expression_eye_control": "pass",
|
|
"anatomy_proportion": "pass",
|
|
"prompt_noise": "pass",
|
|
}
|
|
)
|
|
scored_sheet["probes"][1]["score"].update(
|
|
{
|
|
"atlas_pose_match": "partial",
|
|
"contact_match": "pass",
|
|
"pose_ownership": "pass",
|
|
"workspace_continuity": "pass",
|
|
"clothing_visibility": "pass",
|
|
"subject_identity": "pass",
|
|
"expression_eye_control": "pass",
|
|
"anatomy_proportion": "pass",
|
|
"prompt_noise": "pass",
|
|
}
|
|
)
|
|
scored_sheet["probes"][1]["analysis_notes"] = "Candidate keeps the same subject and moves the foot contact axis."
|
|
promotion_report = manifest_module.build_promotion_report(scored_sheet)
|
|
_expect(
|
|
promotion_report.get("schema") == "sxcp_atlas_refine_promotion_report_v1",
|
|
"Atlas refine promotion report lost schema",
|
|
)
|
|
_expect(promotion_report.get("seed") == 101, "Atlas refine promotion report should keep the fixed sampler seed")
|
|
_expect(promotion_report.get("promotion_ready_count") == 1, "Atlas refine promotion report should count ready candidates")
|
|
_expect(
|
|
promotion_report.get("blocked_count") == 0,
|
|
"Atlas refine promotion report should not block a fully scored passing candidate",
|
|
)
|
|
candidates = promotion_report.get("candidates") or []
|
|
_expect(len(candidates) == 1, f"Atlas refine promotion report should include one non-baseline candidate: {candidates}")
|
|
_expect(candidates[0].get("decision") == "seedable_candidate", "Atlas refine promotion report should mark passing candidate seedable")
|
|
_expect(
|
|
candidates[0].get("prompt_variant_id") == "soles_more_forward",
|
|
"Atlas refine promotion report should recover sidecar prompt variant id",
|
|
)
|
|
_expect(
|
|
candidates[0].get("cue_axes", {}).get("contact_depth") == "contact_line_farther_forward",
|
|
"Atlas refine promotion report should keep candidate cue axes",
|
|
)
|
|
_expect(
|
|
candidates[0].get("reference_images") == ["blowjob_top_view/22_blowjob_top_view.png"],
|
|
"Atlas refine promotion report should keep candidate reference-image provenance",
|
|
)
|
|
_expect(
|
|
candidates[0].get("prompt_source", {}).get("append_cues") == [
|
|
"the woman's soles press farther forward along the same contact line"
|
|
],
|
|
"Atlas refine promotion report should keep append-cue provenance for catalog drafts",
|
|
)
|
|
noisy_scored_sheet = json.loads(json.dumps(scored_sheet))
|
|
noisy_scored_sheet["probes"][1]["text"] += " either the foot or hand moves while the contact stays."
|
|
noisy_promotion_report = manifest_module.build_promotion_report(noisy_scored_sheet)
|
|
noisy_candidate = (noisy_promotion_report.get("candidates") or [{}])[0]
|
|
_expect(
|
|
noisy_candidate.get("decision") == "rejected"
|
|
and "prompt_noise_issue" in noisy_candidate.get("blockers", [])
|
|
and any(issue.get("code") == "option_word" for issue in noisy_candidate.get("prompt_noise_issues", [])),
|
|
f"Atlas refine promotion report should reject noisy candidate text even when manual prompt_noise score passes: {noisy_candidate}",
|
|
)
|
|
noisy_sidecar_draft = manifest_module.build_sidecar_update_draft(noisy_promotion_report)
|
|
_expect(
|
|
noisy_sidecar_draft.get("ready_candidate_count") == 0,
|
|
f"Atlas refine sidecar draft should skip noisy promoted candidates: {noisy_sidecar_draft}",
|
|
)
|
|
scored_sheet["probes"][1]["score"]["subject_identity"] = "fail"
|
|
rejected_report = manifest_module.build_promotion_report(scored_sheet)
|
|
_expect(
|
|
rejected_report.get("promotion_ready_count") == 0,
|
|
"Atlas refine promotion report should block candidates that lose subject identity",
|
|
)
|
|
_expect(
|
|
rejected_report.get("candidates", [{}])[0].get("decision") == "rejected",
|
|
"Atlas refine promotion report should reject failed preservation gates",
|
|
)
|
|
scored_sheet["probes"][1]["score"]["subject_identity"] = "pass"
|
|
with tempfile.NamedTemporaryFile("w", suffix=".json", delete=False, encoding="utf-8") as sheet_handle:
|
|
json.dump(scored_sheet, sheet_handle)
|
|
cli_sheet_path = Path(sheet_handle.name)
|
|
try:
|
|
promotion_cli_result = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
str(ROOT / "tools" / "krea2_atlas_refine_manifest.py"),
|
|
"--print-promotion-report",
|
|
"--result-sheet-json",
|
|
str(cli_sheet_path),
|
|
],
|
|
cwd=ROOT,
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
finally:
|
|
cli_sheet_path.unlink(missing_ok=True)
|
|
_expect(promotion_cli_result.returncode == 0, f"Atlas refine promotion-report CLI failed: {promotion_cli_result.stderr}")
|
|
cli_promotion_report = json.loads(promotion_cli_result.stdout)
|
|
_expect(
|
|
cli_promotion_report.get("promotion_ready_count") == 1,
|
|
"Atlas refine promotion-report CLI should keep ready candidate count",
|
|
)
|
|
sidecar_draft = manifest_module.build_sidecar_update_draft(promotion_report)
|
|
_expect(
|
|
sidecar_draft.get("schema") == "sxcp_atlas_refine_sidecar_update_draft_v1",
|
|
"Atlas refine sidecar update draft lost schema",
|
|
)
|
|
_expect(
|
|
sidecar_draft.get("ready_candidate_count") == 1,
|
|
"Atlas refine sidecar update draft should count ready candidates",
|
|
)
|
|
updates = sidecar_draft.get("updates") or []
|
|
_expect(len(updates) == 1, f"Atlas refine sidecar update draft should include one sidecar update: {updates}")
|
|
_expect(
|
|
updates[0].get("sidecar_filename") == "pov_footjob_frontal_sole_stroke_00001_.json",
|
|
"Atlas refine sidecar update draft should preserve the original same-stem sidecar filename",
|
|
)
|
|
drafted_variants = updates[0].get("prompt_variants") or []
|
|
_expect(len(drafted_variants) == 1, "Atlas refine sidecar update draft should include one prompt variant")
|
|
_expect(
|
|
drafted_variants[0].get("id") == "soles_more_forward",
|
|
"Atlas refine sidecar update draft should preserve prompt variant id",
|
|
)
|
|
_expect(
|
|
drafted_variants[0].get("text") == sheet_probes[1].get("text"),
|
|
"Atlas refine sidecar update draft should use the exact tested prompt text",
|
|
)
|
|
_expect(
|
|
drafted_variants[0].get("prompt_source", {}).get("kind") == "append_cues",
|
|
"Atlas refine sidecar update draft should keep source prompt-variant kind",
|
|
)
|
|
_expect(
|
|
drafted_variants[0].get("prompt_source", {}).get("append_cues") == [
|
|
"the woman's soles press farther forward along the same contact line"
|
|
],
|
|
"Atlas refine sidecar update draft should keep tested append-cue deltas",
|
|
)
|
|
_expect(
|
|
drafted_variants[0].get("cue_axes", {}).get("contact_depth") == "contact_line_farther_forward",
|
|
"Atlas refine sidecar update draft should keep cue-axis metadata",
|
|
)
|
|
_expect(
|
|
drafted_variants[0].get("reference_images") == ["blowjob_top_view/22_blowjob_top_view.png"],
|
|
"Atlas refine sidecar update draft should keep nearest atlas reference-image provenance",
|
|
)
|
|
_expect(
|
|
drafted_variants[0].get("evidence", {}).get("image_path") == "/tmp/pov_footjob_soles_more_forward.png",
|
|
"Atlas refine sidecar update draft should keep candidate evidence image path",
|
|
)
|
|
_expect(
|
|
drafted_variants[0].get("evidence", {}).get("score", {}).get("subject_identity") == "pass",
|
|
"Atlas refine sidecar update draft should keep visual score evidence",
|
|
)
|
|
rejected_draft = manifest_module.build_sidecar_update_draft(rejected_report)
|
|
_expect(
|
|
rejected_draft.get("ready_candidate_count") == 0,
|
|
"Atlas refine sidecar update draft should not include rejected candidates",
|
|
)
|
|
with tempfile.NamedTemporaryFile("w", suffix=".json", delete=False, encoding="utf-8") as report_handle:
|
|
json.dump(promotion_report, report_handle)
|
|
cli_report_path = Path(report_handle.name)
|
|
try:
|
|
sidecar_cli_result = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
str(ROOT / "tools" / "krea2_atlas_refine_manifest.py"),
|
|
"--print-sidecar-update-draft",
|
|
"--promotion-report-json",
|
|
str(cli_report_path),
|
|
],
|
|
cwd=ROOT,
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
finally:
|
|
cli_report_path.unlink(missing_ok=True)
|
|
_expect(sidecar_cli_result.returncode == 0, f"Atlas refine sidecar-update CLI failed: {sidecar_cli_result.stderr}")
|
|
cli_sidecar_draft = json.loads(sidecar_cli_result.stdout)
|
|
_expect(
|
|
cli_sidecar_draft.get("ready_candidate_count") == 1,
|
|
"Atlas refine sidecar-update CLI should keep ready candidate count",
|
|
)
|
|
draft_validation = manifest_module.validate_sidecar_update_draft(sidecar_draft)
|
|
_expect(
|
|
draft_validation.get("schema") == "sxcp_atlas_refine_sidecar_update_validation_v1",
|
|
"Atlas refine sidecar update validation lost schema",
|
|
)
|
|
_expect(draft_validation.get("valid") is True, f"Atlas refine sidecar update draft should validate: {draft_validation}")
|
|
_expect(draft_validation.get("error_count") == 0, "Atlas refine sidecar update validation should have no errors")
|
|
_expect(draft_validation.get("validated_variant_count") == 1, "Atlas refine sidecar update validation should count variants")
|
|
invalid_draft = json.loads(json.dumps(sidecar_draft))
|
|
invalid_draft["updates"][0]["prompt_variants"][0]["evidence"]["score"]["subject_identity"] = "fail"
|
|
invalid_draft["updates"][0]["prompt_variants"][0]["negative_prompt"] = "do not include this"
|
|
invalid_draft["updates"][0]["prompt_variants"][0]["prompt_source"]["prompt_variant_id"] = "wrong_axis"
|
|
invalid_validation = manifest_module.validate_sidecar_update_draft(invalid_draft)
|
|
_expect(invalid_validation.get("valid") is False, "Atlas refine sidecar update validation should reject failed evidence")
|
|
_expect(
|
|
any("subject_identity=fail" in error for error in invalid_validation.get("errors", [])),
|
|
f"Atlas refine sidecar update validation should report failed subject identity: {invalid_validation}",
|
|
)
|
|
_expect(
|
|
any("negative_prompt" in error for error in invalid_validation.get("errors", [])),
|
|
f"Atlas refine sidecar update validation should reject negative prompt fields: {invalid_validation}",
|
|
)
|
|
_expect(
|
|
any("prompt_source.prompt_variant_id" in error for error in invalid_validation.get("errors", [])),
|
|
f"Atlas refine sidecar update validation should reject mismatched prompt-source ids: {invalid_validation}",
|
|
)
|
|
with tempfile.NamedTemporaryFile("w", suffix=".json", delete=False, encoding="utf-8") as draft_handle:
|
|
json.dump(sidecar_draft, draft_handle)
|
|
cli_draft_path = Path(draft_handle.name)
|
|
try:
|
|
validation_cli_result = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
str(ROOT / "tools" / "krea2_atlas_refine_manifest.py"),
|
|
"--validate-sidecar-update-draft",
|
|
"--sidecar-update-draft-json",
|
|
str(cli_draft_path),
|
|
],
|
|
cwd=ROOT,
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
finally:
|
|
cli_draft_path.unlink(missing_ok=True)
|
|
_expect(validation_cli_result.returncode == 0, f"Atlas refine sidecar-update validation CLI failed: {validation_cli_result.stderr}")
|
|
cli_draft_validation = json.loads(validation_cli_result.stdout)
|
|
_expect(
|
|
cli_draft_validation.get("valid") is True,
|
|
"Atlas refine sidecar-update validation CLI should validate the draft",
|
|
)
|
|
with tempfile.TemporaryDirectory() as duplicate_apply_tmpdir:
|
|
duplicate_apply_root = Path(duplicate_apply_tmpdir)
|
|
duplicate_apply_sidecar = duplicate_apply_root / "pov_footjob_frontal_sole_stroke_00001_.json"
|
|
duplicate_apply_sidecar.write_text(
|
|
json.dumps(
|
|
{
|
|
"notes": "ambiguous existing sidecar should not be silently rewritten",
|
|
"prompt_variants": [
|
|
{
|
|
"id": "old_axis",
|
|
"text": "First old reviewed prompt variant.",
|
|
},
|
|
{
|
|
"id": "old_axis",
|
|
"text": "Second old reviewed prompt variant.",
|
|
},
|
|
],
|
|
},
|
|
ensure_ascii=True,
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
try:
|
|
manifest_module.apply_sidecar_update_draft(sidecar_draft, duplicate_apply_root)
|
|
except ValueError as exc:
|
|
_expect(
|
|
"old_axis" in str(exc) and "duplicated" in str(exc),
|
|
f"Atlas refine sidecar apply duplicate existing id error should identify the duplicated id: {exc}",
|
|
)
|
|
else:
|
|
raise AssertionError("Atlas refine sidecar apply should reject existing duplicate prompt_variant ids")
|
|
with tempfile.TemporaryDirectory() as apply_tmpdir:
|
|
apply_root = Path(apply_tmpdir)
|
|
sidecar_path = apply_root / "pov_footjob_frontal_sole_stroke_00001_.json"
|
|
sidecar_path.write_text(
|
|
json.dumps(
|
|
{
|
|
"notes": "preserve existing sidecar notes",
|
|
"seed_metadata": {"sampler_seed": 101},
|
|
"prompt_variants": [
|
|
{
|
|
"id": "old_axis",
|
|
"text": "Old reviewed prompt variant.",
|
|
"cue_axes": {"foot_position": "old"},
|
|
}
|
|
],
|
|
},
|
|
ensure_ascii=True,
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
apply_report = manifest_module.apply_sidecar_update_draft(sidecar_draft, apply_root)
|
|
_expect(
|
|
apply_report.get("schema") == "sxcp_atlas_refine_sidecar_apply_report_v1",
|
|
"Atlas refine sidecar apply report lost schema",
|
|
)
|
|
_expect(apply_report.get("applied") is True, "Atlas refine sidecar apply should mark applied")
|
|
_expect(apply_report.get("updated_file_count") == 1, "Atlas refine sidecar apply should update one sidecar")
|
|
applied_sidecar = json.loads(sidecar_path.read_text(encoding="utf-8"))
|
|
_expect(
|
|
applied_sidecar.get("notes") == "preserve existing sidecar notes",
|
|
"Atlas refine sidecar apply should preserve unrelated sidecar metadata",
|
|
)
|
|
applied_variants = applied_sidecar.get("prompt_variants") or []
|
|
_expect(
|
|
[variant.get("id") for variant in applied_variants] == ["old_axis", "soles_more_forward"],
|
|
f"Atlas refine sidecar apply should append the ready variant without dropping existing variants: {applied_variants}",
|
|
)
|
|
_expect(
|
|
applied_variants[1].get("text") == sheet_probes[1].get("text"),
|
|
"Atlas refine sidecar apply should write exact tested prompt text",
|
|
)
|
|
_expect(
|
|
applied_variants[1].get("evidence", {}).get("score", {}).get("subject_identity") == "pass",
|
|
"Atlas refine sidecar apply should preserve evidence scores",
|
|
)
|
|
_expect(
|
|
applied_variants[1].get("prompt_source", {}).get("append_cues") == [
|
|
"the woman's soles press farther forward along the same contact line"
|
|
],
|
|
"Atlas refine sidecar apply should preserve append-cue provenance",
|
|
)
|
|
second_apply_report = manifest_module.apply_sidecar_update_draft(sidecar_draft, apply_root)
|
|
_expect(
|
|
second_apply_report.get("updated_file_count") == 1,
|
|
"Atlas refine sidecar apply should be idempotent and still report the touched sidecar",
|
|
)
|
|
applied_again = json.loads(sidecar_path.read_text(encoding="utf-8"))
|
|
_expect(
|
|
[variant.get("id") for variant in applied_again.get("prompt_variants", [])].count("soles_more_forward") == 1,
|
|
"Atlas refine sidecar apply should upsert prompt variants by id instead of duplicating them",
|
|
)
|
|
with tempfile.NamedTemporaryFile("w", suffix=".json", delete=False, encoding="utf-8") as apply_draft_handle:
|
|
json.dump(sidecar_draft, apply_draft_handle)
|
|
cli_apply_draft_path = Path(apply_draft_handle.name)
|
|
try:
|
|
apply_cli_result = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
str(ROOT / "tools" / "krea2_atlas_refine_manifest.py"),
|
|
"--apply-sidecar-update-draft",
|
|
"--sidecar-update-draft-json",
|
|
str(cli_apply_draft_path),
|
|
"--folder",
|
|
str(apply_root),
|
|
],
|
|
cwd=ROOT,
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
finally:
|
|
cli_apply_draft_path.unlink(missing_ok=True)
|
|
_expect(apply_cli_result.returncode == 0, f"Atlas refine sidecar apply CLI failed: {apply_cli_result.stderr}")
|
|
cli_apply_report = json.loads(apply_cli_result.stdout)
|
|
_expect(
|
|
cli_apply_report.get("applied") is True,
|
|
"Atlas refine sidecar apply CLI should report applied",
|
|
)
|
|
# Add the paired prompt/image artifacts after apply so the manifest scanner
|
|
# can roundtrip the sidecar update through the normal atlas-refine path.
|
|
(apply_root / "pov_footjob_frontal_sole_stroke_00001_.txt").write_text(
|
|
footjob.get("prompt_text", ""),
|
|
encoding="utf-8",
|
|
)
|
|
(apply_root / "pov_footjob_frontal_sole_stroke_00001_.png").write_bytes(b"fake-png")
|
|
applied_manifest = manifest_module.build_manifest(apply_root, subject_id="same_woman_001")
|
|
applied_entry = (applied_manifest.get("entries") or [{}])[0]
|
|
applied_prompt_variants = {
|
|
variant.get("id"): variant for variant in applied_entry.get("prompt_variants", [])
|
|
}
|
|
_expect(
|
|
applied_prompt_variants.get("soles_more_forward", {}).get("text") == sheet_probes[1].get("text"),
|
|
"Atlas refine applied sidecar should rescan with the exact tested prompt text",
|
|
)
|
|
_expect(
|
|
applied_prompt_variants.get("soles_more_forward", {}).get("evidence", {}).get("image_path")
|
|
== "/tmp/pov_footjob_soles_more_forward.png",
|
|
"Atlas refine applied sidecar should rescan with evidence image provenance",
|
|
)
|
|
_expect(
|
|
applied_prompt_variants.get("soles_more_forward", {}).get("prompt_source", {}).get("kind") == "append_cues",
|
|
"Atlas refine applied sidecar should rescan with append-cue provenance",
|
|
)
|
|
_expect(
|
|
applied_prompt_variants.get("soles_more_forward", {}).get("reference_images")
|
|
== ["blowjob_top_view/22_blowjob_top_view.png"],
|
|
"Atlas refine applied sidecar should rescan with reference-image provenance",
|
|
)
|
|
catalog_cue_draft = manifest_module.build_catalog_cue_draft(
|
|
applied_manifest,
|
|
variant_key="pov_footjob_frontal_sole_stroke",
|
|
)
|
|
_expect(
|
|
catalog_cue_draft.get("schema") == "sxcp_atlas_refine_catalog_cue_draft_v1",
|
|
"Atlas refine catalog cue draft lost schema",
|
|
)
|
|
_expect(
|
|
catalog_cue_draft.get("ready_cue_count") == 1,
|
|
f"Atlas refine catalog cue draft should include one seedable append-cue candidate: {catalog_cue_draft}",
|
|
)
|
|
catalog_cue_candidates = catalog_cue_draft.get("candidates") or []
|
|
_expect(
|
|
catalog_cue_candidates[0].get("prompt_variant_cues") == [
|
|
"the woman's soles press farther forward along the same contact line"
|
|
],
|
|
"Atlas refine catalog cue draft should carry exact tested append cues",
|
|
)
|
|
_expect(
|
|
catalog_cue_candidates[0].get("evidence", {}).get("score", {}).get("subject_identity") == "pass",
|
|
"Atlas refine catalog cue draft should require seedable visual evidence",
|
|
)
|
|
_expect(
|
|
catalog_cue_candidates[0].get("reference_images") == ["blowjob_top_view/22_blowjob_top_view.png"],
|
|
"Atlas refine catalog cue draft should carry nearest atlas reference-image provenance",
|
|
)
|
|
catalog_cue_cli_result = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
str(ROOT / "tools" / "krea2_atlas_refine_manifest.py"),
|
|
"--folder",
|
|
str(apply_root),
|
|
"--subject-id",
|
|
"same_woman_001",
|
|
"--print-catalog-cue-draft",
|
|
"--variant-key",
|
|
"pov_footjob_frontal_sole_stroke",
|
|
],
|
|
cwd=ROOT,
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
_expect(catalog_cue_cli_result.returncode == 0, f"Atlas refine catalog cue draft CLI failed: {catalog_cue_cli_result.stderr}")
|
|
cli_catalog_cue_draft = json.loads(catalog_cue_cli_result.stdout)
|
|
_expect(
|
|
cli_catalog_cue_draft.get("ready_cue_count") == 1,
|
|
"Atlas refine catalog cue draft CLI should keep ready cue count",
|
|
)
|
|
applied_coverage = manifest_module.build_coverage_report(applied_manifest)
|
|
applied_footjob_coverage = (applied_coverage.get("entries") or [{}])[0]
|
|
_expect(
|
|
applied_footjob_coverage.get("state") == "ready_for_catalog_review",
|
|
f"Atlas refine coverage should mark seedable append-cue sidecars ready for catalog review: {applied_footjob_coverage}",
|
|
)
|
|
_expect(
|
|
applied_footjob_coverage.get("seedable_variant_count") == 1,
|
|
"Atlas refine coverage should count seedable variants",
|
|
)
|
|
_expect(
|
|
applied_footjob_coverage.get("catalog_cue_candidate_count") == 1,
|
|
"Atlas refine coverage should count seedable append-cue catalog candidates",
|
|
)
|
|
coverage_cli_result = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
str(ROOT / "tools" / "krea2_atlas_refine_manifest.py"),
|
|
"--folder",
|
|
str(apply_root),
|
|
"--subject-id",
|
|
"same_woman_001",
|
|
"--print-coverage-report",
|
|
],
|
|
cwd=ROOT,
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
_expect(coverage_cli_result.returncode == 0, f"Atlas refine coverage report CLI failed: {coverage_cli_result.stderr}")
|
|
cli_coverage = json.loads(coverage_cli_result.stdout)
|
|
_expect(
|
|
cli_coverage.get("ready_for_catalog_review_count") == 1,
|
|
"Atlas refine coverage report CLI should count catalog-review-ready entries",
|
|
)
|
|
applied_batch = manifest_module.build_prompt_batch(applied_manifest, "pov_footjob_frontal_sole_stroke")
|
|
applied_batch_probes = applied_batch.get("probes") or []
|
|
applied_variant_probe = next(
|
|
(
|
|
probe
|
|
for probe in applied_batch_probes
|
|
if probe.get("id") == "pov_footjob_frontal_sole_stroke_00001__soles_more_forward"
|
|
),
|
|
{},
|
|
)
|
|
_expect(
|
|
applied_variant_probe.get("text") == sheet_probes[1].get("text"),
|
|
"Atlas refine applied sidecar should regenerate the exact tested prompt in the next batch",
|
|
)
|
|
_expect(
|
|
applied_variant_probe.get("evidence", {}).get("score", {}).get("subject_identity") == "pass",
|
|
"Atlas refine applied sidecar should carry evidence into regenerated batch probes",
|
|
)
|
|
seed_selection = manifest_module.select_seeded_prompt_variant(
|
|
applied_manifest,
|
|
"pov_footjob_frontal_sole_stroke",
|
|
selection_seed=202,
|
|
seed_slot="atlas_cue_seed",
|
|
)
|
|
_expect(
|
|
seed_selection.get("schema") == "sxcp_atlas_refine_seed_selection_v1",
|
|
"Atlas refine seed selection lost schema",
|
|
)
|
|
_expect(seed_selection.get("selection_seed") == 202, "Atlas refine seed selection should keep the cue seed")
|
|
_expect(seed_selection.get("seed_slot") == "atlas_cue_seed", "Atlas refine seed selection should keep seed slot")
|
|
_expect(seed_selection.get("eligible_candidate_count") == 1, "Atlas refine seed selection should only use promoted candidates")
|
|
_expect(
|
|
seed_selection.get("selected", {}).get("prompt_variant_id") == "soles_more_forward",
|
|
"Atlas refine seed selection should select the promoted prompt variant",
|
|
)
|
|
_expect(
|
|
seed_selection.get("selected", {}).get("text") == sheet_probes[1].get("text"),
|
|
"Atlas refine seed selection should preserve exact tested prompt text",
|
|
)
|
|
_expect(
|
|
seed_selection.get("selected", {}).get("evidence", {}).get("score", {}).get("subject_identity") == "pass",
|
|
"Atlas refine seed selection should carry evidence score",
|
|
)
|
|
_expect(
|
|
seed_selection.get("ineligible", [{}])[0].get("prompt_variant_id") == "old_axis",
|
|
"Atlas refine seed selection should report unproven variants as ineligible",
|
|
)
|
|
_expect(
|
|
"missing_seedable_evidence" in seed_selection.get("ineligible", [{}])[0].get("reason", ""),
|
|
"Atlas refine seed selection should explain why unproven variants are ineligible",
|
|
)
|
|
try:
|
|
manifest_module.select_seeded_prompt_variant(
|
|
applied_manifest,
|
|
"pov_footjob_frontal_sole_stroke",
|
|
selection_seed=202,
|
|
seed_slot="sampler_seed",
|
|
)
|
|
except ValueError as exc:
|
|
_expect(
|
|
"seed_slot" in str(exc) and "sampler_seed" in str(exc),
|
|
f"Atlas refine seed selection should reject sampler_seed as a cue slot: {exc}",
|
|
)
|
|
else:
|
|
raise AssertionError("Atlas refine seed selection should not allow cue selection through sampler_seed")
|
|
seed_selection_again = manifest_module.select_seeded_prompt_variant(
|
|
applied_manifest,
|
|
"pov_footjob_frontal_sole_stroke",
|
|
selection_seed=202,
|
|
seed_slot="atlas_cue_seed",
|
|
)
|
|
_expect(
|
|
seed_selection_again.get("selected", {}).get("prompt_variant_id")
|
|
== seed_selection.get("selected", {}).get("prompt_variant_id"),
|
|
"Atlas refine seed selection should be deterministic for the same cue seed",
|
|
)
|
|
order_manifest = json.loads(json.dumps(applied_manifest))
|
|
order_entry = (order_manifest.get("entries") or [{}])[0]
|
|
soles_variant = json.loads(json.dumps(applied_prompt_variants.get("soles_more_forward") or {}))
|
|
ankle_variant = json.loads(json.dumps(soles_variant))
|
|
ankle_variant["id"] = "ankle_angle_shift"
|
|
ankle_variant["text"] = (
|
|
footjob.get("prompt_text", "")
|
|
+ " the woman's ankle angle turns inward while the same contact line stays centered"
|
|
)
|
|
ankle_variant["cue_axes"]["foot_position"] = "ankle_angle_shift"
|
|
ankle_variant["prompt_source"] = {
|
|
"kind": "append_cues",
|
|
"prompt_variant_id": "ankle_angle_shift",
|
|
"append_cues": [
|
|
"the woman's ankle angle turns inward while the same contact line stays centered"
|
|
],
|
|
"tested_text_sha256": manifest_module._sha256_text(ankle_variant["text"]),
|
|
}
|
|
ankle_variant["evidence"]["image_path"] = "/tmp/pov_footjob_ankle_angle_shift.png"
|
|
order_entry["prompt_variants"] = [soles_variant, ankle_variant]
|
|
reversed_order_manifest = json.loads(json.dumps(order_manifest))
|
|
(reversed_order_manifest.get("entries") or [{}])[0]["prompt_variants"] = [
|
|
ankle_variant,
|
|
soles_variant,
|
|
]
|
|
ordered_selection = manifest_module.select_seeded_prompt_variant(
|
|
order_manifest,
|
|
"pov_footjob_frontal_sole_stroke",
|
|
selection_seed=1,
|
|
seed_slot="atlas_cue_seed",
|
|
)
|
|
reversed_order_selection = manifest_module.select_seeded_prompt_variant(
|
|
reversed_order_manifest,
|
|
"pov_footjob_frontal_sole_stroke",
|
|
selection_seed=1,
|
|
seed_slot="atlas_cue_seed",
|
|
)
|
|
_expect(
|
|
ordered_selection.get("selected", {}).get("prompt_variant_id")
|
|
== reversed_order_selection.get("selected", {}).get("prompt_variant_id")
|
|
== "soles_more_forward",
|
|
f"Atlas refine seed selection should be independent of sidecar prompt-variant order: {ordered_selection} vs {reversed_order_selection}",
|
|
)
|
|
selection_cli_result = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
str(ROOT / "tools" / "krea2_atlas_refine_manifest.py"),
|
|
"--folder",
|
|
str(apply_root),
|
|
"--subject-id",
|
|
"same_woman_001",
|
|
"--print-seed-selection",
|
|
"--variant-key",
|
|
"pov_footjob_frontal_sole_stroke",
|
|
"--selection-seed",
|
|
"202",
|
|
"--seed-slot",
|
|
"atlas_cue_seed",
|
|
],
|
|
cwd=ROOT,
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
_expect(selection_cli_result.returncode == 0, f"Atlas refine seed-selection CLI failed: {selection_cli_result.stderr}")
|
|
cli_seed_selection = json.loads(selection_cli_result.stdout)
|
|
_expect(
|
|
cli_seed_selection.get("selected", {}).get("prompt_variant_id") == "soles_more_forward",
|
|
"Atlas refine seed-selection CLI should select the promoted prompt variant",
|
|
)
|
|
selected_batch = manifest_module.build_seed_selected_prompt_batch(
|
|
applied_manifest,
|
|
"pov_footjob_frontal_sole_stroke",
|
|
selection_seed=202,
|
|
sampler_seed=101,
|
|
seed_slot="atlas_cue_seed",
|
|
)
|
|
_expect(
|
|
selected_batch.get("schema") == "sxcp_atlas_refine_prompt_batch_v1",
|
|
"Atlas refine seed-selected batch should use prompt batch schema",
|
|
)
|
|
_expect(selected_batch.get("seed") == 101, "Atlas refine seed-selected batch should keep sampler seed")
|
|
_expect(
|
|
selected_batch.get("selection", {}).get("selected", {}).get("prompt_variant_id") == "soles_more_forward",
|
|
"Atlas refine seed-selected batch should include the seed selection report",
|
|
)
|
|
selected_batch_probes = selected_batch.get("probes") or []
|
|
_expect(
|
|
[probe.get("id") for probe in selected_batch_probes]
|
|
== [
|
|
"pov_footjob_frontal_sole_stroke_00001__baseline",
|
|
"pov_footjob_frontal_sole_stroke_00001__soles_more_forward",
|
|
],
|
|
f"Atlas refine seed-selected batch should contain baseline and selected candidate only: {selected_batch_probes}",
|
|
)
|
|
_expect(
|
|
selected_batch_probes[1].get("text") == sheet_probes[1].get("text"),
|
|
"Atlas refine seed-selected batch should use exact selected prompt text",
|
|
)
|
|
_expect(
|
|
selected_batch_probes[1].get("seed_metadata", {}).get("atlas_cue_seed") == 202,
|
|
"Atlas refine seed-selected batch should record selection seed in the requested seed slot",
|
|
)
|
|
_expect(
|
|
selected_batch_probes[1].get("evidence", {}).get("image_path") == "/tmp/pov_footjob_soles_more_forward.png",
|
|
"Atlas refine seed-selected batch should preserve selected evidence",
|
|
)
|
|
with tempfile.NamedTemporaryFile("w", suffix=".json", delete=False, encoding="utf-8") as selected_batch_handle:
|
|
json.dump(selected_batch, selected_batch_handle)
|
|
selected_batch_path = Path(selected_batch_handle.name)
|
|
try:
|
|
loaded_selected_batch = sxcp_prompt_batch.load_batch(selected_batch_path)
|
|
finally:
|
|
selected_batch_path.unlink(missing_ok=True)
|
|
_expect(
|
|
len(loaded_selected_batch.get("probes") or []) == 2,
|
|
"Atlas refine seed-selected batch should load through sxcp prompt batch helper",
|
|
)
|
|
seed_matrix = manifest_module.build_seed_matrix(
|
|
applied_manifest,
|
|
"pov_footjob_frontal_sole_stroke",
|
|
selection_seeds=[202, 203],
|
|
sampler_seeds=[101, 102],
|
|
seed_slot="atlas_cue_seed",
|
|
)
|
|
_expect(
|
|
seed_matrix.get("schema") == "sxcp_atlas_refine_seed_matrix_v1",
|
|
"Atlas refine seed matrix lost schema",
|
|
)
|
|
_expect(
|
|
seed_matrix.get("job_count") == 4,
|
|
f"Atlas refine seed matrix should include every sampler/cue seed pair: {seed_matrix}",
|
|
)
|
|
matrix_jobs = seed_matrix.get("jobs") or []
|
|
_expect(
|
|
[(job.get("sampler_seed"), job.get("selection_seed")) for job in matrix_jobs]
|
|
== [(101, 202), (101, 203), (102, 202), (102, 203)],
|
|
f"Atlas refine seed matrix should keep deterministic sampler-major job order: {matrix_jobs}",
|
|
)
|
|
_expect(
|
|
matrix_jobs[0].get("batch", {}).get("seed") == 101,
|
|
"Atlas refine seed matrix should embed sampler-seeded batches",
|
|
)
|
|
sampler_102_job = next(
|
|
(
|
|
job
|
|
for job in matrix_jobs
|
|
if job.get("sampler_seed") == 102 and job.get("selection_seed") == 202
|
|
),
|
|
{},
|
|
)
|
|
sampler_102_probes = sampler_102_job.get("batch", {}).get("probes") or []
|
|
_expect(
|
|
sampler_102_job.get("batch", {}).get("seed") == 102
|
|
and sampler_102_job.get("candidate_probe", {}).get("seed_metadata", {}).get("sampler_seed") == 102
|
|
and sampler_102_probes
|
|
and all(probe.get("seed_metadata", {}).get("sampler_seed") == 102 for probe in sampler_102_probes),
|
|
f"Atlas refine seed matrix should propagate the actual per-job sampler seed into probe metadata: {sampler_102_job}",
|
|
)
|
|
_expect(
|
|
matrix_jobs[0].get("batch", {}).get("selection", {}).get("selection_seed") == 202,
|
|
"Atlas refine seed matrix should embed cue-seed selection reports",
|
|
)
|
|
_expect(
|
|
matrix_jobs[0].get("selected", {}).get("prompt_variant_id") == "soles_more_forward",
|
|
"Atlas refine seed matrix should preserve selected prompt variant ids",
|
|
)
|
|
_expect(
|
|
matrix_jobs[0].get("candidate_probe", {}).get("text") == sheet_probes[1].get("text"),
|
|
"Atlas refine seed matrix should preserve exact selected candidate prompt text",
|
|
)
|
|
_expect(
|
|
matrix_jobs[0].get("candidate_probe", {}).get("seed_metadata", {}).get("atlas_cue_seed") == 202,
|
|
"Atlas refine seed matrix should record cue seed on candidate probes",
|
|
)
|
|
_expect(
|
|
matrix_jobs[0].get("candidate_probe", {}).get("reference_images") == ["blowjob_top_view/22_blowjob_top_view.png"],
|
|
"Atlas refine seed matrix should preserve selected candidate reference-image provenance",
|
|
)
|
|
with tempfile.NamedTemporaryFile("w", suffix=".json", delete=False, encoding="utf-8") as matrix_batch_handle:
|
|
json.dump(matrix_jobs[0].get("batch"), matrix_batch_handle)
|
|
matrix_batch_path = Path(matrix_batch_handle.name)
|
|
try:
|
|
loaded_matrix_batch = sxcp_prompt_batch.load_batch(matrix_batch_path)
|
|
finally:
|
|
matrix_batch_path.unlink(missing_ok=True)
|
|
_expect(
|
|
loaded_matrix_batch.get("seed") == 101 and len(loaded_matrix_batch.get("probes") or []) == 2,
|
|
"Atlas refine seed matrix embedded batches should load through sxcp prompt batch helper",
|
|
)
|
|
_expect(
|
|
(loaded_matrix_batch.get("probes") or [])[1].get("reference_images") == ["blowjob_top_view/22_blowjob_top_view.png"],
|
|
"SxCP prompt batch loader should preserve matrix candidate reference-image provenance",
|
|
)
|
|
for duplicate_field, duplicate_kwargs in (
|
|
("sampler_seeds", {"selection_seeds": [202, 203], "sampler_seeds": [101, 101]}),
|
|
("selection_seeds", {"selection_seeds": [202, 202], "sampler_seeds": [101, 102]}),
|
|
):
|
|
try:
|
|
manifest_module.build_seed_matrix(
|
|
applied_manifest,
|
|
"pov_footjob_frontal_sole_stroke",
|
|
seed_slot="atlas_cue_seed",
|
|
**duplicate_kwargs,
|
|
)
|
|
except ValueError as exc:
|
|
_expect(
|
|
duplicate_field in str(exc) and "duplicate" in str(exc),
|
|
f"Atlas refine seed matrix duplicate {duplicate_field} error should be explicit: {exc}",
|
|
)
|
|
else:
|
|
raise AssertionError(f"Atlas refine seed matrix should reject duplicate {duplicate_field}")
|
|
seed_matrix_cli_result = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
str(ROOT / "tools" / "krea2_atlas_refine_manifest.py"),
|
|
"--folder",
|
|
str(apply_root),
|
|
"--subject-id",
|
|
"same_woman_001",
|
|
"--print-seed-matrix",
|
|
"--variant-key",
|
|
"pov_footjob_frontal_sole_stroke",
|
|
"--selection-seeds",
|
|
"202,203",
|
|
"--sampler-seeds",
|
|
"101,102",
|
|
"--seed-slot",
|
|
"atlas_cue_seed",
|
|
],
|
|
cwd=ROOT,
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
_expect(seed_matrix_cli_result.returncode == 0, f"Atlas refine seed matrix CLI failed: {seed_matrix_cli_result.stderr}")
|
|
cli_seed_matrix = json.loads(seed_matrix_cli_result.stdout)
|
|
_expect(
|
|
cli_seed_matrix.get("job_count") == 4,
|
|
"Atlas refine seed matrix CLI should preserve matrix job count",
|
|
)
|
|
matrix_results = {
|
|
"schema": "sxcp_atlas_refine_seed_matrix_results_v1",
|
|
"jobs": [
|
|
{
|
|
"id": job.get("id"),
|
|
"results": {
|
|
"seed": job.get("sampler_seed"),
|
|
"channel_in": "sxcp_eval_in",
|
|
"probes": [
|
|
{
|
|
"id": job.get("batch", {}).get("probes", [{}])[0].get("id"),
|
|
"prompt_order": "subject_first",
|
|
"turn": 300 + index * 2,
|
|
"image_path": f"/tmp/atlas_matrix_{index}_baseline.png",
|
|
"returned_seed": job.get("sampler_seed"),
|
|
},
|
|
{
|
|
"id": job.get("batch", {}).get("probes", [{}, {}])[1].get("id"),
|
|
"prompt_order": "subject_first",
|
|
"turn": 301 + index * 2,
|
|
"image_path": f"/tmp/atlas_matrix_{index}_candidate.png",
|
|
"returned_seed": job.get("sampler_seed"),
|
|
},
|
|
],
|
|
},
|
|
}
|
|
for index, job in enumerate(matrix_jobs)
|
|
],
|
|
}
|
|
matrix_result_sheet = manifest_module.build_seed_matrix_result_sheet(
|
|
seed_matrix,
|
|
matrix_results,
|
|
notes="matrix scoring pending",
|
|
)
|
|
_expect(
|
|
matrix_result_sheet.get("schema") == "sxcp_atlas_refine_seed_matrix_result_sheet_v1",
|
|
"Atlas refine seed matrix result sheet lost schema",
|
|
)
|
|
_expect(
|
|
matrix_result_sheet.get("job_count") == 4,
|
|
"Atlas refine seed matrix result sheet should preserve every matrix job",
|
|
)
|
|
matrix_sheet_jobs = matrix_result_sheet.get("jobs") or []
|
|
_expect(
|
|
matrix_sheet_jobs[0].get("sampler_seed") == 101
|
|
and matrix_sheet_jobs[0].get("selection_seed") == 202,
|
|
"Atlas refine seed matrix result sheet should keep sampler and cue seeds",
|
|
)
|
|
_expect(
|
|
matrix_sheet_jobs[0].get("selected", {}).get("prompt_variant_id") == "soles_more_forward",
|
|
"Atlas refine seed matrix result sheet should keep selected prompt variant id",
|
|
)
|
|
_expect(
|
|
matrix_sheet_jobs[0].get("result_sheet", {}).get("selection", {}).get("selection_seed") == 202,
|
|
"Atlas refine seed matrix result sheet should keep per-job selection reports",
|
|
)
|
|
matrix_job_probes = matrix_sheet_jobs[0].get("result_sheet", {}).get("probes") or []
|
|
_expect(
|
|
matrix_job_probes[1].get("text") == sheet_probes[1].get("text"),
|
|
"Atlas refine seed matrix result sheet should preserve exact candidate prompt text",
|
|
)
|
|
_expect(
|
|
matrix_job_probes[1].get("score", {}).get("pose_ownership") is None,
|
|
"Atlas refine seed matrix result sheet should leave score slots empty for visual scoring",
|
|
)
|
|
_expect(
|
|
matrix_job_probes[1].get("seed_metadata", {}).get("atlas_cue_seed") == 202,
|
|
"Atlas refine seed matrix result sheet should keep cue seed metadata on candidate probes",
|
|
)
|
|
_expect(
|
|
matrix_job_probes[1].get("reference_images") == ["blowjob_top_view/22_blowjob_top_view.png"],
|
|
"Atlas refine seed matrix result sheet should keep candidate reference-image provenance",
|
|
)
|
|
duplicate_job_matrix = json.loads(json.dumps(seed_matrix))
|
|
duplicate_job_matrix["jobs"][1]["id"] = duplicate_job_matrix["jobs"][0]["id"]
|
|
try:
|
|
manifest_module.build_seed_matrix_result_sheet(
|
|
duplicate_job_matrix,
|
|
matrix_results,
|
|
notes="duplicate matrix job id should fail",
|
|
)
|
|
except ValueError as exc:
|
|
_expect(
|
|
"seed matrix jobs" in str(exc) and "duplicated" in str(exc),
|
|
f"Atlas refine seed matrix result sheet duplicate job-id error should be explicit: {exc}",
|
|
)
|
|
else:
|
|
raise AssertionError("Atlas refine seed matrix result sheet should reject duplicate matrix job ids")
|
|
with tempfile.NamedTemporaryFile("w", suffix=".json", delete=False, encoding="utf-8") as matrix_handle:
|
|
json.dump(seed_matrix, matrix_handle)
|
|
cli_matrix_path = Path(matrix_handle.name)
|
|
with tempfile.NamedTemporaryFile("w", suffix=".json", delete=False, encoding="utf-8") as matrix_results_handle:
|
|
json.dump(matrix_results, matrix_results_handle)
|
|
cli_matrix_results_path = Path(matrix_results_handle.name)
|
|
try:
|
|
matrix_result_sheet_cli_result = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
str(ROOT / "tools" / "krea2_atlas_refine_manifest.py"),
|
|
"--print-seed-matrix-result-sheet",
|
|
"--seed-matrix-json",
|
|
str(cli_matrix_path),
|
|
"--seed-matrix-results-json",
|
|
str(cli_matrix_results_path),
|
|
"--notes",
|
|
"matrix scoring pending",
|
|
],
|
|
cwd=ROOT,
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
finally:
|
|
cli_matrix_path.unlink(missing_ok=True)
|
|
cli_matrix_results_path.unlink(missing_ok=True)
|
|
_expect(
|
|
matrix_result_sheet_cli_result.returncode == 0,
|
|
f"Atlas refine seed matrix result-sheet CLI failed: {matrix_result_sheet_cli_result.stderr}",
|
|
)
|
|
cli_matrix_result_sheet = json.loads(matrix_result_sheet_cli_result.stdout)
|
|
_expect(
|
|
cli_matrix_result_sheet.get("job_count") == 4,
|
|
"Atlas refine seed matrix result-sheet CLI should preserve job count",
|
|
)
|
|
scored_matrix_result_sheet = json.loads(json.dumps(matrix_result_sheet))
|
|
for index, job in enumerate(scored_matrix_result_sheet.get("jobs") or []):
|
|
probes = job.get("result_sheet", {}).get("probes") or []
|
|
probes[0]["score"].update(
|
|
{
|
|
"atlas_pose_match": "baseline",
|
|
"contact_match": "baseline",
|
|
"pose_ownership": "pass",
|
|
"workspace_continuity": "pass",
|
|
"clothing_visibility": "pass",
|
|
"subject_identity": "pass",
|
|
"expression_eye_control": "pass",
|
|
"anatomy_proportion": "pass",
|
|
"prompt_noise": "pass",
|
|
}
|
|
)
|
|
probes[1]["score"].update(
|
|
{
|
|
"atlas_pose_match": "pass",
|
|
"contact_match": "pass",
|
|
"pose_ownership": "pass",
|
|
"workspace_continuity": "pass",
|
|
"clothing_visibility": "pass",
|
|
"subject_identity": "fail" if index == 3 else "pass",
|
|
"expression_eye_control": "pass",
|
|
"anatomy_proportion": "pass",
|
|
"prompt_noise": "pass",
|
|
}
|
|
)
|
|
probes[1]["analysis_notes"] = f"matrix visual score {index}"
|
|
matrix_promotion_report = manifest_module.build_seed_matrix_promotion_report(scored_matrix_result_sheet)
|
|
_expect(
|
|
matrix_promotion_report.get("schema") == "sxcp_atlas_refine_seed_matrix_promotion_report_v1",
|
|
"Atlas refine seed matrix promotion report lost schema",
|
|
)
|
|
_expect(
|
|
matrix_promotion_report.get("job_count") == 4,
|
|
"Atlas refine seed matrix promotion report should preserve job count",
|
|
)
|
|
_expect(
|
|
matrix_promotion_report.get("promotion_ready_job_count") == 3,
|
|
f"Atlas refine seed matrix promotion report should count passing matrix jobs: {matrix_promotion_report}",
|
|
)
|
|
_expect(
|
|
matrix_promotion_report.get("blocked_job_count") == 1,
|
|
f"Atlas refine seed matrix promotion report should count blocked matrix jobs: {matrix_promotion_report}",
|
|
)
|
|
matrix_groups = matrix_promotion_report.get("groups") or []
|
|
_expect(
|
|
len(matrix_groups) == 2,
|
|
f"Atlas refine seed matrix promotion report should group by cue seed and selected variant: {matrix_groups}",
|
|
)
|
|
first_group = matrix_groups[0]
|
|
_expect(
|
|
first_group.get("selection_seed") == 202
|
|
and first_group.get("prompt_variant_id") == "soles_more_forward",
|
|
f"Atlas refine seed matrix promotion report should preserve cue seed and variant group identity: {first_group}",
|
|
)
|
|
_expect(
|
|
first_group.get("sampler_seed_count") == 2
|
|
and first_group.get("promotion_ready_count") == 2
|
|
and first_group.get("stable") is True,
|
|
f"Atlas refine seed matrix promotion report should mark all-passing cue groups stable: {first_group}",
|
|
)
|
|
first_matrix_job = (matrix_promotion_report.get("jobs") or [{}])[0]
|
|
_expect(
|
|
first_matrix_job.get("candidate", {}).get("reference_images") == ["blowjob_top_view/22_blowjob_top_view.png"],
|
|
f"Atlas refine seed matrix promotion report should preserve candidate reference-image provenance: {first_matrix_job}",
|
|
)
|
|
second_group = matrix_groups[1]
|
|
_expect(
|
|
second_group.get("selection_seed") == 203
|
|
and second_group.get("promotion_ready_count") == 1
|
|
and second_group.get("blocked_count") == 1
|
|
and second_group.get("stable") is False,
|
|
f"Atlas refine seed matrix promotion report should mark failed cue groups unstable: {second_group}",
|
|
)
|
|
_expect(
|
|
any("subject_identity=fail" in blocker for blocker in second_group.get("blockers", [])),
|
|
f"Atlas refine seed matrix promotion report should aggregate blockers: {second_group}",
|
|
)
|
|
duplicate_promotion_result_sheet = json.loads(json.dumps(scored_matrix_result_sheet))
|
|
duplicate_promotion_result_sheet["jobs"][1]["id"] = duplicate_promotion_result_sheet["jobs"][0]["id"]
|
|
try:
|
|
manifest_module.build_seed_matrix_promotion_report(duplicate_promotion_result_sheet)
|
|
except ValueError as exc:
|
|
_expect(
|
|
"seed matrix result sheet jobs" in str(exc) and "duplicated" in str(exc),
|
|
f"Atlas refine seed matrix promotion duplicate job-id error should be explicit: {exc}",
|
|
)
|
|
else:
|
|
raise AssertionError("Atlas refine seed matrix promotion should reject duplicate result-sheet job ids")
|
|
mismatched_selected_result_sheet = json.loads(json.dumps(scored_matrix_result_sheet))
|
|
mismatched_selected_result_sheet["jobs"][0]["selected"]["prompt_variant_id"] = "wrong_prompt_variant"
|
|
try:
|
|
manifest_module.build_seed_matrix_promotion_report(mismatched_selected_result_sheet)
|
|
except ValueError as exc:
|
|
_expect(
|
|
"selected.prompt_variant_id" in str(exc) and "wrong_prompt_variant" in str(exc),
|
|
f"Atlas refine seed matrix promotion selected/candidate mismatch error should be explicit: {exc}",
|
|
)
|
|
else:
|
|
raise AssertionError("Atlas refine seed matrix promotion should reject selected/candidate prompt variant mismatches")
|
|
mismatched_seed_slot_result_sheet = json.loads(json.dumps(scored_matrix_result_sheet))
|
|
mismatched_seed_slot_result_sheet["jobs"][0]["seed_slot"] = "workspace_seed"
|
|
try:
|
|
manifest_module.build_seed_matrix_promotion_report(mismatched_seed_slot_result_sheet)
|
|
except ValueError as exc:
|
|
_expect(
|
|
"seed_slot" in str(exc) and "workspace_seed" in str(exc) and "atlas_cue_seed" in str(exc),
|
|
f"Atlas refine seed matrix promotion seed-slot mismatch error should be explicit: {exc}",
|
|
)
|
|
else:
|
|
raise AssertionError("Atlas refine seed matrix promotion should reject job seed-slot drift")
|
|
duplicate_declared_sampler_result_sheet = json.loads(json.dumps(scored_matrix_result_sheet))
|
|
duplicate_declared_sampler_result_sheet["sampler_seeds"] = [101, 101]
|
|
try:
|
|
manifest_module.build_seed_matrix_promotion_report(duplicate_declared_sampler_result_sheet)
|
|
except ValueError as exc:
|
|
_expect(
|
|
"sampler_seeds" in str(exc) and "duplicate" in str(exc),
|
|
f"Atlas refine seed matrix promotion duplicate declared sampler-seed error should be explicit: {exc}",
|
|
)
|
|
else:
|
|
raise AssertionError("Atlas refine seed matrix promotion should reject duplicate declared sampler seeds")
|
|
undeclared_sampler_result_sheet = json.loads(json.dumps(scored_matrix_result_sheet))
|
|
undeclared_sampler_result_sheet["jobs"][0]["sampler_seed"] = 999
|
|
try:
|
|
manifest_module.build_seed_matrix_promotion_report(undeclared_sampler_result_sheet)
|
|
except ValueError as exc:
|
|
_expect(
|
|
"sampler_seed" in str(exc) and "999" in str(exc) and "sampler_seeds" in str(exc),
|
|
f"Atlas refine seed matrix promotion undeclared sampler-seed error should be explicit: {exc}",
|
|
)
|
|
else:
|
|
raise AssertionError("Atlas refine seed matrix promotion should reject jobs outside declared sampler seeds")
|
|
duplicate_sampler_job_result_sheet = json.loads(json.dumps(scored_matrix_result_sheet))
|
|
duplicate_sampler_job = json.loads(json.dumps(duplicate_sampler_job_result_sheet["jobs"][0]))
|
|
duplicate_sampler_job["id"] = f"{duplicate_sampler_job['id']}__duplicate_sampler"
|
|
duplicate_sampler_job_result_sheet["jobs"].append(duplicate_sampler_job)
|
|
try:
|
|
manifest_module.build_seed_matrix_promotion_report(duplicate_sampler_job_result_sheet)
|
|
except ValueError as exc:
|
|
_expect(
|
|
"sampler_seed" in str(exc) and "duplicated" in str(exc),
|
|
f"Atlas refine seed matrix promotion duplicate sampler job error should be explicit: {exc}",
|
|
)
|
|
else:
|
|
raise AssertionError("Atlas refine seed matrix promotion should reject duplicate sampler jobs inside a cue group")
|
|
duplicate_declared_selection_result_sheet = json.loads(json.dumps(scored_matrix_result_sheet))
|
|
duplicate_declared_selection_result_sheet["selection_seeds"] = [202, 202]
|
|
try:
|
|
manifest_module.build_seed_matrix_promotion_report(duplicate_declared_selection_result_sheet)
|
|
except ValueError as exc:
|
|
_expect(
|
|
"selection_seeds" in str(exc) and "duplicate" in str(exc),
|
|
f"Atlas refine seed matrix promotion duplicate declared cue-seed error should be explicit: {exc}",
|
|
)
|
|
else:
|
|
raise AssertionError("Atlas refine seed matrix promotion should reject duplicate declared cue seeds")
|
|
undeclared_selection_result_sheet = json.loads(json.dumps(scored_matrix_result_sheet))
|
|
undeclared_selection_result_sheet["jobs"][0]["selection_seed"] = 999
|
|
try:
|
|
manifest_module.build_seed_matrix_promotion_report(undeclared_selection_result_sheet)
|
|
except ValueError as exc:
|
|
_expect(
|
|
"selection_seed" in str(exc) and "999" in str(exc) and "selection_seeds" in str(exc),
|
|
f"Atlas refine seed matrix promotion undeclared cue-seed error should be explicit: {exc}",
|
|
)
|
|
else:
|
|
raise AssertionError("Atlas refine seed matrix promotion should reject jobs outside declared cue seeds")
|
|
mismatched_variant_result_sheet = json.loads(json.dumps(scored_matrix_result_sheet))
|
|
mismatched_variant_job = mismatched_variant_result_sheet["jobs"][2]
|
|
mismatched_variant_job["result_sheet"]["variant_key"] = "pov_other_pose_candidate"
|
|
mismatched_variant_candidate = mismatched_variant_job["result_sheet"]["probes"][1]
|
|
mismatched_variant_candidate["variant_key"] = "pov_other_pose_candidate"
|
|
try:
|
|
manifest_module.build_seed_matrix_promotion_report(mismatched_variant_result_sheet)
|
|
except ValueError as exc:
|
|
_expect(
|
|
"variant_key" in str(exc) and "pov_other_pose_candidate" in str(exc),
|
|
f"Atlas refine seed matrix promotion variant-key mismatch error should be explicit: {exc}",
|
|
)
|
|
else:
|
|
raise AssertionError("Atlas refine seed matrix promotion should reject candidate variant-key drift inside a stable group")
|
|
mismatched_source_result_sheet = json.loads(json.dumps(scored_matrix_result_sheet))
|
|
mismatched_source_job = mismatched_source_result_sheet["jobs"][2]
|
|
mismatched_source_job["result_sheet"]["source_entry_id"] = "pov_other_pose_00001"
|
|
mismatched_source_job["result_sheet"]["source_stem"] = "pov_other_pose_00001_"
|
|
mismatched_source_candidate = mismatched_source_job["result_sheet"]["probes"][1]
|
|
mismatched_source_candidate["source_entry_id"] = "pov_other_pose_00001"
|
|
mismatched_source_candidate["source_stem"] = "pov_other_pose_00001_"
|
|
try:
|
|
manifest_module.build_seed_matrix_promotion_report(mismatched_source_result_sheet)
|
|
except ValueError as exc:
|
|
_expect(
|
|
"source_stem" in str(exc) and "pov_other_pose_00001_" in str(exc),
|
|
f"Atlas refine seed matrix promotion source-stem mismatch error should be explicit: {exc}",
|
|
)
|
|
else:
|
|
raise AssertionError("Atlas refine seed matrix promotion should reject source-stem drift inside a stable group")
|
|
mismatched_prompt_text_result_sheet = json.loads(json.dumps(scored_matrix_result_sheet))
|
|
mismatched_prompt_text_job = mismatched_prompt_text_result_sheet["jobs"][2]
|
|
mismatched_prompt_text_candidate = mismatched_prompt_text_job["result_sheet"]["probes"][1]
|
|
mismatched_prompt_text_candidate["text"] = (
|
|
"A controlled same-subject footjob reference prompt with foreground soles. "
|
|
"the woman's heels stay farther back along the same contact line"
|
|
)
|
|
try:
|
|
manifest_module.build_seed_matrix_promotion_report(mismatched_prompt_text_result_sheet)
|
|
except ValueError as exc:
|
|
_expect(
|
|
"prompt text" in str(exc) and "does not match" in str(exc),
|
|
f"Atlas refine seed matrix promotion prompt-text drift error should be explicit: {exc}",
|
|
)
|
|
else:
|
|
raise AssertionError("Atlas refine seed matrix promotion should reject prompt-text drift inside a stable group")
|
|
incomplete_matrix_result_sheet = json.loads(json.dumps(scored_matrix_result_sheet))
|
|
incomplete_matrix_result_sheet["jobs"] = [
|
|
job
|
|
for job in incomplete_matrix_result_sheet.get("jobs", [])
|
|
if job.get("sampler_seed") == 101
|
|
]
|
|
incomplete_matrix_promotion_report = manifest_module.build_seed_matrix_promotion_report(incomplete_matrix_result_sheet)
|
|
incomplete_matrix_groups = incomplete_matrix_promotion_report.get("groups") or []
|
|
_expect(
|
|
incomplete_matrix_promotion_report.get("stable_group_count") == 0,
|
|
f"Atlas refine seed matrix promotion should not mark incomplete sampler coverage stable: {incomplete_matrix_promotion_report}",
|
|
)
|
|
_expect(
|
|
incomplete_matrix_groups
|
|
and all(102 in group.get("missing_sampler_seeds", []) for group in incomplete_matrix_groups)
|
|
and all("missing_sampler_coverage" in group.get("blockers", []) for group in incomplete_matrix_groups),
|
|
f"Atlas refine seed matrix promotion should expose missing sampler coverage per group: {incomplete_matrix_groups}",
|
|
)
|
|
one_sampler_matrix_result_sheet = json.loads(json.dumps(scored_matrix_result_sheet))
|
|
one_sampler_matrix_result_sheet["sampler_seeds"] = [101]
|
|
one_sampler_matrix_result_sheet["jobs"] = [
|
|
job
|
|
for job in one_sampler_matrix_result_sheet.get("jobs", [])
|
|
if job.get("sampler_seed") == 101
|
|
]
|
|
one_sampler_matrix_promotion_report = manifest_module.build_seed_matrix_promotion_report(one_sampler_matrix_result_sheet)
|
|
one_sampler_matrix_groups = one_sampler_matrix_promotion_report.get("groups") or []
|
|
_expect(
|
|
one_sampler_matrix_promotion_report.get("stable_group_count") == 0,
|
|
f"Atlas refine seed matrix promotion should not mark single-sampler matrices stable: {one_sampler_matrix_promotion_report}",
|
|
)
|
|
_expect(
|
|
one_sampler_matrix_groups
|
|
and all("insufficient_sampler_coverage" in group.get("blockers", []) for group in one_sampler_matrix_groups),
|
|
f"Atlas refine seed matrix promotion should expose insufficient sampler coverage per group: {one_sampler_matrix_groups}",
|
|
)
|
|
with tempfile.NamedTemporaryFile("w", suffix=".json", delete=False, encoding="utf-8") as scored_matrix_handle:
|
|
json.dump(scored_matrix_result_sheet, scored_matrix_handle)
|
|
cli_scored_matrix_path = Path(scored_matrix_handle.name)
|
|
try:
|
|
matrix_promotion_cli_result = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
str(ROOT / "tools" / "krea2_atlas_refine_manifest.py"),
|
|
"--print-seed-matrix-promotion-report",
|
|
"--seed-matrix-result-sheet-json",
|
|
str(cli_scored_matrix_path),
|
|
],
|
|
cwd=ROOT,
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
finally:
|
|
cli_scored_matrix_path.unlink(missing_ok=True)
|
|
_expect(
|
|
matrix_promotion_cli_result.returncode == 0,
|
|
f"Atlas refine seed matrix promotion CLI failed: {matrix_promotion_cli_result.stderr}",
|
|
)
|
|
cli_matrix_promotion_report = json.loads(matrix_promotion_cli_result.stdout)
|
|
_expect(
|
|
cli_matrix_promotion_report.get("promotion_ready_job_count") == 3,
|
|
"Atlas refine seed matrix promotion CLI should preserve ready job count",
|
|
)
|
|
matrix_sidecar_draft = manifest_module.build_matrix_sidecar_update_draft(matrix_promotion_report)
|
|
_expect(
|
|
matrix_sidecar_draft.get("schema") == "sxcp_atlas_refine_matrix_sidecar_update_draft_v1",
|
|
"Atlas refine matrix sidecar draft lost schema",
|
|
)
|
|
_expect(
|
|
matrix_sidecar_draft.get("ready_group_count") == 1
|
|
and matrix_sidecar_draft.get("skipped_group_count") == 1,
|
|
f"Atlas refine matrix sidecar draft should include only stable cue groups: {matrix_sidecar_draft}",
|
|
)
|
|
matrix_updates = matrix_sidecar_draft.get("updates") or []
|
|
_expect(
|
|
len(matrix_updates) == 1
|
|
and matrix_updates[0].get("sidecar_filename") == "pov_footjob_frontal_sole_stroke_00001_.json",
|
|
f"Atlas refine matrix sidecar draft should preserve same-stem sidecar filename: {matrix_updates}",
|
|
)
|
|
matrix_variant = (matrix_updates[0].get("prompt_variants") or [{}])[0]
|
|
_expect(
|
|
matrix_variant.get("id") == "soles_more_forward",
|
|
"Atlas refine matrix sidecar draft should preserve prompt variant id",
|
|
)
|
|
_expect(
|
|
matrix_variant.get("text") == sheet_probes[1].get("text"),
|
|
"Atlas refine matrix sidecar draft should keep exact selected prompt text",
|
|
)
|
|
matrix_evidence = matrix_variant.get("matrix_evidence") or {}
|
|
_expect(
|
|
matrix_evidence.get("stable") is True
|
|
and matrix_evidence.get("selection_seed") == 202
|
|
and matrix_evidence.get("sampler_seeds") == [101, 102],
|
|
f"Atlas refine matrix sidecar draft should preserve stable sampler/cue evidence: {matrix_evidence}",
|
|
)
|
|
missing_job_promotion_report = json.loads(json.dumps(matrix_promotion_report))
|
|
missing_job_promotion_report["groups"][0]["job_ids"].append("missing_matrix_job_id")
|
|
try:
|
|
manifest_module.build_matrix_sidecar_update_draft(missing_job_promotion_report)
|
|
except ValueError as exc:
|
|
_expect(
|
|
"job_ids" in str(exc) and "missing_matrix_job_id" in str(exc),
|
|
f"Atlas refine matrix sidecar draft missing job-id error should be explicit: {exc}",
|
|
)
|
|
else:
|
|
raise AssertionError("Atlas refine matrix sidecar draft should reject stable groups with missing job ids")
|
|
duplicate_group_job_id_promotion_report = json.loads(json.dumps(matrix_promotion_report))
|
|
duplicate_group_job_id = duplicate_group_job_id_promotion_report["groups"][0]["job_ids"][0]
|
|
duplicate_group_job_id_promotion_report["groups"][0]["job_ids"].append(duplicate_group_job_id)
|
|
duplicate_group_job_id_promotion_report["groups"][0]["job_count"] = 3
|
|
duplicate_group_job_id_promotion_report["groups"][0]["promotion_ready_count"] = 3
|
|
try:
|
|
manifest_module.build_matrix_sidecar_update_draft(duplicate_group_job_id_promotion_report)
|
|
except ValueError as exc:
|
|
_expect(
|
|
"job_ids" in str(exc) and "duplicated" in str(exc) and duplicate_group_job_id in str(exc),
|
|
f"Atlas refine matrix sidecar draft duplicate job-id error should be explicit: {exc}",
|
|
)
|
|
else:
|
|
raise AssertionError("Atlas refine matrix sidecar draft should reject stable groups with duplicated job ids")
|
|
mismatched_group_job_promotion_report = json.loads(json.dumps(matrix_promotion_report))
|
|
stable_group = mismatched_group_job_promotion_report["groups"][0]
|
|
mismatched_group_job = next(
|
|
job
|
|
for job in mismatched_group_job_promotion_report["jobs"]
|
|
if job.get("selection_seed") != stable_group.get("selection_seed")
|
|
and job.get("sampler_seed") == stable_group.get("sampler_seeds", [None])[0]
|
|
and job.get("decision") == "seedable_candidate"
|
|
)
|
|
stable_group["job_ids"][0] = mismatched_group_job["id"]
|
|
try:
|
|
manifest_module.build_matrix_sidecar_update_draft(mismatched_group_job_promotion_report)
|
|
except ValueError as exc:
|
|
_expect(
|
|
"selection_seed" in str(exc) and str(mismatched_group_job["selection_seed"]) in str(exc),
|
|
f"Atlas refine matrix sidecar draft group identity drift error should be explicit: {exc}",
|
|
)
|
|
else:
|
|
raise AssertionError("Atlas refine matrix sidecar draft should reject jobs outside the stable group identity")
|
|
mismatched_group_prompt_text_report = json.loads(json.dumps(matrix_promotion_report))
|
|
mismatched_group_prompt_text_job = next(
|
|
job
|
|
for job in mismatched_group_prompt_text_report["jobs"]
|
|
if job.get("selection_seed") == 202 and job.get("sampler_seed") == 102
|
|
)
|
|
mismatched_group_prompt_text_job["candidate"]["text"] = (
|
|
"A controlled same-subject footjob reference prompt with foreground soles. "
|
|
"the woman's heels stay farther back along the same contact line"
|
|
)
|
|
try:
|
|
manifest_module.build_matrix_sidecar_update_draft(mismatched_group_prompt_text_report)
|
|
except ValueError as exc:
|
|
_expect(
|
|
"prompt text" in str(exc) and "expected" in str(exc),
|
|
f"Atlas refine matrix sidecar draft prompt-text drift error should be explicit: {exc}",
|
|
)
|
|
else:
|
|
raise AssertionError("Atlas refine matrix sidecar draft should reject prompt-text drift inside stable group job ids")
|
|
incomplete_group_promotion_report = json.loads(json.dumps(matrix_promotion_report))
|
|
incomplete_group_promotion_report["groups"][0]["job_ids"] = incomplete_group_promotion_report["groups"][0]["job_ids"][:1]
|
|
try:
|
|
manifest_module.build_matrix_sidecar_update_draft(incomplete_group_promotion_report)
|
|
except ValueError as exc:
|
|
_expect(
|
|
"sampler_seeds" in str(exc) and "job_ids" in str(exc),
|
|
f"Atlas refine matrix sidecar draft incomplete sampler coverage error should be explicit: {exc}",
|
|
)
|
|
else:
|
|
raise AssertionError("Atlas refine matrix sidecar draft should reject stable groups whose job ids omit sampler coverage")
|
|
inflated_count_promotion_report = json.loads(json.dumps(matrix_promotion_report))
|
|
inflated_count_promotion_report["groups"][0]["job_count"] = 99
|
|
inflated_count_promotion_report["groups"][0]["promotion_ready_count"] = 99
|
|
try:
|
|
manifest_module.build_matrix_sidecar_update_draft(inflated_count_promotion_report)
|
|
except ValueError as exc:
|
|
_expect(
|
|
"job_count" in str(exc) and "promotion_ready_count" in str(exc),
|
|
f"Atlas refine matrix sidecar draft count-drift error should be explicit: {exc}",
|
|
)
|
|
else:
|
|
raise AssertionError("Atlas refine matrix sidecar draft should reject stable groups with inflated evidence counts")
|
|
_expect(
|
|
len(matrix_evidence.get("jobs") or []) == 2,
|
|
"Atlas refine matrix sidecar draft should keep every passing matrix job",
|
|
)
|
|
_expect(
|
|
matrix_variant.get("evidence", {}).get("seed") == 101,
|
|
"Atlas refine matrix sidecar draft should keep representative single-image evidence for compatibility",
|
|
)
|
|
skipped_matrix_groups = matrix_sidecar_draft.get("skipped") or []
|
|
_expect(
|
|
skipped_matrix_groups
|
|
and skipped_matrix_groups[0].get("selection_seed") == 203
|
|
and "subject_identity=fail" in skipped_matrix_groups[0].get("blockers", []),
|
|
f"Atlas refine matrix sidecar draft should skip unstable groups with blockers: {skipped_matrix_groups}",
|
|
)
|
|
with tempfile.NamedTemporaryFile("w", suffix=".json", delete=False, encoding="utf-8") as matrix_promotion_handle:
|
|
json.dump(matrix_promotion_report, matrix_promotion_handle)
|
|
cli_matrix_promotion_path = Path(matrix_promotion_handle.name)
|
|
try:
|
|
matrix_sidecar_cli_result = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
str(ROOT / "tools" / "krea2_atlas_refine_manifest.py"),
|
|
"--print-matrix-sidecar-update-draft",
|
|
"--seed-matrix-promotion-report-json",
|
|
str(cli_matrix_promotion_path),
|
|
],
|
|
cwd=ROOT,
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
finally:
|
|
cli_matrix_promotion_path.unlink(missing_ok=True)
|
|
_expect(
|
|
matrix_sidecar_cli_result.returncode == 0,
|
|
f"Atlas refine matrix sidecar draft CLI failed: {matrix_sidecar_cli_result.stderr}",
|
|
)
|
|
cli_matrix_sidecar_draft = json.loads(matrix_sidecar_cli_result.stdout)
|
|
_expect(
|
|
cli_matrix_sidecar_draft.get("ready_group_count") == 1,
|
|
"Atlas refine matrix sidecar draft CLI should keep ready group count",
|
|
)
|
|
matrix_validation = manifest_module.validate_matrix_sidecar_update_draft(matrix_sidecar_draft)
|
|
_expect(
|
|
matrix_validation.get("schema") == "sxcp_atlas_refine_matrix_sidecar_update_validation_v1",
|
|
"Atlas refine matrix sidecar validation lost schema",
|
|
)
|
|
_expect(
|
|
matrix_validation.get("valid") is True,
|
|
f"Atlas refine matrix sidecar draft should validate: {matrix_validation}",
|
|
)
|
|
_expect(
|
|
matrix_validation.get("validated_variant_count") == 1,
|
|
"Atlas refine matrix sidecar validation should count prompt variants",
|
|
)
|
|
duplicate_matrix_job_draft = json.loads(json.dumps(matrix_sidecar_draft))
|
|
duplicate_matrix_evidence = duplicate_matrix_job_draft["updates"][0]["prompt_variants"][0]["matrix_evidence"]
|
|
duplicate_matrix_evidence["jobs"].append(json.loads(json.dumps(duplicate_matrix_evidence["jobs"][0])))
|
|
duplicate_matrix_evidence["job_count"] = len(duplicate_matrix_evidence["jobs"])
|
|
duplicate_matrix_evidence["promotion_ready_count"] = len(duplicate_matrix_evidence["jobs"])
|
|
duplicate_matrix_validation = manifest_module.validate_matrix_sidecar_update_draft(duplicate_matrix_job_draft)
|
|
_expect(
|
|
duplicate_matrix_validation.get("valid") is False,
|
|
"Atlas refine matrix sidecar validation should reject duplicated matrix evidence jobs",
|
|
)
|
|
_expect(
|
|
any("matrix_evidence.jobs" in error and "duplicated" in error for error in duplicate_matrix_validation.get("errors", [])),
|
|
f"Atlas refine matrix sidecar validation duplicate matrix job error should be explicit: {duplicate_matrix_validation}",
|
|
)
|
|
duplicate_declared_sampler_draft = json.loads(json.dumps(matrix_sidecar_draft))
|
|
duplicate_declared_sampler_evidence = duplicate_declared_sampler_draft["updates"][0]["prompt_variants"][0]["matrix_evidence"]
|
|
duplicate_declared_sampler_evidence["sampler_seeds"] = [
|
|
duplicate_declared_sampler_evidence["sampler_seeds"][0],
|
|
duplicate_declared_sampler_evidence["sampler_seeds"][0],
|
|
*duplicate_declared_sampler_evidence["sampler_seeds"][1:],
|
|
]
|
|
duplicate_declared_sampler_validation = manifest_module.validate_matrix_sidecar_update_draft(duplicate_declared_sampler_draft)
|
|
_expect(
|
|
duplicate_declared_sampler_validation.get("valid") is False,
|
|
"Atlas refine matrix sidecar validation should reject duplicated declared matrix sampler seeds",
|
|
)
|
|
_expect(
|
|
any("matrix_evidence.sampler_seeds" in error and "duplicated" in error for error in duplicate_declared_sampler_validation.get("errors", [])),
|
|
f"Atlas refine matrix sidecar validation duplicate declared sampler-seed error should be explicit: {duplicate_declared_sampler_validation}",
|
|
)
|
|
single_sampler_matrix_draft = json.loads(json.dumps(matrix_sidecar_draft))
|
|
single_sampler_matrix_evidence = single_sampler_matrix_draft["updates"][0]["prompt_variants"][0]["matrix_evidence"]
|
|
single_sampler_matrix_evidence["jobs"] = single_sampler_matrix_evidence["jobs"][:1]
|
|
single_sampler_matrix_evidence["sampler_seeds"] = [single_sampler_matrix_evidence["jobs"][0]["sampler_seed"]]
|
|
single_sampler_matrix_evidence["job_count"] = 1
|
|
single_sampler_matrix_evidence["promotion_ready_count"] = 1
|
|
single_sampler_matrix_validation = manifest_module.validate_matrix_sidecar_update_draft(single_sampler_matrix_draft)
|
|
_expect(
|
|
single_sampler_matrix_validation.get("valid") is False,
|
|
"Atlas refine matrix sidecar validation should reject single-sampler stable matrix evidence",
|
|
)
|
|
_expect(
|
|
any("matrix_evidence.sampler_seeds" in error and "at least 2" in error for error in single_sampler_matrix_validation.get("errors", [])),
|
|
f"Atlas refine matrix sidecar validation single-sampler error should be explicit: {single_sampler_matrix_validation}",
|
|
)
|
|
invalid_matrix_turn_draft = json.loads(json.dumps(matrix_sidecar_draft))
|
|
invalid_matrix_turn_draft["updates"][0]["prompt_variants"][0]["matrix_evidence"]["jobs"][0]["turn"] = "not-an-int"
|
|
invalid_matrix_turn_validation = manifest_module.validate_matrix_sidecar_update_draft(invalid_matrix_turn_draft)
|
|
_expect(
|
|
invalid_matrix_turn_validation.get("valid") is False,
|
|
"Atlas refine matrix sidecar validation should reject non-integer matrix job turns",
|
|
)
|
|
_expect(
|
|
any("matrix_evidence.jobs[0].turn" in error and "integer" in error for error in invalid_matrix_turn_validation.get("errors", [])),
|
|
f"Atlas refine matrix sidecar validation matrix job turn error should be explicit: {invalid_matrix_turn_validation}",
|
|
)
|
|
invalid_representative_turn_draft = json.loads(json.dumps(matrix_sidecar_draft))
|
|
invalid_representative_turn_draft["updates"][0]["prompt_variants"][0]["evidence"]["turn"] = "not-an-int"
|
|
invalid_representative_turn_validation = manifest_module.validate_matrix_sidecar_update_draft(invalid_representative_turn_draft)
|
|
_expect(
|
|
invalid_representative_turn_validation.get("valid") is False,
|
|
"Atlas refine matrix sidecar validation should reject non-integer representative evidence turns",
|
|
)
|
|
_expect(
|
|
any("evidence.turn" in error and "integer" in error for error in invalid_representative_turn_validation.get("errors", [])),
|
|
f"Atlas refine matrix sidecar validation representative evidence turn error should be explicit: {invalid_representative_turn_validation}",
|
|
)
|
|
mismatched_seed_metadata_draft = json.loads(json.dumps(matrix_sidecar_draft))
|
|
mismatched_seed_metadata_variant = mismatched_seed_metadata_draft["updates"][0]["prompt_variants"][0]
|
|
mismatched_seed_metadata_variant["seed_metadata"]["atlas_cue_seed"] = 999
|
|
mismatched_seed_metadata_validation = manifest_module.validate_matrix_sidecar_update_draft(mismatched_seed_metadata_draft)
|
|
_expect(
|
|
mismatched_seed_metadata_validation.get("valid") is False,
|
|
"Atlas refine matrix sidecar validation should reject cue seed_metadata drift",
|
|
)
|
|
_expect(
|
|
any("seed_metadata.atlas_cue_seed" in error and "999" in error and "202" in error for error in mismatched_seed_metadata_validation.get("errors", [])),
|
|
f"Atlas refine matrix sidecar validation seed metadata drift error should be explicit: {mismatched_seed_metadata_validation}",
|
|
)
|
|
mismatched_representative_evidence_draft = json.loads(json.dumps(matrix_sidecar_draft))
|
|
mismatched_representative_variant = mismatched_representative_evidence_draft["updates"][0]["prompt_variants"][0]
|
|
mismatched_representative_variant["evidence"]["image_path"] = "/tmp/atlas_matrix_wrong_representative.png"
|
|
mismatched_representative_validation = manifest_module.validate_matrix_sidecar_update_draft(
|
|
mismatched_representative_evidence_draft
|
|
)
|
|
_expect(
|
|
mismatched_representative_validation.get("valid") is False,
|
|
"Atlas refine matrix sidecar validation should reject representative evidence drift",
|
|
)
|
|
_expect(
|
|
any("evidence.image_path" in error and "matrix_evidence.jobs" in error for error in mismatched_representative_validation.get("errors", [])),
|
|
f"Atlas refine matrix sidecar validation representative evidence drift error should be explicit: {mismatched_representative_validation}",
|
|
)
|
|
invalid_matrix_draft = json.loads(json.dumps(matrix_sidecar_draft))
|
|
invalid_matrix_variant = invalid_matrix_draft["updates"][0]["prompt_variants"][0]
|
|
invalid_matrix_variant["matrix_evidence"]["stable"] = False
|
|
invalid_matrix_variant["matrix_evidence"]["seed_slot"] = "sampler_seed"
|
|
invalid_matrix_variant["matrix_evidence"]["jobs"][0]["score"]["subject_identity"] = "fail"
|
|
invalid_matrix_variant["negative_prompt"] = "do not include this"
|
|
invalid_matrix_variant["prompt_source"]["prompt_variant_id"] = "wrong_axis"
|
|
invalid_matrix_validation = manifest_module.validate_matrix_sidecar_update_draft(invalid_matrix_draft)
|
|
_expect(
|
|
invalid_matrix_validation.get("valid") is False,
|
|
"Atlas refine matrix sidecar validation should reject unstable or failed matrix evidence",
|
|
)
|
|
_expect(
|
|
any("matrix_evidence.stable" in error for error in invalid_matrix_validation.get("errors", [])),
|
|
f"Atlas refine matrix sidecar validation should report unstable matrix evidence: {invalid_matrix_validation}",
|
|
)
|
|
_expect(
|
|
any("matrix_evidence.seed_slot" in error and "sampler_seed" in error for error in invalid_matrix_validation.get("errors", [])),
|
|
f"Atlas refine matrix sidecar validation should reject sampler_seed as a cue slot: {invalid_matrix_validation}",
|
|
)
|
|
_expect(
|
|
any("matrix_evidence.jobs[0].score" in error for error in invalid_matrix_validation.get("errors", [])),
|
|
f"Atlas refine matrix sidecar validation should report failed matrix job scores: {invalid_matrix_validation}",
|
|
)
|
|
_expect(
|
|
any("negative_prompt" in error for error in invalid_matrix_validation.get("errors", [])),
|
|
f"Atlas refine matrix sidecar validation should reject negative prompt fields: {invalid_matrix_validation}",
|
|
)
|
|
_expect(
|
|
any("prompt_source.prompt_variant_id" in error for error in invalid_matrix_validation.get("errors", [])),
|
|
f"Atlas refine matrix sidecar validation should reject mismatched prompt-source ids: {invalid_matrix_validation}",
|
|
)
|
|
with tempfile.NamedTemporaryFile("w", suffix=".json", delete=False, encoding="utf-8") as matrix_draft_handle:
|
|
json.dump(matrix_sidecar_draft, matrix_draft_handle)
|
|
cli_matrix_draft_path = Path(matrix_draft_handle.name)
|
|
try:
|
|
matrix_validation_cli_result = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
str(ROOT / "tools" / "krea2_atlas_refine_manifest.py"),
|
|
"--validate-matrix-sidecar-update-draft",
|
|
"--matrix-sidecar-update-draft-json",
|
|
str(cli_matrix_draft_path),
|
|
],
|
|
cwd=ROOT,
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
finally:
|
|
cli_matrix_draft_path.unlink(missing_ok=True)
|
|
_expect(
|
|
matrix_validation_cli_result.returncode == 0,
|
|
f"Atlas refine matrix sidecar validation CLI failed: {matrix_validation_cli_result.stderr}",
|
|
)
|
|
cli_matrix_validation = json.loads(matrix_validation_cli_result.stdout)
|
|
_expect(
|
|
cli_matrix_validation.get("valid") is True,
|
|
"Atlas refine matrix sidecar validation CLI should validate the draft",
|
|
)
|
|
with tempfile.TemporaryDirectory() as matrix_apply_tmpdir:
|
|
matrix_apply_root = Path(matrix_apply_tmpdir)
|
|
matrix_sidecar_path = matrix_apply_root / "pov_footjob_frontal_sole_stroke_00001_.json"
|
|
matrix_sidecar_path.write_text(
|
|
json.dumps(
|
|
{
|
|
"notes": "preserve existing matrix sidecar notes",
|
|
"prompt_variants": [
|
|
{
|
|
"id": "old_axis",
|
|
"text": "Old reviewed prompt variant.",
|
|
"cue_axes": {"foot_position": "old"},
|
|
}
|
|
],
|
|
},
|
|
ensure_ascii=True,
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
matrix_apply_report = manifest_module.apply_matrix_sidecar_update_draft(
|
|
matrix_sidecar_draft,
|
|
matrix_apply_root,
|
|
)
|
|
_expect(
|
|
matrix_apply_report.get("schema") == "sxcp_atlas_refine_matrix_sidecar_apply_report_v1",
|
|
"Atlas refine matrix sidecar apply report lost schema",
|
|
)
|
|
_expect(
|
|
matrix_apply_report.get("applied") is True,
|
|
"Atlas refine matrix sidecar apply should mark applied",
|
|
)
|
|
_expect(
|
|
matrix_apply_report.get("updated_file_count") == 1,
|
|
"Atlas refine matrix sidecar apply should update one sidecar",
|
|
)
|
|
matrix_applied_sidecar = json.loads(matrix_sidecar_path.read_text(encoding="utf-8"))
|
|
_expect(
|
|
matrix_applied_sidecar.get("notes") == "preserve existing matrix sidecar notes",
|
|
"Atlas refine matrix sidecar apply should preserve unrelated sidecar fields",
|
|
)
|
|
matrix_applied_variants = matrix_applied_sidecar.get("prompt_variants") or []
|
|
_expect(
|
|
[variant.get("id") for variant in matrix_applied_variants] == ["old_axis", "soles_more_forward"],
|
|
f"Atlas refine matrix sidecar apply should append stable variants: {matrix_applied_variants}",
|
|
)
|
|
applied_matrix_evidence = matrix_applied_variants[1].get("matrix_evidence") or {}
|
|
_expect(
|
|
applied_matrix_evidence.get("stable") is True
|
|
and applied_matrix_evidence.get("selection_seed") == 202
|
|
and len(applied_matrix_evidence.get("jobs") or []) == 2,
|
|
f"Atlas refine matrix sidecar apply should preserve full matrix evidence: {applied_matrix_evidence}",
|
|
)
|
|
matrix_second_apply_report = manifest_module.apply_matrix_sidecar_update_draft(
|
|
matrix_sidecar_draft,
|
|
matrix_apply_root,
|
|
)
|
|
_expect(
|
|
matrix_second_apply_report.get("updated_file_count") == 1,
|
|
"Atlas refine matrix sidecar apply should be idempotent and still report the touched sidecar",
|
|
)
|
|
matrix_applied_again = json.loads(matrix_sidecar_path.read_text(encoding="utf-8"))
|
|
_expect(
|
|
[variant.get("id") for variant in matrix_applied_again.get("prompt_variants", [])].count("soles_more_forward") == 1,
|
|
"Atlas refine matrix sidecar apply should upsert matrix variants by id instead of duplicating them",
|
|
)
|
|
with tempfile.NamedTemporaryFile("w", suffix=".json", delete=False, encoding="utf-8") as matrix_apply_draft_handle:
|
|
json.dump(matrix_sidecar_draft, matrix_apply_draft_handle)
|
|
cli_matrix_apply_draft_path = Path(matrix_apply_draft_handle.name)
|
|
try:
|
|
matrix_apply_cli_result = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
str(ROOT / "tools" / "krea2_atlas_refine_manifest.py"),
|
|
"--apply-matrix-sidecar-update-draft",
|
|
"--matrix-sidecar-update-draft-json",
|
|
str(cli_matrix_apply_draft_path),
|
|
"--folder",
|
|
str(matrix_apply_root),
|
|
],
|
|
cwd=ROOT,
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
finally:
|
|
cli_matrix_apply_draft_path.unlink(missing_ok=True)
|
|
_expect(
|
|
matrix_apply_cli_result.returncode == 0,
|
|
f"Atlas refine matrix sidecar apply CLI failed: {matrix_apply_cli_result.stderr}",
|
|
)
|
|
cli_matrix_apply_report = json.loads(matrix_apply_cli_result.stdout)
|
|
_expect(
|
|
cli_matrix_apply_report.get("applied") is True,
|
|
"Atlas refine matrix sidecar apply CLI should report applied",
|
|
)
|
|
(matrix_apply_root / "pov_footjob_frontal_sole_stroke_00001_.txt").write_text(
|
|
footjob.get("prompt_text", ""),
|
|
encoding="utf-8",
|
|
)
|
|
(matrix_apply_root / "pov_footjob_frontal_sole_stroke_00001_.png").write_bytes(b"fake-png")
|
|
matrix_applied_manifest = manifest_module.build_manifest(matrix_apply_root, subject_id="same_woman_001")
|
|
matrix_applied_entry = (matrix_applied_manifest.get("entries") or [{}])[0]
|
|
matrix_applied_prompt_variants = {
|
|
variant.get("id"): variant for variant in matrix_applied_entry.get("prompt_variants", [])
|
|
}
|
|
rescanned_matrix_evidence = matrix_applied_prompt_variants.get("soles_more_forward", {}).get("matrix_evidence") or {}
|
|
_expect(
|
|
rescanned_matrix_evidence.get("stable") is True
|
|
and rescanned_matrix_evidence.get("selection_seed") == 202,
|
|
f"Atlas refine applied matrix sidecar should rescan with matrix evidence: {rescanned_matrix_evidence}",
|
|
)
|
|
matrix_prompt_batch = manifest_module.build_prompt_batch(
|
|
matrix_applied_manifest,
|
|
"pov_footjob_frontal_sole_stroke",
|
|
sampler_seed=101,
|
|
)
|
|
matrix_batch_variant = next(
|
|
(
|
|
probe
|
|
for probe in matrix_prompt_batch.get("probes", [])
|
|
if probe.get("id") == "pov_footjob_frontal_sole_stroke_00001__soles_more_forward"
|
|
),
|
|
{},
|
|
)
|
|
_expect(
|
|
matrix_batch_variant.get("matrix_evidence", {}).get("stable") is True
|
|
and matrix_batch_variant.get("matrix_evidence", {}).get("selection_seed") == 202,
|
|
f"Atlas refine normal prompt batch should carry stable matrix evidence: {matrix_batch_variant}",
|
|
)
|
|
matrix_batch_results = {
|
|
"seed": matrix_prompt_batch.get("seed"),
|
|
"channel_in": "sxcp_eval_in",
|
|
"probes": [
|
|
{
|
|
"id": probe.get("id"),
|
|
"prompt_order": probe.get("prompt_order"),
|
|
"turn": 40 + index,
|
|
"image_path": f"/tmp/atlas_matrix_prompt_batch_{index}.png",
|
|
"returned_seed": matrix_prompt_batch.get("seed"),
|
|
}
|
|
for index, probe in enumerate(matrix_prompt_batch.get("probes") or [])
|
|
],
|
|
}
|
|
matrix_batch_result_sheet = manifest_module.build_result_sheet(
|
|
matrix_prompt_batch,
|
|
matrix_batch_results,
|
|
notes="normal matrix batch scoring pending",
|
|
)
|
|
matrix_sheet_variant = next(
|
|
(
|
|
probe
|
|
for probe in matrix_batch_result_sheet.get("probes", [])
|
|
if probe.get("id") == "pov_footjob_frontal_sole_stroke_00001__soles_more_forward"
|
|
),
|
|
{},
|
|
)
|
|
_expect(
|
|
matrix_sheet_variant.get("matrix_evidence", {}).get("stable") is True
|
|
and matrix_sheet_variant.get("matrix_evidence", {}).get("selection_seed") == 202,
|
|
f"Atlas refine normal result sheet should keep stable matrix evidence: {matrix_sheet_variant}",
|
|
)
|
|
scored_matrix_batch_result_sheet = json.loads(json.dumps(matrix_batch_result_sheet))
|
|
for probe in scored_matrix_batch_result_sheet.get("probes") or []:
|
|
if probe.get("id") == "pov_footjob_frontal_sole_stroke_00001__soles_more_forward":
|
|
probe["score"].update(
|
|
{
|
|
"atlas_pose_match": "pass",
|
|
"contact_match": "pass",
|
|
"pose_ownership": "pass",
|
|
"workspace_continuity": "pass",
|
|
"clothing_visibility": "pass",
|
|
"subject_identity": "pass",
|
|
"expression_eye_control": "pass",
|
|
"anatomy_proportion": "pass",
|
|
"prompt_noise": "pass",
|
|
}
|
|
)
|
|
matrix_single_promotion = manifest_module.build_promotion_report(scored_matrix_batch_result_sheet)
|
|
matrix_single_candidate = next(
|
|
(
|
|
candidate
|
|
for candidate in matrix_single_promotion.get("candidates", [])
|
|
if candidate.get("prompt_variant_id") == "soles_more_forward"
|
|
),
|
|
{},
|
|
)
|
|
_expect(
|
|
matrix_single_candidate.get("matrix_evidence", {}).get("stable") is True
|
|
and matrix_single_candidate.get("matrix_evidence", {}).get("selection_seed") == 202,
|
|
f"Atlas refine normal promotion report should preserve matrix evidence: {matrix_single_candidate}",
|
|
)
|
|
matrix_single_sidecar_draft = manifest_module.build_sidecar_update_draft(matrix_single_promotion)
|
|
matrix_single_variant = ((matrix_single_sidecar_draft.get("updates") or [{}])[0].get("prompt_variants") or [{}])[0]
|
|
_expect(
|
|
matrix_single_variant.get("matrix_evidence", {}).get("stable") is True
|
|
and matrix_single_variant.get("matrix_evidence", {}).get("selection_seed") == 202,
|
|
f"Atlas refine normal sidecar draft should preserve matrix evidence: {matrix_single_variant}",
|
|
)
|
|
unstable_matrix_result_sheet = json.loads(json.dumps(scored_matrix_batch_result_sheet))
|
|
for probe in unstable_matrix_result_sheet.get("probes") or []:
|
|
if probe.get("id") == "pov_footjob_frontal_sole_stroke_00001__soles_more_forward":
|
|
probe["matrix_evidence"]["stable"] = False
|
|
unstable_matrix_promotion = manifest_module.build_promotion_report(unstable_matrix_result_sheet)
|
|
unstable_matrix_candidate = next(
|
|
(
|
|
candidate
|
|
for candidate in unstable_matrix_promotion.get("candidates", [])
|
|
if candidate.get("prompt_variant_id") == "soles_more_forward"
|
|
),
|
|
{},
|
|
)
|
|
_expect(
|
|
unstable_matrix_candidate.get("decision") == "rejected"
|
|
and "unstable_matrix_evidence" in unstable_matrix_candidate.get("blockers", []),
|
|
f"Atlas refine promotion report should reject unstable matrix evidence: {unstable_matrix_candidate}",
|
|
)
|
|
unstable_matrix_sidecar_draft = manifest_module.build_sidecar_update_draft(unstable_matrix_promotion)
|
|
_expect(
|
|
unstable_matrix_sidecar_draft.get("ready_candidate_count") == 0,
|
|
f"Atlas refine sidecar draft should skip unstable matrix evidence: {unstable_matrix_sidecar_draft}",
|
|
)
|
|
matrix_selection = manifest_module.select_seeded_prompt_variant(
|
|
matrix_applied_manifest,
|
|
"pov_footjob_frontal_sole_stroke",
|
|
selection_seed=202,
|
|
seed_slot="atlas_cue_seed",
|
|
)
|
|
selected_matrix_evidence = matrix_selection.get("selected", {}).get("matrix_evidence") or {}
|
|
_expect(
|
|
selected_matrix_evidence.get("stable") is True
|
|
and selected_matrix_evidence.get("selection_seed") == 202
|
|
and selected_matrix_evidence.get("sampler_seeds") == [101, 102],
|
|
f"Atlas refine seed selection should preserve stable matrix evidence: {selected_matrix_evidence}",
|
|
)
|
|
malformed_stable_matrix_manifest = json.loads(json.dumps(matrix_applied_manifest))
|
|
malformed_entry = (malformed_stable_matrix_manifest.get("entries") or [{}])[0]
|
|
for malformed_variant in malformed_entry.get("prompt_variants") or []:
|
|
if malformed_variant.get("id") == "soles_more_forward":
|
|
malformed_matrix_evidence = malformed_variant["matrix_evidence"]
|
|
malformed_matrix_evidence["sampler_seeds"] = [
|
|
malformed_matrix_evidence["sampler_seeds"][0],
|
|
malformed_matrix_evidence["sampler_seeds"][0],
|
|
*malformed_matrix_evidence["sampler_seeds"][1:],
|
|
]
|
|
malformed_stable_matrix_selection = manifest_module.select_seeded_prompt_variant(
|
|
malformed_stable_matrix_manifest,
|
|
"pov_footjob_frontal_sole_stroke",
|
|
selection_seed=202,
|
|
seed_slot="atlas_cue_seed",
|
|
)
|
|
_expect(
|
|
malformed_stable_matrix_selection.get("eligible_candidate_count") == 0
|
|
and malformed_stable_matrix_selection.get("selected") == {},
|
|
f"Atlas refine seed selection should reject malformed stable matrix evidence: {malformed_stable_matrix_selection}",
|
|
)
|
|
_expect(
|
|
any("unstable_matrix_evidence" in item.get("reason", "") for item in malformed_stable_matrix_selection.get("ineligible", [])),
|
|
f"Atlas refine seed selection should explain malformed stable matrix evidence rejection: {malformed_stable_matrix_selection}",
|
|
)
|
|
context_drift_matrix_manifest = json.loads(json.dumps(matrix_applied_manifest))
|
|
context_drift_entry = (context_drift_matrix_manifest.get("entries") or [{}])[0]
|
|
for context_drift_variant in context_drift_entry.get("prompt_variants") or []:
|
|
if context_drift_variant.get("id") == "soles_more_forward":
|
|
context_drift_variant["seed_metadata"]["atlas_cue_seed"] = 999
|
|
context_drift_matrix_selection = manifest_module.select_seeded_prompt_variant(
|
|
context_drift_matrix_manifest,
|
|
"pov_footjob_frontal_sole_stroke",
|
|
selection_seed=202,
|
|
seed_slot="atlas_cue_seed",
|
|
)
|
|
_expect(
|
|
context_drift_matrix_selection.get("eligible_candidate_count") == 0
|
|
and context_drift_matrix_selection.get("selected") == {},
|
|
f"Atlas refine seed selection should reject context-drifted stable matrix evidence: {context_drift_matrix_selection}",
|
|
)
|
|
_expect(
|
|
any("unstable_matrix_evidence" in item.get("reason", "") for item in context_drift_matrix_selection.get("ineligible", [])),
|
|
f"Atlas refine seed selection should explain context-drifted stable matrix evidence rejection: {context_drift_matrix_selection}",
|
|
)
|
|
unstable_matrix_manifest = json.loads(json.dumps(matrix_applied_manifest))
|
|
unstable_entry = (unstable_matrix_manifest.get("entries") or [{}])[0]
|
|
for unstable_variant in unstable_entry.get("prompt_variants") or []:
|
|
if unstable_variant.get("id") == "soles_more_forward":
|
|
unstable_variant["matrix_evidence"]["stable"] = False
|
|
unstable_matrix_selection = manifest_module.select_seeded_prompt_variant(
|
|
unstable_matrix_manifest,
|
|
"pov_footjob_frontal_sole_stroke",
|
|
selection_seed=202,
|
|
seed_slot="atlas_cue_seed",
|
|
)
|
|
_expect(
|
|
unstable_matrix_selection.get("eligible_candidate_count") == 0
|
|
and unstable_matrix_selection.get("selected") == {},
|
|
f"Atlas refine seed selection should reject explicitly unstable matrix evidence: {unstable_matrix_selection}",
|
|
)
|
|
_expect(
|
|
any("unstable_matrix_evidence" in item.get("reason", "") for item in unstable_matrix_selection.get("ineligible", [])),
|
|
f"Atlas refine seed selection should explain unstable matrix evidence rejection: {unstable_matrix_selection}",
|
|
)
|
|
matrix_catalog_cue_draft = manifest_module.build_catalog_cue_draft(
|
|
matrix_applied_manifest,
|
|
variant_key="pov_footjob_frontal_sole_stroke",
|
|
)
|
|
matrix_catalog_candidates = matrix_catalog_cue_draft.get("candidates") or []
|
|
_expect(
|
|
matrix_catalog_cue_draft.get("ready_cue_count") == 1
|
|
and matrix_catalog_candidates[0].get("matrix_evidence", {}).get("stable") is True,
|
|
f"Atlas refine catalog cue draft should preserve stable matrix evidence: {matrix_catalog_cue_draft}",
|
|
)
|
|
unstable_catalog_cue_draft = manifest_module.build_catalog_cue_draft(
|
|
unstable_matrix_manifest,
|
|
variant_key="pov_footjob_frontal_sole_stroke",
|
|
)
|
|
_expect(
|
|
unstable_catalog_cue_draft.get("ready_cue_count") == 0,
|
|
f"Atlas refine catalog cue draft should reject unstable matrix evidence: {unstable_catalog_cue_draft}",
|
|
)
|
|
_expect(
|
|
any("unstable_matrix_evidence" in item.get("reason", "") for item in unstable_catalog_cue_draft.get("skipped", [])),
|
|
f"Atlas refine catalog cue draft should explain unstable matrix evidence rejection: {unstable_catalog_cue_draft}",
|
|
)
|
|
unstable_coverage = manifest_module.build_coverage_report(unstable_matrix_manifest)
|
|
unstable_coverage_entry = (unstable_coverage.get("entries") or [{}])[0]
|
|
_expect(
|
|
unstable_coverage_entry.get("catalog_cue_candidate_count") == 0
|
|
and unstable_coverage_entry.get("state") != "ready_for_catalog_review",
|
|
f"Atlas refine coverage should not mark unstable matrix evidence ready for catalog review: {unstable_coverage_entry}",
|
|
)
|
|
unstable_variant_summary = next(
|
|
(
|
|
variant
|
|
for variant in unstable_coverage_entry.get("prompt_variants", [])
|
|
if variant.get("prompt_variant_id") == "soles_more_forward"
|
|
),
|
|
{},
|
|
)
|
|
_expect(
|
|
unstable_variant_summary.get("decision") == "rejected"
|
|
and "unstable_matrix_evidence" in unstable_variant_summary.get("blockers", []),
|
|
f"Atlas refine coverage should attach unstable matrix evidence blockers: {unstable_variant_summary}",
|
|
)
|
|
matrix_selected_batch = manifest_module.build_seed_selected_prompt_batch(
|
|
matrix_applied_manifest,
|
|
"pov_footjob_frontal_sole_stroke",
|
|
selection_seed=202,
|
|
sampler_seed=101,
|
|
seed_slot="atlas_cue_seed",
|
|
)
|
|
matrix_selected_candidate = (matrix_selected_batch.get("probes") or [{}])[-1]
|
|
candidate_matrix_evidence = matrix_selected_candidate.get("matrix_evidence") or {}
|
|
_expect(
|
|
candidate_matrix_evidence.get("stable") is True
|
|
and candidate_matrix_evidence.get("selection_seed") == 202,
|
|
f"Atlas refine selected batch should carry stable matrix evidence: {candidate_matrix_evidence}",
|
|
)
|
|
matrix_selected_results = {
|
|
"seed": 101,
|
|
"channel_in": "sxcp_eval_in",
|
|
"probes": [
|
|
{
|
|
"id": (matrix_selected_batch.get("probes") or [{}])[0].get("id"),
|
|
"prompt_order": "subject_first",
|
|
"turn": 31,
|
|
"image_path": "/tmp/pov_footjob_matrix_selected_baseline.png",
|
|
"returned_seed": 101,
|
|
},
|
|
{
|
|
"id": matrix_selected_candidate.get("id"),
|
|
"prompt_order": "subject_first",
|
|
"turn": 32,
|
|
"image_path": "/tmp/pov_footjob_matrix_selected_candidate.png",
|
|
"returned_seed": 101,
|
|
},
|
|
],
|
|
}
|
|
matrix_selected_result_sheet = manifest_module.build_result_sheet(
|
|
matrix_selected_batch,
|
|
matrix_selected_results,
|
|
notes="matrix-selected visual scoring pending",
|
|
)
|
|
matrix_selected_sheet_candidate = (matrix_selected_result_sheet.get("probes") or [{}])[-1]
|
|
sheet_matrix_evidence = matrix_selected_sheet_candidate.get("matrix_evidence") or {}
|
|
_expect(
|
|
sheet_matrix_evidence.get("stable") is True
|
|
and sheet_matrix_evidence.get("selection_seed") == 202,
|
|
f"Atlas refine selected result sheet should preserve matrix evidence for scoring: {sheet_matrix_evidence}",
|
|
)
|
|
selected_results = {
|
|
"seed": 101,
|
|
"channel_in": "sxcp_eval_in",
|
|
"probes": [
|
|
{
|
|
"id": "pov_footjob_frontal_sole_stroke_00001__baseline",
|
|
"prompt_order": "subject_first",
|
|
"turn": 21,
|
|
"image_path": "/tmp/pov_footjob_selected_baseline.png",
|
|
"returned_seed": 101,
|
|
},
|
|
{
|
|
"id": "pov_footjob_frontal_sole_stroke_00001__soles_more_forward",
|
|
"prompt_order": "subject_first",
|
|
"turn": 22,
|
|
"image_path": "/tmp/pov_footjob_selected_candidate.png",
|
|
"returned_seed": 101,
|
|
},
|
|
],
|
|
}
|
|
selected_result_sheet = manifest_module.build_result_sheet(
|
|
selected_batch,
|
|
selected_results,
|
|
notes="selected seed visual scoring pending",
|
|
)
|
|
_expect(
|
|
selected_result_sheet.get("selection", {}).get("selection_seed") == 202,
|
|
"Atlas refine selected result sheet should keep cue seed selection metadata",
|
|
)
|
|
selected_sheet_probes = selected_result_sheet.get("probes") or []
|
|
_expect(
|
|
selected_sheet_probes[1].get("selection", {}).get("prompt_variant_id") == "soles_more_forward",
|
|
"Atlas refine selected result sheet should keep selected prompt variant id on the candidate",
|
|
)
|
|
_expect(
|
|
selected_sheet_probes[1].get("seed_metadata", {}).get("atlas_cue_seed") == 202,
|
|
"Atlas refine selected result sheet should keep candidate cue seed metadata",
|
|
)
|
|
selected_batch_cli_result = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
str(ROOT / "tools" / "krea2_atlas_refine_manifest.py"),
|
|
"--folder",
|
|
str(apply_root),
|
|
"--subject-id",
|
|
"same_woman_001",
|
|
"--print-seed-selected-batch",
|
|
"--variant-key",
|
|
"pov_footjob_frontal_sole_stroke",
|
|
"--selection-seed",
|
|
"202",
|
|
"--sampler-seed",
|
|
"101",
|
|
"--seed-slot",
|
|
"atlas_cue_seed",
|
|
],
|
|
cwd=ROOT,
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
_expect(selected_batch_cli_result.returncode == 0, f"Atlas refine seed-selected batch CLI failed: {selected_batch_cli_result.stderr}")
|
|
cli_selected_batch = json.loads(selected_batch_cli_result.stdout)
|
|
_expect(
|
|
cli_selected_batch.get("selection", {}).get("selected", {}).get("prompt_variant_id") == "soles_more_forward",
|
|
"Atlas refine seed-selected batch CLI should include the selected prompt variant",
|
|
)
|
|
unknown = by_variant.get("pov_unknown_pose_candidate") or {}
|
|
_expect(unknown.get("known_variant") is False, "Atlas refine manifest should keep unknown pairs but flag them")
|
|
missing = manifest.get("missing_pairs") or []
|
|
_expect(
|
|
missing and missing[0].get("stem") == "pov_blowjob_side_profile_oral_00002_",
|
|
f"Atlas refine manifest should report the orphan stem: {missing}",
|
|
)
|
|
_expect(cli_manifest.get("entry_count") == 3, "Atlas refine manifest CLI lost paired entries")
|
|
_expect("pov_footjob_frontal_sole_stroke" in cli_stdout, "Atlas refine manifest CLI lost known variant key")
|
|
|
|
|
|
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")
|
|
cast_index = lower.find("adult woman")
|
|
viewer_index = lower.find("viewer")
|
|
_expect(
|
|
0 <= cast_index < viewer_index,
|
|
"POV hardcore prompt should establish the visible woman before viewer/action 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("action geography" not in lower, "POV penetration leaked abstract action-geography wording")
|
|
_expect("use the multiangle camera" not in lower, "POV penetration leaked internal multiangle instruction")
|
|
_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")
|
|
style_index = lower.find("explicit consensual")
|
|
_expect(
|
|
style_index > 0 and lower[:style_index].rstrip().endswith("."),
|
|
"POV penetration composition sentence should keep punctuation before style suffix",
|
|
)
|
|
|
|
|
|
def smoke_pov_outercourse_position_routes() -> None:
|
|
cases = [
|
|
(
|
|
"pov_outercourse_boobjob",
|
|
"boobjob",
|
|
("breasts inward around", "directly above the glans"),
|
|
("pov boobjob position", "kneels upright", "pressed-together breasts", "woman's own fingers", "glans emerging"),
|
|
),
|
|
(
|
|
"pov_outercourse_testicle",
|
|
"testicle_sucking",
|
|
(
|
|
"cheek against the pov viewer's inner thigh",
|
|
"scrotum is the mouth surface",
|
|
"testicles resting across her open lips",
|
|
"face is the closest visible partner part",
|
|
),
|
|
(
|
|
"low side-pelvis pov",
|
|
"face is the closest visible partner part",
|
|
"cheek against the viewer's inner thigh",
|
|
"scrotum is the mouth surface",
|
|
"scrotal skin is the nearest mouth surface",
|
|
"testicles resting across her open lips",
|
|
"both testicles rest against her tongue from below",
|
|
),
|
|
),
|
|
(
|
|
"pov_outercourse_penis_licking",
|
|
"penis_licking",
|
|
("head low under the pov viewer's penis", "tongue touches the underside"),
|
|
("tongue touches the underside", "glans at the tip", "viewer"),
|
|
),
|
|
(
|
|
"pov_outercourse_handjob",
|
|
"handjob",
|
|
("one hand grips and strokes the pov viewer's penis", "strokes toward the glans"),
|
|
("pov handjob position", "woman's right hand wraps", "left hand steadies", "without his hands covering"),
|
|
),
|
|
(
|
|
"pov_outercourse_footjob",
|
|
"footjob",
|
|
(
|
|
"two large overlapping soles dominate the pov viewer's lower center foreground",
|
|
"inner arches press inward",
|
|
"toes curl around both edges",
|
|
"narrow visible strip of shaft and glans rises between the compressed feet",
|
|
),
|
|
(
|
|
"two large overlapping soles dominate the lower center foreground",
|
|
"inner arches press inward",
|
|
"toes curl around both edges",
|
|
"narrow visible strip of shaft and glans rises between the compressed feet",
|
|
"face and torso stay visible behind the large foreground feet",
|
|
),
|
|
),
|
|
]
|
|
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(hard_row.get("position_key") == position_key, f"{name} selected position should be primary position_key")
|
|
_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}")
|
|
if position_key == "handjob":
|
|
_expect("behind the penis shaft" not in prompt, f"{name} Krea prompt kept vague shaft-only wording: {prompt}")
|
|
if position_key == "testicle_sucking":
|
|
_expect("head is tucked under the penis shaft" not in prompt, f"{name} Krea prompt kept high-head testicle wording: {prompt}")
|
|
if position_key == "footjob":
|
|
_expect(" only " not in f" {prompt} ", f"{name} Krea prompt kept limiting-only wording: {prompt}")
|
|
|
|
prone_item = "reclining penis-licking position while slow tongue licking on the underside of the penis"
|
|
prone_axis = {"position": "reclining penis-licking position"}
|
|
prone_role_graph = hardcore_role_outercourse.build_outercourse_role_graph(
|
|
"Woman A",
|
|
"Man A",
|
|
prone_item,
|
|
prone_axis,
|
|
pov_labels=["Man A"],
|
|
).lower()
|
|
for term in (
|
|
"belly-down",
|
|
"wide v-frame",
|
|
"torso stretched low and horizontal",
|
|
"hands wrap the base",
|
|
):
|
|
_expect(term in prone_role_graph, f"Prone frontal penis-licking role graph missing {term!r}: {prone_role_graph}")
|
|
_expect("head low under" not in prone_role_graph, f"Prone frontal penis-licking role graph kept under-shaft wording: {prone_role_graph}")
|
|
|
|
prone_pair = pb.build_insta_of_pair(
|
|
row_number=1,
|
|
start_index=1,
|
|
seed=3717,
|
|
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", ["penis_licking"]),
|
|
location_config=_coworking_location_config(),
|
|
hardcore_camera_config=_orbit_camera(
|
|
horizontal_angle=45,
|
|
vertical_angle=0,
|
|
zoom=7.5,
|
|
subject_focus="action",
|
|
),
|
|
)
|
|
prone_row = prone_pair["hardcore_row"]
|
|
prone_row["item"] = prone_item
|
|
prone_row["item_axis_values"] = prone_axis
|
|
prone_row["position_key"] = "penis_licking"
|
|
prone_row["position_keys"] = ["reclining_oral", "penis_licking"]
|
|
prone_row["source_role_graph"] = prone_role_graph
|
|
prone_krea = krea_formatter.format_krea2_prompt("", metadata_json=_json(prone_pair), target="hardcore")
|
|
prone_prompt = _expect_text("pov_outercourse_prone_frontal_penis_licking.krea_prompt", prone_krea.get("krea_prompt"), 60).lower()
|
|
for term in (
|
|
"pov prone frontal oral position",
|
|
"wide symmetrical v-frame",
|
|
"torso stretched low and horizontal",
|
|
"hands wrap the base",
|
|
"centered mouth-to-shaft contact",
|
|
):
|
|
_expect(term in prone_prompt, f"Prone frontal penis-licking Krea prompt missing {term!r}: {prone_prompt}")
|
|
_expect("head low under the viewer's penis" not in prone_prompt, f"Prone frontal penis-licking Krea prompt kept under-shaft wording: {prone_prompt}")
|
|
|
|
|
|
def smoke_pov_oral_position_routes() -> None:
|
|
cases = [
|
|
(
|
|
"pov_oral_kneeling",
|
|
"kneeling",
|
|
("viewer's penis", "takes the viewer's penis in her mouth"),
|
|
("nadir-angle standing male pov top-view oral position", "short centered vertical column", "top-down office anchors"),
|
|
),
|
|
(
|
|
"pov_oral_face_sitting",
|
|
"face_sitting",
|
|
("straddling the viewer's face", "pussy directly over the viewer's mouth"),
|
|
("close first-person underview", "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"),
|
|
("pov sixty-nine oral position", "head-to-hips", "viewer's mouth on the woman's pussy", "lower-foreground body cues aligned"),
|
|
),
|
|
(
|
|
"pov_oral_edge_supported",
|
|
"edge_supported",
|
|
("raised edge with thighs open", "viewer kneels between her legs"),
|
|
("pov raised-edge cunnilingus position", "raised edge with thighs open", "face at pussy height"),
|
|
),
|
|
(
|
|
"pov_oral_side_lying",
|
|
"side_lying",
|
|
("woman a lies on her side", "viewer lies beside her hips"),
|
|
("pov side-lying cunnilingus position", "woman lies on her side", "top thigh lifted", "viewer lies beside her hips"),
|
|
),
|
|
(
|
|
"pov_oral_chair",
|
|
"chair_oral",
|
|
("viewer sits in a chair", "kneels between his thighs"),
|
|
("pov chair oral position", "viewer sits in a chair", "kneels between the viewer's thighs", "head is low at his pelvis"),
|
|
),
|
|
]
|
|
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")
|
|
_expect("as he looks down" not in prompt, f"{name} Krea prompt kept unresolved male pronoun: {prompt}")
|
|
_expect(
|
|
"the woman takes the viewer's penis in her mouth with" not in prompt,
|
|
f"{name} Krea prompt repeated oral contact detail after POV rewrite: {prompt}",
|
|
)
|
|
_expect("edge-supported oral position;" not in prompt, f"{name} Krea prompt kept source oral-position scaffold: {prompt}")
|
|
if position_key == "kneeling":
|
|
for term in (
|
|
"nadir-angle standing male pov top-view oral position",
|
|
"viewer looks almost straight down from his torso toward the floor",
|
|
"nearby carpet/floor plane dominating the image",
|
|
"short centered vertical column",
|
|
"directly below the viewer between his feet",
|
|
"hair crown, forehead, shoulders, hands, knees",
|
|
"top-down office anchors",
|
|
):
|
|
_expect(term in prompt, f"{name} Krea prompt lost top-down oral hierarchy term {term!r}: {prompt}")
|
|
_expect("plumb-line" not in prompt and "map" not in prompt, f"{name} Krea prompt kept literalized top-view wording: {prompt}")
|
|
for term in krea_terms:
|
|
_expect(term in prompt, f"{name} Krea prompt missing {term!r}: {prompt}")
|
|
|
|
top_variant_config = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPKrea2POVOralFilter"]().build(
|
|
"replace",
|
|
"",
|
|
include_blowjob_top_down_vertical_shaft=True,
|
|
)[0]
|
|
top_variant_pair = pb.build_insta_of_pair(
|
|
row_number=1,
|
|
start_index=1,
|
|
seed=3828,
|
|
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=top_variant_config,
|
|
location_config=_coworking_location_config(),
|
|
hardcore_camera_config=_orbit_camera(
|
|
horizontal_angle=45,
|
|
vertical_angle=0,
|
|
zoom=7.5,
|
|
subject_focus="action",
|
|
),
|
|
)
|
|
_expect_pair(top_variant_pair, "pov_oral_top_view_variant_filter")
|
|
top_variant_row = top_variant_pair.get("hardcore_row") or {}
|
|
top_variant_config_row = top_variant_row.get("hardcore_position_config") or {}
|
|
_expect(
|
|
top_variant_config_row.get("krea2_variant_keys") == ["pov_blowjob_top_down_vertical_shaft"],
|
|
"Top-view POV filter lost exact variant key in row metadata",
|
|
)
|
|
top_axis_values = top_variant_row.get("item_axis_values") or {}
|
|
_expect(
|
|
top_axis_values.get("krea2_variant_keys") == ["pov_blowjob_top_down_vertical_shaft"],
|
|
"Top-view POV filter exact variant key did not reach formatter axis metadata",
|
|
)
|
|
top_krea = krea_formatter.format_krea2_prompt("", metadata_json=_json(top_variant_pair), target="hardcore")
|
|
top_prompt = _expect_text("pov_oral_top_view_variant_filter.krea_prompt", top_krea.get("krea_prompt"), 60).lower()
|
|
_expect("nadir-angle standing male pov top-view oral position" in top_prompt, "Top-view variant prompt lost exact top-view route")
|
|
_expect("eye-level shot" not in top_prompt, f"Top-view variant prompt kept contradictory eye-level camera text: {top_prompt}")
|
|
_expect("tongue extended toward genitals" not in top_prompt, f"Top-view variant prompt kept contradictory tongue-extension expression: {top_prompt}")
|
|
|
|
restored_top_config = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPKrea2POVPromptRestore"]().build(
|
|
True,
|
|
True,
|
|
True,
|
|
True,
|
|
True,
|
|
top_variant_config,
|
|
)[0]
|
|
restored_top_pair = pb.build_insta_of_pair(
|
|
row_number=1,
|
|
start_index=1,
|
|
seed=3828,
|
|
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=restored_top_config,
|
|
location_config=_coworking_location_config(),
|
|
hardcore_camera_config=_orbit_camera(
|
|
horizontal_angle=45,
|
|
vertical_angle=0,
|
|
zoom=7.5,
|
|
subject_focus="action",
|
|
),
|
|
)
|
|
restored_top_row = restored_top_pair.get("hardcore_row") or {}
|
|
restored_top_axis = restored_top_row.get("item_axis_values") or {}
|
|
restored_details = restored_top_axis.get("restored_prompt_details") or []
|
|
_expect(isinstance(restored_details, list), "Krea2 POV Prompt Restore should keep restored prompt details as a list")
|
|
_expect(restored_details, "Krea2 POV Prompt Restore should add sampled restored axis details to row metadata")
|
|
restored_top_krea = krea_formatter.format_krea2_prompt("", metadata_json=_json(restored_top_pair), target="hardcore")
|
|
restored_top_prompt = _expect_text("pov_oral_top_view_restore.krea_prompt", restored_top_krea.get("krea_prompt"), 60).lower()
|
|
_expect(
|
|
restored_top_prompt != top_prompt,
|
|
"Krea2 POV Prompt Restore should visibly change the final Krea prompt, not only metadata",
|
|
)
|
|
_expect(
|
|
"nadir-angle standing male pov top-view oral position" in restored_top_prompt,
|
|
"Krea2 POV Prompt Restore should preserve the exact atlas pose while adding detail",
|
|
)
|
|
_expect(
|
|
"eye-level" not in restored_top_prompt,
|
|
f"Krea2 POV Prompt Restore should not reintroduce contradictory eye-level camera wording: {restored_top_prompt}",
|
|
)
|
|
_expect(
|
|
any(str(detail).lower() in restored_top_prompt for detail in restored_details),
|
|
f"Krea2 POV Prompt Restore final prompt did not include sampled restored details: {restored_top_prompt}",
|
|
)
|
|
clothing_only_top_config = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPKrea2POVPromptRestore"]().build(
|
|
True,
|
|
False,
|
|
False,
|
|
False,
|
|
True,
|
|
top_variant_config,
|
|
)[0]
|
|
clothing_only_top_pair = pb.build_insta_of_pair(
|
|
row_number=1,
|
|
start_index=1,
|
|
seed=3828,
|
|
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=clothing_only_top_config,
|
|
location_config=_coworking_location_config(),
|
|
hardcore_camera_config=_orbit_camera(
|
|
horizontal_angle=45,
|
|
vertical_angle=0,
|
|
zoom=7.5,
|
|
subject_focus="action",
|
|
),
|
|
)
|
|
clothing_only_top_row = clothing_only_top_pair.get("hardcore_row") or {}
|
|
clothing_only_top_axis = clothing_only_top_row.get("item_axis_values") or {}
|
|
clothing_only_axes = clothing_only_top_axis.get("restored_prompt_axes") or []
|
|
clothing_only_details = clothing_only_top_axis.get("restored_prompt_details") or []
|
|
_expect(
|
|
clothing_only_axes == ["clothing_detail"],
|
|
f"Krea2 POV Prompt Restore clothing-only mode should restore clothing_detail, got {clothing_only_axes!r}",
|
|
)
|
|
_expect(
|
|
clothing_only_details,
|
|
"Krea2 POV Prompt Restore clothing-only mode should add a sampled clothing detail",
|
|
)
|
|
clothing_only_top_krea = krea_formatter.format_krea2_prompt("", metadata_json=_json(clothing_only_top_pair), target="hardcore")
|
|
clothing_only_top_prompt = _expect_text(
|
|
"pov_oral_top_view_restore_clothing_only.krea_prompt",
|
|
clothing_only_top_krea.get("krea_prompt"),
|
|
60,
|
|
).lower()
|
|
clothing_only_prompt_key = _clean_key(clothing_only_top_prompt)
|
|
clothing_only_state = _clean_key(clothing_only_top_pair.get("hardcore_clothing_state"))
|
|
expected_clothing_terms = [
|
|
term
|
|
for term in (
|
|
"body is fully exposed",
|
|
"bare skin unobstructed",
|
|
"pushed aside",
|
|
"partly removed",
|
|
"oral contact unobstructed",
|
|
"belt open and pants lowered below the hips",
|
|
)
|
|
if term in clothing_only_state
|
|
]
|
|
_expect(
|
|
expected_clothing_terms,
|
|
f"Krea2 POV Prompt Restore clothing-only fixture lost softcore-derived clothing state: {clothing_only_state}",
|
|
)
|
|
_expect(
|
|
any(term in clothing_only_prompt_key for term in expected_clothing_terms),
|
|
f"Krea2 POV Prompt Restore clothing-only final prompt did not follow softcore clothing continuity: {clothing_only_top_prompt}",
|
|
)
|
|
_expect(
|
|
"clothing state:" not in clothing_only_top_prompt,
|
|
f"Krea2 POV Prompt Restore clothing-only final prompt leaked raw clothing label: {clothing_only_top_prompt}",
|
|
)
|
|
_expect(
|
|
"pov foreground clothing cue" not in clothing_only_top_prompt,
|
|
f"Krea2 POV Prompt Restore clothing-only final prompt should not add a second POV clothing/body cue: {clothing_only_top_prompt}",
|
|
)
|
|
_expect(
|
|
not any(str(detail).lower() in clothing_only_top_prompt for detail in clothing_only_details),
|
|
f"Krea2 POV Prompt Restore clothing-only final prompt should not dump raw clothing_detail: {clothing_only_top_prompt}",
|
|
)
|
|
|
|
sitting_variant_config = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPKrea2POVOralFilter"]().build(
|
|
"replace",
|
|
"",
|
|
include_blowjob_sitting_upright_oral=True,
|
|
)[0]
|
|
sitting_variant_pair = pb.build_insta_of_pair(
|
|
row_number=1,
|
|
start_index=1,
|
|
seed=3831,
|
|
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=sitting_variant_config,
|
|
location_config=_coworking_location_config(),
|
|
hardcore_camera_config=_orbit_camera(
|
|
horizontal_angle=45,
|
|
vertical_angle=0,
|
|
zoom=7.5,
|
|
subject_focus="action",
|
|
),
|
|
)
|
|
_expect_pair(sitting_variant_pair, "pov_oral_sitting_variant_filter")
|
|
sitting_variant_row = sitting_variant_pair.get("hardcore_row") or {}
|
|
sitting_variant_config_row = sitting_variant_row.get("hardcore_position_config") or {}
|
|
_expect(
|
|
sitting_variant_config_row.get("krea2_variant_keys") == ["pov_blowjob_sitting_upright_oral"],
|
|
"Sitting oral POV filter lost exact variant key in row metadata",
|
|
)
|
|
_expect(
|
|
"blowjob_sitting" in (sitting_variant_config_row.get("positions") or []),
|
|
"Sitting oral POV filter lost exact sitting route position in row config",
|
|
)
|
|
sitting_axis_values = sitting_variant_row.get("item_axis_values") or {}
|
|
_expect(
|
|
sitting_axis_values.get("krea2_variant_keys") == ["pov_blowjob_sitting_upright_oral"],
|
|
"Sitting oral POV filter exact variant key did not reach formatter axis metadata",
|
|
)
|
|
sitting_variant_krea = krea_formatter.format_krea2_prompt("", metadata_json=_json(sitting_variant_pair), target="hardcore")
|
|
sitting_variant_prompt = _expect_text(
|
|
"pov_oral_sitting_variant_filter.krea_prompt",
|
|
sitting_variant_krea.get("krea_prompt"),
|
|
60,
|
|
).lower()
|
|
for term in (
|
|
"pov upright sitting oral position",
|
|
"woman sits low between the viewer's open thighs",
|
|
"face lowers close to the exact center contact point",
|
|
"open mouth covers the centered tip",
|
|
"hands wrapped low at the base",
|
|
):
|
|
_expect(term in sitting_variant_prompt, f"Sitting oral variant prompt missing {term!r}: {sitting_variant_prompt}")
|
|
_expect(
|
|
"pov open-thigh cunnilingus position" not in sitting_variant_prompt,
|
|
f"Sitting oral variant drifted into generic cunnilingus route: {sitting_variant_prompt}",
|
|
)
|
|
_expect(
|
|
"viewer kneels between her legs" not in sitting_variant_prompt,
|
|
f"Sitting oral variant kept generic kneeling-between-legs route: {sitting_variant_prompt}",
|
|
)
|
|
_expect(
|
|
"tongue extended toward genitals" not in sitting_variant_prompt,
|
|
f"Sitting oral variant kept contradictory tongue-extension expression: {sitting_variant_prompt}",
|
|
)
|
|
_expect("eye-level shot" not in sitting_variant_prompt, f"Sitting oral variant kept contradictory eye-level camera text: {sitting_variant_prompt}")
|
|
|
|
side_body_item = "side-lying oral position while blowjob with lips wrapped around the viewer's penis"
|
|
side_body_axis = {"position": "side-lying oral position"}
|
|
side_body_role_graph = hardcore_role_oral.build_oral_role_graph(
|
|
"Woman A",
|
|
"Man A",
|
|
side_body_item,
|
|
side_body_axis,
|
|
pov_labels=["Man A"],
|
|
).lower()
|
|
for term in (
|
|
"adult male viewer's abdomen, navel, pelvis, and near thigh",
|
|
"broad horizontal body surface",
|
|
"woman a enters laterally from the left edge",
|
|
"mouth on the shaft at the male abdomen line",
|
|
"lips touching the shaft at the male abdomen line",
|
|
"mouth-to-shaft contact is the nearest facial detail",
|
|
"adult male viewer's own torso starts at the lower edge",
|
|
"lower-right foreground",
|
|
"camera owner's body",
|
|
"hand around the base under her lips",
|
|
):
|
|
_expect(term in side_body_role_graph, f"Side-profile oral role graph missing {term!r}: {side_body_role_graph}")
|
|
|
|
side_body_pair = pb.build_insta_of_pair(
|
|
row_number=1,
|
|
start_index=1,
|
|
seed=3729,
|
|
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", ["side_lying"]),
|
|
location_config=_coworking_location_config(),
|
|
hardcore_camera_config=_orbit_camera(
|
|
horizontal_angle=45,
|
|
vertical_angle=0,
|
|
zoom=7.5,
|
|
subject_focus="action",
|
|
),
|
|
)
|
|
side_body_row = side_body_pair["hardcore_row"]
|
|
side_body_row["item"] = side_body_item
|
|
side_body_row["item_axis_values"] = side_body_axis
|
|
side_body_row["position_key"] = "side_lying"
|
|
side_body_row["position_keys"] = ["side_lying", "blowjob_side"]
|
|
side_body_row["source_role_graph"] = side_body_role_graph
|
|
side_body_row["role_graph"] = side_body_role_graph
|
|
side_body_krea = krea_formatter.format_krea2_prompt("", metadata_json=_json(side_body_pair), target="hardcore")
|
|
side_body_prompt = _expect_text("pov_oral_side_profile_body_line.krea_prompt", side_body_krea.get("krea_prompt"), 80).lower()
|
|
for term in (
|
|
"pov side-profile oral body-line position",
|
|
"male viewer's abdomen, navel, pelvis, and near thigh create a broad horizontal body surface",
|
|
"woman enters laterally from the left edge beside his hip",
|
|
"mouth on the shaft at the male abdomen line",
|
|
"lips touching the shaft at the male abdomen line",
|
|
"mouth-to-shaft contact is the nearest facial detail",
|
|
"adult male viewer's own torso starts at the lower edge",
|
|
"lower-right foreground",
|
|
"camera owner's body",
|
|
"hand around the base under her lips",
|
|
):
|
|
_expect(term in side_body_prompt, f"Side-profile oral Krea prompt missing {term!r}: {side_body_prompt}")
|
|
_expect("side-phone" not in side_body_prompt, f"Side-profile oral Krea prompt drifted into non-POV side-phone wording: {side_body_prompt}")
|
|
|
|
side_body_clothing_config = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPKrea2POVOralFilter"]().build(
|
|
"replace",
|
|
"",
|
|
include_blowjob_side_profile_oral=True,
|
|
)[0]
|
|
side_body_clothing_config = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPKrea2POVPromptRestore"]().build(
|
|
True,
|
|
False,
|
|
False,
|
|
False,
|
|
True,
|
|
side_body_clothing_config,
|
|
)[0]
|
|
side_body_clothing_pair = pb.build_insta_of_pair(
|
|
row_number=1,
|
|
start_index=1,
|
|
seed=3730,
|
|
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",
|
|
hardcore_clothing_continuity="partially_removed",
|
|
),
|
|
character_cast=_character_cast(pov_man=True),
|
|
hardcore_position_config=side_body_clothing_config,
|
|
location_config=_coworking_location_config(),
|
|
hardcore_camera_config=_orbit_camera(
|
|
horizontal_angle=45,
|
|
vertical_angle=0,
|
|
zoom=7.5,
|
|
subject_focus="action",
|
|
),
|
|
)
|
|
side_body_problem_clothing = (
|
|
"Clothing state: Woman A's denim shorts are pulled aside or removed below the hips; "
|
|
"button-down shirt tied at the waist, a fitted bralette remain visible from the same outfit; "
|
|
"POV foreground clothing cue: a casual shirt with belt open and pants partly lowered, "
|
|
"appearing as the viewer's hands, forearms, sleeves, or torso edge."
|
|
)
|
|
side_body_clothing_pair["hardcore_clothing_state"] = side_body_problem_clothing
|
|
side_body_clothing_pair["hardcore_row"]["hardcore_clothing_state"] = side_body_problem_clothing
|
|
side_body_clothing_krea = krea_formatter.format_krea2_prompt("", metadata_json=_json(side_body_clothing_pair), target="hardcore")
|
|
side_body_clothing_prompt = _expect_text(
|
|
"pov_oral_side_profile_body_line_restore_clothing.krea_prompt",
|
|
side_body_clothing_krea.get("krea_prompt"),
|
|
80,
|
|
).lower()
|
|
_expect(
|
|
"pov side-profile oral body-line position" in side_body_clothing_prompt,
|
|
"Side-profile oral clothing restore lost the atlas body-line pose",
|
|
)
|
|
_expect(
|
|
"pov foreground clothing cue" not in side_body_clothing_prompt,
|
|
f"Side-profile oral clothing restore should not add a second POV foreground clothing cue: {side_body_clothing_prompt}",
|
|
)
|
|
_expect(
|
|
"denim shorts" not in side_body_clothing_prompt and "below the hips" not in side_body_clothing_prompt,
|
|
f"Side-profile oral clothing restore should not expose hidden lower-body clothing: {side_body_clothing_prompt}",
|
|
)
|
|
_expect(
|
|
"button-down shirt tied at the waist" in side_body_clothing_prompt and "fitted bralette" in side_body_clothing_prompt,
|
|
f"Side-profile oral clothing restore should preserve visible upper outfit continuity: {side_body_clothing_prompt}",
|
|
)
|
|
_expect(
|
|
"the woman's lower garments are pulled aside out of frame" in side_body_clothing_prompt,
|
|
f"Side-profile oral clothing restore should keep partial-removal state without exposing hidden garments: {side_body_clothing_prompt}",
|
|
)
|
|
_expect(
|
|
"the woman wears the button-down shirt tied at the waist" in side_body_clothing_prompt,
|
|
f"Side-profile oral clothing restore should assign visible clothing to the woman: {side_body_clothing_prompt}",
|
|
)
|
|
|
|
sitting_pair = pb.build_insta_of_pair(
|
|
row_number=1,
|
|
start_index=1,
|
|
seed=3731,
|
|
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", ["reclining_oral"]),
|
|
location_config=_coworking_location_config(),
|
|
hardcore_camera_config=_orbit_camera(
|
|
horizontal_angle=45,
|
|
vertical_angle=0,
|
|
zoom=7.5,
|
|
subject_focus="action",
|
|
),
|
|
)
|
|
sitting_row = sitting_pair["hardcore_row"]
|
|
sitting_role_graph = (
|
|
"Woman A sits upright between Man A's open thighs in a first-person sitting oral frame; "
|
|
"her face lowers close to the shaft tip while her hands stay low at the base."
|
|
)
|
|
sitting_row["item"] = "upright sitting oral position with her mouth on the viewer's penis"
|
|
sitting_row["item_axis_values"] = {"position": "upright sitting oral position"}
|
|
sitting_row["position_key"] = "reclining_oral"
|
|
sitting_row["position_keys"] = ["reclining_oral", "blowjob_sitting"]
|
|
sitting_row["source_role_graph"] = sitting_role_graph
|
|
sitting_row["role_graph"] = sitting_role_graph
|
|
sitting_krea = krea_formatter.format_krea2_prompt("", metadata_json=_json(sitting_pair), target="hardcore")
|
|
sitting_prompt = _expect_text("pov_oral_sitting_upright.krea_prompt", sitting_krea.get("krea_prompt"), 80).lower()
|
|
for term in (
|
|
"pov upright sitting oral position",
|
|
"woman sits low between his open thighs",
|
|
"face lowered close to the exact center contact point",
|
|
"open mouth covers the tip",
|
|
"hands stay low at the base",
|
|
):
|
|
_expect(term in sitting_prompt, f"Upright sitting oral Krea prompt missing {term!r}: {sitting_prompt}")
|
|
_expect("pov prone frontal oral position" not in sitting_prompt, f"Upright sitting oral drifted into laying route: {sitting_prompt}")
|
|
_expect("pov kneeling oral position" not in sitting_prompt, f"Upright sitting oral drifted into top-view kneeling route: {sitting_prompt}")
|
|
|
|
|
|
def smoke_pov_climax_target_policy() -> None:
|
|
context = (
|
|
"doggy rear-entry on all fours with hips raised and ass toward the camera; "
|
|
"mouth, face, lips, and tongue appear only in expression detail"
|
|
)
|
|
target = krea_pov_actions.pov_ejaculation_target(context)
|
|
_expect(
|
|
target == "across her ass, thighs, and lower back",
|
|
f"rear-entry climax target should beat face-expression tokens, got {target!r}",
|
|
)
|
|
phrase = krea_pov_actions.pov_action_phrase(
|
|
"Man A ejaculates semen onto Woman A's face and chest; visible semen lands while her mouth stays open",
|
|
["Man A"],
|
|
role_graph="Woman A is on all fours directly in front of Man A with hips raised while Man A is positioned behind her.",
|
|
hard_item="cumshot with mouth and face expression",
|
|
composition="first-person rear-view frame looking down at the woman's raised ass",
|
|
axis_values={"position": "doggy style position"},
|
|
).lower()
|
|
_expect("face and chest" not in phrase, f"POV rear-entry climax kept incompatible face/chest target: {phrase}")
|
|
_expect("across her ass, thighs, and lower back" in phrase, f"POV rear-entry climax lost rear target: {phrase}")
|
|
open_thigh_phrase = krea_pov_actions.pov_action_phrase(
|
|
(
|
|
"Woman A lies on her back with thighs open while Man A kneels between her legs "
|
|
"and ejaculates semen across her pussy and thighs; thick visible fluid covers the exposed opening"
|
|
),
|
|
["Man A"],
|
|
role_graph=(
|
|
"Woman A lies on her back with thighs open toward Man A after ejaculation, "
|
|
"with semen and clear fluid covering the exposed pussy and inner thighs."
|
|
),
|
|
hard_item="post-ejaculation open-thigh display with thick semen or fluid around the exposed opening",
|
|
composition="first-person open-thigh aftermath frame with her face and torso visible behind the wet detail",
|
|
axis_values={"position": "reclining with thighs open"},
|
|
).lower()
|
|
_expect(
|
|
"pov post-ejaculation open-thigh display" in open_thigh_phrase,
|
|
f"POV open-thigh climax should use aftermath display wording: {open_thigh_phrase}",
|
|
)
|
|
_expect(
|
|
"wet aftermath detail is the exact center" in open_thigh_phrase,
|
|
f"POV open-thigh climax lost centered wet aftermath hierarchy: {open_thigh_phrase}",
|
|
)
|
|
_expect(
|
|
"missionary position" not in open_thigh_phrase and "penetrative sex position" not in open_thigh_phrase,
|
|
f"POV open-thigh climax should not read as active penetration: {open_thigh_phrase}",
|
|
)
|
|
|
|
|
|
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_missionary_folded",
|
|
"missionary_folded",
|
|
("woman a lies on her back", "knees folded high toward her chest", "penis thrusts into her pussy"),
|
|
(
|
|
"pov folded missionary high-leg penetration position",
|
|
"large centered shaft rising from the lower center",
|
|
"compact knee block above the contact",
|
|
"penetrates her pussy",
|
|
),
|
|
),
|
|
(
|
|
"pov_penetration_cowgirl",
|
|
"cowgirl",
|
|
(
|
|
"woman a straddles man a's hips facing him",
|
|
"man a lies under her",
|
|
"wide horizontal thigh bridge",
|
|
"man a's hands grip the sides of her thighs",
|
|
),
|
|
(
|
|
"pov frontal cowgirl wide-thigh bridge position",
|
|
"viewer reclines underneath her",
|
|
"wide horizontal thigh bridge",
|
|
"knees planted outside the viewer's hips",
|
|
"viewer hands grip the sides of her thighs",
|
|
"centered contact remains below her belly",
|
|
),
|
|
),
|
|
(
|
|
"pov_penetration_cowgirl_alt",
|
|
"cowgirl_alt",
|
|
("woman a faces man a in a low seated squat", "man a lies flat on his back", "man a supports the underside of her thighs"),
|
|
(
|
|
"pov low cowgirl seated-squat penetration position",
|
|
"viewer lies flat on his back",
|
|
"lens sits low at the viewer's abdomen",
|
|
"high room background behind her upper body",
|
|
"viewer supports the underside of her thighs",
|
|
"knees bent wide and close to the camera",
|
|
"penetrates her pussy",
|
|
),
|
|
),
|
|
(
|
|
"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",
|
|
"back, hips, and ass are the nearest largest shapes",
|
|
"viewer thighs frame the lower corners",
|
|
"centered contact sits directly between her thighs below her ass",
|
|
),
|
|
),
|
|
(
|
|
"pov_penetration_reverse_cowgirl_alt",
|
|
"reverse_cowgirl_alt",
|
|
("woman a sits upright facing away", "man a lies under her", "man a's penis thrusts into her pussy"),
|
|
(
|
|
"pov upright reverse cowgirl back-facing penetration position",
|
|
"back stays vertical and readable",
|
|
"viewer hands hold her hips",
|
|
"viewer thighs frame the lower corners",
|
|
"centered contact remains visible below her ass",
|
|
),
|
|
),
|
|
(
|
|
"pov_penetration_doggy",
|
|
"doggy",
|
|
("woman a is on all fours", "man a is positioned behind her"),
|
|
("top-down pov doggy position", "on all fours", "forearms folded", "penetrates her pussy"),
|
|
),
|
|
(
|
|
"pov_penetration_edge_supported",
|
|
"edge_supported",
|
|
("raised edge", "man a kneels between her thighs"),
|
|
("pov elevated-edge missionary position", "flat elevated support", "hands holding her calves or outer thighs", "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"),
|
|
("top-down pov doggy position", "on all fours", "forearms folded", "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(hard_row.get("action_family") == "anal", f"{name} action_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(hard_row.get("action_family") == "toy_double", "double route action_family should stay toy_double")
|
|
_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,
|
|
("foreground hand is the largest lower-frame object", "open thighs form a v", "wrist enters from the bottom center"),
|
|
("foreground hand is the largest lower-frame object", "two fingers at her vulva and clit"),
|
|
),
|
|
(
|
|
"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)
|
|
expected_action_family = "manual" if family == "manual" else "foreplay"
|
|
_expect(
|
|
row.get("action_family") == expected_action_family,
|
|
f"{name} action_family mismatch: {row.get('action_family')} != {expected_action_family}",
|
|
)
|
|
_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")
|
|
|
|
wand_role_graph = hardcore_role_interaction.build_manual_role_graph(
|
|
"Woman A",
|
|
"Man A",
|
|
item_text="toy-assisted manual stimulation with one hand controlling the toy",
|
|
item_axis_values={
|
|
"position": "reclining open-thigh manual position",
|
|
"manual_act": "partner-held vibrator pressed to the clit",
|
|
"manual_detail": "toy and fingers both visible at the contact point",
|
|
},
|
|
)
|
|
wand_role_lower = wand_role_graph.lower()
|
|
for term in (
|
|
"single continuous teal wand-style massager",
|
|
"rounded bulb head presses flat",
|
|
"smooth handle angles in from the bottom right",
|
|
"open thighs and knees form a v",
|
|
):
|
|
_expect(term in wand_role_lower, f"manual wand role graph missing {term!r}: {wand_role_lower}")
|
|
wand_row = _fixture_hardcore_row(
|
|
subcategory="Manual stimulation",
|
|
subcategory_slug="manual_stimulation",
|
|
item="toy-assisted manual stimulation with one hand controlling the toy, partner-held vibrator pressed to the clit",
|
|
custom_item="Manual stimulation",
|
|
item_axis_values={
|
|
"position": "reclining open-thigh manual position",
|
|
"manual_act": "partner-held vibrator pressed to the clit",
|
|
"manual_detail": "toy and fingers both visible at the contact point",
|
|
},
|
|
scene_text=(
|
|
"coworking lounge with tall windows, warm desks, laptop tables, "
|
|
"glass partition seams, repeated desk rows, plants, and soft shared-office depth"
|
|
),
|
|
composition="close first-person office-chair toy-contact frame",
|
|
source_composition="close first-person office-chair toy-contact frame",
|
|
role_graph=wand_role_graph,
|
|
source_role_graph=wand_role_graph,
|
|
action_family="manual",
|
|
position_family="manual",
|
|
position_key="wand",
|
|
position_keys=["wand", "toy_contact", "open_thighs"],
|
|
)
|
|
wand_krea = krea_formatter.format_krea2_prompt("", metadata_json=_json(wand_row), target="single")
|
|
wand_prompt = _expect_text("interaction_wand_toy_contact.krea_prompt", wand_krea.get("krea_prompt"), 80).lower()
|
|
for term in (
|
|
"single continuous teal wand-style massager",
|
|
"rounded bulb head presses flat",
|
|
"smooth handle angles in from the bottom right",
|
|
"open thighs and knees form a v",
|
|
"face and torso remain visible behind",
|
|
):
|
|
_expect(term in wand_prompt, f"manual wand Krea prompt missing {term!r}: {wand_prompt}")
|
|
|
|
generated_wand_row = _prompt_row(
|
|
name="interaction_generated_wand_toy_contact",
|
|
category="Hardcore sexual poses",
|
|
subcategory="Manual stimulation",
|
|
seed=46,
|
|
character_cast=_character_cast(pov_man=True),
|
|
women_count=1,
|
|
men_count=1,
|
|
hardcore_position_config=_position_filter("manual_only", "manual", ["fingering"]),
|
|
location_config=_coworking_location_config(),
|
|
)
|
|
generated_wand_krea = krea_formatter.format_krea2_prompt("", metadata_json=_json(generated_wand_row), target="single")
|
|
generated_wand_prompt = _expect_text(
|
|
"interaction_generated_wand_toy_contact.krea_prompt",
|
|
generated_wand_krea.get("krea_prompt"),
|
|
80,
|
|
).lower()
|
|
for term in (
|
|
"single continuous teal wand-style massager",
|
|
"rounded bulb head presses flat",
|
|
"smooth handle angles in from the bottom right",
|
|
"open thighs and knees form a v",
|
|
):
|
|
_expect(term in generated_wand_prompt, f"generated manual wand Krea prompt missing {term!r}: {generated_wand_prompt}")
|
|
|
|
spread_role_graph = hardcore_role_interaction.build_interaction_role_graph(
|
|
"Woman A",
|
|
"Man A",
|
|
slug="camera_performance",
|
|
item_text="spread open for camera in a reclining camera-presentation position",
|
|
item_axis_values={
|
|
"position": "reclining camera-presentation position",
|
|
"performance_act": "spread open for camera",
|
|
"presentation_detail": "open thighs and knees held wide",
|
|
"hand_detail": "hands holding knees",
|
|
},
|
|
)
|
|
spread_role_lower = spread_role_graph.lower()
|
|
for term in ("knees raised", "broad v-frame", "hands hold her knees", "face and torso remain visible"):
|
|
_expect(term in spread_role_lower, f"Spread presentation role graph missing {term!r}: {spread_role_lower}")
|
|
spread_row = _fixture_hardcore_row(
|
|
subcategory="Camera performance",
|
|
subcategory_slug="camera_performance",
|
|
item="spread open for camera in a reclining camera-presentation position, open thighs and knees held wide, hands holding knees",
|
|
custom_item="Camera performance",
|
|
item_axis_values={
|
|
"position": "reclining camera-presentation position",
|
|
"performance_act": "spread open for camera",
|
|
"presentation_detail": "open thighs and knees held wide",
|
|
"hand_detail": "hands holding knees",
|
|
},
|
|
scene_text=(
|
|
"coworking lounge with tall windows, warm desks, laptop tables, "
|
|
"glass partition seams, repeated desk rows, plants, and soft shared-office depth"
|
|
),
|
|
composition="close seated camera frame in a coworking lounge",
|
|
source_composition="close seated camera frame in a coworking lounge",
|
|
role_graph=spread_role_graph,
|
|
source_role_graph=spread_role_graph,
|
|
action_family="foreplay",
|
|
position_family="interaction",
|
|
position_key="open_thighs",
|
|
position_keys=["camera_showing", "open_thighs"],
|
|
)
|
|
_expect_custom_row(spread_row, "interaction_spread_open_thigh_presentation")
|
|
_expect(spread_row.get("position_family") == "interaction", "Spread presentation position_family should be interaction")
|
|
_expect("camera_showing" in (spread_row.get("position_keys") or []), "Spread presentation lost camera_showing key")
|
|
_expect("open_thighs" in (spread_row.get("position_keys") or []), "Spread presentation lost open_thighs key")
|
|
spread_krea = krea_formatter.format_krea2_prompt("", metadata_json=_json(spread_row), target="single")
|
|
spread_prompt = _expect_text("interaction_spread_open_thigh_presentation.krea_prompt", spread_krea.get("krea_prompt"), 80).lower()
|
|
_expect("metadata" in spread_krea.get("method", ""), "Spread presentation Krea did not use metadata")
|
|
for term in ("knees raised", "broad v-frame", "hands hold her knees", "office chair seat and chair arms"):
|
|
_expect(term in spread_prompt, f"Spread presentation Krea prompt missing {term!r}: {spread_prompt}")
|
|
|
|
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')}")
|
|
if family == "threesome":
|
|
_expect(row.get("action_family") == "threesome", f"{name} action_family should be threesome")
|
|
_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",
|
|
},
|
|
scene_text=(
|
|
"coworking lounge with tall windows, warm desks, laptop tables, "
|
|
"glass partition seams, repeated desk rows, plants, and soft shared-office depth"
|
|
),
|
|
composition="close crop on hands and face in a coworking lounge",
|
|
source_composition="close crop on hands and face in a coworking lounge",
|
|
role_graph=(
|
|
"Woman A reclines with thighs open while Man A's foreground hand is the largest lower-frame object; "
|
|
"her open thighs form a V around the hand, the wrist enters from the bottom center, "
|
|
"and two fingers at her vulva and clit make the central manual-contact point while her face and torso remain visible behind the open thighs."
|
|
),
|
|
source_role_graph=(
|
|
"Woman A reclines with thighs open while Man A's foreground hand is the largest lower-frame object; "
|
|
"her open thighs form a V around the hand, the wrist enters from the bottom center, "
|
|
"and two fingers at her vulva and clit make the central manual-contact point while her face and torso remain visible behind the open thighs."
|
|
),
|
|
action_family="manual",
|
|
position_family="manual",
|
|
position_key="fingering",
|
|
position_keys=["fingering", "open_thighs"],
|
|
),
|
|
"krea_terms": ("foreground hand is the largest lower-frame object", "office chair seat and chair arms"),
|
|
"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",),
|
|
},
|
|
{
|
|
"name": "fixture_climax_open_thigh_aftermath",
|
|
"row": _fixture_hardcore_row(
|
|
subcategory="Cumshot and climax",
|
|
subcategory_slug="cumshot_climax",
|
|
item=(
|
|
"post-ejaculation open-thigh display with thick semen and clear fluid "
|
|
"around the exposed opening, body still after ejaculation"
|
|
),
|
|
custom_item="Cumshot and climax",
|
|
item_axis_values={"position": "reclining with thighs open"},
|
|
scene_text=(
|
|
"coworking lounge with tall windows, warm desks, laptop tables, "
|
|
"glass partition seams, repeated desk rows, plants, and soft shared-office depth"
|
|
),
|
|
composition="first-person open-thigh aftermath frame in a coworking lounge",
|
|
source_composition="first-person open-thigh aftermath frame in a coworking lounge",
|
|
role_graph=(
|
|
"Woman A lies on her back with thighs open toward Man A after ejaculation, "
|
|
"with semen and clear fluid covering the exposed pussy and inner thighs."
|
|
),
|
|
source_role_graph=(
|
|
"Woman A lies on her back with thighs open toward Man A after ejaculation, "
|
|
"with semen and clear fluid covering the exposed pussy and inner thighs."
|
|
),
|
|
action_family="climax",
|
|
position_family="climax",
|
|
position_key="open_thighs",
|
|
position_keys=["open_thighs"],
|
|
pov_character_labels=["Man A"],
|
|
),
|
|
"krea_terms": (
|
|
"pov post-ejaculation open-thigh display",
|
|
"wet aftermath detail is the exact center",
|
|
"office chair seat and chair arms",
|
|
),
|
|
"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")
|
|
|
|
external_pair = {
|
|
"trigger": Trigger,
|
|
"shared_descriptor": "25-year-old adult woman, slim figure, fair skin, blonde hair, blue eyes",
|
|
"shared_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",
|
|
],
|
|
"options": {
|
|
"softcore_cast": "same_as_hardcore",
|
|
"continuity": "same_creator_same_room",
|
|
"softcore_level": "lingerie_tease",
|
|
"hardcore_level": "hardcore",
|
|
},
|
|
"softcore_row": {
|
|
"prompt": f"{Trigger}, embedded soft pair prompt.",
|
|
"caption": f"{Trigger}, embedded soft pair caption.",
|
|
"negative_prompt": "bad hands, bad hands",
|
|
"subject_type": "configured_cast",
|
|
"primary_subject": "woman",
|
|
"item": "red satin lingerie set",
|
|
"softcore_item_prompt_label": "Erotic outfit",
|
|
"pose": "standing together beside a mirror",
|
|
"scene_text": "private studio room with warm light",
|
|
"composition": "mirror teaser composition",
|
|
"expression": "confident soft smile",
|
|
"softcore_partner_styling": {
|
|
"outfits": ["Man A wears a black buttoned shirt and dark trousers"],
|
|
"pose": "standing close with one hand at the waist",
|
|
},
|
|
"camera_config": {
|
|
"camera_mode": "standard",
|
|
"custom_camera_prompt": "front-right quarter view, eye-level shot, medium shot",
|
|
},
|
|
"camera_directive": "Camera: row soft front-right view.",
|
|
"camera_scene_directive": "Row soft mirror camera layout.",
|
|
},
|
|
"hardcore_row": {
|
|
"prompt": f"{Trigger}, embedded hard pair prompt.",
|
|
"caption": f"{Trigger}, embedded hard pair caption.",
|
|
"negative_prompt": "low quality, low quality",
|
|
"subject_type": "configured_cast",
|
|
"primary_subject": "configured_cast",
|
|
"item": "missionary position while full-body penetrative sex",
|
|
"custom_item": "Penetrative sex",
|
|
"item_label": "Sexual pose",
|
|
"item_axis_values": {
|
|
"position": "missionary position",
|
|
"penetration_act": "full-body penetrative sex",
|
|
},
|
|
"scene_text": "private studio room with warm light",
|
|
"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 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 penis thrusts into her pussy."
|
|
),
|
|
"expression": "focused adult expression",
|
|
"action_family": "penetration",
|
|
"position_family": "penetrative",
|
|
"position_key": "missionary",
|
|
"position_keys": ["missionary"],
|
|
"hardcore_clothing_state": "Woman A's red satin lingerie set is pushed aside at the hips",
|
|
"camera_config": {
|
|
"camera_mode": "standard",
|
|
"custom_camera_prompt": "right side view, eye-level shot, medium shot",
|
|
},
|
|
"camera_directive": "Camera: row hard right-side view.",
|
|
"camera_scene_directive": "Row hard body-aligned camera layout.",
|
|
},
|
|
}
|
|
external_metadata = _json(external_pair)
|
|
|
|
krea_pair = krea_formatter.format_krea2_prompt("", metadata_json=external_metadata, target="auto")
|
|
krea_soft = _expect_text("fixture_external_pair.krea_soft", krea_pair.get("krea_softcore_prompt"), 40).lower()
|
|
krea_hard = _expect_text("fixture_external_pair.krea_hard", krea_pair.get("krea_hardcore_prompt"), 40).lower()
|
|
_expect(krea_pair.get("method") == "metadata_json:krea2(insta_of_pair)", "External pair Krea route changed method")
|
|
_expect("black buttoned shirt" in krea_soft, "External pair Krea soft route lost embedded partner styling")
|
|
_expect_no_softcore_noise("fixture_external_pair.krea_soft", krea_pair.get("krea_softcore_prompt"))
|
|
_expect("styled creator-teaser frame" in krea_soft, "External pair Krea soft route lost clean cast-presence wording")
|
|
_expect("red satin lingerie set" in krea_hard, "External pair Krea hard route lost embedded clothing state")
|
|
_expect("row hard right-side view" in krea_hard, "External pair Krea hard route lost embedded camera directive")
|
|
_expect_no_duplicate_comma_items("fixture_external_pair.krea_negative", krea_pair.get("negative_prompt"))
|
|
|
|
sdxl_pair = sdxl_formatter.format_sdxl_prompt(
|
|
"",
|
|
metadata_json=external_metadata,
|
|
target="auto",
|
|
trigger=SdxlTrigger,
|
|
prepend_trigger=True,
|
|
)
|
|
sdxl_soft = _expect_text("fixture_external_pair.sdxl_soft", sdxl_pair.get("sdxl_softcore_prompt"), 40).lower()
|
|
sdxl_hard = _expect_text("fixture_external_pair.sdxl_hard", sdxl_pair.get("sdxl_hardcore_prompt"), 40).lower()
|
|
_expect(sdxl_pair.get("method") == "metadata_json:sdxl(insta_of_pair)", "External pair SDXL route changed method")
|
|
_expect_trigger_once("fixture_external_pair.sdxl_soft", sdxl_pair.get("sdxl_softcore_prompt"), SdxlTrigger)
|
|
_expect_trigger_once("fixture_external_pair.sdxl_hard", sdxl_pair.get("sdxl_hardcore_prompt"), SdxlTrigger)
|
|
_expect("black buttoned shirt" in sdxl_soft, "External pair SDXL soft route lost embedded partner styling")
|
|
_expect("1man" in sdxl_soft, "External pair SDXL soft route lost partner count tag")
|
|
_expect("40-year-old adult man" in sdxl_soft, "External pair SDXL soft route lost partner descriptor")
|
|
_expect("softcore teaser" in sdxl_soft, "External pair SDXL soft route lost softcore teaser tag")
|
|
_expect_no_softcore_noise("fixture_external_pair.sdxl_soft", sdxl_pair.get("sdxl_softcore_prompt"))
|
|
_expect("red satin lingerie set" in sdxl_hard, "External pair SDXL hard route lost embedded clothing state")
|
|
_expect("row hard right-side view" in sdxl_hard, "External pair SDXL hard route lost embedded camera directive")
|
|
_expect_no_duplicate_comma_items("fixture_external_pair.sdxl_negative", sdxl_pair.get("negative_prompt"))
|
|
|
|
caption, method = caption_naturalizer.naturalize_caption(
|
|
"",
|
|
metadata_json=external_metadata,
|
|
target="auto",
|
|
trigger=Trigger,
|
|
include_trigger=True,
|
|
)
|
|
caption_text = _expect_text("fixture_external_pair.caption", caption, 40).lower()
|
|
_expect(method == "metadata_json:metadata(insta_of_pair)", "External pair caption route changed method")
|
|
_expect_trigger_once("fixture_external_pair.caption", caption, Trigger)
|
|
_expect("black buttoned shirt" in caption_text, "External pair caption route lost embedded partner styling")
|
|
_expect("softcore side" in caption_text, "External pair caption route lost softcore side")
|
|
_expect("hardcore side" in caption_text, "External pair caption route lost hardcore side")
|
|
_expect("softcore version" not in caption_text and "hardcore version" not in caption_text, "External pair caption kept version labels")
|
|
|
|
generated = _prompt_row(
|
|
name="fixture_generated_formatter_invariants",
|
|
category="Casual clothes",
|
|
subcategory="Casual clothes / Smart casual",
|
|
seed=4101,
|
|
men_count=0,
|
|
character_cast=_character_cast_subjects(["woman"]),
|
|
camera_config=_orbit_camera(horizontal_angle=45, vertical_angle=0, zoom=5.5),
|
|
location_config=_coworking_location_config(),
|
|
)
|
|
_expect_row_base(generated, "fixture_generated_formatter_invariants")
|
|
_expect(generated.get("source") == "json_category", "Generated formatter invariant should use category JSON route")
|
|
generated_metadata = _json(generated)
|
|
generated_item = _expect_text("fixture_generated_formatter_invariants.item", generated.get("item"), 8)
|
|
item_anchor = " ".join(generated_item.lower().split()[:3])
|
|
|
|
krea = krea_formatter.format_krea2_prompt("", metadata_json=generated_metadata, target="single")
|
|
krea_prompt = _expect_text("fixture_generated_formatter_invariants.krea_prompt", krea.get("krea_prompt"), 40).lower()
|
|
_expect("metadata" in krea.get("method", ""), "Generated Krea invariant did not use metadata")
|
|
_expect(item_anchor in krea_prompt, "Generated Krea prompt lost clothing/item metadata")
|
|
_expect("coworking lounge" in krea_prompt, "Generated Krea prompt lost scene metadata")
|
|
_expect("front-right quarter view" in krea_prompt, "Generated Krea prompt lost camera metadata")
|
|
|
|
sdxl = sdxl_formatter.format_sdxl_prompt(
|
|
"",
|
|
metadata_json=generated_metadata,
|
|
target="single",
|
|
trigger=SdxlTrigger,
|
|
prepend_trigger=True,
|
|
)
|
|
sdxl_prompt = _expect_text("fixture_generated_formatter_invariants.sdxl_prompt", sdxl.get("sdxl_prompt"), 40).lower()
|
|
_expect("metadata" in sdxl.get("method", ""), "Generated SDXL invariant did not use metadata")
|
|
_expect_trigger_once("fixture_generated_formatter_invariants.sdxl_prompt", sdxl.get("sdxl_prompt"), SdxlTrigger)
|
|
_expect(item_anchor in sdxl_prompt, "Generated SDXL prompt lost clothing/item metadata")
|
|
_expect("coworking lounge" in sdxl_prompt, "Generated SDXL prompt lost scene metadata")
|
|
_expect("front-right quarter view" in sdxl_prompt, "Generated SDXL prompt lost camera metadata")
|
|
for leaked_label in ("camera:", "composition:", "scene:", "setting:", "pose:", "clothing:"):
|
|
_expect(leaked_label not in sdxl_prompt, f"Generated SDXL prompt leaked raw label {leaked_label!r}")
|
|
|
|
caption, method = caption_naturalizer.naturalize_caption(
|
|
"",
|
|
metadata_json=generated_metadata,
|
|
trigger=Trigger,
|
|
include_trigger=False,
|
|
caption_profile="training_dense",
|
|
)
|
|
caption_text = _expect_text("fixture_generated_formatter_invariants.caption", caption, 40).lower()
|
|
_expect("metadata" in method, "Generated caption invariant did not use metadata")
|
|
_expect_trigger_once("fixture_generated_formatter_invariants.caption", caption, Trigger)
|
|
_expect(item_anchor in caption_text, "Generated training caption lost clothing/item metadata")
|
|
_expect("coworking lounge" in caption_text, "Generated training caption lost scene metadata")
|
|
|
|
|
|
def _expect_comfy_input_spec(node_name: str, group: str, input_name: str, spec: Any) -> None:
|
|
_expect(isinstance(input_name, str) and input_name, f"{node_name}.{group} has invalid input name {input_name!r}")
|
|
if group == "hidden":
|
|
_expect(
|
|
isinstance(spec, (str, tuple)),
|
|
f"{node_name}.{input_name} hidden input should be a ComfyUI hidden token or tuple",
|
|
)
|
|
return
|
|
|
|
_expect(isinstance(spec, tuple) and spec, f"{node_name}.{input_name} visible input has invalid spec")
|
|
type_spec = spec[0]
|
|
_expect(
|
|
isinstance(type_spec, str) or isinstance(type_spec, list),
|
|
f"{node_name}.{input_name} input type should be a socket type or choices list",
|
|
)
|
|
if isinstance(type_spec, list):
|
|
_expect(type_spec, f"{node_name}.{input_name} choice list should not be empty")
|
|
_expect(len(spec) >= 2 and isinstance(spec[1], dict), f"{node_name}.{input_name} visible input options should be a dict")
|
|
_expect("tooltip" in spec[1], f"{node_name}.{input_name} visible input is missing tooltip")
|
|
|
|
|
|
def smoke_node_runtime_contracts() -> None:
|
|
node_names = sorted(sxcp_nodes.NODE_CLASS_MAPPINGS)
|
|
_expect(node_names, "Node registry is empty")
|
|
_expect(
|
|
set(sxcp_nodes.NODE_CLASS_MAPPINGS) == set(sxcp_nodes.NODE_DISPLAY_NAME_MAPPINGS),
|
|
"Node class and display registries are out of sync",
|
|
)
|
|
_expect(len(node_names) >= 50, "Node registry unexpectedly small")
|
|
_expect(
|
|
loop_nodes._explicit_loop_schedule("1,3,5", 5) == [1, 3, 5],
|
|
"Loop schedule should parse comma-separated indexes",
|
|
)
|
|
_expect(
|
|
loop_nodes._explicit_loop_schedule("2-4", 5) == [2, 3, 4],
|
|
"Loop schedule should expand inclusive ranges",
|
|
)
|
|
_expect(
|
|
loop_nodes._next_loop_index(4, 10, schedule="4,2") == (2, True),
|
|
"Loop schedule should preserve explicit order",
|
|
)
|
|
_expect(
|
|
loop_nodes._next_loop_index(2, 10, schedule="4,2") == (11, False),
|
|
"Loop schedule should stop after the last scheduled index",
|
|
)
|
|
|
|
for node_name in node_names:
|
|
node_class = sxcp_nodes.NODE_CLASS_MAPPINGS[node_name]
|
|
display_name = sxcp_nodes.NODE_DISPLAY_NAME_MAPPINGS.get(node_name)
|
|
_expect(isinstance(display_name, str) and display_name, f"{node_name} has no display name")
|
|
|
|
input_types = node_class.INPUT_TYPES()
|
|
_expect(isinstance(input_types, dict), f"{node_name}.INPUT_TYPES should return a dict")
|
|
_expect("required" in input_types, f"{node_name}.INPUT_TYPES lost required group")
|
|
for group, inputs in input_types.items():
|
|
_expect(group in {"required", "optional", "hidden"}, f"{node_name}.INPUT_TYPES has unknown group {group!r}")
|
|
_expect(isinstance(inputs, dict), f"{node_name}.{group} inputs should be a dict")
|
|
for input_name, spec in inputs.items():
|
|
_expect_comfy_input_spec(node_name, group, input_name, spec)
|
|
|
|
return_types = getattr(node_class, "RETURN_TYPES", None)
|
|
_expect(isinstance(return_types, tuple) and return_types, f"{node_name}.RETURN_TYPES should be a non-empty tuple")
|
|
for output_type in return_types:
|
|
_expect(isinstance(output_type, str) and output_type, f"{node_name}.RETURN_TYPES contains an invalid output type")
|
|
|
|
return_names = getattr(node_class, "RETURN_NAMES", None)
|
|
if return_names is not None:
|
|
_expect(isinstance(return_names, tuple), f"{node_name}.RETURN_NAMES should be a tuple when present")
|
|
_expect(len(return_names) == len(return_types), f"{node_name}.RETURN_NAMES length does not match RETURN_TYPES")
|
|
for output_name in return_names:
|
|
_expect(isinstance(output_name, str) and output_name, f"{node_name}.RETURN_NAMES contains an invalid name")
|
|
|
|
function_name = getattr(node_class, "FUNCTION", None)
|
|
_expect(isinstance(function_name, str) and function_name, f"{node_name}.FUNCTION should name the executor method")
|
|
_expect(callable(getattr(node_class(), function_name, None)), f"{node_name}.FUNCTION target is not callable")
|
|
category = getattr(node_class, "CATEGORY", None)
|
|
_expect(isinstance(category, str) and category, f"{node_name}.CATEGORY should be a non-empty string")
|
|
|
|
|
|
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")
|
|
global_seed_inputs = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPGlobalSeed"].INPUT_TYPES().get("required") or {}
|
|
global_seed_tooltip = global_seed_inputs.get("global_seed", (None, {}))[1].get("tooltip", "")
|
|
_expect("Connect seed to generator seed" in global_seed_tooltip, "Global Seed tooltip should explain generator wiring")
|
|
|
|
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("clothing_seed_mode" in seed_inputs, "Seed Control lost clothing seed mode input")
|
|
_expect("clothing_seed" in seed_inputs, "Seed Control lost clothing seed input")
|
|
_expect("tooltip" in seed_inputs["category_seed_mode"][1], "Seed Control tooltip injection missing")
|
|
_expect(seed_control.RETURN_NAMES == ("seed_config", "summary"), "Seed Control lost visible summary output")
|
|
category_seed_tooltip = node_tooltips._tooltip_for_input("SxCPSeedControl", "category_seed_mode")
|
|
_expect(
|
|
"random rerolls this axis at queue time" in category_seed_tooltip
|
|
and "field value stays unchanged" in category_seed_tooltip,
|
|
"Node tooltip policy lost Seed Control override",
|
|
)
|
|
clothing_seed_tooltip = node_tooltips._tooltip_for_input("SxCPSeedControl", "clothing_seed_mode")
|
|
_expect("clothing/outfit" in clothing_seed_tooltip, "Node tooltip policy lost Seed Control clothing override")
|
|
_expect(
|
|
"Autoscaling switch input" in node_tooltips._tooltip_for_input("SxCPIndexSwitch", "input_12"),
|
|
"Node tooltip policy lost autoscaling input fallback",
|
|
)
|
|
seed_control_config, seed_control_summary = seed_control().build(
|
|
"fixed",
|
|
-1,
|
|
"follow_main",
|
|
-1,
|
|
"random",
|
|
-1,
|
|
"fixed",
|
|
222,
|
|
"auto",
|
|
123,
|
|
"auto",
|
|
-1,
|
|
"fixed",
|
|
777,
|
|
"follow_main",
|
|
888,
|
|
"auto",
|
|
-1,
|
|
"fixed",
|
|
999,
|
|
)
|
|
parsed_seed_control = json.loads(seed_control_config)
|
|
_expect(parsed_seed_control.get("category_seed") == 0, "Seed Control fixed mode did not clamp negative seed")
|
|
_expect(parsed_seed_control.get("subcategory_seed") == -1, "Seed Control follow_main mode should emit -1")
|
|
_expect(int(parsed_seed_control.get("content_seed", -1)) >= 0, "Seed Control random mode did not emit resolved seed")
|
|
_expect(parsed_seed_control.get("clothing_seed") == 222, "Seed Control fixed clothing seed changed")
|
|
_expect(parsed_seed_control.get("person_seed") == 123, "Seed Control auto mode did not preserve explicit value")
|
|
_expect(parsed_seed_control.get("pose_seed") == 777, "Seed Control fixed mode did not preserve positive seed")
|
|
_expect(parsed_seed_control.get("role_seed") == -1, "Seed Control follow_main role did not emit -1")
|
|
_expect(parsed_seed_control.get("composition_seed") == 999, "Seed Control fixed composition seed changed")
|
|
_expect("category=0" in seed_control_summary, "Seed Control summary lost fixed resolved seed")
|
|
_expect("subcategory=follow_main" in seed_control_summary, "Seed Control summary lost follow_main marker")
|
|
_expect(
|
|
f"content={parsed_seed_control['content_seed']}" in seed_control_summary,
|
|
"Seed Control summary lost random resolved seed value",
|
|
)
|
|
|
|
seed_locker = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPSeedLocker"]
|
|
locker_inputs = seed_locker.INPUT_TYPES().get("required") or {}
|
|
_expect(
|
|
list(locker_inputs["reroll_axis"][0]) == seed_config.seed_reroll_axis_choices(),
|
|
"Seed Locker reroll choices drifted from seed_config",
|
|
)
|
|
|
|
seed, global_seed_config, summary = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPGlobalSeed"]().build(12345)
|
|
parsed_seed = json.loads(global_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 = seed_locker().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")
|
|
bucket_seed_config = json.dumps({"camera_seed": 9001})
|
|
bucket_seeded_a = bucket_node.build("landscape", -1, 4, 0, bucket_seed_config)
|
|
bucket_seeded_b = bucket_node.build("landscape", -1, 4, 0, bucket_seed_config)
|
|
_expect(bucket_seeded_a == bucket_seeded_b, "SDXL bucket should honor seed_config aliases deterministically")
|
|
_expect(
|
|
bucket_node._configured_bucket_seed({"camera_seed": "42"}) == 42,
|
|
"SDXL bucket seed should delegate through composition/camera seed aliases",
|
|
)
|
|
|
|
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",
|
|
"prompt": {"api": "prompt"},
|
|
"extra_pnginfo": {"workflow": {"nodes": [{"id": 1, "type": "SmokeNode"}]}},
|
|
},
|
|
{"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")
|
|
|
|
retake = server_routes.accumulator_retake_payload({"store_key": key, "preview_key": "first-key"})
|
|
_expect(
|
|
retake.get("workflow", {}).get("nodes", [{}])[0].get("type") == "SmokeNode",
|
|
"Accumulator retake payload lost workflow metadata",
|
|
)
|
|
_expect(retake.get("prompt", {}).get("api") == "prompt", "Accumulator retake payload lost prompt metadata")
|
|
|
|
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")
|
|
_expect(
|
|
pb.seed_reroll_axis_choices() == seed_config.seed_reroll_axis_choices(),
|
|
"seed reroll axis choices drifted from seed_config",
|
|
)
|
|
_expect(pb.normalize_seed_mode("follow main") == "follow_main", "seed mode normalizer should accept spaced labels")
|
|
_expect(pb.normalize_seed_mode("FOLLOW-MAIN") == "follow_main", "seed mode normalizer should accept hyphenated labels")
|
|
_expect(pb.normalize_reroll_axis("content pose") == "content_pose", "reroll axis normalizer should accept spaced labels")
|
|
reroll_choices = pb.seed_reroll_axis_choices()
|
|
for expected_axis in ("clothing", "content_clothing", "clothing_pose"):
|
|
_expect(expected_axis in reroll_choices, f"seed reroll axis choices missing {expected_axis}")
|
|
_expect(pb.normalize_reroll_axis("clothing pose") == "clothing_pose", "reroll axis normalizer should accept clothing pose")
|
|
_expect(pb.normalize_reroll_axis("content clothing") == "content_clothing", "reroll axis normalizer should accept content clothing")
|
|
|
|
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", "outfit_seed": "66", "bad": "nope"})
|
|
_expect(
|
|
parsed == {"item_seed": 44, "pose_seed": 55, "outfit_seed": 66},
|
|
"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, "clothing") == 66, "clothing axis should honor outfit_seed alias")
|
|
_expect(
|
|
pb._configured_axis_seed({"content_seed": 77}, "clothing") == 77,
|
|
"clothing axis should keep content_seed as a legacy fallback",
|
|
)
|
|
_expect(
|
|
pb._configured_axis_seed({"content_seed": 77, "clothing_seed": 88}, "clothing") == 88,
|
|
"clothing_seed should override legacy content_seed fallback",
|
|
)
|
|
_expect(pb._configured_axis_seed(parsed, "role") == 55, "role axis should honor pose seed alias")
|
|
_expect(
|
|
seed_config.configured_seed_from_axes({"camera_seed": "88", "content_seed": "99"}, ("composition", "content")) == 88,
|
|
"seed helper should honor axis aliases in precedence order",
|
|
)
|
|
_expect(
|
|
seed_config.configured_seed_from_axes('{"bad": "json"', ("content",)) is None,
|
|
"seed helper should return no seed for invalid config JSON",
|
|
)
|
|
|
|
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")
|
|
clothing_locked = json.loads(pb.build_seed_lock_config_json(base_seed=100, reroll_axis="clothing", reroll_seed=777))
|
|
_expect(clothing_locked["clothing_seed"] == 777, "clothing reroll should alter clothing seed")
|
|
_expect(clothing_locked["content_seed"] == 100, "clothing reroll should leave content locked")
|
|
_expect(clothing_locked["pose_seed"] == 100 and clothing_locked["role_seed"] == 100, "clothing reroll should leave pose and role locked")
|
|
|
|
content_clothing_locked = json.loads(
|
|
pb.build_seed_lock_config_json(base_seed=100, reroll_axis="content_clothing", reroll_seed=778)
|
|
)
|
|
_expect(content_clothing_locked["content_seed"] == 778, "content_clothing reroll should alter content seed")
|
|
_expect(content_clothing_locked["clothing_seed"] == 778, "content_clothing reroll should alter clothing seed")
|
|
_expect(content_clothing_locked["pose_seed"] == 100, "content_clothing reroll should leave pose locked")
|
|
|
|
clothing_pose_locked = json.loads(pb.build_seed_lock_config_json(base_seed=100, reroll_axis="clothing_pose", reroll_seed=779))
|
|
_expect(clothing_pose_locked["clothing_seed"] == 779, "clothing_pose reroll should alter clothing seed")
|
|
_expect(clothing_pose_locked["pose_seed"] == 779 and clothing_pose_locked["role_seed"] == 779, "clothing_pose reroll should alter pose and role seeds")
|
|
_expect(clothing_pose_locked["content_seed"] == 100, "clothing_pose reroll should leave content locked")
|
|
|
|
content_pose_locked = json.loads(pb.build_seed_lock_config_json(base_seed=100, reroll_axis="content_pose", reroll_seed=780))
|
|
_expect(content_pose_locked["clothing_seed"] == 100, "content_pose reroll should not alter clothing seed")
|
|
axis_trace = seed_config.axis_seed_trace({"content_seed": 44, "clothing_seed": 66}, 99, 3, axes=("content", "clothing", "scene"))
|
|
_expect(axis_trace["content"]["source"] == "configured", "Seed axis trace lost configured source")
|
|
_expect(axis_trace["content"]["seed"] == 44, "Seed axis trace lost configured seed")
|
|
_expect(axis_trace["clothing"]["source"] == "configured", "Seed axis trace lost clothing configured source")
|
|
_expect(axis_trace["clothing"]["seed"] == 66, "Seed axis trace lost configured clothing seed")
|
|
_expect(axis_trace["scene"]["source"] == "main", "Seed axis trace lost main source")
|
|
_expect(axis_trace["scene"]["seed"] == 99, "Seed axis trace lost main seed")
|
|
_expect(
|
|
axis_trace["content"]["rng_seed"] == seed_config.row_seed(44, 3, seed_config.SEED_AXIS_SALTS["content"]),
|
|
"Seed axis trace lost content RNG seed",
|
|
)
|
|
|
|
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")
|
|
|
|
deterministic_seed = 3901
|
|
deterministic_config = pb.build_seed_lock_config_json(base_seed=deterministic_seed)
|
|
deterministic_kwargs = {
|
|
"name": "seed_config_policy_deterministic",
|
|
"category": "Hardcore sexual poses",
|
|
"subcategory": "Penetrative sex",
|
|
"seed": deterministic_seed,
|
|
"seed_config": deterministic_config,
|
|
"character_cast": _character_cast(),
|
|
"hardcore_position_config": _action_filter("penetration_only"),
|
|
"location_config": _coworking_location_config(),
|
|
}
|
|
deterministic_a = _prompt_row(**deterministic_kwargs)
|
|
deterministic_b = _prompt_row(**deterministic_kwargs)
|
|
_expect(deterministic_a == deterministic_b, "locked seed config should reproduce identical prompt row output")
|
|
|
|
slot_descriptor_keys = ("age_band", "body_phrase", "skin", "hair", "eyes", "figure")
|
|
|
|
def slot_seed_cast(slot_seed: int) -> str:
|
|
return pb.build_character_slot_json(
|
|
subject_type="woman",
|
|
label="A",
|
|
slot_seed=slot_seed,
|
|
age="random",
|
|
ethnicity="random",
|
|
figure="random",
|
|
body="random",
|
|
hair_color="random",
|
|
hair_length="random",
|
|
hair_style="random",
|
|
descriptor_detail="full",
|
|
)["character_cast"]
|
|
|
|
def slot_seed_row(character_cast: str, reroll_seed: int) -> dict[str, Any]:
|
|
return _prompt_row(
|
|
name=f"seed_config_policy_slot_seed_{reroll_seed}",
|
|
category="Casual clothes",
|
|
subcategory="Casual clothes / Streetwear",
|
|
seed=41001,
|
|
seed_config=pb.build_seed_lock_config_json(
|
|
base_seed=41001,
|
|
reroll_axis="person",
|
|
reroll_seed=reroll_seed,
|
|
),
|
|
character_cast=character_cast,
|
|
women_count=1,
|
|
men_count=0,
|
|
)
|
|
|
|
seeded_slot_cast = slot_seed_cast(50123)
|
|
seeded_slot_a = slot_seed_row(seeded_slot_cast, 60001)
|
|
seeded_slot_b = slot_seed_row(seeded_slot_cast, 60002)
|
|
_expect(
|
|
tuple(seeded_slot_a.get(key) for key in slot_descriptor_keys)
|
|
== tuple(seeded_slot_b.get(key) for key in slot_descriptor_keys),
|
|
"slot_seed should keep random character-slot descriptors stable across person-axis rerolls",
|
|
)
|
|
seeded_trace_a = seeded_slot_a.get("generation_trace") if isinstance(seeded_slot_a.get("generation_trace"), dict) else {}
|
|
seeded_trace_b = seeded_slot_b.get("generation_trace") if isinstance(seeded_slot_b.get("generation_trace"), dict) else {}
|
|
_expect(
|
|
seeded_trace_a.get("seed_axes", {}).get("person", {}).get("seed")
|
|
!= seeded_trace_b.get("seed_axes", {}).get("person", {}).get("seed"),
|
|
"slot_seed regression check should actually reroll the person axis",
|
|
)
|
|
seeded_krea_a = krea_formatter.format_krea2_prompt("", metadata_json=_json(seeded_slot_a), target="single")
|
|
seeded_krea_b = krea_formatter.format_krea2_prompt("", metadata_json=_json(seeded_slot_b), target="single")
|
|
_expect(
|
|
seeded_krea_a.get("krea_prompt") == seeded_krea_b.get("krea_prompt"),
|
|
"slot_seed should keep final Krea prompt stable across person-axis rerolls",
|
|
)
|
|
|
|
unseeded_slot_cast = slot_seed_cast(-1)
|
|
unseeded_slot_a = slot_seed_row(unseeded_slot_cast, 60001)
|
|
unseeded_slot_b = slot_seed_row(unseeded_slot_cast, 60002)
|
|
_expect(
|
|
tuple(unseeded_slot_a.get(key) for key in slot_descriptor_keys)
|
|
!= tuple(unseeded_slot_b.get(key) for key in slot_descriptor_keys),
|
|
"unseeded random character slot should still follow person-axis rerolls",
|
|
)
|
|
|
|
pose_changed = False
|
|
for reroll_seed in range(deterministic_seed + 1, deterministic_seed + 10):
|
|
pose_reroll = _prompt_row(
|
|
name="seed_config_policy_pose_reroll",
|
|
category="Hardcore sexual poses",
|
|
subcategory="Penetrative sex",
|
|
seed=deterministic_seed,
|
|
seed_config=pb.build_seed_lock_config_json(
|
|
base_seed=deterministic_seed,
|
|
reroll_axis="pose",
|
|
reroll_seed=reroll_seed,
|
|
),
|
|
character_cast=_character_cast(),
|
|
hardcore_position_config=_action_filter("penetration_only"),
|
|
location_config=_coworking_location_config(),
|
|
)
|
|
_expect(
|
|
pose_reroll.get("cast_descriptor_text") == deterministic_a.get("cast_descriptor_text"),
|
|
"pose reroll should keep locked cast descriptors stable",
|
|
)
|
|
_expect(
|
|
pose_reroll.get("scene_text") == deterministic_a.get("scene_text"),
|
|
"pose reroll should keep locked scene stable",
|
|
)
|
|
if (
|
|
pose_reroll.get("position_key") != deterministic_a.get("position_key")
|
|
or pose_reroll.get("source_role_graph") != deterministic_a.get("source_role_graph")
|
|
or pose_reroll.get("item") != deterministic_a.get("item")
|
|
):
|
|
pose_changed = True
|
|
break
|
|
_expect(pose_changed, "pose reroll should change pose/action metadata while cast and scene stay locked")
|
|
|
|
|
|
def smoke_prompt_route_simulation_policy() -> None:
|
|
report = prompt_route_simulation.run_simulation(seed=3901, include_prompts=False)
|
|
summary = report.get("summary") or {}
|
|
quality = report.get("quality") or {}
|
|
_expect(summary.get("cases") == 16, "Prompt route simulation case count changed unexpectedly")
|
|
_expect(summary.get("coverage_checks") == 2, "Prompt route simulation lost family coverage checks")
|
|
_expect(summary.get("axis_checks") == 6, "Prompt route simulation lost axis check coverage")
|
|
_expect(summary.get("pair_seed_checks") == 7, "Prompt route simulation lost pair seed check coverage")
|
|
_expect(summary.get("issues") == 0, f"Prompt route simulation reported issues: {report.get('issues')}")
|
|
_expect(quality.get("route_cases") == 16, "Prompt route simulation quality summary lost route case count")
|
|
_expect(quality.get("route_issues") == 0, f"Prompt route simulation quality reported route issues: {quality}")
|
|
_expect(quality.get("check_issues") == 0, f"Prompt route simulation quality reported check issues: {quality}")
|
|
_expect((quality.get("targets") or {}).get("single", {}).get("cases") == 10, "Prompt route simulation quality lost single target count")
|
|
_expect((quality.get("targets") or {}).get("softcore", {}).get("cases") == 3, "Prompt route simulation quality lost softcore target count")
|
|
_expect((quality.get("targets") or {}).get("hardcore", {}).get("cases") == 3, "Prompt route simulation quality lost hardcore target count")
|
|
_expect(not quality.get("issue_buckets"), "Prompt route simulation quality should have no issue buckets on clean baseline")
|
|
_expect(not quality.get("weakest_cases"), "Prompt route simulation quality should have no weak cases on clean baseline")
|
|
cases = {case.get("name"): case for case in report.get("cases") or []}
|
|
for route_name in (
|
|
"hardcore.single.oral",
|
|
"hardcore.single.manual",
|
|
"hardcore.single.outercourse",
|
|
"hardcore.single.foreplay",
|
|
"hardcore.single.interaction",
|
|
"hardcore.single.anal",
|
|
"hardcore.single.threesome",
|
|
"hardcore.single.group",
|
|
"hardcore.single.climax",
|
|
"insta_pair.penetration.hardcore",
|
|
"insta_pair.pov_ballsucking.hardcore",
|
|
):
|
|
_expect(route_name in cases, f"Prompt route simulation lost route family case {route_name}")
|
|
coverage_checks = {check.get("name"): check for check in report.get("coverage_checks") or []}
|
|
action_coverage = coverage_checks.get("route_coverage.action_families") or {}
|
|
position_coverage = coverage_checks.get("route_coverage.position_families") or {}
|
|
_expect(not action_coverage.get("issues"), f"Prompt route simulation action coverage failed: {action_coverage}")
|
|
_expect(not position_coverage.get("issues"), f"Prompt route simulation position coverage failed: {position_coverage}")
|
|
expected_actions = (
|
|
set(hardcore_action_metadata.HARDCORE_ACTION_FAMILY_CHOICES)
|
|
- prompt_route_simulation.ROUTE_SIM_ACTION_FAMILY_EXCLUSIONS
|
|
)
|
|
expected_positions = (
|
|
set(hardcore_position_config.hardcore_position_family_choices())
|
|
- prompt_route_simulation.ROUTE_SIM_POSITION_FAMILY_EXCLUSIONS
|
|
)
|
|
_expect(set(action_coverage.get("observed") or []) == expected_actions, "Prompt route simulation action coverage drifted")
|
|
_expect(set(position_coverage.get("observed") or []) == expected_positions, "Prompt route simulation position coverage drifted")
|
|
pov_hard = cases.get("insta_pair.pov_outercourse.hardcore") or {}
|
|
pov_summary = pov_hard.get("summary") or {}
|
|
_expect(
|
|
pov_summary.get("position_key") == "penis_licking",
|
|
"Prompt route simulation should catch selected outercourse position as primary position_key",
|
|
)
|
|
_expect(
|
|
"penis_licking" in (pov_summary.get("position_keys") or []),
|
|
"Prompt route simulation lost selected outercourse key from position_keys",
|
|
)
|
|
ballsucking_hard = cases.get("insta_pair.pov_ballsucking.hardcore") or {}
|
|
ballsucking_summary = ballsucking_hard.get("summary") or {}
|
|
_expect(
|
|
ballsucking_summary.get("position_key") == "testicle_sucking",
|
|
"Prompt route simulation should include a dedicated ballsucking/testicle POV route",
|
|
)
|
|
_expect(
|
|
"testicle_sucking" in (ballsucking_summary.get("position_keys") or []),
|
|
"Prompt route simulation ballsucking route lost selected testicle_sucking key",
|
|
)
|
|
axis_checks = {check.get("name"): check for check in report.get("axis_checks") or []}
|
|
for check_name in (
|
|
"seed_axis.locked_determinism",
|
|
"seed_axis.person_reroll",
|
|
"seed_axis.scene_reroll",
|
|
"seed_axis.pose_reroll",
|
|
"seed_axis.expression_reroll",
|
|
"seed_axis.composition_reroll",
|
|
):
|
|
check = axis_checks.get(check_name) or {}
|
|
_expect(check, f"Prompt route simulation lost seed-axis check {check_name}")
|
|
_expect(not check.get("issues"), f"Prompt route simulation seed-axis check reported issues: {check_name}")
|
|
_expect(axis_checks["seed_axis.locked_determinism"].get("changed") is False, "Locked determinism check should not be a reroll")
|
|
for check_name in (
|
|
"seed_axis.person_reroll",
|
|
"seed_axis.scene_reroll",
|
|
"seed_axis.pose_reroll",
|
|
"seed_axis.expression_reroll",
|
|
"seed_axis.composition_reroll",
|
|
):
|
|
_expect(axis_checks[check_name].get("changed") is True, f"{check_name} should prove its axis can reroll")
|
|
pair_seed_checks = {check.get("name"): check for check in report.get("pair_seed_checks") or []}
|
|
for check_name in (
|
|
"pair_seed.locked_determinism",
|
|
"pair_seed.person_reroll",
|
|
"pair_seed.scene_reroll",
|
|
"pair_seed.content_reroll",
|
|
"pair_seed.pose_reroll",
|
|
"pair_seed.expression_reroll",
|
|
"pair_seed.composition_reroll",
|
|
):
|
|
check = pair_seed_checks.get(check_name) or {}
|
|
_expect(check, f"Prompt route simulation lost pair seed check {check_name}")
|
|
_expect(not check.get("issues"), f"Prompt route simulation pair seed check reported issues: {check_name}")
|
|
_expect(
|
|
pair_seed_checks["pair_seed.locked_determinism"].get("changed") is False,
|
|
"Pair locked determinism check should not be a reroll",
|
|
)
|
|
for check_name in (
|
|
"pair_seed.person_reroll",
|
|
"pair_seed.scene_reroll",
|
|
"pair_seed.content_reroll",
|
|
"pair_seed.pose_reroll",
|
|
"pair_seed.expression_reroll",
|
|
"pair_seed.composition_reroll",
|
|
):
|
|
_expect(pair_seed_checks[check_name].get("changed") is True, f"{check_name} should prove its axis can reroll")
|
|
_expect(
|
|
pair_seed_checks["pair_seed.content_reroll"].get("changed") is True,
|
|
"Pair content reroll should prove soft outfit/content can reroll while hard action stays locked",
|
|
)
|
|
_expect(
|
|
pair_seed_checks["pair_seed.pose_reroll"].get("changed") is True,
|
|
"Pair pose reroll should prove soft pose or hard action can reroll while cast/scene axes stay locked",
|
|
)
|
|
climax_regression = prompt_route_simulation.run_simulation(seed=42052, include_prompts=False)
|
|
_expect(
|
|
climax_regression.get("summary", {}).get("issues") == 0,
|
|
f"Prompt route simulation climax regression reported issues: {climax_regression.get('issues')}",
|
|
)
|
|
sweep = prompt_route_simulation.run_simulation_sweep(seed=3901, count=3, seed_step=101, include_prompts=False)
|
|
sweep_summary = sweep.get("summary") or {}
|
|
sweep_quality = sweep.get("quality") or {}
|
|
_expect(sweep_summary.get("runs") == 3, "Prompt route simulation sweep lost run coverage")
|
|
_expect(sweep_summary.get("seeds") == [3901, 4002, 4103], "Prompt route simulation sweep seed sequence changed")
|
|
_expect(sweep_summary.get("cases") == 48, "Prompt route simulation sweep case count changed")
|
|
_expect(sweep_summary.get("issues") == 0, f"Prompt route simulation sweep reported issues: {sweep.get('issues')}")
|
|
_expect(sweep_quality.get("route_cases") == 48, "Prompt route simulation sweep quality lost route case count")
|
|
_expect(sweep_quality.get("route_issues") == 0, f"Prompt route simulation sweep quality reported route issues: {sweep_quality}")
|
|
_expect(sweep_quality.get("check_issues") == 0, f"Prompt route simulation sweep quality reported check issues: {sweep_quality}")
|
|
_expect((sweep_quality.get("targets") or {}).get("single", {}).get("cases") == 30, "Prompt route simulation sweep quality lost single target count")
|
|
_expect((sweep_quality.get("targets") or {}).get("softcore", {}).get("cases") == 9, "Prompt route simulation sweep quality lost softcore target count")
|
|
_expect((sweep_quality.get("targets") or {}).get("hardcore", {}).get("cases") == 9, "Prompt route simulation sweep quality lost hardcore target count")
|
|
|
|
|
|
def smoke_sxcp_mcp_client_cli_policy() -> None:
|
|
helper_path = ROOT / "tools" / "sxcp_mcp_client.py"
|
|
eval_loop_doc = (ROOT / "docs" / "sxcp-eval-loop.md").read_text(encoding="utf-8")
|
|
_expect("## MCP Helper Command" in eval_loop_doc, "SxCP eval loop doc lost MCP helper command memory")
|
|
_expect(
|
|
"/media/p5/miniforge3/bin/python tools/sxcp_mcp_client.py call-tool comfy_push" in eval_loop_doc,
|
|
"SxCP eval loop doc lost approved MCP push helper command",
|
|
)
|
|
_expect("sxcp_eval_out" in eval_loop_doc, "SxCP eval loop doc lost output channel name")
|
|
top_help = subprocess.run(
|
|
[sys.executable, str(helper_path), "--help"],
|
|
cwd=ROOT,
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
_expect(top_help.returncode == 0, f"sxcp MCP client --help failed: {top_help.stderr}")
|
|
help_text = top_help.stdout
|
|
_expect("list-tools" in help_text, "sxcp MCP client help lost list-tools command")
|
|
_expect("call-tool" in help_text, "sxcp MCP client help lost call-tool command")
|
|
_expect("--bridge-url" in help_text, "sxcp MCP client help lost bridge URL option")
|
|
call_help = subprocess.run(
|
|
[sys.executable, str(helper_path), "call-tool", "--help"],
|
|
cwd=ROOT,
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
_expect(call_help.returncode == 0, f"sxcp MCP client call-tool --help failed: {call_help.stderr}")
|
|
_expect("--arguments-json" in call_help.stdout, "sxcp MCP client help lost JSON argument option")
|
|
|
|
|
|
def smoke_watch_prompt_image_folder_cli_policy() -> None:
|
|
helper_path = ROOT / "tools" / "watch_prompt_image_folder.sh"
|
|
_expect(helper_path.exists(), "watch prompt/image folder helper is missing")
|
|
_expect(helper_path.stat().st_mode & 0o111, "watch prompt/image folder helper must be executable")
|
|
|
|
help_result = subprocess.run(
|
|
[str(helper_path), "--help"],
|
|
cwd=ROOT,
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
_expect(help_result.returncode == 0, f"watch helper --help failed: {help_result.stderr}")
|
|
for term in ("--folder", "--target", "--notes", "--once", "--dry-run", "tmux send-keys"):
|
|
_expect(term in help_result.stdout, f"watch helper help lost {term!r}")
|
|
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
folder = Path(temp_dir)
|
|
prompt_path = folder / "atlas_case_001.txt"
|
|
image_path = folder / "atlas_case_001.png"
|
|
state_path = folder / "seen.state"
|
|
notes_path = folder / "prompt-learning.md"
|
|
prompt_path.write_text("nadir-angle standing male POV test prompt\n", encoding="utf-8")
|
|
image_path.write_bytes(b"fake-png")
|
|
|
|
first_scan = subprocess.run(
|
|
[
|
|
str(helper_path),
|
|
"--folder",
|
|
str(folder),
|
|
"--target",
|
|
"codex:1.0",
|
|
"--notes",
|
|
str(notes_path),
|
|
"--state",
|
|
str(state_path),
|
|
"--once",
|
|
"--dry-run",
|
|
],
|
|
cwd=ROOT,
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
_expect(first_scan.returncode == 0, f"watch helper dry-run scan failed: {first_scan.stderr}")
|
|
_expect("tmux send-keys -t codex:1.0" in first_scan.stdout, "watch helper dry-run did not render tmux target")
|
|
_expect(str(prompt_path) in first_scan.stdout, "watch helper notification lost prompt path")
|
|
_expect(str(image_path) in first_scan.stdout, "watch helper notification lost image path")
|
|
_expect(str(notes_path) in first_scan.stdout, "watch helper notification lost notes path")
|
|
_expect("atlas_case_001.txt" in state_path.read_text(encoding="utf-8"), "watch helper did not record notified prompt")
|
|
|
|
second_scan = subprocess.run(
|
|
[
|
|
str(helper_path),
|
|
"--folder",
|
|
str(folder),
|
|
"--target",
|
|
"codex:1.0",
|
|
"--notes",
|
|
str(notes_path),
|
|
"--state",
|
|
str(state_path),
|
|
"--once",
|
|
"--dry-run",
|
|
],
|
|
cwd=ROOT,
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
_expect(second_scan.returncode == 0, f"watch helper repeated dry-run scan failed: {second_scan.stderr}")
|
|
_expect("no new prompt/image pairs" in second_scan.stdout, "watch helper should dedupe already-notified pairs")
|
|
|
|
jpeg_prompt = folder / "atlas_case_002.prompt"
|
|
jpeg_image = folder / "atlas_case_002.jpg"
|
|
jpeg_prompt.write_text("side-profile oral prompt\n", encoding="utf-8")
|
|
jpeg_image.write_bytes(b"fake-jpg")
|
|
third_scan = subprocess.run(
|
|
[
|
|
str(helper_path),
|
|
"--folder",
|
|
str(folder),
|
|
"--target",
|
|
"codex:1.0",
|
|
"--notes",
|
|
str(notes_path),
|
|
"--state",
|
|
str(state_path),
|
|
"--once",
|
|
"--dry-run",
|
|
],
|
|
cwd=ROOT,
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
_expect(third_scan.returncode == 0, f"watch helper jpeg dry-run scan failed: {third_scan.stderr}")
|
|
_expect(str(jpeg_prompt) in third_scan.stdout and str(jpeg_image) in third_scan.stdout, "watch helper lost prompt/JPEG pairing")
|
|
|
|
|
|
def smoke_sxcp_prompt_batch_cli_policy() -> None:
|
|
helper_path = ROOT / "tools" / "sxcp_prompt_batch.py"
|
|
eval_loop_doc = (ROOT / "docs" / "sxcp-eval-loop.md").read_text(encoding="utf-8")
|
|
methodology = (ROOT / "docs" / "krea2-ab-methodology.md").read_text(encoding="utf-8")
|
|
_expect("tools/sxcp_prompt_batch.py" in eval_loop_doc, "SxCP eval loop doc lost prompt batch helper")
|
|
_expect("print-push-commands" in eval_loop_doc, "SxCP eval loop doc lost prompt batch command rendering")
|
|
_expect("print-result-template" in eval_loop_doc, "SxCP eval loop doc lost prompt batch result template")
|
|
_expect("run-batch" in eval_loop_doc, "SxCP eval loop doc lost prompt batch runner")
|
|
_expect("validate-results" in eval_loop_doc, "SxCP eval loop doc lost prompt batch result validation")
|
|
_expect("print-eval-entry-draft" in eval_loop_doc, "SxCP eval loop doc lost prompt batch eval-entry draft")
|
|
_expect("--allow-geometry-only" in eval_loop_doc, "SxCP eval loop doc lost geometry-only draft guard")
|
|
_expect("tools/sxcp_prompt_batch.py" in methodology, "Krea2 A/B methodology lost prompt batch helper memory")
|
|
_expect("run-batch" in methodology, "Krea2 A/B methodology lost prompt batch runner")
|
|
_expect("validate-results" in methodology, "Krea2 A/B methodology lost prompt batch result validation")
|
|
_expect("print-eval-entry-draft" in methodology, "Krea2 A/B methodology lost prompt batch eval-entry draft memory")
|
|
_expect("--allow-geometry-only" in methodology, "Krea2 A/B methodology lost geometry-only draft guard")
|
|
batch = {
|
|
"seed": 123456789,
|
|
"channel_out": "sxcp_eval_out",
|
|
"channel_in": "sxcp_eval_in",
|
|
"subject_id": "same_woman_001",
|
|
"variant_key": "pov_footjob_frontal_sole_stroke",
|
|
"source_entry_id": "pov_footjob_frontal_sole_stroke_00001",
|
|
"source_stem": "pov_footjob_frontal_sole_stroke_00001_",
|
|
"selection": {"selection_seed": 202, "seed_slot": "atlas_cue_seed"},
|
|
"probes": [
|
|
{
|
|
"id": "subject_first_axis",
|
|
"prompt_order": "subject_first",
|
|
"text": "A 24-year-old adult woman with long wavy brunette hair. Subject-first controlled pose wording.",
|
|
"variant_key": "pov_footjob_frontal_sole_stroke",
|
|
"source_entry_id": "pov_footjob_frontal_sole_stroke_00001",
|
|
"source_stem": "pov_footjob_frontal_sole_stroke_00001_",
|
|
"cue_axes": {"foot_position": "soles_more_forward"},
|
|
"seed_metadata": {"atlas_cue_seed": 202},
|
|
"evidence": {"seed": 123456789, "image_path": "/tmp/subject_first_axis.png"},
|
|
"matrix_evidence": {
|
|
"stable": True,
|
|
"selection_seed": 202,
|
|
"sampler_seeds": [123456789],
|
|
"jobs": [{"id": "job_1"}],
|
|
},
|
|
"selection": {"selection_seed": 202, "seed_slot": "atlas_cue_seed"},
|
|
"prompt_source": {"kind": "append_cues", "append_cues": ["soles farther forward"]},
|
|
"notes": "metadata should survive helper loading",
|
|
},
|
|
{
|
|
"id": "geometry_only_axis",
|
|
"prompt_order": "geometry_only",
|
|
"text": "Geometry-only floor-plane wording for rough pose-axis discovery.",
|
|
},
|
|
],
|
|
}
|
|
with tempfile.NamedTemporaryFile("w", encoding="utf-8", suffix=".json", delete=False) as handle:
|
|
json.dump(batch, handle)
|
|
batch_path = Path(handle.name)
|
|
try:
|
|
loaded_batch = sxcp_prompt_batch.load_batch(batch_path)
|
|
loaded_probe = loaded_batch.get("probes", [{}])[0]
|
|
_expect(
|
|
loaded_batch.get("variant_key") == "pov_footjob_frontal_sole_stroke"
|
|
and loaded_batch.get("selection", {}).get("selection_seed") == 202,
|
|
f"sxcp prompt batch loader should preserve atlas batch metadata: {loaded_batch}",
|
|
)
|
|
_expect(
|
|
loaded_probe.get("matrix_evidence", {}).get("stable") is True
|
|
and loaded_probe.get("cue_axes", {}).get("foot_position") == "soles_more_forward"
|
|
and loaded_probe.get("seed_metadata", {}).get("atlas_cue_seed") == 202,
|
|
f"sxcp prompt batch loader should preserve atlas probe metadata: {loaded_probe}",
|
|
)
|
|
commands = subprocess.run(
|
|
[sys.executable, str(helper_path), "print-push-commands", "--batch-json", str(batch_path)],
|
|
cwd=ROOT,
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
_expect(commands.returncode == 0, f"sxcp prompt batch command rendering failed: {commands.stderr}")
|
|
_expect("tools/sxcp_mcp_client.py call-tool comfy_push" in commands.stdout, "sxcp prompt batch lost MCP push helper command")
|
|
_expect('"channel":"sxcp_eval_out"' in commands.stdout, "sxcp prompt batch lost positive output channel")
|
|
_expect('"seed":123456789' in commands.stdout, "sxcp prompt batch lost fixed sampler seed")
|
|
_expect("subject_first_axis" in commands.stdout and "geometry_only_axis" in commands.stdout, "sxcp prompt batch lost probe ids")
|
|
_expect("geometry-only" in commands.stdout, "sxcp prompt batch lost geometry-only caveat")
|
|
_expect("sxcp_eval_negative_out" not in commands.stdout, "sxcp prompt batch should not emit negative output channel")
|
|
|
|
template = subprocess.run(
|
|
[sys.executable, str(helper_path), "print-result-template", "--batch-json", str(batch_path)],
|
|
cwd=ROOT,
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
_expect(template.returncode == 0, f"sxcp prompt batch result template failed: {template.stderr}")
|
|
result_template = json.loads(template.stdout)
|
|
_expect(result_template.get("seed") == 123456789, "sxcp prompt batch result template lost seed")
|
|
_expect(result_template.get("channel_in") == "sxcp_eval_in", "sxcp prompt batch result template lost input channel")
|
|
probes = result_template.get("probes") or []
|
|
_expect([probe.get("id") for probe in probes] == ["subject_first_axis", "geometry_only_axis"], "sxcp prompt batch result template lost probe order")
|
|
_expect(all("image_path" in probe and "turn" in probe for probe in probes), "sxcp prompt batch result template lost image presence fields")
|
|
|
|
result_template["probes"][0].update(
|
|
{
|
|
"turn": 101,
|
|
"image_path": "/media/unraid/comfyui/output/agent_bridge/img_subject_first.png",
|
|
"returned_seed": 123456789,
|
|
}
|
|
)
|
|
result_template["probes"][1].update(
|
|
{
|
|
"turn": 102,
|
|
"image_path": "/media/unraid/comfyui/output/agent_bridge/img_geometry_only.png",
|
|
"returned_seed": 123456789,
|
|
}
|
|
)
|
|
with tempfile.NamedTemporaryFile("w", encoding="utf-8", suffix=".json", delete=False) as result_handle:
|
|
json.dump(result_template, result_handle)
|
|
result_path = Path(result_handle.name)
|
|
try:
|
|
validated_results = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
str(helper_path),
|
|
"validate-results",
|
|
"--batch-json",
|
|
str(batch_path),
|
|
"--result-json",
|
|
str(result_path),
|
|
],
|
|
cwd=ROOT,
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
_expect(validated_results.returncode == 0, f"sxcp prompt batch result validation failed: {validated_results.stderr}")
|
|
_expect("validated results: 2 probes, seed 123456789" in validated_results.stdout, "sxcp prompt batch result validation lost summary")
|
|
|
|
draft = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
str(helper_path),
|
|
"print-eval-entry-draft",
|
|
"--batch-json",
|
|
str(batch_path),
|
|
"--result-json",
|
|
str(result_path),
|
|
"--variant-key",
|
|
"pov_missionary_open_leg_penetration",
|
|
"--entry-id",
|
|
"missionary-open-123456789-subject-first-axis",
|
|
"--baseline-image",
|
|
"/media/unraid/comfyui/output/agent_bridge/img_baseline.png",
|
|
"--candidate-id",
|
|
"subject_first_axis",
|
|
"--source",
|
|
"sxcp_eval_mcp_batch_smoke",
|
|
],
|
|
cwd=ROOT,
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
_expect(draft.returncode == 0, f"sxcp prompt batch eval-entry draft failed: {draft.stderr}")
|
|
entry = json.loads(draft.stdout)
|
|
_expect(entry.get("id") == "missionary-open-123456789-subject-first-axis", "sxcp prompt batch eval draft lost entry id")
|
|
_expect(entry.get("variant_key") == "pov_missionary_open_leg_penetration", "sxcp prompt batch eval draft lost variant key")
|
|
_expect(entry.get("seed") == 123456789, "sxcp prompt batch eval draft lost seed")
|
|
_expect(entry.get("baseline_image") == "/media/unraid/comfyui/output/agent_bridge/img_baseline.png", "sxcp prompt batch eval draft lost baseline image")
|
|
_expect(entry.get("candidate_image") == "/media/unraid/comfyui/output/agent_bridge/img_subject_first.png", "sxcp prompt batch eval draft lost selected candidate image")
|
|
_expect(entry.get("result") == "inconclusive" and entry.get("decision") == "needs_more_tests", "sxcp prompt batch eval draft should default to inconclusive needs_more_tests")
|
|
_expect("subject_first_axis" in entry.get("candidate_prompt_summary", ""), "sxcp prompt batch eval draft lost candidate probe id")
|
|
_expect("subject_first" in entry.get("observation", ""), "sxcp prompt batch eval draft lost prompt-order note")
|
|
errors = krea2_eval_log.validate_entry(entry, catalog_keys=set(krea2_pose_variant_catalog.variant_keys()))
|
|
_expect(errors == [], f"sxcp prompt batch eval draft should validate: {errors}")
|
|
|
|
geometry_draft = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
str(helper_path),
|
|
"print-eval-entry-draft",
|
|
"--batch-json",
|
|
str(batch_path),
|
|
"--result-json",
|
|
str(result_path),
|
|
"--variant-key",
|
|
"pov_missionary_open_leg_penetration",
|
|
"--baseline-image",
|
|
"/media/unraid/comfyui/output/agent_bridge/img_baseline.png",
|
|
"--candidate-id",
|
|
"geometry_only_axis",
|
|
],
|
|
cwd=ROOT,
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
_expect(geometry_draft.returncode != 0, "sxcp prompt batch eval draft should reject geometry-only candidates by default")
|
|
_expect("geometry_only" in geometry_draft.stderr, "sxcp prompt batch geometry-only rejection should name prompt order")
|
|
|
|
allowed_geometry_draft = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
str(helper_path),
|
|
"print-eval-entry-draft",
|
|
"--batch-json",
|
|
str(batch_path),
|
|
"--result-json",
|
|
str(result_path),
|
|
"--variant-key",
|
|
"pov_missionary_open_leg_penetration",
|
|
"--baseline-image",
|
|
"/media/unraid/comfyui/output/agent_bridge/img_baseline.png",
|
|
"--candidate-id",
|
|
"geometry_only_axis",
|
|
"--allow-geometry-only",
|
|
],
|
|
cwd=ROOT,
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
_expect(allowed_geometry_draft.returncode == 0, f"sxcp prompt batch should allow explicit geometry-only drafts: {allowed_geometry_draft.stderr}")
|
|
allowed_entry = json.loads(allowed_geometry_draft.stdout)
|
|
_expect("not treat as subject/look-controlled evidence" in allowed_entry.get("observation", ""), "sxcp prompt batch allowed geometry draft lost caveat")
|
|
|
|
mock_pulls = [
|
|
{
|
|
"turn": 201,
|
|
"image_path": "/media/unraid/comfyui/output/agent_bridge/img_subject_first_run.png",
|
|
"seed": 123456789,
|
|
},
|
|
{
|
|
"turn": 201,
|
|
"image_path": "/media/unraid/comfyui/output/agent_bridge/img_stale_run.png",
|
|
"seed": 123456789,
|
|
},
|
|
{
|
|
"turn": 202,
|
|
"image_path": "/media/unraid/comfyui/output/agent_bridge/img_geometry_run.png",
|
|
"seed": 123456789,
|
|
},
|
|
]
|
|
with tempfile.NamedTemporaryFile("w", encoding="utf-8", suffix=".json", delete=False) as mock_handle:
|
|
json.dump(mock_pulls, mock_handle)
|
|
mock_path = Path(mock_handle.name)
|
|
with tempfile.NamedTemporaryFile("w", encoding="utf-8", suffix=".json", delete=False) as run_result_handle:
|
|
run_result_path = Path(run_result_handle.name)
|
|
run_result_path.unlink(missing_ok=True)
|
|
try:
|
|
runner = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
str(helper_path),
|
|
"run-batch",
|
|
"--batch-json",
|
|
str(batch_path),
|
|
"--result-json",
|
|
str(run_result_path),
|
|
"--mock-pulls-json",
|
|
str(mock_path),
|
|
"--previous-turn",
|
|
"200",
|
|
"--max-polls",
|
|
"3",
|
|
],
|
|
cwd=ROOT,
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
_expect(runner.returncode == 0, f"sxcp prompt batch runner failed: {runner.stderr}")
|
|
_expect("recorded results: 2 probes, seed 123456789" in runner.stdout, "sxcp prompt batch runner lost result summary")
|
|
run_results = json.loads(run_result_path.read_text(encoding="utf-8"))
|
|
_expect(
|
|
[probe.get("turn") for probe in run_results.get("probes") or []] == [201, 202],
|
|
"sxcp prompt batch runner should skip stale turns and preserve batch order",
|
|
)
|
|
_expect(
|
|
[probe.get("image_path") for probe in run_results.get("probes") or []]
|
|
== [
|
|
"/media/unraid/comfyui/output/agent_bridge/img_subject_first_run.png",
|
|
"/media/unraid/comfyui/output/agent_bridge/img_geometry_run.png",
|
|
],
|
|
"sxcp prompt batch runner lost selected image paths",
|
|
)
|
|
validate_runner_results = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
str(helper_path),
|
|
"validate-results",
|
|
"--batch-json",
|
|
str(batch_path),
|
|
"--result-json",
|
|
str(run_result_path),
|
|
],
|
|
cwd=ROOT,
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
_expect(validate_runner_results.returncode == 0, f"sxcp prompt batch runner wrote invalid results: {validate_runner_results.stderr}")
|
|
finally:
|
|
mock_path.unlink(missing_ok=True)
|
|
run_result_path.unlink(missing_ok=True)
|
|
|
|
bad_result = json.loads(json.dumps(result_template))
|
|
bad_result["probes"][1]["returned_seed"] = 987654321
|
|
with tempfile.NamedTemporaryFile("w", encoding="utf-8", suffix=".json", delete=False) as bad_result_handle:
|
|
json.dump(bad_result, bad_result_handle)
|
|
bad_result_path = Path(bad_result_handle.name)
|
|
try:
|
|
bad_validated_results = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
str(helper_path),
|
|
"validate-results",
|
|
"--batch-json",
|
|
str(batch_path),
|
|
"--result-json",
|
|
str(bad_result_path),
|
|
],
|
|
cwd=ROOT,
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
_expect(bad_validated_results.returncode != 0, "sxcp prompt batch result validation should reject returned seed mismatch")
|
|
_expect("returned_seed" in bad_validated_results.stderr, "sxcp prompt batch result validation should name returned_seed mismatch")
|
|
finally:
|
|
bad_result_path.unlink(missing_ok=True)
|
|
|
|
bad_result = json.loads(json.dumps(result_template))
|
|
bad_result["probes"][1]["id"] = "unexpected_axis"
|
|
with tempfile.NamedTemporaryFile("w", encoding="utf-8", suffix=".json", delete=False) as bad_result_handle:
|
|
json.dump(bad_result, bad_result_handle)
|
|
bad_result_path = Path(bad_result_handle.name)
|
|
try:
|
|
bad_validated_results = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
str(helper_path),
|
|
"validate-results",
|
|
"--batch-json",
|
|
str(batch_path),
|
|
"--result-json",
|
|
str(bad_result_path),
|
|
],
|
|
cwd=ROOT,
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
_expect(bad_validated_results.returncode != 0, "sxcp prompt batch result validation should reject probe order mismatch")
|
|
_expect("probe ids" in bad_validated_results.stderr, "sxcp prompt batch result validation should name probe ids mismatch")
|
|
finally:
|
|
bad_result_path.unlink(missing_ok=True)
|
|
finally:
|
|
result_path.unlink(missing_ok=True)
|
|
finally:
|
|
batch_path.unlink(missing_ok=True)
|
|
|
|
bad_batch = dict(batch)
|
|
bad_batch["channel_out"] = "sxcp_eval_negative_out"
|
|
with tempfile.NamedTemporaryFile("w", encoding="utf-8", suffix=".json", delete=False) as handle:
|
|
json.dump(bad_batch, handle)
|
|
bad_batch_path = Path(handle.name)
|
|
try:
|
|
bad = subprocess.run(
|
|
[sys.executable, str(helper_path), "validate", "--batch-json", str(bad_batch_path)],
|
|
cwd=ROOT,
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
_expect(bad.returncode != 0, "sxcp prompt batch should reject negative output channel")
|
|
_expect("sxcp_eval_negative_out" in bad.stderr, "sxcp prompt batch rejection should name the negative channel")
|
|
finally:
|
|
bad_batch_path.unlink(missing_ok=True)
|
|
|
|
|
|
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",
|
|
"SxCPStylePool",
|
|
"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")
|
|
|
|
style_node = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPStylePool"]
|
|
style_inputs = style_node.INPUT_TYPES().get("required") or {}
|
|
_expect("preset" in style_inputs, "Style Pool lost preset input")
|
|
_expect("tooltip" in style_inputs["preset"][1], "Style Pool tooltip injection missing")
|
|
style_config, style_summary = style_node().build(
|
|
True,
|
|
"replace",
|
|
"comic_pinup_colored_pencil",
|
|
"",
|
|
"",
|
|
"",
|
|
)
|
|
parsed_style = json.loads(style_config)
|
|
_expect(parsed_style.get("schema") == "sxcp_style_config_v1", "Style Pool emitted wrong schema")
|
|
_expect("comic pin-up" in parsed_style.get("style", ""), "Style Pool lost comic preset style")
|
|
_expect("comic pin-up" in style_summary, "Style Pool summary lost preset label")
|
|
|
|
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")
|
|
bias_seed_config = json.dumps({"category": 702})
|
|
seeded_bias_a = cast_bias.build(-1, 2, "0.7,0.3", 1, "0.4,0.6", 0, "force_one_woman", bias_seed_config)
|
|
seeded_bias_b = cast_bias.build(-1, 2, "0.7,0.3", 1, "0.4,0.6", 0, "force_one_woman", bias_seed_config)
|
|
_expect(seeded_bias_a == seeded_bias_b, "Cast Bias should honor seed_config aliases deterministically")
|
|
_expect(cast_bias._configured_cast_seed({"category": "702"}) == 702, "Cast Bias seed should delegate through category aliases")
|
|
|
|
|
|
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_input_types = woman_slot_node.INPUT_TYPES()
|
|
woman_slot_inputs = woman_slot_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")
|
|
woman_slot_optional = woman_slot_input_types.get("optional") or {}
|
|
cast_tooltip = woman_slot_optional.get("character_cast", (None, {}))[1].get("tooltip", "")
|
|
_expect("incoming cast from the previous slot" in cast_tooltip, "Woman Slot character_cast tooltip should explain chain order")
|
|
man_slot_required = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPManSlot"].INPUT_TYPES().get("required") or {}
|
|
presence_tooltip = man_slot_required.get("presence_mode", (None, {}))[1].get("tooltip", "")
|
|
_expect("first-person viewer" in presence_tooltip, "Man Slot presence tooltip should explain POV behavior")
|
|
|
|
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",
|
|
"SxCPKrea2PoseVariant",
|
|
"SxCPKrea2POVPenetrationFilter",
|
|
"SxCPKrea2POVOralFilter",
|
|
"SxCPKrea2POVOutercourseFilter",
|
|
"SxCPKrea2POVManualFilter",
|
|
"SxCPKrea2POVToyFilter",
|
|
"SxCPKrea2POVClimaxFilter",
|
|
"SxCPKrea2POVInteractionFilter",
|
|
"SxCPKrea2POVPromptRestore",
|
|
"SxCPKrea2VariantEvidence",
|
|
]
|
|
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")
|
|
|
|
variant_node = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPKrea2PoseVariant"]
|
|
variant_inputs = variant_node.INPUT_TYPES().get("required") or {}
|
|
_expect("variant_key" in variant_inputs, "Krea2 Pose Variant lost variant selector")
|
|
_expect("tooltip" in variant_inputs["variant_key"][1], "Krea2 Pose Variant tooltip injection missing")
|
|
_expect("pov_boobjob_upright_cleavage" in variant_inputs["variant_key"][0], "Krea2 Pose Variant lost boobjob option")
|
|
variant_config, variant_key, prompt_cues, avoid_cues, variant_summary, variant_json = variant_node().build(
|
|
"pov_boobjob_upright_cleavage",
|
|
"replace",
|
|
"",
|
|
)
|
|
parsed_variant_config = json.loads(variant_config)
|
|
parsed_variant = json.loads(variant_json)
|
|
_expect(variant_key == "pov_boobjob_upright_cleavage", "Krea2 Pose Variant returned wrong key")
|
|
_expect(parsed_variant_config.get("positions") == ["boobjob"], "Krea2 Pose Variant did not map to boobjob position config")
|
|
_expect(parsed_variant.get("status") == "proven", "Krea2 Pose Variant lost status metadata")
|
|
_expect("pressed-together breasts" in prompt_cues, "Krea2 Pose Variant lost prompt cues output")
|
|
_expect("torso bent forward" in avoid_cues, "Krea2 Pose Variant lost avoid cues output")
|
|
_expect("variant=pov_boobjob_upright_cleavage" in variant_summary, "Krea2 Pose Variant summary lost key")
|
|
|
|
oral_filter = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPKrea2POVOralFilter"]
|
|
synthetic_variant = {
|
|
"key": "pov_synthetic_node_seeded_oral",
|
|
"family": "oral",
|
|
"status": "candidate",
|
|
"action_family": "oral",
|
|
"position_keys": ["top_down_oral"],
|
|
"canonical_geometry": "synthetic seeded oral atlas geometry",
|
|
"prompt_cues": ["synthetic baseline oral atlas cue"],
|
|
"prompt_variant_cues": [
|
|
["synthetic alternate oral atlas cue"],
|
|
{"prompt_cues": ["synthetic second oral atlas cue"]},
|
|
],
|
|
"avoid_cues": ["synthetic avoid cue"],
|
|
}
|
|
original_get_variant = krea2_pose_variant_catalog.get_variant
|
|
original_variants = krea2_pose_variant_catalog.variants
|
|
try:
|
|
def fake_get_variant(key: str, **kwargs):
|
|
if key == "pov_synthetic_node_seeded_oral":
|
|
return dict(synthetic_variant)
|
|
return original_get_variant(key, **kwargs)
|
|
|
|
def fake_variants(*, status=None, family=None, action_family=None, path=None):
|
|
if action_family == "oral":
|
|
return [dict(synthetic_variant)]
|
|
return original_variants(status=status, family=family, action_family=action_family, path=path)
|
|
|
|
krea2_pose_variant_catalog.get_variant = fake_get_variant
|
|
krea2_pose_variant_catalog.variants = fake_variants
|
|
|
|
seeded_config_a, _seeded_key_a, _seeded_cues_a, _seeded_avoid_a, seeded_summary_a, _seeded_json_a = (
|
|
variant_node().build(
|
|
"pov_synthetic_node_seeded_oral",
|
|
"replace",
|
|
"",
|
|
atlas_cue_seed=901,
|
|
)
|
|
)
|
|
seeded_config_b, _seeded_key_b, _seeded_cues_b, _seeded_avoid_b, seeded_summary_b, _seeded_json_b = (
|
|
variant_node().build(
|
|
"pov_synthetic_node_seeded_oral",
|
|
"replace",
|
|
"",
|
|
atlas_cue_seed=901,
|
|
)
|
|
)
|
|
parsed_seeded_a = json.loads(seeded_config_a)
|
|
parsed_seeded_b = json.loads(seeded_config_b)
|
|
seeded_index = parsed_seeded_a.get("krea2_prompt_variant_indices", {}).get("pov_synthetic_node_seeded_oral")
|
|
_expect(
|
|
parsed_seeded_a.get("krea2_variant_keys") == ["pov_synthetic_node_seeded_oral"],
|
|
"Krea2 Pose Variant should write selected atlas variant metadata",
|
|
)
|
|
_expect(
|
|
isinstance(seeded_index, int) and 0 <= seeded_index < 3,
|
|
f"Krea2 Pose Variant should store a seeded prompt cue index, got {seeded_index!r}",
|
|
)
|
|
_expect(
|
|
parsed_seeded_b.get("krea2_prompt_variant_indices", {}).get("pov_synthetic_node_seeded_oral") == seeded_index,
|
|
"Krea2 Pose Variant atlas cue seed should be deterministic for the same seed",
|
|
)
|
|
_expect(
|
|
parsed_seeded_a.get("krea2_prompt_variant_seed") == 901,
|
|
"Krea2 Pose Variant should record atlas cue seed provenance",
|
|
)
|
|
_expect(
|
|
"cue_seed=901" in seeded_summary_a and "cue_indices=pov_synthetic_node_seeded_oral:" in seeded_summary_a,
|
|
"Krea2 Pose Variant summary should expose atlas cue seed and selected cue index",
|
|
)
|
|
|
|
include_synthetic = _atlas_variant_include_key("pov_synthetic_node_seeded_oral")
|
|
synthetic_filter_config, synthetic_filter_keys, _synthetic_positions, _synthetic_notes, synthetic_filter_summary, _ = (
|
|
oral_filter().build(
|
|
"replace",
|
|
"",
|
|
atlas_cue_seed=902,
|
|
**{include_synthetic: True},
|
|
)
|
|
)
|
|
parsed_filter_seeded = json.loads(synthetic_filter_config)
|
|
_expect(
|
|
synthetic_filter_keys == "pov_synthetic_node_seeded_oral",
|
|
"Krea2 POV filter should select the synthetic seeded variant",
|
|
)
|
|
_expect(
|
|
parsed_filter_seeded.get("krea2_prompt_variant_seed") == 902,
|
|
"Krea2 POV filter should record atlas cue seed provenance",
|
|
)
|
|
_expect(
|
|
"cue_seed=902" in synthetic_filter_summary,
|
|
"Krea2 POV filter summary should expose atlas cue seed",
|
|
)
|
|
finally:
|
|
krea2_pose_variant_catalog.get_variant = original_get_variant
|
|
krea2_pose_variant_catalog.variants = original_variants
|
|
|
|
penetration_filter = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPKrea2POVPenetrationFilter"]
|
|
penetration_inputs = penetration_filter.INPUT_TYPES().get("required") or {}
|
|
_expect("include_doggy_top_down_rear_entry" in penetration_inputs, "POV Penetration Filter lost doggy atlas checkbox")
|
|
_expect(
|
|
"include_blowjob_side_profile_oral" not in penetration_inputs,
|
|
"POV Penetration Filter should not expose oral atlas checkboxes",
|
|
)
|
|
oral_inputs = oral_filter.INPUT_TYPES().get("required") or {}
|
|
_expect("include_blowjob_side_profile_oral" in oral_inputs, "POV Oral Filter lost blowjob side-profile checkbox")
|
|
_expect(
|
|
"include_doggy_top_down_rear_entry" not in oral_inputs,
|
|
"POV Oral Filter should not expose penetration atlas checkboxes",
|
|
)
|
|
top_config, top_keys, top_positions, top_notes, top_summary, _top_variants_json = oral_filter().build(
|
|
"replace",
|
|
"",
|
|
include_blowjob_top_down_vertical_shaft=True,
|
|
)
|
|
parsed_top_config = json.loads(top_config)
|
|
_expect(top_keys == "pov_blowjob_top_down_vertical_shaft", "POV Oral Filter returned wrong exact top-view variant key")
|
|
_expect(
|
|
parsed_top_config.get("krea2_variant_keys") == ["pov_blowjob_top_down_vertical_shaft"],
|
|
"POV Oral Filter lost exact top-view variant metadata",
|
|
)
|
|
_expect("kneeling" in top_positions, "POV Oral Filter lost top-view route position")
|
|
_expect(
|
|
"nadir-angle" not in top_notes and "viewer looks almost straight down" not in top_notes,
|
|
"POV Oral Filter should not emit raw atlas prompt prose as a connectable text output",
|
|
)
|
|
_expect("variants=pov_blowjob_top_down_vertical_shaft" in top_summary, "POV Oral Filter summary lost top-view variant key")
|
|
|
|
sitting_config, sitting_keys, sitting_positions, _sitting_notes, sitting_summary, _sitting_variants_json = oral_filter().build(
|
|
"replace",
|
|
"",
|
|
include_blowjob_sitting_upright_oral=True,
|
|
)
|
|
parsed_sitting_config = json.loads(sitting_config)
|
|
_expect(sitting_keys == "pov_blowjob_sitting_upright_oral", "POV Oral Filter returned wrong exact sitting oral variant key")
|
|
_expect(
|
|
parsed_sitting_config.get("krea2_variant_keys") == ["pov_blowjob_sitting_upright_oral"],
|
|
"POV Oral Filter lost exact sitting oral variant metadata",
|
|
)
|
|
_expect(
|
|
"blowjob_sitting" in (parsed_sitting_config.get("positions") or []),
|
|
"POV Oral Filter dropped sitting oral route position from config",
|
|
)
|
|
_expect("blowjob_sitting" in sitting_positions.split(","), "POV Oral Filter dropped sitting oral route position summary")
|
|
_expect("variants=pov_blowjob_sitting_upright_oral" in sitting_summary, "POV Oral Filter summary lost sitting oral variant key")
|
|
|
|
doggy_config, doggy_keys, doggy_positions, doggy_notes, doggy_summary, doggy_variants_json = penetration_filter().build(
|
|
"replace",
|
|
"",
|
|
include_doggy_top_down_rear_entry=True,
|
|
)
|
|
parsed_doggy_config = json.loads(doggy_config)
|
|
_expect(parsed_doggy_config.get("family") == "penetrative", "POV Penetration Filter should constrain single-family output")
|
|
_expect(parsed_doggy_config.get("positions") == ["doggy"], "POV Penetration Filter did not map doggy variant to position key")
|
|
_expect(
|
|
parsed_doggy_config.get("krea2_variant_keys") == ["pov_doggy_top_down_rear_entry"],
|
|
"POV Penetration Filter lost exact selected variant metadata",
|
|
)
|
|
_expect(doggy_keys == "pov_doggy_top_down_rear_entry", "POV Penetration Filter returned wrong selected variant keys")
|
|
_expect(doggy_positions == "doggy", "POV Penetration Filter returned wrong selected positions")
|
|
_expect("pov_doggy_top_down_rear_entry" in doggy_notes and "top-down POV" not in doggy_notes, "POV Penetration Filter should emit compact variant notes, not raw prompt cues")
|
|
_expect("variants=pov_doggy_top_down_rear_entry" in doggy_summary, "POV Penetration Filter summary lost selected variant")
|
|
_expect(json.loads(doggy_variants_json)[0].get("key") == "pov_doggy_top_down_rear_entry", "POV Penetration Filter returned wrong variant JSON")
|
|
|
|
mixed_config, mixed_keys, mixed_positions, _mixed_cues, mixed_summary, _mixed_variants_json = oral_filter().build(
|
|
"add",
|
|
doggy_config,
|
|
include_blowjob_side_profile_oral=True,
|
|
)
|
|
parsed_mixed_config = json.loads(mixed_config)
|
|
_expect(parsed_mixed_config.get("family") == "any", "Chained POV filters should use any family for mixed categories")
|
|
_expect(
|
|
parsed_mixed_config.get("positions") == ["doggy", "side_lying", "reclining_oral", "penis_licking"],
|
|
"Chained POV filters did not preserve doggy and blowjob position keys",
|
|
)
|
|
_expect(
|
|
parsed_mixed_config.get("krea2_variant_keys") == [
|
|
"pov_doggy_top_down_rear_entry",
|
|
"pov_blowjob_side_profile_oral",
|
|
],
|
|
"Chained POV filters lost exact selected variant metadata",
|
|
)
|
|
_expect(
|
|
mixed_keys == "pov_doggy_top_down_rear_entry,pov_blowjob_side_profile_oral",
|
|
"Chained POV filters returned wrong selected variant key list",
|
|
)
|
|
_expect("doggy" in mixed_positions and "penis_licking" in mixed_positions, "Chained POV filters returned wrong positions summary")
|
|
_expect("family=any" in mixed_summary, "Chained POV filters summary should show mixed family")
|
|
|
|
restore_node = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPKrea2POVPromptRestore"]
|
|
restore_inputs = restore_node.INPUT_TYPES().get("required") or {}
|
|
restore_optional = restore_node.INPUT_TYPES().get("optional") or {}
|
|
_expect(
|
|
"hardcore_position_config" not in restore_inputs,
|
|
"Krea2 POV Prompt Restore should not require an incoming position config connection",
|
|
)
|
|
_expect(
|
|
"hardcore_position_config" in restore_optional,
|
|
"Krea2 POV Prompt Restore should expose incoming position config as an optional connection",
|
|
)
|
|
for key in (
|
|
"restore_clothing_detail",
|
|
"restore_face_expression_detail",
|
|
"restore_body_touch_detail",
|
|
"restore_camera_presentation_detail",
|
|
"relax_non_pose_axis_conflicts",
|
|
):
|
|
_expect(key in restore_inputs, f"Krea2 POV Prompt Restore lost input {key}")
|
|
standalone_restore_config, standalone_restore_summary = restore_node().build(
|
|
True,
|
|
True,
|
|
False,
|
|
False,
|
|
True,
|
|
"",
|
|
)
|
|
parsed_standalone_restore = json.loads(standalone_restore_config)
|
|
_expect(
|
|
parsed_standalone_restore.get("restore_prompt_axes") == [
|
|
"clothing_detail",
|
|
"face_detail",
|
|
"expression_detail",
|
|
"mouth_detail",
|
|
"reaction_detail",
|
|
],
|
|
"Krea2 POV Prompt Restore should emit restore axes without an incoming connection",
|
|
)
|
|
_expect(
|
|
"restore_axes=clothing_detail,face_detail" in standalone_restore_summary,
|
|
"Krea2 POV Prompt Restore standalone summary lost restored axes",
|
|
)
|
|
pre_restore_config, _pre_restore_summary = restore_node().build(
|
|
True,
|
|
True,
|
|
False,
|
|
False,
|
|
True,
|
|
"",
|
|
)
|
|
pre_restored_top_config, _pre_restored_top_keys, _pre_restored_top_positions, _, _, _ = oral_filter().build(
|
|
"replace",
|
|
pre_restore_config,
|
|
include_blowjob_top_down_vertical_shaft=True,
|
|
)
|
|
parsed_pre_restored_top = json.loads(pre_restored_top_config)
|
|
_expect(
|
|
parsed_pre_restored_top.get("krea2_variant_keys") == ["pov_blowjob_top_down_vertical_shaft"],
|
|
"Restore-before-filter should keep the selected top-view atlas variant metadata",
|
|
)
|
|
_expect(
|
|
parsed_pre_restored_top.get("restore_prompt_axes") == [
|
|
"clothing_detail",
|
|
"face_detail",
|
|
"expression_detail",
|
|
"mouth_detail",
|
|
"reaction_detail",
|
|
],
|
|
"Restore-before-filter should preserve restore axes through replace-mode pose filtering",
|
|
)
|
|
_expect(
|
|
parsed_pre_restored_top.get("relax_non_pose_axis_conflicts") is True,
|
|
"Restore-before-filter should preserve non-pose conflict relaxation through replace-mode pose filtering",
|
|
)
|
|
restored_config, restored_summary = restore_node().build(
|
|
True,
|
|
True,
|
|
False,
|
|
False,
|
|
True,
|
|
doggy_config,
|
|
)
|
|
parsed_restored_config = json.loads(restored_config)
|
|
_expect(
|
|
parsed_restored_config.get("krea2_variant_keys") == ["pov_doggy_top_down_rear_entry"],
|
|
"Krea2 POV Prompt Restore should preserve selected atlas variant metadata",
|
|
)
|
|
_expect(parsed_restored_config.get("positions") == ["doggy"], "Krea2 POV Prompt Restore should preserve selected pose lock")
|
|
_expect(parsed_restored_config.get("family") == "penetrative", "Krea2 POV Prompt Restore should preserve atlas family lock")
|
|
_expect(
|
|
parsed_restored_config.get("restore_prompt_axes") == [
|
|
"clothing_detail",
|
|
"face_detail",
|
|
"expression_detail",
|
|
"mouth_detail",
|
|
"reaction_detail",
|
|
],
|
|
"Krea2 POV Prompt Restore should record requested prompt axes",
|
|
)
|
|
_expect(
|
|
parsed_restored_config.get("relax_non_pose_axis_conflicts") is True,
|
|
"Krea2 POV Prompt Restore should enable non-pose conflict relaxation",
|
|
)
|
|
_expect("restore_axes=clothing_detail,face_detail" in restored_summary, "Krea2 POV Prompt Restore summary lost restored axes")
|
|
parsed_restored = hardcore_position_config.parse_hardcore_position_config(parsed_restored_config)
|
|
conflicting_clothing = {"text": "shirt pulled open while standing", "position_keys": ["standing"]}
|
|
neutral_clothing = {"text": "shirt pulled open at the hips"}
|
|
restored_clothing = hardcore_position_config.filter_hardcore_axis(
|
|
"clothing_detail",
|
|
[conflicting_clothing, neutral_clothing],
|
|
parsed_restored,
|
|
)
|
|
_expect(
|
|
restored_clothing == [conflicting_clothing, neutral_clothing],
|
|
"Krea2 POV Prompt Restore should allow restored clothing detail through non-pose conflict pruning",
|
|
)
|
|
unrestored_hand = hardcore_position_config.filter_hardcore_axis(
|
|
"hand_detail",
|
|
[conflicting_clothing, neutral_clothing],
|
|
parsed_restored,
|
|
)
|
|
_expect(
|
|
unrestored_hand == [neutral_clothing],
|
|
"Krea2 POV Prompt Restore should not relax unrequested non-pose axes",
|
|
)
|
|
restored_position = hardcore_position_config.filter_hardcore_axis(
|
|
"position",
|
|
[
|
|
{"text": "standing sex position", "position_keys": ["standing"]},
|
|
{"text": "doggy style position", "position_keys": ["doggy"]},
|
|
],
|
|
parsed_restored,
|
|
)
|
|
_expect(
|
|
restored_position == [{"text": "doggy style position", "position_keys": ["doggy"]}],
|
|
"Krea2 POV Prompt Restore should keep position axes locked to the atlas pose",
|
|
)
|
|
|
|
evidence_node = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPKrea2VariantEvidence"]
|
|
evidence_inputs = evidence_node.INPUT_TYPES().get("required") or {}
|
|
_expect("variant_key" in evidence_inputs, "Krea2 Variant Evidence lost variant selector")
|
|
_expect("tooltip" in evidence_inputs["variant_key"][1], "Krea2 Variant Evidence tooltip injection missing")
|
|
(
|
|
evidence_summary,
|
|
baseline_image,
|
|
candidate_image,
|
|
evidence_json,
|
|
evidence_seed,
|
|
evidence_decision,
|
|
) = evidence_node().build(
|
|
"pov_boobjob_upright_cleavage",
|
|
"accepted",
|
|
"",
|
|
)
|
|
parsed_evidence = json.loads(evidence_json)
|
|
_expect(evidence_seed == 7302, "Krea2 Variant Evidence returned wrong fixed seed")
|
|
_expect(evidence_decision == "generator_patch", "Krea2 Variant Evidence returned wrong decision")
|
|
_expect("boobjob-7302" in evidence_summary, "Krea2 Variant Evidence summary lost entry id")
|
|
_expect(baseline_image.endswith(".png") and candidate_image.endswith(".png"), "Krea2 Variant Evidence lost image paths")
|
|
_expect(parsed_evidence.get("variant_key") == "pov_boobjob_upright_cleavage", "Krea2 Variant Evidence returned wrong JSON")
|
|
|
|
|
|
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")
|
|
krea_optional = krea_node.INPUT_TYPES().get("optional") or {}
|
|
_expect("source_text_input" in krea_optional, "Krea2 Formatter lost connectable source_text_input")
|
|
_expect("metadata_json" in krea_optional, "Krea2 Formatter lost metadata_json input")
|
|
_expect(krea_optional["metadata_json"][1].get("forceInput") is True, "Krea2 Formatter metadata_json should be connectable")
|
|
_expect(krea_optional["negative_prompt"][1].get("forceInput") is True, "Krea2 Formatter negative_prompt should be connectable")
|
|
|
|
caption, caption_method, caption_trace_json = 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_trace = json.loads(_expect_text("node_formatter.caption_trace", caption_trace_json, 20))
|
|
_expect(caption_trace.get("formatter") == "caption", "Caption Naturalizer trace lost formatter")
|
|
_expect(caption_trace.get("branch") == "text", "Caption Naturalizer trace lost text branch")
|
|
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("target" in caption_inputs, "Caption Naturalizer lost target input")
|
|
_expect(caption_inputs["target"][0] == formatter_target.target_choices(), "Caption Naturalizer target choices drifted")
|
|
_expect(caption_inputs["detail_level"][0] == formatter_detail.detail_level_choices(), "Caption Naturalizer detail choices drifted")
|
|
_expect(caption_inputs["style_policy"][0] == caption_policy.style_policy_choices(), "Caption Naturalizer style choices drifted")
|
|
_expect("tooltip" in caption_inputs["caption_profile"][1], "Caption profile tooltip injection missing")
|
|
_expect(krea_inputs["target"][0] == formatter_target.target_choices(), "Krea2 Formatter target choices drifted")
|
|
_expect(krea_inputs["detail_level"][0] == formatter_detail.detail_level_choices(), "Krea2 Formatter detail choices drifted")
|
|
_expect(krea_inputs["style_mode"][0] == krea_format_route.style_mode_choices(), "Krea2 Formatter style choices drifted")
|
|
|
|
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")
|
|
krea_trace = json.loads(_expect_text("node_formatter.krea_trace", krea_output[7], 20))
|
|
_expect(krea_trace.get("formatter") == "krea2", "Krea2 Formatter trace lost formatter")
|
|
_expect(krea_trace.get("branch") == "fallback", "Krea2 Formatter trace lost fallback branch")
|
|
|
|
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_trace = json.loads(_expect_text("node_formatter.sdxl_trace", sdxl_output[7], 20))
|
|
_expect(sdxl_trace.get("formatter") == "sdxl", "SDXL Formatter trace lost formatter")
|
|
_expect(sdxl_trace.get("branch") == "fallback", "SDXL Formatter trace lost fallback branch")
|
|
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(sdxl_inputs["target"][0] == formatter_target.target_choices(), "SDXL Formatter target choices drifted")
|
|
_expect("tooltip" in sdxl_inputs["formatter_profile"][1], "SDXL formatter_profile tooltip injection missing")
|
|
|
|
pair = pb.build_insta_of_pair(
|
|
row_number=1,
|
|
start_index=1,
|
|
seed=4311,
|
|
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"),
|
|
)
|
|
pair_metadata = _json(pair)
|
|
|
|
expected_krea_pair = krea_formatter.format_krea2_prompt(
|
|
"",
|
|
metadata_json=pair_metadata,
|
|
input_hint="metadata_json",
|
|
target="hardcore",
|
|
detail_level="balanced",
|
|
style_mode="preserve",
|
|
extra_positive="node krea pair marker",
|
|
extra_negative="node krea pair negative",
|
|
)
|
|
node_krea_pair = krea_node().build(
|
|
"",
|
|
"metadata_json",
|
|
"hardcore",
|
|
"balanced",
|
|
"preserve",
|
|
False,
|
|
metadata_json=pair_metadata,
|
|
extra_positive="node krea pair marker",
|
|
extra_negative="node krea pair negative",
|
|
)
|
|
_expect(
|
|
node_krea_pair
|
|
== (
|
|
expected_krea_pair["krea_prompt"],
|
|
expected_krea_pair["negative_prompt"],
|
|
expected_krea_pair["krea_softcore_prompt"],
|
|
expected_krea_pair["krea_hardcore_prompt"],
|
|
expected_krea_pair["softcore_negative_prompt"],
|
|
expected_krea_pair["hardcore_negative_prompt"],
|
|
expected_krea_pair["method"],
|
|
expected_krea_pair["route_trace_json"],
|
|
),
|
|
"Krea2 Formatter node output drifted from public metadata formatter",
|
|
)
|
|
|
|
expected_sdxl_pair = sdxl_formatter.format_sdxl_prompt(
|
|
"",
|
|
metadata_json=pair_metadata,
|
|
input_hint="metadata_json",
|
|
target="hardcore",
|
|
formatter_profile="manual_controls",
|
|
style_preset="flat_vector_pony",
|
|
quality_preset="pony_high",
|
|
trigger=SdxlTrigger,
|
|
prepend_trigger=True,
|
|
preserve_trigger=False,
|
|
nude_weight=1.29,
|
|
extra_positive="node sdxl pair marker",
|
|
extra_negative="node sdxl pair negative",
|
|
)
|
|
node_sdxl_pair = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPSDXLFormatter"]().build(
|
|
"",
|
|
"metadata_json",
|
|
"hardcore",
|
|
"manual_controls",
|
|
"flat_vector_pony",
|
|
"pony_high",
|
|
SdxlTrigger,
|
|
True,
|
|
False,
|
|
1.29,
|
|
metadata_json=pair_metadata,
|
|
extra_positive="node sdxl pair marker",
|
|
extra_negative="node sdxl pair negative",
|
|
)
|
|
_expect(
|
|
node_sdxl_pair
|
|
== (
|
|
expected_sdxl_pair["sdxl_prompt"],
|
|
expected_sdxl_pair["negative_prompt"],
|
|
expected_sdxl_pair["sdxl_softcore_prompt"],
|
|
expected_sdxl_pair["sdxl_hardcore_prompt"],
|
|
expected_sdxl_pair["softcore_negative_prompt"],
|
|
expected_sdxl_pair["hardcore_negative_prompt"],
|
|
expected_sdxl_pair["method"],
|
|
expected_sdxl_pair["route_trace_json"],
|
|
),
|
|
"SDXL Formatter node output drifted from public metadata formatter",
|
|
)
|
|
|
|
expected_caption_pair = caption_naturalizer.naturalize_caption_with_trace(
|
|
"",
|
|
metadata_json=pair_metadata,
|
|
input_hint="metadata_json",
|
|
target="hardcore",
|
|
trigger=Trigger,
|
|
include_trigger=True,
|
|
detail_level="balanced",
|
|
style_policy="drop_style_tail",
|
|
caption_profile="training_dense",
|
|
)
|
|
node_caption_pair = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPCaptionNaturalizer"]().build(
|
|
"",
|
|
"metadata_json",
|
|
"training_dense",
|
|
"balanced",
|
|
"drop_style_tail",
|
|
Trigger,
|
|
True,
|
|
"hardcore",
|
|
metadata_json=pair_metadata,
|
|
)
|
|
_expect(
|
|
node_caption_pair == expected_caption_pair,
|
|
"Caption Naturalizer node output drifted from public metadata formatter",
|
|
)
|
|
|
|
|
|
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_scene_chain_registration() -> None:
|
|
required_nodes = [
|
|
"SxCPSceneLayerSeedOptions",
|
|
"SxCPSceneCastOptions",
|
|
"SxCPSceneCharacterOptions",
|
|
"SxCPSceneWardrobeOptions",
|
|
"SxCPSceneLocationLayoutOptions",
|
|
"SxCPSceneSetDressingOptions",
|
|
"SxCPSceneBlockingOptions",
|
|
"SxCPSceneActionOptions",
|
|
"SxCPScenePerformanceOptions",
|
|
"SxCPSceneCameraOptions",
|
|
"SxCPSceneCompositionOptions",
|
|
"SxCPSceneLightingOptions",
|
|
"SxCPSceneBranchOptions",
|
|
"SxCPSceneStart",
|
|
"SxCPSceneCast",
|
|
"SxCPSceneCharacter",
|
|
"SxCPSceneWardrobe",
|
|
"SxCPSceneLocation",
|
|
"SxCPSceneSetDressing",
|
|
"SxCPSceneBlocking",
|
|
"SxCPSceneAction",
|
|
"SxCPScenePerformance",
|
|
"SxCPSceneCamera",
|
|
"SxCPSceneComposition",
|
|
"SxCPSceneLighting",
|
|
"SxCPSceneBranchPair",
|
|
"SxCPSoftcoreBranchOptions",
|
|
"SxCPHardcoreBranchOptions",
|
|
"SxCPSceneOutput",
|
|
"SxCPScenePairOutput",
|
|
"SxCPChoiceBoard",
|
|
]
|
|
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")
|
|
|
|
nodes = sxcp_nodes.NODE_CLASS_MAPPINGS
|
|
random_seed_options = nodes["SxCPSceneLayerSeedOptions"]().build(
|
|
"softcore_branch",
|
|
"random",
|
|
123456789,
|
|
"content_pose",
|
|
"same_for_all_rows",
|
|
"replace_layer",
|
|
)[0]
|
|
fixed_seed_options = nodes["SxCPSceneLayerSeedOptions"]().build(
|
|
"softcore_branch",
|
|
"fixed",
|
|
123456789,
|
|
"content_pose",
|
|
"same_for_all_rows",
|
|
"replace_layer",
|
|
)[0]
|
|
random_seed_item = json.loads(random_seed_options)["items"][0]
|
|
fixed_seed_item = json.loads(fixed_seed_options)["items"][0]
|
|
_expect(
|
|
random_seed_item.get("seed") == 123456789,
|
|
"Scene random layer seed should use the visible node seed",
|
|
)
|
|
_expect(
|
|
random_seed_item.get("seed") == fixed_seed_item.get("seed"),
|
|
"Scene random and fixed layer seeds should match when the visible seed matches",
|
|
)
|
|
scene, start_summary, _start_metadata = nodes["SxCPSceneStart"]().build(
|
|
1,
|
|
41,
|
|
777,
|
|
"raw",
|
|
"woman",
|
|
"random",
|
|
"balanced",
|
|
Trigger,
|
|
True,
|
|
)
|
|
_expect("scene v" in start_summary, "Scene Start summary changed unexpectedly")
|
|
parsed_scene = json.loads(scene)
|
|
_expect(parsed_scene.get("schema") == "sxcp_scene_v2", "Scene Start did not emit v2 schema")
|
|
|
|
cast_options = nodes["SxCPSceneCastOptions"]().build("replace", "mixed_couple", 1, 1, "woman_a", "none")[0]
|
|
scene, _cast_config, _cast_summary, _cast_metadata = nodes["SxCPSceneCast"]().build(
|
|
scene,
|
|
"mixed_couple",
|
|
1,
|
|
1,
|
|
"woman_a",
|
|
"none",
|
|
cast_options=cast_options,
|
|
)
|
|
character_options = nodes["SxCPSceneCharacterOptions"]().build(
|
|
"replace",
|
|
"medium",
|
|
"visible",
|
|
"enabled",
|
|
0.5,
|
|
-1,
|
|
-1,
|
|
"controlled slot performance note",
|
|
)[0]
|
|
scene, character_cast, _slot, _summary, _metadata = nodes["SxCPSceneCharacter"]().build(
|
|
scene,
|
|
True,
|
|
"woman",
|
|
"A",
|
|
-1,
|
|
"25-year-old adult",
|
|
"random",
|
|
"random",
|
|
"random",
|
|
"medium",
|
|
True,
|
|
0.5,
|
|
"visible",
|
|
-1,
|
|
-1,
|
|
character_options=character_options,
|
|
)
|
|
scene, character_cast, _slot, _summary, _metadata = nodes["SxCPSceneCharacter"]().build(
|
|
scene,
|
|
True,
|
|
"man",
|
|
"A",
|
|
-1,
|
|
"40-year-old adult",
|
|
"random",
|
|
"random",
|
|
"average",
|
|
"compact",
|
|
True,
|
|
0.5,
|
|
"visible",
|
|
-1,
|
|
-1,
|
|
)
|
|
seed_fixture_scene = scene
|
|
wardrobe_seed_options = nodes["SxCPSceneLayerSeedOptions"]().build("wardrobe", "fixed", 9981, "content", "same_for_all_rows", "replace_layer")[0]
|
|
wardrobe_options = nodes["SxCPSceneWardrobeOptions"]().build(
|
|
"replace",
|
|
"woman",
|
|
"A",
|
|
"full",
|
|
"explicit_nude",
|
|
True,
|
|
"simple black dress",
|
|
"",
|
|
"thin necklace",
|
|
"",
|
|
)[0]
|
|
scene, character_cast, _wardrobe_summary, _wardrobe_metadata = nodes["SxCPSceneWardrobe"]().build(
|
|
scene,
|
|
True,
|
|
"woman",
|
|
"A",
|
|
"full",
|
|
"simple black dress",
|
|
"fully nude",
|
|
"",
|
|
wardrobe_options=wardrobe_options,
|
|
seed_options=wardrobe_seed_options,
|
|
)
|
|
slots = json.loads(character_cast).get("slots") or []
|
|
woman_slot = next(slot for slot in slots if slot.get("subject_type") == "woman")
|
|
_expect(woman_slot.get("softcore_outfit") == "simple black dress", "Scene Wardrobe did not update softcore outfit")
|
|
_expect(woman_slot.get("hardcore_clothing") == "fully nude", "Scene Wardrobe did not update hardcore clothing")
|
|
_expect(json.loads(scene).get("seed_trace", {}).get("wardrobe", {}).get("seed") == 9981, "Scene Wardrobe seed options did not write seed trace")
|
|
|
|
location_options = nodes["SxCPSceneLocationLayoutOptions"]().build(
|
|
"replace",
|
|
"mirror edge",
|
|
"soft curtain layer",
|
|
"repeating lamp reflections",
|
|
"private",
|
|
"private",
|
|
"warm mirror-room geometry",
|
|
)[0]
|
|
scene = nodes["SxCPSceneLocation"]().build(
|
|
scene,
|
|
True,
|
|
"replace",
|
|
"custom_only",
|
|
"quiet studio room with a large mirror",
|
|
"",
|
|
location_options=location_options,
|
|
)[0]
|
|
set_options = nodes["SxCPSceneSetDressingOptions"]().build("replace", "mirror edge", "soft curtains", "small lamp", "warm fabric texture", "")[0]
|
|
scene = nodes["SxCPSceneSetDressing"]().build(scene, True, "mirror edge", "soft curtains", "small lamp", "", set_options=set_options)[0]
|
|
blocking_options = nodes["SxCPSceneBlockingOptions"]().build(
|
|
"replace",
|
|
"standing",
|
|
"woman near mirror",
|
|
"man behind her",
|
|
"three_quarter",
|
|
"foreground",
|
|
"standing close",
|
|
"",
|
|
)[0]
|
|
scene = nodes["SxCPSceneBlocking"]().build(scene, True, "standing", "woman near mirror", "man behind her", "", blocking_options=blocking_options)[0]
|
|
action_options = nodes["SxCPSceneActionOptions"]().build("replace", "softcore", "softcore_tease", "no_change", "quiet pose transition")[0]
|
|
scene = nodes["SxCPSceneAction"]().build(scene, True, "regular", "no_change", "", action_options=action_options)[0]
|
|
performance_options = nodes["SxCPScenePerformanceOptions"]().build("replace", "enabled", "fixed", 0.4, "camera", "on_body", "posed", "controlled eye contact")[0]
|
|
scene = nodes["SxCPScenePerformance"]().build(scene, True, "fixed", 0.4, "controlled eye contact", performance_options=performance_options)[0]
|
|
camera_options = nodes["SxCPSceneCameraOptions"]().build("replace", "from_camera_config", True, "mirror-aware camera note")[0]
|
|
scene = nodes["SxCPSceneCamera"]().build(
|
|
scene,
|
|
True,
|
|
"standard",
|
|
"three_quarter",
|
|
"eye_level",
|
|
"auto",
|
|
"auto",
|
|
"auto",
|
|
"auto",
|
|
"strong",
|
|
"compact",
|
|
"",
|
|
camera_options=camera_options,
|
|
)[0]
|
|
composition_options = nodes["SxCPSceneCompositionOptions"]().build("replace", "body", "three_quarter", "clear", "mirror-aware three-quarter frame")[0]
|
|
scene = nodes["SxCPSceneComposition"]().build(scene, True, "replace", "no_outfit_check", "", "", composition_options=composition_options)[0]
|
|
lighting_options = nodes["SxCPSceneLightingOptions"]().build("replace", "practical_lamps", "soft", "medium", "warm", "evening", "")[0]
|
|
scene = nodes["SxCPSceneLighting"]().build(scene, True, "practical_lamps", "soft", "medium", "warm", "", lighting_options=lighting_options)[0]
|
|
|
|
output = nodes["SxCPSceneOutput"]().build(scene)
|
|
_expect_text("node_scene_chain.prompt", output[0], 40)
|
|
_expect_trigger_once("node_scene_chain.prompt", output[0], Trigger)
|
|
row = json.loads(output[3])
|
|
_expect(row.get("scene_chain", {}).get("schema") == "sxcp_scene_v2", "Scene Output lost scene_chain metadata")
|
|
|
|
branch_options = nodes["SxCPSceneBranchOptions"]().build("replace", "both", "same_creator_same_room", "hybrid", "shared branch note")[0]
|
|
branch_seed_options = nodes["SxCPSceneLayerSeedOptions"]().build("hardcore_branch", "fixed", 7799, "pose", "same_for_all_rows", "replace_layer")[0]
|
|
soft_scene, hard_scene, _branch_summary, _branch_metadata = nodes["SxCPSceneBranchPair"]().build(
|
|
scene,
|
|
"same_creator_same_room",
|
|
"hybrid",
|
|
branch_options=branch_options,
|
|
seed_options=branch_seed_options,
|
|
)
|
|
soft_scene = nodes["SxCPSoftcoreBranchOptions"]().build(
|
|
soft_scene,
|
|
"same_as_hardcore",
|
|
"lingerie_tease",
|
|
True,
|
|
0.45,
|
|
"from_camera_config",
|
|
"compact",
|
|
"",
|
|
branch_options=branch_options,
|
|
)[0]
|
|
hard_scene = nodes["SxCPHardcoreBranchOptions"]().build(
|
|
hard_scene,
|
|
"couple",
|
|
1,
|
|
1,
|
|
"hardcore",
|
|
True,
|
|
0.85,
|
|
"explicit_nude",
|
|
"from_camera_config",
|
|
"compact",
|
|
"balanced",
|
|
"",
|
|
branch_options=branch_options,
|
|
seed_options=branch_seed_options,
|
|
)[0]
|
|
pair_output = nodes["SxCPScenePairOutput"]().build(soft_scene, hard_scene)
|
|
_expect_text("node_scene_chain.softcore_prompt", pair_output[0], 40)
|
|
_expect_text("node_scene_chain.hardcore_prompt", pair_output[1], 40)
|
|
pair = json.loads(pair_output[7])
|
|
_expect_pair(pair, "node_scene_chain_pair")
|
|
_expect(pair.get("options", {}).get("hardcore_cast") == "couple", "Scene Pair Output lost hardcore branch options")
|
|
_expect("scene_chain" in pair, "Scene Pair Output lost scene_chain metadata")
|
|
_expect(
|
|
pair.get("scene_chain", {}).get("hardcore", {}).get("seed_trace", {}).get("hardcore.hardcore_branch", {}).get("seed") == 7799,
|
|
"Scene branch seed options did not write hardcore branch seed trace",
|
|
)
|
|
hard_seed_config = pair.get("hardcore_row", {}).get("seed_config") if isinstance(pair.get("hardcore_row"), dict) else {}
|
|
_expect(hard_seed_config.get("pose_seed") == 7799, "Scene Pair Output did not pass hardcore branch pose seed to generator")
|
|
_expect(hard_seed_config.get("role_seed") == 7799, "Scene Pair Output did not pass hardcore branch role seed to generator")
|
|
hard_trace_axes = pair.get("hardcore_row", {}).get("generation_trace", {}).get("seed_axes", {}) if isinstance(pair.get("hardcore_row"), dict) else {}
|
|
_expect(
|
|
hard_trace_axes.get("pose", {}).get("seed") == 7799,
|
|
"Scene Pair Output generation trace did not use hardcore branch pose seed",
|
|
)
|
|
soft_content_seed_options = nodes["SxCPSceneLayerSeedOptions"]().build(
|
|
"softcore_branch",
|
|
"fixed",
|
|
6679,
|
|
"content",
|
|
"same_for_all_rows",
|
|
"replace_layer",
|
|
)[0]
|
|
seeded_soft_scene, seeded_hard_scene, _summary, _metadata = nodes["SxCPSceneBranchPair"]().build(
|
|
seed_fixture_scene,
|
|
"same_creator_same_room",
|
|
"hybrid",
|
|
branch_options=branch_options,
|
|
)
|
|
seeded_soft_scene = nodes["SxCPSoftcoreBranchOptions"]().build(
|
|
seeded_soft_scene,
|
|
"same_as_hardcore",
|
|
"lingerie_tease",
|
|
True,
|
|
0.45,
|
|
"from_camera_config",
|
|
"compact",
|
|
"",
|
|
branch_options=branch_options,
|
|
seed_options=soft_content_seed_options,
|
|
)[0]
|
|
default_hardcore_continuity = (
|
|
nodes["SxCPHardcoreBranchOptions"].INPUT_TYPES()
|
|
.get("required", {})
|
|
.get("hardcore_clothing_continuity", (None, {}))[1]
|
|
.get("default")
|
|
)
|
|
_expect(
|
|
default_hardcore_continuity == "partially_removed",
|
|
"Hardcore Branch Options default should inherit softcore outfit continuity",
|
|
)
|
|
seeded_hard_scene = nodes["SxCPHardcoreBranchOptions"]().build(
|
|
seeded_hard_scene,
|
|
"couple",
|
|
1,
|
|
1,
|
|
"hardcore",
|
|
True,
|
|
0.85,
|
|
default_hardcore_continuity,
|
|
"from_camera_config",
|
|
"compact",
|
|
"balanced",
|
|
"",
|
|
branch_options=branch_options,
|
|
)[0]
|
|
seeded_pair = json.loads(nodes["SxCPScenePairOutput"]().build(seeded_soft_scene, seeded_hard_scene)[7])
|
|
seeded_soft_item = str(seeded_pair.get("softcore_row", {}).get("item") or "").lower()
|
|
seeded_hard_clothing = str(seeded_pair.get("hardcore_clothing_state") or "").lower()
|
|
seeded_hard_prompt = str(seeded_pair.get("hardcore_prompt") or "").lower()
|
|
_expect(
|
|
"black lace lingerie" in seeded_soft_item,
|
|
"Scene softcore branch content seed fixture no longer selects the expected outfit",
|
|
)
|
|
_expect(
|
|
"black lace lingerie" in seeded_hard_clothing,
|
|
"Hardcore prompt did not inherit the softcore-branch seeded woman outfit",
|
|
)
|
|
_expect(
|
|
"black lace lingerie" in seeded_hard_prompt,
|
|
"Hardcore prompt text did not include the softcore-branch seeded woman outfit",
|
|
)
|
|
content_pose_seed_options = nodes["SxCPSceneLayerSeedOptions"]().build(
|
|
"hardcore_branch",
|
|
"fixed",
|
|
8899,
|
|
"content_pose",
|
|
"same_for_all_rows",
|
|
"replace_layer",
|
|
)[0]
|
|
soft_scene_content, hard_scene_content, _branch_summary, _branch_metadata = nodes["SxCPSceneBranchPair"]().build(
|
|
scene,
|
|
"same_creator_same_room",
|
|
"hybrid",
|
|
branch_options=branch_options,
|
|
seed_options=content_pose_seed_options,
|
|
)
|
|
soft_scene_content = nodes["SxCPSoftcoreBranchOptions"]().build(
|
|
soft_scene_content,
|
|
"same_as_hardcore",
|
|
"lingerie_tease",
|
|
True,
|
|
0.45,
|
|
"from_camera_config",
|
|
"compact",
|
|
"",
|
|
branch_options=branch_options,
|
|
)[0]
|
|
hard_scene_content = nodes["SxCPHardcoreBranchOptions"]().build(
|
|
hard_scene_content,
|
|
"couple",
|
|
1,
|
|
1,
|
|
"hardcore",
|
|
True,
|
|
0.85,
|
|
"explicit_nude",
|
|
"from_camera_config",
|
|
"compact",
|
|
"balanced",
|
|
"",
|
|
branch_options=branch_options,
|
|
seed_options=content_pose_seed_options,
|
|
)[0]
|
|
content_pair = json.loads(nodes["SxCPScenePairOutput"]().build(soft_scene_content, hard_scene_content)[7])
|
|
content_soft_seed_config = (
|
|
content_pair.get("softcore_row", {}).get("seed_config")
|
|
if isinstance(content_pair.get("softcore_row"), dict)
|
|
else {}
|
|
)
|
|
content_hard_seed_config = (
|
|
content_pair.get("hardcore_row", {}).get("seed_config")
|
|
if isinstance(content_pair.get("hardcore_row"), dict)
|
|
else {}
|
|
)
|
|
_expect(
|
|
content_soft_seed_config.get("content_seed") != 8899,
|
|
"Hardcore branch content_pose reroll leaked into softcore clothing seed",
|
|
)
|
|
_expect(
|
|
content_hard_seed_config.get("content_seed") == 8899,
|
|
"Hardcore branch content_pose reroll did not reach hardcore content seed",
|
|
)
|
|
_expect(
|
|
content_hard_seed_config.get("pose_seed") == 8899 and content_hard_seed_config.get("role_seed") == 8899,
|
|
"Hardcore branch content_pose reroll did not reach hardcore pose and role seeds",
|
|
)
|
|
|
|
def _soft_pose_pair(soft_pose_seed: int) -> dict[str, Any]:
|
|
soft_pose_seed_options = nodes["SxCPSceneLayerSeedOptions"]().build(
|
|
"softcore_branch",
|
|
"fixed",
|
|
soft_pose_seed,
|
|
"pose",
|
|
"same_for_all_rows",
|
|
"replace_layer",
|
|
)[0]
|
|
soft_scene_pose, hard_scene_pose, _summary, _metadata = nodes["SxCPSceneBranchPair"]().build(
|
|
scene,
|
|
"same_creator_same_room",
|
|
"hybrid",
|
|
branch_options=branch_options,
|
|
seed_options=soft_pose_seed_options,
|
|
)
|
|
soft_scene_pose = nodes["SxCPSoftcoreBranchOptions"]().build(
|
|
soft_scene_pose,
|
|
"same_as_hardcore",
|
|
"lingerie_tease",
|
|
True,
|
|
0.45,
|
|
"from_camera_config",
|
|
"compact",
|
|
"",
|
|
branch_options=branch_options,
|
|
seed_options=soft_pose_seed_options,
|
|
)[0]
|
|
hard_scene_pose = nodes["SxCPHardcoreBranchOptions"]().build(
|
|
hard_scene_pose,
|
|
"couple",
|
|
1,
|
|
1,
|
|
"hardcore",
|
|
True,
|
|
0.85,
|
|
"explicit_nude",
|
|
"from_camera_config",
|
|
"compact",
|
|
"balanced",
|
|
"",
|
|
branch_options=branch_options,
|
|
)[0]
|
|
return json.loads(nodes["SxCPScenePairOutput"]().build(soft_scene_pose, hard_scene_pose)[7])
|
|
|
|
soft_pose_pairs = [_soft_pose_pair(seed) for seed in (6677, 6678, 6679, 6680)]
|
|
soft_pose_items = {pair.get("softcore_row", {}).get("item") for pair in soft_pose_pairs}
|
|
soft_pose_values = {pair.get("softcore_row", {}).get("pose") for pair in soft_pose_pairs}
|
|
_expect(len(soft_pose_items) == 1, "Softcore branch pose reroll should not change softcore outfit/content")
|
|
_expect(len(soft_pose_values) > 1, "Softcore branch pose reroll did not change softcore pose")
|
|
for expected_seed, pose_pair in zip((6677, 6678, 6679, 6680), soft_pose_pairs):
|
|
soft_seed_config = pose_pair.get("softcore_row", {}).get("seed_config") if isinstance(pose_pair.get("softcore_row"), dict) else {}
|
|
hard_seed_config = pose_pair.get("hardcore_row", {}).get("seed_config") if isinstance(pose_pair.get("hardcore_row"), dict) else {}
|
|
_expect(
|
|
soft_seed_config.get("pose_seed") == expected_seed,
|
|
"Softcore branch pose seed did not reach softcore generator seed config",
|
|
)
|
|
_expect(
|
|
hard_seed_config.get("pose_seed") != expected_seed,
|
|
"Softcore branch pose seed leaked into hardcore generator seed config",
|
|
)
|
|
choice_board_node = nodes["SxCPChoiceBoard"]()
|
|
choice_board_output = choice_board_node.build(
|
|
json.dumps(content_pair),
|
|
"hardcore_position_current",
|
|
"",
|
|
"",
|
|
"auto",
|
|
"auto",
|
|
"Woman A",
|
|
"",
|
|
"",
|
|
)
|
|
choice_result = choice_board_output["result"]
|
|
position_config = json.loads(choice_result[2])
|
|
board_json = json.loads(choice_result[4])
|
|
_expect(position_config.get("enabled"), "Choice Board did not emit active hardcore position config")
|
|
_expect(board_json.get("choices"), "Choice Board did not expose resolved choice rows")
|
|
location_board_output = choice_board_node.build(
|
|
json.dumps(content_pair),
|
|
"none",
|
|
"fixed private test location with repeated room anchors",
|
|
"",
|
|
"auto",
|
|
"auto",
|
|
"Woman A",
|
|
"",
|
|
"",
|
|
)
|
|
location_config = json.loads(location_board_output["result"][0])
|
|
_expect(
|
|
location_config.get("scene_entries", [{}])[0].get("prompt") == "fixed private test location with repeated room anchors",
|
|
"Choice Board did not convert location override into a replace location config",
|
|
)
|
|
|
|
|
|
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")
|
|
direct_trace = direct_row.get("generation_trace")
|
|
_expect(isinstance(direct_trace, dict), "Prompt Builder metadata lost generation_trace")
|
|
_expect(direct_trace.get("branch") == "built_in", "Prompt Builder metadata generation_trace lost branch")
|
|
_expect(direct_trace.get("seed_axes", {}).get("content", {}).get("source") == "main", "Prompt Builder metadata trace lost content seed source")
|
|
_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")
|
|
config_trace = config_row.get("generation_trace")
|
|
_expect(isinstance(config_trace, dict), "Prompt Builder From Configs metadata lost generation_trace")
|
|
_expect(config_trace.get("builder") == "prompt_builder", "Prompt Builder From Configs trace lost builder label")
|
|
_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),
|
|
("builtin_couple_metadata", smoke_builtin_couple_metadata),
|
|
("camera_scene_single", smoke_camera_scene_single),
|
|
("scene_camera_adapter_pov_profile_policy", smoke_scene_camera_adapter_pov_profile_policy),
|
|
("row_camera_policy", smoke_row_camera_policy),
|
|
("config_route_location_theme", smoke_config_route_location_theme),
|
|
("builder_prompt_route_policy", smoke_builder_prompt_route_policy),
|
|
("builder_config_route_policy", smoke_builder_config_route_policy),
|
|
("krea_normal_row_routes", smoke_krea_normal_row_routes),
|
|
("krea_action_details_policy", smoke_krea_action_details_policy),
|
|
("outercourse_action_policy", smoke_outercourse_action_policy),
|
|
("item_axis_policy", smoke_item_axis_policy),
|
|
("krea_row_fields_policy", smoke_krea_row_fields_policy),
|
|
("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),
|
|
("prompt_hygiene_policy", smoke_prompt_hygiene_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_target_policy", smoke_formatter_target_policy),
|
|
("formatter_detail_policy", smoke_formatter_detail_policy),
|
|
("krea_format_route_policy", smoke_krea_format_route_policy),
|
|
("formatter_cast_policy", smoke_formatter_cast_policy),
|
|
("caption_policy", smoke_caption_policy),
|
|
("caption_format_route_policy", smoke_caption_format_route_policy),
|
|
("caption_text_policy", smoke_caption_text_policy),
|
|
("caption_metadata_routes", smoke_caption_metadata_routes),
|
|
("sdxl_presets_policy", smoke_sdxl_presets_policy),
|
|
("sdxl_format_route_policy", smoke_sdxl_format_route_policy),
|
|
("sdxl_tag_policy", smoke_sdxl_tag_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),
|
|
("category_subcategory_matrix", smoke_category_subcategory_matrix),
|
|
("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),
|
|
("krea_anal_axis_compatibility", smoke_krea_anal_axis_compatibility),
|
|
("insta_pair_pov_man", smoke_insta_pair_pov),
|
|
("insta_pair_camera_split", smoke_insta_pair_camera_split),
|
|
("pov_camera_scene", smoke_pov_camera_scene),
|
|
("krea2_pov_pose_variant_catalog", smoke_krea2_pov_pose_variant_catalog),
|
|
("krea2_pov_atlas_variant_prompt_routes", smoke_krea2_pov_atlas_variant_prompt_routes),
|
|
("krea2_pose_variant_catalog_policy", smoke_krea2_pose_variant_catalog_policy),
|
|
("krea2_eval_log_policy", smoke_krea2_eval_log_policy),
|
|
("krea2_prompt_guide_policy", smoke_krea2_prompt_guide_policy),
|
|
("krea2_tuning_report_policy", smoke_krea2_tuning_report_policy),
|
|
("krea2_atlas_refine_manifest_policy", smoke_krea2_atlas_refine_manifest_policy),
|
|
("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_climax_target_policy", smoke_pov_climax_target_policy),
|
|
("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_runtime_contracts", smoke_node_runtime_contracts),
|
|
("node_utility_registration", smoke_node_utility_registration),
|
|
("server_route_payload_policy", smoke_server_route_payload_policy),
|
|
("seed_config_policy", smoke_seed_config_policy),
|
|
("prompt_route_simulation_policy", smoke_prompt_route_simulation_policy),
|
|
("sxcp_mcp_client_cli_policy", smoke_sxcp_mcp_client_cli_policy),
|
|
("watch_prompt_image_folder_cli_policy", smoke_watch_prompt_image_folder_cli_policy),
|
|
("sxcp_prompt_batch_cli_policy", smoke_sxcp_prompt_batch_cli_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_scene_chain_registration", smoke_node_scene_chain_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(
|
|
"--list",
|
|
action="store_true",
|
|
help="List available smoke case names and exit.",
|
|
)
|
|
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.",
|
|
)
|
|
parser.add_argument(
|
|
"--quiet",
|
|
action="store_true",
|
|
help="Suppress passing case lines and print one success summary.",
|
|
)
|
|
args = parser.parse_args(argv)
|
|
if args.list:
|
|
for name, _func in SMOKE_CASES:
|
|
print(name)
|
|
return 0
|
|
selected = set(args.case or [])
|
|
report = SmokeReport(verbose=not args.quiet)
|
|
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)
|
|
if args.quiet and not report.failed:
|
|
print(f"OK: smoke passed ({len(report.passed)} cases).")
|
|
else:
|
|
print(f"\nSummary: {len(report.passed)} passed, {len(report.failed)} failed")
|
|
if report.failed:
|
|
return 1
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|