@adatechnology/game-floorplan 0.2.1 → 0.3.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
@@ -1,134 +1,120 @@
1
- // src/constants.ts
2
- var WALL_THICKNESS = 0.15;
3
- var MARGIN = WALL_THICKNESS / 2;
4
- var EPS = 1e-6;
5
- var TOL = 1e-3;
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
1
  // 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" };
2
+ var createRegistries = () => ({
3
+ CATALOG: {},
4
+ LIBRARIES: {},
5
+ LIBRARY_ALIAS: {},
6
+ TEMPLATES: {},
7
+ STYLES: {},
8
+ STYLE_ALIAS: {},
9
+ PALETTE_STYLES: {},
10
+ ROOM_COLORS: [],
11
+ PASSES: [],
12
+ DECOR_PROFILES: {},
13
+ GROUND_OVERLAYS: [],
14
+ STATE_PLUGINS: { normal: { apply: () => [] } },
15
+ WALL_TOP_HEIGHTS: { none: 0 },
16
+ SEED_KEYS: { rooms: {}, items: {} },
17
+ stateDistribution: { weights: null, seedKey: "state" },
18
+ RESULT_EXTENSIONS: [],
19
+ FINDS_PROVIDERS: [],
20
+ VIEWER_LAYERS: [],
21
+ LOCALES: {},
22
+ ZONES: {},
23
+ PERMEABLE_GROUNDS: /* @__PURE__ */ new Set()
24
+ });
25
+ var defaultRegistries;
26
+ var activeRegistries;
27
+ var getDefaultRegistries = () => defaultRegistries ?? (defaultRegistries = createRegistries());
28
+ var registries = () => activeRegistries ?? getDefaultRegistries();
29
+ function withRegistries(scope, run) {
30
+ const previous = activeRegistries;
31
+ activeRegistries = scope;
32
+ try {
33
+ return run();
34
+ } finally {
35
+ activeRegistries = previous;
36
+ }
37
+ }
49
38
  function registerItem(def) {
50
39
  const h = def.h;
51
40
  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;
41
+ registries().CATALOG[def.type] = item;
53
42
  return item;
54
43
  }
55
44
  function registerItems(list) {
56
45
  for (const d of list) registerItem(d);
57
46
  }
58
47
  function registerLibrary(id, library) {
59
- LIBRARIES[id] = library;
48
+ registries().LIBRARIES[id] = library;
60
49
  }
61
50
  function registerLibraryAlias(alias, id) {
62
- LIBRARY_ALIAS[alias] = id;
51
+ registries().LIBRARY_ALIAS[alias] = id;
63
52
  }
64
53
  function registerTemplate(t) {
65
- TEMPLATES[t.id] = t;
54
+ registries().TEMPLATES[t.id] = t;
66
55
  return t;
67
56
  }
68
57
  function registerStyle(style) {
69
- STYLES[style.id] = style;
70
- for (const a of style.aliases || []) STYLE_ALIAS[a] = style.id;
58
+ registries().STYLES[style.id] = style;
59
+ for (const a of style.aliases || []) registries().STYLE_ALIAS[a] = style.id;
71
60
  }
72
61
  function registerPaletteStyle(id, def) {
73
- PALETTE_STYLES[id] = def;
62
+ registries().PALETTE_STYLES[id] = def;
74
63
  }
75
64
  function registerRoomColors(list) {
76
65
  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);
66
+ const i = registries().ROOM_COLORS.findIndex((x) => x.type === d.type);
67
+ if (i >= 0) registries().ROOM_COLORS.splice(i, 1);
68
+ registries().ROOM_COLORS.push(d);
80
69
  }
81
- ROOM_COLORS.sort((a, b) => a.order - b.order);
70
+ registries().ROOM_COLORS.sort((a, b) => a.order - b.order);
82
71
  }
83
72
  function registerDecorProfile(styleId, profile) {
84
- DECOR_PROFILES[styleId] = profile;
73
+ registries().DECOR_PROFILES[styleId] = profile;
85
74
  }
86
75
  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 });
76
+ const i = registries().PASSES.findIndex((p) => p.name === name);
77
+ if (i >= 0) registries().PASSES[i] = { name, run };
78
+ else registries().PASSES.push({ name, run });
90
79
  }
91
80
  function registerGroundOverlay(rule) {
92
- GROUND_OVERLAYS.push(rule);
81
+ registries().GROUND_OVERLAYS.push(rule);
93
82
  }
94
83
  function registerWallTop(id, height) {
95
- WALL_TOP_HEIGHTS[id] = height;
84
+ registries().WALL_TOP_HEIGHTS[id] = height;
96
85
  }
97
86
  function registerSeedKeys(keys) {
98
- Object.assign(SEED_KEYS.rooms, keys.rooms);
99
- Object.assign(SEED_KEYS.items, keys.items);
87
+ Object.assign(registries().SEED_KEYS.rooms, keys.rooms);
88
+ Object.assign(registries().SEED_KEYS.items, keys.items);
100
89
  }
101
90
  function registerState(name, plugin) {
102
- STATE_PLUGINS[name] = plugin;
91
+ registries().STATE_PLUGINS[name] = plugin;
103
92
  }
104
93
  function setStateDistribution(weights, seedKey = "state") {
105
- stateDistribution.weights = weights;
106
- stateDistribution.seedKey = seedKey;
94
+ registries().stateDistribution.weights = weights;
95
+ registries().stateDistribution.seedKey = seedKey;
107
96
  }
108
- var listStates = () => Object.keys(STATE_PLUGINS);
97
+ var listStates = () => Object.keys(registries().STATE_PLUGINS);
109
98
  function resolveStyleId(id) {
110
99
  const s = id ?? "";
111
- return STYLE_ALIAS[s] ?? s;
100
+ return registries().STYLE_ALIAS[s] ?? s;
112
101
  }
113
102
  function styleOf(id) {
114
- return STYLES[id] || STYLES.budget || Object.values(STYLES)[0];
103
+ return registries().STYLES[id] || registries().STYLES.budget || Object.values(registries().STYLES)[0];
115
104
  }
116
- var RESULT_EXTENSIONS = [];
117
- var FINDS_PROVIDERS = [];
118
105
  function registerResultExtension(name, run, options = {}) {
119
106
  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);
107
+ const i = registries().RESULT_EXTENSIONS.findIndex((x) => x.name === name);
108
+ if (i >= 0) registries().RESULT_EXTENSIONS[i] = entry;
109
+ else registries().RESULT_EXTENSIONS.push(entry);
123
110
  }
124
111
  function registerFindsProvider(provider) {
125
- FINDS_PROVIDERS.push(provider);
112
+ registries().FINDS_PROVIDERS.push(provider);
126
113
  }
127
- var VIEWER_LAYERS = [];
128
114
  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);
115
+ const i = registries().VIEWER_LAYERS.findIndex((x) => x.id === layer.id);
116
+ if (i >= 0) registries().VIEWER_LAYERS[i] = layer;
117
+ else registries().VIEWER_LAYERS.push(layer);
132
118
  }
133
119
 
134
120
  // src/rng.ts
@@ -208,7 +194,7 @@ function orderOf(extensions) {
208
194
  return ordered;
209
195
  }
210
196
  function runResultExtensions(plan, result, seed, options) {
211
- for (const extension of orderOf(RESULT_EXTENSIONS)) {
197
+ for (const extension of orderOf(registries().RESULT_EXTENSIONS)) {
212
198
  result[extension.name] = extension.run({ plan, result, seed, rng: createRng(seed, extension.seedKey), options });
213
199
  }
214
200
  }
@@ -219,13 +205,45 @@ function findsAnswer(query) {
219
205
  return answerOf(query);
220
206
  }
221
207
  function answerOf(query) {
222
- for (const provider of FINDS_PROVIDERS) {
208
+ for (const provider of registries().FINDS_PROVIDERS) {
223
209
  const stacks = provider.findsFor(query);
224
210
  if (stacks !== null) return stacks;
225
211
  }
226
212
  return null;
227
213
  }
228
214
 
215
+ // src/constants.ts
216
+ var WALL_THICKNESS = 0.15;
217
+ var MARGIN = WALL_THICKNESS / 2;
218
+ var EPS = 1e-6;
219
+ var TOL = 1e-3;
220
+ var DOOR_CLEARANCE = 0.9;
221
+ var PLAYER_RADIUS = 0.24;
222
+ var DOOR_EDGE_CLEARANCE = 0.12;
223
+ var WINDOW_EDGE_CLEARANCE = 0.3;
224
+ var OVERHEAD_TOP = 2.15;
225
+ var WINDOWLESS_ROOM_TYPES = /* @__PURE__ */ new Set([
226
+ "stockroom",
227
+ "kitchen",
228
+ "laundry",
229
+ "bathroom",
230
+ "void",
231
+ "elevator",
232
+ "stairs",
233
+ "pantry",
234
+ "closet",
235
+ "corridor",
236
+ "hall",
237
+ "balcony",
238
+ "terrace",
239
+ "garage",
240
+ "storage",
241
+ "porch",
242
+ "tent",
243
+ "outhouse"
244
+ ]);
245
+ var CAR_CLEARANCE = { driver: 0.8, side: 0.6, end: 0.6 };
246
+
229
247
  // src/geometry.ts
230
248
  var r2 = (x) => Math.round(x * 100) / 100;
231
249
  var clamp = (x, a, b) => Math.max(a, Math.min(b, x));
@@ -559,7 +577,7 @@ function createGrid(plan, room, ignored, radius = plan.playerRadius) {
559
577
  const nz = Math.ceil((v1 - v0) / gs);
560
578
  const insideCells = new Uint8Array(nx * nz);
561
579
  const insets = room.outdoor ? usableParts(room, 0.08) : room.parts;
562
- const bodies = room.outdoor ? null : wallBodies(plan, room.floor);
580
+ const bodies = room.outdoor ? null : wallBodies(plan, room.floor).filter((r) => r[0] - radius <= u1 && r[2] + radius >= u0 && r[1] - radius <= v1 && r[3] + radius >= v0);
563
581
  const tooClose = (u, v) => bodies.some((r) => {
564
582
  const dx = Math.max(r[0] - u, 0, u - r[2]);
565
583
  const dz = Math.max(r[1] - v, 0, v - r[3]);
@@ -660,7 +678,7 @@ function candidatesOf(cats) {
660
678
  const out = [];
661
679
  for (const [cat, weight] of Object.entries(cats)) {
662
680
  const exact = cat[0] === "=";
663
- const its = Object.values(CATALOG).filter((i) => !i.decor && (exact ? i.type === cat.slice(1) : !i.exactOnly && (i.category === cat || i.type === cat)));
681
+ const its = Object.values(registries().CATALOG).filter((i) => !i.decor && (exact ? i.type === cat.slice(1) : !i.exactOnly && (i.category === cat || i.type === cat)));
664
682
  for (const it of its) out.push({ it, weight: weight / its.length });
665
683
  }
666
684
  return out;
@@ -751,7 +769,7 @@ function fillRoom(plan, room, slots2, rng) {
751
769
  low: it.low,
752
770
  aabb: r.map(r2),
753
771
  _r: r,
754
- _seedId: SEED_KEYS.items[id] ?? id
772
+ _seedId: registries().SEED_KEYS.items[id] ?? id
755
773
  };
756
774
  if (it.overhead) m.overhead = !overheadOff;
757
775
  if (it.category === "car") {
@@ -902,9 +920,9 @@ var placementKit = {
902
920
  clearRect,
903
921
  pickWeighted,
904
922
  createRng,
905
- roomSeedKey: (room) => room.seedKey ?? SEED_KEYS.rooms[room.id] ?? room.id,
923
+ roomSeedKey: (room) => room.seedKey ?? registries().SEED_KEYS.rooms[room.id] ?? room.id,
906
924
  shuffle,
907
- facingRotation: (f, type) => rotationFor(f, CATALOG[type].front),
925
+ facingRotation: (f, type) => rotationFor(f, registries().CATALOG[type].front),
908
926
  cutIntervals,
909
927
  blocksWindow
910
928
  };
@@ -912,7 +930,7 @@ function createPlacementContext(plan, items) {
912
930
  const added = [];
913
931
  const counters = {};
914
932
  const make = (room, type, r, f, o) => {
915
- const it = CATALOG[type];
933
+ const it = registries().CATALOG[type];
916
934
  const n = counters[room.id + type] = (counters[room.id + type] || 0) + 1;
917
935
  return Object.assign(
918
936
  {
@@ -958,9 +976,9 @@ function createPlacementContext(plan, items) {
958
976
  const gridOf = (c) => gridCache[c.id] || (gridCache[c.id] = createGrid(plan, c, ignored));
959
977
  function placeOnFloor(room, type, rng2, optsIn) {
960
978
  const opts = Object.assign({}, optsIn || {});
961
- const it0 = CATALOG[type];
979
+ const it0 = registries().CATALOG[type];
962
980
  if (it0.flat) opts.flat = true;
963
- const it = CATALOG[type];
981
+ const it = registries().CATALOG[type];
964
982
  const inners = usableParts(room, 0);
965
983
  const solidList = solidsOfRoom(room);
966
984
  const zones = zonesOfFloor(plan, room.floor);
@@ -1039,7 +1057,7 @@ function groundCover(plan, items, ctx) {
1039
1057
  z.parts.forEach((p, i) => groundPiece(z, z.ground, p, 0, i));
1040
1058
  let k = 0;
1041
1059
  const clip = (r) => z.parts.map((p) => [Math.max(r[0], p[0]), Math.max(r[1], p[1]), Math.min(r[2], p[2]), Math.min(r[3], p[3])]).filter((q) => q[2] - q[0] > 0.3 && q[3] - q[1] > 0.3).sort((a, b) => areaOf(b) - areaOf(a))[0];
1042
- for (const rule of GROUND_OVERLAYS) {
1060
+ for (const rule of registries().GROUND_OVERLAYS) {
1043
1061
  const triggers = items.filter((m) => m.room === z.id && m.category === rule.category);
1044
1062
  if (rule.merge) {
1045
1063
  if (triggers.length && z.ground !== rule.unlessGround) {
@@ -1059,19 +1077,20 @@ function groundCover(plan, items, ctx) {
1059
1077
  function runPostProcess(plan, items, seed) {
1060
1078
  const ctx = createPlacementContext(plan, items);
1061
1079
  const rng = createRng(seed, STREAM.decor);
1062
- for (const pass of PASSES) pass.run({ plan, items, seed, rng, ctx, kit: placementKit, catalog: CATALOG, profiles: DECOR_PROFILES });
1080
+ for (const pass of registries().PASSES) pass.run({ plan, items, seed, rng, ctx, kit: placementKit, catalog: registries().CATALOG, profiles: registries().DECOR_PROFILES });
1063
1081
  groundCover(plan, items, ctx);
1064
1082
  return ctx;
1065
1083
  }
1066
1084
 
1067
1085
  // src/locale.ts
1068
- var locales = {};
1069
1086
  function registerLocale(code, table) {
1087
+ const { LOCALES: locales } = registries();
1070
1088
  const cur = locales[code] = locales[code] || {};
1071
1089
  for (const ns of Object.keys(table)) cur[ns] = Object.assign(cur[ns] || {}, table[ns]);
1072
1090
  }
1073
- var listLocales = () => Object.keys(locales);
1091
+ var listLocales = () => Object.keys(registries().LOCALES);
1074
1092
  function translate(ns, key, locale = "en", vars) {
1093
+ const { LOCALES: locales } = registries();
1075
1094
  const raw = locales[locale]?.[ns]?.[key] ?? locales.en?.[ns]?.[key] ?? key;
1076
1095
  return vars ? raw.replace(/\{(\w+)\}/g, (m, k) => k in vars ? String(vars[k]) : m) : raw;
1077
1096
  }
@@ -1200,22 +1219,20 @@ function addExteriorDoorPaths(plan, c) {
1200
1219
  }
1201
1220
 
1202
1221
  // src/zoning.ts
1203
- var ZONES = {};
1204
- var PERMEABLE_GROUNDS = /* @__PURE__ */ new Set();
1205
1222
  function defineZone(zone) {
1206
1223
  if (!zone || !zone.id) throw new Error("defineZone: id is required");
1207
- ZONES[zone.id] = zone;
1224
+ registries().ZONES[zone.id] = zone;
1208
1225
  return zone;
1209
1226
  }
1210
- var getZone = (id) => ZONES[id];
1211
- var listZones = () => Object.values(ZONES);
1227
+ var getZone = (id) => registries().ZONES[id];
1228
+ var listZones = () => Object.values(registries().ZONES);
1212
1229
  function registerPermeableGround(...types) {
1213
- for (const t of types) PERMEABLE_GROUNDS.add(t);
1230
+ for (const t of types) registries().PERMEABLE_GROUNDS.add(t);
1214
1231
  }
1215
1232
  function resolveZone(z) {
1216
1233
  if (!z) return null;
1217
1234
  if (typeof z === "string") {
1218
- const found = ZONES[z];
1235
+ const found = registries().ZONES[z];
1219
1236
  if (!found) throw new Error("Unknown zone: " + z);
1220
1237
  return found;
1221
1238
  }
@@ -1261,7 +1278,7 @@ function checkLot(zoneIn, lot, plan) {
1261
1278
  const far = m.built / area;
1262
1279
  if (far > zone.maxFar + 1e-9) out.push({ rule: "far", actual: r2(far), limit: zone.maxFar });
1263
1280
  let permeable = 0;
1264
- for (const z of lot.zones) if (PERMEABLE_GROUNDS.has(z.ground)) for (const p of z.parts) permeable += (p[2] - p[0]) * (p[3] - p[1]);
1281
+ for (const z of lot.zones) if (registries().PERMEABLE_GROUNDS.has(z.ground)) for (const p of z.parts) permeable += (p[2] - p[0]) * (p[3] - p[1]);
1265
1282
  const permeability = permeable / area;
1266
1283
  if (permeability < zone.permeableMin - 1e-9) out.push({ rule: "permeable", actual: r2(permeability), limit: zone.permeableMin });
1267
1284
  return out;
@@ -1299,14 +1316,14 @@ function clampLotToZone(cfg, zone, adjustments, partyWalls = []) {
1299
1316
  }
1300
1317
  var ADJUSTABLE_ZONES = /* @__PURE__ */ new Set(["garden", "backyard", "side_yard"]);
1301
1318
  function enforcePermeability(lot, zones, zone, adjustments) {
1302
- const target = [...PERMEABLE_GROUNDS][0];
1319
+ const target = [...registries().PERMEABLE_GROUNDS][0];
1303
1320
  if (!target) return;
1304
1321
  const [u0, v0, u1, v1] = lot.rect;
1305
1322
  const area = (u1 - u0) * (v1 - v0);
1306
1323
  const areaOfZone = (z) => z.parts.reduce((s, p) => s + (p[2] - p[0]) * (p[3] - p[1]), 0);
1307
- let permeable = zones.filter((z) => PERMEABLE_GROUNDS.has(z.ground)).reduce((s, z) => s + areaOfZone(z), 0);
1324
+ let permeable = zones.filter((z) => registries().PERMEABLE_GROUNDS.has(z.ground)).reduce((s, z) => s + areaOfZone(z), 0);
1308
1325
  if (permeable / area >= zone.permeableMin) return;
1309
- const candidates = zones.filter((z) => ADJUSTABLE_ZONES.has(z.type) && !PERMEABLE_GROUNDS.has(z.ground)).sort((a, b) => areaOfZone(b) - areaOfZone(a));
1326
+ const candidates = zones.filter((z) => ADJUSTABLE_ZONES.has(z.type) && !registries().PERMEABLE_GROUNDS.has(z.ground)).sort((a, b) => areaOfZone(b) - areaOfZone(a));
1310
1327
  for (const z of candidates) {
1311
1328
  if (permeable / area >= zone.permeableMin) break;
1312
1329
  adjustments.push({ field: "ground:" + z.id, from: z.ground, to: target, reason: "permeable_min" });
@@ -1539,9 +1556,9 @@ function buildLot(plan, tpl, w, d, op, style, styleId, bc) {
1539
1556
  paths: [],
1540
1557
  pool: !!cfg.pool
1541
1558
  };
1542
- const ZONES2 = [];
1559
+ const ZONES = [];
1543
1560
  const libs = Object.assign({ garden: "garden", backyard: "backyard", parking: "parking", sideYard: "side_yard" }, cfg.libraries || {});
1544
- const addZone = (id, type, label, parts, library) => ZONES2.push(Object.assign(normalizeRoom({ id, type, label, rect: parts[0], parts: parts.slice(1), library, outdoor: true }, -1), { floor: -1, outdoor: true }));
1561
+ const addZone = (id, type, label, parts, library) => ZONES.push(Object.assign(normalizeRoom({ id, type, label, rect: parts[0], parts: parts.slice(1), library, outdoor: true }, -1), { floor: -1, outdoor: true }));
1545
1562
  if (setback > 0) {
1546
1563
  let parts = [[-marginL, -setback, w + marginR, 0]];
1547
1564
  if (cfg.parking) {
@@ -1621,14 +1638,14 @@ function buildLot(plan, tpl, w, d, op, style, styleId, bc) {
1621
1638
  }
1622
1639
  }
1623
1640
  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 });
1641
+ else addExteriorDoorPaths(plan, { gates, paths, zones: ZONES, setback, marginLeft: marginL, marginRight: marginR, backyard: backDepth, w, target: cfg.doorTarget });
1625
1642
  plan.paths = paths;
1626
1643
  lot.paths = paths;
1627
1644
  const boundary = (rule) => {
1628
1645
  const type = pickWeighted(rLot, rule.type);
1629
1646
  const top = type === "railing" ? "none" : pickWeighted(rLot, rule.top);
1630
1647
  const wallHeight = type === "railing" ? 1.2 : type === "fence" ? style.fenceHeight ?? 2 : style.height;
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 };
1648
+ return { type, material: type === "wall" ? "block" : type === "fence" ? "wire_mesh" : "iron", height: wallHeight, top, totalHeight: r2(wallHeight + registries().WALL_TOP_HEIGHTS[top]), thickness: WALL_THICKNESS };
1632
1649
  };
1633
1650
  const A = { front: boundary(style.front), left: boundary(style.sideYard), right: boundary(style.sideYard), back: boundary(style.back) };
1634
1651
  const seg = (side, u0, v0, u1, v1) => {
@@ -1661,10 +1678,10 @@ function buildLot(plan, tpl, w, d, op, style, styleId, bc) {
1661
1678
  else if (backDepth > 0) seg("left", 0, d, 0, d + backDepth);
1662
1679
  if (marginR > 0.5) seg("right", w + marginR, vi, w + marginR, d + backDepth);
1663
1680
  else if (backDepth > 0) seg("right", w, d, w, d + backDepth);
1664
- for (const z of ZONES2) z.ground = (cfg.grounds || {})[z.id] || (cfg.grounds || {})[z.type] || pickWeighted(rLot, style.grounds[z.type] || { concrete: 1 });
1665
- if (bc.zone) enforcePermeability(lot, ZONES2, bc.zone, bc.adjustments || []);
1666
- plan.zones = ZONES2;
1667
- lot.zones = ZONES2;
1681
+ for (const z of ZONES) z.ground = (cfg.grounds || {})[z.id] || (cfg.grounds || {})[z.type] || pickWeighted(rLot, style.grounds[z.type] || { concrete: 1 });
1682
+ if (bc.zone) enforcePermeability(lot, ZONES, bc.zone, bc.adjustments || []);
1683
+ plan.zones = ZONES;
1684
+ lot.zones = ZONES;
1668
1685
  plan.lot = lot;
1669
1686
  }
1670
1687
 
@@ -1714,8 +1731,8 @@ var hexString = (n) => "#" + (n >>> 0).toString(16).padStart(6, "0");
1714
1731
  function palette(o) {
1715
1732
  const opt = o || {};
1716
1733
  let style = resolveStyleId(opt.style || "budget");
1717
- if (!PALETTE_STYLES[style]) style = PALETTE_STYLES.mid ? "mid" : Object.keys(PALETTE_STYLES)[0];
1718
- const def = PALETTE_STYLES[style];
1734
+ if (!registries().PALETTE_STYLES[style]) style = registries().PALETTE_STYLES.mid ? "mid" : Object.keys(registries().PALETTE_STYLES)[0];
1735
+ const def = registries().PALETTE_STYLES[style];
1719
1736
  const state = opt.state || "normal";
1720
1737
  const seed = opt.seed == null ? 1 : opt.seed;
1721
1738
  const rng = createRng(seed, STREAM.palettePrefix + (def.seedKey ?? style));
@@ -1733,14 +1750,14 @@ function palette(o) {
1733
1750
  const { wallMode, sMax } = early;
1734
1751
  const R = Object.assign({}, early.roles);
1735
1752
  const ctx = Object.assign({ style, sMax, wallMode, generalFloor: early.generalFloor }, draw);
1736
- for (const c of ROOM_COLORS) {
1753
+ for (const c of registries().ROOM_COLORS) {
1737
1754
  const [wh, wl] = c.wall(ctx);
1738
1755
  R["wall_" + c.type] = hsl(wh, c.neutral ? Math.min(0.16, sMax) : clamp(jit(sMax * 0.75, sMax * 0.5), 0.03, sMax), jit(wl, 0.03));
1739
1756
  const pi = c.floor || early.generalFloor;
1740
1757
  R["floor_" + c.type] = hsl(pi[0], Math.min(pi[1], wallMode ? 0.18 : 0.3), jit(pi[2], 0.04));
1741
1758
  }
1742
1759
  Object.assign(R, def.late(draw, early));
1743
- STATE_PLUGINS[state]?.adjustPalette?.(R);
1760
+ registries().STATE_PLUGINS[state]?.adjustPalette?.(R);
1744
1761
  const adjust = (color, ref, min, preferredDir) => {
1745
1762
  for (let i = 0; i < 60; i++) {
1746
1763
  if (Math.abs(lstar(hslToInt(color.h, color.s, color.l)) - lstar(hslToInt(ref.h, ref.s, ref.l))) >= min) return;
@@ -1750,7 +1767,7 @@ function palette(o) {
1750
1767
  color.l = clamp(color.l + dir * 0.015, 0.04, 0.96);
1751
1768
  }
1752
1769
  };
1753
- const types = ROOM_COLORS.map((c) => c.type);
1770
+ const types = registries().ROOM_COLORS.map((c) => c.type);
1754
1771
  for (const c of types) {
1755
1772
  const w = R["wall_" + c];
1756
1773
  const p = R["floor_" + c];
@@ -1804,8 +1821,8 @@ var SIDE_OPPOSITE = { front: "back", back: "front", left: "right", right: "left"
1804
1821
  var WINDOW_SIDE = { front: "front", back: "back", left: "left", right: "right" };
1805
1822
  function slotDefsOf(plan, room) {
1806
1823
  const name = room.library || room.type;
1807
- const key = LIBRARY_ALIAS[name] || name;
1808
- const library = room.slots ? room.slots : LIBRARIES[key];
1824
+ const key = registries().LIBRARY_ALIAS[name] || name;
1825
+ const library = room.slots ? room.slots : registries().LIBRARIES[key];
1809
1826
  if (!library) return [];
1810
1827
  const R = inner(room.parts[0]);
1811
1828
  let defs = typeof library === "function" ? library({ w: R[2] - R[0], d: R[3] - R[1] }) : library.slice();
@@ -1995,8 +2012,8 @@ function resolveSlot(plan, room, def, byId, zones) {
1995
2012
  // src/generate.ts
1996
2013
  function templateOf(t) {
1997
2014
  if (typeof t === "string") {
1998
- if (!TEMPLATES[t]) throw new Error("Unknown template: " + t);
1999
- return TEMPLATES[t];
2015
+ if (!registries().TEMPLATES[t]) throw new Error("Unknown template: " + t);
2016
+ return registries().TEMPLATES[t];
2000
2017
  }
2001
2018
  return t;
2002
2019
  }
@@ -2014,10 +2031,10 @@ function slots(template, options) {
2014
2031
  return resolveSlots(plan).map(cleanSlot).map((s) => s.reason ? Object.assign(s, { reason: translate("reasons", s.reason, locale) }) : s);
2015
2032
  }
2016
2033
  function pickState(requested, seed) {
2017
- if (requested && STATE_PLUGINS[requested]) return requested;
2018
- const dist = stateDistribution.weights;
2034
+ if (requested && registries().STATE_PLUGINS[requested]) return requested;
2035
+ const dist = registries().stateDistribution.weights;
2019
2036
  if (!dist) return "normal";
2020
- const x = createRng(seed, stateDistribution.seedKey)();
2037
+ const x = createRng(seed, registries().stateDistribution.seedKey)();
2021
2038
  let acc = 0;
2022
2039
  const keys = Object.keys(dist);
2023
2040
  for (const k of keys) {
@@ -2027,13 +2044,13 @@ function pickState(requested, seed) {
2027
2044
  return keys[keys.length - 1];
2028
2045
  }
2029
2046
  function colorOf(m, seed) {
2030
- const spec = m.colorSpec || CATALOG[m.type]?.color || { role: "wood" };
2047
+ const spec = m.colorSpec || registries().CATALOG[m.type]?.color || { role: "wood" };
2031
2048
  if ("role" in spec) return spec.role;
2032
2049
  if ("hex" in spec) return spec.hex;
2033
2050
  return spec.pick[hashString(seed + "|" + (m._seedId ?? m.id)) % spec.pick.length];
2034
2051
  }
2035
2052
  function applyState(plan, items, ctx, seed, state) {
2036
- const plugin = STATE_PLUGINS[state];
2053
+ const plugin = registries().STATE_PLUGINS[state];
2037
2054
  if (!plugin || state === "normal") return [];
2038
2055
  const c = {
2039
2056
  plan,
@@ -2043,8 +2060,8 @@ function applyState(plan, items, ctx, seed, state) {
2043
2060
  rng: createRng(seed, plugin.seedKey ?? "state:" + state),
2044
2061
  ctx,
2045
2062
  kit: placementKit,
2046
- catalog: CATALOG,
2047
- wallTopHeights: WALL_TOP_HEIGHTS
2063
+ catalog: registries().CATALOG,
2064
+ wallTopHeights: registries().WALL_TOP_HEIGHTS
2048
2065
  };
2049
2066
  return plugin.apply(c);
2050
2067
  }
@@ -2064,7 +2081,7 @@ function generate(template, options) {
2064
2081
  const missing = [];
2065
2082
  for (const room of plan.rooms.concat(plan.zones)) {
2066
2083
  const sl = all.filter((s) => s.room === room.id && s.floor === room.floor);
2067
- const r = fillRoom(plan, room, sl, createRng(seed, STREAM.roomPrefix + (room.seedKey ?? SEED_KEYS.rooms[room.id] ?? room.id)));
2084
+ const r = fillRoom(plan, room, sl, createRng(seed, STREAM.roomPrefix + (room.seedKey ?? registries().SEED_KEYS.rooms[room.id] ?? room.id)));
2068
2085
  items.push(...r.items);
2069
2086
  missing.push(...r.missing);
2070
2087
  }
@@ -2177,7 +2194,7 @@ function limitsOf(template, options) {
2177
2194
  return { limits: tpl.limitsFn ? tpl.limitsFn(op) : tpl.limits, defaults: tpl.defaultsFn ? tpl.defaultsFn(op) : tpl.defaults };
2178
2195
  }
2179
2196
  function listTemplates(locale = "en") {
2180
- return Object.values(TEMPLATES).map((t) => {
2197
+ return Object.values(registries().TEMPLATES).map((t) => {
2181
2198
  const a = floorsOf(t, {});
2182
2199
  return {
2183
2200
  id: t.id,
@@ -2194,6 +2211,9 @@ function listTemplates(locale = "en") {
2194
2211
  });
2195
2212
  }
2196
2213
 
2214
+ // src/instance.ts
2215
+ import { applyPatches, removeIds } from "@adatechnology/game-contracts";
2216
+
2197
2217
  // src/ground-items.ts
2198
2218
  function registerGroundItems() {
2199
2219
  const flat = { category: "ground_cover", w: 1, d: 1, h: 0, decor: true, mount: "ground", flat: true };
@@ -2414,6 +2434,109 @@ var pt_BR_default = {
2414
2434
  }
2415
2435
  };
2416
2436
 
2437
+ // src/instance.ts
2438
+ var version = "0.1.0";
2439
+ function editCatalog(catalog, patch, remove) {
2440
+ if (!patch && !remove) return;
2441
+ const entries = new Map(Object.entries(catalog).map(([key, definition]) => [key, { ...definition, id: key }]));
2442
+ applyPatches(entries, patch);
2443
+ removeIds(entries, remove);
2444
+ for (const key of Object.keys(catalog)) {
2445
+ const edited = entries.get(key);
2446
+ if (!edited) delete catalog[key];
2447
+ else if (patch && key in patch) {
2448
+ const hadId = "id" in catalog[key];
2449
+ const next = { ...edited };
2450
+ if (!hadId) delete next.id;
2451
+ catalog[key] = next;
2452
+ }
2453
+ }
2454
+ }
2455
+ function buildEngine(scope) {
2456
+ const scoped = (fn) => ((...args) => withRegistries(scope, () => fn(...args)));
2457
+ return {
2458
+ version,
2459
+ generate: scoped(generate),
2460
+ slots: scoped(slots),
2461
+ validate: scoped(validate),
2462
+ palette: scoped(palette),
2463
+ limitsOf: scoped(limitsOf),
2464
+ listTemplates: scoped(listTemplates),
2465
+ listStates: scoped(listStates),
2466
+ // content registration
2467
+ registerItem: scoped(registerItem),
2468
+ registerItems: scoped(registerItems),
2469
+ registerLibrary: scoped(registerLibrary),
2470
+ registerLibraryAlias: scoped(registerLibraryAlias),
2471
+ registerTemplate: scoped(registerTemplate),
2472
+ registerStyle: scoped(registerStyle),
2473
+ registerPaletteStyle: scoped(registerPaletteStyle),
2474
+ registerRoomColors: scoped(registerRoomColors),
2475
+ registerDecorProfile: scoped(registerDecorProfile),
2476
+ registerPass: scoped(registerPass),
2477
+ registerGroundOverlay: scoped(registerGroundOverlay),
2478
+ registerWallTop: scoped(registerWallTop),
2479
+ registerSeedKeys: scoped(registerSeedKeys),
2480
+ registerState: scoped(registerState),
2481
+ setStateDistribution: scoped(setStateDistribution),
2482
+ registerLocale: scoped(registerLocale),
2483
+ registerResultExtension: scoped(registerResultExtension),
2484
+ registerFindsProvider: scoped(registerFindsProvider),
2485
+ findsFor: scoped(findsFor),
2486
+ findsAnswer: scoped(findsAnswer),
2487
+ registerViewerLayer: scoped(registerViewerLayer),
2488
+ viewerLayers: scope.VIEWER_LAYERS,
2489
+ blocksWindow: scoped(blocksWindow),
2490
+ // zoning
2491
+ defineZone: scoped(defineZone),
2492
+ getZone: scoped(getZone),
2493
+ listZones: scoped(listZones),
2494
+ checkLot: scoped(checkLot),
2495
+ registerPermeableGround: scoped(registerPermeableGround),
2496
+ // kits and data
2497
+ slotKit,
2498
+ translate: scoped(translate),
2499
+ listLocales: scoped(listLocales),
2500
+ templates: scope.TEMPLATES,
2501
+ catalog: scope.CATALOG,
2502
+ libraries: scope.LIBRARIES,
2503
+ styles: scope.STYLES,
2504
+ paletteStyles: scope.PALETTE_STYLES,
2505
+ createRng,
2506
+ mulberry32,
2507
+ hashString,
2508
+ overlaps,
2509
+ windowZone,
2510
+ openingZones,
2511
+ color: { lstar, hex: hexString, hsl: intToHsl, HIGH_SATURATION, MIN_WALL_FLOOR_CONTRAST, MIN_FABRIC_FLOOR_CONTRAST },
2512
+ constants: { WALL_THICKNESS, DOOR_CLEARANCE, PLAYER_RADIUS, DOOR_EDGE_CLEARANCE, WINDOW_EDGE_CLEARANCE, OVERHEAD_TOP, CAR_CLEARANCE }
2513
+ };
2514
+ }
2515
+ function seedBase(scope) {
2516
+ withRegistries(scope, () => {
2517
+ registerGroundItems();
2518
+ registerPermeableGround("lawn", "dirt");
2519
+ registerLocale("en", en_default);
2520
+ registerLocale("pt-BR", pt_BR_default);
2521
+ });
2522
+ }
2523
+ function createFloorplan(options = {}) {
2524
+ const scope = createRegistries();
2525
+ seedBase(scope);
2526
+ const instance = buildEngine(scope);
2527
+ for (const pack of options.packs ?? []) (typeof pack === "function" ? pack : pack.register)(instance);
2528
+ editCatalog(scope.CATALOG, options.patch?.items, options.remove?.items);
2529
+ editCatalog(scope.TEMPLATES, options.patch?.templates, options.remove?.templates);
2530
+ return instance;
2531
+ }
2532
+ var defaultEngine;
2533
+ function defaultFloorplan() {
2534
+ if (defaultEngine) return defaultEngine;
2535
+ seedBase(getDefaultRegistries());
2536
+ defaultEngine = buildEngine(getDefaultRegistries());
2537
+ return defaultEngine;
2538
+ }
2539
+
2417
2540
  // src/silhouettes/random.ts
2418
2541
  function createSilhouetteRandom(seed) {
2419
2542
  let state = seed;
@@ -2623,68 +2746,7 @@ function createDetachedHouse(p) {
2623
2746
  }
2624
2747
 
2625
2748
  // src/index.ts
2626
- registerGroundItems();
2627
- registerPermeableGround("lawn", "dirt");
2628
- registerLocale("en", en_default);
2629
- registerLocale("pt-BR", pt_BR_default);
2630
- var version = "0.1.0";
2631
- var engine = {
2632
- version,
2633
- generate,
2634
- slots,
2635
- validate,
2636
- palette,
2637
- limitsOf,
2638
- listTemplates,
2639
- listStates,
2640
- // content registration
2641
- registerItem,
2642
- registerItems,
2643
- registerLibrary,
2644
- registerLibraryAlias,
2645
- registerTemplate,
2646
- registerStyle,
2647
- registerPaletteStyle,
2648
- registerRoomColors,
2649
- registerDecorProfile,
2650
- registerPass,
2651
- registerGroundOverlay,
2652
- registerWallTop,
2653
- registerSeedKeys,
2654
- registerState,
2655
- setStateDistribution,
2656
- registerLocale,
2657
- registerResultExtension,
2658
- registerFindsProvider,
2659
- findsFor,
2660
- findsAnswer,
2661
- registerViewerLayer,
2662
- viewerLayers: VIEWER_LAYERS,
2663
- blocksWindow,
2664
- // zoning
2665
- defineZone,
2666
- getZone,
2667
- listZones,
2668
- checkLot,
2669
- registerPermeableGround,
2670
- // kits and data
2671
- slotKit,
2672
- translate,
2673
- listLocales,
2674
- templates: TEMPLATES,
2675
- catalog: CATALOG,
2676
- libraries: LIBRARIES,
2677
- styles: STYLES,
2678
- paletteStyles: PALETTE_STYLES,
2679
- createRng,
2680
- mulberry32,
2681
- hashString,
2682
- overlaps,
2683
- windowZone,
2684
- openingZones,
2685
- color: { lstar, hex: hexString, hsl: intToHsl, HIGH_SATURATION, MIN_WALL_FLOOR_CONTRAST, MIN_FABRIC_FLOOR_CONTRAST },
2686
- constants: { WALL_THICKNESS, DOOR_CLEARANCE, PLAYER_RADIUS, DOOR_EDGE_CLEARANCE, WINDOW_EDGE_CLEARANCE, OVERHEAD_TOP, CAR_CLEARANCE }
2687
- };
2749
+ var engine = defaultFloorplan();
2688
2750
  export {
2689
2751
  blocksWindow,
2690
2752
  boxRadius,
@@ -2695,11 +2757,13 @@ export {
2695
2757
  createCompanyHouse,
2696
2758
  createDetachedHouse,
2697
2759
  createFarmhouse,
2760
+ createFloorplan,
2698
2761
  createRng,
2699
2762
  createSilhouetteRandom,
2700
2763
  createSlabHouse,
2701
2764
  createStiltHouse,
2702
2765
  createTwoStoreyHouse,
2766
+ defaultFloorplan,
2703
2767
  defineZone,
2704
2768
  engine,
2705
2769
  findsAnswer,