diff --git a/categories/krea2_pov_pose_variants.json b/categories/krea2_pov_pose_variants.json index 7324afa..2c79e52 100644 --- a/categories/krea2_pov_pose_variants.json +++ b/categories/krea2_pov_pose_variants.json @@ -291,6 +291,44 @@ "guide_section": "", "notes": "Atlas shows a repeated top-down oral POV with a centered vertical shaft; needs fixed-seed Krea2 tests before promotion to proven." } + }, + { + "key": "pov_blowjob_side_profile_oral", + "family": "blowjob_side", + "status": "candidate", + "atlas_folders": ["blowjob_side"], + "action_family": "oral", + "position_keys": ["reclining_oral", "penis_licking"], + "canonical_geometry": "Side-profile first-person oral view: the viewer reclines with torso or thighs visible in the foreground, the woman leans beside the viewer's pelvis from the side, and her side-facing mouth aligns to the shaft near the lower center of the frame.", + "prompt_cues": [ + "POV side-profile oral position", + "viewer reclines with torso or thighs visible in the foreground", + "woman leans beside the viewer's pelvis from the side", + "her side-facing mouth aligns to the shaft", + "shaft stays near the lower center of the frame", + "side-facing face, jawline, hand support, and mouth contact remain readable" + ], + "avoid_cues": [ + "top-down oral framing", + "front-facing centered face instead of side profile", + "woman standing level with the viewer", + "camera behind the woman", + "hands replacing the mouth as the main oral contact" + ], + "reference_images": [ + "blowjob_side/103_blowjob_side.png", + "blowjob_side/105_blowjob_side.png", + "blowjob_side/29_blowjob_side.png" + ], + "generator_hook": { + "module": "krea_pov_actions.py", + "route_terms": ["blowjob_side", "side-profile oral", "oral"] + }, + "evidence": { + "fixed_seed_tests": [], + "guide_section": "", + "notes": "Atlas shows repeated side-profile first-person oral geometry; needs fixed-seed Krea2 tests before promotion to proven." + } } ] } diff --git a/docs/krea2-pov-pose-atlas.md b/docs/krea2-pov-pose-atlas.md index 05bc520..f567bb5 100644 --- a/docs/krea2-pov-pose-atlas.md +++ b/docs/krea2-pov-pose-atlas.md @@ -134,3 +134,11 @@ lower edge, the shaft is vertical and centered, and the woman kneels below looking upward with mouth and hand aligned to it. Treat `pov_blowjob_top_down_vertical_shaft` as a candidate until it has fixed-seed Krea2 evidence. + +### Blowjob Side + +The `blowjob_side` folder repeats a side-profile first-person oral layout: the +viewer reclines with torso or thighs visible, the woman leans beside the +viewer's pelvis from the side, and her side-facing mouth aligns to the shaft +near the lower center of the frame. Treat `pov_blowjob_side_profile_oral` as a +candidate until it has fixed-seed Krea2 evidence. diff --git a/tools/prompt_smoke.py b/tools/prompt_smoke.py index 0de8f72..55ee9de 100644 --- a/tools/prompt_smoke.py +++ b/tools/prompt_smoke.py @@ -6804,7 +6804,10 @@ def smoke_krea2_pose_variant_catalog_policy() -> None: ) oral = krea2_pose_variant_catalog.variant_keys(action_family="oral") _expect( - oral == ["pov_blowjob_top_down_vertical_shaft"], + oral == [ + "pov_blowjob_top_down_vertical_shaft", + "pov_blowjob_side_profile_oral", + ], f"Krea2 pose-variant oral filtering changed unexpectedly: {oral}", ) handjob = krea2_pose_variant_catalog.get_variant("pov_handjob_upright_centered") @@ -6839,6 +6842,12 @@ def smoke_krea2_pose_variant_catalog_policy() -> None: any("shaft is vertical and centered" in str(cue) for cue in oral_top.get("prompt_cues", [])), "Blowjob top-view variant lost vertical-shaft cue", ) + oral_side = krea2_pose_variant_catalog.get_variant("pov_blowjob_side_profile_oral") + _expect(oral_side.get("status") == "candidate", "Blowjob side variant should remain a candidate until fixed-seed evidence exists") + _expect( + any("woman leans beside the viewer's pelvis" in str(cue) for cue in oral_side.get("prompt_cues", [])), + "Blowjob side variant lost side-profile pelvis 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") @@ -6908,9 +6917,12 @@ def smoke_krea2_tuning_report_policy() -> None: oral_top = by_key.get("pov_blowjob_top_down_vertical_shaft") or {} _expect(oral_top.get("coverage_state") == "needs_fixed_seed_tests", "Blowjob top-view report should need fixed-seed tests") _expect(oral_top.get("accepted_evidence_count") == 0, "Blowjob top-view report should not have accepted evidence yet") + oral_side = by_key.get("pov_blowjob_side_profile_oral") or {} + _expect(oral_side.get("coverage_state") == "needs_fixed_seed_tests", "Blowjob side report should need fixed-seed tests") + _expect(oral_side.get("accepted_evidence_count") == 0, "Blowjob side report should not have accepted evidence yet") summary = krea2_tuning_report.coverage_summary() _expect(summary.get("status_counts", {}).get("proven") == 3, "Krea2 tuning report proven count changed") - _expect(summary.get("status_counts", {}).get("candidate") == 5, "Krea2 tuning report candidate count changed") + _expect(summary.get("status_counts", {}).get("candidate") == 6, "Krea2 tuning report candidate count changed") _expect( summary.get("variants_without_accepted_evidence") == [ "pov_ballsucking_low_head", @@ -6918,6 +6930,7 @@ def smoke_krea2_tuning_report_policy() -> None: "pov_fingering_reclined_open_thighs", "pov_spread_open_thigh_presentation", "pov_blowjob_top_down_vertical_shaft", + "pov_blowjob_side_profile_oral", ], f"Krea2 tuning report missing-evidence set changed: {summary.get('variants_without_accepted_evidence')}", ) @@ -6930,6 +6943,7 @@ def smoke_krea2_tuning_report_policy() -> None: "pov_fingering_reclined_open_thighs", "pov_spread_open_thigh_presentation", "pov_blowjob_top_down_vertical_shaft", + "pov_blowjob_side_profile_oral", ], "Krea2 tuning report next plans changed", ) @@ -6983,6 +6997,15 @@ def smoke_krea2_tuning_report_policy() -> None: any(str(path).endswith("blowjob_top_view/102_blowjob_top_view.png") for path in oral_top_plan.get("reference_paths") or []), "Blowjob top-view test plan lost atlas reference path", ) + oral_side_plan = plan_by_key["pov_blowjob_side_profile_oral"] + _expect( + "woman leans beside the viewer's pelvis" in " ".join(oral_side_plan.get("prompt_cues") or []), + "Blowjob side test plan lost side-profile pelvis cue", + ) + _expect( + any(str(path).endswith("blowjob_side/103_blowjob_side.png") for path in oral_side_plan.get("reference_paths") or []), + "Blowjob side test plan lost atlas reference path", + ) with tempfile.TemporaryDirectory() as tmpdir: atlas_root = Path(tmpdir) for folder in ("doggy", "doggy_control", "custom_pose", "custom_pose_control", "bg", "woman", "doggy_bg"): @@ -7019,6 +7042,7 @@ def smoke_krea2_tuning_report_policy() -> None: _expect("pov_fingering_reclined_open_thighs" in markdown, "Krea2 tuning report markdown lost fingering candidate variant") _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("needs_fixed_seed_tests" in markdown, "Krea2 tuning report markdown lost coverage state") _expect("Prompt cues" in markdown, "Krea2 tuning report markdown lost next-test cue section") _expect("Avoid cues" in markdown, "Krea2 tuning report markdown lost next-test avoid section")