wadi-mcp 0.1.9 → 0.1.11

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.
Files changed (2) hide show
  1. package/dist/server.mjs +40 -12
  2. package/package.json +1 -1
package/dist/server.mjs CHANGED
@@ -411972,6 +411972,14 @@ function anchorItem(rect, spec, wallT, units) {
411972
411972
  else y = (iy0 + iy1) / 2 + gy;
411973
411973
  return { x, y };
411974
411974
  }
411975
+ function anchorFacing(anchor) {
411976
+ const { h, v } = parseAnchor(anchor);
411977
+ if (v === "top") return 0;
411978
+ if (v === "bottom") return 180;
411979
+ if (h === "left") return 90;
411980
+ if (h === "right") return 270;
411981
+ return 0;
411982
+ }
411975
411983
 
411976
411984
  // ../editor/src/svg2d/expand.ts
411977
411985
  var SIDES = ["north", "south", "east", "west"];
@@ -412059,13 +412067,14 @@ function expandRoomWalls(houseConfig, wallThickness, opts, _depth = 0) {
412059
412067
  }
412060
412068
  const anchoredItem = (rect, specIn) => {
412061
412069
  const spec = resolveItemFormulas(specIn);
412070
+ const rotation = spec.rotation ?? anchorFacing(spec.anchor);
412062
412071
  const p = anchorItem(
412063
412072
  rect,
412064
412073
  {
412065
412074
  anchor: spec.anchor,
412066
412075
  gapX: spec.gap_x,
412067
412076
  gapY: spec.gap_y,
412068
- rotation: spec.rotation,
412077
+ rotation,
412069
412078
  scale: spec.scale,
412070
412079
  dimensions: spec.asset.dimensions
412071
412080
  },
@@ -412078,7 +412087,7 @@ function expandRoomWalls(houseConfig, wallThickness, opts, _depth = 0) {
412078
412087
  asset: spec.asset,
412079
412088
  x: p.x,
412080
412089
  y: p.y,
412081
- rotation: spec.rotation ?? 0,
412090
+ rotation,
412082
412091
  scale: spec.scale,
412083
412092
  z_offset: spec.z_offset,
412084
412093
  layer: spec.layer
@@ -412121,6 +412130,7 @@ function expandRoomWalls(houseConfig, wallThickness, opts, _depth = 0) {
412121
412130
  const p = anchoredItem(rr, obj);
412122
412131
  obj.x = p.x;
412123
412132
  obj.y = p.y;
412133
+ obj.rotation = p.rotation;
412124
412134
  } else if (opts?.lenient) {
412125
412135
  opts.onWarning?.(`item '${obj.name ?? "?"}': anchor_to room '${at}' not found`);
412126
412136
  }
@@ -414648,7 +414658,20 @@ function svgDrawItem(cx, cy, w, l, yawDeg = 0) {
414648
414658
  const x = cx - w / 2;
414649
414659
  const y = cy - l / 2;
414650
414660
  const rot = yawDeg ? ` transform="rotate(${fFloat(yawDeg)} ${fFloat(cx)} ${fFloat(cy)})"` : "";
414651
- return `<rect${rot} x="${fFloat(x)}" y="${fFloat(y)}" width="${f(w)}" height="${f(l)}" rx="${f(Math.min(w, l) * 0.06)}" fill="#e8d8c0" fill-opacity="0.2" stroke="#a0826d" stroke-width="0.8"/>
414661
+ const rect = `<rect${rot} x="${fFloat(x)}" y="${fFloat(y)}" width="${f(w)}" height="${f(l)}" rx="${f(
414662
+ Math.min(w, l) * 0.06
414663
+ )}" fill="#e8d8c0" fill-opacity="0.2" stroke="#a0826d" stroke-width="0.8"/>`;
414664
+ const r2 = yawDeg * Math.PI / 180;
414665
+ const fx = Math.sin(r2), fy = Math.cos(r2);
414666
+ const px = -fy, py = fx;
414667
+ const ex = cx + fx * (l / 2), ey = cy + fy * (l / 2);
414668
+ const hw = Math.min(w * 0.35, Math.min(w, l) * 0.28);
414669
+ const hh = Math.min(w, l) * 0.22;
414670
+ const p1 = `${fFloat(ex + px * hw)},${fFloat(ey + py * hw)}`;
414671
+ const p2 = `${fFloat(ex - px * hw)},${fFloat(ey - py * hw)}`;
414672
+ const tip = `${fFloat(ex + fx * hh)},${fFloat(ey + fy * hh)}`;
414673
+ const front = `<polygon points="${p1} ${p2} ${tip}" fill="#a0826d" fill-opacity="0.9"/>`;
414674
+ return `${rect}${front}
414652
414675
  `;
414653
414676
  }
414654
414677
  function svgDrawBeam(x, y, width, length) {
@@ -417984,7 +418007,7 @@ house TwoRoom {
417984
418007
  `,
417985
418008
  "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',
417986
418009
  "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',
417987
- "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',
418010
+ "complete": '// Coverage showcase \u2014 every model entity as FIRST-CLASS syntax, no `raw`:\n// module imports, layers, a goal-tagged component library (definition + in-file\n// `use` + cross-file `use kb.Comp`), ground, plinth, slab, beam, room (with\n// openings + a pack item AND an inline item), a free-standing wall, a kitchen\n// platform, free furniture, a pillar, and a gable roof that is `enabled`-gated by\n// the configurator. If it compiles + validates, 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 // Module packs: furniture assets (item f."id") + Konkan house parts (use kb.Name).\n import "std-furniture" as f\n import "konkan/base" as kb\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 in-file component (with a discovery goal), authored in local coords.\n component Bench goal "a low bench to sit on" {\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 from the furniture pack\n // (`item f."id"`) 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 f."bed_double" 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. The inline `asset {\u2026}`\n // form still works for a one-off GLB not in any pack.\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, an in-file Bench (param overridden), and a part stamped\n // from the konkan/base pack (cross-file `use kb.Comp`).\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 use kb.Otla at (155, 420) with { wide = 90, deep = 55 } // entrance platform in the front yard\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',
417988
418011
  "konkan_cottage": '// konkan_cottage \u2014 a small single-storey Konkan house assembled from the\n// bundled MODULE packs, showing the whole import/reuse flow in one file:\n// \u2022 `import "std-furniture" as f` \u2192 drop GLB furniture with `item f."id"`\n// \u2022 `import "konkan/base" as kb` \u2192 stamp goal-tagged parts with `use kb.Name`\n// The two hand-built rooms (Hall, Bedroom) are furnished; everything else\n// (kitchen, bathroom, verandah, otla, tulsi vrindavan) comes from the pack.\nhouse KonkanCottage {\n convention center\n units feet_inches per_unit 10\n\n site { plot (420, 440) ref (0, 0) }\n defaults { floor_height 116 wall_height 108 slab_thickness 8 wall_thickness 8 }\n\n import "std-furniture" as f // furniture pack (assets \u2192 item f."id")\n import "konkan/base" as kb // house-parts pack (components \u2192 use kb.Name)\n\n // One habitable floor sitting straight on grade (no slab \u21D2 slab_thickness 0, C3).\n floor 1 "Ground" slab_thickness 0 {\n\n // --- hand-built, furnished rooms ---\n room Hall at (40, 40) size (180, 150) {\n wall north east west\n wall south { door HallDoor at 80 size (36, 84) }\n item f."sofa" anchor center\n }\n room Bedroom at (240, 40) size (140, 150) {\n wall north south west\n wall east { window BedWin at 55 size (55, 55) sill 35 }\n item f."bed_double" anchor center\n }\n\n // --- parts from konkan/base ---\n use kb.Kitchen at (40, 210) // "cooking area with an L-shaped counter"\n use kb.Bathroom at (210, 210) with { wide = 70, deep = 60 } // "compact enclosed wet area"\n use kb.Verandah at (40, 350) with { across = 200, deep = 70 } // "shaded sit-out along the front"\n use kb.Otla at (260, 360) // "raised entrance platform (otla)"\n use kb.TulsiVrindavan at (360, 370) // "courtyard planter for tulsi"\n }\n}\n'
417989
418012
  };
417990
418013
  var DOCS = {
@@ -417994,7 +418017,7 @@ var DOCS = {
417994
418017
  },
417995
418018
  "dsl": {
417996
418019
  "title": "The Wadi DSL (.wdl) \u2014 syntax reference",
417997
- "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\n// three ways to name the GLB, in order of preference:\nitem [name "N"] f."sofa" // 1. from an imported module (see Imports)\nitem [name "N"] "sofa" // 2. a same-file / bare-imported `asset` id\nitem [name "N"] asset { id "sofa" src "\u2026/sofa.glb" dims (w,h,d) [category "\u2026"] } // 3. inline one-off\n at (x,y) [rotation <deg>] [scale <s>]\n [anchor_to "RoomName" anchor center gap (gx,gy)]\n```\n\nPrefer the module form (`item f."bed_double"`) \u2014 `import "std-furniture" as f`\nonce and every piece is a short id, no URLs. The bare form (`item "sofa"`) needs\na matching top-level `asset "sofa" \u2026` in the file (or a bare `import`). The inline\n`asset { \u2026 }` block is only for a one-off GLB not in any pack. All three produce\nthe identical `{id,src,dims}` downstream. Furniture `dims` are the real-world size\nin **metres** `(width, height, depth)`; `src` is a GLB URL (an unreachable GLB\nshows a placeholder box, never a blank). `anchor` is one of `top-left top-center\ntop-right center-left center center-right bottom-left bottom-center bottom-right`.\n\n## Imports & modules (reusable `.wdl` libraries)\n\nA `.wdl` file can be a **module** \u2014 top-level declarations (no `house` needed) \u2014\nthat another file `import`s. Two bundled ones: `std-furniture` (asset pack \u2192\n`item ns."id"`) and `konkan/base` (goal-tagged component pack \u2192 `use ns.Comp`;\nStairwell, Verandah, Otla, Bathroom, Kitchen, TulsiVrindavan, Parapet). The\n`konkan_cottage` example (`wadi_examples`) assembles a whole house from both.\n\n```wdl\nhouse Home {\n import "std-furniture" as f // aliased: refer to its assets as f."<id>"\n // import "std-furniture" // bare: its ids drop into scope for item "<id>"\n floor 1 "G" slab_thickness 0 {\n room Bed at (20,20) size (160,200) { wall north east south west\n item f."bed_double" anchor center }\n }\n}\n```\n\nA module file itself is just top-level `asset` (later: `component`) decls:\n\n```wdl\n// my-furniture.wdl \u2014 a house-less module (a reusable library)\nasset "daybed" src "https://\u2026/daybed.glb" dims (1.8, 0.4, 0.9) name "Daybed" category "Living"\n```\n\nOver MCP, `wadi_modules` lists importable modules and `wadi_module "<name>"`\nshows a module\'s asset ids + dimensions (filter with a `query`). Import refs\nresolve by name against the bundled `std-*` packs (a local `modules/` search\npath and git refs come later).\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>] // uniform eave, all four sides\n [overhang_start <o>] [overhang_end <o>] // per-side along the axis (shed;\n // on a gable end = gable_overhang)\n [overhang_low <o>] [overhang_high <o>] // SHED eaves (down-slope / up-slope)\n [overhang_left <o>] [overhang_right <o>] // PITCHED eaves (left / right of ridge)\n [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**Per-side overhang (cantilever one edge).** `overhang <o>` sets a uniform eave on\nall four sides. Any sloping roof can override a side independently \u2014 each defaults to\n`overhang`. **Along the axis:** `overhang_start` / `overhang_end` (on a shed, or a\ngable open end \u2014 there they\'re the same as `gable_overhang`; a hip end is geometric,\ntuned via `hip_setback`). **Eaves:** `overhang_low` / `overhang_high` on a **shed**\n(down-slope / up-slope); `overhang_left` / `overhang_right` on a **pitched** roof\n(the two eaves either side of the ridge). A bigger eave overhang also drops that\neave\'s edge along the same pitch, so the slope stays planar. (Per-eave on a *pitched*\nroof is single-segment only \u2014 on a multi-segment roof the eaves share one height so\njoints line up.)\nIdiom: keep the roof FOOTPRINT (its supported edges) on the main room, then cantilever\none eave to cover an entry landing / stair \u2014 end the axis on the room wall and set a big\n`overhang_end`:\n```wdl\n// footprint ends on the main room\'s east wall (x204 centreline \u2192 x208 outer);\n// the east eave reaches 258, covering a landing that sticks out to x256.\nsegment "seg0" from (4,124) to (204,124) width 240 high_side right overhang 25 overhang_end 50\n```\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\nA component may carry a **`goal`** \u2014 a short description of what it accomplishes,\nthe discovery key for module lookup (`wadi_module` / a `wadi_modules` query):\n\n```wdl\ncomponent Stairwell goal "climb to the next floor" {\n param rise = 116\n staircase name "Stair" at (0,0) step (7,11,44) direction south total_height rise\n}\n```\n\nComponents can also come from an **imported module** (see *Imports & modules*),\nstamped with a namespaced `use ns.Comp`:\n\n```wdl\nhouse Home {\n import "konkan/base" as kb // Stairwell, Verandah, Otla (goal-tagged)\n floor 1 "G" slab_thickness 0 {\n room Hall at (20,20) size (200,200) { wall north east south west }\n use kb.Stairwell at (60,60) with { rise = 116 } // param args use `=`, not `:`\n }\n}\n```\n\n`use ns.Comp` expands byte-identical to an inline `component`; keep module\ncomponents flat (they don\'t `use` other components). Un-overridden `param`s fall\nback to their declared defaults.\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'
418020
+ "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\n// three ways to name the GLB, in order of preference:\nitem [name "N"] f."sofa" // 1. from an imported module (see Imports)\nitem [name "N"] "sofa" // 2. a same-file / bare-imported `asset` id\nitem [name "N"] asset { id "sofa" src "\u2026/sofa.glb" dims (w,h,d) [category "\u2026"] } // 3. inline one-off\n at (x,y) [rotation <deg>] [scale <s>]\n [anchor_to "RoomName" anchor center gap (gx,gy)]\n```\n\nPrefer the module form (`item f."bed_double"`) \u2014 `import "std-furniture" as f`\nonce and every piece is a short id, no URLs. The bare form (`item "sofa"`) needs\na matching top-level `asset "sofa" \u2026` in the file (or a bare `import`). The inline\n`asset { \u2026 }` block is only for a one-off GLB not in any pack. All three produce\nthe identical `{id,src,dims}` downstream. Furniture `dims` are the real-world size\nin **metres** `(width, height, depth)`; `src` is a GLB URL (an unreachable GLB\nshows a placeholder box, never a blank). `anchor` is one of `top-left top-center\ntop-right center-left center center-right bottom-left bottom-center bottom-right`.\n\n**Orientation \u2014 this is how you point furniture the right way.** A piece\'s FRONT\n(the side you sit at / the doors / the open side) faces a known compass direction\nper its `rotation` (degrees):\n\n| `rotation` | front faces |\n|---|---|\n| `0` | **South** (the plot front / entrance side, +Y) |\n| `90` | East |\n| `180` | North |\n| `270` | West |\n\nSo a sofa against the NORTH wall (facing into the room, i.e. south) is `rotation\n0`; against the SOUTH wall (facing north) it\'s `rotation 180`; against the WEST\nwall (facing east) `rotation 90`. The floor plan (`wadi_preview plans`) draws a\nsmall triangle on each piece\'s front edge so you can verify the way it points;\nfor a definitive 3D check use `wadi_capture_3d({ room: "\u2026" })` (first-person from\ninside the room).\n\n**Anchoring auto-orients.** When you `anchor` a piece to a wall and DON\'T give a\n`rotation`, it automatically faces away from that wall, into the room \u2014 `anchor\ntop-center` \u2192 faces south, `bottom-center` \u2192 north, `center-left` \u2192 east,\n`center-right` \u2192 west (a corner uses its north/south edge). So `item f."bed_double"\nanchor top-center` needs no rotation. An explicit `rotation` always overrides,\nand the derived value is written into the resolved model, so the plan notch and\nthe 3D view show it \u2014 anchoring never changes facing silently.\n\n## Imports & modules (reusable `.wdl` libraries)\n\nA `.wdl` file can be a **module** \u2014 top-level declarations (no `house` needed) \u2014\nthat another file `import`s. Two bundled ones: `std-furniture` (asset pack \u2192\n`item ns."id"`) and `konkan/base` (goal-tagged component pack \u2192 `use ns.Comp`;\nStairwell, Verandah, Otla, Bathroom, Kitchen, TulsiVrindavan, Parapet). The\n`konkan_cottage` example (`wadi_examples`) assembles a whole house from both.\n\n```wdl\nhouse Home {\n import "std-furniture" as f // aliased: refer to its assets as f."<id>"\n // import "std-furniture" // bare: its ids drop into scope for item "<id>"\n floor 1 "G" slab_thickness 0 {\n room Bed at (20,20) size (160,200) { wall north east south west\n item f."bed_double" anchor center }\n }\n}\n```\n\nA module file itself is just top-level `asset` (later: `component`) decls:\n\n```wdl\n// my-furniture.wdl \u2014 a house-less module (a reusable library)\nasset "daybed" src "https://\u2026/daybed.glb" dims (1.8, 0.4, 0.9) name "Daybed" category "Living"\n```\n\nOver MCP, `wadi_modules` lists importable modules and `wadi_module "<name>"`\nshows a module\'s asset ids + dimensions (filter with a `query`). Import refs\nresolve by name against the bundled `std-*` packs (a local `modules/` search\npath and git refs come later).\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>] // uniform eave, all four sides\n [overhang_start <o>] [overhang_end <o>] // per-side along the axis (shed;\n // on a gable end = gable_overhang)\n [overhang_low <o>] [overhang_high <o>] // SHED eaves (down-slope / up-slope)\n [overhang_left <o>] [overhang_right <o>] // PITCHED eaves (left / right of ridge)\n [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**Per-side overhang (cantilever one edge).** `overhang <o>` sets a uniform eave on\nall four sides. Any sloping roof can override a side independently \u2014 each defaults to\n`overhang`. **Along the axis:** `overhang_start` / `overhang_end` (on a shed, or a\ngable open end \u2014 there they\'re the same as `gable_overhang`; a hip end is geometric,\ntuned via `hip_setback`). **Eaves:** `overhang_low` / `overhang_high` on a **shed**\n(down-slope / up-slope); `overhang_left` / `overhang_right` on a **pitched** roof\n(the two eaves either side of the ridge). A bigger eave overhang also drops that\neave\'s edge along the same pitch, so the slope stays planar. (Per-eave on a *pitched*\nroof is single-segment only \u2014 on a multi-segment roof the eaves share one height so\njoints line up.)\nIdiom: keep the roof FOOTPRINT (its supported edges) on the main room, then cantilever\none eave to cover an entry landing / stair \u2014 end the axis on the room wall and set a big\n`overhang_end`:\n```wdl\n// footprint ends on the main room\'s east wall (x204 centreline \u2192 x208 outer);\n// the east eave reaches 258, covering a landing that sticks out to x256.\nsegment "seg0" from (4,124) to (204,124) width 240 high_side right overhang 25 overhang_end 50\n```\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\nA component may carry a **`goal`** \u2014 a short description of what it accomplishes,\nthe discovery key for module lookup (`wadi_module` / a `wadi_modules` query):\n\n```wdl\ncomponent Stairwell goal "climb to the next floor" {\n param rise = 116\n staircase name "Stair" at (0,0) step (7,11,44) direction south total_height rise\n}\n```\n\nComponents can also come from an **imported module** (see *Imports & modules*),\nstamped with a namespaced `use ns.Comp`:\n\n```wdl\nhouse Home {\n import "konkan/base" as kb // Stairwell, Verandah, Otla (goal-tagged)\n floor 1 "G" slab_thickness 0 {\n room Hall at (20,20) size (200,200) { wall north east south west }\n use kb.Stairwell at (60,60) with { rise = 116 } // param args use `=`, not `:`\n }\n}\n```\n\n`use ns.Comp` expands byte-identical to an inline `component`; keep module\ncomponents flat (they don\'t `use` other components). Un-overridden `param`s fall\nback to their declared defaults.\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'
417998
418021
  },
417999
418022
  "conventions": {
418000
418023
  "title": "Structural coding conventions (C1/C2/C3\u2026)",
@@ -418113,11 +418136,11 @@ async function appLoad(config3) {
418113
418136
  });
418114
418137
  if (!r2.ok) throw new Error(`app /load returned ${r2.status}`);
418115
418138
  }
418116
- async function appCapture(config3) {
418139
+ async function appCapture(config3, view) {
418117
418140
  const r2 = await fetch(`${BASE}/capture`, {
418118
418141
  method: "POST",
418119
418142
  headers: { "content-type": "application/json" },
418120
- body: JSON.stringify({ config: config3 }),
418143
+ body: JSON.stringify({ config: config3, view }),
418121
418144
  signal: AbortSignal.timeout(25e3)
418122
418145
  });
418123
418146
  if (!r2.ok) throw new Error(`app /capture returned ${r2.status}`);
@@ -418229,13 +418252,14 @@ server.registerTool(
418229
418252
  "wadi_capture_3d",
418230
418253
  {
418231
418254
  title: "Capture a 3D image of a Wadi design",
418232
- description: "Render a Wadi DSL house in the RUNNING Wadi desktop app and return a real 3D PNG \u2014 the actual textured model, not a 2D drawing. Requires the Wadi app to be open. The PNG is BOTH returned inline AND written to a file whose absolute path is in the text (open it if your client can't show inline images). (Headless 2D plans/elevations/roof: wadi_preview.)",
418255
+ description: 'Render a Wadi DSL house in the RUNNING Wadi desktop app and return a real 3D PNG \u2014 the actual textured model, not a 2D drawing. Requires the Wadi app to be open. Pass `room` (a room name from the .wdl, e.g. "Bedroom") to place the camera INSIDE that room for a first-person look \u2014 the best way to check FURNITURE PLACEMENT and ORIENTATION, which a plan can\'t show. Omit `room` for the outside orbit view. The PNG is BOTH returned inline AND written to a file whose path is in the text. (Headless 2D plans/elevations/roof: wadi_preview.)',
418233
418256
  inputSchema: {
418234
418257
  wdl: external_exports.string().describe("The full .wdl source text."),
418258
+ room: external_exports.string().optional().describe("A room name (as written in the .wdl) to view from inside. Omit for the outside orbit view."),
418235
418259
  out_dir: external_exports.string().optional().describe("Absolute directory to save the PNG to. Default: the OS temp dir. The saved path is returned in the text.")
418236
418260
  }
418237
418261
  },
418238
- async ({ wdl, out_dir }) => {
418262
+ async ({ wdl, room: room3, out_dir }) => {
418239
418263
  let config3;
418240
418264
  try {
418241
418265
  config3 = compileConfig(wdl);
@@ -418244,11 +418268,12 @@ server.registerTool(
418244
418268
  }
418245
418269
  if (!await appReachable()) return { content: [{ type: "text", text: APP_NOT_RUNNING }] };
418246
418270
  try {
418247
- const img = await appCapture(config3);
418248
- const file2 = savePng(Buffer.from(img.data, "base64"), out_dir, "wadi_3d.png");
418271
+ const img = await appCapture(config3, room3 ? { room: room3 } : void 0);
418272
+ const file2 = savePng(Buffer.from(img.data, "base64"), out_dir, room3 ? `wadi_3d_${room3.replace(/\W+/g, "_")}.png` : "wadi_3d.png");
418273
+ const label = room3 ? `3D interior view of "${room3}" (from the live app)` : "3D view (from the live app)";
418249
418274
  return {
418250
418275
  content: [
418251
- { type: "text", text: `\u2014 3D view (from the live app) \u2014 saved to: ${file2}` },
418276
+ { type: "text", text: `\u2014 ${label} \u2014 saved to: ${file2}` },
418252
418277
  { type: "image", data: img.data, mimeType: img.mime }
418253
418278
  ]
418254
418279
  };
@@ -418414,6 +418439,9 @@ server.registerTool(
418414
418439
  }
418415
418440
  }
418416
418441
  lines.push(` e.g. \`item ns."${assets[0].id}" at (x, y)\` or in a room \`item ns."${assets[0].id}" anchor center\``);
418442
+ lines.push(
418443
+ ` Orientation: a piece's front faces SOUTH at rotation 0 (90=East, 180=North, 270=West). ANCHORING auto-orients \u2014 an anchored piece with no rotation faces into the room (anchor top-center\u2192south, bottom-center\u2192north, center-left\u2192east, center-right\u2192west); an explicit rotation overrides. wadi_preview plans draws a front-notch; wadi_capture_3d({room}) shows it from inside.`
418444
+ );
418417
418445
  }
418418
418446
  return { content: [{ type: "text", text: lines.join("\n") }] };
418419
418447
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wadi-mcp",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
4
4
  "description": "MCP server for the Wadi house designer — check, preview, and reference the Wadi DSL (.wdl) without the repo.",
5
5
  "type": "module",
6
6
  "license": "MIT",