Inherit hardcore template metadata
This commit is contained in:
@@ -250,6 +250,19 @@ def _template_axis_errors(path: str, node: dict[str, Any]) -> list[tuple[str, st
|
||||
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(
|
||||
value: Any,
|
||||
*,
|
||||
@@ -261,6 +274,10 @@ def _walk_json_references(
|
||||
) -> None:
|
||||
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 _container_template_metadata_errors(path, value)
|
||||
)
|
||||
for key, child in value.items():
|
||||
if at_root and key in POOL_DEFINITION_KEYS and isinstance(child, dict):
|
||||
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
|
||||
|
||||
|
||||
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]:
|
||||
if not path.exists():
|
||||
return set()
|
||||
@@ -445,6 +486,13 @@ def main() -> int:
|
||||
return 1
|
||||
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")
|
||||
routing_doc_errors = _routing_doc_errors()
|
||||
if routing_doc_errors:
|
||||
|
||||
@@ -3936,6 +3936,19 @@ def smoke_hardcore_position_config_policy() -> None:
|
||||
"generic contact",
|
||||
)
|
||||
_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(
|
||||
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("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")
|
||||
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 = {
|
||||
"action_family": "penetrative",
|
||||
"position_family": "Oral",
|
||||
|
||||
Reference in New Issue
Block a user