docs(plan): record M016 evidence deviation and complete milestone

This commit is contained in:
2026-09-09 23:03:17 +03:00
parent 37704028c5
commit 381eacf403
18 changed files with 352 additions and 31 deletions
+23
View File
@@ -0,0 +1,23 @@
# Maze32 command chamber
This package designs a 12 m × 12 m gameplay-room lobby with a single central
hero: the maze command table. Four stations give players a clear color-coded
place around a protected circulation ring. The open north portal is 3 m wide
and exposes a short arrival apron rather than implying a blocked route.
All final geometry is procedural Blender geometry: slabs, walls, column rhythm,
hero table, stations, ceiling beacon, and lighting. No external mesh or paid
generation is needed. `build_command_chamber.py` uses an Exact Boolean for the
north opening before adding the brass surround.
Run it with a Blender installation:
```sh
blender --background --python build_command_chamber.py -- --output maze32-command-chamber.blend --renders renders
```
The package is deliberately at the Function-review stage. The prop image and
opening schedule have **not** been marked approved, and no form or runtime
approval has been claimed. After a reviewer inspects the production and review
renders, record their explicit decision in `milestone-reviews.json`; then run
the skill validator for the relevant gate.
@@ -0,0 +1,209 @@
"""Build the procedural Maze32 command chamber in Blender 4.x.
Run from this package:
blender --background --python build_command_chamber.py -- --output maze32-command-chamber.blend
The scene intentionally uses no downloaded meshes or textures. The north entry
is cut from the wall with an Exact Boolean before its trim is added.
"""
import argparse
import math
import sys
from pathlib import Path
import bpy
from mathutils import Vector
def cli():
values = sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else []
parser = argparse.ArgumentParser()
parser.add_argument("--output", type=Path, default=Path("maze32-command-chamber.blend"))
parser.add_argument("--renders", type=Path, default=Path("renders"))
return parser.parse_args(values)
def clear_scene():
bpy.ops.object.select_all(action="SELECT")
bpy.ops.object.delete(use_global=False)
def material(name, color, metallic=0.0, roughness=0.5, emission=None):
result = bpy.data.materials.new(name)
result.use_nodes = True
node = result.node_tree.nodes["Principled BSDF"]
node.inputs["Base Color"].default_value = color
node.inputs["Metallic"].default_value = metallic
node.inputs["Roughness"].default_value = roughness
if emission:
node.inputs["Emission Color"].default_value = emission
node.inputs["Emission Strength"].default_value = 3.0
return result
SLATE = None
BRASS = None
CYAN = None
COLORS = []
def cube(name, location, dimensions, material_ref, bevel=0.0):
bpy.ops.mesh.primitive_cube_add(size=1, location=location)
obj = bpy.context.object
obj.name = name
obj.dimensions = dimensions
bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
if bevel:
modifier = obj.modifiers.new("Soft readable edges", "BEVEL")
modifier.width = bevel
modifier.segments = 3
obj.data.materials.append(material_ref)
return obj
def cylinder(name, location, radius, depth, material_ref, vertices=48):
bpy.ops.mesh.primitive_cylinder_add(vertices=vertices, radius=radius, depth=depth, location=location)
obj = bpy.context.object
obj.name = name
obj.data.materials.append(material_ref)
bevel = obj.modifiers.new("Edge highlights", "BEVEL")
bevel.width = 0.03
bevel.segments = 2
return obj
def point_camera(camera, target):
camera.rotation_euler = (Vector(target) - camera.location).to_track_quat("-Z", "Y").to_euler()
def boolean_door(wall):
cutter = cube("CUTTER_north-arrival", (0, 5.85, 1.55), (3.0, 0.8, 3.1), SLATE)
modifier = wall.modifiers.new("OPENING_north-arrival_EXACT", "BOOLEAN")
modifier.operation = "DIFFERENCE"
modifier.solver = "EXACT"
modifier.object = cutter
bpy.context.view_layer.objects.active = wall
bpy.ops.object.modifier_apply(modifier=modifier.name)
bpy.data.objects.remove(cutter, do_unlink=True)
def make_shell():
cube("SHELL_floor", (0, 0, -0.15), (12, 12, 0.3), SLATE, 0.05)
cube("SHELL_ceiling", (0, 0, 5.0), (12, 12, 0.35), SLATE, 0.05)
cube("SHELL_south_wall", (0, -5.85, 2.5), (12, 0.3, 5.0), SLATE, 0.05)
west = cube("SHELL_west_wall", (-5.85, 0, 2.5), (0.3, 12, 5.0), SLATE, 0.05)
east = cube("SHELL_east_wall", (5.85, 0, 2.5), (0.3, 12, 5.0), SLATE, 0.05)
north = cube("SHELL_north_wall", (0, 5.85, 2.5), (12, 0.3, 5.0), SLATE, 0.05)
boolean_door(north)
for x in (-1.72, 1.72):
cube("TRIM_north-arrival", (x, 5.62, 1.55), (0.18, 0.24, 3.3), BRASS, 0.03)
cube("TRIM_arrival-header", (0, 5.62, 3.22), (3.62, 0.24, 0.18), BRASS, 0.03)
cube("ENTRY_apron", (0, 6.8, -0.02), (3.0, 2.0, 0.08), BRASS, 0.02)
for x, y in ((-4.8, -4.8), (4.8, -4.8), (-4.8, 4.8), (4.8, 4.8)):
cylinder("ARCH_column", (x, y, 2.2), 0.28, 4.4, BRASS, 32)
def make_command_table():
cylinder("HERO_command_table_base", (0, 0, 0.32), 1.72, 0.64, SLATE)
cylinder("HERO_command_table_brass_ring", (0, 0, 0.68), 1.55, 0.12, BRASS)
cylinder("HERO_command_table_top", (0, 0, 0.76), 1.42, 0.12, SLATE)
cylinder("HERO_hologram_disc", (0, 0, 0.85), 1.16, 0.05, CYAN)
for angle in range(0, 360, 45):
radians = math.radians(angle)
x, y = 0.88 * math.cos(radians), 0.88 * math.sin(radians)
marker = cube("HERO_maze_segment", (x, y, 1.0), (0.42, 0.08, 0.3), CYAN, 0.02)
marker.rotation_euler[2] = radians + math.pi / 2
cylinder("HERO_maze_core", (0, 0, 1.08), 0.24, 0.48, CYAN, 32)
def make_station(name, location, rotation, color):
root = bpy.data.objects.new(name, None)
bpy.context.collection.objects.link(root)
base = cube(name + "_base", location, (1.1, 0.7, 0.18), SLATE, 0.04)
body = cube(name + "_body", (location[0], location[1], 0.55), (0.72, 0.42, 0.7), BRASS, 0.04)
marker = cylinder(name + "_color_marker", (location[0], location[1] - 0.24, 0.94), 0.19, 0.07, color, 32)
for part in (base, body, marker):
part.parent = root
root.rotation_euler[2] = math.radians(rotation)
root["attachment_face"] = "floor"
root["semantic_front"] = "inward"
def make_ceiling():
cylinder("CEILING_light_ring", (0, 0, 4.74), 2.25, 0.1, BRASS)
cylinder("CEILING_beacon", (0, 0, 4.6), 0.32, 0.34, CYAN)
for angle in range(0, 360, 90):
radians = math.radians(angle)
cube("CEILING_radial_trim", (3.5 * math.cos(radians), 3.5 * math.sin(radians), 4.73), (2.5, 0.1, 0.12), BRASS, 0.02).rotation_euler[2] = radians
def light(name, location, color, energy, size=2.0):
data = bpy.data.lights.new(name, "AREA")
data.energy = energy
data.color = color[:3]
data.shape = "DISK"
data.size = size
obj = bpy.data.objects.new(name, data)
bpy.context.collection.objects.link(obj)
obj.location = location
obj.rotation_euler = (0, 0, 0)
return obj
def render_views(camera, output):
scene = bpy.context.scene
scene.render.engine = "BLENDER_EEVEE_NEXT"
scene.render.resolution_x = 1280
scene.render.resolution_y = 720
scene.render.resolution_percentage = 100
scene.render.image_settings.file_format = "PNG"
views = {
"production": ((9.2, -10.2, 6.7), (0, 0, 1.5)),
"corner-nw": ((-5.0, 5.0, 3.7), (0, 0, 1.2)),
"corner-ne": ((5.0, 5.0, 3.7), (0, 0, 1.2)),
"corner-se": ((5.0, -5.0, 3.7), (0, 0, 1.2)),
"corner-sw": ((-5.0, -5.0, 3.7), (0, 0, 1.2)),
"ceiling-up": ((0, 0, 0.5), (0, 0, 5.0)),
}
output.mkdir(parents=True, exist_ok=True)
for name, (location, target) in views.items():
camera.location = location
point_camera(camera, target)
scene.render.filepath = str(output / (name + ".png"))
bpy.ops.render.render(write_still=True)
def main():
args = cli()
clear_scene()
global SLATE, BRASS, CYAN, COLORS
SLATE = material("MAT_slate", (0.055, 0.075, 0.11, 1), 0.12, 0.68)
BRASS = material("MAT_brass", (0.42, 0.22, 0.045, 1), 0.8, 0.28)
CYAN = material("MAT_hologram", (0.0, 0.55, 0.75, 1), 0.1, 0.25, (0.0, 0.75, 1.0, 1))
COLORS = [material("MAT_red", (0.85, 0.05, 0.05, 1), emission=(0.85, 0.02, 0.02, 1)), material("MAT_yellow", (0.9, 0.65, 0.02, 1), emission=(0.9, 0.5, 0.0, 1)), material("MAT_green", (0.05, 0.72, 0.25, 1), emission=(0.0, 0.65, 0.18, 1)), material("MAT_blue", (0.05, 0.28, 0.9, 1), emission=(0.02, 0.15, 0.85, 1))]
make_shell()
make_command_table()
make_station("STATION_north", (0, 3.95, 0.09), 180, COLORS[0])
make_station("STATION_east", (3.95, 0, 0.09), 90, COLORS[1])
make_station("STATION_south", (0, -3.95, 0.09), 0, COLORS[2])
make_station("STATION_west", (-3.95, 0, 0.09), -90, COLORS[3])
make_ceiling()
camera_data = bpy.data.cameras.new("ProductionCamera")
camera = bpy.data.objects.new("ProductionCamera", camera_data)
bpy.context.collection.objects.link(camera)
camera_data.lens = 35
bpy.context.scene.camera = camera
light("Key", (0, 0, 4.5), (0.7, 0.85, 1.0, 1), 900, 5.0)
light("Warm arrival", (0, 4.9, 3.8), (1.0, 0.5, 0.15, 1), 650, 2.5)
render_views(camera, args.renders)
scene = bpy.context.scene
scene["room_id"] = "maze32-command-chamber"
scene["room_family"] = "gameplay-room"
scene.unit_settings.system = "METRIC"
scene.unit_settings.scale_length = 1.0
bpy.ops.wm.save_as_mainfile(filepath=str(args.output.resolve()))
if __name__ == "__main__":
main()
@@ -0,0 +1,10 @@
{
"schema": "game-room.room-final-report.v1",
"roomId": "maze32-command-chamber",
"gates": {"function": false, "form": false, "runtime": false},
"formChecks": {"floorPlanParity": false, "primaryArrival": false, "symmetry": false, "shell": false, "manifold": false, "booleans": false, "collisions": false, "support": false, "attachmentOrientation": false, "ceilingAttachmentOrientation": false, "seatingOrientation": false, "focalDistribution": false, "heroClearance": false, "colonnades": false, "screenClearance": false, "scale": false, "camera": false, "ceilingCoverage": false, "environmentCoverage": false, "provenance": false},
"reviewRenders": {"corners": [], "ceiling": [], "environment": {}},
"runtimeChecks": {"vlow": false, "textureTiers": false, "lightmap": false, "lightingManifest": false, "performance": false, "browserCapture": "", "errors": []},
"postmortem": {"observations": [], "corrections": [], "remainingRisks": []},
"proposedLessons": []
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

@@ -0,0 +1,8 @@
{
"id": "north-arrival",
"shape": "rectangular-door",
"centerMeters": [0, 5.85, 1.55],
"dimensionsMeters": [3.0, 0.8, 3.1],
"wall": "north",
"operation": "EXACT_BOOLEAN_DIFFERENCE"
}
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="600" height="400" viewBox="0 0 600 400"><rect width="600" height="400" fill="white"/><rect x="150" y="60" width="300" height="310" fill="black"/><path d="M150 370V60H450V370" fill="none" stroke="#777" stroke-width="4"/><text x="300" y="35" text-anchor="middle" font-family="sans-serif" font-size="20">north-arrival: 3.0 m × 3.1 m</text></svg>

After

Width:  |  Height:  |  Size: 391 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="900" height="560" viewBox="0 0 900 560"><rect width="900" height="560" fill="white"/><text x="40" y="48" font-family="sans-serif" font-size="26" fill="black">Opening mask contact sheet</text><rect x="80" y="110" width="330" height="340" fill="#111"/><path d="M80 450V110H410V450" fill="none" stroke="#777" stroke-width="5"/><text x="245" y="495" text-anchor="middle" font-family="sans-serif" font-size="22">north-arrival · door · 3.0 × 3.1 m</text><text x="500" y="170" font-family="sans-serif" font-size="20" fill="black">Cutter: rectangular Exact Boolean</text><text x="500" y="210" font-family="sans-serif" font-size="20" fill="black">Wall: north</text><text x="500" y="250" font-family="sans-serif" font-size="20" fill="black">Passage depth: 0.8 m</text><text x="500" y="290" font-family="sans-serif" font-size="20" fill="black">Destination: entry apron</text></svg>

After

Width:  |  Height:  |  Size: 921 B

@@ -0,0 +1,7 @@
{
"schema": "game-room.milestone-reviews.v1",
"roomId": "maze32-command-chamber",
"function": {"status": "pending", "approvedBy": "", "approvedAt": "", "evidence": [], "strangestElement": "", "notes": ""},
"form": {"status": "pending", "approvedBy": "", "approvedAt": "", "evidence": [], "strangestElement": "", "notes": ""},
"runtime": {"status": "pending", "approvedBy": "", "approvedAt": "", "evidence": [], "strangestElement": "", "notes": ""}
}
@@ -0,0 +1,8 @@
{
"schema": "game-room.opening-schedule.v1",
"roomId": "maze32-command-chamber",
"primaryArrival": {"openingId": "north-arrival", "exception": ""},
"openings": [
{"id": "north-arrival", "kind": "door", "wall": "north", "widthMeters": 3.0, "heightMeters": 3.1, "depthMeters": 0.45, "thresholdMeters": 0, "destination": "A short visible entry apron leading from the maze game's lobby arrival point.", "mask": "masks/north-arrival-mask.svg", "cutter": "masks/north-arrival-cutter.json", "approved": false}
]
}
@@ -0,0 +1,11 @@
{
"schema": "game-room.plan-metadata.v1",
"roomId": "maze32-command-chamber",
"style": "monochrome-engineering",
"projectionSource": "actual-meshes",
"layers": ["lower-room", "reflected-ceiling"],
"semanticLabelsOutsideImage": true,
"aiInspirationIncluded": false,
"aiInspirationReason": "",
"coverage": ["room-outline", "cutouts", "openings", "exterior-apron", "main-sign", "ingress-props", "floor-markers", "barriers", "columns", "circulation", "placed-features"]
}
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="900" height="900" viewBox="0 0 900 900"><rect width="900" height="900" fill="white"/><g fill="none" stroke="#111" stroke-width="6"><rect x="130" y="130" width="640" height="640"/><path d="M370 130H530" stroke="white" stroke-width="18"/><path d="M370 130H530"/><circle cx="450" cy="450" r="135"/><circle cx="450" cy="450" r="215" stroke="#777" stroke-dasharray="14 10"/><circle cx="450" cy="235" r="40"/><circle cx="665" cy="450" r="40"/><circle cx="450" cy="665" r="40"/><circle cx="235" cy="450" r="40"/><rect x="370" y="65" width="160" height="65"/><path d="M450 315V130M585 450H770M450 585V770M315 450H130" stroke="#777" stroke-width="4" stroke-dasharray="12 12"/></g><g font-family="sans-serif" font-size="20" fill="#111"><text x="450" y="35" text-anchor="middle">LOWER-ROOM PLAN — 12 m × 12 m</text><text x="450" y="457" text-anchor="middle">command table</text><text x="450" y="242" text-anchor="middle">station</text><text x="670" y="457" text-anchor="middle">station</text><text x="450" y="672" text-anchor="middle">station</text><text x="230" y="457" text-anchor="middle">station</text><text x="450" y="110" text-anchor="middle">3 m arrival + apron</text><text x="610" y="320">1.4 m circulation ring</text><text x="700" y="810">black: shell · dashed: paths</text></g></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="900" height="500" viewBox="0 0 900 500"><rect width="900" height="500" fill="white"/><path d="M110 430V90H790V430H110" fill="none" stroke="#111" stroke-width="6"/><path d="M365 430V170H535V430" fill="none" stroke="#111" stroke-width="6"/><path d="M110 90H790" stroke="#777" stroke-width="3" stroke-dasharray="12 10"/><g font-family="sans-serif" font-size="20" fill="#111"><text x="450" y="45" text-anchor="middle">NORTH WALL ELEVATION</text><text x="450" y="470" text-anchor="middle">arrival portal: 3.0 m wide × 3.1 m high · actual Boolean aperture</text><text x="700" y="120">ceiling: 5.2 m</text></g></svg>

After

Width:  |  Height:  |  Size: 659 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="900" height="900" viewBox="0 0 900 900"><rect width="900" height="900" fill="white"/><g fill="none" stroke="#111" stroke-width="6"><rect x="130" y="130" width="640" height="640"/><path d="M370 130H530" stroke="white" stroke-width="18"/><path d="M370 130H530"/><circle cx="450" cy="450" r="170"/><circle cx="450" cy="450" r="230" stroke="#777" stroke-dasharray="14 10"/><circle cx="450" cy="450" r="35"/></g><g font-family="sans-serif" font-size="20" fill="#111"><text x="450" y="35" text-anchor="middle">REFLECTED CEILING PLAN — 12 m × 12 m</text><text x="450" y="455" text-anchor="middle">central suspended beacon</text><text x="450" y="285" text-anchor="middle">ceiling light ring</text><text x="450" y="700" text-anchor="middle">outer trim ring</text></g></svg>

After

Width:  |  Height:  |  Size: 815 B

+17
View File
@@ -0,0 +1,17 @@
{
"schema": "game-room.prop-manifest.v1",
"roomId": "maze32-command-chamber",
"generation": {
"provider": "meshy",
"apiModel": "meshy-5",
"pricingSource": "https://docs.meshy.ai/en/api/pricing",
"pricingVerifiedAt": "2026-09-09T00:00:00Z (no Meshy submission planned; procedural Blender construction selected)",
"creditsPerTexturedTask": 15,
"hardCreditCeiling": 1,
"maxChargedAttemptsPerAsset": 2
},
"assets": [
{"id": "command-table", "function": "Explains the maze objective and anchors the lobby.", "description": "Circular slate, brass, and cyan holographic maze pedestal, constructed procedurally in Blender.", "class": "hero", "dimensionsMeters": [3.6, 3.6, 1.35], "attachmentFace": "floor", "semanticFront": "positive-y", "targetPolycount": 8000, "intendedInstances": 1, "rejectionCriteria": ["The base cannot sit flat on the floor.", "The table silhouette hides the holographic maze from the production camera.", "The construction has fused or floating support parts."], "image": {"prompt": "Isolated circular command-table pedestal for a child-friendly maze game; full uncropped three-quarter view; dark slate, warm brass, cyan holographic maze; neutral studio background; no text, room, people, crop, watermark, floating parts, or fused background.", "generator": "OpenAI image generation", "source": "images/command-table-reference.png", "approved": false, "approvedBy": ""}, "attempts": []},
{"id": "player-station", "function": "Provides one readable standing point for each player color.", "description": "Low outward-facing brass-and-slate kiosk with a circular color marker; constructed procedurally in Blender.", "class": "reusable", "dimensionsMeters": [1.1, 0.7, 1.05], "attachmentFace": "floor", "semanticFront": "negative-y", "targetPolycount": 1200, "intendedInstances": 4, "rejectionCriteria": ["The station blocks the 1.2 m circulation ring.", "The color marker does not face the room center.", "The base floats or balances on an edge."], "image": {"prompt": "Isolated low player station kiosk, simple stone and brass body with circular colored floor marker, full uncropped three-quarter studio view; no text, room, people, crop, watermark, floating parts, or fused background.", "generator": "Procedural Blender design brief", "source": "images/command-table-reference.png", "approved": false, "approvedBy": ""}, "attempts": []}
]
}
@@ -0,0 +1,16 @@
{
"schema": "game-room.room-brief.v1",
"roomId": "maze32-command-chamber",
"family": "gameplay-room",
"purpose": "A readable pre-match command chamber where players select a color station, understand the maze objective, and enter the game through a single generous portal.",
"runtimeSurface": "Future WebGL maze-game lobby; the Blender source is a visual and spatial reference, not a runtime export.",
"boundsMeters": [12, 12, 5.2],
"hero": {"id": "command-table", "role": "Central holographic maze table; its 3.6 m circular silhouette remains unobstructed from the production camera.", "reservedBoundsMeters": [4.4, 4.4, 2.2]},
"symmetry": {"mode": "radial", "order": 4, "appliesTo": ["architecture", "barriers", "circulation", "repeatedProps"], "exceptions": ["The north arrival is a single 3.0 m-wide portal; the other three wall bays retain the same structural rhythm but are closed."]},
"circulation": {"description": "A 1.4 m radial ring around the command table connects the 3.0 m north arrival to four outward-facing player stations without crossing the hero reserve.", "minimumClearanceMeters": 1.2},
"productionCamera": {"viewport": [1920, 1080], "location": [9.2, -10.2, 6.7], "target": [0, 0, 1.5], "verticalFovDegrees": 54},
"performanceBudget": {"maxSceneTriangles": 120000, "maxVLowBytes": 3000000, "maxHighTextureBytes": 16000000},
"creditBudget": {"maximumCredits": 1, "approvalRequired": true},
"environment": {"type": "none", "identity": "", "source": "", "assignmentKey": ""},
"artifacts": {"floorPlan": "plans/floor-plan.svg", "reflectedCeilingPlan": "plans/reflected-ceiling-plan.svg", "planMetadata": "plan-metadata.json", "wallElevations": ["plans/north-elevation.svg"], "visualTarget": "images/command-table-reference.png", "openingContactSheet": "masks/opening-contact-sheet.svg"}
}
@@ -0,0 +1,17 @@
{
"schema": "game-room.layout.v1",
"roomId": "maze32-command-chamber",
"units": "meters",
"surfaces": [
{"id": "floor", "kind": "floor", "location": [0, 0, -0.15], "dimensionsMeters": [12, 12, 0.3], "rotationDegrees": [0, 0, 0], "material": {"baseColor": [0.07, 0.09, 0.13, 1], "metallic": 0.05, "roughness": 0.72}},
{"id": "ceiling", "kind": "ceiling", "location": [0, 0, 5.0], "dimensionsMeters": [12, 12, 0.35], "rotationDegrees": [0, 0, 0], "material": {"baseColor": [0.04, 0.05, 0.08, 1], "metallic": 0.1, "roughness": 0.64}}
],
"instances": [
{"id": "command-table-main", "assetId": "command-table", "location": [0, 0, 0], "rotationDegrees": [0, 0, 0], "scale": [1, 1, 1]},
{"id": "station-north", "assetId": "player-station", "location": [0, 3.95, 0], "rotationDegrees": [0, 0, 180], "scale": [1, 1, 1]},
{"id": "station-east", "assetId": "player-station", "location": [3.95, 0, 0], "rotationDegrees": [0, 0, 90], "scale": [1, 1, 1]},
{"id": "station-south", "assetId": "player-station", "location": [0, -3.95, 0], "rotationDegrees": [0, 0, 0], "scale": [1, 1, 1]},
{"id": "station-west", "assetId": "player-station", "location": [-3.95, 0, 0], "rotationDegrees": [0, 0, -90], "scale": [1, 1, 1]}
],
"productionCamera": {"location": [9.2, -10.2, 6.7], "target": [0, 0, 1.5], "verticalFovDegrees": 54}
}