Add typed pair route contracts

This commit is contained in:
2026-06-27 10:49:58 +02:00
parent 2c978c7eab
commit 28612f9d00
7 changed files with 439 additions and 74 deletions
+145
View File
@@ -45,8 +45,10 @@ import krea_cast # noqa: E402
import krea_formatter # noqa: E402
import location_config # noqa: E402
import loop_nodes # noqa: E402
import pair_camera # noqa: E402
import pair_cast # noqa: E402
import pair_clothing # noqa: E402
import pair_rows # noqa: E402
import prompt_builder as pb # noqa: E402
import pov_policy # noqa: E402
import row_normalization # noqa: E402
@@ -2922,6 +2924,148 @@ def smoke_pair_options_policy() -> None:
_expect(pb._insta_of_softcore_category("social_tease") == ("Casual clothes", "Casual clothes / Smart casual"), "softcore category mapping changed")
def smoke_pair_route_policy() -> None:
def _fake_build_prompt(**kwargs: Any) -> dict[str, Any]:
is_hard = kwargs.get("category") == "Hardcore sexual poses"
return {
"category": kwargs.get("category"),
"subcategory": kwargs.get("subcategory"),
"scene_text": "shared test room",
"source_scene_text": "",
"composition": "centered test composition",
"source_composition": "",
"expression": "steady look",
"role_graph": "test role graph",
"item": "test item",
"positive_suffix": "test positive suffix",
"negative_prompt": "test negative",
"prompt": "test hard prompt" if is_hard else "test soft prompt",
"caption": "test hard caption" if is_hard else "test soft caption",
}
pair_options_data = {
"softcore_cast": "solo",
"softcore_expression_enabled": True,
"softcore_expression_intensity": 0.45,
"hardcore_expression_enabled": True,
"hardcore_expression_intensity": 0.85,
"hardcore_detail_density": "balanced",
}
pair_rows_kwargs: dict[str, Any] = {
"row_number": 1,
"start_index": 1,
"seed": 10,
"active_trigger": Trigger,
"parsed_seed_config": {},
"options": pair_options_data,
"ethnicity": "any",
"figure": "random",
"no_plus_women": False,
"no_black": False,
"character_profile": "",
"character_cast": "",
"character_slot_map": {},
"pov_character_labels": [],
"hard_women_count": 1,
"hard_men_count": 1,
"soft_category": "Casual clothes",
"soft_subcategory": "Casual clothes / Smart casual",
"softcore_level_key": "social_tease",
"hardcore_random_subcategory": pb.RANDOM_SUBCATEGORY,
"hardcore_position_config": "",
"location_config": "",
"composition_config": "",
"build_prompt": _fake_build_prompt,
"axis_rng": lambda _config, _axis, seed_value, row_value: random.Random(seed_value + row_value),
"cast_expression_intensity_override": lambda value, _slots, _women, _men, _phase: (value, "input"),
"context_from_character_slot": lambda *_args, **_kwargs: {},
"apply_character_context_to_row": lambda row, context: {**row, **context},
"disable_row_expression": lambda row, source: {**row, "expression_disabled": True, "expression_intensity_source": source},
"slot_softcore_outfit": lambda _slot, _rng: "",
"softcore_outfit": lambda _rng, _level: "test soft outfit",
"softcore_pose": lambda _rng, _level: "test soft pose",
"softcore_item_prompt_label": lambda _level: "Softcore test outfit",
"pov_prompt_directive": lambda labels: "POV directive" if labels else "",
"pov_composition_prompt": lambda composition, labels: f"{composition} for {','.join(labels)}" if labels else str(composition),
}
rows_route = pair_rows.build_insta_pair_rows_result(**pair_rows_kwargs)
rows_legacy = pair_rows.build_insta_pair_rows(**pair_rows_kwargs)
_expect(rows_route.soft_row == rows_legacy["soft_row"], "Typed pair row route should match legacy soft row")
_expect(rows_route.hard_row == rows_legacy["hard_row"], "Typed pair row route should match legacy hard row")
_expect(
rows_route.hard_content_rng.getstate() == rows_legacy["hard_content_rng"].getstate(),
"Typed pair row route should match legacy hard content RNG state",
)
_expect(rows_route.soft_row["item"] == "test soft outfit", "Typed pair row route lost soft outfit override")
_expect(rows_route.hard_row["hardcore_detail_density"] == "balanced", "Typed pair row route lost hard density")
camera_options = {
"hardcore_camera_mode": "same_as_softcore",
"softcore_camera_mode": "standard",
"camera_detail": "compact",
"softcore_cast": "same_as_hardcore",
"continuity": "same_creator_same_room",
}
def _camera_config_with_mode(source: Any, mode: str) -> dict[str, Any]:
parsed = dict(source or {})
parsed["camera_mode"] = mode
return parsed
def _camera_directive(config: dict[str, Any]) -> tuple[str, dict[str, Any]]:
return f"{config.get('camera_mode')} camera", config
def _camera_rows() -> tuple[dict[str, Any], dict[str, Any]]:
return (
{"scene_text": "soft room", "composition": "soft composition"},
{"scene_text": "hard room", "composition": "hard composition"},
)
camera_common = {
"options": camera_options,
"camera_config": {"base": "camera"},
"softcore_camera_config": None,
"hardcore_camera_config": None,
"hard_women_count": 1,
"hard_men_count": 1,
"pov_character_labels": [],
"camera_detail_choices": ("compact",),
"camera_config_with_mode": _camera_config_with_mode,
"camera_directive": _camera_directive,
"apply_contextual_composition": lambda row, subject_kind: {**row, "subject_kind": subject_kind},
"contextual_composition_prompt": lambda scene, composition, subject_kind: f"{subject_kind}: {scene}: {composition}",
"composition_prompt": lambda composition: f"Framed as {composition}",
"camera_scene_directive_for_context": lambda _scene, _composition, config, _pov, subject: (f"{subject} scene directive", config),
}
soft_row, hard_row = _camera_rows()
camera_route = pair_camera.resolve_insta_pair_camera_result(soft_row=soft_row, hard_row=hard_row, **camera_common)
soft_row, hard_row = _camera_rows()
camera_legacy = pair_camera.resolve_insta_pair_camera(soft_row=soft_row, hard_row=hard_row, **camera_common)
_expect(camera_route.as_dict() == camera_legacy, "Typed pair camera route should match legacy dict route")
_expect(camera_route.hard_scene == "soft room", "Typed pair camera route lost same-room continuity")
_expect(camera_route.hard_camera_sentence.startswith("Camera control:"), "Typed pair camera route lost hard camera sentence")
clothing_common = {
"hard_row": {
"role_graph": "the man thrusts his penis into the woman",
"item": "penetrative test item",
},
"mode": "explicit_nude",
"softcore_outfit": "test lingerie",
"character_hardcore_clothing_entries": [],
"men_count": 0,
"pov_labels": [],
"rng": random.Random(1),
"continuity_map": pb.INSTA_OF_HARDCORE_CLOTHING_CONTINUITY,
"choose": lambda _rng, pool: pool[0],
}
clothing_route = pair_clothing.resolve_hardcore_pair_clothing_result(**clothing_common)
clothing_legacy = pair_clothing.resolve_hardcore_pair_clothing(**clothing_common)
_expect(clothing_route.as_dict() == clothing_legacy, "Typed pair clothing route should match legacy dict route")
_expect(clothing_route.woman_access == "lower", "Typed pair clothing route lost lower-access detection")
_expect(clothing_route.requires_body_exposure_scene is True, "Typed pair clothing route lost exposure-scene flag")
def _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")
@@ -4823,6 +4967,7 @@ SMOKE_CASES: list[tuple[str, Callable[[], None]]] = [
("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),
("insta_pair_same_cast", smoke_insta_pair),
("krea_pair_clothing_state", smoke_krea_pair_clothing_state),
("insta_pair_pov_man", smoke_insta_pair_pov),