diff --git a/docs/prompt-architecture-improvement-plan.md b/docs/prompt-architecture-improvement-plan.md index 305f372..73e34d5 100644 --- a/docs/prompt-architecture-improvement-plan.md +++ b/docs/prompt-architecture-improvement-plan.md @@ -241,8 +241,10 @@ Near-term: - Add final prose hygiene already done through `prompt_hygiene.py`. - Add smoke coverage through `tools/prompt_smoke.py` for metadata-driven Krea2 formatting across built-in rows, hardcore rows, same-cast pairs, and POV - pairs. Expand it next for close foreplay, POV penetration, and camera-scene - preservation. + 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. Medium-term: @@ -300,8 +302,8 @@ Medium-term: ## Recommended Next Passes -1. Expand `tools/prompt_smoke.py` with camera-scene, explicit nude, and - different-camera pair fixtures. +1. Expand `tools/prompt_smoke.py` with close foreplay, POV penetration, and + location-theme fixtures. 2. Split Krea action/POV/clothing helpers into separate modules. 3. Add category JSON pool reference validation to `tools/prompt_map_audit.py`. 4. Extract scene-camera adapters from `prompt_builder.py`. diff --git a/docs/prompt-pool-routing-map.md b/docs/prompt-pool-routing-map.md index 67d5245..8e2c735 100644 --- a/docs/prompt-pool-routing-map.md +++ b/docs/prompt-pool-routing-map.md @@ -675,6 +675,11 @@ pair metadata through the core Python APIs, then verifies: - negative prompts do not duplicate comma-list items; - same-room Insta/OF continuity keeps prompt text and `hardcore_row.scene_text` synchronized; +- camera-aware coworking scene text survives single-row Krea formatting; +- softcore and hardcore pair rows can carry different camera configs without + collapsing to the same camera phrase; +- POV camera-scene directives suppress normal third-person camera text while + preserving first-person spatial layout; - expression-disabled rows do not fall back to generated expression text. ## Editing Cheatsheet diff --git a/tools/prompt_smoke.py b/tools/prompt_smoke.py index e6703e4..99ac01b 100644 --- a/tools/prompt_smoke.py +++ b/tools/prompt_smoke.py @@ -179,6 +179,44 @@ def _action_filter(focus: str) -> str: return pb.build_hardcore_action_filter_json(focus=focus, **kwargs) +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 _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, @@ -189,6 +227,9 @@ def _prompt_row( women_count: int = 1, men_count: int = 1, hardcore_position_config: str = "", + camera_config: str | dict[str, Any] | None = "", + location_config: str | dict[str, Any] | None = "", + composition_config: str | dict[str, Any] | None = "", ) -> dict[str, Any]: row = pb.build_prompt( category=category, @@ -215,6 +256,9 @@ def _prompt_row( expression_enabled=True, expression_intensity=0.6, hardcore_position_config=hardcore_position_config, + camera_config=camera_config, + location_config=location_config, + composition_config=composition_config, ) _expect_row_base(row, name) return row @@ -227,6 +271,34 @@ def smoke_builtin_single() -> None: _expect_formatter_outputs(row, "builtin_single_woman", target="single") +def smoke_camera_scene_single() -> None: + row = _prompt_row( + name="camera_scene_single", + category="woman", + subcategory="random", + seed=1051, + men_count=0, + camera_config=_orbit_camera( + horizontal_angle=45, + vertical_angle=-30, + zoom=5.0, + subject_focus="environment", + ), + location_config=_coworking_location_config(), + ) + scene_directive = _expect_text("camera_scene_single.camera_scene_directive", row.get("camera_scene_directive"), 40) + camera_directive = _expect_text("camera_scene_single.camera_directive", row.get("camera_directive"), 20) + _expect("Coworking camera layout" in scene_directive, "single camera-scene adapter did not identify coworking layout") + _expect("front-right quarter view" in scene_directive, "single camera scene missed orbit direction") + _expect("low-angle shot" in scene_directive, "single camera scene missed orbit elevation") + _expect("45-degree front-right quarter view" in camera_directive, "single camera directive missed custom orbit prompt") + krea = krea_formatter.format_krea2_prompt("", metadata_json=_json(row), target="single") + prompt = krea.get("krea_prompt") or "" + _expect("Coworking camera layout" in prompt, "Krea single prompt lost camera-scene directive") + _expect("45-degree front-right quarter view" in prompt, "Krea single prompt lost camera directive") + _expect_formatter_outputs(row, "camera_scene_single", target="single") + + def smoke_hardcore_category_routes() -> None: cast = _character_cast() cases = [ @@ -303,6 +375,9 @@ def smoke_insta_pair() -> None: ) _expect_pair(pair, "insta_pair_same_cast") _expect(pair["softcore_row"].get("scene_text") == pair["hardcore_row"].get("scene_text"), "pair scene continuity broke") + clothing_state = _clean_key(pair.get("hardcore_clothing_state")) + _expect("body is fully exposed" in clothing_state, "explicit nude pair should keep body exposure state") + _expect("teaser outfit detail" not in clothing_state, "explicit nude pair should not repeat softcore outfit detail") def smoke_insta_pair_pov() -> None: @@ -330,6 +405,92 @@ def smoke_insta_pair_pov() -> None: _expect("viewer" in prompt.lower(), "POV Krea prompt should mention viewer perspective") +def smoke_insta_pair_camera_split() -> None: + soft_camera = _orbit_camera( + horizontal_angle=45, + vertical_angle=-30, + zoom=5.0, + subject_focus="environment", + ) + hard_camera = _orbit_camera( + horizontal_angle=135, + vertical_angle=30, + zoom=8.0, + subject_focus="action", + ) + pair = pb.build_insta_of_pair( + row_number=1, + start_index=1, + seed=2251, + ethnicity="any", + figure="random", + no_plus_women=False, + no_black=False, + trigger=Trigger, + prepend_trigger_to_prompt=True, + options_json=_insta_options( + softcore_camera_mode="from_camera_config", + hardcore_camera_mode="from_camera_config", + camera_detail="compact", + ), + character_cast=_character_cast(), + hardcore_position_config=_action_filter("penetration_only"), + location_config=_coworking_location_config(), + softcore_camera_config=soft_camera, + hardcore_camera_config=hard_camera, + ) + _expect_pair(pair, "insta_pair_camera_split") + soft_scene = _expect_text("insta_pair_camera_split.soft_camera_scene", pair.get("softcore_camera_scene_directive"), 40) + hard_scene = _expect_text("insta_pair_camera_split.hard_camera_scene", pair.get("hardcore_camera_scene_directive"), 40) + _expect("front-right quarter view" in soft_scene, "soft camera scene missed soft orbit direction") + _expect("back-right quarter view" in hard_scene, "hard camera scene missed hard orbit direction") + _expect("low-angle shot" in soft_scene, "soft camera scene missed soft elevation") + _expect("elevated shot" in hard_scene, "hard camera scene missed hard elevation") + _expect("front-right quarter view" in str(pair.get("softcore_camera_directive")), "soft pair camera directive was not preserved") + _expect("back-right quarter view" in str(pair.get("hardcore_camera_directive")), "hard pair camera directive was not preserved") + krea = krea_formatter.format_krea2_prompt("", metadata_json=_json(pair), target="auto") + _expect("front-right quarter view" in (krea.get("krea_softcore_prompt") or ""), "Krea soft pair lost soft camera geometry") + _expect("back-right quarter view" in (krea.get("krea_hardcore_prompt") or ""), "Krea hard pair lost hard camera geometry") + + +def smoke_pov_camera_scene() -> None: + pair = pb.build_insta_of_pair( + row_number=1, + start_index=1, + seed=2261, + ethnicity="any", + figure="random", + no_plus_women=False, + no_black=False, + trigger=Trigger, + prepend_trigger_to_prompt=True, + options_json=_insta_options( + softcore_camera_mode="from_camera_config", + hardcore_camera_mode="from_camera_config", + camera_detail="compact", + ), + character_cast=_character_cast(pov_man=True), + hardcore_position_config=_action_filter("oral_only"), + location_config=_coworking_location_config(), + hardcore_camera_config=_orbit_camera( + horizontal_angle=135, + vertical_angle=30, + zoom=8.0, + subject_focus="action", + ), + ) + _expect_pair(pair, "pov_camera_scene") + hard_row = pair.get("hardcore_row") or {} + _expect(not hard_row.get("camera_directive"), "POV hard row should suppress normal camera directive") + scene_directive = _expect_text("pov_camera_scene.hard_camera_scene", hard_row.get("camera_scene_directive"), 40) + _expect("from POV" in scene_directive, "POV camera scene should be marked as first-person") + _expect("not in the lower foreground" in scene_directive, "POV camera scene should keep location anchors out of lower foreground") + krea = krea_formatter.format_krea2_prompt("", metadata_json=_json(pair), target="hardcore") + prompt = krea.get("krea_prompt") or "" + _expect("from POV" in prompt, "Krea POV prompt lost camera-scene directive") + _expect("Camera:" not in prompt, "Krea POV prompt should not emit normal third-person camera directive") + + def smoke_no_expression_fallback() -> None: cast = pb.build_character_slot_json( subject_type="woman", @@ -358,9 +519,12 @@ 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), ("hardcore_category_routes", smoke_hardcore_category_routes), ("insta_pair_same_cast", smoke_insta_pair), ("insta_pair_pov_man", smoke_insta_pair_pov), + ("insta_pair_camera_split", smoke_insta_pair_camera_split), + ("pov_camera_scene", smoke_pov_camera_scene), ("expression_disabled", smoke_no_expression_fallback), ]