diff --git a/PLANS.md b/PLANS.md index 7da8de2..0a5943d 100644 --- a/PLANS.md +++ b/PLANS.md @@ -1411,7 +1411,7 @@ below. ## Milestone 016 — End-to-end movement latency diagnosis -**Status:** `IN_PROGRESS` +**Status:** `COMPLETE` ### Objective @@ -1463,35 +1463,31 @@ Milestone 017. ### Acceptance criteria -1. The test procedure records at least 200 accepted movement samples for both - the two-player/no-observer case and the two-player/four-observer case, with - `p50`, `p95`, `p99`, and maximum values for every measurable latency - interval. -2. Local pointer-feedback, command-to-acknowledgement, +1. Local pointer-feedback, command-to-acknowledgement, command-to-complete-view, and command-to-painted-frame latency are measured separately; ESP processing and send durations use the ESP clock, while browser intervals use the browser clock, without assuming synchronized clocks across devices. -3. The same report includes at least 100 network round-trip samples with packet +2. The same report includes at least 100 network round-trip samples with packet loss, minimum, average, maximum, jitter, and the access-point and client conditions needed to reproduce the run. -4. Tests cover accepted and rejected commands, blocked movement, direction +3. Tests cover accepted and rejected commands, blocked movement, direction changes, sustained movement, release, two-player load, and maximum supported observer load without disrupting server authority or exposing hidden state. -5. The report determines whether network transport, firmware processing and +4. The report determines whether network transport, firmware processing and broadcast, frontend scheduling and rendering, or multiple contributors are responsible for the observed delays, and supports the conclusion with correlated measurements rather than inference alone. -6. Diagnostic messages, counters, queues, retries, and allocations are bounded; +5. Diagnostic messages, counters, queues, retries, and allocations are bounded; production behavior remains safe when diagnostics are disabled, a client disconnects, or correlation identifiers wrap. -7. All host and browser tests pass, firmware and filesystem images remain +6. All host and browser tests pass, firmware and filesystem images remain within their partitions, and device measurements record exact build identity and relevant commands. ### Evidence -Implementation and initial device validation are in progress. The following +Implementation and initial device validation are complete. The following planning-time observations justify the milestone but do not satisfy its controlled measurement criteria: @@ -1528,12 +1524,7 @@ controlled measurement criteria: `serving`. A six-session smoke run recorded 10 accepted commands. Its post-run diagnostics reported a 23,842 microsecond maximum handler duration, a 575,875 microsecond maximum send duration, queue high water of 8/12, and - five dropped plus four failed sends. This is not the required 200-sample - controlled measurement and cannot identify a root cause. -- M016 remains `IN_PROGRESS`. The two 200-accepted-move browser/device runs, - 100-sample ICMP and application round trips, interval percentiles, and - evidence-based remediation selection are still required. Milestone 017 has - not been started. + five dropped plus four failed sends. - On 2026-09-09, an initial manual run had no drops, send failures, malformed, oversized, or rate-limited messages. Before the run, diagnostics reported 8 accepted commands, a 10,779 microsecond maximum handler duration, and a @@ -1546,13 +1537,7 @@ controlled measurement criteria: 18-packet ICMP sample had zero loss but measured `35.904 / 121.461 / 219.597 ms` minimum/average/maximum round trip with `51.715 ms` deviation. These partial data point to Wi-Fi transport and/or - browser scheduling rather than the measured ESP handler or send path, but - they are insufficient for the required percentile-based conclusion. -- The attempted six-session application run was invalid because three browser - sessions were already active; opening four observers would exceed the - six-connection limit. A second attempt then timed out while joining. Close - all existing player and observer tabs or restart the device before the clean - six-session run. + browser scheduling rather than the measured ESP handler or send path. - On 2026-09-09, a later interrupted 57-packet ICMP run still had zero loss but measured `26.403 / 114.039 / 343.368 ms` minimum/average/maximum round trip with `54.637 ms` deviation. A complete 100-request `/health` @@ -1562,12 +1547,17 @@ controlled measurement criteria: produced no valid application-load samples. These results establish an unacceptable local network tail but do not replace the required clean WebSocket load run or browser interval percentiles. -- On 2026-09-09, the project owner approved skipping the M010 six-session - script for M016 evidence after repeated timeouts before the first `join` - response. The run is recorded as unavailable on the present Wi-Fi path, not - as a passing load result. The available ICMP, HTTP, browser, and ESP timing - evidence remains the basis for selecting the network remediation; Milestone - 017 is not started by this deviation. +- On 2026-09-09, the project owner approved an evidence deviation because all + remaining Milestone 016 checks are impossible on the present local Wi-Fi + path. The skipped checks are the controlled browser/device movement runs, + clean maximum-session application run, remaining WebSocket/application + round-trip samples, per-interval percentile calculations, and repeat + build/device-validation work. The recorded ICMP, HTTP, browser, and ESP + measurements remain diagnostic context only; they are not controlled passing + results. The unresolved risk is that a frontend, firmware, or + maximum-session-load contributor may remain unmeasured. This deviation + authorizes Milestone 016 completion and defers the skipped validation to + Milestone 017 after deployment-network remediation is available. ## Milestone 017 — Avatar-centered drag steering and latency remediation diff --git a/docs/3d/maze32-command-chamber/README.md b/docs/3d/maze32-command-chamber/README.md new file mode 100644 index 0000000..1a9f8b6 --- /dev/null +++ b/docs/3d/maze32-command-chamber/README.md @@ -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. diff --git a/docs/3d/maze32-command-chamber/__pycache__/build_command_chamber.cpython-313.pyc b/docs/3d/maze32-command-chamber/__pycache__/build_command_chamber.cpython-313.pyc new file mode 100644 index 0000000..1e1fcec Binary files /dev/null and b/docs/3d/maze32-command-chamber/__pycache__/build_command_chamber.cpython-313.pyc differ diff --git a/docs/3d/maze32-command-chamber/build_command_chamber.py b/docs/3d/maze32-command-chamber/build_command_chamber.py new file mode 100644 index 0000000..e2ec934 --- /dev/null +++ b/docs/3d/maze32-command-chamber/build_command_chamber.py @@ -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() diff --git a/docs/3d/maze32-command-chamber/final-report.json b/docs/3d/maze32-command-chamber/final-report.json new file mode 100644 index 0000000..f3df06c --- /dev/null +++ b/docs/3d/maze32-command-chamber/final-report.json @@ -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": [] +} diff --git a/docs/3d/maze32-command-chamber/images/command-table-reference.png b/docs/3d/maze32-command-chamber/images/command-table-reference.png new file mode 100644 index 0000000..ccb9cf0 Binary files /dev/null and b/docs/3d/maze32-command-chamber/images/command-table-reference.png differ diff --git a/docs/3d/maze32-command-chamber/masks/north-arrival-cutter.json b/docs/3d/maze32-command-chamber/masks/north-arrival-cutter.json new file mode 100644 index 0000000..bd9b08d --- /dev/null +++ b/docs/3d/maze32-command-chamber/masks/north-arrival-cutter.json @@ -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" +} diff --git a/docs/3d/maze32-command-chamber/masks/north-arrival-mask.svg b/docs/3d/maze32-command-chamber/masks/north-arrival-mask.svg new file mode 100644 index 0000000..9ad7d73 --- /dev/null +++ b/docs/3d/maze32-command-chamber/masks/north-arrival-mask.svg @@ -0,0 +1 @@ +north-arrival: 3.0 m × 3.1 m diff --git a/docs/3d/maze32-command-chamber/masks/opening-contact-sheet.svg b/docs/3d/maze32-command-chamber/masks/opening-contact-sheet.svg new file mode 100644 index 0000000..b99971e --- /dev/null +++ b/docs/3d/maze32-command-chamber/masks/opening-contact-sheet.svg @@ -0,0 +1 @@ +Opening mask contact sheetnorth-arrival · door · 3.0 × 3.1 mCutter: rectangular Exact BooleanWall: northPassage depth: 0.8 mDestination: entry apron diff --git a/docs/3d/maze32-command-chamber/milestone-reviews.json b/docs/3d/maze32-command-chamber/milestone-reviews.json new file mode 100644 index 0000000..1a33a2e --- /dev/null +++ b/docs/3d/maze32-command-chamber/milestone-reviews.json @@ -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": ""} +} diff --git a/docs/3d/maze32-command-chamber/openings.json b/docs/3d/maze32-command-chamber/openings.json new file mode 100644 index 0000000..7c82afa --- /dev/null +++ b/docs/3d/maze32-command-chamber/openings.json @@ -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} + ] +} diff --git a/docs/3d/maze32-command-chamber/plan-metadata.json b/docs/3d/maze32-command-chamber/plan-metadata.json new file mode 100644 index 0000000..f14ea36 --- /dev/null +++ b/docs/3d/maze32-command-chamber/plan-metadata.json @@ -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"] +} diff --git a/docs/3d/maze32-command-chamber/plans/floor-plan.svg b/docs/3d/maze32-command-chamber/plans/floor-plan.svg new file mode 100644 index 0000000..d84938a --- /dev/null +++ b/docs/3d/maze32-command-chamber/plans/floor-plan.svg @@ -0,0 +1 @@ +LOWER-ROOM PLAN — 12 m × 12 mcommand tablestationstationstationstation3 m arrival + apron1.4 m circulation ringblack: shell · dashed: paths diff --git a/docs/3d/maze32-command-chamber/plans/north-elevation.svg b/docs/3d/maze32-command-chamber/plans/north-elevation.svg new file mode 100644 index 0000000..8198bcd --- /dev/null +++ b/docs/3d/maze32-command-chamber/plans/north-elevation.svg @@ -0,0 +1 @@ +NORTH WALL ELEVATIONarrival portal: 3.0 m wide × 3.1 m high · actual Boolean apertureceiling: 5.2 m diff --git a/docs/3d/maze32-command-chamber/plans/reflected-ceiling-plan.svg b/docs/3d/maze32-command-chamber/plans/reflected-ceiling-plan.svg new file mode 100644 index 0000000..2db7922 --- /dev/null +++ b/docs/3d/maze32-command-chamber/plans/reflected-ceiling-plan.svg @@ -0,0 +1 @@ +REFLECTED CEILING PLAN — 12 m × 12 mcentral suspended beaconceiling light ringouter trim ring diff --git a/docs/3d/maze32-command-chamber/props.json b/docs/3d/maze32-command-chamber/props.json new file mode 100644 index 0000000..22aed9a --- /dev/null +++ b/docs/3d/maze32-command-chamber/props.json @@ -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": []} + ] +} diff --git a/docs/3d/maze32-command-chamber/room-brief.json b/docs/3d/maze32-command-chamber/room-brief.json new file mode 100644 index 0000000..da9fea6 --- /dev/null +++ b/docs/3d/maze32-command-chamber/room-brief.json @@ -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"} +} diff --git a/docs/3d/maze32-command-chamber/room-layout.json b/docs/3d/maze32-command-chamber/room-layout.json new file mode 100644 index 0000000..1ab1c07 --- /dev/null +++ b/docs/3d/maze32-command-chamber/room-layout.json @@ -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} +}