wadi-mcp 0.1.25 → 0.1.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/server.mjs +103 -63
- package/package.json +1 -1
package/dist/server.mjs
CHANGED
|
@@ -29567,13 +29567,20 @@ var init_houseConfig = __esm({
|
|
|
29567
29567
|
enabled: enabledField.optional(),
|
|
29568
29568
|
layer: external_exports2.string().optional(),
|
|
29569
29569
|
name: external_exports2.string().optional(),
|
|
29570
|
-
//
|
|
29571
|
-
//
|
|
29572
|
-
//
|
|
29573
|
-
//
|
|
29574
|
-
//
|
|
29575
|
-
//
|
|
29576
|
-
//
|
|
29570
|
+
// `climb` picks which end (start_x, start_y) is and which way the flight runs
|
|
29571
|
+
// in z as it extends into `direction`:
|
|
29572
|
+
// • "up" (recommended): BOTTOM-anchored. Put the stair on the LOWER floor it
|
|
29573
|
+
// rises FROM; (start_x, start_y) is the bottom step's near corner on that
|
|
29574
|
+
// floor and the flight ASCENDS into `direction`. `rise_height` defaults to
|
|
29575
|
+
// THIS floor's height (climb to the next level). The intuitive way.
|
|
29576
|
+
// • "down" (DEFAULT, kept for older configs): TOP-anchored. Put the stair on
|
|
29577
|
+
// the upper DESTINATION floor; (start_x, start_y) is the top connection and
|
|
29578
|
+
// the flight DESCENDS into `direction`. `rise_height` defaults to the floor
|
|
29579
|
+
// immediately BELOW this one.
|
|
29580
|
+
// Either way the body + landings fill the box [start, start + max_run] along
|
|
29581
|
+
// `direction`, and `z_offset` is the ANCHORED end's height above the floor base
|
|
29582
|
+
// (omitted → this floor's slab thickness, flush with the walking surface).
|
|
29583
|
+
climb: external_exports2.enum(["up", "down"]).optional(),
|
|
29577
29584
|
start_x: external_exports2.number(),
|
|
29578
29585
|
start_y: external_exports2.number(),
|
|
29579
29586
|
// Total height the stair covers, top → floor below. The step COUNT is
|
|
@@ -370112,21 +370119,24 @@ function stripMulti(sc) {
|
|
|
370112
370119
|
turn: _tn,
|
|
370113
370120
|
flight_gap: _fg,
|
|
370114
370121
|
rise_height: _rh,
|
|
370122
|
+
climb: _cl,
|
|
370115
370123
|
...rest
|
|
370116
370124
|
} = sc;
|
|
370117
370125
|
return rest;
|
|
370118
370126
|
}
|
|
370119
|
-
function expandStaircase(sc, slabThickness, floorBelowHeight) {
|
|
370127
|
+
function expandStaircase(sc, slabThickness, floorBelowHeight, floorOwnHeight) {
|
|
370128
|
+
const up = (sc.climb ?? "down") === "up";
|
|
370120
370129
|
const tread = n(sc.step_tread);
|
|
370121
370130
|
const riser = n(sc.step_rise);
|
|
370122
370131
|
const width = n(sc.step_width);
|
|
370123
370132
|
const direction = sc.direction ?? "south";
|
|
370124
|
-
const
|
|
370133
|
+
const defaultRise = up ? floorOwnHeight : floorBelowHeight;
|
|
370134
|
+
const riseHeight = typeof sc.rise_height === "number" && sc.rise_height > 0 ? sc.rise_height : defaultRise;
|
|
370125
370135
|
const totalSteps = Math.max(1, Math.round(riseHeight / riser));
|
|
370126
370136
|
const totalRise = totalSteps * riser;
|
|
370127
370137
|
const maxRun = typeof sc.max_run === "number" ? sc.max_run : 0;
|
|
370128
|
-
const
|
|
370129
|
-
const
|
|
370138
|
+
const anchorZ = sc.z_offset !== void 0 ? n(sc.z_offset) : slabThickness;
|
|
370139
|
+
const bottomZ = up ? anchorZ : anchorZ - totalRise;
|
|
370130
370140
|
const landingDepth = typeof sc.landing_depth === "number" && sc.landing_depth > 0 ? sc.landing_depth : width;
|
|
370131
370141
|
const landingThickness = typeof sc.landing_thickness === "number" ? sc.landing_thickness : riser;
|
|
370132
370142
|
const latSign = sc.turn === "anticlockwise" ? 1 : -1;
|
|
@@ -370148,72 +370158,66 @@ function expandStaircase(sc, slabThickness, floorBelowHeight) {
|
|
|
370148
370158
|
const treads = Math.max(1, totalSteps - 1);
|
|
370149
370159
|
const run = treads * tread;
|
|
370150
370160
|
const o = stripMulti(sc);
|
|
370151
|
-
o.direction = OPP[direction];
|
|
370152
|
-
o.start_x = n(sc.start_x) + run * dvx;
|
|
370153
|
-
o.start_y = n(sc.start_y) + run * dvy;
|
|
370154
370161
|
o.num_steps = treads;
|
|
370155
|
-
|
|
370162
|
+
if (up) {
|
|
370163
|
+
o.direction = direction;
|
|
370164
|
+
o.start_x = n(sc.start_x);
|
|
370165
|
+
o.start_y = n(sc.start_y);
|
|
370166
|
+
o.z_offset = bottomZ;
|
|
370167
|
+
} else {
|
|
370168
|
+
o.direction = OPP[direction];
|
|
370169
|
+
o.start_x = n(sc.start_x) + run * dvx;
|
|
370170
|
+
o.start_y = n(sc.start_y) + run * dvy;
|
|
370171
|
+
o.z_offset = bottomZ;
|
|
370172
|
+
}
|
|
370156
370173
|
return [o];
|
|
370157
370174
|
}
|
|
370158
370175
|
const perFlight = Math.ceil(totalSteps / numFlights);
|
|
370159
|
-
const
|
|
370176
|
+
const risersFor = (t) => Math.max(0, Math.min(perFlight, totalSteps - t * perFlight));
|
|
370160
370177
|
const flightRun = Math.max(1, perFlight - 1) * tread;
|
|
370161
370178
|
const items = [];
|
|
370162
|
-
|
|
370179
|
+
const stepFields = { step_rise: riser, step_tread: tread, step_width: width };
|
|
370180
|
+
let zCur = 0;
|
|
370163
370181
|
for (let t = 0; t < numFlights; t++) {
|
|
370164
|
-
const risers =
|
|
370182
|
+
const risers = risersFor(t);
|
|
370165
370183
|
if (risers <= 0) break;
|
|
370166
370184
|
const treads = Math.max(1, risers - 1);
|
|
370167
370185
|
const even = t % 2 === 0;
|
|
370168
370186
|
const lane = even ? 0 : laneOffset;
|
|
370169
|
-
const zBottom = zTop - risers * riser;
|
|
370170
370187
|
const run = treads * tread;
|
|
370171
|
-
|
|
370172
|
-
|
|
370173
|
-
|
|
370174
|
-
|
|
370175
|
-
|
|
370176
|
-
direction: "north",
|
|
370177
|
-
|
|
370178
|
-
|
|
370179
|
-
|
|
370180
|
-
|
|
370181
|
-
|
|
370182
|
-
|
|
370183
|
-
z_offset: zBottom
|
|
370184
|
-
} : {
|
|
370185
|
-
// top at FAR end (y=flightRun), descends −Y → renders as "south"
|
|
370186
|
-
type: "staircase",
|
|
370187
|
-
direction: "south",
|
|
370188
|
-
start_x: lane,
|
|
370189
|
-
start_y: flightRun - run,
|
|
370190
|
-
num_steps: treads,
|
|
370191
|
-
step_rise: riser,
|
|
370192
|
-
step_tread: tread,
|
|
370193
|
-
step_width: width,
|
|
370194
|
-
z_offset: zBottom
|
|
370188
|
+
if (up) {
|
|
370189
|
+
const zBottomFlight = zCur;
|
|
370190
|
+
const zTopFlight = zCur + risers * riser;
|
|
370191
|
+
items.push({
|
|
370192
|
+
isStair: true,
|
|
370193
|
+
o: even ? { type: "staircase", direction: "south", start_x: lane, start_y: 0, num_steps: treads, ...stepFields, z_offset: zBottomFlight } : { type: "staircase", direction: "north", start_x: lane, start_y: flightRun, num_steps: treads, ...stepFields, z_offset: zBottomFlight }
|
|
370194
|
+
});
|
|
370195
|
+
if (t < numFlights - 1) {
|
|
370196
|
+
items.push({
|
|
370197
|
+
isStair: false,
|
|
370198
|
+
o: { type: "floor_slab", x: landingX, y: even ? flightRun : 0, width: landingWidth, length: landingDepth, thickness: landingThickness, z_offset: zTopFlight - landingThickness }
|
|
370199
|
+
});
|
|
370195
370200
|
}
|
|
370196
|
-
|
|
370197
|
-
|
|
370201
|
+
zCur = zTopFlight;
|
|
370202
|
+
} else {
|
|
370203
|
+
const zBottom = zCur - risers * riser;
|
|
370198
370204
|
items.push({
|
|
370199
|
-
isStair:
|
|
370200
|
-
o: {
|
|
370201
|
-
type: "floor_slab",
|
|
370202
|
-
x: landingX,
|
|
370203
|
-
y: even ? flightRun : 0,
|
|
370204
|
-
width: landingWidth,
|
|
370205
|
-
length: landingDepth,
|
|
370206
|
-
thickness: landingThickness,
|
|
370207
|
-
z_offset: zBottom - landingThickness
|
|
370208
|
-
// top flush with the platform
|
|
370209
|
-
}
|
|
370205
|
+
isStair: true,
|
|
370206
|
+
o: even ? { type: "staircase", direction: "north", start_x: lane, start_y: run, num_steps: treads, ...stepFields, z_offset: zBottom } : { type: "staircase", direction: "south", start_x: lane, start_y: flightRun - run, num_steps: treads, ...stepFields, z_offset: zBottom }
|
|
370210
370207
|
});
|
|
370208
|
+
if (t < numFlights - 1) {
|
|
370209
|
+
items.push({
|
|
370210
|
+
isStair: false,
|
|
370211
|
+
o: { type: "floor_slab", x: landingX, y: even ? flightRun : 0, width: landingWidth, length: landingDepth, thickness: landingThickness, z_offset: zBottom - landingThickness }
|
|
370212
|
+
});
|
|
370213
|
+
}
|
|
370214
|
+
zCur = zBottom;
|
|
370211
370215
|
}
|
|
370212
|
-
zTop = zBottom;
|
|
370213
370216
|
}
|
|
370214
370217
|
const rotated = items.map((it) => ({ ...it, o: rotateObject(it.o, direction) }));
|
|
370215
370218
|
const dx = n(sc.start_x);
|
|
370216
370219
|
const dy = n(sc.start_y);
|
|
370220
|
+
const liftZ = anchorZ;
|
|
370217
370221
|
const baseName = sc.name ?? "Stair";
|
|
370218
370222
|
let fi = 0;
|
|
370219
370223
|
let li = 0;
|
|
@@ -370222,7 +370226,7 @@ function expandStaircase(sc, slabThickness, floorBelowHeight) {
|
|
|
370222
370226
|
if (typeof o.y === "number") o.y += dy;
|
|
370223
370227
|
if (typeof o.start_x === "number") o.start_x += dx;
|
|
370224
370228
|
if (typeof o.start_y === "number") o.start_y += dy;
|
|
370225
|
-
o.z_offset = n(o.z_offset) +
|
|
370229
|
+
o.z_offset = n(o.z_offset) + liftZ;
|
|
370226
370230
|
if (sc.layer !== void 0) o.layer = sc.layer;
|
|
370227
370231
|
if (isStair && sc.material !== void 0) o.material = sc.material;
|
|
370228
370232
|
o.name = isStair ? `${baseName}_F${++fi}` : `${baseName}_L${++li}`;
|
|
@@ -370434,6 +370438,7 @@ function expandRoomWalls(houseConfig, wallThickness, opts, _depth = 0) {
|
|
|
370434
370438
|
const floorSlabThickness = floor2.slab_thickness ?? houseDefaults2?.slab_thickness ?? DEFAULT_GLOBAL_CONFIG.floor_slab_thickness;
|
|
370435
370439
|
const belowHeightRaw = fi > 0 ? floorList[fi - 1].height : void 0;
|
|
370436
370440
|
const floorBelowHeight = typeof belowHeightRaw === "number" && belowHeightRaw > 0 ? belowHeightRaw : houseDefaults2?.floor_height ?? DEFAULT_GLOBAL_CONFIG.floor_height;
|
|
370441
|
+
const floorOwnHeight = typeof floor2.height === "number" && floor2.height > 0 ? floor2.height : houseDefaults2?.floor_height ?? DEFAULT_GLOBAL_CONFIG.floor_height;
|
|
370437
370442
|
const units = hc.units;
|
|
370438
370443
|
const roomRects = /* @__PURE__ */ new Map();
|
|
370439
370444
|
for (const o of objs) {
|
|
@@ -370522,7 +370527,7 @@ function expandRoomWalls(houseConfig, wallThickness, opts, _depth = 0) {
|
|
|
370522
370527
|
if (obj.type === "staircase") {
|
|
370523
370528
|
let flights;
|
|
370524
370529
|
try {
|
|
370525
|
-
flights = expandStaircase(obj, floorSlabThickness, floorBelowHeight);
|
|
370530
|
+
flights = expandStaircase(obj, floorSlabThickness, floorBelowHeight, floorOwnHeight);
|
|
370526
370531
|
} catch (e) {
|
|
370527
370532
|
if (!opts?.lenient) throw e;
|
|
370528
370533
|
opts.onWarning?.(e instanceof Error ? e.message : String(e));
|
|
@@ -405123,6 +405128,7 @@ var SpiralStaircase = {
|
|
|
405123
405128
|
};
|
|
405124
405129
|
var Staircase = {
|
|
405125
405130
|
$type: "Staircase",
|
|
405131
|
+
climb: "climb",
|
|
405126
405132
|
direction: "direction",
|
|
405127
405133
|
enabled: "enabled",
|
|
405128
405134
|
flight_gap: "flight_gap",
|
|
@@ -406803,6 +406809,10 @@ var WadiAstReflection = class extends AbstractAstReflection {
|
|
|
406803
406809
|
Staircase: {
|
|
406804
406810
|
name: Staircase.$type,
|
|
406805
406811
|
properties: {
|
|
406812
|
+
climb: {
|
|
406813
|
+
name: Staircase.climb,
|
|
406814
|
+
optional: true
|
|
406815
|
+
},
|
|
406806
406816
|
direction: {
|
|
406807
406817
|
name: Staircase.direction
|
|
406808
406818
|
},
|
|
@@ -413092,6 +413102,34 @@ var WadiGrammar = () => loadedWadiGrammar ?? (loadedWadiGrammar = loadGrammarFro
|
|
|
413092
413102
|
"arguments": []
|
|
413093
413103
|
}
|
|
413094
413104
|
},
|
|
413105
|
+
{
|
|
413106
|
+
"$type": "Group",
|
|
413107
|
+
"elements": [
|
|
413108
|
+
{
|
|
413109
|
+
"$type": "Keyword",
|
|
413110
|
+
"value": "climb"
|
|
413111
|
+
},
|
|
413112
|
+
{
|
|
413113
|
+
"$type": "Assignment",
|
|
413114
|
+
"feature": "climb",
|
|
413115
|
+
"operator": "=",
|
|
413116
|
+
"terminal": {
|
|
413117
|
+
"$type": "Alternatives",
|
|
413118
|
+
"elements": [
|
|
413119
|
+
{
|
|
413120
|
+
"$type": "Keyword",
|
|
413121
|
+
"value": "up"
|
|
413122
|
+
},
|
|
413123
|
+
{
|
|
413124
|
+
"$type": "Keyword",
|
|
413125
|
+
"value": "down"
|
|
413126
|
+
}
|
|
413127
|
+
]
|
|
413128
|
+
}
|
|
413129
|
+
}
|
|
413130
|
+
],
|
|
413131
|
+
"cardinality": "?"
|
|
413132
|
+
},
|
|
413095
413133
|
{
|
|
413096
413134
|
"$type": "Group",
|
|
413097
413135
|
"elements": [
|
|
@@ -416282,6 +416320,7 @@ function staircase(s) {
|
|
|
416282
416320
|
direction: s.direction
|
|
416283
416321
|
};
|
|
416284
416322
|
if (s.name) o.name = unquote2(s.name);
|
|
416323
|
+
if (s.climb) o.climb = s.climb;
|
|
416285
416324
|
const rh = put("rise_height", s.rise_height, 1);
|
|
416286
416325
|
if (rh !== void 0) o.rise_height = rh;
|
|
416287
416326
|
const mr = put("max_run", s.max_run, 1);
|
|
@@ -419053,6 +419092,7 @@ function lintStructure(config3) {
|
|
|
419053
419092
|
}
|
|
419054
419093
|
for (const o of objs) {
|
|
419055
419094
|
if (o.type !== "staircase") continue;
|
|
419095
|
+
if ((o.climb ?? "down") === "up") continue;
|
|
419056
419096
|
const riser = num3(o.step_rise);
|
|
419057
419097
|
if (riser <= 0) continue;
|
|
419058
419098
|
const belowH = fi > 0 && floors[fi - 1].height != null ? num3(floors[fi - 1].height) : floorHeightDefault;
|
|
@@ -422716,7 +422756,7 @@ house TwoRoom {
|
|
|
422716
422756
|
}
|
|
422717
422757
|
}
|
|
422718
422758
|
`,
|
|
422719
|
-
"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
|
|
422759
|
+
"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 // Bottom-anchored: it sits on the floor you climb FROM and ascends to the next\n // level (`climb up`). `total_height` defaults to this floor\'s height.\n staircase name "Stair" at (300, 60) step (7, 10, 36) direction south climb up\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 }\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',
|
|
422720
422760
|
"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',
|
|
422721
422761
|
"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',
|
|
422722
422762
|
"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'
|
|
@@ -422728,11 +422768,11 @@ var DOCS = {
|
|
|
422728
422768
|
},
|
|
422729
422769
|
"dsl": {
|
|
422730
422770
|
"title": "The Wadi DSL (.wdl) \u2014 syntax reference",
|
|
422731
|
-
"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 owner-facing template you author\n title "Configure your home" // panel heading (optional)\n note "Everything re-flows to fit." // panel subtitle (optional)\n\n slider pillarW "Column size" ft [8 .. 14 step 1] note "help text" // trailing note optional\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 select floorH "Ceiling height" { "9 ft" = 90, "10 ft (std)" = 100 } // labels can be quoted strings\n\n group "Plot" note "about this section" { // sections the panel; label only\n slider W "Plot width" ft [340 .. 520 step 10]\n }\n}\n```\n\nEvery knob binds to a `var` by name (`target`). To expose a plot dimension, model\nit as a `var` and reference it from the point (`point House { x = W }`), then bind\nthe knob to `W` \u2014 knobs target vars, never a point field like `House.W`.\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>] // symmetric pitch (one value)\n [slope angle (<left>, <right>)] // asymmetric (saltbox) gable \u2014 angle pair\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.)\n\n**Asymmetric gable (saltbox) \u2014 an angle pair.** A pitched roof\'s `slope` gives both\nfaces the same pitch (a symmetric gable). To make the two sides different, give\n`slope angle` a **pair** instead of one value \u2014 `slope angle (45, 25)`. A single value\nis symmetric; a pair is asymmetric, and that\'s the whole distinction (no separate\nkeywords, nothing half-settable). The two eaves stay put (footprint unchanged) and the\nridge shifts across the width so each face takes its angle; the gable-end triangle and\ntrusses follow. The pair is `(left, right)` by the segment\'s left normal \u2014 the **same\nsides as** `overhang_left`/`overhang_right`. Angles are measured to the wall-top eave\nline. `height` is single-value only (both faces share one ridge line). Intended for a\nsingle-segment gable (`endpoint open`); on a hip end the ridge shift skews the hip.\n\n```wdl\nroof pitched endpoint open slope angle (45, 25) {\n segment "s0" from (House.W/2, 0) to (House.W/2, House.L) width House.W\n}\n```\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) [rotation <deg>] with { blen = 80 } // stamp 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`. Components **nest\nfreely**: a library component may `use` a sibling, `use` a component from a\nlibrary it itself `import`s, and place `item ns."id"` furniture from its own\nimports \u2014 imports resolve **transitively** (cycles are a compile error).\nUn-overridden `param`s fall back to their declared defaults.\n\n`rotation <deg>` (optional, yaw\xB0: 0=south, 90=east) turns the whole stamped\nassembly about its origin. **Right angles (0/90/180/270) are exact for any\ncomponent** (rooms/pillars/beams/slabs swap dims + remap wall sides; furniture\nturns with them). A **non-right angle** is allowed **only for a furniture-only\ncomponent** (items/free walls rotate to any angle); a free angle on a component\nthat contains a room/pillar/beam/slab/staircase is a compile error (arbitrary\nstructural rotation is a future feature).\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'
|
|
422771
|
+
"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 owner-facing template you author\n title "Configure your home" // panel heading (optional)\n note "Everything re-flows to fit." // panel subtitle (optional)\n\n slider pillarW "Column size" ft [8 .. 14 step 1] note "help text" // trailing note optional\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 select floorH "Ceiling height" { "9 ft" = 90, "10 ft (std)" = 100 } // labels can be quoted strings\n\n group "Plot" note "about this section" { // sections the panel; label only\n slider W "Plot width" ft [340 .. 520 step 10]\n }\n}\n```\n\nEvery knob binds to a `var` by name (`target`). To expose a plot dimension, model\nit as a `var` and reference it from the point (`point House { x = W }`), then bind\nthe knob to `W` \u2014 knobs target vars, never a point field like `House.W`.\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 [climb up|down]\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**`climb` picks the anchor + z direction.** Prefer **`climb up`** \u2014 the intuitive way:\n\n- Put the stair on the **LOWER** floor it rises FROM. `at (x,y)` is the **bottom** step\'s\n near corner on that floor; `direction` is the **ascent** direction; the flight climbs UP.\n- `total_height` is the **rise** to the next level (omit \u2192 this floor\'s own 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\n```wdl\nfloor 1 "Ground Floor" height 116 {\n slab at (\u2026) size (\u2026)\n staircase name "Stair" at (212, 64) step (7, 11, 44) // bottom = this floor\n direction south climb up // ascends south to the floor above\n}\n```\n\n`climb down` is the legacy mode (**DEFAULT** for older configs): put the stair on the\n**UPPER** destination floor; `at (x,y)` is the **top** connection, `direction` is the\ndescent, and `total_height` defaults to the floor-below\'s height. New designs should use\n`climb up`.\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>] // symmetric pitch (one value)\n [slope angle (<left>, <right>)] // asymmetric (saltbox) gable \u2014 angle pair\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.)\n\n**Asymmetric gable (saltbox) \u2014 an angle pair.** A pitched roof\'s `slope` gives both\nfaces the same pitch (a symmetric gable). To make the two sides different, give\n`slope angle` a **pair** instead of one value \u2014 `slope angle (45, 25)`. A single value\nis symmetric; a pair is asymmetric, and that\'s the whole distinction (no separate\nkeywords, nothing half-settable). The two eaves stay put (footprint unchanged) and the\nridge shifts across the width so each face takes its angle; the gable-end triangle and\ntrusses follow. The pair is `(left, right)` by the segment\'s left normal \u2014 the **same\nsides as** `overhang_left`/`overhang_right`. Angles are measured to the wall-top eave\nline. `height` is single-value only (both faces share one ridge line). Intended for a\nsingle-segment gable (`endpoint open`); on a hip end the ridge shift skews the hip.\n\n```wdl\nroof pitched endpoint open slope angle (45, 25) {\n segment "s0" from (House.W/2, 0) to (House.W/2, House.L) width House.W\n}\n```\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) [rotation <deg>] with { blen = 80 } // stamp 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`. Components **nest\nfreely**: a library component may `use` a sibling, `use` a component from a\nlibrary it itself `import`s, and place `item ns."id"` furniture from its own\nimports \u2014 imports resolve **transitively** (cycles are a compile error).\nUn-overridden `param`s fall back to their declared defaults.\n\n`rotation <deg>` (optional, yaw\xB0: 0=south, 90=east) turns the whole stamped\nassembly about its origin. **Right angles (0/90/180/270) are exact for any\ncomponent** (rooms/pillars/beams/slabs swap dims + remap wall sides; furniture\nturns with them). A **non-right angle** is allowed **only for a furniture-only\ncomponent** (items/free walls rotate to any angle); a free angle on a component\nthat contains a room/pillar/beam/slab/staircase is a compile error (arbitrary\nstructural rotation is a future feature).\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'
|
|
422732
422772
|
},
|
|
422733
422773
|
"conventions": {
|
|
422734
422774
|
"title": "Structural coding conventions (C1/C2/C3\u2026)",
|
|
422735
|
-
"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## C6 \u2014 Openings on the same wall must not overlap \xB7 **error**\n\n**Statement.** Two openings (doors/windows) cut into the **same physical wall** must not\noverlap along it. This includes openings that belong to **two different rooms sharing a\nboundary wall** \u2014 a door on Living's east side and a door on Bedroom's west side sit on the\nsame wall line and can collide.\n\n**Rationale.** Each opening is a boolean-subtract from the wall. Overlapping spans merge into\none ragged hole (or fight over the same brick), which is never what you meant \u2014 and on a\nshared wall it silently punches a bigger gap than either room's plan shows.\n\n**Fix.** Offset or narrow one opening so the spans are disjoint. Openings are measured from\nthe wall's start corner (`offset` = near edge; the opening occupies `[offset, offset+width]`).\nFor a shared wall, remember both rooms' offsets are measured along the **same** line, so a\ndoor at `offset 50 width 40` (\u2192 `[50,90]`) on one room clears a door at `offset 90` on the\nother, but not one at `offset 60`.\n\n## C7 \u2014 Furniture items should not overlap \xB7 **warning**\n\n**Statement.** Two furniture `item`s whose plan footprints overlap are flagged \u2014 as a\n**warning**, because it is sometimes intentional (a rug under a table, a lamp on a desk,\ndeliberately stacked pieces).\n\n**Rationale.** More often it's a placement slip \u2014 two beds dropped on the same spot, or an\nanchored piece that reflowed into another when a room was resized. The footprint used is the\nitem's rotated bounding box (yaw-aware), so it matches what the plan draws.\n\n**Fix.** Reposition one item, or ignore the warning if the overlap is deliberate.\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"
|
|
422775
|
+
"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.** Only a `climb down` (top-anchored) stair can fall below ground: you place it\non the **upper** floor and it **descends** (`at` is the top, `direction` is the descent,\n`total_height` is the drop). Put it on the wrong floor, 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. A\n`climb up` stair is anchored on its own floor and ascends, so it never trips this.\n\n**Fix.** Prefer **`climb up`**: put the stair on the **lower** floor it rises FROM and let\nit ascend. The stair connecting the ground floor to the first floor lives on the **Ground\nFloor**:\n\n```wdl\nfloor 1 \"Ground Floor\" height 116 {\n slab at (\u2026) size (\u2026)\n staircase name \"Stair\" at (212, 64) step (7, 11, 44) // `at` = the BOTTOM (this floor)\n direction south climb up // ascends to the floor above\n}\n```\n\n(Or, if you must keep it `climb down`, move it **up one floor** or reduce `total_height`.)\n\n*(See the staircase section of `dsl.md` for the full `climb` convention.)*\n\n## C6 \u2014 Openings on the same wall must not overlap \xB7 **error**\n\n**Statement.** Two openings (doors/windows) cut into the **same physical wall** must not\noverlap along it. This includes openings that belong to **two different rooms sharing a\nboundary wall** \u2014 a door on Living's east side and a door on Bedroom's west side sit on the\nsame wall line and can collide.\n\n**Rationale.** Each opening is a boolean-subtract from the wall. Overlapping spans merge into\none ragged hole (or fight over the same brick), which is never what you meant \u2014 and on a\nshared wall it silently punches a bigger gap than either room's plan shows.\n\n**Fix.** Offset or narrow one opening so the spans are disjoint. Openings are measured from\nthe wall's start corner (`offset` = near edge; the opening occupies `[offset, offset+width]`).\nFor a shared wall, remember both rooms' offsets are measured along the **same** line, so a\ndoor at `offset 50 width 40` (\u2192 `[50,90]`) on one room clears a door at `offset 90` on the\nother, but not one at `offset 60`.\n\n## C7 \u2014 Furniture items should not overlap \xB7 **warning**\n\n**Statement.** Two furniture `item`s whose plan footprints overlap are flagged \u2014 as a\n**warning**, because it is sometimes intentional (a rug under a table, a lamp on a desk,\ndeliberately stacked pieces).\n\n**Rationale.** More often it's a placement slip \u2014 two beds dropped on the same spot, or an\nanchored piece that reflowed into another when a room was resized. The footprint used is the\nitem's rotated bounding box (yaw-aware), so it matches what the plan draws.\n\n**Fix.** Reposition one item, or ignore the warning if the overlap is deliberate.\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"
|
|
422736
422776
|
},
|
|
422737
422777
|
"coordinate-system": {
|
|
422738
422778
|
"title": "Coordinates, units & the centreline convention",
|
|
@@ -422748,7 +422788,7 @@ var DOCS = {
|
|
|
422748
422788
|
},
|
|
422749
422789
|
"data-model": {
|
|
422750
422790
|
"title": "The underlying .wadi schema (generated from Zod)",
|
|
422751
|
-
"body": '# Wadi data model (`.wadi` / `house_config.json`)\n\n> **Generated from `editor/src/schema/houseConfig.ts` \u2014 do not edit by hand.**\n> Regenerate: `node scripts/gen-schema-doc.mjs <path/to/houseConfig.ts> reference/data-model.md`\n> Some primitives (beam, floor_slab, pillar, plinth, ground) are generated from their\n> `fields` (schema/fields/\\*) into generated/objects.generated.ts \u2014 run `npm run gen-primitives`\n> in editor/ first if you changed those, so the generated schemas (which this doc reads) are current.\n> The Zod schema is the single source of truth; this file mirrors it (structure + the\n> semantics carried in its comments) so it can\'t drift.\n\nA `.wadi` file is one JSON object matching **HouseConfig**. Geometry is in **project\nunits** (a unitless grid; by default `units.per_unit = 10` means 10 units = 1 ft).\nPlan coordinates are **Inkscape-style**: origin top-left, **X \u2192 right, Y \u2192 down**.\nSee `coordinate-system.md` for the coordinate/units detail and `parametric-conventions.md`\nfor variables/points/formulas.\n\n## Fields shared by (almost) every object\n\nThese appear on most object types; documented once here, marked *(cross-cutting)* below.\n\n| field | type | req | notes |\n|---|---|---|---|\n| `type` | literal `room` | **yes** | |\n| `formulas` | map: field name \u2192 `"= formula"` string | | |\n| `enabled` | boolean or number (`false`/`0` = hidden) | | |\n| `layer` | string | | |\n| `name` | string | **yes** | |\n| `material` | string | | |\n| `z_offset` | number | | Vertical position of the room (its floor + walls), as a lift above the FLOOR BASE (slabZ = plinth top for floor 0, else the floor below\'s top; project units, 10 = 1 ft). This is the UNIFIED z_offset convention: every object is placed at `slabZ + z_offset`. When OMITTED, on-slab objects (room, wall, staircase, kitchen_platform) default z_offset to the floor\'s resolved slab thickness (floor.slab_thickness \u2192 house.defaults.slab_thickness \u2192 code default), so by default they sit on top of the slab, exactly as before. Set it explicitly for split-level floors \u2014 e.g. a room raised onto a thicker slab uses the same value the raised slab\'s top sits at. |\n\n\n- `type` \u2014 the discriminated-union tag; selects the object shape (values below).\n- `formulas` \u2014 per-field `"= expression"` overrides; the resolver evaluates each into the\n matching numeric field. See `parametric-conventions.md`.\n- `z_offset` \u2014 vertical lift above the floor base (slab top). On-slab objects (room, wall,\n staircase, kitchen_platform) default it to the floor\'s slab thickness; slab/beam/pillar/\n roof default to 0.\n\n## Top level \u2014 HouseConfig\n\n| field | type | req | notes |\n|---|---|---|---|\n| `coord_convention` | enum: `outer` `center` | | How a rectangular object\'s x/y/width/length relate to its walls (plans/grid-convention.md). "center" (new/canonical): coordinates are wall CENTRELINES \u2014 adjacent rooms ABUT on a shared line (no overlap), walls are centred on the boundary, and expandRoomWalls grows each footprint by wall_thickness/2 to the outer face. "outer" / absent (legacy): coordinates are the OUTER wall face and adjacent rooms must overlap by wall_thickness. |\n| `plinth` | any (freeform) | | Legacy top-level plinth (pre-"Plinth floor"). Tolerated but IGNORED so an un-migrated file still loads (it just renders without a plinth/ground) instead of failing .strict() validation. New configs put the plinth on the Plinth floor as a `plinth` object. |\n| `defaults` | [houseDefaults](#housedefaults) | | |\n| `units` | [units](#units) | | |\n| `layers` | array of [LayerDef](#layerdef) | | Configurable 3D visibility layers (optional; defaults applied when absent). Objects opt in via their own `layer` field. |\n| `variables` | map: string \u2192 number, or `"= formula"` string | | Parametric layer (plans/object-relationships-plan.md). Named scalar variables (number or "= formula", may reference other variables) and named 2D points; object `formulas` maps reference these. Optional \u2014 absent = a plain non-parametric house, resolved as a no-op. |\n| `points` | map: string \u2192 inline object | | |\n| `components` | map: string \u2192 [ComponentDef](#componentdef) | | Reusable-component library (in-file). Map of id \u2192 ComponentDef. A `component` object instantiates one by `ref`. Stored once; referenced by many instances; edit here to update every instance. |\n| `grids` | map: string \u2192 `gridDef` | | First-class parametric grids (plans/grid-convention.md). Map of id \u2192 GridDef (named X/Y wall centrelines). Rooms/slabs bind via `grid`+`cell`, pillars via `grid`+`node`; the resolver derives their geometry from the centrelines + wall thickness. Optional; reusable across templates. |\n| `configurator` | [configurator](#configurator) | | Configurator metadata (Gharkul owner UI). Optional; see plans/configurator-plan.md. |\n| `thumbnails` | array of string | | Preview snapshots (data: URLs) captured by the architect editor and saved WITH the template so the owner gallery can show real previews \u2014 multiple angles + the floor plan. `thumbnails[0]` is the gallery cover. Optional; excluded from share links (a preview isn\'t model data \u2014 see io/shareLink.ts). `thumbnail` (singular) is the legacy one-image form, still read as a fallback so old template files keep working. |\n| `thumbnail` | string | | |\n| `floors` | array of [floor](#floor) | **yes** | |\n| `_walls_expanded` | boolean | | |\n\n\n## floor\n\n| field | type | req | notes |\n|---|---|---|---|\n| `floor_number` | integer \u2265 0 | **yes** | |\n| `name` | string | **yes** | |\n| `height` | number > 0 | | Per-floor overrides for the default heights in GlobalConfig. In project units (10 units = 1 ft). All three are INDEPENDENT \u2014 no relationship enforced between them: height \u2014 floor-to-floor rise (drives roof wallTop-Z stack) wall_height \u2014 standing wall height (floor top \u2192 ceiling) slab_thickness \u2014 RCC deck between this floor and the one above All fall back to GlobalConfig defaults when omitted. |\n| `wall_height` | number > 0 | | |\n| `slab_thickness` | number \u2265 0 | | |\n| `formulas` | map: field name \u2192 `"= formula"` string | | |\n| `enabled` | boolean or number (`false`/`0` = hidden) | | |\n| `objects` | array of [Object types](#object-types) | **yes** | |\n\n\n## Object types (`floors[].objects[]`)\n\nEvery entry in a floor\'s `objects` array is one of these, tagged by `type`:\n\n### `plinth`\n\nThe plinth is now a normal object placed on the "Plinth" floor (the first floor, number 0), not a top-level config key. Its footprint + height match the old top-level plinth; the plinth floor\'s `height` drives the rise to the floor above (replacing the old hardcoded plinth_height seed).\n\n| field | type | req | notes |\n|---|---|---|---|\n| `type` | literal `plinth` | **yes** | |\n| `formulas` | map: field name \u2192 `"= formula"` string | | *(shared \u2014 see top)* |\n| `enabled` | boolean or number (`false`/`0` = hidden) | | *(shared \u2014 see top)* |\n| `layer` | string | | *(shared \u2014 see top)* |\n| `name` | string | | Label |\n| `material` | string | | Material key |\n| `x` | number | **yes** | Top-left X (project units) |\n| `y` | number | **yes** | Top-left Y (project units) |\n| `width` | number > 0 | **yes** | X extent (project units) |\n| `length` | number > 0 | **yes** | Y extent (project units) |\n| `height` | number > 0 | **yes** | Plinth height (project units) |\n| `z_offset` | number | | Lift above ground (project units) |\n\n\n### `ground`\n\nThe ground plane, also on the Plinth floor. Extent defaults to the site plot when authored by the migration. `height` is an optional thickness (0 = a flat plane); slope fields are a later phase.\n\n| field | type | req | notes |\n|---|---|---|---|\n| `type` | literal `ground` | **yes** | |\n| `formulas` | map: field name \u2192 `"= formula"` string | | *(shared \u2014 see top)* |\n| `enabled` | boolean or number (`false`/`0` = hidden) | | *(shared \u2014 see top)* |\n| `layer` | string | | *(shared \u2014 see top)* |\n| `name` | string | | Label |\n| `material` | string | | Material key |\n| `x` | number | **yes** | Top-left X (project units) |\n| `y` | number | **yes** | Top-left Y (project units) |\n| `width` | number > 0 | **yes** | X extent (project units) |\n| `length` | number > 0 | **yes** | Y extent (project units) |\n| `height` | number \u2265 0 | | Thickness (0 = flat) (project units) |\n| `z_offset` | number | | Lift above origin (project units) |\n\n\n### `component`\n\nAn INSTANCE of a reusable component from the in-file `components` library. It references a component by id (`ref`), overrides the component\'s input variables via `params`, and places it at (x, y) with a `z_offset` lift on its parent floor. At render time `expandRoomWalls` flattens it into concrete objects (resolve component with param+origin overrides \u2192 recurse \u2192 offset), so no renderer needs to know about `component`.\n\n| field | type | req | notes |\n|---|---|---|---|\n| `type` | literal `component` | **yes** | |\n| `formulas` | map: field name \u2192 `"= formula"` string | | *(shared \u2014 see top)* |\n| `enabled` | boolean or number (`false`/`0` = hidden) | | *(shared \u2014 see top)* |\n| `layer` | string | | *(shared \u2014 see top)* |\n| `name` | string | | |\n| `ref` | string | **yes** | |\n| `params` | map: string \u2192 union \u2014 see notes | | Overrides for the component\'s declared input variables. A string starting with "=" is a formula evaluated in the HOST scope (so it can reference the host\'s variables/points); a number is used directly. |\n| `x` | number | **yes** | |\n| `y` | number | **yes** | |\n| `rotation` | number | | Standard placement: yaw\xB0 about the instance origin (clockwise, same sense as item rotation: 0=south, 90=east). Right angles (0/90/180/270) are exact for any component; a non-right angle is allowed only for furniture-only ones. |\n| `z_offset` | number | | |\n\n\n### `item`\n\nA free-standing GLB furniture / decor instance placed directly on a floor (for pieces that aren\'t inside an enclosed room \u2014 outdoor/site/verandah decor, a loft item, etc.). `x`/`y` are the item\'s plan CENTRE. It MAY instead anchor to a named room via `anchor_to` + `anchor` + `gap`, in which case `x`/`y` are DERIVED at expand time (same anchor model as room-nested items). `rotation` is yaw\xB0; `scale` is a uniform resize; `z_offset` lifts it above the floor base (default = slab thickness). (`itemAsset`, `itemAnchor`, `gapField` are defined above `room`.)\n\n| field | type | req | notes |\n|---|---|---|---|\n| `type` | literal `item` | **yes** | |\n| `formulas` | map: field name \u2192 `"= formula"` string | | *(shared \u2014 see top)* |\n| `enabled` | boolean or number (`false`/`0` = hidden) | | *(shared \u2014 see top)* |\n| `layer` | string | | *(shared \u2014 see top)* |\n| `name` | string | | |\n| `asset` | [ItemAsset](#itemasset) | **yes** | |\n| `x` | number | **yes** | |\n| `y` | number | **yes** | |\n| `rotation` | number | | |\n| `scale` | number > 0 | | |\n| `z_offset` | number | | |\n| `anchor_to` | string | | Optional room-relative anchoring (for a free item that should follow a room). |\n| `anchor` | [ItemAnchor](#itemanchor) | | |\n| `gap_x` | number | | |\n| `gap_y` | number | | |\n\n\n### `floor_slab`\n| field | type | req | notes |\n|---|---|---|---|\n| `type` | literal `floor_slab` | **yes** | |\n| `formulas` | map: field name \u2192 `"= formula"` string | | *(shared \u2014 see top)* |\n| `enabled` | boolean or number (`false`/`0` = hidden) | | *(shared \u2014 see top)* |\n| `layer` | string | | *(shared \u2014 see top)* |\n| `name` | string | | Label |\n| `x` | number | **yes** | Top-left X (project units) |\n| `y` | number | **yes** | Top-left Y (project units) |\n| `width` | number > 0 | **yes** | X extent (project units) |\n| `length` | number > 0 | **yes** | Y extent (project units) |\n| `thickness` | number \u2265 0 | | Slab thickness (defaults to floor\'s) (project units) |\n| `z_offset` | number | | Lift above floor base (project units) |\n\n\n### `pillar`\n| field | type | req | notes |\n|---|---|---|---|\n| `type` | literal `pillar` | **yes** | |\n| `formulas` | map: field name \u2192 `"= formula"` string | | *(shared \u2014 see top)* |\n| `enabled` | boolean or number (`false`/`0` = hidden) | | *(shared \u2014 see top)* |\n| `layer` | string | | *(shared \u2014 see top)* |\n| `name` | string | **yes** | Label |\n| `x` | number | **yes** | Top-left corner X (project units) |\n| `y` | number | **yes** | Top-left corner Y (project units) |\n| `width` | number > 0 | | X extent (project units) |\n| `length` | number > 0 | | Y extent (project units) |\n| `height` | number > 0 | **yes** | Column height (project units) |\n| `z_offset` | number | | Lift above floor base (project units) |\n\n\n### `beam`\n| field | type | req | notes |\n|---|---|---|---|\n| `type` | literal `beam` | **yes** | |\n| `formulas` | map: field name \u2192 `"= formula"` string | | *(shared \u2014 see top)* |\n| `enabled` | boolean or number (`false`/`0` = hidden) | | *(shared \u2014 see top)* |\n| `layer` | string | | *(shared \u2014 see top)* |\n| `name` | string | | Label |\n| `x` | number | **yes** | Top-left X (project units) |\n| `y` | number | **yes** | Top-left Y (project units) |\n| `width` | number > 0 | **yes** | X extent (project units) |\n| `length` | number > 0 | **yes** | Y extent (project units) |\n| `height` | number > 0 | | Vertical thickness (project units) |\n| `z_offset` | number | | Lift above floor base (project units) |\n\n\n### `room`\n| field | type | req | notes |\n|---|---|---|---|\n| `type` | literal `room` | **yes** | |\n| `formulas` | map: field name \u2192 `"= formula"` string | | *(shared \u2014 see top)* |\n| `enabled` | boolean or number (`false`/`0` = hidden) | | *(shared \u2014 see top)* |\n| `layer` | string | | *(shared \u2014 see top)* |\n| `name` | string | **yes** | |\n| `x` | number | **yes** | |\n| `y` | number | **yes** | |\n| `width` | number > 0 | **yes** | |\n| `length` | number > 0 | **yes** | |\n| `height` | number \u2265 0 | | 0 accepted \u2014 semantically the same as absent ("use floor default"). Old configs that accidentally saved height: 0 keep loading; the form treats 0 as "no override" and doesn\'t write it back. |\n| `material` | string | | |\n| `z_offset` | number | | Vertical position of the room (its floor + walls), as a lift above the FLOOR BASE (slabZ = plinth top for floor 0, else the floor below\'s top; project units, 10 = 1 ft). This is the UNIFIED z_offset convention: every object is placed at `slabZ + z_offset`. When OMITTED, on-slab objects (room, wall, staircase, kitchen_platform) default z_offset to the floor\'s resolved slab thickness (floor.slab_thickness \u2192 house.defaults.slab_thickness \u2192 code default), so by default they sit on top of the slab, exactly as before. Set it explicitly for split-level floors \u2014 e.g. a room raised onto a thicker slab uses the same value the raised slab\'s top sits at. |\n| `walls` | union \u2014 see notes | | |\n| `wall_heights` | map: string \u2192 [wall_heights entry](#wall-heights-entry) | | |\n| `items` | array of [RoomItem](#roomitem) | | Furniture nested in this room. Each piece is anchored to the room\'s inner footprint (see roomItem), so it reflows when the room resizes. Expanded into top-level `item` objects at render time. |\n\n\n### `wall`\n| field | type | req | notes |\n|---|---|---|---|\n| `type` | literal `wall` | **yes** | |\n| `formulas` | map: field name \u2192 `"= formula"` string | | *(shared \u2014 see top)* |\n| `enabled` | boolean or number (`false`/`0` = hidden) | | *(shared \u2014 see top)* |\n| `layer` | string | | *(shared \u2014 see top)* |\n| `name` | string | **yes** | |\n| `start_x` | number | **yes** | |\n| `start_y` | number | **yes** | |\n| `end_x` | number | **yes** | |\n| `end_y` | number | **yes** | |\n| `height` | number > 0 | | |\n| `height_end` | number | | |\n| `material` | string | | |\n| `facing` | enum: `north` `south` `east` `west` | | |\n| `z_offset` | number | | Lift above the FLOOR BASE (slabZ), project units. Omitted \u2192 defaults to the floor\'s resolved slab thickness (sits on the slab, as before). Set it for a split-level wall. Same convention as `room`. |\n| `openings` | array of [Opening](#opening) | | |\n\n\n### `staircase`\n| field | type | req | notes |\n|---|---|---|---|\n| `type` | literal `staircase` | **yes** | |\n| `formulas` | map: field name \u2192 `"= formula"` string | | *(shared \u2014 see top)* |\n| `enabled` | boolean or number (`false`/`0` = hidden) | | *(shared \u2014 see top)* |\n| `layer` | string | | *(shared \u2014 see top)* |\n| `name` | string | | |\n| `start_x` | number | **yes** | A staircase belongs to the DESTINATION (upper) floor it leads to \u2014 put it on that floor\'s `objects` so deleting the floor deletes the stair. It is TOP-anchored and DESCENDS: (start_x, start_y) is the top connection where it meets this floor, and the stair descends INTO `direction` from there (its body + landings fill the box [start, start + max_run] along `direction`). `z_offset` is the top\'s height above the floor base (omitted \u2192 this floor\'s slab thickness, flush with the walking surface). |\n| `start_y` | number | **yes** | |\n| `rise_height` | number > 0 | | Total height the stair covers, top \u2192 floor below. The step COUNT is derived: num_steps = round(rise_height / step_rise). Omitted \u2192 defaults to the height of the floor immediately below this one. Formula-capable (e.g. "= floor_height"). Replaces the old explicit `num_steps`. |\n| `step_rise` | number > 0 | **yes** | |\n| `step_tread` | number > 0 | **yes** | |\n| `step_width` | number > 0 | **yes** | |\n| `direction` | enum: `north` `south` `east` `west` | **yes** | The direction the stair EXTENDS from its top \u2014 the whole assembly fills the allocated box from (start_x,start_y) going this way for up to `max_run`. |\n| `max_run` | number > 0 | | ALLOCATED run: the length of space reserved for the stair along `direction`. The WHOLE assembly (flights + turn landings) is kept within [start, start+max_run]; when the run won\'t fit as one flight it auto-splits into switchback flights (more flights when tight), expanded in expandRoomWalls into plain staircases + floor_slab landings so every renderer is unchanged. Omit \u2192 one flight, no length limit. |\n| `landing_depth` | number > 0 | | Turn-landing depth (along the run). Omitted \u2192 equals step_width. |\n| `landing_thickness` | number \u2265 0 | | Turn-landing slab thickness. Omitted \u2192 equals step_rise. |\n| `turn` | enum: `clockwise` `anticlockwise` | | Switchback handedness, reckoned DESCENDING from the top. Omitted \u2192 "clockwise". Only affects split stairs. |\n| `flight_gap` | number > 0 | | Lateral gap between the two switchback flights (a stairwell void for a spine wall). Omitted/0 \u2192 flights are adjacent. The turn landings widen to bridge the gap. Only affects split stairs. |\n| `z_offset` | number | | Height of the stair\'s TOP above the floor base (slabZ; project units, 10 = 1 ft). Omitted \u2192 this floor\'s slab thickness, so the top is flush with the walking surface and the flights descend to the floor below. Raise it for an internal step whose top sits above the floor. |\n| `material` | string | | |\n\n\n### `spiral_staircase`\n\nA helical staircase: `steps` treads winding `turns` revolutions around a central pole, from the floor to `total_height`, within `radius`. Placed by its CENTRE (x, y). Optional fields fall back to sensible defaults at render time.\n\n| field | type | req | notes |\n|---|---|---|---|\n| `type` | literal `spiral_staircase` | **yes** | |\n| `formulas` | map: field name \u2192 `"= formula"` string | | *(shared \u2014 see top)* |\n| `enabled` | boolean or number (`false`/`0` = hidden) | | *(shared \u2014 see top)* |\n| `layer` | string | | *(shared \u2014 see top)* |\n| `name` | string | | Label |\n| `x` | number | **yes** | Centre X (project units) |\n| `y` | number | **yes** | Centre Y (project units) |\n| `radius` | number > 0 | **yes** | Outer radius (project units) |\n| `total_height` | number > 0 | **yes** | Total rise (floor to top step) (project units) |\n| `turns` | number > 0 | | Revolutions (default 1) |\n| `steps` | integer | | Number of treads (default ~12 per turn) |\n| `tread_thickness` | number > 0 | | Tread slab thickness (project units) |\n| `pole_radius` | number > 0 | | Central pole radius (project units) |\n| `z_offset` | number | | Lift above floor base (project units) |\n\n\n### `door`\n\nFlat door/window remain valid as a legacy schema \u2014 new configs nest them inside room.walls[side].openings or wall.openings.\n\n| field | type | req | notes |\n|---|---|---|---|\n| `type` | literal `door` | **yes** | |\n| `formulas` | map: field name \u2192 `"= formula"` string | | *(shared \u2014 see top)* |\n| `enabled` | boolean or number (`false`/`0` = hidden) | | *(shared \u2014 see top)* |\n| `layer` | string | | *(shared \u2014 see top)* |\n| `name` | string | **yes** | |\n| `x` | number | **yes** | |\n| `y` | number | **yes** | |\n| `width` | number > 0 | **yes** | |\n| `height` | number > 0 | **yes** | |\n| `direction` | enum: `north` `south` `east` `west` | **yes** | |\n| `room` | string | | |\n| `wall` | string | | |\n| `open` | boolean | | |\n\n\n### `window`\n| field | type | req | notes |\n|---|---|---|---|\n| `type` | literal `window` | **yes** | |\n| `formulas` | map: field name \u2192 `"= formula"` string | | *(shared \u2014 see top)* |\n| `enabled` | boolean or number (`false`/`0` = hidden) | | *(shared \u2014 see top)* |\n| `layer` | string | | *(shared \u2014 see top)* |\n| `name` | string | **yes** | |\n| `x` | number | **yes** | |\n| `y` | number | **yes** | |\n| `width` | number > 0 | **yes** | |\n| `height` | number > 0 | **yes** | |\n| `sill_height` | number \u2265 0 | | |\n| `direction` | enum: `north` `south` `east` `west` | **yes** | |\n| `room` | string | | |\n| `wall` | string | | |\n| `open` | boolean | | |\n\n\n### `kitchen_platform`\n\nKitchen platform \u2014 a polyline countertop / cooking slab that runs along the base of walls. Path is the wall-side edge; the platform extends `depth` units perpendicular to each segment on the given `side`. Renders as one box per path segment; corners meet at the shared point (no fancy mitering in v1).\n\n| field | type | req | notes |\n|---|---|---|---|\n| `type` | literal `kitchen_platform` | **yes** | |\n| `formulas` | map: field name \u2192 `"= formula"` string | | *(shared \u2014 see top)* |\n| `enabled` | boolean or number (`false`/`0` = hidden) | | *(shared \u2014 see top)* |\n| `layer` | string | | *(shared \u2014 see top)* |\n| `name` | string | | |\n| `path` | array of tuple `[n,n]` | **yes** | |\n| `side` | enum: `left` `right` | **yes** | |\n| `depth` | number > 0 | **yes** | |\n| `height` | number > 0 | **yes** | |\n| `z_offset` | number | | Lift above the FLOOR BASE (slabZ), project units. Omitted \u2192 defaults to the floor\'s resolved slab thickness (sits on the slab top, as before). Same convention as `room`. |\n| `base_z` | number | | |\n| `material` | string | | |\n\n\n### `roof`\n\nv2 roof \u2014 unified segment-based type that replaces hip/gable/flat/shed. Schema is permissive; the v2 pipeline (svg2d/roof/v2/) validates segments + slope + endpoint style at derivation time.\n\n\n> **Freeform:** extra fields are allowed (`.catchall`) and validated at derivation time. See `roof-v2-guide.md`.\n\n| field | type | req | notes |\n|---|---|---|---|\n| `type` | literal `roof` | **yes** | |\n| `formulas` | map: field name \u2192 `"= formula"` string | | *(shared \u2014 see top)* |\n| `enabled` | boolean or number (`false`/`0` = hidden) | | *(shared \u2014 see top)* |\n\n\n## Shared & nested schemas\n\n### site\n\nObjects don\'t bind to the grid with a special field \u2014 a grid line\'s position is published as a formula symbol (`<gridId>.x<name>` / `.y<name>`, see param/resolve.ts), so a room places itself with ordinary `formulas`, e.g. { x: "= main.x1", width: "= main.x5 - main.x1" }. With coord_convention:"center" those are wall centrelines and expandRoomWalls handles the wall extent.\n\n| field | type | req | notes |\n|---|---|---|---|\n| `reference_x` | number | **yes** | |\n| `reference_y` | number | **yes** | |\n| `plot_length` | number > 0 | **yes** | |\n| `plot_width` | number > 0 | **yes** | |\n| `formulas` | map: field name \u2192 `"= formula"` string | | |\n| `enabled` | boolean or number (`false`/`0` = hidden) | | |\n\n\n### houseDefaults\n\nHouse-level overrides for the built-in GlobalConfig defaults. Every floor without its own value falls back to these; if these are absent too, the code defaults in DEFAULT_GLOBAL_CONFIG apply.\n\n| field | type | req | notes |\n|---|---|---|---|\n| `floor_height` | number > 0 | | |\n| `wall_height` | number > 0 | | |\n| `slab_thickness` | number \u2265 0 | | |\n| `wall_thickness` | number > 0 | | House-wide wall thickness (project units). Per-object `wall_thickness`/`thickness` overrides still win. Falls back to the code default (DEFAULT_GLOBAL_CONFIG.wall_thickness = 8) when omitted. |\n| `formulas` | map: field name \u2192 `"= formula"` string | | |\n| `enabled` | boolean or number (`false`/`0` = hidden) | | |\n\n\n### units\n\nHow dimensions are LABELLED on the drawings. Display-only \u2014 geometry always stays in project units; this just controls the text on the dimension lines. Omitted = the built-in default (feet & inches, 10 project units = 1 ft).\n\n| field | type | req | notes |\n|---|---|---|---|\n| `system` | enum: `feet_inches` `feet` `meters` `centimeters` `millimeters` | | feet_inches \u2192 12\' 6" ; the rest \u2192 decimal with a unit suffix. |\n| `per_unit` | number > 0 | | Project units that equal ONE display unit (10 \u2192 10 units = 1 ft; 100 \u2192 100 units = 1 m). Default 10. |\n| `precision` | integer \u2265 0 | | Decimal places for the non-feet_inches systems. |\n\n\n### Opening\n\nThe plinth is now a normal object placed on the "Plinth" floor (the first floor, number 0), not a top-level config key. Its footprint + height match the old top-level plinth; the plinth floor\'s `height` drives the rise to the floor above (replacing the old hardcoded plinth_height seed). `plinth` + `ground` are GENERATED from fields (schema/fields/{plinth,ground}.ts), imported above as plinthObject / groundObject. (P2b)\n\n| field | type | req | notes |\n|---|---|---|---|\n| `kind` | enum: `door` `window` | **yes** | |\n| `name` | string | | |\n| `formulas` | map: field name \u2192 `"= formula"` string | | Numeric fields hold the RESOLVED value; a `= formula` for any of them lives in `formulas` (e.g. formulas.offset), evaluated by resolveParametric against the house variables/points \u2014 same pattern as every other object. |\n| `offset` | number \u2265 0 | **yes** | |\n| `width` | number > 0 | **yes** | |\n| `height` | number > 0 | **yes** | |\n| `sill_height` | number | | |\n| `direction` | enum: `north` `south` `east` `west` | | |\n| `facing` | enum: `north` `south` `east` `west` | | |\n| `open` | boolean | | When true, the opening is left BARE (just a hole) \u2014 no glazing/frame for a window, no leaf for a door \u2014 e.g. an open doorway or unglazed vent. |\n\n\n### RoomWallSide\n| field | type | req | notes |\n|---|---|---|---|\n| `height` | number | | |\n| `height_end` | number | | |\n| `openings` | array of [Opening](#opening) | | |\n\n\n### RoomItem\n\nA furniture piece nested INSIDE a room (room.items[]). It has NO x/y \u2014 its plan position is DERIVED at expand time from the parent room\'s footprint + `anchor` + per-axis gap (+ its own `rotation`). Flattened into a top-level `item` for every renderer. `gap_x`/`gap_y` are the inset (project units) kept from the anchor into the room (edge/corner anchor \u2192 clears the wall; centre anchor \u2192 signed offset, +x east / +y south). `gap_x`/`gap_y`/`rotation`/`scale`/`z_offset` are all plain numeric fields so each can be driven by a `= formula` (via the `formulas` map).\n\n| field | type | req | notes |\n|---|---|---|---|\n| `name` | string | | |\n| `formulas` | map: field name \u2192 `"= formula"` string | | |\n| `enabled` | boolean or number (`false`/`0` = hidden) | | |\n| `layer` | string | | |\n| `asset` | [ItemAsset](#itemasset) | **yes** | |\n| `anchor` | [ItemAnchor](#itemanchor) | | |\n| `gap_x` | number | | |\n| `gap_y` | number | | |\n| `rotation` | number | | |\n| `scale` | number > 0 | | |\n| `z_offset` | number | | |\n\n\n### ItemAsset\n\nFurniture (GLB `item`) \u2014 shared schema pieces Defined BEFORE `room` so a room can nest its own `items[]`. Asset distances are METRES (the GLB\'s native unit); the 3D/2D layers scale them into project units. See registry/nodes/item + three/units. The asset backing a furniture item \u2014 stored INLINE so a .wadi is self-contained (share links / web load the GLB from `src`). A catalog is just a picker convenience.\n\n| field | type | req | notes |\n|---|---|---|---|\n| `id` | string | **yes** | |\n| `name` | string | | |\n| `src` | string | **yes** | |\n| `dimensions` | tuple `[n>0,n>0,n>0]` | **yes** | |\n| `thumbnail` | string | | |\n| `floorPlanUrl` | string | | |\n| `category` | string | | |\n| `tags` | array of string | | |\n| `offset` | tuple `[n,n,n]` | | |\n| `corrRotation` | tuple `[n,n,n]` | | |\n| `corrScale` | tuple `[n>0,n>0,n>0]` | | |\n\n\n### ComponentDef\n| field | type | req | notes |\n|---|---|---|---|\n| `name` | string | | |\n| `goal` | string | | A short natural-language description of what this component accomplishes (the discovery key for goal-based module lookup, e.g. "climb to the next floor"). Purely metadata \u2014 renderers ignore it. |\n| `params` | array of [ComponentParam](#componentparam) | | |\n| `variables` | map: string \u2192 number, or `"= formula"` string | | |\n| `points` | map: string \u2192 inline object | | |\n| `objects` | array of [Object types](#object-types) | **yes** | |\n\n\n### ComponentParam\n\nA reusable component DEFINITION in the in-file `components` library. It is a mini-house: its own `variables`/`points` and a flat `objects` body authored in LOCAL coords (origin 0,0). `params` names which variables are the public inputs (label/default for the instance form). A `component` instance overrides those variables and places the body at its (x,y,z_offset). Stored once; referenced by many instances.\n\n| field | type | req | notes |\n|---|---|---|---|\n| `name` | string | **yes** | |\n| `label` | string | | |\n| `description` | string | | |\n| `default` | number | | |\n\n\n### LayerDef\n\nA visibility layer for the 3D view. Each object may reference a layer by `id` (via its `layer` field); the layers menu toggles whole layers on/off. Display-only \u2014 never affects geometry. Optional: when absent, a built-in default layer set is used, and objects fall back to an automatic per-type/floor mapping.\n\n| field | type | req | notes |\n|---|---|---|---|\n| `id` | string (non-empty) | **yes** | |\n| `label` | string | **yes** | |\n| `color` | string | | |\n| `group` | string | | Friendly group for the owner "Show/hide layers" menu (e.g. "Roof", "Walls"). Layers sharing a group toggle together. Optional. |\n\n\n### configurator\n| field | type | req | notes |\n|---|---|---|---|\n| `title` | string | | |\n| `description` | string | | |\n| `groups` | array of inline object | | |\n| `inputs` | array of [ConfiguratorInput](#configuratorinput) | **yes** | |\n\n\n### ConfiguratorInput\n\nConfigurator (Gharkul owner UI) Optional, author-supplied metadata: which `variables`/`points` a template exposes to end users, and how to present them. IGNORED by the resolver and every geometry consumer \u2014 read only by the owner-facing Configurator UI. `target` is a variable name (e.g. "floorH") or a point coordinate ("House.W" \u2192 points.House.x; W/L/X/Y/x/y are resolver synonyms). `min`/`max`/ `step` are in RAW project units; `unit` only affects display.\n\n| field | type | req | notes |\n|---|---|---|---|\n| `target` | string (non-empty) | **yes** | |\n| `label` | string (non-empty) | **yes** | |\n| `description` | string | | |\n| `control` | enum: `slider` `number` `select` `toggle` | | |\n| `unit` | enum: `ft` `in` `m` `units` `percent` `count` `none` | | |\n| `min` | number | | |\n| `max` | number | | |\n| `step` | number > 0 | | |\n| `options` | array of inline object | | |\n| `group` | string | | |\n\n\n### ItemAnchor\n\n9-point anchor on a room\'s INNER footprint. First token = vertical (top = north \u2026 bottom = south), second = horizontal (left = west \u2026 right = east); "center" alone = both. The item aligns its matching edge/corner to this spot, held `gap` off it, into the room \u2014 so it reflows when the room is resized.\n\nEnum: `top-left` `top-center` `top-right` `center-left` `center` `center-right` `bottom-left` `bottom-center` `bottom-right`\n'
|
|
422791
|
+
"body": '# Wadi data model (`.wadi` / `house_config.json`)\n\n> **Generated from `editor/src/schema/houseConfig.ts` \u2014 do not edit by hand.**\n> Regenerate: `node scripts/gen-schema-doc.mjs <path/to/houseConfig.ts> reference/data-model.md`\n> Some primitives (beam, floor_slab, pillar, plinth, ground) are generated from their\n> `fields` (schema/fields/\\*) into generated/objects.generated.ts \u2014 run `npm run gen-primitives`\n> in editor/ first if you changed those, so the generated schemas (which this doc reads) are current.\n> The Zod schema is the single source of truth; this file mirrors it (structure + the\n> semantics carried in its comments) so it can\'t drift.\n\nA `.wadi` file is one JSON object matching **HouseConfig**. Geometry is in **project\nunits** (a unitless grid; by default `units.per_unit = 10` means 10 units = 1 ft).\nPlan coordinates are **Inkscape-style**: origin top-left, **X \u2192 right, Y \u2192 down**.\nSee `coordinate-system.md` for the coordinate/units detail and `parametric-conventions.md`\nfor variables/points/formulas.\n\n## Fields shared by (almost) every object\n\nThese appear on most object types; documented once here, marked *(cross-cutting)* below.\n\n| field | type | req | notes |\n|---|---|---|---|\n| `type` | literal `room` | **yes** | |\n| `formulas` | map: field name \u2192 `"= formula"` string | | |\n| `enabled` | boolean or number (`false`/`0` = hidden) | | |\n| `layer` | string | | |\n| `name` | string | **yes** | |\n| `material` | string | | |\n| `z_offset` | number | | Vertical position of the room (its floor + walls), as a lift above the FLOOR BASE (slabZ = plinth top for floor 0, else the floor below\'s top; project units, 10 = 1 ft). This is the UNIFIED z_offset convention: every object is placed at `slabZ + z_offset`. When OMITTED, on-slab objects (room, wall, staircase, kitchen_platform) default z_offset to the floor\'s resolved slab thickness (floor.slab_thickness \u2192 house.defaults.slab_thickness \u2192 code default), so by default they sit on top of the slab, exactly as before. Set it explicitly for split-level floors \u2014 e.g. a room raised onto a thicker slab uses the same value the raised slab\'s top sits at. |\n\n\n- `type` \u2014 the discriminated-union tag; selects the object shape (values below).\n- `formulas` \u2014 per-field `"= expression"` overrides; the resolver evaluates each into the\n matching numeric field. See `parametric-conventions.md`.\n- `z_offset` \u2014 vertical lift above the floor base (slab top). On-slab objects (room, wall,\n staircase, kitchen_platform) default it to the floor\'s slab thickness; slab/beam/pillar/\n roof default to 0.\n\n## Top level \u2014 HouseConfig\n\n| field | type | req | notes |\n|---|---|---|---|\n| `coord_convention` | enum: `outer` `center` | | How a rectangular object\'s x/y/width/length relate to its walls (plans/grid-convention.md). "center" (new/canonical): coordinates are wall CENTRELINES \u2014 adjacent rooms ABUT on a shared line (no overlap), walls are centred on the boundary, and expandRoomWalls grows each footprint by wall_thickness/2 to the outer face. "outer" / absent (legacy): coordinates are the OUTER wall face and adjacent rooms must overlap by wall_thickness. |\n| `plinth` | any (freeform) | | Legacy top-level plinth (pre-"Plinth floor"). Tolerated but IGNORED so an un-migrated file still loads (it just renders without a plinth/ground) instead of failing .strict() validation. New configs put the plinth on the Plinth floor as a `plinth` object. |\n| `defaults` | [houseDefaults](#housedefaults) | | |\n| `units` | [units](#units) | | |\n| `layers` | array of [LayerDef](#layerdef) | | Configurable 3D visibility layers (optional; defaults applied when absent). Objects opt in via their own `layer` field. |\n| `variables` | map: string \u2192 number, or `"= formula"` string | | Parametric layer (plans/object-relationships-plan.md). Named scalar variables (number or "= formula", may reference other variables) and named 2D points; object `formulas` maps reference these. Optional \u2014 absent = a plain non-parametric house, resolved as a no-op. |\n| `points` | map: string \u2192 inline object | | |\n| `components` | map: string \u2192 [ComponentDef](#componentdef) | | Reusable-component library (in-file). Map of id \u2192 ComponentDef. A `component` object instantiates one by `ref`. Stored once; referenced by many instances; edit here to update every instance. |\n| `grids` | map: string \u2192 `gridDef` | | First-class parametric grids (plans/grid-convention.md). Map of id \u2192 GridDef (named X/Y wall centrelines). Rooms/slabs bind via `grid`+`cell`, pillars via `grid`+`node`; the resolver derives their geometry from the centrelines + wall thickness. Optional; reusable across templates. |\n| `configurator` | [configurator](#configurator) | | Configurator metadata (Gharkul owner UI). Optional; see plans/configurator-plan.md. |\n| `thumbnails` | array of string | | Preview snapshots (data: URLs) captured by the architect editor and saved WITH the template so the owner gallery can show real previews \u2014 multiple angles + the floor plan. `thumbnails[0]` is the gallery cover. Optional; excluded from share links (a preview isn\'t model data \u2014 see io/shareLink.ts). `thumbnail` (singular) is the legacy one-image form, still read as a fallback so old template files keep working. |\n| `thumbnail` | string | | |\n| `template` | inline object | | Catalog metadata that makes a `.wadi` SELF-DESCRIBING: the editorial fields a gallery card needs that can\'t be derived from geometry (title, blurb, style/roof tags, min plot). With this block + `thumbnails[]`, a folder of `.wadi` files IS the catalog \u2014 the app lists the folder and indexes each file, with no separate index.json to maintain (see io/templateSource.ts). Non-strict so newer editorial fields don\'t break an older build. |\n| `floors` | array of [floor](#floor) | **yes** | |\n| `_walls_expanded` | boolean | | |\n\n\n## floor\n\n| field | type | req | notes |\n|---|---|---|---|\n| `floor_number` | integer \u2265 0 | **yes** | |\n| `name` | string | **yes** | |\n| `height` | number > 0 | | Per-floor overrides for the default heights in GlobalConfig. In project units (10 units = 1 ft). All three are INDEPENDENT \u2014 no relationship enforced between them: height \u2014 floor-to-floor rise (drives roof wallTop-Z stack) wall_height \u2014 standing wall height (floor top \u2192 ceiling) slab_thickness \u2014 RCC deck between this floor and the one above All fall back to GlobalConfig defaults when omitted. |\n| `wall_height` | number > 0 | | |\n| `slab_thickness` | number \u2265 0 | | |\n| `formulas` | map: field name \u2192 `"= formula"` string | | |\n| `enabled` | boolean or number (`false`/`0` = hidden) | | |\n| `objects` | array of [Object types](#object-types) | **yes** | |\n\n\n## Object types (`floors[].objects[]`)\n\nEvery entry in a floor\'s `objects` array is one of these, tagged by `type`:\n\n### `plinth`\n\nThe plinth is now a normal object placed on the "Plinth" floor (the first floor, number 0), not a top-level config key. Its footprint + height match the old top-level plinth; the plinth floor\'s `height` drives the rise to the floor above (replacing the old hardcoded plinth_height seed).\n\n| field | type | req | notes |\n|---|---|---|---|\n| `type` | literal `plinth` | **yes** | |\n| `formulas` | map: field name \u2192 `"= formula"` string | | *(shared \u2014 see top)* |\n| `enabled` | boolean or number (`false`/`0` = hidden) | | *(shared \u2014 see top)* |\n| `layer` | string | | *(shared \u2014 see top)* |\n| `name` | string | | Label |\n| `material` | string | | Material key |\n| `x` | number | **yes** | Top-left X (project units) |\n| `y` | number | **yes** | Top-left Y (project units) |\n| `width` | number > 0 | **yes** | X extent (project units) |\n| `length` | number > 0 | **yes** | Y extent (project units) |\n| `height` | number > 0 | **yes** | Plinth height (project units) |\n| `z_offset` | number | | Lift above ground (project units) |\n\n\n### `ground`\n\nThe ground plane, also on the Plinth floor. Extent defaults to the site plot when authored by the migration. `height` is an optional thickness (0 = a flat plane); slope fields are a later phase.\n\n| field | type | req | notes |\n|---|---|---|---|\n| `type` | literal `ground` | **yes** | |\n| `formulas` | map: field name \u2192 `"= formula"` string | | *(shared \u2014 see top)* |\n| `enabled` | boolean or number (`false`/`0` = hidden) | | *(shared \u2014 see top)* |\n| `layer` | string | | *(shared \u2014 see top)* |\n| `name` | string | | Label |\n| `material` | string | | Material key |\n| `x` | number | **yes** | Top-left X (project units) |\n| `y` | number | **yes** | Top-left Y (project units) |\n| `width` | number > 0 | **yes** | X extent (project units) |\n| `length` | number > 0 | **yes** | Y extent (project units) |\n| `height` | number \u2265 0 | | Thickness (0 = flat) (project units) |\n| `z_offset` | number | | Lift above origin (project units) |\n\n\n### `component`\n\nAn INSTANCE of a reusable component from the in-file `components` library. It references a component by id (`ref`), overrides the component\'s input variables via `params`, and places it at (x, y) with a `z_offset` lift on its parent floor. At render time `expandRoomWalls` flattens it into concrete objects (resolve component with param+origin overrides \u2192 recurse \u2192 offset), so no renderer needs to know about `component`.\n\n| field | type | req | notes |\n|---|---|---|---|\n| `type` | literal `component` | **yes** | |\n| `formulas` | map: field name \u2192 `"= formula"` string | | *(shared \u2014 see top)* |\n| `enabled` | boolean or number (`false`/`0` = hidden) | | *(shared \u2014 see top)* |\n| `layer` | string | | *(shared \u2014 see top)* |\n| `name` | string | | |\n| `ref` | string | **yes** | |\n| `params` | map: string \u2192 union \u2014 see notes | | Overrides for the component\'s declared input variables. A string starting with "=" is a formula evaluated in the HOST scope (so it can reference the host\'s variables/points); a number is used directly. |\n| `x` | number | **yes** | |\n| `y` | number | **yes** | |\n| `rotation` | number | | Standard placement: yaw\xB0 about the instance origin (clockwise, same sense as item rotation: 0=south, 90=east). Right angles (0/90/180/270) are exact for any component; a non-right angle is allowed only for furniture-only ones. |\n| `z_offset` | number | | |\n\n\n### `item`\n\nA free-standing GLB furniture / decor instance placed directly on a floor (for pieces that aren\'t inside an enclosed room \u2014 outdoor/site/verandah decor, a loft item, etc.). `x`/`y` are the item\'s plan CENTRE. It MAY instead anchor to a named room via `anchor_to` + `anchor` + `gap`, in which case `x`/`y` are DERIVED at expand time (same anchor model as room-nested items). `rotation` is yaw\xB0; `scale` is a uniform resize; `z_offset` lifts it above the floor base (default = slab thickness). (`itemAsset`, `itemAnchor`, `gapField` are defined above `room`.)\n\n| field | type | req | notes |\n|---|---|---|---|\n| `type` | literal `item` | **yes** | |\n| `formulas` | map: field name \u2192 `"= formula"` string | | *(shared \u2014 see top)* |\n| `enabled` | boolean or number (`false`/`0` = hidden) | | *(shared \u2014 see top)* |\n| `layer` | string | | *(shared \u2014 see top)* |\n| `name` | string | | |\n| `asset` | [ItemAsset](#itemasset) | **yes** | |\n| `x` | number | **yes** | |\n| `y` | number | **yes** | |\n| `rotation` | number | | |\n| `scale` | number > 0 | | |\n| `z_offset` | number | | |\n| `anchor_to` | string | | Optional room-relative anchoring (for a free item that should follow a room). |\n| `anchor` | [ItemAnchor](#itemanchor) | | |\n| `gap_x` | number | | |\n| `gap_y` | number | | |\n\n\n### `model`\n\nA GLB placed at real scale and manipulated by a `rig` of named-node ops. Distinct from `item` (furniture, catalog + anchoring): `model` is a rigged structural asset. `asset.dimensions` is the real metre size, used for the 2D footprint and the scale.\n\n| field | type | req | notes |\n|---|---|---|---|\n| `type` | literal `model` | **yes** | |\n| `formulas` | map: field name \u2192 `"= formula"` string | | *(shared \u2014 see top)* |\n| `enabled` | boolean or number (`false`/`0` = hidden) | | *(shared \u2014 see top)* |\n| `layer` | string | | *(shared \u2014 see top)* |\n| `name` | string | | |\n| `asset` | [ItemAsset](#itemasset) | **yes** | |\n| `x` | number | **yes** | |\n| `y` | number | **yes** | |\n| `rotation` | number | | |\n| `scale` | number > 0 | | |\n| `z_offset` | number | | |\n| `rig` | array of `rigOp` | | |\n\n\n### `floor_slab`\n| field | type | req | notes |\n|---|---|---|---|\n| `type` | literal `floor_slab` | **yes** | |\n| `formulas` | map: field name \u2192 `"= formula"` string | | *(shared \u2014 see top)* |\n| `enabled` | boolean or number (`false`/`0` = hidden) | | *(shared \u2014 see top)* |\n| `layer` | string | | *(shared \u2014 see top)* |\n| `name` | string | | Label |\n| `x` | number | **yes** | Top-left X (project units) |\n| `y` | number | **yes** | Top-left Y (project units) |\n| `width` | number > 0 | **yes** | X extent (project units) |\n| `length` | number > 0 | **yes** | Y extent (project units) |\n| `thickness` | number \u2265 0 | | Slab thickness (defaults to floor\'s) (project units) |\n| `z_offset` | number | | Lift above floor base (project units) |\n\n\n### `pillar`\n| field | type | req | notes |\n|---|---|---|---|\n| `type` | literal `pillar` | **yes** | |\n| `formulas` | map: field name \u2192 `"= formula"` string | | *(shared \u2014 see top)* |\n| `enabled` | boolean or number (`false`/`0` = hidden) | | *(shared \u2014 see top)* |\n| `layer` | string | | *(shared \u2014 see top)* |\n| `name` | string | **yes** | Label |\n| `x` | number | **yes** | Top-left corner X (project units) |\n| `y` | number | **yes** | Top-left corner Y (project units) |\n| `width` | number > 0 | | X extent (project units) |\n| `length` | number > 0 | | Y extent (project units) |\n| `height` | number > 0 | **yes** | Column height (project units) |\n| `z_offset` | number | | Lift above floor base (project units) |\n\n\n### `beam`\n| field | type | req | notes |\n|---|---|---|---|\n| `type` | literal `beam` | **yes** | |\n| `formulas` | map: field name \u2192 `"= formula"` string | | *(shared \u2014 see top)* |\n| `enabled` | boolean or number (`false`/`0` = hidden) | | *(shared \u2014 see top)* |\n| `layer` | string | | *(shared \u2014 see top)* |\n| `name` | string | | Label |\n| `x` | number | **yes** | Top-left X (project units) |\n| `y` | number | **yes** | Top-left Y (project units) |\n| `width` | number > 0 | **yes** | X extent (project units) |\n| `length` | number > 0 | **yes** | Y extent (project units) |\n| `height` | number > 0 | | Vertical thickness (project units) |\n| `z_offset` | number | | Lift above floor base (project units) |\n\n\n### `room`\n| field | type | req | notes |\n|---|---|---|---|\n| `type` | literal `room` | **yes** | |\n| `formulas` | map: field name \u2192 `"= formula"` string | | *(shared \u2014 see top)* |\n| `enabled` | boolean or number (`false`/`0` = hidden) | | *(shared \u2014 see top)* |\n| `layer` | string | | *(shared \u2014 see top)* |\n| `name` | string | **yes** | |\n| `x` | number | **yes** | |\n| `y` | number | **yes** | |\n| `width` | number > 0 | **yes** | |\n| `length` | number > 0 | **yes** | |\n| `height` | number \u2265 0 | | 0 accepted \u2014 semantically the same as absent ("use floor default"). Old configs that accidentally saved height: 0 keep loading; the form treats 0 as "no override" and doesn\'t write it back. |\n| `material` | string | | |\n| `z_offset` | number | | Vertical position of the room (its floor + walls), as a lift above the FLOOR BASE (slabZ = plinth top for floor 0, else the floor below\'s top; project units, 10 = 1 ft). This is the UNIFIED z_offset convention: every object is placed at `slabZ + z_offset`. When OMITTED, on-slab objects (room, wall, staircase, kitchen_platform) default z_offset to the floor\'s resolved slab thickness (floor.slab_thickness \u2192 house.defaults.slab_thickness \u2192 code default), so by default they sit on top of the slab, exactly as before. Set it explicitly for split-level floors \u2014 e.g. a room raised onto a thicker slab uses the same value the raised slab\'s top sits at. |\n| `walls` | union \u2014 see notes | | |\n| `wall_heights` | map: string \u2192 [wall_heights entry](#wall-heights-entry) | | |\n| `items` | array of [RoomItem](#roomitem) | | Furniture nested in this room. Each piece is anchored to the room\'s inner footprint (see roomItem), so it reflows when the room resizes. Expanded into top-level `item` objects at render time. |\n\n\n### `wall`\n| field | type | req | notes |\n|---|---|---|---|\n| `type` | literal `wall` | **yes** | |\n| `formulas` | map: field name \u2192 `"= formula"` string | | *(shared \u2014 see top)* |\n| `enabled` | boolean or number (`false`/`0` = hidden) | | *(shared \u2014 see top)* |\n| `layer` | string | | *(shared \u2014 see top)* |\n| `name` | string | **yes** | |\n| `start_x` | number | **yes** | |\n| `start_y` | number | **yes** | |\n| `end_x` | number | **yes** | |\n| `end_y` | number | **yes** | |\n| `height` | number > 0 | | |\n| `height_end` | number | | |\n| `material` | string | | |\n| `facing` | enum: `north` `south` `east` `west` | | |\n| `z_offset` | number | | Lift above the FLOOR BASE (slabZ), project units. Omitted \u2192 defaults to the floor\'s resolved slab thickness (sits on the slab, as before). Set it for a split-level wall. Same convention as `room`. |\n| `openings` | array of [Opening](#opening) | | |\n\n\n### `staircase`\n| field | type | req | notes |\n|---|---|---|---|\n| `type` | literal `staircase` | **yes** | |\n| `formulas` | map: field name \u2192 `"= formula"` string | | *(shared \u2014 see top)* |\n| `enabled` | boolean or number (`false`/`0` = hidden) | | *(shared \u2014 see top)* |\n| `layer` | string | | *(shared \u2014 see top)* |\n| `name` | string | | |\n| `climb` | enum: `up` `down` | | `climb` picks which end (start_x, start_y) is and which way the flight runs in z as it extends into `direction`: \u2022 "up" (recommended): BOTTOM-anchored. Put the stair on the LOWER floor it rises FROM; (start_x, start_y) is the bottom step\'s near corner on that floor and the flight ASCENDS into `direction`. `rise_height` defaults to THIS floor\'s height (climb to the next level). The intuitive way. \u2022 "down" (DEFAULT, kept for older configs): TOP-anchored. Put the stair on the upper DESTINATION floor; (start_x, start_y) is the top connection and the flight DESCENDS into `direction`. `rise_height` defaults to the floor immediately BELOW this one. Either way the body + landings fill the box [start, start + max_run] along `direction`, and `z_offset` is the ANCHORED end\'s height above the floor base (omitted \u2192 this floor\'s slab thickness, flush with the walking surface). |\n| `start_x` | number | **yes** | |\n| `start_y` | number | **yes** | |\n| `rise_height` | number > 0 | | Total height the stair covers, top \u2192 floor below. The step COUNT is derived: num_steps = round(rise_height / step_rise). Omitted \u2192 defaults to the height of the floor immediately below this one. Formula-capable (e.g. "= floor_height"). Replaces the old explicit `num_steps`. |\n| `step_rise` | number > 0 | **yes** | |\n| `step_tread` | number > 0 | **yes** | |\n| `step_width` | number > 0 | **yes** | |\n| `direction` | enum: `north` `south` `east` `west` | **yes** | The direction the stair EXTENDS from its top \u2014 the whole assembly fills the allocated box from (start_x,start_y) going this way for up to `max_run`. |\n| `max_run` | number > 0 | | ALLOCATED run: the length of space reserved for the stair along `direction`. The WHOLE assembly (flights + turn landings) is kept within [start, start+max_run]; when the run won\'t fit as one flight it auto-splits into switchback flights (more flights when tight), expanded in expandRoomWalls into plain staircases + floor_slab landings so every renderer is unchanged. Omit \u2192 one flight, no length limit. |\n| `landing_depth` | number > 0 | | Turn-landing depth (along the run). Omitted \u2192 equals step_width. |\n| `landing_thickness` | number \u2265 0 | | Turn-landing slab thickness. Omitted \u2192 equals step_rise. |\n| `turn` | enum: `clockwise` `anticlockwise` | | Switchback handedness, reckoned DESCENDING from the top. Omitted \u2192 "clockwise". Only affects split stairs. |\n| `flight_gap` | number > 0 | | Lateral gap between the two switchback flights (a stairwell void for a spine wall). Omitted/0 \u2192 flights are adjacent. The turn landings widen to bridge the gap. Only affects split stairs. |\n| `z_offset` | number | | Height of the stair\'s TOP above the floor base (slabZ; project units, 10 = 1 ft). Omitted \u2192 this floor\'s slab thickness, so the top is flush with the walking surface and the flights descend to the floor below. Raise it for an internal step whose top sits above the floor. |\n| `material` | string | | |\n\n\n### `spiral_staircase`\n\nA helical staircase: `steps` treads winding `turns` revolutions around a central pole, from the floor to `total_height`, within `radius`. Placed by its CENTRE (x, y). Optional fields fall back to sensible defaults at render time.\n\n| field | type | req | notes |\n|---|---|---|---|\n| `type` | literal `spiral_staircase` | **yes** | |\n| `formulas` | map: field name \u2192 `"= formula"` string | | *(shared \u2014 see top)* |\n| `enabled` | boolean or number (`false`/`0` = hidden) | | *(shared \u2014 see top)* |\n| `layer` | string | | *(shared \u2014 see top)* |\n| `name` | string | | Label |\n| `x` | number | **yes** | Centre X (project units) |\n| `y` | number | **yes** | Centre Y (project units) |\n| `radius` | number > 0 | **yes** | Outer radius (project units) |\n| `total_height` | number > 0 | **yes** | Total rise (floor to top step) (project units) |\n| `turns` | number > 0 | | Revolutions (default 1) |\n| `steps` | integer | | Number of treads (default ~12 per turn) |\n| `tread_thickness` | number > 0 | | Tread slab thickness (project units) |\n| `pole_radius` | number > 0 | | Central pole radius (project units) |\n| `z_offset` | number | | Lift above floor base (project units) |\n\n\n### `door`\n\nFlat door/window remain valid as a legacy schema \u2014 new configs nest them inside room.walls[side].openings or wall.openings.\n\n| field | type | req | notes |\n|---|---|---|---|\n| `type` | literal `door` | **yes** | |\n| `formulas` | map: field name \u2192 `"= formula"` string | | *(shared \u2014 see top)* |\n| `enabled` | boolean or number (`false`/`0` = hidden) | | *(shared \u2014 see top)* |\n| `layer` | string | | *(shared \u2014 see top)* |\n| `name` | string | **yes** | |\n| `x` | number | **yes** | |\n| `y` | number | **yes** | |\n| `width` | number > 0 | **yes** | |\n| `height` | number > 0 | **yes** | |\n| `direction` | enum: `north` `south` `east` `west` | **yes** | |\n| `room` | string | | |\n| `wall` | string | | |\n| `open` | boolean | | |\n\n\n### `window`\n| field | type | req | notes |\n|---|---|---|---|\n| `type` | literal `window` | **yes** | |\n| `formulas` | map: field name \u2192 `"= formula"` string | | *(shared \u2014 see top)* |\n| `enabled` | boolean or number (`false`/`0` = hidden) | | *(shared \u2014 see top)* |\n| `layer` | string | | *(shared \u2014 see top)* |\n| `name` | string | **yes** | |\n| `x` | number | **yes** | |\n| `y` | number | **yes** | |\n| `width` | number > 0 | **yes** | |\n| `height` | number > 0 | **yes** | |\n| `sill_height` | number \u2265 0 | | |\n| `direction` | enum: `north` `south` `east` `west` | **yes** | |\n| `room` | string | | |\n| `wall` | string | | |\n| `open` | boolean | | |\n\n\n### `kitchen_platform`\n\nKitchen platform \u2014 a polyline countertop / cooking slab that runs along the base of walls. Path is the wall-side edge; the platform extends `depth` units perpendicular to each segment on the given `side`. Renders as one box per path segment; corners meet at the shared point (no fancy mitering in v1).\n\n| field | type | req | notes |\n|---|---|---|---|\n| `type` | literal `kitchen_platform` | **yes** | |\n| `formulas` | map: field name \u2192 `"= formula"` string | | *(shared \u2014 see top)* |\n| `enabled` | boolean or number (`false`/`0` = hidden) | | *(shared \u2014 see top)* |\n| `layer` | string | | *(shared \u2014 see top)* |\n| `name` | string | | |\n| `path` | array of tuple `[n,n]` | **yes** | |\n| `side` | enum: `left` `right` | **yes** | |\n| `depth` | number > 0 | **yes** | |\n| `height` | number > 0 | **yes** | |\n| `z_offset` | number | | Lift above the FLOOR BASE (slabZ), project units. Omitted \u2192 defaults to the floor\'s resolved slab thickness (sits on the slab top, as before). Same convention as `room`. |\n| `base_z` | number | | |\n| `material` | string | | |\n\n\n### `roof`\n\nv2 roof \u2014 unified segment-based type that replaces hip/gable/flat/shed. Schema is permissive; the v2 pipeline (svg2d/roof/v2/) validates segments + slope + endpoint style at derivation time.\n\n\n> **Freeform:** extra fields are allowed (`.catchall`) and validated at derivation time. See `roof-v2-guide.md`.\n\n| field | type | req | notes |\n|---|---|---|---|\n| `type` | literal `roof` | **yes** | |\n| `formulas` | map: field name \u2192 `"= formula"` string | | *(shared \u2014 see top)* |\n| `enabled` | boolean or number (`false`/`0` = hidden) | | *(shared \u2014 see top)* |\n\n\n## Shared & nested schemas\n\n### site\n\nObjects don\'t bind to the grid with a special field \u2014 a grid line\'s position is published as a formula symbol (`<gridId>.x<name>` / `.y<name>`, see param/resolve.ts), so a room places itself with ordinary `formulas`, e.g. { x: "= main.x1", width: "= main.x5 - main.x1" }. With coord_convention:"center" those are wall centrelines and expandRoomWalls handles the wall extent.\n\n| field | type | req | notes |\n|---|---|---|---|\n| `reference_x` | number | **yes** | |\n| `reference_y` | number | **yes** | |\n| `plot_length` | number > 0 | **yes** | |\n| `plot_width` | number > 0 | **yes** | |\n| `formulas` | map: field name \u2192 `"= formula"` string | | |\n| `enabled` | boolean or number (`false`/`0` = hidden) | | |\n\n\n### houseDefaults\n\nHouse-level overrides for the built-in GlobalConfig defaults. Every floor without its own value falls back to these; if these are absent too, the code defaults in DEFAULT_GLOBAL_CONFIG apply.\n\n| field | type | req | notes |\n|---|---|---|---|\n| `floor_height` | number > 0 | | |\n| `wall_height` | number > 0 | | |\n| `slab_thickness` | number \u2265 0 | | |\n| `wall_thickness` | number > 0 | | House-wide wall thickness (project units). Per-object `wall_thickness`/`thickness` overrides still win. Falls back to the code default (DEFAULT_GLOBAL_CONFIG.wall_thickness = 8) when omitted. |\n| `formulas` | map: field name \u2192 `"= formula"` string | | |\n| `enabled` | boolean or number (`false`/`0` = hidden) | | |\n\n\n### units\n\nHow dimensions are LABELLED on the drawings. Display-only \u2014 geometry always stays in project units; this just controls the text on the dimension lines. Omitted = the built-in default (feet & inches, 10 project units = 1 ft).\n\n| field | type | req | notes |\n|---|---|---|---|\n| `system` | enum: `feet_inches` `feet` `meters` `centimeters` `millimeters` | | feet_inches \u2192 12\' 6" ; the rest \u2192 decimal with a unit suffix. |\n| `per_unit` | number > 0 | | Project units that equal ONE display unit (10 \u2192 10 units = 1 ft; 100 \u2192 100 units = 1 m). Default 10. |\n| `precision` | integer \u2265 0 | | Decimal places for the non-feet_inches systems. |\n\n\n### Opening\n\nThe plinth is now a normal object placed on the "Plinth" floor (the first floor, number 0), not a top-level config key. Its footprint + height match the old top-level plinth; the plinth floor\'s `height` drives the rise to the floor above (replacing the old hardcoded plinth_height seed). `plinth` + `ground` are GENERATED from fields (schema/fields/{plinth,ground}.ts), imported above as plinthObject / groundObject. (P2b)\n\n| field | type | req | notes |\n|---|---|---|---|\n| `kind` | enum: `door` `window` | **yes** | |\n| `name` | string | | |\n| `formulas` | map: field name \u2192 `"= formula"` string | | Numeric fields hold the RESOLVED value; a `= formula` for any of them lives in `formulas` (e.g. formulas.offset), evaluated by resolveParametric against the house variables/points \u2014 same pattern as every other object. |\n| `offset` | number \u2265 0 | **yes** | |\n| `width` | number > 0 | **yes** | |\n| `height` | number > 0 | **yes** | |\n| `sill_height` | number | | |\n| `direction` | enum: `north` `south` `east` `west` | | |\n| `facing` | enum: `north` `south` `east` `west` | | |\n| `open` | boolean | | When true, the opening is left BARE (just a hole) \u2014 no glazing/frame for a window, no leaf for a door \u2014 e.g. an open doorway or unglazed vent. |\n\n\n### RoomWallSide\n| field | type | req | notes |\n|---|---|---|---|\n| `height` | number | | |\n| `height_end` | number | | |\n| `openings` | array of [Opening](#opening) | | |\n\n\n### RoomItem\n\nA furniture piece nested INSIDE a room (room.items[]). It has NO x/y \u2014 its plan position is DERIVED at expand time from the parent room\'s footprint + `anchor` + per-axis gap (+ its own `rotation`). Flattened into a top-level `item` for every renderer. `gap_x`/`gap_y` are the inset (project units) kept from the anchor into the room (edge/corner anchor \u2192 clears the wall; centre anchor \u2192 signed offset, +x east / +y south). `gap_x`/`gap_y`/`rotation`/`scale`/`z_offset` are all plain numeric fields so each can be driven by a `= formula` (via the `formulas` map).\n\n| field | type | req | notes |\n|---|---|---|---|\n| `name` | string | | |\n| `formulas` | map: field name \u2192 `"= formula"` string | | |\n| `enabled` | boolean or number (`false`/`0` = hidden) | | |\n| `layer` | string | | |\n| `asset` | [ItemAsset](#itemasset) | **yes** | |\n| `anchor` | [ItemAnchor](#itemanchor) | | |\n| `gap_x` | number | | |\n| `gap_y` | number | | |\n| `rotation` | number | | |\n| `scale` | number > 0 | | |\n| `z_offset` | number | | |\n\n\n### ItemAsset\n\nFurniture (GLB `item`) \u2014 shared schema pieces Defined BEFORE `room` so a room can nest its own `items[]`. Asset distances are METRES (the GLB\'s native unit); the 3D/2D layers scale them into project units. See registry/nodes/item + three/units. The asset backing a furniture item \u2014 stored INLINE so a .wadi is self-contained (share links / web load the GLB from `src`). A catalog is just a picker convenience.\n\n| field | type | req | notes |\n|---|---|---|---|\n| `id` | string | **yes** | |\n| `name` | string | | |\n| `src` | string | **yes** | |\n| `dimensions` | tuple `[n>0,n>0,n>0]` | **yes** | |\n| `thumbnail` | string | | |\n| `floorPlanUrl` | string | | |\n| `category` | string | | |\n| `tags` | array of string | | |\n| `offset` | tuple `[n,n,n]` | | |\n| `corrRotation` | tuple `[n,n,n]` | | |\n| `corrScale` | tuple `[n>0,n>0,n>0]` | | |\n\n\n### ComponentDef\n| field | type | req | notes |\n|---|---|---|---|\n| `name` | string | | |\n| `goal` | string | | A short natural-language description of what this component accomplishes (the discovery key for goal-based module lookup, e.g. "climb to the next floor"). Purely metadata \u2014 renderers ignore it. |\n| `params` | array of [ComponentParam](#componentparam) | | |\n| `variables` | map: string \u2192 number, or `"= formula"` string | | |\n| `points` | map: string \u2192 inline object | | |\n| `objects` | array of [Object types](#object-types) | **yes** | |\n| `expose` | inline object | | Promote this component to a typed primitive at load time (plans/declarative-plugins.md P0). When present, the component registers a NodeDefinition of type `expose.type` whose fields come from `params`; it can then be used like any core object type. `type` is namespaced (`pack.thing`). |\n\n\n### ComponentParam\n\nA reusable component DEFINITION in the in-file `components` library. It is a mini-house: its own `variables`/`points` and a flat `objects` body authored in LOCAL coords (origin 0,0). `params` names which variables are the public inputs (label/default for the instance form). A `component` instance overrides those variables and places the body at its (x,y,z_offset). Stored once; referenced by many instances.\n\n| field | type | req | notes |\n|---|---|---|---|\n| `name` | string | **yes** | |\n| `label` | string | | |\n| `description` | string | | |\n| `default` | number | | |\n| `kind` | string | | Field-projection annotations, used only when the component is `expose`d as a typed primitive (plans/declarative-plugins.md). `kind` is a FieldKind preset (coord/extent/nonneg/int/text/flag/enum); when absent the kind is inferred from the default\'s type. `unit` is a doc-only unit hint. |\n| `unit` | string | | |\n\n\n### LayerDef\n\nA visibility layer for the 3D view. Each object may reference a layer by `id` (via its `layer` field); the layers menu toggles whole layers on/off. Display-only \u2014 never affects geometry. Optional: when absent, a built-in default layer set is used, and objects fall back to an automatic per-type/floor mapping.\n\n| field | type | req | notes |\n|---|---|---|---|\n| `id` | string (non-empty) | **yes** | |\n| `label` | string | **yes** | |\n| `color` | string | | |\n| `group` | string | | Friendly group for the owner "Show/hide layers" menu (e.g. "Roof", "Walls"). Layers sharing a group toggle together. Optional. |\n\n\n### configurator\n| field | type | req | notes |\n|---|---|---|---|\n| `title` | string | | |\n| `description` | string | | |\n| `groups` | array of inline object | | |\n| `inputs` | array of [ConfiguratorInput](#configuratorinput) | **yes** | |\n\n\n### ConfiguratorInput\n\nConfigurator (Gharkul owner UI) Optional, author-supplied metadata: which `variables`/`points` a template exposes to end users, and how to present them. IGNORED by the resolver and every geometry consumer \u2014 read only by the owner-facing Configurator UI. `target` is a variable name (e.g. "floorH") or a point coordinate ("House.W" \u2192 points.House.x; W/L/X/Y/x/y are resolver synonyms). `min`/`max`/ `step` are in RAW project units; `unit` only affects display.\n\n| field | type | req | notes |\n|---|---|---|---|\n| `target` | string (non-empty) | **yes** | |\n| `label` | string (non-empty) | **yes** | |\n| `description` | string | | |\n| `control` | enum: `slider` `number` `select` `toggle` | | |\n| `unit` | enum: `ft` `in` `m` `units` `percent` `count` `none` | | |\n| `min` | number | | |\n| `max` | number | | |\n| `step` | number > 0 | | |\n| `options` | array of inline object | | |\n| `group` | string | | |\n\n\n### ItemAnchor\n\n9-point anchor on a room\'s INNER footprint. First token = vertical (top = north \u2026 bottom = south), second = horizontal (left = west \u2026 right = east); "center" alone = both. The item aligns its matching edge/corner to this spot, held `gap` off it, into the room \u2014 so it reflows when the room is resized.\n\nEnum: `top-left` `top-center` `top-right` `center-left` `center` `center-right` `bottom-left` `bottom-center` `bottom-right`\n'
|
|
422752
422792
|
}
|
|
422753
422793
|
};
|
|
422754
422794
|
var MODULES = {
|