@adatechnology/game-animals-three 0.1.3 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../../game-animals/src/data/gaits.json","../../game-animals/src/gaits.ts","../../game-animals/src/layout.ts","../../game-animals/src/registry.ts","../../game-animals/src/kit.ts","../../game-props3d/src/colors.ts","../../game-props3d/src/atlas.ts","../../game-props3d/src/catalog.ts","../../game-props3d/src/builders/ceiling.ts","../../game-props3d/src/builders/floors.ts","../../game-props3d/src/builders/shared.ts","../../game-props3d/src/builders/furniture.ts","../../game-props3d/src/builders/house-state.ts","../../game-props3d/src/builders/plants.ts","../../game-props3d/src/builders/wall.ts","../../game-props3d/src/builders/yard.ts","../../game-props3d/src/cover.ts","../../game-props3d/src/kit.ts","../../game-props3d/src/locale.ts","../../game-props3d/src/create.ts","../../game-props3d/src/node-skin.ts","../../game-props3d/src/recorder.ts","../../game-props3d/src/merge-by-material.ts","../src/animate-bird.ts","../src/animate-quadruped.ts","../src/animate-snake.ts","../src/context.ts","../src/bird.ts","../src/extras.ts","../src/textures.ts","../src/profile.ts","../src/quadruped.ts","../src/snake.ts","../src/visual-state.ts","../src/views.ts","../src/line-up.ts","../src/test-area/scenarios.ts","../src/test-area/text.ts","../src/test-area/module.ts"],"sourcesContent":["/**\n * @adatechnology/game-animals-three: the three.js drawing of the animals, a literal port of the Bichos do Vale builders (`quad`, `bird`, `snake`) and\n * their animation, with the alive / infected / dead looks for every species, the joints and split views, and a GPU node skin for crowds.\n */\nimport { GAIT_PHASES } from '@adatechnology/game-animals'\nimport type { AnimalSpecies, AnimalVisualState, GaitPhases } from '@adatechnology/game-animals'\nimport { collectReferencedNodes, createNodeSkin } from '@adatechnology/game-props3d'\nimport type { NodeSkin } from '@adatechnology/game-props3d'\nimport type * as THREE_NS from 'three'\n\nimport { animateBird } from './animate-bird'\nimport { animateQuadruped } from './animate-quadruped'\nimport { animateSnake } from './animate-snake'\nimport type { AnimateParams } from './animate-types'\nimport { buildBird } from './bird'\nimport { createRig, createStream } from './context'\nimport { BIRD_EXTRAS, QUADRUPED_EXTRAS } from './extras'\nimport { buildQuadruped } from './quadruped'\nimport { buildSnake } from './snake'\nimport type { AnimalRig, AnimalsThreeOptions, DocumentLike, Three } from './types'\nimport { applyVisualState } from './visual-state'\nimport { setJointView, setSplit, updateSplit } from './views'\n\nexport type { AnimalLeg, AnimalPivot, AnimalRig, AnimalWing, AnimalsThreeOptions, CanvasLike, DocumentLike, Three } from './types'\nexport type { AnimateParams } from './animate-types'\nexport type { BirdExtra, BirdParts, ExtraContext, QuadrupedExtra, QuadrupedParts } from './extras'\nexport { BIRD_EXTRAS, QUADRUPED_EXTRAS } from './extras'\nexport { lineUpAnimals } from './line-up'\nexport type { LineUpOptions } from './line-up'\nexport { profileGeometry } from './profile'\nexport { createCoatTexture } from './textures'\nexport { applyVisualState } from './visual-state'\nexport { setJointView, setSplit, updateSplit } from './views'\n\nexport type AnimalsThree = {\n /** Builds one animal from its species; consumes one number of the stream for the start phase unless `phase` is given. */\n build(species: AnimalSpecies, buildOptions?: { readonly phase?: number }): AnimalRig\n /** Advances the animation of one animal (steps, flight, wings, head, tail, a dead one lying down). */\n animate(rig: AnimalRig, params: AnimateParams): void\n setVisualState(rig: AnimalRig, state: AnimalVisualState): void\n setJointView(rig: AnimalRig, on: boolean): void\n setSplit(rig: AnimalRig, on: boolean): void\n updateSplit(rig: AnimalRig, delta: number): void\n /** One mesh that stands for the whole tree, for crowds: call `skin.update()` each frame after `animate`. */\n createSkin(rig: AnimalRig): NodeSkin\n readonly jointMaterial: THREE_NS.MeshStandardMaterial\n}\n\nconst countTriangles = (root: THREE_NS.Object3D): number => {\n let triangles = 0\n root.traverse((object) => {\n const mesh = object as THREE_NS.Mesh\n if (!mesh.isMesh) return\n const geometry = mesh.geometry\n triangles += geometry.index ? geometry.index.count / 3 : geometry.attributes.position.count / 3\n })\n return Math.round(triangles)\n}\n\nexport const createAnimalsThree = (THREE: Three, options: AnimalsThreeOptions = {}): AnimalsThree => {\n const random = options.random ?? createStream(options.seed ?? 7)\n const documentOf = (): DocumentLike => {\n const found = options.document ?? (globalThis as { document?: DocumentLike }).document\n if (!found) throw new Error('game-animals-three: a species with a coat texture needs `options.document` (no global document found)')\n return found\n }\n const jointMaterial = new THREE.MeshStandardMaterial({ color: 0xe0a548, emissive: 0xe0a548, emissiveIntensity: 0.8, roughness: 0.4 })\n const gaits: Readonly<Record<'walk' | 'trot' | 'gallop', GaitPhases>> = GAIT_PHASES\n\n return {\n jointMaterial,\n build(species, buildOptions = {}) {\n const rig = createRig(THREE, species, { phase: buildOptions.phase ?? random() * 6 })\n if (species.body.kind === 'snake') buildSnake(THREE, rig)\n else if (species.body.kind === 'bird') buildBird(THREE, rig, { extra: BIRD_EXTRAS[species.id] })\n else buildQuadruped(THREE, rig, { document: species.body.texture ? documentOf() : (undefined as never), extra: QUADRUPED_EXTRAS[species.id] })\n rig.triangles = countTriangles(rig.root)\n rig.root.traverse((object) => {\n if ((object as THREE_NS.Mesh).isMesh) object.userData.speciesId = species.id\n })\n return rig\n },\n animate(rig, params) {\n const kind = rig.species.body.kind\n if (kind === 'snake') animateSnake(THREE, rig, params)\n else if (kind === 'bird') animateBird(rig, params)\n else animateQuadruped(rig, params, gaits)\n },\n setVisualState: (rig, state) => applyVisualState(THREE, rig, state),\n setJointView: (rig, on) => setJointView(rig, on, jointMaterial),\n setSplit,\n updateSplit,\n createSkin: (rig) => createNodeSkin(THREE, rig.root, { nodes: collectReferencedNodes(rig, ['species']) }),\n }\n}\nexport { createAnimalsTestModule, ANIMALS_AREA_ID, ANIMAL_ITEM_PREFIX } from './test-area/module'\nexport type { AnimalsContext } from './test-area/scenarios'\nexport type { AnimalsTestOptions, OutbreakLike } from './test-area/types'\n","{\n \"walk\": [\n 1.570796327,\n 4.71238898,\n 0,\n 3.141592654\n ],\n \"trot\": [\n 0,\n 3.141592654,\n 3.141592654,\n 0\n ],\n \"gallop\": [\n 2.890265241,\n 3.707079331,\n 0,\n 0.879645943\n ]\n}\n","import gaitsData from './data/gaits.json'\nimport type { AnimalGaitName, GaitPhases } from './types'\n\n/** The phase of each leg per gait, in radians, from the Bichos do Vale page (`GAIT`); the leg order is front right, front left, hind right, hind left. */\nexport const GAIT_PHASES = gaitsData as unknown as Readonly<Record<AnimalGaitName, GaitPhases>>\n","import type { BirdSpec, QuadrupedSpec } from './types'\n\nexport type QuadrupedLayout = {\n /** Height of the front and the hind hips above the ground. */\n readonly frontHipHeight: number\n readonly hindHipHeight: number\n readonly footHeight: number\n /** Height of the body centre. */\n readonly bodyHeight: number\n /** Distance between the shoulders and the hips. */\n readonly span: number\n /** Largest dimension, used to scale the offsets of the split view and the blood pool. */\n readonly size: number\n}\n\n/** The rest-pose geometry of a quadruped: leg lengths and angles decide how high the body stands. */\nexport const quadrupedLayout = (spec: QuadrupedSpec): QuadrupedLayout => {\n const leg = spec.leg\n const frontAngle1 = leg.fa1 ?? 0.06\n const frontAngle2 = leg.fa2 ?? -0.08\n const hindAngle1 = leg.ha1 ?? -0.32\n const hindAngle2 = leg.ha2 ?? 0.58\n const footHeight = leg.hoof ? leg.r * 1.1 : leg.r * 0.7\n const frontHipHeight = leg.fu * Math.cos(frontAngle1) + leg.fl * Math.cos(frontAngle1 + frontAngle2) + footHeight\n const hindHipHeight = leg.hu * Math.cos(hindAngle1) + leg.hl * Math.cos(hindAngle1 + hindAngle2) + footHeight\n const bodyHeight = (frontHipHeight + hindHipHeight) / 2 + (spec.up ?? 0) * spec.R\n return { frontHipHeight, hindHipHeight, footHeight, bodyHeight, span: spec.L * spec.span, size: Math.max(spec.L, bodyHeight + spec.R) }\n}\n\n/** The size of a bird: the largest of its body and its standing height. */\nexport const birdSize = (spec: BirdSpec): number => Math.max(spec.r * 3, spec.tars + spec.shank * 0.9 + spec.r * 2)\n\n/** The length a species takes in a line-up: used to space the animals apart. */\nexport const lineUpLength = (body: QuadrupedSpec | BirdSpec | { readonly kind: 'snake' }): number => {\n if (body.kind === 'quadruped') return body.L * 1.3\n if (body.kind === 'snake') return 0.9\n return body.r * 4\n}\n","type Entry = { readonly id: string }\n\n/** The shipped entries with the ones from `extra` added or, when the id exists, replaced. */\nexport const mergeById = <TEntry extends Entry>(shipped: readonly TEntry[], extra: readonly TEntry[] | undefined): Map<string, TEntry> => {\n const entries = new Map<string, TEntry>()\n for (const entry of [...shipped, ...(extra ?? [])]) entries.set(entry.id, entry)\n return entries\n}\n\n/** Applies `patch` by id (a shallow merge); a patch for an id that does not exist is ignored. */\nexport const applyPatches = <TEntry extends Entry>(entries: Map<string, TEntry>, patches: Readonly<Record<string, Partial<TEntry>>> | undefined): void => {\n for (const [id, patch] of Object.entries(patches ?? {})) {\n const entry = entries.get(id)\n if (entry) entries.set(id, { ...entry, ...patch, id })\n }\n}\n\nexport const removeIds = (entries: Map<string, unknown>, ids: readonly string[] | undefined): void => {\n for (const id of ids ?? []) entries.delete(id)\n}\n","import { GAIT_PHASES } from './gaits'\nimport { applyPatches, mergeById, removeIds } from './registry'\nimport type { AnimalGaitName, AnimalOutcome, AnimalSpecies, AnimalVisualState, AnimalsOptions, GaitPhases } from './types'\n\nexport type AnimalKit = {\n readonly species: readonly AnimalSpecies[]\n get(id: string): AnimalSpecies | undefined\n gait(name: AnimalGaitName): GaitPhases\n}\n\n/**\n * What a bite does to the look of an animal: a species that turns shows the infected look, one that dies shows the dead look, and an immune one shows no\n * change. `forceDead` shows the dead look for any species (a carcass, an animal killed by something else).\n */\nexport const visualStateOf = (outcome: AnimalOutcome, isBitten: boolean, forceDead = false): AnimalVisualState => {\n if (forceDead) return 'dead'\n if (!isBitten) return 'alive'\n if (outcome === 'turns') return 'infected'\n return outcome === 'dies' ? 'dead' : 'alive'\n}\n\nexport const createAnimals = (options: AnimalsOptions = {}): AnimalKit => {\n const entries = mergeById([], options.packs?.flatMap((pack) => pack.species ?? []))\n applyPatches(entries, options.patch)\n removeIds(entries, options.remove)\n const species = [...entries.values()].sort((first, second) => first.order - second.order)\n const gaits = { ...GAIT_PHASES, ...options.gaits }\n return { species, get: (id) => entries.get(id), gait: (name) => gaits[name] }\n}\n\n","import type { Color, PropItem } from './types'\n\n/** Deterministic PRNG (mulberry32 variant) returning floats in [0, 1). */\nexport function rng(seed: number): () => number {\n let s = seed >>> 0\n return () => {\n s = (s + 0x6d2b79f5) >>> 0\n let t = s\n t = Math.imul(t ^ (t >>> 15), t | 1)\n t ^= t + Math.imul(t ^ (t >>> 7), t | 61)\n return ((t ^ (t >>> 14)) >>> 0) / 4294967296\n }\n}\n\n/** Seed of the details draw: `M.seed` (number or string) or a hash of the position. */\nexport const seedOf = (M: PropItem): number =>\n (M.seed != null\n ? typeof M.seed === 'number'\n ? M.seed\n : [...String(M.seed)].reduce((a, c) => (a * 31 + c.charCodeAt(0)) | 0, 7)\n : Math.round((M.x || 0) * 37 + (M.z || 0) * 91 + 1000)) >>> 0\n\n/** `'#rrggbb'` or number to number. */\nexport const hex = (v: Color): number => (typeof v === 'string' ? parseInt(v.replace('#', ''), 16) : v)\n\n/** Scales the brightness of a colour by factor `f` (clamped per channel). */\nexport const shade = (c: Color, f: number): number => {\n const n = hex(c)\n const r = Math.min(255, Math.round(((n >> 16) & 255) * f))\n const g = Math.min(255, Math.round(((n >> 8) & 255) * f))\n const b = Math.min(255, Math.round((n & 255) * f))\n return (r << 16) | (g << 8) | b\n}\n\n/** Linear mix of two colours. */\nexport const mix = (a: Color, b: Color, t: number): number => {\n const x = hex(a)\n const y = hex(b)\n const m = (s: number) => Math.round(((x >> s) & 255) * (1 - t) + ((y >> s) & 255) * t)\n return (m(16) << 16) | (m(8) << 8) | m(0)\n}\n","import { rng } from './colors'\nimport type { AtlasMap, AtlasName, DocumentLike, FloorAtlas, FloorKind, PictureAtlas, Uv, WaterAtlas } from './types'\n\ntype Ctx = CanvasRenderingContext2D\ntype Rand = () => number\n\nconst makeCanvas = (doc: DocumentLike, w: number, h: number): HTMLCanvasElement => {\n const c = doc.createElement('canvas')\n c.width = w\n c.height = h\n return c\n}\n\n// draws f() nine times (offsets of +-n) inside the clipped cell, so the pattern tiles without a seam\nfunction seamless(g: Ctx, x0: number, y0: number, n: number, f: () => void) {\n g.save()\n g.beginPath()\n g.rect(x0, y0, n, n)\n g.clip()\n for (let dy = -n; dy <= n; dy += n)\n for (let dx = -n; dx <= n; dx += n) {\n g.save()\n g.translate(x0 + dx, y0 + dy)\n f()\n g.restore()\n }\n g.restore()\n}\nconst noise = (g: Ctx, x0: number, y0: number, n: number, r: Rand, a: number) => {\n for (let i = 0; i < (n * n) / 6; i++) {\n const v = r() < 0.5 ? 0 : 255\n g.fillStyle = `rgba(${v},${v},${v},${r() * a})`\n g.fillRect(x0 + r() * n, y0 + r() * n, 1 + r() * 2, 1 + r() * 2)\n }\n}\n\n/** Floor kinds that live in the floor atlas: atlas cells and the world size (meters) one cell covers. */\nexport const FLOORS: Record<FloorKind, { cells: number[]; size: number }> = {\n lawn: { cells: [0, 1], size: 2.0 },\n tile_floor: { cells: [2], size: 1.0 },\n concrete: { cells: [3], size: 2.0 },\n deck_floor: { cells: [4], size: 1.2 },\n dirt: { cells: [5], size: 2.0 },\n pool_tile: { cells: [6], size: 1.0 },\n paving: { cells: [7], size: 1.0 },\n}\nconst PICTURE_CELLS = 8 // 0-3 abstract, 4-6 landscapes, 7 mirror\n\nfunction floorAtlas(doc: DocumentLike): FloorAtlas {\n const N = 256, cv = makeCanvas(doc, N * 4, N * 2), g = cv.getContext('2d')!, P = (c: number): [number, number] => [(c % 4) * N, Math.floor(c / 4) * N];\n const cell = (c: number, seed: number, f: (g: Ctx, x0: number, y0: number, r: Rand) => void) => { const [x0, y0] = P(c), r = rng(seed); f(g, x0, y0, r); };\n for (const [c, seed] of [[0, 11], [1, 12]] as Array<[number, number]>) cell(c, seed, (g, x0, y0, r) => { // lawn: base + blades + dry patches\n g.fillStyle = c ? '#648f3a' : '#5b8a37'; g.fillRect(x0, y0, N, N);\n seamless(g, x0, y0, N, () => { for (let i = 0; i < 40; i++) { g.fillStyle = `rgba(${r() < 0.5 ? '160,170,60' : '40,80,30'},${0.12 + r() * 0.12})`; g.beginPath(); g.arc(r() * N, r() * N, 8 + r() * 22, 0, 6.3); g.fill(); }\n for (let i = 0; i < 520; i++) { r(); /* the original drew a blade height here and never used it; the call keeps the random sequence identical */ g.strokeStyle = `hsl(${85 + r() * 30},${40 + r() * 25}%,${22 + r() * 24}%)`; g.lineWidth = 1; const x = r() * N, y = r() * N; g.beginPath(); g.moveTo(x, y); g.lineTo(x + (r() - 0.5) * 6, y - 4 - r() * 9); g.stroke(); } }); });\n cell(2, 21, (g, x0, y0, r) => { // beige 4x4 ceramic tiles with grout\n const q = N / 4; g.fillStyle = '#8c877b'; g.fillRect(x0, y0, N, N);\n for (let j = 0; j < 4; j++) for (let i = 0; i < 4; i++) { const v = 204 + (r() - 0.5) * 16; g.fillStyle = `rgb(${v},${v - 6},${v - 18})`; g.fillRect(x0 + i * q + 2, y0 + j * q + 2, q - 4, q - 4); g.fillStyle = 'rgba(255,255,255,.18)'; g.fillRect(x0 + i * q + 3, y0 + j * q + 3, q - 8, 3); }\n for (let k = 0; k < 14; k++) { const gr = g.createRadialGradient(0, 0, 0, 0, 0, 40); const x = x0 + r() * N, y = y0 + r() * N; g.save(); g.beginPath(); g.rect(x0, y0, N, N); g.clip(); g.translate(x, y); gr.addColorStop(0, 'rgba(90,80,60,.10)'); gr.addColorStop(1, 'rgba(90,80,60,0)'); g.fillStyle = gr; g.fillRect(-40, -40, 80, 80); g.restore(); } });\n cell(3, 31, (g, x0, y0, r) => { // concrete: slab, expansion joint, crack, stain\n g.fillStyle = '#a7a7a0'; g.fillRect(x0, y0, N, N);\n for (let k = 0; k < 18; k++) { const gr = g.createRadialGradient(0, 0, 0, 0, 0, 50); g.save(); g.beginPath(); g.rect(x0, y0, N, N); g.clip(); g.translate(x0 + r() * N, y0 + r() * N); gr.addColorStop(0, `rgba(${r() < 0.5 ? '70,70,60' : '200,200,190'},.12)`); gr.addColorStop(1, 'rgba(0,0,0,0)'); g.fillStyle = gr; g.fillRect(-50, -50, 100, 100); g.restore(); }\n noise(g, x0, y0, N, r, 0.25); g.fillStyle = 'rgba(40,40,36,.55)'; g.fillRect(x0, y0 + N / 2 - 1, N, 2); g.fillRect(x0 + N / 2 - 1, y0, 2, N); g.fillRect(x0, y0, N, 1); g.fillRect(x0, y0, 1, N);\n g.strokeStyle = 'rgba(50,50,46,.5)'; g.lineWidth = 1; let x = x0 + 30 + r() * 60, y = y0 + 8; g.beginPath(); g.moveTo(x, y); for (let s = 0; s < 8; s++) { x += (r() - 0.5) * 16; y += 12 + r() * 8; g.lineTo(x, y); } g.stroke(); });\n cell(4, 41, (g, x0, y0, r) => { // deck: 12 boards in staggered rows\n const th = N / 12; g.fillStyle = '#2a1c10'; g.fillRect(x0, y0, N, N);\n for (let j = 0; j < 12; j++) { const cut = r() * N; for (const [a, b] of [[0, cut], [cut, N]]) { const v = 150 + (r() - 0.5) * 36; g.fillStyle = `rgb(${v},${v * 0.68 | 0},${v * 0.42 | 0})`; g.fillRect(x0 + a + 1, y0 + j * th + 1, b - a - 2, th - 2);\n g.strokeStyle = 'rgba(50,28,12,.3)'; for (let s = 0; s < 3; s++) { const y = y0 + j * th + 3 + r() * (th - 6); g.beginPath(); g.moveTo(x0 + a, y); g.lineTo(x0 + b, y + (r() - 0.5) * 2); g.stroke(); }\n g.fillStyle = 'rgba(30,20,10,.7)'; g.fillRect(x0 + a + 4, y0 + j * th + th / 2, 2, 2); g.fillRect(x0 + b - 6, y0 + j * th + th / 2, 2, 2); } } });\n cell(5, 51, (g, x0, y0, r) => { // packed dirt\n g.fillStyle = '#7b5538'; g.fillRect(x0, y0, N, N);\n seamless(g, x0, y0, N, () => { for (let k = 0; k < 30; k++) { g.fillStyle = `rgba(${r() < 0.5 ? '60,38,24' : '150,112,80'},${0.12 + r() * 0.15})`; g.beginPath(); g.arc(r() * N, r() * N, 6 + r() * 20, 0, 6.3); g.fill(); }\n for (let k = 0; k < 90; k++) { const v = 90 + r() * 90; g.fillStyle = `rgb(${v},${v * 0.85 | 0},${v * 0.7 | 0})`; g.fillRect(r() * N, r() * N, 1 + r() * 3, 1 + r() * 3); } }); });\n cell(6, 61, (g, x0, y0, r) => { // 8x8 pool tiles\n const q = N / 8; g.fillStyle = '#d4e8ee'; g.fillRect(x0, y0, N, N);\n for (let j = 0; j < 8; j++) for (let i = 0; i < 8; i++) { const v = (r() - 0.5) * 24; g.fillStyle = `rgb(${50 + v},${150 + v},${190 + v})`; g.fillRect(x0 + i * q + 1, y0 + j * q + 1, q - 2, q - 2); g.fillStyle = 'rgba(255,255,255,.2)'; g.fillRect(x0 + i * q + 2, y0 + j * q + 2, q - 6, 2); } });\n cell(7, 71, (g, x0, y0, r) => { // reddish interlocking paving (small bricks)\n const bw = N / 4, bh = N / 8; g.fillStyle = '#7a7268'; g.fillRect(x0, y0, N, N);\n for (let j = 0; j < 8; j++) for (let i = -1; i < 4; i++) { const off = (j % 2) * bw / 2, v = 150 + (r() - 0.5) * 30; g.fillStyle = `rgb(${v + 20},${v * 0.55 | 0},${v * 0.42 | 0})`; g.fillRect(x0 + ((i * bw + off) % N + N) % N + 1, y0 + j * bh + 1, bw - 2, bh - 2); }\n noise(g, x0, y0, N, r, 0.2); });\n const cells = {} as Record<FloorKind, Uv[]>; for (const k of Object.keys(FLOORS) as FloorKind[]) cells[k] = FLOORS[k].cells.map(c => ({ u0: (c % 4) / 4, u1: (c % 4 + 1) / 4, v0: 1 - (Math.floor(c / 4) + 1) / 2, v1: 1 - Math.floor(c / 4) / 2 }));\n const e = 1.5 / (N * 4), ev = 1.5 / (N * 2); // inset of one and a half pixels: no bleeding from the neighbouring cell\n return { canvas: cv, cells, size: (k: FloorKind) => FLOORS[k].size, uv(k: FloorKind, v = 0) { const L = cells[k]; const c = L[v % L.length]; return { u0: c.u0 + e, u1: c.u1 - e, v0: c.v0 + ev, v1: c.v1 - ev }; }, variants: (k: FloorKind) => FLOORS[k].cells.length };\n}\n\nfunction pictureAtlas(doc: DocumentLike, names: string[]): PictureAtlas {\n const N = 256, cv = makeCanvas(doc, N * 4, N * 2), g = cv.getContext('2d')!;\n const cell = (c: number, seed: number, f: (r: Rand) => void) => { const x0 = (c % 4) * N, y0 = Math.floor(c / 4) * N, r = rng(seed); g.save(); g.beginPath(); g.rect(x0, y0, N, N); g.clip(); g.translate(x0, y0); f(r); g.restore(); };\n cell(0, 101, r => { g.fillStyle = '#e8dcc4'; g.fillRect(0, 0, N, N); g.fillStyle = '#c0613c'; g.beginPath(); g.arc(90 + r() * 30, 110, 60, 0, 6.3); g.fill(); g.fillStyle = '#2f7f84'; g.fillRect(120, 40 + r() * 30, 90, 150); g.fillStyle = '#d9a43a'; g.beginPath(); g.arc(150, 200, 70, Math.PI, 0); g.fill(); g.strokeStyle = '#2a2a2a'; g.lineWidth = 3; g.beginPath(); g.moveTo(20, 230); g.lineTo(236, 30); g.stroke(); });\n cell(1, 102, r => { g.fillStyle = '#1f3a5f'; g.fillRect(0, 0, N, N); for (let i = 0; i < 7; i++) { g.fillStyle = ['#f08a3c', '#f4e4c1', '#d94f4f', '#7fb5a5'][i % 4]; g.globalAlpha = 0.85; g.beginPath(); g.ellipse(r() * N, r() * N, 20 + r() * 50, 12 + r() * 30, r() * 3, 0, 6.3); g.fill(); } g.globalAlpha = 1; g.strokeStyle = '#f4e4c1'; g.lineWidth = 4; for (let i = 0; i < 3; i++) { g.beginPath(); g.moveTo(0, 50 + i * 70); g.bezierCurveTo(80, 10 + i * 70, 170, 120 + i * 40, N, 60 + i * 60); g.stroke(); } });\n cell(2, 103, r => { const cs = ['#e9e1cf', '#c8553d', '#2e5e7e', '#e4b23e', '#4b4b4b']; let x = 0; while (x < N) { const w = 30 + r() * 60; g.fillStyle = cs[(r() * cs.length) | 0]; g.fillRect(x, 0, w, N); x += w; } g.fillStyle = '#e9e1cf'; g.fillRect(40, 60, 176, 136); g.fillStyle = cs[1]; g.fillRect(70, 90, 60, 76); g.fillStyle = cs[2]; g.fillRect(140, 90, 50, 36); });\n cell(3, 104, r => { g.fillStyle = '#f2f0ea'; g.fillRect(0, 0, N, N); g.fillStyle = '#1a1a1a'; g.fillRect(40, 190, 176, 8); g.fillStyle = '#b8352e'; g.beginPath(); g.arc(128, 100, 52, 0, 6.3); g.fill(); g.strokeStyle = '#1a1a1a'; g.lineWidth = 2; for (let i = 0; i < 5; i++) { g.beginPath(); g.moveTo(40, 30 + i * 8 + r() * 4); g.lineTo(216, 30 + i * 8); g.stroke(); } });\n cell(4, 105, r => { const gr = g.createLinearGradient(0, 0, 0, N * 0.7); gr.addColorStop(0, '#5a4a8a'); gr.addColorStop(0.5, '#e8805a'); gr.addColorStop(1, '#f7d27a'); g.fillStyle = gr; g.fillRect(0, 0, N, N); g.fillStyle = '#fff3c0'; g.beginPath(); g.arc(150, 150, 36, 0, 6.3); g.fill();\n ([['#7a5a6a', 150], ['#4a4a5e', 175], ['#2a3a3a', 205]] as Array<[string, number]>).forEach(([c, y], k) => { g.fillStyle = c; g.beginPath(); g.moveTo(0, N); g.lineTo(0, y); for (let x = 0; x <= N; x += 32) g.lineTo(x, y - 10 + r() * 24 - k * 3); g.lineTo(N, N); g.fill(); }); });\n cell(5, 106, r => { const gr = g.createLinearGradient(0, 0, 0, N); gr.addColorStop(0, '#9fc8e8'); gr.addColorStop(0.55, '#e8f0f4'); gr.addColorStop(0.56, '#3d7a9a'); gr.addColorStop(1, '#22506a'); g.fillStyle = gr; g.fillRect(0, 0, N, N);\n g.fillStyle = '#5a6a78'; g.beginPath(); g.moveTo(0, 140); for (let x = 0; x <= N; x += 28) g.lineTo(x, 70 + r() * 50); g.lineTo(N, 140); g.fill(); g.fillStyle = '#3a5a4a'; g.beginPath(); g.moveTo(150, 140); g.quadraticCurveTo(200, 60, 230, 140); g.fill(); });\n cell(6, 107, () => { const gr = g.createLinearGradient(0, 0, 0, N); gr.addColorStop(0, '#f6e0a8'); gr.addColorStop(0.5, '#fbeed0'); gr.addColorStop(0.5, '#4bb0c4'); gr.addColorStop(0.72, '#2e8aa8'); gr.addColorStop(0.72, '#ecd8a2'); gr.addColorStop(1, '#d9bd7e'); g.fillStyle = gr; g.fillRect(0, 0, N, N);\n g.strokeStyle = '#4a3a22'; g.lineWidth = 5; g.beginPath(); g.moveTo(60, 255); g.quadraticCurveTo(70, 160, 100, 70); g.stroke(); g.fillStyle = '#2f6a3a'; for (let i = 0; i < 6; i++) { g.beginPath(); g.moveTo(100, 70); g.quadraticCurveTo(100 + Math.cos(i) * 40, 40 + Math.sin(i * 2) * 20, 100 + Math.cos(i * 1.1) * 60, 80 + i * 6); g.quadraticCurveTo(100 + Math.cos(i) * 30, 70, 100, 70); g.fill(); } });\n cell(7, 108, () => { const gr = g.createLinearGradient(0, 0, N, N); gr.addColorStop(0, '#e4e9eb'); gr.addColorStop(0.5, '#c4ccd0'); gr.addColorStop(1, '#a8b2b8'); g.fillStyle = gr; g.fillRect(0, 0, N, N);\n g.fillStyle = 'rgba(255,255,255,.35)'; g.beginPath(); g.moveTo(30, 0); g.lineTo(90, 0); g.lineTo(0, 120); g.lineTo(0, 60); g.fill(); g.beginPath(); g.moveTo(150, 0); g.lineTo(175, 0); g.lineTo(0, 256); g.lineTo(0, 232); g.fill();\n const v = g.createRadialGradient(128, 128, 90, 128, 128, 190); v.addColorStop(0, 'rgba(0,0,0,0)'); v.addColorStop(1, 'rgba(0,0,0,.18)'); g.fillStyle = v; g.fillRect(0, 0, N, N); });\n const uvc = (c: number): Uv => ({ u0: (c % 4) / 4 + 1.5 / (N * 4), u1: (c % 4 + 1) / 4 - 1.5 / (N * 4), v0: 1 - (Math.floor(c / 4) + 1) / 2 + 1.5 / (N * 2), v1: 1 - Math.floor(c / 4) / 2 - 1.5 / (N * 2) });\n return { canvas: cv, n: PICTURE_CELLS - 1, uv: c => uvc(c), mirror: () => uvc(7), names };\n}\n\n// light/dark ripple that tiles; the host scrolls texture.offset for the shimmer\nfunction waterAtlas(doc: DocumentLike): WaterAtlas {\n const N = 128, cv = makeCanvas(doc, N, N), g = cv.getContext('2d')!, r = rng(901);\n g.fillStyle = '#e6f2f8'; g.fillRect(0, 0, N, N);\n seamless(g, 0, 0, N, () => { for (let i = 0; i < 26; i++) { const x = r() * N, y = r() * N, w = 14 + r() * 26; g.strokeStyle = r() < 0.55 ? 'rgba(255,255,255,.9)' : 'rgba(110,170,200,.5)'; g.lineWidth = 1 + r() * 1.5; g.beginPath(); g.moveTo(x, y); g.quadraticCurveTo(x + w / 2, y - 5 - r() * 4, x + w, y); g.stroke(); } });\n return { canvas: cv };\n}\n\n/** Lazily builds and caches the procedural canvas atlases (one canvas per atlas; the host makes one texture per canvas). */\nexport function createAtlasProvider(doc: DocumentLike | null, pictureNames: string[]) {\n const cache: { [K in AtlasName]?: AtlasMap[AtlasName] } = {}\n return function atlas<K extends AtlasName>(name: K): AtlasMap[K] {\n if (!doc) throw new Error('game-props3d: a document is needed to build the atlases (pass options.document)')\n const made = cache[name]\n if (made) return made as AtlasMap[K]\n const build = { floor: () => floorAtlas(doc), picture: () => pictureAtlas(doc, pictureNames), water: () => waterAtlas(doc) }[name]\n return (cache[name] = build()) as AtlasMap[K]\n }\n}\n","import type { PropGroup, TypeDef } from './types'\n\nconst T = <I extends string>(id: I, interior: boolean, w: number, d: number, h: number, y: number, solid: boolean, group: PropGroup): TypeDef<I> => ({ id, interior, w, d, h, y, solid, group })\n\n/** id, interior, w, d, h, y (mounting height), solid (false = no collision), group. Names live in the locale table. */\nexport const TYPE_DEFS = [\n T('ceiling_light', true, 0.4, 0.4, 0.07, 0, false, 'ceiling'),\n T('pendant', true, 0.34, 0.34, 1.0, 0, false, 'ceiling'),\n T('track_spot', true, 1.2, 0.1, 0.2, 0, false, 'ceiling'),\n T('chandelier', true, 0.7, 0.7, 0.7, 0, false, 'ceiling'),\n T('wall_ac', true, 0.9, 0.2, 0.28, 2.15, false, 'wall'),\n T('picture_small', true, 0.35, 0.03, 0.45, 1.3, false, 'wall'),\n T('picture_large', true, 1.0, 0.04, 0.7, 1.2, false, 'wall'),\n T('mirror', true, 0.6, 0.03, 1.2, 0.9, false, 'wall'),\n T('curtain', true, 1.6, 0.12, 2.3, 0.1, false, 'wall'),\n T('blinds', true, 1.2, 0.08, 1.3, 1.0, false, 'wall'),\n T('flat_tv', true, 1.2, 0.06, 0.7, 1.0, false, 'wall'),\n T('floor_plant', true, 0.5, 0.5, 1.1, 0, true, 'plants'),\n T('table_plant', true, 0.2, 0.2, 0.3, 0, false, 'plants'),\n T('hanging_plant', true, 0.5, 0.5, 1.0, 0, false, 'plants'),\n T('tv_stand', true, 1.6, 0.42, 1.05, 0, true, 'furniture'),\n T('breakfast_bar', true, 2.0, 1.6, 0.95, 0, true, 'furniture'),\n T('sofa_l', true, 2.8, 1.7, 0.85, 0, true, 'furniture'),\n T('double_bed_modern', true, 1.6, 2.1, 1.2, 0, true, 'furniture'),\n T('home_office', true, 1.4, 0.75, 1.2, 0, true, 'furniture'),\n T('closet', true, 2.2, 0.6, 2.3, 0, true, 'furniture'),\n T('vanity', true, 0.6, 0.45, 1.5, 0, true, 'furniture'),\n T('ac_condenser', false, 0.8, 0.3, 0.55, 0.5, true, 'yard'),\n T('lawn', false, 4, 4, 0.02, 0, false, 'floor'),\n T('tile_floor', false, 4, 3, 0.03, 0, false, 'floor'),\n T('concrete', false, 4, 4, 0.03, 0, false, 'floor'),\n T('deck_floor', false, 4, 3, 0.04, 0, false, 'floor'),\n T('dirt', false, 4, 4, 0.02, 0, false, 'floor'),\n T('gourmet_counter', false, 4.2, 3.0, 2.9, 0, true, 'yard'),\n T('sauna', false, 2.0, 1.8, 2.1, 0, true, 'yard'),\n T('soaking_tub', false, 1.3, 1.3, 0.8, 0, true, 'yard'),\n T('pool', false, 4.5, 2.8, 0.1, 0, true, 'yard'),\n T('deck', false, 3.5, 3.0, 0.22, 0, true, 'yard'),\n T('sun_lounger', false, 0.62, 1.9, 0.5, 0, true, 'yard'),\n T('garden_bed', false, 2.0, 1.0, 0.9, 0, true, 'yard'),\n T('fruit_tree', false, 2.4, 2.4, 3.2, 0, true, 'yard'),\n T('palm_tree', false, 2.0, 2.0, 4.5, 0, true, 'yard'),\n T('pergola', false, 3.5, 3.0, 2.4, 0, true, 'yard'),\n T('slide', false, 0.8, 2.6, 1.5, 0, true, 'yard'),\n T('swing', false, 2.2, 1.7, 2.2, 0, true, 'yard'),\n T('bbq_simple', false, 0.9, 0.6, 1.0, 0, true, 'yard'),\n T('garage_car', false, 1.8, 4.2, 1.45, 0, true, 'yard'),\n T('window_boards', true, 1.2, 0.06, 1.15, 1.0, false, 'house_state'),\n T('barricade', true, 1.5, 0.7, 1.8, 0, true, 'house_state'),\n T('sandbags', false, 1.6, 0.5, 0.6, 0, true, 'house_state'),\n T('barbed_wire', false, 4.0, 0.2, 0.6, 0, false, 'house_state'),\n T('razor_wire', false, 4.0, 0.6, 0.6, 0, false, 'house_state'),\n T('water_bottle_crates', true, 0.9, 0.6, 0.65, 0, true, 'house_state'),\n T('canned_food', true, 0.4, 0.3, 0.3, 0, false, 'house_state'),\n T('water_jug', true, 0.3, 0.3, 0.5, 0, true, 'house_state'),\n T('floor_mattress', true, 0.9, 1.9, 0.3, 0, false, 'house_state'),\n T('lantern', true, 0.16, 0.16, 0.3, 0, false, 'house_state'),\n T('candles', true, 0.25, 0.25, 0.2, 0, false, 'house_state'),\n T('open_suitcases', true, 1.5, 0.7, 0.6, 0, false, 'house_state'),\n T('clothes_on_floor', true, 1.2, 1.0, 0.05, 0, false, 'house_state'),\n T('fallen_chair', true, 0.5, 0.5, 0.5, 0, false, 'house_state'),\n T('blood_stains', true, 1.2, 1.2, 0.01, 0, false, 'house_state'),\n T('open_door', true, 0.9, 0.15, 2.15, 0, false, 'house_state'),\n]\n\nexport type PropType = (typeof TYPE_DEFS)[number]['id']\n\nexport const DEFS = Object.fromEntries(TYPE_DEFS.map((t) => [t.id, t])) as Record<PropType, (typeof TYPE_DEFS)[number]>\n\nexport const MATERIALS = ['plain', 'metal', 'wood', 'glass', 'fabric', 'plant', 'water', 'emissive', 'atlas_picture', 'atlas_floor'] as const\n","import type { BuilderContext, BuilderMap } from '../types'\n\nexport function ceilingBuilders({ kit }: BuilderContext): Partial<BuilderMap> {\n const B: Partial<BuilderMap> = {}\n B.ceiling_light = (M, a) => { const K = kit(M, a), t = K.ceiling(), on = !!M.on;\n K.cyl('plain', 0, t - 0.05, 0, 0.2, 0.05, 0xece8e0); K.cyl('plain', 0, t - 0.07, 0, 0.15, 0.02, 0xd8d4c8); K.cyl(on ? 'emissive' : 'plain', 0, t - 0.078, 0, 0.12, 0.012, on ? 0xfff0c8 : 0xf4f0e6);\n if (on) K.light(0, t - 0.15, 0, 0xffe8b8, 6, 'ceiling_light'); };\n B.pendant = (M, a) => { const K = kit(M, a), t = K.ceiling(), { h } = K.dim('pendant'), ys = t - h, color = K.main(0x2a2a2a);\n K.box('plain', 0, ys + 0.1, 0, 0.012, h - 0.1, 0.012, 0x1a1a1a); K.cyl('plain', 0, t - 0.03, 0, 0.05, 0.03, 0xe8e4dc);\n K.cyl('plain', 0, ys, 0, 0.17, 0.14, color, 0, 0, 'sphere'); K.cyl('emissive', 0, ys - 0.035, 0, 0.045, 0.09, 0xffe0a0, 0, 0, 'sphere');\n if (M.light) K.light(0, ys - 0.1, 0, 0xffc880, 6, 'pendant'); };\n B.track_spot = (M, a) => { const K = kit(M, a), t = K.ceiling(), { w } = K.dim('track_spot'), on = !!M.on;\n K.box('metal', 0, t - 0.025, 0, w, 0.025, 0.035, 0x2a2a2a);\n for (let i = -1; i <= 1; i++) { const u = i * w / 3; K.box('metal', u, t - 0.1, 0, 0.02, 0.075, 0.02, 0x2a2a2a); K.cyl('metal', u, t - 0.16, 0.04, 0.035, 0.11, 0x222222, 2.6, 0); K.cyl(on ? 'emissive' : 'plain', u, t - 0.208, 0.069, 0.028, 0.006, on ? 0xfff0c8 : 0xe8e8e8, 2.6, 0); } };\n B.chandelier = (M, a) => { const K = kit(M, a), t = K.ceiling(), on = !!M.on, y = t - 0.5, gold = K.metal(0x8a7a5a);\n K.box('metal', 0, y, 0, 0.012, 0.5, 0.012, gold); K.cyl('metal', 0, y - 0.04, 0, 0.06, 0.08, gold); K.cyl('metal', 0, y - 0.06, 0, 0.14, 0.015, gold);\n for (let i = 0; i < 6; i++) { const an = i * Math.PI / 3, cx = Math.cos(an), cz = Math.sin(an);\n K.box('metal', cx * 0.14, y - 0.06, cz * 0.14, 0.28, 0.012, 0.012, gold, -an); K.cyl('plain', cx * 0.28, y - 0.06, cz * 0.28, 0.02, 0.09, 0xf0e6c8); K.cyl(on ? 'emissive' : 'plain', cx * 0.28, y + 0.03, cz * 0.28, 0.012, 0.03, on ? 0xffd890 : 0xe6d8b0, 0, 0, 'sphere');\n K.box('glass', cx * 0.3, y - 0.2, cz * 0.3, 0.025, 0.14, 0.025, 0xffffff, -an); K.box('glass', cx * 0.14, y - 0.18, cz * 0.14, 0.02, 0.12, 0.02, 0xffffff); }\n K.box('glass', 0, y - 0.3, 0, 0.05, 0.2, 0.05, 0xffffff, 0.7);\n if (M.light) K.light(0, y - 0.1, 0, 0xffd8a0, 6, 'chandelier'); };\n return B\n}\n","import type { BuilderContext, BuilderMap, FloorKind } from '../types'\n\nconst FLOOR_ITEMS: Array<FloorKind & keyof BuilderMap> = ['lawn', 'tile_floor', 'concrete', 'deck_floor', 'dirt']\n\n/** Ground cover items: one builder each, tiling the item's rectangle (or `M.poly`) with the floor atlas. */\nexport function floorBuilders({ cover }: BuilderContext): Partial<BuilderMap> {\n const B: Partial<BuilderMap> = {}\n for (const kind of FLOOR_ITEMS) B[kind] = (M, api) => cover(api, M, kind, { color: M.color })\n return B\n}\n","import { shade } from '../colors'\nimport type { Kit } from '../types'\n\nexport const GREENS = [0x3f7a3a, 0x2f6a30, 0x4f8a42, 0x5a9448]\n/** i-th shade of green (from the `plant` palette role or the defaults), alternating darker. */\nexport const green = (K: Kit, i: number): number => {\n const v = K.color('plant', GREENS[i % GREENS.length])\n return i % 2 ? shade(v, 0.85) : v\n}\nexport const pick = <T>(K: Kit, L: T[]): T => L[Math.floor(K.rnd() * L.length) % L.length]\n\n// flat TV: panel, screen, back box and either a stand (two feet) or a wall bracket; returns the panel height\nexport const drawTv = (K: Kit, x: number, y: number, z: number, w: number, stand: boolean): number => { const h = w * 0.5625, yb = stand ? y + 0.07 : y;\n K.box('plain', x, yb, z, w, h, 0.035, 0x101112); K.box('metal', x, yb + 0.015, z + 0.0175, w - 0.03, h - 0.03, 0.004, 0x0a0c10); K.box('plain', x, yb + h * 0.2, z - 0.035, w * 0.5, h * 0.55, 0.04, 0x1a1a1a);\n if (stand) { for (const s of [-1, 1]) K.box('metal', x + s * w * 0.33, y, z, 0.1, 0.07, 0.22, 0x1a1a1a); } else K.box('metal', x, yb + h * 0.35, z - 0.065, w * 0.3, 0.03, 0.03, 0x2a2a2a); return h; };\n","import type { BuilderContext, BuilderMap } from '../types'\nimport { shade } from '../colors'\nimport { drawTv, pick } from './shared'\n\nexport function furnitureBuilders({ kit, atlas }: BuilderContext): Partial<BuilderMap> {\n const B: Partial<BuilderMap> = {}\n B.breakfast_bar = (M, a) => { const K = kit(M, a), { w, d } = K.dim('breakfast_bar'), zb = -d / 2, cd = 0.65, body = K.color('wall_kitchen', 0xf0eee8), wood = K.wood(0xa07a50), paving = 0xf4f4f0;\n K.box('plain', 0, 0, zb + cd / 2 - 0.03, w - 0.06, 0.08, cd - 0.08, 0x2a2a2a); K.box('plain', 0, 0.08, zb + cd / 2, w, 0.8, cd, body);\n for (let i = 0; i < 4; i++) { K.box('wood', -w / 2 + (i + 0.5) * w / 4, 0.12, zb - 0.004, w / 4 - 0.025, 0.72, 0.012, wood); K.box('metal', -w / 2 + (i + 0.5) * w / 4 + (i % 2 ? -0.12 : 0.12), 0.75, zb - 0.012, 0.012, 0.14, 0.016, 0x9a9a9a); }\n K.box('plain', 0, 0.88, zb + (cd + 0.32) / 2, w + 0.04, 0.04, cd + 0.32, paving);\n for (const s of [-1, 1]) K.box('metal', s * (w / 2 - 0.05), 0, zb + cd + 0.24, 0.05, 0.88, 0.05, 0x2a2a2a);\n K.box('metal', -w * 0.22, 0.915, zb + cd / 2, 0.52, 0.006, 0.38, 0xb0b4b8); K.box('metal', -w * 0.22, 0.92, zb + cd / 2, 0.46, 0.004, 0.32, 0x70767a); K.cyl('metal', -w * 0.22, 0.92, zb + 0.1, 0.014, 0.26, 0xb8bcc0); K.box('metal', -w * 0.22, 1.16, zb + 0.15, 0.02, 0.02, 0.1, 0xb8bcc0);\n K.cyl('plain', w * 0.25, 0.92, zb + cd / 2 + 0.2, 0.13, 0.05, 0xf0f0ea); for (let i = 0; i < 3; i++) K.cyl('plain', w * 0.25 + (i - 1) * 0.07, 0.97, zb + cd / 2 + 0.2 + (i % 2) * 0.03, 0.045, 0.09, [0xe8801c, 0xc83030, 0xd8c040][i], 0, 0, 'sphere');\n const stool = K.accent(0x8a5a38); for (let i = 0; i < 3; i++) { const x = (i - 1) * w * 0.3, z = zb + cd + 0.5;\n K.cyl('plain', x, 0.62, z, 0.17, 0.05, stool); K.cyl('metal', x, 0.04, z, 0.02, 0.58, 0x2a2a2a); K.cyl('metal', x, 0, z, 0.17, 0.02, 0x2a2a2a); K.cyl('metal', x, 0.25, z, 0.14, 0.012, 0x2a2a2a); } };\n B.sofa_l = (M, a) => { const K = kit(M, a), { w, d } = K.dim('sofa_l'), c = K.fabric(pick(K, [0x6e7078, 0x8a7a68, 0x45586a])), cd = shade(c, 0.82), zb = -d / 2, pf = 0.95, ac = K.accent(0xc89a4a), cw = 0.95;\n for (const sx of [-1, 1]) for (const sz of [-1, 1]) K.box('wood', sx * (w / 2 - 0.1), 0, sz * (d / 2 - 0.1), 0.05, 0.08, 0.05, K.wood(0x2a1d14));\n K.box('fabric', -(cw / 2), 0.08, zb + pf / 2, w - cw, 0.26, pf, cd); K.box('fabric', w / 2 - cw / 2, 0.08, 0, cw, 0.26, d, cd);\n K.box('fabric', 0, 0.08, zb + 0.11, w, 0.72, 0.22, cd); K.box('fabric', -w / 2 + 0.09, 0.08, zb + pf / 2, 0.18, 0.55, pf, cd);\n const nm = 2, mw = (w - cw - 0.18) / nm; for (let i = 0; i < nm; i++) K.box('fabric', -w / 2 + 0.18 + (i + 0.5) * mw, 0.34, zb + 0.12 + 0.39, mw - 0.02, 0.16, 0.78, c);\n K.box('fabric', w / 2 - cw / 2, 0.34, 0.035, cw - 0.04, 0.16, d - 0.2, c);\n for (let i = 0; i < 4; i++) K.box('fabric', -w / 2 + 0.18 + (i + 0.5) * (w - 0.2) / 4, 0.42, zb + 0.28, (w - 0.2) / 4 - 0.03, 0.4, 0.14, c, 0, 0.15);\n K.box('fabric', -w * 0.3, 0.5, zb + 0.5, 0.4, 0.38, 0.12, ac, 0.3, 0.3); K.box('fabric', w * 0.32, 0.5, zb + 0.5, 0.38, 0.36, 0.12, shade(ac, 0.8), -0.3, 0.3); };\n B.double_bed_modern = (M, a) => { const K = kit(M, a), { w, d } = K.dim('double_bed_modern'), wood = K.wood(0x6a4c34), edr = K.fabric(0x8a8d92), accent = K.accent(0xc89a4a);\n K.box('wood', 0, 0.05, 0.02, w + 0.06, 0.2, d + 0.02, shade(wood, 0.85)); K.box('fabric', 0, 0.25, 0.05, w - 0.04, 0.24, d - 0.1, 0xf2f0ea);\n K.box('fabric', 0, 0.47, 0.35, w, 0.06, d * 0.64, edr); K.box('fabric', 0, 0.5, -0.05, w * 0.98, 0.05, 0.3, shade(edr, 1.12)); K.box('fabric', 0, 0.52, d / 2 - 0.4, w * 0.9, 0.03, 0.3, accent);\n for (const s of [-1, 1]) K.box('fabric', s * w * 0.24, 0.49, -d / 2 + 0.3, w * 0.4, 0.13, 0.36, 0xf4f2ec, s * 0.06);\n K.box('fabric', 0, 0.55, -d / 2 + 0.5, 0.4, 0.16, 0.12, accent, 0, 0.5);\n const hw = w + 0.5, n = Math.round(hw / 0.11); K.box('wood', 0, 0.2, -d / 2 - 0.05, hw, 1.0, 0.04, shade(wood, 0.7)); for (let i = 0; i < n; i++) K.box('wood', -hw / 2 + (i + 0.5) * hw / n, 0.25, -d / 2 - 0.02, hw / n - 0.025, 0.95, 0.04, i % 2 ? wood : shade(wood, 1.12));\n for (const s of [-1, 1]) { K.box('wood', s * (w / 2 + 0.3), 0.35, -d / 2 + 0.22, 0.4, 0.16, 0.36, shade(wood, 1.05)); K.cyl('plain', s * (w / 2 + 0.3), 0.51, -d / 2 + 0.2, 0.06, 0.03, 0xe8e4dc); K.cyl('plain', s * (w / 2 + 0.3), 0.54, -d / 2 + 0.2, 0.012, 0.18, 0xe8e4dc); K.cyl('plain', s * (w / 2 + 0.3), 0.72, -d / 2 + 0.2, 0.09, 0.12, 0xf0e6c8); } };\n B.home_office = (M, a) => { const K = kit(M, a), { w, d } = K.dim('home_office'), wood = K.wood(0xd2b48c), zb = -d / 2;\n K.box('wood', 0, 0.72, 0, w, 0.035, d, wood); for (const s of [-1, 1]) K.box('metal', s * (w / 2 - 0.04), 0, 0, 0.035, 0.72, d - 0.1, 0x222226); K.box('metal', 0, 0.3, zb + 0.05, w - 0.1, 0.03, 0.02, 0x222226);\n K.box('plain', w / 2 - 0.25, 0.28, 0, 0.4, 0.44, d - 0.08, 0xe8e4dc); for (let i = 0; i < 2; i++) { K.box('plain', w / 2 - 0.25, 0.3 + i * 0.2, d / 2 - 0.04, 0.37, 0.18, 0.01, 0xf2eee6); K.box('metal', w / 2 - 0.25, 0.39 + i * 0.2, d / 2 - 0.03, 0.12, 0.012, 0.012, 0x9a9a9a); }\n K.box('metal', 0, 0.755, zb + 0.2, 0.22, 0.012, 0.16, 0x1a1a1a); K.box('metal', 0, 0.76, zb + 0.14, 0.04, 0.2, 0.025, 0x1a1a1a); K.box('plain', 0, 0.88, zb + 0.15, 0.62, 0.36, 0.035, 0x101112); K.box('metal', 0, 0.895, zb + 0.1685, 0.6, 0.33, 0.004, 0x0a0c10);\n K.box('plain', 0, 0.755, zb + 0.43, 0.42, 0.014, 0.14, 0x1a1a1c); K.box('plain', 0.34, 0.755, zb + 0.43, 0.06, 0.025, 0.1, 0x1a1a1c, 0.2);\n K.cyl('metal', -w * 0.4, 0.755, zb + 0.2, 0.07, 0.015, 0x2a2a2a); K.box('metal', -w * 0.4, 0.77, zb + 0.2, 0.012, 0.3, 0.012, 0x2a2a2a, 0, 0.3); K.cyl('plain', -w * 0.4, 1.02, zb + 0.28, 0.06, 0.05, K.main(0xd8d4c8), 0.7, 0);\n K.box('fabric', -0.5, 0.755, 0.05, 0.2, 0.02, 0.28, K.accent(0x2e5e7e), 0.2); K.cyl('plain', -0.3, 0.755, 0.12, 0.04, 0.09, 0xd8d0c0);\n const zc = d / 2 + 0.3, cc = K.fabric(0x2a2a2e); K.box('plain', 0, 0.44, zc, 0.48, 0.07, 0.46, cc); K.box('plain', 0, 0.52, zc + 0.22, 0.44, 0.52, 0.07, cc, 0, 0.12); K.cyl('metal', 0, 0.06, zc, 0.025, 0.38, 0x2a2a2a);\n for (let i = 0; i < 5; i++) { const an = i * 1.2566, x = Math.cos(an) * 0.17, z = zc + Math.sin(an) * 0.17; K.box('metal', Math.cos(an) * 0.085, 0.06, zc + Math.sin(an) * 0.085, 0.18, 0.025, 0.03, 0x2a2a2a, -an); K.cyl('plain', x, 0, z, 0.03, 0.05, 0x1a1a1a); } };\n B.closet = (M, a) => { const K = kit(M, a), { w, d, h } = K.dim('closet'), wood = K.color('wall_bedroom', 0xe6e0d4), mw = K.wood(0xb89468), dv = -w * 0.1;\n K.box('plain', 0, 0, -d / 2 + 0.01, w, h, 0.02, shade(wood, 0.95)); for (const s of [-1, 1]) K.box('plain', s * (w / 2 - 0.015), 0, 0, 0.03, h, d, wood); K.box('plain', 0, h - 0.03, 0, w, 0.03, d, wood); K.box('plain', 0, 0.06, 0, w - 0.06, 0.02, d - 0.02, wood); K.box('plain', 0, 0, 0.0, w - 0.04, 0.06, d - 0.06, 0x2a2a2a); K.box('plain', dv, 0.06, 0, 0.025, h - 0.09, d - 0.02, wood);\n const rod = h * 0.76; K.cyl('metal', (-w / 2 + dv) / 2, rod, 0, 0.012, dv + w / 2 - 0.06, 0xb0b4b8, 0, Math.PI / 2); K.box('plain', (-w / 2 + dv) / 2, rod + 0.05, 0, dv + w / 2 - 0.05, 0.02, d - 0.04, wood);\n const nc = 9, L0 = -w / 2 + 0.06, L1 = dv - 0.05, cs = [0x2a2a30, 0xc8c0a8, 0x4a6a8a, 0xa8483a, 0xe0dcd0, 0x6a8a5a, 0x3a3a3a, 0x8a7a68, 0xd8b8a0];\n for (let i = 0; i < nc; i++) { const gh = 0.6 + K.rnd() * 0.5; K.box('fabric', L0 + (i + 0.5) * (L1 - L0) / nc, rod - gh - 0.02, 0, 0.045, gh, 0.4, i % 3 === 0 ? K.accent(cs[i]) : cs[i]); K.box('metal', L0 + (i + 0.5) * (L1 - L0) / nc, rod - 0.04, 0, 0.004, 0.04, 0.2, 0xb0b4b8); }\n const sx = (dv + w / 2) / 2 + dv / 2 + 0.0, rx0 = dv + 0.015, rw = w / 2 - 0.015 - rx0, rc = rx0 + rw / 2;\n for (let i = 0; i < 5; i++) { const y = 0.5 + i * 0.35; K.box('plain', rc, y, 0, rw, 0.022, d - 0.02, wood); if (i < 4) for (let j = 0; j < 3; j++) { const sh = 0.04 + K.rnd() * 0.12; K.box('fabric', rc - rw * 0.3 + j * rw * 0.3, y + 0.022, 0.0, 0.26, sh, 0.32, cs[(i * 3 + j) % cs.length]); } }\n for (let i = 0; i < 2; i++) { K.box('wood', rc, 0.08 + i * 0.2, d / 2 - 0.01, rw - 0.03, 0.18, 0.02, mw); K.box('metal', rc, 0.2 + i * 0.2, d / 2 + 0.005, 0.2, 0.012, 0.014, 0xb0b4b8); } void sx; };\n B.vanity = (M, a) => { const K = kit(M, a), { w, d } = K.dim('vanity'), zb = -d / 2, br = 0xf4f4f0;\n K.cyl('plain', 0, 0, zb + 0.12, 0.08, 0.7, br); K.cyl('plain', 0, 0, zb + 0.12, 0.1, 0.03, br); K.box('plain', 0, 0.74, 0, w, 0.12, d, br); K.box('plain', 0, 0.865, 0.02, w - 0.1, 0.012, d - 0.1, 0xd4d8da); K.cyl('metal', 0, 0.87, 0.05, 0.02, 0.01, 0x70767a);\n K.cyl('metal', 0, 0.87, zb + 0.05, 0.014, 0.1, 0xb8bcc0); K.box('metal', 0, 0.96, zb + 0.09, 0.02, 0.014, 0.09, 0xb8bcc0); K.box('metal', w * 0.3, 0.87, zb + 0.06, 0.04, 0.06, 0.04, 0x2a7a8a);\n K.box('plain', 0, 1.1, zb + 0.012, 0.52, 0.72, 0.024, K.color('metal', 0xe4e0d8)); K.plane('atlas_picture', 0, 1.46, zb + 0.026, 0.46, 0.66, 0xffffff, 0, 0, 0, atlas('picture').mirror());\n K.box('metal', -w / 2 - 0.02, 0.95, zb + 0.06, 0.03, 0.03, 0.03, 0xb0b4b8); K.box('metal', -w / 2 - 0.02, 0.95, zb + 0.06, 0.28, 0.014, 0.014, 0xb0b4b8); K.box('fabric', -w / 2 - 0.02, 0.62, zb + 0.065, 0.26, 0.34, 0.02, K.accent(0x2e6e7e)); };\n B.tv_stand = (M, a) => { const K = kit(M, a), { w, d } = K.dim('tv_stand'), wood = K.wood(0xc8a070), br = K.color('fabric_main', 0xf0ede6);\n for (const sx of [-1, 1]) for (const sz of [-1, 1]) K.box('metal', sx * (w / 2 - 0.08), 0, sz * (d / 2 - 0.06), 0.035, 0.16, 0.035, 0x1a1a1a);\n K.box('wood', 0, 0.16, 0, w, 0.32, d, wood); K.box('plain', 0, 0.48, 0, w + 0.02, 0.025, d + 0.02, br);\n for (let i = 0; i < 2; i++) K.box('plain', -w / 2 + (i + 0.5) * 0.375 * w, 0.18, d / 2 + 0.004, 0.375 * w - 0.02, 0.28, 0.01, shade(br, 0.97)); // two doors on the left\n K.box('plain', w / 4 + w / 8, 0.19, 0, w / 4 - 0.04, 0.2, d - 0.04, 0x1c1c20); K.box('plain', w / 4 + w / 8, 0.22, 0.05, 0.28, 0.06, 0.2, 0x2a2a30); K.box('fabric', w / 4 + w / 8 + 0.03, 0.29, 0.0, 0.2, 0.05, 0.14, K.accent(0x8a4a3a)); // open niche: console and books\n const th = drawTv(K, 0, 0.5, 0, Math.min(1.3, w * 0.78), true); K.box('plain', 0, 0.5, d / 2 - 0.06, Math.min(1.0, w * 0.55), 0.06, 0.06, 0x16161a); void th; };\n return B\n}\n","import { seedOf, shade } from '../colors'\nimport type { BuilderContext, BuilderMap, Material, PropItem, PropsApi } from '../types'\n\nexport function houseStateBuilders({ kit }: BuilderContext): Partial<BuilderMap> {\n const B: Partial<BuilderMap> = {}\n B.window_boards = (M, a) => { const K = kit(M, a), { w, h, d } = K.dim('window_boards'), y = M.y || 1.0, x = M.variant ? M.variant === 'x' : (seedOf(M) & 1) === 0, cs = [0x7a5634, 0x6b4a2a, 0x8a6a44, 0x5e4228], z = d / 2;\n if (x) { const L = Math.hypot(w, h) + 0.15, an = Math.atan2(h, w); for (const s of [-1, 1]) K.box('wood', 0, y + h / 2 - 0.075, z + (s > 0 ? 0 : 0.03), L, 0.15, 0.03, cs[s > 0 ? 0 : 1], 0, 0, s * an + (K.rnd() - 0.5) * 0.05); K.box('wood', 0, y + h * 0.3, z + 0.06, w + 0.3, 0.15, 0.03, cs[2], 0, 0, (K.rnd() - 0.5) * 0.06); for (const [nx, ny] of [[-w / 2, y], [w / 2, y], [-w / 2, y + h], [w / 2, y + h], [0, y + h / 2]]) K.cyl('metal', nx, ny, z + 0.09, 0.012, 0.02, 0x5a5a5e, Math.PI / 2, 0); }\n else { const n = Math.ceil(h / 0.17); for (let i = 0; i < n; i++) { const yy = y + i * (h - 0.15) / (n - 1 || 1), ww = w + 0.2 + K.rnd() * 0.15; K.box('wood', (K.rnd() - 0.5) * 0.1, yy, z + (i % 2) * 0.03, ww, 0.15, 0.03, cs[i % 4], 0, 0, (K.rnd() - 0.5) * 0.05); for (const s of [-1, 1]) K.cyl('metal', s * (w / 2 + 0.02), yy + 0.075, z + (i % 2) * 0.03 + 0.016, 0.01, 0.015, 0x5a5a5e, Math.PI / 2, 0); } } };\n B.barricade = (M, a) => { const K = kit(M, a), { w, d, h } = K.dim('barricade'), wood = K.wood(0x6a4a34), z0 = -d / 2;\n K.box('wood', -w * 0.22, 0, z0 + 0.3, w * 0.5, h * 0.8, 0.45, wood, 0.06, -0.12); K.box('plain', -w * 0.22, h * 0.3, z0 + 0.54, 0.3, 0.012, 0.01, 0x9a9a9a);\n K.box('fabric', w * 0.2, 0, z0 + 0.4, w * 0.5, h * 0.95, 0.18, K.fabric(0xc8c0b0), -0.08, -0.28); K.box('fabric', w * 0.2, 0, z0 + 0.4, w * 0.5 - 0.1, h * 0.9, 0.19, K.accent(0x5a7a9a), -0.08, -0.28);\n K.box('plain', w * 0.38, 0, z0 + 0.35, 0.5, 0.4, 0.4, 0xb89a6a, 0.2); K.box('plain', w * 0.4, 0.4, z0 + 0.33, 0.45, 0.35, 0.38, 0xa8885a, -0.3);\n for (let i = 0; i < 4; i++) { K.box('wood', 0, 0.45 + i * 0.3, z0 + 0.62, w + 0.3, 0.14, 0.04, [0x7a5634, 0x8a6a44, 0x6b4a2a, 0x7a5a38][i], 0, 0, (i % 2 ? 1 : -1) * (0.06 + K.rnd() * 0.08)); for (const s of [-1, 1]) K.cyl('metal', s * w * 0.4, 0.45 + i * 0.3, z0 + 0.65, 0.012, 0.02, 0x5a5a5e, Math.PI / 2, 0); }\n K.box('wood', -w * 0.4, 0.0, z0 + 0.9, 0.4, 0.04, 0.4, wood, 0.3, 0, 0.4); K.box('wood', -w * 0.45, 0.0, z0 + 0.7, 0.04, 0.5, 0.04, wood, 0, 0.9, 0.2); };\n B.sandbags = (M, a) => { const K = kit(M, a), { w, d, h } = K.dim('sandbags'), cs = [K.fabric(0xb8a678), 0xa89868, 0xc4b284], bw = 0.46, bh = 0.17, nl = Math.max(1, Math.min(4, Math.round(h / bh)));\n for (let l = 0; l < nl; l++) { const nb = Math.max(1, Math.floor((w - l * 0.18) / bw)); for (let i = 0; i < nb; i++) { const x = -nb * bw / 2 + (i + 0.5) * bw + (l % 2) * 0.12; K.box('fabric', x, l * bh * 0.95, (K.rnd() - 0.5) * 0.04, bw * 0.97, bh, Math.min(d, 0.3), cs[(i + l) % 3], (K.rnd() - 0.5) * 0.08, 0, (K.rnd() - 0.5) * 0.04); K.box('fabric', x + bw * 0.46, l * bh * 0.95 + 0.04, 0, 0.03, 0.09, 0.2, shade(cs[(i + l) % 3], 0.8)); } } };\n B.barbed_wire = (M, a) => { const K = kit(M, a), { w } = K.dim('barbed_wire'), np = Math.max(2, Math.round(w / 2) + 1), wire = 0x8a8c90;\n for (let i = 0; i < np; i++) { const x = -w / 2 + i * w / (np - 1); K.box('wood', x, 0, 0, 0.07, 0.6, 0.07, 0x5a4a3a); }\n for (const y of [0.25, 0.4, 0.55]) { K.box('metal', 0, y, 0.04, w, 0.008, 0.008, wire); const nb = Math.floor(w / 0.16); for (let i = 0; i < nb; i++) { const x = -w / 2 + (i + 0.5) * w / nb; K.box('metal', x, y - 0.012, 0.04, 0.05, 0.006, 0.006, wire, 0, 0, 0.8); K.box('metal', x, y - 0.012, 0.04, 0.05, 0.006, 0.006, wire, 0, 0, -0.8); } } };\n B.razor_wire = (M, a) => { const K = kit(M, a), { w } = K.dim('razor_wire'), r = 0.28, wire = 0x9a9ca0, nr = Math.max(3, Math.round(w / 0.22)), ns = 8, sl = 2 * Math.PI * r / ns * 1.05, y0 = M.y || 0;\n for (let i = 0; i < nr; i++) { const x = -w / 2 + (i + 0.5) * w / nr, lean = (i % 2 ? 0.1 : -0.1); for (let s = 0; s < ns; s++) { const th = (s + 0.5) / ns * Math.PI * 2; K.box('metal', x + Math.sin(th) * lean * 0.3, y0 + r + Math.sin(th) * r - 0.006, Math.cos(th) * r, 0.01, 0.012, sl, wire, 0, -(th + Math.PI / 2), lean); } }\n for (const s of [-1, 1]) K.box('metal', s * w / 2, y0, 0, 0.03, 0.12, 0.03, 0x5a5a5e); };\n B.water_bottle_crates = (M, a) => { const K = kit(M, a), { w, d } = K.dim('water_bottle_crates'), pw = 0.4, pd = 0.28, ph = 0.27, nx = Math.max(1, Math.floor(w / pw)), nz = Math.max(1, Math.floor(d / pd)), nl = 2;\n K.box('wood', 0, 0, 0, nx * pw, 0.05, nz * pd, 0x8a6a44);\n for (let l = 0; l < nl; l++) for (let i = 0; i < nx; i++) for (let j = 0; j < nz; j++) { const x = -nx * pw / 2 + (i + 0.5) * pw, z = -nz * pd / 2 + (j + 0.5) * pd, y = 0.05 + l * ph; K.box('plain', x, y, z, pw - 0.01, ph, pd - 0.01, 0xdceaf4); K.box('plain', x, y + 0.08, z, pw - 0.005, 0.08, pd - 0.005, K.accent(0x2a6aa8)); if (l === nl - 1) for (let u = 0; u < 3; u++) for (let v = 0; v < 2; v++) K.cyl('plain', x - 0.11 + u * 0.11, y + ph, z - 0.06 + v * 0.12, 0.028, 0.02, 0x2a6aa8); } };\n B.canned_food = (M, a) => { const K = kit(M, a), y = M.y || 0, cs = [0xc8a030, 0xb03a2a, 0x4a7a3a, 0xd8d0b0, 0x8a3a2a], rw = [4, 3, 2];\n rw.forEach((n, r) => { for (let i = 0; i < n; i++) { const x = (i - (n - 1) / 2) * 0.072, c = cs[(i + r * 2) % cs.length]; K.cyl('plain', x, y + r * 0.1, 0, 0.033, 0.1, c); K.cyl('plain', x, y + r * 0.1 + 0.02, 0, 0.0345, 0.055, 0xe8e4d8); K.cyl('metal', x, y + r * 0.1 + 0.1, 0, 0.031, 0.004, 0xb8bcc0); } });\n K.cyl('plain', 0.3, y + 0.033, 0.1, 0.033, 0.1, 0xc8a030, 0, Math.PI / 2); K.cyl('plain', -0.32, y + 0.033, 0.12, 0.033, 0.1, 0xb03a2a, 0, 1.2); };\n B.water_jug = (M, a) => { const K = kit(M, a), y = M.y || 0, az = K.accent(0x5aa0d8);\n K.cyl('plain', 0, y, 0, 0.13, 0.32, az); K.cyl('plain', 0, y + 0.32, 0, 0.1, 0.05, az); K.cyl('plain', 0, y + 0.37, 0, 0.05, 0.08, az); K.cyl('plain', 0, y + 0.45, 0, 0.055, 0.035, 0x2a5a9a); K.cyl('plain', 0, y + 0.12, 0, 0.1325, 0.1, 0xf4f4f0); K.box('plain', 0.14, y + 0.22, 0, 0.04, 0.12, 0.05, shade(az, 0.85)); };\n B.floor_mattress = (M, a) => { const K = kit(M, a), { w, d } = K.dim('floor_mattress'), h = 0.17, c = K.fabric(0xc8c0b0), ac = K.accent(0x6a4a3a);\n K.box('fabric', 0, 0, 0, w, h, d, c); for (let i = 0; i < 4; i++) K.box('fabric', -w / 2 + (i + 0.5) * w / 4, h, 0, w / 8, 0.003, d - 0.04, shade(c, 0.86)); K.box('fabric', 0, h, -d / 2 + 0.28, w * 0.7, 0.1, 0.38, 0xf0ece0, 0.05);\n K.box('fabric', 0.03, h, 0.3, w * 0.95, 0.05, d * 0.55, ac, 0.05); K.box('fabric', 0.15, h + 0.05, 0.0, w * 0.5, 0.05, 0.4, shade(ac, 1.2), 0.3); K.box('fabric', -0.2, h + 0.05, 0.5, w * 0.3, 0.06, 0.3, shade(ac, 0.8), -0.4); };\n B.lantern = (M, a) => { const K = kit(M, a), y = M.y || 0;\n K.cyl('metal', 0, y, 0, 0.07, 0.03, 0x2a4a2a); K.cyl('emissive', 0, y + 0.03, 0, 0.055, 0.18, 0xffd890); for (const sx of [-1, 1]) for (const sz of [-1, 1]) K.box('metal', sx * 0.05, y + 0.03, sz * 0.05, 0.01, 0.18, 0.01, 0x2a2a2a);\n K.cyl('metal', 0, y + 0.21, 0, 0.075, 0.025, 0x2a4a2a); K.box('metal', 0, y + 0.235, 0, 0.1, 0.012, 0.012, 0x2a2a2a); for (const s of [-1, 1]) K.box('metal', s * 0.05, y + 0.235, 0, 0.01, 0.045, 0.01, 0x2a2a2a);\n if (M.lit !== false) K.light(0, y + 0.15, 0, 0xffc880, 7, 'lantern'); };\n B.candles = (M, a) => { const K = kit(M, a), y = M.y || 0;\n K.cyl('plain', 0, y, 0, 0.09, 0.012, 0xf0f0ea); [[0, 0, 0.16], [0.06, 0.03, 0.1], [-0.05, 0.04, 0.12], [0.02, -0.06, 0.07]].forEach(([x, z, h], i) => { K.cyl('plain', x, y + 0.012, z, 0.017, h, 0xf0e6c8); K.cyl('emissive', x, y + 0.012 + h, z, 0.008, 0.03, i % 2 ? 0xffc060 : 0xffd890, 0, 0, 'sphere'); });\n K.cyl('plain', 0.02, y + 0.012, 0.0, 0.05, 0.008, 0xf0e6c8); if (M.lit !== false) K.light(0, y + 0.22, 0, 0xffb060, 5, 'candles'); };\n B.open_suitcases = (M, a) => { const K = kit(M, a), cs = [K.main(0xb03a34), 0x2a3a5a], rs = [0xc8c0a8, 0x4a6a8a, 0xe0dcd0, 0xa8483a];\n const suitcase = (x: number, z: number, ww: number, dd: number, c: number, ry: number, al: number) => { const lx = x, hz = z - dd / 2;\n K.box('plain', lx, 0, z, ww, 0.16, dd, c, ry); K.box('fabric', lx, 0.12, z, ww - 0.06, 0.05, dd - 0.06, 0xd8d0c0, ry);\n for (let i = 0; i < 5; i++) K.box('fabric', lx + (K.rnd() - 0.5) * (ww - 0.2), 0.16, z + (K.rnd() - 0.5) * (dd - 0.2), 0.14 + K.rnd() * 0.1, 0.04 + K.rnd() * 0.04, 0.12 + K.rnd() * 0.1, rs[i % 4], K.rnd() * 3);\n K.ramp('plain', lx, 0.16, hz, ww, 0.07, dd, c, al); K.box('metal', lx + ww * 0.3, 0.1, z + dd / 2, 0.04, 0.03, 0.01, 0xc0c0c0); };\n suitcase(-0.4, 0, 0.75, 0.5, cs[0], 0.15, 1.35); suitcase(0.45, 0.1, 0.6, 0.42, cs[1], -0.3, 1.2); };\n B.clothes_on_floor = (M, a) => { const K = kit(M, a), { w, d } = K.dim('clothes_on_floor'), cs = [0xc8c0a8, 0x4a6a8a, 0xa8483a, 0xe0dcd0, 0x6a8a5a, 0x2a2a30, K.accent(0xc89a4a)];\n for (let i = 0; i < 9; i++) { const x = (K.rnd() - 0.5) * (w - 0.3), z = (K.rnd() - 0.5) * (d - 0.3), r = K.rnd() * 6, c = cs[i % cs.length], y = 0.012 + i * 0.003;\n if (i % 3 === 0) { K.box('fabric', x, y, z, 0.3, 0.012, 0.36, c, r); for (const s of [-1, 1]) K.box('fabric', x + Math.cos(r) * s * 0.2, y, z - Math.sin(r) * s * 0.2, 0.2, 0.012, 0.09, shade(c, 0.9), r + s * 0.5); }\n else if (i % 3 === 1) { for (const s of [-1, 1]) K.box('fabric', x + s * 0.06, y, z, 0.13, 0.014, 0.5, 0x3a4a6a, r + s * 0.15); } else K.box('fabric', x, y, z, 0.14, 0.01, 0.07, c, r); }\n const sx = (K.rnd() - 0.5) * (w - 0.4), sz = (K.rnd() - 0.5) * (d - 0.4); K.box('plain', sx, 0.012, sz, 0.09, 0.07, 0.27, 0x2a2a2a, 0.4); K.box('plain', sx + 0.15, 0.012, sz + 0.05, 0.09, 0.07, 0.27, 0x2a2a2a, 0.9); };\n B.fallen_chair = (M, a) => { // wooden chair knocked over on its side: the parts are axis-aligned boxes, rotated 90 degrees around z\n const g = Math.PI / 2, dy = 0.23, api = { box: (Mm: PropItem, mat: Material, x: number, y: number, z: number, w: number, h: number, d: number, color: number, ry: number, rx: number, rz?: number) => { const cy = y + h / 2; a.box(Mm, mat, -cy + 0.0, dy + x - h / 2 + 0.0 - 0.0, z, w, h, d, color, ry, rx, g + (rz || 0)); } };\n const K = kit(M, api as unknown as PropsApi), c = K.main(0x6a4a30); // center (x, y, z) becomes (-cy, x): the api y is the base, so h/2 is subtracted\n K.box('wood', 0, 0.44, 0, 0.42, 0.04, 0.42, c); for (const sx of [-1, 1]) for (const sz of [-1, 1]) K.box('wood', sx * 0.18, 0, sz * 0.18, 0.035, 0.44, 0.035, shade(c, 0.85));\n K.box('wood', 0, 0.48, -0.19, 0.42, 0.45, 0.03, c); };\n B.blood_stains = (M, a) => { const K = kit(M, a), { w, d } = K.dim('blood_stains');\n for (let k = 0; k < 7; k++) K.cyl('plain', (K.rnd() - 0.5) * w * 0.3, 0.012 + k * 0.0004, (K.rnd() - 0.5) * d * 0.3, 0.12 + K.rnd() * 0.18, 0.003, k < 2 ? 0x3a0806 : 0x5a100c);\n for (let k = 0; k < 8; k++) { const an = K.rnd() * 6.28, r = 0.3 + K.rnd() * Math.min(w, d) * 0.4; K.cyl('plain', Math.cos(an) * r, 0.013 + k * 0.0002, Math.sin(an) * r, 0.02 + K.rnd() * 0.05, 0.003, 0x4a0a08); }\n if (M.drag !== false) for (let k = 1; k < 6; k++) K.box('plain', 0.3 + k * 0.1, 0.012, 0.15 + k * 0.17, 0.16 - k * 0.015, 0.003, 0.2, 0x4a0c0a, 0.5); };\n B.open_door = (M, a) => { const K = kit(M, a), { w, d, h } = K.dim('open_door'), wood = K.wood(0x6a4a2c), angle = M.angle != null ? M.angle : 1.2, L = w - 0.06, hx = -w / 2 + 0.03;\n K.box('wood', -w / 2 - 0.03, 0, 0, 0.06, h + 0.06, d, shade(wood, 0.8)); K.box('wood', w / 2 + 0.03, 0, 0, 0.06, h + 0.06, d, shade(wood, 0.8)); K.box('wood', 0, h, 0, w + 0.12, 0.06, d, shade(wood, 0.8));\n K.box('wood', hx + Math.cos(angle) * L / 2, 0.0, Math.sin(angle) * L / 2, L, h - 0.02, 0.04, wood, -angle); K.cyl('metal', hx + Math.cos(angle) * (L - 0.08), 1.0, Math.sin(angle) * (L - 0.08) + 0.04, 0.025, 0.06, 0xc8b070, Math.PI / 2, 0);\n for (const y of [0.2, 1.0, 1.8]) K.box('metal', hx, y, 0.0, 0.02, 0.1, 0.02, 0x8a8a8a); };\n return B\n}\n","import type { BuilderContext, BuilderMap } from '../types'\nimport { shade } from '../colors'\nimport type { Kit } from '../types'\nimport { green } from './shared'\n\nexport function plantBuilders({ kit }: BuilderContext): Partial<BuilderMap> {\n const B: Partial<BuilderMap> = {}\n const foliage = (K: Kit, x: number, y: number, z: number, n: number, w: number, h0: number, h1: number, tilt0: number, tilt1: number, twist = 2.4) => { for (let i = 0; i < n; i++) { const an = i * twist + K.rnd() * 0.5, t = tilt0 + (tilt1 - tilt0) * K.rnd(); K.leaf(x + Math.cos(an) * 0.03, y, z + Math.sin(an) * 0.03, w * (0.8 + K.rnd() * 0.4), h0 + (h1 - h0) * K.rnd(), green(K, i), an, t); } };\n B.floor_plant = (M, a) => { const K = kit(M, a), { w } = K.dim('floor_plant'), r = Math.min(0.24, w * 0.4), pot = K.main(0xb5603c);\n K.cyl('plain', 0, 0, 0, r, 0.38, pot); K.cyl('plain', 0, 0.34, 0, r * 1.07, 0.05, shade(pot, 0.92)); K.cyl('plain', 0, 0.385, 0, r * 0.92, 0.012, 0x3a2a1e);\n foliage(K, 0, 0.39, 0, 11, 0.2, 0.5, 0.85, 0.2, 0.85); };\n B.table_plant = (M, a) => { const K = kit(M, a), y = M.y || 0, pot = K.main(0xf0eee8);\n K.cyl('plain', 0, y, 0, 0.05, 0.08, pot); K.cyl('plain', 0, y + 0.08, 0, 0.045, 0.008, 0x3a2a1e); foliage(K, 0, y + 0.085, 0, 7, 0.07, 0.1, 0.2, 0.2, 0.9); };\n B.hanging_plant = (M, a) => { const K = kit(M, a), t = K.ceiling(), h = K.dim('hanging_plant').h, yp = t - h, pot = K.main(0xf0eee8), rope = K.accent(0xd8ccb0);\n K.box('metal', 0, t - 0.025, 0, 0.03, 0.025, 0.03, 0x8a8a8a); for (let i = 0; i < 3; i++) { const an = i * 2.09; K.box('fabric', Math.cos(an) * 0.08, yp + 0.12, Math.sin(an) * 0.08, 0.01, t - yp - 0.15, 0.01, rope); }\n K.cyl('plain', 0, yp, 0, 0.1, 0.14, pot); K.cyl('plain', 0, yp + 0.135, 0, 0.09, 0.01, 0x3a2a1e);\n for (let i = 0; i < 11; i++) { const an = i * 2.4, up = i < 3; K.leaf(Math.cos(an) * 0.06, yp + 0.14, Math.sin(an) * 0.06, 0.09, up ? 0.22 : 0.4 + K.rnd() * 0.3, green(K, i), an, up ? 0.5 : 2.3 + K.rnd() * 0.3); } };\n return B\n}\n","import type { BuilderContext, BuilderMap } from '../types'\nimport type { PropType } from '../catalog'\nimport { hex, shade } from '../colors'\nimport type { Kit } from '../types'\nimport { drawTv, pick } from './shared'\n\nexport function wallBuilders({ kit, atlas }: BuilderContext): Partial<BuilderMap> {\n const B: Partial<BuilderMap> = {}\n B.wall_ac = (M, a) => { const K = kit(M, a), { w, d, h } = K.dim('wall_ac'), y = M.y || 2.15;\n K.box('plain', 0, y, 0, w, h, d, 0xf0f0ee); K.box('plain', 0, y + h - 0.02, d * 0.1, w - 0.04, 0.02, d * 0.8, 0xe6e6e2); // curved cover\n K.box('plain', 0, y + 0.02, d / 2 - 0.02, w - 0.1, 0.035, 0.04, 0x30343a); K.box('plain', 0, y - 0.005, d / 2 - 0.03, w - 0.12, 0.012, 0.07, 0xe6e6e2, 0, 0.45); // slot and flap\n K.box('plain', w / 2 - 0.1, y + h - 0.1, d / 2 + 0.001, 0.05, 0.02, 0.004, 0x2a3a30); K.box('plain', -w / 2 + 0.01, y + 0.02, 0.0, 0.01, h - 0.04, d - 0.02, 0xdcdcd8); };\n const frame = (K: Kit, id: PropType, def: number, large: boolean) => { const { w, d, h } = K.dim(id), y = K.M.y || def, fr = Math.min(w, h) * 0.07 + 0.008, margin = large ? 0.05 : 0.035, A = atlas('picture'), idx = K.M.art != null ? K.M.art % 7 : Math.floor(K.rnd() * 7);\n const cm = K.color('picture_accent', pick(K, [0x4a3324, 0x1c1c1c, 0xe8e2d4, 0x8a6a44])); K.box('wood', 0, y, 0, w, h, d, K.M.color != null ? hex(K.M.color) : cm);\n K.box('plain', 0, y + fr, d / 2 + 0.0005, w - 2 * fr, h - 2 * fr, 0.004, 0xf4f1e8); K.plane('atlas_picture', 0, y + h / 2, d / 2 + 0.005, w - 2 * fr - 2 * margin, h - 2 * fr - 2 * margin, 0xffffff, 0, 0, 0, A.uv(idx)); };\n B.picture_small = (M, a) => frame(kit(M, a), 'picture_small', 1.3, false);\n B.picture_large = (M, a) => frame(kit(M, a), 'picture_large', 1.2, true);\n B.mirror = (M, a) => { const K = kit(M, a), { w, d, h } = K.dim('mirror'), y = M.y || 0.9, A = atlas('picture'), cm = K.color('metal', 0x3a3a3c);\n K.box('metal', 0, y, 0, w, h, d, M.color != null ? hex(M.color) : cm); K.plane('atlas_picture', 0, y + h / 2, d / 2 + 0.003, w - 0.05, h - 0.05, 0xffffff, 0, 0, 0, A.mirror()); };\n B.curtain = (M, a) => { const K = kit(M, a), { w, h } = K.dim('curtain'), y = M.y != null && M.y > 0 ? M.y : 0.1, color = K.fabric(pick(K, [0xd8cdb4, 0x9fb0be, 0xc5a88c, 0xe0dcd0])), isOpen = M.open !== false, top = y + h;\n K.box('metal', 0, top, 0, w + 0.1, 0.025, 0.025, K.metal(0x5a5048)); for (const s of [-1, 1]) K.cyl('metal', s * (w / 2 + 0.07), top - 0.02, 0, 0.03, 0.07, 0x5a5048, 0, 0, 'sphere');\n const pw = isOpen ? Math.min(0.5, w * 0.3) : w / 2 + 0.03, n = Math.max(4, Math.round(pw / 0.08));\n for (const s of [-1, 1]) for (let i = 0; i < n; i++) { const ux = isOpen ? s * (w / 2 + 0.03 - pw + (i + 0.5) * pw / n) : s * ((i + 0.5) * pw / n); K.box('fabric', ux, y, (i % 2 ? 0.025 : -0.01), pw / n * 1.15, h - 0.02, 0.02, i % 2 ? shade(color, 0.9) : color); }\n if (isOpen) for (const s of [-1, 1]) K.box('fabric', s * (w / 2 - pw * 0.25), y + h * 0.4, 0.04, pw * 0.5, 0.05, 0.02, shade(color, 0.7)); }; // tie-back\n B.blinds = (M, a) => { const K = kit(M, a), { w, h } = K.dim('blinds'), y = M.y || 1.0, retract = M.retracted != null ? M.retracted : 0.15, isWood = M.variant === 'wood', color = isWood ? K.wood(0x9a7a52) : K.color('wall_living', 0xe8e2d4), mat = isWood ? 'wood' : 'plain', yb = y + h * retract, n = Math.floor((y + h - yb) / 0.055);\n K.box('plain', 0, y + h - 0.05, 0, w, 0.05, 0.06, 0xdcd8d0); for (let i = 0; i < n; i++) K.box(mat, 0, y + h - 0.06 - (i + 1) * 0.055, 0.0, w - 0.05, 0.01, 0.035, color, 0, 0.3);\n for (const s of [-1, 1]) K.box('plain', s * (w / 2 - 0.05), yb, 0.02, 0.008, y + h - 0.05 - yb, 0.008, 0xb8b4ac); K.box('plain', 0, yb - 0.02, 0, w - 0.03, 0.025, 0.04, 0xdcd8d0); };\n B.flat_tv = (M, a) => { const K = kit(M, a), { w } = K.dim('flat_tv'); drawTv(K, 0, M.y != null && M.y > 0 ? M.y : 1.0, 0, w, false); };\n return B\n}\n","import type { BuilderContext, BuilderMap } from '../types'\nimport { hex, shade } from '../colors'\nimport { green, pick } from './shared'\n\nexport function yardBuilders({ kit, cover }: BuilderContext): Partial<BuilderMap> {\n const B: Partial<BuilderMap> = {}\n B.ac_condenser = (M, a) => { const K = kit(M, a), { w, d, h } = K.dim('ac_condenser'), y = M.y != null && M.y > 0 ? M.y : 0.5;\n for (const s of [-1, 1]) { K.box('metal', s * w * 0.32, y - 0.06, -d / 2 - 0.2, 0.04, 0.04, 0.3, 0x6a6a6a); K.box('metal', s * w * 0.32, y - 0.5 + 0.06, -d / 2 - 0.2 - 0.0, 0.035, 0.46, 0.035, 0x6a6a6a, 0, 0.0); }\n K.box('plain', 0, y, 0, w, h, d, 0xe8e8e4); K.box('plain', 0, y + h - 0.02, 0, w + 0.02, 0.025, d + 0.02, 0xd8d8d4);\n K.cyl('metal', -w * 0.12, y + h / 2, d / 2 + 0.004, 0.2, 0.012, 0x2a2a2e, Math.PI / 2, 0); K.cyl('metal', -w * 0.12, y + h / 2, d / 2 + 0.012, 0.085, 0.02, 0x4a4a4e, Math.PI / 2, 0);\n for (let i = 0; i < 4; i++) K.box('metal', -w * 0.12, y + h / 2 - 0.2, d / 2 + 0.018, 0.4, 0.008, 0.008, 0x8a8a8a, 0, 0, i * 0.785);\n for (let i = 0; i < 3; i++) K.cyl('metal', -w * 0.12, y + h / 2, d / 2 + 0.02, 0.07 + i * 0.043, 0.006, 0x7a7a7e, Math.PI / 2, 0);\n for (let i = 0; i < 6; i++) K.box('plain', w * 0.33, y + 0.06 + i * 0.075, d / 2 + 0.003, 0.14, 0.03, 0.006, 0xc4c4c0);\n K.cyl('metal', w / 2 - 0.05, y + 0.1, -d / 2 - 0.08, 0.012, 0.18, 0xb8733c, Math.PI / 2, 0); K.box('plain', w / 2 - 0.05, y + 0.06, -d / 2 - 0.02, 0.05, 0.12, 0.05, 0xe0e0dc); };\n B.pool = (M, a) => { const K = kit(M, a), { w, d } = K.dim('pool'), t = 0.32, bo = K.color('exterior_floor', 0xe8e4da);\n K.box('plain', 0, 0, -d / 2 - t / 2, w + 2 * t, 0.1, t, bo); K.box('plain', 0, 0, d / 2 + t / 2, w + 2 * t, 0.1, t, bo); for (const s of [-1, 1]) K.box('plain', s * (w / 2 + t / 2), 0, 0, t, 0.1, d, bo);\n cover(a, M, 'pool_tile', { w, d, y: 0.035, size: 1.0 });\n K.plane('water', 0, 0.075, 0, w, d, 0x8fd4ee, 0, -Math.PI / 2, 0);\n for (let i = 0; i < 3; i++) K.box('plain', w / 2 - 0.35 + i * 0.0, 0.035 + i * 0.01, d / 2 - 0.2 - i * 0.28, 0.7 - i * 0.05, 0.03, 0.26, 0xdfeef2);\n for (const s of [-1, 1]) { K.box('metal', -w / 4 + s * 0.2, 0.0, d / 2 + t * 0.6, 0.03, 0.85, 0.03, 0xc8ccd0); K.box('metal', -w / 4 + s * 0.2, 0.85, d / 2 + t * 0.6 - 0.1, 0.03, 0.03, 0.22, 0xc8ccd0); K.box('metal', -w / 4 + s * 0.2, 0.0, d / 2 - 0.25, 0.03, 0.88, 0.03, 0xc8ccd0, 0, 0.0); }\n for (let i = 0; i < 3; i++) K.box('metal', -w / 4, 0.02, d / 2 - 0.08 - i * 0.07, 0.4, 0.015, 0.05, 0xc8ccd0); };\n B.deck = (M, a) => { const K = kit(M, a), { w, d } = K.dim('deck'), h = 0.2, wood = K.wood(0x6a4a30);\n K.box('wood', 0, 0, 0, w - 0.05, h, d - 0.05, shade(wood, 0.7)); cover(a, M, 'deck_floor', { w, d, y: h + 0.012, size: 1.2, color: M.color });\n for (const [x, z, ww, dd] of [[0, -d / 2, w, 0.05], [0, d / 2, w, 0.05], [-w / 2, 0, 0.05, d], [w / 2, 0, 0.05, d]]) K.box('wood', x, 0, z, ww, h + 0.02, dd, wood);\n K.box('wood', 0, 0, d / 2 + 0.17, 1.4, 0.1, 0.34, wood); K.box('wood', 0, 0.1, d / 2 + 0.17, 1.4, 0.012, 0.34, shade(wood, 1.2)); };\n B.sun_lounger = (M, a) => { const K = kit(M, a), { w } = K.dim('sun_lounger'), fr = K.metal(0xe8e8e4), al = 0.95, cu = K.fabric(0x3a6a9a), stripe = K.accent(0xf0ece0);\n for (const s of [-1, 1]) { K.box('metal', s * (w / 2 - 0.03), 0.17, -0.3, 0.035, 0.035, 1.35, fr); K.box('metal', s * (w / 2 - 0.03), 0.0, 0.95, 0.035, 0.2, 0.035, fr); K.box('metal', s * (w / 2 - 0.03), 0.0, -0.3, 0.035, 0.2, 0.035, fr); K.cyl('plain', s * (w / 2 - 0.03), 0.07, -0.3, 0.07, 0.03, 0x2a2a2a, 0, Math.PI / 2); }\n K.box('fabric', 0, 0.21, 0.35, w - 0.1, 0.06, 1.3, cu); K.ramp('fabric', 0, 0.2, -0.3, w - 0.1, 0.06, 0.8, cu, al);\n for (let i = 0; i < 4; i++) { K.box('fabric', 0, 0.265, 0.0 + i * 0.3, w - 0.098, 0.004, 0.08, stripe); } K.ramp('metal', w / 2 - 0.03, 0.18, -0.3, 0.03, 0.03, 0.5, fr, al - 0.3); K.ramp('metal', -w / 2 + 0.03, 0.18, -0.3, 0.03, 0.03, 0.5, fr, al - 0.3); };\n B.garden_bed = (M, a) => { const K = kit(M, a), { w, d } = K.dim('garden_bed'), wood = K.wood(0x9a7048), hb = 0.3;\n for (const s of [-1, 1]) { K.box('wood', 0, 0, s * (d / 2 - 0.015), w, hb, 0.03, wood); K.box('wood', s * (w / 2 - 0.015), 0, 0, 0.03, hb, d - 0.06, shade(wood, 0.9)); }\n K.box('plain', 0, 0.02, 0, w - 0.06, hb - 0.05, d - 0.06, 0x3a2a1e);\n const lin = d > 0.8 ? 3 : 2, n = Math.max(3, Math.round(w / 0.4));\n for (let r = 0; r < lin; r++) for (let i = 0; i < n; i++) { const x = -w / 2 + (i + 0.5) * w / n, z = (r - (lin - 1) / 2) * 0.3;\n if (r === 0) { for (let j = 0; j < 6; j++) K.leaf(x, hb - 0.02, z, 0.11, 0.14, green(K, j) , j * 1.05, 0.9); }\n else if (r === 1) { K.box('wood', x, hb - 0.03, z, 0.015, 0.75, 0.015, 0x8a7048); K.cyl('plant', x, hb + 0.05, z, 0.1, 0.28, green(K, i), 0, 0, 'sphere'); K.cyl('plain', x + 0.05, hb + 0.18, z + 0.07, 0.03, 0.06, 0xc83030, 0, 0, 'sphere'); K.cyl('plain', x - 0.06, hb + 0.3, z + 0.05, 0.03, 0.06, 0xd04030, 0, 0, 'sphere'); }\n else { K.cyl('plant', x, hb - 0.02, z, 0.1, 0.17, 0x8ab05a, 0, 0, 'sphere'); for (let j = 0; j < 5; j++) K.leaf(x, hb, z, 0.14, 0.2, green(K, j + 1), j * 1.3, 1.0); } } };\n B.fruit_tree = (M, a) => { const K = kit(M, a), { w, h } = K.dim('fruit_tree'), fruit = M.fruit || pick(K, [0xe8801c, 0xe8a030, 0xb8c050, 0xc83030, 0xd8d040]), tr = K.wood(0x6a4a30);\n K.cyl('wood', 0, 0, 0, 0.13, h * 0.5, tr); K.box('wood', 0.2, h * 0.35, 0, 0.08, 0.6, 0.08, tr, 0, 0, -0.7); K.box('wood', -0.2, h * 0.35, 0.05, 0.08, 0.55, 0.08, tr, 0, 0, 0.7);\n const bl = [[0, h * 0.72, 0, 1.0], [0.55, h * 0.62, 0.2, 0.7], [-0.55, h * 0.64, -0.1, 0.72], [0.1, h * 0.6, -0.55, 0.65], [0.1, h * 0.9, 0.1, 0.6]];\n bl.forEach(([x, y, z, r], i) => K.cyl('plant', x * w / 2.4, y - r * 0.4, z * w / 2.4, r * w / 2.4, r * 1.5 * w / 2.4, green(K, i), 0, 0, 'sphere'));\n for (let i = 0; i < 16; i++) { const b = bl[i % 4], an = K.rnd() * 6.28, el = K.rnd() * 0.9, r = b[3] * w / 2.4 * 0.95; K.cyl('plain', b[0] * w / 2.4 + Math.cos(an) * r * Math.cos(el), b[1] * 1.0 - b[3] * 0.4 + Math.sin(el) * r * 0.5, b[2] * w / 2.4 + Math.sin(an) * r * Math.cos(el), 0.055, 0.11, fruit, 0, 0, 'sphere'); } };\n B.palm_tree = (M, a) => { const K = kit(M, a), { h } = K.dim('palm_tree'), tr = K.wood(0x8a7a5a), lean = 0.35, n = 9, sh = h * 0.78 / n; let x = 0, y = 0;\n for (let i = 0; i < n; i++) { const r = 0.14 - i * 0.006; K.cyl('wood', x, y, 0, r, sh, i % 2 ? tr : shade(tr, 0.82)); y += sh; x += lean * (2 * i + 1) / (n * n) * 1.2; }\n const top = y;\n for (let i = 0; i < 10; i++) { const an = i * 0.628 + K.rnd() * 0.2, t1 = 0.7 + K.rnd() * 0.4, L = 1.5, w = 0.5; K.leaf(x, top, 0, w, L, green(K, i), an, t1);\n const ex = x + Math.sin(t1) * Math.sin(an) * L, ey = top + Math.cos(t1) * L, ez = Math.sin(t1) * Math.cos(an) * L; K.leaf(ex, ey, ez, w * 0.7, 0.9, green(K, i + 1), an, t1 + 1.0); }\n for (let i = 0; i < 3; i++) K.cyl('wood', x + Math.cos(i * 2.1) * 0.14, top - 0.2, Math.sin(i * 2.1) * 0.14, 0.09, 0.2, 0x6a5a3a, 0, 0, 'sphere'); };\n B.pergola = (M, a) => { const K = kit(M, a), { w, d, h } = K.dim('pergola'), wood = M.variant === 'dark' ? shade(K.wood(0x6a4a30), 0.8) : K.color('exterior_floor', 0xe8e2d4), pc = M.color != null ? hex(M.color) : wood;\n for (const sx of [-1, 1]) for (const sz of [-1, 1]) K.box('wood', sx * (w / 2 - 0.1), 0, sz * (d / 2 - 0.1), 0.14, h, 0.14, pc);\n for (const sz of [-1, 1]) K.box('wood', 0, h - 0.04, sz * (d / 2 - 0.1), w + 0.2, 0.16, 0.08, pc); const n = Math.round(w / 0.4);\n for (let i = 0; i <= n; i++) K.box('wood', -w / 2 - 0.1 + i * (w + 0.2) / n, h + 0.12, 0, 0.06, 0.08, d + 0.3, shade(pc, 0.92));\n for (let i = 0; i < 9; i++) K.leaf(-w / 2 + 0.1 + (i % 2) * 0.06, 0.2 + i * 0.25, -d / 2 + 0.1, 0.2, 0.28, green(K, i), i * 1.7, 0.7); for (let i = 0; i < 7; i++) K.leaf(-w / 2 + 0.2 + i * 0.35, h + 0.2, -d / 4 + K.rnd() * 0.3, 0.22, 0.3, green(K, i), i * 2.0, 1.3); };\n B.slide = (M, a) => { const K = kit(M, a), { d, h } = K.dim('slide'), py = h * 0.8, pz = -d / 2 + 0.55, az = K.accent(0xe8c030), vm = K.main(0xc8403a), az2 = 0x3a6ab0;\n for (const sx of [-1, 1]) for (const sz of [-1, 1]) K.box('metal', sx * 0.3, 0, pz + sz * 0.3, 0.05, py, 0.05, az2);\n K.box('plain', 0, py, pz, 0.74, 0.05, 0.74, vm); for (const s of [-1, 1]) { K.box('plain', s * 0.35, py + 0.05, pz, 0.04, 0.42, 0.74, az); }\n K.box('plain', 0, py + 0.05, pz - 0.35, 0.74, 0.42, 0.04, az); const run = d / 2 - pz - 0.4, al = Math.atan2(py - 0.1, run);\n K.ramp('plain', 0, py, pz + 0.37, 0.5, 0.04, Math.hypot(run, py - 0.1) + 0.1, 0xd8d0c0, -al + 0.0 + 0); for (const s of [-1, 1]) K.ramp('plain', s * 0.27, py + 0.03, pz + 0.37, 0.04, 0.12, Math.hypot(run, py - 0.1) + 0.1, az, -al);\n for (const s of [-1, 1]) K.box('metal', s * 0.28, 0, pz - 0.45, 0.04, py + 0.1, 0.04, az2, 0, -0.1 + 0); for (let i = 0; i < 5; i++) K.box('metal', 0, 0.2 + i * (py - 0.2) / 5, pz - 0.46, 0.56, 0.03, 0.03, 0xc8ccd0); K.box('plain', 0, py + 0.4, pz, 0.8, 0.04, 0.8, vm); };\n B.swing = (M, a) => { const K = kit(M, a), { w, d, h } = K.dim('swing'), mt = K.color('metal', 0x3a6ab0), L = Math.hypot(h, d / 2), an = Math.atan2(d / 2, h);\n for (const sx of [-1, 1]) for (const sz of [-1, 1]) K.box('metal', sx * w / 2, h / 2 - L / 2 + 0.0, sz * d / 4, 0.07, L, 0.07, mt, 0, -sz * an, 0);\n K.box('metal', 0, h - 0.04, 0, w + 0.1, 0.1, 0.1, shade(mt, 0.85)); const cs = [K.accent(0xc8403a), 0x2a2a2a];\n for (let i = 0; i < 2; i++) { const x = (i - 0.5) * w * 0.45, sy = 0.5, sw = 0.46; for (const s of [-1, 1]) K.box('metal', x + s * (sw / 2 - 0.02), sy, 0, 0.012, h - 0.05 - sy, 0.012, 0xa0a4a8); K.box('plain', x, sy - 0.04, 0, sw, 0.045, 0.17, cs[i]); } };\n B.bbq_simple = (M, a) => { const K = kit(M, a), { w, d } = K.dim('bbq_simple'), br = K.color('exterior_floor', 0xb25a40), hb = 0.78;\n K.box('plain', 0, 0, 0, w, 0.45, d, br); for (const [x, z, ww, dd] of [[0, -d / 2 + 0.06, w, 0.12], [0, d / 2 - 0.06, w, 0.12], [-w / 2 + 0.06, 0, 0.12, d - 0.2], [w / 2 - 0.06, 0, 0.12, d - 0.2]]) K.box('plain', x, 0.45, z, ww, hb - 0.45, dd, br);\n K.box('plain', 0, 0.45, 0, w - 0.22, 0.04, d - 0.22, 0x2a2624); for (let i = 0; i < 10; i++) K.box('plain', (K.rnd() - 0.5) * (w - 0.3), 0.49, (K.rnd() - 0.5) * (d - 0.3), 0.08, 0.02, 0.07, i % 3 ? 0xc84a1a : 0x3a2a24, K.rnd() * 3);\n for (let i = 0; i < 7; i++) K.box('metal', -w / 2 + 0.16 + i * (w - 0.32) / 6, hb + 0.01, 0, 0.012, 0.012, d - 0.1, 0x5a5a5e); K.box('metal', 0, hb + 0.01, -d / 2 + 0.2, w - 0.1, 0.012, 0.02, 0x5a5a5e);\n for (let i = 0; i < 3; i++) { const z = (i - 1) * 0.15; K.cyl('metal', 0, hb + 0.05, z, 0.006, w + 0.12, 0xb0b4b8, 0, Math.PI / 2); for (let j = 0; j < 3; j++) K.cyl('plain', -w * 0.25 + j * w * 0.22, hb + 0.05, z, 0.035, 0.07, [0x8a3a2a, 0xa8483a, 0xd8b090][j], 0, Math.PI / 2); }\n for (let i = 0; i < 4; i++) K.cyl('wood', w / 2 + 0.2, 0.05 + (i > 2 ? 0.1 : 0), -0.1 + (i % 3) * 0.1, 0.04, 0.3, 0x8a6a44, 0, Math.PI / 2); };\n B.garage_car = (M, a) => { const K = kit(M, a), { w, d } = K.dim('garage_car'), c = K.main(pick(K, [0xa8aaac, 0xe8e8e6, 0x2a3a5a, 0xa83a34, 0x222426])), zf = d / 2;\n K.box('plain', 0, 0.26, 0, w, 0.5, d, c); K.box('plain', 0, 0.5, zf - 0.7, w - 0.04, 0.1, 1.4, shade(c, 1.04), 0, 0.04); K.box('plain', 0, 0.76, -zf + 0.3, w - 0.04, 0.1, 0.6, c);\n const cz = -0.35; for (const sx of [-1, 1]) for (const sz of [-1, 1]) K.box('plain', sx * (w / 2 - 0.1), 0.76, cz + sz * 0.8, 0.07, 0.5, 0.07, shade(c, 0.9)); K.box('plain', 0, 1.26, cz, w - 0.1, 0.05, 1.75, shade(c, 1.04));\n K.box('glass', 0, 0.76, cz, w - 0.06, 0.5, 1.6, 0xffffff); K.box('glass', 0, 0.76, cz + 0.8, w - 0.12, 0.5, 0.04, 0xffffff, 0, 0.5); for (const s of [-1, 1]) { K.box('plain', s * 0.4, 0.55, cz + 0.4, 0.4, 0.3, 0.4, 0x2a2a2e); K.box('plain', s * 0.4, 0.55, cz - 0.45, 0.4, 0.3, 0.4, 0x2a2a2e); }\n for (const sx of [-1, 1]) for (const sz of [-1, 1]) { K.cyl('plain', sx * (w / 2 - 0.1), 0.3, sz * 1.3, 0.3, 0.2, 0x1a1a1a, 0, Math.PI / 2); K.cyl('metal', sx * (w / 2 - 0.0), 0.3, sz * 1.3, 0.17, 0.02, 0xb8bcc0, 0, Math.PI / 2); }\n for (const s of [-1, 1]) { K.box('plain', s * 0.62, 0.5, zf - 0.02, 0.32, 0.12, 0.05, 0xf4f0d8); K.box('plain', s * 0.62, 0.52, -zf + 0.02, 0.3, 0.1, 0.05, 0xb02a24); K.box('plain', s * (w / 2 + 0.03), 0.9, cz + 0.55, 0.1, 0.1, 0.14, shade(c, 0.9)); }\n K.box('plain', 0, 0.2, zf - 0.01, w, 0.16, 0.06, 0x2a2a2a); K.box('plain', 0, 0.2, -zf + 0.01, w, 0.16, 0.06, 0x2a2a2a); K.box('plain', 0, 0.32, zf + 0.02, 0.5, 0.16, 0.02, 0x1c1c1c); K.box('plain', 0, 0.28, -zf - 0.015, 0.4, 0.12, 0.01, 0xf0e8c0); };\n B.gourmet_counter = (M, a) => { const K = kit(M, a), { w, d } = K.dim('gourmet_counter'), br = K.color('exterior_floor', 0xa8553c), zc = -d / 2 + 0.35, xa = -w / 2 + 0.8, xb = xa + 0.8 + 0.72, xc = xb + 0.72 + 0.65, wood = K.wood(0x8a5a38), gr = 0x4a4844, cab = K.color('wall_kitchen', 0xe8e4dc);\n K.box('plain', xa, 0, zc, 1.6, 0.85, 0.7, br); for (let i = 1; i < 4; i++) K.box('plain', xa, i * 0.2, zc + 0.351, 1.6, 0.012, 0.004, shade(br, 0.7)); K.box('plain', xa, 0.12, zc + 0.352, 0.7, 0.4, 0.004, 0x1a1612);\n for (const [x, z, ww, dd] of [[xa, zc - 0.25, 1.6, 0.2], [xa, zc + 0.3, 1.6, 0.1], [xa - 0.75, zc, 0.1, 0.7], [xa + 0.75, zc, 0.1, 0.7]]) K.box('plain', x, 0.85, z, ww, 0.12, dd, br);\n K.box('plain', xa, 0.85, zc + 0.02, 1.4, 0.025, 0.4, 0x2a2624); for (let i = 0; i < 8; i++) K.box('metal', xa - 0.62 + i * 0.18, 0.97, zc + 0.02, 0.012, 0.012, 0.4, 0x5a5a5e); for (let i = 0; i < 6; i++) K.box('plain', xa + (K.rnd() - 0.5) * 1.2, 0.88, zc + (K.rnd() - 0.5) * 0.3, 0.1, 0.02, 0.08, i % 2 ? 0xc84a1a : 0x3a2a24, K.rnd() * 3);\n K.box('plain', xa, 1.5, zc - 0.07, 1.5, 0.4, 0.56, br); K.box('plain', xa, 1.5, zc + 0.215, 1.5, 0.4, 0.01, 0x1a1612); K.box('plain', xa, 1.9, zc - 0.12, 0.62, 1.0, 0.46, br); K.box('plain', xa, 2.9, zc - 0.12, 0.76, 0.08, 0.6, shade(br, 0.8)); K.box('plain', xa, 2.98, zc - 0.12, 0.5, 0.06, 0.4, shade(br, 0.75));\n K.box('plain', xb, 0, zc, 1.44, 0.85, 0.7, cab); for (let i = 0; i < 3; i++) K.box('wood', xb - 0.48 + i * 0.48, 0.08, zc + 0.355, 0.44, 0.7, 0.012, wood); K.box('plain', (xb + xc) / 2, 0.85, zc, 0.72 * 2 + 0.65 + 0.04, 0.04, 0.74, gr);\n K.box('metal', xb, 0.89, zc, 0.5, 0.01, 0.38, 0xb0b4b8); K.box('metal', xb, 0.895, zc, 0.44, 0.006, 0.32, 0x70767a); K.cyl('metal', xb, 0.89, zc - 0.28, 0.014, 0.3, 0xb8bcc0); K.box('metal', xb, 1.17, zc - 0.2, 0.02, 0.02, 0.11, 0xb8bcc0);\n K.box('plain', xc, 0, zc, 1.3, 0.85, 0.7, cab); for (let i = 0; i < 2; i++) K.box('wood', xc - 0.32 + i * 0.64, 0.08, zc + 0.355, 0.6, 0.7, 0.012, wood);\n K.cyl('plain', xc, 0.89, zc, 0.1, 0.05, 0xf0f0ea); K.cyl('plain', xc + 0.4, 0.89, zc, 0.04, 0.26, 0x2a6a3a);\n const tz = 0.55, tl = Math.min(2.6, w * 0.65); K.box('wood', 0, 0.74, tz, tl, 0.05, 0.85, wood); for (const s of [-1, 1]) { K.box('wood', s * (tl / 2 - 0.2), 0, tz, 0.08, 0.74, 0.7, shade(wood, 0.8)); K.box('wood', s * (tl / 2 - 0.2), 0.3, tz - 0.001, 0.0 + 0.6, 0.04, 0.05, shade(wood, 0.75)); }\n for (const s of [-1, 1]) { K.box('wood', 0, 0.42, tz + s * 0.72, tl - 0.2, 0.04, 0.3, shade(wood, 1.1)); for (const sx of [-1, 1]) K.box('wood', sx * (tl / 2 - 0.4), 0, tz + s * 0.72, 0.06, 0.42, 0.26, shade(wood, 0.8)); }\n K.cyl('plain', 0.3, 0.79, tz, 0.15, 0.08, 0xd8d0c0); K.cyl('plain', -0.5, 0.79, tz + 0.1, 0.035, 0.26, 0x2a6a3a); for (let i = 0; i < 3; i++) K.cyl('plain', -0.9 + i * 0.2, 0.79, tz - 0.2, 0.035, 0.1, 0xe8e4d8); };\n B.sauna = (M, a) => { const K = kit(M, a), { w, d, h } = K.dim('sauna'), t = 0.08, wood = K.wood(0xc08a54), shade1 = shade(wood, 0.88), dw = 0.7, dx = w / 2 - 0.55;\n K.box('wood', 0, 0, 0, w, 0.05, d, shade(wood, 0.75)); K.box('wood', 0, 0.05, -d / 2 + t / 2, w, h - 0.11, t, wood); for (const s of [-1, 1]) K.box('wood', s * (w / 2 - t / 2), 0.05, 0, t, h - 0.11, d - 2 * t * 0 , wood);\n K.box('wood', 0, h - 0.06, 0, w + 0.12, 0.06, d + 0.12, shade(wood, 0.75));\n const lx0 = -w / 2, lx1 = dx - dw / 2; K.box('wood', (lx0 + lx1) / 2, 0.05, d / 2 - t / 2, lx1 - lx0, h - 0.11, t, wood); K.box('wood', (dx + dw / 2 + w / 2) / 2, 0.05, d / 2 - t / 2, w / 2 - (dx + dw / 2), h - 0.11, t, wood); K.box('wood', dx, 1.85, d / 2 - t / 2, dw, h - 0.11 - 1.8, t, wood);\n for (let i = 0; i < 12; i++) { const x = lx0 + 0.1 + i * (lx1 - lx0) / 12; K.box('wood', x, 0.05, d / 2 + 0.003, 0.012, h - 0.11, 0.005, shade(wood, 0.6)); }\n K.box('wood', dx - dw / 2 - 0.025, 0.05, d / 2 - 0.02, 0.05, 1.82, 0.06, shade1); K.box('wood', dx + dw / 2 + 0.025, 0.05, d / 2 - 0.02, 0.05, 1.82, 0.06, shade1); K.box('glass', dx, 0.06, d / 2 - 0.02, dw, 1.75, 0.02, 0xffffff); K.box('metal', dx - dw / 2 + 0.08, 0.7, d / 2 + 0.03, 0.02, 0.5, 0.03, 0xc8ccd0);\n K.box('wood', 0, 0.85, -d / 2 + t + 0.25, w - 2 * t, 0.05, 0.5, shade(wood, 1.1)); K.box('wood', 0, 0.45, -d / 2 + t + 0.65, w - 2 * t, 0.05, 0.4, shade(wood, 1.1)); K.box('wood', 0, 0.05, -d / 2 + t + 0.1, w - 2 * t, 0.8, 0.04, shade(wood, 0.8));\n K.cyl('metal', -w / 2 + 0.3, 0.05, d / 2 - 0.4, 0.14, 0.55, 0x2a2a2e); for (let i = 0; i < 5; i++) K.cyl('plain', -w / 2 + 0.3 + Math.cos(i * 1.3) * 0.06, 0.6, d / 2 - 0.4 + Math.sin(i * 1.3) * 0.06, 0.04, 0.07, 0x6a6a6a, 0, 0, 'sphere');\n K.box('plain', 0, h - 0.2, -d / 2 + t + 0.02, 0.4, 0.06, 0.04, 0xfff0c8); K.box('emissive', 0, h - 0.19, -d / 2 + t + 0.045, 0.34, 0.01, 0.006, 0xffe0a0); };\n B.soaking_tub = (M, a) => { const K = kit(M, a), { w } = K.dim('soaking_tub'), r = Math.min(0.62, w * 0.48), wood = K.wood(0xa8784a);\n K.cyl('wood', 0, 0, 0, r, 0.78, wood, 0, 0, 'cyl'); K.cyl('wood', 0, 0.76, 0, r + 0.025, 0.04, shade(wood, 1.15)); for (const y of [0.15, 0.5]) K.cyl('metal', 0, y, 0, r + 0.012, 0.03, 0x2a2a2e);\n K.cyl('water', 0, 0.74, 0, r - 0.04, 0.01, 0x9ad4e8); for (let i = 0; i < 8; i++) K.box('wood', Math.cos(i * 0.785) * (r + 0.001), 0.02, Math.sin(i * 0.785) * (r + 0.001), 0.012, 0.74, 0.12, shade(wood, 0.7), -i * 0.785 + 0);\n K.box('wood', 0, 0, r + 0.4, 0.5, 0.3, 0.34, shade(wood, 0.9)); K.cyl('wood', 0.2, 0.3, r + 0.4, 0.1, 0.14, shade(wood, 1.1)); K.box('wood', -0.1, 0.34, r + 0.4, 0.3, 0.02, 0.025, shade(wood, 1.1), 0.5); K.box('fabric', -0.1, 0.3, r + 0.42, 0.26, 0.03, 0.2, K.accent(0xf0ece0)); };\n return B\n}\n","import { hex, mix, rng, seedOf } from './colors'\nimport type { AtlasMap, CoverOptions, FloorKind, PropItem, PropsApi, Uv } from './types'\n\n/** Default height of each floor kind above the ground. */\nconst HEIGHTS: Record<FloorKind, number> = { lawn: 0.02, dirt: 0.025, concrete: 0.03, tile_floor: 0.035, deck_floor: 0.04, pool_tile: 0.04, paving: 0.035 }\n\nconst inside = (poly: Array<[number, number]>, x: number, z: number): boolean => {\n let in_ = false\n for (let i = 0, j = poly.length - 1; i < poly.length; j = i++) {\n const [xi, zi] = poly[i]\n const [xj, zj] = poly[j]\n if (zi > z !== zj > z && x < ((xj - xi) * (z - zi)) / (zj - zi) + xi) in_ = !in_\n }\n return in_\n}\n\n/**\n * Covers a rectangle centered on M (w x d) or a polygon `[[x, z], ...]` in local coordinates with floor tiles:\n * one atlas square per cell (repeats and tiles); the polygon uses cells of a quarter of the size.\n */\nexport function createCover(atlas: <K extends 'floor'>(name: K) => AtlasMap[K]) {\n return function cover(api: PropsApi, M: PropItem, kind: FloorKind, o: CoverOptions = {}): void {\n const A = atlas('floor')\n const size = o.size || A.size(kind)\n const nv = A.variants(kind)\n const y = o.y != null ? o.y : HEIGHTS[kind] || 0.03\n const color = o.color != null ? hex(o.color) : 0xffffff\n const poly = o.poly || M.poly\n const sd = seedOf(M)\n const rnd = rng(sd)\n const tint = () => {\n const v = 0.93 + rnd() * 0.07\n return mix(0x000000, color, v)\n }\n const quad = (cx: number, cz: number, qw: number, qd: number, uv: Uv) => api.plane(M, 'atlas_floor', cx, y, cz, qw, qd, tint(), 0, -Math.PI / 2, 0, uv)\n if (!poly) {\n const w = o.w || M.w || 4\n const d = o.d || M.d || 4\n for (let j = 0, v = 0; v < d - 1e-6; j++, v += size)\n for (let i = 0, u = 0; u < w - 1e-6; i++, u += size) {\n const qw = Math.min(size, w - u)\n const qd = Math.min(size, d - v)\n const b = A.uv(kind, (i * 7 + j * 13 + sd) % nv)\n const uv = { u0: b.u0, u1: b.u0 + ((b.u1 - b.u0) * qw) / size, v1: b.v1, v0: b.v1 - ((b.v1 - b.v0) * qd) / size }\n quad(-w / 2 + u + qw / 2, -d / 2 + v + qd / 2, qw, qd, uv)\n }\n return\n }\n let x0 = 1e9\n let z0 = 1e9\n let x1 = -1e9\n let z1 = -1e9\n for (const [x, z] of poly) {\n x0 = Math.min(x0, x)\n x1 = Math.max(x1, x)\n z0 = Math.min(z0, z)\n z1 = Math.max(z1, z)\n }\n const st = size / 4\n for (let j = 0; z0 + j * st < z1; j++)\n for (let i = 0; x0 + i * st < x1; i++) {\n const cx = x0 + (i + 0.5) * st\n const cz = z0 + (j + 0.5) * st\n if (!inside(poly, cx, cz)) continue\n const b = A.uv(kind, (((i >> 2) * 7 + (j >> 2) * 13 + sd) % nv))\n const du = (b.u1 - b.u0) / 4\n const dv = (b.v1 - b.v0) / 4\n const ui = i & 3\n const vj = j & 3\n quad(cx, cz, st, st, { u0: b.u0 + ui * du, u1: b.u0 + (ui + 1) * du, v1: b.v1 - vj * dv, v0: b.v1 - (vj + 1) * dv })\n }\n }\n}\n","import { DEFS } from './catalog'\nimport { hex, rng, seedOf } from './colors'\nimport type { EmitFn, Kit, PropItem, PropsApi } from './types'\n\n/** Shortcuts that fix `M` and the api, so builders read as a list of primitives. */\nexport function makeKit(M: PropItem, api: PropsApi, emit: EmitFn): Kit {\n const role = (name: string): number | null => {\n const v = M.palette && M.palette[name]\n return v != null ? hex(v) : null\n }\n const K: Kit = {\n M,\n api,\n rnd: rng(seedOf(M)),\n box: (material, x, y, z, w, h, d, color, ry, rx, rz) => api.box(M, material, x, y, z, w, h, d, hex(color), ry || 0, rx || 0, rz || 0),\n cyl: (material, x, y, z, r, h, color, rx, rz, shape) => api.cylinder(M, material, x, y, z, r, h, hex(color), rx || 0, rz || 0, shape),\n leaf: (x, y, z, w, h, color, ry, rx) => api.leaf(M, x, y, z, w, h, hex(color), ry || 0, rx || 0),\n plane: (material, x, y, z, w, h, color, ry, rx, rz, uv) => api.plane(M, material, x, y, z, w, h, hex(color), ry || 0, rx || 0, rz || 0, uv),\n light: (x, y, z, color, range, kind) => {\n if (api.light) api.light(M, x, y, z, hex(color), range, kind)\n else emit('light', { M, x, y, z, color: hex(color), range, kind })\n },\n // colours: role in the house palette (M.palette) or the default; main = M.color\n color: (name, fallback) => role(name) ?? hex(fallback),\n main: (fallback) => (M.color != null ? hex(M.color) : hex(fallback)),\n fabric: (fallback) => (M.color != null ? hex(M.color) : (role('fabric_main') ?? hex(fallback))),\n accent: (fallback) => role('fabric_accent') ?? hex(fallback),\n wood: (fallback) => role('wood') ?? hex(fallback),\n metal: (fallback) => role('metal') ?? hex(fallback),\n dim: (id) => {\n const t = DEFS[id]\n return { w: M.w || t.w, d: M.d || t.d, h: M.h || t.h }\n },\n ceiling: () => M.ceiling || 2.9,\n // long tilted box: starts at (x, y, z) and follows u = (0, sin a, -cos a) for L meters\n ramp(material, x, y, z, w, t, L, color, alpha) {\n const cy = y + (Math.sin(alpha) * L) / 2\n const cz = z - (Math.cos(alpha) * L) / 2\n K.box(material, x, cy - t / 2, cz, w, t, L, color, 0, alpha + Math.PI, 0)\n },\n }\n return K\n}\n","import type { PropType } from './catalog'\n\n/** Locales shipped with the package. Unknown locales fall back to `en`. */\nexport type PropsLocale = 'en' | 'pt-BR'\n\nexport interface PropsStrings {\n /** Display name of each prop type. */\n typeNames: Record<PropType, string>\n /** Names of the picture atlas cells (4 abstract, 3 landscapes, the mirror). */\n pictureNames: string[]\n}\n\nexport const locales: Record<PropsLocale, PropsStrings> = {\n en: {\n typeNames: {\n ceiling_light: \"Ceiling light\",\n pendant: \"Pendant lamp\",\n track_spot: \"Track spot\",\n chandelier: \"Chandelier\",\n wall_ac: \"Indoor AC unit (split)\",\n picture_small: \"Small picture\",\n picture_large: \"Large picture\",\n mirror: \"Mirror\",\n curtain: \"Curtain\",\n blinds: \"Blinds\",\n flat_tv: \"Flat TV\",\n floor_plant: \"Floor plant\",\n table_plant: \"Table plant\",\n hanging_plant: \"Hanging plant\",\n tv_stand: \"TV stand with flat TV\",\n breakfast_bar: \"Breakfast bar\",\n sofa_l: \"L-shaped sofa\",\n double_bed_modern: \"Modern double bed\",\n home_office: \"Home office\",\n closet: \"Closet\",\n vanity: \"Bathroom vanity\",\n ac_condenser: \"AC condenser\",\n lawn: \"Lawn\",\n tile_floor: \"Ceramic tile floor\",\n concrete: \"Concrete slab\",\n deck_floor: \"Deck (flooring)\",\n dirt: \"Packed dirt\",\n gourmet_counter: \"Gourmet counter\",\n sauna: \"Sauna\",\n soaking_tub: \"Soaking tub\",\n pool: \"Pool\",\n deck: \"Raised deck\",\n sun_lounger: \"Sun lounger\",\n garden_bed: \"Vegetable garden bed\",\n fruit_tree: \"Fruit tree\",\n palm_tree: \"Palm tree\",\n pergola: \"Pergola\",\n slide: \"Slide\",\n swing: \"Swing\",\n bbq_simple: \"Simple barbecue\",\n garage_car: \"Car\",\n window_boards: \"Boards on the window\",\n barricade: \"Barricade\",\n sandbags: \"Sandbags\",\n barbed_wire: \"Barbed wire\",\n razor_wire: \"Razor wire\",\n water_bottle_crates: \"Mineral water crates\",\n canned_food: \"Canned food\",\n water_jug: \"Water jug\",\n floor_mattress: \"Mattress on the floor\",\n lantern: \"Lantern\",\n candles: \"Candles\",\n open_suitcases: \"Open suitcases\",\n clothes_on_floor: \"Clothes on the floor\",\n fallen_chair: \"Fallen chair\",\n blood_stains: \"Blood stains\",\n open_door: \"Open door\",\n },\n pictureNames: ['warm abstract', 'blue abstract', 'color blocks', 'minimal', 'sunset', 'mountains and lake', 'beach with palm tree', 'mirror'],\n },\n 'pt-BR': {\n typeNames: {\n ceiling_light: \"Plafon\",\n pendant: \"Pendente\",\n track_spot: \"Spot de trilho\",\n chandelier: \"Lustre\",\n wall_ac: \"Split interno\",\n picture_small: \"Quadro pequeno\",\n picture_large: \"Quadro grande\",\n mirror: \"Espelho\",\n curtain: \"Cortina\",\n blinds: \"Persiana\",\n flat_tv: \"TV plana\",\n floor_plant: \"Vaso de chão\",\n table_plant: \"Vaso de mesa\",\n hanging_plant: \"Planta pendente\",\n tv_stand: \"Rack com TV plana\",\n breakfast_bar: \"Bancada americana\",\n sofa_l: \"Sofá em L\",\n double_bed_modern: \"Cama de casal moderna\",\n home_office: \"Home office\",\n closet: \"Closet\",\n vanity: \"Lavabo\",\n ac_condenser: \"Condensadora\",\n lawn: \"Gramado\",\n tile_floor: \"Piso cerâmico\",\n concrete: \"Cimentado\",\n deck_floor: \"Deck (piso)\",\n dirt: \"Terra batida\",\n gourmet_counter: \"Bancada gourmet\",\n sauna: \"Sauna\",\n soaking_tub: \"Ofurô\",\n pool: \"Piscina\",\n deck: \"Deck elevado\",\n sun_lounger: \"Espreguiçadeira\",\n garden_bed: \"Canteiro de horta\",\n fruit_tree: \"Árvore frutífera\",\n palm_tree: \"Palmeira\",\n pergola: \"Pergolado\",\n slide: \"Escorregador\",\n swing: \"Balanço\",\n bbq_simple: \"Churrasqueira simples\",\n garage_car: \"Carro\",\n window_boards: \"Tábuas na janela\",\n barricade: \"Barricada\",\n sandbags: \"Sacos de areia\",\n barbed_wire: \"Arame farpado\",\n razor_wire: \"Concertina\",\n water_bottle_crates: \"Caixas de água mineral\",\n canned_food: \"Enlatados\",\n water_jug: \"Galão de água\",\n floor_mattress: \"Colchão no chão\",\n lantern: \"Lanterna\",\n candles: \"Velas\",\n open_suitcases: \"Malas abertas\",\n clothes_on_floor: \"Roupas no chão\",\n fallen_chair: \"Cadeira caída\",\n blood_stains: \"Manchas de sangue\",\n open_door: \"Porta aberta\",\n },\n pictureNames: ['abstrato quente', 'abstrato azul', 'blocos de cor', 'minimal', 'pôr do sol', 'montanhas e lago', 'praia com coqueiro', 'espelho'],\n },\n}\n\nexport const resolveStrings = (locale?: string): PropsStrings => locales[locale as PropsLocale] ?? locales.en\n","import { createAtlasProvider } from './atlas'\nimport { DEFS, MATERIALS, TYPE_DEFS, type PropType } from './catalog'\nimport { ceilingBuilders } from './builders/ceiling'\nimport { floorBuilders } from './builders/floors'\nimport { furnitureBuilders } from './builders/furniture'\nimport { houseStateBuilders } from './builders/house-state'\nimport { plantBuilders } from './builders/plants'\nimport { wallBuilders } from './builders/wall'\nimport { yardBuilders } from './builders/yard'\nimport { createCover } from './cover'\nimport { makeKit } from './kit'\nimport { resolveStrings } from './locale'\nimport type { BuilderContext, BuilderMap, DocumentLike, EmitFn, Props3d, Props3dOptions, TypeInfo } from './types'\n\n/** Type metadata with display names for a locale (`en` by default). */\nexport function listTypes(locale?: string): TypeInfo[] {\n const names = resolveStrings(locale).typeNames\n return TYPE_DEFS.map((t) => ({ ...t, name: names[t.id] }))\n}\n\n/**\n * Creates the props kit. Builders only describe primitives through the `api` you pass to them; nothing here touches a renderer.\n *\n * `three` is accepted so the call looks like the other game-kit packages, but the props never use it.\n *\n * ```ts\n * const props = create(THREE, { document })\n * props.builders.sofa_l({ w: 2.8, d: 1.7, h: 0.85, seed: 'living' }, api)\n * ```\n */\nexport function create(_three?: unknown, options: Props3dOptions = {}): Props3d {\n const emit: EmitFn = options.emit || (() => {})\n const doc: DocumentLike | null = options.document || (typeof document !== 'undefined' ? document : null)\n const strings = resolveStrings(options.locale)\n const atlas = createAtlasProvider(doc, strings.pictureNames)\n const cover = createCover(atlas)\n const ctx: BuilderContext = { kit: (M, api) => makeKit(M, api, emit), atlas, cover }\n const builders = Object.assign(\n {},\n ceilingBuilders(ctx),\n wallBuilders(ctx),\n plantBuilders(ctx),\n furnitureBuilders(ctx),\n floorBuilders(ctx),\n yardBuilders(ctx),\n houseStateBuilders(ctx),\n ) as BuilderMap\n return {\n builders,\n types: listTypes(options.locale),\n names: Object.keys(builders) as PropType[],\n atlas,\n cover,\n materials: MATERIALS,\n }\n}\n\nexport { DEFS, MATERIALS, TYPE_DEFS }\n","import type * as ThreeNamespace from 'three'\n\ntype Three = typeof ThreeNamespace\n\n/** What `createNodeSkin` makes: one mesh that stands for a whole tree of meshes, each vertex tied to the node of the tree that moves it. */\nexport type NodeSkin = {\n readonly mesh: ThreeNamespace.Mesh\n /** The nodes the animation moves, in the order of the skin's `aNodeIndex`. */\n readonly nodes: readonly ThreeNamespace.Object3D[]\n /** The matrix of each node, uploaded as the `uNodes` uniform. */\n readonly matrices: { readonly value: ThreeNamespace.Matrix4[] }\n /** Copies the world matrix of every node into `matrices`: call it once per frame, after the tree was animated. */\n update(): void\n}\n\nexport type NodeSkinOptions = {\n /** Meshes the animation moves and that therefore stay nodes of their own (a head ball, an eye). Groups and meshes with children are always nodes. */\n readonly nodes?: Iterable<ThreeNamespace.Object3D>\n}\n\nconst MAX_WALK_DEPTH = 3\n\n/**\n * Every Object3D reachable from the fields of `owner` (up to 3 levels, skipping the keys in `skip`): what the animation of a tree keeps in its own\n * state (head, jaw, wings, legs). Pass the result as `nodes` so those meshes are not merged into their parents.\n */\nexport const collectReferencedNodes = (owner: object, skip: readonly string[] = []): Set<ThreeNamespace.Object3D> => {\n const found = new Set<ThreeNamespace.Object3D>()\n const seen = new Set<unknown>()\n const walk = (value: unknown, depth: number): void => {\n if (!value || typeof value !== 'object' || depth > MAX_WALK_DEPTH || seen.has(value)) return\n seen.add(value)\n const candidate = value as { isObject3D?: boolean; isMaterial?: boolean; isBufferGeometry?: boolean }\n if (candidate.isObject3D) {\n found.add(value as ThreeNamespace.Object3D)\n return\n }\n if (candidate.isMaterial || candidate.isBufferGeometry) return\n for (const key in value) walk((value as Record<string, unknown>)[key], depth + 1)\n }\n for (const key in owner) if (!skip.includes(key)) walk((owner as Record<string, unknown>)[key], 0)\n return found\n}\n\ntype TextureSample = { readonly data: Uint8ClampedArray; readonly width: number; readonly height: number }\n\nconst isVisibleInTree = (object: ThreeNamespace.Object3D): boolean => {\n for (let node: ThreeNamespace.Object3D | null = object; node; node = node.parent) if (!node.visible) return false\n return true\n}\n\n/**\n * A GPU \"node skin\": a tree of about fifty meshes (an animal, a character prop) merged into one mesh, every vertex tied to the node that moves it.\n * The tree stays out of the scene and keeps animating; `update()` copies its node matrices each frame. Colour (with the texture sampled at the uv),\n * roughness, metalness and emissive are baked per vertex, so the 40 animals of a scene cost 40 draw calls instead of 2,000.\n * Generalised from the game's `bichoPele` (G 66536); the colours are read the way r160 does (linear converted to sRGB), as the game does.\n */\nexport const createNodeSkin = (THREE: Three, root: ThreeNamespace.Object3D, options: NodeSkinOptions = {}): NodeSkin => {\n const referenced = new Set(options.nodes ?? [])\n const nodes: ThreeNamespace.Object3D[] = []\n const indexOf = new Map<ThreeNamespace.Object3D, number>()\n root.traverse((object) => {\n const mesh = object as ThreeNamespace.Mesh\n if (!mesh.isMesh || object.children.length > 0 || referenced.has(object)) {\n indexOf.set(object, nodes.length)\n nodes.push(object)\n }\n })\n\n const positions: number[] = []\n const normals: number[] = []\n const tints: number[] = []\n const surfaces: number[] = []\n const nodeIndices: number[] = []\n const samples = new Map<ThreeNamespace.Texture, TextureSample>()\n const color = new THREE.Color()\n const vertex = new THREE.Vector3()\n const normal = new THREE.Vector3()\n\n const sampleOf = (texture: ThreeNamespace.Texture): TextureSample | undefined => {\n const image = texture.image as { getContext?: (kind: string) => CanvasRenderingContext2D | null; width: number; height: number } | undefined\n if (!image?.getContext) return undefined\n const cached = samples.get(texture)\n if (cached) return cached\n const context = image.getContext('2d')\n if (!context) return undefined\n const sample = { data: context.getImageData(0, 0, image.width, image.height).data, width: image.width, height: image.height }\n samples.set(texture, sample)\n return sample\n }\n\n root.traverse((object) => {\n const mesh = object as ThreeNamespace.Mesh\n if (!mesh.isMesh || !isVisibleInTree(object)) return\n const material = (Array.isArray(mesh.material) ? mesh.material[0] : mesh.material) as ThreeNamespace.MeshStandardMaterial\n const geometry = mesh.geometry\n const position = geometry.attributes.position as ThreeNamespace.BufferAttribute\n const normalAttribute = geometry.attributes.normal as ThreeNamespace.BufferAttribute | undefined\n const uv = geometry.attributes.uv as ThreeNamespace.BufferAttribute | undefined\n const matrix = new THREE.Matrix4()\n let nodeIndex = indexOf.get(object)\n if (nodeIndex === undefined) {\n nodeIndex = indexOf.get(object.parent as ThreeNamespace.Object3D) ?? 0\n object.updateMatrix()\n matrix.copy(object.matrix)\n }\n const normalMatrix = new THREE.Matrix3().getNormalMatrix(matrix)\n const sample = material.map ? sampleOf(material.map) : undefined\n const emissive = material.emissive ? (material.emissive.r + material.emissive.g + material.emissive.b) * (material.emissiveIntensity ?? 1) : 0\n const index = geometry.index ? geometry.index.array : null\n const count = index ? index.length : position.count\n for (let item = 0; item < count; item++) {\n const at = index ? (index[item] as number) : item\n vertex.fromBufferAttribute(position, at).applyMatrix4(matrix)\n positions.push(vertex.x, vertex.y, vertex.z)\n if (normalAttribute) normal.fromBufferAttribute(normalAttribute, at).applyMatrix3(normalMatrix).normalize()\n else normal.set(0, 1, 0)\n normals.push(normal.x, normal.y, normal.z)\n color.copy(material.color || color.set(1, 1, 1)).convertLinearToSRGB()\n if (sample && uv && material.map) {\n const repeat = material.map.repeat\n const u = (((uv.getX(at) * repeat.x) % 1) + 1) % 1\n const v = (((uv.getY(at) * repeat.y) % 1) + 1) % 1\n const pixelX = Math.min(sample.width - 1, (u * sample.width) | 0)\n const pixelY = Math.min(sample.height - 1, ((1 - v) * sample.height) | 0)\n const offset = (pixelY * sample.width + pixelX) * 4\n color.multiply(new THREE.Color().setRGB((sample.data[offset] as number) / 255, (sample.data[offset + 1] as number) / 255, (sample.data[offset + 2] as number) / 255, THREE.SRGBColorSpace))\n }\n if (emissive > 0.3) color.lerp(material.emissive, 0.7)\n tints.push(color.r, color.g, color.b)\n surfaces.push(material.roughness ?? 0.8, material.metalness ?? 0, emissive > 0.3 ? 1 : 0)\n nodeIndices.push(nodeIndex)\n }\n })\n\n const geometry = new THREE.BufferGeometry()\n geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3))\n geometry.setAttribute('normal', new THREE.Float32BufferAttribute(normals, 3))\n geometry.setAttribute('aTint', new THREE.Float32BufferAttribute(tints, 3))\n geometry.setAttribute('aSurface', new THREE.Float32BufferAttribute(surfaces, 3))\n geometry.setAttribute('aNodeIndex', new THREE.Float32BufferAttribute(nodeIndices, 1))\n geometry.computeBoundingSphere()\n\n const matrices = { value: nodes.map(() => new THREE.Matrix4()) }\n const skinVertex = (shader: ThreeNamespace.WebGLProgramParametersWithUniforms): void => {\n shader.uniforms.uNodes = matrices\n shader.vertexShader = shader.vertexShader\n .replace(\n '#include <common>',\n `#include <common>\\nuniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3 aTint; attribute vec3 aSurface; varying vec3 vTint; varying vec3 vSurface;`,\n )\n .replace('#include <beginnormal_vertex>', 'vec3 objectNormal = mat3(uNodes[int(aNodeIndex + .5)]) * normal;')\n .replace('#include <begin_vertex>', 'vec3 transformed = (uNodes[int(aNodeIndex + .5)] * vec4(position, 1.)).xyz; vTint = aTint; vSurface = aSurface;')\n }\n\n const material = new THREE.MeshStandardMaterial({ roughness: 1, metalness: 0 })\n material.onBeforeCompile = (shader) => {\n skinVertex(shader)\n shader.fragmentShader = shader.fragmentShader\n .replace('#include <common>', '#include <common>\\nvarying vec3 vTint; varying vec3 vSurface;')\n .replace('vec4 diffuseColor = vec4( diffuse, opacity );', 'vec4 diffuseColor = vec4( vTint, opacity );')\n .replace('#include <roughnessmap_fragment>', 'float roughnessFactor = vSurface.x;')\n .replace('#include <metalnessmap_fragment>', 'float metalnessFactor = vSurface.y;')\n .replace('#include <emissivemap_fragment>', 'totalEmissiveRadiance = vTint * vSurface.z * 1.5;')\n }\n material.customProgramCacheKey = () => `nodeSkin${nodes.length}`\n const depthMaterial = new THREE.MeshDepthMaterial({ depthPacking: THREE.RGBADepthPacking })\n depthMaterial.onBeforeCompile = skinVertex\n depthMaterial.customProgramCacheKey = () => `nodeSkinDepth${nodes.length}`\n\n const mesh = new THREE.Mesh(geometry, material)\n mesh.customDepthMaterial = depthMaterial\n mesh.frustumCulled = false\n root.updateMatrixWorld(true)\n return {\n mesh,\n nodes,\n matrices,\n update() {\n root.updateMatrixWorld(true)\n for (let index = 0; index < nodes.length; index++) (matrices.value[index] as ThreeNamespace.Matrix4).copy((nodes[index] as ThreeNamespace.Object3D).matrixWorld)\n },\n }\n}\n","import type { Primitive, PropsApi } from './types'\n\n/**\n * An api that records every primitive instead of drawing it. Handy for tests, previews and for measuring a prop.\n *\n * ```ts\n * const { api, primitives } = createRecorder()\n * props.builders.sofa_l(item, api)\n * console.log(primitives.length)\n * ```\n * Pass `{ light: false }` to leave `api.light` undefined, so builders fall back to `emit('light', ...)`.\n */\nexport function createRecorder(options: { light?: boolean } = {}): { api: PropsApi; primitives: Primitive[] } {\n const primitives: Primitive[] = []\n const api: PropsApi = {\n box: (M, material, x, y, z, w, h, d, color, ry, rx, rz) => void primitives.push({ kind: 'box', M, material, x, y, z, w, h, d, color, ry, rx, rz }),\n cylinder: (M, material, x, y, z, r, h, color, rx, rz, shape) => void primitives.push({ kind: 'cylinder', M, material, x, y, z, r, h, color, rx, rz, shape }),\n leaf: (M, x, y, z, w, h, color, ry, rx) => void primitives.push({ kind: 'leaf', M, x, y, z, w, h, color, ry, rx }),\n plane: (M, material, x, y, z, w, h, color, ry, rx, rz, uv) => void primitives.push({ kind: 'plane', M, material, x, y, z, w, h, color, ry, rx, rz, uv }),\n }\n if (options.light !== false) {\n api.light = (M, x, y, z, color, range, lightKind) => void primitives.push({ kind: 'light', M, x, y, z, color, range, lightKind })\n }\n return { api, primitives }\n}\n","import type * as THREE_NS from 'three'\n\nconst ATTRIBUTES = ['position', 'normal', 'uv'] as const\n\nconst isVisible = (object: THREE_NS.Object3D): boolean => {\n for (let node: THREE_NS.Object3D | null = object; node; node = node.parent) if (!node.visible) return false\n return true\n}\n\n/** A copy of the geometry with the matrix baked in, not indexed, keeping only what every model has (position, normal, uv), so that any two of them can be joined. */\nconst bake = (THREE: typeof THREE_NS, mesh: THREE_NS.Mesh, matrix: THREE_NS.Matrix4): THREE_NS.BufferGeometry => {\n const source = mesh.geometry.index ? mesh.geometry.toNonIndexed() : mesh.geometry.clone()\n const baked = new THREE.BufferGeometry()\n const count = source.getAttribute('position').count\n for (const name of ATTRIBUTES) {\n const attribute = source.getAttribute(name)\n baked.setAttribute(name, attribute ?? new THREE.BufferAttribute(new Float32Array(count * (name === 'uv' ? 2 : 3)), name === 'uv' ? 2 : 3))\n }\n source.dispose()\n return baked.applyMatrix4(matrix)\n}\n\n/** One non-indexed geometry out of several, in order (the port of `mergeGeometries` of three/addons for the three attributes above). */\nconst join = (THREE: typeof THREE_NS, geometries: readonly THREE_NS.BufferGeometry[]): THREE_NS.BufferGeometry => {\n const merged = new THREE.BufferGeometry()\n for (const name of ATTRIBUTES) {\n const size = (geometries[0]?.getAttribute(name) as THREE_NS.BufferAttribute).itemSize\n const total = geometries.reduce((sum, geometry) => sum + geometry.getAttribute(name).count, 0)\n const array = new Float32Array(total * size)\n let offset = 0\n for (const geometry of geometries) {\n array.set(geometry.getAttribute(name).array, offset)\n offset += geometry.getAttribute(name).count * size\n }\n merged.setAttribute(name, new THREE.BufferAttribute(array, size))\n }\n return merged\n}\n\n/**\n * Every visible mesh under `root` (whose world matrix must be up to date), baked into the space of `space` and joined by material: one mesh per material, no shadow cast.\n * Weapons lying on a counter are never animated, so the hundreds of meshes of a model cost one draw call per material instead.\n */\nexport function mergeByMaterial(THREE: typeof THREE_NS, root: THREE_NS.Object3D, space: THREE_NS.Object3D): THREE_NS.Mesh[] {\n const toSpace = space.matrixWorld.clone().invert()\n const byMaterial = new Map<THREE_NS.Material, THREE_NS.BufferGeometry[]>()\n root.traverse((object) => {\n const mesh = object as THREE_NS.Mesh\n if (!mesh.isMesh || Array.isArray(mesh.material) || !isVisible(mesh)) return\n const list = byMaterial.get(mesh.material) ?? []\n list.push(bake(THREE, mesh, new THREE.Matrix4().multiplyMatrices(toSpace, mesh.matrixWorld)))\n byMaterial.set(mesh.material, list)\n })\n return [...byMaterial].map(([material, geometries]) => {\n const merged = new THREE.Mesh(join(THREE, geometries), material)\n for (const geometry of geometries) geometry.dispose()\n merged.castShadow = false\n merged.receiveShadow = true\n return merged\n })\n}\n","import type { BirdSpec } from '@adatechnology/game-animals'\n\nimport type { AnimateParams } from './animate-types'\nimport type { AnimalRig } from './types'\n\nconst { PI } = Math\n\n/** Advances a bird's cycle (page: `animBird`): steps, flight for the species that fly, wings, peck, alarm, tail. */\nexport const animateBird = (rig: AnimalRig, params: AnimateParams): void => {\n const { time: t, delta: dt, clip } = params\n const species = rig.species\n const spec = species.body as BirdSpec\n const isVulture = species.id === 'vulture'\n const isChicken = species.id === 'chicken'\n const isLapwing = species.id === 'southern_lapwing'\n const flying = clip === 'run' && !isChicken\n const hz = clip === 'run' && !flying ? species.runHz : clip === 'walk' ? species.walkHz : 0\n rig.phase += dt * (flying ? (isVulture ? 1.1 : 4.5) : hz) * 2 * PI\n const targetHeight = flying ? rig.size * 1.2 : 0\n rig.trunk.position.y += (targetHeight - rig.trunk.position.y) * Math.min(1, dt * 3)\n rig.legs.forEach((leg, index) => {\n if (flying) {\n leg.hip.rotation.x = 1.2\n leg.knee.rotation.x = -1.6\n } else if (clip === 'idle') {\n leg.hip.rotation.x = 0\n leg.knee.rotation.x = 0\n } else {\n const phase = rig.phase + index * PI\n leg.hip.rotation.x = -Math.sin(phase) * 0.45\n leg.knee.rotation.x = -Math.max(0, Math.cos(phase)) * 0.7\n }\n leg.foot.rotation.x = -(leg.hip.rotation.x + leg.knee.rotation.x)\n })\n rig.wings.forEach((wing) => {\n const { side, shoulder, elbow } = wing\n if (flying) {\n const flap = isVulture ? 0.25 + Math.sin(rig.phase) * 0.18 : Math.sin(rig.phase) * 0.9\n shoulder.rotation.y += (0 - shoulder.rotation.y) * Math.min(1, dt * 6)\n shoulder.rotation.z = side * flap\n elbow.rotation.z = side * (isVulture ? 0.08 : Math.sin(rig.phase - 0.6) * 0.4)\n elbow.rotation.y = 0\n shoulder.scale.x += (1 - shoulder.scale.x) * Math.min(1, dt * 6)\n } else {\n const alert = isLapwing && clip === 'idle' ? Math.max(0, Math.sin(t * 1.3)) * 0.9 : 0\n const flutter = isChicken && clip === 'run' ? Math.abs(Math.sin(rig.phase * 1.5)) * 0.8 : 0\n shoulder.rotation.y += ((side * PI) / 2 * 0.97 - alert * side * 0.9 - shoulder.rotation.y) * Math.min(1, dt * 6)\n shoulder.rotation.z = side * (-0.06 + flutter + alert * 0.6)\n elbow.rotation.z = side * 0.04\n elbow.rotation.y = 0\n shoulder.scale.x += ((alert > 0.1 ? 1 : 0.58) - shoulder.scale.x) * Math.min(1, dt * 6)\n }\n })\n const peck = isChicken && clip === 'idle' ? Math.max(0, Math.sin(t * 1.6)) ** 8 * 1.2 : 0\n if (rig.neck) {\n rig.neck.rotation.x = rig.neckAngle + (clip === 'walk' && !isVulture ? Math.max(0, Math.sin(rig.phase * 2)) * 0.35 : 0) + peck + (flying ? 0.6 : 0)\n rig.neck.rotation.y = clip === 'idle' ? Math.sin(t * 0.9) * 0.5 : 0\n }\n if (rig.jaw) rig.jaw.rotation.x = isLapwing && clip === 'idle' ? Math.max(0, Math.sin(t * 1.3)) * Math.abs(Math.sin(t * 14)) * 0.5 : 0\n if (rig.tailRoot) rig.tailRoot.rotation.x = (spec.tailA || 0) + (flying ? 0.6 : 0)\n}\n","import type { GaitPhases, QuadrupedSpec } from '@adatechnology/game-animals'\n\nimport type { AnimateParams } from './animate-types'\nimport type { AnimalRig } from './types'\n\nconst { PI } = Math\n\n/** Advances a quadruped's step cycle (page: `animQuad`): legs by gait phase, trunk bob, neck, head, jaw and tail; a dead one lies down. */\nexport const animateQuadruped = (rig: AnimalRig, params: AnimateParams, gaits: { readonly walk: GaitPhases; readonly trot: GaitPhases; readonly gallop: GaitPhases }): void => {\n const { time: t, delta: dt, clip } = params\n const species = rig.species\n const spec = species.body as QuadrupedSpec\n if (rig.dead) {\n rig.root.rotation.z += (PI / 2 * 0.97 - rig.root.rotation.z) * Math.min(1, dt * 3)\n rig.root.position.y += (spec.R * (spec.sx || 1) * 0.85 - rig.root.position.y) * Math.min(1, dt * 3)\n rig.legs.forEach((leg) => {\n leg.hip.rotation.x = leg.angle1 + (leg.isFront ? -0.5 : 0.4)\n leg.knee.rotation.x = 0.05\n leg.foot.rotation.x = 0\n })\n if (rig.neck) rig.neck.rotation.x = rig.neckAngle + 0.35\n if (rig.jaw) rig.jaw.rotation.x = 0.25\n return\n }\n const hz = (clip === 'run' ? species.runHz : clip === 'walk' ? species.walkHz : 0) * (rig.infected ? 1.2 : 1)\n rig.phase += dt * hz * 2 * PI\n const gait = clip === 'run' ? (species.gallop ? gaits.gallop : gaits.trot) : gaits.walk\n const amplitude = clip === 'run' ? 0.62 : 0.34\n const flex = clip === 'run' ? 1.15 : 0.75\n rig.legs.forEach((leg, index) => {\n if (clip === 'idle') {\n leg.hip.rotation.x = leg.angle1\n leg.knee.rotation.x = leg.angle2\n } else {\n const phase = rig.phase + (gait[index] as number)\n const limit = rig.infected && index === 1 ? 0.35 : 1\n leg.hip.rotation.x = leg.angle1 - Math.sin(phase) * amplitude * limit\n leg.knee.rotation.x = leg.angle2 + Math.max(0, Math.cos(phase)) * flex * limit\n }\n leg.foot.rotation.x = -(leg.hip.rotation.x + leg.knee.rotation.x) * 0.85\n })\n const bob = clip === 'idle' ? 0 : Math.abs(Math.sin(rig.phase)) * (clip === 'run' ? 0.05 : 0.018) * rig.size\n rig.trunk.position.y = bob * 0.6\n const bodyGroup = rig.bodyGroup\n if (bodyGroup) {\n bodyGroup.rotation.x = clip === 'run' && species.gallop ? Math.sin(rig.phase + PI * 0.4) * 0.07 : 0\n bodyGroup.scale.y = 1 + (clip === 'idle' ? Math.sin(t * 2.2) * 0.012 : 0)\n }\n const low = rig.infected ? 0.35 : 0\n if (rig.neck) {\n rig.neck.rotation.x = rig.neckAngle + low + (clip === 'idle' ? Math.sin(t * 0.7) * 0.08 : Math.sin(rig.phase * 2) * 0.04)\n rig.neck.rotation.y = clip === 'idle' ? Math.sin(t * 0.45) * 0.35 : 0\n }\n if (rig.head) rig.head.rotation.z = rig.infected ? Math.sin(t * 11) * Math.sin(t * 1.7) * 0.12 : 0\n if (rig.jaw) rig.jaw.rotation.x = rig.infected ? 0.25 + Math.max(0, Math.sin(t * 6)) * 0.25 : clip === 'run' ? 0.12 : 0\n const wagging = species.id === 'dog' && !rig.infected\n rig.tail.forEach((segment, index) => {\n segment.rotation.z = Math.sin(t * (wagging ? 9 : 2.2) - index * 0.6) * (wagging ? 0.35 : 0.12)\n })\n}\n","import type * as THREE_NS from 'three'\n\nimport type { AnimateParams } from './animate-types'\nimport type { AnimalRig, Three } from './types'\n\nconst { PI } = Math\n\n/** The path of the body for a clip: crawling in a wave, or coiled with the head up in an S (and striking when `strike` rises). */\nconst bodyPath = (THREE: Three, rig: AnimalRig, params: AnimateParams, spacing: number): THREE_NS.Vector3[] => {\n const { time: t, clip } = params\n const points: THREE_NS.Vector3[] = []\n for (let index = 0; index < rig.segmentCount; index++) {\n let x: number\n let z: number\n let y = 0\n if (clip === 'walk') {\n z = 0.6 - index * spacing\n x = Math.sin(rig.phase * 2.2 - index * 0.42) * 0.08 * Math.min(1, index / 6 + 0.3)\n } else {\n const strike = clip === 'run' ? Math.max(0, Math.sin(t * 2.2)) ** 6 : 0\n if (index < 9) {\n const k = index / 9\n z = 0.08 + (1 - k) * 0.12 + strike * (1 - k) * 0.35\n y = (1 - k) * 0.13 * (1 - strike * 0.6)\n x = Math.sin(k * PI * 1.6) * 0.05 * (1 - strike)\n } else {\n const angle = (index - 9) * 0.36\n const radius = 0.16 - (index - 9) * 0.0045\n x = Math.cos(angle) * radius\n z = Math.sin(angle) * radius - 0.05\n y = (index - 9) * 0.0012\n }\n }\n points.push(new THREE.Vector3(x, y, z))\n }\n return points\n}\n\n/** Advances a snake (page: `animSnake`): lays the 34 segments along the path of the clip, then the head, jaw and tongue. */\nexport const animateSnake = (THREE: Three, rig: AnimalRig, params: AnimateParams): void => {\n const { time: t, delta: dt, clip } = params\n const spacing = rig.segmentLength * (params.split ? 1.5 : 1)\n if (clip === 'walk') rig.phase += dt * 2.4\n const points = bodyPath(THREE, rig, params, spacing)\n const at = (index: number): THREE_NS.Vector3 => points[index] as THREE_NS.Vector3\n for (let index = 0; index < rig.segmentCount; index++) {\n const group = (rig.segments[index] as AnimalRig['segments'][number]).group\n group.position.copy(at(index))\n const direction = index === 0 ? at(0).clone().sub(at(1)) : at(index - 1).clone().sub(at(index))\n group.rotation.set(0, Math.atan2(direction.x, direction.z), 0)\n group.rotateX(-Math.atan2(direction.y, Math.hypot(direction.x, direction.z)))\n }\n const head = rig.head as THREE_NS.Group\n const headDirection = at(0).clone().sub(at(1)).normalize()\n head.position.copy(at(0)).addScaledVector(headDirection, 0.02)\n head.rotation.set(0, Math.atan2(headDirection.x, headDirection.z), 0)\n head.rotateX(-Math.atan2(headDirection.y, Math.hypot(headDirection.x, headDirection.z)) * 0.5)\n const jaw = rig.jaw as THREE_NS.Group\n jaw.rotation.x = clip === 'run' ? Math.max(0, Math.sin(t * 2.2)) ** 6 * 0.9 : 0\n const tongue = rig.tongue as THREE_NS.Mesh\n tongue.visible = Math.sin(t * 5) > 0.3\n tongue.scale.z = 1 + Math.sin(t * 20) * 0.3\n}\n","import type * as THREE_NS from 'three'\n\nimport type { AnimalRig, AnimalsThreeOptions, Three } from './types'\n\nexport type MaterialExtras = Partial<THREE_NS.MeshStandardMaterialParameters>\n\n/** The mulberry32 stream of the page (`rng`): the same numbers for the same seed. */\nexport const createStream = (seed: number): (() => number) => {\n let state = seed\n return () => {\n state |= 0\n state = (state + 0x6d2b79f5) | 0\n let t = Math.imul(state ^ (state >>> 15), 1 | state)\n t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t\n return ((t ^ (t >>> 14)) >>> 0) / 4294967296\n }\n}\n\n/** The page's `add`: a mesh at a position and rotation under a parent, casting and receiving shadows. */\nexport const addMesh = (\n THREE: Three,\n parent: THREE_NS.Object3D,\n geometry: THREE_NS.BufferGeometry,\n material: THREE_NS.Material,\n x?: number,\n y?: number,\n z?: number,\n rx?: number,\n ry?: number,\n rz?: number,\n): THREE_NS.Mesh => {\n const mesh = new THREE.Mesh(geometry, material)\n mesh.position.set(x || 0, y || 0, z || 0)\n if (rx || ry || rz) mesh.rotation.set(rx || 0, ry || 0, rz || 0)\n mesh.castShadow = true\n mesh.receiveShadow = true\n parent.add(mesh)\n return mesh\n}\n\n/** The part builders of the page's `Animal` (`M`, `ball`, `limb`, `joint`, `pivot`), bound to one rig. */\nexport type RigTools = {\n material(color: number, extras?: MaterialExtras): THREE_NS.MeshStandardMaterial\n ball(parent: THREE_NS.Object3D, radius: number, material: THREE_NS.Material, x?: number, y?: number, z?: number, sx?: number, sy?: number, sz?: number): THREE_NS.Mesh\n /** A cylinder hanging from the origin; `radius1` is the one the page calls the top, but the geometry takes it as the bottom (kept as the page has it). */\n limb(parent: THREE_NS.Object3D, radius1: number, radius2: number, length: number, material: THREE_NS.Material): THREE_NS.Mesh\n joint(parent: THREE_NS.Object3D, radius: number, material: THREE_NS.Material, x?: number, y?: number, z?: number): THREE_NS.Mesh\n pivot(parent: THREE_NS.Object3D, x: number, y: number, z: number, offset?: THREE_NS.Vector3): THREE_NS.Group\n eyes(head: THREE_NS.Object3D, radius: number, x: number, y: number, z: number): void\n}\n\nexport const createRigTools = (THREE: Three, rig: AnimalRig): RigTools => {\n const tools: RigTools = {\n material(color, extras) {\n const material = new THREE.MeshStandardMaterial(Object.assign({ color, roughness: 0.8, metalness: 0, flatShading: false }, extras || {}))\n material.userData.base = material.color.clone()\n rig.materials.push(material)\n return material\n },\n ball(parent, radius, material, x, y, z, sx, sy, sz) {\n const mesh = addMesh(THREE, parent, new THREE.SphereGeometry(radius, 16, 12), material, x, y, z)\n if (sx) mesh.scale.set(sx, sy as number, sz as number)\n return mesh\n },\n limb: (parent, radius1, radius2, length, material) => addMesh(THREE, parent, new THREE.CylinderGeometry(radius2, radius1, length, 14, 1).translate(0, -length / 2, 0), material),\n joint(parent, radius, material, x, y, z) {\n const joint = tools.ball(parent, radius, material, x, y, z)\n joint.userData.joint = 1\n rig.joints.push(joint)\n return joint\n },\n pivot(parent, x, y, z, offset) {\n const group = new THREE.Group()\n group.position.set(x, y, z)\n parent.add(group)\n rig.pivots.push({ group, rest: group.position.clone(), offset: offset || new THREE.Vector3() })\n return group\n },\n eyes(head, radius, x, y, z) {\n const eyeMaterial = tools.material(0x111111, { roughness: 0.25 })\n rig.eyeMaterial = eyeMaterial\n for (const side of [-1, 1]) rig.eyes.push(tools.ball(head, radius, eyeMaterial, side * x, y, z))\n },\n }\n return tools\n}\n\nexport const createRig = (THREE: Three, species: AnimalRig['species'], options: AnimalsThreeOptions & { phase: number }): AnimalRig => {\n const rig: AnimalRig = {\n species,\n root: new THREE.Group(),\n trunk: new THREE.Group(),\n materials: [],\n joints: [],\n pivots: [],\n legs: [],\n tail: [],\n ears: [],\n infectionParts: [],\n eyes: [],\n wings: [],\n segments: [],\n phase: options.phase,\n size: 1,\n segmentLength: 0,\n segmentCount: 0,\n home: new THREE.Vector3(),\n infected: false,\n dead: false,\n neckAngle: 0,\n headAngle: 0,\n triangles: 0,\n }\n rig.root.add(rig.trunk)\n return rig\n}\n","import { birdSize } from '@adatechnology/game-animals'\nimport type { BirdSpec } from '@adatechnology/game-animals'\nimport type * as THREE_NS from 'three'\n\nimport { addMesh, createRigTools } from './context'\nimport type { BirdExtra } from './extras'\nimport type { AnimalRig, Three } from './types'\n\nconst buildWings = (THREE: Three, rig: AnimalRig, spec: BirdSpec, parts: { bodyGroup: THREE_NS.Group; main: THREE_NS.MeshStandardMaterial; dark: THREE_NS.MeshStandardMaterial }): void => {\n const tools = createRigTools(THREE, rig)\n const { bodyGroup, main, dark } = parts\n for (const side of [-1, 1]) {\n const shoulder = tools.pivot(bodyGroup, side * spec.r * spec.s[0] * 1.02, spec.r * 0.22, spec.r * 0.3, new THREE.Vector3(side * 0.12, 0.03, 0).multiplyScalar(rig.size))\n tools.joint(shoulder, spec.r * 0.14, main, 0, 0, 0)\n tools.ball(shoulder, 0.5, spec.wingM ? tools.material(spec.wingM) : dark, side * spec.wi * 0.5, 0, -spec.wc * 0.3, spec.wi * 1.05, spec.r * 0.16, spec.wc)\n const elbow = tools.pivot(shoulder, side * spec.wi, 0, 0, new THREE.Vector3(side * 0.06, 0, 0).multiplyScalar(rig.size))\n tools.joint(elbow, spec.r * 0.1, main, 0, 0, 0)\n tools.ball(elbow, 0.5, dark, side * spec.wo * 0.45, 0, -spec.wc * 0.32, spec.wo * 1.05, spec.r * 0.12, spec.wc * 0.75)\n if (spec.fingers) {\n for (let finger = 0; finger < 4; finger++) {\n addMesh(THREE, elbow, new THREE.BoxGeometry(spec.wo * 0.35, spec.r * 0.03, spec.wc * 0.12), dark, side * (spec.wo + spec.wo * 0.12), 0, -spec.wc * 0.05 - finger * spec.wc * 0.18, 0, side * (0.05 + finger * 0.12), 0)\n }\n }\n if (spec.spur) addMesh(THREE, elbow, new THREE.ConeGeometry(spec.r * 0.06, spec.r * 0.25, 5), tools.material(0xc0302a), 0, 0, spec.wc * 0.2, Math.PI / 2, 0, 0)\n rig.wings.push({ shoulder, elbow, side })\n }\n}\n\nconst buildLegs = (THREE: Three, rig: AnimalRig, spec: BirdSpec, parts: { bodyGroup: THREE_NS.Group; main: THREE_NS.MeshStandardMaterial; leg: THREE_NS.MeshStandardMaterial }): void => {\n const tools = createRigTools(THREE, rig)\n const { bodyGroup, main, leg } = parts\n for (const side of [-1, 1]) {\n const hip = tools.pivot(bodyGroup, side * spec.r * 0.38, -spec.r * 0.55, spec.r * 0.05, new THREE.Vector3(side * 0.06, -0.05, 0).multiplyScalar(rig.size))\n tools.joint(hip, spec.r * 0.18, main, 0, 0, 0)\n tools.limb(hip, spec.r * 0.16, spec.r * 0.09, spec.shank, spec.featherLeg ? main : leg)\n const ankle = tools.pivot(hip, 0, -spec.shank, 0, new THREE.Vector3(0, -0.04, 0).multiplyScalar(rig.size))\n tools.joint(ankle, spec.r * 0.06, leg, 0, 0, 0)\n tools.limb(ankle, spec.r * 0.055, spec.r * 0.05, spec.tars, leg)\n const foot = tools.pivot(ankle, 0, -spec.tars, 0)\n for (const angle of [-0.45, 0, 0.45]) addMesh(THREE, foot, new THREE.BoxGeometry(spec.r * 0.05, spec.r * 0.04, spec.r * 0.55).translate(0, 0, spec.r * 0.25), leg, 0, -spec.r * 0.02, 0, 0, angle, 0)\n addMesh(THREE, foot, new THREE.BoxGeometry(spec.r * 0.05, spec.r * 0.04, spec.r * 0.25).translate(0, 0, -spec.r * 0.12), leg, 0, -spec.r * 0.02, 0)\n rig.legs.push({ hip, knee: ankle, foot, isFront: false, angle1: 0.35, angle2: -0.5, side })\n }\n}\n\n/** Builds a bird: body, tail, two wings with shoulder and elbow pivots, neck, head with beak and jaw, eyes, the species' extra parts, and two legs. */\nexport const buildBird = (THREE: Three, rig: AnimalRig, options: { extra?: BirdExtra }): AnimalRig => {\n const spec = rig.species.body as BirdSpec\n const tools = createRigTools(THREE, rig)\n const colors = spec.c\n const main = tools.material(colors.main)\n const dark = tools.material(colors.dark)\n const leg = tools.material(colors.leg)\n const beak = tools.material(colors.beak, { roughness: 0.5 })\n const belly = tools.material(colors.belly || colors.main)\n rig.main = main\n const hipY = spec.tars + spec.shank * 0.9\n rig.size = birdSize(spec)\n const bodyGroup = tools.pivot(rig.trunk, 0, hipY + spec.r * 0.55, 0)\n rig.bodyGroup = bodyGroup\n bodyGroup.rotation.x = spec.pitch || 0\n tools.ball(bodyGroup, spec.r, main, 0, 0, 0, spec.s[0], spec.s[1], spec.s[2])\n tools.ball(bodyGroup, spec.r * 0.9, belly, 0, -spec.r * 0.22, spec.r * 0.15, spec.s[0] * 0.9, spec.s[1] * 0.8, spec.s[2] * 0.85)\n const tail = tools.pivot(bodyGroup, 0, spec.r * 0.15, -spec.r * spec.s[2] * 0.85, new THREE.Vector3(0, 0, -0.08).multiplyScalar(rig.size))\n tail.rotation.x = spec.tailA || -0.3\n rig.tailRoot = tail\n addMesh(THREE, tail, new THREE.BoxGeometry(spec.tailW, spec.r * 0.08, spec.tailL).translate(0, 0, -spec.tailL / 2), spec.tailM ? tools.material(spec.tailM) : dark)\n buildWings(THREE, rig, spec, { bodyGroup, main, dark })\n const neck = tools.pivot(bodyGroup, 0, spec.r * 0.45, spec.r * spec.s[2] * 0.75, new THREE.Vector3(0, 0.05, 0.08).multiplyScalar(rig.size))\n neck.rotation.x = spec.neckA\n rig.neck = neck\n rig.neckAngle = spec.neckA\n tools.joint(neck, spec.nr * 1.1, spec.neckM ? tools.material(spec.neckM) : main, 0, 0, 0)\n addMesh(THREE, neck, new THREE.CylinderGeometry(spec.nr * 0.85, spec.nr, spec.nl, 12).translate(0, spec.nl / 2, 0), spec.neckM ? tools.material(spec.neckM) : main)\n const head = tools.pivot(neck, 0, spec.nl, 0, new THREE.Vector3(0, 0.05, 0.05).multiplyScalar(rig.size))\n head.rotation.x = -spec.neckA + (spec.headA || 0)\n rig.head = head\n rig.headAngle = head.rotation.x\n const headMaterial = spec.headM ? tools.material(spec.headM) : main\n tools.ball(head, spec.hr, headMaterial, 0, 0, 0, spec.hs ? spec.hs[0] : 1, spec.hs ? spec.hs[1] : 1, spec.hs ? spec.hs[2] : 1.1)\n addMesh(THREE, head, new THREE.ConeGeometry(spec.br, spec.bl, 8).rotateX(Math.PI / 2).translate(0, 0, spec.bl / 2), beak, 0, -spec.hr * 0.1, spec.hr * 0.85, spec.hook || 0, 0, 0)\n const jaw = tools.pivot(head, 0, -spec.hr * 0.25, spec.hr * 0.8)\n rig.jaw = jaw\n addMesh(THREE, jaw, new THREE.ConeGeometry(spec.br * 0.7, spec.bl * 0.85, 8).rotateX(Math.PI / 2).translate(0, 0, spec.bl * 0.42), beak).scale.set(1, 0.5, 1)\n tools.eyes(head, spec.hr * 0.17, spec.hr * 0.68, spec.hr * 0.2, spec.hr * 0.35)\n if (spec.eyeC && rig.eyeMaterial) {\n rig.eyeMaterial.color.set(spec.eyeC)\n rig.eyeMaterial.userData.base = rig.eyeMaterial.color.clone()\n }\n options.extra?.({ THREE, rig, tools }, { bodyGroup, head, neck, main, dark })\n buildLegs(THREE, rig, spec, { bodyGroup, main, leg })\n return rig\n}\n","import type * as THREE_NS from 'three'\n\nimport { addMesh } from './context'\nimport type { RigTools } from './context'\nimport type { HeadSpecOf, NeckSpecOf } from './extras-types'\nimport type { AnimalRig, Three } from './types'\n\nexport type ExtraContext = { readonly THREE: Three; readonly rig: AnimalRig; readonly tools: RigTools }\n\nexport type QuadrupedParts = {\n readonly bodyGroup: THREE_NS.Group\n readonly head: THREE_NS.Group\n readonly neck: THREE_NS.Group\n readonly main: THREE_NS.MeshStandardMaterial\n readonly belly: THREE_NS.MeshStandardMaterial\n readonly dark: THREE_NS.MeshStandardMaterial\n readonly L: number\n readonly Rb: number\n readonly bodyY: number\n readonly hd: HeadSpecOf\n readonly nk: NeckSpecOf\n}\n\nexport type BirdParts = {\n readonly bodyGroup: THREE_NS.Group\n readonly head: THREE_NS.Group\n readonly neck: THREE_NS.Group\n readonly main: THREE_NS.MeshStandardMaterial\n readonly dark: THREE_NS.MeshStandardMaterial\n}\n\nexport type QuadrupedExtra = (context: ExtraContext, parts: QuadrupedParts) => void\nexport type BirdExtra = (context: ExtraContext, parts: BirdParts) => void\n\nconst cow: QuadrupedExtra = ({ THREE, tools }, k) => {\n tools.ball(k.bodyGroup, 0.2, k.main, 0, k.Rb * 1.1, k.L * 0.33, 0.95, 1.1, 1.1) // hump\n tools.ball(k.neck, 0.13, k.main, 0, 0.12, 0.08, 0.55, 1.4, 1.05) // dewlap\n for (const side of [-1, 1]) addMesh(THREE, k.head, new THREE.ConeGeometry(0.025, 0.13, 8), tools.material(0xcfc6a8), side * 0.12, k.hd.r * 1.05, k.hd.r * 0.05, 0, 0, side * -0.9)\n}\n\nconst horse: QuadrupedExtra = ({ THREE, rig, tools }, k) => {\n for (let index = 0; index < 9; index++) addMesh(THREE, k.neck, new THREE.BoxGeometry(0.035, 0.16, 0.08), k.dark, 0, 0.06 + index * 0.065, -0.12 + index * 0.004, -0.25, 0, 0) // mane\n addMesh(THREE, k.head, new THREE.BoxGeometry(0.03, 0.12, 0.06), k.dark, 0, k.hd.r * 1.05, k.hd.r * 0.3, 0.6, 0, 0) // forelock\n const last = rig.tail[rig.tail.length - 1] as THREE_NS.Group\n tools.ball(last, 0.08, k.dark, 0, -0.42, 0, 0.7, 4.5, 0.7) // tail hair\n}\n\nconst wildBoar: QuadrupedExtra = ({ THREE, tools }, k) => {\n for (let index = 0; index < 12; index++) addMesh(THREE, k.bodyGroup, new THREE.BoxGeometry(0.03, 0.1, 0.06), k.dark, 0, k.Rb * 1.12 - index * 0.004, k.L * 0.38 - index * 0.06, -0.2, 0, 0) // bristles\n for (const side of [-1, 1])\n addMesh(THREE, k.head, new THREE.ConeGeometry(0.012, 0.09, 6), tools.material(0xe8e0cc), side * 0.07, k.hd.r * -0.25 + 0.03, k.hd.r * 0.25 + k.hd.r * k.hd.s[2] * 0.55 + 0.13, -0.6, 0, side * 0.5) // tusks\n}\n\nconst chicken: BirdExtra = ({ tools }, k) => {\n const red = tools.material(0xc0281e)\n for (let index = 0; index < 4; index++) tools.ball(k.head, 0.014, red, 0, 0.045, -0.012 + index * 0.014, 0.5, 1, 0.8) // comb\n tools.ball(k.head, 0.012, red, 0, -0.04, 0.04, 0.5, 1.3, 0.7) // wattle\n}\n\nconst southernLapwing: BirdExtra = ({ THREE, tools }, k) => {\n const black = tools.material(0x161616)\n tools.ball(k.bodyGroup, 0.05, black, 0, 0.03, 0.08, 1.1, 0.9, 0.6) // breast patch\n addMesh(THREE, k.head, new THREE.BoxGeometry(0.006, 0.006, 0.06).translate(0, 0, -0.03), black, 0, 0.025, -0.01, 0.5, 0, 0) // crest\n tools.ball(k.head, 0.026, black, 0, -0.005, 0.02, 1.05, 0.7, 0.9) // cap\n}\n\n/** The parts the page builds with code for five species (the page's `extra` closures), by species id. */\nexport const QUADRUPED_EXTRAS: Readonly<Record<string, QuadrupedExtra>> = { cow, horse, wild_boar: wildBoar }\nexport const BIRD_EXTRAS: Readonly<Record<string, BirdExtra>> = { chicken, southern_lapwing: southernLapwing }\n","import type { TextureMarker } from '@adatechnology/game-animals'\nimport type * as THREE_NS from 'three'\n\nimport { createStream } from './context'\nimport type { CanvasLike, DocumentLike, Three } from './types'\n\ntype Painter = {\n fillStyle: string\n strokeStyle: string\n lineWidth: number\n fillRect(x: number, y: number, width: number, height: number): void\n beginPath(): void\n arc(x: number, y: number, radius: number, from: number, to: number): void\n stroke(): void\n fill(): void\n}\n\nconst canvasTexture = (THREE: Three, document: DocumentLike, width: number, height: number, draw: (painter: Painter, width: number, height: number) => void): THREE_NS.CanvasTexture => {\n const canvas: CanvasLike = document.createElement('canvas')\n canvas.width = width\n canvas.height = height\n draw(canvas.getContext('2d') as Painter, width, height)\n const texture = new THREE.CanvasTexture(canvas as unknown as HTMLCanvasElement)\n texture.colorSpace = THREE.SRGBColorSpace\n return texture\n}\n\nconst spotsTexture = (THREE: Three, document: DocumentLike, base: string, spot: string): THREE_NS.CanvasTexture =>\n canvasTexture(THREE, document, 256, 256, (painter, width, height) => {\n painter.fillStyle = base\n painter.fillRect(0, 0, width, height)\n const next = createStream(11)\n for (let index = 0; index < 70; index++) {\n const x = next() * width\n const y = next() * height\n const radius = 5 + next() * 7\n painter.strokeStyle = spot\n painter.lineWidth = 3\n painter.beginPath()\n painter.arc(x, y, radius, 0, 7)\n painter.stroke()\n painter.fillStyle = 'rgba(120,70,20,.45)'\n painter.beginPath()\n painter.arc(x, y, radius * 0.6, 0, 7)\n painter.fill()\n }\n for (let index = 0; index < 60; index++) {\n painter.fillStyle = spot\n painter.beginPath()\n painter.arc(next() * width, next() * height, 1.5 + next() * 2.5, 0, 7)\n painter.fill()\n }\n })\n\nconst stripesTexture = (THREE: Three, document: DocumentLike, base: string, stripe: string): THREE_NS.CanvasTexture =>\n canvasTexture(THREE, document, 128, 128, (painter, width) => {\n painter.fillStyle = base\n painter.fillRect(0, 0, width, 128)\n painter.fillStyle = stripe\n for (let index = 0; index < 9; index++) painter.fillRect(0, index * 14 + (index % 2) * 3, width, 4)\n })\n\n/** The coat texture a species spec asks for (cat stripes, jaguar rosettes), painted on a canvas. */\nexport const createCoatTexture = (THREE: Three, document: DocumentLike, marker: TextureMarker): THREE_NS.CanvasTexture =>\n marker.kind === 'stripes' ? stripesTexture(THREE, document, marker.base, marker.stripe) : spotsTexture(THREE, document, marker.base, marker.spot)\n","import type * as THREE_NS from 'three'\n\nimport type { Three } from './types'\n\n/** A body of revolution whose radius follows `profile` along the length, rear to front, closed by a point at each end. */\nexport const profileGeometry = (THREE: Three, length: number, profile: readonly number[], segments?: number): THREE_NS.LatheGeometry => {\n const points = profile.map((radius, index) => new THREE.Vector2(index / (profile.length - 1), radius))\n const curve = new THREE.SplineCurve(points)\n const outline: THREE_NS.Vector2[] = []\n outline.push(new THREE.Vector2(0.001, -length / 2 - 0.0))\n for (let step = 0; step <= 30; step++) {\n const point = curve.getPoint(step / 30)\n outline.push(new THREE.Vector2(Math.max(0.001, point.y), -length / 2 + point.x * length))\n }\n outline.push(new THREE.Vector2(0.001, length / 2))\n return new THREE.LatheGeometry(outline, segments || 22).rotateX(Math.PI / 2)\n}\n","import { quadrupedLayout } from '@adatechnology/game-animals'\nimport type { QuadrupedSpec } from '@adatechnology/game-animals'\nimport type * as THREE_NS from 'three'\n\nimport { addMesh, createRigTools } from './context'\nimport { createCoatTexture } from './textures'\nimport { profileGeometry } from './profile'\nimport type { AnimalRig, DocumentLike, Three } from './types'\nimport type { QuadrupedExtra } from './extras'\n\nconst RIB_COUNT = 4\n\ntype LegDefinition = readonly [number, number, 'F' | 'H']\n\nconst LEG_DEFINITIONS: readonly LegDefinition[] = [\n [1, 1, 'F'],\n [-1, 1, 'F'],\n [1, -1, 'H'],\n [-1, -1, 'H'],\n]\n\n/** The body and the four legs of a quadruped (page: the first half of `quad`). */\nconst buildBodyAndLegs = (THREE: Three, rig: AnimalRig, spec: QuadrupedSpec, main: THREE_NS.MeshStandardMaterial, belly: THREE_NS.MeshStandardMaterial, dark: THREE_NS.MeshStandardMaterial) => {\n const tools = createRigTools(THREE, rig)\n const { L, R: radius } = spec\n const leg = spec.leg\n const layout = quadrupedLayout(spec)\n const { frontHipHeight, hindHipHeight, footHeight, bodyHeight, span } = layout\n const frontAngle1 = leg.fa1 ?? 0.06\n const frontAngle2 = leg.fa2 ?? -0.08\n const hindAngle1 = leg.ha1 ?? -0.32\n const hindAngle2 = leg.ha2 ?? 0.58\n rig.size = layout.size\n const bodyGroup = tools.pivot(rig.trunk, 0, bodyHeight, 0)\n rig.bodyGroup = bodyGroup\n const body = addMesh(THREE, bodyGroup, profileGeometry(THREE, L, spec.prof.map((value) => value * radius)), main)\n body.scale.set(spec.sx || 1, spec.sy || 1, 1)\n body.rotation.x = Math.atan2(hindHipHeight - frontHipHeight, span) * 0.6\n rig.body = body\n const belt = addMesh(THREE, bodyGroup, profileGeometry(THREE, L * 0.8, spec.prof.map((value) => value * 0.8 * radius)), belly)\n belt.scale.set((spec.sx || 1) * 0.92, (spec.sy || 1) * 0.85, 1)\n belt.position.y = -radius * 0.2\n LEG_DEFINITIONS.forEach(([sx, sz, kind]) => {\n const front = kind === 'F'\n const upper = front ? leg.fu : leg.hu\n const lower = front ? leg.fl : leg.hl\n const height = front ? frontHipHeight : hindHipHeight\n const hip = tools.pivot(rig.trunk, sx * radius * (spec.sx || 1) * 0.55, height, (sz * span) / 2, new THREE.Vector3(sx * 0.18, -0.06, sz * 0.12).multiplyScalar(rig.size))\n tools.ball(hip, leg.r * (front ? 1.9 : 2.3), main, 0, -upper * 0.18, front ? 0 : -leg.r * 0.3, 0.75, 1.25, 1)\n tools.joint(hip, leg.r * 1.15, main, 0, 0, 0)\n tools.limb(hip, leg.r * 1.25, leg.r * 0.95, upper, main)\n const knee = tools.pivot(hip, 0, -upper, 0, new THREE.Vector3(0, -0.07 * rig.size, 0))\n tools.joint(knee, leg.r * 0.95, main, 0, 0, 0)\n tools.limb(knee, leg.r * 0.92, leg.r * 0.68, lower, main)\n const foot = tools.pivot(knee, 0, -lower, 0, new THREE.Vector3(0, -0.05 * rig.size, 0))\n if (leg.hoof) {\n tools.joint(foot, leg.r * 0.72, dark, 0, 0, 0)\n addMesh(THREE, foot, new THREE.CylinderGeometry(leg.r * 0.8, leg.r * 1.05, footHeight, 12).translate(0, -footHeight / 2, 0), dark)\n } else {\n tools.joint(foot, leg.r * 0.66, main, 0, 0, 0)\n tools.ball(foot, leg.r * 0.95, leg.dark ? dark : main, 0, -footHeight * 0.55, leg.r * 0.5, 1, 0.62, 1.45)\n }\n rig.legs.push({ hip, knee, foot, isFront: front, angle1: front ? frontAngle1 : hindAngle1, angle2: front ? frontAngle2 : hindAngle2, side: sx })\n })\n return { bodyGroup, bodyHeight }\n}\n\nconst buildHead = (THREE: Three, rig: AnimalRig, spec: QuadrupedSpec, parts: { bodyGroup: THREE_NS.Group; main: THREE_NS.MeshStandardMaterial; belly: THREE_NS.MeshStandardMaterial; nose: THREE_NS.MeshStandardMaterial }) => {\n const tools = createRigTools(THREE, rig)\n const { bodyGroup, main, belly, nose } = parts\n const { L, R: radius } = spec\n const neckSpec = spec.neck\n const headSpec = spec.head\n const neck = tools.pivot(bodyGroup, 0, radius * (spec.sy || 1) * (neckSpec.y ?? 0.35), (L / 2) * (neckSpec.z ?? 0.78), new THREE.Vector3(0, 0.05, 0.12).multiplyScalar(rig.size))\n neck.rotation.x = neckSpec.ang\n rig.neck = neck\n rig.neckAngle = neckSpec.ang\n tools.joint(neck, neckSpec.r * 1.05, main, 0, 0, 0)\n addMesh(THREE, neck, new THREE.CylinderGeometry(neckSpec.r2 || neckSpec.r * 0.85, neckSpec.r, neckSpec.len, 14).translate(0, neckSpec.len / 2, 0), main)\n const head = tools.pivot(neck, 0, neckSpec.len, 0, new THREE.Vector3(0, 0.06, 0.08).multiplyScalar(rig.size))\n head.rotation.x = -neckSpec.ang + (headSpec.down || 0)\n rig.head = head\n rig.headAngle = head.rotation.x\n tools.joint(head, neckSpec.r2 || neckSpec.r * 0.85, main, 0, 0, 0)\n tools.ball(head, headSpec.r, main, 0, headSpec.r * 0.25, headSpec.r * 0.25, headSpec.s[0], headSpec.s[1], headSpec.s[2])\n const snoutZ = headSpec.r * 0.25 + headSpec.r * headSpec.s[2] * 0.55\n const snout = addMesh(THREE, head, new THREE.CylinderGeometry(headSpec.sr2, headSpec.sr1, headSpec.sl, 14).rotateX(Math.PI / 2), headSpec.muzzle ? tools.material(headSpec.muzzle) : main, 0, headSpec.r * (headSpec.sy ?? 0), snoutZ + headSpec.sl / 2)\n snout.scale.set(1, headSpec.sq || 1, 1)\n tools.ball(head, headSpec.sr2 * 1.02, nose, 0, headSpec.r * (headSpec.sy ?? 0) + headSpec.sr2 * 0.1, snoutZ + headSpec.sl, 1, 0.85 * (headSpec.sq || 1), 0.45)\n const jaw = tools.pivot(head, 0, headSpec.r * (headSpec.sy ?? 0) - headSpec.sr1 * 0.55 * (headSpec.sq || 1), snoutZ - headSpec.sr1 * 0.2, new THREE.Vector3(0, -0.04, 0.04).multiplyScalar(rig.size))\n rig.jaw = jaw\n addMesh(THREE, jaw, new THREE.CylinderGeometry(headSpec.sr2 * 0.7, headSpec.sr1 * 0.75, headSpec.sl * 0.92, 12).rotateX(Math.PI / 2).translate(0, 0, headSpec.sl * 0.46), belly).scale.set(1, 0.45, 1)\n const blood = tools.ball(jaw, headSpec.sr2 * 0.9, rig.woundMaterial || (rig.woundMaterial = new THREE.MeshStandardMaterial({ color: 0x6a100c, roughness: 0.3 })), 0, 0.0, headSpec.sl * 0.9, 1.1, 0.6, 0.6)\n tools.eyes(head, headSpec.r * 0.13, headSpec.r * headSpec.s[0] * 0.52, headSpec.r * 0.5, headSpec.r * 0.25 + headSpec.r * headSpec.s[2] * 0.55)\n const ears = spec.ears\n for (const side of [-1, 1]) {\n const earGroup = tools.pivot(head, side * headSpec.r * headSpec.s[0] * 0.55, headSpec.r * (0.25 + headSpec.s[1] * 0.75), headSpec.r * 0.1, new THREE.Vector3(side * 0.04, 0.04, 0).multiplyScalar(rig.size))\n rig.ears.push(earGroup)\n if (ears.t === 'cone') addMesh(THREE, earGroup, new THREE.ConeGeometry(ears.s * 0.45, ears.s, 8).translate(0, ears.s / 2, 0), main, 0, 0, 0, ears.fwd || 0, 0, side * -(ears.tilt ?? 0.3))\n else if (ears.t === 'round') tools.ball(earGroup, ears.s, main, 0, ears.s * 0.4, 0, 1, 1, 0.35)\n else if (ears.t === 'flop') {\n const ear = tools.ball(earGroup, ears.s, main, side * ears.s * 0.9, -ears.s * 0.1, 0, 1.5, 0.45, 0.7)\n ear.rotation.z = side * -0.25\n }\n }\n return { neck, head, blood, neckSpec, headSpec }\n}\n\nconst buildTail = (THREE: Three, rig: AnimalRig, spec: QuadrupedSpec, parts: { bodyGroup: THREE_NS.Group; main: THREE_NS.MeshStandardMaterial; dark: THREE_NS.MeshStandardMaterial }): void => {\n const tools = createRigTools(THREE, rig)\n const tailSpec = spec.tail\n if (!tailSpec || !tailSpec.n) return\n const { bodyGroup, main, dark } = parts\n const { L, R: radius } = spec\n let parent = tools.pivot(bodyGroup, 0, radius * (spec.sy || 1) * (tailSpec.y ?? 0.3), (-L / 2) * 0.95, new THREE.Vector3(0, 0.02, -0.12).multiplyScalar(rig.size))\n parent.rotation.x = tailSpec.ang\n rig.tailRoot = parent\n const segmentLength = tailSpec.len / tailSpec.n\n for (let index = 0; index < tailSpec.n; index++) {\n const radius1 = tailSpec.r * (1 - (index / tailSpec.n) * 0.6)\n const radius2 = tailSpec.r * (1 - ((index + 1) / tailSpec.n) * 0.6)\n tools.limb(parent, radius1, radius2, segmentLength, tailSpec.dark && index === tailSpec.n - 1 ? dark : main)\n tools.joint(parent, radius1, main, 0, 0, 0)\n rig.tail.push(parent)\n if (index < tailSpec.n - 1) {\n const next = tools.pivot(parent, 0, -segmentLength, 0)\n next.rotation.x = tailSpec.curl || 0\n parent = next\n } else if (tailSpec.tuft) tools.ball(parent, tailSpec.r * 2.2, dark, 0, -segmentLength - tailSpec.r, 0, 1, 1.8, 1)\n }\n if (tailSpec.bushy) tools.ball(rig.tail[0] as THREE_NS.Group, tailSpec.r * 2.2, dark, 0, -tailSpec.len * 0.55, 0, 1, (tailSpec.len * 2.2) / tailSpec.r / 6, 1)\n}\n\n/** Wounds, ribs and the blood of the jaw: shown only while the animal is infected (page: the end of `quad`). */\nconst buildInfectionParts = (THREE: Three, rig: AnimalRig, spec: QuadrupedSpec, parts: { bodyGroup: THREE_NS.Group; neck: THREE_NS.Group; blood: THREE_NS.Mesh; neckSpec: QuadrupedSpec['neck'] }): void => {\n const tools = createRigTools(THREE, rig)\n const { bodyGroup, neck, blood, neckSpec } = parts\n const { L, R: radius } = spec\n const sideX = radius * (spec.sx || 1)\n const sideY = radius * (spec.sy || 1)\n const list: THREE_NS.Object3D[] = [blood]\n const wound = (parent: THREE_NS.Object3D, size: number, x: number, y: number, z: number, sx: number, sy: number, sz: number): THREE_NS.Mesh =>\n tools.ball(parent, size, rig.woundMaterial || (rig.woundMaterial = new THREE.MeshStandardMaterial({ color: 0x5a120e, roughness: 0.45 })), x, y, z, sx || 1, sy || 1, sz || 1)\n list.push(wound(bodyGroup, radius * 0.28, sideX * 0.92, sideY * 0.15, L * 0.05, 0.35, 1, 1.3))\n list.push(wound(bodyGroup, radius * 0.2, -sideX * 0.9, sideY * 0.3, -L * 0.2, 0.35, 1, 1.1))\n list.push(wound(neck, neckSpec.r * 0.5, neckSpec.r * 0.85, neckSpec.len * 0.5, 0, 0.4, 1, 1))\n const bone = tools.material(0xd9cfb8, { roughness: 0.6 })\n for (let index = 0; index < RIB_COUNT; index++) {\n const rib = addMesh(THREE, bodyGroup, new THREE.TorusGeometry(radius * 0.95, radius * 0.035, 6, 16, Math.PI * 0.42), bone, sideX * 0.12, sideY * 0.05, L * 0.12 - index * L * 0.07, 0, Math.PI / 2, -0.3)\n rib.scale.set(1, 1.02 * (spec.sy || 1), spec.sx || 1)\n list.push(rib)\n }\n rig.infectionParts.push(...list)\n list.forEach((part) => (part.visible = false))\n}\n\n/** Builds a quadruped: body, four legs on hip-knee-foot pivots, neck, head with jaw and eyes, ears, tail, the species' extra parts, and the infection parts. */\nexport const buildQuadruped = (THREE: Three, rig: AnimalRig, options: { document: DocumentLike; extra?: QuadrupedExtra }): AnimalRig => {\n const spec = rig.species.body as QuadrupedSpec\n const tools = createRigTools(THREE, rig)\n const colors = spec.c\n const texture = spec.texture ? createCoatTexture(THREE, options.document, spec.texture) : undefined\n const main = tools.material(texture ? 0xffffff : colors.main, texture ? { map: texture } : undefined)\n const belly = tools.material(colors.belly)\n const dark = tools.material(colors.dark)\n const nose = tools.material(colors.nose || colors.dark, { roughness: 0.4 })\n rig.main = main\n rig.dark = dark\n const { bodyGroup, bodyHeight } = buildBodyAndLegs(THREE, rig, spec, main, belly, dark)\n const { neck, head, blood, neckSpec, headSpec } = buildHead(THREE, rig, spec, { bodyGroup, main, belly, nose })\n buildTail(THREE, rig, spec, { bodyGroup, main, dark })\n options.extra?.({ THREE, rig, tools }, { bodyGroup, head, neck, main, belly, dark, L: spec.L, Rb: spec.R, bodyY: bodyHeight, hd: headSpec, nk: neckSpec })\n if (rig.species.outcome === 'turns') buildInfectionParts(THREE, rig, spec, { bodyGroup, neck, blood, neckSpec })\n else blood.visible = false\n return rig\n}\n","import { addMesh, createRigTools } from './context'\nimport type { AnimalRig, Three } from './types'\n\nconst SEGMENT_COUNT = 34\nconst SEGMENT_LENGTH = 0.042\n\n/** Builds a snake: 34 spheres on their own groups along the trunk (the animation lays them on a path), a head with a jaw and a tongue. */\nexport const buildSnake = (THREE: Three, rig: AnimalRig): AnimalRig => {\n const tools = createRigTools(THREE, rig)\n rig.size = 0.5\n rig.segmentLength = SEGMENT_LENGTH\n rig.segmentCount = SEGMENT_COUNT\n const primary = tools.material(0x7a6a4a)\n const secondary = tools.material(0x3a2e22)\n const belly = tools.material(0xb8a888)\n rig.main = primary\n for (let index = 0; index < SEGMENT_COUNT; index++) {\n const t = index / (SEGMENT_COUNT - 1)\n const radius = 0.012 + 0.03 * Math.sin(Math.min(1, t * 1.15) * Math.PI) * (t < 0.1 ? 0.75 + t * 2.5 : 1) + (t < 0.05 ? 0.012 : 0)\n const material = index % 4 === 1 || index % 4 === 2 ? secondary : primary\n const group = new THREE.Group()\n rig.trunk.add(group)\n const sphere = tools.ball(group, radius, material, 0, radius, 0, 1, 0.78, 1.5)\n sphere.userData.joint = index % 3 === 0 ? 1 : 0\n if (index % 3 === 0) rig.joints.push(sphere)\n rig.segments.push({ group, radius })\n }\n const head = new THREE.Group()\n rig.trunk.add(head)\n rig.head = head\n tools.ball(head, 0.032, primary, 0, 0.03, 0.025, 1.25, 0.6, 1.45)\n tools.ball(head, 0.024, secondary, 0, 0.04, 0.0, 1.3, 0.4, 0.9)\n tools.ball(head, 0.02, primary, 0, 0.028, 0.06, 0.85, 0.55, 1.1)\n const jaw = new THREE.Group()\n jaw.position.set(0, 0.018, 0.0)\n head.add(jaw)\n rig.jaw = jaw\n tools.ball(jaw, 0.026, belly, 0, 0, 0.035, 1.1, 0.35, 1.5)\n tools.eyes(head, 0.006, 0.03, 0.04, 0.045)\n rig.tongue = addMesh(THREE, head, new THREE.BoxGeometry(0.004, 0.002, 0.05), tools.material(0xb02020), 0, 0.024, 0.1)\n return rig\n}\n","import type { AnimalVisualState } from '@adatechnology/game-animals'\nimport type * as THREE_NS from 'three'\n\nimport type { AnimalRig, Three } from './types'\n\nconst INFECTED_TINT = 0x7d8a66\nconst POOL_COLOR = 0x3a0806\n\n/**\n * Applies a visual state (page: `setInfected`, with the dead look for every species): infected greys the coat, glows the eyes, shows the wounds and hides\n * the first ear; dead lets the animation lay the body down and shows a blood pool at `home`. The pool goes under `rig.root.parent`; with no parent yet,\n * it is created hidden from the scene and `rig.pool` is for the caller to add.\n */\nexport const applyVisualState = (THREE: Three, rig: AnimalRig, state: AnimalVisualState): void => {\n rig.infected = state === 'infected'\n rig.dead = state === 'dead'\n const tint = new THREE.Color(INFECTED_TINT)\n rig.materials.forEach((material) => {\n if (material === rig.eyeMaterial || material === rig.woundMaterial) return\n material.color.copy(material.userData.base as THREE_NS.Color)\n if (rig.infected) material.color.lerp(tint, 0.45).multiplyScalar(0.72)\n })\n const eye = rig.eyeMaterial\n if (eye) {\n if (rig.infected) {\n eye.color.set(0xdcd8b0)\n eye.emissive = new THREE.Color(0x8a8660)\n eye.emissiveIntensity = 0.8\n } else {\n eye.color.copy(eye.userData.base as THREE_NS.Color)\n eye.emissive = new THREE.Color(0)\n }\n }\n rig.infectionParts.forEach((part) => (part.visible = rig.infected))\n const firstEar = rig.ears[0]\n if (firstEar) firstEar.visible = !rig.infected\n if (!rig.dead) {\n rig.root.rotation.z = 0\n rig.root.position.y = 0\n }\n if (rig.pool) {\n rig.pool.visible = rig.dead\n return\n }\n if (!rig.dead) return\n const pool = new THREE.Mesh(new THREE.CircleGeometry(1, 22).rotateX(-Math.PI / 2), new THREE.MeshStandardMaterial({ color: POOL_COLOR, roughness: 0.25 }))\n pool.position.set(rig.home.x, 0.005, rig.home.z + 0.1)\n pool.receiveShadow = true\n pool.scale.setScalar(rig.size * 0.45)\n rig.root.parent?.add(pool)\n rig.pool = pool\n}\n","import type * as THREE_NS from 'three'\n\nimport type { AnimalRig } from './types'\n\n/** The \"joints\" view (page: `setJoint`): the coat turns see-through and every joint ball shows in `jointMaterial`. */\nexport const setJointView = (rig: AnimalRig, on: boolean, jointMaterial: THREE_NS.Material): void => {\n rig.materials.forEach((material) => {\n if (material === rig.eyeMaterial) return\n material.transparent = on\n material.opacity = on ? 0.28 : 1\n material.depthWrite = !on\n material.needsUpdate = true\n })\n rig.joints.forEach((joint) => {\n if (on) joint.userData.restMaterial ??= joint.material\n joint.material = on ? jointMaterial : (joint.userData.restMaterial as THREE_NS.Material)\n joint.renderOrder = on ? 2 : 0\n })\n}\n\n/** The \"split\" view (page: `setSplit`): sets where each pivot should go, the rest position or the rest plus its offset; `updateSplit` moves it there. */\nexport const setSplit = (rig: AnimalRig, on: boolean): void => {\n rig.pivots.forEach((pivot) => {\n pivot.group.userData.target = on ? pivot.rest.clone().add(pivot.offset) : pivot.rest.clone()\n })\n}\n\n/** Moves each pivot toward its split target (the page does it in the frame loop: a lerp of 5 per second). */\nexport const updateSplit = (rig: AnimalRig, delta: number): void => {\n rig.pivots.forEach((pivot) => {\n const target = pivot.group.userData.target as THREE_NS.Vector3 | undefined\n if (target) pivot.group.position.lerp(target, Math.min(1, delta * 5))\n })\n}\n","import { lineUpLength } from '@adatechnology/game-animals'\n\nimport type { AnimalRig } from './types'\n\nexport type LineUpOptions = {\n /** X where the row starts (page: -19). */\n readonly start?: number\n /** Gap between animals (page: 0.85). */\n readonly gap?: number\n /** Heading of the animals, radians (page: 1.1; snakes 2.2). */\n readonly yaw?: number\n readonly snakeYaw?: number\n}\n\n/** Puts the animals in a row on the x axis, each given room by its length, the row centred on the origin, and keeps each place as `home` (page: the line-up). */\nexport const lineUpAnimals = (rigs: readonly AnimalRig[], options: LineUpOptions = {}): void => {\n const start = options.start ?? -19\n const gap = options.gap ?? 0.85\n let cursor = start\n for (const rig of rigs) {\n const length = lineUpLength(rig.species.body)\n cursor += length / 2\n rig.root.position.set(cursor, 0, 0)\n rig.home = rig.root.position.clone()\n rig.root.rotation.y = rig.species.body.kind === 'snake' ? (options.snakeYaw ?? 2.2) : (options.yaw ?? 1.1)\n cursor += length / 2 + gap\n }\n const offset = -(cursor - gap + start) / 2\n for (const rig of rigs) {\n rig.root.position.x += offset\n rig.home.x += offset\n }\n}\n","import { visualStateOf } from '@adatechnology/game-animals'\nimport type { AnimalSpecies } from '@adatechnology/game-animals'\nimport type { ModuleScenario } from '@adatechnology/game-contracts'\n\nimport type { AnimalsThree } from '../index'\nimport type { OutbreakLike } from './types'\n\n/** What a scenario needs of the room: the pass/fail line and time. The test room's `ScenarioContext` has both. */\nexport type AnimalsContext = { expect(condition: boolean, label: string, data?: Record<string, unknown>): void; step(seconds: number): void }\n\nexport type ScenarioDeps = {\n readonly text: Readonly<Record<string, string>>\n species(): readonly AnimalSpecies[]\n ensure(): AnimalsThree\n outbreak(): OutbreakLike | null\n rebuildOutbreak(enabled: boolean): OutbreakLike | null\n skins(): { count: number; meshes: { drawCalls: number; triangles: number }; nodes: number; rich: number }\n clearSkins(): void\n}\n\nconst SKIN_COUNT = 40\n\nexport const createScenarios = (deps: ScenarioDeps): ModuleScenario<AnimalsContext>[] => {\n const scenario = (id: string, run: ModuleScenario<AnimalsContext>['run']): ModuleScenario<AnimalsContext> => ({ id, name: deps.text[id] ?? id, run })\n const rate = () => 0.5\n return [\n scenario('visual_states', ({ expect }) => {\n const animals = deps.ensure()\n for (const species of deps.species()) {\n const rig = animals.build(species)\n animals.setVisualState(rig, 'infected')\n expect(rig.infected && !rig.dead, `${species.id}: infected`)\n animals.setVisualState(rig, 'dead')\n expect(rig.dead && !rig.infected, `${species.id}: dead`)\n animals.setVisualState(rig, 'alive')\n expect(!rig.dead && !rig.infected, `${species.id}: alive again`)\n }\n }),\n scenario('gait_moves_legs', ({ expect }) => {\n const animals = deps.ensure()\n const species = deps.species().find((entry) => entry.body.kind === 'quadruped')\n if (!species) return expect(false, 'no quadruped among the species')\n const rig = animals.build(species)\n animals.animate(rig, { time: 0, delta: 0, clip: 'walk' })\n const before = rig.legs.map((leg) => leg.hip.rotation.x)\n animals.animate(rig, { time: 0.4, delta: 0.4, clip: 'walk' })\n expect(rig.legs.some((leg, index) => leg.hip.rotation.x !== before[index]), `${species.id}: a walk step swings a hip`)\n }),\n scenario('outbreak_rule', ({ expect }) => {\n const turns = deps.species().find((entry) => entry.outcome === 'turns')\n const immune = deps.species().find((entry) => entry.outcome === 'immune')\n const on = deps.rebuildOutbreak(true)\n if (!on || !turns) return expect(false, 'no outbreak rule or no species that turns')\n expect(on.bite(turns.id, rate, 0) !== undefined, `a bite on ${turns.id} starts a fate with the rule on`)\n if (immune) expect(on.bite(immune.id, rate, 0) === undefined, `${immune.id} shrugs the bite off`)\n const off = deps.rebuildOutbreak(false)\n expect(off?.bite(turns.id, rate, 0) === undefined, 'with the rule off a bite does nothing')\n deps.rebuildOutbreak(true)\n }),\n scenario('carcass_lure', ({ expect }) => {\n const outbreak = deps.rebuildOutbreak(true)\n const dies = deps.species().find((entry) => entry.outbreak?.carcass)\n if (!outbreak || !dies) return expect(false, 'no outbreak rule or no species that leaves a carcass')\n const carcass = outbreak.carcass(dies.id, [0, 0, 0], 0)\n expect(carcass !== undefined, `${dies.id} leaves a carcass`)\n if (!carcass) return\n expect(outbreak.lures(carcass, carcass.lureRadiusMetres - 1, 1), 'a zombie inside the radius is drawn')\n expect(!outbreak.lures(carcass, carcass.lureRadiusMetres + 1, 1), 'a zombie outside it is not')\n }),\n scenario('bite_to_turn_timer', ({ expect }) => {\n const outbreak = deps.rebuildOutbreak(true)\n const turns = deps.species().find((entry) => entry.outcome === 'turns')\n if (!outbreak || !turns) return expect(false, 'no outbreak rule or no species that turns')\n const fate = outbreak.bite(turns.id, rate, 0)\n if (!fate) return expect(false, 'the bite took')\n expect(outbreak.stateOf(fate, 0) === 'incubating', 'it incubates at first')\n expect(outbreak.stateOf(fate, fate.dueAt + 1) === 'turned', 'it has turned once the time is up (the bench clock runs it accelerated)')\n expect(visualStateOf(turns.outcome, true) === 'infected', 'and shows the infected look')\n }),\n scenario('skins_40', ({ expect }) => {\n const result = deps.skins()\n expect(result.count === SKIN_COUNT, `${SKIN_COUNT} animals are in the field`, { count: result.count })\n expect(result.meshes.drawCalls <= SKIN_COUNT + 16, 'the skins are about one draw call each', result.meshes)\n expect(result.nodes > 0, 'each skin keeps the nodes the animation moves', { nodes: result.nodes })\n deps.clearSkins()\n }),\n ]\n}\n","export type AnimalsText = {\n group: string\n name: string\n controls: Record<'visual_state' | 'gait' | 'speed' | 'outbreak' | 'time_scale', string>\n hints: Record<'visual_state' | 'gait' | 'outbreak' | 'time_scale', string>\n options: { visual_state: Record<string, string>; gait: Record<string, string> }\n actions: Record<'bite_all' | 'recover' | 'skin_40' | 'clear_skins', string>\n layers: Record<'joints' | 'split' | 'sound_range' | 'carcass_lure', string>\n metrics: Record<'animals' | 'draw_calls' | 'triangles' | 'nodes_per_skin' | 'incubating' | 'turned' | 'dead', string>\n scenarios: Record<string, string>\n}\n\nexport const TEXT: Record<string, AnimalsText> = {\n en: {\n group: 'Animals',\n name: 'Animals area',\n controls: { visual_state: 'Visual state', gait: 'Gait', speed: 'Animation speed', outbreak: 'Outbreak rule', time_scale: 'Bite clock' },\n hints: {\n visual_state: 'The look of every animal: alive, infected (grey coat, glowing eyes, wounds) or dead (lying down, a pool of blood)',\n gait: 'idle, walk or run; the cycle of each species runs at its own rate',\n outbreak: 'Off: a bite does nothing. On: a species that turns incubates and turns, one that dies dies, the rest shrug it off',\n time_scale: 'Game seconds per real second for the bite timers (60: ten minutes pass in ten seconds)',\n },\n options: { visual_state: { alive: 'Alive', infected: 'Infected', dead: 'Dead' }, gait: { idle: 'Idle', walk: 'Walk', run: 'Run' } },\n actions: { bite_all: 'Bite every animal', recover: 'Heal every animal', skin_40: 'Add 40 animals as node skins', clear_skins: 'Remove the skins' },\n layers: { joints: 'Joints', split: 'Split view', sound_range: 'Sound range', carcass_lure: 'Carcass lure radius' },\n metrics: { animals: 'Animals', draw_calls: 'Draw calls', triangles: 'Triangles', nodes_per_skin: 'Nodes per skin', incubating: 'Incubating', turned: 'Turned', dead: 'Dead' },\n scenarios: {\n visual_states: 'Alive, infected and dead for every species',\n gait_moves_legs: 'The gait moves the legs',\n outbreak_rule: 'Outbreak rule on and off',\n carcass_lure: 'A carcass lures zombies within its radius',\n bite_to_turn_timer: 'Bite, incubation, turn (accelerated)',\n skins_40: '40 animals as node skins',\n },\n },\n 'pt-BR': {\n group: 'Animais',\n name: 'Área de animais',\n controls: { visual_state: 'Estado visual', gait: 'Andar', speed: 'Velocidade da animação', outbreak: 'Regra de surto', time_scale: 'Relógio da mordida' },\n hints: {\n visual_state: 'O aspecto de todos os bichos: vivo, infectado (pelo cinza, olhos brilhando, feridas) ou morto (deitado, poça de sangue)',\n gait: 'idle, walk ou run; o ciclo de cada espécie anda no seu ritmo',\n outbreak: 'Desligada: a mordida não faz nada. Ligada: a espécie que vira incuba e vira, a que morre morre, as outras não sentem',\n time_scale: 'Segundos de jogo por segundo real nos prazos da mordida (60: dez minutos passam em dez segundos)',\n },\n options: { visual_state: { alive: 'Vivo', infected: 'Infectado', dead: 'Morto' }, gait: { idle: 'Parado', walk: 'Andando', run: 'Correndo' } },\n actions: { bite_all: 'Morder todos os bichos', recover: 'Curar todos os bichos', skin_40: 'Pôr 40 bichos como pele de nós', clear_skins: 'Tirar as peles' },\n layers: { joints: 'Juntas', split: 'Vista aberta', sound_range: 'Alcance do som', carcass_lure: 'Raio de atração da carcaça' },\n metrics: { animals: 'Bichos', draw_calls: 'Chamadas de desenho', triangles: 'Triângulos', nodes_per_skin: 'Nós por pele', incubating: 'Incubando', turned: 'Viraram', dead: 'Morreram' },\n scenarios: {\n visual_states: 'Vivo, infectado e morto em cada espécie',\n gait_moves_legs: 'O andar mexe as pernas',\n outbreak_rule: 'Regra de surto ligada e desligada',\n carcass_lure: 'A carcaça atrai zumbis dentro do raio',\n bite_to_turn_timer: 'Mordida, incubação, vira (acelerado)',\n skins_40: '40 bichos como pele de nós',\n },\n },\n}\n","import { lineUpLength, visualStateOf } from '@adatechnology/game-animals'\nimport type { AnimalSpecies, AnimalVisualState } from '@adatechnology/game-animals'\nimport type { BenchChange, ModuleArea, ModuleControl, ModuleHost, ModuleLayer, ModuleMetric, ModuleScenario, TestableItem, TestableModule } from '@adatechnology/game-contracts'\nimport type { NodeSkin } from '@adatechnology/game-props3d'\nimport type * as THREE_NS from 'three'\n\nimport { createAnimalsThree } from '../index'\nimport type { AnimalsThree } from '../index'\nimport type { AnimalRig, Three } from '../types'\nimport { createScenarios } from './scenarios'\nimport type { AnimalsContext, ScenarioDeps } from './scenarios'\nimport { TEXT } from './text'\nimport type { AnimalsTestOptions, OutbreakLike } from './types'\n\nexport const ANIMALS_AREA_ID = 'animals_area'\nexport const ANIMAL_ITEM_PREFIX = 'animal_'\nconst SKIN_COUNT = 40\nconst SKIN_FIELD = { x0: -18, z0: -10, columns: 8, gap: 4.5 }\nconst DEFAULT_TIME_SCALE = 60\n\ntype Fate = NonNullable<ReturnType<OutbreakLike['bite']>>\ntype Placed = { readonly id: string; readonly rig: AnimalRig; fate?: Fate; counted?: 'turned' | 'dead' }\n\n/**\n * The animals test area as a `TestableModule`: the species of the pack in a row, each drawn by the Bichos do Vale builders, with the alive / infected / dead looks, the gait,\n * the joints and split views, the rings for how far its sound carries and how far a carcass lures, the outbreak rule with an accelerated bite clock, and a field of 40 animals\n * drawn as node skins. The species and the outbreak rule come in by option, so this package knows no game.\n */\nexport function createAnimalsTestModule(options: AnimalsTestOptions = {}): TestableModule<AnimalsContext> & { readonly placed: ReadonlyMap<string, unknown> } {\n const locale = options.locale ?? 'pt-BR'\n const text = TEXT[locale] ?? (TEXT['pt-BR'] as (typeof TEXT)[string])\n const species = options.species ?? []\n const settings = { visual: 'alive' as AnimalVisualState, gait: 'walk', speed: 1, outbreak: true, timeScale: DEFAULT_TIME_SCALE }\n let three: Three | null = null\n let animals: AnimalsThree | null = null\n let host: ModuleHost | null = null\n let outbreak: OutbreakLike | null = null\n let time = 0\n let clock = 0\n let skinGroup: THREE_NS.Group | null = null\n let skins: { rig: AnimalRig; skin: NodeSkin }[] = []\n let rings: THREE_NS.Object3D[] = []\n const placed = new Map<string, Placed>()\n const random = (): number => 0.5\n const drawSeed = options.seed ?? 7\n const bitten = () => ({ turned: [...placed.values()].filter((p) => p.counted === 'turned').length, dead: [...placed.values()].filter((p) => p.counted === 'dead').length })\n\n const ensure = (THREE: Three): AnimalsThree => {\n three = THREE\n animals ??= createAnimalsThree(THREE, { seed: drawSeed, ...(options.document ? { document: options.document } : {}) })\n return animals\n }\n const rebuildOutbreak = (): void => {\n outbreak = options.createOutbreak ? options.createOutbreak({ species, enabled: settings.outbreak, timeScale: settings.timeScale }) : null\n }\n rebuildOutbreak()\n\n const item = (animal: AnimalSpecies): TestableItem => ({\n id: `${ANIMAL_ITEM_PREFIX}${animal.id}`,\n name: animal.name[locale as 'en'] ?? animal.id,\n movable: false,\n build: (THREE: unknown) => {\n const rig = ensure(THREE as Three).build(animal)\n // the room puts the tree in its scene right after this; trees that never reach it are dropped at the next frame\n placed.set(rig.root.uuid, { id: rig.root.uuid, rig })\n if (settings.visual !== 'alive') animals?.setVisualState(rig, settings.visual)\n return rig.root\n },\n })\n\n const statusOf = (entry: Placed): void => {\n if (!entry.fate || !outbreak) return\n const state = outbreak.stateOf(entry.fate, clock)\n if (state === 'incubating') return\n entry.counted = state === 'turned' ? 'turned' : 'dead'\n animals?.setVisualState(entry.rig, visualStateOf(entry.rig.species.outcome, true, state === 'dead'))\n }\n\n const clearRings = (): void => {\n for (const ring of rings) ring.parent?.remove(ring)\n rings = []\n }\n const refreshLayers = (): void => {\n if (!host?.bench || !animals || !three) return\n clearRings()\n for (const entry of placed.values()) {\n animals.setJointView(entry.rig, host.bench.layer('joints'))\n animals.setSplit(entry.rig, host.bench.layer('split'))\n }\n const ring = (radius: number, color: number, parent: THREE_NS.Object3D): void => {\n const mesh = new (three as Three).Mesh(new (three as Three).RingGeometry(radius - 0.15, radius, 96).rotateX(-Math.PI / 2), new (three as Three).MeshBasicMaterial({ color, transparent: true, opacity: 0.55, depthWrite: false }))\n mesh.position.y = 0.03\n parent.add(mesh)\n rings.push(mesh)\n }\n for (const entry of placed.values()) {\n if (!entry.rig.root.parent) continue\n const radius = entry.rig.species.soundRangeMetres\n if (host.bench.layer('sound_range') && radius) ring(radius, 0x44aaff, entry.rig.root)\n const lure = entry.rig.species.outbreak?.carcass?.lureRadiusMetres\n if (host.bench.layer('carcass_lure') && lure) ring(lure, 0xff5533, entry.rig.root)\n }\n }\n\n const addSkins = (): void => {\n if (!animals || !three || !host) return\n skinGroup ??= new three.Group()\n if (!skinGroup.parent) (host.scene as THREE_NS.Object3D).add(skinGroup)\n for (let index = skins.length; index < SKIN_COUNT; index++) {\n const animal = species[index % Math.max(1, species.length)] as AnimalSpecies\n const rig = animals.build(animal)\n const skin = animals.createSkin(rig)\n rig.root.position.set(SKIN_FIELD.x0 + (index % SKIN_FIELD.columns) * SKIN_FIELD.gap, 0, SKIN_FIELD.z0 - Math.floor(index / SKIN_FIELD.columns) * SKIN_FIELD.gap)\n // the tree still drives the animation, but only the skin is drawn\n rig.root.traverse((object) => {\n if ((object as THREE_NS.Mesh).isMesh) object.visible = false\n })\n skinGroup.add(rig.root, skin.mesh)\n skins.push({ rig, skin })\n }\n }\n const clearSkins = (): void => {\n skinGroup?.parent?.remove(skinGroup)\n skinGroup = null\n skins = []\n }\n\n const applyVisual = (): void => {\n for (const entry of placed.values()) {\n if (entry.fate) continue\n animals?.setVisualState(entry.rig, settings.visual)\n }\n }\n\n const onChange = (change: BenchChange): void => {\n if (change.kind === 'layer') return refreshLayers()\n if (change.kind === 'action') {\n if (change.id === 'bite_all') {\n for (const entry of placed.values()) {\n entry.fate = outbreak?.bite(entry.rig.species.id, random, clock)\n if (entry.fate) animals?.setVisualState(entry.rig, visualStateOf(entry.rig.species.outcome, true))\n }\n } else if (change.id === 'recover') {\n for (const entry of placed.values()) {\n delete entry.fate\n delete entry.counted\n animals?.setVisualState(entry.rig, settings.visual)\n }\n } else if (change.id === 'skin_40') addSkins()\n else if (change.id === 'clear_skins') clearSkins()\n return\n }\n switch (change.id) {\n case 'visual_state': settings.visual = String(change.value) as AnimalVisualState; applyVisual(); break\n case 'gait': settings.gait = String(change.value); break\n case 'speed': settings.speed = Number(change.value); break\n case 'outbreak': settings.outbreak = Boolean(change.value); if (outbreak) outbreak.enabled = settings.outbreak; break\n case 'time_scale': settings.timeScale = Number(change.value); rebuildOutbreak(); break\n default: break\n }\n }\n\n const frame = (dt: number): void => {\n time += dt * settings.speed\n clock += dt\n for (const [id, entry] of placed) {\n if (!entry.rig.root.parent) {\n placed.delete(id)\n continue\n }\n statusOf(entry)\n animals?.animate(entry.rig, { time, delta: dt * settings.speed, clip: settings.gait, split: host?.bench?.layer('split') ?? false })\n animals?.updateSplit(entry.rig, dt)\n }\n for (const { rig, skin } of skins) {\n animals?.animate(rig, { time, delta: dt * settings.speed, clip: settings.gait })\n rig.root.updateMatrixWorld(true)\n skin.update()\n }\n }\n\n const countMeshes = (only?: 'skins'): { drawCalls: number; triangles: number } => {\n let drawCalls = 0\n let triangles = 0\n const count = (root: THREE_NS.Object3D): void =>\n root.traverse((object) => {\n const mesh = object as THREE_NS.Mesh\n if (!mesh.isMesh || !mesh.visible) return\n drawCalls += 1\n const geometry = mesh.geometry\n triangles += geometry.index ? geometry.index.count / 3 : geometry.attributes.position.count / 3\n })\n if (only !== 'skins') for (const entry of placed.values()) count(entry.rig.root)\n for (const { skin } of skins) count(skin.mesh)\n return { drawCalls, triangles: Math.round(triangles) }\n }\n\n const deps: ScenarioDeps = {\n text: text.scenarios,\n species: () => species,\n ensure: () => ensure((three ?? host?.THREE) as Three),\n outbreak: () => outbreak,\n rebuildOutbreak: (enabled) => {\n settings.outbreak = enabled\n rebuildOutbreak()\n return outbreak\n },\n skins: () => {\n ensure((three ?? host?.THREE) as Three)\n addSkins()\n return { count: skins.length, meshes: countMeshes('skins'), nodes: skins[0]?.skin.nodes.length ?? 0, rich: skins[0]?.rig.triangles ?? 0 }\n },\n clearSkins,\n }\n\n return {\n id: ANIMALS_AREA_ID,\n name: text.name,\n group: text.group,\n get placed() {\n return placed\n },\n items: () => species.map(item),\n area: (): ModuleArea => {\n // the page's line-up: each animal given room by its length (and a gap), the row centred on the origin\n const gap = 0.85\n let cursor = 0\n const spots = species.map((animal) => {\n const length = lineUpLength(animal.body)\n const x = cursor + length / 2\n cursor += length + gap\n return { animal, x }\n })\n const offset = -(cursor - gap) / 2\n return {\n placements: spots.map(({ animal, x }) => ({ item: `${ANIMAL_ITEM_PREFIX}${animal.id}`, position: [x + offset, 0, 0], rotationY: animal.body.kind === 'snake' ? 2.2 : 1.1 })),\n view: { eye: [0, 4, Math.max(10, (cursor - gap) * 0.55)], target: [0, 0.6, 0] },\n }\n },\n controls: (): ModuleControl[] => [\n { kind: 'choice', id: 'visual_state', name: text.controls.visual_state, hint: text.hints.visual_state, options: (['alive', 'infected', 'dead'] as const).map((id) => ({ id, name: text.options.visual_state[id] as string })), default: 'alive' },\n { kind: 'choice', id: 'gait', name: text.controls.gait, hint: text.hints.gait, options: (['idle', 'walk', 'run'] as const).map((id) => ({ id, name: text.options.gait[id] as string })), default: 'walk' },\n { kind: 'number', id: 'speed', name: text.controls.speed, min: 0, max: 4, step: 0.25, default: 1 },\n { kind: 'boolean', id: 'outbreak', name: text.controls.outbreak, hint: text.hints.outbreak, default: true },\n { kind: 'number', id: 'time_scale', name: text.controls.time_scale, hint: text.hints.time_scale, min: 1, max: 600, step: 1, default: DEFAULT_TIME_SCALE },\n { kind: 'action', id: 'bite_all', name: text.actions.bite_all },\n { kind: 'action', id: 'recover', name: text.actions.recover },\n { kind: 'action', id: 'skin_40', name: text.actions.skin_40 },\n { kind: 'action', id: 'clear_skins', name: text.actions.clear_skins },\n ],\n layers: (): ModuleLayer[] => (['joints', 'split', 'sound_range', 'carcass_lure'] as const).map((id) => ({ id, name: text.layers[id], default: false })),\n metrics: (): ModuleMetric[] => [\n { id: 'animals', name: text.metrics.animals, read: () => placed.size + skins.length },\n { id: 'draw_calls', name: text.metrics.draw_calls, read: () => countMeshes().drawCalls },\n { id: 'triangles', name: text.metrics.triangles, read: () => countMeshes().triangles },\n { id: 'nodes_per_skin', name: text.metrics.nodes_per_skin, read: () => skins[0]?.skin.nodes.length ?? 0 },\n { id: 'incubating', name: text.metrics.incubating, read: () => [...placed.values()].filter((p) => p.fate && !p.counted).length },\n { id: 'turned', name: text.metrics.turned, read: () => bitten().turned },\n { id: 'dead', name: text.metrics.dead, read: () => bitten().dead },\n ],\n scenarios: (): ModuleScenario<AnimalsContext>[] => createScenarios(deps),\n attach(lent) {\n host = lent\n ensure(lent.THREE as Three)\n const stopChange = lent.bench?.onChange(onChange)\n const stopFrame = lent.onFrame(frame)\n return () => {\n stopChange?.()\n stopFrame()\n clearRings()\n clearSkins()\n host = null\n }\n },\n }\n\n}\n"],"mappings":"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","names":["mesh","material","geometry","PI","PI","document","SKIN_COUNT"]}
1
+ {"version":3,"sources":["../src/index.ts","../../game-animals/src/data/gaits.json","../../game-animals/src/gaits.ts","../../game-animals/src/layout.ts","../../game-animals/src/registry.ts","../../game-animals/src/kit.ts","../../game-props3d/src/colors.ts","../../game-props3d/src/atlas.ts","../../game-props3d/src/catalog.ts","../../game-props3d/src/builders/ceiling.ts","../../game-props3d/src/builders/floors.ts","../../game-props3d/src/builders/shared.ts","../../game-props3d/src/builders/furniture.ts","../../game-props3d/src/builders/house-state.ts","../../game-props3d/src/builders/plants.ts","../../game-props3d/src/builders/wall.ts","../../game-props3d/src/builders/yard.ts","../../game-props3d/src/cover.ts","../../game-props3d/src/kit.ts","../../game-props3d/src/locale.ts","../../game-props3d/src/create.ts","../../game-props3d/src/node-skin.ts","../../game-props3d/src/recorder.ts","../../game-props3d/src/merge-by-material.ts","../src/animate-bird.ts","../src/animate-quadruped.ts","../src/animate-snake.ts","../src/context.ts","../src/bird.ts","../src/extras.ts","../src/textures.ts","../src/profile.ts","../src/quadruped.ts","../src/snake.ts","../src/visual-state.ts","../src/views.ts","../../game-audio/src/rng.ts","../../game-audio/src/recipe.ts","../../game-audio/src/falloff.ts","../../game-audio/src/listener.ts","../../game-audio/src/data/voice-groups.json","../../game-audio/src/voices.ts","../../game-audio/src/plan.ts","../../game-audio/src/loops.ts","../../game-audio/src/data/mix-profiles.json","../../game-audio/src/mix.ts","../../game-audio/src/bank.ts","../../game-audio/src/data/game-sfx.json","../../game-audio/src/recipes/noise-class.ts","../../game-audio/src/recipes/scene.ts","../../game-audio/src/library/crowd_rumble.json","../../game-audio/src/library/engine_saw.json","../../game-audio/src/library/horn.json","../../game-audio/src/library/radio_static.json","../../game-audio/src/library/rotor_blades.json","../../game-audio/src/library/siren.json","../../game-audio/src/library/index.ts","../../game-audio/src/webaudio/noise.ts","../../game-audio/src/webaudio/chain.ts","../../game-audio/src/webaudio/context.ts","../../game-audio/src/webaudio/nodes.ts","../../game-audio/src/webaudio/realize-loop.ts","../../game-audio/src/webaudio/engine-loop.ts","../../game-audio/src/webaudio/engine-noise.ts","../../game-audio/src/webaudio/realize-game.ts","../../game-audio/src/webaudio/realize-layered.ts","../../game-audio/src/webaudio/realize-weapon.ts","../../game-audio/src/webaudio/realize.ts","../../game-audio/src/webaudio/engine.ts","../../game-audio/src/webaudio/sink.ts","../../game-audio/src/bench/locale.ts","../../game-audio/src/bench/module.ts","../src/audio/recipes.ts","../src/audio/calls.json","../src/audio/sound.ts","../src/line-up.ts","../src/test-area/scenarios.ts","../src/test-area/text.ts","../src/test-area/module.ts"],"sourcesContent":["/**\n * @adatechnology/game-animals-three: the three.js drawing of the animals, a literal port of the Bichos do Vale builders (`quad`, `bird`, `snake`) and\n * their animation, with the alive / infected / dead looks for every species, the joints and split views, and a GPU node skin for crowds.\n */\nimport { GAIT_PHASES } from '@adatechnology/game-animals'\nimport type { AnimalSpecies, AnimalVisualState, GaitPhases } from '@adatechnology/game-animals'\nimport { collectReferencedNodes, createNodeSkin } from '@adatechnology/game-props3d'\nimport type { NodeSkin } from '@adatechnology/game-props3d'\nimport type * as THREE_NS from 'three'\n\nimport { animateBird } from './animate-bird'\nimport { animateQuadruped } from './animate-quadruped'\nimport { animateSnake } from './animate-snake'\nimport type { AnimateParams } from './animate-types'\nimport { buildBird } from './bird'\nimport { createRig, createStream } from './context'\nimport { BIRD_EXTRAS, QUADRUPED_EXTRAS } from './extras'\nimport { buildQuadruped } from './quadruped'\nimport { buildSnake } from './snake'\nimport type { AnimalRig, AnimalsThreeOptions, DocumentLike, Three } from './types'\nimport { applyVisualState } from './visual-state'\nimport { setJointView, setSplit, updateSplit } from './views'\n\nexport { ANIMAL_SOUND_BANK, ANIMAL_SOUNDS, COW_MOO, DOG_BARK, HEN_CLUCK, LAPWING_CALL } from './audio/recipes'\nexport { ANIMAL_CALLS, ANIMAL_CALL_SOURCES, createAnimalSound } from './audio/sound'\nexport type { AnimalCall, AnimalSound, AnimalSoundOptions, AnimalSoundSink } from './audio/sound'\nexport type { AnimalLeg, AnimalPivot, AnimalRig, AnimalWing, AnimalsThreeOptions, CanvasLike, DocumentLike, Three } from './types'\nexport type { AnimateParams } from './animate-types'\nexport type { BirdExtra, BirdParts, ExtraContext, QuadrupedExtra, QuadrupedParts } from './extras'\nexport { BIRD_EXTRAS, QUADRUPED_EXTRAS } from './extras'\nexport { lineUpAnimals } from './line-up'\nexport type { LineUpOptions } from './line-up'\nexport { profileGeometry } from './profile'\nexport { createCoatTexture } from './textures'\nexport { applyVisualState } from './visual-state'\nexport { setJointView, setSplit, updateSplit } from './views'\n\nexport type AnimalsThree = {\n /** Builds one animal from its species; consumes one number of the stream for the start phase unless `phase` is given. */\n build(species: AnimalSpecies, buildOptions?: { readonly phase?: number }): AnimalRig\n /** Advances the animation of one animal (steps, flight, wings, head, tail, a dead one lying down). */\n animate(rig: AnimalRig, params: AnimateParams): void\n setVisualState(rig: AnimalRig, state: AnimalVisualState): void\n setJointView(rig: AnimalRig, on: boolean): void\n setSplit(rig: AnimalRig, on: boolean): void\n updateSplit(rig: AnimalRig, delta: number): void\n /** One mesh that stands for the whole tree, for crowds: call `skin.update()` each frame after `animate`. */\n createSkin(rig: AnimalRig): NodeSkin\n readonly jointMaterial: THREE_NS.MeshStandardMaterial\n}\n\nconst countTriangles = (root: THREE_NS.Object3D): number => {\n let triangles = 0\n root.traverse((object) => {\n const mesh = object as THREE_NS.Mesh\n if (!mesh.isMesh) return\n const geometry = mesh.geometry\n triangles += geometry.index ? geometry.index.count / 3 : geometry.attributes.position.count / 3\n })\n return Math.round(triangles)\n}\n\nexport const createAnimalsThree = (THREE: Three, options: AnimalsThreeOptions = {}): AnimalsThree => {\n const random = options.random ?? createStream(options.seed ?? 7)\n const documentOf = (): DocumentLike => {\n const found = options.document ?? (globalThis as { document?: DocumentLike }).document\n if (!found) throw new Error('game-animals-three: a species with a coat texture needs `options.document` (no global document found)')\n return found\n }\n const jointMaterial = new THREE.MeshStandardMaterial({ color: 0xe0a548, emissive: 0xe0a548, emissiveIntensity: 0.8, roughness: 0.4 })\n const gaits: Readonly<Record<'walk' | 'trot' | 'gallop', GaitPhases>> = GAIT_PHASES\n\n return {\n jointMaterial,\n build(species, buildOptions = {}) {\n const rig = createRig(THREE, species, { phase: buildOptions.phase ?? random() * 6 })\n if (species.body.kind === 'snake') buildSnake(THREE, rig)\n else if (species.body.kind === 'bird') buildBird(THREE, rig, { extra: BIRD_EXTRAS[species.id] })\n else buildQuadruped(THREE, rig, { document: species.body.texture ? documentOf() : (undefined as never), extra: QUADRUPED_EXTRAS[species.id] })\n rig.triangles = countTriangles(rig.root)\n rig.root.traverse((object) => {\n if ((object as THREE_NS.Mesh).isMesh) object.userData.speciesId = species.id\n })\n return rig\n },\n animate(rig, params) {\n const kind = rig.species.body.kind\n if (kind === 'snake') animateSnake(THREE, rig, params)\n else if (kind === 'bird') animateBird(rig, params)\n else animateQuadruped(rig, params, gaits)\n },\n setVisualState: (rig, state) => applyVisualState(THREE, rig, state),\n setJointView: (rig, on) => setJointView(rig, on, jointMaterial),\n setSplit,\n updateSplit,\n createSkin: (rig) => createNodeSkin(THREE, rig.root, { nodes: collectReferencedNodes(rig, ['species']) }),\n }\n}\nexport { createAnimalsTestModule, ANIMALS_AREA_ID, ANIMAL_ITEM_PREFIX } from './test-area/module'\nexport type { AnimalsContext } from './test-area/scenarios'\nexport type { AnimalsTestOptions, OutbreakLike } from './test-area/types'\n","{\n \"walk\": [\n 1.570796327,\n 4.71238898,\n 0,\n 3.141592654\n ],\n \"trot\": [\n 0,\n 3.141592654,\n 3.141592654,\n 0\n ],\n \"gallop\": [\n 2.890265241,\n 3.707079331,\n 0,\n 0.879645943\n ]\n}\n","import gaitsData from './data/gaits.json'\nimport type { AnimalGaitName, GaitPhases } from './types'\n\n/** The phase of each leg per gait, in radians, from the Bichos do Vale page (`GAIT`); the leg order is front right, front left, hind right, hind left. */\nexport const GAIT_PHASES = gaitsData as unknown as Readonly<Record<AnimalGaitName, GaitPhases>>\n","import type { BirdSpec, QuadrupedSpec } from './types'\n\nexport type QuadrupedLayout = {\n /** Height of the front and the hind hips above the ground. */\n readonly frontHipHeight: number\n readonly hindHipHeight: number\n readonly footHeight: number\n /** Height of the body centre. */\n readonly bodyHeight: number\n /** Distance between the shoulders and the hips. */\n readonly span: number\n /** Largest dimension, used to scale the offsets of the split view and the blood pool. */\n readonly size: number\n}\n\n/** The rest-pose geometry of a quadruped: leg lengths and angles decide how high the body stands. */\nexport const quadrupedLayout = (spec: QuadrupedSpec): QuadrupedLayout => {\n const leg = spec.leg\n const frontAngle1 = leg.fa1 ?? 0.06\n const frontAngle2 = leg.fa2 ?? -0.08\n const hindAngle1 = leg.ha1 ?? -0.32\n const hindAngle2 = leg.ha2 ?? 0.58\n const footHeight = leg.hoof ? leg.r * 1.1 : leg.r * 0.7\n const frontHipHeight = leg.fu * Math.cos(frontAngle1) + leg.fl * Math.cos(frontAngle1 + frontAngle2) + footHeight\n const hindHipHeight = leg.hu * Math.cos(hindAngle1) + leg.hl * Math.cos(hindAngle1 + hindAngle2) + footHeight\n const bodyHeight = (frontHipHeight + hindHipHeight) / 2 + (spec.up ?? 0) * spec.R\n return { frontHipHeight, hindHipHeight, footHeight, bodyHeight, span: spec.L * spec.span, size: Math.max(spec.L, bodyHeight + spec.R) }\n}\n\n/** The size of a bird: the largest of its body and its standing height. */\nexport const birdSize = (spec: BirdSpec): number => Math.max(spec.r * 3, spec.tars + spec.shank * 0.9 + spec.r * 2)\n\n/** The length a species takes in a line-up: used to space the animals apart. */\nexport const lineUpLength = (body: QuadrupedSpec | BirdSpec | { readonly kind: 'snake' }): number => {\n if (body.kind === 'quadruped') return body.L * 1.3\n if (body.kind === 'snake') return 0.9\n return body.r * 4\n}\n","type Entry = { readonly id: string }\n\n/** The shipped entries with the ones from `extra` added or, when the id exists, replaced. */\nexport const mergeById = <TEntry extends Entry>(shipped: readonly TEntry[], extra: readonly TEntry[] | undefined): Map<string, TEntry> => {\n const entries = new Map<string, TEntry>()\n for (const entry of [...shipped, ...(extra ?? [])]) entries.set(entry.id, entry)\n return entries\n}\n\n/** Applies `patch` by id (a shallow merge); a patch for an id that does not exist is ignored. */\nexport const applyPatches = <TEntry extends Entry>(entries: Map<string, TEntry>, patches: Readonly<Record<string, Partial<TEntry>>> | undefined): void => {\n for (const [id, patch] of Object.entries(patches ?? {})) {\n const entry = entries.get(id)\n if (entry) entries.set(id, { ...entry, ...patch, id })\n }\n}\n\nexport const removeIds = (entries: Map<string, unknown>, ids: readonly string[] | undefined): void => {\n for (const id of ids ?? []) entries.delete(id)\n}\n","import { GAIT_PHASES } from './gaits'\nimport { applyPatches, mergeById, removeIds } from './registry'\nimport type { AnimalGaitName, AnimalOutcome, AnimalSpecies, AnimalVisualState, AnimalsOptions, GaitPhases } from './types'\n\nexport type AnimalKit = {\n readonly species: readonly AnimalSpecies[]\n get(id: string): AnimalSpecies | undefined\n gait(name: AnimalGaitName): GaitPhases\n}\n\n/**\n * What a bite does to the look of an animal: a species that turns shows the infected look, one that dies shows the dead look, and an immune one shows no\n * change. `forceDead` shows the dead look for any species (a carcass, an animal killed by something else).\n */\nexport const visualStateOf = (outcome: AnimalOutcome, isBitten: boolean, forceDead = false): AnimalVisualState => {\n if (forceDead) return 'dead'\n if (!isBitten) return 'alive'\n if (outcome === 'turns') return 'infected'\n return outcome === 'dies' ? 'dead' : 'alive'\n}\n\nexport const createAnimals = (options: AnimalsOptions = {}): AnimalKit => {\n const entries = mergeById([], options.packs?.flatMap((pack) => pack.species ?? []))\n applyPatches(entries, options.patch)\n removeIds(entries, options.remove)\n const species = [...entries.values()].sort((first, second) => first.order - second.order)\n const gaits = { ...GAIT_PHASES, ...options.gaits }\n return { species, get: (id) => entries.get(id), gait: (name) => gaits[name] }\n}\n\n","import type { Color, PropItem } from './types'\n\n/** Deterministic PRNG (mulberry32 variant) returning floats in [0, 1). */\nexport function rng(seed: number): () => number {\n let s = seed >>> 0\n return () => {\n s = (s + 0x6d2b79f5) >>> 0\n let t = s\n t = Math.imul(t ^ (t >>> 15), t | 1)\n t ^= t + Math.imul(t ^ (t >>> 7), t | 61)\n return ((t ^ (t >>> 14)) >>> 0) / 4294967296\n }\n}\n\n/** Seed of the details draw: `M.seed` (number or string) or a hash of the position. */\nexport const seedOf = (M: PropItem): number =>\n (M.seed != null\n ? typeof M.seed === 'number'\n ? M.seed\n : [...String(M.seed)].reduce((a, c) => (a * 31 + c.charCodeAt(0)) | 0, 7)\n : Math.round((M.x || 0) * 37 + (M.z || 0) * 91 + 1000)) >>> 0\n\n/** `'#rrggbb'` or number to number. */\nexport const hex = (v: Color): number => (typeof v === 'string' ? parseInt(v.replace('#', ''), 16) : v)\n\n/** Scales the brightness of a colour by factor `f` (clamped per channel). */\nexport const shade = (c: Color, f: number): number => {\n const n = hex(c)\n const r = Math.min(255, Math.round(((n >> 16) & 255) * f))\n const g = Math.min(255, Math.round(((n >> 8) & 255) * f))\n const b = Math.min(255, Math.round((n & 255) * f))\n return (r << 16) | (g << 8) | b\n}\n\n/** Linear mix of two colours. */\nexport const mix = (a: Color, b: Color, t: number): number => {\n const x = hex(a)\n const y = hex(b)\n const m = (s: number) => Math.round(((x >> s) & 255) * (1 - t) + ((y >> s) & 255) * t)\n return (m(16) << 16) | (m(8) << 8) | m(0)\n}\n","import { rng } from './colors'\nimport type { AtlasMap, AtlasName, DocumentLike, FloorAtlas, FloorKind, PictureAtlas, Uv, WaterAtlas } from './types'\n\ntype Ctx = CanvasRenderingContext2D\ntype Rand = () => number\n\nconst makeCanvas = (doc: DocumentLike, w: number, h: number): HTMLCanvasElement => {\n const c = doc.createElement('canvas')\n c.width = w\n c.height = h\n return c\n}\n\n// draws f() nine times (offsets of +-n) inside the clipped cell, so the pattern tiles without a seam\nfunction seamless(g: Ctx, x0: number, y0: number, n: number, f: () => void) {\n g.save()\n g.beginPath()\n g.rect(x0, y0, n, n)\n g.clip()\n for (let dy = -n; dy <= n; dy += n)\n for (let dx = -n; dx <= n; dx += n) {\n g.save()\n g.translate(x0 + dx, y0 + dy)\n f()\n g.restore()\n }\n g.restore()\n}\nconst noise = (g: Ctx, x0: number, y0: number, n: number, r: Rand, a: number) => {\n for (let i = 0; i < (n * n) / 6; i++) {\n const v = r() < 0.5 ? 0 : 255\n g.fillStyle = `rgba(${v},${v},${v},${r() * a})`\n g.fillRect(x0 + r() * n, y0 + r() * n, 1 + r() * 2, 1 + r() * 2)\n }\n}\n\n/** Floor kinds that live in the floor atlas: atlas cells and the world size (meters) one cell covers. */\nexport const FLOORS: Record<FloorKind, { cells: number[]; size: number }> = {\n lawn: { cells: [0, 1], size: 2.0 },\n tile_floor: { cells: [2], size: 1.0 },\n concrete: { cells: [3], size: 2.0 },\n deck_floor: { cells: [4], size: 1.2 },\n dirt: { cells: [5], size: 2.0 },\n pool_tile: { cells: [6], size: 1.0 },\n paving: { cells: [7], size: 1.0 },\n}\nconst PICTURE_CELLS = 8 // 0-3 abstract, 4-6 landscapes, 7 mirror\n\nfunction floorAtlas(doc: DocumentLike): FloorAtlas {\n const N = 256, cv = makeCanvas(doc, N * 4, N * 2), g = cv.getContext('2d')!, P = (c: number): [number, number] => [(c % 4) * N, Math.floor(c / 4) * N];\n const cell = (c: number, seed: number, f: (g: Ctx, x0: number, y0: number, r: Rand) => void) => { const [x0, y0] = P(c), r = rng(seed); f(g, x0, y0, r); };\n for (const [c, seed] of [[0, 11], [1, 12]] as Array<[number, number]>) cell(c, seed, (g, x0, y0, r) => { // lawn: base + blades + dry patches\n g.fillStyle = c ? '#648f3a' : '#5b8a37'; g.fillRect(x0, y0, N, N);\n seamless(g, x0, y0, N, () => { for (let i = 0; i < 40; i++) { g.fillStyle = `rgba(${r() < 0.5 ? '160,170,60' : '40,80,30'},${0.12 + r() * 0.12})`; g.beginPath(); g.arc(r() * N, r() * N, 8 + r() * 22, 0, 6.3); g.fill(); }\n for (let i = 0; i < 520; i++) { r(); /* the original drew a blade height here and never used it; the call keeps the random sequence identical */ g.strokeStyle = `hsl(${85 + r() * 30},${40 + r() * 25}%,${22 + r() * 24}%)`; g.lineWidth = 1; const x = r() * N, y = r() * N; g.beginPath(); g.moveTo(x, y); g.lineTo(x + (r() - 0.5) * 6, y - 4 - r() * 9); g.stroke(); } }); });\n cell(2, 21, (g, x0, y0, r) => { // beige 4x4 ceramic tiles with grout\n const q = N / 4; g.fillStyle = '#8c877b'; g.fillRect(x0, y0, N, N);\n for (let j = 0; j < 4; j++) for (let i = 0; i < 4; i++) { const v = 204 + (r() - 0.5) * 16; g.fillStyle = `rgb(${v},${v - 6},${v - 18})`; g.fillRect(x0 + i * q + 2, y0 + j * q + 2, q - 4, q - 4); g.fillStyle = 'rgba(255,255,255,.18)'; g.fillRect(x0 + i * q + 3, y0 + j * q + 3, q - 8, 3); }\n for (let k = 0; k < 14; k++) { const gr = g.createRadialGradient(0, 0, 0, 0, 0, 40); const x = x0 + r() * N, y = y0 + r() * N; g.save(); g.beginPath(); g.rect(x0, y0, N, N); g.clip(); g.translate(x, y); gr.addColorStop(0, 'rgba(90,80,60,.10)'); gr.addColorStop(1, 'rgba(90,80,60,0)'); g.fillStyle = gr; g.fillRect(-40, -40, 80, 80); g.restore(); } });\n cell(3, 31, (g, x0, y0, r) => { // concrete: slab, expansion joint, crack, stain\n g.fillStyle = '#a7a7a0'; g.fillRect(x0, y0, N, N);\n for (let k = 0; k < 18; k++) { const gr = g.createRadialGradient(0, 0, 0, 0, 0, 50); g.save(); g.beginPath(); g.rect(x0, y0, N, N); g.clip(); g.translate(x0 + r() * N, y0 + r() * N); gr.addColorStop(0, `rgba(${r() < 0.5 ? '70,70,60' : '200,200,190'},.12)`); gr.addColorStop(1, 'rgba(0,0,0,0)'); g.fillStyle = gr; g.fillRect(-50, -50, 100, 100); g.restore(); }\n noise(g, x0, y0, N, r, 0.25); g.fillStyle = 'rgba(40,40,36,.55)'; g.fillRect(x0, y0 + N / 2 - 1, N, 2); g.fillRect(x0 + N / 2 - 1, y0, 2, N); g.fillRect(x0, y0, N, 1); g.fillRect(x0, y0, 1, N);\n g.strokeStyle = 'rgba(50,50,46,.5)'; g.lineWidth = 1; let x = x0 + 30 + r() * 60, y = y0 + 8; g.beginPath(); g.moveTo(x, y); for (let s = 0; s < 8; s++) { x += (r() - 0.5) * 16; y += 12 + r() * 8; g.lineTo(x, y); } g.stroke(); });\n cell(4, 41, (g, x0, y0, r) => { // deck: 12 boards in staggered rows\n const th = N / 12; g.fillStyle = '#2a1c10'; g.fillRect(x0, y0, N, N);\n for (let j = 0; j < 12; j++) { const cut = r() * N; for (const [a, b] of [[0, cut], [cut, N]]) { const v = 150 + (r() - 0.5) * 36; g.fillStyle = `rgb(${v},${v * 0.68 | 0},${v * 0.42 | 0})`; g.fillRect(x0 + a + 1, y0 + j * th + 1, b - a - 2, th - 2);\n g.strokeStyle = 'rgba(50,28,12,.3)'; for (let s = 0; s < 3; s++) { const y = y0 + j * th + 3 + r() * (th - 6); g.beginPath(); g.moveTo(x0 + a, y); g.lineTo(x0 + b, y + (r() - 0.5) * 2); g.stroke(); }\n g.fillStyle = 'rgba(30,20,10,.7)'; g.fillRect(x0 + a + 4, y0 + j * th + th / 2, 2, 2); g.fillRect(x0 + b - 6, y0 + j * th + th / 2, 2, 2); } } });\n cell(5, 51, (g, x0, y0, r) => { // packed dirt\n g.fillStyle = '#7b5538'; g.fillRect(x0, y0, N, N);\n seamless(g, x0, y0, N, () => { for (let k = 0; k < 30; k++) { g.fillStyle = `rgba(${r() < 0.5 ? '60,38,24' : '150,112,80'},${0.12 + r() * 0.15})`; g.beginPath(); g.arc(r() * N, r() * N, 6 + r() * 20, 0, 6.3); g.fill(); }\n for (let k = 0; k < 90; k++) { const v = 90 + r() * 90; g.fillStyle = `rgb(${v},${v * 0.85 | 0},${v * 0.7 | 0})`; g.fillRect(r() * N, r() * N, 1 + r() * 3, 1 + r() * 3); } }); });\n cell(6, 61, (g, x0, y0, r) => { // 8x8 pool tiles\n const q = N / 8; g.fillStyle = '#d4e8ee'; g.fillRect(x0, y0, N, N);\n for (let j = 0; j < 8; j++) for (let i = 0; i < 8; i++) { const v = (r() - 0.5) * 24; g.fillStyle = `rgb(${50 + v},${150 + v},${190 + v})`; g.fillRect(x0 + i * q + 1, y0 + j * q + 1, q - 2, q - 2); g.fillStyle = 'rgba(255,255,255,.2)'; g.fillRect(x0 + i * q + 2, y0 + j * q + 2, q - 6, 2); } });\n cell(7, 71, (g, x0, y0, r) => { // reddish interlocking paving (small bricks)\n const bw = N / 4, bh = N / 8; g.fillStyle = '#7a7268'; g.fillRect(x0, y0, N, N);\n for (let j = 0; j < 8; j++) for (let i = -1; i < 4; i++) { const off = (j % 2) * bw / 2, v = 150 + (r() - 0.5) * 30; g.fillStyle = `rgb(${v + 20},${v * 0.55 | 0},${v * 0.42 | 0})`; g.fillRect(x0 + ((i * bw + off) % N + N) % N + 1, y0 + j * bh + 1, bw - 2, bh - 2); }\n noise(g, x0, y0, N, r, 0.2); });\n const cells = {} as Record<FloorKind, Uv[]>; for (const k of Object.keys(FLOORS) as FloorKind[]) cells[k] = FLOORS[k].cells.map(c => ({ u0: (c % 4) / 4, u1: (c % 4 + 1) / 4, v0: 1 - (Math.floor(c / 4) + 1) / 2, v1: 1 - Math.floor(c / 4) / 2 }));\n const e = 1.5 / (N * 4), ev = 1.5 / (N * 2); // inset of one and a half pixels: no bleeding from the neighbouring cell\n return { canvas: cv, cells, size: (k: FloorKind) => FLOORS[k].size, uv(k: FloorKind, v = 0) { const L = cells[k]; const c = L[v % L.length]; return { u0: c.u0 + e, u1: c.u1 - e, v0: c.v0 + ev, v1: c.v1 - ev }; }, variants: (k: FloorKind) => FLOORS[k].cells.length };\n}\n\nfunction pictureAtlas(doc: DocumentLike, names: string[]): PictureAtlas {\n const N = 256, cv = makeCanvas(doc, N * 4, N * 2), g = cv.getContext('2d')!;\n const cell = (c: number, seed: number, f: (r: Rand) => void) => { const x0 = (c % 4) * N, y0 = Math.floor(c / 4) * N, r = rng(seed); g.save(); g.beginPath(); g.rect(x0, y0, N, N); g.clip(); g.translate(x0, y0); f(r); g.restore(); };\n cell(0, 101, r => { g.fillStyle = '#e8dcc4'; g.fillRect(0, 0, N, N); g.fillStyle = '#c0613c'; g.beginPath(); g.arc(90 + r() * 30, 110, 60, 0, 6.3); g.fill(); g.fillStyle = '#2f7f84'; g.fillRect(120, 40 + r() * 30, 90, 150); g.fillStyle = '#d9a43a'; g.beginPath(); g.arc(150, 200, 70, Math.PI, 0); g.fill(); g.strokeStyle = '#2a2a2a'; g.lineWidth = 3; g.beginPath(); g.moveTo(20, 230); g.lineTo(236, 30); g.stroke(); });\n cell(1, 102, r => { g.fillStyle = '#1f3a5f'; g.fillRect(0, 0, N, N); for (let i = 0; i < 7; i++) { g.fillStyle = ['#f08a3c', '#f4e4c1', '#d94f4f', '#7fb5a5'][i % 4]; g.globalAlpha = 0.85; g.beginPath(); g.ellipse(r() * N, r() * N, 20 + r() * 50, 12 + r() * 30, r() * 3, 0, 6.3); g.fill(); } g.globalAlpha = 1; g.strokeStyle = '#f4e4c1'; g.lineWidth = 4; for (let i = 0; i < 3; i++) { g.beginPath(); g.moveTo(0, 50 + i * 70); g.bezierCurveTo(80, 10 + i * 70, 170, 120 + i * 40, N, 60 + i * 60); g.stroke(); } });\n cell(2, 103, r => { const cs = ['#e9e1cf', '#c8553d', '#2e5e7e', '#e4b23e', '#4b4b4b']; let x = 0; while (x < N) { const w = 30 + r() * 60; g.fillStyle = cs[(r() * cs.length) | 0]; g.fillRect(x, 0, w, N); x += w; } g.fillStyle = '#e9e1cf'; g.fillRect(40, 60, 176, 136); g.fillStyle = cs[1]; g.fillRect(70, 90, 60, 76); g.fillStyle = cs[2]; g.fillRect(140, 90, 50, 36); });\n cell(3, 104, r => { g.fillStyle = '#f2f0ea'; g.fillRect(0, 0, N, N); g.fillStyle = '#1a1a1a'; g.fillRect(40, 190, 176, 8); g.fillStyle = '#b8352e'; g.beginPath(); g.arc(128, 100, 52, 0, 6.3); g.fill(); g.strokeStyle = '#1a1a1a'; g.lineWidth = 2; for (let i = 0; i < 5; i++) { g.beginPath(); g.moveTo(40, 30 + i * 8 + r() * 4); g.lineTo(216, 30 + i * 8); g.stroke(); } });\n cell(4, 105, r => { const gr = g.createLinearGradient(0, 0, 0, N * 0.7); gr.addColorStop(0, '#5a4a8a'); gr.addColorStop(0.5, '#e8805a'); gr.addColorStop(1, '#f7d27a'); g.fillStyle = gr; g.fillRect(0, 0, N, N); g.fillStyle = '#fff3c0'; g.beginPath(); g.arc(150, 150, 36, 0, 6.3); g.fill();\n ([['#7a5a6a', 150], ['#4a4a5e', 175], ['#2a3a3a', 205]] as Array<[string, number]>).forEach(([c, y], k) => { g.fillStyle = c; g.beginPath(); g.moveTo(0, N); g.lineTo(0, y); for (let x = 0; x <= N; x += 32) g.lineTo(x, y - 10 + r() * 24 - k * 3); g.lineTo(N, N); g.fill(); }); });\n cell(5, 106, r => { const gr = g.createLinearGradient(0, 0, 0, N); gr.addColorStop(0, '#9fc8e8'); gr.addColorStop(0.55, '#e8f0f4'); gr.addColorStop(0.56, '#3d7a9a'); gr.addColorStop(1, '#22506a'); g.fillStyle = gr; g.fillRect(0, 0, N, N);\n g.fillStyle = '#5a6a78'; g.beginPath(); g.moveTo(0, 140); for (let x = 0; x <= N; x += 28) g.lineTo(x, 70 + r() * 50); g.lineTo(N, 140); g.fill(); g.fillStyle = '#3a5a4a'; g.beginPath(); g.moveTo(150, 140); g.quadraticCurveTo(200, 60, 230, 140); g.fill(); });\n cell(6, 107, () => { const gr = g.createLinearGradient(0, 0, 0, N); gr.addColorStop(0, '#f6e0a8'); gr.addColorStop(0.5, '#fbeed0'); gr.addColorStop(0.5, '#4bb0c4'); gr.addColorStop(0.72, '#2e8aa8'); gr.addColorStop(0.72, '#ecd8a2'); gr.addColorStop(1, '#d9bd7e'); g.fillStyle = gr; g.fillRect(0, 0, N, N);\n g.strokeStyle = '#4a3a22'; g.lineWidth = 5; g.beginPath(); g.moveTo(60, 255); g.quadraticCurveTo(70, 160, 100, 70); g.stroke(); g.fillStyle = '#2f6a3a'; for (let i = 0; i < 6; i++) { g.beginPath(); g.moveTo(100, 70); g.quadraticCurveTo(100 + Math.cos(i) * 40, 40 + Math.sin(i * 2) * 20, 100 + Math.cos(i * 1.1) * 60, 80 + i * 6); g.quadraticCurveTo(100 + Math.cos(i) * 30, 70, 100, 70); g.fill(); } });\n cell(7, 108, () => { const gr = g.createLinearGradient(0, 0, N, N); gr.addColorStop(0, '#e4e9eb'); gr.addColorStop(0.5, '#c4ccd0'); gr.addColorStop(1, '#a8b2b8'); g.fillStyle = gr; g.fillRect(0, 0, N, N);\n g.fillStyle = 'rgba(255,255,255,.35)'; g.beginPath(); g.moveTo(30, 0); g.lineTo(90, 0); g.lineTo(0, 120); g.lineTo(0, 60); g.fill(); g.beginPath(); g.moveTo(150, 0); g.lineTo(175, 0); g.lineTo(0, 256); g.lineTo(0, 232); g.fill();\n const v = g.createRadialGradient(128, 128, 90, 128, 128, 190); v.addColorStop(0, 'rgba(0,0,0,0)'); v.addColorStop(1, 'rgba(0,0,0,.18)'); g.fillStyle = v; g.fillRect(0, 0, N, N); });\n const uvc = (c: number): Uv => ({ u0: (c % 4) / 4 + 1.5 / (N * 4), u1: (c % 4 + 1) / 4 - 1.5 / (N * 4), v0: 1 - (Math.floor(c / 4) + 1) / 2 + 1.5 / (N * 2), v1: 1 - Math.floor(c / 4) / 2 - 1.5 / (N * 2) });\n return { canvas: cv, n: PICTURE_CELLS - 1, uv: c => uvc(c), mirror: () => uvc(7), names };\n}\n\n// light/dark ripple that tiles; the host scrolls texture.offset for the shimmer\nfunction waterAtlas(doc: DocumentLike): WaterAtlas {\n const N = 128, cv = makeCanvas(doc, N, N), g = cv.getContext('2d')!, r = rng(901);\n g.fillStyle = '#e6f2f8'; g.fillRect(0, 0, N, N);\n seamless(g, 0, 0, N, () => { for (let i = 0; i < 26; i++) { const x = r() * N, y = r() * N, w = 14 + r() * 26; g.strokeStyle = r() < 0.55 ? 'rgba(255,255,255,.9)' : 'rgba(110,170,200,.5)'; g.lineWidth = 1 + r() * 1.5; g.beginPath(); g.moveTo(x, y); g.quadraticCurveTo(x + w / 2, y - 5 - r() * 4, x + w, y); g.stroke(); } });\n return { canvas: cv };\n}\n\n/** Lazily builds and caches the procedural canvas atlases (one canvas per atlas; the host makes one texture per canvas). */\nexport function createAtlasProvider(doc: DocumentLike | null, pictureNames: string[]) {\n const cache: { [K in AtlasName]?: AtlasMap[AtlasName] } = {}\n return function atlas<K extends AtlasName>(name: K): AtlasMap[K] {\n if (!doc) throw new Error('game-props3d: a document is needed to build the atlases (pass options.document)')\n const made = cache[name]\n if (made) return made as AtlasMap[K]\n const build = { floor: () => floorAtlas(doc), picture: () => pictureAtlas(doc, pictureNames), water: () => waterAtlas(doc) }[name]\n return (cache[name] = build()) as AtlasMap[K]\n }\n}\n","import type { PropGroup, TypeDef } from './types'\n\nconst T = <I extends string>(id: I, interior: boolean, w: number, d: number, h: number, y: number, solid: boolean, group: PropGroup): TypeDef<I> => ({ id, interior, w, d, h, y, solid, group })\n\n/** id, interior, w, d, h, y (mounting height), solid (false = no collision), group. Names live in the locale table. */\nexport const TYPE_DEFS = [\n T('ceiling_light', true, 0.4, 0.4, 0.07, 0, false, 'ceiling'),\n T('pendant', true, 0.34, 0.34, 1.0, 0, false, 'ceiling'),\n T('track_spot', true, 1.2, 0.1, 0.2, 0, false, 'ceiling'),\n T('chandelier', true, 0.7, 0.7, 0.7, 0, false, 'ceiling'),\n T('wall_ac', true, 0.9, 0.2, 0.28, 2.15, false, 'wall'),\n T('picture_small', true, 0.35, 0.03, 0.45, 1.3, false, 'wall'),\n T('picture_large', true, 1.0, 0.04, 0.7, 1.2, false, 'wall'),\n T('mirror', true, 0.6, 0.03, 1.2, 0.9, false, 'wall'),\n T('curtain', true, 1.6, 0.12, 2.3, 0.1, false, 'wall'),\n T('blinds', true, 1.2, 0.08, 1.3, 1.0, false, 'wall'),\n T('flat_tv', true, 1.2, 0.06, 0.7, 1.0, false, 'wall'),\n T('floor_plant', true, 0.5, 0.5, 1.1, 0, true, 'plants'),\n T('table_plant', true, 0.2, 0.2, 0.3, 0, false, 'plants'),\n T('hanging_plant', true, 0.5, 0.5, 1.0, 0, false, 'plants'),\n T('tv_stand', true, 1.6, 0.42, 1.05, 0, true, 'furniture'),\n T('breakfast_bar', true, 2.0, 1.6, 0.95, 0, true, 'furniture'),\n T('sofa_l', true, 2.8, 1.7, 0.85, 0, true, 'furniture'),\n T('double_bed_modern', true, 1.6, 2.1, 1.2, 0, true, 'furniture'),\n T('home_office', true, 1.4, 0.75, 1.2, 0, true, 'furniture'),\n T('closet', true, 2.2, 0.6, 2.3, 0, true, 'furniture'),\n T('vanity', true, 0.6, 0.45, 1.5, 0, true, 'furniture'),\n T('ac_condenser', false, 0.8, 0.3, 0.55, 0.5, true, 'yard'),\n T('lawn', false, 4, 4, 0.02, 0, false, 'floor'),\n T('tile_floor', false, 4, 3, 0.03, 0, false, 'floor'),\n T('concrete', false, 4, 4, 0.03, 0, false, 'floor'),\n T('deck_floor', false, 4, 3, 0.04, 0, false, 'floor'),\n T('dirt', false, 4, 4, 0.02, 0, false, 'floor'),\n T('gourmet_counter', false, 4.2, 3.0, 2.9, 0, true, 'yard'),\n T('sauna', false, 2.0, 1.8, 2.1, 0, true, 'yard'),\n T('soaking_tub', false, 1.3, 1.3, 0.8, 0, true, 'yard'),\n T('pool', false, 4.5, 2.8, 0.1, 0, true, 'yard'),\n T('deck', false, 3.5, 3.0, 0.22, 0, true, 'yard'),\n T('sun_lounger', false, 0.62, 1.9, 0.5, 0, true, 'yard'),\n T('garden_bed', false, 2.0, 1.0, 0.9, 0, true, 'yard'),\n T('fruit_tree', false, 2.4, 2.4, 3.2, 0, true, 'yard'),\n T('palm_tree', false, 2.0, 2.0, 4.5, 0, true, 'yard'),\n T('pergola', false, 3.5, 3.0, 2.4, 0, true, 'yard'),\n T('slide', false, 0.8, 2.6, 1.5, 0, true, 'yard'),\n T('swing', false, 2.2, 1.7, 2.2, 0, true, 'yard'),\n T('bbq_simple', false, 0.9, 0.6, 1.0, 0, true, 'yard'),\n T('garage_car', false, 1.8, 4.2, 1.45, 0, true, 'yard'),\n T('window_boards', true, 1.2, 0.06, 1.15, 1.0, false, 'house_state'),\n T('barricade', true, 1.5, 0.7, 1.8, 0, true, 'house_state'),\n T('sandbags', false, 1.6, 0.5, 0.6, 0, true, 'house_state'),\n T('barbed_wire', false, 4.0, 0.2, 0.6, 0, false, 'house_state'),\n T('razor_wire', false, 4.0, 0.6, 0.6, 0, false, 'house_state'),\n T('water_bottle_crates', true, 0.9, 0.6, 0.65, 0, true, 'house_state'),\n T('canned_food', true, 0.4, 0.3, 0.3, 0, false, 'house_state'),\n T('water_jug', true, 0.3, 0.3, 0.5, 0, true, 'house_state'),\n T('floor_mattress', true, 0.9, 1.9, 0.3, 0, false, 'house_state'),\n T('lantern', true, 0.16, 0.16, 0.3, 0, false, 'house_state'),\n T('candles', true, 0.25, 0.25, 0.2, 0, false, 'house_state'),\n T('open_suitcases', true, 1.5, 0.7, 0.6, 0, false, 'house_state'),\n T('clothes_on_floor', true, 1.2, 1.0, 0.05, 0, false, 'house_state'),\n T('fallen_chair', true, 0.5, 0.5, 0.5, 0, false, 'house_state'),\n T('blood_stains', true, 1.2, 1.2, 0.01, 0, false, 'house_state'),\n T('open_door', true, 0.9, 0.15, 2.15, 0, false, 'house_state'),\n]\n\nexport type PropType = (typeof TYPE_DEFS)[number]['id']\n\nexport const DEFS = Object.fromEntries(TYPE_DEFS.map((t) => [t.id, t])) as Record<PropType, (typeof TYPE_DEFS)[number]>\n\nexport const MATERIALS = ['plain', 'metal', 'wood', 'glass', 'fabric', 'plant', 'water', 'emissive', 'atlas_picture', 'atlas_floor'] as const\n","import type { BuilderContext, BuilderMap } from '../types'\n\nexport function ceilingBuilders({ kit }: BuilderContext): Partial<BuilderMap> {\n const B: Partial<BuilderMap> = {}\n B.ceiling_light = (M, a) => { const K = kit(M, a), t = K.ceiling(), on = !!M.on;\n K.cyl('plain', 0, t - 0.05, 0, 0.2, 0.05, 0xece8e0); K.cyl('plain', 0, t - 0.07, 0, 0.15, 0.02, 0xd8d4c8); K.cyl(on ? 'emissive' : 'plain', 0, t - 0.078, 0, 0.12, 0.012, on ? 0xfff0c8 : 0xf4f0e6);\n if (on) K.light(0, t - 0.15, 0, 0xffe8b8, 6, 'ceiling_light'); };\n B.pendant = (M, a) => { const K = kit(M, a), t = K.ceiling(), { h } = K.dim('pendant'), ys = t - h, color = K.main(0x2a2a2a);\n K.box('plain', 0, ys + 0.1, 0, 0.012, h - 0.1, 0.012, 0x1a1a1a); K.cyl('plain', 0, t - 0.03, 0, 0.05, 0.03, 0xe8e4dc);\n K.cyl('plain', 0, ys, 0, 0.17, 0.14, color, 0, 0, 'sphere'); K.cyl('emissive', 0, ys - 0.035, 0, 0.045, 0.09, 0xffe0a0, 0, 0, 'sphere');\n if (M.light) K.light(0, ys - 0.1, 0, 0xffc880, 6, 'pendant'); };\n B.track_spot = (M, a) => { const K = kit(M, a), t = K.ceiling(), { w } = K.dim('track_spot'), on = !!M.on;\n K.box('metal', 0, t - 0.025, 0, w, 0.025, 0.035, 0x2a2a2a);\n for (let i = -1; i <= 1; i++) { const u = i * w / 3; K.box('metal', u, t - 0.1, 0, 0.02, 0.075, 0.02, 0x2a2a2a); K.cyl('metal', u, t - 0.16, 0.04, 0.035, 0.11, 0x222222, 2.6, 0); K.cyl(on ? 'emissive' : 'plain', u, t - 0.208, 0.069, 0.028, 0.006, on ? 0xfff0c8 : 0xe8e8e8, 2.6, 0); } };\n B.chandelier = (M, a) => { const K = kit(M, a), t = K.ceiling(), on = !!M.on, y = t - 0.5, gold = K.metal(0x8a7a5a);\n K.box('metal', 0, y, 0, 0.012, 0.5, 0.012, gold); K.cyl('metal', 0, y - 0.04, 0, 0.06, 0.08, gold); K.cyl('metal', 0, y - 0.06, 0, 0.14, 0.015, gold);\n for (let i = 0; i < 6; i++) { const an = i * Math.PI / 3, cx = Math.cos(an), cz = Math.sin(an);\n K.box('metal', cx * 0.14, y - 0.06, cz * 0.14, 0.28, 0.012, 0.012, gold, -an); K.cyl('plain', cx * 0.28, y - 0.06, cz * 0.28, 0.02, 0.09, 0xf0e6c8); K.cyl(on ? 'emissive' : 'plain', cx * 0.28, y + 0.03, cz * 0.28, 0.012, 0.03, on ? 0xffd890 : 0xe6d8b0, 0, 0, 'sphere');\n K.box('glass', cx * 0.3, y - 0.2, cz * 0.3, 0.025, 0.14, 0.025, 0xffffff, -an); K.box('glass', cx * 0.14, y - 0.18, cz * 0.14, 0.02, 0.12, 0.02, 0xffffff); }\n K.box('glass', 0, y - 0.3, 0, 0.05, 0.2, 0.05, 0xffffff, 0.7);\n if (M.light) K.light(0, y - 0.1, 0, 0xffd8a0, 6, 'chandelier'); };\n return B\n}\n","import type { BuilderContext, BuilderMap, FloorKind } from '../types'\n\nconst FLOOR_ITEMS: Array<FloorKind & keyof BuilderMap> = ['lawn', 'tile_floor', 'concrete', 'deck_floor', 'dirt']\n\n/** Ground cover items: one builder each, tiling the item's rectangle (or `M.poly`) with the floor atlas. */\nexport function floorBuilders({ cover }: BuilderContext): Partial<BuilderMap> {\n const B: Partial<BuilderMap> = {}\n for (const kind of FLOOR_ITEMS) B[kind] = (M, api) => cover(api, M, kind, { color: M.color })\n return B\n}\n","import { shade } from '../colors'\nimport type { Kit } from '../types'\n\nexport const GREENS = [0x3f7a3a, 0x2f6a30, 0x4f8a42, 0x5a9448]\n/** i-th shade of green (from the `plant` palette role or the defaults), alternating darker. */\nexport const green = (K: Kit, i: number): number => {\n const v = K.color('plant', GREENS[i % GREENS.length])\n return i % 2 ? shade(v, 0.85) : v\n}\nexport const pick = <T>(K: Kit, L: T[]): T => L[Math.floor(K.rnd() * L.length) % L.length]\n\n// flat TV: panel, screen, back box and either a stand (two feet) or a wall bracket; returns the panel height\nexport const drawTv = (K: Kit, x: number, y: number, z: number, w: number, stand: boolean): number => { const h = w * 0.5625, yb = stand ? y + 0.07 : y;\n K.box('plain', x, yb, z, w, h, 0.035, 0x101112); K.box('metal', x, yb + 0.015, z + 0.0175, w - 0.03, h - 0.03, 0.004, 0x0a0c10); K.box('plain', x, yb + h * 0.2, z - 0.035, w * 0.5, h * 0.55, 0.04, 0x1a1a1a);\n if (stand) { for (const s of [-1, 1]) K.box('metal', x + s * w * 0.33, y, z, 0.1, 0.07, 0.22, 0x1a1a1a); } else K.box('metal', x, yb + h * 0.35, z - 0.065, w * 0.3, 0.03, 0.03, 0x2a2a2a); return h; };\n","import type { BuilderContext, BuilderMap } from '../types'\nimport { shade } from '../colors'\nimport { drawTv, pick } from './shared'\n\nexport function furnitureBuilders({ kit, atlas }: BuilderContext): Partial<BuilderMap> {\n const B: Partial<BuilderMap> = {}\n B.breakfast_bar = (M, a) => { const K = kit(M, a), { w, d } = K.dim('breakfast_bar'), zb = -d / 2, cd = 0.65, body = K.color('wall_kitchen', 0xf0eee8), wood = K.wood(0xa07a50), paving = 0xf4f4f0;\n K.box('plain', 0, 0, zb + cd / 2 - 0.03, w - 0.06, 0.08, cd - 0.08, 0x2a2a2a); K.box('plain', 0, 0.08, zb + cd / 2, w, 0.8, cd, body);\n for (let i = 0; i < 4; i++) { K.box('wood', -w / 2 + (i + 0.5) * w / 4, 0.12, zb - 0.004, w / 4 - 0.025, 0.72, 0.012, wood); K.box('metal', -w / 2 + (i + 0.5) * w / 4 + (i % 2 ? -0.12 : 0.12), 0.75, zb - 0.012, 0.012, 0.14, 0.016, 0x9a9a9a); }\n K.box('plain', 0, 0.88, zb + (cd + 0.32) / 2, w + 0.04, 0.04, cd + 0.32, paving);\n for (const s of [-1, 1]) K.box('metal', s * (w / 2 - 0.05), 0, zb + cd + 0.24, 0.05, 0.88, 0.05, 0x2a2a2a);\n K.box('metal', -w * 0.22, 0.915, zb + cd / 2, 0.52, 0.006, 0.38, 0xb0b4b8); K.box('metal', -w * 0.22, 0.92, zb + cd / 2, 0.46, 0.004, 0.32, 0x70767a); K.cyl('metal', -w * 0.22, 0.92, zb + 0.1, 0.014, 0.26, 0xb8bcc0); K.box('metal', -w * 0.22, 1.16, zb + 0.15, 0.02, 0.02, 0.1, 0xb8bcc0);\n K.cyl('plain', w * 0.25, 0.92, zb + cd / 2 + 0.2, 0.13, 0.05, 0xf0f0ea); for (let i = 0; i < 3; i++) K.cyl('plain', w * 0.25 + (i - 1) * 0.07, 0.97, zb + cd / 2 + 0.2 + (i % 2) * 0.03, 0.045, 0.09, [0xe8801c, 0xc83030, 0xd8c040][i], 0, 0, 'sphere');\n const stool = K.accent(0x8a5a38); for (let i = 0; i < 3; i++) { const x = (i - 1) * w * 0.3, z = zb + cd + 0.5;\n K.cyl('plain', x, 0.62, z, 0.17, 0.05, stool); K.cyl('metal', x, 0.04, z, 0.02, 0.58, 0x2a2a2a); K.cyl('metal', x, 0, z, 0.17, 0.02, 0x2a2a2a); K.cyl('metal', x, 0.25, z, 0.14, 0.012, 0x2a2a2a); } };\n B.sofa_l = (M, a) => { const K = kit(M, a), { w, d } = K.dim('sofa_l'), c = K.fabric(pick(K, [0x6e7078, 0x8a7a68, 0x45586a])), cd = shade(c, 0.82), zb = -d / 2, pf = 0.95, ac = K.accent(0xc89a4a), cw = 0.95;\n for (const sx of [-1, 1]) for (const sz of [-1, 1]) K.box('wood', sx * (w / 2 - 0.1), 0, sz * (d / 2 - 0.1), 0.05, 0.08, 0.05, K.wood(0x2a1d14));\n K.box('fabric', -(cw / 2), 0.08, zb + pf / 2, w - cw, 0.26, pf, cd); K.box('fabric', w / 2 - cw / 2, 0.08, 0, cw, 0.26, d, cd);\n K.box('fabric', 0, 0.08, zb + 0.11, w, 0.72, 0.22, cd); K.box('fabric', -w / 2 + 0.09, 0.08, zb + pf / 2, 0.18, 0.55, pf, cd);\n const nm = 2, mw = (w - cw - 0.18) / nm; for (let i = 0; i < nm; i++) K.box('fabric', -w / 2 + 0.18 + (i + 0.5) * mw, 0.34, zb + 0.12 + 0.39, mw - 0.02, 0.16, 0.78, c);\n K.box('fabric', w / 2 - cw / 2, 0.34, 0.035, cw - 0.04, 0.16, d - 0.2, c);\n for (let i = 0; i < 4; i++) K.box('fabric', -w / 2 + 0.18 + (i + 0.5) * (w - 0.2) / 4, 0.42, zb + 0.28, (w - 0.2) / 4 - 0.03, 0.4, 0.14, c, 0, 0.15);\n K.box('fabric', -w * 0.3, 0.5, zb + 0.5, 0.4, 0.38, 0.12, ac, 0.3, 0.3); K.box('fabric', w * 0.32, 0.5, zb + 0.5, 0.38, 0.36, 0.12, shade(ac, 0.8), -0.3, 0.3); };\n B.double_bed_modern = (M, a) => { const K = kit(M, a), { w, d } = K.dim('double_bed_modern'), wood = K.wood(0x6a4c34), edr = K.fabric(0x8a8d92), accent = K.accent(0xc89a4a);\n K.box('wood', 0, 0.05, 0.02, w + 0.06, 0.2, d + 0.02, shade(wood, 0.85)); K.box('fabric', 0, 0.25, 0.05, w - 0.04, 0.24, d - 0.1, 0xf2f0ea);\n K.box('fabric', 0, 0.47, 0.35, w, 0.06, d * 0.64, edr); K.box('fabric', 0, 0.5, -0.05, w * 0.98, 0.05, 0.3, shade(edr, 1.12)); K.box('fabric', 0, 0.52, d / 2 - 0.4, w * 0.9, 0.03, 0.3, accent);\n for (const s of [-1, 1]) K.box('fabric', s * w * 0.24, 0.49, -d / 2 + 0.3, w * 0.4, 0.13, 0.36, 0xf4f2ec, s * 0.06);\n K.box('fabric', 0, 0.55, -d / 2 + 0.5, 0.4, 0.16, 0.12, accent, 0, 0.5);\n const hw = w + 0.5, n = Math.round(hw / 0.11); K.box('wood', 0, 0.2, -d / 2 - 0.05, hw, 1.0, 0.04, shade(wood, 0.7)); for (let i = 0; i < n; i++) K.box('wood', -hw / 2 + (i + 0.5) * hw / n, 0.25, -d / 2 - 0.02, hw / n - 0.025, 0.95, 0.04, i % 2 ? wood : shade(wood, 1.12));\n for (const s of [-1, 1]) { K.box('wood', s * (w / 2 + 0.3), 0.35, -d / 2 + 0.22, 0.4, 0.16, 0.36, shade(wood, 1.05)); K.cyl('plain', s * (w / 2 + 0.3), 0.51, -d / 2 + 0.2, 0.06, 0.03, 0xe8e4dc); K.cyl('plain', s * (w / 2 + 0.3), 0.54, -d / 2 + 0.2, 0.012, 0.18, 0xe8e4dc); K.cyl('plain', s * (w / 2 + 0.3), 0.72, -d / 2 + 0.2, 0.09, 0.12, 0xf0e6c8); } };\n B.home_office = (M, a) => { const K = kit(M, a), { w, d } = K.dim('home_office'), wood = K.wood(0xd2b48c), zb = -d / 2;\n K.box('wood', 0, 0.72, 0, w, 0.035, d, wood); for (const s of [-1, 1]) K.box('metal', s * (w / 2 - 0.04), 0, 0, 0.035, 0.72, d - 0.1, 0x222226); K.box('metal', 0, 0.3, zb + 0.05, w - 0.1, 0.03, 0.02, 0x222226);\n K.box('plain', w / 2 - 0.25, 0.28, 0, 0.4, 0.44, d - 0.08, 0xe8e4dc); for (let i = 0; i < 2; i++) { K.box('plain', w / 2 - 0.25, 0.3 + i * 0.2, d / 2 - 0.04, 0.37, 0.18, 0.01, 0xf2eee6); K.box('metal', w / 2 - 0.25, 0.39 + i * 0.2, d / 2 - 0.03, 0.12, 0.012, 0.012, 0x9a9a9a); }\n K.box('metal', 0, 0.755, zb + 0.2, 0.22, 0.012, 0.16, 0x1a1a1a); K.box('metal', 0, 0.76, zb + 0.14, 0.04, 0.2, 0.025, 0x1a1a1a); K.box('plain', 0, 0.88, zb + 0.15, 0.62, 0.36, 0.035, 0x101112); K.box('metal', 0, 0.895, zb + 0.1685, 0.6, 0.33, 0.004, 0x0a0c10);\n K.box('plain', 0, 0.755, zb + 0.43, 0.42, 0.014, 0.14, 0x1a1a1c); K.box('plain', 0.34, 0.755, zb + 0.43, 0.06, 0.025, 0.1, 0x1a1a1c, 0.2);\n K.cyl('metal', -w * 0.4, 0.755, zb + 0.2, 0.07, 0.015, 0x2a2a2a); K.box('metal', -w * 0.4, 0.77, zb + 0.2, 0.012, 0.3, 0.012, 0x2a2a2a, 0, 0.3); K.cyl('plain', -w * 0.4, 1.02, zb + 0.28, 0.06, 0.05, K.main(0xd8d4c8), 0.7, 0);\n K.box('fabric', -0.5, 0.755, 0.05, 0.2, 0.02, 0.28, K.accent(0x2e5e7e), 0.2); K.cyl('plain', -0.3, 0.755, 0.12, 0.04, 0.09, 0xd8d0c0);\n const zc = d / 2 + 0.3, cc = K.fabric(0x2a2a2e); K.box('plain', 0, 0.44, zc, 0.48, 0.07, 0.46, cc); K.box('plain', 0, 0.52, zc + 0.22, 0.44, 0.52, 0.07, cc, 0, 0.12); K.cyl('metal', 0, 0.06, zc, 0.025, 0.38, 0x2a2a2a);\n for (let i = 0; i < 5; i++) { const an = i * 1.2566, x = Math.cos(an) * 0.17, z = zc + Math.sin(an) * 0.17; K.box('metal', Math.cos(an) * 0.085, 0.06, zc + Math.sin(an) * 0.085, 0.18, 0.025, 0.03, 0x2a2a2a, -an); K.cyl('plain', x, 0, z, 0.03, 0.05, 0x1a1a1a); } };\n B.closet = (M, a) => { const K = kit(M, a), { w, d, h } = K.dim('closet'), wood = K.color('wall_bedroom', 0xe6e0d4), mw = K.wood(0xb89468), dv = -w * 0.1;\n K.box('plain', 0, 0, -d / 2 + 0.01, w, h, 0.02, shade(wood, 0.95)); for (const s of [-1, 1]) K.box('plain', s * (w / 2 - 0.015), 0, 0, 0.03, h, d, wood); K.box('plain', 0, h - 0.03, 0, w, 0.03, d, wood); K.box('plain', 0, 0.06, 0, w - 0.06, 0.02, d - 0.02, wood); K.box('plain', 0, 0, 0.0, w - 0.04, 0.06, d - 0.06, 0x2a2a2a); K.box('plain', dv, 0.06, 0, 0.025, h - 0.09, d - 0.02, wood);\n const rod = h * 0.76; K.cyl('metal', (-w / 2 + dv) / 2, rod, 0, 0.012, dv + w / 2 - 0.06, 0xb0b4b8, 0, Math.PI / 2); K.box('plain', (-w / 2 + dv) / 2, rod + 0.05, 0, dv + w / 2 - 0.05, 0.02, d - 0.04, wood);\n const nc = 9, L0 = -w / 2 + 0.06, L1 = dv - 0.05, cs = [0x2a2a30, 0xc8c0a8, 0x4a6a8a, 0xa8483a, 0xe0dcd0, 0x6a8a5a, 0x3a3a3a, 0x8a7a68, 0xd8b8a0];\n for (let i = 0; i < nc; i++) { const gh = 0.6 + K.rnd() * 0.5; K.box('fabric', L0 + (i + 0.5) * (L1 - L0) / nc, rod - gh - 0.02, 0, 0.045, gh, 0.4, i % 3 === 0 ? K.accent(cs[i]) : cs[i]); K.box('metal', L0 + (i + 0.5) * (L1 - L0) / nc, rod - 0.04, 0, 0.004, 0.04, 0.2, 0xb0b4b8); }\n const sx = (dv + w / 2) / 2 + dv / 2 + 0.0, rx0 = dv + 0.015, rw = w / 2 - 0.015 - rx0, rc = rx0 + rw / 2;\n for (let i = 0; i < 5; i++) { const y = 0.5 + i * 0.35; K.box('plain', rc, y, 0, rw, 0.022, d - 0.02, wood); if (i < 4) for (let j = 0; j < 3; j++) { const sh = 0.04 + K.rnd() * 0.12; K.box('fabric', rc - rw * 0.3 + j * rw * 0.3, y + 0.022, 0.0, 0.26, sh, 0.32, cs[(i * 3 + j) % cs.length]); } }\n for (let i = 0; i < 2; i++) { K.box('wood', rc, 0.08 + i * 0.2, d / 2 - 0.01, rw - 0.03, 0.18, 0.02, mw); K.box('metal', rc, 0.2 + i * 0.2, d / 2 + 0.005, 0.2, 0.012, 0.014, 0xb0b4b8); } void sx; };\n B.vanity = (M, a) => { const K = kit(M, a), { w, d } = K.dim('vanity'), zb = -d / 2, br = 0xf4f4f0;\n K.cyl('plain', 0, 0, zb + 0.12, 0.08, 0.7, br); K.cyl('plain', 0, 0, zb + 0.12, 0.1, 0.03, br); K.box('plain', 0, 0.74, 0, w, 0.12, d, br); K.box('plain', 0, 0.865, 0.02, w - 0.1, 0.012, d - 0.1, 0xd4d8da); K.cyl('metal', 0, 0.87, 0.05, 0.02, 0.01, 0x70767a);\n K.cyl('metal', 0, 0.87, zb + 0.05, 0.014, 0.1, 0xb8bcc0); K.box('metal', 0, 0.96, zb + 0.09, 0.02, 0.014, 0.09, 0xb8bcc0); K.box('metal', w * 0.3, 0.87, zb + 0.06, 0.04, 0.06, 0.04, 0x2a7a8a);\n K.box('plain', 0, 1.1, zb + 0.012, 0.52, 0.72, 0.024, K.color('metal', 0xe4e0d8)); K.plane('atlas_picture', 0, 1.46, zb + 0.026, 0.46, 0.66, 0xffffff, 0, 0, 0, atlas('picture').mirror());\n K.box('metal', -w / 2 - 0.02, 0.95, zb + 0.06, 0.03, 0.03, 0.03, 0xb0b4b8); K.box('metal', -w / 2 - 0.02, 0.95, zb + 0.06, 0.28, 0.014, 0.014, 0xb0b4b8); K.box('fabric', -w / 2 - 0.02, 0.62, zb + 0.065, 0.26, 0.34, 0.02, K.accent(0x2e6e7e)); };\n B.tv_stand = (M, a) => { const K = kit(M, a), { w, d } = K.dim('tv_stand'), wood = K.wood(0xc8a070), br = K.color('fabric_main', 0xf0ede6);\n for (const sx of [-1, 1]) for (const sz of [-1, 1]) K.box('metal', sx * (w / 2 - 0.08), 0, sz * (d / 2 - 0.06), 0.035, 0.16, 0.035, 0x1a1a1a);\n K.box('wood', 0, 0.16, 0, w, 0.32, d, wood); K.box('plain', 0, 0.48, 0, w + 0.02, 0.025, d + 0.02, br);\n for (let i = 0; i < 2; i++) K.box('plain', -w / 2 + (i + 0.5) * 0.375 * w, 0.18, d / 2 + 0.004, 0.375 * w - 0.02, 0.28, 0.01, shade(br, 0.97)); // two doors on the left\n K.box('plain', w / 4 + w / 8, 0.19, 0, w / 4 - 0.04, 0.2, d - 0.04, 0x1c1c20); K.box('plain', w / 4 + w / 8, 0.22, 0.05, 0.28, 0.06, 0.2, 0x2a2a30); K.box('fabric', w / 4 + w / 8 + 0.03, 0.29, 0.0, 0.2, 0.05, 0.14, K.accent(0x8a4a3a)); // open niche: console and books\n const th = drawTv(K, 0, 0.5, 0, Math.min(1.3, w * 0.78), true); K.box('plain', 0, 0.5, d / 2 - 0.06, Math.min(1.0, w * 0.55), 0.06, 0.06, 0x16161a); void th; };\n return B\n}\n","import { seedOf, shade } from '../colors'\nimport type { BuilderContext, BuilderMap, Material, PropItem, PropsApi } from '../types'\n\nexport function houseStateBuilders({ kit }: BuilderContext): Partial<BuilderMap> {\n const B: Partial<BuilderMap> = {}\n B.window_boards = (M, a) => { const K = kit(M, a), { w, h, d } = K.dim('window_boards'), y = M.y || 1.0, x = M.variant ? M.variant === 'x' : (seedOf(M) & 1) === 0, cs = [0x7a5634, 0x6b4a2a, 0x8a6a44, 0x5e4228], z = d / 2;\n if (x) { const L = Math.hypot(w, h) + 0.15, an = Math.atan2(h, w); for (const s of [-1, 1]) K.box('wood', 0, y + h / 2 - 0.075, z + (s > 0 ? 0 : 0.03), L, 0.15, 0.03, cs[s > 0 ? 0 : 1], 0, 0, s * an + (K.rnd() - 0.5) * 0.05); K.box('wood', 0, y + h * 0.3, z + 0.06, w + 0.3, 0.15, 0.03, cs[2], 0, 0, (K.rnd() - 0.5) * 0.06); for (const [nx, ny] of [[-w / 2, y], [w / 2, y], [-w / 2, y + h], [w / 2, y + h], [0, y + h / 2]]) K.cyl('metal', nx, ny, z + 0.09, 0.012, 0.02, 0x5a5a5e, Math.PI / 2, 0); }\n else { const n = Math.ceil(h / 0.17); for (let i = 0; i < n; i++) { const yy = y + i * (h - 0.15) / (n - 1 || 1), ww = w + 0.2 + K.rnd() * 0.15; K.box('wood', (K.rnd() - 0.5) * 0.1, yy, z + (i % 2) * 0.03, ww, 0.15, 0.03, cs[i % 4], 0, 0, (K.rnd() - 0.5) * 0.05); for (const s of [-1, 1]) K.cyl('metal', s * (w / 2 + 0.02), yy + 0.075, z + (i % 2) * 0.03 + 0.016, 0.01, 0.015, 0x5a5a5e, Math.PI / 2, 0); } } };\n B.barricade = (M, a) => { const K = kit(M, a), { w, d, h } = K.dim('barricade'), wood = K.wood(0x6a4a34), z0 = -d / 2;\n K.box('wood', -w * 0.22, 0, z0 + 0.3, w * 0.5, h * 0.8, 0.45, wood, 0.06, -0.12); K.box('plain', -w * 0.22, h * 0.3, z0 + 0.54, 0.3, 0.012, 0.01, 0x9a9a9a);\n K.box('fabric', w * 0.2, 0, z0 + 0.4, w * 0.5, h * 0.95, 0.18, K.fabric(0xc8c0b0), -0.08, -0.28); K.box('fabric', w * 0.2, 0, z0 + 0.4, w * 0.5 - 0.1, h * 0.9, 0.19, K.accent(0x5a7a9a), -0.08, -0.28);\n K.box('plain', w * 0.38, 0, z0 + 0.35, 0.5, 0.4, 0.4, 0xb89a6a, 0.2); K.box('plain', w * 0.4, 0.4, z0 + 0.33, 0.45, 0.35, 0.38, 0xa8885a, -0.3);\n for (let i = 0; i < 4; i++) { K.box('wood', 0, 0.45 + i * 0.3, z0 + 0.62, w + 0.3, 0.14, 0.04, [0x7a5634, 0x8a6a44, 0x6b4a2a, 0x7a5a38][i], 0, 0, (i % 2 ? 1 : -1) * (0.06 + K.rnd() * 0.08)); for (const s of [-1, 1]) K.cyl('metal', s * w * 0.4, 0.45 + i * 0.3, z0 + 0.65, 0.012, 0.02, 0x5a5a5e, Math.PI / 2, 0); }\n K.box('wood', -w * 0.4, 0.0, z0 + 0.9, 0.4, 0.04, 0.4, wood, 0.3, 0, 0.4); K.box('wood', -w * 0.45, 0.0, z0 + 0.7, 0.04, 0.5, 0.04, wood, 0, 0.9, 0.2); };\n B.sandbags = (M, a) => { const K = kit(M, a), { w, d, h } = K.dim('sandbags'), cs = [K.fabric(0xb8a678), 0xa89868, 0xc4b284], bw = 0.46, bh = 0.17, nl = Math.max(1, Math.min(4, Math.round(h / bh)));\n for (let l = 0; l < nl; l++) { const nb = Math.max(1, Math.floor((w - l * 0.18) / bw)); for (let i = 0; i < nb; i++) { const x = -nb * bw / 2 + (i + 0.5) * bw + (l % 2) * 0.12; K.box('fabric', x, l * bh * 0.95, (K.rnd() - 0.5) * 0.04, bw * 0.97, bh, Math.min(d, 0.3), cs[(i + l) % 3], (K.rnd() - 0.5) * 0.08, 0, (K.rnd() - 0.5) * 0.04); K.box('fabric', x + bw * 0.46, l * bh * 0.95 + 0.04, 0, 0.03, 0.09, 0.2, shade(cs[(i + l) % 3], 0.8)); } } };\n B.barbed_wire = (M, a) => { const K = kit(M, a), { w } = K.dim('barbed_wire'), np = Math.max(2, Math.round(w / 2) + 1), wire = 0x8a8c90;\n for (let i = 0; i < np; i++) { const x = -w / 2 + i * w / (np - 1); K.box('wood', x, 0, 0, 0.07, 0.6, 0.07, 0x5a4a3a); }\n for (const y of [0.25, 0.4, 0.55]) { K.box('metal', 0, y, 0.04, w, 0.008, 0.008, wire); const nb = Math.floor(w / 0.16); for (let i = 0; i < nb; i++) { const x = -w / 2 + (i + 0.5) * w / nb; K.box('metal', x, y - 0.012, 0.04, 0.05, 0.006, 0.006, wire, 0, 0, 0.8); K.box('metal', x, y - 0.012, 0.04, 0.05, 0.006, 0.006, wire, 0, 0, -0.8); } } };\n B.razor_wire = (M, a) => { const K = kit(M, a), { w } = K.dim('razor_wire'), r = 0.28, wire = 0x9a9ca0, nr = Math.max(3, Math.round(w / 0.22)), ns = 8, sl = 2 * Math.PI * r / ns * 1.05, y0 = M.y || 0;\n for (let i = 0; i < nr; i++) { const x = -w / 2 + (i + 0.5) * w / nr, lean = (i % 2 ? 0.1 : -0.1); for (let s = 0; s < ns; s++) { const th = (s + 0.5) / ns * Math.PI * 2; K.box('metal', x + Math.sin(th) * lean * 0.3, y0 + r + Math.sin(th) * r - 0.006, Math.cos(th) * r, 0.01, 0.012, sl, wire, 0, -(th + Math.PI / 2), lean); } }\n for (const s of [-1, 1]) K.box('metal', s * w / 2, y0, 0, 0.03, 0.12, 0.03, 0x5a5a5e); };\n B.water_bottle_crates = (M, a) => { const K = kit(M, a), { w, d } = K.dim('water_bottle_crates'), pw = 0.4, pd = 0.28, ph = 0.27, nx = Math.max(1, Math.floor(w / pw)), nz = Math.max(1, Math.floor(d / pd)), nl = 2;\n K.box('wood', 0, 0, 0, nx * pw, 0.05, nz * pd, 0x8a6a44);\n for (let l = 0; l < nl; l++) for (let i = 0; i < nx; i++) for (let j = 0; j < nz; j++) { const x = -nx * pw / 2 + (i + 0.5) * pw, z = -nz * pd / 2 + (j + 0.5) * pd, y = 0.05 + l * ph; K.box('plain', x, y, z, pw - 0.01, ph, pd - 0.01, 0xdceaf4); K.box('plain', x, y + 0.08, z, pw - 0.005, 0.08, pd - 0.005, K.accent(0x2a6aa8)); if (l === nl - 1) for (let u = 0; u < 3; u++) for (let v = 0; v < 2; v++) K.cyl('plain', x - 0.11 + u * 0.11, y + ph, z - 0.06 + v * 0.12, 0.028, 0.02, 0x2a6aa8); } };\n B.canned_food = (M, a) => { const K = kit(M, a), y = M.y || 0, cs = [0xc8a030, 0xb03a2a, 0x4a7a3a, 0xd8d0b0, 0x8a3a2a], rw = [4, 3, 2];\n rw.forEach((n, r) => { for (let i = 0; i < n; i++) { const x = (i - (n - 1) / 2) * 0.072, c = cs[(i + r * 2) % cs.length]; K.cyl('plain', x, y + r * 0.1, 0, 0.033, 0.1, c); K.cyl('plain', x, y + r * 0.1 + 0.02, 0, 0.0345, 0.055, 0xe8e4d8); K.cyl('metal', x, y + r * 0.1 + 0.1, 0, 0.031, 0.004, 0xb8bcc0); } });\n K.cyl('plain', 0.3, y + 0.033, 0.1, 0.033, 0.1, 0xc8a030, 0, Math.PI / 2); K.cyl('plain', -0.32, y + 0.033, 0.12, 0.033, 0.1, 0xb03a2a, 0, 1.2); };\n B.water_jug = (M, a) => { const K = kit(M, a), y = M.y || 0, az = K.accent(0x5aa0d8);\n K.cyl('plain', 0, y, 0, 0.13, 0.32, az); K.cyl('plain', 0, y + 0.32, 0, 0.1, 0.05, az); K.cyl('plain', 0, y + 0.37, 0, 0.05, 0.08, az); K.cyl('plain', 0, y + 0.45, 0, 0.055, 0.035, 0x2a5a9a); K.cyl('plain', 0, y + 0.12, 0, 0.1325, 0.1, 0xf4f4f0); K.box('plain', 0.14, y + 0.22, 0, 0.04, 0.12, 0.05, shade(az, 0.85)); };\n B.floor_mattress = (M, a) => { const K = kit(M, a), { w, d } = K.dim('floor_mattress'), h = 0.17, c = K.fabric(0xc8c0b0), ac = K.accent(0x6a4a3a);\n K.box('fabric', 0, 0, 0, w, h, d, c); for (let i = 0; i < 4; i++) K.box('fabric', -w / 2 + (i + 0.5) * w / 4, h, 0, w / 8, 0.003, d - 0.04, shade(c, 0.86)); K.box('fabric', 0, h, -d / 2 + 0.28, w * 0.7, 0.1, 0.38, 0xf0ece0, 0.05);\n K.box('fabric', 0.03, h, 0.3, w * 0.95, 0.05, d * 0.55, ac, 0.05); K.box('fabric', 0.15, h + 0.05, 0.0, w * 0.5, 0.05, 0.4, shade(ac, 1.2), 0.3); K.box('fabric', -0.2, h + 0.05, 0.5, w * 0.3, 0.06, 0.3, shade(ac, 0.8), -0.4); };\n B.lantern = (M, a) => { const K = kit(M, a), y = M.y || 0;\n K.cyl('metal', 0, y, 0, 0.07, 0.03, 0x2a4a2a); K.cyl('emissive', 0, y + 0.03, 0, 0.055, 0.18, 0xffd890); for (const sx of [-1, 1]) for (const sz of [-1, 1]) K.box('metal', sx * 0.05, y + 0.03, sz * 0.05, 0.01, 0.18, 0.01, 0x2a2a2a);\n K.cyl('metal', 0, y + 0.21, 0, 0.075, 0.025, 0x2a4a2a); K.box('metal', 0, y + 0.235, 0, 0.1, 0.012, 0.012, 0x2a2a2a); for (const s of [-1, 1]) K.box('metal', s * 0.05, y + 0.235, 0, 0.01, 0.045, 0.01, 0x2a2a2a);\n if (M.lit !== false) K.light(0, y + 0.15, 0, 0xffc880, 7, 'lantern'); };\n B.candles = (M, a) => { const K = kit(M, a), y = M.y || 0;\n K.cyl('plain', 0, y, 0, 0.09, 0.012, 0xf0f0ea); [[0, 0, 0.16], [0.06, 0.03, 0.1], [-0.05, 0.04, 0.12], [0.02, -0.06, 0.07]].forEach(([x, z, h], i) => { K.cyl('plain', x, y + 0.012, z, 0.017, h, 0xf0e6c8); K.cyl('emissive', x, y + 0.012 + h, z, 0.008, 0.03, i % 2 ? 0xffc060 : 0xffd890, 0, 0, 'sphere'); });\n K.cyl('plain', 0.02, y + 0.012, 0.0, 0.05, 0.008, 0xf0e6c8); if (M.lit !== false) K.light(0, y + 0.22, 0, 0xffb060, 5, 'candles'); };\n B.open_suitcases = (M, a) => { const K = kit(M, a), cs = [K.main(0xb03a34), 0x2a3a5a], rs = [0xc8c0a8, 0x4a6a8a, 0xe0dcd0, 0xa8483a];\n const suitcase = (x: number, z: number, ww: number, dd: number, c: number, ry: number, al: number) => { const lx = x, hz = z - dd / 2;\n K.box('plain', lx, 0, z, ww, 0.16, dd, c, ry); K.box('fabric', lx, 0.12, z, ww - 0.06, 0.05, dd - 0.06, 0xd8d0c0, ry);\n for (let i = 0; i < 5; i++) K.box('fabric', lx + (K.rnd() - 0.5) * (ww - 0.2), 0.16, z + (K.rnd() - 0.5) * (dd - 0.2), 0.14 + K.rnd() * 0.1, 0.04 + K.rnd() * 0.04, 0.12 + K.rnd() * 0.1, rs[i % 4], K.rnd() * 3);\n K.ramp('plain', lx, 0.16, hz, ww, 0.07, dd, c, al); K.box('metal', lx + ww * 0.3, 0.1, z + dd / 2, 0.04, 0.03, 0.01, 0xc0c0c0); };\n suitcase(-0.4, 0, 0.75, 0.5, cs[0], 0.15, 1.35); suitcase(0.45, 0.1, 0.6, 0.42, cs[1], -0.3, 1.2); };\n B.clothes_on_floor = (M, a) => { const K = kit(M, a), { w, d } = K.dim('clothes_on_floor'), cs = [0xc8c0a8, 0x4a6a8a, 0xa8483a, 0xe0dcd0, 0x6a8a5a, 0x2a2a30, K.accent(0xc89a4a)];\n for (let i = 0; i < 9; i++) { const x = (K.rnd() - 0.5) * (w - 0.3), z = (K.rnd() - 0.5) * (d - 0.3), r = K.rnd() * 6, c = cs[i % cs.length], y = 0.012 + i * 0.003;\n if (i % 3 === 0) { K.box('fabric', x, y, z, 0.3, 0.012, 0.36, c, r); for (const s of [-1, 1]) K.box('fabric', x + Math.cos(r) * s * 0.2, y, z - Math.sin(r) * s * 0.2, 0.2, 0.012, 0.09, shade(c, 0.9), r + s * 0.5); }\n else if (i % 3 === 1) { for (const s of [-1, 1]) K.box('fabric', x + s * 0.06, y, z, 0.13, 0.014, 0.5, 0x3a4a6a, r + s * 0.15); } else K.box('fabric', x, y, z, 0.14, 0.01, 0.07, c, r); }\n const sx = (K.rnd() - 0.5) * (w - 0.4), sz = (K.rnd() - 0.5) * (d - 0.4); K.box('plain', sx, 0.012, sz, 0.09, 0.07, 0.27, 0x2a2a2a, 0.4); K.box('plain', sx + 0.15, 0.012, sz + 0.05, 0.09, 0.07, 0.27, 0x2a2a2a, 0.9); };\n B.fallen_chair = (M, a) => { // wooden chair knocked over on its side: the parts are axis-aligned boxes, rotated 90 degrees around z\n const g = Math.PI / 2, dy = 0.23, api = { box: (Mm: PropItem, mat: Material, x: number, y: number, z: number, w: number, h: number, d: number, color: number, ry: number, rx: number, rz?: number) => { const cy = y + h / 2; a.box(Mm, mat, -cy + 0.0, dy + x - h / 2 + 0.0 - 0.0, z, w, h, d, color, ry, rx, g + (rz || 0)); } };\n const K = kit(M, api as unknown as PropsApi), c = K.main(0x6a4a30); // center (x, y, z) becomes (-cy, x): the api y is the base, so h/2 is subtracted\n K.box('wood', 0, 0.44, 0, 0.42, 0.04, 0.42, c); for (const sx of [-1, 1]) for (const sz of [-1, 1]) K.box('wood', sx * 0.18, 0, sz * 0.18, 0.035, 0.44, 0.035, shade(c, 0.85));\n K.box('wood', 0, 0.48, -0.19, 0.42, 0.45, 0.03, c); };\n B.blood_stains = (M, a) => { const K = kit(M, a), { w, d } = K.dim('blood_stains');\n for (let k = 0; k < 7; k++) K.cyl('plain', (K.rnd() - 0.5) * w * 0.3, 0.012 + k * 0.0004, (K.rnd() - 0.5) * d * 0.3, 0.12 + K.rnd() * 0.18, 0.003, k < 2 ? 0x3a0806 : 0x5a100c);\n for (let k = 0; k < 8; k++) { const an = K.rnd() * 6.28, r = 0.3 + K.rnd() * Math.min(w, d) * 0.4; K.cyl('plain', Math.cos(an) * r, 0.013 + k * 0.0002, Math.sin(an) * r, 0.02 + K.rnd() * 0.05, 0.003, 0x4a0a08); }\n if (M.drag !== false) for (let k = 1; k < 6; k++) K.box('plain', 0.3 + k * 0.1, 0.012, 0.15 + k * 0.17, 0.16 - k * 0.015, 0.003, 0.2, 0x4a0c0a, 0.5); };\n B.open_door = (M, a) => { const K = kit(M, a), { w, d, h } = K.dim('open_door'), wood = K.wood(0x6a4a2c), angle = M.angle != null ? M.angle : 1.2, L = w - 0.06, hx = -w / 2 + 0.03;\n K.box('wood', -w / 2 - 0.03, 0, 0, 0.06, h + 0.06, d, shade(wood, 0.8)); K.box('wood', w / 2 + 0.03, 0, 0, 0.06, h + 0.06, d, shade(wood, 0.8)); K.box('wood', 0, h, 0, w + 0.12, 0.06, d, shade(wood, 0.8));\n K.box('wood', hx + Math.cos(angle) * L / 2, 0.0, Math.sin(angle) * L / 2, L, h - 0.02, 0.04, wood, -angle); K.cyl('metal', hx + Math.cos(angle) * (L - 0.08), 1.0, Math.sin(angle) * (L - 0.08) + 0.04, 0.025, 0.06, 0xc8b070, Math.PI / 2, 0);\n for (const y of [0.2, 1.0, 1.8]) K.box('metal', hx, y, 0.0, 0.02, 0.1, 0.02, 0x8a8a8a); };\n return B\n}\n","import type { BuilderContext, BuilderMap } from '../types'\nimport { shade } from '../colors'\nimport type { Kit } from '../types'\nimport { green } from './shared'\n\nexport function plantBuilders({ kit }: BuilderContext): Partial<BuilderMap> {\n const B: Partial<BuilderMap> = {}\n const foliage = (K: Kit, x: number, y: number, z: number, n: number, w: number, h0: number, h1: number, tilt0: number, tilt1: number, twist = 2.4) => { for (let i = 0; i < n; i++) { const an = i * twist + K.rnd() * 0.5, t = tilt0 + (tilt1 - tilt0) * K.rnd(); K.leaf(x + Math.cos(an) * 0.03, y, z + Math.sin(an) * 0.03, w * (0.8 + K.rnd() * 0.4), h0 + (h1 - h0) * K.rnd(), green(K, i), an, t); } };\n B.floor_plant = (M, a) => { const K = kit(M, a), { w } = K.dim('floor_plant'), r = Math.min(0.24, w * 0.4), pot = K.main(0xb5603c);\n K.cyl('plain', 0, 0, 0, r, 0.38, pot); K.cyl('plain', 0, 0.34, 0, r * 1.07, 0.05, shade(pot, 0.92)); K.cyl('plain', 0, 0.385, 0, r * 0.92, 0.012, 0x3a2a1e);\n foliage(K, 0, 0.39, 0, 11, 0.2, 0.5, 0.85, 0.2, 0.85); };\n B.table_plant = (M, a) => { const K = kit(M, a), y = M.y || 0, pot = K.main(0xf0eee8);\n K.cyl('plain', 0, y, 0, 0.05, 0.08, pot); K.cyl('plain', 0, y + 0.08, 0, 0.045, 0.008, 0x3a2a1e); foliage(K, 0, y + 0.085, 0, 7, 0.07, 0.1, 0.2, 0.2, 0.9); };\n B.hanging_plant = (M, a) => { const K = kit(M, a), t = K.ceiling(), h = K.dim('hanging_plant').h, yp = t - h, pot = K.main(0xf0eee8), rope = K.accent(0xd8ccb0);\n K.box('metal', 0, t - 0.025, 0, 0.03, 0.025, 0.03, 0x8a8a8a); for (let i = 0; i < 3; i++) { const an = i * 2.09; K.box('fabric', Math.cos(an) * 0.08, yp + 0.12, Math.sin(an) * 0.08, 0.01, t - yp - 0.15, 0.01, rope); }\n K.cyl('plain', 0, yp, 0, 0.1, 0.14, pot); K.cyl('plain', 0, yp + 0.135, 0, 0.09, 0.01, 0x3a2a1e);\n for (let i = 0; i < 11; i++) { const an = i * 2.4, up = i < 3; K.leaf(Math.cos(an) * 0.06, yp + 0.14, Math.sin(an) * 0.06, 0.09, up ? 0.22 : 0.4 + K.rnd() * 0.3, green(K, i), an, up ? 0.5 : 2.3 + K.rnd() * 0.3); } };\n return B\n}\n","import type { BuilderContext, BuilderMap } from '../types'\nimport type { PropType } from '../catalog'\nimport { hex, shade } from '../colors'\nimport type { Kit } from '../types'\nimport { drawTv, pick } from './shared'\n\nexport function wallBuilders({ kit, atlas }: BuilderContext): Partial<BuilderMap> {\n const B: Partial<BuilderMap> = {}\n B.wall_ac = (M, a) => { const K = kit(M, a), { w, d, h } = K.dim('wall_ac'), y = M.y || 2.15;\n K.box('plain', 0, y, 0, w, h, d, 0xf0f0ee); K.box('plain', 0, y + h - 0.02, d * 0.1, w - 0.04, 0.02, d * 0.8, 0xe6e6e2); // curved cover\n K.box('plain', 0, y + 0.02, d / 2 - 0.02, w - 0.1, 0.035, 0.04, 0x30343a); K.box('plain', 0, y - 0.005, d / 2 - 0.03, w - 0.12, 0.012, 0.07, 0xe6e6e2, 0, 0.45); // slot and flap\n K.box('plain', w / 2 - 0.1, y + h - 0.1, d / 2 + 0.001, 0.05, 0.02, 0.004, 0x2a3a30); K.box('plain', -w / 2 + 0.01, y + 0.02, 0.0, 0.01, h - 0.04, d - 0.02, 0xdcdcd8); };\n const frame = (K: Kit, id: PropType, def: number, large: boolean) => { const { w, d, h } = K.dim(id), y = K.M.y || def, fr = Math.min(w, h) * 0.07 + 0.008, margin = large ? 0.05 : 0.035, A = atlas('picture'), idx = K.M.art != null ? K.M.art % 7 : Math.floor(K.rnd() * 7);\n const cm = K.color('picture_accent', pick(K, [0x4a3324, 0x1c1c1c, 0xe8e2d4, 0x8a6a44])); K.box('wood', 0, y, 0, w, h, d, K.M.color != null ? hex(K.M.color) : cm);\n K.box('plain', 0, y + fr, d / 2 + 0.0005, w - 2 * fr, h - 2 * fr, 0.004, 0xf4f1e8); K.plane('atlas_picture', 0, y + h / 2, d / 2 + 0.005, w - 2 * fr - 2 * margin, h - 2 * fr - 2 * margin, 0xffffff, 0, 0, 0, A.uv(idx)); };\n B.picture_small = (M, a) => frame(kit(M, a), 'picture_small', 1.3, false);\n B.picture_large = (M, a) => frame(kit(M, a), 'picture_large', 1.2, true);\n B.mirror = (M, a) => { const K = kit(M, a), { w, d, h } = K.dim('mirror'), y = M.y || 0.9, A = atlas('picture'), cm = K.color('metal', 0x3a3a3c);\n K.box('metal', 0, y, 0, w, h, d, M.color != null ? hex(M.color) : cm); K.plane('atlas_picture', 0, y + h / 2, d / 2 + 0.003, w - 0.05, h - 0.05, 0xffffff, 0, 0, 0, A.mirror()); };\n B.curtain = (M, a) => { const K = kit(M, a), { w, h } = K.dim('curtain'), y = M.y != null && M.y > 0 ? M.y : 0.1, color = K.fabric(pick(K, [0xd8cdb4, 0x9fb0be, 0xc5a88c, 0xe0dcd0])), isOpen = M.open !== false, top = y + h;\n K.box('metal', 0, top, 0, w + 0.1, 0.025, 0.025, K.metal(0x5a5048)); for (const s of [-1, 1]) K.cyl('metal', s * (w / 2 + 0.07), top - 0.02, 0, 0.03, 0.07, 0x5a5048, 0, 0, 'sphere');\n const pw = isOpen ? Math.min(0.5, w * 0.3) : w / 2 + 0.03, n = Math.max(4, Math.round(pw / 0.08));\n for (const s of [-1, 1]) for (let i = 0; i < n; i++) { const ux = isOpen ? s * (w / 2 + 0.03 - pw + (i + 0.5) * pw / n) : s * ((i + 0.5) * pw / n); K.box('fabric', ux, y, (i % 2 ? 0.025 : -0.01), pw / n * 1.15, h - 0.02, 0.02, i % 2 ? shade(color, 0.9) : color); }\n if (isOpen) for (const s of [-1, 1]) K.box('fabric', s * (w / 2 - pw * 0.25), y + h * 0.4, 0.04, pw * 0.5, 0.05, 0.02, shade(color, 0.7)); }; // tie-back\n B.blinds = (M, a) => { const K = kit(M, a), { w, h } = K.dim('blinds'), y = M.y || 1.0, retract = M.retracted != null ? M.retracted : 0.15, isWood = M.variant === 'wood', color = isWood ? K.wood(0x9a7a52) : K.color('wall_living', 0xe8e2d4), mat = isWood ? 'wood' : 'plain', yb = y + h * retract, n = Math.floor((y + h - yb) / 0.055);\n K.box('plain', 0, y + h - 0.05, 0, w, 0.05, 0.06, 0xdcd8d0); for (let i = 0; i < n; i++) K.box(mat, 0, y + h - 0.06 - (i + 1) * 0.055, 0.0, w - 0.05, 0.01, 0.035, color, 0, 0.3);\n for (const s of [-1, 1]) K.box('plain', s * (w / 2 - 0.05), yb, 0.02, 0.008, y + h - 0.05 - yb, 0.008, 0xb8b4ac); K.box('plain', 0, yb - 0.02, 0, w - 0.03, 0.025, 0.04, 0xdcd8d0); };\n B.flat_tv = (M, a) => { const K = kit(M, a), { w } = K.dim('flat_tv'); drawTv(K, 0, M.y != null && M.y > 0 ? M.y : 1.0, 0, w, false); };\n return B\n}\n","import type { BuilderContext, BuilderMap } from '../types'\nimport { hex, shade } from '../colors'\nimport { green, pick } from './shared'\n\nexport function yardBuilders({ kit, cover }: BuilderContext): Partial<BuilderMap> {\n const B: Partial<BuilderMap> = {}\n B.ac_condenser = (M, a) => { const K = kit(M, a), { w, d, h } = K.dim('ac_condenser'), y = M.y != null && M.y > 0 ? M.y : 0.5;\n for (const s of [-1, 1]) { K.box('metal', s * w * 0.32, y - 0.06, -d / 2 - 0.2, 0.04, 0.04, 0.3, 0x6a6a6a); K.box('metal', s * w * 0.32, y - 0.5 + 0.06, -d / 2 - 0.2 - 0.0, 0.035, 0.46, 0.035, 0x6a6a6a, 0, 0.0); }\n K.box('plain', 0, y, 0, w, h, d, 0xe8e8e4); K.box('plain', 0, y + h - 0.02, 0, w + 0.02, 0.025, d + 0.02, 0xd8d8d4);\n K.cyl('metal', -w * 0.12, y + h / 2, d / 2 + 0.004, 0.2, 0.012, 0x2a2a2e, Math.PI / 2, 0); K.cyl('metal', -w * 0.12, y + h / 2, d / 2 + 0.012, 0.085, 0.02, 0x4a4a4e, Math.PI / 2, 0);\n for (let i = 0; i < 4; i++) K.box('metal', -w * 0.12, y + h / 2 - 0.2, d / 2 + 0.018, 0.4, 0.008, 0.008, 0x8a8a8a, 0, 0, i * 0.785);\n for (let i = 0; i < 3; i++) K.cyl('metal', -w * 0.12, y + h / 2, d / 2 + 0.02, 0.07 + i * 0.043, 0.006, 0x7a7a7e, Math.PI / 2, 0);\n for (let i = 0; i < 6; i++) K.box('plain', w * 0.33, y + 0.06 + i * 0.075, d / 2 + 0.003, 0.14, 0.03, 0.006, 0xc4c4c0);\n K.cyl('metal', w / 2 - 0.05, y + 0.1, -d / 2 - 0.08, 0.012, 0.18, 0xb8733c, Math.PI / 2, 0); K.box('plain', w / 2 - 0.05, y + 0.06, -d / 2 - 0.02, 0.05, 0.12, 0.05, 0xe0e0dc); };\n B.pool = (M, a) => { const K = kit(M, a), { w, d } = K.dim('pool'), t = 0.32, bo = K.color('exterior_floor', 0xe8e4da);\n K.box('plain', 0, 0, -d / 2 - t / 2, w + 2 * t, 0.1, t, bo); K.box('plain', 0, 0, d / 2 + t / 2, w + 2 * t, 0.1, t, bo); for (const s of [-1, 1]) K.box('plain', s * (w / 2 + t / 2), 0, 0, t, 0.1, d, bo);\n cover(a, M, 'pool_tile', { w, d, y: 0.035, size: 1.0 });\n K.plane('water', 0, 0.075, 0, w, d, 0x8fd4ee, 0, -Math.PI / 2, 0);\n for (let i = 0; i < 3; i++) K.box('plain', w / 2 - 0.35 + i * 0.0, 0.035 + i * 0.01, d / 2 - 0.2 - i * 0.28, 0.7 - i * 0.05, 0.03, 0.26, 0xdfeef2);\n for (const s of [-1, 1]) { K.box('metal', -w / 4 + s * 0.2, 0.0, d / 2 + t * 0.6, 0.03, 0.85, 0.03, 0xc8ccd0); K.box('metal', -w / 4 + s * 0.2, 0.85, d / 2 + t * 0.6 - 0.1, 0.03, 0.03, 0.22, 0xc8ccd0); K.box('metal', -w / 4 + s * 0.2, 0.0, d / 2 - 0.25, 0.03, 0.88, 0.03, 0xc8ccd0, 0, 0.0); }\n for (let i = 0; i < 3; i++) K.box('metal', -w / 4, 0.02, d / 2 - 0.08 - i * 0.07, 0.4, 0.015, 0.05, 0xc8ccd0); };\n B.deck = (M, a) => { const K = kit(M, a), { w, d } = K.dim('deck'), h = 0.2, wood = K.wood(0x6a4a30);\n K.box('wood', 0, 0, 0, w - 0.05, h, d - 0.05, shade(wood, 0.7)); cover(a, M, 'deck_floor', { w, d, y: h + 0.012, size: 1.2, color: M.color });\n for (const [x, z, ww, dd] of [[0, -d / 2, w, 0.05], [0, d / 2, w, 0.05], [-w / 2, 0, 0.05, d], [w / 2, 0, 0.05, d]]) K.box('wood', x, 0, z, ww, h + 0.02, dd, wood);\n K.box('wood', 0, 0, d / 2 + 0.17, 1.4, 0.1, 0.34, wood); K.box('wood', 0, 0.1, d / 2 + 0.17, 1.4, 0.012, 0.34, shade(wood, 1.2)); };\n B.sun_lounger = (M, a) => { const K = kit(M, a), { w } = K.dim('sun_lounger'), fr = K.metal(0xe8e8e4), al = 0.95, cu = K.fabric(0x3a6a9a), stripe = K.accent(0xf0ece0);\n for (const s of [-1, 1]) { K.box('metal', s * (w / 2 - 0.03), 0.17, -0.3, 0.035, 0.035, 1.35, fr); K.box('metal', s * (w / 2 - 0.03), 0.0, 0.95, 0.035, 0.2, 0.035, fr); K.box('metal', s * (w / 2 - 0.03), 0.0, -0.3, 0.035, 0.2, 0.035, fr); K.cyl('plain', s * (w / 2 - 0.03), 0.07, -0.3, 0.07, 0.03, 0x2a2a2a, 0, Math.PI / 2); }\n K.box('fabric', 0, 0.21, 0.35, w - 0.1, 0.06, 1.3, cu); K.ramp('fabric', 0, 0.2, -0.3, w - 0.1, 0.06, 0.8, cu, al);\n for (let i = 0; i < 4; i++) { K.box('fabric', 0, 0.265, 0.0 + i * 0.3, w - 0.098, 0.004, 0.08, stripe); } K.ramp('metal', w / 2 - 0.03, 0.18, -0.3, 0.03, 0.03, 0.5, fr, al - 0.3); K.ramp('metal', -w / 2 + 0.03, 0.18, -0.3, 0.03, 0.03, 0.5, fr, al - 0.3); };\n B.garden_bed = (M, a) => { const K = kit(M, a), { w, d } = K.dim('garden_bed'), wood = K.wood(0x9a7048), hb = 0.3;\n for (const s of [-1, 1]) { K.box('wood', 0, 0, s * (d / 2 - 0.015), w, hb, 0.03, wood); K.box('wood', s * (w / 2 - 0.015), 0, 0, 0.03, hb, d - 0.06, shade(wood, 0.9)); }\n K.box('plain', 0, 0.02, 0, w - 0.06, hb - 0.05, d - 0.06, 0x3a2a1e);\n const lin = d > 0.8 ? 3 : 2, n = Math.max(3, Math.round(w / 0.4));\n for (let r = 0; r < lin; r++) for (let i = 0; i < n; i++) { const x = -w / 2 + (i + 0.5) * w / n, z = (r - (lin - 1) / 2) * 0.3;\n if (r === 0) { for (let j = 0; j < 6; j++) K.leaf(x, hb - 0.02, z, 0.11, 0.14, green(K, j) , j * 1.05, 0.9); }\n else if (r === 1) { K.box('wood', x, hb - 0.03, z, 0.015, 0.75, 0.015, 0x8a7048); K.cyl('plant', x, hb + 0.05, z, 0.1, 0.28, green(K, i), 0, 0, 'sphere'); K.cyl('plain', x + 0.05, hb + 0.18, z + 0.07, 0.03, 0.06, 0xc83030, 0, 0, 'sphere'); K.cyl('plain', x - 0.06, hb + 0.3, z + 0.05, 0.03, 0.06, 0xd04030, 0, 0, 'sphere'); }\n else { K.cyl('plant', x, hb - 0.02, z, 0.1, 0.17, 0x8ab05a, 0, 0, 'sphere'); for (let j = 0; j < 5; j++) K.leaf(x, hb, z, 0.14, 0.2, green(K, j + 1), j * 1.3, 1.0); } } };\n B.fruit_tree = (M, a) => { const K = kit(M, a), { w, h } = K.dim('fruit_tree'), fruit = M.fruit || pick(K, [0xe8801c, 0xe8a030, 0xb8c050, 0xc83030, 0xd8d040]), tr = K.wood(0x6a4a30);\n K.cyl('wood', 0, 0, 0, 0.13, h * 0.5, tr); K.box('wood', 0.2, h * 0.35, 0, 0.08, 0.6, 0.08, tr, 0, 0, -0.7); K.box('wood', -0.2, h * 0.35, 0.05, 0.08, 0.55, 0.08, tr, 0, 0, 0.7);\n const bl = [[0, h * 0.72, 0, 1.0], [0.55, h * 0.62, 0.2, 0.7], [-0.55, h * 0.64, -0.1, 0.72], [0.1, h * 0.6, -0.55, 0.65], [0.1, h * 0.9, 0.1, 0.6]];\n bl.forEach(([x, y, z, r], i) => K.cyl('plant', x * w / 2.4, y - r * 0.4, z * w / 2.4, r * w / 2.4, r * 1.5 * w / 2.4, green(K, i), 0, 0, 'sphere'));\n for (let i = 0; i < 16; i++) { const b = bl[i % 4], an = K.rnd() * 6.28, el = K.rnd() * 0.9, r = b[3] * w / 2.4 * 0.95; K.cyl('plain', b[0] * w / 2.4 + Math.cos(an) * r * Math.cos(el), b[1] * 1.0 - b[3] * 0.4 + Math.sin(el) * r * 0.5, b[2] * w / 2.4 + Math.sin(an) * r * Math.cos(el), 0.055, 0.11, fruit, 0, 0, 'sphere'); } };\n B.palm_tree = (M, a) => { const K = kit(M, a), { h } = K.dim('palm_tree'), tr = K.wood(0x8a7a5a), lean = 0.35, n = 9, sh = h * 0.78 / n; let x = 0, y = 0;\n for (let i = 0; i < n; i++) { const r = 0.14 - i * 0.006; K.cyl('wood', x, y, 0, r, sh, i % 2 ? tr : shade(tr, 0.82)); y += sh; x += lean * (2 * i + 1) / (n * n) * 1.2; }\n const top = y;\n for (let i = 0; i < 10; i++) { const an = i * 0.628 + K.rnd() * 0.2, t1 = 0.7 + K.rnd() * 0.4, L = 1.5, w = 0.5; K.leaf(x, top, 0, w, L, green(K, i), an, t1);\n const ex = x + Math.sin(t1) * Math.sin(an) * L, ey = top + Math.cos(t1) * L, ez = Math.sin(t1) * Math.cos(an) * L; K.leaf(ex, ey, ez, w * 0.7, 0.9, green(K, i + 1), an, t1 + 1.0); }\n for (let i = 0; i < 3; i++) K.cyl('wood', x + Math.cos(i * 2.1) * 0.14, top - 0.2, Math.sin(i * 2.1) * 0.14, 0.09, 0.2, 0x6a5a3a, 0, 0, 'sphere'); };\n B.pergola = (M, a) => { const K = kit(M, a), { w, d, h } = K.dim('pergola'), wood = M.variant === 'dark' ? shade(K.wood(0x6a4a30), 0.8) : K.color('exterior_floor', 0xe8e2d4), pc = M.color != null ? hex(M.color) : wood;\n for (const sx of [-1, 1]) for (const sz of [-1, 1]) K.box('wood', sx * (w / 2 - 0.1), 0, sz * (d / 2 - 0.1), 0.14, h, 0.14, pc);\n for (const sz of [-1, 1]) K.box('wood', 0, h - 0.04, sz * (d / 2 - 0.1), w + 0.2, 0.16, 0.08, pc); const n = Math.round(w / 0.4);\n for (let i = 0; i <= n; i++) K.box('wood', -w / 2 - 0.1 + i * (w + 0.2) / n, h + 0.12, 0, 0.06, 0.08, d + 0.3, shade(pc, 0.92));\n for (let i = 0; i < 9; i++) K.leaf(-w / 2 + 0.1 + (i % 2) * 0.06, 0.2 + i * 0.25, -d / 2 + 0.1, 0.2, 0.28, green(K, i), i * 1.7, 0.7); for (let i = 0; i < 7; i++) K.leaf(-w / 2 + 0.2 + i * 0.35, h + 0.2, -d / 4 + K.rnd() * 0.3, 0.22, 0.3, green(K, i), i * 2.0, 1.3); };\n B.slide = (M, a) => { const K = kit(M, a), { d, h } = K.dim('slide'), py = h * 0.8, pz = -d / 2 + 0.55, az = K.accent(0xe8c030), vm = K.main(0xc8403a), az2 = 0x3a6ab0;\n for (const sx of [-1, 1]) for (const sz of [-1, 1]) K.box('metal', sx * 0.3, 0, pz + sz * 0.3, 0.05, py, 0.05, az2);\n K.box('plain', 0, py, pz, 0.74, 0.05, 0.74, vm); for (const s of [-1, 1]) { K.box('plain', s * 0.35, py + 0.05, pz, 0.04, 0.42, 0.74, az); }\n K.box('plain', 0, py + 0.05, pz - 0.35, 0.74, 0.42, 0.04, az); const run = d / 2 - pz - 0.4, al = Math.atan2(py - 0.1, run);\n K.ramp('plain', 0, py, pz + 0.37, 0.5, 0.04, Math.hypot(run, py - 0.1) + 0.1, 0xd8d0c0, -al + 0.0 + 0); for (const s of [-1, 1]) K.ramp('plain', s * 0.27, py + 0.03, pz + 0.37, 0.04, 0.12, Math.hypot(run, py - 0.1) + 0.1, az, -al);\n for (const s of [-1, 1]) K.box('metal', s * 0.28, 0, pz - 0.45, 0.04, py + 0.1, 0.04, az2, 0, -0.1 + 0); for (let i = 0; i < 5; i++) K.box('metal', 0, 0.2 + i * (py - 0.2) / 5, pz - 0.46, 0.56, 0.03, 0.03, 0xc8ccd0); K.box('plain', 0, py + 0.4, pz, 0.8, 0.04, 0.8, vm); };\n B.swing = (M, a) => { const K = kit(M, a), { w, d, h } = K.dim('swing'), mt = K.color('metal', 0x3a6ab0), L = Math.hypot(h, d / 2), an = Math.atan2(d / 2, h);\n for (const sx of [-1, 1]) for (const sz of [-1, 1]) K.box('metal', sx * w / 2, h / 2 - L / 2 + 0.0, sz * d / 4, 0.07, L, 0.07, mt, 0, -sz * an, 0);\n K.box('metal', 0, h - 0.04, 0, w + 0.1, 0.1, 0.1, shade(mt, 0.85)); const cs = [K.accent(0xc8403a), 0x2a2a2a];\n for (let i = 0; i < 2; i++) { const x = (i - 0.5) * w * 0.45, sy = 0.5, sw = 0.46; for (const s of [-1, 1]) K.box('metal', x + s * (sw / 2 - 0.02), sy, 0, 0.012, h - 0.05 - sy, 0.012, 0xa0a4a8); K.box('plain', x, sy - 0.04, 0, sw, 0.045, 0.17, cs[i]); } };\n B.bbq_simple = (M, a) => { const K = kit(M, a), { w, d } = K.dim('bbq_simple'), br = K.color('exterior_floor', 0xb25a40), hb = 0.78;\n K.box('plain', 0, 0, 0, w, 0.45, d, br); for (const [x, z, ww, dd] of [[0, -d / 2 + 0.06, w, 0.12], [0, d / 2 - 0.06, w, 0.12], [-w / 2 + 0.06, 0, 0.12, d - 0.2], [w / 2 - 0.06, 0, 0.12, d - 0.2]]) K.box('plain', x, 0.45, z, ww, hb - 0.45, dd, br);\n K.box('plain', 0, 0.45, 0, w - 0.22, 0.04, d - 0.22, 0x2a2624); for (let i = 0; i < 10; i++) K.box('plain', (K.rnd() - 0.5) * (w - 0.3), 0.49, (K.rnd() - 0.5) * (d - 0.3), 0.08, 0.02, 0.07, i % 3 ? 0xc84a1a : 0x3a2a24, K.rnd() * 3);\n for (let i = 0; i < 7; i++) K.box('metal', -w / 2 + 0.16 + i * (w - 0.32) / 6, hb + 0.01, 0, 0.012, 0.012, d - 0.1, 0x5a5a5e); K.box('metal', 0, hb + 0.01, -d / 2 + 0.2, w - 0.1, 0.012, 0.02, 0x5a5a5e);\n for (let i = 0; i < 3; i++) { const z = (i - 1) * 0.15; K.cyl('metal', 0, hb + 0.05, z, 0.006, w + 0.12, 0xb0b4b8, 0, Math.PI / 2); for (let j = 0; j < 3; j++) K.cyl('plain', -w * 0.25 + j * w * 0.22, hb + 0.05, z, 0.035, 0.07, [0x8a3a2a, 0xa8483a, 0xd8b090][j], 0, Math.PI / 2); }\n for (let i = 0; i < 4; i++) K.cyl('wood', w / 2 + 0.2, 0.05 + (i > 2 ? 0.1 : 0), -0.1 + (i % 3) * 0.1, 0.04, 0.3, 0x8a6a44, 0, Math.PI / 2); };\n B.garage_car = (M, a) => { const K = kit(M, a), { w, d } = K.dim('garage_car'), c = K.main(pick(K, [0xa8aaac, 0xe8e8e6, 0x2a3a5a, 0xa83a34, 0x222426])), zf = d / 2;\n K.box('plain', 0, 0.26, 0, w, 0.5, d, c); K.box('plain', 0, 0.5, zf - 0.7, w - 0.04, 0.1, 1.4, shade(c, 1.04), 0, 0.04); K.box('plain', 0, 0.76, -zf + 0.3, w - 0.04, 0.1, 0.6, c);\n const cz = -0.35; for (const sx of [-1, 1]) for (const sz of [-1, 1]) K.box('plain', sx * (w / 2 - 0.1), 0.76, cz + sz * 0.8, 0.07, 0.5, 0.07, shade(c, 0.9)); K.box('plain', 0, 1.26, cz, w - 0.1, 0.05, 1.75, shade(c, 1.04));\n K.box('glass', 0, 0.76, cz, w - 0.06, 0.5, 1.6, 0xffffff); K.box('glass', 0, 0.76, cz + 0.8, w - 0.12, 0.5, 0.04, 0xffffff, 0, 0.5); for (const s of [-1, 1]) { K.box('plain', s * 0.4, 0.55, cz + 0.4, 0.4, 0.3, 0.4, 0x2a2a2e); K.box('plain', s * 0.4, 0.55, cz - 0.45, 0.4, 0.3, 0.4, 0x2a2a2e); }\n for (const sx of [-1, 1]) for (const sz of [-1, 1]) { K.cyl('plain', sx * (w / 2 - 0.1), 0.3, sz * 1.3, 0.3, 0.2, 0x1a1a1a, 0, Math.PI / 2); K.cyl('metal', sx * (w / 2 - 0.0), 0.3, sz * 1.3, 0.17, 0.02, 0xb8bcc0, 0, Math.PI / 2); }\n for (const s of [-1, 1]) { K.box('plain', s * 0.62, 0.5, zf - 0.02, 0.32, 0.12, 0.05, 0xf4f0d8); K.box('plain', s * 0.62, 0.52, -zf + 0.02, 0.3, 0.1, 0.05, 0xb02a24); K.box('plain', s * (w / 2 + 0.03), 0.9, cz + 0.55, 0.1, 0.1, 0.14, shade(c, 0.9)); }\n K.box('plain', 0, 0.2, zf - 0.01, w, 0.16, 0.06, 0x2a2a2a); K.box('plain', 0, 0.2, -zf + 0.01, w, 0.16, 0.06, 0x2a2a2a); K.box('plain', 0, 0.32, zf + 0.02, 0.5, 0.16, 0.02, 0x1c1c1c); K.box('plain', 0, 0.28, -zf - 0.015, 0.4, 0.12, 0.01, 0xf0e8c0); };\n B.gourmet_counter = (M, a) => { const K = kit(M, a), { w, d } = K.dim('gourmet_counter'), br = K.color('exterior_floor', 0xa8553c), zc = -d / 2 + 0.35, xa = -w / 2 + 0.8, xb = xa + 0.8 + 0.72, xc = xb + 0.72 + 0.65, wood = K.wood(0x8a5a38), gr = 0x4a4844, cab = K.color('wall_kitchen', 0xe8e4dc);\n K.box('plain', xa, 0, zc, 1.6, 0.85, 0.7, br); for (let i = 1; i < 4; i++) K.box('plain', xa, i * 0.2, zc + 0.351, 1.6, 0.012, 0.004, shade(br, 0.7)); K.box('plain', xa, 0.12, zc + 0.352, 0.7, 0.4, 0.004, 0x1a1612);\n for (const [x, z, ww, dd] of [[xa, zc - 0.25, 1.6, 0.2], [xa, zc + 0.3, 1.6, 0.1], [xa - 0.75, zc, 0.1, 0.7], [xa + 0.75, zc, 0.1, 0.7]]) K.box('plain', x, 0.85, z, ww, 0.12, dd, br);\n K.box('plain', xa, 0.85, zc + 0.02, 1.4, 0.025, 0.4, 0x2a2624); for (let i = 0; i < 8; i++) K.box('metal', xa - 0.62 + i * 0.18, 0.97, zc + 0.02, 0.012, 0.012, 0.4, 0x5a5a5e); for (let i = 0; i < 6; i++) K.box('plain', xa + (K.rnd() - 0.5) * 1.2, 0.88, zc + (K.rnd() - 0.5) * 0.3, 0.1, 0.02, 0.08, i % 2 ? 0xc84a1a : 0x3a2a24, K.rnd() * 3);\n K.box('plain', xa, 1.5, zc - 0.07, 1.5, 0.4, 0.56, br); K.box('plain', xa, 1.5, zc + 0.215, 1.5, 0.4, 0.01, 0x1a1612); K.box('plain', xa, 1.9, zc - 0.12, 0.62, 1.0, 0.46, br); K.box('plain', xa, 2.9, zc - 0.12, 0.76, 0.08, 0.6, shade(br, 0.8)); K.box('plain', xa, 2.98, zc - 0.12, 0.5, 0.06, 0.4, shade(br, 0.75));\n K.box('plain', xb, 0, zc, 1.44, 0.85, 0.7, cab); for (let i = 0; i < 3; i++) K.box('wood', xb - 0.48 + i * 0.48, 0.08, zc + 0.355, 0.44, 0.7, 0.012, wood); K.box('plain', (xb + xc) / 2, 0.85, zc, 0.72 * 2 + 0.65 + 0.04, 0.04, 0.74, gr);\n K.box('metal', xb, 0.89, zc, 0.5, 0.01, 0.38, 0xb0b4b8); K.box('metal', xb, 0.895, zc, 0.44, 0.006, 0.32, 0x70767a); K.cyl('metal', xb, 0.89, zc - 0.28, 0.014, 0.3, 0xb8bcc0); K.box('metal', xb, 1.17, zc - 0.2, 0.02, 0.02, 0.11, 0xb8bcc0);\n K.box('plain', xc, 0, zc, 1.3, 0.85, 0.7, cab); for (let i = 0; i < 2; i++) K.box('wood', xc - 0.32 + i * 0.64, 0.08, zc + 0.355, 0.6, 0.7, 0.012, wood);\n K.cyl('plain', xc, 0.89, zc, 0.1, 0.05, 0xf0f0ea); K.cyl('plain', xc + 0.4, 0.89, zc, 0.04, 0.26, 0x2a6a3a);\n const tz = 0.55, tl = Math.min(2.6, w * 0.65); K.box('wood', 0, 0.74, tz, tl, 0.05, 0.85, wood); for (const s of [-1, 1]) { K.box('wood', s * (tl / 2 - 0.2), 0, tz, 0.08, 0.74, 0.7, shade(wood, 0.8)); K.box('wood', s * (tl / 2 - 0.2), 0.3, tz - 0.001, 0.0 + 0.6, 0.04, 0.05, shade(wood, 0.75)); }\n for (const s of [-1, 1]) { K.box('wood', 0, 0.42, tz + s * 0.72, tl - 0.2, 0.04, 0.3, shade(wood, 1.1)); for (const sx of [-1, 1]) K.box('wood', sx * (tl / 2 - 0.4), 0, tz + s * 0.72, 0.06, 0.42, 0.26, shade(wood, 0.8)); }\n K.cyl('plain', 0.3, 0.79, tz, 0.15, 0.08, 0xd8d0c0); K.cyl('plain', -0.5, 0.79, tz + 0.1, 0.035, 0.26, 0x2a6a3a); for (let i = 0; i < 3; i++) K.cyl('plain', -0.9 + i * 0.2, 0.79, tz - 0.2, 0.035, 0.1, 0xe8e4d8); };\n B.sauna = (M, a) => { const K = kit(M, a), { w, d, h } = K.dim('sauna'), t = 0.08, wood = K.wood(0xc08a54), shade1 = shade(wood, 0.88), dw = 0.7, dx = w / 2 - 0.55;\n K.box('wood', 0, 0, 0, w, 0.05, d, shade(wood, 0.75)); K.box('wood', 0, 0.05, -d / 2 + t / 2, w, h - 0.11, t, wood); for (const s of [-1, 1]) K.box('wood', s * (w / 2 - t / 2), 0.05, 0, t, h - 0.11, d - 2 * t * 0 , wood);\n K.box('wood', 0, h - 0.06, 0, w + 0.12, 0.06, d + 0.12, shade(wood, 0.75));\n const lx0 = -w / 2, lx1 = dx - dw / 2; K.box('wood', (lx0 + lx1) / 2, 0.05, d / 2 - t / 2, lx1 - lx0, h - 0.11, t, wood); K.box('wood', (dx + dw / 2 + w / 2) / 2, 0.05, d / 2 - t / 2, w / 2 - (dx + dw / 2), h - 0.11, t, wood); K.box('wood', dx, 1.85, d / 2 - t / 2, dw, h - 0.11 - 1.8, t, wood);\n for (let i = 0; i < 12; i++) { const x = lx0 + 0.1 + i * (lx1 - lx0) / 12; K.box('wood', x, 0.05, d / 2 + 0.003, 0.012, h - 0.11, 0.005, shade(wood, 0.6)); }\n K.box('wood', dx - dw / 2 - 0.025, 0.05, d / 2 - 0.02, 0.05, 1.82, 0.06, shade1); K.box('wood', dx + dw / 2 + 0.025, 0.05, d / 2 - 0.02, 0.05, 1.82, 0.06, shade1); K.box('glass', dx, 0.06, d / 2 - 0.02, dw, 1.75, 0.02, 0xffffff); K.box('metal', dx - dw / 2 + 0.08, 0.7, d / 2 + 0.03, 0.02, 0.5, 0.03, 0xc8ccd0);\n K.box('wood', 0, 0.85, -d / 2 + t + 0.25, w - 2 * t, 0.05, 0.5, shade(wood, 1.1)); K.box('wood', 0, 0.45, -d / 2 + t + 0.65, w - 2 * t, 0.05, 0.4, shade(wood, 1.1)); K.box('wood', 0, 0.05, -d / 2 + t + 0.1, w - 2 * t, 0.8, 0.04, shade(wood, 0.8));\n K.cyl('metal', -w / 2 + 0.3, 0.05, d / 2 - 0.4, 0.14, 0.55, 0x2a2a2e); for (let i = 0; i < 5; i++) K.cyl('plain', -w / 2 + 0.3 + Math.cos(i * 1.3) * 0.06, 0.6, d / 2 - 0.4 + Math.sin(i * 1.3) * 0.06, 0.04, 0.07, 0x6a6a6a, 0, 0, 'sphere');\n K.box('plain', 0, h - 0.2, -d / 2 + t + 0.02, 0.4, 0.06, 0.04, 0xfff0c8); K.box('emissive', 0, h - 0.19, -d / 2 + t + 0.045, 0.34, 0.01, 0.006, 0xffe0a0); };\n B.soaking_tub = (M, a) => { const K = kit(M, a), { w } = K.dim('soaking_tub'), r = Math.min(0.62, w * 0.48), wood = K.wood(0xa8784a);\n K.cyl('wood', 0, 0, 0, r, 0.78, wood, 0, 0, 'cyl'); K.cyl('wood', 0, 0.76, 0, r + 0.025, 0.04, shade(wood, 1.15)); for (const y of [0.15, 0.5]) K.cyl('metal', 0, y, 0, r + 0.012, 0.03, 0x2a2a2e);\n K.cyl('water', 0, 0.74, 0, r - 0.04, 0.01, 0x9ad4e8); for (let i = 0; i < 8; i++) K.box('wood', Math.cos(i * 0.785) * (r + 0.001), 0.02, Math.sin(i * 0.785) * (r + 0.001), 0.012, 0.74, 0.12, shade(wood, 0.7), -i * 0.785 + 0);\n K.box('wood', 0, 0, r + 0.4, 0.5, 0.3, 0.34, shade(wood, 0.9)); K.cyl('wood', 0.2, 0.3, r + 0.4, 0.1, 0.14, shade(wood, 1.1)); K.box('wood', -0.1, 0.34, r + 0.4, 0.3, 0.02, 0.025, shade(wood, 1.1), 0.5); K.box('fabric', -0.1, 0.3, r + 0.42, 0.26, 0.03, 0.2, K.accent(0xf0ece0)); };\n return B\n}\n","import { hex, mix, rng, seedOf } from './colors'\nimport type { AtlasMap, CoverOptions, FloorKind, PropItem, PropsApi, Uv } from './types'\n\n/** Default height of each floor kind above the ground. */\nconst HEIGHTS: Record<FloorKind, number> = { lawn: 0.02, dirt: 0.025, concrete: 0.03, tile_floor: 0.035, deck_floor: 0.04, pool_tile: 0.04, paving: 0.035 }\n\nconst inside = (poly: Array<[number, number]>, x: number, z: number): boolean => {\n let in_ = false\n for (let i = 0, j = poly.length - 1; i < poly.length; j = i++) {\n const [xi, zi] = poly[i]\n const [xj, zj] = poly[j]\n if (zi > z !== zj > z && x < ((xj - xi) * (z - zi)) / (zj - zi) + xi) in_ = !in_\n }\n return in_\n}\n\n/**\n * Covers a rectangle centered on M (w x d) or a polygon `[[x, z], ...]` in local coordinates with floor tiles:\n * one atlas square per cell (repeats and tiles); the polygon uses cells of a quarter of the size.\n */\nexport function createCover(atlas: <K extends 'floor'>(name: K) => AtlasMap[K]) {\n return function cover(api: PropsApi, M: PropItem, kind: FloorKind, o: CoverOptions = {}): void {\n const A = atlas('floor')\n const size = o.size || A.size(kind)\n const nv = A.variants(kind)\n const y = o.y != null ? o.y : HEIGHTS[kind] || 0.03\n const color = o.color != null ? hex(o.color) : 0xffffff\n const poly = o.poly || M.poly\n const sd = seedOf(M)\n const rnd = rng(sd)\n const tint = () => {\n const v = 0.93 + rnd() * 0.07\n return mix(0x000000, color, v)\n }\n const quad = (cx: number, cz: number, qw: number, qd: number, uv: Uv) => api.plane(M, 'atlas_floor', cx, y, cz, qw, qd, tint(), 0, -Math.PI / 2, 0, uv)\n if (!poly) {\n const w = o.w || M.w || 4\n const d = o.d || M.d || 4\n for (let j = 0, v = 0; v < d - 1e-6; j++, v += size)\n for (let i = 0, u = 0; u < w - 1e-6; i++, u += size) {\n const qw = Math.min(size, w - u)\n const qd = Math.min(size, d - v)\n const b = A.uv(kind, (i * 7 + j * 13 + sd) % nv)\n const uv = { u0: b.u0, u1: b.u0 + ((b.u1 - b.u0) * qw) / size, v1: b.v1, v0: b.v1 - ((b.v1 - b.v0) * qd) / size }\n quad(-w / 2 + u + qw / 2, -d / 2 + v + qd / 2, qw, qd, uv)\n }\n return\n }\n let x0 = 1e9\n let z0 = 1e9\n let x1 = -1e9\n let z1 = -1e9\n for (const [x, z] of poly) {\n x0 = Math.min(x0, x)\n x1 = Math.max(x1, x)\n z0 = Math.min(z0, z)\n z1 = Math.max(z1, z)\n }\n const st = size / 4\n for (let j = 0; z0 + j * st < z1; j++)\n for (let i = 0; x0 + i * st < x1; i++) {\n const cx = x0 + (i + 0.5) * st\n const cz = z0 + (j + 0.5) * st\n if (!inside(poly, cx, cz)) continue\n const b = A.uv(kind, (((i >> 2) * 7 + (j >> 2) * 13 + sd) % nv))\n const du = (b.u1 - b.u0) / 4\n const dv = (b.v1 - b.v0) / 4\n const ui = i & 3\n const vj = j & 3\n quad(cx, cz, st, st, { u0: b.u0 + ui * du, u1: b.u0 + (ui + 1) * du, v1: b.v1 - vj * dv, v0: b.v1 - (vj + 1) * dv })\n }\n }\n}\n","import { DEFS } from './catalog'\nimport { hex, rng, seedOf } from './colors'\nimport type { EmitFn, Kit, PropItem, PropsApi } from './types'\n\n/** Shortcuts that fix `M` and the api, so builders read as a list of primitives. */\nexport function makeKit(M: PropItem, api: PropsApi, emit: EmitFn): Kit {\n const role = (name: string): number | null => {\n const v = M.palette && M.palette[name]\n return v != null ? hex(v) : null\n }\n const K: Kit = {\n M,\n api,\n rnd: rng(seedOf(M)),\n box: (material, x, y, z, w, h, d, color, ry, rx, rz) => api.box(M, material, x, y, z, w, h, d, hex(color), ry || 0, rx || 0, rz || 0),\n cyl: (material, x, y, z, r, h, color, rx, rz, shape) => api.cylinder(M, material, x, y, z, r, h, hex(color), rx || 0, rz || 0, shape),\n leaf: (x, y, z, w, h, color, ry, rx) => api.leaf(M, x, y, z, w, h, hex(color), ry || 0, rx || 0),\n plane: (material, x, y, z, w, h, color, ry, rx, rz, uv) => api.plane(M, material, x, y, z, w, h, hex(color), ry || 0, rx || 0, rz || 0, uv),\n light: (x, y, z, color, range, kind) => {\n if (api.light) api.light(M, x, y, z, hex(color), range, kind)\n else emit('light', { M, x, y, z, color: hex(color), range, kind })\n },\n // colours: role in the house palette (M.palette) or the default; main = M.color\n color: (name, fallback) => role(name) ?? hex(fallback),\n main: (fallback) => (M.color != null ? hex(M.color) : hex(fallback)),\n fabric: (fallback) => (M.color != null ? hex(M.color) : (role('fabric_main') ?? hex(fallback))),\n accent: (fallback) => role('fabric_accent') ?? hex(fallback),\n wood: (fallback) => role('wood') ?? hex(fallback),\n metal: (fallback) => role('metal') ?? hex(fallback),\n dim: (id) => {\n const t = DEFS[id]\n return { w: M.w || t.w, d: M.d || t.d, h: M.h || t.h }\n },\n ceiling: () => M.ceiling || 2.9,\n // long tilted box: starts at (x, y, z) and follows u = (0, sin a, -cos a) for L meters\n ramp(material, x, y, z, w, t, L, color, alpha) {\n const cy = y + (Math.sin(alpha) * L) / 2\n const cz = z - (Math.cos(alpha) * L) / 2\n K.box(material, x, cy - t / 2, cz, w, t, L, color, 0, alpha + Math.PI, 0)\n },\n }\n return K\n}\n","import type { PropType } from './catalog'\n\n/** Locales shipped with the package. Unknown locales fall back to `en`. */\nexport type PropsLocale = 'en' | 'pt-BR'\n\nexport interface PropsStrings {\n /** Display name of each prop type. */\n typeNames: Record<PropType, string>\n /** Names of the picture atlas cells (4 abstract, 3 landscapes, the mirror). */\n pictureNames: string[]\n}\n\nexport const locales: Record<PropsLocale, PropsStrings> = {\n en: {\n typeNames: {\n ceiling_light: \"Ceiling light\",\n pendant: \"Pendant lamp\",\n track_spot: \"Track spot\",\n chandelier: \"Chandelier\",\n wall_ac: \"Indoor AC unit (split)\",\n picture_small: \"Small picture\",\n picture_large: \"Large picture\",\n mirror: \"Mirror\",\n curtain: \"Curtain\",\n blinds: \"Blinds\",\n flat_tv: \"Flat TV\",\n floor_plant: \"Floor plant\",\n table_plant: \"Table plant\",\n hanging_plant: \"Hanging plant\",\n tv_stand: \"TV stand with flat TV\",\n breakfast_bar: \"Breakfast bar\",\n sofa_l: \"L-shaped sofa\",\n double_bed_modern: \"Modern double bed\",\n home_office: \"Home office\",\n closet: \"Closet\",\n vanity: \"Bathroom vanity\",\n ac_condenser: \"AC condenser\",\n lawn: \"Lawn\",\n tile_floor: \"Ceramic tile floor\",\n concrete: \"Concrete slab\",\n deck_floor: \"Deck (flooring)\",\n dirt: \"Packed dirt\",\n gourmet_counter: \"Gourmet counter\",\n sauna: \"Sauna\",\n soaking_tub: \"Soaking tub\",\n pool: \"Pool\",\n deck: \"Raised deck\",\n sun_lounger: \"Sun lounger\",\n garden_bed: \"Vegetable garden bed\",\n fruit_tree: \"Fruit tree\",\n palm_tree: \"Palm tree\",\n pergola: \"Pergola\",\n slide: \"Slide\",\n swing: \"Swing\",\n bbq_simple: \"Simple barbecue\",\n garage_car: \"Car\",\n window_boards: \"Boards on the window\",\n barricade: \"Barricade\",\n sandbags: \"Sandbags\",\n barbed_wire: \"Barbed wire\",\n razor_wire: \"Razor wire\",\n water_bottle_crates: \"Mineral water crates\",\n canned_food: \"Canned food\",\n water_jug: \"Water jug\",\n floor_mattress: \"Mattress on the floor\",\n lantern: \"Lantern\",\n candles: \"Candles\",\n open_suitcases: \"Open suitcases\",\n clothes_on_floor: \"Clothes on the floor\",\n fallen_chair: \"Fallen chair\",\n blood_stains: \"Blood stains\",\n open_door: \"Open door\",\n },\n pictureNames: ['warm abstract', 'blue abstract', 'color blocks', 'minimal', 'sunset', 'mountains and lake', 'beach with palm tree', 'mirror'],\n },\n 'pt-BR': {\n typeNames: {\n ceiling_light: \"Plafon\",\n pendant: \"Pendente\",\n track_spot: \"Spot de trilho\",\n chandelier: \"Lustre\",\n wall_ac: \"Split interno\",\n picture_small: \"Quadro pequeno\",\n picture_large: \"Quadro grande\",\n mirror: \"Espelho\",\n curtain: \"Cortina\",\n blinds: \"Persiana\",\n flat_tv: \"TV plana\",\n floor_plant: \"Vaso de chão\",\n table_plant: \"Vaso de mesa\",\n hanging_plant: \"Planta pendente\",\n tv_stand: \"Rack com TV plana\",\n breakfast_bar: \"Bancada americana\",\n sofa_l: \"Sofá em L\",\n double_bed_modern: \"Cama de casal moderna\",\n home_office: \"Home office\",\n closet: \"Closet\",\n vanity: \"Lavabo\",\n ac_condenser: \"Condensadora\",\n lawn: \"Gramado\",\n tile_floor: \"Piso cerâmico\",\n concrete: \"Cimentado\",\n deck_floor: \"Deck (piso)\",\n dirt: \"Terra batida\",\n gourmet_counter: \"Bancada gourmet\",\n sauna: \"Sauna\",\n soaking_tub: \"Ofurô\",\n pool: \"Piscina\",\n deck: \"Deck elevado\",\n sun_lounger: \"Espreguiçadeira\",\n garden_bed: \"Canteiro de horta\",\n fruit_tree: \"Árvore frutífera\",\n palm_tree: \"Palmeira\",\n pergola: \"Pergolado\",\n slide: \"Escorregador\",\n swing: \"Balanço\",\n bbq_simple: \"Churrasqueira simples\",\n garage_car: \"Carro\",\n window_boards: \"Tábuas na janela\",\n barricade: \"Barricada\",\n sandbags: \"Sacos de areia\",\n barbed_wire: \"Arame farpado\",\n razor_wire: \"Concertina\",\n water_bottle_crates: \"Caixas de água mineral\",\n canned_food: \"Enlatados\",\n water_jug: \"Galão de água\",\n floor_mattress: \"Colchão no chão\",\n lantern: \"Lanterna\",\n candles: \"Velas\",\n open_suitcases: \"Malas abertas\",\n clothes_on_floor: \"Roupas no chão\",\n fallen_chair: \"Cadeira caída\",\n blood_stains: \"Manchas de sangue\",\n open_door: \"Porta aberta\",\n },\n pictureNames: ['abstrato quente', 'abstrato azul', 'blocos de cor', 'minimal', 'pôr do sol', 'montanhas e lago', 'praia com coqueiro', 'espelho'],\n },\n}\n\nexport const resolveStrings = (locale?: string): PropsStrings => locales[locale as PropsLocale] ?? locales.en\n","import { createAtlasProvider } from './atlas'\nimport { DEFS, MATERIALS, TYPE_DEFS, type PropType } from './catalog'\nimport { ceilingBuilders } from './builders/ceiling'\nimport { floorBuilders } from './builders/floors'\nimport { furnitureBuilders } from './builders/furniture'\nimport { houseStateBuilders } from './builders/house-state'\nimport { plantBuilders } from './builders/plants'\nimport { wallBuilders } from './builders/wall'\nimport { yardBuilders } from './builders/yard'\nimport { createCover } from './cover'\nimport { makeKit } from './kit'\nimport { resolveStrings } from './locale'\nimport type { BuilderContext, BuilderMap, DocumentLike, EmitFn, Props3d, Props3dOptions, TypeInfo } from './types'\n\n/** Type metadata with display names for a locale (`en` by default). */\nexport function listTypes(locale?: string): TypeInfo[] {\n const names = resolveStrings(locale).typeNames\n return TYPE_DEFS.map((t) => ({ ...t, name: names[t.id] }))\n}\n\n/**\n * Creates the props kit. Builders only describe primitives through the `api` you pass to them; nothing here touches a renderer.\n *\n * `three` is accepted so the call looks like the other game-kit packages, but the props never use it.\n *\n * ```ts\n * const props = create(THREE, { document })\n * props.builders.sofa_l({ w: 2.8, d: 1.7, h: 0.85, seed: 'living' }, api)\n * ```\n */\nexport function create(_three?: unknown, options: Props3dOptions = {}): Props3d {\n const emit: EmitFn = options.emit || (() => {})\n const doc: DocumentLike | null = options.document || (typeof document !== 'undefined' ? document : null)\n const strings = resolveStrings(options.locale)\n const atlas = createAtlasProvider(doc, strings.pictureNames)\n const cover = createCover(atlas)\n const ctx: BuilderContext = { kit: (M, api) => makeKit(M, api, emit), atlas, cover }\n const builders = Object.assign(\n {},\n ceilingBuilders(ctx),\n wallBuilders(ctx),\n plantBuilders(ctx),\n furnitureBuilders(ctx),\n floorBuilders(ctx),\n yardBuilders(ctx),\n houseStateBuilders(ctx),\n ) as BuilderMap\n return {\n builders,\n types: listTypes(options.locale),\n names: Object.keys(builders) as PropType[],\n atlas,\n cover,\n materials: MATERIALS,\n }\n}\n\nexport { DEFS, MATERIALS, TYPE_DEFS }\n","import type * as ThreeNamespace from 'three'\n\ntype Three = typeof ThreeNamespace\n\n/** What `createNodeSkin` makes: one mesh that stands for a whole tree of meshes, each vertex tied to the node of the tree that moves it. */\nexport type NodeSkin = {\n readonly mesh: ThreeNamespace.Mesh\n /** The nodes the animation moves, in the order of the skin's `aNodeIndex`. */\n readonly nodes: readonly ThreeNamespace.Object3D[]\n /** The matrix of each node, uploaded as the `uNodes` uniform. */\n readonly matrices: { readonly value: ThreeNamespace.Matrix4[] }\n /** Copies the world matrix of every node into `matrices`: call it once per frame, after the tree was animated. */\n update(): void\n}\n\nexport type NodeSkinOptions = {\n /** Meshes the animation moves and that therefore stay nodes of their own (a head ball, an eye). Groups and meshes with children are always nodes. */\n readonly nodes?: Iterable<ThreeNamespace.Object3D>\n}\n\nconst MAX_WALK_DEPTH = 3\n\n/**\n * Every Object3D reachable from the fields of `owner` (up to 3 levels, skipping the keys in `skip`): what the animation of a tree keeps in its own\n * state (head, jaw, wings, legs). Pass the result as `nodes` so those meshes are not merged into their parents.\n */\nexport const collectReferencedNodes = (owner: object, skip: readonly string[] = []): Set<ThreeNamespace.Object3D> => {\n const found = new Set<ThreeNamespace.Object3D>()\n const seen = new Set<unknown>()\n const walk = (value: unknown, depth: number): void => {\n if (!value || typeof value !== 'object' || depth > MAX_WALK_DEPTH || seen.has(value)) return\n seen.add(value)\n const candidate = value as { isObject3D?: boolean; isMaterial?: boolean; isBufferGeometry?: boolean }\n if (candidate.isObject3D) {\n found.add(value as ThreeNamespace.Object3D)\n return\n }\n if (candidate.isMaterial || candidate.isBufferGeometry) return\n for (const key in value) walk((value as Record<string, unknown>)[key], depth + 1)\n }\n for (const key in owner) if (!skip.includes(key)) walk((owner as Record<string, unknown>)[key], 0)\n return found\n}\n\ntype TextureSample = { readonly data: Uint8ClampedArray; readonly width: number; readonly height: number }\n\nconst isVisibleInTree = (object: ThreeNamespace.Object3D): boolean => {\n for (let node: ThreeNamespace.Object3D | null = object; node; node = node.parent) if (!node.visible) return false\n return true\n}\n\n/**\n * A GPU \"node skin\": a tree of about fifty meshes (an animal, a character prop) merged into one mesh, every vertex tied to the node that moves it.\n * The tree stays out of the scene and keeps animating; `update()` copies its node matrices each frame. Colour (with the texture sampled at the uv),\n * roughness, metalness and emissive are baked per vertex, so the 40 animals of a scene cost 40 draw calls instead of 2,000.\n * Generalised from the game's `bichoPele` (G 66536); the colours are read the way r160 does (linear converted to sRGB), as the game does.\n */\nexport const createNodeSkin = (THREE: Three, root: ThreeNamespace.Object3D, options: NodeSkinOptions = {}): NodeSkin => {\n const referenced = new Set(options.nodes ?? [])\n const nodes: ThreeNamespace.Object3D[] = []\n const indexOf = new Map<ThreeNamespace.Object3D, number>()\n root.traverse((object) => {\n const mesh = object as ThreeNamespace.Mesh\n if (!mesh.isMesh || object.children.length > 0 || referenced.has(object)) {\n indexOf.set(object, nodes.length)\n nodes.push(object)\n }\n })\n\n const positions: number[] = []\n const normals: number[] = []\n const tints: number[] = []\n const surfaces: number[] = []\n const nodeIndices: number[] = []\n const samples = new Map<ThreeNamespace.Texture, TextureSample>()\n const color = new THREE.Color()\n const vertex = new THREE.Vector3()\n const normal = new THREE.Vector3()\n\n const sampleOf = (texture: ThreeNamespace.Texture): TextureSample | undefined => {\n const image = texture.image as { getContext?: (kind: string) => CanvasRenderingContext2D | null; width: number; height: number } | undefined\n if (!image?.getContext) return undefined\n const cached = samples.get(texture)\n if (cached) return cached\n const context = image.getContext('2d')\n if (!context) return undefined\n const sample = { data: context.getImageData(0, 0, image.width, image.height).data, width: image.width, height: image.height }\n samples.set(texture, sample)\n return sample\n }\n\n root.traverse((object) => {\n const mesh = object as ThreeNamespace.Mesh\n if (!mesh.isMesh || !isVisibleInTree(object)) return\n const material = (Array.isArray(mesh.material) ? mesh.material[0] : mesh.material) as ThreeNamespace.MeshStandardMaterial\n const geometry = mesh.geometry\n const position = geometry.attributes.position as ThreeNamespace.BufferAttribute\n const normalAttribute = geometry.attributes.normal as ThreeNamespace.BufferAttribute | undefined\n const uv = geometry.attributes.uv as ThreeNamespace.BufferAttribute | undefined\n const matrix = new THREE.Matrix4()\n let nodeIndex = indexOf.get(object)\n if (nodeIndex === undefined) {\n nodeIndex = indexOf.get(object.parent as ThreeNamespace.Object3D) ?? 0\n object.updateMatrix()\n matrix.copy(object.matrix)\n }\n const normalMatrix = new THREE.Matrix3().getNormalMatrix(matrix)\n const sample = material.map ? sampleOf(material.map) : undefined\n const emissive = material.emissive ? (material.emissive.r + material.emissive.g + material.emissive.b) * (material.emissiveIntensity ?? 1) : 0\n const index = geometry.index ? geometry.index.array : null\n const count = index ? index.length : position.count\n for (let item = 0; item < count; item++) {\n const at = index ? (index[item] as number) : item\n vertex.fromBufferAttribute(position, at).applyMatrix4(matrix)\n positions.push(vertex.x, vertex.y, vertex.z)\n if (normalAttribute) normal.fromBufferAttribute(normalAttribute, at).applyMatrix3(normalMatrix).normalize()\n else normal.set(0, 1, 0)\n normals.push(normal.x, normal.y, normal.z)\n color.copy(material.color || color.set(1, 1, 1)).convertLinearToSRGB()\n if (sample && uv && material.map) {\n const repeat = material.map.repeat\n const u = (((uv.getX(at) * repeat.x) % 1) + 1) % 1\n const v = (((uv.getY(at) * repeat.y) % 1) + 1) % 1\n const pixelX = Math.min(sample.width - 1, (u * sample.width) | 0)\n const pixelY = Math.min(sample.height - 1, ((1 - v) * sample.height) | 0)\n const offset = (pixelY * sample.width + pixelX) * 4\n color.multiply(new THREE.Color().setRGB((sample.data[offset] as number) / 255, (sample.data[offset + 1] as number) / 255, (sample.data[offset + 2] as number) / 255, THREE.SRGBColorSpace))\n }\n if (emissive > 0.3) color.lerp(material.emissive, 0.7)\n tints.push(color.r, color.g, color.b)\n surfaces.push(material.roughness ?? 0.8, material.metalness ?? 0, emissive > 0.3 ? 1 : 0)\n nodeIndices.push(nodeIndex)\n }\n })\n\n const geometry = new THREE.BufferGeometry()\n geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3))\n geometry.setAttribute('normal', new THREE.Float32BufferAttribute(normals, 3))\n geometry.setAttribute('aTint', new THREE.Float32BufferAttribute(tints, 3))\n geometry.setAttribute('aSurface', new THREE.Float32BufferAttribute(surfaces, 3))\n geometry.setAttribute('aNodeIndex', new THREE.Float32BufferAttribute(nodeIndices, 1))\n geometry.computeBoundingSphere()\n\n const matrices = { value: nodes.map(() => new THREE.Matrix4()) }\n const skinVertex = (shader: ThreeNamespace.WebGLProgramParametersWithUniforms): void => {\n shader.uniforms.uNodes = matrices\n shader.vertexShader = shader.vertexShader\n .replace(\n '#include <common>',\n `#include <common>\\nuniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3 aTint; attribute vec3 aSurface; varying vec3 vTint; varying vec3 vSurface;`,\n )\n .replace('#include <beginnormal_vertex>', 'vec3 objectNormal = mat3(uNodes[int(aNodeIndex + .5)]) * normal;')\n .replace('#include <begin_vertex>', 'vec3 transformed = (uNodes[int(aNodeIndex + .5)] * vec4(position, 1.)).xyz; vTint = aTint; vSurface = aSurface;')\n }\n\n const material = new THREE.MeshStandardMaterial({ roughness: 1, metalness: 0 })\n material.onBeforeCompile = (shader) => {\n skinVertex(shader)\n shader.fragmentShader = shader.fragmentShader\n .replace('#include <common>', '#include <common>\\nvarying vec3 vTint; varying vec3 vSurface;')\n .replace('vec4 diffuseColor = vec4( diffuse, opacity );', 'vec4 diffuseColor = vec4( vTint, opacity );')\n .replace('#include <roughnessmap_fragment>', 'float roughnessFactor = vSurface.x;')\n .replace('#include <metalnessmap_fragment>', 'float metalnessFactor = vSurface.y;')\n .replace('#include <emissivemap_fragment>', 'totalEmissiveRadiance = vTint * vSurface.z * 1.5;')\n }\n material.customProgramCacheKey = () => `nodeSkin${nodes.length}`\n const depthMaterial = new THREE.MeshDepthMaterial({ depthPacking: THREE.RGBADepthPacking })\n depthMaterial.onBeforeCompile = skinVertex\n depthMaterial.customProgramCacheKey = () => `nodeSkinDepth${nodes.length}`\n\n const mesh = new THREE.Mesh(geometry, material)\n mesh.customDepthMaterial = depthMaterial\n mesh.frustumCulled = false\n root.updateMatrixWorld(true)\n return {\n mesh,\n nodes,\n matrices,\n update() {\n root.updateMatrixWorld(true)\n for (let index = 0; index < nodes.length; index++) (matrices.value[index] as ThreeNamespace.Matrix4).copy((nodes[index] as ThreeNamespace.Object3D).matrixWorld)\n },\n }\n}\n","import type { Primitive, PropsApi } from './types'\n\n/**\n * An api that records every primitive instead of drawing it. Handy for tests, previews and for measuring a prop.\n *\n * ```ts\n * const { api, primitives } = createRecorder()\n * props.builders.sofa_l(item, api)\n * console.log(primitives.length)\n * ```\n * Pass `{ light: false }` to leave `api.light` undefined, so builders fall back to `emit('light', ...)`.\n */\nexport function createRecorder(options: { light?: boolean } = {}): { api: PropsApi; primitives: Primitive[] } {\n const primitives: Primitive[] = []\n const api: PropsApi = {\n box: (M, material, x, y, z, w, h, d, color, ry, rx, rz) => void primitives.push({ kind: 'box', M, material, x, y, z, w, h, d, color, ry, rx, rz }),\n cylinder: (M, material, x, y, z, r, h, color, rx, rz, shape) => void primitives.push({ kind: 'cylinder', M, material, x, y, z, r, h, color, rx, rz, shape }),\n leaf: (M, x, y, z, w, h, color, ry, rx) => void primitives.push({ kind: 'leaf', M, x, y, z, w, h, color, ry, rx }),\n plane: (M, material, x, y, z, w, h, color, ry, rx, rz, uv) => void primitives.push({ kind: 'plane', M, material, x, y, z, w, h, color, ry, rx, rz, uv }),\n }\n if (options.light !== false) {\n api.light = (M, x, y, z, color, range, lightKind) => void primitives.push({ kind: 'light', M, x, y, z, color, range, lightKind })\n }\n return { api, primitives }\n}\n","import type * as THREE_NS from 'three'\n\nconst ATTRIBUTES = ['position', 'normal', 'uv'] as const\n\nconst isVisible = (object: THREE_NS.Object3D): boolean => {\n for (let node: THREE_NS.Object3D | null = object; node; node = node.parent) if (!node.visible) return false\n return true\n}\n\n/** A copy of the geometry with the matrix baked in, not indexed, keeping only what every model has (position, normal, uv), so that any two of them can be joined. */\nconst bake = (THREE: typeof THREE_NS, mesh: THREE_NS.Mesh, matrix: THREE_NS.Matrix4): THREE_NS.BufferGeometry => {\n const source = mesh.geometry.index ? mesh.geometry.toNonIndexed() : mesh.geometry.clone()\n const baked = new THREE.BufferGeometry()\n const count = source.getAttribute('position').count\n for (const name of ATTRIBUTES) {\n const attribute = source.getAttribute(name)\n baked.setAttribute(name, attribute ?? new THREE.BufferAttribute(new Float32Array(count * (name === 'uv' ? 2 : 3)), name === 'uv' ? 2 : 3))\n }\n source.dispose()\n return baked.applyMatrix4(matrix)\n}\n\n/** One non-indexed geometry out of several, in order (the port of `mergeGeometries` of three/addons for the three attributes above). */\nconst join = (THREE: typeof THREE_NS, geometries: readonly THREE_NS.BufferGeometry[]): THREE_NS.BufferGeometry => {\n const merged = new THREE.BufferGeometry()\n for (const name of ATTRIBUTES) {\n const size = (geometries[0]?.getAttribute(name) as THREE_NS.BufferAttribute).itemSize\n const total = geometries.reduce((sum, geometry) => sum + geometry.getAttribute(name).count, 0)\n const array = new Float32Array(total * size)\n let offset = 0\n for (const geometry of geometries) {\n array.set(geometry.getAttribute(name).array, offset)\n offset += geometry.getAttribute(name).count * size\n }\n merged.setAttribute(name, new THREE.BufferAttribute(array, size))\n }\n return merged\n}\n\n/**\n * Every visible mesh under `root` (whose world matrix must be up to date), baked into the space of `space` and joined by material: one mesh per material, no shadow cast.\n * Weapons lying on a counter are never animated, so the hundreds of meshes of a model cost one draw call per material instead.\n */\nexport function mergeByMaterial(THREE: typeof THREE_NS, root: THREE_NS.Object3D, space: THREE_NS.Object3D): THREE_NS.Mesh[] {\n const toSpace = space.matrixWorld.clone().invert()\n const byMaterial = new Map<THREE_NS.Material, THREE_NS.BufferGeometry[]>()\n root.traverse((object) => {\n const mesh = object as THREE_NS.Mesh\n if (!mesh.isMesh || Array.isArray(mesh.material) || !isVisible(mesh)) return\n const list = byMaterial.get(mesh.material) ?? []\n list.push(bake(THREE, mesh, new THREE.Matrix4().multiplyMatrices(toSpace, mesh.matrixWorld)))\n byMaterial.set(mesh.material, list)\n })\n return [...byMaterial].map(([material, geometries]) => {\n const merged = new THREE.Mesh(join(THREE, geometries), material)\n for (const geometry of geometries) geometry.dispose()\n merged.castShadow = false\n merged.receiveShadow = true\n return merged\n })\n}\n","import type { BirdSpec } from '@adatechnology/game-animals'\n\nimport type { AnimateParams } from './animate-types'\nimport type { AnimalRig } from './types'\n\nconst { PI } = Math\n\n/** Advances a bird's cycle (page: `animBird`): steps, flight for the species that fly, wings, peck, alarm, tail. */\nexport const animateBird = (rig: AnimalRig, params: AnimateParams): void => {\n const { time: t, delta: dt, clip } = params\n const species = rig.species\n const spec = species.body as BirdSpec\n const isVulture = species.id === 'vulture'\n const isChicken = species.id === 'chicken'\n const isLapwing = species.id === 'southern_lapwing'\n const flying = clip === 'run' && !isChicken\n const hz = clip === 'run' && !flying ? species.runHz : clip === 'walk' ? species.walkHz : 0\n rig.phase += dt * (flying ? (isVulture ? 1.1 : 4.5) : hz) * 2 * PI\n const targetHeight = flying ? rig.size * 1.2 : 0\n rig.trunk.position.y += (targetHeight - rig.trunk.position.y) * Math.min(1, dt * 3)\n rig.legs.forEach((leg, index) => {\n if (flying) {\n leg.hip.rotation.x = 1.2\n leg.knee.rotation.x = -1.6\n } else if (clip === 'idle') {\n leg.hip.rotation.x = 0\n leg.knee.rotation.x = 0\n } else {\n const phase = rig.phase + index * PI\n leg.hip.rotation.x = -Math.sin(phase) * 0.45\n leg.knee.rotation.x = -Math.max(0, Math.cos(phase)) * 0.7\n }\n leg.foot.rotation.x = -(leg.hip.rotation.x + leg.knee.rotation.x)\n })\n rig.wings.forEach((wing) => {\n const { side, shoulder, elbow } = wing\n if (flying) {\n const flap = isVulture ? 0.25 + Math.sin(rig.phase) * 0.18 : Math.sin(rig.phase) * 0.9\n shoulder.rotation.y += (0 - shoulder.rotation.y) * Math.min(1, dt * 6)\n shoulder.rotation.z = side * flap\n elbow.rotation.z = side * (isVulture ? 0.08 : Math.sin(rig.phase - 0.6) * 0.4)\n elbow.rotation.y = 0\n shoulder.scale.x += (1 - shoulder.scale.x) * Math.min(1, dt * 6)\n } else {\n const alert = isLapwing && clip === 'idle' ? Math.max(0, Math.sin(t * 1.3)) * 0.9 : 0\n const flutter = isChicken && clip === 'run' ? Math.abs(Math.sin(rig.phase * 1.5)) * 0.8 : 0\n shoulder.rotation.y += ((side * PI) / 2 * 0.97 - alert * side * 0.9 - shoulder.rotation.y) * Math.min(1, dt * 6)\n shoulder.rotation.z = side * (-0.06 + flutter + alert * 0.6)\n elbow.rotation.z = side * 0.04\n elbow.rotation.y = 0\n shoulder.scale.x += ((alert > 0.1 ? 1 : 0.58) - shoulder.scale.x) * Math.min(1, dt * 6)\n }\n })\n const peck = isChicken && clip === 'idle' ? Math.max(0, Math.sin(t * 1.6)) ** 8 * 1.2 : 0\n if (rig.neck) {\n rig.neck.rotation.x = rig.neckAngle + (clip === 'walk' && !isVulture ? Math.max(0, Math.sin(rig.phase * 2)) * 0.35 : 0) + peck + (flying ? 0.6 : 0)\n rig.neck.rotation.y = clip === 'idle' ? Math.sin(t * 0.9) * 0.5 : 0\n }\n if (rig.jaw) rig.jaw.rotation.x = isLapwing && clip === 'idle' ? Math.max(0, Math.sin(t * 1.3)) * Math.abs(Math.sin(t * 14)) * 0.5 : 0\n if (rig.tailRoot) rig.tailRoot.rotation.x = (spec.tailA || 0) + (flying ? 0.6 : 0)\n}\n","import type { GaitPhases, QuadrupedSpec } from '@adatechnology/game-animals'\n\nimport type { AnimateParams } from './animate-types'\nimport type { AnimalRig } from './types'\n\nconst { PI } = Math\n\n/** Advances a quadruped's step cycle (page: `animQuad`): legs by gait phase, trunk bob, neck, head, jaw and tail; a dead one lies down. */\nexport const animateQuadruped = (rig: AnimalRig, params: AnimateParams, gaits: { readonly walk: GaitPhases; readonly trot: GaitPhases; readonly gallop: GaitPhases }): void => {\n const { time: t, delta: dt, clip } = params\n const species = rig.species\n const spec = species.body as QuadrupedSpec\n if (rig.dead) {\n rig.root.rotation.z += (PI / 2 * 0.97 - rig.root.rotation.z) * Math.min(1, dt * 3)\n rig.root.position.y += (spec.R * (spec.sx || 1) * 0.85 - rig.root.position.y) * Math.min(1, dt * 3)\n rig.legs.forEach((leg) => {\n leg.hip.rotation.x = leg.angle1 + (leg.isFront ? -0.5 : 0.4)\n leg.knee.rotation.x = 0.05\n leg.foot.rotation.x = 0\n })\n if (rig.neck) rig.neck.rotation.x = rig.neckAngle + 0.35\n if (rig.jaw) rig.jaw.rotation.x = 0.25\n return\n }\n const hz = (clip === 'run' ? species.runHz : clip === 'walk' ? species.walkHz : 0) * (rig.infected ? 1.2 : 1)\n rig.phase += dt * hz * 2 * PI\n const gait = clip === 'run' ? (species.gallop ? gaits.gallop : gaits.trot) : gaits.walk\n const amplitude = clip === 'run' ? 0.62 : 0.34\n const flex = clip === 'run' ? 1.15 : 0.75\n rig.legs.forEach((leg, index) => {\n if (clip === 'idle') {\n leg.hip.rotation.x = leg.angle1\n leg.knee.rotation.x = leg.angle2\n } else {\n const phase = rig.phase + (gait[index] as number)\n const limit = rig.infected && index === 1 ? 0.35 : 1\n leg.hip.rotation.x = leg.angle1 - Math.sin(phase) * amplitude * limit\n leg.knee.rotation.x = leg.angle2 + Math.max(0, Math.cos(phase)) * flex * limit\n }\n leg.foot.rotation.x = -(leg.hip.rotation.x + leg.knee.rotation.x) * 0.85\n })\n const bob = clip === 'idle' ? 0 : Math.abs(Math.sin(rig.phase)) * (clip === 'run' ? 0.05 : 0.018) * rig.size\n rig.trunk.position.y = bob * 0.6\n const bodyGroup = rig.bodyGroup\n if (bodyGroup) {\n bodyGroup.rotation.x = clip === 'run' && species.gallop ? Math.sin(rig.phase + PI * 0.4) * 0.07 : 0\n bodyGroup.scale.y = 1 + (clip === 'idle' ? Math.sin(t * 2.2) * 0.012 : 0)\n }\n const low = rig.infected ? 0.35 : 0\n if (rig.neck) {\n rig.neck.rotation.x = rig.neckAngle + low + (clip === 'idle' ? Math.sin(t * 0.7) * 0.08 : Math.sin(rig.phase * 2) * 0.04)\n rig.neck.rotation.y = clip === 'idle' ? Math.sin(t * 0.45) * 0.35 : 0\n }\n if (rig.head) rig.head.rotation.z = rig.infected ? Math.sin(t * 11) * Math.sin(t * 1.7) * 0.12 : 0\n if (rig.jaw) rig.jaw.rotation.x = rig.infected ? 0.25 + Math.max(0, Math.sin(t * 6)) * 0.25 : clip === 'run' ? 0.12 : 0\n const wagging = species.id === 'dog' && !rig.infected\n rig.tail.forEach((segment, index) => {\n segment.rotation.z = Math.sin(t * (wagging ? 9 : 2.2) - index * 0.6) * (wagging ? 0.35 : 0.12)\n })\n}\n","import type * as THREE_NS from 'three'\n\nimport type { AnimateParams } from './animate-types'\nimport type { AnimalRig, Three } from './types'\n\nconst { PI } = Math\n\n/** The path of the body for a clip: crawling in a wave, or coiled with the head up in an S (and striking when `strike` rises). */\nconst bodyPath = (THREE: Three, rig: AnimalRig, params: AnimateParams, spacing: number): THREE_NS.Vector3[] => {\n const { time: t, clip } = params\n const points: THREE_NS.Vector3[] = []\n for (let index = 0; index < rig.segmentCount; index++) {\n let x: number\n let z: number\n let y = 0\n if (clip === 'walk') {\n z = 0.6 - index * spacing\n x = Math.sin(rig.phase * 2.2 - index * 0.42) * 0.08 * Math.min(1, index / 6 + 0.3)\n } else {\n const strike = clip === 'run' ? Math.max(0, Math.sin(t * 2.2)) ** 6 : 0\n if (index < 9) {\n const k = index / 9\n z = 0.08 + (1 - k) * 0.12 + strike * (1 - k) * 0.35\n y = (1 - k) * 0.13 * (1 - strike * 0.6)\n x = Math.sin(k * PI * 1.6) * 0.05 * (1 - strike)\n } else {\n const angle = (index - 9) * 0.36\n const radius = 0.16 - (index - 9) * 0.0045\n x = Math.cos(angle) * radius\n z = Math.sin(angle) * radius - 0.05\n y = (index - 9) * 0.0012\n }\n }\n points.push(new THREE.Vector3(x, y, z))\n }\n return points\n}\n\n/** Advances a snake (page: `animSnake`): lays the 34 segments along the path of the clip, then the head, jaw and tongue. */\nexport const animateSnake = (THREE: Three, rig: AnimalRig, params: AnimateParams): void => {\n const { time: t, delta: dt, clip } = params\n const spacing = rig.segmentLength * (params.split ? 1.5 : 1)\n if (clip === 'walk') rig.phase += dt * 2.4\n const points = bodyPath(THREE, rig, params, spacing)\n const at = (index: number): THREE_NS.Vector3 => points[index] as THREE_NS.Vector3\n for (let index = 0; index < rig.segmentCount; index++) {\n const group = (rig.segments[index] as AnimalRig['segments'][number]).group\n group.position.copy(at(index))\n const direction = index === 0 ? at(0).clone().sub(at(1)) : at(index - 1).clone().sub(at(index))\n group.rotation.set(0, Math.atan2(direction.x, direction.z), 0)\n group.rotateX(-Math.atan2(direction.y, Math.hypot(direction.x, direction.z)))\n }\n const head = rig.head as THREE_NS.Group\n const headDirection = at(0).clone().sub(at(1)).normalize()\n head.position.copy(at(0)).addScaledVector(headDirection, 0.02)\n head.rotation.set(0, Math.atan2(headDirection.x, headDirection.z), 0)\n head.rotateX(-Math.atan2(headDirection.y, Math.hypot(headDirection.x, headDirection.z)) * 0.5)\n const jaw = rig.jaw as THREE_NS.Group\n jaw.rotation.x = clip === 'run' ? Math.max(0, Math.sin(t * 2.2)) ** 6 * 0.9 : 0\n const tongue = rig.tongue as THREE_NS.Mesh\n tongue.visible = Math.sin(t * 5) > 0.3\n tongue.scale.z = 1 + Math.sin(t * 20) * 0.3\n}\n","import type * as THREE_NS from 'three'\n\nimport type { AnimalRig, AnimalsThreeOptions, Three } from './types'\n\nexport type MaterialExtras = Partial<THREE_NS.MeshStandardMaterialParameters>\n\n/** The mulberry32 stream of the page (`rng`): the same numbers for the same seed. */\nexport const createStream = (seed: number): (() => number) => {\n let state = seed\n return () => {\n state |= 0\n state = (state + 0x6d2b79f5) | 0\n let t = Math.imul(state ^ (state >>> 15), 1 | state)\n t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t\n return ((t ^ (t >>> 14)) >>> 0) / 4294967296\n }\n}\n\n/** The page's `add`: a mesh at a position and rotation under a parent, casting and receiving shadows. */\nexport const addMesh = (\n THREE: Three,\n parent: THREE_NS.Object3D,\n geometry: THREE_NS.BufferGeometry,\n material: THREE_NS.Material,\n x?: number,\n y?: number,\n z?: number,\n rx?: number,\n ry?: number,\n rz?: number,\n): THREE_NS.Mesh => {\n const mesh = new THREE.Mesh(geometry, material)\n mesh.position.set(x || 0, y || 0, z || 0)\n if (rx || ry || rz) mesh.rotation.set(rx || 0, ry || 0, rz || 0)\n mesh.castShadow = true\n mesh.receiveShadow = true\n parent.add(mesh)\n return mesh\n}\n\n/** The part builders of the page's `Animal` (`M`, `ball`, `limb`, `joint`, `pivot`), bound to one rig. */\nexport type RigTools = {\n material(color: number, extras?: MaterialExtras): THREE_NS.MeshStandardMaterial\n ball(parent: THREE_NS.Object3D, radius: number, material: THREE_NS.Material, x?: number, y?: number, z?: number, sx?: number, sy?: number, sz?: number): THREE_NS.Mesh\n /** A cylinder hanging from the origin; `radius1` is the one the page calls the top, but the geometry takes it as the bottom (kept as the page has it). */\n limb(parent: THREE_NS.Object3D, radius1: number, radius2: number, length: number, material: THREE_NS.Material): THREE_NS.Mesh\n joint(parent: THREE_NS.Object3D, radius: number, material: THREE_NS.Material, x?: number, y?: number, z?: number): THREE_NS.Mesh\n pivot(parent: THREE_NS.Object3D, x: number, y: number, z: number, offset?: THREE_NS.Vector3): THREE_NS.Group\n eyes(head: THREE_NS.Object3D, radius: number, x: number, y: number, z: number): void\n}\n\nexport const createRigTools = (THREE: Three, rig: AnimalRig): RigTools => {\n const tools: RigTools = {\n material(color, extras) {\n const material = new THREE.MeshStandardMaterial(Object.assign({ color, roughness: 0.8, metalness: 0, flatShading: false }, extras || {}))\n material.userData.base = material.color.clone()\n rig.materials.push(material)\n return material\n },\n ball(parent, radius, material, x, y, z, sx, sy, sz) {\n const mesh = addMesh(THREE, parent, new THREE.SphereGeometry(radius, 16, 12), material, x, y, z)\n if (sx) mesh.scale.set(sx, sy as number, sz as number)\n return mesh\n },\n limb: (parent, radius1, radius2, length, material) => addMesh(THREE, parent, new THREE.CylinderGeometry(radius2, radius1, length, 14, 1).translate(0, -length / 2, 0), material),\n joint(parent, radius, material, x, y, z) {\n const joint = tools.ball(parent, radius, material, x, y, z)\n joint.userData.joint = 1\n rig.joints.push(joint)\n return joint\n },\n pivot(parent, x, y, z, offset) {\n const group = new THREE.Group()\n group.position.set(x, y, z)\n parent.add(group)\n rig.pivots.push({ group, rest: group.position.clone(), offset: offset || new THREE.Vector3() })\n return group\n },\n eyes(head, radius, x, y, z) {\n const eyeMaterial = tools.material(0x111111, { roughness: 0.25 })\n rig.eyeMaterial = eyeMaterial\n for (const side of [-1, 1]) rig.eyes.push(tools.ball(head, radius, eyeMaterial, side * x, y, z))\n },\n }\n return tools\n}\n\nexport const createRig = (THREE: Three, species: AnimalRig['species'], options: AnimalsThreeOptions & { phase: number }): AnimalRig => {\n const rig: AnimalRig = {\n species,\n root: new THREE.Group(),\n trunk: new THREE.Group(),\n materials: [],\n joints: [],\n pivots: [],\n legs: [],\n tail: [],\n ears: [],\n infectionParts: [],\n eyes: [],\n wings: [],\n segments: [],\n phase: options.phase,\n size: 1,\n segmentLength: 0,\n segmentCount: 0,\n home: new THREE.Vector3(),\n infected: false,\n dead: false,\n neckAngle: 0,\n headAngle: 0,\n triangles: 0,\n }\n rig.root.add(rig.trunk)\n return rig\n}\n","import { birdSize } from '@adatechnology/game-animals'\nimport type { BirdSpec } from '@adatechnology/game-animals'\nimport type * as THREE_NS from 'three'\n\nimport { addMesh, createRigTools } from './context'\nimport type { BirdExtra } from './extras'\nimport type { AnimalRig, Three } from './types'\n\nconst buildWings = (THREE: Three, rig: AnimalRig, spec: BirdSpec, parts: { bodyGroup: THREE_NS.Group; main: THREE_NS.MeshStandardMaterial; dark: THREE_NS.MeshStandardMaterial }): void => {\n const tools = createRigTools(THREE, rig)\n const { bodyGroup, main, dark } = parts\n for (const side of [-1, 1]) {\n const shoulder = tools.pivot(bodyGroup, side * spec.r * spec.s[0] * 1.02, spec.r * 0.22, spec.r * 0.3, new THREE.Vector3(side * 0.12, 0.03, 0).multiplyScalar(rig.size))\n tools.joint(shoulder, spec.r * 0.14, main, 0, 0, 0)\n tools.ball(shoulder, 0.5, spec.wingM ? tools.material(spec.wingM) : dark, side * spec.wi * 0.5, 0, -spec.wc * 0.3, spec.wi * 1.05, spec.r * 0.16, spec.wc)\n const elbow = tools.pivot(shoulder, side * spec.wi, 0, 0, new THREE.Vector3(side * 0.06, 0, 0).multiplyScalar(rig.size))\n tools.joint(elbow, spec.r * 0.1, main, 0, 0, 0)\n tools.ball(elbow, 0.5, dark, side * spec.wo * 0.45, 0, -spec.wc * 0.32, spec.wo * 1.05, spec.r * 0.12, spec.wc * 0.75)\n if (spec.fingers) {\n for (let finger = 0; finger < 4; finger++) {\n addMesh(THREE, elbow, new THREE.BoxGeometry(spec.wo * 0.35, spec.r * 0.03, spec.wc * 0.12), dark, side * (spec.wo + spec.wo * 0.12), 0, -spec.wc * 0.05 - finger * spec.wc * 0.18, 0, side * (0.05 + finger * 0.12), 0)\n }\n }\n if (spec.spur) addMesh(THREE, elbow, new THREE.ConeGeometry(spec.r * 0.06, spec.r * 0.25, 5), tools.material(0xc0302a), 0, 0, spec.wc * 0.2, Math.PI / 2, 0, 0)\n rig.wings.push({ shoulder, elbow, side })\n }\n}\n\nconst buildLegs = (THREE: Three, rig: AnimalRig, spec: BirdSpec, parts: { bodyGroup: THREE_NS.Group; main: THREE_NS.MeshStandardMaterial; leg: THREE_NS.MeshStandardMaterial }): void => {\n const tools = createRigTools(THREE, rig)\n const { bodyGroup, main, leg } = parts\n for (const side of [-1, 1]) {\n const hip = tools.pivot(bodyGroup, side * spec.r * 0.38, -spec.r * 0.55, spec.r * 0.05, new THREE.Vector3(side * 0.06, -0.05, 0).multiplyScalar(rig.size))\n tools.joint(hip, spec.r * 0.18, main, 0, 0, 0)\n tools.limb(hip, spec.r * 0.16, spec.r * 0.09, spec.shank, spec.featherLeg ? main : leg)\n const ankle = tools.pivot(hip, 0, -spec.shank, 0, new THREE.Vector3(0, -0.04, 0).multiplyScalar(rig.size))\n tools.joint(ankle, spec.r * 0.06, leg, 0, 0, 0)\n tools.limb(ankle, spec.r * 0.055, spec.r * 0.05, spec.tars, leg)\n const foot = tools.pivot(ankle, 0, -spec.tars, 0)\n for (const angle of [-0.45, 0, 0.45]) addMesh(THREE, foot, new THREE.BoxGeometry(spec.r * 0.05, spec.r * 0.04, spec.r * 0.55).translate(0, 0, spec.r * 0.25), leg, 0, -spec.r * 0.02, 0, 0, angle, 0)\n addMesh(THREE, foot, new THREE.BoxGeometry(spec.r * 0.05, spec.r * 0.04, spec.r * 0.25).translate(0, 0, -spec.r * 0.12), leg, 0, -spec.r * 0.02, 0)\n rig.legs.push({ hip, knee: ankle, foot, isFront: false, angle1: 0.35, angle2: -0.5, side })\n }\n}\n\n/** Builds a bird: body, tail, two wings with shoulder and elbow pivots, neck, head with beak and jaw, eyes, the species' extra parts, and two legs. */\nexport const buildBird = (THREE: Three, rig: AnimalRig, options: { extra?: BirdExtra }): AnimalRig => {\n const spec = rig.species.body as BirdSpec\n const tools = createRigTools(THREE, rig)\n const colors = spec.c\n const main = tools.material(colors.main)\n const dark = tools.material(colors.dark)\n const leg = tools.material(colors.leg)\n const beak = tools.material(colors.beak, { roughness: 0.5 })\n const belly = tools.material(colors.belly || colors.main)\n rig.main = main\n const hipY = spec.tars + spec.shank * 0.9\n rig.size = birdSize(spec)\n const bodyGroup = tools.pivot(rig.trunk, 0, hipY + spec.r * 0.55, 0)\n rig.bodyGroup = bodyGroup\n bodyGroup.rotation.x = spec.pitch || 0\n tools.ball(bodyGroup, spec.r, main, 0, 0, 0, spec.s[0], spec.s[1], spec.s[2])\n tools.ball(bodyGroup, spec.r * 0.9, belly, 0, -spec.r * 0.22, spec.r * 0.15, spec.s[0] * 0.9, spec.s[1] * 0.8, spec.s[2] * 0.85)\n const tail = tools.pivot(bodyGroup, 0, spec.r * 0.15, -spec.r * spec.s[2] * 0.85, new THREE.Vector3(0, 0, -0.08).multiplyScalar(rig.size))\n tail.rotation.x = spec.tailA || -0.3\n rig.tailRoot = tail\n addMesh(THREE, tail, new THREE.BoxGeometry(spec.tailW, spec.r * 0.08, spec.tailL).translate(0, 0, -spec.tailL / 2), spec.tailM ? tools.material(spec.tailM) : dark)\n buildWings(THREE, rig, spec, { bodyGroup, main, dark })\n const neck = tools.pivot(bodyGroup, 0, spec.r * 0.45, spec.r * spec.s[2] * 0.75, new THREE.Vector3(0, 0.05, 0.08).multiplyScalar(rig.size))\n neck.rotation.x = spec.neckA\n rig.neck = neck\n rig.neckAngle = spec.neckA\n tools.joint(neck, spec.nr * 1.1, spec.neckM ? tools.material(spec.neckM) : main, 0, 0, 0)\n addMesh(THREE, neck, new THREE.CylinderGeometry(spec.nr * 0.85, spec.nr, spec.nl, 12).translate(0, spec.nl / 2, 0), spec.neckM ? tools.material(spec.neckM) : main)\n const head = tools.pivot(neck, 0, spec.nl, 0, new THREE.Vector3(0, 0.05, 0.05).multiplyScalar(rig.size))\n head.rotation.x = -spec.neckA + (spec.headA || 0)\n rig.head = head\n rig.headAngle = head.rotation.x\n const headMaterial = spec.headM ? tools.material(spec.headM) : main\n tools.ball(head, spec.hr, headMaterial, 0, 0, 0, spec.hs ? spec.hs[0] : 1, spec.hs ? spec.hs[1] : 1, spec.hs ? spec.hs[2] : 1.1)\n addMesh(THREE, head, new THREE.ConeGeometry(spec.br, spec.bl, 8).rotateX(Math.PI / 2).translate(0, 0, spec.bl / 2), beak, 0, -spec.hr * 0.1, spec.hr * 0.85, spec.hook || 0, 0, 0)\n const jaw = tools.pivot(head, 0, -spec.hr * 0.25, spec.hr * 0.8)\n rig.jaw = jaw\n addMesh(THREE, jaw, new THREE.ConeGeometry(spec.br * 0.7, spec.bl * 0.85, 8).rotateX(Math.PI / 2).translate(0, 0, spec.bl * 0.42), beak).scale.set(1, 0.5, 1)\n tools.eyes(head, spec.hr * 0.17, spec.hr * 0.68, spec.hr * 0.2, spec.hr * 0.35)\n if (spec.eyeC && rig.eyeMaterial) {\n rig.eyeMaterial.color.set(spec.eyeC)\n rig.eyeMaterial.userData.base = rig.eyeMaterial.color.clone()\n }\n options.extra?.({ THREE, rig, tools }, { bodyGroup, head, neck, main, dark })\n buildLegs(THREE, rig, spec, { bodyGroup, main, leg })\n return rig\n}\n","import type * as THREE_NS from 'three'\n\nimport { addMesh } from './context'\nimport type { RigTools } from './context'\nimport type { HeadSpecOf, NeckSpecOf } from './extras-types'\nimport type { AnimalRig, Three } from './types'\n\nexport type ExtraContext = { readonly THREE: Three; readonly rig: AnimalRig; readonly tools: RigTools }\n\nexport type QuadrupedParts = {\n readonly bodyGroup: THREE_NS.Group\n readonly head: THREE_NS.Group\n readonly neck: THREE_NS.Group\n readonly main: THREE_NS.MeshStandardMaterial\n readonly belly: THREE_NS.MeshStandardMaterial\n readonly dark: THREE_NS.MeshStandardMaterial\n readonly L: number\n readonly Rb: number\n readonly bodyY: number\n readonly hd: HeadSpecOf\n readonly nk: NeckSpecOf\n}\n\nexport type BirdParts = {\n readonly bodyGroup: THREE_NS.Group\n readonly head: THREE_NS.Group\n readonly neck: THREE_NS.Group\n readonly main: THREE_NS.MeshStandardMaterial\n readonly dark: THREE_NS.MeshStandardMaterial\n}\n\nexport type QuadrupedExtra = (context: ExtraContext, parts: QuadrupedParts) => void\nexport type BirdExtra = (context: ExtraContext, parts: BirdParts) => void\n\nconst cow: QuadrupedExtra = ({ THREE, tools }, k) => {\n tools.ball(k.bodyGroup, 0.2, k.main, 0, k.Rb * 1.1, k.L * 0.33, 0.95, 1.1, 1.1) // hump\n tools.ball(k.neck, 0.13, k.main, 0, 0.12, 0.08, 0.55, 1.4, 1.05) // dewlap\n for (const side of [-1, 1]) addMesh(THREE, k.head, new THREE.ConeGeometry(0.025, 0.13, 8), tools.material(0xcfc6a8), side * 0.12, k.hd.r * 1.05, k.hd.r * 0.05, 0, 0, side * -0.9)\n}\n\nconst horse: QuadrupedExtra = ({ THREE, rig, tools }, k) => {\n for (let index = 0; index < 9; index++) addMesh(THREE, k.neck, new THREE.BoxGeometry(0.035, 0.16, 0.08), k.dark, 0, 0.06 + index * 0.065, -0.12 + index * 0.004, -0.25, 0, 0) // mane\n addMesh(THREE, k.head, new THREE.BoxGeometry(0.03, 0.12, 0.06), k.dark, 0, k.hd.r * 1.05, k.hd.r * 0.3, 0.6, 0, 0) // forelock\n const last = rig.tail[rig.tail.length - 1] as THREE_NS.Group\n tools.ball(last, 0.08, k.dark, 0, -0.42, 0, 0.7, 4.5, 0.7) // tail hair\n}\n\nconst wildBoar: QuadrupedExtra = ({ THREE, tools }, k) => {\n for (let index = 0; index < 12; index++) addMesh(THREE, k.bodyGroup, new THREE.BoxGeometry(0.03, 0.1, 0.06), k.dark, 0, k.Rb * 1.12 - index * 0.004, k.L * 0.38 - index * 0.06, -0.2, 0, 0) // bristles\n for (const side of [-1, 1])\n addMesh(THREE, k.head, new THREE.ConeGeometry(0.012, 0.09, 6), tools.material(0xe8e0cc), side * 0.07, k.hd.r * -0.25 + 0.03, k.hd.r * 0.25 + k.hd.r * k.hd.s[2] * 0.55 + 0.13, -0.6, 0, side * 0.5) // tusks\n}\n\nconst chicken: BirdExtra = ({ tools }, k) => {\n const red = tools.material(0xc0281e)\n for (let index = 0; index < 4; index++) tools.ball(k.head, 0.014, red, 0, 0.045, -0.012 + index * 0.014, 0.5, 1, 0.8) // comb\n tools.ball(k.head, 0.012, red, 0, -0.04, 0.04, 0.5, 1.3, 0.7) // wattle\n}\n\nconst southernLapwing: BirdExtra = ({ THREE, tools }, k) => {\n const black = tools.material(0x161616)\n tools.ball(k.bodyGroup, 0.05, black, 0, 0.03, 0.08, 1.1, 0.9, 0.6) // breast patch\n addMesh(THREE, k.head, new THREE.BoxGeometry(0.006, 0.006, 0.06).translate(0, 0, -0.03), black, 0, 0.025, -0.01, 0.5, 0, 0) // crest\n tools.ball(k.head, 0.026, black, 0, -0.005, 0.02, 1.05, 0.7, 0.9) // cap\n}\n\n/** The parts the page builds with code for five species (the page's `extra` closures), by species id. */\nexport const QUADRUPED_EXTRAS: Readonly<Record<string, QuadrupedExtra>> = { cow, horse, wild_boar: wildBoar }\nexport const BIRD_EXTRAS: Readonly<Record<string, BirdExtra>> = { chicken, southern_lapwing: southernLapwing }\n","import type { TextureMarker } from '@adatechnology/game-animals'\nimport type * as THREE_NS from 'three'\n\nimport { createStream } from './context'\nimport type { CanvasLike, DocumentLike, Three } from './types'\n\ntype Painter = {\n fillStyle: string\n strokeStyle: string\n lineWidth: number\n fillRect(x: number, y: number, width: number, height: number): void\n beginPath(): void\n arc(x: number, y: number, radius: number, from: number, to: number): void\n stroke(): void\n fill(): void\n}\n\nconst canvasTexture = (THREE: Three, document: DocumentLike, width: number, height: number, draw: (painter: Painter, width: number, height: number) => void): THREE_NS.CanvasTexture => {\n const canvas: CanvasLike = document.createElement('canvas')\n canvas.width = width\n canvas.height = height\n draw(canvas.getContext('2d') as Painter, width, height)\n const texture = new THREE.CanvasTexture(canvas as unknown as HTMLCanvasElement)\n texture.colorSpace = THREE.SRGBColorSpace\n return texture\n}\n\nconst spotsTexture = (THREE: Three, document: DocumentLike, base: string, spot: string): THREE_NS.CanvasTexture =>\n canvasTexture(THREE, document, 256, 256, (painter, width, height) => {\n painter.fillStyle = base\n painter.fillRect(0, 0, width, height)\n const next = createStream(11)\n for (let index = 0; index < 70; index++) {\n const x = next() * width\n const y = next() * height\n const radius = 5 + next() * 7\n painter.strokeStyle = spot\n painter.lineWidth = 3\n painter.beginPath()\n painter.arc(x, y, radius, 0, 7)\n painter.stroke()\n painter.fillStyle = 'rgba(120,70,20,.45)'\n painter.beginPath()\n painter.arc(x, y, radius * 0.6, 0, 7)\n painter.fill()\n }\n for (let index = 0; index < 60; index++) {\n painter.fillStyle = spot\n painter.beginPath()\n painter.arc(next() * width, next() * height, 1.5 + next() * 2.5, 0, 7)\n painter.fill()\n }\n })\n\nconst stripesTexture = (THREE: Three, document: DocumentLike, base: string, stripe: string): THREE_NS.CanvasTexture =>\n canvasTexture(THREE, document, 128, 128, (painter, width) => {\n painter.fillStyle = base\n painter.fillRect(0, 0, width, 128)\n painter.fillStyle = stripe\n for (let index = 0; index < 9; index++) painter.fillRect(0, index * 14 + (index % 2) * 3, width, 4)\n })\n\n/** The coat texture a species spec asks for (cat stripes, jaguar rosettes), painted on a canvas. */\nexport const createCoatTexture = (THREE: Three, document: DocumentLike, marker: TextureMarker): THREE_NS.CanvasTexture =>\n marker.kind === 'stripes' ? stripesTexture(THREE, document, marker.base, marker.stripe) : spotsTexture(THREE, document, marker.base, marker.spot)\n","import type * as THREE_NS from 'three'\n\nimport type { Three } from './types'\n\n/** A body of revolution whose radius follows `profile` along the length, rear to front, closed by a point at each end. */\nexport const profileGeometry = (THREE: Three, length: number, profile: readonly number[], segments?: number): THREE_NS.LatheGeometry => {\n const points = profile.map((radius, index) => new THREE.Vector2(index / (profile.length - 1), radius))\n const curve = new THREE.SplineCurve(points)\n const outline: THREE_NS.Vector2[] = []\n outline.push(new THREE.Vector2(0.001, -length / 2 - 0.0))\n for (let step = 0; step <= 30; step++) {\n const point = curve.getPoint(step / 30)\n outline.push(new THREE.Vector2(Math.max(0.001, point.y), -length / 2 + point.x * length))\n }\n outline.push(new THREE.Vector2(0.001, length / 2))\n return new THREE.LatheGeometry(outline, segments || 22).rotateX(Math.PI / 2)\n}\n","import { quadrupedLayout } from '@adatechnology/game-animals'\nimport type { QuadrupedSpec } from '@adatechnology/game-animals'\nimport type * as THREE_NS from 'three'\n\nimport { addMesh, createRigTools } from './context'\nimport { createCoatTexture } from './textures'\nimport { profileGeometry } from './profile'\nimport type { AnimalRig, DocumentLike, Three } from './types'\nimport type { QuadrupedExtra } from './extras'\n\nconst RIB_COUNT = 4\n\ntype LegDefinition = readonly [number, number, 'F' | 'H']\n\nconst LEG_DEFINITIONS: readonly LegDefinition[] = [\n [1, 1, 'F'],\n [-1, 1, 'F'],\n [1, -1, 'H'],\n [-1, -1, 'H'],\n]\n\n/** The body and the four legs of a quadruped (page: the first half of `quad`). */\nconst buildBodyAndLegs = (THREE: Three, rig: AnimalRig, spec: QuadrupedSpec, main: THREE_NS.MeshStandardMaterial, belly: THREE_NS.MeshStandardMaterial, dark: THREE_NS.MeshStandardMaterial) => {\n const tools = createRigTools(THREE, rig)\n const { L, R: radius } = spec\n const leg = spec.leg\n const layout = quadrupedLayout(spec)\n const { frontHipHeight, hindHipHeight, footHeight, bodyHeight, span } = layout\n const frontAngle1 = leg.fa1 ?? 0.06\n const frontAngle2 = leg.fa2 ?? -0.08\n const hindAngle1 = leg.ha1 ?? -0.32\n const hindAngle2 = leg.ha2 ?? 0.58\n rig.size = layout.size\n const bodyGroup = tools.pivot(rig.trunk, 0, bodyHeight, 0)\n rig.bodyGroup = bodyGroup\n const body = addMesh(THREE, bodyGroup, profileGeometry(THREE, L, spec.prof.map((value) => value * radius)), main)\n body.scale.set(spec.sx || 1, spec.sy || 1, 1)\n body.rotation.x = Math.atan2(hindHipHeight - frontHipHeight, span) * 0.6\n rig.body = body\n const belt = addMesh(THREE, bodyGroup, profileGeometry(THREE, L * 0.8, spec.prof.map((value) => value * 0.8 * radius)), belly)\n belt.scale.set((spec.sx || 1) * 0.92, (spec.sy || 1) * 0.85, 1)\n belt.position.y = -radius * 0.2\n LEG_DEFINITIONS.forEach(([sx, sz, kind]) => {\n const front = kind === 'F'\n const upper = front ? leg.fu : leg.hu\n const lower = front ? leg.fl : leg.hl\n const height = front ? frontHipHeight : hindHipHeight\n const hip = tools.pivot(rig.trunk, sx * radius * (spec.sx || 1) * 0.55, height, (sz * span) / 2, new THREE.Vector3(sx * 0.18, -0.06, sz * 0.12).multiplyScalar(rig.size))\n tools.ball(hip, leg.r * (front ? 1.9 : 2.3), main, 0, -upper * 0.18, front ? 0 : -leg.r * 0.3, 0.75, 1.25, 1)\n tools.joint(hip, leg.r * 1.15, main, 0, 0, 0)\n tools.limb(hip, leg.r * 1.25, leg.r * 0.95, upper, main)\n const knee = tools.pivot(hip, 0, -upper, 0, new THREE.Vector3(0, -0.07 * rig.size, 0))\n tools.joint(knee, leg.r * 0.95, main, 0, 0, 0)\n tools.limb(knee, leg.r * 0.92, leg.r * 0.68, lower, main)\n const foot = tools.pivot(knee, 0, -lower, 0, new THREE.Vector3(0, -0.05 * rig.size, 0))\n if (leg.hoof) {\n tools.joint(foot, leg.r * 0.72, dark, 0, 0, 0)\n addMesh(THREE, foot, new THREE.CylinderGeometry(leg.r * 0.8, leg.r * 1.05, footHeight, 12).translate(0, -footHeight / 2, 0), dark)\n } else {\n tools.joint(foot, leg.r * 0.66, main, 0, 0, 0)\n tools.ball(foot, leg.r * 0.95, leg.dark ? dark : main, 0, -footHeight * 0.55, leg.r * 0.5, 1, 0.62, 1.45)\n }\n rig.legs.push({ hip, knee, foot, isFront: front, angle1: front ? frontAngle1 : hindAngle1, angle2: front ? frontAngle2 : hindAngle2, side: sx })\n })\n return { bodyGroup, bodyHeight }\n}\n\nconst buildHead = (THREE: Three, rig: AnimalRig, spec: QuadrupedSpec, parts: { bodyGroup: THREE_NS.Group; main: THREE_NS.MeshStandardMaterial; belly: THREE_NS.MeshStandardMaterial; nose: THREE_NS.MeshStandardMaterial }) => {\n const tools = createRigTools(THREE, rig)\n const { bodyGroup, main, belly, nose } = parts\n const { L, R: radius } = spec\n const neckSpec = spec.neck\n const headSpec = spec.head\n const neck = tools.pivot(bodyGroup, 0, radius * (spec.sy || 1) * (neckSpec.y ?? 0.35), (L / 2) * (neckSpec.z ?? 0.78), new THREE.Vector3(0, 0.05, 0.12).multiplyScalar(rig.size))\n neck.rotation.x = neckSpec.ang\n rig.neck = neck\n rig.neckAngle = neckSpec.ang\n tools.joint(neck, neckSpec.r * 1.05, main, 0, 0, 0)\n addMesh(THREE, neck, new THREE.CylinderGeometry(neckSpec.r2 || neckSpec.r * 0.85, neckSpec.r, neckSpec.len, 14).translate(0, neckSpec.len / 2, 0), main)\n const head = tools.pivot(neck, 0, neckSpec.len, 0, new THREE.Vector3(0, 0.06, 0.08).multiplyScalar(rig.size))\n head.rotation.x = -neckSpec.ang + (headSpec.down || 0)\n rig.head = head\n rig.headAngle = head.rotation.x\n tools.joint(head, neckSpec.r2 || neckSpec.r * 0.85, main, 0, 0, 0)\n tools.ball(head, headSpec.r, main, 0, headSpec.r * 0.25, headSpec.r * 0.25, headSpec.s[0], headSpec.s[1], headSpec.s[2])\n const snoutZ = headSpec.r * 0.25 + headSpec.r * headSpec.s[2] * 0.55\n const snout = addMesh(THREE, head, new THREE.CylinderGeometry(headSpec.sr2, headSpec.sr1, headSpec.sl, 14).rotateX(Math.PI / 2), headSpec.muzzle ? tools.material(headSpec.muzzle) : main, 0, headSpec.r * (headSpec.sy ?? 0), snoutZ + headSpec.sl / 2)\n snout.scale.set(1, headSpec.sq || 1, 1)\n tools.ball(head, headSpec.sr2 * 1.02, nose, 0, headSpec.r * (headSpec.sy ?? 0) + headSpec.sr2 * 0.1, snoutZ + headSpec.sl, 1, 0.85 * (headSpec.sq || 1), 0.45)\n const jaw = tools.pivot(head, 0, headSpec.r * (headSpec.sy ?? 0) - headSpec.sr1 * 0.55 * (headSpec.sq || 1), snoutZ - headSpec.sr1 * 0.2, new THREE.Vector3(0, -0.04, 0.04).multiplyScalar(rig.size))\n rig.jaw = jaw\n addMesh(THREE, jaw, new THREE.CylinderGeometry(headSpec.sr2 * 0.7, headSpec.sr1 * 0.75, headSpec.sl * 0.92, 12).rotateX(Math.PI / 2).translate(0, 0, headSpec.sl * 0.46), belly).scale.set(1, 0.45, 1)\n const blood = tools.ball(jaw, headSpec.sr2 * 0.9, rig.woundMaterial || (rig.woundMaterial = new THREE.MeshStandardMaterial({ color: 0x6a100c, roughness: 0.3 })), 0, 0.0, headSpec.sl * 0.9, 1.1, 0.6, 0.6)\n tools.eyes(head, headSpec.r * 0.13, headSpec.r * headSpec.s[0] * 0.52, headSpec.r * 0.5, headSpec.r * 0.25 + headSpec.r * headSpec.s[2] * 0.55)\n const ears = spec.ears\n for (const side of [-1, 1]) {\n const earGroup = tools.pivot(head, side * headSpec.r * headSpec.s[0] * 0.55, headSpec.r * (0.25 + headSpec.s[1] * 0.75), headSpec.r * 0.1, new THREE.Vector3(side * 0.04, 0.04, 0).multiplyScalar(rig.size))\n rig.ears.push(earGroup)\n if (ears.t === 'cone') addMesh(THREE, earGroup, new THREE.ConeGeometry(ears.s * 0.45, ears.s, 8).translate(0, ears.s / 2, 0), main, 0, 0, 0, ears.fwd || 0, 0, side * -(ears.tilt ?? 0.3))\n else if (ears.t === 'round') tools.ball(earGroup, ears.s, main, 0, ears.s * 0.4, 0, 1, 1, 0.35)\n else if (ears.t === 'flop') {\n const ear = tools.ball(earGroup, ears.s, main, side * ears.s * 0.9, -ears.s * 0.1, 0, 1.5, 0.45, 0.7)\n ear.rotation.z = side * -0.25\n }\n }\n return { neck, head, blood, neckSpec, headSpec }\n}\n\nconst buildTail = (THREE: Three, rig: AnimalRig, spec: QuadrupedSpec, parts: { bodyGroup: THREE_NS.Group; main: THREE_NS.MeshStandardMaterial; dark: THREE_NS.MeshStandardMaterial }): void => {\n const tools = createRigTools(THREE, rig)\n const tailSpec = spec.tail\n if (!tailSpec || !tailSpec.n) return\n const { bodyGroup, main, dark } = parts\n const { L, R: radius } = spec\n let parent = tools.pivot(bodyGroup, 0, radius * (spec.sy || 1) * (tailSpec.y ?? 0.3), (-L / 2) * 0.95, new THREE.Vector3(0, 0.02, -0.12).multiplyScalar(rig.size))\n parent.rotation.x = tailSpec.ang\n rig.tailRoot = parent\n const segmentLength = tailSpec.len / tailSpec.n\n for (let index = 0; index < tailSpec.n; index++) {\n const radius1 = tailSpec.r * (1 - (index / tailSpec.n) * 0.6)\n const radius2 = tailSpec.r * (1 - ((index + 1) / tailSpec.n) * 0.6)\n tools.limb(parent, radius1, radius2, segmentLength, tailSpec.dark && index === tailSpec.n - 1 ? dark : main)\n tools.joint(parent, radius1, main, 0, 0, 0)\n rig.tail.push(parent)\n if (index < tailSpec.n - 1) {\n const next = tools.pivot(parent, 0, -segmentLength, 0)\n next.rotation.x = tailSpec.curl || 0\n parent = next\n } else if (tailSpec.tuft) tools.ball(parent, tailSpec.r * 2.2, dark, 0, -segmentLength - tailSpec.r, 0, 1, 1.8, 1)\n }\n if (tailSpec.bushy) tools.ball(rig.tail[0] as THREE_NS.Group, tailSpec.r * 2.2, dark, 0, -tailSpec.len * 0.55, 0, 1, (tailSpec.len * 2.2) / tailSpec.r / 6, 1)\n}\n\n/** Wounds, ribs and the blood of the jaw: shown only while the animal is infected (page: the end of `quad`). */\nconst buildInfectionParts = (THREE: Three, rig: AnimalRig, spec: QuadrupedSpec, parts: { bodyGroup: THREE_NS.Group; neck: THREE_NS.Group; blood: THREE_NS.Mesh; neckSpec: QuadrupedSpec['neck'] }): void => {\n const tools = createRigTools(THREE, rig)\n const { bodyGroup, neck, blood, neckSpec } = parts\n const { L, R: radius } = spec\n const sideX = radius * (spec.sx || 1)\n const sideY = radius * (spec.sy || 1)\n const list: THREE_NS.Object3D[] = [blood]\n const wound = (parent: THREE_NS.Object3D, size: number, x: number, y: number, z: number, sx: number, sy: number, sz: number): THREE_NS.Mesh =>\n tools.ball(parent, size, rig.woundMaterial || (rig.woundMaterial = new THREE.MeshStandardMaterial({ color: 0x5a120e, roughness: 0.45 })), x, y, z, sx || 1, sy || 1, sz || 1)\n list.push(wound(bodyGroup, radius * 0.28, sideX * 0.92, sideY * 0.15, L * 0.05, 0.35, 1, 1.3))\n list.push(wound(bodyGroup, radius * 0.2, -sideX * 0.9, sideY * 0.3, -L * 0.2, 0.35, 1, 1.1))\n list.push(wound(neck, neckSpec.r * 0.5, neckSpec.r * 0.85, neckSpec.len * 0.5, 0, 0.4, 1, 1))\n const bone = tools.material(0xd9cfb8, { roughness: 0.6 })\n for (let index = 0; index < RIB_COUNT; index++) {\n const rib = addMesh(THREE, bodyGroup, new THREE.TorusGeometry(radius * 0.95, radius * 0.035, 6, 16, Math.PI * 0.42), bone, sideX * 0.12, sideY * 0.05, L * 0.12 - index * L * 0.07, 0, Math.PI / 2, -0.3)\n rib.scale.set(1, 1.02 * (spec.sy || 1), spec.sx || 1)\n list.push(rib)\n }\n rig.infectionParts.push(...list)\n list.forEach((part) => (part.visible = false))\n}\n\n/** Builds a quadruped: body, four legs on hip-knee-foot pivots, neck, head with jaw and eyes, ears, tail, the species' extra parts, and the infection parts. */\nexport const buildQuadruped = (THREE: Three, rig: AnimalRig, options: { document: DocumentLike; extra?: QuadrupedExtra }): AnimalRig => {\n const spec = rig.species.body as QuadrupedSpec\n const tools = createRigTools(THREE, rig)\n const colors = spec.c\n const texture = spec.texture ? createCoatTexture(THREE, options.document, spec.texture) : undefined\n const main = tools.material(texture ? 0xffffff : colors.main, texture ? { map: texture } : undefined)\n const belly = tools.material(colors.belly)\n const dark = tools.material(colors.dark)\n const nose = tools.material(colors.nose || colors.dark, { roughness: 0.4 })\n rig.main = main\n rig.dark = dark\n const { bodyGroup, bodyHeight } = buildBodyAndLegs(THREE, rig, spec, main, belly, dark)\n const { neck, head, blood, neckSpec, headSpec } = buildHead(THREE, rig, spec, { bodyGroup, main, belly, nose })\n buildTail(THREE, rig, spec, { bodyGroup, main, dark })\n options.extra?.({ THREE, rig, tools }, { bodyGroup, head, neck, main, belly, dark, L: spec.L, Rb: spec.R, bodyY: bodyHeight, hd: headSpec, nk: neckSpec })\n if (rig.species.outcome === 'turns') buildInfectionParts(THREE, rig, spec, { bodyGroup, neck, blood, neckSpec })\n else blood.visible = false\n return rig\n}\n","import { addMesh, createRigTools } from './context'\nimport type { AnimalRig, Three } from './types'\n\nconst SEGMENT_COUNT = 34\nconst SEGMENT_LENGTH = 0.042\n\n/** Builds a snake: 34 spheres on their own groups along the trunk (the animation lays them on a path), a head with a jaw and a tongue. */\nexport const buildSnake = (THREE: Three, rig: AnimalRig): AnimalRig => {\n const tools = createRigTools(THREE, rig)\n rig.size = 0.5\n rig.segmentLength = SEGMENT_LENGTH\n rig.segmentCount = SEGMENT_COUNT\n const primary = tools.material(0x7a6a4a)\n const secondary = tools.material(0x3a2e22)\n const belly = tools.material(0xb8a888)\n rig.main = primary\n for (let index = 0; index < SEGMENT_COUNT; index++) {\n const t = index / (SEGMENT_COUNT - 1)\n const radius = 0.012 + 0.03 * Math.sin(Math.min(1, t * 1.15) * Math.PI) * (t < 0.1 ? 0.75 + t * 2.5 : 1) + (t < 0.05 ? 0.012 : 0)\n const material = index % 4 === 1 || index % 4 === 2 ? secondary : primary\n const group = new THREE.Group()\n rig.trunk.add(group)\n const sphere = tools.ball(group, radius, material, 0, radius, 0, 1, 0.78, 1.5)\n sphere.userData.joint = index % 3 === 0 ? 1 : 0\n if (index % 3 === 0) rig.joints.push(sphere)\n rig.segments.push({ group, radius })\n }\n const head = new THREE.Group()\n rig.trunk.add(head)\n rig.head = head\n tools.ball(head, 0.032, primary, 0, 0.03, 0.025, 1.25, 0.6, 1.45)\n tools.ball(head, 0.024, secondary, 0, 0.04, 0.0, 1.3, 0.4, 0.9)\n tools.ball(head, 0.02, primary, 0, 0.028, 0.06, 0.85, 0.55, 1.1)\n const jaw = new THREE.Group()\n jaw.position.set(0, 0.018, 0.0)\n head.add(jaw)\n rig.jaw = jaw\n tools.ball(jaw, 0.026, belly, 0, 0, 0.035, 1.1, 0.35, 1.5)\n tools.eyes(head, 0.006, 0.03, 0.04, 0.045)\n rig.tongue = addMesh(THREE, head, new THREE.BoxGeometry(0.004, 0.002, 0.05), tools.material(0xb02020), 0, 0.024, 0.1)\n return rig\n}\n","import type { AnimalVisualState } from '@adatechnology/game-animals'\nimport type * as THREE_NS from 'three'\n\nimport type { AnimalRig, Three } from './types'\n\nconst INFECTED_TINT = 0x7d8a66\nconst POOL_COLOR = 0x3a0806\n\n/**\n * Applies a visual state (page: `setInfected`, with the dead look for every species): infected greys the coat, glows the eyes, shows the wounds and hides\n * the first ear; dead lets the animation lay the body down and shows a blood pool at `home`. The pool goes under `rig.root.parent`; with no parent yet,\n * it is created hidden from the scene and `rig.pool` is for the caller to add.\n */\nexport const applyVisualState = (THREE: Three, rig: AnimalRig, state: AnimalVisualState): void => {\n rig.infected = state === 'infected'\n rig.dead = state === 'dead'\n const tint = new THREE.Color(INFECTED_TINT)\n rig.materials.forEach((material) => {\n if (material === rig.eyeMaterial || material === rig.woundMaterial) return\n material.color.copy(material.userData.base as THREE_NS.Color)\n if (rig.infected) material.color.lerp(tint, 0.45).multiplyScalar(0.72)\n })\n const eye = rig.eyeMaterial\n if (eye) {\n if (rig.infected) {\n eye.color.set(0xdcd8b0)\n eye.emissive = new THREE.Color(0x8a8660)\n eye.emissiveIntensity = 0.8\n } else {\n eye.color.copy(eye.userData.base as THREE_NS.Color)\n eye.emissive = new THREE.Color(0)\n }\n }\n rig.infectionParts.forEach((part) => (part.visible = rig.infected))\n const firstEar = rig.ears[0]\n if (firstEar) firstEar.visible = !rig.infected\n if (!rig.dead) {\n rig.root.rotation.z = 0\n rig.root.position.y = 0\n }\n if (rig.pool) {\n rig.pool.visible = rig.dead\n return\n }\n if (!rig.dead) return\n const pool = new THREE.Mesh(new THREE.CircleGeometry(1, 22).rotateX(-Math.PI / 2), new THREE.MeshStandardMaterial({ color: POOL_COLOR, roughness: 0.25 }))\n pool.position.set(rig.home.x, 0.005, rig.home.z + 0.1)\n pool.receiveShadow = true\n pool.scale.setScalar(rig.size * 0.45)\n rig.root.parent?.add(pool)\n rig.pool = pool\n}\n","import type * as THREE_NS from 'three'\n\nimport type { AnimalRig } from './types'\n\n/** The \"joints\" view (page: `setJoint`): the coat turns see-through and every joint ball shows in `jointMaterial`. */\nexport const setJointView = (rig: AnimalRig, on: boolean, jointMaterial: THREE_NS.Material): void => {\n rig.materials.forEach((material) => {\n if (material === rig.eyeMaterial) return\n material.transparent = on\n material.opacity = on ? 0.28 : 1\n material.depthWrite = !on\n material.needsUpdate = true\n })\n rig.joints.forEach((joint) => {\n if (on) joint.userData.restMaterial ??= joint.material\n joint.material = on ? jointMaterial : (joint.userData.restMaterial as THREE_NS.Material)\n joint.renderOrder = on ? 2 : 0\n })\n}\n\n/** The \"split\" view (page: `setSplit`): sets where each pivot should go, the rest position or the rest plus its offset; `updateSplit` moves it there. */\nexport const setSplit = (rig: AnimalRig, on: boolean): void => {\n rig.pivots.forEach((pivot) => {\n pivot.group.userData.target = on ? pivot.rest.clone().add(pivot.offset) : pivot.rest.clone()\n })\n}\n\n/** Moves each pivot toward its split target (the page does it in the frame loop: a lerp of 5 per second). */\nexport const updateSplit = (rig: AnimalRig, delta: number): void => {\n rig.pivots.forEach((pivot) => {\n const target = pivot.group.userData.target as THREE_NS.Vector3 | undefined\n if (target) pivot.group.position.lerp(target, Math.min(1, delta * 5))\n })\n}\n","const FNV_OFFSET = 0x811c9dc5\nconst FNV_PRIME = 0x01000193\nconst MIX = 0x9e3779b1\n\n/** Deterministic mulberry32 generator; floats in [0, 1). The same generator the golden tool seeds `Math.random` with, so a team routine and its recipe draw the same numbers. */\nexport function createRng(seed: number): () => number {\n let state = seed >>> 0\n return () => {\n state = (state + 0x6d2b79f5) >>> 0\n let mixed = Math.imul(state ^ (state >>> 15), state | 1)\n mixed ^= mixed + Math.imul(mixed ^ (mixed >>> 7), mixed | 61)\n return ((mixed ^ (mixed >>> 14)) >>> 0) / 4294967296\n }\n}\n\n/** The seed of the stream called `name` (or numbered `name`): one seed gives each channel its own sequence, so one subject's sounds never shift another's. */\nexport function streamSeed(seed: number, name: string | number): number {\n const label = String(name)\n let hash = FNV_OFFSET\n for (let index = 0; index < label.length; index++) hash = Math.imul(hash ^ label.charCodeAt(index), FNV_PRIME) >>> 0\n return Math.imul((seed >>> 0) ^ hash, MIX) >>> 0\n}\n\nexport function streamFor(seed: number, name: string | number): () => number {\n return createRng(streamSeed(seed, name))\n}\n\n/** `min + random × (max − min)`: the form every team routine uses (`95 + Math.random() * 40`). */\nexport function drawBetween(random: () => number, min: number, max: number): number {\n return min + random() * (max - min)\n}\n","/**\n * Sound recipes are data. A recipe says which nodes a sound has and which numbers drive them; ranges are drawn when the sound is planned, in\n * the order the fields are written (see `planSound`), so a recipe ported from a team routine draws its random numbers in the routine's order.\n */\n\nexport const BUS_IDS = ['weapons', 'voices', 'world', 'ambience', 'weather', 'ui'] as const\nexport type BusId = (typeof BUS_IDS)[number]\n\n/** A named number: from `PlayOptions.params` or the recipe's `vars`, times `scale`, plus `add`. */\nexport type ParamRef = { readonly param: string; readonly scale?: number; readonly add?: number }\n\n/** A number, a `[min, max]` drawn uniformly (`min + random × (max − min)`), or a named number. */\nexport type Range = number | readonly [min: number, max: number] | ParamRef\n\n/** Seconds from the layer start: a range, the layer `'duration'`, or `'0.6d'` (0.6 × the layer duration). */\nexport type Time = Range | 'duration' | `${number}d`\n\nexport type Curve = 'linear' | 'exp'\n\n/**\n * How an AudioParam moves:\n * - `{ set }` assigns `.value`;\n * - `{ set, to, curve, over }` assigns `.value`, then ramps to `to` at `over`;\n * - `{ from }` calls `setValueAtTime(from)` at the start; with `to`, `curve` and `over` it then ramps to `to` (`'from'` is the value drawn for `from`: the art scene ramps a tone back to its own start when it gives no end).\n */\nexport type Automation =\n | { readonly set: Range; readonly to?: Range | 'from'; readonly curve?: Curve; readonly over?: Time }\n | { readonly from: Range; readonly to?: Range | 'from'; readonly curve?: Curve; readonly over?: Time }\n\n/**\n * One gain point: time (seconds, or `'0.6d'`), value and how it is reached (`set` = `setValueAtTime`, or a ramp). The value is multiplied by the\n * layer level (volume, distance and layer gain) except when the fourth item is `'fixed'` (the floor a ramp falls to, such as 0.001).\n */\nexport type EnvelopePoint =\n | readonly [time: number | `${number}d`, value: number, curve: 'set' | Curve]\n | readonly [time: number | `${number}d`, value: number, curve: 'set' | Curve, fixed: 'fixed']\nexport type Envelope = readonly EnvelopePoint[]\n\nexport type FilterSpec = {\n readonly type: BiquadFilterType\n readonly frequency: Automation\n readonly q?: Range\n /** The BiquadFilter gain (shelf and peaking types). */\n readonly gain?: Range\n /** A gain node after the filter, for a parallel bank where each band has its own level (the game's scream formants). */\n readonly level?: Range\n}\n\nexport type FilterBank = { readonly mode: 'serial' | 'parallel'; readonly bank: readonly FilterSpec[] }\n\n/** An LFO. `depth` is absolute, or a factor of the source start frequency. With `base`, gain modulation goes through its own gain stage (the game rotor). */\nexport type Modulator = {\n readonly target: 'gain' | 'frequency'\n /** Left out, the oscillator keeps its default (sine) and no type is set. */\n readonly wave?: OscillatorType\n readonly rate: Range\n readonly depth: Range | { readonly scaleOf: 'frequency'; readonly factor: number }\n readonly base?: number\n}\n\n/**\n * Noise from the engine's seeded buffer. `offset` is where playback starts in it: `'random'` (random × `offsetScale`, default 1), `'fit'`\n * (random × (buffer length − duration)) or seconds. `loop` sets the source to loop (the art scene sets it on every noise source).\n */\nexport type NoiseSource = {\n readonly kind: 'noise'\n readonly offset?: 'random' | 'fit' | number\n readonly offsetScale?: number\n readonly loop?: boolean\n}\n\nexport type OscSource = { readonly kind: 'osc'; readonly wave: OscillatorType; readonly frequency: Automation }\n\n/** The art scene's `voice`: an oscillator with vibrato through formant bands, plus a band of breath noise, summed and boosted. */\nexport type FormantVoiceSource = {\n readonly kind: 'formant_voice'\n readonly wave: OscillatorType\n readonly frequency: Automation\n /** Vibrato rate, and depth as a factor of the start frequency. */\n readonly vibrato: { readonly rate: Range; readonly depth: number }\n readonly formants: readonly { readonly frequency: Range; readonly q: number }[]\n readonly breath: { readonly frequency: Range; readonly q: number; readonly gain: Range }\n readonly mix: number\n}\n\nexport type LayerSource = NoiseSource | OscSource | FormantVoiceSource\n\nexport type Layer = {\n /** Start, seconds after the sound starts. */\n readonly at?: Range\n /** Overrides the recipe duration for this layer. */\n readonly duration?: Range\n /** Seconds the source keeps running after the duration (the `+ 0.05` of the team routines). Default: the recipe's, else 0.05. */\n readonly tail?: number\n /** The layer played `count` times, `spacing` seconds apart (the game's animal calls). */\n readonly repeat?: { readonly count: number; readonly spacing: number }\n /** Layer level before distance. Default 1. */\n readonly gain?: Range\n /** Send to the reverb, 0..1 or more (the art scene uses up to 1.2). */\n readonly wet?: number\n readonly source: LayerSource\n readonly filters?: FilterBank\n readonly modulators?: readonly Modulator[]\n readonly envelope: Envelope\n}\n\n/**\n * Distance falloff of a sound's level:\n * - `inverse_square`: `volume / (1 + d² × k)`; `cutoffK` also divides every lowpass cutoff by `1 + d × cutoffK` (the game's muffling);\n * - `linear`: `max(0, 1 − d / range)`, silent at or below `minGain`;\n * - `none`: no distance.\n * `scale` multiplies the level (the game's boom is 1.4 at the source), and below `minGain` the sound is not played.\n */\nexport type Falloff =\n | { readonly model: 'inverse_square'; readonly k: number; readonly minGain?: number; readonly cutoffK?: number; readonly scale?: number }\n | { readonly model: 'linear'; readonly range: number; readonly minGain?: number; readonly scale?: number }\n | { readonly model: 'none' }\n\n/**\n * The order the realizer creates and wires the nodes in. Each one is the order of the team routine it reproduces, so the trace of the SDK\n * matches the trace of the team code node for node:\n * - `layered` (default): the art scene's `sNoise`, `sTone` and `voice`; every layer has its own output stage (distance gain, pan, reverb send);\n * - `burst`: the game's `sfx` (jogo L62089);\n * - `scream`: the game's `grito` (L62128);\n * - `groan`: the game's `voz` (L62143);\n * - `boom`: the game's `boom` (L68278);\n * - `static`: the game's `chiado` (L69998);\n * - `chord`: the game's `buzina` (L70272);\n * - `calls`: the game's `bichoSom` (L66625);\n * - `siren`: the art scene's `siren` (L1369);\n * - `weapon`: the weapon audio of the kit (`weapons-three`): per layer a filtered noise burst or an oscillator, then its own gain, chained to the bus.\n * Every order but `layered` puts the level into the envelope and connects straight to the bus.\n */\nexport const BUILD_ORDERS = ['layered', 'burst', 'scream', 'groan', 'boom', 'static', 'chord', 'calls', 'siren', 'weapon'] as const\nexport type BuildOrder = (typeof BUILD_ORDERS)[number]\n\nexport type SoundRecipe = {\n readonly id: string\n readonly bus: BusId\n readonly order?: BuildOrder\n /** Named numbers drawn first, in the order written; layers read them with `{ param }`. */\n readonly vars?: Readonly<Record<string, Range>>\n readonly duration?: Range\n readonly tail?: number\n readonly layers: readonly Layer[]\n readonly falloff?: Falloff\n /** Stereo pan from the listener (the art scene's `panFor`). Only the `layered` order has an output stage to pan. */\n readonly pan?: boolean\n /** Voice group for the limiter (`undead_voice`...). */\n readonly group?: string\n /** Limiter priority; default from the bus (weapons > voices > world > ambience). */\n readonly priority?: number\n /**\n * The team routine creates nodes before it checks the level (the game's `bichoSom` creates its gain and filter first): a silent sound is kept as\n * a plan with `silent: true` so the realizer repeats those nodes. Off by default.\n */\n readonly keepSilent?: boolean\n /** Where the numbers come from: `\"jogo:L62128\"`, `\"cena:L1342\"`, or `\"sdk\"` for values the kit chose. */\n readonly sources: readonly string[]\n}\n\n/**\n * The order the loop realizer creates the nodes in:\n * - `game_loop`: the game's `loopSet` (L62109): gain and filter first, nodes created the first time the volume passes `startAbove`, gain from 0;\n * - `chain_loop`: the art scene's loops in `initAudio` (L1314–1316): source, filters, gain at its starting volume, created when the loop opens.\n */\nexport type LoopOrder = 'game_loop' | 'chain_loop'\n\nexport type LoopRecipe = {\n readonly id: string\n readonly bus: BusId\n readonly order?: LoopOrder\n /** An oscillator loop may leave the frequency out: it starts at the default and `set({ frequency })` glides it (the game's motor). */\n readonly source: (NoiseSource & { readonly loop: true }) | { readonly kind: 'osc'; readonly wave: OscillatorType; readonly frequency?: Automation; readonly loop: true }\n readonly filters?: FilterBank\n readonly modulators?: readonly Modulator[]\n /** `setTargetAtTime` time constants (jogo L62125–62126: 0.15 for the volume, 0.08 for the frequency). */\n readonly smoothing?: { readonly volume: number; readonly frequency: number }\n /** Nodes are created the first time the volume reaches this (jogo L62112: 0.002). */\n readonly startAbove?: number\n /** Volume the gain starts at when the loop opens (`chain_loop`). */\n readonly volume?: number\n readonly sources: readonly string[]\n}\n\nexport type SoundBank = {\n readonly id: string\n readonly sounds?: readonly SoundRecipe[]\n readonly loops?: readonly LoopRecipe[]\n}\n","import type { Falloff } from './recipe'\n\n/** The distance curves of the team routines, by name. Each recipe carries its own; these are the values to copy. */\nexport const DISTANCE_CURVES = {\n /** The game's `sfx`: `vol / (1 + d² × 0.004)`, cutoff `/ (1 + d × 0.01)`, silent below 0.01 (jogo L62091–62092, L62101). */\n game_sfx: { model: 'inverse_square', k: 0.004, cutoffK: 0.01, minGain: 0.01 },\n /** The game's `grito`: `1 / (1 + d² × 0.006)`, silent below 0.02 (jogo L62129–62130). */\n game_scream: { model: 'inverse_square', k: 0.006, minGain: 0.02 },\n /** The game's `voz`: `1 / (1 + d² × 0.01)`, silent below 0.02 (jogo L62146–62147). */\n game_voice: { model: 'inverse_square', k: 0.01, minGain: 0.02 },\n /** The game's `boom`: `1.4 / (1 + d² × 0.002)`, never silent (jogo L68280). */\n game_boom: { model: 'inverse_square', k: 0.002, scale: 1.4 },\n /** The art scene's `outAt`: `vol / (1 + d² × 0.008)` (cena L1320). */\n art_scene: { model: 'inverse_square', k: 0.008 },\n /** The game's animal calls: `max(0, 1 − d / 70) × 0.25`, silent at or below 0.005 (jogo L66627–66628). */\n animals: { model: 'linear', range: 70, scale: 0.25, minGain: 0.005 },\n} as const satisfies Readonly<Record<string, Falloff>>\n\nexport type DistanceCurveName = keyof typeof DISTANCE_CURVES\n\n/** The level at `distance` for a sound at `volume`, before the recipe's envelope. */\nexport function falloffGain(falloff: Falloff | undefined, volume: number, distance: number): number {\n if (!falloff || falloff.model === 'none') return volume\n if (falloff.model === 'linear') {\n const level = Math.max(0, 1 - distance / falloff.range) * volume\n return falloff.scale === undefined ? level : level * falloff.scale\n }\n const level = volume / (1 + distance * distance * falloff.k)\n return falloff.scale === undefined ? level : falloff.scale * level\n}\n\n/** Whether a level is too quiet to play: below `minGain` (the game's `att < 0.01`), or at or below it for `linear` (the animal calls' `v <= 0.005`). */\nexport function isSilent(falloff: Falloff | undefined, gain: number): boolean {\n if (!falloff || falloff.model === 'none' || falloff.minGain === undefined) return false\n return falloff.model === 'linear' ? gain <= falloff.minGain : gain < falloff.minGain\n}\n\n/** What every lowpass cutoff is divided by at `distance` (1 when the curve has no muffling). */\nexport function cutoffDivisor(falloff: Falloff | undefined, distance: number): number {\n if (!falloff || falloff.model !== 'inverse_square' || falloff.cutoffK === undefined) return 1\n return 1 + distance * falloff.cutoffK\n}\n","import type { Vec3 } from '@adatechnology/game-contracts'\n\n/** Where the ears are. `yaw` turns around y like a three.js camera: the right ear points at `(cos yaw, 0, −sin yaw)`. */\nexport type Listener = { readonly position: Vec3; readonly yaw: number }\n\nexport type DistanceMode = 'xz' | '3d'\n\nexport const DEFAULT_LISTENER: Listener = { position: [0, 0, 0], yaw: 0 }\n\n/** The art scene clamps the pan to ±0.85 and never divides by less than 2 m (cena L1319). */\nexport const PAN_LIMIT = 0.85\nexport const PAN_MIN_DISTANCE = 2\n\n/** Distance from the listener: on the ground (`xz`, the teams' choice) or in 3D. */\nexport function listenerDistance(listener: Listener, position: Vec3, mode: DistanceMode = 'xz'): number {\n const dx = position[0] - listener.position[0]\n const dz = position[2] - listener.position[2]\n if (mode === 'xz') return Math.hypot(dx, dz)\n return Math.hypot(dx, position[1] - listener.position[1], dz)\n}\n\n/** The art scene's `panFor`: the side the sound is on, −0.85 (left) to 0.85 (right). No HRTF, for cost. */\nexport function panFor(listener: Listener, position: Vec3): number {\n const rightX = Math.cos(listener.yaw)\n const rightZ = -Math.sin(listener.yaw)\n const dx = position[0] - listener.position[0]\n const dz = position[2] - listener.position[2]\n const distance = Math.hypot(dx, dz) || 1\n const pan = (dx * rightX + dz * rightZ) / Math.max(distance, PAN_MIN_DISTANCE)\n return pan < -PAN_LIMIT ? -PAN_LIMIT : pan > PAN_LIMIT ? PAN_LIMIT : pan\n}\n","{\n \"undead_voice\": {\n \"maxConcurrent\": 5,\n \"cooldown\": 0.25,\n \"maxDistance\": 40,\n \"sources\": [\n \"cena:L1342 `AU.groans > 4` refuses, so five play at once\",\n \"jogo:L66986 one groan every 0.25 s (`groanBudget`), only nearer than 40 m\",\n \"jogo:L70472 the budget counts down by the frame time\"\n ]\n }\n}\n","import voiceGroupsJson from './data/voice-groups.json'\nimport type { BusId } from './recipe'\n\n/**\n * Rules for a group of voices (`undead_voice`...). A request passes when every rule passes:\n * - `maxConcurrent`: at most this many of the group play at once (`steal` replaces the group's oldest instead of refusing);\n * - `cooldown`: seconds after an accepted voice before the next one (the game's `groanBudget`, jogo L66986 and L70472);\n * - `maxDistance`: only voices nearer than this (the game's `d < 40`).\n */\nexport type VoiceGroup = {\n readonly maxConcurrent?: number\n readonly cooldown?: number\n readonly maxDistance?: number\n readonly steal?: boolean\n readonly sources?: readonly string[]\n}\n\n/** `undead_voice`: the art scene's five at once with the game's 0.25 s cooldown and 40 m. */\nexport const VOICE_GROUPS: Readonly<Record<string, VoiceGroup>> = voiceGroupsJson\n\n/** Q7: weapons > voices > world > ambience; the interface above all, weather with the ambience. */\nexport const BUS_PRIORITY: Readonly<Record<BusId, number>> = { ui: 5, weapons: 4, voices: 3, world: 2, weather: 1, ambience: 1 }\nexport const DEFAULT_MAX_VOICES = 32\n\nexport function busPriority(bus: BusId): number {\n return BUS_PRIORITY[bus]\n}\n\nexport type VoiceRequest = {\n readonly group?: string\n readonly priority: number\n /** Planned level; the global cap drops the lowest `priority × gain`. */\n readonly gain: number\n readonly distance?: number\n /** Seconds the voice lasts. */\n readonly length: number\n}\n\nexport type VoiceRefusal = 'cooldown' | 'distance' | 'group_full' | 'global_full'\n\nexport type VoiceDecision =\n | { readonly accepted: true; readonly id: number; readonly evicted: readonly number[] }\n | { readonly accepted: false; readonly reason: VoiceRefusal }\n\nexport type VoiceStats = { readonly active: number; readonly peak: number; readonly dropped: number; readonly maxVoices: number }\n\nexport type VoiceManager = {\n request(request: VoiceRequest): VoiceDecision\n /** Ends a voice early (stopped by the host). */\n release(id: number): void\n /** Sets how long an accepted voice lasts (the engine admits a voice before its random length is drawn). */\n setLength(id: number, seconds: number): void\n /** Moves time on: cooldowns count down and finished voices leave. Returns the ids that ended. */\n advance(dt: number): number[]\n setGroup(id: string, group: VoiceGroup | undefined): void\n stats(): VoiceStats\n}\n\nexport type VoiceManagerOptions = {\n /** Default `VOICE_GROUPS`. */\n readonly groups?: Readonly<Record<string, VoiceGroup>>\n readonly maxVoices?: number\n}\n\ntype ActiveVoice = { readonly id: number; readonly group?: string; readonly score: number; remaining: number }\n\n/** The voice limiter: per-group rules, a global cap and priority dropping. Pure and time-driven (`advance`), so goldens script it frame by frame. */\nexport function createVoiceManager(options: VoiceManagerOptions = {}): VoiceManager {\n const groups = new Map(Object.entries(options.groups ?? VOICE_GROUPS))\n const maxVoices = options.maxVoices ?? DEFAULT_MAX_VOICES\n const budgets = new Map<string, number>()\n let voices: ActiveVoice[] = []\n let nextId = 1\n let peak = 0\n let dropped = 0\n\n const refuse = (reason: VoiceRefusal): VoiceDecision => {\n dropped += 1\n return { accepted: false, reason }\n }\n\n const groupCheck = (request: VoiceRequest, evicted: number[]): VoiceRefusal | undefined => {\n const group = request.group === undefined ? undefined : groups.get(request.group)\n if (!group) return undefined\n if (group.maxDistance !== undefined && !((request.distance ?? 0) < group.maxDistance)) return 'distance'\n if (group.cooldown !== undefined && (budgets.get(request.group as string) ?? 0) > 0) return 'cooldown'\n const members = voices.filter((voice) => voice.group === request.group)\n if (group.maxConcurrent === undefined || members.length < group.maxConcurrent) return undefined\n if (!group.steal) return 'group_full'\n evicted.push((members[0] as ActiveVoice).id)\n return undefined\n }\n\n return {\n request(request) {\n const evicted: number[] = []\n const refusal = groupCheck(request, evicted)\n if (refusal) return refuse(refusal)\n const score = request.priority * request.gain\n const remaining = voices.filter((voice) => !evicted.includes(voice.id))\n if (remaining.length >= maxVoices) {\n const lowest = remaining.reduce((low, voice) => (voice.score < low.score ? voice : low))\n if (score <= lowest.score) return refuse('global_full')\n evicted.push(lowest.id)\n dropped += 1\n }\n const group = request.group === undefined ? undefined : groups.get(request.group)\n if (group?.cooldown !== undefined) budgets.set(request.group as string, group.cooldown)\n const id = nextId++\n voices = [...voices.filter((voice) => !evicted.includes(voice.id)), { id, ...(request.group === undefined ? {} : { group: request.group }), score, remaining: request.length }]\n peak = Math.max(peak, voices.length)\n return { accepted: true, id, evicted }\n },\n release(id) {\n voices = voices.filter((voice) => voice.id !== id)\n },\n setLength(id, seconds) {\n const voice = voices.find((entry) => entry.id === id)\n if (voice) voice.remaining = seconds\n },\n advance(dt) {\n for (const [group, budget] of budgets) budgets.set(group, budget - dt)\n const ended: number[] = []\n for (const voice of voices) {\n voice.remaining -= dt\n if (voice.remaining <= 0) ended.push(voice.id)\n }\n if (ended.length > 0) voices = voices.filter((voice) => voice.remaining > 0)\n return ended\n },\n setGroup(id, group) {\n if (group) groups.set(id, group)\n else groups.delete(id)\n },\n stats: () => ({ active: voices.length, peak, dropped, maxVoices }),\n }\n}\n","import type { Vec3 } from '@adatechnology/game-contracts'\n\nimport { cutoffDivisor, falloffGain, isSilent } from './falloff'\nimport { DEFAULT_LISTENER, listenerDistance, panFor } from './listener'\nimport type { DistanceMode, Listener } from './listener'\nimport type { Automation, BuildOrder, BusId, Curve, Envelope, FilterSpec, Layer, LayerSource, Modulator, Range, SoundRecipe, Time } from './recipe'\nimport { createRng } from './rng'\nimport { busPriority } from './voices'\n\n/** How an AudioParam moves, with every number resolved and every time in seconds from the layer start. */\nexport type PlannedAutomation = {\n /** Assigned to `.value`. */\n readonly value?: number\n /** `setValueAtTime(from)` at the layer start. */\n readonly from?: number\n /** Ramp target, reached `at` seconds after the layer start. */\n readonly to?: number\n readonly curve?: Curve\n readonly at?: number\n}\n\nexport type PlannedFilter = {\n readonly type: BiquadFilterType\n readonly frequency: PlannedAutomation\n readonly q?: number\n readonly gain?: number\n readonly level?: number\n}\n\nexport type PlannedModulator = {\n readonly target: 'gain' | 'frequency'\n readonly wave?: OscillatorType\n readonly rate: number\n readonly depth: number\n readonly base?: number\n}\n\nexport type PlannedSource =\n | { readonly kind: 'noise'; readonly offset: number; readonly loop: boolean }\n | { readonly kind: 'osc'; readonly wave: OscillatorType; readonly frequency: PlannedAutomation }\n | {\n readonly kind: 'formant_voice'\n readonly wave: OscillatorType\n readonly frequency: PlannedAutomation\n readonly vibrato: { readonly rate: number; readonly depth: number }\n readonly formants: readonly { readonly frequency: number; readonly q: number }[]\n readonly breath: { readonly frequency: number; readonly q: number; readonly gain: number; readonly offset: number }\n readonly mix: number\n }\n\nexport type PlannedEnvelopePoint = { readonly time: number; readonly value: number; readonly curve: 'set' | Curve }\n\nexport type PlannedLayer = {\n readonly at: number\n readonly duration: number\n readonly tail: number\n /** Layer gain × the sound's level: in the envelope for every order but `layered`, in the output stage for `layered`. */\n readonly level: number\n readonly wet?: number\n readonly source: PlannedSource\n readonly filters?: { readonly mode: 'serial' | 'parallel'; readonly bank: readonly PlannedFilter[] }\n readonly modulators: readonly PlannedModulator[]\n readonly envelope: readonly PlannedEnvelopePoint[]\n}\n\n/** A sound with every number decided: what the realizer builds, what goldens compare and what the bench sheet prints. */\nexport type SoundPlan = {\n readonly id: string\n readonly bus: BusId\n readonly order: BuildOrder\n readonly group?: string\n readonly priority: number\n /** The level from volume and distance, before layer gains. */\n readonly level: number\n readonly distance: number\n readonly pan?: number\n /** Too quiet to hear, kept only for a recipe with `keepSilent`. */\n readonly silent: boolean\n /** Seconds until the last source stops. */\n readonly length: number\n readonly layers: readonly PlannedLayer[]\n}\n\nexport type PlanInput = {\n /** Where the numbers are drawn from. Default: a fresh stream with seed 1. */\n readonly random?: () => number\n readonly volume?: number\n /** Distance in meters; overrides `position`. */\n readonly distance?: number\n readonly position?: Vec3\n readonly listener?: Listener\n readonly distanceMode?: DistanceMode\n readonly params?: Readonly<Record<string, number>>\n /** 0..1 from the host's occlusion callback: gain × (1 − 0.5 × o), lowpass cutoffs × (1 − 0.6 × o). */\n readonly occlusion?: number\n /** Length of the noise buffer, for `offset: 'fit'`. Default 1.5. */\n readonly noiseSeconds?: number\n}\n\nconst DEFAULT_TAIL = 0.05\nconst DEFAULT_NOISE_SECONDS = 1.5\nconst OCCLUSION_GAIN = 0.5\nconst OCCLUSION_CUTOFF = 0.6\n\ntype Scope = { readonly random: () => number; readonly params: Record<string, number> }\n\nfunction isParamRef(range: Range): range is Extract<Range, { readonly param: string }> {\n return typeof range === 'object' && !Array.isArray(range)\n}\n\nfunction draw(range: Range, scope: Scope): number {\n if (typeof range === 'number') return range\n if (isParamRef(range)) {\n const value = scope.params[range.param]\n if (value === undefined) throw new Error(`game-audio: the recipe reads the param \"${range.param}\", which was not given`)\n return value * (range.scale ?? 1) + (range.add ?? 0)\n }\n const [min, max] = range as readonly [number, number]\n return min + scope.random() * (max - min)\n}\n\n// a time is drawn now when it is a range; 'duration' and '0.6d' wait for the layer duration\ntype PendingTime = number | 'duration' | `${number}d`\n\nfunction drawTime(time: Time, scope: Scope): PendingTime {\n if (time === 'duration' || (typeof time === 'string' && time.endsWith('d'))) return time as PendingTime\n return draw(time as Range, scope)\n}\n\nfunction timeOf(time: PendingTime, duration: number): number {\n if (typeof time === 'number') return time\n if (time === 'duration') return duration\n return Number(time.slice(0, -1)) * duration\n}\n\ntype PendingAutomation = { value?: number; from?: number; to?: number; curve?: Curve; over?: PendingTime }\n\nfunction drawAutomation(automation: Automation, scope: Scope): PendingAutomation {\n const pending: PendingAutomation = {}\n for (const key of Object.keys(automation)) {\n if (key === 'set' && 'set' in automation) pending.value = draw(automation.set, scope)\n else if (key === 'from' && 'from' in automation) pending.from = draw(automation.from, scope)\n else if (key === 'to' && automation.to !== undefined) pending.to = automation.to === 'from' ? (pending.from ?? pending.value ?? 0) : draw(automation.to, scope)\n else if (key === 'curve') pending.curve = automation.curve\n else if (key === 'over' && automation.over !== undefined) pending.over = drawTime(automation.over, scope)\n }\n return pending\n}\n\nfunction finishAutomation(pending: PendingAutomation, duration: number): PlannedAutomation {\n if (pending.from !== undefined && pending.to === undefined) return { from: pending.from }\n if (pending.from !== undefined) return { from: pending.from, to: pending.to, curve: pending.curve ?? 'linear', at: timeOf(pending.over ?? 'duration', duration) }\n if (pending.to === undefined || pending.over === undefined) return { value: pending.value }\n return { value: pending.value, to: pending.to, curve: pending.curve ?? 'linear', at: timeOf(pending.over, duration) }\n}\n\nconst startFrequency = (automation: PendingAutomation): number => automation.from ?? automation.value ?? 0\n\ntype PendingFilter = { spec: FilterSpec; frequency: PendingAutomation; q?: number; gain?: number; level?: number }\n\nfunction drawFilter(spec: FilterSpec, scope: Scope): PendingFilter {\n const pending: PendingFilter = { spec, frequency: {} }\n for (const key of Object.keys(spec)) {\n if (key === 'frequency') pending.frequency = drawAutomation(spec.frequency, scope)\n else if (key === 'q' && spec.q !== undefined) pending.q = draw(spec.q, scope)\n else if (key === 'gain' && spec.gain !== undefined) pending.gain = draw(spec.gain, scope)\n else if (key === 'level' && spec.level !== undefined) pending.level = draw(spec.level, scope)\n }\n return pending\n}\n\ntype PendingModulator = { modulator: Modulator; rate: number; depth?: number }\n\nfunction drawModulator(modulator: Modulator, scope: Scope): PendingModulator {\n const pending: PendingModulator = { modulator, rate: 0 }\n for (const key of Object.keys(modulator)) {\n if (key === 'rate') pending.rate = draw(modulator.rate, scope)\n else if (key === 'depth' && !(typeof modulator.depth === 'object' && 'scaleOf' in modulator.depth)) pending.depth = draw(modulator.depth as Range, scope)\n }\n return pending\n}\n\ntype PendingVoice = { frequency: PendingAutomation; vibratoRate: number; formants: { frequency: number; q: number }[]; breath: { frequency: number; q: number; gain: number } }\n\nfunction drawVoice(source: Extract<LayerSource, { kind: 'formant_voice' }>, scope: Scope): PendingVoice {\n const pending: PendingVoice = { frequency: {}, vibratoRate: 0, formants: [], breath: { frequency: 0, q: source.breath.q, gain: 0 } }\n for (const key of Object.keys(source)) {\n if (key === 'frequency') pending.frequency = drawAutomation(source.frequency, scope)\n else if (key === 'vibrato') pending.vibratoRate = draw(source.vibrato.rate, scope)\n else if (key === 'formants') pending.formants = source.formants.map((formant) => ({ frequency: draw(formant.frequency, scope), q: formant.q }))\n else if (key === 'breath') pending.breath = { frequency: draw(source.breath.frequency, scope), q: source.breath.q, gain: draw(source.breath.gain, scope) }\n }\n return pending\n}\n\ntype LayerDefaults = { readonly duration?: number; readonly tail?: number; readonly noiseSeconds: number }\n\ntype PendingLayer = {\n at: number\n duration?: number\n gain: number\n automation?: PendingAutomation\n voice?: PendingVoice\n filters: PendingFilter[]\n modulators: PendingModulator[]\n}\n\nfunction drawLayer(layer: Layer, scope: Scope): PendingLayer {\n const pending: PendingLayer = { at: 0, gain: 1, filters: [], modulators: [] }\n for (const key of Object.keys(layer)) {\n if (key === 'at' && layer.at !== undefined) pending.at = draw(layer.at, scope)\n else if (key === 'duration' && layer.duration !== undefined) pending.duration = draw(layer.duration, scope)\n else if (key === 'gain' && layer.gain !== undefined) pending.gain = draw(layer.gain, scope)\n else if (key === 'source' && layer.source.kind === 'osc') pending.automation = drawAutomation(layer.source.frequency, scope)\n else if (key === 'source' && layer.source.kind === 'formant_voice') pending.voice = drawVoice(layer.source, scope)\n else if (key === 'filters' && layer.filters) pending.filters = layer.filters.bank.map((spec) => drawFilter(spec, scope))\n else if (key === 'modulators' && layer.modulators) pending.modulators = layer.modulators.map((modulator) => drawModulator(modulator, scope))\n }\n return pending\n}\n\n// the team code draws the noise start point when the source starts, after every other number of the layer\nfunction drawOffset(source: LayerSource, duration: number, scope: Scope, noiseSeconds: number): number {\n if (source.kind === 'formant_voice') return scope.random()\n if (source.kind !== 'noise' || source.offset === undefined) return 0\n if (typeof source.offset === 'number') return source.offset\n if (source.offset === 'fit') return scope.random() * Math.max(0, noiseSeconds - duration)\n return scope.random() * (source.offsetScale ?? 1)\n}\n\nfunction finishSource(layer: Layer, pending: PendingLayer, duration: number, offset: number): PlannedSource {\n const source = layer.source\n if (source.kind === 'noise') return { kind: 'noise', offset, loop: source.loop === true }\n if (source.kind === 'osc') return { kind: 'osc', wave: source.wave, frequency: finishAutomation(pending.automation ?? {}, duration) }\n const voice = pending.voice as PendingVoice\n return {\n kind: 'formant_voice',\n wave: source.wave,\n frequency: finishAutomation(voice.frequency, duration),\n vibrato: { rate: voice.vibratoRate, depth: startFrequency(voice.frequency) * source.vibrato.depth },\n formants: voice.formants,\n breath: { ...voice.breath, offset },\n mix: source.mix,\n }\n}\n\nfunction finishModulator(pending: PendingModulator, sourceFrequency: number): PlannedModulator {\n const { modulator } = pending\n const depth = typeof modulator.depth === 'object' && 'scaleOf' in modulator.depth ? sourceFrequency * modulator.depth.factor : (pending.depth ?? 0)\n return { target: modulator.target, ...(modulator.wave ? { wave: modulator.wave } : {}), rate: pending.rate, depth, ...(modulator.base === undefined ? {} : { base: modulator.base }) }\n}\n\nfunction finishEnvelope(envelope: Envelope, duration: number, level: number): PlannedEnvelopePoint[] {\n return envelope.map(([time, value, curve, fixed]) => ({ time: timeOf(time, duration), value: fixed === 'fixed' ? value : value * level, curve }))\n}\n\nfunction planLayer(layer: Layer, scope: Scope, defaults: LayerDefaults, levelInEnvelope: boolean, soundLevel: number): PlannedLayer[] {\n const pending = drawLayer(layer, scope)\n const duration = pending.duration ?? defaults.duration\n if (duration === undefined) throw new Error('game-audio: a layer needs a duration (its own or the recipe’s)')\n const offset = drawOffset(layer.source, duration, scope, defaults.noiseSeconds)\n const level = pending.gain * soundLevel\n const sourceFrequency = layer.source.kind === 'noise' ? 0 : startFrequency(pending.automation ?? pending.voice?.frequency ?? {})\n const planned: PlannedLayer = {\n at: pending.at,\n duration,\n tail: layer.tail ?? defaults.tail ?? DEFAULT_TAIL,\n level,\n ...(layer.wet === undefined ? {} : { wet: layer.wet }),\n source: finishSource(layer, pending, duration, offset),\n ...(layer.filters\n ? {\n filters: {\n mode: layer.filters.mode,\n bank: pending.filters.map(({ spec, frequency, q, gain, level: filterLevel }) => ({\n type: spec.type,\n frequency: finishAutomation(frequency, duration),\n ...(q === undefined ? {} : { q }),\n ...(gain === undefined ? {} : { gain }),\n ...(filterLevel === undefined ? {} : { level: filterLevel }),\n })),\n },\n }\n : {}),\n modulators: pending.modulators.map((modulator) => finishModulator(modulator, sourceFrequency)),\n envelope: finishEnvelope(layer.envelope, duration, levelInEnvelope ? level : 1),\n }\n if (!layer.repeat) return [planned]\n return Array.from({ length: layer.repeat.count }, (_, index) => ({ ...planned, at: planned.at + index * (layer.repeat?.spacing ?? 0) }))\n}\n\nfunction scaleAutomation(automation: PlannedAutomation, divisor: number): PlannedAutomation {\n const scale = (value: number | undefined): number | undefined => (value === undefined ? undefined : value / divisor)\n const scaled: Record<string, unknown> = { ...automation }\n for (const key of ['value', 'from', 'to'] as const) if (automation[key] !== undefined) scaled[key] = scale(automation[key])\n return scaled as PlannedAutomation\n}\n\n// distance muffling and occlusion lower every lowpass cutoff of the sound\nfunction muffle(layer: PlannedLayer, divisor: number): PlannedLayer {\n if (divisor === 1 || !layer.filters) return layer\n const bank = layer.filters.bank.map((filter) => (filter.type === 'lowpass' ? { ...filter, frequency: scaleAutomation(filter.frequency, divisor) } : filter))\n return { ...layer, filters: { ...layer.filters, bank } }\n}\n\nconst layerEnd = (layer: PlannedLayer): number => layer.at + layer.duration + layer.tail\n\n/**\n * Turns a recipe into a plan: pure, Node-safe, the same plan for the same `random`. Numbers are drawn in this order: `vars` (in the order written),\n * `duration`, then each layer in the order its fields are written, the noise start point last in its layer. A sound below its falloff's `minGain`\n * gives `null` (or a silent plan with `keepSilent`) before any number is drawn.\n */\nexport function planSound(recipe: SoundRecipe, input: PlanInput = {}): SoundPlan | null {\n const listener = input.listener ?? DEFAULT_LISTENER\n const distance = input.distance ?? (input.position ? listenerDistance(listener, input.position, input.distanceMode) : 0)\n const occlusion = Math.min(1, Math.max(0, input.occlusion ?? 0))\n const order = recipe.order ?? 'layered'\n const falloffLevel = falloffGain(recipe.falloff, input.volume ?? 1, distance)\n const level = occlusion > 0 ? falloffLevel * (1 - OCCLUSION_GAIN * occlusion) : falloffLevel\n const silent = isSilent(recipe.falloff, falloffLevel)\n const base = {\n id: recipe.id,\n bus: recipe.bus,\n order,\n ...(recipe.group === undefined ? {} : { group: recipe.group }),\n priority: recipe.priority ?? busPriority(recipe.bus),\n level,\n distance,\n ...(recipe.pan ? { pan: input.position ? panFor(listener, input.position) : 0 } : {}),\n }\n if (silent) return recipe.keepSilent ? { ...base, silent: true, length: 0, layers: [] } : null\n\n const scope: Scope = { random: input.random ?? createRng(1), params: { ...input.params } }\n for (const [name, range] of Object.entries(recipe.vars ?? {})) if (scope.params[name] === undefined) scope.params[name] = draw(range, scope)\n const defaults: LayerDefaults = {\n ...(recipe.duration === undefined ? {} : { duration: draw(recipe.duration, scope) }),\n ...(recipe.tail === undefined ? {} : { tail: recipe.tail }),\n noiseSeconds: input.noiseSeconds ?? DEFAULT_NOISE_SECONDS,\n }\n const divisor = cutoffDivisor(recipe.falloff, distance) / (1 - OCCLUSION_CUTOFF * occlusion)\n const layers = recipe.layers.flatMap((layer) => planLayer(layer, scope, defaults, order !== 'layered', level)).map((layer) => muffle(layer, divisor))\n return { ...base, silent: false, length: Math.max(0, ...layers.map(layerEnd)), layers }\n}\n","import type { LoopRecipe } from './recipe'\n\n/** jogo L62112: a loop's nodes exist only once its volume reaches 0.002. */\nexport const LOOP_START_ABOVE = 0.002\n/** jogo L62125–62126: `setTargetAtTime` time constants. */\nexport const LOOP_SMOOTHING = { volume: 0.15, frequency: 0.08 } as const\n\n/** What the realizer does for one `set`: create the nodes first (once), then glide the volume and, for an oscillator, the frequency. */\nexport type LoopAction = { readonly create: boolean; readonly volume: number; readonly frequency?: number }\n\nexport type LoopState = {\n /** `null` while the loop has no nodes and the volume is still below the start threshold. */\n set(state: { readonly volume?: number; readonly frequency?: number }): LoopAction | null\n /** Whether the nodes exist. */\n readonly created: boolean\n /** The nodes were torn down (`stop`): the next audible `set` creates them again. */\n reset(): void\n}\n\n/**\n * The game's `loopSet` as state: nodes on the first volume at or above `startAbove`, then every `set` glides. The volume set last is kept, so a\n * `set` of only the frequency keeps the level. `chain_loop` recipes open with their nodes already made (`open` is true).\n */\nexport function createLoopState(recipe: LoopRecipe, open = false): LoopState {\n const startAbove = recipe.startAbove ?? LOOP_START_ABOVE\n let created = open\n let volume = recipe.volume ?? 0\n return {\n set(state) {\n volume = state.volume ?? volume\n const create = !created\n if (create && !(volume >= startAbove)) return null\n created = true\n const frequency = recipe.source.kind === 'osc' && state.frequency ? state.frequency : undefined\n return { create, volume, ...(frequency === undefined ? {} : { frequency }) }\n },\n get created() {\n return created\n },\n reset() {\n created = false\n },\n }\n}\n","[\n {\n \"id\": \"game\",\n \"master\": 0.5,\n \"noise\": { \"seconds\": 1.5, \"create\": \"lazy\" },\n \"sources\": [\"jogo:L62077-62081 master 0.5 to the output\", \"jogo:L62082-62088 noise 1.5 s made by the first sound that needs it\"]\n },\n {\n \"id\": \"art_scene\",\n \"master\": 0.85,\n \"compressor\": { \"threshold\": -12, \"ratio\": 5 },\n \"reverb\": { \"seconds\": 2.2, \"decay\": 3.4, \"wet\": 0.4 },\n \"noise\": { \"seconds\": 2, \"create\": \"eager\" },\n \"sources\": [\n \"cena:L1308 compressor -12 dB, ratio 5\",\n \"cena:L1309 master 0.85\",\n \"cena:L1310 convolver, impulse 2.2 s of (1 - i/len)^3.4\",\n \"cena:L1311 wet 0.4\",\n \"cena:L1312 noise 2 s made with the chain\"\n ]\n }\n]\n","import mixProfilesJson from './data/mix-profiles.json'\nimport type { BusId } from './recipe'\n\nexport type BusSetting = { readonly gain?: number; readonly muted?: boolean }\n\n/**\n * A mixing chain. `master` → (compressor) → output; with `reverb`, a wet bus → convolver (an impulse from the engine's seed) → master. `noise` is\n * the shared noise buffer: its length, and whether it is made with the chain (`eager`, the art scene) or by the first sound that needs it (`lazy`,\n * the game). Buses sit under the master and have no node until one is changed.\n */\nexport type MixProfile = {\n readonly id: string\n readonly master: number\n readonly compressor?: { readonly threshold: number; readonly ratio: number; readonly knee?: number; readonly attack?: number; readonly release?: number }\n readonly reverb?: { readonly seconds: number; readonly decay: number; readonly wet: number }\n readonly noise: { readonly seconds: number; readonly create: 'lazy' | 'eager' }\n readonly buses?: Readonly<Partial<Record<BusId, BusSetting>>>\n readonly sources: readonly string[]\n}\n\n/** The two team chains: `game` (master 0.5, jogo L62080) and `art_scene` (compressor and reverb, cena L1305–1313). */\nexport const MIX_PROFILES: Readonly<Record<string, MixProfile>> = Object.fromEntries(\n (mixProfilesJson as unknown as readonly MixProfile[]).map((profile) => [profile.id, profile]),\n)\n/** Q1: the kit plays through the art scene chain (compressor and reverb); `mix: 'game'` reverts. */\nexport const DEFAULT_MIX = 'art_scene'\n\n/** A profile by id from `profiles`, or the profile itself. */\nexport function resolveMix(mix: string | MixProfile, profiles: Readonly<Record<string, MixProfile>> = MIX_PROFILES): MixProfile {\n if (typeof mix !== 'string') return mix\n const profile = profiles[mix]\n if (!profile) throw new Error(`game-audio: unknown mix profile \"${mix}\" (known: ${Object.keys(profiles).join(', ')})`)\n return profile\n}\n\n/** The gain a bus plays at: 0 when muted, its gain (default 1) otherwise. */\nexport function busGain(setting: BusSetting | undefined): number {\n if (!setting) return 1\n return setting.muted ? 0 : (setting.gain ?? 1)\n}\n","import type { LoopRecipe, SoundBank, SoundRecipe } from './recipe'\n\nexport type DeepPartial<TValue> = TValue extends readonly unknown[] ? TValue : TValue extends object ? { readonly [Key in keyof TValue]?: DeepPartial<TValue[Key]> } : TValue\n\n/** Every sound and loop the engine knows, by id. Banks register in order; a later bank replaces an earlier recipe with the same id. */\nexport type SoundRegistry = {\n register(bank: SoundBank): void\n /** Merged into the recipe by id (objects merge, arrays such as `layers` are replaced); an unknown id is ignored. */\n patch(id: string, patch: DeepPartial<SoundRecipe> | DeepPartial<LoopRecipe>): void\n remove(id: string): void\n sound(id: string): SoundRecipe | undefined\n loop(id: string): LoopRecipe | undefined\n soundIds(): string[]\n loopIds(): string[]\n /** Which bank each id came from. */\n bankOf(id: string): string | undefined\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n// keys keep their place, so a patched range is still drawn where it was\nfunction mergeRecords(base: Record<string, unknown>, patch: Record<string, unknown>): Record<string, unknown> {\n const merged: Record<string, unknown> = { ...base }\n for (const [key, value] of Object.entries(patch)) {\n if (value === undefined) continue\n const current = merged[key]\n merged[key] = isPlainObject(current) && isPlainObject(value) ? mergeRecords(current, value) : value\n }\n return merged\n}\n\nexport function createSoundRegistry(banks: readonly SoundBank[] = []): SoundRegistry {\n const sounds = new Map<string, SoundRecipe>()\n const loops = new Map<string, LoopRecipe>()\n const owners = new Map<string, string>()\n const registry: SoundRegistry = {\n register(bank) {\n for (const sound of bank.sounds ?? []) {\n sounds.set(sound.id, sound)\n owners.set(sound.id, bank.id)\n }\n for (const loop of bank.loops ?? []) {\n loops.set(loop.id, loop)\n owners.set(loop.id, bank.id)\n }\n },\n patch(id, patch) {\n const sound = sounds.get(id)\n if (sound) sounds.set(id, mergeRecords(sound, patch as Record<string, unknown>) as SoundRecipe)\n const loop = loops.get(id)\n if (loop) loops.set(id, mergeRecords(loop, patch as Record<string, unknown>) as LoopRecipe)\n },\n remove(id) {\n sounds.delete(id)\n loops.delete(id)\n owners.delete(id)\n },\n sound: (id) => sounds.get(id),\n loop: (id) => loops.get(id),\n soundIds: () => [...sounds.keys()],\n loopIds: () => [...loops.keys()],\n bankOf: (id) => owners.get(id),\n }\n for (const bank of banks) registry.register(bank)\n return registry\n}\n","{\n \"sources\": [\"jogo:L62089-62107\"],\n \"note\": \"The game's sfx table P: [lowpass cutoff Hz, peak gain, seconds]. Played as a noise burst with the game's distance curve (falloff game_sfx) and a 0.05 s tail. Team names in `team`.\",\n \"classes\": [\n { \"id\": \"pistol\", \"team\": \"pistola\", \"bus\": \"weapons\", \"cutoff\": 1800, \"gain\": 0.9, \"seconds\": 0.18 },\n { \"id\": \"shotgun\", \"team\": \"escopeta\", \"bus\": \"weapons\", \"cutoff\": 900, \"gain\": 1.2, \"seconds\": 0.35 },\n { \"id\": \"rifle\", \"team\": \"fuzil\", \"bus\": \"weapons\", \"cutoff\": 1400, \"gain\": 1.1, \"seconds\": 0.28 },\n { \"id\": \"long_rifle\", \"team\": \"rifle\", \"bus\": \"weapons\", \"cutoff\": 1100, \"gain\": 1.3, \"seconds\": 0.45 },\n { \"id\": \"impact\", \"team\": \"impacto\", \"bus\": \"world\", \"cutoff\": 2500, \"gain\": 0.25, \"seconds\": 0.05 },\n { \"id\": \"flesh\", \"team\": \"carne\", \"bus\": \"world\", \"cutoff\": 600, \"gain\": 0.35, \"seconds\": 0.08 },\n { \"id\": \"step\", \"team\": \"passo\", \"bus\": \"world\", \"cutoff\": 400, \"gain\": 0.08, \"seconds\": 0.05 },\n { \"id\": \"glass\", \"team\": \"vidro\", \"bus\": \"world\", \"cutoff\": 5000, \"gain\": 0.5, \"seconds\": 0.3 },\n { \"id\": \"wood\", \"team\": \"madeira\", \"bus\": \"world\", \"cutoff\": 700, \"gain\": 0.5, \"seconds\": 0.2 },\n { \"id\": \"reload\", \"team\": \"recarga\", \"bus\": \"weapons\", \"cutoff\": 3000, \"gain\": 0.15, \"seconds\": 0.06 },\n { \"id\": \"empty\", \"team\": \"vazio\", \"bus\": \"weapons\", \"cutoff\": 4000, \"gain\": 0.12, \"seconds\": 0.03 },\n { \"id\": \"explosion\", \"team\": \"explosao\", \"bus\": \"world\", \"cutoff\": 200, \"gain\": 1.6, \"seconds\": 1.4 }\n ],\n \"fallback\": { \"id\": \"unknown\", \"team\": \"(any other name)\", \"bus\": \"world\", \"cutoff\": 1000, \"gain\": 0.5, \"seconds\": 0.1 }\n}\n","import { DISTANCE_CURVES } from '../falloff'\nimport type { BusId, SoundBank, SoundRecipe } from '../recipe'\nimport gameSfxJson from '../data/game-sfx.json'\n\n/** One row of the game's `sfx` table (jogo L62096–62100): lowpass cutoff, peak gain and seconds of a noise burst. */\nexport type NoiseClass = {\n readonly id: string\n /** The name in the team code (`pistola`, `vidro`...). */\n readonly team: string\n readonly bus: BusId\n readonly cutoff: number\n readonly gain: number\n readonly seconds: number\n}\n\ntype GameSfxTable = { readonly sources: readonly string[]; readonly classes: readonly NoiseClass[]; readonly fallback: NoiseClass }\n\nconst GAME_SFX = gameSfxJson as unknown as GameSfxTable\n\n/** The 12 game classes, in the table's order. */\nexport const GAME_SFX_CLASSES: readonly NoiseClass[] = GAME_SFX.classes\n/** What the game plays for a name it does not know: `[1000, 0.5, 0.1]`. */\nexport const GAME_SFX_FALLBACK: NoiseClass = GAME_SFX.fallback\n\n/**\n * A class as a recipe: noise from a random point, the class lowpass (muffled by distance), the peak set at once and an exponential fall to 0.001 over\n * the class seconds, stopped 0.05 s later; the game's distance curve, silent below 0.01. Built in the game's `sfx` order (`burst`).\n */\nexport function noiseClassRecipe(row: NoiseClass, id = `sfx_${row.id}`): SoundRecipe {\n return {\n id,\n bus: row.bus,\n order: 'burst',\n duration: row.seconds,\n tail: 0.05,\n layers: [\n {\n source: { kind: 'noise', offset: 'random' },\n filters: { mode: 'serial', bank: [{ type: 'lowpass', frequency: { set: row.cutoff } }] },\n envelope: [\n [0, row.gain, 'set'],\n ['1d', 0.001, 'exp', 'fixed'],\n ],\n },\n ],\n falloff: DISTANCE_CURVES.game_sfx,\n sources: [...GAME_SFX.sources.map((source) => `${source} ${row.team}`)],\n }\n}\n\n/** The game's classes as a bank (`sfx_pistol`... and `sfx_unknown`). Subject banks pick their own classes from `GAME_SFX_CLASSES`. */\nexport const GAME_SFX_BANK: SoundBank = { id: 'game_sfx', sounds: [...GAME_SFX_CLASSES, GAME_SFX_FALLBACK].map((row) => noiseClassRecipe(row)) }\n","// The art scene's synthesis primitives (cena L1322-L1328) as recipe layers, shared by every subject bank that ports a `sfx.*` of the page: the\n// materials in `destruction-three`, the voices and wounds in `characters-three`. Each helper writes the layer the team routine builds, with its\n// fields in the order the routine draws its random numbers, so a recipe built from them leaves the trace of the team code.\nimport { DISTANCE_CURVES } from '../falloff'\nimport type { BusId, Envelope, Layer, ParamRef, Range, SoundRecipe } from '../recipe'\n\nconst ATTACK = 0.002\n\n/** A length in seconds: a number, a `[min, max]` range or a named number. */\nexport type SceneLength = number | readonly [number, number] | ParamRef\n\n// the layer is `attack + d` long (the art scene's gain rises over `a` and falls over `d`)\nfunction lengthOf(d: SceneLength, attack: number): Range {\n if (typeof d === 'number') return attack + d\n if (Array.isArray(d)) return [attack + d[0], attack + d[1]] as const\n return { ...(d as ParamRef), add: ((d as ParamRef).add ?? 0) + attack }\n}\n\nconst envelope = (attack: number): Envelope => [\n [0, 0.0001, 'set', 'fixed'],\n [attack, 1, 'exp'],\n ['1d', 0.0001, 'exp', 'fixed'],\n]\n\nexport type SceneNoise = {\n readonly type?: BiquadFilterType\n readonly f: Range\n /** The filter slides here over `d` (needs a fixed `d`). */\n readonly f2?: number\n readonly q?: number\n readonly a?: number\n readonly d: SceneLength\n readonly vol: Range\n readonly wet?: number\n readonly delay?: Range\n}\n\n/** `sNoise` (cena L1323): noise from a random point, a filter (default lowpass, Q 0.7), the gain rising over `a` and falling over `d`. */\nexport function sceneNoise(o: SceneNoise): Layer {\n const a = o.a ?? ATTACK\n const over = typeof o.d === 'number' ? o.d : 0\n return {\n filters: {\n mode: 'serial',\n bank: [{ type: o.type ?? 'lowpass', frequency: o.f2 === undefined ? { set: o.f } : { set: o.f, to: o.f2, curve: 'exp', over }, q: o.q ?? 0.7 }],\n },\n duration: lengthOf(o.d, a),\n gain: o.vol,\n ...(o.delay === undefined ? {} : { at: o.delay }),\n ...(o.wet === undefined ? {} : { wet: o.wet }),\n source: { kind: 'noise', offset: 'random', offsetScale: 1.5, loop: true },\n envelope: envelope(a),\n }\n}\n\nexport type SceneTone = { readonly wave?: OscillatorType; readonly f: Range; readonly f2?: number; readonly d: SceneLength; readonly vol: Range; readonly wet?: number; readonly delay?: Range }\n\n/** `sTone` (cena L1324): an oscillator sliding from `f` to `f2` (back to `f` when none), the gain rising over 0.002 s and falling over `d`. */\nexport function sceneTone(o: SceneTone): Layer {\n return {\n source: { kind: 'osc', wave: o.wave ?? 'sine', frequency: { from: o.f, to: o.f2 ?? 'from', curve: 'exp', over: 'duration' } },\n duration: lengthOf(o.d, ATTACK),\n gain: o.vol,\n ...(o.delay === undefined ? {} : { at: o.delay }),\n ...(o.wet === undefined ? {} : { wet: o.wet }),\n envelope: envelope(ATTACK),\n }\n}\n\nexport type SceneVoice = {\n /** Start and end frequency of the sweep (the page ramps linearly from `f0` to `f1` over the length). */\n readonly f0: Range\n readonly f1: Range\n /** Length in seconds. */\n readonly d: SceneLength\n /** Centre of the first and second formant (Q 4 and 6) and of the breath band (Q 1.1). */\n readonly fA: Range\n readonly fB: Range\n /** Level of the breath noise. */\n readonly noise: Range\n readonly vol: Range\n readonly wave?: OscillatorType\n}\n\n/**\n * `voice` (cena L1325): an oscillator swept from `f0` to `f1` with a vibrato of 4 to 10 Hz (depth 0.08 of `f0`) through two formant bands plus a band of\n * breath noise, summed and boosted 3.2 times, the gain rising over 0.1 s, held to 0.6 of the length and falling; reverb send 0.45. It is the one\n * layer the page builds with `voice(...)`, so a scream is two of them.\n */\nexport function sceneVoice(o: SceneVoice): Layer {\n return {\n duration: lengthOf(o.d, 0),\n gain: o.vol,\n wet: 0.45,\n source: {\n kind: 'formant_voice',\n wave: o.wave ?? 'sawtooth',\n frequency: { from: o.f0, to: o.f1, curve: 'linear', over: 'duration' },\n vibrato: { rate: [4, 10], depth: 0.08 },\n formants: [\n { frequency: o.fA, q: 4 },\n { frequency: o.fB, q: 6 },\n ],\n breath: { frequency: o.fB, q: 1.1, gain: o.noise },\n mix: 3.2,\n },\n envelope: [\n [0, 0.0001, 'set', 'fixed'],\n [0.1, 1, 'exp'],\n ['0.6d', 1, 'set'],\n ['1d', 0.0001, 'exp', 'fixed'],\n ],\n }\n}\n\n/** The layer played `count` times (a team `for` loop whose body draws numbers): each pass is its own layer, so the draws stay in the loop's order. */\nexport const sceneMany = (count: number, make: () => Layer): Layer[] => Array.from({ length: count }, make)\n\nexport type SceneRecipeOptions = {\n /** Named numbers drawn first (`PlayOptions.params` can override them). */\n readonly vars?: SoundRecipe['vars']\n readonly bus?: BusId\n readonly group?: string\n}\n\n/** A recipe in the art scene's mix: positional with the art scene's curve (`vol / (1 + d² × 0.008)`) and pan; `line` is the team code it comes from (`cena:<line>`). */\nexport function sceneRecipe(id: string, layers: readonly Layer[], line: string, options: SceneRecipeOptions = {}): SoundRecipe {\n return {\n id,\n bus: options.bus ?? 'world',\n pan: true,\n layers,\n falloff: DISTANCE_CURVES.art_scene,\n ...(options.vars ? { vars: options.vars } : {}),\n ...(options.group ? { group: options.group } : {}),\n sources: [`cena:${line}`],\n }\n}\n","{\n \"id\": \"crowd_rumble\",\n \"bus\": \"ambience\",\n \"order\": \"game_loop\",\n \"source\": { \"kind\": \"noise\", \"loop\": true },\n \"filters\": { \"mode\": \"serial\", \"bank\": [{ \"type\": \"lowpass\", \"frequency\": { \"set\": 160 } }] },\n \"smoothing\": { \"volume\": 0.15, \"frequency\": 0.08 },\n \"startAbove\": 0.002,\n \"sources\": [\"jogo:L62116 horda: noise through a 160 Hz lowpass\", \"jogo:L70424 level min(0.16, near × 0.0016)\", \"temporary home until the crowd package (Q12)\"]\n}\n","{\n \"id\": \"engine_saw\",\n \"bus\": \"world\",\n \"order\": \"game_loop\",\n \"source\": { \"kind\": \"osc\", \"wave\": \"sawtooth\", \"loop\": true },\n \"filters\": { \"mode\": \"serial\", \"bank\": [{ \"type\": \"lowpass\", \"frequency\": { \"set\": 400 } }] },\n \"smoothing\": { \"volume\": 0.15, \"frequency\": 0.08 },\n \"startAbove\": 0.002,\n \"sources\": [\n \"jogo:L62115 motor: sawtooth through a 400 Hz lowpass\",\n \"jogo:L70421 level 0.035 + speed × 0.003, frequency 38 + speed × 7 (the host drives `set`)\",\n \"temporary home until the vehicles package (Q12)\"\n ]\n}\n","{\n \"id\": \"horn\",\n \"bus\": \"world\",\n \"order\": \"chord\",\n \"duration\": 0.6,\n \"tail\": 0.02,\n \"layers\": [\n { \"source\": { \"kind\": \"osc\", \"wave\": \"square\", \"frequency\": { \"set\": 415 } }, \"envelope\": [[0, 0.08, \"set\"], [0.6, 0.0001, \"set\", \"fixed\"]] },\n { \"source\": { \"kind\": \"osc\", \"wave\": \"square\", \"frequency\": { \"set\": 523 } }, \"envelope\": [[0, 0.08, \"set\"], [0.6, 0.0001, \"set\", \"fixed\"]] }\n ],\n \"falloff\": { \"model\": \"none\" },\n \"sources\": [\"jogo:L70272-70276 buzina: squares at 415 and 523 Hz, 0.08 for 0.6 s\", \"temporary home until the vehicles package (Q12)\"]\n}\n","{\n \"id\": \"radio_static\",\n \"bus\": \"world\",\n \"order\": \"static\",\n \"duration\": 0.6,\n \"layers\": [\n {\n \"source\": { \"kind\": \"noise\", \"offset\": \"random\" },\n \"filters\": { \"mode\": \"serial\", \"bank\": [{ \"type\": \"bandpass\", \"frequency\": { \"set\": 1800 }, \"q\": 0.8 }] },\n \"envelope\": [[0, 0.06, \"set\"], [\"1d\", 0.001, \"exp\", \"fixed\"]]\n }\n ],\n \"falloff\": { \"model\": \"none\" },\n \"sources\": [\"jogo:L69998-70004 chiado: noise through a 1800 Hz bandpass (Q 0.8), 0.06 falling over 0.6 s\", \"temporary home until the props or radio package (Q12)\"]\n}\n","{\n \"id\": \"rotor_blades\",\n \"bus\": \"world\",\n \"order\": \"game_loop\",\n \"source\": { \"kind\": \"noise\", \"loop\": true },\n \"filters\": { \"mode\": \"serial\", \"bank\": [{ \"type\": \"lowpass\", \"frequency\": { \"set\": 300 } }] },\n \"modulators\": [{ \"target\": \"gain\", \"rate\": 11, \"depth\": 0.5, \"base\": 0.5 }],\n \"smoothing\": { \"volume\": 0.15, \"frequency\": 0.08 },\n \"startAbove\": 0.002,\n \"sources\": [\n \"jogo:L62116-62120 rotor: noise through a 300 Hz lowpass, blade beat as an 11 Hz LFO of 0.5 on a 0.5 gain stage\",\n \"jogo:L70426 level min(0.5, 30 / (distance + 10))\",\n \"temporary home until the vehicles package (Q12)\"\n ]\n}\n","{\n \"id\": \"siren\",\n \"bus\": \"world\",\n \"order\": \"siren\",\n \"duration\": 9,\n \"tail\": 0.2,\n \"layers\": [\n {\n \"wet\": 1.5,\n \"source\": { \"kind\": \"osc\", \"wave\": \"sine\", \"frequency\": { \"set\": 850 } },\n \"modulators\": [{ \"target\": \"frequency\", \"rate\": 0.18, \"depth\": 280 }],\n \"filters\": { \"mode\": \"serial\", \"bank\": [{ \"type\": \"lowpass\", \"frequency\": { \"set\": 1400 } }] },\n \"envelope\": [[0, 0.0001, \"set\", \"fixed\"], [2, 0.035, \"exp\"], [6, 0.035, \"set\"], [9, 0.0001, \"exp\", \"fixed\"]]\n }\n ],\n \"falloff\": { \"model\": \"none\" },\n \"sources\": [\"cena:L1369 siren: 850 Hz sine swept ±280 Hz at 0.18 Hz, 1400 Hz lowpass, 0.035 for 9 s, reverb send 1.5\", \"temporary home until the vehicles package (Q12)\"]\n}\n","// Sounds whose subject has no package yet (Q12): they move to vehicles, crowd and props in Phases 7 and 8 and stay here as re-exports.\nimport type { LoopRecipe, SoundBank, SoundRecipe } from '../recipe'\nimport crowdRumble from './crowd_rumble.json'\nimport engineSaw from './engine_saw.json'\nimport horn from './horn.json'\nimport radioStatic from './radio_static.json'\nimport rotorBlades from './rotor_blades.json'\nimport siren from './siren.json'\n\nexport const LIBRARY_SOUNDS: readonly SoundRecipe[] = [horn, radioStatic, siren] as unknown as readonly SoundRecipe[]\nexport const LIBRARY_LOOPS: readonly LoopRecipe[] = [engineSaw, rotorBlades, crowdRumble] as unknown as readonly LoopRecipe[]\nexport const LIBRARY_BANK: SoundBank = { id: 'library', sounds: LIBRARY_SOUNDS, loops: LIBRARY_LOOPS }\n","/** `length` samples of white noise, `random × 2 − 1`, drawn in order (jogo L62086, cena L1312). */\nexport function fillNoise(random: () => number, length: number): Float32Array {\n const samples = new Float32Array(length)\n for (let index = 0; index < length; index++) samples[index] = random() * 2 - 1\n return samples\n}\n\n/**\n * A reverb impulse: per channel, noise times `(1 − i / length)^decay` (cena L1310). `length` is `sampleRate × seconds` as the art scene computes it,\n * which is not always whole (48000 × 2.2 = 105600.00000000001): the loop runs while `i < length`, so one more number is drawn than the buffer holds,\n * exactly like the team code. The power is `exp(decay × log(base))`, which every engine computes the same.\n */\nexport function fillImpulse(random: () => number, options: { readonly channels: number; readonly length: number; readonly decay: number }): Float32Array[] {\n const size = Math.floor(options.length)\n return Array.from({ length: options.channels }, () => {\n const samples = new Float32Array(size)\n for (let index = 0; index < options.length; index++) {\n const value = (random() * 2 - 1) * Math.exp(options.decay * Math.log(1 - index / options.length))\n if (index < size) samples[index] = value\n }\n return samples\n })\n}\n\n/** An AudioBuffer holding the samples (one channel per array). */\nexport function bufferOf(context: AudioContext, channels: readonly Float32Array[], requestedLength: number): AudioBuffer {\n const buffer = context.createBuffer(channels.length, requestedLength, context.sampleRate)\n channels.forEach((samples, channel) => buffer.getChannelData(channel).set(samples))\n return buffer\n}\n","import type { MixProfile } from '../mix'\nimport { bufferOf, fillImpulse } from './noise'\n\nexport type MixChain = { readonly master: GainNode; readonly wet?: GainNode; disconnect(): void }\n\n/**\n * The mixing chain in the team order (cena L1308–1311; jogo L62080): compressor to the output, master into it (or straight to the output), then the\n * convolver with an impulse drawn from `random` and the wet bus into it, back into the master.\n */\nexport function buildChain(context: AudioContext, profile: MixProfile, random: () => number): MixChain {\n let compressor: DynamicsCompressorNode | undefined\n if (profile.compressor) {\n compressor = context.createDynamicsCompressor()\n compressor.threshold.value = profile.compressor.threshold\n compressor.ratio.value = profile.compressor.ratio\n if (profile.compressor.knee !== undefined) compressor.knee.value = profile.compressor.knee\n if (profile.compressor.attack !== undefined) compressor.attack.value = profile.compressor.attack\n if (profile.compressor.release !== undefined) compressor.release.value = profile.compressor.release\n compressor.connect(context.destination)\n }\n const master = context.createGain()\n master.gain.value = profile.master\n master.connect(compressor ?? context.destination)\n if (!profile.reverb) {\n return {\n master,\n disconnect: () => {\n master.disconnect()\n compressor?.disconnect()\n },\n }\n }\n const convolver = context.createConvolver()\n const length = context.sampleRate * profile.reverb.seconds\n convolver.buffer = bufferOf(context, fillImpulse(random, { channels: 2, length, decay: profile.reverb.decay }), length)\n const wet = context.createGain()\n wet.gain.value = profile.reverb.wet\n wet.connect(convolver)\n convolver.connect(master)\n return {\n master,\n wet,\n disconnect: () => {\n wet.disconnect()\n convolver.disconnect()\n master.disconnect()\n compressor?.disconnect()\n },\n }\n}\n","type AudioContextConstructor = new () => AudioContext\n\n/** A new context from the page (`AudioContext` or the old `webkitAudioContext`), or `null` in Node and workers. */\nexport function globalContext(): AudioContext | null {\n const scope = globalThis as { AudioContext?: AudioContextConstructor; webkitAudioContext?: AudioContextConstructor }\n const Constructor = scope.AudioContext ?? scope.webkitAudioContext\n return Constructor ? new Constructor() : null\n}\n\n/** Browsers start a context suspended until a click or key; call from one. */\nexport function unlockContext(context: AudioContext): void {\n if (context.state === 'suspended') void context.resume()\n}\n","import type { PlannedAutomation, PlannedEnvelopePoint, PlannedFilter } from '../plan'\n\n/** Where a plan is built: the context, the bus it plays into, the reverb send (if the mix has one), the shared noise and the start time. */\nexport type RealizeTarget = {\n readonly context: AudioContext\n readonly output: AudioNode\n readonly wet?: AudioNode\n /** The noise buffer; made on the first call when the mix makes it on demand. */\n readonly noise: () => AudioBuffer\n readonly time: number\n}\n\n/** What `realize` built: every scheduled source (so the voice can be cut short) and `stop`. */\nexport type RealizedVoice = { readonly sources: readonly AudioScheduledSourceNode[]; stop(): void }\n\n/** `.value = value`, or `setValueAtTime(from)` at `time`. */\nexport function assign(param: AudioParam, automation: PlannedAutomation, time: number): void {\n if (automation.value !== undefined) param.value = automation.value\n if (automation.from !== undefined) param.setValueAtTime(automation.from, time)\n}\n\nexport function ramp(param: AudioParam, curve: 'set' | 'linear' | 'exp', value: number, at: number): void {\n if (curve === 'set') param.setValueAtTime(value, at)\n else if (curve === 'exp') param.exponentialRampToValueAtTime(value, at)\n else param.linearRampToValueAtTime(value, at)\n}\n\n/** The ramp of an automation, if it has one. */\nexport function rampTo(param: AudioParam, automation: PlannedAutomation, time: number): void {\n if (automation.to !== undefined && automation.at !== undefined) ramp(param, automation.curve ?? 'linear', automation.to, time + automation.at)\n}\n\nexport function applyEnvelope(param: AudioParam, points: readonly PlannedEnvelopePoint[], time: number): void {\n for (const point of points) ramp(param, point.curve, point.value, time + point.time)\n}\n\n/** A noise source on the shared buffer (`loop` set only when asked, as the art scene's `nsrc` does). */\nexport function noiseSource(target: RealizeTarget, loop: boolean): AudioBufferSourceNode {\n const source = target.context.createBufferSource()\n source.buffer = target.noise()\n if (loop) source.loop = true\n return source\n}\n\n/** The art scene's `bq`: type, frequency, then Q and gain when given. The frequency ramp is left to the caller. */\nexport function filterNode(context: AudioContext, filter: PlannedFilter, time: number): BiquadFilterNode {\n const node = context.createBiquadFilter()\n node.type = filter.type\n assign(node.frequency, filter.frequency, time)\n if (filter.q !== undefined) node.Q.value = filter.q\n if (filter.gain !== undefined) node.gain.value = filter.gain\n return node\n}\n\nexport function gainNode(context: AudioContext, value: number): GainNode {\n const node = context.createGain()\n node.gain.value = value\n return node\n}\n\nexport function voiceOf(sources: readonly AudioScheduledSourceNode[]): RealizedVoice {\n return {\n sources,\n stop() {\n for (const source of sources) {\n try {\n source.stop()\n } catch {\n // already stopped\n }\n }\n },\n }\n}\n","// Loop nodes in the two team orders: the game's `loopSet` (jogo L62109–62127) and the art scene's loops in `initAudio` (cena L1314–1316).\nimport type { Automation, LoopRecipe, Range } from '../recipe'\nimport { filterNode, gainNode, noiseSource } from './nodes'\nimport type { RealizeTarget } from './nodes'\n\nexport type RealizedLoop = {\n readonly gain: GainNode\n readonly source: AudioScheduledSourceNode\n readonly frequency?: AudioParam\n stop(): void\n}\n\n// loops take fixed numbers: a range or a param in a loop recipe reads as 0\nconst fixed = (range: Range | undefined): number => (typeof range === 'number' ? range : 0)\nconst fixedFrequency = (automation: Automation): number => fixed('set' in automation ? automation.set : automation.from)\n\nfunction sourceOf(recipe: LoopRecipe, target: RealizeTarget): { source: AudioScheduledSourceNode; frequency?: AudioParam } {\n if (recipe.source.kind === 'noise') return { source: noiseSource(target, true) }\n const oscillator = target.context.createOscillator()\n oscillator.type = recipe.source.wave\n if (recipe.source.frequency) oscillator.frequency.value = fixedFrequency(recipe.source.frequency)\n return { source: oscillator, frequency: oscillator.frequency }\n}\n\nfunction stopAll(sources: readonly AudioScheduledSourceNode[]): () => void {\n return () => {\n for (const source of sources) {\n try {\n source.stop()\n } catch {\n // already stopped\n }\n }\n }\n}\n\n/** `loopSet` on first use: gain from 0 and filter wired first, the source and filter set, an optional blade-beat stage, then the source starts. */\nexport function realizeGameLoop(recipe: LoopRecipe, target: RealizeTarget): RealizedLoop {\n const { context } = target\n const gain = context.createGain()\n const filters = (recipe.filters?.bank ?? []).map(() => context.createBiquadFilter())\n gain.gain.value = 0\n const last = filters.reduce<AudioNode | undefined>((previous, filter) => (previous ? previous.connect(filter) : filter), undefined)\n last?.connect(gain)\n gain.connect(target.output)\n const { source, frequency } = sourceOf(recipe, target)\n recipe.filters?.bank.forEach((spec, index) => {\n const filter = filters[index] as BiquadFilterNode\n filter.type = spec.type\n filter.frequency.value = fixedFrequency(spec.frequency)\n if (spec.q !== undefined) filter.Q.value = fixed(spec.q)\n })\n const sources: AudioScheduledSourceNode[] = [source]\n for (const modulator of recipe.modulators ?? []) {\n const lfo = context.createOscillator()\n const depth = context.createGain()\n const stage = context.createGain()\n if (modulator.wave) lfo.type = modulator.wave\n lfo.frequency.value = fixed(modulator.rate)\n depth.gain.value = fixed(modulator.depth as Range)\n stage.gain.value = modulator.base ?? 0\n lfo.connect(depth)\n depth.connect(stage.gain)\n last?.disconnect()\n last?.connect(stage)\n stage.connect(gain)\n lfo.start()\n sources.push(lfo)\n }\n source.connect(filters[0] ?? gain)\n source.start()\n return { gain, source, ...(frequency ? { frequency } : {}), stop: stopAll(sources) }\n}\n\n/** The art scene's loops: source, filters, gain at its starting volume, a gain LFO, wired, started. */\nexport function realizeChainLoop(recipe: LoopRecipe, target: RealizeTarget, volume: number): RealizedLoop {\n const { context } = target\n const { source, frequency } = sourceOf(recipe, target)\n const filters = (recipe.filters?.bank ?? []).map((spec) =>\n filterNode(context, { type: spec.type, frequency: { value: fixedFrequency(spec.frequency) }, ...(spec.q === undefined ? {} : { q: fixed(spec.q) }) }, target.time),\n )\n const gain = gainNode(context, volume)\n const lfos = (recipe.modulators ?? []).map((modulator) => {\n const lfo = context.createOscillator()\n if (modulator.wave) lfo.type = modulator.wave\n lfo.frequency.value = fixed(modulator.rate)\n const depth = gainNode(context, fixed(modulator.depth as Range))\n lfo.connect(depth)\n depth.connect(gain.gain)\n return lfo\n })\n let tail: AudioNode = source\n for (const filter of filters) tail = tail.connect(filter)\n tail.connect(gain)\n gain.connect(target.output)\n source.start()\n for (const lfo of lfos) lfo.start()\n return { gain, source, ...(frequency ? { frequency } : {}), stop: stopAll([source, ...lfos]) }\n}\n","import { LOOP_SMOOTHING, createLoopState } from '../loops'\nimport type { BusId, LoopRecipe } from '../recipe'\nimport type { RealizeTarget } from './nodes'\nimport { realizeChainLoop, realizeGameLoop } from './realize-loop'\nimport type { RealizedLoop } from './realize-loop'\n\n/** A continuous sound: `set` glides the volume and, for an oscillator, the frequency; `stop` tears the nodes down. */\nexport type AudioLoopHandle = {\n set(state: { readonly volume?: number; readonly frequency?: number }): void\n stop(): void\n}\n\nexport type LoopEntry = { rewire(): void; stop(): void; readonly created: boolean }\n\n/** What a loop needs from its engine. */\nexport type LoopHost = {\n ready(): AudioContext | null\n output(bus: BusId): AudioNode\n prepareNoise(): unknown\n noise(): AudioBuffer\n register(loop: LoopEntry): void\n unregister(loop: LoopEntry): void\n}\n\nconst INERT: AudioLoopHandle = { set() {}, stop() {} }\n\n/** A `game_loop` makes its nodes on the first audible `set`; a `chain_loop` makes them when it opens, at `volume` (default the recipe's). */\nexport function createLoopHandle(recipe: LoopRecipe | undefined, host: LoopHost, volume?: number): AudioLoopHandle {\n if (!recipe) return INERT\n const loopRecipe = recipe\n const isChain = loopRecipe.order === 'chain_loop'\n const opening = volume ?? loopRecipe.volume ?? 0\n const smoothing = loopRecipe.smoothing ?? LOOP_SMOOTHING\n let state = createLoopState(loopRecipe)\n let nodes: RealizedLoop | undefined\n\n const entry: LoopEntry = {\n get created() {\n return nodes !== undefined\n },\n rewire() {\n nodes?.gain.disconnect()\n nodes?.gain.connect(host.output(loopRecipe.bus))\n },\n stop() {\n nodes?.stop()\n nodes?.gain.disconnect()\n nodes = undefined\n state = createLoopState(loopRecipe)\n host.unregister(entry)\n },\n }\n\n const create = (context: AudioContext, startVolume: number): RealizedLoop => {\n if (loopRecipe.source.kind === 'noise') host.prepareNoise()\n const target: RealizeTarget = { context, output: host.output(loopRecipe.bus), noise: host.noise, time: context.currentTime }\n const made = isChain ? realizeChainLoop(loopRecipe, target, startVolume) : realizeGameLoop(loopRecipe, target)\n host.register(entry)\n return made\n }\n\n const open = (): void => {\n const context = host.ready()\n if (!context) return\n nodes = create(context, opening)\n state = createLoopState({ ...loopRecipe, volume: opening }, true)\n }\n if (isChain) open()\n\n return {\n set(next) {\n if (isChain && !nodes) open()\n const context = host.ready()\n if (!context) return\n const action = state.set(next)\n if (!action) return\n nodes ??= create(context, 0)\n nodes.gain.gain.setTargetAtTime(action.volume, context.currentTime, smoothing.volume)\n if (action.frequency !== undefined && nodes.frequency) nodes.frequency.setTargetAtTime(action.frequency, context.currentTime, smoothing.frequency)\n },\n stop: entry.stop,\n }\n}\n","import { bufferOf, fillNoise } from './noise'\n\n/** One noise buffer and the stream it is filled from. The samples are drawn before the sound that first needs them draws its own numbers. */\nexport type NoiseStore = { readonly random: () => number; samples?: Float32Array; buffer?: AudioBuffer; reset(): void }\n\nexport function createNoiseStore(random: () => number): NoiseStore {\n const store: NoiseStore = {\n random,\n reset() {\n store.samples = undefined\n store.buffer = undefined\n },\n }\n return store\n}\n\n/** Draws the samples (once): `sampleRate × seconds` of them, as the team code sizes its buffer. */\nexport function prepareNoise(store: NoiseStore, context: AudioContext, seconds: number): Float32Array {\n store.samples ??= fillNoise(store.random, Math.floor(context.sampleRate * seconds))\n return store.samples\n}\n\n/** The AudioBuffer (made on the first call, which is where the team code calls its `noise()`). */\nexport function noiseBuffer(store: NoiseStore, context: AudioContext, seconds: number): AudioBuffer {\n const samples = prepareNoise(store, context, seconds)\n store.buffer ??= bufferOf(context, [samples], context.sampleRate * seconds)\n return store.buffer\n}\n","// The game's routines (jogo.js), one builder each: the nodes are created, set and wired in the routine's own order, so the trace of a plan\n// matches the trace of the team function. The level is already in the envelope; every builder connects straight to the bus.\nimport type { PlannedFilter, PlannedLayer, SoundPlan } from '../plan'\nimport { applyEnvelope, assign, noiseSource, rampTo, voiceOf } from './nodes'\nimport type { RealizedVoice, RealizeTarget } from './nodes'\n\nconst end = (layer: PlannedLayer, start: number): number => start + layer.duration + layer.tail\nconst firstFilter = (layer: PlannedLayer): PlannedFilter | undefined => layer.filters?.bank[0]\nconst offsetOf = (layer: PlannedLayer): number => (layer.source.kind === 'noise' ? layer.source.offset : 0)\n\nfunction setFilter(node: BiquadFilterNode, filter: PlannedFilter | undefined, time: number): void {\n if (!filter) return\n node.type = filter.type\n assign(node.frequency, filter.frequency, time)\n if (filter.q !== undefined) node.Q.value = filter.q\n}\n\n/** `sfx` (L62089): gain to the bus first, then the noise, its lowpass, the envelope, and a random start in the noise. */\nexport function realizeBurst(plan: SoundPlan, target: RealizeTarget): RealizedVoice {\n const { context } = target\n const layer = plan.layers[0] as PlannedLayer\n const time = target.time + layer.at\n const gain = context.createGain()\n gain.connect(target.output)\n const source = noiseSource(target, layer.source.kind === 'noise' && layer.source.loop)\n const filter = context.createBiquadFilter()\n source.connect(filter)\n filter.connect(gain)\n setFilter(filter, firstFilter(layer), time)\n applyEnvelope(gain.gain, layer.envelope, time)\n source.start(time, offsetOf(layer))\n source.stop(end(layer, time))\n return voiceOf([source])\n}\n\n/** `grito` (L62128): noise through a parallel formant bank (each band with its own level), a gain LFO for roughness. */\nexport function realizeScream(plan: SoundPlan, target: RealizeTarget): RealizedVoice {\n const { context } = target\n const layer = plan.layers[0] as PlannedLayer\n const time = target.time + layer.at\n const source = noiseSource(target, layer.source.kind === 'noise' && layer.source.loop)\n const gain = context.createGain()\n gain.connect(target.output)\n for (const band of layer.filters?.bank ?? []) {\n const filter = context.createBiquadFilter()\n filter.type = band.type\n if (band.q !== undefined) filter.Q.value = band.q\n assign(filter.frequency, band.frequency, time)\n rampTo(filter.frequency, band.frequency, time)\n const level = context.createGain()\n level.gain.value = band.level ?? 1\n source.connect(filter)\n filter.connect(level)\n level.connect(gain)\n }\n const sources: AudioScheduledSourceNode[] = [source]\n const lfos = layer.modulators.map((modulator) => {\n const lfo = context.createOscillator()\n const depth = context.createGain()\n if (modulator.wave) lfo.type = modulator.wave\n lfo.frequency.value = modulator.rate\n depth.gain.value = modulator.depth\n lfo.connect(depth)\n depth.connect(gain.gain)\n return lfo\n })\n applyEnvelope(gain.gain, layer.envelope, time)\n source.start(time, offsetOf(layer))\n source.stop(end(layer, time))\n for (const lfo of lfos) {\n lfo.start(time)\n lfo.stop(end(layer, time))\n sources.push(lfo)\n }\n return voiceOf(sources)\n}\n\n/** `voz` (L62143): oscillator → bandpass → gain; the filter frequency is set after the pitch sweep. */\nexport function realizeGroan(plan: SoundPlan, target: RealizeTarget): RealizedVoice {\n const { context } = target\n const layer = plan.layers[0] as PlannedLayer\n const time = target.time + layer.at\n const oscillator = context.createOscillator()\n const gain = context.createGain()\n const filter = context.createBiquadFilter()\n const band = firstFilter(layer)\n if (layer.source.kind === 'osc') oscillator.type = layer.source.wave\n if (band) {\n filter.type = band.type\n if (band.q !== undefined) filter.Q.value = band.q\n }\n if (layer.source.kind === 'osc') {\n assign(oscillator.frequency, layer.source.frequency, time)\n rampTo(oscillator.frequency, layer.source.frequency, time)\n }\n if (band) assign(filter.frequency, band.frequency, time)\n applyEnvelope(gain.gain, layer.envelope, time)\n oscillator.connect(filter)\n filter.connect(gain)\n gain.connect(target.output)\n oscillator.start(time)\n oscillator.stop(end(layer, time))\n return voiceOf([oscillator])\n}\n\n/** `boom` (L68278): one oscillator sweeping down into a gain. */\nexport function realizeBoom(plan: SoundPlan, target: RealizeTarget): RealizedVoice {\n const { context } = target\n const layer = plan.layers[0] as PlannedLayer\n const time = target.time + layer.at\n const oscillator = context.createOscillator()\n const gain = context.createGain()\n if (layer.source.kind === 'osc') {\n oscillator.type = layer.source.wave\n assign(oscillator.frequency, layer.source.frequency, time)\n rampTo(oscillator.frequency, layer.source.frequency, time)\n }\n applyEnvelope(gain.gain, layer.envelope, time)\n oscillator.connect(gain)\n gain.connect(target.output)\n oscillator.start(time)\n oscillator.stop(end(layer, time))\n return voiceOf([oscillator])\n}\n\n/** `chiado` (L69998): gain, noise and bandpass created first, then set, then wired. */\nexport function realizeStatic(plan: SoundPlan, target: RealizeTarget): RealizedVoice {\n const { context } = target\n const layer = plan.layers[0] as PlannedLayer\n const time = target.time + layer.at\n const gain = context.createGain()\n const source = context.createBufferSource()\n const filter = context.createBiquadFilter()\n source.buffer = target.noise()\n setFilter(filter, firstFilter(layer), time)\n applyEnvelope(gain.gain, layer.envelope, time)\n source.connect(filter)\n filter.connect(gain)\n gain.connect(target.output)\n source.start(time, offsetOf(layer))\n source.stop(end(layer, time))\n return voiceOf([source])\n}\n\n/** `buzina` (L70272): one oscillator per layer into one gain with the first layer's envelope. */\nexport function realizeChord(plan: SoundPlan, target: RealizeTarget): RealizedVoice {\n const { context } = target\n const layers = plan.layers\n const oscillators = layers.map(() => context.createOscillator())\n const gain = context.createGain()\n // the team writes `o.type = o2.type = 'square'`, which assigns right to left\n for (let index = layers.length - 1; index >= 0; index--) {\n const source = (layers[index] as PlannedLayer).source\n if (source.kind === 'osc') (oscillators[index] as OscillatorNode).type = source.wave\n }\n layers.forEach((layer, index) => {\n if (layer.source.kind === 'osc') assign((oscillators[index] as OscillatorNode).frequency, layer.source.frequency, target.time + layer.at)\n })\n applyEnvelope(gain.gain, (layers[0] as PlannedLayer).envelope, target.time + (layers[0] as PlannedLayer).at)\n for (const oscillator of oscillators) oscillator.connect(gain)\n gain.connect(target.output)\n layers.forEach((layer, index) => (oscillators[index] as OscillatorNode).start(target.time + layer.at))\n layers.forEach((layer, index) => (oscillators[index] as OscillatorNode).stop(end(layer, target.time + layer.at)))\n return voiceOf(oscillators)\n}\n\n/**\n * `bichoSom` (L66625): gain and filter are created before the level is checked (a silent plan stops there), then each repeat is an oscillator\n * with its own envelope into the shared filter, whose frequency and Q are set again on every repeat.\n */\nexport function realizeCalls(plan: SoundPlan, target: RealizeTarget): RealizedVoice {\n const { context } = target\n const gain = context.createGain()\n const filter = context.createBiquadFilter()\n if (plan.silent) return voiceOf([])\n const band = firstFilter(plan.layers[0] as PlannedLayer)\n if (band) filter.type = band.type\n filter.connect(gain)\n gain.connect(target.output)\n const oscillators = plan.layers.map((layer) => {\n const time = target.time + layer.at\n const oscillator = context.createOscillator()\n if (layer.source.kind === 'osc') {\n oscillator.type = layer.source.wave\n assign(oscillator.frequency, layer.source.frequency, time)\n rampTo(oscillator.frequency, layer.source.frequency, time)\n }\n if (band) {\n assign(filter.frequency, band.frequency, time)\n if (band.q !== undefined) filter.Q.value = band.q\n }\n const envelope = context.createGain()\n applyEnvelope(envelope.gain, layer.envelope, time)\n oscillator.connect(envelope)\n envelope.connect(filter)\n oscillator.start(time)\n oscillator.stop(end(layer, time))\n return oscillator\n })\n return voiceOf(oscillators)\n}\n","// The art scene's primitives (cena L1319–1328, L1369): `sNoise`, `sTone` and `voice`, each layer with its own output stage (`outAt`: distance\n// gain, pan, reverb send), and the siren straight to the bus. Built in the team's order so the traces match node for node.\nimport type { PlannedLayer, SoundPlan } from '../plan'\nimport { applyEnvelope, assign, filterNode, gainNode, noiseSource, rampTo, voiceOf } from './nodes'\nimport type { RealizedVoice, RealizeTarget } from './nodes'\n\nconst end = (layer: PlannedLayer, start: number): number => start + layer.duration + layer.tail\n\n/** `outAt`: the layer level, the stereo panner when the plan pans and the context has one, the bus, then the reverb send. */\nfunction outputStage(plan: SoundPlan, layer: PlannedLayer, target: RealizeTarget): GainNode {\n const { context } = target\n const level = gainNode(context, layer.level)\n let node: AudioNode = level\n if (plan.pan !== undefined && typeof context.createStereoPanner === 'function') {\n const panner = context.createStereoPanner()\n panner.pan.value = plan.pan\n level.connect(panner)\n node = panner\n }\n node.connect(target.output)\n if (layer.wet && target.wet) {\n const send = gainNode(context, layer.wet)\n node.connect(send)\n send.connect(target.wet)\n }\n return level\n}\n\nfunction realizeNoiseLayer(plan: SoundPlan, layer: PlannedLayer, target: RealizeTarget): AudioScheduledSourceNode[] {\n const { context } = target\n const time = target.time + layer.at\n const source = noiseSource(target, layer.source.kind === 'noise' && layer.source.loop)\n const filters = (layer.filters?.bank ?? []).map((filter) => {\n const node = filterNode(context, filter, time)\n rampTo(node.frequency, filter.frequency, time)\n return node\n })\n const envelope = context.createGain()\n applyEnvelope(envelope.gain, layer.envelope, time)\n let tail: AudioNode = source\n for (const filter of filters) tail = tail.connect(filter)\n tail.connect(envelope)\n envelope.connect(outputStage(plan, layer, target))\n source.start(time, layer.source.kind === 'noise' ? layer.source.offset : 0)\n source.stop(end(layer, time))\n return [source]\n}\n\nfunction realizeToneLayer(plan: SoundPlan, layer: PlannedLayer, target: RealizeTarget): AudioScheduledSourceNode[] {\n const { context } = target\n const time = target.time + layer.at\n const oscillator = context.createOscillator()\n if (layer.source.kind === 'osc') {\n oscillator.type = layer.source.wave\n assign(oscillator.frequency, layer.source.frequency, time)\n rampTo(oscillator.frequency, layer.source.frequency, time)\n }\n const envelope = context.createGain()\n applyEnvelope(envelope.gain, layer.envelope, time)\n oscillator.connect(envelope)\n envelope.connect(outputStage(plan, layer, target))\n oscillator.start(time)\n oscillator.stop(end(layer, time))\n return [oscillator]\n}\n\nfunction realizeVoiceLayer(plan: SoundPlan, layer: PlannedLayer, target: RealizeTarget): AudioScheduledSourceNode[] {\n const source = layer.source\n if (source.kind !== 'formant_voice') return []\n const { context } = target\n const time = target.time + layer.at\n const oscillator = context.createOscillator()\n oscillator.type = source.wave\n assign(oscillator.frequency, source.frequency, time)\n rampTo(oscillator.frequency, source.frequency, time)\n const vibrato = context.createOscillator()\n vibrato.frequency.value = source.vibrato.rate\n const vibratoDepth = gainNode(context, source.vibrato.depth)\n vibrato.connect(vibratoDepth)\n vibratoDepth.connect(oscillator.frequency)\n const formants = source.formants.map((formant) => filterNode(context, { type: 'bandpass', frequency: { value: formant.frequency }, q: formant.q }, time))\n const breath = noiseSource(target, true)\n const breathBand = filterNode(context, { type: 'bandpass', frequency: { value: source.breath.frequency }, q: source.breath.q }, time)\n const breathLevel = gainNode(context, source.breath.gain)\n const mix = gainNode(context, source.mix)\n for (const formant of formants) oscillator.connect(formant)\n breath.connect(breathBand)\n breathBand.connect(breathLevel)\n for (const formant of formants) formant.connect(mix)\n breathLevel.connect(mix)\n const envelope = context.createGain()\n applyEnvelope(envelope.gain, layer.envelope, time)\n mix.connect(envelope)\n envelope.connect(outputStage(plan, layer, target))\n const stop = end(layer, time)\n oscillator.start(time)\n vibrato.start(time)\n breath.start(time, source.breath.offset)\n oscillator.stop(stop)\n vibrato.stop(stop)\n breath.stop(stop)\n return [oscillator, vibrato, breath]\n}\n\n/** Every layer in order, each with its own output stage. */\nexport function realizeLayered(plan: SoundPlan, target: RealizeTarget): RealizedVoice {\n const sources = plan.layers.flatMap((layer) => {\n if (layer.source.kind === 'noise') return realizeNoiseLayer(plan, layer, target)\n if (layer.source.kind === 'osc') return realizeToneLayer(plan, layer, target)\n return realizeVoiceLayer(plan, layer, target)\n })\n return voiceOf(sources)\n}\n\n/** The siren: oscillator with a frequency LFO, envelope, lowpass, straight to the bus with a reverb send. */\nexport function realizeSiren(plan: SoundPlan, target: RealizeTarget): RealizedVoice {\n const { context } = target\n const layer = plan.layers[0] as PlannedLayer\n const time = target.time + layer.at\n const oscillator = context.createOscillator()\n if (layer.source.kind === 'osc') {\n oscillator.type = layer.source.wave\n assign(oscillator.frequency, layer.source.frequency, time)\n rampTo(oscillator.frequency, layer.source.frequency, time)\n }\n const depths = layer.modulators.map((modulator) => {\n const lfo = context.createOscillator()\n if (modulator.wave) lfo.type = modulator.wave\n lfo.frequency.value = modulator.rate\n const depth = gainNode(context, modulator.depth)\n lfo.connect(depth)\n if (modulator.target === 'frequency') depth.connect(oscillator.frequency)\n return { lfo, depth, target: modulator.target }\n })\n const lfos = depths.map(({ lfo }) => lfo)\n const envelope = context.createGain()\n applyEnvelope(envelope.gain, layer.envelope, time)\n for (const { depth, target: modulated } of depths) if (modulated === 'gain') depth.connect(envelope.gain)\n const filters = (layer.filters?.bank ?? []).map((filter) => filterNode(context, filter, time))\n let tail: AudioNode = oscillator\n for (const filter of filters) tail = tail.connect(filter)\n tail.connect(envelope)\n envelope.connect(target.output)\n if (layer.wet && target.wet) {\n const send = gainNode(context, layer.wet)\n envelope.connect(send)\n send.connect(target.wet)\n }\n const stop = end(layer, time)\n oscillator.start(time)\n for (const lfo of lfos) lfo.start(time)\n oscillator.stop(stop)\n for (const lfo of lfos) lfo.stop(stop)\n return voiceOf([oscillator, ...lfos])\n}\n","// The weapon audio of the kit (`weapons-three`, first written against raw WebAudio): per layer, either a noise burst (source, filter, envelope gain)\n// or an oscillator (oscillator, envelope gain), created in that order and chained straight to the bus. The level is already in the envelope.\nimport type { PlannedLayer, SoundPlan } from '../plan'\nimport { applyEnvelope, assign, filterNode, noiseSource, rampTo, voiceOf } from './nodes'\nimport type { RealizedVoice, RealizeTarget } from './nodes'\n\nfunction noiseLayer(layer: PlannedLayer, target: RealizeTarget, time: number): AudioScheduledSourceNode {\n const { context } = target\n const source = noiseSource(target, layer.source.kind === 'noise' && layer.source.loop)\n const filters = (layer.filters?.bank ?? []).map((filter) => filterNode(context, filter, time))\n const envelope = context.createGain()\n applyEnvelope(envelope.gain, layer.envelope, time)\n let tail: AudioNode = source\n for (const filter of filters) tail = tail.connect(filter)\n tail.connect(envelope).connect(target.output)\n source.start(time, layer.source.kind === 'noise' ? layer.source.offset : 0)\n source.stop(time + layer.duration + layer.tail)\n return source\n}\n\nfunction toneLayer(layer: PlannedLayer, target: RealizeTarget, time: number): AudioScheduledSourceNode {\n const { context } = target\n const oscillator = context.createOscillator()\n if (layer.source.kind === 'osc') {\n oscillator.type = layer.source.wave\n assign(oscillator.frequency, layer.source.frequency, time)\n rampTo(oscillator.frequency, layer.source.frequency, time)\n }\n const envelope = context.createGain()\n applyEnvelope(envelope.gain, layer.envelope, time)\n oscillator.connect(envelope).connect(target.output)\n oscillator.start(time)\n oscillator.stop(time + layer.duration + layer.tail)\n return oscillator\n}\n\n/** One noise burst or one tone per layer, in the order written. */\nexport function realizeWeapon(plan: SoundPlan, target: RealizeTarget): RealizedVoice {\n const sources = plan.layers.map((layer) => {\n const time = target.time + layer.at\n return layer.source.kind === 'noise' ? noiseLayer(layer, target, time) : toneLayer(layer, target, time)\n })\n return voiceOf(sources)\n}\n","import type { SoundPlan } from '../plan'\nimport type { BuildOrder } from '../recipe'\nimport type { RealizedVoice, RealizeTarget } from './nodes'\nimport { realizeBoom, realizeBurst, realizeCalls, realizeChord, realizeGroan, realizeScream, realizeStatic } from './realize-game'\nimport { realizeLayered, realizeSiren } from './realize-layered'\nimport { realizeWeapon } from './realize-weapon'\n\nconst BUILDERS: Readonly<Record<BuildOrder, (plan: SoundPlan, target: RealizeTarget) => RealizedVoice>> = {\n layered: realizeLayered,\n burst: realizeBurst,\n scream: realizeScream,\n groan: realizeGroan,\n boom: realizeBoom,\n static: realizeStatic,\n chord: realizeChord,\n calls: realizeCalls,\n siren: realizeSiren,\n weapon: realizeWeapon,\n}\n\n/** Builds a plan's nodes on `target`, in the node order of the team routine its `order` names. */\nexport function realize(plan: SoundPlan, target: RealizeTarget): RealizedVoice {\n return BUILDERS[plan.order](plan, target)\n}\n","import type { Vec3 } from '@adatechnology/game-contracts'\n\nimport { createSoundRegistry } from '../bank'\nimport type { SoundRegistry } from '../bank'\nimport { falloffGain, isSilent } from '../falloff'\nimport { DEFAULT_LISTENER, listenerDistance } from '../listener'\nimport type { DistanceMode, Listener } from '../listener'\nimport { DEFAULT_MIX, busGain, resolveMix } from '../mix'\nimport type { BusSetting, MixProfile } from '../mix'\nimport { planSound } from '../plan'\nimport type { PlanInput, SoundPlan } from '../plan'\nimport type { BusId, SoundBank, SoundRecipe } from '../recipe'\nimport { createRng, streamFor } from '../rng'\nimport { busPriority, createVoiceManager } from '../voices'\nimport type { VoiceGroup } from '../voices'\nimport { buildChain } from './chain'\nimport type { MixChain } from './chain'\nimport { globalContext, unlockContext } from './context'\nimport { createLoopHandle } from './engine-loop'\nimport type { AudioLoopHandle, LoopHost } from './engine-loop'\nimport { createNoiseStore, noiseBuffer, prepareNoise } from './engine-noise'\nimport type { NoiseStore } from './engine-noise'\nimport type { RealizedVoice } from './nodes'\nimport { realize } from './realize'\n\nexport type PlayOptions = {\n readonly position?: Vec3\n /** Meters; overrides `position` for the distance (not for the pan). */\n readonly distance?: number\n readonly volume?: number\n readonly params?: Readonly<Record<string, number>>\n /** This sound's own stream: same seed, same sound. */\n readonly seed?: number\n /** Seconds from now. */\n readonly delay?: number\n /** A named stream of the engine (`streamFor(seed, name)`), so one subject never shifts another's sounds. */\n readonly channel?: string\n}\n\nexport type VoiceHandle = { readonly id: number; readonly plan: SoundPlan; stop(): void }\n\n/** A subject's own random stream; `ownNoise` also gives it its own noise buffer, filled from that stream (the order `createWeaponAudio` uses). */\nexport type AudioChannel = {\n readonly random: () => number\n play(id: string, options?: PlayOptions): VoiceHandle | null\n plan(id: string, options?: PlayOptions): SoundPlan | null\n}\n\nexport type AudioStats = {\n readonly context: 'none' | AudioContextState\n readonly voices: number\n readonly peak: number\n readonly dropped: number\n readonly maxVoices: number\n readonly loops: number\n}\n\nexport type AudioEngineOptions = {\n /** Seeds the engine stream: the noise, the reverb impulse and every sound played without its own seed or channel. Default 1. */\n readonly seed?: number\n /** Default `art_scene` (Q1). */\n readonly mix?: string | MixProfile\n readonly profiles?: Readonly<Record<string, MixProfile>>\n readonly banks?: readonly SoundBank[]\n readonly maxVoices?: number\n readonly groups?: Readonly<Record<string, VoiceGroup>>\n readonly distance?: DistanceMode\n /** 0 (clear) to 1 (behind a wall) for a sound at a point; without it there is no occlusion (Q8). */\n readonly occlusion?: (point: Vec3) => number\n readonly createContext?: () => AudioContext | null\n}\n\nexport type AudioEngine = {\n readonly registry: SoundRegistry\n register(bank: SoundBank): void\n /** Pure: the plan a sound would play with, from `options.seed` or the engine seed; draws nothing from the live streams. */\n plan(id: string, options?: PlayOptions): SoundPlan | null\n play(id: string, options?: PlayOptions): VoiceHandle | null\n playPlan(plan: SoundPlan, options?: { readonly delay?: number }): VoiceHandle | null\n loop(id: string, options?: { readonly volume?: number }): AudioLoopHandle\n channel(options: { readonly seed?: number; readonly random?: () => number; readonly ownNoise?: boolean }): AudioChannel\n setListener(listener: Listener): void\n setBus(id: BusId, setting: BusSetting): void\n setMix(mix: string | MixProfile): void\n /** The master volume of the current chain and of any later one (0 to 1). */\n setMaster(gain: number): void\n /** Creates the context and the mixing chain if needed and resumes it; call from a click or a key. */\n unlock(): void\n stats(): AudioStats\n /** RMS (0 to 1) of what a bus carries now, read from an analyser the first time you ask; sounds started before that do not reach it. 0 without a context. */\n level(bus: BusId): number\n dispose(): void\n}\n\nconst usesNoise = (recipe: SoundRecipe): boolean => recipe.layers.some((layer) => layer.source.kind !== 'osc')\nconst planUsesNoise = (plan: SoundPlan): boolean => plan.layers.some((layer) => layer.source.kind !== 'osc')\n\n/**\n * The sound engine: plans recipes and builds them on a lazily created AudioContext through one of the team mixing chains. Without an AudioContext\n * (Node, workers) every call except `plan` does nothing.\n */\nexport function createAudioEngine(options: AudioEngineOptions = {}): AudioEngine {\n const seed = options.seed ?? 1\n const registry = createSoundRegistry(options.banks)\n const voices = createVoiceManager({ ...(options.groups ? { groups: options.groups } : {}), ...(options.maxVoices === undefined ? {} : { maxVoices: options.maxVoices }) })\n const engineRandom = createRng(seed)\n const shared = createNoiseStore(engineRandom)\n const busSettings = new Map<BusId, BusSetting>()\n const busNodes = new Map<BusId, GainNode>()\n const meters = new Map<BusId, { readonly analyser: AnalyserNode; readonly data: Float32Array<ArrayBuffer> }>()\n const active = new Map<number, RealizedVoice>()\n const loops = new Set<{ rewire(): void; stop(): void; readonly created: boolean }>()\n const named = new Map<string, () => number>()\n let mix = resolveMix(options.mix ?? DEFAULT_MIX, options.profiles)\n let context: AudioContext | null = null\n let chain: MixChain | null = null\n let listener: Listener = DEFAULT_LISTENER\n let lastTime = 0\n\n const outputFor = (bus: BusId): AudioNode => (busNodes.get(bus) ?? chain?.master) as AudioNode\n\n // a bus somebody asked the level of gets an analyser as soon as it has a node, so sounds started before the first reading are metered too\n const watched = new Set<BusId>()\n const meterOf = (bus: BusId): { readonly analyser: AnalyserNode; readonly data: Float32Array<ArrayBuffer> } | undefined => {\n const existing = meters.get(bus)\n if (existing) return existing\n const node = busNodes.get(bus)\n if (!context || !node || typeof context.createAnalyser !== 'function') return undefined\n const analyser = context.createAnalyser()\n analyser.fftSize = 1024\n node.connect(analyser)\n const made = { analyser, data: new Float32Array(analyser.fftSize) }\n meters.set(bus, made)\n return made\n }\n\n const ensureBus = (bus: BusId): void => {\n if (!context || !chain || busNodes.has(bus)) return\n const node = context.createGain()\n node.connect(chain.master)\n busNodes.set(bus, node)\n if (watched.has(bus)) meterOf(bus)\n }\n\n const ready = (): AudioContext | null => {\n if (context) return context\n const created = (options.createContext ?? globalContext)()\n if (!created) return null\n context = created\n chain = buildChain(created, mix, engineRandom)\n if (mix.noise.create === 'eager') noiseBuffer(shared, created, mix.noise.seconds)\n for (const [bus, setting] of Object.entries(mix.buses ?? {}) as [BusId, BusSetting][]) {\n busSettings.set(bus, { ...setting, ...busSettings.get(bus) })\n }\n for (const [bus, setting] of busSettings) {\n ensureBus(bus)\n ;(busNodes.get(bus) as GainNode).gain.value = busGain(setting)\n }\n lastTime = created.currentTime\n return created\n }\n\n const advance = (): void => {\n if (!context) return\n const elapsed = context.currentTime - lastTime\n lastTime = context.currentTime\n if (elapsed > 0) for (const id of voices.advance(elapsed)) active.delete(id)\n }\n\n const inputFor = (recipe: SoundRecipe, playOptions: PlayOptions, random: () => number): PlanInput => ({\n random,\n listener,\n noiseSeconds: mix.noise.seconds,\n ...(options.distance ? { distanceMode: options.distance } : {}),\n ...(playOptions.volume === undefined ? {} : { volume: playOptions.volume }),\n ...(playOptions.distance === undefined ? {} : { distance: playOptions.distance }),\n ...(playOptions.position ? { position: playOptions.position } : {}),\n ...(playOptions.params ? { params: playOptions.params } : {}),\n ...(options.occlusion && playOptions.position && recipe.falloff?.model !== 'none' ? { occlusion: options.occlusion(playOptions.position) } : {}),\n })\n\n const start = (plan: SoundPlan, store: NoiseStore, voiceId: number | undefined, delay = 0): VoiceHandle | null => {\n const audio = context as AudioContext\n const realized = realize(plan, {\n context: audio,\n output: outputFor(plan.bus),\n ...(chain?.wet ? { wet: chain.wet } : {}),\n noise: () => noiseBuffer(store, audio, mix.noise.seconds),\n time: audio.currentTime + delay,\n })\n if (voiceId === undefined) return null\n voices.setLength(voiceId, plan.length + delay)\n active.set(voiceId, realized)\n return {\n id: voiceId,\n plan,\n stop() {\n realized.stop()\n voices.release(voiceId)\n active.delete(voiceId)\n },\n }\n }\n\n const admit = (recipe: SoundRecipe, gain: number, distance: number): number | null => {\n const decision = voices.request({ ...(recipe.group ? { group: recipe.group } : {}), priority: recipe.priority ?? busPriority(recipe.bus), gain, distance, length: 0 })\n if (!decision.accepted) return null\n for (const id of decision.evicted) {\n active.get(id)?.stop()\n active.delete(id)\n }\n return decision.id\n }\n\n const playWith = (id: string, playOptions: PlayOptions, random: () => number, store: NoiseStore): VoiceHandle | null => {\n const recipe = registry.sound(id)\n if (!recipe || busSettings.get(recipe.bus)?.muted) return null\n const distance = playOptions.distance ?? (playOptions.position ? listenerDistance(listener, playOptions.position, options.distance) : 0)\n const level = falloffGain(recipe.falloff, playOptions.volume ?? 1, distance)\n const silent = isSilent(recipe.falloff, level)\n // a sound too quiet to play does not even start the context (the team routines return before touching it)\n if (silent && !recipe.keepSilent) return null\n const audio = ready()\n if (!audio) return null\n unlockContext(audio)\n advance()\n const voiceId = silent ? undefined : admit(recipe, level, distance)\n if (voiceId === null) return null\n if (!silent && usesNoise(recipe)) prepareNoise(store, audio, mix.noise.seconds)\n const plan = planSound(recipe, inputFor(recipe, playOptions, playOptions.seed === undefined ? random : createRng(playOptions.seed)))\n return plan ? start(plan, store, voiceId, playOptions.delay) : null\n }\n\n const randomFor = (playOptions: PlayOptions): (() => number) => {\n if (playOptions.channel === undefined) return engineRandom\n const existing = named.get(playOptions.channel)\n if (existing) return existing\n const stream = streamFor(seed, playOptions.channel)\n named.set(playOptions.channel, stream)\n return stream\n }\n\n const planWith = (id: string, playOptions: PlayOptions, random: () => number): SoundPlan | null => {\n const recipe = registry.sound(id)\n return recipe ? planSound(recipe, inputFor(recipe, playOptions, random)) : null\n }\n\n const loopHost: LoopHost = {\n ready,\n output: outputFor,\n prepareNoise: () => context && prepareNoise(shared, context, mix.noise.seconds),\n noise: () => noiseBuffer(shared, context as AudioContext, mix.noise.seconds),\n register: (loop) => void loops.add(loop),\n unregister: (loop) => void loops.delete(loop),\n }\n\n return {\n registry,\n register: (bank) => registry.register(bank),\n plan: (id, playOptions = {}) => planWith(id, playOptions, createRng(playOptions.seed ?? seed)),\n play: (id, playOptions = {}) => playWith(id, playOptions, randomFor(playOptions), shared),\n playPlan(plan, playOptions = {}) {\n const audio = ready()\n if (!audio) return null\n advance()\n const recipe = { id: plan.id, bus: plan.bus, layers: [], sources: [], priority: plan.priority, ...(plan.group ? { group: plan.group } : {}) }\n const voiceId = plan.silent ? undefined : admit(recipe, plan.level, plan.distance)\n if (voiceId === null) return null\n if (planUsesNoise(plan)) prepareNoise(shared, audio, mix.noise.seconds)\n return start(plan, shared, voiceId, playOptions.delay)\n },\n loop: (id, loopOptions = {}) => createLoopHandle(registry.loop(id), loopHost, loopOptions.volume),\n channel(channelOptions) {\n const random = channelOptions.random ?? (channelOptions.seed === undefined ? engineRandom : createRng(channelOptions.seed))\n const store = channelOptions.ownNoise ? createNoiseStore(random) : shared\n return {\n random,\n play: (id, playOptions = {}) => playWith(id, playOptions, random, store),\n plan: (id, playOptions = {}) => planWith(id, playOptions, createRng(playOptions.seed ?? seed)),\n }\n },\n setListener(next) {\n listener = next\n },\n setBus(id, setting) {\n busSettings.set(id, { ...busSettings.get(id), ...setting })\n if (!context) return\n ensureBus(id)\n ;(busNodes.get(id) as GainNode).gain.value = busGain(busSettings.get(id))\n },\n setMix(next) {\n const previous = mix\n mix = resolveMix(next, options.profiles)\n if (!context || !chain) return\n const old = chain\n chain = buildChain(context, mix, engineRandom)\n if (mix.noise.seconds !== previous.noise.seconds) shared.reset()\n for (const node of busNodes.values()) {\n node.disconnect()\n node.connect(chain.master)\n }\n for (const loop of loops) loop.rewire()\n old.disconnect()\n },\n setMaster(gain) {\n mix = { ...mix, master: gain }\n if (chain) chain.master.gain.value = gain\n },\n unlock() {\n const audio = ready()\n if (audio) unlockContext(audio)\n },\n level(bus) {\n watched.add(bus)\n ensureBus(bus)\n const meter = meterOf(bus)\n if (!meter) return 0\n meter.analyser.getFloatTimeDomainData(meter.data)\n let sum = 0\n for (const sample of meter.data) sum += sample * sample\n return Math.sqrt(sum / meter.data.length)\n },\n stats() {\n advance()\n const voiceStats = voices.stats()\n return {\n context: context ? context.state : 'none',\n voices: voiceStats.active,\n peak: voiceStats.peak,\n dropped: voiceStats.dropped,\n maxVoices: voiceStats.maxVoices,\n loops: [...loops].filter((loop) => loop.created).length,\n }\n },\n dispose() {\n // closing the context ends every voice; they only leave the limiter\n for (const id of active.keys()) voices.release(id)\n for (const loop of [...loops]) loop.stop()\n active.clear()\n void context?.close()\n context = null\n chain = null\n busNodes.clear()\n meters.clear()\n shared.reset()\n },\n }\n}\n","import type { AudioListener, AudioSink, LoopHandle, SoundHandle, SoundRequest } from '@adatechnology/game-contracts'\n\nimport type { SoundBank } from '../recipe'\nimport type { AudioEngine, VoiceHandle } from './engine'\n\n/** An `AudioSink` (what a host lends its modules) that carries the engine behind it, so a module that wants more than `play` can use the engine. */\nexport type EngineSink = AudioSink & { readonly engine: AudioEngine }\n\n/** Lends an engine as the host's `audio`: sounds by key, loops, the listener, and banks registered into the same engine. */\nexport function createAudioSink(engine: AudioEngine): EngineSink {\n const playing = new Map<number, VoiceHandle>()\n return {\n engine,\n play(request: SoundRequest): SoundHandle | null {\n const handle = engine.play(request.sound, {\n ...(request.position ? { position: request.position } : {}),\n ...(request.volume === undefined ? {} : { volume: request.volume }),\n ...(request.params ? { params: request.params } : {}),\n ...(request.seed === undefined ? {} : { seed: request.seed }),\n })\n if (!handle) return null\n playing.set(handle.id, handle)\n return { id: handle.id }\n },\n stop(handle) {\n playing.get(handle.id)?.stop()\n playing.delete(handle.id)\n },\n loop(sound): LoopHandle {\n const loop = engine.loop(sound)\n return { set: (state) => loop.set({ ...(state.volume === undefined ? {} : { volume: state.volume }), ...(state.frequency === undefined ? {} : { frequency: state.frequency }) }), stop: () => loop.stop() }\n },\n setListener(listener: AudioListener) {\n engine.setListener(listener)\n },\n register(bank: unknown) {\n engine.register(bank as SoundBank)\n },\n }\n}\n","export type AudioBenchText = {\n readonly group: string\n readonly name: string\n readonly probe: string\n readonly controls: Readonly<Record<'sound' | 'distance' | 'bearing' | 'volume' | 'mix' | 'seed' | 'play' | 'voice_storm' | 'loop' | 'loop_level' | 'loop_pitch' | 'source', string>>\n readonly hints: Readonly<Record<'distance' | 'bearing' | 'mix' | 'seed' | 'voice_storm' | 'loop_pitch' | 'source', string>>\n readonly options: Readonly<Record<'none' | 'bench' | 'probe', string>>\n readonly layers: Readonly<Record<'reverb' | 'compressor' | 'panning' | 'muffling', string>>\n readonly metrics: Readonly<Record<'voices' | 'peak' | 'dropped' | 'loops' | 'context' | 'plan_length' | 'plan_level' | 'plan_pan' | 'level_weapons' | 'level_voices' | 'level_world' | 'level_ambience' | 'level_weather' | 'level_ui', string>>\n readonly scenarios: Readonly<Record<'panning' | 'distance' | 'determinism' | 'limiter', string>>\n readonly mixes: Readonly<Record<string, string>>\n /** Text of a sound or loop id when the game has one; the bench falls back to the id. */\n readonly sheet: Readonly<Record<'title' | 'level' | 'distance' | 'pan' | 'length' | 'silent' | 'layer' | 'none', string>>\n}\n\nconst EN: AudioBenchText = {\n group: 'Sound',\n name: 'Sound area',\n probe: 'Sound probe',\n controls: {\n sound: 'Sound',\n distance: 'Distance',\n bearing: 'Bearing',\n volume: 'Volume',\n mix: 'Mixing chain',\n seed: 'Seed',\n play: 'Play',\n voice_storm: 'Voice storm',\n loop: 'Loop',\n loop_level: 'Loop level',\n loop_pitch: 'Loop pitch',\n source: 'Source',\n },\n hints: {\n distance: 'Metres from the listener: the level falls and the sound is muffled with it.',\n bearing: 'Degrees from straight ahead, positive to the right: shows the panning.',\n mix: 'The game chain (master only) or the art scene chain (compressor and reverb).',\n seed: 'The same seed gives the same sound.',\n voice_storm: 'Twenty of the chosen sound at once: the voice limiter drops what its group and the global cap refuse.',\n loop_pitch: 'Frequency of an oscillator loop, in hertz (0 leaves it where it is).',\n source: 'Where the sound comes from: the distance and bearing set here, or the probe standing in the area.',\n },\n options: { none: 'None', bench: 'Bench settings', probe: 'The probe' },\n layers: { reverb: 'Reverb', compressor: 'Compressor', panning: 'Panning', muffling: 'Muffling by distance' },\n metrics: {\n voices: 'Voices playing',\n peak: 'Most voices at once',\n dropped: 'Voices dropped',\n loops: 'Loops running',\n context: 'Audio context',\n plan_length: 'Chosen sound: length',\n plan_level: 'Chosen sound: level',\n plan_pan: 'Chosen sound: pan',\n level_weapons: 'Level: weapons',\n level_voices: 'Level: voices',\n level_world: 'Level: world',\n level_ambience: 'Level: ambience',\n level_weather: 'Level: weather',\n level_ui: 'Level: interface',\n },\n scenarios: {\n panning: 'A sound to the right is heard on the right, one to the left on the left, one ahead in the middle',\n distance: 'The farther a sound, the quieter, until it is not played',\n determinism: 'The same seed gives the same sound plan, another seed another one',\n limiter: 'The voice limiter holds a storm of voices to its group rule and its global cap',\n },\n mixes: { game: 'Game', art_scene: 'Art scene' },\n sheet: { title: 'Sound plan', level: 'level', distance: 'distance', pan: 'pan', length: 'seconds', silent: 'too far to be heard', layer: 'layer', none: 'no sound chosen' },\n}\n\nconst PT_BR: AudioBenchText = {\n group: 'Som',\n name: 'Área de som',\n probe: 'Sonda de som',\n controls: {\n sound: 'Som',\n distance: 'Distância',\n bearing: 'Direção',\n volume: 'Volume',\n mix: 'Cadeia de mixagem',\n seed: 'Semente',\n play: 'Tocar',\n voice_storm: 'Tempestade de vozes',\n loop: 'Laço',\n loop_level: 'Nível do laço',\n loop_pitch: 'Altura do laço',\n source: 'Origem',\n },\n hints: {\n distance: 'Metros até o ouvinte: o nível cai e o som abafa junto.',\n bearing: 'Graus a partir da frente, positivo para a direita: mostra o pan.',\n mix: 'A cadeia do jogo (só o master) ou a da Cena de Arte (compressor e reverb).',\n seed: 'A mesma semente dá o mesmo som.',\n voice_storm: 'Vinte do som escolhido de uma vez: o limite de vozes derruba o que a regra do grupo e o teto global recusam.',\n loop_pitch: 'Frequência de um laço de oscilador, em hertz (0 deixa onde está).',\n source: 'De onde vem o som: a distância e a direção daqui, ou a sonda que está na área.',\n },\n options: { none: 'Nenhum', bench: 'Ajustes da bancada', probe: 'A sonda' },\n layers: { reverb: 'Reverb', compressor: 'Compressor', panning: 'Pan', muffling: 'Abafamento pela distância' },\n metrics: {\n voices: 'Vozes tocando',\n peak: 'Mais vozes de uma vez',\n dropped: 'Vozes derrubadas',\n loops: 'Laços ligados',\n context: 'Contexto de áudio',\n plan_length: 'Som escolhido: duração',\n plan_level: 'Som escolhido: nível',\n plan_pan: 'Som escolhido: pan',\n level_weapons: 'Nível: armas',\n level_voices: 'Nível: vozes',\n level_world: 'Nível: mundo',\n level_ambience: 'Nível: ambiente',\n level_weather: 'Nível: clima',\n level_ui: 'Nível: interface',\n },\n scenarios: {\n panning: 'Um som à direita sai à direita, à esquerda sai à esquerda, à frente sai no meio',\n distance: 'Quanto mais longe, mais baixo, até não tocar',\n determinism: 'A mesma semente dá o mesmo plano de som, outra semente dá outro',\n limiter: 'O limite de vozes segura uma tempestade de vozes na regra do grupo e no teto global',\n },\n mixes: { game: 'Jogo', art_scene: 'Cena de Arte' },\n sheet: { title: 'Plano do som', level: 'nível', distance: 'distância', pan: 'pan', length: 'segundos', silent: 'longe demais para ouvir', layer: 'camada', none: 'nenhum som escolhido' },\n}\n\nexport const audioBenchTextOf = (locale: string): AudioBenchText => (locale === 'en' ? EN : PT_BR)\n","import type { AreaPlacement, BenchChange, ChoiceOption, ModuleControl, ModuleHost, ModuleLayer, ModuleMetric, ModuleScenario, TestableItem, TestableModule, Vec3 } from '@adatechnology/game-contracts'\n\nimport { GAME_SFX_BANK } from '../recipes/noise-class'\nimport { LIBRARY_BANK } from '../library'\nimport { DISTANCE_CURVES } from '../falloff'\nimport { MIX_PROFILES } from '../mix'\nimport type { MixProfile } from '../mix'\nimport { BUS_IDS } from '../recipe'\nimport type { BusId, Layer, SoundBank } from '../recipe'\nimport type { SoundPlan } from '../plan'\nimport { createVoiceManager } from '../voices'\nimport { createAudioEngine } from '../webaudio/engine'\nimport type { AudioEngine, VoiceHandle } from '../webaudio/engine'\nimport type { EngineSink } from '../webaudio/sink'\nimport type { AudioLoopHandle } from '../webaudio/engine-loop'\nimport { audioBenchTextOf } from './locale'\nimport type { AudioBenchText } from './locale'\n\n/**\n * Two probe sounds of the bench itself (a ping and a hiss), so panning, distance and the seed can be heard on something that has no subject. They are\n * made by the kit, not delivered by any team (`sdk` in the sources).\n */\nexport const BENCH_BANK: SoundBank = {\n id: 'audio_bench',\n sounds: [\n {\n id: 'bench_ping',\n bus: 'ui',\n pan: true,\n duration: 0.25,\n layers: [\n {\n gain: 0.5,\n wet: 0.3,\n source: { kind: 'osc', wave: 'sine', frequency: { from: 880 } },\n envelope: [\n [0, 0.0001, 'set', 'fixed'],\n [0.002, 1, 'exp'],\n ['1d', 0.0001, 'exp', 'fixed'],\n ],\n } satisfies Layer,\n ],\n falloff: DISTANCE_CURVES.art_scene,\n sources: ['sdk: bench probe, a 880 Hz ping through the art scene output stage'],\n },\n {\n id: 'bench_hiss',\n bus: 'ui',\n pan: true,\n duration: [0.3, 0.9],\n layers: [\n {\n gain: 0.4,\n source: { kind: 'noise', offset: 'random', offsetScale: 1.5, loop: true },\n filters: { mode: 'serial', bank: [{ type: 'highpass', frequency: { set: 3000 }, q: 0.7 }] },\n envelope: [\n [0, 0.0001, 'set', 'fixed'],\n [0.002, 1, 'exp'],\n ['1d', 0.0001, 'exp', 'fixed'],\n ],\n } satisfies Layer,\n ],\n falloff: DISTANCE_CURVES.art_scene,\n sources: ['sdk: bench probe, a highpass hiss of random length'],\n },\n ],\n}\n\nexport type AudioTestModuleOptions = {\n /** The engine to test (default: the one the host lends in `host.audio`, else a new one of the module's own). */\n engine?: AudioEngine\n /** Banks registered in the engine besides the bench's probes (default the game's noise classes and the library). */\n banks?: readonly SoundBank[]\n locale?: string\n /** Text of a sound or loop id (`sound.glass`) in the game's words; without it the bench shows the bank and the id. */\n translate?: (key: string, locale: string) => string | undefined\n seed?: number\n /** First sound chosen (default `bench_ping`). */\n sound?: string\n}\n\n/** What the scenarios use of the context the test room passes. */\nexport interface AudioScenarioLike {\n expect(condition: boolean, label: string, data?: Record<string, unknown>): void\n}\n\nexport type AudioTestModule = TestableModule<AudioScenarioLike> & {\n /** The engine in use: the host's when it lent one, the module's own otherwise. */\n readonly engine: AudioEngine\n /** The resolved plan of the chosen sound as text (the S3 view: every number decided). */\n sheet(): string\n /** Plays the chosen sound as the bench is set. */\n play(): VoiceHandle | null\n}\n\nconst DISTANCES = [0, 10, 40, 60, 120, 160] as const\nconst STORM = 20\nconst PROBE_ID = 'audio_probe'\nconst PROBE_AT: Vec3 = [0, 0, -10]\nconst NONE = 'none'\nconst DEFAULT_SOUND = 'bench_ping'\n\ntype Camera = { readonly position: { readonly x: number; readonly y: number; readonly z: number }; getWorldDirection(target: unknown): { readonly x: number; readonly z: number } }\ntype ThreeLike = { Vector3: new () => unknown }\ntype ProbeObject = { userData?: Record<string, unknown>; getWorldPosition(target: unknown): { readonly x: number; readonly y: number; readonly z: number }; traverse(visit: (object: ProbeObject) => void): void }\n\nconst variantOf = (profile: MixProfile, reverb: boolean, compressor: boolean): MixProfile => {\n const { reverb: wet, compressor: chain, ...rest } = profile\n return { ...rest, id: `${profile.id}${reverb ? '' : '+dry'}${compressor ? '' : '+open'}`, ...(wet && reverb ? { reverb: wet } : {}), ...(chain && compressor ? { compressor: chain } : {}) }\n}\n\n/**\n * The sound area of a test room, written against the generic contracts only: it plays any sound of an engine at a distance and bearing from the listener\n * (or from a probe standing in the area), runs loops, throws a storm of voices at the limiter, switches the mixing chain and its parts, reads the\n * level of each bus and prints the resolved plan. The engine is the host's (`host.audio`) when lent, else the module's own.\n */\nexport function createAudioTestModule(options: AudioTestModuleOptions = {}): AudioTestModule {\n const locale = options.locale ?? 'pt-BR'\n const text: AudioBenchText = audioBenchTextOf(locale)\n const banks = [BENCH_BANK, ...(options.banks ?? [GAME_SFX_BANK, LIBRARY_BANK])]\n let engine: AudioEngine = options.engine ?? createAudioEngine({ seed: options.seed ?? 1, banks })\n for (const bank of banks) engine.register(bank)\n let host: ModuleHost | null = null\n let loop: AudioLoopHandle | undefined\n const state = {\n sound: options.sound ?? DEFAULT_SOUND,\n distance: 10,\n bearing: 0,\n volume: 1,\n mix: 'art_scene',\n seed: options.seed ?? 1,\n source: 'bench',\n loop: NONE,\n loopLevel: 0.1,\n loopPitch: 0,\n reverb: true,\n compressor: true,\n panning: true,\n muffling: true,\n }\n\n const nameOf = (id: string): string => {\n const translated = options.translate?.(`sound.${id}`, locale)\n if (translated) return translated\n const bank = engine.registry.bankOf(id)\n return bank ? `${bank} / ${id}` : id\n }\n const soundOptions = (): ChoiceOption[] => engine.registry.soundIds().map((id) => ({ id, name: nameOf(id) }))\n const loopOptions = (): ChoiceOption[] => [{ id: NONE, name: text.options.none }, ...engine.registry.loopIds().map((id) => ({ id, name: nameOf(id) }))]\n\n // the listener is the host camera; without a host (a test) it stands at the origin looking along -z\n const listener = (): { position: Vec3; yaw: number } => {\n const camera = host?.camera as Camera | undefined\n const three = host?.THREE as ThreeLike | undefined\n if (typeof camera?.getWorldDirection !== 'function' || typeof three?.Vector3 !== 'function' || !camera.position) return { position: [0, 0, 0], yaw: 0 }\n const direction = camera.getWorldDirection(new three.Vector3())\n return { position: [camera.position.x, camera.position.y, camera.position.z], yaw: Math.atan2(-direction.x, -direction.z) }\n }\n\n const probePosition = (): Vec3 | undefined => {\n const scene = host?.scene as ProbeObject | undefined\n const three = host?.THREE as ThreeLike | undefined\n if (typeof scene?.traverse !== 'function' || typeof three?.Vector3 !== 'function') return undefined\n let found: Vec3 | undefined\n scene.traverse((object) => {\n if (found || object.userData?.[PROBE_ID] !== true) return\n const at = object.getWorldPosition(new three.Vector3())\n found = [at.x, at.y, at.z]\n })\n return found\n }\n\n // `bearing` is degrees to the right of straight ahead\n const sourceAt = (ears: { position: Vec3; yaw: number }): Vec3 => {\n const probe = state.source === 'probe' ? probePosition() : undefined\n if (probe) return probe\n const bearing = (state.bearing * Math.PI) / 180\n const forward = [-Math.sin(ears.yaw), -Math.cos(ears.yaw)] as const\n const right = [Math.cos(ears.yaw), -Math.sin(ears.yaw)] as const\n return [ears.position[0] + state.distance * (forward[0] * Math.cos(bearing) + right[0] * Math.sin(bearing)), ears.position[1], ears.position[2] + state.distance * (forward[1] * Math.cos(bearing) + right[1] * Math.sin(bearing))]\n }\n\n const playOptions = (): { position?: Vec3; distance?: number; volume: number; seed: number } => {\n const ears = listener()\n engine.setListener(ears)\n const at = sourceAt(ears)\n const distance = Math.hypot(at[0] - ears.position[0], at[2] - ears.position[2])\n return { ...(state.panning ? { position: at } : { distance }), volume: state.volume, seed: state.seed }\n }\n\n // no muffling: the recipe's cutoff divisor is switched off for the one call\n const withMuffling = <T>(id: string, run: () => T): T => {\n const falloff = engine.registry.sound(id)?.falloff\n if (state.muffling || !falloff || falloff.model !== 'inverse_square' || falloff.cutoffK === undefined) return run()\n engine.registry.patch(id, { falloff: { cutoffK: 0 } })\n try {\n return run()\n } finally {\n engine.registry.patch(id, { falloff: { cutoffK: falloff.cutoffK } })\n }\n }\n\n const play = (): VoiceHandle | null => {\n engine.unlock()\n const chosen = state.sound\n return withMuffling(chosen, () => engine.play(chosen, playOptions()))\n }\n\n const plan = (): SoundPlan | null => {\n const chosen = state.sound\n return withMuffling(chosen, () => engine.plan(chosen, playOptions()))\n }\n\n const sheet = (): string => {\n const planned = plan()\n if (!planned) return `${text.sheet.title}: ${engine.registry.sound(state.sound) ? text.sheet.silent : text.sheet.none}`\n const head = `${text.sheet.title}: ${planned.id} (${planned.bus}) ${text.sheet.level} ${planned.level.toFixed(4)} ${text.sheet.distance} ${planned.distance.toFixed(1)} m` + `${planned.pan === undefined ? '' : ` ${text.sheet.pan} ${planned.pan.toFixed(3)}`} ${planned.length.toFixed(3)} ${text.sheet.length}`\n const layers = planned.layers.map((layer, index) => {\n const source = layer.source.kind === 'noise' ? `noise@${layer.source.offset.toFixed(4)}` : layer.source.kind === 'osc' ? `${layer.source.wave}@${layer.source.frequency.value ?? layer.source.frequency.from}` : layer.source.kind\n const filters = (layer.filters?.bank ?? []).map((filter) => `${filter.type}@${(filter.frequency.value ?? filter.frequency.from ?? 0).toFixed(1)}`).join(' ')\n const envelope = layer.envelope.map((point) => `${point.time.toFixed(3)}:${point.value.toPrecision(3)}`).join(' ')\n return ` ${text.sheet.layer} ${index + 1}: ${source} at ${layer.at.toFixed(3)} for ${layer.duration.toFixed(3)} ${filters} [${envelope}]`\n })\n return [head, ...layers].join('\\n')\n }\n\n const applyMix = (): void => {\n const profile = engine && MIX_PROFILES[state.mix]\n if (profile) engine.setMix(variantOf(profile, state.reverb, state.compressor))\n }\n\n const setLoop = (): void => {\n loop?.stop()\n loop = undefined\n if (state.loop === NONE) return\n engine.unlock()\n loop = engine.loop(state.loop)\n loop.set({ volume: state.loopLevel, ...(state.loopPitch > 0 ? { frequency: state.loopPitch } : {}) })\n }\n\n const storm = (): void => {\n engine.unlock()\n const base = playOptions()\n const chosen = state.sound\n for (let index = 0; index < STORM; index++) {\n const spread = base.position ? { position: [base.position[0] + (index - STORM / 2), base.position[1], base.position[2]] as Vec3 } : {}\n withMuffling(chosen, () => engine.play(chosen, { ...base, ...spread, seed: state.seed + index }))\n }\n }\n\n const onChange = (change: BenchChange): void => {\n if (change.kind === 'layer') {\n if (change.id === 'reverb' || change.id === 'compressor') {\n state[change.id] = change.on\n applyMix()\n } else if (change.id === 'panning' || change.id === 'muffling') state[change.id] = change.on\n return\n }\n if (change.kind === 'action') {\n if (change.id === 'play') play()\n else if (change.id === 'voice_storm') storm()\n return\n }\n const { id, value } = change\n if (id === 'sound' && typeof value === 'string') state.sound = value\n else if (id === 'distance' && typeof value === 'string') state.distance = Number(value)\n else if (id === 'bearing' && typeof value === 'number') state.bearing = value\n else if (id === 'volume' && typeof value === 'number') state.volume = value\n else if (id === 'seed' && typeof value === 'number') state.seed = value\n else if (id === 'source' && typeof value === 'string') state.source = value\n else if (id === 'mix' && typeof value === 'string') {\n state.mix = value\n applyMix()\n } else if (id === 'loop' && typeof value === 'string') {\n state.loop = value\n setLoop()\n } else if (id === 'loop_level' && typeof value === 'number') {\n state.loopLevel = value\n loop?.set({ volume: value })\n } else if (id === 'loop_pitch' && typeof value === 'number') {\n state.loopPitch = value\n if (value > 0) loop?.set({ frequency: value })\n }\n }\n\n const items = (): TestableItem[] => [\n {\n id: PROBE_ID,\n name: text.probe,\n movable: false,\n build: (THREE) => {\n const T = THREE as { Group: new () => { add(...objects: unknown[]): void; userData: Record<string, unknown> }; Mesh: new (geometry: unknown, material: unknown) => { position: { set(x: number, y: number, z: number): void } }; CylinderGeometry: new (top: number, bottom: number, height: number, segments: number) => unknown; BoxGeometry: new (x: number, y: number, z: number) => unknown; MeshStandardMaterial: new (parameters: Record<string, unknown>) => unknown }\n const group = new T.Group()\n const post = new T.Mesh(new T.CylinderGeometry(0.04, 0.05, 1.5, 10), new T.MeshStandardMaterial({ color: 0x4a4f55 }))\n post.position.set(0, 0.75, 0)\n const speaker = new T.Mesh(new T.BoxGeometry(0.32, 0.4, 0.26), new T.MeshStandardMaterial({ color: 0x1d2024 }))\n speaker.position.set(0, 1.5, 0)\n group.add(post, speaker)\n group.userData[PROBE_ID] = true\n return group\n },\n spec: { parts: [{ id: 'post', material: 'steel', shape: { kind: 'box', size: [0.12, 1.7, 0.3] }, position: [0, 0.85, 0], mass: 25, hp: 400 }] },\n },\n ]\n\n const controls = (): ModuleControl[] => [\n { kind: 'choice', id: 'sound', name: text.controls.sound, options: soundOptions(), default: state.sound },\n { kind: 'choice', id: 'source', name: text.controls.source, hint: text.hints.source, options: [{ id: 'bench', name: text.options.bench }, { id: 'probe', name: text.options.probe }], default: state.source },\n { kind: 'choice', id: 'distance', name: text.controls.distance, hint: text.hints.distance, options: DISTANCES.map((meters) => ({ id: String(meters), name: `${meters} m` })), default: String(state.distance) },\n { kind: 'number', id: 'bearing', name: text.controls.bearing, hint: text.hints.bearing, unit: '°', min: -180, max: 180, step: 15, default: state.bearing },\n { kind: 'number', id: 'volume', name: text.controls.volume, min: 0, max: 1, step: 0.05, default: state.volume },\n { kind: 'choice', id: 'mix', name: text.controls.mix, hint: text.hints.mix, options: Object.keys(MIX_PROFILES).map((id) => ({ id, name: text.mixes[id] ?? id })), default: state.mix },\n { kind: 'number', id: 'seed', name: text.controls.seed, hint: text.hints.seed, min: 1, max: 9999, step: 1, default: state.seed },\n { kind: 'action', id: 'play', name: text.controls.play },\n { kind: 'action', id: 'voice_storm', name: text.controls.voice_storm, hint: text.hints.voice_storm },\n { kind: 'choice', id: 'loop', name: text.controls.loop, options: loopOptions(), default: state.loop },\n { kind: 'number', id: 'loop_level', name: text.controls.loop_level, min: 0, max: 0.6, step: 0.01, default: state.loopLevel },\n { kind: 'number', id: 'loop_pitch', name: text.controls.loop_pitch, hint: text.hints.loop_pitch, unit: 'Hz', min: 0, max: 400, step: 5, default: state.loopPitch },\n ]\n\n const layers = (): ModuleLayer[] => (['reverb', 'compressor', 'panning', 'muffling'] as const).map((id) => ({ id, name: text.layers[id] }))\n\n const metrics = (): ModuleMetric[] => [\n { id: 'voices', name: text.metrics.voices, digits: 0, read: () => engine.stats().voices },\n { id: 'peak', name: text.metrics.peak, digits: 0, max: engine.stats().maxVoices > 0 && Number.isFinite(engine.stats().maxVoices) ? engine.stats().maxVoices : undefined, read: () => engine.stats().peak },\n { id: 'dropped', name: text.metrics.dropped, digits: 0, read: () => engine.stats().dropped },\n { id: 'loops', name: text.metrics.loops, digits: 0, read: () => engine.stats().loops },\n { id: 'context', name: text.metrics.context, read: () => engine.stats().context },\n { id: 'plan_length', name: text.metrics.plan_length, unit: 's', digits: 2, read: () => plan()?.length ?? 0 },\n { id: 'plan_level', name: text.metrics.plan_level, digits: 3, read: () => plan()?.level ?? 0 },\n { id: 'plan_pan', name: text.metrics.plan_pan, digits: 2, read: () => plan()?.pan ?? 0 },\n ...BUS_IDS.map((bus) => ({ id: `level_${bus}`, name: text.metrics[`level_${bus}` as keyof AudioBenchText['metrics']], digits: 3, read: () => engine.level(bus as BusId) })),\n ]\n\n const scenarios = (): Array<ModuleScenario<AudioScenarioLike>> => [\n {\n id: 'panning',\n name: text.scenarios.panning,\n run: ({ expect }) => {\n engine.setListener({ position: [0, 0, 0], yaw: 0 })\n const pan = (at: Vec3): number => engine.plan('bench_ping', { position: at })?.pan ?? Number.NaN\n expect(pan([10, 0, 0]) > 0.3, 'a sound to the right pans right', { pan: pan([10, 0, 0]) })\n expect(pan([-10, 0, 0]) < -0.3, 'a sound to the left pans left', { pan: pan([-10, 0, 0]) })\n expect(Math.abs(pan([0, 0, -10])) < 0.05, 'a sound ahead stays in the middle', { pan: pan([0, 0, -10]) })\n engine.setListener({ position: [0, 0, 0], yaw: Math.PI / 2 })\n expect(pan([0, 0, -10]) > 0.3, 'turning the listener left puts what was ahead on the right', { pan: pan([0, 0, -10]) })\n engine.setListener(listener())\n },\n },\n {\n id: 'distance',\n name: text.scenarios.distance,\n run: ({ expect }) => {\n const levels = [0, 10, 40, 100].map((meters) => engine.plan('bench_ping', { distance: meters })?.level ?? Number.NaN)\n expect(levels.every((level, index) => index === 0 || level < (levels[index - 1] as number)), 'the level falls with the distance', { levels })\n const far = engine.registry.sound('sfx_pistol') ? engine.plan('sfx_pistol', { distance: 400 }) : null\n expect(far === null, 'a game noise class is not played too far to be heard')\n },\n },\n {\n id: 'determinism',\n name: text.scenarios.determinism,\n run: ({ expect }) => {\n const a = engine.plan('bench_hiss', { seed: 5 })\n const b = engine.plan('bench_hiss', { seed: 5 })\n const c = engine.plan('bench_hiss', { seed: 6 })\n expect(JSON.stringify(a) === JSON.stringify(b), 'the same seed gives the same plan')\n expect(JSON.stringify(a) !== JSON.stringify(c), 'another seed gives another plan')\n },\n },\n {\n id: 'limiter',\n name: text.scenarios.limiter,\n run: ({ expect }) => {\n const voices = createVoiceManager({ maxVoices: 8 })\n const request = (group?: string) => voices.request({ ...(group ? { group } : {}), priority: 3, gain: 0.5, distance: 5, length: 1 })\n const grouped = Array.from({ length: STORM }, () => request('undead_voice')).filter((decision) => decision.accepted).length\n expect(grouped === 1, 'the group cooldown lets one voice of a storm through', { accepted: grouped })\n const plain = Array.from({ length: STORM }, () => request()).filter((decision) => decision.accepted).length\n expect(plain <= 8, 'the global cap holds the rest', { accepted: plain })\n },\n },\n ]\n\n const area = (): { placements: AreaPlacement[]; view: { eye: Vec3; target: Vec3 } } => ({\n placements: [{ item: PROBE_ID, position: PROBE_AT, asleep: true }],\n view: { eye: [0, 1.7, 3], target: [0, 1.4, -10] },\n })\n\n const module: AudioTestModule = {\n id: 'audio_area',\n name: text.name,\n group: text.group,\n get engine() {\n return engine\n },\n items: () => items(),\n area: () => area(),\n controls: () => controls(),\n layers: () => layers(),\n metrics: () => metrics(),\n scenarios: () => scenarios(),\n sheet,\n play,\n attach(lent) {\n host = lent\n const shared = (lent.audio as EngineSink | undefined)?.engine\n if (shared && shared !== engine) {\n for (const bank of banks) shared.register(bank)\n engine = shared\n }\n // buses get their nodes now, so the level meters see every sound\n for (const bus of BUS_IDS) {\n engine.setBus(bus, {})\n engine.level(bus)\n }\n lent.bench?.patch('sound', { options: soundOptions() })\n lent.bench?.patch('loop', { options: loopOptions() })\n const off = lent.bench?.onChange(onChange)\n const offFrame = lent.onFrame(() => {\n if (!shared) engine.setListener(listener())\n })\n return () => {\n off?.()\n offFrame()\n loop?.stop()\n loop = undefined\n host = null\n }\n },\n }\n return module\n}\n","// The calls of the animals, as recipes of @adatechnology/game-audio: the game's `bichoSom` (jogo L66625), which synthesizes four calls with the same\n// routine (the dog's two filtered clicks, the lapwing's shriek, the hen's cluck, the cow's moo). Linear fall to silence at 70 m, as the page has it; the\n// numbers are drawn and the nodes created in the page's order, so a recipe leaves the trace of the team code (tests/audio.test.ts against\n// tools/golden/fixtures/audio/game-audio.json).\nimport { DISTANCE_CURVES } from '@adatechnology/game-audio'\nimport type { SoundBank, SoundRecipe } from '@adatechnology/game-audio'\n\ntype Call = {\n readonly id: string\n /** The name in the team code. */\n readonly team: string\n readonly count: number\n readonly spacing: number\n readonly seconds: number\n readonly wave: OscillatorType\n readonly frequency: number\n /** The pitch falls to this fraction of itself over each pass. */\n readonly drop: number\n}\n\nconst call = (o: Call): SoundRecipe => ({\n id: o.id,\n bus: 'world',\n order: 'calls',\n duration: o.seconds,\n tail: 0.02,\n // the page makes its gain and filter before it looks at the level\n keepSilent: true,\n layers: [\n {\n repeat: { count: o.count, spacing: o.spacing },\n source: { kind: 'osc', wave: o.wave, frequency: { from: o.frequency, to: o.frequency * o.drop, curve: 'exp', over: 'duration' } },\n filters: { mode: 'serial', bank: [{ type: 'bandpass', frequency: { set: o.frequency * 1.2 }, q: 3 }] },\n envelope: [\n [0, 0.0001, 'set', 'fixed'],\n [0.015, 1, 'exp'],\n ['1d', 0.0001, 'exp', 'fixed'],\n ],\n },\n ],\n falloff: DISTANCE_CURVES.animals,\n sources: [`jogo:L66625-66639 bichoSom('${o.team}')`],\n})\n\n/** `latido`: two square clicks 0.22 s apart, 520 Hz falling to 0.6, 0.12 s each. */\nexport const DOG_BARK = call({ id: 'dog_bark', team: 'latido', count: 2, spacing: 0.22, seconds: 0.12, wave: 'square', frequency: 520, drop: 0.6 })\n/** `queroquero`: three squares 0.14 s apart, 2400 Hz falling to 0.6, 0.09 s each. */\nexport const LAPWING_CALL = call({ id: 'lapwing_call', team: 'queroquero', count: 3, spacing: 0.14, seconds: 0.09, wave: 'square', frequency: 2400, drop: 0.6 })\n/** `cacarejo`: four squares 0.14 s apart, 900 Hz falling to 0.6, 0.09 s each. */\nexport const HEN_CLUCK = call({ id: 'hen_cluck', team: 'cacarejo', count: 4, spacing: 0.14, seconds: 0.09, wave: 'square', frequency: 900, drop: 0.6 })\n/** `mugido`: one sawtooth, 140 Hz falling to 0.8, 1.2 s. */\nexport const COW_MOO = call({ id: 'cow_moo', team: 'mugido', count: 1, spacing: 0.14, seconds: 1.2, wave: 'sawtooth', frequency: 140, drop: 0.8 })\n\nexport const ANIMAL_SOUNDS: readonly SoundRecipe[] = [DOG_BARK, LAPWING_CALL, HEN_CLUCK, COW_MOO]\n\nexport const ANIMAL_SOUND_BANK: SoundBank = { id: 'animals_three', sounds: ANIMAL_SOUNDS }\n","{\n \"sources\": [\n \"jogo:L66677 galinha: cacarejo, 6 s between calls, noise 30 m\",\n \"jogo:L66680 quero-quero: queroquero when the player is near (alarme), 8 s, noise 40 m\",\n \"jogo:L66681 vaca: mugido, 30 s, noise 80 m\",\n \"jogo:L66683 cachorro: latido when a zombie is within 12 m, 3 s, noise 60 m\"\n ],\n \"species\": {\n \"chicken\": { \"sound\": \"hen_cluck\", \"cooldown\": 6, \"noiseMetres\": 30 },\n \"southern_lapwing\": { \"sound\": \"lapwing_call\", \"cooldown\": 8, \"noiseMetres\": 40 },\n \"cow\": { \"sound\": \"cow_moo\", \"cooldown\": 30, \"noiseMetres\": 80 },\n \"dog\": { \"sound\": \"dog_bark\", \"cooldown\": 3, \"noiseMetres\": 60 }\n }\n}\n","import type { SoundRequest, Vec3 } from '@adatechnology/game-contracts'\n\nimport { ANIMAL_SOUND_BANK } from './recipes'\nimport callsData from './calls.json'\n\n/** The call of a species: the recipe, the seconds the page waits before the same animal calls again, and the metres its noise reaches the AI (`emitNoise`). */\nexport type AnimalCall = { readonly sound: string; readonly cooldown: number; readonly noiseMetres: number }\n\ntype CallsData = { readonly sources: readonly string[]; readonly species: Readonly<Record<string, AnimalCall>> }\n\nconst DATA = callsData as unknown as CallsData\n\n/** The calls the page delivered, by species id. The other species (horse, pig, boar, capybara, jaguar, cat, vulture, snake, rat) have none: the page gives them no sound. */\nexport const ANIMAL_CALLS: Readonly<Record<string, AnimalCall>> = DATA.species\nexport const ANIMAL_CALL_SOURCES: readonly string[] = DATA.sources\n\n/** The part of the host's sound an animal needs: `play`, and `register` to put the bank in its engine (`createAudioSink(...)` has both). */\nexport type AnimalSoundSink = {\n play(request: SoundRequest): unknown\n register?(bank: unknown): void\n}\n\nexport type AnimalSoundOptions = {\n /** Replaces a species' call; `null` silences it. */\n readonly calls?: Readonly<Record<string, AnimalCall | null>>\n}\n\nexport type AnimalSound = {\n /** The call of a species, if it has one. */\n callOf(speciesId: string): AnimalCall | undefined\n /**\n * Makes the species call where the animal is. `key` is the animal (default the species): the same animal waits out its cooldown, and the call is\n * refused (false) until then. Returns whether a sound was asked for.\n */\n call(speciesId: string, position: Vec3, options?: { readonly volume?: number; readonly key?: string }): boolean\n /** Counts the cooldowns down: call it each frame. */\n update(dt: number): void\n dispose(): void\n}\n\n/**\n * Plays the calls of the animals on the host's sound: the four of the Bichos do Vale (hen, lapwing, cow, dog), positional with the page's linear 70 m\n * fall, each animal waiting out the page's cooldown. The bank is registered in the host's engine.\n */\nexport function createAnimalSound(sink: AnimalSoundSink, options: AnimalSoundOptions = {}): AnimalSound {\n sink.register?.(ANIMAL_SOUND_BANK)\n const calls: Record<string, AnimalCall | null | undefined> = { ...ANIMAL_CALLS, ...options.calls }\n const waiting = new Map<string, number>()\n const callOf = (speciesId: string): AnimalCall | undefined => calls[speciesId] ?? undefined\n return {\n callOf,\n call(speciesId, position, o = {}) {\n const found = callOf(speciesId)\n if (!found) return false\n const key = o.key ?? speciesId\n if ((waiting.get(key) ?? 0) > 0) return false\n waiting.set(key, found.cooldown)\n sink.play({ sound: found.sound, position, ...(o.volume === undefined ? {} : { volume: o.volume }) })\n return true\n },\n update(dt) {\n for (const [key, seconds] of waiting) {\n if (seconds - dt <= 0) waiting.delete(key)\n else waiting.set(key, seconds - dt)\n }\n },\n dispose: () => waiting.clear(),\n }\n}\n","import { lineUpLength } from '@adatechnology/game-animals'\n\nimport type { AnimalRig } from './types'\n\nexport type LineUpOptions = {\n /** X where the row starts (page: -19). */\n readonly start?: number\n /** Gap between animals (page: 0.85). */\n readonly gap?: number\n /** Heading of the animals, radians (page: 1.1; snakes 2.2). */\n readonly yaw?: number\n readonly snakeYaw?: number\n}\n\n/** Puts the animals in a row on the x axis, each given room by its length, the row centred on the origin, and keeps each place as `home` (page: the line-up). */\nexport const lineUpAnimals = (rigs: readonly AnimalRig[], options: LineUpOptions = {}): void => {\n const start = options.start ?? -19\n const gap = options.gap ?? 0.85\n let cursor = start\n for (const rig of rigs) {\n const length = lineUpLength(rig.species.body)\n cursor += length / 2\n rig.root.position.set(cursor, 0, 0)\n rig.home = rig.root.position.clone()\n rig.root.rotation.y = rig.species.body.kind === 'snake' ? (options.snakeYaw ?? 2.2) : (options.yaw ?? 1.1)\n cursor += length / 2 + gap\n }\n const offset = -(cursor - gap + start) / 2\n for (const rig of rigs) {\n rig.root.position.x += offset\n rig.home.x += offset\n }\n}\n","import { visualStateOf } from '@adatechnology/game-animals'\nimport type { AnimalSpecies } from '@adatechnology/game-animals'\nimport type { ModuleScenario } from '@adatechnology/game-contracts'\n\nimport type { AnimalsThree } from '../index'\nimport type { OutbreakLike } from './types'\n\n/** What a scenario needs of the room: the pass/fail line and time. The test room's `ScenarioContext` has both. */\nexport type AnimalsContext = { expect(condition: boolean, label: string, data?: Record<string, unknown>): void; step(seconds: number): void }\n\nexport type ScenarioDeps = {\n readonly text: Readonly<Record<string, string>>\n species(): readonly AnimalSpecies[]\n ensure(): AnimalsThree\n outbreak(): OutbreakLike | null\n rebuildOutbreak(enabled: boolean): OutbreakLike | null\n skins(): { count: number; meshes: { drawCalls: number; triangles: number }; nodes: number; rich: number }\n clearSkins(): void\n}\n\nconst SKIN_COUNT = 40\n\nexport const createScenarios = (deps: ScenarioDeps): ModuleScenario<AnimalsContext>[] => {\n const scenario = (id: string, run: ModuleScenario<AnimalsContext>['run']): ModuleScenario<AnimalsContext> => ({ id, name: deps.text[id] ?? id, run })\n const rate = () => 0.5\n return [\n scenario('visual_states', ({ expect }) => {\n const animals = deps.ensure()\n for (const species of deps.species()) {\n const rig = animals.build(species)\n animals.setVisualState(rig, 'infected')\n expect(rig.infected && !rig.dead, `${species.id}: infected`)\n animals.setVisualState(rig, 'dead')\n expect(rig.dead && !rig.infected, `${species.id}: dead`)\n animals.setVisualState(rig, 'alive')\n expect(!rig.dead && !rig.infected, `${species.id}: alive again`)\n }\n }),\n scenario('gait_moves_legs', ({ expect }) => {\n const animals = deps.ensure()\n const species = deps.species().find((entry) => entry.body.kind === 'quadruped')\n if (!species) return expect(false, 'no quadruped among the species')\n const rig = animals.build(species)\n animals.animate(rig, { time: 0, delta: 0, clip: 'walk' })\n const before = rig.legs.map((leg) => leg.hip.rotation.x)\n animals.animate(rig, { time: 0.4, delta: 0.4, clip: 'walk' })\n expect(rig.legs.some((leg, index) => leg.hip.rotation.x !== before[index]), `${species.id}: a walk step swings a hip`)\n }),\n scenario('outbreak_rule', ({ expect }) => {\n const turns = deps.species().find((entry) => entry.outcome === 'turns')\n const immune = deps.species().find((entry) => entry.outcome === 'immune')\n const on = deps.rebuildOutbreak(true)\n if (!on || !turns) return expect(false, 'no outbreak rule or no species that turns')\n expect(on.bite(turns.id, rate, 0) !== undefined, `a bite on ${turns.id} starts a fate with the rule on`)\n if (immune) expect(on.bite(immune.id, rate, 0) === undefined, `${immune.id} shrugs the bite off`)\n const off = deps.rebuildOutbreak(false)\n expect(off?.bite(turns.id, rate, 0) === undefined, 'with the rule off a bite does nothing')\n deps.rebuildOutbreak(true)\n }),\n scenario('carcass_lure', ({ expect }) => {\n const outbreak = deps.rebuildOutbreak(true)\n const dies = deps.species().find((entry) => entry.outbreak?.carcass)\n if (!outbreak || !dies) return expect(false, 'no outbreak rule or no species that leaves a carcass')\n const carcass = outbreak.carcass(dies.id, [0, 0, 0], 0)\n expect(carcass !== undefined, `${dies.id} leaves a carcass`)\n if (!carcass) return\n expect(outbreak.lures(carcass, carcass.lureRadiusMetres - 1, 1), 'a zombie inside the radius is drawn')\n expect(!outbreak.lures(carcass, carcass.lureRadiusMetres + 1, 1), 'a zombie outside it is not')\n }),\n scenario('bite_to_turn_timer', ({ expect }) => {\n const outbreak = deps.rebuildOutbreak(true)\n const turns = deps.species().find((entry) => entry.outcome === 'turns')\n if (!outbreak || !turns) return expect(false, 'no outbreak rule or no species that turns')\n const fate = outbreak.bite(turns.id, rate, 0)\n if (!fate) return expect(false, 'the bite took')\n expect(outbreak.stateOf(fate, 0) === 'incubating', 'it incubates at first')\n expect(outbreak.stateOf(fate, fate.dueAt + 1) === 'turned', 'it has turned once the time is up (the bench clock runs it accelerated)')\n expect(visualStateOf(turns.outcome, true) === 'infected', 'and shows the infected look')\n }),\n scenario('skins_40', ({ expect }) => {\n const result = deps.skins()\n expect(result.count === SKIN_COUNT, `${SKIN_COUNT} animals are in the field`, { count: result.count })\n expect(result.meshes.drawCalls <= SKIN_COUNT + 16, 'the skins are about one draw call each', result.meshes)\n expect(result.nodes > 0, 'each skin keeps the nodes the animation moves', { nodes: result.nodes })\n deps.clearSkins()\n }),\n ]\n}\n","export type AnimalsText = {\n group: string\n name: string\n controls: Record<'visual_state' | 'gait' | 'speed' | 'outbreak' | 'time_scale', string>\n hints: Record<'visual_state' | 'gait' | 'outbreak' | 'time_scale', string>\n options: { visual_state: Record<string, string>; gait: Record<string, string> }\n actions: Record<'bite_all' | 'recover' | 'call' | 'skin_40' | 'clear_skins', string>\n layers: Record<'joints' | 'split' | 'sound_range' | 'carcass_lure', string>\n metrics: Record<'animals' | 'draw_calls' | 'triangles' | 'nodes_per_skin' | 'incubating' | 'turned' | 'dead', string>\n scenarios: Record<string, string>\n}\n\nexport const TEXT: Record<string, AnimalsText> = {\n en: {\n group: 'Animals',\n name: 'Animals area',\n controls: { visual_state: 'Visual state', gait: 'Gait', speed: 'Animation speed', outbreak: 'Outbreak rule', time_scale: 'Bite clock' },\n hints: {\n visual_state: 'The look of every animal: alive, infected (grey coat, glowing eyes, wounds) or dead (lying down, a pool of blood)',\n gait: 'idle, walk or run; the cycle of each species runs at its own rate',\n outbreak: 'Off: a bite does nothing. On: a species that turns incubates and turns, one that dies dies, the rest shrug it off',\n time_scale: 'Game seconds per real second for the bite timers (60: ten minutes pass in ten seconds)',\n },\n options: { visual_state: { alive: 'Alive', infected: 'Infected', dead: 'Dead' }, gait: { idle: 'Idle', walk: 'Walk', run: 'Run' } },\n actions: { bite_all: 'Bite every animal', recover: 'Heal every animal', call: 'Make the animals with a call call (hen, lapwing, cow, dog)', skin_40: 'Add 40 animals as node skins', clear_skins: 'Remove the skins' },\n layers: { joints: 'Joints', split: 'Split view', sound_range: 'Sound range', carcass_lure: 'Carcass lure radius' },\n metrics: { animals: 'Animals', draw_calls: 'Draw calls', triangles: 'Triangles', nodes_per_skin: 'Nodes per skin', incubating: 'Incubating', turned: 'Turned', dead: 'Dead' },\n scenarios: {\n visual_states: 'Alive, infected and dead for every species',\n gait_moves_legs: 'The gait moves the legs',\n outbreak_rule: 'Outbreak rule on and off',\n carcass_lure: 'A carcass lures zombies within its radius',\n bite_to_turn_timer: 'Bite, incubation, turn (accelerated)',\n skins_40: '40 animals as node skins',\n },\n },\n 'pt-BR': {\n group: 'Animais',\n name: 'Área de animais',\n controls: { visual_state: 'Estado visual', gait: 'Andar', speed: 'Velocidade da animação', outbreak: 'Regra de surto', time_scale: 'Relógio da mordida' },\n hints: {\n visual_state: 'O aspecto de todos os bichos: vivo, infectado (pelo cinza, olhos brilhando, feridas) ou morto (deitado, poça de sangue)',\n gait: 'idle, walk ou run; o ciclo de cada espécie anda no seu ritmo',\n outbreak: 'Desligada: a mordida não faz nada. Ligada: a espécie que vira incuba e vira, a que morre morre, as outras não sentem',\n time_scale: 'Segundos de jogo por segundo real nos prazos da mordida (60: dez minutos passam em dez segundos)',\n },\n options: { visual_state: { alive: 'Vivo', infected: 'Infectado', dead: 'Morto' }, gait: { idle: 'Parado', walk: 'Andando', run: 'Correndo' } },\n actions: { bite_all: 'Morder todos os bichos', recover: 'Curar todos os bichos', call: 'Fazer os bichos com chamado chamarem (galinha, quero-quero, vaca, cachorro)', skin_40: 'Pôr 40 bichos como pele de nós', clear_skins: 'Tirar as peles' },\n layers: { joints: 'Juntas', split: 'Vista aberta', sound_range: 'Alcance do som', carcass_lure: 'Raio de atração da carcaça' },\n metrics: { animals: 'Bichos', draw_calls: 'Chamadas de desenho', triangles: 'Triângulos', nodes_per_skin: 'Nós por pele', incubating: 'Incubando', turned: 'Viraram', dead: 'Morreram' },\n scenarios: {\n visual_states: 'Vivo, infectado e morto em cada espécie',\n gait_moves_legs: 'O andar mexe as pernas',\n outbreak_rule: 'Regra de surto ligada e desligada',\n carcass_lure: 'A carcaça atrai zumbis dentro do raio',\n bite_to_turn_timer: 'Mordida, incubação, vira (acelerado)',\n skins_40: '40 bichos como pele de nós',\n },\n },\n}\n","import { lineUpLength, visualStateOf } from '@adatechnology/game-animals'\nimport type { AnimalSpecies, AnimalVisualState } from '@adatechnology/game-animals'\nimport type { BenchChange, ModuleArea, ModuleControl, ModuleHost, ModuleLayer, ModuleMetric, ModuleScenario, TestableItem, TestableModule } from '@adatechnology/game-contracts'\nimport type { NodeSkin } from '@adatechnology/game-props3d'\nimport type * as THREE_NS from 'three'\n\nimport { createAnimalSound } from '../audio/sound'\nimport type { AnimalSound } from '../audio/sound'\nimport { createAnimalsThree } from '../index'\nimport type { AnimalsThree } from '../index'\nimport type { AnimalRig, Three } from '../types'\nimport { createScenarios } from './scenarios'\nimport type { AnimalsContext, ScenarioDeps } from './scenarios'\nimport { TEXT } from './text'\nimport type { AnimalsTestOptions, OutbreakLike } from './types'\n\nexport const ANIMALS_AREA_ID = 'animals_area'\nexport const ANIMAL_ITEM_PREFIX = 'animal_'\nconst SKIN_COUNT = 40\nconst SKIN_FIELD = { x0: -18, z0: -10, columns: 8, gap: 4.5 }\nconst DEFAULT_TIME_SCALE = 60\n\ntype Fate = NonNullable<ReturnType<OutbreakLike['bite']>>\ntype Placed = { readonly id: string; readonly rig: AnimalRig; fate?: Fate; counted?: 'turned' | 'dead' }\n\n/**\n * The animals test area as a `TestableModule`: the species of the pack in a row, each drawn by the Bichos do Vale builders, with the alive / infected / dead looks, the gait,\n * the joints and split views, the rings for how far its sound carries and how far a carcass lures, the outbreak rule with an accelerated bite clock, and a field of 40 animals\n * drawn as node skins. The species and the outbreak rule come in by option, so this package knows no game.\n */\nexport function createAnimalsTestModule(options: AnimalsTestOptions = {}): TestableModule<AnimalsContext> & { readonly placed: ReadonlyMap<string, unknown> } {\n const locale = options.locale ?? 'pt-BR'\n const text = TEXT[locale] ?? (TEXT['pt-BR'] as (typeof TEXT)[string])\n const species = options.species ?? []\n const settings = { visual: 'alive' as AnimalVisualState, gait: 'walk', speed: 1, outbreak: true, timeScale: DEFAULT_TIME_SCALE }\n let three: Three | null = null\n let animals: AnimalsThree | null = null\n let host: ModuleHost | null = null\n let outbreak: OutbreakLike | null = null\n let sound: AnimalSound | null = null\n let time = 0\n let clock = 0\n let skinGroup: THREE_NS.Group | null = null\n let skins: { rig: AnimalRig; skin: NodeSkin }[] = []\n let rings: THREE_NS.Object3D[] = []\n const placed = new Map<string, Placed>()\n const random = (): number => 0.5\n const drawSeed = options.seed ?? 7\n const bitten = () => ({ turned: [...placed.values()].filter((p) => p.counted === 'turned').length, dead: [...placed.values()].filter((p) => p.counted === 'dead').length })\n\n const ensure = (THREE: Three): AnimalsThree => {\n three = THREE\n animals ??= createAnimalsThree(THREE, { seed: drawSeed, ...(options.document ? { document: options.document } : {}) })\n return animals\n }\n const rebuildOutbreak = (): void => {\n outbreak = options.createOutbreak ? options.createOutbreak({ species, enabled: settings.outbreak, timeScale: settings.timeScale }) : null\n }\n rebuildOutbreak()\n\n const item = (animal: AnimalSpecies): TestableItem => ({\n id: `${ANIMAL_ITEM_PREFIX}${animal.id}`,\n name: animal.name[locale as 'en'] ?? animal.id,\n movable: false,\n build: (THREE: unknown) => {\n const rig = ensure(THREE as Three).build(animal)\n // the room puts the tree in its scene right after this; trees that never reach it are dropped at the next frame\n placed.set(rig.root.uuid, { id: rig.root.uuid, rig })\n if (settings.visual !== 'alive') animals?.setVisualState(rig, settings.visual)\n return rig.root\n },\n })\n\n const statusOf = (entry: Placed): void => {\n if (!entry.fate || !outbreak) return\n const state = outbreak.stateOf(entry.fate, clock)\n if (state === 'incubating') return\n entry.counted = state === 'turned' ? 'turned' : 'dead'\n animals?.setVisualState(entry.rig, visualStateOf(entry.rig.species.outcome, true, state === 'dead'))\n }\n\n const clearRings = (): void => {\n for (const ring of rings) ring.parent?.remove(ring)\n rings = []\n }\n const refreshLayers = (): void => {\n if (!host?.bench || !animals || !three) return\n clearRings()\n for (const entry of placed.values()) {\n animals.setJointView(entry.rig, host.bench.layer('joints'))\n animals.setSplit(entry.rig, host.bench.layer('split'))\n }\n const ring = (radius: number, color: number, parent: THREE_NS.Object3D): void => {\n const mesh = new (three as Three).Mesh(new (three as Three).RingGeometry(radius - 0.15, radius, 96).rotateX(-Math.PI / 2), new (three as Three).MeshBasicMaterial({ color, transparent: true, opacity: 0.55, depthWrite: false }))\n mesh.position.y = 0.03\n parent.add(mesh)\n rings.push(mesh)\n }\n for (const entry of placed.values()) {\n if (!entry.rig.root.parent) continue\n const radius = entry.rig.species.soundRangeMetres\n if (host.bench.layer('sound_range') && radius) ring(radius, 0x44aaff, entry.rig.root)\n const lure = entry.rig.species.outbreak?.carcass?.lureRadiusMetres\n if (host.bench.layer('carcass_lure') && lure) ring(lure, 0xff5533, entry.rig.root)\n }\n }\n\n const addSkins = (): void => {\n if (!animals || !three || !host) return\n skinGroup ??= new three.Group()\n if (!skinGroup.parent) (host.scene as THREE_NS.Object3D).add(skinGroup)\n for (let index = skins.length; index < SKIN_COUNT; index++) {\n const animal = species[index % Math.max(1, species.length)] as AnimalSpecies\n const rig = animals.build(animal)\n const skin = animals.createSkin(rig)\n rig.root.position.set(SKIN_FIELD.x0 + (index % SKIN_FIELD.columns) * SKIN_FIELD.gap, 0, SKIN_FIELD.z0 - Math.floor(index / SKIN_FIELD.columns) * SKIN_FIELD.gap)\n // the tree still drives the animation, but only the skin is drawn\n rig.root.traverse((object) => {\n if ((object as THREE_NS.Mesh).isMesh) object.visible = false\n })\n skinGroup.add(rig.root, skin.mesh)\n skins.push({ rig, skin })\n }\n }\n const clearSkins = (): void => {\n skinGroup?.parent?.remove(skinGroup)\n skinGroup = null\n skins = []\n }\n\n const applyVisual = (): void => {\n for (const entry of placed.values()) {\n if (entry.fate) continue\n animals?.setVisualState(entry.rig, settings.visual)\n }\n }\n\n const onChange = (change: BenchChange): void => {\n if (change.kind === 'layer') return refreshLayers()\n if (change.kind === 'action') {\n if (change.id === 'bite_all') {\n for (const entry of placed.values()) {\n entry.fate = outbreak?.bite(entry.rig.species.id, random, clock)\n if (entry.fate) animals?.setVisualState(entry.rig, visualStateOf(entry.rig.species.outcome, true))\n }\n } else if (change.id === 'recover') {\n for (const entry of placed.values()) {\n delete entry.fate\n delete entry.counted\n animals?.setVisualState(entry.rig, settings.visual)\n }\n } else if (change.id === 'call' && sound) {\n for (const [id, entry] of placed) {\n const at = entry.rig.root.getWorldPosition(new (three as Three).Vector3())\n sound.call(entry.rig.species.id, [at.x, at.y + 0.5, at.z], { key: id })\n }\n } else if (change.id === 'skin_40') addSkins()\n else if (change.id === 'clear_skins') clearSkins()\n return\n }\n switch (change.id) {\n case 'visual_state': settings.visual = String(change.value) as AnimalVisualState; applyVisual(); break\n case 'gait': settings.gait = String(change.value); break\n case 'speed': settings.speed = Number(change.value); break\n case 'outbreak': settings.outbreak = Boolean(change.value); if (outbreak) outbreak.enabled = settings.outbreak; break\n case 'time_scale': settings.timeScale = Number(change.value); rebuildOutbreak(); break\n default: break\n }\n }\n\n const frame = (dt: number): void => {\n time += dt * settings.speed\n clock += dt\n sound?.update(dt)\n for (const [id, entry] of placed) {\n if (!entry.rig.root.parent) {\n placed.delete(id)\n continue\n }\n statusOf(entry)\n animals?.animate(entry.rig, { time, delta: dt * settings.speed, clip: settings.gait, split: host?.bench?.layer('split') ?? false })\n animals?.updateSplit(entry.rig, dt)\n }\n for (const { rig, skin } of skins) {\n animals?.animate(rig, { time, delta: dt * settings.speed, clip: settings.gait })\n rig.root.updateMatrixWorld(true)\n skin.update()\n }\n }\n\n const countMeshes = (only?: 'skins'): { drawCalls: number; triangles: number } => {\n let drawCalls = 0\n let triangles = 0\n const count = (root: THREE_NS.Object3D): void =>\n root.traverse((object) => {\n const mesh = object as THREE_NS.Mesh\n if (!mesh.isMesh || !mesh.visible) return\n drawCalls += 1\n const geometry = mesh.geometry\n triangles += geometry.index ? geometry.index.count / 3 : geometry.attributes.position.count / 3\n })\n if (only !== 'skins') for (const entry of placed.values()) count(entry.rig.root)\n for (const { skin } of skins) count(skin.mesh)\n return { drawCalls, triangles: Math.round(triangles) }\n }\n\n const deps: ScenarioDeps = {\n text: text.scenarios,\n species: () => species,\n ensure: () => ensure((three ?? host?.THREE) as Three),\n outbreak: () => outbreak,\n rebuildOutbreak: (enabled) => {\n settings.outbreak = enabled\n rebuildOutbreak()\n return outbreak\n },\n skins: () => {\n ensure((three ?? host?.THREE) as Three)\n addSkins()\n return { count: skins.length, meshes: countMeshes('skins'), nodes: skins[0]?.skin.nodes.length ?? 0, rich: skins[0]?.rig.triangles ?? 0 }\n },\n clearSkins,\n }\n\n return {\n id: ANIMALS_AREA_ID,\n name: text.name,\n group: text.group,\n get placed() {\n return placed\n },\n items: () => species.map(item),\n area: (): ModuleArea => {\n // the page's line-up: each animal given room by its length (and a gap), the row centred on the origin\n const gap = 0.85\n let cursor = 0\n const spots = species.map((animal) => {\n const length = lineUpLength(animal.body)\n const x = cursor + length / 2\n cursor += length + gap\n return { animal, x }\n })\n const offset = -(cursor - gap) / 2\n return {\n placements: spots.map(({ animal, x }) => ({ item: `${ANIMAL_ITEM_PREFIX}${animal.id}`, position: [x + offset, 0, 0], rotationY: animal.body.kind === 'snake' ? 2.2 : 1.1 })),\n view: { eye: [0, 4, Math.max(10, (cursor - gap) * 0.55)], target: [0, 0.6, 0] },\n }\n },\n controls: (): ModuleControl[] => [\n { kind: 'choice', id: 'visual_state', name: text.controls.visual_state, hint: text.hints.visual_state, options: (['alive', 'infected', 'dead'] as const).map((id) => ({ id, name: text.options.visual_state[id] as string })), default: 'alive' },\n { kind: 'choice', id: 'gait', name: text.controls.gait, hint: text.hints.gait, options: (['idle', 'walk', 'run'] as const).map((id) => ({ id, name: text.options.gait[id] as string })), default: 'walk' },\n { kind: 'number', id: 'speed', name: text.controls.speed, min: 0, max: 4, step: 0.25, default: 1 },\n { kind: 'boolean', id: 'outbreak', name: text.controls.outbreak, hint: text.hints.outbreak, default: true },\n { kind: 'number', id: 'time_scale', name: text.controls.time_scale, hint: text.hints.time_scale, min: 1, max: 600, step: 1, default: DEFAULT_TIME_SCALE },\n { kind: 'action', id: 'bite_all', name: text.actions.bite_all },\n { kind: 'action', id: 'recover', name: text.actions.recover },\n { kind: 'action', id: 'call', name: text.actions.call },\n { kind: 'action', id: 'skin_40', name: text.actions.skin_40 },\n { kind: 'action', id: 'clear_skins', name: text.actions.clear_skins },\n ],\n layers: (): ModuleLayer[] => (['joints', 'split', 'sound_range', 'carcass_lure'] as const).map((id) => ({ id, name: text.layers[id], default: false })),\n metrics: (): ModuleMetric[] => [\n { id: 'animals', name: text.metrics.animals, read: () => placed.size + skins.length },\n { id: 'draw_calls', name: text.metrics.draw_calls, read: () => countMeshes().drawCalls },\n { id: 'triangles', name: text.metrics.triangles, read: () => countMeshes().triangles },\n { id: 'nodes_per_skin', name: text.metrics.nodes_per_skin, read: () => skins[0]?.skin.nodes.length ?? 0 },\n { id: 'incubating', name: text.metrics.incubating, read: () => [...placed.values()].filter((p) => p.fate && !p.counted).length },\n { id: 'turned', name: text.metrics.turned, read: () => bitten().turned },\n { id: 'dead', name: text.metrics.dead, read: () => bitten().dead },\n ],\n scenarios: (): ModuleScenario<AnimalsContext>[] => createScenarios(deps),\n attach(lent) {\n host = lent\n ensure(lent.THREE as Three)\n sound = lent.audio && options.sound !== false ? createAnimalSound(lent.audio, options.sound ?? {}) : null\n const stopChange = lent.bench?.onChange(onChange)\n const stopFrame = lent.onFrame(frame)\n return () => {\n stopChange?.()\n stopFrame()\n clearRings()\n clearSkins()\n sound?.dispose()\n sound = null\n host = null\n }\n },\n }\n\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,MAAA,gBAAA;IACE,MAAQ;MACN;MACA;MACA;MACA;IACF;IACA,MAAQ;MACN;MACA;MACA;MACA;IACF;IACA,QAAU;MACR;MACA;MACA;MACA;IACF;EACF;ACfO,MAAM,cAAc;ACYpB,MAAM,kBAAkB,CAAC,SAAyC;AACvE,UAAM,MAAM,KAAK;AACjB,UAAM,cAAc,IAAI,OAAO;AAC/B,UAAM,cAAc,IAAI,OAAO;AAC/B,UAAM,aAAa,IAAI,OAAO;AAC9B,UAAM,aAAa,IAAI,OAAO;AAC9B,UAAM,aAAa,IAAI,OAAO,IAAI,IAAI,MAAM,IAAI,IAAI;AACpD,UAAM,iBAAiB,IAAI,KAAK,KAAK,IAAI,WAAW,IAAI,IAAI,KAAK,KAAK,IAAI,cAAc,WAAW,IAAI;AACvG,UAAM,gBAAgB,IAAI,KAAK,KAAK,IAAI,UAAU,IAAI,IAAI,KAAK,KAAK,IAAI,aAAa,UAAU,IAAI;AACnG,UAAM,cAAc,iBAAiB,iBAAiB,KAAK,KAAK,MAAM,KAAK,KAAK;AAChF,WAAO,EAAE,gBAAgB,eAAe,YAAY,YAAY,MAAM,KAAK,IAAI,KAAK,MAAM,MAAM,KAAK,IAAI,KAAK,GAAG,aAAa,KAAK,CAAC,EAAE;EACxI;AAGO,MAAM,WAAW,CAAC,SAA2B,KAAK,IAAI,KAAK,IAAI,GAAG,KAAK,OAAO,KAAK,QAAQ,MAAM,KAAK,IAAI,CAAC;AAG3G,MAAM,eAAe,CAAC,SAAwE;AACnG,QAAI,KAAK,SAAS,YAAa,QAAO,KAAK,IAAI;AAC/C,QAAI,KAAK,SAAS,QAAS,QAAO;AAClC,WAAO,KAAK,IAAI;EAClB;AEvBO,MAAM,gBAAgB,CAAC,SAAwB,UAAmB,YAAY,UAA6B;AAChH,QAAI,UAAW,QAAO;AACtB,QAAI,CAAC,SAAU,QAAO;AACtB,QAAI,YAAY,QAAS,QAAO;AAChC,WAAO,YAAY,SAAS,SAAS;EACvC;;;AGjBA,MAAM,IAAI,CAAmB,IAAO,UAAmB,GAAW,GAAW,GAAW,GAAW,OAAgB,WAAkC,EAAE,IAAI,UAAU,GAAG,GAAG,GAAG,GAAG,OAAO,MAAM;AAGvL,MAAM,YAAY;IACvB,EAAE,iBAAiB,MAAM,KAAK,KAAK,MAAM,GAAG,OAAO,SAAS;IAC5D,EAAE,WAAW,MAAM,MAAM,MAAM,GAAK,GAAG,OAAO,SAAS;IACvD,EAAE,cAAc,MAAM,KAAK,KAAK,KAAK,GAAG,OAAO,SAAS;IACxD,EAAE,cAAc,MAAM,KAAK,KAAK,KAAK,GAAG,OAAO,SAAS;IACxD,EAAE,WAAW,MAAM,KAAK,KAAK,MAAM,MAAM,OAAO,MAAM;IACtD,EAAE,iBAAiB,MAAM,MAAM,MAAM,MAAM,KAAK,OAAO,MAAM;IAC7D,EAAE,iBAAiB,MAAM,GAAK,MAAM,KAAK,KAAK,OAAO,MAAM;IAC3D,EAAE,UAAU,MAAM,KAAK,MAAM,KAAK,KAAK,OAAO,MAAM;IACpD,EAAE,WAAW,MAAM,KAAK,MAAM,KAAK,KAAK,OAAO,MAAM;IACrD,EAAE,UAAU,MAAM,KAAK,MAAM,KAAK,GAAK,OAAO,MAAM;IACpD,EAAE,WAAW,MAAM,KAAK,MAAM,KAAK,GAAK,OAAO,MAAM;IACrD,EAAE,eAAe,MAAM,KAAK,KAAK,KAAK,GAAG,MAAM,QAAQ;IACvD,EAAE,eAAe,MAAM,KAAK,KAAK,KAAK,GAAG,OAAO,QAAQ;IACxD,EAAE,iBAAiB,MAAM,KAAK,KAAK,GAAK,GAAG,OAAO,QAAQ;IAC1D,EAAE,YAAY,MAAM,KAAK,MAAM,MAAM,GAAG,MAAM,WAAW;IACzD,EAAE,iBAAiB,MAAM,GAAK,KAAK,MAAM,GAAG,MAAM,WAAW;IAC7D,EAAE,UAAU,MAAM,KAAK,KAAK,MAAM,GAAG,MAAM,WAAW;IACtD,EAAE,qBAAqB,MAAM,KAAK,KAAK,KAAK,GAAG,MAAM,WAAW;IAChE,EAAE,eAAe,MAAM,KAAK,MAAM,KAAK,GAAG,MAAM,WAAW;IAC3D,EAAE,UAAU,MAAM,KAAK,KAAK,KAAK,GAAG,MAAM,WAAW;IACrD,EAAE,UAAU,MAAM,KAAK,MAAM,KAAK,GAAG,MAAM,WAAW;IACtD,EAAE,gBAAgB,OAAO,KAAK,KAAK,MAAM,KAAK,MAAM,MAAM;IAC1D,EAAE,QAAQ,OAAO,GAAG,GAAG,MAAM,GAAG,OAAO,OAAO;IAC9C,EAAE,cAAc,OAAO,GAAG,GAAG,MAAM,GAAG,OAAO,OAAO;IACpD,EAAE,YAAY,OAAO,GAAG,GAAG,MAAM,GAAG,OAAO,OAAO;IAClD,EAAE,cAAc,OAAO,GAAG,GAAG,MAAM,GAAG,OAAO,OAAO;IACpD,EAAE,QAAQ,OAAO,GAAG,GAAG,MAAM,GAAG,OAAO,OAAO;IAC9C,EAAE,mBAAmB,OAAO,KAAK,GAAK,KAAK,GAAG,MAAM,MAAM;IAC1D,EAAE,SAAS,OAAO,GAAK,KAAK,KAAK,GAAG,MAAM,MAAM;IAChD,EAAE,eAAe,OAAO,KAAK,KAAK,KAAK,GAAG,MAAM,MAAM;IACtD,EAAE,QAAQ,OAAO,KAAK,KAAK,KAAK,GAAG,MAAM,MAAM;IAC/C,EAAE,QAAQ,OAAO,KAAK,GAAK,MAAM,GAAG,MAAM,MAAM;IAChD,EAAE,eAAe,OAAO,MAAM,KAAK,KAAK,GAAG,MAAM,MAAM;IACvD,EAAE,cAAc,OAAO,GAAK,GAAK,KAAK,GAAG,MAAM,MAAM;IACrD,EAAE,cAAc,OAAO,KAAK,KAAK,KAAK,GAAG,MAAM,MAAM;IACrD,EAAE,aAAa,OAAO,GAAK,GAAK,KAAK,GAAG,MAAM,MAAM;IACpD,EAAE,WAAW,OAAO,KAAK,GAAK,KAAK,GAAG,MAAM,MAAM;IAClD,EAAE,SAAS,OAAO,KAAK,KAAK,KAAK,GAAG,MAAM,MAAM;IAChD,EAAE,SAAS,OAAO,KAAK,KAAK,KAAK,GAAG,MAAM,MAAM;IAChD,EAAE,cAAc,OAAO,KAAK,KAAK,GAAK,GAAG,MAAM,MAAM;IACrD,EAAE,cAAc,OAAO,KAAK,KAAK,MAAM,GAAG,MAAM,MAAM;IACtD,EAAE,iBAAiB,MAAM,KAAK,MAAM,MAAM,GAAK,OAAO,aAAa;IACnE,EAAE,aAAa,MAAM,KAAK,KAAK,KAAK,GAAG,MAAM,aAAa;IAC1D,EAAE,YAAY,OAAO,KAAK,KAAK,KAAK,GAAG,MAAM,aAAa;IAC1D,EAAE,eAAe,OAAO,GAAK,KAAK,KAAK,GAAG,OAAO,aAAa;IAC9D,EAAE,cAAc,OAAO,GAAK,KAAK,KAAK,GAAG,OAAO,aAAa;IAC7D,EAAE,uBAAuB,MAAM,KAAK,KAAK,MAAM,GAAG,MAAM,aAAa;IACrE,EAAE,eAAe,MAAM,KAAK,KAAK,KAAK,GAAG,OAAO,aAAa;IAC7D,EAAE,aAAa,MAAM,KAAK,KAAK,KAAK,GAAG,MAAM,aAAa;IAC1D,EAAE,kBAAkB,MAAM,KAAK,KAAK,KAAK,GAAG,OAAO,aAAa;IAChE,EAAE,WAAW,MAAM,MAAM,MAAM,KAAK,GAAG,OAAO,aAAa;IAC3D,EAAE,WAAW,MAAM,MAAM,MAAM,KAAK,GAAG,OAAO,aAAa;IAC3D,EAAE,kBAAkB,MAAM,KAAK,KAAK,KAAK,GAAG,OAAO,aAAa;IAChE,EAAE,oBAAoB,MAAM,KAAK,GAAK,MAAM,GAAG,OAAO,aAAa;IACnE,EAAE,gBAAgB,MAAM,KAAK,KAAK,KAAK,GAAG,OAAO,aAAa;IAC9D,EAAE,gBAAgB,MAAM,KAAK,KAAK,MAAM,GAAG,OAAO,aAAa;IAC/D,EAAE,aAAa,MAAM,KAAK,MAAM,MAAM,GAAG,OAAO,aAAa;EAC/D;AAIO,MAAM,OAAO,OAAO,YAAY,UAAU,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;Aa/CtE,MAAM,iBAAiB;AAMhB,MAAM,yBAAyB,CAAC,OAAe,OAA0B,CAAC,MAAoC;AACnH,UAAM,QAAQ,oBAAI,IAA6B;AAC/C,UAAM,OAAO,oBAAI,IAAa;AAC9B,UAAM,OAAO,CAAC,OAAgB,UAAwB;AACpD,UAAI,CAAC,SAAS,OAAO,UAAU,YAAY,QAAQ,kBAAkB,KAAK,IAAI,KAAK,EAAG;AACtF,WAAK,IAAI,KAAK;AACd,YAAM,YAAY;AAClB,UAAI,UAAU,YAAY;AACxB,cAAM,IAAI,KAAgC;AAC1C;MACF;AACA,UAAI,UAAU,cAAc,UAAU,iBAAkB;AACxD,iBAAW,OAAO,MAAO,MAAM,MAAkC,GAAG,GAAG,QAAQ,CAAC;IAClF;AACA,eAAW,OAAO,MAAO,KAAI,CAAC,KAAK,SAAS,GAAG,EAAG,MAAM,MAAkC,GAAG,GAAG,CAAC;AACjG,WAAO;EACT;AAIA,MAAM,kBAAkB,CAAC,WAA6C;AACpE,aAAS,OAAuC,QAAQ,MAAM,OAAO,KAAK,OAAQ,KAAI,CAAC,KAAK,QAAS,QAAO;AAC5G,WAAO;EACT;AAQO,MAAM,iBAAiB,CAAC,OAAc,MAA+B,UAA2B,CAAC,MAAgB;AACtH,UAAM,aAAa,IAAI,IAAI,QAAQ,SAAS,CAAC,CAAC;AAC9C,UAAM,QAAmC,CAAC;AAC1C,UAAM,UAAU,oBAAI,IAAqC;AACzD,SAAK,SAAS,CAAC,WAAW;AACxB,YAAMA,QAAO;AACb,UAAI,CAACA,MAAK,UAAU,OAAO,SAAS,SAAS,KAAK,WAAW,IAAI,MAAM,GAAG;AACxE,gBAAQ,IAAI,QAAQ,MAAM,MAAM;AAChC,cAAM,KAAK,MAAM;MACnB;IACF,CAAC;AAED,UAAM,YAAsB,CAAC;AAC7B,UAAM,UAAoB,CAAC;AAC3B,UAAM,QAAkB,CAAC;AACzB,UAAM,WAAqB,CAAC;AAC5B,UAAM,cAAwB,CAAC;AAC/B,UAAM,UAAU,oBAAI,IAA2C;AAC/D,UAAM,QAAQ,IAAI,MAAM,MAAM;AAC9B,UAAM,SAAS,IAAI,MAAM,QAAQ;AACjC,UAAM,SAAS,IAAI,MAAM,QAAQ;AAEjC,UAAM,WAAW,CAAC,YAA+D;AAC/E,YAAM,QAAQ,QAAQ;AACtB,UAAI,CAAC,OAAO,WAAY,QAAO;AAC/B,YAAM,SAAS,QAAQ,IAAI,OAAO;AAClC,UAAI,OAAQ,QAAO;AACnB,YAAM,UAAU,MAAM,WAAW,IAAI;AACrC,UAAI,CAAC,QAAS,QAAO;AACrB,YAAM,SAAS,EAAE,MAAM,QAAQ,aAAa,GAAG,GAAG,MAAM,OAAO,MAAM,MAAM,EAAE,MAAM,OAAO,MAAM,OAAO,QAAQ,MAAM,OAAO;AAC5H,cAAQ,IAAI,SAAS,MAAM;AAC3B,aAAO;IACT;AAEA,SAAK,SAAS,CAAC,WAAW;AACxB,YAAMA,QAAO;AACb,UAAI,CAACA,MAAK,UAAU,CAAC,gBAAgB,MAAM,EAAG;AAC9C,YAAMC,YAAY,MAAM,QAAQD,MAAK,QAAQ,IAAIA,MAAK,SAAS,CAAC,IAAIA,MAAK;AACzE,YAAME,YAAWF,MAAK;AACtB,YAAM,WAAWE,UAAS,WAAW;AACrC,YAAM,kBAAkBA,UAAS,WAAW;AAC5C,YAAM,KAAKA,UAAS,WAAW;AAC/B,YAAM,SAAS,IAAI,MAAM,QAAQ;AACjC,UAAI,YAAY,QAAQ,IAAI,MAAM;AAClC,UAAI,cAAc,QAAW;AAC3B,oBAAY,QAAQ,IAAI,OAAO,MAAiC,KAAK;AACrE,eAAO,aAAa;AACpB,eAAO,KAAK,OAAO,MAAM;MAC3B;AACA,YAAM,eAAe,IAAI,MAAM,QAAQ,EAAE,gBAAgB,MAAM;AAC/D,YAAM,SAASD,UAAS,MAAM,SAASA,UAAS,GAAG,IAAI;AACvD,YAAM,WAAWA,UAAS,YAAYA,UAAS,SAAS,IAAIA,UAAS,SAAS,IAAIA,UAAS,SAAS,MAAMA,UAAS,qBAAqB,KAAK;AAC7I,YAAM,QAAQC,UAAS,QAAQA,UAAS,MAAM,QAAQ;AACtD,YAAM,QAAQ,QAAQ,MAAM,SAAS,SAAS;AAC9C,eAAS,OAAO,GAAG,OAAO,OAAO,QAAQ;AACvC,cAAM,KAAK,QAAS,MAAM,IAAI,IAAe;AAC7C,eAAO,oBAAoB,UAAU,EAAE,EAAE,aAAa,MAAM;AAC5D,kBAAU,KAAK,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;AAC3C,YAAI,gBAAiB,QAAO,oBAAoB,iBAAiB,EAAE,EAAE,aAAa,YAAY,EAAE,UAAU;YACrG,QAAO,IAAI,GAAG,GAAG,CAAC;AACvB,gBAAQ,KAAK,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;AACzC,cAAM,KAAKD,UAAS,SAAS,MAAM,IAAI,GAAG,GAAG,CAAC,CAAC,EAAE,oBAAoB;AACrE,YAAI,UAAU,MAAMA,UAAS,KAAK;AAChC,gBAAM,SAASA,UAAS,IAAI;AAC5B,gBAAM,KAAO,GAAG,KAAK,EAAE,IAAI,OAAO,IAAK,IAAK,KAAK;AACjD,gBAAM,KAAO,GAAG,KAAK,EAAE,IAAI,OAAO,IAAK,IAAK,KAAK;AACjD,gBAAM,SAAS,KAAK,IAAI,OAAO,QAAQ,GAAI,IAAI,OAAO,QAAS,CAAC;AAChE,gBAAM,SAAS,KAAK,IAAI,OAAO,SAAS,IAAK,IAAI,KAAK,OAAO,SAAU,CAAC;AACxE,gBAAM,UAAU,SAAS,OAAO,QAAQ,UAAU;AAClD,gBAAM,SAAS,IAAI,MAAM,MAAM,EAAE,OAAQ,OAAO,KAAK,MAAM,IAAe,KAAM,OAAO,KAAK,SAAS,CAAC,IAAe,KAAM,OAAO,KAAK,SAAS,CAAC,IAAe,KAAK,MAAM,cAAc,CAAC;QAC5L;AACA,YAAI,WAAW,IAAK,OAAM,KAAKA,UAAS,UAAU,GAAG;AACrD,cAAM,KAAK,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AACpC,iBAAS,KAAKA,UAAS,aAAa,KAAKA,UAAS,aAAa,GAAG,WAAW,MAAM,IAAI,CAAC;AACxF,oBAAY,KAAK,SAAS;MAC5B;IACF,CAAC;AAED,UAAM,WAAW,IAAI,MAAM,eAAe;AAC1C,aAAS,aAAa,YAAY,IAAI,MAAM,uBAAuB,WAAW,CAAC,CAAC;AAChF,aAAS,aAAa,UAAU,IAAI,MAAM,uBAAuB,SAAS,CAAC,CAAC;AAC5E,aAAS,aAAa,SAAS,IAAI,MAAM,uBAAuB,OAAO,CAAC,CAAC;AACzE,aAAS,aAAa,YAAY,IAAI,MAAM,uBAAuB,UAAU,CAAC,CAAC;AAC/E,aAAS,aAAa,cAAc,IAAI,MAAM,uBAAuB,aAAa,CAAC,CAAC;AACpF,aAAS,sBAAsB;AAE/B,UAAM,WAAW,EAAE,OAAO,MAAM,IAAI,MAAM,IAAI,MAAM,QAAQ,CAAC,EAAE;AAC/D,UAAM,aAAa,CAAC,WAAoE;AACtF,aAAO,SAAS,SAAS;AACzB,aAAO,eAAe,OAAO,aAC1B;QACC;QACA;sBAA0C,MAAM,MAAM;MACxD,EACC,QAAQ,iCAAiC,kEAAkE,EAC3G,QAAQ,2BAA2B,iHAAiH;IACzJ;AAEA,UAAM,WAAW,IAAI,MAAM,qBAAqB,EAAE,WAAW,GAAG,WAAW,EAAE,CAAC;AAC9E,aAAS,kBAAkB,CAAC,WAAW;AACrC,iBAAW,MAAM;AACjB,aAAO,iBAAiB,OAAO,eAC5B,QAAQ,qBAAqB,+DAA+D,EAC5F,QAAQ,iDAAiD,6CAA6C,EACtG,QAAQ,oCAAoC,qCAAqC,EACjF,QAAQ,oCAAoC,qCAAqC,EACjF,QAAQ,mCAAmC,mDAAmD;IACnG;AACA,aAAS,wBAAwB,MAAM,WAAW,MAAM,MAAM;AAC9D,UAAM,gBAAgB,IAAI,MAAM,kBAAkB,EAAE,cAAc,MAAM,iBAAiB,CAAC;AAC1F,kBAAc,kBAAkB;AAChC,kBAAc,wBAAwB,MAAM,gBAAgB,MAAM,MAAM;AAExE,UAAM,OAAO,IAAI,MAAM,KAAK,UAAU,QAAQ;AAC9C,SAAK,sBAAsB;AAC3B,SAAK,gBAAgB;AACrB,SAAK,kBAAkB,IAAI;AAC3B,WAAO;MACL;MACA;MACA;MACA,SAAS;AACP,aAAK,kBAAkB,IAAI;AAC3B,iBAAS,QAAQ,GAAG,QAAQ,MAAM,QAAQ,QAAU,UAAS,MAAM,KAAK,EAA6B,KAAM,MAAM,KAAK,EAA8B,WAAW;MACjK;IACF;EACF;;;AGlLA,MAAM,EAAE,GAAG,IAAI;AAGR,MAAM,cAAc,CAAC,KAAgB,WAAgC;AAC1E,UAAM,EAAE,MAAM,GAAG,OAAO,IAAI,KAAK,IAAI;AACrC,UAAM,UAAU,IAAI;AACpB,UAAM,OAAO,QAAQ;AACrB,UAAM,YAAY,QAAQ,OAAO;AACjC,UAAM,YAAY,QAAQ,OAAO;AACjC,UAAM,YAAY,QAAQ,OAAO;AACjC,UAAM,SAAS,SAAS,SAAS,CAAC;AAClC,UAAM,KAAK,SAAS,SAAS,CAAC,SAAS,QAAQ,QAAQ,SAAS,SAAS,QAAQ,SAAS;AAC1F,QAAI,SAAS,MAAM,SAAU,YAAY,MAAM,MAAO,MAAM,IAAI;AAChE,UAAM,eAAe,SAAS,IAAI,OAAO,MAAM;AAC/C,QAAI,MAAM,SAAS,MAAM,eAAe,IAAI,MAAM,SAAS,KAAK,KAAK,IAAI,GAAG,KAAK,CAAC;AAClF,QAAI,KAAK,QAAQ,CAAC,KAAK,UAAU;AAC/B,UAAI,QAAQ;AACV,YAAI,IAAI,SAAS,IAAI;AACrB,YAAI,KAAK,SAAS,IAAI;AAAA,MACxB,WAAW,SAAS,QAAQ;AAC1B,YAAI,IAAI,SAAS,IAAI;AACrB,YAAI,KAAK,SAAS,IAAI;AAAA,MACxB,OAAO;AACL,cAAM,QAAQ,IAAI,QAAQ,QAAQ;AAClC,YAAI,IAAI,SAAS,IAAI,CAAC,KAAK,IAAI,KAAK,IAAI;AACxC,YAAI,KAAK,SAAS,IAAI,CAAC,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,CAAC,IAAI;AAAA,MACxD;AACA,UAAI,KAAK,SAAS,IAAI,EAAE,IAAI,IAAI,SAAS,IAAI,IAAI,KAAK,SAAS;AAAA,IACjE,CAAC;AACD,QAAI,MAAM,QAAQ,CAAC,SAAS;AAC1B,YAAM,EAAE,MAAM,UAAU,MAAM,IAAI;AAClC,UAAI,QAAQ;AACV,cAAM,OAAO,YAAY,OAAO,KAAK,IAAI,IAAI,KAAK,IAAI,OAAO,KAAK,IAAI,IAAI,KAAK,IAAI;AACnF,iBAAS,SAAS,MAAM,IAAI,SAAS,SAAS,KAAK,KAAK,IAAI,GAAG,KAAK,CAAC;AACrE,iBAAS,SAAS,IAAI,OAAO;AAC7B,cAAM,SAAS,IAAI,QAAQ,YAAY,OAAO,KAAK,IAAI,IAAI,QAAQ,GAAG,IAAI;AAC1E,cAAM,SAAS,IAAI;AACnB,iBAAS,MAAM,MAAM,IAAI,SAAS,MAAM,KAAK,KAAK,IAAI,GAAG,KAAK,CAAC;AAAA,MACjE,OAAO;AACL,cAAM,QAAQ,aAAa,SAAS,SAAS,KAAK,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,CAAC,IAAI,MAAM;AACpF,cAAM,UAAU,aAAa,SAAS,QAAQ,KAAK,IAAI,KAAK,IAAI,IAAI,QAAQ,GAAG,CAAC,IAAI,MAAM;AAC1F,iBAAS,SAAS,MAAO,OAAO,KAAM,IAAI,OAAO,QAAQ,OAAO,MAAM,SAAS,SAAS,KAAK,KAAK,IAAI,GAAG,KAAK,CAAC;AAC/G,iBAAS,SAAS,IAAI,QAAQ,QAAQ,UAAU,QAAQ;AACxD,cAAM,SAAS,IAAI,OAAO;AAC1B,cAAM,SAAS,IAAI;AACnB,iBAAS,MAAM,OAAO,QAAQ,MAAM,IAAI,QAAQ,SAAS,MAAM,KAAK,KAAK,IAAI,GAAG,KAAK,CAAC;AAAA,MACxF;AAAA,IACF,CAAC;AACD,UAAM,OAAO,aAAa,SAAS,SAAS,KAAK,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,CAAC,KAAK,IAAI,MAAM;AACxF,QAAI,IAAI,MAAM;AACZ,UAAI,KAAK,SAAS,IAAI,IAAI,aAAa,SAAS,UAAU,CAAC,YAAY,KAAK,IAAI,GAAG,KAAK,IAAI,IAAI,QAAQ,CAAC,CAAC,IAAI,OAAO,KAAK,QAAQ,SAAS,MAAM;AACjJ,UAAI,KAAK,SAAS,IAAI,SAAS,SAAS,KAAK,IAAI,IAAI,GAAG,IAAI,MAAM;AAAA,IACpE;AACA,QAAI,IAAI,IAAK,KAAI,IAAI,SAAS,IAAI,aAAa,SAAS,SAAS,KAAK,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,CAAC,IAAI,KAAK,IAAI,KAAK,IAAI,IAAI,EAAE,CAAC,IAAI,MAAM;AACrI,QAAI,IAAI,SAAU,KAAI,SAAS,SAAS,KAAK,KAAK,SAAS,MAAM,SAAS,MAAM;AAAA,EAClF;;;ACvDA,MAAM,EAAE,IAAAE,IAAG,IAAI;AAGR,MAAM,mBAAmB,CAAC,KAAgB,QAAuB,UAAuG;AAC7K,UAAM,EAAE,MAAM,GAAG,OAAO,IAAI,KAAK,IAAI;AACrC,UAAM,UAAU,IAAI;AACpB,UAAM,OAAO,QAAQ;AACrB,QAAI,IAAI,MAAM;AACZ,UAAI,KAAK,SAAS,MAAMA,MAAK,IAAI,OAAO,IAAI,KAAK,SAAS,KAAK,KAAK,IAAI,GAAG,KAAK,CAAC;AACjF,UAAI,KAAK,SAAS,MAAM,KAAK,KAAK,KAAK,MAAM,KAAK,OAAO,IAAI,KAAK,SAAS,KAAK,KAAK,IAAI,GAAG,KAAK,CAAC;AAClG,UAAI,KAAK,QAAQ,CAAC,QAAQ;AACxB,YAAI,IAAI,SAAS,IAAI,IAAI,UAAU,IAAI,UAAU,OAAO;AACxD,YAAI,KAAK,SAAS,IAAI;AACtB,YAAI,KAAK,SAAS,IAAI;AAAA,MACxB,CAAC;AACD,UAAI,IAAI,KAAM,KAAI,KAAK,SAAS,IAAI,IAAI,YAAY;AACpD,UAAI,IAAI,IAAK,KAAI,IAAI,SAAS,IAAI;AAClC;AAAA,IACF;AACA,UAAM,MAAM,SAAS,QAAQ,QAAQ,QAAQ,SAAS,SAAS,QAAQ,SAAS,MAAM,IAAI,WAAW,MAAM;AAC3G,QAAI,SAAS,KAAK,KAAK,IAAIA;AAC3B,UAAM,OAAO,SAAS,QAAS,QAAQ,SAAS,MAAM,SAAS,MAAM,OAAQ,MAAM;AACnF,UAAM,YAAY,SAAS,QAAQ,OAAO;AAC1C,UAAM,OAAO,SAAS,QAAQ,OAAO;AACrC,QAAI,KAAK,QAAQ,CAAC,KAAK,UAAU;AAC/B,UAAI,SAAS,QAAQ;AACnB,YAAI,IAAI,SAAS,IAAI,IAAI;AACzB,YAAI,KAAK,SAAS,IAAI,IAAI;AAAA,MAC5B,OAAO;AACL,cAAM,QAAQ,IAAI,QAAS,KAAK,KAAK;AACrC,cAAM,QAAQ,IAAI,YAAY,UAAU,IAAI,OAAO;AACnD,YAAI,IAAI,SAAS,IAAI,IAAI,SAAS,KAAK,IAAI,KAAK,IAAI,YAAY;AAChE,YAAI,KAAK,SAAS,IAAI,IAAI,SAAS,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,CAAC,IAAI,OAAO;AAAA,MAC3E;AACA,UAAI,KAAK,SAAS,IAAI,EAAE,IAAI,IAAI,SAAS,IAAI,IAAI,KAAK,SAAS,KAAK;AAAA,IACtE,CAAC;AACD,UAAM,MAAM,SAAS,SAAS,IAAI,KAAK,IAAI,KAAK,IAAI,IAAI,KAAK,CAAC,KAAK,SAAS,QAAQ,OAAO,SAAS,IAAI;AACxG,QAAI,MAAM,SAAS,IAAI,MAAM;AAC7B,UAAM,YAAY,IAAI;AACtB,QAAI,WAAW;AACb,gBAAU,SAAS,IAAI,SAAS,SAAS,QAAQ,SAAS,KAAK,IAAI,IAAI,QAAQA,MAAK,GAAG,IAAI,OAAO;AAClG,gBAAU,MAAM,IAAI,KAAK,SAAS,SAAS,KAAK,IAAI,IAAI,GAAG,IAAI,QAAQ;AAAA,IACzE;AACA,UAAM,MAAM,IAAI,WAAW,OAAO;AAClC,QAAI,IAAI,MAAM;AACZ,UAAI,KAAK,SAAS,IAAI,IAAI,YAAY,OAAO,SAAS,SAAS,KAAK,IAAI,IAAI,GAAG,IAAI,OAAO,KAAK,IAAI,IAAI,QAAQ,CAAC,IAAI;AACpH,UAAI,KAAK,SAAS,IAAI,SAAS,SAAS,KAAK,IAAI,IAAI,IAAI,IAAI,OAAO;AAAA,IACtE;AACA,QAAI,IAAI,KAAM,KAAI,KAAK,SAAS,IAAI,IAAI,WAAW,KAAK,IAAI,IAAI,EAAE,IAAI,KAAK,IAAI,IAAI,GAAG,IAAI,OAAO;AACjG,QAAI,IAAI,IAAK,KAAI,IAAI,SAAS,IAAI,IAAI,WAAW,OAAO,KAAK,IAAI,GAAG,KAAK,IAAI,IAAI,CAAC,CAAC,IAAI,OAAO,SAAS,QAAQ,OAAO;AACtH,UAAM,UAAU,QAAQ,OAAO,SAAS,CAAC,IAAI;AAC7C,QAAI,KAAK,QAAQ,CAAC,SAAS,UAAU;AACnC,cAAQ,SAAS,IAAI,KAAK,IAAI,KAAK,UAAU,IAAI,OAAO,QAAQ,GAAG,KAAK,UAAU,OAAO;AAAA,IAC3F,CAAC;AAAA,EACH;;;ACtDA,MAAM,EAAE,IAAAC,IAAG,IAAI;AAGf,MAAM,WAAW,CAAC,OAAc,KAAgB,QAAuB,YAAwC;AAC7G,UAAM,EAAE,MAAM,GAAG,KAAK,IAAI;AAC1B,UAAM,SAA6B,CAAC;AACpC,aAAS,QAAQ,GAAG,QAAQ,IAAI,cAAc,SAAS;AACrD,UAAI;AACJ,UAAI;AACJ,UAAI,IAAI;AACR,UAAI,SAAS,QAAQ;AACnB,YAAI,MAAM,QAAQ;AAClB,YAAI,KAAK,IAAI,IAAI,QAAQ,MAAM,QAAQ,IAAI,IAAI,OAAO,KAAK,IAAI,GAAG,QAAQ,IAAI,GAAG;AAAA,MACnF,OAAO;AACL,cAAM,SAAS,SAAS,QAAQ,KAAK,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,CAAC,KAAK,IAAI;AACtE,YAAI,QAAQ,GAAG;AACb,gBAAM,IAAI,QAAQ;AAClB,cAAI,QAAQ,IAAI,KAAK,OAAO,UAAU,IAAI,KAAK;AAC/C,eAAK,IAAI,KAAK,QAAQ,IAAI,SAAS;AACnC,cAAI,KAAK,IAAI,IAAIA,MAAK,GAAG,IAAI,QAAQ,IAAI;AAAA,QAC3C,OAAO;AACL,gBAAM,SAAS,QAAQ,KAAK;AAC5B,gBAAM,SAAS,QAAQ,QAAQ,KAAK;AACpC,cAAI,KAAK,IAAI,KAAK,IAAI;AACtB,cAAI,KAAK,IAAI,KAAK,IAAI,SAAS;AAC/B,eAAK,QAAQ,KAAK;AAAA,QACpB;AAAA,MACF;AACA,aAAO,KAAK,IAAI,MAAM,QAAQ,GAAG,GAAG,CAAC,CAAC;AAAA,IACxC;AACA,WAAO;AAAA,EACT;AAGO,MAAM,eAAe,CAAC,OAAc,KAAgB,WAAgC;AACzF,UAAM,EAAE,MAAM,GAAG,OAAO,IAAI,KAAK,IAAI;AACrC,UAAM,UAAU,IAAI,iBAAiB,OAAO,QAAQ,MAAM;AAC1D,QAAI,SAAS,OAAQ,KAAI,SAAS,KAAK;AACvC,UAAM,SAAS,SAAS,OAAO,KAAK,QAAQ,OAAO;AACnD,UAAM,KAAK,CAAC,UAAoC,OAAO,KAAK;AAC5D,aAAS,QAAQ,GAAG,QAAQ,IAAI,cAAc,SAAS;AACrD,YAAM,QAAS,IAAI,SAAS,KAAK,EAAoC;AACrE,YAAM,SAAS,KAAK,GAAG,KAAK,CAAC;AAC7B,YAAM,YAAY,UAAU,IAAI,GAAG,CAAC,EAAE,MAAM,EAAE,IAAI,GAAG,CAAC,CAAC,IAAI,GAAG,QAAQ,CAAC,EAAE,MAAM,EAAE,IAAI,GAAG,KAAK,CAAC;AAC9F,YAAM,SAAS,IAAI,GAAG,KAAK,MAAM,UAAU,GAAG,UAAU,CAAC,GAAG,CAAC;AAC7D,YAAM,QAAQ,CAAC,KAAK,MAAM,UAAU,GAAG,KAAK,MAAM,UAAU,GAAG,UAAU,CAAC,CAAC,CAAC;AAAA,IAC9E;AACA,UAAM,OAAO,IAAI;AACjB,UAAM,gBAAgB,GAAG,CAAC,EAAE,MAAM,EAAE,IAAI,GAAG,CAAC,CAAC,EAAE,UAAU;AACzD,SAAK,SAAS,KAAK,GAAG,CAAC,CAAC,EAAE,gBAAgB,eAAe,IAAI;AAC7D,SAAK,SAAS,IAAI,GAAG,KAAK,MAAM,cAAc,GAAG,cAAc,CAAC,GAAG,CAAC;AACpE,SAAK,QAAQ,CAAC,KAAK,MAAM,cAAc,GAAG,KAAK,MAAM,cAAc,GAAG,cAAc,CAAC,CAAC,IAAI,GAAG;AAC7F,UAAM,MAAM,IAAI;AAChB,QAAI,SAAS,IAAI,SAAS,QAAQ,KAAK,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,CAAC,KAAK,IAAI,MAAM;AAC9E,UAAM,SAAS,IAAI;AACnB,WAAO,UAAU,KAAK,IAAI,IAAI,CAAC,IAAI;AACnC,WAAO,MAAM,IAAI,IAAI,KAAK,IAAI,IAAI,EAAE,IAAI;AAAA,EAC1C;;;ACvDO,MAAM,eAAe,CAAC,SAAiC;AAC5D,QAAI,QAAQ;AACZ,WAAO,MAAM;AACX,eAAS;AACT,cAAS,QAAQ,aAAc;AAC/B,UAAI,IAAI,KAAK,KAAK,QAAS,UAAU,IAAK,IAAI,KAAK;AACnD,UAAK,IAAI,KAAK,KAAK,IAAK,MAAM,GAAI,KAAK,CAAC,IAAK;AAC7C,eAAS,IAAK,MAAM,QAAS,KAAK;AAAA,IACpC;AAAA,EACF;AAGO,MAAM,UAAU,CACrB,OACA,QACA,UACA,UACA,GACA,GACA,GACA,IACA,IACA,OACkB;AAClB,UAAM,OAAO,IAAI,MAAM,KAAK,UAAU,QAAQ;AAC9C,SAAK,SAAS,IAAI,KAAK,GAAG,KAAK,GAAG,KAAK,CAAC;AACxC,QAAI,MAAM,MAAM,GAAI,MAAK,SAAS,IAAI,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AAC/D,SAAK,aAAa;AAClB,SAAK,gBAAgB;AACrB,WAAO,IAAI,IAAI;AACf,WAAO;AAAA,EACT;AAaO,MAAM,iBAAiB,CAAC,OAAc,QAA6B;AACxE,UAAM,QAAkB;AAAA,MACtB,SAAS,OAAO,QAAQ;AACtB,cAAM,WAAW,IAAI,MAAM,qBAAqB,OAAO,OAAO,EAAE,OAAO,WAAW,KAAK,WAAW,GAAG,aAAa,MAAM,GAAG,UAAU,CAAC,CAAC,CAAC;AACxI,iBAAS,SAAS,OAAO,SAAS,MAAM,MAAM;AAC9C,YAAI,UAAU,KAAK,QAAQ;AAC3B,eAAO;AAAA,MACT;AAAA,MACA,KAAK,QAAQ,QAAQ,UAAU,GAAG,GAAG,GAAG,IAAI,IAAI,IAAI;AAClD,cAAM,OAAO,QAAQ,OAAO,QAAQ,IAAI,MAAM,eAAe,QAAQ,IAAI,EAAE,GAAG,UAAU,GAAG,GAAG,CAAC;AAC/F,YAAI,GAAI,MAAK,MAAM,IAAI,IAAI,IAAc,EAAY;AACrD,eAAO;AAAA,MACT;AAAA,MACA,MAAM,CAAC,QAAQ,SAAS,SAAS,QAAQ,aAAa,QAAQ,OAAO,QAAQ,IAAI,MAAM,iBAAiB,SAAS,SAAS,QAAQ,IAAI,CAAC,EAAE,UAAU,GAAG,CAAC,SAAS,GAAG,CAAC,GAAG,QAAQ;AAAA,MAC/K,MAAM,QAAQ,QAAQ,UAAU,GAAG,GAAG,GAAG;AACvC,cAAM,QAAQ,MAAM,KAAK,QAAQ,QAAQ,UAAU,GAAG,GAAG,CAAC;AAC1D,cAAM,SAAS,QAAQ;AACvB,YAAI,OAAO,KAAK,KAAK;AACrB,eAAO;AAAA,MACT;AAAA,MACA,MAAM,QAAQ,GAAG,GAAG,GAAG,QAAQ;AAC7B,cAAM,QAAQ,IAAI,MAAM,MAAM;AAC9B,cAAM,SAAS,IAAI,GAAG,GAAG,CAAC;AAC1B,eAAO,IAAI,KAAK;AAChB,YAAI,OAAO,KAAK,EAAE,OAAO,MAAM,MAAM,SAAS,MAAM,GAAG,QAAQ,UAAU,IAAI,MAAM,QAAQ,EAAE,CAAC;AAC9F,eAAO;AAAA,MACT;AAAA,MACA,KAAK,MAAM,QAAQ,GAAG,GAAG,GAAG;AAC1B,cAAM,cAAc,MAAM,SAAS,SAAU,EAAE,WAAW,KAAK,CAAC;AAChE,YAAI,cAAc;AAClB,mBAAW,QAAQ,CAAC,IAAI,CAAC,EAAG,KAAI,KAAK,KAAK,MAAM,KAAK,MAAM,QAAQ,aAAa,OAAO,GAAG,GAAG,CAAC,CAAC;AAAA,MACjG;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAEO,MAAM,YAAY,CAAC,OAAc,SAA+B,YAAgE;AACrI,UAAM,MAAiB;AAAA,MACrB;AAAA,MACA,MAAM,IAAI,MAAM,MAAM;AAAA,MACtB,OAAO,IAAI,MAAM,MAAM;AAAA,MACvB,WAAW,CAAC;AAAA,MACZ,QAAQ,CAAC;AAAA,MACT,QAAQ,CAAC;AAAA,MACT,MAAM,CAAC;AAAA,MACP,MAAM,CAAC;AAAA,MACP,MAAM,CAAC;AAAA,MACP,gBAAgB,CAAC;AAAA,MACjB,MAAM,CAAC;AAAA,MACP,OAAO,CAAC;AAAA,MACR,UAAU,CAAC;AAAA,MACX,OAAO,QAAQ;AAAA,MACf,MAAM;AAAA,MACN,eAAe;AAAA,MACf,cAAc;AAAA,MACd,MAAM,IAAI,MAAM,QAAQ;AAAA,MACxB,UAAU;AAAA,MACV,MAAM;AAAA,MACN,WAAW;AAAA,MACX,WAAW;AAAA,MACX,WAAW;AAAA,IACb;AACA,QAAI,KAAK,IAAI,IAAI,KAAK;AACtB,WAAO;AAAA,EACT;;;AC3GA,MAAM,aAAa,CAAC,OAAc,KAAgB,MAAgB,UAAyH;AACzL,UAAM,QAAQ,eAAe,OAAO,GAAG;AACvC,UAAM,EAAE,WAAW,MAAM,KAAK,IAAI;AAClC,eAAW,QAAQ,CAAC,IAAI,CAAC,GAAG;AAC1B,YAAM,WAAW,MAAM,MAAM,WAAW,OAAO,KAAK,IAAI,KAAK,EAAE,CAAC,IAAI,MAAM,KAAK,IAAI,MAAM,KAAK,IAAI,KAAK,IAAI,MAAM,QAAQ,OAAO,MAAM,MAAM,CAAC,EAAE,eAAe,IAAI,IAAI,CAAC;AACvK,YAAM,MAAM,UAAU,KAAK,IAAI,MAAM,MAAM,GAAG,GAAG,CAAC;AAClD,YAAM,KAAK,UAAU,KAAK,KAAK,QAAQ,MAAM,SAAS,KAAK,KAAK,IAAI,MAAM,OAAO,KAAK,KAAK,KAAK,GAAG,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,MAAM,KAAK,IAAI,MAAM,KAAK,EAAE;AACzJ,YAAM,QAAQ,MAAM,MAAM,UAAU,OAAO,KAAK,IAAI,GAAG,GAAG,IAAI,MAAM,QAAQ,OAAO,MAAM,GAAG,CAAC,EAAE,eAAe,IAAI,IAAI,CAAC;AACvH,YAAM,MAAM,OAAO,KAAK,IAAI,KAAK,MAAM,GAAG,GAAG,CAAC;AAC9C,YAAM,KAAK,OAAO,KAAK,MAAM,OAAO,KAAK,KAAK,MAAM,GAAG,CAAC,KAAK,KAAK,MAAM,KAAK,KAAK,MAAM,KAAK,IAAI,MAAM,KAAK,KAAK,IAAI;AACrH,UAAI,KAAK,SAAS;AAChB,iBAAS,SAAS,GAAG,SAAS,GAAG,UAAU;AACzC,kBAAQ,OAAO,OAAO,IAAI,MAAM,YAAY,KAAK,KAAK,MAAM,KAAK,IAAI,MAAM,KAAK,KAAK,IAAI,GAAG,MAAM,QAAQ,KAAK,KAAK,KAAK,KAAK,OAAO,GAAG,CAAC,KAAK,KAAK,OAAO,SAAS,KAAK,KAAK,MAAM,GAAG,QAAQ,OAAO,SAAS,OAAO,CAAC;AAAA,QACxN;AAAA,MACF;AACA,UAAI,KAAK,KAAM,SAAQ,OAAO,OAAO,IAAI,MAAM,aAAa,KAAK,IAAI,MAAM,KAAK,IAAI,MAAM,CAAC,GAAG,MAAM,SAAS,QAAQ,GAAG,GAAG,GAAG,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG,GAAG,CAAC;AAC9J,UAAI,MAAM,KAAK,EAAE,UAAU,OAAO,KAAK,CAAC;AAAA,IAC1C;AAAA,EACF;AAEA,MAAM,YAAY,CAAC,OAAc,KAAgB,MAAgB,UAAwH;AACvL,UAAM,QAAQ,eAAe,OAAO,GAAG;AACvC,UAAM,EAAE,WAAW,MAAM,IAAI,IAAI;AACjC,eAAW,QAAQ,CAAC,IAAI,CAAC,GAAG;AAC1B,YAAM,MAAM,MAAM,MAAM,WAAW,OAAO,KAAK,IAAI,MAAM,CAAC,KAAK,IAAI,MAAM,KAAK,IAAI,MAAM,IAAI,MAAM,QAAQ,OAAO,MAAM,OAAO,CAAC,EAAE,eAAe,IAAI,IAAI,CAAC;AACzJ,YAAM,MAAM,KAAK,KAAK,IAAI,MAAM,MAAM,GAAG,GAAG,CAAC;AAC7C,YAAM,KAAK,KAAK,KAAK,IAAI,MAAM,KAAK,IAAI,MAAM,KAAK,OAAO,KAAK,aAAa,OAAO,GAAG;AACtF,YAAM,QAAQ,MAAM,MAAM,KAAK,GAAG,CAAC,KAAK,OAAO,GAAG,IAAI,MAAM,QAAQ,GAAG,OAAO,CAAC,EAAE,eAAe,IAAI,IAAI,CAAC;AACzG,YAAM,MAAM,OAAO,KAAK,IAAI,MAAM,KAAK,GAAG,GAAG,CAAC;AAC9C,YAAM,KAAK,OAAO,KAAK,IAAI,OAAO,KAAK,IAAI,MAAM,KAAK,MAAM,GAAG;AAC/D,YAAM,OAAO,MAAM,MAAM,OAAO,GAAG,CAAC,KAAK,MAAM,CAAC;AAChD,iBAAW,SAAS,CAAC,OAAO,GAAG,IAAI,EAAG,SAAQ,OAAO,MAAM,IAAI,MAAM,YAAY,KAAK,IAAI,MAAM,KAAK,IAAI,MAAM,KAAK,IAAI,IAAI,EAAE,UAAU,GAAG,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,GAAG,CAAC,KAAK,IAAI,MAAM,GAAG,GAAG,OAAO,CAAC;AACpM,cAAQ,OAAO,MAAM,IAAI,MAAM,YAAY,KAAK,IAAI,MAAM,KAAK,IAAI,MAAM,KAAK,IAAI,IAAI,EAAE,UAAU,GAAG,GAAG,CAAC,KAAK,IAAI,IAAI,GAAG,KAAK,GAAG,CAAC,KAAK,IAAI,MAAM,CAAC;AAClJ,UAAI,KAAK,KAAK,EAAE,KAAK,MAAM,OAAO,MAAM,SAAS,OAAO,QAAQ,MAAM,QAAQ,MAAM,KAAK,CAAC;AAAA,IAC5F;AAAA,EACF;AAGO,MAAM,YAAY,CAAC,OAAc,KAAgB,YAA8C;AACpG,UAAM,OAAO,IAAI,QAAQ;AACzB,UAAM,QAAQ,eAAe,OAAO,GAAG;AACvC,UAAM,SAAS,KAAK;AACpB,UAAM,OAAO,MAAM,SAAS,OAAO,IAAI;AACvC,UAAM,OAAO,MAAM,SAAS,OAAO,IAAI;AACvC,UAAM,MAAM,MAAM,SAAS,OAAO,GAAG;AACrC,UAAM,OAAO,MAAM,SAAS,OAAO,MAAM,EAAE,WAAW,IAAI,CAAC;AAC3D,UAAM,QAAQ,MAAM,SAAS,OAAO,SAAS,OAAO,IAAI;AACxD,QAAI,OAAO;AACX,UAAM,OAAO,KAAK,OAAO,KAAK,QAAQ;AACtC,QAAI,OAAO,SAAS,IAAI;AACxB,UAAM,YAAY,MAAM,MAAM,IAAI,OAAO,GAAG,OAAO,KAAK,IAAI,MAAM,CAAC;AACnE,QAAI,YAAY;AAChB,cAAU,SAAS,IAAI,KAAK,SAAS;AACrC,UAAM,KAAK,WAAW,KAAK,GAAG,MAAM,GAAG,GAAG,GAAG,KAAK,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC,CAAC;AAC5E,UAAM,KAAK,WAAW,KAAK,IAAI,KAAK,OAAO,GAAG,CAAC,KAAK,IAAI,MAAM,KAAK,IAAI,MAAM,KAAK,EAAE,CAAC,IAAI,KAAK,KAAK,EAAE,CAAC,IAAI,KAAK,KAAK,EAAE,CAAC,IAAI,IAAI;AAC/H,UAAM,OAAO,MAAM,MAAM,WAAW,GAAG,KAAK,IAAI,MAAM,CAAC,KAAK,IAAI,KAAK,EAAE,CAAC,IAAI,MAAM,IAAI,MAAM,QAAQ,GAAG,GAAG,KAAK,EAAE,eAAe,IAAI,IAAI,CAAC;AACzI,SAAK,SAAS,IAAI,KAAK,SAAS;AAChC,QAAI,WAAW;AACf,YAAQ,OAAO,MAAM,IAAI,MAAM,YAAY,KAAK,OAAO,KAAK,IAAI,MAAM,KAAK,KAAK,EAAE,UAAU,GAAG,GAAG,CAAC,KAAK,QAAQ,CAAC,GAAG,KAAK,QAAQ,MAAM,SAAS,KAAK,KAAK,IAAI,IAAI;AAClK,eAAW,OAAO,KAAK,MAAM,EAAE,WAAW,MAAM,KAAK,CAAC;AACtD,UAAM,OAAO,MAAM,MAAM,WAAW,GAAG,KAAK,IAAI,MAAM,KAAK,IAAI,KAAK,EAAE,CAAC,IAAI,MAAM,IAAI,MAAM,QAAQ,GAAG,MAAM,IAAI,EAAE,eAAe,IAAI,IAAI,CAAC;AAC1I,SAAK,SAAS,IAAI,KAAK;AACvB,QAAI,OAAO;AACX,QAAI,YAAY,KAAK;AACrB,UAAM,MAAM,MAAM,KAAK,KAAK,KAAK,KAAK,QAAQ,MAAM,SAAS,KAAK,KAAK,IAAI,MAAM,GAAG,GAAG,CAAC;AACxF,YAAQ,OAAO,MAAM,IAAI,MAAM,iBAAiB,KAAK,KAAK,MAAM,KAAK,IAAI,KAAK,IAAI,EAAE,EAAE,UAAU,GAAG,KAAK,KAAK,GAAG,CAAC,GAAG,KAAK,QAAQ,MAAM,SAAS,KAAK,KAAK,IAAI,IAAI;AAClK,UAAM,OAAO,MAAM,MAAM,MAAM,GAAG,KAAK,IAAI,GAAG,IAAI,MAAM,QAAQ,GAAG,MAAM,IAAI,EAAE,eAAe,IAAI,IAAI,CAAC;AACvG,SAAK,SAAS,IAAI,CAAC,KAAK,SAAS,KAAK,SAAS;AAC/C,QAAI,OAAO;AACX,QAAI,YAAY,KAAK,SAAS;AAC9B,UAAM,eAAe,KAAK,QAAQ,MAAM,SAAS,KAAK,KAAK,IAAI;AAC/D,UAAM,KAAK,MAAM,KAAK,IAAI,cAAc,GAAG,GAAG,GAAG,KAAK,KAAK,KAAK,GAAG,CAAC,IAAI,GAAG,KAAK,KAAK,KAAK,GAAG,CAAC,IAAI,GAAG,KAAK,KAAK,KAAK,GAAG,CAAC,IAAI,GAAG;AAC/H,YAAQ,OAAO,MAAM,IAAI,MAAM,aAAa,KAAK,IAAI,KAAK,IAAI,CAAC,EAAE,QAAQ,KAAK,KAAK,CAAC,EAAE,UAAU,GAAG,GAAG,KAAK,KAAK,CAAC,GAAG,MAAM,GAAG,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,MAAM,KAAK,QAAQ,GAAG,GAAG,CAAC;AACjL,UAAM,MAAM,MAAM,MAAM,MAAM,GAAG,CAAC,KAAK,KAAK,MAAM,KAAK,KAAK,GAAG;AAC/D,QAAI,MAAM;AACV,YAAQ,OAAO,KAAK,IAAI,MAAM,aAAa,KAAK,KAAK,KAAK,KAAK,KAAK,MAAM,CAAC,EAAE,QAAQ,KAAK,KAAK,CAAC,EAAE,UAAU,GAAG,GAAG,KAAK,KAAK,IAAI,GAAG,IAAI,EAAE,MAAM,IAAI,GAAG,KAAK,CAAC;AAC5J,UAAM,KAAK,MAAM,KAAK,KAAK,MAAM,KAAK,KAAK,MAAM,KAAK,KAAK,KAAK,KAAK,KAAK,IAAI;AAC9E,QAAI,KAAK,QAAQ,IAAI,aAAa;AAChC,UAAI,YAAY,MAAM,IAAI,KAAK,IAAI;AACnC,UAAI,YAAY,SAAS,OAAO,IAAI,YAAY,MAAM,MAAM;AAAA,IAC9D;AACA,YAAQ,QAAQ,EAAE,OAAO,KAAK,MAAM,GAAG,EAAE,WAAW,MAAM,MAAM,MAAM,KAAK,CAAC;AAC5E,cAAU,OAAO,KAAK,MAAM,EAAE,WAAW,MAAM,IAAI,CAAC;AACpD,WAAO;AAAA,EACT;;;AC1DA,MAAM,MAAsB,CAAC,EAAE,OAAO,MAAM,GAAG,MAAM;AACnD,UAAM,KAAK,EAAE,WAAW,KAAK,EAAE,MAAM,GAAG,EAAE,KAAK,KAAK,EAAE,IAAI,MAAM,MAAM,KAAK,GAAG;AAC9E,UAAM,KAAK,EAAE,MAAM,MAAM,EAAE,MAAM,GAAG,MAAM,MAAM,MAAM,KAAK,IAAI;AAC/D,eAAW,QAAQ,CAAC,IAAI,CAAC,EAAG,SAAQ,OAAO,EAAE,MAAM,IAAI,MAAM,aAAa,OAAO,MAAM,CAAC,GAAG,MAAM,SAAS,QAAQ,GAAG,OAAO,MAAM,EAAE,GAAG,IAAI,MAAM,EAAE,GAAG,IAAI,MAAM,GAAG,GAAG,OAAO,IAAI;AAAA,EACnL;AAEA,MAAM,QAAwB,CAAC,EAAE,OAAO,KAAK,MAAM,GAAG,MAAM;AAC1D,aAAS,QAAQ,GAAG,QAAQ,GAAG,QAAS,SAAQ,OAAO,EAAE,MAAM,IAAI,MAAM,YAAY,OAAO,MAAM,IAAI,GAAG,EAAE,MAAM,GAAG,OAAO,QAAQ,OAAO,QAAQ,QAAQ,MAAO,OAAO,GAAG,CAAC;AAC5K,YAAQ,OAAO,EAAE,MAAM,IAAI,MAAM,YAAY,MAAM,MAAM,IAAI,GAAG,EAAE,MAAM,GAAG,EAAE,GAAG,IAAI,MAAM,EAAE,GAAG,IAAI,KAAK,KAAK,GAAG,CAAC;AACjH,UAAM,OAAO,IAAI,KAAK,IAAI,KAAK,SAAS,CAAC;AACzC,UAAM,KAAK,MAAM,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,KAAK,KAAK,GAAG;AAAA,EAC3D;AAEA,MAAM,WAA2B,CAAC,EAAE,OAAO,MAAM,GAAG,MAAM;AACxD,aAAS,QAAQ,GAAG,QAAQ,IAAI,QAAS,SAAQ,OAAO,EAAE,WAAW,IAAI,MAAM,YAAY,MAAM,KAAK,IAAI,GAAG,EAAE,MAAM,GAAG,EAAE,KAAK,OAAO,QAAQ,MAAO,EAAE,IAAI,OAAO,QAAQ,MAAM,MAAM,GAAG,CAAC;AAC1L,eAAW,QAAQ,CAAC,IAAI,CAAC;AACvB,cAAQ,OAAO,EAAE,MAAM,IAAI,MAAM,aAAa,OAAO,MAAM,CAAC,GAAG,MAAM,SAAS,QAAQ,GAAG,OAAO,MAAM,EAAE,GAAG,IAAI,QAAQ,MAAM,EAAE,GAAG,IAAI,OAAO,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,CAAC,IAAI,OAAO,MAAM,MAAM,GAAG,OAAO,GAAG;AAAA,EACtM;AAEA,MAAM,UAAqB,CAAC,EAAE,MAAM,GAAG,MAAM;AAC3C,UAAM,MAAM,MAAM,SAAS,QAAQ;AACnC,aAAS,QAAQ,GAAG,QAAQ,GAAG,QAAS,OAAM,KAAK,EAAE,MAAM,OAAO,KAAK,GAAG,OAAO,SAAS,QAAQ,OAAO,KAAK,GAAG,GAAG;AACpH,UAAM,KAAK,EAAE,MAAM,OAAO,KAAK,GAAG,OAAO,MAAM,KAAK,KAAK,GAAG;AAAA,EAC9D;AAEA,MAAM,kBAA6B,CAAC,EAAE,OAAO,MAAM,GAAG,MAAM;AAC1D,UAAM,QAAQ,MAAM,SAAS,OAAQ;AACrC,UAAM,KAAK,EAAE,WAAW,MAAM,OAAO,GAAG,MAAM,MAAM,KAAK,KAAK,GAAG;AACjE,YAAQ,OAAO,EAAE,MAAM,IAAI,MAAM,YAAY,MAAO,MAAO,IAAI,EAAE,UAAU,GAAG,GAAG,KAAK,GAAG,OAAO,GAAG,OAAO,OAAO,KAAK,GAAG,CAAC;AAC1H,UAAM,KAAK,EAAE,MAAM,OAAO,OAAO,GAAG,OAAQ,MAAM,MAAM,KAAK,GAAG;AAAA,EAClE;AAGO,MAAM,mBAA6D,EAAE,KAAK,OAAO,WAAW,SAAS;AACrG,MAAM,cAAmD,EAAE,SAAS,kBAAkB,gBAAgB;;;ACnD7G,MAAM,gBAAgB,CAAC,OAAcC,WAAwB,OAAe,QAAgB,SAA4F;AACtL,UAAM,SAAqBA,UAAS,cAAc,QAAQ;AAC1D,WAAO,QAAQ;AACf,WAAO,SAAS;AAChB,SAAK,OAAO,WAAW,IAAI,GAAc,OAAO,MAAM;AACtD,UAAM,UAAU,IAAI,MAAM,cAAc,MAAsC;AAC9E,YAAQ,aAAa,MAAM;AAC3B,WAAO;AAAA,EACT;AAEA,MAAM,eAAe,CAAC,OAAcA,WAAwB,MAAc,SACxE,cAAc,OAAOA,WAAU,KAAK,KAAK,CAAC,SAAS,OAAO,WAAW;AACnE,YAAQ,YAAY;AACpB,YAAQ,SAAS,GAAG,GAAG,OAAO,MAAM;AACpC,UAAM,OAAO,aAAa,EAAE;AAC5B,aAAS,QAAQ,GAAG,QAAQ,IAAI,SAAS;AACvC,YAAM,IAAI,KAAK,IAAI;AACnB,YAAM,IAAI,KAAK,IAAI;AACnB,YAAM,SAAS,IAAI,KAAK,IAAI;AAC5B,cAAQ,cAAc;AACtB,cAAQ,YAAY;AACpB,cAAQ,UAAU;AAClB,cAAQ,IAAI,GAAG,GAAG,QAAQ,GAAG,CAAC;AAC9B,cAAQ,OAAO;AACf,cAAQ,YAAY;AACpB,cAAQ,UAAU;AAClB,cAAQ,IAAI,GAAG,GAAG,SAAS,KAAK,GAAG,CAAC;AACpC,cAAQ,KAAK;AAAA,IACf;AACA,aAAS,QAAQ,GAAG,QAAQ,IAAI,SAAS;AACvC,cAAQ,YAAY;AACpB,cAAQ,UAAU;AAClB,cAAQ,IAAI,KAAK,IAAI,OAAO,KAAK,IAAI,QAAQ,MAAM,KAAK,IAAI,KAAK,GAAG,CAAC;AACrE,cAAQ,KAAK;AAAA,IACf;AAAA,EACF,CAAC;AAEH,MAAM,iBAAiB,CAAC,OAAcA,WAAwB,MAAc,WAC1E,cAAc,OAAOA,WAAU,KAAK,KAAK,CAAC,SAAS,UAAU;AAC3D,YAAQ,YAAY;AACpB,YAAQ,SAAS,GAAG,GAAG,OAAO,GAAG;AACjC,YAAQ,YAAY;AACpB,aAAS,QAAQ,GAAG,QAAQ,GAAG,QAAS,SAAQ,SAAS,GAAG,QAAQ,KAAM,QAAQ,IAAK,GAAG,OAAO,CAAC;AAAA,EACpG,CAAC;AAGI,MAAM,oBAAoB,CAAC,OAAcA,WAAwB,WACtE,OAAO,SAAS,YAAY,eAAe,OAAOA,WAAU,OAAO,MAAM,OAAO,MAAM,IAAI,aAAa,OAAOA,WAAU,OAAO,MAAM,OAAO,IAAI;;;AC3D3I,MAAM,kBAAkB,CAAC,OAAc,QAAgB,SAA4B,aAA8C;AACtI,UAAM,SAAS,QAAQ,IAAI,CAAC,QAAQ,UAAU,IAAI,MAAM,QAAQ,SAAS,QAAQ,SAAS,IAAI,MAAM,CAAC;AACrG,UAAM,QAAQ,IAAI,MAAM,YAAY,MAAM;AAC1C,UAAM,UAA8B,CAAC;AACrC,YAAQ,KAAK,IAAI,MAAM,QAAQ,MAAO,CAAC,SAAS,IAAI,CAAG,CAAC;AACxD,aAAS,OAAO,GAAG,QAAQ,IAAI,QAAQ;AACrC,YAAM,QAAQ,MAAM,SAAS,OAAO,EAAE;AACtC,cAAQ,KAAK,IAAI,MAAM,QAAQ,KAAK,IAAI,MAAO,MAAM,CAAC,GAAG,CAAC,SAAS,IAAI,MAAM,IAAI,MAAM,CAAC;AAAA,IAC1F;AACA,YAAQ,KAAK,IAAI,MAAM,QAAQ,MAAO,SAAS,CAAC,CAAC;AACjD,WAAO,IAAI,MAAM,cAAc,SAAS,YAAY,EAAE,EAAE,QAAQ,KAAK,KAAK,CAAC;AAAA,EAC7E;;;ACNA,MAAM,YAAY;AAIlB,MAAM,kBAA4C;AAAA,IAChD,CAAC,GAAG,GAAG,GAAG;AAAA,IACV,CAAC,IAAI,GAAG,GAAG;AAAA,IACX,CAAC,GAAG,IAAI,GAAG;AAAA,IACX,CAAC,IAAI,IAAI,GAAG;AAAA,EACd;AAGA,MAAM,mBAAmB,CAAC,OAAc,KAAgB,MAAqB,MAAqC,OAAsC,SAAwC;AAC9L,UAAM,QAAQ,eAAe,OAAO,GAAG;AACvC,UAAM,EAAE,GAAG,GAAG,OAAO,IAAI;AACzB,UAAM,MAAM,KAAK;AACjB,UAAM,SAAS,gBAAgB,IAAI;AACnC,UAAM,EAAE,gBAAgB,eAAe,YAAY,YAAY,KAAK,IAAI;AACxE,UAAM,cAAc,IAAI,OAAO;AAC/B,UAAM,cAAc,IAAI,OAAO;AAC/B,UAAM,aAAa,IAAI,OAAO;AAC9B,UAAM,aAAa,IAAI,OAAO;AAC9B,QAAI,OAAO,OAAO;AAClB,UAAM,YAAY,MAAM,MAAM,IAAI,OAAO,GAAG,YAAY,CAAC;AACzD,QAAI,YAAY;AAChB,UAAM,OAAO,QAAQ,OAAO,WAAW,gBAAgB,OAAO,GAAG,KAAK,KAAK,IAAI,CAAC,UAAU,QAAQ,MAAM,CAAC,GAAG,IAAI;AAChH,SAAK,MAAM,IAAI,KAAK,MAAM,GAAG,KAAK,MAAM,GAAG,CAAC;AAC5C,SAAK,SAAS,IAAI,KAAK,MAAM,gBAAgB,gBAAgB,IAAI,IAAI;AACrE,QAAI,OAAO;AACX,UAAM,OAAO,QAAQ,OAAO,WAAW,gBAAgB,OAAO,IAAI,KAAK,KAAK,KAAK,IAAI,CAAC,UAAU,QAAQ,MAAM,MAAM,CAAC,GAAG,KAAK;AAC7H,SAAK,MAAM,KAAK,KAAK,MAAM,KAAK,OAAO,KAAK,MAAM,KAAK,MAAM,CAAC;AAC9D,SAAK,SAAS,IAAI,CAAC,SAAS;AAC5B,oBAAgB,QAAQ,CAAC,CAAC,IAAI,IAAI,IAAI,MAAM;AAC1C,YAAM,QAAQ,SAAS;AACvB,YAAM,QAAQ,QAAQ,IAAI,KAAK,IAAI;AACnC,YAAM,QAAQ,QAAQ,IAAI,KAAK,IAAI;AACnC,YAAM,SAAS,QAAQ,iBAAiB;AACxC,YAAM,MAAM,MAAM,MAAM,IAAI,OAAO,KAAK,UAAU,KAAK,MAAM,KAAK,MAAM,QAAS,KAAK,OAAQ,GAAG,IAAI,MAAM,QAAQ,KAAK,MAAM,OAAO,KAAK,IAAI,EAAE,eAAe,IAAI,IAAI,CAAC;AACxK,YAAM,KAAK,KAAK,IAAI,KAAK,QAAQ,MAAM,MAAM,MAAM,GAAG,CAAC,QAAQ,MAAM,QAAQ,IAAI,CAAC,IAAI,IAAI,KAAK,MAAM,MAAM,CAAC;AAC5G,YAAM,MAAM,KAAK,IAAI,IAAI,MAAM,MAAM,GAAG,GAAG,CAAC;AAC5C,YAAM,KAAK,KAAK,IAAI,IAAI,MAAM,IAAI,IAAI,MAAM,OAAO,IAAI;AACvD,YAAM,OAAO,MAAM,MAAM,KAAK,GAAG,CAAC,OAAO,GAAG,IAAI,MAAM,QAAQ,GAAG,QAAQ,IAAI,MAAM,CAAC,CAAC;AACrF,YAAM,MAAM,MAAM,IAAI,IAAI,MAAM,MAAM,GAAG,GAAG,CAAC;AAC7C,YAAM,KAAK,MAAM,IAAI,IAAI,MAAM,IAAI,IAAI,MAAM,OAAO,IAAI;AACxD,YAAM,OAAO,MAAM,MAAM,MAAM,GAAG,CAAC,OAAO,GAAG,IAAI,MAAM,QAAQ,GAAG,QAAQ,IAAI,MAAM,CAAC,CAAC;AACtF,UAAI,IAAI,MAAM;AACZ,cAAM,MAAM,MAAM,IAAI,IAAI,MAAM,MAAM,GAAG,GAAG,CAAC;AAC7C,gBAAQ,OAAO,MAAM,IAAI,MAAM,iBAAiB,IAAI,IAAI,KAAK,IAAI,IAAI,MAAM,YAAY,EAAE,EAAE,UAAU,GAAG,CAAC,aAAa,GAAG,CAAC,GAAG,IAAI;AAAA,MACnI,OAAO;AACL,cAAM,MAAM,MAAM,IAAI,IAAI,MAAM,MAAM,GAAG,GAAG,CAAC;AAC7C,cAAM,KAAK,MAAM,IAAI,IAAI,MAAM,IAAI,OAAO,OAAO,MAAM,GAAG,CAAC,aAAa,MAAM,IAAI,IAAI,KAAK,GAAG,MAAM,IAAI;AAAA,MAC1G;AACA,UAAI,KAAK,KAAK,EAAE,KAAK,MAAM,MAAM,SAAS,OAAO,QAAQ,QAAQ,cAAc,YAAY,QAAQ,QAAQ,cAAc,YAAY,MAAM,GAAG,CAAC;AAAA,IACjJ,CAAC;AACD,WAAO,EAAE,WAAW,WAAW;AAAA,EACjC;AAEA,MAAM,YAAY,CAAC,OAAc,KAAgB,MAAqB,UAAyJ;AAC7N,UAAM,QAAQ,eAAe,OAAO,GAAG;AACvC,UAAM,EAAE,WAAW,MAAM,OAAO,KAAK,IAAI;AACzC,UAAM,EAAE,GAAG,GAAG,OAAO,IAAI;AACzB,UAAM,WAAW,KAAK;AACtB,UAAM,WAAW,KAAK;AACtB,UAAM,OAAO,MAAM,MAAM,WAAW,GAAG,UAAU,KAAK,MAAM,MAAM,SAAS,KAAK,OAAQ,IAAI,KAAM,SAAS,KAAK,OAAO,IAAI,MAAM,QAAQ,GAAG,MAAM,IAAI,EAAE,eAAe,IAAI,IAAI,CAAC;AAChL,SAAK,SAAS,IAAI,SAAS;AAC3B,QAAI,OAAO;AACX,QAAI,YAAY,SAAS;AACzB,UAAM,MAAM,MAAM,SAAS,IAAI,MAAM,MAAM,GAAG,GAAG,CAAC;AAClD,YAAQ,OAAO,MAAM,IAAI,MAAM,iBAAiB,SAAS,MAAM,SAAS,IAAI,MAAM,SAAS,GAAG,SAAS,KAAK,EAAE,EAAE,UAAU,GAAG,SAAS,MAAM,GAAG,CAAC,GAAG,IAAI;AACvJ,UAAM,OAAO,MAAM,MAAM,MAAM,GAAG,SAAS,KAAK,GAAG,IAAI,MAAM,QAAQ,GAAG,MAAM,IAAI,EAAE,eAAe,IAAI,IAAI,CAAC;AAC5G,SAAK,SAAS,IAAI,CAAC,SAAS,OAAO,SAAS,QAAQ;AACpD,QAAI,OAAO;AACX,QAAI,YAAY,KAAK,SAAS;AAC9B,UAAM,MAAM,MAAM,SAAS,MAAM,SAAS,IAAI,MAAM,MAAM,GAAG,GAAG,CAAC;AACjE,UAAM,KAAK,MAAM,SAAS,GAAG,MAAM,GAAG,SAAS,IAAI,MAAM,SAAS,IAAI,MAAM,SAAS,EAAE,CAAC,GAAG,SAAS,EAAE,CAAC,GAAG,SAAS,EAAE,CAAC,CAAC;AACvH,UAAM,SAAS,SAAS,IAAI,OAAO,SAAS,IAAI,SAAS,EAAE,CAAC,IAAI;AAChE,UAAM,QAAQ,QAAQ,OAAO,MAAM,IAAI,MAAM,iBAAiB,SAAS,KAAK,SAAS,KAAK,SAAS,IAAI,EAAE,EAAE,QAAQ,KAAK,KAAK,CAAC,GAAG,SAAS,SAAS,MAAM,SAAS,SAAS,MAAM,IAAI,MAAM,GAAG,SAAS,KAAK,SAAS,MAAM,IAAI,SAAS,SAAS,KAAK,CAAC;AACvP,UAAM,MAAM,IAAI,GAAG,SAAS,MAAM,GAAG,CAAC;AACtC,UAAM,KAAK,MAAM,SAAS,MAAM,MAAM,MAAM,GAAG,SAAS,KAAK,SAAS,MAAM,KAAK,SAAS,MAAM,KAAK,SAAS,SAAS,IAAI,GAAG,QAAQ,SAAS,MAAM,IAAI,IAAI;AAC7J,UAAM,MAAM,MAAM,MAAM,MAAM,GAAG,SAAS,KAAK,SAAS,MAAM,KAAK,SAAS,MAAM,QAAQ,SAAS,MAAM,IAAI,SAAS,SAAS,MAAM,KAAK,IAAI,MAAM,QAAQ,GAAG,OAAO,IAAI,EAAE,eAAe,IAAI,IAAI,CAAC;AACpM,QAAI,MAAM;AACV,YAAQ,OAAO,KAAK,IAAI,MAAM,iBAAiB,SAAS,MAAM,KAAK,SAAS,MAAM,MAAM,SAAS,KAAK,MAAM,EAAE,EAAE,QAAQ,KAAK,KAAK,CAAC,EAAE,UAAU,GAAG,GAAG,SAAS,KAAK,IAAI,GAAG,KAAK,EAAE,MAAM,IAAI,GAAG,MAAM,CAAC;AACrM,UAAM,QAAQ,MAAM,KAAK,KAAK,SAAS,MAAM,KAAK,IAAI,kBAAkB,IAAI,gBAAgB,IAAI,MAAM,qBAAqB,EAAE,OAAO,SAAU,WAAW,IAAI,CAAC,IAAI,GAAG,GAAK,SAAS,KAAK,KAAK,KAAK,KAAK,GAAG;AAC1M,UAAM,KAAK,MAAM,SAAS,IAAI,MAAM,SAAS,IAAI,SAAS,EAAE,CAAC,IAAI,MAAM,SAAS,IAAI,KAAK,SAAS,IAAI,OAAO,SAAS,IAAI,SAAS,EAAE,CAAC,IAAI,IAAI;AAC9I,UAAM,OAAO,KAAK;AAClB,eAAW,QAAQ,CAAC,IAAI,CAAC,GAAG;AAC1B,YAAM,WAAW,MAAM,MAAM,MAAM,OAAO,SAAS,IAAI,SAAS,EAAE,CAAC,IAAI,MAAM,SAAS,KAAK,OAAO,SAAS,EAAE,CAAC,IAAI,OAAO,SAAS,IAAI,KAAK,IAAI,MAAM,QAAQ,OAAO,MAAM,MAAM,CAAC,EAAE,eAAe,IAAI,IAAI,CAAC;AAC3M,UAAI,KAAK,KAAK,QAAQ;AACtB,UAAI,KAAK,MAAM,OAAQ,SAAQ,OAAO,UAAU,IAAI,MAAM,aAAa,KAAK,IAAI,MAAM,KAAK,GAAG,CAAC,EAAE,UAAU,GAAG,KAAK,IAAI,GAAG,CAAC,GAAG,MAAM,GAAG,GAAG,GAAG,KAAK,OAAO,GAAG,GAAG,OAAO,EAAE,KAAK,QAAQ,IAAI;AAAA,eAChL,KAAK,MAAM,QAAS,OAAM,KAAK,UAAU,KAAK,GAAG,MAAM,GAAG,KAAK,IAAI,KAAK,GAAG,GAAG,GAAG,IAAI;AAAA,eACrF,KAAK,MAAM,QAAQ;AAC1B,cAAM,MAAM,MAAM,KAAK,UAAU,KAAK,GAAG,MAAM,OAAO,KAAK,IAAI,KAAK,CAAC,KAAK,IAAI,KAAK,GAAG,KAAK,MAAM,GAAG;AACpG,YAAI,SAAS,IAAI,OAAO;AAAA,MAC1B;AAAA,IACF;AACA,WAAO,EAAE,MAAM,MAAM,OAAO,UAAU,SAAS;AAAA,EACjD;AAEA,MAAM,YAAY,CAAC,OAAc,KAAgB,MAAqB,UAAyH;AAC7L,UAAM,QAAQ,eAAe,OAAO,GAAG;AACvC,UAAM,WAAW,KAAK;AACtB,QAAI,CAAC,YAAY,CAAC,SAAS,EAAG;AAC9B,UAAM,EAAE,WAAW,MAAM,KAAK,IAAI;AAClC,UAAM,EAAE,GAAG,GAAG,OAAO,IAAI;AACzB,QAAI,SAAS,MAAM,MAAM,WAAW,GAAG,UAAU,KAAK,MAAM,MAAM,SAAS,KAAK,MAAO,CAAC,IAAI,IAAK,MAAM,IAAI,MAAM,QAAQ,GAAG,MAAM,KAAK,EAAE,eAAe,IAAI,IAAI,CAAC;AACjK,WAAO,SAAS,IAAI,SAAS;AAC7B,QAAI,WAAW;AACf,UAAM,gBAAgB,SAAS,MAAM,SAAS;AAC9C,aAAS,QAAQ,GAAG,QAAQ,SAAS,GAAG,SAAS;AAC/C,YAAM,UAAU,SAAS,KAAK,IAAK,QAAQ,SAAS,IAAK;AACzD,YAAM,UAAU,SAAS,KAAK,KAAM,QAAQ,KAAK,SAAS,IAAK;AAC/D,YAAM,KAAK,QAAQ,SAAS,SAAS,eAAe,SAAS,QAAQ,UAAU,SAAS,IAAI,IAAI,OAAO,IAAI;AAC3G,YAAM,MAAM,QAAQ,SAAS,MAAM,GAAG,GAAG,CAAC;AAC1C,UAAI,KAAK,KAAK,MAAM;AACpB,UAAI,QAAQ,SAAS,IAAI,GAAG;AAC1B,cAAM,OAAO,MAAM,MAAM,QAAQ,GAAG,CAAC,eAAe,CAAC;AACrD,aAAK,SAAS,IAAI,SAAS,QAAQ;AACnC,iBAAS;AAAA,MACX,WAAW,SAAS,KAAM,OAAM,KAAK,QAAQ,SAAS,IAAI,KAAK,MAAM,GAAG,CAAC,gBAAgB,SAAS,GAAG,GAAG,GAAG,KAAK,CAAC;AAAA,IACnH;AACA,QAAI,SAAS,MAAO,OAAM,KAAK,IAAI,KAAK,CAAC,GAAqB,SAAS,IAAI,KAAK,MAAM,GAAG,CAAC,SAAS,MAAM,MAAM,GAAG,GAAI,SAAS,MAAM,MAAO,SAAS,IAAI,GAAG,CAAC;AAAA,EAC/J;AAGA,MAAM,sBAAsB,CAAC,OAAc,KAAgB,MAAqB,UAA4H;AAC1M,UAAM,QAAQ,eAAe,OAAO,GAAG;AACvC,UAAM,EAAE,WAAW,MAAM,OAAO,SAAS,IAAI;AAC7C,UAAM,EAAE,GAAG,GAAG,OAAO,IAAI;AACzB,UAAM,QAAQ,UAAU,KAAK,MAAM;AACnC,UAAM,QAAQ,UAAU,KAAK,MAAM;AACnC,UAAM,OAA4B,CAAC,KAAK;AACxC,UAAM,QAAQ,CAAC,QAA2B,MAAc,GAAW,GAAW,GAAW,IAAY,IAAY,OAC/G,MAAM,KAAK,QAAQ,MAAM,IAAI,kBAAkB,IAAI,gBAAgB,IAAI,MAAM,qBAAqB,EAAE,OAAO,SAAU,WAAW,KAAK,CAAC,IAAI,GAAG,GAAG,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AAC9K,SAAK,KAAK,MAAM,WAAW,SAAS,MAAM,QAAQ,MAAM,QAAQ,MAAM,IAAI,MAAM,MAAM,GAAG,GAAG,CAAC;AAC7F,SAAK,KAAK,MAAM,WAAW,SAAS,KAAK,CAAC,QAAQ,KAAK,QAAQ,KAAK,CAAC,IAAI,KAAK,MAAM,GAAG,GAAG,CAAC;AAC3F,SAAK,KAAK,MAAM,MAAM,SAAS,IAAI,KAAK,SAAS,IAAI,MAAM,SAAS,MAAM,KAAK,GAAG,KAAK,GAAG,CAAC,CAAC;AAC5F,UAAM,OAAO,MAAM,SAAS,UAAU,EAAE,WAAW,IAAI,CAAC;AACxD,aAAS,QAAQ,GAAG,QAAQ,WAAW,SAAS;AAC9C,YAAM,MAAM,QAAQ,OAAO,WAAW,IAAI,MAAM,cAAc,SAAS,MAAM,SAAS,OAAO,GAAG,IAAI,KAAK,KAAK,IAAI,GAAG,MAAM,QAAQ,MAAM,QAAQ,MAAM,IAAI,OAAO,QAAQ,IAAI,MAAM,GAAG,KAAK,KAAK,GAAG,IAAI;AACxM,UAAI,MAAM,IAAI,GAAG,QAAQ,KAAK,MAAM,IAAI,KAAK,MAAM,CAAC;AACpD,WAAK,KAAK,GAAG;AAAA,IACf;AACA,QAAI,eAAe,KAAK,GAAG,IAAI;AAC/B,SAAK,QAAQ,CAAC,SAAU,KAAK,UAAU,KAAM;AAAA,EAC/C;AAGO,MAAM,iBAAiB,CAAC,OAAc,KAAgB,YAA2E;AACtI,UAAM,OAAO,IAAI,QAAQ;AACzB,UAAM,QAAQ,eAAe,OAAO,GAAG;AACvC,UAAM,SAAS,KAAK;AACpB,UAAM,UAAU,KAAK,UAAU,kBAAkB,OAAO,QAAQ,UAAU,KAAK,OAAO,IAAI;AAC1F,UAAM,OAAO,MAAM,SAAS,UAAU,WAAW,OAAO,MAAM,UAAU,EAAE,KAAK,QAAQ,IAAI,MAAS;AACpG,UAAM,QAAQ,MAAM,SAAS,OAAO,KAAK;AACzC,UAAM,OAAO,MAAM,SAAS,OAAO,IAAI;AACvC,UAAM,OAAO,MAAM,SAAS,OAAO,QAAQ,OAAO,MAAM,EAAE,WAAW,IAAI,CAAC;AAC1E,QAAI,OAAO;AACX,QAAI,OAAO;AACX,UAAM,EAAE,WAAW,WAAW,IAAI,iBAAiB,OAAO,KAAK,MAAM,MAAM,OAAO,IAAI;AACtF,UAAM,EAAE,MAAM,MAAM,OAAO,UAAU,SAAS,IAAI,UAAU,OAAO,KAAK,MAAM,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAC9G,cAAU,OAAO,KAAK,MAAM,EAAE,WAAW,MAAM,KAAK,CAAC;AACrD,YAAQ,QAAQ,EAAE,OAAO,KAAK,MAAM,GAAG,EAAE,WAAW,MAAM,MAAM,MAAM,OAAO,MAAM,GAAG,KAAK,GAAG,IAAI,KAAK,GAAG,OAAO,YAAY,IAAI,UAAU,IAAI,SAAS,CAAC;AACzJ,QAAI,IAAI,QAAQ,YAAY,QAAS,qBAAoB,OAAO,KAAK,MAAM,EAAE,WAAW,MAAM,OAAO,SAAS,CAAC;AAAA,QAC1G,OAAM,UAAU;AACrB,WAAO;AAAA,EACT;;;AC5KA,MAAM,gBAAgB;AACtB,MAAM,iBAAiB;AAGhB,MAAM,aAAa,CAAC,OAAc,QAA8B;AACrE,UAAM,QAAQ,eAAe,OAAO,GAAG;AACvC,QAAI,OAAO;AACX,QAAI,gBAAgB;AACpB,QAAI,eAAe;AACnB,UAAM,UAAU,MAAM,SAAS,OAAQ;AACvC,UAAM,YAAY,MAAM,SAAS,OAAQ;AACzC,UAAM,QAAQ,MAAM,SAAS,QAAQ;AACrC,QAAI,OAAO;AACX,aAAS,QAAQ,GAAG,QAAQ,eAAe,SAAS;AAClD,YAAM,IAAI,SAAS,gBAAgB;AACnC,YAAM,SAAS,QAAQ,OAAO,KAAK,IAAI,KAAK,IAAI,GAAG,IAAI,IAAI,IAAI,KAAK,EAAE,KAAK,IAAI,MAAM,OAAO,IAAI,MAAM,MAAM,IAAI,OAAO,QAAQ;AAC/H,YAAM,WAAW,QAAQ,MAAM,KAAK,QAAQ,MAAM,IAAI,YAAY;AAClE,YAAM,QAAQ,IAAI,MAAM,MAAM;AAC9B,UAAI,MAAM,IAAI,KAAK;AACnB,YAAM,SAAS,MAAM,KAAK,OAAO,QAAQ,UAAU,GAAG,QAAQ,GAAG,GAAG,MAAM,GAAG;AAC7E,aAAO,SAAS,QAAQ,QAAQ,MAAM,IAAI,IAAI;AAC9C,UAAI,QAAQ,MAAM,EAAG,KAAI,OAAO,KAAK,MAAM;AAC3C,UAAI,SAAS,KAAK,EAAE,OAAO,OAAO,CAAC;AAAA,IACrC;AACA,UAAM,OAAO,IAAI,MAAM,MAAM;AAC7B,QAAI,MAAM,IAAI,IAAI;AAClB,QAAI,OAAO;AACX,UAAM,KAAK,MAAM,OAAO,SAAS,GAAG,MAAM,OAAO,MAAM,KAAK,IAAI;AAChE,UAAM,KAAK,MAAM,OAAO,WAAW,GAAG,MAAM,GAAK,KAAK,KAAK,GAAG;AAC9D,UAAM,KAAK,MAAM,MAAM,SAAS,GAAG,OAAO,MAAM,MAAM,MAAM,GAAG;AAC/D,UAAM,MAAM,IAAI,MAAM,MAAM;AAC5B,QAAI,SAAS,IAAI,GAAG,OAAO,CAAG;AAC9B,SAAK,IAAI,GAAG;AACZ,QAAI,MAAM;AACV,UAAM,KAAK,KAAK,OAAO,OAAO,GAAG,GAAG,OAAO,KAAK,MAAM,GAAG;AACzD,UAAM,KAAK,MAAM,MAAO,MAAM,MAAM,KAAK;AACzC,QAAI,SAAS,QAAQ,OAAO,MAAM,IAAI,MAAM,YAAY,MAAO,MAAO,IAAI,GAAG,MAAM,SAAS,QAAQ,GAAG,GAAG,OAAO,GAAG;AACpH,WAAO;AAAA,EACT;;;ACpCA,MAAM,gBAAgB;AACtB,MAAM,aAAa;AAOZ,MAAM,mBAAmB,CAAC,OAAc,KAAgB,UAAmC;AAChG,QAAI,WAAW,UAAU;AACzB,QAAI,OAAO,UAAU;AACrB,UAAM,OAAO,IAAI,MAAM,MAAM,aAAa;AAC1C,QAAI,UAAU,QAAQ,CAAC,aAAa;AAClC,UAAI,aAAa,IAAI,eAAe,aAAa,IAAI,cAAe;AACpE,eAAS,MAAM,KAAK,SAAS,SAAS,IAAsB;AAC5D,UAAI,IAAI,SAAU,UAAS,MAAM,KAAK,MAAM,IAAI,EAAE,eAAe,IAAI;AAAA,IACvE,CAAC;AACD,UAAM,MAAM,IAAI;AAChB,QAAI,KAAK;AACP,UAAI,IAAI,UAAU;AAChB,YAAI,MAAM,IAAI,QAAQ;AACtB,YAAI,WAAW,IAAI,MAAM,MAAM,OAAQ;AACvC,YAAI,oBAAoB;AAAA,MAC1B,OAAO;AACL,YAAI,MAAM,KAAK,IAAI,SAAS,IAAsB;AAClD,YAAI,WAAW,IAAI,MAAM,MAAM,CAAC;AAAA,MAClC;AAAA,IACF;AACA,QAAI,eAAe,QAAQ,CAAC,SAAU,KAAK,UAAU,IAAI,QAAS;AAClE,UAAM,WAAW,IAAI,KAAK,CAAC;AAC3B,QAAI,SAAU,UAAS,UAAU,CAAC,IAAI;AACtC,QAAI,CAAC,IAAI,MAAM;AACb,UAAI,KAAK,SAAS,IAAI;AACtB,UAAI,KAAK,SAAS,IAAI;AAAA,IACxB;AACA,QAAI,IAAI,MAAM;AACZ,UAAI,KAAK,UAAU,IAAI;AACvB;AAAA,IACF;AACA,QAAI,CAAC,IAAI,KAAM;AACf,UAAM,OAAO,IAAI,MAAM,KAAK,IAAI,MAAM,eAAe,GAAG,EAAE,EAAE,QAAQ,CAAC,KAAK,KAAK,CAAC,GAAG,IAAI,MAAM,qBAAqB,EAAE,OAAO,YAAY,WAAW,KAAK,CAAC,CAAC;AACzJ,SAAK,SAAS,IAAI,IAAI,KAAK,GAAG,MAAO,IAAI,KAAK,IAAI,GAAG;AACrD,SAAK,gBAAgB;AACrB,SAAK,MAAM,UAAU,IAAI,OAAO,IAAI;AACpC,QAAI,KAAK,QAAQ,IAAI,IAAI;AACzB,QAAI,OAAO;AAAA,EACb;;;AC9CO,MAAM,eAAe,CAAC,KAAgB,IAAa,kBAA2C;AACnG,QAAI,UAAU,QAAQ,CAAC,aAAa;AAClC,UAAI,aAAa,IAAI,YAAa;AAClC,eAAS,cAAc;AACvB,eAAS,UAAU,KAAK,OAAO;AAC/B,eAAS,aAAa,CAAC;AACvB,eAAS,cAAc;AAAA,IACzB,CAAC;AACD,QAAI,OAAO,QAAQ,CAAC,UAAU;AAbhC;AAcI,UAAI,GAAI,aAAM,UAAS,iBAAf,GAAe,eAAiB,MAAM;AAC9C,YAAM,WAAW,KAAK,gBAAiB,MAAM,SAAS;AACtD,YAAM,cAAc,KAAK,IAAI;AAAA,IAC/B,CAAC;AAAA,EACH;AAGO,MAAM,WAAW,CAAC,KAAgB,OAAsB;AAC7D,QAAI,OAAO,QAAQ,CAAC,UAAU;AAC5B,YAAM,MAAM,SAAS,SAAS,KAAK,MAAM,KAAK,MAAM,EAAE,IAAI,MAAM,MAAM,IAAI,MAAM,KAAK,MAAM;AAAA,IAC7F,CAAC;AAAA,EACH;AAGO,MAAM,cAAc,CAAC,KAAgB,UAAwB;AAClE,QAAI,OAAO,QAAQ,CAAC,UAAU;AAC5B,YAAM,SAAS,MAAM,MAAM,SAAS;AACpC,UAAI,OAAQ,OAAM,MAAM,SAAS,KAAK,QAAQ,KAAK,IAAI,GAAG,QAAQ,CAAC,CAAC;AAAA,IACtE,CAAC;AAAA,EACH;;;AG9BO,MAAM,kBAAkB;;IAE7B,UAAU,EAAE,OAAO,kBAAkB,GAAG,MAAO,SAAS,MAAM,SAAS,KAAK;;IAE5E,aAAa,EAAE,OAAO,kBAAkB,GAAG,MAAO,SAAS,KAAK;;IAEhE,YAAY,EAAE,OAAO,kBAAkB,GAAG,MAAM,SAAS,KAAK;;IAE9D,WAAW,EAAE,OAAO,kBAAkB,GAAG,MAAO,OAAO,IAAI;;IAE3D,WAAW,EAAE,OAAO,kBAAkB,GAAG,KAAM;;IAE/C,SAAS,EAAE,OAAO,UAAU,OAAO,IAAI,OAAO,MAAM,SAAS,KAAM;EACrE;AMhBA,MAAA,uBAAA;IACE;MACE,IAAM;MACN,QAAU;MACV,OAAS,EAAE,SAAW,KAAK,QAAU,OAAO;MAC5C,SAAW,CAAC,8CAA8C,qEAAqE;IACjI;IACA;MACE,IAAM;MACN,QAAU;MACV,YAAc,EAAE,WAAa,KAAK,OAAS,EAAE;MAC7C,QAAU,EAAE,SAAW,KAAK,OAAS,KAAK,KAAO,IAAI;MACrD,OAAS,EAAE,SAAW,GAAG,QAAU,QAAQ;MAC3C,SAAW;QACT;QACA;QACA;QACA;QACA;MACF;IACF;EACF;ACAO,MAAM,eAAqD,OAAO;IACtE,qBAAqD,IAAI,CAAC,YAAY,CAAC,QAAQ,IAAI,OAAO,CAAC;EAC9F;AEvBA,MAAA,mBAAA;IACE,SAAW,CAAC,mBAAmB;IAC/B,MAAQ;IACR,SAAW;MACT,EAAE,IAAM,UAAU,MAAQ,WAAW,KAAO,WAAW,QAAU,MAAM,MAAQ,KAAK,SAAW,KAAK;MACpG,EAAE,IAAM,WAAW,MAAQ,YAAY,KAAO,WAAW,QAAU,KAAK,MAAQ,KAAK,SAAW,KAAK;MACrG,EAAE,IAAM,SAAS,MAAQ,SAAS,KAAO,WAAW,QAAU,MAAM,MAAQ,KAAK,SAAW,KAAK;MACjG,EAAE,IAAM,cAAc,MAAQ,SAAS,KAAO,WAAW,QAAU,MAAM,MAAQ,KAAK,SAAW,KAAK;MACtG,EAAE,IAAM,UAAU,MAAQ,WAAW,KAAO,SAAS,QAAU,MAAM,MAAQ,MAAM,SAAW,KAAK;MACnG,EAAE,IAAM,SAAS,MAAQ,SAAS,KAAO,SAAS,QAAU,KAAK,MAAQ,MAAM,SAAW,KAAK;MAC/F,EAAE,IAAM,QAAQ,MAAQ,SAAS,KAAO,SAAS,QAAU,KAAK,MAAQ,MAAM,SAAW,KAAK;MAC9F,EAAE,IAAM,SAAS,MAAQ,SAAS,KAAO,SAAS,QAAU,KAAM,MAAQ,KAAK,SAAW,IAAI;MAC9F,EAAE,IAAM,QAAQ,MAAQ,WAAW,KAAO,SAAS,QAAU,KAAK,MAAQ,KAAK,SAAW,IAAI;MAC9F,EAAE,IAAM,UAAU,MAAQ,WAAW,KAAO,WAAW,QAAU,KAAM,MAAQ,MAAM,SAAW,KAAK;MACrG,EAAE,IAAM,SAAS,MAAQ,SAAS,KAAO,WAAW,QAAU,KAAM,MAAQ,MAAM,SAAW,KAAK;MAClG,EAAE,IAAM,aAAa,MAAQ,YAAY,KAAO,SAAS,QAAU,KAAK,MAAQ,KAAK,SAAW,IAAI;IACtG;IACA,UAAY,EAAE,IAAM,WAAW,MAAQ,oBAAoB,KAAO,SAAS,QAAU,KAAM,MAAQ,KAAK,SAAW,IAAI;EACzH;ACDA,MAAM,WAAW;AAGV,MAAM,mBAA0C,SAAS;AAEzD,MAAM,oBAAgC,SAAS;AAM/C,WAAS,iBAAiB,KAAiB,KAAK,OAAO,IAAI,EAAE,IAAiB;AACnF,WAAO;MACL;MACA,KAAK,IAAI;MACT,OAAO;MACP,UAAU,IAAI;MACd,MAAM;MACN,QAAQ;QACN;UACE,QAAQ,EAAE,MAAM,SAAS,QAAQ,SAAS;UAC1C,SAAS,EAAE,MAAM,UAAU,MAAM,CAAC,EAAE,MAAM,WAAW,WAAW,EAAE,KAAK,IAAI,OAAO,EAAE,CAAC,EAAE;UACvF,UAAU;YACR,CAAC,GAAG,IAAI,MAAM,KAAK;YACnB,CAAC,MAAM,MAAO,OAAO,OAAO;UAC9B;QACF;MACF;MACA,SAAS,gBAAgB;MACzB,SAAS,CAAC,GAAG,SAAS,QAAQ,IAAI,CAAC,WAAW,GAAG,MAAM,IAAI,IAAI,IAAI,EAAE,CAAC;IACxE;EACF;AAGO,MAAM,gBAA2B,EAAE,IAAI,YAAY,QAAQ,CAAC,GAAG,kBAAkB,iBAAiB,EAAE,IAAI,CAAC,QAAQ,iBAAiB,GAAG,CAAC,EAAE;AuB7BxI,MAAM,aAAwB;IACnC,IAAI;IACJ,QAAQ;MACN;QACE,IAAI;QACJ,KAAK;QACL,KAAK;QACL,UAAU;QACV,QAAQ;UACN;YACE,MAAM;YACN,KAAK;YACL,QAAQ,EAAE,MAAM,OAAO,MAAM,QAAQ,WAAW,EAAE,MAAM,IAAI,EAAE;YAC9D,UAAU;cACR,CAAC,GAAG,MAAQ,OAAO,OAAO;cAC1B,CAAC,MAAO,GAAG,KAAK;cAChB,CAAC,MAAM,MAAQ,OAAO,OAAO;YAC/B;UACF;QACF;QACA,SAAS,gBAAgB;QACzB,SAAS,CAAC,oEAAoE;MAChF;MACA;QACE,IAAI;QACJ,KAAK;QACL,KAAK;QACL,UAAU,CAAC,KAAK,GAAG;QACnB,QAAQ;UACN;YACE,MAAM;YACN,QAAQ,EAAE,MAAM,SAAS,QAAQ,UAAU,aAAa,KAAK,MAAM,KAAK;YACxE,SAAS,EAAE,MAAM,UAAU,MAAM,CAAC,EAAE,MAAM,YAAY,WAAW,EAAE,KAAK,IAAK,GAAG,GAAG,IAAI,CAAC,EAAE;YAC1F,UAAU;cACR,CAAC,GAAG,MAAQ,OAAO,OAAO;cAC1B,CAAC,MAAO,GAAG,KAAK;cAChB,CAAC,MAAM,MAAQ,OAAO,OAAO;YAC/B;UACF;QACF;QACA,SAAS,gBAAgB;QACzB,SAAS,CAAC,oDAAoD;MAChE;IACF;EACF;;;AC9CA,MAAM,OAAO,CAAC,OAA0B;AAAA,IACtC,IAAI,EAAE;AAAA,IACN,KAAK;AAAA,IACL,OAAO;AAAA,IACP,UAAU,EAAE;AAAA,IACZ,MAAM;AAAA;AAAA,IAEN,YAAY;AAAA,IACZ,QAAQ;AAAA,MACN;AAAA,QACE,QAAQ,EAAE,OAAO,EAAE,OAAO,SAAS,EAAE,QAAQ;AAAA,QAC7C,QAAQ,EAAE,MAAM,OAAO,MAAM,EAAE,MAAM,WAAW,EAAE,MAAM,EAAE,WAAW,IAAI,EAAE,YAAY,EAAE,MAAM,OAAO,OAAO,MAAM,WAAW,EAAE;AAAA,QAChI,SAAS,EAAE,MAAM,UAAU,MAAM,CAAC,EAAE,MAAM,YAAY,WAAW,EAAE,KAAK,EAAE,YAAY,IAAI,GAAG,GAAG,EAAE,CAAC,EAAE;AAAA,QACrG,UAAU;AAAA,UACR,CAAC,GAAG,MAAQ,OAAO,OAAO;AAAA,UAC1B,CAAC,OAAO,GAAG,KAAK;AAAA,UAChB,CAAC,MAAM,MAAQ,OAAO,OAAO;AAAA,QAC/B;AAAA,MACF;AAAA,IACF;AAAA,IACA,SAAS,gBAAgB;AAAA,IACzB,SAAS,CAAC,+BAA+B,EAAE,IAAI,IAAI;AAAA,EACrD;AAGO,MAAM,WAAW,KAAK,EAAE,IAAI,YAAY,MAAM,UAAU,OAAO,GAAG,SAAS,MAAM,SAAS,MAAM,MAAM,UAAU,WAAW,KAAK,MAAM,IAAI,CAAC;AAE3I,MAAM,eAAe,KAAK,EAAE,IAAI,gBAAgB,MAAM,cAAc,OAAO,GAAG,SAAS,MAAM,SAAS,MAAM,MAAM,UAAU,WAAW,MAAM,MAAM,IAAI,CAAC;AAExJ,MAAM,YAAY,KAAK,EAAE,IAAI,aAAa,MAAM,YAAY,OAAO,GAAG,SAAS,MAAM,SAAS,MAAM,MAAM,UAAU,WAAW,KAAK,MAAM,IAAI,CAAC;AAE/I,MAAM,UAAU,KAAK,EAAE,IAAI,WAAW,MAAM,UAAU,OAAO,GAAG,SAAS,MAAM,SAAS,KAAK,MAAM,YAAY,WAAW,KAAK,MAAM,IAAI,CAAC;AAE1I,MAAM,gBAAwC,CAAC,UAAU,cAAc,WAAW,OAAO;AAEzF,MAAM,oBAA+B,EAAE,IAAI,iBAAiB,QAAQ,cAAc;;;ACvDzF;AAAA,IACE,SAAW;AAAA,MACT;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,SAAW;AAAA,MACT,SAAW,EAAE,OAAS,aAAa,UAAY,GAAG,aAAe,GAAG;AAAA,MACpE,kBAAoB,EAAE,OAAS,gBAAgB,UAAY,GAAG,aAAe,GAAG;AAAA,MAChF,KAAO,EAAE,OAAS,WAAW,UAAY,IAAI,aAAe,GAAG;AAAA,MAC/D,KAAO,EAAE,OAAS,YAAY,UAAY,GAAG,aAAe,GAAG;AAAA,IACjE;AAAA,EACF;;;ACHA,MAAM,OAAO;AAGN,MAAM,eAAqD,KAAK;AAChE,MAAM,sBAAyC,KAAK;AA8BpD,WAAS,kBAAkB,MAAuB,UAA8B,CAAC,GAAgB;AACtG,SAAK,WAAW,iBAAiB;AACjC,UAAM,QAAuD,EAAE,GAAG,cAAc,GAAG,QAAQ,MAAM;AACjG,UAAM,UAAU,oBAAI,IAAoB;AACxC,UAAM,SAAS,CAAC,cAA8C,MAAM,SAAS,KAAK;AAClF,WAAO;AAAA,MACL;AAAA,MACA,KAAK,WAAW,UAAU,IAAI,CAAC,GAAG;AAChC,cAAM,QAAQ,OAAO,SAAS;AAC9B,YAAI,CAAC,MAAO,QAAO;AACnB,cAAM,MAAM,EAAE,OAAO;AACrB,aAAK,QAAQ,IAAI,GAAG,KAAK,KAAK,EAAG,QAAO;AACxC,gBAAQ,IAAI,KAAK,MAAM,QAAQ;AAC/B,aAAK,KAAK,EAAE,OAAO,MAAM,OAAO,UAAU,GAAI,EAAE,WAAW,SAAY,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAG,CAAC;AACnG,eAAO;AAAA,MACT;AAAA,MACA,OAAO,IAAI;AACT,mBAAW,CAAC,KAAK,OAAO,KAAK,SAAS;AACpC,cAAI,UAAU,MAAM,EAAG,SAAQ,OAAO,GAAG;AAAA,cACpC,SAAQ,IAAI,KAAK,UAAU,EAAE;AAAA,QACpC;AAAA,MACF;AAAA,MACA,SAAS,MAAM,QAAQ,MAAM;AAAA,IAC/B;AAAA,EACF;;;ACrDO,MAAM,gBAAgB,CAAC,MAA4B,UAAyB,CAAC,MAAY;AAC9F,UAAM,QAAQ,QAAQ,SAAS;AAC/B,UAAM,MAAM,QAAQ,OAAO;AAC3B,QAAI,SAAS;AACb,eAAW,OAAO,MAAM;AACtB,YAAM,SAAS,aAAa,IAAI,QAAQ,IAAI;AAC5C,gBAAU,SAAS;AACnB,UAAI,KAAK,SAAS,IAAI,QAAQ,GAAG,CAAC;AAClC,UAAI,OAAO,IAAI,KAAK,SAAS,MAAM;AACnC,UAAI,KAAK,SAAS,IAAI,IAAI,QAAQ,KAAK,SAAS,UAAW,QAAQ,YAAY,MAAQ,QAAQ,OAAO;AACtG,gBAAU,SAAS,IAAI;AAAA,IACzB;AACA,UAAM,SAAS,EAAE,SAAS,MAAM,SAAS;AACzC,eAAW,OAAO,MAAM;AACtB,UAAI,KAAK,SAAS,KAAK;AACvB,UAAI,KAAK,KAAK;AAAA,IAChB;AAAA,EACF;;;ACZA,MAAM,aAAa;AAEZ,MAAM,kBAAkB,CAAC,SAAyD;AACvF,UAAM,WAAW,CAAC,IAAY,SAAgF,EAAE,IAAI,MAAM,KAAK,KAAK,EAAE,KAAK,IAAI,IAAI;AACnJ,UAAM,OAAO,MAAM;AACnB,WAAO;AAAA,MACL,SAAS,iBAAiB,CAAC,EAAE,OAAO,MAAM;AACxC,cAAM,UAAU,KAAK,OAAO;AAC5B,mBAAW,WAAW,KAAK,QAAQ,GAAG;AACpC,gBAAM,MAAM,QAAQ,MAAM,OAAO;AACjC,kBAAQ,eAAe,KAAK,UAAU;AACtC,iBAAO,IAAI,YAAY,CAAC,IAAI,MAAM,GAAG,QAAQ,EAAE,YAAY;AAC3D,kBAAQ,eAAe,KAAK,MAAM;AAClC,iBAAO,IAAI,QAAQ,CAAC,IAAI,UAAU,GAAG,QAAQ,EAAE,QAAQ;AACvD,kBAAQ,eAAe,KAAK,OAAO;AACnC,iBAAO,CAAC,IAAI,QAAQ,CAAC,IAAI,UAAU,GAAG,QAAQ,EAAE,eAAe;AAAA,QACjE;AAAA,MACF,CAAC;AAAA,MACD,SAAS,mBAAmB,CAAC,EAAE,OAAO,MAAM;AAC1C,cAAM,UAAU,KAAK,OAAO;AAC5B,cAAM,UAAU,KAAK,QAAQ,EAAE,KAAK,CAAC,UAAU,MAAM,KAAK,SAAS,WAAW;AAC9E,YAAI,CAAC,QAAS,QAAO,OAAO,OAAO,gCAAgC;AACnE,cAAM,MAAM,QAAQ,MAAM,OAAO;AACjC,gBAAQ,QAAQ,KAAK,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,OAAO,CAAC;AACxD,cAAM,SAAS,IAAI,KAAK,IAAI,CAAC,QAAQ,IAAI,IAAI,SAAS,CAAC;AACvD,gBAAQ,QAAQ,KAAK,EAAE,MAAM,KAAK,OAAO,KAAK,MAAM,OAAO,CAAC;AAC5D,eAAO,IAAI,KAAK,KAAK,CAAC,KAAK,UAAU,IAAI,IAAI,SAAS,MAAM,OAAO,KAAK,CAAC,GAAG,GAAG,QAAQ,EAAE,4BAA4B;AAAA,MACvH,CAAC;AAAA,MACD,SAAS,iBAAiB,CAAC,EAAE,OAAO,MAAM;AACxC,cAAM,QAAQ,KAAK,QAAQ,EAAE,KAAK,CAAC,UAAU,MAAM,YAAY,OAAO;AACtE,cAAM,SAAS,KAAK,QAAQ,EAAE,KAAK,CAAC,UAAU,MAAM,YAAY,QAAQ;AACxE,cAAM,KAAK,KAAK,gBAAgB,IAAI;AACpC,YAAI,CAAC,MAAM,CAAC,MAAO,QAAO,OAAO,OAAO,2CAA2C;AACnF,eAAO,GAAG,KAAK,MAAM,IAAI,MAAM,CAAC,MAAM,QAAW,aAAa,MAAM,EAAE,iCAAiC;AACvG,YAAI,OAAQ,QAAO,GAAG,KAAK,OAAO,IAAI,MAAM,CAAC,MAAM,QAAW,GAAG,OAAO,EAAE,sBAAsB;AAChG,cAAM,MAAM,KAAK,gBAAgB,KAAK;AACtC,eAAO,KAAK,KAAK,MAAM,IAAI,MAAM,CAAC,MAAM,QAAW,uCAAuC;AAC1F,aAAK,gBAAgB,IAAI;AAAA,MAC3B,CAAC;AAAA,MACD,SAAS,gBAAgB,CAAC,EAAE,OAAO,MAAM;AACvC,cAAM,WAAW,KAAK,gBAAgB,IAAI;AAC1C,cAAM,OAAO,KAAK,QAAQ,EAAE,KAAK,CAAC,UAAU,MAAM,UAAU,OAAO;AACnE,YAAI,CAAC,YAAY,CAAC,KAAM,QAAO,OAAO,OAAO,sDAAsD;AACnG,cAAM,UAAU,SAAS,QAAQ,KAAK,IAAI,CAAC,GAAG,GAAG,CAAC,GAAG,CAAC;AACtD,eAAO,YAAY,QAAW,GAAG,KAAK,EAAE,mBAAmB;AAC3D,YAAI,CAAC,QAAS;AACd,eAAO,SAAS,MAAM,SAAS,QAAQ,mBAAmB,GAAG,CAAC,GAAG,qCAAqC;AACtG,eAAO,CAAC,SAAS,MAAM,SAAS,QAAQ,mBAAmB,GAAG,CAAC,GAAG,4BAA4B;AAAA,MAChG,CAAC;AAAA,MACD,SAAS,sBAAsB,CAAC,EAAE,OAAO,MAAM;AAC7C,cAAM,WAAW,KAAK,gBAAgB,IAAI;AAC1C,cAAM,QAAQ,KAAK,QAAQ,EAAE,KAAK,CAAC,UAAU,MAAM,YAAY,OAAO;AACtE,YAAI,CAAC,YAAY,CAAC,MAAO,QAAO,OAAO,OAAO,2CAA2C;AACzF,cAAM,OAAO,SAAS,KAAK,MAAM,IAAI,MAAM,CAAC;AAC5C,YAAI,CAAC,KAAM,QAAO,OAAO,OAAO,eAAe;AAC/C,eAAO,SAAS,QAAQ,MAAM,CAAC,MAAM,cAAc,uBAAuB;AAC1E,eAAO,SAAS,QAAQ,MAAM,KAAK,QAAQ,CAAC,MAAM,UAAU,yEAAyE;AACrI,eAAO,cAAc,MAAM,SAAS,IAAI,MAAM,YAAY,6BAA6B;AAAA,MACzF,CAAC;AAAA,MACD,SAAS,YAAY,CAAC,EAAE,OAAO,MAAM;AACnC,cAAM,SAAS,KAAK,MAAM;AAC1B,eAAO,OAAO,UAAU,YAAY,GAAG,UAAU,6BAA6B,EAAE,OAAO,OAAO,MAAM,CAAC;AACrG,eAAO,OAAO,OAAO,aAAa,aAAa,IAAI,0CAA0C,OAAO,MAAM;AAC1G,eAAO,OAAO,QAAQ,GAAG,iDAAiD,EAAE,OAAO,OAAO,MAAM,CAAC;AACjG,aAAK,WAAW;AAAA,MAClB,CAAC;AAAA,IACH;AAAA,EACF;;;AC3EO,MAAM,OAAoC;AAAA,IAC/C,IAAI;AAAA,MACF,OAAO;AAAA,MACP,MAAM;AAAA,MACN,UAAU,EAAE,cAAc,gBAAgB,MAAM,QAAQ,OAAO,mBAAmB,UAAU,iBAAiB,YAAY,aAAa;AAAA,MACtI,OAAO;AAAA,QACL,cAAc;AAAA,QACd,MAAM;AAAA,QACN,UAAU;AAAA,QACV,YAAY;AAAA,MACd;AAAA,MACA,SAAS,EAAE,cAAc,EAAE,OAAO,SAAS,UAAU,YAAY,MAAM,OAAO,GAAG,MAAM,EAAE,MAAM,QAAQ,MAAM,QAAQ,KAAK,MAAM,EAAE;AAAA,MAClI,SAAS,EAAE,UAAU,qBAAqB,SAAS,qBAAqB,MAAM,8DAA8D,SAAS,gCAAgC,aAAa,mBAAmB;AAAA,MACrN,QAAQ,EAAE,QAAQ,UAAU,OAAO,cAAc,aAAa,eAAe,cAAc,sBAAsB;AAAA,MACjH,SAAS,EAAE,SAAS,WAAW,YAAY,cAAc,WAAW,aAAa,gBAAgB,kBAAkB,YAAY,cAAc,QAAQ,UAAU,MAAM,OAAO;AAAA,MAC5K,WAAW;AAAA,QACT,eAAe;AAAA,QACf,iBAAiB;AAAA,QACjB,eAAe;AAAA,QACf,cAAc;AAAA,QACd,oBAAoB;AAAA,QACpB,UAAU;AAAA,MACZ;AAAA,IACF;AAAA,IACA,SAAS;AAAA,MACP,OAAO;AAAA,MACP,MAAM;AAAA,MACN,UAAU,EAAE,cAAc,iBAAiB,MAAM,SAAS,OAAO,gCAA0B,UAAU,kBAAkB,YAAY,wBAAqB;AAAA,MACxJ,OAAO;AAAA,QACL,cAAc;AAAA,QACd,MAAM;AAAA,QACN,UAAU;AAAA,QACV,YAAY;AAAA,MACd;AAAA,MACA,SAAS,EAAE,cAAc,EAAE,OAAO,QAAQ,UAAU,aAAa,MAAM,QAAQ,GAAG,MAAM,EAAE,MAAM,UAAU,MAAM,WAAW,KAAK,WAAW,EAAE;AAAA,MAC7I,SAAS,EAAE,UAAU,0BAA0B,SAAS,yBAAyB,MAAM,+EAA+E,SAAS,wCAAkC,aAAa,iBAAiB;AAAA,MAC/O,QAAQ,EAAE,QAAQ,UAAU,OAAO,gBAAgB,aAAa,kBAAkB,cAAc,sCAA6B;AAAA,MAC7H,SAAS,EAAE,SAAS,UAAU,YAAY,uBAAuB,WAAW,iBAAc,gBAAgB,mBAAgB,YAAY,aAAa,QAAQ,WAAW,MAAM,WAAW;AAAA,MACvL,WAAW;AAAA,QACT,eAAe;AAAA,QACf,iBAAiB;AAAA,QACjB,eAAe;AAAA,QACf,cAAc;AAAA,QACd,oBAAoB;AAAA,QACpB,UAAU;AAAA,MACZ;AAAA,IACF;AAAA,EACF;;;AC3CO,MAAM,kBAAkB;AACxB,MAAM,qBAAqB;AAClC,MAAMC,cAAa;AACnB,MAAM,aAAa,EAAE,IAAI,KAAK,IAAI,KAAK,SAAS,GAAG,KAAK,IAAI;AAC5D,MAAM,qBAAqB;AAUpB,WAAS,wBAAwB,UAA8B,CAAC,GAAuF;AAC5J,UAAM,SAAS,QAAQ,UAAU;AACjC,UAAM,OAAO,KAAK,MAAM,KAAM,KAAK,OAAO;AAC1C,UAAM,UAAU,QAAQ,WAAW,CAAC;AACpC,UAAM,WAAW,EAAE,QAAQ,SAA8B,MAAM,QAAQ,OAAO,GAAG,UAAU,MAAM,WAAW,mBAAmB;AAC/H,QAAI,QAAsB;AAC1B,QAAI,UAA+B;AACnC,QAAI,OAA0B;AAC9B,QAAI,WAAgC;AACpC,QAAI,QAA4B;AAChC,QAAI,OAAO;AACX,QAAI,QAAQ;AACZ,QAAI,YAAmC;AACvC,QAAI,QAA8C,CAAC;AACnD,QAAI,QAA6B,CAAC;AAClC,UAAM,SAAS,oBAAI,IAAoB;AACvC,UAAM,SAAS,MAAc;AAC7B,UAAM,WAAW,QAAQ,QAAQ;AACjC,UAAM,SAAS,OAAO,EAAE,QAAQ,CAAC,GAAG,OAAO,OAAO,CAAC,EAAE,OAAO,CAAC,MAAM,EAAE,YAAY,QAAQ,EAAE,QAAQ,MAAM,CAAC,GAAG,OAAO,OAAO,CAAC,EAAE,OAAO,CAAC,MAAM,EAAE,YAAY,MAAM,EAAE,OAAO;AAEzK,UAAM,SAAS,CAAC,UAA+B;AAC7C,cAAQ;AACR,4BAAY,mBAAmB,OAAO,EAAE,MAAM,UAAU,GAAI,QAAQ,WAAW,EAAE,UAAU,QAAQ,SAAS,IAAI,CAAC,EAAG,CAAC;AACrH,aAAO;AAAA,IACT;AACA,UAAM,kBAAkB,MAAY;AAClC,iBAAW,QAAQ,iBAAiB,QAAQ,eAAe,EAAE,SAAS,SAAS,SAAS,UAAU,WAAW,SAAS,UAAU,CAAC,IAAI;AAAA,IACvI;AACA,oBAAgB;AAEhB,UAAM,OAAO,CAAC,YAAyC;AAAA,MACrD,IAAI,GAAG,kBAAkB,GAAG,OAAO,EAAE;AAAA,MACrC,MAAM,OAAO,KAAK,MAAc,KAAK,OAAO;AAAA,MAC5C,SAAS;AAAA,MACT,OAAO,CAAC,UAAmB;AACzB,cAAM,MAAM,OAAO,KAAc,EAAE,MAAM,MAAM;AAE/C,eAAO,IAAI,IAAI,KAAK,MAAM,EAAE,IAAI,IAAI,KAAK,MAAM,IAAI,CAAC;AACpD,YAAI,SAAS,WAAW,QAAS,UAAS,eAAe,KAAK,SAAS,MAAM;AAC7E,eAAO,IAAI;AAAA,MACb;AAAA,IACF;AAEA,UAAM,WAAW,CAAC,UAAwB;AACxC,UAAI,CAAC,MAAM,QAAQ,CAAC,SAAU;AAC9B,YAAM,QAAQ,SAAS,QAAQ,MAAM,MAAM,KAAK;AAChD,UAAI,UAAU,aAAc;AAC5B,YAAM,UAAU,UAAU,WAAW,WAAW;AAChD,eAAS,eAAe,MAAM,KAAK,cAAc,MAAM,IAAI,QAAQ,SAAS,MAAM,UAAU,MAAM,CAAC;AAAA,IACrG;AAEA,UAAM,aAAa,MAAY;AAC7B,iBAAW,QAAQ,MAAO,MAAK,QAAQ,OAAO,IAAI;AAClD,cAAQ,CAAC;AAAA,IACX;AACA,UAAM,gBAAgB,MAAY;AAChC,UAAI,CAAC,MAAM,SAAS,CAAC,WAAW,CAAC,MAAO;AACxC,iBAAW;AACX,iBAAW,SAAS,OAAO,OAAO,GAAG;AACnC,gBAAQ,aAAa,MAAM,KAAK,KAAK,MAAM,MAAM,QAAQ,CAAC;AAC1D,gBAAQ,SAAS,MAAM,KAAK,KAAK,MAAM,MAAM,OAAO,CAAC;AAAA,MACvD;AACA,YAAM,OAAO,CAAC,QAAgB,OAAe,WAAoC;AAC/E,cAAM,OAAO,IAAK,MAAgB,KAAK,IAAK,MAAgB,aAAa,SAAS,MAAM,QAAQ,EAAE,EAAE,QAAQ,CAAC,KAAK,KAAK,CAAC,GAAG,IAAK,MAAgB,kBAAkB,EAAE,OAAO,aAAa,MAAM,SAAS,MAAM,YAAY,MAAM,CAAC,CAAC;AACjO,aAAK,SAAS,IAAI;AAClB,eAAO,IAAI,IAAI;AACf,cAAM,KAAK,IAAI;AAAA,MACjB;AACA,iBAAW,SAAS,OAAO,OAAO,GAAG;AACnC,YAAI,CAAC,MAAM,IAAI,KAAK,OAAQ;AAC5B,cAAM,SAAS,MAAM,IAAI,QAAQ;AACjC,YAAI,KAAK,MAAM,MAAM,aAAa,KAAK,OAAQ,MAAK,QAAQ,SAAU,MAAM,IAAI,IAAI;AACpF,cAAM,OAAO,MAAM,IAAI,QAAQ,UAAU,SAAS;AAClD,YAAI,KAAK,MAAM,MAAM,cAAc,KAAK,KAAM,MAAK,MAAM,UAAU,MAAM,IAAI,IAAI;AAAA,MACnF;AAAA,IACF;AAEA,UAAM,WAAW,MAAY;AAC3B,UAAI,CAAC,WAAW,CAAC,SAAS,CAAC,KAAM;AACjC,gCAAc,IAAI,MAAM,MAAM;AAC9B,UAAI,CAAC,UAAU,OAAQ,CAAC,KAAK,MAA4B,IAAI,SAAS;AACtE,eAAS,QAAQ,MAAM,QAAQ,QAAQA,aAAY,SAAS;AAC1D,cAAM,SAAS,QAAQ,QAAQ,KAAK,IAAI,GAAG,QAAQ,MAAM,CAAC;AAC1D,cAAM,MAAM,QAAQ,MAAM,MAAM;AAChC,cAAM,OAAO,QAAQ,WAAW,GAAG;AACnC,YAAI,KAAK,SAAS,IAAI,WAAW,KAAM,QAAQ,WAAW,UAAW,WAAW,KAAK,GAAG,WAAW,KAAK,KAAK,MAAM,QAAQ,WAAW,OAAO,IAAI,WAAW,GAAG;AAE/J,YAAI,KAAK,SAAS,CAAC,WAAW;AAC5B,cAAK,OAAyB,OAAQ,QAAO,UAAU;AAAA,QACzD,CAAC;AACD,kBAAU,IAAI,IAAI,MAAM,KAAK,IAAI;AACjC,cAAM,KAAK,EAAE,KAAK,KAAK,CAAC;AAAA,MAC1B;AAAA,IACF;AACA,UAAM,aAAa,MAAY;AAC7B,iBAAW,QAAQ,OAAO,SAAS;AACnC,kBAAY;AACZ,cAAQ,CAAC;AAAA,IACX;AAEA,UAAM,cAAc,MAAY;AAC9B,iBAAW,SAAS,OAAO,OAAO,GAAG;AACnC,YAAI,MAAM,KAAM;AAChB,iBAAS,eAAe,MAAM,KAAK,SAAS,MAAM;AAAA,MACpD;AAAA,IACF;AAEA,UAAM,WAAW,CAAC,WAA8B;AAC9C,UAAI,OAAO,SAAS,QAAS,QAAO,cAAc;AAClD,UAAI,OAAO,SAAS,UAAU;AAC5B,YAAI,OAAO,OAAO,YAAY;AAC5B,qBAAW,SAAS,OAAO,OAAO,GAAG;AACnC,kBAAM,OAAO,UAAU,KAAK,MAAM,IAAI,QAAQ,IAAI,QAAQ,KAAK;AAC/D,gBAAI,MAAM,KAAM,UAAS,eAAe,MAAM,KAAK,cAAc,MAAM,IAAI,QAAQ,SAAS,IAAI,CAAC;AAAA,UACnG;AAAA,QACF,WAAW,OAAO,OAAO,WAAW;AAClC,qBAAW,SAAS,OAAO,OAAO,GAAG;AACnC,mBAAO,MAAM;AACb,mBAAO,MAAM;AACb,qBAAS,eAAe,MAAM,KAAK,SAAS,MAAM;AAAA,UACpD;AAAA,QACF,WAAW,OAAO,OAAO,UAAU,OAAO;AACxC,qBAAW,CAAC,IAAI,KAAK,KAAK,QAAQ;AAChC,kBAAM,KAAK,MAAM,IAAI,KAAK,iBAAiB,IAAK,MAAgB,QAAQ,CAAC;AACzE,kBAAM,KAAK,MAAM,IAAI,QAAQ,IAAI,CAAC,GAAG,GAAG,GAAG,IAAI,KAAK,GAAG,CAAC,GAAG,EAAE,KAAK,GAAG,CAAC;AAAA,UACxE;AAAA,QACF,WAAW,OAAO,OAAO,UAAW,UAAS;AAAA,iBACpC,OAAO,OAAO,cAAe,YAAW;AACjD;AAAA,MACF;AACA,cAAQ,OAAO,IAAI;AAAA,QACjB,KAAK;AAAgB,mBAAS,SAAS,OAAO,OAAO,KAAK;AAAwB,sBAAY;AAAG;AAAA,QACjG,KAAK;AAAQ,mBAAS,OAAO,OAAO,OAAO,KAAK;AAAG;AAAA,QACnD,KAAK;AAAS,mBAAS,QAAQ,OAAO,OAAO,KAAK;AAAG;AAAA,QACrD,KAAK;AAAY,mBAAS,WAAW,QAAQ,OAAO,KAAK;AAAG,cAAI,SAAU,UAAS,UAAU,SAAS;AAAU;AAAA,QAChH,KAAK;AAAc,mBAAS,YAAY,OAAO,OAAO,KAAK;AAAG,0BAAgB;AAAG;AAAA,QACjF;AAAS;AAAA,MACX;AAAA,IACF;AAEA,UAAM,QAAQ,CAAC,OAAqB;AAClC,cAAQ,KAAK,SAAS;AACtB,eAAS;AACT,aAAO,OAAO,EAAE;AAChB,iBAAW,CAAC,IAAI,KAAK,KAAK,QAAQ;AAChC,YAAI,CAAC,MAAM,IAAI,KAAK,QAAQ;AAC1B,iBAAO,OAAO,EAAE;AAChB;AAAA,QACF;AACA,iBAAS,KAAK;AACd,iBAAS,QAAQ,MAAM,KAAK,EAAE,MAAM,OAAO,KAAK,SAAS,OAAO,MAAM,SAAS,MAAM,OAAO,MAAM,OAAO,MAAM,OAAO,KAAK,MAAM,CAAC;AAClI,iBAAS,YAAY,MAAM,KAAK,EAAE;AAAA,MACpC;AACA,iBAAW,EAAE,KAAK,KAAK,KAAK,OAAO;AACjC,iBAAS,QAAQ,KAAK,EAAE,MAAM,OAAO,KAAK,SAAS,OAAO,MAAM,SAAS,KAAK,CAAC;AAC/E,YAAI,KAAK,kBAAkB,IAAI;AAC/B,aAAK,OAAO;AAAA,MACd;AAAA,IACF;AAEA,UAAM,cAAc,CAAC,SAA6D;AAChF,UAAI,YAAY;AAChB,UAAI,YAAY;AAChB,YAAM,QAAQ,CAAC,SACb,KAAK,SAAS,CAAC,WAAW;AACxB,cAAM,OAAO;AACb,YAAI,CAAC,KAAK,UAAU,CAAC,KAAK,QAAS;AACnC,qBAAa;AACb,cAAM,WAAW,KAAK;AACtB,qBAAa,SAAS,QAAQ,SAAS,MAAM,QAAQ,IAAI,SAAS,WAAW,SAAS,QAAQ;AAAA,MAChG,CAAC;AACH,UAAI,SAAS,QAAS,YAAW,SAAS,OAAO,OAAO,EAAG,OAAM,MAAM,IAAI,IAAI;AAC/E,iBAAW,EAAE,KAAK,KAAK,MAAO,OAAM,KAAK,IAAI;AAC7C,aAAO,EAAE,WAAW,WAAW,KAAK,MAAM,SAAS,EAAE;AAAA,IACvD;AAEA,UAAM,OAAqB;AAAA,MACzB,MAAM,KAAK;AAAA,MACX,SAAS,MAAM;AAAA,MACf,QAAQ,MAAM,OAAQ,SAAS,MAAM,KAAe;AAAA,MACpD,UAAU,MAAM;AAAA,MAChB,iBAAiB,CAAC,YAAY;AAC5B,iBAAS,WAAW;AACpB,wBAAgB;AAChB,eAAO;AAAA,MACT;AAAA,MACA,OAAO,MAAM;AACX,eAAQ,SAAS,MAAM,KAAe;AACtC,iBAAS;AACT,eAAO,EAAE,OAAO,MAAM,QAAQ,QAAQ,YAAY,OAAO,GAAG,OAAO,MAAM,CAAC,GAAG,KAAK,MAAM,UAAU,GAAG,MAAM,MAAM,CAAC,GAAG,IAAI,aAAa,EAAE;AAAA,MAC1I;AAAA,MACA;AAAA,IACF;AAEA,WAAO;AAAA,MACL,IAAI;AAAA,MACJ,MAAM,KAAK;AAAA,MACX,OAAO,KAAK;AAAA,MACZ,IAAI,SAAS;AACX,eAAO;AAAA,MACT;AAAA,MACA,OAAO,MAAM,QAAQ,IAAI,IAAI;AAAA,MAC7B,MAAM,MAAkB;AAEtB,cAAM,MAAM;AACZ,YAAI,SAAS;AACb,cAAM,QAAQ,QAAQ,IAAI,CAAC,WAAW;AACpC,gBAAM,SAAS,aAAa,OAAO,IAAI;AACvC,gBAAM,IAAI,SAAS,SAAS;AAC5B,oBAAU,SAAS;AACnB,iBAAO,EAAE,QAAQ,EAAE;AAAA,QACrB,CAAC;AACD,cAAM,SAAS,EAAE,SAAS,OAAO;AACjC,eAAO;AAAA,UACL,YAAY,MAAM,IAAI,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,GAAG,kBAAkB,GAAG,OAAO,EAAE,IAAI,UAAU,CAAC,IAAI,QAAQ,GAAG,CAAC,GAAG,WAAW,OAAO,KAAK,SAAS,UAAU,MAAM,IAAI,EAAE;AAAA,UAC3K,MAAM,EAAE,KAAK,CAAC,GAAG,GAAG,KAAK,IAAI,KAAK,SAAS,OAAO,IAAI,CAAC,GAAG,QAAQ,CAAC,GAAG,KAAK,CAAC,EAAE;AAAA,QAChF;AAAA,MACF;AAAA,MACA,UAAU,MAAuB;AAAA,QAC/B,EAAE,MAAM,UAAU,IAAI,gBAAgB,MAAM,KAAK,SAAS,cAAc,MAAM,KAAK,MAAM,cAAc,SAAU,CAAC,SAAS,YAAY,MAAM,EAAY,IAAI,CAAC,QAAQ,EAAE,IAAI,MAAM,KAAK,QAAQ,aAAa,EAAE,EAAY,EAAE,GAAG,SAAS,QAAQ;AAAA,QAChP,EAAE,MAAM,UAAU,IAAI,QAAQ,MAAM,KAAK,SAAS,MAAM,MAAM,KAAK,MAAM,MAAM,SAAU,CAAC,QAAQ,QAAQ,KAAK,EAAY,IAAI,CAAC,QAAQ,EAAE,IAAI,MAAM,KAAK,QAAQ,KAAK,EAAE,EAAY,EAAE,GAAG,SAAS,OAAO;AAAA,QACzM,EAAE,MAAM,UAAU,IAAI,SAAS,MAAM,KAAK,SAAS,OAAO,KAAK,GAAG,KAAK,GAAG,MAAM,MAAM,SAAS,EAAE;AAAA,QACjG,EAAE,MAAM,WAAW,IAAI,YAAY,MAAM,KAAK,SAAS,UAAU,MAAM,KAAK,MAAM,UAAU,SAAS,KAAK;AAAA,QAC1G,EAAE,MAAM,UAAU,IAAI,cAAc,MAAM,KAAK,SAAS,YAAY,MAAM,KAAK,MAAM,YAAY,KAAK,GAAG,KAAK,KAAK,MAAM,GAAG,SAAS,mBAAmB;AAAA,QACxJ,EAAE,MAAM,UAAU,IAAI,YAAY,MAAM,KAAK,QAAQ,SAAS;AAAA,QAC9D,EAAE,MAAM,UAAU,IAAI,WAAW,MAAM,KAAK,QAAQ,QAAQ;AAAA,QAC5D,EAAE,MAAM,UAAU,IAAI,QAAQ,MAAM,KAAK,QAAQ,KAAK;AAAA,QACtD,EAAE,MAAM,UAAU,IAAI,WAAW,MAAM,KAAK,QAAQ,QAAQ;AAAA,QAC5D,EAAE,MAAM,UAAU,IAAI,eAAe,MAAM,KAAK,QAAQ,YAAY;AAAA,MACtE;AAAA,MACA,QAAQ,MAAsB,CAAC,UAAU,SAAS,eAAe,cAAc,EAAY,IAAI,CAAC,QAAQ,EAAE,IAAI,MAAM,KAAK,OAAO,EAAE,GAAG,SAAS,MAAM,EAAE;AAAA,MACtJ,SAAS,MAAsB;AAAA,QAC7B,EAAE,IAAI,WAAW,MAAM,KAAK,QAAQ,SAAS,MAAM,MAAM,OAAO,OAAO,MAAM,OAAO;AAAA,QACpF,EAAE,IAAI,cAAc,MAAM,KAAK,QAAQ,YAAY,MAAM,MAAM,YAAY,EAAE,UAAU;AAAA,QACvF,EAAE,IAAI,aAAa,MAAM,KAAK,QAAQ,WAAW,MAAM,MAAM,YAAY,EAAE,UAAU;AAAA,QACrF,EAAE,IAAI,kBAAkB,MAAM,KAAK,QAAQ,gBAAgB,MAAM,MAAM,MAAM,CAAC,GAAG,KAAK,MAAM,UAAU,EAAE;AAAA,QACxG,EAAE,IAAI,cAAc,MAAM,KAAK,QAAQ,YAAY,MAAM,MAAM,CAAC,GAAG,OAAO,OAAO,CAAC,EAAE,OAAO,CAAC,MAAM,EAAE,QAAQ,CAAC,EAAE,OAAO,EAAE,OAAO;AAAA,QAC/H,EAAE,IAAI,UAAU,MAAM,KAAK,QAAQ,QAAQ,MAAM,MAAM,OAAO,EAAE,OAAO;AAAA,QACvE,EAAE,IAAI,QAAQ,MAAM,KAAK,QAAQ,MAAM,MAAM,MAAM,OAAO,EAAE,KAAK;AAAA,MACnE;AAAA,MACA,WAAW,MAAwC,gBAAgB,IAAI;AAAA,MACvE,OAAO,MAAM;AACX,eAAO;AACP,eAAO,KAAK,KAAc;AAC1B,gBAAQ,KAAK,SAAS,QAAQ,UAAU,QAAQ,kBAAkB,KAAK,OAAO,QAAQ,SAAS,CAAC,CAAC,IAAI;AACrG,cAAM,aAAa,KAAK,OAAO,SAAS,QAAQ;AAChD,cAAM,YAAY,KAAK,QAAQ,KAAK;AACpC,eAAO,MAAM;AACX,uBAAa;AACb,oBAAU;AACV,qBAAW;AACX,qBAAW;AACX,iBAAO,QAAQ;AACf,kBAAQ;AACR,iBAAO;AAAA,QACT;AAAA,MACF;AAAA,IACF;AAAA,EAEF;;;A9E9OA,MAAM,iBAAiB,CAAC,SAAoC;AAC1D,QAAI,YAAY;AAChB,SAAK,SAAS,CAAC,WAAW;AACxB,YAAM,OAAO;AACb,UAAI,CAAC,KAAK,OAAQ;AAClB,YAAM,WAAW,KAAK;AACtB,mBAAa,SAAS,QAAQ,SAAS,MAAM,QAAQ,IAAI,SAAS,WAAW,SAAS,QAAQ;AAAA,IAChG,CAAC;AACD,WAAO,KAAK,MAAM,SAAS;AAAA,EAC7B;AAEO,MAAM,qBAAqB,CAAC,OAAc,UAA+B,CAAC,MAAoB;AACnG,UAAM,SAAS,QAAQ,UAAU,aAAa,QAAQ,QAAQ,CAAC;AAC/D,UAAM,aAAa,MAAoB;AACrC,YAAM,QAAQ,QAAQ,YAAa,WAA2C;AAC9E,UAAI,CAAC,MAAO,OAAM,IAAI,MAAM,uGAAuG;AACnI,aAAO;AAAA,IACT;AACA,UAAM,gBAAgB,IAAI,MAAM,qBAAqB,EAAE,OAAO,UAAU,UAAU,UAAU,mBAAmB,KAAK,WAAW,IAAI,CAAC;AACpI,UAAM,QAAkE;AAExE,WAAO;AAAA,MACL;AAAA,MACA,MAAM,SAAS,eAAe,CAAC,GAAG;AAChC,cAAM,MAAM,UAAU,OAAO,SAAS,EAAE,OAAO,aAAa,SAAS,OAAO,IAAI,EAAE,CAAC;AACnF,YAAI,QAAQ,KAAK,SAAS,QAAS,YAAW,OAAO,GAAG;AAAA,iBAC/C,QAAQ,KAAK,SAAS,OAAQ,WAAU,OAAO,KAAK,EAAE,OAAO,YAAY,QAAQ,EAAE,EAAE,CAAC;AAAA,YAC1F,gBAAe,OAAO,KAAK,EAAE,UAAU,QAAQ,KAAK,UAAU,WAAW,IAAK,QAAqB,OAAO,iBAAiB,QAAQ,EAAE,EAAE,CAAC;AAC7I,YAAI,YAAY,eAAe,IAAI,IAAI;AACvC,YAAI,KAAK,SAAS,CAAC,WAAW;AAC5B,cAAK,OAAyB,OAAQ,QAAO,SAAS,YAAY,QAAQ;AAAA,QAC5E,CAAC;AACD,eAAO;AAAA,MACT;AAAA,MACA,QAAQ,KAAK,QAAQ;AACnB,cAAM,OAAO,IAAI,QAAQ,KAAK;AAC9B,YAAI,SAAS,QAAS,cAAa,OAAO,KAAK,MAAM;AAAA,iBAC5C,SAAS,OAAQ,aAAY,KAAK,MAAM;AAAA,YAC5C,kBAAiB,KAAK,QAAQ,KAAK;AAAA,MAC1C;AAAA,MACA,gBAAgB,CAAC,KAAK,UAAU,iBAAiB,OAAO,KAAK,KAAK;AAAA,MAClE,cAAc,CAAC,KAAK,OAAO,aAAa,KAAK,IAAI,aAAa;AAAA,MAC9D;AAAA,MACA;AAAA,MACA,YAAY,CAAC,QAAQ,eAAe,OAAO,IAAI,MAAM,EAAE,OAAO,uBAAuB,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC;AAAA,IAC1G;AAAA,EACF;","names":["mesh","material","geometry","PI","PI","document","SKIN_COUNT"]}