Inherit hardcore template metadata

This commit is contained in:
2026-06-27 14:56:08 +02:00
parent 29e5e65e5f
commit a8d69083cd
7 changed files with 244 additions and 6 deletions
+60
View File
@@ -92,6 +92,10 @@
"inherit_expressions": false, "inherit_expressions": false,
"inherit_compositions": false, "inherit_compositions": false,
"weight": 0.75, "weight": 0.75,
"item_template_metadata": {
"action_family": "foreplay",
"position_family": "foreplay"
},
"item_label": "Foreplay action", "item_label": "Foreplay action",
"positive_suffix": "Use clear adult body contact, readable hands and faces, visible undressing, exposed skin, warm erotic lighting, crisp comic linework, detailed hatching, and tactile textured paper.", "positive_suffix": "Use clear adult body contact, readable hands and faces, visible undressing, exposed skin, warm erotic lighting, crisp comic linework, detailed hatching, and tactile textured paper.",
"prompt_template": "{subject_phrase}, all 21+ consenting adults: {style}. Cast: {cast_summary}. Role graph: {role_graph} Foreplay action: {item}. Setting: {scene}. Composition: {composition}. Facial expressions: {expression}. Make the scene explicit through adult body contact, kissing, caressing, undressing, visible arousal, exposed skin, and readable hand placement. {positive_suffix} Avoid: {negative_prompt}.", "prompt_template": "{subject_phrase}, all 21+ consenting adults: {style}. Cast: {cast_summary}. Role graph: {role_graph} Foreplay action: {item}. Setting: {scene}. Composition: {composition}. Facial expressions: {expression}. Make the scene explicit through adult body contact, kissing, caressing, undressing, visible arousal, exposed skin, and readable hand placement. {positive_suffix} Avoid: {negative_prompt}.",
@@ -211,6 +215,10 @@
"inherit_expressions": false, "inherit_expressions": false,
"inherit_compositions": false, "inherit_compositions": false,
"weight": 0.85, "weight": 0.85,
"item_template_metadata": {
"action_family": "foreplay",
"position_family": "manual"
},
"item_label": "Manual action", "item_label": "Manual action",
"positive_suffix": "Use clear adult manual contact, readable hands, explicit body positioning, exposed skin, warm erotic lighting, crisp comic linework, detailed hatching, and tactile textured paper.", "positive_suffix": "Use clear adult manual contact, readable hands, explicit body positioning, exposed skin, warm erotic lighting, crisp comic linework, detailed hatching, and tactile textured paper.",
"prompt_template": "{subject_phrase}, all 21+ consenting adults: {style}. Cast: {cast_summary}. Role graph: {role_graph} Manual action: {item}. Setting: {scene}. Composition: {composition}. Facial expressions: {expression}. Make the scene explicit through adult manual stimulation, visible hands, exposed skin, clear body positioning, and readable reaction. {positive_suffix} Avoid: {negative_prompt}.", "prompt_template": "{subject_phrase}, all 21+ consenting adults: {style}. Cast: {cast_summary}. Role graph: {role_graph} Manual action: {item}. Setting: {scene}. Composition: {composition}. Facial expressions: {expression}. Make the scene explicit through adult manual stimulation, visible hands, exposed skin, clear body positioning, and readable reaction. {positive_suffix} Avoid: {negative_prompt}.",
@@ -316,6 +324,10 @@
"inherit_expressions": false, "inherit_expressions": false,
"inherit_compositions": false, "inherit_compositions": false,
"weight": 0.7, "weight": 0.7,
"item_template_metadata": {
"action_family": "foreplay",
"position_family": "interaction"
},
"item_label": "Body interaction", "item_label": "Body interaction",
"positive_suffix": "Use readable adult body contact, hands and mouth on skin, exposed skin, warm erotic lighting, crisp comic linework, detailed hatching, and tactile textured paper.", "positive_suffix": "Use readable adult body contact, hands and mouth on skin, exposed skin, warm erotic lighting, crisp comic linework, detailed hatching, and tactile textured paper.",
"prompt_template": "{subject_phrase}, all 21+ consenting adults: {style}. Cast: {cast_summary}. Role graph: {role_graph} Body interaction: {item}. Setting: {scene}. Composition: {composition}. Facial expressions: {expression}. Make the scene explicit through adult body worship, close skin contact, mouth and hand placement, exposed skin, and readable body positioning. {positive_suffix} Avoid: {negative_prompt}.", "prompt_template": "{subject_phrase}, all 21+ consenting adults: {style}. Cast: {cast_summary}. Role graph: {role_graph} Body interaction: {item}. Setting: {scene}. Composition: {composition}. Facial expressions: {expression}. Make the scene explicit through adult body worship, close skin contact, mouth and hand placement, exposed skin, and readable body positioning. {positive_suffix} Avoid: {negative_prompt}.",
@@ -425,6 +437,10 @@
"inherit_expressions": false, "inherit_expressions": false,
"inherit_compositions": false, "inherit_compositions": false,
"weight": 0.65, "weight": 0.65,
"item_template_metadata": {
"action_family": "foreplay",
"position_family": "interaction"
},
"item_label": "Transition action", "item_label": "Transition action",
"positive_suffix": "Use readable adult movement, clothing being moved, hands guiding bodies, exposed skin, warm erotic lighting, crisp comic linework, detailed hatching, and tactile textured paper.", "positive_suffix": "Use readable adult movement, clothing being moved, hands guiding bodies, exposed skin, warm erotic lighting, crisp comic linework, detailed hatching, and tactile textured paper.",
"prompt_template": "{subject_phrase}, all 21+ consenting adults: {style}. Cast: {cast_summary}. Role graph: {role_graph} Transition action: {item}. Setting: {scene}. Composition: {composition}. Facial expressions: {expression}. Make the scene explicit through adult undressing, position changes, visible hands, exposed skin, and clear movement from one sexual beat to the next. {positive_suffix} Avoid: {negative_prompt}.", "prompt_template": "{subject_phrase}, all 21+ consenting adults: {style}. Cast: {cast_summary}. Role graph: {role_graph} Transition action: {item}. Setting: {scene}. Composition: {composition}. Facial expressions: {expression}. Make the scene explicit through adult undressing, position changes, visible hands, exposed skin, and clear movement from one sexual beat to the next. {positive_suffix} Avoid: {negative_prompt}.",
@@ -530,6 +546,10 @@
"inherit_expressions": false, "inherit_expressions": false,
"inherit_compositions": false, "inherit_compositions": false,
"weight": 0.55, "weight": 0.55,
"item_template_metadata": {
"action_family": "foreplay",
"position_family": "interaction"
},
"item_label": "Guidance action", "item_label": "Guidance action",
"positive_suffix": "Use consensual adult control, readable hand placement, clear body positioning, exposed skin, warm erotic lighting, crisp comic linework, detailed hatching, and tactile textured paper.", "positive_suffix": "Use consensual adult control, readable hand placement, clear body positioning, exposed skin, warm erotic lighting, crisp comic linework, detailed hatching, and tactile textured paper.",
"prompt_template": "{subject_phrase}, all 21+ consenting adults: {style}. Cast: {cast_summary}. Role graph: {role_graph} Guidance action: {item}. Setting: {scene}. Composition: {composition}. Facial expressions: {expression}. Make the scene explicit through consensual adult guidance, hair or wrist control, body positioning, visible hands, exposed skin, and clear power dynamic. {positive_suffix} Avoid: {negative_prompt}.", "prompt_template": "{subject_phrase}, all 21+ consenting adults: {style}. Cast: {cast_summary}. Role graph: {role_graph} Guidance action: {item}. Setting: {scene}. Composition: {composition}. Facial expressions: {expression}. Make the scene explicit through consensual adult guidance, hair or wrist control, body positioning, visible hands, exposed skin, and clear power dynamic. {positive_suffix} Avoid: {negative_prompt}.",
@@ -640,6 +660,10 @@
"inherit_expressions": false, "inherit_expressions": false,
"inherit_compositions": false, "inherit_compositions": false,
"weight": 0.6, "weight": 0.6,
"item_template_metadata": {
"action_family": "foreplay",
"position_family": "interaction"
},
"item_label": "Camera performance", "item_label": "Camera performance",
"positive_suffix": "Use creator-shot adult presentation, readable camera-facing pose, exposed skin, clear hand placement, warm erotic lighting, crisp comic linework, detailed hatching, and tactile textured paper.", "positive_suffix": "Use creator-shot adult presentation, readable camera-facing pose, exposed skin, clear hand placement, warm erotic lighting, crisp comic linework, detailed hatching, and tactile textured paper.",
"prompt_template": "{subject_phrase}, all 21+ consenting adults: {style}. Cast: {cast_summary}. Role graph: {role_graph} Camera performance: {item}. Setting: {scene}. Composition: {composition}. Facial expressions: {expression}. Make the scene explicit through camera-aware adult presentation, body opened or displayed to the viewer, visible hands, exposed skin, and clean creator-shot framing. {positive_suffix} Avoid: {negative_prompt}.", "prompt_template": "{subject_phrase}, all 21+ consenting adults: {style}. Cast: {cast_summary}. Role graph: {role_graph} Camera performance: {item}. Setting: {scene}. Composition: {composition}. Facial expressions: {expression}. Make the scene explicit through camera-aware adult presentation, body opened or displayed to the viewer, visible hands, exposed skin, and clean creator-shot framing. {positive_suffix} Avoid: {negative_prompt}.",
@@ -744,6 +768,10 @@
"inherit_expressions": false, "inherit_expressions": false,
"inherit_compositions": false, "inherit_compositions": false,
"weight": 0.55, "weight": 0.55,
"item_template_metadata": {
"action_family": "foreplay",
"position_family": "interaction"
},
"item_label": "Group interaction", "item_label": "Group interaction",
"positive_suffix": "Use readable adult group coordination, clear body spacing, visible watching/touching roles, exposed skin, warm erotic lighting, crisp comic linework, detailed hatching, and tactile textured paper.", "positive_suffix": "Use readable adult group coordination, clear body spacing, visible watching/touching roles, exposed skin, warm erotic lighting, crisp comic linework, detailed hatching, and tactile textured paper.",
"prompt_template": "{subject_phrase}, all 21+ consenting adults: {style}. Cast: {cast_summary}. Role graph: {role_graph} Group interaction: {item}. Setting: {scene}. Composition: {composition}. Facial expressions: {expression}. Make the scene explicit through adult group coordination, watching, guiding hands, body presentation, exposed skin, and clear role spacing. {positive_suffix} Avoid: {negative_prompt}.", "prompt_template": "{subject_phrase}, all 21+ consenting adults: {style}. Cast: {cast_summary}. Role graph: {role_graph} Group interaction: {item}. Setting: {scene}. Composition: {composition}. Facial expressions: {expression}. Make the scene explicit through adult group coordination, watching, guiding hands, body presentation, exposed skin, and clear role spacing. {positive_suffix} Avoid: {negative_prompt}.",
@@ -846,6 +874,10 @@
"inherit_expressions": false, "inherit_expressions": false,
"inherit_compositions": false, "inherit_compositions": false,
"weight": 0.35, "weight": 0.35,
"item_template_metadata": {
"action_family": "foreplay",
"position_family": "interaction"
},
"item_label": "Aftermath interaction", "item_label": "Aftermath interaction",
"positive_suffix": "Use adult post-sex intimacy, readable bodies and hands, visible aftermath details, warm erotic lighting, crisp comic linework, detailed hatching, and tactile textured paper.", "positive_suffix": "Use adult post-sex intimacy, readable bodies and hands, visible aftermath details, warm erotic lighting, crisp comic linework, detailed hatching, and tactile textured paper.",
"prompt_template": "{subject_phrase}, all 21+ consenting adults: {style}. Cast: {cast_summary}. Role graph: {role_graph} Aftermath interaction: {item}. Setting: {scene}. Composition: {composition}. Facial expressions: {expression}. Make the scene explicit through adult post-sex closeness, cleanup, visible skin, relaxed body contact, aftermath details, and readable hands and faces. {positive_suffix} Avoid: {negative_prompt}.", "prompt_template": "{subject_phrase}, all 21+ consenting adults: {style}. Cast: {cast_summary}. Role graph: {role_graph} Aftermath interaction: {item}. Setting: {scene}. Composition: {composition}. Facial expressions: {expression}. Make the scene explicit through adult post-sex closeness, cleanup, visible skin, relaxed body contact, aftermath details, and readable hands and faces. {positive_suffix} Avoid: {negative_prompt}.",
@@ -950,6 +982,10 @@
"inherit_expressions": false, "inherit_expressions": false,
"inherit_compositions": false, "inherit_compositions": false,
"weight": 1.0, "weight": 1.0,
"item_template_metadata": {
"action_family": "penetration",
"position_family": "penetrative"
},
"scene_pools": ["hardcore_penetrative_scenes", "hardcore_bed_scenes", "hardcore_mirror_scenes"], "scene_pools": ["hardcore_penetrative_scenes", "hardcore_bed_scenes", "hardcore_mirror_scenes"],
"expression_pools": ["hardcore_penetration_expressions"], "expression_pools": ["hardcore_penetration_expressions"],
"composition_pools": ["penetration_compositions"], "composition_pools": ["penetration_compositions"],
@@ -1123,6 +1159,10 @@
"inherit_expressions": false, "inherit_expressions": false,
"inherit_compositions": false, "inherit_compositions": false,
"weight": 1.0, "weight": 1.0,
"item_template_metadata": {
"action_family": "oral",
"position_family": "oral"
},
"scene_pools": ["hardcore_oral_scenes", "hardcore_bed_scenes", "hardcore_mirror_scenes"], "scene_pools": ["hardcore_oral_scenes", "hardcore_bed_scenes", "hardcore_mirror_scenes"],
"expression_pools": ["hardcore_oral_expressions"], "expression_pools": ["hardcore_oral_expressions"],
"composition_pools": ["oral_compositions"], "composition_pools": ["oral_compositions"],
@@ -1266,6 +1306,10 @@
"inherit_expressions": false, "inherit_expressions": false,
"inherit_compositions": false, "inherit_compositions": false,
"weight": 1.0, "weight": 1.0,
"item_template_metadata": {
"action_family": "outercourse",
"position_family": "outercourse"
},
"scene_pools": ["hardcore_private_scenes", "hardcore_bed_scenes", "hardcore_mirror_scenes"], "scene_pools": ["hardcore_private_scenes", "hardcore_bed_scenes", "hardcore_mirror_scenes"],
"expression_pools": ["hardcore_outercourse_expressions"], "expression_pools": ["hardcore_outercourse_expressions"],
"compositions": [ "compositions": [
@@ -1420,6 +1464,10 @@
"inherit_expressions": false, "inherit_expressions": false,
"inherit_compositions": false, "inherit_compositions": false,
"weight": 1.0, "weight": 1.0,
"item_template_metadata": {
"action_family": "default",
"position_family": "anal"
},
"scene_pools": ["hardcore_anal_scenes", "hardcore_bed_scenes", "hardcore_mirror_scenes"], "scene_pools": ["hardcore_anal_scenes", "hardcore_bed_scenes", "hardcore_mirror_scenes"],
"expression_pools": ["hardcore_anal_dp_expressions"], "expression_pools": ["hardcore_anal_dp_expressions"],
"composition_pools": ["anal_dp_compositions"], "composition_pools": ["anal_dp_compositions"],
@@ -1639,6 +1687,10 @@
"inherit_expressions": false, "inherit_expressions": false,
"inherit_compositions": false, "inherit_compositions": false,
"weight": 1.0, "weight": 1.0,
"item_template_metadata": {
"action_family": "default",
"position_family": "threesome"
},
"scene_pools": ["hardcore_threesome_scenes", "hardcore_group_scenes", "hardcore_mirror_scenes"], "scene_pools": ["hardcore_threesome_scenes", "hardcore_group_scenes", "hardcore_mirror_scenes"],
"expression_pools": ["hardcore_group_expressions"], "expression_pools": ["hardcore_group_expressions"],
"composition_pools": ["threesome_compositions"], "composition_pools": ["threesome_compositions"],
@@ -1822,6 +1874,10 @@
"inherit_expressions": false, "inherit_expressions": false,
"inherit_compositions": false, "inherit_compositions": false,
"weight": 1.0, "weight": 1.0,
"item_template_metadata": {
"action_family": "default",
"position_family": "group"
},
"scene_pools": ["hardcore_group_scenes"], "scene_pools": ["hardcore_group_scenes"],
"expression_pools": ["hardcore_group_expressions"], "expression_pools": ["hardcore_group_expressions"],
"composition_pools": ["group_sex_compositions"], "composition_pools": ["group_sex_compositions"],
@@ -1994,6 +2050,10 @@
"inherit_expressions": false, "inherit_expressions": false,
"inherit_compositions": false, "inherit_compositions": false,
"weight": 1.0, "weight": 1.0,
"item_template_metadata": {
"action_family": "climax",
"position_family": "climax"
},
"scene_pools": ["hardcore_climax_scenes", "hardcore_bed_scenes", "hardcore_mirror_scenes"], "scene_pools": ["hardcore_climax_scenes", "hardcore_bed_scenes", "hardcore_mirror_scenes"],
"expression_pools": ["hardcore_climax_expressions"], "expression_pools": ["hardcore_climax_expressions"],
"composition_pools": ["climax_compositions"], "composition_pools": ["climax_compositions"],
+37
View File
@@ -34,6 +34,43 @@ def template_metadata(item: Any) -> dict[str, Any]:
return {key: item[key] for key in TEMPLATE_METADATA_KEYS if key in item} return {key: item[key] for key in TEMPLATE_METADATA_KEYS if key in item}
def merge_template_metadata(*metadata_values: Any) -> dict[str, Any]:
merged: dict[str, Any] = {}
for value in metadata_values:
metadata = template_metadata(value)
if not metadata:
continue
for key in ("action_family", "action_type", "family", "position_family", "position_key"):
if str(metadata.get(key) or "").strip():
merged[key] = metadata[key]
if metadata.get("position_keys") is not None:
merged["position_keys"] = merge_position_keys(
template_position_keys(merged),
template_position_keys(metadata),
)
hint_map = formatter_hints(metadata)
if hint_map:
existing = formatter_hints(merged)
for route, hints in hint_map.items():
for hint in hints:
if hint not in existing.setdefault(route, []):
existing[route].append(hint)
merged["formatter_hint"] = existing
return merged
def inherited_template_metadata(*containers: Any) -> dict[str, Any]:
metadata_parts: list[dict[str, Any]] = []
for container in containers:
if not isinstance(container, dict):
continue
nested = container.get("item_template_metadata")
if isinstance(nested, dict):
metadata_parts.append(nested)
metadata_parts.append(container)
return merge_template_metadata(*metadata_parts)
def template_position_family(metadata: dict[str, Any]) -> str: def template_position_family(metadata: dict[str, Any]) -> str:
return normalize_hardcore_position_family( return normalize_hardcore_position_family(
metadata.get("position_family") or metadata.get("family"), metadata.get("position_family") or metadata.get("family"),
+9 -3
View File
@@ -75,7 +75,7 @@ Core helper ownership:
| `builder_prompt_route.py` | Single-prompt builder orchestration, input normalization, seed-axis setup, built-in/custom row routing, legacy location/composition handling, camera application, and final prompt-row normalization. | | `builder_prompt_route.py` | Single-prompt builder orchestration, input normalization, seed-axis setup, built-in/custom row routing, legacy location/composition handling, camera application, and final prompt-row normalization. |
| `builder_config_route.py` | Config-driven prompt-builder request parsing, category/cast/profile/filter helper-node mapping, and direct `build_prompt` kwarg assembly. | | `builder_config_route.py` | Config-driven prompt-builder request parsing, category/cast/profile/filter helper-node mapping, and direct `build_prompt` kwarg assembly. |
| `category_extensions.py` | JSON `pool_extensions`, legacy pool patching, built-in category choice lists, and category/subcategory UI choices. | | `category_extensions.py` | JSON `pool_extensions`, legacy pool patching, built-in category choice lists, and category/subcategory UI choices. |
| `category_template_metadata.py` | Object-style item-template metadata extraction, action/position family normalization, position-key normalization, key merging, and audit validation errors. | | `category_template_metadata.py` | Object-style and inherited item-template metadata extraction, action/position family normalization, position-key normalization, key merging, formatter-hint merging, and audit validation errors. |
| `row_item.py` | Row item selection, weighted item/pair choice, item-template axis filling, and oral/outercourse axis compatibility filters. | | `row_item.py` | Row item selection, weighted item/pair choice, item-template axis filling, and oral/outercourse axis compatibility filters. |
| `row_category_route.py` | Row category/subcategory/item route resolution behind `CategoryItemRoute`, hardcore position-category filtering, cast-count adjustment, pose-vs-content seed-axis choice, item metadata collection, legacy dict compatibility, and pose-category item sanitizing. | | `row_category_route.py` | Row category/subcategory/item route resolution behind `CategoryItemRoute`, hardcore position-category filtering, cast-count adjustment, pose-vs-content seed-axis choice, item metadata collection, legacy dict compatibility, and pose-category item sanitizing. |
| `row_rendering.py` | Row prompt/caption text-field resolution, template selection, safe formatting, default prompt templates, configured-cast descriptor insertion, and POV directive insertion. | | `row_rendering.py` | Row prompt/caption text-field resolution, template selection, safe formatting, default prompt templates, configured-cast descriptor insertion, and POV directive insertion. |
@@ -266,6 +266,12 @@ Important JSON keys:
map keyed by `krea`, `sdxl`, or `caption`; aliases such as `krea2` and map keyed by `krea`, `sdxl`, or `caption`; aliases such as `krea2` and
`training_caption` are normalized by `category_template_metadata.py` and `training_caption` are normalized by `category_template_metadata.py` and
consumed only by the matching formatter route plus the shared `all` route. consumed only by the matching formatter route plus the shared `all` route.
- `item_template_metadata`: optional default route metadata on a category,
subcategory, or item. String templates inherit it, and object templates can
override it while formatter hints merge.
- For mixed hardcore subcategories, `action_family: default` keeps the explicit
position family while allowing `row_route_metadata.py` to infer the semantic
action family from the selected action/role text.
- `axes`: values used to fill `item_templates`. - `axes`: values used to fill `item_templates`.
- `scene_pool` / `scene_pools` or direct `scenes`: location road. - `scene_pool` / `scene_pools` or direct `scenes`: location road.
- `expression_pool` / `expression_pools` or direct `expressions`: expression road. - `expression_pool` / `expression_pools` or direct `expressions`: expression road.
@@ -505,8 +511,8 @@ plain prompt text. When debugging, inspect these fields before editing pools.
| `content_seed_axis` | `row_category_route.select_category_item_route` | Debug | Shows whether the item/action was driven by `content` or `pose`. Critical for hardcore pose categories. | | `content_seed_axis` | `row_category_route.select_category_item_route` | Debug | Shows whether the item/action was driven by `content` or `pose`. Critical for hardcore pose categories. |
| `item` | `row_category_route.select_category_item_route` or Insta override | Krea/SDXL/Naturalizer | Clothing item, category item, or sexual scene/action text. | | `item` | `row_category_route.select_category_item_route` or Insta override | Krea/SDXL/Naturalizer | Clothing item, category item, or sexual scene/action text. |
| `item_axis_values` | `row_category_route.select_category_item_route` | Krea hardcore rewrite, SDXL tags | Filled template axes such as position/action/detail values. | | `item_axis_values` | `row_category_route.select_category_item_route` | Krea hardcore rewrite, SDXL tags | Filled template axes such as position/action/detail values. |
| `item_template_metadata` | `row_category_route.select_category_item_route` | Debug, Krea/SDXL/Naturalizer route metadata | Optional metadata from object-style item templates; currently used to prefer explicit action/position families and keys before inference. | | `item_template_metadata` | `row_category_route.select_category_item_route` | Debug, Krea/SDXL/Naturalizer route metadata | Metadata inherited from category/subcategory/item `item_template_metadata` plus selected object-template metadata; used to prefer explicit action/position families and keys before inference. |
| `formatter_hints` | `row_category_route.select_category_item_route` | Krea/SDXL/Naturalizer route specialization, debug | Normalized route-specific hints from object-style item templates, keyed by `all`, `krea`, `sdxl`, or `caption`; each formatter consumes `all` plus its own route only. | | `formatter_hints` | `row_category_route.select_category_item_route` | Krea/SDXL/Naturalizer route specialization, debug | Normalized route-specific hints inherited from template metadata, keyed by `all`, `krea`, `sdxl`, or `caption`; each formatter consumes `all` plus its own route only. |
| `action_family` | `row_route_metadata.resolve_action_position_route` | Krea hardcore rewrite, SDXL tags, natural captions, debug | Source-aware formatter semantic family such as `foreplay`, `outercourse`, `oral`, `penetration`, `toy_double`, or `climax`. | | `action_family` | `row_route_metadata.resolve_action_position_route` | Krea hardcore rewrite, SDXL tags, natural captions, debug | Source-aware formatter semantic family such as `foreplay`, `outercourse`, `oral`, `penetration`, `toy_double`, or `climax`. |
| `position_family` | `row_route_metadata.resolve_action_position_route` | Debug/filtering | Source/UI hardcore family selected by template metadata or subcategory, such as `manual`, `interaction`, `oral`, `anal`, or `climax`. | | `position_family` | `row_route_metadata.resolve_action_position_route` | Debug/filtering | Source/UI hardcore family selected by template metadata or subcategory, such as `manual`, `interaction`, `oral`, `anal`, or `climax`. |
| `position_key`, `position_keys` | `row_route_metadata.resolve_action_position_route` | Debug/future filters | Concrete position tokens from object-template metadata and inferred axes/role text, such as `kneeling`, `doggy`, `boobjob`, or `open_thighs`. | | `position_key`, `position_keys` | `row_route_metadata.resolve_action_position_route` | Debug/future filters | Concrete position tokens from object-template metadata and inferred axes/role text, such as `kneeling`, `doggy`, `boobjob`, or `open_thighs`. |
+11 -2
View File
@@ -309,6 +309,7 @@ def compose_item(
) -> tuple[str, str, dict[str, str], dict[str, Any]]: ) -> tuple[str, str, dict[str, str], dict[str, Any]]:
templates = category_policy.template_list(category, subcategory, item, "item_templates") templates = category_policy.template_list(category, subcategory, item, "item_templates")
axes = category_policy.merged_axes(category, subcategory, item) axes = category_policy.merged_axes(category, subcategory, item)
inherited_metadata = template_policy.inherited_template_metadata(category, subcategory, item)
if templates and axes: if templates and axes:
template_entry = weighted_choice(rng, category_policy.compatible_entries(templates, women_count, men_count)) template_entry = weighted_choice(rng, category_policy.compatible_entries(templates, women_count, men_count))
template = entry_text(template_entry) template = entry_text(template_entry)
@@ -339,5 +340,13 @@ def compose_item(
axis_values[name] = entry_text(weighted_choice(rng, values)) axis_values[name] = entry_text(weighted_choice(rng, values))
item_prompt = _format(template, axis_values).strip() item_prompt = _format(template, axis_values).strip()
name = item_name(item) or subcategory["name"] name = item_name(item) or subcategory["name"]
return item_prompt, name, axis_values, template_policy.template_metadata(template_entry) return (
return item_text(item), item_name(item), {}, template_policy.template_metadata(item) item_prompt,
name,
axis_values,
template_policy.merge_template_metadata(inherited_metadata, template_policy.template_metadata(template_entry)),
)
return item_text(item), item_name(item), {}, template_policy.merge_template_metadata(
inherited_metadata,
template_policy.template_metadata(item),
)
+2 -1
View File
@@ -83,7 +83,8 @@ def resolve_action_position_route_result(
template_policy.template_position_keys(metadata), template_policy.template_position_keys(metadata),
inferred_position_keys, inferred_position_keys,
) )
action_family = template_policy.template_action_family(metadata) explicit_action_family = template_policy.template_action_family(metadata)
action_family = "" if explicit_action_family == "default" else explicit_action_family
if not action_family: if not action_family:
action_family = source_hardcore_action_family( action_family = source_hardcore_action_family(
position_family, position_family,
+48
View File
@@ -250,6 +250,19 @@ def _template_axis_errors(path: str, node: dict[str, Any]) -> list[tuple[str, st
return errors return errors
def _container_template_metadata_errors(path: str, node: dict[str, Any]) -> list[tuple[str, str]]:
if "item_template_metadata" not in node:
return []
metadata = node.get("item_template_metadata")
if not isinstance(metadata, dict):
return [(f"{path}.item_template_metadata", "item_template_metadata must be an object")]
normalized = template_metadata_policy.template_metadata(metadata)
return [
(f"{path}.item_template_metadata", issue)
for issue in template_metadata_policy.template_metadata_errors(normalized)
]
def _walk_json_references( def _walk_json_references(
value: Any, value: Any,
*, *,
@@ -261,6 +274,10 @@ def _walk_json_references(
) -> None: ) -> None:
if isinstance(value, dict): if isinstance(value, dict):
errors.extend((file_name, item_path, issue) for item_path, issue in _template_axis_errors(path, value)) errors.extend((file_name, item_path, issue) for item_path, issue in _template_axis_errors(path, value))
errors.extend(
(file_name, item_path, issue)
for item_path, issue in _container_template_metadata_errors(path, value)
)
for key, child in value.items(): for key, child in value.items():
if at_root and key in POOL_DEFINITION_KEYS and isinstance(child, dict): if at_root and key in POOL_DEFINITION_KEYS and isinstance(child, dict):
for pool_name, pool_values in child.items(): for pool_name, pool_values in child.items():
@@ -318,6 +335,30 @@ def _json_reference_errors(paths: list[Path]) -> list[tuple[str, str, str]]:
return errors return errors
def _hardcore_template_metadata_errors(paths: list[Path]) -> list[tuple[str, str, str]]:
errors: list[tuple[str, str, str]] = []
for path in paths:
data = _load_category_json(path)
for category in data.get("categories") or []:
if str(category.get("slug") or "") != "hardcore_sexual_poses":
continue
for subcategory in category.get("subcategories") or []:
templates = subcategory.get("item_templates")
if not isinstance(templates, list) or not templates:
continue
sub_path = f"categories.{category.get('slug')}.subcategories.{subcategory.get('slug')}"
metadata = subcategory.get("item_template_metadata")
if not isinstance(metadata, dict):
errors.append((path.name, sub_path, "missing item_template_metadata default block"))
continue
normalized = template_metadata_policy.template_metadata(metadata)
if not template_metadata_policy.template_action_family(normalized):
errors.append((path.name, f"{sub_path}.item_template_metadata", "missing normalized action_family"))
if not template_metadata_policy.template_position_family(normalized):
errors.append((path.name, f"{sub_path}.item_template_metadata", "missing normalized position_family"))
return errors
def _smoke_case_names(path: Path) -> set[str]: def _smoke_case_names(path: Path) -> set[str]:
if not path.exists(): if not path.exists():
return set() return set()
@@ -445,6 +486,13 @@ def main() -> int:
return 1 return 1
print("OK: all JSON pool references and item template axes resolve.") print("OK: all JSON pool references and item template axes resolve.")
print("\n# Hardcore Template Metadata Validation")
hardcore_metadata_errors = _hardcore_template_metadata_errors(category_paths)
if hardcore_metadata_errors:
print_table(("File", "Path", "Issue"), hardcore_metadata_errors)
return 1
print("OK: hardcore template subcategories define explicit route metadata defaults.")
print("\n# Routing Documentation Validation") print("\n# Routing Documentation Validation")
routing_doc_errors = _routing_doc_errors() routing_doc_errors = _routing_doc_errors()
if routing_doc_errors: if routing_doc_errors:
+77
View File
@@ -3936,6 +3936,19 @@ def smoke_hardcore_position_config_policy() -> None:
"generic contact", "generic contact",
) )
_expect(source_action_family == "outercourse", "Source action-family fallback should accept hyphenated source aliases") _expect(source_action_family == "outercourse", "Source action-family fallback should accept hyphenated source aliases")
default_action_route = row_route_metadata.resolve_action_position_route(
is_pose_category=True,
subcategory={"slug": "anal_double_penetration"},
hardcore_position_config=None,
item_template_metadata={"action_family": "default", "position_family": "anal"},
item_text="toy-assisted double penetration with front-and-back contact",
source_role_graph="one partner between two bodies",
source_composition="",
pose="",
item_axis_values={"double_act": "toy-assisted double penetration"},
)
_expect(default_action_route.get("position_family") == "anal", "Default-action metadata should preserve position family")
_expect(default_action_route.get("action_family") == "toy_double", "Default-action metadata should still allow action inference")
item_text, item_name, axis_values, template_metadata = pb._compose_item( item_text, item_name, axis_values, template_metadata = pb._compose_item(
random.Random(42), random.Random(42),
{}, {},
@@ -3974,6 +3987,70 @@ def smoke_hardcore_position_config_policy() -> None:
_expect(formatter_hints.get("krea") == ["keep mouth contact readable"], "Template metadata route lost Krea formatter hint") _expect(formatter_hints.get("krea") == ["keep mouth contact readable"], "Template metadata route lost Krea formatter hint")
_expect(formatter_hints.get("sdxl") == ["oral contact", "kneeling oral"], "Template metadata route lost SDXL formatter hints") _expect(formatter_hints.get("sdxl") == ["oral contact", "kneeling oral"], "Template metadata route lost SDXL formatter hints")
_expect(formatter_hints.get("caption") == ["oral contact caption detail"], "Template metadata route lost caption formatter hint") _expect(formatter_hints.get("caption") == ["oral contact caption detail"], "Template metadata route lost caption formatter hint")
inherited_text, _inherited_name, inherited_axis_values, inherited_metadata = pb._compose_item(
random.Random(42),
{},
{
"name": "Inherited metadata route",
"item_template_metadata": {
"action_family": "foreplay",
"position_family": "manual",
"position_keys": ["kneeling"],
"formatter_hint": {"caption": "inherited caption cue"},
},
"item_templates": ["{act} in {position}"],
"item_axes": {
"act": ["hand stimulation"],
"position": ["kneeling manual position"],
},
},
"Inherited metadata route",
women_count=1,
men_count=1,
)
_expect(inherited_text == "hand stimulation in kneeling manual position", "Inherited template metadata changed item text")
_expect(inherited_axis_values == {"act": "hand stimulation", "position": "kneeling manual position"}, "Inherited template metadata lost axis values")
_expect(inherited_metadata.get("action_family") == "foreplay", "String template did not inherit action family")
_expect(inherited_metadata.get("position_family") == "manual", "String template did not inherit position family")
_expect(pb._template_position_keys(inherited_metadata) == ["kneeling"], "String template did not inherit position keys")
_expect(
route_metadata.row_formatter_hints({"item_template_metadata": inherited_metadata}, "caption") == ["inherited caption cue"],
"String template did not inherit formatter hints",
)
override_text, _override_name, _override_axis_values, override_metadata = pb._compose_item(
random.Random(42),
{},
{
"name": "Override metadata route",
"item_template_metadata": {
"action_family": "foreplay",
"position_family": "manual",
"formatter_hint": {"all": "inherited shared cue"},
},
"item_templates": [
{
"template": "{act} in {position}",
"action_family": "oral",
"formatter_hint": {"krea2": "override krea cue"},
}
],
"item_axes": {
"act": ["mouth contact"],
"position": ["kneeling oral position"],
},
},
"Override metadata route",
women_count=1,
men_count=1,
)
_expect(override_text == "mouth contact in kneeling oral position", "Override template metadata changed item text")
_expect(override_metadata.get("action_family") == "oral", "Template object did not override inherited action family")
_expect(override_metadata.get("position_family") == "manual", "Template object should keep inherited position family when absent")
_expect(
route_metadata.row_formatter_hints({"item_template_metadata": override_metadata}, "krea")
== ["inherited shared cue", "override krea cue"],
"Template metadata did not merge inherited and template formatter hints",
)
route_row = { route_row = {
"action_family": "penetrative", "action_family": "penetrative",
"position_family": "Oral", "position_family": "Oral",