diff --git a/categories/krea2_pov_pose_variants.json b/categories/krea2_pov_pose_variants.json index 35a26cb..c5eb763 100644 --- a/categories/krea2_pov_pose_variants.json +++ b/categories/krea2_pov_pose_variants.json @@ -118,16 +118,22 @@ "atlas_folders": ["ballsucking"], "action_family": "outercourse", "position_keys": ["testicle_sucking", "ballsucking"], - "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.", + "canonical_geometry": "Low first-person pelvis view: the woman stays low beside or between the viewer's open thighs, with cheek/thigh proximity, the scrotum as the mouth surface, scrotal skin as the nearest mouth surface, and testicles resting across her open lips while both testicles rest against her tongue from below as the accepted partial target.", "prompt_cues": [ "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" + "low side-pelvis POV", + "face is the closest visible partner part", + "cheek against the viewer's inner thigh", + "scrotum is the mouth surface", + "scrotal skin is the nearest mouth surface", + "testicles resting across her open lips while her tongue cups them from below", + "both testicles rest against her tongue from below", + "viewer abdomen and inner thighs frame the close foreground" ], "avoid_cues": [ "head tucked under the penis shaft without testicle-height wording", + "repeating shaft/hand-on-shaft wording before scrotum/testicle contact is established", "viewer first as the main subject", "mid-height head placement" ], @@ -140,26 +146,28 @@ "route_terms": ["testicle_sucking", "balls licking", "testicle"] }, "evidence": { - "fixed_seed_tests": [], + "fixed_seed_tests": ["238365845574312", "1212121212", "5757575757", "6262626262", "9797979797", "9898989898", "5959595959", "6060606060", "6161616161", "7171717171", "7272727272"], "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": "Fifty-probe threshold search accepted tongue/lips on testicles as a partial improvement over baseline shaft/glans collapse; generator carried the side-low partial axis provisionally. Fresh seed 6262626262 then showed open-lips scrotum-surface wording on turns 252 and 258 improved target contact over the generated-route controls 250 and 256. Fresh seed 9797979797 repeated the scrotal-skin target-object branch on turns 288 and 293, with scrotal skin as the nearest mouth surface and both testicles resting against tongue from below. Fresh seed 9898989898 validated the patched generated route on turns 296 and 297, preserving side-low cheek/thigh geometry while keeping scrotum/testicles at the tongue/lip contact. Fresh seed 5959595959 tested lip-oval, sideways mouth pocket, and chin-pelvis upward seal wording across three women; all branches kept some low-pelvis geometry but collapsed back toward shaft/glans contact, so record it as a weak case. Fresh seed 6060606060 tested foreground occlusion, under-scrotum tongue shelf, and hand-guided scrotum wording; every branch still became shaft-centered or hand/shaft-dominant, so keep the route candidate and do not patch those axes. Fresh seed 6161616161 tested exact mouth-sucking, single-testicle, hanging-balls-below-shaft, side-mouth-wrap, and chin-pelvis lower-mouth wording across three women; generated-route controls stayed the best repeated partials on turns 331 and 337, side-mouth and chin-pelvis branches produced isolated useful partials on turns 335 and 348, and the rest collapsed back to shaft/glans contact. Fresh seed 7171717171 tested flat pelvis-valley, thigh-tunnel, pubic-hair mouth-line, low-cushion chin-anchor, and pelvis-edge target-first wording across three women; flat pelvis-valley repeated a better viewer-flat body plane on turns 350, 356, and 362 but stayed shaft-centered, while the cushion and pelvis-edge branches drifted into wrong open-thigh/presentation geometry. Fresh seed 7272727272 tested hybrid flat-valley scrotal-skin, valley-floor open-lips, upper-frame shaft lower-scrotum, cropped upper-shaft valley-mouth, and side-low flat-valley wording; the flat-valley branch repeated the body plane on turns 368, 374, and 380 but stayed shaft-centered, and side-low flat-valley gave only look hints. Stop text-only expansion for now: do not patch those hybrid axes. The provisional generator route uses scrotum-as-mouth-surface, testicles resting across open lips, and scrotal-skin nearest-surface wording while staying candidate." } }, { "key": "pov_footjob_frontal_sole_stroke", "family": "footjob", - "status": "candidate", + "status": "proven", "atlas_folders": ["footjob"], "action_family": "outercourse", "position_keys": ["footjob"], - "canonical_geometry": "Frontal first-person footjob view: viewer reclines with thighs framing the lower foreground, penis upright near the center, and the woman sits opposite with both soles and toes pressing around the shaft while her body and face stay behind the feet.", + "canonical_geometry": "Frontal first-person footjob view: viewer reclines with thighs framing the lower foreground while the woman sits opposite with two large overlapping soles dominating the lower center foreground, inner arches pressing inward around the upright shaft, toes curled around both edges, a narrow visible strip of shaft and glans rising between the compressed feet, and her body and face behind the feet.", "prompt_cues": [ "POV footjob position", "viewer reclines with thighs framing the lower foreground", "woman sits opposite facing him with legs open toward the camera", - "both soles press around the upright penis", - "toes curl around the shaft as the feet stroke from base toward glans", - "woman's body and face remain visible behind her feet" + "two large overlapping soles dominate the lower center foreground", + "inner arches press inward from both sides around the upright shaft", + "toes curl around both edges", + "narrow visible strip of shaft and glans rises between the compressed feet", + "woman's face and torso stay visible behind the large foreground feet" ], "avoid_cues": [ "generic foot contact without both soles around the shaft", @@ -176,9 +184,9 @@ "route_terms": ["footjob", "foot job", "feet stroking"] }, "evidence": { - "fixed_seed_tests": [], - "guide_section": "", - "notes": "Small atlas family with consistent frontal sole-contact geometry; needs fixed-seed Krea2 tests before promotion to proven." + "fixed_seed_tests": ["238365845574312", "3434343434", "6868686868", "7373737373"], + "guide_section": "docs/krea2-prompt-guide.md#footjob", + "notes": "Same-seed two-woman expansions repeated the two-sole clamp as a provisional generator improvement over valid baselines; seed 6868686868 showed overlapping soles plus a narrow visible shaft/glans strip is more reliable than generic large-sole wording. Fresh seed 7373737373 then repeated the generated overlapping-sole/narrow-strip route across two women on turns 264 and 267, with tight center-gap repeats on turns 265 and 268. Promote the default route to proven and keep cross-foot side press as an alternate branch." } }, { @@ -221,16 +229,17 @@ { "key": "pov_wand_foreground_tool_contact", "family": "wand", - "status": "candidate", + "status": "proven", "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.", + "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 single continuous teal wand-style massager from the foreground with the rounded bulb head pressed flat to the central contact point.", "prompt_cues": [ "POV wand toy position", "woman reclines with thighs spread wide toward the camera", + "single continuous teal wand-style massager is the largest lower-frame object", "viewer hand holds a wand-style toy from the foreground", - "rounded toy head is pressed to the central contact point between her open thighs", + "rounded bulb head presses flat to her vulva and clit as the central contact point", "her face and torso remain visible behind the open-leg frame", "thighs and knees form the main frame around the foreground tool" ], @@ -251,9 +260,9 @@ "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." + "fixed_seed_tests": ["246813579", "8642086420", "7979797979"], + "guide_section": "docs/krea2-prompt-guide.md#wand-toy-contact", + "notes": "The teal lower-right single-continuous-wand axis repeated across two women on sampler seeds 8642086420 and 7979797979 and validated through generated-route turns 197, 234, and 238. The pale upper-left wand remains a useful alternate branch; oversized bulb wording can hide contact." } }, { @@ -268,7 +277,7 @@ "POV post-ejaculation open-thigh display pose", "woman reclines or sits back facing the viewer with thighs spread open", "thick semen or fluid is visible around the exposed pussy or anal opening", - "the body is still after ejaculation rather than actively thrusting", + "the body stays still after ejaculation", "viewer body cue or recently withdrawn foreground cue stays near the lower edge", "her face and torso remain visible behind the open-leg frame", "thighs and knees frame the wet aftermath detail without hiding it" @@ -291,9 +300,9 @@ "route_terms": ["post-ejaculation open-thigh display", "thick visible semen or fluid", "open thighs"] }, "evidence": { - "fixed_seed_tests": [], - "guide_section": "", - "notes": "The ready folder is a post-ejaculation open-thigh display pose with thick visible fluid around the exposed opening, not a neutral ready/setup pose; needs fixed-seed Krea2 tests before promotion to proven." + "fixed_seed_tests": ["1123581321"], + "guide_section": "docs/krea2-prompt-guide.md#ready--post-ejaculation-open-thigh-display", + "notes": "The ready folder is a post-ejaculation open-thigh display pose with thick visible fluid around the exposed opening, not a neutral ready/setup pose. First fixed-seed evidence on source 52 was mirrored into the generator as a provisional improvement; repeat before promotion to proven." } }, { @@ -328,9 +337,9 @@ "route_terms": ["spread", "open thighs", "legs spread"] }, "evidence": { - "fixed_seed_tests": [], - "guide_section": "", - "notes": "Atlas shows a repeated open-thigh presentation/setup pose; needs fixed-seed Krea2 tests before promotion to proven." + "fixed_seed_tests": ["3141592653"], + "guide_section": "docs/krea2-prompt-guide.md#spread--open-thigh-presentation", + "notes": "Same-seed A/B on source 50 and 47 showed raised-knee V-frame and hand-on-knee hierarchy improves over generic spread wording. Mirrored into the generator as a provisional improvement; repeat on another seed before promotion to proven." } }, { @@ -381,22 +390,25 @@ "status": "candidate", "atlas_folders": ["blowjob_top_view"], "action_family": "oral", - "position_keys": ["reclining_oral", "penis_licking"], - "canonical_geometry": "Top-down first-person oral view: the viewer looks down from chest or pelvis height, viewer torso or thighs stay at the lower edge, shaft is vertical and centered in the foreground, and the woman kneels below looking upward with mouth and hand aligned to the shaft.", + "position_keys": ["kneeling", "top_down_oral"], + "canonical_geometry": "Nadir-angle standing male POV top-view oral view: the viewer looks almost straight down from his torso toward the floor, nearby floor plane dominates the image, the viewer abdomen, shorts, thighs, and feet frame the lower foreground, the shaft is a short centered vertical column, and one woman kneels directly below between his feet with hair crown, forehead, shoulders, hands, knees, mouth, and shaft alignment visible from above.", "prompt_cues": [ - "POV top-down oral position", - "viewer looks down from chest or pelvis height", - "viewer torso or thighs stay at the lower edge", - "shaft is vertical and centered in the foreground", - "woman kneels below the viewer looking upward", - "her mouth and hand align to the centered shaft" + "nadir-angle standing male POV top-view oral position", + "viewer looks almost straight down from his torso toward the floor", + "nearby carpet/floor plane dominates the image", + "viewer abdomen, shorts, thighs, and feet frame the lower foreground", + "shaft is a short centered vertical column", + "one woman kneels directly below the viewer between his feet", + "hair crown, forehead, shoulders, hands, and knees are visible from above", + "desk legs, chair wheels, carpet texture, and floor seams act as top-down office anchors" ], "avoid_cues": [ "side-view oral framing", "woman standing level with the viewer", "shaft angled sideways or cropped away from the mouth", "hands replacing the mouth as the main oral contact", - "camera placed behind the woman instead of above the viewer" + "camera placed behind the woman instead of above the viewer", + "literal plumb-line or map wording that renders as drawn graphics" ], "reference_images": [ "blowjob_top_view/102_blowjob_top_view.png", @@ -405,36 +417,43 @@ ], "generator_hook": { "module": "krea_pov_actions.py", - "route_terms": ["blowjob_top_view", "top-down oral", "oral"] + "route_terms": ["kneeling oral", "top-down oral", "oral"] }, "evidence": { - "fixed_seed_tests": [], - "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." + "fixed_seed_tests": ["4242424242"], + "guide_section": "docs/krea2-prompt-guide.md#blowjob-top-view--overhead-vertical-shaft", + "notes": "Same-sampler source 46/47 A/B showed that top-down oral hierarchy tightens hand-at-base support and centered shaft-to-mouth alignment over generic kneeling oral. A follow-up axis loop on the same seed showed that generic steep-overhead wording can still feel horizontal, while nadir-angle standing male POV plus a dominating nearby floor plane, one woman directly between the viewer's feet, top-down office anchors, and a short centered vertical shaft column gives the strongest atlas-like verticality. Avoid plumb-line/map wording because Krea2 can literalize it as drawn graphics. Keep candidate until another source or seed repeats the nadir-angle axis." } }, { "key": "pov_blowjob_side_profile_oral", "family": "blowjob_side", - "status": "candidate", + "status": "proven", "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.", + "position_keys": ["side_lying", "reclining_oral", "penis_licking"], + "canonical_geometry": "Side-profile first-person oral body-line view: the male viewer's abdomen, navel, pelvis, and near thigh create the broad lower-frame foreground surface, the adult male viewer's own torso starts at the lower edge and runs diagonally into the lower-right foreground, the woman enters laterally from the left edge beside his hip, and her side-facing mouth plus hand contact align to the shaft at the male abdomen line.", "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" + "POV side-profile oral body-line position", + "male viewer's abdomen, navel, pelvis, and near thigh create a broad horizontal body surface", + "adult male viewer's own torso starts at the lower edge and runs diagonally into the lower-right foreground", + "navel, abdomen hair, pelvis, and near thigh mark the camera owner's body", + "woman enters laterally from the left edge beside his hip", + "cheek and jaw stay in profile", + "mouth on the shaft at the male abdomen line", + "lips touching the shaft at the male abdomen line", + "mouth-to-shaft contact is the nearest facial detail", + "hand around the base under her lips", + "shoulder and torso trail sideways along the edge" ], "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" + "hands replacing the mouth as the main oral contact", + "pure male-body-axis wording that exposes the male as a photographed subject", + "transferring the central body surface to the woman" ], "reference_images": [ "blowjob_side/103_blowjob_side.png", @@ -443,12 +462,12 @@ ], "generator_hook": { "module": "krea_pov_actions.py", - "route_terms": ["blowjob_side", "side-profile oral", "oral"] + "route_terms": ["side_lying", "side-lying oral", "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." + "fixed_seed_tests": ["5656565656", "9753197531", "9595959595", "9696969696", "5858585858"], + "guide_section": "docs/krea2-prompt-guide.md#blowjob-side-profile--side-phone-weak-case", + "notes": "Seed 5656565656 first produced attractive side-phone / external side-camera oral compositions across source 46 and 47, but not valid POV evidence. A later source-46 candidate with explicit adult-male foreground ownership recovered a more atlas-like first-person body-line view, while a related source-47 body-axis candidate failed by transferring the central body surface to the woman. Seed 9753197531 then repeated the lateral-edge body-line wording across two women. Generated-route turn 207 showed the route also needs lips-touching and mouth-to-shaft-contact priority to keep the mouth from floating above the shaft. Seed 9595959595 repeated the lower-right torso anchor on turns 279 and 283 across two visible women, improving camera-owner torso ownership over a control that could expose the male as a photographed side subject. Seed 9696969696 generated-route validation repeated the patched route on turns 284 and 285, keeping lower-right own-body foreground, profile mouth contact, and office depth across two visible women. Seed 5858585858 added a three-woman generated-route repeat on turns 298, 301, and 304; all controls preserved the patched camera-owner lower-right body plane, lateral profile entry, mouth contact at the abdomen line, and office depth. Promote the generated side-profile POV hierarchy to proven while keeping side-camera-style self-body crop wording as a look branch rather than the default." } }, { @@ -484,9 +503,9 @@ "route_terms": ["blowjob_laying", "prone frontal oral", "oral"] }, "evidence": { - "fixed_seed_tests": [], - "guide_section": "", - "notes": "Atlas shows repeated prone, front-facing first-person oral geometry; needs fixed-seed Krea2 tests before promotion to proven." + "fixed_seed_tests": ["6767676767"], + "guide_section": "docs/krea2-prompt-guide.md#blowjob-laying-frontal--wide-v-frame", + "notes": "Seed 6767676767 improved source 46 and 50 with a wide symmetrical V-frame, lower abdomen near-edge anchor, torso stretched low and horizontal between the viewer's thighs, hands at the base, and centered mouth-to-shaft contact. Baselines were already strong but read more raised-hips or all-fours than prone belly-down, so keep the route candidate until another seed repeats the low-horizontal body improvement." } }, { @@ -495,15 +514,15 @@ "status": "candidate", "atlas_folders": ["blowjob_sitting"], "action_family": "oral", - "position_keys": ["reclining_oral", "penis_licking"], + "position_keys": ["reclining_oral", "penis_licking", "blowjob_sitting"], "canonical_geometry": "Upright seated first-person oral view: the viewer reclines with open thighs framing the lower foreground, the woman sits upright between the viewer's open thighs, and her close front-facing mouth aligns to a vertical shaft centered between the viewer's legs.", "prompt_cues": [ "POV upright sitting oral position", "viewer reclines with open thighs framing the lower foreground", - "woman sits upright between the viewer's open thighs", - "her shoulders and face stay vertical and close to the camera", + "woman sits low between the viewer's open thighs with torso upright behind the action", + "her face lowers close to the exact center contact point", "vertical shaft centered between the viewer's legs", - "her mouth aligns to the centered shaft with hands low near the base" + "her open mouth covers the centered tip with hands wrapped low at the base" ], "avoid_cues": [ "prone belly-down oral framing", @@ -522,9 +541,9 @@ "route_terms": ["blowjob_sitting", "upright sitting oral", "oral"] }, "evidence": { - "fixed_seed_tests": [], - "guide_section": "", - "notes": "Atlas contains some top-view outliers, but the named sitting files show repeated upright, front-facing first-person oral geometry; needs fixed-seed Krea2 tests before promotion to proven." + "fixed_seed_tests": ["7878787878"], + "guide_section": "docs/krea2-prompt-guide.md#blowjob-sitting-upright--low-mouth-contact", + "notes": "Seed 7878787878 improved source 46 and 50 with low-mouth seated hierarchy: viewer thigh V-frame, lower abdomen near edge, woman sitting low between the thighs with torso upright behind the action, face lowered to the exact center contact point, open mouth covering the centered shaft tip, and both hands wrapped at the base. Source 50 had some outfit looseness/drift, so keep the route candidate and provisional until another seed repeats it." } }, { @@ -541,7 +560,8 @@ "her face, torso, and open thighs remain visible in one frame", "viewer is positioned between her legs from the lower foreground", "thighs frame the central penetration line", - "viewer hands may hold her thighs without blocking the contact geometry" + "viewer hands may hold her thighs without blocking the contact geometry", + "for flat elevated-support examples, viewer stands or braces at the foot edge with feet, shins, or side-dropping legs below the support edge" ], "avoid_cues": [ "folded-leg or knees-to-chest geometry", @@ -560,9 +580,9 @@ "route_terms": ["missionary", "open-leg penetration", "front-entry"] }, "evidence": { - "fixed_seed_tests": [], - "guide_section": "", - "notes": "Atlas shows repeated front-facing open-leg missionary geometry; keep separate from folded-leg missionary until fixed-seed Krea2 tests prove which wording is reliable." + "fixed_seed_tests": ["8989898989"], + "guide_section": "missionary-open-leg--seated-lounge-drift", + "notes": "Same-seed batches on 8989898989 show two valid subcases. Generic/angled missionary can preserve open thighs, viewer hands, and centered contact, while the flatter atlas examples need elevated-support edge placement: woman flat across a table/platform, viewer standing or braced at the foot edge, and viewer feet/shins or side-dropping legs below the support. The accepted turn84 axis is mirrored only into the raised-edge/edge-supported route as a provisional patch; keep generic missionary available and keep the catalog candidate until another seed repeats it." } }, { @@ -571,11 +591,13 @@ "status": "candidate", "atlas_folders": ["missionary_folded"], "action_family": "penetration", - "position_keys": ["missionary", "folded_legs", "knees_to_chest", "front_entry"], + "position_keys": ["missionary_folded", "front_entry"], "canonical_geometry": "First-person folded missionary view from above the viewer's pelvis: the woman reclines on her back facing the viewer, knees folded high toward her chest, feet or ankles close to the camera, and the viewer's hands hold her calves or ankles while the central contact line stays below the raised legs.", "prompt_cues": [ "POV folded missionary high-leg penetration position", "woman reclines on her back with knees folded high toward her chest", + "viewer lower abdomen and a large centered shaft anchor the lower center first", + "compact folded-knee block sits above the contact point", "feet or ankles sit close to the camera above the contact line", "viewer hands hold her calves or ankles in the foreground", "her face and torso remain visible between or behind the raised legs", @@ -598,15 +620,15 @@ "route_terms": ["missionary_folded", "folded missionary", "knees-to-chest"] }, "evidence": { - "fixed_seed_tests": [], - "guide_section": "", - "notes": "Atlas shows repeated high-leg folded missionary geometry; needs fixed-seed Krea2 tests before promotion to proven." + "fixed_seed_tests": ["8989898989"], + "guide_section": "missionary-folded--contact-first-knee-block", + "notes": "Same-seed turns 85-92 show that subject-first knees-to-chest wording can produce folded high-leg geometry, but Krea2 drops readable shaft/contact when the knees and feet dominate first. The accepted turn89 axis puts the viewer lower abdomen and large centered shaft/contact before the compact folded-knee block, then holds calves/ankles and keeps the face/torso behind the raised knees. Mirrored into the folded-missionary route as a provisional patch; keep catalog candidate until another seed or subject repeats it." } }, { "key": "pov_cowgirl_frontal_straddle_penetration", "family": "cowgirl", - "status": "candidate", + "status": "proven", "atlas_folders": ["5.cowgirl"], "action_family": "penetration", "position_keys": ["cowgirl", "frontal_straddle", "woman_on_top"], @@ -615,6 +637,8 @@ "POV frontal cowgirl straddle penetration position", "woman straddles the viewer facing him", "her torso stays upright above the viewer", + "viewer lower abdomen and pelvis anchor the bottom edge", + "wide horizontal thigh bridge spans left edge to right edge", "her knees are open to either side of the viewer's hips", "viewer reclines below with thighs or pelvis in the lower foreground", "viewer hands may hold her thighs or hips without blocking the centered contact line" @@ -636,9 +660,9 @@ "route_terms": ["cowgirl", "frontal cowgirl", "woman-on-top"] }, "evidence": { - "fixed_seed_tests": [], - "guide_section": "", - "notes": "Atlas shows repeated frontal woman-on-top straddle geometry; needs fixed-seed Krea2 tests before promotion to proven." + "fixed_seed_tests": ["8989898989", "2828282828", "9191919191"], + "guide_section": "cowgirl-frontal--wide-thigh-bridge", + "notes": "Same-seed turns 93-96 show the generic baseline already validly hits frontal cowgirl on seed 8989898989. The best atlas-like improvement was turn95: wide horizontal thigh bridge from left edge to right edge, viewer lower abdomen/pelvis at the bottom edge, upright torso above the contact, and hands gripping the thigh sides. Seed 2828282828 then repeated the wide-thigh bridge hierarchy across two visible women on turns 209 and 213, and generated-route turn 216 validated the patched normal cowgirl route. Fresh seed 9191919191 repeated the generated route and three branch wordings across turns 242-249, with turns 242, 243, 244, and 248 giving the clearest atlas-like wide-thigh bridge. Promote the normal cowgirl route to proven while keeping cowgirl-alt and reverse-cowgirl families separate." } }, { @@ -647,13 +671,16 @@ "status": "candidate", "atlas_folders": ["5.cowgirl_alt"], "action_family": "penetration", - "position_keys": ["cowgirl", "frontal_straddle", "woman_on_top", "low_squat"], - "canonical_geometry": "Close first-person cowgirl-alt view: the viewer reclines below while the woman faces him in a low seated squat over the viewer's pelvis, knees bent wide near the camera, torso close above the contact line, and viewer hands or thighs anchoring the lower foreground.", + "position_keys": ["cowgirl_alt", "woman_on_top"], + "canonical_geometry": "Close first-person cowgirl-alt view: the viewer lies flat on his back underneath while the woman faces him in a low seated squat over the viewer's pelvis, knees bent wide near the camera, torso close above the contact line, and ceiling or upper-wall background cues confirm the low upward viewpoint.", "prompt_cues": [ "POV low cowgirl seated-squat penetration position", + "viewer lies flat on his back underneath her", + "lens sits low at the viewer's abdomen looking upward from his pelvis", "woman faces the viewer in a low seated squat over the viewer's pelvis", "her knees are bent wide and close to the camera on either side of the viewer's hips", "her torso stays close above the centered contact line", + "ceiling lights, upper walls, or high partition lines appear behind her upper body", "viewer reclines below with thighs, pelvis, or lower torso anchoring the foreground", "viewer hands may hold the underside of her thighs or hips without blocking the centered contact line" ], @@ -676,9 +703,9 @@ "route_terms": ["cowgirl_alt", "low cowgirl", "seated-squat cowgirl", "woman-on-top"] }, "evidence": { - "fixed_seed_tests": [], - "guide_section": "", - "notes": "Atlas shows repeated frontal woman-on-top geometry with a lower seated squat and closer thigh/hand anchors than the main cowgirl folder; needs fixed-seed Krea2 tests before promotion to proven." + "fixed_seed_tests": ["8989898989"], + "guide_section": "cowgirl-alt--flat-supine-low-angle", + "notes": "Same-seed turns 97-104 show that low-squat/contact wording can still miss the atlas orientation by reading as a platform/high-camera setup. The accepted turn104 axis uses flat-supine viewer wording plus ceiling and upper glass/room background cues: viewer abdomen and chest lie flat at the bottom, lens looks upward from his abdomen/pelvis, woman is mounted over him with wide bent knees, and centered contact remains readable. Mirrored into the cowgirl-alt route as a provisional patch; keep catalog candidate until another seed or subject repeats it." } }, { @@ -692,9 +719,12 @@ "prompt_cues": [ "POV reverse cowgirl back-facing penetration position", "woman faces away from the viewer in a back-facing straddle", + "her back, hips, and ass are the nearest largest shapes to the camera", "her back, hips, and ass are closest to the camera while her face may turn over one shoulder", "her knees or thighs are planted to either side of the viewer's hips", "viewer reclines underneath with thighs, pelvis, or lower torso anchoring the foreground", + "viewer thighs frame the lower corners", + "centered contact sits directly between her thighs below her ass", "viewer hands may hold her hips or thighs without changing the woman-on-top geometry" ], "avoid_cues": [ @@ -716,9 +746,9 @@ "route_terms": ["cowgirl_reverse", "reverse cowgirl", "back-facing straddle", "woman-on-top"] }, "evidence": { - "fixed_seed_tests": [], - "guide_section": "", - "notes": "Atlas shows repeated reverse cowgirl geometry with the viewer underneath and the woman facing away; needs fixed-seed Krea2 tests before promotion to proven." + "fixed_seed_tests": ["8989898989"], + "guide_section": "reverse-cowgirl--close-back-hip-dominant", + "notes": "Same-seed turns 105-108 show that generic facing-away reverse-cowgirl wording can collapse into frontal cowgirl. The accepted turn106 axis makes the back/hips/ass the nearest largest shapes, puts the viewer underneath with thighs framing the lower corners, and keeps centered contact directly between her thighs below her ass. Turns 107 and 108 are useful secondary evidence for viewer-leg V-frame and over-shoulder glance variants. Mirrored into the reverse-cowgirl route as a provisional patch; keep candidate until another seed or subject repeats it, and keep reverse-cowgirl-alt separate for the more upright seated atlas family." } }, { @@ -727,13 +757,16 @@ "status": "candidate", "atlas_folders": ["cowgirl_reversere_alt"], "action_family": "penetration", - "position_keys": ["reverse_cowgirl", "back_facing_straddle", "woman_on_top", "upright_seated"], + "position_keys": ["reverse_cowgirl_alt", "reverse_cowgirl", "back_facing_straddle", "woman_on_top", "upright_seated"], "canonical_geometry": "Upright first-person reverse cowgirl alt view: the viewer reclines below while the woman sits upright facing away in a back-facing straddle, her back and ass stay centered above the viewer's pelvis, her knees or thighs frame the viewer's hips, and viewer hands may hold her hips, thighs, wrists, or hands.", "prompt_cues": [ "POV upright reverse cowgirl back-facing penetration position", "woman sits upright facing away from the viewer in a back-facing straddle", "her back stays vertical and readable above her hips", "her ass is centered above the viewer's pelvis while her knees or thighs frame the viewer's hips", + "viewer hands hold her hips", + "viewer thighs frame the lower corners", + "centered contact remains visible below her ass", "viewer reclines underneath with thighs, pelvis, or lower torso anchoring the foreground", "viewer hands may hold her hips, thighs, wrists, or hands without changing the upright woman-on-top posture" ], @@ -756,9 +789,9 @@ "route_terms": ["cowgirl_reversere_alt", "reverse cowgirl alt", "upright back-facing straddle", "woman-on-top"] }, "evidence": { - "fixed_seed_tests": [], - "guide_section": "", - "notes": "Atlas shows repeated upright seated reverse cowgirl geometry; keep separate from the closer reverse cowgirl route until fixed-seed Krea2 tests prove whether one wording can cover both." + "fixed_seed_tests": ["8989898989"], + "guide_section": "reverse-cowgirl-alt--upright-seated-back-facing", + "notes": "Same-seed turns 109-112 show that the upright seated reverse-cowgirl-alt family is distinct from the close normal reverse-cowgirl route. Turn 109's generic upright baseline was already valid, while turn110's vertical-back plus hands-on-hips wording best matched the alt atlas: back and shoulders stay upright/readable, ass centered over the viewer's pelvis, viewer hands hold both hips, viewer thighs frame the lower corners, and centered contact remains below her ass. Mirrored into a separate reverse-cowgirl-alt route as a provisional patch; keep candidate until another seed or subject repeats it." } } ] diff --git a/categories/sexual_poses.json b/categories/sexual_poses.json index de9dd79..912402b 100644 --- a/categories/sexual_poses.json +++ b/categories/sexual_poses.json @@ -7,7 +7,7 @@ "weight": 1.0, "subject_type": "configured_cast", "item_label": "Sexual pose", - "style": "explicit consensual adult hardcore sex scene, anatomically clear body positioning, adults only", + "style": "explicit consensual adult hardcore sex scene, anatomically clear body positioning, all participants are adults", "positive_suffix": "Use clear adult anatomy, visible sexual contact, readable limb placement, precise body orientation, coherent spatial depth, and intense body language.", "negative_prompt": "minors, childlike appearance, teen, schoolgirl, incest, bestiality, non-consensual, coercion, rape, violence, injury, blood, gore, watermark", "scene_pools": ["hardcore_private_scenes"], @@ -1110,8 +1110,11 @@ ], "position": [ "missionary position", + "folded missionary position", "cowgirl position", + "low cowgirl seated-squat position", "reverse cowgirl position", + "upright reverse cowgirl position", "doggy style position", "standing sex position", "spooning sex position", @@ -2105,6 +2108,7 @@ "standing with cum on the body", "straddling a partner's hips in cowgirl position", "reverse cowgirl over a partner's hips", + "upright reverse cowgirl over a partner's hips", "on all fours with hips raised", "face-down ass-up on the mattress", "side-lying with thighs parted", diff --git a/docs/krea2-ab-methodology.md b/docs/krea2-ab-methodology.md new file mode 100644 index 0000000..8ddfaa6 --- /dev/null +++ b/docs/krea2-ab-methodology.md @@ -0,0 +1,461 @@ +# Krea2 A/B Methodology Memory + +This file is the persistent memory for SxCP Krea2 prompt A/B methodology. +Update it whenever the testing method improves. + +## Current Method + +Version: `2026-06-30-generated-route-validation-positive-channel-cleanup` + +1. Pull or construct the baseline from an actual SxCP/CodexMCPTest source case. +2. Keep the sampler seed fixed across the baseline and candidate. +3. Keep subject, location family, camera family, and target pose fixed unless + the experiment explicitly tests one of those axes. +4. Change one prompt variable at a time when possible, usually the visual + hierarchy for the target contact or pose. +5. Keep `sxcp_eval_out` positive-only. Do not place negative-conditioning + phrases in the visible prompt. +6. Use location-compatible anchors only. For coworking/office scenes, use chair + edge, desk edge, laptop table, glass partitions, repeated desk rows, plants, + and window depth instead of bedroom or bedding anchors. +7. Treat a manual prompt win as proof that Krea2 responds to the wording, not + proof that the SxCP generator already emits it. +8. Mirror a prompt win into the generator as a provisional improvement when + leaving a category if same-seed evidence shows it improves over baseline and + the wording is generator-safe. Keep the route `candidate` until the broader + generator-patch evidence matrix proves it. +9. When a subject-first batch preserves appearance but repeatedly misses the + atlas body plane, record it as weak-case evidence and consider stronger + control before adding more generator text. +10. Score spatial orientation against the atlas before accepting evidence, + and treat a contradictory room/background read as a rejection even when + contact or limb placement is clear. Use background cues to decide whether + the viewer or partner is high, low, standing, seated, supine, or on a + support before grading pose/contact quality. +11. For hard text-only pose families, set an exploration budget before calling + the route weak or deciding it needs stronger control. Eight prompt probes + are only an early signal. Use batched wording-axis probes and aim for about + fifty positive-only tries across meaningful axes before concluding that + prompt text cannot reliably express the pose. +12. Do not require a perfect atlas hit before carrying progress forward. After + the exploration budget, a repeatable partial that beats the baseline failure + mode can become an accepted provisional generator improvement while the + remaining miss stays documented for later seed/source expansion. +13. After patching generator wording, render one prompt produced by the actual + code path before closing the category. Manual prompt-axis wins are not + enough; the generated route can still drop the key contact hierarchy or add + limiting positive-channel wording. + +## Promotion Gates + +- One clean fixed-seed A/B can be recorded as evidence for that source case. +- A prompt-guide rule needs repeated evidence across distinct subjects, + locations, or seeds, unless the generated prompt is structurally wrong before + rendering. +- A catalog variant remains candidate until the rule repeats under controlled + conditions. +- A provisional generator patch is allowed when leaving a category if the best + tested wording improves over baseline on a fixed seed. It should preserve the + selected subject, outfit, location, and camera semantics, and it must not patch + in a scene workaround that only solved one render. +- A proven/default generator patch still needs the broader evidence matrix below, + unless the generated prompt is structurally wrong before rendering. + +## Generator Mirroring + +After a manual A/B prompt win, do not assume the SxCP generator mirrors the +wording. Add a failing regression against the final formatter output first, then +patch the narrow route boundary that owns the wording. The regression should +assert the accepted hierarchy terms and reject the failure mode that caused the +bad render, such as scene-incompatible anchors or negative-conditioning text in +the positive prompt. + +After the route patch, run a generated-route probe through `sxcp_eval_out` with +the same sampler seed when feasible. Use the actual formatter output, not a +hand-normalized prompt. If the generated route regresses compared with the +manual prompt-axis winner, record the failed generated-route image as the +baseline, tighten the route wording, and validate again before logging the +candidate as generated-route evidence. + +For location-specific wins, split the implementation: + +- the action or role graph owns the pose/contact hierarchy; +- the final Krea formatter owns scene-compatible anchor expansion because it can + see the selected scene, camera, and composition; +- existing route phrases that downstream tests rely on should be preserved +inside the stronger wording when they do not conflict with the A/B evidence. + +## MCP Command Memory + +Use the checked helper instead of ad hoc Python snippets for bridge calls. The +approved command prefix is: + +```bash +/media/p5/miniforge3/bin/python tools/sxcp_mcp_client.py +``` + +Common calls: + +```bash +/media/p5/miniforge3/bin/python tools/sxcp_mcp_client.py list-tools +/media/p5/miniforge3/bin/python tools/sxcp_mcp_client.py call-tool comfy_pull --arguments-json '{"channel":"sxcp_eval_in"}' +/media/p5/miniforge3/bin/python tools/sxcp_mcp_client.py call-tool comfy_push --arguments-json '{"channel":"sxcp_eval_out","seed":5656565656,"text":"PROMPT_ONLY_POSITIVE_CONDITIONING"}' +``` + +For batched prompt-axis search, prepare a JSON batch and use the offline command +renderer before touching the bridge manually: + +```bash +python tools/sxcp_prompt_batch.py validate --batch-json /tmp/sxcp-batch.json +python tools/sxcp_prompt_batch.py print-push-commands --batch-json /tmp/sxcp-batch.json +python tools/sxcp_prompt_batch.py print-result-template --batch-json /tmp/sxcp-batch.json +python tools/sxcp_prompt_batch.py run-batch --batch-json /tmp/sxcp-batch.json --result-json /tmp/sxcp-results.json --previous-turn 80 --run +python tools/sxcp_prompt_batch.py validate-results --batch-json /tmp/sxcp-batch.json --result-json /tmp/sxcp-results.json +python tools/sxcp_prompt_batch.py print-eval-entry-draft --batch-json /tmp/sxcp-batch.json --result-json /tmp/sxcp-results.json --variant-key pov_example_variant --baseline-image /absolute/baseline.png --candidate-id controlled_subject_first +``` + +Use `run-batch --run` for normal batch execution. It pushes one positive prompt, +polls `sxcp_eval_in` until the turn advances and an absolute PNG appears with +the fixed sampler seed, writes the filled result JSON, then sends the next +prompt. Omit `--run` for a dry-run command preview. Run `validate-results` after +the batch and before drafting evidence. It checks that every probe returned a +new ordered turn, an absolute PNG image path, and the same sampler seed as the +batch. This keeps batched prompt search as image-presence collection first and +bulk analysis second. + +Before drafting evidence, compare atlas references and generated images for +spatial orientation, not only limb/contact similarity. First decide the +atlas's surface and camera-height relationship, then check whether the +generated background supports the same read. Use the background as a +camera-height witness: ceiling, upper walls, and high partition lines usually +support a low viewer looking upward; floor, carpet, table tops, platform edges, +or furniture behind the body can reveal a higher camera, seated support, or a +different surface. If the atlas target has the viewer flat on his back or the +partner mounted over him, do not accept a candidate only because contact is +clear; the room geometry must also support that flat/low read. Reject the +candidate before generator mirroring when the background says the bodies are on +a different surface or at a different height than the atlas. + +`print-eval-entry-draft` rejects `geometry_only` candidates by default. Use +`--allow-geometry-only` only when the entry is explicitly labeled as +non-controlled prompt-axis evidence rather than subject/look-controlled A/B +evidence. + +Keep `sxcp_eval_out` prompt-only and positive-only. Do not use +`sxcp_eval_negative_out` for Krea2 tuning. + +## Generator-Patch Evidence Matrix + +Do prompt and image exploration before editing production generator wording. A +normal pose-wording generator patch needs all of this evidence first: + +- at least three distinct source cases with different visible subjects; +- at least two sampler seeds, unless the source prompt is structurally wrong + before rendering; +- location-family coverage when the proposed wording changes scene anchors; +- one baseline and one candidate per source case, with subject, location family, + camera family, and sampler seed fixed inside each pair; +- positive-only candidate prompts, with no negative-conditioning phrases in the + positive prompt. + +A generated-route probe that works before the full matrix is useful evidence. +If it is the best tested improvement when leaving the category, it can become a +`provisional_generator_patch` with final prompt regression coverage. It should +not become a proven `generator_patch` decision until the matrix repeats and the +final generated prompt is regression-tested. + +## Hard-Pose Exploration Budget + +Use this budget for atlas poses where early prompt-only results repeatedly miss +the core spatial read. + +- Define the failure threshold before the run. The default threshold is about + fifty positive-only prompt tries across distinct wording axes before declaring + the pose text-insufficient or moving it to a stronger-control bucket. +- Run the search in batches, usually six to twelve prompts at a time. Send each + prompt through `sxcp_eval_out`, wait for the image path, then analyze the + batch together instead of overreacting to one render. +- Keep a short axis ledger for each batch: intended wording axis, seed, source + subject, best image, repeated failure mode, and words that literalized or + harmed the result. +- Treat a small failed batch as direction, not a conclusion. If a batch shows a + repeated failure such as head height, camera height, viewer/partner elevation, + or background-plane mismatch, the next batch should vary that axis directly. +- Stop early only for a strong positive result that is worth repeating on a + second source or seed, or for a hard technical blocker. A weak but improving + result should feed the next wording batch rather than ending the category. +- If the threshold run finds a repeatable partial that is materially better + than baseline, accept the partial target explicitly and mirror only that + generator-safe improvement. Keep the route candidate and mark the evidence as + needing expansion when the full atlas target is still unsolved. + +## Current Fingering Test Pattern + +The prior bedding-based fingering prompt is invalid as a general rule because +it solved a lower-foreground artifact by adding bedroom context to an office +scene. The corrected test pattern keeps the coworking location intact: + +- baseline: generic POV fingering/manual-contact wording from the same source + case; +- candidate: foreground hand first, open-thigh geometry second, visible woman + face/torso third, office chair and coworking depth fourth; +- anchors: black office chair seat/arms, desk edge, laptop table corners, glass + partitions, repeated desk rows, plants, tall-window depth; +- rejection trigger: any result that fixes contact by changing the scene family + instead of improving the pose hierarchy. + +## Improvement Log + +- `2026-06-30`: Added side-camera/result-label separation after ballsucking + seed `5757575757` produced attractive low side-camera oral views while still + collapsing the requested contact object onto the shaft/glans. Future scoring + should record that as side-view oral evidence and keep target-contact evidence + separate. +- `2026-06-30`: Added generated-route validation discipline after footjob turn + `183` kept large foreground soles but hid the shaft/contact that manual probes + had preserved. Future provisional generator patches should render the exact + final Krea prompt once after the code change; if shared route wording adds + limiting positive-channel language, clean it before sending the validation + prompt. +- `2026-06-30`: Added a hard-pose exploration budget after ballsucking wording + tests produced only eight early probes before the first weak-case note. Future + hard text-only poses should use batched wording-axis search and aim for about + fifty positive-only tries before concluding the pose needs stronger control. +- `2026-06-30`: Added partial-acceptance discipline after ballsucking produced + repeatable tongue/lips-on-testicles results that beat the shaft/glans + baseline but did not fully solve mouth-wrapped contact. Future hard-pose exits + should preserve repeatable progress as a provisional generator patch while + keeping the remaining miss in the expansion queue. +- `2026-06-30`: Added ballsucking target-object refinement after sampler seed + `9797979797` repeated the `scrotal skin is the nearest mouth surface` branch + on turns `288` and `293`. Score target-object ownership separately from the + side-low camera family: a route can preserve face/thigh geometry while still + drifting to shaft/base contact. Avoid promoting balls-first center-object + wording when it creates multi-subject or body-layout artifacts. +- `2026-06-30`: Added ballsucking generated-route validation after sampler seed + `9898989898` repeated the patched scrotal-skin route on turns `296` and + `297`. Validation can accept a provisional target-object improvement while + still keeping the pose queued when the remaining miss is full mouth-wrapped + testicle contact. +- `2026-06-30`: Added ballsucking fresh weak-case evidence after sampler seed + `5959595959` tested lip-oval, sideways mouth pocket, and chin-pelvis upward + seal wording across three women. The batch preserved low-pelvis/cheek-thigh + geometry in places, but every branch returned to shaft/glans collapse or + generic oral contact. Do not retry those axes as generator defaults; the next + search should change the target-object control strategy rather than adding + more mouth-shape synonyms. +- `2026-06-30`: Added ballsucking occlusion weak-case evidence after sampler + seed `6060606060` tested foreground occlusion, under-scrotum tongue shelf, + and hand-guided scrotum wording across three women. The generated route + remained the best partial while those axes became shaft-centered or + hand/shaft-dominant. Do not retry occlusion or hand-support synonyms as + generator defaults; the next useful move is a different target-object strategy + or stronger control. +- `2026-06-30`: Added ballsucking mouth-axis mixed-case evidence after sampler + seed `6161616161` tested exact mouth-sucking, single-testicle, hanging balls + below shaft, side-mouth wrap, and chin-pelvis lower-mouth wording across + three women. The generated-route controls stayed the best repeated partials + on two subjects, side-mouth and chin-pelvis variants produced isolated useful + partials, and the rest drifted back to shaft/glans contact. Record isolated + partials as axis hints, but do not patch generator wording unless a branch + repeats across subjects or beats the generated-route controls. +- `2026-06-30`: Added ballsucking pelvis-valley weak-case evidence after + sampler seed `7171717171` tested flat pelvis-valley, thigh tunnel, + pubic-hair mouth-line, low-cushion chin-anchor, and pelvis-edge target-first + wording across three women. The flat pelvis-valley branch repeated a strong + body-plane correction on three subjects, matching the atlas viewer-flat + thigh-wall read better, but it stayed shaft-centered. Score body-plane + orientation and target-object contact separately; do not patch a route when + it improves orientation while regressing the target. +- `2026-06-30`: Stopped the ballsucking text-only loop after sampler seed + `7272727272` combined `flat-valley scrotal-skin` target wording with the + prior side-low route across three women. The hybrid repeated the body-plane + hint on turns `368`, `374`, and `380`, but the target stayed shaft-centered, + while side-low flat-valley variants only gave look hints. Preserve the + current side-low scrotal-skin partial, do not patch the hybrid axes, and move + future full-target work toward stronger pose/control evidence rather than + more positive-prompt synonyms. +- `2026-06-30`: Promoted blowjob side-profile POV after sampler seed + `5858585858` produced a three-woman generated-route repeat on turns `298`, + `301`, and `304`. When the current generated route repeats across multiple + subjects on a fresh seed and alternate branches do not beat it cleanly, mark + the route proven instead of continuing to queue it. Keep attractive + side-camera-style self-body crop results as a separate look branch when they + risk drifting toward external side framing. +- `2026-06-29`: Added the multisource/generator-safe method after an overfit + single-character coworking test produced a visually usable but invalid + bedding foreground. Future A/B runs must test at least two source cases before + promoting wording that is meant to become a durable guide or generator rule. +- `2026-06-29`: Added generator mirroring discipline after the accepted + fingering wording proved Krea2 behavior but not generator output. Future + mirroring changes need a red-green regression at final Krea formatter output, + not just a guide entry. +- `2026-06-29`: Tightened generator-patch promotion after the fingering + generated-route probe looked good but had too little image coverage. Future + pose-wording generator edits need a broader seed, subject, and location matrix + before production route code changes. +- `2026-06-29`: Added semantic-axis discipline after source 52 fingering tests. + If a candidate succeeds by changing ownership, viewpoint, location family, or + role semantics, record it as a weak-case or prompt note unless that semantic + change is the intended generator behavior. Do not count it as direct evidence + for the original route even when the image is visually cleaner. +- `2026-06-29`: Added provisional generator-patch discipline after the user + clarified that leaving a category should still carry forward same-seed progress + over baseline. Future category exits should patch the generator with the best + generator-safe improvement, record it as `provisional_generator_patch`, and + keep the catalog route as `candidate` until repeated evidence proves it. +- `2026-06-29`: Applied the category-exit rule to spread/open-thigh presentation + after two source subjects improved on the same sampler seed. For setup poses + that are not structurally broken before rendering, prefer at least two source + subjects before mirroring a provisional generator patch, and keep the + observation explicit about remaining weak points such as insufficient V-frame + width or outfit closure. +- `2026-06-29`: Applied the same category-exit rule to blowjob top-view after + two source subjects improved on sampler seed `4242424242`. When the baseline is already usable, + record the improvement narrowly: name the axis that got better, keep the route + candidate, and avoid overstating the finding as proven until another seed + repeats it. +- `2026-06-29`: Corrected blowjob top-view criteria after atlas review and a + same-seed source-`46` probe showed that vertical shaft alignment alone can + still render as frontal/eye-height oral. Future top-view evidence must show + steep overhead camera geometry: viewer abdomen at the lower edge, camera + looking down from above the viewer chest/abdomen, and the woman's hair crown, + shoulders, and hands visible from above. +- `2026-06-29`: Refined blowjob top-view prompt-axis search after the user + rejected horizontally biased probes. Run several prompt-only probes before + editing the generator, wait for `sxcp_eval_in` to advance to the new turn, and + compare each image against the atlas verticality criteria. The useful axis is + `nadir-angle` or `bird's-eye` plus standing male POV, nearby floor plane + dominating the image, one woman directly below between the viewer's feet, and + top-down office anchors. Avoid `plumb-line` and `map` in generator prompts + because Krea2 can literalize them as drawn graphics. +- `2026-06-29`: For quick wording-axis search, prefer a batched prompt-probe + loop before analysis-heavy iteration. Prepare several positive-only alternate + prompts that isolate likely wording axes, send them one at a time through + `sxcp_eval_out` with the same sampler seed, pull only until each new + `sxcp_eval_in` turn and image path exists, then inspect the returned images as + a batch. Use the bulk comparison to pick the best axis, identify literalized + or harmful words, and only then update the generator, guide, catalog, or eval + log. +- `2026-06-29`: Preserve prompt-order controls when testing anything beyond + rough pose-axis discovery. Prompts that start with pose geometry and omit or + move the subject/look block can reduce female-look adherence, so treat those + runs as geometry-only probes. Durable A/B prompts should keep the original + subject/look description first, then the pose hierarchy, then location and + style/background anchors, unless the test is explicitly about prompt-order + sensitivity. +- `2026-06-29`: Added result-validation discipline to the batched prompt helper. + After sending a batch, fill the result template from `sxcp_eval_in`, run + `validate-results`, and only then draft evidence. The validation step proves + each probe returned an ordered turn, an absolute PNG artifact, and the fixed + sampler seed before bulk analysis or log-entry drafting. +- `2026-06-29`: Added `run-batch` automation to the batched prompt helper. It + removes manual push/pull copy-paste from normal A/B runs while keeping the same + gates: positive-only prompts, fixed sampler seed, turn advancement, absolute + PNG image path, and `validate-results` before evidence drafting. +- `2026-06-29`: Split missionary subcases after turns `77`-`84`. Turns `76` and + `80` are valid angled/cushion missionary results, not failures. The flatter + atlas examples need a different positive axis: woman flat across an elevated + table/platform, viewer standing or braced at the foot edge, and viewer feet, + shins, or side-dropping legs placed below the support edge. Patch this only + into the raised-edge/edge-supported route; keep generic missionary available + for angled valid views. +- `2026-06-29`: Folded-missionary tuning on seed `8989898989` used two + subject-first batches before code changes. Turns `85`-`88` showed that + compact knee-block and vertical-thigh-column wording can produce the folded + high-leg geometry, but the shaft/contact disappears when knees and feet lead + the hierarchy. Turns `89`-`92` then tested contact-first variants; turn `89` + was accepted because it placed the viewer lower abdomen and large centered + shaft/contact before the compact folded-knee block. This confirms the + method: use the first batch to identify the failed axis, run a targeted + second batch, then mirror only the accepted generator-safe hierarchy as a + provisional patch. +- `2026-06-29`: Frontal cowgirl on seed `8989898989` used a baseline-plus- + variants batch instead of comparing against a previous category. Turn `93` + was a valid generic cowgirl baseline, so turn `95`'s wide horizontal thigh + bridge improvement became a prompt-guide rule rather than a generator patch. + When the baseline already hits the pose, record the useful atlas refinement + and leave the generator unchanged unless repeated evidence shows a systemic + weakness. +- `2026-06-29`: Cowgirl-alt on seed `8989898989` exposed a spatial-orientation + blind spot. Turns `97`-`100` had readable contact and squat-like knees, but + the background still read as a platform/high-camera setup. After rechecking + the atlas, turns `101`-`104` tested flat-supine viewer wording with ceiling + and upper-room cues; turn `104` was accepted. Future pose analysis must + compare atlas and generated room geometry before accepting an image. +- `2026-06-29`: Reverse cowgirl on seed `8989898989` showed that a correct + semantic label such as `facing away` can be ignored when the visual hierarchy + still resembles frontal cowgirl. Future back-facing straddle tests should + score facing direction before contact quality and should name the back, hips, + and ass as the nearest largest shapes before viewer-leg and contact details. + Treat over-shoulder glances as secondary refinements only after the + back-facing straddle is already locked. +- `2026-06-29`: Reverse-cowgirl-alt on seed `8989898989` confirmed that atlas + sibling folders can need separate generator routes even when the baseline is + already valid. Normal reverse cowgirl is close back/hip dominant; reverse-alt + is upright seated with vertical back/shoulders and viewer hands or thighs + forming the lower frame. Keep those prompt hierarchies separate instead of + merging all back-facing woman-on-top evidence into one route. +- `2026-06-29`: Added non-target-viewpoint discipline after blowjob side-profile + oral produced an attractive side-camera result on seed `5656565656`. If a + render is visually useful but reads as a different camera family, record it as + a weak case for a future route and do not mirror it into the current POV + generator path. +- `2026-06-29`: Added MCP command memory after repeated context loss around the + bridge workflow. Future A/B calls should use the checked helper command + `/media/p5/miniforge3/bin/python tools/sxcp_mcp_client.py ...`, with + `comfy_push` to `sxcp_eval_out` for prompt-only positive conditioning and + `comfy_pull` from `sxcp_eval_in` for returned prompt/image/seed payloads. +- `2026-06-29`: Added side-profile oral ownership discipline after source `46` + improved with explicit adult-male foreground ownership while source `47` + rejected a related `body-axis` cue by transferring the body surface to the + woman. Future side-profile tests should name the foreground owner repeatedly + and verify that the woman's body stays lateral before considering any + generator mirroring. +- `2026-06-30`: Promoted the side-profile oral lateral-edge body-line axis + after sampler seed `9753197531` repeated it across two visible women. Pure + male-body-axis wording can expose the male as a photographed subject or let + Krea2 transfer the central body surface away from the intended first-person + view. Future generator patches should combine adult-male foreground ownership + with explicit lateral entry from the left edge, mouth at the male abdomen + line, and hand under the lips; keep the route provisional until another + seed/source expansion repeats it. +- `2026-06-30`: Added side-profile oral generated-route contact validation + after turn `206` kept the male body-line geometry but let the mouth float + above the shaft while the hand became the contact anchor. Turn `207` improved + after adding lips-touching and mouth-to-shaft-contact priority. Future + generated-route validation for oral side-profile should score both viewpoint + ownership and which body part actually anchors the contact. +- `2026-06-30`: Added the side-profile oral lower-right torso anchor after + sampler seed `9595959595` repeated it on turns `279` and `283` across two + visible women. The useful wording makes the adult male viewer's own torso + start at the lower edge and run diagonally into the lower-right foreground, + with navel, abdomen hair, pelvis, and near thigh marking the camera owner's + body. Prefer this over generic body-axis wording, which can expose the male + as a photographed side subject or transfer the axis onto the woman. +- `2026-06-30`: Added side-profile oral generated-route validation after + sampler seed `9696969696` repeated the patched route on turns `284` and + `285`. Count generated-route validation separately from prompt-axis search: + it proves the formatter can carry the new wording, while promotion still + requires broader source/seed evidence. +- `2026-06-30`: Promoted normal frontal cowgirl from guide-only to provisional + generator patch after seed `2828282828` repeated the wide-thigh bridge axis + across two visible women. When the baseline is already valid, a generator + patch is still appropriate if a later seed repeats a narrow atlas refinement + that improves geometry without harming subject/look, contact, or setting. + Generated-route turn `216` validated the patched formatter route with viewer + hands on outer thighs, wide foreground thigh bridge, upright torso, centered + contact, and coworking depth. Keep the route candidate until another + source/seed repeats the refinement. +- `2026-06-29`: Applied the category-exit rule to blowjob laying frontal after + source `46` and source `50` improved on sampler seed `6767676767`. When + baselines are already strong, preserve the exact improved axis: wide V-frame and low-horizontal torso hierarchy, while noting residual high-hip posture and + keeping the generator patch provisional until another seed repeats it. +- `2026-06-29`: Applied the category-exit rule to blowjob sitting upright after + source `46` and source `50` improved on sampler seed `7878787878`. When a + baseline preserves the seated pose but floats the face above the contact + point, prefer low-mouth seated hierarchy over generic `mouth aligned` wording: + face lowered to the exact center contact point, open mouth covering the + centered tip, and hands directly at the base. Record outfit looseness/drift as + residual risk and keep the generator patch provisional until another seed + repeats it. diff --git a/docs/krea2-eval-log.json b/docs/krea2-eval-log.json index 287676c..d1a3455 100644 --- a/docs/krea2-eval-log.json +++ b/docs/krea2-eval-log.json @@ -1,7 +1,7 @@ { "version": 1, "purpose": "Structured fixed-seed Krea2 prompt/image evidence for SxCP atlas pose variants.", - "artifact_policy": "Image paths are external ComfyUI artifacts and may be cleaned; seed, summaries, observation, decision, and commit are the durable record.", + "artifact_policy": "Image paths are external ComfyUI artifacts and may be cleaned; sampler seed, optional generator seed, summaries, observation, decision, and commit are the durable record.", "entries": [ { "id": "doggy-52-climax-target-structural", @@ -47,6 +47,1696 @@ "baseline_image": "/media/unraid/comfyui/output/agent_bridge/img_e21f615e5f5246d486167ae5a1c03527.png", "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_7263741723d64271b04dce8ed63f560b.png", "commit": "a484783" + }, + { + "id": "ballsucking-238365845574312-low-head-inconclusive", + "date": "2026-06-29", + "variant_key": "pov_ballsucking_low_head", + "seed": 238365845574312, + "generator_seed": 9001, + "source": "sxcp_eval_mcp", + "result": "inconclusive", + "decision": "needs_more_tests", + "baseline_prompt_summary": "Baseline prompt used generic kneeling and head-tucked-under-shaft wording for the same fixed sampler seed.", + "candidate_prompt_summary": "Candidate prompt moved the visible partner very low between the viewer's thighs and placed the face at testicle height below the raised shaft.", + "observation": "Fixed-sampler A/B used sampler seed 238365845574312; the MCP seed field carried generator/control value 9001 during the run. The low-head wording improved body placement by moving the woman lower and more forward between the thighs, but both atlas and tighter candidates still read too much like generic shaft-focused oral instead of a distinct testicle-height target. Keep this variant as a prompt-only retry/needs-more-tests case rather than promoting it.", + "baseline_image": "/media/unraid/comfyui/output/agent_bridge/img_8e82bf289ba345a6a4e59bf10ad922b6.png", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_e10f5f45bad54cc388dbee53314ce775.png", + "commit": "pending" + }, + { + "id": "ballsucking-238365845574312-side-cropped-scrotum-target", + "date": "2026-06-29", + "variant_key": "pov_ballsucking_low_head", + "seed": 238365845574312, + "source": "sxcp_eval_mcp", + "result": "accepted", + "decision": "prompt_guide_rule", + "baseline_prompt_summary": "Retry prompt made scrotum contact explicit, but kept the raised shaft centered above the face as a major visual landmark.", + "candidate_prompt_summary": "Candidate made the balls/scrotum the largest exact-center foreground object with the shaft cropped to a side landmark away from the mouth.", + "observation": "Same-sampler A/B on seed 238365845574312 showed that low-head and testicle-height wording alone still collapsed into shaft-focused oral. The accepted candidate changed the composition hierarchy: balls first, scrotum contact second, woman's low face third, and shaft cropped to the side. That produced a readable testicle-contact image, though the catalog variant should remain candidate until repeated on another seed.", + "baseline_image": "/media/unraid/comfyui/output/agent_bridge/img_25d28e7c4d0e41f5876f01d80b1055f5.png", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_46c5abffd6304844b9edc2fa4563ce56.png", + "commit": "pending" + }, + { + "id": "ballsucking-1212121212-two-women-head-too-high-weak-case", + "date": "2026-06-30", + "variant_key": "pov_ballsucking_low_head", + "seed": 1212121212, + "source": "sxcp_eval_mcp_batch", + "result": "inconclusive", + "decision": "needs_more_tests", + "baseline_prompt_summary": "Turns 116 and 118 tested the previous low-head baseline across two visible women: subject-first descriptions, low between the viewer's thighs, face below the penis at testicle height, mouth/tongue on balls, and coworking lounge anchors.", + "candidate_prompt_summary": "Turns 117 and 119 repeated the prior accepted side-cropped scrotum hierarchy across the same two women. Turns 120-123 then removed the compound label and tested literal action wording such as sucking the testicles with her mouth and mouth closed around the balls.", + "observation": "Same-sampler two-woman expansion on seed 1212121212 did not repeat the prior guide-only win. The baseline turns 116 and 118 collapsed into generic shaft/glans oral. The side-cropped scrotum candidates enlarged the balls/scrotum but still placed the mouth on the shaft or glans. The literal action wording helped turn 122 move the shaft off-axis and bring the tongue toward the base/scrotum, but her head remained too high and contact still read as tongue/base rather than mouth wrapped around testicles. The user's correction is the next axis: avoid label-like 'testicle-sucking position' and force the head below the shaft base, chin near the viewer pelvis, mouth lower than the balls/scrotum contact point. Record as weak-case expansion evidence; do not patch the generator.", + "baseline_image": "/media/unraid/comfyui/output/agent_bridge/img_e842a0b9bd7243b8b42e10ce71fff395.png", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_d037dc614a044cb9a4b3b8f8257b56a6.png", + "repeat_cases": [ + { + "source_case": "turn117 woman_a_candidate_side_cropped_scrotum", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_69fc19cf23d8492188dc17c548f54aa0.png", + "observation": "Balls/scrotum became large and centered, but her mouth still targeted the shaft/glans area." + }, + { + "source_case": "turn119 woman_b_candidate_side_cropped_scrotum", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_1330707b39fb43268d6640b3fe866e1e.png", + "observation": "Repeated the same failure across a second woman: larger centered balls/scrotum, but high head and mouth on shaft/glans." + }, + { + "source_case": "turn122 woman_b_literal_sucking_testicles", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_d037dc614a044cb9a4b3b8f8257b56a6.png", + "observation": "Best weak candidate: literal action wording moved the shaft off-axis and placed the tongue near the lower base/scrotum, but the head remained too high for atlas-like mouth-to-testicles contact." + }, + { + "source_case": "turn123 woman_b_mouth_closed_around_balls", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_6561f60fb7e44bc08275c32b6ab41c53.png", + "observation": "Rejected branch: 'mouth closed around the balls' was interpreted as mouth closed around the shaft/glans." + } + ], + "commit": "pending" + }, + { + "id": "ballsucking-1212121212-fifty-probe-side-low-partial-axis", + "date": "2026-06-30", + "variant_key": "pov_ballsucking_low_head", + "seed": 1212121212, + "source": "sxcp_eval_mcp_batch", + "result": "accepted", + "decision": "provisional_generator_patch", + "needs_expansion": true, + "baseline_prompt_summary": "After the first two-woman weak case, turns 124-165 tested fifty total prompt variants for woman A on the same sampler seed: under-base/chin-near-pelvis wording, hand-held-side wording, direct scrotum/testicle vocabulary, and combined side-low wording. All prompts were positive-only.", + "candidate_prompt_summary": "The best partial axis combined low side-pelvis POV, cheek against the viewer's inner thigh, and scrotum/testicles centered against the tongue/lips. Turns 166-171 repeated six partial-winner prompts on a second visible woman with the same seed.", + "observation": "The threshold search showed that head-height wording alone is not enough for Krea2 on this route. Under-base/chin-near-pelvis prompts and hand-held-side prompts still collapsed into generic shaft/glans oral. Direct vocabulary such as balls in mouth or scrotum in mouth also mostly collapsed unless paired with side-low geometry. The repeatable partial axis is low side-pelvis POV plus cheek-on-inner-thigh plus scrotum/testicles centered at tongue/lips. It repeats as useful partials on turns 149 and 162 for woman A and turns 167 and 170 for woman B. The accepted target is tongue/lips on the testicles/scrotum because that is materially better than the baseline shaft/glans collapse, even when it does not produce a fully closed-mouth suck. Mirror this partial improvement into the generator provisionally and keep the route queued for broader seed/source expansion.", + "baseline_image": "/media/unraid/comfyui/output/agent_bridge/img_c131f12f289341c88395b717e179f1b0.png", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_a787e055fbcb4c6c9958daf26ae85ed7.png", + "repeat_cases": [ + { + "source_case": "turn147 woman_a_testicles_on_tongue_side_lie", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_09c60673e748496ba626331f7a183e8e.png", + "observation": "First strong spatial partial: low side posture, cheek near thigh, and hand/base contact, but still more tongue/contact than mouth-around-testicles." + }, + { + "source_case": "turn149 woman_a_scrotum_tongue_testicle_contact", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_f5eba1e974fb40d4a948e958f9f3c48b.png", + "observation": "Best woman-A target-contact partial: balls centered and tongue/lips near scrotum/testicles, but the head remained a little high and contact read as licking." + }, + { + "source_case": "turn162 woman_a_scrotum_pressed_into_lips_low_side", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_3b01bfb0b2fc434d8beffbc2a6d105c9.png", + "observation": "Best combined compromise: side-low geometry with balls prominent at mouth/tongue, still short of mouth closed around testicles." + }, + { + "source_case": "turn167 woman_b_scrotum_tongue_testicle_contact", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_a787e055fbcb4c6c9958daf26ae85ed7.png", + "observation": "Second-subject repeat of the contact axis: centered balls/scrotum and tongue contact, but still not a full sucking/mouth-wrapped result." + }, + { + "source_case": "turn170 woman_b_side_lie_scrotum_on_tongue_lips_close", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_ad0d8042587c48bca2a05f99966f0125.png", + "observation": "Second-subject repeat of the side-low axis: cheek/thigh geometry and balls at tongue/lips repeat cleanly, but contact remains a lick/contact read." + } + ], + "commit": "pending" + }, + { + "id": "ballsucking-1212121212-generated-route-side-low-face-priority", + "date": "2026-06-30", + "variant_key": "pov_ballsucking_low_head", + "seed": 1212121212, + "source": "sxcp_eval_mcp_generated_route", + "result": "accepted", + "decision": "provisional_generator_patch", + "needs_expansion": true, + "baseline_prompt_summary": "Turn 172 used the first generated-route side-low partial prompt after the initial generator patch: low side-pelvis POV, cheek against inner thigh, scrotum/testicles centered at tongue/lips, and positive-only coworking anchors.", + "candidate_prompt_summary": "Turn 173 added the face-priority cue to the generated route: her face is the closest visible partner part while her head stays low under the viewer's pelvis and scrotum/testicles stay centered against tongue/lips.", + "observation": "Generated-route A/B on the same sampler seed showed why the face-priority cue matters. Turn 172 kept tongue/testicle contact low in frame but let the woman's torso/body take over the composition. Turn 173 moved the face low beside the viewer's thigh, preserved coworking context, and kept tongue/testicle contact centered. This validates the partial target as better than the baseline shaft/glans collapse, but the route remains provisional because body/rear prominence is still strong and broader seeds/sources are needed.", + "baseline_image": "/media/unraid/comfyui/output/agent_bridge/img_308ef3e2887a407e926bbdb56f81c302.png", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_8da4542762814ca7ac46bf470b9285d1.png", + "commit": "pending" + }, + { + "id": "ballsucking-5757575757-two-woman-side-camera-collapse", + "date": "2026-06-30", + "variant_key": "pov_ballsucking_low_head", + "seed": 5757575757, + "source": "sxcp_eval_mcp_batch", + "result": "inconclusive", + "decision": "needs_more_tests", + "needs_expansion": true, + "baseline_prompt_summary": "Turns 217 and 221 repeated the current generated-route side-low face-priority wording across two visible women: low side-pelvis POV, cheek near the viewer's inner thigh, face closest, and scrotum/testicles centered at tongue/lips.", + "candidate_prompt_summary": "Turns 218-224 tested side-held shaft, under-base mouth-lower, and balls-first contact variants. The batch produced strong side-camera oral framing, but the mouth and tongue still collapsed onto the shaft/glans instead of a distinct testicle-contact target.", + "observation": "Fresh-seed two-woman expansion showed that the side-low axis is visually strong for side-camera oral views: the viewer abdomen/thigh foreground, coworking depth, low partner face, and cheek/thigh proximity stayed coherent across all eight renders. It did not validate additional ballsucking wording because every variant still made the shaft the primary contact object. Treat this as a weak-case expansion record, preserve the side-camera look for oral side-view reuse, and keep ballsucking queued for stronger target-object control or more wording search before any proven promotion.", + "baseline_image": "/media/unraid/comfyui/output/agent_bridge/img_35f9060e540f4be6bd74ea9ce2102ac7.png", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_1f226b0927194915bb3a3344328a5cb9.png", + "repeat_cases": [ + { + "source_case": "turn217 woman_a_generated_side_low_face_priority", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_35f9060e540f4be6bd74ea9ce2102ac7.png", + "observation": "Generated-route wording held a strong low side-camera view, but contact read as shaft/glans oral." + }, + { + "source_case": "turn219 woman_a_under_base_mouth_lower", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_35b2373beba345e3a63d94b29c1185c1.png", + "observation": "Under-base wording enlarged the base/balls area, but the mouth still targeted the centered shaft/glans." + }, + { + "source_case": "turn221 woman_b_generated_side_low_face_priority", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_b5e1bcd7a16c4cf5b45b677433fc6037.png", + "observation": "Second subject repeated the attractive side-low camera read while preserving the same shaft-contact collapse." + }, + { + "source_case": "turn223 woman_b_under_base_mouth_lower", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_1f226b0927194915bb3a3344328a5cb9.png", + "observation": "Best frontal-ish target-object attempt on the second subject, but still a shaft/glans oral read rather than testicle contact." + } + ], + "commit": "pending" + }, + { + "id": "ballsucking-6262626262-open-lips-scrotum-surface-partial", + "date": "2026-06-30", + "variant_key": "pov_ballsucking_low_head", + "seed": 6262626262, + "source": "sxcp_eval_mcp_batch_two_visible_women", + "result": "accepted", + "decision": "provisional_generator_patch", + "needs_expansion": true, + "baseline_prompt_summary": "Turns 250 and 256 repeated the current generated-route side-low face-priority wording across two visible women on fresh sampler seed 6262626262. Both preserved the low coworking side-pelvis camera family, but contact still drifted toward shaft/glans oral.", + "candidate_prompt_summary": "Turns 252 and 258 used open-lips scrotum-surface wording: the scrotum is the mouth surface, testicles rest across her open lips, tongue cups them from below, head lies sideways on the viewer's inner thigh, and chin stays close to the viewer pelvis. Turns 253 and 259 tested a more centered tongue-cradle underside branch.", + "observation": "Fresh-seed two-woman expansion showed a repeatable partial improvement over the current generated route. The generated-route controls on turns 250 and 256 kept good low side-camera framing but still made the shaft/glans the dominant mouth target. The open-lips scrotum-surface branch on turns 252 and 258 moved the head sideways onto the thigh and made tongue/lip contact land on the scrotum/testicles more like the atlas side-low examples. The tongue-cradle branch on turns 253 and 259 also improved target-object emphasis, but it read more centered and less side-low. Patch the provisional generator wording from generic scrotum/testicles centered against tongue/lips to scrotum-as-mouth-surface, testicles resting across open lips, tongue cups from below, while keeping the route candidate and queued because full mouth-wrapped testicle sucking remains unreliable. Generated-route validation turns 262 and 263 then rendered the patched wording directly on both women; both preserved the low side/thigh relationship and moved tongue/lip contact to the underside scrotum/testicle area with less shaft-tip dominance than turns 250 and 256.", + "baseline_image": "/media/unraid/comfyui/output/agent_bridge/img_ba8f9444998b4a6f9a1b25078e135024.png", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_47a40dd56a8d4812a33bd4b4523174d1.png", + "repeat_cases": [ + { + "source_case": "woman_b_open_lips_scrotum_surface", + "baseline_image": "/media/unraid/comfyui/output/agent_bridge/img_4beae63cea4f498a8b11108b17c01caa.png", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_bed5397a2f094294bb16c052ab487cb9.png", + "observation": "Second-subject repeat of the open-lips branch preserved side-low head placement and moved contact toward scrotum/testicles instead of the shaft tip." + }, + { + "source_case": "woman_a_tongue_cradle_underside", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_4f6f14b016814ea4b832f4fad51967c6.png", + "observation": "Useful secondary branch: testicles/tongue contact became clearer, but the image read more centered and less side-low than the open-lips scrotum-surface branch." + }, + { + "source_case": "woman_b_tongue_cradle_underside", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_451f60c1dd854f958979f2060dfed4df.png", + "observation": "Second-subject tongue-cradle repeat strengthened testicle/tongue contact but stayed closer to a frontal centered mouth pose." + }, + { + "source_case": "cheek_compressed_low_head_branch", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_43f021ffd48d48e6857a4916347cbfb9.png", + "repeat_image": "/media/unraid/comfyui/output/agent_bridge/img_4c47705ae28445ba9ca3e0b25e257ea8.png", + "observation": "Cheek-compressed wording kept the head low but often let the shaft/body landmark dominate again, so it is not the default branch." + }, + { + "source_case": "patched_generated_route_validation", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_7eea4e949d8e4e2788bfb61f5dea655e.png", + "repeat_image": "/media/unraid/comfyui/output/agent_bridge/img_f66157870f38420c949d648442c28220.png", + "observation": "Exact generated-route validation after the route patch. Both turns kept the side-low face/thigh relationship and reduced shaft-tip dominance compared with the generated-route controls." + } + ], + "commit": "pending" + }, + { + "id": "ballsucking-9797979797-scrotal-skin-target-repeat", + "date": "2026-06-30", + "variant_key": "pov_ballsucking_low_head", + "seed": 9797979797, + "source": "sxcp_eval_mcp_batch_two_visible_women", + "result": "accepted", + "decision": "provisional_generator_patch", + "needs_expansion": true, + "baseline_prompt_summary": "Turns 286 and 291 retested the current generated side-low open-lips route across two visible women on fresh sampler seed 9797979797: face closest, cheek against inner thigh, scrotum at mouth, scrotum as mouth surface, testicles across open lips, tongue cupping from below, and viewer abdomen/thigh foreground.", + "candidate_prompt_summary": "Turns 288 and 293 used the scrotal-skin target branch: scrotal skin is the nearest mouth surface, spread across her open lips, while both testicles rest against the wet tongue from below, with head sideways on the viewer's inner thigh, cheek and jaw compressed into the thigh, eyes upward, and chin close to the pelvis.", + "observation": "Fresh-seed two-woman target-object expansion found another repeatable partial, not a proven solution. Turn 286 showed the patched control can already land tongue/testicle contact well on this seed, while turn 291 still drifted toward shaft/base contact. The scrotal-skin branch on turns 288 and 293 repeated the best target-object hierarchy across both women: balls/testicles stayed visible near the mouth, scrotal skin read as the mouth-facing surface, and the side-low cheek/thigh geometry survived better than the lower-lip and center-object branches. It still falls short of fully mouth-wrapped testicle sucking, and turn 290 showed that balls-first center-object wording can overfit into a multi-subject/body-layout artifact. Patch the provisional generator with the scrotal-skin nearest-surface clause and keep the route queued.", + "baseline_image": "/media/unraid/comfyui/output/agent_bridge/img_a91e7b1616ea4c32b12762da66b803e5.png", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_d428ae013a6346c997afc21e86145647.png", + "repeat_cases": [ + { + "source_case": "woman_a_scrotum_skin_to_lips", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_e1e4ae46bcde499cad84e26f561c86ef.png", + "observation": "Turn 288 was the strongest target-object image in the brunette row: side-low head position plus balls/testicles visible at the tongue/lip contact surface." + }, + { + "source_case": "woman_a_generated_route_control", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_a9413f748926472d90d72d1ec04f0bce.png", + "observation": "Turn 286 showed the current route can succeed on this seed, so the new branch is a narrow target-object refinement rather than a full route replacement." + }, + { + "source_case": "lower_lip_bowl_branch", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_5c140c0e38b144fa863ae2528069b3ad.png", + "repeat_image": "/media/unraid/comfyui/output/agent_bridge/img_9f5cb1605cde41c5b112356f7a47ef02.png", + "observation": "Lower-lip bowl wording made the result more frontal and did not beat the scrotal-skin target branch as a default." + }, + { + "source_case": "balls_first_center_object_branch", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_e9ecbec4fd0c48e0b580abde0ae85b42.png", + "repeat_image": "/media/unraid/comfyui/output/agent_bridge/img_ecd8233b70e341d2b5d379002722498d.png", + "observation": "Rejected as default: the balls-first center-object branch can overfit into a frontal or multi-subject/body-layout artifact." + } + ], + "commit": "pending" + }, + { + "id": "ballsucking-9898989898-scrotal-skin-route-validation", + "date": "2026-06-30", + "variant_key": "pov_ballsucking_low_head", + "seed": 9898989898, + "source": "sxcp_eval_mcp_generated_route_validation", + "result": "accepted", + "decision": "provisional_generator_patch", + "needs_expansion": true, + "baseline_prompt_summary": "No separate baseline in this validation batch. The probes rendered the patched generated ballsucking route directly on a fresh sampler seed after adding the scrotal-skin nearest-surface clause.", + "candidate_prompt_summary": "Turns 296 and 297 used the patched generated-route wording across two visible women: low side-pelvis POV, face closest, cheek against inner thigh, head low under pelvis, scrotum is the mouth surface, testicles resting across open lips, scrotal skin is the nearest mouth surface, and both testicles rest against her tongue from below.", + "observation": "Generated-route validation on fresh seed 9898989898 carried the scrotal-skin patch across both women. turns 296 and 297 both preserved side-low cheek/thigh geometry, kept the face low between viewer abdomen and inner thigh cues, and placed scrotum/testicles at the tongue/lip contact surface. This validates the provisional generator wording as a target-object improvement, but it still remains a partial because the outputs are tongue/lips-on-testicles rather than fully mouth-wrapped testicle sucking.", + "baseline_image": "/media/unraid/comfyui/output/agent_bridge/img_da11c8fe97614dc09110616ac28bada7.png", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_85dd87fb838a4b84bd4cdc519e1663f2.png", + "repeat_cases": [ + { + "source_case": "woman_a_patched_generated_route", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_da11c8fe97614dc09110616ac28bada7.png", + "observation": "Turn 296 preserved the side-low cheek/thigh geometry with scrotum/testicle contact against the tongue." + }, + { + "source_case": "woman_b_patched_generated_route", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_85dd87fb838a4b84bd4cdc519e1663f2.png", + "observation": "Turn 297 repeated the patched generated route on the blonde subject with the same target-object placement." + } + ], + "commit": "pending" + }, + { + "id": "ballsucking-5959595959-lip-oval-pocket-weak-case", + "date": "2026-06-30", + "variant_key": "pov_ballsucking_low_head", + "seed": 5959595959, + "source": "sxcp_eval_mcp_three_woman_batch", + "result": "inconclusive", + "decision": "needs_more_tests", + "needs_expansion": true, + "baseline_prompt_summary": "Turns 307, 311, and 315 retested the current generated side-low scrotal-skin route across three visible women on fresh sampler seed 5959595959: face closest, cheek against inner thigh, scrotum as mouth surface, testicles across open lips, scrotal skin nearest mouth surface, and both testicles against tongue from below.", + "candidate_prompt_summary": "Turns 308-310, 312-314, and 316-318 tested three positive-only contact axes across the same three women: lip-oval scrotal-skin wording, sideways mouth pocket wording, and chin-pelvis upward seal wording.", + "observation": "Fresh-seed weak case on turns 307-318: the batch preserved some low-pelvis and cheek/thigh geometry, especially in the generated controls and sideways mouth pocket branch, but every branch collapsed back toward shaft/glans collapse or generic oral contact instead of improving mouth-wrapped scrotum/testicle contact. Lip-oval wording made the scene more upright and shaft-centered, sideways mouth pocket kept the best side-low head placement but still targeted the shaft, and chin-pelvis upward seal became a frontal shaft-focused oral read. Do not patch generator wording from this batch; keep the current provisional route and continue target-object exploration or stronger control.", + "baseline_image": "/media/unraid/comfyui/output/agent_bridge/img_ca1c67d82126491087a36fe366fae52f.png", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_29d0010035244a2391e5dac683379543.png", + "repeat_cases": [ + { + "source_case": "woman_a_generated_route_control", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_ca1c67d82126491087a36fe366fae52f.png", + "observation": "Turn 307 kept low-pelvis framing and visible testicle area, but tongue/contact still read as shaft/glans-oriented." + }, + { + "source_case": "woman_a_lip_oval_scrotal_skin", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_7dea9ec927d24e79958cd191073e5106.png", + "observation": "Turn 308 shifted more upright and shaft-centered; lip-oval wording did not improve target-object contact." + }, + { + "source_case": "woman_a_sideways_mouth_pocket", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_b5ce9ae629a64f4d9d737f7d3ddb6f56.png", + "observation": "Turn 309 kept the strongest side-low cheek/thigh read for this subject, but the mouth still targeted the shaft/glans." + }, + { + "source_case": "woman_a_chin_pelvis_upward_seal", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_c4754fa9414e45dbafdf42ac7e9b7ee3.png", + "observation": "Turn 310 became a centered frontal shaft-focused oral read." + }, + { + "source_case": "woman_b_generated_route_control", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_70b3db265c1a440ab0745bd0e071d99a.png", + "observation": "Turn 311 kept low framing but repeated shaft/glans-oriented tongue contact." + }, + { + "source_case": "woman_b_lip_oval_scrotal_skin", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_944f294c00944a0b8cc387825e2ba5ec.png", + "observation": "Turn 312 did not beat the generated control and stayed shaft-centered." + }, + { + "source_case": "woman_b_sideways_mouth_pocket", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_29d0010035244a2391e5dac683379543.png", + "observation": "Turn 313 was the best-looking weak case: side-low cheek placement and contact near the base improved, but it remained tongue/base/shaft contact rather than mouth-wrapped testicle contact." + }, + { + "source_case": "woman_b_chin_pelvis_upward_seal", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_1b4aa4a913764132a9b5304feaa5b04d.png", + "observation": "Turn 314 stayed frontal and shaft-focused." + }, + { + "source_case": "woman_c_generated_route_control", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_f5063fc0ffbf4e78969b6be20e60de20.png", + "observation": "Turn 315 repeated the low frame but still drifted to shaft/glans oral contact." + }, + { + "source_case": "woman_c_lip_oval_scrotal_skin", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_6385d63401644364b8850dd84ffbc0b6.png", + "observation": "Turn 316 kept the woman low between thighs but made the contact shaft-centered." + }, + { + "source_case": "woman_c_sideways_mouth_pocket", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_3ca53a49bda44f8eacc24ff051ecd51d.png", + "observation": "Turn 317 repeated the side-low geometry but not the target-object contact." + }, + { + "source_case": "woman_c_chin_pelvis_upward_seal", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_40731b617b3e44adb08eb97fbc53c121.png", + "observation": "Turn 318 became a centered shaft-focused oral read with little target-object improvement." + } + ], + "commit": "pending" + }, + { + "id": "ballsucking-6060606060-occlusion-hand-weak-case", + "date": "2026-06-30", + "variant_key": "pov_ballsucking_low_head", + "seed": 6060606060, + "source": "sxcp_eval_mcp_three_woman_batch", + "result": "inconclusive", + "decision": "needs_more_tests", + "needs_expansion": true, + "baseline_prompt_summary": "Turns 319, 323, and 327 retested the current generated side-low scrotal-skin route across three visible women on fresh sampler seed 6060606060.", + "candidate_prompt_summary": "Turns 320-322, 324-326, and 328-330 tested three positive-only target-control axes across the same three women: scrotum foreground occlusion, under-scrotum tongue shelf, and hand-guided scrotum-to-mouth wording.", + "observation": "Fresh-seed occlusion weak case on turns 319-330: the current generated controls still gave the best partial side-low scrotum/testicle visibility, while the new target-control axes mostly became shaft-centered oral or hand/shaft-dominant. Scrotum foreground occlusion wording on turns 320, 324, and 328 pushed the shaft upright and centered instead of letting the scrotum cover the mouth. Under-scrotum tongue shelf wording on turns 321, 325, and 329 preserved some low-pelvis geometry but still targeted the shaft/glans. Hand-guided scrotum wording on turns 322, 326, and 330 made hands and shaft ownership dominate. Do not patch generator wording from this batch; the next useful step is a different target-object strategy or stronger control rather than more occlusion/hand synonyms.", + "baseline_image": "/media/unraid/comfyui/output/agent_bridge/img_c0e18f14e933448a84c48f53cd17a17b.png", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_61fccca8ff8f401c90af78cec0f71381.png", + "repeat_cases": [ + { + "source_case": "woman_a_generated_route_control", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_c0e18f14e933448a84c48f53cd17a17b.png", + "observation": "Turn 319 was the best row-A partial: low side-pelvis geometry and visible testicle area, but still tongue/testicle rather than mouth-wrapped contact." + }, + { + "source_case": "woman_a_scrotum_foreground_occlusion", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_a5d915be90f14230a4f7bd1e0bcd3c52.png", + "observation": "Turn 320 became upright shaft-centered oral; occlusion wording did not make the scrotum cover the mouth." + }, + { + "source_case": "woman_a_under_scrotum_tongue_shelf", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_5074ec045fc74369b028e709704bd3da.png", + "observation": "Turn 321 kept some low framing but still targeted the shaft/glans." + }, + { + "source_case": "woman_a_hand_guided_scrotum_to_mouth", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_226be5dc2ec0430a86ba6e56723a61fd.png", + "observation": "Turn 322 made hand support and shaft contact dominate." + }, + { + "source_case": "woman_b_generated_route_control", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_61fccca8ff8f401c90af78cec0f71381.png", + "observation": "Turn 323 repeated the strongest partial structure for the blonde subject: low side geometry with tongue/testicle contact, still short of mouth-wrapped contact." + }, + { + "source_case": "woman_b_scrotum_foreground_occlusion", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_21f45835d9934ff8a429aa8a191c2c7e.png", + "observation": "Turn 324 became centered shaft-focused oral." + }, + { + "source_case": "woman_b_under_scrotum_tongue_shelf", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_82f3821c648f4d59bb1ef5a447d74eb6.png", + "observation": "Turn 325 stayed shaft/glans centered despite the under-scrotum wording." + }, + { + "source_case": "woman_b_hand_guided_scrotum_to_mouth", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_43d053edfaa74877b919a966fc04d260.png", + "observation": "Turn 326 showed hand/shaft ownership more than target-object improvement." + }, + { + "source_case": "woman_c_generated_route_control", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_cbd5302e2b2a486a995b6c7b52db6d75.png", + "observation": "Turn 327 kept low side framing but remained a partial." + }, + { + "source_case": "woman_c_scrotum_foreground_occlusion", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_a3eb6a7b74514a9cacc0e06c723e6bc2.png", + "observation": "Turn 328 became shaft-centered." + }, + { + "source_case": "woman_c_under_scrotum_tongue_shelf", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_4df3c128c9804042bf71b83960b7bf2c.png", + "observation": "Turn 329 kept the centered shaft read and did not improve the target object." + }, + { + "source_case": "woman_c_hand_guided_scrotum_to_mouth", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_64d022a255214fcd856ebe9f036cd50e.png", + "observation": "Turn 330 repeated the hand/shaft-dominant failure mode." + } + ], + "commit": "pending" + }, + { + "id": "ballsucking-6161616161-mouth-axis-mixed-case", + "date": "2026-06-30", + "variant_key": "pov_ballsucking_low_head", + "seed": 6161616161, + "source": "sxcp_eval_mcp_three_woman_batch", + "result": "inconclusive", + "decision": "needs_more_tests", + "needs_expansion": true, + "baseline_prompt_summary": "Turns 331, 337, and 343 retested the current generated side-low scrotal-skin route across three visible women on fresh sampler seed 6161616161.", + "candidate_prompt_summary": "Turns 332-336, 338-342, and 344-348 tested five positive-only mouth-axis strategies across the same three women: exact mouth-sucking wording, single-testicle-between-lips wording, hanging balls below shaft wording, side-mouth wrap around a round testicle, and chin-pelvis lower-mouth target wording.", + "observation": "Fresh-seed mouth-axis mixed case on turns 331-348: the generated-route controls on turns 331 and 337 still gave the best repeatable partials, preserving low pelvis framing and tongue/testicle contact, while turn 343 collapsed onto shaft contact. Exact mouth-sucking, single-testicle, and hanging-balls-below-shaft wording mostly became shaft/glans oral or did not beat the controls. The side-mouth-wrap branch produced one useful side-oriented partial on turn 335 but collapsed on turns 341 and 347. The chin-pelvis lower-mouth branch produced a useful frontal target-object partial on turn 348, but the branch is not repeatable enough for generator wording. Do not patch generator wording from this batch; keep the current provisional route and continue expansion or stronger-control testing for the full mouth-wrapped target.", + "baseline_image": "/media/unraid/comfyui/output/agent_bridge/img_76c1699f5a4f42d1b4e0251fe78ccc8d.png", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_cc8516ddae214ad38f1ded46c5994157.png", + "repeat_cases": [ + { + "source_case": "woman_a_generated_route_control", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_76c1699f5a4f42d1b4e0251fe78ccc8d.png", + "observation": "Turn 331 was a useful generated-route partial: low pelvis framing with tongue/testicle contact." + }, + { + "source_case": "woman_b_generated_route_control", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_b03d714de09844168185f59009bec43d.png", + "observation": "Turn 337 repeated the strongest generated-route partial on a second visible woman." + }, + { + "source_case": "woman_c_generated_route_control", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_4e5c964640ba40d3992b62df9404c81e.png", + "observation": "Turn 343 showed the control can still collapse to shaft/glans contact." + }, + { + "source_case": "woman_a_exact_mouth_sucking_testicles", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_e86c1935e9704aed91353695878bb3f4.png", + "observation": "Turn 332 made literal exact mouth-sucking wording become shaft/glans-focused." + }, + { + "source_case": "woman_a_side_mouth_wrap_round_testicle", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_c8382f1a7d074d9a95cf12d420c9f0e0.png", + "observation": "Turn 335 was the best side-oriented candidate, with the side of the mouth contacting a round testicle-like target while preserving cheek/thigh geometry." + }, + { + "source_case": "woman_b_side_mouth_wrap_round_testicle", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_d5cd7f4d4d4a4cdeba3d8707ca1f9c7d.png", + "observation": "Turn 341 showed the same side-mouth wording can collapse back to shaft contact." + }, + { + "source_case": "woman_c_chin_pelvis_lower_mouth_target", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_cc8516ddae214ad38f1ded46c5994157.png", + "observation": "Turn 348 was a useful frontal partial: mouth lower, testicles visible at the tongue/lip surface, and less shaft dominance than most new branches." + } + ], + "commit": "pending" + }, + { + "id": "ballsucking-7171717171-pelvis-valley-weak-case", + "date": "2026-06-30", + "variant_key": "pov_ballsucking_low_head", + "seed": 7171717171, + "source": "sxcp_eval_mcp_three_woman_batch", + "result": "inconclusive", + "decision": "needs_more_tests", + "needs_expansion": true, + "baseline_prompt_summary": "Turns 349, 355, and 361 retested the current generated side-low scrotal-skin route across three visible women on fresh sampler seed 7171717171.", + "candidate_prompt_summary": "Turns 350-354, 356-360, and 362-366 tested five positive-only body-plane strategies across the same three women: flat pelvis-valley wording, thigh tunnel mouth target wording, pubic-hair mouth-line wording, low-cushion chin anchor wording, and pelvis-edge target-first wording.", + "observation": "Fresh-seed pelvis-valley weak case on turns 349-366: flat pelvis-valley wording repeated a strong body-plane correction on turns 350, 356, and 362, making the viewer read flatter and placing the woman's face inside a thigh-wall pelvis valley closer to atlas references. It still centered the shaft and did not improve balls/testicle contact, so it is not a generator patch for ballsucking. Thigh-tunnel and pubic-hair mouth-line wording mostly repeated the same shaft-centered target. Low-cushion chin-anchor and pelvis-edge target-first wording drifted into open-thigh/presentation geometry or target-object ambiguity. Record flat pelvis-valley as an orientation hint for future control or a separate flat-pelvis oral branch, but keep the current ballsucking generator route unchanged.", + "baseline_image": "/media/unraid/comfyui/output/agent_bridge/img_19e5b7e526384ab98357cadf4c11b64c.png", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_371499f482b545868949d91345f4dcdc.png", + "repeat_cases": [ + { + "source_case": "woman_a_generated_route_control", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_19e5b7e526384ab98357cadf4c11b64c.png", + "observation": "Turn 349 kept the side-low generated-route partial but did not improve full target contact." + }, + { + "source_case": "woman_a_flat_pelvis_valley", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_371499f482b545868949d91345f4dcdc.png", + "observation": "Turn 350 gave a strong flat viewer and thigh-wall pelvis-valley body plane, but the mouth target remained shaft-centered." + }, + { + "source_case": "woman_b_flat_pelvis_valley", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_63744a0dfdfb4842adeda36dc3e4cc6f.png", + "observation": "Turn 356 repeated the atlas-like flat pelvis-valley orientation on a second woman while keeping shaft-centered contact." + }, + { + "source_case": "woman_c_flat_pelvis_valley", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_72fb133006c6479fb875f5b5b3284fa6.png", + "observation": "Turn 362 repeated the body-plane correction on a third woman, again as shaft-centered oral rather than ballsucking." + }, + { + "source_case": "woman_a_low_cushion_chin_anchor", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_4387ece461dc4067bf263aae4696e821.png", + "observation": "Turn 353 drifted into wrong open-thigh/presentation geometry and target-object ambiguity." + }, + { + "source_case": "woman_b_pelvis_edge_target_first", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_8652f323fdfd4076a1caa2b14bc317cc.png", + "observation": "Turn 360 repeated the pelvis-edge branch as a wrong open-thigh/presentation read, not the atlas ballsucking target." + } + ], + "commit": "pending" + }, + { + "id": "ballsucking-7272727272-flat-target-hybrid-weak-case", + "date": "2026-06-30", + "variant_key": "pov_ballsucking_low_head", + "seed": 7272727272, + "source": "sxcp_eval_mcp_three_woman_batch", + "result": "inconclusive", + "decision": "needs_more_tests", + "needs_expansion": true, + "baseline_prompt_summary": "Turns 367, 373, and 379 retested the current generated side-low scrotal-skin route across three visible women on fresh sampler seed 7272727272.", + "candidate_prompt_summary": "Turns 368-372, 374-378, and 380-384 tested five positive-only hybrid strategies across the same three women: flat-valley scrotal-skin target wording, valley-floor open-lips wording, upper-frame shaft lower-scrotum wording, cropped upper-shaft valley-mouth wording, and side-low flat-valley hybrid wording.", + "observation": "Fresh-seed flat-target hybrid weak case on turns 367-384: combining flat pelvis-valley body-plane wording with scrotal-skin/open-lips target wording did not beat the current route. The flat-valley scrotal-skin target branch on turns 368, 374, and 380 repeated the better flat viewer/thigh-wall body plane but remained shaft-centered, with hand/shaft support taking over on turn 380. Valley-floor, upper-frame-shaft/lower-scrotum, and cropped-shaft branches also stayed shaft-centered or drifted into open-thigh presentation. Side-low flat-valley hybrid wording produced useful side-low look hints on turns 372, 378, and 384, but target contact remained ambiguous or shaft-centered. Stop the ballsucking text search here for now; do not patch generator wording from this batch. Preserve the existing side-low scrotal-skin partial and treat full mouth-wrapped target contact as likely to need stronger control.", + "baseline_image": "/media/unraid/comfyui/output/agent_bridge/img_a74296b9c1d643b3a7c490224c296be8.png", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_a7d2ac6b80f646d7a104cddc4e876442.png", + "repeat_cases": [ + { + "source_case": "woman_a_flat_valley_scrotal_skin_target", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_a7d2ac6b80f646d7a104cddc4e876442.png", + "observation": "Turn 368 had good flat-valley composition but clear shaft/mouth contact." + }, + { + "source_case": "woman_b_flat_valley_scrotal_skin_target", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_23838e7ddf1842f189c6e17f4e60ea5d.png", + "observation": "Turn 374 repeated the flat body plane on a second woman while staying shaft-centered." + }, + { + "source_case": "woman_c_flat_valley_scrotal_skin_target", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_c6d87ca3dbdd48a3a5b4451c5e606587.png", + "observation": "Turn 380 repeated the flat body plane but hand/shaft support took over the target." + }, + { + "source_case": "woman_a_side_low_flat_valley_hybrid", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_f1538b98437d4cf983f27a55fe4961d6.png", + "observation": "Turn 372 was a useful side-low look hint, but target contact remained shaft-centered or ambiguous." + }, + { + "source_case": "woman_b_side_low_flat_valley_hybrid", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_c3a22188b8bb4fc08d29d87401ac97fb.png", + "observation": "Turn 378 repeated the side-low look family without proving the target." + }, + { + "source_case": "woman_c_side_low_flat_valley_hybrid", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_3b1c740073494ff3bd8339af7530381e.png", + "observation": "Turn 384 kept the side-low look but did not make target contact reliable." + } + ], + "commit": "pending" + }, + { + "id": "footjob-238365845574312-overlapping-soles", + "date": "2026-06-29", + "variant_key": "pov_footjob_frontal_sole_stroke", + "seed": 238365845574312, + "source": "sxcp_eval_mcp", + "result": "accepted", + "decision": "prompt_guide_rule", + "baseline_prompt_summary": "Generic footjob prompt placed both feet near the shaft but left the shaft standing mostly free and the sole pressure weak.", + "candidate_prompt_summary": "Candidate used large overlapping soles, curled toes, and a shaft trapped between the two soles with only a narrow visible strip.", + "observation": "Same-sampler A/B on seed 238365845574312 showed that generic sole/toe wording already produced a usable frontal POV footjob, but the feet sat beside the shaft more than around it. The accepted prompt made the feet dominate the lower foreground and partly hide the shaft between pressed soles, improving atlas-like foot scale and contact compression. Keep this as a prompt-guide rule, not a proven catalog promotion, until it repeats on another controlled seed.", + "baseline_image": "/media/unraid/comfyui/output/agent_bridge/img_046a6849afe242a6b4640da5ccc9ee7c.png", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_1c2a96385c2b4cf8939531c47f2c3bc3.png", + "commit": "pending" + }, + { + "id": "footjob-3434343434-two-woman-large-sole-clamp", + "date": "2026-06-30", + "variant_key": "pov_footjob_frontal_sole_stroke", + "seed": 3434343434, + "source": "sxcp_eval_mcp_batch", + "result": "accepted", + "decision": "provisional_generator_patch", + "needs_expansion": true, + "baseline_prompt_summary": "Turns 175 and 179 used current generator-style frontal footjob wording across two visible women: hips back, torso behind raised legs, knees open, soles wrapped around the upright shaft, toes curled around both sides.", + "candidate_prompt_summary": "Turns 176 and 180 used tight frontal POV close-up wording with two large soles dominating the lower center foreground, inner arches pressing inward from both sides, toe pads curled around both edges, and face/torso visible behind the large foreground feet. Turns 177 and 181 tested a cross-foot side-press alternate.", + "observation": "Same-sampler two-woman expansion showed the baseline already produced valid frontal POV footjob images, so this is not a structural failure. The large two-sole clamp axis repeated on both women and improved foot scale, foreground dominance, toe curl, and contact compression over the valid baselines. The cross-foot side-press axis also produced useful atlas-ref-86 style alternates, but it is a separate branch and weaker as the default because the two-sole enclosure is less consistent. Mirror the two-sole clamp wording into the generator provisionally and keep the route queued for another seed before marking it proven.", + "baseline_image": "/media/unraid/comfyui/output/agent_bridge/img_b6c3d0a9220744d2b5fdfeb8cf8f6747.png", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_1582ef852156442a9335f1c593610eb7.png", + "repeat_cases": [ + { + "source_case": "turn176 woman_a_large_two_sole_clamp", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_8e9ae76552a8461688a73360468c7872.png", + "observation": "Strong main candidate: large soles dominate the foreground, toes curl inward, and the shaft is compressed between the feet while the face/body remain visible behind." + }, + { + "source_case": "turn180 woman_b_large_two_sole_clamp", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_1582ef852156442a9335f1c593610eb7.png", + "observation": "Repeated the same main candidate on a second woman: large sole scale, centered compression, visible face and torso behind the feet." + }, + { + "source_case": "turn177 woman_a_cross_foot_side_press", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_37e1cd0b44744adab6023162e4b2ef7d.png", + "observation": "Useful alternate branch: one foot crosses and presses sideways into the upper shaft/glans, similar to atlas ref 86, but it sacrifices the two-sole clamp." + }, + { + "source_case": "turn181 woman_b_cross_foot_side_press", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_7389212d9ffa400c908fc8b19015b0f1.png", + "observation": "Cross-foot branch repeated but weaker than turn 177 because the feet separated more and compression was less clear." + } + ], + "commit": "pending" + }, + { + "id": "footjob-3434343434-generated-route-visible-glans-correction", + "date": "2026-06-30", + "variant_key": "pov_footjob_frontal_sole_stroke", + "seed": 3434343434, + "source": "sxcp_eval_mcp_generated_route", + "result": "accepted", + "decision": "provisional_generator_patch", + "needs_expansion": true, + "baseline_prompt_summary": "Turn 183 used the first generated-route footjob patch: two large soles dominated the lower center foreground, inner arches pressed inward around the upright shaft, toes curled, and face/torso stayed behind the feet.", + "candidate_prompt_summary": "Turns 184-186 kept two large soles dominant and added explicit visibility/contact hierarchy: the soles clamp the upright shaft between them and the glans rises between the compressed feet. Turn 188 used the patched generated route with the same clamp-plus-visible-glans cue; turn 187 repeated the cross-foot side-press alternate.", + "observation": "The generated route at turn 183 improved foot scale but failed the footjob contact target because the shaft/contact disappeared into a feet-presented-to-viewer composition. The corrective batch showed that the route needs a readable centered gap or compression cue: turns 184, 185, and 186 restored visible shaft/glans contact between the soles, while turn 187 remained a useful but weaker cross-foot alternate. Turn 188 then validated the patched generated route itself: soles remained large in the foreground, the shaft stayed visible between them, and the contact was readable. Keep the clamp-plus-visible-glans language and keep the route queued for another seed/source expansion.", + "baseline_image": "/media/unraid/comfyui/output/agent_bridge/img_b09a0003707a48468c972f07f7a10824.png", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_ff3fb77ef09a43feb2b67ba7d344ba0b.png", + "repeat_cases": [ + { + "source_case": "turn184 visible_glans_between_compressed_soles", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_856bf7e085f64a73bfca2cb400227952.png", + "observation": "Best generated-route correction: large soles dominate, shaft stays visible in the center, and the glans rises between the compressed feet." + }, + { + "source_case": "turn185 center_gap_full_shaft_visible", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_d76f9f9f2819445bba918f62ef40f34e.png", + "observation": "Second correction repeat: centered visible shaft gap between the soles and readable toe/arch pressure." + }, + { + "source_case": "turn186 vertical_v_clamp_visible_tip", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_23c02a7202934c8bada1e564c9fa238e.png", + "observation": "Third correction repeat: V-shaped sole clamp keeps the glans visible above the toe line, with slightly less foreground sole dominance." + }, + { + "source_case": "turn187 cross_foot_visible_side_press", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_472e61ef3bad4948b29ee2c7a506e2ed.png", + "observation": "Useful alternate branch with visible side contact, but weaker as the default because it sacrifices the two-sole enclosure." + }, + { + "source_case": "turn188 generated_route_visible_glans_patch", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_ff3fb77ef09a43feb2b67ba7d344ba0b.png", + "observation": "Patched generated route validation: large soles dominate, the shaft remains visible between them, and the contact reads as a footjob rather than feet presented to the viewer." + } + ], + "commit": "pending" + }, + { + "id": "footjob-6868686868-two-woman-overlapping-visible-strip", + "date": "2026-06-30", + "variant_key": "pov_footjob_frontal_sole_stroke", + "seed": 6868686868, + "source": "sxcp_eval_mcp_batch", + "result": "accepted", + "decision": "provisional_generator_patch", + "needs_expansion": true, + "baseline_prompt_summary": "Turns 225 and 229 repeated the current generated-route footjob wording across two women: large soles dominate the lower center foreground, clamp the upright shaft, toes curl around both edges, and the glans rises between the compressed feet.", + "candidate_prompt_summary": "Turns 226, 227, and 231 showed that tighter wording with two large overlapping soles and a narrow visible strip of shaft and glans between the compressed feet restores readable contact while keeping the woman and coworking scene visible. Turns 228 and 232 repeated the cross-foot side-press alternate as a separate branch. Turn 233 validated the patched generated route with the same overlapping-sole/narrow-strip wording.", + "observation": "Fresh-seed two-woman expansion confirmed the generated route still needs a stricter visibility cue. The generated-route prompts preserved foreground sole scale and office-chair/coworking layout but hid the shaft/contact on both women. The overlapping-sole narrow-strip wording repeated across both women and restored the visible shaft/glans between the feet while keeping the soles dominant. The patched generated-route prompt then rendered successfully on turn 233 with overlapping soles, visible centered shaft/glans strip, viewer body cues, and coworking depth. Patch the footjob route from generic large-sole clamp to large overlapping soles plus a narrow visible strip of shaft and glans; keep cross-foot side-press as an alternate guide branch.", + "baseline_image": "/media/unraid/comfyui/output/agent_bridge/img_8481ac6e26064c0a92698a88ac13db31.png", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_7222dec23b7d4b37a43dcab87385f3c2.png", + "repeat_cases": [ + { + "source_case": "turn226 woman_a_tight_visible_center_gap", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_544d63eac93e4d61b721b8f9b490b75b.png", + "observation": "Visible center-gap wording restored a readable shaft and glans between the soles while preserving large foreground feet and office depth." + }, + { + "source_case": "turn227 woman_a_overlapping_soles_narrow_strip", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_e509490fefe6480a9c4879ee9e2828f6.png", + "observation": "Best woman-A default candidate: overlapping soles stayed dominant and a narrow visible shaft/glans strip remained centered between the feet." + }, + { + "source_case": "turn231 woman_b_overlapping_soles_narrow_strip", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_53581c4fd5774713acaec0a795d0de04.png", + "observation": "Second-woman repeat of the default candidate: overlapping soles, visible centered strip, toe/arch pressure, and coherent coworking setting." + }, + { + "source_case": "turn232 woman_b_cross_foot_side_press_alternate", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_66a09c26f6204d62985a37fb311dda24.png", + "observation": "Useful alternate branch with a crossed sole pressing into the upper shaft/glans, but weaker as the default because it gives up the two-sole enclosure." + }, + { + "source_case": "turn233 generated_route_after_overlap_visible_strip_patch", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_7222dec23b7d4b37a43dcab87385f3c2.png", + "observation": "Patched generated-route validation: overlapping soles stay large, the centered shaft/glans strip remains visible, viewer body cues anchor the foreground, and coworking depth remains readable." + } + ], + "commit": "pending" + }, + { + "id": "footjob-7373737373-generated-route-repeat-proven", + "date": "2026-06-30", + "variant_key": "pov_footjob_frontal_sole_stroke", + "seed": 7373737373, + "source": "sxcp_eval_mcp_batch_two_visible_women", + "result": "accepted", + "decision": "proven_with_evidence", + "baseline_prompt_summary": "Prior footjob evidence established the progression from generic two-sole clamp to overlapping soles with a narrow visible shaft/glans strip, including generated-route validation on turn 233.", + "candidate_prompt_summary": "Fresh sampler seed 7373737373 retested the patched generated route across two visible women. Turns 264 and 267 used the generated two large overlapping soles wording with a narrow visible strip of shaft and glans between the compressed feet. Turns 265 and 268 used a tight center-gap visible-glans branch, and turns 266 and 269 tested the cross-foot side-press alternate.", + "observation": "Fresh-seed generated-route repeat confirms the footjob default is now reliable enough for proven status. Turns 264 and 267 both preserved large overlapping soles as the dominant lower-frame objects, visible centered shaft/glans strip between compressed feet, toe/arch pressure, face/torso behind the feet, and coworking depth. Turns 265 and 268 repeated the visible center-gap/glans axis as a useful supporting branch. Cross-foot turns 266 and 269 remained valid alternates but are less suitable as default because they sacrifice the two-sole enclosure. Promote the overlapping-sole/narrow-strip generated route to proven while keeping cross-foot side-press as a guide alternate.", + "baseline_image": "/media/unraid/comfyui/output/agent_bridge/img_7222dec23b7d4b37a43dcab87385f3c2.png", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_25458cf340d5458da88f964f0d84be65.png", + "repeat_cases": [ + { + "source_case": "woman_b_generated_overlapping_visible_strip", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_9f7f8700b0744ef5a6e69edf4fcd78b6.png", + "observation": "Second-subject generated-route repeat preserved overlapping foreground soles, visible centered shaft/glans strip, toe pressure, and coworking depth." + }, + { + "source_case": "tight_center_gap_visible_glans", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_0c2baa36dec04c019486864dd4fb1f70.png", + "repeat_image": "/media/unraid/comfyui/output/agent_bridge/img_f28de88fb87b4838828be17e71fc9060.png", + "observation": "Visible center-gap branch repeated on both women and supports the same shaft/glans visibility rule." + }, + { + "source_case": "cross_foot_side_press_alternate", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_6010012bf15b4daebc626c043fc09db6.png", + "repeat_image": "/media/unraid/comfyui/output/agent_bridge/img_100712e4223746d690f434860dc104d6.png", + "observation": "Cross-foot branch remained valid as an alternate atlas family, but it is not the default because it gives up the two-sole enclosure." + } + ], + "commit": "pending" + }, + { + "id": "fingering-238365845574312-hand-bedding-foreground", + "date": "2026-06-29", + "variant_key": "pov_fingering_reclined_open_thighs", + "seed": 238365845574312, + "source": "sxcp_eval_mcp", + "result": "inconclusive", + "decision": "needs_more_tests", + "baseline_prompt_summary": "Positive-only fingering retry kept male participant and explicit-hardcore foreground triggers, which produced a usable hand but also inserted lower-body foreground artifacts.", + "candidate_prompt_summary": "Candidate removed male and explicit-hardcore foreground triggers, made the partner hand the dominant object, and said pink bedding fills the lower foreground around the hand.", + "observation": "Same-sampler A/B on seed 238365845574312 showed that positive-only hand hierarchy alone was not enough while the prompt still cued a male first-person hardcore foreground. The candidate produced a readable manual-contact image, but it used a single coworking character/location setup and solved the lower foreground by adding pink bedding to an office-like scene. Treat this as an inconclusive prompt-only retry note, not a prompt-guide rule, until the manual-contact geometry works across multiple characters/locations with location-consistent foreground anchors.", + "baseline_image": "/media/unraid/comfyui/output/agent_bridge/img_c8fb7d752c844f54bb10db02d455005e.png", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_0494384224c4494a9b6ec4589a46e928.png", + "commit": "pending" + }, + { + "id": "fingering-238365845574312-office-chair-hand-hierarchy", + "date": "2026-06-29", + "variant_key": "pov_fingering_reclined_open_thighs", + "seed": 238365845574312, + "source": "sxcp_eval_mcp_codexmcptest_46_47", + "result": "accepted", + "decision": "prompt_guide_rule", + "baseline_prompt_summary": "Two source-case baselines used generic male-participant POV fingering/manual-contact wording in a coworking scene and both collapsed into shaft-forward lower-foreground anatomy.", + "candidate_prompt_summary": "Candidate kept the same subjects, seed, and coworking scene while making one foreground hand the first visual object, open-thigh geometry second, visible woman face/torso third, and office-chair/coworking depth fourth.", + "observation": "Same-sampler A/B on seed 238365845574312 repeated across CodexMCPTest source subjects 46 and 47. The candidate removed the shaft-forward artifact in both cases without adding bedding or changing the office/coworking location family. Treat the hand-first hierarchy and location-compatible office-chair anchors as a prompt-guide rule, while keeping the catalog variant candidate until it repeats on another seed or location family.", + "baseline_image": "/media/unraid/comfyui/output/agent_bridge/img_dc15a882605d403ea5ac022a8905bdc8.png", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_e186eaea164645f6b1171066b3080280.png", + "repeat_cases": [ + { + "source_case": "CodexMCPTest 47", + "baseline_image": "/media/unraid/comfyui/output/agent_bridge/img_9d7c725374d24586bad1719ecfebbeb6.png", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_a4a9757eb268414e8093fc2ac0c973eb.png" + } + ], + "commit": "pending" + }, + { + "id": "fingering-238365845574312-generated-route-office-hand", + "date": "2026-06-29", + "variant_key": "pov_fingering_reclined_open_thighs", + "seed": 238365845574312, + "generator_seed": 4303, + "source": "sxcp_eval_mcp_experimental_route", + "result": "inconclusive", + "decision": "needs_more_tests", + "baseline_prompt_summary": "The final generated Krea prompt for manual/fingering routes only carried weak hand-between-legs wording and lacked the accepted hand-first hierarchy plus office-chair/coworking anchors.", + "candidate_prompt_summary": "Experimental generated-route prompt made one foreground hand the largest lower-frame object, placed two fingers at the vulva/clit contact point, preserved open-thigh geometry, and added office-chair/coworking anchors for the coworking scene.", + "observation": "Experimental generated-route probe used generator seed 4303 and sampler seed 238365845574312. The rendered image preserved the coworking office setting and produced a clean hand-led manual-contact composition without the shaft-forward foreground artifact that appeared in generic baseline prompts. This is promising, but one generated-route render is insufficient to justify a production generator patch; collect a broader seed/subject/location matrix first.", + "baseline_image": "", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_5a13d12a67f24a2eb65fe95c23d6be0f.png", + "commit": "pending" + }, + { + "id": "fingering-987654321-source50-office-chair-hand-hierarchy", + "date": "2026-06-29", + "variant_key": "pov_fingering_reclined_open_thighs", + "seed": 987654321, + "source": "sxcp_eval_mcp_codexmcptest_50", + "result": "accepted", + "decision": "provisional_generator_patch", + "baseline_prompt_summary": "CodexMCPTest source 50 subject with generic male first-person fingering wording and lower-foreground body cues in the coworking scene.", + "candidate_prompt_summary": "Same subject, location family, camera family, and sampler seed with hand-first hierarchy: one foreground hand as the largest lower-frame object, two fingers at the vulva/clit contact point, open-thigh geometry second, and office-chair/coworking anchors behind the body.", + "observation": "Same-sampler A/B on seed 987654321 added a new source subject and second sampler seed to the fingering matrix. The baseline preserved coworking context but collapsed into shaft/body-dominant lower foreground anatomy. The candidate preserved the coworking office setting and produced a clear hand-led manual-contact composition with the active fingers readable. After the provisional category-exit rule was added, this evidence was mirrored into the manual/fingering generator as a provisional improvement over baseline while the catalog route remains candidate pending broader repetition.", + "baseline_image": "/media/unraid/comfyui/output/agent_bridge/img_03c3360ab1d84f458f59ec6f5a830831.png", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_49de5e26fd79446c81d9995c4a8633e1.png", + "commit": "pending" + }, + { + "id": "fingering-1357913579-source52-own-hand-weak-case", + "date": "2026-06-29", + "variant_key": "pov_fingering_reclined_open_thighs", + "seed": 1357913579, + "source": "sxcp_eval_mcp_codexmcptest_52", + "result": "inconclusive", + "decision": "needs_more_tests", + "baseline_prompt_summary": "CodexMCPTest source 52 subject with generic male first-person fingering/manual-contact wording and coworking lower-foreground body cues.", + "candidate_prompt_summary": "Same subject, location family, camera family, and sampler seed tested hand-first hierarchy twice: camera-side foreground hand still failed, while same-woman right-hand ownership plus chair-cushion lower-frame structure removed the lower-foreground anatomy artifact.", + "observation": "Same-sampler source-52 A/B on seed 1357913579 shows why the fingering finding is still early. The generic baseline preserved the coworking setting but collapsed into shaft/body-dominant lower foreground anatomy. A camera-side hand-first candidate preserved the office scene and added a visible hand, but still left the wrong lower-foreground anatomy dominant. A second candidate made the active hand explicitly the woman's own right hand and used the office chair cushion/arms as the positive lower-frame structure; that produced a clean hand-led composition. Record this as a weak-case prompt note, not a generator patch, because the successful wording changed hand ownership away from the participant-hand POV route.", + "baseline_image": "/media/unraid/comfyui/output/agent_bridge/img_5a7bb03ba35f4c0a811d948cb23d641e.png", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_ed899d764fd140eba95b6e217fef6d93.png", + "intermediate_candidates": [ + { + "name": "camera-side hand-first hierarchy", + "image": "/media/unraid/comfyui/output/agent_bridge/img_234e3f243d344701b21b692b48681129.png", + "result": "failed" + } + ], + "commit": "pending" + }, + { + "id": "wand-246813579-source48-single-continuous-device", + "date": "2026-06-29", + "variant_key": "pov_wand_foreground_tool_contact", + "seed": 246813579, + "source": "sxcp_eval_mcp_codexmcptest_48", + "result": "accepted", + "decision": "prompt_guide_rule", + "baseline_prompt_summary": "CodexMCPTest source 48 subject with generic handheld wand toy-contact wording in the coworking scene.", + "candidate_prompt_summary": "Same subject, location family, camera family, and sampler seed with a single continuous teal wand-style massager: rounded bulb head at the central contact point, smooth handle angled in from the bottom right inside the visible hand, open-thigh geometry second, face/torso and coworking depth behind.", + "observation": "Same-sampler A/B on seed 246813579 produced useful first wand evidence. The baseline preserved the coworking scene and found a toy-contact pose, but Krea2 split the cue into a contact toy plus a second wand-like foreground object. The candidate kept one continuous teal wand device with a readable hand grip and bulb-head contact while preserving the office chair/coworking setting and the visible subject. Record this as first prompt-guide evidence for wand hierarchy only; do not promote generator defaults until it repeats on another source or seed.", + "baseline_image": "/media/unraid/comfyui/output/agent_bridge/img_79b79b76bab24a298fc7d35b95760030.png", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_fdb4bf6e6a504bab95b49817776123dc.png", + "commit": "pending" + }, + { + "id": "wand-8642086420-two-woman-teal-generated-route", + "date": "2026-06-30", + "variant_key": "pov_wand_foreground_tool_contact", + "seed": 8642086420, + "generator_seed": 46, + "source": "sxcp_eval_mcp_generated_route", + "result": "accepted", + "decision": "provisional_generator_patch", + "needs_expansion": true, + "baseline_prompt_summary": "Turns 189 and 193 used generic first-person wand-toy wording across two visible women in a coworking office-chair scene: open thighs, face/torso behind, viewer hand holding a handheld wand vibrator near the central contact point.", + "candidate_prompt_summary": "Turns 190 and 194 used the guide's single-continuous-device axis across both women: a single continuous teal wand-style massager as the largest lower-frame object, rounded bulb head pressed flat to the central vulva/clit contact point, smooth handle angled from the bottom right inside the viewer hand, and open thighs forming a V around the foreground wand. Turn 197 validated the patched generated route from generator row seed 46 on the same sampler seed.", + "observation": "The generic baselines kept the open-thigh office-chair geometry but made the toy read as a generic handheld object with weaker bulb/contact structure. The teal lower-right axis repeated on two women and preserved the coworking chair context while improving device continuity, visible hand ownership, and central bulb-head contact. The black close-bulb axis drifted toward mouth/face contact and is rejected as a default; the upper-left pale wand branch is usable as an atlas-107 style alternate but less stable. Turn 197 confirmed the actual generated Krea route now emits and renders the teal single-continuous-device hierarchy, so mirror it as a provisional generator patch and keep the route queued for another source/seed expansion.", + "baseline_image": "/media/unraid/comfyui/output/agent_bridge/img_bdd5f9314c49472dbc7ef68f5a5ff2f9.png", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_15e2cacb9ea3425c973141b16e285fb9.png", + "repeat_cases": [ + { + "source_case": "turn190 woman_a_teal_continuous_lower_right", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_4561c508672c4193ae881ff71f103849.png", + "observation": "Strong manual prompt-axis winner: one continuous teal device, visible hand on handle, large bulb at the central contact point, open-thigh office-chair context intact." + }, + { + "source_case": "turn194 woman_b_teal_continuous_lower_right", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_b6a5892a80a745888590142344a25d19.png", + "observation": "Second-woman repeat of the teal lower-right axis with continuous device, central bulb contact, and coherent coworking chair context." + }, + { + "source_case": "turn191 woman_a_black_close_bulb_hand", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_80e5260b13534d84898157023fce15a4.png", + "observation": "Rejected default branch: the black device became a face/mouth-directed object and lost the central toy-contact target." + }, + { + "source_case": "turn192 woman_a_upper_left_handle_entry", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_9cd179f163c64daca838025172f22e05.png", + "observation": "Useful atlas-107 style alternate with diagonal upper-left handle entry, but the oversized pale handle is less stable than the teal lower-right default." + }, + { + "source_case": "turn197 generated_route_seed46_teal_wand", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_15e2cacb9ea3425c973141b16e285fb9.png", + "observation": "Patched generated-route validation: the final formatter output rendered a continuous teal wand, visible viewer hand, central bulb contact, and open-thigh office-chair geometry." + } + ], + "commit": "pending" + }, + { + "id": "wand-7979797979-two-woman-teal-repeat-proven", + "date": "2026-06-30", + "variant_key": "pov_wand_foreground_tool_contact", + "seed": 7979797979, + "source": "sxcp_eval_mcp_batch", + "result": "accepted", + "decision": "generator_patch", + "baseline_prompt_summary": "Turns 234 and 238 repeated the current generated-route teal wand wording across two visible women: single continuous teal wand-style massager, rounded bulb head flat at the central contact point, smooth handle from the lower-right viewer hand, open-thigh V-frame, visible face/torso, and coworking office-chair depth.", + "candidate_prompt_summary": "Turns 235 and 239 emphasized contact-first bulb-head wording, turns 236 and 240 tested the white upper-left branch, and turns 237 and 241 tested a large-bulb foreground branch. The single continuous teal wand generated route repeated cleanly across both women; the white upper-left branch is a valid atlas alternate, while the oversized bulb branch can hide exact contact.", + "observation": "Fresh-seed two-woman expansion confirmed the generated wand route is now repeatable enough for proven catalog status. Turns 234 and 238 preserved one continuous teal device, visible viewer hand/handle, central bulb-head contact, open-thigh office-chair geometry, visible subject, and coworking depth. Contact-first teal variants also worked, and the white upper-left branch repeated as a useful alternate matching atlas refs with upper-left handle entry. The large-bulb branch is rejected as a default because it can obscure the exact contact and overtake the lower frame. Promote the catalog route to proven while keeping the upper-left wand as a guide alternate.", + "baseline_image": "/media/unraid/comfyui/output/agent_bridge/img_a20f02985c804ed781964db193cd279b.png", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_61073156e24944928de32ced40864b9e.png", + "repeat_cases": [ + { + "source_case": "turn234 woman_a_generated_teal_lower_right", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_a20f02985c804ed781964db193cd279b.png", + "observation": "Generated-route repeat on woman A: continuous teal device, visible viewer hand, central bulb contact, open-thigh chair geometry, and coworking depth." + }, + { + "source_case": "turn238 woman_b_generated_teal_lower_right", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_61073156e24944928de32ced40864b9e.png", + "observation": "Generated-route repeat on woman B with the same teal lower-right hierarchy and clean device continuity." + }, + { + "source_case": "turn240 woman_b_white_wand_upper_left_branch", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_3211350ed56349649a17d1faa7f94add.png", + "observation": "Useful alternate branch: pale wand enters from the upper left with a visible hand and central contact while preserving open-thigh geometry." + }, + { + "source_case": "turn241 woman_b_large_bulb_between_thighs", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_42c90855513249a191aba0fe05be747f.png", + "observation": "Rejected default branch: oversized bulb remains continuous but hides exact contact and can introduce extra handle/device clutter." + } + ], + "commit": "pending" + }, + { + "id": "ready-1123581321-source52-wet-aftermath-hierarchy", + "date": "2026-06-29", + "variant_key": "pov_ejaculation_aftermath_open_thigh_candidate", + "seed": 1123581321, + "source": "sxcp_eval_mcp_codexmcptest_52", + "result": "accepted", + "decision": "provisional_generator_patch", + "baseline_prompt_summary": "CodexMCPTest source 52 subject with generic post-ejaculation aftermath wording in a coworking office-chair scene.", + "candidate_prompt_summary": "Same subject, location family, camera family, and sampler seed with wet aftermath hierarchy: thick white semen and clear fluid as the exact-center detail, open thighs framing the fluid-covered opening, still body posture, and coworking office-chair depth anchors.", + "observation": "Same-sampler A/B on seed 1123581321 produced first ready/aftermath evidence. The baseline preserved the coworking office setting and open-thigh pose but collapsed into an active lower-foreground penetration/shaft read with the fluid detail secondary. The candidate made the fluid-covered exposed opening the primary visual event and preserved the office-chair/coworking setting, improving the post-ejaculation aftermath read. Under the category-exit rule, this progress was mirrored into the ready/open-thigh generator as a provisional improvement while the catalog route remains candidate pending broader repetition.", + "baseline_image": "/media/unraid/comfyui/output/agent_bridge/img_a201795547524fa6a58b7e40f3455dab.png", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_a9fdfd8a1dd141be8218aadafebf9fbe.png", + "commit": "pending" + }, + { + "id": "spread-3141592653-source50-47-raised-knee-v-frame", + "date": "2026-06-29", + "variant_key": "pov_spread_open_thigh_presentation", + "seed": 3141592653, + "source": "sxcp_eval_mcp_codexmcptest_50_47", + "result": "accepted", + "decision": "provisional_generator_patch", + "baseline_prompt_summary": "Two CodexMCPTest source baselines used generic frontal open-thigh presentation wording in the coworking scene.", + "candidate_prompt_summary": "Same subjects, location family, camera family, and sampler seed with atlas spread hierarchy: knees raised and held wide, thighs forming a broad V-frame, hands holding knees/thighs, centered exposed anatomy, face/torso behind, and office-chair/coworking anchors.", + "observation": "Same-sampler A/B on seed 3141592653 improved the spread route across source subjects 50 and 47. Source 50 baseline was coherent and preserved coworking but stayed a low seated spread without hands or the atlas raised-knee V-frame; the candidate added raised knees, hands on knees, a stronger V-frame, and kept office depth. Source 47 baseline preserved subject and office but kept shorts closed over the central target; the candidate exposed the center, added hands gripping raised knees, and moved closer to the atlas V-frame while preserving the office chair/coworking setting. The result is still not proven because the V-frame is less wide than the strongest atlas examples and clothing can still occupy the lower center, but under the category-exit rule the generator now carries this as a provisional improvement over baseline.", + "baseline_image": "/media/unraid/comfyui/output/agent_bridge/img_aa6f662652344ef4985d605d4fbf0e47.png", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_c355de0c863a417b80b91815069a4ddb.png", + "repeat_cases": [ + { + "source_case": "CodexMCPTest 47", + "baseline_image": "/media/unraid/comfyui/output/agent_bridge/img_5d8be2e457eb4a8db5f9bca877352b78.png", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_fe4b3e9b4f2d445fba76bd2ec97855c2.png" + } + ], + "commit": "pending" + }, + { + "id": "blowjob-top-4242424242-source46-47-vertical-axis", + "date": "2026-06-29", + "variant_key": "pov_blowjob_top_down_vertical_shaft", + "seed": 4242424242, + "source": "sxcp_eval_mcp_codexmcptest_46_47", + "result": "accepted", + "decision": "provisional_generator_patch", + "baseline_prompt_summary": "Two CodexMCPTest source baselines used generic POV blowjob/oral wording with a kneeling woman in the coworking scene.", + "candidate_prompt_summary": "Same subjects, location family, camera family, and sampler seed with atlas top-down oral hierarchy: viewer looks down from chest or pelvis height, lower torso and thighs at the near edge, vertical exact-center shaft, woman kneeling below looking upward, mouth sealed around the centered shaft, one hand wrapped at the base, and face/shoulders/torso readable beneath it.", + "observation": "Same-sampler A/B on seed 4242424242 improved the top-down blowjob route across source subjects 46 and 47. Source 46 baseline was already usable with a centered vertical shaft and preserved coworking setting, but the candidate added clearer hand-at-base structure and a tighter shaft-to-mouth vertical axis. Source 47 baseline preserved the subject and office but drifted toward a side-profile off-center oral read; the candidate centered the shaft and mouth, strengthened the top-down viewer-underbody frame, and preserved the sporty outfit/coworking scene. The result is still not proven because both baselines were already strong and this needs another seed, but under the category-exit rule the generator now carries this as a provisional improvement over baseline.", + "baseline_image": "/media/unraid/comfyui/output/agent_bridge/img_39997082e18041769472881b88bcca65.png", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_82345e52c0f84c649a2758b9139934a6.png", + "repeat_cases": [ + { + "source_case": "CodexMCPTest 47", + "baseline_image": "/media/unraid/comfyui/output/agent_bridge/img_6e713629c82a4cb4b3de2543c3a0f58c.png", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_d8fd2a11388943cdbfbaee1507353250.png" + } + ], + "commit": "pending" + }, + { + "id": "blowjob-side-5656565656-side-phone-non-pov", + "date": "2026-06-29", + "variant_key": "pov_blowjob_side_profile_oral", + "seed": 5656565656, + "source": "sxcp_eval_mcp_codexmcptest_46_47", + "result": "inconclusive", + "decision": "needs_more_tests", + "baseline_prompt_summary": "Two CodexMCPTest source baselines used generic side-profile blowjob/oral wording with a reclining viewer in the coworking scene.", + "candidate_prompt_summary": "Same subjects, location family, camera family, and sampler seed with stricter side-profile hierarchy: horizontal viewer torso/thigh foreground, woman beside the pelvis from the side, readable cheek/jawline/nose bridge, mouth sealed around the lower-center shaft, and one hand supporting the base.", + "observation": "Same-sampler A/B on seed 5656565656 produced attractive side-phone or external side-camera oral compositions across source subjects 46 and 47, but none of the four renders should count as valid POV evidence for the catalog route. Source 46 baseline was already a strong side-phone read with office continuity; its candidate sharpened the face profile but weakened the viewer-underbody lower-frame cue. Source 47 baseline was also a strong side-phone composition, and the candidate stayed in that external side-camera family. Preserve this as useful future non-POV side-camera wording, but do not patch the POV generator from it; the true side-profile POV route still needs evidence or stronger viewpoint control.", + "baseline_image": "/media/unraid/comfyui/output/agent_bridge/img_133b34ad32c348e282a00be2cef59785.png", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_c646e07cc64d4add8d0fb3bca50d7ab9.png", + "repeat_cases": [ + { + "source_case": "CodexMCPTest 47", + "baseline_image": "/media/unraid/comfyui/output/agent_bridge/img_d66dd57152d14219ac10dfb0ef50867e.png", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_f4dcf5040e3f4ff787a6695cc5b21a92.png" + } + ], + "commit": "pending" + }, + { + "id": "blowjob-side-5656565656-source46-male-body-axis", + "date": "2026-06-29", + "variant_key": "pov_blowjob_side_profile_oral", + "seed": 5656565656, + "source": "sxcp_eval_mcp_codexmcptest_46", + "result": "accepted", + "decision": "needs_more_tests", + "baseline_prompt_summary": "CodexMCPTest source 46 side-profile oral baseline and first stricter candidate produced attractive side-phone or external side-camera oral compositions instead of a true first-person body-line view.", + "candidate_prompt_summary": "Same source subject, coworking location family, camera family, and sampler seed with explicit adult male body ownership: male chest, navel, abdomen hair, pelvis, and thighs form the central foreground, while the woman stays lateral along the left edge with her side-facing mouth aligned to the shaft near the male abdomen.", + "observation": "Same-sampler source-46 A/B on seed 5656565656 showed the explicit adult-male body ownership hierarchy can recover the atlas-like side-profile POV read: the male abdomen and navel become the central foreground surface, the woman enters laterally beside the hip, and mouth/hand contact stays aligned near the male body line. Keep this as accepted single-source evidence only. A related source-47 body-axis candidate on the same seed collapsed into a non-POV seated or straddling composition where the woman's body took over the body-axis cue, so the route still needs more tests and should not receive a generator patch yet.", + "baseline_image": "/media/unraid/comfyui/output/agent_bridge/img_133b34ad32c348e282a00be2cef59785.png", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_86e9c3a2c13949208f562b6316df09f7.png", + "repeat_cases": [ + { + "source_case": "CodexMCPTest 47 body-axis probe", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_804195a058c94b07ac5c5780a959fd07.png", + "result": "rejected", + "observation": "The body-axis wording transferred to the woman's torso/body and produced a non-POV seated or straddling composition instead of male-viewer body ownership." + } + ], + "commit": "pending" + }, + { + "id": "blowjob-side-9753197531-two-woman-lateral-edge-body-line", + "date": "2026-06-30", + "variant_key": "pov_blowjob_side_profile_oral", + "seed": 9753197531, + "source": "sxcp_eval_mcp_batch_two_visible_women", + "result": "accepted", + "decision": "provisional_generator_patch", + "needs_expansion": true, + "baseline_prompt_summary": "Turns 198 and 202 used generic side-profile oral wording across two visible women in the coworking scene. Both preserved attractive side-camera or side-phone composition, but neither made the male foreground body line own the viewpoint strongly enough.", + "candidate_prompt_summary": "Turns 200 and 204 used the lateral-edge body-line axis: the adult male viewer's abdomen, navel, pelvis, and near thigh create the broad foreground body surface, the woman enters laterally from the left edge beside his hip, and her profile mouth plus hand contact stay aligned at the male abdomen line.", + "observation": "Same-sampler two-woman expansion on seed 9753197531 repeated the side-profile POV body-line improvement. The generic baselines stayed useful as non-POV side-phone or external side-camera reads. The pure male-body-axis wording could expose the male as a photographed subject, so it remains fragile. The lateral-edge wording repeated on both women while keeping the male abdomen/torso as the foreground body line and the woman's face/body lateral along the edge. Mirror this exact lateral-edge hierarchy into the generator provisionally and keep the route queued for broader seed/source expansion.", + "baseline_image": "/media/unraid/comfyui/output/agent_bridge/img_89e8f6bab18a47f28787efa349933734.png", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_da164a54653949598e06cf4e6e905924.png", + "repeat_cases": [ + { + "source_case": "CodexMCPTest 50 lateral_edge_entry", + "baseline_image": "/media/unraid/comfyui/output/agent_bridge/img_5a776aafda8f4a80ab12d995e4ce8020.png", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_a6be29614fd14c7db4315e1fb7ae451e.png", + "observation": "Second-subject repeat preserved the male body-line foreground and woman-lateral edge entry." + }, + { + "source_case": "pure male_body_axis wording", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_e9d83408b4964abbbf64c35570f273ef.png", + "repeat_image": "/media/unraid/comfyui/output/agent_bridge/img_7b5fb8df253348af8dfc4c39248068ae.png", + "result": "rejected_as_default", + "observation": "The pure male-body-axis wording can make the male a photographed subject or transfer the axis away from the intended first-person body line." + }, + { + "source_case": "abdomen_surface_contact secondary branch", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_c07cf70ed83e4a1eb50e6e2a432f4f6d.png", + "repeat_image": "/media/unraid/comfyui/output/agent_bridge/img_dd2f68bcc187421bacd19b5e36a9aa00.png", + "observation": "Useful secondary wording, but less clearly repeated than the lateral-edge body-line axis." + } + ], + "commit": "pending" + }, + { + "id": "blowjob-side-9753197531-generated-route-mouth-contact-priority", + "date": "2026-06-30", + "variant_key": "pov_blowjob_side_profile_oral", + "seed": 9753197531, + "generator_seed": 3729, + "source": "sxcp_eval_mcp_generated_route", + "result": "accepted", + "decision": "provisional_generator_patch", + "needs_expansion": true, + "baseline_prompt_summary": "Turn 206 used the first generated-route side-profile body-line patch: male abdomen, navel, pelvis, and near thigh as the foreground body surface, woman entering laterally from the left edge, coworking anchors, and hand under the mouth.", + "candidate_prompt_summary": "Turn 207 kept the generated side-profile body-line route: the adult male viewer's abdomen, navel, pelvis, and near thigh create the broad foreground body surface, the woman enters laterally beside his hip, and the route adds contact-priority wording with lips touching the shaft at the male abdomen line and mouth-to-shaft contact as the nearest facial detail.", + "observation": "Generated-route A/B on seed 9753197531 showed that the initial route patch validated the body-line/coworking geometry but still let the mouth float above the shaft while the hand became the contact anchor. Adding lips-touching and mouth-to-shaft-contact priority preserved the male abdomen foreground, lateral side-profile entry, office depth, and visible hand support while moving the oral contact onto the shaft. This is a better generated-route partial, but it still reads more as mouth/tongue contact than fully mouth-wrapped side-profile oral, so keep the route provisional and queued for broader seed/source expansion.", + "baseline_image": "/media/unraid/comfyui/output/agent_bridge/img_68b588b722dc40ecba7cee2a01973652.png", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_63948d38edd8468c999a3ca698298263.png", + "commit": "pending" + }, + { + "id": "blowjob-side-8484848484-fresh-seed-body-line-weak-case", + "date": "2026-06-30", + "variant_key": "pov_blowjob_side_profile_oral", + "seed": 8484848484, + "source": "sxcp_eval_mcp_batch_two_visible_women", + "result": "inconclusive", + "decision": "needs_more_tests", + "needs_expansion": true, + "baseline_prompt_summary": "Turns 270 and 273 retested the current generated side-profile body-line route on a fresh sampler seed across two visible women: male abdomen/navel/pelvis/near thigh as the broad foreground body surface, lateral woman entry, lips touching the shaft at the male abdomen line, and hand under the mouth.", + "candidate_prompt_summary": "Turns 271 and 274 tested a stronger abdomen-line contact lock; turns 272 and 275 tested a lateral-edge mouth-first branch where the mouth should contact before the hand.", + "observation": "Fresh-seed side-profile expansion did not validate promotion. Turns 270 and 273 were useful partials, preserving some male body-line foreground and mouth contact, but they still read partly as external side-camera compositions rather than fully owned first-person body-line POV. Turns 271 and 274 transferred the body-line cue onto the woman, making her torso/body become the photographed foreground subject. Turns 272 and 275 preserved attractive side-camera oral contact but showed the male as a visible photographed subject at frame edge, so they should not count as POV evidence. Keep the existing lateral-edge/mouth-priority generator patch provisional and continue testing with stronger viewpoint ownership; do not promote or patch from this batch.", + "baseline_image": "/media/unraid/comfyui/output/agent_bridge/img_ab718ed2d0574f2faeceefd30f0be980.png", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_8603142129514eddb8df7036cda3377a.png", + "repeat_cases": [ + { + "source_case": "woman_b_generated_body_line_contact", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_bc2eb2ac1c354add85b7de19f5b1f7de.png", + "observation": "Second-subject generated-route repeat preserved side oral contact but exposed the male as a photographed side subject, weakening POV ownership." + }, + { + "source_case": "abdomen_line_contact_lock_branch", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_bbdcd0e5ec0c49518e5dc3042488ee44.png", + "repeat_image": "/media/unraid/comfyui/output/agent_bridge/img_c80a617d5c984738a6544d1a78667ec0.png", + "observation": "Rejected as default: the body-line wording transferred onto the woman and made her photographed torso/body the main axis." + }, + { + "source_case": "lateral_edge_mouth_first_branch", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_8603142129514eddb8df7036cda3377a.png", + "repeat_image": "/media/unraid/comfyui/output/agent_bridge/img_5ec0e477c175430f8453230031b0de4d.png", + "observation": "Attractive side-camera oral branch with good mouth contact, but the male appears as a visible photographed subject at the frame edge, so it remains non-POV evidence." + } + ], + "commit": "pending" + }, + { + "id": "blowjob-side-9595959595-lower-right-torso-anchor-repeat", + "date": "2026-06-30", + "variant_key": "pov_blowjob_side_profile_oral", + "seed": 9595959595, + "source": "sxcp_eval_mcp_batch_two_visible_women", + "result": "accepted", + "decision": "provisional_generator_patch", + "needs_expansion": true, + "baseline_prompt_summary": "Turns 276 and 280 retested the current generated side-profile body-line route on a fresh sampler seed across two visible women: male abdomen/navel/pelvis/near thigh foreground, lateral woman entry, lips touching the shaft at the male abdomen line, mouth-to-shaft contact priority, and hand under the mouth.", + "candidate_prompt_summary": "Turns 279 and 283 tested the lower-right torso anchor: the adult male viewer's own torso starts at the lower edge and runs diagonally into the lower-right foreground, with navel, abdomen hair, pelvis, and near thigh marking the camera owner's body, while the woman enters laterally from the left side and keeps profile mouth contact at the abdomen hair line.", + "observation": "Fresh-seed side-profile expansion found a repeatable improvement but not a promotion. Control turn 276 was already a strong body-line partial, while control turn 280 preserved contact but exposed the male as a photographed side subject. The lower-right torso anchor repeated on turns 279 and 283 across both women: it strengthened camera-owner torso ownership, kept the male abdomen/near thigh as the foreground body plane, and preserved side-profile mouth contact at the abdomen line. Chest-mounted turns 277 and 281 were useful partials but still side-camera prone; near-skin turns 278 and 282 varied and could let the woman become too central. Patch the generated side-lying oral route provisionally with the lower-right torso anchor and keep the variant queued for broader expansion.", + "baseline_image": "/media/unraid/comfyui/output/agent_bridge/img_4f490e8dd1ad45e7b5cd3023c400110a.png", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_6d870e114ba3466583673e4c964265a0.png", + "repeat_cases": [ + { + "source_case": "woman_a_lower_right_torso_anchor", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_2c3509e1be184749b6f9719cfd2f604b.png", + "observation": "Turn 279 repeated the lower-right torso foreground and side-profile mouth contact while keeping the woman's body lateral." + }, + { + "source_case": "woman_a_generated_route_control", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_7f5e9a24fbdf4b1088475d3d0df20fa5.png", + "observation": "Turn 276 was already a strong body-line partial, so the branch is a narrow repeatability improvement rather than a full promotion." + }, + { + "source_case": "chest_mounted_self_body_branch", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_7b247e8a12a0431ba51170f9cc4950c5.png", + "repeat_image": "/media/unraid/comfyui/output/agent_bridge/img_b50df87a93a045d58b3d4acfff512824.png", + "observation": "Useful partials, but still prone to external side-camera framing." + }, + { + "source_case": "near_skin_sideways_view_branch", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_6f9a0e0bd17243919524c5b2c57798e4.png", + "repeat_image": "/media/unraid/comfyui/output/agent_bridge/img_c24fce2f92f94ca8ab8b74790d3b63d0.png", + "observation": "Rejected as default because the woman can become too central or the body ownership can weaken." + } + ], + "commit": "pending" + }, + { + "id": "blowjob-side-9696969696-generated-route-validation", + "date": "2026-06-30", + "variant_key": "pov_blowjob_side_profile_oral", + "seed": 9696969696, + "source": "sxcp_eval_mcp_generated_route_validation", + "result": "accepted", + "decision": "provisional_generator_patch", + "needs_expansion": true, + "baseline_prompt_summary": "No separate baseline in this validation batch. The probes rendered the patched generated side-profile route directly on a fresh sampler seed after adding the lower-right self-torso anchor to the route.", + "candidate_prompt_summary": "Turns 284 and 285 used the patched generated-route wording across two visible women: male abdomen/navel/pelvis/near thigh broad lower frame, adult male viewer's own torso starts at the lower edge and runs diagonally into the lower-right foreground, navel/abdomen hair/pelvis/near thigh marking the camera owner's body, lateral woman entry, lips touching at the male abdomen line, and mouth-to-shaft contact as nearest facial detail.", + "observation": "Fresh generated-route validation on seed 9696969696 carried the lower-right torso anchor patch across both women. turns 284 and 285 both kept lower-right own-body foreground, navel/abdomen hair/pelvis/near thigh as camera-owner landmarks, side-profile mouth contact at the abdomen line, and coworking depth, while avoiding the previous photographed-male-face side-camera failure. Treat this as validation of the provisional route patch, not full promotion; the pose still needs broader source/seed expansion before proven status.", + "baseline_image": "/media/unraid/comfyui/output/agent_bridge/img_da8a4d47e853468f8f3706483d48a59a.png", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_ac6cc1b0c3b04734ac149a0672eeb416.png", + "repeat_cases": [ + { + "source_case": "woman_a_patched_generated_route", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_da8a4d47e853468f8f3706483d48a59a.png", + "observation": "Turn 284 preserved lower-right own-body foreground, readable profile contact, and office depth." + }, + { + "source_case": "woman_b_patched_generated_route", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_ac6cc1b0c3b04734ac149a0672eeb416.png", + "observation": "Turn 285 repeated the patched generated route with the blonde subject while avoiding the prior male-face side-camera failure." + } + ], + "commit": "pending" + }, + { + "id": "blowjob-side-5858585858-three-woman-generated-route-proven", + "date": "2026-06-30", + "variant_key": "pov_blowjob_side_profile_oral", + "seed": 5858585858, + "source": "sxcp_eval_mcp_three_woman_generated_route_repeat", + "result": "accepted", + "decision": "proven_with_evidence", + "baseline_prompt_summary": "No separate baseline in this promotion batch. The probes retested the current patched generated side-profile route against lower-edge owner and side-camera-style self-body crop variants across three subject descriptions after prior weak-case and generated-route validation evidence.", + "candidate_prompt_summary": "Turns 298, 301, and 304 used the patched generated-route wording across three visible women: lower-frame male abdomen/navel/pelvis/near thigh foreground, adult male viewer's own torso starts at the lower edge and runs diagonally into the lower-right foreground, camera-owner landmarks, lateral woman entry, lips touching at the male abdomen line, and mouth-to-shaft contact as nearest facial detail. Turns 299, 302, and 305 tested lower-edge owner reinforcement; turns 300, 303, and 306 tested side-camera-style self-body crop wording.", + "observation": "Fresh three-woman generated-route repeat on seed 5858585858 promoted the side-profile POV route. Generated-route controls on turns 298, 301, and 304 all preserved the lower-right torso anchor, camera-owner abdomen/near-thigh foreground, lateral profile entry, mouth contact at the abdomen line, and coworking depth without the earlier photographed-male-face failure. Lower-edge owner variants reinforced body ownership but were not cleaner enough to replace the default. Side-camera-style self-body crop was visually strong, but it should remain an alternate look or future side-camera branch because it can drift toward external side framing. Promote the current generated hierarchy to proven and keep the side-camera boundary documented.", + "baseline_image": "/media/unraid/comfyui/output/agent_bridge/img_930476d1c44e47948b451583d7c6ecb1.png", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_b8a56a1abad94f59813c184a9107b8c1.png", + "repeat_cases": [ + { + "source_case": "woman_a_generated_route_control", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_930476d1c44e47948b451583d7c6ecb1.png", + "observation": "Turn 298 repeated the patched generated route with lower-frame camera-owner body plane, lateral profile entry, and readable contact at the abdomen line." + }, + { + "source_case": "woman_a_lower_edge_owner_reinforce", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_a6f7c86dff3e47bda1643f1e638770d3.png", + "observation": "Turn 299 reinforced lower-edge body ownership, but it did not improve clearly enough over the current generated route to replace the default wording." + }, + { + "source_case": "woman_a_side_camera_style_self_body_crop", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_ecce7d883401430c84738bc1fd9e39eb.png", + "observation": "Turn 300 produced a strong side-camera-style look while preserving a self-body foreground; keep this as a look branch because it is closer to external side framing." + }, + { + "source_case": "woman_b_generated_route_control", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_b3082c2e2c4746f09a2f3b99630669e5.png", + "observation": "Turn 301 repeated the patched generated route with the blonde subject, preserving side-profile mouth contact and camera-owner lower body." + }, + { + "source_case": "woman_b_lower_edge_owner_reinforce", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_525fb5fc1dcd47eebb6062d26b48cc50.png", + "observation": "Turn 302 reinforced body ownership but shifted the composition slightly more vertical, so it remains a useful alternate rather than the route default." + }, + { + "source_case": "woman_b_side_camera_style_self_body_crop", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_490e7d61fa654cb8b046b30d7ee490f0.png", + "observation": "Turn 303 produced the strongest side-camera-style visual read for this subject, but it is also the branch most likely to drift toward non-POV side framing." + }, + { + "source_case": "woman_c_generated_route_control", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_b8a56a1abad94f59813c184a9107b8c1.png", + "observation": "Turn 304 repeated the patched generated route with a third subject, keeping the camera-owner lower body and lateral profile contact." + }, + { + "source_case": "woman_c_lower_edge_owner_reinforce", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_f01d6df3add64b57a6d677bfc1aed713.png", + "observation": "Turn 305 kept body ownership and profile contact, but the route control already solved the same structure cleanly." + }, + { + "source_case": "woman_c_side_camera_style_self_body_crop", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_56c84c0de04940b889220ea793ff7d43.png", + "observation": "Turn 306 kept the appealing side-camera-style look with lower-right self-body foreground; keep it documented as a visual branch." + } + ], + "commit": "pending" + }, + { + "id": "blowjob-laying-6767676767-source46-50-wide-v-frame", + "date": "2026-06-29", + "variant_key": "pov_blowjob_laying_frontal_oral", + "seed": 6767676767, + "source": "sxcp_eval_mcp_codexmcptest_46_50", + "result": "accepted", + "decision": "provisional_generator_patch", + "baseline_prompt_summary": "Two CodexMCPTest source baselines used generic prone frontal oral wording: viewer reclines with open thighs, woman belly-down between thighs, front-facing mouth aligned to the shaft, and coworking context behind the bodies.", + "candidate_prompt_summary": "Same source subjects, coworking location family, camera family, and sampler seed with atlas hierarchy: open thighs form a wide symmetrical V-frame, lower abdomen anchors the near edge, the woman lies low between the thighs with torso stretched low and horizontal, hands at the base, centered mouth-to-shaft contact, and her body extending away behind her face.", + "observation": "Same-sampler A/B on seed 6767676767 improved blowjob-laying frontal oral across source subjects 46 and 50 with a wide V-frame and low-horizontal torso hierarchy. Both baselines were already strong on centered mouth contact, hands, and coworking continuity, but they read more raised-hips or all-fours than prone belly-down. The candidates preserved subject, outfit, office depth, and centered contact while strengthening the viewer-thigh V-frame and the woman's body extension away from the camera. Source 46 still kept some high-hip posture, so the route remains candidate; under the category-exit rule the generator now carries this as a provisional improvement over baseline.", + "baseline_image": "/media/unraid/comfyui/output/agent_bridge/img_b3b684c0ee474faf871253cd7e1f26f1.png", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_9ac7b844ffa44a7187400416e108a6fd.png", + "repeat_cases": [ + { + "source_case": "CodexMCPTest 50", + "baseline_image": "/media/unraid/comfyui/output/agent_bridge/img_6be4c1d849f74b7f9fc37edbba142894.png", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_9fd8e2e5c0bf4e6cad380aca9f899c3d.png", + "observation": "The candidate strengthened the visible body extension away between the viewer's thighs and kept the centered frontal mouth-to-shaft line." + } + ], + "commit": "pending" + }, + { + "id": "blowjob-sitting-7878787878-source46-50-low-mouth-contact", + "date": "2026-06-29", + "variant_key": "pov_blowjob_sitting_upright_oral", + "seed": 7878787878, + "source": "sxcp_eval_mcp_codexmcptest_46_50", + "result": "accepted", + "decision": "provisional_generator_patch", + "baseline_prompt_summary": "Two CodexMCPTest source baselines used generic upright sitting oral wording in the coworking scene: viewer reclines with open thighs, woman sits upright between the thighs, front-facing mouth aligned to a centered vertical shaft, and hands low near the base.", + "candidate_prompt_summary": "Same source subjects, coworking location family, camera family, and sampler seed with low-mouth seated hierarchy: viewer thigh V-frame, lower abdomen near edge, woman sitting low between the thighs, torso upright behind the action, face lowered to the exact center contact point, open mouth covering the centered shaft tip, and both hands wrapped low at the base.", + "observation": "Same-sampler A/B on seed 7878787878 improved upright sitting oral across source subjects 46 and 50. Both baselines preserved the seated coworking layout but kept the woman's face too high, leaving mouth contact implied or absent while hands sat beside the base. The first source-46 candidate over-weighted vertical shoulders and made the mouth drift farther away. The accepted low-mouth hierarchy moved the face down to the shaft tip, kept hands directly at the base, and preserved the viewer thigh frame and coworking depth on both sources. Source 50 shows some outfit looseness/drift, so keep the route candidate and the generator patch provisional until another seed repeats it.", + "baseline_image": "/media/unraid/comfyui/output/agent_bridge/img_3e97bd75c77d407897fc8a5669db945e.png", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_beaae9f93fa948e19eb4e26337adfc71.png", + "repeat_cases": [ + { + "source_case": "CodexMCPTest 50", + "baseline_image": "/media/unraid/comfyui/output/agent_bridge/img_4020afec82cf4207bad8aeb365f21be0.png", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_29caf732e4d9472ea51e449e4409eae5.png", + "observation": "The candidate lowered the face to the centered shaft tip and made the hands-at-base structure readable while preserving the seated viewer-thigh frame and coworking depth; outfit continuity is looser than baseline." + } + ], + "commit": "pending" + }, + { + "id": "missionary-open-8989898989-source50-47-seated-lounge-drift", + "date": "2026-06-29", + "variant_key": "pov_missionary_open_leg_penetration", + "seed": 8989898989, + "source": "sxcp_eval_mcp_codexmcptest_50_47", + "result": "inconclusive", + "decision": "needs_more_tests", + "baseline_prompt_summary": "Two CodexMCPTest source baselines used generic POV missionary wording in a coworking lounge: viewer above her, woman on her back with legs open around his hips, foreground arms or hands, central contact, and office-lounge support.", + "candidate_prompt_summary": "Same source subjects, coworking location family, camera family, and sampler seed with atlas open-leg hierarchy: viewer between her legs from the lower foreground, hands holding the undersides of her thighs, knees opened toward the camera, broad V-frame around the centered contact line, and reclined torso/face behind the thighs.", + "observation": "Same-sampler A/B on seed 8989898989 produced mixed missionary evidence. Source 50 improved the open-thigh V-frame, viewer-hand thigh hold, lower-body anchor, and centered contact line, but still read partly as an upright lounge-seat composition instead of a woman reclined flat on her back. Source 47 baseline was already strong on open thighs, hand placement, central contact, and coworking continuity; the candidate preserved those features but did not materially solve the same upright sofa/office-lounge drift. Do not patch the generator from this pair. The next attempt should target reclined-back body angle and overhead/pelvis-height camera geometry more strongly, while avoiding sofa-back cues that make the woman look seated.", + "baseline_image": "/media/unraid/comfyui/output/agent_bridge/img_5537dda8e12947e9aaa28e23ef75504a.png", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_84d35ca9c5f7467bab7d3cf6de6c714e.png", + "repeat_cases": [ + { + "source_case": "CodexMCPTest 47", + "baseline_image": "/media/unraid/comfyui/output/agent_bridge/img_ec3a1f38bfa94b6a95563f1890665601.png", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_d4fb16d69d654774b5339f71338f544a.png", + "observation": "The candidate retained the open-thigh central-contact composition but still read as upright lounge seating rather than clearly reclined-on-back missionary." + } + ], + "commit": "pending" + }, + { + "id": "missionary-open-8989898989-turn71-76-prompt-order-axis", + "date": "2026-06-29", + "variant_key": "pov_missionary_open_leg_penetration", + "seed": 8989898989, + "source": "sxcp_eval_mcp_missionary_axis_batch", + "result": "inconclusive", + "decision": "needs_more_tests", + "baseline_prompt_summary": "Earlier missionary A/B on the same sampler seed improved some open-thigh framing but still drifted into upright lounge or sofa seating instead of a woman reclined flat on her back.", + "candidate_prompt_summary": "Batched prompt-axis search tested pose-first flat floor mat, supine sheet/rug plane, close pelvis V-shaped tunnel, and diagonal thigh-rail wording, then retested the two best axes with the subject/look block placed first. The useful geometry words were flat/thin floor cushion, spine/shoulders/head flat, lifted thighs opened wide, V-shaped tunnel or diagonal thigh rails, viewer hands gripping thighs from below, and low coworking floor anchors.", + "observation": "Turns 71-74 showed that pose-first prompts can recover stronger open-leg missionary geometry: the woman is on a flat floor mat/cushion, viewer hands hold the thighs, the contact line is centered, and office floor anchors stay low instead of becoming a sofa back. The user correctly noted that this prompt order can reduce female-look adherence. Subject-look-first follow-ups on turns 75-76 restored look adherence but weakened the flat-back geometry; turn 76 kept the best controlled V-frame and hand placement but still leaned toward upright cushion posture. Record this as prompt-axis evidence only, not a generator patch.", + "baseline_image": "/media/unraid/comfyui/output/agent_bridge/img_84d35ca9c5f7467bab7d3cf6de6c714e.png", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_73ea3beba62847e382cb8b54d7e57a51.png", + "repeat_cases": [ + { + "source_case": "turn71 flat floor mat pose-first", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_e0f1ebc2dfc44860bde4610697169e75.png", + "observation": "Good flat mat and open-thigh hand support, but the face turned away and the prompt did not preserve the subject/look block." + }, + { + "source_case": "turn72 supine sheet/rug plane pose-first", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_227d7f1ce78f47169b690dd8ad62cce4.png", + "observation": "Strong wide V-frame and flat sheet plane, but it leaned away from the coworking location and remained pose-first only." + }, + { + "source_case": "turn73 V-shaped tunnel pose-first", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_a2423a109f55450bb4fbad148d140925.png", + "observation": "Best pose-first flat-back geometry with lifted thighs, hands under thighs, and centered contact, but subject/look adherence was uncontrolled." + }, + { + "source_case": "turn74 diagonal thigh rails pose-first", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_c6e36eb5f30a4f84903348fbf53c1067.png", + "observation": "Strong flat-back office-rug framing and diagonal thigh rails, but subject/look adherence was uncontrolled." + }, + { + "source_case": "turn75 subject-first diagonal thigh rails", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_32306090bf3e40eba92e740e36c71904.png", + "observation": "Subject/look adherence improved, but the body posture drifted upright and became less atlas-like." + } + ], + "commit": "pending" + }, + { + "id": "blowjob-top-4242424242-source46-overhead-correction", + "date": "2026-06-29", + "variant_key": "pov_blowjob_top_down_vertical_shaft", + "seed": 4242424242, + "source": "sxcp_eval_mcp_codexmcptest_46_probe", + "result": "inconclusive", + "decision": "needs_more_tests", + "baseline_prompt_summary": "Same source-style subject, coworking location family, camera family, and sampler seed using top-down oral wording focused on vertical shaft, viewer lower torso/thighs, woman kneeling below, mouth contact, and hand at the base.", + "candidate_prompt_summary": "First corrected atlas wording made the camera steep overhead: camera above the viewer chest and abdomen looking downward to the floor, viewer abdomen at the bottom edge, woman below the camera on the floor, top of hair/shoulders/hands visible from above, and body foreshortening downward away from the viewer.", + "observation": "Same-sampler top-view probe on seed 4242424242 showed that vertical center-shaft wording alone can still render as frontal/eye-height oral rather than atlas top-view. This first corrected overhead wording moved the render toward the atlas, but later review found it still too horizontal to promote as the route wording. Keep it as partial evidence only; the successful wording came from the later nadir-angle floor-plane axis loop.", + "baseline_image": "/media/unraid/comfyui/output/agent_bridge/img_f48547c8b44b46c098e296cce1197df5.png", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_e908aca65fdf4d3a8a801e925ab2d1a5.png", + "commit": "pending" + }, + { + "id": "blowjob-top-4242424242-turn67-70-nadir-floor-plane-axis", + "date": "2026-06-29", + "variant_key": "pov_blowjob_top_down_vertical_shaft", + "seed": 4242424242, + "source": "sxcp_eval_mcp_top_view_axis_loop", + "result": "accepted", + "decision": "provisional_generator_patch", + "baseline_prompt_summary": "Earlier top-view probes on the same seed used vertical-shaft or generic steep-overhead wording and could still read too frontal, too horizontal, or too dependent on long office depth cues.", + "candidate_prompt_summary": "Prompt-only axis loop tested near-vertical floor-plane, plumb-line/map, nadir-angle, and bird's-eye standing male POV wording. The accepted axis is nadir-angle or bird's-eye standing male POV, viewer looking almost straight down from torso to floor, nearby carpet/floor plane dominating, one woman kneeling directly below between the viewer's feet, top-down office anchors, and a short centered vertical shaft column.", + "observation": "Turns 67, 69, and 70 on sampler seed 4242424242 produced the first clearly atlas-like verticality after the user rejected horizontal probes: viewer abdomen/shorts/feet anchor the lower edge, the woman is directly below between the viewer's feet, floor plane and nearby office furniture read as top-down anchors, her hair crown/forehead/shoulders/hands/knees are visible from above, and mouth contact stays centered. Turn 68 showed that plumb-line and map are unsafe generator words because Krea2 literalized them into drawn graphics. Mirror the nadir-angle/floor-plane wording as a provisional generator patch, but keep the catalog route candidate until another source or seed repeats it.", + "baseline_image": "/media/unraid/comfyui/output/agent_bridge/img_e421319637bc45f0bb49bfc486c7f4ad.png", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_ea922fa7bd6642f5bbe76b50f48b558b.png", + "repeat_cases": [ + { + "source_case": "turn67 near-vertical floor-plane", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_56d6738989794a569cfe45d79dd96e88.png", + "observation": "Strong vertical floor plane, one woman below the viewer, table and chair legs as top-down anchors, and coherent centered contact." + }, + { + "source_case": "turn68 plumb-line map", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_9262f4886f34400f9ff8cc03b3ddb67f.png", + "observation": "Geometry stayed strong, but the wording produced literal drawn line/map artifacts and should not be used in generator prompts." + }, + { + "source_case": "turn69 nadir-angle", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_c4fc77cdf1b6496ab03e1680fb91332f.png", + "observation": "Clean near-vertical result without the literal plumb-line/map artifact; this is the safest wording family for the generator." + } + ], + "commit": "pending" + }, + { + "id": "missionary-open-8989898989-turn77-80-subject-first-flat-plane", + "date": "2026-06-29", + "variant_key": "pov_missionary_open_leg_penetration", + "seed": 8989898989, + "source": "sxcp_eval_mcp_missionary_subject_first_batch", + "result": "inconclusive", + "decision": "needs_more_tests", + "baseline_prompt_summary": "Turn 76 used a subject/look-first controlled missionary prompt with a thin floor cushion, spine/shoulders/head flat, lifted open thighs, V-shaped tunnel, viewer hands gripping thighs, centered contact, and low coworking floor anchors.", + "candidate_prompt_summary": "Turns 77-80 kept the same subject/look block first and tested four stronger flat-plane axes: camera just above the male pelvis with a low horizontal plane, thigh-gate forearm rails, overhead torso-to-floor axis, and thin cushion directly on coworking carpet with no backrest cue.", + "observation": "The validated same-sampler batch proves the prompt-order warning matters but does not solve missionary. All four subject-first candidates preserved the woman look and coworking continuity better than pose-first probes, but none materially improved the atlas flat-on-back missionary plane over turn 76. Turn 80 was the best new candidate because the coworking floor, laptop table, glass depth, viewer hands, and V-frame stayed coherent, but it still read as seated or upright on a cushion rather than a clearly flat reclined body. Record this as text-only weak-case evidence and do not patch the generator yet.", + "baseline_image": "/media/unraid/comfyui/output/agent_bridge/img_73ea3beba62847e382cb8b54d7e57a51.png", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_87b391672fef40bea5f37f58342a91f2.png", + "repeat_cases": [ + { + "source_case": "turn77 subject_first_pelvis_camera_flat_plane", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_594021eebf1e43d496e2566243d8a2d7.png", + "observation": "Preserved subject/look and office floor cues, but the body sat more upright on the cushion and did not strengthen the flat-back atlas plane." + }, + { + "source_case": "turn78 subject_first_thigh_gate_forearm_rails", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_f643a7b4d2c94281a42775158a4b5eab.png", + "observation": "Produced readable viewer hands and a close thigh frame, but drifted further toward upright seated posture with the torso elevated." + }, + { + "source_case": "turn79 subject_first_overhead_torso_to_floor_axis", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_5586dfc6bf454ffda3d66422bdd6307a.png", + "observation": "The torso-to-floor wording did not enforce flat floor geometry; it produced the clearest seated/backrest read of the batch." + }, + { + "source_case": "turn80 subject_first_low_cushion_no_backrest_axis", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_87b391672fef40bea5f37f58342a91f2.png", + "observation": "Best candidate in the batch: good office depth, viewer hands, and V-frame, but still not enough flat-on-back geometry for a generator patch." + } + ], + "commit": "pending" + }, + { + "id": "missionary-open-8989898989-turn81-84-elevated-edge-support", + "date": "2026-06-29", + "variant_key": "pov_missionary_open_leg_penetration", + "seed": 8989898989, + "source": "sxcp_eval_mcp_missionary_edge_support_batch", + "result": "accepted", + "decision": "provisional_generator_patch", + "baseline_prompt_summary": "Turns 76 and 80 are valid angled/open-leg missionary results on the same sampler seed, with good subject/look adherence, viewer hands, V-frame, and coworking continuity, but they target the lower cushion/foreground-thigh angle rather than the flatter elevated-support atlas subcase.", + "candidate_prompt_summary": "Turns 81-84 kept subject/look first and tested the flat atlas axis: viewer standing, kneeling, or braced at the foot edge of a flat elevated support; viewer feet, shins, or side-dropping legs placed below the support edge; woman flat across the tabletop/platform; hands holding calves or outer thighs; office furniture around the support.", + "observation": "Same-sampler edge-support A/B on seed 8989898989 showed the missing axis was viewer/support placement, not generic missionary validity. Turn 81 and especially turn 84 moved the scene onto a flat elevated table/platform, kept the woman flat across the support, preserved coworking anchors, and placed the viewer at the foot edge instead of reclined with thighs projecting forward. Turn 82 is a weak branch because kneeling-edge wording hallucinated an extra male body behind her. Patch only the raised-edge/edge-supported route with the flat elevated-support wording; keep generic missionary as a valid angled route.", + "baseline_image": "/media/unraid/comfyui/output/agent_bridge/img_87b391672fef40bea5f37f58342a91f2.png", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_7a9128699e4d4f239d9a9d956eb9b996.png", + "repeat_cases": [ + { + "source_case": "turn81 subject_first_standing_feet_below_support", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_633402fac645467dbb5b302b7cf737c1.png", + "observation": "Strong elevated white support and standing/feet-below-support cues; useful secondary evidence for the flat support axis." + }, + { + "source_case": "turn82 subject_first_kneeling_edge_legs_drop_sides", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_3a02fc7520bb4b40b1e6f43a38028f6f.png", + "observation": "Rejected branch: kneeling-edge wording produced an extra male body behind her, so avoid treating kneeling-edge body placement as the primary generator cue." + }, + { + "source_case": "turn83 subject_first_tabletop_edge_leg_lift", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_388b39b4458f4861989a347ba2c6c289.png", + "observation": "Readable office tabletop and viewer at the edge, but it kept a more upright/seated torso than turn 84." + }, + { + "source_case": "turn84 subject_first_rounded_lounge_platform_flat", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_7a9128699e4d4f239d9a9d956eb9b996.png", + "observation": "Best accepted candidate: rounded elevated platform, woman flat on one support plane, hands on outer thighs, office lounge depth, and viewer positioned at the foot edge." + } + ], + "commit": "pending" + }, + { + "id": "missionary-folded-8989898989-turn85-92-contact-first-knee-block", + "date": "2026-06-29", + "variant_key": "pov_missionary_folded_high_leg_penetration", + "seed": 8989898989, + "source": "sxcp_eval_mcp_batch", + "result": "accepted", + "decision": "provisional_generator_patch", + "baseline_prompt_summary": "Turn 85 kept the subject/look and coworking lounge, and moved from normal open-leg missionary into a compact knees-to-chest folded geometry with hands on calves, but the visible shaft/contact line was weak or absent because the raised knees and feet dominated the lower frame.", + "candidate_prompt_summary": "Turn 89 kept the same subject, location family, camera family, and sampler seed, but moved the viewer lower abdomen and a large centered shaft/contact anchor before the compact folded-knee block. The prompt then placed both knees folded tightly toward her chest above the contact, with viewer hands holding her calves and her face/torso behind the raised knees.", + "observation": "Same-sampler folded-missionary A/B on seed 8989898989 showed two prompt axes. The first turn85-turn88 batch proved that subject-first knees-to-chest wording can produce the folded high-leg pose, especially compact knee-block and vertical-thigh-column phrasing, but it often loses readable penetration/contact. The contact-focused turn89-turn92 batch showed that lower-center shaft/contact must be named before the folded knees. Turn 89 was the strongest combined result: compact knees-to-chest block, hands on calves, visible lower-center shaft/contact, preserved subject/look, and coherent coworking/platform anchors. Turn 92 was useful secondary evidence for pelvis/shaft anchoring but looser on the folded knee block. Mirror turn89's contact-first knee-block hierarchy into the generator as a provisional folded-missionary route; keep the catalog candidate until another seed or subject repeats it.", + "baseline_image": "/media/unraid/comfyui/output/agent_bridge/img_9e687c32358d4ea8b2f2131a2562d6a7.png", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_8ad856c1ae7e4d27a31fc03b6af690ab.png", + "commit": "pending" + }, + { + "id": "cowgirl-frontal-8989898989-turn93-96-wide-thigh-bridge", + "date": "2026-06-29", + "variant_key": "pov_cowgirl_frontal_straddle_penetration", + "seed": 8989898989, + "source": "sxcp_eval_mcp_batch", + "result": "accepted", + "decision": "prompt_guide_rule", + "baseline_prompt_summary": "Turn 93 used a generic subject-first frontal cowgirl baseline: viewer lying below her, woman straddling him facing him, upright torso, knees open beside the viewer's hips, lower abdomen/thighs/hands foreground anchors, and centered shaft contact in the coworking lounge.", + "candidate_prompt_summary": "Turn 95 kept the same subject, location family, camera family, and sampler seed, but changed the geometry hierarchy to a wide straddling thigh bridge: viewer reclines underneath with lower abdomen/pelvis at the bottom edge, her thighs stretch wide across the lower frame from left edge to right edge, knees planted outside the viewer's hips, torso upright, centered shaft contact below her belly, and viewer hands gripping the sides of her thighs.", + "observation": "Same-sampler frontal cowgirl A/B on seed 8989898989 showed the generic baseline was already a valid woman-on-top POV cowgirl image with coherent subject/look, coworking depth, viewer-underneath body cues, and centered contact. The strongest atlas-like improvement was turn 95: wide-thigh-bridge wording made the thighs span the frame more like refs 100 and 101 while preserving the upright torso, visible contact, viewer lower abdomen/pelvis, and coworking platform. Turn 96 was useful secondary evidence for hands-on-hips/contact-centered wording, but it did not improve the wide straddling silhouette as much. Record this as a prompt-guide rule and first accepted evidence; do not patch the generator from this seed because the baseline already validly hit frontal cowgirl.", + "baseline_image": "/media/unraid/comfyui/output/agent_bridge/img_3328745b7d4b40f99a0b06f25b61f147.png", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_e4bc5d84ca76425b96ac5bf3a5da3f22.png", + "commit": "pending" + }, + { + "id": "cowgirl-frontal-2828282828-two-woman-wide-thigh-bridge", + "date": "2026-06-30", + "variant_key": "pov_cowgirl_frontal_straddle_penetration", + "seed": 2828282828, + "source": "sxcp_eval_mcp_batch", + "result": "accepted", + "decision": "provisional_generator_patch", + "needs_expansion": true, + "baseline_prompt_summary": "Turns 208 and 212 used generic first-person frontal cowgirl wording across two visible women in the coworking scene: viewer underneath, woman straddling his hips facing him, torso upright, knees open beside his hips, lower body foreground anchors, and centered contact.", + "candidate_prompt_summary": "Turns 209 and 213 repeated the wide-thigh bridge hierarchy across both women: viewer lower abdomen and pelvis at the bottom edge, thighs stretching wide from left edge to right edge, knees planted outside the viewer's hips, torso upright above centered contact, and viewer hands or hand/thigh edge support around the outer thighs.", + "observation": "Same-sampler two-woman expansion on seed 2828282828 confirmed the prior prompt-guide rule. The generic baselines were already valid frontal POV cowgirl images with coherent coworking depth and centered contact, so this is an atlas refinement rather than a structural failure. The wide-thigh bridge wording improved the left-to-right thigh span and preserved the upright torso, viewer-underneath body cues, centered contact, and coworking setting on both women. Turn 209 was strongest because it kept explicit viewer hands gripping both outer thighs; turn 213 repeated the broad thigh bridge cleanly with stronger face/torso readability but weaker hand visibility. Mirror the wide-thigh bridge hierarchy into the normal cowgirl generator route provisionally while keeping the route candidate until another seed/source expansion repeats it.", + "baseline_image": "/media/unraid/comfyui/output/agent_bridge/img_aae0319341634b7cac0bdfe54f90518f.png", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_f6d5520d237f4485b688fc50ed9fd77d.png", + "repeat_cases": [ + { + "source_case": "woman_b_wide_thigh_bridge", + "baseline_image": "/media/unraid/comfyui/output/agent_bridge/img_13572d97b7904e2a88abd01dafa3f35f.png", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_e29d225eab974dc191a521ac15d78f1b.png", + "observation": "Second-woman repeat preserved the wide thigh bridge, upright torso, centered contact, and coworking scene; explicit viewer-hand visibility was weaker than turn 209." + }, + { + "source_case": "contact_first_thigh_span_branch", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_2bbdb1e9389d4d20a449128310bfaab3.png", + "repeat_image": "/media/unraid/comfyui/output/agent_bridge/img_df0848af64d542b0b426cae8c1b5be01.png", + "observation": "Valid alternate branch, but it did not improve the wide left-to-right thigh silhouette as clearly as the direct wide-thigh bridge wording." + }, + { + "source_case": "hand_held_thigh_bridge_branch", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_635765baaf0046ec83dddb4b097d1bcc.png", + "repeat_image": "/media/unraid/comfyui/output/agent_bridge/img_92483ea5a6e14d3ebdbf961304d35ef3.png", + "observation": "Useful hand-support wording, but the direct wide-thigh bridge wording gave the cleaner default hierarchy." + } + ], + "commit": "pending" + }, + { + "id": "cowgirl-frontal-2828282828-generated-route-wide-thigh-bridge", + "date": "2026-06-30", + "variant_key": "pov_cowgirl_frontal_straddle_penetration", + "seed": 2828282828, + "generator_seed": 2829, + "source": "sxcp_eval_mcp_generated_route", + "result": "accepted", + "decision": "provisional_generator_patch", + "needs_expansion": true, + "baseline_prompt_summary": "Turns 208 and 212 showed that generic generated-style frontal cowgirl wording already produces valid viewer-underneath cowgirl with centered contact and coworking continuity.", + "candidate_prompt_summary": "Turn 216 used the patched generated cowgirl route: POV frontal cowgirl wide-thigh bridge, viewer lower abdomen and pelvis at the bottom edge, thighs spanning left edge to right edge, knees outside the viewer's hips, viewer hands gripping thigh sides, upright torso, centered contact below her belly, and coworking anchors.", + "observation": "Generated-route validation on seed 2828282828 confirmed the route now carries the repeated wide-thigh hierarchy. Turn 216 preserved the coworking scene, subject readability, viewer lower body, hands on outer thighs, upright torso, and centered contact while making the thighs span the foreground more like the atlas references. Keep this as a provisional normal-cowgirl generator patch because the route was already valid before the refinement; another seed/source expansion should repeat the improvement before promotion.", + "baseline_image": "/media/unraid/comfyui/output/agent_bridge/img_aae0319341634b7cac0bdfe54f90518f.png", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_caebf8fb0fd64fedb69b49ac49b624b5.png", + "commit": "pending" + }, + { + "id": "cowgirl-frontal-9191919191-fresh-seed-wide-thigh-proven", + "date": "2026-06-30", + "variant_key": "pov_cowgirl_frontal_straddle_penetration", + "seed": 9191919191, + "source": "sxcp_eval_mcp_batch_two_visible_women", + "result": "accepted", + "decision": "proven_with_evidence", + "baseline_prompt_summary": "Turn 216 was the previous generated-route validation for the normal cowgirl wide-thigh bridge patch on seed 2828282828.", + "candidate_prompt_summary": "Turns 242-249 retested the patched route on fresh sampler seed 9191919191 across two visible women. The batch included the generated-route wording plus edge-to-edge thigh bridge, low-contact bottom-edge, and hands-on-outer-thigh anchor branches.", + "observation": "Fresh seed 9191919191 repeated the normal frontal cowgirl route across all eight renders. Turns 242 and 246 used the current generated-route hierarchy and preserved viewer-low body ownership, lower abdomen/pelvis bottom-edge anchoring, centered contact below the belly, upright woman torso, coworking depth, and visible hand/thigh support. Turns 242, 243, 244, and 248 were the clearest atlas-like repeats for the wide horizontal thigh sweep; turn 247 remained valid but weaker because one thigh side did not read as fully edge-to-edge. Promote the normal cowgirl wide-thigh bridge route to proven while keeping cowgirl-alt, reverse-cowgirl, and reverse-cowgirl-alt as separate candidate families.", + "baseline_image": "/media/unraid/comfyui/output/agent_bridge/img_caebf8fb0fd64fedb69b49ac49b624b5.png", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_9bdeeba542c1443bad4feef5efad7798.png", + "repeat_cases": [ + { + "source_case": "woman_b_generated_route_wide_bridge", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_4dc1fcc13108424db3f49d83eb8efaef.png", + "observation": "Second-subject generated-route repeat preserved the frontal woman-on-top read, viewer body ownership, centered contact, and coworking scene." + }, + { + "source_case": "edge_to_edge_thigh_bridge_branch", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_fd3c80139f714853a873faccfb07f5b4.png", + "repeat_image": "/media/unraid/comfyui/output/agent_bridge/img_1c0619415c2944b1ae252277fbec4410.png", + "observation": "The edge-to-edge branch produced valid frontal cowgirl on both subjects; turn 243 was the stronger wide-thigh repeat, while turn 247 was valid but less symmetric." + }, + { + "source_case": "low_contact_bottom_edge_branch", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_f29634fa9af94de5b666a4789de84471.png", + "repeat_image": "/media/unraid/comfyui/output/agent_bridge/img_63092703ddbd40539c5167c4f13e7710.png", + "observation": "The low-contact branch preserved the bottom-edge viewer body anchor and centered contact on both subjects, with turn 248 especially close to the atlas body relationship." + }, + { + "source_case": "hands_outer_thigh_anchor_branch", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_4963e3e3975140f7a455e198cbb70b74.png", + "repeat_image": "/media/unraid/comfyui/output/agent_bridge/img_a5836499672e49259e6875a0e695e673.png", + "observation": "The hands branch kept clear hand/thigh support and valid frontal cowgirl on both subjects, though the generated-route and low-contact branches carried the atlas-width thigh sweep more cleanly." + } + ], + "commit": "pending" + }, + { + "id": "cowgirl-alt-8989898989-turn97-104-flat-supine-low-angle", + "date": "2026-06-29", + "variant_key": "pov_cowgirl_alt_low_squat_penetration", + "seed": 8989898989, + "source": "sxcp_eval_mcp_batch", + "result": "accepted", + "decision": "provisional_generator_patch", + "baseline_prompt_summary": "Turn 97 used a subject-first low cowgirl seated-squat baseline: viewer below her with lower abdomen/pelvis/thighs in the lower foreground, woman facing him in a low squat over his pelvis, knees bent wide near the camera, viewer hands under thighs, centered shaft contact, and coworking platform context.", + "candidate_prompt_summary": "Turn 104 kept the same subject, location family, camera family, and sampler seed, but changed the spatial orientation hierarchy: low-angle first-person view from the viewer lying flat beneath her, lens almost on the viewer's abdomen looking upward, woman straddling over him facing him, knees bent wide near the camera, hips low over his pelvis, ceiling lights and upper room lines behind her, and centered contact below her belly.", + "observation": "Same-sampler cowgirl-alt A/B on seed 8989898989 showed that low-squat/contact wording alone is not enough. Turns 97-100 produced coherent woman-on-top images, and turn 99 improved supported-under-thigh squat shape, but the room background still read too much like a platform/high-camera setup rather than the atlas orientation. The user's atlas comparison clarified the missing axis: the man/viewer is completely flat on his back while the woman is mounted over him, which should be visible through ceiling and upper-wall background cues. Turns 101-104 retested that flat-supine axis. Turn 104 was the strongest accepted candidate: viewer abdomen and chest lie flat at the bottom, the camera looks upward from the viewer's body, ceiling/upper glass/vertical window lines sit behind her upper body, and the woman remains mounted over him with wide bent knees and readable centered contact. Mirror this flat-supine low-angle hierarchy into the cowgirl-alt route as a provisional generator patch; keep the catalog candidate until another seed or subject repeats it.", + "baseline_image": "/media/unraid/comfyui/output/agent_bridge/img_b421ea74699e4a31a734f3d17cc8e636.png", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_b9f8c576b5cb418098b4c9128f2def4b.png", + "commit": "pending" + }, + { + "id": "reverse-cowgirl-8989898989-turn105-108-close-back-hip-dominant", + "date": "2026-06-29", + "variant_key": "pov_reverse_cowgirl_back_facing_penetration", + "seed": 8989898989, + "source": "sxcp_eval_mcp_batch", + "result": "accepted", + "decision": "provisional_generator_patch", + "baseline_prompt_summary": "Turn 105 used a subject-first generic reverse-cowgirl baseline: viewer lies on his back, woman straddles his hips facing away, back/ass/thighs and viewer foreground legs visible, centered contact, and coworking lounge depth.", + "candidate_prompt_summary": "Turn 106 kept the same subject, location family, camera family, and sampler seed, but made the close reverse-cowgirl hierarchy explicit: the woman sits on the viewer's pelvis facing away, her back, hips, and ass are the nearest largest shapes, her thighs spread to either side of the viewer's hips, the viewer thighs frame the lower corners, and centered contact sits directly between her thighs below her ass.", + "observation": "Same-sampler reverse-cowgirl A/B on seed 8989898989 showed that generic facing-away wording can still collapse into frontal cowgirl. Turn 105 preserved viewer-underneath contact and coworking depth, but the woman faced the viewer frontally. Turn 106 fixed the core atlas read by making the back/hips/ass closest and largest, keeping the viewer underneath with thighs in the lower corners, and keeping centered contact directly below her ass. Turn 107 was useful secondary evidence for the viewer-leg V-frame but read slightly farther back/upright. Turn 108 was useful secondary evidence for an over-shoulder glance while staying back-facing. Mirror turn106's close back/hip hierarchy into the reverse-cowgirl route as a provisional generator patch; keep reverse-cowgirl-alt separate for the more upright seated atlas family.", + "baseline_image": "/media/unraid/comfyui/output/agent_bridge/img_f1f5902f43c146e3b0ede969744f575f.png", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_bb310b0045464550abbade2dff28dd56.png", + "repeat_cases": [ + { + "source_case": "turn107 viewer_leg_frame_center_contact", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_826e2303147544188a88b5b5a654650d.png", + "observation": "Valid back-facing straddle with a clean viewer-underneath leg frame and readable center contact, but slightly farther back and more upright than the close normal reverse refs." + }, + { + "source_case": "turn108 twist_back_over_shoulder", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_32f4b484b5054811b34d99854546bb92.png", + "observation": "Strong secondary axis for refs with an over-shoulder glance: back-facing hips stay close, viewer thighs frame the bottom, and centered contact remains readable." + } + ], + "commit": "pending" + }, + { + "id": "reverse-cowgirl-alt-8989898989-turn109-112-upright-hands-on-hips", + "date": "2026-06-29", + "variant_key": "pov_reverse_cowgirl_alt_upright_back_facing_penetration", + "seed": 8989898989, + "source": "sxcp_eval_mcp_batch", + "result": "accepted", + "decision": "provisional_generator_patch", + "baseline_prompt_summary": "Turn 109 used a subject-first upright reverse-cowgirl-alt baseline: viewer reclines below, woman sits upright facing away in a back-facing straddle, vertical back and shoulders readable above her hips, ass centered over viewer pelvis, viewer thighs framing the lower foreground, centered contact, and coworking depth.", + "candidate_prompt_summary": "Turn 110 kept the same subject, location family, camera family, and sampler seed, but emphasized the alt atlas hierarchy: she sits tall on the viewer's pelvis facing away, back nearly vertical from hips to shoulders, ass centered above the viewer pelvis, viewer hands holding both sides of her hips, viewer thighs as a lower V-frame, centered contact below her ass, and office depth behind her upright back.", + "observation": "Same-sampler reverse-cowgirl-alt A/B on seed 8989898989 showed the upright seated alt route is manually controllable and visually distinct from the close normal reverse-cowgirl route. Turn 109 was already a valid upright back-facing baseline. Turn 110 was the strongest atlas-alt match because it added the viewer hands on both hips while preserving the vertical back, seated straddle distance, viewer thigh frame, centered contact, subject/look, and coworking setting. Turn 111 was valid but weaker because it lost the viewer hands. Turn 112 was strong secondary over-shoulder evidence, but turn 110 is the cleaner generator-safe hierarchy. Mirror turn110 into a separate reverse-cowgirl-alt route as a provisional generator patch; keep the catalog candidate until another seed or subject repeats it.", + "baseline_image": "/media/unraid/comfyui/output/agent_bridge/img_dcf94ee15e4448c981026e3a68d35c67.png", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_7cd7f02e65a1471ebc061a9ef6a024ff.png", + "repeat_cases": [ + { + "source_case": "turn109 baseline_upright_reverse_alt", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_dcf94ee15e4448c981026e3a68d35c67.png", + "observation": "Valid upright seated baseline: vertical back and shoulders readable, viewer thighs frame the bottom, and coworking depth supports the seated straddle." + }, + { + "source_case": "turn112 over_shoulder_upright_back", + "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_e82150d9fd614770a676f6a53fb20032.png", + "observation": "Strong secondary over-shoulder variant with upright back, viewer hands on hips, and centered contact, but the glance is a refinement rather than the core alt hierarchy." + } + ], + "commit": "pending" } ] } diff --git a/docs/krea2-prompt-guide.md b/docs/krea2-prompt-guide.md index 0b667b3..06f31f3 100644 --- a/docs/krea2-prompt-guide.md +++ b/docs/krea2-prompt-guide.md @@ -25,16 +25,31 @@ Avoid letting two sections describe incompatible camera or framing intents. - `sxcp_eval_out` must contain only the prompt being tested. - Analysis, scoring, and generator notes belong in chat or `sxcp_eval_log`. - Keep one experiment variable per cycle when possible. -- Lock seed, character, location, and camera when testing wording changes. +- Lock sampler seed, character, location, and camera when testing wording changes. - Treat the MCP seed as transport metadata. Preserve it for prompt-only A/B tests and do not write it into the visible prompt text. +## Positive-Only Conditioning + +Never put negative-conditioning phrases inside the positive Krea2 prompt. A +positive prompt should describe only the visual elements that should appear. +Do not write phrases such as `no shaft`, `no hands`, `without clothing`, or +`avoid X` into `sxcp_eval_out`; for distilled Krea2, those phrases can reinforce +or hallucinate the unwanted object. + +This SxCP loop has no active negative-output contract. A same-positive, +same-seed probe on seed `424242` compared empty negative conditioning against +strong negative text targeting visible prompt attributes, and the rendered +images stayed visually unchanged. For this workflow, keep Krea2 pose fixes in +positive wording only. + ## Seed-Controlled A/B Tests -Use one fixed seed when deciding whether prompt wording helped Krea2. A single -image can justify a prompt-only retry when the mismatch is obvious, but a -generator rule needs either repeated evidence or a generated prompt that is -structurally wrong before rendering. +Use one fixed sampler seed when deciding whether prompt wording helped Krea2. +If the SxCP generator/control seed differs from the sampler seed, record it as +`generator_seed` in the eval log. A single image can justify a prompt-only retry +when the mismatch is obvious, but a generator rule needs either repeated +evidence or a generated prompt that is structurally wrong before rendering. When a workflow batches soft/hard prompts through an index switch, sidecar text files may not be the exact prompt used for each rendered image. If the sidecar @@ -43,12 +58,28 @@ input before patching the generator. When reviewing an eval payload, log: -- emitted seed, +- emitted sampler seed, +- generator/control seed when it differs, - original generated prompt, - edited prompt, - image failure or improvement, - whether the change should stay prompt-only or become a generator patch. +## Evidence Promotion Threshold + +Do not promote a single-character, single-location prompt hack into a durable +guide rule. A prompt-only fix can be recorded as accepted when it cleanly +improves the fixed-seed image, but a general wording rule needs either repeated +evidence across distinct subjects/locations or a source prompt that is +structurally wrong before rendering. + +Scene repair words must match the selected location. Do not use a bedroom, +bedding, couch, shower, car, or other scene anchor to solve a foreground problem +inside an office/coworking setup unless that anchor is already present in the +selected scene. If the model needs a foreground filler to avoid hallucinating +extra anatomy, choose an anchor from the actual location or keep the result as +an inconclusive prompt-only retry. + ## Camera And Composition ### Orbit / Multiangle Camera @@ -146,6 +177,72 @@ standing/kneeling climax pose, but the Krea prompt dropped `semen` and kept only generic aftermath language. The generator now preserves direct `ejaculates semen` wording for this path before the prompt reaches Krea2. +### Ready / Post-Ejaculation Open-Thigh Display + +The ready atlas folder is not a neutral setup pose. Treat it as a still +post-ejaculation open-thigh display where the fluid-covered exposed opening is +the first visual priority and the active lower-foreground action is secondary. + +Works better: + +- `POV post-ejaculation open-thigh display` +- `the wet aftermath detail is the exact center` +- `thick semen and clear fluid cover the exposed pussy and inner thighs` +- `her body stays still after ejaculation` +- `her face and torso remain visible behind the open-thigh frame` +- `office chair seat and chair arms frame the lower foreground` +- `desk edges, laptop tables, glass partitions, plants, and tall-window depth` + +Failure modes: + +- generic `ready` or setup-before-sex wording +- active penetration or thrusting becoming the main read +- fluid detail present but secondary to a lower-foreground body cue +- bedroom/bedding anchors inserted into coworking scenes +- closed thighs or cropped torso hiding the atlas open-thigh display + +Fixed-seed finding: CodexMCPTest source subject `52` with sampler seed +`1123581321` produced first ready/aftermath evidence. The generic baseline kept +the coworking scene and open-thigh pose, but collapsed into active +lower-foreground penetration with fluid secondary. The accepted candidate made +the wet aftermath detail the primary visual event while preserving the +office-chair/coworking setting. Under the category-exit rule this is mirrored +into the generator as a provisional improvement over baseline, while the catalog +route remains candidate pending broader repetition. + +### Spread / Open-Thigh Presentation + +The spread atlas folder is a direct camera-facing presentation pose. The key +read is the leg geometry first: raised knees or wide thighs form a V-frame +toward the viewer, hands hold the knees or thighs, and the face and torso remain +visible behind the open-thigh frame. + +Works better: + +- `knees raised and held wide` +- `thighs form a broad V-frame around the centered exposed vulva` +- `hands hold her knees and upper thighs` +- `face and torso remain visible behind the open-thigh frame` +- `office chair seat and chair arms frame the lower foreground` +- `desk edges, laptop tables, glass partitions, plants, and tall-window depth` + +Failure modes: + +- low seated spread with legs open but no raised-knee V-frame +- missing hands, or hands not anchoring the knees/thighs +- outfit remaining closed over the center of the pose +- partner/contact wording competing with the camera-facing presentation +- scene repair words that turn the coworking office into a bedroom setup + +Fixed-seed finding: CodexMCPTest source subjects `50` and `47` with sampler seed +`3141592653` showed the atlas hierarchy improved over generic spread wording on +two visible subjects. Source `50` already produced a coherent low seated spread, +but the candidate added hands on raised knees and a stronger V-frame. Source +`47` failed baseline exposure because the shorts stayed closed; the candidate +opened the center and added raised knees and hand anchors. The generator now +carries this as a provisional improvement over baseline, while the catalog route +remains candidate pending a wider V-frame repeat on another seed. + ## POV Outercourse ### Ballsucking / Testicle Sucking @@ -161,8 +258,21 @@ 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` +- `head below the shaft base, chin near the viewer's pelvis` +- `mouth lower than the shaft base and aligned to the underside/scrotum` +- `balls fill the exact center foreground as the largest contact object` +- `lips and tongue press to the scrotum and balls` +- `shaft is cropped to the side as a landmark, away from her lips` +- `composition priority is balls first, scrotum contact second, woman's low face third` + +Partial side-low axis: + +- `low side-pelvis POV` +- `face is the closest visible partner part` +- `cheek against the viewer's inner thigh` +- `scrotum and testicles centered against her tongue and lips` +- `balls fill the lower center foreground at her mouth` +- `viewer abdomen and inner thighs close around her face` Avoid: @@ -170,6 +280,427 @@ Avoid: - generic `kneels in front of him` - making the viewer the main subject before the visible woman is established - mid-height head placement +- making the raised shaft the centered visual target above her mouth +- repeating shaft/hand-on-shaft wording before the scrotum/testicle contact is + visually established +- `hanging` or `pressed from above` wording when it creates suspended-object + artifacts +- `two testicles against tongue` if it turns into wrong rounded anatomy or a + different body layout + +Fixed-seed finding: sampler seed `238365845574312` showed that low-head wording +improves body placement, but Krea2 can still collapse into generic shaft-focused +oral contact when the raised shaft stays centered above the mouth. The accepted +prompt made the scrotum/balls the largest exact-center object and moved the +shaft to a cropped side landmark. Keep this as a prompt-guide rule, but do not +promote the catalog route to proven until the rule repeats on another controlled +seed. + +Expansion weak case: sampler seed `1212121212` repeated across two women did +not validate the side-cropped scrotum rule. The candidates made the balls and +scrotum larger, but the head stayed too high and the mouth still targeted the +shaft/glans. Literal wording like `sucking the testicles with her mouth` helped +one result move the shaft off-axis, but it still used tongue-to-base contact +with the head too high. Next tests should avoid label-like +`testicle-sucking position` wording and make head height the primary axis: +head below the shaft base, chin near the viewer pelvis, mouth lower than the +balls/scrotum contact point. + +Threshold search update: sampler seed `1212121212` then received about fifty +positive-only prompt probes on one subject plus six repeats on a second subject. +The under-base, chin-near-pelvis, hand-held-side, and direct +`balls in mouth` wording families mostly collapsed back to generic shaft/glans +oral contact. The repeatable partial axis was the side-low family: +`low side-pelvis POV`, `cheek against the viewer's inner thigh`, and +`scrotum/testicles centered against her tongue and lips`. Turns `149`, `162`, +`167`, and `170` show that this can move the contact toward the balls/scrotum, +and tongue-on-testicles is an accepted improvement over the baseline shaft/glans +collapse. Mirror this side-low partial axis into the generator provisionally, +but keep the route queued for broader seed/source expansion because it still +does not reliably produce lips fully wrapped around the testicles. + +Generated-route validation: turn `172` showed that the first generator patch +could keep tongue/testicle contact low but still let the woman's torso dominate +the composition. Adding `face is the closest visible partner part` produced +turn `173`, where the face stayed low beside the viewer's thigh and the +tongue/testicle contact remained centered. Keep the face-priority cue in the +generator wording and continue testing because body/rear prominence can still +steal the pose. + +Fresh-seed side-camera weak case: sampler seed `5757575757` repeated the +generated side-low route and three contact-priority variants across two women. +Turns `217`-`224` looked strong as side-camera oral views: the face stayed low +between the viewer abdomen/thigh foreground, coworking depth held, and the +cheek/inner-thigh relationship stayed readable. They did not improve exact +ballsucking contact because the mouth and tongue still targeted the shaft/glans. +Record this as useful side-view oral wording, not proof that text alone controls +the testicle-contact object. Bookmark: +`docs/krea2-visual-bookmarks/ballsucking-side-camera-turn221-side-low-oral-weakcase.png`. + +Fresh-seed partial update: sampler seed `6262626262` retested the route across +two visible women with twelve positive-only probes. The generated-route +controls on turns `250` and `256` preserved the low coworking side-pelvis view +but still drifted toward shaft/glans oral contact. The best repeatable branch +was turns `252` and `258`: + +- `scrotum is the mouth surface` +- `testicles resting across her open lips` +- `tongue cups them from below` +- `head lies sideways on the viewer's inner thigh` +- `chin close to the viewer pelvis` + +This is still a partial, not a proven full mouth-wrapped testicle-sucking +result. It is better than the generated-route controls because the contact +moves toward the scrotum/testicles while retaining the atlas side-low +cheek/thigh relationship. Turns `253` and `259` showed a useful secondary +tongue-cradle underside branch, but it reads more centered/frontal than the +side-low atlas family. Patch the provisional generator route to the open-lips +scrotum-surface hierarchy and keep the route queued for more seeds. + +Generated-route validation: turns `262` and `263` rendered the patched route +wording directly after the generator edit. Both validation renders kept the low +side face/thigh relationship and moved the tongue/lip contact to the underside +scrotum/testicle area with less shaft-tip dominance than the generated-route +controls on turns `250` and `256`. Keep this as a provisional improvement, not +a proven route, because the result remains tongue/lips-on-testicles rather than +fully mouth-wrapped testicle sucking. + +Fresh-seed target-object repeat: sampler seed `9797979797` retested the current +open-lips route and four target-object branches across two visible women. The +generated-route control could succeed on turn `286`, but the blonde control on +turn `291` still drifted toward shaft/base contact. The best repeatable branch +was `scrotum_skin_to_lips` on turns `288` and `293`: + +- `scrotal skin is the nearest mouth surface` +- `both testicles rest against her tongue from below` +- `cheek and jaw compressed into the thigh` +- `chin tucked close to the pelvis` + +Treat this as a narrow target-object refinement, not a full solution. It +improves the partial hierarchy over the weak control while preserving side-low +cheek/thigh geometry, but it still reads as tongue/lips-on-testicles rather +than fully mouth-wrapped testicle sucking. The `balls_first_center_object` +branch is unsafe as default because turn `290` overfit into a multi-subject or +body-layout artifact. + +Generated-route validation: sampler seed `9898989898` rendered the patched +scrotal-skin route directly across two visible women. turns `296` and `297` +both preserved side-low cheek/thigh geometry, kept the face low between viewer +abdomen and inner-thigh cues, and placed scrotum/testicles at the tongue/lip +contact surface. This validates the route patch as a target-object improvement, +but it remains partial because the result is still tongue/lips-on-testicles, +not fully mouth-wrapped testicle sucking. + +Fresh-seed weak case: sampler seed `5959595959` retested the current generated +route and three stronger mouth-contact axes across three visible women. The +generated controls on turns `307`, `311`, and `315` kept useful low-pelvis +framing but still drifted toward shaft/glans contact. The `lip-oval` branch on +turns `308`, `312`, and `316` made the scene more upright and shaft-centered. +The `sideways mouth pocket` branch on turns `309`, `313`, and `317` preserved +the best cheek/thigh side-low geometry, but the mouth still targeted the shaft +or base rather than wrapping the testicles. The `chin-pelvis upward seal` +branch on turns `310`, `314`, and `318` became a frontal shaft-focused oral +read. Do not patch the generator from these branches; record this as another +target-object weak case and continue exploring stronger control or a different +positive wording axis. + +Fresh-seed occlusion weak case: sampler seed `6060606060` tested a different +target-control strategy after the mouth-shape words failed. The generated +controls on turns `319`, `323`, and `327` were still the best partials, +preserving side-low geometry and some visible scrotum/testicle contact. The +`scrotum foreground occlusion` branch on turns `320`, `324`, and `328` became +upright shaft-centered oral instead of making the scrotum cover the mouth. The +`under-scrotum tongue shelf` branch on turns `321`, `325`, and `329` kept some +low-pelvis framing but still targeted the shaft/glans. The `hand-guided scrotum` +branch on turns `322`, `326`, and `330` made hands and shaft ownership +dominate. Do not patch these axes into the generator; future attempts should +change the target-object strategy or use stronger control rather than adding +more occlusion or hand-support synonyms. + +Fresh-seed mouth-axis mixed case: sampler seed `6161616161` tested five new +positive-only axes across three visible women after the occlusion/hand-support +batch failed. The generated-route controls on turns `331` and `337` gave the +best repeated partials: low pelvis framing, face close to the inner thigh, and +tongue/testicle contact. The third generated control on turn `343` still +collapsed onto shaft contact, so the route remains candidate. + +Failed or non-repeatable axes: + +- `exact mouth-sucking`: literal `sucking the testicles with her mouth` wording + still became shaft/glans-focused on the clearest inspected branch. +- `single-testicle`: putting a single testicle between the lips did not make the + model hold the testicle as the mouth target. +- `hanging balls below shaft`: separating the target below the lifted shaft + mostly preserved shaft/glans oral. +- `side mouth wrap`: turn `335` produced a useful side-oriented partial, but + turns `341` and `347` collapsed back to shaft contact. +- `chin-pelvis lower-mouth target`: turn `348` produced a useful frontal + target-object partial with the mouth lower and testicles visible at the + tongue/lip surface, but the branch is not repeatable enough for generator + wording. + +Conclusion: keep the current provisional generated route as the best repeated +partial for now. Do not patch exact-mouth, single-testicle, hanging-balls, +side-mouth-wrap, or chin-pelvis lower-mouth wording into the generator from +this batch. The full mouth-wrapped target still needs broader expansion or +stronger control. + +Fresh-seed pelvis-valley weak case: sampler seed `7171717171` tested +body-plane and camera-height wording after mouth-shape and target-object words +kept failing. The useful atlas comparison was that refs such as `101` place the +viewer flatter and lower, with the partner tucked into the pelvis valley +between the thigh roots. + +The `flat pelvis-valley` branch repeated the strongest spatial improvement on +turns `350`, `356`, and `362`: + +- viewer reads flatter and more supine; +- inner thighs become side walls around her face; +- her face is tucked lower inside the thigh-wall pelvis valley; +- coworking depth remains location-compatible. + +However, this was a body-plane correction, not a target-object correction. All +three repeated flat-pelvis results still made the mouth target the shaft, so do +not patch `flat pelvis-valley` into the ballsucking generator route as-is. Treat +it as a useful orientation hint for stronger control or for a separate +flat-pelvis oral branch. + +Other branches from this batch were weaker: + +- `thigh tunnel` and pubic-hair mouth-line wording mostly repeated the same + shaft-centered target. +- `low-cushion chin anchor` drifted into wrong open-thigh/presentation geometry. +- `pelvis-edge target-first` also drifted toward open-thigh/presentation layout + or target ambiguity. + +Conclusion: body-plane wording can fix the atlas orientation while making the +target object worse. Score these separately. For this route, the current +side-low scrotal-skin generated wording remains the better ballsucking partial. + +Fresh-seed flat-target hybrid weak case: sampler seed `7272727272` combined the +best prior body-plane wording with the best prior target-object wording across +three women. The tested branches were `flat-valley scrotal-skin target`, +`valley-floor open lips`, `upper-frame shaft lower-scrotum`, +`cropped upper-shaft valley-mouth`, and `side-low flat-valley hybrid`. + +The hybrid did not beat the current generated route. The +`flat-valley scrotal-skin target` branch repeated the flatter body-plane read +on turns `368`, `374`, and `380`, but all three stayed shaft-centered. The +`side-low flat-valley hybrid` branch kept some useful side-low look hints on +turns `372`, `378`, and `384`, but the target contact remained ambiguous or +shaft-centered. The upper-frame and cropped-shaft branches also reinforced the +wrong target. + +Conclusion: stop the ballsucking text-only prompt loop here for now. Keep the +current side-low scrotal-skin partial as the provisional generator route, keep +the pose queued as candidate/weak, and treat the full target as likely needing +stronger control rather than more positive-prompt synonyms. + +### Footjob + +The atlas examples are frontal first-person views. The viewer reclines with +thighs framing the lower foreground, the woman sits opposite with her body and +face behind her feet, and the feet are the main contact object around the +upright shaft. + +Works better: + +- `tight frontal POV footjob close-up` +- `large overlapping soles dominate the lower center foreground` +- `upright shaft compressed between the two soles` +- `a narrow vertical strip of shaft and glans rises between her pressed soles` +- `inner arches and toe pads press inward against the shaft` +- `toes curled around both edges of the shaft` +- `her face, torso, and open thighs remain visible behind her large foreground feet` + +Alternate branch: + +- `one bare foot crosses horizontally in front of the shaft` +- `sole presses sideways into the glans and upper shaft` +- `other foot braces from the opposite side near the base` + +Avoid: + +- generic `uses her feet on the viewer's penis` without overlap/contact pressure +- `soles on either side` if it leaves the shaft standing free +- feet too small or too low at the base of the shaft +- hands or mouth competing with the feet as the active contact + +Fixed-seed finding: sampler seed `238365845574312` showed that generic footjob +wording can produce the correct frontal POV layout, but the feet may sit beside +the shaft rather than visibly compressing it. The accepted prompt used large +overlapping soles and a shaft trapped between the two soles, with only a narrow +visible strip between the feet. Keep this as a prompt-guide rule, but do not +promote the catalog route to proven until it repeats on another controlled seed. + +Expansion finding: sampler seed `3434343434` repeated across two women showed +the baseline was already a valid frontal POV footjob, but `large two-sole clamp` +wording improved foot scale and contact compression on turns `176` and `180`. +The cross-foot side-press wording produced atlas-ref-`86` style alternates on +turns `177` and `181`, but it is a separate branch and weaker as the default. +Mirror the two-sole clamp wording into the generator provisionally and keep +cross-foot wording as an alternate guide branch. + +Generated-route correction: turn `183` showed that `two large soles dominate` +is not enough by itself; the route can turn into feet presented to the viewer +and hide the actual contact. Turns `184`, `185`, and `186` restored the target +when the prompt said the soles `clamp the upright shaft between them` and `the +glans rises between the compressed feet`. Turn `188` validated the patched +generated route with the same cue: large soles stayed dominant while the shaft +and contact remained readable. Keep that visibility cue in generator wording +and treat cross-foot side-press as a separate alternate branch. + +Fresh-seed route tightening: sampler seed `6868686868` repeated the generated +route and three contact variants across two women. Turns `225` and `229` showed +that the generated route can still hide the shaft/contact behind large soles. +Turns `227` and `231` repeated the stronger default: `large overlapping soles` +with `a narrow visible strip of shaft and glans` rising between the compressed +feet. Turn `226` supported the same center-gap visibility axis; turns `228` and +`232` kept cross-foot side-press useful as a separate alternate. Turn `233` +validated the patched generated route with overlapping soles, the centered +shaft/glans strip, viewer body cues, and coworking depth intact. The generator +route now uses the overlapping-sole/narrow-strip wording. + +Fresh-seed promotion: sampler seed `7373737373` repeated the patched generated +route across two visible women. Turns `264` and `267` used the exact +overlapping-sole/narrow-strip generated route and preserved large foreground +sole dominance, visible centered shaft/glans strip, toe/arch pressure, face and +torso behind the feet, and coworking depth. Turns `265` and `268` repeated the +tight center-gap visible-glans branch as supporting evidence. Turns `266` and +`269` remained valid cross-foot side-press alternates, but they are not the +default because they give up the two-sole enclosure. Treat the default frontal +footjob route as proven; keep cross-foot side-press as an alternate guide +branch. + +### Fingering / Manual Stimulation + +The atlas examples are close first-person manual-contact views. The visible +partner reclines with open thighs toward the camera, one foreground hand enters +from the lower frame, and the hand is the main contact object. Generic +male-participant POV wording can make Krea2 insert lower-foreground anatomy that +competes with the hand. + +Works better: + +- `single foreground hand is the largest lower-frame object` +- `wrist entering from bottom center` +- `two fingers placed at her vulva and clit as the clear contact point` +- `open thighs forming a V around one camera-side hand` +- `manual contact first, open-thigh geometry second, woman face and torso third` +- `office-chair support and coworking depth fourth` + +For coworking or office scenes, keep the foreground and depth anchors from the +selected location: + +- `black office chair seat and chair arms frame the lower foreground` +- `near desk edge` +- `laptop table corners` +- `glass partition seams` +- `repeated desk rows` +- `plants and tall-window depth` + +Failure modes: + +- generic `viewer fingers her` wording without hand scale or ownership +- male-participant POV language that makes the lower foreground the main subject +- bedding or soft-surface anchors inside an office/coworking scene +- multiple foreground body cues competing with the active hand + +Fixed-seed finding: sampler seed `238365845574312` repeated across +CodexMCPTest source subjects `46` and `47`. In both baselines, generic +fingering/manual-contact wording collapsed into shaft-forward lower-foreground +anatomy. The accepted prompt kept the coworking scene intact and changed only +the hierarchy: one large foreground hand, open thighs, woman face/torso, then +office-chair/coworking depth. Keep this as a prompt-guide rule, but do not +promote the catalog route to proven until it repeats on another seed or location +family. + +Matrix update: CodexMCPTest source subject `50` with sampler seed `987654321` +repeated the same pattern. The generic baseline kept the coworking setting but +collapsed into shaft/body-dominant lower-foreground anatomy; the hand-first +candidate preserved the coworking office setting and produced readable active +fingers. This strengthens the prompt-guide rule and adds the required second +sampler seed. The manual/fingering generator now carries this hierarchy as a +provisional improvement over baseline, while the catalog route remains +`candidate` until broader evidence proves it. + +Weak-case update: CodexMCPTest source subject `52` with sampler seed +`1357913579` shows the current rule is still too early for generator mirroring. +The generic baseline failed in the same lower-foreground way, and a camera-side +hand-first candidate still kept the wrong lower-foreground anatomy dominant. A +second candidate succeeded only after making the active hand explicitly the +woman's own right hand and using the office chair cushion/arms as the positive +lower-frame structure. Keep this as a prompt note, not a generator rule for the +participant-hand POV route, because the successful candidate changed hand +ownership semantics. + +Experimental generator-mirror probe: generator seed `4303` with sampler seed +`238365845574312` showed that a generated-route prompt using the hand-first +hierarchy can preserve the office/coworking setting and remove the shaft-forward +foreground artifact from generic baselines. This is not enough to justify a +production generator patch. Collect a broader seed, subject, and location matrix +before changing route code for this case. + +### Wand Toy Contact + +The atlas examples are close first-person toy-contact views. The visible +partner reclines or sits back with open thighs toward the camera, face and torso +behind the leg frame, and one foreground hand holds a wand-style massager with +the rounded head at the central contact point. + +Works better: + +- `single continuous teal wand-style massager is the largest lower-frame object` +- `rounded bulb head presses flat to her vulva and clit as the central contact point` +- `smooth handle angles in from the bottom right inside the viewer's visible hand` +- `open thighs and knees form a V around the foreground wand` +- `wand head contact first, open-thigh geometry second, visible hand and handle third` +- `woman face and torso fourth, coworking depth fifth` + +For coworking or office scenes, reuse the location anchors instead of adding +bedroom surface cues: + +- `office chair seat and chair arms frame the lower foreground` +- `desk edges` +- `laptop tables` +- `glass partitions` +- `plants and tall-window depth` + +Failure modes: + +- generic `handheld wand vibrator is used near her` without device continuity +- Krea2 splitting the cue into a contact toy plus a second wand-like foreground object +- toy floating without a visible hand or handle +- the woman holding the toy when the intended view is a foreground viewer hand +- mouth, foot, penetration, or manual-contact wording competing with the wand + +Fixed-seed finding: CodexMCPTest source subject `48` with sampler seed +`246813579` produced first wand evidence. The generic baseline preserved the +coworking setting and found a toy-contact pose, but split the prompt into two +wand-like objects. The accepted candidate made one continuous teal wand device +the primary lower-frame object, with a readable handle grip and bulb-head +contact. Treat this as first prompt-guide evidence only; repeat it on another +source or seed before changing generator defaults. + +Expansion finding: sampler seed `8642086420` repeated the teal lower-right +single-device axis across two visible women on turns `190` and `194`, improving +over generic baselines `189` and `193`. The black close-bulb wording drifted +toward face/mouth contact, while the upper-left pale wand is useful as an +atlas-`107` alternate but weaker as a default. Turn `197` validated the patched +generated route from generator row seed `46`: one continuous teal wand, visible +viewer hand, central bulb contact, and coworking office-chair depth all +survived. Mirror this axis into the generator provisionally and keep the route +queued for another source/seed expansion. + +Proven-route repeat: sampler seed `7979797979` repeated the generated teal +lower-right route across two women on turns `234` and `238`. Device continuity, +visible viewer hand/handle, central bulb-head contact, open-thigh chair +geometry, visible subject, and coworking depth all survived. Contact-first teal +variants on `235` and `239` also worked. The white upper-left branch repeated +on `236` and `240` as a valid alternate for atlas refs with diagonal handle +entry. The large-bulb branch on `237` and `241` is rejected as the default +because it can hide exact contact and overtake the lower frame. The catalog +route is now proven with the teal lower-right single-continuous-device default. ### Boobjob / Titjob @@ -250,6 +781,468 @@ Evidence: target consistently when the wording used `across her ass, thighs, and lower back` and kept the clothing state tied to the lower garment. +### Blowjob Top View / Overhead Vertical Shaft + +The atlas examples are steep overhead first-person oral views. The useful +structure is not generic kneeling oral and not just a vertical shaft; it is a +nadir-angle standing male POV where the nearby floor plane dominates and the +woman is directly below the viewer between his feet. + +Works better: + +- `nadir-angle standing male POV top-view oral position` +- `viewer looks almost straight down from his torso toward the floor` +- `nearby carpet/floor plane dominates the image` +- `viewer abdomen, shorts, thighs, and feet frame the lower foreground` +- `shaft appears as a short centered vertical column from the foreground` +- `one woman kneels directly below the viewer between his feet` +- `hair crown, forehead, shoulders, hands, and knees are visible from above` +- `desk legs, chair wheels, carpet texture, and floor seams as top-down office anchors` +- `mouth seals around the centered shaft` +- `one hand wraps the base` +- `face, shoulders, and compact foreshortened torso remain readable beneath the shaft` + +Failure modes: + +- generic kneeling oral that drifts into side-profile framing +- off-center shaft/mouth alignment +- hand present but not supporting the vertical centerline +- a close face crop that loses the viewer-underbody lower edge +- `plumb-line` or `map` wording that literalizes into drawn graphics + +Fixed-seed finding: CodexMCPTest source subjects `46` and `47` with sampler +seed `4242424242` showed that generic blowjob/oral wording can already produce +a usable top-down image, but the atlas hierarchy tightened the shaft-to-mouth +axis. Source `46` gained clearer hand-at-base structure; source `47` moved from +a side-profile/off-center read into a centered vertical top-down read. The +generator now carries this as a provisional improvement over baseline, while +the catalog route remains candidate pending another seed. + +Correction probes: a later same-seed source-`46` probe showed that vertical +center-shaft wording and generic steep-overhead wording can still feel too +frontal or horizontal. A short axis loop found the stronger terms: `nadir-angle` +or `bird's-eye` paired with `standing male POV`, `floor plane dominates`, nearby +top-down office anchors, `one woman directly below between his feet`, and +`short centered vertical column`. The `plumb-line` and `map` terms produced +good geometry but literal drawn artifacts, so they should stay out of generator +wording. + +### Blowjob Side Profile / Side-Phone Weak Case + +Seed `5656565656` showed that current side-profile oral wording is good at a +related composition, but that composition is not valid POV evidence for +`pov_blowjob_side_profile_oral`. It reads like a side-phone or external +side-camera shot taken from beside the man rather than a true first-person +viewpoint from his eyes or body line. + +Useful for a future non-POV route: + +- `side-phone oral composition` +- `external side-camera oral view` +- `woman kneels or leans beside the man's pelvis` +- `side-facing mouth and cheek profile remain readable` +- `office/coworking depth remains visible beside and behind the bodies` + +Visual bookmark: turn `202` +[`blowjob-side-camera-turn202-awesome-side-phone.png`](krea2-visual-bookmarks/blowjob-side-camera-turn202-awesome-side-phone.png) +is a strong side-camera / side-phone composition for a future non-POV route. It +should not be counted as POV evidence, but it is a good look target for the +side-camera family. The original output filename is also preserved as +[`blowjob-side-camera-img_5a776aafda8f4a80ab12d995e4ce8020.png`](krea2-visual-bookmarks/blowjob-side-camera-img_5a776aafda8f4a80ab12d995e4ce8020.png). + +Do not count as POV evidence: + +- attractive side-camera framing where the camera is beside the man +- the viewer's body appearing as a photographed subject rather than the camera + owner +- side profile that loses the first-person lower-body viewpoint + +Fixed-seed weak case: CodexMCPTest source subjects `46` and `47` with sampler +seed `5656565656` produced four attractive side-phone / external side-camera +renders. The stricter candidate improved profile readability in some ways but +did not make the result more POV. Keep this wording for a later non-POV +side-camera route; do not patch the POV generator from it. + +Single-source improvement: the same seed on source `46` improved when the prompt +made the foreground owner explicit as the adult male viewer: + +- `visible adult male chest, navel, abdomen hair, pelvis, and thighs form the + central foreground surface` +- `woman lies on her side beside his left hip` +- `her torso lateral along the left edge of the frame` +- `side-facing mouth seals around the shaft near the center of the male abdomen` +- `one of her hands wraps the base directly below her lips` + +This produced an atlas-like first-person body-line view with the male abdomen +and navel as the central foreground and the woman entering laterally from the +side. Treat it as accepted single-source evidence, not a generator rule. + +Fragility warning: source `47` on the same sampler seed rejected a related +`body-axis` candidate because Krea2 transferred the body-axis cue to the +woman's torso/body, producing a seated or straddling non-POV composition. Future +side-profile oral tests should name the foreground owner repeatedly as the +adult male viewer and verify that the woman's body stays lateral instead of +becoming the central body surface. + +Matrix update: sampler seed `9753197531` repeated the corrected side-profile +body-line axis across two visible women. Generic baselines on turns `198` and +`202` remained attractive side-phone or external side-camera reads. Pure +`male_body_axis` wording on turns `199` and `203` was fragile because it could +make the male read as the photographed subject. The repeatable winner was the +`lateral_edge_entry` axis on turns `200` and `204`: the male viewer's abdomen, +navel, pelvis, and near thigh create a broad horizontal foreground body +surface; the woman enters laterally from the left edge beside his hip; cheek +and jaw stay in profile; mouth contact sits at the male abdomen line; and one +hand stays around the base under her lips. Mirror this lateral-edge body-line +hierarchy into the side-lying oral generator route provisionally, while keeping +the variant queued for broader seed/source expansion. + +Generated-route validation: turn `206` proved that the patched generated route +can preserve the male abdomen body-line, lateral side-profile entry, and +coworking depth, but it let the mouth/tongue float above the shaft while the +hand became the contact anchor. Adding `lips touching the shaft at the male +abdomen line` and `mouth-to-shaft contact is the nearest facial detail` +produced turn `207`, which preserved the body-line composition and moved the +oral contact onto the shaft. Keep those contact-priority terms in the generator +wording, and keep the route provisional because the result is still closer to +mouth/tongue contact than fully mouth-wrapped side-profile oral. + +Fresh-seed weak case: sampler seed `8484848484` retested the side-profile +body-line route across two visible women. Generated-route turns `270` and +`273` preserved some useful male body-line foreground and mouth contact, but +still read partly as external side-camera compositions. Abdomen-line contact +lock turns `271` and `274` made Krea2 transfer the body-line cue onto the +woman's torso/body, so the woman became the photographed foreground subject. +Lateral-edge mouth-first turns `272` and `275` improved attractive side-camera +mouth contact, but the male appeared as a photographed subject at the frame +edge. Treat this as a weak-case record: do not promote the route and do not +patch from these branches. Future probes need stronger viewpoint ownership +without making either participant a photographed side-camera subject. + +Fresh-seed lower-right torso repeat: sampler seed `9595959595` retested the +current generated route against body-owner wording across two visible women. +The current route controls, turns `276` and `280`, preserved contact, but the +blonde control still exposed the male as a photographed side subject. The +`lower_right_torso_anchor` branch repeated on turns `279` and `283`: `adult male viewer's own torso starts at the lower edge`, +runs into the lower-right foreground, and navel, abdomen hair, pelvis, and +near thigh mark the camera owner's body. This improved viewpoint ownership +while keeping lateral side-profile mouth contact. Mirror this lower-right +torso anchor into the side-lying oral generator provisionally, keep the route +queued, and continue rejecting the attractive side-camera branch as non-POV +evidence. + +Generated-route validation: sampler seed `9696969696` rendered the patched +side-profile route directly across the same two visible women. Turns `284` and +`285`; turns `284` and `285` both kept the lower-right own-body foreground, navel/abdomen hair/pelvis +and near thigh as camera-owner landmarks, side-profile mouth contact at the +abdomen line, and coworking depth. This validates the provisional route patch +against the previous photographed-male-face side-camera failure, but it is not +enough to promote the pose; keep expanding across source images and seeds. + +Fresh-seed promotion: sampler seed `5858585858` retested the patched generated +route across three visible women, with two conservative wording branches beside +each control. Generated-route controls on turns `298`, `301`, and `304` +repeated the same usable structure: the male abdomen/navel/pelvis/near thigh +remain the lower-right camera-owner body plane, the woman enters laterally in +side profile, mouth contact stays at the abdomen line, and the coworking depth +remains behind the bodies. The lower-edge owner branch on turns `299`, `302`, +and `305` strengthened body ownership but did not beat the default cleanly. +The `side-camera-style self-body crop` branch on turns `300`, `303`, and `306` +looked strongest aesthetically, but it sits closer to the non-POV side-camera +family. Promote the current generated side-profile POV hierarchy to proven and +keep the side-camera-style wording as a future look branch rather than the +default POV route. + +### Blowjob Laying Frontal / Wide V-Frame + +For prone frontal oral, generic `woman lies belly-down between his open thighs` +wording can already produce usable centered oral contact. The fixed-seed +improvement is the atlas hierarchy around the viewer's thigh frame and the +woman's low body line. + +Useful wording: + +- `open thighs form a wide symmetrical V-frame from the lower corners toward + the center` +- `pelvis and lower abdomen at the near edge` +- `woman lies belly-down in the gap between his thighs` +- `torso stretched low and horizontal` +- `hips and legs trailing away behind her along the center line` +- `hands wrap the base` +- `centered mouth-to-shaft contact` + +Fixed-seed finding: CodexMCPTest source subjects `46` and `50` with sampler +seed `6767676767` improved over already-usable baselines. The baseline prompts +preserved coworking continuity, hands, and centered mouth contact, but tended +to read as raised-hips or all-fours rather than prone belly-down. The candidate +kept the subject/outfit/office scene and strengthened the wide V-frame and +low-horizontal torso hierarchy. Source `46` still kept some high-hip posture, +so this is a provisional generator improvement, not a proven rule. + +### Blowjob Sitting Upright / Low Mouth Contact + +For upright sitting oral, generic `mouth aligned to the centered shaft` wording +can preserve the seated office layout but leave the face too high, making mouth +contact implied rather than visible. The useful fixed-seed change is to make the +mouth-contact point the central hierarchy while preserving the seated torso. + +Useful wording: + +- `viewer reclines with open thighs forming the lower V-frame` +- `lower abdomen anchoring the near edge` +- `woman sits low between his open thighs` +- `torso upright behind the action` +- `face lowered close to the exact center contact point` +- `vertical shaft rises from the exact lower center` +- `open mouth covers the tip at the centerline` +- `both hands stay low at the base directly below her mouth` + +Fixed-seed finding: CodexMCPTest source subjects `46` and `50` with sampler +seed `7878787878` improved when the candidate replaced high-face alignment with +low-mouth seated hierarchy. Both baselines preserved coworking continuity and +the viewer-thigh frame but kept the woman's face too high and hands beside the +base. A first source-`46` candidate that over-weighted vertical shoulders made +the mouth drift farther away. The accepted wording lowered the face to the +centered tip and made hands-at-base structure readable on both sources. Source +`50` had some outfit looseness/drift, so this is a provisional generator +improvement, not a proven rule. + +### Missionary Open-Leg / Seated-Lounge Drift + +The first open-leg missionary A/B did not justify a generator patch. It showed +that office-lounge support words can preserve coworking continuity but also +push the result toward a seated or angled cushion route. That route can still be +a valid missionary angle; it is just not the flatter elevated-support atlas +subcase. + +Useful partial cues from seed `8989898989`: + +- `viewer between her legs from the lower foreground` +- `hands hold the undersides of her thighs` +- `open thighs form a broad V-frame around the centered contact line` +- `lower abdomen and hands anchor the lower foreground` +- `face and torso visible behind the open thighs` + +Weak points: + +- `low office-lounge sofa surface` and visible sofa backs made both source + subjects read like an angled lounge-seat missionary route +- the candidate improved source `50` thigh framing and hand placement, but did + not materially improve source `47` over its already-strong baseline +- neither candidate fully matched the flatter elevated-support references + +Follow-up prompt-axis probes on the same sampler seed showed a useful but still +unproven direction. Pose-first prompts with `flat floor mat`, `thin floor +cushion`, `wide V-shaped tunnel`, `diagonal thigh rails`, `viewer hands grip +the thighs from below`, and `coworking carpet, floor seams, desk legs, and +chair wheels stay low around the cushion` improved the flat-back and open-thigh +geometry. However, those pose-first probes omitted the original subject/look +block, so they only count as geometry-axis discovery. Subject-look-first +follow-ups preserved the woman look better, especially with the `V-shaped +tunnel` wording, but still stayed in the angled cushion family. Do not treat +that as invalid missionary; treat it as the wrong subcase when the target is the +flat elevated-support atlas view. + +A subject-first follow-up batch on turns `77`-`80` confirmed the same weak +case. Keeping the original woman/look block first preserved identity and face +adherence across all four candidates, but stronger phrases like `camera mounted +just above the male pelvis`, `head, shoulders, spine, hips, and hair all +resting on the same low horizontal plane`, `wide thigh gate`, `overhead +first-person missionary`, and `thin cushion placed directly on a coworking +carpet` still did not beat turn `76`. The best candidate, turn `80`, kept +coworking depth, viewer hands, and the V-frame readable, but still looked +seated/upright on a cushion rather than the flat elevated-support atlas +subcase. + +The successful split came from atlas refs where the viewer is not reclined with +thighs projecting forward. Turns `81`-`84` tested elevated supports and viewer +edge placement. The accepted axis is: + +- `flat elevated support` or `rounded white lounge platform` +- `standing or braced man at the foot edge` +- `feet and vertical shins visible below the support edge` +- `legs drop down along the side edges below the support` +- `woman lies flat across the tabletop/platform` +- `hands holding her calves or outer thighs at the edge` + +Turn `84` was the best subject-first candidate: the woman stayed flat on one +rounded platform plane, office depth remained coherent, viewer hands held the +outer thighs, and the lower edge read as a support edge instead of a reclined +viewer-thigh foreground. Turn `81` was useful secondary evidence with a white +table/support and feet below the edge. Turn `82` is a weak branch: `kneeling +tightly at the foot edge` hallucinated an extra male body behind her, so do not +make kneeling-edge wording the primary cue. Patch only the raised-edge or +edge-supported route with this elevated-support wording; keep generic +missionary available for valid angled missionary views. + +### Missionary Folded / Contact-First Knee Block + +Folded/high-leg missionary can be reached with subject-first wording, but Krea2 +will often let the knees and feet become the whole image. The first fixed-seed +batch on seed `8989898989` showed that `compact knee block`, `vertical thigh +columns`, `ankles and feet close to the camera`, and `hands holding calves` +produce folded geometry, but the visible shaft/contact can disappear. + +The accepted follow-up axis from turn `89` is contact-first: + +- `viewer lower abdomen anchors the bottom edge` +- `large centered shaft rising from the lower center` +- `shaft/contact enters the exposed vulva below the folded knees` +- `both knees folded tightly toward her chest into one compact knee block` +- `viewer hands hold her calves` +- `face, breasts, and torso remain visible behind the raised knees` + +Turn `89` was the strongest combined result: it preserved the same subject/look +and coworking platform, recovered a visible lower-center shaft/contact, and +kept the knees folded high as a compact block. Turn `92` was useful secondary +evidence for pelvis/shaft anchoring, but the fold was looser. Prefer the +turn-`89` hierarchy over abstract `penetration line` wording. Mirror it only +into the folded-missionary route as a provisional patch; keep generic +missionary and elevated-edge missionary separate. + +### Cowgirl Frontal / Wide Thigh Bridge + +Frontal cowgirl is not currently a structural generator failure. On seed +`8989898989`, the generic subject-first baseline already produced a valid +woman-on-top POV: the viewer was underneath, the woman faced him, torso stayed +upright, coworking depth remained coherent, and lower-center contact was +readable. + +The useful atlas refinement is the wide-thigh silhouette. For refs like +`5.cowgirl/100_cowgirl.png` and `5.cowgirl/101_cowgirl.png`, prefer: + +- `viewer reclines underneath her` +- `viewer lower abdomen and pelvis at the bottom edge` +- `wide horizontal thigh bridge from left edge to right edge` +- `knees planted outside the viewer's hips` +- `torso upright above the centered contact point` +- `centered shaft contact below her belly` +- `viewer hands gripping the sides of her thighs` + +Turn `95` was the strongest same-seed candidate because it made the thighs span +the lower frame while preserving contact, subject/look, and coworking context. +Turn `96` was useful secondary evidence for hands-on-hips/contact-centered +wording, but it did not improve the wide straddling silhouette as much. + +Matrix update: sampler seed `2828282828` repeated the wide-thigh bridge +hierarchy across two visible women. Turns `208` and `212` were already valid +generic frontal cowgirl baselines with coherent coworking depth and centered +contact. Turns `209` and `213` improved the atlas silhouette by making the +thighs span left edge to right edge while preserving the viewer-underneath +body cues, upright torso, centered contact, and coworking setting. Turn `209` +was strongest because the viewer hands gripped both outer thighs; turn `213` +repeated the wide bridge with cleaner face/torso readability but weaker hand +visibility. Mirror the wide-thigh bridge hierarchy into the normal cowgirl +generator route provisionally. This is still a candidate route because the +baseline is already valid and another seed/source expansion should repeat the +refinement before promotion. + +Generated-route validation: turn `216` used the patched normal cowgirl route +and preserved the full hierarchy: viewer lower abdomen and pelvis at the bottom +edge, hands gripping the outer thighs, wide thigh bridge across the foreground, +upright torso, centered contact below her belly, and coworking depth. Keep this +route patch provisional because it refines an already-valid baseline rather +than fixing a structural route failure. + +Fresh-seed promotion: sampler seed `9191919191` repeated the patched normal +cowgirl route across turns `242`-`249` and two visible women. Turns `242` and +`246` used the generated-route wording directly and stayed valid: viewer-low +body ownership, lower abdomen/pelvis at the bottom edge, centered contact below +her belly, upright torso, coworking depth, and visible hand/thigh support. +Turns `242`, `243`, `244`, and `248` were the clearest atlas-like repeats for +the wide horizontal thigh sweep. Turn `247` was still valid cowgirl, but one +side did not span edge-to-edge as strongly. Treat the normal frontal cowgirl +wide-thigh bridge route as proven. Keep cowgirl-alt, reverse-cowgirl, and +reverse-cowgirl-alt separate because their atlas orientation is different. + +### Cowgirl Alt / Flat Supine Low Angle + +Cowgirl-alt should not be scored only by squat depth or contact clarity. The +atlas refs repeatedly show the man/viewer flat on his back while the woman is +mounted over him, and the room background proves the camera is low: ceiling, +upper walls, overhead lights, or high window/partition lines sit behind her +upper body. + +Same-seed turns `97`-`100` showed the weak case. Low-squat wording produced +coherent woman-on-top images, and turn `99` improved supported-under-thigh +shape, but the background still read too much like a platform or high camera. +Do not accept that as cowgirl-alt just because contact and bent knees are +readable. + +The accepted turn `104` hierarchy is: + +- `viewer lies flat on his back underneath her` +- `lens sits low at the viewer's abdomen looking upward from his pelvis` +- `viewer abdomen and chest lie flat at the bottom edge` +- `woman mounted over him facing him` +- `knees bent wide and close to the camera` +- `ceiling lights, upper walls, or high glass partition lines behind her` +- `centered contact below her belly` + +Patch only the cowgirl-alt route with this flat-supine/low-angle wording. +Normal frontal cowgirl can remain a higher, cleaner woman-on-top straddle when +the background still reads like a platform or elevated support. + +### Reverse Cowgirl / Close Back-Hip Dominant + +Generic reverse-cowgirl wording is not enough for Krea2. On seed `8989898989`, +turn `105` included `facing away`, viewer-underneath contact, and coworking +depth, but the woman still rendered as frontal cowgirl. The route needs the +back-facing straddle hierarchy before generic contact language. + +For normal reverse cowgirl refs like `cowgirl_reverse/101_cowgirl_reverse.png`, +`cowgirl_reverse/106_cowgirl_reverse.png`, and +`cowgirl_reverse/1_cowgirl_reverse.png`, prefer: + +- `woman sits on the viewer's pelvis facing away` +- `back, hips, and ass are the nearest largest shapes to the camera` +- `viewer thighs frame the lower corners` +- `centered contact sits directly between her thighs below her ass` +- `her thighs spread to either side of the viewer's hips` +- `partial over-shoulder glance` only after the back-facing straddle is locked + +Turn `106` was the strongest same-seed candidate because it turned the frontal +baseline into a close back/hip-dominant straddle while preserving subject/look, +office depth, viewer-underneath legs, and centered contact. Turn `107` was a +valid secondary viewer-leg-frame variant, but a little farther back and more +upright. Turn `108` is useful secondary evidence for refs with an over-shoulder +glance. + +Patch only the normal reverse-cowgirl route with this close back/hip hierarchy. +Keep reverse-cowgirl-alt separate: the alt atlas family is more upright and +seated, with the vertical back/shoulders and viewer hands or thighs forming a +cleaner seated straddle frame. + +### Reverse Cowgirl Alt / Upright Seated Back-Facing + +Reverse-cowgirl-alt is the upright seated branch, not just another close crop +of normal reverse cowgirl. The atlas refs such as +`cowgirl_reversere_alt/100_cowgirl_reversere_alt.png`, +`cowgirl_reversere_alt/101_cowgirl_reversere_alt.png`, and +`cowgirl_reversere_alt/102_cowgirl_reversere_alt.png` keep the woman's back and +shoulders vertical and readable above the hips, with the viewer's thighs and +often hands forming the lower seated straddle frame. + +On seed `8989898989`, turn `109` showed that a subject-first upright baseline +already hits the route. Turn `110` was the strongest atlas-alt wording because +it added viewer hands on both hips while preserving the vertical back, centered +ass-over-pelvis placement, viewer thigh V-frame, centered contact, and coworking +depth. + +Prefer: + +- `POV upright reverse cowgirl back-facing penetration position` +- `woman sits upright facing away in a back-facing straddle` +- `back stays vertical and readable above her hips` +- `viewer hands hold both sides of her hips` +- `viewer thighs frame the lower corners` +- `centered contact remains visible below her ass` +- `office desks, glass partitions, plants, or tall-window light behind her upright back` + +Use over-shoulder glance wording only as a secondary refinement. Turn `112` was +a strong over-shoulder variant, but the core route should first preserve the +upright seated back-facing silhouette. + ### POV Doggy / Rear-Entry For doggy-style POV, visible viewer thighs, lower torso, or pelvis can be diff --git a/docs/krea2-visual-bookmarks/ballsucking-side-camera-turn221-side-low-oral-weakcase.png b/docs/krea2-visual-bookmarks/ballsucking-side-camera-turn221-side-low-oral-weakcase.png new file mode 100755 index 0000000..31f6632 Binary files /dev/null and b/docs/krea2-visual-bookmarks/ballsucking-side-camera-turn221-side-low-oral-weakcase.png differ diff --git a/docs/krea2-visual-bookmarks/blowjob-side-camera-img_5a776aafda8f4a80ab12d995e4ce8020.png b/docs/krea2-visual-bookmarks/blowjob-side-camera-img_5a776aafda8f4a80ab12d995e4ce8020.png new file mode 100755 index 0000000..9ab0893 Binary files /dev/null and b/docs/krea2-visual-bookmarks/blowjob-side-camera-img_5a776aafda8f4a80ab12d995e4ce8020.png differ diff --git a/docs/krea2-visual-bookmarks/blowjob-side-camera-turn202-awesome-side-phone.png b/docs/krea2-visual-bookmarks/blowjob-side-camera-turn202-awesome-side-phone.png new file mode 100755 index 0000000..9ab0893 Binary files /dev/null and b/docs/krea2-visual-bookmarks/blowjob-side-camera-turn202-awesome-side-phone.png differ diff --git a/docs/sxcp-eval-loop.md b/docs/sxcp-eval-loop.md index 8f77128..364155a 100644 --- a/docs/sxcp-eval-loop.md +++ b/docs/sxcp-eval-loop.md @@ -5,6 +5,9 @@ ComfyUI sends a generated prompt, image, and seed to Codex, Codex analyzes the result, then sends back exactly one edited prompt for the next A/B test. Confirmed findings become either generator changes or durable prompt rules in [`krea2-prompt-guide.md`](krea2-prompt-guide.md). +The active A/B testing method is recorded in +[`krea2-ab-methodology.md`](krea2-ab-methodology.md); update that memory when +the method improves. ## Channels @@ -14,6 +17,76 @@ Confirmed findings become either generator changes or durable prompt rules in the MCP signal when supported. Do not put analysis here. - `sxcp_eval_log`: optional analysis/log channel. +## MCP Helper Command + +Use the checked helper for bridge calls instead of ad hoc Python snippets. The +approved command prefix is: + +```bash +/media/p5/miniforge3/bin/python tools/sxcp_mcp_client.py +``` + +Common calls: + +```bash +/media/p5/miniforge3/bin/python tools/sxcp_mcp_client.py list-tools +/media/p5/miniforge3/bin/python tools/sxcp_mcp_client.py call-tool comfy_pull --arguments-json '{"channel":"sxcp_eval_in"}' +/media/p5/miniforge3/bin/python tools/sxcp_mcp_client.py call-tool comfy_push --arguments-json '{"channel":"sxcp_eval_out","seed":5656565656,"text":"PROMPT_ONLY_POSITIVE_CONDITIONING"}' +``` + +## Batch Prompt Helper + +For prompt-axis batches, prepare a local JSON file and use the offline helper to +render the approved MCP push/pull commands and an image-presence checklist: + +```bash +python tools/sxcp_prompt_batch.py validate --batch-json /tmp/sxcp-batch.json +python tools/sxcp_prompt_batch.py print-push-commands --batch-json /tmp/sxcp-batch.json +python tools/sxcp_prompt_batch.py print-result-template --batch-json /tmp/sxcp-batch.json +python tools/sxcp_prompt_batch.py run-batch --batch-json /tmp/sxcp-batch.json --result-json /tmp/sxcp-results.json --previous-turn 80 --run +python tools/sxcp_prompt_batch.py validate-results --batch-json /tmp/sxcp-batch.json --result-json /tmp/sxcp-results.json +python tools/sxcp_prompt_batch.py print-eval-entry-draft --batch-json /tmp/sxcp-batch.json --result-json /tmp/sxcp-results.json --variant-key pov_example_variant --baseline-image /absolute/baseline.png --candidate-id controlled_subject_first +``` + +Batch files use the fixed sampler seed and one positive prompt per probe: + +```json +{ + "seed": 8989898989, + "channel_out": "sxcp_eval_out", + "channel_in": "sxcp_eval_in", + "probes": [ + { + "id": "controlled_subject_first", + "prompt_order": "subject_first", + "text": "SUBJECT_LOOK_FIRST. POSE_HIERARCHY. LOCATION_ANCHORS." + }, + { + "id": "rough_geometry_axis", + "prompt_order": "geometry_only", + "text": "POSE_AXIS_ONLY_FOR_DISCOVERY." + } + ] +} +``` + +`geometry_only` probes are for rough pose-axis discovery and are not durable +subject/look-controlled A/B evidence. The helper rejects +`sxcp_eval_negative_out`; keep batch prompts positive-only. + +Use `run-batch --run` to push one positive prompt, poll `sxcp_eval_in` until a +new turn and absolute PNG image path appear with the fixed sampler seed, write +the filled result JSON, then send the next probe. Omit `--run` for a dry-run +command preview. After a live run, run `validate-results`; it requires the +result probe ids to match the batch order, each turn to advance in batch order, +every image path to be an absolute PNG artifact, and every returned seed to +match the fixed sampler seed. Then use `print-eval-entry-draft` to create a +valid `krea2-eval-log.json` entry draft. Replace the generated summaries and +observation with the real visual comparison before recording it with +`tools/krea2_record_eval.py`. By default the draft command rejects +`geometry_only` candidates; pass `--allow-geometry-only` only when deliberately +recording non-controlled prompt-axis evidence. + ## Manual Loop Start the helper after sending a test prompt: @@ -30,23 +103,30 @@ Every three minutes it prints a structured request asking Codex to: 4. Compare it to the prompt and previous edit. 5. Push one prompt-only edit to `sxcp_eval_out`, preserving the same seed through the MCP signal when available. -6. Classify the finding as prompt-only, prompt-guide rule, or generator fix. -7. Change generator code/data only when the issue is systemic. -8. Record the finding and update the Krea2 prompt guide when a rule is confirmed. +6. Classify the finding as prompt-only, prompt-guide rule, provisional generator + improvement, or proven generator fix. +7. When leaving a category after same-seed progress over baseline, mirror the + best generator-safe wording into the responsible generator path as + `provisional_generator_patch`. +8. Promote a generator change to proven only when the issue is systemic, + repeated, or structurally wrong before rendering. +9. Record the finding and update the Krea2 prompt guide when a rule is confirmed. Runtime logs are written under `.sxcp_eval/` and ignored by git. Durable fixed-seed findings that justify a guide rule, generator patch, or pose variant promotion are recorded in [`krea2-eval-log.json`](krea2-eval-log.json). +Method changes belong in [`krea2-ab-methodology.md`](krea2-ab-methodology.md). Use runtime logs for scratch notes; use the JSON log only for evidence that should remain tied to a catalog variant. Image paths in that log point at external ComfyUI artifacts and may be cleaned; the durable evidence is the fixed -seed, prompt summaries, observation, decision, and commit. +sampler seed, optional generator seed, prompt summaries, observation, decision, +and commit. Record durable findings with the checked helper instead of hand-editing the log: ```bash -python tools/krea2_record_eval.py --print-template --variant-key pov_footjob_frontal_sole_stroke --seed 1234 > /tmp/krea2-entry.json +python tools/krea2_record_eval.py --print-template --variant-key pov_footjob_frontal_sole_stroke --seed 1234 --generator-seed 5678 > /tmp/krea2-entry.json python tools/krea2_record_eval.py --entry-json /tmp/krea2-entry.json --dry-run python tools/krea2_record_eval.py --entry-json /tmp/krea2-entry.json ``` @@ -59,6 +139,7 @@ Entry template: "date": "2026-06-29", "variant_key": "pov_example_variant", "seed": 1234, + "generator_seed": 5678, "source": "sxcp_eval_mcp", "result": "accepted", "decision": "generator_patch", @@ -141,22 +222,45 @@ pelvis can be correct. Do not treat them as automatic failures. ## Seed Contract -The seed is transport metadata, not prompt text. When the graph emits a seed, an -A/B wording test should reuse that exact seed so the image difference mostly -comes from wording, not sampling randomness. If a payload has no seed, mark that +The sampler seed is transport metadata, not prompt text. When the graph emits a +sampler seed, an A/B wording test should reuse that exact seed so the image +difference mostly comes from wording, not sampling randomness. If the SxCP +generator/control seed differs from the sampler seed, record it as +`generator_seed` in the eval entry. If a payload has no sampler seed, mark that cycle as uncontrolled and avoid turning the result into a durable generator rule without another controlled run. +## Positive-Only Conditioning + +`sxcp_eval_out` is positive conditioning only. Never send negative-conditioning +phrases such as `no shaft`, `no hands`, `without clothing`, or `avoid X` inside +the positive prompt; distilled Krea2 can reinforce or hallucinate the unwanted +object from that wording. + +This loop has no active negative-output channel. A same-positive, same-seed +probe on seed `424242` compared empty negative conditioning against strong +negative text targeting visible prompt attributes, and the rendered image stayed +visually unchanged. Do not rely on negative conditioning for Krea2 pose tuning; +keep prompt fixes positive-only. + ## Generator Fix Rule -Only edit the generator when the image shows a repeatable, systemic prompt -failure. Examples: +Use two levels of generator change: + +- `provisional_generator_patch`: apply the best generator-safe wording when + leaving a category after fixed-seed progress over baseline. Keep the catalog + variant as `candidate`. +- `generator_patch`: promote as a proven/default generator rule when the issue + is repeated, systemic, or structurally wrong before rendering. + +Examples of proven generator fixes: - Selfie wording overrides orbit camera. - Clothing continuity loses the selected softcore outfit. - POV wording makes the off-camera participant the visual subject. - Location camera layout inserts foreground anchors in the wrong place. -For one-off model drift, send a cleaner prompt to `sxcp_eval_out` and keep the -generator unchanged. For repeated prompt behavior, update the generator and add -the rule to `docs/krea2-prompt-guide.md`. +For one-off model drift inside an active category, send a cleaner prompt to +`sxcp_eval_out` and keep collecting evidence. When exiting a category, carry +forward same-seed improvements over baseline as provisional generator changes +and add the rule or weak case to `docs/krea2-prompt-guide.md`. diff --git a/hardcore_position_config.py b/hardcore_position_config.py index c0d6656..f6ea518 100644 --- a/hardcore_position_config.py +++ b/hardcore_position_config.py @@ -39,8 +39,11 @@ HARDCORE_POSITION_FOCUS_CHOICES = [ ] HARDCORE_POSITION_KEY_CHOICES = [ "missionary", + "missionary_folded", "cowgirl", + "cowgirl_alt", "reverse_cowgirl", + "reverse_cowgirl_alt", "doggy", "bent_over", "face_down_ass_up", @@ -123,8 +126,11 @@ HARDCORE_POSITION_FAMILY_SUBCATEGORIES = { } HARDCORE_POSITION_KEY_MATCHES = { "missionary": ("missionary", "above her", "under her"), + "missionary_folded": ("folded missionary", "knees-to-chest", "knees to chest", "folded legs", "folded high"), "cowgirl": ("cowgirl", "straddling", "straddles", "on top", "squatting on top"), + "cowgirl_alt": ("cowgirl-alt", "low cowgirl", "seated-squat cowgirl", "low seated squat"), "reverse_cowgirl": ("reverse cowgirl", "facing away"), + "reverse_cowgirl_alt": ("reverse cowgirl alt", "upright reverse cowgirl", "upright back-facing straddle"), "doggy": ("doggy", "all fours", "rear-entry", "from behind"), "bent_over": ("bent-over", "bent over", "hips raised"), "face_down_ass_up": ("face-down", "ass-up"), @@ -168,6 +174,28 @@ HARDCORE_POSITION_KEY_MATCHES = { "open_thighs": ("thighs open", "legs spread", "open thighs", "legs open", "reclining with thighs open"), "front_back": ("front-and-back", "front and back", "one behind and one in front", "between two partners"), } + + +def _text_matches_position_key(text: str, position: str) -> bool: + terms = HARDCORE_POSITION_KEY_MATCHES.get(position, ()) + if not any(term in text for term in terms): + return False + if position == "missionary" and any(term in text for term in HARDCORE_POSITION_KEY_MATCHES["missionary_folded"]): + return False + if position == "cowgirl" and any( + term in text + for term in ( + HARDCORE_POSITION_KEY_MATCHES["cowgirl_alt"] + + HARDCORE_POSITION_KEY_MATCHES["reverse_cowgirl"] + + HARDCORE_POSITION_KEY_MATCHES["reverse_cowgirl_alt"] + ) + ): + return False + if position == "reverse_cowgirl" and any(term in text for term in HARDCORE_POSITION_KEY_MATCHES["reverse_cowgirl_alt"]): + return False + return True + + HARDCORE_POSITION_AXIS_KEYS = { "position", "body_position", @@ -733,7 +761,7 @@ def hardcore_position_entry_matches(entry: Any, config: dict[str, Any]) -> bool: return bool(set(metadata_keys) & set(positions)) text = _entry_text(entry).lower() for position in positions: - if any(term in text for term in HARDCORE_POSITION_KEY_MATCHES.get(position, ())): + if _text_matches_position_key(text, position): return True return False @@ -749,8 +777,8 @@ def hardcore_position_entry_conflicts(entry: Any, config: dict[str, Any]) -> boo text = _entry_text(entry).lower() matched = { position - for position, terms in HARDCORE_POSITION_KEY_MATCHES.items() - if any(term in text for term in terms) + for position in HARDCORE_POSITION_KEY_MATCHES + if _text_matches_position_key(text, position) } return bool(matched) and not bool(matched & selected) @@ -861,8 +889,8 @@ def hardcore_position_keys(*parts: Any, axis_values: dict[str, Any] | None = Non if not text: return [] keys: list[str] = [] - for key, tokens in HARDCORE_POSITION_KEY_MATCHES.items(): - if any(token in text for token in tokens): + for key in HARDCORE_POSITION_KEY_MATCHES: + if _text_matches_position_key(text, key): keys.append(key) return keys diff --git a/hardcore_role_climax.py b/hardcore_role_climax.py index ca71519..ce9372b 100644 --- a/hardcore_role_climax.py +++ b/hardcore_role_climax.py @@ -43,7 +43,10 @@ def build_climax_role_graph( if "lying at the bed edge with thighs open" in context: return f"{woman} lies at the bed edge with thighs open while {man} kneels between her legs and ejaculates semen across her pussy and thighs." if "reclining with thighs open" in context or "lying on the back with legs spread" in context: - return f"{woman} lies on her back with thighs open while {man} kneels between her legs and ejaculates semen across her pussy and thighs." + return ( + f"{woman} lies on her back with thighs open after ejaculation; thick semen and clear fluid cover her exposed pussy " + f"and inner thighs as the exact-center aftermath detail, her body stays still, and her face and torso remain visible behind the open thighs." + ) if "on all fours with hips raised" in context: return f"{woman} is on all fours with hips raised while {man} is positioned behind her and ejaculates semen across her ass, thighs, and lower back." if "face-down ass-up" in context: diff --git a/hardcore_role_interaction.py b/hardcore_role_interaction.py index f696d06..164904c 100644 --- a/hardcore_role_interaction.py +++ b/hardcore_role_interaction.py @@ -58,11 +58,25 @@ def build_manual_role_graph( return f"{primary} reclines with thighs open, one hand between her legs and fingers visibly stimulating her pussy." if "mutual" in text: return f"{primary} and {partner} sit close facing each other, both touching themselves while keeping hands, faces, and bodies visible." + if "toy" in text or "vibrator" in text or "wand" in text: + return ( + f"{primary} reclines in a close first-person toy-contact view with thighs spread wide toward the camera; " + "a single continuous teal wand-style massager is the largest lower-frame object, " + "the rounded bulb head presses flat to her vulva and clit as the central contact point, " + f"and the smooth handle angles in from the bottom right inside {partner}'s visible hand. " + f"{primary}'s open thighs and knees form a V around the foreground wand while her face and torso remain visible behind the leg frame." + ) if "clit" in text or "clitoris" in text: - return f"{primary} reclines with thighs open while {partner}'s hand is between her legs, fingers rubbing her clit as her hips tilt toward the touch." - if "toy" in text or "vibrator" in text: - return f"{primary} reclines with thighs open while {partner} holds a vibrator or toy against her clit, one hand keeping her thigh open." - return f"{primary} reclines with thighs open while {partner}'s hand is between her legs, fingers visibly stimulating her pussy." + return ( + f"{primary} reclines with thighs open while {partner}'s foreground hand is the largest lower-frame object; " + f"her open thighs form a V around the hand, the wrist enters from the bottom center, " + f"and two fingers press at her vulva and clit as the clear contact point while her face and torso remain visible behind the open thighs." + ) + return ( + f"{primary} reclines with thighs open while {partner}'s foreground hand is the largest lower-frame object; " + f"her open thighs form a V around the hand, the wrist enters from the bottom center, " + f"and two fingers at her vulva and clit make the central manual-contact point while her face and torso remain visible behind the open thighs." + ) def build_interaction_role_graph( @@ -79,6 +93,12 @@ def build_interaction_role_graph( return f"{primary} reclines after sex while {partner} kneels close and wipes her skin with a towel, hands and relaxed body contact visible." return f"{primary} and {partner} lie close together after sex, bodies relaxed and hands resting on skin in a post-sex cuddle." if "camera_performance" in slug or any(term in text for term in ("camera", "presenting", "showing", "viewer", "creator-shot")): + if any(term in text for term in ("spread open", "open thighs", "thighs open", "legs spread", "knees held wide")): + return ( + f"{primary} sits back facing the camera with knees raised and held wide; " + f"her thighs form a broad V-frame around her centered exposed vulva, " + f"her hands hold her knees and upper thighs, and her face and torso remain visible behind the open-thigh frame." + ) if third: return f"{primary} faces the camera while {partner} and {third} hold and present her body, hands framing the exposed skin for the viewer." return f"{primary} faces the camera and presents her body while {partner}'s hands hold her hips or thighs open for a clear creator-shot reveal." diff --git a/hardcore_role_oral.py b/hardcore_role_oral.py index b2aaea3..0c4b0bc 100644 --- a/hardcore_role_oral.py +++ b/hardcore_role_oral.py @@ -83,7 +83,14 @@ def build_oral_role_graph( if "side-lying oral" in position_text or ("side-lying oral" in text and not position_text): if woman_gives and not man_gives: if man_is_pov: - return f"The viewer lies on his side with hips angled toward {woman} while {woman} lies beside his thighs and takes the viewer's penis in her mouth." + return ( + f"The adult male viewer reclines in a first-person side-profile body-line view with the adult male viewer's abdomen, " + "navel, pelvis, and near thigh creating a broad horizontal body surface; the adult male viewer's own torso starts at the lower edge " + f"and runs diagonally into the lower-right foreground, with navel, abdomen hair, pelvis, and near thigh marking the camera owner's body; {woman} enters laterally from the left edge beside his hip, " + "cheek and jaw in profile, mouth on the shaft at the male abdomen line, lips touching the shaft at the male abdomen line, " + "mouth-to-shaft contact is the nearest facial detail, hand around the base under her lips, " + "shoulder and torso trailing sideways along the edge." + ) return f"{man} lies on his side with hips angled toward {woman} while {woman} lies beside his thighs and takes his penis in her mouth." if man_is_pov: return f"{woman} lies on her side with her top thigh lifted while the viewer lies beside her hips with his mouth pressed to her pussy." diff --git a/hardcore_role_outercourse.py b/hardcore_role_outercourse.py index fb82709..608b084 100644 --- a/hardcore_role_outercourse.py +++ b/hardcore_role_outercourse.py @@ -42,15 +42,34 @@ def build_outercourse_role_graph( if action_kind == outercourse_policy.OUTERCOURSE_TESTICLE: if man_is_pov: return ( - 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, " - "while his penis points upward in the lower foreground above her forehead." + f"{woman} lies low in a side-pelvis POV beside the POV viewer's open thighs, " + "her face is the closest visible partner part, her cheek against the POV viewer's inner thigh and her head low under his pelvis, " + "with the POV viewer's scrotum at her mouth; scrotum is the mouth surface, testicles resting across her open lips while her tongue cups them from below, " + "scrotal skin is the nearest mouth surface and both testicles rest against her tongue from below, " + "and his abdomen and inner thighs frame the close foreground." ) return ( - 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"{man} reclines with legs apart while {woman} lies low beside his inner thigh, " + f"her face as the closest visible partner part, her cheek against his thigh and her head low under his pelvis, " + f"with {man}'s scrotum at her mouth; scrotum is the mouth surface, scrotal skin is the nearest mouth surface, and testicles resting across her open lips while both testicles rest against her tongue from below." ) if action_kind == outercourse_policy.OUTERCOURSE_PENIS_LICKING: + prone_laying = any( + term in f"{position_text} {text}" + for term in ("reclining", "prone", "belly-down", "belly down", "lying") + ) + if prone_laying: + if man_is_pov: + return ( + f"{woman} lies belly-down between the POV viewer's open thighs while his thighs form a wide V-frame in the foreground, " + "her torso stretched low and horizontal between his knees, her front-facing mouth and tongue aligned to the POV viewer's penis, " + "and her hands wrap the base of the POV viewer's penis." + ) + return ( + f"{woman} lies belly-down between {man}'s open thighs while his thighs form a wide V-frame in the foreground, " + f"her torso stretched low and horizontal between his knees, her front-facing mouth and tongue aligned to {man}'s penis, " + f"and her hands wrap the base of {man}'s penis." + ) if man_is_pov: return ( f"{woman} bends forward between the POV viewer's open thighs with her head low under the POV viewer's penis, " @@ -65,12 +84,15 @@ def build_outercourse_role_graph( if action_kind == outercourse_policy.OUTERCOURSE_FOOTJOB: if man_is_pov: return ( - f"{woman} faces the POV viewer with her hips back, torso visible behind her raised legs, and both knees bent open toward the camera, " - "both soles wrapped around the POV viewer's penis shaft in the lower foreground." + f"{woman} faces the POV viewer with hips back, torso visible behind her raised legs, and both knees bent open toward the camera, " + "while two large overlapping soles dominate the POV viewer's lower center foreground and clamp the POV viewer's upright shaft between them. " + "Her inner arches press inward from both sides, toes curl around both edges, a narrow visible strip of shaft and glans rises between the compressed feet, " + "and her face and torso stay visible behind the large foreground feet." ) return ( f"{man} reclines with hips forward while {woman} faces him with her hips back and both knees bent open, " - f"wrapping both soles around {man}'s penis shaft while the contact stays centered." + f"two large overlapping soles dominating the lower center foreground as her inner arches press inward around {man}'s upright shaft, " + "her toes curl around both edges, and a narrow visible strip of shaft and glans rises between the compressed feet." ) if action_kind == outercourse_policy.OUTERCOURSE_HANDJOB: if man_is_pov: diff --git a/hardcore_role_penetration.py b/hardcore_role_penetration.py index 7e2563a..6a5a9be 100644 --- a/hardcore_role_penetration.py +++ b/hardcore_role_penetration.py @@ -19,15 +19,34 @@ def build_penetration_role_graph( item_axis_values: dict[str, Any] | None = None, ) -> str: text = _context_text(item_text, item_axis_values) + if "folded missionary" in text or "knees-to-chest" in text or "knees to chest" in text: + return ( + f"{woman} lies on her back facing {man} with knees folded high toward her chest while {man} is above her between her thighs; " + f"{man}'s hands hold her calves and {man}'s penis thrusts into her pussy below the raised knees." + ) if "missionary" in text: return ( f"{woman} lies on her back with legs open around {man}'s hips while {man} is above her between her thighs; " f"{man}'s hips press close and {man}'s penis thrusts into her pussy." ) + if "cowgirl-alt" in text or "low cowgirl" in text or "seated-squat cowgirl" in text or "low seated squat" in text: + return ( + f"{woman} faces {man} in a low seated squat over {man}'s pelvis while {man} lies flat on his back under her; " + f"{man} supports the underside of her thighs and {man}'s penis thrusts into her pussy." + ) + if "reverse cowgirl alt" in text or "upright reverse cowgirl" in text or "upright back-facing straddle" in text: + return ( + f"{woman} sits upright facing away in a back-facing straddle over {man}'s pelvis while {man} lies under her; " + f"{man}'s hands hold her hips and {man}'s penis thrusts into her pussy." + ) if "reverse cowgirl" in text: return f"{woman} straddles {man}'s hips facing away while {man} lies under her and {man}'s penis thrusts into her pussy." if "cowgirl" in text or "straddling" in text: - return f"{woman} straddles {man}'s hips facing him while {man} lies under her and {man}'s penis thrusts into her pussy." + return ( + f"{woman} straddles {man}'s hips facing him while {man} lies under her; " + f"{man}'s lower abdomen and pelvis anchor the bottom edge, {woman}'s thighs form a wide horizontal thigh bridge from left edge to right edge, " + f"her knees plant outside {man}'s hips, {man}'s hands grip the sides of her thighs, and centered contact remains below her belly as {man}'s penis thrusts into her pussy." + ) if "doggy" in text or "rear-entry" in text or "bent-over" in text or "bent over" in text: return f"{woman} is on all fours with hips raised while {man} is positioned behind her and {man}'s penis thrusts into her pussy." if "standing" in text: diff --git a/krea2_eval_log.py b/krea2_eval_log.py index 058cc24..1023059 100644 --- a/krea2_eval_log.py +++ b/krea2_eval_log.py @@ -10,7 +10,13 @@ from typing import Any ROOT = Path(__file__).resolve().parent DEFAULT_EVAL_LOG_PATH = ROOT / "docs" / "krea2-eval-log.json" VALID_RESULTS = {"accepted", "rejected", "inconclusive"} -VALID_DECISIONS = {"generator_patch", "prompt_guide_rule", "prompt_only_retry", "needs_more_tests"} +VALID_DECISIONS = { + "generator_patch", + "provisional_generator_patch", + "prompt_guide_rule", + "prompt_only_retry", + "needs_more_tests", +} def _path_key(path: str | Path | None = None) -> str: @@ -55,6 +61,7 @@ def entry_template( variant_key: str, *, seed: int, + generator_seed: int | None = None, source: str = "sxcp_eval_mcp", date: str = "", result: str = "inconclusive", @@ -63,10 +70,12 @@ def entry_template( ) -> dict[str, Any]: if not isinstance(seed, int) or isinstance(seed, bool): raise ValueError("seed must be an integer") + if generator_seed is not None and (not isinstance(generator_seed, int) or isinstance(generator_seed, bool)): + raise ValueError("generator_seed must be an integer") variant = _text(variant_key).strip() if not variant: raise ValueError("variant_key is required") - return { + entry = { "id": f"{_entry_id_slug(variant)}-{seed}-eval", "date": date, "variant_key": variant, @@ -81,6 +90,9 @@ def entry_template( "candidate_image": "", "commit": commit, } + if generator_seed is not None: + entry["generator_seed"] = generator_seed + return entry def validate_entry( @@ -108,6 +120,9 @@ def validate_entry( seed = entry.get("seed") if not isinstance(seed, int) or isinstance(seed, bool): errors.append("seed must be an integer") + generator_seed = entry.get("generator_seed") + if generator_seed is not None and (not isinstance(generator_seed, int) or isinstance(generator_seed, bool)): + errors.append("generator_seed must be an integer") result = entry.get("result") if result not in VALID_RESULTS: diff --git a/krea2_tuning_report.py b/krea2_tuning_report.py index 0374600..412647a 100644 --- a/krea2_tuning_report.py +++ b/krea2_tuning_report.py @@ -2,6 +2,7 @@ from __future__ import annotations from collections import Counter from pathlib import Path +import sys from typing import Any try: @@ -31,11 +32,13 @@ def _latest_evidence(entries: list[dict[str, Any]], *, result: str | None = None return { "id": entry.get("id") or "", "seed": entry.get("seed"), + "generator_seed": entry.get("generator_seed"), "result": entry.get("result") or "", "decision": entry.get("decision") or "", "baseline_prompt_summary": entry.get("baseline_prompt_summary") or "", "candidate_prompt_summary": entry.get("candidate_prompt_summary") or "", "observation": entry.get("observation") or "", + "needs_expansion": bool(entry.get("needs_expansion")), "commit": entry.get("commit") or "", } @@ -228,6 +231,45 @@ def next_test_plans() -> list[dict[str, Any]]: return plans +def guide_expansion_plans() -> list[dict[str, Any]]: + plans: list[dict[str, Any]] = [] + for row in coverage_rows(): + latest_accepted = row.get("latest_accepted_evidence") or {} + decision = str(latest_accepted.get("decision") or "") + if decision not in {"prompt_guide_rule", "needs_more_tests"} and not ( + decision == "provisional_generator_patch" and latest_accepted.get("needs_expansion") + ): + continue + key = str(row.get("key") or "") + variant = krea2_pose_variant_catalog.get_variant(key) + if not variant: + continue + evidence = variant.get("evidence") or {} + plans.append( + { + "key": key, + "family": variant.get("family") or "", + "action_family": variant.get("action_family") or "", + "status": variant.get("status") or "", + "coverage_state": row.get("coverage_state") or "", + "target": "multi_seed_multi_woman_matrix", + "latest_accepted_id": latest_accepted.get("id") or "", + "latest_accepted_seed": latest_accepted.get("seed"), + "latest_accepted_decision": decision, + "accepted_evidence_count": row.get("accepted_evidence_count") or 0, + "total_evidence_count": row.get("total_evidence_count") or 0, + "canonical_geometry": variant.get("canonical_geometry") or "", + "prompt_cues": list(variant.get("prompt_cues") or []), + "avoid_cues": list(variant.get("avoid_cues") or []), + "reference_paths": [str(path) for path in krea2_pose_variant_catalog.reference_paths(key)], + "generator_hook": variant.get("generator_hook") or {}, + "guide_section": evidence.get("guide_section") or "", + "notes": evidence.get("notes") or "", + } + ) + return plans + + def next_eval_template_commands(*, seed_token: str = "") -> list[dict[str, str]]: commands: list[dict[str, str]] = [] for plan in next_test_plans(): @@ -261,14 +303,32 @@ def markdown_report(atlas_root: str | Path | None = None) -> str: evidence = row.get("latest_evidence") or {} seed = evidence.get("seed") seed_text = f"seed {seed}" if isinstance(seed, int) else "seed unknown" + generator_seed = evidence.get("generator_seed") + generator_seed_text = f", generator seed {generator_seed}" if isinstance(generator_seed, int) else "" commit = evidence.get("commit") or "uncommitted" lines.append( - f"- {row['key']}: {evidence.get('id') or 'unnamed'} ({evidence.get('result') or 'unknown'}, {seed_text}, {evidence.get('decision') or 'unknown'}, commit {commit})" + f"- {row['key']}: {evidence.get('id') or 'unnamed'} ({evidence.get('result') or 'unknown'}, {seed_text}{generator_seed_text}, {evidence.get('decision') or 'unknown'}, commit {commit})" ) if evidence.get("candidate_prompt_summary"): lines.append(f" Candidate: {evidence['candidate_prompt_summary']}") if evidence.get("observation"): lines.append(f" Observation: {evidence['observation']}") + accepted = row.get("latest_accepted_evidence") or {} + if accepted and accepted.get("id") != evidence.get("id"): + accepted_seed = accepted.get("seed") + accepted_seed_text = f"seed {accepted_seed}" if isinstance(accepted_seed, int) else "seed unknown" + accepted_generator_seed = accepted.get("generator_seed") + accepted_generator_seed_text = ( + f", generator seed {accepted_generator_seed}" if isinstance(accepted_generator_seed, int) else "" + ) + accepted_commit = accepted.get("commit") or "uncommitted" + lines.append( + f" Latest accepted: {accepted.get('id') or 'unnamed'} ({accepted.get('result') or 'unknown'}, {accepted_seed_text}{accepted_generator_seed_text}, {accepted.get('decision') or 'unknown'}, commit {accepted_commit})" + ) + if accepted.get("candidate_prompt_summary"): + lines.append(f" Accepted candidate: {accepted['candidate_prompt_summary']}") + if accepted.get("observation"): + lines.append(f" Accepted observation: {accepted['observation']}") summary = coverage_summary() if summary["next_test_candidates"]: lines.extend( @@ -294,6 +354,16 @@ def markdown_report(atlas_root: str | Path | None = None) -> str: lines.append( f"- {row['key']}: {difficulty}, {priority} priority, {control_requirement}" ) + expansion_plans = guide_expansion_plans() + if expansion_plans: + lines.extend(["", "## Guide/Fragile Evidence Expansion", ""]) + for plan in expansion_plans: + seed = plan.get("latest_accepted_seed") + seed_text = f"seed {seed}" if isinstance(seed, int) else "seed unknown" + lines.append( + f"- {plan['key']}: {plan['target']} after {plan['latest_accepted_decision']} " + f"({plan['latest_accepted_id']}, {seed_text})" + ) plans = next_test_plans() if plans: lines.extend(["", "## Next Test Plans"]) @@ -344,3 +414,13 @@ def markdown_report(atlas_root: str | Path | None = None) -> str: ] ) return "\n".join(lines) + + +def main(argv: list[str] | None = None) -> int: + _ = argv + print(markdown_report()) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main(sys.argv[1:])) diff --git a/krea_action_climax.py b/krea_action_climax.py index 2a78e08..37f7cae 100644 --- a/krea_action_climax.py +++ b/krea_action_climax.py @@ -86,7 +86,10 @@ def climax_role_graph(role_graph: str, hard_item: str, axis_values: Any = None) if "lying at the bed edge with thighs open" in text: return "the woman lies at the bed edge with thighs open while the man kneels between her legs and ejaculates semen across her pussy and thighs" if "reclining with thighs open" in text or "lying on the back with legs spread" in text: - return "the woman lies on her back with thighs open while the man kneels between her legs and ejaculates semen across her pussy and thighs" + return ( + "the woman lies on her back with thighs open after ejaculation; thick semen and clear fluid cover her exposed pussy " + "and inner thighs as the exact-center aftermath detail, her body stays still, and her face and torso remain visible behind the open thighs" + ) if "on all fours with hips raised" in text: return "the woman is on all fours with hips raised while the man is positioned behind her and ejaculates semen across her ass, thighs, and lower back" if "face-down ass-up" in text or "lies face-down" in text or "face down" in text: @@ -116,7 +119,10 @@ def climax_role_graph(role_graph: str, hard_item: str, axis_values: Any = None) or "arousal dripping from pussy" in text or "open thighs" in text ): - return "the woman lies on her back with thighs open while the man kneels between her legs and ejaculates semen across her pussy and thighs" + return ( + "the woman lies on her back with thighs open after ejaculation; thick semen and clear fluid cover her exposed pussy " + "and inner thighs as the exact-center aftermath detail, her body stays still, and her face and torso remain visible behind the open thighs" + ) if role_graph: return role_graph return "the woman lies on her back with thighs open while the man kneels between her legs and ejaculates semen across her body" diff --git a/krea_action_positions.py b/krea_action_positions.py index 3127b1c..b8c5b33 100644 --- a/krea_action_positions.py +++ b/krea_action_positions.py @@ -179,6 +179,10 @@ def hardcore_pose_anchor(role_graph: str, hard_item: str, composition: str = "", return "rear-entry anal pose" if "edge-supported" in text or "raised edge" in text or "edge-of-bed" in text or "bed-edge" in text: return "edge-supported penetrative sex pose" + if "folded missionary" in text or "knees-to-chest" in text or "knees to chest" in text: + return "folded missionary penetrative sex pose" + if "cowgirl-alt" in text or "low cowgirl" in text or "seated-squat cowgirl" in text or "low seated squat" in text: + return "low cowgirl seated-squat penetrative sex pose" positions = ( "missionary", "reverse cowgirl", @@ -300,6 +304,11 @@ def hardcore_pose_arrangement(anchor: str, role_graph: str, hard_item: str, comp "with the giver kneeling between the receiver's open thighs", ) return "with the giver kneeling at the receiver's hips" + if "reverse cowgirl alt" in text or "upright reverse cowgirl" in text or "upright back-facing straddle" in text: + return cast_phrase( + "with the man lying on his back under the woman while she sits upright straddling his hips facing away", + "with the lower partner lying on their back while the upper partner sits upright straddling them facing away", + ) if "reverse cowgirl" in text: return cast_phrase( "with the man lying on his back under the woman while she straddles his hips facing away", @@ -433,6 +442,8 @@ def action_position_phrase(action: str) -> str: action = _clean(action).lower() if is_close_foreplay_text(action): return "single-frame close-body first-person position" + if "pov upright reverse cowgirl" in action or "pov reverse cowgirl alt" in action: + return "upright reverse-cowgirl first-person position" if "pov reverse cowgirl" in action: return "reverse-cowgirl first-person position" if "pov cowgirl" in action: diff --git a/krea_configured_cast_formatter.py b/krea_configured_cast_formatter.py index adaea68..f2d01ea 100644 --- a/krea_configured_cast_formatter.py +++ b/krea_configured_cast_formatter.py @@ -51,6 +51,31 @@ class KreaConfiguredCastDependencies: paragraph: Callable[[list[str]], str] +def _coworking_action_anchor(action_family: str, scene_text: str, action: str) -> str: + action_lower = action.lower() + if "office chair seat and chair arms" in action_lower: + return "" + scene_lower = scene_text.lower() + if not any(term in scene_lower for term in ("coworking", "office", "desk", "laptop", "glass partition")): + return "" + if action_family == "climax" and "post-ejaculation open-thigh display" in action_lower: + return ( + "office chair seat and chair arms frame the lower foreground around her open thighs, " + "with desk edges, laptop tables, glass partitions, plants, and tall-window depth beside and behind her body" + ) + if "broad v-frame" in action_lower and "open-thigh frame" in action_lower: + return ( + "office chair seat and chair arms frame the lower foreground around her hips and raised knees, " + "with desk edges, laptop tables, glass partitions, plants, and tall-window depth beside and behind her body" + ) + if action_family != "manual": + return "" + return ( + "office chair seat and chair arms frame the lower foreground around her hips, " + "with desk edges, laptop tables, glass partitions, plants, and tall-window depth beside and behind her body" + ) + + def format_configured_cast_result( request: KreaConfiguredCastRequest, deps: KreaConfiguredCastDependencies, @@ -88,13 +113,14 @@ def format_configured_cast_result( item = deps.natural_label_text(item, cast_labels) axis_values = deps.sanitize_hardcore_axis_values(row.get("item_axis_values")) detail_density = deps.normalize_hardcore_detail_density(row.get("hardcore_detail_density")) + action_family = deps.row_action_family(row) action = deps.hardcore_action_sentence( role_graph, item, source_composition, axis_values, detail_density, - deps.row_action_family(row), + action_family, ) action = deps.pov_action_phrase( action, @@ -105,9 +131,15 @@ def format_configured_cast_result( axis_values, detail_density, ) + scene_anchor = _coworking_action_anchor( + action_family, + " ".join(part for part in (request.scene, request.camera_scene, composition, source_composition) if part), + action, + ) output_composition = deps.pov_composition_text(composition, pov_labels) parts = [ action, + scene_anchor, deps.pov_camera_phrase(pov_labels), cast_prose, f"A consensual explicit adult scene with {subject}" if not action else "", diff --git a/krea_pov.py b/krea_pov.py index 1dde1be..d1a284a 100644 --- a/krea_pov.py +++ b/krea_pov.py @@ -28,7 +28,7 @@ def pov_camera_phrase(pov_labels: list[str], softcore: bool = False) -> str: "Camera is the male participant's first-person creator view in one continuous frame, with him implied by perspective or foreground cues" ) return ( - "Camera is the male participant's first-person view in one continuous frame; only his foreground hands or body cues appear" + "Camera is the male participant's first-person view in one continuous frame, with his foreground hands or body cues anchoring the lower frame" ) diff --git a/krea_pov_actions.py b/krea_pov_actions.py index e8e421c..b974b36 100644 --- a/krea_pov_actions.py +++ b/krea_pov_actions.py @@ -89,6 +89,48 @@ def pov_contact_clause( return contact +def _is_open_thigh_aftermath_context(context: str, action_lower: str, position_context: str) -> bool: + combined = f"{context} {action_lower} {position_context}" + has_open_thighs = any( + token in combined + for token in ( + "open thighs", + "thighs open", + "legs open", + "legs spread", + "reclining with thighs open", + ) + ) + has_aftermath = any( + token in combined + for token in ( + "post-ejaculation", + "after ejaculation", + "aftermath", + "semen", + "visible fluid", + "thick fluid", + "clear fluid", + ) + ) + has_rear_entry = any( + token in combined + for token in ( + "rear-entry", + "rear entry", + "doggy", + "on all fours", + "face-down", + "face down", + "bent-over", + "bent over", + "behind her", + "lower back", + ) + ) + return has_open_thighs and has_aftermath and not has_rear_entry + + def pov_clean_detail(detail: Any, context: str, detail_density: str) -> str: detail = _clean(detail).strip(" .;") if not detail: @@ -100,7 +142,7 @@ def pov_clean_detail(detail: Any, context: str, detail_density: str) -> str: detail = re.sub(r"\bhis\b", "the viewer's", detail, flags=re.IGNORECASE) detail = re.sub(r"\bhim\b", "the viewer", detail, flags=re.IGNORECASE) detail = re.sub( - r"^(?:missionary|cowgirl|reverse cowgirl|doggy style|standing sex|spooning sex|edge-supported|edge-of-bed|raised edge|kneeling straddle|lotus sex|bent-over|face-down ass-up|side-lying|kneeling rear-entry)\s+(?:position|pose)\s+(?:featuring|with|while|,)?\s*", + r"^(?:folded missionary|missionary|low cowgirl seated-squat|low cowgirl|cowgirl-alt|cowgirl|reverse cowgirl|doggy style|standing sex|spooning sex|edge-supported|edge-of-bed|raised edge|kneeling straddle|lotus sex|bent-over|face-down ass-up|side-lying|kneeling rear-entry)\s+(?:position|pose)\s+(?:featuring|with|while|,)?\s*", "", detail, flags=re.IGNORECASE, @@ -251,6 +293,8 @@ def pov_hardcore_pose_sentence( "anal", "cowgirl", "missionary", + "knees-to-chest", + "knees to chest", "doggy", "rear-entry", "spooning", @@ -262,6 +306,19 @@ def pov_hardcore_pose_sentence( "climax", ) has_penetrative_context = any(token in context or token in action_lower for token in penetrative_tokens) + toy_contact_context = f"{context} {action_lower}" + if ( + any(token in toy_contact_context for token in ("wand-style", "wand toy", "wand-toy", "vibrator", "massager")) + and any(token in toy_contact_context for token in ("clit", "vulva", "toy-contact", "toy contact")) + and not has_penetrative_context + ): + return outercourse_sentence( + "Close first-person POV wand-toy contact: the woman reclines with thighs spread wide toward the camera; " + "a single continuous teal wand-style massager is the largest lower-frame object, " + "the rounded bulb head presses flat to her vulva and clit as the central contact point, " + "and the smooth handle angles in from the bottom right inside the viewer's visible hand; " + "her open thighs and knees form a V around the foreground wand while her face and torso remain visible behind the leg frame" + ) if ( "face-sitting" in context @@ -286,10 +343,22 @@ def pov_hardcore_pose_sentence( ) if action_kind == outercourse_policy.OUTERCOURSE_TESTICLE: return outercourse_sentence( - "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" + "Low side-pelvis POV: the woman lies low beside the viewer's open thighs with her cheek against the viewer's inner thigh; " + "her face is the closest visible partner part and her head stays low under the viewer's pelvis, with the viewer's scrotum at her mouth; scrotum is the mouth surface, " + "testicles resting across her open lips while her tongue cups them from below, scrotal skin is the nearest mouth surface and both testicles rest against her tongue from below, " + "and the viewer's abdomen and inner thighs framing the close foreground" ) if action_kind == outercourse_policy.OUTERCOURSE_PENIS_LICKING: + prone_laying = any( + term in position_context + for term in ("reclining", "prone", "belly-down", "belly down", "lying") + ) + if prone_laying: + return outercourse_sentence( + "POV prone frontal oral position: the viewer reclines with open thighs forming a wide symmetrical V-frame from the lower corners toward the center; " + "the woman lies belly-down between his thighs with her torso stretched low and horizontal, hips and legs trailing away behind her along the center line; " + "her front-facing mouth and tongue align to the shaft rising from the exact lower center, hands wrap the base, and the centered mouth-to-shaft contact stays framed by his thighs" + ) return outercourse_sentence( "The woman bends forward between the viewer's open thighs with her head low under the viewer's penis; " "her face is just under the penis while her tongue touches the underside from the base toward the glans at the tip, " @@ -305,8 +374,9 @@ def pov_hardcore_pose_sentence( ) if action_kind == outercourse_policy.OUTERCOURSE_FOOTJOB: return outercourse_sentence( - "The woman faces the viewer with her hips back, torso visible behind her raised legs, and both knees bent open toward the camera; " - "her soles wrap around the penis shaft in the lower foreground, toes curled around the penis shaft with her face visible beyond her feet" + "Frontal POV footjob close-up: the woman faces the viewer with hips back, torso behind raised legs, and knees bent open toward the camera; " + "two large overlapping soles dominate the lower center foreground and clamp the upright shaft between them, inner arches press inward from both sides, " + "toes curl around both edges, a narrow visible strip of shaft and glans rises between the compressed feet, and her face and torso stay visible behind the large foreground feet" ) return outercourse_sentence( "The woman stays close to the viewer's pelvis, keeping the non-penetrative contact centered in the lower foreground with her face visible behind the contact" @@ -319,11 +389,29 @@ def pov_hardcore_pose_sentence( "POV sixty-nine oral position: the woman lies head-to-hips over the viewer, her pelvis close to his face and her head lowered toward his hips; " "her mouth on the viewer's penis and the viewer's mouth on the woman's pussy, with her torso, hips, mouth, and the viewer's lower-foreground body cues aligned in one first-person frame" ) + if woman_gives and not man_gives and any( + term in position_context + for term in ( + "upright sitting oral", + "sitting oral", + "seated oral", + "blowjob_sitting", + ) + ): + return oral_sentence( + "POV upright sitting oral position: the viewer reclines with open thighs forming the lower V-frame and his lower abdomen anchoring the near edge; " + "the woman sits low between his open thighs with hips between his knees, torso upright behind the action, shoulders square to the camera, and face lowered close to the exact center contact point; " + "the vertical shaft rises from the exact lower center between the viewer thighs, her open mouth covers the tip at the centerline, lips wrapped around the glans, and mouth-to-shaft contact is the nearest facial detail; " + "both hands stay low at the base directly below her mouth, fingers wrapped around the shaft, while her eyes, face, shoulders, torso, hands, shaft, and the viewer thigh frame remain readable in one first-person seated frame" + ) if "side-lying oral" in position_context or "side lying oral" in position_context: if woman_gives and not man_gives: return oral_sentence( - "POV side-lying oral position: the viewer lies on his side with hips angled toward the woman while she lies beside his thighs; " - "her head stays at penis height with her mouth on the viewer's penis, shoulders and hands close to his pelvis in the lower foreground" + "POV side-profile oral body-line position: the male viewer's abdomen, navel, pelvis, and near thigh create a broad horizontal body surface across the lower frame; " + "the adult male viewer's own torso starts at the lower edge and runs diagonally into the lower-right foreground, with navel, abdomen hair, pelvis, and near thigh marking the camera owner's body; " + "the woman enters laterally from the left edge beside his hip, cheek and jaw in profile, mouth on the shaft at the male abdomen line, " + "lips touching the shaft at the male abdomen line, mouth-to-shaft contact is the nearest facial detail, " + "hand around the base under her lips, shoulder and torso trailing sideways along the edge" ) return oral_sentence( "POV side-lying cunnilingus position: the woman lies on her side with her top thigh lifted while the viewer lies beside her hips; " @@ -396,8 +484,10 @@ def pov_hardcore_pose_sentence( "his face is at pussy height, with her knees, hips, and torso readable from the first-person angle" ) return oral_sentence( - "POV kneeling oral position: the viewer stands over her with hips forward while the woman kneels directly in front of him; " - "her head is at penis height, mouth on the viewer's penis, shoulders below his hips and his thighs framing the lower foreground" + "Nadir-angle standing male POV top-view oral position: the viewer looks almost straight down from his torso toward the floor, with nearby carpet/floor plane dominating the image; " + "the viewer's abdomen, shorts, thighs, and feet frame the lower foreground, and the viewer's penis shaft appears as a short centered vertical column from the foreground; " + "one kneeling woman is directly below the viewer between his feet, her face tilts upward beneath the shaft, her mouth seals around it, and one hand wraps the base; " + "her hair crown, forehead, shoulders, hands, knees, and compact foreshortened torso are visible from above, with desk legs, chair wheels, carpet texture, and floor seams as top-down office anchors around her" ) if man_gives and not woman_gives: return oral_sentence( @@ -416,16 +506,46 @@ def pov_hardcore_pose_sentence( return "" contact = pov_contact_clause(action, role_graph, hard_item, axis_values, context) + if is_climax_text(action, role_graph, hard_item, axis_values_text(axis_values)) and _is_open_thigh_aftermath_context( + context, + action_lower, + position_context, + ): + return sentence( + "POV post-ejaculation open-thigh display: the woman reclines or sits back facing the viewer with thighs spread open; " + "the wet aftermath detail is the exact center, thick semen and clear fluid cover the exposed pussy and inner thighs, " + "her body stays still after ejaculation, and her face and torso remain visible behind the open-thigh frame" + ) + if "reverse cowgirl alt" in position_context or "upright reverse cowgirl" in position_context or "upright back-facing straddle" in position_context: + return sentence( + "POV upright reverse cowgirl back-facing penetration position: the viewer lies on his back while the woman sits upright on his pelvis facing away; " + "her back stays vertical and readable above her hips, her ass is centered over the viewer's pelvis, " + f"viewer hands hold her hips, viewer thighs frame the lower corners, and centered contact remains visible below her ass {contact}" + ) if "reverse cowgirl" in position_context: return sentence( "POV reverse cowgirl position: the viewer lies on his back while the woman straddles his hips facing away; " - f"her back, ass, thighs, and the viewer's foreground legs are visible {contact}" + "her back, hips, and ass are the nearest largest shapes to the camera; " + f"the viewer thighs frame the lower corners, and the centered contact sits directly between her thighs below her ass {contact}" + ) + if "folded missionary" in position_context or "knees-to-chest" in position_context or "knees to chest" in position_context: + return sentence( + "POV folded missionary high-leg penetration position: the viewer's lower abdomen anchors the bottom edge with a large centered shaft rising from the lower center; " + "the woman lies on her back facing the viewer with both knees folded tightly toward her chest into a compact knee block above the contact; " + f"viewer hands hold her calves, her feet and ankles sit close to the camera, and her face and torso remain visible behind the raised knees {contact}" + ) + if "cowgirl-alt" in position_context or "low cowgirl" in position_context or "seated-squat cowgirl" in position_context or "low seated squat" in position_context: + return sentence( + "POV low cowgirl seated-squat penetration position: the viewer lies flat on his back underneath her, and the lens sits low at the viewer's abdomen looking upward from his pelvis; " + "the woman faces the viewer in a low squat mounted over his hips with knees bent wide and close to the camera; " + f"the viewer supports the underside of her thighs, her torso stays close above the centered contact, and the high room background behind her upper body reinforces the low supine viewpoint {contact}" ) if "cowgirl" in position_context or "straddling a partner" in position_context or "squatting on top" in position_context: return sentence( - "POV cowgirl position: the viewer lies on his back while the woman straddles his hips facing him; " - f"her torso, hips, and open thighs fill the frame from below {contact}" + "POV frontal cowgirl wide-thigh bridge position: the viewer reclines underneath her with lower abdomen and pelvis anchoring the bottom edge; " + "the woman straddles his hips facing him, her thighs form a wide horizontal thigh bridge from left edge to right edge, " + f"knees planted outside the viewer's hips, torso upright above the centered contact point, viewer hands grip the sides of her thighs, and centered contact remains below her belly {contact}" ) if "lotus" in position_context or "seated in a partner's lap" in position_context: return sentence( @@ -449,9 +569,15 @@ def pov_hardcore_pose_sentence( or "bed edge" in position_context or (not position_text and "kneels between her legs" in context) ): + if "penetrates her ass" in contact: + return sentence( + "POV raised-edge penetration position: the woman reclines at the raised edge with thighs open toward the viewer; " + f"the viewer kneels between her legs with his hands near her hips {contact}" + ) return sentence( - "POV raised-edge penetration position: the woman reclines at the raised edge with thighs open toward the viewer; " - f"the viewer kneels between her legs with his hands near her hips {contact}" + "POV elevated-edge missionary position: the woman lies flat on her back across a flat elevated support with hair, shoulders, spine, and hips aligned on one horizontal surface; " + "her legs open toward the viewer at the foot edge, thighs forming a broad U-frame around the centered contact line; " + f"the viewer stands, kneels, or braces at the foot edge with hands holding her calves or outer thighs and feet, shins, or side-dropping legs placed below the support edge {contact}" ) if "standing" in position_context: return sentence( diff --git a/node_hardcore_position.py b/node_hardcore_position.py index a2ef6bf..cc63bf5 100644 --- a/node_hardcore_position.py +++ b/node_hardcore_position.py @@ -8,9 +8,12 @@ try: from .hardcore_position_config import ( build_hardcore_action_filter_json, build_hardcore_position_pool_json, + empty_hardcore_position_config, hardcore_position_family_choices, hardcore_position_focus_choices, hardcore_position_key_choices, + hardcore_position_summary, + parse_hardcore_position_config, ) except ImportError: # Allows local smoke tests from the repository root. import krea2_eval_log @@ -18,9 +21,12 @@ except ImportError: # Allows local smoke tests from the repository root. from hardcore_position_config import ( build_hardcore_action_filter_json, build_hardcore_position_pool_json, + empty_hardcore_position_config, hardcore_position_family_choices, hardcore_position_focus_choices, hardcore_position_key_choices, + hardcore_position_summary, + parse_hardcore_position_config, ) @@ -34,6 +40,19 @@ def _choice_input_key(prefix, choice): return f"{prefix}_{key}" +def _variant_input_key(variant_key): + return _choice_input_key("include", str(variant_key or "").removeprefix("pov_")) + + +def _unique_extend(values): + selected = [] + for value in values: + text = str(value or "").strip() + if text and text not in selected: + selected.append(text) + return selected + + def _variant_family(value): family = str(value or "any") if family == "penetration": @@ -46,6 +65,90 @@ def _variant_positions(variant): return [str(key) for key in variant.get("position_keys", []) if str(key) in valid] +def _variants_for_action_family(action_family): + return krea2_pose_variant_catalog.variants(action_family=action_family) + + +def _selected_variant_rows(action_family, kwargs): + return [ + variant + for variant in _variants_for_action_family(action_family) + if bool(kwargs.get(_variant_input_key(variant.get("key")), False)) + ] + + +def _join_variant_cues(variants, cue_key): + cues = [] + for variant in variants: + cues.extend(str(cue) for cue in variant.get(cue_key, []) if str(cue).strip()) + return "; ".join(_unique_extend(cues)) + + +def _selected_variant_positions(variants): + positions = [] + for variant in variants: + positions.extend(_variant_positions(variant)) + return _unique_extend(positions) + + +def _selected_variant_keys(variants): + return [str(variant.get("key")) for variant in variants if variant.get("key")] + + +def _merged_family_for_variant_filter(incoming_config, combine_mode, family): + family = _variant_family(family) + if combine_mode != "add": + return family + incoming = parse_hardcore_position_config(incoming_config) + incoming_family = _variant_family(incoming.get("family")) + incoming_positions = incoming.get("positions") or [] + if not incoming.get("enabled") or (not incoming_positions and incoming_family == "any"): + return family + if incoming_family == family: + return family + return "any" + + +def _empty_or_incoming_config(incoming_config, combine_mode): + if combine_mode == "add" and incoming_config: + config = parse_hardcore_position_config(incoming_config) + else: + config = empty_hardcore_position_config() + config["summary"] = hardcore_position_summary(config) + return json.dumps(config, ensure_ascii=True, sort_keys=True) + + +def _merge_variant_metadata(config_json, variants): + config = json.loads(config_json) + selected_keys = _selected_variant_keys(variants) + existing_keys = config.get("krea2_variant_keys") or [] + if not isinstance(existing_keys, list): + existing_keys = [existing_keys] + variant_keys = _unique_extend([*existing_keys, *selected_keys]) + config["krea2_variant_keys"] = variant_keys + + selected_statuses = {str(variant.get("key")): str(variant.get("status") or "") for variant in variants if variant.get("key")} + existing_statuses = config.get("krea2_variant_statuses") if isinstance(config.get("krea2_variant_statuses"), dict) else {} + config["krea2_variant_statuses"] = {**existing_statuses, **selected_statuses} + + prompt_cues = _unique_extend( + [*(config.get("krea2_prompt_cues") or []), *(_join_variant_cues(variants, "prompt_cues").split("; ") if variants else [])] + ) + avoid_cues = _unique_extend( + [*(config.get("krea2_avoid_cues") or []), *(_join_variant_cues(variants, "avoid_cues").split("; ") if variants else [])] + ) + if prompt_cues: + config["krea2_prompt_cues"] = prompt_cues + if avoid_cues: + config["krea2_avoid_cues"] = avoid_cues + + base_summary = str(config.get("summary") or hardcore_position_summary(config)) + if variant_keys and "variants=" not in base_summary: + base_summary = f"{base_summary}; variants={','.join(variant_keys)}" + config["summary"] = base_summary + return json.dumps(config, ensure_ascii=True, sort_keys=True) + + class SxCPHardcorePositionPool: @classmethod def INPUT_TYPES(cls): @@ -142,6 +245,104 @@ class SxCPKrea2PoseVariant: ) +class _SxCPKrea2POVVariantFilter: + ACTION_FAMILY = "" + POSITION_FAMILY = "any" + + @classmethod + def INPUT_TYPES(cls): + required = { + "combine_mode": (["replace", "add"], {"default": "replace"}), + } + for variant in _variants_for_action_family(cls.ACTION_FAMILY): + required[_variant_input_key(variant.get("key"))] = ("BOOLEAN", {"default": False}) + return { + "required": required, + "optional": { + "hardcore_position_config": (SXCP_HARDCORE_POSITION_CONFIG,), + }, + } + + RETURN_TYPES = (SXCP_HARDCORE_POSITION_CONFIG, "STRING", "STRING", "STRING", "STRING", "STRING") + RETURN_NAMES = ( + "hardcore_position_config", + "selected_variant_keys", + "selected_positions", + "prompt_cues", + "summary", + "variants_json", + ) + FUNCTION = "build" + CATEGORY = "prompt_builder" + + def build(self, combine_mode="replace", hardcore_position_config="", **kwargs): + variants = _selected_variant_rows(self.ACTION_FAMILY, kwargs) + if not variants: + config = _empty_or_incoming_config(hardcore_position_config or "", combine_mode) + return config, "", "", "", json.loads(config).get("summary", ""), "[]" + + positions = _selected_variant_positions(variants) + family = _merged_family_for_variant_filter( + hardcore_position_config or "", + combine_mode, + self.POSITION_FAMILY or self.ACTION_FAMILY, + ) + config = build_hardcore_position_pool_json( + hardcore_position_config=hardcore_position_config or "", + combine_mode=combine_mode, + family=family, + selected_positions=positions, + ) + config = _merge_variant_metadata(config, variants) + parsed = json.loads(config) + selected_keys = parsed.get("krea2_variant_keys") or [] + selected_positions = parsed.get("positions") or [] + prompt_cues = _join_variant_cues(variants, "prompt_cues") + return ( + config, + ",".join(str(key) for key in selected_keys), + ",".join(str(position) for position in selected_positions), + prompt_cues, + str(parsed.get("summary") or ""), + json.dumps(variants, ensure_ascii=True, sort_keys=True), + ) + + +class SxCPKrea2POVPenetrationFilter(_SxCPKrea2POVVariantFilter): + ACTION_FAMILY = "penetration" + POSITION_FAMILY = "penetration" + + +class SxCPKrea2POVOralFilter(_SxCPKrea2POVVariantFilter): + ACTION_FAMILY = "oral" + POSITION_FAMILY = "oral" + + +class SxCPKrea2POVOutercourseFilter(_SxCPKrea2POVVariantFilter): + ACTION_FAMILY = "outercourse" + POSITION_FAMILY = "outercourse" + + +class SxCPKrea2POVManualFilter(_SxCPKrea2POVVariantFilter): + ACTION_FAMILY = "manual" + POSITION_FAMILY = "manual" + + +class SxCPKrea2POVToyFilter(_SxCPKrea2POVVariantFilter): + ACTION_FAMILY = "toy" + POSITION_FAMILY = "any" + + +class SxCPKrea2POVClimaxFilter(_SxCPKrea2POVVariantFilter): + ACTION_FAMILY = "climax" + POSITION_FAMILY = "climax" + + +class SxCPKrea2POVInteractionFilter(_SxCPKrea2POVVariantFilter): + ACTION_FAMILY = "interaction" + POSITION_FAMILY = "interaction" + + class SxCPKrea2VariantEvidence: @classmethod def INPUT_TYPES(cls): @@ -258,6 +459,13 @@ NODE_CLASS_MAPPINGS = { "SxCPHardcorePositionPool": SxCPHardcorePositionPool, "SxCPHardcoreActionFilter": SxCPHardcoreActionFilter, "SxCPKrea2PoseVariant": SxCPKrea2PoseVariant, + "SxCPKrea2POVPenetrationFilter": SxCPKrea2POVPenetrationFilter, + "SxCPKrea2POVOralFilter": SxCPKrea2POVOralFilter, + "SxCPKrea2POVOutercourseFilter": SxCPKrea2POVOutercourseFilter, + "SxCPKrea2POVManualFilter": SxCPKrea2POVManualFilter, + "SxCPKrea2POVToyFilter": SxCPKrea2POVToyFilter, + "SxCPKrea2POVClimaxFilter": SxCPKrea2POVClimaxFilter, + "SxCPKrea2POVInteractionFilter": SxCPKrea2POVInteractionFilter, "SxCPKrea2VariantEvidence": SxCPKrea2VariantEvidence, } @@ -265,5 +473,12 @@ NODE_DISPLAY_NAME_MAPPINGS = { "SxCPHardcorePositionPool": "SxCP Hardcore Position Pool", "SxCPHardcoreActionFilter": "SxCP Hardcore Action Filter", "SxCPKrea2PoseVariant": "SxCP Krea2 Pose Variant", + "SxCPKrea2POVPenetrationFilter": "SxCP Krea2 POV Penetration Filter", + "SxCPKrea2POVOralFilter": "SxCP Krea2 POV Oral Filter", + "SxCPKrea2POVOutercourseFilter": "SxCP Krea2 POV Outercourse Filter", + "SxCPKrea2POVManualFilter": "SxCP Krea2 POV Manual Filter", + "SxCPKrea2POVToyFilter": "SxCP Krea2 POV Toy Filter", + "SxCPKrea2POVClimaxFilter": "SxCP Krea2 POV Climax Filter", + "SxCPKrea2POVInteractionFilter": "SxCP Krea2 POV Interaction Filter", "SxCPKrea2VariantEvidence": "SxCP Krea2 Variant Evidence", } diff --git a/pair_clothing.py b/pair_clothing.py index b81f43b..cbe348d 100644 --- a/pair_clothing.py +++ b/pair_clothing.py @@ -444,15 +444,15 @@ def _pov_clothing_sentence(clothing: str, needs_lower_access: bool) -> str: lower = clothing.lower() if lower.startswith(("fully nude", "nude")): if needs_lower_access: - return "POV foreground body cue: the viewer's bare hips, thighs, hands, and penis are visible only as first-person body cues" - return "POV foreground body cue: the viewer's bare hands, forearms, or torso edge are visible only as first-person body cues" + return "POV foreground body cue: the viewer's bare hips, thighs, hands, and penis appear as first-person body cues" + return "POV foreground body cue: the viewer's bare hands, forearms, or torso edge appear as first-person body cues" clothing = re.sub(r"^(?:wears|wearing|keeps|has|with)\s+", "", clothing, flags=re.IGNORECASE).strip() if needs_lower_access: return ( - f"POV foreground clothing cue: {clothing}, visible only as the viewer's hands, hips, thighs, or lowered waistband" + f"POV foreground clothing cue: {clothing}, appearing as the viewer's hands, hips, thighs, or lowered waistband" ) return ( - f"POV foreground clothing cue: {clothing}, visible only as the viewer's hands, forearms, sleeves, or torso edge" + f"POV foreground clothing cue: {clothing}, appearing as the viewer's hands, forearms, sleeves, or torso edge" ) diff --git a/scene_camera_adapters.py b/scene_camera_adapters.py index 5c754c6..ae70fcf 100644 --- a/scene_camera_adapters.py +++ b/scene_camera_adapters.py @@ -923,12 +923,12 @@ def scene_direction_detail( if "left side" in direction: return f"{subject} stays readable in the first-person action while {midground} run along the viewer's left-side background toward {background}, with {detail_label} kept at the frame edges" if "back-right" in direction or "back-left" in direction: - return f"{subject} stays close in one continuous first-person action frame; {midground} lead diagonally toward {background} at the edges, not in the lower foreground" + return f"{subject} stays close in one continuous first-person action frame; {midground} lead diagonally toward {background} along the side/background edges" if direction == "back view": - return f"{subject} and the action stay primary while {midground} and {background} remain beyond the body, not between viewer and action; only POV body cues sit low in frame" + return f"{subject} and the action stay primary while {midground} and {background} remain beyond the body, with POV body cues sitting low in frame" if "front-right" in direction or "front-left" in direction: return f"{subject} fills the first-person action frame while {midground} recede diagonally behind {pronoun} toward {background}" - return f"{subject} faces the viewer in first-person view; {midground} and {background} stay behind {pronoun}, not between viewer and body" + return f"{subject} faces the viewer in first-person view; {midground} and {background} stay behind {pronoun} at background depth" if "right side" in direction or "left side" in direction: return f"{subject} {is_verb} held in side profile along the {foreground}; {midground} run laterally behind {pronoun}, with {background} still readable" if "back-right" in direction or "back-left" in direction: @@ -1045,7 +1045,7 @@ def scene_camera_directive( subject, _pronoun = scene_subject_terms(subject_kind, pov_labels) return ( f"{profile['layout_label']} from POV{geometry_clause}: keep {subject} and the action primary; " - f"{profile['place']} context stays beside or behind the bodies, not in the lower foreground; " + f"{profile['place']} context stays beside or behind the bodies and along the side/background edges; " f"POV body or hand cues stay in the lower foreground." ) return ( diff --git a/tools/krea2_record_eval.py b/tools/krea2_record_eval.py index 2b7df96..6fce83d 100644 --- a/tools/krea2_record_eval.py +++ b/tools/krea2_record_eval.py @@ -30,6 +30,7 @@ def main() -> int: parser.add_argument("--print-template", action="store_true", help="Print a valid eval entry template instead of recording.") parser.add_argument("--variant-key", help="Catalog variant key for --print-template.") parser.add_argument("--seed", type=int, help="Fixed seed for --print-template.") + parser.add_argument("--generator-seed", type=int, help="Optional SxCP generator/control seed for --print-template.") parser.add_argument("--source", default="sxcp_eval_mcp", help="Source label for --print-template.") parser.add_argument("--date", default=date.today().isoformat(), help="Date for --print-template.") parser.add_argument("--log-path", default=str(krea2_eval_log.DEFAULT_EVAL_LOG_PATH), help="Eval log path to update.") @@ -43,6 +44,7 @@ def main() -> int: entry = krea2_eval_log.entry_template( args.variant_key, seed=args.seed, + generator_seed=args.generator_seed, source=args.source, date=args.date, ) diff --git a/tools/prompt_smoke.py b/tools/prompt_smoke.py index 45fb1ce..4be872f 100644 --- a/tools/prompt_smoke.py +++ b/tools/prompt_smoke.py @@ -50,6 +50,8 @@ import hardcore_action_metadata # noqa: E402 import hardcore_position_config # noqa: E402 import __init__ as sxcp_nodes # noqa: E402 import generation_profile_config # noqa: E402 +import hardcore_role_interaction # noqa: E402 +import hardcore_role_oral # noqa: E402 import hardcore_role_outercourse # noqa: E402 import index_switch_policy # noqa: E402 import item_axis_policy # noqa: E402 @@ -1752,8 +1754,12 @@ def smoke_outercourse_action_policy() -> None: ["Man A"], ) lower_role = role_graph.lower() - _expect("face below the pov viewer's penis at testicle height" in lower_role, "POV testicle role graph lost low-head geometry") - _expect("penis points upward" in lower_role, "POV testicle role graph lost foreground penis geometry") + _expect("side-pelvis pov" in lower_role, "POV testicle role graph lost side-low geometry") + _expect("cheek against the pov viewer's inner thigh" in lower_role, "POV testicle role graph lost cheek/thigh geometry") + _expect("scrotum is the mouth surface" in lower_role, "POV testicle role graph lost scrotum-mouth surface target") + _expect("scrotal skin is the nearest mouth surface" in lower_role, "POV testicle role graph lost scrotal-skin nearest-surface target") + _expect("testicles resting across her open lips" in lower_role, "POV testicle role graph lost open-lips testicle target") + _expect("both testicles rest against her tongue from below" in lower_role, "POV testicle role graph lost tongue-from-below target") deduped = krea_action_details.dedupe_outercourse_detail( "testicle sucking with lips around the balls, balls and mouth contact visible, wet lips and tongue contact", role_graph, @@ -6705,7 +6711,11 @@ def smoke_pov_camera_scene() -> None: _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") + _expect( + "context stays beside or behind the bodies and along the side/background edges" in scene_directive, + "POV camera scene should place location anchors with positive wording", + ) + _expect("not in the lower foreground" not in scene_directive, "POV camera scene should avoid negative foreground wording") _expect( "near desk edge" not in scene_directive and "laptop corner" not in scene_directive and "chair back" not in scene_directive, "POV camera scene should not reuse coworking foreground anchors as viewer-side objects", @@ -6780,6 +6790,10 @@ def smoke_krea2_pose_variant_catalog_policy() -> None: "pov_doggy_top_down_rear_entry", "pov_boobjob_upright_cleavage", "pov_handjob_upright_centered", + "pov_footjob_frontal_sole_stroke", + "pov_wand_foreground_tool_contact", + "pov_blowjob_side_profile_oral", + "pov_cowgirl_frontal_straddle_penetration", ], f"Krea2 pose-variant proven keys changed unexpectedly: {proven}", ) @@ -6847,15 +6861,40 @@ def smoke_krea2_pose_variant_catalog_policy() -> None: _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", + any("scrotum is the mouth surface" in str(cue) for cue in ballsucking.get("prompt_cues", [])), + "Ballsucking variant lost open-lips scrotum-surface cue", + ) + _expect( + any("scrotal skin is the nearest mouth surface" in str(cue) for cue in ballsucking.get("prompt_cues", [])), + "Ballsucking variant lost scrotal-skin nearest-surface cue", + ) + ballsucking_evidence = ballsucking.get("evidence") or {} + _expect( + ballsucking_evidence.get("fixed_seed_tests") == [ + "238365845574312", + "1212121212", + "5757575757", + "6262626262", + "9797979797", + "9898989898", + "5959595959", + "6060606060", + "6161616161", + "7171717171", + "7272727272", + ], + "Ballsucking variant lost fresh-seed target-object evidence", ) 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") == "proven", "Footjob variant should be proven after fresh-seed generated-route repeat evidence") _expect( - any("both soles press" in str(cue) for cue in footjob.get("prompt_cues", [])), + any("two large overlapping soles dominate the lower center foreground" in str(cue) for cue in footjob.get("prompt_cues", [])), "Footjob variant lost sole-contact cue", ) + _expect( + any("narrow visible strip of shaft and glans rises between the compressed feet" in str(cue) for cue in footjob.get("prompt_cues", [])), + "Footjob variant lost visible-glans contact cue", + ) fingering = krea2_pose_variant_catalog.get_variant("pov_fingering_reclined_open_thighs") _expect(fingering.get("status") == "candidate", "Fingering variant should remain a candidate until fixed-seed evidence exists") _expect( @@ -6863,7 +6902,7 @@ def smoke_krea2_pose_variant_catalog_policy() -> None: "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(wand.get("status") == "proven", "Wand variant should be proven after repeated generated-route evidence") _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", @@ -6899,26 +6938,112 @@ def smoke_krea2_pose_variant_catalog_policy() -> None: oral_top = krea2_pose_variant_catalog.get_variant("pov_blowjob_top_down_vertical_shaft") _expect(oral_top.get("status") == "candidate", "Blowjob top-view variant should remain a candidate until fixed-seed evidence exists") _expect( - 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", + any("nadir-angle standing male POV top-view oral position" in str(cue) for cue in oral_top.get("prompt_cues", [])), + "Blowjob top-view variant lost nadir-angle cue", + ) + _expect("kneeling" in (oral_top.get("position_keys") or []), "Blowjob top-view variant lost kneeling route key") + oral_top_evidence = oral_top.get("evidence") or {} + _expect( + oral_top_evidence.get("fixed_seed_tests") == ["4242424242"], + "Blowjob top-view variant lost fixed-seed evidence", + ) + _expect( + "blowjob-top-view--overhead-vertical-shaft" in str(oral_top_evidence.get("guide_section") or ""), + "Blowjob top-view variant lost guide-section link", + ) + _expect( + "nadir-angle standing male POV" in str(oral_top_evidence.get("notes") or ""), + "Blowjob top-view variant lost nadir-angle evidence note", ) 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(oral_side.get("status") == "proven", "Blowjob side variant should be proven after fresh-seed three-woman generated-route repeat evidence") _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", + any("woman enters laterally from the left edge beside his hip" in str(cue) for cue in oral_side.get("prompt_cues", [])), + "Blowjob side variant lost lateral-edge body-line cue", + ) + _expect( + any("mouth-to-shaft contact is the nearest facial detail" in str(cue) for cue in oral_side.get("prompt_cues", [])), + "Blowjob side variant lost generated-route contact-priority cue", + ) + _expect( + any("adult male viewer's own torso starts at the lower edge" in str(cue) for cue in oral_side.get("prompt_cues", [])), + "Blowjob side variant lost lower-right self-torso anchor cue", + ) + oral_side_evidence = oral_side.get("evidence") or {} + _expect( + oral_side_evidence.get("fixed_seed_tests") == ["5656565656", "9753197531", "9595959595", "9696969696", "5858585858"], + "Blowjob side variant lost fixed-seed evidence", + ) + _expect( + "blowjob-side-profile--side-phone-weak-case" in str(oral_side_evidence.get("guide_section") or ""), + "Blowjob side variant lost weak-case guide-section link", + ) + _expect( + "explicit adult-male foreground ownership" in str(oral_side_evidence.get("notes") or ""), + "Blowjob side variant lost male-body ownership evidence note", + ) + _expect( + "transferring the central body surface to the woman" in str(oral_side_evidence.get("notes") or ""), + "Blowjob side variant lost source-47 body-axis failure note", + ) + _expect( + "9753197531" in str(oral_side_evidence.get("notes") or "") and "lateral-edge body-line" in str(oral_side_evidence.get("notes") or ""), + "Blowjob side variant lost repeated lateral-edge evidence note", + ) + _expect( + "turn 207" in str(oral_side_evidence.get("notes") or "") and "mouth-to-shaft-contact priority" in str(oral_side_evidence.get("notes") or ""), + "Blowjob side variant lost generated-route contact-priority note", + ) + _expect( + "9595959595" in str(oral_side_evidence.get("notes") or "") and "lower-right torso anchor" in str(oral_side_evidence.get("notes") or ""), + "Blowjob side variant lost lower-right torso repeat evidence note", + ) + _expect( + "9696969696" in str(oral_side_evidence.get("notes") or "") and "generated-route validation" in str(oral_side_evidence.get("notes") or ""), + "Blowjob side variant lost patched generated-route validation note", + ) + _expect( + "5858585858" in str(oral_side_evidence.get("notes") or "") and "three-woman generated-route repeat" in str(oral_side_evidence.get("notes") or ""), + "Blowjob side variant lost fresh-seed promotion note", ) oral_laying = krea2_pose_variant_catalog.get_variant("pov_blowjob_laying_frontal_oral") - _expect(oral_laying.get("status") == "candidate", "Blowjob laying variant should remain a candidate until fixed-seed evidence exists") + _expect(oral_laying.get("status") == "candidate", "Blowjob laying variant should remain a candidate until repeated evidence exists") _expect( any("woman lies belly-down between the viewer's open thighs" in str(cue) for cue in oral_laying.get("prompt_cues", [])), "Blowjob laying variant lost prone frontal cue", ) + oral_laying_evidence = oral_laying.get("evidence") or {} + _expect( + oral_laying_evidence.get("fixed_seed_tests") == ["6767676767"], + "Blowjob laying variant lost fixed-seed evidence", + ) + _expect( + "blowjob-laying-frontal--wide-v-frame" in str(oral_laying_evidence.get("guide_section") or ""), + "Blowjob laying variant lost guide-section link", + ) + _expect( + "wide symmetrical V-frame" in str(oral_laying_evidence.get("notes") or ""), + "Blowjob laying variant lost V-frame evidence note", + ) oral_sitting = krea2_pose_variant_catalog.get_variant("pov_blowjob_sitting_upright_oral") _expect(oral_sitting.get("status") == "candidate", "Blowjob sitting variant should remain a candidate until fixed-seed evidence exists") _expect( - any("woman sits upright between the viewer's open thighs" in str(cue) for cue in oral_sitting.get("prompt_cues", [])), - "Blowjob sitting variant lost upright sitting cue", + any("woman sits low between the viewer's open thighs" in str(cue) for cue in oral_sitting.get("prompt_cues", [])), + "Blowjob sitting variant lost low seated cue", + ) + _expect("blowjob_sitting" in (oral_sitting.get("position_keys") or []), "Blowjob sitting variant lost route key") + oral_sitting_evidence = oral_sitting.get("evidence") or {} + _expect( + oral_sitting_evidence.get("fixed_seed_tests") == ["7878787878"], + "Blowjob sitting variant lost fixed-seed evidence", + ) + _expect( + "blowjob-sitting-upright--low-mouth-contact" in str(oral_sitting_evidence.get("guide_section") or ""), + "Blowjob sitting variant lost guide-section link", + ) + _expect( + "low-mouth seated hierarchy" in str(oral_sitting_evidence.get("notes") or ""), + "Blowjob sitting variant lost low-mouth evidence note", ) missionary = krea2_pose_variant_catalog.get_variant("pov_missionary_open_leg_penetration") _expect(missionary.get("status") == "candidate", "Missionary variant should remain a candidate until fixed-seed evidence exists") @@ -6926,24 +7051,80 @@ def smoke_krea2_pose_variant_catalog_policy() -> None: any("woman reclines on her back with knees open toward the viewer" in str(cue) for cue in missionary.get("prompt_cues", [])), "Missionary variant lost open-leg reclined cue", ) + _expect( + any("viewer stands or braces at the foot edge" in str(cue) for cue in missionary.get("prompt_cues", [])), + "Missionary variant lost elevated-support edge cue", + ) + missionary_evidence = missionary.get("evidence") or {} + _expect( + missionary_evidence.get("fixed_seed_tests") == ["8989898989"], + "Missionary variant lost fixed-seed evidence", + ) + _expect( + "edge-supported route" in str(missionary_evidence.get("notes") or ""), + "Missionary variant lost edge-supported patch boundary", + ) missionary_folded = krea2_pose_variant_catalog.get_variant("pov_missionary_folded_high_leg_penetration") - _expect(missionary_folded.get("status") == "candidate", "Folded missionary variant should remain a candidate until fixed-seed evidence exists") + _expect(missionary_folded.get("status") == "candidate", "Folded missionary variant should remain a candidate until repeated evidence exists") _expect( any("woman reclines on her back with knees folded high toward her chest" in str(cue) for cue in missionary_folded.get("prompt_cues", [])), "Folded missionary variant lost high-leg folded cue", ) + _expect( + any("large centered shaft" in str(cue) for cue in missionary_folded.get("prompt_cues", [])), + "Folded missionary variant lost contact-first shaft cue", + ) + missionary_folded_evidence = missionary_folded.get("evidence") or {} + _expect( + missionary_folded_evidence.get("fixed_seed_tests") == ["8989898989"], + "Folded missionary variant lost fixed-seed evidence", + ) + _expect( + "contact before the compact folded-knee block" in str(missionary_folded_evidence.get("notes") or ""), + "Folded missionary variant lost contact-first evidence note", + ) cowgirl = krea2_pose_variant_catalog.get_variant("pov_cowgirl_frontal_straddle_penetration") - _expect(cowgirl.get("status") == "candidate", "Cowgirl variant should remain a candidate until fixed-seed evidence exists") + _expect(cowgirl.get("status") == "proven", "Cowgirl variant should be proven after fresh-seed generated-route repeat evidence") _expect( any("woman straddles the viewer facing him" in str(cue) for cue in cowgirl.get("prompt_cues", [])), "Cowgirl variant lost frontal straddle cue", ) + _expect( + any("wide horizontal thigh bridge" in str(cue) for cue in cowgirl.get("prompt_cues", [])), + "Cowgirl variant lost wide thigh bridge cue", + ) + cowgirl_evidence = cowgirl.get("evidence") or {} + _expect( + cowgirl_evidence.get("fixed_seed_tests") == ["8989898989", "2828282828", "9191919191"], + "Cowgirl variant lost fixed-seed evidence", + ) + _expect( + "baseline already valid" in str(cowgirl_evidence.get("notes") or ""), + "Cowgirl variant lost baseline-valid evidence note", + ) + _expect( + "9191919191" in str(cowgirl_evidence.get("notes") or "") and "turns 242" in str(cowgirl_evidence.get("notes") or ""), + "Cowgirl variant lost fresh-seed proven evidence note", + ) cowgirl_alt = krea2_pose_variant_catalog.get_variant("pov_cowgirl_alt_low_squat_penetration") - _expect(cowgirl_alt.get("status") == "candidate", "Cowgirl alt variant should remain a candidate until fixed-seed evidence exists") + _expect(cowgirl_alt.get("status") == "candidate", "Cowgirl alt variant should remain a candidate until repeated evidence exists") _expect( any("low seated squat over the viewer's pelvis" in str(cue) for cue in cowgirl_alt.get("prompt_cues", [])), "Cowgirl alt variant lost low seated squat cue", ) + _expect( + any("viewer lies flat on his back" in str(cue) for cue in cowgirl_alt.get("prompt_cues", [])), + "Cowgirl alt variant lost flat-supine viewer cue", + ) + cowgirl_alt_evidence = cowgirl_alt.get("evidence") or {} + _expect( + cowgirl_alt_evidence.get("fixed_seed_tests") == ["8989898989"], + "Cowgirl alt variant lost fixed-seed evidence", + ) + _expect( + "ceiling and upper glass" in str(cowgirl_alt_evidence.get("notes") or ""), + "Cowgirl alt variant lost spatial-orientation evidence note", + ) reverse_cowgirl = krea2_pose_variant_catalog.get_variant("pov_reverse_cowgirl_back_facing_penetration") _expect(reverse_cowgirl.get("status") == "candidate", "Reverse cowgirl variant should remain a candidate until fixed-seed evidence exists") _expect( @@ -6983,7 +7164,14 @@ def smoke_krea2_eval_log_policy() -> None: _expect(isinstance(entry.get("seed"), int), f"{entry_id} has no integer fixed seed") _expect(entry.get("result") in {"accepted", "rejected", "inconclusive"}, f"{entry_id} has unknown result") _expect( - entry.get("decision") in {"generator_patch", "prompt_guide_rule", "prompt_only_retry", "needs_more_tests"}, + entry.get("decision") in { + "generator_patch", + "provisional_generator_patch", + "proven_with_evidence", + "prompt_guide_rule", + "prompt_only_retry", + "needs_more_tests", + }, f"{entry_id} has unknown decision", ) _expect_text(f"{entry_id}.baseline_prompt_summary", entry.get("baseline_prompt_summary"), 20) @@ -7008,6 +7196,7 @@ def smoke_krea2_eval_log_policy() -> None: "date": "2026-06-29", "variant_key": "pov_ballsucking_low_head", "seed": 9001, + "generator_seed": 4101, "source": "smoke", "result": "inconclusive", "decision": "needs_more_tests", @@ -7024,6 +7213,17 @@ def smoke_krea2_eval_log_policy() -> None: catalog_keys=set(krea2_pose_variant_catalog.variant_keys()), ) _expect(errors == [], f"Valid Krea2 eval entry failed validation: {errors}") + provisional_entry = dict(smoke_entry) + provisional_entry["id"] = "ballsucking-9002-provisional-generator-smoke" + provisional_entry["seed"] = 9002 + provisional_entry["result"] = "accepted" + provisional_entry["decision"] = "provisional_generator_patch" + provisional_errors = krea2_eval_log.validate_entry( + provisional_entry, + existing_entries=log.get("entries") or [], + catalog_keys=set(krea2_pose_variant_catalog.variant_keys()), + ) + _expect(provisional_errors == [], f"Provisional generator eval entry failed validation: {provisional_errors}") appended_log = krea2_eval_log.append_entry(smoke_entry, path=temp_log_path) _expect(len(appended_log.get("entries") or []) == len(entries) + 1, "Krea2 eval append did not add one entry") appended_entries = krea2_eval_log.entries_for_variant("pov_ballsucking_low_head", path=temp_log_path) @@ -7043,14 +7243,28 @@ def smoke_krea2_eval_log_policy() -> None: catalog_keys=set(krea2_pose_variant_catalog.variant_keys()), ) _expect(any("unknown variant" in error for error in bad_variant_errors), "Krea2 eval validation should reject unknown variants") + bad_generator_seed = dict(smoke_entry) + bad_generator_seed["id"] = "bad-generator-seed-9001" + bad_generator_seed["generator_seed"] = "4101" + bad_generator_seed_errors = krea2_eval_log.validate_entry( + bad_generator_seed, + existing_entries=appended_log.get("entries") or [], + catalog_keys=set(krea2_pose_variant_catalog.variant_keys()), + ) + _expect( + any("generator_seed must be an integer" in error for error in bad_generator_seed_errors), + "Krea2 eval validation should reject non-integer generator_seed", + ) template = krea2_eval_log.entry_template( "pov_footjob_frontal_sole_stroke", seed=9102, + generator_seed=9104, source="smoke", date="2026-06-29", ) _expect(template.get("variant_key") == "pov_footjob_frontal_sole_stroke", "Krea2 eval template lost variant key") _expect(template.get("seed") == 9102, "Krea2 eval template lost fixed seed") + _expect(template.get("generator_seed") == 9104, "Krea2 eval template lost generator seed") _expect(template.get("result") == "inconclusive", "Krea2 eval template should default to inconclusive") _expect(template.get("decision") == "needs_more_tests", "Krea2 eval template should default to needs_more_tests") _expect("footjob" in str(template.get("id") or ""), "Krea2 eval template id should include variant family") @@ -7069,6 +7283,8 @@ def smoke_krea2_eval_log_policy() -> None: "pov_fingering_reclined_open_thighs", "--seed", "9103", + "--generator-seed", + "9105", "--source", "smoke", "--date", @@ -7083,6 +7299,7 @@ def smoke_krea2_eval_log_policy() -> None: cli_template = json.loads(cli_result.stdout) _expect(cli_template.get("variant_key") == "pov_fingering_reclined_open_thighs", "Krea2 eval template CLI lost variant") _expect(cli_template.get("seed") == 9103, "Krea2 eval template CLI lost seed") + _expect(cli_template.get("generator_seed") == 9105, "Krea2 eval template CLI lost generator seed") def smoke_krea2_prompt_guide_policy() -> None: @@ -7091,6 +7308,141 @@ def smoke_krea2_prompt_guide_policy() -> None: _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("balls fill the exact center" in guide, "Krea2 prompt guide lost balls-first scrotum framing rule") + _expect("shaft is cropped to the side" in guide, "Krea2 prompt guide lost side-cropped shaft rule") + _expect("Fresh-seed partial update: sampler seed `6262626262`" in guide, "Krea2 prompt guide lost ballsucking fresh-seed partial update") + _expect("Generated-route validation: turns `262` and `263`" in guide, "Krea2 prompt guide lost ballsucking generated-route validation update") + _expect("Fresh-seed target-object repeat: sampler seed `9797979797`" in guide, "Krea2 prompt guide lost ballsucking target-object repeat update") + _expect("scrotal skin is the nearest mouth surface" in guide and "turns `288` and `293`" in guide, "Krea2 prompt guide lost ballsucking scrotal-skin target evidence") + _expect("Generated-route validation: sampler seed `9898989898`" in guide, "Krea2 prompt guide lost ballsucking scrotal-skin generated-route validation") + _expect("turns `296` and `297`" in guide and "side-low cheek/thigh geometry" in guide, "Krea2 prompt guide lost ballsucking validation turn evidence") + _expect("Fresh-seed weak case: sampler seed `5959595959`" in guide, "Krea2 prompt guide lost ballsucking fresh-seed weak-case update") + _expect("lip-oval" in guide and "sideways mouth pocket" in guide and "chin-pelvis upward seal" in guide, "Krea2 prompt guide lost ballsucking weak-case wording axes") + _expect("Fresh-seed occlusion weak case: sampler seed `6060606060`" in guide, "Krea2 prompt guide lost ballsucking occlusion weak-case update") + _expect("scrotum foreground occlusion" in guide and "under-scrotum tongue shelf" in guide and "hand-guided scrotum" in guide, "Krea2 prompt guide lost ballsucking occlusion weak-case wording axes") + _expect("Fresh-seed mouth-axis mixed case: sampler seed `6161616161`" in guide, "Krea2 prompt guide lost ballsucking mouth-axis mixed-case update") + _expect("exact mouth-sucking" in guide and "single-testicle" in guide and "hanging balls below shaft" in guide, "Krea2 prompt guide lost ballsucking mouth-axis wording axes") + _expect("turns `331` and `337`" in guide and "turn `348`" in guide, "Krea2 prompt guide lost ballsucking mouth-axis turn evidence") + _expect("Fresh-seed pelvis-valley weak case: sampler seed `7171717171`" in guide, "Krea2 prompt guide lost ballsucking pelvis-valley weak-case update") + _expect("flat pelvis-valley" in guide and "thigh tunnel" in guide and "pelvis-edge target-first" in guide, "Krea2 prompt guide lost ballsucking pelvis-valley wording axes") + _expect("turns `350`, `356`, and `362`" in guide and "body-plane correction" in guide, "Krea2 prompt guide lost ballsucking pelvis-valley turn evidence") + _expect("Fresh-seed flat-target hybrid weak case: sampler seed `7272727272`" in guide, "Krea2 prompt guide lost ballsucking flat-target hybrid weak-case update") + _expect("flat-valley scrotal-skin target" in guide and "upper-frame shaft lower-scrotum" in guide and "side-low flat-valley hybrid" in guide, "Krea2 prompt guide lost ballsucking flat-target hybrid wording axes") + _expect("turns `368`, `374`, and `380`" in guide and "shaft-centered" in guide, "Krea2 prompt guide lost ballsucking flat-target hybrid turn evidence") + _expect("### Footjob" in guide, "Krea2 prompt guide lost footjob section") + _expect("large overlapping soles" in guide, "Krea2 prompt guide lost overlapping-sole footjob rule") + _expect("shaft trapped between the two soles" in guide, "Krea2 prompt guide lost trapped-shaft footjob rule") + _expect("glans rises between the compressed feet" in guide, "Krea2 prompt guide lost visible-glans footjob correction rule") + _expect("Fresh-seed promotion: sampler seed `7373737373`" in guide, "Krea2 prompt guide lost footjob fresh-seed promotion note") + _expect("### Fingering / Manual Stimulation" in guide, "Krea2 prompt guide lost fingering section") + _expect("single foreground hand is the largest lower-frame object" in guide, "Krea2 prompt guide lost hand-first fingering rule") + _expect("office-chair support and coworking depth fourth" in guide, "Krea2 prompt guide lost coworking-safe fingering hierarchy") + _expect("provisional improvement over baseline" in guide, "Krea2 prompt guide lost provisional fingering generator note") + _expect("### Wand Toy Contact" in guide, "Krea2 prompt guide lost wand section") + _expect("single continuous teal wand-style massager" in guide, "Krea2 prompt guide lost wand single-device rule") + _expect("Krea2 splitting the cue into a contact toy plus a second wand-like foreground object" in guide, "Krea2 prompt guide lost wand split-device failure mode") + _expect("### Ready / Post-Ejaculation Open-Thigh Display" in guide, "Krea2 prompt guide lost ready aftermath section") + _expect("the wet aftermath detail is the exact center" in guide, "Krea2 prompt guide lost ready wet-aftermath hierarchy") + _expect("category-exit rule" in guide, "Krea2 prompt guide lost ready provisional generator note") + _expect("### Spread / Open-Thigh Presentation" in guide, "Krea2 prompt guide lost spread section") + _expect("thighs form a broad V-frame" in guide, "Krea2 prompt guide lost spread V-frame rule") + _expect("hands hold the knees or thighs" in guide, "Krea2 prompt guide lost spread hand-anchor rule") + _expect("### Blowjob Top View / Overhead Vertical Shaft" in guide, "Krea2 prompt guide lost blowjob top-view section") + _expect("nadir-angle standing male POV top-view oral position" in guide, "Krea2 prompt guide lost top-view nadir-angle rule") + _expect("nearby carpet/floor plane dominates the image" in guide, "Krea2 prompt guide lost top-view floor-plane rule") + _expect("shaft appears as a short centered vertical column" in guide, "Krea2 prompt guide lost top-view vertical-column rule") + _expect("hair crown, forehead, shoulders, hands, and knees are visible from above" in guide, "Krea2 prompt guide lost top-view from-above body-surface rule") + _expect("plumb-line" in guide and "literal drawn artifacts" in guide, "Krea2 prompt guide lost top-view literalization warning") + _expect("### Blowjob Side Profile / Side-Phone Weak Case" in guide, "Krea2 prompt guide lost blowjob side weak-case section") + _expect("side-phone" in guide, "Krea2 prompt guide lost side-phone oral weak-case wording") + _expect("not valid POV evidence" in guide, "Krea2 prompt guide lost side-profile non-POV boundary") + _expect("visible adult male chest, navel, abdomen hair" in guide, "Krea2 prompt guide lost side-profile male-body ownership cue") + _expect("accepted single-source evidence" in guide, "Krea2 prompt guide lost side-profile single-source evidence boundary") + _expect("Krea2 transferred the body-axis cue" in guide, "Krea2 prompt guide lost side-profile body-axis transfer failure") + _expect("lateral_edge_entry" in guide and "male viewer's abdomen" in guide and "near thigh create a broad horizontal foreground body" in guide, "Krea2 prompt guide lost side-profile lateral-edge matrix update") + _expect("turn `206`" in guide and "mouth-to-shaft contact is the nearest facial detail" in guide, "Krea2 prompt guide lost side-profile generated-route contact update") + _expect("Fresh-seed weak case: sampler seed `8484848484`" in guide, "Krea2 prompt guide lost side-profile fresh-seed weak-case update") + _expect("Fresh-seed lower-right torso repeat: sampler seed `9595959595`" in guide, "Krea2 prompt guide lost side-profile lower-right torso repeat") + _expect("turns `279` and `283`" in guide and "adult male viewer's own torso starts at the lower edge" in guide, "Krea2 prompt guide lost side-profile lower-right self-torso evidence") + _expect("Generated-route validation: sampler seed `9696969696`" in guide, "Krea2 prompt guide lost side-profile generated-route validation") + _expect("turns `284` and `285`" in guide and "lower-right own-body foreground" in guide, "Krea2 prompt guide lost side-profile generated-route validation turn evidence") + _expect("Fresh-seed promotion: sampler seed `5858585858`" in guide, "Krea2 prompt guide lost side-profile fresh-seed promotion") + _expect("turns `298`, `301`, and `304`" in guide and "side-camera-style self-body crop" in guide, "Krea2 prompt guide lost side-profile three-woman promotion evidence") + _expect("### Blowjob Laying Frontal / Wide V-Frame" in guide, "Krea2 prompt guide lost blowjob laying frontal section") + _expect("wide symmetrical V-frame" in guide, "Krea2 prompt guide lost blowjob laying V-frame rule") + _expect("torso stretched low and horizontal" in guide, "Krea2 prompt guide lost blowjob laying low-horizontal rule") + _expect("centered mouth-to-shaft contact" in guide, "Krea2 prompt guide lost blowjob laying centered-contact rule") + _expect("### Blowjob Sitting Upright / Low Mouth Contact" in guide, "Krea2 prompt guide lost blowjob sitting section") + _expect("face lowered close to the exact center contact point" in guide, "Krea2 prompt guide lost blowjob sitting low-face rule") + _expect("open mouth covers the tip at the centerline" in guide, "Krea2 prompt guide lost blowjob sitting mouth-tip rule") + _expect("both hands stay low at the base directly below her mouth" in guide, "Krea2 prompt guide lost blowjob sitting hand-base rule") + _expect("### Missionary Open-Leg / Seated-Lounge Drift" in guide, "Krea2 prompt guide lost missionary weak-case section") + _expect("Follow-up prompt-axis probes on the same sampler seed" in guide, "Krea2 prompt guide lost missionary prompt-axis follow-up") + _expect("pose-first probes omitted the original subject/look" in guide and "block" in guide, "Krea2 prompt guide lost missionary prompt-order caveat") + _expect("Patch only the raised-edge or" in guide and "edge-supported route" in guide, "Krea2 prompt guide lost missionary edge-supported patch boundary") + _expect("keep generic" in guide and "valid angled missionary views" in guide, "Krea2 prompt guide lost generic missionary validity boundary") + _expect("office-lounge support words" in guide, "Krea2 prompt guide lost missionary lounge-drift warning") + _expect("flat elevated-support atlas" in guide, "Krea2 prompt guide lost flat elevated-support axis") + _expect("### Cowgirl Frontal / Wide Thigh Bridge" in guide, "Krea2 prompt guide lost cowgirl wide-thigh section") + _expect("Matrix update: sampler seed `2828282828`" in guide, "Krea2 prompt guide lost cowgirl repeat matrix update") + _expect("Mirror the wide-thigh bridge hierarchy into the normal cowgirl" in guide, "Krea2 prompt guide lost cowgirl provisional generator note") + _expect("Generated-route validation: turn `216`" in guide, "Krea2 prompt guide lost cowgirl generated-route validation note") + _expect("Fresh-seed promotion: sampler seed `9191919191`" in guide, "Krea2 prompt guide lost cowgirl fresh-seed promotion note") + _expect("## Evidence Promotion Threshold" in guide, "Krea2 prompt guide lost evidence promotion threshold") + _expect("Do not promote a single-character, single-location prompt hack" in guide, "Krea2 prompt guide lost single-scene overfit guard") + _expect("Scene repair words must match the selected location" in guide, "Krea2 prompt guide lost location-consistency guard") + methodology = (ROOT / "docs" / "krea2-ab-methodology.md").read_text(encoding="utf-8") + _expect("2026-06-30-generated-route-validation-positive-channel-cleanup" in methodology, "Krea2 A/B methodology memory lost current method version") + _expect("Update it whenever the testing method improves" in methodology, "Krea2 A/B methodology memory lost update contract") + _expect("Use location-compatible anchors only" in methodology, "Krea2 A/B methodology memory lost anchor rule") + _expect("## Generator Mirroring" in methodology, "Krea2 A/B methodology memory lost generator mirroring section") + _expect("final formatter output" in methodology, "Krea2 A/B methodology memory lost formatter-output regression rule") + _expect("## Generator-Patch Evidence Matrix" in methodology, "Krea2 A/B methodology memory lost generator-patch matrix") + _expect("at least three distinct source cases" in methodology, "Krea2 A/B methodology memory lost source-case threshold") + _expect("at least two sampler seeds" in methodology, "Krea2 A/B methodology memory lost sampler-seed threshold") + _expect("provisional_generator_patch" in methodology, "Krea2 A/B methodology memory lost provisional generator rule") + _expect("same-seed evidence shows it improves over baseline" in methodology, "Krea2 A/B methodology memory lost category-exit progress rule") + _expect("two source subjects improved on the same sampler seed" in methodology, "Krea2 A/B methodology memory lost spread two-source update") + _expect("baseline is already usable" in methodology, "Krea2 A/B methodology memory lost narrow-improvement update") + _expect("attractive side-camera result" in methodology, "Krea2 A/B methodology memory lost non-target-viewpoint weak-case rule") + _expect( + "/media/p5/miniforge3/bin/python tools/sxcp_mcp_client.py call-tool comfy_push" in methodology, + "Krea2 A/B methodology memory lost approved MCP push helper command", + ) + _expect("source `46`" in methodology and "improved with explicit adult-male foreground ownership" in methodology, "Krea2 A/B methodology memory lost side-profile ownership update") + _expect("source `47`" in methodology and "rejected a related `body-axis` cue" in methodology, "Krea2 A/B methodology memory lost body-axis rejection update") + _expect("9753197531" in methodology and "lateral-edge body-line axis" in methodology, "Krea2 A/B methodology memory lost side-profile lateral-edge update") + _expect("turn `206`" in methodology and "mouth-to-shaft-contact priority" in methodology, "Krea2 A/B methodology memory lost side-profile generated-route contact update") + _expect("9595959595" in methodology and "lower-right torso anchor" in methodology, "Krea2 A/B methodology memory lost side-profile lower-right anchor update") + _expect("9696969696" in methodology and "generated-route validation" in methodology, "Krea2 A/B methodology memory lost side-profile generated-route validation update") + _expect("5858585858" in methodology and "three-woman generated-route repeat" in methodology, "Krea2 A/B methodology memory lost side-profile fresh-seed promotion update") + _expect("5959595959" in methodology and "lip-oval" in methodology and "shaft/glans collapse" in methodology, "Krea2 A/B methodology memory lost ballsucking fresh weak-case update") + _expect("6060606060" in methodology and "foreground occlusion" in methodology and "hand-guided" in methodology, "Krea2 A/B methodology memory lost ballsucking occlusion weak-case update") + _expect("6161616161" in methodology and "exact mouth-sucking" in methodology and "generated-route controls" in methodology, "Krea2 A/B methodology memory lost ballsucking mouth-axis mixed-case update") + _expect("7171717171" in methodology and "flat pelvis-valley" in methodology and "body-plane correction" in methodology, "Krea2 A/B methodology memory lost ballsucking pelvis-valley weak-case update") + _expect("7272727272" in methodology and "flat-valley scrotal-skin" in methodology and "shaft-centered" in methodology, "Krea2 A/B methodology memory lost ballsucking flat-target hybrid weak-case update") + _expect("2828282828" in methodology and "wide-thigh bridge axis" in methodology, "Krea2 A/B methodology memory lost cowgirl provisional patch update") + _expect("blowjob laying frontal" in methodology, "Krea2 A/B methodology memory lost blowjob laying update") + _expect("wide V-frame and low-horizontal torso" in methodology, "Krea2 A/B methodology memory lost blowjob laying hierarchy update") + _expect("blowjob sitting upright" in methodology, "Krea2 A/B methodology memory lost blowjob sitting update") + _expect("low-mouth seated hierarchy" in methodology, "Krea2 A/B methodology memory lost blowjob sitting hierarchy update") + _expect("Corrected blowjob top-view criteria" in methodology, "Krea2 A/B methodology memory lost top-view correction update") + _expect("Refined blowjob top-view prompt-axis search" in methodology, "Krea2 A/B methodology memory lost top-view axis-search update") + _expect("Avoid `plumb-line` and `map` in generator prompts" in methodology, "Krea2 A/B methodology memory lost top-view literalization warning") + _expect("batched prompt-probe" in methodology and "loop before analysis-heavy iteration" in methodology, "Krea2 A/B methodology memory lost batched prompt-probe loop") + _expect("pull only until each new" in methodology and "`sxcp_eval_in` turn and image path exists" in methodology, "Krea2 A/B methodology memory lost image-presence probe rule") + _expect("Preserve prompt-order controls" in methodology, "Krea2 A/B methodology memory lost prompt-order control rule") + _expect("subject/look description first" in methodology, "Krea2 A/B methodology memory lost subject-look-first rule") + _expect("geometry-only probes" in methodology, "Krea2 A/B methodology memory lost geometry-only probe caveat") + _expect("vertical shaft alignment alone" in methodology, "Krea2 A/B methodology memory lost vertical-shaft insufficiency rule") + _expect( + "Never put negative-conditioning phrases inside the positive Krea2 prompt" in guide, + "Krea2 prompt guide lost positive-only conditioning rule", + ) + _expect( + "no active negative-output contract" in guide, + "Krea2 prompt guide lost no-negative-channel workflow rule", + ) _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("hardest" in guide and "low-priority" in guide, "Krea2 prompt guide lost hardest low-priority wording") @@ -7113,20 +7465,101 @@ def smoke_krea2_tuning_report_policy() -> None: _expect(boobjob_latest.get("decision") == "generator_patch", "Boobjob report lost latest evidence decision") _expect("upright frontal boobjob geometry" in str(boobjob_latest.get("candidate_prompt_summary") or ""), "Boobjob report lost latest candidate summary") ballsucking = by_key.get("pov_ballsucking_low_head") or {} - _expect(ballsucking.get("coverage_state") == "needs_fixed_seed_tests", "Ballsucking report should need fixed-seed tests") - _expect(ballsucking.get("accepted_evidence_count") == 0, "Ballsucking report should not have accepted evidence yet") + _expect(ballsucking.get("coverage_state") == "tracked", "Ballsucking report should track accepted prompt-guide evidence without proving the candidate") + _expect(ballsucking.get("accepted_evidence_count") == 6, "Ballsucking report lost fresh generated-route validation evidence") + _expect(ballsucking.get("total_evidence_count") == 14, "Ballsucking report lost fresh flat-target hybrid weak-case history") + ballsucking_latest = ballsucking.get("latest_evidence") or {} + _expect( + ballsucking_latest.get("id") == "ballsucking-7272727272-flat-target-hybrid-weak-case", + "Ballsucking report lost latest fresh flat-target hybrid weak-case evidence id", + ) + _expect(ballsucking_latest.get("seed") == 7272727272, "Ballsucking report lost fresh flat-target hybrid weak-case sampler seed evidence") + _expect(ballsucking_latest.get("result") == "inconclusive", "Ballsucking latest weak-case evidence should be inconclusive") + _expect(ballsucking_latest.get("decision") == "needs_more_tests", "Ballsucking latest weak-case evidence should keep route queued") + _expect( + "flat-valley scrotal-skin" in str(ballsucking_latest.get("candidate_prompt_summary") or "") and "side-low flat-valley" in str(ballsucking_latest.get("candidate_prompt_summary") or ""), + "Ballsucking report lost fresh flat-target hybrid wording axes", + ) + _expect( + "turns 367-384" in str(ballsucking_latest.get("observation") or "") and "shaft-centered" in str(ballsucking_latest.get("observation") or ""), + "Ballsucking report lost fresh flat-target hybrid weak-case turn observation", + ) + ballsucking_latest_accepted = ballsucking.get("latest_accepted_evidence") or {} + _expect( + ballsucking_latest_accepted.get("id") == "ballsucking-9898989898-scrotal-skin-route-validation", + "Ballsucking report lost latest accepted validation evidence id", + ) footjob = by_key.get("pov_footjob_frontal_sole_stroke") or {} - _expect(footjob.get("coverage_state") == "needs_fixed_seed_tests", "Footjob report should need fixed-seed tests") - _expect(footjob.get("accepted_evidence_count") == 0, "Footjob report should not have accepted evidence yet") + _expect(footjob.get("coverage_state") == "proven_with_evidence", "Footjob report should be proven after fresh-seed generated-route repeat evidence") + _expect(footjob.get("accepted_evidence_count") == 5, "Footjob report lost fresh-seed proven evidence") + _expect(footjob.get("total_evidence_count") == 5, "Footjob report lost accepted A/B history") + footjob_latest = footjob.get("latest_evidence") or {} + _expect( + footjob_latest.get("id") == "footjob-7373737373-generated-route-repeat-proven", + "Footjob report lost latest fresh-seed proven evidence id", + ) + _expect(footjob_latest.get("seed") == 7373737373, "Footjob report lost fresh sampler seed evidence") + _expect(footjob_latest.get("result") == "accepted", "Footjob report lost accepted evidence result") + _expect(footjob_latest.get("decision") == "proven_with_evidence", "Footjob report lost proven evidence decision") + _expect( + "narrow visible strip of shaft and glans" in str(footjob_latest.get("candidate_prompt_summary") or ""), + "Footjob report lost narrow-strip visible-contact correction rule", + ) 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") + _expect(fingering.get("coverage_state") == "tracked", "Fingering report should track accepted prompt-guide evidence without proving the candidate") + _expect(fingering.get("accepted_evidence_count") == 2, "Fingering report lost repeated prompt-guide evidence") + _expect(fingering.get("total_evidence_count") == 5, "Fingering report should retain inconclusive/prompt-guide/generator/matrix/weak-case A/B history") + fingering_latest = fingering.get("latest_evidence") or {} + _expect( + fingering_latest.get("id") == "fingering-1357913579-source52-own-hand-weak-case", + "Fingering report lost latest source-52 weak-case evidence id", + ) + _expect(fingering_latest.get("seed") == 1357913579, "Fingering report lost source-52 sampler seed evidence") + _expect(fingering_latest.get("result") == "inconclusive", "Fingering source-52 weak case should stay inconclusive") + _expect(fingering_latest.get("decision") == "needs_more_tests", "Fingering source-52 weak case should need more tests") + _expect( + "changed hand ownership" in str(fingering_latest.get("observation") or ""), + "Fingering report lost hand-ownership weak-case note", + ) + fingering_latest_accepted = fingering.get("latest_accepted_evidence") or {} + _expect( + fingering_latest_accepted.get("id") == "fingering-987654321-source50-office-chair-hand-hierarchy", + "Fingering report lost latest accepted prompt-guide evidence id", + ) + _expect( + fingering_latest_accepted.get("decision") == "provisional_generator_patch", + "Fingering latest accepted evidence should be a provisional generator patch", + ) 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") + _expect(wand.get("coverage_state") == "proven_with_evidence", "Wand report should be proven after repeated generated-route evidence") + _expect(wand.get("accepted_evidence_count") == 3, "Wand report lost accepted evidence") + _expect(wand.get("total_evidence_count") == 3, "Wand report lost accepted A/B history") + wand_latest = wand.get("latest_evidence") or {} + _expect( + wand_latest.get("id") == "wand-7979797979-two-woman-teal-repeat-proven", + "Wand report lost latest accepted evidence id", + ) + _expect(wand_latest.get("seed") == 7979797979, "Wand report lost sampler seed evidence") + _expect(wand_latest.get("decision") == "generator_patch", "Wand latest evidence should finalize the generator default") + _expect( + "white upper-left branch" in str(wand_latest.get("candidate_prompt_summary") or ""), + "Wand report lost upper-left alternate evidence", + ) ready = by_key.get("pov_ejaculation_aftermath_open_thigh_candidate") or {} - _expect(ready.get("coverage_state") == "needs_fixed_seed_tests", "Ready aftermath report should need fixed-seed tests") - _expect(ready.get("accepted_evidence_count") == 0, "Ready aftermath report should not have accepted evidence yet") + _expect(ready.get("coverage_state") == "tracked", "Ready aftermath report should track first accepted evidence") + _expect(ready.get("accepted_evidence_count") == 1, "Ready aftermath report lost first accepted evidence") + _expect(ready.get("total_evidence_count") == 1, "Ready aftermath report should have one recorded A/B result") + ready_latest = ready.get("latest_evidence") or {} + _expect( + ready_latest.get("id") == "ready-1123581321-source52-wet-aftermath-hierarchy", + "Ready aftermath report lost latest accepted evidence id", + ) + _expect(ready_latest.get("seed") == 1123581321, "Ready aftermath report lost sampler seed evidence") + _expect(ready_latest.get("decision") == "provisional_generator_patch", "Ready first evidence should be a provisional generator patch") + _expect( + "wet aftermath hierarchy" in str(ready_latest.get("candidate_prompt_summary") or ""), + "Ready aftermath report lost wet hierarchy evidence", + ) sixty_nine = by_key.get("pov_sixty_nine_close_reversed_oral") or {} _expect(sixty_nine.get("coverage_state") == "needs_stronger_control", "Sixty-nine report should require stronger control") _expect(sixty_nine.get("accepted_evidence_count") == 0, "Sixty-nine report should not have accepted evidence yet") @@ -7137,61 +7570,170 @@ def smoke_krea2_tuning_report_policy() -> None: "Sixty-nine report lost control-first marker", ) 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") + _expect(spread.get("coverage_state") == "tracked", "Spread report should track provisional generator evidence") + _expect(spread.get("accepted_evidence_count") == 1, "Spread report lost first accepted evidence") + _expect(spread.get("total_evidence_count") == 1, "Spread report should have one recorded A/B result") + spread_latest = spread.get("latest_evidence") or {} + _expect( + spread_latest.get("id") == "spread-3141592653-source50-47-raised-knee-v-frame", + "Spread report lost latest accepted evidence id", + ) + _expect(spread_latest.get("seed") == 3141592653, "Spread report lost sampler seed evidence") + _expect(spread_latest.get("decision") == "provisional_generator_patch", "Spread first evidence should be a provisional generator patch") + _expect( + "atlas spread hierarchy" in str(spread_latest.get("candidate_prompt_summary") or ""), + "Spread report lost atlas hierarchy evidence", + ) 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") + _expect(oral_top.get("coverage_state") == "tracked", "Blowjob top-view report should track provisional generator evidence") + _expect(oral_top.get("accepted_evidence_count") == 2, "Blowjob top-view report lost accepted correction evidence") + _expect(oral_top.get("total_evidence_count") == 3, "Blowjob top-view report should have three recorded A/B results") + oral_top_latest = oral_top.get("latest_evidence") or {} + _expect( + oral_top_latest.get("id") == "blowjob-top-4242424242-turn67-70-nadir-floor-plane-axis", + "Blowjob top-view report lost latest nadir-angle correction evidence id", + ) + _expect(oral_top_latest.get("seed") == 4242424242, "Blowjob top-view report lost sampler seed evidence") + _expect( + oral_top_latest.get("decision") == "provisional_generator_patch", + "Blowjob top-view first evidence should be a provisional generator patch", + ) + _expect( + "nadir-angle" in str(oral_top_latest.get("candidate_prompt_summary") or ""), + "Blowjob top-view report lost nadir-angle hierarchy evidence", + ) 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") + _expect(oral_side.get("coverage_state") == "proven_with_evidence", "Blowjob side report should be proven after three-woman generated-route repeat evidence") + _expect(oral_side.get("accepted_evidence_count") == 6, "Blowjob side report should count male-body-axis, lateral-edge, generated-route, lower-right torso, validation, and promotion evidence") + _expect(oral_side.get("total_evidence_count") == 8, "Blowjob side report should retain fresh weak-case and promotion evidence") + oral_side_latest = oral_side.get("latest_evidence") or {} + _expect( + oral_side_latest.get("id") == "blowjob-side-5858585858-three-woman-generated-route-proven", + "Blowjob side report lost latest three-woman generated-route evidence id", + ) + _expect(oral_side_latest.get("seed") == 5858585858, "Blowjob side report lost three-woman generated-route sampler seed evidence") + _expect(oral_side_latest.get("result") == "accepted", "Blowjob side three-woman generated-route evidence should be accepted") + _expect(oral_side_latest.get("decision") == "proven_with_evidence", "Blowjob side three-woman generated-route evidence should promote the route") + _expect(oral_side_latest.get("needs_expansion") is False, "Blowjob side three-woman generated-route evidence should leave the expansion queue") + _expect( + "three-woman generated-route repeat" in str(oral_side_latest.get("observation") or ""), + "Blowjob side report lost three-woman generated-route observation", + ) + _expect( + "turns 298, 301, and 304" in str(oral_side_latest.get("observation") or ""), + "Blowjob side report lost three-woman generated-route turn evidence", + ) oral_laying = by_key.get("pov_blowjob_laying_frontal_oral") or {} - _expect(oral_laying.get("coverage_state") == "needs_fixed_seed_tests", "Blowjob laying report should need fixed-seed tests") - _expect(oral_laying.get("accepted_evidence_count") == 0, "Blowjob laying report should not have accepted evidence yet") + _expect(oral_laying.get("coverage_state") == "tracked", "Blowjob laying report should track provisional generator evidence") + _expect(oral_laying.get("accepted_evidence_count") == 1, "Blowjob laying report lost first accepted evidence") + _expect(oral_laying.get("total_evidence_count") == 1, "Blowjob laying report should have one recorded A/B result") + oral_laying_latest = oral_laying.get("latest_evidence") or {} + _expect( + oral_laying_latest.get("id") == "blowjob-laying-6767676767-source46-50-wide-v-frame", + "Blowjob laying report lost latest wide-V evidence id", + ) + _expect(oral_laying_latest.get("seed") == 6767676767, "Blowjob laying report lost sampler seed evidence") + _expect(oral_laying_latest.get("decision") == "provisional_generator_patch", "Blowjob laying evidence should be provisional generator patch") + _expect( + "wide V-frame and low-horizontal torso hierarchy" in str(oral_laying_latest.get("observation") or ""), + "Blowjob laying report lost low-horizontal hierarchy observation", + ) oral_sitting = by_key.get("pov_blowjob_sitting_upright_oral") or {} - _expect(oral_sitting.get("coverage_state") == "needs_fixed_seed_tests", "Blowjob sitting report should need fixed-seed tests") - _expect(oral_sitting.get("accepted_evidence_count") == 0, "Blowjob sitting report should not have accepted evidence yet") + _expect(oral_sitting.get("coverage_state") == "tracked", "Blowjob sitting report should track provisional generator evidence") + _expect(oral_sitting.get("accepted_evidence_count") == 1, "Blowjob sitting report lost first accepted evidence") + _expect(oral_sitting.get("total_evidence_count") == 1, "Blowjob sitting report should have one recorded A/B result") + oral_sitting_latest = oral_sitting.get("latest_evidence") or {} + _expect( + oral_sitting_latest.get("id") == "blowjob-sitting-7878787878-source46-50-low-mouth-contact", + "Blowjob sitting report lost latest low-mouth evidence id", + ) + _expect(oral_sitting_latest.get("seed") == 7878787878, "Blowjob sitting report lost sampler seed evidence") + _expect(oral_sitting_latest.get("decision") == "provisional_generator_patch", "Blowjob sitting evidence should be provisional generator patch") + _expect( + "low-mouth hierarchy" in str(oral_sitting_latest.get("observation") or ""), + "Blowjob sitting report lost low-mouth hierarchy observation", + ) missionary = by_key.get("pov_missionary_open_leg_penetration") or {} - _expect(missionary.get("coverage_state") == "needs_fixed_seed_tests", "Missionary report should need fixed-seed tests") - _expect(missionary.get("accepted_evidence_count") == 0, "Missionary report should not have accepted evidence yet") + _expect(missionary.get("coverage_state") == "tracked", "Missionary report should track accepted elevated-edge evidence") + _expect(missionary.get("accepted_evidence_count") == 1, "Missionary report lost accepted elevated-edge evidence") + _expect(missionary.get("total_evidence_count") == 4, "Missionary report should retain mixed, prompt-axis, subject-first, and elevated-edge evidence entries") + missionary_latest = missionary.get("latest_evidence") or {} + _expect( + missionary_latest.get("id") == "missionary-open-8989898989-turn81-84-elevated-edge-support", + "Missionary report lost latest elevated-edge support evidence id", + ) + _expect(missionary_latest.get("result") == "accepted", "Missionary latest evidence should be accepted") + _expect(missionary_latest.get("decision") == "provisional_generator_patch", "Missionary latest evidence should be a provisional generator patch") + missionary_latest_text = f"{missionary_latest.get('candidate_prompt_summary') or ''} {missionary_latest.get('observation') or ''}" + _expect("elevated support" in missionary_latest_text and "keep generic missionary" in missionary_latest_text, "Missionary latest evidence lost elevated-support/generic-route boundary") missionary_folded = by_key.get("pov_missionary_folded_high_leg_penetration") or {} - _expect(missionary_folded.get("coverage_state") == "needs_fixed_seed_tests", "Folded missionary report should need fixed-seed tests") - _expect(missionary_folded.get("accepted_evidence_count") == 0, "Folded missionary report should not have accepted evidence yet") + _expect(missionary_folded.get("coverage_state") == "tracked", "Folded missionary report should track accepted contact-first evidence") + _expect(missionary_folded.get("accepted_evidence_count") == 1, "Folded missionary report lost accepted contact-first evidence") + _expect(missionary_folded.get("total_evidence_count") == 1, "Folded missionary report should have one durable evidence entry") + missionary_folded_latest = missionary_folded.get("latest_evidence") or {} + _expect( + missionary_folded_latest.get("id") == "missionary-folded-8989898989-turn85-92-contact-first-knee-block", + "Folded missionary report lost latest contact-first evidence id", + ) + _expect(missionary_folded_latest.get("decision") == "provisional_generator_patch", "Folded missionary latest evidence should be a provisional generator patch") cowgirl = by_key.get("pov_cowgirl_frontal_straddle_penetration") or {} - _expect(cowgirl.get("coverage_state") == "needs_fixed_seed_tests", "Cowgirl report should need fixed-seed tests") - _expect(cowgirl.get("accepted_evidence_count") == 0, "Cowgirl report should not have accepted evidence yet") + _expect(cowgirl.get("coverage_state") == "proven_with_evidence", "Cowgirl report should be proven after fresh-seed wide-thigh evidence") + _expect(cowgirl.get("accepted_evidence_count") == 4, "Cowgirl report lost fresh-seed accepted wide-thigh evidence") + _expect(cowgirl.get("total_evidence_count") == 4, "Cowgirl report should have four durable evidence entries") + cowgirl_latest = cowgirl.get("latest_evidence") or {} + _expect( + cowgirl_latest.get("id") == "cowgirl-frontal-9191919191-fresh-seed-wide-thigh-proven", + "Cowgirl report lost latest fresh-seed wide-thigh evidence id", + ) + _expect(cowgirl_latest.get("decision") == "proven_with_evidence", "Cowgirl latest evidence should promote the route") + _expect(cowgirl_latest.get("needs_expansion") is False, "Cowgirl proven evidence should leave the expansion queue") + _expect( + "Fresh seed" in str(cowgirl_latest.get("observation") or ""), + "Cowgirl latest evidence lost fresh-seed observation", + ) cowgirl_alt = by_key.get("pov_cowgirl_alt_low_squat_penetration") or {} - _expect(cowgirl_alt.get("coverage_state") == "needs_fixed_seed_tests", "Cowgirl alt report should need fixed-seed tests") - _expect(cowgirl_alt.get("accepted_evidence_count") == 0, "Cowgirl alt report should not have accepted evidence yet") + _expect(cowgirl_alt.get("coverage_state") == "tracked", "Cowgirl alt report should track accepted flat-supine evidence") + _expect(cowgirl_alt.get("accepted_evidence_count") == 1, "Cowgirl alt report lost accepted flat-supine evidence") + _expect(cowgirl_alt.get("total_evidence_count") == 1, "Cowgirl alt report should have one durable evidence entry") + cowgirl_alt_latest = cowgirl_alt.get("latest_evidence") or {} + _expect( + cowgirl_alt_latest.get("id") == "cowgirl-alt-8989898989-turn97-104-flat-supine-low-angle", + "Cowgirl alt report lost latest flat-supine evidence id", + ) + _expect(cowgirl_alt_latest.get("decision") == "provisional_generator_patch", "Cowgirl alt latest evidence should be a provisional generator patch") reverse_cowgirl = by_key.get("pov_reverse_cowgirl_back_facing_penetration") or {} - _expect(reverse_cowgirl.get("coverage_state") == "needs_fixed_seed_tests", "Reverse cowgirl report should need fixed-seed tests") - _expect(reverse_cowgirl.get("accepted_evidence_count") == 0, "Reverse cowgirl report should not have accepted evidence yet") + _expect(reverse_cowgirl.get("coverage_state") == "tracked", "Reverse cowgirl report should track accepted close-back evidence") + _expect(reverse_cowgirl.get("accepted_evidence_count") == 1, "Reverse cowgirl report lost accepted close-back evidence") + _expect(reverse_cowgirl.get("total_evidence_count") == 1, "Reverse cowgirl report should have one durable evidence entry") + reverse_cowgirl_latest = reverse_cowgirl.get("latest_evidence") or {} + _expect( + reverse_cowgirl_latest.get("id") == "reverse-cowgirl-8989898989-turn105-108-close-back-hip-dominant", + "Reverse cowgirl report lost latest close-back evidence id", + ) + _expect( + reverse_cowgirl_latest.get("decision") == "provisional_generator_patch", + "Reverse cowgirl latest evidence should be a provisional generator patch", + ) reverse_cowgirl_alt = by_key.get("pov_reverse_cowgirl_alt_upright_back_facing_penetration") or {} - _expect(reverse_cowgirl_alt.get("coverage_state") == "needs_fixed_seed_tests", "Reverse cowgirl alt report should need fixed-seed tests") - _expect(reverse_cowgirl_alt.get("accepted_evidence_count") == 0, "Reverse cowgirl alt report should not have accepted evidence yet") + _expect(reverse_cowgirl_alt.get("coverage_state") == "tracked", "Reverse cowgirl alt report should track accepted upright evidence") + _expect(reverse_cowgirl_alt.get("accepted_evidence_count") == 1, "Reverse cowgirl alt report lost accepted upright evidence") + _expect(reverse_cowgirl_alt.get("total_evidence_count") == 1, "Reverse cowgirl alt report should have one durable evidence entry") + reverse_cowgirl_alt_latest = reverse_cowgirl_alt.get("latest_evidence") or {} + _expect( + reverse_cowgirl_alt_latest.get("id") == "reverse-cowgirl-alt-8989898989-turn109-112-upright-hands-on-hips", + "Reverse cowgirl alt report lost latest upright evidence id", + ) + _expect( + reverse_cowgirl_alt_latest.get("decision") == "provisional_generator_patch", + "Reverse cowgirl alt latest evidence should be a provisional generator patch", + ) 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") == 16, "Krea2 tuning report candidate count changed") + _expect(summary.get("status_counts", {}).get("proven") == 7, "Krea2 tuning report proven count changed") + _expect(summary.get("status_counts", {}).get("candidate") == 12, "Krea2 tuning report candidate count changed") _expect(summary.get("status_counts", {}).get("unstable") == 1, "Krea2 tuning report unstable 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_ejaculation_aftermath_open_thigh_candidate", - "pov_spread_open_thigh_presentation", "pov_sixty_nine_close_reversed_oral", - "pov_blowjob_top_down_vertical_shaft", - "pov_blowjob_side_profile_oral", - "pov_blowjob_laying_frontal_oral", - "pov_blowjob_sitting_upright_oral", - "pov_missionary_open_leg_penetration", - "pov_missionary_folded_high_leg_penetration", - "pov_cowgirl_frontal_straddle_penetration", - "pov_cowgirl_alt_low_squat_penetration", - "pov_reverse_cowgirl_back_facing_penetration", - "pov_reverse_cowgirl_alt_upright_back_facing_penetration", ], f"Krea2 tuning report missing-evidence set changed: {summary.get('variants_without_accepted_evidence')}", ) @@ -7199,198 +7741,113 @@ def smoke_krea2_tuning_report_policy() -> None: summary.get("stronger_control_cases") == ["pov_sixty_nine_close_reversed_oral"], f"Krea2 tuning report stronger-control set changed: {summary.get('stronger_control_cases')}", ) + expansion_plans = krea2_tuning_report.guide_expansion_plans() + _expect( + [plan.get("key") for plan in expansion_plans] + == [ + "pov_ballsucking_low_head", + ], + f"Krea2 guide expansion queue changed: {[plan.get('key') for plan in expansion_plans]}", + ) + ballsucking_expansion = expansion_plans[0] + _expect( + ballsucking_expansion.get("latest_accepted_decision") == "provisional_generator_patch", + "Ballsucking expansion plan should identify the provisional generator evidence", + ) + _expect( + ballsucking_expansion.get("target") == "multi_seed_multi_woman_matrix", + "Guide expansion plan should target multi-seed/multi-woman evidence", + ) + _expect( + "pov_cowgirl_frontal_straddle_penetration" not in [plan.get("key") for plan in expansion_plans], + "Cowgirl should leave the expansion queue after fresh-seed promotion", + ) + _expect( + "pov_blowjob_side_profile_oral" not in [plan.get("key") for plan in expansion_plans], + "Blowjob side should leave the expansion queue after fresh-seed three-woman promotion", + ) plans = krea2_tuning_report.next_test_plans() + _expect(plans == [], f"Krea2 tuning report should have no normal next-test plans after reverse-alt evidence: {plans}") _expect( "pov_sixty_nine_close_reversed_oral" not in [plan.get("key") for plan in plans], "Unstable sixty-nine route should not be queued as a normal fixed-seed candidate", ) template_commands = krea2_tuning_report.next_eval_template_commands(seed_token="$SEED") + _expect(template_commands == [], f"Krea2 eval template commands should be empty when no normal next-test plans remain: {template_commands}") _expect( [command.get("key") for command in template_commands] == [plan.get("key") for plan in plans], "Krea2 eval template commands should follow normal next-test candidates", ) - first_template_command = template_commands[0] - _expect(first_template_command.get("key") == "pov_ballsucking_low_head", "First eval template command should target first next-test variant") - _expect( - "python tools/krea2_record_eval.py --print-template" in str(first_template_command.get("command") or ""), - "Krea2 eval template command should use the validated recorder", - ) - _expect("--seed $SEED" in str(first_template_command.get("command") or ""), "Krea2 eval template command should preserve seed placeholder") _expect( "pov_sixty_nine_close_reversed_oral" not in " ".join(str(command.get("command") or "") for command in template_commands), "Krea2 eval template commands should exclude low-priority stronger-control routes", ) - _expect( - [plan.get("key") for plan in plans] - == [ - "pov_ballsucking_low_head", - "pov_footjob_frontal_sole_stroke", - "pov_fingering_reclined_open_thighs", - "pov_wand_foreground_tool_contact", - "pov_ejaculation_aftermath_open_thigh_candidate", - "pov_spread_open_thigh_presentation", - "pov_blowjob_top_down_vertical_shaft", - "pov_blowjob_side_profile_oral", - "pov_blowjob_laying_frontal_oral", - "pov_blowjob_sitting_upright_oral", - "pov_missionary_open_leg_penetration", - "pov_missionary_folded_high_leg_penetration", - "pov_cowgirl_frontal_straddle_penetration", - "pov_cowgirl_alt_low_squat_penetration", - "pov_reverse_cowgirl_back_facing_penetration", - "pov_reverse_cowgirl_alt_upright_back_facing_penetration", - ], - "Krea2 tuning report next plans changed", - ) + _expect([plan.get("key") for plan in plans] == [], "Krea2 tuning report next plans changed") plan_by_key = {plan.get("key"): plan for plan in plans} - ballsucking_plan = plan_by_key["pov_ballsucking_low_head"] _expect( - "woman bends forward and kneels very low" in " ".join(ballsucking_plan.get("prompt_cues") or []), - "Ballsucking test plan lost atlas-backed prompt cue", + "pov_ballsucking_low_head" not in plan_by_key, + "Ballsucking should leave the normal next-test queue after accepted prompt-guide evidence", ) _expect( - "mid-height head placement" in " ".join(ballsucking_plan.get("avoid_cues") or []), - "Ballsucking test plan lost avoid cue", + "pov_footjob_frontal_sole_stroke" not in plan_by_key, + "Footjob should leave the normal next-test queue after accepted prompt-guide evidence", ) _expect( - any(str(path).endswith("ballsucking/101_ballsucking.png") for path in ballsucking_plan.get("reference_paths") or []), - "Ballsucking test plan lost atlas reference path", - ) - footjob_plan = plan_by_key["pov_footjob_frontal_sole_stroke"] - _expect( - "both soles press" in " ".join(footjob_plan.get("prompt_cues") or []), - "Footjob test plan lost sole-contact cue", + "pov_fingering_reclined_open_thighs" not in plan_by_key, + "Fingering should leave the normal next-test queue after accepted prompt-guide evidence", ) _expect( - any(str(path).endswith("footjob/59_footjob.png") for path in footjob_plan.get("reference_paths") or []), - "Footjob test plan lost atlas reference path", - ) - fingering_plan = plan_by_key["pov_fingering_reclined_open_thighs"] - _expect( - "viewer hand enters from the foreground" in " ".join(fingering_plan.get("prompt_cues") or []), - "Fingering test plan lost foreground-hand cue", + "pov_wand_foreground_tool_contact" not in plan_by_key, + "Wand should leave the normal next-test queue after accepted prompt-guide evidence", ) _expect( - 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", + "pov_ejaculation_aftermath_open_thigh_candidate" not in plan_by_key, + "Ready aftermath should leave the normal next-test queue after provisional generator evidence", ) _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", - ) - ready_plan = plan_by_key["pov_ejaculation_aftermath_open_thigh_candidate"] - _expect( - "thick semen or fluid is visible around the exposed pussy or anal opening" in " ".join(ready_plan.get("prompt_cues") or []), - "Ready aftermath test plan lost explicit post-ejaculation fluid/opening cue", + "pov_spread_open_thigh_presentation" not in plan_by_key, + "Spread should leave the normal next-test queue after provisional generator evidence", ) _expect( - any(str(path).endswith("ready/105_ready_.png") for path in ready_plan.get("reference_paths") or []), - "Ready aftermath 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 []), - "Spread test plan lost open-thigh cue", + "pov_blowjob_top_down_vertical_shaft" not in plan_by_key, + "Blowjob top-view should leave the normal next-test queue after provisional generator evidence", ) _expect( - any(str(path).endswith("spread/100_spread_.png") for path in spread_plan.get("reference_paths") or []), - "Spread test plan lost atlas reference path", - ) - oral_top_plan = plan_by_key["pov_blowjob_top_down_vertical_shaft"] - _expect( - "shaft is vertical and centered" in " ".join(oral_top_plan.get("prompt_cues") or []), - "Blowjob top-view test plan lost vertical-shaft cue", + "pov_blowjob_side_profile_oral" not in plan_by_key, + "Blowjob side-profile should leave the normal next-test queue after accepted fragile evidence", ) _expect( - 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", + "pov_blowjob_laying_frontal_oral" not in plan_by_key, + "Blowjob laying should leave the normal next-test queue after provisional generator evidence", ) _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", - ) - oral_laying_plan = plan_by_key["pov_blowjob_laying_frontal_oral"] - _expect( - "woman lies belly-down between the viewer's open thighs" in " ".join(oral_laying_plan.get("prompt_cues") or []), - "Blowjob laying test plan lost prone frontal cue", + "pov_blowjob_sitting_upright_oral" not in plan_by_key, + "Blowjob sitting should leave the normal next-test queue after provisional generator evidence", ) _expect( - any(str(path).endswith("blowjob_laying/101_blowjob_laying.png") for path in oral_laying_plan.get("reference_paths") or []), - "Blowjob laying test plan lost atlas reference path", - ) - oral_sitting_plan = plan_by_key["pov_blowjob_sitting_upright_oral"] - _expect( - "woman sits upright between the viewer's open thighs" in " ".join(oral_sitting_plan.get("prompt_cues") or []), - "Blowjob sitting test plan lost upright sitting cue", + "pov_missionary_open_leg_penetration" not in plan_by_key, + "Missionary should leave the normal next-test queue after accepted elevated-edge evidence", ) _expect( - any(str(path).endswith("blowjob_sitting/100_blowjob_sitting.png") for path in oral_sitting_plan.get("reference_paths") or []), - "Blowjob sitting test plan lost atlas reference path", - ) - missionary_plan = plan_by_key["pov_missionary_open_leg_penetration"] - _expect( - "woman reclines on her back with knees open toward the viewer" in " ".join(missionary_plan.get("prompt_cues") or []), - "Missionary test plan lost open-leg reclined cue", + "pov_missionary_folded_high_leg_penetration" not in plan_by_key, + "Folded missionary should leave the normal next-test queue after accepted contact-first evidence", ) _expect( - any(str(path).endswith("missionary/101_missionary.png") for path in missionary_plan.get("reference_paths") or []), - "Missionary test plan lost atlas reference path", - ) - missionary_folded_plan = plan_by_key["pov_missionary_folded_high_leg_penetration"] - _expect( - "woman reclines on her back with knees folded high toward her chest" in " ".join(missionary_folded_plan.get("prompt_cues") or []), - "Folded missionary test plan lost high-leg folded cue", + "pov_cowgirl_frontal_straddle_penetration" not in plan_by_key, + "Cowgirl should leave the normal next-test queue after accepted wide-thigh evidence", ) _expect( - any(str(path).endswith("missionary_folded/16_missionary_folded.png") for path in missionary_folded_plan.get("reference_paths") or []), - "Folded missionary test plan lost atlas reference path", - ) - cowgirl_plan = plan_by_key["pov_cowgirl_frontal_straddle_penetration"] - _expect( - "woman straddles the viewer facing him" in " ".join(cowgirl_plan.get("prompt_cues") or []), - "Cowgirl test plan lost frontal straddle cue", + "pov_cowgirl_alt_low_squat_penetration" not in plan_by_key, + "Cowgirl alt should leave the normal next-test queue after accepted flat-supine evidence", ) _expect( - any(str(path).endswith("5.cowgirl/100_cowgirl.png") for path in cowgirl_plan.get("reference_paths") or []), - "Cowgirl test plan lost atlas reference path", - ) - cowgirl_alt_plan = plan_by_key["pov_cowgirl_alt_low_squat_penetration"] - _expect( - "low seated squat over the viewer's pelvis" in " ".join(cowgirl_alt_plan.get("prompt_cues") or []), - "Cowgirl alt test plan lost low seated squat cue", + "pov_reverse_cowgirl_back_facing_penetration" not in plan_by_key, + "Reverse cowgirl should leave the normal next-test queue after accepted close-back evidence", ) _expect( - any(str(path).endswith("5.cowgirl_alt/101_cowgirl_alt.png") for path in cowgirl_alt_plan.get("reference_paths") or []), - "Cowgirl alt test plan lost atlas reference path", - ) - reverse_cowgirl_plan = plan_by_key["pov_reverse_cowgirl_back_facing_penetration"] - _expect( - "woman faces away from the viewer in a back-facing straddle" in " ".join(reverse_cowgirl_plan.get("prompt_cues") or []), - "Reverse cowgirl test plan lost back-facing straddle cue", - ) - _expect( - any(str(path).endswith("cowgirl_reverse/101_cowgirl_reverse.png") for path in reverse_cowgirl_plan.get("reference_paths") or []), - "Reverse cowgirl test plan lost atlas reference path", - ) - reverse_cowgirl_alt_plan = plan_by_key["pov_reverse_cowgirl_alt_upright_back_facing_penetration"] - _expect( - "woman sits upright facing away from the viewer in a back-facing straddle" in " ".join(reverse_cowgirl_alt_plan.get("prompt_cues") or []), - "Reverse cowgirl alt test plan lost upright back-facing cue", - ) - _expect( - any(str(path).endswith("cowgirl_reversere_alt/100_cowgirl_reversere_alt.png") for path in reverse_cowgirl_alt_plan.get("reference_paths") or []), - "Reverse cowgirl alt test plan lost atlas reference path", + "pov_reverse_cowgirl_alt_upright_back_facing_penetration" not in plan_by_key, + "Reverse cowgirl alt should leave the normal next-test queue after accepted upright evidence", ) with tempfile.TemporaryDirectory() as tmpdir: atlas_root = Path(tmpdir) @@ -7439,16 +7896,46 @@ def smoke_krea2_tuning_report_policy() -> None: _expect("seed 7302" in markdown, "Krea2 tuning report markdown lost evidence seed") _expect("generator_patch" in markdown, "Krea2 tuning report markdown lost evidence decision") _expect("upright frontal boobjob geometry" in markdown, "Krea2 tuning report markdown lost evidence prompt summary") + _expect("ballsucking-7272727272-flat-target-hybrid-weak-case" in markdown, "Krea2 tuning report markdown lost ballsucking fresh flat-target hybrid weak-case evidence id") + _expect("flat-valley scrotal-skin" in markdown and "shaft-centered" in markdown, "Krea2 tuning report markdown lost ballsucking fresh flat-target hybrid weak-case evidence") + _expect("footjob-7373737373-generated-route-repeat-proven" in markdown, "Krea2 tuning report markdown lost footjob fresh-seed proven evidence id") + _expect("two large overlapping soles" in markdown, "Krea2 tuning report markdown lost overlapping-sole footjob evidence") + _expect("narrow visible strip of shaft and glans" in markdown, "Krea2 tuning report markdown lost visible-strip footjob evidence") + _expect("fingering-1357913579-source52-own-hand-weak-case" in markdown, "Krea2 tuning report markdown lost fingering source-52 weak-case evidence id") + _expect("changed hand ownership" in markdown, "Krea2 tuning report markdown lost fingering hand-ownership weak-case evidence") + _expect("provisional_generator_patch" in markdown, "Krea2 tuning report markdown lost provisional generator decision") + _expect("wand-7979797979-two-woman-teal-repeat-proven" in markdown, "Krea2 tuning report markdown lost wand evidence id") + _expect("single continuous teal wand" in markdown, "Krea2 tuning report markdown lost wand single-device evidence") + _expect("ready-1123581321-source52-wet-aftermath-hierarchy" in markdown, "Krea2 tuning report markdown lost ready aftermath evidence id") + _expect("wet aftermath hierarchy" in markdown, "Krea2 tuning report markdown lost ready wet hierarchy evidence") + _expect("spread-3141592653-source50-47-raised-knee-v-frame" in markdown, "Krea2 tuning report markdown lost spread evidence id") + _expect("atlas spread hierarchy" in markdown, "Krea2 tuning report markdown lost spread atlas hierarchy evidence") + _expect("blowjob-top-4242424242-turn67-70-nadir-floor-plane-axis" in markdown, "Krea2 tuning report markdown lost blowjob top-view nadir evidence id") + _expect("nadir-angle" in markdown, "Krea2 tuning report markdown lost blowjob top-view nadir evidence") + _expect("blowjob-side-5858585858-three-woman-generated-route-proven" in markdown, "Krea2 tuning report markdown lost blowjob side three-woman generated-route evidence id") + _expect("adult male viewer's own torso starts at the lower edge" in markdown, "Krea2 tuning report markdown lost side-profile self-torso ownership wording") + _expect("three-woman generated-route repeat" in markdown, "Krea2 tuning report markdown lost side-profile promotion observation") + _expect("lower-right torso anchor" in markdown, "Krea2 tuning report markdown lost side-profile lower-right torso wording") + _expect("blowjob-laying-6767676767-source46-50-wide-v-frame" in markdown, "Krea2 tuning report markdown lost blowjob laying evidence id") + _expect("wide V-frame and low-horizontal torso hierarchy" in markdown, "Krea2 tuning report markdown lost blowjob laying hierarchy wording") + _expect("blowjob-sitting-7878787878-source46-50-low-mouth-contact" in markdown, "Krea2 tuning report markdown lost blowjob sitting evidence id") + _expect("low-mouth seated hierarchy" in markdown, "Krea2 tuning report markdown lost blowjob sitting hierarchy wording") + _expect("missionary-open-8989898989-turn81-84-elevated-edge-support" in markdown, "Krea2 tuning report markdown lost missionary elevated-edge evidence id") + _expect("flat elevated-support" in markdown, "Krea2 tuning report markdown lost missionary elevated-support evidence") _expect("## Stronger Control Cases" in markdown, "Krea2 tuning report markdown lost stronger-control section") _expect("hardest" in markdown, "Krea2 tuning report markdown lost hardest-route marker") _expect("low priority" in markdown, "Krea2 tuning report markdown lost low-priority marker") _expect("pose_or_image_guidance_first" in markdown, "Krea2 tuning report markdown lost control-first marker") - _expect("## Eval Entry Template Commands" in markdown, "Krea2 tuning report markdown lost eval template command section") - _expect("python tools/krea2_record_eval.py --print-template" in markdown, "Krea2 tuning report markdown lost recorder template command") - _expect("--seed " in markdown, "Krea2 tuning report markdown lost fixed-seed placeholder") + _expect("## Guide/Fragile Evidence Expansion" in markdown, "Krea2 tuning report markdown lost guide expansion section") + _expect("multi_seed_multi_woman_matrix" in markdown, "Krea2 tuning report markdown lost expansion target") + _expect("## Eval Entry Template Commands" not in markdown, "Krea2 tuning report should omit eval template commands when no normal next tests remain") + _expect( + "python tools/krea2_record_eval.py --print-template" not in markdown, + "Krea2 tuning report should omit recorder template commands when no normal next tests remain", + ) + _expect("--seed " not in markdown, "Krea2 tuning report should omit fixed-seed placeholder when no normal next tests remain") _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_ejaculation_aftermath_open_thigh_candidate" in markdown, "Krea2 tuning report markdown lost ready aftermath candidate variant") _expect("pov_sixty_nine_close_reversed_oral" in markdown, "Krea2 tuning report markdown lost sixty-nine unstable variant") @@ -7459,14 +7946,24 @@ def smoke_krea2_tuning_report_policy() -> None: _expect("pov_blowjob_laying_frontal_oral" in markdown, "Krea2 tuning report markdown lost blowjob laying candidate variant") _expect("pov_blowjob_sitting_upright_oral" in markdown, "Krea2 tuning report markdown lost blowjob sitting candidate variant") _expect("pov_missionary_open_leg_penetration" in markdown, "Krea2 tuning report markdown lost missionary candidate variant") + cli_result = subprocess.run( + [sys.executable, str(ROOT / "krea2_tuning_report.py")], + cwd=ROOT, + capture_output=True, + text=True, + check=True, + ) + _expect("# Krea2 Pose Variant Coverage" in cli_result.stdout, "Krea2 tuning report CLI should print markdown coverage") + _expect("## Next Fixed-Seed Tests" not in cli_result.stdout, "Krea2 tuning report CLI should omit next fixed-seed tests when none remain") _expect("pov_missionary_folded_high_leg_penetration" in markdown, "Krea2 tuning report markdown lost folded missionary candidate variant") _expect("pov_cowgirl_frontal_straddle_penetration" in markdown, "Krea2 tuning report markdown lost cowgirl candidate variant") + _expect("cowgirl-frontal-9191919191-fresh-seed-wide-thigh-proven" in markdown, "Krea2 tuning report markdown lost cowgirl fresh-seed promotion evidence id") _expect("pov_cowgirl_alt_low_squat_penetration" in markdown, "Krea2 tuning report markdown lost cowgirl alt candidate variant") _expect("pov_reverse_cowgirl_back_facing_penetration" in markdown, "Krea2 tuning report markdown lost reverse cowgirl candidate variant") _expect("pov_reverse_cowgirl_alt_upright_back_facing_penetration" in markdown, "Krea2 tuning report markdown lost reverse cowgirl alt 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") + _expect("needs_fixed_seed_tests" not in markdown, "Krea2 tuning report should not list normal fixed-seed gaps after reverse-alt evidence") + _expect("Prompt cues" not in markdown, "Krea2 tuning report should omit next-test cue section when no normal tests remain") + _expect("Avoid cues" not in markdown, "Krea2 tuning report should omit next-test avoid section when no normal tests remain") def smoke_krea_pov_penetration_route() -> None: @@ -7534,8 +8031,21 @@ def smoke_pov_outercourse_position_routes() -> None: ( "pov_outercourse_testicle", "testicle_sucking", - ("chest low over the pov viewer's pelvis", "face below the pov viewer's penis at testicle height", "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"), + ( + "cheek against the pov viewer's inner thigh", + "scrotum is the mouth surface", + "testicles resting across her open lips", + "face is the closest visible partner part", + ), + ( + "low side-pelvis pov", + "face is the closest visible partner part", + "cheek against the viewer's inner thigh", + "scrotum is the mouth surface", + "scrotal skin is the nearest mouth surface", + "testicles resting across her open lips", + "both testicles rest against her tongue from below", + ), ), ( "pov_outercourse_penis_licking", @@ -7552,8 +8062,19 @@ def smoke_pov_outercourse_position_routes() -> None: ( "pov_outercourse_footjob", "footjob", - ("both soles wrapped around the pov viewer's penis", "lower foreground"), - ("soles wrap around", "penis shaft", "lower foreground"), + ( + "two large overlapping soles dominate the pov viewer's lower center foreground", + "inner arches press inward", + "toes curl around both edges", + "narrow visible strip of shaft and glans rises between the compressed feet", + ), + ( + "two large overlapping soles dominate the lower center foreground", + "inner arches press inward", + "toes curl around both edges", + "narrow visible strip of shaft and glans rises between the compressed feet", + "face and torso stay visible behind the large foreground feet", + ), ), ] for offset, (name, position_key, role_terms, krea_terms) in enumerate(cases, start=3601): @@ -7602,6 +8123,69 @@ def smoke_pov_outercourse_position_routes() -> None: _expect("behind the penis shaft" not in prompt, f"{name} Krea prompt kept vague shaft-only wording: {prompt}") if position_key == "testicle_sucking": _expect("head is tucked under the penis shaft" not in prompt, f"{name} Krea prompt kept high-head testicle wording: {prompt}") + if position_key == "footjob": + _expect(" only " not in f" {prompt} ", f"{name} Krea prompt kept limiting-only wording: {prompt}") + + prone_item = "reclining penis-licking position while slow tongue licking on the underside of the penis" + prone_axis = {"position": "reclining penis-licking position"} + prone_role_graph = hardcore_role_outercourse.build_outercourse_role_graph( + "Woman A", + "Man A", + prone_item, + prone_axis, + pov_labels=["Man A"], + ).lower() + for term in ( + "belly-down", + "wide v-frame", + "torso stretched low and horizontal", + "hands wrap the base", + ): + _expect(term in prone_role_graph, f"Prone frontal penis-licking role graph missing {term!r}: {prone_role_graph}") + _expect("head low under" not in prone_role_graph, f"Prone frontal penis-licking role graph kept under-shaft wording: {prone_role_graph}") + + prone_pair = pb.build_insta_of_pair( + row_number=1, + start_index=1, + seed=3717, + 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=_position_filter("outercourse_only", "outercourse", ["penis_licking"]), + location_config=_coworking_location_config(), + hardcore_camera_config=_orbit_camera( + horizontal_angle=45, + vertical_angle=0, + zoom=7.5, + subject_focus="action", + ), + ) + prone_row = prone_pair["hardcore_row"] + prone_row["item"] = prone_item + prone_row["item_axis_values"] = prone_axis + prone_row["position_key"] = "penis_licking" + prone_row["position_keys"] = ["reclining_oral", "penis_licking"] + prone_row["source_role_graph"] = prone_role_graph + prone_krea = krea_formatter.format_krea2_prompt("", metadata_json=_json(prone_pair), target="hardcore") + prone_prompt = _expect_text("pov_outercourse_prone_frontal_penis_licking.krea_prompt", prone_krea.get("krea_prompt"), 60).lower() + for term in ( + "pov prone frontal oral position", + "wide symmetrical v-frame", + "torso stretched low and horizontal", + "hands wrap the base", + "centered mouth-to-shaft contact", + ): + _expect(term in prone_prompt, f"Prone frontal penis-licking Krea prompt missing {term!r}: {prone_prompt}") + _expect("head low under the viewer's penis" not in prone_prompt, f"Prone frontal penis-licking Krea prompt kept under-shaft wording: {prone_prompt}") def smoke_pov_oral_position_routes() -> None: @@ -7610,7 +8194,7 @@ def smoke_pov_oral_position_routes() -> None: "pov_oral_kneeling", "kneeling", ("viewer's penis", "takes the viewer's penis in her mouth"), - ("pov kneeling oral position", "viewer stands over her", "head is at penis height", "thighs framing"), + ("nadir-angle standing male pov top-view oral position", "short centered vertical column", "top-down office anchors"), ), ( "pov_oral_face_sitting", @@ -7689,9 +8273,142 @@ def smoke_pov_oral_position_routes() -> None: f"{name} Krea prompt repeated oral contact detail after POV rewrite: {prompt}", ) _expect("edge-supported oral position;" not in prompt, f"{name} Krea prompt kept source oral-position scaffold: {prompt}") + if position_key == "kneeling": + for term in ( + "nadir-angle standing male pov top-view oral position", + "viewer looks almost straight down from his torso toward the floor", + "nearby carpet/floor plane dominating the image", + "short centered vertical column", + "directly below the viewer between his feet", + "hair crown, forehead, shoulders, hands, knees", + "top-down office anchors", + ): + _expect(term in prompt, f"{name} Krea prompt lost top-down oral hierarchy term {term!r}: {prompt}") + _expect("plumb-line" not in prompt and "map" not in prompt, f"{name} Krea prompt kept literalized top-view wording: {prompt}") for term in krea_terms: _expect(term in prompt, f"{name} Krea prompt missing {term!r}: {prompt}") + side_body_item = "side-lying oral position while blowjob with lips wrapped around the viewer's penis" + side_body_axis = {"position": "side-lying oral position"} + side_body_role_graph = hardcore_role_oral.build_oral_role_graph( + "Woman A", + "Man A", + side_body_item, + side_body_axis, + pov_labels=["Man A"], + ).lower() + for term in ( + "adult male viewer's abdomen, navel, pelvis, and near thigh", + "broad horizontal body surface", + "woman a enters laterally from the left edge", + "mouth on the shaft at the male abdomen line", + "lips touching the shaft at the male abdomen line", + "mouth-to-shaft contact is the nearest facial detail", + "adult male viewer's own torso starts at the lower edge", + "lower-right foreground", + "camera owner's body", + "hand around the base under her lips", + ): + _expect(term in side_body_role_graph, f"Side-profile oral role graph missing {term!r}: {side_body_role_graph}") + + side_body_pair = pb.build_insta_of_pair( + row_number=1, + start_index=1, + seed=3729, + 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=_position_filter("oral_only", "oral", ["side_lying"]), + location_config=_coworking_location_config(), + hardcore_camera_config=_orbit_camera( + horizontal_angle=45, + vertical_angle=0, + zoom=7.5, + subject_focus="action", + ), + ) + side_body_row = side_body_pair["hardcore_row"] + side_body_row["item"] = side_body_item + side_body_row["item_axis_values"] = side_body_axis + side_body_row["position_key"] = "side_lying" + side_body_row["position_keys"] = ["side_lying", "blowjob_side"] + side_body_row["source_role_graph"] = side_body_role_graph + side_body_row["role_graph"] = side_body_role_graph + side_body_krea = krea_formatter.format_krea2_prompt("", metadata_json=_json(side_body_pair), target="hardcore") + side_body_prompt = _expect_text("pov_oral_side_profile_body_line.krea_prompt", side_body_krea.get("krea_prompt"), 80).lower() + for term in ( + "pov side-profile oral body-line position", + "male viewer's abdomen, navel, pelvis, and near thigh create a broad horizontal body surface", + "woman enters laterally from the left edge beside his hip", + "mouth on the shaft at the male abdomen line", + "lips touching the shaft at the male abdomen line", + "mouth-to-shaft contact is the nearest facial detail", + "adult male viewer's own torso starts at the lower edge", + "lower-right foreground", + "camera owner's body", + "hand around the base under her lips", + ): + _expect(term in side_body_prompt, f"Side-profile oral Krea prompt missing {term!r}: {side_body_prompt}") + _expect("side-phone" not in side_body_prompt, f"Side-profile oral Krea prompt drifted into non-POV side-phone wording: {side_body_prompt}") + + sitting_pair = pb.build_insta_of_pair( + row_number=1, + start_index=1, + seed=3731, + 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=_position_filter("oral_only", "oral", ["reclining_oral"]), + location_config=_coworking_location_config(), + hardcore_camera_config=_orbit_camera( + horizontal_angle=45, + vertical_angle=0, + zoom=7.5, + subject_focus="action", + ), + ) + sitting_row = sitting_pair["hardcore_row"] + sitting_role_graph = ( + "Woman A sits upright between Man A's open thighs in a first-person sitting oral frame; " + "her face lowers close to the shaft tip while her hands stay low at the base." + ) + sitting_row["item"] = "upright sitting oral position with her mouth on the viewer's penis" + sitting_row["item_axis_values"] = {"position": "upright sitting oral position"} + sitting_row["position_key"] = "reclining_oral" + sitting_row["position_keys"] = ["reclining_oral", "blowjob_sitting"] + sitting_row["source_role_graph"] = sitting_role_graph + sitting_row["role_graph"] = sitting_role_graph + sitting_krea = krea_formatter.format_krea2_prompt("", metadata_json=_json(sitting_pair), target="hardcore") + sitting_prompt = _expect_text("pov_oral_sitting_upright.krea_prompt", sitting_krea.get("krea_prompt"), 80).lower() + for term in ( + "pov upright sitting oral position", + "woman sits low between his open thighs", + "face lowered close to the exact center contact point", + "open mouth covers the tip", + "hands stay low at the base", + ): + _expect(term in sitting_prompt, f"Upright sitting oral Krea prompt missing {term!r}: {sitting_prompt}") + _expect("pov prone frontal oral position" not in sitting_prompt, f"Upright sitting oral drifted into laying route: {sitting_prompt}") + _expect("pov kneeling oral position" not in sitting_prompt, f"Upright sitting oral drifted into top-view kneeling route: {sitting_prompt}") + def smoke_pov_climax_target_policy() -> None: context = ( @@ -7713,6 +8430,32 @@ def smoke_pov_climax_target_policy() -> None: ).lower() _expect("face and chest" not in phrase, f"POV rear-entry climax kept incompatible face/chest target: {phrase}") _expect("across her ass, thighs, and lower back" in phrase, f"POV rear-entry climax lost rear target: {phrase}") + open_thigh_phrase = krea_pov_actions.pov_action_phrase( + ( + "Woman A lies on her back with thighs open while Man A kneels between her legs " + "and ejaculates semen across her pussy and thighs; thick visible fluid covers the exposed opening" + ), + ["Man A"], + role_graph=( + "Woman A lies on her back with thighs open toward Man A after ejaculation, " + "with semen and clear fluid covering the exposed pussy and inner thighs." + ), + hard_item="post-ejaculation open-thigh display with thick semen or fluid around the exposed opening", + composition="first-person open-thigh aftermath frame with her face and torso visible behind the wet detail", + axis_values={"position": "reclining with thighs open"}, + ).lower() + _expect( + "pov post-ejaculation open-thigh display" in open_thigh_phrase, + f"POV open-thigh climax should use aftermath display wording: {open_thigh_phrase}", + ) + _expect( + "wet aftermath detail is the exact center" in open_thigh_phrase, + f"POV open-thigh climax lost centered wet aftermath hierarchy: {open_thigh_phrase}", + ) + _expect( + "missionary position" not in open_thigh_phrase and "penetrative sex position" not in open_thigh_phrase, + f"POV open-thigh climax should not read as active penetration: {open_thigh_phrase}", + ) def smoke_pov_penetration_position_routes() -> None: @@ -7723,17 +8466,73 @@ def smoke_pov_penetration_position_routes() -> None: ("woman a lies on her back", "man a is above her between her thighs"), ("pov missionary position", "viewer is above her", "penetrates her pussy"), ), + ( + "pov_penetration_missionary_folded", + "missionary_folded", + ("woman a lies on her back", "knees folded high toward her chest", "penis thrusts into her pussy"), + ( + "pov folded missionary high-leg penetration position", + "large centered shaft rising from the lower center", + "compact knee block above the contact", + "penetrates her pussy", + ), + ), ( "pov_penetration_cowgirl", "cowgirl", - ("woman a straddles man a's hips facing him", "man a lies under her"), - ("pov cowgirl position", "viewer lies on his back", "woman straddles his hips"), + ( + "woman a straddles man a's hips facing him", + "man a lies under her", + "wide horizontal thigh bridge", + "man a's hands grip the sides of her thighs", + ), + ( + "pov frontal cowgirl wide-thigh bridge position", + "viewer reclines underneath her", + "wide horizontal thigh bridge", + "knees planted outside the viewer's hips", + "viewer hands grip the sides of her thighs", + "centered contact remains below her belly", + ), + ), + ( + "pov_penetration_cowgirl_alt", + "cowgirl_alt", + ("woman a faces man a in a low seated squat", "man a lies flat on his back", "man a supports the underside of her thighs"), + ( + "pov low cowgirl seated-squat penetration position", + "viewer lies flat on his back", + "lens sits low at the viewer's abdomen", + "high room background behind her upper body", + "viewer supports the underside of her thighs", + "knees bent wide and close to the camera", + "penetrates her pussy", + ), ), ( "pov_penetration_reverse_cowgirl", "reverse_cowgirl", ("woman a straddles man a's hips facing away", "man a lies under her"), - ("pov reverse cowgirl position", "facing away", "viewer lies on his back"), + ( + "pov reverse cowgirl position", + "facing away", + "viewer lies on his back", + "back, hips, and ass are the nearest largest shapes", + "viewer thighs frame the lower corners", + "centered contact sits directly between her thighs below her ass", + ), + ), + ( + "pov_penetration_reverse_cowgirl_alt", + "reverse_cowgirl_alt", + ("woman a sits upright facing away", "man a lies under her", "man a's penis thrusts into her pussy"), + ( + "pov upright reverse cowgirl back-facing penetration position", + "back stays vertical and readable", + "viewer hands hold her hips", + "viewer thighs frame the lower corners", + "centered contact remains visible below her ass", + ), ), ( "pov_penetration_doggy", @@ -7745,7 +8544,7 @@ def smoke_pov_penetration_position_routes() -> None: "pov_penetration_edge_supported", "edge_supported", ("raised edge", "man a kneels between her thighs"), - ("pov raised-edge penetration position", "viewer kneels between her legs", "penetrates her pussy"), + ("pov elevated-edge missionary position", "flat elevated support", "hands holding her calves or outer thighs", "penetrates her pussy"), ), ( "pov_penetration_lotus", @@ -8022,8 +8821,8 @@ def smoke_interaction_role_graph_routes() -> None: _character_cast(), 1, 1, - ("reclines with thighs open", "fingers visibly stimulating"), - ("fingers visibly stimulating", "between her legs"), + ("foreground hand is the largest lower-frame object", "open thighs form a v", "wrist enters from the bottom center"), + ("foreground hand is the largest lower-frame object", "two fingers at her vulva and clit"), ), ( "interaction_clothing_transition", @@ -8121,6 +8920,131 @@ def smoke_interaction_role_graph_routes() -> None: _expect(term in prompt, f"{name} Krea prompt missing {term!r}: {prompt}") _expect_formatter_outputs(row, name, target="single") + wand_role_graph = hardcore_role_interaction.build_manual_role_graph( + "Woman A", + "Man A", + item_text="toy-assisted manual stimulation with one hand controlling the toy", + item_axis_values={ + "position": "reclining open-thigh manual position", + "manual_act": "partner-held vibrator pressed to the clit", + "manual_detail": "toy and fingers both visible at the contact point", + }, + ) + wand_role_lower = wand_role_graph.lower() + for term in ( + "single continuous teal wand-style massager", + "rounded bulb head presses flat", + "smooth handle angles in from the bottom right", + "open thighs and knees form a v", + ): + _expect(term in wand_role_lower, f"manual wand role graph missing {term!r}: {wand_role_lower}") + wand_row = _fixture_hardcore_row( + subcategory="Manual stimulation", + subcategory_slug="manual_stimulation", + item="toy-assisted manual stimulation with one hand controlling the toy, partner-held vibrator pressed to the clit", + custom_item="Manual stimulation", + item_axis_values={ + "position": "reclining open-thigh manual position", + "manual_act": "partner-held vibrator pressed to the clit", + "manual_detail": "toy and fingers both visible at the contact point", + }, + scene_text=( + "coworking lounge with tall windows, warm desks, laptop tables, " + "glass partition seams, repeated desk rows, plants, and soft shared-office depth" + ), + composition="close first-person office-chair toy-contact frame", + source_composition="close first-person office-chair toy-contact frame", + role_graph=wand_role_graph, + source_role_graph=wand_role_graph, + action_family="manual", + position_family="manual", + position_key="wand", + position_keys=["wand", "toy_contact", "open_thighs"], + ) + wand_krea = krea_formatter.format_krea2_prompt("", metadata_json=_json(wand_row), target="single") + wand_prompt = _expect_text("interaction_wand_toy_contact.krea_prompt", wand_krea.get("krea_prompt"), 80).lower() + for term in ( + "single continuous teal wand-style massager", + "rounded bulb head presses flat", + "smooth handle angles in from the bottom right", + "open thighs and knees form a v", + "face and torso remain visible behind", + ): + _expect(term in wand_prompt, f"manual wand Krea prompt missing {term!r}: {wand_prompt}") + + generated_wand_row = _prompt_row( + name="interaction_generated_wand_toy_contact", + category="Hardcore sexual poses", + subcategory="Manual stimulation", + seed=46, + character_cast=_character_cast(pov_man=True), + women_count=1, + men_count=1, + hardcore_position_config=_position_filter("manual_only", "manual", ["fingering"]), + location_config=_coworking_location_config(), + ) + generated_wand_krea = krea_formatter.format_krea2_prompt("", metadata_json=_json(generated_wand_row), target="single") + generated_wand_prompt = _expect_text( + "interaction_generated_wand_toy_contact.krea_prompt", + generated_wand_krea.get("krea_prompt"), + 80, + ).lower() + for term in ( + "single continuous teal wand-style massager", + "rounded bulb head presses flat", + "smooth handle angles in from the bottom right", + "open thighs and knees form a v", + ): + _expect(term in generated_wand_prompt, f"generated manual wand Krea prompt missing {term!r}: {generated_wand_prompt}") + + spread_role_graph = hardcore_role_interaction.build_interaction_role_graph( + "Woman A", + "Man A", + slug="camera_performance", + item_text="spread open for camera in a reclining camera-presentation position", + item_axis_values={ + "position": "reclining camera-presentation position", + "performance_act": "spread open for camera", + "presentation_detail": "open thighs and knees held wide", + "hand_detail": "hands holding knees", + }, + ) + spread_role_lower = spread_role_graph.lower() + for term in ("knees raised", "broad v-frame", "hands hold her knees", "face and torso remain visible"): + _expect(term in spread_role_lower, f"Spread presentation role graph missing {term!r}: {spread_role_lower}") + spread_row = _fixture_hardcore_row( + subcategory="Camera performance", + subcategory_slug="camera_performance", + item="spread open for camera in a reclining camera-presentation position, open thighs and knees held wide, hands holding knees", + custom_item="Camera performance", + item_axis_values={ + "position": "reclining camera-presentation position", + "performance_act": "spread open for camera", + "presentation_detail": "open thighs and knees held wide", + "hand_detail": "hands holding knees", + }, + scene_text=( + "coworking lounge with tall windows, warm desks, laptop tables, " + "glass partition seams, repeated desk rows, plants, and soft shared-office depth" + ), + composition="close seated camera frame in a coworking lounge", + source_composition="close seated camera frame in a coworking lounge", + role_graph=spread_role_graph, + source_role_graph=spread_role_graph, + action_family="foreplay", + position_family="interaction", + position_key="open_thighs", + position_keys=["camera_showing", "open_thighs"], + ) + _expect_custom_row(spread_row, "interaction_spread_open_thigh_presentation") + _expect(spread_row.get("position_family") == "interaction", "Spread presentation position_family should be interaction") + _expect("camera_showing" in (spread_row.get("position_keys") or []), "Spread presentation lost camera_showing key") + _expect("open_thighs" in (spread_row.get("position_keys") or []), "Spread presentation lost open_thighs key") + spread_krea = krea_formatter.format_krea2_prompt("", metadata_json=_json(spread_row), target="single") + spread_prompt = _expect_text("interaction_spread_open_thigh_presentation.krea_prompt", spread_krea.get("krea_prompt"), 80).lower() + _expect("metadata" in spread_krea.get("method", ""), "Spread presentation Krea did not use metadata") + for term in ("knees raised", "broad v-frame", "hands hold her knees", "office chair seat and chair arms"): + _expect(term in spread_prompt, f"Spread presentation Krea prompt missing {term!r}: {spread_prompt}") def smoke_fallback_role_graph_routes() -> None: cases = [ @@ -8292,22 +9216,28 @@ def smoke_formatter_metadata_fixtures() -> None: "position": "kneeling hand-between-thighs position", "manual_act": "wet fingers moving between the thighs", }, - composition="close crop on hands and face", - source_composition="close crop on hands and face", + scene_text=( + "coworking lounge with tall windows, warm desks, laptop tables, " + "glass partition seams, repeated desk rows, plants, and soft shared-office depth" + ), + composition="close crop on hands and face in a coworking lounge", + source_composition="close crop on hands and face in a coworking lounge", role_graph=( - "Woman A reclines with thighs open while Man A's hand is between her legs, " - "fingers visibly stimulating her pussy." + "Woman A reclines with thighs open while Man A's foreground hand is the largest lower-frame object; " + "her open thighs form a V around the hand, the wrist enters from the bottom center, " + "and two fingers at her vulva and clit make the central manual-contact point while her face and torso remain visible behind the open thighs." ), source_role_graph=( - "Woman A reclines with thighs open while Man A's hand is between her legs, " - "fingers visibly stimulating her pussy." + "Woman A reclines with thighs open while Man A's foreground hand is the largest lower-frame object; " + "her open thighs form a V around the hand, the wrist enters from the bottom center, " + "and two fingers at her vulva and clit make the central manual-contact point while her face and torso remain visible behind the open thighs." ), action_family="manual", position_family="manual", position_key="fingering", position_keys=["fingering", "open_thighs"], ), - "krea_terms": ("fingers visibly stimulating",), + "krea_terms": ("foreground hand is the largest lower-frame object", "office chair seat and chair arms"), "sdxl_terms": ("manual stimulation", "fingering"), "caption_terms": ("manual action",), }, @@ -8338,6 +9268,45 @@ def smoke_formatter_metadata_fixtures() -> None: "sdxl_terms": ("climax", "semen"), "caption_terms": ("climax action",), }, + { + "name": "fixture_climax_open_thigh_aftermath", + "row": _fixture_hardcore_row( + subcategory="Cumshot and climax", + subcategory_slug="cumshot_climax", + item=( + "post-ejaculation open-thigh display with thick semen and clear fluid " + "around the exposed opening, body still after ejaculation" + ), + custom_item="Cumshot and climax", + item_axis_values={"position": "reclining with thighs open"}, + scene_text=( + "coworking lounge with tall windows, warm desks, laptop tables, " + "glass partition seams, repeated desk rows, plants, and soft shared-office depth" + ), + composition="first-person open-thigh aftermath frame in a coworking lounge", + source_composition="first-person open-thigh aftermath frame in a coworking lounge", + role_graph=( + "Woman A lies on her back with thighs open toward Man A after ejaculation, " + "with semen and clear fluid covering the exposed pussy and inner thighs." + ), + source_role_graph=( + "Woman A lies on her back with thighs open toward Man A after ejaculation, " + "with semen and clear fluid covering the exposed pussy and inner thighs." + ), + action_family="climax", + position_family="climax", + position_key="open_thighs", + position_keys=["open_thighs"], + pov_character_labels=["Man A"], + ), + "krea_terms": ( + "pov post-ejaculation open-thigh display", + "wet aftermath detail is the exact center", + "office chair seat and chair arms", + ), + "sdxl_terms": ("climax", "semen"), + "caption_terms": ("climax action",), + }, ] for case in cases: name = case["name"] @@ -9172,6 +10141,384 @@ def smoke_prompt_route_simulation_policy() -> None: _expect((sweep_quality.get("targets") or {}).get("hardcore", {}).get("cases") == 9, "Prompt route simulation sweep quality lost hardcore target count") +def smoke_sxcp_mcp_client_cli_policy() -> None: + helper_path = ROOT / "tools" / "sxcp_mcp_client.py" + eval_loop_doc = (ROOT / "docs" / "sxcp-eval-loop.md").read_text(encoding="utf-8") + _expect("## MCP Helper Command" in eval_loop_doc, "SxCP eval loop doc lost MCP helper command memory") + _expect( + "/media/p5/miniforge3/bin/python tools/sxcp_mcp_client.py call-tool comfy_push" in eval_loop_doc, + "SxCP eval loop doc lost approved MCP push helper command", + ) + _expect("sxcp_eval_out" in eval_loop_doc, "SxCP eval loop doc lost output channel name") + top_help = subprocess.run( + [sys.executable, str(helper_path), "--help"], + cwd=ROOT, + capture_output=True, + text=True, + check=False, + ) + _expect(top_help.returncode == 0, f"sxcp MCP client --help failed: {top_help.stderr}") + help_text = top_help.stdout + _expect("list-tools" in help_text, "sxcp MCP client help lost list-tools command") + _expect("call-tool" in help_text, "sxcp MCP client help lost call-tool command") + _expect("--bridge-url" in help_text, "sxcp MCP client help lost bridge URL option") + call_help = subprocess.run( + [sys.executable, str(helper_path), "call-tool", "--help"], + cwd=ROOT, + capture_output=True, + text=True, + check=False, + ) + _expect(call_help.returncode == 0, f"sxcp MCP client call-tool --help failed: {call_help.stderr}") + _expect("--arguments-json" in call_help.stdout, "sxcp MCP client help lost JSON argument option") + + +def smoke_sxcp_prompt_batch_cli_policy() -> None: + helper_path = ROOT / "tools" / "sxcp_prompt_batch.py" + eval_loop_doc = (ROOT / "docs" / "sxcp-eval-loop.md").read_text(encoding="utf-8") + methodology = (ROOT / "docs" / "krea2-ab-methodology.md").read_text(encoding="utf-8") + _expect("tools/sxcp_prompt_batch.py" in eval_loop_doc, "SxCP eval loop doc lost prompt batch helper") + _expect("print-push-commands" in eval_loop_doc, "SxCP eval loop doc lost prompt batch command rendering") + _expect("print-result-template" in eval_loop_doc, "SxCP eval loop doc lost prompt batch result template") + _expect("run-batch" in eval_loop_doc, "SxCP eval loop doc lost prompt batch runner") + _expect("validate-results" in eval_loop_doc, "SxCP eval loop doc lost prompt batch result validation") + _expect("print-eval-entry-draft" in eval_loop_doc, "SxCP eval loop doc lost prompt batch eval-entry draft") + _expect("--allow-geometry-only" in eval_loop_doc, "SxCP eval loop doc lost geometry-only draft guard") + _expect("tools/sxcp_prompt_batch.py" in methodology, "Krea2 A/B methodology lost prompt batch helper memory") + _expect("run-batch" in methodology, "Krea2 A/B methodology lost prompt batch runner") + _expect("validate-results" in methodology, "Krea2 A/B methodology lost prompt batch result validation") + _expect("print-eval-entry-draft" in methodology, "Krea2 A/B methodology lost prompt batch eval-entry draft memory") + _expect("--allow-geometry-only" in methodology, "Krea2 A/B methodology lost geometry-only draft guard") + batch = { + "seed": 123456789, + "channel_out": "sxcp_eval_out", + "channel_in": "sxcp_eval_in", + "probes": [ + { + "id": "subject_first_axis", + "prompt_order": "subject_first", + "text": "A 24-year-old adult woman with long wavy brunette hair. Subject-first controlled pose wording.", + }, + { + "id": "geometry_only_axis", + "prompt_order": "geometry_only", + "text": "Geometry-only floor-plane wording for rough pose-axis discovery.", + }, + ], + } + with tempfile.NamedTemporaryFile("w", encoding="utf-8", suffix=".json", delete=False) as handle: + json.dump(batch, handle) + batch_path = Path(handle.name) + try: + commands = subprocess.run( + [sys.executable, str(helper_path), "print-push-commands", "--batch-json", str(batch_path)], + cwd=ROOT, + capture_output=True, + text=True, + check=False, + ) + _expect(commands.returncode == 0, f"sxcp prompt batch command rendering failed: {commands.stderr}") + _expect("tools/sxcp_mcp_client.py call-tool comfy_push" in commands.stdout, "sxcp prompt batch lost MCP push helper command") + _expect('"channel":"sxcp_eval_out"' in commands.stdout, "sxcp prompt batch lost positive output channel") + _expect('"seed":123456789' in commands.stdout, "sxcp prompt batch lost fixed sampler seed") + _expect("subject_first_axis" in commands.stdout and "geometry_only_axis" in commands.stdout, "sxcp prompt batch lost probe ids") + _expect("geometry-only" in commands.stdout, "sxcp prompt batch lost geometry-only caveat") + _expect("sxcp_eval_negative_out" not in commands.stdout, "sxcp prompt batch should not emit negative output channel") + + template = subprocess.run( + [sys.executable, str(helper_path), "print-result-template", "--batch-json", str(batch_path)], + cwd=ROOT, + capture_output=True, + text=True, + check=False, + ) + _expect(template.returncode == 0, f"sxcp prompt batch result template failed: {template.stderr}") + result_template = json.loads(template.stdout) + _expect(result_template.get("seed") == 123456789, "sxcp prompt batch result template lost seed") + _expect(result_template.get("channel_in") == "sxcp_eval_in", "sxcp prompt batch result template lost input channel") + probes = result_template.get("probes") or [] + _expect([probe.get("id") for probe in probes] == ["subject_first_axis", "geometry_only_axis"], "sxcp prompt batch result template lost probe order") + _expect(all("image_path" in probe and "turn" in probe for probe in probes), "sxcp prompt batch result template lost image presence fields") + + result_template["probes"][0].update( + { + "turn": 101, + "image_path": "/media/unraid/comfyui/output/agent_bridge/img_subject_first.png", + "returned_seed": 123456789, + } + ) + result_template["probes"][1].update( + { + "turn": 102, + "image_path": "/media/unraid/comfyui/output/agent_bridge/img_geometry_only.png", + "returned_seed": 123456789, + } + ) + with tempfile.NamedTemporaryFile("w", encoding="utf-8", suffix=".json", delete=False) as result_handle: + json.dump(result_template, result_handle) + result_path = Path(result_handle.name) + try: + validated_results = subprocess.run( + [ + sys.executable, + str(helper_path), + "validate-results", + "--batch-json", + str(batch_path), + "--result-json", + str(result_path), + ], + cwd=ROOT, + capture_output=True, + text=True, + check=False, + ) + _expect(validated_results.returncode == 0, f"sxcp prompt batch result validation failed: {validated_results.stderr}") + _expect("validated results: 2 probes, seed 123456789" in validated_results.stdout, "sxcp prompt batch result validation lost summary") + + draft = subprocess.run( + [ + sys.executable, + str(helper_path), + "print-eval-entry-draft", + "--batch-json", + str(batch_path), + "--result-json", + str(result_path), + "--variant-key", + "pov_missionary_open_leg_penetration", + "--entry-id", + "missionary-open-123456789-subject-first-axis", + "--baseline-image", + "/media/unraid/comfyui/output/agent_bridge/img_baseline.png", + "--candidate-id", + "subject_first_axis", + "--source", + "sxcp_eval_mcp_batch_smoke", + ], + cwd=ROOT, + capture_output=True, + text=True, + check=False, + ) + _expect(draft.returncode == 0, f"sxcp prompt batch eval-entry draft failed: {draft.stderr}") + entry = json.loads(draft.stdout) + _expect(entry.get("id") == "missionary-open-123456789-subject-first-axis", "sxcp prompt batch eval draft lost entry id") + _expect(entry.get("variant_key") == "pov_missionary_open_leg_penetration", "sxcp prompt batch eval draft lost variant key") + _expect(entry.get("seed") == 123456789, "sxcp prompt batch eval draft lost seed") + _expect(entry.get("baseline_image") == "/media/unraid/comfyui/output/agent_bridge/img_baseline.png", "sxcp prompt batch eval draft lost baseline image") + _expect(entry.get("candidate_image") == "/media/unraid/comfyui/output/agent_bridge/img_subject_first.png", "sxcp prompt batch eval draft lost selected candidate image") + _expect(entry.get("result") == "inconclusive" and entry.get("decision") == "needs_more_tests", "sxcp prompt batch eval draft should default to inconclusive needs_more_tests") + _expect("subject_first_axis" in entry.get("candidate_prompt_summary", ""), "sxcp prompt batch eval draft lost candidate probe id") + _expect("subject_first" in entry.get("observation", ""), "sxcp prompt batch eval draft lost prompt-order note") + errors = krea2_eval_log.validate_entry(entry, catalog_keys=set(krea2_pose_variant_catalog.variant_keys())) + _expect(errors == [], f"sxcp prompt batch eval draft should validate: {errors}") + + geometry_draft = subprocess.run( + [ + sys.executable, + str(helper_path), + "print-eval-entry-draft", + "--batch-json", + str(batch_path), + "--result-json", + str(result_path), + "--variant-key", + "pov_missionary_open_leg_penetration", + "--baseline-image", + "/media/unraid/comfyui/output/agent_bridge/img_baseline.png", + "--candidate-id", + "geometry_only_axis", + ], + cwd=ROOT, + capture_output=True, + text=True, + check=False, + ) + _expect(geometry_draft.returncode != 0, "sxcp prompt batch eval draft should reject geometry-only candidates by default") + _expect("geometry_only" in geometry_draft.stderr, "sxcp prompt batch geometry-only rejection should name prompt order") + + allowed_geometry_draft = subprocess.run( + [ + sys.executable, + str(helper_path), + "print-eval-entry-draft", + "--batch-json", + str(batch_path), + "--result-json", + str(result_path), + "--variant-key", + "pov_missionary_open_leg_penetration", + "--baseline-image", + "/media/unraid/comfyui/output/agent_bridge/img_baseline.png", + "--candidate-id", + "geometry_only_axis", + "--allow-geometry-only", + ], + cwd=ROOT, + capture_output=True, + text=True, + check=False, + ) + _expect(allowed_geometry_draft.returncode == 0, f"sxcp prompt batch should allow explicit geometry-only drafts: {allowed_geometry_draft.stderr}") + allowed_entry = json.loads(allowed_geometry_draft.stdout) + _expect("not treat as subject/look-controlled evidence" in allowed_entry.get("observation", ""), "sxcp prompt batch allowed geometry draft lost caveat") + + mock_pulls = [ + { + "turn": 201, + "image_path": "/media/unraid/comfyui/output/agent_bridge/img_subject_first_run.png", + "seed": 123456789, + }, + { + "turn": 201, + "image_path": "/media/unraid/comfyui/output/agent_bridge/img_stale_run.png", + "seed": 123456789, + }, + { + "turn": 202, + "image_path": "/media/unraid/comfyui/output/agent_bridge/img_geometry_run.png", + "seed": 123456789, + }, + ] + with tempfile.NamedTemporaryFile("w", encoding="utf-8", suffix=".json", delete=False) as mock_handle: + json.dump(mock_pulls, mock_handle) + mock_path = Path(mock_handle.name) + with tempfile.NamedTemporaryFile("w", encoding="utf-8", suffix=".json", delete=False) as run_result_handle: + run_result_path = Path(run_result_handle.name) + run_result_path.unlink(missing_ok=True) + try: + runner = subprocess.run( + [ + sys.executable, + str(helper_path), + "run-batch", + "--batch-json", + str(batch_path), + "--result-json", + str(run_result_path), + "--mock-pulls-json", + str(mock_path), + "--previous-turn", + "200", + "--max-polls", + "3", + ], + cwd=ROOT, + capture_output=True, + text=True, + check=False, + ) + _expect(runner.returncode == 0, f"sxcp prompt batch runner failed: {runner.stderr}") + _expect("recorded results: 2 probes, seed 123456789" in runner.stdout, "sxcp prompt batch runner lost result summary") + run_results = json.loads(run_result_path.read_text(encoding="utf-8")) + _expect( + [probe.get("turn") for probe in run_results.get("probes") or []] == [201, 202], + "sxcp prompt batch runner should skip stale turns and preserve batch order", + ) + _expect( + [probe.get("image_path") for probe in run_results.get("probes") or []] + == [ + "/media/unraid/comfyui/output/agent_bridge/img_subject_first_run.png", + "/media/unraid/comfyui/output/agent_bridge/img_geometry_run.png", + ], + "sxcp prompt batch runner lost selected image paths", + ) + validate_runner_results = subprocess.run( + [ + sys.executable, + str(helper_path), + "validate-results", + "--batch-json", + str(batch_path), + "--result-json", + str(run_result_path), + ], + cwd=ROOT, + capture_output=True, + text=True, + check=False, + ) + _expect(validate_runner_results.returncode == 0, f"sxcp prompt batch runner wrote invalid results: {validate_runner_results.stderr}") + finally: + mock_path.unlink(missing_ok=True) + run_result_path.unlink(missing_ok=True) + + bad_result = json.loads(json.dumps(result_template)) + bad_result["probes"][1]["returned_seed"] = 987654321 + with tempfile.NamedTemporaryFile("w", encoding="utf-8", suffix=".json", delete=False) as bad_result_handle: + json.dump(bad_result, bad_result_handle) + bad_result_path = Path(bad_result_handle.name) + try: + bad_validated_results = subprocess.run( + [ + sys.executable, + str(helper_path), + "validate-results", + "--batch-json", + str(batch_path), + "--result-json", + str(bad_result_path), + ], + cwd=ROOT, + capture_output=True, + text=True, + check=False, + ) + _expect(bad_validated_results.returncode != 0, "sxcp prompt batch result validation should reject returned seed mismatch") + _expect("returned_seed" in bad_validated_results.stderr, "sxcp prompt batch result validation should name returned_seed mismatch") + finally: + bad_result_path.unlink(missing_ok=True) + + bad_result = json.loads(json.dumps(result_template)) + bad_result["probes"][1]["id"] = "unexpected_axis" + with tempfile.NamedTemporaryFile("w", encoding="utf-8", suffix=".json", delete=False) as bad_result_handle: + json.dump(bad_result, bad_result_handle) + bad_result_path = Path(bad_result_handle.name) + try: + bad_validated_results = subprocess.run( + [ + sys.executable, + str(helper_path), + "validate-results", + "--batch-json", + str(batch_path), + "--result-json", + str(bad_result_path), + ], + cwd=ROOT, + capture_output=True, + text=True, + check=False, + ) + _expect(bad_validated_results.returncode != 0, "sxcp prompt batch result validation should reject probe order mismatch") + _expect("probe ids" in bad_validated_results.stderr, "sxcp prompt batch result validation should name probe ids mismatch") + finally: + bad_result_path.unlink(missing_ok=True) + finally: + result_path.unlink(missing_ok=True) + finally: + batch_path.unlink(missing_ok=True) + + bad_batch = dict(batch) + bad_batch["channel_out"] = "sxcp_eval_negative_out" + with tempfile.NamedTemporaryFile("w", encoding="utf-8", suffix=".json", delete=False) as handle: + json.dump(bad_batch, handle) + bad_batch_path = Path(handle.name) + try: + bad = subprocess.run( + [sys.executable, str(helper_path), "validate", "--batch-json", str(bad_batch_path)], + cwd=ROOT, + capture_output=True, + text=True, + check=False, + ) + _expect(bad.returncode != 0, "sxcp prompt batch should reject negative output channel") + _expect("sxcp_eval_negative_out" in bad.stderr, "sxcp prompt batch rejection should name the negative channel") + finally: + bad_batch_path.unlink(missing_ok=True) + + def smoke_node_camera_registration() -> None: required_nodes = [ "SxCPCameraControl", @@ -9513,6 +10860,13 @@ def smoke_node_hardcore_position_registration() -> None: "SxCPHardcorePositionPool", "SxCPHardcoreActionFilter", "SxCPKrea2PoseVariant", + "SxCPKrea2POVPenetrationFilter", + "SxCPKrea2POVOralFilter", + "SxCPKrea2POVOutercourseFilter", + "SxCPKrea2POVManualFilter", + "SxCPKrea2POVToyFilter", + "SxCPKrea2POVClimaxFilter", + "SxCPKrea2POVInteractionFilter", "SxCPKrea2VariantEvidence", ] for node_name in required_nodes: @@ -9575,6 +10929,63 @@ def smoke_node_hardcore_position_registration() -> None: _expect("torso bent forward" in avoid_cues, "Krea2 Pose Variant lost avoid cues output") _expect("variant=pov_boobjob_upright_cleavage" in variant_summary, "Krea2 Pose Variant summary lost key") + penetration_filter = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPKrea2POVPenetrationFilter"] + penetration_inputs = penetration_filter.INPUT_TYPES().get("required") or {} + _expect("include_doggy_top_down_rear_entry" in penetration_inputs, "POV Penetration Filter lost doggy atlas checkbox") + _expect( + "include_blowjob_side_profile_oral" not in penetration_inputs, + "POV Penetration Filter should not expose oral atlas checkboxes", + ) + oral_filter = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPKrea2POVOralFilter"] + oral_inputs = oral_filter.INPUT_TYPES().get("required") or {} + _expect("include_blowjob_side_profile_oral" in oral_inputs, "POV Oral Filter lost blowjob side-profile checkbox") + _expect( + "include_doggy_top_down_rear_entry" not in oral_inputs, + "POV Oral Filter should not expose penetration atlas checkboxes", + ) + doggy_config, doggy_keys, doggy_positions, doggy_cues, doggy_summary, doggy_variants_json = penetration_filter().build( + "replace", + "", + include_doggy_top_down_rear_entry=True, + ) + parsed_doggy_config = json.loads(doggy_config) + _expect(parsed_doggy_config.get("family") == "penetrative", "POV Penetration Filter should constrain single-family output") + _expect(parsed_doggy_config.get("positions") == ["doggy"], "POV Penetration Filter did not map doggy variant to position key") + _expect( + parsed_doggy_config.get("krea2_variant_keys") == ["pov_doggy_top_down_rear_entry"], + "POV Penetration Filter lost exact selected variant metadata", + ) + _expect(doggy_keys == "pov_doggy_top_down_rear_entry", "POV Penetration Filter returned wrong selected variant keys") + _expect(doggy_positions == "doggy", "POV Penetration Filter returned wrong selected positions") + _expect("top-down" in doggy_cues, "POV Penetration Filter lost prompt cues output") + _expect("variants=pov_doggy_top_down_rear_entry" in doggy_summary, "POV Penetration Filter summary lost selected variant") + _expect(json.loads(doggy_variants_json)[0].get("key") == "pov_doggy_top_down_rear_entry", "POV Penetration Filter returned wrong variant JSON") + + mixed_config, mixed_keys, mixed_positions, _mixed_cues, mixed_summary, _mixed_variants_json = oral_filter().build( + "add", + doggy_config, + include_blowjob_side_profile_oral=True, + ) + parsed_mixed_config = json.loads(mixed_config) + _expect(parsed_mixed_config.get("family") == "any", "Chained POV filters should use any family for mixed categories") + _expect( + parsed_mixed_config.get("positions") == ["doggy", "side_lying", "reclining_oral", "penis_licking"], + "Chained POV filters did not preserve doggy and blowjob position keys", + ) + _expect( + parsed_mixed_config.get("krea2_variant_keys") == [ + "pov_doggy_top_down_rear_entry", + "pov_blowjob_side_profile_oral", + ], + "Chained POV filters lost exact selected variant metadata", + ) + _expect( + mixed_keys == "pov_doggy_top_down_rear_entry,pov_blowjob_side_profile_oral", + "Chained POV filters returned wrong selected variant key list", + ) + _expect("doggy" in mixed_positions and "penis_licking" in mixed_positions, "Chained POV filters returned wrong positions summary") + _expect("family=any" in mixed_summary, "Chained POV filters summary should show mixed family") + evidence_node = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPKrea2VariantEvidence"] evidence_inputs = evidence_node.INPUT_TYPES().get("required") or {} _expect("variant_key" in evidence_inputs, "Krea2 Variant Evidence lost variant selector") @@ -10510,6 +11921,8 @@ SMOKE_CASES: list[tuple[str, Callable[[], None]]] = [ ("server_route_payload_policy", smoke_server_route_payload_policy), ("seed_config_policy", smoke_seed_config_policy), ("prompt_route_simulation_policy", smoke_prompt_route_simulation_policy), + ("sxcp_mcp_client_cli_policy", smoke_sxcp_mcp_client_cli_policy), + ("sxcp_prompt_batch_cli_policy", smoke_sxcp_prompt_batch_cli_policy), ("node_camera_registration", smoke_node_camera_registration), ("node_route_config_registration", smoke_node_route_config_registration), ("node_character_registration", smoke_node_character_registration), diff --git a/tools/sxcp_mcp_client.py b/tools/sxcp_mcp_client.py new file mode 100644 index 0000000..1b6dca0 --- /dev/null +++ b/tools/sxcp_mcp_client.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python3 +"""Small CLI for one-off SxCP MCP bridge calls. + +The repository smoke tests run with the system Python, so MCP dependencies are +imported only after a network subcommand is selected. For live bridge calls, run +this with the Python environment that has the `mcp` package installed. +""" + +from __future__ import annotations + +import argparse +import json +import sys +from typing import Any + + +DEFAULT_BRIDGE_URL = "http://192.168.1.12:9188/mcp" + + +def _json_loads(value: str) -> dict[str, Any]: + try: + parsed = json.loads(value) + except json.JSONDecodeError as exc: + raise argparse.ArgumentTypeError(str(exc)) from exc + if not isinstance(parsed, dict): + raise argparse.ArgumentTypeError("arguments JSON must decode to an object") + return parsed + + +def _json_default(value: Any) -> Any: + if hasattr(value, "model_dump"): + return value.model_dump(mode="json") + if hasattr(value, "__dict__"): + return value.__dict__ + return str(value) + + +async def _list_tools(bridge_url: str) -> int: + from mcp.client.session import ClientSession + from mcp.client.streamable_http import streamablehttp_client + + async with streamablehttp_client(bridge_url) as (read, write, _): + async with ClientSession(read, write) as session: + await session.initialize() + tools = (await session.list_tools()).tools + for tool in tools: + print(tool.name) + return 0 + + +async def _call_tool(bridge_url: str, tool_name: str, arguments: dict[str, Any]) -> int: + from mcp.client.session import ClientSession + from mcp.client.streamable_http import streamablehttp_client + + async with streamablehttp_client(bridge_url) as (read, write, _): + async with ClientSession(read, write) as session: + await session.initialize() + result = await session.call_tool(tool_name, arguments) + print(json.dumps(result, ensure_ascii=True, indent=2, default=_json_default)) + return 0 + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "--bridge-url", + default=DEFAULT_BRIDGE_URL, + help=f"MCP bridge URL. Default: {DEFAULT_BRIDGE_URL}", + ) + subparsers = parser.add_subparsers(dest="command", required=True) + + subparsers.add_parser("list-tools", help="List available MCP tool names.") + + call_parser = subparsers.add_parser("call-tool", help="Call one MCP tool.") + call_parser.add_argument("tool_name", help="Tool name to call.") + call_parser.add_argument( + "--arguments-json", + type=_json_loads, + default={}, + help="JSON object with tool arguments.", + ) + return parser + + +def main(argv: list[str] | None = None) -> int: + parser = build_parser() + args = parser.parse_args(argv) + + try: + import anyio + except ImportError as exc: + raise SystemExit( + "sxcp_mcp_client requires the MCP Python environment for network calls; " + "try /media/p5/miniforge3/bin/python." + ) from exc + + if args.command == "list-tools": + return anyio.run(_list_tools, args.bridge_url) + if args.command == "call-tool": + return anyio.run(_call_tool, args.bridge_url, args.tool_name, args.arguments_json) + parser.error(f"unknown command: {args.command}") + return 2 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/sxcp_prompt_batch.py b/tools/sxcp_prompt_batch.py new file mode 100644 index 0000000..641a2f1 --- /dev/null +++ b/tools/sxcp_prompt_batch.py @@ -0,0 +1,541 @@ +#!/usr/bin/env python3 +"""Prepare repeatable SxCP prompt-probe batches without opening the MCP bridge. + +The live bridge call remains `tools/sxcp_mcp_client.py`. This helper keeps the +batch plan and image-presence checklist deterministic so prompt-axis probes are +less dependent on hand-copied commands. +""" + +from __future__ import annotations + +import argparse +from datetime import date +import json +import shlex +import subprocess +import sys +import time +from pathlib import Path +from typing import Any + + +ROOT = Path(__file__).resolve().parents[1] +APPROVED_PYTHON = "/media/p5/miniforge3/bin/python" +MCP_HELPER = "tools/sxcp_mcp_client.py" +DEFAULT_OUT_CHANNEL = "sxcp_eval_out" +DEFAULT_IN_CHANNEL = "sxcp_eval_in" +NEGATIVE_OUT_CHANNEL = "sxcp_eval_negative_out" +PROMPT_ORDERS = {"subject_first", "geometry_only", "prompt_order_test"} + + +class BatchError(ValueError): + pass + + +def _load_json(path: Path) -> dict[str, Any]: + with path.open("r", encoding="utf-8") as handle: + data = json.load(handle) + if not isinstance(data, dict): + raise BatchError("batch JSON must contain one object") + return data + + +def _load_json_list(path: Path) -> list[Any]: + with path.open("r", encoding="utf-8") as handle: + data = json.load(handle) + if not isinstance(data, list): + raise BatchError("mock pulls JSON must contain a list") + return data + + +def _json_arg(value: dict[str, Any]) -> str: + return shlex.quote(json.dumps(value, ensure_ascii=True, separators=(",", ":"))) + + +def _text(value: Any) -> str: + return "" if value is None else str(value).strip() + + +def _validate_no_negative_channel(value: Any, *, field: str) -> None: + text = _text(value) + if text == NEGATIVE_OUT_CHANNEL: + raise BatchError(f"{field} must not use {NEGATIVE_OUT_CHANNEL}") + if NEGATIVE_OUT_CHANNEL in text: + raise BatchError(f"{field} must not mention {NEGATIVE_OUT_CHANNEL}") + + +def _validate_probe(raw: Any, index: int) -> dict[str, str]: + if not isinstance(raw, dict): + raise BatchError(f"probes[{index}] must be an object") + for forbidden in ("negative", "negative_prompt", "negative_text", "negative_channel"): + if forbidden in raw: + raise BatchError(f"probes[{index}] must not contain {forbidden}") + probe_id = _text(raw.get("id")) + if not probe_id: + raise BatchError(f"probes[{index}].id is required") + prompt_order = _text(raw.get("prompt_order") or "subject_first") + if prompt_order not in PROMPT_ORDERS: + raise BatchError(f"probes[{index}].prompt_order must be one of {sorted(PROMPT_ORDERS)}") + text = _text(raw.get("text")) + if not text: + raise BatchError(f"probes[{index}].text is required") + _validate_no_negative_channel(text, field=f"probes[{index}].text") + return {"id": probe_id, "prompt_order": prompt_order, "text": text} + + +def _validate_image_path(value: Any, *, field: str) -> str: + path_text = _text(value) + if not path_text: + raise BatchError(f"{field} is required") + path = Path(path_text) + if not path.is_absolute(): + raise BatchError(f"{field} must be absolute") + if path.suffix.lower() != ".png": + raise BatchError(f"{field} must reference a PNG artifact") + return path_text + + +def load_batch(path: Path) -> dict[str, Any]: + batch = _load_json(path) + for forbidden in ("negative", "negative_prompt", "negative_text", "negative_channel"): + if forbidden in batch: + raise BatchError(f"batch must not contain {forbidden}") + seed = batch.get("seed") + if not isinstance(seed, int): + raise BatchError("seed must be an integer sampler seed") + channel_out = _text(batch.get("channel_out") or DEFAULT_OUT_CHANNEL) + channel_in = _text(batch.get("channel_in") or DEFAULT_IN_CHANNEL) + _validate_no_negative_channel(channel_out, field="channel_out") + _validate_no_negative_channel(channel_in, field="channel_in") + probes_raw = batch.get("probes") + if not isinstance(probes_raw, list) or not probes_raw: + raise BatchError("probes must be a non-empty list") + probes = [_validate_probe(raw, index) for index, raw in enumerate(probes_raw)] + return { + "seed": seed, + "channel_out": channel_out, + "channel_in": channel_in, + "probes": probes, + } + + +def load_results(path: Path) -> dict[str, Any]: + data = _load_json(path) + seed = data.get("seed") + if not isinstance(seed, int): + raise BatchError("result seed must be an integer sampler seed") + channel_in = _text(data.get("channel_in") or DEFAULT_IN_CHANNEL) + _validate_no_negative_channel(channel_in, field="channel_in") + probes_raw = data.get("probes") + if not isinstance(probes_raw, list) or not probes_raw: + raise BatchError("result probes must be a non-empty list") + probes: list[dict[str, Any]] = [] + for index, raw in enumerate(probes_raw): + if not isinstance(raw, dict): + raise BatchError(f"result probes[{index}] must be an object") + probe_id = _text(raw.get("id")) + if not probe_id: + raise BatchError(f"result probes[{index}].id is required") + prompt_order = _text(raw.get("prompt_order") or "subject_first") + if prompt_order not in PROMPT_ORDERS: + raise BatchError(f"result probes[{index}].prompt_order must be one of {sorted(PROMPT_ORDERS)}") + turn = raw.get("turn") + if turn is not None and (not isinstance(turn, int) or isinstance(turn, bool)): + raise BatchError(f"result probes[{index}].turn must be an integer when present") + returned_seed = raw.get("returned_seed") + if returned_seed is not None and (not isinstance(returned_seed, int) or isinstance(returned_seed, bool)): + raise BatchError(f"result probes[{index}].returned_seed must be an integer when present") + image_path = _text(raw.get("image_path")) + probes.append( + { + "id": probe_id, + "prompt_order": prompt_order, + "turn": turn, + "image_path": image_path, + "returned_seed": returned_seed, + } + ) + return {"seed": seed, "channel_in": channel_in, "probes": probes} + + +def print_push_commands(batch: dict[str, Any]) -> None: + for index, probe in enumerate(batch["probes"], start=1): + prompt_order = probe["prompt_order"] + caveat = "geometry-only: pose-axis discovery; not subject/look-controlled" if prompt_order == "geometry_only" else prompt_order + print(f"# {index}/{len(batch['probes'])} {probe['id']} ({caveat})") + push_args = { + "channel": batch["channel_out"], + "seed": batch["seed"], + "text": probe["text"], + } + print(f"{APPROVED_PYTHON} {MCP_HELPER} call-tool comfy_push --arguments-json {_json_arg(push_args)}") + pull_args = {"channel": batch["channel_in"]} + print(f"{APPROVED_PYTHON} {MCP_HELPER} call-tool comfy_pull --arguments-json {_json_arg(pull_args)}") + + +def print_result_template(batch: dict[str, Any]) -> None: + template = { + "seed": batch["seed"], + "channel_in": batch["channel_in"], + "probes": [ + { + "id": probe["id"], + "prompt_order": probe["prompt_order"], + "turn": None, + "image_path": "", + "returned_seed": None, + } + for probe in batch["probes"] + ], + } + print(json.dumps(template, ensure_ascii=True, indent=2)) + + +def _probe_by_id(probes: list[dict[str, Any]], probe_id: str, *, label: str) -> dict[str, Any]: + for probe in probes: + if probe.get("id") == probe_id: + return probe + raise BatchError(f"{label} {probe_id!r} was not found") + + +def validate_results(batch: dict[str, Any], results: dict[str, Any]) -> None: + if results["seed"] != batch["seed"]: + raise BatchError(f"result seed {results['seed']} does not match batch seed {batch['seed']}") + batch_probe_ids = [probe["id"] for probe in batch["probes"]] + result_probe_ids = [probe["id"] for probe in results["probes"]] + if result_probe_ids != batch_probe_ids: + raise BatchError(f"result probe ids must match batch probe ids in order: expected {batch_probe_ids}, got {result_probe_ids}") + turns: list[int] = [] + for index, (batch_probe, result_probe) in enumerate(zip(batch["probes"], results["probes"])): + expected_order = batch_probe["prompt_order"] + if result_probe["prompt_order"] != expected_order: + raise BatchError( + f"result probes[{index}].prompt_order must match batch prompt_order {expected_order!r}" + ) + turn = result_probe.get("turn") + if not isinstance(turn, int) or isinstance(turn, bool): + raise BatchError(f"result probes[{index}].turn is required and must be an integer") + turns.append(turn) + _validate_image_path(result_probe.get("image_path"), field=f"result probes[{index}].image_path") + returned_seed = result_probe.get("returned_seed") + if returned_seed != batch["seed"]: + raise BatchError( + f"result probes[{index}].returned_seed must match batch seed {batch['seed']}" + ) + if len(set(turns)) != len(turns): + raise BatchError("result probe turns must be unique") + if turns != sorted(turns): + raise BatchError("result probe turns must be in batch order") + + +def _payload_from_mcp_response(data: Any) -> dict[str, Any]: + if isinstance(data, dict) and ("turn" in data or "image_path" in data or "seed" in data): + return data + if not isinstance(data, dict): + raise BatchError("MCP response must be an object") + content = data.get("content") + if not isinstance(content, list): + raise BatchError("MCP response did not contain content") + for item in content: + if not isinstance(item, dict): + continue + text = _text(item.get("text")) + if not text: + continue + try: + payload = json.loads(text) + except json.JSONDecodeError as exc: + raise BatchError(f"MCP content text was not JSON: {exc}") from exc + if isinstance(payload, dict): + return payload + raise BatchError("MCP response did not contain a JSON payload") + + +def load_mock_pulls(path: Path) -> list[dict[str, Any]]: + return [_payload_from_mcp_response(item) for item in _load_json_list(path)] + + +def _call_mcp_tool(tool_name: str, arguments: dict[str, Any]) -> dict[str, Any]: + result = subprocess.run( + [ + APPROVED_PYTHON, + MCP_HELPER, + "call-tool", + tool_name, + "--arguments-json", + json.dumps(arguments, ensure_ascii=True, separators=(",", ":")), + ], + cwd=ROOT, + capture_output=True, + text=True, + check=False, + ) + if result.returncode != 0: + detail = result.stderr.strip() or result.stdout.strip() or f"exit {result.returncode}" + raise BatchError(f"MCP {tool_name} failed: {detail}") + try: + data = json.loads(result.stdout) + except json.JSONDecodeError as exc: + raise BatchError(f"MCP {tool_name} returned non-JSON output: {exc}") from exc + return _payload_from_mcp_response(data) + + +def _result_probe_from_payload( + probe: dict[str, Any], + payload: dict[str, Any], + *, + expected_seed: int, + previous_turn: int, +) -> dict[str, Any] | None: + turn = payload.get("turn") + if not isinstance(turn, int) or isinstance(turn, bool): + raise BatchError(f"pull result for {probe['id']} must contain an integer turn") + if turn <= previous_turn: + return None + image_path = _validate_image_path(payload.get("image_path"), field=f"pull result for {probe['id']}.image_path") + returned_seed = payload.get("seed") + if not isinstance(returned_seed, int) or isinstance(returned_seed, bool): + raise BatchError(f"pull result for {probe['id']} must contain an integer seed") + if returned_seed != expected_seed: + raise BatchError(f"pull result for {probe['id']} returned seed {returned_seed}, expected {expected_seed}") + return { + "id": probe["id"], + "prompt_order": probe["prompt_order"], + "turn": turn, + "image_path": image_path, + "returned_seed": returned_seed, + } + + +def run_batch( + batch: dict[str, Any], + *, + result_path: Path, + previous_turn: int, + max_polls: int, + poll_interval: float, + run_live: bool = False, + mock_pulls: list[dict[str, Any]] | None = None, +) -> dict[str, Any]: + if max_polls <= 0: + raise BatchError("max_polls must be positive") + if poll_interval < 0: + raise BatchError("poll_interval must be non-negative") + if run_live and mock_pulls is not None: + raise BatchError("--run and --mock-pulls-json cannot be used together") + if not run_live and mock_pulls is None: + raise BatchError("run_batch requires live mode or mock pulls") + + mock_index = 0 + result_probes: list[dict[str, Any]] = [] + current_turn = previous_turn + for probe in batch["probes"]: + if run_live: + _call_mcp_tool( + "comfy_push", + {"channel": batch["channel_out"], "seed": batch["seed"], "text": probe["text"]}, + ) + for poll_index in range(max_polls): + if mock_pulls is not None: + if mock_index >= len(mock_pulls): + raise BatchError(f"mock pulls exhausted while waiting for {probe['id']}") + payload = mock_pulls[mock_index] + mock_index += 1 + else: + payload = _call_mcp_tool("comfy_pull", {"channel": batch["channel_in"]}) + result_probe = _result_probe_from_payload( + probe, + payload, + expected_seed=batch["seed"], + previous_turn=current_turn, + ) + if result_probe is not None: + result_probes.append(result_probe) + current_turn = result_probe["turn"] + break + if run_live and poll_index < max_polls - 1: + time.sleep(poll_interval) + else: + raise BatchError(f"no new result for {probe['id']} after {max_polls} polls") + + results = {"seed": batch["seed"], "channel_in": batch["channel_in"], "probes": result_probes} + validate_results(batch, results) + result_path.write_text(json.dumps(results, ensure_ascii=True, indent=2) + "\n", encoding="utf-8") + return results + + +def _entry_id_slug(value: str) -> str: + chars = [char.lower() if char.isalnum() else "-" for char in value] + slug = "".join(chars).strip("-") + while "--" in slug: + slug = slug.replace("--", "-") + return slug or "sxcp-batch" + + +def eval_entry_draft( + batch: dict[str, Any], + results: dict[str, Any], + *, + variant_key: str, + entry_id: str, + baseline_image: str, + candidate_id: str, + source: str, + result: str, + decision: str, + entry_date: str, + allow_geometry_only: bool = False, +) -> dict[str, Any]: + validate_results(batch, results) + batch_probe = _probe_by_id(batch["probes"], candidate_id, label="candidate probe") + result_probe = _probe_by_id(results["probes"], candidate_id, label="candidate result") + candidate_image = _validate_image_path(result_probe.get("image_path"), field="candidate image_path") + baseline = _validate_image_path(baseline_image, field="baseline_image") + prompt_order = batch_probe["prompt_order"] + turn = result_probe.get("turn") + returned_seed = result_probe.get("returned_seed") + if returned_seed is not None and returned_seed != batch["seed"]: + raise BatchError(f"candidate returned_seed {returned_seed} does not match batch seed {batch['seed']}") + if prompt_order == "geometry_only" and not allow_geometry_only: + raise BatchError("candidate prompt_order is geometry_only; rerun with --allow-geometry-only to draft non-controlled prompt-axis evidence") + order_note = ( + "subject/look-controlled candidate" + if prompt_order == "subject_first" + else "geometry-only prompt-order probe; do not treat as subject/look-controlled evidence" + if prompt_order == "geometry_only" + else "prompt-order sensitivity probe" + ) + entry = { + "id": entry_id or f"{_entry_id_slug(variant_key)}-{batch['seed']}-{_entry_id_slug(candidate_id)}", + "date": entry_date, + "variant_key": variant_key, + "seed": batch["seed"], + "source": source, + "result": result, + "decision": decision, + "baseline_prompt_summary": f"Replace with the same-seed baseline summary for {variant_key}.", + "candidate_prompt_summary": ( + f"Batch candidate {candidate_id!r} used prompt_order={prompt_order!r}; " + f"replace with the pose-axis change and controlled variables." + ), + "observation": ( + f"Replace with image comparison for candidate {candidate_id!r}" + f"{f' on turn {turn}' if turn is not None else ''}. Prompt-order note: {order_note}." + ), + "baseline_image": baseline, + "candidate_image": candidate_image, + "commit": "pending", + } + return entry + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser(description=__doc__) + subparsers = parser.add_subparsers(dest="command", required=True) + for command in ("validate", "print-push-commands", "print-result-template"): + subparser = subparsers.add_parser(command) + subparser.add_argument("--batch-json", required=True, help="Path to the prompt batch JSON file.") + validate_results_parser = subparsers.add_parser("validate-results") + validate_results_parser.add_argument("--batch-json", required=True, help="Path to the prompt batch JSON file.") + validate_results_parser.add_argument("--result-json", required=True, help="Path to a filled result template JSON file.") + run_parser = subparsers.add_parser("run-batch") + run_parser.add_argument("--batch-json", required=True, help="Path to the prompt batch JSON file.") + run_parser.add_argument("--result-json", required=True, help="Path where the filled result JSON should be written.") + run_parser.add_argument("--run", action="store_true", help="Call the live MCP helper. Omit for dry-run or mock mode.") + run_parser.add_argument("--mock-pulls-json", help="Path to a JSON list of mocked sxcp_eval_in payloads for local testing.") + run_parser.add_argument("--previous-turn", type=int, default=0, help="Ignore pulls at or below this turn before the first probe.") + run_parser.add_argument("--max-polls", type=int, default=60, help="Maximum pull attempts per probe.") + run_parser.add_argument("--poll-interval", type=float, default=2.0, help="Seconds to wait between live pull attempts.") + draft_parser = subparsers.add_parser("print-eval-entry-draft") + draft_parser.add_argument("--batch-json", required=True, help="Path to the prompt batch JSON file.") + draft_parser.add_argument("--result-json", required=True, help="Path to a filled result template JSON file.") + draft_parser.add_argument("--variant-key", required=True, help="Catalog variant key for the eval entry.") + draft_parser.add_argument("--entry-id", default="", help="Durable eval entry id. Defaults to a generated id.") + draft_parser.add_argument("--baseline-image", required=True, help="Absolute PNG path for the baseline image.") + draft_parser.add_argument("--candidate-id", required=True, help="Probe id to use as the candidate image.") + draft_parser.add_argument("--source", default="sxcp_eval_mcp_batch", help="Source label for the eval entry.") + draft_parser.add_argument("--result", default="inconclusive", help="Eval result. Default: inconclusive.") + draft_parser.add_argument("--decision", default="needs_more_tests", help="Eval decision. Default: needs_more_tests.") + draft_parser.add_argument("--date", default=date.today().isoformat(), help="Eval entry date.") + draft_parser.add_argument( + "--allow-geometry-only", + action="store_true", + help="Allow drafting an entry from a geometry_only probe. Use only for non-controlled prompt-axis evidence.", + ) + return parser + + +def main(argv: list[str] | None = None) -> int: + parser = build_parser() + args = parser.parse_args(argv) + try: + batch = load_batch(Path(args.batch_json)) + except Exception as exc: + print(f"error: {exc}", file=sys.stderr) + return 1 + if args.command == "validate": + print(f"validated: {len(batch['probes'])} probes, seed {batch['seed']}") + return 0 + if args.command == "print-push-commands": + print_push_commands(batch) + return 0 + if args.command == "print-result-template": + print_result_template(batch) + return 0 + if args.command == "validate-results": + try: + results = load_results(Path(args.result_json)) + validate_results(batch, results) + except Exception as exc: + print(f"error: {exc}", file=sys.stderr) + return 1 + print(f"validated results: {len(batch['probes'])} probes, seed {batch['seed']}") + return 0 + if args.command == "run-batch": + try: + if not args.run and not args.mock_pulls_json: + print(f"dry-run: {len(batch['probes'])} probes, seed {batch['seed']}") + print_push_commands(batch) + return 0 + mock_pulls = load_mock_pulls(Path(args.mock_pulls_json)) if args.mock_pulls_json else None + results = run_batch( + batch, + result_path=Path(args.result_json), + previous_turn=args.previous_turn, + max_polls=args.max_polls, + poll_interval=args.poll_interval, + run_live=args.run, + mock_pulls=mock_pulls, + ) + except Exception as exc: + print(f"error: {exc}", file=sys.stderr) + return 1 + print(f"recorded results: {len(results['probes'])} probes, seed {results['seed']} -> {args.result_json}") + return 0 + if args.command == "print-eval-entry-draft": + try: + results = load_results(Path(args.result_json)) + entry = eval_entry_draft( + batch, + results, + variant_key=args.variant_key, + entry_id=args.entry_id, + baseline_image=args.baseline_image, + candidate_id=args.candidate_id, + source=args.source, + result=args.result, + decision=args.decision, + entry_date=args.date, + allow_geometry_only=args.allow_geometry_only, + ) + except Exception as exc: + print(f"error: {exc}", file=sys.stderr) + return 1 + print(json.dumps(entry, ensure_ascii=True, indent=2)) + return 0 + parser.error(f"unknown command: {args.command}") + return 2 + + +if __name__ == "__main__": + raise SystemExit(main())