Add Krea2 ballsucking route scaffold

This commit is contained in:
2026-06-29 10:20:58 +02:00
parent 364c42103b
commit 284c6279e6
6 changed files with 59 additions and 15 deletions
+3 -2
View File
@@ -118,9 +118,10 @@
"atlas_folders": ["ballsucking"], "atlas_folders": ["ballsucking"],
"action_family": "outercourse", "action_family": "outercourse",
"position_keys": ["testicle_sucking", "ballsucking"], "position_keys": ["testicle_sucking", "ballsucking"],
"canonical_geometry": "Low first-person pelvis view: the woman's head is below the shaft at testicle height, mouth and tongue at the balls, with the penis pointing upward above or in front of her face.", "canonical_geometry": "Low first-person pelvis view: the woman bends forward between the viewer's open thighs with her chest low over his pelvis, head below the shaft at testicle height, mouth and tongue at the balls, and the penis pointing upward above or in front of her face.",
"prompt_cues": [ "prompt_cues": [
"woman bends forward and kneels very low between the viewer's open thighs", "woman bends forward and kneels very low between the viewer's open thighs",
"chest low over the viewer's pelvis",
"face is below the viewer's penis at testicle height", "face is below the viewer's penis at testicle height",
"mouth and tongue licking the viewer's balls", "mouth and tongue licking the viewer's balls",
"penis points upward in the lower foreground above her forehead" "penis points upward in the lower foreground above her forehead"
@@ -140,7 +141,7 @@
}, },
"evidence": { "evidence": {
"fixed_seed_tests": [], "fixed_seed_tests": [],
"guide_section": "", "guide_section": "docs/krea2-prompt-guide.md#ballsucking--testicle-sucking",
"notes": "Atlas supports low-head geometry, but this route still needs controlled fixed-seed tests before promotion to proven." "notes": "Atlas supports low-head geometry, but this route still needs controlled fixed-seed tests before promotion to proven."
} }
}, },
+23
View File
@@ -148,6 +148,29 @@ semen` wording for this path before the prompt reaches Krea2.
## POV Outercourse ## POV Outercourse
### Ballsucking / Testicle Sucking
The atlas examples are low first-person pelvis views. The visible partner should
read as bent forward between the viewer's open thighs, with the chest low over
the viewer's pelvis and the face placed below the shaft at testicle height.
Starting from the viewer or using only generic kneeling language can make the
head float too high.
Works better:
- `the woman bends forward and kneels very low between the viewer's open thighs`
- `chest low over the viewer's pelvis`
- `face is below the viewer's penis at testicle height`
- `mouth and tongue licking the viewer's balls`
- `penis points upward in the lower foreground above her forehead`
Avoid:
- `head tucked under the penis shaft` without testicle-height wording
- generic `kneels in front of him`
- making the viewer the main subject before the visible woman is established
- mid-height head placement
### Boobjob / Titjob ### Boobjob / Titjob
The atlas examples are frontal and upright: the visible partner faces the viewer, The atlas examples are frontal and upright: the visible partner faces the viewer,
+2 -2
View File
@@ -42,12 +42,12 @@ def build_outercourse_role_graph(
if action_kind == outercourse_policy.OUTERCOURSE_TESTICLE: if action_kind == outercourse_policy.OUTERCOURSE_TESTICLE:
if man_is_pov: if man_is_pov:
return ( return (
f"{woman} bends forward and kneels very low between the POV viewer's open thighs with her shoulders between his knees, " f"{woman} bends forward and kneels very low between the POV viewer's open thighs with her chest low over the POV viewer's pelvis and shoulders between his knees, "
"her face below the POV viewer's penis at testicle height, mouth and tongue on the POV viewer's balls, " "her face below the POV viewer's penis at testicle height, mouth and tongue on the POV viewer's balls, "
"while his penis points upward in the lower foreground above her forehead." "while his penis points upward in the lower foreground above her forehead."
) )
return ( return (
f"{man} sits with legs apart while {woman} kneels very low between his open thighs with her torso bent forward and shoulders between his knees, " f"{man} sits with legs apart while {woman} kneels very low between his open thighs with her chest low over his pelvis and shoulders between his knees, "
f"{woman}'s face below {man}'s penis at testicle height, mouth and tongue on his balls, while {man}'s penis points upward above her forehead." f"{woman}'s face below {man}'s penis at testicle height, mouth and tongue on his balls, while {man}'s penis points upward above her forehead."
) )
if action_kind == outercourse_policy.OUTERCOURSE_PENIS_LICKING: if action_kind == outercourse_policy.OUTERCOURSE_PENIS_LICKING:
+1 -1
View File
@@ -286,7 +286,7 @@ def pov_hardcore_pose_sentence(
) )
if action_kind == outercourse_policy.OUTERCOURSE_TESTICLE: if action_kind == outercourse_policy.OUTERCOURSE_TESTICLE:
return outercourse_sentence( return outercourse_sentence(
"The woman bends forward and kneels very low between the viewer's open thighs with her shoulders between his knees; " "The woman bends forward and kneels very low between the viewer's open thighs with her chest low over the viewer's pelvis and shoulders between his knees; "
"her face is below the viewer's penis at testicle height, mouth and tongue licking the viewer's balls while his penis points upward in the lower foreground above her forehead" "her face is below the viewer's penis at testicle height, mouth and tongue licking the viewer's balls while his penis points upward in the lower foreground above her forehead"
) )
if action_kind == outercourse_policy.OUTERCOURSE_PENIS_LICKING: if action_kind == outercourse_policy.OUTERCOURSE_PENIS_LICKING:
+2
View File
@@ -1661,6 +1661,8 @@ def run_simulation(seed: int = 3901, *, include_prompts: bool = False) -> dict[s
cases.extend(_pair_reports("insta_pair.penetration", penetration_pair, include_prompts=include_prompts)) cases.extend(_pair_reports("insta_pair.penetration", penetration_pair, include_prompts=include_prompts))
pov_pair = _insta_pair_case(seed + 2, pov=True, position="penis_licking", focus="outercourse_only", family="outercourse") pov_pair = _insta_pair_case(seed + 2, pov=True, position="penis_licking", focus="outercourse_only", family="outercourse")
cases.extend(_pair_reports("insta_pair.pov_outercourse", pov_pair, include_prompts=include_prompts)) cases.extend(_pair_reports("insta_pair.pov_outercourse", pov_pair, include_prompts=include_prompts))
ballsucking_pair = _insta_pair_case(seed + 5, pov=True, position="testicle_sucking", focus="outercourse_only", family="outercourse")
cases.extend(_pair_reports("insta_pair.pov_ballsucking", ballsucking_pair, include_prompts=include_prompts))
coverage_checks = _route_family_coverage_checks(cases) coverage_checks = _route_family_coverage_checks(cases)
axis_checks = _seed_axis_checks(seed + 3) axis_checks = _seed_axis_checks(seed + 3)
pair_seed_checks = _pair_seed_checks(seed + 4) pair_seed_checks = _pair_seed_checks(seed + 4)
+28 -10
View File
@@ -6845,6 +6845,11 @@ def smoke_krea2_pose_variant_catalog_policy() -> None:
handjob["prompt_cues"].append("mutation should not leak") handjob["prompt_cues"].append("mutation should not leak")
clean_handjob = krea2_pose_variant_catalog.get_variant("pov_handjob_upright_centered") clean_handjob = krea2_pose_variant_catalog.get_variant("pov_handjob_upright_centered")
_expect("mutation should not leak" not in clean_handjob.get("prompt_cues", []), "Catalog loader leaked caller mutation") _expect("mutation should not leak" not in clean_handjob.get("prompt_cues", []), "Catalog loader leaked caller mutation")
ballsucking = krea2_pose_variant_catalog.get_variant("pov_ballsucking_low_head")
_expect(
any("chest low over the viewer's pelvis" in str(cue) for cue in ballsucking.get("prompt_cues", [])),
"Ballsucking variant lost low-body pelvis cue",
)
footjob = krea2_pose_variant_catalog.get_variant("pov_footjob_frontal_sole_stroke") footjob = krea2_pose_variant_catalog.get_variant("pov_footjob_frontal_sole_stroke")
_expect(footjob.get("status") == "candidate", "Footjob variant should remain a candidate until fixed-seed evidence exists") _expect(footjob.get("status") == "candidate", "Footjob variant should remain a candidate until fixed-seed evidence exists")
_expect( _expect(
@@ -7084,6 +7089,8 @@ def smoke_krea2_prompt_guide_policy() -> None:
guide = (ROOT / "docs" / "krea2-prompt-guide.md").read_text(encoding="utf-8") guide = (ROOT / "docs" / "krea2-prompt-guide.md").read_text(encoding="utf-8")
_expect("## Climax / Ejaculation Wording" in guide, "Krea2 prompt guide lost climax wording section") _expect("## Climax / Ejaculation Wording" in guide, "Krea2 prompt guide lost climax wording section")
_expect("ejaculates semen" in guide, "Krea2 prompt guide lost explicit semen wording rule") _expect("ejaculates semen" in guide, "Krea2 prompt guide lost explicit semen wording rule")
_expect("### Ballsucking / Testicle Sucking" in guide, "Krea2 prompt guide lost ballsucking section")
_expect("chest low over the viewer's pelvis" in guide, "Krea2 prompt guide lost low-body ballsucking cue")
_expect("## Stronger-Control / Low-Priority Cases" in guide, "Krea2 prompt guide lost stronger-control section") _expect("## Stronger-Control / Low-Priority Cases" in guide, "Krea2 prompt guide lost stronger-control section")
_expect("pov_sixty_nine_close_reversed_oral" in guide, "Krea2 prompt guide lost sixty-nine unstable route") _expect("pov_sixty_nine_close_reversed_oral" in guide, "Krea2 prompt guide lost sixty-nine unstable route")
_expect("hardest" in guide and "low-priority" in guide, "Krea2 prompt guide lost hardest low-priority wording") _expect("hardest" in guide and "low-priority" in guide, "Krea2 prompt guide lost hardest low-priority wording")
@@ -7527,8 +7534,8 @@ def smoke_pov_outercourse_position_routes() -> None:
( (
"pov_outercourse_testicle", "pov_outercourse_testicle",
"testicle_sucking", "testicle_sucking",
("face below the pov viewer's penis at testicle height", "penis points upward"), ("chest low over the pov viewer's pelvis", "face below the pov viewer's penis at testicle height", "penis points upward"),
("face is below the viewer's penis at testicle height", "mouth and tongue licking", "penis points upward"), ("chest low over the viewer's pelvis", "face is below the viewer's penis at testicle height", "mouth and tongue licking", "penis points upward"),
), ),
( (
"pov_outercourse_penis_licking", "pov_outercourse_penis_licking",
@@ -9027,17 +9034,17 @@ def smoke_prompt_route_simulation_policy() -> None:
report = prompt_route_simulation.run_simulation(seed=3901, include_prompts=False) report = prompt_route_simulation.run_simulation(seed=3901, include_prompts=False)
summary = report.get("summary") or {} summary = report.get("summary") or {}
quality = report.get("quality") or {} quality = report.get("quality") or {}
_expect(summary.get("cases") == 14, "Prompt route simulation case count changed unexpectedly") _expect(summary.get("cases") == 16, "Prompt route simulation case count changed unexpectedly")
_expect(summary.get("coverage_checks") == 2, "Prompt route simulation lost family coverage checks") _expect(summary.get("coverage_checks") == 2, "Prompt route simulation lost family coverage checks")
_expect(summary.get("axis_checks") == 6, "Prompt route simulation lost axis check coverage") _expect(summary.get("axis_checks") == 6, "Prompt route simulation lost axis check coverage")
_expect(summary.get("pair_seed_checks") == 7, "Prompt route simulation lost pair seed check coverage") _expect(summary.get("pair_seed_checks") == 7, "Prompt route simulation lost pair seed check coverage")
_expect(summary.get("issues") == 0, f"Prompt route simulation reported issues: {report.get('issues')}") _expect(summary.get("issues") == 0, f"Prompt route simulation reported issues: {report.get('issues')}")
_expect(quality.get("route_cases") == 14, "Prompt route simulation quality summary lost route case count") _expect(quality.get("route_cases") == 16, "Prompt route simulation quality summary lost route case count")
_expect(quality.get("route_issues") == 0, f"Prompt route simulation quality reported route issues: {quality}") _expect(quality.get("route_issues") == 0, f"Prompt route simulation quality reported route issues: {quality}")
_expect(quality.get("check_issues") == 0, f"Prompt route simulation quality reported check issues: {quality}") _expect(quality.get("check_issues") == 0, f"Prompt route simulation quality reported check issues: {quality}")
_expect((quality.get("targets") or {}).get("single", {}).get("cases") == 10, "Prompt route simulation quality lost single target count") _expect((quality.get("targets") or {}).get("single", {}).get("cases") == 10, "Prompt route simulation quality lost single target count")
_expect((quality.get("targets") or {}).get("softcore", {}).get("cases") == 2, "Prompt route simulation quality lost softcore target count") _expect((quality.get("targets") or {}).get("softcore", {}).get("cases") == 3, "Prompt route simulation quality lost softcore target count")
_expect((quality.get("targets") or {}).get("hardcore", {}).get("cases") == 2, "Prompt route simulation quality lost hardcore target count") _expect((quality.get("targets") or {}).get("hardcore", {}).get("cases") == 3, "Prompt route simulation quality lost hardcore target count")
_expect(not quality.get("issue_buckets"), "Prompt route simulation quality should have no issue buckets on clean baseline") _expect(not quality.get("issue_buckets"), "Prompt route simulation quality should have no issue buckets on clean baseline")
_expect(not quality.get("weakest_cases"), "Prompt route simulation quality should have no weak cases on clean baseline") _expect(not quality.get("weakest_cases"), "Prompt route simulation quality should have no weak cases on clean baseline")
cases = {case.get("name"): case for case in report.get("cases") or []} cases = {case.get("name"): case for case in report.get("cases") or []}
@@ -9052,6 +9059,7 @@ def smoke_prompt_route_simulation_policy() -> None:
"hardcore.single.group", "hardcore.single.group",
"hardcore.single.climax", "hardcore.single.climax",
"insta_pair.penetration.hardcore", "insta_pair.penetration.hardcore",
"insta_pair.pov_ballsucking.hardcore",
): ):
_expect(route_name in cases, f"Prompt route simulation lost route family case {route_name}") _expect(route_name in cases, f"Prompt route simulation lost route family case {route_name}")
coverage_checks = {check.get("name"): check for check in report.get("coverage_checks") or []} coverage_checks = {check.get("name"): check for check in report.get("coverage_checks") or []}
@@ -9079,6 +9087,16 @@ def smoke_prompt_route_simulation_policy() -> None:
"penis_licking" in (pov_summary.get("position_keys") or []), "penis_licking" in (pov_summary.get("position_keys") or []),
"Prompt route simulation lost selected outercourse key from position_keys", "Prompt route simulation lost selected outercourse key from position_keys",
) )
ballsucking_hard = cases.get("insta_pair.pov_ballsucking.hardcore") or {}
ballsucking_summary = ballsucking_hard.get("summary") or {}
_expect(
ballsucking_summary.get("position_key") == "testicle_sucking",
"Prompt route simulation should include a dedicated ballsucking/testicle POV route",
)
_expect(
"testicle_sucking" in (ballsucking_summary.get("position_keys") or []),
"Prompt route simulation ballsucking route lost selected testicle_sucking key",
)
axis_checks = {check.get("name"): check for check in report.get("axis_checks") or []} axis_checks = {check.get("name"): check for check in report.get("axis_checks") or []}
for check_name in ( for check_name in (
"seed_axis.locked_determinism", "seed_axis.locked_determinism",
@@ -9144,14 +9162,14 @@ def smoke_prompt_route_simulation_policy() -> None:
sweep_quality = sweep.get("quality") or {} sweep_quality = sweep.get("quality") or {}
_expect(sweep_summary.get("runs") == 3, "Prompt route simulation sweep lost run coverage") _expect(sweep_summary.get("runs") == 3, "Prompt route simulation sweep lost run coverage")
_expect(sweep_summary.get("seeds") == [3901, 4002, 4103], "Prompt route simulation sweep seed sequence changed") _expect(sweep_summary.get("seeds") == [3901, 4002, 4103], "Prompt route simulation sweep seed sequence changed")
_expect(sweep_summary.get("cases") == 42, "Prompt route simulation sweep case count changed") _expect(sweep_summary.get("cases") == 48, "Prompt route simulation sweep case count changed")
_expect(sweep_summary.get("issues") == 0, f"Prompt route simulation sweep reported issues: {sweep.get('issues')}") _expect(sweep_summary.get("issues") == 0, f"Prompt route simulation sweep reported issues: {sweep.get('issues')}")
_expect(sweep_quality.get("route_cases") == 42, "Prompt route simulation sweep quality lost route case count") _expect(sweep_quality.get("route_cases") == 48, "Prompt route simulation sweep quality lost route case count")
_expect(sweep_quality.get("route_issues") == 0, f"Prompt route simulation sweep quality reported route issues: {sweep_quality}") _expect(sweep_quality.get("route_issues") == 0, f"Prompt route simulation sweep quality reported route issues: {sweep_quality}")
_expect(sweep_quality.get("check_issues") == 0, f"Prompt route simulation sweep quality reported check issues: {sweep_quality}") _expect(sweep_quality.get("check_issues") == 0, f"Prompt route simulation sweep quality reported check issues: {sweep_quality}")
_expect((sweep_quality.get("targets") or {}).get("single", {}).get("cases") == 30, "Prompt route simulation sweep quality lost single target count") _expect((sweep_quality.get("targets") or {}).get("single", {}).get("cases") == 30, "Prompt route simulation sweep quality lost single target count")
_expect((sweep_quality.get("targets") or {}).get("softcore", {}).get("cases") == 6, "Prompt route simulation sweep quality lost softcore target count") _expect((sweep_quality.get("targets") or {}).get("softcore", {}).get("cases") == 9, "Prompt route simulation sweep quality lost softcore target count")
_expect((sweep_quality.get("targets") or {}).get("hardcore", {}).get("cases") == 6, "Prompt route simulation sweep quality lost hardcore target count") _expect((sweep_quality.get("targets") or {}).get("hardcore", {}).get("cases") == 9, "Prompt route simulation sweep quality lost hardcore target count")
def smoke_node_camera_registration() -> None: def smoke_node_camera_registration() -> None: