From e028419e6d2175116053d107820cc612ae17fbbf Mon Sep 17 00:00:00 2001 From: Ethanfel Date: Mon, 29 Jun 2026 07:44:03 +0200 Subject: [PATCH] Add Krea2 wand atlas candidate --- categories/krea2_pov_pose_variants.json | 38 ++++++++++++++++++++ docs/krea2-pov-pose-atlas.md | 15 ++++++++ krea2_tuning_report.py | 2 ++ tools/prompt_smoke.py | 48 +++++++++++++++++++++---- 4 files changed, 97 insertions(+), 6 deletions(-) diff --git a/categories/krea2_pov_pose_variants.json b/categories/krea2_pov_pose_variants.json index 2a83f6a..54082cd 100644 --- a/categories/krea2_pov_pose_variants.json +++ b/categories/krea2_pov_pose_variants.json @@ -217,6 +217,44 @@ "notes": "Atlas shows a repeated open-thigh manual-contact POV layout; needs fixed-seed Krea2 tests before promotion to proven." } }, + { + "key": "pov_wand_foreground_tool_contact", + "family": "wand", + "status": "candidate", + "atlas_folders": ["wand"], + "action_family": "toy", + "position_keys": ["wand", "toy_contact", "open_thighs"], + "canonical_geometry": "First-person toy-contact view: the woman reclines or sits back with thighs spread toward the camera, face and torso visible behind the open-leg frame, and the viewer hand holds a wand-style toy from the foreground with the rounded head pressed to the central contact point.", + "prompt_cues": [ + "POV wand toy position", + "woman reclines with thighs spread wide toward the camera", + "viewer hand holds a wand-style toy from the foreground", + "rounded toy head is pressed to the central contact point between her open thighs", + "her face and torso remain visible behind the open-leg frame", + "thighs and knees form the main frame around the foreground tool" + ], + "avoid_cues": [ + "generic toy nearby without contact", + "the woman holding the toy when the foreground viewer hand is intended", + "mouth, foot, or penetration action competing with the toy contact", + "closed legs hiding the contact point", + "toy floating without a visible hand or handle" + ], + "reference_images": [ + "wand/106_wand_.png", + "wand/107_wand_.png", + "wand/108_wand_.png" + ], + "generator_hook": { + "module": "krea_pov_actions.py", + "route_terms": ["wand", "toy", "vibrator"] + }, + "evidence": { + "fixed_seed_tests": [], + "guide_section": "", + "notes": "The wand folder repeats a foreground-hand toy-contact layout with open thighs and the visible partner behind the tool; needs fixed-seed Krea2 tests before promotion to proven." + } + }, { "key": "pov_spread_open_thigh_presentation", "family": "spread", diff --git a/docs/krea2-pov-pose-atlas.md b/docs/krea2-pov-pose-atlas.md index 297586a..53e7074 100644 --- a/docs/krea2-pov-pose-atlas.md +++ b/docs/krea2-pov-pose-atlas.md @@ -28,6 +28,11 @@ catalog variants are proven or pending and which atlas pose folders are still unmapped by the catalog. Unmapped folders include sample pose/control image paths and a suggested candidate key to start the next catalog entry. +The `ready` folder name is misleading for prompt planning: treat it as an +aftermath/open-thigh family with visible fluid at the central contact area, not +as a neutral setup pose. The report therefore suggests an aftermath candidate +key for that folder until a proper catalog variant is added. + ## Inventory | Family | Pose images | Control images | First sample | @@ -118,6 +123,16 @@ torso visible behind the open-leg frame, and the viewer hand entering from the foreground as the contact anchor. Treat `pov_fingering_reclined_open_thighs` as a candidate until it has fixed-seed Krea2 evidence. +### Wand / Toy Contact + +The wand folder repeats a close first-person tool-contact layout: the woman is +reclined or sitting back with thighs spread toward camera, face and torso visible +behind the open-leg frame, and the viewer hand holding a wand-style toy from the +foreground with the rounded head pressed to the central contact point. Treat +`pov_wand_foreground_tool_contact` as a candidate until it has fixed-seed Krea2 +evidence. Keep the visible hand/handle in the wording; otherwise Krea2 may float +the toy or transfer ownership to the visible partner. + ### Spread / Open-Thigh Presentation The spread folder is a setup/presentation family rather than a required contact diff --git a/krea2_tuning_report.py b/krea2_tuning_report.py index ad28d0f..749b9a6 100644 --- a/krea2_tuning_report.py +++ b/krea2_tuning_report.py @@ -144,6 +144,8 @@ def atlas_coverage_summary(atlas_root: str | Path | None = None) -> dict[str, An def _suggested_variant_key(folder_name: str) -> str: + if folder_name.lower() == "ready": + return "pov_ejaculation_aftermath_open_thigh_candidate" normalized = "".join(char if char.isalnum() else "_" for char in folder_name.lower()).strip("_") while "__" in normalized: normalized = normalized.replace("__", "_") diff --git a/tools/prompt_smoke.py b/tools/prompt_smoke.py index 94a7feb..1699663 100644 --- a/tools/prompt_smoke.py +++ b/tools/prompt_smoke.py @@ -6810,6 +6810,11 @@ def smoke_krea2_pose_variant_catalog_policy() -> None: 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}", + ) interaction = krea2_pose_variant_catalog.variant_keys(action_family="interaction") _expect( interaction == ["pov_spread_open_thigh_presentation"], @@ -6845,6 +6850,12 @@ def smoke_krea2_pose_variant_catalog_policy() -> None: 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") == "candidate", "Wand variant should remain a candidate until fixed-seed evidence exists") + _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", + ) 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( @@ -6974,6 +6985,9 @@ def smoke_krea2_tuning_report_policy() -> None: fingering = by_key.get("pov_fingering_reclined_open_thighs") or {} _expect(fingering.get("coverage_state") == "needs_fixed_seed_tests", "Fingering report should need fixed-seed tests") _expect(fingering.get("accepted_evidence_count") == 0, "Fingering report should not have accepted evidence yet") + wand = by_key.get("pov_wand_foreground_tool_contact") or {} + _expect(wand.get("coverage_state") == "needs_fixed_seed_tests", "Wand report should need fixed-seed tests") + _expect(wand.get("accepted_evidence_count") == 0, "Wand report should not have accepted evidence yet") spread = by_key.get("pov_spread_open_thigh_presentation") or {} _expect(spread.get("coverage_state") == "needs_fixed_seed_tests", "Spread report should need fixed-seed tests") _expect(spread.get("accepted_evidence_count") == 0, "Spread report should not have accepted evidence yet") @@ -7009,12 +7023,13 @@ def smoke_krea2_tuning_report_policy() -> None: _expect(reverse_cowgirl_alt.get("accepted_evidence_count") == 0, "Reverse cowgirl alt 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") == 14, "Krea2 tuning report candidate count changed") + _expect(summary.get("status_counts", {}).get("candidate") == 15, "Krea2 tuning report candidate count changed") _expect( summary.get("variants_without_accepted_evidence") == [ "pov_ballsucking_low_head", "pov_footjob_frontal_sole_stroke", "pov_fingering_reclined_open_thighs", + "pov_wand_foreground_tool_contact", "pov_spread_open_thigh_presentation", "pov_blowjob_top_down_vertical_shaft", "pov_blowjob_side_profile_oral", @@ -7036,6 +7051,7 @@ def smoke_krea2_tuning_report_policy() -> None: "pov_ballsucking_low_head", "pov_footjob_frontal_sole_stroke", "pov_fingering_reclined_open_thighs", + "pov_wand_foreground_tool_contact", "pov_spread_open_thigh_presentation", "pov_blowjob_top_down_vertical_shaft", "pov_blowjob_side_profile_oral", @@ -7082,6 +7098,15 @@ def smoke_krea2_tuning_report_policy() -> None: any(str(path).endswith("fingering/103_fingering.png") for path in fingering_plan.get("reference_paths") or []), "Fingering test plan lost atlas reference path", ) + wand_plan = plan_by_key["pov_wand_foreground_tool_contact"] + _expect( + "viewer hand holds a wand-style toy from the foreground" in " ".join(wand_plan.get("prompt_cues") or []), + "Wand test plan lost foreground tool-hold cue", + ) + _expect( + any(str(path).endswith("wand/106_wand_.png") for path in wand_plan.get("reference_paths") or []), + "Wand test plan lost atlas reference path", + ) spread_plan = plan_by_key["pov_spread_open_thigh_presentation"] _expect( "legs raised or knees held wide" in " ".join(spread_plan.get("prompt_cues") or []), @@ -7183,38 +7208,49 @@ def smoke_krea2_tuning_report_policy() -> None: ) with tempfile.TemporaryDirectory() as tmpdir: atlas_root = Path(tmpdir) - for folder in ("doggy", "doggy_control", "custom_pose", "custom_pose_control", "bg", "woman", "doggy_bg"): + for folder in ("doggy", "doggy_control", "custom_pose", "custom_pose_control", "ready", "ready_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_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 False, "Atlas report should expose unmapped ready folder") _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") == 2, "Atlas report should count only pose folders") + _expect(atlas_summary.get("pose_folder_count") == 3, "Atlas report should count only pose folders") _expect(atlas_summary.get("mapped_folder_count") == 1, "Atlas report should count mapped pose folders") - _expect(atlas_summary.get("unmapped_folders") == ["custom_pose"], "Atlas report should identify unmapped pose folders") + _expect(atlas_summary.get("unmapped_folders") == ["custom_pose", "ready"], "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") - custom_gap = gap_plans[0] + _expect([plan.get("folder") for plan in gap_plans] == ["custom_pose", "ready"], "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") + ready_gap = gap_by_folder["ready"] + _expect( + ready_gap.get("suggested_variant_key") == "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") + _expect("pov_ejaculation_aftermath_open_thigh_candidate" in atlas_markdown, "Krea2 tuning report markdown lost ready aftermath suggested key") markdown = krea2_tuning_report.markdown_report() _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_fingering_reclined_open_thighs" in markdown, "Krea2 tuning report markdown lost fingering candidate variant") + _expect("pov_wand_foreground_tool_contact" in markdown, "Krea2 tuning report markdown lost wand 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")