@adatechnology/game-items-three 0.0.0-stage → 0.1.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.
@@ -0,0 +1,2254 @@
1
+ "use strict";
2
+ var GameItemsThree = (() => {
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key2 of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key2) && key2 !== except)
15
+ __defProp(to, key2, { get: () => from[key2], enumerable: !(desc = __getOwnPropDesc(from, key2)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // src/index.ts
22
+ var index_exports = {};
23
+ __export(index_exports, {
24
+ FINDS_TABLE_ID: () => FINDS_TABLE_ID,
25
+ HUMAN_HEIGHT: () => HUMAN_HEIGHT,
26
+ HUMAN_ID: () => HUMAN_ID,
27
+ ITEM_BUDGETS: () => ITEM_BUDGETS,
28
+ ITEM_PREFIX: () => ITEM_PREFIX,
29
+ MODEL_BUDGET_DEVIATIONS: () => MODEL_BUDGET_DEVIATIONS,
30
+ ROW_LIMIT: () => ROW_LIMIT,
31
+ RULER_HEIGHT: () => RULER_HEIGHT,
32
+ RULER_ID: () => RULER_ID,
33
+ SHAPES: () => SHAPES,
34
+ SHELF_HEIGHT: () => SHELF_HEIGHT,
35
+ SHELF_PREFIX: () => SHELF_PREFIX,
36
+ SHIPPED_MODELS: () => SHIPPED_MODELS,
37
+ SHOWCASE_PREFIX: () => SHOWCASE_PREFIX,
38
+ SHOWCASE_STAND_ID: () => SHOWCASE_STAND_ID,
39
+ WEAR_AMOUNT: () => WEAR_AMOUNT,
40
+ WEAR_TAGS: () => WEAR_TAGS,
41
+ WEAR_TARGET: () => WEAR_TARGET,
42
+ buildItem: () => buildItem,
43
+ buildItemLod: () => buildItemLod,
44
+ canonicalFrame: () => canonicalFrame,
45
+ checkBudget: () => checkBudget,
46
+ composeThumbnailScene: () => composeThumbnailScene,
47
+ countDrawCalls: () => countDrawCalls,
48
+ countTriangles: () => countTriangles,
49
+ createItemKit: () => createItemKit,
50
+ createItemsTestModule: () => createItemsTestModule,
51
+ cullDistanceOf: () => cullDistanceOf,
52
+ drawShape: () => drawShape,
53
+ hasItemModel: () => hasItemModel,
54
+ isShape: () => isShape,
55
+ itemModelBuilder: () => itemModelBuilder,
56
+ itemModelEntry: () => itemModelEntry,
57
+ itemThumbnail: () => itemThumbnail,
58
+ itemThumbnailSvg: () => itemThumbnailSvg,
59
+ listItemModels: () => listItemModels,
60
+ mergeItem: () => mergeItem,
61
+ registerItemModel: () => registerItemModel,
62
+ renderItemThumbnail: () => renderItemThumbnail,
63
+ setWear: () => setWear,
64
+ shade: () => shade,
65
+ shapeOf: () => shapeOf,
66
+ shapeThumbnailSvg: () => shapeThumbnailSvg,
67
+ shelvesLayout: () => shelvesLayout,
68
+ sizeClassOf: () => sizeClassOf,
69
+ thumbnailPatch: () => thumbnailPatch,
70
+ triangleBudgetOf: () => triangleBudgetOf,
71
+ unregisterItemModel: () => unregisterItemModel,
72
+ wearableMaterials: () => wearableMaterials
73
+ });
74
+
75
+ // src/kit.ts
76
+ var WEAR_TAGS = ["metal", "wood", "cloth", "plastic", "paper", "food", "glass"];
77
+ var channelsOf = (hex2) => [(hex2 >> 16 & 255) / 255, (hex2 >> 8 & 255) / 255, (hex2 & 255) / 255];
78
+ function createItemKit(T2) {
79
+ const materials = [];
80
+ const M = (color, tag, options = {}) => {
81
+ const material = new T2.MeshStandardMaterial(Object.assign({ color, roughness: 0.78, metalness: 0 }, options));
82
+ const base = new T2.Color(color);
83
+ const data = { base, baseRgb: channelsOf(color), tag, rough: material.roughness };
84
+ material.userData = data;
85
+ materials.push(material);
86
+ return material;
87
+ };
88
+ const mesh = (geometry, material, position = [0, 0, 0], rotation = [0, 0, 0], scale) => {
89
+ const object = new T2.Mesh(geometry, material);
90
+ object.position.set(...position);
91
+ object.rotation.set(...rotation);
92
+ if (scale) object.scale.set(...scale);
93
+ return object;
94
+ };
95
+ return {
96
+ T: T2,
97
+ materials,
98
+ M,
99
+ mesh,
100
+ cyl: (radiusTop, radiusBottom, height, segments = 14) => new T2.CylinderGeometry(radiusTop, radiusBottom, height, segments),
101
+ bx: (width, height, depth) => new T2.BoxGeometry(width, height, depth),
102
+ lathe: (points, segments = 16) => new T2.LatheGeometry(points.map(([x, y]) => new T2.Vector2(x, y)), segments),
103
+ torus: (radius, tube2, arc = Math.PI * 2) => new T2.TorusGeometry(radius, tube2, 8, 18, arc),
104
+ extrude: (points, depth, bevel = 0.015) => {
105
+ const shape = new T2.Shape();
106
+ points.forEach(([x, y], index) => index ? shape.lineTo(x, y) : shape.moveTo(x, y));
107
+ return new T2.ExtrudeGeometry(shape, { depth, bevelEnabled: true, bevelThickness: bevel, bevelSize: bevel, bevelSegments: 2 });
108
+ },
109
+ G: (...children) => {
110
+ const group = new T2.Group();
111
+ children.forEach((child) => group.add(child));
112
+ return group;
113
+ },
114
+ metal: (color) => M(color, "metal", { metalness: 0.55, roughness: 0.45 }),
115
+ wood: (color) => M(color, "wood"),
116
+ cloth: (color) => M(color, "cloth", { roughness: 0.95 }),
117
+ plastic: (color) => M(color, "plastic", { roughness: 0.6 }),
118
+ paper: (color) => M(color, "paper", { roughness: 0.9 }),
119
+ food: (color) => M(color, "food", { roughness: 0.85 }),
120
+ glass: (color, opacity) => M(color, "glass", { roughness: 0.15, metalness: 0.1, transparent: true, opacity })
121
+ };
122
+ }
123
+
124
+ // src/wear.ts
125
+ var WEAR_TARGET = { food: 6122286, metal: 8011293, wood: 4141862, cloth: 4866099, plastic: 7038294, paper: 9075282, glass: 5591098 };
126
+ var WEAR_AMOUNT = [0, 0.3, 0.62];
127
+ var hexToRgb = (hex2) => [(hex2 >> 16 & 255) / 255, (hex2 >> 8 & 255) / 255, (hex2 & 255) / 255];
128
+ var dataOf = (material) => {
129
+ const data = material.userData;
130
+ return data.base && data.baseRgb && data.tag !== void 0 && data.rough !== void 0 ? data : void 0;
131
+ };
132
+ function wearableMaterials(root) {
133
+ const found = /* @__PURE__ */ new Set();
134
+ root.traverse((object) => {
135
+ const mesh = object;
136
+ if (!mesh.isMesh) return;
137
+ for (const material of Array.isArray(mesh.material) ? mesh.material : [mesh.material]) if (dataOf(material)) found.add(material);
138
+ });
139
+ return [...found];
140
+ }
141
+ function setWear(target, stage) {
142
+ const materials = "traverse" in target ? wearableMaterials(target) : target;
143
+ if ("traverse" in target) target.userData.wear = stage;
144
+ const amount = WEAR_AMOUNT[stage];
145
+ for (const material of materials) {
146
+ const data = dataOf(material);
147
+ if (!data) continue;
148
+ const goal = WEAR_TARGET[data.tag];
149
+ if (goal === void 0) continue;
150
+ const [tr, tg, tb] = hexToRgb(goal);
151
+ const [r, g, b] = data.baseRgb;
152
+ material.color.setRGB(r + (tr - r) * amount, g + (tg - g) * amount, b + (tb - b) * amount, "srgb");
153
+ material.roughness = Math.min(1, data.rough + 0.2 * stage / 2);
154
+ if (data.tag === "metal") material.metalness = Math.max(0.1, 0.55 - 0.2 * stage);
155
+ }
156
+ }
157
+
158
+ // src/shapes.ts
159
+ var SHAPES = [
160
+ "can",
161
+ "bag",
162
+ "box",
163
+ "bottle",
164
+ "tool",
165
+ "roll",
166
+ "dish",
167
+ "cloth",
168
+ "shoe",
169
+ "jar",
170
+ "paper",
171
+ "fruit",
172
+ "cup",
173
+ "tube",
174
+ "blade",
175
+ "long_tool",
176
+ "garment",
177
+ "backpack",
178
+ "jerrycan",
179
+ "tank",
180
+ "book",
181
+ "key",
182
+ "battery",
183
+ "plank",
184
+ "device"
185
+ ];
186
+ var DEFAULT_COLOR = 9080724;
187
+ var PI = Math.PI;
188
+ function shade(color, factor) {
189
+ const channel = (shift) => Math.max(0, Math.min(255, Math.round((color >> shift & 255) * factor)));
190
+ return channel(16) << 16 | channel(8) << 8 | channel(0);
191
+ }
192
+ var seg = (c, n) => Math.max(5, Math.round(n * c.detail));
193
+ function canonicalFrame(size) {
194
+ const [w, h, d] = size;
195
+ if (h >= w && h >= d) return { axis: 1, L: h, a: w, b: d };
196
+ if (d >= w && d >= h) return { axis: 2, L: d, a: h, b: w };
197
+ return { axis: 0, L: w, a: h, b: d };
198
+ }
199
+ function orient(c, group, axis) {
200
+ if (axis === 0) return group;
201
+ const holder = c.k.G(group);
202
+ if (axis === 1) holder.rotation.z = PI / 2;
203
+ else holder.rotation.y = -PI / 2;
204
+ return holder;
205
+ }
206
+ var alongLong = (draw) => (c) => {
207
+ const { axis, L, a, b } = canonicalFrame([c.w, c.h, c.d]);
208
+ return orient(c, draw(c, L, a, b), axis);
209
+ };
210
+ var across = (c, group) => {
211
+ group.scale.set(c.w, 1, c.d);
212
+ return c.k.G(group);
213
+ };
214
+ var can = (c) => {
215
+ const { k, h } = c;
216
+ const body = k.metal(9277590);
217
+ return across(c, k.G(k.mesh(k.cyl(0.5, 0.5, h * 0.94, seg(c, 14)), body), k.mesh(k.cyl(0.5, 0.5, h * 0.5, seg(c, 14)), k.food(c.color), [0, 0, 0], [0, 0, 0], [1, 1, 1]), k.mesh(k.cyl(0.5, 0.5, h * 0.03, seg(c, 14)), body, [0, h * 0.485, 0]), k.mesh(k.cyl(0.5, 0.5, h * 0.03, seg(c, 14)), body, [0, -h * 0.485, 0])));
218
+ };
219
+ var bottle = (c) => {
220
+ const { k, h } = c;
221
+ const r = 0.5;
222
+ const profile = [[0, -h / 2], [r, -h / 2], [r, h * 0.12], [r * 0.45, h * 0.3], [r * 0.4, h * 0.4], [0, h * 0.4]];
223
+ return across(c, k.G(k.mesh(k.lathe(profile, seg(c, 14)), k.glass(c.color, 0.6)), k.mesh(k.cyl(r * 0.42, r * 0.42, h * 0.1, seg(c, 10)), k.plastic(shade(c.color, 0.5)), [0, h * 0.45, 0])));
224
+ };
225
+ var jar = (c) => {
226
+ const { k, h } = c;
227
+ return across(c, k.G(k.mesh(k.cyl(0.5, 0.5, h * 0.82, seg(c, 14)), k.glass(c.color, 0.55), [0, -h * 0.09, 0]), k.mesh(k.cyl(0.5, 0.5, h * 0.18, seg(c, 14)), k.metal(12095802), [0, h * 0.41, 0])));
228
+ };
229
+ var bag = (c) => {
230
+ const { k, w, h, d } = c;
231
+ return k.G(k.mesh(k.bx(w, h * 0.88, d), k.paper(c.color), [0, -h * 0.06, 0]), k.mesh(k.bx(w * 1, h * 0.12, d * 0.5), k.paper(shade(c.color, 0.85)), [0, h * 0.44, 0]), k.mesh(k.bx(w * 0.7, h * 0.3, d * 1), k.paper(shade(c.color, 1.2)), [0, 0, 0]));
232
+ };
233
+ var box = (c) => {
234
+ const { k, w, h, d } = c;
235
+ return k.G(k.mesh(k.bx(w, h, d), k.paper(c.color)), k.mesh(k.bx(w, h * 0.12, d), k.paper(shade(c.color, 0.8)), [0, h * 0.44, 0]), k.mesh(k.bx(w * 0.62, h * 0.4, d), k.paper(shade(c.color, 1.25)), [0, -h * 0.02, 0]));
236
+ };
237
+ var cloth = (c) => {
238
+ const { k, w, h, d } = c;
239
+ const material = k.cloth(c.color);
240
+ return k.G(k.mesh(k.bx(w, h * 0.55, d), material, [0, -h * 0.225, 0]), k.mesh(k.bx(w * 0.96, h * 0.45, d * 0.96), k.cloth(shade(c.color, 0.9)), [0, h * 0.275, 0]));
241
+ };
242
+ var paper = (c) => {
243
+ const { k, w, h, d } = c;
244
+ const sheet = k.paper(c.color);
245
+ return k.G(k.mesh(k.bx(w, h / 3, d), sheet, [0, -h / 3, 0]), k.mesh(k.bx(w * 0.98, h / 3, d * 0.98), k.paper(shade(c.color, 0.95)), [0, 0, 0]), k.mesh(k.bx(w, h / 3, d), sheet, [0, h / 3, 0]));
246
+ };
247
+ var fruit = (c) => {
248
+ const { k, w, h, d } = c;
249
+ const sphere = new k.T.SphereGeometry(0.5, seg(c, 12), seg(c, 8));
250
+ return k.G(k.mesh(sphere, k.food(c.color), [0, -h * 0.04, 0], [0, 0, 0], [w, h * 0.92, d]), k.mesh(k.cyl(h * 0.02, h * 0.02, h * 0.1, 6), k.wood(5913122), [0, h * 0.45, 0]));
251
+ };
252
+ var dish = (c) => {
253
+ const { k, w, h, d } = c;
254
+ return k.G(k.mesh(k.cyl(0.5, 0.35, h, seg(c, 16)), k.plastic(c.color), [0, 0, 0], [0, 0, 0], [w, 1, d]));
255
+ };
256
+ var cup = (c) => {
257
+ const { k, w, h, d } = c;
258
+ const profile = [[0, -h / 2], [0.38, -h / 2], [0.5, h / 2], [0.44, h / 2], [0.33, -h / 2 + h * 0.06], [0, -h / 2 + h * 0.06]];
259
+ return k.G(k.mesh(k.lathe(profile, seg(c, 14)), k.plastic(c.color), [0, 0, 0], [0, 0, 0], [w, 1, d]));
260
+ };
261
+ var tube = (c) => {
262
+ const { k, h } = c;
263
+ const material = k.plastic(c.color);
264
+ return across(c, k.G(k.mesh(k.cyl(0.5, 0.42, h * 0.86, seg(c, 12)), material, [0, -h * 0.04, 0]), k.mesh(k.cyl(0.35, 0.35, h * 0.12, seg(c, 12)), k.plastic(shade(c.color, 0.6)), [0, h * 0.44, 0]), k.mesh(k.bx(1, h * 0.06, 0.25), material, [0, -h * 0.47, 0])));
265
+ };
266
+ var roll = (c) => {
267
+ const { k, w, h, d } = c;
268
+ const body = k.G(k.mesh(k.cyl(0.5, 0.5, 1, seg(c, 18)), k.paper(c.color)), k.mesh(k.cyl(0.18, 0.18, 0.98, seg(c, 12)), k.paper(11902328)));
269
+ if (h <= w && h <= d) body.scale.set(w, h, d);
270
+ else if (w <= d) {
271
+ body.rotation.z = PI / 2;
272
+ body.scale.set(h, w, d);
273
+ } else {
274
+ body.rotation.x = PI / 2;
275
+ body.scale.set(w, d, h);
276
+ }
277
+ return k.G(body);
278
+ };
279
+ var shoe = (c) => {
280
+ const { k, w, h, d } = c;
281
+ const upper = k.cloth(c.color);
282
+ return k.G(k.mesh(k.bx(w, h * 0.22, d), k.plastic(2237727), [0, -h * 0.39, 0]), k.mesh(k.bx(w * 0.42, h * 0.78, d * 0.96), upper, [-w * 0.29, h * 0.11, 0]), k.mesh(k.bx(w * 0.5, h * 0.5, d * 0.96), upper, [w * 0.25, -h * 0.03, 0]));
283
+ };
284
+ var tool = alongLong((c, L, a, b) => {
285
+ const { k } = c;
286
+ const radius = Math.max(4e-3, Math.min(a, b) / 2);
287
+ return k.G(k.mesh(k.cyl(radius, radius, L * 0.74, seg(c, 10)), k.wood(9069628), [-L * 0.13, 0, 0], [0, 0, PI / 2]), k.mesh(k.bx(L * 0.26, a, b), k.metal(shade(c.color, 1)), [L * 0.37, 0, 0]));
288
+ });
289
+ var longTool = alongLong((c, L, a, b) => {
290
+ const { k } = c;
291
+ const radius = Math.max(4e-3, Math.min(a, b) / 2) * 0.55;
292
+ return k.G(k.mesh(k.cyl(radius, radius, L * 0.94, seg(c, 10)), k.wood(10122312), [0, 0, 0], [0, 0, PI / 2]), k.mesh(k.bx(L * 0.12, a, b), k.metal(c.color), [L * 0.44, 0, 0]));
293
+ });
294
+ var blade = alongLong((c, L, a, b) => {
295
+ const { k } = c;
296
+ const edge = Math.min(2e-4, b * 0.05);
297
+ const steel = k.extrude([[0, 0], [L * 0.66, 0], [L * 0.68, a * 0.4], [0, a]], Math.max(5e-4, b * 0.4 - 2 * edge), edge);
298
+ return k.G(k.mesh(steel, k.metal(11120048), [-L * 0.18, -a / 2, -b * 0.2 + edge]), k.mesh(k.bx(L * 0.34, a * 0.6, b), k.wood(3877408), [-L * 0.33, 0, 0]));
299
+ });
300
+ var garment = (c) => {
301
+ const { k, w, h, d } = c;
302
+ const material = k.cloth(c.color);
303
+ return k.G(k.mesh(k.bx(w * 0.56, h, d), material), k.mesh(k.bx(w * 0.22, h * 0.42, d * 0.9), material, [-w * 0.39, h * 0.29, 0]), k.mesh(k.bx(w * 0.22, h * 0.42, d * 0.9), material, [w * 0.39, h * 0.29, 0]));
304
+ };
305
+ var backpack = (c) => {
306
+ const { k, w, h, d } = c;
307
+ const material = k.cloth(c.color);
308
+ const dome = new k.T.SphereGeometry(0.5, seg(c, 12), seg(c, 6), 0, PI * 2, 0, PI / 2);
309
+ const strap = k.cloth(shade(c.color, 0.6));
310
+ return k.G(k.mesh(k.bx(w, h * 0.78, d * 0.68), material, [0, -h * 0.11, -d * 0.14]), k.mesh(dome, material, [0, h * 0.28, -d * 0.14], [0, 0, 0], [w, h * 0.44, d * 0.68]), k.mesh(k.bx(w * 0.7, h * 0.4, d * 0.3), k.cloth(shade(c.color, 0.75)), [0, -h * 0.18, d * 0.35]), k.mesh(k.bx(w * 0.1, h * 0.7, d * 0.04), strap, [-w * 0.25, -h * 0.05, -d * 0.48]), k.mesh(k.bx(w * 0.1, h * 0.7, d * 0.04), strap, [w * 0.25, -h * 0.05, -d * 0.48]));
311
+ };
312
+ var jerrycan = (c) => {
313
+ const { k, w, h, d } = c;
314
+ const material = k.plastic(c.color);
315
+ return k.G(k.mesh(k.bx(w, h * 0.86, d), material, [0, -h * 0.07, 0]), k.mesh(k.bx(w * 0.4, h * 0.1, d * 0.5), material, [-w * 0.2, h * 0.45, 0]), k.mesh(k.cyl(d * 0.14, d * 0.18, h * 0.12, seg(c, 10)), k.plastic(14263361), [w * 0.3, h * 0.44, 0]));
316
+ };
317
+ var tank = (c) => {
318
+ const { k, h } = c;
319
+ const r = 0.5;
320
+ const profile = [[0, -h / 2], [r * 0.9, -h / 2], [r, -h * 0.4], [r, h * 0.22], [r * 0.6, h * 0.36], [r * 0.2, h * 0.4], [0, h * 0.4]];
321
+ const metal = k.metal(c.color);
322
+ return across(c, k.G(k.mesh(k.lathe(profile, seg(c, 14)), metal), k.mesh(k.cyl(r * 0.62, r * 0.62, h * 0.05, seg(c, 14)), metal, [0, h * 0.3, 0]), k.mesh(k.cyl(r * 0.18, r * 0.18, h * 0.12, seg(c, 8)), k.metal(12095802), [0, h * 0.44, 0])));
323
+ };
324
+ var book = (c) => {
325
+ const { k, w, h, d } = c;
326
+ const cover = k.paper(c.color);
327
+ return k.G(k.mesh(k.bx(w, h, d * 0.14), cover, [0, 0, d * 0.43]), k.mesh(k.bx(w, h, d * 0.14), cover, [0, 0, -d * 0.43]), k.mesh(k.bx(w * 0.94, h * 0.96, d * 0.72), k.paper(15327692), [w * 0.02, 0, 0]), k.mesh(k.bx(w * 0.08, h, d), k.paper(shade(c.color, 0.8)), [-w * 0.46, 0, 0]));
328
+ };
329
+ var key = alongLong((c, L, a, b) => {
330
+ const { k } = c;
331
+ const metal = k.metal(12095802);
332
+ const ring = a * 0.5;
333
+ const tube2 = Math.min(b * 0.4, ring * 0.2);
334
+ return k.G(k.mesh(new c.k.T.TorusGeometry(ring * 0.8, tube2, 5, seg(c, 10)), k.metal(10132896), [-L / 2 + ring * 0.8 + tube2, 0, 0]), k.mesh(k.bx(L * 0.62, a * 0.28, b), metal, [L * 0.12, 0, 0]), k.mesh(k.bx(L * 0.08, a * 0.22, b), metal, [L * 0.36, -a * 0.2, 0]), k.mesh(k.bx(L * 0.06, a * 0.22, b), metal, [L * 0.2, -a * 0.2, 0]));
335
+ });
336
+ var battery = alongLong((c, L, a, b) => {
337
+ const { k } = c;
338
+ const r = Math.min(a, b) / 2;
339
+ return k.G(k.mesh(k.cyl(r, r, L * 0.94, seg(c, 14)), k.metal(c.color), [0, 0, 0], [0, 0, PI / 2]), k.mesh(k.cyl(r * 0.36, r * 0.36, L * 0.06, seg(c, 8)), k.metal(12172736), [L * 0.47, 0, 0], [0, 0, PI / 2]));
340
+ });
341
+ var plank = alongLong((c, L, a, b) => {
342
+ const { k } = c;
343
+ const nail = k.metal(8225158);
344
+ return k.G(k.mesh(k.bx(L, a, b), k.wood(11043929)), k.mesh(k.cyl(Math.min(a, b) * 0.1, Math.min(a, b) * 0.1, b * 0.98, 6), nail, [-L * 0.4, 0, 0], [PI / 2, 0, 0]), k.mesh(k.cyl(Math.min(a, b) * 0.1, Math.min(a, b) * 0.1, b * 0.98, 6), nail, [L * 0.4, 0, 0], [PI / 2, 0, 0]));
345
+ });
346
+ var device = (c) => {
347
+ const { k, w, h, d } = c;
348
+ const screen = k.M(10469258, "glass", { emissive: 3099170, emissiveIntensity: 0.6, roughness: 0.2 });
349
+ return k.G(k.mesh(k.bx(w, h, d * 0.96), k.plastic(c.color), [0, 0, -d * 0.02]), k.mesh(k.bx(w * 0.7, h * 0.32, d * 0.06), screen, [0, h * 0.22, d * 0.47]), k.mesh(k.cyl(Math.min(w, h) * 0.08, Math.min(w, h) * 0.08, d * 0.1, seg(c, 8)), k.metal(12172736), [w * 0.25, -h * 0.18, d * 0.45], [PI / 2, 0, 0]));
350
+ };
351
+ var DRAWERS = {
352
+ can,
353
+ bag,
354
+ box,
355
+ bottle,
356
+ tool,
357
+ roll,
358
+ dish,
359
+ cloth,
360
+ shoe,
361
+ jar,
362
+ paper,
363
+ fruit,
364
+ cup,
365
+ tube,
366
+ blade,
367
+ long_tool: longTool,
368
+ garment,
369
+ backpack,
370
+ jerrycan,
371
+ tank,
372
+ book,
373
+ key,
374
+ battery,
375
+ plank,
376
+ device
377
+ };
378
+ var isShape = (value) => value !== void 0 && value in DRAWERS;
379
+ function shapeOf(subject) {
380
+ const hint = subject.shape;
381
+ if (!isShape(hint)) return "box";
382
+ const [w, h, d] = subject.size;
383
+ const { L, a, b } = canonicalFrame(subject.size);
384
+ const long = L / Math.max(a, b, 1e-6) >= 4;
385
+ const flat = Math.min(w, h, d) / Math.max(w, h, d) <= 0.2;
386
+ switch (hint) {
387
+ case "tool":
388
+ if (subject.category === "keys") return "key";
389
+ if (subject.category === "electronics") return "device";
390
+ if (long) return subject.category === "melee" && Math.min(a, b) / L <= 0.1 && flat ? "blade" : "long_tool";
391
+ return hint;
392
+ case "cloth":
393
+ return subject.category === "clothing" ? "garment" : hint;
394
+ case "bag":
395
+ return subject.category === "bags" ? "backpack" : hint;
396
+ case "box":
397
+ if (subject.category === "electronics") return "device";
398
+ if (subject.category === "books") return "book";
399
+ if (subject.category === "keys") return "key";
400
+ return hint;
401
+ case "paper":
402
+ return subject.category === "books" ? "book" : hint;
403
+ default:
404
+ return hint;
405
+ }
406
+ }
407
+ function drawShape(k, shape, size, options = {}) {
408
+ const [w, h, d] = size;
409
+ return DRAWERS[shape]({ k, w, h, d, color: options.color ?? DEFAULT_COLOR, detail: options.detail ?? 1 });
410
+ }
411
+
412
+ // src/data/models.json
413
+ var models_default = [
414
+ {
415
+ id: "canned_beans",
416
+ teamId: "feijao_lata",
417
+ teamBox: [
418
+ 0.67,
419
+ 0.57,
420
+ 0.66
421
+ ],
422
+ scale: 0.119403
423
+ },
424
+ {
425
+ id: "water_bottle",
426
+ teamId: "agua_500",
427
+ teamBox: [
428
+ 0.452,
429
+ 1.25,
430
+ 0.452
431
+ ],
432
+ scale: 0.146018
433
+ },
434
+ {
435
+ id: "rice",
436
+ teamId: "arroz",
437
+ teamBox: [
438
+ 0.72,
439
+ 1.055,
440
+ 0.3
441
+ ],
442
+ scale: 0.236967
443
+ },
444
+ {
445
+ id: "military_ration",
446
+ teamId: "racao_militar",
447
+ teamBox: [
448
+ 0.77,
449
+ 1.03,
450
+ 0.235
451
+ ],
452
+ scale: 0.252427
453
+ },
454
+ {
455
+ id: "cachaca",
456
+ teamId: "cachaca",
457
+ teamBox: [
458
+ 0.552,
459
+ 1.31,
460
+ 0.552
461
+ ],
462
+ scale: 0.163043
463
+ },
464
+ {
465
+ id: "first_aid_kit",
466
+ teamId: "kit_ps",
467
+ teamBox: [
468
+ 1.07,
469
+ 0.915,
470
+ 0.41
471
+ ],
472
+ scale: 0.233645
473
+ },
474
+ {
475
+ id: "bandage",
476
+ teamId: "atadura",
477
+ teamBox: [
478
+ 0.9851,
479
+ 0.7575,
480
+ 0.43
481
+ ],
482
+ scale: 0.102528
483
+ },
484
+ {
485
+ id: "amoxicillin",
486
+ teamId: "amoxicilina",
487
+ teamBox: [
488
+ 0.8859,
489
+ 0.47,
490
+ 0.607
491
+ ],
492
+ scale: 0.11288
493
+ },
494
+ {
495
+ id: "crowbar",
496
+ teamId: "pe_cabra",
497
+ teamBox: [
498
+ 0.428,
499
+ 1.7087,
500
+ 0.11
501
+ ],
502
+ scale: 0.43893
503
+ },
504
+ {
505
+ id: "hammer",
506
+ teamId: "martelo",
507
+ teamBox: [
508
+ 0.6487,
509
+ 1.3477,
510
+ 0.19
511
+ ],
512
+ scale: 0.244862
513
+ },
514
+ {
515
+ id: "machete",
516
+ teamId: "facao",
517
+ teamBox: [
518
+ 1.8759,
519
+ 0.2906,
520
+ 0.12
521
+ ],
522
+ scale: 0.293193
523
+ },
524
+ {
525
+ id: "axe",
526
+ teamId: "machado",
527
+ teamBox: [
528
+ 0.5952,
529
+ 1.6693,
530
+ 0.14
531
+ ],
532
+ scale: 0.419337
533
+ },
534
+ {
535
+ id: "nail_bat",
536
+ teamId: "taco_pregos",
537
+ teamBox: [
538
+ 0.4384,
539
+ 1.69,
540
+ 0.4352
541
+ ],
542
+ scale: 0.508876
543
+ },
544
+ {
545
+ id: "hiking_backpack_40l",
546
+ teamId: "mochila_trilha",
547
+ teamBox: [
548
+ 0.8,
549
+ 1.07,
550
+ 0.62
551
+ ],
552
+ scale: 0.514019
553
+ },
554
+ {
555
+ id: "flashlight",
556
+ teamId: "lanterna",
557
+ teamBox: [
558
+ 0.38,
559
+ 1.08,
560
+ 0.38
561
+ ],
562
+ scale: 0.148148
563
+ },
564
+ {
565
+ id: "portable_radio",
566
+ teamId: "radio_portatil",
567
+ teamBox: [
568
+ 1.1041,
569
+ 1.4821,
570
+ 0.35
571
+ ],
572
+ scale: 0.199257
573
+ },
574
+ {
575
+ id: "house_key",
576
+ teamId: "chave_casa",
577
+ teamBox: [
578
+ 1.5433,
579
+ 0.8305,
580
+ 0.08
581
+ ],
582
+ scale: 0.055077
583
+ },
584
+ {
585
+ id: "survivor_diary",
586
+ teamId: "diario",
587
+ teamBox: [
588
+ 0.8,
589
+ 1.04,
590
+ 0.27
591
+ ],
592
+ scale: 0.201923
593
+ },
594
+ {
595
+ id: "motorcycle_helmet",
596
+ teamId: "capacete_moto",
597
+ teamBox: [
598
+ 1.11,
599
+ 1.0825,
600
+ 1.17
601
+ ],
602
+ scale: 0.234234
603
+ },
604
+ {
605
+ id: "lpg_cylinder_13kg",
606
+ teamId: "botijao",
607
+ teamBox: [
608
+ 0.9257,
609
+ 1.285,
610
+ 0.94
611
+ ],
612
+ scale: 0.381323
613
+ },
614
+ {
615
+ id: "fuel_jerrycan",
616
+ teamId: "galao",
617
+ teamBox: [
618
+ 0.8788,
619
+ 1.342,
620
+ 0.44
621
+ ],
622
+ scale: 0.350224
623
+ },
624
+ {
625
+ id: "walkie_talkies",
626
+ teamId: "walkie",
627
+ teamBox: [
628
+ 0.465,
629
+ 1.47,
630
+ 0.22
631
+ ],
632
+ scale: 0.14966
633
+ },
634
+ {
635
+ id: "batteries",
636
+ teamId: "pilha_aa",
637
+ teamBox: [
638
+ 0.744,
639
+ 1.005,
640
+ 0.344
641
+ ],
642
+ scale: 0.050249
643
+ },
644
+ {
645
+ id: "wooden_plank",
646
+ teamId: "tabua",
647
+ teamBox: [
648
+ 1.5203,
649
+ 0.4741,
650
+ 0.5377
651
+ ],
652
+ scale: 0.526212
653
+ }
654
+ ];
655
+
656
+ // src/models/canned-beans.ts
657
+ var cannedBeans = ({ mesh, cyl, torus, G, metal, paper: paper2, food }) => {
658
+ const m = metal(9277590);
659
+ return G(mesh(cyl(0.32, 0.32, 0.52), m), mesh(cyl(0.326, 0.326, 0.34, 14), food(10172964)), mesh(cyl(0.33, 0.33, 0.06), paper2(14861162), [0, 0.02, 0]), mesh(torus(0.31, 0.025), m, [0, 0.26, 0], [Math.PI / 2, 0, 0]), mesh(torus(0.31, 0.025), m, [0, -0.26, 0], [Math.PI / 2, 0, 0]));
660
+ };
661
+
662
+ // src/models/water-bottle.ts
663
+ var waterBottle = ({ mesh, cyl, lathe, G, plastic, paper: paper2, glass }) => G(mesh(lathe([[0, -0.6], [0.2, -0.6], [0.22, -0.5], [0.22, 0.2], [0.19, 0.32], [0.09, 0.45], [0.08, 0.55], [0, 0.55]]), glass(9421788, 0.55)), mesh(cyl(0.085, 0.085, 0.1, 12), plastic(2907530), [0, 0.6, 0]), mesh(cyl(0.225, 0.225, 0.22, 16), paper2(15330543), [0, -0.15, 0]), mesh(cyl(0.226, 0.226, 0.05, 16), plastic(3831976), [0, -0.12, 0]));
664
+
665
+ // src/models/rice.ts
666
+ var rice = ({ mesh, bx, G, paper: paper2, food }) => G(mesh(bx(0.7, 0.95, 0.28), paper2(15129792)), mesh(bx(0.72, 0.12, 0.1), paper2(14010792), [0, 0.52, 0]), mesh(bx(0.5, 0.3, 0.02), food(11883039), [0, 0.05, 0.15]), mesh(bx(0.3, 0.1, 0.02), paper2(4160069), [0, -0.25, 0.15]));
667
+
668
+ // src/models/military-ration.ts
669
+ var militaryRation = ({ mesh, bx, G, plastic, paper: paper2 }) => G(mesh(bx(0.75, 1, 0.22), plastic(5923142)), mesh(bx(0.77, 0.1, 0.23), plastic(4672823), [0, 0.48, 0]), mesh(bx(0.5, 0.14, 0.01), paper2(2237727), [0, 0.18, 0.115]), mesh(bx(0.36, 0.06, 0.01), paper2(2237727), [0, -0.02, 0.115]));
670
+
671
+ // src/models/cachaca.ts
672
+ var cachaca = ({ mesh, cyl, lathe, G, wood, paper: paper2, food, glass }) => G(mesh(lathe([[0, -0.6], [0.25, -0.6], [0.27, -0.52], [0.27, 0.1], [0.2, 0.25], [0.09, 0.35], [0.09, 0.58], [0, 0.58]]), glass(12749354, 0.78)), mesh(cyl(0.08, 0.07, 0.14, 10), wood(9071172), [0, 0.64, 0]), mesh(cyl(0.275, 0.275, 0.34, 16), paper2(15327692), [0, -0.25, 0]), mesh(cyl(0.276, 0.276, 0.06, 16), food(8007455), [0, -0.15, 0]));
673
+
674
+ // src/models/first-aid-kit.ts
675
+ var firstAidKit = ({ mesh, bx, torus, G, plastic }) => {
676
+ const r = plastic(11680300);
677
+ return G(mesh(bx(1.05, 0.72, 0.38), plastic(15131608)), mesh(bx(1.07, 0.06, 0.4), plastic(12433578), [0, 0, 0]), mesh(bx(0.36, 0.1, 0.02), r, [0, 0.1, 0.2]), mesh(bx(0.1, 0.36, 0.02), r, [0, 0.1, 0.2]), mesh(torus(0.16, 0.035, Math.PI), plastic(5591884), [0, 0.36, 0]));
678
+ };
679
+
680
+ // src/models/bandage.ts
681
+ var bandage = ({ mesh, cyl, bx, G, cloth: cloth2 }) => {
682
+ const c = cloth2(15525330);
683
+ return G(mesh(cyl(0.34, 0.34, 0.38, 18), c, [0, 0, 0], [Math.PI / 2, 0, 0]), mesh(cyl(0.1, 0.1, 0.4, 12), cloth2(9078135), [0, 0, 0], [Math.PI / 2, 0, 0]), mesh(bx(0.36, 0.02, 0.36), c, [0.2, -0.33, 0.05], [0, 0, -0.25]), mesh(bx(0.3, 0.02, 0.36), c, [0.5, -0.4, 0.05], [0, 0, -0.05]));
684
+ };
685
+
686
+ // src/models/amoxicillin.ts
687
+ var amoxicillin = ({ T: T2, mesh, bx, G, metal, plastic, paper: paper2 }) => G(mesh(bx(0.8, 0.42, 0.24), paper2(15789798)), mesh(bx(0.81, 0.1, 0.25), plastic(5141127), [0, 0.1, 0]), mesh(bx(0.6, 0.04, 0.3), metal(12172736), [0.15, -0.24, 0.25], [0, 0.3, 0]), ...[0, 1, 2, 3].map((k) => mesh(new T2.SphereGeometry(0.05, 10, 8), plastic(14263361), [-0.05 + k * 0.14, -0.21, 0.3], [0, 0, 0], [1, 0.6, 1])));
688
+
689
+ // src/models/crowbar.ts
690
+ var crowbar = ({ T: T2, mesh, cyl, G, metal }) => {
691
+ const c = new T2.CatmullRomCurve3([[0, -0.8, 0], [0, 0.45, 0], [0.02, 0.62, 0], [0.12, 0.72, 0], [0.26, 0.7, 0], [0.32, 0.6, 0]].map((p) => new T2.Vector3(...p)));
692
+ return G(mesh(new T2.TubeGeometry(c, 30, 0.055, 8), metal(10168863)), mesh(cyl(0.02, 0.055, 0.14, 8), metal(3816510), [0, -0.86, 0]));
693
+ };
694
+
695
+ // src/models/hammer.ts
696
+ var hammer = ({ T: T2, mesh, cyl, bx, G, metal, wood, plastic }) => G(mesh(cyl(0.05, 0.065, 1.1, 10), wood(9069628), [0, -0.15, 0]), mesh(cyl(0.07, 0.07, 0.3, 10), plastic(2237727), [0, -0.6, 0]), mesh(bx(0.28, 0.15, 0.15), metal(5593437), [0.05, 0.45, 0]), mesh(cyl(0.08, 0.08, 0.1, 12), metal(5593437), [0.22, 0.45, 0], [0, 0, Math.PI / 2]), mesh(new T2.ConeGeometry(0.06, 0.3, 6), metal(5593437), [-0.22, 0.5, 0.035], [0, 0, 1.9]), mesh(new T2.ConeGeometry(0.06, 0.3, 6), metal(5593437), [-0.22, 0.5, -0.035], [0, 0, 1.9]));
697
+
698
+ // src/models/machete.ts
699
+ var machete = ({ mesh, cyl, bx, extrude, G, metal, wood }) => G(mesh(extrude([[0, 0], [1.25, 0.05], [1.4, 0.14], [1.3, 0.26], [0, 0.2]], 0.03), metal(11120048), [-0.55, 0, 0]), mesh(bx(0.45, 0.2, 0.1), wood(3877408), [-0.78, 0.1, 0.015]), ...[-0.9, -0.68].map((x) => mesh(cyl(0.025, 0.025, 0.12, 8), metal(12095802), [x, 0.1, 0.015], [Math.PI / 2, 0, 0])));
700
+
701
+ // src/models/axe.ts
702
+ var axe = ({ M, mesh, cyl, bx, extrude, G, metal, wood }) => G(mesh(cyl(0.05, 0.06, 1.5, 10), wood(10122312), [0, -0.1, 0]), mesh(extrude([[0, 0], [0.42, -0.16], [0.5, 0.05], [0.48, 0.3], [0.4, 0.42], [0, 0.22]], 0.07), metal(7106677), [0.02, 0.38, -0.05]), mesh(bx(0.12, 0.26, 0.14), metal(4869456), [0, 0.5, 0]), mesh(bx(0.1, 0.2, 0.08), M(10168863, "plastic"), [0, 0.55, 0]));
703
+
704
+ // src/models/nail-bat.ts
705
+ var nailBat = ({ mesh, cyl, lathe, G, metal, wood }) => {
706
+ const g = G(mesh(lathe([[0, -0.85], [0.05, -0.85], [0.05, -0.8], [0.035, -0.75], [0.04, -0.2], [0.09, 0.3], [0.11, 0.7], [0.09, 0.82], [0, 0.84]], 14), wood(11569754)));
707
+ const nm = metal(8225158);
708
+ for (let k = 0; k < 9; k++) {
709
+ const a = k * 2.4, y = 0.25 + k % 5 * 0.12;
710
+ const n = mesh(cyl(0.012, 0.012, 0.2, 6), nm, [Math.cos(a) * 0.12, y, Math.sin(a) * 0.12], [0, 0, 0]);
711
+ n.lookAt(Math.cos(a) * 2, y, Math.sin(a) * 2);
712
+ n.rotateX(Math.PI / 2);
713
+ g.add(n);
714
+ }
715
+ return g;
716
+ };
717
+
718
+ // src/models/hiking-backpack-40l.ts
719
+ var hikingBackpack40L = ({ T: T2, mesh, bx, G, cloth: cloth2, plastic }) => {
720
+ const c = cloth2(5204794), d = cloth2(3557162);
721
+ return G(mesh(bx(0.78, 0.9, 0.42), c), mesh(new T2.SphereGeometry(0.4, 14, 8, 0, Math.PI * 2, 0, Math.PI / 2), c, [0, 0.44, 0], [0, 0, 0], [0.98, 0.45, 0.53]), mesh(bx(0.56, 0.4, 0.16), d, [0, -0.15, 0.27]), mesh(bx(0.8, 0.12, 0.46), d, [0, 0.2, 0]), mesh(bx(0.06, 0.9, 0.06), cloth2(2237727), [-0.22, 0, -0.24]), mesh(bx(0.06, 0.9, 0.06), cloth2(2237727), [0.22, 0, -0.24]), mesh(bx(0.1, 0.08, 0.04), plastic(14263361), [0, 0.2, 0.24]));
722
+ };
723
+
724
+ // src/models/flashlight.ts
725
+ var flashlight = ({ M, mesh, cyl, bx, torus, G, metal, plastic }) => G(mesh(cyl(0.11, 0.11, 0.8, 14), metal(2763822), [0, -0.15, 0]), mesh(cyl(0.19, 0.12, 0.28, 16), metal(3816768), [0, 0.37, 0]), mesh(cyl(0.17, 0.17, 0.02, 16), M(16773568, "glass", { emissive: 16769162, emissiveIntensity: 0.9 }), [0, 0.52, 0]), mesh(bx(0.06, 0.1, 0.05), plastic(11883039), [0, 0.05, 0.11]), ...[-0.4, -0.3, -0.2].map((y) => mesh(torus(0.112, 0.012), plastic(1776669), [0, y, 0], [Math.PI / 2, 0, 0])));
726
+
727
+ // src/models/portable-radio.ts
728
+ var portableRadio = ({ M, mesh, cyl, bx, torus, G, metal, plastic }) => {
729
+ const g = G(mesh(bx(1.05, 0.62, 0.3), plastic(3816757)), mesh(cyl(0.2, 0.2, 0.02, 18), plastic(1974043), [-0.24, -0.04, 0.16], [Math.PI / 2, 0, 0]), mesh(bx(0.36, 0.14, 0.02), M(14263361, "glass", { emissive: 6965776, emissiveIntensity: 0.6 }), [0.26, 0.1, 0.16]), mesh(cyl(0.06, 0.06, 0.06, 12), plastic(9276032), [0.18, -0.15, 0.17], [Math.PI / 2, 0, 0]), mesh(cyl(0.06, 0.06, 0.06, 12), plastic(9276032), [0.36, -0.15, 0.17], [Math.PI / 2, 0, 0]), mesh(torus(0.24, 0.03, Math.PI), plastic(2237727), [0, 0.3, 0]));
730
+ const a = mesh(cyl(0.012, 0.02, 0.95, 6), metal(12172736), [0.4, 0.72, 0], [0, 0, -0.35]);
731
+ g.add(a);
732
+ return g;
733
+ };
734
+
735
+ // src/models/house-key.ts
736
+ var houseKey = ({ mesh, bx, torus, extrude, G, metal, plastic }) => {
737
+ const b = metal(12095802);
738
+ return G(mesh(torus(0.22, 0.04), metal(10132896), [-0.55, 0.1, 0]), mesh(extrude([[0, -0.2], [0.25, -0.22], [0.3, -0.1], [0.3, 0.1], [0.25, 0.22], [0, 0.2]], 0.05), b, [-0.38, 0.02, -0.025]), mesh(extrude([[0, 0], [0.75, 0], [0.75, -0.08], [0.66, -0.08], [0.66, -0.14], [0.56, -0.08], [0.46, -0.14], [0.4, -0.08], [0, -0.08]], 0.035), b, [-0.1, 0.06, -0.017]), mesh(bx(0.24, 0.34, 0.03), plastic(11883039), [-0.72, -0.28, 0], [0, 0, 0.25]));
739
+ };
740
+
741
+ // src/models/survivor-diary.ts
742
+ var survivorDiary = ({ mesh, bx, G, cloth: cloth2, paper: paper2 }) => G(mesh(bx(0.78, 1.02, 0.05), paper2(7028522), [0, 0, 0.1]), mesh(bx(0.78, 1.02, 0.05), paper2(7028522), [0, 0, -0.1]), mesh(bx(0.74, 0.98, 0.16), paper2(15327692), [0.01, 0, 0]), mesh(bx(0.06, 1.02, 0.25), paper2(5911330), [-0.38, 0, 0]), mesh(bx(0.03, 1.04, 0.27), cloth2(2237727), [0.25, 0, 0]), mesh(bx(0.18, 0.3, 2e-3), paper2(14010792), [0.05, 0.2, 0.126], [0, 0, 0.08]));
743
+
744
+ // src/models/motorcycle-helmet.ts
745
+ var motorcycleHelmet = ({ T: T2, M, mesh, cyl, bx, G, plastic }) => G(mesh(new T2.SphereGeometry(0.55, 18, 12), plastic(2830138), [0, 0, 0], [0, 0, 0], [1, 0.95, 1.08]), mesh(new T2.SphereGeometry(0.56, 18, 10, -0.9, 1.8, 1.05, 0.75), M(1316636, "glass", { roughness: 0.15, metalness: 0.4 }), [0, 0.02, 0.02], [0, 0, 0], [1, 0.95, 1.08]), mesh(bx(0.2, 0.12, 0.02), plastic(14710591), [0.42, 0.2, 0.22], [0, 0.95, 0]), mesh(cyl(0.5, 0.55, 0.12, 18), plastic(1908776), [0, -0.5, 0]));
746
+
747
+ // src/models/lpg-cylinder-13kg.ts
748
+ var lpgCylinder13Kg = ({ mesh, cyl, bx, lathe, torus, G, metal }) => G(mesh(lathe([[0, -0.6], [0.38, -0.6], [0.42, -0.55], [0.45, -0.3], [0.45, 0.15], [0.4, 0.32], [0.28, 0.42], [0.12, 0.46], [0, 0.46]], 18), metal(3497862)), mesh(cyl(0.47, 0.47, 0.1, 18), metal(2705514), [0, -0.62, 0]), mesh(torus(0.24, 0.035), metal(2705514), [0, 0.58, 0], [Math.PI / 2, 0, 0]), mesh(cyl(0.07, 0.07, 0.14, 10), metal(12095802), [0, 0.5, 0]), mesh(bx(0.04, 0.2, 0.04), metal(2705514), [0.22, 0.5, 0]), mesh(bx(0.04, 0.2, 0.04), metal(2705514), [-0.22, 0.5, 0]));
749
+
750
+ // src/models/fuel-jerrycan.ts
751
+ var fuelJerrycan = ({ mesh, cyl, bx, G, plastic }) => G(mesh(bx(0.85, 1, 0.42), plastic(10695210)), mesh(bx(0.6, 0.1, 0.3), plastic(8267296), [0, 0.45, 0]), mesh(bx(0.36, 0.2, 0.1), plastic(10695210), [-0.12, 0.62, 0]), mesh(cyl(0.05, 0.07, 0.35, 10), plastic(14263361), [0.3, 0.66, 0], [0, 0, -0.6]), mesh(bx(0.6, 0.5, 0.02), plastic(9054754), [0, -0.1, 0.22]));
752
+
753
+ // src/models/walkie-talkies.ts
754
+ var walkieTalkies = ({ M, mesh, cyl, bx, G, plastic }) => G(mesh(bx(0.42, 0.9, 0.2), plastic(2764071)), mesh(bx(0.34, 0.3, 0.02), plastic(1447956), [0, -0.12, 0.11]), mesh(bx(0.24, 0.12, 0.02), M(10469258, "glass", { emissive: 3099170, emissiveIntensity: 0.7 }), [0, 0.22, 0.11]), mesh(cyl(0.035, 0.045, 0.6, 8), plastic(1776666), [0.12, 0.72, 0]), mesh(cyl(0.04, 0.04, 0.08, 10), plastic(11883039), [-0.12, 0.48, 0]), mesh(bx(0.05, 0.2, 0.1), plastic(3816757), [-0.23, 0.05, 0]));
755
+
756
+ // src/models/batteries.ts
757
+ var batteries = ({ mesh, cyl, G, metal }) => {
758
+ const g = G();
759
+ [-0.2, 0.2].forEach((x, k) => g.add(mesh(cyl(0.17, 0.17, 0.95, 16), metal(k ? 11883039 : 2763822), [x, 0, 0]), mesh(cyl(0.172, 0.172, 0.3, 16), metal(13214247), [x, 0.33, 0]), mesh(cyl(0.06, 0.06, 0.06, 10), metal(12172736), [x, 0.5, 0])));
760
+ return g;
761
+ };
762
+
763
+ // src/models/wooden-plank.ts
764
+ var woodenPlank = ({ mesh, cyl, bx, G, metal, wood }) => {
765
+ const g = G();
766
+ [11043929, 9333829, 11833960].forEach((c, k) => g.add(mesh(bx(1.5, 0.07, 0.26), wood(c), [0, -0.15 + k * 0.12, (k - 1) * 0.05], [0, (k - 1) * 0.12, 0])));
767
+ const nm = metal(8225158);
768
+ [-0.5, -0.3, 0.4].forEach((x, k) => g.add(mesh(cyl(0.012, 0.012, 0.18, 6), nm, [x, 0.18 + k * 0.01, 0.2], [0, 0, (k - 1) * 0.3])));
769
+ return g;
770
+ };
771
+
772
+ // src/models/shipped.ts
773
+ var BUILDERS = {
774
+ canned_beans: cannedBeans,
775
+ water_bottle: waterBottle,
776
+ rice,
777
+ military_ration: militaryRation,
778
+ cachaca,
779
+ first_aid_kit: firstAidKit,
780
+ bandage,
781
+ amoxicillin,
782
+ crowbar,
783
+ hammer,
784
+ machete,
785
+ axe,
786
+ nail_bat: nailBat,
787
+ hiking_backpack_40l: hikingBackpack40L,
788
+ flashlight,
789
+ portable_radio: portableRadio,
790
+ house_key: houseKey,
791
+ survivor_diary: survivorDiary,
792
+ motorcycle_helmet: motorcycleHelmet,
793
+ lpg_cylinder_13kg: lpgCylinder13Kg,
794
+ fuel_jerrycan: fuelJerrycan,
795
+ walkie_talkies: walkieTalkies,
796
+ batteries,
797
+ wooden_plank: woodenPlank
798
+ };
799
+ var SHIPPED_MODELS = models_default.map((entry) => {
800
+ const builder = BUILDERS[entry.id];
801
+ if (!builder) throw new Error(`game-items-three: no builder for the model "${entry.id}"`);
802
+ return { entry, builder };
803
+ });
804
+
805
+ // src/models/registry.ts
806
+ var entries = new Map(SHIPPED_MODELS.map(({ entry }) => [entry.id, entry]));
807
+ var builders = new Map(SHIPPED_MODELS.map(({ entry, builder }) => [entry.id, builder]));
808
+ function registerItemModel(entry, builder) {
809
+ entries.set(entry.id, entry);
810
+ builders.set(entry.id, builder);
811
+ }
812
+ function unregisterItemModel(id) {
813
+ entries.delete(id);
814
+ builders.delete(id);
815
+ }
816
+ var itemModelEntry = (id) => entries.get(id);
817
+ var itemModelBuilder = (id) => builders.get(id);
818
+ var hasItemModel = (id) => builders.has(id);
819
+ var listItemModels = () => [...entries.keys()];
820
+
821
+ // src/build-item.ts
822
+ function restOnBase(T2, inner) {
823
+ const box2 = new T2.Box3().setFromObject(inner);
824
+ const centre = box2.getCenter(new T2.Vector3());
825
+ inner.position.set(-centre.x, -box2.min.y, -centre.z);
826
+ }
827
+ function buildItem(T2, item, options = {}) {
828
+ const kit = options.kit ?? createItemKit(T2);
829
+ const first = kit.materials.length;
830
+ const root = new T2.Group();
831
+ root.name = item.id;
832
+ const entry = item.model === void 0 ? void 0 : itemModelEntry(item.model);
833
+ const builder = item.model === void 0 ? void 0 : itemModelBuilder(item.model);
834
+ let kind = "shape";
835
+ let shape;
836
+ if (entry && builder && !options.simple) {
837
+ kind = "model";
838
+ const inner = builder(kit);
839
+ inner.name = entry.teamId;
840
+ root.scale.setScalar(entry.scale);
841
+ root.add(inner);
842
+ restOnBase(T2, inner);
843
+ } else {
844
+ shape = shapeOf(item);
845
+ const inner = drawShape(kit, shape, item.size, { ...item.color === void 0 ? {} : { color: item.color }, ...options.detail === void 0 ? {} : { detail: options.detail } });
846
+ inner.name = shape;
847
+ root.add(inner);
848
+ restOnBase(T2, inner);
849
+ }
850
+ const materials = kit.materials.slice(first);
851
+ if (options.wear) setWear(materials, options.wear);
852
+ root.userData.wear = options.wear ?? 0;
853
+ return { root, kind, ...shape ? { shape } : {}, size: item.size, materials };
854
+ }
855
+
856
+ // ../game-props3d/dist/index.js
857
+ var T = (id, interior, w, d, h, y, solid, group) => ({ id, interior, w, d, h, y, solid, group });
858
+ var TYPE_DEFS = [
859
+ T("ceiling_light", true, 0.4, 0.4, 0.07, 0, false, "ceiling"),
860
+ T("pendant", true, 0.34, 0.34, 1, 0, false, "ceiling"),
861
+ T("track_spot", true, 1.2, 0.1, 0.2, 0, false, "ceiling"),
862
+ T("chandelier", true, 0.7, 0.7, 0.7, 0, false, "ceiling"),
863
+ T("wall_ac", true, 0.9, 0.2, 0.28, 2.15, false, "wall"),
864
+ T("picture_small", true, 0.35, 0.03, 0.45, 1.3, false, "wall"),
865
+ T("picture_large", true, 1, 0.04, 0.7, 1.2, false, "wall"),
866
+ T("mirror", true, 0.6, 0.03, 1.2, 0.9, false, "wall"),
867
+ T("curtain", true, 1.6, 0.12, 2.3, 0.1, false, "wall"),
868
+ T("blinds", true, 1.2, 0.08, 1.3, 1, false, "wall"),
869
+ T("flat_tv", true, 1.2, 0.06, 0.7, 1, false, "wall"),
870
+ T("floor_plant", true, 0.5, 0.5, 1.1, 0, true, "plants"),
871
+ T("table_plant", true, 0.2, 0.2, 0.3, 0, false, "plants"),
872
+ T("hanging_plant", true, 0.5, 0.5, 1, 0, false, "plants"),
873
+ T("tv_stand", true, 1.6, 0.42, 1.05, 0, true, "furniture"),
874
+ T("breakfast_bar", true, 2, 1.6, 0.95, 0, true, "furniture"),
875
+ T("sofa_l", true, 2.8, 1.7, 0.85, 0, true, "furniture"),
876
+ T("double_bed_modern", true, 1.6, 2.1, 1.2, 0, true, "furniture"),
877
+ T("home_office", true, 1.4, 0.75, 1.2, 0, true, "furniture"),
878
+ T("closet", true, 2.2, 0.6, 2.3, 0, true, "furniture"),
879
+ T("vanity", true, 0.6, 0.45, 1.5, 0, true, "furniture"),
880
+ T("ac_condenser", false, 0.8, 0.3, 0.55, 0.5, true, "yard"),
881
+ T("lawn", false, 4, 4, 0.02, 0, false, "floor"),
882
+ T("tile_floor", false, 4, 3, 0.03, 0, false, "floor"),
883
+ T("concrete", false, 4, 4, 0.03, 0, false, "floor"),
884
+ T("deck_floor", false, 4, 3, 0.04, 0, false, "floor"),
885
+ T("dirt", false, 4, 4, 0.02, 0, false, "floor"),
886
+ T("gourmet_counter", false, 4.2, 3, 2.9, 0, true, "yard"),
887
+ T("sauna", false, 2, 1.8, 2.1, 0, true, "yard"),
888
+ T("soaking_tub", false, 1.3, 1.3, 0.8, 0, true, "yard"),
889
+ T("pool", false, 4.5, 2.8, 0.1, 0, true, "yard"),
890
+ T("deck", false, 3.5, 3, 0.22, 0, true, "yard"),
891
+ T("sun_lounger", false, 0.62, 1.9, 0.5, 0, true, "yard"),
892
+ T("garden_bed", false, 2, 1, 0.9, 0, true, "yard"),
893
+ T("fruit_tree", false, 2.4, 2.4, 3.2, 0, true, "yard"),
894
+ T("palm_tree", false, 2, 2, 4.5, 0, true, "yard"),
895
+ T("pergola", false, 3.5, 3, 2.4, 0, true, "yard"),
896
+ T("slide", false, 0.8, 2.6, 1.5, 0, true, "yard"),
897
+ T("swing", false, 2.2, 1.7, 2.2, 0, true, "yard"),
898
+ T("bbq_simple", false, 0.9, 0.6, 1, 0, true, "yard"),
899
+ T("garage_car", false, 1.8, 4.2, 1.45, 0, true, "yard"),
900
+ T("window_boards", true, 1.2, 0.06, 1.15, 1, false, "house_state"),
901
+ T("barricade", true, 1.5, 0.7, 1.8, 0, true, "house_state"),
902
+ T("sandbags", false, 1.6, 0.5, 0.6, 0, true, "house_state"),
903
+ T("barbed_wire", false, 4, 0.2, 0.6, 0, false, "house_state"),
904
+ T("razor_wire", false, 4, 0.6, 0.6, 0, false, "house_state"),
905
+ T("water_bottle_crates", true, 0.9, 0.6, 0.65, 0, true, "house_state"),
906
+ T("canned_food", true, 0.4, 0.3, 0.3, 0, false, "house_state"),
907
+ T("water_jug", true, 0.3, 0.3, 0.5, 0, true, "house_state"),
908
+ T("floor_mattress", true, 0.9, 1.9, 0.3, 0, false, "house_state"),
909
+ T("lantern", true, 0.16, 0.16, 0.3, 0, false, "house_state"),
910
+ T("candles", true, 0.25, 0.25, 0.2, 0, false, "house_state"),
911
+ T("open_suitcases", true, 1.5, 0.7, 0.6, 0, false, "house_state"),
912
+ T("clothes_on_floor", true, 1.2, 1, 0.05, 0, false, "house_state"),
913
+ T("fallen_chair", true, 0.5, 0.5, 0.5, 0, false, "house_state"),
914
+ T("blood_stains", true, 1.2, 1.2, 0.01, 0, false, "house_state"),
915
+ T("open_door", true, 0.9, 0.15, 2.15, 0, false, "house_state")
916
+ ];
917
+ var DEFS = Object.fromEntries(TYPE_DEFS.map((t) => [t.id, t]));
918
+ var ATTRIBUTES = ["position", "normal", "uv"];
919
+ var isVisible = (object) => {
920
+ for (let node = object; node; node = node.parent) if (!node.visible) return false;
921
+ return true;
922
+ };
923
+ var bake = (THREE, mesh, matrix) => {
924
+ const source = mesh.geometry.index ? mesh.geometry.toNonIndexed() : mesh.geometry.clone();
925
+ const baked = new THREE.BufferGeometry();
926
+ const count = source.getAttribute("position").count;
927
+ for (const name of ATTRIBUTES) {
928
+ const attribute = source.getAttribute(name);
929
+ baked.setAttribute(name, attribute ?? new THREE.BufferAttribute(new Float32Array(count * (name === "uv" ? 2 : 3)), name === "uv" ? 2 : 3));
930
+ }
931
+ source.dispose();
932
+ return baked.applyMatrix4(matrix);
933
+ };
934
+ var join = (THREE, geometries) => {
935
+ const merged = new THREE.BufferGeometry();
936
+ for (const name of ATTRIBUTES) {
937
+ const size = (geometries[0]?.getAttribute(name)).itemSize;
938
+ const total = geometries.reduce((sum, geometry) => sum + geometry.getAttribute(name).count, 0);
939
+ const array = new Float32Array(total * size);
940
+ let offset = 0;
941
+ for (const geometry of geometries) {
942
+ array.set(geometry.getAttribute(name).array, offset);
943
+ offset += geometry.getAttribute(name).count * size;
944
+ }
945
+ merged.setAttribute(name, new THREE.BufferAttribute(array, size));
946
+ }
947
+ return merged;
948
+ };
949
+ function mergeByMaterial(THREE, root, space) {
950
+ const toSpace = space.matrixWorld.clone().invert();
951
+ const byMaterial = /* @__PURE__ */ new Map();
952
+ root.traverse((object) => {
953
+ const mesh = object;
954
+ if (!mesh.isMesh || Array.isArray(mesh.material) || !isVisible(mesh)) return;
955
+ const list = byMaterial.get(mesh.material) ?? [];
956
+ list.push(bake(THREE, mesh, new THREE.Matrix4().multiplyMatrices(toSpace, mesh.matrixWorld)));
957
+ byMaterial.set(mesh.material, list);
958
+ });
959
+ return [...byMaterial].map(([material, geometries]) => {
960
+ const merged = new THREE.Mesh(join(THREE, geometries), material);
961
+ for (const geometry of geometries) geometry.dispose();
962
+ merged.castShadow = false;
963
+ merged.receiveShadow = true;
964
+ return merged;
965
+ });
966
+ }
967
+
968
+ // src/merge.ts
969
+ function mergeItem(T2, model) {
970
+ model.root.updateMatrixWorld(true);
971
+ const merged = new T2.Group();
972
+ merged.name = model.root.name;
973
+ merged.scale.copy(model.root.scale);
974
+ merged.userData = { ...model.root.userData };
975
+ merged.add(...mergeByMaterial(T2, model.root, model.root));
976
+ return { ...model, root: merged };
977
+ }
978
+
979
+ // src/budgets.ts
980
+ var ITEM_BUDGETS = {
981
+ drawCalls: 30,
982
+ small: { triangles: 400, cullDistance: 30 },
983
+ large: { triangles: 1200, cullDistance: 60 },
984
+ lod1Ratio: 0.5,
985
+ largeFrom: 0.6
986
+ };
987
+ var sizeClassOf = (size, budgets = ITEM_BUDGETS) => Math.max(...size) >= budgets.largeFrom ? "large" : "small";
988
+ var triangleBudgetOf = (size, budgets = ITEM_BUDGETS) => budgets[sizeClassOf(size, budgets)].triangles;
989
+ var cullDistanceOf = (size, budgets = ITEM_BUDGETS) => budgets[sizeClassOf(size, budgets)].cullDistance;
990
+ function countTriangles(root) {
991
+ let total = 0;
992
+ root.traverse((object) => {
993
+ const mesh = object;
994
+ if (!mesh.isMesh || !mesh.visible) return;
995
+ const geometry = mesh.geometry;
996
+ total += geometry.index ? geometry.index.count / 3 : geometry.getAttribute("position").count / 3;
997
+ });
998
+ return Math.round(total);
999
+ }
1000
+ function countDrawCalls(root) {
1001
+ let total = 0;
1002
+ root.traverse((object) => {
1003
+ const mesh = object;
1004
+ if (!mesh.isMesh || !mesh.visible) return;
1005
+ total += Array.isArray(mesh.material) ? Math.max(1, mesh.geometry.groups.length) : 1;
1006
+ });
1007
+ return total;
1008
+ }
1009
+ function checkBudget(root, size, budgets = ITEM_BUDGETS) {
1010
+ const triangles = countTriangles(root);
1011
+ const limit = triangleBudgetOf(size, budgets);
1012
+ return { triangles, limit, drawCalls: countDrawCalls(root), sizeClass: sizeClassOf(size, budgets), ok: triangles <= limit };
1013
+ }
1014
+
1015
+ // src/lod.ts
1016
+ function buildItemLod(T2, item, options = {}) {
1017
+ const budgets = options.budgets ?? ITEM_BUDGETS;
1018
+ const cullDistance = cullDistanceOf(item.size, budgets);
1019
+ const lod1Distance = options.lod1Distance ?? cullDistance / 3;
1020
+ const joined = (model) => options.merge === false ? model : mergeItem(T2, model);
1021
+ const wear = options.wear === void 0 ? {} : { wear: options.wear };
1022
+ const near = joined(buildItem(T2, item, { ...wear, kit: createItemKit(T2) }));
1023
+ const far = joined(buildItem(T2, item, { ...wear, simple: true, detail: budgets.lod1Ratio, kit: createItemKit(T2) }));
1024
+ const root = new T2.LOD();
1025
+ root.name = item.id;
1026
+ root.addLevel(near.root, 0);
1027
+ root.addLevel(far.root, lod1Distance);
1028
+ root.addLevel(new T2.Group(), cullDistance);
1029
+ root.userData.wear = options.wear ?? 0;
1030
+ return { root, near, far, cullDistance, lod1Distance };
1031
+ }
1032
+
1033
+ // src/models/deviations.ts
1034
+ var MODEL_BUDGET_DEVIATIONS = {
1035
+ canned_beans: 744,
1036
+ amoxicillin: 596,
1037
+ flashlight: 1060,
1038
+ portable_radio: 504,
1039
+ house_key: 472,
1040
+ motorcycle_helmet: 840,
1041
+ lpg_cylinder_13kg: 712
1042
+ };
1043
+
1044
+ // src/thumbnails.ts
1045
+ var hex = (color) => `#${(color & 16777215).toString(16).padStart(6, "0")}`;
1046
+ var DEFAULT_COLOR2 = 9080724;
1047
+ var STEEL = "#9aa0a6";
1048
+ var BRASS = "#b8913a";
1049
+ var WOOD = "#8a643c";
1050
+ var PAPER = "#ece4d0";
1051
+ var ART = {
1052
+ can: (c, d, l) => `<rect x="19" y="13" width="26" height="42" rx="3" fill="${c}"/><rect x="19" y="24" width="26" height="18" fill="${l}"/><rect x="18" y="11" width="28" height="5" rx="2" fill="${STEEL}"/><rect x="18" y="52" width="28" height="4" rx="2" fill="${STEEL}"/>`,
1053
+ bag: (c, d, l) => `<path d="M16 55 L13 25 Q32 14 51 25 L48 55 Z" fill="${c}"/><rect x="14" y="17" width="36" height="7" rx="2" fill="${d}"/><rect x="22" y="32" width="20" height="14" rx="2" fill="${l}"/>`,
1054
+ box: (c, d, l) => `<rect x="11" y="18" width="42" height="37" fill="${c}"/><rect x="11" y="18" width="42" height="8" fill="${d}"/><rect x="22" y="33" width="20" height="13" fill="${l}"/>`,
1055
+ bottle: (c, d) => `<rect x="28" y="7" width="8" height="8" rx="2" fill="${d}"/><path d="M23 56 V30 Q23 23 28 18 H36 Q41 23 41 30 V56 Z" fill="${c}" fill-opacity=".75"/><rect x="23" y="34" width="18" height="14" fill="${PAPER}" fill-opacity=".9"/>`,
1056
+ tool: (c) => `<rect x="29" y="20" width="6" height="37" rx="2" fill="${WOOD}"/><rect x="17" y="9" width="30" height="13" rx="2" fill="${c === "#808080" ? STEEL : c}"/>`,
1057
+ roll: (c, d) => `<circle cx="32" cy="34" r="22" fill="${c}"/><circle cx="32" cy="34" r="8" fill="${d}"/>`,
1058
+ dish: (c, d, l) => `<ellipse cx="32" cy="44" rx="27" ry="9" fill="${c}"/><ellipse cx="32" cy="42" rx="19" ry="5" fill="${l}"/><ellipse cx="32" cy="42" rx="9" ry="2" fill="${d}"/>`,
1059
+ cloth: (c, d) => `<rect x="9" y="34" width="46" height="21" rx="3" fill="${c}"/><rect x="13" y="22" width="38" height="14" rx="3" fill="${d}"/>`,
1060
+ shoe: (c) => `<path d="M9 48 V27 H26 L30 36 H47 Q56 38 56 48 Z" fill="${c}"/><rect x="7" y="48" width="50" height="7" rx="2" fill="#22251f"/>`,
1061
+ jar: (c) => `<rect x="17" y="22" width="30" height="33" rx="6" fill="${c}" fill-opacity=".7"/><rect x="16" y="13" width="32" height="10" rx="2" fill="${BRASS}"/>`,
1062
+ paper: (c, d) => `<rect x="14" y="10" width="36" height="46" fill="${PAPER}"/><rect x="19" y="18" width="26" height="3" fill="${d}"/><rect x="19" y="26" width="26" height="3" fill="${d}"/><rect x="19" y="34" width="18" height="3" fill="${d}"/><rect x="19" y="42" width="22" height="3" fill="${c}"/>`,
1063
+ fruit: (c) => `<circle cx="32" cy="38" r="18" fill="${c}"/><rect x="31" y="13" width="3" height="9" fill="#5a3a22"/><path d="M34 16 Q44 10 48 16 Q40 20 34 16 Z" fill="#4f7a3a"/>`,
1064
+ cup: (c, d) => `<path d="M18 16 H46 L41 56 H23 Z" fill="${c}"/><rect x="18" y="14" width="28" height="4" fill="${d}"/>`,
1065
+ tube: (c, d) => `<path d="M23 15 H41 V48 L37 56 H27 L23 48 Z" fill="${c}"/><rect x="25" y="7" width="14" height="9" rx="2" fill="${d}"/>`,
1066
+ blade: () => `<rect x="5" y="33" width="18" height="8" rx="2" fill="#3b2a20"/><path d="M23 33 H55 L60 38 L23 42 Z" fill="#a9adb0"/><rect x="22" y="31" width="3" height="12" fill="${BRASS}"/>`,
1067
+ long_tool: (c) => `<rect x="5" y="30" width="50" height="5" rx="2" fill="${WOOD}"/><rect x="47" y="21" width="12" height="22" rx="2" fill="${c === "#808080" ? STEEL : c}"/>`,
1068
+ garment: (c, d) => `<path d="M20 12 L6 24 L13 33 L20 28 V56 H44 V28 L51 33 L58 24 L44 12 Q32 22 20 12 Z" fill="${c}"/><path d="M20 12 Q32 22 44 12 Q32 28 20 12 Z" fill="${d}"/>`,
1069
+ backpack: (c, d) => `<path d="M24 18 Q32 4 40 18" fill="none" stroke="${d}" stroke-width="3"/><rect x="15" y="16" width="34" height="40" rx="9" fill="${c}"/><rect x="21" y="35" width="22" height="16" rx="3" fill="${d}"/>`,
1070
+ jerrycan: (c, d) => `<rect x="13" y="17" width="38" height="39" rx="3" fill="${c}"/><rect x="17" y="8" width="16" height="10" rx="2" fill="${d}"/><rect x="39" y="9" width="9" height="9" fill="#d9a441"/>`,
1071
+ tank: (c, d) => `<rect x="21" y="18" width="22" height="38" rx="10" fill="${c}"/><rect x="28" y="9" width="8" height="10" fill="${BRASS}"/><rect x="20" y="43" width="24" height="3" fill="${d}"/>`,
1072
+ book: (c, d) => `<rect x="14" y="9" width="37" height="47" fill="${c}"/><rect x="14" y="9" width="6" height="47" fill="${d}"/><rect x="23" y="13" width="25" height="39" fill="#e9e1cc"/>`,
1073
+ key: () => `<circle cx="16" cy="32" r="9" fill="none" stroke="#9a9da0" stroke-width="5"/><rect x="24" y="30" width="33" height="5" fill="${BRASS}"/><rect x="45" y="35" width="4" height="7" fill="${BRASS}"/><rect x="51" y="35" width="3" height="5" fill="${BRASS}"/>`,
1074
+ battery: (c, d, l) => `<rect x="23" y="14" width="18" height="42" rx="2" fill="${c}"/><rect x="28" y="9" width="8" height="6" fill="#b9bdc0"/><rect x="23" y="26" width="18" height="16" fill="${l}"/><rect x="23" y="47" width="18" height="9" fill="${d}"/>`,
1075
+ plank: (c) => `<rect x="5" y="26" width="54" height="13" fill="${WOOD}"/><rect x="5" y="30" width="54" height="2" fill="${c === "#808080" ? "#6b4a2c" : c}" fill-opacity=".5"/><circle cx="12" cy="32" r="2" fill="#7d8186"/><circle cx="52" cy="32" r="2" fill="#7d8186"/>`,
1076
+ device: (c) => `<rect x="15" y="7" width="34" height="50" rx="5" fill="${c}"/><rect x="20" y="13" width="24" height="18" rx="2" fill="#9fbf8a"/><circle cx="26" cy="43" r="4" fill="#b9bdc0"/><rect x="35" y="40" width="9" height="6" rx="1" fill="#b9bdc0"/>`
1077
+ };
1078
+ function shapeThumbnailSvg(shape, color = DEFAULT_COLOR2) {
1079
+ const art = ART[shape];
1080
+ return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">${art(hex(color), hex(shade(color, 0.7)), hex(shade(color, 1.3)))}</svg>`;
1081
+ }
1082
+ function itemThumbnail(item) {
1083
+ if (item.model !== void 0 && hasItemModel(item.model)) return { render: true };
1084
+ return { svg: shapeThumbnailSvg(shapeOf(item), item.color) };
1085
+ }
1086
+ var itemThumbnailSvg = (item) => shapeThumbnailSvg(shapeOf(item), item.color);
1087
+ function thumbnailPatch(items) {
1088
+ const patch = {};
1089
+ for (const item of items) patch[item.id] = { thumbnail: itemThumbnail(item) };
1090
+ return patch;
1091
+ }
1092
+ var FIT_MARGIN = 1.15;
1093
+ var FIELD_OF_VIEW = 30;
1094
+ function composeThumbnailScene(T2, item, options = {}) {
1095
+ const { yaw = -0.6, pitch = 0.45, distance = 1, wear = 0 } = options;
1096
+ const scene = new T2.Scene();
1097
+ const model = buildItem(T2, item);
1098
+ if (wear) setWear(model.root, wear);
1099
+ scene.add(model.root);
1100
+ scene.add(new T2.HemisphereLight(14673646, 3814440, 0.85));
1101
+ const sun = new T2.DirectionalLight(16769727, 1.1);
1102
+ sun.position.set(2.5, 3.5, 2.5);
1103
+ scene.add(sun);
1104
+ const rim = new T2.DirectionalLight(9417430, 0.45);
1105
+ rim.position.set(-3, 1, -2.5);
1106
+ scene.add(rim);
1107
+ const box2 = new T2.Box3().setFromObject(model.root);
1108
+ const centre = box2.getCenter(new T2.Vector3());
1109
+ const radius = box2.getSize(new T2.Vector3()).length() / 2;
1110
+ const camera = new T2.PerspectiveCamera(FIELD_OF_VIEW, 1, radius / 50, radius * 40);
1111
+ const fit = radius * FIT_MARGIN / Math.sin(FIELD_OF_VIEW * Math.PI / 360);
1112
+ const away = fit * distance;
1113
+ camera.position.set(centre.x + away * Math.cos(pitch) * Math.sin(yaw), centre.y + away * Math.sin(pitch), centre.z + away * Math.cos(pitch) * Math.cos(yaw));
1114
+ camera.lookAt(centre);
1115
+ camera.updateMatrixWorld(true);
1116
+ return { scene, camera };
1117
+ }
1118
+ function renderItemThumbnail(T2, renderer, item, options = {}) {
1119
+ const { scene, camera } = composeThumbnailScene(T2, item, options);
1120
+ const size = options.size ?? 128;
1121
+ renderer.setSize(size, size);
1122
+ renderer.setClearColor?.(0, 0);
1123
+ renderer.render(scene, camera);
1124
+ return renderer.domElement.toDataURL("image/png");
1125
+ }
1126
+
1127
+ // ../game-interactables/dist/index.js
1128
+ function hashString(text) {
1129
+ let h = 2166136261;
1130
+ for (let i = 0; i < text.length; i++) {
1131
+ h ^= text.charCodeAt(i);
1132
+ h = Math.imul(h, 16777619);
1133
+ }
1134
+ return h >>> 0;
1135
+ }
1136
+ function rng(seed = 1) {
1137
+ let a = (typeof seed === "number" ? Math.floor(seed) >>> 0 : hashString(seed)) || 2654435769;
1138
+ return () => {
1139
+ a = a + 1831565813 >>> 0;
1140
+ let t = a;
1141
+ t = Math.imul(t ^ t >>> 15, t | 1);
1142
+ t ^= t + Math.imul(t ^ t >>> 7, t | 61);
1143
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
1144
+ };
1145
+ }
1146
+
1147
+ // ../game-contracts/dist/index.js
1148
+ var UNSAFE_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
1149
+ var isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype;
1150
+ var deepMerge = (base, patch) => {
1151
+ if (!patch) return base;
1152
+ const result = { ...base };
1153
+ for (const [key2, value] of Object.entries(patch)) {
1154
+ if (value === void 0 || UNSAFE_KEYS.has(key2)) continue;
1155
+ const current = result[key2];
1156
+ result[key2] = isPlainObject(current) && isPlainObject(value) ? deepMerge(current, value) : value;
1157
+ }
1158
+ return result;
1159
+ };
1160
+
1161
+ // ../game-items/dist/index.js
1162
+ var ITEM_CATEGORIES = [
1163
+ { id: "food", name: "Food" },
1164
+ { id: "drink", name: "Drink" },
1165
+ { id: "medical", name: "Medical" },
1166
+ { id: "tools", name: "Tools" },
1167
+ { id: "melee", name: "Melee" },
1168
+ { id: "materials", name: "Materials" },
1169
+ { id: "clothing", name: "Clothing" },
1170
+ { id: "bags", name: "Bags" },
1171
+ { id: "electronics", name: "Electronics" },
1172
+ { id: "fuel", name: "Fuel" },
1173
+ { id: "outdoors", name: "Outdoors" },
1174
+ { id: "books", name: "Books" },
1175
+ { id: "collectibles", name: "Collectibles" },
1176
+ { id: "keys", name: "Keys" }
1177
+ ];
1178
+ var DEFAULT_RULES = {
1179
+ rarity: {
1180
+ weights: { common: 60, uncommon: 28, rare: 9, very_rare: 2.5, legendary: 0.5, unique: 0 },
1181
+ fixedOnly: ["legendary", "unique"]
1182
+ },
1183
+ freshness: { stale: 0.5, spoiled: 1, rotten: 1.5 },
1184
+ storage: {
1185
+ fridge: { multiplier: 0.33, needsPower: true },
1186
+ freezer: { multiplier: 0.1, needsPower: true },
1187
+ cooler: { multiplier: 0.5, limitDays: 2, afterLimit: 1 },
1188
+ delivery_bag: { multiplier: 0.8 },
1189
+ cellar: { multiplier: 0.66 },
1190
+ ambient: { multiplier: 1 },
1191
+ hot: { multiplier: 2 }
1192
+ },
1193
+ carry: { baseKg: 8, perStrengthKg: 2.5, factor: 1.2, encumbered: 1, overloaded: 1.5 },
1194
+ protection: { cap: 0.9, wear: { bite: 8, scratch: 3 }, tornShare: 0.25, repairMargin: 10 },
1195
+ aging: { thawShelfDays: { freezer: 2 } },
1196
+ inventory: { defaultSlots: 10, encumberedRunFactor: 0.75 },
1197
+ use: {
1198
+ freshness: {
1199
+ stale: { satietyFactor: 0.7, poisonChance: 0.1, morale: 0, isEdible: true },
1200
+ spoiled: { satietyFactor: 0.7, poisonChance: 0.5, morale: -20, isEdible: true },
1201
+ rotten: { satietyFactor: 0, poisonChance: 0, morale: 0, isEdible: false }
1202
+ },
1203
+ cookedPoisonFactor: 0.25
1204
+ },
1205
+ spawn: {
1206
+ retailNew: true,
1207
+ retailPlaces: ["supermarket", "pharmacy", "mall", "gas_station", "gun_shop"],
1208
+ retailContainers: ["store_shelf", "stockroom"],
1209
+ condition: { common: [30, 90], uncommon: [40, 90], rare: [50, 100], very_rare: [50, 100], legendary: [50, 100], unique: [50, 100] },
1210
+ ageShare: [0, 0.6],
1211
+ charge: [0.2, 1],
1212
+ fillable: ["fuel_jerrycan"],
1213
+ fill: [0, 0.6]
1214
+ },
1215
+ finds: {
1216
+ fallbackContainer: "zombie_corpse",
1217
+ rollExcluded: ["ammo_7_62x39"],
1218
+ depletion: [
1219
+ { places: ["pharmacy", "gun_shop", "police_station"], perDay: 0.05, floor: 0.3 },
1220
+ { places: ["supermarket", "gas_station"], perDay: 0.04, floor: 0.35 },
1221
+ { places: ["mall", "hospital"], perDay: 0.025, floor: 0.5 },
1222
+ { places: ["house", "apartment"], perDay: 0.01, floor: 0.7 },
1223
+ { places: ["farm", "warehouse", "mine", "dam", "lodge", "church"], perDay: 5e-3, floor: 0.85 },
1224
+ { places: ["military_base"], perDay: 0, floor: 1 }
1225
+ ],
1226
+ regionMultiplier: {},
1227
+ states: { normal: "normal", fortified: "fortified", abandoned: "abandoned" },
1228
+ pool: {
1229
+ defaultFootprint: 0.5,
1230
+ offset: 1.5,
1231
+ perSquareMetre: 3,
1232
+ jitter: 1.5,
1233
+ min: 2,
1234
+ max: 5,
1235
+ fortifiedExtra: 2,
1236
+ supplyWeight: 2.5,
1237
+ supplyExtraQty: 1,
1238
+ abandonedEmptyChance: 0.4,
1239
+ abandonedKept: 1,
1240
+ junkChance: 0.5,
1241
+ junkItem: "food_packaging",
1242
+ spoiledFoodItem: "leftover_meal",
1243
+ coldContainers: ["geladeira", "refrig_exp", "freezer"]
1244
+ }
1245
+ }
1246
+ };
1247
+ var storageRuleOf = (rules, storage) => {
1248
+ const rule = rules.storage[storage];
1249
+ if (!rule) throw new Error(`game-items: unknown storage "${storage}"`);
1250
+ return rule;
1251
+ };
1252
+ var accruedDays = (storage, window, options) => {
1253
+ const { powered = true, rules = DEFAULT_RULES } = options;
1254
+ const rule = storageRuleOf(rules, storage);
1255
+ const elapsed = window.to - window.from;
1256
+ if (elapsed <= 0) return 0;
1257
+ if (rule.needsPower && !powered) return elapsed;
1258
+ if (rule.limitDays === void 0) return elapsed * rule.multiplier;
1259
+ const limitEnd = window.storedAt + rule.limitDays;
1260
+ const early = Math.max(0, Math.min(window.to, limitEnd) - Math.max(window.from, window.storedAt));
1261
+ return early * rule.multiplier + (elapsed - early) * (rule.afterLimit ?? 1);
1262
+ };
1263
+ var advanceAge = (state, now, options = {}) => {
1264
+ const { powered = true, rules = DEFAULT_RULES } = options;
1265
+ const rule = storageRuleOf(rules, state.storage);
1266
+ const thawDays = rules.aging.thawShelfDays[state.storage];
1267
+ const isThawing = rule.needsPower === true && !powered && thawDays !== void 0 && state.shelfLife === void 0;
1268
+ const base = isThawing ? { ...state, ageDays: 0, shelfLife: thawDays } : state;
1269
+ const ageDays = base.ageDays + accruedDays(state.storage, { storedAt: state.storedAt, from: state.since, to: now }, options);
1270
+ return { ...base, ageDays, since: Math.max(now, state.since) };
1271
+ };
1272
+ var shelfRatio = (item, state) => {
1273
+ const shelfLife = state.shelfLife ?? item.shelfLife;
1274
+ return shelfLife === void 0 ? 0 : state.ageDays / shelfLife;
1275
+ };
1276
+ var freshness = (item, state, now, options = {}) => {
1277
+ const { rules = DEFAULT_RULES } = options;
1278
+ const ratio = shelfRatio(item, advanceAge(state, now, options));
1279
+ if (ratio >= rules.freshness.rotten) return "rotten";
1280
+ if (ratio >= rules.freshness.spoiled) return "spoiled";
1281
+ if (ratio >= rules.freshness.stale) return "stale";
1282
+ return "fresh";
1283
+ };
1284
+ var countOf = (context, id) => context.inventory.get(id) ?? 0;
1285
+ var candidatesOf = (input, context) => {
1286
+ if (input.item !== void 0) return [input.item];
1287
+ if (input.any) return [...input.any];
1288
+ if (!input.match) return [];
1289
+ const { category, cookable } = input.match;
1290
+ return context.catalog.list().filter((item) => (category === void 0 || item.category === category) && (cookable === void 0 || item.cookable === cookable)).map((item) => item.id);
1291
+ };
1292
+ var describeInput = (input) => {
1293
+ if (input.item !== void 0) return `item:${input.item}`;
1294
+ if (input.any) return `any:${input.any.join("|")}`;
1295
+ if (input.match) return `match:${input.match.category ?? "cookable"}`;
1296
+ if (input.fluid !== void 0) return `fluid:${input.fluid}`;
1297
+ return `world:${(input.world ?? []).join("|")}`;
1298
+ };
1299
+ var takeUnits = (candidates, needed, context) => {
1300
+ const taken = [];
1301
+ let remaining = needed;
1302
+ for (const id of candidates) {
1303
+ if (remaining <= 0) break;
1304
+ const count = Math.min(remaining, countOf(context, id));
1305
+ if (count > 0) taken.push({ item: id, count });
1306
+ remaining -= count;
1307
+ }
1308
+ return remaining > 0 ? void 0 : taken;
1309
+ };
1310
+ var consumptionFor = (input, context) => {
1311
+ if (input.world) return input.world.some((thing) => context.world?.has(thing)) ? [] : void 0;
1312
+ if (input.fluid !== void 0) {
1313
+ const liters = input.liters ?? 1;
1314
+ return (context.fluids?.[input.fluid] ?? 0) >= liters ? [{ fluid: input.fluid, liters }] : void 0;
1315
+ }
1316
+ const taken = takeUnits(candidatesOf(input, context), input.qty ?? 1, context);
1317
+ if (!taken || input.meters === void 0) return taken;
1318
+ return taken.map((entry) => "item" in entry ? { ...entry, meters: input.meters } : entry);
1319
+ };
1320
+ var toolMissing = (recipe, context) => recipe.tools.flatMap((tool2) => {
1321
+ const missing = [];
1322
+ if (tool2.anyOf.length > 0 && !tool2.anyOf.some((id) => countOf(context, id) > 0)) missing.push(`tool:${tool2.anyOf.join("|")}`);
1323
+ if (tool2.world && !tool2.world.some((station) => context.world?.has(station))) missing.push(`world:${tool2.world.join("|")}`);
1324
+ return missing;
1325
+ });
1326
+ var gateMissing = (recipe, context) => {
1327
+ const missing = [];
1328
+ if (context.skills && recipe.skill && (context.skills[recipe.skill.id] ?? 0) < recipe.skill.level) missing.push(`skill:${recipe.skill.id}:${recipe.skill.level}`);
1329
+ if (context.unlocked && recipe.unlockedBy !== void 0 && !context.unlocked.has(recipe.unlockedBy)) missing.push(`unlock:${recipe.unlockedBy}`);
1330
+ return missing;
1331
+ };
1332
+ var canCraft = (recipe, context) => {
1333
+ const missing = [
1334
+ ...gateMissing(recipe, context),
1335
+ ...toolMissing(recipe, context),
1336
+ ...recipe.inputs.filter((input) => consumptionFor(input, context) === void 0).map(describeInput)
1337
+ ];
1338
+ return { isPossible: missing.length === 0, missing };
1339
+ };
1340
+ var outcomeOf = (output, random) => {
1341
+ if ("effect" in output) return { effect: output.effect, params: output.params, ...output.text === void 0 ? {} : { text: output.text } };
1342
+ const [low, high] = output.qty;
1343
+ return { item: output.item, count: low + Math.floor(random() * (high - low + 1)) };
1344
+ };
1345
+ var craft = (recipe, context, random) => {
1346
+ const check = canCraft(recipe, context);
1347
+ if (!check.isPossible) return { isCrafted: false, missing: check.missing };
1348
+ const consumed = recipe.inputs.flatMap((input) => consumptionFor(input, context) ?? []);
1349
+ return { isCrafted: true, seconds: recipe.seconds, consumed, output: outcomeOf(recipe.output, random) };
1350
+ };
1351
+ var depletionFactor = (place, day, rules) => {
1352
+ if (day <= 0) return 1;
1353
+ const rule = rules.finds.depletion.find((entry) => entry.places.includes(place));
1354
+ return rule ? Math.max(rule.floor, 1 - rule.perDay * day) : 1;
1355
+ };
1356
+ var scaleCount = (count, scale, rules) => {
1357
+ const region = scale.region === void 0 ? 1 : rules.finds.regionMultiplier[scale.region] ?? 1;
1358
+ return Math.round(count * depletionFactor(scale.place, scale.day, rules) * region);
1359
+ };
1360
+ var rollableItems = (catalog, category, place) => {
1361
+ const { rules } = catalog;
1362
+ const rollable = catalog.byCategory(category).filter((item) => !rules.rarity.fixedOnly.includes(item.rarity) && !rules.finds.rollExcluded.includes(item.id));
1363
+ const here = rollable.filter((item) => item.finds?.places.includes(place));
1364
+ return here.length > 0 ? here : rollable;
1365
+ };
1366
+ var rarityWeight = (catalog, item) => catalog.rules.rarity.weights[item.rarity];
1367
+ var pickCategory = (weights, random) => {
1368
+ let remaining = random();
1369
+ const categories = Object.keys(weights);
1370
+ for (const category of categories) {
1371
+ remaining -= weights[category];
1372
+ if (remaining <= 0) return category;
1373
+ }
1374
+ return categories[0];
1375
+ };
1376
+ var rollByCategory = (catalog, query) => {
1377
+ const { rules } = catalog;
1378
+ const table = catalog.containers.get(query.container) ?? catalog.containers.get(rules.finds.fallbackContainer);
1379
+ if (!table) return [];
1380
+ const [low, high] = table.count ?? [0, 0];
1381
+ const drawn = low + Math.floor(query.rng() * (high - low + 1));
1382
+ const count = scaleCount(drawn, { place: query.place, day: query.day ?? 0, ...query.region === void 0 ? {} : { region: query.region } }, rules);
1383
+ const weights = table.categories ?? table.categoriesByPlace?.[query.place] ?? table.categoriesByPlace?.["*"];
1384
+ if (!weights) return [];
1385
+ const picked = [];
1386
+ for (let stack = 0; stack < count; stack++) {
1387
+ const category = pickCategory(weights, query.rng);
1388
+ const pool = category === void 0 ? [] : rollableItems(catalog, category, query.place);
1389
+ if (pool.length === 0) continue;
1390
+ const total = pool.reduce((sum, item) => sum + rarityWeight(catalog, item), 0);
1391
+ let remaining = query.rng() * total;
1392
+ for (const item of pool) {
1393
+ remaining -= rarityWeight(catalog, item);
1394
+ if (remaining <= 0) {
1395
+ picked.push(item.id);
1396
+ break;
1397
+ }
1398
+ }
1399
+ }
1400
+ return picked;
1401
+ };
1402
+ var weightedOrder = (candidates, random) => {
1403
+ const remaining = [...candidates];
1404
+ const ordered = [];
1405
+ while (remaining.length > 0) {
1406
+ const total = remaining.reduce((sum, candidate) => sum + candidate.weight, 0);
1407
+ let x = random() * total;
1408
+ let index = 0;
1409
+ for (; index < remaining.length - 1; index++) {
1410
+ x -= remaining[index].weight;
1411
+ if (x < 0) break;
1412
+ }
1413
+ ordered.push(...remaining.splice(index, 1));
1414
+ }
1415
+ return ordered;
1416
+ };
1417
+ var countFor = (pool, state, footprint, query, rules) => {
1418
+ const settings = rules.finds.pool;
1419
+ const base = Math.max(settings.min, Math.min(settings.max, Math.round(settings.offset + footprint * settings.perSquareMetre + query.rng() * settings.jitter)));
1420
+ if (state === "fortified") return Math.min(pool.length, base + settings.fortifiedExtra);
1421
+ if (state === "abandoned") return query.rng() < settings.abandonedEmptyChance ? 0 : settings.abandonedKept;
1422
+ return base;
1423
+ };
1424
+ var rollExplicitPool = (pool, state, query, rules) => {
1425
+ const settings = rules.finds.pool;
1426
+ const n = countFor(pool, state, query.footprint ?? settings.defaultFootprint, query, rules);
1427
+ const isFortified = state === "fortified";
1428
+ const candidates = pool.map((entry) => ({ entry, weight: isFortified && entry.isSupply ? entry.weight * settings.supplyWeight : entry.weight }));
1429
+ const picks = [];
1430
+ for (const { entry } of weightedOrder(candidates, query.rng).slice(0, n)) {
1431
+ const [low, high] = entry.qty;
1432
+ const bonus = isFortified && entry.isSupply ? settings.supplyExtraQty : 0;
1433
+ picks.push({ itemId: entry.item, count: low + Math.floor(query.rng() * (high - low + 1)) + bonus, entry });
1434
+ }
1435
+ if (state === "abandoned") {
1436
+ if (picks.length === 0 && query.rng() < settings.junkChance) picks.push({ itemId: settings.junkItem, count: 1 });
1437
+ if (settings.coldContainers.includes(query.container) || query.key !== void 0 && settings.coldContainers.includes(query.key)) picks.push({ itemId: settings.spoiledFoodItem, count: 1 });
1438
+ }
1439
+ return picks;
1440
+ };
1441
+ var WEARING_DURABILITY = ["condition", "hits", "cuts"];
1442
+ var hasCondition = (item) => item.protection !== void 0 || item.weapon !== void 0 || WEARING_DURABILITY.includes(item.durability?.kind ?? "");
1443
+ var between = (range, random) => range[0] + random() * (range[1] - range[0]);
1444
+ var spawnAgeDays = (item, outbreakDays, random, catalog) => item.shelfLife === void 0 ? void 0 : outbreakDays + between(catalog.rules.spawn.ageShare, random) * item.shelfLife;
1445
+ var isRetailStock = (query, catalog) => {
1446
+ const { spawn } = catalog.rules;
1447
+ const isRetail = spawn.retailNew && (query.day ?? 0) <= 0 && spawn.retailPlaces.includes(query.place);
1448
+ return isRetail && (spawn.retailContainers.length === 0 || spawn.retailContainers.includes(query.container));
1449
+ };
1450
+ var spawnData = (item, query, catalog) => {
1451
+ const { spawn } = catalog.rules;
1452
+ const data = {};
1453
+ const ageDays = spawnAgeDays(item, query.day ?? 0, query.rng, catalog);
1454
+ if (ageDays !== void 0) data.ageDays = ageDays;
1455
+ if (hasCondition(item)) data.condition = isRetailStock(query, catalog) ? 100 : Math.round(between(spawn.condition[item.rarity], query.rng));
1456
+ if (item.charge) data.charge = between(spawn.charge, query.rng);
1457
+ if (spawn.fillable.includes(item.id)) data.fill = between(spawn.fill, query.rng);
1458
+ return Object.keys(data).length > 0 ? data : void 0;
1459
+ };
1460
+ function createFindsProvider(catalog, options = {}) {
1461
+ const rules = options.rules ? deepMerge(catalog.rules, options.rules) : catalog.rules;
1462
+ const scoped = rules === catalog.rules ? catalog : { ...catalog, rules };
1463
+ const usesPools = (options.pools ?? "team") === "team";
1464
+ const aliases = options.placeAliases ?? {};
1465
+ const poolPlaces = options.poolPlaces;
1466
+ const passes = options.unknownContainers === "pass";
1467
+ const choose = (query) => {
1468
+ const pool = usesPools && (poolPlaces === void 0 || poolPlaces.includes(query.place)) ? scoped.pools.get(query.key ?? query.container) ?? scoped.pools.get(query.container) : void 0;
1469
+ if (!pool && passes && !scoped.containers.has(query.container)) return null;
1470
+ if (!pool) return rollByCategory(scoped, query).map((itemId) => ({ itemId, count: 1 }));
1471
+ return rollExplicitPool(pool, rules.finds.states[query.state] ?? "normal", query, rules).map(({ itemId, count }) => ({ itemId, count }));
1472
+ };
1473
+ return {
1474
+ findsFor(asked) {
1475
+ const query = aliases[asked.place] === void 0 ? asked : { ...asked, place: aliases[asked.place] };
1476
+ const chosen = choose(query);
1477
+ if (chosen === null) return null;
1478
+ return chosen.filter((stack) => scoped.has(stack.itemId)).map((stack) => {
1479
+ const data = spawnData(scoped.get(stack.itemId), query, scoped);
1480
+ return data ? { ...stack, data } : stack;
1481
+ });
1482
+ }
1483
+ };
1484
+ }
1485
+ var MM = 1e-3;
1486
+ var BOX_WALL = 6 * MM;
1487
+
1488
+ // src/area-layout.ts
1489
+ var SHELF_HEIGHT = 0.9;
1490
+ var ROW_LIMIT = 5;
1491
+ var GAP = 0.06;
1492
+ var EDGE = 0.12;
1493
+ var COLUMNS = 4;
1494
+ var COLUMN_PITCH = 6.2;
1495
+ var ROW_GAP = 1.5;
1496
+ var FIRST_Z = -11;
1497
+ var SHELF_PREFIX = "shelf_";
1498
+ var ITEM_PREFIX = "item_";
1499
+ var SHOWCASE_PREFIX = "showcase_";
1500
+ var rowsOf = (items) => {
1501
+ const rows = [];
1502
+ for (const item of items) {
1503
+ const [w, , d] = item.size;
1504
+ let row = rows[rows.length - 1];
1505
+ if (!row || row.items.length > 0 && row.width + w + GAP > ROW_LIMIT) {
1506
+ row = { items: [], width: 0, depth: 0 };
1507
+ rows.push(row);
1508
+ }
1509
+ row.items.push(item);
1510
+ row.width += w + GAP;
1511
+ row.depth = Math.max(row.depth, d);
1512
+ }
1513
+ return rows;
1514
+ };
1515
+ function shelvesLayout(items, categories) {
1516
+ const plans = categories.flatMap((category) => {
1517
+ const own = items.filter((item) => item.category === category);
1518
+ if (own.length === 0) return [];
1519
+ const rows = rowsOf(own);
1520
+ const length = Math.max(...rows.map((row) => row.width - GAP)) + 2 * EDGE;
1521
+ const depth = rows.reduce((sum, row) => sum + row.depth + GAP, 0) - GAP + 2 * EDGE;
1522
+ return [{ category, rows, length, depth }];
1523
+ });
1524
+ const result = [];
1525
+ let z = FIRST_Z;
1526
+ for (let start = 0; start < plans.length; start += COLUMNS) {
1527
+ const line = plans.slice(start, start + COLUMNS);
1528
+ const lineDepth = Math.max(...line.map((plan) => plan.depth));
1529
+ line.forEach((plan, column) => {
1530
+ const x = (column - (COLUMNS - 1) / 2) * COLUMN_PITCH;
1531
+ const centre = [x, 0, z + plan.depth / 2];
1532
+ const placed = [];
1533
+ let rowZ = -plan.depth / 2 + EDGE;
1534
+ for (const row of plan.rows) {
1535
+ let cursor = -plan.length / 2 + EDGE;
1536
+ for (const item of row.items) {
1537
+ const [w, , d] = item.size;
1538
+ placed.push({ item: `${ITEM_PREFIX}${item.id}`, position: [centre[0] + cursor + w / 2, SHELF_HEIGHT + 0.01, centre[2] + rowZ + d / 2], asleep: true });
1539
+ cursor += w + GAP;
1540
+ }
1541
+ rowZ += row.depth + GAP;
1542
+ }
1543
+ result.push({ category: plan.category, id: `${SHELF_PREFIX}${plan.category}`, length: plan.length, depth: plan.depth, position: centre, items: placed });
1544
+ });
1545
+ z += lineDepth + ROW_GAP;
1546
+ }
1547
+ return result;
1548
+ }
1549
+
1550
+ // src/area.ts
1551
+ var SHOWCASE_STAND_ID = "stand_models";
1552
+ var RULER_ID = "ruler_155";
1553
+ var HUMAN_ID = "human_180";
1554
+ var FINDS_TABLE_ID = "finds_table";
1555
+ var RULER_HEIGHT = 1.55;
1556
+ var HUMAN_HEIGHT = 1.8;
1557
+ var STAND = { at: [0, 0, 9.5], radius: 2.1, height: 0.8, ring: 1.35 };
1558
+ var RULER_AT = [-6.5, 0, 9];
1559
+ var HUMAN_AT = [-5.4, 0, 9];
1560
+ var TABLE = { at: [7, 0, 9.5], length: 3.2, depth: 1, height: 0.8 };
1561
+ var WEARS = ["new", "used", "spoiled"];
1562
+ var WEAR_STAGE = { new: 0, used: 1, spoiled: 2 };
1563
+ var SPIN = 0.6;
1564
+ var MAX_COPIES = 5;
1565
+ var CATEGORY_ALL = "all";
1566
+ var BOX_COLOR = 5227511;
1567
+ var HIGHLIGHT = 6961664;
1568
+ var TEXT = {
1569
+ en: {
1570
+ group: "Items",
1571
+ name: "Items area",
1572
+ shelf: "Shelf of {category}",
1573
+ stand: "Showcase stand of the 3D models",
1574
+ ruler: "Ruler 1.55 m",
1575
+ human: "Human figure 1.80 m",
1576
+ table: "Finds table",
1577
+ control: {
1578
+ category: "Category",
1579
+ wear: "Wear",
1580
+ age_days: "Age",
1581
+ storage: "Storage",
1582
+ powered: "Storage has power",
1583
+ place: "Place",
1584
+ container: "Container",
1585
+ state: "State of the place",
1586
+ day: "Outbreak day",
1587
+ seed: "Seed",
1588
+ roll_finds: "Roll finds",
1589
+ recipe: "Recipe",
1590
+ craft: "Craft"
1591
+ },
1592
+ hint: {
1593
+ age_days: "Days the food has been kept: it spoils by its shelf life, faster or slower by the storage",
1594
+ powered: "A fridge or freezer only keeps the food while it has power",
1595
+ day: "Days since the outbreak: 0 is the normal world",
1596
+ roll_finds: "Draws what the container holds and lays the stacks on the finds table",
1597
+ craft: "Makes the recipe with exactly what it asks for and lays the result on the finds table"
1598
+ },
1599
+ all: "All",
1600
+ wears: { new: "New", used: "Used", spoiled: "Spoiled" },
1601
+ states: { normal: "Normal", fortified: "Fortified", abandoned: "Abandoned" },
1602
+ storages: {},
1603
+ layers: { labels: "Labels", boxes: "Size boxes", simple_shapes: "Highlight simple shapes" },
1604
+ metrics: { items: "Items shown", draw_calls: "Draw calls", triangles: "Triangles", modeled: "With a team model", finds: "Stacks on the table" },
1605
+ rolled: "Finds in {container} of {place} ({state}, day {day}): {stacks} stacks",
1606
+ rolledNothing: "{container} of {place} holds nothing",
1607
+ crafted: "Crafted {recipe}: {output} ({seconds} s)",
1608
+ craftMissing: "Cannot craft {recipe}: missing {missing}",
1609
+ craftEffect: "effect {effect}",
1610
+ scenarios: {
1611
+ real_size: "Every item is drawn at its real size",
1612
+ models: "The 24 models of the team are shipped and draw their items",
1613
+ budgets: "Models and shapes stay within the triangle and draw call budgets",
1614
+ ruler: "The ruler is 1.55 m and the human figure 1.80 m",
1615
+ finds: "The finds roll is the same for the same seed",
1616
+ freshness: "Food spoils with age and the fridge slows it down",
1617
+ craft: "A recipe made with what it asks for gives its output"
1618
+ }
1619
+ },
1620
+ "pt-BR": {
1621
+ group: "Itens",
1622
+ name: "\xC1rea de itens",
1623
+ shelf: "Prateleira de {category}",
1624
+ stand: "Pedestal dos modelos 3D",
1625
+ ruler: "R\xE9gua de 1,55 m",
1626
+ human: "Silhueta humana de 1,80 m",
1627
+ table: "Mesa de achados",
1628
+ control: {
1629
+ category: "Categoria",
1630
+ wear: "Desgaste",
1631
+ age_days: "Idade",
1632
+ storage: "Armazenamento",
1633
+ powered: "Armazenamento com energia",
1634
+ place: "Lugar",
1635
+ container: "Recipiente",
1636
+ state: "Estado do lugar",
1637
+ day: "Dia do surto",
1638
+ seed: "Semente",
1639
+ roll_finds: "Sortear achados",
1640
+ recipe: "Receita",
1641
+ craft: "Fabricar"
1642
+ },
1643
+ hint: {
1644
+ age_days: "Dias que a comida ficou guardada: estraga pela validade, mais r\xE1pido ou mais devagar conforme onde est\xE1",
1645
+ powered: "Geladeira e freezer s\xF3 conservam enquanto t\xEAm energia",
1646
+ day: "Dias desde o surto: 0 \xE9 o mundo normal",
1647
+ roll_finds: "Sorteia o que o recipiente guarda e p\xF5e as pilhas na mesa de achados",
1648
+ craft: "Faz a receita com exatamente o que ela pede e p\xF5e o resultado na mesa de achados"
1649
+ },
1650
+ all: "Todas",
1651
+ wears: { new: "Novo", used: "Usado", spoiled: "Estragado" },
1652
+ states: { normal: "Normal", fortified: "Fortificado", abandoned: "Abandonado" },
1653
+ storages: {},
1654
+ layers: { labels: "R\xF3tulos", boxes: "Caixas de tamanho", simple_shapes: "Destacar formas simples" },
1655
+ metrics: { items: "Itens \xE0 mostra", draw_calls: "Chamadas de desenho", triangles: "Tri\xE2ngulos", modeled: "Com modelo do time", finds: "Pilhas na mesa" },
1656
+ rolled: "Achados em {container} de {place} ({state}, dia {day}): {stacks} pilhas",
1657
+ rolledNothing: "{container} de {place} n\xE3o tem nada",
1658
+ crafted: "Fabricou {recipe}: {output} ({seconds} s)",
1659
+ craftMissing: "N\xE3o d\xE1 para fabricar {recipe}: falta {missing}",
1660
+ craftEffect: "efeito {effect}",
1661
+ scenarios: {
1662
+ real_size: "Todo item \xE9 desenhado no tamanho real",
1663
+ models: "Os 24 modelos do time v\xEAm no kit e desenham os seus itens",
1664
+ budgets: "Modelos e formas ficam dentro do or\xE7amento de tri\xE2ngulos e de chamadas de desenho",
1665
+ ruler: "A r\xE9gua tem 1,55 m e a silhueta humana 1,80 m",
1666
+ finds: "O sorteio de achados \xE9 o mesmo para a mesma semente",
1667
+ freshness: "A comida estraga com a idade e a geladeira atrasa isso",
1668
+ craft: "Uma receita feita com o que ela pede d\xE1 o que promete"
1669
+ }
1670
+ }
1671
+ };
1672
+ var textOf = (locale) => TEXT[locale] ?? TEXT.en;
1673
+ var fill = (template, vars) => template.replace(/\{(\w+)\}/g, (_, key2) => String(vars[key2] ?? ""));
1674
+ var humanize = (id) => id.replace(/_/g, " ").replace(/^./, (c) => c.toUpperCase());
1675
+ var stageOf = (catalog, item, base, ageDays, storage, powered) => {
1676
+ if (item.shelfLife === void 0 || ageDays <= 0) return base;
1677
+ const state = freshness(item, { ageDays: 0, since: 0, storage, storedAt: 0 }, ageDays, { powered, rules: catalog.rules });
1678
+ const aged = state === "fresh" ? 0 : state === "stale" ? 1 : 2;
1679
+ return Math.max(base, aged);
1680
+ };
1681
+ var sizeOfModel = (id) => {
1682
+ const entry = itemModelEntry(id);
1683
+ return entry ? [entry.teamBox[0] * entry.scale, entry.teamBox[1] * entry.scale, entry.teamBox[2] * entry.scale] : void 0;
1684
+ };
1685
+ function createItemsTestModule(options) {
1686
+ const { catalog } = options;
1687
+ const locale = options.locale ?? "pt-BR";
1688
+ const text = textOf(locale);
1689
+ const label = (key2, fallback) => {
1690
+ const found = options.translate?.(key2, locale);
1691
+ return found !== void 0 && found !== key2 ? found : fallback;
1692
+ };
1693
+ const nameOf = (item) => label(item.name ?? `item.${item.id}`, label(`item.${item.id}`, humanize(item.id)));
1694
+ const categoryName = (id) => label(`category.${id}`, humanize(id));
1695
+ const provider = options.provider ?? createFindsProvider(catalog);
1696
+ const items = catalog.list();
1697
+ const categories = [...ITEM_CATEGORIES.map((c) => c.id), ...[...catalog.categories.keys()].filter((id) => !ITEM_CATEGORIES.some((c) => c.id === id))].filter((id) => items.some((item) => item.category === id));
1698
+ const shelves = shelvesLayout(items, categories);
1699
+ const showcase = listItemModels().map((id) => ({ id, size: sizeOfModel(id), def: catalog.has(id) ? catalog.get(id) : void 0 }));
1700
+ const state = {
1701
+ category: CATEGORY_ALL,
1702
+ wear: WEARS.includes(options.wear) ? options.wear : "new",
1703
+ ageDays: 0,
1704
+ storage: catalog.rules.storage.ambient ? "ambient" : Object.keys(catalog.rules.storage)[0] ?? "ambient",
1705
+ powered: true,
1706
+ place: catalog.places.has("house") ? "house" : [...catalog.places.keys()][0] ?? "house",
1707
+ container: catalog.containers.has("fridge") ? "fridge" : [...catalog.containers.keys()][0] ?? "fridge",
1708
+ state: "normal",
1709
+ day: 0,
1710
+ seed: options.seed ?? 1,
1711
+ recipe: [...catalog.recipes.keys()][0] ?? ""
1712
+ };
1713
+ const entries2 = [];
1714
+ let host = null;
1715
+ let latestTHREE = null;
1716
+ let stack = [];
1717
+ let findsGroup = null;
1718
+ let boxes = [];
1719
+ const layers = { labels: true, boxes: false, simple_shapes: false };
1720
+ const baseStage = () => WEAR_STAGE[state.wear] ?? 0;
1721
+ const stageFor = (def) => def ? stageOf(catalog, def, baseStage(), state.ageDays, state.storage, state.powered) : baseStage();
1722
+ const drawable = (def) => def;
1723
+ const register = (entry) => {
1724
+ entries2.push({ ...entry, triangles: countTriangles(entry.root), drawCalls: countDrawCalls(entry.root) });
1725
+ };
1726
+ const shelfItem = (plan) => ({
1727
+ id: plan.id,
1728
+ name: fill(text.shelf, { category: categoryName(plan.category) }),
1729
+ movable: false,
1730
+ build: (THREE) => {
1731
+ const T2 = THREE;
1732
+ const group = new T2.Group();
1733
+ const wood = new T2.MeshStandardMaterial({ color: 9071173, roughness: 0.85 });
1734
+ const frame = new T2.MeshStandardMaterial({ color: 4869458, roughness: 0.7, metalness: 0.3 });
1735
+ const top = new T2.Mesh(new T2.BoxGeometry(plan.length, 0.04, plan.depth), wood);
1736
+ top.position.y = SHELF_HEIGHT - 0.02;
1737
+ group.add(top);
1738
+ for (const sx of [-1, 1]) for (const sz of [-1, 1]) {
1739
+ const leg = new T2.Mesh(new T2.BoxGeometry(0.05, SHELF_HEIGHT - 0.04, 0.05), frame);
1740
+ leg.position.set(sx * (plan.length - 0.1) / 2, (SHELF_HEIGHT - 0.04) / 2, sz * (plan.depth - 0.1) / 2);
1741
+ group.add(leg);
1742
+ }
1743
+ group.userData.category = plan.category;
1744
+ group.userData.shelf = true;
1745
+ shelfRoots.push(group);
1746
+ return group;
1747
+ }
1748
+ });
1749
+ const shelfRoots = [];
1750
+ const itemOf = (def) => ({
1751
+ id: `${ITEM_PREFIX}${def.id}`,
1752
+ name: nameOf(def),
1753
+ movable: true,
1754
+ thumbnail: itemThumbnailSvg(drawable(def)),
1755
+ build: (THREE) => {
1756
+ const T2 = THREE;
1757
+ latestTHREE = T2;
1758
+ const model = buildItem(T2, drawable(def), { wear: stageFor(def) });
1759
+ register({ id: `${ITEM_PREFIX}${def.id}`, itemId: def.id, group: "shelf", category: def.category, def, kind: model.kind, size: def.size, root: model.root });
1760
+ return model.root;
1761
+ }
1762
+ });
1763
+ const showcaseItem = (model) => ({
1764
+ id: `${SHOWCASE_PREFIX}${model.id}`,
1765
+ name: model.def ? nameOf(model.def) : label(`item.${model.id}`, humanize(model.id)),
1766
+ movable: false,
1767
+ build: (THREE) => {
1768
+ const T2 = THREE;
1769
+ const subject = { id: model.id, category: model.def?.category ?? "", size: model.size, model: model.id };
1770
+ const built = buildItem(T2, subject, { wear: stageFor(model.def) });
1771
+ register({ id: `${SHOWCASE_PREFIX}${model.id}`, itemId: model.id, group: "showcase", category: model.def?.category ?? "", def: model.def, kind: built.kind, size: model.size, root: built.root });
1772
+ return built.root;
1773
+ }
1774
+ });
1775
+ const standItem = () => ({
1776
+ id: SHOWCASE_STAND_ID,
1777
+ name: text.stand,
1778
+ movable: false,
1779
+ build: (THREE) => {
1780
+ const T2 = THREE;
1781
+ const group = new T2.Group();
1782
+ const base = new T2.Mesh(new T2.CylinderGeometry(STAND.radius, STAND.radius + 0.1, STAND.height - 0.04, 40), new T2.MeshStandardMaterial({ color: 3816772, roughness: 0.8 }));
1783
+ base.position.y = (STAND.height - 0.04) / 2;
1784
+ const top = new T2.Mesh(new T2.CylinderGeometry(STAND.radius, STAND.radius, 0.04, 40), new T2.MeshStandardMaterial({ color: 13617848, roughness: 0.6 }));
1785
+ top.position.y = STAND.height - 0.02;
1786
+ group.add(base, top);
1787
+ return group;
1788
+ }
1789
+ });
1790
+ const rulerItem = () => ({
1791
+ id: RULER_ID,
1792
+ name: text.ruler,
1793
+ movable: false,
1794
+ build: (THREE) => {
1795
+ const T2 = THREE;
1796
+ const group = new T2.Group();
1797
+ const post = new T2.Mesh(new T2.BoxGeometry(0.06, RULER_HEIGHT, 0.02), new T2.MeshStandardMaterial({ color: 15262408, roughness: 0.7 }));
1798
+ post.position.y = RULER_HEIGHT / 2;
1799
+ group.add(post);
1800
+ const tick = new T2.MeshStandardMaterial({ color: 1776411 });
1801
+ for (let cm = 0; cm <= 155; cm += 5) {
1802
+ const long = cm % 10 === 0;
1803
+ const mark = new T2.Mesh(new T2.BoxGeometry(long ? 0.05 : 0.03, 4e-3, 0.024), tick);
1804
+ mark.position.set(0.03 + (long ? 0.025 : 0.015), cm / 100, 0);
1805
+ group.add(mark);
1806
+ }
1807
+ const base = new T2.Mesh(new T2.BoxGeometry(0.3, 0.03, 0.3), new T2.MeshStandardMaterial({ color: 5593180 }));
1808
+ base.position.y = 0.015;
1809
+ group.add(base);
1810
+ group.userData.height = RULER_HEIGHT;
1811
+ return group;
1812
+ }
1813
+ });
1814
+ const humanItem = () => ({
1815
+ id: HUMAN_ID,
1816
+ name: text.human,
1817
+ movable: false,
1818
+ build: (THREE) => {
1819
+ const T2 = THREE;
1820
+ const group = new T2.Group();
1821
+ const skin = new T2.MeshStandardMaterial({ color: 10134445, roughness: 0.9 });
1822
+ const part = (geometry, x, y) => {
1823
+ const mesh = new T2.Mesh(geometry, skin);
1824
+ mesh.position.set(x, y, 0);
1825
+ group.add(mesh);
1826
+ };
1827
+ for (const x of [-0.09, 0.09]) part(new T2.CylinderGeometry(0.075, 0.06, 0.88, 10), x, 0.44);
1828
+ part(new T2.BoxGeometry(0.4, 0.62, 0.22), 0, 0.88 + 0.31);
1829
+ for (const x of [-0.26, 0.26]) part(new T2.CylinderGeometry(0.045, 0.04, 0.6, 8), x, 1.2);
1830
+ part(new T2.CylinderGeometry(0.05, 0.055, 0.07, 8), 0, 1.535);
1831
+ part(new T2.SphereGeometry(0.115, 14, 10), 0, 1.685);
1832
+ group.userData.height = HUMAN_HEIGHT;
1833
+ return group;
1834
+ }
1835
+ });
1836
+ const tableItem = () => ({
1837
+ id: FINDS_TABLE_ID,
1838
+ name: text.table,
1839
+ movable: false,
1840
+ build: (THREE) => {
1841
+ const T2 = THREE;
1842
+ const group = new T2.Group();
1843
+ const top = new T2.Mesh(new T2.BoxGeometry(TABLE.length, 0.05, TABLE.depth), new T2.MeshStandardMaterial({ color: 8022613, roughness: 0.8 }));
1844
+ top.position.y = TABLE.height - 0.025;
1845
+ group.add(top);
1846
+ const frame = new T2.MeshStandardMaterial({ color: 4013892 });
1847
+ for (const sx of [-1, 1]) for (const sz of [-1, 1]) {
1848
+ const leg = new T2.Mesh(new T2.BoxGeometry(0.06, TABLE.height - 0.05, 0.06), frame);
1849
+ leg.position.set(sx * (TABLE.length - 0.12) / 2, (TABLE.height - 0.05) / 2, sz * (TABLE.depth - 0.12) / 2);
1850
+ group.add(leg);
1851
+ }
1852
+ return group;
1853
+ }
1854
+ });
1855
+ const liveEntries = () => entries2.filter((entry) => entry.root.parent !== null);
1856
+ const visibleEntries = () => liveEntries().filter((entry) => entry.root.visible);
1857
+ const applyCategory = () => {
1858
+ for (const entry of entries2) if (entry.group === "shelf") entry.root.visible = state.category === CATEGORY_ALL || entry.category === state.category;
1859
+ for (const shelf of shelfRoots) shelf.visible = state.category === CATEGORY_ALL || shelf.userData.category === state.category;
1860
+ };
1861
+ const applyWear = () => {
1862
+ for (const entry of entries2) setWear(entry.root, stageFor(entry.def));
1863
+ };
1864
+ const applyHighlight = () => {
1865
+ for (const entry of entries2) {
1866
+ entry.root.traverse((object) => {
1867
+ const mesh = object;
1868
+ if (!mesh.isMesh) return;
1869
+ for (const material of Array.isArray(mesh.material) ? mesh.material : [mesh.material]) {
1870
+ const standard = material;
1871
+ if (standard.emissive) standard.emissive.setHex(layers.simple_shapes && entry.kind === "shape" ? HIGHLIGHT : 0);
1872
+ }
1873
+ });
1874
+ }
1875
+ };
1876
+ const clearBoxes = () => {
1877
+ for (const box2 of boxes) {
1878
+ box2.parent?.remove(box2);
1879
+ }
1880
+ boxes = [];
1881
+ };
1882
+ const applyBoxes = () => {
1883
+ clearBoxes();
1884
+ if (!layers.boxes || !host || !latestTHREE) return;
1885
+ const T2 = latestTHREE;
1886
+ const scene = host.scene;
1887
+ for (const entry of liveEntries()) {
1888
+ if (!entry.root.visible) continue;
1889
+ const box2 = new T2.Box3().setFromObject(entry.root);
1890
+ if (box2.isEmpty()) continue;
1891
+ const helper = new T2.Box3Helper(box2, BOX_COLOR);
1892
+ scene.add(helper);
1893
+ boxes.push(helper);
1894
+ }
1895
+ };
1896
+ const clearFinds = () => {
1897
+ if (findsGroup) {
1898
+ findsGroup.parent?.remove(findsGroup);
1899
+ findsGroup = null;
1900
+ }
1901
+ };
1902
+ const labelSprite = (T2, content) => {
1903
+ if (typeof document === "undefined") return null;
1904
+ const canvas = document.createElement("canvas");
1905
+ const context = canvas.getContext("2d");
1906
+ if (!context) return null;
1907
+ canvas.width = 256;
1908
+ canvas.height = 64;
1909
+ context.fillStyle = "rgba(15,17,20,0.78)";
1910
+ context.fillRect(0, 0, 256, 64);
1911
+ context.fillStyle = "#ffffff";
1912
+ context.font = "24px sans-serif";
1913
+ context.textAlign = "center";
1914
+ context.textBaseline = "middle";
1915
+ context.fillText(content.length > 22 ? `${content.slice(0, 21)}\u2026` : content, 128, 32);
1916
+ const texture = new T2.CanvasTexture(canvas);
1917
+ const sprite = new T2.Sprite(new T2.SpriteMaterial({ map: texture, depthTest: false, transparent: true }));
1918
+ sprite.scale.set(0.5, 0.125, 1);
1919
+ sprite.renderOrder = 10;
1920
+ return sprite;
1921
+ };
1922
+ const showFinds = () => {
1923
+ clearFinds();
1924
+ if (!host || !latestTHREE) return;
1925
+ const T2 = latestTHREE;
1926
+ const group = new T2.Group();
1927
+ group.position.set(TABLE.at[0], TABLE.height + 5e-3, TABLE.at[2]);
1928
+ let cursor = -TABLE.length / 2 + 0.1;
1929
+ const scale = 1;
1930
+ for (const found of stack) {
1931
+ const def = catalog.get(found.itemId);
1932
+ const copies = Math.min(found.count, MAX_COPIES);
1933
+ const start = cursor;
1934
+ for (let i = 0; i < copies; i++) {
1935
+ const model = buildItem(T2, drawable(def), { wear: stageFor(def) });
1936
+ model.root.position.set(cursor + def.size[0] * scale / 2, 0, 0);
1937
+ group.add(model.root);
1938
+ cursor += def.size[0] + 0.03;
1939
+ }
1940
+ const sprite = labelSprite(T2, `${nameOf(def)} x${found.count}`);
1941
+ if (sprite) {
1942
+ sprite.position.set((start + cursor) / 2, Math.max(def.size[1], 0.1) + 0.12, 0);
1943
+ sprite.visible = layers.labels;
1944
+ sprite.userData.label = true;
1945
+ group.add(sprite);
1946
+ }
1947
+ cursor += 0.1;
1948
+ }
1949
+ ;
1950
+ host.scene.add(group);
1951
+ findsGroup = group;
1952
+ };
1953
+ const applyLabels = () => {
1954
+ findsGroup?.traverse((object) => {
1955
+ if (object.userData.label) object.visible = layers.labels;
1956
+ });
1957
+ };
1958
+ const rollFinds = () => {
1959
+ const query = { place: state.place, container: state.container, state: state.state, rng: rng(state.seed), day: state.day };
1960
+ stack = [...provider.findsFor(query) ?? []];
1961
+ showFinds();
1962
+ const container = label(`container.${state.container}`, humanize(state.container));
1963
+ const place = label(`place.${state.place}`, humanize(state.place));
1964
+ host?.log(stack.length > 0 ? fill(text.rolled, { container, place, state: text.states[state.state] ?? state.state, day: state.day, stacks: stack.length }) : fill(text.rolledNothing, { container, place }), { place: state.place, container: state.container, seed: state.seed });
1965
+ return stack;
1966
+ };
1967
+ const kitFor = (recipe) => {
1968
+ const inventory = /* @__PURE__ */ new Map();
1969
+ const world = /* @__PURE__ */ new Set();
1970
+ const add = (id, count) => void inventory.set(id, (inventory.get(id) ?? 0) + count);
1971
+ for (const input of recipe.inputs) {
1972
+ if (input.world) for (const w of input.world) world.add(w);
1973
+ else if (input.item !== void 0) add(input.item, input.qty ?? 1);
1974
+ else if (input.any?.[0] !== void 0) add(input.any[0], input.qty ?? 1);
1975
+ else if (input.match) {
1976
+ const pick = catalog.list().find((item) => (input.match?.category === void 0 || item.category === input.match.category) && (input.match?.cookable === void 0 || item.cookable === input.match.cookable));
1977
+ if (pick) add(pick.id, input.qty ?? 1);
1978
+ }
1979
+ }
1980
+ for (const tool2 of recipe.tools) {
1981
+ if (tool2.anyOf[0] !== void 0) add(tool2.anyOf[0], 1);
1982
+ for (const w of tool2.world ?? []) world.add(w);
1983
+ }
1984
+ return { inventory, world };
1985
+ };
1986
+ const craftSelected = () => {
1987
+ const recipe = catalog.recipes.get(state.recipe);
1988
+ if (!recipe) return { ok: false, missing: [state.recipe] };
1989
+ const { inventory, world } = kitFor(recipe);
1990
+ const fluids = {};
1991
+ for (const input of recipe.inputs) if (input.fluid !== void 0) fluids[input.fluid] = (fluids[input.fluid] ?? 0) + (input.liters ?? 1);
1992
+ const context = { catalog, inventory, world, fluids };
1993
+ const name = label(`recipe.${recipe.id}`, humanize(recipe.id));
1994
+ const check = canCraft(recipe, context);
1995
+ if (!check.isPossible) {
1996
+ host?.log(fill(text.craftMissing, { recipe: name, missing: check.missing.join(", ") }), { recipe: recipe.id });
1997
+ return { ok: false, missing: check.missing };
1998
+ }
1999
+ const result = craft(recipe, context, rng(state.seed));
2000
+ if (!result.isCrafted) return { ok: false, missing: result.missing };
2001
+ if ("item" in result.output) {
2002
+ stack = [{ itemId: result.output.item, count: result.output.count }];
2003
+ showFinds();
2004
+ }
2005
+ const output = "item" in result.output ? `${nameOf(catalog.get(result.output.item))} x${result.output.count}` : fill(text.craftEffect, { effect: result.output.effect });
2006
+ host?.log(fill(text.crafted, { recipe: name, output, seconds: result.seconds }), { recipe: recipe.id });
2007
+ return { ok: true, missing: [] };
2008
+ };
2009
+ const areaPlacements = () => {
2010
+ const placements = [];
2011
+ for (const shelf of shelves) {
2012
+ placements.push({ item: shelf.id, position: shelf.position, surface: { halfSize: [shelf.length / 2, shelf.depth / 2], height: SHELF_HEIGHT } }, ...shelf.items);
2013
+ }
2014
+ placements.push({ item: SHOWCASE_STAND_ID, position: STAND.at, surface: { halfSize: [STAND.radius, STAND.radius], height: STAND.height } });
2015
+ showcase.forEach((model, index) => {
2016
+ const angle = index / showcase.length * Math.PI * 2;
2017
+ placements.push({ item: `${SHOWCASE_PREFIX}${model.id}`, position: [STAND.at[0] + Math.cos(angle) * STAND.ring, STAND.height + 0.01, STAND.at[2] + Math.sin(angle) * STAND.ring], asleep: true });
2018
+ });
2019
+ placements.push({ item: RULER_ID, position: RULER_AT }, { item: HUMAN_ID, position: HUMAN_AT });
2020
+ placements.push({ item: FINDS_TABLE_ID, position: TABLE.at, surface: { halfSize: [TABLE.length / 2, TABLE.depth / 2], height: TABLE.height } });
2021
+ return placements;
2022
+ };
2023
+ const onChange = (change) => {
2024
+ if (change.kind === "layer") {
2025
+ if (change.id === "labels" || change.id === "boxes" || change.id === "simple_shapes") layers[change.id] = change.on;
2026
+ if (change.id === "labels") applyLabels();
2027
+ else if (change.id === "boxes") applyBoxes();
2028
+ else if (change.id === "simple_shapes") applyHighlight();
2029
+ return;
2030
+ }
2031
+ if (change.kind === "action") {
2032
+ if (change.id === "roll_finds") rollFinds();
2033
+ else if (change.id === "craft") craftSelected();
2034
+ return;
2035
+ }
2036
+ const value = change.value;
2037
+ switch (change.id) {
2038
+ case "category":
2039
+ state.category = String(value);
2040
+ applyCategory();
2041
+ if (layers.boxes) applyBoxes();
2042
+ break;
2043
+ case "wear":
2044
+ state.wear = String(value);
2045
+ applyWear();
2046
+ break;
2047
+ case "age_days":
2048
+ state.ageDays = Number(value);
2049
+ applyWear();
2050
+ break;
2051
+ case "storage":
2052
+ state.storage = String(value);
2053
+ applyWear();
2054
+ break;
2055
+ case "powered":
2056
+ state.powered = Boolean(value);
2057
+ applyWear();
2058
+ break;
2059
+ case "place":
2060
+ state.place = String(value);
2061
+ break;
2062
+ case "container":
2063
+ state.container = String(value);
2064
+ break;
2065
+ case "state":
2066
+ state.state = String(value);
2067
+ break;
2068
+ case "day":
2069
+ state.day = Number(value);
2070
+ break;
2071
+ case "seed":
2072
+ state.seed = Number(value);
2073
+ break;
2074
+ case "recipe":
2075
+ state.recipe = String(value);
2076
+ break;
2077
+ }
2078
+ };
2079
+ const choice = (id, values, def) => ({ kind: "choice", id, name: text.control[id], options: values, default: def });
2080
+ const module = {
2081
+ id: "items_area",
2082
+ name: text.name,
2083
+ group: text.group,
2084
+ state,
2085
+ placed: () => entries2.map((entry) => ({ id: entry.id, itemId: entry.itemId, group: entry.group, kind: entry.kind, size: entry.size, root: entry.root })),
2086
+ finds: () => stack,
2087
+ shelves: () => shelves,
2088
+ rollFinds,
2089
+ craftSelected,
2090
+ selectRecipe: (id) => {
2091
+ state.recipe = id;
2092
+ },
2093
+ measure: (root) => {
2094
+ if (!latestTHREE) return null;
2095
+ const size = new latestTHREE.Box3().setFromObject(root).getSize(new latestTHREE.Vector3());
2096
+ return [size.x, size.y, size.z];
2097
+ },
2098
+ items: () => [
2099
+ ...items.map(itemOf),
2100
+ ...shelves.map(shelfItem),
2101
+ standItem(),
2102
+ ...showcase.map(showcaseItem),
2103
+ rulerItem(),
2104
+ humanItem(),
2105
+ tableItem()
2106
+ ],
2107
+ area: () => ({ placements: areaPlacements(), view: { eye: [0, 5.5, 16], target: [0, 0.8, 0] } }),
2108
+ controls: () => [
2109
+ choice("category", [{ id: CATEGORY_ALL, name: text.all }, ...categories.map((id) => ({ id, name: categoryName(id) }))], CATEGORY_ALL),
2110
+ choice("wear", WEARS.map((id) => ({ id, name: text.wears[id] })), state.wear),
2111
+ { kind: "number", id: "age_days", name: text.control.age_days, hint: text.hint.age_days, unit: "d", min: 0, max: 400, step: 5, default: 0 },
2112
+ choice("storage", Object.keys(catalog.rules.storage).map((id) => ({ id, name: label(`storage.${id}`, humanize(id)) })), state.storage),
2113
+ { kind: "boolean", id: "powered", name: text.control.powered, hint: text.hint.powered, default: true },
2114
+ choice("place", [...catalog.places.keys()].map((id) => ({ id, name: label(`place.${id}`, humanize(id)) })), state.place),
2115
+ choice("container", [...catalog.containers.keys()].map((id) => ({ id, name: label(`container.${id}`, humanize(id)) })), state.container),
2116
+ choice("state", ["normal", "fortified", "abandoned"].map((id) => ({ id, name: text.states[id] })), "normal"),
2117
+ { kind: "number", id: "day", name: text.control.day, hint: text.hint.day, unit: "d", min: 0, max: 60, step: 1, default: 0 },
2118
+ { kind: "number", id: "seed", name: text.control.seed, min: 1, max: 9999, step: 1, default: state.seed },
2119
+ { kind: "action", id: "roll_finds", name: text.control.roll_finds, hint: text.hint.roll_finds },
2120
+ ...state.recipe ? [choice("recipe", [...catalog.recipes.keys()].map((id) => ({ id, name: label(`recipe.${id}`, humanize(id)) })), state.recipe)] : [],
2121
+ ...state.recipe ? [{ kind: "action", id: "craft", name: text.control.craft, hint: text.hint.craft }] : []
2122
+ ],
2123
+ layers: () => [
2124
+ { id: "labels", name: text.layers.labels },
2125
+ { id: "boxes", name: text.layers.boxes, default: false },
2126
+ { id: "simple_shapes", name: text.layers.simple_shapes, default: false }
2127
+ ],
2128
+ metrics: () => [
2129
+ { id: "items", name: text.metrics.items, digits: 0, read: () => visibleEntries().length },
2130
+ { id: "draw_calls", name: text.metrics.draw_calls, digits: 0, read: () => visibleEntries().reduce((sum, entry) => sum + entry.drawCalls, 0) },
2131
+ { id: "triangles", name: text.metrics.triangles, digits: 0, read: () => visibleEntries().reduce((sum, entry) => sum + entry.triangles, 0) },
2132
+ {
2133
+ id: "modeled",
2134
+ name: text.metrics.modeled,
2135
+ read: () => {
2136
+ const shown = visibleEntries().filter((entry) => entry.group === "shelf");
2137
+ return `${shown.filter((entry) => entry.kind === "model").length}/${shown.length}`;
2138
+ }
2139
+ },
2140
+ { id: "finds", name: text.metrics.finds, digits: 0, read: () => stack.length }
2141
+ ],
2142
+ scenarios: () => scenariosOf(module, catalog, text, provider),
2143
+ attach(lent) {
2144
+ host = lent;
2145
+ latestTHREE = lent.THREE;
2146
+ const offChange = lent.bench?.onChange(onChange);
2147
+ const offFrame = lent.onFrame((dt) => {
2148
+ for (const entry of entries2) if (entry.group === "showcase" && entry.root.parent) entry.root.rotation.y += dt * SPIN;
2149
+ });
2150
+ return () => {
2151
+ offChange?.();
2152
+ offFrame();
2153
+ clearBoxes();
2154
+ clearFinds();
2155
+ host = null;
2156
+ };
2157
+ }
2158
+ };
2159
+ return module;
2160
+ }
2161
+ var OVER = 0.02;
2162
+ var UNDER = 0.08;
2163
+ function scenariosOf(module, catalog, text, provider) {
2164
+ const names = text.scenarios;
2165
+ const shelf = () => module.placed().filter((entry) => entry.group === "shelf");
2166
+ return [
2167
+ {
2168
+ id: "real_size",
2169
+ name: names.real_size,
2170
+ run: ({ expect }) => {
2171
+ const shown = shelf();
2172
+ expect(shown.length > 0, "items are on the shelves", { count: shown.length });
2173
+ for (const entry of shown) {
2174
+ const measured = module.measure(entry.root);
2175
+ if (!measured) continue;
2176
+ const longest = Math.max(...entry.size);
2177
+ const axis = entry.size.indexOf(longest);
2178
+ const ok = measured.every((side, a) => side <= entry.size[a] * (1 + OVER) + 2e-3) && Math.abs(measured[axis] - longest) <= longest * UNDER + 2e-3;
2179
+ expect(ok, `${entry.itemId} measures its real size`, { measured, size: entry.size });
2180
+ }
2181
+ }
2182
+ },
2183
+ {
2184
+ id: "models",
2185
+ name: names.models,
2186
+ run: ({ expect }) => {
2187
+ const models = listItemModels();
2188
+ expect(models.length === 24, "24 models are shipped", { count: models.length });
2189
+ const withModel = catalog.list().filter((item) => item.model !== void 0 && hasItemModel(item.model));
2190
+ expect(withModel.length === 23, "23 items of the catalog name a model", { count: withModel.length });
2191
+ const stand = module.placed().filter((entry) => entry.group === "showcase");
2192
+ expect(stand.length === 0 || stand.every((entry) => entry.kind === "model"), "every piece of the stand is drawn by the model of the team");
2193
+ }
2194
+ },
2195
+ {
2196
+ id: "budgets",
2197
+ name: names.budgets,
2198
+ run: ({ expect }) => {
2199
+ for (const entry of module.placed()) {
2200
+ const report = checkBudget(entry.root, entry.size);
2201
+ expect(report.drawCalls <= ITEM_BUDGETS.drawCalls, `${entry.itemId} stays within ${ITEM_BUDGETS.drawCalls} draw calls`, { drawCalls: report.drawCalls });
2202
+ const deviation = entry.kind === "model" ? MODEL_BUDGET_DEVIATIONS[entry.itemId] : void 0;
2203
+ expect(report.ok || deviation !== void 0 && report.triangles <= deviation, `${entry.itemId} stays within its triangle budget`, { triangles: report.triangles, limit: report.limit });
2204
+ }
2205
+ }
2206
+ },
2207
+ {
2208
+ id: "ruler",
2209
+ name: names.ruler,
2210
+ run: ({ expect }) => {
2211
+ expect(RULER_HEIGHT === 1.55 && HUMAN_HEIGHT === 1.8, "the references have the measures of the page");
2212
+ }
2213
+ },
2214
+ {
2215
+ id: "finds",
2216
+ name: names.finds,
2217
+ run: ({ expect }) => {
2218
+ const query = (seed) => ({ place: module.state.place, container: module.state.container, state: "normal", rng: rng(seed), day: 0 });
2219
+ const a = JSON.stringify(provider.findsFor(query(7)));
2220
+ const b = JSON.stringify(provider.findsFor(query(7)));
2221
+ expect(a === b, "the same seed gives the same finds");
2222
+ expect((provider.findsFor(query(7)) ?? []).every((found) => catalog.has(found.itemId)), "every find is an item of the catalog");
2223
+ }
2224
+ },
2225
+ {
2226
+ id: "freshness",
2227
+ name: names.freshness,
2228
+ run: ({ expect }) => {
2229
+ const food = catalog.list().find((item) => item.shelfLife !== void 0 && item.shelfLife > 0);
2230
+ if (!food) return expect(false, "the catalog has a food that spoils");
2231
+ const at = (storage, days2) => freshness(food, { ageDays: 0, since: 0, storage, storedAt: 0 }, days2, { rules: catalog.rules });
2232
+ const days = food.shelfLife * 1.2;
2233
+ expect(at("ambient", days) !== "fresh", "at room temperature it is past its best", { food: food.id });
2234
+ if ("fridge" in catalog.rules.storage) expect(at("fridge", days) !== at("ambient", days) || at("fridge", days) === "fresh", "in the fridge it keeps better", { food: food.id });
2235
+ }
2236
+ },
2237
+ {
2238
+ id: "craft",
2239
+ name: names.craft,
2240
+ run: ({ expect }) => {
2241
+ const before = module.state.recipe;
2242
+ for (const id of catalog.recipes.keys()) {
2243
+ module.selectRecipe(id);
2244
+ const result = module.craftSelected();
2245
+ expect(result.ok, `recipe ${id} is made with what it asks for`, { missing: result.missing });
2246
+ }
2247
+ module.selectRecipe(before);
2248
+ }
2249
+ }
2250
+ ];
2251
+ }
2252
+ return __toCommonJS(index_exports);
2253
+ })();
2254
+ //# sourceMappingURL=index.global.js.map