diff --git a/docs/prompt-architecture-improvement-plan.md b/docs/prompt-architecture-improvement-plan.md index c0dc50c..a19854f 100644 --- a/docs/prompt-architecture-improvement-plan.md +++ b/docs/prompt-architecture-improvement-plan.md @@ -244,7 +244,9 @@ Near-term: pairs. - Cover camera-scene preservation through `tools/prompt_smoke.py` for single rows, split soft/hard pair cameras, and POV camera-scene routing. Expand it - next for close foreplay and POV penetration. +- Cover config-node routing through `tools/prompt_smoke.py` for category, cast, + generation profile, seed lock, camera, location theme, and composition config. + Expand it next for close foreplay and POV penetration. Medium-term: @@ -302,8 +304,8 @@ Medium-term: ## Recommended Next Passes -1. Expand `tools/prompt_smoke.py` with close foreplay, POV penetration, and - location-theme fixtures. +1. Expand `tools/prompt_smoke.py` with close foreplay and POV penetration + fixtures. 2. Split Krea action/POV/clothing helpers into separate modules. 3. Extract scene-camera adapters from `prompt_builder.py`. 4. Split `__init__.py` node classes by family after behavior is covered by smoke diff --git a/docs/prompt-pool-routing-map.md b/docs/prompt-pool-routing-map.md index 65106a9..6055bdf 100644 --- a/docs/prompt-pool-routing-map.md +++ b/docs/prompt-pool-routing-map.md @@ -678,6 +678,8 @@ pair metadata through the core Python APIs, then verifies: - Krea2, SDXL, and natural caption routes use metadata instead of text fallback; - SDXL and caption trigger handling keeps one trigger; - negative prompts do not duplicate comma-list items; +- `SxCP Prompt Builder From Configs`-style wiring preserves category, cast, + generation profile, seed lock, camera, location theme, and composition config; - same-room Insta/OF continuity keeps prompt text and `hardcore_row.scene_text` synchronized; - camera-aware coworking scene text survives single-row Krea formatting; diff --git a/tools/prompt_smoke.py b/tools/prompt_smoke.py index 99ac01b..c149b5e 100644 --- a/tools/prompt_smoke.py +++ b/tools/prompt_smoke.py @@ -192,6 +192,15 @@ def _coworking_location_config() -> str: ) +def _classical_library_theme_configs() -> tuple[str, str]: + location_config, composition_config, _summary = pb.build_thematic_location_json( + enabled=True, + combine_mode="replace", + theme="classical_library", + ) + return location_config, composition_config + + def _orbit_camera( *, horizontal_angle: int, @@ -299,6 +308,59 @@ def smoke_camera_scene_single() -> None: _expect_formatter_outputs(row, "camera_scene_single", target="single") +def smoke_config_route_location_theme() -> None: + location_config, composition_config = _classical_library_theme_configs() + row = pb.build_prompt_from_configs( + row_number=1, + start_index=1, + seed=3301, + category_config=pb.build_category_config_json("hardcore_pose", "Foreplay and teasing"), + cast_config=pb.build_cast_config_json("mixed_couple"), + generation_profile=pb.build_generation_profile_json( + profile="hardcore_intense", + trigger_policy="prepend_trigger", + ), + filter_config=pb.build_ethnicity_list_json( + include_french_european=True, + strict_excludes=True, + )["filter_config"], + seed_config=pb.build_seed_lock_config_json( + base_seed=3301, + reroll_axis="pose", + reroll_seed=3302, + ), + camera_config=_orbit_camera( + horizontal_angle=315, + vertical_angle=0, + zoom=5.0, + subject_focus="action", + ), + character_cast=_character_cast(), + hardcore_position_config=_action_filter("foreplay_only"), + location_config=location_config, + composition_config=composition_config, + ) + _expect_custom_row(row, "config_route_location_theme") + _expect(row.get("subcategory") == "Foreplay and teasing", "config route did not preserve requested subcategory") + _expect(row.get("subject_type") == "configured_cast", "config route did not apply character cast") + scene = _expect_text("config_route_location_theme.scene_text", row.get("scene_text"), 20) + composition = _expect_text("config_route_location_theme.composition", row.get("composition"), 10) + camera = _expect_text("config_route_location_theme.camera_directive", row.get("camera_directive"), 20) + _expect("library" in scene.lower() or "bookshelves" in scene.lower(), "location theme did not drive scene") + _expect("books" in composition.lower() or "shelf" in composition.lower() or "library" in composition.lower(), "location theme did not drive composition") + _expect("315-degree front-left quarter view" in camera, "config route did not preserve orbit camera directive") + seed_config = row.get("seed_config") if isinstance(row.get("seed_config"), dict) else {} + _expect(seed_config.get("pose_seed") == 3302, "seed lock did not reroll pose axis") + _expect(seed_config.get("role_seed") == 3302, "seed lock did not reroll role axis") + _expect(row.get("trigger") == "sxcpinup_coloredpencil", "generation profile trigger did not apply") + _expect_trigger_once("config_route_location_theme.prompt", row.get("prompt"), "sxcpinup_coloredpencil") + krea = krea_formatter.format_krea2_prompt("", metadata_json=_json(row), target="single") + prompt = krea.get("krea_prompt") or "" + _expect("library" in prompt.lower() or "bookshelves" in prompt.lower(), "Krea config route lost theme scene") + _expect("315-degree front-left quarter view" in prompt, "Krea config route lost camera directive") + _expect_formatter_outputs(row, "config_route_location_theme", target="single") + + def smoke_hardcore_category_routes() -> None: cast = _character_cast() cases = [ @@ -520,6 +582,7 @@ def smoke_no_expression_fallback() -> None: SMOKE_CASES: list[tuple[str, Callable[[], None]]] = [ ("builtin_single_woman", smoke_builtin_single), ("camera_scene_single", smoke_camera_scene_single), + ("config_route_location_theme", smoke_config_route_location_theme), ("hardcore_category_routes", smoke_hardcore_category_routes), ("insta_pair_same_cast", smoke_insta_pair), ("insta_pair_pov_man", smoke_insta_pair_pov),