wadi-mcp 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/server.mjs +25 -7
- package/package.json +1 -1
package/dist/server.mjs
CHANGED
|
@@ -411115,9 +411115,27 @@ function expandRoomWalls(houseConfig, wallThickness, opts, _depth = 0) {
|
|
|
411115
411115
|
if (typeof r2.y === "number") r2.y = r2.y - ot / 2;
|
|
411116
411116
|
if (typeof r2.width === "number") r2.width = r2.width + ot;
|
|
411117
411117
|
if (typeof r2.length === "number") r2.length = r2.length + ot;
|
|
411118
|
-
} else if (o.type === "
|
|
411119
|
-
|
|
411120
|
-
if (
|
|
411118
|
+
} else if (o.type === "roof") {
|
|
411119
|
+
const segs = r2.segments;
|
|
411120
|
+
if (Array.isArray(segs)) {
|
|
411121
|
+
const half = t / 2;
|
|
411122
|
+
for (const s of segs) {
|
|
411123
|
+
const st = s.start;
|
|
411124
|
+
const en = s.end;
|
|
411125
|
+
if (Array.isArray(st) && Array.isArray(en) && typeof st[0] === "number" && typeof st[1] === "number" && typeof en[0] === "number" && typeof en[1] === "number") {
|
|
411126
|
+
const dx = en[0] - st[0];
|
|
411127
|
+
const dy = en[1] - st[1];
|
|
411128
|
+
const len = Math.hypot(dx, dy);
|
|
411129
|
+
if (len > 0) {
|
|
411130
|
+
const ux = dx / len;
|
|
411131
|
+
const uy = dy / len;
|
|
411132
|
+
s.start = [st[0] - ux * half, st[1] - uy * half];
|
|
411133
|
+
s.end = [en[0] + ux * half, en[1] + uy * half];
|
|
411134
|
+
}
|
|
411135
|
+
}
|
|
411136
|
+
if (typeof s.width === "number") s.width = s.width + t;
|
|
411137
|
+
}
|
|
411138
|
+
}
|
|
411121
411139
|
}
|
|
411122
411140
|
}
|
|
411123
411141
|
}
|
|
@@ -417167,7 +417185,7 @@ house TwoRoom {
|
|
|
417167
417185
|
}
|
|
417168
417186
|
`,
|
|
417169
417187
|
"two_story": '// Multi-floor, grid-driven: a Plinth floor, two occupied floors, and a hip roof.\n// Everything is first-class now \u2014 ground, plinth, slab, staircase, and roof are\n// real entities with parameters (no `raw`). Widen it by editing `point House`.\nhouse TwoStory {\n convention center\n units feet_inches per_unit 10\n\n site { plot (500, 500) ref (0, 0) }\n defaults { floor_height 116 wall_height 108 slab_thickness 8 wall_thickness 8 } // floor_height = wall_height + slab_thickness (C4)\n\n var wallT = 8\n point House { x = 352, y = 352 }\n grid main {\n x: 1 @ wallT / 2, 2 @ House.W / 2, 3 @ House.W - wallT / 2\n y: A @ wallT / 2, B @ House.L / 2, C @ House.L - wallT / 2\n }\n\n // The plinth floor\'s height MUST equal the plinth block height (convention\n // C1) or the Ground Floor above would float 40 units into the air.\n floor 0 "Plinth" height 40 {\n ground name "Ground" at (0, 0) size (500, 500) layer "ground"\n plinth name "Plinth"\n at (main.x1, main.yA) size (main.x3 - main.x1, main.yC - main.yA)\n height 40 layer "plinth"\n }\n\n floor 1 "Ground Floor" {\n slab at (main.x1, main.yA) size (main.x3 - main.x1, main.yC - main.yA)\n room Hall at (main.x1, main.yA) size (main.x3 - main.x1, main.yB - main.yA) {\n wall north east west // all three exterior sides (C2)\n wall south { door Main at 120 size (36, 84) }\n }\n room Kitchen at (main.x1, main.yB) size (main.x2 - main.x1, main.yC - main.yB) {\n wall south // exterior side (C2)\n wall west { window KW at 40 size (45, 45) sill 40 }\n }\n room Bath at (main.x2, main.yB) size (main.x3 - main.x2, main.yC - main.yB) {\n wall south // exterior side (C2)\n wall east { window BW at 30 size (40, 40) sill 45 }\n }\n }\n\n floor 2 "First Floor" {\n slab at (main.x1, main.yA) size (main.x3 - main.x1, main.yC - main.yA)\n room Bedroom1 at (main.x1, main.yA) size (main.x2 - main.x1, main.yC - main.yA) {\n wall north south // exterior sides (C2)\n wall west { window B1 at 60 size (55, 50) sill 35 }\n }\n room Bedroom2 at (main.x2, main.yA) size (main.x3 - main.x2, main.yC - main.yA) {\n wall north south // exterior sides (C2)\n wall east { window B2 at 60 size (55, 50) sill 35 }\n }\n staircase name "Stair" at (300, 60) step (7, 10, 36) direction south\n }\n\n floor 3 "Loft Floor" {\n roof name "Hip Roof" pitched endpoint closed slope height 100 overhang 25 {\n segment "seg0" from (176, 0) to (176, 352) width 352 hip_setback (80, 80) tie_beams 3\n truss "seg0" fink at (80, 176, 272)\n }\n }\n}\n',
|
|
417170
|
-
"coastal": '// A coastal Konkan cottage, authored entirely in the Wadi DSL.\n// `tsx src/cli/main.ts examples/coastal.wdl` compiles it to a .wadi that the\n// real Wadi pipeline validates + resolves + renders. This one file exercises\n// the parametric core (var/point/grid/formula/configurator) and the domain\n// vocabulary \u2014 every primitive is FIRST-CLASS (ground, plinth, slab, room,\n// wall, opening, pillar, roof), no `raw` needed.\n\nhouse CoastalCottage {\n convention center\n units feet_inches per_unit 10\n\n site { plot (600, 700) ref (0, 0) }\n defaults { floor_height 116 wall_height 108 slab_thickness 8 wall_thickness 8 } // floor_height = wall_height + slab_thickness (C4)\n\n // --- Parametric core: the degrees of freedom + the grid scaffold ---\n var wallT = 8\n var pillarW = 10\n var pilInset = (pillarW - wallT) / 2\n var roof_style = 3\n\n point House { x = 420, y = 470 }\n\n grid main {\n x: 1 @ wallT / 2 role structural,\n 2 @ House.W / 2,\n 3 @ House.W - wallT / 2 role structural\n y: A @ wallT / 2 role structural,\n B @ House.L / 2,\n C @ House.L - wallT / 2 role structural\n }\n\n // --- Control knobs a homeowner turns (a curated projection of the vars) ---\n configurator {\n slider pillarW "Column size" ft [8 .. 14 step 1]\n select roof_style "Roof style" { Flat = 0, Shed = 1, Gable = 2, Hip = 3 }\n }\n\n // --- Plinth floor: terrain + raised base ---\n floor 0 "Plinth" height 40 { // floor height == plinth height (C1)\n ground name "Ground" at (0, 0) size (600, 700) layer "ground"\n plinth name "Plinth"\n at (main.x1, main.yA) size (main.x3 - main.x1, main.yC - main.yA)\n height 40 layer "plinth"\n }\n\n // --- Ground floor: slab + grid-placed rooms + corner pillars ---\n floor 1 "Ground Floor" {\n slab at (main.x1, main.yA) size (main.x3 - main.x1, main.yC - main.yA)\n\n room Living\n at (main.x1, main.yA)\n size (main.x2 - main.x1, main.yB - main.yA) {\n wall west // exterior side (C2)\n wall north { window LivWinN at 55 size (55, 55) sill 35 }\n wall south { door LivDoor at 60 size (32, 80) }\n }\n\n room Kitchen\n at (main.x2, main.yA)\n size (main.x3 - main.x2, main.yB - main.yA) {\n wall north // exterior side (C2)\n wall east { window KitWinE at 40 size (45, 45) sill 40 }\n }\n\n room Bedroom\n at (main.x1, main.yB)\n size (main.x3 - main.x1, main.yC - main.yB) {\n wall east // exterior side (C2)\n wall west { window BedWinW at 50 size (55, 55) sill 35 }\n wall south { door BedDoor at 70 size (32, 80) }\n }\n\n pillar C1 at (main.x1 + pilInset, main.yA + pilInset) size (pillarW, pillarW) height 116\n pillar C2 at (main.x3 -
|
|
417188
|
+
"coastal": '// A coastal Konkan cottage, authored entirely in the Wadi DSL.\n// `tsx src/cli/main.ts examples/coastal.wdl` compiles it to a .wadi that the\n// real Wadi pipeline validates + resolves + renders. This one file exercises\n// the parametric core (var/point/grid/formula/configurator) and the domain\n// vocabulary \u2014 every primitive is FIRST-CLASS (ground, plinth, slab, room,\n// wall, opening, pillar, roof), no `raw` needed.\n\nhouse CoastalCottage {\n convention center\n units feet_inches per_unit 10\n\n site { plot (600, 700) ref (0, 0) }\n defaults { floor_height 116 wall_height 108 slab_thickness 8 wall_thickness 8 } // floor_height = wall_height + slab_thickness (C4)\n\n // --- Parametric core: the degrees of freedom + the grid scaffold ---\n var wallT = 8\n var pillarW = 10\n var pilInset = (pillarW - wallT) / 2\n var roof_style = 3\n\n point House { x = 420, y = 470 }\n\n grid main {\n x: 1 @ wallT / 2 role structural,\n 2 @ House.W / 2,\n 3 @ House.W - wallT / 2 role structural\n y: A @ wallT / 2 role structural,\n B @ House.L / 2,\n C @ House.L - wallT / 2 role structural\n }\n\n // --- Control knobs a homeowner turns (a curated projection of the vars) ---\n configurator {\n slider pillarW "Column size" ft [8 .. 14 step 1]\n select roof_style "Roof style" { Flat = 0, Shed = 1, Gable = 2, Hip = 3 }\n }\n\n // --- Plinth floor: terrain + raised base ---\n floor 0 "Plinth" height 40 { // floor height == plinth height (C1)\n ground name "Ground" at (0, 0) size (600, 700) layer "ground"\n plinth name "Plinth"\n at (main.x1, main.yA) size (main.x3 - main.x1, main.yC - main.yA)\n height 40 layer "plinth"\n }\n\n // --- Ground floor: slab + grid-placed rooms + corner pillars ---\n floor 1 "Ground Floor" {\n slab at (main.x1, main.yA) size (main.x3 - main.x1, main.yC - main.yA)\n\n room Living\n at (main.x1, main.yA)\n size (main.x2 - main.x1, main.yB - main.yA) {\n wall west // exterior side (C2)\n wall north { window LivWinN at 55 size (55, 55) sill 35 }\n wall south { door LivDoor at 60 size (32, 80) }\n }\n\n room Kitchen\n at (main.x2, main.yA)\n size (main.x3 - main.x2, main.yB - main.yA) {\n wall north // exterior side (C2)\n wall east { window KitWinE at 40 size (45, 45) sill 40 }\n }\n\n room Bedroom\n at (main.x1, main.yB)\n size (main.x3 - main.x1, main.yC - main.yB) {\n wall east // exterior side (C2)\n wall west { window BedWinW at 50 size (55, 55) sill 35 }\n wall south { door BedDoor at 70 size (32, 80) }\n }\n\n // A pillar\'s `at` is its TOP-LEFT corner \u2014 to centre a column on a grid node,\n // subtract half its width (perimeter columns also inset by pilInset).\n pillar C1 at (main.x1 + pilInset - pillarW/2, main.yA + pilInset - pillarW/2) size (pillarW, pillarW) height 116\n pillar C2 at (main.x3 - pillarW/2, main.yA + pilInset - pillarW/2) size (pillarW, pillarW) height 116\n }\n\n // --- Loft floor: the hip roof (first-class \u2014 nested segments/slope/trusses) ---\n floor 2 "Loft Floor" {\n roof name "Hip Roof" pitched endpoint closed slope height 100 overhang 25 {\n segment "seg0" from (210, 0) to (210, 470) width 420 hip_setback (90, 90)\n truss "seg0" fink at (95, 235, 375)\n }\n }\n}\n',
|
|
417171
417189
|
"complete": '// Coverage showcase \u2014 every model entity as FIRST-CLASS syntax, no `raw`:\n// layers, a component library (definition + `use` instance), ground, plinth,\n// slab, beam, room (with openings + an anchored item), a free-standing wall,\n// a kitchen platform, free furniture (item), a pillar, and a gable roof that is\n// `enabled`-gated by the configurator variable. If it compiles + validates,\n// the DSL covers the whole model.\nhouse CompleteShowcase {\n convention center\n units feet_inches per_unit 10\n\n site { plot (400, 500) ref (0, 0) }\n defaults { floor_height 116 wall_height 108 slab_thickness 8 wall_thickness 8 } // floor_height = wall_height + slab_thickness (C4)\n\n var wallT = 8\n var roof_style = 2 // 2 = Gable (drives the enabled gate below)\n point House { x = 300, y = 400 }\n\n // Per-house display layers (registry).\n layer "structure" "Structure" group "Frame"\n layer "furniture" "Furniture" color "#8B5A2B"\n\n // Reusable component: a low RCC bench, authored in local coords.\n component Bench {\n param blen = 60 label "Bench length"\n param bdep = 18\n beam name "BenchTop" at (0, 0) size (blen, bdep) height 6 layer "structure"\n }\n\n configurator {\n slider wallT "Wall thickness" in [6 .. 12 step 1]\n select roof_style "Roof style" { Flat = 0, Shed = 1, Gable = 2, Hip = 3 }\n }\n\n grid main {\n x: 1 @ wallT / 2, 2 @ House.W - wallT / 2\n y: A @ wallT / 2, B @ House.L - wallT / 2\n }\n\n floor 0 "Plinth" height 40 { // floor height == plinth height (C1)\n ground name "Ground" at (0, 0) size (400, 500) layer "ground"\n plinth name "Plinth"\n at (main.x1, main.yA) size (main.x2 - main.x1, main.yB - main.yA)\n height 40 layer "plinth"\n }\n\n floor 1 "Ground Floor" {\n slab at (main.x1, main.yA) size (main.x2 - main.x1, main.yB - main.yA)\n\n // A tie beam across the rear span.\n beam name "Tie" at (main.x1, main.yB - 8) size (main.x2 - main.x1, 8) height 8 layer "structure"\n\n // Living room with a door, a window, and a bed anchored to a corner.\n room Living at (main.x1, main.yA) size (main.x2 - main.x1, main.yB - main.yA) {\n wall east west // plain walls \u2014 several in one line\n wall south { door Main at 120 size (36, 84) } // walls with openings: one side each\n wall north { window N1 at 100 size (60, 50) sill 35 }\n item asset { id "bed_double" src "furniture/bed_double.glb" dims (1.5, 0.5, 2.0) category "bedroom" }\n anchor bottom-right gap (12, 12) rotation 0\n }\n\n // A free-standing partition wall.\n wall Partition from (main.x1 + 100, main.yA) to (main.x1 + 100, main.yB - 150)\n height 108 facing east layer "structure"\n\n // An L-shaped kitchen counter (polyline path).\n kitchen name "Counter" path ((40, 40), (140, 40), (140, 120)) side right depth 24 height 36 layer "structure"\n\n // Free furniture placed by absolute plan coordinates.\n item name "Sofa" asset { id "sofa" src "furniture/sofa.glb" dims (1.9, 0.8, 0.9) category "living" }\n at (150, 300) rotation 90 scale 1 layer "furniture"\n\n // A corner column, and a stamped Bench component (param overridden).\n pillar C1 at (main.x1, main.yA) size (10, 10) height 116 layer "structure"\n use Bench as "WindowBench" at (60, 60) with { blen = 80 }\n }\n\n floor 2 "Loft" {\n // Gable roof (open endpoints), gated so it renders only when roof_style == 2.\n roof name "Gable Roof" pitched endpoint open slope angle 30 overhang 20\n enabled 1 - min(1, abs(roof_style - 2)) layer "roof" {\n segment "seg0" from (150, 0) to (150, 400) width 300 gable_overhang (20, 20) tie_beams 2\n truss "seg0" fink at (80, 200, 320)\n }\n }\n}\n'
|
|
417172
417190
|
};
|
|
417173
417191
|
var DOCS = {
|
|
@@ -417177,7 +417195,7 @@ var DOCS = {
|
|
|
417177
417195
|
},
|
|
417178
417196
|
"dsl": {
|
|
417179
417197
|
"title": "The Wadi DSL (.wdl) \u2014 syntax reference",
|
|
417180
|
-
"body": '# The Wadi DSL (`.wdl`) \u2014 authoring reference\n\nYou author houses in the **Wadi DSL** \u2014 a small, formal language (`.wdl`) that\ncompiles to a resolved `.wadi` (`house_config.json`). The DSL is **complete**:\nevery object type in the model has first-class syntax, so you rarely need the\n`raw` escape. Authoring the DSL is more direct and less error-prone than writing\nJSON \u2014 the grammar enforces structure, and `check.sh` reports parse errors with\nline:col.\n\n**This file is the SYNTAX reference.** The *semantics* live in the other\nreferences and apply unchanged \u2014 read them:\n\n- `coordinate-system.md` \u2014 X\u2192right, **Y\u2192DOWN**, Z\u2192up; **10 units = 1 ft**;\n the **centreline** convention. The #1 source of mistakes.\n- `conventions.md` \u2014 the **structural coding conventions** (`check.sh` enforces\n them): plinth-floor height must match the plinth block, rooms must wall every\n exterior side, a no-slab floor must set `slab_thickness 0`.\n- `parametric-conventions.md` \u2014 the grid-first recipe for reusable templates.\n- `roof-v2-guide.md` \u2014 roof segments, hip vs gable, trusses, joints.\n- `data-model.md` \u2014 the underlying `.wadi` schema (what the DSL compiles to; also\n the field reference for the `raw` escape).\n\n## The loop\n\n1. Write / edit `house.wdl` \u2014 the **single shared source** (you and the human\n co-edit it; the app\'s DSL previewer renders it live). You never produce a `.wadi`.\n2. `wadi-skill/architect/scripts/check.sh house.wdl` \u2014 runs the DSL compiler +\n validator (schema + wall/roof geometry) against a **throwaway temp** just for\n feedback; fix any reported error and re-run.\n3. `preview.sh house.wdl` \u2192 read the PNGs (plans / elevations / roof) to check your\n work. (It also compiles to a throwaway temp \u2014 no persistent `.wadi`.)\n\n## Skeleton\n\n```wdl\nhouse MyHouse {\n convention center // ALWAYS use center (wall-centreline coords)\n units feet_inches per_unit 10 // 10 project units = 1 ft\n site { plot (WIDTH, LENGTH) ref (0, 0) }\n defaults { floor_height 120 wall_height 108 slab_thickness 8 wall_thickness 8 }\n\n // parametric core (optional): var, point, grid, configurator\n // component / layer declarations (optional)\n floor 0 "Plinth" { \u2026 } // floors stack in source order (0 = plinth)\n floor 1 "Ground Floor" { \u2026 }\n floor 2 "Loft" { roof \u2026 } // roof lives ALONE on its own top floor\n}\n```\n\nNumbers are **project units** (feet \xD7 10 by default). Names after `house`,\n`room`, `pillar`, `var`, `point`, `grid`, and `use`/`component` are bare\nidentifiers (no spaces); names introduced with the `name` keyword are quoted\nstrings.\n\n## Parametric core (domain-neutral)\n\n```wdl\nvar wallT = 8 // a knob; may reference other vars\nvar pilInset = (pillarW - wallT) / 2\n\npoint House { x = 420, y = 470 } // reference as House.x / House.W / House.L\n // (.W = x, .L = y \u2014 a point doubles as a size)\n\ngrid main { // named wall centrelines; publishes main.x1 / main.yA\n x: 1 @ wallT / 2, 2 @ House.W / 2, 3 @ House.W - wallT / 2\n y: A @ wallT / 2, B @ House.L / 2, C @ House.L - wallT / 2\n}\n// each line may add: \u2026 @ <expr> thick <expr> role structural|planning\n\nconfigurator { // the knobs a downstream user turns\n slider pillarW "Column size" ft [8 .. 14 step 1]\n number ceiling "Ceiling height" ft\n toggle has_loft "Add a loft"\n select roof_style "Roof style" { Flat = 0, Shed = 1, Gable = 2, Hip = 3 }\n}\n```\n\n**Formulas are automatic.** Any geometry number can be a formula \u2014 just write the\nexpression instead of a literal (`at (main.x1, main.yA)`, `size (House.W/2, 200)`).\nOperators: `+ - * /`, unary `-`, parentheses, and the functions\n`min max clamp round floor ceil abs`. References: a `var`, a `point`\n(`House.W`), or a grid line (`main.x3 - main.x1`). No comparison operators \u2014 gate\nthings with the `min/abs` idiom (see `enabled` below).\n\n## Common attribute tail (every object)\n\nAfter an object\'s geometry, in THIS order, any of:\n\n```\n\u2026 z_offset <expr> enabled <expr> layer "id" [material "id"]\n```\n\n- `enabled <expr>` \u2014 the on/off switch. A `0`/`false` value hides the object. To\n gate on a configurator variable, use a 0/1 formula:\n `enabled 1 - min(1, abs(roof_style - 3))` renders the object only when\n `roof_style == 3`. (This is how one template carries several roofs and shows\n only the chosen one.)\n- `z_offset <expr>` \u2014 lift above the floor base (split levels).\n- `material "id"` \u2014 only on plinth / ground / room / wall / staircase / kitchen /\n roof.\n\n## Objects \u2014 structure & envelope\n\n```wdl\nslab [name "N"] at (x,y) size (w,l) [thickness <t>] // floor_slab\nbeam [name "N"] at (x,y) size (w,l) [height <h>]\nplinth [name "N"] at (x,y) size (w,l) height <h> // raised base (Plinth floor)\nground [name "N"] at (x,y) size (w,l) [height <h>] // terrain plane\npillar Name at (x,y) size (w,l) [height <h>] // (x,y) = TOP-LEFT corner\n```\n\n`at (x,y)` is the **TOP-LEFT CORNER** \u2014 **not the centre** \u2014 for every one of these\n(same as rooms/slabs/beams); `size (w,l)` is width \xD7 length. All accept the common tail.\n\n**Pillars catch people out here.** A column reads as "placed at a point," but `at` is\nstill its corner. To **centre a column on a point** `(cx, cy)` \u2014 a grid node, a room\ncorner \u2014 place it at **`at (cx - w/2, cy - l/2)`**, never at `(cx, cy)`. On a grid, the\n`pilInset` idiom (see `parametric-conventions.md`) does exactly this so columns sit flush.\n\n## Objects \u2014 rooms, walls & openings\n\nA room shows exactly the walls you declare. A **bare room (no `wall` lines) is\nenclosed on all four sides.** List plain walls compactly; give a wall its own line\nonly when it carries a door/window; omit a side to leave it open (verandah).\n\n```wdl\nroom Name at (x,y) size (w,l) [height <h>] [material "\u2026"] {\n wall east west north // plain walls \u2014 several in one statement\n wall south { door Main at <offset> size (w,h) [open] } // wall WITH openings: one side\n wall west { window W at <offset> size (w,h) [sill <s>] [open] }\n item asset { \u2026 } anchor center [gap (gx,gy)] // furniture anchored inside the room\n}\n```\n\n- `wall <side>\u2026` sides are `north|south|east|west`. A `wall <side>` line may also\n add `height <h>` / `height_end <h>` (sloped).\n- `door`/`window` `at <offset>` is measured along the wall from its start;\n `size (width, height)`; `window \u2026 sill <s>` sets the sill height; `open` = a bare\n hole (no leaf/glazing).\n\nA **free-standing wall** (not a room side):\n\n```wdl\nwall Name from (x1,y1) to (x2,y2) [height <h>] [height_end <h>] [facing north|\u2026] {\n \u2026 door/window openings \u2026\n}\n```\n\n- `from`/`to` are the wall\'s **centreline** endpoints; the wall is drawn as a rectangle\n `wall_thickness` wide, centred on that line.\n- **Overlap walls at corners \u2014 they do NOT auto-mitre.** Two free-standing walls that\n merely *touch* at a shared endpoint leave an unfilled square notch (\xBD\xB7`wall_thickness`)\n at the corner, because each is just a rectangle capped at its endpoint. To fill the\n corner, **extend the endpoints so the wall bodies OVERLAP** \u2014 run at least one wall\'s\n end **half the wall thickness past** the shared point (overlapping by the full thickness\n is fine and simplest). For an L of thickness 8 meeting at `(160,40)`:\n\n ```wdl\n wall H from (40, 40) to (164, 40) height 108 // ends 4 (\xBD\xB78) PAST the corner\n wall V from (160, 40) to (160, 160) height 108 // butts into H\'s overlapped body\n ```\n\n (Room walls handle their own corners; this only applies to `wall \u2026 from \u2026 to \u2026`.)\n\n## Objects \u2014 circulation & fittings\n\n```wdl\nstaircase [name "N"] at (start_x, start_y) step (rise, tread, width)\n direction north|south|east|west\n [total_height <h>] [max_run <r>] [landing_depth <d>]\n [landing_thickness <t>] [turn clockwise|anticlockwise] [flight_gap <g>]\n\nkitchen [name "N"] path ((x,y), (x,y), \u2026) side left|right\n depth <d> height <h> [base_z <z>] // path points are literal numbers\n```\n\n**Staircases are TOP-anchored \u2014 this is the #1 mistake.** You put a staircase on the\n**UPPER** floor and it **DESCENDS** to the floor below:\n\n- `at (x,y)` is the **TOP** of the stair (where it meets the floor it\'s declared on).\n- `direction` is the **descent** direction (the way it travels going *down*).\n- `total_height` is the **drop** to the floor below (omit \u2192 the floor-below\'s height).\n- `max_run` caps a flight\'s run; exceed it and the stair auto-splits into switchback\n flights with turn landings (`landing_depth`/`turn`/`flight_gap` tune the switchback).\n\nSo a stair connecting the ground floor **up** to the first floor lives on the **First\nFloor**, descending to the ground:\n\n```wdl\nfloor 2 "First Floor" height 116 {\n slab at (\u2026) size (\u2026)\n staircase name "Stair" at (212, 64) step (7, 11, 44) // top = this floor, at the landing\n direction south total_height 116 // descends south to the floor below\n}\n```\n\nPut it on the *lower* floor (thinking of it as "climbing up") and it descends the wrong way\n\u2014 **below ground** \u2014 where it draws in 2D plans but is buried/invisible in 3D. `check.sh`\ncatches that (convention **C5**), but author it top-anchored from the start.\n\nitem [name "N"] asset { id "sofa" src "furniture/sofa.glb" dims (w,h,d) [category "\u2026"] }\n at (x,y) [rotation <deg>] [scale <s>]\n [anchor_to "RoomName" anchor center gap (gx,gy)]\n```\n\nFurniture `dims` are the real-world size in **metres** `(width, height, depth)`;\n`src` is a GLB URL (bundled ids resolve at `furniture/<id>.glb` \u2014 e.g. `sofa`,\n`bed_double`, `dining_table`; an unreachable GLB shows a placeholder box, never a\nblank). `anchor` is one of `top-left top-center top-right center-left center\ncenter-right bottom-left bottom-center bottom-right`.\n\n## Objects \u2014 roof (one object; flat / shed / gable / hip)\n\nThe roof lives ALONE on its own top floor and you never set its Z (see\n`roof-v2-guide.md`). `endpoint`: `closed` = hip triangle, `open` = gable end-wall.\n\n```wdl\nroof [name "N"] pitched|shed|flat\n [endpoint open|closed]\n [slope angle <deg> | slope height <ridge_h>]\n [overhang <o>] [slab_thickness <t>] [parapet <h> x <t>] [gable_wall_thickness <t>] {\n segment "id" from (x,y) to (x,y) width <w>\n [high_side left|right] // shed only\n [start_endpoint open|closed] [end_endpoint open|closed]\n [hip_setback (a,b)] [gable_overhang (a,b)] [hip_ridge_extension (a,b)]\n [overhang <o>] [tie_beams N]\n truss "segId" fink|mono_pitch at (pos, pos, \u2026)\n }\n```\n\nSegment `from`/`to`/`width` and the `hip_setback`/\u2026 values accept formulas, so a\nroof scales with the plot (e.g. `width House.W`, `hip_setback (Verandah.L, Padvi.L)`).\n\n## Components & layers\n\n```wdl\ncomponent Bench { // a reusable mini-house in LOCAL coords (origin 0,0)\n param blen = 60 label "Bench length"\n beam name "Top" at (0,0) size (blen, 18) height 6\n}\nuse Bench as "B1" at (x,y) with { blen = 80 } // stamp it onto a floor\n\nlayer "structure" "Structure" [color "#rrggbb"] [group "Frame"] // per-house layer registry\n```\n\n## The `raw` escape (rarely needed)\n\nAnything the first-class syntax doesn\'t cover can be written as literal JSON per\nthe `.wadi` schema (`data-model.md`):\n\n```wdl\nraw "type" { "field": 1, "formulas": { "field": "= expr" } }\n```\n\n## DSL-specific pitfalls\n\n- **`convention center` and `units \u2026 per_unit 10`** belong at the top of every\n `house` \u2014 same as the JSON path. All the `coordinate-system.md` rules (Y-down,\n units, centreline abutment) apply identically; the DSL just writes them shorter.\n- **Formulas are bare expressions**, not `"= \u2026"` strings \u2014 the compiler emits the\n `= \u2026` form for you. Write `at (main.x1, main.yA)`, not `at ("= main.x1", \u2026)`.\n- **`name "\u2026"` is quoted; `room`/`pillar`/`var`/grid-line names are bare** ids\n (no spaces, and not a reserved word like `width`, `height`, `size`, `at`).\n- **Roof alone on the top floor**; segment widths/positions come from the walls\n they sit on. See `roof-v2-guide.md`.\n- **A pillar\'s `at` is its TOP-LEFT corner, not its centre.** To centre a column on\n `(cx,cy)`, author `at (cx - w/2, cy - l/2)`.\n- **Free-standing walls don\'t auto-mitre at corners** \u2014 extend endpoints so the wall\n bodies overlap (\u2265 \xBD\xB7`wall_thickness` past the shared point), or the corner is left\n as a gap.\n- **Staircases are top-anchored** \u2014 put them on the UPPER floor; they descend to the\n floor below (`check.sh` C5 flags one that lands below ground). See the staircase note.\n- **Structural conventions are enforced** \u2014 `check.sh` fails on floating floors\n (plinth-floor `height` \u2260 plinth block height; a no-slab floor with nonzero\n `slab_thickness`) and warns on exterior room sides left open. See\n `conventions.md`; the DSL editor shows the same findings in its status pill.\n- Compile after **every** edit; a parse error means the `.wadi` wasn\'t updated, so\n the live model just won\'t change \u2014 never silently wrong.\n'
|
|
417198
|
+
"body": '# The Wadi DSL (`.wdl`) \u2014 authoring reference\n\nYou author houses in the **Wadi DSL** \u2014 a small, formal language (`.wdl`) that\ncompiles to a resolved `.wadi` (`house_config.json`). The DSL is **complete**:\nevery object type in the model has first-class syntax, so you rarely need the\n`raw` escape. Authoring the DSL is more direct and less error-prone than writing\nJSON \u2014 the grammar enforces structure, and `check.sh` reports parse errors with\nline:col.\n\n**This file is the SYNTAX reference.** The *semantics* live in the other\nreferences and apply unchanged \u2014 read them:\n\n- `coordinate-system.md` \u2014 X\u2192right, **Y\u2192DOWN**, Z\u2192up; **10 units = 1 ft**;\n the **centreline** convention. The #1 source of mistakes.\n- `conventions.md` \u2014 the **structural coding conventions** (`check.sh` enforces\n them): plinth-floor height must match the plinth block, rooms must wall every\n exterior side, a no-slab floor must set `slab_thickness 0`.\n- `parametric-conventions.md` \u2014 the grid-first recipe for reusable templates.\n- `roof-v2-guide.md` \u2014 roof segments, hip vs gable, trusses, joints.\n- `data-model.md` \u2014 the underlying `.wadi` schema (what the DSL compiles to; also\n the field reference for the `raw` escape).\n\n## The loop\n\n1. Write / edit `house.wdl` \u2014 the **single shared source** (you and the human\n co-edit it; the app\'s DSL previewer renders it live). You never produce a `.wadi`.\n2. `wadi-skill/architect/scripts/check.sh house.wdl` \u2014 runs the DSL compiler +\n validator (schema + wall/roof geometry) against a **throwaway temp** just for\n feedback; fix any reported error and re-run.\n3. `preview.sh house.wdl` \u2192 read the PNGs (plans / elevations / roof) to check your\n work. (It also compiles to a throwaway temp \u2014 no persistent `.wadi`.)\n\n## Skeleton\n\n```wdl\nhouse MyHouse {\n convention center // ALWAYS use center (wall-centreline coords)\n units feet_inches per_unit 10 // 10 project units = 1 ft\n site { plot (WIDTH, LENGTH) ref (0, 0) }\n defaults { floor_height 120 wall_height 108 slab_thickness 8 wall_thickness 8 }\n\n // parametric core (optional): var, point, grid, configurator\n // component / layer declarations (optional)\n floor 0 "Plinth" { \u2026 } // floors stack in source order (0 = plinth)\n floor 1 "Ground Floor" { \u2026 }\n floor 2 "Loft" { roof \u2026 } // roof lives ALONE on its own top floor\n}\n```\n\nNumbers are **project units** (feet \xD7 10 by default). Names after `house`,\n`room`, `pillar`, `var`, `point`, `grid`, and `use`/`component` are bare\nidentifiers (no spaces); names introduced with the `name` keyword are quoted\nstrings.\n\n## Parametric core (domain-neutral)\n\n```wdl\nvar wallT = 8 // a knob; may reference other vars\nvar pilInset = (pillarW - wallT) / 2\n\npoint House { x = 420, y = 470 } // reference as House.x / House.W / House.L\n // (.W = x, .L = y \u2014 a point doubles as a size)\n\ngrid main { // named wall centrelines; publishes main.x1 / main.yA\n x: 1 @ wallT / 2, 2 @ House.W / 2, 3 @ House.W - wallT / 2\n y: A @ wallT / 2, B @ House.L / 2, C @ House.L - wallT / 2\n}\n// each line may add: \u2026 @ <expr> thick <expr> role structural|planning\n\nconfigurator { // the knobs a downstream user turns\n slider pillarW "Column size" ft [8 .. 14 step 1]\n number ceiling "Ceiling height" ft\n toggle has_loft "Add a loft"\n select roof_style "Roof style" { Flat = 0, Shed = 1, Gable = 2, Hip = 3 }\n}\n```\n\n**Formulas are automatic.** Any geometry number can be a formula \u2014 just write the\nexpression instead of a literal (`at (main.x1, main.yA)`, `size (House.W/2, 200)`).\nOperators: `+ - * /`, unary `-`, parentheses, and the functions\n`min max clamp round floor ceil abs`. References: a `var`, a `point`\n(`House.W`), or a grid line (`main.x3 - main.x1`). No comparison operators \u2014 gate\nthings with the `min/abs` idiom (see `enabled` below).\n\n## Common attribute tail (every object)\n\nAfter an object\'s geometry, in THIS order, any of:\n\n```\n\u2026 z_offset <expr> enabled <expr> layer "id" [material "id"]\n```\n\n- `enabled <expr>` \u2014 the on/off switch. A `0`/`false` value hides the object. To\n gate on a configurator variable, use a 0/1 formula:\n `enabled 1 - min(1, abs(roof_style - 3))` renders the object only when\n `roof_style == 3`. (This is how one template carries several roofs and shows\n only the chosen one.)\n- `z_offset <expr>` \u2014 lift above the floor base (split levels).\n- `material "id"` \u2014 only on plinth / ground / room / wall / staircase / kitchen /\n roof.\n\n## Objects \u2014 structure & envelope\n\n```wdl\nslab [name "N"] at (x,y) size (w,l) [thickness <t>] // floor_slab\nbeam [name "N"] at (x,y) size (w,l) [height <h>]\nplinth [name "N"] at (x,y) size (w,l) height <h> // raised base (Plinth floor)\nground [name "N"] at (x,y) size (w,l) [height <h>] // terrain plane\npillar Name at (x,y) size (w,l) [height <h>] // (x,y) = TOP-LEFT corner\n```\n\n`at (x,y)` is the **TOP-LEFT CORNER** \u2014 **not the centre** \u2014 for every one of these\n(same as rooms/slabs/beams); `size (w,l)` is width \xD7 length. All accept the common tail.\n\n**Pillars catch people out here.** A column reads as "placed at a point," but `at` is\nstill its corner. To **centre a column on a point** `(cx, cy)` \u2014 a grid node, a room\ncorner \u2014 place it at **`at (cx - w/2, cy - l/2)`**, never at `(cx, cy)`. On a grid, the\n`pilInset` idiom (see `parametric-conventions.md`) does exactly this so columns sit flush.\n\n## Objects \u2014 rooms, walls & openings\n\nA room shows exactly the walls you declare. A **bare room (no `wall` lines) is\nenclosed on all four sides.** List plain walls compactly; give a wall its own line\nonly when it carries a door/window; omit a side to leave it open (verandah).\n\n```wdl\nroom Name at (x,y) size (w,l) [height <h>] [material "\u2026"] {\n wall east west north // plain walls \u2014 several in one statement\n wall south { door Main at <offset> size (w,h) [open] } // wall WITH openings: one side\n wall west { window W at <offset> size (w,h) [sill <s>] [open] }\n item asset { \u2026 } anchor center [gap (gx,gy)] // furniture anchored inside the room\n}\n```\n\n- `wall <side>\u2026` sides are `north|south|east|west`. A `wall <side>` line may also\n add `height <h>` / `height_end <h>` (sloped).\n- `door`/`window` `at <offset>` is measured along the wall from its start;\n `size (width, height)`; `window \u2026 sill <s>` sets the sill height; `open` = a bare\n hole (no leaf/glazing).\n\nA **free-standing wall** (not a room side):\n\n```wdl\nwall Name from (x1,y1) to (x2,y2) [height <h>] [height_end <h>] [facing north|\u2026] {\n \u2026 door/window openings \u2026\n}\n```\n\n- `from`/`to` are the wall\'s **centreline** endpoints; the wall is drawn as a rectangle\n `wall_thickness` wide, centred on that line.\n- **Overlap walls at corners \u2014 they do NOT auto-mitre.** Two free-standing walls that\n merely *touch* at a shared endpoint leave an unfilled square notch (\xBD\xB7`wall_thickness`)\n at the corner, because each is just a rectangle capped at its endpoint. To fill the\n corner, **extend the endpoints so the wall bodies OVERLAP** \u2014 run at least one wall\'s\n end **half the wall thickness past** the shared point (overlapping by the full thickness\n is fine and simplest). For an L of thickness 8 meeting at `(160,40)`:\n\n ```wdl\n wall H from (40, 40) to (164, 40) height 108 // ends 4 (\xBD\xB78) PAST the corner\n wall V from (160, 40) to (160, 160) height 108 // butts into H\'s overlapped body\n ```\n\n (Room walls handle their own corners; this only applies to `wall \u2026 from \u2026 to \u2026`.)\n\n## Objects \u2014 circulation & fittings\n\n```wdl\nstaircase [name "N"] at (start_x, start_y) step (rise, tread, width)\n direction north|south|east|west\n [total_height <h>] [max_run <r>] [landing_depth <d>]\n [landing_thickness <t>] [turn clockwise|anticlockwise] [flight_gap <g>]\n\nkitchen [name "N"] path ((x,y), (x,y), \u2026) side left|right\n depth <d> height <h> [base_z <z>] // path points are literal numbers\n```\n\n**Staircases are TOP-anchored \u2014 this is the #1 mistake.** You put a staircase on the\n**UPPER** floor and it **DESCENDS** to the floor below:\n\n- `at (x,y)` is the **TOP** of the stair (where it meets the floor it\'s declared on).\n- `direction` is the **descent** direction (the way it travels going *down*).\n- `total_height` is the **drop** to the floor below (omit \u2192 the floor-below\'s height).\n- `max_run` caps a flight\'s run; exceed it and the stair auto-splits into switchback\n flights with turn landings (`landing_depth`/`turn`/`flight_gap` tune the switchback).\n\nSo a stair connecting the ground floor **up** to the first floor lives on the **First\nFloor**, descending to the ground:\n\n```wdl\nfloor 2 "First Floor" height 116 {\n slab at (\u2026) size (\u2026)\n staircase name "Stair" at (212, 64) step (7, 11, 44) // top = this floor, at the landing\n direction south total_height 116 // descends south to the floor below\n}\n```\n\nPut it on the *lower* floor (thinking of it as "climbing up") and it descends the wrong way\n\u2014 **below ground** \u2014 where it draws in 2D plans but is buried/invisible in 3D. `check.sh`\ncatches that (convention **C5**), but author it top-anchored from the start.\n\nitem [name "N"] asset { id "sofa" src "furniture/sofa.glb" dims (w,h,d) [category "\u2026"] }\n at (x,y) [rotation <deg>] [scale <s>]\n [anchor_to "RoomName" anchor center gap (gx,gy)]\n```\n\nFurniture `dims` are the real-world size in **metres** `(width, height, depth)`;\n`src` is a GLB URL (bundled ids resolve at `furniture/<id>.glb` \u2014 e.g. `sofa`,\n`bed_double`, `dining_table`; an unreachable GLB shows a placeholder box, never a\nblank). `anchor` is one of `top-left top-center top-right center-left center\ncenter-right bottom-left bottom-center bottom-right`.\n\n## Objects \u2014 roof (one object; flat / shed / gable / hip)\n\nThe roof lives ALONE on its own top floor and you never set its Z (see\n`roof-v2-guide.md`). `endpoint`: `closed` = hip triangle, `open` = gable end-wall.\n\n```wdl\nroof [name "N"] pitched|shed|flat\n [endpoint open|closed]\n [slope angle <deg> | slope height <ridge_h>]\n [overhang <o>] [slab_thickness <t>] [parapet <h> x <t>] [gable_wall_thickness <t>] {\n segment "id" from (x,y) to (x,y) width <w>\n [high_side left|right] // shed only\n [start_endpoint open|closed] [end_endpoint open|closed]\n [hip_setback (a,b)] [gable_overhang (a,b)] [hip_ridge_extension (a,b)]\n [overhang <o>] [tie_beams N]\n truss "segId" fink|mono_pitch at (pos, pos, \u2026)\n }\n```\n\nSegment `from`/`to`/`width` and the `hip_setback`/\u2026 values accept formulas, so a\nroof scales with the plot (e.g. `width House.W`, `hip_setback (Verandah.L, Padvi.L)`).\n\n**Roof coordinates are wall centrelines (under `convention center`), same as\nrooms.** `from`/`to` is the segment\'s ridge/axis and `width` its span *centred on\nthat axis*. Author them on the **same centreline grid as the walls** \u2014 a segment\nwhose axis + width match the rooms\' centrelines auto-grows to the **outer wall\nface** on every side (the compiler extends the axis by \xBD\xB7wall_thickness at each end\nand widens by wall_thickness, exactly the grow a room gets). `overhang` then\nextends *beyond* the outer face. So to cover a footprint spanning wall centrelines\n`x1..x2` (E\u2013W) and `yA..yB` (N\u2013S), write `from (x1, (yA+yB)/2) to (x2, (yA+yB)/2)\nwidth (yB - yA)` \u2014 do **not** add \xBD-wall fudge factors; the convention handles it.\n(Before this, a roof drawn on the grid sat half a wall-thickness *inside* the walls.)\n\n## Components & layers\n\n```wdl\ncomponent Bench { // a reusable mini-house in LOCAL coords (origin 0,0)\n param blen = 60 label "Bench length"\n beam name "Top" at (0,0) size (blen, 18) height 6\n}\nuse Bench as "B1" at (x,y) with { blen = 80 } // stamp it onto a floor\n\nlayer "structure" "Structure" [color "#rrggbb"] [group "Frame"] // per-house layer registry\n```\n\n## The `raw` escape (rarely needed)\n\nAnything the first-class syntax doesn\'t cover can be written as literal JSON per\nthe `.wadi` schema (`data-model.md`):\n\n```wdl\nraw "type" { "field": 1, "formulas": { "field": "= expr" } }\n```\n\n## DSL-specific pitfalls\n\n- **`convention center` and `units \u2026 per_unit 10`** belong at the top of every\n `house` \u2014 same as the JSON path. All the `coordinate-system.md` rules (Y-down,\n units, centreline abutment) apply identically; the DSL just writes them shorter.\n- **Formulas are bare expressions**, not `"= \u2026"` strings \u2014 the compiler emits the\n `= \u2026` form for you. Write `at (main.x1, main.yA)`, not `at ("= main.x1", \u2026)`.\n- **`name "\u2026"` is quoted; `room`/`pillar`/`var`/grid-line names are bare** ids\n (no spaces, and not a reserved word like `width`, `height`, `size`, `at`).\n- **Roof alone on the top floor**; segment widths/positions come from the walls\n they sit on. See `roof-v2-guide.md`.\n- **A pillar\'s `at` is its TOP-LEFT corner, not its centre.** To centre a column on\n `(cx,cy)`, author `at (cx - w/2, cy - l/2)`.\n- **Free-standing walls don\'t auto-mitre at corners** \u2014 extend endpoints so the wall\n bodies overlap (\u2265 \xBD\xB7`wall_thickness` past the shared point), or the corner is left\n as a gap.\n- **Staircases are top-anchored** \u2014 put them on the UPPER floor; they descend to the\n floor below (`check.sh` C5 flags one that lands below ground). See the staircase note.\n- **Structural conventions are enforced** \u2014 `check.sh` fails on floating floors\n (plinth-floor `height` \u2260 plinth block height; a no-slab floor with nonzero\n `slab_thickness`) and warns on exterior room sides left open. See\n `conventions.md`; the DSL editor shows the same findings in its status pill.\n- Compile after **every** edit; a parse error means the `.wadi` wasn\'t updated, so\n the live model just won\'t change \u2014 never silently wrong.\n'
|
|
417181
417199
|
},
|
|
417182
417200
|
"conventions": {
|
|
417183
417201
|
"title": "Structural coding conventions (C1/C2/C3\u2026)",
|
|
@@ -417189,11 +417207,11 @@ var DOCS = {
|
|
|
417189
417207
|
},
|
|
417190
417208
|
"parametric-conventions": {
|
|
417191
417209
|
"title": "Building a fully-parametric template",
|
|
417192
|
-
"body": '# Parametric model conventions (reusable house templates)\n\nHow to author a **fully-parametric** `.wadi` \u2014 a template that stays valid under *any*\nknob change (resize the plot, widen a room, add a floor) instead of a one-off with\nbaked-in numbers. Use it when building a **template** for the library / picker; skip it\nfor a quick fixed sketch.\n\nThe canonical worked example is **`examples/coastal_konkan.wadi`** \u2014 read it alongside\nthis. The engine: `variables` \u2192 `grids` \u2192 per-object `formulas`, resolved\ntopologically by `editor/src/param/resolve.ts`. Formula ops: `+ - * /`, `min`, `max`,\n`clamp`, `round`, `floor`, `ceil`, `abs` (no `eval`, no comparisons/ternary).\n\n## 0. The one big idea: the GRID is the parametric layer\n\nA **grid** is a set of named wall-**centreline** lines \u2014 X (west\u2192east: `1,2,3\u2026`) and Y\n(north\u2192south: `A,B,C\u2026`). Each line\'s position (`at`) is a formula of the house size and\nyour knobs. **Rooms don\'t compute their own geometry \u2014 they name grid lines.** Move a\nline (or the knob its `at` depends on) and every room, slab and column on it follows.\n\n```\nHouse size + knobs \u2500\u2500\u25B6 grid line `at` formulas \u2500\u2500\u25B6 rooms/pillars reference lines\n (variables) (the grids block) (= main.x1, \u2026)\n```\n\nThis one-directional flow is the whole recipe. There is **no** room-corner point layer\nand **no** per-room wall math (both existed in the old convention \u2014 don\'t use them).\n\n## 1. Set the centreline convention\n\nAt the top of the house set:\n\n```jsonc\n"coord_convention": "center"\n```\n\nThen a rect object\'s `x, y, width, length` are wall **centrelines**, and **two rooms\nthat share a wall just ABUT on the shared line** \u2014 room A spans X `[x1,x4]`, room B\nspans `[x4,x8]`, the wall is centred on `x4` and shared. No overlap, no `wallT` math;\nthe renderer grows each footprint to the outer face automatically. See\n`coordinate-system.md` \u2192 "the centreline convention".\n\n## 2. Variables \u2014 the knobs\n\n- **Base dims:** `wallT` (wall thickness), `floorH`, `slabH`, plus derived helpers\n (`wallH = "=floorH-slabH"`).\n- **Column sizes + inset:** `pillarW` (and `pillarL` if rectangular), and\n **`pilInset = "= (pillarW - wallT) / 2"`** \u2014 the amount a perimeter column must move\n inward to sit flush with the facade (see \xA75).\n- **Opening sizes:** `doorW/doorH`, `winW/winH/winSill`, `entranceW`, `doorMargin` \u2014 never\n inline literals.\n- **Room-proportion knobs:** `pct<Room><W/L>` + `min<Room><W/L>` (see \xA73).\n\nVariables may reference only other variables (they resolve before the grid).\n\n## 3. The grid \u2014 named lines as formulas\n\nDefine `grids.main` with X and Y lines. The **outer** lines anchor to the plinth edge;\n**interior** lines carry the room-proportion logic:\n\n```jsonc\n"grids": { "main": {\n "x": [\n { "name": "1", "at": "= wallT / 2" }, // left outer face at 0\n { "name": "2", "at": "= max(minBedW, round(pctBedW * House.W)) + 3*wallT/2" },\n { "name": "8", "at": "= House.W - wallT / 2" } // right outer face at House.W\n ],\n "y": [ { "name": "A", "at": "= wallT / 2" }, { "name": "F", "at": "= House.L - wallT / 2" } ]\n} }\n```\n\n- **Outer lines:** `1`/`A` at `wallT/2`, the last at `House.dim \u2212 wallT/2`. A wall\n centred on `wallT/2` has its outer face at `0`; on `House.W \u2212 wallT/2`, at `House.W`.\n So the building fills the plot exactly.\n- **Interior lines \u2014 where pct/min lives.** Either a fixed **fraction** of the span\n (`round(wallT/2 + frac*(House.W - wallT))`, pure proportional \u2014 the coastal style) or\n a **`max(min, pct*House.dim)`** offset (keeps a room from getting unusably small on a\n tight plot \u2014 the cottage/family style). Because a room\'s clear interior is\n `(line-to-line) \u2212 wallT`, add `+ 3*wallT/2` to a `max(min, pct*\u2026)` size so the KNOB\n equals the clear interior.\n- Each grid line is published as a formula **symbol** `main.x<name>` / `main.y<name>`\n (`main.x1`, `main.yA`). Positions are conserved automatically \u2014 the lines partition\n the span, so there are no gaps/overlaps to reconcile.\n\n## 4. Rooms, slabs, plinth \u2014 reference the grid, nothing else\n\n```jsonc\n{ "type":"room", "name":"Hall",\n "formulas": { "x":"= main.x1", "y":"= main.yA",\n "width":"= main.x4 - main.x1", "length":"= main.yC - main.yA" } }\n```\n\n- `x`/`y` = the room\'s top-left grid node; `width`/`length` = the span to another node.\n- Adjacent rooms use the **same** line for their shared edge (room A `\u2026x4`, room B\n `x:"= main.x4"`) \u2192 they abut and share one wall.\n- The floor slab / plinth span the outermost lines (`main.x1 \u2192 main.x8`,\n `main.yA \u2192 main.yF`).\n- **Keep valid placeholder literals** on `x/y/width/length` (positive numbers) \u2014 the\n strict schema checks stored literals *before* resolve. The resolver overwrites them.\n\n## 5. Pillars \u2014 centre on a node
|
|
417210
|
+
"body": '# Parametric model conventions (reusable house templates)\n\nHow to author a **fully-parametric** `.wadi` \u2014 a template that stays valid under *any*\nknob change (resize the plot, widen a room, add a floor) instead of a one-off with\nbaked-in numbers. Use it when building a **template** for the library / picker; skip it\nfor a quick fixed sketch.\n\nThe canonical worked example is **`examples/coastal_konkan.wadi`** \u2014 read it alongside\nthis. The engine: `variables` \u2192 `grids` \u2192 per-object `formulas`, resolved\ntopologically by `editor/src/param/resolve.ts`. Formula ops: `+ - * /`, `min`, `max`,\n`clamp`, `round`, `floor`, `ceil`, `abs` (no `eval`, no comparisons/ternary).\n\n## 0. The one big idea: the GRID is the parametric layer\n\nA **grid** is a set of named wall-**centreline** lines \u2014 X (west\u2192east: `1,2,3\u2026`) and Y\n(north\u2192south: `A,B,C\u2026`). Each line\'s position (`at`) is a formula of the house size and\nyour knobs. **Rooms don\'t compute their own geometry \u2014 they name grid lines.** Move a\nline (or the knob its `at` depends on) and every room, slab and column on it follows.\n\n```\nHouse size + knobs \u2500\u2500\u25B6 grid line `at` formulas \u2500\u2500\u25B6 rooms/pillars reference lines\n (variables) (the grids block) (= main.x1, \u2026)\n```\n\nThis one-directional flow is the whole recipe. There is **no** room-corner point layer\nand **no** per-room wall math (both existed in the old convention \u2014 don\'t use them).\n\n## 1. Set the centreline convention\n\nAt the top of the house set:\n\n```jsonc\n"coord_convention": "center"\n```\n\nThen a rect object\'s `x, y, width, length` are wall **centrelines**, and **two rooms\nthat share a wall just ABUT on the shared line** \u2014 room A spans X `[x1,x4]`, room B\nspans `[x4,x8]`, the wall is centred on `x4` and shared. No overlap, no `wallT` math;\nthe renderer grows each footprint to the outer face automatically. See\n`coordinate-system.md` \u2192 "the centreline convention".\n\n## 2. Variables \u2014 the knobs\n\n- **Base dims:** `wallT` (wall thickness), `floorH`, `slabH`, plus derived helpers\n (`wallH = "=floorH-slabH"`).\n- **Column sizes + inset:** `pillarW` (and `pillarL` if rectangular), and\n **`pilInset = "= (pillarW - wallT) / 2"`** \u2014 the amount a perimeter column must move\n inward to sit flush with the facade (see \xA75).\n- **Opening sizes:** `doorW/doorH`, `winW/winH/winSill`, `entranceW`, `doorMargin` \u2014 never\n inline literals.\n- **Room-proportion knobs:** `pct<Room><W/L>` + `min<Room><W/L>` (see \xA73).\n\nVariables may reference only other variables (they resolve before the grid).\n\n## 3. The grid \u2014 named lines as formulas\n\nDefine `grids.main` with X and Y lines. The **outer** lines anchor to the plinth edge;\n**interior** lines carry the room-proportion logic:\n\n```jsonc\n"grids": { "main": {\n "x": [\n { "name": "1", "at": "= wallT / 2" }, // left outer face at 0\n { "name": "2", "at": "= max(minBedW, round(pctBedW * House.W)) + 3*wallT/2" },\n { "name": "8", "at": "= House.W - wallT / 2" } // right outer face at House.W\n ],\n "y": [ { "name": "A", "at": "= wallT / 2" }, { "name": "F", "at": "= House.L - wallT / 2" } ]\n} }\n```\n\n- **Outer lines:** `1`/`A` at `wallT/2`, the last at `House.dim \u2212 wallT/2`. A wall\n centred on `wallT/2` has its outer face at `0`; on `House.W \u2212 wallT/2`, at `House.W`.\n So the building fills the plot exactly.\n- **Interior lines \u2014 where pct/min lives.** Either a fixed **fraction** of the span\n (`round(wallT/2 + frac*(House.W - wallT))`, pure proportional \u2014 the coastal style) or\n a **`max(min, pct*House.dim)`** offset (keeps a room from getting unusably small on a\n tight plot \u2014 the cottage/family style). Because a room\'s clear interior is\n `(line-to-line) \u2212 wallT`, add `+ 3*wallT/2` to a `max(min, pct*\u2026)` size so the KNOB\n equals the clear interior.\n- Each grid line is published as a formula **symbol** `main.x<name>` / `main.y<name>`\n (`main.x1`, `main.yA`). Positions are conserved automatically \u2014 the lines partition\n the span, so there are no gaps/overlaps to reconcile.\n\n## 4. Rooms, slabs, plinth \u2014 reference the grid, nothing else\n\n```jsonc\n{ "type":"room", "name":"Hall",\n "formulas": { "x":"= main.x1", "y":"= main.yA",\n "width":"= main.x4 - main.x1", "length":"= main.yC - main.yA" } }\n```\n\n- `x`/`y` = the room\'s top-left grid node; `width`/`length` = the span to another node.\n- Adjacent rooms use the **same** line for their shared edge (room A `\u2026x4`, room B\n `x:"= main.x4"`) \u2192 they abut and share one wall.\n- The floor slab / plinth span the outermost lines (`main.x1 \u2192 main.x8`,\n `main.yA \u2192 main.yF`).\n- **Keep valid placeholder literals** on `x/y/width/length` (positive numbers) \u2014 the\n strict schema checks stored literals *before* resolve. The resolver overwrites them.\n\n## 5. Pillars \u2014 corner-anchored; centre on a node by subtracting half the width\n\nA pillar\'s `(x,y)` is its **TOP-LEFT CORNER** in **both** conventions (columns align\nto corners \u2014 the convention never shifts a pillar the way it grows a room). To drop a\ncolumn **centred** on a grid node, subtract **half its width** in the formula:\n\n```jsonc\n{ "type":"pillar", "name":"P_mid",\n "formulas": { "x":"= main.x3 - pillarW/2", "y":"= main.yF - pillarW/2",\n "width":"= pillarW", "length":"= pillarW" } }\n```\n\n- **Interior columns** centre on their node: `"= main.x3 - pillarW/2"`.\n- **Perimeter columns** are wider than the wall, so centred on an outer line they\'d jut\n past the plinth. Inset by `pilInset` first, **then** subtract `pillarW/2` \u2014 the net\n effect puts the column\'s OUTER face flush with the wall\'s outer face:\n - on the **min** line (`x1`, `yA`): `"= (main.x1) + pilInset - pillarW/2"`\n - on the **max** line (`x8`, `yF`): `"= (main.x8) - pilInset - pillarW/2"`\n - a corner column does both axes; a front-row column that\'s interior in X only insets Y.\n- Because `pilInset = (pillarW \u2212 wallT)/2` and `pillarW` are formulas, columns stay flush\n when you resize the plot, the wall, or the column.\n- **Colonnade columns** between nodes: `"= (main.x1 + main.x2)/2 - pillarW/2"` (centre on\n the midpoint); still inset the axis that sits on an outer line.\n- **Freehand (no grid):** put `at (x,y)` where the column\'s **corner** should sit \u2014 it\n lands there exactly, so you can butt a column against a room corner or wall junction.\n\n## 6. Openings\n\n- Opening sizes are **variables**; every opening is **positioned by formula** from its\n room\'s line span.\n- **Windows / wide entrances are CENTRED:** offset `= ((span) \u2212 w)/2`.\n- **Internal doors tuck into a corner** (`doorMargin` from one end) to keep a\n continuous wall run for furniture.\n- **A shared wall is declared once** \u2014 the room with the door declares that side\'s\n opening; the neighbour omits the side (the coincident wall is already there).\n- **Validate every opening:** `0 \u2264 offset && offset + width \u2264 wallSpan`, or\n `expandRoomWalls` throws.\n\n## 7. Konkan layout conventions\n\n- **Central hall (Majghar) is the circulation hub** \u2014 rooms open into it, not into each\n other. Preserves privacy and frees wall runs.\n- **Consolidate wet services** \u2014 kitchen + bathroom share a wall so plumbing runs\n together.\n- **Front verandah** (full-width, pillared) + **rear Padvi** (rear verandah).\n- Give fixed rooms a comfortable **minimum** (a 6\'\xD76\' bathroom reads cramped; ~7\'\xD77\' is\n better) \u2014 raise the `min`, not just the `pct`.\n\n## 8. Build & verify \u2014 the loop\n\n1. **Author** the grid + formulas with valid placeholder literals.\n2. **Validate:** `cd editor && npx tsx ../wadi-skill/architect/scripts/validate.mjs <ABS_PATH>`\n \u2014 resolves, schema-checks, and runs the wall/roof pipeline. Exit 0 = good.\n3. **Render + read:** `wadi-skill/architect/scripts/preview.sh <ABS_PATH>` \u2192 look at the\n plans (rooms in place, columns flush, roof over the plinth).\n4. **Scale sweep \u2014 the whole point.** Resolve + render at a **small**, a **large**, and\n an **off-aspect** plot (change `House.W`/`House.L` or the plot). Confirm: no negative\n rooms, rooms fill the plot exactly, every opening fits, **no column juts past the\n plinth**, no pillar/door overlap. A change that only works at the default size isn\'t\n done.\n5. In the app: the **Geometry-issues panel says "No geometry issues"**, and the live 3D\n model updates on save.\n\nSee also `coordinate-system.md` (Y is DOWN, 10 units = 1 ft, the centreline rule),\n`data-model.md` (every field), and `examples/coastal_konkan.wadi` (the reference).\n'
|
|
417193
417211
|
},
|
|
417194
417212
|
"roof-v2-guide": {
|
|
417195
417213
|
"title": "The roof object (hip/gable/shed/flat)",
|
|
417196
|
-
"body": '# Roof v2 guide (the unified `roof` object)\n\nThe `roof` object replaces the legacy `hip_roof`/`gable_roof`/`flat_roof`/\n`shed_roof`. It is **segment-based**. The schema is permissive; the real\nvalidation happens in the compute pipeline, so **always run `validate.mjs`**\nafter writing a roof (it runs the exact derivation the app uses).\n\n> **Strategy:** roof geometry is the subtlest part of the config. Don\'t\n> hand-invent it \u2014 **copy the roof from the closest example** (see the shape map\n> below), then adjust its segment coordinates/widths to your plinth, and validate.\n\n## Where the roof lives, and its height (you do NOT set a Z)\n\nRead this first \u2014 it\'s the part that isn\'t obvious and that you must not try to\nreverse-engineer from the geometry.\n\n- **A roof object goes on its OWN top floor** \u2014 a floor whose `floor_number` is\n ABOVE every floor it covers, containing only the roof object(s). Name it\n something like `"Roof"` or `"Loft Floor"`. Example: ground = 0, first = 1 \u2192 the\n roof floor is `floor_number` **2**. The roof then covers the floors below it.\n (All the examples do this: the roof sits alone on the topmost floor.)\n- **You NEVER write a Z or height on the roof.** There is no Z field anywhere in\n the roof object. Its base sits automatically at the top of the walls below it,\n computed by the pipeline as:\n\n ```\n roof base Z = plinth.height + \u03A3 (height of every floor BELOW the roof\'s floor)\n ```\n\n using each floor\'s own `height` (falling back to `defaults.floor_height`).\n **`slab_thickness` does NOT enter this stack** \u2014 `height` already is the full\n floor-to-floor rise.\n- **To raise or lower the roof, change the FLOORS\' `height` (or `plinth.height`),\n not the roof.** If the roof floats too low/high, you put it on the wrong floor\n or a floor below it has the wrong `height`.\n- `slope` / `ridge_h` is the rise **above** that computed wall-top; segment\n `start`/`end` are 2D `[x, y]` only. Don\'t compute a Z \u2014 the pipeline does it\n entirely from the floor stack.\n\n## The core idea\n\nA roof is a set of **segments**. Each segment is a line (`start`\u2192`end`) with a\n**`width`** measured *perpendicular* to it, and **the segment sits at the CENTRE\nof that width** (the roof extends `width/2` to each side of the line). What the\nline means depends on `roof_type`:\n\n- **pitched** \u2014 the segment **is the ridge**; two symmetric slopes rise from the\n eaves up to it. Ends are covered per `default_endpoint`:\n - `"closed"` \u2192 **hip** (a sloped triangular hip face at the end).\n - `"open"` \u2192 **gable** (a vertical gable-end wall triangle).\n- **shed** \u2014 the segment is the **high edge**; the roof slopes down perpendicular\n to the far side. `shed_high_side` names which side is high.\n- **flat** \u2014 the segment\'s width band is a flat slab extruded down.\n\nSegments can run in **any direction** (not just axis-aligned).\n\n## Object shape\n\n```jsonc\n{\n "type": "roof",\n "roof_type": "pitched" | "shed" | "flat",\n "default_endpoint": "closed" | "open", // pitched only: closed=hip, open=gable\n "min_overhang": 25, // > 0 \u2014 eave overhang beyond the walls\n "slope": { "by": "height", "ridge_h": 50 }, // rise above wall top \u2026\n // or { "by": "angle", "angle_deg": 30 }, // \u2026 or a pitch angle\n "segments": [\n {\n "id": "seg0",\n "start": [x, y], "end": [x, y], // the ridge (pitched) / high edge (shed)\n "width": 200, // perpendicular span, segment centred in it\n "slope": { \u2026 }, // optional per-segment slope override\n "shed_high_side": "\u2026", // shed only\n "hip_setback_start": \u2026, "hip_setback_end": \u2026 // optional, tune closed hip faces\n }\n ],\n "trusses": [\n { "segment_id": "seg0", "type": "fink", "positions_along": [100, 210, 320] }\n // type: "fink" (pitched) | "mono_pitch" (shed)\n // positions_along: distances along the segment where a truss sits\n ],\n "framing": { \u2026 } // optional overrides \u2014 omit unless asked\n}\n```\n\n`slope` may be given once at the roof level (applies to all segments) or per\nsegment. `by: "height"` \u2192 `ridge_h` is the ridge rise above the wall top; `by:\n"angle"` \u2192 `angle_deg` is the pitch.\n\n## Joints (multi-segment shapes)\n\nWhen two segments share an **exactly coincident endpoint**, the pipeline\nauto-resolves that end as a **joint** (no hip/gable face there) \u2014 this is how L,\nU, and closed-loop (courtyard) roofs are formed. Get the endpoints numerically\nequal or they won\'t join.\n\n## Shape \u2192 which example to copy\n\n| Want | Segments | Copy from |\n|---|---|---|\n| Simple hip (one ridge, hipped ends) | 1, `pitched`, `closed` | `two_story_konkan.json` / `verandah_cottage.json` |\n| Simple gable (one ridge, gable ends) | 1, `pitched`, `open` | same, set `default_endpoint: "open"` |\n| **L-shape** (two wings) | 2, `pitched`, coincident inner endpoints | `l_shape_villa.json` |\n| **Courtyard** (ridge loop) | 4, `pitched`, endpoints chained in a loop | `courtyard_home.json` |\n| **Flat** roof/terrace | 1+, `flat` | `modern_flat.json` |\n| Shed / mono-pitch (lean-to, verandah) | 1, `shed`, `shed_high_side` set | derive from a `pitched` seg; add `shed_high_side` + slope |\n\n## Rules & pitfalls\n\n- The roof **footprint must cover the plinth footprint** \u2014 segment lines +\n widths should span the walls they sit on. Mirror the example\'s relationship to\n its plinth.\n- `min_overhang` must be **> 0**.\n- Every segment needs an `id`; every truss references a real `segment_id`.\n- Shed segments **require** a slope (`ridge_h` or `angle_deg`) and a\n `shed_high_side`, or derivation throws.\n- After any roof edit: run `validate.mjs` \u2014 a zero-length segment, missing slope,\n or non-covering footprint fails there, not in the schema.\n'
|
|
417214
|
+
"body": '# Roof v2 guide (the unified `roof` object)\n\nThe `roof` object replaces the legacy `hip_roof`/`gable_roof`/`flat_roof`/\n`shed_roof`. It is **segment-based**. The schema is permissive; the real\nvalidation happens in the compute pipeline, so **always run `validate.mjs`**\nafter writing a roof (it runs the exact derivation the app uses).\n\n> **Strategy:** roof geometry is the subtlest part of the config. Don\'t\n> hand-invent it \u2014 **copy the roof from the closest example** (see the shape map\n> below), then adjust its segment coordinates/widths to your plinth, and validate.\n\n## Where the roof lives, and its height (you do NOT set a Z)\n\nRead this first \u2014 it\'s the part that isn\'t obvious and that you must not try to\nreverse-engineer from the geometry.\n\n- **A roof object goes on its OWN top floor** \u2014 a floor whose `floor_number` is\n ABOVE every floor it covers, containing only the roof object(s). Name it\n something like `"Roof"` or `"Loft Floor"`. Example: ground = 0, first = 1 \u2192 the\n roof floor is `floor_number` **2**. The roof then covers the floors below it.\n (All the examples do this: the roof sits alone on the topmost floor.)\n- **You NEVER write a Z or height on the roof.** There is no Z field anywhere in\n the roof object. Its base sits automatically at the top of the walls below it,\n computed by the pipeline as:\n\n ```\n roof base Z = plinth.height + \u03A3 (height of every floor BELOW the roof\'s floor)\n ```\n\n using each floor\'s own `height` (falling back to `defaults.floor_height`).\n **`slab_thickness` does NOT enter this stack** \u2014 `height` already is the full\n floor-to-floor rise.\n- **To raise or lower the roof, change the FLOORS\' `height` (or `plinth.height`),\n not the roof.** If the roof floats too low/high, you put it on the wrong floor\n or a floor below it has the wrong `height`.\n- `slope` / `ridge_h` is the rise **above** that computed wall-top; segment\n `start`/`end` are 2D `[x, y]` only. Don\'t compute a Z \u2014 the pipeline does it\n entirely from the floor stack.\n\n## The core idea\n\nA roof is a set of **segments**. Each segment is a line (`start`\u2192`end`) with a\n**`width`** measured *perpendicular* to it, and **the segment sits at the CENTRE\nof that width** (the roof extends `width/2` to each side of the line). What the\nline means depends on `roof_type`:\n\n- **pitched** \u2014 the segment **is the ridge**; two symmetric slopes rise from the\n eaves up to it. Ends are covered per `default_endpoint`:\n - `"closed"` \u2192 **hip** (a sloped triangular hip face at the end).\n - `"open"` \u2192 **gable** (a vertical gable-end wall triangle).\n- **shed** \u2014 the segment is the **high edge**; the roof slopes down perpendicular\n to the far side. `shed_high_side` names which side is high.\n- **flat** \u2014 the segment\'s width band is a flat slab extruded down.\n\nSegments can run in **any direction** (not just axis-aligned).\n\n**Centreline convention (`coord_convention: "center"`).** A segment\'s\n`start`/`end`/`width` are **wall centrelines**, exactly like a room\'s\n`x/y/width/length`. On expand, each segment grows to the **outer wall face** \u2014\nthe axis is extended by \xBD\xB7`wall_thickness` at each end and `width` grows by\n`wall_thickness` \u2014 then `overhang` extends beyond that. So author segments on the\n**same centreline grid as the walls below** (e.g. `width House.W`, axis endpoints\non grid lines); do **not** hand-add \xBD-wall offsets, or the roof lands half a wall\nthickness inside the walls. (In the default `"outer"` convention the coordinates\nare literal outer-face positions and no grow happens.)\n\n## Object shape\n\n```jsonc\n{\n "type": "roof",\n "roof_type": "pitched" | "shed" | "flat",\n "default_endpoint": "closed" | "open", // pitched only: closed=hip, open=gable\n "min_overhang": 25, // > 0 \u2014 eave overhang beyond the walls\n "slope": { "by": "height", "ridge_h": 50 }, // rise above wall top \u2026\n // or { "by": "angle", "angle_deg": 30 }, // \u2026 or a pitch angle\n "segments": [\n {\n "id": "seg0",\n "start": [x, y], "end": [x, y], // the ridge (pitched) / high edge (shed)\n "width": 200, // perpendicular span, segment centred in it\n "slope": { \u2026 }, // optional per-segment slope override\n "shed_high_side": "\u2026", // shed only\n "hip_setback_start": \u2026, "hip_setback_end": \u2026 // optional, tune closed hip faces\n }\n ],\n "trusses": [\n { "segment_id": "seg0", "type": "fink", "positions_along": [100, 210, 320] }\n // type: "fink" (pitched) | "mono_pitch" (shed)\n // positions_along: distances along the segment where a truss sits\n ],\n "framing": { \u2026 } // optional overrides \u2014 omit unless asked\n}\n```\n\n`slope` may be given once at the roof level (applies to all segments) or per\nsegment. `by: "height"` \u2192 `ridge_h` is the ridge rise above the wall top; `by:\n"angle"` \u2192 `angle_deg` is the pitch.\n\n## Joints (multi-segment shapes)\n\nWhen two segments share an **exactly coincident endpoint**, the pipeline\nauto-resolves that end as a **joint** (no hip/gable face there) \u2014 this is how L,\nU, and closed-loop (courtyard) roofs are formed. Get the endpoints numerically\nequal or they won\'t join.\n\n## Shape \u2192 which example to copy\n\n| Want | Segments | Copy from |\n|---|---|---|\n| Simple hip (one ridge, hipped ends) | 1, `pitched`, `closed` | `two_story_konkan.json` / `verandah_cottage.json` |\n| Simple gable (one ridge, gable ends) | 1, `pitched`, `open` | same, set `default_endpoint: "open"` |\n| **L-shape** (two wings) | 2, `pitched`, coincident inner endpoints | `l_shape_villa.json` |\n| **Courtyard** (ridge loop) | 4, `pitched`, endpoints chained in a loop | `courtyard_home.json` |\n| **Flat** roof/terrace | 1+, `flat` | `modern_flat.json` |\n| Shed / mono-pitch (lean-to, verandah) | 1, `shed`, `shed_high_side` set | derive from a `pitched` seg; add `shed_high_side` + slope |\n\n## Rules & pitfalls\n\n- The roof **footprint must cover the plinth footprint** \u2014 segment lines +\n widths should span the walls they sit on. Mirror the example\'s relationship to\n its plinth.\n- `min_overhang` must be **> 0**.\n- Every segment needs an `id`; every truss references a real `segment_id`.\n- Shed segments **require** a slope (`ridge_h` or `angle_deg`) and a\n `shed_high_side`, or derivation throws.\n- After any roof edit: run `validate.mjs` \u2014 a zero-length segment, missing slope,\n or non-covering footprint fails there, not in the schema.\n'
|
|
417197
417215
|
},
|
|
417198
417216
|
"data-model": {
|
|
417199
417217
|
"title": "The underlying .wadi schema (generated from Zod)",
|