@adatechnology/game-floorplan 0.1.0 → 0.2.1

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.
@@ -21,10 +21,24 @@ var GameFloorplan = (() => {
21
21
  // src/index.ts
22
22
  var index_exports = {};
23
23
  __export(index_exports, {
24
+ blocksWindow: () => blocksWindow,
25
+ boxRadius: () => boxRadius,
24
26
  checkLot: () => checkLot,
27
+ createChalet: () => createChalet,
28
+ createCityBuilding: () => createCityBuilding,
29
+ createColonialHouse: () => createColonialHouse,
30
+ createCompanyHouse: () => createCompanyHouse,
31
+ createDetachedHouse: () => createDetachedHouse,
32
+ createFarmhouse: () => createFarmhouse,
25
33
  createRng: () => createRng,
34
+ createSilhouetteRandom: () => createSilhouetteRandom,
35
+ createSlabHouse: () => createSlabHouse,
36
+ createStiltHouse: () => createStiltHouse,
37
+ createTwoStoreyHouse: () => createTwoStoreyHouse,
26
38
  defineZone: () => defineZone,
27
39
  engine: () => engine,
40
+ findsAnswer: () => findsAnswer,
41
+ findsFor: () => findsFor,
28
42
  generate: () => generate,
29
43
  getZone: () => getZone,
30
44
  hashString: () => hashString,
@@ -38,6 +52,7 @@ var GameFloorplan = (() => {
38
52
  overlaps: () => overlaps,
39
53
  palette: () => palette,
40
54
  registerDecorProfile: () => registerDecorProfile,
55
+ registerFindsProvider: () => registerFindsProvider,
41
56
  registerGroundOverlay: () => registerGroundOverlay,
42
57
  registerItem: () => registerItem,
43
58
  registerItems: () => registerItems,
@@ -47,11 +62,13 @@ var GameFloorplan = (() => {
47
62
  registerPaletteStyle: () => registerPaletteStyle,
48
63
  registerPass: () => registerPass,
49
64
  registerPermeableGround: () => registerPermeableGround,
65
+ registerResultExtension: () => registerResultExtension,
50
66
  registerRoomColors: () => registerRoomColors,
51
67
  registerSeedKeys: () => registerSeedKeys,
52
68
  registerState: () => registerState,
53
69
  registerStyle: () => registerStyle,
54
70
  registerTemplate: () => registerTemplate,
71
+ registerViewerLayer: () => registerViewerLayer,
55
72
  registerWallTop: () => registerWallTop,
56
73
  setStateDistribution: () => setStateDistribution,
57
74
  slotKit: () => slotKit,
@@ -68,6 +85,227 @@ var GameFloorplan = (() => {
68
85
  var EPS = 1e-6;
69
86
  var TOL = 1e-3;
70
87
  var DOOR_CLEARANCE = 0.9;
88
+ var PLAYER_RADIUS = 0.24;
89
+ var DOOR_EDGE_CLEARANCE = 0.12;
90
+ var WINDOW_EDGE_CLEARANCE = 0.3;
91
+ var OVERHEAD_TOP = 2.15;
92
+ var WINDOWLESS_ROOM_TYPES = /* @__PURE__ */ new Set([
93
+ "stockroom",
94
+ "kitchen",
95
+ "laundry",
96
+ "bathroom",
97
+ "void",
98
+ "elevator",
99
+ "stairs",
100
+ "pantry",
101
+ "closet",
102
+ "corridor",
103
+ "hall",
104
+ "balcony",
105
+ "terrace",
106
+ "garage",
107
+ "storage",
108
+ "porch",
109
+ "tent",
110
+ "outhouse"
111
+ ]);
112
+ var CAR_CLEARANCE = { driver: 0.8, side: 0.6, end: 0.6 };
113
+
114
+ // src/registry.ts
115
+ var CATALOG = {};
116
+ var LIBRARIES = {};
117
+ var LIBRARY_ALIAS = {};
118
+ var TEMPLATES = {};
119
+ var STYLES = {};
120
+ var STYLE_ALIAS = {};
121
+ var PALETTE_STYLES = {};
122
+ var ROOM_COLORS = [];
123
+ var PASSES = [];
124
+ var DECOR_PROFILES = {};
125
+ var GROUND_OVERLAYS = [];
126
+ var STATE_PLUGINS = { normal: { apply: () => [] } };
127
+ var WALL_TOP_HEIGHTS = { none: 0 };
128
+ var SEED_KEYS = { rooms: {}, items: {} };
129
+ var stateDistribution = { weights: null, seedKey: "state" };
130
+ function registerItem(def) {
131
+ const h = def.h;
132
+ const item = Object.assign({ front: "+z", window: h <= 0.95, low: h <= 0.5, flat: false, deck: false, rim: 0 }, def);
133
+ CATALOG[def.type] = item;
134
+ return item;
135
+ }
136
+ function registerItems(list) {
137
+ for (const d of list) registerItem(d);
138
+ }
139
+ function registerLibrary(id, library) {
140
+ LIBRARIES[id] = library;
141
+ }
142
+ function registerLibraryAlias(alias, id) {
143
+ LIBRARY_ALIAS[alias] = id;
144
+ }
145
+ function registerTemplate(t) {
146
+ TEMPLATES[t.id] = t;
147
+ return t;
148
+ }
149
+ function registerStyle(style) {
150
+ STYLES[style.id] = style;
151
+ for (const a of style.aliases || []) STYLE_ALIAS[a] = style.id;
152
+ }
153
+ function registerPaletteStyle(id, def) {
154
+ PALETTE_STYLES[id] = def;
155
+ }
156
+ function registerRoomColors(list) {
157
+ for (const d of list) {
158
+ const i = ROOM_COLORS.findIndex((x) => x.type === d.type);
159
+ if (i >= 0) ROOM_COLORS.splice(i, 1);
160
+ ROOM_COLORS.push(d);
161
+ }
162
+ ROOM_COLORS.sort((a, b) => a.order - b.order);
163
+ }
164
+ function registerDecorProfile(styleId, profile) {
165
+ DECOR_PROFILES[styleId] = profile;
166
+ }
167
+ function registerPass(name, run) {
168
+ const i = PASSES.findIndex((p) => p.name === name);
169
+ if (i >= 0) PASSES[i] = { name, run };
170
+ else PASSES.push({ name, run });
171
+ }
172
+ function registerGroundOverlay(rule) {
173
+ GROUND_OVERLAYS.push(rule);
174
+ }
175
+ function registerWallTop(id, height) {
176
+ WALL_TOP_HEIGHTS[id] = height;
177
+ }
178
+ function registerSeedKeys(keys) {
179
+ Object.assign(SEED_KEYS.rooms, keys.rooms);
180
+ Object.assign(SEED_KEYS.items, keys.items);
181
+ }
182
+ function registerState(name, plugin) {
183
+ STATE_PLUGINS[name] = plugin;
184
+ }
185
+ function setStateDistribution(weights, seedKey = "state") {
186
+ stateDistribution.weights = weights;
187
+ stateDistribution.seedKey = seedKey;
188
+ }
189
+ var listStates = () => Object.keys(STATE_PLUGINS);
190
+ function resolveStyleId(id) {
191
+ const s = id ?? "";
192
+ return STYLE_ALIAS[s] ?? s;
193
+ }
194
+ function styleOf(id) {
195
+ return STYLES[id] || STYLES.budget || Object.values(STYLES)[0];
196
+ }
197
+ var RESULT_EXTENSIONS = [];
198
+ var FINDS_PROVIDERS = [];
199
+ function registerResultExtension(name, run, options = {}) {
200
+ const entry = { name, run, after: options.after || [], seedKey: options.seedKey ?? name };
201
+ const i = RESULT_EXTENSIONS.findIndex((x) => x.name === name);
202
+ if (i >= 0) RESULT_EXTENSIONS[i] = entry;
203
+ else RESULT_EXTENSIONS.push(entry);
204
+ }
205
+ function registerFindsProvider(provider) {
206
+ FINDS_PROVIDERS.push(provider);
207
+ }
208
+ var VIEWER_LAYERS = [];
209
+ function registerViewerLayer(layer) {
210
+ const i = VIEWER_LAYERS.findIndex((x) => x.id === layer.id);
211
+ if (i >= 0) VIEWER_LAYERS[i] = layer;
212
+ else VIEWER_LAYERS.push(layer);
213
+ }
214
+
215
+ // src/rng.ts
216
+ function mulberry32(a) {
217
+ return function() {
218
+ a |= 0;
219
+ a = a + 1831565813 | 0;
220
+ let t = Math.imul(a ^ a >>> 15, 1 | a);
221
+ t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
222
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
223
+ };
224
+ }
225
+ function hashString(input) {
226
+ const s = String(input);
227
+ let h = 1779033703 ^ s.length;
228
+ for (let i = 0; i < s.length; i++) {
229
+ h = Math.imul(h ^ s.charCodeAt(i), 3432918353);
230
+ h = h << 13 | h >>> 19;
231
+ }
232
+ h = Math.imul(h ^ h >>> 16, 2246822507);
233
+ h = Math.imul(h ^ h >>> 13, 3266489909);
234
+ return (h ^ h >>> 16) >>> 0;
235
+ }
236
+ var createRng = (seed, key) => mulberry32(hashString(seed + "|" + key));
237
+ function pickWeighted(rng, weights) {
238
+ const keys = Object.keys(weights);
239
+ let total = 0;
240
+ for (const k of keys) total += weights[k];
241
+ let x = rng() * total;
242
+ for (const k of keys) {
243
+ x -= weights[k];
244
+ if (x < 0) return k;
245
+ }
246
+ return keys[keys.length - 1];
247
+ }
248
+ function weightedOrder(candidates, rng) {
249
+ const arr = candidates.slice();
250
+ const out = [];
251
+ while (arr.length) {
252
+ let total = 0;
253
+ for (const c of arr) total += c.weight;
254
+ let x = rng() * total;
255
+ let i = 0;
256
+ for (; i < arr.length - 1; i++) {
257
+ x -= arr[i].weight;
258
+ if (x < 0) break;
259
+ }
260
+ out.push(arr.splice(i, 1)[0]);
261
+ }
262
+ return out;
263
+ }
264
+ function shuffle(arr, rng) {
265
+ const a = arr.slice();
266
+ for (let i = a.length - 1; i > 0; i--) {
267
+ const j = Math.floor(rng() * (i + 1));
268
+ [a[i], a[j]] = [a[j], a[i]];
269
+ }
270
+ return a;
271
+ }
272
+
273
+ // src/extensions.ts
274
+ function orderOf(extensions) {
275
+ const ordered = [];
276
+ const visiting = /* @__PURE__ */ new Set();
277
+ const visit = (extension) => {
278
+ if (ordered.includes(extension)) return;
279
+ if (visiting.has(extension.name)) throw new Error("Result extensions depend on each other: " + extension.name);
280
+ visiting.add(extension.name);
281
+ for (const name of extension.after) {
282
+ const before = extensions.find((x) => x.name === name);
283
+ if (before) visit(before);
284
+ }
285
+ visiting.delete(extension.name);
286
+ ordered.push(extension);
287
+ };
288
+ extensions.forEach(visit);
289
+ return ordered;
290
+ }
291
+ function runResultExtensions(plan, result, seed, options) {
292
+ for (const extension of orderOf(RESULT_EXTENSIONS)) {
293
+ result[extension.name] = extension.run({ plan, result, seed, rng: createRng(seed, extension.seedKey), options });
294
+ }
295
+ }
296
+ function findsFor(query) {
297
+ return answerOf(query) ?? [];
298
+ }
299
+ function findsAnswer(query) {
300
+ return answerOf(query);
301
+ }
302
+ function answerOf(query) {
303
+ for (const provider of FINDS_PROVIDERS) {
304
+ const stacks = provider.findsFor(query);
305
+ if (stacks !== null) return stacks;
306
+ }
307
+ return null;
308
+ }
71
309
 
72
310
  // src/geometry.ts
73
311
  var r2 = (x) => Math.round(x * 100) / 100;
@@ -170,13 +408,39 @@ var GameFloorplan = (() => {
170
408
  };
171
409
  }
172
410
  function usableParts(room, extra) {
173
- return room.parts.map((p) => {
174
- const isOpen = (k) => room.parts.some(
175
- (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)
176
- );
177
- const m = MARGIN + (extra || 0);
178
- 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)];
179
- });
411
+ const m = MARGIN + (extra || 0);
412
+ const out = [];
413
+ for (const p of room.parts) {
414
+ out.push([p[0] + m, p[1] + m, p[2] - m, p[3] - m]);
415
+ for (let k = 0; k < 4; k++) {
416
+ const spans = [];
417
+ for (const q of room.parts) {
418
+ if (q === p) continue;
419
+ if (k === 0 && eq(q[2], p[0])) spans.push([Math.max(q[1], p[1]), Math.min(q[3], p[3])]);
420
+ else if (k === 2 && eq(q[0], p[2])) spans.push([Math.max(q[1], p[1]), Math.min(q[3], p[3])]);
421
+ else if (k === 1 && eq(q[3], p[1])) spans.push([Math.max(q[0], p[0]), Math.min(q[2], p[2])]);
422
+ else if (k === 3 && eq(q[1], p[3])) spans.push([Math.max(q[0], p[0]), Math.min(q[2], p[2])]);
423
+ }
424
+ spans.sort((x, y) => x[0] - y[0]);
425
+ const merged = [];
426
+ for (const span of spans) {
427
+ const last = merged[merged.length - 1];
428
+ if (last && span[0] <= last[1] + TOL) last[1] = Math.max(last[1], span[1]);
429
+ else merged.push([span[0], span[1]]);
430
+ }
431
+ for (const [a, b] of merged) {
432
+ if (b - a <= 0.3) continue;
433
+ const x0 = a + m;
434
+ const x1 = b - m;
435
+ if (x1 <= x0) continue;
436
+ if (k === 0) out.push([p[0], x0, p[0] + m, x1]);
437
+ else if (k === 2) out.push([p[2] - m, x0, p[2], x1]);
438
+ else if (k === 1) out.push([x0, p[1], x1, p[1] + m]);
439
+ else out.push([x0, p[3] - m, x1, p[3]]);
440
+ }
441
+ }
442
+ }
443
+ return out;
180
444
  }
181
445
  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);
182
446
  function buildWalls(rooms, doors, openPairs, floorIndex) {
@@ -231,7 +495,7 @@ var GameFloorplan = (() => {
231
495
  pieces = n;
232
496
  }
233
497
  for (const [x, y] of pieces) {
234
- const isOpen = (t) => t === "balcony" || t === "terrace";
498
+ const isOpen = (t) => t === "balcony" || t === "terrace" || t === "porch";
235
499
  const isVoid = typeOf[s.A] === "void" || typeOf[s.B] === "void";
236
500
  if (isVoid && (s.A === "outside" || s.B === "outside")) continue;
237
501
  const railing = isVoid || isOpen(typeOf[s.A]) && s.B === "outside" || isOpen(typeOf[s.B]) && s.A === "outside";
@@ -254,6 +518,39 @@ var GameFloorplan = (() => {
254
518
  return out;
255
519
  }
256
520
 
521
+ // src/parking.ts
522
+ var STEP = 0.05;
523
+ var MAX_CANDIDATES = 80;
524
+ function carCandidates(slot, item, car) {
525
+ const front = [slot.frontU, slot.frontV];
526
+ const zone = slot._cell.R;
527
+ const alongV = Math.abs(front[1]) === 1;
528
+ const [extentU, extentV] = extent([item.w, item.d], front);
529
+ const across = alongV ? extentU : extentV;
530
+ const along = alongV ? extentV : extentU;
531
+ const a = alongV ? 0 : 1;
532
+ const b = alongV ? 1 : 0;
533
+ const driverSign = alongV ? front[1] : -front[0];
534
+ const frontSign = alongV ? front[1] : front[0];
535
+ const lowClear = driverSign < 0 ? car.driver : car.side;
536
+ const highClear = driverSign < 0 ? car.side : car.driver;
537
+ const lo = zone[a] + lowClear;
538
+ const hi = zone[a + 2] - highClear - across;
539
+ const alongLo = zone[b] + car.end;
540
+ const alongHi = zone[b + 2] - car.end - along;
541
+ if (hi < lo - 1e-6 || alongHi < alongLo - 1e-6) return [];
542
+ const preferred = (alongV ? slot.x : slot.z) - across / 2;
543
+ const alongStart = frontSign < 0 ? alongHi : alongLo;
544
+ const positions = [];
545
+ for (let p = lo; p <= hi + 1e-6; p += STEP) positions.push(Math.min(p, hi));
546
+ positions.sort((x, y) => Math.abs(x - preferred) - Math.abs(y - preferred));
547
+ return positions.slice(0, MAX_CANDIDATES).map((p) => {
548
+ const rect = alongV ? [p, alongStart, p + across, alongStart + along] : [alongStart, p, alongStart + along, p + across];
549
+ 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];
550
+ return { rect, clear };
551
+ });
552
+ }
553
+
257
554
  // src/queries.ts
258
555
  function zonesOfFloor(plan, floorIndex) {
259
556
  const out = [];
@@ -263,6 +560,7 @@ var GameFloorplan = (() => {
263
560
  for (const p of plan.doors) if (p.floor === 0 && p.exterior) out.push(...p.zones);
264
561
  for (const g of plan.gates) out.push(...g.zones);
265
562
  for (const c of plan.paths) out.push(...c.rects);
563
+ out.push(...plan.reservations);
266
564
  }
267
565
  return out;
268
566
  }
@@ -284,7 +582,10 @@ var GameFloorplan = (() => {
284
582
  if (j.axis === "u") return n[1] > 0 ? [u - h, v - MARGIN, u + h, v + depth] : [u - h, v - depth, u + h, v + MARGIN];
285
583
  return n[0] > 0 ? [u - MARGIN, v - h, u + depth, v + h] : [u - depth, v - h, u + MARGIN, v + h];
286
584
  }
287
- var blocksWindow = (it, win) => !it.window || it.h > 1 + EPS || it.h > win.y0 - 0.05;
585
+ function blocksWindow(it, win, overheadOff) {
586
+ const height = Math.max(it.h, it.windowHeight || 0, it.overhead && !overheadOff ? OVERHEAD_TOP : 0);
587
+ return !it.window || it.h > 1 + EPS || height > win.y0 - 0.05 + EPS;
588
+ }
288
589
  function windowsOfRoom(plan, room) {
289
590
  if (!room.outdoor) return plan.windows.filter((j) => j.room === room.id);
290
591
  return plan.windows.filter(
@@ -309,8 +610,22 @@ var GameFloorplan = (() => {
309
610
  }
310
611
 
311
612
  // src/grid.ts
312
- function createGrid(plan, room, ignored) {
313
- const gs = room.outdoor ? 0.3 : 0.2;
613
+ function wallBodies(plan, floor) {
614
+ const cache = plan.wallBodies = plan.wallBodies || {};
615
+ if (cache[floor]) return cache[floor];
616
+ const doors = plan.doors.filter((p) => p.floor === floor);
617
+ return cache[floor] = plan.walls.filter((p) => p.floor === floor && (p.type === "wall" || p.type === "railing")).map((p) => {
618
+ const a0 = p.axis === "u" ? p.u0 : p.v0;
619
+ const a1 = p.axis === "u" ? p.u1 : p.v1;
620
+ const pos = p.axis === "u" ? p.v0 : p.u0;
621
+ 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)));
622
+ const e0 = atDoor(a0) ? 0 : MARGIN;
623
+ const e1 = atDoor(a1) ? 0 : MARGIN;
624
+ return p.axis === "u" ? [a0 - e0, pos - MARGIN, a1 + e1, pos + MARGIN] : [pos - MARGIN, a0 - e0, pos + MARGIN, a1 + e1];
625
+ });
626
+ }
627
+ function createGrid(plan, room, ignored, radius = plan.playerRadius) {
628
+ const gs = room.outdoor ? 0.3 : 0.1;
314
629
  let u0 = 1e9;
315
630
  let v0 = 1e9;
316
631
  let u1 = -1e9;
@@ -324,12 +639,18 @@ var GameFloorplan = (() => {
324
639
  const nx = Math.ceil((u1 - u0) / gs);
325
640
  const nz = Math.ceil((v1 - v0) / gs);
326
641
  const insideCells = new Uint8Array(nx * nz);
327
- const insets = usableParts(room, 0.08);
642
+ const insets = room.outdoor ? usableParts(room, 0.08) : room.parts;
643
+ const bodies = room.outdoor ? null : wallBodies(plan, room.floor);
644
+ const tooClose = (u, v) => bodies.some((r) => {
645
+ const dx = Math.max(r[0] - u, 0, u - r[2]);
646
+ const dz = Math.max(r[1] - v, 0, v - r[3]);
647
+ return dx * dx + dz * dz < radius * radius - 1e-9;
648
+ });
328
649
  for (let j = 0; j < nz; j++)
329
650
  for (let i = 0; i < nx; i++) {
330
651
  const u = u0 + (i + 0.5) * gs;
331
652
  const v = v0 + (j + 0.5) * gs;
332
- if (insets.some((r) => u >= r[0] && u <= r[2] && v >= r[1] && v <= r[3])) insideCells[j * nx + i] = 1;
653
+ 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;
333
654
  }
334
655
  const entries = [];
335
656
  for (const ab of openingsOfFloor(plan, room.floor))
@@ -340,6 +661,22 @@ var GameFloorplan = (() => {
340
661
  const j = Math.floor((pt[1] - v0) / gs);
341
662
  if (i >= 0 && j >= 0 && i < nx && j < nz && insideCells[j * nx + i]) entries.push([i, j]);
342
663
  }
664
+ if (!room.outdoor)
665
+ for (const pair of plan.openPairs[room.floor] || []) {
666
+ const otherId = pair[0] === room.id ? pair[1] : pair[1] === room.id ? pair[0] : null;
667
+ const other = otherId && plan.rooms.find((c) => c.id === otherId && c.floor === room.floor);
668
+ if (!other) continue;
669
+ const edge = sharedEdge(room, other, null);
670
+ if (!edge || edge.a1 - edge.a0 > 2) continue;
671
+ const mid = (edge.a0 + edge.a1) / 2;
672
+ for (const offset of [-0.4, 0.4]) {
673
+ const pt = edge.axis === "u" ? [mid, edge.pos + offset] : [edge.pos + offset, mid];
674
+ if (!inRect(room, pt[0], pt[1])) continue;
675
+ const i = Math.floor((pt[0] - u0) / gs);
676
+ const j = Math.floor((pt[1] - v0) / gs);
677
+ if (i >= 0 && j >= 0 && i < nx && j < nz && insideCells[j * nx + i]) entries.push([i, j]);
678
+ }
679
+ }
343
680
  const R = inner(room.parts[0]);
344
681
  return { gs, u0, v0, nx, nz, inside: insideCells, entries, center: [(R[0] + R[2]) / 2, (R[1] + R[3]) / 2] };
345
682
  }
@@ -399,154 +736,12 @@ var GameFloorplan = (() => {
399
736
  return true;
400
737
  }
401
738
 
402
- // src/registry.ts
403
- var CATALOG = {};
404
- var LIBRARIES = {};
405
- var LIBRARY_ALIAS = {};
406
- var TEMPLATES = {};
407
- var STYLES = {};
408
- var STYLE_ALIAS = {};
409
- var PALETTE_STYLES = {};
410
- var ROOM_COLORS = [];
411
- var PASSES = [];
412
- var DECOR_PROFILES = {};
413
- var GROUND_OVERLAYS = [];
414
- var STATE_PLUGINS = { normal: { apply: () => [] } };
415
- var WALL_TOP_HEIGHTS = { none: 0 };
416
- var SEED_KEYS = { rooms: {}, items: {} };
417
- var stateDistribution = { weights: null, seedKey: "state" };
418
- function registerItem(def) {
419
- const h = def.h;
420
- const item = Object.assign({ front: "+z", window: h <= 0.95, low: h <= 0.5, flat: false, deck: false, rim: 0 }, def);
421
- CATALOG[def.type] = item;
422
- return item;
423
- }
424
- function registerItems(list) {
425
- for (const d of list) registerItem(d);
426
- }
427
- function registerLibrary(id, library) {
428
- LIBRARIES[id] = library;
429
- }
430
- function registerLibraryAlias(alias, id) {
431
- LIBRARY_ALIAS[alias] = id;
432
- }
433
- function registerTemplate(t) {
434
- TEMPLATES[t.id] = t;
435
- return t;
436
- }
437
- function registerStyle(style) {
438
- STYLES[style.id] = style;
439
- for (const a of style.aliases || []) STYLE_ALIAS[a] = style.id;
440
- }
441
- function registerPaletteStyle(id, def) {
442
- PALETTE_STYLES[id] = def;
443
- }
444
- function registerRoomColors(list) {
445
- for (const d of list) {
446
- const i = ROOM_COLORS.findIndex((x) => x.type === d.type);
447
- if (i >= 0) ROOM_COLORS.splice(i, 1);
448
- ROOM_COLORS.push(d);
449
- }
450
- ROOM_COLORS.sort((a, b) => a.order - b.order);
451
- }
452
- function registerDecorProfile(styleId, profile) {
453
- DECOR_PROFILES[styleId] = profile;
454
- }
455
- function registerPass(name, run) {
456
- const i = PASSES.findIndex((p) => p.name === name);
457
- if (i >= 0) PASSES[i] = { name, run };
458
- else PASSES.push({ name, run });
459
- }
460
- function registerGroundOverlay(rule) {
461
- GROUND_OVERLAYS.push(rule);
462
- }
463
- function registerWallTop(id, height) {
464
- WALL_TOP_HEIGHTS[id] = height;
465
- }
466
- function registerSeedKeys(keys) {
467
- Object.assign(SEED_KEYS.rooms, keys.rooms);
468
- Object.assign(SEED_KEYS.items, keys.items);
469
- }
470
- function registerState(name, plugin) {
471
- STATE_PLUGINS[name] = plugin;
472
- }
473
- function setStateDistribution(weights, seedKey = "state") {
474
- stateDistribution.weights = weights;
475
- stateDistribution.seedKey = seedKey;
476
- }
477
- var listStates = () => Object.keys(STATE_PLUGINS);
478
- function resolveStyleId(id) {
479
- const s = id ?? "";
480
- return STYLE_ALIAS[s] ?? s;
481
- }
482
- function styleOf(id) {
483
- return STYLES[id] || STYLES.budget || Object.values(STYLES)[0];
484
- }
485
-
486
- // src/rng.ts
487
- function mulberry32(a) {
488
- return function() {
489
- a |= 0;
490
- a = a + 1831565813 | 0;
491
- let t = Math.imul(a ^ a >>> 15, 1 | a);
492
- t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
493
- return ((t ^ t >>> 14) >>> 0) / 4294967296;
494
- };
495
- }
496
- function hashString(input) {
497
- const s = String(input);
498
- let h = 1779033703 ^ s.length;
499
- for (let i = 0; i < s.length; i++) {
500
- h = Math.imul(h ^ s.charCodeAt(i), 3432918353);
501
- h = h << 13 | h >>> 19;
502
- }
503
- h = Math.imul(h ^ h >>> 16, 2246822507);
504
- h = Math.imul(h ^ h >>> 13, 3266489909);
505
- return (h ^ h >>> 16) >>> 0;
506
- }
507
- var createRng = (seed, key) => mulberry32(hashString(seed + "|" + key));
508
- function pickWeighted(rng, weights) {
509
- const keys = Object.keys(weights);
510
- let total = 0;
511
- for (const k of keys) total += weights[k];
512
- let x = rng() * total;
513
- for (const k of keys) {
514
- x -= weights[k];
515
- if (x < 0) return k;
516
- }
517
- return keys[keys.length - 1];
518
- }
519
- function weightedOrder(candidates, rng) {
520
- const arr = candidates.slice();
521
- const out = [];
522
- while (arr.length) {
523
- let total = 0;
524
- for (const c of arr) total += c.weight;
525
- let x = rng() * total;
526
- let i = 0;
527
- for (; i < arr.length - 1; i++) {
528
- x -= arr[i].weight;
529
- if (x < 0) break;
530
- }
531
- out.push(arr.splice(i, 1)[0]);
532
- }
533
- return out;
534
- }
535
- function shuffle(arr, rng) {
536
- const a = arr.slice();
537
- for (let i = a.length - 1; i > 0; i--) {
538
- const j = Math.floor(rng() * (i + 1));
539
- [a[i], a[j]] = [a[j], a[i]];
540
- }
541
- return a;
542
- }
543
-
544
739
  // src/fill.ts
545
740
  function candidatesOf(cats) {
546
741
  const out = [];
547
742
  for (const [cat, weight] of Object.entries(cats)) {
548
743
  const exact = cat[0] === "=";
549
- const its = Object.values(CATALOG).filter((i) => !i.decor && (exact ? i.type === cat.slice(1) : i.category === cat || i.type === cat));
744
+ const its = Object.values(CATALOG).filter((i) => !i.decor && (exact ? i.type === cat.slice(1) : !i.exactOnly && (i.category === cat || i.type === cat)));
550
745
  for (const it of its) out.push({ it, weight: weight / its.length });
551
746
  }
552
747
  return out;
@@ -570,6 +765,7 @@ var GameFloorplan = (() => {
570
765
  }
571
766
  return [u0, v0, u0 + extentU, v0 + extentV];
572
767
  }
768
+ var RELAXED_RADIUS = 0.12;
573
769
  var MESH_FRONT = { "+z": [0, 1], "-z": [0, -1], "+x": [1, 0], "-x": [-1, 0] };
574
770
  function rotationFor(f, front) {
575
771
  const meshFront = MESH_FRONT[front] || [0, 1];
@@ -587,9 +783,10 @@ var GameFloorplan = (() => {
587
783
  const rims = [];
588
784
  const inners = usableParts(room, 0);
589
785
  const grid = createGrid(plan, room);
786
+ const relaxedGrid = room.outdoor ? grid : createGrid(plan, room, null, RELAXED_RADIUS);
590
787
  const zones = zonesOfFloor(plan, room.floor);
591
788
  const windowZones = windowsOfRoom(plan, room).map((j) => ({ z: windowZone(j, room.outdoor), j }));
592
- const inflation = room.outdoor ? 0.15 : 0.12;
789
+ const inflation = room.outdoor ? 0.15 : plan.playerRadius;
593
790
  const units = [];
594
791
  const seen = {};
595
792
  for (const s of slots2) {
@@ -608,7 +805,7 @@ var GameFloorplan = (() => {
608
805
  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)));
609
806
  const done = /* @__PURE__ */ new Set();
610
807
  const ready = (u) => dependencies(u).every((id) => done.has(id));
611
- const make = (s, it, r, parent) => {
808
+ const make = (s, it, r, parent, overheadOff) => {
612
809
  const f = [s.frontU, s.frontV];
613
810
  const id = room.id + "." + s.id;
614
811
  const m = {
@@ -637,13 +834,18 @@ var GameFloorplan = (() => {
637
834
  _r: r,
638
835
  _seedId: SEED_KEYS.items[id] ?? id
639
836
  };
837
+ if (it.overhead) m.overhead = !overheadOff;
838
+ if (it.category === "car") {
839
+ m.driver = [f[1], -f[0]];
840
+ m.carClearance = plan.carClearance;
841
+ }
640
842
  if (parent && s.anchor === "inside") m.y = r2(parent.y || 0);
641
843
  if (room.link) m.link = { from: room.link[0], to: room.link[1] };
642
844
  state[s.id] = m;
643
845
  items.push(m);
644
846
  return m;
645
847
  };
646
- const tryFit = (s, relax) => {
848
+ const tryFit = (s, relax, overheadOff) => {
647
849
  const cands = weightedOrder(candidatesOf(s.cats), rng);
648
850
  if (s.anchor === "on" || s.anchor === "inside") {
649
851
  const parent = state[s.on];
@@ -666,19 +868,22 @@ var GameFloorplan = (() => {
666
868
  const shifts = [0];
667
869
  for (let k = 1; k * 0.1 <= slide + 1e-9; k++) shifts.push(k * 0.1, -k * 0.1);
668
870
  let found = null;
669
- for (const shift of shifts) {
670
- const r3 = itemRect(s, it, shift);
871
+ const car = it.category === "car" && !relax;
872
+ const tries = car ? carCandidates(s, it, plan.carClearance) : shifts.map((shift) => ({ rect: itemRect(s, it, shift) }));
873
+ for (const attempt of tries) {
874
+ const r3 = attempt.rect;
671
875
  if (!insideUnion(r3, inners)) continue;
876
+ if (car && (!insideUnion(attempt.clear, inners) || solids.some((o) => overlaps(attempt.clear, o)))) continue;
672
877
  if (it.flat) {
673
878
  found = { rect: r3 };
674
879
  break;
675
880
  }
676
881
  if (zones.some((z) => overlaps(r3, z))) continue;
677
- if (windowZones.some(({ z, j }) => blocksWindow(it, j) && overlaps(r3, z))) continue;
882
+ if (windowZones.some(({ z, j }) => blocksWindow(it, j, overheadOff) && overlaps(r3, z))) continue;
678
883
  if (solids.some((o) => overlaps(r3, o))) continue;
679
884
  if (!it.deck && rims.some((a) => overlaps(r3, a))) continue;
680
- let cl2 = null;
681
- if (!relax && s.clearance > 0 && !it.low) {
885
+ let cl2 = car ? attempt.clear : null;
886
+ if (!car && !relax && s.clearance > 0 && !it.low) {
682
887
  cl2 = clearRect(r3, f, s.clearance);
683
888
  if (!insideUnion(cl2, inners)) continue;
684
889
  if (items.some((m2) => !m2.low && !m2.flat && !m2.on && overlaps(cl2, m2._r))) continue;
@@ -689,14 +894,14 @@ var GameFloorplan = (() => {
689
894
  rimRect2 = [r3[0] - it.rim, r3[1] - it.rim, r3[2] + it.rim, r3[3] + it.rim];
690
895
  if (!insideUnion(rimRect2, inners) || solids.some((o) => overlaps(rimRect2, o)) || zones.some((z) => overlaps(rimRect2, z))) continue;
691
896
  }
692
- if (!reachable(grid, solids.concat([r3]), inflation)) continue;
897
+ if (!(relax ? reachable(relaxedGrid, solids.concat([r3]), RELAXED_RADIUS) : reachable(grid, solids.concat([r3]), inflation))) continue;
693
898
  found = { rect: r3, cl: cl2, rimRect: rimRect2 };
694
899
  break;
695
900
  }
696
901
  if (!found) continue;
697
902
  const { rect: r, cl, rimRect } = found;
698
903
  if (it.flat) return make(s, it, r, null);
699
- const m = make(s, it, r, null);
904
+ const m = make(s, it, r, null, overheadOff);
700
905
  if (cl) reserves.push(cl);
701
906
  if (rimRect) rims.push(rimRect);
702
907
  solids.push(r);
@@ -719,9 +924,16 @@ var GameFloorplan = (() => {
719
924
  break;
720
925
  }
721
926
  }
927
+ if (!ok)
928
+ for (const m of applicable) {
929
+ if (tryFit(m, false, true)) {
930
+ ok = true;
931
+ break;
932
+ }
933
+ }
722
934
  if (!ok && u.required)
723
935
  for (const m of applicable) {
724
- if (tryFit(m, true)) {
936
+ if (tryFit(m, true, true)) {
725
937
  ok = true;
726
938
  break;
727
939
  }
@@ -770,6 +982,8 @@ var GameFloorplan = (() => {
770
982
  createGrid,
771
983
  clearRect,
772
984
  pickWeighted,
985
+ createRng,
986
+ roomSeedKey: (room) => room.seedKey ?? SEED_KEYS.rooms[room.id] ?? room.id,
773
987
  shuffle,
774
988
  facingRotation: (f, type) => rotationFor(f, CATALOG[type].front),
775
989
  cutIntervals,
@@ -833,6 +1047,10 @@ var GameFloorplan = (() => {
833
1047
  const zones = zonesOfFloor(plan, room.floor);
834
1048
  const occupied = itemsOfRoom(room);
835
1049
  const windowZones = windowsOfRoom(plan, room).map((j) => ({ z: windowZone(j, room.outdoor), j }));
1050
+ const landings = it0.flat ? [] : items.filter((m) => m.type === "stairs" && m.floor === room.floor && m.aabb).map((m) => {
1051
+ const r = m.aabb;
1052
+ 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]];
1053
+ });
836
1054
  const cand = opts.cands ? opts.cands.slice() : [];
837
1055
  if (!opts.cands)
838
1056
  for (const p of inners) {
@@ -847,9 +1065,9 @@ var GameFloorplan = (() => {
847
1065
  cand.push([x, z, x + extentU, z + extentV]);
848
1066
  }
849
1067
  }
850
- for (const r of shuffle(cand, rng2)) {
1068
+ for (const r of opts.ordered ? cand : shuffle(cand, rng2)) {
851
1069
  if (!insideUnion(r, inners)) continue;
852
- if (zones.some((z) => overlaps(r, z))) continue;
1070
+ if (zones.some((z) => overlaps(r, z)) || landings.some((z) => overlaps(r, z))) continue;
853
1071
  if (windowZones.some(({ z, j }) => blocksWindow(it, j) && overlaps(r, z))) continue;
854
1072
  if (solidList.some((o) => overlaps(r, o))) continue;
855
1073
  if (opts.flat) {
@@ -858,7 +1076,7 @@ var GameFloorplan = (() => {
858
1076
  }
859
1077
  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;
860
1078
  if (opts.without && opts.without(r)) continue;
861
- if (!reachable(opts.railing || gridOf(room), solidList.concat([r]), 0.16)) continue;
1079
+ if (!reachable(opts.railing || gridOf(room), solidList.concat([r]), room.outdoor ? 0.16 : plan.playerRadius)) continue;
862
1080
  return r;
863
1081
  }
864
1082
  return null;
@@ -939,6 +1157,129 @@ var GameFloorplan = (() => {
939
1157
  return vars ? raw.replace(/\{(\w+)\}/g, (m, k) => k in vars ? String(vars[k]) : m) : raw;
940
1158
  }
941
1159
 
1160
+ // src/paths.ts
1161
+ var CLOSE = 1e-6;
1162
+ var rounded = (r) => r.map(r2);
1163
+ var touches = (a, r) => a[0] <= r[2] + CLOSE && a[2] >= r[0] - CLOSE && a[1] <= r[3] + CLOSE && a[3] >= r[1] - CLOSE;
1164
+ function addExteriorDoorPaths(plan, c) {
1165
+ const { gates, paths, zones, setback, marginLeft, marginRight, backyard, w } = c;
1166
+ const social = gates.find((g) => g.type === "pedestrian");
1167
+ const socialPath = social ? paths.find((p) => p.from === social.id && p.type === "pedestrian") : null;
1168
+ const parkingZone = zones.find((z) => z.id === "parking");
1169
+ const parking = parkingZone ? parkingZone.parts[0] : null;
1170
+ const midY = setback > 0 ? -setback / 2 : 0;
1171
+ const joinToSocial = (x, width, rects, points) => {
1172
+ if (!socialPath) return;
1173
+ const h = width / 2;
1174
+ 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];
1175
+ const targetX = x < horizontal[0] ? horizontal[0] + h : x > horizontal[2] ? horizontal[2] - h : x;
1176
+ if (Math.abs(targetX - x) > CLOSE) {
1177
+ rects.push([Math.min(x, targetX) - h, midY - h, Math.max(x, targetX) + h, midY + h]);
1178
+ points.push([targetX, midY]);
1179
+ }
1180
+ };
1181
+ for (const door of plan.doors.filter((q) => q.floor === 0 && q.exterior)) {
1182
+ if (paths.some((p) => p.to === door.id)) continue;
1183
+ const half = Math.max(1, Math.min(door.width, 2)) / 2;
1184
+ const rects = [];
1185
+ const points = [];
1186
+ const isTarget = c.target && door.a === c.target.a && door.side === c.target.side;
1187
+ let fromId = social ? social.id : null;
1188
+ let width = 1;
1189
+ const id = isTarget ? "entrance_path" : "path_" + door.id;
1190
+ if (door.side === "front") {
1191
+ if (setback > 0) {
1192
+ const stub = Math.min(1, setback);
1193
+ rects.push([door.u - half, -stub, door.u + half, 0]);
1194
+ points.push([door.u, 0], [door.u, -stub]);
1195
+ if (-stub > midY) {
1196
+ rects.push([door.u - width / 2, midY - width / 2, door.u + width / 2, -stub]);
1197
+ points.push([door.u, midY]);
1198
+ }
1199
+ joinToSocial(door.u, width, rects, points);
1200
+ } else {
1201
+ rects.push([door.u - half, -1.2, door.u + half, 0]);
1202
+ points.push([door.u, 0], [door.u, -1.2]);
1203
+ }
1204
+ } else {
1205
+ const lanes = [];
1206
+ if (marginLeft > 0.5) lanes.push({ x: -marginLeft / 2, width: marginLeft, side: "left" });
1207
+ if (marginRight > 0.5) lanes.push({ x: w + marginRight / 2, width: marginRight, side: "right" });
1208
+ const targets = paths.flatMap((p) => p.rects).concat(parking ? [parking] : []);
1209
+ let best = null;
1210
+ for (const lane of lanes) {
1211
+ if (door.side === "left" && lane.side !== "left" || door.side === "right" && lane.side !== "right") continue;
1212
+ const laneWidth = Math.min(0.9, lane.width - 0.1);
1213
+ const h = laneWidth / 2;
1214
+ const rearStrip = Math.min(0.8, backyard);
1215
+ const rearY = door.v + rearStrip + laneWidth / 2;
1216
+ const overlapsParking = !!parking && lane.x + h > parking[0] && lane.x - h < parking[2];
1217
+ const frontY = overlapsParking || setback <= 0 ? 0 : -0.6;
1218
+ let frontStub = null;
1219
+ if (!overlapsParking && setback > 0) {
1220
+ const dir = social && social.u < lane.x ? -1 : 1;
1221
+ const band = [-0.6 - h, -0.6 + h];
1222
+ let nearestX = social ? social.u : lane.x;
1223
+ let nearestGap = Infinity;
1224
+ for (const r of targets) {
1225
+ if (!(r[1] <= band[1] && r[3] >= band[0])) continue;
1226
+ const edge = dir < 0 ? r[2] : r[0];
1227
+ const gap = Math.abs(edge - lane.x);
1228
+ if ((dir < 0 ? edge <= lane.x : edge >= lane.x) && gap < nearestGap) {
1229
+ nearestGap = gap;
1230
+ nearestX = edge;
1231
+ }
1232
+ }
1233
+ 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];
1234
+ }
1235
+ const vertical = [lane.x - h, frontY - (frontY < 0 ? h : 0), lane.x + h, (door.side === "back" ? rearY : door.v) + laneWidth / 2];
1236
+ 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;
1237
+ const cost = (horizontal ? horizontal[2] - horizontal[0] : 0) + (frontStub ? frontStub[2] - frontStub[0] : 0);
1238
+ if (!best || cost < best.cost) best = { lane, laneWidth, half: h, vertical, horizontal, frontStub, cost, rearStrip, rearY, frontY, overlapsParking };
1239
+ }
1240
+ if (best) {
1241
+ const { lane, laneWidth, half: h, vertical, horizontal, frontStub, rearStrip, rearY, frontY, overlapsParking } = best;
1242
+ width = laneWidth;
1243
+ if (overlapsParking) fromId = (gates.find((g) => g.type === "garage") || social || {}).id || null;
1244
+ if (door.side === "back") {
1245
+ rects.push([door.u - half, door.v, door.u + half, door.v + rearStrip + laneWidth], horizontal, vertical);
1246
+ points.push([door.u, door.v], [door.u, rearY], [lane.x, rearY], [lane.x, frontY]);
1247
+ } else {
1248
+ const x0 = door.side === "left" ? lane.x - h : w;
1249
+ const x1 = door.side === "left" ? 0 : lane.x + h;
1250
+ rects.push([Math.min(x0, x1), door.v - half, Math.max(x0, x1), door.v + half], vertical);
1251
+ points.push([door.u, door.v], [lane.x, door.v], [lane.x, frontY]);
1252
+ }
1253
+ if (frontStub) {
1254
+ rects.push(frontStub);
1255
+ points.push([frontStub[0] < lane.x ? frontStub[0] : frontStub[2], -0.6]);
1256
+ }
1257
+ } else {
1258
+ const stub = 1;
1259
+ if (door.side === "back") rects.push([door.u - half, door.v, door.u + half, door.v + Math.min(stub, backyard)]);
1260
+ 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]);
1261
+ points.push([door.u, door.v]);
1262
+ }
1263
+ }
1264
+ const reached = rects.slice();
1265
+ const pool = paths.flatMap((p) => p.rects).concat(parking ? [parking] : []);
1266
+ let grows = true;
1267
+ while (grows) {
1268
+ grows = false;
1269
+ for (const r of pool) {
1270
+ if (!reached.includes(r) && reached.some((a) => touches(a, r))) {
1271
+ reached.push(r);
1272
+ grows = true;
1273
+ }
1274
+ }
1275
+ }
1276
+ 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);
1277
+ const gate = gates.find((q) => q.type === "pedestrian" && inside(q)) || gates.find((q) => q.type !== "cargo" && inside(q));
1278
+ if (gate) fromId = gate.id;
1279
+ paths.push({ id, type: "pedestrian", from: fromId, to: door.id, width, rects: rects.map(rounded), points: points.map((q) => q.map(r2)) });
1280
+ }
1281
+ }
1282
+
942
1283
  // src/zoning.ts
943
1284
  var ZONES = {};
944
1285
  var PERMEABLE_GROUNDS = /* @__PURE__ */ new Set();
@@ -1070,14 +1411,34 @@ var GameFloorplan = (() => {
1070
1411
  const styleId = resolveStyleId(op.style || (typeof tpl.style === "function" ? tpl.style(op) : tpl.style) || "budget");
1071
1412
  const FLOOR_DEFS = floorsOf(tpl, op);
1072
1413
  const style = styleOf(styleId);
1073
- const plan = { w, d, tpl, floorDefs: FLOOR_DEFS, style: styleId, floors: [], rooms: [], zones: [], doors: [], windows: [], walls: [], gates: [], paths: [], lot: null, openPairs: {} };
1414
+ const plan = {
1415
+ w,
1416
+ d,
1417
+ tpl,
1418
+ floorDefs: FLOOR_DEFS,
1419
+ style: styleId,
1420
+ floors: [],
1421
+ rooms: [],
1422
+ zones: [],
1423
+ doors: [],
1424
+ windows: [],
1425
+ walls: [],
1426
+ gates: [],
1427
+ paths: [],
1428
+ lot: null,
1429
+ openPairs: {},
1430
+ reservations: [],
1431
+ options: op,
1432
+ playerRadius: op.playerRadius ?? PLAYER_RADIUS,
1433
+ carClearance: Object.assign({}, CAR_CLEARANCE, op.carClearance)
1434
+ };
1074
1435
  FLOOR_DEFS.forEach((A, floorIndex) => {
1075
1436
  const floorNumber = tpl.singleFloor ? op.floor || 3 : floorIndex + 1;
1076
1437
  plan.floors.push({
1077
1438
  index: floorIndex,
1078
1439
  name: tpl.singleFloor ? "floor_n" : A.label,
1079
1440
  number: tpl.singleFloor ? floorNumber : void 0,
1080
- y0: tpl.singleFloor ? (floorNumber - 1) * A.ceiling : floorIndex * A.ceiling,
1441
+ y0: tpl.singleFloor ? (floorNumber - 1) * A.ceiling : tpl.cumulativeCeilings ? FLOOR_DEFS.slice(0, floorIndex).reduce((t, q) => t + q.ceiling, 0) : floorIndex * A.ceiling,
1081
1442
  ceiling: A.ceiling
1082
1443
  });
1083
1444
  plan.openPairs[floorIndex] = A.openPairs || [];
@@ -1093,7 +1454,8 @@ var GameFloorplan = (() => {
1093
1454
  const a = byId[sp.a];
1094
1455
  if (!a) throw new Error("Door references a missing room: " + sp.a);
1095
1456
  let edge;
1096
- if (sp.b === "outside") edge = exteriorEdge(a, sp.side, w, d, sp.at);
1457
+ if (sp.edge) edge = sp.edge;
1458
+ else if (sp.b === "outside") edge = exteriorEdge(a, sp.side, w, d, sp.at);
1097
1459
  else {
1098
1460
  const b = byId[sp.b];
1099
1461
  if (!b) throw new Error("Door references a missing room: " + sp.b);
@@ -1101,7 +1463,7 @@ var GameFloorplan = (() => {
1101
1463
  }
1102
1464
  if (!edge) throw new Error("Door without a wall: " + sp.a + " / " + sp.b);
1103
1465
  const width = sp.width;
1104
- const margin = width / 2 + 0.12;
1466
+ const margin = width / 2 + (sp.clearance ?? tpl.clearance?.door ?? DOOR_EDGE_CLEARANCE);
1105
1467
  if (edge.a1 - edge.a0 < width + 0.24) throw new Error("Wall too short for the door: " + sp.a + " / " + sp.b);
1106
1468
  const c = clamp(sp.at != null ? sp.at : (edge.a0 + edge.a1) / 2, edge.a0 + margin, edge.a1 - margin);
1107
1469
  const exterior = sp.b === "outside";
@@ -1123,51 +1485,101 @@ var GameFloorplan = (() => {
1123
1485
  exterior,
1124
1486
  side: sp.side || null,
1125
1487
  leaves: exterior && sp.steel ? 0 : width > 1.1 ? 2 : 1,
1126
- model: sp.steel ? "steel" : width > 1.1 ? "double" : "single",
1488
+ model: sp.model || (sp.steel ? "steel" : width > 1.1 ? "double" : "single"),
1489
+ type: sp.type || "door",
1490
+ steps: sp.steps || 0,
1127
1491
  hinge: "start",
1128
1492
  zones: openingZones(u, v, edge.axis, width, openingDepth(width, leaf))
1129
1493
  });
1130
1494
  });
1131
1495
  plan.doors.push(...floorDoors);
1132
- const windowsOut = [];
1133
- (A.windows(w, d, op) || []).forEach((windowDef) => {
1134
- const a = byId[windowDef.room];
1135
- if (!a) throw new Error("Window references a missing room: " + windowDef.room);
1136
- const edge = exteriorEdge(a, windowDef.side, w, d, windowDef.at);
1137
- if (!edge) return;
1138
- const width = windowDef.width;
1139
- const m0 = edge.a0 + width / 2 + 0.3;
1140
- const m1 = edge.a1 - width / 2 - 0.3;
1141
- if (m1 < m0 - TOL) return;
1142
- const centers = windowDef.n ? Array.from({ length: windowDef.n }, (_, i) => edge.a0 + (edge.a1 - edge.a0) * (i + 1) / (windowDef.n + 1)) : [windowDef.at];
1143
- for (const c0 of centers) {
1144
- const c = clamp(c0, m0, m1);
1145
- const u = edge.axis === "u" ? c : edge.pos;
1146
- const v = edge.axis === "u" ? edge.pos : c;
1147
- 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;
1148
- 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;
1149
- windowsOut.push({
1150
- id: "w" + floorIndex + "_" + windowsOut.length,
1151
- floor: floorIndex,
1152
- u: r2(u),
1153
- v: r2(v),
1154
- axis: edge.axis,
1155
- pos: edge.pos,
1156
- at: r2(c),
1157
- width,
1158
- y0: windowDef.y0 || 1,
1159
- y1: windowDef.y1 || 2.15,
1160
- room: windowDef.room,
1161
- side: windowDef.side
1162
- });
1163
- }
1164
- });
1496
+ const floorWalls = (tpl.noWalls ? [] : buildWalls(list, floorDoors, A.openPairs || [], floorIndex)).map((p) => Object.assign(p, { h: p.h || A.ceiling }));
1497
+ 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 });
1165
1498
  plan.windows.push(...windowsOut);
1166
- plan.walls.push(...buildWalls(list, floorDoors, A.openPairs || [], floorIndex).map((p) => Object.assign(p, { h: p.h || A.ceiling })));
1499
+ plan.walls.push(...floorWalls);
1167
1500
  });
1501
+ if (tpl.config) plan.config = tpl.config(op, w, d);
1502
+ if (tpl.reservations) tpl.reservations(plan);
1168
1503
  buildLot(plan, tpl, w, d, op, style, styleId, bc);
1169
1504
  return plan;
1170
1505
  }
1506
+ function placeWindows(params) {
1507
+ const { list, byId, floorWalls, windowDefs, floorIndex, ceiling, w, d, clearance } = params;
1508
+ const stretches = floorWalls.filter((p) => p.exterior && p.type === "wall");
1509
+ const found = [];
1510
+ const groups = [];
1511
+ let order = 0;
1512
+ for (const def of windowDefs) {
1513
+ const room = byId[def.room];
1514
+ if (!room) throw new Error("Window references a missing room: " + def.room);
1515
+ const edge = exteriorEdge(room, def.side, w, d, def.at);
1516
+ if (!edge) continue;
1517
+ const centres = def.n ? Array.from({ length: def.n }, (_, i) => edge.a0 + (edge.a1 - edge.a0) * (i + 1) / (def.n + 1)) : [def.at];
1518
+ for (const centre of centres) {
1519
+ const onEdge = stretches.filter((t) => t.axis === edge.axis && eq(edge.axis === "u" ? t.v0 : t.u0, edge.pos) && t.sides.includes(def.room));
1520
+ const start = (t) => edge.axis === "u" ? t.u0 : t.v0;
1521
+ const end = (t) => edge.axis === "u" ? t.u1 : t.v1;
1522
+ const gap = (t) => Math.min(Math.abs(centre - start(t)), Math.abs(centre - end(t)));
1523
+ const target = onEdge.find((t) => centre >= start(t) - TOL && centre <= end(t) + TOL) || onEdge.slice().sort((x, y) => gap(x) - gap(y))[0];
1524
+ if (!target) continue;
1525
+ let group = groups.find((x) => x.wall === target && x.def.room === def.room);
1526
+ if (!group) groups.push(group = { wall: target, edge, start: start(target), end: end(target), def, entries: [] });
1527
+ group.entries.push({ def, centre, order: order++ });
1528
+ }
1529
+ }
1530
+ for (const g of groups) {
1531
+ const room = byId[g.def.room];
1532
+ const bath = room.type === "bathroom";
1533
+ const length = g.end - g.start;
1534
+ 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 }));
1535
+ const fits = (l) => l.reduce((t, x) => t + x.width, 0) + (l.length + 1) * clearance <= length + TOL;
1536
+ while (its.length && !fits(its)) {
1537
+ const k = its.reduce((m, x, i, arr) => x.width > arr[m].width ? i : m, 0);
1538
+ if (its[k].width > 0.6 + TOL) its[k] = Object.assign({}, its[k], { width: Math.max(0.6, r2(its[k].width - 0.1)) });
1539
+ else its.pop();
1540
+ }
1541
+ if (!its.length) continue;
1542
+ const space = (length - its.reduce((t, x) => t + x.width, 0)) / (its.length + 1);
1543
+ let pos = g.start + space;
1544
+ for (const x of its) {
1545
+ const c = pos + x.width / 2;
1546
+ pos += x.width + space;
1547
+ const u = g.edge.axis === "u" ? c : g.edge.pos;
1548
+ const v = g.edge.axis === "u" ? g.edge.pos : c;
1549
+ let y0 = x.def.y0 || 1;
1550
+ let y1 = x.def.y1 || 2.15;
1551
+ if (bath) {
1552
+ y0 = Math.max(y0, 2.1);
1553
+ y1 = Math.min(Math.max(y1, y0 + 0.4), ceiling - 0.25);
1554
+ }
1555
+ 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 });
1556
+ }
1557
+ }
1558
+ for (const room of list) {
1559
+ if (WINDOWLESS_ROOM_TYPES.has(room.type) || found.some((j) => j.room === room.id)) continue;
1560
+ let best = null;
1561
+ for (const t of stretches) {
1562
+ if (!t.sides.includes(room.id)) continue;
1563
+ const i0 = t.axis === "u" ? t.u0 : t.v0;
1564
+ const i1 = t.axis === "u" ? t.u1 : t.v1;
1565
+ const length = i1 - i0;
1566
+ const width = Math.min(room.type === "bathroom" ? 0.6 : 1.2, length - 2 * clearance);
1567
+ if (width < 0.6 - TOL || best && best.length >= length) continue;
1568
+ const side = t.axis === "u" ? t.sides[0] === "outside" ? "front" : "back" : t.sides[0] === "outside" ? "left" : "right";
1569
+ const c = (i0 + i1) / 2;
1570
+ const pos = t.axis === "u" ? t.v0 : t.u0;
1571
+ const bath = room.type === "bathroom";
1572
+ 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 } };
1573
+ }
1574
+ if (best) found.push(best.window);
1575
+ }
1576
+ found.sort((x, y) => x._order - y._order);
1577
+ found.forEach((j, i) => {
1578
+ delete j._order;
1579
+ j.id = "w" + floorIndex + "_" + i;
1580
+ });
1581
+ return found;
1582
+ }
1171
1583
  function buildLot(plan, tpl, w, d, op, style, styleId, bc) {
1172
1584
  const seed = op.seed == null ? 1 : op.seed;
1173
1585
  const rFacade = createRng(seed, STREAM.facade);
@@ -1227,9 +1639,11 @@ var GameFloorplan = (() => {
1227
1639
  parts.sort((a, b) => b[2] - b[0] - (a[2] - a[0]));
1228
1640
  addZone("garden", "garden", "garden", parts, libs.garden);
1229
1641
  }
1230
- if (backDepth > 0 && cfg.yardZones) {
1231
- 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);
1642
+ const yard = cfg.yardZones;
1643
+ if (backDepth > 0 && yard) {
1644
+ 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);
1232
1645
  } else if (backDepth > 0) addZone("backyard", "backyard", "backyard", [[-marginL, d, w + marginR, d + backDepth]], libs.backyard);
1646
+ for (const q of cfg.extraZones || []) addZone(q.id, q.type, q.label || q.id, q.parts, q.library);
1233
1647
  if (marginL > 0.5) addZone("side_left", "side_yard", "side_left", [[-marginL, 0, 0, d]], libs.sideYard);
1234
1648
  if (marginR > 0.5) addZone("side_right", "side_yard", "side_right", [[w, 0, w + marginR, d]], libs.sideYard);
1235
1649
  const gates = [];
@@ -1287,12 +1701,14 @@ var GameFloorplan = (() => {
1287
1701
  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]] });
1288
1702
  }
1289
1703
  }
1704
+ if (tpl.paths) paths.push(...tpl.paths(plan, { gates, setback, marginLeft: marginL, marginRight: marginR, backyard: backDepth, w, d }));
1705
+ else addExteriorDoorPaths(plan, { gates, paths, zones: ZONES2, setback, marginLeft: marginL, marginRight: marginR, backyard: backDepth, w, target: cfg.doorTarget });
1290
1706
  plan.paths = paths;
1291
1707
  lot.paths = paths;
1292
1708
  const boundary = (rule) => {
1293
1709
  const type = pickWeighted(rLot, rule.type);
1294
1710
  const top = type === "railing" ? "none" : pickWeighted(rLot, rule.top);
1295
- const wallHeight = type === "railing" ? 1.2 : type === "fence" ? 2 : style.height;
1711
+ const wallHeight = type === "railing" ? 1.2 : type === "fence" ? style.fenceHeight ?? 2 : style.height;
1296
1712
  return { type, material: type === "wall" ? "block" : type === "fence" ? "wire_mesh" : "iron", height: wallHeight, top, totalHeight: r2(wallHeight + WALL_TOP_HEIGHTS[top]), thickness: WALL_THICKNESS };
1297
1713
  };
1298
1714
  const A = { front: boundary(style.front), left: boundary(style.sideYard), right: boundary(style.sideYard), back: boundary(style.back) };
@@ -1562,7 +1978,7 @@ var GameFloorplan = (() => {
1562
1978
  }
1563
1979
  return roomCenter;
1564
1980
  };
1565
- const free = (rect) => !zones.some((z) => overlaps(rect, z, room.type === "stairs" ? -0.011 : EPS));
1981
+ const free = (rect) => !!def.floorFree || !zones.some((z) => overlaps(rect, z, room.type === "stairs" ? -0.011 : EPS));
1566
1982
  const rule = def.facing || "center";
1567
1983
  let R = inner(room.parts[Math.min(def.part || 0, room.parts.length - 1)]);
1568
1984
  if (def.anchor === "wall" || def.anchor === "window") {
@@ -1808,6 +2224,8 @@ var GameFloorplan = (() => {
1808
2224
  };
1809
2225
  if (tpl.singleFloor) result.floorNumber = op.floor || 3;
1810
2226
  result.partyWalls = tpl.partyWalls || [];
2227
+ if (plan.config) result.config = plan.config;
2228
+ runResultExtensions(plan, result, seed, op);
1811
2229
  if (zone) {
1812
2230
  const report = { zone: zone.id, adjustments, violations: checkLot(zone, lot, Object.assign({}, result, { rooms: plan.rooms })) };
1813
2231
  result.zoning = report;
@@ -2077,6 +2495,214 @@ var GameFloorplan = (() => {
2077
2495
  }
2078
2496
  };
2079
2497
 
2498
+ // src/silhouettes/random.ts
2499
+ function createSilhouetteRandom(seed) {
2500
+ let state = seed;
2501
+ return () => {
2502
+ state |= 0;
2503
+ state = state + 1831565813 | 0;
2504
+ let t = Math.imul(state ^ state >>> 15, 1 | state);
2505
+ t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
2506
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
2507
+ };
2508
+ }
2509
+
2510
+ // src/silhouettes/builder.ts
2511
+ var ROOT = -1;
2512
+ var MAX_BOX_RADIUS = 0.15;
2513
+ var BOX_RADIUS_RATIO = 0.22;
2514
+ var PLAIN_BOX_RADIUS = 0.012;
2515
+ function boxRadius(width, height, depth) {
2516
+ const radius = Math.min(Math.min(width, height, depth) * BOX_RADIUS_RATIO, MAX_BOX_RADIUS);
2517
+ return radius < PLAIN_BOX_RADIUS ? 0 : radius;
2518
+ }
2519
+ function createSilhouetteBuilder(random) {
2520
+ const parts = [];
2521
+ const windows = [];
2522
+ const pick = (list) => list[Math.floor(random() * list.length)];
2523
+ const group = (x, y, z, ry = 0, parent = ROOT) => parts.push({ shape: "group", parent, x, y, z, ry }) - 1;
2524
+ const box = (x, y, z, width, height, depth, color, o = {}) => {
2525
+ 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) });
2526
+ };
2527
+ const prism = (x, y, z, width, height, depth, color, parent = ROOT) => {
2528
+ parts.push({ shape: "prism", parent, x, y, z, width, height, depth, color, ry: 0 });
2529
+ };
2530
+ const cylinder = (x, y, z, radiusTop, radiusBottom, height, color, parent = ROOT, segments = 14) => {
2531
+ parts.push({ shape: "cylinder", parent, x, y: y + height / 2, z, radiusTop, radiusBottom, height, color, segments });
2532
+ };
2533
+ const line = (points, color, parent = ROOT) => {
2534
+ parts.push({ shape: "line", parent, points, color });
2535
+ };
2536
+ const sign = (text, x, y, z, width, height, ry, colors, parent = ROOT) => {
2537
+ parts.push({ shape: "sign", parent, x, y, z, width, height, ry, text, ...colors });
2538
+ };
2539
+ const fence = (x0, z0, x1, z1, height, color, parent = ROOT) => {
2540
+ const length = Math.hypot(x1 - x0, z1 - z0);
2541
+ const count = Math.ceil(length / 3);
2542
+ for (let index = 0; index <= count; index++) {
2543
+ const t = index / count;
2544
+ cylinder(x0 + (x1 - x0) * t, 0, z0 + (z1 - z0) * t, 0.05, 0.05, height, color, parent, 5);
2545
+ }
2546
+ for (const level of [height * 0.35, height * 0.65, height * 0.98]) line([[x0, level, z0], [x1, level, z1]], 9079950, parent);
2547
+ };
2548
+ const finish = (footprint) => ({ parts, windows, footprint });
2549
+ return { random, pick, group, box, prism, cylinder, line, sign, fence, windows, finish };
2550
+ }
2551
+
2552
+ // src/silhouettes/building.ts
2553
+ var STOREY_HEIGHT = 3;
2554
+ var SHOP_AWNING_HEIGHT = 4.5;
2555
+ var WINDOW_COLUMN_WIDTH = 2.6;
2556
+ var SHOP_SIGNS = ["FARM\xC1CIA", "MAGAZINE", "LOT\xC9RICA", "PADARIA", "\xD3TICA", "CAL\xC7ADOS", "BAR"];
2557
+ var SIGN_COLORS = ["#ffd27a", "#e8dfcf", "#c4483b"];
2558
+ var DEFAULT_LIT_CHANCE = 0.07;
2559
+ function createCityBuilding(p) {
2560
+ const random = p.random ?? createSilhouetteRandom(p.seed ?? 7);
2561
+ const b = createSilhouetteBuilder(random);
2562
+ const { x, z, width: w, depth: d, height: h } = p;
2563
+ b.box(x, 0, z, w, h, d, p.color);
2564
+ b.box(x, h, z, w + 0.3, 0.35, d + 0.3, 2763308);
2565
+ 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);
2566
+ const hasShop = p.shopChance !== void 0 && p.shopChance > 0 && random() < p.shopChance;
2567
+ if (hasShop) {
2568
+ for (const face of [1, -1]) {
2569
+ const zFace = z + face * (d / 2);
2570
+ b.box(x, SHOP_AWNING_HEIGHT, zFace + face * 0.8, w, 0.2, 1.6, 2763308);
2571
+ b.box(x, 0, zFace + face * 0.03, w * 0.6, 3.5, 0.05, 4869198);
2572
+ const text = b.pick(SHOP_SIGNS);
2573
+ 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) });
2574
+ }
2575
+ }
2576
+ addWindows(b, p, hasShop ? SHOP_AWNING_HEIGHT : 0);
2577
+ return b.finish({ x, z, width: w, depth: d, height: h + 0.35 });
2578
+ }
2579
+ function addWindows(b, p, base) {
2580
+ const { x, z, width: w, depth: d, height: h } = p;
2581
+ const floors = Math.max(1, Math.floor((h - 1) / STOREY_HEIGHT));
2582
+ 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]];
2583
+ for (const [ox, oz, faceWidth, ry] of faces) {
2584
+ const columns = Math.max(1, Math.floor(faceWidth / WINDOW_COLUMN_WIDTH));
2585
+ for (let floor = 0; floor < floors; floor++) {
2586
+ const y = base + 1.8 + floor * STOREY_HEIGHT;
2587
+ for (let column = 0; column < columns; column++) {
2588
+ const u = (column - (columns - 1) / 2) * WINDOW_COLUMN_WIDTH;
2589
+ 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) });
2590
+ }
2591
+ }
2592
+ }
2593
+ }
2594
+
2595
+ // src/silhouettes/houses.ts
2596
+ var LIT_WINDOW = 16757854;
2597
+ var DARK_WINDOW = 1053979;
2598
+ var DEFAULT_ROOF = 5913130;
2599
+ var randomOf = (o) => o.random ?? createSilhouetteRandom(o.seed ?? 7);
2600
+ var pane = (isLit) => ({ color: isLit ? LIT_WINDOW : DARK_WINDOW, emissive: isLit });
2601
+ function createTwoStoreyHouse(p) {
2602
+ const b = createSilhouetteBuilder(randomOf(p));
2603
+ const { width: w, depth: d } = p;
2604
+ const g = b.group(p.x, 0, p.z, p.ry);
2605
+ b.box(0, 0, 0, w, 6, d, p.color, { parent: g });
2606
+ b.prism(0, 6, 0, w + 0.4, 1.4, d + 0.4, p.roofColor ?? DEFAULT_ROOF, g);
2607
+ b.box(0, 3.1, d / 2 + 0.5, w * 0.6, 0.15, 1, 6974054, { parent: g });
2608
+ for (const floor of [0, 1]) {
2609
+ for (const side of [-1, 1]) {
2610
+ const { color, emissive } = pane(b.random() < 0.12);
2611
+ b.box(side * w * 0.26, 1.1 + floor * 3, d / 2 + 0.03, 1.1, 1.2, 0.04, color, { parent: g, emissive });
2612
+ }
2613
+ }
2614
+ b.box(0, 0, d / 2 + 0.02, 1, 2.1, 0.05, 2759956, { parent: g });
2615
+ return b.finish({ x: p.x, z: p.z, width: w, depth: d, height: 7.4 });
2616
+ }
2617
+ function createSlabHouse(p) {
2618
+ const b = createSilhouetteBuilder(randomOf(p));
2619
+ const { width: w, depth: d, floors } = p;
2620
+ const g = b.group(p.x, 0, p.z, 0);
2621
+ for (let floor = 0; floor < floors; floor++) {
2622
+ const isTop = floor === floors - 1;
2623
+ b.box(0, floor * 2.9, 0, w, 2.9, d, isTop && b.random() < 0.5 ? 10119760 : p.color, { parent: g });
2624
+ const { color, emissive } = pane(b.random() < 0.1);
2625
+ b.box(0, floor * 2.9 + 1.1, d / 2 + 0.03, 1, 1, 0.04, color, { parent: g, emissive });
2626
+ }
2627
+ b.cylinder(w * 0.25, floors * 2.9, -d * 0.2, 0.55, 0.55, 0.9, 2775712, g, 10);
2628
+ 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 });
2629
+ 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 });
2630
+ return b.finish({ x: p.x, z: p.z, width: w, depth: d, height: floors * 2.9 + 1 });
2631
+ }
2632
+ function createColonialHouse(p) {
2633
+ const b = createSilhouetteBuilder(randomOf(p));
2634
+ const { width: w } = p;
2635
+ const g = b.group(p.x, 0, p.z, 0);
2636
+ b.box(0, 0, 0, w, 4.2, 10, p.color, { parent: g });
2637
+ b.box(0, 4.2, 4.6, w, 0.8, 0.25, p.color, { parent: g });
2638
+ b.prism(0, 4.2, -0.4, w, 1.4, 9.6, 8014386, g);
2639
+ const count = Math.max(1, Math.floor(w / 2.2));
2640
+ for (let index = 0; index < count; index++) {
2641
+ const u = (index - (count - 1) / 2) * 2.2;
2642
+ const isDoor = index === 0 && p.hasDoor;
2643
+ const lit = isDoor ? false : b.random() < 0.15;
2644
+ const color = isDoor ? 3810326 : lit ? LIT_WINDOW : 2767434;
2645
+ b.box(u, 1, 5.02, 0.9, 2, 0.05, color, { parent: g, emissive: lit });
2646
+ }
2647
+ return b.finish({ x: p.x, z: p.z, width: w, depth: 10, height: 6 });
2648
+ }
2649
+ function createChalet(p) {
2650
+ const b = createSilhouetteBuilder(randomOf(p));
2651
+ const g = b.group(p.x, 0, p.z, p.ry);
2652
+ b.box(0, 0, 0, 7, 0.8, 9, 5920336, { parent: g });
2653
+ b.box(0, 0.8, 0, 7, 4.2, 9, p.color ?? 7031344, { parent: g });
2654
+ b.prism(0, 5, 0, 7.6, 4, 9.6, 2894376, g);
2655
+ b.box(2.2, 5, -1, 0.8, 4.2, 0.8, 5920336, { parent: g });
2656
+ const { color, emissive } = pane(b.random() < 0.3);
2657
+ b.box(0, 1.6, 4.52, 1.3, 1.3, 0.04, color, { parent: g, emissive });
2658
+ b.box(0, 6.2, 4.6, 1, 1, 0.04, DARK_WINDOW, { parent: g });
2659
+ return b.finish({ x: p.x, z: p.z, width: 7.6, depth: 9.6, height: 9 });
2660
+ }
2661
+ function createCompanyHouse(p) {
2662
+ const b = createSilhouetteBuilder(randomOf(p));
2663
+ const g = b.group(p.x, 0, p.z, 0);
2664
+ b.box(0, 0, 0, 6, 3, 9, p.color ?? 14209216, { parent: g });
2665
+ b.prism(0, 3, 0, 6.4, 1.6, 9.4, 9062962, g);
2666
+ b.box(0, 0, 4.52, 0.9, 2.1, 0.05, 3811868, { parent: g });
2667
+ b.box(1.8, 1.1, 4.52, 1, 1, 0.04, DARK_WINDOW, { parent: g });
2668
+ b.fence(-3.2, 6.5, 3.2, 6.5, 0.9, 14735560, g);
2669
+ return b.finish({ x: p.x, z: p.z, width: 6.4, depth: 13, height: 4.6 });
2670
+ }
2671
+ function createStiltHouse(p) {
2672
+ const b = createSilhouetteBuilder(randomOf(p));
2673
+ const g = b.group(p.x, 0, p.z, 0);
2674
+ 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);
2675
+ b.box(0, 1.5, 0, 5, 0.2, 7, 5916208, { parent: g });
2676
+ b.box(0, 1.7, 0, 4.6, 2.6, 6.4, p.color ?? 6969926, { parent: g });
2677
+ b.prism(0, 4.3, 0, 5.2, 1.1, 7, 8026744, g);
2678
+ b.box(0, 1.5, 5, 1.2, 0.15, 4, 5916208, { parent: g });
2679
+ return b.finish({ x: p.x, z: p.z, width: 5.2, depth: 9, height: 5.4 });
2680
+ }
2681
+ function createFarmhouse(p) {
2682
+ const b = createSilhouetteBuilder(randomOf(p));
2683
+ const g = b.group(p.x, 0, p.z, 0);
2684
+ b.box(0, 0, 0, 15, 3.2, 11, 14208952, { parent: g });
2685
+ b.prism(0, 3.2, 0, 17, 2.2, 13, 9062962, g);
2686
+ b.box(0, 0, 6.8, 15, 0.25, 2.6, 6969926, { parent: g });
2687
+ 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);
2688
+ b.box(-3, 1, 5.52, 1.2, 1.2, 0.04, LIT_WINDOW, { parent: g, emissive: true });
2689
+ return b.finish({ x: p.x, z: p.z, width: 17, depth: 13, height: 5.4 });
2690
+ }
2691
+ function createDetachedHouse(p) {
2692
+ const b = createSilhouetteBuilder(randomOf(p));
2693
+ const { width: w, depth: d } = p;
2694
+ const g = b.group(p.x, 0, p.z, p.ry);
2695
+ b.box(0, 0, 0, w, 3, d, p.color, { parent: g });
2696
+ b.prism(0, 3, 0, w + 0.5, 1.6, d + 0.5, p.roofColor ?? DEFAULT_ROOF, g);
2697
+ b.box(0, 0, d / 2 + 0.02, 1, 2.1, 0.05, 2759956, { parent: g });
2698
+ for (const side of [-1, 1]) {
2699
+ const { color, emissive } = pane(b.random() < 0.15);
2700
+ b.box(side * w * 0.28, 1.1, d / 2 + 0.03, 1.1, 1.1, 0.04, color, { parent: g, emissive });
2701
+ 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 });
2702
+ }
2703
+ return b.finish({ x: p.x, z: p.z, width: w + 0.5, depth: d + 0.5, height: 4.6 });
2704
+ }
2705
+
2080
2706
  // src/index.ts
2081
2707
  registerGroundItems();
2082
2708
  registerPermeableGround("lawn", "dirt");
@@ -2109,6 +2735,13 @@ var GameFloorplan = (() => {
2109
2735
  registerState,
2110
2736
  setStateDistribution,
2111
2737
  registerLocale,
2738
+ registerResultExtension,
2739
+ registerFindsProvider,
2740
+ findsFor,
2741
+ findsAnswer,
2742
+ registerViewerLayer,
2743
+ viewerLayers: VIEWER_LAYERS,
2744
+ blocksWindow,
2112
2745
  // zoning
2113
2746
  defineZone,
2114
2747
  getZone,
@@ -2123,6 +2756,7 @@ var GameFloorplan = (() => {
2123
2756
  catalog: CATALOG,
2124
2757
  libraries: LIBRARIES,
2125
2758
  styles: STYLES,
2759
+ paletteStyles: PALETTE_STYLES,
2126
2760
  createRng,
2127
2761
  mulberry32,
2128
2762
  hashString,
@@ -2130,7 +2764,7 @@ var GameFloorplan = (() => {
2130
2764
  windowZone,
2131
2765
  openingZones,
2132
2766
  color: { lstar, hex: hexString, hsl: intToHsl, HIGH_SATURATION, MIN_WALL_FLOOR_CONTRAST, MIN_FABRIC_FLOOR_CONTRAST },
2133
- constants: { WALL_THICKNESS, DOOR_CLEARANCE }
2767
+ constants: { WALL_THICKNESS, DOOR_CLEARANCE, PLAYER_RADIUS, DOOR_EDGE_CLEARANCE, WINDOW_EDGE_CLEARANCE, OVERHEAD_TOP, CAR_CLEARANCE }
2134
2768
  };
2135
2769
  return __toCommonJS(index_exports);
2136
2770
  })();