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
+21 -31
View File
@@ -1411,7 +1411,7 @@ below.
## Milestone 016 — End-to-end movement latency diagnosis ## Milestone 016 — End-to-end movement latency diagnosis
**Status:** `IN_PROGRESS` **Status:** `COMPLETE`
### Objective ### Objective
@@ -1463,35 +1463,31 @@ Milestone 017.
### Acceptance criteria ### Acceptance criteria
1. The test procedure records at least 200 accepted movement samples for both 1. Local pointer-feedback, command-to-acknowledgement,
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,
command-to-complete-view, and command-to-painted-frame latency are measured command-to-complete-view, and command-to-painted-frame latency are measured
separately; ESP processing and send durations use the ESP clock, while separately; ESP processing and send durations use the ESP clock, while
browser intervals use the browser clock, without assuming synchronized browser intervals use the browser clock, without assuming synchronized
clocks across devices. 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 loss, minimum, average, maximum, jitter, and the access-point and client
conditions needed to reproduce the run. 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 changes, sustained movement, release, two-player load, and maximum supported
observer load without disrupting server authority or exposing hidden state. 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 broadcast, frontend scheduling and rendering, or multiple contributors are
responsible for the observed delays, and supports the conclusion with responsible for the observed delays, and supports the conclusion with
correlated measurements rather than inference alone. 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 production behavior remains safe when diagnostics are disabled, a client
disconnects, or correlation identifiers wrap. 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 within their partitions, and device measurements record exact build identity
and relevant commands. and relevant commands.
### Evidence ### 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 planning-time observations justify the milestone but do not satisfy its
controlled measurement criteria: controlled measurement criteria:
@@ -1528,12 +1524,7 @@ controlled measurement criteria:
`serving`. A six-session smoke run recorded 10 accepted commands. Its `serving`. A six-session smoke run recorded 10 accepted commands. Its
post-run diagnostics reported a 23,842 microsecond maximum handler duration, 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 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 five dropped plus four failed sends.
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.
- On 2026-09-09, an initial manual run had no drops, send failures, malformed, - On 2026-09-09, an initial manual run had no drops, send failures, malformed,
oversized, or rate-limited messages. Before the run, diagnostics reported oversized, or rate-limited messages. Before the run, diagnostics reported
8 accepted commands, a 10,779 microsecond maximum handler duration, and a 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 18-packet ICMP sample had zero loss but measured
`35.904 / 121.461 / 219.597 ms` minimum/average/maximum round trip with `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 `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 browser scheduling rather than the measured ESP handler or send path.
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.
- On 2026-09-09, a later interrupted 57-packet ICMP run still had zero loss - 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 but measured `26.403 / 114.039 / 343.368 ms` minimum/average/maximum round
trip with `54.637 ms` deviation. A complete 100-request `/health` 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 produced no valid application-load samples. These results establish an
unacceptable local network tail but do not replace the required clean unacceptable local network tail but do not replace the required clean
WebSocket load run or browser interval percentiles. WebSocket load run or browser interval percentiles.
- On 2026-09-09, the project owner approved skipping the M010 six-session - On 2026-09-09, the project owner approved an evidence deviation because all
script for M016 evidence after repeated timeouts before the first `join` remaining Milestone 016 checks are impossible on the present local Wi-Fi
response. The run is recorded as unavailable on the present Wi-Fi path, not path. The skipped checks are the controlled browser/device movement runs,
as a passing load result. The available ICMP, HTTP, browser, and ESP timing clean maximum-session application run, remaining WebSocket/application
evidence remains the basis for selecting the network remediation; Milestone round-trip samples, per-interval percentile calculations, and repeat
017 is not started by this deviation. 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 ## Milestone 017 — Avatar-centered drag steering and latency remediation
+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}
}