@adatechnology/game-floorplan 0.1.0 → 0.2.0

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/index.js CHANGED
@@ -4,6 +4,227 @@ var MARGIN = WALL_THICKNESS / 2;
4
4
  var EPS = 1e-6;
5
5
  var TOL = 1e-3;
6
6
  var DOOR_CLEARANCE = 0.9;
7
+ var PLAYER_RADIUS = 0.24;
8
+ var DOOR_EDGE_CLEARANCE = 0.12;
9
+ var WINDOW_EDGE_CLEARANCE = 0.3;
10
+ var OVERHEAD_TOP = 2.15;
11
+ var WINDOWLESS_ROOM_TYPES = /* @__PURE__ */ new Set([
12
+ "stockroom",
13
+ "kitchen",
14
+ "laundry",
15
+ "bathroom",
16
+ "void",
17
+ "elevator",
18
+ "stairs",
19
+ "pantry",
20
+ "closet",
21
+ "corridor",
22
+ "hall",
23
+ "balcony",
24
+ "terrace",
25
+ "garage",
26
+ "storage",
27
+ "porch",
28
+ "tent",
29
+ "outhouse"
30
+ ]);
31
+ var CAR_CLEARANCE = { driver: 0.8, side: 0.6, end: 0.6 };
32
+
33
+ // src/registry.ts
34
+ var CATALOG = {};
35
+ var LIBRARIES = {};
36
+ var LIBRARY_ALIAS = {};
37
+ var TEMPLATES = {};
38
+ var STYLES = {};
39
+ var STYLE_ALIAS = {};
40
+ var PALETTE_STYLES = {};
41
+ var ROOM_COLORS = [];
42
+ var PASSES = [];
43
+ var DECOR_PROFILES = {};
44
+ var GROUND_OVERLAYS = [];
45
+ var STATE_PLUGINS = { normal: { apply: () => [] } };
46
+ var WALL_TOP_HEIGHTS = { none: 0 };
47
+ var SEED_KEYS = { rooms: {}, items: {} };
48
+ var stateDistribution = { weights: null, seedKey: "state" };
49
+ function registerItem(def) {
50
+ const h = def.h;
51
+ const item = Object.assign({ front: "+z", window: h <= 0.95, low: h <= 0.5, flat: false, deck: false, rim: 0 }, def);
52
+ CATALOG[def.type] = item;
53
+ return item;
54
+ }
55
+ function registerItems(list) {
56
+ for (const d of list) registerItem(d);
57
+ }
58
+ function registerLibrary(id, library) {
59
+ LIBRARIES[id] = library;
60
+ }
61
+ function registerLibraryAlias(alias, id) {
62
+ LIBRARY_ALIAS[alias] = id;
63
+ }
64
+ function registerTemplate(t) {
65
+ TEMPLATES[t.id] = t;
66
+ return t;
67
+ }
68
+ function registerStyle(style) {
69
+ STYLES[style.id] = style;
70
+ for (const a of style.aliases || []) STYLE_ALIAS[a] = style.id;
71
+ }
72
+ function registerPaletteStyle(id, def) {
73
+ PALETTE_STYLES[id] = def;
74
+ }
75
+ function registerRoomColors(list) {
76
+ for (const d of list) {
77
+ const i = ROOM_COLORS.findIndex((x) => x.type === d.type);
78
+ if (i >= 0) ROOM_COLORS.splice(i, 1);
79
+ ROOM_COLORS.push(d);
80
+ }
81
+ ROOM_COLORS.sort((a, b) => a.order - b.order);
82
+ }
83
+ function registerDecorProfile(styleId, profile) {
84
+ DECOR_PROFILES[styleId] = profile;
85
+ }
86
+ function registerPass(name, run) {
87
+ const i = PASSES.findIndex((p) => p.name === name);
88
+ if (i >= 0) PASSES[i] = { name, run };
89
+ else PASSES.push({ name, run });
90
+ }
91
+ function registerGroundOverlay(rule) {
92
+ GROUND_OVERLAYS.push(rule);
93
+ }
94
+ function registerWallTop(id, height) {
95
+ WALL_TOP_HEIGHTS[id] = height;
96
+ }
97
+ function registerSeedKeys(keys) {
98
+ Object.assign(SEED_KEYS.rooms, keys.rooms);
99
+ Object.assign(SEED_KEYS.items, keys.items);
100
+ }
101
+ function registerState(name, plugin) {
102
+ STATE_PLUGINS[name] = plugin;
103
+ }
104
+ function setStateDistribution(weights, seedKey = "state") {
105
+ stateDistribution.weights = weights;
106
+ stateDistribution.seedKey = seedKey;
107
+ }
108
+ var listStates = () => Object.keys(STATE_PLUGINS);
109
+ function resolveStyleId(id) {
110
+ const s = id ?? "";
111
+ return STYLE_ALIAS[s] ?? s;
112
+ }
113
+ function styleOf(id) {
114
+ return STYLES[id] || STYLES.budget || Object.values(STYLES)[0];
115
+ }
116
+ var RESULT_EXTENSIONS = [];
117
+ var FINDS_PROVIDERS = [];
118
+ function registerResultExtension(name, run, options = {}) {
119
+ const entry = { name, run, after: options.after || [], seedKey: options.seedKey ?? name };
120
+ const i = RESULT_EXTENSIONS.findIndex((x) => x.name === name);
121
+ if (i >= 0) RESULT_EXTENSIONS[i] = entry;
122
+ else RESULT_EXTENSIONS.push(entry);
123
+ }
124
+ function registerFindsProvider(provider) {
125
+ FINDS_PROVIDERS.push(provider);
126
+ }
127
+ var VIEWER_LAYERS = [];
128
+ function registerViewerLayer(layer) {
129
+ const i = VIEWER_LAYERS.findIndex((x) => x.id === layer.id);
130
+ if (i >= 0) VIEWER_LAYERS[i] = layer;
131
+ else VIEWER_LAYERS.push(layer);
132
+ }
133
+
134
+ // src/rng.ts
135
+ function mulberry32(a) {
136
+ return function() {
137
+ a |= 0;
138
+ a = a + 1831565813 | 0;
139
+ let t = Math.imul(a ^ a >>> 15, 1 | a);
140
+ t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
141
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
142
+ };
143
+ }
144
+ function hashString(input) {
145
+ const s = String(input);
146
+ let h = 1779033703 ^ s.length;
147
+ for (let i = 0; i < s.length; i++) {
148
+ h = Math.imul(h ^ s.charCodeAt(i), 3432918353);
149
+ h = h << 13 | h >>> 19;
150
+ }
151
+ h = Math.imul(h ^ h >>> 16, 2246822507);
152
+ h = Math.imul(h ^ h >>> 13, 3266489909);
153
+ return (h ^ h >>> 16) >>> 0;
154
+ }
155
+ var createRng = (seed, key) => mulberry32(hashString(seed + "|" + key));
156
+ function pickWeighted(rng, weights) {
157
+ const keys = Object.keys(weights);
158
+ let total = 0;
159
+ for (const k of keys) total += weights[k];
160
+ let x = rng() * total;
161
+ for (const k of keys) {
162
+ x -= weights[k];
163
+ if (x < 0) return k;
164
+ }
165
+ return keys[keys.length - 1];
166
+ }
167
+ function weightedOrder(candidates, rng) {
168
+ const arr = candidates.slice();
169
+ const out = [];
170
+ while (arr.length) {
171
+ let total = 0;
172
+ for (const c of arr) total += c.weight;
173
+ let x = rng() * total;
174
+ let i = 0;
175
+ for (; i < arr.length - 1; i++) {
176
+ x -= arr[i].weight;
177
+ if (x < 0) break;
178
+ }
179
+ out.push(arr.splice(i, 1)[0]);
180
+ }
181
+ return out;
182
+ }
183
+ function shuffle(arr, rng) {
184
+ const a = arr.slice();
185
+ for (let i = a.length - 1; i > 0; i--) {
186
+ const j = Math.floor(rng() * (i + 1));
187
+ [a[i], a[j]] = [a[j], a[i]];
188
+ }
189
+ return a;
190
+ }
191
+
192
+ // src/extensions.ts
193
+ function orderOf(extensions) {
194
+ const ordered = [];
195
+ const visiting = /* @__PURE__ */ new Set();
196
+ const visit = (extension) => {
197
+ if (ordered.includes(extension)) return;
198
+ if (visiting.has(extension.name)) throw new Error("Result extensions depend on each other: " + extension.name);
199
+ visiting.add(extension.name);
200
+ for (const name of extension.after) {
201
+ const before = extensions.find((x) => x.name === name);
202
+ if (before) visit(before);
203
+ }
204
+ visiting.delete(extension.name);
205
+ ordered.push(extension);
206
+ };
207
+ extensions.forEach(visit);
208
+ return ordered;
209
+ }
210
+ function runResultExtensions(plan, result, seed, options) {
211
+ for (const extension of orderOf(RESULT_EXTENSIONS)) {
212
+ result[extension.name] = extension.run({ plan, result, seed, rng: createRng(seed, extension.seedKey), options });
213
+ }
214
+ }
215
+ function findsFor(query) {
216
+ return answerOf(query) ?? [];
217
+ }
218
+ function findsAnswer(query) {
219
+ return answerOf(query);
220
+ }
221
+ function answerOf(query) {
222
+ for (const provider of FINDS_PROVIDERS) {
223
+ const stacks = provider.findsFor(query);
224
+ if (stacks !== null) return stacks;
225
+ }
226
+ return null;
227
+ }
7
228
 
8
229
  // src/geometry.ts
9
230
  var r2 = (x) => Math.round(x * 100) / 100;
@@ -106,13 +327,39 @@ function normalizeRoom(c, floorIndex) {
106
327
  };
107
328
  }
108
329
  function usableParts(room, extra) {
109
- return room.parts.map((p) => {
110
- const isOpen = (k) => room.parts.some(
111
- (q) => q !== p && (k === 0 ? eq(q[2], p[0]) && Math.min(q[3], p[3]) - Math.max(q[1], p[1]) > 0.3 : k === 2 ? eq(q[0], p[2]) && Math.min(q[3], p[3]) - Math.max(q[1], p[1]) > 0.3 : k === 1 ? eq(q[3], p[1]) && Math.min(q[2], p[2]) - Math.max(q[0], p[0]) > 0.3 : eq(q[1], p[3]) && Math.min(q[2], p[2]) - Math.max(q[0], p[0]) > 0.3)
112
- );
113
- const m = MARGIN + (extra || 0);
114
- return [p[0] + (isOpen(0) ? 0 : m), p[1] + (isOpen(1) ? 0 : m), p[2] - (isOpen(2) ? 0 : m), p[3] - (isOpen(3) ? 0 : m)];
115
- });
330
+ const m = MARGIN + (extra || 0);
331
+ const out = [];
332
+ for (const p of room.parts) {
333
+ out.push([p[0] + m, p[1] + m, p[2] - m, p[3] - m]);
334
+ for (let k = 0; k < 4; k++) {
335
+ const spans = [];
336
+ for (const q of room.parts) {
337
+ if (q === p) continue;
338
+ if (k === 0 && eq(q[2], p[0])) spans.push([Math.max(q[1], p[1]), Math.min(q[3], p[3])]);
339
+ else if (k === 2 && eq(q[0], p[2])) spans.push([Math.max(q[1], p[1]), Math.min(q[3], p[3])]);
340
+ else if (k === 1 && eq(q[3], p[1])) spans.push([Math.max(q[0], p[0]), Math.min(q[2], p[2])]);
341
+ else if (k === 3 && eq(q[1], p[3])) spans.push([Math.max(q[0], p[0]), Math.min(q[2], p[2])]);
342
+ }
343
+ spans.sort((x, y) => x[0] - y[0]);
344
+ const merged = [];
345
+ for (const span of spans) {
346
+ const last = merged[merged.length - 1];
347
+ if (last && span[0] <= last[1] + TOL) last[1] = Math.max(last[1], span[1]);
348
+ else merged.push([span[0], span[1]]);
349
+ }
350
+ for (const [a, b] of merged) {
351
+ if (b - a <= 0.3) continue;
352
+ const x0 = a + m;
353
+ const x1 = b - m;
354
+ if (x1 <= x0) continue;
355
+ if (k === 0) out.push([p[0], x0, p[0] + m, x1]);
356
+ else if (k === 2) out.push([p[2] - m, x0, p[2], x1]);
357
+ else if (k === 1) out.push([x0, p[1], x1, p[1] + m]);
358
+ else out.push([x0, p[3] - m, x1, p[3]]);
359
+ }
360
+ }
361
+ }
362
+ return out;
116
363
  }
117
364
  var inRect = (room, u, v) => room.parts.some((p) => u > p[0] + EPS && u < p[2] - EPS && v > p[1] + EPS && v < p[3] - EPS);
118
365
  function buildWalls(rooms, doors, openPairs, floorIndex) {
@@ -167,7 +414,7 @@ function buildWalls(rooms, doors, openPairs, floorIndex) {
167
414
  pieces = n;
168
415
  }
169
416
  for (const [x, y] of pieces) {
170
- const isOpen = (t) => t === "balcony" || t === "terrace";
417
+ const isOpen = (t) => t === "balcony" || t === "terrace" || t === "porch";
171
418
  const isVoid = typeOf[s.A] === "void" || typeOf[s.B] === "void";
172
419
  if (isVoid && (s.A === "outside" || s.B === "outside")) continue;
173
420
  const railing = isVoid || isOpen(typeOf[s.A]) && s.B === "outside" || isOpen(typeOf[s.B]) && s.A === "outside";
@@ -190,6 +437,39 @@ function buildWalls(rooms, doors, openPairs, floorIndex) {
190
437
  return out;
191
438
  }
192
439
 
440
+ // src/parking.ts
441
+ var STEP = 0.05;
442
+ var MAX_CANDIDATES = 80;
443
+ function carCandidates(slot, item, car) {
444
+ const front = [slot.frontU, slot.frontV];
445
+ const zone = slot._cell.R;
446
+ const alongV = Math.abs(front[1]) === 1;
447
+ const [extentU, extentV] = extent([item.w, item.d], front);
448
+ const across = alongV ? extentU : extentV;
449
+ const along = alongV ? extentV : extentU;
450
+ const a = alongV ? 0 : 1;
451
+ const b = alongV ? 1 : 0;
452
+ const driverSign = alongV ? front[1] : -front[0];
453
+ const frontSign = alongV ? front[1] : front[0];
454
+ const lowClear = driverSign < 0 ? car.driver : car.side;
455
+ const highClear = driverSign < 0 ? car.side : car.driver;
456
+ const lo = zone[a] + lowClear;
457
+ const hi = zone[a + 2] - highClear - across;
458
+ const alongLo = zone[b] + car.end;
459
+ const alongHi = zone[b + 2] - car.end - along;
460
+ if (hi < lo - 1e-6 || alongHi < alongLo - 1e-6) return [];
461
+ const preferred = (alongV ? slot.x : slot.z) - across / 2;
462
+ const alongStart = frontSign < 0 ? alongHi : alongLo;
463
+ const positions = [];
464
+ for (let p = lo; p <= hi + 1e-6; p += STEP) positions.push(Math.min(p, hi));
465
+ positions.sort((x, y) => Math.abs(x - preferred) - Math.abs(y - preferred));
466
+ return positions.slice(0, MAX_CANDIDATES).map((p) => {
467
+ const rect = alongV ? [p, alongStart, p + across, alongStart + along] : [alongStart, p, alongStart + along, p + across];
468
+ const clear = alongV ? [p - lowClear, alongStart - car.end, p + across + highClear, alongStart + along + car.end] : [alongStart - car.end, p - lowClear, alongStart + along + car.end, p + across + highClear];
469
+ return { rect, clear };
470
+ });
471
+ }
472
+
193
473
  // src/queries.ts
194
474
  function zonesOfFloor(plan, floorIndex) {
195
475
  const out = [];
@@ -199,6 +479,7 @@ function zonesOfFloor(plan, floorIndex) {
199
479
  for (const p of plan.doors) if (p.floor === 0 && p.exterior) out.push(...p.zones);
200
480
  for (const g of plan.gates) out.push(...g.zones);
201
481
  for (const c of plan.paths) out.push(...c.rects);
482
+ out.push(...plan.reservations);
202
483
  }
203
484
  return out;
204
485
  }
@@ -220,7 +501,10 @@ function windowZone(j, outside) {
220
501
  if (j.axis === "u") return n[1] > 0 ? [u - h, v - MARGIN, u + h, v + depth] : [u - h, v - depth, u + h, v + MARGIN];
221
502
  return n[0] > 0 ? [u - MARGIN, v - h, u + depth, v + h] : [u - depth, v - h, u + MARGIN, v + h];
222
503
  }
223
- var blocksWindow = (it, win) => !it.window || it.h > 1 + EPS || it.h > win.y0 - 0.05;
504
+ function blocksWindow(it, win, overheadOff) {
505
+ const height = Math.max(it.h, it.windowHeight || 0, it.overhead && !overheadOff ? OVERHEAD_TOP : 0);
506
+ return !it.window || it.h > 1 + EPS || height > win.y0 - 0.05 + EPS;
507
+ }
224
508
  function windowsOfRoom(plan, room) {
225
509
  if (!room.outdoor) return plan.windows.filter((j) => j.room === room.id);
226
510
  return plan.windows.filter(
@@ -245,8 +529,22 @@ function isOpenSide(plan, room, pu, pv) {
245
529
  }
246
530
 
247
531
  // src/grid.ts
248
- function createGrid(plan, room, ignored) {
249
- const gs = room.outdoor ? 0.3 : 0.2;
532
+ function wallBodies(plan, floor) {
533
+ const cache = plan.wallBodies = plan.wallBodies || {};
534
+ if (cache[floor]) return cache[floor];
535
+ const doors = plan.doors.filter((p) => p.floor === floor);
536
+ return cache[floor] = plan.walls.filter((p) => p.floor === floor && (p.type === "wall" || p.type === "railing")).map((p) => {
537
+ const a0 = p.axis === "u" ? p.u0 : p.v0;
538
+ const a1 = p.axis === "u" ? p.u1 : p.v1;
539
+ const pos = p.axis === "u" ? p.v0 : p.u0;
540
+ const atDoor = (x) => doors.some((q) => q.axis === p.axis && eq(q.pos, pos) && (eq(q.at - q.width / 2, x) || eq(q.at + q.width / 2, x)));
541
+ const e0 = atDoor(a0) ? 0 : MARGIN;
542
+ const e1 = atDoor(a1) ? 0 : MARGIN;
543
+ return p.axis === "u" ? [a0 - e0, pos - MARGIN, a1 + e1, pos + MARGIN] : [pos - MARGIN, a0 - e0, pos + MARGIN, a1 + e1];
544
+ });
545
+ }
546
+ function createGrid(plan, room, ignored, radius = plan.playerRadius) {
547
+ const gs = room.outdoor ? 0.3 : 0.1;
250
548
  let u0 = 1e9;
251
549
  let v0 = 1e9;
252
550
  let u1 = -1e9;
@@ -260,12 +558,18 @@ function createGrid(plan, room, ignored) {
260
558
  const nx = Math.ceil((u1 - u0) / gs);
261
559
  const nz = Math.ceil((v1 - v0) / gs);
262
560
  const insideCells = new Uint8Array(nx * nz);
263
- const insets = usableParts(room, 0.08);
561
+ const insets = room.outdoor ? usableParts(room, 0.08) : room.parts;
562
+ const bodies = room.outdoor ? null : wallBodies(plan, room.floor);
563
+ const tooClose = (u, v) => bodies.some((r) => {
564
+ const dx = Math.max(r[0] - u, 0, u - r[2]);
565
+ const dz = Math.max(r[1] - v, 0, v - r[3]);
566
+ return dx * dx + dz * dz < radius * radius - 1e-9;
567
+ });
264
568
  for (let j = 0; j < nz; j++)
265
569
  for (let i = 0; i < nx; i++) {
266
570
  const u = u0 + (i + 0.5) * gs;
267
571
  const v = v0 + (j + 0.5) * gs;
268
- if (insets.some((r) => u >= r[0] && u <= r[2] && v >= r[1] && v <= r[3])) insideCells[j * nx + i] = 1;
572
+ if (insets.some((r) => u >= r[0] && u <= r[2] && v >= r[1] && v <= r[3]) && (room.outdoor || !tooClose(u, v))) insideCells[j * nx + i] = 1;
269
573
  }
270
574
  const entries = [];
271
575
  for (const ab of openingsOfFloor(plan, room.floor))
@@ -276,6 +580,22 @@ function createGrid(plan, room, ignored) {
276
580
  const j = Math.floor((pt[1] - v0) / gs);
277
581
  if (i >= 0 && j >= 0 && i < nx && j < nz && insideCells[j * nx + i]) entries.push([i, j]);
278
582
  }
583
+ if (!room.outdoor)
584
+ for (const pair of plan.openPairs[room.floor] || []) {
585
+ const otherId = pair[0] === room.id ? pair[1] : pair[1] === room.id ? pair[0] : null;
586
+ const other = otherId && plan.rooms.find((c) => c.id === otherId && c.floor === room.floor);
587
+ if (!other) continue;
588
+ const edge = sharedEdge(room, other, null);
589
+ if (!edge || edge.a1 - edge.a0 > 2) continue;
590
+ const mid = (edge.a0 + edge.a1) / 2;
591
+ for (const offset of [-0.4, 0.4]) {
592
+ const pt = edge.axis === "u" ? [mid, edge.pos + offset] : [edge.pos + offset, mid];
593
+ if (!inRect(room, pt[0], pt[1])) continue;
594
+ const i = Math.floor((pt[0] - u0) / gs);
595
+ const j = Math.floor((pt[1] - v0) / gs);
596
+ if (i >= 0 && j >= 0 && i < nx && j < nz && insideCells[j * nx + i]) entries.push([i, j]);
597
+ }
598
+ }
279
599
  const R = inner(room.parts[0]);
280
600
  return { gs, u0, v0, nx, nz, inside: insideCells, entries, center: [(R[0] + R[2]) / 2, (R[1] + R[3]) / 2] };
281
601
  }
@@ -335,154 +655,12 @@ function reachable(g, solids, inflation) {
335
655
  return true;
336
656
  }
337
657
 
338
- // src/registry.ts
339
- var CATALOG = {};
340
- var LIBRARIES = {};
341
- var LIBRARY_ALIAS = {};
342
- var TEMPLATES = {};
343
- var STYLES = {};
344
- var STYLE_ALIAS = {};
345
- var PALETTE_STYLES = {};
346
- var ROOM_COLORS = [];
347
- var PASSES = [];
348
- var DECOR_PROFILES = {};
349
- var GROUND_OVERLAYS = [];
350
- var STATE_PLUGINS = { normal: { apply: () => [] } };
351
- var WALL_TOP_HEIGHTS = { none: 0 };
352
- var SEED_KEYS = { rooms: {}, items: {} };
353
- var stateDistribution = { weights: null, seedKey: "state" };
354
- function registerItem(def) {
355
- const h = def.h;
356
- const item = Object.assign({ front: "+z", window: h <= 0.95, low: h <= 0.5, flat: false, deck: false, rim: 0 }, def);
357
- CATALOG[def.type] = item;
358
- return item;
359
- }
360
- function registerItems(list) {
361
- for (const d of list) registerItem(d);
362
- }
363
- function registerLibrary(id, library) {
364
- LIBRARIES[id] = library;
365
- }
366
- function registerLibraryAlias(alias, id) {
367
- LIBRARY_ALIAS[alias] = id;
368
- }
369
- function registerTemplate(t) {
370
- TEMPLATES[t.id] = t;
371
- return t;
372
- }
373
- function registerStyle(style) {
374
- STYLES[style.id] = style;
375
- for (const a of style.aliases || []) STYLE_ALIAS[a] = style.id;
376
- }
377
- function registerPaletteStyle(id, def) {
378
- PALETTE_STYLES[id] = def;
379
- }
380
- function registerRoomColors(list) {
381
- for (const d of list) {
382
- const i = ROOM_COLORS.findIndex((x) => x.type === d.type);
383
- if (i >= 0) ROOM_COLORS.splice(i, 1);
384
- ROOM_COLORS.push(d);
385
- }
386
- ROOM_COLORS.sort((a, b) => a.order - b.order);
387
- }
388
- function registerDecorProfile(styleId, profile) {
389
- DECOR_PROFILES[styleId] = profile;
390
- }
391
- function registerPass(name, run) {
392
- const i = PASSES.findIndex((p) => p.name === name);
393
- if (i >= 0) PASSES[i] = { name, run };
394
- else PASSES.push({ name, run });
395
- }
396
- function registerGroundOverlay(rule) {
397
- GROUND_OVERLAYS.push(rule);
398
- }
399
- function registerWallTop(id, height) {
400
- WALL_TOP_HEIGHTS[id] = height;
401
- }
402
- function registerSeedKeys(keys) {
403
- Object.assign(SEED_KEYS.rooms, keys.rooms);
404
- Object.assign(SEED_KEYS.items, keys.items);
405
- }
406
- function registerState(name, plugin) {
407
- STATE_PLUGINS[name] = plugin;
408
- }
409
- function setStateDistribution(weights, seedKey = "state") {
410
- stateDistribution.weights = weights;
411
- stateDistribution.seedKey = seedKey;
412
- }
413
- var listStates = () => Object.keys(STATE_PLUGINS);
414
- function resolveStyleId(id) {
415
- const s = id ?? "";
416
- return STYLE_ALIAS[s] ?? s;
417
- }
418
- function styleOf(id) {
419
- return STYLES[id] || STYLES.budget || Object.values(STYLES)[0];
420
- }
421
-
422
- // src/rng.ts
423
- function mulberry32(a) {
424
- return function() {
425
- a |= 0;
426
- a = a + 1831565813 | 0;
427
- let t = Math.imul(a ^ a >>> 15, 1 | a);
428
- t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
429
- return ((t ^ t >>> 14) >>> 0) / 4294967296;
430
- };
431
- }
432
- function hashString(input) {
433
- const s = String(input);
434
- let h = 1779033703 ^ s.length;
435
- for (let i = 0; i < s.length; i++) {
436
- h = Math.imul(h ^ s.charCodeAt(i), 3432918353);
437
- h = h << 13 | h >>> 19;
438
- }
439
- h = Math.imul(h ^ h >>> 16, 2246822507);
440
- h = Math.imul(h ^ h >>> 13, 3266489909);
441
- return (h ^ h >>> 16) >>> 0;
442
- }
443
- var createRng = (seed, key) => mulberry32(hashString(seed + "|" + key));
444
- function pickWeighted(rng, weights) {
445
- const keys = Object.keys(weights);
446
- let total = 0;
447
- for (const k of keys) total += weights[k];
448
- let x = rng() * total;
449
- for (const k of keys) {
450
- x -= weights[k];
451
- if (x < 0) return k;
452
- }
453
- return keys[keys.length - 1];
454
- }
455
- function weightedOrder(candidates, rng) {
456
- const arr = candidates.slice();
457
- const out = [];
458
- while (arr.length) {
459
- let total = 0;
460
- for (const c of arr) total += c.weight;
461
- let x = rng() * total;
462
- let i = 0;
463
- for (; i < arr.length - 1; i++) {
464
- x -= arr[i].weight;
465
- if (x < 0) break;
466
- }
467
- out.push(arr.splice(i, 1)[0]);
468
- }
469
- return out;
470
- }
471
- function shuffle(arr, rng) {
472
- const a = arr.slice();
473
- for (let i = a.length - 1; i > 0; i--) {
474
- const j = Math.floor(rng() * (i + 1));
475
- [a[i], a[j]] = [a[j], a[i]];
476
- }
477
- return a;
478
- }
479
-
480
658
  // src/fill.ts
481
659
  function candidatesOf(cats) {
482
660
  const out = [];
483
661
  for (const [cat, weight] of Object.entries(cats)) {
484
662
  const exact = cat[0] === "=";
485
- const its = Object.values(CATALOG).filter((i) => !i.decor && (exact ? i.type === cat.slice(1) : i.category === cat || i.type === cat));
663
+ const its = Object.values(CATALOG).filter((i) => !i.decor && (exact ? i.type === cat.slice(1) : !i.exactOnly && (i.category === cat || i.type === cat)));
486
664
  for (const it of its) out.push({ it, weight: weight / its.length });
487
665
  }
488
666
  return out;
@@ -506,6 +684,7 @@ function itemRect(slot, it, shift) {
506
684
  }
507
685
  return [u0, v0, u0 + extentU, v0 + extentV];
508
686
  }
687
+ var RELAXED_RADIUS = 0.12;
509
688
  var MESH_FRONT = { "+z": [0, 1], "-z": [0, -1], "+x": [1, 0], "-x": [-1, 0] };
510
689
  function rotationFor(f, front) {
511
690
  const meshFront = MESH_FRONT[front] || [0, 1];
@@ -523,9 +702,10 @@ function fillRoom(plan, room, slots2, rng) {
523
702
  const rims = [];
524
703
  const inners = usableParts(room, 0);
525
704
  const grid = createGrid(plan, room);
705
+ const relaxedGrid = room.outdoor ? grid : createGrid(plan, room, null, RELAXED_RADIUS);
526
706
  const zones = zonesOfFloor(plan, room.floor);
527
707
  const windowZones = windowsOfRoom(plan, room).map((j) => ({ z: windowZone(j, room.outdoor), j }));
528
- const inflation = room.outdoor ? 0.15 : 0.12;
708
+ const inflation = room.outdoor ? 0.15 : plan.playerRadius;
529
709
  const units = [];
530
710
  const seen = {};
531
711
  for (const s of slots2) {
@@ -544,7 +724,7 @@ function fillRoom(plan, room, slots2, rng) {
544
724
  const dependencies = (u) => u.members.flatMap((m) => [m.requires, m.on]).filter(Boolean).filter((id) => slots2.some((s) => s.id === id && !u.members.includes(s)));
545
725
  const done = /* @__PURE__ */ new Set();
546
726
  const ready = (u) => dependencies(u).every((id) => done.has(id));
547
- const make = (s, it, r, parent) => {
727
+ const make = (s, it, r, parent, overheadOff) => {
548
728
  const f = [s.frontU, s.frontV];
549
729
  const id = room.id + "." + s.id;
550
730
  const m = {
@@ -573,13 +753,18 @@ function fillRoom(plan, room, slots2, rng) {
573
753
  _r: r,
574
754
  _seedId: SEED_KEYS.items[id] ?? id
575
755
  };
756
+ if (it.overhead) m.overhead = !overheadOff;
757
+ if (it.category === "car") {
758
+ m.driver = [f[1], -f[0]];
759
+ m.carClearance = plan.carClearance;
760
+ }
576
761
  if (parent && s.anchor === "inside") m.y = r2(parent.y || 0);
577
762
  if (room.link) m.link = { from: room.link[0], to: room.link[1] };
578
763
  state[s.id] = m;
579
764
  items.push(m);
580
765
  return m;
581
766
  };
582
- const tryFit = (s, relax) => {
767
+ const tryFit = (s, relax, overheadOff) => {
583
768
  const cands = weightedOrder(candidatesOf(s.cats), rng);
584
769
  if (s.anchor === "on" || s.anchor === "inside") {
585
770
  const parent = state[s.on];
@@ -602,19 +787,22 @@ function fillRoom(plan, room, slots2, rng) {
602
787
  const shifts = [0];
603
788
  for (let k = 1; k * 0.1 <= slide + 1e-9; k++) shifts.push(k * 0.1, -k * 0.1);
604
789
  let found = null;
605
- for (const shift of shifts) {
606
- const r3 = itemRect(s, it, shift);
790
+ const car = it.category === "car" && !relax;
791
+ const tries = car ? carCandidates(s, it, plan.carClearance) : shifts.map((shift) => ({ rect: itemRect(s, it, shift) }));
792
+ for (const attempt of tries) {
793
+ const r3 = attempt.rect;
607
794
  if (!insideUnion(r3, inners)) continue;
795
+ if (car && (!insideUnion(attempt.clear, inners) || solids.some((o) => overlaps(attempt.clear, o)))) continue;
608
796
  if (it.flat) {
609
797
  found = { rect: r3 };
610
798
  break;
611
799
  }
612
800
  if (zones.some((z) => overlaps(r3, z))) continue;
613
- if (windowZones.some(({ z, j }) => blocksWindow(it, j) && overlaps(r3, z))) continue;
801
+ if (windowZones.some(({ z, j }) => blocksWindow(it, j, overheadOff) && overlaps(r3, z))) continue;
614
802
  if (solids.some((o) => overlaps(r3, o))) continue;
615
803
  if (!it.deck && rims.some((a) => overlaps(r3, a))) continue;
616
- let cl2 = null;
617
- if (!relax && s.clearance > 0 && !it.low) {
804
+ let cl2 = car ? attempt.clear : null;
805
+ if (!car && !relax && s.clearance > 0 && !it.low) {
618
806
  cl2 = clearRect(r3, f, s.clearance);
619
807
  if (!insideUnion(cl2, inners)) continue;
620
808
  if (items.some((m2) => !m2.low && !m2.flat && !m2.on && overlaps(cl2, m2._r))) continue;
@@ -625,14 +813,14 @@ function fillRoom(plan, room, slots2, rng) {
625
813
  rimRect2 = [r3[0] - it.rim, r3[1] - it.rim, r3[2] + it.rim, r3[3] + it.rim];
626
814
  if (!insideUnion(rimRect2, inners) || solids.some((o) => overlaps(rimRect2, o)) || zones.some((z) => overlaps(rimRect2, z))) continue;
627
815
  }
628
- if (!reachable(grid, solids.concat([r3]), inflation)) continue;
816
+ if (!(relax ? reachable(relaxedGrid, solids.concat([r3]), RELAXED_RADIUS) : reachable(grid, solids.concat([r3]), inflation))) continue;
629
817
  found = { rect: r3, cl: cl2, rimRect: rimRect2 };
630
818
  break;
631
819
  }
632
820
  if (!found) continue;
633
821
  const { rect: r, cl, rimRect } = found;
634
822
  if (it.flat) return make(s, it, r, null);
635
- const m = make(s, it, r, null);
823
+ const m = make(s, it, r, null, overheadOff);
636
824
  if (cl) reserves.push(cl);
637
825
  if (rimRect) rims.push(rimRect);
638
826
  solids.push(r);
@@ -655,9 +843,16 @@ function fillRoom(plan, room, slots2, rng) {
655
843
  break;
656
844
  }
657
845
  }
846
+ if (!ok)
847
+ for (const m of applicable) {
848
+ if (tryFit(m, false, true)) {
849
+ ok = true;
850
+ break;
851
+ }
852
+ }
658
853
  if (!ok && u.required)
659
854
  for (const m of applicable) {
660
- if (tryFit(m, true)) {
855
+ if (tryFit(m, true, true)) {
661
856
  ok = true;
662
857
  break;
663
858
  }
@@ -706,6 +901,8 @@ var placementKit = {
706
901
  createGrid,
707
902
  clearRect,
708
903
  pickWeighted,
904
+ createRng,
905
+ roomSeedKey: (room) => room.seedKey ?? SEED_KEYS.rooms[room.id] ?? room.id,
709
906
  shuffle,
710
907
  facingRotation: (f, type) => rotationFor(f, CATALOG[type].front),
711
908
  cutIntervals,
@@ -769,6 +966,10 @@ function createPlacementContext(plan, items) {
769
966
  const zones = zonesOfFloor(plan, room.floor);
770
967
  const occupied = itemsOfRoom(room);
771
968
  const windowZones = windowsOfRoom(plan, room).map((j) => ({ z: windowZone(j, room.outdoor), j }));
969
+ const landings = it0.flat ? [] : items.filter((m) => m.type === "stairs" && m.floor === room.floor && m.aabb).map((m) => {
970
+ const r = m.aabb;
971
+ return m.frontV === 1 ? [r[0], r[3], r[2], r[3] + 1] : m.frontV === -1 ? [r[0], r[1] - 1, r[2], r[1]] : m.frontU === 1 ? [r[2], r[1], r[2] + 1, r[3]] : [r[0] - 1, r[1], r[0], r[3]];
972
+ });
772
973
  const cand = opts.cands ? opts.cands.slice() : [];
773
974
  if (!opts.cands)
774
975
  for (const p of inners) {
@@ -783,9 +984,9 @@ function createPlacementContext(plan, items) {
783
984
  cand.push([x, z, x + extentU, z + extentV]);
784
985
  }
785
986
  }
786
- for (const r of shuffle(cand, rng2)) {
987
+ for (const r of opts.ordered ? cand : shuffle(cand, rng2)) {
787
988
  if (!insideUnion(r, inners)) continue;
788
- if (zones.some((z) => overlaps(r, z))) continue;
989
+ if (zones.some((z) => overlaps(r, z)) || landings.some((z) => overlaps(r, z))) continue;
789
990
  if (windowZones.some(({ z, j }) => blocksWindow(it, j) && overlaps(r, z))) continue;
790
991
  if (solidList.some((o) => overlaps(r, o))) continue;
791
992
  if (opts.flat) {
@@ -794,7 +995,7 @@ function createPlacementContext(plan, items) {
794
995
  }
795
996
  if (occupied.some((m) => !m.low && !m.flat && !m.on && !m.decor && m.clearance > 0 && overlaps(r, clearRect(m._r, [m.frontU, m.frontV], m.clearance)))) continue;
796
997
  if (opts.without && opts.without(r)) continue;
797
- if (!reachable(opts.railing || gridOf(room), solidList.concat([r]), 0.16)) continue;
998
+ if (!reachable(opts.railing || gridOf(room), solidList.concat([r]), room.outdoor ? 0.16 : plan.playerRadius)) continue;
798
999
  return r;
799
1000
  }
800
1001
  return null;
@@ -875,6 +1076,129 @@ function translate(ns, key, locale = "en", vars) {
875
1076
  return vars ? raw.replace(/\{(\w+)\}/g, (m, k) => k in vars ? String(vars[k]) : m) : raw;
876
1077
  }
877
1078
 
1079
+ // src/paths.ts
1080
+ var CLOSE = 1e-6;
1081
+ var rounded = (r) => r.map(r2);
1082
+ var touches = (a, r) => a[0] <= r[2] + CLOSE && a[2] >= r[0] - CLOSE && a[1] <= r[3] + CLOSE && a[3] >= r[1] - CLOSE;
1083
+ function addExteriorDoorPaths(plan, c) {
1084
+ const { gates, paths, zones, setback, marginLeft, marginRight, backyard, w } = c;
1085
+ const social = gates.find((g) => g.type === "pedestrian");
1086
+ const socialPath = social ? paths.find((p) => p.from === social.id && p.type === "pedestrian") : null;
1087
+ const parkingZone = zones.find((z) => z.id === "parking");
1088
+ const parking = parkingZone ? parkingZone.parts[0] : null;
1089
+ const midY = setback > 0 ? -setback / 2 : 0;
1090
+ const joinToSocial = (x, width, rects, points) => {
1091
+ if (!socialPath) return;
1092
+ const h = width / 2;
1093
+ const horizontal = socialPath.rects.find((r) => midY >= r[1] - CLOSE && midY <= r[3] + CLOSE && r[2] - r[0] > r[3] - r[1]) || socialPath.rects[0];
1094
+ const targetX = x < horizontal[0] ? horizontal[0] + h : x > horizontal[2] ? horizontal[2] - h : x;
1095
+ if (Math.abs(targetX - x) > CLOSE) {
1096
+ rects.push([Math.min(x, targetX) - h, midY - h, Math.max(x, targetX) + h, midY + h]);
1097
+ points.push([targetX, midY]);
1098
+ }
1099
+ };
1100
+ for (const door of plan.doors.filter((q) => q.floor === 0 && q.exterior)) {
1101
+ if (paths.some((p) => p.to === door.id)) continue;
1102
+ const half = Math.max(1, Math.min(door.width, 2)) / 2;
1103
+ const rects = [];
1104
+ const points = [];
1105
+ const isTarget = c.target && door.a === c.target.a && door.side === c.target.side;
1106
+ let fromId = social ? social.id : null;
1107
+ let width = 1;
1108
+ const id = isTarget ? "entrance_path" : "path_" + door.id;
1109
+ if (door.side === "front") {
1110
+ if (setback > 0) {
1111
+ const stub = Math.min(1, setback);
1112
+ rects.push([door.u - half, -stub, door.u + half, 0]);
1113
+ points.push([door.u, 0], [door.u, -stub]);
1114
+ if (-stub > midY) {
1115
+ rects.push([door.u - width / 2, midY - width / 2, door.u + width / 2, -stub]);
1116
+ points.push([door.u, midY]);
1117
+ }
1118
+ joinToSocial(door.u, width, rects, points);
1119
+ } else {
1120
+ rects.push([door.u - half, -1.2, door.u + half, 0]);
1121
+ points.push([door.u, 0], [door.u, -1.2]);
1122
+ }
1123
+ } else {
1124
+ const lanes = [];
1125
+ if (marginLeft > 0.5) lanes.push({ x: -marginLeft / 2, width: marginLeft, side: "left" });
1126
+ if (marginRight > 0.5) lanes.push({ x: w + marginRight / 2, width: marginRight, side: "right" });
1127
+ const targets = paths.flatMap((p) => p.rects).concat(parking ? [parking] : []);
1128
+ let best = null;
1129
+ for (const lane of lanes) {
1130
+ if (door.side === "left" && lane.side !== "left" || door.side === "right" && lane.side !== "right") continue;
1131
+ const laneWidth = Math.min(0.9, lane.width - 0.1);
1132
+ const h = laneWidth / 2;
1133
+ const rearStrip = Math.min(0.8, backyard);
1134
+ const rearY = door.v + rearStrip + laneWidth / 2;
1135
+ const overlapsParking = !!parking && lane.x + h > parking[0] && lane.x - h < parking[2];
1136
+ const frontY = overlapsParking || setback <= 0 ? 0 : -0.6;
1137
+ let frontStub = null;
1138
+ if (!overlapsParking && setback > 0) {
1139
+ const dir = social && social.u < lane.x ? -1 : 1;
1140
+ const band = [-0.6 - h, -0.6 + h];
1141
+ let nearestX = social ? social.u : lane.x;
1142
+ let nearestGap = Infinity;
1143
+ for (const r of targets) {
1144
+ if (!(r[1] <= band[1] && r[3] >= band[0])) continue;
1145
+ const edge = dir < 0 ? r[2] : r[0];
1146
+ const gap = Math.abs(edge - lane.x);
1147
+ if ((dir < 0 ? edge <= lane.x : edge >= lane.x) && gap < nearestGap) {
1148
+ nearestGap = gap;
1149
+ nearestX = edge;
1150
+ }
1151
+ }
1152
+ frontStub = [Math.min(lane.x, nearestX) - (dir < 0 ? 0 : h), -0.6 - h, Math.max(lane.x, nearestX) + (dir < 0 ? h : 0), -0.6 + h];
1153
+ }
1154
+ const vertical = [lane.x - h, frontY - (frontY < 0 ? h : 0), lane.x + h, (door.side === "back" ? rearY : door.v) + laneWidth / 2];
1155
+ const horizontal = door.side === "back" ? [Math.min(door.u, lane.x) - half, rearY - laneWidth / 2, Math.max(door.u, lane.x) + h, rearY + laneWidth / 2] : null;
1156
+ const cost = (horizontal ? horizontal[2] - horizontal[0] : 0) + (frontStub ? frontStub[2] - frontStub[0] : 0);
1157
+ if (!best || cost < best.cost) best = { lane, laneWidth, half: h, vertical, horizontal, frontStub, cost, rearStrip, rearY, frontY, overlapsParking };
1158
+ }
1159
+ if (best) {
1160
+ const { lane, laneWidth, half: h, vertical, horizontal, frontStub, rearStrip, rearY, frontY, overlapsParking } = best;
1161
+ width = laneWidth;
1162
+ if (overlapsParking) fromId = (gates.find((g) => g.type === "garage") || social || {}).id || null;
1163
+ if (door.side === "back") {
1164
+ rects.push([door.u - half, door.v, door.u + half, door.v + rearStrip + laneWidth], horizontal, vertical);
1165
+ points.push([door.u, door.v], [door.u, rearY], [lane.x, rearY], [lane.x, frontY]);
1166
+ } else {
1167
+ const x0 = door.side === "left" ? lane.x - h : w;
1168
+ const x1 = door.side === "left" ? 0 : lane.x + h;
1169
+ rects.push([Math.min(x0, x1), door.v - half, Math.max(x0, x1), door.v + half], vertical);
1170
+ points.push([door.u, door.v], [lane.x, door.v], [lane.x, frontY]);
1171
+ }
1172
+ if (frontStub) {
1173
+ rects.push(frontStub);
1174
+ points.push([frontStub[0] < lane.x ? frontStub[0] : frontStub[2], -0.6]);
1175
+ }
1176
+ } else {
1177
+ const stub = 1;
1178
+ if (door.side === "back") rects.push([door.u - half, door.v, door.u + half, door.v + Math.min(stub, backyard)]);
1179
+ else rects.push(door.side === "left" ? [-Math.min(stub, marginLeft), door.v - half, 0, door.v + half] : [w, door.v - half, w + Math.min(stub, marginRight), door.v + half]);
1180
+ points.push([door.u, door.v]);
1181
+ }
1182
+ }
1183
+ const reached = rects.slice();
1184
+ const pool = paths.flatMap((p) => p.rects).concat(parking ? [parking] : []);
1185
+ let grows = true;
1186
+ while (grows) {
1187
+ grows = false;
1188
+ for (const r of pool) {
1189
+ if (!reached.includes(r) && reached.some((a) => touches(a, r))) {
1190
+ reached.push(r);
1191
+ grows = true;
1192
+ }
1193
+ }
1194
+ }
1195
+ const inside = (g) => reached.some((r) => g.u >= r[0] - CLOSE && g.u <= r[2] + CLOSE && g.v >= r[1] - CLOSE && g.v <= r[3] + CLOSE);
1196
+ const gate = gates.find((q) => q.type === "pedestrian" && inside(q)) || gates.find((q) => q.type !== "cargo" && inside(q));
1197
+ if (gate) fromId = gate.id;
1198
+ paths.push({ id, type: "pedestrian", from: fromId, to: door.id, width, rects: rects.map(rounded), points: points.map((q) => q.map(r2)) });
1199
+ }
1200
+ }
1201
+
878
1202
  // src/zoning.ts
879
1203
  var ZONES = {};
880
1204
  var PERMEABLE_GROUNDS = /* @__PURE__ */ new Set();
@@ -1006,14 +1330,34 @@ function buildPlan(tpl, wIn, dIn, opIn, bc = {}) {
1006
1330
  const styleId = resolveStyleId(op.style || (typeof tpl.style === "function" ? tpl.style(op) : tpl.style) || "budget");
1007
1331
  const FLOOR_DEFS = floorsOf(tpl, op);
1008
1332
  const style = styleOf(styleId);
1009
- const plan = { w, d, tpl, floorDefs: FLOOR_DEFS, style: styleId, floors: [], rooms: [], zones: [], doors: [], windows: [], walls: [], gates: [], paths: [], lot: null, openPairs: {} };
1333
+ const plan = {
1334
+ w,
1335
+ d,
1336
+ tpl,
1337
+ floorDefs: FLOOR_DEFS,
1338
+ style: styleId,
1339
+ floors: [],
1340
+ rooms: [],
1341
+ zones: [],
1342
+ doors: [],
1343
+ windows: [],
1344
+ walls: [],
1345
+ gates: [],
1346
+ paths: [],
1347
+ lot: null,
1348
+ openPairs: {},
1349
+ reservations: [],
1350
+ options: op,
1351
+ playerRadius: op.playerRadius ?? PLAYER_RADIUS,
1352
+ carClearance: Object.assign({}, CAR_CLEARANCE, op.carClearance)
1353
+ };
1010
1354
  FLOOR_DEFS.forEach((A, floorIndex) => {
1011
1355
  const floorNumber = tpl.singleFloor ? op.floor || 3 : floorIndex + 1;
1012
1356
  plan.floors.push({
1013
1357
  index: floorIndex,
1014
1358
  name: tpl.singleFloor ? "floor_n" : A.label,
1015
1359
  number: tpl.singleFloor ? floorNumber : void 0,
1016
- y0: tpl.singleFloor ? (floorNumber - 1) * A.ceiling : floorIndex * A.ceiling,
1360
+ y0: tpl.singleFloor ? (floorNumber - 1) * A.ceiling : tpl.cumulativeCeilings ? FLOOR_DEFS.slice(0, floorIndex).reduce((t, q) => t + q.ceiling, 0) : floorIndex * A.ceiling,
1017
1361
  ceiling: A.ceiling
1018
1362
  });
1019
1363
  plan.openPairs[floorIndex] = A.openPairs || [];
@@ -1029,7 +1373,8 @@ function buildPlan(tpl, wIn, dIn, opIn, bc = {}) {
1029
1373
  const a = byId[sp.a];
1030
1374
  if (!a) throw new Error("Door references a missing room: " + sp.a);
1031
1375
  let edge;
1032
- if (sp.b === "outside") edge = exteriorEdge(a, sp.side, w, d, sp.at);
1376
+ if (sp.edge) edge = sp.edge;
1377
+ else if (sp.b === "outside") edge = exteriorEdge(a, sp.side, w, d, sp.at);
1033
1378
  else {
1034
1379
  const b = byId[sp.b];
1035
1380
  if (!b) throw new Error("Door references a missing room: " + sp.b);
@@ -1037,7 +1382,7 @@ function buildPlan(tpl, wIn, dIn, opIn, bc = {}) {
1037
1382
  }
1038
1383
  if (!edge) throw new Error("Door without a wall: " + sp.a + " / " + sp.b);
1039
1384
  const width = sp.width;
1040
- const margin = width / 2 + 0.12;
1385
+ const margin = width / 2 + (sp.clearance ?? tpl.clearance?.door ?? DOOR_EDGE_CLEARANCE);
1041
1386
  if (edge.a1 - edge.a0 < width + 0.24) throw new Error("Wall too short for the door: " + sp.a + " / " + sp.b);
1042
1387
  const c = clamp(sp.at != null ? sp.at : (edge.a0 + edge.a1) / 2, edge.a0 + margin, edge.a1 - margin);
1043
1388
  const exterior = sp.b === "outside";
@@ -1059,51 +1404,101 @@ function buildPlan(tpl, wIn, dIn, opIn, bc = {}) {
1059
1404
  exterior,
1060
1405
  side: sp.side || null,
1061
1406
  leaves: exterior && sp.steel ? 0 : width > 1.1 ? 2 : 1,
1062
- model: sp.steel ? "steel" : width > 1.1 ? "double" : "single",
1407
+ model: sp.model || (sp.steel ? "steel" : width > 1.1 ? "double" : "single"),
1408
+ type: sp.type || "door",
1409
+ steps: sp.steps || 0,
1063
1410
  hinge: "start",
1064
1411
  zones: openingZones(u, v, edge.axis, width, openingDepth(width, leaf))
1065
1412
  });
1066
1413
  });
1067
1414
  plan.doors.push(...floorDoors);
1068
- const windowsOut = [];
1069
- (A.windows(w, d, op) || []).forEach((windowDef) => {
1070
- const a = byId[windowDef.room];
1071
- if (!a) throw new Error("Window references a missing room: " + windowDef.room);
1072
- const edge = exteriorEdge(a, windowDef.side, w, d, windowDef.at);
1073
- if (!edge) return;
1074
- const width = windowDef.width;
1075
- const m0 = edge.a0 + width / 2 + 0.3;
1076
- const m1 = edge.a1 - width / 2 - 0.3;
1077
- if (m1 < m0 - TOL) return;
1078
- const centers = windowDef.n ? Array.from({ length: windowDef.n }, (_, i) => edge.a0 + (edge.a1 - edge.a0) * (i + 1) / (windowDef.n + 1)) : [windowDef.at];
1079
- for (const c0 of centers) {
1080
- const c = clamp(c0, m0, m1);
1081
- const u = edge.axis === "u" ? c : edge.pos;
1082
- const v = edge.axis === "u" ? edge.pos : c;
1083
- if (floorDoors.some((p) => p.axis === edge.axis && eq(p.pos, edge.pos) && Math.abs(p.at - c) < (p.width + width) / 2 + 0.15)) continue;
1084
- if (windowsOut.some((j) => j.axis === edge.axis && eq(j.pos, edge.pos) && Math.abs(j.at - c) < (j.width + width) / 2 + 0.2)) continue;
1085
- windowsOut.push({
1086
- id: "w" + floorIndex + "_" + windowsOut.length,
1087
- floor: floorIndex,
1088
- u: r2(u),
1089
- v: r2(v),
1090
- axis: edge.axis,
1091
- pos: edge.pos,
1092
- at: r2(c),
1093
- width,
1094
- y0: windowDef.y0 || 1,
1095
- y1: windowDef.y1 || 2.15,
1096
- room: windowDef.room,
1097
- side: windowDef.side
1098
- });
1099
- }
1100
- });
1415
+ const floorWalls = (tpl.noWalls ? [] : buildWalls(list, floorDoors, A.openPairs || [], floorIndex)).map((p) => Object.assign(p, { h: p.h || A.ceiling }));
1416
+ const windowsOut = placeWindows({ list, byId, floorWalls, windowDefs: A.windows(w, d, op) || [], floorIndex, ceiling: A.ceiling, w, d, clearance: tpl.clearance?.window ?? WINDOW_EDGE_CLEARANCE });
1101
1417
  plan.windows.push(...windowsOut);
1102
- plan.walls.push(...buildWalls(list, floorDoors, A.openPairs || [], floorIndex).map((p) => Object.assign(p, { h: p.h || A.ceiling })));
1418
+ plan.walls.push(...floorWalls);
1103
1419
  });
1420
+ if (tpl.config) plan.config = tpl.config(op, w, d);
1421
+ if (tpl.reservations) tpl.reservations(plan);
1104
1422
  buildLot(plan, tpl, w, d, op, style, styleId, bc);
1105
1423
  return plan;
1106
1424
  }
1425
+ function placeWindows(params) {
1426
+ const { list, byId, floorWalls, windowDefs, floorIndex, ceiling, w, d, clearance } = params;
1427
+ const stretches = floorWalls.filter((p) => p.exterior && p.type === "wall");
1428
+ const found = [];
1429
+ const groups = [];
1430
+ let order = 0;
1431
+ for (const def of windowDefs) {
1432
+ const room = byId[def.room];
1433
+ if (!room) throw new Error("Window references a missing room: " + def.room);
1434
+ const edge = exteriorEdge(room, def.side, w, d, def.at);
1435
+ if (!edge) continue;
1436
+ const centres = def.n ? Array.from({ length: def.n }, (_, i) => edge.a0 + (edge.a1 - edge.a0) * (i + 1) / (def.n + 1)) : [def.at];
1437
+ for (const centre of centres) {
1438
+ const onEdge = stretches.filter((t) => t.axis === edge.axis && eq(edge.axis === "u" ? t.v0 : t.u0, edge.pos) && t.sides.includes(def.room));
1439
+ const start = (t) => edge.axis === "u" ? t.u0 : t.v0;
1440
+ const end = (t) => edge.axis === "u" ? t.u1 : t.v1;
1441
+ const gap = (t) => Math.min(Math.abs(centre - start(t)), Math.abs(centre - end(t)));
1442
+ const target = onEdge.find((t) => centre >= start(t) - TOL && centre <= end(t) + TOL) || onEdge.slice().sort((x, y) => gap(x) - gap(y))[0];
1443
+ if (!target) continue;
1444
+ let group = groups.find((x) => x.wall === target && x.def.room === def.room);
1445
+ if (!group) groups.push(group = { wall: target, edge, start: start(target), end: end(target), def, entries: [] });
1446
+ group.entries.push({ def, centre, order: order++ });
1447
+ }
1448
+ }
1449
+ for (const g of groups) {
1450
+ const room = byId[g.def.room];
1451
+ const bath = room.type === "bathroom";
1452
+ const length = g.end - g.start;
1453
+ const its = g.entries.slice().sort((x, y) => x.centre - y.centre).map((x) => ({ def: x.def, order: x.order, width: bath ? Math.min(x.def.width, 0.6) : x.def.width }));
1454
+ const fits = (l) => l.reduce((t, x) => t + x.width, 0) + (l.length + 1) * clearance <= length + TOL;
1455
+ while (its.length && !fits(its)) {
1456
+ const k = its.reduce((m, x, i, arr) => x.width > arr[m].width ? i : m, 0);
1457
+ if (its[k].width > 0.6 + TOL) its[k] = Object.assign({}, its[k], { width: Math.max(0.6, r2(its[k].width - 0.1)) });
1458
+ else its.pop();
1459
+ }
1460
+ if (!its.length) continue;
1461
+ const space = (length - its.reduce((t, x) => t + x.width, 0)) / (its.length + 1);
1462
+ let pos = g.start + space;
1463
+ for (const x of its) {
1464
+ const c = pos + x.width / 2;
1465
+ pos += x.width + space;
1466
+ const u = g.edge.axis === "u" ? c : g.edge.pos;
1467
+ const v = g.edge.axis === "u" ? g.edge.pos : c;
1468
+ let y0 = x.def.y0 || 1;
1469
+ let y1 = x.def.y1 || 2.15;
1470
+ if (bath) {
1471
+ y0 = Math.max(y0, 2.1);
1472
+ y1 = Math.min(Math.max(y1, y0 + 0.4), ceiling - 0.25);
1473
+ }
1474
+ found.push({ id: "", floor: floorIndex, u: r2(u), v: r2(v), axis: g.edge.axis, pos: g.edge.pos, at: r2(c), width: x.width, y0: r2(y0), y1: r2(y1), room: x.def.room, side: x.def.side, _order: x.order });
1475
+ }
1476
+ }
1477
+ for (const room of list) {
1478
+ if (WINDOWLESS_ROOM_TYPES.has(room.type) || found.some((j) => j.room === room.id)) continue;
1479
+ let best = null;
1480
+ for (const t of stretches) {
1481
+ if (!t.sides.includes(room.id)) continue;
1482
+ const i0 = t.axis === "u" ? t.u0 : t.v0;
1483
+ const i1 = t.axis === "u" ? t.u1 : t.v1;
1484
+ const length = i1 - i0;
1485
+ const width = Math.min(room.type === "bathroom" ? 0.6 : 1.2, length - 2 * clearance);
1486
+ if (width < 0.6 - TOL || best && best.length >= length) continue;
1487
+ const side = t.axis === "u" ? t.sides[0] === "outside" ? "front" : "back" : t.sides[0] === "outside" ? "left" : "right";
1488
+ const c = (i0 + i1) / 2;
1489
+ const pos = t.axis === "u" ? t.v0 : t.u0;
1490
+ const bath = room.type === "bathroom";
1491
+ best = { length, window: { id: "", floor: floorIndex, u: r2(t.axis === "u" ? c : pos), v: r2(t.axis === "u" ? pos : c), axis: t.axis, pos, at: r2(c), width: r2(width), y0: bath ? 2.1 : 1, y1: bath ? Math.min(2.5, ceiling - 0.25) : 2.15, room: room.id, side, _order: 1e6 + found.length } };
1492
+ }
1493
+ if (best) found.push(best.window);
1494
+ }
1495
+ found.sort((x, y) => x._order - y._order);
1496
+ found.forEach((j, i) => {
1497
+ delete j._order;
1498
+ j.id = "w" + floorIndex + "_" + i;
1499
+ });
1500
+ return found;
1501
+ }
1107
1502
  function buildLot(plan, tpl, w, d, op, style, styleId, bc) {
1108
1503
  const seed = op.seed == null ? 1 : op.seed;
1109
1504
  const rFacade = createRng(seed, STREAM.facade);
@@ -1163,9 +1558,11 @@ function buildLot(plan, tpl, w, d, op, style, styleId, bc) {
1163
1558
  parts.sort((a, b) => b[2] - b[0] - (a[2] - a[0]));
1164
1559
  addZone("garden", "garden", "garden", parts, libs.garden);
1165
1560
  }
1166
- if (backDepth > 0 && cfg.yardZones) {
1167
- for (const q of cfg.yardZones) addZone(q.id, q.type, q.label || q.id, [[-marginL, d + q.v0, w + marginR, d + Math.min(backDepth, q.v1)]], q.library);
1561
+ const yard = cfg.yardZones;
1562
+ if (backDepth > 0 && yard) {
1563
+ for (const q of yard) addZone(q.id, q.type, q.label || q.id, [[-marginL, d + q.v0, w + marginR, d + Math.min(backDepth, q.v1)]], q.library);
1168
1564
  } else if (backDepth > 0) addZone("backyard", "backyard", "backyard", [[-marginL, d, w + marginR, d + backDepth]], libs.backyard);
1565
+ for (const q of cfg.extraZones || []) addZone(q.id, q.type, q.label || q.id, q.parts, q.library);
1169
1566
  if (marginL > 0.5) addZone("side_left", "side_yard", "side_left", [[-marginL, 0, 0, d]], libs.sideYard);
1170
1567
  if (marginR > 0.5) addZone("side_right", "side_yard", "side_right", [[w, 0, w + marginR, d]], libs.sideYard);
1171
1568
  const gates = [];
@@ -1223,12 +1620,14 @@ function buildLot(plan, tpl, w, d, op, style, styleId, bc) {
1223
1620
  paths.push({ id: "cargo_path", type: "vehicle", from: g.id, to: pt.id, width: hw * 2, rects: [[g.u - hw, d, g.u + hw, d + backDepth].map(r2)], points: [[g.u, d + backDepth], [g.u, d]] });
1224
1621
  }
1225
1622
  }
1623
+ if (tpl.paths) paths.push(...tpl.paths(plan, { gates, setback, marginLeft: marginL, marginRight: marginR, backyard: backDepth, w, d }));
1624
+ else addExteriorDoorPaths(plan, { gates, paths, zones: ZONES2, setback, marginLeft: marginL, marginRight: marginR, backyard: backDepth, w, target: cfg.doorTarget });
1226
1625
  plan.paths = paths;
1227
1626
  lot.paths = paths;
1228
1627
  const boundary = (rule) => {
1229
1628
  const type = pickWeighted(rLot, rule.type);
1230
1629
  const top = type === "railing" ? "none" : pickWeighted(rLot, rule.top);
1231
- const wallHeight = type === "railing" ? 1.2 : type === "fence" ? 2 : style.height;
1630
+ const wallHeight = type === "railing" ? 1.2 : type === "fence" ? style.fenceHeight ?? 2 : style.height;
1232
1631
  return { type, material: type === "wall" ? "block" : type === "fence" ? "wire_mesh" : "iron", height: wallHeight, top, totalHeight: r2(wallHeight + WALL_TOP_HEIGHTS[top]), thickness: WALL_THICKNESS };
1233
1632
  };
1234
1633
  const A = { front: boundary(style.front), left: boundary(style.sideYard), right: boundary(style.sideYard), back: boundary(style.back) };
@@ -1498,7 +1897,7 @@ function resolveSlot(plan, room, def, byId, zones) {
1498
1897
  }
1499
1898
  return roomCenter;
1500
1899
  };
1501
- const free = (rect) => !zones.some((z) => overlaps(rect, z, room.type === "stairs" ? -0.011 : EPS));
1900
+ const free = (rect) => !!def.floorFree || !zones.some((z) => overlaps(rect, z, room.type === "stairs" ? -0.011 : EPS));
1502
1901
  const rule = def.facing || "center";
1503
1902
  let R = inner(room.parts[Math.min(def.part || 0, room.parts.length - 1)]);
1504
1903
  if (def.anchor === "wall" || def.anchor === "window") {
@@ -1744,6 +2143,8 @@ function generate(template, options) {
1744
2143
  };
1745
2144
  if (tpl.singleFloor) result.floorNumber = op.floor || 3;
1746
2145
  result.partyWalls = tpl.partyWalls || [];
2146
+ if (plan.config) result.config = plan.config;
2147
+ runResultExtensions(plan, result, seed, op);
1747
2148
  if (zone) {
1748
2149
  const report = { zone: zone.id, adjustments, violations: checkLot(zone, lot, Object.assign({}, result, { rooms: plan.rooms })) };
1749
2150
  result.zoning = report;
@@ -2013,6 +2414,214 @@ var pt_BR_default = {
2013
2414
  }
2014
2415
  };
2015
2416
 
2417
+ // src/silhouettes/random.ts
2418
+ function createSilhouetteRandom(seed) {
2419
+ let state = seed;
2420
+ return () => {
2421
+ state |= 0;
2422
+ state = state + 1831565813 | 0;
2423
+ let t = Math.imul(state ^ state >>> 15, 1 | state);
2424
+ t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
2425
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
2426
+ };
2427
+ }
2428
+
2429
+ // src/silhouettes/builder.ts
2430
+ var ROOT = -1;
2431
+ var MAX_BOX_RADIUS = 0.15;
2432
+ var BOX_RADIUS_RATIO = 0.22;
2433
+ var PLAIN_BOX_RADIUS = 0.012;
2434
+ function boxRadius(width, height, depth) {
2435
+ const radius = Math.min(Math.min(width, height, depth) * BOX_RADIUS_RATIO, MAX_BOX_RADIUS);
2436
+ return radius < PLAIN_BOX_RADIUS ? 0 : radius;
2437
+ }
2438
+ function createSilhouetteBuilder(random) {
2439
+ const parts = [];
2440
+ const windows = [];
2441
+ const pick = (list) => list[Math.floor(random() * list.length)];
2442
+ const group = (x, y, z, ry = 0, parent = ROOT) => parts.push({ shape: "group", parent, x, y, z, ry }) - 1;
2443
+ const box = (x, y, z, width, height, depth, color, o = {}) => {
2444
+ parts.push({ shape: "box", parent: o.parent ?? ROOT, x, y: y + height / 2, z, width, height, depth, color, emissive: o.emissive ?? false, ry: o.ry ?? 0, rz: o.rz ?? 0, radius: boxRadius(width, height, depth) });
2445
+ };
2446
+ const prism = (x, y, z, width, height, depth, color, parent = ROOT) => {
2447
+ parts.push({ shape: "prism", parent, x, y, z, width, height, depth, color, ry: 0 });
2448
+ };
2449
+ const cylinder = (x, y, z, radiusTop, radiusBottom, height, color, parent = ROOT, segments = 14) => {
2450
+ parts.push({ shape: "cylinder", parent, x, y: y + height / 2, z, radiusTop, radiusBottom, height, color, segments });
2451
+ };
2452
+ const line = (points, color, parent = ROOT) => {
2453
+ parts.push({ shape: "line", parent, points, color });
2454
+ };
2455
+ const sign = (text, x, y, z, width, height, ry, colors, parent = ROOT) => {
2456
+ parts.push({ shape: "sign", parent, x, y, z, width, height, ry, text, ...colors });
2457
+ };
2458
+ const fence = (x0, z0, x1, z1, height, color, parent = ROOT) => {
2459
+ const length = Math.hypot(x1 - x0, z1 - z0);
2460
+ const count = Math.ceil(length / 3);
2461
+ for (let index = 0; index <= count; index++) {
2462
+ const t = index / count;
2463
+ cylinder(x0 + (x1 - x0) * t, 0, z0 + (z1 - z0) * t, 0.05, 0.05, height, color, parent, 5);
2464
+ }
2465
+ for (const level of [height * 0.35, height * 0.65, height * 0.98]) line([[x0, level, z0], [x1, level, z1]], 9079950, parent);
2466
+ };
2467
+ const finish = (footprint) => ({ parts, windows, footprint });
2468
+ return { random, pick, group, box, prism, cylinder, line, sign, fence, windows, finish };
2469
+ }
2470
+
2471
+ // src/silhouettes/building.ts
2472
+ var STOREY_HEIGHT = 3;
2473
+ var SHOP_AWNING_HEIGHT = 4.5;
2474
+ var WINDOW_COLUMN_WIDTH = 2.6;
2475
+ var SHOP_SIGNS = ["FARM\xC1CIA", "MAGAZINE", "LOT\xC9RICA", "PADARIA", "\xD3TICA", "CAL\xC7ADOS", "BAR"];
2476
+ var SIGN_COLORS = ["#ffd27a", "#e8dfcf", "#c4483b"];
2477
+ var DEFAULT_LIT_CHANCE = 0.07;
2478
+ function createCityBuilding(p) {
2479
+ const random = p.random ?? createSilhouetteRandom(p.seed ?? 7);
2480
+ const b = createSilhouetteBuilder(random);
2481
+ const { x, z, width: w, depth: d, height: h } = p;
2482
+ b.box(x, 0, z, w, h, d, p.color);
2483
+ b.box(x, h, z, w + 0.3, 0.35, d + 0.3, 2763308);
2484
+ if (random() < 0.4) b.cylinder(x + (random() - 0.5) * (w - 3), h + 0.35, z + (random() - 0.5) * (d - 3), 0.8, 0.8, 1.4, 4932928);
2485
+ const hasShop = p.shopChance !== void 0 && p.shopChance > 0 && random() < p.shopChance;
2486
+ if (hasShop) {
2487
+ for (const face of [1, -1]) {
2488
+ const zFace = z + face * (d / 2);
2489
+ b.box(x, SHOP_AWNING_HEIGHT, zFace + face * 0.8, w, 0.2, 1.6, 2763308);
2490
+ b.box(x, 0, zFace + face * 0.03, w * 0.6, 3.5, 0.05, 4869198);
2491
+ const text = b.pick(SHOP_SIGNS);
2492
+ b.sign(text, x, 5.3, zFace + face * 0.12, Math.min(w * 0.7, 8), 1, face > 0 ? 0 : Math.PI, { background: "#1a1a1c", foreground: b.pick(SIGN_COLORS) });
2493
+ }
2494
+ }
2495
+ addWindows(b, p, hasShop ? SHOP_AWNING_HEIGHT : 0);
2496
+ return b.finish({ x, z, width: w, depth: d, height: h + 0.35 });
2497
+ }
2498
+ function addWindows(b, p, base) {
2499
+ const { x, z, width: w, depth: d, height: h } = p;
2500
+ const floors = Math.max(1, Math.floor((h - 1) / STOREY_HEIGHT));
2501
+ const faces = [[0, d / 2 + 0.03, w, 0], [0, -d / 2 - 0.03, w, Math.PI], [w / 2 + 0.03, 0, d, Math.PI / 2], [-w / 2 - 0.03, 0, d, -Math.PI / 2]];
2502
+ for (const [ox, oz, faceWidth, ry] of faces) {
2503
+ const columns = Math.max(1, Math.floor(faceWidth / WINDOW_COLUMN_WIDTH));
2504
+ for (let floor = 0; floor < floors; floor++) {
2505
+ const y = base + 1.8 + floor * STOREY_HEIGHT;
2506
+ for (let column = 0; column < columns; column++) {
2507
+ const u = (column - (columns - 1) / 2) * WINDOW_COLUMN_WIDTH;
2508
+ b.windows.push({ x: ox ? x + ox : x + u, y, z: ox ? z + u : z + oz, ry, width: 1.2, height: 1.4, isLit: b.random() < (p.litChance || DEFAULT_LIT_CHANCE) });
2509
+ }
2510
+ }
2511
+ }
2512
+ }
2513
+
2514
+ // src/silhouettes/houses.ts
2515
+ var LIT_WINDOW = 16757854;
2516
+ var DARK_WINDOW = 1053979;
2517
+ var DEFAULT_ROOF = 5913130;
2518
+ var randomOf = (o) => o.random ?? createSilhouetteRandom(o.seed ?? 7);
2519
+ var pane = (isLit) => ({ color: isLit ? LIT_WINDOW : DARK_WINDOW, emissive: isLit });
2520
+ function createTwoStoreyHouse(p) {
2521
+ const b = createSilhouetteBuilder(randomOf(p));
2522
+ const { width: w, depth: d } = p;
2523
+ const g = b.group(p.x, 0, p.z, p.ry);
2524
+ b.box(0, 0, 0, w, 6, d, p.color, { parent: g });
2525
+ b.prism(0, 6, 0, w + 0.4, 1.4, d + 0.4, p.roofColor ?? DEFAULT_ROOF, g);
2526
+ b.box(0, 3.1, d / 2 + 0.5, w * 0.6, 0.15, 1, 6974054, { parent: g });
2527
+ for (const floor of [0, 1]) {
2528
+ for (const side of [-1, 1]) {
2529
+ const { color, emissive } = pane(b.random() < 0.12);
2530
+ b.box(side * w * 0.26, 1.1 + floor * 3, d / 2 + 0.03, 1.1, 1.2, 0.04, color, { parent: g, emissive });
2531
+ }
2532
+ }
2533
+ b.box(0, 0, d / 2 + 0.02, 1, 2.1, 0.05, 2759956, { parent: g });
2534
+ return b.finish({ x: p.x, z: p.z, width: w, depth: d, height: 7.4 });
2535
+ }
2536
+ function createSlabHouse(p) {
2537
+ const b = createSilhouetteBuilder(randomOf(p));
2538
+ const { width: w, depth: d, floors } = p;
2539
+ const g = b.group(p.x, 0, p.z, 0);
2540
+ for (let floor = 0; floor < floors; floor++) {
2541
+ const isTop = floor === floors - 1;
2542
+ b.box(0, floor * 2.9, 0, w, 2.9, d, isTop && b.random() < 0.5 ? 10119760 : p.color, { parent: g });
2543
+ const { color, emissive } = pane(b.random() < 0.1);
2544
+ b.box(0, floor * 2.9 + 1.1, d / 2 + 0.03, 1, 1, 0.04, color, { parent: g, emissive });
2545
+ }
2546
+ b.cylinder(w * 0.25, floors * 2.9, -d * 0.2, 0.55, 0.55, 0.9, 2775712, g, 10);
2547
+ for (let level = 0; level < floors; level++) b.box(w / 2 + 0.4, level * 2.9, 0, 0.8, 0.18, 1.2, 8026740, { parent: g });
2548
+ if (b.random() < 0.6) for (let index = 0; index < 4; index++) b.box(-w * 0.3 + index * 0.4, floors * 2.9, 0, 0.02, 1, 0.02, 9079434, { parent: g });
2549
+ return b.finish({ x: p.x, z: p.z, width: w, depth: d, height: floors * 2.9 + 1 });
2550
+ }
2551
+ function createColonialHouse(p) {
2552
+ const b = createSilhouetteBuilder(randomOf(p));
2553
+ const { width: w } = p;
2554
+ const g = b.group(p.x, 0, p.z, 0);
2555
+ b.box(0, 0, 0, w, 4.2, 10, p.color, { parent: g });
2556
+ b.box(0, 4.2, 4.6, w, 0.8, 0.25, p.color, { parent: g });
2557
+ b.prism(0, 4.2, -0.4, w, 1.4, 9.6, 8014386, g);
2558
+ const count = Math.max(1, Math.floor(w / 2.2));
2559
+ for (let index = 0; index < count; index++) {
2560
+ const u = (index - (count - 1) / 2) * 2.2;
2561
+ const isDoor = index === 0 && p.hasDoor;
2562
+ const lit = isDoor ? false : b.random() < 0.15;
2563
+ const color = isDoor ? 3810326 : lit ? LIT_WINDOW : 2767434;
2564
+ b.box(u, 1, 5.02, 0.9, 2, 0.05, color, { parent: g, emissive: lit });
2565
+ }
2566
+ return b.finish({ x: p.x, z: p.z, width: w, depth: 10, height: 6 });
2567
+ }
2568
+ function createChalet(p) {
2569
+ const b = createSilhouetteBuilder(randomOf(p));
2570
+ const g = b.group(p.x, 0, p.z, p.ry);
2571
+ b.box(0, 0, 0, 7, 0.8, 9, 5920336, { parent: g });
2572
+ b.box(0, 0.8, 0, 7, 4.2, 9, p.color ?? 7031344, { parent: g });
2573
+ b.prism(0, 5, 0, 7.6, 4, 9.6, 2894376, g);
2574
+ b.box(2.2, 5, -1, 0.8, 4.2, 0.8, 5920336, { parent: g });
2575
+ const { color, emissive } = pane(b.random() < 0.3);
2576
+ b.box(0, 1.6, 4.52, 1.3, 1.3, 0.04, color, { parent: g, emissive });
2577
+ b.box(0, 6.2, 4.6, 1, 1, 0.04, DARK_WINDOW, { parent: g });
2578
+ return b.finish({ x: p.x, z: p.z, width: 7.6, depth: 9.6, height: 9 });
2579
+ }
2580
+ function createCompanyHouse(p) {
2581
+ const b = createSilhouetteBuilder(randomOf(p));
2582
+ const g = b.group(p.x, 0, p.z, 0);
2583
+ b.box(0, 0, 0, 6, 3, 9, p.color ?? 14209216, { parent: g });
2584
+ b.prism(0, 3, 0, 6.4, 1.6, 9.4, 9062962, g);
2585
+ b.box(0, 0, 4.52, 0.9, 2.1, 0.05, 3811868, { parent: g });
2586
+ b.box(1.8, 1.1, 4.52, 1, 1, 0.04, DARK_WINDOW, { parent: g });
2587
+ b.fence(-3.2, 6.5, 3.2, 6.5, 0.9, 14735560, g);
2588
+ return b.finish({ x: p.x, z: p.z, width: 6.4, depth: 13, height: 4.6 });
2589
+ }
2590
+ function createStiltHouse(p) {
2591
+ const b = createSilhouetteBuilder(randomOf(p));
2592
+ const g = b.group(p.x, 0, p.z, 0);
2593
+ for (const a of [-2, 2]) for (const c of [-3, 3]) b.cylinder(a, -1.5, c, 0.12, 0.12, 3.1, 3811868, g, 6);
2594
+ b.box(0, 1.5, 0, 5, 0.2, 7, 5916208, { parent: g });
2595
+ b.box(0, 1.7, 0, 4.6, 2.6, 6.4, p.color ?? 6969926, { parent: g });
2596
+ b.prism(0, 4.3, 0, 5.2, 1.1, 7, 8026744, g);
2597
+ b.box(0, 1.5, 5, 1.2, 0.15, 4, 5916208, { parent: g });
2598
+ return b.finish({ x: p.x, z: p.z, width: 5.2, depth: 9, height: 5.4 });
2599
+ }
2600
+ function createFarmhouse(p) {
2601
+ const b = createSilhouetteBuilder(randomOf(p));
2602
+ const g = b.group(p.x, 0, p.z, 0);
2603
+ b.box(0, 0, 0, 15, 3.2, 11, 14208952, { parent: g });
2604
+ b.prism(0, 3.2, 0, 17, 2.2, 13, 9062962, g);
2605
+ b.box(0, 0, 6.8, 15, 0.25, 2.6, 6969926, { parent: g });
2606
+ for (let index = 0; index < 6; index++) b.cylinder(-7 + index * 2.8, 0, 7.8, 0.1, 0.1, 3.2, 5916208, g, 6);
2607
+ b.box(-3, 1, 5.52, 1.2, 1.2, 0.04, LIT_WINDOW, { parent: g, emissive: true });
2608
+ return b.finish({ x: p.x, z: p.z, width: 17, depth: 13, height: 5.4 });
2609
+ }
2610
+ function createDetachedHouse(p) {
2611
+ const b = createSilhouetteBuilder(randomOf(p));
2612
+ const { width: w, depth: d } = p;
2613
+ const g = b.group(p.x, 0, p.z, p.ry);
2614
+ b.box(0, 0, 0, w, 3, d, p.color, { parent: g });
2615
+ b.prism(0, 3, 0, w + 0.5, 1.6, d + 0.5, p.roofColor ?? DEFAULT_ROOF, g);
2616
+ b.box(0, 0, d / 2 + 0.02, 1, 2.1, 0.05, 2759956, { parent: g });
2617
+ for (const side of [-1, 1]) {
2618
+ const { color, emissive } = pane(b.random() < 0.15);
2619
+ b.box(side * w * 0.28, 1.1, d / 2 + 0.03, 1.1, 1.1, 0.04, color, { parent: g, emissive });
2620
+ if (p.isBoarded || b.random() < 0.3) for (const k of [-1, 1]) b.box(side * w * 0.28, 1.5 + k * 0.25, d / 2 + 0.08, 1.3, 0.14, 0.04, 7031338, { parent: g, rz: k * 0.15 });
2621
+ }
2622
+ return b.finish({ x: p.x, z: p.z, width: w + 0.5, depth: d + 0.5, height: 4.6 });
2623
+ }
2624
+
2016
2625
  // src/index.ts
2017
2626
  registerGroundItems();
2018
2627
  registerPermeableGround("lawn", "dirt");
@@ -2045,6 +2654,13 @@ var engine = {
2045
2654
  registerState,
2046
2655
  setStateDistribution,
2047
2656
  registerLocale,
2657
+ registerResultExtension,
2658
+ registerFindsProvider,
2659
+ findsFor,
2660
+ findsAnswer,
2661
+ registerViewerLayer,
2662
+ viewerLayers: VIEWER_LAYERS,
2663
+ blocksWindow,
2048
2664
  // zoning
2049
2665
  defineZone,
2050
2666
  getZone,
@@ -2059,6 +2675,7 @@ var engine = {
2059
2675
  catalog: CATALOG,
2060
2676
  libraries: LIBRARIES,
2061
2677
  styles: STYLES,
2678
+ paletteStyles: PALETTE_STYLES,
2062
2679
  createRng,
2063
2680
  mulberry32,
2064
2681
  hashString,
@@ -2066,13 +2683,26 @@ var engine = {
2066
2683
  windowZone,
2067
2684
  openingZones,
2068
2685
  color: { lstar, hex: hexString, hsl: intToHsl, HIGH_SATURATION, MIN_WALL_FLOOR_CONTRAST, MIN_FABRIC_FLOOR_CONTRAST },
2069
- constants: { WALL_THICKNESS, DOOR_CLEARANCE }
2686
+ constants: { WALL_THICKNESS, DOOR_CLEARANCE, PLAYER_RADIUS, DOOR_EDGE_CLEARANCE, WINDOW_EDGE_CLEARANCE, OVERHEAD_TOP, CAR_CLEARANCE }
2070
2687
  };
2071
2688
  export {
2689
+ blocksWindow,
2072
2690
  checkLot,
2691
+ createChalet,
2692
+ createCityBuilding,
2693
+ createColonialHouse,
2694
+ createCompanyHouse,
2695
+ createDetachedHouse,
2696
+ createFarmhouse,
2073
2697
  createRng,
2698
+ createSilhouetteRandom,
2699
+ createSlabHouse,
2700
+ createStiltHouse,
2701
+ createTwoStoreyHouse,
2074
2702
  defineZone,
2075
2703
  engine,
2704
+ findsAnswer,
2705
+ findsFor,
2076
2706
  generate,
2077
2707
  getZone,
2078
2708
  hashString,
@@ -2086,6 +2716,7 @@ export {
2086
2716
  overlaps,
2087
2717
  palette,
2088
2718
  registerDecorProfile,
2719
+ registerFindsProvider,
2089
2720
  registerGroundOverlay,
2090
2721
  registerItem,
2091
2722
  registerItems,
@@ -2095,11 +2726,13 @@ export {
2095
2726
  registerPaletteStyle,
2096
2727
  registerPass,
2097
2728
  registerPermeableGround,
2729
+ registerResultExtension,
2098
2730
  registerRoomColors,
2099
2731
  registerSeedKeys,
2100
2732
  registerState,
2101
2733
  registerStyle,
2102
2734
  registerTemplate,
2735
+ registerViewerLayer,
2103
2736
  registerWallTop,
2104
2737
  setStateDistribution,
2105
2738
  slotKit,