diff --git a/categories/sexual_poses.json b/categories/sexual_poses.json index 39ff9db..44c6758 100644 --- a/categories/sexual_poses.json +++ b/categories/sexual_poses.json @@ -106,7 +106,7 @@ "item_templates": [ "{tease_act} in {position}, with {touch_detail}, {clothing_detail}, and {mood_detail}", "{position} featuring {tease_act}, {body_contact}, {touch_detail}, and {visibility}", - "hardcore foreplay setup: {tease_act}, {clothing_detail}, {face_detail}, and {body_contact}", + "hardcore foreplay setup with {tease_act}, {clothing_detail}, {face_detail}, and {body_contact}", "{tease_act} on {surface}, with {touch_detail}, {mood_detail}, and {visibility}", "{position} while {tease_act}, with {face_detail}, {clothing_detail}, and {touch_detail}" ], @@ -230,7 +230,7 @@ "{manual_act} in {position}, with {manual_detail}, {body_contact}, and {visibility}", "{position} featuring {manual_act}, {hand_detail}, {reaction_detail}, and {visibility}", "{manual_act} on {surface}, with {manual_detail}, {hand_detail}, and {reaction_detail}", - "manual stimulation setup: {position}, {manual_act}, {body_contact}, and {visibility}", + "manual stimulation setup with {position}, {manual_act}, {body_contact}, and {visibility}", "{position} while {manual_act}, with {hand_detail}, {manual_detail}, and {reaction_detail}" ], "item_axes": { @@ -339,7 +339,7 @@ "{worship_act} in {position}, with {touch_detail}, {face_detail}, and {visibility}", "{position} featuring {worship_act}, {body_contact}, {touch_detail}, and {reaction_detail}", "{worship_act} on {surface}, with {body_contact}, {face_detail}, and {visibility}", - "body worship setup: {position}, {worship_act}, {touch_detail}, and {reaction_detail}", + "body worship setup with {position}, {worship_act}, {touch_detail}, and {reaction_detail}", "{position} while {worship_act}, with {body_contact}, {face_detail}, and {visibility}" ], "item_axes": { @@ -452,7 +452,7 @@ "{transition_act} in {position}, with {clothing_detail}, {hand_detail}, and {visibility}", "{position} featuring {transition_act}, {body_contact}, {clothing_detail}, and {movement_detail}", "{transition_act} on {surface}, with {hand_detail}, {body_contact}, and {visibility}", - "position transition: {position}, {transition_act}, {movement_detail}, and {clothing_detail}", + "position transition with {position}, {transition_act}, {movement_detail}, and {clothing_detail}", "{position} while {transition_act}, with {hand_detail}, {body_contact}, and {visibility}" ], "item_axes": { @@ -561,7 +561,7 @@ "{control_act} in {position}, with {hand_detail}, {body_contact}, and {visibility}", "{position} featuring {control_act}, {power_detail}, {hand_detail}, and {reaction_detail}", "{control_act} on {surface}, with {body_contact}, {power_detail}, and {visibility}", - "consensual control setup: {position}, {control_act}, {hand_detail}, and {reaction_detail}", + "consensual control setup with {position}, {control_act}, {hand_detail}, and {reaction_detail}", "{position} while {control_act}, with {power_detail}, {body_contact}, and {visibility}" ], "item_axes": { @@ -675,7 +675,7 @@ "{performance_act} in {position}, with {presentation_detail}, {hand_detail}, and {visibility}", "{position} featuring {performance_act}, {camera_detail}, {presentation_detail}, and {reaction_detail}", "{performance_act} on {surface}, with {hand_detail}, {camera_detail}, and {visibility}", - "creator-performance setup: {position}, {performance_act}, {presentation_detail}, and {reaction_detail}", + "creator-performance setup with {position}, {performance_act}, {presentation_detail}, and {reaction_detail}", "{position} while {performance_act}, with {camera_detail}, {hand_detail}, and {visibility}" ], "item_axes": { @@ -783,7 +783,7 @@ "{coordination_act} with {arrangement}, {touch_detail}, {reaction_detail}, and {visibility}", "{arrangement} featuring {coordination_act}, {body_contact}, {watching_detail}, and {visibility}", "{coordination_act} on {surface}, with {touch_detail}, {watching_detail}, and {body_contact}", - "group coordination setup: {arrangement}, {coordination_act}, {watching_detail}, and {visibility}", + "group coordination setup with {arrangement}, {coordination_act}, {watching_detail}, and {visibility}", "{arrangement} while {coordination_act}, with {touch_detail}, {reaction_detail}, and {visibility}" ], "item_axes": { @@ -889,7 +889,7 @@ "{aftercare_act} in {position}, with {cleanup_detail}, {body_contact}, and {visibility}", "{position} featuring {aftercare_act}, {touch_detail}, {cleanup_detail}, and {reaction_detail}", "{aftercare_act} on {surface}, with {body_contact}, {touch_detail}, and {visibility}", - "post-sex aftermath setup: {position}, {aftercare_act}, {cleanup_detail}, and {reaction_detail}", + "post-sex aftermath setup with {position}, {aftercare_act}, {cleanup_detail}, and {reaction_detail}", "{position} while {aftercare_act}, with {touch_detail}, {body_contact}, and {visibility}" ], "item_axes": { @@ -1178,7 +1178,7 @@ "{oral_act} on {surface}, {hand_detail}, {mouth_detail}, and {climax_hint}", "{angle} view of {oral_act}, with {visibility}, {body_contact}, and {expression_detail}", "{position} while {oral_act}, with {saliva_detail}, {hand_detail}, and {climax_hint}", - "explicit mouth-to-genitals pose: {oral_act}, {mouth_detail}, {body_contact}, and {visibility}" + "explicit mouth-to-genitals pose with {oral_act}, {mouth_detail}, {body_contact}, and {visibility}" ], "item_axes": { "angle": [ @@ -1330,7 +1330,7 @@ }, "{position} featuring {outer_act}, {body_contact}, {texture_detail}, seen from a {angle} view", "{angle} view of {outer_act}, with {visibility}, {contact_detail}, and {expression_detail}", - "explicit non-penetrative sex pose: {outer_act}, {position}, {contact_detail}, and {visibility}", + "explicit non-penetrative sex pose with {outer_act}, {position}, {contact_detail}, and {visibility}", "{outer_act} on {surface}, with {hand_detail}, {body_contact}, and {texture_detail}", "{position} while {outer_act}, with {texture_detail}, {hand_detail}, and {visibility}" ], @@ -1479,7 +1479,7 @@ "{double_act} on {surface}, with {leg_detail}, {intensity}, and {climax_hint}", "{angle} view of {double_act}, {body_arrangement}, {mouth_detail}, and {visibility}", "{anal_act} with {thrust_detail}, {hand_detail}, {body_contact}, and {climax_hint}", - "explicit double-contact sex pose: {double_act}, {leg_detail}, {visibility}, and {intensity}" + "explicit double-contact sex pose with {double_act}, {leg_detail}, {visibility}, and {intensity}" ], "item_axes": { "anal_act": [ @@ -1698,7 +1698,7 @@ "{threesome_act} with {body_arrangement}, {oral_detail}, {penetration_detail}, and {visibility}", "{body_arrangement} while {threesome_act}, with {hand_detail}, {mouth_detail}, and {climax_hint}", "{angle} threesome view featuring {threesome_act}, {body_contact}, {penetration_detail}, and {visibility}", - "hardcore threesome pose: {threesome_act}, {body_arrangement}, {oral_detail}, and {climax_hint}", + "hardcore threesome pose with {threesome_act}, {body_arrangement}, {oral_detail}, and {climax_hint}", "{threesome_act} on {surface}, with {hand_detail}, {body_contact}, and {visibility}", "{angle} view of {body_arrangement}, {penetration_detail}, {mouth_detail}, and {intensity}", "three-body explicit sex pose with {threesome_act}, {oral_detail}, {hand_detail}, and {visibility}", @@ -1885,7 +1885,7 @@ "{group_act} with {arrangement}, {contact_detail}, {fluid_detail}, and {visibility}", "{arrangement} featuring {group_act}, {oral_detail}, {penetration_detail}, and {intensity}", "{angle} group-sex view with {group_act}, {contact_detail}, {climax_detail}, and {visibility}", - "hardcore orgy pose: {arrangement}, {group_act}, {oral_detail}, and {fluid_detail}", + "hardcore orgy pose with {arrangement}, {group_act}, {oral_detail}, and {fluid_detail}", "{group_act} on {surface}, with {penetration_detail}, {contact_detail}, and {visibility}", "{angle} view of {arrangement}, {fluid_detail}, {intensity}, and {climax_detail}", "explicit adult group pile with {group_act}, {oral_detail}, {penetration_detail}, and {visibility}", diff --git a/docs/prompt-architecture-improvement-plan.md b/docs/prompt-architecture-improvement-plan.md index adaf54f..d771885 100644 --- a/docs/prompt-architecture-improvement-plan.md +++ b/docs/prompt-architecture-improvement-plan.md @@ -36,6 +36,9 @@ The map audit currently sees: - Insta/OF side-target training captions no longer prepend shared cast descriptors when the selected side row already emits its own cast prose, and route simulation flags repeated cast descriptors. +- Route simulation now has an opt-in multi-seed sweep, and the smoke suite runs + a three-seed sweep so representative route/noise checks are not proven by one + lucky seed only. ## Architectural Finding diff --git a/docs/prompt-pool-routing-map.md b/docs/prompt-pool-routing-map.md index 83f16b5..39220d1 100644 --- a/docs/prompt-pool-routing-map.md +++ b/docs/prompt-pool-routing-map.md @@ -1021,9 +1021,13 @@ issues for: scene, soft outfit, and composition axes stable; - pose-axis rerolls changing cast/scene metadata or failing to move pose/action metadata. +- multi-seed route sweeps that repeat the same route/noise/seed checks across + spaced seeds to catch random-pool drift hidden by a single clean seed. Use `--json --include-prompts` when you need the exact raw and formatted text -for debugging a route. +for debugging a route. Use `--sweep-count 5 --seed-step 101` when changing pool +selection, route terms, or formatter noise rules and you need more than one +seed of evidence. ## Editing Cheatsheet diff --git a/tools/prompt_map_audit.py b/tools/prompt_map_audit.py index d2ee82f..f008663 100644 --- a/tools/prompt_map_audit.py +++ b/tools/prompt_map_audit.py @@ -114,6 +114,10 @@ AUDIT_DOC_SNIPPETS: tuple[tuple[str, str], ...] = ( "docs/prompt-pool-routing-map.md", "repeated cast descriptors in training-caption formatter output", ), + ( + "docs/prompt-pool-routing-map.md", + "multi-seed route sweeps", + ), ) PROMPT_ROW_READ_SCAN_GLOBS: tuple[str, ...] = ( diff --git a/tools/prompt_route_simulation.py b/tools/prompt_route_simulation.py index 8cc12c6..8d48247 100644 --- a/tools/prompt_route_simulation.py +++ b/tools/prompt_route_simulation.py @@ -308,6 +308,7 @@ HARDCORE_ROUTE_CASES = ( "subcategory": "Foreplay and teasing", "focus": "foreplay_only", "family": "foreplay", + "positions": ("undressing",), "expected_route": {"action_family": "foreplay", "position_family": "foreplay"}, "expected_terms": { "krea": ("clothing",), @@ -317,12 +318,13 @@ HARDCORE_ROUTE_CASES = ( }, { "name": "hardcore.single.interaction", - "subcategory": "Aftercare and cleanup", + "subcategory": "Clothing and position transitions", "focus": "interaction_only", "family": "interaction", + "positions": ("position_transition",), "expected_route": {"action_family": "foreplay", "position_family": "interaction"}, "expected_terms": { - "krea": ("mid-transition",), + "krea": ("clothing",), "sdxl": ("interaction",), "caption": ("interaction beat",), }, @@ -332,9 +334,10 @@ HARDCORE_ROUTE_CASES = ( "subcategory": "Anal and double penetration", "focus": "anal_only", "family": "anal", + "positions": ("doggy", "face_down_ass_up"), "expected_route": {"action_family": "anal", "position_family": "anal"}, "expected_terms": { - "krea": ("anal",), + "krea": ("ass",), "sdxl": ("anal sex",), "caption": ("anal action",), }, @@ -853,7 +856,13 @@ def _regular_single_case(seed: int) -> dict[str, Any]: ) -def _hardcore_single_case(seed: int, subcategory: str, focus: str, family: str) -> dict[str, Any]: +def _hardcore_single_case( + seed: int, + subcategory: str, + focus: str, + family: str, + positions: list[str] | tuple[str, ...] | str = (), +) -> dict[str, Any]: women_count, men_count, character_cast = { "threesome": (1, 2, _character_cast_subjects(("woman", "man", "man"))), "group": (2, 2, _character_cast_subjects(("woman", "woman", "man", "man"))), @@ -881,7 +890,7 @@ def _hardcore_single_case(seed: int, subcategory: str, focus: str, family: str) women_count=women_count, men_count=men_count, character_cast=character_cast, - hardcore_position_config=_position_filter(focus, family, []), + hardcore_position_config=_position_filter(focus, family, positions), location_config=_coworking_location_config(), camera_config=_orbit_camera(horizontal_angle=35, vertical_angle=0, zoom=6.5), ) @@ -1404,6 +1413,7 @@ def run_simulation(seed: int = 3901, *, include_prompts: bool = False) -> dict[s str(route_case["subcategory"]), str(route_case["focus"]), str(route_case["family"]), + route_case.get("positions") or (), ) cases.append( _case_report( @@ -1459,6 +1469,38 @@ def run_simulation(seed: int = 3901, *, include_prompts: bool = False) -> dict[s } +def run_simulation_sweep( + seed: int = 3901, + *, + count: int = 3, + seed_step: int = 101, + include_prompts: bool = False, +) -> dict[str, Any]: + count = max(1, int(count)) + seed_step = int(seed_step) + seeds = [seed + index * seed_step for index in range(count)] + runs = [run_simulation(seed=current_seed, include_prompts=include_prompts) for current_seed in seeds] + issues: list[dict[str, Any]] = [] + for run in runs: + run_seed = (run.get("summary") or {}).get("seed") + issues.extend({"seed": run_seed, **issue} for issue in run.get("issues") or []) + return { + "summary": { + "seed": seed, + "seed_step": seed_step, + "seeds": seeds, + "runs": len(runs), + "cases": sum((run.get("summary") or {}).get("cases", 0) for run in runs), + "coverage_checks": sum((run.get("summary") or {}).get("coverage_checks", 0) for run in runs), + "axis_checks": sum((run.get("summary") or {}).get("axis_checks", 0) for run in runs), + "pair_seed_checks": sum((run.get("summary") or {}).get("pair_seed_checks", 0) for run in runs), + "issues": len(issues), + }, + "issues": issues, + "runs": runs, + } + + def _print_text_report(report: dict[str, Any]) -> None: summary = report.get("summary") or {} print( @@ -1490,17 +1532,49 @@ def _print_text_report(report: dict[str, Any]) -> None: print(f" ISSUE {issue}") +def _print_sweep_report(report: dict[str, Any]) -> None: + summary = report.get("summary") or {} + seeds = ", ".join(str(seed) for seed in (summary.get("seeds") or [])) + print( + f"Prompt route simulation sweep: seed={summary.get('seed')} " + f"seed_step={summary.get('seed_step')} runs={summary.get('runs')} " + f"seeds={seeds} cases={summary.get('cases')} coverage_checks={summary.get('coverage_checks')} " + f"axis_checks={summary.get('axis_checks')} pair_seed_checks={summary.get('pair_seed_checks')} " + f"issues={summary.get('issues')}" + ) + for run in report.get("runs") or []: + run_summary = run.get("summary") or {} + print( + f"- seed {run_summary.get('seed')}: " + f"cases={run_summary.get('cases')} issues={run_summary.get('issues')}" + ) + for issue in run.get("issues") or []: + print(f" ISSUE {issue.get('case')}: {issue.get('issue')}") + + def main(argv: list[str] | None = None) -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--seed", type=int, default=3901, help="Base seed for deterministic simulations.") + parser.add_argument("--sweep-count", type=int, default=1, help="Run this many seed-spaced simulations.") + parser.add_argument("--seed-step", type=int, default=101, help="Seed increment used by --sweep-count.") parser.add_argument("--json", action="store_true", help="Print the full JSON report.") parser.add_argument("--include-prompts", action="store_true", help="Include raw and formatted prompt text in the report.") parser.add_argument("--fail-on-issues", action="store_true", help="Exit with code 1 when any issue is reported.") args = parser.parse_args(argv) - report = run_simulation(seed=args.seed, include_prompts=args.include_prompts) + if args.sweep_count > 1: + report = run_simulation_sweep( + seed=args.seed, + count=args.sweep_count, + seed_step=args.seed_step, + include_prompts=args.include_prompts, + ) + else: + report = run_simulation(seed=args.seed, include_prompts=args.include_prompts) if args.json: print(json.dumps(report, ensure_ascii=True, indent=2, sort_keys=True)) + elif args.sweep_count > 1: + _print_sweep_report(report) else: _print_text_report(report) return 1 if args.fail_on_issues and report.get("issues") else 0 diff --git a/tools/prompt_smoke.py b/tools/prompt_smoke.py index 5ca4a36..493023e 100644 --- a/tools/prompt_smoke.py +++ b/tools/prompt_smoke.py @@ -8025,6 +8025,12 @@ def smoke_prompt_route_simulation_policy() -> None: pair_seed_checks["pair_seed.pose_reroll"].get("changed") is True, "Pair pose reroll should prove hard action can reroll while soft/cast/scene axes stay locked", ) + sweep = prompt_route_simulation.run_simulation_sweep(seed=3901, count=3, seed_step=101, include_prompts=False) + sweep_summary = sweep.get("summary") or {} + _expect(sweep_summary.get("runs") == 3, "Prompt route simulation sweep lost run coverage") + _expect(sweep_summary.get("seeds") == [3901, 4002, 4103], "Prompt route simulation sweep seed sequence changed") + _expect(sweep_summary.get("cases") == 42, "Prompt route simulation sweep case count changed") + _expect(sweep_summary.get("issues") == 0, f"Prompt route simulation sweep reported issues: {sweep.get('issues')}") def smoke_node_camera_registration() -> None: