← Files Build 3D Game RoomsARCHIVED FILE
skills/build-3d-game-rooms/scripts/room_contracts.py
18.3 KB · Sep 30, 2026 · 23:15 UTC
#!/usr/bin/env python3
"""Shared contracts for the build-3d-game-rooms skill."""
from __future__ import annotations
import json
from pathlib import Path
SCHEMAS = {
"room-brief.json": "game-room.room-brief.v1",
"plan-metadata.json": "game-room.plan-metadata.v1",
"props.json": "game-room.prop-manifest.v1",
"room-layout.json": "game-room.layout.v1",
"openings.json": "game-room.opening-schedule.v1",
"milestone-reviews.json": "game-room.milestone-reviews.v1",
"final-report.json": "game-room.room-final-report.v1",
}
GATES = ("function", "form", "runtime")
FORM_CHECKS = (
"floorPlanParity", "primaryArrival", "symmetry", "shell", "manifold", "booleans",
"collisions", "support", "attachmentOrientation", "ceilingAttachmentOrientation",
"seatingOrientation", "focalDistribution", "heroClearance", "colonnades",
"screenClearance", "scale", "camera", "ceilingCoverage", "environmentCoverage", "provenance",
)
RUNTIME_CHECKS = ("vlow", "textureTiers", "lightmap", "lightingManifest", "performance")
def load_json(path: Path):
try:
return json.loads(path.read_text(encoding="utf-8"))
except FileNotFoundError:
raise ValueError(f"missing file: {path}") from None
except json.JSONDecodeError as error:
raise ValueError(f"invalid JSON in {path}: {error}") from None
def nonempty(value):
return isinstance(value, str) and bool(value.strip())
def positive_vector(value, count):
return isinstance(value, list) and len(value) == count and all(isinstance(item, (int, float)) and item > 0 for item in value)
def existing_relative(package: Path, value):
return nonempty(value) and (package / value).is_file()
def validate_package(package: Path, gate: str = "runtime"):
if gate not in GATES:
raise ValueError(f"unknown gate {gate!r}")
errors = []
docs = {}
for filename, schema in SCHEMAS.items():
path = package / filename
try:
docs[filename] = load_json(path)
except ValueError as error:
errors.append(str(error))
continue
if docs[filename].get("schema") != schema:
errors.append(f"{filename}: expected schema {schema}")
if errors:
return errors
brief = docs["room-brief.json"]
plan = docs["plan-metadata.json"]
props = docs["props.json"]
layout = docs["room-layout.json"]
openings = docs["openings.json"]
reviews = docs["milestone-reviews.json"]
report = docs["final-report.json"]
room_id = brief.get("roomId")
if not nonempty(room_id):
errors.append("room-brief.json: roomId is required")
for filename, document in docs.items():
if document.get("roomId") != room_id:
errors.append(f"{filename}: roomId does not match room brief")
if plan.get("style") != "monochrome-engineering" or plan.get("projectionSource") != "actual-meshes":
errors.append("plan-metadata.json: plans must be monochrome engineering projections of actual meshes")
if plan.get("layers") != ["lower-room", "reflected-ceiling"]:
errors.append("plan-metadata.json: lower-room and reflected-ceiling layers are required")
if plan.get("semanticLabelsOutsideImage") is not True:
errors.append("plan-metadata.json: semantic labels must remain outside the plan image")
if plan.get("aiInspirationIncluded") is True and not nonempty(plan.get("aiInspirationReason")):
errors.append("plan-metadata.json: included AI inspiration requires an explicit reason")
required_plan_coverage = {"room-outline", "cutouts", "openings", "exterior-apron", "main-sign", "ingress-props", "floor-markers", "barriers", "columns", "circulation", "placed-features"}
if set(plan.get("coverage", [])) != required_plan_coverage:
errors.append("plan-metadata.json: complete reviewed-space coverage is required")
if brief.get("family") not in {"display-gallery", "gameplay-room", "social-hub", "custom"}:
errors.append("room-brief.json: invalid family")
for field in ("purpose", "runtimeSurface"):
if not nonempty(brief.get(field)):
errors.append(f"room-brief.json: {field} is required")
if not positive_vector(brief.get("boundsMeters"), 3):
errors.append("room-brief.json: boundsMeters must contain three positive values")
hero = brief.get("hero", {})
if not nonempty(hero.get("id")) or not nonempty(hero.get("role")) or not positive_vector(hero.get("reservedBoundsMeters"), 3):
errors.append("room-brief.json: complete hero contract is required")
symmetry = brief.get("symmetry", {})
symmetry_mode = symmetry.get("mode")
symmetry_order = symmetry.get("order")
symmetry_targets = symmetry.get("appliesTo")
symmetry_exceptions = symmetry.get("exceptions")
if symmetry_mode not in {"none", "bilateral", "radial"}:
errors.append("room-brief.json: symmetry.mode must be none, bilateral, or radial")
if not isinstance(symmetry_order, int) or isinstance(symmetry_order, bool):
errors.append("room-brief.json: symmetry.order must be an integer")
elif ((symmetry_mode == "none" and symmetry_order != 1)
or (symmetry_mode == "bilateral" and symmetry_order != 2)
or (symmetry_mode == "radial" and symmetry_order < 3)):
errors.append("room-brief.json: symmetry.order must be 1 for none, 2 for bilateral, or at least 3 for radial")
allowed_symmetry_targets = {"architecture", "barriers", "circulation", "repeatedProps"}
if not isinstance(symmetry_targets, list) or any(item not in allowed_symmetry_targets for item in symmetry_targets):
errors.append("room-brief.json: symmetry.appliesTo contains an invalid target")
elif symmetry_mode in {"bilateral", "radial"} and set(symmetry_targets) != allowed_symmetry_targets:
errors.append("room-brief.json: declared symmetry must apply to architecture, barriers, circulation, and repeatedProps")
if not isinstance(symmetry_exceptions, list) or any(not nonempty(item) for item in symmetry_exceptions):
errors.append("room-brief.json: symmetry.exceptions must be a list of non-empty explanations")
camera = brief.get("productionCamera", {})
if not positive_vector(camera.get("viewport"), 2) or not positive_vector([camera.get("verticalFovDegrees", 0)], 1):
errors.append("room-brief.json: production camera viewport/FOV are required")
for vector in ("location", "target"):
if not isinstance(camera.get(vector), list) or len(camera[vector]) != 3:
errors.append(f"room-brief.json: productionCamera.{vector} requires three numbers")
artifacts = brief.get("artifacts", {})
for field in ("floorPlan", "reflectedCeilingPlan", "planMetadata", "visualTarget", "openingContactSheet"):
if not existing_relative(package, artifacts.get(field)):
errors.append(f"Function: missing artifact {field}")
elevations = artifacts.get("wallElevations")
if not isinstance(elevations, list) or not elevations or any(not existing_relative(package, item) for item in elevations):
errors.append("Function: at least one existing wall elevation is required")
for key in ("maxSceneTriangles", "maxVLowBytes", "maxHighTextureBytes"):
if brief.get("performanceBudget", {}).get(key, 0) <= 0:
errors.append(f"room-brief.json: positive performanceBudget.{key} is required")
if brief.get("creditBudget", {}).get("maximumCredits", -1) < 0:
errors.append("room-brief.json: non-negative credit budget is required")
generation = props.get("generation", {})
if generation.get("provider") != "meshy" or generation.get("apiModel") != "meshy-5":
errors.append("props.json: generation must use Meshy model meshy-5")
if generation.get("pricingSource") != "https://docs.meshy.ai/en/api/pricing" or not nonempty(generation.get("pricingVerifiedAt")):
errors.append("props.json: current official Meshy pricing verification is required")
if not isinstance(generation.get("creditsPerTexturedTask"), int) or generation.get("creditsPerTexturedTask", 0) <= 0:
errors.append("props.json: positive creditsPerTexturedTask is required")
hard_ceiling = generation.get("hardCreditCeiling", 0)
if hard_ceiling <= 0 or hard_ceiling > brief.get("creditBudget", {}).get("maximumCredits", 0):
errors.append("props.json: hardCreditCeiling must be positive and no greater than the room credit budget")
if generation.get("maxChargedAttemptsPerAsset") != 2:
errors.append("props.json: maxChargedAttemptsPerAsset must equal 2")
asset_ids = set()
assets = props.get("assets", [])
if not assets:
errors.append("props.json: conceive at least one prop before Function approval")
for asset in assets:
asset_id = asset.get("id")
if not nonempty(asset_id) or asset_id in asset_ids:
errors.append("props.json: asset IDs must be unique and non-empty")
continue
asset_ids.add(asset_id)
for field in ("function", "description"):
if not nonempty(asset.get(field)):
errors.append(f"prop {asset_id}: {field} is required")
if asset.get("class") not in {"reusable", "hero"}:
errors.append(f"prop {asset_id}: class must be reusable or hero")
if not positive_vector(asset.get("dimensionsMeters"), 3):
errors.append(f"prop {asset_id}: three positive dimensions are required")
if asset.get("attachmentFace") not in {"floor", "wall", "ceiling", "freestanding"}:
errors.append(f"prop {asset_id}: attachmentFace is invalid")
if asset.get("semanticFront") not in {"positive-x", "negative-x", "positive-y", "negative-y", "positive-z", "negative-z"}:
errors.append(f"prop {asset_id}: semanticFront is invalid")
if not isinstance(asset.get("targetPolycount"), int) or not 100 <= asset.get("targetPolycount", 0) <= 300000:
errors.append(f"prop {asset_id}: targetPolycount must be 100-300000")
if not asset.get("rejectionCriteria") or any(not nonempty(item) for item in asset.get("rejectionCriteria", [])):
errors.append(f"prop {asset_id}: objective rejection criteria are required")
image = asset.get("image", {})
if not nonempty(image.get("prompt")) or not nonempty(image.get("generator")):
errors.append(f"prop {asset_id}: image prompt and generator are required")
if not existing_relative(package, image.get("source")):
errors.append(f"prop {asset_id}: approved reference image is missing")
if image.get("approved") is not True or not nonempty(image.get("approvedBy")):
errors.append(f"prop {asset_id}: reference image requires explicit approval")
if layout.get("units") != "meters" or not layout.get("surfaces"):
errors.append("room-layout.json: meter units and at least one shell surface are required")
surface_ids = [item.get("id") for item in layout.get("surfaces", [])]
if any(not nonempty(item) for item in surface_ids) or len(surface_ids) != len(set(surface_ids)):
errors.append("room-layout.json: surface IDs must be unique and non-empty")
instance_ids = [item.get("id") for item in layout.get("instances", [])]
if any(not nonempty(item) for item in instance_ids) or len(instance_ids) != len(set(instance_ids)):
errors.append("room-layout.json: instance IDs must be unique and non-empty")
counts = {asset_id: 0 for asset_id in asset_ids}
for instance in layout.get("instances", []):
asset_id = instance.get("assetId")
if asset_id not in asset_ids:
errors.append(f"room-layout.json: instance references unknown asset {asset_id!r}")
else:
counts[asset_id] += 1
for asset in assets:
if nonempty(asset.get("id")) and counts.get(asset["id"], 0) != asset.get("intendedInstances"):
errors.append(f"room-layout.json: instance count for {asset['id']} does not match intendedInstances")
environment = brief.get("environment", {})
if environment.get("type") not in {"none", "equirectangular"}:
errors.append("room-brief.json: environment.type must be none or equirectangular")
elif environment.get("type") == "equirectangular":
for field in ("identity", "assignmentKey"):
if not nonempty(environment.get(field)):
errors.append(f"room-brief.json: equirectangular environment requires {field}")
if not existing_relative(package, environment.get("source")):
errors.append("room-brief.json: equirectangular environment source is missing")
seen = set()
for opening in openings.get("openings", []):
opening_id = opening.get("id")
if not nonempty(opening_id) or opening_id in seen:
errors.append("openings.json: opening IDs must be unique and non-empty")
continue
seen.add(opening_id)
kind = opening.get("kind")
if kind not in {"door", "window", "deep_alcove"}:
errors.append(f"opening {opening_id}: invalid kind")
if opening.get("widthMeters", 0) <= 0 or opening.get("heightMeters", 0) <= 0:
errors.append(f"opening {opening_id}: positive dimensions are required")
if kind == "window" and opening.get("sillMeters", -1) < 0:
errors.append(f"opening {opening_id}: window requires sillMeters")
if kind == "deep_alcove":
minimum = max(0.35, 0.15 * opening.get("heightMeters", 0))
if opening.get("depthMeters", 0) < minimum:
errors.append(f"opening {opening_id}: alcove depth must be at least {minimum:.3f} m")
if not nonempty(opening.get("destination")):
errors.append(f"opening {opening_id}: destination/purpose is required")
for field in ("mask", "cutter"):
if not existing_relative(package, opening.get(field)):
errors.append(f"opening {opening_id}: missing {field} artifact")
if opening.get("approved") is not True:
errors.append(f"opening {opening_id}: mask/dimensions are not approved")
primary_arrival = openings.get("primaryArrival", {})
primary_id = primary_arrival.get("openingId")
primary_exception = primary_arrival.get("exception")
if nonempty(primary_id):
primary = next((item for item in openings.get("openings", []) if item.get("id") == primary_id), None)
if primary is None:
errors.append("openings.json: primaryArrival.openingId does not exist")
elif primary.get("kind") != "door":
errors.append("openings.json: primary arrival must be a door/passage opening")
elif primary.get("widthMeters", 0) < 2.4 and not nonempty(primary_exception):
errors.append("openings.json: primary arrival must be at least 2.4 m wide or have an exception")
elif not nonempty(primary_exception):
errors.append("openings.json: primary arrival opening or functional exception is required")
gate_index = GATES.index(gate)
for required_gate in GATES[: gate_index + 1]:
review = reviews.get(required_gate, {})
if review.get("status") != "approved" or not nonempty(review.get("approvedBy")) or not nonempty(review.get("approvedAt")):
errors.append(f"{required_gate.title()} gate: explicit approval is required")
if not review.get("evidence") or any(not existing_relative(package, item) for item in review.get("evidence", [])):
errors.append(f"{required_gate.title()} gate: existing evidence is required")
if not nonempty(review.get("strangestElement")):
errors.append(f"{required_gate.title()} gate: strangestElement review is required")
if report.get("gates", {}).get(required_gate) is not True:
errors.append(f"final-report.json: gates.{required_gate} must be true")
if gate_index >= 1:
actual_credits = 0
for asset in assets:
attempts = asset.get("attempts", [])
charged = [attempt for attempt in attempts if (attempt.get("consumedCredits") or 0) > 0]
actual_credits += sum(attempt.get("consumedCredits", 0) or 0 for attempt in attempts)
if len(charged) > 2:
errors.append(f"Form gate: prop {asset.get('id')} exceeds two charged Meshy attempts")
if any(attempt.get("status") in {"SUBMITTING", "SUBMITTED", "PENDING", "IN_PROGRESS"} for attempt in attempts):
errors.append(f"Form gate: prop {asset.get('id')} has unresolved Meshy work")
accepted = asset.get("acceptedMesh") or {}
if not existing_relative(package, accepted.get("path")) or not nonempty(accepted.get("sha256")) or not nonempty(accepted.get("taskId")):
errors.append(f"Form gate: prop {asset.get('id')} requires an accepted, hashed Meshy GLB")
if actual_credits > hard_ceiling:
errors.append("Form gate: actual Meshy credits exceed the hard ceiling")
for check in FORM_CHECKS:
if report.get("formChecks", {}).get(check) is not True:
errors.append(f"Form gate: formChecks.{check} must be true")
review_renders = report.get("reviewRenders", {})
corners = review_renders.get("corners", [])
ceilings = review_renders.get("ceiling", [])
if len(corners) != 4 or any(not existing_relative(package, item) for item in corners):
errors.append("Form gate: four existing 120-degree corner renders are required")
if len(ceilings) != 3 or any(not existing_relative(package, item) for item in ceilings):
errors.append("Form gate: center-up and two ceiling-oblique renders are required")
if environment.get("type") == "equirectangular":
directions = review_renders.get("environment", {})
for direction in ("front", "right", "rear", "left", "up", "down"):
if not existing_relative(package, directions.get(direction)):
errors.append(f"Form gate: environment-only {direction} render is required")
if gate_index >= 2:
for check in RUNTIME_CHECKS:
if report.get("runtimeChecks", {}).get(check) is not True:
errors.append(f"Runtime gate: runtimeChecks.{check} must be true")
capture = report.get("runtimeChecks", {}).get("browserCapture")
if not existing_relative(package, capture):
errors.append("Runtime gate: browser capture is required")
if report.get("runtimeChecks", {}).get("errors") != []:
errors.append("Runtime gate: browser errors must be empty")
return errors
SHA-256: f9a3a34183fa6f458723fbda3133ab32bcfad3b6675bcfa08212b8ff001a9e94