# Spatial Lab JSON contract, version 1 ## Editable project: `spatial-lab.project/1` ```json { "schema": "spatial-lab.project/1", "id": "corridor-study", "name": "Corridor study", "parameters": {"length": 10}, "nodes": [ {"id":"axis","op":"polyline","version":1,"visible":false, "params":{"points":[[0,0,0],[0,"length",0]]}}, {"id":"basis","op":"frames","visible":false,"inputs":{"path":"axis"}}, {"id":"deck","op":"sweep","inputs":{"frames":"basis"}, "params":{"profile":[[-1,-0.15],[1,-0.15],[1,0.15],[-1,0.15]]}, "color":[0.4,0.56,0.68],"role":"gallery"} ] } ``` `id` identifies a project for ownership in the Blender adapter. Use a different project ID for independently managed studies in the same scene. Node IDs identify graph operations, never rename them during ordinary parameter edits. Version defaults to 1; visibility defaults to true. `name`, `color` and `role` are optional semantic presentation metadata. Parameters may be numbers or bounded arithmetic expressions. Dependencies are explicitly listed in `inputs`, as a node ID or list of node IDs. Cycles and missing references fail validation. ## Evaluated bundle: `spatial-lab.bundle/1` | Field | Meaning | | --- | --- | | `project_id`, `name` | Source project identity | | `units`, `handedness`, `up_axis` | Always `meters`, `right`, `+Z` | | `matrix_convention` | Row arrays, column vectors, local-to-world matrices | | `recipe` | Complete original project for further editing | | `project_hash` | SHA-256 of canonical recipe JSON | | `nodes` | Evaluated node summaries, input links and parameters | | `geometries` | Dictionary keyed by geometry hash; each value has `vertices` and `faces` | | `objects` | Stable ID, node ID, kind, transform, semantic metadata and geometry reference/data | | `bounds` | World-space axis-aligned `min`/`max` | | `validation` | Mesh reports, warnings, and explicit flags for checks not performed | | `stats` | Node/object counts, unique vertex/mesh counts, triangles including instances | | `bundle_hash` | SHA-256 of canonical bundle JSON excluding this field | Every vertex is `[x,y,z]` in metres. Every face is exactly three zero-based vertex indices with outward winding for solid meshes. Geometry hash covers the complete `{"vertices":...,"faces":...}` object. Mesh instances reference it through `geometry`; they do not duplicate vertex arrays. Every object has `id`, `node_id`, `name`, `kind`, `role`, `color` and a 4×4 `matrix`. Its bottom row is `[0,0,0,1]`; matrices are invertible and orientation-preserving. Translation is in the last column. A local position becomes `matrix * [x,y,z,1]`. Colour has three normalized RGB components. Mesh object IDs combine node ID and part key, e.g. `braided-galleries/0000/main`. A curve object instead embeds `points` and `closed` and uses ID `NODE/path`. Its points are joined in order, with the final-to-first edge added only for closed curves. Frames are individual objects of kind `frame`, with local axes in matrix columns and their origin in its last column; ID `NODE/frame-0000`. Canonical JSON is the Python kernel's `json.dumps(value, sort_keys=True, separators=(',', ':'), allow_nan=False)` encoded as UTF-8. These integrity hashes are for reproducibility and accidental-corruption detection, not authentication. `validate_bundle` checks the supported convention, hashes, references, finite coordinates, transforms and triangle/edge structure before import. Bundles should be generated by `build` rather than assembled manually. A consumer only needs the evaluated geometry and matrices to reproduce the shape; it does not need the expression evaluator or custom operators. Blender natively uses the same axis convention. Unity/Godot/custom-engine adapters must explicitly convert basis/units and triangle winding if their chosen basis changes handedness. No such engine adapter is included yet.