Route clothing choices through clothing seed

This commit is contained in:
2026-07-01 16:43:43 +02:00
parent 885f136cf3
commit 8c3f61ea6d
8 changed files with 331 additions and 22 deletions
+261 -2
View File
@@ -2493,7 +2493,7 @@ def smoke_row_category_route_policy() -> None:
hardcore_position_config={},
)
_expect(casual_route["category"]["slug"] == "casual_clothes", "Row category route selected wrong casual category")
_expect(casual_route["content_axis"] == "content", "Non-pose category should use content seed axis")
_expect(casual_route["content_axis"] == "clothing", "Casual clothes category should use clothing seed axis")
_expect(casual_route["is_pose_category"] is False, "Non-pose category should not be marked as pose content")
exposed_route = row_category_route.select_category_item_route(
@@ -2507,9 +2507,35 @@ def smoke_row_category_route_policy() -> None:
hardcore_position_config={},
)
_expect(exposed_route["subcategory"]["slug"] == "sheer_exposed", "Row category route selected wrong exposed category")
_expect(exposed_route["content_axis"] == "content", "Exposed clothing slug should not be treated as pose content")
_expect(exposed_route["content_axis"] == "clothing", "Exposed clothing slug should use clothing seed axis")
_expect(exposed_route["is_pose_category"] is False, "Exposed clothing slug should not be marked as pose content")
generic_categories = [
{
"name": "Lighting mood",
"slug": "lighting_mood",
"subcategories": [
{
"name": "Window light",
"slug": "window_light",
"items": ["soft window glow", "warm rim light"],
}
],
}
]
generic_route = row_category_route.select_category_item_route(
category_choice="Lighting mood",
subcategory_choice="Window light",
seed_config=seed_cfg,
seed=2303,
row_number=1,
women_count=1,
men_count=0,
hardcore_position_config={},
categories=generic_categories,
)
_expect(generic_route["content_axis"] == "content", "Generic non-clothing category should keep content seed axis")
def smoke_row_generation_policy() -> None:
_expect(pb._ratio_or_none(-1) is None, "Prompt builder ratio helper should treat negative as unset")
@@ -2557,6 +2583,36 @@ def smoke_row_generation_policy() -> None:
pb._build_direct_builtin_row(**direct_args) == row_generation.build_direct_builtin_row(**direct_args),
"Prompt builder direct built-in row should delegate to row_generation",
)
direct_random_args = {**direct_args, "clothing": "random", "minimal_clothing_ratio": 0.5}
direct_locked = row_generation.build_direct_builtin_row(
**direct_random_args,
seed_config=seed_config.parse_seed_config(pb.build_seed_lock_config_json(base_seed=5050)),
)
direct_clothing_reroll = row_generation.build_direct_builtin_row(
**direct_random_args,
seed_config=seed_config.parse_seed_config(
pb.build_seed_lock_config_json(base_seed=5050, reroll_axis="clothing", reroll_seed=5052)
),
)
direct_stable_keys = ("primary_subject", "age_band", "body_type", "scene", "composition", "pose_mode")
_expect(
tuple(direct_locked.get(key) for key in direct_stable_keys)
== tuple(direct_clothing_reroll.get(key) for key in direct_stable_keys),
"Direct built-in clothing reroll should keep non-clothing row fields stable",
)
_expect(
(
direct_locked.get("clothing_mode"),
direct_locked.get("prompt"),
direct_locked.get("caption"),
)
!= (
direct_clothing_reroll.get("clothing_mode"),
direct_clothing_reroll.get("prompt"),
direct_clothing_reroll.get("caption"),
),
"Direct built-in clothing reroll should change clothing mode or clothing text",
)
auto_args = dict(
row_number=2,
start_index=41,
@@ -14336,6 +14392,209 @@ def smoke_seed_config_policy() -> None:
break
_expect(pose_changed, "pose reroll should change pose/action metadata while cast and scene stay locked")
clothing_axis_seed = 42001
def clothing_category_row(seed_config_value: str | dict[str, Any], *, name: str = "seed_config_policy_clothing_axis") -> dict[str, Any]:
return pb.build_prompt(
category="Casual clothes",
subcategory="Casual clothes / Streetwear",
row_number=1,
start_index=1,
seed=clothing_axis_seed,
clothing="random",
ethnicity="any",
poses="standard",
backside_bias=0.0,
figure="curvy",
no_plus_women=False,
no_black=False,
minimal_clothing_ratio=0.5,
standard_pose_ratio=-1,
trigger=Trigger,
prepend_trigger_to_prompt=True,
extra_positive="",
extra_negative="",
seed_config=seed_config_value,
women_count=1,
men_count=0,
expression_enabled=True,
expression_intensity=0.6,
)
def clothing_trace(row: dict[str, Any]) -> str:
trace = row.get("generation_trace") if isinstance(row.get("generation_trace"), dict) else {}
return str(trace.get("clothing") or "")
clothing_locked_row = clothing_category_row(pb.build_seed_lock_config_json(base_seed=clothing_axis_seed))
content_pose_row = clothing_category_row(
pb.build_seed_lock_config_json(
base_seed=clothing_axis_seed,
reroll_axis="content_pose",
reroll_seed=clothing_axis_seed + 2,
),
name="seed_config_policy_content_pose_locked_clothing",
)
clothing_stable_fields = ("item", "scene_text", "subject_phrase")
_expect(
tuple(clothing_locked_row.get(key) for key in clothing_stable_fields)
== tuple(content_pose_row.get(key) for key in clothing_stable_fields),
"content_pose reroll should keep clothing-category item, scene, and subject stable when clothing is locked",
)
_expect(
clothing_trace(clothing_locked_row) == clothing_trace(content_pose_row),
"content_pose reroll should not change prompt clothing mode when clothing seed is locked",
)
clothing_reroll_row = clothing_category_row(
pb.build_seed_lock_config_json(
base_seed=clothing_axis_seed,
reroll_axis="clothing",
reroll_seed=clothing_axis_seed + 2,
),
name="seed_config_policy_clothing_reroll",
)
_expect(
tuple(clothing_locked_row.get(key) for key in ("scene_text", "pose", "subject_phrase"))
== tuple(clothing_reroll_row.get(key) for key in ("scene_text", "pose", "subject_phrase")),
"clothing reroll should keep scene, pose, and subject stable",
)
_expect(
(
clothing_trace(clothing_locked_row),
clothing_locked_row.get("item"),
)
!= (
clothing_trace(clothing_reroll_row),
clothing_reroll_row.get("item"),
),
"clothing reroll should change clothing mode or outfit text",
)
content_clothing_row = clothing_category_row(
pb.build_seed_lock_config_json(
base_seed=clothing_axis_seed,
reroll_axis="content_clothing",
reroll_seed=clothing_axis_seed + 2,
),
name="seed_config_policy_content_clothing_reroll",
)
_expect(
clothing_trace(clothing_locked_row) != clothing_trace(content_clothing_row),
"content_clothing reroll should change prompt clothing mode for clothing categories",
)
_expect(
clothing_locked_row.get("item") != content_clothing_row.get("item"),
"content_clothing reroll should change outfit text for clothing categories",
)
outfit_alias_config = seed_config.parse_seed_config(pb.build_seed_lock_config_json(base_seed=clothing_axis_seed))
outfit_alias_config.pop("clothing_seed", None)
outfit_alias_config["content_seed"] = clothing_axis_seed
outfit_alias_config["outfit_seed"] = clothing_axis_seed + 2
outfit_alias_row = clothing_category_row(outfit_alias_config, name="seed_config_policy_outfit_alias")
_expect(
clothing_trace(outfit_alias_row) == clothing_trace(clothing_reroll_row),
"outfit_seed alias should drive prompt clothing mode ahead of content_seed",
)
_expect(
outfit_alias_row.get("item") == clothing_reroll_row.get("item"),
"outfit_seed alias should drive clothing-category outfit text ahead of content_seed",
)
def pair_row_outfit(seed_config_value: dict[str, int]) -> str:
route = pair_rows.build_insta_pair_rows_result(
row_number=1,
start_index=1,
seed=clothing_axis_seed,
active_trigger=Trigger,
parsed_seed_config=seed_config_value,
options={
"softcore_cast": "solo",
"softcore_expression_enabled": False,
"softcore_expression_intensity": 0.5,
"hardcore_expression_enabled": False,
"hardcore_expression_intensity": 0.5,
"hardcore_detail_density": "standard",
},
ethnicity="any",
figure="curvy",
no_plus_women=False,
no_black=False,
character_profile="",
character_cast="",
character_slot_map={},
pov_character_labels=[],
hard_women_count=1,
hard_men_count=1,
soft_category="Casual clothes",
soft_subcategory="Casual clothes / Streetwear",
softcore_level_key="suggestive",
hardcore_random_subcategory="Penetrative sex",
hardcore_position_config="",
location_config="",
composition_config="",
build_prompt=lambda **kwargs: {"prompt": "", "negative_prompt": "", "caption": ""},
axis_rng=seed_config.axis_rng,
cast_expression_intensity_override=lambda *_args: (None, "disabled"),
context_from_character_slot=lambda *_args: {},
apply_character_context_to_row=lambda row, _context: row,
disable_row_expression=lambda row, _source: row,
slot_softcore_outfit=lambda _slot, _rng: "",
softcore_outfit=lambda rng, _level: f"outfit-{rng.randint(1, 1000000)}",
softcore_pose=lambda rng, _level: f"pose-{rng.randint(1, 1000000)}",
softcore_item_prompt_label=lambda _level: "outfit",
pov_prompt_directive=lambda _labels: "",
pov_composition_prompt=lambda composition, _labels: str(composition),
)
return str(route.soft_row.get("item") or "")
pair_base_config = seed_config.parse_seed_config(pb.build_seed_lock_config_json(base_seed=clothing_axis_seed))
pair_content_reroll_config = seed_config.parse_seed_config(
pb.build_seed_lock_config_json(
base_seed=clothing_axis_seed,
reroll_axis="content",
reroll_seed=clothing_axis_seed + 4,
)
)
pair_clothing_reroll_config = seed_config.parse_seed_config(
pb.build_seed_lock_config_json(
base_seed=clothing_axis_seed,
reroll_axis="clothing",
reroll_seed=clothing_axis_seed + 4,
)
)
_expect(
pair_row_outfit(pair_base_config) == pair_row_outfit(pair_content_reroll_config),
"Pair softcore primary outfit should stay stable across content reroll when clothing is locked",
)
_expect(
pair_row_outfit(pair_base_config) != pair_row_outfit(pair_clothing_reroll_config),
"Pair softcore primary outfit should follow clothing reroll",
)
def partner_outfits(seed_config_value: dict[str, int]) -> list[str]:
return pair_cast.softcore_partner_styling(
seed_config=seed_config_value,
seed=clothing_axis_seed,
row_number=1,
women_count=2,
men_count=1,
pov_labels=[],
label_map={},
axis_rng=seed_config.axis_rng,
choose=lambda rng, values: values[rng.randrange(len(values))],
slot_softcore_outfit=lambda _slot, _rng: "",
)["outfits"]
_expect(
partner_outfits(pair_base_config) == partner_outfits(pair_content_reroll_config),
"Pair partner outfits should stay stable across content reroll when clothing is locked",
)
_expect(
partner_outfits(pair_base_config) != partner_outfits(pair_clothing_reroll_config),
"Pair partner outfits should follow clothing reroll",
)
def smoke_prompt_route_simulation_policy() -> None:
report = prompt_route_simulation.run_simulation(seed=3901, include_prompts=False)