wadi-mcp 0.1.0 → 0.1.2

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 +25 -2
  2. package/package.json +1 -1
package/dist/server.mjs CHANGED
@@ -413465,10 +413465,13 @@ function lintStructure(config3) {
413465
413465
  const findings = [];
413466
413466
  const rects = buildRoomRects(config3);
413467
413467
  const wallSegs = collectWallSegments(floors);
413468
+ let baseZ = 0;
413468
413469
  for (let fi = 0; fi < floors.length; fi++) {
413469
413470
  const fl = floors[fi];
413470
413471
  const objs = activeObjects2(fl);
413471
413472
  const fnum = num2(fl.floor_number);
413473
+ const floorBaseZ = baseZ;
413474
+ baseZ += fl.height != null ? num2(fl.height) : floorHeightDefault;
413472
413475
  const plinths = objs.filter((o) => o.type === "plinth");
413473
413476
  if (plinths.length) {
413474
413477
  const fh = fl.height;
@@ -413524,6 +413527,26 @@ function lintStructure(config3) {
413524
413527
  });
413525
413528
  }
413526
413529
  }
413530
+ for (const o of objs) {
413531
+ if (o.type !== "staircase") continue;
413532
+ const riser = num2(o.step_rise);
413533
+ if (riser <= 0) continue;
413534
+ const belowH = fi > 0 && floors[fi - 1].height != null ? num2(floors[fi - 1].height) : floorHeightDefault;
413535
+ const riseHeight = o.rise_height != null && num2(o.rise_height) > 0 ? num2(o.rise_height) : belowH;
413536
+ const totalRise = Math.max(1, Math.round(riseHeight / riser)) * riser;
413537
+ const slabT = fl.slab_thickness != null ? num2(fl.slab_thickness) : slabDefault;
413538
+ const topZ = o.z_offset != null ? num2(o.z_offset) : slabT;
413539
+ const bottomZ = floorBaseZ + (topZ - totalRise);
413540
+ if (bottomZ < -1) {
413541
+ findings.push({
413542
+ rule: "C5",
413543
+ level: "warn",
413544
+ floor: fnum,
413545
+ where: objLabel(o),
413546
+ message: `Staircase ${objLabel(o)} on ${floorLabel(fl)} descends to z=${Math.round(bottomZ)} \u2014 below the ground plane, so it draws in 2D plans but is buried (invisible) in 3D. Staircases are TOP-anchored: place them on the UPPER floor and they DESCEND to the floor below (\`direction\` is the descent). Move it up a floor or reduce total_height.`
413547
+ });
413548
+ }
413549
+ }
413527
413550
  for (const o of objs) {
413528
413551
  if (o.type !== "room") continue;
413529
413552
  const declared = declaredSides(o.walls);
@@ -417154,11 +417177,11 @@ var DOCS = {
417154
417177
  },
417155
417178
  "dsl": {
417156
417179
  "title": "The Wadi DSL (.wdl) \u2014 syntax reference",
417157
- "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; `size (w,l)` is width \xD7 length. All accept the\ncommon tail.\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## 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\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- **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'
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'
417158
417181
  },
417159
417182
  "conventions": {
417160
417183
  "title": "Structural coding conventions (C1/C2/C3\u2026)",
417161
- "body": "# Wadi structural conventions (coding guidelines)\n\nA house can be **well-formed but structurally unsound**: it passes the schema and\nthe wall/roof geometry check, yet the building would not stand up \u2014 a floor floats\nin mid-air, a room is open to the weather, walls hover above a phantom slab. These\nare the *coding conventions* every Wadi house must follow.\n\nThey are **formally defined here** and **enforced in code** by the structural\nlinter (`editor/src/lint/structural.ts`), which runs automatically:\n\n- in **`check.sh`** (and `validate.mjs`) \u2014 **errors fail** the check, **warnings**\n are printed but advisory;\n- in the **DSL editor** \u2014 the status pill shows the count and lists every finding\n in its hover tooltip, while still rendering the model so you can *see* the\n unsound part.\n\nEach finding carries its convention id (`C1`, `C2`, \u2026) so this document and the\nlinter stay in lockstep. Add a rule by adding a check to `lintStructure` **and** a\nsection here with the next id.\n\n---\n\n## The vertical model (why C1 and C3 exist)\n\nFloors stack in source order (floor 0 = the Plinth floor). The renderer places\nthem like this (`editor/src/three/coords.ts`):\n\n- **A floor's base elevation = the running sum of the previous floors' `height`\n only.** `wall_height` and `slab_thickness` do **not** raise the next floor.\n- The **plinth block** is drawn to its *own* `height`. So the floor above sits at\n `plinth-floor.height`, while the plinth top is at `plinth.height` \u2014 they must be\n equal or the floor above floats/sinks by the difference. \u2192 **C1**\n- **`slab_thickness` lifts a floor's walls within its band** (`wallZ = base +\n slab_thickness`) \u2014 it is the deck the walls stand on. With no slab object there\n is no deck, so the walls float by that amount. \u2192 **C3**\n\n`height`, `wall_height`, and `slab_thickness` are otherwise **independent** \u2014 the\nmodel enforces no relationship between them. These conventions add the few\nrelationships that structural soundness *does* require.\n\n---\n\n## C1 \u2014 The plinth floor's height must match the plinth block height \xB7 **error**\n\n**Statement.** A floor that carries a `plinth` object (the Plinth floor) must set\nan explicit `height`, and that height must equal the plinth block's `height`.\n\n**Rationale.** The floor above is stacked at `plinth-floor.height`; the plinth\nblock rises to `plinth.height`. If they differ, the floor above floats above the\nplinth (`floor.height > plinth.height`) or sinks into it (`<`). If the floor\n`height` is omitted it silently defaults to `100`, almost never the plinth height.\n\n**Fix.**\n\n```wdl\nfloor 0 \"Plinth\" height 40 { // == the plinth block height below\n ground name \"Ground\" at (0,0) size (500,500)\n plinth name \"Plinth\" at (\u2026) size (\u2026) height 40\n}\n```\n\n(If the plinth block omits its own `height`, it follows the floor height and is\nconsistent by construction \u2014 but set the floor `height` explicitly anyway, so the\nstack is not left to the default.)\n\n---\n\n## C2 \u2014 A room must wall every exterior side \xB7 **warning**\n\n**Statement.** A room shown with a **partial** `walls` list must still wall every\nside that faces **outside** (no room beyond it). Interior (shared) sides may be\nomitted \u2014 the neighbour's wall stands on the shared centreline.\n\n**Rationale.** A room shows exactly the walls it declares; a **bare room (no\n`wall` lines) is enclosed on all four sides**. But the moment you add a `wall`\nline to hang a door or window, the room switches to a *whitelist* \u2014 every side you\ndon't list is now a hole. An exterior hole leaves the room open to the weather.\nThis is the #1 footgun the conventions guard against.\n\nIt is a **warning**, not an error, because an open exterior side is sometimes\nintentional (a verandah / open padvi). If it is deliberate, leave it \u2014 the warning\ndocuments the choice. Otherwise, add the wall.\n\n**Fix.** List the plain exterior walls compactly alongside the opening walls:\n\n```wdl\nroom Living at (x,y) size (w,l) {\n wall east west // plain exterior sides \u2014 enclosed\n wall south { door Main at 120 size (36,84) }\n wall north { window N1 at 100 size (60,50) sill 35 }\n}\n```\n\n*(The check samples several points along each side, so a side sheltered by rooms\nabove is correctly treated as interior, not open. It also skips a side that\nalready has a wall on its line \u2014 one declared by an adjacent or overlapping room,\nor a standalone wall \u2014 so a shared exterior wall the neighbour declares is not\ndouble-flagged.)*\n\n---\n\n## C3 \u2014 A floor with no slab must set slab_thickness to 0 \xB7 **error**\n\n**Statement.** A floor that has wall/room objects but **no `floor_slab` object**\nmust set `slab_thickness 0`.\n\n**Rationale.** `slab_thickness` is the deck the floor's walls stand on\n(`wallZ = base + slab_thickness`). Its default is `8`. With no slab object there is\nno deck, so every wall on the floor floats `slab_thickness` units above the floor\nbase. Setting it to `0` puts the walls on the floor base; alternatively, model the\ndeck by adding a `slab`.\n\n**Fix.**\n\n```wdl\nfloor 1 \"Ground\" slab_thickness 0 { // no slab modelled \u2192 walls sit on the base\n room Studio at (\u2026) size (\u2026) { \u2026 }\n}\n```\n\n*(This does not fire on a floor that carries no walls/rooms \u2014 e.g. a Plinth floor\nof just `ground` + `plinth`, or a roof-only top floor \u2014 where `slab_thickness` is\nharmless.)*\n\n---\n\n## C4 \u2014 A stacked floor's height should equal wall_height + slab_thickness \xB7 **warning**\n\n**Statement.** A floor that carries a floor above it (and has walls/rooms) should set\n`height` = `wall_height` + `slab_thickness`.\n\n**Rationale.** The next floor sits at `base + height`; this floor's walls stand on the\ndeck and reach `base + slab_thickness + wall_height`. When `height` is larger, the floor\nabove leaves a gap over the walls; when smaller, the walls poke through it. (These three\nfields are otherwise independent \u2014 see the vertical model above.) It is a **warning** \u2014 a\ndeliberate gap is legitimate (a service plenum, a deep transfer beam) \u2014 but usually they\nshould match.\n\n**Fix.** Make them add up, most simply via the house defaults:\n\n```wdl\ndefaults { floor_height 116 wall_height 108 slab_thickness 8 } // 108 + 8 = 116\n```\n\n*(Skipped for the plinth floor \u2014 governed by C1 \u2014 and for the topmost floor, since nothing\nstacks on its walls.)*\n\n## Running the checks\n\n```bash\nwadi-skill/architect/scripts/check.sh house.wdl\n```\n\n- **`\u2716 [C\u2026]`** \u2014 a structural **error**; the check exits non-zero. Fix before you\n save/share.\n- **`\u26A0 [C\u2026]`** \u2014 a structural **warning**; advisory. Fix, or keep it if the open\n side is intentional.\n\nIn the DSL editor the same findings appear in the status pill (hover for the full\nlist); the model still renders so you can see the problem.\n\n---\n\n## Planned conventions (not yet enforced)\n\nDocumented so authors know they matter; not linted yet:\n\n- **Interior partition gaps** \u2014 where two rooms share a centreline and *neither*\n declares that wall, there is no partition between them. (C2 only covers\n *exterior* sides.)\n- **Slab thickness \u2194 slab object** \u2014 when a floor *does* carry a `floor_slab`,\n its `slab_thickness` should match the slab's own thickness so walls sit on the\n real deck.\n- **Roof footprint coverage** \u2014 the roof segments should span the top occupied\n floor's footprint (no uncovered rooms).\n"
417184
+ "body": '# Wadi structural conventions (coding guidelines)\n\nA house can be **well-formed but structurally unsound**: it passes the schema and\nthe wall/roof geometry check, yet the building would not stand up \u2014 a floor floats\nin mid-air, a room is open to the weather, walls hover above a phantom slab. These\nare the *coding conventions* every Wadi house must follow.\n\nThey are **formally defined here** and **enforced in code** by the structural\nlinter (`editor/src/lint/structural.ts`), which runs automatically:\n\n- in **`check.sh`** (and `validate.mjs`) \u2014 **errors fail** the check, **warnings**\n are printed but advisory;\n- in the **DSL editor** \u2014 the status pill shows the count and lists every finding\n in its hover tooltip, while still rendering the model so you can *see* the\n unsound part.\n\nEach finding carries its convention id (`C1`, `C2`, \u2026) so this document and the\nlinter stay in lockstep. Add a rule by adding a check to `lintStructure` **and** a\nsection here with the next id.\n\n---\n\n## The vertical model (why C1 and C3 exist)\n\nFloors stack in source order (floor 0 = the Plinth floor). The renderer places\nthem like this (`editor/src/three/coords.ts`):\n\n- **A floor\'s base elevation = the running sum of the previous floors\' `height`\n only.** `wall_height` and `slab_thickness` do **not** raise the next floor.\n- The **plinth block** is drawn to its *own* `height`. So the floor above sits at\n `plinth-floor.height`, while the plinth top is at `plinth.height` \u2014 they must be\n equal or the floor above floats/sinks by the difference. \u2192 **C1**\n- **`slab_thickness` lifts a floor\'s walls within its band** (`wallZ = base +\n slab_thickness`) \u2014 it is the deck the walls stand on. With no slab object there\n is no deck, so the walls float by that amount. \u2192 **C3**\n\n`height`, `wall_height`, and `slab_thickness` are otherwise **independent** \u2014 the\nmodel enforces no relationship between them. These conventions add the few\nrelationships that structural soundness *does* require.\n\n---\n\n## C1 \u2014 The plinth floor\'s height must match the plinth block height \xB7 **error**\n\n**Statement.** A floor that carries a `plinth` object (the Plinth floor) must set\nan explicit `height`, and that height must equal the plinth block\'s `height`.\n\n**Rationale.** The floor above is stacked at `plinth-floor.height`; the plinth\nblock rises to `plinth.height`. If they differ, the floor above floats above the\nplinth (`floor.height > plinth.height`) or sinks into it (`<`). If the floor\n`height` is omitted it silently defaults to `100`, almost never the plinth height.\n\n**Fix.**\n\n```wdl\nfloor 0 "Plinth" height 40 { // == the plinth block height below\n ground name "Ground" at (0,0) size (500,500)\n plinth name "Plinth" at (\u2026) size (\u2026) height 40\n}\n```\n\n(If the plinth block omits its own `height`, it follows the floor height and is\nconsistent by construction \u2014 but set the floor `height` explicitly anyway, so the\nstack is not left to the default.)\n\n---\n\n## C2 \u2014 A room must wall every exterior side \xB7 **warning**\n\n**Statement.** A room shown with a **partial** `walls` list must still wall every\nside that faces **outside** (no room beyond it). Interior (shared) sides may be\nomitted \u2014 the neighbour\'s wall stands on the shared centreline.\n\n**Rationale.** A room shows exactly the walls it declares; a **bare room (no\n`wall` lines) is enclosed on all four sides**. But the moment you add a `wall`\nline to hang a door or window, the room switches to a *whitelist* \u2014 every side you\ndon\'t list is now a hole. An exterior hole leaves the room open to the weather.\nThis is the #1 footgun the conventions guard against.\n\nIt is a **warning**, not an error, because an open exterior side is sometimes\nintentional (a verandah / open padvi). If it is deliberate, leave it \u2014 the warning\ndocuments the choice. Otherwise, add the wall.\n\n**Fix.** List the plain exterior walls compactly alongside the opening walls:\n\n```wdl\nroom Living at (x,y) size (w,l) {\n wall east west // plain exterior sides \u2014 enclosed\n wall south { door Main at 120 size (36,84) }\n wall north { window N1 at 100 size (60,50) sill 35 }\n}\n```\n\n*(The check samples several points along each side, so a side sheltered by rooms\nabove is correctly treated as interior, not open. It also skips a side that\nalready has a wall on its line \u2014 one declared by an adjacent or overlapping room,\nor a standalone wall \u2014 so a shared exterior wall the neighbour declares is not\ndouble-flagged.)*\n\n---\n\n## C3 \u2014 A floor with no slab must set slab_thickness to 0 \xB7 **error**\n\n**Statement.** A floor that has wall/room objects but **no `floor_slab` object**\nmust set `slab_thickness 0`.\n\n**Rationale.** `slab_thickness` is the deck the floor\'s walls stand on\n(`wallZ = base + slab_thickness`). Its default is `8`. With no slab object there is\nno deck, so every wall on the floor floats `slab_thickness` units above the floor\nbase. Setting it to `0` puts the walls on the floor base; alternatively, model the\ndeck by adding a `slab`.\n\n**Fix.**\n\n```wdl\nfloor 1 "Ground" slab_thickness 0 { // no slab modelled \u2192 walls sit on the base\n room Studio at (\u2026) size (\u2026) { \u2026 }\n}\n```\n\n*(This does not fire on a floor that carries no walls/rooms \u2014 e.g. a Plinth floor\nof just `ground` + `plinth`, or a roof-only top floor \u2014 where `slab_thickness` is\nharmless.)*\n\n---\n\n## C4 \u2014 A stacked floor\'s height should equal wall_height + slab_thickness \xB7 **warning**\n\n**Statement.** A floor that carries a floor above it (and has walls/rooms) should set\n`height` = `wall_height` + `slab_thickness`.\n\n**Rationale.** The next floor sits at `base + height`; this floor\'s walls stand on the\ndeck and reach `base + slab_thickness + wall_height`. When `height` is larger, the floor\nabove leaves a gap over the walls; when smaller, the walls poke through it. (These three\nfields are otherwise independent \u2014 see the vertical model above.) It is a **warning** \u2014 a\ndeliberate gap is legitimate (a service plenum, a deep transfer beam) \u2014 but usually they\nshould match.\n\n**Fix.** Make them add up, most simply via the house defaults:\n\n```wdl\ndefaults { floor_height 116 wall_height 108 slab_thickness 8 } // 108 + 8 = 116\n```\n\n*(Skipped for the plinth floor \u2014 governed by C1 \u2014 and for the topmost floor, since nothing\nstacks on its walls.)*\n\n## C5 \u2014 A staircase must land on a floor, not below ground \xB7 **warning**\n\n**Statement.** A staircase\'s descent must not carry it below the ground plane (z < 0).\n\n**Rationale.** Staircases are **top-anchored**: you place one on the **upper** floor and it\n**descends** to the floor below (`at` is the top, `direction` is the descent, `total_height`\nis the drop). Put it on the *lower* floor (as if climbing up), or give it too large a\n`total_height`, and the expanded flight lands **below ground** \u2014 it still draws in the 2D\nplans (which ignore Z) but is **buried and invisible in 3D**, with no other error. This is\nthe one that bit a real design: a stair on the ground floor "climbing" to the first floor\nwas expanded to `z_offset: -105` and vanished from the 3D view.\n\n**Fix.** Move the staircase **up one floor** and let it descend. The stair that connects the\nground floor to the first floor lives on the **First Floor**:\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) // `at` = the TOP (this floor)\n direction south total_height 116 // descends to the floor below\n}\n```\n\n*(See the staircase section of `dsl.md` for the full top-anchored convention.)*\n\n## Running the checks\n\n```bash\nwadi-skill/architect/scripts/check.sh house.wdl\n```\n\n- **`\u2716 [C\u2026]`** \u2014 a structural **error**; the check exits non-zero. Fix before you\n save/share.\n- **`\u26A0 [C\u2026]`** \u2014 a structural **warning**; advisory. Fix, or keep it if the open\n side is intentional.\n\nIn the DSL editor the same findings appear in the status pill (hover for the full\nlist); the model still renders so you can see the problem.\n\n---\n\n## Planned conventions (not yet enforced)\n\nDocumented so authors know they matter; not linted yet:\n\n- **Interior partition gaps** \u2014 where two rooms share a centreline and *neither*\n declares that wall, there is no partition between them. (C2 only covers\n *exterior* sides.)\n- **Slab thickness \u2194 slab object** \u2014 when a floor *does* carry a `floor_slab`,\n its `slab_thickness` should match the slab\'s own thickness so walls sit on the\n real deck.\n- **Roof footprint coverage** \u2014 the roof segments should span the top occupied\n floor\'s footprint (no uncovered rooms).\n'
417162
417185
  },
417163
417186
  "coordinate-system": {
417164
417187
  "title": "Coordinates, units & the centreline convention",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wadi-mcp",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
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",