@adatechnology/game-interactables-three 0.2.3 → 0.2.5

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-contracts/src/blast-shove.ts","../../game-contracts/src/blast-impulse.ts","../../game-contracts/src/blast-knock.ts","../../game-contracts/src/impact-cost.ts","../../game-contracts/src/impact.ts","../../game-contracts/src/body.ts","../../game-contracts/src/spec.ts","../../game-contracts/src/weapons.ts","../../game-contracts/src/ignition.ts","../../game-contracts/src/seal.ts","../../game-contracts/src/twist.ts","../../game-contracts/src/animation.ts","../../game-contracts/src/tool-animation.ts","../../game-contracts/src/frame.ts","../../game-contracts/src/warn.ts","../../game-contracts/src/registry.ts","../../game-contracts/src/bench.ts","../../game-contracts/src/environment.ts","../../game-contracts/src/categories.ts","../../game-contracts/src/origin.ts","../../game-contracts/src/footprint.ts","../../game-contracts/src/foley.ts","../../game-contracts/src/index.ts","../src/routed-particles.ts","../src/effects.ts","../../game-shapes/src/color.ts","../../game-shapes/src/hash.ts","../../game-shapes/src/math.ts","../../game-shapes/src/primitives/three-generators.ts","../../game-shapes/src/primitives/difference.ts","../../game-shapes/src/primitives/earcut.ts","../../game-shapes/src/primitives/extrude.ts","../../game-shapes/src/primitives/index.ts","../../game-shapes/src/random.ts","../../game-shapes/src/registry.ts","../../game-shapes/schema/shape.schema.json","../../game-shapes/src/schema.ts","../../game-shapes/src/generate.ts","../../game-shapes/src/merge.ts","../../game-shapes/src/worker.ts","../../game-shapes-three/src/geometry.ts","../../game-shapes-three/src/namespace.ts","../../game-shapes-three/src/object.ts","../../game-shapes-three/src/merge-by-material.ts","../../game-shapes-three/src/cord.ts","../../game-shapes-three/src/release.ts","../../game-render-three/src/lights/slots.ts","../../game-render-three/src/lights/arbiter.ts","../../game-render-three/src/fog.ts","../../game-render-three/src/lights/scene-state.ts","../../game-render-three/src/lights/budget.ts","../../game-render-three/src/lights/types.ts","../../game-render-three/src/lights/guard.ts","../../game-render-three/src/program-key.ts","../../game-render-three/src/pools/anchors.ts","../../game-render-three/src/probes.ts","../../game-render-three/src/warmup.ts","../../game-render-three/src/pools/pooled.ts","../../game-render-three/src/pools/geometry.ts","../../game-render-three/src/pools/material.ts","../../game-render-three/src/pools/render-pools.ts","../../game-render-three/src/frame-pacer.ts","../../game-render-three/src/static-batcher.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","../../game-props3d/src/shape-recorder.ts","../src/types.ts","../../game-interactables/src/colliders.ts","../../game-interactables/src/rng.ts","../../game-interactables/src/loot.ts","../../game-interactables/src/locale.ts","../../game-interactables/src/math.ts","../../game-interactables/src/world.ts","../src/pose.ts","../src/merge.ts","../src/mesh.ts","../src/mirrors.ts","../src/screen.ts","../src/sway.ts","../src/adapter.ts"],"sourcesContent":["export { createThreeAdapter } from './adapter'\nexport type { ThreeAdapter, ThreeAdapterOptions } from './adapter'\nexport { createMeshApi, createDefaultMaterials } from './mesh'\nexport type { FinishOptions, MaterialMap, MeshApi, MeshApiOptions, MeshResult } from './mesh'\nexport { mergeParts } from './merge'\nexport type { MergedGroup, MergeOptions, PoseBatch, SwayBatch } from './merge'\nexport { applyPose, poseOf, rememberRest, syncObject } from './pose'\nexport type { Pose, SyncOptions } from './pose'\nexport { attachSway, patchSwayShader, swayMaterial } from './sway'\nexport type { SwayHandle, SwayOptions, SwayUniforms } from './sway'\nexport { createEffects } from './effects'\nexport type { Effects, EffectsOptions, EffectsStats, PerspectiveLike, PoolKind } from './effects'\nexport { createScreen } from './screen'\nexport type { Screen, ScreenOptions } from './screen'\nexport { createMirrors } from './mirrors'\nexport type { MirrorCandidate, MirrorOptions, Mirrors, ReflectorConstructor } from './mirrors'\nexport { catenary, createCord } from './cord'\nexport { MATERIAL_KEYS } from './types'\nexport type { CanvasFactory, HouseholdApi, MaterialKey, Quality, ThreeLib, Vec3 } from './types'\n","import type { Vec3 } from './destruction'\n\n/**\n * The numbers of the shove a blast gives to a body that moves (a loose item, a piece of furniture, a person): all of them a game value, with a default that reads well in a room,\n * not a physical table. A host reads them once and lends them to the modules that move their own bodies (`ModuleHost.blastShove`).\n */\nexport interface BlastShoveOptions {\n /**\n * The share of the blast energy that reaches a body as motion: the speed at the centre is `sqrt(2 * energy * coupling / mass)`, before the falloff (default 0.02).\n */\n readonly coupling?: number\n /** Fastest a body is thrown, in m/s, at the centre of the blast (default 20). */\n readonly maxSpeed?: number\n /** How much of the push goes up: the direction is `(away, lift)` normalised from the horizontal outward direction (default 0.3). */\n readonly lift?: number\n /** Below this speed (m/s) nothing moves: a heavy thing far from a small blast stays where it is (default 0.3). */\n readonly minSpeed?: number\n /** Exponent of the falloff `(1 - distance / radius) ^ power`, the same as the damage of the destruction world (default 2). */\n readonly falloffPower?: number\n}\n\n/** The defaults of `BlastShoveOptions`. */\nexport const DEFAULT_BLAST_SHOVE: Readonly<Required<BlastShoveOptions>> = { coupling: 0.02, maxSpeed: 20, lift: 0.3, minSpeed: 0.3, falloffPower: 2 }\n\n/** A blast as the shove reads it: where, how much energy (J) and how far it reaches (m). `reach` (0..1) is the share that got through what stood in the way. */\nexport interface BlastSource {\n readonly point: Vec3\n readonly energy: number\n readonly radius: number\n readonly reach?: number\n /**\n * Kilograms of TNT equivalent of the charge. With it a body is pushed by the blast wave itself (`blast-impulse`), and the shove of loose items keeps this module's rule; without it the body\n * takes the charge from `energy` by `BlastImpulseOptions.tntPerJoule`.\n */\n readonly tnt?: number\n /** Inside this radius (metres) the blast is lethal: a body there that the blast reaches (not behind cover) goes down, wounded, wherever the wave pushed it. */\n readonly lethalRadius?: number\n}\n\n/** What a blast does to one body: how fast it leaves (m/s), the unit direction it leaves in, and how far from the centre it was. */\nexport interface BlastShove {\n readonly speed: number\n readonly direction: Vec3\n readonly distance: number\n /** The falloff applied, 0..1. */\n readonly falloff: number\n}\n\n/**\n * The shove a blast gives to a body of `mass` kg whose centre is at `position`: away from the centre, lifted a little, with the speed\n * `min(maxSpeed, sqrt(2 * energy * reach * coupling / mass)) * (1 - distance / radius) ^ 2`. Undefined when the body is outside the radius or the speed is under `minSpeed`.\n * The same rule moves the loose items and furniture of the test room and the people of the characters module, so a game that tunes the options tunes all of them.\n */\nexport function blastShove(blast: BlastSource, target: { readonly position: Vec3; readonly mass: number }, options: BlastShoveOptions = {}): BlastShove | undefined {\n const o = { ...DEFAULT_BLAST_SHOVE, ...options }\n const dx = target.position[0] - blast.point[0]\n const dy = target.position[1] - blast.point[1]\n const dz = target.position[2] - blast.point[2]\n const distance = Math.hypot(dx, dy, dz)\n if (!(blast.radius > 0) || distance > blast.radius) return undefined\n const falloff = (1 - distance / blast.radius) ** o.falloffPower\n const mass = Math.max(target.mass, 0.05)\n const speed = Math.min(o.maxSpeed, Math.sqrt((2 * blast.energy * (blast.reach ?? 1) * o.coupling) / mass)) * falloff\n if (!(speed >= o.minSpeed)) return undefined\n // straight above or below the centre there is no \"away\": the push goes up\n const direction: Vec3 = distance > 1e-4 ? [dx / distance, dy / distance + o.lift, dz / distance] : [0, 1, 0]\n const length = Math.hypot(direction[0], direction[1], direction[2])\n return { speed, direction: [direction[0] / length, direction[1] / length, direction[2] / length], distance, falloff }\n}\n","/**\n * The push a blast gives a standing body, from the physics of the blast wave and not from a game coupling: the impulse of the wave per square metre at a distance from a charge of\n * `tnt` kg of TNT equivalent (Kinney and Graham's fit of the side-on impulse, scaled by the cube root of the charge), reflected on the body, over the area the body shows the wave,\n * divided by its mass. A hand grenade at 3 m gives a person a few tenths of a metre per second; what floors that person is the fragments, not the wave. A big charge close throws.\n */\n\n/** The numbers of the body a blast wave pushes. All have a default for a standing adult. */\nexport interface BlastImpulseOptions {\n /** Square metres of the body the wave meets: about 1.75 m tall by 0.4 m wide (default 0.7). */\n readonly bodyArea?: number\n /** How much of the push goes up: the direction is `(away, lift)` normalised (default 0.15: the wave runs along the ground). */\n readonly lift?: number\n /** Below this speed (m/s) the body does not move (default 0.3). */\n readonly minSpeed?: number\n /**\n * Kilograms of TNT per joule of the blast `energy`, for a blast that does not say its charge (`BlastSource.tnt`). Default 0.24 / 200000: the arsenal's energy scale, where the M67 (180 g\n * of Composition B, 0.24 kg of TNT) is 200 kJ. It is a game equivalence for blasts without a charge, not a physical constant.\n */\n readonly tntPerJoule?: number\n}\n\nexport const DEFAULT_BLAST_IMPULSE: Readonly<Required<BlastImpulseOptions>> = { bodyArea: 0.7, lift: 0.15, minSpeed: 0.3, tntPerJoule: 0.24 / 200000 }\n\n/** Closest distance (m) the fit is read at: inside it the charge is in contact and the body is torn apart anyway. */\nconst NEAREST = 0.3\n\n/**\n * Side-on (incident) specific impulse of the wave, in Pa·s, at `distance` metres from `tnt` kg of TNT in free air: Kinney and Graham, \"Explosive Shocks in Air\" (1985),\n * `I/W^(1/3) = 0.067 sqrt(1 + (Z/0.23)^4) / (Z^2 (1 + (Z/1.55)^3)^(1/3))` bar·ms per kg^(1/3), with the scaled distance `Z = R / W^(1/3)`.\n */\nexport function sideOnImpulse(tnt: number, distance: number): number {\n if (!(tnt > 0)) return 0\n const w3 = Math.cbrt(tnt)\n const z = Math.max(distance, NEAREST) / w3\n const scaled = (0.067 * Math.sqrt(1 + (z / 0.23) ** 4)) / (z * z * Math.cbrt(1 + (z / 1.55) ** 3))\n return scaled * w3 * 100 // bar·ms to Pa·s\n}\n\n/**\n * The impulse per square metre a body facing the wave takes, Pa·s: the side-on impulse reflected. The reflection doubles a weak wave and grows up to about six times close to the charge,\n * where the wave is strong (the ratio of reflected to side-on impulse of the Kingery-Bulmash charts, fitted as `2 + 4 / (1 + Z^2)`).\n */\nexport function bodyImpulse(tnt: number, distance: number): number {\n if (!(tnt > 0)) return 0\n const z = Math.max(distance, NEAREST) / Math.cbrt(tnt)\n return sideOnImpulse(tnt, distance) * (2 + 4 / (1 + z * z))\n}\n\n/** The speed (m/s) a blast wave gives a free body of `mass` kg at `distance` metres from `tnt` kg of TNT, `reach` (0..1) of it getting through what stands in the way. */\nexport function blastBodySpeed(tnt: number, distance: number, mass: number, reach = 1, options: BlastImpulseOptions = {}): number {\n const area = options.bodyArea ?? DEFAULT_BLAST_IMPULSE.bodyArea\n return (bodyImpulse(tnt, distance) * area * Math.max(0, reach)) / Math.max(mass, 1)\n}\n","import { blastBodySpeed, DEFAULT_BLAST_IMPULSE } from './blast-impulse'\nimport type { BlastImpulseOptions } from './blast-impulse'\nimport type { BlastShoveOptions, BlastSource } from './blast-shove'\nimport type { Vec3 } from './destruction'\n\n/** From this speed (m/s) a blast takes a standing body off its feet. Below it the body is only pushed. */\nexport const KNOCK_DOWN_SPEED = 2.5\n/** The angle of a body lying where it fell (the same tilt a dead body takes). */\nexport const KNOCKED_DOWN_ANGLE = 1.35\n/** Seconds the body lies after it lands: this plus the landing speed times `STAND_UP_PER_SPEED`. */\nconst LIE_SECONDS = 1.6\n/** Seconds a body floored by its wounds (inside the lethal radius) lies before it gets up, when it is not dead. */\nconst WOUNDED_LIE_SECONDS = 6\n/** The share of the blast that has to get through for its fragments to reach a body: behind cover that stops half of it the fragments stop too. */\nexport const FRAGMENT_COVER = 0.5\n/** Seconds a body is stunned at the edge of the lethal radius (the ringing ears, the dizziness); it falls with the square of the way to the edge of the blast. */\nconst STUN_SECONDS = 4\nconst STAND_UP_PER_SPEED = 0.25\nconst FALL_SECONDS = 0.25\nconst RISE_SECONDS = 0.6\n/** m/s squared that slows a body sliding on the ground. */\nconst GROUND_FRICTION = 7\nconst GRAVITY = 9.81\n/** Height of the chest above the feet: where the blast takes the body. */\nexport const KNOCK_CHEST = 1.1\nconst CHEST = KNOCK_CHEST\n\n/** What a blast is doing to one body: its velocity (m/s), whether it is in the air, and how far down it is (0 on its feet, 1 lying). */\nexport interface Knock {\n velocity: [number, number, number]\n airborne: boolean\n /** 0..1 */\n lying: number\n /** Seconds left to lie on the ground after it stopped. */\n lieLeft: number\n /** Knocked off its feet by this blast. */\n down: boolean\n /** Floored by its wounds: inside the lethal radius and not behind cover. It lies longer and falls where it stood. */\n wounded?: boolean\n /** Seconds of stun the blast gave the body: dizziness and ringing ears. */\n stunSeconds?: number\n /** The horizontal unit direction away from the blast, even when the push is too weak to move the body: the way it falls. */\n away?: Vec3\n}\n\n/** The mass of a body in kg from its height (m) and its build (1 = average): 70 kg at 1.75 m. */\nexport const bodyMass = (height: number, build = 1): number => 70 * (height / 1.75) ** 2 * build\n\n/**\n * What a blast does to a body standing at `feet`, of `mass` kg. Undefined when the blast does not reach it or does nothing to it.\n *\n * The body is pushed by the blast wave (`blastBodySpeed`, from the charge `blast.tnt`, or from `energy` by `tntPerJoule`), taken at the chest and cut by `reach`, the share that got through\n * cover. It is floored when that push is `KNOCK_DOWN_SPEED` or more, or when it stands inside `lethalRadius` with the blast reaching it (the fragments and the overpressure fell it where it\n * stands). It is stunned for a while inside the radius. `options` are the shove numbers of the host's loose items; only their `minSpeed` applies to a body.\n */\nexport function knockOf(blast: BlastSource, feet: Vec3, mass: number, options?: BlastShoveOptions, impulse: BlastImpulseOptions = {}): Knock | undefined {\n const o = { ...DEFAULT_BLAST_IMPULSE, ...impulse }\n const chest: Vec3 = [feet[0], feet[1] + CHEST, feet[2]]\n const dx = chest[0] - blast.point[0]\n const dy = chest[1] - blast.point[1]\n const dz = chest[2] - blast.point[2]\n const distance = Math.hypot(dx, dy, dz)\n if (!(blast.radius > 0) || distance > blast.radius) return undefined\n const reach = Math.max(0, Math.min(1, blast.reach ?? 1))\n const tnt = blast.tnt ?? blast.energy * o.tntPerJoule\n let speed = blastBodySpeed(tnt, distance, mass, reach, o)\n const minSpeed = impulse.minSpeed ?? options?.minSpeed ?? o.minSpeed\n if (speed < minSpeed) speed = 0\n const struck = reach >= FRAGMENT_COVER\n const wounded = struck && blast.lethalRadius !== undefined && distance <= blast.lethalRadius\n const edge = 1 - distance / blast.radius\n const stunSeconds = struck ? STUN_SECONDS * edge * edge * reach : 0\n if (speed === 0 && !wounded && stunSeconds < 0.25) return undefined\n // straight above or below the centre there is no \"away\": the push goes up\n const horizontal = Math.hypot(dx, dz)\n const away: Vec3 = horizontal > 1e-4 ? [dx / horizontal, o.lift, dz / horizontal] : [0, 1, 0]\n const length = Math.hypot(away[0], away[1], away[2])\n const velocity: [number, number, number] = [(away[0] / length) * speed, (away[1] / length) * speed, (away[2] / length) * speed]\n return {\n velocity,\n airborne: velocity[1] > 0.5,\n lying: 0,\n lieLeft: wounded ? WOUNDED_LIE_SECONDS : 0,\n away: horizontal > 1e-4 ? [dx / horizontal, 0, dz / horizontal] : [0, 0, 0],\n down: speed >= KNOCK_DOWN_SPEED || wounded,\n ...(wounded ? { wounded } : {}),\n ...(stunSeconds >= 0.25 ? { stunSeconds } : {}),\n }\n}\n\n/**\n * Moves a body one step: it slides on the ground and slows, falls when it was knocked down, lies a while and gets up (a dead body stays down). Moves `position` in place; returns true when the\n * body is on its feet and still, and the knock can be dropped.\n */\nexport function stepKnock(knock: Knock, position: { x: number; y: number; z: number }, dt: number, groundAt: (x: number, z: number) => number, alive: boolean): boolean {\n const v = knock.velocity\n position.x += v[0] * dt\n position.z += v[2] * dt\n const ground = groundAt(position.x, position.z)\n if (knock.airborne || position.y > ground + 1e-4) {\n v[1] -= GRAVITY * dt\n position.y += v[1] * dt\n if (position.y <= ground) {\n position.y = ground\n v[1] = 0\n knock.airborne = false\n }\n } else position.y = ground\n const horizontal = Math.hypot(v[0], v[2])\n if (!knock.airborne && horizontal > 0) {\n const slowed = Math.max(0, horizontal - GROUND_FRICTION * dt)\n v[0] *= slowed / horizontal\n v[2] *= slowed / horizontal\n }\n const moving = horizontal > 0.05 || knock.airborne\n if (knock.down) {\n // a body floored where it stands (its wounds, not the push) still takes the time of a fall to reach the ground\n if (moving || knock.lying < 1) knock.lying = Math.min(1, knock.lying + dt / FALL_SECONDS)\n else if (alive) {\n knock.lieLeft = knock.lieLeft > 0 ? knock.lieLeft : LIE_SECONDS + Math.hypot(v[0], v[2]) * STAND_UP_PER_SPEED\n knock.lieLeft -= dt\n if (knock.lieLeft <= 0) knock.down = false\n }\n } else if (knock.lying > 0) knock.lying = Math.max(0, knock.lying - dt / RISE_SECONDS)\n return !moving && !knock.down && knock.lying === 0\n}\n","export interface CostOptions {\n /** Reads of `state()` and of `describe()` to time. Default 10 000. */\n reads?: number\n /** Ceiling, in milliseconds, for all the reads of one method. Default 50 (5 microseconds per read). */\n ceilingMs?: number\n /** Ceiling of heap growth, in bytes, over all the reads of one method (only where `process.memoryUsage` exists). Default 1 MiB. */\n allocationBytes?: number\n}\n\nconst DEFAULT_COST_READS = 10_000\nconst DEFAULT_COST_CEILING_MS = 50\nconst DEFAULT_COST_ALLOCATION_BYTES = 1024 * 1024\n\nconst heapUsedBytes = (): number | undefined => {\n const nodeProcess = (globalThis as { process?: { memoryUsage?: () => { heapUsed: number } } }).process\n return nodeProcess?.memoryUsage?.().heapUsed\n}\n\ntype CheckReadCostParams = { readonly name: string; readonly read: () => unknown; readonly options: CostOptions; readonly problems: string[] }\n\nexport const checkReadCost = ({ name, read, options, problems }: CheckReadCostParams): void => {\n const reads = options.reads ?? DEFAULT_COST_READS\n const ceilingMs = options.ceilingMs ?? DEFAULT_COST_CEILING_MS\n const allocationBytes = options.allocationBytes ?? DEFAULT_COST_ALLOCATION_BYTES\n if (read() !== read()) problems.push(`${name}() returns a new object on each read: cache it between changes (cost contract)`)\n // a first pass lets the engine compile and optimise the loop: that code and its feedback are heap too, and would read as the method allocating\n for (let i = 0; i < reads; i++) read()\n const heapBefore = heapUsedBytes()\n const started = performance.now()\n for (let i = 0; i < reads; i++) read()\n const elapsedMs = performance.now() - started\n const heapAfter = heapUsedBytes()\n if (elapsedMs > ceilingMs) problems.push(`${name}() took ${elapsedMs.toFixed(1)} ms for ${reads} reads, over the ${ceilingMs} ms ceiling (cost contract)`)\n if (heapBefore !== undefined && heapAfter !== undefined && heapAfter - heapBefore > allocationBytes)\n problems.push(`${name}() grew the heap by ${heapAfter - heapBefore} bytes over ${reads} reads, over the ${allocationBytes} byte ceiling (cost contract)`)\n}\n","import { checkReadCost } from './impact-cost'\nimport type { CostOptions } from './impact-cost'\nimport type { DamageType, DestructibleSpec, DestructionEvent, Hit, JointSpec, ObjectState, ToolKind, Vec3 } from './destruction'\n\n/**\n * What a game module implements for an object whose behaviour it owns (a vehicle with its own panel deformation, a creature, a machine). The\n * world (`DestructionWorld.register`) hosts it next to its own objects: raycast, shoot, explode, hit, disassemble, extinguish, update, state,\n * reset and `on` all route to it.\n *\n * Space: the impl receives hits in WORLD space and returns events in WORLD space (it owns its transform; the transform given to `register` or\n * `setTransform` is only used by the world for raycasts and blast range). Every event must carry `object === id` and name parts listed by\n * `describe()`.\n */\n/** World-space ray for `DestructibleImpl.surfaceRay`. */\nexport interface SurfaceRay {\n readonly origin: Vec3\n readonly direction: Vec3\n readonly maxDistance: number\n ignored?(part: string): boolean\n}\n\nexport interface SurfaceRayHit {\n readonly part: string\n readonly point: Vec3\n readonly normal: Vec3\n readonly distance: number\n /** Thickness the ray crosses in this part, metres. */\n readonly pathLen: number\n}\n\nexport interface DestructibleImpl {\n readonly id: string\n readonly name?: string\n /**\n * Parts, joints and structure in the impl's local space, for raycast, inspector and log.\n * COST CONTRACT (D8): called per frame by the renderer, so it is O(1), allocation-free and cached between changes: the same object comes\n * back until a hit, tool, fire or pose change alters it. `checkDestructible(impl, { cost: true })` measures it.\n */\n describe(): DestructibleSpec\n /** Apply one hit (see `IMPACT_CONTRACT` for the fields per type) and return the events it caused. */\n hit(hit: Hit): DestructionEvent[]\n /**\n * The real surface a ray meets, for an impl whose drawn shape is not its boxes (a car body): the boxes of `describe()` only say where to look, and a shot that crosses\n * them without touching the drawn surface goes on. `ignored` answers for the parts the round already went through. Null when the ray meets nothing.\n */\n surfaceRay?(ray: SurfaceRay): SurfaceRayHit | null\n /** Advance fire, melting, collapse and other timed behaviour. */\n update?(dt: number): DestructionEvent[]\n /**\n * COST CONTRACT (D8): the host reads it per part per frame, so it is O(1), allocation-free and cached between changes (see `describe()`).\n * The cached object is shared with every reader: treat it as read-only, copy before changing it.\n */\n state(): Readonly<ObjectState>\n reset?(): void\n /** The world took the object out (`DestructionWorld.remove`): the impl lets go of what it joined (shared queues, listeners). */\n dispose?(): void\n /** Same contract as `DestructionWorld.disassemble`: progress 0..1, or -1 when the tool cannot undo the joint. */\n disassemble?(jointId: string, tool: ToolKind, dt: number): number\n extinguish?(partId?: string): void\n /** Optional: an animation moved a part; `position` / `rotation` (Euler XYZ, radians) are object-local. Keep raycast and hit shapes in sync. */\n setPartPose?(partId: string, position: Vec3, rotation?: Vec3): void\n /** Plain JSON of the impl's own state, stored in `DestructionSnapshot.impls`. */\n serialize?(): unknown\n load?(data: unknown): void\n}\n\n/** The fields a weapon or hazard must put in a `Hit`, per damage type. THE impact contract. */\nexport interface ImpactContract {\n required: string[]\n optional: string[]\n note: string\n}\n\nexport const IMPACT_CONTRACT: Record<DamageType, ImpactContract> = {\n ballistic: { required: ['type', 'point', 'direction', 'energy', 'caliber'], optional: ['space', 'source'], note: 'bullets and pellets: joules and diameter in millimetres' },\n blunt: { required: ['type', 'point', 'energy'], optional: ['direction', 'radius', 'space', 'source'], note: 'contact radius in metres (default 3 cm)' },\n slash: { required: ['type', 'point', 'energy'], optional: ['direction', 'radius', 'space', 'source'], note: 'axes, knives, machetes' },\n explosive: { required: ['type', 'point', 'energy', 'radius'], optional: ['lethalRadius', 'space', 'source'], note: 'point is the centre of the blast, radius the range in metres' },\n fire: { required: ['type', 'point', 'temperature', 'radius'], optional: ['duration', 'space', 'source'], note: 'flame temperature in °C; duration in seconds (call every frame with `dt`)' },\n heat: { required: ['type', 'point', 'temperature', 'radius'], optional: ['duration', 'space', 'source'], note: 'heat without flame (cutting torch, hot plate)' },\n}\n\n/** Problems with a hit according to the impact contract (empty when it is valid). */\nexport const checkHit = (hit: Hit): string[] => {\n const c = IMPACT_CONTRACT[hit.type]\n if (!c) return [`unknown damage type \"${String(hit.type)}\"`]\n const out: string[] = []\n for (const f of c.required) {\n const v = (hit as unknown as Record<string, unknown>)[f]\n if (v === undefined || (Array.isArray(v) ? v.some((n) => !Number.isFinite(n)) : typeof v === 'number' && !Number.isFinite(v))) out.push(`${hit.type} hit needs \"${f}\"`)\n }\n return out\n}\n\nexport interface CheckOptions {\n /**\n * Ids of every material the impl may use (the contracts package does not know the materials of your world: pass the ids of\n * `world.materials`, or the built-in plus your own). When omitted, the material check is skipped.\n */\n materials?: Iterable<string>\n /** Opt-in cost measurement (D8): `true` for the defaults, or the limits to use. */\n cost?: boolean | CostOptions\n}\n\nconst finiteJson = (v: unknown, path: string, out: string[]) => {\n if (typeof v === 'number') {\n if (!Number.isFinite(v)) out.push(`${path} is not a finite number`)\n } else if (Array.isArray(v)) v.forEach((x, i) => finiteJson(x, `${path}[${i}]`, out))\n else if (v && typeof v === 'object') for (const [k, x] of Object.entries(v)) finiteJson(x, `${path}.${k}`, out)\n else if (typeof v === 'function' || typeof v === 'symbol' || typeof v === 'bigint') out.push(`${path} is not JSON`)\n}\n\n/**\n * Contract checker for the test room (it needs no world): `describe()` is valid, a 500 J ballistic hit at a part centre returns well-formed events of this impl, `state()`\n * matches `describe()`, hit points never go negative, `update(0.016)` returns an array and every event is JSON-serializable. It hits the impl\n * (and calls `reset()` afterwards when there is one). With `options.cost` it also times `state()` and `describe()` (the cost contract of D8).\n */\nexport const checkDestructible = (impl: DestructibleImpl, options: CheckOptions = {}): { ok: boolean; problems: string[] } => {\n const problems: string[] = []\n const known = options.materials ? new Set(options.materials) : null\n let spec: DestructibleSpec | undefined\n try {\n spec = impl.describe()\n } catch (e) {\n return { ok: false, problems: [`describe() threw: ${String(e)}`] }\n }\n if (!spec || !Array.isArray(spec.parts) || spec.parts.length === 0) return { ok: false, problems: ['describe() must return at least one part'] }\n const ids = new Set<string>()\n for (const p of spec.parts) {\n if (ids.has(p.id)) problems.push(`duplicate part id \"${p.id}\"`)\n ids.add(p.id)\n if (known && !known.has(p.material)) problems.push(`part \"${p.id}\" has unknown material \"${p.material}\"`)\n if (!p.shape || !['box', 'cylinder', 'sphere'].includes(p.shape.kind)) problems.push(`part \"${p.id}\" has no valid shape`)\n if (!Array.isArray(p.position) || p.position.length !== 3) problems.push(`part \"${p.id}\" has no position`)\n }\n const jointIds = new Set<string>()\n for (const j of (spec.joints ?? []) as JointSpec[]) {\n if (!ids.has(j.a) || !ids.has(j.b)) problems.push(`joint \"${j.id ?? `${j.a}~${j.b}`}\" references an unknown part`)\n const jid = j.id ?? `${j.a}~${j.b}`\n if (jointIds.has(jid)) problems.push(`duplicate joint id \"${jid}\"`)\n jointIds.add(jid)\n }\n const checkState = (label: string) => {\n let st: ObjectState\n try {\n st = impl.state()\n } catch (e) {\n problems.push(`state() threw ${label}: ${String(e)}`)\n return\n }\n const sids = st.parts.map((p) => p.id).sort().join(',')\n if (sids !== [...ids].sort().join(',')) problems.push(`state() part ids do not match describe() ${label}`)\n for (const p of st.parts) if (!(p.hp >= 0)) problems.push(`part \"${p.id}\" has negative or invalid hp ${label}`)\n }\n const checkEvents = (events: unknown, label: string) => {\n if (!Array.isArray(events)) {\n problems.push(`${label} must return an array`)\n return\n }\n const evs = events as DestructionEvent[]\n evs.forEach((e, i) => {\n if (e.object !== impl.id) problems.push(`${label} event ${i} (${e.type}) has object \"${String(e.object)}\", expected \"${impl.id}\"`)\n const part = (e as { part?: string }).part\n if (part !== undefined && !ids.has(part)) problems.push(`${label} event ${i} (${e.type}) names unknown part \"${part}\"`)\n })\n const bad: string[] = []\n finiteJson(evs, 'events', bad)\n problems.push(...bad.map((b) => `${label}: ${b}`))\n try {\n const s = JSON.stringify(evs)\n if (JSON.stringify(JSON.parse(s)) !== s) problems.push(`${label} events do not survive a JSON round trip`)\n } catch (e) {\n problems.push(`${label} events are not JSON-serializable: ${String(e)}`)\n }\n }\n checkState('before the hit')\n const target = spec.parts[0]\n try {\n const hit: Hit = { type: 'ballistic', point: [target.position[0], target.position[1], target.position[2]], direction: [0, 0, -1], energy: 500, caliber: 9 }\n checkEvents(impl.hit(hit), 'hit()')\n } catch (e) {\n problems.push(`hit() threw: ${String(e)}`)\n }\n checkState('after the hit')\n if (impl.update) {\n try {\n checkEvents(impl.update(0.016), 'update()')\n } catch (e) {\n problems.push(`update() threw: ${String(e)}`)\n }\n checkState('after update')\n }\n try {\n impl.reset?.()\n } catch (e) {\n problems.push(`reset() threw: ${String(e)}`)\n }\n if (options.cost) {\n const cost = options.cost === true ? {} : options.cost\n try {\n checkReadCost({ name: 'state', read: () => impl.state(), options: cost, problems })\n checkReadCost({ name: 'describe', read: () => impl.describe(), options: cost, problems })\n } catch (e) {\n problems.push(`cost measurement threw: ${String(e)}`)\n }\n }\n return { ok: problems.length === 0, problems }\n}\n","/** The slots of a body an item can cover (the item slot vocabulary: game-items imports it from here). */\nexport type BodySlot = 'head' | 'eyes' | 'torso' | 'arms' | 'hands' | 'legs' | 'feet' | 'over'\n\n/** The 11 segments of the detailed body model (the Arsenal ids). */\nexport type SegmentKind = 'head' | 'jaw' | 'neck' | 'torso' | 'pelvis' | 'upperArm' | 'forearm' | 'hand' | 'thigh' | 'shin' | 'foot'\n\n/** The 6 regions of the crowd model (the game regions 0..5). */\nexport type BodyRegion = 'head' | 'torso' | 'armL' | 'armR' | 'legL' | 'legR'\n\n/** Bits of the `missing` mask of a body. The instanced body shader reads them, so the values are part of the contract. */\nexport const MISSING_BITS = {\n head: 1,\n armL: 2,\n armR: 4,\n legL: 8,\n legR: 16,\n armLBroken: 32,\n armRBroken: 64,\n legLBroken: 128,\n legRBroken: 256,\n jaw: 512,\n} as const\n","import type { DestructibleSpec, PartSpec } from './destruction'\n\nexport interface CheckSpecOptions {\n /** Known material ids. When given, every part's material must be one of them. */\n materials?: Iterable<string>\n}\n\nexport interface SpecReport {\n /** False when there is at least one problem (warnings do not count). */\n ok: boolean\n problems: string[]\n /** Things that are legal but probably wrong, e.g. structural parts that cannot reach an anchored part. */\n warnings: string[]\n}\n\nconst STRUCTURE_ROLES = ['foundation', 'column', 'beam', 'slab', 'wall', 'partition', 'roof', 'stair', 'none']\nconst BEARS = [true, false, 'a_on_b', 'b_on_a']\n\nconst finite = (n: unknown): n is number => typeof n === 'number' && Number.isFinite(n)\nconst positive = (n: unknown): boolean => finite(n) && n > 0\nconst vec3 = (v: unknown): boolean => Array.isArray(v) && v.length === 3 && v.every(finite)\nconst nonNegative = (n: unknown): boolean => n === undefined || (finite(n) && n >= 0)\n\nconst FACES = ['both', '+', '-']\nconst BONDS = ['running', 'stack']\n\n/** Layers, reinforcement, units and breach: known materials, finite positive sizes. */\nconst checkStagedFields = (p: PartSpec | undefined, label: string, materials: Set<string> | null, problems: string[]): void => {\n const known = (m: unknown, what: string): void => {\n if (materials && !materials.has(m as string)) problems.push(`${label} ${what} has unknown material \"${String(m)}\"`)\n }\n ;(p?.layers ?? []).forEach((l, i) => {\n known(l?.material, `layer ${i}`)\n if (!positive(l?.thickness)) problems.push(`${label} layer ${i} thickness must be finite and positive`)\n if (!FACES.includes(l?.faces)) problems.push(`${label} layer ${i} has an invalid faces \"${String(l?.faces)}\"`)\n })\n const r = p?.reinforcement\n if (r) {\n known(r.material, 'reinforcement')\n if (!positive(r.spacing) || !positive(r.diameter)) problems.push(`${label} reinforcement spacing and diameter must be finite and positive`)\n if (r.residualCapacity !== undefined && !(finite(r.residualCapacity) && r.residualCapacity >= 0 && r.residualCapacity <= 1)) problems.push(`${label} reinforcement residualCapacity must be between 0 and 1`)\n }\n const u = p?.units\n if (u) {\n known(u.mortar, 'units mortar')\n if (!vec3(u.size) || !u.size.every(positive)) problems.push(`${label} units size must be three finite positive numbers`)\n if (!BONDS.includes(u.bond)) problems.push(`${label} units has an invalid bond \"${String(u.bond)}\"`)\n }\n if (p?.breach && p.breach.area !== undefined && !positive(p.breach.area)) problems.push(`${label} breach area must be finite and positive`)\n}\n\n/**\n * Static checks on a `DestructibleSpec` (no world needed), for items that give a `spec` instead of an `impl`: unique part ids, known materials\n * (when `options.materials` is given), joints that reference existing, different parts, finite positive sizes, non-negative mass / hp / capacity,\n * valid structure roles and `bears` directions. A structural part with no path of joints to an anchored part is a warning, not a problem: it\n * is allowed, it just falls.\n */\nexport const checkSpec = (spec: DestructibleSpec, options: CheckSpecOptions = {}): SpecReport => {\n const problems: string[] = []\n const warnings: string[] = []\n const materials = options.materials ? new Set(options.materials) : null\n const parts = Array.isArray(spec?.parts) ? spec.parts : []\n if (!Array.isArray(spec?.parts) || parts.length === 0) problems.push('the spec needs at least one part')\n const ids = new Set<string>()\n for (const p of parts) {\n const id = String(p?.id ?? '')\n const label = `part \"${id}\"`\n if (id === '') problems.push('a part has no id')\n else if (ids.has(id)) problems.push(`duplicate part id \"${id}\"`)\n ids.add(id)\n if (materials && !materials.has(p?.material)) problems.push(`${label} has unknown material \"${String(p?.material)}\"`)\n else if (typeof p?.material !== 'string' || p.material === '') problems.push(`${label} needs a material`)\n const s = p?.shape\n if (s?.kind === 'box') {\n if (!Array.isArray(s.size) || s.size.length !== 3 || !s.size.every(positive)) problems.push(`${label} box size must be three finite positive numbers`)\n } else if (s?.kind === 'cylinder') {\n if (!positive(s.radius) || !positive(s.height)) problems.push(`${label} cylinder radius and height must be finite and positive`)\n } else if (s?.kind === 'sphere') {\n if (!positive(s.radius)) problems.push(`${label} sphere radius must be finite and positive`)\n } else problems.push(`${label} has an unknown shape`)\n if (!vec3(p?.position)) problems.push(`${label} needs a finite position [x, y, z]`)\n if (p?.rotation !== undefined && !vec3(p.rotation)) problems.push(`${label} has an invalid rotation`)\n for (const f of ['mass', 'hp', 'thickness'] as const) if (!nonNegative(p?.[f])) problems.push(`${label} ${f} must be a finite number >= 0`)\n if (p?.fill !== undefined && !(finite(p.fill) && p.fill >= 0 && p.fill <= 1)) problems.push(`${label} fill must be between 0 and 1`)\n if (p?.structure) {\n if (!STRUCTURE_ROLES.includes(p.structure.role)) problems.push(`${label} has an invalid structure role \"${String(p.structure.role)}\"`)\n if (!nonNegative(p.structure.capacity)) problems.push(`${label} structure capacity must be a finite number >= 0`)\n }\n if (p?.contains && !nonNegative(p.contains.liters)) problems.push(`${label} contains.liters must be a finite number >= 0`)\n checkStagedFields(p, label, materials, problems)\n }\n\n const jointIds = new Set<string>()\n const edges: Array<[string, string]> = []\n for (const j of spec?.joints ?? []) {\n const id = j.id ?? `${j.a}~${j.b}`\n const label = `joint \"${id}\"`\n if (jointIds.has(id)) problems.push(`duplicate joint id \"${id}\"`)\n jointIds.add(id)\n if (!ids.has(j.a)) problems.push(`${label} references unknown part \"${String(j.a)}\"`)\n if (!ids.has(j.b)) problems.push(`${label} references unknown part \"${String(j.b)}\"`)\n if (j.a === j.b) problems.push(`${label} joins part \"${String(j.a)}\" to itself`)\n if (j.bears !== undefined && !BEARS.includes(j.bears)) problems.push(`${label} has an invalid bears \"${String(j.bears)}\"`)\n if (!nonNegative(j.strength)) problems.push(`${label} strength must be a finite number >= 0`)\n if (!nonNegative(j.seconds)) problems.push(`${label} seconds must be a finite number >= 0`)\n if (j.bears !== false && ids.has(j.a) && ids.has(j.b) && j.a !== j.b) edges.push([j.a, j.b])\n }\n\n // structural parts must reach an anchored part over the joints (only when the spec lists its joints: otherwise they are inferred later)\n const structural = parts.filter((p) => p?.structure && p.structure.role !== 'none')\n if (structural.length && spec.joints) {\n const anchored = parts.filter((p) => p.anchored || p.structure?.role === 'foundation').map((p) => p.id)\n const adj = new Map<string, string[]>()\n for (const [a, b] of edges) {\n adj.set(a, [...(adj.get(a) ?? []), b])\n adj.set(b, [...(adj.get(b) ?? []), a])\n }\n const seen = new Set(anchored)\n const queue = [...anchored]\n while (queue.length) {\n for (const n of adj.get(queue.pop()!) ?? []) {\n if (seen.has(n)) continue\n seen.add(n)\n queue.push(n)\n }\n }\n const lost = structural.filter((p) => !seen.has(p.id)).map((p) => p.id)\n if (lost.length) warnings.push(`structural part(s) ${lost.map((i) => `\"${i}\"`).join(', ')} cannot reach an anchored part, so they will fall`)\n }\n return { ok: problems.length === 0, problems, warnings }\n}\n","import type { BallisticExtras, Hit, HitIgnition, Vec3 } from './destruction'\n\n/**\n * A ballistic shot (one per pellet) as a weapon module produces it. Plain data: any damage model can consume it (`shotToHit` makes the `Hit`).\n */\nexport interface Shot {\n origin: Vec3\n /** Unit vector. */\n direction: Vec3\n /** Muzzle energy in joules. */\n energy: number\n /** Bullet diameter in millimetres. */\n caliber: number\n /** snake_case calibre id, e.g. `9x19`. */\n caliberId: string\n /** Muzzle speed in m/s. */\n speed: number\n /** Projectile mass in grams (one pellet for a shotgun load). */\n mass?: number\n /** Energy at a distance in metres, with the calibre's drag. */\n energyAt(distance: number): number\n pellet: number\n /** snake_case weapon id. */\n weapon: string\n source?: string\n /** Kind of the round that was loaded (`fmj`, `hp`, `ap`, `buck`, `slug`, `incendiary`, `tracer`...). */\n ammo?: string\n /** Millimetres of steel, concrete, brick and wood the round goes through at the muzzle (the ammo table of the weapons module). `shotToHit` scales it by the energy left. */\n penetration?: Readonly<Record<string, number>>\n incendiary?: boolean\n tracer?: boolean\n /** Chances that the round lights flammable vapour or fuel. */\n ignition?: HitIgnition\n}\n\n/**\n * The fields of the round that a damage model takes besides the geometry: the penetration by material scaled by the energy that is left at `distance`\n * (linear in the energy: half the energy goes through half as much), the incendiary and tracer flags and the ignition chances. Pass it as the sixth\n * argument of `DestructionWorld.shoot` or spread it in a `Hit`.\n */\nexport const shotExtras = (shot: Shot, distance = 0): BallisticExtras => {\n const out: { -readonly [K in keyof BallisticExtras]: BallisticExtras[K] } = {}\n if (shot.penetration) {\n const k = shot.energy > 0 ? Math.min(1, Math.max(0, shot.energyAt(distance) / shot.energy)) : 1\n const scaled: Record<string, number> = {}\n for (const key of Object.keys(shot.penetration)) scaled[key] = shot.penetration[key]! * k\n out.penetration = scaled\n }\n if (shot.incendiary) out.incendiary = true\n if (shot.tracer) out.tracer = true\n if (shot.ignition) out.ignition = { spark: shot.ignition.spark, flame: shot.ignition.flame }\n return out\n}\n\n/** One use of a non-firearm (melee swing, flame tick, heat tick, throw, tool tick, extinguisher tick). */\nexport type Use =\n | { kind: 'melee'; damage: 'blunt' | 'slash'; energy: number; radius: number; reach: number; origin: Vec3; direction: Vec3 }\n | { kind: 'flame'; temperature: number; radius: number; range: number; cone: boolean; duration: number; origin: Vec3; direction: Vec3 }\n | { kind: 'heat'; temperature: number; radius: number; range: number; duration: number; origin: Vec3; direction: Vec3 }\n | {\n kind: 'throw'\n energy: number\n radius: number\n fuse: number\n velocity: Vec3\n origin: Vec3\n fire?: { temperature: number; radius: number; seconds: number }\n /** Explosive id (`m67`, `molotov`, `pg7v`...): the host looks the numbers up in the arsenal. Absent on a plain thrown object. */\n explosive?: string\n /** What sets it off: `timer` after `fuse` seconds, `impact` on the first hit once armed. */\n trigger?: 'timer' | 'impact'\n /** Metres the projectile must fly before an impact sets it off; closer, it hits without exploding (rocket launchers). */\n armDistance?: number\n /** Radius in metres of the wounding area (`radius` is the lethal one). */\n injuryRadius?: number\n /** Seconds the blast stuns whoever is inside the injury radius (flashbang). */\n stunSeconds?: number\n /** Seconds the thrower held it after pulling the pin: `fuse` is what was left of the full fuse when it left the hand. */\n cooked?: number\n /** Went off in the thrower's hand (held past the fuse): no velocity, `fuse` is 0 and the blast is at `origin`. */\n inHand?: boolean\n }\n /** An explosive set down where the trigger is pulled (mine, C4, charge): it waits for its trigger instead of flying. */\n | {\n kind: 'place'\n explosive: string\n /** `wire`: something trips it; `remote`: the player fires it; `timer`: after `fuse` seconds. */\n triggers: readonly ('timer' | 'wire' | 'remote')[]\n energy: number\n /** Lethal radius in metres. */\n radius: number\n injuryRadius: number\n /** Cone the blast goes through, in front of `direction` (mine); absent for a blast in every direction. */\n cone?: { halfAngleDeg: number; length: number }\n /** The wire a `wire` charge stretches from where it is set down: metres out, degrees from its facing (counter-clockwise from above) and metres above it. */\n tripwire?: { length: number; angleDeg: number; height: number }\n fuse?: number\n origin: Vec3\n direction: Vec3\n }\n /** The blast behind a rocket launcher: dangerous inside `radius` metres, in the cone around `direction` (pointing away from the target). */\n | { kind: 'backblast'; radius: number; halfAngleDeg: number; origin: Vec3; direction: Vec3 }\n | { kind: 'tool'; tool: string; reach: number; duration: number; origin: Vec3; direction: Vec3 }\n | { kind: 'extinguish'; range: number; coolC: number; duration: number; origin: Vec3; direction: Vec3 }\n | { kind: 'hand'; reach: number; origin: Vec3; direction: Vec3 }\n\n/**\n * The `Hit` a ballistic shot makes at `point` (world space): the energy is what is left of the shot at that distance from its origin\n * (`shot.energyAt`), the direction and calibre come from the shot. `normal` (the surface normal at the point, when the caller knows it) does not\n * change the hit; it is accepted so that callers can pass what their raycast returned and so that a stricter implementation could use it.\n */\nexport const shotToHit = (shot: Shot, point: Vec3, normal?: Vec3): Hit => {\n void normal\n const dx = point[0] - shot.origin[0]\n const dy = point[1] - shot.origin[1]\n const dz = point[2] - shot.origin[2]\n const distance = Math.hypot(dx, dy, dz)\n return {\n type: 'ballistic',\n point: [point[0], point[1], point[2]],\n direction: [shot.direction[0], shot.direction[1], shot.direction[2]],\n energy: shot.energyAt(distance),\n caliber: shot.caliber,\n ...(shot.mass === undefined ? {} : { mass: shot.mass }),\n ...shotExtras(shot, distance),\n source: shot.source ?? shot.weapon,\n }\n}\n\n/**\n * The `Hit` a non-firearm `Use` makes at `point` (world space), or `null` for uses that do not damage by themselves.\n *\n * - `melee` -> `blunt` / `slash` with the swing's `energy`, contact `radius` and `direction`;\n * - `flame` -> `fire` and `heat` -> `heat`, with `temperature`, `duration` and a contact radius of at most 0.3 m (one ray of the cone);\n * - `throw` -> the blast (`explosive`, `energy`, `radius`, centred on `point`, the place it detonates). A thrown item with no blast energy but\n * a `fire` patch (a molotov) gives that patch as a `fire` hit (`temperature`, `radius`, `seconds` as `duration`). No energy and no fire -> `null`;\n * - `tool`, `extinguish` and `hand` -> `null` (tools work on joints, the extinguisher cools, the hand only grabs).\n *\n * `normal` does not change the hit (same as `shotToHit`).\n */\nexport const useToHit = (use: Use, point: Vec3, normal?: Vec3): Hit | null => {\n void normal\n const p: Vec3 = [point[0], point[1], point[2]]\n switch (use.kind) {\n case 'melee':\n return { type: use.damage, point: p, direction: [use.direction[0], use.direction[1], use.direction[2]], energy: use.energy, radius: use.radius }\n case 'flame':\n case 'heat':\n return {\n type: use.kind === 'flame' ? 'fire' : 'heat',\n point: p,\n direction: [use.direction[0], use.direction[1], use.direction[2]],\n temperature: use.temperature,\n radius: Math.min(use.radius, 0.3),\n duration: use.duration,\n }\n case 'throw':\n if (use.energy > 0) return { type: 'explosive', point: p, energy: use.energy, radius: use.radius }\n if (use.fire) return { type: 'fire', point: p, temperature: use.fire.temperature, radius: use.fire.radius, duration: use.fire.seconds }\n return null\n default:\n return null\n }\n}\n","import type { Hit } from './destruction'\n\n/**\n * How a container of flammable fluid (a fuel tank, a jerrycan, a gas bottle) is set alight by what hits it. A normal bullet that goes through only\n * makes a leak; the leak lights when something supplies a flame: a spark of a hard round on metal, an incendiary or tracer round, a fire nearby\n * or a blast. These are the numbers of the target side; the numbers of the round (core, jacket, incendiary, tracer) are in the weapons module\n * (`rules.ignition` of `@adatechnology/game-weapons`) and reach the target in `Hit.ignition`. Every number is a default to be changed per game.\n */\nexport interface IgnitionRules {\n /**\n * How much of the spark and of the flame of a round reaches the vapour, by how full the container is: points `[filled, factor]`, `filled` 0..1\n * (litres over capacity), `factor` 0..1, linear between points. A full container holds a mixture too rich to burn (the vapour has no air) and\n * mostly leaks; a nearly empty one holds the vapour/air mixture of a flammable range and lights the easiest; a dry one has little vapour left.\n * Default: 0.6 empty, 1 at 10 percent, 0.6 at 35, 0.3 at 65, 0.15 at 90 and 0.12 full.\n */\n fillCurve: ReadonlyArray<readonly [number, number]>\n /** 0..1: how much of the fill factor also weighs on the flame of an incendiary or tracer (1: as much as on a spark; 0: not at all). Default 0.5. */\n flameFillInfluence: number\n /** Chance of the flame of a round that is flagged `incendiary` or `tracer` but carries no `ignition` numbers (a host that passes only the flags). */\n flameDefaults: { incendiary: number; tracer: number }\n /**\n * What a spark needs to strike: a factor 0..1 by material id of the struck part. A material that is not listed but is metal takes\n * `sparkMetalDefault`; a part that is not metal never sparks. Default: steel and iron 1, aluminium 0.25.\n */\n sparkTarget: Readonly<Record<string, number>>\n sparkMetalDefault: number\n /**\n * Factor 0..1 on the spark by fluid (default 1): diesel has a flash point of 52 degrees C and more, so a spark that lights petrol vapour does\n * not light it (a flame does). Default: diesel 0.15.\n */\n sparkFluid: Readonly<Record<string, number>>\n /** A leaking container of flammable fluid within `radius` metres of a burning part or a burning fuel stain lights at `chancePerSecond`. */\n fireNear: { radius: number; chancePerSecond: number }\n}\n\n/** Defaults of `IgnitionRules`. */\nexport const DEFAULT_IGNITION_RULES: IgnitionRules = {\n fillCurve: [\n [0, 0.6],\n [0.1, 1],\n [0.35, 0.6],\n [0.65, 0.3],\n [0.9, 0.15],\n [1, 0.12],\n ],\n flameFillInfluence: 0.5,\n flameDefaults: { incendiary: 0.7, tracer: 0.2 },\n sparkTarget: { steel: 1, iron: 1, aluminium: 0.25 },\n sparkMetalDefault: 0.5,\n sparkFluid: { diesel: 0.15 },\n fireNear: { radius: 1.5, chancePerSecond: 0.5 },\n}\n\n/** Merges a partial `IgnitionRules` over the defaults (an object is merged one level deep). */\nexport const resolveIgnitionRules = (rules?: Partial<IgnitionRules>): IgnitionRules => ({\n ...DEFAULT_IGNITION_RULES,\n ...rules,\n flameDefaults: { ...DEFAULT_IGNITION_RULES.flameDefaults, ...rules?.flameDefaults },\n sparkTarget: { ...DEFAULT_IGNITION_RULES.sparkTarget, ...rules?.sparkTarget },\n sparkFluid: { ...DEFAULT_IGNITION_RULES.sparkFluid, ...rules?.sparkFluid },\n fireNear: { ...DEFAULT_IGNITION_RULES.fireNear, ...rules?.fireNear },\n})\n\n/** Linear interpolation of the fill curve at `fill` (0..1); outside the points it keeps the first or the last. */\nexport const fillFactor = (fill: number, curve: IgnitionRules['fillCurve'] = DEFAULT_IGNITION_RULES.fillCurve): number => {\n if (curve.length === 0) return 1\n const f = Math.min(1, Math.max(0, fill))\n if (f <= curve[0]![0]) return curve[0]![1]\n for (let i = 1; i < curve.length; i++) {\n const [x1, y1] = curve[i]!\n const [x0, y0] = curve[i - 1]!\n if (f <= x1) return x1 === x0 ? y1 : y0 + ((y1 - y0) * (f - x0)) / (x1 - x0)\n }\n return curve[curve.length - 1]![1]\n}\n\n/** The factor of what a spark strikes: 0 when the struck part is not metal. */\nexport const sparkTargetFactor = (materialId: string, metal: boolean, rules: IgnitionRules = DEFAULT_IGNITION_RULES): number =>\n !metal ? 0 : rules.sparkTarget[materialId] ?? rules.sparkMetalDefault\n\n/**\n * Chance that one round that went through the wall of a container lights the fluid that leaks: the spark (only on metal) and the flame (any\n * wall), each weighed by the fill of the container; the two are independent. 0 when the round carries nothing (`Hit.ignition` absent and not\n * flagged `incendiary` or `tracer`): a plain bullet on a plain wall only leaks.\n */\nexport const ignitionChance = (hit: Pick<Hit, 'incendiary' | 'tracer' | 'ignition'>, target: { sparkTarget: number; fill: number; fluid?: string }, rules: IgnitionRules = DEFAULT_IGNITION_RULES): number => {\n const k = fillFactor(target.fill, rules.fillCurve)\n const spark = (hit.ignition?.spark ?? 0) * target.sparkTarget * (target.fluid === undefined ? 1 : rules.sparkFluid[target.fluid] ?? 1) * k\n const flameBase = hit.ignition?.flame ?? Math.max(hit.incendiary ? rules.flameDefaults.incendiary : 0, hit.tracer ? rules.flameDefaults.tracer : 0)\n const flame = flameBase * (1 - rules.flameFillInfluence * (1 - k))\n return 1 - (1 - Math.min(1, Math.max(0, spark))) * (1 - Math.min(1, Math.max(0, flame)))\n}\n","/**\n * The seal of a closed container (a food tin, a drink can, a jar, a bottle with a cap): what opens it and whether a breach spoils what is inside.\n * Plain data and one pure check, shared by the item rules (`resolveUse` of game-items) and any host that lets the player open and eat (the test room).\n */\nexport interface SealSpec {\n /** Ids of the tools that open it (items or weapons: `can_opener`, `knife`); any one of them is enough. */\n readonly openWith?: readonly string[]\n /** It opens by hand (a pull tab, a ring pull, a screw cap): no tool needed. */\n readonly byHand?: boolean\n /** A hole through the container or a leak spoils what is inside: it can no longer be eaten or drunk. */\n readonly spoilsWhenBreached?: boolean\n}\n\n/** The state of one container: opened, holed through (a shot, a blade) or leaking. */\nexport interface SealState {\n readonly opened?: boolean\n readonly breached?: boolean\n readonly leaked?: boolean\n}\n\n/** Why a sealed container cannot be eaten or drunk now. */\nexport type SealBlock = 'sealed' | 'breached' | 'leaked'\n\n/** Whether it can be opened with the tools at hand, and with which one (undefined when it opens by hand). */\nexport const sealOpener = (seal: SealSpec, tools: Iterable<string>): { readonly ok: boolean; readonly tool?: string } => {\n if (seal.byHand || !seal.openWith || seal.openWith.length === 0) return { ok: true }\n const have = new Set(tools)\n const tool = seal.openWith.find((id) => have.has(id))\n return tool === undefined ? { ok: false } : { ok: true, tool }\n}\n\n/** What stops eating or drinking it now, or undefined when nothing does. A spoiled container (breached, leaked) is blocked even once open. */\nexport const sealBlock = (seal: SealSpec, state: SealState): SealBlock | undefined => {\n if (seal.spoilsWhenBreached && state.breached) return 'breached'\n if (seal.spoilsWhenBreached && state.leaked) return 'leaked'\n if (!state.opened) return 'sealed'\n return undefined\n}\n","/** How a hand turns something round in strokes (a screw cap, a tap handle, a knob): what `twistStroke` answers for one moment of the turning. */\nexport interface TwistStroke {\n /** Radians the wrist is turned from the middle of its stroke, positive the way the part turns. */\n readonly angle: number\n /** Radians the part has turned in all, from 0 to `strokes * reach`. */\n readonly turned: number\n /** How closed the fingers are, 0 open to 1 closed: closed on the part while it turns, open while the wrist turns back. */\n readonly grip: number\n /** 0..1, how far the hand rises off the part while the wrist turns back (1 is `TWIST_LIFT` metres). */\n readonly lift: number\n}\n\nexport interface TwistOptions {\n /** Strokes of the wrist. Default 3. */\n readonly strokes?: number\n /** Radians the part turns in one stroke (a wrist turns about a quarter round with its hold). Default 1.6. */\n readonly reach?: number\n}\n\n/** Metres a hand rises off the part between two strokes. */\nexport const TWIST_LIFT = 0.012\n/** Share of each stroke spent turning the part; the rest turns the wrist back open. */\nconst TURNING = 0.7\n\nconst smooth = (k: number): number => k * k * (3 - 2 * k)\n\n/**\n * The hand turning a part round at `share` (0..1) of the whole turning: in each stroke the wrist turns with the part, then lets go, rises a little and turns back.\n * Pure and the same for every host, so first and third person turn the same hand the same way.\n */\nexport const twistStroke = (share: number, options: TwistOptions = {}): TwistStroke => {\n const strokes = Math.max(1, Math.round(options.strokes ?? 3))\n const reach = options.reach ?? 1.6\n const along = Math.min(1, Math.max(0, share)) * strokes\n const index = Math.min(strokes - 1, Math.floor(along))\n const phase = along - index\n if (phase <= TURNING) {\n const k = smooth(phase / TURNING)\n return { angle: reach * (k - 0.5), turned: reach * (index + k), grip: 1, lift: 0 }\n }\n const k = (phase - TURNING) / (1 - TURNING)\n return { angle: reach * (0.5 - smooth(k)), turned: reach * (index + 1), grip: 0.35, lift: Math.sin(k * Math.PI) }\n}\n","import type { Vec3 } from './destruction'\n\n/** Pose of one part (or of the whole item) at a key: position offset in metres and Euler XYZ rotation in radians, both relative to the rest pose. */\nexport interface AnimationPose {\n position?: Vec3\n rotation?: Vec3\n}\n\nexport interface AnimationKey {\n /** Seconds from the start of the clip. */\n t: number\n /** Pose of the item's root. */\n position?: Vec3\n rotation?: Vec3\n /** Poses of named parts: the key is a destruction part id (a pistol `reload` moves `mag`; a car door `open` rotates `door_l`). */\n parts?: Record<string, AnimationPose>\n}\n\nexport interface AnimationEventSpec {\n /** Seconds from the start of the clip. */\n t: number\n /** snake_case type the host maps (`sound`, `eject_casing`, `door_latch`...). */\n type: string\n data?: Record<string, unknown>\n /**\n * Seconds of real time the event fires before `t`, not stretched with the clip: a recorded sound whose hit comes that long after its start\n * (the click of a magazine seating) starts early so the hit lands on `t`. The player passes the lead it could apply as `data.lead`.\n */\n lead?: number\n}\n\n/** A use animation a module brings with its item. Poses between keys are interpolated by the player (the test room does it linearly). */\nexport interface AnimationClipSpec {\n /** snake_case: `reload`, `open`, `drink`... Unique within the item. */\n id: string\n /** Seconds. */\n duration: number\n keys: AnimationKey[]\n events?: AnimationEventSpec[]\n}\n\n/** What an item exposes when it brings its own animations. */\nexport interface ItemAnimationContract {\n animations: AnimationClipSpec[]\n}\n\nconst finite3 = (v: unknown): boolean => Array.isArray(v) && v.length === 3 && v.every((n) => typeof n === 'number' && Number.isFinite(n))\n\n/**\n * Problems with a clip (empty when it is valid): a snake_case id, a positive finite duration, at least one key, key and event times inside\n * `[0, duration]` and not going backwards, finite vectors, every named part one of `partIds`, event types that are non-empty strings and\n * JSON-serializable event data.\n */\nexport const checkAnimation = (clip: AnimationClipSpec, partIds: Iterable<string>): { ok: boolean; problems: string[] } => {\n const problems: string[] = []\n const parts = new Set(partIds)\n const id = String(clip?.id ?? '')\n if (!/^[a-z][a-z0-9_]*$/.test(id)) problems.push(`clip id \"${id}\" must be snake_case`)\n const label = `clip \"${id}\"`\n if (!(typeof clip?.duration === 'number' && Number.isFinite(clip.duration) && clip.duration > 0)) problems.push(`${label} needs a positive finite duration`)\n const dur = typeof clip?.duration === 'number' && clip.duration > 0 ? clip.duration : Infinity\n if (!Array.isArray(clip?.keys) || clip.keys.length === 0) {\n problems.push(`${label} needs at least one key`)\n return { ok: false, problems }\n }\n let prev = -Infinity\n clip.keys.forEach((k, i) => {\n if (!(typeof k.t === 'number' && Number.isFinite(k.t)) || k.t < 0 || k.t > dur + 1e-9) problems.push(`${label} key ${i} has t=${String(k.t)} outside 0..${dur}`)\n else if (k.t < prev) problems.push(`${label} key ${i} goes back in time (${k.t} < ${prev})`)\n else prev = k.t\n for (const f of ['position', 'rotation'] as const) if (k[f] !== undefined && !finite3(k[f])) problems.push(`${label} key ${i} has an invalid ${f}`)\n for (const [pid, pose] of Object.entries(k.parts ?? {})) {\n if (!parts.has(pid)) problems.push(`${label} key ${i} moves unknown part \"${pid}\"`)\n for (const f of ['position', 'rotation'] as const) if (pose?.[f] !== undefined && !finite3(pose[f])) problems.push(`${label} key ${i} part \"${pid}\" has an invalid ${f}`)\n }\n })\n let prevEv = -Infinity\n ;(clip.events ?? []).forEach((e, i) => {\n if (!(typeof e.t === 'number' && Number.isFinite(e.t)) || e.t < 0 || e.t > dur + 1e-9) problems.push(`${label} event ${i} has t=${String(e.t)} outside 0..${dur}`)\n else if (e.t < prevEv) problems.push(`${label} event ${i} goes back in time`)\n else prevEv = e.t\n if (typeof e.type !== 'string' || e.type === '') problems.push(`${label} event ${i} needs a type`)\n try {\n JSON.stringify(e.data ?? {})\n } catch {\n problems.push(`${label} event ${i} data is not JSON`)\n }\n })\n return { ok: problems.length === 0, problems }\n}\n","import type { AnimationClipSpec, AnimationEventSpec, AnimationPose } from './animation'\nimport { checkAnimation } from './animation'\nimport type { HostEvent } from './module'\n\n/** Clips a tool can bring, one per move of its cycle. A tool brings the ones it has; a missing clip is simply not played. */\nexport const TOOL_CLIP = {\n DRAW: 'draw',\n SHOT: 'shot',\n MISFIRE: 'misfire',\n EMPTY: 'empty',\n CYCLE: 'cycle',\n RELOAD: 'reload',\n FEED: 'feed',\n FAULT: 'fault',\n CLEAR: 'clear',\n SAFETY_ON: 'safety_on',\n SAFETY_OFF: 'safety_off',\n MODE: 'mode',\n /** A throwable with the pin out, held until it is thrown. */\n COOK: 'cook',\n /** The stock folds, unfolds, slides in or out (variants `fold`, `unfold`, `collapse`, `extend`). */\n STOCK: 'stock',\n /** A light or a laser is switched (variants `on`, `off`). */\n TOGGLE: 'toggle',\n} as const\nexport type ToolClipId = (typeof TOOL_CLIP)[keyof typeof TOOL_CLIP]\n\n/** What a renderer does when a rule event arrives. `variant` picks `<clip>_<variant>` when the tool has it, else `<clip>`. */\nexport type ToolAnimationStep =\n | { readonly type: 'play'; readonly clip: ToolClipId; readonly variant?: string; readonly duration?: number; readonly repeat?: number }\n /** Move the clip that is playing to the marker (a clip event `stage` with `data.stage`) the rule just reached. */\n | { readonly type: 'sync'; readonly stage: string }\n /** The rule says the phase is over: the clip jumps to its end if it is still running. */\n | { readonly type: 'finish'; readonly clip: ToolClipId }\n\nconst stockVariant = (event: Extract<HostEvent, { type: 'stock' }>): string => {\n const isFolding = event.kind === undefined ? false : event.kind.includes('fold')\n if (event.state === 'collapsed') return isFolding ? 'fold' : 'collapse'\n return isFolding ? 'unfold' : 'extend'\n}\n\n/** The move a host event asks of the tool in hand, or null when it has no motion. */\nexport const toolAnimationStep = (event: HostEvent): ToolAnimationStep | null => {\n switch (event.type) {\n case 'tool':\n return { type: 'play', clip: TOOL_CLIP.DRAW, ...(event.duration === undefined ? {} : { duration: event.duration }) }\n case 'shot':\n return { type: 'play', clip: TOOL_CLIP.SHOT, repeat: Math.max(1, event.rounds ?? 1) }\n case 'misfire':\n return { type: 'play', clip: TOOL_CLIP.MISFIRE }\n case 'empty':\n return { type: 'play', clip: TOOL_CLIP.EMPTY }\n case 'mode':\n return { type: 'play', clip: TOOL_CLIP.MODE, variant: event.mode }\n case 'fault':\n return { type: 'play', clip: TOOL_CLIP.FAULT, variant: event.fault }\n case 'clear':\n if (event.phase === 'end') return { type: 'finish', clip: TOOL_CLIP.CLEAR }\n return { type: 'play', clip: TOOL_CLIP.CLEAR, variant: event.fault, ...(event.duration === undefined ? {} : { duration: event.duration }) }\n case 'safety':\n return { type: 'play', clip: event.on ? TOOL_CLIP.SAFETY_ON : TOOL_CLIP.SAFETY_OFF }\n case 'cycle':\n if (event.phase === 'end') return { type: 'finish', clip: TOOL_CLIP.CYCLE }\n return { type: 'play', clip: TOOL_CLIP.CYCLE, ...(event.duration === undefined ? {} : { duration: event.duration }) }\n case 'reload':\n if (event.phase === 'end') return { type: 'finish', clip: TOOL_CLIP.RELOAD }\n return { type: 'play', clip: TOOL_CLIP.RELOAD, ...(event.kind === undefined ? {} : { variant: event.kind }), ...(event.duration === undefined ? {} : { duration: event.duration }) }\n case 'stage':\n return { type: 'sync', stage: event.stage }\n case 'feed':\n return { type: 'play', clip: TOOL_CLIP.FEED }\n case 'cook':\n if (event.phase === 'start') return { type: 'play', clip: TOOL_CLIP.COOK, duration: event.fuse }\n return { type: 'finish', clip: TOOL_CLIP.COOK }\n case 'stock':\n return { type: 'play', clip: TOOL_CLIP.STOCK, variant: stockVariant(event), ...(event.duration === undefined ? {} : { duration: event.duration }) }\n case 'toggle':\n return { type: 'play', clip: TOOL_CLIP.TOGGLE, variant: event.on ? 'on' : 'off' }\n default:\n return null\n }\n}\n\n/** Where a renderer puts the poses: every part of the tool by id (the ids the clips move), and the whole tool. Poses are offsets from rest. */\nexport interface ToolRig {\n setPartPose(partId: string, pose: AnimationPose): void\n setRootPose?(pose: AnimationPose): void\n}\n\n/** Clips by id, with the variants as `<clip>_<variant>`. */\nexport type ToolClipSet = Readonly<Record<string, AnimationClipSpec>>\n\n/** A renderer's tool animator: feed it the host events and the frame time. */\nexport interface ToolAnimator {\n handle(event: HostEvent): void\n update(dt: number): void\n /** Ids of the clips playing now. */\n readonly playing: readonly string[]\n dispose(): void\n}\n\nexport const STAGE_EVENT = 'stage'\n\n/** The clip with its times (keys and events) scaled so that it lasts `duration` seconds. */\nexport const scaleClip = (clip: AnimationClipSpec, duration: number): AnimationClipSpec => {\n const factor = clip.duration > 0 ? duration / clip.duration : 1\n return {\n ...clip,\n duration,\n keys: clip.keys.map((key) => ({ ...key, t: key.t * factor })),\n ...(clip.events ? { events: clip.events.map((event) => ({ ...event, t: event.t * factor })) } : {}),\n }\n}\n\nconst ZERO: [number, number, number] = [0, 0, 0]\nconst mix = (from: readonly number[], to: readonly number[], fraction: number): [number, number, number] => [\n (from[0] ?? 0) + ((to[0] ?? 0) - (from[0] ?? 0)) * fraction,\n (from[1] ?? 0) + ((to[1] ?? 0) - (from[1] ?? 0)) * fraction,\n (from[2] ?? 0) + ((to[2] ?? 0) - (from[2] ?? 0)) * fraction,\n]\n\n/** Pose of the whole tool (`partId` undefined) or of one part at `time`, linear between keys; a key that does not name it means rest. */\nexport const sampleClip = (clip: AnimationClipSpec, partId: string | undefined, time: number): { position: [number, number, number]; rotation: [number, number, number] } => {\n const points = clip.keys.map((key) => {\n const pose = partId === undefined ? key : key.parts?.[partId]\n return { t: key.t, position: pose?.position ?? ZERO, rotation: pose?.rotation ?? ZERO }\n })\n const first = points[0]\n if (!first || first.t > 0) points.unshift({ t: 0, position: ZERO, rotation: ZERO })\n const last = points[points.length - 1]\n if (!last) return { position: ZERO, rotation: ZERO }\n if (time >= last.t) return { position: mix(last.position, last.position, 0), rotation: mix(last.rotation, last.rotation, 0) }\n const index = Math.max(0, points.findIndex((point, i) => time >= point.t && time < (points[i + 1]?.t ?? Infinity)))\n const from = points[index]\n const to = points[index + 1]\n if (!from || !to) return { position: ZERO, rotation: ZERO }\n const fraction = to.t > from.t ? (time - from.t) / (to.t - from.t) : 0\n return { position: mix(from.position, to.position, fraction), rotation: mix(from.rotation, to.rotation, fraction) }\n}\n\n/** Problems with a clip set (empty when valid): each key is the clip id (or `<clip>_<variant>`), every clip passes `checkAnimation`, and a `stage` event names its stage. */\nexport const checkToolClips = (clips: ToolClipSet, partIds: Iterable<string>): { ok: boolean; problems: string[] } => {\n const parts = [...partIds]\n const problems: string[] = []\n for (const [key, clip] of Object.entries(clips)) {\n if (clip.id !== key) problems.push(`clip \"${key}\" has the id \"${clip.id}\"`)\n if (!(clip.duration > 0)) problems.push(`clip \"${key}\" has no duration`)\n problems.push(...checkAnimation(clip, parts).problems)\n for (const event of clip.events ?? []) if (event.type === STAGE_EVENT && typeof event.data?.stage !== 'string') problems.push(`clip \"${key}\" has a stage event without data.stage`)\n }\n return { ok: problems.length === 0, problems }\n}\n\ninterface Instance {\n readonly name: string\n readonly clip: AnimationClipSpec\n time: number\n delay: number\n fired: number\n}\n\nexport interface ToolClipPlayerOptions {\n clips: ToolClipSet\n rig: ToolRig\n /** Called for each clip event as its time passes (`eject_casing`, `drop_magazine`, `stage`...). */\n onEvent?(event: AnimationEventSpec, clip: string): void\n /** Seconds between the rounds of a `repeat` (a burst): default 0.1. */\n repeatInterval?(): number\n}\n\n/** The default player of a tool's clips: runs the steps of `toolAnimationStep` against a rig, scaled to the duration the rule gives. */\nexport interface ToolClipPlayer {\n apply(step: ToolAnimationStep): void\n update(dt: number): void\n readonly playing: readonly string[]\n /** The clips being played with the time each has reached and the clip itself (scaled to the duration the rule gave), for a renderer that has to follow what the clip does now (the hands working a part). */\n readonly running: ReadonlyArray<{ readonly name: string; readonly time: number; readonly clip: AnimationClipSpec }>\n}\n\n/**\n * The clip with every event that has a `lead` moved that many seconds earlier, the lead it got in `data.lead`. A led event never goes before 0\n * nor before the event written ahead of it (a fast clip keeps its order: the lead shrinks). Run after `scaleClip`: the lead is real time, the\n * clip's stretch does not touch it.\n */\nexport const leadClipEvents = (clip: AnimationClipSpec): AnimationClipSpec => {\n const events = clip.events ?? []\n if (!events.some((event) => (event.lead ?? 0) > 0)) return clip\n let floor = 0\n const led = events.map((event) => {\n const lead = Math.max(0, Math.min(event.t - floor, event.lead ?? 0))\n const t = event.t - lead\n floor = Math.max(floor, t)\n if (event.lead === undefined) return event\n const moved: AnimationEventSpec = { ...event, t, data: lead > 0 ? { ...event.data, lead } : { ...event.data } }\n delete moved.lead\n return moved\n })\n return { ...clip, events: led }\n}\n\nexport const createToolClipPlayer = (options: ToolClipPlayerOptions): ToolClipPlayer => {\n const { clips, rig, onEvent } = options\n let instances: Instance[] = []\n\n const find = (clip: string, variant?: string): { name: string; clip: AnimationClipSpec } | undefined => {\n const name = variant !== undefined && clips[`${clip}_${variant}`] ? `${clip}_${variant}` : clip\n const found = clips[name]\n return found ? { name, clip: found } : undefined\n }\n\n const render = (instance: Instance): void => {\n const time = Math.min(instance.time, instance.clip.duration)\n const root = sampleClip(instance.clip, undefined, time)\n if (instance.clip.keys.some((key) => key.position || key.rotation)) rig.setRootPose?.(root)\n const touched = new Set(instance.clip.keys.flatMap((key) => Object.keys(key.parts ?? {})))\n for (const id of touched) rig.setPartPose(id, sampleClip(instance.clip, id, time))\n }\n\n const fireEvents = (instance: Instance, upTo: number): void => {\n const events = instance.clip.events ?? []\n while (instance.fired < events.length && (events[instance.fired]?.t ?? Infinity) <= upTo + 1e-9) {\n const event = events[instance.fired]\n instance.fired += 1\n if (event) onEvent?.(event, instance.name)\n }\n }\n\n return {\n apply(step) {\n if (step.type === 'sync') {\n for (const instance of instances) {\n const marker = (instance.clip.events ?? []).find((event) => event.type === STAGE_EVENT && event.data?.stage === step.stage)\n if (marker && instance.time < marker.t) instance.time = marker.t\n }\n return\n }\n if (step.type === 'finish') {\n for (const instance of instances) {\n if (instance.name !== step.clip && !instance.name.startsWith(`${step.clip}_`)) continue\n fireEvents(instance, instance.clip.duration)\n instance.time = instance.clip.duration\n }\n return\n }\n const found = find(step.clip, step.variant)\n if (!found) return\n const clip = leadClipEvents(step.duration && step.duration > 0 ? scaleClip(found.clip, step.duration) : found.clip)\n const interval = options.repeatInterval?.() ?? 0.1\n instances = instances.filter((existing) => existing.name !== found.name || existing.delay > 0)\n for (let round = 0; round < (step.repeat ?? 1); round++) {\n instances.push({ name: found.name, clip, time: 0, delay: round * interval, fired: 0 })\n }\n },\n update(dt) {\n for (const instance of instances) {\n const carried = instance.delay > 0 ? dt - instance.delay : dt\n if (instance.delay > 0) {\n instance.delay = Math.max(0, instance.delay - dt)\n if (instance.delay > 0) continue\n }\n instance.time += carried\n fireEvents(instance, instance.time)\n render(instance)\n }\n instances = instances.filter((instance) => instance.delay > 0 || instance.time < instance.clip.duration)\n },\n get playing() {\n return instances.map((instance) => instance.name)\n },\n get running() {\n return instances.filter((instance) => instance.delay <= 0).map((instance) => ({ name: instance.name, time: instance.time, clip: instance.clip }))\n },\n }\n}\n","export const FRAME_PHASES = ['input', 'fixedSimulate', 'lateSimulate', 'present', 'render', 'idle'] as const\nexport type FramePhase = (typeof FRAME_PHASES)[number]\n\n/**\n * Presentation work a system leaves for later frames, run in small units within a shared time budget.\n * Never simulation: nothing that consumes a seeded generator or decides an outcome may wait in a queue.\n */\nexport interface DeferredQueue {\n /** Stable name for stats and logs (see `DEFERRED_QUEUE_ID`). */\n readonly id: string\n /**\n * Units waiting. 0 means idle; the budget skips the queue without calling `run`. `frame` is the budget's frame counter: a queue may\n * report 0 for the rest of a frame (the crease queue does in the frame a panel was torn). Same value for repeated calls in one frame.\n */\n pending(frame?: number): number\n /**\n * Runs units in order until `clock() >= deadline`. Contract: when `pending() > 0` it runs **at least one** unit, even with a deadline\n * already past; it never runs a unit after it saw the deadline pass. Returns how many units it ran.\n */\n run(deadline: number, clock: () => number): number\n /** Runs everything now (synchronous step, clear, dispose). */\n flush(): void\n /**\n * Called by `budget.register`: from now on the owner stops draining on its own (no MessageChannel, no drain inside `update`).\n * Never called by a getter; a queue nobody registered keeps today's internal draining.\n */\n attach?(): void\n /** Called by the unregister function and by `budget.dispose`: the owner goes back to its internal draining. */\n detach?(): void\n}\n\n/** Where a module registers a deferred queue with the host's budget. */\nexport interface DeferredQueueOptions {\n /** Higher runs first. See DEFERRED_PRIORITY. */\n readonly priority: number\n /** `present` runs before the render (the work must show this frame); `idle` runs after it. Default `idle`. */\n readonly phase?: 'present' | 'idle'\n /** Starvation guard: the queue runs at least one unit every this many frames, even with the budget spent. Default 4. */\n readonly minEveryFrames?: number\n /** Milliseconds of every frame kept for this queue before the others share the rest (when it has work). Default 0. */\n readonly reserveMs?: number\n}\n\n/** The ids of the kit's queues: what `DeferredQueue.id` says and what `DEFERRED_PRIORITY` is keyed by. */\nexport const DEFERRED_QUEUE_ID = {\n DESTRUCTION_EVENTS: 'destruction.events',\n BREACH_PAINT: 'floorplan.breach-paint',\n AUDIO_VOICES: 'audio.voices',\n VEHICLE_CREASE: 'vehicles.crease',\n} as const\n\n/** Default priorities and reserves of the kit's queues; a host may override any of them. */\nexport const DEFERRED_PRIORITY = {\n [DEFERRED_QUEUE_ID.DESTRUCTION_EVENTS]: { priority: 300, phase: 'present' },\n [DEFERRED_QUEUE_ID.BREACH_PAINT]: { priority: 250, phase: 'present' },\n [DEFERRED_QUEUE_ID.AUDIO_VOICES]: { priority: 200, phase: 'idle', reserveMs: 1 },\n [DEFERRED_QUEUE_ID.VEHICLE_CREASE]: { priority: 100, phase: 'idle' },\n} as const satisfies Readonly<Record<string, DeferredQueueOptions>>\n\nexport interface DeferredQueueReport {\n readonly ok: boolean\n readonly problems: readonly string[]\n}\n\n/**\n * Checks the `DeferredQueue` contract. `fill` leaves the queue with at least two units waiting (the same call is made on a fresh queue).\n * It cannot prove the queue only carries presentation work: that is a rule of review.\n */\nexport function checkDeferredQueue(queue: DeferredQueue, fill: () => void): DeferredQueueReport {\n const problems: string[] = []\n fill()\n const waiting = queue.pending(1)\n if (waiting < 2) problems.push(`fill() must leave at least 2 units waiting (pending is ${waiting})`)\n if (queue.pending(1) !== waiting) problems.push('pending(frame) changed between two calls in the same frame')\n const ran = queue.run(0, () => 1)\n if (ran !== 1) problems.push(`run with a deadline already past must run exactly one unit (ran ${ran})`)\n if (waiting >= 2 && queue.pending(2) !== waiting - 1) problems.push(`one unit ran but pending went from ${waiting} to ${queue.pending(2)}`)\n try {\n queue.attach?.()\n queue.attach?.()\n queue.detach?.()\n queue.detach?.()\n } catch (error) {\n problems.push(`attach/detach must be idempotent: ${error instanceof Error ? error.message : String(error)}`)\n }\n queue.flush()\n if (queue.pending(3) !== 0) problems.push(`flush must leave nothing pending (pending is ${queue.pending(3)})`)\n if (queue.run(0, () => 1) !== 0) problems.push('run on an empty queue must run no unit')\n return { ok: problems.length === 0, problems }\n}\n","const WARNED_KEY = Symbol.for('@adatechnology/game-contracts.warned')\n\ntype WarnedHolder = { [WARNED_KEY]?: Set<string> }\n\nconst warnedKeys = (): Set<string> => {\n const holder = globalThis as WarnedHolder\n return (holder[WARNED_KEY] ??= new Set<string>())\n}\n\ndeclare const process: { readonly env: { readonly NODE_ENV?: string } }\n\n// The literal `process.env.NODE_ENV` is what bundlers replace; where there is no `process` the reference throws and counts as development.\nconst isProduction = (): boolean => {\n try {\n return process.env.NODE_ENV === 'production'\n } catch {\n return false\n }\n}\n\nconst isQuiet = (): boolean => (globalThis as { __GAME_KIT_QUIET__?: unknown }).__GAME_KIT_QUIET__ === true\n\n/**\n * Warns on the console the first time `key` is seen, and only outside production and when `globalThis.__GAME_KIT_QUIET__` is not `true`: for a silent fallback that keeps working (an adapter that made its own effects\n * world or audio engine because the host lent none). The seen keys live on `globalThis`, so several bundles of the kit on one page warn once together.\n */\nexport const warnOnce = (key: string, message: string): void => {\n if (isQuiet() || isProduction()) return\n const seen = warnedKeys()\n if (seen.has(key)) return\n seen.add(key)\n console.warn(`[game-kit] ${message}`)\n}\n\n/** Forgets what `warnOnce` has seen (tests that count warnings). */\nexport const resetWarnOnce = (): void => warnedKeys().clear()\n","import { warnOnce } from './warn'\n\ntype PlainObject = Record<string, unknown>\ntype Entry = { readonly id: string }\n\n/** Keys that would reach the prototype of the result when a patch comes from parsed JSON. */\nconst UNSAFE_KEYS: ReadonlySet<string> = new Set(['__proto__', 'constructor', 'prototype'])\n\nconst isPlainObject = (value: unknown): value is PlainObject =>\n typeof value === 'object' && value !== null && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype\n\n/** Recursively optional; arrays and functions are replaced as a whole, objects are merged. */\nexport type DeepPartial<TValue> = TValue extends (...args: never[]) => unknown\n ? TValue\n : TValue extends readonly unknown[]\n ? TValue\n : TValue extends object\n ? { [TKey in keyof TValue]?: DeepPartial<TValue[TKey]> }\n : TValue\n\n/** Merges `patch` over `base`: plain objects merge field by field, arrays, functions and primitives replace. `undefined` in the patch is ignored. */\nexport const deepMerge = <TBase extends object>(base: TBase, patch: object | undefined): TBase => {\n if (!patch) return base\n const result: PlainObject = { ...(base as PlainObject) }\n for (const [key, value] of Object.entries(patch)) {\n if (value === undefined || UNSAFE_KEYS.has(key)) continue\n const current = result[key]\n result[key] = isPlainObject(current) && isPlainObject(value) ? deepMerge(current, value) : value\n }\n return result as TBase\n}\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 patch for an id that does not exist is ignored (and warned about once, in development). */\nexport const applyPatches = <TEntry extends Entry>(entries: Map<string, TEntry>, patches: Readonly<Record<string, DeepPartial<TEntry> | 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, deepMerge(entry, patch))\n else warnOnce(`registry.patch.${id}`, `patch for \"${id}\" ignored: no entry has that id`)\n }\n}\n\n/** Takes the ids out; an id that does not exist is ignored (and warned about once, in development). */\nexport const removeIds = (entries: Map<string, unknown>, ids: readonly string[] | undefined): void => {\n for (const id of ids ?? []) {\n if (!entries.delete(id)) warnOnce(`registry.remove.${id}`, `remove of \"${id}\" ignored: no entry has that id`)\n }\n}\n","import type { Vec3 } from './destruction'\n\n/**\n * The bench of a module: controls, layers and counters it declares as data. The host keeps the values, draws them in a generic way and hands\n * every change back through `ModuleHost.bench`. Nothing here is specific to a kind of module.\n */\nexport type ControlValue = number | boolean | string\n\nexport interface ChoiceOption {\n readonly id: string\n readonly name: string\n /** Heading the host lists the option under (`optgroup`, a grouped row of buttons). Options of a group must sit next to each other. */\n readonly group?: string\n}\n\ninterface ControlBase {\n /** snake_case, unique across the module's controls, layers and metrics. */\n readonly id: string\n /** Display name, already translated for the locale the host asked for. */\n readonly name: string\n readonly hint?: string\n readonly hidden?: boolean\n /**\n * The change (or the press, for an action) takes a noticeable time, a third of a second or more: the host shows its loading indicator while it works, with this text, or the control's name for `true`.\n * Only the host's own controls panel does it; a change made through `TestRoom.bench().set / act` runs at once.\n */\n readonly slow?: boolean | string\n}\n\nexport interface NumberControl extends ControlBase {\n readonly kind: 'number'\n readonly min: number\n readonly max: number\n readonly step?: number\n readonly unit?: string\n readonly default: number\n}\n\nexport interface BooleanControl extends ControlBase {\n readonly kind: 'boolean'\n readonly default: boolean\n}\n\nexport interface ChoiceControl extends ControlBase {\n readonly kind: 'choice'\n readonly options: readonly ChoiceOption[]\n readonly default: string\n}\n\n/** A button: no value, only the press. */\nexport interface ActionControl extends ControlBase {\n readonly kind: 'action'\n}\n\nexport type ModuleControl = NumberControl | BooleanControl | ChoiceControl | ActionControl\n\n/** Something of the module the viewer turns on or off (smoke, flash, casings). */\nexport interface ModuleLayer {\n readonly id: string\n readonly name: string\n /** On unless `false`. */\n readonly default?: boolean\n}\n\n/** Pulled: the room calls read only when it samples (~4 Hz, or on TestRoom.metrics()). */\nexport interface ModuleMetric {\n readonly id: string\n readonly name: string\n readonly unit?: string\n /** Budget: the host shows a bar and warns past it. */\n readonly max?: number\n readonly digits?: number\n read(): number | string\n}\n\nexport type BenchChange =\n | { readonly kind: 'control'; readonly id: string; readonly value: ControlValue }\n | { readonly kind: 'layer'; readonly id: string; readonly on: boolean }\n | { readonly kind: 'action'; readonly id: string }\n\nexport interface ControlPatch {\n readonly hidden?: boolean\n readonly disabled?: boolean\n readonly options?: readonly ChoiceOption[]\n readonly value?: ControlValue\n}\n\n/** What the host lends a module that declares a bench: the current values and the changes. */\nexport interface ModuleBench {\n value(id: string): ControlValue | undefined\n layer(id: string): boolean\n /** Right after attach returns, the host sends one change per control (not actions) and per layer, in declaration order. */\n onChange(listener: (change: BenchChange) => void): () => void\n /** `value` sets without emitting (no echo loops); `options` falls back to the first option if the value is no longer offered. */\n patch(id: string, patch: ControlPatch): void\n /** Move the host camera (a near / middle / far choice). */\n look(eye: Vec3, target: Vec3): void\n /** Shows a sheet on the host's panel, or takes it down with `null`. A host without a sheet area ignores it. */\n sheet?(sheet: BenchSheet | null): void\n}\n\n/**\n * A card of text the module puts on the host's panel (the sheet of an effect, of a weapon): what it is, its numbers, its steps, its colours and,\n * optionally, a strip of segments over time. Names and texts are already translated for the host's locale.\n */\nexport interface BenchSheet {\n readonly kicker?: string\n readonly title: string\n readonly body?: string\n readonly facts?: readonly { readonly label: string; readonly value: string }[]\n readonly steps?: readonly { readonly at: string; readonly text: string }[]\n readonly swatches?: readonly { readonly color: string; readonly name: string }[]\n readonly note?: string\n /** A strip of named segments (seconds), with tick marks and a playhead that follows the module metric `metric`. */\n readonly timeline?: {\n readonly segments: readonly { readonly name: string; readonly start: number; readonly end: number; readonly color: string }[]\n readonly ticks: readonly number[]\n readonly metric: string\n }\n}\n\nexport interface BenchReport {\n readonly ok: boolean\n readonly problems: readonly string[]\n}\n\nexport interface BenchDeclaration {\n readonly controls?: readonly ModuleControl[]\n readonly layers?: readonly ModuleLayer[]\n readonly metrics?: readonly ModuleMetric[]\n}\n\nconst SNAKE_CASE = /^[a-z][a-z0-9_]*$/\nconst finite = (n: unknown): n is number => typeof n === 'number' && Number.isFinite(n)\n\nfunction checkControl(control: ModuleControl, problems: string[]): void {\n const label = `control \"${control.id}\"`\n if (control.kind === 'number') {\n if (!finite(control.min) || !finite(control.max) || control.min >= control.max) problems.push(`${label} needs min < max`)\n if (control.step !== undefined && !(finite(control.step) && control.step > 0)) problems.push(`${label} needs a step > 0`)\n if (!finite(control.default) || control.default < control.min || control.default > control.max) problems.push(`${label} default ${String(control.default)} is outside ${control.min}..${control.max}`)\n return\n }\n if (control.kind !== 'choice') return\n if (control.options.length === 0) {\n problems.push(`${label} needs at least one option`)\n return\n }\n const seen = new Set<string>()\n const groups = new Set<string>()\n let previousGroup: string | undefined\n for (const option of control.options) {\n if (seen.has(option.id)) problems.push(`${label} repeats option \"${option.id}\"`)\n seen.add(option.id)\n if (option.group !== undefined && (typeof option.group !== 'string' || option.group === '')) problems.push(`${label} option \"${option.id}\" has an empty group`)\n else if (option.group !== undefined && option.group !== previousGroup && groups.has(option.group)) problems.push(`${label} group \"${option.group}\" is split: its options must be next to each other`)\n if (typeof option.group === 'string' && option.group !== '') groups.add(option.group)\n previousGroup = option.group\n }\n if (!seen.has(control.default)) problems.push(`${label} default \"${control.default}\" is not among its options`)\n}\n\n/** snake_case ids unique across controls, layers and metrics; min < max; step > 0; default in range; options non-empty, unique, default among them, each group in one run; metric max > 0. */\nexport function checkBench(declared: BenchDeclaration): BenchReport {\n const problems: string[] = []\n const ids = new Set<string>()\n const claim = (kind: string, id: string): void => {\n if (!SNAKE_CASE.test(id)) problems.push(`${kind} id \"${id}\" must be snake_case`)\n if (ids.has(id)) problems.push(`duplicate id \"${id}\" across controls, layers and metrics`)\n ids.add(id)\n }\n for (const control of declared.controls ?? []) {\n claim('control', control.id)\n checkControl(control, problems)\n }\n for (const layer of declared.layers ?? []) claim('layer', layer.id)\n for (const metric of declared.metrics ?? []) {\n claim('metric', metric.id)\n if (metric.max !== undefined && !(finite(metric.max) && metric.max > 0)) problems.push(`metric \"${metric.id}\" needs a max > 0`)\n }\n return { ok: problems.length === 0, problems }\n}\n","/** The weather, light and ground as the world is right now. Written by whoever drives the weather, read by whoever reacts to it. */\nexport interface EnvironmentState {\n /** Rain in mm/h; 0 is dry. */\n readonly rain: number\n /** Wind in m/s along x and z, and its strength (the length of that vector). */\n readonly windX: number\n readonly windZ: number\n readonly windSpeed: number\n /** Strongest gust in m/s. */\n readonly gust: number\n /** Render fog far distance in metres. */\n readonly fogFar: number\n /** How far one can see for gameplay, in metres (not the render fog). */\n readonly visibility: number\n /** 0 is full daylight, 1 is the darkest night. */\n readonly nightFactor: number\n readonly temperatureC: number\n /** Wet ground, 0..1: rises with rain and dries after it stops. */\n readonly wetness: number\n}\n\nexport type EnvironmentKey = keyof EnvironmentState\n\n/** A calm dry day (the normal world, before anything went wrong). */\nexport const DEFAULT_ENVIRONMENT: EnvironmentState = {\n rain: 0,\n windX: 0,\n windZ: 0,\n windSpeed: 0,\n gust: 0,\n fogFar: 220,\n visibility: 220,\n nightFactor: 0,\n temperatureC: 20,\n wetness: 0,\n}\n\n/** The host's environment (`ModuleHost.environment`): a store the weather module writes to and the others read. */\nexport interface EnvironmentBus {\n get(): EnvironmentState\n /** Merges `patch` into the state; listeners hear it only when a value really changed. */\n set(patch: Partial<EnvironmentState>): void\n /** Hears every change with the keys that changed. Returns the unsubscribe function. */\n on(listener: (state: EnvironmentState, changed: readonly EnvironmentKey[]) => void): () => void\n}\n\n/** A plain store that implements the bus; the test room and tests use it, a game may bring its own. */\nexport function createEnvironmentBus(initial: Partial<EnvironmentState> = {}): EnvironmentBus {\n let state: EnvironmentState = { ...DEFAULT_ENVIRONMENT, ...initial }\n const listeners = new Set<(state: EnvironmentState, changed: readonly EnvironmentKey[]) => void>()\n return {\n get: () => state,\n set(patch) {\n const changed = (Object.keys(patch) as EnvironmentKey[]).filter((key) => patch[key] !== undefined && patch[key] !== state[key])\n if (changed.length === 0) return\n const next = { ...state } as { -readonly [Key in EnvironmentKey]: number }\n for (const key of changed) next[key] = patch[key] as number\n state = next\n for (const listener of [...listeners]) listener(state, changed)\n },\n on(listener) {\n listeners.add(listener)\n return () => void listeners.delete(listener)\n },\n }\n}\n","import { applyPatches, deepMerge, mergeById, removeIds } from './registry'\nimport type { DeepPartial } from './registry'\n\n/** One category (kind) of items. Defaults and required contracts are inherited from the `extends` chain. */\nexport interface CategoryDef<TDefaults extends object = Record<string, unknown>> {\n readonly id: string\n /** Parent category id; the child inherits its defaults and its required contracts. */\n readonly extends?: string\n /** Field values every entry of this kind starts with; objects merge down the chain, arrays replace. */\n readonly defaults?: DeepPartial<TDefaults>\n /** Contract ids (`ContractChecker.id`) every entry of this kind, or of a descendant, must satisfy. */\n readonly requires?: readonly string[]\n /** An abstract category only groups: entries cannot declare it directly, only its descendants. */\n readonly abstract?: boolean\n}\n\n/** Where a checker is being run: the entry's kind and the chain of categories root first. */\nexport interface ContractContext {\n readonly kind: string\n readonly chain: readonly string[]\n}\n\n/** A named contract: returns the problems of an entry, each a short phrase like `missing leftover.despawnSeconds`. Empty means it holds. */\nexport interface ContractChecker<TEntry> {\n readonly id: string\n check(entry: TEntry, ctx: ContractContext): string[]\n}\n\nexport interface CategoryOptions {\n /** Categories added to the shipped ones; the same id replaces. */\n readonly extra?: readonly CategoryDef[]\n /** Partial changes by id, applied after `extra`. */\n readonly patches?: Readonly<Record<string, DeepPartial<CategoryDef> | Partial<CategoryDef>>>\n /** Ids taken out of the tree (their children are then reported by `check()`). */\n readonly remove?: readonly string[]\n}\n\nexport interface CategoryRegistry {\n get(id: string): CategoryDef | undefined\n has(id: string): boolean\n ids(): string[]\n /** Root first, ending at `id`. Throws on an unknown id, an unknown parent or a cycle. */\n chain(id: string): string[]\n /** `defaults` merged along the chain, root first, so the closest category wins. */\n defaults(id: string): Record<string, unknown>\n /** Union of `requires` along the chain, order kept, without duplicates. */\n requires(id: string): string[]\n /** True when `id` is `ancestorId` or descends from it. */\n isA(id: string, ancestorId: string): boolean\n /** Problems in the tree itself: duplicate ids, unknown `extends`, cycles. */\n check(): string[]\n}\n\nexport interface CategorizedOptions {\n /** Field of the entry that names its category. Default `kind`. */\n readonly kindField?: string\n /** Known gaps by entry id: problems starting with one of these texts go to `allowed`, so the list can shrink over time. */\n readonly allow?: Readonly<Record<string, readonly string[]>>\n}\n\nexport interface CategorizedReport {\n readonly ok: boolean\n readonly problems: string[]\n readonly allowed: string[]\n}\n\nexport const defineCategories = (defs: readonly CategoryDef[], options: CategoryOptions = {}): CategoryRegistry => {\n const problems: string[] = []\n const duplicates = (list: readonly CategoryDef[]): void => {\n const ids = new Set<string>()\n for (const def of list) {\n if (ids.has(def.id)) problems.push(`duplicate category \"${def.id}\"`)\n ids.add(def.id)\n }\n }\n duplicates(defs)\n duplicates(options.extra ?? [])\n\n const entries = mergeById(defs, options.extra)\n applyPatches(entries, options.patches)\n removeIds(entries, options.remove)\n\n const chain = (id: string): string[] => {\n const path: string[] = []\n let current: string | undefined = id\n while (current !== undefined) {\n const def = entries.get(current)\n if (!def) throw new Error(path.length === 0 ? `unknown category \"${current}\"` : `category \"${path[0]}\" has unknown parent \"${current}\"`)\n if (path.includes(current)) throw new Error(`category cycle: ${[...path, current].reverse().join(' -> ')}`)\n path.push(current)\n current = def.extends\n }\n return path.reverse()\n }\n\n const registry: CategoryRegistry = {\n get: (id) => entries.get(id),\n has: (id) => entries.has(id),\n ids: () => [...entries.keys()],\n chain,\n defaults: (id) => chain(id).reduce<Record<string, unknown>>((acc, ancestor) => deepMerge(acc, entries.get(ancestor)?.defaults), {}),\n requires: (id) => {\n const out: string[] = []\n for (const ancestor of chain(id)) for (const contract of entries.get(ancestor)?.requires ?? []) if (!out.includes(contract)) out.push(contract)\n return out\n },\n isA: (id, ancestorId) => {\n try {\n return chain(id).includes(ancestorId)\n } catch {\n return false\n }\n },\n check: () => {\n const found = [...problems]\n for (const [id, def] of entries) {\n if (def.extends !== undefined && !entries.has(def.extends)) found.push(`category \"${id}\" extends unknown \"${def.extends}\"`)\n else {\n try {\n chain(id)\n } catch (error) {\n const message = (error as Error).message\n // The same cycle is found from each of its members; the dedupe keeps one line per distinct message.\n if (message.startsWith('category cycle') && !found.includes(message)) found.push(message)\n }\n }\n }\n return found\n },\n }\n return registry\n}\n\n/** The entry with the defaults of its category underneath: the entry wins, objects merge, arrays replace. */\nexport const resolveEntry = <TEntry extends object>(registry: CategoryRegistry, entry: TEntry, kindField = 'kind'): TEntry =>\n deepMerge(registry.defaults(String((entry as Record<string, unknown>)[kindField])) as TEntry, entry)\n\n/** Checks that each entry has a known, concrete kind and satisfies every contract its chain requires. */\nexport const checkCategorized = <TEntry extends { readonly id: string }>(\n entries: readonly TEntry[],\n registry: CategoryRegistry,\n checkers: readonly ContractChecker<TEntry>[],\n options: CategorizedOptions = {},\n): CategorizedReport => {\n const kindField = options.kindField ?? 'kind'\n const byId = new Map(checkers.map((checker) => [checker.id, checker]))\n const problems: string[] = [...registry.check()]\n const allowed: string[] = []\n\n for (const entry of entries) {\n const kind = String((entry as Record<string, unknown>)[kindField])\n if (!registry.has(kind)) {\n problems.push(`\"${entry.id}\" has unknown kind \"${kind}\"`)\n continue\n }\n if (registry.get(kind)?.abstract) {\n problems.push(`\"${entry.id}\" has abstract kind \"${kind}\"`)\n continue\n }\n let path: string[]\n try {\n path = registry.chain(kind)\n } catch (error) {\n problems.push(`\"${entry.id}\" (${kind}): ${(error as Error).message}`)\n continue\n }\n const resolved = resolveEntry(registry, entry, kindField)\n for (const contract of registry.requires(kind)) {\n const owner = path.find((ancestor) => registry.get(ancestor)?.requires?.includes(contract)) ?? kind\n const checker = byId.get(contract)\n if (!checker) {\n problems.push(`kind \"${kind}\" requires contract \"${contract}\" but no checker is registered`)\n continue\n }\n const prefix = `\"${entry.id}\" (${kind}): `\n for (const problem of checker.check(resolved, { kind, chain: path })) {\n const text = `${prefix}${problem}, required by ${owner}`\n if ((options.allow?.[entry.id] ?? []).some((start) => problem.startsWith(start))) allowed.push(text)\n else problems.push(text)\n }\n }\n }\n return { ok: problems.length === 0, problems: [...new Set(problems)], allowed }\n}\n","/**\n * Where a drawn object comes from. Every builder of the kit that draws a piece (a car of the fleet, a house or a wall, a piece of furniture, a person, an\n * animal, a plant, an item, a weapon) marks the root it returns, and every renderer that draws for the room (effects, debris, liquids, the room itself)\n * marks its own roots, so a host can tell a piece of the kit from something drawn on the spot. Duck-typed on three.js objects (`userData`, `parent`,\n * `children`), so the contracts stay free of three.js.\n */\nexport const KIT_ORIGIN_KEY = 'kitOrigin'\n\n/** What drew an object: the package and the kind of piece (`vehicle`, `structure`, `furniture`, `person`... or `effect`, `room` for what is not a piece). */\nexport interface KitOrigin {\n readonly owner: string\n readonly kind: string\n /** The catalogue id of the piece, when it has one. */\n readonly id?: string\n /** Drawn as a simple stand-in shape because the kit has no model of the piece yet (an item with no team model): a model still to make. */\n readonly standIn?: boolean\n}\n\ninterface Node {\n userData?: Record<string, unknown>\n parent?: Node | null\n children?: readonly Node[]\n name?: string\n visible?: boolean\n isMesh?: boolean\n isPoints?: boolean\n isLine?: boolean\n isSprite?: boolean\n}\n\n/** Marks `object` as drawn by `origin.owner` and returns it. */\nexport function markKitOrigin<T>(object: T, origin: KitOrigin): T {\n const node = object as unknown as Node\n node.userData = node.userData ?? {}\n node.userData[KIT_ORIGIN_KEY] = { ...origin }\n return object\n}\n\n/** The origin of the object or of its nearest marked ancestor; null when nothing up the tree is marked. */\nexport function kitOriginOf(object: unknown): KitOrigin | null {\n for (let node = object as Node | null | undefined; node; node = node.parent) {\n const origin = node.userData?.[KIT_ORIGIN_KEY] as KitOrigin | undefined\n if (origin && typeof origin.owner === 'string') return origin\n }\n return null\n}\n\n/** A drawn thing that no builder of the kit marked: what was drawn on the spot. */\nexport interface UnmarkedDrawing {\n /** The names of the object and its named ancestors, outermost first. */\n readonly path: string\n readonly object: unknown\n}\n\n/** Every visible mesh, line, point cloud or sprite under `root` with no marked ancestor (`root` itself not counted as one). */\nexport function unmarkedDrawings(root: unknown): UnmarkedDrawing[] {\n const out: UnmarkedDrawing[] = []\n const pathOf = (node: Node): string => {\n const names: string[] = []\n for (let n: Node | null | undefined = node; n && n !== root; n = n.parent) if (n.name) names.push(n.name)\n return names.reverse().join(' > ') || '(no name)'\n }\n const walk = (node: Node, marked: boolean): void => {\n if (node.visible === false) return\n const here = marked || (node !== root && Boolean((node.userData?.[KIT_ORIGIN_KEY] as KitOrigin | undefined)?.owner))\n if (!here && (node.isMesh || node.isPoints || node.isLine || node.isSprite)) out.push({ path: pathOf(node), object: node })\n for (const child of node.children ?? []) walk(child, here)\n }\n walk(root as Node, false)\n return out\n}\n","/** A floor rectangle in an object's own space, metres (x across, z along). */\nexport interface Footprint {\n readonly minX: number\n readonly maxX: number\n readonly minZ: number\n readonly maxZ: number\n}\n\n/** What `footprintBlocker` reads of an object: its world matrix (three.js `Object3D`, duck-typed so the contracts stay free of three.js). */\nexport interface FootprintObject {\n readonly matrixWorld: { readonly elements: ArrayLike<number> }\n readonly parent?: unknown\n updateWorldMatrix?(updateParents: boolean, updateChildren: boolean): void\n}\n\n/**\n * The `ModuleHost.addBlocker` function of a solid object standing on the floor: the walking player stays out of `footprint`, a rectangle in the object's own space,\n * so the object blocks turned with it and follows it when it moves. An object out of the scene (no parent) blocks nothing.\n */\nexport function footprintBlocker(object: FootprintObject | (() => FootprintObject | null | undefined), footprint: Footprint | (() => Footprint | null | undefined)): (x: number, z: number, radius: number) => boolean {\n return (x, z, radius) => {\n const o = typeof object === 'function' ? object() : object\n const fp = typeof footprint === 'function' ? footprint() : footprint\n if (!o || !fp || !o.parent) return false\n o.updateWorldMatrix?.(true, false)\n const e = o.matrixWorld.elements\n // the world point in the object's own floor axes: its x and z columns are at right angles, so a projection on each (over its squared length, for a scale) inverts the matrix\n const dx = x - (e[12] as number)\n const dz = z - (e[14] as number)\n const ax = (e[0] as number) ** 2 + (e[1] as number) ** 2 + (e[2] as number) ** 2\n const az = (e[8] as number) ** 2 + (e[9] as number) ** 2 + (e[10] as number) ** 2\n const lx = (dx * (e[0] as number) + dz * (e[2] as number)) / (ax || 1)\n const lz = (dx * (e[8] as number) + dz * (e[10] as number)) / (az || 1)\n return lx > fp.minX - radius && lx < fp.maxX + radius && lz > fp.minZ - radius && lz < fp.maxZ + radius\n }\n}\n","/**\n * The sounds of a body (footsteps, jumps, landings, blows, hurt), as ids every package agrees on: the rule that knows what the body did\n * (`game-player`, a host's own player) names the sound, and the bank that knows how it sounds (`game-audio`'s `BODY_FOLEY_BANK`) answers it.\n * A game swaps the sound of an id (a sample instead of the synthesis) without touching the rule.\n */\n\n/** What the feet touch, by how it sounds (not by what it is made of: a wooden floor and a plywood board both sound `wood`). */\nexport const SURFACE_IDS = ['concrete', 'asphalt', 'wood', 'tile', 'dirt', 'grass', 'gravel', 'sand', 'metal', 'water', 'glass', 'carpet'] as const\nexport type SurfaceId = (typeof SURFACE_IDS)[number]\n\n/** The surface a body stands on when nobody says (the test room's floor is a concrete slab). */\nexport const DEFAULT_SURFACE: SurfaceId = 'concrete'\n\n/** How the feet move: a run hits harder and shorter; a crouched step is the walk, quieter. */\nexport const GAIT_IDS = ['walk', 'run'] as const\nexport type GaitId = (typeof GAIT_IDS)[number]\n\n/** How hard a landing is: `light` for a jump on the spot or a step down (up to about 5 m/s), `heavy` for a drop of more than a metre and a half. */\nexport const LANDING_IDS = ['light', 'heavy'] as const\nexport type LandingId = (typeof LANDING_IDS)[number]\n\n/** The fall speed (m/s) from which a landing is `heavy`. */\nexport const HEAVY_LANDING_SPEED = 5.5\n\nexport type FootstepSoundId = `footstep_${SurfaceId}_${GaitId}`\nexport type LandSoundId = `land_${SurfaceId}_${LandingId}`\n\n/**\n * How a blow is struck, by the body part or the tool that does it. `blade` stabs or slashes, `blunt` is a club, a bat, a pipe or a hammer, `chop` an\n * axe or a machete driven down.\n */\nexport const BLOW_IDS = ['punch', 'kick', 'shove', 'blade', 'blunt', 'chop'] as const\nexport type BlowId = (typeof BLOW_IDS)[number]\n\n/** What the blow meets: a body (flesh and bone, the head apart), something else (the material answers with its own impact), or nothing. */\nexport type BlowTarget = 'body' | 'head' | 'object' | 'air'\n\n/** The sounds of the body that are not a footstep or a landing. */\nexport const BODY_SOUND_IDS = [\n 'jump',\n 'cloth_rustle',\n 'body_fall',\n 'fall_damage',\n 'player_hurt',\n 'player_breath_run',\n 'player_death',\n 'bite',\n 'grab',\n 'punch_swing',\n 'punch_hit_body',\n 'punch_hit_face',\n 'punch_hit_object',\n 'kick_swing',\n 'kick_hit',\n 'kick_hit_object',\n 'shove_two_hands',\n 'knife_swing',\n 'knife_stab',\n 'knife_slash',\n 'knife_hit_object',\n 'blunt_swing',\n 'blunt_hit',\n 'blunt_hit_object',\n 'axe_swing',\n 'axe_chop',\n 'axe_hit_object',\n] as const\nexport type BodySoundId = (typeof BODY_SOUND_IDS)[number]\n\n/** Every sound of a body: what a host plays for the player, a survivor or a zombie. */\nexport type BodyFoleyId = FootstepSoundId | LandSoundId | BodySoundId\n\nexport const footstepSoundId = (surface: SurfaceId, gait: GaitId): FootstepSoundId => `footstep_${surface}_${gait}`\nexport const landSoundId = (surface: SurfaceId, fallSpeed: number): LandSoundId => `land_${surface}_${fallSpeed >= HEAVY_LANDING_SPEED ? 'heavy' : 'light'}`\n\n/** Every footstep and landing id, in the order of the surfaces. */\nexport const FOOTSTEP_SOUND_IDS: readonly FootstepSoundId[] = SURFACE_IDS.flatMap((surface) => GAIT_IDS.map((gait) => footstepSoundId(surface, gait)))\nexport const LAND_SOUND_IDS: readonly LandSoundId[] = SURFACE_IDS.flatMap((surface) => LANDING_IDS.map((landing): LandSoundId => `land_${surface}_${landing}`))\nexport const BODY_FOLEY_IDS: readonly BodyFoleyId[] = [...FOOTSTEP_SOUND_IDS, ...LAND_SOUND_IDS, ...BODY_SOUND_IDS]\n\nconst SWING: Readonly<Record<BlowId, BodySoundId | null>> = {\n punch: 'punch_swing',\n kick: 'kick_swing',\n // two hands pushed out do not whistle: the shove is one sound, at the contact\n shove: null,\n blade: 'knife_swing',\n blunt: 'blunt_swing',\n chop: 'axe_swing',\n}\n\n/**\n * Seconds from the start of a swing's sound to its loudest point (the limb or the tool at full speed, just before the contact). A host that knows when the\n * blow lands starts the swing this long before; one that does not plays the swing now and the hit this long after.\n */\nexport const SWING_LEAD_SECONDS: Readonly<Record<BlowId, number>> = { punch: 0.07, kick: 0.09, shove: 0, blade: 0.05, blunt: 0.1, chop: 0.11 }\n\n/** The swing through the air, played as the blow starts (`null`: the blow has none). */\nexport const swingSoundId = (blow: BlowId): BodySoundId | null => SWING[blow]\n\nconst HIT: Readonly<Record<BlowId, Readonly<Record<Exclude<BlowTarget, 'air'>, BodySoundId>>>> = {\n punch: { body: 'punch_hit_body', head: 'punch_hit_face', object: 'punch_hit_object' },\n kick: { body: 'kick_hit', head: 'kick_hit', object: 'kick_hit_object' },\n shove: { body: 'shove_two_hands', head: 'shove_two_hands', object: 'shove_two_hands' },\n blade: { body: 'knife_stab', head: 'knife_stab', object: 'knife_hit_object' },\n blunt: { body: 'blunt_hit', head: 'blunt_hit', object: 'blunt_hit_object' },\n chop: { body: 'axe_chop', head: 'axe_chop', object: 'axe_hit_object' },\n}\n\n/**\n * The body's side of a blow that lands: the knuckles, the shoe, the blade or the club meeting what it hit. On an object the material's own impact\n * plays too (the wood, the metal); this is the part that says it was a punch and not a shot.\n */\nexport const hitSoundId = (blow: BlowId, target: Exclude<BlowTarget, 'air'>): BodySoundId => HIT[blow][target]\n\n/**\n * The surface a material sounds like underfoot, by the material ids of `game-destruction` and `game-terrain` (unknown ids: `fallback`). Data, so\n * a game with its own materials passes its own table to `surfaceOfMaterial`.\n */\nexport const MATERIAL_SURFACES: Readonly<Record<string, SurfaceId>> = {\n concrete: 'concrete',\n mortar: 'concrete',\n stone: 'concrete',\n brick: 'concrete',\n plaster: 'concrete',\n asphalt: 'asphalt',\n wood_soft: 'wood',\n wood_hard: 'wood',\n green_wood: 'wood',\n plywood: 'wood',\n mdf: 'wood',\n ceramic: 'tile',\n plastic: 'tile',\n soil: 'dirt',\n wax: 'dirt',\n flesh_fruit: 'dirt',\n turf: 'grass',\n foliage: 'grass',\n cactus: 'grass',\n gravel: 'gravel',\n sand: 'sand',\n steel: 'metal',\n cast_iron: 'metal',\n aluminium: 'metal',\n copper: 'metal',\n water: 'water',\n glass: 'glass',\n tempered_glass: 'glass',\n fabric: 'carpet',\n foam: 'carpet',\n leather: 'carpet',\n rubber: 'carpet',\n paper: 'carpet',\n}\n\nexport const isSurfaceId = (value: unknown): value is SurfaceId => typeof value === 'string' && (SURFACE_IDS as readonly string[]).includes(value)\n\n/** The surface of a material id; a surface id passes through. */\nexport function surfaceOfMaterial(material: string | undefined, table: Readonly<Record<string, SurfaceId>> = MATERIAL_SURFACES, fallback: SurfaceId = DEFAULT_SURFACE): SurfaceId {\n if (material === undefined) return fallback\n if (isSurfaceId(material)) return material\n return table[material] ?? fallback\n}\n","/**\n * @adatechnology/game-contracts: the contracts every game module (vehicles, furniture, plants, houses, weapons) depends on, and nothing else.\n * Types plus tiny pure checkers, no runtime dependencies. The default destruction world, the renderers and the test room are optional.\n */\nexport type {\n ChainDelay,\n Crack,\n DamageStage,\n DamageType,\n DebrisKind,\n Dent,\n DestructibleSpec,\n BlastOptions,\n BlastReport,\n ConfinementOptions,\n ExplosionChain,\n FragmentHitOptions,\n ShelterOptions,\n DestructionEvent,\n DestructionEventType,\n FluidKind,\n Fragment,\n FragmentMark,\n FractureStyle,\n BallisticExtras,\n Hit,\n HitIgnition,\n HitResult,\n Hole,\n JointKind,\n JointSpec,\n JointState,\n MaterialId,\n ObjectState,\n PartShape,\n PartSpec,\n PartBreach,\n PartState,\n PartStatus,\n PartUnits,\n Quat,\n RayHit,\n Reinforcement,\n TensionRebar,\n TunedMassDamper,\n WindExposure,\n WindState,\n PileSpec,\n SoilLayer,\n SoilProfile,\n SoilState,\n StructureRole,\n SurfaceLayer,\n ToolKind,\n Vec3,\n} from './destruction'\nexport type { BusyOptions, BusySink, BusyTask } from './busy'\nexport { blastShove, DEFAULT_BLAST_SHOVE } from './blast-shove'\nexport { bodyMass, FRAGMENT_COVER, KNOCK_CHEST, KNOCK_DOWN_SPEED, KNOCKED_DOWN_ANGLE, knockOf, stepKnock } from './blast-knock'\nexport { blastBodySpeed, bodyImpulse, DEFAULT_BLAST_IMPULSE, sideOnImpulse } from './blast-impulse'\nexport type { BlastImpulseOptions } from './blast-impulse'\nexport type { Knock } from './blast-knock'\nexport type { BlastShove, BlastShoveOptions, BlastSource } from './blast-shove'\nexport { IMPACT_CONTRACT, checkDestructible, checkHit } from './impact'\nexport type { CheckOptions, DestructibleImpl, ImpactContract, SurfaceRay, SurfaceRayHit } from './impact'\nexport type { CostOptions } from './impact-cost'\nexport { MISSING_BITS } from './body'\nexport type { BodyRegion, BodySlot, SegmentKind } from './body'\nexport { checkSpec } from './spec'\nexport type { CheckSpecOptions, SpecReport } from './spec'\nexport { shotToHit, shotExtras, useToHit } from './weapons'\nexport { DEFAULT_IGNITION_RULES, fillFactor, ignitionChance, resolveIgnitionRules, sparkTargetFactor } from './ignition'\nexport type { IgnitionRules } from './ignition'\nexport type { Shot, Use } from './weapons'\nexport type { ContainerSpec, ItemUseDef, LiquidId } from './fluids'\nexport { sealBlock, sealOpener } from './seal'\nexport type { SealBlock, SealSpec, SealState } from './seal'\nexport type { HandPose, HandTargetSource, HandTargets } from './hands'\nexport { TWIST_LIFT, twistStroke } from './twist'\nexport type { TwistOptions, TwistStroke } from './twist'\nexport type { EffectHandle, EffectRequest, EffectSink, Enclosure, EnclosureBox } from './effects'\nexport type { WarmUpProbes } from './warm-up'\nexport { checkAnimation } from './animation'\nexport type { AnimationClipSpec, AnimationEventSpec, AnimationKey, AnimationPose, ItemAnimationContract } from './animation'\nexport { STAGE_EVENT, TOOL_CLIP, checkToolClips, createToolClipPlayer, leadClipEvents, sampleClip, scaleClip, toolAnimationStep } from './tool-animation'\nexport type { ToolAnimationStep, ToolAnimator, ToolClipId, ToolClipPlayer, ToolClipPlayerOptions, ToolClipSet, ToolRig } from './tool-animation'\nexport { DEFERRED_PRIORITY, DEFERRED_QUEUE_ID, FRAME_PHASES, checkDeferredQueue } from './frame'\nexport type { DeferredQueue, DeferredQueueOptions, DeferredQueueReport, FramePhase } from './frame'\nexport type { FrameOptions, AreaPlacement, GroundLayout, GroundZone, HostEvent, HostGraphics, HostTool, HostToolAttachment, ModuleArea, ModuleHost, ModuleView, ModuleItemFluid, ModuleItemSeal, ModuleScenario, PlayerState, ItemSeat, ItemSounds, TestableItem, TestableModule, Thumbnail, ToolCommand, ToolCommandResult, HostReach } from './module'\nexport { applyPatches, deepMerge, mergeById, removeIds } from './registry'\nexport type { DeepPartial } from './registry'\nimport { resetWarnOnce as resetWarnOnceForTests } from './warn'\n\nexport { warnOnce } from './warn'\n/** @deprecated Import it from `@adatechnology/game-contracts/testing`. */\nexport const resetWarnOnce = resetWarnOnceForTests\nexport { checkBench } from './bench'\nexport type {\n ActionControl,\n BenchChange,\n BenchDeclaration,\n BenchReport,\n BenchSheet,\n BooleanControl,\n ChoiceControl,\n ChoiceOption,\n ControlPatch,\n ControlValue,\n ModuleBench,\n ModuleControl,\n ModuleLayer,\n ModuleMetric,\n NumberControl,\n} from './bench'\nexport type { AudioListener, AudioSink, LoopHandle, SoundHandle, SoundRequest } from './audio'\nexport { DEFAULT_ENVIRONMENT, createEnvironmentBus } from './environment'\nexport type { EnvironmentBus, EnvironmentKey, EnvironmentState } from './environment'\nexport { checkCategorized, defineCategories, resolveEntry } from './categories'\nexport type { CategorizedOptions, CategorizedReport, CategoryDef, CategoryOptions, CategoryRegistry, ContractChecker, ContractContext } from './categories'\nexport { KIT_ORIGIN_KEY, kitOriginOf, markKitOrigin, unmarkedDrawings } from './origin'\nexport type { KitOrigin, UnmarkedDrawing } from './origin'\nexport { footprintBlocker } from './footprint'\nexport type { Footprint, FootprintObject } from './footprint'\nexport {\n BLOW_IDS,\n BODY_FOLEY_IDS,\n BODY_SOUND_IDS,\n DEFAULT_SURFACE,\n FOOTSTEP_SOUND_IDS,\n GAIT_IDS,\n HEAVY_LANDING_SPEED,\n LANDING_IDS,\n LAND_SOUND_IDS,\n MATERIAL_SURFACES,\n SURFACE_IDS,\n SWING_LEAD_SECONDS,\n footstepSoundId,\n hitSoundId,\n isSurfaceId,\n landSoundId,\n surfaceOfMaterial,\n swingSoundId,\n} from './foley'\nexport type { BlowId, BlowTarget, BodyFoleyId, BodySoundId, FootstepSoundId, GaitId, LandSoundId, LandingId, SurfaceId } from './foley'\n","/** The kinds of particle pool of the effects, as `effects.ts` draws them. */\nexport type PoolKind = 'smoke' | 'steam' | 'fire' | 'spark' | 'water'\n\n/** What `EffectSink.play` of `game-contracts` takes, as a structural type. */\nexport interface ParticleEffectRequest {\n readonly effect: string\n readonly point: [number, number, number]\n readonly values: Readonly<Record<string, number>>\n}\n\n/** The host's effects world as far as the particles need it (`EffectSink` of `game-contracts` fits). */\nexport interface ParticleEffects {\n play(request: ParticleEffectRequest): unknown\n}\n\nexport interface RoutedParticle {\n readonly kind: PoolKind\n readonly flag: number\n readonly position: [number, number, number]\n readonly velocity: [number, number, number]\n readonly life: number\n readonly startSize: number\n readonly endSize: number\n readonly tint: number\n}\n\nconst channel = (value: number): number => Math.round(Math.min(1, Math.max(0, value)) * 255)\nconst rgb = (red: number, green: number, blue: number): number => (channel(red) << 16) | (channel(green) << 8) | channel(blue)\n\nconst EFFECT_OF: Readonly<Partial<Record<PoolKind, string>>> = { smoke: 'particle_puff', steam: 'particle_puff', fire: 'particle_glow', spark: 'particle_streak' }\n\n/** True for the kinds the effects world draws; water stays local because it ends at a height of its own. */\nexport const isRoutedKind = (kind: PoolKind): boolean => EFFECT_OF[kind] !== undefined\n\n/** The numbers of the local particle at birth: the colour and peak opacity the local shader gives it, its life, its two sizes (as a ratio). */\nexport function routedParticleRequest(particle: RoutedParticle): ParticleEffectRequest {\n const { kind, flag, tint } = particle\n const look = lookOf(kind, flag, tint)\n return {\n effect: EFFECT_OF[kind]!,\n point: particle.position,\n values: {\n vx: particle.velocity[0],\n vy: particle.velocity[1],\n vz: particle.velocity[2],\n life: particle.life,\n size: particle.startSize,\n grow: particle.endSize / particle.startSize,\n color: look.color,\n alpha: look.alpha,\n gravity: 0,\n drag: 0,\n },\n }\n}\n\nfunction lookOf(kind: PoolKind, flag: number, tint: number): { color: number; alpha: number } {\n if (kind === 'smoke') return { color: rgb(tint, tint, tint), alpha: 0.55 }\n if (kind === 'steam') return { color: rgb(tint, tint, tint), alpha: 0.4 }\n if (kind === 'spark') return { color: rgb(1, 0.75, 0.3), alpha: 1 }\n if (flag) return { color: rgb(0.25 + 0.15 * tint, 0.5 + 0.2 * tint, 1), alpha: 0.95 }\n return { color: rgb(1, Math.max(0.15, 0.85 - tint * 0.1), 0.25), alpha: 0.8 }\n}\n","import type { ActiveEmitter, EmitterKind } from '@adatechnology/game-interactables'\nimport { warnOnce } from '@adatechnology/game-contracts'\nimport { isRoutedKind, routedParticleRequest } from './routed-particles'\nimport type { ParticleEffects, PoolKind } from './routed-particles'\nimport type { CanvasFactory, ThreeLib, ThreeNS } from './types'\n\nexport interface EffectsOptions {\n /** Where the particle objects are added (the `Points` of every kind, the water streams and the flame ring). Required. */\n scene: ThreeNS.Object3D\n /**\n * Camera whose vertical field of view sizes the sprites, read again every `update` so zoom and fov changes follow. Required: pass the\n * camera the scene is rendered with (a `PerspectiveCamera` fits), or a function returning the current one (`null` = 90 degree lens).\n */\n camera: PerspectiveLike | (() => PerspectiveLike | null) | null\n /** Total particles alive at once, shared by all kinds (split by weight). Default 800. */\n maxParticles?: number\n /** Used to paint the soft sprite on a canvas. Without it (or without a 2D context) a data texture is generated. */\n document?: CanvasFactory\n /** Seed of the random spread. Default 1. */\n seed?: number\n /** Wind vector [x, z] in m/s that bends smoke and steam. Default [0, 0]. */\n wind?: [number, number]\n /** Viewport height in pixels, used to size the point sprites. Update with `setViewHeight` on resize. Default 720. */\n viewHeight?: number\n /** Height (m) water streams fall to when the emitter has no `reach`. Default 0. */\n floorY?: number\n /** Most water streams drawn at once (one draw call each). Default 6. */\n maxStreams?: number\n /** Most gas flame rings drawn at once (all rings share one draw call). Default 16. */\n maxFlames?: number\n /**\n * The host's effects world, read at each particle. With one, smoke, steam, fire and sparks go through it and its one budget (`maxParticles` then only caps water);\n * without one every kind is drawn here with its own cap (deprecated, removed in the next major).\n */\n effects?: () => ParticleEffects | undefined\n}\n\n/** What the effects read from a camera. */\nexport interface PerspectiveLike {\n /** Vertical field of view in degrees. */\n fov: number\n zoom?: number\n}\n\nexport interface EffectsStats {\n alive: number\n capacity: number\n byKind: Record<PoolKind, number>\n}\n\nexport type { PoolKind }\n\nexport interface Effects {\n /**\n * Spawns for the active emitters and advances every particle by `dt` seconds, sizing the sprites for the camera given to\n * `createEffects` (or `setCamera`). No allocations per frame once warmed up. `camera` overrides it for this call (deprecated).\n */\n update(dt: number, emitters: readonly ActiveEmitter[], camera?: PerspectiveLike): void\n /** Replace the camera used for the sprite size (or `null` to go back to a 90 degree lens). */\n setCamera(camera: PerspectiveLike | (() => PerspectiveLike | null) | null): void\n setWind(x: number, z: number): void\n setViewHeight(px: number): void\n stats(): EffectsStats\n /** The Points objects (one per kind). */\n objects: Record<PoolKind, ThreeNS.Points>\n /** Water stream meshes (tapered, translucent, following the gravity arc) and the one mesh holding every gas flame ring. */\n meshes: { group: ThreeNS.Group; streams: ThreeNS.Mesh[]; flames: ThreeNS.Mesh }\n clear(): void\n dispose(): void\n}\n\nconst POOLS: PoolKind[] = ['smoke', 'steam', 'fire', 'water', 'spark']\nconst WEIGHT: Record<PoolKind, number> = { smoke: 0.27, steam: 0.15, fire: 0.2, water: 0.28, spark: 0.1 }\n\ninterface Pool {\n kind: PoolKind\n cap: number\n count: number\n pos: Float32Array\n vel: Float32Array\n age: Float32Array\n life: Float32Array\n size0: Float32Array\n size1: Float32Array\n tint: Float32Array\n flags: Uint8Array\n /** Water only: the height at which the particle ends (splashes, droplets). */\n ground: Float32Array\n aSize: ThreeNS.BufferAttribute\n aAlpha: ThreeNS.BufferAttribute\n aColor: ThreeNS.BufferAttribute\n aPos: ThreeNS.BufferAttribute\n points: ThreeNS.Points\n}\n\nconst VERT = /* glsl */ `\nattribute float aSize;\nattribute float aAlpha;\nattribute vec3 aColor;\nuniform float uScale;\nvarying float vAlpha;\nvarying vec3 vColor;\nvoid main() {\n vAlpha = aAlpha;\n vColor = aColor;\n vec4 mv = modelViewMatrix * vec4(position, 1.0);\n gl_PointSize = max(aSize * uScale / max(-mv.z, 0.05), 1.0);\n gl_Position = projectionMatrix * mv;\n}\n`\nconst FRAG = /* glsl */ `\nuniform sampler2D uMap;\nvarying float vAlpha;\nvarying vec3 vColor;\nvoid main() {\n vec4 t = texture2D(uMap, gl_PointCoord);\n gl_FragColor = vec4(vColor, vAlpha * t.a);\n}\n`\n\nfunction mulberry(seed: number): () => number {\n let a = seed >>> 0\n return () => {\n a = (a + 0x6d2b79f5) >>> 0\n let t = a\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/** Soft round sprite (alpha only matters). Canvas when possible, otherwise generated data. */\nfunction makeSprite(THREE: ThreeLib, doc: CanvasFactory | undefined): ThreeNS.Texture {\n const N = 64\n try {\n const canvas = doc?.createElement('canvas')\n const ctx = canvas?.getContext?.('2d')\n if (canvas && ctx && ctx.createRadialGradient) {\n canvas.width = N\n canvas.height = N\n const g = ctx.createRadialGradient(N / 2, N / 2, 0, N / 2, N / 2, N / 2)\n g.addColorStop(0, 'rgba(255,255,255,1)')\n g.addColorStop(0.4, 'rgba(255,255,255,0.55)')\n g.addColorStop(1, 'rgba(255,255,255,0)')\n ctx.fillStyle = g\n ctx.fillRect(0, 0, N, N)\n const tex = new THREE.CanvasTexture(canvas)\n return tex\n }\n } catch {\n // fall through to the data texture\n }\n const data = new Uint8Array(N * N * 4)\n for (let y = 0; y < N; y++) {\n for (let x = 0; x < N; x++) {\n const d = Math.hypot(x + 0.5 - N / 2, y + 0.5 - N / 2) / (N / 2)\n const a = d >= 1 ? 0 : Math.pow(1 - d, 1.6)\n const i = (y * N + x) * 4\n data[i] = data[i + 1] = data[i + 2] = 255\n data[i + 3] = Math.round(a * 255)\n }\n }\n const tex = new THREE.DataTexture(data, N, N, THREE.RGBAFormat)\n tex.needsUpdate = true\n return tex\n}\n\n/** Vertical streaks of water (alpha only matters); scrolled along the stream to show the flow. */\nfunction makeStreaks(THREE: ThreeLib): ThreeNS.DataTexture {\n const W = 16\n const H = 64\n const data = new Uint8Array(W * H * 4)\n const hash = (x: number, y: number): number => {\n let h = Math.imul(x + 1, 374761393) ^ Math.imul(y + 7, 668265263)\n h = Math.imul(h ^ (h >>> 13), 1274126177)\n return ((h ^ (h >>> 16)) >>> 0) / 4294967296\n }\n for (let y = 0; y < H; y++) {\n for (let x = 0; x < W; x++) {\n const col = 0.35 + 0.65 * hash(x, 0)\n const row = 0.55 + 0.45 * Math.sin(((y / H) * 3 + hash(x, 1) * 6) * Math.PI * 2)\n const i = (y * W + x) * 4\n data[i] = data[i + 1] = data[i + 2] = 255\n data[i + 3] = Math.round(255 * Math.min(1, col * row * 1.3))\n }\n }\n const tex = new THREE.DataTexture(data, W, H, THREE.RGBAFormat)\n tex.wrapS = THREE.RepeatWrapping\n tex.wrapT = THREE.RepeatWrapping\n tex.needsUpdate = true\n return tex\n}\n\n/**\n * Pooled particle effects: smoke, steam, fire (and the blue gas `flame` ring), water streams with drips, and sparks.\n * One `Points` per kind, fixed-size buffers, swap-remove; the total alive never exceeds `maxParticles`.\n */\nexport function createEffects(THREE: ThreeLib, options: EffectsOptions): Effects\n/** @deprecated Pass `{ scene, camera, ... }` as the one options argument. */\nexport function createEffects(THREE: ThreeLib, scene: ThreeNS.Object3D, options?: Partial<EffectsOptions>): Effects\nexport function createEffects(THREE: ThreeLib, a: EffectsOptions | ThreeNS.Object3D, b: Partial<EffectsOptions> = {}): Effects {\n const legacy = !!(a as ThreeNS.Object3D).isObject3D\n const options: Partial<EffectsOptions> = legacy ? { ...b, scene: a as ThreeNS.Object3D } : (a as EffectsOptions)\n const scene = options.scene\n if (!scene) throw new Error('createEffects: options.scene is required')\n if (!legacy && options.camera === undefined) throw new Error('createEffects: options.camera is required (pass null for a 90 degree lens)')\n const total = Math.max(POOLS.length, Math.floor(options.maxParticles ?? 800))\n const rand = mulberry(options.seed ?? 1)\n const wind: [number, number] = [options.wind?.[0] ?? 0, options.wind?.[1] ?? 0]\n const sprite = makeSprite(THREE, options.document)\n let viewHeight = options.viewHeight ?? 720\n let cameraSource: PerspectiveLike | (() => PerspectiveLike | null) | null = options.camera ?? null\n let camera: PerspectiveLike | null = typeof cameraSource === 'function' ? cameraSource() : cameraSource\n const scale = { value: 0 }\n /** Point size in pixels is size * viewHeight / (2 tan(fov / 2)) / distance. */\n const refreshScale = (cam: PerspectiveLike | null): void => {\n const fov = cam ? 2 * Math.atan(Math.tan((Math.min(Math.max(cam.fov, 1), 175) * Math.PI) / 360) / Math.max(cam.zoom ?? 1, 0.01)) : Math.PI / 2\n scale.value = viewHeight / (2 * Math.tan(fov / 2))\n }\n refreshScale(camera)\n\n const pools = {} as Record<PoolKind, Pool>\n const objects = {} as Record<PoolKind, ThreeNS.Points>\n let used = 0\n POOLS.forEach((kind, i) => {\n const cap = i === POOLS.length - 1 ? Math.max(1, total - used) : Math.max(1, Math.floor(total * WEIGHT[kind]))\n used += cap\n const geo = new THREE.BufferGeometry()\n const aPos = new THREE.BufferAttribute(new Float32Array(cap * 3), 3)\n const aSize = new THREE.BufferAttribute(new Float32Array(cap), 1)\n const aAlpha = new THREE.BufferAttribute(new Float32Array(cap), 1)\n const aColor = new THREE.BufferAttribute(new Float32Array(cap * 3), 3)\n for (const a of [aPos, aSize, aAlpha, aColor]) a.setUsage(THREE.DynamicDrawUsage)\n geo.setAttribute('position', aPos)\n geo.setAttribute('aSize', aSize)\n geo.setAttribute('aAlpha', aAlpha)\n geo.setAttribute('aColor', aColor)\n geo.setDrawRange(0, 0)\n const additive = kind === 'fire' || kind === 'spark'\n const mat = new THREE.ShaderMaterial({\n uniforms: { uMap: { value: sprite }, uScale: scale },\n vertexShader: VERT,\n fragmentShader: FRAG,\n transparent: true,\n depthWrite: false,\n blending: additive ? THREE.AdditiveBlending : THREE.NormalBlending,\n })\n const points = new THREE.Points(geo, mat)\n points.frustumCulled = false\n points.name = `effects:${kind}`\n scene.add(points)\n objects[kind] = points\n pools[kind] = {\n kind,\n cap,\n count: 0,\n pos: new Float32Array(cap * 3),\n vel: new Float32Array(cap * 3),\n age: new Float32Array(cap),\n life: new Float32Array(cap),\n size0: new Float32Array(cap),\n size1: new Float32Array(cap),\n tint: new Float32Array(cap),\n flags: new Uint8Array(cap),\n ground: new Float32Array(cap).fill(-1e9),\n aSize,\n aAlpha,\n aColor,\n aPos,\n points,\n }\n })\n\n /** Fractional spawns carried over per emitter id (the Map only grows with distinct ids and is pruned by stamp). */\n const debt = new Map<string, { carry: number; stamp: number }>()\n let stamp = 0\n let clock = 0\n\n const spawn = (p: Pool, x: number, y: number, z: number, vx: number, vy: number, vz: number, life: number, s0: number, s1: number, tint: number, flag = 0, ground = -1e9): void => {\n const sink = isRoutedKind(p.kind) ? options.effects?.() : undefined\n if (sink) {\n sink.play(routedParticleRequest({ kind: p.kind, flag, position: [x, y, z], velocity: [vx, vy, vz], life, startSize: s0, endSize: s1, tint }))\n return\n }\n if (isRoutedKind(p.kind)) warnOnce('game-interactables-three.own-particles', 'game-interactables-three drew its smoke, steam, fire and sparks with pools of its own because the host lent no effects world (options.effects); pass the host effects so the particles share one budget')\n if (p.count >= p.cap) return\n const i = p.count++\n p.pos[i * 3] = x\n p.pos[i * 3 + 1] = y\n p.pos[i * 3 + 2] = z\n p.vel[i * 3] = vx\n p.vel[i * 3 + 1] = vy\n p.vel[i * 3 + 2] = vz\n p.age[i] = 0\n p.life[i] = life\n p.size0[i] = s0\n p.size1[i] = s1\n p.tint[i] = tint\n p.flags[i] = flag\n p.ground[i] = ground\n }\n\n const emitFrom = (e: ActiveEmitter, n: number): void => {\n const k = Math.min(1, Math.max(0, e.intensity))\n const size = e.size > 0 ? e.size : 0.2\n const [px, py, pz] = e.pos\n for (let j = 0; j < n; j++) {\n switch (e.kind) {\n case 'smoke':\n // thick, slow, wide plume\n spawn(pools.smoke, px + (rand() - 0.5) * size * 0.6, py, pz + (rand() - 0.5) * size * 0.6, (rand() - 0.5) * 0.2, 0.45 + rand() * 0.45, (rand() - 0.5) * 0.2, 2.8 + rand() * 1.8, size * 0.6, size * 2.6, 0.3 + rand() * 0.25, 0)\n break\n case 'steam':\n spawn(pools.steam, px + (rand() - 0.5) * size * 0.5, py, pz + (rand() - 0.5) * size * 0.5, (rand() - 0.5) * 0.12, 0.3 + rand() * 0.3, (rand() - 0.5) * 0.12, 1.3 + rand() * 1.1, size * 0.3, size * 1.3, 0.92, 0)\n break\n case 'fire':\n spawn(pools.fire, px + (rand() - 0.5) * size * 0.7, py, pz + (rand() - 0.5) * size * 0.7, (rand() - 0.5) * 0.25, 0.8 + rand() * 0.9 * k, (rand() - 0.5) * 0.25, 0.35 + rand() * 0.4, size * 0.75, size * 0.15, rand(), 0)\n break\n case 'flame': {\n // soft blue glow over the ring (the ring of little flames itself is a mesh)\n const a = rand() * Math.PI * 2\n const r = size * 0.8\n spawn(pools.fire, px + Math.cos(a) * r, py + 0.01, pz + Math.sin(a) * r, 0, 0.06 + rand() * 0.1, 0, 0.14 + rand() * 0.12, size * 0.55, size * 0.15, rand(), 1)\n break\n }\n case 'spark': {\n const a = rand() * Math.PI * 2\n const up = 0.4 + rand() * 0.6\n const sp = 1.2 + rand() * 2\n spawn(pools.spark, px, py, pz, Math.cos(a) * sp * (1 - up * 0.5), up * sp * 1.4, Math.sin(a) * sp * (1 - up * 0.5), 0.35 + rand() * 0.5, size * 0.18, size * 0.05, rand(), 0)\n break\n }\n case 'water':\n break\n }\n }\n }\n\n // ---- water: a tapered translucent stream along the gravity arc, droplets around it and a splash where it lands ----------------\n interface Arc {\n x: number\n y: number\n z: number\n vx: number\n vy: number\n vz: number\n /** Flight time to the landing height. */\n t: number\n stopY: number\n r0: number\n r1: number\n }\n const arc: Arc = { x: 0, y: 0, z: 0, vx: 0, vy: 0, vz: 0, t: 0, stopY: 0, r0: 0, r1: 0 }\n const floorY = options.floorY ?? 0\n const arcOf = (e: ActiveEmitter, k: number): boolean => {\n let [dx, dy, dz] = e.dir\n const len = Math.hypot(dx, dy, dz)\n if (len < 1e-6) {\n dx = 0\n dy = -1\n dz = 0\n } else {\n dx /= len\n dy /= len\n dz /= len\n }\n const speed = 0.7 + 1.8 * k\n const stopY = e.reach !== undefined ? Math.max(e.pos[1] - e.reach, floorY) : floorY\n const drop = e.pos[1] - stopY\n if (drop < 0.02) return false\n const vy = dy * speed\n arc.x = e.pos[0]\n arc.y = e.pos[1]\n arc.z = e.pos[2]\n arc.vx = dx * speed\n arc.vy = vy\n arc.vz = dz * speed\n arc.t = Math.min(2.5, (vy + Math.sqrt(vy * vy + 19.6 * drop)) / 9.8)\n arc.stopY = stopY\n const size = e.size > 0 ? e.size : 0.05\n if (size < 0.1) {\n // a tap: a thin jet that narrows as it speeds up\n arc.r0 = Math.max(0.004, size * 0.5)\n arc.r1 = arc.r0 * 0.55\n } else {\n // a shower or a leak: a spray that opens a little\n arc.r0 = size * 0.45\n arc.r1 = arc.r0 * 1.2\n }\n return true\n }\n\n const emitWater = (e: ActiveEmitter, k: number, nSpray: number, nSplash: number, nMist: number): void => {\n const size = e.size > 0 ? e.size : 0.05\n const wide = size >= 0.1\n for (let j = 0; j < nSpray; j++) {\n const s = rand()\n const t = s * arc.t\n const r = (arc.r0 + (arc.r1 - arc.r0) * s) * (0.7 + rand() * 0.5)\n const a = rand() * Math.PI * 2\n const ox = Math.cos(a) * r\n const oz = Math.sin(a) * r\n spawn(\n pools.water,\n arc.x + arc.vx * t + ox,\n arc.y + arc.vy * t - 4.9 * t * t,\n arc.z + arc.vz * t + oz,\n arc.vx + (wide ? ox * 1.2 : 0) + (rand() - 0.5) * 0.25,\n arc.vy - 9.8 * t,\n arc.vz + (wide ? oz * 1.2 : 0) + (rand() - 0.5) * 0.25,\n 0.22 + rand() * 0.25,\n 0.011 + rand() * 0.012,\n 0.009 + rand() * 0.008,\n rand(),\n 2,\n arc.stopY,\n )\n }\n const ex = arc.x + arc.vx * arc.t\n const ez = arc.z + arc.vz * arc.t\n for (let j = 0; j < nSplash; j++) {\n const a = rand() * Math.PI * 2\n const r = arc.r1 * Math.sqrt(rand())\n const sp = (wide ? 0.5 : 0.25) + rand() * 0.7\n spawn(pools.water, ex + Math.cos(a) * r, arc.stopY + 0.005, ez + Math.sin(a) * r, Math.cos(a) * sp, 0.9 + rand() * 1.3, Math.sin(a) * sp, 0.3 + rand() * 0.25, 0.014 + rand() * 0.018, 0.01, rand(), 3, arc.stopY)\n }\n for (let j = 0; j < nMist; j++) {\n const a = rand() * Math.PI * 2\n const r = arc.r1 * 1.4 * Math.sqrt(rand())\n spawn(pools.water, ex + Math.cos(a) * r, arc.stopY + 0.02, ez + Math.sin(a) * r, Math.cos(a) * 0.15, 0.18 + rand() * 0.15, Math.sin(a) * 0.15, 0.7 + rand() * 0.5, 0.06 + size * 0.2, 0.2 + size * 0.5, rand(), 4, arc.stopY)\n }\n void k\n }\n\n const emitDrip = (e: ActiveEmitter, n: number): void => {\n const size = e.size > 0 ? e.size : 0.05\n const stopY = e.reach !== undefined ? Math.max(e.pos[1] - e.reach, floorY) : floorY\n for (let j = 0; j < n; j++) {\n const sx = (rand() - 0.5) * 0.01\n const sz = (rand() - 0.5) * 0.01\n spawn(pools.water, e.pos[0] + sx, e.pos[1], e.pos[2] + sz, e.dir[0] * 0.08, e.dir[1] * 0.08, e.dir[2] * 0.08, 1.4, size * 0.22, size * 0.2, rand(), 1, stopY)\n }\n }\n\n const RATE: Record<EmitterKind, number> = { smoke: 42, steam: 48, fire: 150, flame: 55, spark: 40, water: 0 }\n\n // Stream meshes ----------------------------------------------------------------------------------------------------------------\n const SEG = 12\n const SIDES = 8\n const maxStreams = Math.max(0, Math.floor(options.maxStreams ?? 6))\n const streaksTex = makeStreaks(THREE)\n const streamMat = new THREE.MeshBasicMaterial({ color: 0x9cccff, map: streaksTex, vertexColors: true, transparent: true, opacity: 0.8, depthWrite: false, side: THREE.DoubleSide })\n const streamIndex: number[] = []\n for (let i = 0; i < SEG; i++) {\n for (let m = 0; m < SIDES; m++) {\n const a = i * (SIDES + 1) + m\n const b = a + SIDES + 1\n streamIndex.push(a, b, a + 1, a + 1, b, b + 1)\n }\n }\n const group = new THREE.Group()\n group.name = 'effects:meshes'\n scene.add(group)\n const streams: ThreeNS.Mesh[] = []\n for (let i = 0; i < maxStreams; i++) {\n const geo = new THREE.BufferGeometry()\n const n = (SEG + 1) * (SIDES + 1)\n geo.setAttribute('position', new THREE.BufferAttribute(new Float32Array(n * 3), 3).setUsage(THREE.DynamicDrawUsage))\n geo.setAttribute('color', new THREE.BufferAttribute(new Float32Array(n * 4), 4).setUsage(THREE.DynamicDrawUsage))\n const uv = new Float32Array(n * 2)\n geo.setAttribute('uv', new THREE.BufferAttribute(uv, 2).setUsage(THREE.DynamicDrawUsage))\n geo.setIndex(streamIndex)\n const mesh = new THREE.Mesh(geo, streamMat)\n mesh.frustumCulled = false\n mesh.visible = false\n mesh.name = `effects:stream${i}`\n group.add(mesh)\n streams.push(mesh)\n }\n\n const writeStream = (mesh: ThreeNS.Mesh, k: number): void => {\n const geo = mesh.geometry\n const pos = (geo.getAttribute('position') as ThreeNS.BufferAttribute).array as Float32Array\n const col = (geo.getAttribute('color') as ThreeNS.BufferAttribute).array as Float32Array\n const uv = (geo.getAttribute('uv') as ThreeNS.BufferAttribute).array as Float32Array\n for (let i = 0; i <= SEG; i++) {\n const s = i / SEG\n const t = s * arc.t\n const cx = arc.x + arc.vx * t\n const cy = arc.y + arc.vy * t - 4.9 * t * t\n const cz = arc.z + arc.vz * t\n let tx = arc.vx\n let ty = arc.vy - 9.8 * t\n let tz = arc.vz\n const tl = Math.hypot(tx, ty, tz) || 1\n tx /= tl\n ty /= tl\n tz /= tl\n // binormal = tangent x up, normal = tangent x binormal\n let bx = tz\n const by = 0\n let bz = -tx\n const bl = Math.hypot(bx, bz)\n if (bl < 1e-4) {\n bx = 1\n bz = 0\n } else {\n bx /= bl\n bz /= bl\n }\n const nx = ty * bz - tz * by\n const ny = tz * bx - tx * bz\n const nz = tx * by - ty * bx\n const r = arc.r0 + (arc.r1 - arc.r0) * s\n const alpha = Math.min(1, s * 7) * (0.95 - 0.3 * s) * Math.min(1, 0.55 + k)\n for (let m = 0; m <= SIDES; m++) {\n const a = (m / SIDES) * Math.PI * 2\n const ca = Math.cos(a) * r\n const sa = Math.sin(a) * r\n const v = i * (SIDES + 1) + m\n pos[v * 3] = cx + bx * ca + nx * sa\n pos[v * 3 + 1] = cy + by * ca + ny * sa\n pos[v * 3 + 2] = cz + bz * ca + nz * sa\n col[v * 4] = col[v * 4 + 1] = col[v * 4 + 2] = 1\n col[v * 4 + 3] = alpha\n uv[v * 2] = (m / SIDES) * 3\n uv[v * 2 + 1] = s * arc.t * 2.2\n }\n }\n ;(geo.getAttribute('position') as ThreeNS.BufferAttribute).needsUpdate = true\n ;(geo.getAttribute('color') as ThreeNS.BufferAttribute).needsUpdate = true\n ;(geo.getAttribute('uv') as ThreeNS.BufferAttribute).needsUpdate = true\n mesh.visible = true\n }\n\n // Gas flame rings: every burner is a ring of small blue cones; all rings share one dynamic mesh (one draw call) ------------------\n const CONES = 16\n const maxFlames = Math.max(0, Math.floor(options.maxFlames ?? 16))\n const flameGeo = new THREE.BufferGeometry()\n const flamePos = new THREE.BufferAttribute(new Float32Array(maxFlames * CONES * 4 * 3), 3).setUsage(THREE.DynamicDrawUsage)\n const flameCol = new THREE.BufferAttribute(new Float32Array(maxFlames * CONES * 4 * 3), 3).setUsage(THREE.DynamicDrawUsage)\n flameGeo.setAttribute('position', flamePos)\n flameGeo.setAttribute('color', flameCol)\n const flameIdx: number[] = []\n for (let i = 0; i < maxFlames * CONES; i++) {\n const v = i * 4\n flameIdx.push(v, v + 1, v + 3, v + 1, v + 2, v + 3, v + 2, v, v + 3)\n }\n flameGeo.setIndex(flameIdx)\n flameGeo.setDrawRange(0, 0)\n const flames = new THREE.Mesh(\n flameGeo,\n new THREE.MeshBasicMaterial({ vertexColors: true, transparent: true, blending: THREE.AdditiveBlending, depthWrite: false, side: THREE.DoubleSide }),\n )\n flames.frustumCulled = false\n flames.visible = false\n flames.name = 'effects:flames'\n group.add(flames)\n\n const hashOf = (id: string): number => {\n let h = 2166136261\n for (let i = 0; i < id.length; i++) h = Math.imul(h ^ id.charCodeAt(i), 16777619)\n return ((h >>> 0) % 1000) / 1000\n }\n\n const writeFlame = (slot: number, e: ActiveEmitter, time: number): void => {\n const pa = flamePos.array as Float32Array\n const ca = flameCol.array as Float32Array\n const k = Math.min(1, Math.max(0, e.intensity))\n const size = e.size > 0 ? e.size : 0.05\n const R = size * 0.8\n const bw = Math.max(0.004, size * 0.13)\n const phase = hashOf(e.id) * 40\n for (let c = 0; c < CONES; c++) {\n const a = (c / CONES) * Math.PI * 2\n const ux = Math.cos(a)\n const uz = Math.sin(a)\n const bx = e.pos[0] + ux * R\n const bz = e.pos[2] + uz * R\n const by = e.pos[1]\n const h = (0.025 + size * 0.45) * k * (0.55 + 0.45 * Math.abs(Math.sin(time * 19 + c * 2.3 + phase)))\n const v = (slot * CONES + c) * 4\n // base triangle: tangent +, tangent -, radial out; apex leans slightly inwards\n const tx = -uz * bw\n const tz = ux * bw\n pa.set([bx + tx, by, bz + tz, bx - tx, by, bz - tz, bx + ux * bw * 0.9, by, bz + uz * bw * 0.9, bx - ux * h * 0.12, by + h, bz - uz * h * 0.12], v * 3)\n const lo = 0.3 * k\n ca.set([0.2 * k, 0.45 * k, 1 * k, 0.2 * k, 0.45 * k, 1 * k, 0.2 * k, 0.45 * k, 1 * k, 0.02 * lo, 0.1 * lo, 0.5 * lo], v * 3)\n }\n }\n\n const advance = (p: Pool, dt: number): void => {\n let i = 0\n const swayX = wind[0]\n const swayZ = wind[1]\n while (i < p.count) {\n p.age[i] += dt\n const age = p.age[i]\n const life = p.life[i]\n if (age >= life || (p.kind === 'water' && p.pos[i * 3 + 1] < p.ground[i])) {\n // swap-remove\n const last = --p.count\n if (i !== last) {\n p.pos.copyWithin(i * 3, last * 3, last * 3 + 3)\n p.vel.copyWithin(i * 3, last * 3, last * 3 + 3)\n p.age[i] = p.age[last]\n p.life[i] = p.life[last]\n p.size0[i] = p.size0[last]\n p.size1[i] = p.size1[last]\n p.tint[i] = p.tint[last]\n p.flags[i] = p.flags[last]\n p.ground[i] = p.ground[last]\n }\n continue\n }\n const j = i * 3\n switch (p.kind) {\n case 'smoke':\n case 'steam':\n p.vel[j] += (swayX - p.vel[j]) * Math.min(1, dt * 0.8)\n p.vel[j + 2] += (swayZ - p.vel[j + 2]) * Math.min(1, dt * 0.8)\n break\n case 'water':\n if (p.flags[i] !== 4) p.vel[j + 1] -= 9.8 * dt\n break\n case 'spark':\n p.vel[j + 1] -= 7 * dt\n break\n case 'fire':\n p.vel[j] += (swayX * 0.1 - p.vel[j]) * Math.min(1, dt * 2)\n break\n }\n p.pos[j] += p.vel[j] * dt\n p.pos[j + 1] += p.vel[j + 1] * dt\n p.pos[j + 2] += p.vel[j + 2] * dt\n if (p.kind === 'water' && p.pos[j + 1] < p.ground[i]) {\n // landed: stay on the surface and fade out this frame\n p.pos[j + 1] = p.ground[i]\n p.age[i] = p.life[i]\n }\n i++\n }\n }\n\n const write = (p: Pool): void => {\n const n = p.count\n const pa = p.aPos.array as Float32Array\n const sa = p.aSize.array as Float32Array\n const aa = p.aAlpha.array as Float32Array\n const ca = p.aColor.array as Float32Array\n for (let i = 0; i < n; i++) {\n const u = p.age[i] / p.life[i]\n const j = i * 3\n pa[j] = p.pos[j]\n pa[j + 1] = p.pos[j + 1]\n pa[j + 2] = p.pos[j + 2]\n sa[i] = p.size0[i] + (p.size1[i] - p.size0[i]) * u\n let alpha: number\n switch (p.kind) {\n case 'smoke':\n alpha = 0.55 * Math.min(1, u * 8) * (1 - u)\n ca[j] = ca[j + 1] = ca[j + 2] = p.tint[i]\n break\n case 'steam':\n alpha = 0.4 * Math.min(1, u * 6) * (1 - u)\n ca[j] = ca[j + 1] = ca[j + 2] = p.tint[i]\n break\n case 'fire':\n alpha = (p.flags[i] ? 0.95 : 0.8) * (1 - u * u)\n if (p.flags[i]) {\n ca[j] = 0.25 + 0.15 * p.tint[i]\n ca[j + 1] = 0.5 + 0.2 * p.tint[i]\n ca[j + 2] = 1\n } else {\n // yellow core -> orange -> red as it rises\n ca[j] = 1\n ca[j + 1] = Math.max(0.15, 0.85 - u * 0.7 - p.tint[i] * 0.1)\n ca[j + 2] = Math.max(0, 0.25 - u * 0.25)\n }\n break\n case 'spark':\n alpha = 1 - u\n ca[j] = 1\n ca[j + 1] = 0.75 - u * 0.4\n ca[j + 2] = 0.3\n break\n default: {\n const f = p.flags[i]\n alpha = f === 4 ? 0.16 * (1 - u) : f === 3 ? 0.8 * (1 - u * 0.5) : f === 2 ? 0.65 * (1 - u * 0.6) : 0.8\n ca[j] = f === 4 ? 0.85 : 0.5\n ca[j + 1] = f === 4 ? 0.92 : 0.74\n ca[j + 2] = 1\n }\n }\n aa[i] = alpha\n }\n p.aPos.needsUpdate = true\n p.aSize.needsUpdate = true\n p.aAlpha.needsUpdate = true\n p.aColor.needsUpdate = true\n p.points.geometry.setDrawRange(0, n)\n }\n\n return {\n objects,\n meshes: { group, streams, flames },\n update(dt, emitters, cam) {\n camera = cam ?? (typeof cameraSource === 'function' ? cameraSource() : cameraSource)\n refreshScale(camera)\n const step = Math.min(Math.max(dt, 0), 0.1)\n stamp++\n clock += step\n streaksTex.offset.y = -(clock * 2.4) % 1\n let nStreams = 0\n let nFlames = 0\n const due = (key: string, rate: number): number => {\n let d = debt.get(key)\n if (!d) {\n d = { carry: 0, stamp }\n debt.set(key, d)\n }\n d.stamp = stamp\n d.carry += rate * step\n const n = Math.floor(d.carry)\n d.carry -= n\n return n\n }\n for (let i = 0; i < emitters.length; i++) {\n const e = emitters[i]\n if (!(e.intensity > 0)) continue\n const k = Math.min(1, e.intensity)\n const size = e.size > 0 ? e.size : 0.05\n if (e.kind === 'water') {\n if (k < 0.25) {\n const n = due(e.id, 3 + k * 10)\n if (n > 0) emitDrip(e, n)\n } else if (arcOf(e, k)) {\n const nSpray = due(e.id, (60 + 260 * Math.min(size, 0.4)) * k)\n const nSplash = due(`${e.id}#s`, (45 + 90 * Math.min(size, 0.4)) * k)\n const nMist = due(`${e.id}#m`, 6 * k * (1 + size * 4))\n emitWater(e, k, nSpray, nSplash, nMist)\n if (nStreams < streams.length) writeStream(streams[nStreams++], k)\n }\n continue\n }\n if (e.kind === 'flame' && nFlames < maxFlames) writeFlame(nFlames++, e, clock)\n const n = due(e.id, RATE[e.kind] * k)\n if (n > 0) emitFrom(e, n)\n }\n for (let i = nStreams; i < streams.length; i++) streams[i].visible = false\n flames.visible = nFlames > 0\n flameGeo.setDrawRange(0, nFlames * CONES * 9)\n flamePos.needsUpdate = true\n flameCol.needsUpdate = true\n if (debt.size > emitters.length * 3 + 8) {\n for (const [id, d] of debt) if (d.stamp !== stamp) debt.delete(id)\n }\n for (const kind of POOLS) {\n advance(pools[kind], step)\n write(pools[kind])\n }\n },\n setWind(x, z) {\n wind[0] = x\n wind[1] = z\n },\n setCamera(cam) {\n cameraSource = cam\n camera = typeof cam === 'function' ? cam() : cam\n refreshScale(camera)\n },\n setViewHeight(px) {\n viewHeight = px\n refreshScale(camera)\n },\n stats() {\n const byKind = {} as Record<PoolKind, number>\n let alive = 0\n let capacity = 0\n for (const kind of POOLS) {\n byKind[kind] = pools[kind].count\n alive += pools[kind].count\n capacity += pools[kind].cap\n }\n return { alive, capacity, byKind }\n },\n clear() {\n for (const kind of POOLS) {\n pools[kind].count = 0\n write(pools[kind])\n }\n debt.clear()\n for (const m of streams) m.visible = false\n flames.visible = false\n },\n dispose() {\n for (const kind of POOLS) {\n scene.remove(objects[kind])\n objects[kind].geometry.dispose()\n ;(objects[kind].material as ThreeNS.Material).dispose()\n }\n scene.remove(group)\n for (const m of streams) m.geometry.dispose()\n flameGeo.dispose()\n ;(flames.material as ThreeNS.Material).dispose()\n streamMat.dispose()\n streaksTex.dispose()\n sprite.dispose()\n },\n }\n}\n","import type { Color } from './types'\n\n/** A colour as `0xRRGGBB`: numbers as given (floored, as three's `setHex`), `'#rrggbb'` strings parsed. */\nexport function colorHex(color: Color | undefined, fallback = 0xffffff): number {\n if (color === undefined) return fallback\n if (typeof color === 'number') return Math.floor(color)\n const match = /^#([0-9a-fA-F]{6})$/.exec(color)\n if (!match) throw new Error(`game-shapes: colour \"${color}\" is not 0xRRGGBB or #rrggbb`)\n return parseInt(match[1] as string, 16)\n}\n\n/** three's `SRGBToLinear` (r160 `ColorManagement`), the same constants. */\nexport const srgbToLinear = (c: number): number => (c < 0.04045 ? c * 0.0773993808 : Math.pow(c * 0.9478672986 + 0.0521327014, 2.4))\n\n/**\n * The rgb floats of a hex colour as three's `Color.setHex` stores them: the channels over 255 (`'srgb'`), converted to the linear working space\n * when colour management is on (`'linear'`).\n */\nexport function colorChannels(hex: number, space: 'linear' | 'srgb'): [number, number, number] {\n const r = ((hex >> 16) & 255) / 255\n const g = ((hex >> 8) & 255) / 255\n const b = (hex & 255) / 255\n return space === 'linear' ? [srgbToLinear(r), srgbToLinear(g), srgbToLinear(b)] : [r, g, b]\n}\n","/**\n * Canonical JSON (keys sorted, `undefined` left out, `-0` kept as `-0`, typed arrays as plain lists) and a 64-bit FNV-1a of it in two 32-bit\n * lanes: the key a shape is cached and shared by.\n */\nexport function canonicalJson(value: unknown): string {\n if (value === null) return 'null'\n if (typeof value === 'number') return Object.is(value, -0) ? '-0' : Number.isFinite(value) ? String(value) : JSON.stringify(String(value))\n if (typeof value === 'string') return JSON.stringify(value)\n if (typeof value === 'boolean') return value ? 'true' : 'false'\n if (typeof value === 'function') throw new Error('game-shapes: a function cannot be part of a shape (register it as a generator)')\n if (ArrayBuffer.isView(value)) return canonicalJson(Array.from(value as unknown as ArrayLike<number>))\n if (Array.isArray(value)) return `[${value.map((item) => (item === undefined ? 'null' : canonicalJson(item))).join(',')}]`\n if (typeof value === 'object') {\n const record = value as Record<string, unknown>\n const keys = Object.keys(record).filter((key) => record[key] !== undefined).sort()\n return `{${keys.map((key) => `${JSON.stringify(key)}:${canonicalJson(record[key])}`).join(',')}}`\n }\n return 'null'\n}\n\nconst lane = (text: string, offset: number, prime: number): string => {\n let hash = offset\n for (let index = 0; index < text.length; index++) {\n hash ^= text.charCodeAt(index)\n hash = Math.imul(hash, prime)\n }\n return (hash >>> 0).toString(16).padStart(8, '0')\n}\n\n/** 64-bit FNV-1a of a text as 16 hex digits (two independent 32-bit lanes). */\nexport const hash64 = (text: string): string => lane(text, 0x811c9dc5, 0x01000193) + lane(text, 0x050c5d1f, 0x5bd1e995)\n\n/** `hash64(canonicalJson(value))`. */\nexport const hashValue = (value: unknown): string => hash64(canonicalJson(value))\n","/*\n * Matrix and quaternion arithmetic as three.js r160 does it (MIT, https://github.com/mrdoob/three.js): the same expressions in the same order, so a\n * vertex baked here has the same bits as one baked by `Matrix4.compose` / `multiplyMatrices` / `Matrix3.getNormalMatrix` / `Vector3.normalize`.\n * Matrices are column-major arrays of 16 (4 x 4) or 9 (3 x 3) numbers, the layout of three's `elements`.\n */\nimport type { Mat4, RotationOrder, Transform, Vec3 } from './types'\n\nexport type Matrix4 = number[]\nexport type Matrix3 = number[]\n\nexport const identity4 = (): Matrix4 => [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]\n\n/** `Quaternion.setFromEuler` (x, y, z, w). */\nexport function quaternionFromEuler(x: number, y: number, z: number, order: RotationOrder = 'XYZ'): [number, number, number, number] {\n const cos = Math.cos\n const sin = Math.sin\n const c1 = cos(x / 2)\n const c2 = cos(y / 2)\n const c3 = cos(z / 2)\n const s1 = sin(x / 2)\n const s2 = sin(y / 2)\n const s3 = sin(z / 2)\n switch (order) {\n case 'XYZ':\n return [s1 * c2 * c3 + c1 * s2 * s3, c1 * s2 * c3 - s1 * c2 * s3, c1 * c2 * s3 + s1 * s2 * c3, c1 * c2 * c3 - s1 * s2 * s3]\n case 'YXZ':\n return [s1 * c2 * c3 + c1 * s2 * s3, c1 * s2 * c3 - s1 * c2 * s3, c1 * c2 * s3 - s1 * s2 * c3, c1 * c2 * c3 + s1 * s2 * s3]\n case 'ZXY':\n return [s1 * c2 * c3 - c1 * s2 * s3, c1 * s2 * c3 + s1 * c2 * s3, c1 * c2 * s3 + s1 * s2 * c3, c1 * c2 * c3 - s1 * s2 * s3]\n case 'ZYX':\n return [s1 * c2 * c3 - c1 * s2 * s3, c1 * s2 * c3 + s1 * c2 * s3, c1 * c2 * s3 - s1 * s2 * c3, c1 * c2 * c3 + s1 * s2 * s3]\n case 'YZX':\n return [s1 * c2 * c3 + c1 * s2 * s3, c1 * s2 * c3 + s1 * c2 * s3, c1 * c2 * s3 - s1 * s2 * c3, c1 * c2 * c3 - s1 * s2 * s3]\n case 'XZY':\n return [s1 * c2 * c3 - c1 * s2 * s3, c1 * s2 * c3 - s1 * c2 * s3, c1 * c2 * s3 + s1 * s2 * c3, c1 * c2 * c3 + s1 * s2 * s3]\n default:\n throw new Error(`game-shapes: unknown rotation order \"${String(order)}\"`)\n }\n}\n\n/** `Matrix4.compose(position, quaternion, scale)`. */\nexport function compose(px: number, py: number, pz: number, q: readonly [number, number, number, number], sx: number, sy: number, sz: number): Matrix4 {\n const [x, y, z, w] = q\n const x2 = x + x\n const y2 = y + y\n const z2 = z + z\n const xx = x * x2\n const xy = x * y2\n const xz = x * z2\n const yy = y * y2\n const yz = y * z2\n const zz = z * z2\n const wx = w * x2\n const wy = w * y2\n const wz = w * z2\n return [\n (1 - (yy + zz)) * sx, (xy + wz) * sx, (xz - wy) * sx, 0,\n (xy - wz) * sy, (1 - (xx + zz)) * sy, (yz + wx) * sy, 0,\n (xz + wy) * sz, (yz - wx) * sz, (1 - (xx + yy)) * sz, 0,\n px, py, pz, 1,\n ]\n}\n\n/** `Matrix4.makeRotationY(theta)` followed by `setPosition(x, y, z)`. */\nexport function headingMatrix(theta: number, x = 0, y = 0, z = 0): Matrix4 {\n const c = Math.cos(theta)\n const s = Math.sin(theta)\n return [c, 0, -s, 0, 0, 1, 0, 0, s, 0, c, 0, x, y, z, 1]\n}\n\n/** `Matrix4.multiplyMatrices(a, b)`: a x b. */\nexport function multiply(ae: readonly number[], be: readonly number[]): Matrix4 {\n const a11 = ae[0]!, a12 = ae[4]!, a13 = ae[8]!, a14 = ae[12]!\n const a21 = ae[1]!, a22 = ae[5]!, a23 = ae[9]!, a24 = ae[13]!\n const a31 = ae[2]!, a32 = ae[6]!, a33 = ae[10]!, a34 = ae[14]!\n const a41 = ae[3]!, a42 = ae[7]!, a43 = ae[11]!, a44 = ae[15]!\n const b11 = be[0]!, b12 = be[4]!, b13 = be[8]!, b14 = be[12]!\n const b21 = be[1]!, b22 = be[5]!, b23 = be[9]!, b24 = be[13]!\n const b31 = be[2]!, b32 = be[6]!, b33 = be[10]!, b34 = be[14]!\n const b41 = be[3]!, b42 = be[7]!, b43 = be[11]!, b44 = be[15]!\n return [\n a11 * b11 + a12 * b21 + a13 * b31 + a14 * b41,\n a21 * b11 + a22 * b21 + a23 * b31 + a24 * b41,\n a31 * b11 + a32 * b21 + a33 * b31 + a34 * b41,\n a41 * b11 + a42 * b21 + a43 * b31 + a44 * b41,\n a11 * b12 + a12 * b22 + a13 * b32 + a14 * b42,\n a21 * b12 + a22 * b22 + a23 * b32 + a24 * b42,\n a31 * b12 + a32 * b22 + a33 * b32 + a34 * b42,\n a41 * b12 + a42 * b22 + a43 * b32 + a44 * b42,\n a11 * b13 + a12 * b23 + a13 * b33 + a14 * b43,\n a21 * b13 + a22 * b23 + a23 * b33 + a24 * b43,\n a31 * b13 + a32 * b23 + a33 * b33 + a34 * b43,\n a41 * b13 + a42 * b23 + a43 * b33 + a44 * b43,\n a11 * b14 + a12 * b24 + a13 * b34 + a14 * b44,\n a21 * b14 + a22 * b24 + a23 * b34 + a24 * b44,\n a31 * b14 + a32 * b24 + a33 * b34 + a34 * b44,\n a41 * b14 + a42 * b24 + a43 * b34 + a44 * b44,\n ]\n}\n\n/** `Matrix3.getNormalMatrix(m)`: the inverse transpose of the upper 3 x 3 (all zeros when singular, as three). */\nexport function normalMatrix(me: readonly number[]): Matrix3 {\n // setFromMatrix4\n const n11 = me[0]!, n21 = me[1]!, n31 = me[2]!\n const n12 = me[4]!, n22 = me[5]!, n32 = me[6]!\n const n13 = me[8]!, n23 = me[9]!, n33 = me[10]!\n // invert\n const t11 = n33 * n22 - n32 * n23\n const t12 = n32 * n13 - n33 * n12\n const t13 = n23 * n12 - n22 * n13\n const det = n11 * t11 + n21 * t12 + n31 * t13\n if (det === 0) return [0, 0, 0, 0, 0, 0, 0, 0, 0]\n const detInv = 1 / det\n const te = [\n t11 * detInv,\n (n31 * n23 - n33 * n21) * detInv,\n (n32 * n21 - n31 * n22) * detInv,\n t12 * detInv,\n (n33 * n11 - n31 * n13) * detInv,\n (n31 * n12 - n32 * n11) * detInv,\n t13 * detInv,\n (n21 * n13 - n23 * n11) * detInv,\n (n22 * n11 - n21 * n12) * detInv,\n ]\n // transpose\n let tmp = te[1]!\n te[1] = te[3]!\n te[3] = tmp\n tmp = te[2]!\n te[2] = te[6]!\n te[6] = tmp\n tmp = te[5]!\n te[5] = te[7]!\n te[7] = tmp\n return te\n}\n\n/** True when the last row is (0, 0, 0, 1): `applyMatrix4`'s divide by w is then a multiply by exactly 1. */\nexport const isAffine = (e: readonly number[]): boolean => e[3] === 0 && e[7] === 0 && e[11] === 0 && e[15] === 1\n\n/** `Vector3.applyMatrix4`. */\nexport function transformPoint(e: readonly number[], x: number, y: number, z: number): [number, number, number] {\n const w = 1 / (e[3]! * x + e[7]! * y + e[11]! * z + e[15]!)\n return [(e[0]! * x + e[4]! * y + e[8]! * z + e[12]!) * w, (e[1]! * x + e[5]! * y + e[9]! * z + e[13]!) * w, (e[2]! * x + e[6]! * y + e[10]! * z + e[14]!) * w]\n}\n\nconst toVec3 = (scale: number | Vec3 | undefined): Vec3 => (scale === undefined ? [1, 1, 1] : typeof scale === 'number' ? [scale, scale, scale] : scale)\n\n/** True when the transform has no field set. */\nexport const isIdentityTransform = (t: Transform): boolean =>\n t.matrix === undefined && t.heading === undefined && t.position === undefined && t.rotation === undefined && t.scale === undefined\n\n/** The local matrix of a transform, or `null` when it has none. */\nexport function matrixOf(t: Transform): Matrix4 | null {\n if (t.matrix !== undefined) return [...t.matrix]\n if (t.heading !== undefined) {\n const p = t.position ?? [0, 0, 0]\n return headingMatrix(t.heading, p[0], p[1], p[2])\n }\n if (t.position === undefined && t.rotation === undefined && t.scale === undefined) return null\n const p = t.position ?? [0, 0, 0]\n const r = t.rotation ?? [0, 0, 0]\n const s = toVec3(t.scale)\n return compose(p[0], p[1], p[2], quaternionFromEuler(r[0], r[1], r[2], t.rotationOrder ?? 'XYZ'), s[0], s[1], s[2])\n}\n\n/** The matrix as given, as a plain array (for `ShapePart.matrix`). */\nexport const asMat4 = (m: Matrix4 | null): Mat4 => (m ? [...m] : identity4())\n","/*\n * The geometry generators of three.js r160 (MIT, https://github.com/mrdoob/three.js, src/geometries/*.js), ported line by line: the same loops,\n * the same expressions in the same order, numbers pushed into plain arrays and stored as Float32 at the end, exactly as `Float32BufferAttribute`\n * does. A test compares every array of every generator with three's, bit for bit, over a grid of parameters.\n */\nimport type { GeometryGroup, PrimitiveBuffers } from '../types'\n\nexport interface Arrays {\n vertices: number[]\n normals: number[]\n uvs: number[]\n indices: number[] | null\n groups: GeometryGroup[]\n}\n\n/** three's `arrayNeedsUint32`: 32-bit indices once any index reaches 65535. */\nexport function needsUint32(array: ArrayLike<number>): boolean {\n for (let i = array.length - 1; i >= 0; --i) if ((array[i] as number) >= 65535) return true\n return false\n}\n\nexport const indexArray = (indices: ArrayLike<number>): Uint16Array | Uint32Array => (needsUint32(indices) ? new Uint32Array(indices) : new Uint16Array(indices))\n\nexport function pack(type: string, parameters: Record<string, unknown>, a: Arrays): PrimitiveBuffers {\n return {\n type,\n parameters,\n positions: new Float32Array(a.vertices),\n normals: new Float32Array(a.normals),\n uvs: new Float32Array(a.uvs),\n ...(a.indices ? { indices: indexArray(a.indices) } : {}),\n groups: a.groups,\n }\n}\n\nconst normalize3 = (x: number, y: number, z: number): [number, number, number] => {\n const length = Math.sqrt(x * x + y * y + z * z)\n const s = 1 / (length || 1)\n return [x * s, y * s, z * s]\n}\n\nexport function box(width = 1, height = 1, depth = 1, widthSegments = 1, heightSegments = 1, depthSegments = 1): PrimitiveBuffers {\n const parameters = { width, height, depth, widthSegments, heightSegments, depthSegments }\n widthSegments = Math.floor(widthSegments)\n heightSegments = Math.floor(heightSegments)\n depthSegments = Math.floor(depthSegments)\n const indices: number[] = []\n const vertices: number[] = []\n const normals: number[] = []\n const uvs: number[] = []\n const groups: GeometryGroup[] = []\n let numberOfVertices = 0\n let groupStart = 0\n type Axis = 'x' | 'y' | 'z'\n const buildPlane = (u: Axis, v: Axis, w: Axis, udir: number, vdir: number, width: number, height: number, depth: number, gridX: number, gridY: number, materialIndex: number): void => {\n const segmentWidth = width / gridX\n const segmentHeight = height / gridY\n const widthHalf = width / 2\n const heightHalf = height / 2\n const depthHalf = depth / 2\n const gridX1 = gridX + 1\n const gridY1 = gridY + 1\n let vertexCounter = 0\n let groupCount = 0\n const vector = { x: 0, y: 0, z: 0 }\n for (let iy = 0; iy < gridY1; iy++) {\n const y = iy * segmentHeight - heightHalf\n for (let ix = 0; ix < gridX1; ix++) {\n const x = ix * segmentWidth - widthHalf\n vector[u] = x * udir\n vector[v] = y * vdir\n vector[w] = depthHalf\n vertices.push(vector.x, vector.y, vector.z)\n vector[u] = 0\n vector[v] = 0\n vector[w] = depth > 0 ? 1 : -1\n normals.push(vector.x, vector.y, vector.z)\n uvs.push(ix / gridX)\n uvs.push(1 - iy / gridY)\n vertexCounter += 1\n }\n }\n for (let iy = 0; iy < gridY; iy++) {\n for (let ix = 0; ix < gridX; ix++) {\n const a = numberOfVertices + ix + gridX1 * iy\n const b = numberOfVertices + ix + gridX1 * (iy + 1)\n const c = numberOfVertices + (ix + 1) + gridX1 * (iy + 1)\n const d = numberOfVertices + (ix + 1) + gridX1 * iy\n indices.push(a, b, d)\n indices.push(b, c, d)\n groupCount += 6\n }\n }\n groups.push({ start: groupStart, count: groupCount, materialIndex })\n groupStart += groupCount\n numberOfVertices += vertexCounter\n }\n buildPlane('z', 'y', 'x', -1, -1, depth, height, width, depthSegments, heightSegments, 0)\n buildPlane('z', 'y', 'x', 1, -1, depth, height, -width, depthSegments, heightSegments, 1)\n buildPlane('x', 'z', 'y', 1, 1, width, depth, height, widthSegments, depthSegments, 2)\n buildPlane('x', 'z', 'y', 1, -1, width, depth, -height, widthSegments, depthSegments, 3)\n buildPlane('x', 'y', 'z', 1, -1, width, height, depth, widthSegments, heightSegments, 4)\n buildPlane('x', 'y', 'z', -1, -1, width, height, -depth, widthSegments, heightSegments, 5)\n return pack('BoxGeometry', parameters, { vertices, normals, uvs, indices, groups })\n}\n\nfunction cylinderArrays(radiusTop: number, radiusBottom: number, height: number, radialSegments: number, heightSegments: number, openEnded: boolean, thetaStart: number, thetaLength: number): Arrays {\n radialSegments = Math.floor(radialSegments)\n heightSegments = Math.floor(heightSegments)\n const indices: number[] = []\n const vertices: number[] = []\n const normals: number[] = []\n const uvs: number[] = []\n const groups: GeometryGroup[] = []\n let index = 0\n const indexArray: number[][] = []\n const halfHeight = height / 2\n let groupStart = 0\n\n const generateTorso = (): void => {\n let groupCount = 0\n const slope = (radiusBottom - radiusTop) / height\n for (let y = 0; y <= heightSegments; y++) {\n const indexRow: number[] = []\n const v = y / heightSegments\n const radius = v * (radiusBottom - radiusTop) + radiusTop\n for (let x = 0; x <= radialSegments; x++) {\n const u = x / radialSegments\n const theta = u * thetaLength + thetaStart\n const sinTheta = Math.sin(theta)\n const cosTheta = Math.cos(theta)\n vertices.push(radius * sinTheta, -v * height + halfHeight, radius * cosTheta)\n const n = normalize3(sinTheta, slope, cosTheta)\n normals.push(n[0], n[1], n[2])\n uvs.push(u, 1 - v)\n indexRow.push(index++)\n }\n indexArray.push(indexRow)\n }\n for (let x = 0; x < radialSegments; x++) {\n for (let y = 0; y < heightSegments; y++) {\n const a = indexArray[y]![x]!\n const b = indexArray[y + 1]![x]!\n const c = indexArray[y + 1]![x + 1]!\n const d = indexArray[y]![x + 1]!\n indices.push(a, b, d)\n indices.push(b, c, d)\n groupCount += 6\n }\n }\n groups.push({ start: groupStart, count: groupCount, materialIndex: 0 })\n groupStart += groupCount\n }\n\n const generateCap = (top: boolean): void => {\n const centerIndexStart = index\n let groupCount = 0\n const radius = top === true ? radiusTop : radiusBottom\n const sign = top === true ? 1 : -1\n for (let x = 1; x <= radialSegments; x++) {\n vertices.push(0, halfHeight * sign, 0)\n normals.push(0, sign, 0)\n uvs.push(0.5, 0.5)\n index++\n }\n const centerIndexEnd = index\n for (let x = 0; x <= radialSegments; x++) {\n const u = x / radialSegments\n const theta = u * thetaLength + thetaStart\n const cosTheta = Math.cos(theta)\n const sinTheta = Math.sin(theta)\n vertices.push(radius * sinTheta, halfHeight * sign, radius * cosTheta)\n normals.push(0, sign, 0)\n uvs.push(cosTheta * 0.5 + 0.5, sinTheta * 0.5 * sign + 0.5)\n index++\n }\n for (let x = 0; x < radialSegments; x++) {\n const c = centerIndexStart + x\n const i = centerIndexEnd + x\n if (top === true) indices.push(i, i + 1, c)\n else indices.push(i + 1, i, c)\n groupCount += 3\n }\n groups.push({ start: groupStart, count: groupCount, materialIndex: top === true ? 1 : 2 })\n groupStart += groupCount\n }\n\n generateTorso()\n if (openEnded === false) {\n if (radiusTop > 0) generateCap(true)\n if (radiusBottom > 0) generateCap(false)\n }\n return { vertices, normals, uvs, indices, groups }\n}\n\nexport function cylinder(radiusTop = 1, radiusBottom = 1, height = 1, radialSegments = 32, heightSegments = 1, openEnded = false, thetaStart = 0, thetaLength = Math.PI * 2): PrimitiveBuffers {\n const parameters = { radiusTop, radiusBottom, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength }\n return pack('CylinderGeometry', parameters, cylinderArrays(radiusTop, radiusBottom, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength))\n}\n\nexport function cone(radius = 1, height = 1, radialSegments = 32, heightSegments = 1, openEnded = false, thetaStart = 0, thetaLength = Math.PI * 2): PrimitiveBuffers {\n const parameters = { radius, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength }\n return pack('ConeGeometry', parameters, cylinderArrays(0, radius, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength))\n}\n\nexport function sphere(radius = 1, widthSegments = 32, heightSegments = 16, phiStart = 0, phiLength = Math.PI * 2, thetaStart = 0, thetaLength = Math.PI): PrimitiveBuffers {\n const parameters = { radius, widthSegments, heightSegments, phiStart, phiLength, thetaStart, thetaLength }\n widthSegments = Math.max(3, Math.floor(widthSegments))\n heightSegments = Math.max(2, Math.floor(heightSegments))\n const thetaEnd = Math.min(thetaStart + thetaLength, Math.PI)\n let index = 0\n const grid: number[][] = []\n const indices: number[] = []\n const vertices: number[] = []\n const normals: number[] = []\n const uvs: number[] = []\n for (let iy = 0; iy <= heightSegments; iy++) {\n const verticesRow: number[] = []\n const v = iy / heightSegments\n let uOffset = 0\n if (iy === 0 && thetaStart === 0) uOffset = 0.5 / widthSegments\n else if (iy === heightSegments && thetaEnd === Math.PI) uOffset = -0.5 / widthSegments\n for (let ix = 0; ix <= widthSegments; ix++) {\n const u = ix / widthSegments\n const x = -radius * Math.cos(phiStart + u * phiLength) * Math.sin(thetaStart + v * thetaLength)\n const y = radius * Math.cos(thetaStart + v * thetaLength)\n const z = radius * Math.sin(phiStart + u * phiLength) * Math.sin(thetaStart + v * thetaLength)\n vertices.push(x, y, z)\n const n = normalize3(x, y, z)\n normals.push(n[0], n[1], n[2])\n uvs.push(u + uOffset, 1 - v)\n verticesRow.push(index++)\n }\n grid.push(verticesRow)\n }\n for (let iy = 0; iy < heightSegments; iy++) {\n for (let ix = 0; ix < widthSegments; ix++) {\n const a = grid[iy]![ix + 1]!\n const b = grid[iy]![ix]!\n const c = grid[iy + 1]![ix]!\n const d = grid[iy + 1]![ix + 1]!\n if (iy !== 0 || thetaStart > 0) indices.push(a, b, d)\n if (iy !== heightSegments - 1 || thetaEnd < Math.PI) indices.push(b, c, d)\n }\n }\n return pack('SphereGeometry', parameters, { vertices, normals, uvs, indices, groups: [] })\n}\n\nexport function plane(width = 1, height = 1, widthSegments = 1, heightSegments = 1): PrimitiveBuffers {\n const parameters = { width, height, widthSegments, heightSegments }\n const widthHalf = width / 2\n const heightHalf = height / 2\n const gridX = Math.floor(widthSegments)\n const gridY = Math.floor(heightSegments)\n const gridX1 = gridX + 1\n const gridY1 = gridY + 1\n const segmentWidth = width / gridX\n const segmentHeight = height / gridY\n const indices: number[] = []\n const vertices: number[] = []\n const normals: number[] = []\n const uvs: number[] = []\n for (let iy = 0; iy < gridY1; iy++) {\n const y = iy * segmentHeight - heightHalf\n for (let ix = 0; ix < gridX1; ix++) {\n const x = ix * segmentWidth - widthHalf\n vertices.push(x, -y, 0)\n normals.push(0, 0, 1)\n uvs.push(ix / gridX)\n uvs.push(1 - iy / gridY)\n }\n }\n for (let iy = 0; iy < gridY; iy++) {\n for (let ix = 0; ix < gridX; ix++) {\n const a = ix + gridX1 * iy\n const b = ix + gridX1 * (iy + 1)\n const c = ix + 1 + gridX1 * (iy + 1)\n const d = ix + 1 + gridX1 * iy\n indices.push(a, b, d)\n indices.push(b, c, d)\n }\n }\n return pack('PlaneGeometry', parameters, { vertices, normals, uvs, indices, groups: [] })\n}\n\nexport function circle(radius = 1, segments = 32, thetaStart = 0, thetaLength = Math.PI * 2): PrimitiveBuffers {\n const parameters = { radius, segments, thetaStart, thetaLength }\n segments = Math.max(3, segments)\n const indices: number[] = []\n const vertices: number[] = []\n const normals: number[] = []\n const uvs: number[] = []\n vertices.push(0, 0, 0)\n normals.push(0, 0, 1)\n uvs.push(0.5, 0.5)\n for (let s = 0, i = 3; s <= segments; s++, i += 3) {\n const segment = thetaStart + (s / segments) * thetaLength\n // the vector's z stays 0, as three's Vector3\n vertices.push(radius * Math.cos(segment), radius * Math.sin(segment), 0)\n normals.push(0, 0, 1)\n uvs.push(((vertices[i] as number) / radius + 1) / 2, ((vertices[i + 1] as number) / radius + 1) / 2)\n }\n for (let i = 1; i <= segments; i++) indices.push(i, i + 1, 0)\n return pack('CircleGeometry', parameters, { vertices, normals, uvs, indices, groups: [] })\n}\n\nexport function ring(innerRadius = 0.5, outerRadius = 1, thetaSegments = 32, phiSegments = 1, thetaStart = 0, thetaLength = Math.PI * 2): PrimitiveBuffers {\n const parameters = { innerRadius, outerRadius, thetaSegments, phiSegments, thetaStart, thetaLength }\n thetaSegments = Math.max(3, thetaSegments)\n phiSegments = Math.max(1, phiSegments)\n const indices: number[] = []\n const vertices: number[] = []\n const normals: number[] = []\n const uvs: number[] = []\n let radius = innerRadius\n const radiusStep = (outerRadius - innerRadius) / phiSegments\n for (let j = 0; j <= phiSegments; j++) {\n for (let i = 0; i <= thetaSegments; i++) {\n const segment = thetaStart + (i / thetaSegments) * thetaLength\n const x = radius * Math.cos(segment)\n const y = radius * Math.sin(segment)\n vertices.push(x, y, 0)\n normals.push(0, 0, 1)\n uvs.push((x / outerRadius + 1) / 2, (y / outerRadius + 1) / 2)\n }\n radius += radiusStep\n }\n for (let j = 0; j < phiSegments; j++) {\n const thetaSegmentLevel = j * (thetaSegments + 1)\n for (let i = 0; i < thetaSegments; i++) {\n const segment = i + thetaSegmentLevel\n const a = segment\n const b = segment + thetaSegments + 1\n const c = segment + thetaSegments + 2\n const d = segment + 1\n indices.push(a, b, d)\n indices.push(b, c, d)\n }\n }\n return pack('RingGeometry', parameters, { vertices, normals, uvs, indices, groups: [] })\n}\n\nexport function lathe(points: ReadonlyArray<readonly [number, number]>, segments = 12, phiStart = 0, phiLength = Math.PI * 2): PrimitiveBuffers {\n const parameters = { points: points.map(([x, y]) => ({ x, y })), segments, phiStart, phiLength }\n segments = Math.floor(segments)\n phiLength = Math.max(0, Math.min(Math.PI * 2, phiLength))\n const indices: number[] = []\n const vertices: number[] = []\n const uvs: number[] = []\n const initNormals: number[] = []\n const normals: number[] = []\n const inverseSegments = 1.0 / segments\n const normal = { x: 0, y: 0, z: 0 }\n const curNormal = { x: 0, y: 0, z: 0 }\n const prevNormal = { x: 0, y: 0, z: 0 }\n let dx = 0\n let dy = 0\n const last = points.length - 1\n const px = (j: number): number => (points[j] as readonly [number, number])[0]\n const py = (j: number): number => (points[j] as readonly [number, number])[1]\n const setNormalized = (): void => {\n const n = normalize3(normal.x, normal.y, normal.z)\n normal.x = n[0]\n normal.y = n[1]\n normal.z = n[2]\n }\n for (let j = 0; j <= last; j++) {\n if (j === 0) {\n dx = px(j + 1) - px(j)\n dy = py(j + 1) - py(j)\n normal.x = dy * 1.0\n normal.y = -dx\n normal.z = dy * 0.0\n prevNormal.x = normal.x\n prevNormal.y = normal.y\n prevNormal.z = normal.z\n setNormalized()\n initNormals.push(normal.x, normal.y, normal.z)\n } else if (j === last) {\n initNormals.push(prevNormal.x, prevNormal.y, prevNormal.z)\n } else {\n dx = px(j + 1) - px(j)\n dy = py(j + 1) - py(j)\n normal.x = dy * 1.0\n normal.y = -dx\n normal.z = dy * 0.0\n curNormal.x = normal.x\n curNormal.y = normal.y\n curNormal.z = normal.z\n normal.x += prevNormal.x\n normal.y += prevNormal.y\n normal.z += prevNormal.z\n setNormalized()\n initNormals.push(normal.x, normal.y, normal.z)\n prevNormal.x = curNormal.x\n prevNormal.y = curNormal.y\n prevNormal.z = curNormal.z\n }\n }\n for (let i = 0; i <= segments; i++) {\n const phi = phiStart + i * inverseSegments * phiLength\n const sin = Math.sin(phi)\n const cos = Math.cos(phi)\n for (let j = 0; j <= last; j++) {\n vertices.push(px(j) * sin, py(j), px(j) * cos)\n uvs.push(i / segments, j / (points.length - 1))\n const x = (initNormals[3 * j + 0] as number) * sin\n const y = initNormals[3 * j + 1] as number\n const z = (initNormals[3 * j + 0] as number) * cos\n normals.push(x, y, z)\n }\n }\n for (let i = 0; i < segments; i++) {\n for (let j = 0; j < points.length - 1; j++) {\n const base = j + i * points.length\n const a = base\n const b = base + points.length\n const c = base + points.length + 1\n const d = base + 1\n indices.push(a, b, d)\n indices.push(c, d, b)\n }\n }\n return pack('LatheGeometry', parameters, { vertices, normals, uvs, indices, groups: [] })\n}\n\nexport function torus(radius = 1, tube = 0.4, radialSegments = 12, tubularSegments = 48, arc = Math.PI * 2): PrimitiveBuffers {\n const parameters = { radius, tube, radialSegments, tubularSegments, arc }\n radialSegments = Math.floor(radialSegments)\n tubularSegments = Math.floor(tubularSegments)\n const indices: number[] = []\n const vertices: number[] = []\n const normals: number[] = []\n const uvs: number[] = []\n for (let j = 0; j <= radialSegments; j++) {\n for (let i = 0; i <= tubularSegments; i++) {\n const u = (i / tubularSegments) * arc\n const v = (j / radialSegments) * Math.PI * 2\n const x = (radius + tube * Math.cos(v)) * Math.cos(u)\n const y = (radius + tube * Math.cos(v)) * Math.sin(u)\n const z = tube * Math.sin(v)\n vertices.push(x, y, z)\n const cx = radius * Math.cos(u)\n const cy = radius * Math.sin(u)\n const n = normalize3(x - cx, y - cy, z - 0)\n normals.push(n[0], n[1], n[2])\n uvs.push(i / tubularSegments)\n uvs.push(j / radialSegments)\n }\n }\n for (let j = 1; j <= radialSegments; j++) {\n for (let i = 1; i <= tubularSegments; i++) {\n const a = (tubularSegments + 1) * j + i - 1\n const b = (tubularSegments + 1) * (j - 1) + i - 1\n const c = (tubularSegments + 1) * (j - 1) + i\n const d = (tubularSegments + 1) * j + i\n indices.push(a, b, d)\n indices.push(b, c, d)\n }\n }\n return pack('TorusGeometry', parameters, { vertices, normals, uvs, indices, groups: [] })\n}\n","import type { HoleSpec, PrimitiveBuffers, Vec3 } from '../types'\nimport { indexArray } from './three-generators'\n\n/**\n * A box (centred, `size = [w, h, d]`) minus rectangular prisms that go all the way through it along z: the cheap, exact boolean (walls with\n * windows and doors). The face is cut into a grid at every hole edge; each solid cell gets its front and back quads, and every cell edge between\n * solid and empty (a hole or the outside) gets a quad over the depth facing the empty side, so the outer sides and the reveals come out of the\n * same rule. Front uvs map the face to [0, 1]; side uvs run along the edge and the depth.\n */\nexport function difference(size: Vec3, subtract: readonly HoleSpec[]): PrimitiveBuffers {\n const [w, h, d] = size\n const hw = w / 2\n const hh = h / 2\n const hd = d / 2\n const holes = subtract\n .map((hole) => ({ x0: Math.max(-hw, Math.min(hole.min[0], hole.max[0])), x1: Math.min(hw, Math.max(hole.min[0], hole.max[0])), y0: Math.max(-hh, Math.min(hole.min[1], hole.max[1])), y1: Math.min(hh, Math.max(hole.min[1], hole.max[1])) }))\n .filter((hole) => hole.x1 > hole.x0 && hole.y1 > hole.y0)\n const cuts = (lo: number, hi: number, values: number[]): number[] => [...new Set([lo, hi, ...values.filter((v) => v > lo && v < hi)])].sort((a, b) => a - b)\n const xs = cuts(-hw, hw, holes.flatMap((hole) => [hole.x0, hole.x1]))\n const ys = cuts(-hh, hh, holes.flatMap((hole) => [hole.y0, hole.y1]))\n const nx = xs.length - 1\n const ny = ys.length - 1\n const solid = (i: number, j: number): boolean => {\n if (i < 0 || j < 0 || i >= nx || j >= ny) return false\n const cx = ((xs[i] as number) + (xs[i + 1] as number)) / 2\n const cy = ((ys[j] as number) + (ys[j + 1] as number)) / 2\n return !holes.some((hole) => cx > hole.x0 && cx < hole.x1 && cy > hole.y0 && cy < hole.y1)\n }\n const positions: number[] = []\n const normals: number[] = []\n const uvs: number[] = []\n const indices: number[] = []\n const quad = (corners: readonly (readonly [number, number, number])[], normal: readonly [number, number, number], uv: readonly (readonly [number, number])[]): void => {\n const base = positions.length / 3\n for (let k = 0; k < 4; k++) {\n positions.push(...(corners[k] as [number, number, number]))\n normals.push(...normal)\n uvs.push(...(uv[k] as [number, number]))\n }\n indices.push(base, base + 1, base + 2, base, base + 2, base + 3)\n }\n const fu = (x: number): number => (x + hw) / w\n const fv = (y: number): number => (y + hh) / h\n for (let j = 0; j < ny; j++) {\n for (let i = 0; i < nx; i++) {\n if (!solid(i, j)) continue\n const x0 = xs[i] as number\n const x1 = xs[i + 1] as number\n const y0 = ys[j] as number\n const y1 = ys[j + 1] as number\n quad([[x0, y0, hd], [x1, y0, hd], [x1, y1, hd], [x0, y1, hd]], [0, 0, 1], [[fu(x0), fv(y0)], [fu(x1), fv(y0)], [fu(x1), fv(y1)], [fu(x0), fv(y1)]])\n quad([[x1, y0, -hd], [x0, y0, -hd], [x0, y1, -hd], [x1, y1, -hd]], [0, 0, -1], [[1 - fu(x1), fv(y0)], [1 - fu(x0), fv(y0)], [1 - fu(x0), fv(y1)], [1 - fu(x1), fv(y1)]])\n const zu = (z: number): number => (z + hd) / d\n if (!solid(i - 1, j)) quad([[x0, y0, -hd], [x0, y0, hd], [x0, y1, hd], [x0, y1, -hd]], [-1, 0, 0], [[zu(-hd), fv(y0)], [zu(hd), fv(y0)], [zu(hd), fv(y1)], [zu(-hd), fv(y1)]])\n if (!solid(i + 1, j)) quad([[x1, y0, hd], [x1, y0, -hd], [x1, y1, -hd], [x1, y1, hd]], [1, 0, 0], [[zu(hd), fv(y0)], [zu(-hd), fv(y0)], [zu(-hd), fv(y1)], [zu(hd), fv(y1)]])\n if (!solid(i, j - 1)) quad([[x0, y0, -hd], [x1, y0, -hd], [x1, y0, hd], [x0, y0, hd]], [0, -1, 0], [[fu(x0), zu(-hd)], [fu(x1), zu(-hd)], [fu(x1), zu(hd)], [fu(x0), zu(hd)]])\n if (!solid(i, j + 1)) quad([[x0, y1, hd], [x1, y1, hd], [x1, y1, -hd], [x0, y1, -hd]], [0, 1, 0], [[fu(x0), zu(hd)], [fu(x1), zu(hd)], [fu(x1), zu(-hd)], [fu(x0), zu(-hd)]])\n }\n }\n return {\n type: 'BufferGeometry',\n parameters: { size: [w, h, d], subtract: holes.map((hole) => ({ min: [hole.x0, hole.y0], max: [hole.x1, hole.y1] })) },\n positions: new Float32Array(positions),\n normals: new Float32Array(normals),\n uvs: new Float32Array(uvs),\n indices: indexArray(indices),\n groups: [],\n }\n}\n","/* eslint-disable */\n// @ts-nocheck -- verbatim copy of three.js r160 src/extras/Earcut.js (MIT), itself a port of mapbox/earcut v2.2.4 (ISC); kept as is so triangulation matches three\n/**\n * Port from https://github.com/mapbox/earcut (v2.2.4)\n */\n\nconst Earcut = {\n\n\ttriangulate: function ( data, holeIndices, dim = 2 ) {\n\n\t\tconst hasHoles = holeIndices && holeIndices.length;\n\t\tconst outerLen = hasHoles ? holeIndices[ 0 ] * dim : data.length;\n\t\tlet outerNode = linkedList( data, 0, outerLen, dim, true );\n\t\tconst triangles = [];\n\n\t\tif ( ! outerNode || outerNode.next === outerNode.prev ) return triangles;\n\n\t\tlet minX, minY, maxX, maxY, x, y, invSize;\n\n\t\tif ( hasHoles ) outerNode = eliminateHoles( data, holeIndices, outerNode, dim );\n\n\t\t// if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox\n\t\tif ( data.length > 80 * dim ) {\n\n\t\t\tminX = maxX = data[ 0 ];\n\t\t\tminY = maxY = data[ 1 ];\n\n\t\t\tfor ( let i = dim; i < outerLen; i += dim ) {\n\n\t\t\t\tx = data[ i ];\n\t\t\t\ty = data[ i + 1 ];\n\t\t\t\tif ( x < minX ) minX = x;\n\t\t\t\tif ( y < minY ) minY = y;\n\t\t\t\tif ( x > maxX ) maxX = x;\n\t\t\t\tif ( y > maxY ) maxY = y;\n\n\t\t\t}\n\n\t\t\t// minX, minY and invSize are later used to transform coords into integers for z-order calculation\n\t\t\tinvSize = Math.max( maxX - minX, maxY - minY );\n\t\t\tinvSize = invSize !== 0 ? 32767 / invSize : 0;\n\n\t\t}\n\n\t\tearcutLinked( outerNode, triangles, dim, minX, minY, invSize, 0 );\n\n\t\treturn triangles;\n\n\t}\n\n};\n\n// create a circular doubly linked list from polygon points in the specified winding order\nfunction linkedList( data, start, end, dim, clockwise ) {\n\n\tlet i, last;\n\n\tif ( clockwise === ( signedArea( data, start, end, dim ) > 0 ) ) {\n\n\t\tfor ( i = start; i < end; i += dim ) last = insertNode( i, data[ i ], data[ i + 1 ], last );\n\n\t} else {\n\n\t\tfor ( i = end - dim; i >= start; i -= dim ) last = insertNode( i, data[ i ], data[ i + 1 ], last );\n\n\t}\n\n\tif ( last && equals( last, last.next ) ) {\n\n\t\tremoveNode( last );\n\t\tlast = last.next;\n\n\t}\n\n\treturn last;\n\n}\n\n// eliminate colinear or duplicate points\nfunction filterPoints( start, end ) {\n\n\tif ( ! start ) return start;\n\tif ( ! end ) end = start;\n\n\tlet p = start,\n\t\tagain;\n\tdo {\n\n\t\tagain = false;\n\n\t\tif ( ! p.steiner && ( equals( p, p.next ) || area( p.prev, p, p.next ) === 0 ) ) {\n\n\t\t\tremoveNode( p );\n\t\t\tp = end = p.prev;\n\t\t\tif ( p === p.next ) break;\n\t\t\tagain = true;\n\n\t\t} else {\n\n\t\t\tp = p.next;\n\n\t\t}\n\n\t} while ( again || p !== end );\n\n\treturn end;\n\n}\n\n// main ear slicing loop which triangulates a polygon (given as a linked list)\nfunction earcutLinked( ear, triangles, dim, minX, minY, invSize, pass ) {\n\n\tif ( ! ear ) return;\n\n\t// interlink polygon nodes in z-order\n\tif ( ! pass && invSize ) indexCurve( ear, minX, minY, invSize );\n\n\tlet stop = ear,\n\t\tprev, next;\n\n\t// iterate through ears, slicing them one by one\n\twhile ( ear.prev !== ear.next ) {\n\n\t\tprev = ear.prev;\n\t\tnext = ear.next;\n\n\t\tif ( invSize ? isEarHashed( ear, minX, minY, invSize ) : isEar( ear ) ) {\n\n\t\t\t// cut off the triangle\n\t\t\ttriangles.push( prev.i / dim | 0 );\n\t\t\ttriangles.push( ear.i / dim | 0 );\n\t\t\ttriangles.push( next.i / dim | 0 );\n\n\t\t\tremoveNode( ear );\n\n\t\t\t// skipping the next vertex leads to less sliver triangles\n\t\t\tear = next.next;\n\t\t\tstop = next.next;\n\n\t\t\tcontinue;\n\n\t\t}\n\n\t\tear = next;\n\n\t\t// if we looped through the whole remaining polygon and can't find any more ears\n\t\tif ( ear === stop ) {\n\n\t\t\t// try filtering points and slicing again\n\t\t\tif ( ! pass ) {\n\n\t\t\t\tearcutLinked( filterPoints( ear ), triangles, dim, minX, minY, invSize, 1 );\n\n\t\t\t\t// if this didn't work, try curing all small self-intersections locally\n\n\t\t\t} else if ( pass === 1 ) {\n\n\t\t\t\tear = cureLocalIntersections( filterPoints( ear ), triangles, dim );\n\t\t\t\tearcutLinked( ear, triangles, dim, minX, minY, invSize, 2 );\n\n\t\t\t\t// as a last resort, try splitting the remaining polygon into two\n\n\t\t\t} else if ( pass === 2 ) {\n\n\t\t\t\tsplitEarcut( ear, triangles, dim, minX, minY, invSize );\n\n\t\t\t}\n\n\t\t\tbreak;\n\n\t\t}\n\n\t}\n\n}\n\n// check whether a polygon node forms a valid ear with adjacent nodes\nfunction isEar( ear ) {\n\n\tconst a = ear.prev,\n\t\tb = ear,\n\t\tc = ear.next;\n\n\tif ( area( a, b, c ) >= 0 ) return false; // reflex, can't be an ear\n\n\t// now make sure we don't have other points inside the potential ear\n\tconst ax = a.x, bx = b.x, cx = c.x, ay = a.y, by = b.y, cy = c.y;\n\n\t// triangle bbox; min & max are calculated like this for speed\n\tconst x0 = ax < bx ? ( ax < cx ? ax : cx ) : ( bx < cx ? bx : cx ),\n\t\ty0 = ay < by ? ( ay < cy ? ay : cy ) : ( by < cy ? by : cy ),\n\t\tx1 = ax > bx ? ( ax > cx ? ax : cx ) : ( bx > cx ? bx : cx ),\n\t\ty1 = ay > by ? ( ay > cy ? ay : cy ) : ( by > cy ? by : cy );\n\n\tlet p = c.next;\n\twhile ( p !== a ) {\n\n\t\tif ( p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 &&\n\t\t\tpointInTriangle( ax, ay, bx, by, cx, cy, p.x, p.y ) &&\n\t\t\tarea( p.prev, p, p.next ) >= 0 ) return false;\n\t\tp = p.next;\n\n\t}\n\n\treturn true;\n\n}\n\nfunction isEarHashed( ear, minX, minY, invSize ) {\n\n\tconst a = ear.prev,\n\t\tb = ear,\n\t\tc = ear.next;\n\n\tif ( area( a, b, c ) >= 0 ) return false; // reflex, can't be an ear\n\n\tconst ax = a.x, bx = b.x, cx = c.x, ay = a.y, by = b.y, cy = c.y;\n\n\t// triangle bbox; min & max are calculated like this for speed\n\tconst x0 = ax < bx ? ( ax < cx ? ax : cx ) : ( bx < cx ? bx : cx ),\n\t\ty0 = ay < by ? ( ay < cy ? ay : cy ) : ( by < cy ? by : cy ),\n\t\tx1 = ax > bx ? ( ax > cx ? ax : cx ) : ( bx > cx ? bx : cx ),\n\t\ty1 = ay > by ? ( ay > cy ? ay : cy ) : ( by > cy ? by : cy );\n\n\t// z-order range for the current triangle bbox;\n\tconst minZ = zOrder( x0, y0, minX, minY, invSize ),\n\t\tmaxZ = zOrder( x1, y1, minX, minY, invSize );\n\n\tlet p = ear.prevZ,\n\t\tn = ear.nextZ;\n\n\t// look for points inside the triangle in both directions\n\twhile ( p && p.z >= minZ && n && n.z <= maxZ ) {\n\n\t\tif ( p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && p !== a && p !== c &&\n\t\t\tpointInTriangle( ax, ay, bx, by, cx, cy, p.x, p.y ) && area( p.prev, p, p.next ) >= 0 ) return false;\n\t\tp = p.prevZ;\n\n\t\tif ( n.x >= x0 && n.x <= x1 && n.y >= y0 && n.y <= y1 && n !== a && n !== c &&\n\t\t\tpointInTriangle( ax, ay, bx, by, cx, cy, n.x, n.y ) && area( n.prev, n, n.next ) >= 0 ) return false;\n\t\tn = n.nextZ;\n\n\t}\n\n\t// look for remaining points in decreasing z-order\n\twhile ( p && p.z >= minZ ) {\n\n\t\tif ( p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && p !== a && p !== c &&\n\t\t\tpointInTriangle( ax, ay, bx, by, cx, cy, p.x, p.y ) && area( p.prev, p, p.next ) >= 0 ) return false;\n\t\tp = p.prevZ;\n\n\t}\n\n\t// look for remaining points in increasing z-order\n\twhile ( n && n.z <= maxZ ) {\n\n\t\tif ( n.x >= x0 && n.x <= x1 && n.y >= y0 && n.y <= y1 && n !== a && n !== c &&\n\t\t\tpointInTriangle( ax, ay, bx, by, cx, cy, n.x, n.y ) && area( n.prev, n, n.next ) >= 0 ) return false;\n\t\tn = n.nextZ;\n\n\t}\n\n\treturn true;\n\n}\n\n// go through all polygon nodes and cure small local self-intersections\nfunction cureLocalIntersections( start, triangles, dim ) {\n\n\tlet p = start;\n\tdo {\n\n\t\tconst a = p.prev,\n\t\t\tb = p.next.next;\n\n\t\tif ( ! equals( a, b ) && intersects( a, p, p.next, b ) && locallyInside( a, b ) && locallyInside( b, a ) ) {\n\n\t\t\ttriangles.push( a.i / dim | 0 );\n\t\t\ttriangles.push( p.i / dim | 0 );\n\t\t\ttriangles.push( b.i / dim | 0 );\n\n\t\t\t// remove two nodes involved\n\t\t\tremoveNode( p );\n\t\t\tremoveNode( p.next );\n\n\t\t\tp = start = b;\n\n\t\t}\n\n\t\tp = p.next;\n\n\t} while ( p !== start );\n\n\treturn filterPoints( p );\n\n}\n\n// try splitting polygon into two and triangulate them independently\nfunction splitEarcut( start, triangles, dim, minX, minY, invSize ) {\n\n\t// look for a valid diagonal that divides the polygon into two\n\tlet a = start;\n\tdo {\n\n\t\tlet b = a.next.next;\n\t\twhile ( b !== a.prev ) {\n\n\t\t\tif ( a.i !== b.i && isValidDiagonal( a, b ) ) {\n\n\t\t\t\t// split the polygon in two by the diagonal\n\t\t\t\tlet c = splitPolygon( a, b );\n\n\t\t\t\t// filter colinear points around the cuts\n\t\t\t\ta = filterPoints( a, a.next );\n\t\t\t\tc = filterPoints( c, c.next );\n\n\t\t\t\t// run earcut on each half\n\t\t\t\tearcutLinked( a, triangles, dim, minX, minY, invSize, 0 );\n\t\t\t\tearcutLinked( c, triangles, dim, minX, minY, invSize, 0 );\n\t\t\t\treturn;\n\n\t\t\t}\n\n\t\t\tb = b.next;\n\n\t\t}\n\n\t\ta = a.next;\n\n\t} while ( a !== start );\n\n}\n\n// link every hole into the outer loop, producing a single-ring polygon without holes\nfunction eliminateHoles( data, holeIndices, outerNode, dim ) {\n\n\tconst queue = [];\n\tlet i, len, start, end, list;\n\n\tfor ( i = 0, len = holeIndices.length; i < len; i ++ ) {\n\n\t\tstart = holeIndices[ i ] * dim;\n\t\tend = i < len - 1 ? holeIndices[ i + 1 ] * dim : data.length;\n\t\tlist = linkedList( data, start, end, dim, false );\n\t\tif ( list === list.next ) list.steiner = true;\n\t\tqueue.push( getLeftmost( list ) );\n\n\t}\n\n\tqueue.sort( compareX );\n\n\t// process holes from left to right\n\tfor ( i = 0; i < queue.length; i ++ ) {\n\n\t\touterNode = eliminateHole( queue[ i ], outerNode );\n\n\t}\n\n\treturn outerNode;\n\n}\n\nfunction compareX( a, b ) {\n\n\treturn a.x - b.x;\n\n}\n\n// find a bridge between vertices that connects hole with an outer ring and link it\nfunction eliminateHole( hole, outerNode ) {\n\n\tconst bridge = findHoleBridge( hole, outerNode );\n\tif ( ! bridge ) {\n\n\t\treturn outerNode;\n\n\t}\n\n\tconst bridgeReverse = splitPolygon( bridge, hole );\n\n\t// filter collinear points around the cuts\n\tfilterPoints( bridgeReverse, bridgeReverse.next );\n\treturn filterPoints( bridge, bridge.next );\n\n}\n\n// David Eberly's algorithm for finding a bridge between hole and outer polygon\nfunction findHoleBridge( hole, outerNode ) {\n\n\tlet p = outerNode,\n\t\tqx = - Infinity,\n\t\tm;\n\n\tconst hx = hole.x, hy = hole.y;\n\n\t// find a segment intersected by a ray from the hole's leftmost point to the left;\n\t// segment's endpoint with lesser x will be potential connection point\n\tdo {\n\n\t\tif ( hy <= p.y && hy >= p.next.y && p.next.y !== p.y ) {\n\n\t\t\tconst x = p.x + ( hy - p.y ) * ( p.next.x - p.x ) / ( p.next.y - p.y );\n\t\t\tif ( x <= hx && x > qx ) {\n\n\t\t\t\tqx = x;\n\t\t\t\tm = p.x < p.next.x ? p : p.next;\n\t\t\t\tif ( x === hx ) return m; // hole touches outer segment; pick leftmost endpoint\n\n\t\t\t}\n\n\t\t}\n\n\t\tp = p.next;\n\n\t} while ( p !== outerNode );\n\n\tif ( ! m ) return null;\n\n\t// look for points inside the triangle of hole point, segment intersection and endpoint;\n\t// if there are no points found, we have a valid connection;\n\t// otherwise choose the point of the minimum angle with the ray as connection point\n\n\tconst stop = m,\n\t\tmx = m.x,\n\t\tmy = m.y;\n\tlet tanMin = Infinity, tan;\n\n\tp = m;\n\n\tdo {\n\n\t\tif ( hx >= p.x && p.x >= mx && hx !== p.x &&\n\t\t\t\tpointInTriangle( hy < my ? hx : qx, hy, mx, my, hy < my ? qx : hx, hy, p.x, p.y ) ) {\n\n\t\t\ttan = Math.abs( hy - p.y ) / ( hx - p.x ); // tangential\n\n\t\t\tif ( locallyInside( p, hole ) && ( tan < tanMin || ( tan === tanMin && ( p.x > m.x || ( p.x === m.x && sectorContainsSector( m, p ) ) ) ) ) ) {\n\n\t\t\t\tm = p;\n\t\t\t\ttanMin = tan;\n\n\t\t\t}\n\n\t\t}\n\n\t\tp = p.next;\n\n\t} while ( p !== stop );\n\n\treturn m;\n\n}\n\n// whether sector in vertex m contains sector in vertex p in the same coordinates\nfunction sectorContainsSector( m, p ) {\n\n\treturn area( m.prev, m, p.prev ) < 0 && area( p.next, m, m.next ) < 0;\n\n}\n\n// interlink polygon nodes in z-order\nfunction indexCurve( start, minX, minY, invSize ) {\n\n\tlet p = start;\n\tdo {\n\n\t\tif ( p.z === 0 ) p.z = zOrder( p.x, p.y, minX, minY, invSize );\n\t\tp.prevZ = p.prev;\n\t\tp.nextZ = p.next;\n\t\tp = p.next;\n\n\t} while ( p !== start );\n\n\tp.prevZ.nextZ = null;\n\tp.prevZ = null;\n\n\tsortLinked( p );\n\n}\n\n// Simon Tatham's linked list merge sort algorithm\n// http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html\nfunction sortLinked( list ) {\n\n\tlet i, p, q, e, tail, numMerges, pSize, qSize,\n\t\tinSize = 1;\n\n\tdo {\n\n\t\tp = list;\n\t\tlist = null;\n\t\ttail = null;\n\t\tnumMerges = 0;\n\n\t\twhile ( p ) {\n\n\t\t\tnumMerges ++;\n\t\t\tq = p;\n\t\t\tpSize = 0;\n\t\t\tfor ( i = 0; i < inSize; i ++ ) {\n\n\t\t\t\tpSize ++;\n\t\t\t\tq = q.nextZ;\n\t\t\t\tif ( ! q ) break;\n\n\t\t\t}\n\n\t\t\tqSize = inSize;\n\n\t\t\twhile ( pSize > 0 || ( qSize > 0 && q ) ) {\n\n\t\t\t\tif ( pSize !== 0 && ( qSize === 0 || ! q || p.z <= q.z ) ) {\n\n\t\t\t\t\te = p;\n\t\t\t\t\tp = p.nextZ;\n\t\t\t\t\tpSize --;\n\n\t\t\t\t} else {\n\n\t\t\t\t\te = q;\n\t\t\t\t\tq = q.nextZ;\n\t\t\t\t\tqSize --;\n\n\t\t\t\t}\n\n\t\t\t\tif ( tail ) tail.nextZ = e;\n\t\t\t\telse list = e;\n\n\t\t\t\te.prevZ = tail;\n\t\t\t\ttail = e;\n\n\t\t\t}\n\n\t\t\tp = q;\n\n\t\t}\n\n\t\ttail.nextZ = null;\n\t\tinSize *= 2;\n\n\t} while ( numMerges > 1 );\n\n\treturn list;\n\n}\n\n// z-order of a point given coords and inverse of the longer side of data bbox\nfunction zOrder( x, y, minX, minY, invSize ) {\n\n\t// coords are transformed into non-negative 15-bit integer range\n\tx = ( x - minX ) * invSize | 0;\n\ty = ( y - minY ) * invSize | 0;\n\n\tx = ( x | ( x << 8 ) ) & 0x00FF00FF;\n\tx = ( x | ( x << 4 ) ) & 0x0F0F0F0F;\n\tx = ( x | ( x << 2 ) ) & 0x33333333;\n\tx = ( x | ( x << 1 ) ) & 0x55555555;\n\n\ty = ( y | ( y << 8 ) ) & 0x00FF00FF;\n\ty = ( y | ( y << 4 ) ) & 0x0F0F0F0F;\n\ty = ( y | ( y << 2 ) ) & 0x33333333;\n\ty = ( y | ( y << 1 ) ) & 0x55555555;\n\n\treturn x | ( y << 1 );\n\n}\n\n// find the leftmost node of a polygon ring\nfunction getLeftmost( start ) {\n\n\tlet p = start,\n\t\tleftmost = start;\n\tdo {\n\n\t\tif ( p.x < leftmost.x || ( p.x === leftmost.x && p.y < leftmost.y ) ) leftmost = p;\n\t\tp = p.next;\n\n\t} while ( p !== start );\n\n\treturn leftmost;\n\n}\n\n// check if a point lies within a convex triangle\nfunction pointInTriangle( ax, ay, bx, by, cx, cy, px, py ) {\n\n\treturn ( cx - px ) * ( ay - py ) >= ( ax - px ) * ( cy - py ) &&\n ( ax - px ) * ( by - py ) >= ( bx - px ) * ( ay - py ) &&\n ( bx - px ) * ( cy - py ) >= ( cx - px ) * ( by - py );\n\n}\n\n// check if a diagonal between two polygon nodes is valid (lies in polygon interior)\nfunction isValidDiagonal( a, b ) {\n\n\treturn a.next.i !== b.i && a.prev.i !== b.i && ! intersectsPolygon( a, b ) && // dones't intersect other edges\n ( locallyInside( a, b ) && locallyInside( b, a ) && middleInside( a, b ) && // locally visible\n ( area( a.prev, a, b.prev ) || area( a, b.prev, b ) ) || // does not create opposite-facing sectors\n equals( a, b ) && area( a.prev, a, a.next ) > 0 && area( b.prev, b, b.next ) > 0 ); // special zero-length case\n\n}\n\n// signed area of a triangle\nfunction area( p, q, r ) {\n\n\treturn ( q.y - p.y ) * ( r.x - q.x ) - ( q.x - p.x ) * ( r.y - q.y );\n\n}\n\n// check if two points are equal\nfunction equals( p1, p2 ) {\n\n\treturn p1.x === p2.x && p1.y === p2.y;\n\n}\n\n// check if two segments intersect\nfunction intersects( p1, q1, p2, q2 ) {\n\n\tconst o1 = sign( area( p1, q1, p2 ) );\n\tconst o2 = sign( area( p1, q1, q2 ) );\n\tconst o3 = sign( area( p2, q2, p1 ) );\n\tconst o4 = sign( area( p2, q2, q1 ) );\n\n\tif ( o1 !== o2 && o3 !== o4 ) return true; // general case\n\n\tif ( o1 === 0 && onSegment( p1, p2, q1 ) ) return true; // p1, q1 and p2 are collinear and p2 lies on p1q1\n\tif ( o2 === 0 && onSegment( p1, q2, q1 ) ) return true; // p1, q1 and q2 are collinear and q2 lies on p1q1\n\tif ( o3 === 0 && onSegment( p2, p1, q2 ) ) return true; // p2, q2 and p1 are collinear and p1 lies on p2q2\n\tif ( o4 === 0 && onSegment( p2, q1, q2 ) ) return true; // p2, q2 and q1 are collinear and q1 lies on p2q2\n\n\treturn false;\n\n}\n\n// for collinear points p, q, r, check if point q lies on segment pr\nfunction onSegment( p, q, r ) {\n\n\treturn q.x <= Math.max( p.x, r.x ) && q.x >= Math.min( p.x, r.x ) && q.y <= Math.max( p.y, r.y ) && q.y >= Math.min( p.y, r.y );\n\n}\n\nfunction sign( num ) {\n\n\treturn num > 0 ? 1 : num < 0 ? - 1 : 0;\n\n}\n\n// check if a polygon diagonal intersects any polygon segments\nfunction intersectsPolygon( a, b ) {\n\n\tlet p = a;\n\tdo {\n\n\t\tif ( p.i !== a.i && p.next.i !== a.i && p.i !== b.i && p.next.i !== b.i &&\n\t\t\tintersects( p, p.next, a, b ) ) return true;\n\t\tp = p.next;\n\n\t} while ( p !== a );\n\n\treturn false;\n\n}\n\n// check if a polygon diagonal is locally inside the polygon\nfunction locallyInside( a, b ) {\n\n\treturn area( a.prev, a, a.next ) < 0 ?\n\t\tarea( a, b, a.next ) >= 0 && area( a, a.prev, b ) >= 0 :\n\t\tarea( a, b, a.prev ) < 0 || area( a, a.next, b ) < 0;\n\n}\n\n// check if the middle point of a polygon diagonal is inside the polygon\nfunction middleInside( a, b ) {\n\n\tlet p = a,\n\t\tinside = false;\n\tconst px = ( a.x + b.x ) / 2,\n\t\tpy = ( a.y + b.y ) / 2;\n\tdo {\n\n\t\tif ( ( ( p.y > py ) !== ( p.next.y > py ) ) && p.next.y !== p.y &&\n\t\t\t( px < ( p.next.x - p.x ) * ( py - p.y ) / ( p.next.y - p.y ) + p.x ) )\n\t\t\tinside = ! inside;\n\t\tp = p.next;\n\n\t} while ( p !== a );\n\n\treturn inside;\n\n}\n\n// link two polygon vertices with a bridge; if the vertices belong to the same ring, it splits polygon into two;\n// if one belongs to the outer ring and another to a hole, it merges it into a single ring\nfunction splitPolygon( a, b ) {\n\n\tconst a2 = new Node( a.i, a.x, a.y ),\n\t\tb2 = new Node( b.i, b.x, b.y ),\n\t\tan = a.next,\n\t\tbp = b.prev;\n\n\ta.next = b;\n\tb.prev = a;\n\n\ta2.next = an;\n\tan.prev = a2;\n\n\tb2.next = a2;\n\ta2.prev = b2;\n\n\tbp.next = b2;\n\tb2.prev = bp;\n\n\treturn b2;\n\n}\n\n// create a node and optionally link it with previous one (in a circular doubly linked list)\nfunction insertNode( i, x, y, last ) {\n\n\tconst p = new Node( i, x, y );\n\n\tif ( ! last ) {\n\n\t\tp.prev = p;\n\t\tp.next = p;\n\n\t} else {\n\n\t\tp.next = last.next;\n\t\tp.prev = last;\n\t\tlast.next.prev = p;\n\t\tlast.next = p;\n\n\t}\n\n\treturn p;\n\n}\n\nfunction removeNode( p ) {\n\n\tp.next.prev = p.prev;\n\tp.prev.next = p.next;\n\n\tif ( p.prevZ ) p.prevZ.nextZ = p.nextZ;\n\tif ( p.nextZ ) p.nextZ.prevZ = p.prevZ;\n\n}\n\nfunction Node( i, x, y ) {\n\n\t// vertex index in coordinates array\n\tthis.i = i;\n\n\t// vertex coordinates\n\tthis.x = x;\n\tthis.y = y;\n\n\t// previous and next vertex nodes in a polygon ring\n\tthis.prev = null;\n\tthis.next = null;\n\n\t// z-order curve value\n\tthis.z = 0;\n\n\t// previous and next nodes in z-order\n\tthis.prevZ = null;\n\tthis.nextZ = null;\n\n\t// indicates whether this is a steiner point\n\tthis.steiner = false;\n\n}\n\nfunction signedArea( data, start, end, dim ) {\n\n\tlet sum = 0;\n\tfor ( let i = start, j = end - dim; i < end; i += dim ) {\n\n\t\tsum += ( data[ j ] - data[ i ] ) * ( data[ i + 1 ] + data[ j + 1 ] );\n\t\tj = i;\n\n\t}\n\n\treturn sum;\n\n}\n\nexport { Earcut };\n","/*\n * three.js r160 `ExtrudeGeometry` (MIT) for straight-edged shapes, ported line by line: the outline is sampled as `Shape.extractPoints` samples a\n * path of `LineCurve`s, triangulated by three's `ShapeUtils.triangulateShape` (Earcut), bevelled and walled in the same order, with the\n * `WorldUVGenerator` uvs and `computeVertexNormals` on the Float32 positions. Path extrusion (`extrudePath`) is not supported.\n */\nimport type { BevelSpec, GeometryGroup, PrimitiveBuffers, ShapeOutline, Vec2 } from '../types'\nimport { Earcut } from './earcut'\n\ninterface P {\n x: number\n y: number\n}\n\nconst equals = (a: P, b: P): boolean => a.x === b.x && a.y === b.y\n\n/** `LineCurve.getPoint(t)`. */\nconst linePoint = (v1: P, v2: P, t: number): P => (t === 1 ? { x: v2.x, y: v2.y } : { x: (v2.x - v1.x) * t + v1.x, y: (v2.y - v1.y) * t + v1.y })\n\n/** `Path.setFromPoints(points).getPoints()`: one `LineCurve` per edge, resolution 1, consecutive duplicates dropped, not closed. */\nfunction pathPoints(points: readonly Vec2[]): P[] {\n const out: P[] = []\n let last: P | undefined\n for (let i = 1; i < points.length; i++) {\n const v1 = { x: (points[i - 1] as Vec2)[0], y: (points[i - 1] as Vec2)[1] }\n const v2 = { x: (points[i] as Vec2)[0], y: (points[i] as Vec2)[1] }\n for (let d = 0; d <= 1; d++) {\n const point = linePoint(v1, v2, d / 1)\n if (last && equals(last, point)) continue\n out.push(point)\n last = point\n }\n }\n return out\n}\n\nfunction area(contour: readonly P[]): number {\n const n = contour.length\n let a = 0.0\n for (let p = n - 1, q = 0; q < n; p = q++) a += (contour[p] as P).x * (contour[q] as P).y - (contour[q] as P).x * (contour[p] as P).y\n return a * 0.5\n}\nconst isClockWise = (pts: readonly P[]): boolean => area(pts) < 0\n\nfunction removeDupEndPts(points: P[]): void {\n const l = points.length\n if (l > 2 && equals(points[l - 1] as P, points[0] as P)) points.pop()\n}\n\nfunction triangulateShape(contour: P[], holes: P[][]): number[][] {\n const vertices: number[] = []\n const holeIndices: number[] = []\n const faces: number[][] = []\n removeDupEndPts(contour)\n for (const p of contour) vertices.push(p.x, p.y)\n let holeIndex = contour.length\n holes.forEach(removeDupEndPts)\n for (let i = 0; i < holes.length; i++) {\n holeIndices.push(holeIndex)\n holeIndex += (holes[i] as P[]).length\n for (const p of holes[i] as P[]) vertices.push(p.x, p.y)\n }\n const triangles: number[] = Earcut.triangulate(vertices, holeIndices)\n for (let i = 0; i < triangles.length; i += 3) faces.push(triangles.slice(i, i + 3))\n return faces\n}\n\n/** `BufferGeometry.computeVertexNormals` for non-indexed Float32 positions, then `normalizeNormals`, both stored as Float32. */\nexport function computeVertexNormals(positions: Float32Array, indices?: ArrayLike<number>): Float32Array {\n const normals = new Float32Array(positions.length)\n const get = (array: Float32Array, i: number): [number, number, number] => [array[i * 3] as number, array[i * 3 + 1] as number, array[i * 3 + 2] as number]\n const face = (vA: number, vB: number, vC: number): [number, number, number] => {\n const pA = get(positions, vA)\n const pB = get(positions, vB)\n const pC = get(positions, vC)\n const cb = [pC[0] - pB[0], pC[1] - pB[1], pC[2] - pB[2]] as const\n const ab = [pA[0] - pB[0], pA[1] - pB[1], pA[2] - pB[2]] as const\n // Vector3.cross\n return [cb[1] * ab[2] - cb[2] * ab[1], cb[2] * ab[0] - cb[0] * ab[2], cb[0] * ab[1] - cb[1] * ab[0]]\n }\n const set = (i: number, x: number, y: number, z: number): void => {\n normals[i * 3] = x\n normals[i * 3 + 1] = y\n normals[i * 3 + 2] = z\n }\n if (indices) {\n for (let i = 0; i < indices.length; i += 3) {\n const vA = indices[i] as number\n const vB = indices[i + 1] as number\n const vC = indices[i + 2] as number\n const cb = face(vA, vB, vC)\n for (const v of [vA, vB, vC]) {\n const n = get(normals, v)\n set(v, n[0] + cb[0], n[1] + cb[1], n[2] + cb[2])\n }\n }\n } else {\n for (let i = 0; i < positions.length / 3; i += 3) {\n const cb = face(i, i + 1, i + 2)\n set(i, cb[0], cb[1], cb[2])\n set(i + 1, cb[0], cb[1], cb[2])\n set(i + 2, cb[0], cb[1], cb[2])\n }\n }\n for (let i = 0; i < normals.length / 3; i++) {\n const [x, y, z] = get(normals, i)\n const s = 1 / (Math.sqrt(x * x + y * y + z * z) || 1)\n set(i, x * s, y * s, z * s)\n }\n return normals\n}\n\nexport function extrude(outline: ShapeOutline, depth = 1, steps = 1, bevel: false | BevelSpec | undefined = undefined): PrimitiveBuffers {\n const parameters = { shape: outline, depth, steps, bevel: bevel ?? {} }\n const verticesArray: number[] = []\n const uvArray: number[] = []\n const groups: GeometryGroup[] = []\n const placeholder: number[] = []\n\n const bevelEnabled = bevel !== false\n const spec: BevelSpec = bevel === false || bevel === undefined ? {} : bevel\n let bevelThickness = spec.thickness !== undefined ? spec.thickness : 0.2\n let bevelSize = spec.size !== undefined ? spec.size : bevelThickness - 0.1\n let bevelOffset = spec.offset !== undefined ? spec.offset : 0\n let bevelSegments = spec.segments !== undefined ? spec.segments : 3\n if (!bevelEnabled) {\n bevelSegments = 0\n bevelThickness = 0\n bevelSize = 0\n bevelOffset = 0\n }\n\n let vertices = pathPoints(outline.outer)\n const holes = (outline.holes ?? []).map(pathPoints)\n const reverse = !isClockWise(vertices)\n if (reverse) {\n vertices = vertices.reverse()\n for (let h = 0; h < holes.length; h++) {\n const ahole = holes[h] as P[]\n if (isClockWise(ahole)) holes[h] = ahole.reverse()\n }\n }\n const faces = triangulateShape(vertices, holes)\n const contour = vertices\n for (const ahole of holes) vertices = vertices.concat(ahole)\n\n const scalePt2 = (pt: P, vec: P, size: number): P => ({ x: pt.x + vec.x * size, y: pt.y + vec.y * size })\n const vlen = vertices.length\n const flen = faces.length\n\n const getBevelVec = (inPt: P, inPrev: P, inNext: P): P => {\n let vTransX: number\n let vTransY: number\n let shrinkBy: number\n const vPrevX = inPt.x - inPrev.x\n const vPrevY = inPt.y - inPrev.y\n const vNextX = inNext.x - inPt.x\n const vNextY = inNext.y - inPt.y\n const vPrevLensq = vPrevX * vPrevX + vPrevY * vPrevY\n const collinear0 = vPrevX * vNextY - vPrevY * vNextX\n if (Math.abs(collinear0) > Number.EPSILON) {\n const vPrevLen = Math.sqrt(vPrevLensq)\n const vNextLen = Math.sqrt(vNextX * vNextX + vNextY * vNextY)\n const ptPrevShiftX = inPrev.x - vPrevY / vPrevLen\n const ptPrevShiftY = inPrev.y + vPrevX / vPrevLen\n const ptNextShiftX = inNext.x - vNextY / vNextLen\n const ptNextShiftY = inNext.y + vNextX / vNextLen\n const sf = ((ptNextShiftX - ptPrevShiftX) * vNextY - (ptNextShiftY - ptPrevShiftY) * vNextX) / (vPrevX * vNextY - vPrevY * vNextX)\n vTransX = ptPrevShiftX + vPrevX * sf - inPt.x\n vTransY = ptPrevShiftY + vPrevY * sf - inPt.y\n const vTransLensq = vTransX * vTransX + vTransY * vTransY\n if (vTransLensq <= 2) return { x: vTransX, y: vTransY }\n shrinkBy = Math.sqrt(vTransLensq / 2)\n } else {\n let directionEq = false\n if (vPrevX > Number.EPSILON) {\n if (vNextX > Number.EPSILON) directionEq = true\n } else if (vPrevX < -Number.EPSILON) {\n if (vNextX < -Number.EPSILON) directionEq = true\n } else if (Math.sign(vPrevY) === Math.sign(vNextY)) {\n directionEq = true\n }\n if (directionEq) {\n vTransX = -vPrevY\n vTransY = vPrevX\n shrinkBy = Math.sqrt(vPrevLensq)\n } else {\n vTransX = vPrevX\n vTransY = vPrevY\n shrinkBy = Math.sqrt(vPrevLensq / 2)\n }\n }\n return { x: vTransX / shrinkBy, y: vTransY / shrinkBy }\n }\n\n const movements = (ring: readonly P[]): P[] => {\n const out: P[] = []\n for (let i = 0, il = ring.length, j = il - 1, k = i + 1; i < il; i++, j++, k++) {\n if (j === il) j = 0\n if (k === il) k = 0\n out[i] = getBevelVec(ring[i] as P, ring[j] as P, ring[k] as P)\n }\n return out\n }\n const contourMovements = movements(contour)\n const holesMovements: P[][] = []\n let verticesMovements = contourMovements.concat()\n for (const ahole of holes) {\n const oneHoleMovements = movements(ahole)\n holesMovements.push(oneHoleMovements)\n verticesMovements = verticesMovements.concat(oneHoleMovements)\n }\n\n const v = (x: number, y: number, z: number): void => {\n placeholder.push(x)\n placeholder.push(y)\n placeholder.push(z)\n }\n const bevelLayer = (b: number, zOf: (z: number) => number): void => {\n const t = b / bevelSegments\n const z = bevelThickness * Math.cos((t * Math.PI) / 2)\n const bs = bevelSize * Math.sin((t * Math.PI) / 2) + bevelOffset\n for (let i = 0; i < contour.length; i++) {\n const vert = scalePt2(contour[i] as P, contourMovements[i] as P, bs)\n v(vert.x, vert.y, zOf(z))\n }\n for (let h = 0; h < holes.length; h++) {\n const ahole = holes[h] as P[]\n const oneHoleMovements = holesMovements[h] as P[]\n for (let i = 0; i < ahole.length; i++) {\n const vert = scalePt2(ahole[i] as P, oneHoleMovements[i] as P, bs)\n v(vert.x, vert.y, zOf(z))\n }\n }\n }\n for (let b = 0; b < bevelSegments; b++) bevelLayer(b, (z) => -z)\n const bs = bevelSize + bevelOffset\n for (let i = 0; i < vlen; i++) {\n const vert = bevelEnabled ? scalePt2(vertices[i] as P, verticesMovements[i] as P, bs) : (vertices[i] as P)\n v(vert.x, vert.y, 0)\n }\n for (let s = 1; s <= steps; s++) {\n for (let i = 0; i < vlen; i++) {\n const vert = bevelEnabled ? scalePt2(vertices[i] as P, verticesMovements[i] as P, bs) : (vertices[i] as P)\n v(vert.x, vert.y, (depth / steps) * s)\n }\n }\n for (let b = bevelSegments - 1; b >= 0; b--) bevelLayer(b, (z) => depth + z)\n\n const addVertex = (index: number): void => {\n verticesArray.push(placeholder[index * 3 + 0] as number)\n verticesArray.push(placeholder[index * 3 + 1] as number)\n verticesArray.push(placeholder[index * 3 + 2] as number)\n }\n const addUV = (x: number, y: number): void => {\n uvArray.push(x)\n uvArray.push(y)\n }\n const at = (index: number, axis: number): number => verticesArray[index * 3 + axis] as number\n const f3 = (a: number, b: number, c: number): void => {\n addVertex(a)\n addVertex(b)\n addVertex(c)\n const nextIndex = verticesArray.length / 3\n for (const index of [nextIndex - 3, nextIndex - 2, nextIndex - 1]) addUV(at(index, 0), at(index, 1))\n }\n const f4 = (a: number, b: number, c: number, d: number): void => {\n addVertex(a)\n addVertex(b)\n addVertex(d)\n addVertex(b)\n addVertex(c)\n addVertex(d)\n const nextIndex = verticesArray.length / 3\n const iA = nextIndex - 6\n const iB = nextIndex - 3\n const iC = nextIndex - 2\n const iD = nextIndex - 1\n const byX = Math.abs(at(iA, 1) - at(iB, 1)) < Math.abs(at(iA, 0) - at(iB, 0))\n const uv = (index: number): [number, number] => [byX ? at(index, 0) : at(index, 1), 1 - at(index, 2)]\n const uvs = [uv(iA), uv(iB), uv(iC), uv(iD)]\n for (const k of [0, 1, 3, 1, 2, 3]) addUV(...(uvs[k] as [number, number]))\n }\n\n // lid faces\n let start = verticesArray.length / 3\n if (bevelEnabled) {\n let offset = 0\n for (let i = 0; i < flen; i++) {\n const face = faces[i] as number[]\n f3((face[2] as number) + offset, (face[1] as number) + offset, (face[0] as number) + offset)\n }\n const layer = steps + bevelSegments * 2\n offset = vlen * layer\n for (let i = 0; i < flen; i++) {\n const face = faces[i] as number[]\n f3((face[0] as number) + offset, (face[1] as number) + offset, (face[2] as number) + offset)\n }\n } else {\n for (let i = 0; i < flen; i++) {\n const face = faces[i] as number[]\n f3(face[2] as number, face[1] as number, face[0] as number)\n }\n for (let i = 0; i < flen; i++) {\n const face = faces[i] as number[]\n f3((face[0] as number) + vlen * steps, (face[1] as number) + vlen * steps, (face[2] as number) + vlen * steps)\n }\n }\n groups.push({ start, count: verticesArray.length / 3 - start, materialIndex: 0 })\n\n // side faces\n start = verticesArray.length / 3\n const sidewalls = (ring: readonly P[], layeroffset: number): void => {\n let i = ring.length\n while (--i >= 0) {\n const j = i\n let k = i - 1\n if (k < 0) k = ring.length - 1\n for (let s = 0, sl = steps + bevelSegments * 2; s < sl; s++) {\n const slen1 = vlen * s\n const slen2 = vlen * (s + 1)\n f4(layeroffset + j + slen1, layeroffset + k + slen1, layeroffset + k + slen2, layeroffset + j + slen2)\n }\n }\n }\n let layeroffset = 0\n sidewalls(contour, layeroffset)\n layeroffset += contour.length\n for (const ahole of holes) {\n sidewalls(ahole, layeroffset)\n layeroffset += ahole.length\n }\n groups.push({ start, count: verticesArray.length / 3 - start, materialIndex: 1 })\n\n const positions = new Float32Array(verticesArray)\n return { type: 'ExtrudeGeometry', parameters, positions, normals: computeVertexNormals(positions), uvs: new Float32Array(uvArray), groups }\n}\n","import { canonicalJson } from '../hash'\nimport type { MeshNode, PrimitiveBuffers, PrimitiveNode } from '../types'\nimport { difference } from './difference'\nimport { computeVertexNormals, extrude } from './extrude'\nimport { box, circle, cone, cylinder, indexArray, lathe, plane, ring, sphere, torus } from './three-generators'\n\nexport { box, circle, cone, cylinder, lathe, plane, ring, sphere, torus } from './three-generators'\nexport { computeVertexNormals, extrude } from './extrude'\nexport { difference } from './difference'\n\n/** How a level of detail changes a primitive. */\nexport interface DetailOptions {\n /** Multiplies the segment counts of round primitives (1 leaves them as given). */\n readonly detail?: number\n /** Floor of the scaled segment counts (default 3). */\n readonly minSegments?: number\n}\n\nconst PARAMETER_KEYS: Readonly<Record<PrimitiveNode['kind'], readonly string[]>> = {\n box: ['size', 'segments'],\n cylinder: ['radiusTop', 'radiusBottom', 'height', 'radialSegments', 'heightSegments', 'openEnded', 'thetaStart', 'thetaLength'],\n cone: ['radius', 'height', 'radialSegments', 'heightSegments', 'openEnded', 'thetaStart', 'thetaLength'],\n sphere: ['radius', 'widthSegments', 'heightSegments', 'phiStart', 'phiLength', 'thetaStart', 'thetaLength'],\n plane: ['width', 'height', 'widthSegments', 'heightSegments'],\n circle: ['radius', 'segments', 'thetaStart', 'thetaLength'],\n ring: ['innerRadius', 'outerRadius', 'thetaSegments', 'phiSegments', 'thetaStart', 'thetaLength'],\n lathe: ['points', 'segments', 'phiStart', 'phiLength'],\n torus: ['radius', 'tube', 'radialSegments', 'tubularSegments', 'arc'],\n extrude: ['shape', 'depth', 'steps', 'bevel'],\n difference: ['size', 'subtract'],\n mesh: ['positions', 'normals', 'uvs', 'indices'],\n}\n\n/** The segment fields `detail` scales, per kind. */\nconst SEGMENT_KEYS: Readonly<Partial<Record<PrimitiveNode['kind'], readonly string[]>>> = {\n cylinder: ['radialSegments'],\n cone: ['radialSegments'],\n sphere: ['widthSegments', 'heightSegments'],\n circle: ['segments'],\n ring: ['thetaSegments'],\n lathe: ['segments'],\n torus: ['radialSegments', 'tubularSegments'],\n}\n/** three's defaults of the segment fields, so a scaled default is scaled from the right number. */\nconst SEGMENT_DEFAULTS: Readonly<Record<string, number>> = { radialSegments: 32, widthSegments: 32, heightSegments: 16, segments: 12, thetaSegments: 32, tubularSegments: 48 }\nconst segmentDefault = (kind: string, key: string): number => (kind === 'circle' && key === 'segments' ? 32 : kind === 'torus' && key === 'radialSegments' ? 12 : (SEGMENT_DEFAULTS[key] as number))\n\n/** The parameters of a primitive node (no transform, colour or name), with the level of detail applied: what its geometry depends on. */\nexport function primitiveParameters(node: PrimitiveNode, options: DetailOptions = {}): Record<string, unknown> {\n const out: Record<string, unknown> = {}\n const source = node as unknown as Record<string, unknown>\n for (const key of PARAMETER_KEYS[node.kind]) if (source[key] !== undefined) out[key] = source[key]\n const detail = options.detail ?? 1\n if (detail !== 1) {\n const floor = options.minSegments ?? 3\n for (const key of SEGMENT_KEYS[node.kind] ?? []) {\n const given = (out[key] as number | undefined) ?? segmentDefault(node.kind, key)\n out[key] = Math.max(floor, Math.round(given * detail))\n }\n }\n return out\n}\n\nfunction mesh(p: MeshNode): PrimitiveBuffers {\n const positions = new Float32Array(p.positions as number[])\n const indices = p.indices ? indexArray(p.indices as number[]) : undefined\n return {\n type: 'BufferGeometry',\n parameters: {},\n positions,\n normals: p.normals ? new Float32Array(p.normals as number[]) : computeVertexNormals(positions, indices),\n uvs: p.uvs ? new Float32Array(p.uvs as number[]) : new Float32Array((positions.length / 3) * 2),\n ...(indices ? { indices } : {}),\n groups: [],\n }\n}\n\n/** Builds a node whose parameters were already resolved (`primitiveParameters` merged over it, so the detail level is applied). */\nfunction make(p: PrimitiveNode): PrimitiveBuffers {\n switch (p.kind) {\n case 'box': {\n const size = p.size ?? [1, 1, 1]\n const segments = p.segments ?? [1, 1, 1]\n return box(size[0], size[1], size[2], segments[0], segments[1], segments[2])\n }\n case 'cylinder':\n return cylinder(p.radiusTop, p.radiusBottom, p.height, p.radialSegments, p.heightSegments, p.openEnded, p.thetaStart, p.thetaLength)\n case 'cone':\n return cone(p.radius, p.height, p.radialSegments, p.heightSegments, p.openEnded, p.thetaStart, p.thetaLength)\n case 'sphere':\n return sphere(p.radius, p.widthSegments, p.heightSegments, p.phiStart, p.phiLength, p.thetaStart, p.thetaLength)\n case 'plane':\n return plane(p.width, p.height, p.widthSegments, p.heightSegments)\n case 'circle':\n return circle(p.radius, p.segments, p.thetaStart, p.thetaLength)\n case 'ring':\n return ring(p.innerRadius, p.outerRadius, p.thetaSegments, p.phiSegments, p.thetaStart, p.thetaLength)\n case 'lathe':\n return lathe(p.points, p.segments, p.phiStart, p.phiLength)\n case 'torus':\n return torus(p.radius, p.tube, p.radialSegments, p.tubularSegments, p.arc)\n case 'extrude':\n return extrude(p.shape, p.depth, p.steps, p.bevel)\n case 'difference':\n return difference(p.size, p.subtract ?? [])\n case 'mesh':\n return mesh(p)\n default:\n throw new Error(`game-shapes: unknown primitive kind \"${String((p as { kind: unknown }).kind)}\"`)\n }\n}\n\n/** Templates by their parameter text: the same cylinder is generated once per process. Read-only by contract. */\nconst templates = new Map<string, PrimitiveBuffers>()\nlet templateLimit = 4096\nconst stats = { hits: 0, misses: 0 }\n\n/** Sets how many primitive templates are kept (least recently used out; 0 turns the cache off). */\nexport function setTemplateCacheSize(size: number): void {\n templateLimit = Math.max(0, Math.floor(size))\n while (templates.size > templateLimit) templates.delete(templates.keys().next().value as string)\n}\nexport function clearTemplateCache(): void {\n templates.clear()\n stats.hits = 0\n stats.misses = 0\n}\nexport const templateCacheStats = (): { size: number; hits: number; misses: number } => ({ size: templates.size, ...stats })\n\n/**\n * The geometry of one primitive on its own, not baked: the arrays three's generator of the same kind builds, bit for bit (meshes and differences\n * are `BufferGeometry`). The result comes from a shared template cache and must not be written to (copy it with `clonePrimitive`).\n */\nexport function buildPrimitive(node: PrimitiveNode, options: DetailOptions = {}): PrimitiveBuffers {\n const parameters = primitiveParameters(node, options)\n // raw meshes are not worth a key of their whole arrays; they are cheap to wrap\n if (node.kind === 'mesh' || templateLimit === 0) return make({ ...node, ...parameters } as PrimitiveNode)\n const key = `${node.kind}:${canonicalJson(parameters)}`\n const cached = templates.get(key)\n if (cached) {\n stats.hits++\n templates.delete(key)\n templates.set(key, cached)\n return cached\n }\n stats.misses++\n const built = make({ ...node, ...parameters } as PrimitiveNode)\n templates.set(key, built)\n if (templates.size > templateLimit) templates.delete(templates.keys().next().value as string)\n return built\n}\n\n/** A copy that owns its arrays. */\nexport function clonePrimitive(p: PrimitiveBuffers): PrimitiveBuffers {\n return {\n type: p.type,\n parameters: p.parameters,\n positions: p.positions.slice(),\n normals: p.normals.slice(),\n uvs: p.uvs.slice(),\n ...(p.indices ? { indices: p.indices.slice() } : {}),\n groups: p.groups.map((group) => ({ ...group })),\n }\n}\n","import { hash64 } from './hash'\n\n/** mulberry32: a small, fast 32-bit seeded stream in [0, 1). */\nexport function mulberry32(seed: number): () => number {\n let state = seed >>> 0\n return () => {\n state = (state + 0x6d2b79f5) >>> 0\n let t = state\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/** The stream of one node: its own per (seed, generator, path), so adding a node never shifts another node's draws. */\nexport const streamFor = (seed: number | string, generator: string, path: string): (() => number) =>\n mulberry32(parseInt(hash64(`${String(seed)}\\u0000${generator}\\u0000${path}`).slice(0, 8), 16))\n","import type { ShapeGenerator } from './types'\n\nconst GENERATOR_NAME = /^[a-zA-Z][\\w.:-]*$/\n\n/** The generators of one set: what `{ kind: 'custom', generator: name, params }` nodes look up. */\nexport interface ShapeGenerators {\n /**\n * Registers a generator: a pure function of its params and a seeded stream that returns a node. A worker that generates shapes with custom nodes must\n * register the same generators (import the same module in the worker script).\n */\n register(name: string, generator: ShapeGenerator): void\n unregister(name: string): boolean\n get(name: string): ShapeGenerator | undefined\n list(): string[]\n /** The generators by name, as `GenerateOptions.generators` takes them. */\n record(): Readonly<Record<string, ShapeGenerator>>\n}\n\nexport const generatorsOver = (generators: Map<string, ShapeGenerator>): ShapeGenerators => ({\n register(name, generator) {\n if (!GENERATOR_NAME.test(name)) throw new Error(`game-shapes: generator name \"${name}\" must start with a letter and use letters, digits, \".\", \":\", \"-\" or \"_\"`)\n generators.set(name, generator)\n },\n unregister: (name) => generators.delete(name),\n get: (name) => generators.get(name),\n list: () => [...generators.keys()].sort(),\n record: () => Object.fromEntries(generators),\n})\n\nlet defaultGenerators: ShapeGenerators | undefined\n\n/** The set behind `registerGenerator`, `getGenerator` and the rest of the global functions, created on first use. */\nexport const defaultShapeGenerators = (): ShapeGenerators => (defaultGenerators ??= generatorsOver(new Map()))\n\n/** @deprecated use `createShapes(...).register`. */\nexport const registerGenerator = (name: string, generator: ShapeGenerator): void => defaultShapeGenerators().register(name, generator)\n/** @deprecated use `createShapes(...).unregister`. */\nexport const unregisterGenerator = (name: string): boolean => defaultShapeGenerators().unregister(name)\n/** @deprecated use `createShapes(...).get`. */\nexport const getGenerator = (name: string): ShapeGenerator | undefined => defaultShapeGenerators().get(name)\n/** @deprecated use `createShapes(...).list`. */\nexport const listGenerators = (): string[] => defaultShapeGenerators().list()\n","{\n \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n \"$id\": \"https://github.com/Andersonfrfilho/adatechnology-game-kit/packages/game-shapes/schema/shape.schema.json\",\n \"title\": \"game-shapes shape\",\n \"type\": \"object\",\n \"required\": [\n \"version\",\n \"root\"\n ],\n \"additionalProperties\": false,\n \"properties\": {\n \"version\": {\n \"const\": 1\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"materials\": {\n \"type\": \"object\",\n \"additionalProperties\": {\n \"$ref\": \"#/$defs/material\"\n }\n },\n \"root\": {\n \"$ref\": \"#/$defs/node\"\n },\n \"lod\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/$defs/lodLevel\"\n }\n },\n \"cullDistance\": {\n \"type\": \"number\",\n \"exclusiveMinimum\": 0\n }\n },\n \"$defs\": {\n \"number\": {\n \"type\": \"number\"\n },\n \"count\": {\n \"type\": \"integer\",\n \"minimum\": 0\n },\n \"vec2\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"number\"\n },\n \"minItems\": 2,\n \"maxItems\": 2\n },\n \"vec3\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"number\"\n },\n \"minItems\": 3,\n \"maxItems\": 3\n },\n \"mat4\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"number\"\n },\n \"minItems\": 16,\n \"maxItems\": 16\n },\n \"color\": {\n \"oneOf\": [\n {\n \"type\": \"integer\",\n \"minimum\": 0,\n \"maximum\": 16777215\n },\n {\n \"type\": \"string\",\n \"pattern\": \"^#[0-9a-fA-F]{6}$\"\n }\n ]\n },\n \"uvRect\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\n \"u0\",\n \"v0\",\n \"u1\",\n \"v1\"\n ],\n \"properties\": {\n \"u0\": {\n \"type\": \"number\"\n },\n \"v0\": {\n \"type\": \"number\"\n },\n \"u1\": {\n \"type\": \"number\"\n },\n \"v1\": {\n \"type\": \"number\"\n }\n }\n },\n \"rotationOrder\": {\n \"enum\": [\n \"XYZ\",\n \"YXZ\",\n \"ZXY\",\n \"ZYX\",\n \"YZX\",\n \"XZY\"\n ]\n },\n \"transform\": {\n \"type\": \"object\",\n \"properties\": {\n \"position\": {\n \"$ref\": \"#/$defs/vec3\"\n },\n \"rotation\": {\n \"$ref\": \"#/$defs/vec3\"\n },\n \"rotationOrder\": {\n \"$ref\": \"#/$defs/rotationOrder\"\n },\n \"scale\": {\n \"oneOf\": [\n {\n \"type\": \"number\"\n },\n {\n \"$ref\": \"#/$defs/vec3\"\n }\n ]\n },\n \"heading\": {\n \"type\": \"number\"\n },\n \"matrix\": {\n \"$ref\": \"#/$defs/mat4\"\n }\n }\n },\n \"instance\": {\n \"allOf\": [\n {\n \"$ref\": \"#/$defs/transform\"\n }\n ],\n \"properties\": {\n \"position\": true,\n \"rotation\": true,\n \"rotationOrder\": true,\n \"scale\": true,\n \"heading\": true,\n \"matrix\": true,\n \"color\": {\n \"$ref\": \"#/$defs/color\"\n }\n },\n \"unevaluatedProperties\": false\n },\n \"material\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"properties\": {\n \"color\": {\n \"$ref\": \"#/$defs/color\"\n },\n \"roughness\": {\n \"type\": \"number\",\n \"minimum\": 0,\n \"maximum\": 1\n },\n \"metalness\": {\n \"type\": \"number\",\n \"minimum\": 0,\n \"maximum\": 1\n },\n \"opacity\": {\n \"type\": \"number\",\n \"minimum\": 0,\n \"maximum\": 1\n },\n \"transparent\": {\n \"type\": \"boolean\"\n },\n \"emissive\": {\n \"$ref\": \"#/$defs/color\"\n },\n \"emissiveIntensity\": {\n \"type\": \"number\",\n \"minimum\": 0\n },\n \"side\": {\n \"enum\": [\n \"front\",\n \"back\",\n \"double\"\n ]\n },\n \"flatShading\": {\n \"type\": \"boolean\"\n },\n \"vertexColors\": {\n \"type\": \"boolean\"\n },\n \"depthWrite\": {\n \"type\": \"boolean\"\n }\n }\n },\n \"lodLevel\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\n \"distance\",\n \"detail\"\n ],\n \"properties\": {\n \"distance\": {\n \"type\": \"number\",\n \"minimum\": 0\n },\n \"detail\": {\n \"type\": \"number\",\n \"exclusiveMinimum\": 0,\n \"maximum\": 1\n },\n \"minSize\": {\n \"type\": \"number\",\n \"minimum\": 0\n },\n \"minSegments\": {\n \"type\": \"integer\",\n \"minimum\": 3\n }\n }\n },\n \"common\": {\n \"allOf\": [\n {\n \"$ref\": \"#/$defs/transform\"\n }\n ],\n \"properties\": {\n \"kind\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"part\": {\n \"type\": \"string\"\n },\n \"material\": {\n \"type\": \"string\"\n },\n \"color\": {\n \"$ref\": \"#/$defs/color\"\n },\n \"uv\": {\n \"$ref\": \"#/$defs/uvRect\"\n },\n \"instances\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/$defs/instance\"\n }\n },\n \"minDetail\": {\n \"type\": \"number\",\n \"exclusiveMinimum\": 0,\n \"maximum\": 1\n },\n \"tags\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n }\n },\n \"node\": {\n \"type\": \"object\",\n \"required\": [\n \"kind\"\n ],\n \"allOf\": [\n {\n \"$ref\": \"#/$defs/common\"\n }\n ],\n \"oneOf\": [\n {\n \"properties\": {\n \"kind\": {\n \"const\": \"group\"\n },\n \"children\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/$defs/node\"\n }\n }\n }\n },\n {\n \"properties\": {\n \"kind\": {\n \"const\": \"box\"\n },\n \"size\": {\n \"$ref\": \"#/$defs/vec3\"\n },\n \"segments\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"integer\",\n \"minimum\": 1\n },\n \"minItems\": 3,\n \"maxItems\": 3\n }\n }\n },\n {\n \"properties\": {\n \"kind\": {\n \"const\": \"cylinder\"\n },\n \"radiusTop\": {\n \"type\": \"number\"\n },\n \"radiusBottom\": {\n \"type\": \"number\"\n },\n \"height\": {\n \"type\": \"number\"\n },\n \"radialSegments\": {\n \"type\": \"integer\",\n \"minimum\": 1\n },\n \"heightSegments\": {\n \"type\": \"integer\",\n \"minimum\": 1\n },\n \"openEnded\": {\n \"type\": \"boolean\"\n },\n \"thetaStart\": {\n \"type\": \"number\"\n },\n \"thetaLength\": {\n \"type\": \"number\"\n }\n }\n },\n {\n \"properties\": {\n \"kind\": {\n \"const\": \"cone\"\n },\n \"radius\": {\n \"type\": \"number\"\n },\n \"height\": {\n \"type\": \"number\"\n },\n \"radialSegments\": {\n \"type\": \"integer\",\n \"minimum\": 1\n },\n \"heightSegments\": {\n \"type\": \"integer\",\n \"minimum\": 1\n },\n \"openEnded\": {\n \"type\": \"boolean\"\n },\n \"thetaStart\": {\n \"type\": \"number\"\n },\n \"thetaLength\": {\n \"type\": \"number\"\n }\n }\n },\n {\n \"properties\": {\n \"kind\": {\n \"const\": \"sphere\"\n },\n \"radius\": {\n \"type\": \"number\"\n },\n \"widthSegments\": {\n \"type\": \"integer\",\n \"minimum\": 3\n },\n \"heightSegments\": {\n \"type\": \"integer\",\n \"minimum\": 2\n },\n \"phiStart\": {\n \"type\": \"number\"\n },\n \"phiLength\": {\n \"type\": \"number\"\n },\n \"thetaStart\": {\n \"type\": \"number\"\n },\n \"thetaLength\": {\n \"type\": \"number\"\n }\n }\n },\n {\n \"properties\": {\n \"kind\": {\n \"const\": \"plane\"\n },\n \"width\": {\n \"type\": \"number\"\n },\n \"height\": {\n \"type\": \"number\"\n },\n \"widthSegments\": {\n \"type\": \"integer\",\n \"minimum\": 1\n },\n \"heightSegments\": {\n \"type\": \"integer\",\n \"minimum\": 1\n }\n }\n },\n {\n \"properties\": {\n \"kind\": {\n \"const\": \"circle\"\n },\n \"radius\": {\n \"type\": \"number\"\n },\n \"segments\": {\n \"type\": \"integer\",\n \"minimum\": 3\n },\n \"thetaStart\": {\n \"type\": \"number\"\n },\n \"thetaLength\": {\n \"type\": \"number\"\n }\n }\n },\n {\n \"properties\": {\n \"kind\": {\n \"const\": \"ring\"\n },\n \"innerRadius\": {\n \"type\": \"number\"\n },\n \"outerRadius\": {\n \"type\": \"number\"\n },\n \"thetaSegments\": {\n \"type\": \"integer\",\n \"minimum\": 3\n },\n \"phiSegments\": {\n \"type\": \"integer\",\n \"minimum\": 1\n },\n \"thetaStart\": {\n \"type\": \"number\"\n },\n \"thetaLength\": {\n \"type\": \"number\"\n }\n }\n },\n {\n \"required\": [\n \"points\"\n ],\n \"properties\": {\n \"kind\": {\n \"const\": \"lathe\"\n },\n \"points\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/$defs/vec2\"\n },\n \"minItems\": 2\n },\n \"segments\": {\n \"type\": \"integer\",\n \"minimum\": 1\n },\n \"phiStart\": {\n \"type\": \"number\"\n },\n \"phiLength\": {\n \"type\": \"number\"\n }\n }\n },\n {\n \"properties\": {\n \"kind\": {\n \"const\": \"torus\"\n },\n \"radius\": {\n \"type\": \"number\"\n },\n \"tube\": {\n \"type\": \"number\"\n },\n \"radialSegments\": {\n \"type\": \"integer\",\n \"minimum\": 1\n },\n \"tubularSegments\": {\n \"type\": \"integer\",\n \"minimum\": 1\n },\n \"arc\": {\n \"type\": \"number\"\n }\n }\n },\n {\n \"required\": [\n \"shape\"\n ],\n \"properties\": {\n \"kind\": {\n \"const\": \"extrude\"\n },\n \"shape\": {\n \"type\": \"object\",\n \"required\": [\n \"outer\"\n ],\n \"additionalProperties\": false,\n \"properties\": {\n \"outer\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/$defs/vec2\"\n },\n \"minItems\": 3\n },\n \"holes\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/$defs/vec2\"\n },\n \"minItems\": 3\n }\n }\n }\n },\n \"depth\": {\n \"type\": \"number\"\n },\n \"steps\": {\n \"type\": \"integer\",\n \"minimum\": 1\n },\n \"bevel\": {\n \"oneOf\": [\n {\n \"const\": false\n },\n {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"properties\": {\n \"thickness\": {\n \"type\": \"number\"\n },\n \"size\": {\n \"type\": \"number\"\n },\n \"offset\": {\n \"type\": \"number\"\n },\n \"segments\": {\n \"type\": \"integer\",\n \"minimum\": 1\n }\n }\n }\n ]\n }\n }\n },\n {\n \"required\": [\n \"size\",\n \"subtract\"\n ],\n \"properties\": {\n \"kind\": {\n \"const\": \"difference\"\n },\n \"size\": {\n \"$ref\": \"#/$defs/vec3\"\n },\n \"subtract\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"min\",\n \"max\"\n ],\n \"additionalProperties\": false,\n \"properties\": {\n \"min\": {\n \"$ref\": \"#/$defs/vec2\"\n },\n \"max\": {\n \"$ref\": \"#/$defs/vec2\"\n }\n }\n }\n }\n }\n },\n {\n \"required\": [\n \"positions\"\n ],\n \"properties\": {\n \"kind\": {\n \"const\": \"mesh\"\n },\n \"positions\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"number\"\n }\n },\n \"normals\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"number\"\n }\n },\n \"uvs\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"number\"\n }\n },\n \"indices\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/$defs/count\"\n }\n }\n }\n },\n {\n \"properties\": {\n \"kind\": {\n \"const\": \"point\"\n },\n \"data\": {}\n }\n },\n {\n \"required\": [\n \"generator\"\n ],\n \"properties\": {\n \"kind\": {\n \"const\": \"custom\"\n },\n \"generator\": {\n \"type\": \"string\"\n },\n \"params\": {}\n }\n }\n ]\n }\n }\n}\n","import schemaJson from '../schema/shape.schema.json'\nimport type { ShapeSchema } from './types'\n\n/** The JSON Schema (draft 2020-12) of a shape document; the same file ships as `@adatechnology/game-shapes/schema.json`. */\nexport const SHAPE_SCHEMA: Readonly<Record<string, unknown>> = schemaJson\n\ntype Check = (value: unknown) => string | null\n\nconst num: Check = (v) => (typeof v === 'number' && Number.isFinite(v) ? null : 'expected a finite number')\nconst bool: Check = (v) => (typeof v === 'boolean' ? null : 'expected a boolean')\nconst str: Check = (v) => (typeof v === 'string' ? null : 'expected a string')\nconst int = (min: number): Check => (v) => (typeof v === 'number' && Number.isInteger(v) && v >= min ? null : `expected an integer >= ${min}`)\nconst range = (lo: number, hi: number, loOpen = false): Check => (v) =>\n typeof v === 'number' && (loOpen ? v > lo : v >= lo) && v <= hi ? null : `expected a number in ${loOpen ? '(' : '['}${lo}, ${hi}]`\nconst tuple = (n: number, item: Check = num): Check => (v) => {\n if (!Array.isArray(v) || v.length !== n) return `expected an array of ${n}`\n for (const x of v) if (item(x)) return `expected an array of ${n} numbers`\n return null\n}\nconst list = (item: Check, min = 0): Check => (v) => {\n if (!Array.isArray(v)) return 'expected an array'\n if (v.length < min) return `expected at least ${min} items`\n for (let i = 0; i < v.length; i++) {\n const e = item(v[i])\n if (e) return `[${i}]: ${e}`\n }\n return null\n}\nconst color: Check = (v) =>\n (typeof v === 'number' && Number.isInteger(v) && v >= 0 && v <= 0xffffff) || (typeof v === 'string' && /^#[0-9a-fA-F]{6}$/.test(v)) ? null : 'expected 0xRRGGBB or \"#rrggbb\"'\nconst oneOf = (values: readonly unknown[]): Check => (v) => (values.includes(v) ? null : `expected one of ${values.map((x) => JSON.stringify(x)).join(', ')}`)\nconst object = (fields: Readonly<Record<string, Check>>, required: readonly string[] = []): Check => (v) => {\n if (!v || typeof v !== 'object' || Array.isArray(v)) return 'expected an object'\n const record = v as Record<string, unknown>\n for (const key of required) if (record[key] === undefined) return `${key}: required`\n for (const key of Object.keys(record)) {\n if (record[key] === undefined) continue\n const check = fields[key]\n if (!check) return `${key}: unknown field`\n const e = check(record[key])\n if (e) return `${key}: ${e}`\n }\n return null\n}\n\nconst ORDERS = ['XYZ', 'YXZ', 'ZXY', 'ZYX', 'YZX', 'XZY'] as const\nconst TRANSFORM: Record<string, Check> = {\n position: tuple(3),\n rotation: tuple(3),\n rotationOrder: oneOf(ORDERS),\n scale: (v) => (typeof v === 'number' ? num(v) : tuple(3)(v)),\n heading: num,\n matrix: tuple(16),\n}\nconst UV = object({ u0: num, v0: num, u1: num, v1: num }, ['u0', 'v0', 'u1', 'v1'])\nconst INSTANCE = object({ ...TRANSFORM, color })\nconst COMMON: Record<string, Check> = {\n ...TRANSFORM,\n kind: str,\n name: str,\n part: str,\n material: str,\n color,\n uv: UV,\n instances: list(INSTANCE),\n minDetail: range(0, 1, true),\n tags: list(str),\n}\nconst vec2 = tuple(2)\nconst BEVEL: Check = (v) => (v === false ? null : object({ thickness: num, size: num, offset: num, segments: int(1) })(v))\nconst KINDS: Readonly<Record<string, { fields: Record<string, Check>; required?: string[] }>> = {\n group: { fields: {} },\n box: { fields: { size: tuple(3), segments: tuple(3, int(1)) } },\n cylinder: { fields: { radiusTop: num, radiusBottom: num, height: num, radialSegments: int(1), heightSegments: int(1), openEnded: bool, thetaStart: num, thetaLength: num } },\n cone: { fields: { radius: num, height: num, radialSegments: int(1), heightSegments: int(1), openEnded: bool, thetaStart: num, thetaLength: num } },\n sphere: { fields: { radius: num, widthSegments: int(3), heightSegments: int(2), phiStart: num, phiLength: num, thetaStart: num, thetaLength: num } },\n plane: { fields: { width: num, height: num, widthSegments: int(1), heightSegments: int(1) } },\n circle: { fields: { radius: num, segments: int(3), thetaStart: num, thetaLength: num } },\n ring: { fields: { innerRadius: num, outerRadius: num, thetaSegments: int(3), phiSegments: int(1), thetaStart: num, thetaLength: num } },\n lathe: { fields: { points: list(vec2, 2), segments: int(1), phiStart: num, phiLength: num }, required: ['points'] },\n torus: { fields: { radius: num, tube: num, radialSegments: int(1), tubularSegments: int(1), arc: num } },\n extrude: {\n fields: { shape: object({ outer: list(vec2, 3), holes: list(list(vec2, 3)) }, ['outer']), depth: num, steps: int(1), bevel: BEVEL },\n required: ['shape'],\n },\n difference: { fields: { size: tuple(3), subtract: list(object({ min: vec2, max: vec2 }, ['min', 'max'])) }, required: ['size', 'subtract'] },\n mesh: {\n fields: {\n positions: (v) => list(num)(v) ?? ((v as unknown[]).length % 3 === 0 ? null : 'expected a multiple of 3 numbers'),\n normals: list(num),\n uvs: list(num),\n indices: list(int(0)),\n },\n required: ['positions'],\n },\n point: { fields: { data: () => null } },\n custom: { fields: { generator: str, params: () => null }, required: ['generator'] },\n}\n\nfunction validateNode(node: unknown, path: string, errors: string[]): void {\n if (!node || typeof node !== 'object' || Array.isArray(node)) {\n errors.push(`${path}: expected a node object`)\n return\n }\n const record = node as Record<string, unknown>\n const spec = typeof record.kind === 'string' ? KINDS[record.kind] : undefined\n if (!spec) {\n errors.push(`${path}.kind: expected one of ${Object.keys(KINDS).join(', ')}`)\n return\n }\n for (const key of spec.required ?? []) if (record[key] === undefined) errors.push(`${path}.${key}: required`)\n for (const key of Object.keys(record)) {\n if (record[key] === undefined || key === 'children') continue\n const check = spec.fields[key] ?? COMMON[key]\n if (!check) {\n errors.push(`${path}.${key}: unknown field for a ${String(record.kind)}`)\n continue\n }\n const e = check(record[key])\n if (e) errors.push(`${path}.${key}: ${e}`)\n }\n const exclusive = ['matrix', 'heading'].filter((key) => record[key] !== undefined)\n if (record.matrix !== undefined && (record.position !== undefined || record.rotation !== undefined || record.scale !== undefined || record.heading !== undefined))\n errors.push(`${path}.matrix: cannot be combined with position, rotation, scale or heading`)\n else if (exclusive.includes('heading') && (record.rotation !== undefined || record.scale !== undefined)) errors.push(`${path}.heading: cannot be combined with rotation or scale`)\n if (record.children !== undefined) {\n if (record.kind !== 'group') errors.push(`${path}.children: only a group has children`)\n else if (!Array.isArray(record.children)) errors.push(`${path}.children: expected an array`)\n else record.children.forEach((child, index) => validateNode(child, `${path}.children[${index}]`, errors))\n }\n}\n\n/** Every rule of the JSON Schema, checked without a schema engine: one message per problem, with its path (`root.children[3].radialSegments: ...`). */\nexport function validateShape(schema: unknown): string[] {\n const errors: string[] = []\n if (!schema || typeof schema !== 'object' || Array.isArray(schema)) return ['expected a shape object']\n const record = schema as Record<string, unknown>\n for (const key of Object.keys(record)) if (!['version', 'name', 'materials', 'root', 'lod', 'cullDistance'].includes(key) && record[key] !== undefined) errors.push(`${key}: unknown field`)\n if (record.version !== 1) errors.push('version: expected 1')\n if (record.name !== undefined && typeof record.name !== 'string') errors.push('name: expected a string')\n if (record.materials !== undefined) {\n if (!record.materials || typeof record.materials !== 'object' || Array.isArray(record.materials)) errors.push('materials: expected an object')\n else {\n const material = object({\n color,\n roughness: range(0, 1),\n metalness: range(0, 1),\n opacity: range(0, 1),\n transparent: bool,\n emissive: color,\n emissiveIntensity: (v) => (typeof v === 'number' && v >= 0 ? null : 'expected a number >= 0'),\n side: oneOf(['front', 'back', 'double']),\n flatShading: bool,\n vertexColors: bool,\n depthWrite: bool,\n })\n for (const [id, hint] of Object.entries(record.materials)) {\n const e = material(hint)\n if (e) errors.push(`materials.${id}: ${e}`)\n }\n }\n }\n if (record.lod !== undefined) {\n const e = list(object({ distance: (v) => (typeof v === 'number' && v >= 0 ? null : 'expected a number >= 0'), detail: range(0, 1, true), minSize: (v) => (typeof v === 'number' && v >= 0 ? null : 'expected a number >= 0'), minSegments: int(3) }, ['distance', 'detail']))(record.lod)\n if (e) errors.push(`lod${e.startsWith('[') ? '' : ': '}${e}`)\n }\n if (record.cullDistance !== undefined && !(typeof record.cullDistance === 'number' && record.cullDistance > 0)) errors.push('cullDistance: expected a number > 0')\n if (record.root === undefined) errors.push('root: required')\n else validateNode(record.root, 'root', errors)\n return errors\n}\n\n/** Throws with every problem when the schema is not valid. */\nexport function assertValidShape(schema: unknown): asserts schema is ShapeSchema {\n const errors = validateShape(schema)\n if (errors.length) throw new Error(`game-shapes: invalid shape\\n ${errors.slice(0, 20).join('\\n ')}${errors.length > 20 ? `\\n ... ${errors.length - 20} more` : ''}`)\n}\n","import { colorChannels, colorHex } from './color'\nimport { hashValue } from './hash'\nimport { asMat4, identity4, isAffine, matrixOf, multiply, normalMatrix, transformPoint } from './math'\nimport type { Matrix4 } from './math'\nimport { buildPrimitive, primitiveParameters } from './primitives'\nimport { canonicalJson } from './hash'\nimport { streamFor } from './random'\nimport { getGenerator } from './registry'\nimport { assertValidShape } from './schema'\nimport type {\n Bounds,\n GenerateOptions,\n InstancedBatch,\n NamedRange,\n PrimitiveBuffers,\n PrimitiveNode,\n ShapeBatch,\n ShapeBuffers,\n ShapeLevel,\n ShapeNode,\n ShapePart,\n ShapePoint,\n ShapeSchema,\n UvRect,\n} from './types'\n\nconst PRIMITIVE_KINDS = new Set(['box', 'cylinder', 'cone', 'sphere', 'plane', 'circle', 'ring', 'lathe', 'torus', 'extrude', 'difference', 'mesh'])\nexport const isPrimitiveNode = (node: ShapeNode): node is PrimitiveNode => PRIMITIVE_KINDS.has(node.kind)\n\n/** The options that change the output, as they enter the cache key. */\nexport function keyOptions(options: GenerateOptions = {}): Record<string, unknown> {\n return {\n seed: options.seed ?? 0,\n colors: options.colors ?? 'linear',\n indexed: options.indexed ?? true,\n materials: options.materials,\n fallbackMaterial: options.fallbackMaterial,\n materialAlias: options.materialAlias,\n instancing: options.instancing,\n levels: options.levels,\n }\n}\n\n/** The cache key of a shape under some options: 64-bit FNV-1a of the canonical schema text and the options that change the output. */\nexport const shapeKey = (schema: ShapeSchema, options: GenerateOptions = {}): string => hashValue({ schema, options: keyOptions(options) })\n\nconst IDENTITY_UV: UvRect = { u0: 0, v0: 0, u1: 1, v1: 1 }\n\nclass FloatList {\n array: Float32Array\n length = 0\n constructor(capacity = 256) {\n this.array = new Float32Array(capacity)\n }\n reserve(extra: number): void {\n if (this.length + extra <= this.array.length) return\n let capacity = this.array.length * 2\n while (capacity < this.length + extra) capacity *= 2\n const next = new Float32Array(capacity)\n next.set(this.array.subarray(0, this.length))\n this.array = next\n }\n done(): Float32Array {\n return this.array.slice(0, this.length)\n }\n}\nclass IndexList {\n array: Uint32Array\n length = 0\n constructor(capacity = 256) {\n this.array = new Uint32Array(capacity)\n }\n reserve(extra: number): void {\n if (this.length + extra <= this.array.length) return\n let capacity = this.array.length * 2\n while (capacity < this.length + extra) capacity *= 2\n const next = new Uint32Array(capacity)\n next.set(this.array.subarray(0, this.length))\n this.array = next\n }\n}\n\n/** Growing buffers of one (target, material). */\nclass Accumulator {\n readonly positions = new FloatList()\n readonly normals = new FloatList()\n readonly uvs = new FloatList(170)\n readonly colors = new FloatList()\n readonly indices = new IndexList()\n readonly ranges: NamedRange[] = []\n vertices = 0\n constructor(\n readonly material: string,\n readonly part: string | null,\n ) {}\n}\n\ninterface Target {\n readonly part: string | null\n readonly accs: Map<string, Accumulator>\n}\n\ninterface Settings {\n readonly colors: 'linear' | 'srgb' | 'none'\n readonly indexed: boolean\n readonly materials?: ReadonlySet<string>\n readonly fallback?: string\n readonly alias?: Readonly<Record<string, string>>\n readonly seed: number | string\n readonly generators?: GenerateOptions['generators']\n readonly isolatedGenerators?: boolean\n readonly autoMin?: number\n}\n\ninterface LevelSpec {\n readonly distance: number\n readonly detail: number\n readonly minSize?: number\n readonly minSegments?: number\n readonly index: number\n}\n\ninterface Inherited {\n readonly material: string\n readonly color: number\n readonly uv: UvRect\n readonly target: Target\n}\n\n/**\n * Bakes a template into an accumulator: positions by the world matrix, normals by its normal matrix and normalised, uvs into the atlas cell,\n * one colour for all, indices offset. The arithmetic is three's (`createMeshApi`, `BufferGeometry.applyMatrix4`), so the bits match.\n */\nfunction bake(acc: Accumulator, t: PrimitiveBuffers, world: readonly number[], rgb: readonly number[] | null, u: UvRect, indexed: boolean): void {\n const e = world\n const nm = normalMatrix(world)\n const affine = isAffine(world)\n const source = t.positions\n const count = source.length / 3\n const order: ArrayLike<number> | null = indexed ? null : t.indices ?? null\n const emitted = order ? order.length : count\n acc.positions.reserve(emitted * 3)\n acc.normals.reserve(emitted * 3)\n acc.uvs.reserve(emitted * 2)\n if (rgb) acc.colors.reserve(emitted * 3)\n const P = acc.positions.array\n const N = acc.normals.array\n const U = acc.uvs.array\n const C = acc.colors.array\n let pi = acc.positions.length\n let ni = acc.normals.length\n let ui = acc.uvs.length\n let ci = acc.colors.length\n const du = u.u1 - u.u0\n const dv = u.v1 - u.v0\n const tn = t.normals\n const tu = t.uvs\n for (let k = 0; k < emitted; k++) {\n const i = order ? (order[k] as number) : k\n const x = source[i * 3] as number\n const y = source[i * 3 + 1] as number\n const z = source[i * 3 + 2] as number\n if (affine) {\n P[pi++] = e[0]! * x + e[4]! * y + e[8]! * z + e[12]!\n P[pi++] = e[1]! * x + e[5]! * y + e[9]! * z + e[13]!\n P[pi++] = e[2]! * x + e[6]! * y + e[10]! * z + e[14]!\n } else {\n const p = transformPoint(e, x, y, z)\n P[pi++] = p[0]\n P[pi++] = p[1]\n P[pi++] = p[2]\n }\n const nx = tn[i * 3] as number\n const ny = tn[i * 3 + 1] as number\n const nz = tn[i * 3 + 2] as number\n const vx = nm[0]! * nx + nm[3]! * ny + nm[6]! * nz\n const vy = nm[1]! * nx + nm[4]! * ny + nm[7]! * nz\n const vz = nm[2]! * nx + nm[5]! * ny + nm[8]! * nz\n const s = 1 / (Math.sqrt(vx * vx + vy * vy + vz * vz) || 1)\n N[ni++] = vx * s\n N[ni++] = vy * s\n N[ni++] = vz * s\n U[ui++] = u.u0 + (tu[i * 2] as number) * du\n U[ui++] = u.v0 + (tu[i * 2 + 1] as number) * dv\n if (rgb) {\n C[ci++] = rgb[0]!\n C[ci++] = rgb[1]!\n C[ci++] = rgb[2]!\n }\n }\n acc.positions.length = pi\n acc.normals.length = ni\n acc.uvs.length = ui\n acc.colors.length = ci\n if (indexed) {\n const base = acc.vertices\n const idx = t.indices\n const n = idx ? idx.length : count\n acc.indices.reserve(n)\n const I = acc.indices.array\n let ii = acc.indices.length\n for (let k = 0; k < n; k++) I[ii++] = base + (idx ? (idx[k] as number) : k)\n acc.indices.length = ii\n }\n acc.vertices += emitted\n}\n\nfunction boundsOf(positions: Float32Array): Bounds {\n const min: [number, number, number] = [Infinity, Infinity, Infinity]\n const max: [number, number, number] = [-Infinity, -Infinity, -Infinity]\n for (let i = 0; i < positions.length; i += 3) {\n for (let axis = 0; axis < 3; axis++) {\n const v = positions[i + axis] as number\n if (v < min[axis]!) min[axis] = v\n if (v > max[axis]!) max[axis] = v\n }\n }\n return { min, max }\n}\n\nfunction finish(acc: Accumulator, indexed: boolean, withColors: boolean): ShapeBatch {\n const positions = acc.positions.done()\n const indices = indexed ? (acc.vertices > 65535 ? acc.indices.array.slice(0, acc.indices.length) : Uint16Array.from(acc.indices.array.subarray(0, acc.indices.length))) : undefined\n return {\n material: acc.material,\n part: acc.part,\n positions,\n normals: acc.normals.done(),\n uvs: acc.uvs.done(),\n ...(withColors ? { colors: acc.colors.done() } : {}),\n ...(indices ? { indices } : {}),\n ranges: acc.ranges,\n bounds: boundsOf(positions),\n }\n}\n\nconst radiusOf = (t: PrimitiveBuffers, world: readonly number[]): number => {\n let r = 0\n const p = t.positions\n for (let i = 0; i < p.length; i += 3) r = Math.max(r, Math.hypot(p[i] as number, p[i + 1] as number, p[i + 2] as number))\n const sx = Math.hypot(world[0]!, world[1]!, world[2]!)\n const sy = Math.hypot(world[4]!, world[5]!, world[6]!)\n const sz = Math.hypot(world[8]!, world[9]!, world[10]!)\n return r * Math.max(sx, sy, sz)\n}\n\ninterface LevelResult {\n readonly level: ShapeLevel\n readonly parts: ShapePart[]\n readonly points: ShapePoint[]\n readonly primitives: number\n}\n\n/** Expands a custom node into the node its generator returns (wrapped so that the custom node's own fields still apply). */\nfunction expandCustom(node: Extract<ShapeNode, { kind: 'custom' }>, settings: Settings, detail: number, path: string): ShapeNode {\n const generator = settings.generators?.[node.generator] ?? (settings.isolatedGenerators ? undefined : getGenerator(node.generator))\n if (!generator) throw new Error(`game-shapes: no generator \"${node.generator}\" (register it with registerGenerator, in the worker too) at ${path}`)\n const generated = generator(node.params, { random: streamFor(settings.seed, node.generator, path), seed: settings.seed, detail, path })\n const { kind: _kind, generator: _generator, params: _params, ...common } = node\n return { ...common, kind: 'group', children: [generated] }\n}\n\n/** The key two leaf primitives share when one can stand for the other as an instance. */\nconst leafKey = (node: PrimitiveNode, material: string, color: number, uv: UvRect, level: LevelSpec): string =>\n `${node.kind}|${canonicalJson(primitiveParameters(node, level))}|${material}|${color}|${uv.u0},${uv.v0},${uv.u1},${uv.v1}`\n\nfunction runLevel(schema: ShapeSchema, settings: Settings, level: LevelSpec): LevelResult {\n const bakeParts = level.index > 0\n const root: Target = { part: null, accs: new Map() }\n const targets: Target[] = [root]\n const partTargets = new Map<string, Target>()\n const parts: ShapePart[] = []\n const points: ShapePoint[] = []\n const instanced: InstancedBatch[] = []\n let primitives = 0\n const space = settings.colors === 'none' ? null : settings.colors\n const channels = new Map<number, readonly number[]>()\n const rgbOf = (hex: number): readonly number[] | null => {\n if (!space) return null\n let c = channels.get(hex)\n if (!c) {\n c = colorChannels(hex, space)\n channels.set(hex, c)\n }\n return c\n }\n const resolveMaterial = (id: string): string => {\n let out = id\n if (settings.materials && !settings.materials.has(out)) out = settings.fallback ?? out\n return settings.alias?.[out] ?? out\n }\n const accOf = (target: Target, material: string): Accumulator => {\n let acc = target.accs.get(material)\n if (!acc) {\n acc = new Accumulator(material, target.part)\n target.accs.set(material, acc)\n }\n return acc\n }\n\n // automatic instancing: count the leaves first\n const autoCounts = new Map<string, number>()\n const autoGroups = new Map<string, { node: PrimitiveNode; material: string; color: number; part: string | null; matrices: number[] }>()\n\n const count = (node: ShapeNode, inherited: { material: string; color: number; uv: UvRect; inPart: boolean }, path: string): void => {\n if (node.minDetail !== undefined && level.detail < node.minDetail) return\n if (node.kind === 'custom') return count(expandCustom(node, settings, level.detail, path), inherited, path)\n const next = {\n material: node.material !== undefined ? resolveMaterial(node.material) : inherited.material,\n color: node.color !== undefined ? colorHex(node.color) : inherited.color,\n uv: node.uv ?? inherited.uv,\n inPart: inherited.inPart || (node.part !== undefined && !bakeParts),\n }\n if (node.instances) return\n if (node.kind === 'group') {\n node.children?.forEach((child, index) => count(child, next, `${path}/${index}`))\n return\n }\n if (isPrimitiveNode(node) && node.name === undefined && !next.inPart) {\n const key = leafKey(node, next.material, next.color, next.uv, level)\n autoCounts.set(key, (autoCounts.get(key) ?? 0) + 1)\n }\n }\n const rootMaterial = resolveMaterial('default')\n if (settings.autoMin !== undefined) count(schema.root, { material: rootMaterial, color: 0xffffff, uv: IDENTITY_UV, inPart: false }, 'root')\n\n /** Draws a subtree into `collector` (a target) under `parent` (null: identity). */\n const visit = (node: ShapeNode, parent: Matrix4 | null, inherited: Inherited, path: string): void => {\n if (node.minDetail !== undefined && level.detail < node.minDetail) return\n if (node.kind === 'custom') return visit(expandCustom(node, settings, level.detail, path), parent, inherited, path)\n let target = inherited.target\n let parentWorld = parent\n if (node.part !== undefined) {\n if (bakeParts) {\n // far levels freeze the parts at rest: the part's pivot is relative to the root\n parentWorld = null\n } else {\n let partTarget = partTargets.get(node.part)\n if (!partTarget) {\n partTarget = { part: node.part, accs: new Map() }\n partTargets.set(node.part, partTarget)\n targets.push(partTarget)\n const { position, rotation, rotationOrder, scale, heading, matrix } = node\n parts.push({\n id: node.part,\n ...(position ? { position } : {}),\n ...(rotation ? { rotation } : {}),\n ...(rotationOrder ? { rotationOrder } : {}),\n ...(scale !== undefined ? { scale } : {}),\n ...(heading !== undefined ? { heading } : {}),\n ...(matrix ? { matrix } : { matrix: asMat4(matrixOf(node)) }),\n })\n }\n target = partTarget\n }\n }\n const isPart = node.part !== undefined && !bakeParts\n const local = isPart ? null : matrixOf(node)\n const world = isPart ? null : local ? (parentWorld ? multiply(parentWorld, local) : local) : parentWorld\n const next: Inherited = {\n material: node.material !== undefined ? resolveMaterial(node.material) : inherited.material,\n color: node.color !== undefined ? colorHex(node.color) : inherited.color,\n uv: node.uv ?? inherited.uv,\n target,\n }\n\n if (node.instances) {\n drawInstanced(node, parentWorld, next, path)\n return\n }\n\n const snapshot = node.name !== undefined ? new Map([...target.accs].map(([m, a]) => [m, { v: a.vertices, i: a.indices.length }])) : null\n\n if (node.kind === 'group') {\n node.children?.forEach((child, index) => visit(child, world, next, `${path}/${index}`))\n } else if (node.kind === 'point') {\n const p = node.position ?? [0, 0, 0]\n points.push({\n ...(node.name !== undefined ? { name: node.name } : {}),\n part: target.part,\n position: parent ? transformPoint(parent, p[0], p[1], p[2]) : [p[0], p[1], p[2]],\n ...(node.data !== undefined ? { data: node.data } : {}),\n ...(node.tags ? { tags: node.tags } : {}),\n })\n } else if (isPrimitiveNode(node)) {\n const template = buildPrimitive(node, level)\n const m = world ?? identity4()\n if (level.minSize !== undefined && radiusOf(template, m) < level.minSize) return\n primitives++\n if (settings.autoMin !== undefined && node.name === undefined && target.part === null) {\n const key = leafKey(node, next.material, next.color, next.uv, level)\n if ((autoCounts.get(key) ?? 0) >= settings.autoMin) {\n let group = autoGroups.get(key)\n if (!group) {\n group = { node, material: next.material, color: next.color, part: null, matrices: [] }\n autoGroups.set(key, group)\n }\n group.matrices.push(...m)\n return\n }\n }\n bake(accOf(target, next.material), template, m, rgbOf(next.color), next.uv, settings.indexed)\n }\n\n if (snapshot) {\n for (const [material, acc] of target.accs) {\n const before = snapshot.get(material) ?? { v: 0, i: 0 }\n if (acc.vertices === before.v) continue\n acc.ranges.push({\n name: node.name as string,\n ...(node.tags ? { tags: node.tags } : {}),\n vertexStart: before.v,\n vertexCount: acc.vertices - before.v,\n indexStart: before.i,\n indexCount: acc.indices.length - before.i,\n })\n }\n }\n }\n\n const drawInstanced = (node: ShapeNode, parentWorld: Matrix4 | null, inherited: Inherited, path: string): void => {\n const collector: Target = { part: inherited.target.part, accs: new Map() }\n const { instances, ...single } = node as ShapeNode & { instances: NonNullable<ShapeNode['instances']> }\n const before = parts.length\n visit(single as ShapeNode, null, { ...inherited, target: collector }, path)\n if (parts.length !== before) throw new Error(`game-shapes: an instanced node cannot hold parts (${path})`)\n const matrices = new Float32Array(instances.length * 16)\n const hasColors = instances.some((instance) => instance.color !== undefined)\n const colors = hasColors ? new Float32Array(instances.length * 3) : undefined\n instances.forEach((instance, index) => {\n const local = matrixOf(instance)\n const m = local ? (parentWorld ? multiply(parentWorld, local) : local) : parentWorld ?? identity4()\n matrices.set(m, index * 16)\n if (colors) colors.set(colorChannels(colorHex(instance.color), space ?? 'srgb'), index * 3)\n })\n for (const acc of collector.accs.values()) {\n const batch = finish(acc, settings.indexed, false)\n instanced.push({\n material: acc.material,\n part: collector.part,\n ...(node.name !== undefined ? { name: node.name } : {}),\n geometry: { type: 'BufferGeometry', parameters: {}, positions: batch.positions, normals: batch.normals, uvs: batch.uvs, ...(batch.indices ? { indices: batch.indices } : {}), groups: [] },\n matrices: matrices.slice(),\n ...(colors ? { colors: colors.slice() } : {}),\n count: instances.length,\n })\n }\n }\n\n visit(schema.root, null, { material: rootMaterial, color: 0xffffff, uv: IDENTITY_UV, target: root }, 'root')\n\n for (const group of autoGroups.values()) {\n const template = buildPrimitive(group.node, level)\n const n = group.matrices.length / 16\n const rgb = colorChannels(group.color, space ?? 'srgb')\n const colors = new Float32Array(n * 3)\n for (let i = 0; i < n; i++) colors.set(rgb, i * 3)\n instanced.push({\n material: group.material,\n part: null,\n geometry: { type: template.type, parameters: template.parameters, positions: template.positions.slice(), normals: template.normals.slice(), uvs: template.uvs.slice(), ...(template.indices ? { indices: template.indices.slice() } : {}), groups: template.groups.map((g) => ({ ...g })) },\n matrices: new Float32Array(group.matrices),\n ...(space ? { colors } : {}),\n count: n,\n })\n }\n\n const batches: ShapeBatch[] = []\n for (const target of targets) for (const acc of target.accs.values()) if (acc.vertices > 0 && (!settings.indexed || acc.indices.length > 0)) batches.push(finish(acc, settings.indexed, space !== null))\n return { level: { distance: level.distance, detail: level.detail, batches, instanced }, parts, points, primitives }\n}\n\n/**\n * Turns a shape into buffers: one batch per (part, material) per level, baked; instanced nodes as instance batches; parts with their pivots;\n * points. Pure: the same schema and options give the same bytes, here or in a worker.\n */\nexport function generate(schema: ShapeSchema, options: GenerateOptions = {}): ShapeBuffers {\n if (options.validate !== false) assertValidShape(schema)\n const settings: Settings = {\n colors: options.colors ?? 'linear',\n indexed: options.indexed ?? true,\n ...(options.materials ? { materials: new Set(options.materials) } : {}),\n ...(options.fallbackMaterial !== undefined ? { fallback: options.fallbackMaterial } : {}),\n ...(options.materialAlias ? { alias: options.materialAlias } : {}),\n seed: options.seed ?? 0,\n ...(options.generators ? { generators: options.generators } : {}),\n ...(options.isolatedGenerators ? { isolatedGenerators: true } : {}),\n ...(options.instancing ? { autoMin: Math.max(2, options.instancing.minCount) } : {}),\n }\n const specs: LevelSpec[] = [{ distance: 0, detail: 1, index: 0 }, ...(schema.lod ?? []).map((rule, index) => ({ ...rule, index: index + 1 }))]\n const wanted = options.levels ? specs.filter((spec) => options.levels!.includes(spec.index)) : specs\n let parts: ShapePart[] = []\n let points: ShapePoint[] = []\n let primitives = 0\n const levels: ShapeLevel[] = []\n for (const spec of wanted) {\n const result = runLevel(schema, settings, spec)\n if (spec.index === 0 || levels.length === 0) {\n parts = result.parts\n points = result.points\n primitives = result.primitives\n }\n levels.push(result.level)\n }\n const first = levels[0]\n const vertices = first ? first.batches.reduce((sum, batch) => sum + batch.positions.length / 3, 0) : 0\n const triangles = first\n ? first.batches.reduce((sum, batch) => sum + (batch.indices ? batch.indices.length : batch.positions.length / 3) / 3, 0) +\n first.instanced.reduce((sum, batch) => sum + ((batch.geometry.indices ? batch.geometry.indices.length : batch.geometry.positions.length / 3) / 3) * batch.count, 0)\n : 0\n return {\n key: options.key === false ? '' : options.key ?? shapeKey(schema, options),\n ...(schema.name !== undefined ? { name: schema.name } : {}),\n levels,\n parts,\n points,\n ...(schema.materials ? { materials: schema.materials } : {}),\n ...(schema.cullDistance !== undefined ? { cullDistance: schema.cullDistance } : {}),\n stats: { vertices, triangles, batches: first ? first.batches.length : 0, instances: first ? first.instanced.reduce((sum, batch) => sum + batch.count, 0) : 0, primitives },\n }\n}\n","import { normalMatrix, transformPoint } from './math'\nimport type { InstancedBatch, PrimitiveBuffers, ShapeBatch, ShapeBuffers } from './types'\n\n/** One piece for `mergeTransformed`: arrays (any may be missing) and the matrix that takes them into the merged space. */\nexport interface TransformedPiece {\n readonly positions: ArrayLike<number>\n readonly normals?: ArrayLike<number>\n readonly uvs?: ArrayLike<number>\n readonly indices?: ArrayLike<number> | null\n /** Column-major 4 x 4. */\n readonly matrix: readonly number[]\n}\n\nexport interface MergedArrays {\n readonly positions: Float32Array\n readonly normals: Float32Array\n readonly uvs: Float32Array\n}\n\n/**\n * Joins pieces into one non-indexed set of arrays, each piece de-indexed, its missing normals and uvs zero-filled, its positions by\n * `Vector3.applyMatrix4` and its normals by the normal matrix and normalised: what three's `toNonIndexed` + `applyMatrix4` + a join do,\n * bit for bit (the port of `mergeByMaterial`).\n */\nexport function mergeTransformed(pieces: readonly TransformedPiece[]): MergedArrays {\n let total = 0\n for (const piece of pieces) total += piece.indices ? piece.indices.length : piece.positions.length / 3\n const positions = new Float32Array(total * 3)\n const normals = new Float32Array(total * 3)\n const uvs = new Float32Array(total * 2)\n let at = 0\n for (const piece of pieces) {\n const e = piece.matrix\n const nm = normalMatrix(e)\n const count = piece.indices ? piece.indices.length : piece.positions.length / 3\n // de-index into Float32 first, as toNonIndexed does, then transform the stored values\n const p = new Float32Array(count * 3)\n const n = new Float32Array(count * 3)\n for (let k = 0; k < count; k++) {\n const i = piece.indices ? (piece.indices[k] as number) : k\n for (let c = 0; c < 3; c++) {\n p[k * 3 + c] = piece.positions[i * 3 + c] as number\n if (piece.normals) n[k * 3 + c] = piece.normals[i * 3 + c] as number\n }\n if (piece.uvs) {\n uvs[(at + k) * 2] = piece.uvs[i * 2] as number\n uvs[(at + k) * 2 + 1] = piece.uvs[i * 2 + 1] as number\n }\n }\n for (let k = 0; k < count; k++) {\n const q = transformPoint(e, p[k * 3] as number, p[k * 3 + 1] as number, p[k * 3 + 2] as number)\n positions[(at + k) * 3] = q[0]\n positions[(at + k) * 3 + 1] = q[1]\n positions[(at + k) * 3 + 2] = q[2]\n const x = n[k * 3] as number\n const y = n[k * 3 + 1] as number\n const z = n[k * 3 + 2] as number\n const vx = nm[0]! * x + nm[3]! * y + nm[6]! * z\n const vy = nm[1]! * x + nm[4]! * y + nm[7]! * z\n const vz = nm[2]! * x + nm[5]! * y + nm[8]! * z\n const s = 1 / (Math.sqrt(vx * vx + vy * vy + vz * vz) || 1)\n normals[(at + k) * 3] = vx * s\n normals[(at + k) * 3 + 1] = vy * s\n normals[(at + k) * 3 + 2] = vz * s\n }\n at += count\n }\n return { positions, normals, uvs }\n}\n\nconst primitiveArrays = (p: PrimitiveBuffers): ArrayBuffer[] => [p.positions, p.normals, p.uvs, ...(p.indices ? [p.indices] : [])].map((a) => a.buffer as ArrayBuffer)\nconst batchArrays = (b: ShapeBatch): ArrayBuffer[] => [b.positions, b.normals, b.uvs, ...(b.colors ? [b.colors] : []), ...(b.indices ? [b.indices] : [])].map((a) => a.buffer as ArrayBuffer)\nconst instancedArrays = (b: InstancedBatch): ArrayBuffer[] => [...primitiveArrays(b.geometry), b.matrices.buffer as ArrayBuffer, ...(b.colors ? [b.colors.buffer as ArrayBuffer] : [])]\n\n/** Every `ArrayBuffer` of a result, for `postMessage(buffers, transferablesOf(buffers))`. */\nexport function transferablesOf(buffers: ShapeBuffers): ArrayBuffer[] {\n const out = new Set<ArrayBuffer>()\n for (const level of buffers.levels) {\n for (const batch of level.batches) for (const a of batchArrays(batch)) out.add(a)\n for (const batch of level.instanced) for (const a of instancedArrays(batch)) out.add(a)\n }\n return [...out]\n}\n\nconst copyPrimitive = (p: PrimitiveBuffers): PrimitiveBuffers => ({\n ...p,\n positions: p.positions.slice(),\n normals: p.normals.slice(),\n uvs: p.uvs.slice(),\n ...(p.indices ? { indices: p.indices.slice() } : {}),\n groups: p.groups.map((g) => ({ ...g })),\n})\n\n/** A deep copy whose arrays are its own (cached results are shared and read-only; copy before deforming). */\nexport function cloneBuffers(buffers: ShapeBuffers): ShapeBuffers {\n return {\n ...buffers,\n levels: buffers.levels.map((level) => ({\n ...level,\n batches: level.batches.map((b) => ({\n ...b,\n positions: b.positions.slice(),\n normals: b.normals.slice(),\n uvs: b.uvs.slice(),\n ...(b.colors ? { colors: b.colors.slice() } : {}),\n ...(b.indices ? { indices: b.indices.slice() } : {}),\n ranges: b.ranges.map((r) => ({ ...r })),\n bounds: { min: [...b.bounds.min], max: [...b.bounds.max] },\n })),\n instanced: level.instanced.map((b) => ({ ...b, geometry: copyPrimitive(b.geometry), matrices: b.matrices.slice(), ...(b.colors ? { colors: b.colors.slice() } : {}) })),\n })),\n }\n}\n\n/** Bytes held by the arrays of a result (what the cache counts). */\nexport function byteSizeOf(buffers: ShapeBuffers): number {\n return transferablesOf(buffers).reduce((sum, buffer) => sum + buffer.byteLength, 0)\n}\n","import { generate } from './generate'\nimport { transferablesOf } from './merge'\nimport type { GenerateOptions, ShapeBuffers, ShapeGenerator, ShapeSchema } from './types'\n\n/** A job the pool posts: `options` without `generators` (functions do not cross threads; register them in the worker script). */\nexport interface ShapeJob {\n readonly id: number\n readonly schema: ShapeSchema\n readonly options?: Omit<GenerateOptions, 'generators'>\n}\n\nexport type ShapeReply = { readonly id: number; readonly ok: true; readonly buffers: ShapeBuffers } | { readonly id: number; readonly ok: false; readonly error: string }\n\n/** What a worker scope looks like: `self` in a browser worker, a `worker_threads` `parentPort` wrapper in Node, or an in-thread fake. */\nexport interface WorkerScopeLike {\n onmessage: ((event: { data: unknown }) => void) | null\n postMessage(message: unknown, transfer?: ArrayBuffer[]): void\n}\n\nexport interface ShapeWorkerOptions {\n /** Generators of the `custom` nodes, the same ones the main thread registers. */\n readonly generators?: Readonly<Record<string, ShapeGenerator>>\n /** Transfer the arrays back (default true: zero copy, the worker keeps nothing). */\n readonly transfer?: boolean\n}\n\n/**\n * The worker side of `createShapePool`: answers each job with `generate(schema, options)`, transferring every array.\n *\n * ```ts\n * // shape-worker.ts\n * import { installShapeWorker } from '@adatechnology/game-shapes/worker'\n * installShapeWorker(self as never, { generators: myGenerators })\n * ```\n */\nexport function installShapeWorker(scope: WorkerScopeLike, options: ShapeWorkerOptions = {}): void {\n const transfer = options.transfer !== false\n scope.onmessage = (event) => {\n const job = event.data as ShapeJob\n try {\n const buffers = generate(job.schema, { ...(job.options ?? {}), ...(options.generators ? { generators: options.generators } : {}) })\n const reply: ShapeReply = { id: job.id, ok: true, buffers }\n scope.postMessage(reply, transfer ? transferablesOf(buffers) : [])\n } catch (error) {\n const reply: ShapeReply = { id: job.id, ok: false, error: error instanceof Error ? error.message : String(error) }\n scope.postMessage(reply)\n }\n }\n}\n","import type { PrimitiveBuffers, ShapeBatch } from '@adatechnology/game-shapes'\nimport type * as THREE_NS from 'three'\n\nexport type Three = typeof THREE_NS\n\nexport interface ToGeometryOptions {\n /** Copy the arrays into the attributes (default true). Without a copy the geometry shares the buffers (a cached result is read-only: do not deform it). */\n readonly copy?: boolean\n}\n\n/** The order in which three's generator of each type sets its attributes (toJSON and attribute iteration keep it). */\nconst UV_BEFORE_NORMAL = new Set(['LatheGeometry', 'ExtrudeGeometry'])\n/** Types whose constructor sets the index before the attributes. */\nconst INDEX_FIRST = new Set(['BoxGeometry', 'CylinderGeometry', 'ConeGeometry', 'SphereGeometry', 'PlaneGeometry', 'CircleGeometry', 'RingGeometry', 'LatheGeometry', 'TorusGeometry'])\n\nconst isPrimitive = (value: PrimitiveBuffers | ShapeBatch): value is PrimitiveBuffers => typeof (value as PrimitiveBuffers).type === 'string'\n\n/** Three's own parameters object for a type (lathe points as `Vector2`). */\nfunction threeParameters(THREE: Three, p: PrimitiveBuffers): Record<string, unknown> {\n if (p.type === 'LatheGeometry') {\n const points = (p.parameters.points as Array<{ x: number; y: number }>).map(({ x, y }) => new THREE.Vector2(x, y))\n return { ...p.parameters, points }\n }\n return { ...p.parameters }\n}\n\n/**\n * A `BufferGeometry` from game-shapes buffers. A primitive becomes an instance of the three class it reproduces (`type`, `parameters`, prototype:\n * `instanceof THREE.CylinderGeometry`, `clone()` and `toJSON()` behave as three's), with the same attributes, index and groups. A batch gets\n * `position`, `normal`, `uv`, `color` (when present) and its index.\n */\nexport function toGeometry(THREE: Three, source: PrimitiveBuffers | ShapeBatch, options: ToGeometryOptions = {}): THREE_NS.BufferGeometry {\n const copy = options.copy !== false\n const primitive = isPrimitive(source) ? source : null\n const ThreeClass = primitive && primitive.type !== 'BufferGeometry' ? ((THREE as unknown as Record<string, unknown>)[primitive.type] as (new () => THREE_NS.BufferGeometry) | undefined) : undefined\n // the BufferGeometry constructor only, with the prototype of the class it stands for\n const geometry = (ThreeClass ? Reflect.construct(THREE.BufferGeometry, [], ThreeClass) : new THREE.BufferGeometry()) as THREE_NS.BufferGeometry & { parameters?: Record<string, unknown> }\n if (primitive && ThreeClass) {\n Object.assign(geometry, { type: primitive.type, parameters: threeParameters(THREE, primitive) })\n }\n const attribute = (array: Float32Array, size: number): THREE_NS.BufferAttribute => (copy ? new THREE.Float32BufferAttribute(array, size) : new THREE.BufferAttribute(array, size))\n const index = source.indices\n const setIndex = (): void => {\n if (!index) return\n geometry.setIndex(copy ? (index instanceof Uint32Array ? new THREE.Uint32BufferAttribute(index, 1) : new THREE.Uint16BufferAttribute(index, 1)) : new THREE.BufferAttribute(index, 1))\n }\n const type = primitive?.type ?? 'BufferGeometry'\n if (INDEX_FIRST.has(type)) setIndex()\n geometry.setAttribute('position', attribute(source.positions, 3))\n if (UV_BEFORE_NORMAL.has(type)) {\n geometry.setAttribute('uv', attribute(source.uvs, 2))\n geometry.setAttribute('normal', attribute(source.normals, 3))\n } else {\n geometry.setAttribute('normal', attribute(source.normals, 3))\n geometry.setAttribute('uv', attribute(source.uvs, 2))\n }\n if (!primitive && (source as ShapeBatch).colors) geometry.setAttribute('color', attribute((source as ShapeBatch).colors as Float32Array, 3))\n if (!INDEX_FIRST.has(type)) setIndex()\n if (primitive) for (const group of primitive.groups) geometry.addGroup(group.start, group.count, group.materialIndex)\n return geometry\n}\n","import { box, circle, cone, cylinder, extrude, lathe, plane, ring, sphere, torus } from '@adatechnology/game-shapes'\nimport type { BevelSpec, PrimitiveBuffers, PrimitiveNode, ShapeOutline, Vec2 } from '@adatechnology/game-shapes'\nimport { buildPrimitive } from '@adatechnology/game-shapes'\nimport type * as THREE_NS from 'three'\n\nimport { toGeometry } from './geometry'\nimport type { Three } from './geometry'\n\n/** The geometry constructors `shapeGeometries` replaces. */\nexport const SHAPE_GEOMETRY_TYPES = ['BoxGeometry', 'CylinderGeometry', 'ConeGeometry', 'SphereGeometry', 'PlaneGeometry', 'CircleGeometry', 'RingGeometry', 'LatheGeometry', 'TorusGeometry', 'ExtrudeGeometry'] as const\n\ntype Curve = { isLineCurve?: boolean; v1?: { x: number; y: number }; v2?: { x: number; y: number } }\ntype PathLike = { curves: Curve[]; autoClose?: boolean; holes?: PathLike[] }\n\n/** The point list of a path made only of contiguous `LineCurve`s (what `moveTo` + `lineTo` builds), or null. */\nfunction linePoints(path: PathLike): Vec2[] | null {\n if (path.autoClose || path.curves.length === 0) return null\n const points: Vec2[] = []\n for (const [index, curve] of path.curves.entries()) {\n if (!curve.isLineCurve || !curve.v1 || !curve.v2) return null\n if (index === 0) points.push([curve.v1.x, curve.v1.y])\n else {\n const last = points[points.length - 1] as Vec2\n if (last[0] !== curve.v1.x || last[1] !== curve.v1.y) return null\n }\n points.push([curve.v2.x, curve.v2.y])\n }\n return points\n}\n\ntype ExtrudeOptions = { depth?: number; steps?: number; bevelEnabled?: boolean; bevelThickness?: number; bevelSize?: number; bevelOffset?: number; bevelSegments?: number; extrudePath?: unknown; UVGenerator?: unknown; curveSegments?: number }\n\n/** An extrusion game-shapes can make exactly: one straight-edged shape, no path, the world uvs. */\nfunction extrudeOf(shapes: unknown, options: ExtrudeOptions = {}): PrimitiveBuffers | null {\n if (Array.isArray(shapes) || !shapes || options.extrudePath !== undefined || options.UVGenerator !== undefined) return null\n const shape = shapes as PathLike\n const outer = linePoints(shape)\n if (!outer) return null\n const holes: Vec2[][] = []\n for (const hole of shape.holes ?? []) {\n const points = linePoints(hole)\n if (!points) return null\n holes.push(points)\n }\n const outline: ShapeOutline = holes.length ? { outer, holes } : { outer }\n const bevel: false | BevelSpec =\n options.bevelEnabled === false\n ? false\n : {\n ...(options.bevelThickness !== undefined ? { thickness: options.bevelThickness } : {}),\n ...(options.bevelSize !== undefined ? { size: options.bevelSize } : {}),\n ...(options.bevelOffset !== undefined ? { offset: options.bevelOffset } : {}),\n ...(options.bevelSegments !== undefined ? { segments: options.bevelSegments } : {}),\n }\n return extrude(outline, options.depth, options.steps, bevel)\n}\n\nexport interface ShapeGeometriesOptions {\n /** Go through the template cache of game-shapes (default true): the same primitive is generated once per process. */\n readonly cache?: boolean\n}\n\nconst namespaces = new WeakMap<object, Map<string, Three>>()\n/** The namespaces this module made: wrapping one again gives it back (a package handing its `T` to another). */\nconst shims = new WeakSet<object>()\n\n/**\n * A three namespace whose `BoxGeometry`, `CylinderGeometry`, `ConeGeometry`, `SphereGeometry`, `PlaneGeometry`, `CircleGeometry`, `RingGeometry`,\n * `LatheGeometry`, `TorusGeometry` and (straight-edged) `ExtrudeGeometry` are made by game-shapes, bit-identical to three's and instances of three's\n * classes; everything else is three's own. Hand it to code ported from the teams as `T`, so it keeps reading `new T.CylinderGeometry(...)`.\n * An extrusion with curves, several shapes, a path or a custom uv generator falls back to three.\n */\nexport function shapeGeometries(THREE: Three, options: ShapeGeometriesOptions = {}): Three {\n if (shims.has(THREE)) return THREE\n const cache = options.cache !== false\n const key = cache ? 'cache' : 'nocache'\n let byKey = namespaces.get(THREE)\n if (!byKey) {\n byKey = new Map()\n namespaces.set(THREE, byKey)\n }\n const existing = byKey.get(key)\n if (existing) return existing\n const made = (p: PrimitiveBuffers): THREE_NS.BufferGeometry => toGeometry(THREE, p)\n const viaCache = (node: PrimitiveNode, direct: () => PrimitiveBuffers): PrimitiveBuffers => (cache ? buildPrimitive(node) : direct())\n const defined = <T extends object>(o: T): T => Object.fromEntries(Object.entries(o).filter(([, v]) => v !== undefined)) as T\n const constructors = {\n BoxGeometry: (width?: number, height?: number, depth?: number, ws = 1, hs = 1, ds = 1) =>\n made(viaCache({ kind: 'box', size: [width ?? 1, height ?? 1, depth ?? 1], segments: [ws, hs, ds] }, () => box(width, height, depth, ws, hs, ds))),\n CylinderGeometry: (radiusTop?: number, radiusBottom?: number, height?: number, radialSegments?: number, heightSegments?: number, openEnded?: boolean, thetaStart?: number, thetaLength?: number) =>\n made(viaCache({ kind: 'cylinder', ...defined({ radiusTop, radiusBottom, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength }) }, () => cylinder(radiusTop, radiusBottom, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength))),\n ConeGeometry: (radius?: number, height?: number, radialSegments?: number, heightSegments?: number, openEnded?: boolean, thetaStart?: number, thetaLength?: number) =>\n made(viaCache({ kind: 'cone', ...defined({ radius, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength }) }, () => cone(radius, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength))),\n SphereGeometry: (radius?: number, widthSegments?: number, heightSegments?: number, phiStart?: number, phiLength?: number, thetaStart?: number, thetaLength?: number) =>\n made(viaCache({ kind: 'sphere', ...defined({ radius, widthSegments, heightSegments, phiStart, phiLength, thetaStart, thetaLength }) }, () => sphere(radius, widthSegments, heightSegments, phiStart, phiLength, thetaStart, thetaLength))),\n PlaneGeometry: (width?: number, height?: number, widthSegments?: number, heightSegments?: number) =>\n made(viaCache({ kind: 'plane', ...defined({ width, height, widthSegments, heightSegments }) }, () => plane(width, height, widthSegments, heightSegments))),\n CircleGeometry: (radius?: number, segments?: number, thetaStart?: number, thetaLength?: number) =>\n made(viaCache({ kind: 'circle', ...defined({ radius, segments, thetaStart, thetaLength }) }, () => circle(radius, segments, thetaStart, thetaLength))),\n RingGeometry: (innerRadius?: number, outerRadius?: number, thetaSegments?: number, phiSegments?: number, thetaStart?: number, thetaLength?: number) =>\n made(viaCache({ kind: 'ring', ...defined({ innerRadius, outerRadius, thetaSegments, phiSegments, thetaStart, thetaLength }) }, () => ring(innerRadius, outerRadius, thetaSegments, phiSegments, thetaStart, thetaLength))),\n LatheGeometry: (points?: Array<{ x: number; y: number }>, segments?: number, phiStart?: number, phiLength?: number) => {\n if (!points) return new THREE.LatheGeometry(undefined, segments, phiStart, phiLength)\n const profile = points.map((p) => [p.x, p.y] as const)\n return made(viaCache({ kind: 'lathe', points: profile, ...defined({ segments, phiStart, phiLength }) }, () => lathe(profile, segments, phiStart, phiLength)))\n },\n TorusGeometry: (radius?: number, tube?: number, radialSegments?: number, tubularSegments?: number, arc?: number) =>\n made(viaCache({ kind: 'torus', ...defined({ radius, tube, radialSegments, tubularSegments, arc }) }, () => torus(radius, tube, radialSegments, tubularSegments, arc))),\n ExtrudeGeometry: (shapes?: unknown, extrudeOptions?: ExtrudeOptions) => {\n const buffers = shapes === undefined ? null : extrudeOf(shapes, extrudeOptions)\n if (!buffers) return new THREE.ExtrudeGeometry(shapes as THREE_NS.Shape, extrudeOptions as THREE_NS.ExtrudeGeometryOptions)\n const geometry = toGeometry(THREE, buffers) as THREE_NS.BufferGeometry & { parameters: unknown }\n geometry.parameters = { shapes, options: extrudeOptions ?? {} }\n return geometry\n },\n } satisfies Record<(typeof SHAPE_GEOMETRY_TYPES)[number], (...args: never[]) => THREE_NS.BufferGeometry>\n const namespace: Record<string, unknown> = { ...(THREE as unknown as Record<string, unknown>) }\n for (const type of SHAPE_GEOMETRY_TYPES) {\n // three hands the constructor's own arguments through; each replacement takes the same ones\n const make = constructors[type] as (...args: unknown[]) => THREE_NS.BufferGeometry\n // callable with `new` (the returned object wins), and `instanceof` checks three's prototype\n const Constructor = function (this: unknown, ...args: unknown[]) {\n return make(...args)\n } as unknown as { prototype: unknown }\n Constructor.prototype = (THREE as unknown as Record<string, { prototype: unknown }>)[type]!.prototype\n Object.defineProperty(Constructor, 'name', { value: type })\n namespace[type] = Constructor\n }\n const result = namespace as unknown as Three\n byKey.set(key, result)\n shims.add(result)\n return result\n}\n","import { colorHex } from '@adatechnology/game-shapes'\nimport type { MaterialHint, ShapeBuffers, ShapeLevel, ShapePart, ShapePoint } from '@adatechnology/game-shapes'\nimport type * as THREE_NS from 'three'\n\nimport { toGeometry } from './geometry'\nimport type { Three } from './geometry'\n\n/** A `MeshStandardMaterial` per material id from the schema's hints (vertex colours on unless the hint says otherwise). */\nexport function createMaterials(THREE: Three, hints: Readonly<Record<string, MaterialHint>> = {}, ids: Iterable<string> = []): Record<string, THREE_NS.Material> {\n const out: Record<string, THREE_NS.Material> = {}\n const all = new Set([...Object.keys(hints), ...ids])\n for (const id of all) {\n const hint = hints[id] ?? {}\n const parameters: THREE_NS.MeshStandardMaterialParameters = { vertexColors: hint.vertexColors ?? true }\n if (hint.color !== undefined) parameters.color = colorHex(hint.color)\n if (hint.roughness !== undefined) parameters.roughness = hint.roughness\n if (hint.metalness !== undefined) parameters.metalness = hint.metalness\n if (hint.opacity !== undefined) parameters.opacity = hint.opacity\n if (hint.transparent !== undefined) parameters.transparent = hint.transparent\n if (hint.emissive !== undefined) parameters.emissive = colorHex(hint.emissive)\n if (hint.emissiveIntensity !== undefined) parameters.emissiveIntensity = hint.emissiveIntensity\n if (hint.flatShading !== undefined) parameters.flatShading = hint.flatShading\n if (hint.depthWrite !== undefined) parameters.depthWrite = hint.depthWrite\n if (hint.side !== undefined) parameters.side = hint.side === 'double' ? THREE.DoubleSide : hint.side === 'back' ? THREE.BackSide : THREE.FrontSide\n const material = new THREE.MeshStandardMaterial(parameters)\n material.name = id\n out[id] = material\n }\n return out\n}\n\nexport interface ToObjectOptions {\n /** Materials by id; missing ids get one from the schema hints (`createMaterials`). */\n readonly materials?: Readonly<Record<string, THREE_NS.Material>>\n /** The material of one draw (wins over `materials`): per material id and part (`null` for the static body). */\n readonly materialFor?: (material: string, part: string | null) => THREE_NS.Material | undefined\n /** Cast and receive shadows (default true). */\n readonly shadows?: boolean\n /** Copy the arrays into the attributes (default true; false shares them, read-only). */\n readonly copy?: boolean\n /** Name of the root. */\n readonly name?: string\n}\n\nexport interface ShapeObject {\n /** A `Group`, or a `LOD` when the shape has levels or a cull distance. */\n readonly object: THREE_NS.Object3D\n /** The level 0 group (the root itself without LOD). */\n readonly near: THREE_NS.Group\n /** Part groups of level 0 by id, at their pivots; animate these. */\n readonly parts: Record<string, THREE_NS.Group>\n readonly meshes: THREE_NS.Mesh[]\n readonly instanced: THREE_NS.InstancedMesh[]\n readonly points: readonly ShapePoint[]\n readonly materials: Record<string, THREE_NS.Material>\n}\n\nfunction placePart(THREE: Three, group: THREE_NS.Group, part: ShapePart): void {\n if (part.matrix && part.position === undefined && part.rotation === undefined && part.scale === undefined && part.heading === undefined) {\n new THREE.Matrix4().fromArray(part.matrix as number[]).decompose(group.position, group.quaternion, group.scale)\n return\n }\n if (part.position) group.position.set(part.position[0], part.position[1], part.position[2])\n if (part.heading !== undefined) group.rotation.y = part.heading\n else if (part.rotation) group.rotation.set(part.rotation[0], part.rotation[1], part.rotation[2], part.rotationOrder ?? 'XYZ')\n if (part.scale !== undefined) {\n if (typeof part.scale === 'number') group.scale.setScalar(part.scale)\n else group.scale.set(part.scale[0], part.scale[1], part.scale[2])\n }\n group.updateMatrix()\n}\n\n/**\n * The three objects of a shape: one mesh per batch (`static:<material>` or `part:<id>`, `userData.material`, `userData.part`), part groups at their\n * pivots, an `InstancedMesh` per instanced batch, and a `LOD` (levels at their distances, nothing from `cullDistance`) when there are levels.\n */\nexport function toObject3D(THREE: Three, buffers: ShapeBuffers, options: ToObjectOptions = {}): ShapeObject {\n const shadows = options.shadows !== false\n const ids = new Set<string>()\n for (const level of buffers.levels) {\n for (const b of level.batches) ids.add(b.material)\n for (const b of level.instanced) ids.add(b.material)\n }\n const made = createMaterials(THREE, buffers.materials, [...ids].filter((id) => !options.materials?.[id]))\n const materials: Record<string, THREE_NS.Material> = { ...made, ...(options.materials ?? {}) }\n const materialOf = (id: string, part: string | null): THREE_NS.Material => options.materialFor?.(id, part) ?? (materials[id] as THREE_NS.Material)\n const meshes: THREE_NS.Mesh[] = []\n const instanced: THREE_NS.InstancedMesh[] = []\n\n const build = (level: ShapeLevel, withParts: boolean): { group: THREE_NS.Group; parts: Record<string, THREE_NS.Group> } => {\n const group = new THREE.Group()\n group.name = options.name ?? buffers.name ?? 'shape'\n const parts: Record<string, THREE_NS.Group> = {}\n if (withParts) {\n for (const part of buffers.parts) {\n const g = new THREE.Group()\n g.name = `part:${part.id}`\n g.userData.part = part.id\n placePart(THREE, g, part)\n parts[part.id] = g\n }\n }\n const into = (part: string | null): THREE_NS.Object3D => (part !== null && parts[part]) || group\n for (const batch of level.batches) {\n const mesh = new THREE.Mesh(toGeometry(THREE, batch, { copy: options.copy !== false }), materialOf(batch.material, batch.part))\n mesh.geometry.computeBoundingSphere()\n mesh.geometry.computeBoundingBox()\n mesh.name = batch.part === null ? `static:${batch.material}` : `part:${batch.part}`\n if (batch.part !== null) mesh.userData.part = batch.part\n mesh.userData.material = batch.material\n if (batch.ranges.length) mesh.userData.ranges = batch.ranges\n mesh.castShadow = shadows\n mesh.receiveShadow = shadows\n into(batch.part).add(mesh)\n meshes.push(mesh)\n }\n for (const batch of level.instanced) {\n const mesh = new THREE.InstancedMesh(toGeometry(THREE, batch.geometry, { copy: options.copy !== false }), materialOf(batch.material, batch.part), batch.count)\n mesh.instanceMatrix.array.set(batch.matrices)\n mesh.instanceMatrix.needsUpdate = true\n if (batch.colors) mesh.instanceColor = new THREE.InstancedBufferAttribute(batch.colors.slice(), 3)\n mesh.name = `instanced:${batch.name ?? batch.material}`\n mesh.userData.material = batch.material\n if (batch.part !== null) mesh.userData.part = batch.part\n mesh.castShadow = shadows\n mesh.receiveShadow = shadows\n mesh.computeBoundingSphere()\n into(batch.part).add(mesh)\n instanced.push(mesh)\n }\n for (const part of Object.values(parts)) group.add(part)\n return { group, parts }\n }\n\n const levels = buffers.levels\n const first = build(levels[0] ?? { distance: 0, detail: 1, batches: [], instanced: [] }, true)\n let object: THREE_NS.Object3D = first.group\n if (levels.length > 1 || buffers.cullDistance !== undefined) {\n const lod = new THREE.LOD()\n lod.name = first.group.name\n lod.addLevel(first.group, 0)\n for (const level of levels.slice(1)) lod.addLevel(build(level, false).group, level.distance)\n if (buffers.cullDistance !== undefined) lod.addLevel(new THREE.Group(), buffers.cullDistance)\n object = lod\n }\n object.userData.points = buffers.points\n return { object, near: first.group, parts: first.parts, meshes, instanced, points: buffers.points, materials }\n}\n","import { mergeTransformed } from '@adatechnology/game-shapes'\nimport type { TransformedPiece } from '@adatechnology/game-shapes'\nimport type * as THREE_NS from 'three'\n\nimport type { Three } from './geometry'\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 plain, non-interleaved, not normalised Float32 attribute: what the core merge reads directly. */\nconst plain = (attribute: THREE_NS.BufferAttribute | THREE_NS.InterleavedBufferAttribute | undefined): attribute is THREE_NS.BufferAttribute =>\n attribute !== undefined && !(attribute as THREE_NS.InterleavedBufferAttribute).isInterleavedBufferAttribute && attribute.array instanceof Float32Array && !attribute.normalized\n\n/** Arrays of a geometry the core merge can take as they are; for anything else, the arrays three's own `toNonIndexed` makes (then not indexed). */\nfunction piece(geometry: THREE_NS.BufferGeometry, matrix: THREE_NS.Matrix4): TransformedPiece {\n const position = geometry.getAttribute('position')\n const normal = geometry.getAttribute('normal')\n const uv = geometry.getAttribute('uv')\n const ok = plain(position) && (normal === undefined || plain(normal)) && (uv === undefined || plain(uv)) && position.itemSize === 3 && (uv === undefined || uv.itemSize === 2)\n if (ok) {\n return { positions: position.array, ...(normal ? { normals: normal.array } : {}), ...(uv ? { uvs: uv.array } : {}), indices: geometry.index ? geometry.index.array : null, matrix: matrix.elements }\n }\n const flat = geometry.index ? geometry.toNonIndexed() : geometry\n const read = (name: string, size: number): Float32Array | undefined => {\n const attribute = flat.getAttribute(name)\n if (!attribute) return undefined\n const out = new Float32Array(attribute.count * size)\n for (let i = 0; i < attribute.count; i++) for (let c = 0; c < size; c++) out[i * size + c] = c === 0 ? attribute.getX(i) : c === 1 ? attribute.getY(i) : attribute.getZ(i)\n return out\n }\n const positions = read('position', 3) as Float32Array\n const normals = read('normal', 3)\n const uvs = read('uv', 2)\n if (flat !== geometry) flat.dispose()\n return { positions, ...(normals ? { normals } : {}), ...(uvs ? { uvs } : {}), indices: null, matrix: matrix.elements }\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 non-indexed\n * mesh per material (position, normal, uv), no shadow cast. The bake is game-shapes' `mergeTransformed`, bit-identical to three's\n * `toNonIndexed` + `applyMatrix4` + join, so the result is the one `game-props3d`'s `mergeByMaterial` always gave.\n */\nexport function mergeByMaterial(THREE: Three, 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, TransformedPiece[]>()\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(piece(mesh.geometry, new THREE.Matrix4().multiplyMatrices(toSpace, mesh.matrixWorld)))\n byMaterial.set(mesh.material, list)\n })\n return [...byMaterial].map(([material, pieces]) => {\n const merged = mergeTransformed(pieces)\n const geometry = new THREE.BufferGeometry()\n geometry.setAttribute('position', new THREE.BufferAttribute(merged.positions, 3))\n geometry.setAttribute('normal', new THREE.BufferAttribute(merged.normals, 3))\n geometry.setAttribute('uv', new THREE.BufferAttribute(merged.uvs, 2))\n const mesh = new THREE.Mesh(geometry, material)\n mesh.castShadow = false\n mesh.receiveShadow = true\n return mesh\n })\n}\n","import type * as ThreeNS from 'three'\n\nexport type Vec3 = [number, number, number]\ntype ThreeLib = typeof ThreeNS\n\n/**\n * Points of a hanging cord between `a` and `b` (n points including both ends, n >= 2). The deepest point sits `sag` meters below\n * the straight line between the ends, on a real catenary (y = a cosh(x/a)); when the ends differ in height the curve is\n * measured against the straight line between them.\n */\nexport function catenary(a: Vec3, b: Vec3, sag: number, n = 12): Vec3[] {\n const count = Math.max(2, Math.floor(n))\n const span = Math.hypot(b[0] - a[0], b[2] - a[2]) || Math.hypot(b[0] - a[0], b[1] - a[1], b[2] - a[2])\n const depth = Math.max(0, sag)\n const out: Vec3[] = []\n // Solve depth = c (cosh(span / 2c) - 1) for c by bisection (depth grows as c shrinks).\n let shape: (s: number) => number = () => 0\n if (depth > 1e-6 && span > 1e-6) {\n let lo = 1e-4\n let hi = span * 1000\n for (let i = 0; i < 80; i++) {\n const c = (lo + hi) / 2\n const d = c * (Math.cosh(span / (2 * c)) - 1)\n if (d > depth) lo = c\n else hi = c\n }\n const c = (lo + hi) / 2\n shape = (s) => c * (Math.cosh(((s - 0.5) * span) / c) - Math.cosh(span / (2 * c)))\n }\n for (let i = 0; i < count; i++) {\n const s = i / (count - 1)\n out.push([a[0] + (b[0] - a[0]) * s, a[1] + (b[1] - a[1]) * s + shape(s), a[2] + (b[2] - a[2]) * s])\n }\n return out\n}\n\n/** A thin tube through `points` (Catmull-Rom smoothed) for wires, cords and conduits. */\nexport function createCord(THREE: ThreeLib, points: readonly Vec3[], radius: number, material: ThreeNS.Material, radialSegments = 5): ThreeNS.Mesh {\n if (points.length < 2) throw new Error('createCord needs at least two points')\n const curve = new THREE.CatmullRomCurve3(points.map((p) => new THREE.Vector3(p[0], p[1], p[2])), false, 'centripetal')\n const segments = Math.max(4, (points.length - 1) * 4)\n const mesh = new THREE.Mesh(new THREE.TubeGeometry(curve, segments, radius, radialSegments, false), material)\n mesh.name = 'cord'\n mesh.castShadow = true\n return mesh\n}\n","import type * as THREE_NS from 'three'\n\n/** What `releaseObject3D` freed. */\nexport interface ReleaseReport {\n geometries: number\n materials: number\n textures: number\n}\n\nexport interface ReleaseOptions {\n /**\n * Trees that stay drawn (usually the scene): a geometry, material or texture any of them still uses is kept, so a resource shared by a clone that is\n * still on screen (a catalogue model, a material of the kit's palette) is not freed under it.\n */\n keep?: THREE_NS.Object3D | readonly THREE_NS.Object3D[]\n}\n\ntype Disposable = { dispose(): void; userData?: Record<string, unknown> }\ntype Owner = {\n geometry?: Disposable\n material?: THREE_NS.Material | THREE_NS.Material[]\n skeleton?: { dispose(): void }\n traverse(fn: (o: THREE_NS.Object3D) => void): void\n}\n\nconst isTexture = (v: unknown): v is THREE_NS.Texture => !!v && typeof v === 'object' && (v as { isTexture?: boolean }).isTexture === true\n\nfunction texturesOf(material: THREE_NS.Material, out: Set<THREE_NS.Texture>): void {\n for (const value of Object.values(material)) if (isTexture(value)) out.add(value)\n const uniforms = (material as { uniforms?: Record<string, { value?: unknown }> }).uniforms\n if (uniforms) for (const u of Object.values(uniforms)) if (isTexture(u?.value)) out.add(u.value)\n}\n\nfunction collect(roots: readonly THREE_NS.Object3D[]): { geometries: Set<Disposable>; materials: Set<THREE_NS.Material>; textures: Set<THREE_NS.Texture>; skeletons: Set<{ dispose(): void }> } {\n const geometries = new Set<Disposable>()\n const materials = new Set<THREE_NS.Material>()\n const textures = new Set<THREE_NS.Texture>()\n const skeletons = new Set<{ dispose(): void }>()\n for (const root of roots) {\n root.traverse((object) => {\n const o = object as unknown as Owner\n if (o.geometry && typeof o.geometry.dispose === 'function') geometries.add(o.geometry)\n if (o.skeleton && typeof o.skeleton.dispose === 'function') skeletons.add(o.skeleton)\n const list = Array.isArray(o.material) ? o.material : o.material ? [o.material] : []\n for (const m of list) {\n if (!m || typeof m.dispose !== 'function') continue\n materials.add(m)\n texturesOf(m, textures)\n }\n })\n }\n return { geometries, materials, textures, skeletons }\n}\n\nconst marked = new WeakSet<object>()\n/**\n * Marks geometries, materials or textures as shared on purpose (a palette, a model kit, a catalog): `releaseObject3D` and `discardObject3D` never free\n * them. Same as `userData.shared = true`, without touching the resource (its bytes and userData stay as they were, for digests and goldens).\n */\nexport function markShared<T extends object>(...resources: T[]): T[] {\n for (const resource of resources) marked.add(resource)\n return resources\n}\nconst shared = (r: { userData?: Record<string, unknown> }): boolean => r.userData?.shared === true || marked.has(r)\n\n/**\n * Free the GPU side of trees taken out of the scene: their geometries, materials, textures (maps and shader uniforms) and skeletons. three.js keeps a removed\n * mesh's buffers, programs and textures on the GPU (and the JS objects in its caches) until `dispose()`, so every object a game removes without this stays\n * in memory for good. A resource marked `userData.shared = true`, or still used by a tree in `keep`, is left alone. Disposing a resource that is used again\n * later is safe: three.js uploads it again on the next draw.\n */\nexport function releaseObject3D(roots: THREE_NS.Object3D | readonly THREE_NS.Object3D[], options: ReleaseOptions = {}): ReleaseReport {\n const list = (Array.isArray(roots) ? roots : [roots]) as THREE_NS.Object3D[]\n const report: ReleaseReport = { geometries: 0, materials: 0, textures: 0 }\n if (list.length === 0) return report\n const found = collect(list)\n const keepRoots = options.keep === undefined ? [] : ((Array.isArray(options.keep) ? options.keep : [options.keep]) as THREE_NS.Object3D[])\n // a released tree still hanging under a kept one is not \"kept\": skip it while walking the kept trees\n const releasing = new Set<THREE_NS.Object3D>(list)\n for (const keep of keepRoots) {\n const walk = (o: THREE_NS.Object3D): void => {\n if (releasing.has(o)) return\n const owner = o as unknown as Owner\n if (owner.geometry) found.geometries.delete(owner.geometry)\n if (owner.skeleton) found.skeletons.delete(owner.skeleton)\n const mats = Array.isArray(owner.material) ? owner.material : owner.material ? [owner.material] : []\n for (const m of mats) {\n if (!m) continue\n found.materials.delete(m)\n const t = new Set<THREE_NS.Texture>()\n texturesOf(m, t)\n for (const x of t) found.textures.delete(x)\n }\n for (const c of o.children) walk(c)\n }\n walk(keep)\n }\n for (const g of found.geometries) {\n if (shared(g)) continue\n g.dispose()\n report.geometries++\n }\n for (const m of found.materials) {\n if (shared(m as unknown as Disposable)) continue\n m.dispose()\n report.materials++\n }\n for (const t of found.textures) {\n if (shared(t as unknown as Disposable)) continue\n t.dispose()\n report.textures++\n }\n for (const s of found.skeletons) s.dispose()\n return report\n}\n\n/**\n * Take `objects` out of their trees and free what only they used (`releaseObject3D`, keeping whatever the rest of those trees, usually the scene,\n * still draws). The one call for pieces, fragments, helpers or bodies that leave the game for good. A list costs one walk of each scene, so pass\n * the whole list instead of calling it once per object.\n */\nexport function discardObject3D(objects: THREE_NS.Object3D | readonly THREE_NS.Object3D[], options: ReleaseOptions = {}): ReleaseReport {\n const list = (Array.isArray(objects) ? objects : [objects]) as THREE_NS.Object3D[]\n const keep = options.keep === undefined ? [] : Array.isArray(options.keep) ? [...(options.keep as THREE_NS.Object3D[])] : [options.keep as THREE_NS.Object3D]\n for (const object of list) {\n let top = object\n while (top.parent) top = top.parent\n object.removeFromParent()\n if (top !== object && !keep.includes(top)) keep.push(top)\n }\n return releaseObject3D(list, { keep })\n}\n","import type { Group, Object3D, PointLight, SpotLight } from 'three'\n\nimport type { LightKind, LightPatch, LinearRgb, ThreeForLights } from './types'\n\nconst PARKED_Y = -50\nconst DEFAULT_DECAY = 2\nconst DEFAULT_ANGLE = Math.PI / 3\nconst POSITION_EPSILON = 1e-6\n\nexport type Vec3 = [number, number, number]\n\nexport type LightValues = {\n color: number | LinearRgb\n intensity: number\n distance: number\n decay: number\n position: Vec3\n target: Vec3 | undefined\n angle: number\n penumbra: number\n}\n\n/** Whoever owns a slot: a request, a source or a lease. */\nexport interface Holder {\n readonly kind: 'request' | 'source' | 'lease'\n readonly owner: string\n readonly lightKind: LightKind\n priority: number\n urgent: boolean\n values: LightValues\n slot: Slot | null\n alive: boolean\n onLost: (() => void) | undefined\n /** Sources only. */\n follow: Object3D | undefined\n shadow: boolean\n wanted: boolean\n distance: number\n /** Sources only: `values` with the world position and direction of `follow` of this frame. */\n resolved: LightValues\n}\n\nexport interface Slot {\n readonly index: number\n readonly kind: LightKind\n readonly shadow: boolean\n readonly light: PointLight | SpotLight\n holder: Holder | null\n pending: Holder | null\n leased: boolean\n /** 0..1 multiplier on the holder's intensity. */\n fade: number\n fadeTarget: number\n fadeOutMs: number\n baseIntensity: number\n lastPosition: Vec3\n}\n\nexport function createValues(base: Partial<LightValues> & Pick<LightValues, 'color' | 'intensity' | 'distance' | 'position'>): LightValues {\n return {\n decay: DEFAULT_DECAY,\n target: undefined,\n angle: DEFAULT_ANGLE,\n penumbra: 0,\n ...base,\n position: [...base.position],\n }\n}\n\nexport function mergeValues(values: LightValues, patch: LightPatch): void {\n if (patch.color !== undefined) values.color = patch.color\n if (patch.intensity !== undefined) values.intensity = patch.intensity\n if (patch.distance !== undefined) values.distance = patch.distance\n if (patch.decay !== undefined) values.decay = patch.decay\n if (patch.position !== undefined) values.position = [...patch.position]\n if (patch.target !== undefined) values.target = [...patch.target]\n if (patch.angle !== undefined) values.angle = patch.angle\n if (patch.penumbra !== undefined) values.penumbra = patch.penumbra\n}\n\nexport type ShadowSettings = { readonly bias?: number; readonly normalBias?: number; readonly near?: number }\n\nexport function createSlots(THREE: ThreeForLights, group: Group, kind: LightKind, count: number, shadows: number, mapSize: number, settings: ShadowSettings = {}): Slot[] {\n const slots: Slot[] = []\n for (let index = 0; index < count; index++) {\n const light = kind === 'point' ? new THREE.PointLight(0xffffff, 0, 0, DEFAULT_DECAY) : new THREE.SpotLight(0xffffff, 0, 0, DEFAULT_ANGLE, 0, DEFAULT_DECAY)\n light.position.set(0, PARKED_Y, 0)\n light.layers.enableAll()\n const shadow = index < shadows\n if (shadow) {\n light.castShadow = true\n light.shadow.autoUpdate = false\n light.shadow.mapSize.set(mapSize, mapSize)\n if (settings.bias !== undefined) light.shadow.bias = settings.bias\n if (settings.normalBias !== undefined) light.shadow.normalBias = settings.normalBias\n if (settings.near !== undefined) light.shadow.camera.near = settings.near\n }\n group.add(light)\n if ('target' in light) group.add(light.target)\n slots.push({ index, kind, shadow, light, holder: null, pending: null, leased: false, fade: 0, fadeTarget: 0, fadeOutMs: 0, baseIntensity: 0, lastPosition: [0, PARKED_Y, 0] })\n }\n return slots\n}\n\nexport function isFree(slot: Slot): boolean {\n return !slot.leased && slot.holder === null && slot.pending === null\n}\n\nexport function applyIntensity(slot: Slot): void {\n slot.light.intensity = slot.baseIntensity * slot.fade\n}\n\nexport function markShadow(slot: Slot): void {\n if (slot.shadow) slot.light.shadow.needsUpdate = true\n}\n\nexport function writeLight(THREE: ThreeForLights, slot: Slot, values: LightValues): void {\n const { light } = slot\n if (typeof values.color === 'number') light.color.set(values.color)\n else light.color.setRGB(values.color[0], values.color[1], values.color[2], THREE.LinearSRGBColorSpace)\n light.distance = values.distance\n light.decay = values.decay\n const [x, y, z] = values.position\n light.position.set(x, y, z)\n if ('angle' in light) {\n light.angle = values.angle\n light.penumbra = values.penumbra\n const [tx, ty, tz] = values.target ?? [x, y - 1, z]\n light.target.position.set(tx, ty, tz)\n }\n const last = slot.lastPosition\n if (slot.shadow && (Math.abs(last[0] - x) > POSITION_EPSILON || Math.abs(last[1] - y) > POSITION_EPSILON || Math.abs(last[2] - z) > POSITION_EPSILON)) markShadow(slot)\n slot.lastPosition = [x, y, z]\n slot.baseIntensity = values.intensity\n applyIntensity(slot)\n}\n\n/** The holder's writes apply from now on; its light fades in (requests that cut a victim start at full). */\nexport function install(THREE: ThreeForLights, slot: Slot, holder: Holder, startAtFull: boolean): void {\n slot.holder = holder\n slot.pending = null\n slot.fade = startAtFull ? 1 : 0\n slot.fadeTarget = 1\n holder.slot = slot\n markShadow(slot)\n writeLight(THREE, slot, holder.values)\n}\n\n/** Drops the holder: the light keeps its last values and fades to 0 (at once with `fadeOutMs` 0). */\nexport function vacate(slot: Slot, fadeOutMs: number): void {\n slot.holder = null\n slot.fadeTarget = 0\n slot.fadeOutMs = fadeOutMs\n if (fadeOutMs <= 0) {\n slot.fade = 0\n applyIntensity(slot)\n }\n}\n\nexport function stepFade(slot: Slot, dtSeconds: number, fadeInMs: number): void {\n if (slot.fade < slot.fadeTarget) {\n slot.fade = fadeInMs <= 0 ? slot.fadeTarget : Math.min(slot.fadeTarget, slot.fade + (dtSeconds * 1000) / fadeInMs)\n } else if (slot.fade > slot.fadeTarget) {\n slot.fade = slot.fadeOutMs <= 0 ? slot.fadeTarget : Math.max(slot.fadeTarget, slot.fade - (dtSeconds * 1000) / slot.fadeOutMs)\n }\n applyIntensity(slot)\n}\n","import { type Holder, type Slot, install, isFree, stepFade, vacate } from './slots'\nimport type { LightKind, ThreeForLights } from './types'\n\nexport type ArbiterCore = {\n readonly THREE: ThreeForLights\n readonly slots: Record<LightKind, readonly Slot[]>\n readonly urgentReserve: number\n readonly fadeMs: number\n readonly hysteresis: number\n preempted: number\n refused: number\n}\n\n/** The slots requests and sources compete for: not leased, and shadow slots only on their own pass. */\nexport function poolOf(core: ArbiterCore, kind: LightKind, shadow: boolean): Slot[] {\n return core.slots[kind].filter((slot) => !slot.leased && slot.shadow === shadow)\n}\n\nfunction contribution(holder: Holder): number {\n return holder.values.intensity / Math.max(1, holder.distance) ** 2\n}\n\n/** Lowest priority first; on a tie, the smallest perceived contribution. */\nfunction isWeaker(a: Holder, b: Holder): boolean {\n if (a.priority !== b.priority) return a.priority < b.priority\n return contribution(a) < contribution(b)\n}\n\nfunction pickFree(slots: readonly Slot[]): Slot | undefined {\n let best: Slot | undefined\n for (const slot of slots) if (isFree(slot) && (best === undefined || slot.fade < best.fade)) best = slot\n return best\n}\n\n/** Gives a free slot to the holder: at once when its light is dark or the holder is urgent, else after the slot finishes fading out. */\nfunction place(core: ArbiterCore, slot: Slot, holder: Holder): void {\n holder.slot = slot\n if (slot.fade > 0 && !holder.urgent) {\n slot.pending = holder\n slot.fadeTarget = 0\n return\n }\n install(core.THREE, slot, holder, holder.kind === 'request')\n}\n\n/** Takes the slot from its holder; a request is told and dies, a source goes back to the queue. */\nfunction evict(core: ArbiterCore, slot: Slot, challenger: Holder, cut: boolean): void {\n const victim = slot.holder\n if (victim === null) return\n victim.slot = null\n if (victim.kind === 'request') {\n victim.alive = false\n victim.onLost?.()\n }\n core.preempted++\n vacate(slot, cut ? 0 : core.fadeMs)\n if (cut) {\n install(core.THREE, slot, challenger, true)\n return\n }\n slot.pending = challenger\n challenger.slot = slot\n}\n\nfunction weakestHolder(slots: readonly Slot[]): Slot | undefined {\n let weakest: Slot | undefined\n for (const slot of slots) {\n if (slot.holder === null || slot.pending !== null) continue\n if (weakest === undefined || isWeaker(slot.holder, weakest.holder as Holder)) weakest = slot\n }\n return weakest\n}\n\n/** Slots free to a holder that has no right to the urgent reserve. */\nfunction freeBeyondReserve(core: ArbiterCore, pool: readonly Slot[], reserve: number): Slot | undefined {\n const free = pool.filter(isFree)\n return free.length > reserve ? pickFree(pool) : undefined\n}\n\nexport function admitRequest(core: ArbiterCore, holder: Holder): boolean {\n const pool = poolOf(core, holder.lightKind, false)\n const reserve = holder.lightKind === 'point' ? core.urgentReserve : 0\n const free = holder.urgent ? pickFree(pool) : freeBeyondReserve(core, pool, reserve)\n if (free !== undefined) {\n place(core, free, holder)\n return true\n }\n const victim = weakestHolder(pool)\n if (victim === undefined || (victim.holder as Holder).priority >= holder.priority) {\n core.refused++\n return false\n }\n evict(core, victim, holder, holder.urgent)\n return true\n}\n\nfunction beats(challenger: Holder, incumbent: Holder, hysteresis: number): boolean {\n if (challenger.priority !== incumbent.priority) return challenger.priority > incumbent.priority\n return challenger.distance < incumbent.distance * (1 - hysteresis)\n}\n\nfunction bestFirst(a: Holder, b: Holder): number {\n return b.priority - a.priority || a.distance - b.distance\n}\n\n/** Hands the pool's slots to the wanted sources: best first, an incumbent only yields to a clearly better challenger. */\nexport function arbitrateSources(core: ArbiterCore, pool: readonly Slot[], sources: readonly Holder[], reserve: number): void {\n for (const slot of pool) {\n const holder = slot.holder\n if (slot.pending?.kind === 'source' && !slot.pending.wanted) {\n slot.pending.slot = null\n slot.pending = null\n }\n if (holder?.kind === 'source' && !holder.wanted) {\n holder.slot = null\n vacate(slot, core.fadeMs)\n }\n }\n const challengers = sources.filter((source) => source.wanted && source.slot === null).sort(bestFirst)\n for (const challenger of challengers) {\n const free = freeBeyondReserve(core, pool, reserve)\n if (free !== undefined) {\n place(core, free, challenger)\n continue\n }\n const incumbentSlots = pool.filter((slot) => slot.holder?.kind === 'source')\n let worst: Slot | undefined\n for (const slot of incumbentSlots) {\n if (slot.pending !== null) continue\n if (worst === undefined || bestFirst(slot.holder as Holder, worst.holder as Holder) > 0) worst = slot\n }\n if (worst === undefined || !beats(challenger, worst.holder as Holder, core.hysteresis)) break\n evict(core, worst, challenger, false)\n }\n}\n\nexport function stepSlots(core: ArbiterCore, dtSeconds: number): void {\n for (const kind of ['point', 'spot'] as const) {\n for (const slot of core.slots[kind]) {\n stepFade(slot, dtSeconds, core.fadeMs)\n if (slot.pending !== null && slot.fade <= 0) install(core.THREE, slot, slot.pending, false)\n }\n }\n}\n","import type { Fog, FogBase, FogExp2, Scene } from 'three'\n\nimport type { ThreeLib } from './three-lib'\n\n/** Both exact in half precision (multiples of 32 under 65 504), so the neutral fog survives a `mediump` fragment shader. */\nexport const NEUTRAL_FOG_NEAR = 6.0e4\nexport const NEUTRAL_FOG_FAR = 6.4e4\n\nexport interface FogOptions {\n /** `clear()` leaves a neutral linear Fog instead of `null`: the fog presence and type never change, so no program recompiles. */\n readonly stable?: boolean\n /** Colour of the neutral fog. Default 0x000000. */\n readonly color?: number\n}\n\nexport interface FogController {\n useLinear(color: number, near: number, far: number): Fog\n useExp2(color: number, density: number): FogExp2\n /** Back to no fog: the original fog (or `null`); when `stable`, the host's own linear fog or the neutral one, never `null`. */\n clear(): void\n}\n\nfunction isLinearFog(fog: FogBase | null): fog is Fog {\n return (fog as Partial<Fog> | null)?.isFog === true\n}\n\nfunction isExp2Fog(fog: FogBase | null): fog is FogExp2 {\n return (fog as Partial<FogExp2> | null)?.isFogExp2 === true\n}\n\nexport function isNeutralFog(fog: unknown): boolean {\n const candidate = fog as Partial<Fog> | null | undefined\n return candidate?.isFog === true && candidate.near === NEUTRAL_FOG_NEAR && candidate.far === NEUTRAL_FOG_FAR\n}\n\n/** Writes into the scene's existing fog object when its type matches, as the weather layer does, so nothing is reallocated per change. */\nexport function createFog(THREE: Pick<ThreeLib, 'Fog' | 'FogExp2'>, scene: Scene, options: FogOptions = {}): FogController {\n const original = scene.fog\n const hostLinear = isLinearFog(original) ? { color: original.color.getHex(), near: original.near, far: original.far } : undefined\n const resting = hostLinear ?? { color: options.color ?? 0, near: NEUTRAL_FOG_NEAR, far: NEUTRAL_FOG_FAR }\n return {\n useLinear(color, near, far) {\n const current = scene.fog\n if (isLinearFog(current)) {\n current.color.set(color)\n current.near = near\n current.far = far\n return current\n }\n const fog = new THREE.Fog(color, near, far)\n scene.fog = fog\n return fog\n },\n useExp2(color, density) {\n const current = scene.fog\n if (isExp2Fog(current)) {\n current.color.set(color)\n current.density = density\n return current\n }\n const fog = new THREE.FogExp2(color, density)\n scene.fog = fog\n return fog\n },\n clear() {\n if (options.stable !== true) {\n scene.fog = original\n return\n }\n const current = scene.fog\n if (isLinearFog(current)) {\n current.color.set(resting.color)\n current.near = resting.near\n current.far = resting.far\n return\n }\n scene.fog = new THREE.Fog(resting.color, resting.near, resting.far)\n },\n }\n}\n","import type { Object3D, Scene } from 'three'\n\nimport { NEUTRAL_FOG_NEAR } from '../fog'\nimport type { FogState, LightCheck, RendererState, SealOptions } from './types'\n\ntype Flags = Record<string, unknown>\ntype Counter = { count: number; shadows: number }\n\nconst LIGHT_FLAGS = [\n ['point', 'isPointLight'],\n ['spot', 'isSpotLight'],\n ['hemi', 'isHemisphereLight'],\n ['dir', 'isDirectionalLight'],\n ['ambient', 'isAmbientLight'],\n ['rect', 'isRectAreaLight'],\n] as const\n\n/** The visible lights the way the renderer collects them: they decide the shader's light counts. */\nexport function lightSignature(scene: Object3D): string {\n const counters: Record<string, Counter> = {}\n for (const [name] of LIGHT_FLAGS) counters[name] = { count: 0, shadows: 0 }\n scene.traverseVisible((object) => {\n const flags = object as unknown as Flags\n for (const [name, flag] of LIGHT_FLAGS) {\n if (flags[flag] !== true) continue\n const counter = counters[name] as Counter\n counter.count++\n if ((flags as { castShadow?: boolean }).castShadow === true) counter.shadows++\n }\n })\n return LIGHT_FLAGS.map(([name]) => `${name}:${(counters[name] as Counter).count}/${(counters[name] as Counter).shadows}`).join(' ')\n}\n\nexport function fogStateOf(scene: Scene): FogState {\n const fog = scene.fog as Flags | null\n if (fog === null) return 'none'\n return fog.isFogExp2 === true ? 'exp2' : 'linear'\n}\n\n/** Every PointLight/SpotLight under `scene` that is not under `group`, as a path of child indices. */\nexport function findStrays(scene: Object3D, group: Object3D): string[] {\n const strays: string[] = []\n const visit = (object: Object3D, path: string): void => {\n if (object === group) return\n const flags = object as unknown as Flags\n if (flags.isPointLight === true || flags.isSpotLight === true) strays.push(`${path}${object.name === '' ? object.type : object.name}`)\n object.children.forEach((child, index) => visit(child, `${path}${index}/`))\n }\n visit(scene, '')\n return strays\n}\n\nexport type Sealed = {\n readonly lights: string\n readonly environment: string\n readonly toneMapping: number\n readonly outputColorSpace: string\n readonly targetColorSpace: string\n readonly shadowMapType: number\n readonly fogStates: readonly FogState[]\n readonly cameras: SealOptions['cameras']\n readonly target: SealOptions['target']\n}\n\nfunction environmentOf(scene: Scene): string {\n return scene.environment === null ? 'none' : `texture:${scene.environment.mapping}`\n}\n\nfunction targetColorSpaceOf(target: SealOptions['target']): string {\n return target ? target.texture.colorSpace : 'canvas'\n}\n\nexport function captureSealed(renderer: RendererState, scene: Scene, options: SealOptions): Sealed {\n return {\n lights: lightSignature(scene),\n environment: environmentOf(scene),\n toneMapping: renderer.toneMapping,\n outputColorSpace: renderer.outputColorSpace,\n targetColorSpace: targetColorSpaceOf(options.target),\n shadowMapType: renderer.shadowMap.type,\n fogStates: options.fogStates ?? [fogStateOf(scene)],\n cameras: options.cameras,\n target: options.target,\n }\n}\n\ntype Described = {\n readonly lights: string\n readonly fog: string\n readonly environment: string\n readonly toneMapping: number\n readonly outputColorSpace: string\n readonly targetColorSpace: string\n readonly shadowMapType: number\n}\n\nfunction describe(state: Described): string {\n const { lights, fog, environment, toneMapping, outputColorSpace, targetColorSpace, shadowMapType } = state\n return `${lights} | fog:${fog} env:${environment} tone:${toneMapping} out:${outputColorSpace}/${targetColorSpace} shadow:${shadowMapType}`\n}\n\nfunction farBeyondNeutralFog(scene: Scene, sealed: Sealed): boolean {\n const fog = scene.fog as { isFog?: boolean; near?: number } | null\n if (fog?.isFog !== true || fog.near !== NEUTRAL_FOG_NEAR) return false\n return (sealed.cameras ?? []).some((camera) => ((camera as unknown as { far?: number }).far ?? 0) >= NEUTRAL_FOG_NEAR)\n}\n\nexport function checkSealed(sealed: Sealed | null, renderer: RendererState, scene: Scene, group: Object3D): LightCheck {\n const strays = findStrays(scene, group)\n const lights = lightSignature(scene)\n if (sealed === null) return { ok: strays.length === 0, expected: lights, actual: lights, strays }\n const fog = fogStateOf(scene)\n const fogAllowed = sealed.fogStates.includes(fog)\n const expected = describe({ ...sealed, fog: fogAllowed ? fog : sealed.fogStates.join('|') })\n const actual = describe({\n lights,\n fog,\n environment: environmentOf(scene),\n toneMapping: renderer.toneMapping,\n outputColorSpace: renderer.outputColorSpace,\n targetColorSpace: targetColorSpaceOf(sealed.target),\n shadowMapType: renderer.shadowMap.type,\n })\n const ok = strays.length === 0 && expected === actual && !farBeyondNeutralFog(scene, sealed)\n return { ok, expected, actual, strays }\n}\n","import type { HemisphereLight, Object3D, Scene } from 'three'\n\nimport { type ArbiterCore, admitRequest, arbitrateSources, poolOf, stepSlots } from './arbiter'\nimport { type Sealed, captureSealed, checkSealed } from './scene-state'\nimport { type Holder, type Slot, type Vec3, createSlots, createValues, isFree, markShadow, mergeValues, vacate, writeLight } from './slots'\nimport type {\n LightBudget,\n LightBudgetOptions,\n LightHandle,\n LightKind,\n LightLease,\n LightPatch,\n LightRequest,\n LightSource,\n RendererState,\n SourceHandle,\n ThreeForLights,\n} from './types'\n\nconst DEFAULTS = { point: 16, spot: 4, pointShadows: 2, spotShadows: 0, shadowMapSize: 512, urgentReserve: 2, fadeMs: 120, hysteresis: 0.15, hemisphere: 0 }\nconst ZERO: Vec3 = [0, 0, 0]\nconst ALL_LAYERS_MASK = -1\n\nfunction newHolder(kind: Holder['kind'], lightKind: LightKind, request: LightRequest | LightSource, urgent: boolean): Holder {\n return {\n kind,\n owner: request.owner,\n lightKind,\n priority: request.priority,\n urgent,\n values: createValues({ color: request.color, intensity: request.intensity, distance: request.distance, position: [...request.position], ...optional(request) }),\n slot: null,\n alive: true,\n onLost: undefined,\n follow: undefined,\n shadow: false,\n wanted: true,\n distance: 0,\n resolved: createValues({ color: request.color, intensity: 0, distance: 0, position: ZERO }),\n }\n}\n\nfunction optional(request: LightRequest | LightSource): Partial<Holder['values']> {\n const values: Partial<Holder['values']> = {}\n if (request.decay !== undefined) values.decay = request.decay\n if (request.target !== undefined) values.target = [...request.target]\n if (request.angle !== undefined) values.angle = request.angle\n if (request.penumbra !== undefined) values.penumbra = request.penumbra\n return values\n}\n\nfunction isAttached(object: Object3D, root: Object3D): boolean {\n for (let node: Object3D | null = object; node !== null; node = node.parent) if (node === root) return true\n return false\n}\n\nexport function createLightBudget(THREE: ThreeForLights, scene: Object3D, options: LightBudgetOptions = {}): LightBudget {\n const opts = { ...DEFAULTS, ...options }\n const pointShadows = Math.min(opts.pointShadows, opts.point)\n const spotShadows = Math.min(opts.spotShadows, opts.spot)\n const group = new THREE.Group()\n group.name = 'light-budget'\n const core: ArbiterCore = {\n THREE,\n slots: {\n point: createSlots(THREE, group, 'point', opts.point, pointShadows, opts.shadowMapSize, options.shadowSettings),\n spot: createSlots(THREE, group, 'spot', opts.spot, spotShadows, opts.shadowMapSize, options.shadowSettings),\n },\n urgentReserve: opts.urgentReserve,\n fadeMs: opts.fadeMs,\n hysteresis: opts.hysteresis,\n preempted: 0,\n refused: 0,\n }\n const hemispheres: HemisphereLight[] = []\n for (let index = 0; index < opts.hemisphere; index++) {\n const light = new THREE.HemisphereLight(0xffffff, 0xffffff, 0)\n light.layers.enableAll()\n group.add(light)\n hemispheres.push(light)\n }\n scene.add(group)\n scene.children.splice(scene.children.indexOf(group), 1)\n scene.children.unshift(group)\n\n const sources = new Set<Holder>()\n const world = new THREE.Vector3()\n const direction = new THREE.Vector3()\n let sealed: Sealed | null = null\n let leasedHemispheres = 0\n\n const allSlots = (): Slot[] => [...core.slots.point, ...core.slots.spot]\n\n function writeHolder(holder: Holder): void {\n if (holder.slot !== null && holder.slot.holder === holder) writeLight(THREE, holder.slot, holder.values)\n }\n\n function makeHandle(holder: Holder, release: ((fadeMs: number | undefined) => void) | null): LightHandle {\n return {\n get slot() {\n return holder.slot?.index ?? -1\n },\n kind: holder.lightKind,\n get alive() {\n return holder.alive\n },\n update(patch: LightPatch) {\n if (!holder.alive) return\n mergeValues(holder.values, patch)\n writeHolder(holder)\n },\n release(releaseOptions) {\n if (!holder.alive || release === null) return\n holder.alive = false\n release(releaseOptions?.fadeMs)\n },\n }\n }\n\n function dropHolder(holder: Holder, fadeMs: number): void {\n const slot = holder.slot\n holder.slot = null\n if (slot === null) return\n if (slot.pending === holder) slot.pending = null\n else if (slot.holder === holder) vacate(slot, fadeMs)\n }\n\n function resolveSource(holder: Holder, focus: readonly [number, number, number]): void {\n const { follow, values, resolved } = holder\n holder.wanted = values.intensity > 0\n if (follow === undefined) {\n holder.distance = Math.hypot(values.position[0] - focus[0], values.position[1] - focus[1], values.position[2] - focus[2])\n Object.assign(resolved, values)\n return\n }\n holder.wanted = holder.wanted && isAttached(follow, scene)\n if (!holder.wanted) return\n follow.getWorldPosition(world)\n const [ox, oy, oz] = values.position\n if (ox !== 0 || oy !== 0 || oz !== 0) world.set(ox, oy, oz).applyMatrix4(follow.matrixWorld)\n const axis = follow.matrixWorld.elements\n direction.set(-(axis[8] as number), -(axis[9] as number), -(axis[10] as number))\n holder.distance = Math.hypot(world.x - focus[0], world.y - focus[1], world.z - focus[2])\n Object.assign(resolved, values)\n resolved.position = [world.x, world.y, world.z]\n resolved.target = values.target ?? [world.x + direction.x, world.y + direction.y, world.z + direction.z]\n }\n\n return {\n group,\n sizes: { point: opts.point, spot: opts.spot, pointShadows, spotShadows },\n acquire(request) {\n const holder = newHolder('request', request.kind ?? 'point', request, request.urgent === true)\n holder.onLost = request.onLost\n if (!admitRequest(core, holder)) return null\n return makeHandle(holder, (fadeMs) => dropHolder(holder, fadeMs ?? opts.fadeMs))\n },\n addSource(source) {\n const holder = newHolder('source', source.kind ?? 'point', source, false)\n holder.follow = source.follow\n holder.shadow = source.shadow === true\n sources.add(holder)\n return {\n get slot() {\n return holder.slot !== null && holder.slot.holder === holder ? holder.slot.index : -1\n },\n update(patch: Partial<LightSource>) {\n mergeValues(holder.values, patch)\n if (patch.priority !== undefined) holder.priority = patch.priority\n if ('follow' in patch) holder.follow = patch.follow\n if (holder.follow === undefined) writeHolder(holder)\n },\n remove() {\n holder.alive = false\n holder.wanted = false\n sources.delete(holder)\n dropHolder(holder, opts.fadeMs)\n },\n } satisfies SourceHandle\n },\n lease({ owner, point, shadows = 0 }) {\n const shadowSlots = core.slots.point.filter((slot) => slot.shadow && isFree(slot))\n const plainSlots = core.slots.point.filter((slot) => !slot.shadow && isFree(slot))\n if (shadowSlots.length < shadows || plainSlots.length < point - shadows) return null\n const taken = [...shadowSlots.slice(0, shadows), ...plainSlots.slice(0, point - shadows)]\n const holders = taken.map((slot) => {\n const holder = newHolder('lease', 'point', { owner, priority: Number.POSITIVE_INFINITY, color: 0xffffff, intensity: 0, distance: 0, position: ZERO }, false)\n slot.leased = true\n slot.holder = holder\n slot.fade = 1\n slot.fadeTarget = 1\n holder.slot = slot\n markShadow(slot)\n return holder\n })\n const handles = holders.map((holder) => makeHandle(holder, null))\n return {\n handles,\n invalidateShadow(index) {\n for (const [at, holder] of holders.entries()) if ((index === undefined || index === at) && holder.slot !== null) markShadow(holder.slot)\n },\n release() {\n for (const holder of holders) {\n const slot = holder.slot\n holder.alive = false\n holder.slot = null\n if (slot === null) continue\n slot.leased = false\n slot.holder = null\n vacate(slot, 0)\n }\n },\n } satisfies LightLease\n },\n leaseHemisphere() {\n const light = hemispheres[leasedHemispheres]\n if (light === undefined) return null\n leasedHemispheres++\n return light\n },\n present(focus, dtSeconds) {\n for (const holder of sources) resolveSource(holder, focus)\n for (const kind of ['point', 'spot'] as const) {\n const reserve = kind === 'point' ? core.urgentReserve : 0\n const kindSources = [...sources].filter((holder) => holder.lightKind === kind)\n arbitrateSources(core, poolOf(core, kind, true), kindSources.filter((holder) => holder.shadow), 0)\n arbitrateSources(core, poolOf(core, kind, false), kindSources.filter((holder) => !holder.shadow), reserve)\n }\n stepSlots(core, dtSeconds)\n for (const holder of sources) if (holder.wanted && holder.slot?.holder === holder) writeLight(THREE, holder.slot, holder.resolved)\n },\n seal(renderer, scene, sealOptions = {}) {\n sealed = captureSealed(renderer, scene, sealOptions)\n },\n check(renderer: RendererState, checkedScene: Scene) {\n const result = checkSealed(sealed, renderer, checkedScene, group)\n const layersIntact = allSlots().every((slot) => slot.light.visible && slot.light.layers.mask === ALL_LAYERS_MASK)\n const isFirst = checkedScene.children[0] === group\n if (!layersIntact) return { ...result, ok: false, actual: `${result.actual} | budget lights hidden or off a layer` }\n return isFirst ? result : { ...result, ok: false, actual: `${result.actual} | budget group is not the first child of the scene` }\n },\n stats() {\n const slots = allSlots()\n return {\n lit: slots.filter((slot) => slot.light.intensity > 0).length,\n free: slots.filter(isFree).length,\n leased: slots.filter((slot) => slot.leased).length,\n sources: sources.size,\n sourcesWithoutSlot: [...sources].filter((holder) => holder.wanted && holder.slot === null).length,\n preempted: core.preempted,\n refused: core.refused,\n }\n },\n dispose() {\n group.removeFromParent()\n for (const slot of allSlots()) slot.light.dispose()\n sources.clear()\n },\n }\n}\n","import type { Camera, Group, HemisphereLight, Object3D, Scene, WebGLRenderTarget } from 'three'\n\nimport type { ThreeLib } from '../three-lib'\n\nexport type LightKind = 'point' | 'spot'\nexport type LinearRgb = readonly [number, number, number]\nexport type FogState = 'none' | 'linear' | 'exp2'\nexport type ThreeForLights = Pick<\n ThreeLib,\n 'Group' | 'PointLight' | 'SpotLight' | 'HemisphereLight' | 'Object3D' | 'Vector3' | 'Color' | 'LinearSRGBColorSpace'\n>\n/** What the budget reads of a renderer: a structural subset, so a fake works in Node. */\nexport type RendererState = {\n readonly toneMapping: number\n readonly outputColorSpace: string\n readonly shadowMap: { readonly type: number }\n}\n\nexport interface LightBudgetOptions {\n /** Point lights created at boot. Default 16. */\n readonly point?: number\n /** Spot lights created at boot. Default 4. */\n readonly spot?: number\n /** How many of the point lights cast shadows (cube maps). Default 2. Fixed for the life of the budget. */\n readonly pointShadows?: number\n /** How many of the spot lights cast shadows. Default 0. */\n readonly spotShadows?: number\n /** Shadow map size of the shadow slots. Default 512. */\n readonly shadowMapSize?: number\n /** Bias, normal bias and camera near plane of the shadow slots. Defaults: three.js's (0, 0, 0.5). The house's room lights use -0.0012, 0.05 and 0.12. */\n readonly shadowSettings?: { readonly bias?: number; readonly normalBias?: number; readonly near?: number }\n /** Point slots only `urgent` requests may take while others are free. Default 2. */\n readonly urgentReserve?: number\n /** Milliseconds a light fades out when a source loses its slot, and fades in when it gets one. Default 120. */\n readonly fadeMs?: number\n /** A source keeps its slot until a challenger of the same priority is this much closer (0.15 = 15%). Default 0.15. */\n readonly hysteresis?: number\n /** HemisphereLights created at boot for adapters that flash the sky; leased with `leaseHemisphere`. Default 0. */\n readonly hemisphere?: number\n}\n\n/** A light that lives as long as the request: a blast, a rocket in flight, a muzzle flash, a flare in the hand. */\nexport interface LightRequest {\n readonly owner: string\n readonly kind?: LightKind\n /** Higher wins a slot; see LIGHT_PRIORITY. */\n readonly priority: number\n /** Hex (sRGB, like `new Color(hex)`) or linear RGB. */\n readonly color: number | LinearRgb\n readonly intensity: number\n readonly distance: number\n readonly decay?: number\n readonly position: readonly [number, number, number]\n /** Spot only. */\n readonly target?: readonly [number, number, number]\n readonly angle?: number\n readonly penumbra?: number\n /** Takes a slot at once: cuts the lowest victim instead of fading it, and may use the urgent reserve. */\n readonly urgent?: boolean\n /** Called once when a higher priority took the slot; the handle is dead from then on. */\n readonly onLost?: () => void\n}\n\nexport type LightPatch = Partial<Pick<LightRequest, 'color' | 'intensity' | 'distance' | 'decay' | 'position' | 'target' | 'angle' | 'penumbra'>>\n\nexport interface LightHandle {\n /** Index of this light among the scene's point (or spot) lights in the shader; stable while the handle lives. */\n readonly slot: number\n readonly kind: LightKind\n readonly alive: boolean\n update(patch: LightPatch): void\n /** Gives the slot back; with fadeMs the light fades first. Idempotent. */\n release(options?: { readonly fadeMs?: number }): void\n}\n\n/** A light that exists in the world for a long time and competes for a slot every frame: a lamp, a street light, a headlight. */\nexport interface LightSource extends Omit<LightRequest, 'urgent' | 'onLost'> {\n /** Wants a shadow slot; a shadow source competes for shadow slots only. */\n readonly shadow?: boolean\n /**\n * The object the light sits on. Its world position (and, for a spot, its world -z as the direction when `target` is absent) is read\n * once per `present`; `position` is then an offset in its local space. A source whose `follow` left the scene counts as off.\n */\n readonly follow?: Object3D\n}\n\nexport interface SourceHandle {\n /** Slot now, or -1 while it has none (its light is not drawn). */\n readonly slot: number\n update(patch: Partial<LightSource>): void\n remove(): void\n}\n\n/** Slots a client keeps for itself for good: the room lights of a house (their masks are indexed by slot). */\nexport interface LightLease {\n readonly handles: readonly LightHandle[]\n /** Redraws the shadow map of one leased slot (`handles[index]`), or of all of them, on the next render: the walls it shades changed. */\n invalidateShadow(index?: number): void\n release(): void\n}\n\nexport interface SealOptions {\n /** Fog states the game will switch between at run time. Default: only the state at seal time. */\n readonly fogStates?: readonly FogState[]\n /** Cameras whose `far` must stay under the neutral fog's near (read at every `check`). */\n readonly cameras?: readonly Camera[]\n /** Where the game really draws; its colour space is part of the sealed state. */\n readonly target?: WebGLRenderTarget | null\n}\n\nexport interface LightCheck {\n readonly ok: boolean\n readonly expected: string\n readonly actual: string\n /** Paths of PointLight/SpotLight found outside the budget's group. */\n readonly strays: readonly string[]\n}\n\nexport interface LightBudgetStats {\n readonly lit: number\n readonly free: number\n readonly leased: number\n readonly sources: number\n readonly sourcesWithoutSlot: number\n readonly preempted: number\n readonly refused: number\n}\n\nexport interface LightBudget {\n /** The group holding every light; first child of the scene. Never hidden, never removed while the budget lives. */\n readonly group: Group\n readonly sizes: { readonly point: number; readonly spot: number; readonly pointShadows: number; readonly spotShadows: number }\n acquire(request: LightRequest): LightHandle | null\n addSource(source: LightSource): SourceHandle\n /** Takes `point` slots (of which `shadows` shadow slots) for good. null when not enough unleased slots remain. */\n lease(options: { readonly owner: string; readonly point: number; readonly shadows?: number }): LightLease | null\n /** Takes one of the boot HemisphereLights for good. null when none is left. Its intensity starts at 0. */\n leaseHemisphere(owner: string): HemisphereLight | null\n /** Once per frame, with the camera (or the player) as focus. `dtSeconds` is real time: fades do not stop in pause. */\n present(focus: readonly [number, number, number], dtSeconds: number): void\n /** After this, any change in the program-key state of the scene is a bug and `check()` reports it. */\n seal(renderer: RendererState, scene: Scene, options?: SealOptions): void\n check(renderer: RendererState, scene: Scene): LightCheck\n stats(): LightBudgetStats\n dispose(): void\n}\n\nexport const LIGHT_PRIORITY = {\n blast: 900,\n rocket: 800,\n muzzle: 750,\n fire: 600,\n flare: 500,\n headlight: 400,\n weaponLight: 400,\n lamp: 200,\n street: 150,\n} as const\n","import type { LightBudget } from './types'\n\n/** For adapters that receive the budget as `unknown` through the host (the contract does not know three). */\nexport function isLightBudget(value: unknown): value is LightBudget {\n const candidate = value as Partial<LightBudget> | null | undefined\n return typeof candidate === 'object' && candidate !== null && typeof candidate.acquire === 'function' && typeof candidate.addSource === 'function' && typeof candidate.lease === 'function' && typeof candidate.present === 'function'\n}\n","import type { Material } from 'three'\n\n/** What `programKeysOf` reads of a renderer: `properties` is internal to three r160 and may be absent in another version. */\nexport type PropertiesReader = { readonly properties?: { get(object: object): unknown } }\n\n/**\n * Every program cache key three linked for `material` (one per state it was drawn in: fog type, shadow, light counts). Reads three's\n * internal `properties.get(material).programs` Map (r160). `[]` for a material never drawn; `undefined` when this three does not have\n * that internal, which sends the pools to their conservative mode.\n */\nexport function programKeysOf(renderer: PropertiesReader, material: Material): readonly string[] | undefined {\n const properties = renderer.properties\n if (typeof properties?.get !== 'function') return undefined\n const programs = (properties.get(material) as { programs?: unknown } | undefined)?.programs\n if (programs === undefined) return []\n if (!(programs instanceof Map)) return undefined\n return [...programs.keys()].map(String)\n}\n","import type { Material } from 'three'\n\nimport { type PropertiesReader, programKeysOf } from '../program-key'\n\n/** Materials kept alive and never disposed, one per program family: what keeps a program linked once its users are gone. */\nexport interface MaterialAnchors {\n /**\n * Keeps `material` if any of its program keys has no anchor yet; returns true when it was kept (the caller must then not dispose it).\n * A material whose keys are all covered returns false and may be disposed. A material never drawn is not kept.\n */\n offer(material: Material): boolean\n readonly size: number\n /** Where program keys are read from. Until bound (or when three has no readable keys) every offered material is kept. */\n bind(renderer: PropertiesReader): void\n}\n\nexport function createMaterialAnchors(renderer?: PropertiesReader): MaterialAnchors {\n const anchors = new Set<Material>()\n let reader = renderer\n\n function coveredKeys(): Set<string> {\n const covered = new Set<string>()\n if (reader === undefined) return covered\n for (const anchor of anchors) for (const key of programKeysOf(reader, anchor) ?? []) covered.add(key)\n return covered\n }\n\n return {\n get size() {\n return anchors.size\n },\n bind(next) {\n reader = next\n },\n offer(material) {\n if (anchors.has(material)) return true\n const keys = reader === undefined ? undefined : programKeysOf(reader, material)\n if (keys === undefined) {\n anchors.add(material)\n return true\n }\n if (keys.length === 0) return false\n const covered = coveredKeys()\n if (keys.every((key) => covered.has(key))) return false\n anchors.add(material)\n return true\n },\n }\n}\n","import type { Object3D } from 'three'\n\nimport type { ThreeLib } from './three-lib'\nimport type { WarmupProbe } from './warmup-types'\n\n/** The sprite probes the room builds by hand today: one sprite material with fog and one without. */\nexport function spriteProbes(THREE: Pick<ThreeLib, 'Group' | 'Sprite' | 'SpriteMaterial' | 'DataTexture'>): WarmupProbe {\n return {\n id: 'sprite',\n build() {\n const group = new THREE.Group()\n const texture = new THREE.DataTexture(new Uint8Array([255, 255, 255, 255]), 1, 1)\n texture.needsUpdate = true\n const materials = [true, false].map((fog) => new THREE.SpriteMaterial({ map: texture, fog, transparent: true }))\n for (const material of materials) group.add(new THREE.Sprite(material))\n return { group, materials, dispose: () => texture.dispose() }\n },\n }\n}\n\nexport function hasMissingDistanceMaterial(root: Object3D): string | undefined {\n let found: string | undefined\n root.traverse((object) => {\n const mesh = object as { isInstancedMesh?: boolean; castShadow?: boolean; customDepthMaterial?: unknown; customDistanceMaterial?: unknown }\n if (found === undefined && mesh.isInstancedMesh === true && mesh.castShadow === true && mesh.customDepthMaterial && !mesh.customDistanceMaterial) found = object.name || object.type\n })\n return found\n}\n","import type { BufferGeometry, Camera, Material, Object3D, Scene } from 'three'\n\nimport { fogStateOf, lightSignature } from './lights/scene-state'\nimport type { FogState } from './lights/types'\nimport { createMaterialAnchors } from './pools/anchors'\nimport { hasMissingDistanceMaterial } from './probes'\nimport type { ThreeLib } from './three-lib'\nimport type { EffectWarmup, ProgramWarmup, WarmupContext, WarmupOptions, WarmupProbe, WarmupReport, WarmupRenderer } from './warmup-types'\n\nconst EFFECT_STEP_SECONDS = 1 / 50\nconst DEFAULT_EFFECT_FRAMES = 3\nconst WARM_FOG_DENSITY = 0.01\nconst WARM_FOG_FAR = 100\n\ntype LinkCounter = { count: number }\ntype BuiltProbe = { readonly probe: WarmupProbe; readonly built: ReturnType<WarmupProbe['build']> }\n\nfunction register<TItem>(items: Set<TItem>, item: TItem): () => void {\n items.add(item)\n return () => void items.delete(item)\n}\n\nfunction countLinks(context: WarmupContext, counters: WeakMap<WarmupContext, LinkCounter>): void {\n const existing = counters.get(context)\n if (existing !== undefined) {\n existing.count = 0\n return\n }\n const counter: LinkCounter = { count: 0 }\n counters.set(context, counter)\n const original = context.linkProgram.bind(context)\n context.linkProgram = (program) => {\n counter.count++\n original(program)\n }\n}\n\nfunction failedProgram(renderer: WarmupRenderer): string | undefined {\n for (const program of renderer.info.programs ?? []) {\n const diagnostics = (program as { diagnostics?: { runnable?: boolean; programLog?: string } }).diagnostics\n if (diagnostics?.runnable === false) return diagnostics.programLog ?? 'program failed to link'\n }\n return undefined\n}\n\n/**\n * Finishes every program the renderer holds: three.js links a program when a material first compiles, but reads its link status, its logs and its\n * uniform locations only on the program's first draw (`getUniforms`), and that read waits for the driver to end the link: ~7 ms a program on the\n * RTX 5070 laptop, 43 ms for the six shadow-receiving programs the horde chain's first blast drew for the first time (perf.mjs, 2026-11-03).\n * A program linked here and drawn only in play (a hidden part of a vehicle, an effect of another area) pays that at load instead.\n */\nfunction finishPrograms(renderer: WarmupRenderer): number {\n let finished = 0\n for (const program of renderer.info.programs ?? []) {\n const linked = program as { getUniforms?: () => unknown; getAttributes?: () => unknown }\n if (typeof linked.getUniforms !== 'function') continue\n linked.getUniforms()\n linked.getAttributes?.()\n finished++\n }\n return finished\n}\n\nexport function createProgramWarmup(THREE: Pick<ThreeLib, 'Group' | 'Sprite' | 'SpriteMaterial' | 'DataTexture' | 'Fog' | 'FogExp2' | 'Mesh' | 'Points' | 'LineSegments' | 'BufferGeometry' | 'BufferAttribute'>): ProgramWarmup {\n const probes = new Set<WarmupProbe>()\n const effects = new Set<EffectWarmup>()\n const textures = new Set<Parameters<WarmupRenderer['initTexture']>[0]>()\n const anchors = createMaterialAnchors()\n const linkCounters = new WeakMap<WarmupContext, LinkCounter>()\n\n function setFog(scene: Scene, state: FogState): void {\n const color = scene.fog?.color.getHex() ?? 0\n if (state === 'none') scene.fog = null\n else if (state === 'linear' && (scene.fog as { isFog?: boolean } | null)?.isFog !== true) scene.fog = new THREE.Fog(color, 1, WARM_FOG_FAR)\n else if (state === 'exp2' && (scene.fog as { isFogExp2?: boolean } | null)?.isFogExp2 !== true) scene.fog = new THREE.FogExp2(color, WARM_FOG_DENSITY)\n }\n\n function draw(renderer: WarmupRenderer, scene: Scene, camera: Camera, target: WarmupOptions['target']): void {\n renderer.setRenderTarget(target ?? null)\n renderer.compile(scene, camera)\n renderer.render(scene, camera)\n }\n\n /**\n * Hides every drawable of the scene outside `root` and returns the undo. An object with a light under it stays: the lights are what the programs\n * are keyed by, and hiding one would warm programs for a light count the game never draws.\n */\n function drawOnly(scene: Scene, root: Object3D): () => void {\n const hidden: Object3D[] = []\n const visit = (object: Object3D): void => {\n if (object === root || !object.visible) return\n const drawable = object as Object3D & { isMesh?: boolean; isPoints?: boolean; isLine?: boolean; isSprite?: boolean }\n if (drawable.isMesh === true || drawable.isPoints === true || drawable.isLine === true || drawable.isSprite === true) {\n if (!holdsLight(object) && !holdsObject(object, root)) {\n object.visible = false\n hidden.push(object)\n return\n }\n }\n for (const child of object.children) visit(child)\n }\n for (const child of scene.children) visit(child)\n return () => hidden.forEach((object) => (object.visible = true))\n }\n\n function holdsLight(object: Object3D): boolean {\n let found = false\n object.traverse((each) => {\n if ((each as { isLight?: boolean }).isLight === true) found = true\n })\n return found\n }\n\n function holdsObject(object: Object3D, target: Object3D): boolean {\n for (let at: Object3D | null = target; at !== null; at = at.parent) if (at === object) return true\n return false\n }\n\n function playEffect(renderer: WarmupRenderer, scene: Scene, camera: Camera, effect: EffectWarmup, target: WarmupOptions['target']): void {\n const root = effect.root?.()\n const restore = root === null || root === undefined ? (): void => undefined : drawOnly(scene, root)\n try {\n playFrames(renderer, scene, camera, effect, target)\n } finally {\n restore()\n }\n }\n\n function playFrames(renderer: WarmupRenderer, scene: Scene, camera: Camera, effect: EffectWarmup, target: WarmupOptions['target']): void {\n effect.play()\n try {\n for (let frame = 0; frame < (effect.frames ?? DEFAULT_EFFECT_FRAMES); frame++) {\n effect.step(EFFECT_STEP_SECONDS)\n renderer.setRenderTarget(target ?? null)\n renderer.render(scene, camera)\n }\n } finally {\n effect.stop()\n }\n }\n\n /** One object per material, of the kind its program is for, each a single triangle: drawn once so that the program is used, not only linked. */\n function materialsProbe(materials: readonly Material[]): BuiltProbe {\n const geometry = new THREE.BufferGeometry()\n geometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array([0, 0, 0, 0.001, 0, 0, 0, 0.001, 0]), 3))\n const group = new THREE.Group()\n for (const material of materials) group.add(drawableOf(material, geometry))\n return { probe: { id: 'materials', build: () => ({ group, materials: [], dispose: () => geometry.dispose() }) }, built: { group, materials: [], dispose: () => geometry.dispose() } }\n }\n\n function drawableOf(material: Material, geometry: BufferGeometry): Object3D {\n const flags = material as { isSpriteMaterial?: boolean; isPointsMaterial?: boolean; isLineBasicMaterial?: boolean }\n const object: Object3D =\n flags.isSpriteMaterial === true ? new THREE.Sprite(material as never)\n : flags.isPointsMaterial === true ? new THREE.Points(geometry, material)\n : flags.isLineBasicMaterial === true ? new THREE.LineSegments(geometry, material)\n : new THREE.Mesh(geometry, material)\n object.frustumCulled = false\n return object\n }\n\n function buildProbes(materials: readonly Material[] | undefined): BuiltProbe[] {\n const built = [...probes].map((probe) => ({ probe, built: probe.build() }))\n if (materials !== undefined && materials.length > 0) built.push(materialsProbe(materials))\n return built\n }\n\n function addProbes(scene: Scene, built: readonly BuiltProbe[]): void {\n for (const { probe, built: item } of built) {\n const missing = hasMissingDistanceMaterial(item.group)\n if (missing !== undefined) throw new Error(`warmup probe \"${probe.id}\": instanced mesh \"${missing}\" casts shadows with a customDepthMaterial but no customDistanceMaterial`)\n scene.add(item.group)\n }\n }\n\n function removeProbes(scene: Scene, built: readonly BuiltProbe[]): void {\n for (const { built: item } of built) {\n scene.remove(item.group)\n item.dispose()\n for (const material of item.materials) anchors.offer(material)\n }\n }\n\n function drawStates(renderer: WarmupRenderer, scene: Scene, camera: Camera, states: readonly FogState[], target: WarmupOptions['target']): void {\n for (const state of states) {\n setFog(scene, state)\n draw(renderer, scene, camera, target)\n }\n }\n\n /** Makes every instanced batch draw at least once: three skips an empty instanced batch, and a driver finishes a program's state on its first real draw (a 200 ms stall). Returns the undo. */\n function forceDraws(scene: Scene): () => void {\n const undo: Array<() => void> = []\n scene.traverse((object) => {\n const drawable = object as Object3D & { isInstancedMesh?: boolean; count?: number; geometry?: { isInstancedBufferGeometry?: boolean; instanceCount?: number } }\n const { geometry } = drawable\n if (geometry?.isInstancedBufferGeometry === true && geometry.instanceCount === 0) {\n geometry.instanceCount = 1\n undo.push(() => (geometry.instanceCount = 0))\n }\n if (drawable.isInstancedMesh === true && drawable.count === 0) {\n drawable.count = 1\n undo.push(() => (drawable.count = 0))\n }\n })\n return () => undo.forEach((restore) => restore())\n }\n\n return {\n anchors,\n register: (probe) => register(probes, probe),\n registerEffect: (effect) => register(effects, effect),\n registerTexture: (texture) => register(textures, texture),\n warmUp(renderer, scene, camera, options = {}): WarmupReport {\n const started = performance.now()\n const { lights, target } = options\n anchors.bind(renderer)\n const originalFog = scene.fog\n const states = options.fogStates ?? [fogStateOf(scene)]\n lights?.seal(renderer, scene, { fogStates: options.fogStates, cameras: [camera], target })\n scene.traverse((object) => {\n const light = object as { castShadow?: boolean; shadow?: { needsUpdate: boolean } }\n if (light.castShadow === true && light.shadow !== undefined) light.shadow.needsUpdate = true\n })\n const built = buildProbes(options.materials)\n let restoreDraws = (): void => undefined\n try {\n addProbes(scene, built)\n restoreDraws = forceDraws(scene)\n drawStates(renderer, scene, camera, states, target)\n scene.fog = originalFog\n for (const effect of effects) playEffect(renderer, scene, camera, effect, target)\n for (const texture of textures) renderer.initTexture(texture)\n } finally {\n restoreDraws()\n scene.fog = originalFog\n removeProbes(scene, built)\n }\n // also what lets failedProgram see a broken link: three only fills `diagnostics` on the first use\n finishPrograms(renderer)\n const context = renderer.getContext()\n const uniformVectors = Number(context.getParameter(context.MAX_FRAGMENT_UNIFORM_VECTORS))\n const failure = failedProgram(renderer)\n if (failure !== undefined) {\n throw new Error(`a shader program failed to link (MAX_FRAGMENT_UNIFORM_VECTORS ${uniformVectors}, lights: ${lightSignature(scene)}): ${failure}`)\n }\n countLinks(context, linkCounters)\n // the GPU process is still working through all of the above: wait here, at load, or the first upload of play stalls on it\n if (context.RGBA !== undefined && context.UNSIGNED_BYTE !== undefined) context.readPixels?.(0, 0, 1, 1, context.RGBA, context.UNSIGNED_BYTE, new Uint8Array(4))\n return {\n programs: renderer.info.programs?.length ?? 0,\n geometries: renderer.info.memory.geometries,\n textures: renderer.info.memory.textures,\n milliseconds: performance.now() - started,\n lightSignature: lightSignature(scene),\n uniformVectors,\n anchors: anchors.size,\n }\n },\n linkedSince(renderer) {\n return linkCounters.get(renderer.getContext())?.count ?? 0\n },\n }\n}\n","const POOLED = '__pooled'\n\ntype Disposable = { dispose(): void; userData: Record<string, unknown> }\n\nfunction isDevelopment(): boolean {\n return typeof process === 'undefined' || process.env.NODE_ENV !== 'production'\n}\n\n/** Marks a pool resource and, in development, warns when something disposes it directly instead of releasing it to the pool. */\nexport function markPooled(resource: Disposable, key: string, owner: string): { disposeForPool(): void } {\n resource.userData[POOLED] = key\n const original = resource.dispose.bind(resource)\n let fromPool = false\n if (isDevelopment()) {\n resource.dispose = () => {\n if (!fromPool) console.warn(`[game-render-three] ${owner} \"${key}\" was disposed directly; release it to its pool instead`)\n original()\n }\n }\n return {\n disposeForPool() {\n fromPool = true\n original()\n fromPool = false\n },\n }\n}\n\nexport function pooledKey(resource: { userData: Record<string, unknown> }): string | undefined {\n const key = resource.userData[POOLED]\n return typeof key === 'string' ? key : undefined\n}\n\n/** A number as a short stable string, rounded to `step`. */\nexport function roundedNumber(value: number, step: number): string {\n return String(Number((Math.round(value / step) * step).toFixed(6)))\n}\n","import type { BufferGeometry } from 'three'\n\nimport { markPooled, pooledKey, roundedNumber } from './pooled'\n\nexport interface GeometryPool {\n /** The geometry for `key`, built once with `build`. Counts one use. */\n acquire(key: string, build: () => BufferGeometry): BufferGeometry\n release(geometry: BufferGeometry): void\n /** Never disposed while the pool lives (unit shapes, effect geometries warmed at load). */\n retain(key: string): void\n /** Disposes unretained geometries with no use, oldest first, keeping at most `keep` idle ones. Only at load points, never in play. */\n trim(keep?: number): number\n stats(): { readonly entries: number; readonly retained: number; readonly inUse: number }\n dispose(): void\n}\n\ntype Entry = { geometry: BufferGeometry; uses: number; retained: boolean; idleSince: number; disposeForPool(): void }\n\nconst DEFAULT_KEY_STEP = 1e-3\n\n/** Builds a key from a kind and its numbers, rounded to `step`, so near-equal sizes share one geometry. */\nexport function geometryKey(kind: string, params: readonly number[], step: number = DEFAULT_KEY_STEP): string {\n return [kind, ...params.map((value) => roundedNumber(value, step))].join('|')\n}\n\nexport function createGeometryPool(): GeometryPool {\n const entries = new Map<string, Entry>()\n let clock = 0\n\n function disposeEntry(key: string, entry: Entry): void {\n entries.delete(key)\n entry.disposeForPool()\n }\n\n return {\n acquire(key, build) {\n let entry = entries.get(key)\n if (entry === undefined) {\n const geometry = build()\n entry = { geometry, uses: 0, retained: false, idleSince: 0, ...markPooled(geometry, key, 'geometry') }\n entries.set(key, entry)\n }\n entry.uses++\n return entry.geometry\n },\n release(geometry) {\n const key = pooledKey(geometry)\n const entry = key === undefined ? undefined : entries.get(key)\n if (entry === undefined || entry.uses === 0) return\n entry.uses--\n if (entry.uses === 0) entry.idleSince = ++clock\n },\n retain(key) {\n const entry = entries.get(key)\n if (entry !== undefined) entry.retained = true\n },\n trim(keep = 0) {\n const idle = [...entries].filter(([, entry]) => entry.uses === 0 && !entry.retained).sort((a, b) => a[1].idleSince - b[1].idleSince)\n const excess = idle.slice(0, Math.max(0, idle.length - keep))\n for (const [key, entry] of excess) disposeEntry(key, entry)\n return excess.length\n },\n stats() {\n const all = [...entries.values()]\n return { entries: all.length, retained: all.filter((entry) => entry.retained).length, inUse: all.filter((entry) => entry.uses > 0).length }\n },\n dispose() {\n for (const [key, entry] of [...entries]) disposeEntry(key, entry)\n },\n }\n}\n","import type { Material } from 'three'\n\nimport type { PropertiesReader } from '../program-key'\nimport { type MaterialAnchors, createMaterialAnchors } from './anchors'\nimport { markPooled, pooledKey, roundedNumber } from './pooled'\n\nexport interface MaterialPool {\n /**\n * The material for `key`, built once. Shared: callers must not change it. Per-instance variety goes into instance attributes, not\n * into new materials.\n */\n acquire<TMaterial extends Material>(key: string, build: () => TMaterial): TMaterial\n release(material: Material): void\n /** Declares a variant that must never be disposed (its program stays linked). Effects register theirs at load. */\n register<TMaterial extends Material>(key: string, build: () => TMaterial): TMaterial\n /**\n * A material the caller owns and changes per frame (a puff's opacity): not shared. On `release` the first one of its program family is\n * handed to the anchors, so disposing the others never frees the program.\n */\n own<TMaterial extends Material>(build: () => TMaterial): TMaterial\n /** The materials declared with `register`: the ones that must never be disposed and whose programs the warmup draws once. */\n registeredMaterials?(): readonly Material[]\n /** Disposes unregistered materials with no use, oldest first, keeping at most `keep` idle ones; a family's anchor is kept alive. */\n trim(keep?: number): number\n stats(): { readonly entries: number; readonly registered: number; readonly inUse: number; readonly families: number }\n /**\n * The end of the pool: disposes every material it made, in use or not, registered or anchored (the guarantee that a program stays linked\n * holds while the pool lives, not after it). The anchors object itself is not touched, and the pool is empty and usable again afterwards.\n */\n dispose(): void\n}\n\nexport type MaterialPoolOptions = { readonly anchors?: MaterialAnchors; readonly renderer?: PropertiesReader }\n\ntype Entry = { material: Material; uses: number; registered: boolean; idleSince: number; disposeForPool(): void }\n\nconst DEFAULT_KEY_STEP = 1e-3\n\n/** Key from the material class and every parameter that changes its look, sorted, numbers rounded. */\nexport function materialKey(type: string, params: Readonly<Record<string, unknown>>): string {\n const parts = Object.keys(params)\n .sort()\n .map((name) => {\n const value = params[name]\n return `${name}=${typeof value === 'number' ? roundedNumber(value, DEFAULT_KEY_STEP) : JSON.stringify(value)}`\n })\n return [type, ...parts].join('|')\n}\n\nexport function createMaterialPool(options: MaterialPoolOptions = {}): MaterialPool {\n const anchors = options.anchors ?? createMaterialAnchors()\n if (options.renderer !== undefined) anchors.bind(options.renderer)\n const entries = new Map<string, Entry>()\n const owned = new Map<Material, () => void>()\n let clock = 0\n\n function createEntry(key: string, material: Material, registered: boolean): Entry {\n const entry: Entry = { material, uses: 0, registered, idleSince: 0, ...markPooled(material, key, 'material') }\n entries.set(key, entry)\n return entry\n }\n\n /** Disposes the material unless it is the anchor of a program family. */\n function retire(material: Material, disposeForPool: () => void): boolean {\n if (anchors.offer(material)) return false\n disposeForPool()\n return true\n }\n\n return {\n acquire(key, build) {\n const entry = entries.get(key) ?? createEntry(key, build(), false)\n entry.uses++\n return entry.material as ReturnType<typeof build>\n },\n register(key, build) {\n const entry = entries.get(key) ?? createEntry(key, build(), true)\n entry.registered = true\n return entry.material as ReturnType<typeof build>\n },\n own(build) {\n const material = build()\n owned.set(material, markPooled(material, 'own', 'material').disposeForPool)\n return material\n },\n release(material) {\n const disposeOwned = owned.get(material)\n if (disposeOwned !== undefined) {\n owned.delete(material)\n retire(material, disposeOwned)\n return\n }\n const key = pooledKey(material)\n const entry = key === undefined ? undefined : entries.get(key)\n if (entry === undefined || entry.uses === 0) return\n entry.uses--\n if (entry.uses === 0) entry.idleSince = ++clock\n },\n trim(keep = 0) {\n const idle = [...entries].filter(([, entry]) => entry.uses === 0 && !entry.registered).sort((a, b) => a[1].idleSince - b[1].idleSince)\n let disposed = 0\n for (const [key, entry] of idle.slice(0, Math.max(0, idle.length - keep))) {\n entries.delete(key)\n if (retire(entry.material, entry.disposeForPool)) disposed++\n }\n return disposed\n },\n registeredMaterials() {\n return [...entries.values()].filter((entry) => entry.registered).map((entry) => entry.material)\n },\n stats() {\n const all = [...entries.values()]\n return {\n entries: all.length,\n registered: all.filter((entry) => entry.registered).length,\n inUse: all.filter((entry) => entry.uses > 0).length,\n families: anchors.size,\n }\n },\n dispose() {\n for (const entry of entries.values()) entry.disposeForPool()\n for (const disposeOwned of owned.values()) disposeOwned()\n entries.clear()\n owned.clear()\n },\n }\n}\n","import type { GeometryPool } from './geometry'\nimport type { MaterialPool } from './material'\n\n/** The pools a host lends its adapters (`host.pools`): geometries and materials that outlive the effect that asked for them. */\nexport interface RenderPools {\n readonly geometries: GeometryPool\n readonly materials: MaterialPool\n}\n\n/** For adapters that receive the pools as `unknown` through the host (the contract does not know three). */\nexport function isRenderPools(value: unknown): value is RenderPools {\n const candidate = value as Partial<RenderPools> | null | undefined\n return typeof candidate === 'object' && candidate !== null && typeof candidate.geometries?.acquire === 'function' && typeof candidate.materials?.acquire === 'function'\n}\n","/** The part of a WebGL2 context the pacer reads; the renderer's `getContext()` satisfies it. */\nexport type FramePacerContext = {\n readonly SYNC_GPU_COMMANDS_COMPLETE: number\n readonly TIMEOUT_EXPIRED: number\n fenceSync(condition: number, flags: number): WebGLSync | null\n clientWaitSync(sync: WebGLSync, flags: number, timeout: number): number\n deleteSync(sync: WebGLSync | null): void\n flush(): void\n}\n\nexport type FramePacerOptions = {\n /** Most frames in a row the pacer may hold back before it lets one through (default 3), so a stuck fence cannot freeze the picture. */\n readonly maxSkips?: number\n /** Frames the GPU may be behind before a draw is held back (default 2). The browser reports a fence only some 20 ms after the GPU finished it, so 1 would cap every GPU at one frame per report. */\n readonly framesInFlight?: number\n}\n\nexport type FramePacer = {\n /** True when fewer than `framesInFlight` drawn frames are unfinished: draw this frame. False: the GPU is too far behind, skip the draw and keep simulating. */\n ready(): boolean\n /** Fences the frame just drawn; call it right after the render. */\n submitted(): void\n /** Draws held back so far. */\n readonly skipped: number\n dispose(): void\n}\n\nconst DEFAULT_MAX_SKIPS = 3\nconst DEFAULT_FRAMES_IN_FLIGHT = 2\n\n/**\n * Keeps the GPU a few frames behind the CPU at most. A frame loop that draws as fast as it can (no vsync, as the perf runs do, or a slow GPU) fills the driver's queue and\n * then blocks in the compositor for the length of the whole queue: a run of 5 ms frames and a 150 ms one. Holding back the draw while the last frame is unfinished spends that\n * time on the next simulation step instead, and the longest frame is the GPU's own. A context without fences (WebGL1) is never held back.\n */\nexport function createFramePacer(context: FramePacerContext | null, options: FramePacerOptions = {}): FramePacer {\n const maxSkips = options.maxSkips ?? DEFAULT_MAX_SKIPS\n const framesInFlight = options.framesInFlight ?? DEFAULT_FRAMES_IN_FLIGHT\n const fences: WebGLSync[] = []\n let skippedInRow = 0\n let skipped = 0\n const dropOldest = (): void => {\n const oldest = fences.shift()\n if (oldest && context) context.deleteSync(oldest)\n }\n return {\n ready() {\n if (!context) return true\n while (fences.length > 0 && context.clientWaitSync(fences[0] as WebGLSync, 0, 0) !== context.TIMEOUT_EXPIRED) dropOldest()\n if (fences.length >= framesInFlight && skippedInRow < maxSkips) {\n skippedInRow++\n skipped++\n return false\n }\n skippedInRow = 0\n if (fences.length >= framesInFlight) dropOldest()\n return true\n },\n submitted() {\n if (!context || typeof context.fenceSync !== 'function') return\n const fence = context.fenceSync(context.SYNC_GPU_COMMANDS_COMPLETE, 0)\n if (fence) fences.push(fence)\n context.flush()\n },\n get skipped() {\n return skipped\n },\n dispose() {\n while (fences.length > 0) dropOldest()\n },\n }\n}\n","import type { Bone, BufferAttribute, BufferGeometry, Material, Matrix3, Matrix4, Mesh, Object3D, SkinnedMesh } from 'three'\n\nimport type { ThreeLib } from './three-lib'\n\n/** The layer a batched mesh is moved to: no camera draws it, a raycaster that enables every layer still finds it. */\nexport const BATCHED_LAYER = 30\n\nconst MATRIX_EPSILON = 1e-6\n\nexport type StaticBatcherOptions = {\n /** Which meshes may be batched (default every shown mesh with one material and no instancing, skinning or morphing). */\n readonly include?: (mesh: Mesh) => boolean\n /** Milliseconds of merging per `update` (default all at once, in the call): batches are made a material at a time over the next frames. */\n readonly budgetMs?: number\n /**\n * The meshes move on their own (a body posed bone by bone): each batch is skinned to its meshes, so a mesh carries its part of the batch\n * wherever it goes, and only a new geometry, material, dent or hiding gives it its own draw back.\n */\n readonly followMeshes?: boolean\n}\n\nexport type StaticBatcherStats = { readonly batches: number; readonly batched: number; readonly released: number }\n\nexport type StaticBatcher = {\n /** The batch meshes, a child of the root. */\n readonly group: Object3D\n /**\n * Gives back to its own draw every batched mesh that changed since the batch was made (moved, dented, hidden, given another geometry or\n * material); returns how many it released. Call it once per frame, before the render.\n */\n update(): number\n stats(): StaticBatcherStats\n /** Every mesh draws on its own again and the batches are disposed. */\n dispose(): void\n}\n\ntype Source = {\n readonly mesh: Mesh\n readonly geometry: BufferGeometry\n readonly material: Material\n readonly version: number\n readonly local: Matrix4\n /** The mesh's world matrix when last seen unchanged: equal again, nothing moved and the local check is skipped. */\n readonly world: Matrix4\n readonly mask: number\n readonly batch: Batch\n readonly start: number\n readonly count: number\n released: boolean\n}\n\ntype Batch = { readonly mesh: Mesh; live: number; readonly index: { array: ArrayLike<number> & { [n: number]: number }; needsUpdate: boolean } }\n\nconst ATTRIBUTES = ['position', 'normal', 'uv', 'color'] as const\n\n/** Visible all the way up to `root`; a mesh no longer under it (its model was rebuilt) is not shown. */\nconst isShown = (object: Object3D, root: Object3D): boolean => {\n for (let node: Object3D | null = object; node; node = node.parent) {\n if (node === root) return true\n if (!node.visible) return false\n }\n return false\n}\n\nconst isBatchable = (mesh: Mesh): boolean => {\n const flags = mesh as Mesh & { isInstancedMesh?: boolean; isSkinnedMesh?: boolean }\n if (flags.isInstancedMesh === true || flags.isSkinnedMesh === true || Array.isArray(mesh.material)) return false\n const geometry = mesh.geometry\n return geometry.attributes.position !== undefined && Object.keys(geometry.morphAttributes).length === 0\n}\n\nconst signatureOf = (mesh: Mesh): string => {\n const { attributes } = mesh.geometry\n const present = ATTRIBUTES.filter((name) => attributes[name] !== undefined).map((name) => `${name}${attributes[name]!.itemSize}`)\n return `${(mesh.material as Material).uuid}|${present.join(',')}|${mesh.castShadow ? 1 : 0}${mesh.receiveShadow ? 1 : 0}`\n}\n\nconst sameMatrix = (a: Matrix4, b: Matrix4): boolean => a.elements.every((value, index) => Math.abs(value - (b.elements[index] as number)) < MATRIX_EPSILON)\n\n/**\n * Draws the still, undamaged meshes of an object (a vehicle, a piece of furniture) as one batch per material, and gives each mesh its own draw\n * back the moment it changes. The meshes stay where they are: what hits, dents or cuts them works on them as before, they only leave the\n * camera's layer while batched.\n */\nexport function createStaticBatcher(THREE: ThreeLib, root: Object3D, options: StaticBatcherOptions = {}): StaticBatcher {\n const include = options.include ?? (() => true)\n const follow = options.followMeshes === true\n root.updateMatrixWorld(true)\n const inverse = new THREE.Matrix4().copy(root.matrixWorld).invert()\n const groups = new Map<string, Mesh[]>()\n root.traverse((object) => {\n const mesh = object as Mesh\n if (!mesh.isMesh || !isShown(mesh, root) || !isBatchable(mesh) || !include(mesh)) return\n const key = signatureOf(mesh)\n groups.set(key, [...(groups.get(key) ?? []), mesh])\n })\n const group = new THREE.Group()\n group.name = 'static-batches'\n const sources: Source[] = []\n const pending = [...groups.values()].filter((meshes) => meshes.length >= 2)\n const mergeSome = (deadline: number): void => {\n while (pending.length > 0) {\n const meshes = pending.shift() as Mesh[]\n // a mesh that changed while it waited is left to draw on its own\n if (meshes.every((mesh) => isShown(mesh, root))) sources.push(...mergeGroup(THREE, { meshes, inverse: new THREE.Matrix4().copy(root.matrixWorld).invert(), group, follow }))\n if (performance.now() >= deadline) return\n }\n }\n if (options.budgetMs === undefined) mergeSome(Number.POSITIVE_INFINITY)\n root.add(group)\n const scratch = new THREE.Matrix4()\n let released = 0\n\n const release = (source: Source): void => {\n source.released = true\n source.mesh.layers.mask = source.mask\n const { index } = source.batch\n const first = index.array[source.start] as number\n for (let at = source.start; at < source.start + source.count; at++) index.array[at] = first\n index.needsUpdate = true\n // a batch whose meshes all draw on their own again is an empty draw: it leaves the screen\n if (--source.batch.live === 0) source.batch.mesh.visible = false\n released++\n }\n const hasChanged = (source: Source): boolean => {\n const { mesh } = source\n if (mesh.geometry !== source.geometry || mesh.material !== source.material || !isShown(mesh, root)) return true\n if ((mesh.geometry.attributes.position as BufferAttribute).version !== source.version) return true\n if (follow || sameMatrix(mesh.matrixWorld, source.world)) return false\n if (!sameMatrix(scratch.multiplyMatrices(inverse, mesh.matrixWorld), source.local)) return true\n // the whole object moved, the mesh did not move within it\n source.world.copy(mesh.matrixWorld)\n return false\n }\n\n return {\n group,\n update() {\n if (pending.length > 0) mergeSome(performance.now() + (options.budgetMs ?? Number.POSITIVE_INFINITY))\n const before = released\n inverse.copy(root.matrixWorld).invert()\n for (const source of sources) if (!source.released && hasChanged(source)) release(source)\n return released - before\n },\n stats: () => ({ batches: group.children.length, batched: sources.filter((source) => !source.released).length, released }),\n dispose() {\n for (const source of sources) if (!source.released) source.mesh.layers.mask = source.mask\n group.removeFromParent()\n for (const child of group.children) {\n ;(child as Mesh).geometry.dispose()\n ;(child as SkinnedMesh).skeleton?.dispose()\n }\n },\n }\n}\n\n/** Copies `attribute` into `target` at `offset` vertices: positions through `matrix`, normals through its normal matrix, the rest as they are. */\nfunction copyAttribute(name: string, attribute: BufferAttribute, target: Float32Array, offset: number, matrix: Matrix4, normalMatrix: Matrix3): void {\n const size = attribute.itemSize\n const source = attribute.array as ArrayLike<number>\n const flat = !(attribute as { isInterleavedBufferAttribute?: boolean }).isInterleavedBufferAttribute && source.length === attribute.count * size\n if (name !== 'position' && name !== 'normal') {\n if (flat) target.set(source as Float32Array, offset * size)\n else for (let at = 0; at < attribute.count; at++) for (let k = 0; k < size; k++) target[(offset + at) * size + k] = attribute.getComponent(at, k)\n return\n }\n const e = (name === 'position' ? matrix : normalMatrix).elements\n const isNormal = name === 'normal'\n for (let at = 0; at < attribute.count; at++) {\n const x = attribute.getX(at)\n const y = attribute.getY(at)\n const z = attribute.getZ(at)\n const out = (offset + at) * 3\n if (isNormal) {\n const nx = e[0]! * x + e[3]! * y + e[6]! * z\n const ny = e[1]! * x + e[4]! * y + e[7]! * z\n const nz = e[2]! * x + e[5]! * y + e[8]! * z\n const length = Math.hypot(nx, ny, nz) || 1\n target[out] = nx / length\n target[out + 1] = ny / length\n target[out + 2] = nz / length\n } else {\n target[out] = e[0]! * x + e[4]! * y + e[8]! * z + e[12]!\n target[out + 1] = e[1]! * x + e[5]! * y + e[9]! * z + e[13]!\n target[out + 2] = e[2]! * x + e[6]! * y + e[10]! * z + e[14]!\n }\n }\n}\n\ntype MergeGroupParams = { readonly meshes: readonly Mesh[]; readonly inverse: Matrix4; readonly group: Object3D; readonly follow: boolean }\n\n/** A skinned batch whose bone `n` is mesh `n` itself (no inverse, no bind offset): every vertex lands where its own mesh puts it. */\nfunction skinnedBatch(THREE: ThreeLib, merged: BufferGeometry, material: Material, meshes: readonly Mesh[]): Mesh {\n const vertices = (merged.attributes.position as BufferAttribute).count\n const skinIndex = new Uint16Array(vertices * 4)\n const skinWeight = new Float32Array(vertices * 4)\n let at = 0\n meshes.forEach((mesh, bone) => {\n for (let k = 0; k < (mesh.geometry.attributes.position as BufferAttribute).count; k++, at++) {\n skinIndex[at * 4] = bone\n skinWeight[at * 4] = 1\n }\n })\n merged.setAttribute('skinIndex', new THREE.BufferAttribute(skinIndex, 4))\n merged.setAttribute('skinWeight', new THREE.BufferAttribute(skinWeight, 4))\n const mesh = new THREE.SkinnedMesh(merged, material)\n const identities = meshes.map(() => new THREE.Matrix4())\n mesh.bind(new THREE.Skeleton(meshes as unknown as Bone[], identities), new THREE.Matrix4())\n // the parts move apart as the body is posed: the rest-pose bounds would cull the moved ones\n mesh.frustumCulled = false\n return mesh\n}\n\nfunction mergeGroup(THREE: ThreeLib, { meshes, inverse: rootInverse, group, follow }: MergeGroupParams): Source[] {\n const identity = new THREE.Matrix4()\n const inverse = follow ? identity : rootInverse\n const first = meshes[0] as Mesh\n const names = ATTRIBUTES.filter((name) => first.geometry.attributes[name] !== undefined)\n const pieces = meshes.map((mesh) => {\n const local = new THREE.Matrix4().multiplyMatrices(inverse, mesh.matrixWorld)\n const vertices = (mesh.geometry.attributes.position as BufferAttribute).count\n const indexCount = mesh.geometry.index ? mesh.geometry.index.count : vertices\n return { mesh, local, vertices, indexCount }\n })\n const totalVertices = pieces.reduce((sum, piece) => sum + piece.vertices, 0)\n const totalIndices = pieces.reduce((sum, piece) => sum + piece.indexCount, 0)\n const merged = new THREE.BufferGeometry()\n const normalMatrix = new THREE.Matrix3()\n for (const name of names) {\n const size = first.geometry.attributes[name]!.itemSize\n const array = new Float32Array(totalVertices * size)\n let offset = 0\n for (const piece of pieces) {\n const matrix = follow ? identity : piece.local\n copyAttribute(name, piece.mesh.geometry.attributes[name] as BufferAttribute, array, offset, matrix, normalMatrix.getNormalMatrix(matrix))\n offset += piece.vertices\n }\n merged.setAttribute(name, new THREE.BufferAttribute(array, size))\n }\n const indexArray = totalVertices > 65535 ? new Uint32Array(totalIndices) : new Uint16Array(totalIndices)\n const indexAttribute = new THREE.BufferAttribute(indexArray, 1)\n merged.setIndex(indexAttribute)\n const mesh = follow ? skinnedBatch(THREE, merged, first.material as Material, meshes) : new THREE.Mesh(merged, first.material)\n mesh.name = `batch:${(first.material as Material).name || (first.material as Material).type}`\n mesh.castShadow = first.castShadow\n mesh.receiveShadow = first.receiveShadow\n mesh.raycast = () => undefined\n merged.computeBoundingBox()\n merged.computeBoundingSphere()\n group.add(mesh)\n const batch: Batch = { mesh, live: pieces.length, index: indexAttribute as unknown as Batch['index'] }\n const sources: Source[] = []\n let vertexOffset = 0\n let indexOffset = 0\n for (const piece of pieces) {\n const index = piece.mesh.geometry.index\n if (index) for (let at = 0; at < piece.indexCount; at++) indexArray[indexOffset + at] = index.getX(at) + vertexOffset\n else for (let at = 0; at < piece.indexCount; at++) indexArray[indexOffset + at] = at + vertexOffset\n sources.push({\n mesh: piece.mesh,\n geometry: piece.mesh.geometry,\n material: piece.mesh.material as Material,\n version: (piece.mesh.geometry.attributes.position as BufferAttribute).version,\n local: piece.local,\n world: piece.mesh.matrixWorld.clone(),\n mask: piece.mesh.layers.mask,\n batch,\n start: indexOffset,\n count: piece.indexCount,\n released: false,\n })\n piece.mesh.layers.set(BATCHED_LAYER)\n vertexOffset += piece.vertices\n indexOffset += piece.indexCount\n }\n return sources\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","/**\n * Every visible mesh under `root`, baked into `space` and joined per material. It lives in `@adatechnology/game-shapes-three` now (on the\n * game-shapes merge, the same bytes as the version this package had); this re-export keeps the old import working.\n */\nexport { mergeByMaterial } from '@adatechnology/game-shapes-three'\n","import type { GroupNode, ShapeNode, ShapeSchema, Vec3 } from '@adatechnology/game-shapes'\n\nimport type { CylinderShape, PropItem, PropsApi, Uv } from './types'\n\nexport interface ShapeRecorderOptions {\n /** Radial segments of `cyl` and `cone` (default 12). */\n readonly cylinderSegments?: number\n /** Radial segments of `cyl6` (default 6). */\n readonly hexSegments?: number\n /** Width and height segments of `sphere` (default 12 x 8). */\n readonly sphereSegments?: readonly [number, number]\n /** Name of the schema. */\n readonly name?: string\n}\n\n/** A part as the api received it: the first call's pivot and turn (later calls with the same id draw into it). */\nexport interface RecordedPart {\n readonly id: string\n readonly pivot: Vec3\n readonly ry: number\n}\n\n/** The props api plus parts (the `HouseholdApi` contract): a part draws in its own frame, at `pivot`, turned by `ry`. */\nexport interface ShapeRecorderApi extends PropsApi {\n light(M: PropItem, x: number, y: number, z: number, color: number, range: number, kind: string): void\n part(id: string, pivot: Vec3, ry: number, draw: () => void): void\n}\n\nexport interface ShapeRecorder {\n readonly api: ShapeRecorderApi\n /** The game-shapes schema of everything drawn so far. */\n schema(): ShapeSchema\n /** The parts in the order of their first call. */\n readonly parts: readonly RecordedPart[]\n}\n\n/** The double-sided diamond of `leaf`: base at the origin, tip at +y, in the xy plane (front face counter-clockwise seen from +z). */\nconst LEAF = (() => {\n const f = [0, 0, 0, -0.5, 0.4, 0, 0.5, 0.4, 0, 0, 1, 0]\n const positions: number[] = []\n const normals: number[] = []\n const uvs: number[] = []\n const indices: number[] = []\n for (const side of [1, -1]) {\n const base = positions.length / 3\n for (let i = 0; i < 4; i++) {\n positions.push(f[i * 3] as number, f[i * 3 + 1] as number, f[i * 3 + 2] as number)\n normals.push(0, 0, side)\n uvs.push((f[i * 3] as number) + 0.5, f[i * 3 + 1] as number)\n }\n if (side === 1) indices.push(base, base + 2, base + 1, base + 1, base + 2, base + 3)\n else indices.push(base, base + 1, base + 2, base + 1, base + 3, base + 2)\n }\n return { positions, normals, uvs, indices }\n})()\n\n/**\n * An api that turns what a builder draws into a `game-shapes` schema, with the arithmetic of the interactables mesh api: each primitive\n * under its item frame (`heading` = `M.ry`, at `M.x, 0, M.z`), placed by `compose(position, euler YXZ, scale)` over a unit template (a box\n * with its base at `y`, a cylinder centred as the api says, a sphere of radius `h / 2`, the double-sided leaf, a unit plane with the atlas\n * cell as its uv), parts as part nodes, lights as points (`data: { light: true, color, range, kind }`).\n *\n * ```ts\n * const recorder = createShapeRecorder()\n * props.builders.sofa_l(item, recorder.api)\n * const buffers = generate(recorder.schema(), { validate: false, key: false })\n * ```\n */\nexport function createShapeRecorder(options: ShapeRecorderOptions = {}): ShapeRecorder {\n const cyl = options.cylinderSegments ?? 12\n const hex = options.hexSegments ?? 6\n const [sphereW, sphereH] = options.sphereSegments ?? [12, 8]\n const root: ShapeNode[] = []\n const parts: RecordedPart[] = []\n const partChildren = new Map<string, ShapeNode[]>()\n let current = root\n // consecutive primitives of the same item frame share one frame group (one matrix, the same products)\n let lastFrame: { list: ShapeNode[]; group: GroupNode & { children: ShapeNode[] }; x: number; z: number; ry: number } | null = null\n\n const into = (M: PropItem | undefined): ShapeNode[] => {\n const x = M?.x ?? 0\n const z = M?.z ?? 0\n const ry = M?.ry ?? 0\n const last = lastFrame\n if (last && last.list === current && current[current.length - 1] === last.group && Object.is(last.x, x) && Object.is(last.z, z) && Object.is(last.ry, ry)) return last.group.children\n const group = { kind: 'group' as const, position: [x, 0, z] as Vec3, heading: ry, children: [] as ShapeNode[] }\n current.push(group)\n lastFrame = { list: current, group, x, z, ry }\n return group.children\n }\n\n const api: ShapeRecorderApi = {\n box(M, material, x, y, z, w, h, d, color, ry, rx, rz) {\n into(M).push({ kind: 'box', material, color, position: [x, y + h / 2, z], rotation: [rx, ry, rz], rotationOrder: 'YXZ', scale: [w, h, d] })\n },\n cylinder(M, material, x, y, z, r, h, color, rx, rz, shape?: CylinderShape) {\n const tilted = rx !== 0 || rz !== 0\n const cy = shape === 'sphere' ? y + h / 2 : tilted ? y : y + h / 2\n const sy = shape === 'sphere' ? h / 2 : h\n const place = { material, color, position: [x, cy, z] as Vec3, rotation: [rx, 0, rz] as Vec3, rotationOrder: 'YXZ' as const, scale: [r, sy, r] as Vec3 }\n const list = into(M)\n switch (shape) {\n case 'cyl6':\n list.push({ kind: 'cylinder', radiusTop: 1, radiusBottom: 1, height: 1, radialSegments: hex, ...place })\n break\n case 'cone':\n list.push({ kind: 'cylinder', radiusTop: 0, radiusBottom: 1, height: 1, radialSegments: cyl, ...place })\n break\n case 'sphere':\n list.push({ kind: 'sphere', radius: 1, widthSegments: sphereW, heightSegments: sphereH, ...place })\n break\n default:\n list.push({ kind: 'cylinder', radiusTop: 1, radiusBottom: 1, height: 1, radialSegments: cyl, ...place })\n }\n },\n leaf(M, x, y, z, w, h, color, ry, rx) {\n into(M).push({ kind: 'mesh', ...LEAF, material: 'plant', color, position: [x, y, z], rotation: [rx, ry, 0], rotationOrder: 'YXZ', scale: [w, h, 1] })\n },\n plane(M, material, x, y, z, w, h, color, ry, rx, rz, uv?: Uv) {\n into(M).push({\n kind: 'plane',\n width: 1,\n height: 1,\n material,\n color,\n position: [x, y, z],\n rotation: [rx, ry, rz],\n rotationOrder: 'YXZ',\n scale: [w, h, 1],\n ...(uv ? { uv: { u0: uv.u0, v0: uv.v0, u1: uv.u1, v1: uv.v1 } } : {}),\n })\n },\n light(M, x, y, z, color, range, kind) {\n into(M).push({ kind: 'point', position: [x, y, z], data: { light: true, color, range, kind } })\n },\n part(id, pivot, ry, draw) {\n let children = partChildren.get(id)\n if (!children) {\n children = []\n partChildren.set(id, children)\n parts.push({ id, pivot: [pivot[0], pivot[1], pivot[2]], ry })\n current.push({ kind: 'group', part: id, position: [pivot[0], pivot[1], pivot[2]], rotation: [0, ry, 0], children })\n }\n const outer = current\n current = children\n try {\n draw()\n } finally {\n current = outer\n }\n },\n }\n\n return {\n api,\n parts,\n schema: () => ({ version: 1, ...(options.name !== undefined ? { name: options.name } : {}), root: { kind: 'group', children: root } }),\n }\n}\n","import type * as ThreeNS from 'three'\nimport type { PropsApi } from '@adatechnology/game-props3d'\n\n/** The host's `THREE` namespace (or any object with the same classes). Always passed in, never imported at runtime. */\nexport type ThreeLib = typeof ThreeNS\nexport type { ThreeNS }\n\nexport type Vec3 = [number, number, number]\n\n/** Material keys the mesh api understands (same as `PropsApi` materials). */\nexport type MaterialKey = 'plain' | 'metal' | 'wood' | 'glass' | 'fabric' | 'plant' | 'water' | 'emissive' | 'atlas_picture' | 'atlas_floor'\n\nexport const MATERIAL_KEYS: readonly MaterialKey[] = ['plain', 'metal', 'wood', 'glass', 'fabric', 'plant', 'water', 'emissive', 'atlas_picture', 'atlas_floor']\n\nexport type Quality = 'high' | 'game'\n\n/**\n * What content packs draw through: the props primitives plus `part`.\n * `part(id, pivot, ry, draw)` runs `draw()` and routes every primitive drawn inside it into the movable part `id`.\n * `pivot` is object-local; the primitives drawn inside `draw` are in the part frame (relative to the pivot, turned by `ry`): the part's\n * group origin sits at `pivot`, turned by `ry` around +y.\n */\nexport interface HouseholdApi extends PropsApi {\n part(id: string, pivot: Vec3, ry: number, draw: () => void): void\n}\n\n/** Anything that can create a canvas (the global `document`, or a fake in tests). */\nexport interface CanvasFactory {\n createElement(tag: 'canvas'): HTMLCanvasElement\n}\n","import type { ColliderOptions, WorldCollider } from './types'\n\ninterface Box {\n ids: string[]\n object: string\n u0: number\n u1: number\n v0: number\n v1: number\n y0: number\n y1: number\n}\n\nconst volume = (b: Box): number => (b.u1 - b.u0) * (b.v1 - b.v0) * (b.y1 - b.y0)\nconst overlap = (a0: number, a1: number, b0: number, b1: number): number => Math.max(0, Math.min(a1, b1) - Math.max(a0, b0))\n\nfunction tryMerge(a: Box, b: Box, gap: number, slack: number): Box | null {\n if (a.u0 - gap > b.u1 || b.u0 - gap > a.u1) return null\n if (a.v0 - gap > b.v1 || b.v0 - gap > a.v1) return null\n if (a.y0 - gap > b.y1 || b.y0 - gap > a.y1) return null\n const u0 = Math.min(a.u0, b.u0)\n const u1 = Math.max(a.u1, b.u1)\n const v0 = Math.min(a.v0, b.v0)\n const v1 = Math.max(a.v1, b.v1)\n const y0 = Math.min(a.y0, b.y0)\n const y1 = Math.max(a.y1, b.y1)\n const hull = (u1 - u0) * (v1 - v0) * (y1 - y0)\n const common = overlap(a.u0, a.u1, b.u0, b.u1) * overlap(a.v0, a.v1, b.v0, b.v1) * overlap(a.y0, a.y1, b.y0, b.y1)\n const real = volume(a) + volume(b) - common\n if (hull > real * (1 + slack) + 1e-9) return null\n return { ids: [...a.ids, ...b.ids], object: a.object, u0, u1, v0, v1, y0, y1 }\n}\n\n/**\n * Union overlapping or adjacent colliders of the same object (same rotation) into fewer OBBs.\n * A merge happens only when the merged box adds at most `slack` extra volume over the real union, so coverage is never lost\n * and never grows noticeably. Colliders of different objects or rotations are left alone.\n */\nexport function mergeColliders(list: WorldCollider[], options: Pick<ColliderOptions, 'gap' | 'slack'> = {}): WorldCollider[] {\n const gap = options.gap ?? 0.02\n const slack = options.slack ?? 0.1\n const groups = new Map<string, WorldCollider[]>()\n for (const c of list) {\n const key = `${c.object}|${Math.round(c.ry * 1000)}`\n const g = groups.get(key)\n if (g) g.push(c)\n else groups.set(key, [c])\n }\n const out: WorldCollider[] = []\n for (const g of groups.values()) {\n const ry = g[0]?.ry ?? 0\n const cs = Math.cos(ry)\n const sn = Math.sin(ry)\n let boxes: Box[] = g.map((c) => {\n const u = c.cx * cs - c.cz * sn\n const v = c.cx * sn + c.cz * cs\n return { ids: [c.id], object: c.object, u0: u - c.hw, u1: u + c.hw, v0: v - c.hd, v1: v + c.hd, y0: c.y0, y1: c.y1 }\n })\n let changed = boxes.length > 1\n while (changed) {\n changed = false\n outer: for (let i = 0; i < boxes.length; i++) {\n for (let j = i + 1; j < boxes.length; j++) {\n const m = tryMerge(boxes[i] as Box, boxes[j] as Box, gap, slack)\n if (!m) continue\n boxes = boxes.filter((_, k) => k !== i && k !== j)\n boxes.push(m)\n changed = true\n break outer\n }\n }\n }\n for (const b of boxes) {\n const u = (b.u0 + b.u1) / 2\n const v = (b.v0 + b.v1) / 2\n out.push({\n id: b.ids.length === 1 ? (b.ids[0] as string) : `${b.ids[0]}+${b.ids.length - 1}`,\n object: b.object,\n cx: u * cs + v * sn,\n cz: -u * sn + v * cs,\n hw: (b.u1 - b.u0) / 2,\n hd: (b.v1 - b.v0) / 2,\n ry,\n y0: b.y0,\n y1: b.y1,\n })\n }\n }\n return out\n}\n","/** FNV-1a hash of a string to a 32-bit unsigned integer. */\nexport function hashString(text: string): number {\n let h = 2166136261\n for (let i = 0; i < text.length; i++) {\n h ^= text.charCodeAt(i)\n h = Math.imul(h, 16777619)\n }\n return h >>> 0\n}\n\n/** Deterministic random generator (mulberry32). Returns a function producing floats in [0, 1). Numbers and strings are accepted as seeds. */\nexport function rng(seed: number | string = 1): () => number {\n let a = (typeof seed === 'number' ? Math.floor(seed) >>> 0 : hashString(seed)) || 0x9e3779b9\n return () => {\n a = (a + 0x6d2b79f5) >>> 0\n let t = a\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","import { rng } from './rng'\nimport type { ItemCatalog, ItemStack, LootTables, SlotSpec, Vec3 } from './types'\n\n/** True when an item of `size` [w, h, d] fits in a slot of `max`; width and depth may be swapped (the item is turned). */\nexport function fitsSize(size: Vec3, max: Vec3): boolean {\n const eps = 1e-9\n if (size[1] > max[1] + eps) return false\n return (size[0] <= max[0] + eps && size[2] <= max[2] + eps) || (size[2] <= max[0] + eps && size[0] <= max[2] + eps)\n}\n\n/** Does the item type fit the slot. Types missing from the catalog are accepted (the host owns unknown items). */\nexport function fitsSlot(type: string, slot: SlotSpec, items?: ItemCatalog): boolean {\n const def = items?.[type]\n return def ? fitsSize(def.size, slot.max) : true\n}\n\n/**\n * Deterministic contents for a container: one entry per slot. A slot is filled with probability `fill`; the entry is a weighted pick\n * among the table entries whose item fits the slot. Same seed, same table and same slots always give the same result.\n */\nexport function generateLoot(\n tables: LootTables | undefined,\n tableId: string,\n slots: SlotSpec[],\n seed: number | string,\n items?: ItemCatalog,\n): Array<ItemStack | null> {\n const table = tables?.[tableId]\n const out: Array<ItemStack | null> = slots.map(() => null)\n if (!table) return out\n const rand = rng(seed)\n slots.forEach((slot, i) => {\n // Always draw the same number of random values per slot so a change in one slot does not shift the others.\n const roll = rand()\n const pick = rand()\n const q = rand()\n if (roll >= table.fill) return\n const candidates = table.entries.filter((e) => e.weight > 0 && fitsSlot(e.type, slot, items))\n const total = candidates.reduce((s, e) => s + e.weight, 0)\n if (total <= 0) return\n let r = pick * total\n let chosen = candidates[candidates.length - 1]\n for (const e of candidates) {\n r -= e.weight\n if (r < 0) {\n chosen = e\n break\n }\n }\n const [lo, hi] = chosen.qty ?? [1, 1]\n const def = items?.[chosen.type]\n const stackMax = def ? (def.stack ?? 1) : Number.POSITIVE_INFINITY\n const qty = Math.max(1, Math.min(stackMax, lo + Math.floor(q * (hi - lo + 1))))\n out[i] = { type: chosen.type, qty }\n })\n return out\n}\n","import type { InteractablesLocale, Strings } from './types'\n\n/** Engine-level prompts and messages. Content packs add their own keys through `WorldOptions.strings`. */\nexport const locales: Record<InteractablesLocale, Strings> = {\n en: {\n 'prompt.open': 'Open {name}',\n 'prompt.close': 'Close {name}',\n 'prompt.turn_on': 'Turn on {name}',\n 'prompt.turn_off': 'Turn off {name}',\n 'prompt.take': 'Take',\n 'prompt.sit': 'Sit',\n 'prompt.lie_down': 'Lie down',\n 'prompt.get_up': 'Get up',\n 'prompt.light': 'Light',\n 'prompt.change_channel': 'Change channel',\n 'prompt.read': 'Read',\n 'prompt.breaker_on': 'Switch on {circuit}',\n 'prompt.breaker_off': 'Switch off {circuit}',\n 'prompt.open_valve': 'Open valve',\n 'prompt.close_valve': 'Close valve',\n no_power: 'No power',\n no_gas: 'No gas',\n no_water: 'No water',\n locked: 'Locked',\n requires_open: 'Open it first',\n already_lit: 'Already lit',\n needs_ignition: 'Light it first',\n turn_on_first: 'Turn it on first',\n empty: 'Empty',\n full: 'Full',\n },\n 'pt-BR': {\n 'prompt.open': 'Abrir {name}',\n 'prompt.close': 'Fechar {name}',\n 'prompt.turn_on': 'Ligar {name}',\n 'prompt.turn_off': 'Desligar {name}',\n 'prompt.take': 'Pegar',\n 'prompt.sit': 'Sentar',\n 'prompt.lie_down': 'Deitar',\n 'prompt.get_up': 'Levantar',\n 'prompt.light': 'Acender',\n 'prompt.change_channel': 'Trocar de canal',\n 'prompt.read': 'Ler',\n 'prompt.breaker_on': 'Ligar {circuit}',\n 'prompt.breaker_off': 'Desligar {circuit}',\n 'prompt.open_valve': 'Abrir registro',\n 'prompt.close_valve': 'Fechar registro',\n no_power: 'Sem energia',\n no_gas: 'Sem gás',\n no_water: 'Sem água',\n locked: 'Trancado',\n requires_open: 'Abra primeiro',\n already_lit: 'Já está aceso',\n needs_ignition: 'Acenda primeiro',\n turn_on_first: 'Ligue primeiro',\n empty: 'Vazio',\n full: 'Cheio',\n },\n}\n\n/** Pick a built-in locale for an arbitrary tag (`pt`, `pt-br`, `en-GB`...). Unknown tags give `en`. */\nexport function pickLocale(locale: string | undefined): InteractablesLocale {\n if (!locale) return 'en'\n const l = locale.toLowerCase()\n if (l === 'pt-br' || l === 'pt') return 'pt-BR'\n return 'en'\n}\n\n/**\n * Merged table for a locale: built-in `en`, then the built-in table of `locale`, then the extra strings for `en` and for `locale`.\n * Lookup therefore falls back to English, and the caller never gets undefined for engine keys.\n */\nexport function resolveStrings(locale?: string, extra?: Partial<Record<InteractablesLocale, Strings>>): Strings {\n const loc = pickLocale(locale)\n return { ...locales.en, ...locales[loc], ...(extra?.en ?? {}), ...(loc === 'en' ? {} : (extra?.[loc] ?? {})) }\n}\n\n/** Replace `{name}` placeholders. Unknown placeholders are left untouched. */\nexport function format(text: string, vars?: Record<string, string | number>): string {\n if (!vars) return text\n return text.replace(/\\{(\\w+)\\}/g, (m, k: string) => (k in vars ? String(vars[k]) : m))\n}\n","import type { Motion, Transform, Vec3 } from './types'\n\nexport interface Pose {\n /** Translation relative to the pivot. */\n position: Vec3\n /** Rotation around `axis` (in the part frame) by `angle` radians. */\n rotation: { axis: Vec3; angle: number }\n scale: Vec3\n}\n\nconst DEFAULT_AXIS: Vec3 = [0, 1, 0]\n\n/**\n * Pose of a part at animation value `t` (0..1), relative to its pivot, so any renderer can apply it:\n * `position`, then `rotation` around the pivot, then `scale` along the axis.\n */\nexport function poseOf(motion: Motion, t: number): Pose {\n const tt = Math.max(0, Math.min(1, t))\n const axis = motion.axis ?? DEFAULT_AXIS\n const pose: Pose = { position: [0, 0, 0], rotation: { axis: [axis[0], axis[1], axis[2]], angle: 0 }, scale: [1, 1, 1] }\n switch (motion.kind) {\n case 'hinge':\n case 'rocker':\n case 'knob':\n pose.rotation.angle = (motion.angle ?? Math.PI / 2) * tt\n break\n case 'slide': {\n const d = (motion.distance ?? 0.4) * tt\n pose.position = [axis[0] * d, axis[1] * d, axis[2] * d]\n break\n }\n case 'scale': {\n const k = 1 + ((motion.scale ?? 0.2) - 1) * tt\n pose.scale = [axis[0] !== 0 ? k : 1, axis[1] !== 0 ? k : 1, axis[2] !== 0 ? k : 1]\n break\n }\n }\n return pose\n}\n\n/** Rotate a vector around +y by `ry` radians (same convention as three.js `rotation.y`). */\nexport function rotY(v: Vec3, ry: number): Vec3 {\n const c = Math.cos(ry)\n const s = Math.sin(ry)\n return [c * v[0] + s * v[2], v[1], -s * v[0] + c * v[2]]\n}\n\n/** Object-local point to world space. */\nexport function localToWorld(transform: Transform, v: Vec3): Vec3 {\n const r = rotY(v, transform.ry)\n return [transform.x + r[0], transform.y + r[1], transform.z + r[2]]\n}\n\n/** World point to object-local space. */\nexport function worldToLocal(transform: Transform, v: Vec3): Vec3 {\n return rotY([v[0] - transform.x, v[1] - transform.y, v[2] - transform.z], -transform.ry)\n}\n\n/** Rodrigues rotation of `v` around a unit-length-normalised `axis` by `angle`. */\nexport function rotateAxis(v: Vec3, axis: Vec3, angle: number): Vec3 {\n const len = Math.hypot(axis[0], axis[1], axis[2]) || 1\n const k: Vec3 = [axis[0] / len, axis[1] / len, axis[2] / len]\n const c = Math.cos(angle)\n const s = Math.sin(angle)\n const dot = k[0] * v[0] + k[1] * v[1] + k[2] * v[2]\n const cross: Vec3 = [k[1] * v[2] - k[2] * v[1], k[2] * v[0] - k[0] * v[2], k[0] * v[1] - k[1] * v[0]]\n return [v[0] * c + cross[0] * s + k[0] * dot * (1 - c), v[1] * c + cross[1] * s + k[1] * dot * (1 - c), v[2] * c + cross[2] * s + k[2] * dot * (1 - c)]\n}\n\nexport function clamp(v: number, lo: number, hi: number): number {\n return v < lo ? lo : v > hi ? hi : v\n}\n\nexport function dist3(a: Vec3, b: Vec3): number {\n return Math.hypot(a[0] - b[0], a[1] - b[1], a[2] - b[2])\n}\n","import { mergeColliders } from './colliders'\nimport { fitsSlot, generateLoot } from './loot'\nimport { format, resolveStrings } from './locale'\nimport { clamp, dist3, localToWorld, rotateAxis, rotY, worldToLocal } from './math'\nimport type {\n ActiveEmitter,\n BurnerSpec,\n ColliderOptions,\n DeviceSpec,\n FixtureSpec,\n FuelSourceSpec,\n Interactable,\n InteractionSpec,\n ItemStack,\n ObjectSpec,\n PartSpec,\n SeatResult,\n SoftSpec,\n TankSpec,\n TargetOptions,\n Transform,\n Vec3,\n World,\n WorldCollider,\n WorldEvent,\n WorldObject,\n WorldOptions,\n WorldState,\n} from './types'\n\n// ---------------------------------------------------------------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------------------------------------------------------------\n\nconst DEFAULT_ACTOR = 'player'\nconst DEFAULT_RADIUS = 1.2\nconst AUTO_RANGE = 2\nconst SMOKE_TAIL = 20\nconst FLUSH_SECONDS = 2\nconst ACTOR_HEIGHT = 1.7\nconst PUSH_GAIN = 1.2\nconst SWEEP_STEPS = 24\nconst FLOW_DEFAULTS: Record<string, number> = { tap: 6, shower: 8, utility_sink: 6, flush: 0, hose: 10, filter: 1 }\n\n// ---------------------------------------------------------------------------------------------------------------------------------\n// Internal state\n// ---------------------------------------------------------------------------------------------------------------------------------\n\ninterface PartState {\n spec: PartSpec\n t: number\n target: number\n maxT: number\n}\n\ninterface DeviceState {\n spec: DeviceSpec\n on: boolean\n channel: number\n}\n\ninterface FuelState {\n spec: FuelSourceSpec\n level: number\n}\n\ninterface BurnerState {\n spec: BurnerSpec\n on: boolean\n lit: boolean\n /** Seconds of smoke left after a solid-fuel fire went out. */\n smoke: number\n}\n\ninterface TankState {\n spec: TankSpec\n level: number\n}\n\ninterface FixtureState {\n spec: FixtureSpec\n on: boolean\n flowing: boolean\n /** Delivered / wanted in the last step, 0..1. */\n frac: number\n /** Seconds left of a flush. */\n timer: number\n}\n\ninterface SoftState {\n spec: SoftSpec\n ax: number\n az: number\n vax: number\n vaz: number\n phase: number\n}\n\ninterface DamageState {\n max: number\n hp: number\n}\n\ninterface ObjState {\n id: string\n public: WorldObject\n spec: ObjectSpec\n parts: Map<string, PartState>\n contents: Map<string, Array<ItemStack | null>>\n devices: Map<string, DeviceState>\n fuels: Map<string, FuelState>\n burners: Map<string, BurnerState>\n tanks: Map<string, TankState>\n fixtures: Map<string, FixtureState>\n soft: Map<string, SoftState>\n hp: Map<string, DamageState>\n interactions: InteractionSpec[]\n}\n\nfunction cloneStack(s: ItemStack | null): ItemStack | null {\n return s ? { type: s.type, qty: s.qty, ...(s.data ? { data: JSON.parse(JSON.stringify(s.data)) as Record<string, unknown> } : {}) } : null\n}\n\nfunction humanize(id: string): string {\n const s = id.replace(/[_-]+/g, ' ').trim()\n return s.charAt(0).toUpperCase() + s.slice(1)\n}\n\n// ---------------------------------------------------------------------------------------------------------------------------------\n// World\n// ---------------------------------------------------------------------------------------------------------------------------------\n\nexport function createWorld(options: WorldOptions = {}): World {\n const seed = options.seed ?? 1\n const objs = new Map<string, ObjState>()\n const breakers = new Map<string, boolean>()\n const seats = new Map<string, { object: string; seat: string }>()\n const plugs = new Map<string, string | null>()\n let locale = options.locale ?? 'en'\n let table = resolveStrings(locale, options.strings)\n let grid = options.grid ?? true\n let rain = options.rain ?? 0\n let wind: [number, number] = options.wind ? [options.wind[0], options.wind[1]] : [0, 0]\n let defaultTank: string | null = null\n let defaultTankExplicit = false\n let time = 0\n\n // ------------------------------------------------------------------ text & events\n function t(key: string, vars?: Record<string, string | number>): string {\n return format(table[key] ?? key, vars)\n }\n\n function emit(e: WorldEvent): void {\n options.onEvent?.(e)\n }\n\n function message(key: string): void {\n emit({ type: 'message', key, text: t(key) })\n }\n\n // ------------------------------------------------------------------ lookup helpers\n function gid(o: ObjState, local: string): string {\n return `${o.id}:${local}`\n }\n\n /** Find the first entity matching a world-wide id `objectId:localId`. */\n function findGlobal<T>(id: string, pick: (o: ObjState, local: string) => T | undefined): { o: ObjState; value: T; local: string } | null {\n for (const o of objs.values()) {\n if (!id.startsWith(o.id + ':')) continue\n const local = id.slice(o.id.length + 1)\n const value = pick(o, local)\n if (value !== undefined) return { o, value, local }\n }\n return null\n }\n\n function objectOrThrow(id: string): ObjState {\n const o = objs.get(id)\n if (!o) throw new Error(`Unknown object \"${id}\"`)\n return o\n }\n\n function originOf(o: ObjState): Vec3 {\n const tr = o.public.transform\n return [tr.x, tr.y, tr.z]\n }\n\n function toWorld(o: ObjState, v: Vec3): Vec3 {\n return localToWorld(o.public.transform, v)\n }\n\n // ------------------------------------------------------------------ parts\n function openThreshold(p: PartState): number {\n return Math.min(0.5, p.maxT * 0.9)\n }\n\n function isOpen(o: ObjState, partId: string | undefined): boolean {\n if (!partId) return true\n const p = o.parts.get(partId)\n if (!p) return true\n return p.maxT >= 0.1 && p.t >= openThreshold(p)\n }\n\n function targetIsOpen(p: PartState): boolean {\n return p.maxT >= 0.1 && p.target >= openThreshold(p)\n }\n\n function writeT(o: ObjState, p: PartState, v: number): void {\n p.t = v\n o.public.parts[p.spec.id] = v\n }\n\n function gated(o: ObjState, p: PartState): boolean {\n return !!p.spec.requires && o.parts.has(p.spec.requires) && !isOpen(o, p.spec.requires)\n }\n\n function setOnePart(o: ObjState, p: PartState, target: number, instant: boolean): void {\n const next = clamp(target, 0, p.maxT)\n const changed = next !== p.target\n p.target = next\n if (instant) writeT(o, p, next)\n if (changed || instant) emit({ type: 'part', object: o.id, part: p.spec.id, t: p.t, target: p.target })\n }\n\n function setPartTarget(o: ObjState, partId: string, target: number, instant = false): void {\n const p = o.parts.get(partId)\n if (!p) return\n const group = p.spec.group\n const members = group ? [...o.parts.values()].filter((q) => q.spec.group === group) : [p]\n for (const q of members) {\n setOne(o, q, target, instant)\n }\n }\n\n function setOne(o: ObjState, p: PartState, target: number, instant: boolean): void {\n setOnePart(o, p, target, instant)\n // a part that other parts require closes them with it (the drawer behind a door closes with the door)\n if (!targetIsOpen(p)) {\n for (const q of o.parts.values()) {\n if (q.spec.requires === p.spec.id && q.target > 0) setPartTarget(o, q.spec.id, 0, instant)\n }\n }\n }\n\n /** Frame of a part right now: rotation (axis in object-local, angle) and translation, both about its pivot. */\n function partFrame(o: ObjState, partId: string | undefined): { pivot: Vec3; axis: Vec3; angle: number; offset: Vec3 } | null {\n if (!partId) return null\n const p = o.parts.get(partId)\n if (!p) return null\n const m = p.spec.motion\n const axis = rotY(m.axis ?? [0, 1, 0], p.spec.ry ?? 0)\n const frame = { pivot: p.spec.pivot, axis, angle: 0, offset: [0, 0, 0] as Vec3 }\n if (m.kind === 'slide') {\n const d = (m.distance ?? 0.4) * p.t\n frame.offset = [axis[0] * d, axis[1] * d, axis[2] * d]\n } else if (m.kind !== 'scale') {\n frame.angle = (m.angle ?? Math.PI / 2) * p.t\n }\n return frame\n }\n\n function applyPart(o: ObjState, partId: string | undefined, local: Vec3): Vec3 {\n const f = partFrame(o, partId)\n if (!f) return local\n const rel: Vec3 = [local[0] - f.pivot[0], local[1] - f.pivot[1], local[2] - f.pivot[2]]\n const r = f.angle !== 0 ? rotateAxis(rel, f.axis, f.angle) : rel\n return [f.pivot[0] + r[0] + f.offset[0], f.pivot[1] + r[1] + f.offset[1], f.pivot[2] + r[2] + f.offset[2]]\n }\n\n /** Moves the full closed-pose slab of a part and returns the largest t whose pose keeps every sampled point clear. */\n function fitBounds(o: ObjState, p: PartState, b: NonNullable<PartSpec['bounds']>, blockedAt: (x: number, z: number) => boolean): number {\n const STEP = 0.05\n const ANGLES = 48\n const m = p.spec.motion\n const axis = rotY(m.axis ?? [0, 1, 0], p.spec.ry ?? 0)\n const lo: Vec3 = [Math.min(b[0], b[3]), Math.min(b[1], b[4]), Math.min(b[2], b[5])]\n const hi: Vec3 = [Math.max(b[0], b[3]), Math.max(b[1], b[4]), Math.max(b[2], b[5])]\n const axisPoints = (i: number): number[] => {\n const n = Math.max(1, Math.ceil((hi[i] - lo[i]) / STEP))\n return Array.from({ length: n + 1 }, (_, k) => lo[i] + ((hi[i] - lo[i]) * k) / n)\n }\n const xs = axisPoints(0)\n const ys = axisPoints(1)\n const zs = axisPoints(2)\n // `blocked` only sees x/z, so only the floor footprint matters. About a vertical axis (and for slides) y never changes\n // the footprint, so the x/z outline is enough; otherwise the footprint boundary is the projection of the box edges.\n const flat = m.kind === 'slide' || Math.abs(axis[1]) > 0.999\n const seen = new Set<string>()\n const pts: Vec3[] = []\n const add = (x: number, y: number, z: number): void => {\n const key = `${x.toFixed(5)},${y.toFixed(5)},${z.toFixed(5)}`\n if (seen.has(key)) return\n seen.add(key)\n pts.push([x, y, z])\n }\n if (flat) {\n for (const x of xs) for (const z of [lo[2], hi[2]]) add(x, lo[1], z)\n for (const z of zs) for (const x of [lo[0], hi[0]]) add(x, lo[1], z)\n } else {\n for (const x of xs) for (const y of [lo[1], hi[1]]) for (const z of [lo[2], hi[2]]) add(x, y, z)\n for (const y of ys) for (const x of [lo[0], hi[0]]) for (const z of [lo[2], hi[2]]) add(x, y, z)\n for (const z of zs) for (const x of [lo[0], hi[0]]) for (const y of [lo[1], hi[1]]) add(x, y, z)\n }\n const tr = o.public.transform\n const pivot = p.spec.pivot\n for (let i = 1; i <= ANGLES; i++) {\n const tt = i / ANGLES\n const angle = m.kind === 'slide' ? 0 : (m.angle ?? Math.PI / 2) * tt\n const d = m.kind === 'slide' ? (m.distance ?? 0.4) * tt : 0\n for (const [x, y, z] of pts) {\n let q: Vec3 = [x, y, z]\n if (angle !== 0) {\n const r = rotateAxis([x - pivot[0], y - pivot[1], z - pivot[2]], axis, angle)\n q = [pivot[0] + r[0], pivot[1] + r[1], pivot[2] + r[2]]\n }\n const w = localToWorld(tr, [q[0] + axis[0] * d, 0, q[2] + axis[2] * d])\n if (blockedAt(w[0], w[2])) return (i - 1) / ANGLES\n }\n }\n return 1\n }\n\n function fitSweep(objectId: string, partId: string, blocked: (x: number, z: number) => boolean): number {\n const o = objectOrThrow(objectId)\n const p = o.parts.get(partId)\n if (!p) throw new Error(`Unknown part \"${partId}\" on \"${objectId}\"`)\n const sweep = p.spec.sweep\n let result = 1\n const bounds = p.spec.bounds\n const mk = p.spec.motion.kind\n if (bounds && (mk === 'hinge' || mk === 'rocker' || mk === 'knob' || mk === 'slide')) {\n result = fitBounds(o, p, bounds, blocked)\n } else if (sweep) {\n const x0 = Math.min(sweep[0], sweep[2])\n const x1 = Math.max(sweep[0], sweep[2])\n const z0 = Math.min(sweep[1], sweep[3])\n const z1 = Math.max(sweep[1], sweep[3])\n const tr = o.public.transform\n const hit = (lx: number, lz: number): boolean => {\n const w = localToWorld(tr, [lx, 0, lz])\n return blocked(w[0], w[2])\n }\n const m = p.spec.motion\n const axis = rotY(m.axis ?? [0, 1, 0], p.spec.ry ?? 0)\n if (m.kind === 'hinge' || m.kind === 'rocker' || m.kind === 'knob') {\n if (Math.abs(axis[1]) >= 0.5) {\n const angle = m.angle ?? Math.PI / 2\n const total = -angle * Math.sign(axis[1])\n const [px, , pz] = p.spec.pivot\n const cx = (x0 + x1) / 2\n const cz = (z0 + z1) / 2\n const w = x1 - x0\n const d = z1 - z0\n const radius = Math.abs(angle) > 1.7 ? Math.min(w, d) : Math.max(w, d)\n const tol = 0.05\n const inRect = (x: number, z: number) => x >= x0 - tol && x <= x1 + tol && z >= z0 - tol && z <= z1 + tol\n const centered = Math.hypot(cx - px, cz - pz) < 1e-6\n const mid = Math.atan2(cz - pz, cx - px)\n for (let i = 1; i <= SWEEP_STEPS && result === 1; i++) {\n const tt = i / SWEEP_STEPS\n let isHit = false\n if (centered) {\n for (let x = x0; x <= x1 + 1e-9 && !isHit; x += 0.1) for (let z = z0; z <= z1 + 1e-9 && !isHit; z += 0.1) isHit = hit(x, z)\n } else {\n const phi = mid - total / 2 + total * tt\n const steps = Math.max(2, Math.ceil(radius / 0.1))\n for (let k = 1; k <= steps && !isHit; k++) {\n const r = (radius * k) / steps\n const x = px + Math.cos(phi) * r\n const z = pz + Math.sin(phi) * r\n if (inRect(x, z)) isHit = hit(x, z)\n }\n }\n if (isHit) result = (i - 1) / SWEEP_STEPS\n }\n }\n } else if (m.kind === 'slide') {\n const len = Math.hypot(axis[0], axis[2])\n if (len > 1e-6) {\n const dx = axis[0] / len\n const dz = axis[2] / len\n const proj = [x0 * dx + z0 * dz, x1 * dx + z0 * dz, x0 * dx + z1 * dz, x1 * dx + z1 * dz]\n const pmin = Math.min(...proj)\n const pmax = Math.max(...proj)\n const nx = Math.max(1, Math.ceil((x1 - x0) / 0.1))\n const nz = Math.max(1, Math.ceil((z1 - z0) / 0.1))\n for (let i = 1; i <= SWEEP_STEPS && result === 1; i++) {\n const limit = pmin + (pmax - pmin) * (i / SWEEP_STEPS)\n let isHit = false\n for (let a = 0; a <= nx && !isHit; a++) {\n for (let b = 0; b <= nz && !isHit; b++) {\n const x = x0 + ((x1 - x0) * a) / nx\n const z = z0 + ((z1 - z0) * b) / nz\n if (x * dx + z * dz <= limit + 1e-9 && hit(x, z)) isHit = true\n }\n }\n if (isHit) result = (i - 1) / SWEEP_STEPS\n }\n }\n }\n }\n p.maxT = result\n if (p.target > result) p.target = result\n if (p.t > result) writeT(o, p, result)\n return result\n }\n\n // ------------------------------------------------------------------ containers\n function containerOf(o: ObjState, id: string) {\n return o.spec.containers?.find((c) => c.id === id)\n }\n\n function accessible(objectId: string, containerId: string): boolean {\n const o = objs.get(objectId)\n if (!o) return false\n const c = containerOf(o, containerId)\n if (!c) return false\n return isOpen(o, c.access)\n }\n\n function contents(objectId: string, containerId: string): Array<ItemStack | null> {\n return (objs.get(objectId)?.contents.get(containerId) ?? []).map(cloneStack)\n }\n\n function take(objectId: string, containerId: string, slot: number, actor: string = DEFAULT_ACTOR): ItemStack | null {\n const o = objs.get(objectId)\n if (!o) return null\n const list = o.contents.get(containerId)\n if (!list || !accessible(objectId, containerId)) return null\n const stack = list[slot]\n if (!stack) return null\n const def = options.items?.[stack.type]\n if (def && def.pickable === false) return null\n list[slot] = null\n emit({ type: 'pickup', object: objectId, container: containerId, slot, stack: cloneStack(stack) as ItemStack, actor })\n return stack\n }\n\n function put(objectId: string, containerId: string, stack: ItemStack): number | null {\n const o = objs.get(objectId)\n if (!o) return null\n const c = containerOf(o, containerId)\n const list = o.contents.get(containerId)\n if (!c || !list || !accessible(objectId, containerId)) return null\n const maxStack = options.items?.[stack.type]?.stack ?? 1\n for (let i = 0; i < list.length; i++) {\n const cur = list[i]\n if (cur && cur.type === stack.type && !cur.data && !stack.data && cur.qty + stack.qty <= maxStack) {\n cur.qty += stack.qty\n return i\n }\n }\n for (let i = 0; i < list.length; i++) {\n if (!list[i] && fitsSlot(stack.type, c.slots[i], options.items)) {\n list[i] = cloneStack(stack)\n return i\n }\n }\n message('full')\n return null\n }\n\n function slotPosition(objectId: string, containerId: string, slot: number): Vec3 {\n const o = objectOrThrow(objectId)\n const c = containerOf(o, containerId)\n const s = c?.slots[slot]\n if (!c || !s) throw new Error(`Unknown slot ${slot} of \"${containerId}\" on \"${objectId}\"`)\n return toWorld(o, applyPart(o, s.part, s.pos))\n }\n\n // ------------------------------------------------------------------ power\n function rebuildPlugs(): void {\n plugs.clear()\n const sockets: Array<{ id: string; pos: Vec3; circuit?: string; local: string }> = []\n for (const o of objs.values()) {\n for (const s of o.spec.sockets ?? []) sockets.push({ id: gid(o, s.id), local: s.id, pos: toWorld(o, s.pos), circuit: s.circuit })\n }\n const used = new Set<string>()\n const autos: Array<{ o: ObjState; d: DeviceState }> = []\n for (const o of objs.values()) {\n for (const d of o.devices.values()) {\n const key = gid(o, d.spec.id)\n const want = d.spec.socket\n if (want === undefined || d.spec.circuit) {\n plugs.set(key, null)\n } else if (want === 'auto') {\n plugs.set(key, null)\n autos.push({ o, d })\n } else {\n const s = sockets.find((x) => x.id === want) ?? sockets.find((x) => x.id === gid(o, want)) ?? sockets.find((x) => x.local === want)\n plugs.set(key, s ? s.id : null)\n if (s) used.add(s.id)\n }\n }\n }\n for (const { o, d } of autos) {\n const from = d.spec.plug ? toWorld(o, d.spec.plug) : originOf(o)\n let best: (typeof sockets)[number] | null = null\n let bestD = AUTO_RANGE\n for (const s of sockets) {\n if (used.has(s.id)) continue\n const dd = dist3(from, s.pos)\n if (dd <= bestD) {\n best = s\n bestD = dd\n }\n }\n if (best) {\n used.add(best.id)\n plugs.set(gid(o, d.spec.id), best.id)\n }\n }\n }\n\n function breaker(circuit: string): boolean {\n return breakers.get(circuit) ?? true\n }\n\n function devicePowered(o: ObjState, d: DeviceState): boolean {\n if (d.spec.electric === false) return true\n if (!grid) return false\n if (d.spec.circuit) return breaker(d.spec.circuit)\n if (d.spec.socket === undefined) return false\n const sid = plugs.get(gid(o, d.spec.id))\n if (!sid) return false\n const found = findGlobal(sid, (oo, local) => oo.spec.sockets?.find((s) => s.id === local))\n if (!found) return false\n return found.value.circuit ? breaker(found.value.circuit) : true\n }\n\n function getDevice(objectId: string, deviceId: string): { o: ObjState; d: DeviceState } | null {\n const o = objs.get(objectId)\n const d = o?.devices.get(deviceId)\n return o && d ? { o, d } : null\n }\n\n function powerSnapshot(): Map<string, boolean> {\n const m = new Map<string, boolean>()\n for (const o of objs.values()) for (const d of o.devices.values()) m.set(gid(o, d.spec.id), d.on && devicePowered(o, d))\n return m\n }\n\n function emitPowerChanges(before: Map<string, boolean>): void {\n for (const o of objs.values()) {\n for (const d of o.devices.values()) {\n const key = gid(o, d.spec.id)\n const now = d.on && devicePowered(o, d)\n if (before.get(key) !== now && d.on) emit({ type: 'device', object: o.id, device: d.spec.id, on: d.on, powered: devicePowered(o, d), channel: d.spec.kind === 'screen' ? d.channel : undefined })\n }\n }\n }\n\n function setDeviceOn(o: ObjState, d: DeviceState, on: boolean): void {\n d.on = on\n if (d.spec.part) setPartTarget(o, d.spec.part, on ? 1 : 0)\n const powered = devicePowered(o, d)\n emit({ type: 'device', object: o.id, device: d.spec.id, on, powered, channel: d.spec.kind === 'screen' ? d.channel : undefined })\n if (on && !powered) message('no_power')\n }\n\n function setDevice(objectId: string, deviceId: string, on: boolean): boolean {\n const f = getDevice(objectId, deviceId)\n if (!f) return false\n if (f.d.on !== on) setDeviceOn(f.o, f.d, on)\n return true\n }\n\n function setFixture(objectId: string, fixtureId: string, on: boolean): boolean {\n const fx = objs.get(objectId)?.fixtures.get(fixtureId)\n if (!fx) return false\n if (fx.on !== on) toggleFixture(objs.get(objectId) as ObjState, fixtureId)\n return fx.on === on\n }\n\n function setBurner(objectId: string, burnerId: string, on: boolean): boolean {\n const o = objs.get(objectId)\n const b = o?.burners.get(burnerId)\n if (!o || !b) return false\n const isOn = b.on || b.lit\n if (on === isOn) return true\n if (!on) return toggleBurner(o, burnerId)\n return b.spec.needsIgnition ? ignite(o, burnerId) : toggleBurner(o, burnerId)\n }\n\n function resolveDevice(o: ObjState, id: string): { o: ObjState; d: DeviceState } | null {\n if (id.includes(':')) {\n const f = findGlobal(id, (oo, local) => oo.devices.get(local))\n return f ? { o: f.o, d: f.value } : null\n }\n const local = o.devices.get(id)\n if (local) return { o, d: local }\n for (const oo of objs.values()) {\n const d = oo.devices.get(id)\n if (d) return { o: oo, d }\n }\n return null\n }\n\n function switchIsOn(o: ObjState, spec: { part?: string; controls: string[] }): boolean {\n if (spec.part && o.parts.has(spec.part)) return targetIsOpen(o.parts.get(spec.part) as PartState)\n return spec.controls.some((id) => resolveDevice(o, id)?.d.on)\n }\n\n function toggleSwitch(o: ObjState, id: string): boolean {\n const sw = o.spec.switches?.find((s) => s.id === id)\n if (!sw) return false\n const on = !switchIsOn(o, sw)\n if (sw.part) setPartTarget(o, sw.part, on ? 1 : 0)\n let unpowered = false\n for (const ref of sw.controls) {\n const r = resolveDevice(o, ref)\n if (!r) continue\n r.d.on = on\n if (r.d.spec.part) setPartTarget(r.o, r.d.spec.part, on ? 1 : 0)\n const powered = devicePowered(r.o, r.d)\n if (on && !powered) unpowered = true\n emit({ type: 'device', object: r.o.id, device: r.d.spec.id, on, powered })\n }\n emit({ type: 'switch', object: o.id, switch: sw.id, on })\n if (unpowered) message('no_power')\n return true\n }\n\n // ------------------------------------------------------------------ fuel\n function resolveFuel(o: ObjState, b: BurnerState): { o: ObjState; f: FuelState } | null {\n const want = b.spec.fuel\n if (want === 'auto') {\n const from = toWorld(o, b.spec.pos)\n let best: { o: ObjState; f: FuelState } | null = null\n let bestD = AUTO_RANGE\n for (const oo of objs.values()) {\n for (const f of oo.fuels.values()) {\n if (f.spec.kind !== 'gas') continue\n const d = dist3(from, originOf(oo))\n if (d <= bestD) {\n best = { o: oo, f }\n bestD = d\n }\n }\n }\n return best\n }\n if (want.includes(':')) {\n const f = findGlobal(want, (oo, local) => oo.fuels.get(local))\n return f ? { o: f.o, f: f.value } : null\n }\n const local = o.fuels.get(want)\n return local ? { o, f: local } : null\n }\n\n function valveOpen(o: ObjState, f: FuelState): boolean {\n const v = f.spec.valve\n if (!v || !o.parts.has(v)) return true\n return targetIsOpen(o.parts.get(v) as PartState)\n }\n\n function canBurn(b: BurnerState, ref: { o: ObjState; f: FuelState } | null): boolean {\n return !!ref && ref.f.level > 0 && valveOpen(ref.o, ref.f)\n }\n\n function isSolid(ref: { f: FuelState } | null): boolean {\n return !!ref && ref.f.spec.kind !== 'gas'\n }\n\n function emitBurner(o: ObjState, b: BurnerState): void {\n emit({ type: 'burner', object: o.id, burner: b.spec.id, on: b.on, lit: b.lit })\n }\n\n function extinguish(o: ObjState, b: BurnerState, turnOff: boolean): void {\n const ref = resolveFuel(o, b)\n const wasLit = b.lit\n b.lit = false\n if (turnOff) b.on = false\n if (b.spec.part) setPartTarget(o, b.spec.part, b.on ? 1 : 0)\n if (wasLit && isSolid(ref)) b.smoke = SMOKE_TAIL\n emitBurner(o, b)\n }\n\n function toggleBurner(o: ObjState, id: string): boolean {\n const b = o.burners.get(id)\n if (!b) return false\n if (b.on || b.lit) {\n extinguish(o, b, true)\n return true\n }\n if (b.spec.needsIgnition) return false\n const ref = resolveFuel(o, b)\n b.on = true\n b.lit = canBurn(b, ref)\n if (b.lit) b.smoke = 0\n if (b.spec.part) setPartTarget(o, b.spec.part, 1)\n emitBurner(o, b)\n if (!b.lit) message(ref && ref.f.level <= 0 ? 'empty' : 'no_gas')\n return true\n }\n\n function ignite(o: ObjState, id: string): boolean {\n const b = o.burners.get(id)\n if (!b || b.lit) return false\n const ref = resolveFuel(o, b)\n if (!canBurn(b, ref)) {\n message(ref && ref.f.level <= 0 ? 'empty' : 'no_gas')\n return false\n }\n b.on = true\n b.lit = true\n b.smoke = 0\n if (b.spec.part) setPartTarget(o, b.spec.part, 1)\n emitBurner(o, b)\n return true\n }\n\n function toggleFuel(o: ObjState, id: string): boolean {\n const f = o.fuels.get(id)\n if (!f || !f.spec.valve || !o.parts.has(f.spec.valve)) return false\n const p = o.parts.get(f.spec.valve) as PartState\n setPartTarget(o, f.spec.valve, targetIsOpen(p) ? 0 : 1)\n return true\n }\n\n // ------------------------------------------------------------------ water\n function resolveTank(o: ObjState, id: string | undefined): { o: ObjState; t: TankState } | null {\n const want = id === undefined || id === 'auto' ? defaultTank : id\n if (!want) return null\n const f = findGlobal(want, (oo, local) => oo.tanks.get(local))\n if (f) return { o: f.o, t: f.value }\n const local = o.tanks.get(want)\n return local ? { o, t: local } : null\n }\n\n function drawWater(o: ObjState, fx: FixtureState, liters: number): number {\n if (liters <= 0) return 0\n if (options.waterSupply) return Math.max(0, Math.min(liters, options.waterSupply.draw(gid(o, fx.spec.id), liters)))\n const tank = resolveTank(o, fx.spec.tank)\n if (!tank) return 0\n const got = Math.min(liters, tank.t.level)\n tank.t.level -= got\n return got\n }\n\n function emitFixture(o: ObjState, fx: FixtureState): void {\n emit({ type: 'fixture', object: o.id, fixture: fx.spec.id, on: fx.on, flowing: fx.flowing })\n }\n\n function flowOf(fx: FixtureState): number {\n return fx.spec.flow ?? FLOW_DEFAULTS[fx.spec.kind] ?? 6\n }\n\n function stepFixture(o: ObjState, fx: FixtureState, dt: number): void {\n if (!fx.on) return\n if (fx.spec.kind === 'flush') {\n fx.timer -= dt\n if (fx.timer <= 0) {\n fx.on = false\n fx.flowing = false\n fx.frac = 0\n if (fx.spec.part) setPartTarget(o, fx.spec.part, 0)\n emitFixture(o, fx)\n }\n return\n }\n if (dt <= 0) return\n const wanted = (flowOf(fx) / 60) * dt\n const got = drawWater(o, fx, wanted)\n const flowing = got > 1e-9\n fx.frac = wanted > 0 ? Math.min(1, got / wanted) : 0\n if (flowing !== fx.flowing) {\n fx.flowing = flowing\n emitFixture(o, fx)\n }\n }\n\n function toggleFixture(o: ObjState, id: string): boolean {\n const fx = o.fixtures.get(id)\n if (!fx) return false\n if (fx.on) {\n fx.on = false\n fx.flowing = false\n fx.frac = 0\n if (fx.spec.part) setPartTarget(o, fx.spec.part, 0)\n emitFixture(o, fx)\n return true\n }\n fx.on = true\n if (fx.spec.part) setPartTarget(o, fx.spec.part, 1)\n if (fx.spec.kind === 'flush') {\n const volume = fx.spec.volume ?? 6\n const got = drawWater(o, fx, volume)\n fx.flowing = got > 1e-9\n fx.frac = fx.flowing ? 1 : 0\n fx.timer = FLUSH_SECONDS\n if (!fx.flowing) {\n fx.on = false\n if (fx.spec.part) setPartTarget(o, fx.spec.part, 0)\n }\n } else {\n fx.flowing = false\n fx.frac = 0\n stepFixture(o, fx, 1 / 60)\n }\n emitFixture(o, fx)\n if (!fx.flowing) message('no_water')\n return true\n }\n\n // ------------------------------------------------------------------ seats\n function seatTaken(objectId: string, seatId: string): string | null {\n for (const [actor, s] of seats) if (s.object === objectId && s.seat === seatId) return actor\n return null\n }\n\n function stand(actor: string = DEFAULT_ACTOR): Vec3 | null {\n const s = seats.get(actor)\n if (!s) return null\n seats.delete(actor)\n const o = objs.get(s.object)\n const spec = o?.spec.seats?.find((x) => x.id === s.seat)\n emit({ type: 'seat', object: s.object, seat: s.seat, actor, seated: false })\n if (!o || !spec) return null\n return toWorld(o, spec.exit)\n }\n\n function sit(objectId: string, seatId: string, actor: string = DEFAULT_ACTOR): SeatResult | null {\n const o = objs.get(objectId)\n const seat = o?.spec.seats?.find((s) => s.id === seatId)\n if (!o || !seat) return null\n const holder = seatTaken(objectId, seatId)\n if (holder && holder !== actor) return null\n if (seats.has(actor) && !holder) stand(actor)\n seats.set(actor, { object: objectId, seat: seatId })\n const eye = seat.eye ?? (seat.pose === 'lie' ? 0.18 : 0.62)\n emit({ type: 'seat', object: objectId, seat: seatId, actor, seated: true })\n return { eye: toWorld(o, [seat.pos[0], seat.pos[1] + eye, seat.pos[2]]), yaw: o.public.transform.ry + seat.ry + Math.PI, pose: seat.pose }\n }\n\n // ------------------------------------------------------------------ soft volumes\n function push(pos: Vec3, radius: number, vel: Vec3): void {\n for (const o of objs.values()) {\n if (!o.soft.size) continue\n const tr = o.public.transform\n const lp = worldToLocal(tr, pos)\n const lv = rotY(vel, -tr.ry)\n for (const s of o.soft.values()) {\n const [w, h, d] = s.spec.size\n const hx = w / 2\n const hz = Math.max(d, 0.1) / 2\n const dx = Math.max(Math.abs(lp[0] - s.spec.anchor[0]) - hx, 0)\n const dz = Math.max(Math.abs(lp[2] - s.spec.anchor[2]) - hz, 0)\n if (Math.hypot(dx, dz) > radius) continue\n const top = tr.y + s.spec.anchor[1]\n if (pos[1] + ACTOR_HEIGHT < top - h || pos[1] > top) continue\n const cap = 8\n s.vax = clamp(s.vax - lv[2] * PUSH_GAIN, -cap, cap)\n s.vaz = clamp(s.vaz + lv[0] * PUSH_GAIN, -cap, cap)\n if (s.spec.kind === 'panel' && s.spec.part && lv[2] < -0.05 && lp[2] > s.spec.anchor[2]) setPartTarget(o, s.spec.part, 1)\n }\n }\n }\n\n function stepSoft(dt: number): void {\n const sub = Math.max(1, Math.ceil(dt / (1 / 120)))\n const h = dt / sub\n for (const o of objs.values()) {\n if (!o.soft.size) continue\n const wl = rotY([wind[0], 0, wind[1]], -o.public.transform.ry)\n for (const s of o.soft.values()) {\n const k = s.spec.stiffness ?? 30\n const c = s.spec.damping ?? 4\n const max = s.spec.maxAngle ?? 0.7\n const gain = s.spec.wind ?? (s.spec.kind === 'panel' ? 0 : 1)\n const gust = gain * (0.6 + 0.4 * Math.sin(time * 1.7 + s.phase)) * 0.1\n const fx = -wl[2] * gust\n const fz = wl[0] * gust\n for (let i = 0; i < sub; i++) {\n s.vax += (-k * s.ax - c * s.vax + fx) * h\n s.vaz += (-k * s.az - c * s.vaz + fz) * h\n s.ax += s.vax * h\n s.az += s.vaz * h\n }\n if (Math.abs(s.ax) > max) {\n s.ax = Math.sign(s.ax) * max\n s.vax = 0\n }\n if (Math.abs(s.az) > max) {\n s.az = Math.sign(s.az) * max\n s.vaz = 0\n }\n }\n }\n }\n\n // ------------------------------------------------------------------ damage\n function damage(objectId: string, target: string, amount: number, _kind?: string): { hp: number; destroyed: boolean } | null {\n const o = objs.get(objectId)\n if (o && options.destruction) {\n const pos = toWorld(o, [0, 0.5, 0])\n const type = _kind === 'gas' ? 'explosive' : _kind === 'fabric' ? 'slash' : 'blunt'\n const result = options.destruction.hitPart(objectId, target, { type, point: pos, energy: Math.max(0, amount), source: 'interactables' }) as { events?: Array<{ type?: string; part?: string }> } | null | undefined\n const destroyed = !!result?.events?.some((e) => e.type === 'fracture' && e.part === target)\n emit({ type: 'damage', object: objectId, target, hp: -1, destroyed })\n return { hp: -1, destroyed }\n }\n const st = o?.hp.get(target)\n if (!o || !st) return null\n if (st.hp <= 0) return { hp: 0, destroyed: true }\n const before = st.hp\n st.hp = Math.max(0, st.hp - Math.max(0, amount))\n const destroyed = st.hp <= 0\n emit({ type: 'damage', object: objectId, target, hp: st.hp, destroyed })\n const pos = toWorld(o, [0, 0.5, 0])\n const fuel = o.fuels.get(target)\n if (fuel && destroyed && fuel.spec.kind === 'gas' && fuel.level > 0) {\n emit({ type: 'explosion', object: objectId, pos, radius: 2 + fuel.level / 5 })\n fuel.level = 0\n }\n const tank = o.tanks.get(target)\n if (tank && before === st.max && st.hp < before) emit({ type: 'leak', object: objectId, target, pos })\n return { hp: st.hp, destroyed }\n }\n\n // ------------------------------------------------------------------ interactions\n function deriveInteractions(spec: ObjectSpec): InteractionSpec[] {\n const out: InteractionSpec[] = []\n const used = new Set<string>()\n const partPos = (id: string | undefined, fallback: Vec3): Vec3 => spec.parts?.find((p) => p.id === id)?.pivot ?? fallback\n const partLabel = (id: string | undefined): string => spec.parts?.find((p) => p.id === id)?.label ?? ''\n const claim = (id: string | undefined) => {\n if (id) used.add(id)\n }\n for (const d of spec.devices ?? []) {\n claim(d.part)\n const pos = d.part ? partPos(d.part, [0, 1, 0]) : (d.plug ?? [0, 1, 0])\n // a hard-wired lamp (circuit, no plug, no part to press) is driven by switches and `setDevice`: it never steals the interact key\n if (d.kind === 'lamp' && !d.part && !d.plug && d.electric !== false) continue\n out.push({ id: `device.${d.id}`, pos, action: { do: 'toggle_device', device: d.id }, label: partLabel(d.part) })\n if (d.kind === 'screen' && (d.channels ?? 1) > 1) out.push({ id: `channel.${d.id}`, pos, action: { do: 'cycle_device', device: d.id }, label: '' })\n }\n for (const s of spec.switches ?? []) {\n claim(s.part)\n out.push({ id: `switch.${s.id}`, pos: partPos(s.part, [0, 1.2, 0]), action: { do: 'toggle_switch', switch: s.id }, label: partLabel(s.part) })\n }\n const driven = new Set((spec.devices ?? []).map((d) => d.fixture))\n for (const f of spec.fixtures ?? []) {\n claim(f.part)\n if (driven.has(f.id)) continue\n out.push({ id: `fixture.${f.id}`, pos: f.part ? partPos(f.part, f.outlet) : f.outlet, action: { do: 'toggle_fixture', fixture: f.id }, label: partLabel(f.part) })\n }\n for (const b of spec.burners ?? []) {\n claim(b.part)\n const pos = b.part ? partPos(b.part, b.pos) : b.pos\n if (b.needsIgnition) out.push({ id: `ignite.${b.id}`, pos, action: { do: 'ignite', burner: b.id }, label: '' })\n out.push({ id: `burner.${b.id}`, pos, action: { do: 'toggle_burner', burner: b.id }, label: partLabel(b.part) })\n }\n for (const f of spec.fuels ?? []) {\n if (!f.valve) continue\n claim(f.valve)\n out.push({ id: `fuel.${f.id}`, pos: partPos(f.valve, [0, 0.5, 0]), action: { do: 'toggle_fuel', fuel: f.id }, label: partLabel(f.valve) })\n }\n for (const c of spec.containers ?? []) {\n if (!c.access) out.push({ id: `open.${c.id}`, pos: c.volume.center, action: { do: 'open_container', container: c.id }, label: '' })\n }\n for (const p of spec.parts ?? []) {\n if (used.has(p.id)) continue\n if (p.label) out.push({ id: `part.${p.id}`, pos: p.pivot, action: { do: 'toggle_part', part: p.id }, label: p.label })\n }\n for (const c of spec.containers ?? []) {\n if (c.access && !spec.parts?.find((p) => p.id === c.access)?.label) {\n out.push({ id: `open.${c.id}`, pos: partPos(c.access, c.volume.center), action: { do: 'open_container', container: c.id }, label: '' })\n }\n }\n for (const s of spec.seats ?? []) out.push({ id: `seat.${s.id}`, pos: s.pos, action: { do: 'sit', seat: s.id }, label: '' })\n return out\n }\n\n function typeName(o: ObjState): string {\n return table[`type.${o.spec.type}`] ?? humanize(o.spec.type)\n }\n\n function promptOf(o: ObjState, i: InteractionSpec): { label: string; verb: string; object: string } {\n const a = i.action\n let state: string | null = null\n let def = ''\n switch (a.do) {\n case 'toggle_part': {\n const p = o.parts.get(a.part)\n const open = !!p && targetIsOpen(p)\n state = open ? 'open' : 'closed'\n def = open ? 'prompt.close' : 'prompt.open'\n break\n }\n case 'open_container': {\n const c = containerOf(o, a.container)\n const p = c?.access ? o.parts.get(c.access) : undefined\n const open = p ? targetIsOpen(p) : false\n state = p ? (open ? 'open' : 'closed') : null\n def = open ? 'prompt.close' : 'prompt.open'\n break\n }\n case 'toggle_device': {\n const on = !!o.devices.get(a.device)?.on\n state = on ? 'on' : 'off'\n def = on ? 'prompt.turn_off' : 'prompt.turn_on'\n break\n }\n case 'toggle_switch': {\n const sw = o.spec.switches?.find((s) => s.id === a.switch)\n const on = !!sw && switchIsOn(o, sw)\n state = on ? 'on' : 'off'\n def = on ? 'prompt.turn_off' : 'prompt.turn_on'\n break\n }\n case 'toggle_breaker': {\n const on = breaker(a.circuit)\n state = on ? 'on' : 'off'\n def = on ? 'prompt.breaker_off' : 'prompt.breaker_on'\n break\n }\n case 'toggle_fixture': {\n const on = !!o.fixtures.get(a.fixture)?.on\n state = on ? 'on' : 'off'\n def = on ? 'prompt.turn_off' : 'prompt.turn_on'\n break\n }\n case 'toggle_burner': {\n const b = o.burners.get(a.burner)\n const on = !!b && (b.on || b.lit)\n state = on ? 'on' : 'off'\n def = on ? 'prompt.turn_off' : 'prompt.turn_on'\n break\n }\n case 'toggle_fuel': {\n const f = o.fuels.get(a.fuel)\n const open = !!f && valveOpen(o, f)\n state = open ? 'open' : 'closed'\n def = open ? 'prompt.close_valve' : 'prompt.open_valve'\n break\n }\n case 'take':\n def = 'prompt.take'\n break\n case 'read':\n def = 'prompt.read'\n break\n case 'ignite':\n def = 'prompt.light'\n break\n case 'cycle_device':\n def = 'prompt.change_channel'\n break\n case 'sit': {\n const seat = o.spec.seats?.find((s) => s.id === a.seat)\n const mine = seats.get(DEFAULT_ACTOR)\n def = mine && mine.object === o.id && mine.seat === a.seat ? 'prompt.get_up' : seat?.pose === 'lie' ? 'prompt.lie_down' : 'prompt.sit'\n break\n }\n }\n let key = def\n if (i.label) {\n if (state && table[`${i.label}.${state}`] !== undefined) key = `${i.label}.${state}`\n else if (table[i.label] !== undefined) key = i.label\n }\n const vars = { name: typeName(o), circuit: a.do === 'toggle_breaker' ? (a.name ?? a.circuit) : '' }\n const label = t(key, vars)\n // structured prompt: explicit `<key>.verb` / `<key>.object` entries win; otherwise the first word is the verb and the rest the object\n let verb: string\n let object: string\n if (table[`${key}.verb`] !== undefined) {\n verb = t(`${key}.verb`, vars)\n object = table[`${key}.object`] ? t(`${key}.object`, vars) : vars.name\n } else {\n const sp = label.indexOf(' ')\n verb = sp < 0 ? label : label.slice(0, sp)\n object = sp < 0 ? vars.name : label.slice(sp + 1)\n }\n return { label, verb, object }\n }\n\n /** Locale key of the reason an interaction cannot be used right now, or null when it can. */\n function unavailableReason(o: ObjState, i: InteractionSpec): string | null {\n const a = i.action\n if (!requirementMet(i)) return 'requires_open'\n switch (a.do) {\n case 'toggle_part': {\n const p = o.parts.get(a.part)\n return p && gated(o, p) ? 'requires_open' : null\n }\n case 'open_container': {\n const c = containerOf(o, a.container)\n const p = c?.access ? o.parts.get(c.access) : undefined\n return p && gated(o, p) ? 'requires_open' : null\n }\n case 'toggle_breaker': {\n const rocker = a.part ? o.parts.get(a.part) : undefined\n return rocker && gated(o, rocker) ? 'requires_open' : null\n }\n case 'take':\n case 'read':\n return accessible(o.id, a.container) ? null : 'requires_open'\n case 'ignite':\n return o.burners.get(a.burner)?.lit ? 'already_lit' : null\n case 'toggle_burner': {\n const b = o.burners.get(a.burner)\n return !b?.spec.needsIgnition || b.lit ? null : 'needs_ignition'\n }\n case 'cycle_device': {\n const d = o.devices.get(a.device)\n if (!d || !d.on) return 'turn_on_first'\n return devicePowered(o, d) ? null : 'no_power'\n }\n default:\n return null\n }\n }\n\n function requirementMet(i: InteractionSpec): boolean {\n if (!i.requires) return true\n const p = objs.get(i.requires.object)?.parts.get(i.requires.part)\n return !!p && targetIsOpen(p)\n }\n\n function interactablesOf(o: ObjState): Array<{ item: Interactable; spec: InteractionSpec; o: ObjState }> {\n return o.interactions.map((i) => {\n const id = gid(o, i.id)\n const p = promptOf(o, i)\n const item: Interactable = {\n id,\n object: o.id,\n kind: i.action.do,\n pos: toWorld(o, i.pos),\n radius: i.radius ?? DEFAULT_RADIUS,\n label: p.label,\n verb: p.verb,\n noun: p.object,\n available: true,\n act: (actor) => act(id, actor),\n }\n const reason = unavailableReason(o, i) ?? (options.canInteract?.(item, DEFAULT_ACTOR) === false ? 'locked' : null)\n if (reason) {\n item.available = false\n item.reason = reason\n }\n return { item, spec: i, o }\n })\n }\n\n function interactables(): Interactable[] {\n const out: Interactable[] = []\n for (const o of objs.values()) for (const e of interactablesOf(o)) out.push(e.item)\n return out\n }\n\n function target(eye: { x: number; y: number; z: number; yaw: number; pitch: number }, opts: TargetOptions = {}): Interactable | null {\n const reach = opts.reach ?? 1.8\n const cone = opts.cone ?? 0.45\n const dir: Vec3 = [-Math.sin(eye.yaw) * Math.cos(eye.pitch), Math.sin(eye.pitch), -Math.cos(eye.yaw) * Math.cos(eye.pitch)]\n let best: Interactable | null = null\n let bestScore = Infinity\n for (const o of objs.values()) {\n for (const e of interactablesOf(o)) {\n if (!e.item.available && !opts.includeUnavailable) continue\n if (opts.kinds && !opts.kinds.includes(e.item.kind)) continue\n if (opts.filter && !opts.filter(e.item)) continue\n const v: Vec3 = [e.item.pos[0] - eye.x, e.item.pos[1] - eye.y, e.item.pos[2] - eye.z]\n const d = Math.hypot(v[0], v[1], v[2])\n if (d > reach) continue\n const cos = d < 1e-6 ? 1 : clamp((v[0] * dir[0] + v[1] * dir[1] + v[2] * dir[2]) / d, -1, 1)\n const angle = Math.acos(cos)\n if (angle > cone) continue\n const score = (angle / cone) * 0.6 + (d / reach) * 0.4\n if (score < bestScore) {\n bestScore = score\n best = e.item\n }\n }\n }\n return best\n }\n\n function act(interactableId: string, actor: string = DEFAULT_ACTOR): boolean {\n let found: { item: Interactable; spec: InteractionSpec; o: ObjState } | null = null\n for (const o of objs.values()) {\n if (!interactableId.startsWith(o.id + ':')) continue\n const local = interactableId.slice(o.id.length + 1)\n if (!o.interactions.some((i) => i.id === local)) continue\n found = interactablesOf(o).find((e) => e.spec.id === local) ?? null\n if (found) break\n }\n if (!found) return false\n if (!requirementMet(found.spec)) return false\n if (options.canInteract?.(found.item, actor) === false) return false\n return execute(found.o, found.spec.action, actor)\n }\n\n function execute(o: ObjState, a: InteractionSpec['action'], actor: string): boolean {\n switch (a.do) {\n case 'toggle_part': {\n const p = o.parts.get(a.part)\n if (!p || gated(o, p)) return false\n setPartTarget(o, a.part, targetIsOpen(p) ? 0 : 1)\n return true\n }\n case 'open_container': {\n const c = containerOf(o, a.container)\n if (!c) return false\n if (c.access) return execute(o, { do: 'toggle_part', part: c.access }, actor)\n emit({ type: 'container', object: o.id, container: c.id, open: true })\n return true\n }\n case 'take': {\n if (!accessible(o.id, a.container)) return false\n const list = o.contents.get(a.container)\n if (!list) return false\n const slot = a.slot ?? list.findIndex((s) => s !== null)\n if (slot < 0 || !list[slot]) {\n message('empty')\n return false\n }\n return take(o.id, a.container, slot, actor) !== null\n }\n case 'read': {\n if (!accessible(o.id, a.container)) return false\n const stack = o.contents.get(a.container)?.[a.slot]\n if (!stack) {\n message('empty')\n return false\n }\n const key = typeof stack.data?.text === 'string' ? stack.data.text : null\n if (!key) return false\n message(key)\n return true\n }\n case 'toggle_device': {\n const d = o.devices.get(a.device)\n if (!d) return false\n setDeviceOn(o, d, !d.on)\n return true\n }\n case 'cycle_device': {\n const d = o.devices.get(a.device)\n if (!d || !d.on) return false\n if (!devicePowered(o, d)) {\n message('no_power')\n return false\n }\n d.channel = (d.channel + 1) % Math.max(1, d.spec.channels ?? 1)\n emit({ type: 'device', object: o.id, device: d.spec.id, on: true, powered: true, channel: d.channel })\n return true\n }\n case 'toggle_switch':\n return toggleSwitch(o, a.switch)\n case 'toggle_breaker': {\n const rocker = a.part ? o.parts.get(a.part) : undefined\n if (rocker && gated(o, rocker)) return false\n const on = !breaker(a.circuit)\n if (a.part) setPartTarget(o, a.part, on ? 1 : 0)\n world.power.setBreaker(a.circuit, on)\n return true\n }\n case 'toggle_fixture':\n return toggleFixture(o, a.fixture)\n case 'toggle_burner':\n return toggleBurner(o, a.burner)\n case 'ignite':\n return ignite(o, a.burner)\n case 'toggle_fuel':\n return toggleFuel(o, a.fuel)\n case 'sit': {\n const mine = seats.get(actor)\n if (mine && mine.object === o.id && mine.seat === a.seat) {\n stand(actor)\n return true\n }\n return sit(o.id, a.seat, actor) !== null\n }\n }\n }\n\n // ------------------------------------------------------------------ emitters, mirrors, colliders\n function emitters(): ActiveEmitter[] {\n const out: ActiveEmitter[] = []\n for (const o of objs.values()) {\n for (const e of o.spec.emitters ?? []) {\n let intensity = 0\n const b = o.burners.get(e.source)\n const fx = o.fixtures.get(e.source)\n const dev = o.devices.get(e.source)\n const fuel = o.fuels.get(e.source)\n if (b) {\n const ref = resolveFuel(o, b)\n const frac = ref ? clamp(ref.f.level / ref.f.spec.capacity, 0, 1) : 0\n if (b.lit) intensity = 0.35 + 0.65 * frac\n else if (e.kind === 'smoke' && b.smoke > 0) intensity = 0.6 * (b.smoke / SMOKE_TAIL)\n } else if (fx) {\n if (fx.on && fx.flowing) intensity = fx.frac\n } else if (dev) {\n if (dev.on && devicePowered(o, dev)) intensity = 1\n } else if (fuel) {\n const lit = [...o.burners.values()].some((bb) => bb.lit && resolveFuel(o, bb)?.f === fuel)\n if (lit) intensity = 0.35 + 0.65 * clamp(fuel.level / fuel.spec.capacity, 0, 1)\n }\n if (intensity <= 0) continue\n const dir = rotY(e.dir ?? [0, 1, 0], o.public.transform.ry)\n out.push({ id: gid(o, e.id), object: o.id, kind: e.kind, pos: toWorld(o, e.pos), dir, size: e.size ?? 0.3, ...(e.reach !== undefined ? { reach: e.reach } : {}), intensity: clamp(intensity, 0, 1) })\n }\n }\n return out\n }\n\n function mirrors(eye: Vec3, maxDistance = 10, count = 4) {\n const out: Array<{ id: string; center: Vec3; normal: Vec3; w: number; h: number; distance: number }> = []\n for (const o of objs.values()) {\n for (const m of o.spec.mirrors ?? []) {\n const center = toWorld(o, applyPart(o, m.part, m.center))\n const f = partFrame(o, m.part)\n const ln = f && f.angle !== 0 ? rotateAxis(m.normal, f.axis, f.angle) : m.normal\n const normal = rotY(ln, o.public.transform.ry)\n const len = Math.hypot(normal[0], normal[1], normal[2]) || 1\n const n: Vec3 = [normal[0] / len, normal[1] / len, normal[2] / len]\n const to: Vec3 = [eye[0] - center[0], eye[1] - center[1], eye[2] - center[2]]\n if (n[0] * to[0] + n[1] * to[1] + n[2] * to[2] <= 0) continue\n const distance = Math.hypot(to[0], to[1], to[2])\n if (distance > maxDistance) continue\n out.push({ id: gid(o, m.id), center, normal: n, w: m.w, h: m.h, distance })\n }\n }\n return out.sort((a, b) => a.distance - b.distance).slice(0, count)\n }\n\n function colliders(opts: ColliderOptions = {}): WorldCollider[] {\n const out: WorldCollider[] = []\n const fixed: WorldCollider[] = []\n for (const o of objs.values()) {\n const tr = o.public.transform\n for (const c of o.spec.colliders ?? []) {\n if (c.part && c.offWhenOpen && (o.parts.get(c.part)?.t ?? 0) > 0.5) continue\n let extra = 0\n if (c.part) {\n const f = partFrame(o, c.part)\n if (f && f.angle !== 0 && Math.abs(f.axis[1]) >= 0.5) extra = f.angle * Math.sign(f.axis[1])\n }\n const center = toWorld(o, applyPart(o, c.part, c.center))\n ;(c.part ? out : fixed).push({\n id: gid(o, c.id),\n object: o.id,\n cx: center[0],\n cz: center[2],\n hw: c.half[0],\n hd: c.half[2],\n ry: tr.ry + (c.ry ?? 0) + extra,\n y0: center[1] - c.half[1],\n y1: center[1] + c.half[1],\n })\n }\n }\n if (opts.merge) return [...mergeColliders(fixed, { gap: opts.gap, slack: opts.slack }), ...out]\n return [...fixed, ...out]\n }\n\n // ------------------------------------------------------------------ update\n function update(dt: number): void {\n if (!(dt > 0)) return\n time += dt\n for (const o of objs.values()) {\n for (const p of o.parts.values()) {\n if (p.t === p.target) continue\n if (gated(o, p) && p.target > p.t) continue // a gated part may still close\n const step = (p.spec.motion.speed ?? 2.5) * dt\n const next = p.t < p.target ? Math.min(p.target, p.t + step) : Math.max(p.target, p.t - step)\n writeT(o, p, next)\n if (next === p.target) emit({ type: 'part', object: o.id, part: p.spec.id, t: next, target: p.target })\n }\n }\n stepSoft(dt)\n for (const o of objs.values()) {\n for (const b of o.burners.values()) {\n if (b.lit) {\n const ref = resolveFuel(o, b)\n if (!ref || !valveOpen(ref.o, ref.f)) {\n extinguish(o, b, !!b.spec.needsIgnition)\n } else {\n ref.f.level = Math.max(0, ref.f.level - b.spec.rate * dt)\n if (ref.f.level <= 0) extinguish(o, b, !!b.spec.needsIgnition)\n }\n } else if (b.on && !b.spec.needsIgnition && canBurn(b, resolveFuel(o, b))) {\n // a gas knob left open without gas lights as soon as the bottle valve opens\n b.lit = true\n b.smoke = 0\n emitBurner(o, b)\n } else if (b.smoke > 0) {\n b.smoke = Math.max(0, b.smoke - dt)\n }\n }\n if (rain > 0) {\n for (const tk of o.tanks.values()) {\n if (tk.spec.rainArea) tk.level = Math.min(tk.spec.capacity, tk.level + ((rain * tk.spec.rainArea) / 3600) * dt)\n }\n }\n for (const tk of o.tanks.values()) {\n const st = o.hp.get(tk.spec.id)\n if (st && st.hp < st.max && tk.level > 0) {\n const rate = 0.5 + 2.5 * (1 - st.hp / st.max)\n tk.level = Math.max(0, tk.level - rate * dt)\n }\n }\n }\n for (const o of objs.values()) {\n for (const d of o.devices.values()) {\n const fx = d.spec.fixture ? o.fixtures.get(d.spec.fixture) : undefined\n if (!fx) continue\n const run = d.on && devicePowered(o, d)\n if (fx.on === run) continue\n fx.on = run\n if (!run) {\n fx.flowing = false\n fx.frac = 0\n emitFixture(o, fx)\n }\n }\n for (const fx of o.fixtures.values()) stepFixture(o, fx, dt)\n }\n }\n\n // ------------------------------------------------------------------ add / remove\n function add(id: string, spec: ObjectSpec, transform: Transform): WorldObject {\n if (objs.has(id)) throw new Error(`Object \"${id}\" already exists`)\n const pub: WorldObject = { id, spec, transform: { ...transform }, parts: {} }\n const o: ObjState = {\n id,\n public: pub,\n spec,\n parts: new Map(),\n contents: new Map(),\n devices: new Map(),\n fuels: new Map(),\n burners: new Map(),\n tanks: new Map(),\n fixtures: new Map(),\n soft: new Map(),\n hp: new Map(),\n interactions: spec.interactions ?? deriveInteractions(spec),\n }\n for (const p of spec.parts ?? []) {\n const t0 = clamp(p.t0 ?? 0, 0, 1)\n o.parts.set(p.id, { spec: p, t: t0, target: t0, maxT: 1 })\n pub.parts[p.id] = t0\n }\n for (const c of spec.containers ?? []) {\n let list: Array<ItemStack | null>\n if (c.contents) list = c.slots.map((_, i) => cloneStack(c.contents?.[i] ?? null))\n else if (c.loot) list = generateLoot(options.loot, c.loot, c.slots, `${seed}|${id}|${c.id}`, options.items)\n else list = c.slots.map(() => null)\n o.contents.set(c.id, list)\n }\n for (const d of spec.devices ?? []) {\n o.devices.set(d.id, { spec: d, on: !!d.on, channel: 0 })\n if (d.on && d.part) {\n const p = o.parts.get(d.part)\n if (p) {\n p.target = p.maxT\n writeT(o, p, p.maxT)\n }\n }\n }\n for (const f of spec.fuels ?? []) o.fuels.set(f.id, { spec: f, level: clamp(f.level ?? f.capacity, 0, f.capacity) })\n for (const b of spec.burners ?? []) o.burners.set(b.id, { spec: b, on: false, lit: false, smoke: 0 })\n for (const tk of spec.tanks ?? []) o.tanks.set(tk.id, { spec: tk, level: clamp(tk.level ?? tk.capacity, 0, tk.capacity) })\n for (const f of spec.fixtures ?? []) o.fixtures.set(f.id, { spec: f, on: false, flowing: false, frac: 0, timer: 0 })\n let n = 0\n for (const s of spec.soft ?? []) {\n o.soft.set(s.id, { spec: s, ax: 0, az: 0, vax: 0, vaz: 0, phase: (n++ * 2.399963) % (Math.PI * 2) })\n }\n for (const d of spec.damage ?? []) o.hp.set(d.target, { max: d.hp, hp: d.hp })\n objs.set(id, o)\n if (!defaultTankExplicit && defaultTank === null && o.tanks.size) defaultTank = gid(o, [...o.tanks.keys()][0])\n rebuildPlugs()\n return pub\n }\n\n function remove(id: string): void {\n const o = objs.get(id)\n if (!o) return\n for (const [actor, s] of [...seats]) if (s.object === id) seats.delete(actor)\n objs.delete(id)\n if (defaultTank && defaultTank.startsWith(id + ':')) defaultTank = null\n rebuildPlugs()\n }\n\n // ------------------------------------------------------------------ save / load\n function serialize(): WorldState {\n const objects: WorldState['objects'] = {}\n for (const o of objs.values()) {\n objects[o.id] = {\n parts: Object.fromEntries([...o.parts].map(([k, p]) => [k, p.t])),\n contents: Object.fromEntries([...o.contents].map(([k, v]) => [k, v.map(cloneStack)])),\n devices: Object.fromEntries([...o.devices].map(([k, d]) => [k, { on: d.on, channel: d.channel }])),\n fuels: Object.fromEntries([...o.fuels].map(([k, f]) => [k, { level: f.level, open: valveOpen(o, f) }])),\n burners: Object.fromEntries([...o.burners].map(([k, b]) => [k, { on: b.on, lit: b.lit }])),\n tanks: Object.fromEntries([...o.tanks].map(([k, tk]) => [k, tk.level])),\n fixtures: Object.fromEntries([...o.fixtures].map(([k, f]) => [k, f.on])),\n hp: Object.fromEntries([...o.hp].map(([k, h]) => [k, h.hp])),\n }\n }\n return {\n version: 1,\n objects,\n grid,\n breakers: Object.fromEntries(breakers),\n seats: Object.fromEntries([...seats].map(([a, s]) => [a, { object: s.object, seat: s.seat }])),\n defaultTank,\n }\n }\n\n function load(state: WorldState): void {\n for (const [id, st] of Object.entries(state.objects)) {\n const o = objs.get(id)\n if (!o) continue\n for (const [k, v] of Object.entries(st.parts)) {\n const p = o.parts.get(k)\n if (p) {\n // keep the limit found by fitSweep: a loaded save must not let a door open through a wall again\n p.target = clamp(v, 0, p.maxT)\n writeT(o, p, p.target)\n }\n }\n for (const [k, v] of Object.entries(st.contents)) {\n const c = containerOf(o, k)\n if (c) o.contents.set(k, c.slots.map((_, i) => cloneStack(v[i] ?? null)))\n }\n for (const [k, v] of Object.entries(st.devices)) {\n const d = o.devices.get(k)\n if (d) {\n d.on = v.on\n d.channel = v.channel\n }\n }\n for (const [k, v] of Object.entries(st.fuels)) {\n const f = o.fuels.get(k)\n if (f) f.level = v.level\n }\n for (const [k, v] of Object.entries(st.burners)) {\n const b = o.burners.get(k)\n if (b) {\n b.on = v.on\n b.lit = v.lit\n b.smoke = 0\n }\n }\n for (const [k, v] of Object.entries(st.tanks)) {\n const tk = o.tanks.get(k)\n if (tk) tk.level = v\n }\n for (const [k, v] of Object.entries(st.fixtures)) {\n const f = o.fixtures.get(k)\n if (f) {\n f.on = v\n f.flowing = false\n f.frac = 0\n f.timer = v && f.spec.kind === 'flush' ? FLUSH_SECONDS : 0\n }\n }\n for (const [k, v] of Object.entries(st.hp)) {\n const h = o.hp.get(k)\n if (h) h.hp = v\n }\n }\n grid = state.grid\n breakers.clear()\n for (const [k, v] of Object.entries(state.breakers)) breakers.set(k, v)\n seats.clear()\n for (const [a, s] of Object.entries(state.seats)) if (objs.has(s.object)) seats.set(a, { object: s.object, seat: s.seat })\n defaultTank = state.defaultTank\n defaultTankExplicit = true\n }\n\n // ------------------------------------------------------------------ public surface\n const world: World = {\n add,\n remove,\n get: (id) => objs.get(id)?.public,\n objects: () => [...objs.values()].map((o) => o.public),\n update,\n interactables,\n target,\n act,\n partT: (objectId, partId) => objs.get(objectId)?.parts.get(partId)?.t ?? 0,\n setDevice,\n setFixture,\n setBurner,\n setPart: (objectId, partId, targetT, instant) => {\n const o = objs.get(objectId)\n if (o) setPartTarget(o, partId, targetT, instant)\n },\n fitSweep,\n contents,\n accessible,\n take,\n put,\n slotPosition,\n power: {\n get grid() {\n return grid\n },\n set grid(on: boolean) {\n world.power.setGrid(on)\n },\n setGrid(on) {\n if (on === grid) return\n const before = powerSnapshot()\n grid = on\n emit({ type: 'power', grid })\n emitPowerChanges(before)\n },\n setBreaker(circuit, on) {\n if (breaker(circuit) === on) {\n breakers.set(circuit, on)\n return\n }\n const before = powerSnapshot()\n breakers.set(circuit, on)\n emitPowerChanges(before)\n },\n breaker,\n isPowered: (objectId, deviceId) => {\n const r = getDevice(objectId, deviceId)\n return r ? devicePowered(r.o, r.d) : false\n },\n isOn: (objectId, deviceId) => getDevice(objectId, deviceId)?.d.on ?? false,\n pluggedInto: (objectId, deviceId) => plugs.get(`${objectId}:${deviceId}`) ?? null,\n channel: (objectId, deviceId) => getDevice(objectId, deviceId)?.d.channel ?? 0,\n },\n fuelLevel: (objectId, fuelId) => objs.get(objectId)?.fuels.get(fuelId)?.level ?? 0,\n isLit: (objectId, burnerId) => objs.get(objectId)?.burners.get(burnerId)?.lit ?? false,\n water: {\n setDefaultTank(tankId) {\n defaultTank = tankId\n defaultTankExplicit = true\n },\n level(tankId) {\n return findGlobal(tankId, (oo, local) => oo.tanks.get(local))?.value.level ?? 0\n },\n refill(tankId, liters) {\n const f = findGlobal(tankId, (oo, local) => oo.tanks.get(local))\n if (!f) return 0\n const added = Math.max(0, Math.min(liters, f.value.spec.capacity - f.value.level))\n f.value.level += added\n return added\n },\n setRain(mm) {\n rain = Math.max(0, mm)\n },\n isFlowing: (objectId, fixtureId) => {\n const fx = objs.get(objectId)?.fixtures.get(fixtureId)\n return !!fx && fx.on && fx.flowing\n },\n },\n sit,\n stand,\n seatOf: (actor = DEFAULT_ACTOR) => {\n const s = seats.get(actor)\n return s ? { object: s.object, seat: s.seat } : null\n },\n push,\n sway: (objectId, softId) => {\n const s = objs.get(objectId)?.soft.get(softId)\n return s ? { ax: s.ax, az: s.az } : { ax: 0, az: 0 }\n },\n setWind: (x, z) => {\n wind = [x, z]\n },\n emitters,\n mirrors,\n colliders,\n damage,\n hp: (objectId, target) => (options.destruction ? null : (objs.get(objectId)?.hp.get(target)?.hp ?? null)),\n t,\n setLocale: (l) => {\n locale = l\n table = resolveStrings(locale, options.strings)\n },\n serialize,\n load,\n }\n return world\n}\n","import { poseOf as enginePoseOf } from '@adatechnology/game-interactables'\nimport type { Motion, World } from '@adatechnology/game-interactables'\nimport type { ThreeLib, ThreeNS, Vec3 } from './types'\n\n/** Pure pose of a part at `t`, in the part's own frame (the frame at rest: pivot at the origin, already turned by the part's `ry`). */\nexport interface Pose {\n /** Translation, part frame. */\n position: Vec3\n /** Rotation as a quaternion [x, y, z, w], part frame. */\n quaternion: [number, number, number, number]\n /** Scale per local axis. */\n scale: Vec3\n}\n\n/**\n * The engine's pose math, as a quaternion. This is the engine's own `poseOf` (same default angles and distances), so the rendered\n * part and the engine's `slotPosition`, colliders, mirrors and `fitSweep` always agree. `t` is clamped to [0, 1].\n */\nexport function poseOf(motion: Motion, t: number): Pose {\n const k = Math.min(1, Math.max(0, Number.isFinite(t) ? t : 0))\n const pose = enginePoseOf(motion, k)\n const [x, y, z] = pose.rotation.axis\n const l = Math.hypot(x, y, z) || 1\n const s = Math.sin(pose.rotation.angle / 2)\n return {\n position: [pose.position[0], pose.position[1], pose.position[2]],\n quaternion: [(x / l) * s, (y / l) * s, (z / l) * s, Math.cos(pose.rotation.angle / 2)],\n scale: [pose.scale[0], pose.scale[1], pose.scale[2]],\n }\n}\n\ninterface Rest {\n position: Vec3\n quaternion: [number, number, number, number]\n scale: Vec3\n}\n\n/** Remembers the rest transform of a part group in `userData.rest` (once). */\nexport function rememberRest(group: Pick<ThreeNS.Object3D, 'position' | 'quaternion' | 'scale' | 'userData'>): Rest {\n let rest = group.userData.rest as Rest | undefined\n if (!rest) {\n const p = group.position\n const q = group.quaternion\n const s = group.scale\n rest = { position: [p.x, p.y, p.z], quaternion: [q.x, q.y, q.z, q.w], scale: [s.x, s.y, s.z] }\n group.userData.rest = rest\n }\n return rest\n}\n\n/**\n * Poses `group` for a motion at `t`. The rest pose (position at the pivot, rotation `ry`) is saved in `group.userData.rest` the first\n * time, so calling this every frame never drifts. The motion axis lives in the part frame (after `ry`).\n */\nexport function applyPose<G extends import('three').Object3D>(THREE: ThreeLib, group: G, motion: Motion, t: number): G {\n const rest = rememberRest(group)\n const pose = poseOf(motion, t)\n const restQ = new THREE.Quaternion(rest.quaternion[0], rest.quaternion[1], rest.quaternion[2], rest.quaternion[3])\n const delta = new THREE.Quaternion(pose.quaternion[0], pose.quaternion[1], pose.quaternion[2], pose.quaternion[3])\n group.quaternion.copy(restQ).multiply(delta)\n const offset = new THREE.Vector3(pose.position[0], pose.position[1], pose.position[2]).applyQuaternion(restQ)\n group.position.set(rest.position[0] + offset.x, rest.position[1] + offset.y, rest.position[2] + offset.z)\n group.scale.set(rest.scale[0] * pose.scale[0], rest.scale[1] * pose.scale[1], rest.scale[2] * pose.scale[2])\n group.updateMatrix()\n return group\n}\n\nexport interface SyncOptions {\n /** Seconds, drives the idle flutter of sway shaders. Default 0. */\n time?: number\n}\n\n/**\n * Per-frame sync of one world object: every part group takes the motion of its `PartSpec` at the world's current t,\n * and every soft volume swings. A soft volume drives the group in `parts` under its `part` (or, failing that, its own id):\n * meshes patched with `attachSway` get the swing through their shader uniforms (so shadows follow), any other group is rotated\n * around the anchor line.\n */\nexport function syncObject(THREE: ThreeLib, world: World, objectId: string, parts: Record<string, import('three').Object3D>, options: SyncOptions = {}): void {\n const obj = world.get(objectId)\n if (!obj) return\n const motionOf = new Map<string, Motion>()\n for (const p of obj.spec.parts ?? []) {\n motionOf.set(p.id, p.motion)\n const g = parts[p.id]\n if (!g) continue\n const merged = g.userData.merged as { kind: string; apply?: (m: Motion, t: number) => void } | undefined\n if (merged) {\n // parts merged into one mesh: the batch poses every vertex around its own pivot (cloth batches stay at rest)\n merged.apply?.(p.motion, world.partT(objectId, p.id))\n continue\n }\n applyPose(THREE, g, p.motion, world.partT(objectId, p.id))\n }\n for (const soft of obj.spec.soft ?? []) {\n const key = soft.part ?? soft.id\n const g = parts[key] ?? parts[soft.id]\n if (!g) continue\n const { ax, az } = world.sway(objectId, soft.id)\n const batch = g.userData.merged as { kind: string; index?: Record<string, number>; setAt?: (i: number, ax: number, az: number) => void; setTime?: (t: number) => void } | undefined\n if (batch?.kind === 'sway') {\n const i = batch.index?.[soft.id]\n if (i !== undefined) batch.setAt?.(i, ax, az)\n if (options.time !== undefined) batch.setTime?.(options.time)\n continue\n }\n let shaded = false\n g.traverse((o) => {\n const sway = o.userData.sway as { set(ax: number, az: number, time?: number): void } | undefined\n if (sway) {\n sway.set(ax, az, options.time)\n shaded = true\n }\n })\n if (shaded) continue\n rememberRest(g)\n // rotate around the anchor line: the part's pose (if it has a motion) is already applied above\n if (!motionOf.has(key)) applyPose(THREE, g, { kind: 'hinge', angle: 0 }, 0)\n const rot = new THREE.Quaternion().setFromEuler(new THREE.Euler(ax, 0, az, 'XYZ'))\n const anchor = new THREE.Vector3(soft.anchor[0], soft.anchor[1], soft.anchor[2])\n g.position.sub(anchor).applyQuaternion(rot).add(anchor)\n g.quaternion.premultiply(rot)\n g.updateMatrix()\n }\n}\n","import type { Motion, ObjectSpec } from '@adatechnology/game-interactables'\nimport { poseOf } from './pose'\nimport type { ThreeLib, ThreeNS } from './types'\n\nexport interface MergeOptions {\n /** Idle flutter of merged cloth (see `SwayOptions.amplitude`). Default 0.04. */\n swayAmplitude?: number\n}\n\n/** Posed by `syncObject` in place of the part groups it replaced. */\nexport interface PoseBatch {\n kind: 'pose'\n /** Part ids that moved into the batch. */\n ids: string[]\n /** Applies the motion at `t` to every vertex, each around its own pivot. */\n apply(motion: Motion, t: number): void\n}\n\n/** Cloth hanging from a line: every merged cloth swings with its own (ax, az). */\nexport interface SwayBatch {\n kind: 'sway'\n /** Soft volume id -> index. */\n index: Record<string, number>\n setAt(index: number, ax: number, az: number): void\n setTime(time: number): void\n}\n\nexport type MergedGroup = ThreeNS.Group & { userData: { merged: PoseBatch | SwayBatch } }\n\nconst QROT = /* glsl */ `\nvec3 batchRotate(vec4 q, vec3 v) { return v + 2.0 * cross(q.xyz, cross(q.xyz, v) + q.w * v); }\n`\n\nfunction patchProgram<M extends ThreeNS.Material>(material: M, key: string, patchShader: (shader: { uniforms: Record<string, ThreeNS.IUniform>; vertexShader: string }) => void, inherit?: ThreeNS.Material): M {\n const previous = material.onBeforeCompile\n material.onBeforeCompile = (shader, renderer) => {\n previous?.call(material, shader, renderer)\n patchShader(shader)\n }\n const inherited = inherit?.customProgramCacheKey\n material.customProgramCacheKey = inherited && inherit ? () => `${inherited.call(inherit)}|${key}` : () => key\n return material\n}\n\n/**\n * `Material.clone()` drops `onBeforeCompile`, `customProgramCacheKey` and (for some classes) `defines`. A merged group must still look\n * like the material the host passed in (a light mask, a fog patch...), so the clone gets them back before the batch patch is chained on.\n */\nfunction cloneKeepingHooks(base: ThreeNS.Material): ThreeNS.Material {\n const clone = base.clone()\n if (base.defines) clone.defines = { ...(clone.defines ?? {}), ...base.defines }\n if (base.onBeforeCompile !== Object.getPrototypeOf(base).onBeforeCompile) clone.onBeforeCompile = base.onBeforeCompile\n clone.userData = { ...base.userData, mergedFrom: base.uuid }\n return clone\n}\n\n/** Material, depth material and distance material patched by the same function, so shadows follow the animation. */\nfunction patchedSet(THREE: ThreeLib, base: ThreeNS.Material, key: string, patchShader: (shader: { uniforms: Record<string, ThreeNS.IUniform>; vertexShader: string }) => void) {\n return {\n material: patchProgram(cloneKeepingHooks(base), key, patchShader, base),\n depth: patchProgram(new THREE.MeshDepthMaterial({ depthPacking: THREE.RGBADepthPacking }), key, patchShader),\n distance: patchProgram(new THREE.MeshDistanceMaterial(), key, patchShader),\n }\n}\n\ninterface Piece {\n geometry: ThreeNS.BufferGeometry\n matrix: ThreeNS.Matrix4\n /** Per-vertex extra attributes of this piece. */\n extra: Record<string, number[]>\n}\n\n/** Concatenates pieces (positions and normals moved by their matrices) into one geometry with the extra attributes. */\nfunction concat(THREE: ThreeLib, pieces: Piece[], extraSizes: Record<string, number>): ThreeNS.BufferGeometry {\n let verts = 0\n let idx = 0\n for (const p of pieces) {\n verts += p.geometry.getAttribute('position').count\n idx += p.geometry.index!.count\n }\n const pos = new Float32Array(verts * 3)\n const nor = new Float32Array(verts * 3)\n const col = new Float32Array(verts * 3)\n const uv = new Float32Array(verts * 2)\n const index = new Uint32Array(idx)\n const extra: Record<string, Float32Array> = {}\n for (const [k, n] of Object.entries(extraSizes)) extra[k] = new Float32Array(verts * n)\n const v = new THREE.Vector3()\n const n3 = new THREE.Matrix3()\n let vo = 0\n let io = 0\n for (const p of pieces) {\n const g = p.geometry\n const count = g.getAttribute('position').count\n n3.getNormalMatrix(p.matrix)\n const gp = g.getAttribute('position')\n const gn = g.getAttribute('normal')\n const gc = g.getAttribute('color')\n const gu = g.getAttribute('uv')\n for (let i = 0; i < count; i++) {\n v.fromBufferAttribute(gp, i).applyMatrix4(p.matrix)\n pos.set([v.x, v.y, v.z], (vo + i) * 3)\n v.fromBufferAttribute(gn, i).applyMatrix3(n3).normalize()\n nor.set([v.x, v.y, v.z], (vo + i) * 3)\n col.set([gc.getX(i), gc.getY(i), gc.getZ(i)], (vo + i) * 3)\n uv.set([gu.getX(i), gu.getY(i)], (vo + i) * 2)\n }\n for (const [k, values] of Object.entries(p.extra)) extra[k].set(values, vo * extraSizes[k])\n const gi = g.index!\n for (let i = 0; i < gi.count; i++) index[io + i] = gi.getX(i) + vo\n vo += count\n io += gi.count\n }\n const out = new THREE.BufferGeometry()\n out.setAttribute('position', new THREE.BufferAttribute(pos, 3))\n out.setAttribute('normal', new THREE.BufferAttribute(nor, 3))\n out.setAttribute('color', new THREE.BufferAttribute(col, 3))\n out.setAttribute('uv', new THREE.BufferAttribute(uv, 2))\n for (const [k, n] of Object.entries(extraSizes)) out.setAttribute(k, new THREE.BufferAttribute(extra[k], n))\n out.setIndex(new THREE.BufferAttribute(verts > 65535 ? index : new Uint16Array(index), 1))\n out.computeBoundingSphere()\n out.computeBoundingBox()\n return out\n}\n\nfunction meshesOf(group: ThreeNS.Object3D): ThreeNS.Mesh[] {\n return group.children.filter((c): c is ThreeNS.Mesh => (c as ThreeNS.Mesh).isMesh)\n}\n\n/**\n * Merges parts after `finish()` to cut draw calls (the `mergeParts` option of `createMeshApi`):\n *\n * - parts of one `PartSpec.group` with the same motion (blind slats) become one mesh per material whose vertices carry their own pivot;\n * `syncObject` poses them with one set of uniforms and the shadow materials follow;\n * - `cloth` soft volumes (clothes on a line) become one mesh per material with a per-vertex cloth index and anchor height; each\n * cloth keeps its own swing.\n *\n * The merged group is added to `root`, the part groups are removed, and `parts[id]` points at the merged group for every member.\n * Parts that carry a mirror, that are turned (`ry`) or that stand alone are left as they are.\n */\nexport function mergeParts(THREE: ThreeLib, root: ThreeNS.Group, parts: Record<string, ThreeNS.Group>, spec: ObjectSpec, options: MergeOptions = {}): void {\n const amplitude = options.swayAmplitude ?? 0.04\n const skip = new Set<string>()\n for (const m of spec.mirrors ?? []) if (m.part) skip.add(m.part)\n for (const [id, g] of Object.entries(parts)) if (Math.abs(g.rotation.y) > 1e-9 || g.userData.merged) skip.add(id)\n\n const take = (ids: string[]): ThreeNS.Group[] => ids.map((id) => parts[id])\n const retire = (ids: string[], merged: ThreeNS.Group): void => {\n for (const id of ids) {\n const g = parts[id]\n g.removeFromParent()\n for (const m of meshesOf(g)) m.geometry.dispose()\n parts[id] = merged\n }\n root.add(merged)\n }\n const byMaterial = (groups: ThreeNS.Group[], extra: (g: ThreeNS.Group, count: number) => Record<string, number[]>) => {\n const map = new Map<string, { base: ThreeNS.Material; pieces: Piece[]; mesh: ThreeNS.Mesh }>()\n for (const g of groups) {\n g.updateMatrix()\n for (const mesh of meshesOf(g)) {\n const key = `${String(mesh.userData.material)}|${(mesh.material as ThreeNS.Material).uuid}`\n let e = map.get(key)\n if (!e) {\n e = { base: mesh.material as ThreeNS.Material, pieces: [], mesh }\n map.set(key, e)\n }\n e.pieces.push({ geometry: mesh.geometry, matrix: g.matrix, extra: extra(g, mesh.geometry.getAttribute('position').count) })\n }\n }\n return map\n }\n const finishMesh = (name: string, key: string, geometry: ThreeNS.BufferGeometry, template: ThreeNS.Mesh, set: ReturnType<typeof patchedSet>, group: ThreeNS.Group): void => {\n const mesh = new THREE.Mesh(geometry, set.material)\n mesh.name = `${name}:${key}`\n mesh.userData.material = key\n mesh.castShadow = template.castShadow\n mesh.receiveShadow = template.receiveShadow\n mesh.customDepthMaterial = set.depth\n mesh.customDistanceMaterial = set.distance\n group.add(mesh)\n }\n\n // ---- cloth: one swing per soft volume, merged per material -------------------------------------------------------------------\n const cloths = (spec.soft ?? []).filter((s) => s.kind === 'cloth' && parts[s.part ?? s.id] && !skip.has(s.part ?? s.id))\n const clothPart = (s: { id: string; part?: string }) => s.part ?? s.id\n const toggled = new Set<string>()\n for (const i of spec.interactions ?? []) {\n const a = i.action as { do: string; part?: string }\n if (a.do === 'toggle_part' && a.part) toggled.add(a.part)\n }\n const swing = cloths.filter((s) => !toggled.has(clothPart(s)))\n if (swing.length >= 2) {\n const ids = swing.map(clothPart)\n const index: Record<string, number> = {}\n swing.forEach((s, i) => (index[s.id] = i))\n const indexOfPart = new Map(swing.map((s, i) => [clothPart(s), i]))\n const anchorOfPart = new Map(swing.map((s) => [clothPart(s), s.anchor[1]]))\n const n = swing.length\n const uniforms = { time: { value: 0 }, axz: { value: Array.from({ length: n }, () => new THREE.Vector2()) } }\n const groups = take(ids)\n const partIdOf = new Map(groups.map((g, i) => [g, ids[i]]))\n const plan = byMaterial(groups, (g, count) => {\n const id = partIdOf.get(g)!\n return { swayId: new Array(count).fill(indexOf(indexOfPart, id)), swayAnchor: new Array(count).fill(anchorOfPart.get(id) ?? 0) }\n })\n const merged = new THREE.Group() as unknown as MergedGroup\n merged.name = 'merge:cloth'\n const patch = (shader: { uniforms: Record<string, ThreeNS.IUniform>; vertexShader: string }): void => {\n shader.uniforms.swayTime = uniforms.time\n shader.uniforms.swayAxz = uniforms.axz\n shader.vertexShader =\n `attribute float swayId;\\nattribute float swayAnchor;\\nuniform float swayTime;\\nuniform vec2 swayAxz[${n}];\\n` +\n shader.vertexShader.replace(\n '#include <begin_vertex>',\n `#include <begin_vertex>\n{\n vec2 swayA = swayAxz[int(swayId + 0.5)];\n float swayHang = max(swayAnchor - position.y, 0.0);\n float swayFlutter = ${amplitude.toFixed(4)} * sin(swayTime * 1.9 + swayId * 1.7 + position.x * 4.0 + position.z * 3.0 + swayHang * 3.0);\n transformed.x += swayHang * (sin(swayA.y) + swayFlutter);\n transformed.z += swayHang * (-sin(swayA.x) + swayFlutter * 0.6);\n transformed.y += swayHang * (1.0 - cos(swayA.y) * cos(swayA.x));\n}`,\n )\n }\n for (const e of plan.values()) {\n const key = String(e.mesh.userData.material)\n const set = patchedSet(THREE, e.base, `swayBatch:${n}:${amplitude}`, patch)\n finishMesh('merge:cloth', key, concat(THREE, e.pieces, { swayId: 1, swayAnchor: 1 }), e.mesh, set, merged)\n }\n const batch: SwayBatch = {\n kind: 'sway',\n index,\n setAt(i, ax, az) {\n uniforms.axz.value[i].set(ax, az)\n },\n setTime(t) {\n uniforms.time.value = t\n },\n }\n merged.userData.merged = batch\n retire(ids, merged)\n for (const id of ids) skip.add(id)\n }\n\n // ---- grouped parts with identical motion (blind slats) -----------------------------------------------------------------------\n const bySignature = new Map<string, string[]>()\n for (const p of spec.parts ?? []) {\n if (!p.group || !parts[p.id] || skip.has(p.id)) continue\n const sig = `${p.group}|${JSON.stringify(p.motion)}`\n const list = bySignature.get(sig)\n if (list) list.push(p.id)\n else bySignature.set(sig, [p.id])\n }\n for (const [sig, ids] of bySignature) {\n if (ids.length < 2) continue\n const motion = (spec.parts ?? []).find((p) => p.id === ids[0])!.motion\n const uniforms = { q: { value: new THREE.Vector4(0, 0, 0, 1) }, pos: { value: new THREE.Vector3() }, scale: { value: new THREE.Vector3(1, 1, 1) } }\n const groups = take(ids)\n const plan = byMaterial(groups, (g, count) => {\n const pv = new Array<number>(count * 3)\n for (let i = 0; i < count; i++) {\n pv[i * 3] = g.position.x\n pv[i * 3 + 1] = g.position.y\n pv[i * 3 + 2] = g.position.z\n }\n return { batchPivot: pv }\n })\n const merged = new THREE.Group() as unknown as MergedGroup\n merged.name = `merge:${sig.split('|')[0]}`\n const patch = (shader: { uniforms: Record<string, ThreeNS.IUniform>; vertexShader: string }): void => {\n shader.uniforms.batchQ = uniforms.q\n shader.uniforms.batchPos = uniforms.pos\n shader.uniforms.batchScale = uniforms.scale\n let src = `attribute vec3 batchPivot;\\nuniform vec4 batchQ;\\nuniform vec3 batchPos;\\nuniform vec3 batchScale;\\n${QROT}${shader.vertexShader}`\n src = src.replace('#include <begin_vertex>', '#include <begin_vertex>\\ntransformed = batchPivot + batchRotate(batchQ, (position - batchPivot) * batchScale) + batchPos;')\n src = src.replace('#include <beginnormal_vertex>', '#include <beginnormal_vertex>\\nobjectNormal = batchRotate(batchQ, objectNormal);')\n shader.vertexShader = src\n }\n for (const e of plan.values()) {\n const key = String(e.mesh.userData.material)\n const set = patchedSet(THREE, e.base, 'poseBatch', patch)\n finishMesh(merged.name, key, concat(THREE, e.pieces, { batchPivot: 3 }), e.mesh, set, merged)\n }\n const batch: PoseBatch = {\n kind: 'pose',\n ids,\n apply(m, t) {\n const pose = poseOf(m, t)\n uniforms.q.value.set(pose.quaternion[0], pose.quaternion[1], pose.quaternion[2], pose.quaternion[3])\n uniforms.pos.value.set(pose.position[0], pose.position[1], pose.position[2])\n uniforms.scale.value.set(pose.scale[0], pose.scale[1], pose.scale[2])\n },\n }\n batch.apply(motion, 0)\n merged.userData.merged = batch\n retire(ids, merged)\n }\n}\n\nfunction indexOf(map: Map<string, number>, id: string): number {\n return map.get(id) ?? 0\n}\n","import { markKitOrigin } from '@adatechnology/game-contracts'\nimport { LIGHT_PRIORITY } from '@adatechnology/game-render-three'\nimport type { LightBudget, SourceHandle } from '@adatechnology/game-render-three'\nimport type { ObjectSpec } from '@adatechnology/game-interactables'\nimport { createShapeRecorder } from '@adatechnology/game-props3d'\nimport { colorChannels, generate } from '@adatechnology/game-shapes'\nimport type { ShapeBatch, ShapePoint } from '@adatechnology/game-shapes'\nimport type { HouseholdApi, MaterialKey, Quality, ThreeLib, ThreeNS } from './types'\nimport { MATERIAL_KEYS } from './types'\nimport { mergeParts } from './merge'\nimport { rememberRest } from './pose'\n\nexport type MaterialMap = Partial<Record<MaterialKey, ThreeNS.Material>>\n\nexport interface MeshApiOptions {\n /** Materials per key. Missing keys get a default (vertex-colored) material. */\n materials?: MaterialMap\n /** `high`: smoother cylinders and spheres, standard materials. `game`: fewer segments, Lambert materials. Default `high`. */\n quality?: Quality\n /** Cast and receive shadows on the merged meshes. Default true. */\n shadows?: boolean\n /** Make `plane` primitives double sided (applies to the default materials only). Default false. */\n doubleSidedPlanes?: boolean\n /**\n * Cut draw calls: `finish(spec)` merges the parts of one `PartSpec.group` that share a motion (blind slats) and the `cloth` soft\n * volumes (clothes on a line) into one mesh per material (see `mergeParts`). Needs the spec in `finish`. Default: on in `game` quality.\n */\n mergeParts?: boolean\n /** Idle flutter of merged cloth. Default 0.04. */\n swayAmplitude?: number\n /**\n * Material of one draw: called per mesh with its material key and the part id (`null` for the static body). Return the material the\n * host wants there (a per-room light mask, a flickering emissive...); the default is `materials[key]`. Merged groups (blind slats,\n * clothes) keep what you return, including its `onBeforeCompile`, `customProgramCacheKey`, `defines` and `userData`.\n */\n materialFor?: (key: MaterialKey, partId: string | null) => ThreeNS.Material | undefined\n /**\n * The host's light budget: each `api.light` becomes a source of it (it competes for a slot by distance, and following its bulb) instead of a `PointLight` of its own, so lamps coming and going\n * never change the lit materials' program key. Without it the lamps are real lights (deprecated, removed in the next major).\n */\n lights?: LightBudget\n}\n\nexport interface MeshResult {\n /** Root group: static meshes (one per material) plus one child group per part (merged parts share one group, see `mergeParts`). */\n group: ThreeNS.Group\n /** Part groups by id. Origin at the pivot, rotated by the part's `ry`; `userData.rest` holds the rest pose. A merged part maps to the shared group (`userData.merged`). */\n parts: Record<string, ThreeNS.Group>\n /** Real lights requested by the builders (already children of their group). Empty with `MeshApiOptions.lights`. */\n lights: ThreeNS.Light[]\n /** With `MeshApiOptions.lights`: the lamps as sources of the budget (`remove()` one when its lamp breaks or the object goes). Empty without it. */\n sources: SourceHandle[]\n}\n\nexport interface FinishOptions {\n /** Same as `MeshApiOptions.materialFor`; wins over it for this call. */\n materialFor?: MeshApiOptions['materialFor']\n}\n\nexport interface MeshApi {\n api: HouseholdApi\n /** Builds the groups from everything drawn so far. Call once; the api is spent afterwards. Pass the spec the pack returned to allow `mergeParts`. */\n finish(spec?: ObjectSpec, finishOptions?: FinishOptions): MeshResult\n /** Materials in use (defaults merged with the given ones). */\n materials: Record<MaterialKey, ThreeNS.Material>\n}\n\n/** Default materials: vertex colors everywhere (in `game` quality plain, metal, wood and fabric are one shared Lambert material), glass and water transparent, emissive unlit. */\nexport function createDefaultMaterials(THREE: ThreeLib, quality: Quality = 'high'): Record<MaterialKey, ThreeNS.Material> {\n // three warns about every parameter that is present but undefined: only pass the ones that are set\n const defined = <T extends object>(o: T): T => {\n const out = {} as T\n for (const k of Object.keys(o) as Array<keyof T>) if (o[k] !== undefined) out[k] = o[k]\n return out\n }\n const std = (p: ThreeNS.MeshStandardMaterialParameters): ThreeNS.Material =>\n quality === 'game'\n ? new THREE.MeshLambertMaterial(defined({ vertexColors: true, transparent: p.transparent, opacity: p.opacity, depthWrite: p.depthWrite, side: p.side }))\n : new THREE.MeshStandardMaterial(defined({ vertexColors: true, roughness: 0.85, metalness: 0, ...p }))\n // Lambert has no roughness or metalness: in `game` quality the opaque default materials are one and the same instance, so the\n // mesh api (which merges per material instance) draws plain, metal, wood and fabric together\n const solid = quality === 'game' ? std({}) : null\n return {\n plain: solid ?? std({}),\n metal: solid ?? std({ roughness: 0.35, metalness: 0.7 }),\n wood: solid ?? std({ roughness: 0.8 }),\n glass: std({ roughness: 0.05, transparent: true, opacity: 0.35, depthWrite: false }),\n fabric: solid ?? std({ roughness: 1 }),\n plant: std({ roughness: 0.9, side: THREE.DoubleSide }),\n water: std({ roughness: 0.1, transparent: true, opacity: 0.7, depthWrite: false }),\n emissive: new THREE.MeshBasicMaterial({ vertexColors: true }),\n atlas_picture: std({ roughness: 0.9 }),\n atlas_floor: std({ roughness: 1 }),\n }\n}\n\n/** The channels three stores for a hex here: linear (three r152+ with colour management on) or the raw sRGB numbers (off, or an older three). */\nconst colorSpaces = new WeakMap<object, 'linear' | 'srgb'>()\nfunction colorSpaceOf(THREE: ThreeLib): 'linear' | 'srgb' {\n const known = colorSpaces.get(THREE.Color)\n if (known) return known\n const space = detectColorSpace(THREE)\n colorSpaces.set(THREE.Color, space)\n return space\n}\nfunction detectColorSpace(THREE: ThreeLib): 'linear' | 'srgb' {\n const color = new THREE.Color()\n for (let c = 0; c < 256; c++) {\n color.setHex(c * 0x010101)\n if (color.r !== colorChannels(c * 0x010101, 'linear')[0]) {\n if (color.r !== c / 255) throw new Error('createMeshApi: this three.js stores colours in a space game-shapes does not know')\n return 'srgb'\n }\n }\n return 'linear'\n}\n\n/**\n * Mesh api for content packs. Every primitive is baked (vertex colors) into one `BufferGeometry` per material and per target\n * (the root, or a part), so the draw calls are the number of materials in use plus the same again for each part.\n *\n * The builders draw into a `game-shapes` schema (`createShapeRecorder` of game-props3d: unit templates, the item frame, parts and lights) and\n * `generate` bakes it; the bytes are the ones this api made with its own templates before (tests/exact-golden.test.ts).\n */\nexport function createMeshApi(THREE: ThreeLib, options: MeshApiOptions = {}): MeshApi {\n const quality: Quality = options.quality ?? 'high'\n const shadows = options.shadows !== false\n const materials = { ...createDefaultMaterials(THREE, quality), ...stripUndefined(options.materials) } as Record<MaterialKey, ThreeNS.Material>\n if (options.doubleSidedPlanes) for (const k of MATERIAL_KEYS) materials[k].side = THREE.DoubleSide\n let finished = false\n\n const recorder = quality === 'high' ? createShapeRecorder({ cylinderSegments: 12, sphereSegments: [12, 8] }) : createShapeRecorder({ cylinderSegments: 8, sphereSegments: [8, 6] })\n\n /** Keys that share a material instance share the geometry too: the first key stands for all of them. */\n const materialAlias: Record<string, string> = {}\n for (const k of MATERIAL_KEYS) {\n const first = MATERIAL_KEYS.find((o) => materials[o] === materials[k]) ?? k\n if (first !== k) materialAlias[k] = first\n }\n\n const lightsOut: ThreeNS.Light[] = []\n const sourcesOut: SourceHandle[] = []\n\n const build = (batches: readonly ShapeBatch[], points: readonly ShapePoint[], id: string | null, into: ThreeNS.Group, materialFor: MeshApiOptions['materialFor']): void => {\n for (const batch of batches) {\n const g = new THREE.BufferGeometry()\n g.setAttribute('position', new THREE.BufferAttribute(batch.positions, 3))\n g.setAttribute('normal', new THREE.BufferAttribute(batch.normals, 3))\n g.setAttribute('color', new THREE.BufferAttribute(batch.colors as Float32Array, 3))\n g.setAttribute('uv', new THREE.BufferAttribute(batch.uvs, 2))\n g.setIndex(new THREE.BufferAttribute(batch.indices as Uint16Array | Uint32Array, 1))\n g.computeBoundingSphere()\n g.computeBoundingBox()\n const key = batch.material as MaterialKey\n const mesh = new THREE.Mesh(g, materialFor?.(key, id) ?? materials[key])\n mesh.name = id === null ? `static:${key}` : `part:${id}`\n if (id !== null) mesh.userData.part = id\n mesh.userData.material = key\n mesh.castShadow = shadows\n mesh.receiveShadow = shadows\n into.add(mesh)\n }\n for (const point of points) {\n const l = point.data as { color: number; range: number }\n if (options.lights) {\n const bulb = new THREE.Object3D()\n bulb.name = 'lamp'\n bulb.position.set(point.position[0], point.position[1], point.position[2])\n into.add(bulb)\n sourcesOut.push(options.lights.addSource({ owner: 'interactables.lamp', priority: LIGHT_PRIORITY.lamp, color: l.color, intensity: 1, distance: l.range, position: [0, 0, 0], follow: bulb }))\n continue\n }\n // eslint-disable-next-line no-restricted-syntax -- D2: legacy path without LightBudget, removed in the next major\n const light = new THREE.PointLight(l.color, 1, l.range)\n light.position.set(point.position[0], point.position[1], point.position[2])\n into.add(light)\n lightsOut.push(light)\n }\n }\n\n return {\n api: recorder.api,\n materials,\n finish(spec, finishOptions) {\n const materialFor = finishOptions?.materialFor ?? options.materialFor\n if (finished) throw new Error('createMeshApi: finish() was already called')\n finished = true\n const buffers = generate(recorder.schema(), {\n colors: colorSpaceOf(THREE),\n materials: MATERIAL_KEYS,\n fallbackMaterial: 'plain',\n materialAlias,\n validate: false,\n key: false,\n })\n const batches = buffers.levels[0]?.batches ?? []\n const of = (id: string | null): { batches: ShapeBatch[]; points: ShapePoint[] } => ({ batches: batches.filter((b) => b.part === id), points: buffers.points.filter((p) => p.part === id) })\n const group = markKitOrigin(new THREE.Group(), { owner: 'game-interactables-three', kind: 'furniture', ...(spec ? { id: spec.type } : {}) })\n group.name = 'interactable'\n const parts: Record<string, ThreeNS.Group> = {}\n const body = of(null)\n build(body.batches, body.points, null, group, materialFor)\n for (const part of recorder.parts) {\n const g = new THREE.Group()\n g.name = `part:${part.id}`\n g.userData.part = part.id\n g.position.set(part.pivot[0], part.pivot[1], part.pivot[2])\n g.rotation.y = part.ry\n g.updateMatrix()\n const drawn = of(part.id)\n build(drawn.batches, drawn.points, part.id, g, materialFor)\n rememberRest(g)\n group.add(g)\n parts[part.id] = g\n }\n if (spec && (options.mergeParts ?? quality === 'game')) mergeParts(THREE, group, parts, spec, { swayAmplitude: options.swayAmplitude })\n if (spec) tagDestruction(group, parts, spec)\n return { group, parts, lights: lightsOut, sources: sourcesOut }\n },\n }\n}\n\n/**\n * Copies the spec's destruction tags onto the objects: `userData.destructionMaterial` (SDK material id) and, on part groups,\n * `userData.destructionPart` (the part id, the key @adatechnology/game-destruction-three binds by), `userData.destructionPiece` and\n * `userData.destructionJoint`. Static meshes get the object default; the meshes of a part get the part's tags.\n * Parts merged into a shared group (blind slats) carry the tags of the first part of the group.\n */\nfunction tagDestruction(group: ThreeNS.Group, parts: Record<string, ThreeNS.Group>, spec: ObjectSpec): void {\n const body = spec.destruction\n const partGroups = new Set<ThreeNS.Object3D>(Object.values(parts))\n if (body) {\n for (const child of group.children) {\n if (!partGroups.has(child) && (child as ThreeNS.Mesh).isMesh) child.userData.destructionMaterial = body.material\n }\n }\n const done = new Set<ThreeNS.Object3D>()\n for (const ps of spec.parts ?? []) {\n const g = parts[ps.id]\n const d = ps.destruction\n if (!g || !d || done.has(g)) continue\n done.add(g)\n for (const o of [g, ...g.children]) {\n if (o !== g && !(o as ThreeNS.Mesh).isMesh) continue\n o.userData.destructionMaterial = d.material\n o.userData.destructionPart = ps.id\n if (d.piece) o.userData.destructionPiece = d.piece\n if (d.joint) o.userData.destructionJoint = d.joint\n }\n }\n}\n\nfunction stripUndefined<T extends object>(o: T | undefined): Partial<T> {\n const out: Partial<T> = {}\n if (o) for (const k of Object.keys(o) as Array<keyof T>) if (o[k] !== undefined) out[k] = o[k]\n return out\n}\n","import type { ThreeLib, ThreeNS, Vec3 } from './types'\n\n/** `Reflector` from `three/examples/jsm/objects/Reflector.js`, passed by the host. */\nexport type ReflectorConstructor = new (geometry: ThreeNS.BufferGeometry, options?: { textureWidth?: number; textureHeight?: number; clipBias?: number; color?: ThreeNS.ColorRepresentation }) => ThreeNS.Mesh\n\nexport interface MirrorOptions {\n /** Without it every mirror shows the env map only. */\n Reflector?: ReflectorConstructor\n /** Cube / equirect env map for the cheap look. */\n envMap?: ThreeNS.Texture | null\n /** Real reflectors alive at once. Default 1. */\n max?: number\n /** Reflector texture size in pixels. Default 512. */\n resolution?: number\n /** Where reflectors are added. Default: the root ancestor of the registered mesh. */\n scene?: ThreeNS.Object3D\n /** Distance in front of the plane, meters. Default 0.001. */\n offset?: number\n /**\n * three's `Reflector` renders the scene again inside the main `render()`, and that nested render resets `renderer.info` when\n * `info.autoReset` is on, so the counters would only show the reflector pass. With `keepInfo` (default true) the reflector adds\n * what was counted before it back, so `renderer.info.render.calls` covers the whole frame. Skipped when `info.autoReset` is off.\n */\n keepInfo?: boolean\n}\n\n/** The mirror entries of `world.mirrors(...)` (only these fields are read). */\nexport interface MirrorCandidate {\n id: string\n center: Vec3\n normal: Vec3\n}\n\nexport interface Mirrors {\n /** Registers the display mesh of a mirror (world id `objectId:mirrorId`). It gets the env-map material. `normal` is the world normal; default: the mesh's +z. */\n register(id: string, mesh: ThreeNS.Mesh, normal?: Vec3): void\n unregister(id: string): void\n /** Activates real reflectors for the nearest facing registered mirrors among `candidates`. Returns the active ids. */\n update(camera: ThreeNS.Camera, candidates: readonly MirrorCandidate[]): string[]\n /** Ids that currently have a real reflector. */\n active(): string[]\n /** The shared env-map material registered meshes use. */\n envMaterial: ThreeNS.Material\n dispose(): void\n}\n\ninterface Entry {\n mesh: ThreeNS.Mesh\n normal: Vec3 | null\n original: ThreeNS.Material | ThreeNS.Material[]\n}\n\ninterface Slot {\n reflector: ThreeNS.Mesh\n id: string | null\n}\n\n/**\n * Mirrors: every registered mirror shows an env-map material; the nearest `max` that face the camera get a real `Reflector`\n * (pooled and reused), placed `offset` in front of the plane with the mesh's rotation and size.\n */\nexport function createMirrors(THREE: ThreeLib, options: MirrorOptions = {}): Mirrors {\n const max = Math.max(0, Math.floor(options.max ?? 1))\n const resolution = options.resolution ?? 512\n const offset = options.offset ?? 0.001\n const envMaterial = new THREE.MeshStandardMaterial({ color: 0xdfe6ea, metalness: 1, roughness: 0.04, envMap: options.envMap ?? null })\n const entries = new Map<string, Entry>()\n const slots: Slot[] = []\n const geometry = new THREE.PlaneGeometry(1, 1)\n\n const worldNormal = (e: Entry, out: ThreeNS.Vector3): ThreeNS.Vector3 => {\n if (e.normal) return out.set(e.normal[0], e.normal[1], e.normal[2]).normalize()\n e.mesh.updateWorldMatrix(true, false)\n return out.set(0, 0, 1).transformDirection(e.mesh.matrixWorld)\n }\n\n const slotFor = (id: string): Slot | null => {\n if (!options.Reflector) return null\n const existing = slots.find((s) => s.id === id)\n if (existing) return existing\n const free = slots.find((s) => s.id === null)\n if (free) return free\n if (slots.length >= max) return null\n const reflector = new options.Reflector(geometry, { textureWidth: resolution, textureHeight: resolution, clipBias: 0.003 })\n reflector.name = 'mirror:reflector'\n if (options.keepInfo !== false) {\n const original = reflector.onBeforeRender\n reflector.onBeforeRender = function (this: ThreeNS.Mesh, renderer, scene, camera, geo, mat, grp) {\n const info = renderer.info\n if (!info.autoReset) return original.call(this, renderer, scene, camera, geo, mat, grp)\n const before = { calls: info.render.calls, triangles: info.render.triangles, points: info.render.points, lines: info.render.lines }\n original.call(this, renderer, scene, camera, geo, mat, grp)\n info.render.calls += before.calls\n info.render.triangles += before.triangles\n info.render.points += before.points\n info.render.lines += before.lines\n }\n }\n reflector.visible = false\n const s: Slot = { reflector, id: null }\n slots.push(s)\n return s\n }\n\n const release = (s: Slot): void => {\n s.id = null\n s.reflector.visible = false\n }\n\n const pos = new THREE.Vector3()\n const quat = new THREE.Quaternion()\n const scl = new THREE.Vector3()\n const n = new THREE.Vector3()\n const toEye = new THREE.Vector3()\n const box = new THREE.Box3()\n const size = new THREE.Vector3()\n const facing = new THREE.Vector3()\n const flip = new THREE.Quaternion(0, 1, 0, 0)\n\n return {\n envMaterial,\n register(id, mesh, normal) {\n const previous = entries.get(id)\n entries.set(id, { mesh, normal: normal ?? null, original: previous ? previous.original : mesh.material })\n mesh.material = envMaterial\n },\n unregister(id) {\n const e = entries.get(id)\n const s = slots.find((x) => x.id === id)\n if (s) release(s)\n if (e) e.mesh.material = e.original\n entries.delete(id)\n },\n update(camera, candidates) {\n const eye = camera.getWorldPosition(toEye)\n const wanted: Array<{ id: string; d: number }> = []\n for (const c of candidates) {\n const e = entries.get(c.id)\n if (!e) continue\n n.set(c.normal[0], c.normal[1], c.normal[2])\n const dx = eye.x - c.center[0]\n const dy = eye.y - c.center[1]\n const dz = eye.z - c.center[2]\n if (n.x * dx + n.y * dy + n.z * dz <= 0) continue\n wanted.push({ id: c.id, d: dx * dx + dy * dy + dz * dz })\n }\n wanted.sort((a, b) => a.d - b.d)\n const chosen = new Set<string>()\n for (let i = 0; i < wanted.length && chosen.size < max; i++) chosen.add(wanted[i].id)\n for (const s of slots) if (s.id !== null && !chosen.has(s.id)) release(s)\n for (const id of chosen) {\n const s = slotFor(id)\n if (!s) continue\n const e = entries.get(id)!\n e.mesh.updateWorldMatrix(true, false)\n e.mesh.matrixWorld.decompose(pos, quat, scl)\n if (!e.mesh.geometry.boundingBox) e.mesh.geometry.computeBoundingBox()\n box.copy(e.mesh.geometry.boundingBox!)\n box.getSize(size)\n worldNormal(e, n)\n s.reflector.position.copy(pos).addScaledVector(n, offset)\n s.reflector.quaternion.copy(quat)\n // a Reflector only renders for a camera in front of its +z: when the registered normal points the other way, turn it around\n if (facing.set(0, 0, 1).applyQuaternion(quat).dot(n) < 0) s.reflector.quaternion.multiply(flip)\n s.reflector.scale.set(Math.max(size.x * scl.x, 1e-3), Math.max(size.y * scl.y, 1e-3), 1)\n s.id = id\n s.reflector.visible = true\n if (!s.reflector.parent) {\n let top: ThreeNS.Object3D = e.mesh\n while (top.parent) top = top.parent\n ;(options.scene ?? top).add(s.reflector)\n }\n }\n return slots.filter((s) => s.id !== null).map((s) => s.id as string)\n },\n active() {\n return slots.filter((s) => s.id !== null).map((s) => s.id as string)\n },\n dispose() {\n for (const s of slots) s.reflector.removeFromParent()\n slots.length = 0\n for (const e of entries.values()) e.mesh.material = e.original\n entries.clear()\n envMaterial.dispose()\n geometry.dispose()\n },\n }\n}\n","import type { CanvasFactory, ThreeLib, ThreeNS } from './types'\n\nexport interface ScreenOptions {\n /** Where the canvas is created (the global `document`, or a fake). */\n document: CanvasFactory\n /** Canvas size in pixels. Default 128 x 72. */\n w?: number\n h?: number\n /** Seed of the static noise. Default 1. */\n seed?: number\n /** Redraws per second while on. Default 15. */\n fps?: number\n /** Emissive strength while on. Default 1. */\n intensity?: number\n}\n\nexport interface Screen {\n material: ThreeNS.MeshStandardMaterial\n texture: ThreeNS.CanvasTexture\n canvas: HTMLCanvasElement\n readonly on: boolean\n readonly channel: number\n setOn(on: boolean): void\n setChannel(channel: number): void\n /** Advance the animation; redraws the canvas at `fps` while on. */\n update(dt: number): void\n dispose(): void\n}\n\nconst BARS = ['#c0c0c0', '#c0c000', '#00c0c0', '#00c000', '#c000c0', '#c00000', '#0000c0']\n\nfunction mulberry(seed: number): () => number {\n let a = seed >>> 0\n return () => {\n a = (a + 0x6d2b79f5) >>> 0\n let t = a\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/**\n * A screen material backed by a canvas texture. Off: black canvas, no emission. On: the channel's content (0 color bars,\n * 1 static noise, 2 animated landscape, higher numbers repeat them with shifted hues) lights the surface.\n */\nexport function createScreen(THREE: ThreeLib, options: ScreenOptions): Screen {\n const w = options.w ?? 128\n const h = options.h ?? 72\n const canvas = options.document.createElement('canvas')\n canvas.width = w\n canvas.height = h\n // the canvas is uploaded to the GPU every redraw: tell the browser so it keeps a CPU-backed canvas (no readback warning)\n const ctx = canvas.getContext('2d', { willReadFrequently: true })\n const rand = mulberry(options.seed ?? 1)\n const period = 1 / Math.max(1, options.fps ?? 15)\n const intensity = options.intensity ?? 1\n\n const texture = new THREE.CanvasTexture(canvas)\n texture.colorSpace = THREE.SRGBColorSpace\n const material = new THREE.MeshStandardMaterial({\n color: 0x050505,\n roughness: 0.25,\n metalness: 0,\n map: texture,\n emissive: 0xffffff,\n emissiveMap: texture,\n emissiveIntensity: 0,\n })\n\n let on = false\n let channel = 0\n let clock = 0\n let acc = 0\n\n const fill = (style: string, x: number, y: number, rw: number, rh: number): void => {\n if (!ctx) return\n ctx.fillStyle = style\n ctx.fillRect(x, y, rw, rh)\n }\n\n const drawBars = (): void => {\n const n = BARS.length\n for (let i = 0; i < n; i++) fill(BARS[(i + channel) % n], Math.floor((i * w) / n), 0, Math.ceil(w / n), Math.floor(h * 0.75))\n fill('#101010', 0, Math.floor(h * 0.75), w, h - Math.floor(h * 0.75))\n const x = ((clock * 20) % (w + 12)) - 12\n fill('#ffffff', x, Math.floor(h * 0.8), 12, Math.floor(h * 0.1))\n }\n\n const drawNoise = (): void => {\n const cell = Math.max(2, Math.floor(w / 48))\n for (let y = 0; y < h; y += cell) {\n for (let x = 0; x < w; x += cell) {\n const g = Math.floor(rand() * 220 + 20)\n fill(`rgb(${g},${g},${g})`, x, y, cell, cell)\n }\n }\n }\n\n const drawScene = (): void => {\n const hue = (channel * 40) % 360\n fill(`hsl(${(200 + hue) % 360},60%,${55 + 10 * Math.sin(clock * 0.3)}%)`, 0, 0, w, Math.floor(h * 0.65))\n fill(`hsl(${(110 + hue) % 360},40%,32%)`, 0, Math.floor(h * 0.65), w, h - Math.floor(h * 0.65))\n const sx = (0.5 + 0.4 * Math.sin(clock * 0.5)) * w\n const sy = (0.3 - 0.15 * Math.cos(clock * 0.5)) * h\n fill('#ffe680', sx - 5, sy - 5, 10, 10)\n const bx = (clock * 25) % (w + 16) - 16\n fill('#e03030', bx, Math.floor(h * 0.7), 16, 10)\n }\n\n const draw = (): void => {\n if (!on) {\n fill('#000000', 0, 0, w, h)\n } else if (channel % 3 === 0) {\n drawBars()\n } else if (channel % 3 === 1) {\n drawNoise()\n } else {\n drawScene()\n }\n texture.needsUpdate = true\n }\n\n const apply = (): void => {\n material.emissiveIntensity = on ? intensity : 0\n material.color.setHex(on ? 0x111111 : 0x000000)\n draw()\n }\n apply()\n\n return {\n material,\n texture,\n canvas,\n get on() {\n return on\n },\n get channel() {\n return channel\n },\n setOn(value) {\n on = !!value\n acc = 0\n apply()\n },\n setChannel(n) {\n channel = Math.max(0, Math.floor(n))\n if (on) draw()\n },\n update(dt) {\n if (!on) return\n clock += dt\n acc += dt\n if (acc >= period) {\n acc %= period\n draw()\n }\n },\n dispose() {\n texture.dispose()\n material.dispose()\n },\n }\n}\n","import type { ThreeLib, ThreeNS } from './types'\n\nexport interface SwayOptions {\n /** Strength of the idle flutter (wind) in meters per meter of hang, 0..1. Default 0.04. */\n amplitude?: number\n /** Local y (geometry space) of the line the cloth hangs from. Default: top of the mesh's bounding box. */\n anchorY?: number\n}\n\nexport interface SwayUniforms {\n time: { value: number }\n ax: { value: number }\n az: { value: number }\n anchorY: { value: number }\n}\n\n/** Shared state of one swaying mesh: the three materials read the same uniforms. */\nexport interface SwayHandle {\n uniforms: SwayUniforms\n /** Sets the swing (radians around x and z) and, optionally, the clock for the idle flutter. */\n set(ax: number, az: number, time?: number): void\n}\n\nconst VERTEX_PATCH = /* glsl */ `\n#include <begin_vertex>\n{\n float swayHang = max(swayAnchorY - position.y, 0.0);\n float swayFlutter = swayAmplitude * sin(swayTime * 1.9 + position.x * 4.0 + position.z * 3.0 + swayHang * 3.0);\n transformed.x += swayHang * (sin(swayAz) + swayFlutter);\n transformed.z += swayHang * (-sin(swayAx) + swayFlutter * 0.6);\n transformed.y += swayHang * (1.0 - cos(swayAz) * cos(swayAx));\n}\n`\n\nconst VERTEX_UNIFORMS = /* glsl */ `\nuniform float swayTime;\nuniform float swayAx;\nuniform float swayAz;\nuniform float swayAnchorY;\nuniform float swayAmplitude;\n`\n\n/** Patches a vertex shader so the vertices hang from the anchor line and swing with the shared uniforms. */\nexport function patchSwayShader(shader: { uniforms: Record<string, ThreeNS.IUniform>; vertexShader: string }, uniforms: SwayUniforms, amplitude: number): void {\n shader.uniforms.swayTime = uniforms.time\n shader.uniforms.swayAx = uniforms.ax\n shader.uniforms.swayAz = uniforms.az\n shader.uniforms.swayAnchorY = uniforms.anchorY\n shader.uniforms.swayAmplitude = { value: amplitude }\n shader.vertexShader = VERTEX_UNIFORMS + shader.vertexShader.replace('#include <begin_vertex>', VERTEX_PATCH)\n}\n\nfunction patch<M extends ThreeNS.Material>(material: M, uniforms: SwayUniforms, amplitude: number): M {\n const previous = material.onBeforeCompile\n material.onBeforeCompile = (shader, renderer) => {\n previous?.call(material, shader, renderer)\n patchSwayShader(shader, uniforms, amplitude)\n }\n material.customProgramCacheKey = () => `sway:${amplitude}`\n return material\n}\n\n/**\n * Clones `base` and patches it with a vertex-shader sway (uniforms `time`, `ax`, `az`, `anchorY`). The returned material carries\n * `userData.sway` (a `SwayHandle`), `customDepthMaterial` and `customDistanceMaterial` patched with the same uniforms, so\n * directional / spot shadow maps and point-light shadow cubes follow the animation. Assign it to a mesh, and set\n * `mesh.customDepthMaterial` / `mesh.customDistanceMaterial` from the material (or just use `attachSway`).\n */\n/** A material `swayMaterial` made: it carries its sway handle and the shadow materials that follow it. */\nexport type SwayMaterial = ThreeNS.Material & {\n userData: { sway: SwayHandle; depthMaterial: ThreeNS.MeshDepthMaterial; distanceMaterial: ThreeNS.MeshDistanceMaterial }\n}\n\nexport function swayMaterial(THREE: ThreeLib, base: ThreeNS.Material, options: SwayOptions = {}): SwayMaterial {\n const amplitude = options.amplitude ?? 0.04\n const uniforms: SwayUniforms = { time: { value: 0 }, ax: { value: 0 }, az: { value: 0 }, anchorY: { value: options.anchorY ?? 0 } }\n const handle: SwayHandle = {\n uniforms,\n set(ax, az, time) {\n uniforms.ax.value = ax\n uniforms.az.value = az\n if (time !== undefined) uniforms.time.value = time\n },\n }\n const material = patch(base.clone(), uniforms, amplitude)\n const depth = patch(new THREE.MeshDepthMaterial({ depthPacking: THREE.RGBADepthPacking }), uniforms, amplitude)\n const distance = patch(new THREE.MeshDistanceMaterial(), uniforms, amplitude)\n material.userData.sway = handle\n material.userData.depthMaterial = depth\n material.userData.distanceMaterial = distance\n return material as SwayMaterial // userData filled just above\n}\n\n/**\n * Makes a mesh sway: replaces its material with `swayMaterial(...)` and sets `customDepthMaterial` and `customDistanceMaterial`.\n * `mesh.userData.sway` is the handle `syncObject` drives. Returns the handle.\n */\nexport function attachSway(THREE: ThreeLib, mesh: ThreeNS.Mesh, options: SwayOptions = {}): SwayHandle {\n let anchorY = options.anchorY\n if (anchorY === undefined) {\n if (!mesh.geometry.boundingBox) mesh.geometry.computeBoundingBox()\n anchorY = mesh.geometry.boundingBox ? mesh.geometry.boundingBox.max.y : 0\n }\n const base = Array.isArray(mesh.material) ? mesh.material[0] : mesh.material\n const m = swayMaterial(THREE, base, { ...options, anchorY })\n m.userData.sway.uniforms.anchorY.value = anchorY\n mesh.material = m\n mesh.customDepthMaterial = m.userData.depthMaterial\n mesh.customDistanceMaterial = m.userData.distanceMaterial\n mesh.userData.sway = m.userData.sway\n return m.userData.sway\n}\n","import type { Motion, World } from '@adatechnology/game-interactables'\nimport type { LightBudget } from '@adatechnology/game-render-three'\nimport { createEffects, type Effects, type EffectsOptions } from './effects'\nimport { createCord, catenary } from '@adatechnology/game-shapes-three'\nimport { createMeshApi, type MeshApi, type MeshApiOptions } from './mesh'\nimport { createMirrors, type MirrorOptions, type Mirrors } from './mirrors'\nimport { applyPose, syncObject, type SyncOptions } from './pose'\nimport { createScreen, type Screen, type ScreenOptions } from './screen'\nimport { attachSway, swayMaterial, type SwayHandle, type SwayOptions } from './sway'\nimport type { CanvasFactory, Quality, ThreeLib, ThreeNS, Vec3 } from './types'\n\nexport interface ThreeAdapterOptions {\n /** Default for `createMeshApi` / `createScreen` / `createEffects`. */\n document?: CanvasFactory\n quality?: Quality\n materials?: MeshApiOptions['materials']\n /** `Reflector` and env map for `createMirrors`. */\n Reflector?: MirrorOptions['Reflector']\n envMap?: MirrorOptions['envMap']\n /** The host's light budget: the lamps of every mesh api this adapter makes become sources of it (see `MeshApiOptions.lights`). */\n lights?: LightBudget\n}\n\n/** The package functions with `THREE` and the common options already bound. */\nexport interface ThreeAdapter {\n THREE: ThreeLib\n createMeshApi(options?: MeshApiOptions): MeshApi\n applyPose<G extends ThreeNS.Object3D>(group: G, motion: Motion, t: number): G\n syncObject(world: World, objectId: string, parts: Record<string, ThreeNS.Object3D>, options?: SyncOptions): void\n swayMaterial(base: ThreeNS.Material, options?: SwayOptions): ReturnType<typeof swayMaterial>\n attachSway(mesh: ThreeNS.Mesh, options?: SwayOptions): SwayHandle\n createEffects(options: EffectsOptions): Effects\n /** @deprecated Pass `{ scene, camera, ... }`. */\n createEffects(scene: ThreeNS.Object3D, options?: Partial<EffectsOptions>): Effects\n createScreen(options?: Partial<ScreenOptions>): Screen\n createMirrors(options?: MirrorOptions): Mirrors\n createCord(points: readonly Vec3[], radius: number, material: ThreeNS.Material): ThreeNS.Mesh\n catenary: typeof catenary\n}\n\n/** Binds the host's `THREE` (and shared options) once, so the rest of the host code never passes it around. */\nexport function createThreeAdapter(THREE: ThreeLib, options: ThreeAdapterOptions = {}): ThreeAdapter {\n return {\n THREE,\n createMeshApi: (o = {}) => createMeshApi(THREE, { quality: options.quality, materials: options.materials, ...(options.lights ? { lights: options.lights } : {}), ...o }),\n applyPose: (group, motion, t) => applyPose(THREE, group, motion, t),\n syncObject: (world, objectId, parts, o) => syncObject(THREE, world, objectId, parts, o),\n swayMaterial: (base, o) => swayMaterial(THREE, base, o),\n attachSway: (mesh, o) => attachSway(THREE, mesh, o),\n createEffects: ((a: EffectsOptions | ThreeNS.Object3D, o: Partial<EffectsOptions> = {}) =>\n (a as ThreeNS.Object3D).isObject3D\n ? createEffects(THREE, a as ThreeNS.Object3D, { document: options.document, ...o })\n : createEffects(THREE, { document: options.document, ...(a as EffectsOptions) })) as ThreeAdapter['createEffects'],\n createScreen: (o = {}) => {\n const document = o.document ?? options.document\n if (!document) throw new Error('createScreen needs a document (option of createThreeAdapter or of the call)')\n return createScreen(THREE, { ...o, document })\n },\n createMirrors: (o = {}) => createMirrors(THREE, { Reflector: options.Reflector, envMap: options.envMap, ...o }),\n createCord: (points, radius, material) => createCord(THREE, points, radius, material),\n catenary,\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,kBAAAA;AAAA,IAAA;AAAA;AAAA;AAAA;;;AEqBO,MAAM,wBAAiE,EAAE,UAAU,KAAK,MAAM,MAAM,UAAU,KAAK,aAAa,OAAO,IAAO;AEVrJ,MAAM,gCAAgC,OAAO;AUiCtC,MAAM,oBAAoB;IAC/B,oBAAoB;IACpB,cAAc;IACd,cAAc;IACd,gBAAgB;EAClB;AAGO,MAAM,oBAAoB;IAC/B,CAAC,kBAAkB,kBAAkB,GAAG,EAAE,UAAU,KAAK,OAAO,UAAU;IAC1E,CAAC,kBAAkB,YAAY,GAAG,EAAE,UAAU,KAAK,OAAO,UAAU;IACpE,CAAC,kBAAkB,YAAY,GAAG,EAAE,UAAU,KAAK,OAAO,QAAQ,WAAW,EAAE;IAC/E,CAAC,kBAAkB,cAAc,GAAG,EAAE,UAAU,KAAK,OAAO,OAAO;EACrE;ACzDA,MAAM,aAAa,uBAAO,IAAI,sCAAsC;AAIpE,MAAM,aAAa,MAAmB;AACpC,UAAM,SAAS;AACf,WAAQ,OAAA,UAAA,MAAA,OAAA,UAAA,IAAuB,oBAAI,IAAY;EACjD;AAKA,MAAM,eAAe,MAAe;AAClC,QAAI;AACF,aAAO,QAAQ,IAAI,aAAa;IAClC,QAAQ;AACN,aAAO;IACT;EACF;AAEA,MAAM,UAAU,MAAgB,WAAgD,uBAAuB;AAMhG,MAAM,WAAW,CAAC,KAAa,YAA0B;AAC9D,QAAI,QAAQ,KAAK,aAAa,EAAG;AACjC,UAAM,OAAO,WAAW;AACxB,QAAI,KAAK,IAAI,GAAG,EAAG;AACnB,SAAK,IAAI,GAAG;AACZ,YAAQ,KAAK,cAAc,OAAO,EAAE;EACtC;AK1BO,MAAM,iBAAiB;AAyBvB,WAAS,cAAiBC,SAAW,QAAsB;AAChE,UAAM,OAAOA;AACb,SAAK,WAAW,KAAK,YAAY,CAAC;AAClC,SAAK,SAAS,cAAc,IAAI,EAAE,GAAG,OAAO;AAC5C,WAAOA;EACT;AE7BO,MAAM,cAAc,CAAC,YAAY,WAAW,QAAQ,QAAQ,QAAQ,SAAS,UAAU,QAAQ,SAAS,SAAS,SAAS,QAAQ;AAOlI,MAAM,WAAW,CAAC,QAAQ,KAAK;AAI/B,MAAM,cAAc,CAAC,SAAS,OAAO;AAoBrC,MAAM,iBAAiB;IAC5B;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;EACF;AAMO,MAAM,kBAAkB,CAAC,SAAoB,SAAkC,YAAY,OAAO,IAAI,IAAI;AAI1G,MAAM,qBAAiD,YAAY,QAAQ,CAAC,YAAY,SAAS,IAAI,CAAC,SAAS,gBAAgB,SAAS,IAAI,CAAC,CAAC;AAC9I,MAAM,iBAAyC,YAAY,QAAQ,CAAC,YAAY,YAAY,IAAI,CAAC,YAAyB,QAAQ,OAAO,IAAI,OAAO,EAAE,CAAC;AACvJ,MAAM,iBAAyC,CAAC,GAAG,oBAAoB,GAAG,gBAAgB,GAAG,cAAc;;;AEpDlH,MAAM,UAAU,CAAC,UAA0B,KAAK,MAAM,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,KAAK,CAAC,IAAI,GAAG;AAC3F,MAAM,MAAM,CAAC,KAAa,OAAe,SAA0B,QAAQ,GAAG,KAAK,KAAO,QAAQ,KAAK,KAAK,IAAK,QAAQ,IAAI;AAE7H,MAAM,YAAyD,EAAE,OAAO,iBAAiB,OAAO,iBAAiB,MAAM,iBAAiB,OAAO,kBAAkB;AAG1J,MAAM,eAAe,CAAC,SAA4B,UAAU,IAAI,MAAM;AAGtE,WAAS,sBAAsB,UAAiD;AACrF,UAAM,EAAE,MAAM,MAAM,KAAK,IAAI;AAC7B,UAAM,OAAO,OAAO,MAAM,MAAM,IAAI;AACpC,WAAO;AAAA,MACL,QAAQ,UAAU,IAAI;AAAA,MACtB,OAAO,SAAS;AAAA,MAChB,QAAQ;AAAA,QACN,IAAI,SAAS,SAAS,CAAC;AAAA,QACvB,IAAI,SAAS,SAAS,CAAC;AAAA,QACvB,IAAI,SAAS,SAAS,CAAC;AAAA,QACvB,MAAM,SAAS;AAAA,QACf,MAAM,SAAS;AAAA,QACf,MAAM,SAAS,UAAU,SAAS;AAAA,QAClC,OAAO,KAAK;AAAA,QACZ,OAAO,KAAK;AAAA,QACZ,SAAS;AAAA,QACT,MAAM;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,WAAS,OAAO,MAAgB,MAAc,MAAgD;AAC5F,QAAI,SAAS,QAAS,QAAO,EAAE,OAAO,IAAI,MAAM,MAAM,IAAI,GAAG,OAAO,KAAK;AACzE,QAAI,SAAS,QAAS,QAAO,EAAE,OAAO,IAAI,MAAM,MAAM,IAAI,GAAG,OAAO,IAAI;AACxE,QAAI,SAAS,QAAS,QAAO,EAAE,OAAO,IAAI,GAAG,MAAM,GAAG,GAAG,OAAO,EAAE;AAClE,QAAI,KAAM,QAAO,EAAE,OAAO,IAAI,OAAO,OAAO,MAAM,MAAM,MAAM,MAAM,CAAC,GAAG,OAAO,KAAK;AACpF,WAAO,EAAE,OAAO,IAAI,GAAG,KAAK,IAAI,MAAM,OAAO,OAAO,GAAG,GAAG,IAAI,GAAG,OAAO,IAAI;AAAA,EAC9E;;;ACSA,MAAM,QAAoB,CAAC,SAAS,SAAS,QAAQ,SAAS,OAAO;AACrE,MAAM,SAAmC,EAAE,OAAO,MAAM,OAAO,MAAM,MAAM,KAAK,OAAO,MAAM,OAAO,IAAI;AAuBxG,MAAM;AAAA;AAAA,IAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAexB,MAAM;AAAA;AAAA,IAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAUxB,WAAS,SAAS,MAA4B;AAC5C,QAAI,IAAI,SAAS;AACjB,WAAO,MAAM;AACX,UAAK,IAAI,eAAgB;AACzB,UAAI,IAAI;AACR,UAAI,KAAK,KAAK,IAAK,MAAM,IAAK,IAAI,CAAC;AACnC,WAAK,IAAI,KAAK,KAAK,IAAK,MAAM,GAAI,IAAI,EAAE;AACxC,eAAS,IAAK,MAAM,QAAS,KAAK;AAAA,IACpC;AAAA,EACF;AAGA,WAAS,WAAW,OAAiB,KAAiD;AACpF,UAAM,IAAI;AACV,QAAI;AACF,YAAM,SAAS,KAAK,cAAc,QAAQ;AAC1C,YAAM,MAAM,QAAQ,aAAa,IAAI;AACrC,UAAI,UAAU,OAAO,IAAI,sBAAsB;AAC7C,eAAO,QAAQ;AACf,eAAO,SAAS;AAChB,cAAM,IAAI,IAAI,qBAAqB,IAAI,GAAG,IAAI,GAAG,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AACvE,UAAE,aAAa,GAAG,qBAAqB;AACvC,UAAE,aAAa,KAAK,wBAAwB;AAC5C,UAAE,aAAa,GAAG,qBAAqB;AACvC,YAAI,YAAY;AAChB,YAAI,SAAS,GAAG,GAAG,GAAG,CAAC;AACvB,cAAMC,OAAM,IAAI,MAAM,cAAc,MAAM;AAC1C,eAAOA;AAAA,MACT;AAAA,IACF,QAAQ;AAAA,IAER;AACA,UAAM,OAAO,IAAI,WAAW,IAAI,IAAI,CAAC;AACrC,aAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,eAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,cAAM,IAAI,KAAK,MAAM,IAAI,MAAM,IAAI,GAAG,IAAI,MAAM,IAAI,CAAC,KAAK,IAAI;AAC9D,cAAM,IAAI,KAAK,IAAI,IAAI,KAAK,IAAI,IAAI,GAAG,GAAG;AAC1C,cAAM,KAAK,IAAI,IAAI,KAAK;AACxB,aAAK,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI;AACtC,aAAK,IAAI,CAAC,IAAI,KAAK,MAAM,IAAI,GAAG;AAAA,MAClC;AAAA,IACF;AACA,UAAM,MAAM,IAAI,MAAM,YAAY,MAAM,GAAG,GAAG,MAAM,UAAU;AAC9D,QAAI,cAAc;AAClB,WAAO;AAAA,EACT;AAGA,WAAS,YAAY,OAAsC;AACzD,UAAM,IAAI;AACV,UAAM,IAAI;AACV,UAAM,OAAO,IAAI,WAAW,IAAI,IAAI,CAAC;AACrC,UAAM,OAAO,CAAC,GAAW,MAAsB;AAC7C,UAAI,IAAI,KAAK,KAAK,IAAI,GAAG,SAAS,IAAI,KAAK,KAAK,IAAI,GAAG,SAAS;AAChE,UAAI,KAAK,KAAK,IAAK,MAAM,IAAK,UAAU;AACxC,eAAS,IAAK,MAAM,QAAS,KAAK;AAAA,IACpC;AACA,aAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,eAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,cAAM,MAAM,OAAO,OAAO,KAAK,GAAG,CAAC;AACnC,cAAM,MAAM,OAAO,OAAO,KAAK,KAAM,IAAI,IAAK,IAAI,KAAK,GAAG,CAAC,IAAI,KAAK,KAAK,KAAK,CAAC;AAC/E,cAAM,KAAK,IAAI,IAAI,KAAK;AACxB,aAAK,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI;AACtC,aAAK,IAAI,CAAC,IAAI,KAAK,MAAM,MAAM,KAAK,IAAI,GAAG,MAAM,MAAM,GAAG,CAAC;AAAA,MAC7D;AAAA,IACF;AACA,UAAM,MAAM,IAAI,MAAM,YAAY,MAAM,GAAG,GAAG,MAAM,UAAU;AAC9D,QAAI,QAAQ,MAAM;AAClB,QAAI,QAAQ,MAAM;AAClB,QAAI,cAAc;AAClB,WAAO;AAAA,EACT;AASO,WAAS,cAAc,OAAiB,GAAsC,IAA6B,CAAC,GAAY;AAC7H,UAAM,SAAS,CAAC,CAAE,EAAuB;AACzC,UAAM,UAAmC,SAAS,EAAE,GAAG,GAAG,OAAO,EAAsB,IAAK;AAC5F,UAAM,QAAQ,QAAQ;AACtB,QAAI,CAAC,MAAO,OAAM,IAAI,MAAM,0CAA0C;AACtE,QAAI,CAAC,UAAU,QAAQ,WAAW,OAAW,OAAM,IAAI,MAAM,4EAA4E;AACzI,UAAM,QAAQ,KAAK,IAAI,MAAM,QAAQ,KAAK,MAAM,QAAQ,gBAAgB,GAAG,CAAC;AAC5E,UAAM,OAAO,SAAS,QAAQ,QAAQ,CAAC;AACvC,UAAM,OAAyB,CAAC,QAAQ,OAAO,CAAC,KAAK,GAAG,QAAQ,OAAO,CAAC,KAAK,CAAC;AAC9E,UAAM,SAAS,WAAW,OAAO,QAAQ,QAAQ;AACjD,QAAI,aAAa,QAAQ,cAAc;AACvC,QAAI,eAAwE,QAAQ,UAAU;AAC9F,QAAI,SAAiC,OAAO,iBAAiB,aAAa,aAAa,IAAI;AAC3F,UAAM,QAAQ,EAAE,OAAO,EAAE;AAEzB,UAAM,eAAe,CAAC,QAAsC;AAC1D,YAAM,MAAM,MAAM,IAAI,KAAK,KAAK,KAAK,IAAK,KAAK,IAAI,KAAK,IAAI,IAAI,KAAK,CAAC,GAAG,GAAG,IAAI,KAAK,KAAM,GAAG,IAAI,KAAK,IAAI,IAAI,QAAQ,GAAG,IAAI,CAAC,IAAI,KAAK,KAAK;AAC7I,YAAM,QAAQ,cAAc,IAAI,KAAK,IAAI,MAAM,CAAC;AAAA,IAClD;AACA,iBAAa,MAAM;AAEnB,UAAM,QAAQ,CAAC;AACf,UAAM,UAAU,CAAC;AACjB,QAAI,OAAO;AACX,UAAM,QAAQ,CAAC,MAAM,MAAM;AACzB,YAAM,MAAM,MAAM,MAAM,SAAS,IAAI,KAAK,IAAI,GAAG,QAAQ,IAAI,IAAI,KAAK,IAAI,GAAG,KAAK,MAAM,QAAQ,OAAO,IAAI,CAAC,CAAC;AAC7G,cAAQ;AACR,YAAM,MAAM,IAAI,MAAM,eAAe;AACrC,YAAM,OAAO,IAAI,MAAM,gBAAgB,IAAI,aAAa,MAAM,CAAC,GAAG,CAAC;AACnE,YAAM,QAAQ,IAAI,MAAM,gBAAgB,IAAI,aAAa,GAAG,GAAG,CAAC;AAChE,YAAM,SAAS,IAAI,MAAM,gBAAgB,IAAI,aAAa,GAAG,GAAG,CAAC;AACjE,YAAM,SAAS,IAAI,MAAM,gBAAgB,IAAI,aAAa,MAAM,CAAC,GAAG,CAAC;AACrE,iBAAWC,MAAK,CAAC,MAAM,OAAO,QAAQ,MAAM,EAAG,CAAAA,GAAE,SAAS,MAAM,gBAAgB;AAChF,UAAI,aAAa,YAAY,IAAI;AACjC,UAAI,aAAa,SAAS,KAAK;AAC/B,UAAI,aAAa,UAAU,MAAM;AACjC,UAAI,aAAa,UAAU,MAAM;AACjC,UAAI,aAAa,GAAG,CAAC;AACrB,YAAM,WAAW,SAAS,UAAU,SAAS;AAC7C,YAAM,MAAM,IAAI,MAAM,eAAe;AAAA,QACnC,UAAU,EAAE,MAAM,EAAE,OAAO,OAAO,GAAG,QAAQ,MAAM;AAAA,QACnD,cAAc;AAAA,QACd,gBAAgB;AAAA,QAChB,aAAa;AAAA,QACb,YAAY;AAAA,QACZ,UAAU,WAAW,MAAM,mBAAmB,MAAM;AAAA,MACtD,CAAC;AACD,YAAM,SAAS,IAAI,MAAM,OAAO,KAAK,GAAG;AACxC,aAAO,gBAAgB;AACvB,aAAO,OAAO,WAAW,IAAI;AAC7B,YAAM,IAAI,MAAM;AAChB,cAAQ,IAAI,IAAI;AAChB,YAAM,IAAI,IAAI;AAAA,QACZ;AAAA,QACA;AAAA,QACA,OAAO;AAAA,QACP,KAAK,IAAI,aAAa,MAAM,CAAC;AAAA,QAC7B,KAAK,IAAI,aAAa,MAAM,CAAC;AAAA,QAC7B,KAAK,IAAI,aAAa,GAAG;AAAA,QACzB,MAAM,IAAI,aAAa,GAAG;AAAA,QAC1B,OAAO,IAAI,aAAa,GAAG;AAAA,QAC3B,OAAO,IAAI,aAAa,GAAG;AAAA,QAC3B,MAAM,IAAI,aAAa,GAAG;AAAA,QAC1B,OAAO,IAAI,WAAW,GAAG;AAAA,QACzB,QAAQ,IAAI,aAAa,GAAG,EAAE,KAAK,IAAI;AAAA,QACvC;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF,CAAC;AAGD,UAAM,OAAO,oBAAI,IAA8C;AAC/D,QAAI,QAAQ;AACZ,QAAI,QAAQ;AAEZ,UAAM,QAAQ,CAAC,GAAS,GAAW,GAAW,GAAW,IAAY,IAAY,IAAY,MAAc,IAAY,IAAY,MAAc,OAAO,GAAG,SAAS,SAAe;AACjL,YAAM,OAAO,aAAa,EAAE,IAAI,IAAI,QAAQ,UAAU,IAAI;AAC1D,UAAI,MAAM;AACR,aAAK,KAAK,sBAAsB,EAAE,MAAM,EAAE,MAAM,MAAM,UAAU,CAAC,GAAG,GAAG,CAAC,GAAG,UAAU,CAAC,IAAI,IAAI,EAAE,GAAG,MAAM,WAAW,IAAI,SAAS,IAAI,KAAK,CAAC,CAAC;AAC5I;AAAA,MACF;AACA,UAAI,aAAa,EAAE,IAAI,EAAG,UAAS,0CAA0C,yMAAyM;AACtR,UAAI,EAAE,SAAS,EAAE,IAAK;AACtB,YAAM,IAAI,EAAE;AACZ,QAAE,IAAI,IAAI,CAAC,IAAI;AACf,QAAE,IAAI,IAAI,IAAI,CAAC,IAAI;AACnB,QAAE,IAAI,IAAI,IAAI,CAAC,IAAI;AACnB,QAAE,IAAI,IAAI,CAAC,IAAI;AACf,QAAE,IAAI,IAAI,IAAI,CAAC,IAAI;AACnB,QAAE,IAAI,IAAI,IAAI,CAAC,IAAI;AACnB,QAAE,IAAI,CAAC,IAAI;AACX,QAAE,KAAK,CAAC,IAAI;AACZ,QAAE,MAAM,CAAC,IAAI;AACb,QAAE,MAAM,CAAC,IAAI;AACb,QAAE,KAAK,CAAC,IAAI;AACZ,QAAE,MAAM,CAAC,IAAI;AACb,QAAE,OAAO,CAAC,IAAI;AAAA,IAChB;AAEA,UAAM,WAAW,CAAC,GAAkB,MAAoB;AACtD,YAAM,IAAI,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,EAAE,SAAS,CAAC;AAC9C,YAAM,OAAO,EAAE,OAAO,IAAI,EAAE,OAAO;AACnC,YAAM,CAAC,IAAI,IAAI,EAAE,IAAI,EAAE;AACvB,eAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,gBAAQ,EAAE,MAAM;AAAA,UACd,KAAK;AAEH,kBAAM,MAAM,OAAO,MAAM,KAAK,IAAI,OAAO,OAAO,KAAK,IAAI,MAAM,KAAK,IAAI,OAAO,OAAO,MAAM,KAAK,IAAI,OAAO,KAAK,OAAO,KAAK,IAAI,OAAO,KAAK,IAAI,OAAO,KAAK,MAAM,KAAK,IAAI,KAAK,OAAO,KAAK,OAAO,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC;AAC/N;AAAA,UACF,KAAK;AACH,kBAAM,MAAM,OAAO,MAAM,KAAK,IAAI,OAAO,OAAO,KAAK,IAAI,MAAM,KAAK,IAAI,OAAO,OAAO,MAAM,KAAK,IAAI,OAAO,MAAM,MAAM,KAAK,IAAI,MAAM,KAAK,IAAI,OAAO,MAAM,MAAM,KAAK,IAAI,KAAK,OAAO,KAAK,OAAO,KAAK,MAAM,CAAC;AAChN;AAAA,UACF,KAAK;AACH,kBAAM,MAAM,MAAM,MAAM,KAAK,IAAI,OAAO,OAAO,KAAK,IAAI,MAAM,KAAK,IAAI,OAAO,OAAO,MAAM,KAAK,IAAI,OAAO,MAAM,MAAM,KAAK,IAAI,MAAM,IAAI,KAAK,IAAI,OAAO,MAAM,OAAO,KAAK,IAAI,KAAK,OAAO,MAAM,OAAO,MAAM,KAAK,GAAG,CAAC;AACxN;AAAA,UACF,KAAK,SAAS;AAEZ,kBAAMA,KAAI,KAAK,IAAI,KAAK,KAAK;AAC7B,kBAAM,IAAI,OAAO;AACjB,kBAAM,MAAM,MAAM,KAAK,KAAK,IAAIA,EAAC,IAAI,GAAG,KAAK,MAAM,KAAK,KAAK,IAAIA,EAAC,IAAI,GAAG,GAAG,OAAO,KAAK,IAAI,KAAK,GAAG,OAAO,KAAK,IAAI,MAAM,OAAO,MAAM,OAAO,MAAM,KAAK,GAAG,CAAC;AAC7J;AAAA,UACF;AAAA,UACA,KAAK,SAAS;AACZ,kBAAMA,KAAI,KAAK,IAAI,KAAK,KAAK;AAC7B,kBAAM,KAAK,MAAM,KAAK,IAAI;AAC1B,kBAAM,KAAK,MAAM,KAAK,IAAI;AAC1B,kBAAM,MAAM,OAAO,IAAI,IAAI,IAAI,KAAK,IAAIA,EAAC,IAAI,MAAM,IAAI,KAAK,MAAM,KAAK,KAAK,KAAK,KAAK,IAAIA,EAAC,IAAI,MAAM,IAAI,KAAK,MAAM,OAAO,KAAK,IAAI,KAAK,OAAO,MAAM,OAAO,MAAM,KAAK,GAAG,CAAC;AAC5K;AAAA,UACF;AAAA,UACA,KAAK;AACH;AAAA,QACJ;AAAA,MACF;AAAA,IACF;AAgBA,UAAM,MAAW,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,GAAG,GAAG,OAAO,GAAG,IAAI,GAAG,IAAI,EAAE;AACvF,UAAM,SAAS,QAAQ,UAAU;AACjC,UAAM,QAAQ,CAAC,GAAkB,MAAuB;AACtD,UAAI,CAAC,IAAI,IAAI,EAAE,IAAI,EAAE;AACrB,YAAM,MAAM,KAAK,MAAM,IAAI,IAAI,EAAE;AACjC,UAAI,MAAM,MAAM;AACd,aAAK;AACL,aAAK;AACL,aAAK;AAAA,MACP,OAAO;AACL,cAAM;AACN,cAAM;AACN,cAAM;AAAA,MACR;AACA,YAAM,QAAQ,MAAM,MAAM;AAC1B,YAAM,QAAQ,EAAE,UAAU,SAAY,KAAK,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,OAAO,MAAM,IAAI;AAC7E,YAAM,OAAO,EAAE,IAAI,CAAC,IAAI;AACxB,UAAI,OAAO,KAAM,QAAO;AACxB,YAAM,KAAK,KAAK;AAChB,UAAI,IAAI,EAAE,IAAI,CAAC;AACf,UAAI,IAAI,EAAE,IAAI,CAAC;AACf,UAAI,IAAI,EAAE,IAAI,CAAC;AACf,UAAI,KAAK,KAAK;AACd,UAAI,KAAK;AACT,UAAI,KAAK,KAAK;AACd,UAAI,IAAI,KAAK,IAAI,MAAM,KAAK,KAAK,KAAK,KAAK,KAAK,OAAO,IAAI,KAAK,GAAG;AACnE,UAAI,QAAQ;AACZ,YAAM,OAAO,EAAE,OAAO,IAAI,EAAE,OAAO;AACnC,UAAI,OAAO,KAAK;AAEd,YAAI,KAAK,KAAK,IAAI,MAAO,OAAO,GAAG;AACnC,YAAI,KAAK,IAAI,KAAK;AAAA,MACpB,OAAO;AAEL,YAAI,KAAK,OAAO;AAChB,YAAI,KAAK,IAAI,KAAK;AAAA,MACpB;AACA,aAAO;AAAA,IACT;AAEA,UAAM,YAAY,CAAC,GAAkB,GAAW,QAAgB,SAAiB,UAAwB;AACvG,YAAM,OAAO,EAAE,OAAO,IAAI,EAAE,OAAO;AACnC,YAAM,OAAO,QAAQ;AACrB,eAAS,IAAI,GAAG,IAAI,QAAQ,KAAK;AAC/B,cAAM,IAAI,KAAK;AACf,cAAM,IAAI,IAAI,IAAI;AAClB,cAAM,KAAK,IAAI,MAAM,IAAI,KAAK,IAAI,MAAM,MAAM,MAAM,KAAK,IAAI;AAC7D,cAAMA,KAAI,KAAK,IAAI,KAAK,KAAK;AAC7B,cAAM,KAAK,KAAK,IAAIA,EAAC,IAAI;AACzB,cAAM,KAAK,KAAK,IAAIA,EAAC,IAAI;AACzB;AAAA,UACE,MAAM;AAAA,UACN,IAAI,IAAI,IAAI,KAAK,IAAI;AAAA,UACrB,IAAI,IAAI,IAAI,KAAK,IAAI,MAAM,IAAI;AAAA,UAC/B,IAAI,IAAI,IAAI,KAAK,IAAI;AAAA,UACrB,IAAI,MAAM,OAAO,KAAK,MAAM,MAAM,KAAK,IAAI,OAAO;AAAA,UAClD,IAAI,KAAK,MAAM;AAAA,UACf,IAAI,MAAM,OAAO,KAAK,MAAM,MAAM,KAAK,IAAI,OAAO;AAAA,UAClD,OAAO,KAAK,IAAI;AAAA,UAChB,QAAQ,KAAK,IAAI;AAAA,UACjB,OAAQ,KAAK,IAAI;AAAA,UACjB,KAAK;AAAA,UACL;AAAA,UACA,IAAI;AAAA,QACN;AAAA,MACF;AACA,YAAM,KAAK,IAAI,IAAI,IAAI,KAAK,IAAI;AAChC,YAAM,KAAK,IAAI,IAAI,IAAI,KAAK,IAAI;AAChC,eAAS,IAAI,GAAG,IAAI,SAAS,KAAK;AAChC,cAAMA,KAAI,KAAK,IAAI,KAAK,KAAK;AAC7B,cAAM,IAAI,IAAI,KAAK,KAAK,KAAK,KAAK,CAAC;AACnC,cAAM,MAAM,OAAO,MAAM,QAAQ,KAAK,IAAI;AAC1C,cAAM,MAAM,OAAO,KAAK,KAAK,IAAIA,EAAC,IAAI,GAAG,IAAI,QAAQ,MAAO,KAAK,KAAK,IAAIA,EAAC,IAAI,GAAG,KAAK,IAAIA,EAAC,IAAI,IAAI,MAAM,KAAK,IAAI,KAAK,KAAK,IAAIA,EAAC,IAAI,IAAI,MAAM,KAAK,IAAI,MAAM,QAAQ,KAAK,IAAI,OAAO,MAAM,KAAK,GAAG,GAAG,IAAI,KAAK;AAAA,MACnN;AACA,eAAS,IAAI,GAAG,IAAI,OAAO,KAAK;AAC9B,cAAMA,KAAI,KAAK,IAAI,KAAK,KAAK;AAC7B,cAAM,IAAI,IAAI,KAAK,MAAM,KAAK,KAAK,KAAK,CAAC;AACzC,cAAM,MAAM,OAAO,KAAK,KAAK,IAAIA,EAAC,IAAI,GAAG,IAAI,QAAQ,MAAM,KAAK,KAAK,IAAIA,EAAC,IAAI,GAAG,KAAK,IAAIA,EAAC,IAAI,MAAM,OAAO,KAAK,IAAI,MAAM,KAAK,IAAIA,EAAC,IAAI,MAAM,MAAM,KAAK,IAAI,KAAK,OAAO,OAAO,KAAK,MAAM,OAAO,KAAK,KAAK,GAAG,GAAG,IAAI,KAAK;AAAA,MAC9N;AACA,WAAK;AAAA,IACP;AAEA,UAAM,WAAW,CAAC,GAAkB,MAAoB;AACtD,YAAM,OAAO,EAAE,OAAO,IAAI,EAAE,OAAO;AACnC,YAAM,QAAQ,EAAE,UAAU,SAAY,KAAK,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,OAAO,MAAM,IAAI;AAC7E,eAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,cAAM,MAAM,KAAK,IAAI,OAAO;AAC5B,cAAM,MAAM,KAAK,IAAI,OAAO;AAC5B,cAAM,MAAM,OAAO,EAAE,IAAI,CAAC,IAAI,IAAI,EAAE,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,IAAI,EAAE,IAAI,CAAC,IAAI,MAAM,EAAE,IAAI,CAAC,IAAI,MAAM,EAAE,IAAI,CAAC,IAAI,MAAM,KAAK,OAAO,MAAM,OAAO,KAAK,KAAK,GAAG,GAAG,KAAK;AAAA,MAC9J;AAAA,IACF;AAEA,UAAM,OAAoC,EAAE,OAAO,IAAI,OAAO,IAAI,MAAM,KAAK,OAAO,IAAI,OAAO,IAAI,OAAO,EAAE;AAG5G,UAAM,MAAM;AACZ,UAAM,QAAQ;AACd,UAAM,aAAa,KAAK,IAAI,GAAG,KAAK,MAAM,QAAQ,cAAc,CAAC,CAAC;AAClE,UAAM,aAAa,YAAY,KAAK;AACpC,UAAM,YAAY,IAAI,MAAM,kBAAkB,EAAE,OAAO,UAAU,KAAK,YAAY,cAAc,MAAM,aAAa,MAAM,SAAS,KAAK,YAAY,OAAO,MAAM,MAAM,WAAW,CAAC;AAClL,UAAM,cAAwB,CAAC;AAC/B,aAAS,IAAI,GAAG,IAAI,KAAK,KAAK;AAC5B,eAAS,IAAI,GAAG,IAAI,OAAO,KAAK;AAC9B,cAAMA,KAAI,KAAK,QAAQ,KAAK;AAC5B,cAAMC,KAAID,KAAI,QAAQ;AACtB,oBAAY,KAAKA,IAAGC,IAAGD,KAAI,GAAGA,KAAI,GAAGC,IAAGA,KAAI,CAAC;AAAA,MAC/C;AAAA,IACF;AACA,UAAM,QAAQ,IAAI,MAAM,MAAM;AAC9B,UAAM,OAAO;AACb,UAAM,IAAI,KAAK;AACf,UAAM,UAA0B,CAAC;AACjC,aAAS,IAAI,GAAG,IAAI,YAAY,KAAK;AACnC,YAAM,MAAM,IAAI,MAAM,eAAe;AACrC,YAAM,KAAK,MAAM,MAAM,QAAQ;AAC/B,UAAI,aAAa,YAAY,IAAI,MAAM,gBAAgB,IAAI,aAAa,IAAI,CAAC,GAAG,CAAC,EAAE,SAAS,MAAM,gBAAgB,CAAC;AACnH,UAAI,aAAa,SAAS,IAAI,MAAM,gBAAgB,IAAI,aAAa,IAAI,CAAC,GAAG,CAAC,EAAE,SAAS,MAAM,gBAAgB,CAAC;AAChH,YAAM,KAAK,IAAI,aAAa,IAAI,CAAC;AACjC,UAAI,aAAa,MAAM,IAAI,MAAM,gBAAgB,IAAI,CAAC,EAAE,SAAS,MAAM,gBAAgB,CAAC;AACxF,UAAI,SAAS,WAAW;AACxB,YAAMC,QAAO,IAAI,MAAM,KAAK,KAAK,SAAS;AAC1C,MAAAA,MAAK,gBAAgB;AACrB,MAAAA,MAAK,UAAU;AACf,MAAAA,MAAK,OAAO,iBAAiB,CAAC;AAC9B,YAAM,IAAIA,KAAI;AACd,cAAQ,KAAKA,KAAI;AAAA,IACnB;AAEA,UAAM,cAAc,CAACA,OAAoB,MAAoB;AAC3D,YAAM,MAAMA,MAAK;AACjB,YAAM,MAAO,IAAI,aAAa,UAAU,EAA8B;AACtE,YAAM,MAAO,IAAI,aAAa,OAAO,EAA8B;AACnE,YAAM,KAAM,IAAI,aAAa,IAAI,EAA8B;AAC/D,eAAS,IAAI,GAAG,KAAK,KAAK,KAAK;AAC7B,cAAM,IAAI,IAAI;AACd,cAAM,IAAI,IAAI,IAAI;AAClB,cAAM,KAAK,IAAI,IAAI,IAAI,KAAK;AAC5B,cAAM,KAAK,IAAI,IAAI,IAAI,KAAK,IAAI,MAAM,IAAI;AAC1C,cAAM,KAAK,IAAI,IAAI,IAAI,KAAK;AAC5B,YAAI,KAAK,IAAI;AACb,YAAI,KAAK,IAAI,KAAK,MAAM;AACxB,YAAI,KAAK,IAAI;AACb,cAAM,KAAK,KAAK,MAAM,IAAI,IAAI,EAAE,KAAK;AACrC,cAAM;AACN,cAAM;AACN,cAAM;AAEN,YAAI,KAAK;AACT,cAAM,KAAK;AACX,YAAI,KAAK,CAAC;AACV,cAAM,KAAK,KAAK,MAAM,IAAI,EAAE;AAC5B,YAAI,KAAK,MAAM;AACb,eAAK;AACL,eAAK;AAAA,QACP,OAAO;AACL,gBAAM;AACN,gBAAM;AAAA,QACR;AACA,cAAM,KAAK,KAAK,KAAK,KAAK;AAC1B,cAAM,KAAK,KAAK,KAAK,KAAK;AAC1B,cAAM,KAAK,KAAK,KAAK,KAAK;AAC1B,cAAM,IAAI,IAAI,MAAM,IAAI,KAAK,IAAI,MAAM;AACvC,cAAM,QAAQ,KAAK,IAAI,GAAG,IAAI,CAAC,KAAK,OAAO,MAAM,KAAK,KAAK,IAAI,GAAG,OAAO,CAAC;AAC1E,iBAAS,IAAI,GAAG,KAAK,OAAO,KAAK;AAC/B,gBAAMF,KAAK,IAAI,QAAS,KAAK,KAAK;AAClC,gBAAM,KAAK,KAAK,IAAIA,EAAC,IAAI;AACzB,gBAAM,KAAK,KAAK,IAAIA,EAAC,IAAI;AACzB,gBAAM,IAAI,KAAK,QAAQ,KAAK;AAC5B,cAAI,IAAI,CAAC,IAAI,KAAK,KAAK,KAAK,KAAK;AACjC,cAAI,IAAI,IAAI,CAAC,IAAI,KAAK,KAAK,KAAK,KAAK;AACrC,cAAI,IAAI,IAAI,CAAC,IAAI,KAAK,KAAK,KAAK,KAAK;AACrC,cAAI,IAAI,CAAC,IAAI,IAAI,IAAI,IAAI,CAAC,IAAI,IAAI,IAAI,IAAI,CAAC,IAAI;AAC/C,cAAI,IAAI,IAAI,CAAC,IAAI;AACjB,aAAG,IAAI,CAAC,IAAK,IAAI,QAAS;AAC1B,aAAG,IAAI,IAAI,CAAC,IAAI,IAAI,IAAI,IAAI;AAAA,QAC9B;AAAA,MACF;AACA;AAAC,MAAC,IAAI,aAAa,UAAU,EAA8B,cAAc;AACxE,MAAC,IAAI,aAAa,OAAO,EAA8B,cAAc;AACrE,MAAC,IAAI,aAAa,IAAI,EAA8B,cAAc;AACnE,MAAAE,MAAK,UAAU;AAAA,IACjB;AAGA,UAAM,QAAQ;AACd,UAAM,YAAY,KAAK,IAAI,GAAG,KAAK,MAAM,QAAQ,aAAa,EAAE,CAAC;AACjE,UAAM,WAAW,IAAI,MAAM,eAAe;AAC1C,UAAM,WAAW,IAAI,MAAM,gBAAgB,IAAI,aAAa,YAAY,QAAQ,IAAI,CAAC,GAAG,CAAC,EAAE,SAAS,MAAM,gBAAgB;AAC1H,UAAM,WAAW,IAAI,MAAM,gBAAgB,IAAI,aAAa,YAAY,QAAQ,IAAI,CAAC,GAAG,CAAC,EAAE,SAAS,MAAM,gBAAgB;AAC1H,aAAS,aAAa,YAAY,QAAQ;AAC1C,aAAS,aAAa,SAAS,QAAQ;AACvC,UAAM,WAAqB,CAAC;AAC5B,aAAS,IAAI,GAAG,IAAI,YAAY,OAAO,KAAK;AAC1C,YAAM,IAAI,IAAI;AACd,eAAS,KAAK,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,GAAG,IAAI,CAAC;AAAA,IACrE;AACA,aAAS,SAAS,QAAQ;AAC1B,aAAS,aAAa,GAAG,CAAC;AAC1B,UAAM,SAAS,IAAI,MAAM;AAAA,MACvB;AAAA,MACA,IAAI,MAAM,kBAAkB,EAAE,cAAc,MAAM,aAAa,MAAM,UAAU,MAAM,kBAAkB,YAAY,OAAO,MAAM,MAAM,WAAW,CAAC;AAAA,IACpJ;AACA,WAAO,gBAAgB;AACvB,WAAO,UAAU;AACjB,WAAO,OAAO;AACd,UAAM,IAAI,MAAM;AAEhB,UAAM,SAAS,CAAC,OAAuB;AACrC,UAAI,IAAI;AACR,eAAS,IAAI,GAAG,IAAI,GAAG,QAAQ,IAAK,KAAI,KAAK,KAAK,IAAI,GAAG,WAAW,CAAC,GAAG,QAAQ;AAChF,cAAS,MAAM,KAAK,MAAQ;AAAA,IAC9B;AAEA,UAAM,aAAa,CAAC,MAAc,GAAkB,SAAuB;AACzE,YAAM,KAAK,SAAS;AACpB,YAAM,KAAK,SAAS;AACpB,YAAM,IAAI,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,EAAE,SAAS,CAAC;AAC9C,YAAM,OAAO,EAAE,OAAO,IAAI,EAAE,OAAO;AACnC,YAAM,IAAI,OAAO;AACjB,YAAM,KAAK,KAAK,IAAI,MAAO,OAAO,IAAI;AACtC,YAAM,QAAQ,OAAO,EAAE,EAAE,IAAI;AAC7B,eAAS,IAAI,GAAG,IAAI,OAAO,KAAK;AAC9B,cAAMF,KAAK,IAAI,QAAS,KAAK,KAAK;AAClC,cAAM,KAAK,KAAK,IAAIA,EAAC;AACrB,cAAM,KAAK,KAAK,IAAIA,EAAC;AACrB,cAAM,KAAK,EAAE,IAAI,CAAC,IAAI,KAAK;AAC3B,cAAM,KAAK,EAAE,IAAI,CAAC,IAAI,KAAK;AAC3B,cAAM,KAAK,EAAE,IAAI,CAAC;AAClB,cAAM,KAAK,QAAQ,OAAO,QAAQ,KAAK,OAAO,OAAO,KAAK,IAAI,KAAK,IAAI,OAAO,KAAK,IAAI,MAAM,KAAK,CAAC;AACnG,cAAM,KAAK,OAAO,QAAQ,KAAK;AAE/B,cAAM,KAAK,CAAC,KAAK;AACjB,cAAM,KAAK,KAAK;AAChB,WAAG,IAAI,CAAC,KAAK,IAAI,IAAI,KAAK,IAAI,KAAK,IAAI,IAAI,KAAK,IAAI,KAAK,KAAK,KAAK,KAAK,IAAI,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,IAAI,MAAM,KAAK,GAAG,KAAK,KAAK,IAAI,IAAI,GAAG,IAAI,CAAC;AACtJ,cAAM,KAAK,MAAM;AACjB,WAAG,IAAI,CAAC,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,OAAO,IAAI,MAAM,IAAI,MAAM,EAAE,GAAG,IAAI,CAAC;AAAA,MAC7H;AAAA,IACF;AAEA,UAAM,UAAU,CAAC,GAAS,OAAqB;AAC7C,UAAI,IAAI;AACR,YAAM,QAAQ,KAAK,CAAC;AACpB,YAAM,QAAQ,KAAK,CAAC;AACpB,aAAO,IAAI,EAAE,OAAO;AAClB,UAAE,IAAI,CAAC,KAAK;AACZ,cAAM,MAAM,EAAE,IAAI,CAAC;AACnB,cAAM,OAAO,EAAE,KAAK,CAAC;AACrB,YAAI,OAAO,QAAS,EAAE,SAAS,WAAW,EAAE,IAAI,IAAI,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,GAAI;AAEzE,gBAAM,OAAO,EAAE,EAAE;AACjB,cAAI,MAAM,MAAM;AACd,cAAE,IAAI,WAAW,IAAI,GAAG,OAAO,GAAG,OAAO,IAAI,CAAC;AAC9C,cAAE,IAAI,WAAW,IAAI,GAAG,OAAO,GAAG,OAAO,IAAI,CAAC;AAC9C,cAAE,IAAI,CAAC,IAAI,EAAE,IAAI,IAAI;AACrB,cAAE,KAAK,CAAC,IAAI,EAAE,KAAK,IAAI;AACvB,cAAE,MAAM,CAAC,IAAI,EAAE,MAAM,IAAI;AACzB,cAAE,MAAM,CAAC,IAAI,EAAE,MAAM,IAAI;AACzB,cAAE,KAAK,CAAC,IAAI,EAAE,KAAK,IAAI;AACvB,cAAE,MAAM,CAAC,IAAI,EAAE,MAAM,IAAI;AACzB,cAAE,OAAO,CAAC,IAAI,EAAE,OAAO,IAAI;AAAA,UAC7B;AACA;AAAA,QACF;AACA,cAAM,IAAI,IAAI;AACd,gBAAQ,EAAE,MAAM;AAAA,UACd,KAAK;AAAA,UACL,KAAK;AACH,cAAE,IAAI,CAAC,MAAM,QAAQ,EAAE,IAAI,CAAC,KAAK,KAAK,IAAI,GAAG,KAAK,GAAG;AACrD,cAAE,IAAI,IAAI,CAAC,MAAM,QAAQ,EAAE,IAAI,IAAI,CAAC,KAAK,KAAK,IAAI,GAAG,KAAK,GAAG;AAC7D;AAAA,UACF,KAAK;AACH,gBAAI,EAAE,MAAM,CAAC,MAAM,EAAG,GAAE,IAAI,IAAI,CAAC,KAAK,MAAM;AAC5C;AAAA,UACF,KAAK;AACH,cAAE,IAAI,IAAI,CAAC,KAAK,IAAI;AACpB;AAAA,UACF,KAAK;AACH,cAAE,IAAI,CAAC,MAAM,QAAQ,MAAM,EAAE,IAAI,CAAC,KAAK,KAAK,IAAI,GAAG,KAAK,CAAC;AACzD;AAAA,QACJ;AACA,UAAE,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,IAAI;AACvB,UAAE,IAAI,IAAI,CAAC,KAAK,EAAE,IAAI,IAAI,CAAC,IAAI;AAC/B,UAAE,IAAI,IAAI,CAAC,KAAK,EAAE,IAAI,IAAI,CAAC,IAAI;AAC/B,YAAI,EAAE,SAAS,WAAW,EAAE,IAAI,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG;AAEpD,YAAE,IAAI,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC;AACzB,YAAE,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC;AAAA,QACrB;AACA;AAAA,MACF;AAAA,IACF;AAEA,UAAM,QAAQ,CAAC,MAAkB;AAC/B,YAAM,IAAI,EAAE;AACZ,YAAM,KAAK,EAAE,KAAK;AAClB,YAAM,KAAK,EAAE,MAAM;AACnB,YAAM,KAAK,EAAE,OAAO;AACpB,YAAM,KAAK,EAAE,OAAO;AACpB,eAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,cAAM,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC;AAC7B,cAAM,IAAI,IAAI;AACd,WAAG,CAAC,IAAI,EAAE,IAAI,CAAC;AACf,WAAG,IAAI,CAAC,IAAI,EAAE,IAAI,IAAI,CAAC;AACvB,WAAG,IAAI,CAAC,IAAI,EAAE,IAAI,IAAI,CAAC;AACvB,WAAG,CAAC,IAAI,EAAE,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,KAAK;AACjD,YAAI;AACJ,gBAAQ,EAAE,MAAM;AAAA,UACd,KAAK;AACH,oBAAQ,OAAO,KAAK,IAAI,GAAG,IAAI,CAAC,KAAK,IAAI;AACzC,eAAG,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC;AACxC;AAAA,UACF,KAAK;AACH,oBAAQ,MAAM,KAAK,IAAI,GAAG,IAAI,CAAC,KAAK,IAAI;AACxC,eAAG,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC;AACxC;AAAA,UACF,KAAK;AACH,qBAAS,EAAE,MAAM,CAAC,IAAI,OAAO,QAAQ,IAAI,IAAI;AAC7C,gBAAI,EAAE,MAAM,CAAC,GAAG;AACd,iBAAG,CAAC,IAAI,OAAO,OAAO,EAAE,KAAK,CAAC;AAC9B,iBAAG,IAAI,CAAC,IAAI,MAAM,MAAM,EAAE,KAAK,CAAC;AAChC,iBAAG,IAAI,CAAC,IAAI;AAAA,YACd,OAAO;AAEL,iBAAG,CAAC,IAAI;AACR,iBAAG,IAAI,CAAC,IAAI,KAAK,IAAI,MAAM,OAAO,IAAI,MAAM,EAAE,KAAK,CAAC,IAAI,GAAG;AAC3D,iBAAG,IAAI,CAAC,IAAI,KAAK,IAAI,GAAG,OAAO,IAAI,IAAI;AAAA,YACzC;AACA;AAAA,UACF,KAAK;AACH,oBAAQ,IAAI;AACZ,eAAG,CAAC,IAAI;AACR,eAAG,IAAI,CAAC,IAAI,OAAO,IAAI;AACvB,eAAG,IAAI,CAAC,IAAI;AACZ;AAAA,UACF,SAAS;AACP,kBAAM,IAAI,EAAE,MAAM,CAAC;AACnB,oBAAQ,MAAM,IAAI,QAAQ,IAAI,KAAK,MAAM,IAAI,OAAO,IAAI,IAAI,OAAO,MAAM,IAAI,QAAQ,IAAI,IAAI,OAAO;AACpG,eAAG,CAAC,IAAI,MAAM,IAAI,OAAO;AACzB,eAAG,IAAI,CAAC,IAAI,MAAM,IAAI,OAAO;AAC7B,eAAG,IAAI,CAAC,IAAI;AAAA,UACd;AAAA,QACF;AACA,WAAG,CAAC,IAAI;AAAA,MACV;AACA,QAAE,KAAK,cAAc;AACrB,QAAE,MAAM,cAAc;AACtB,QAAE,OAAO,cAAc;AACvB,QAAE,OAAO,cAAc;AACvB,QAAE,OAAO,SAAS,aAAa,GAAG,CAAC;AAAA,IACrC;AAEA,WAAO;AAAA,MACL;AAAA,MACA,QAAQ,EAAE,OAAO,SAAS,OAAO;AAAA,MACjC,OAAO,IAAI,UAAU,KAAK;AACxB,iBAAS,QAAQ,OAAO,iBAAiB,aAAa,aAAa,IAAI;AACvE,qBAAa,MAAM;AACnB,cAAM,OAAO,KAAK,IAAI,KAAK,IAAI,IAAI,CAAC,GAAG,GAAG;AAC1C;AACA,iBAAS;AACT,mBAAW,OAAO,IAAI,EAAE,QAAQ,OAAO;AACvC,YAAI,WAAW;AACf,YAAI,UAAU;AACd,cAAM,MAAM,CAAC,KAAa,SAAyB;AACjD,cAAI,IAAI,KAAK,IAAI,GAAG;AACpB,cAAI,CAAC,GAAG;AACN,gBAAI,EAAE,OAAO,GAAG,MAAM;AACtB,iBAAK,IAAI,KAAK,CAAC;AAAA,UACjB;AACA,YAAE,QAAQ;AACV,YAAE,SAAS,OAAO;AAClB,gBAAM,IAAI,KAAK,MAAM,EAAE,KAAK;AAC5B,YAAE,SAAS;AACX,iBAAO;AAAA,QACT;AACA,iBAAS,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;AACxC,gBAAM,IAAI,SAAS,CAAC;AACpB,cAAI,EAAE,EAAE,YAAY,GAAI;AACxB,gBAAM,IAAI,KAAK,IAAI,GAAG,EAAE,SAAS;AACjC,gBAAM,OAAO,EAAE,OAAO,IAAI,EAAE,OAAO;AACnC,cAAI,EAAE,SAAS,SAAS;AACtB,gBAAI,IAAI,MAAM;AACZ,oBAAMG,KAAI,IAAI,EAAE,IAAI,IAAI,IAAI,EAAE;AAC9B,kBAAIA,KAAI,EAAG,UAAS,GAAGA,EAAC;AAAA,YAC1B,WAAW,MAAM,GAAG,CAAC,GAAG;AACtB,oBAAM,SAAS,IAAI,EAAE,KAAK,KAAK,MAAM,KAAK,IAAI,MAAM,GAAG,KAAK,CAAC;AAC7D,oBAAM,UAAU,IAAI,GAAG,EAAE,EAAE,OAAO,KAAK,KAAK,KAAK,IAAI,MAAM,GAAG,KAAK,CAAC;AACpE,oBAAM,QAAQ,IAAI,GAAG,EAAE,EAAE,MAAM,IAAI,KAAK,IAAI,OAAO,EAAE;AACrD,wBAAU,GAAG,GAAG,QAAQ,SAAS,KAAK;AACtC,kBAAI,WAAW,QAAQ,OAAQ,aAAY,QAAQ,UAAU,GAAG,CAAC;AAAA,YACnE;AACA;AAAA,UACF;AACA,cAAI,EAAE,SAAS,WAAW,UAAU,UAAW,YAAW,WAAW,GAAG,KAAK;AAC7E,gBAAM,IAAI,IAAI,EAAE,IAAI,KAAK,EAAE,IAAI,IAAI,CAAC;AACpC,cAAI,IAAI,EAAG,UAAS,GAAG,CAAC;AAAA,QAC1B;AACA,iBAAS,IAAI,UAAU,IAAI,QAAQ,QAAQ,IAAK,SAAQ,CAAC,EAAE,UAAU;AACrE,eAAO,UAAU,UAAU;AAC3B,iBAAS,aAAa,GAAG,UAAU,QAAQ,CAAC;AAC5C,iBAAS,cAAc;AACvB,iBAAS,cAAc;AACvB,YAAI,KAAK,OAAO,SAAS,SAAS,IAAI,GAAG;AACvC,qBAAW,CAAC,IAAI,CAAC,KAAK,KAAM,KAAI,EAAE,UAAU,MAAO,MAAK,OAAO,EAAE;AAAA,QACnE;AACA,mBAAW,QAAQ,OAAO;AACxB,kBAAQ,MAAM,IAAI,GAAG,IAAI;AACzB,gBAAM,MAAM,IAAI,CAAC;AAAA,QACnB;AAAA,MACF;AAAA,MACA,QAAQ,GAAG,GAAG;AACZ,aAAK,CAAC,IAAI;AACV,aAAK,CAAC,IAAI;AAAA,MACZ;AAAA,MACA,UAAU,KAAK;AACb,uBAAe;AACf,iBAAS,OAAO,QAAQ,aAAa,IAAI,IAAI;AAC7C,qBAAa,MAAM;AAAA,MACrB;AAAA,MACA,cAAc,IAAI;AAChB,qBAAa;AACb,qBAAa,MAAM;AAAA,MACrB;AAAA,MACA,QAAQ;AACN,cAAM,SAAS,CAAC;AAChB,YAAI,QAAQ;AACZ,YAAI,WAAW;AACf,mBAAW,QAAQ,OAAO;AACxB,iBAAO,IAAI,IAAI,MAAM,IAAI,EAAE;AAC3B,mBAAS,MAAM,IAAI,EAAE;AACrB,sBAAY,MAAM,IAAI,EAAE;AAAA,QAC1B;AACA,eAAO,EAAE,OAAO,UAAU,OAAO;AAAA,MACnC;AAAA,MACA,QAAQ;AACN,mBAAW,QAAQ,OAAO;AACxB,gBAAM,IAAI,EAAE,QAAQ;AACpB,gBAAM,MAAM,IAAI,CAAC;AAAA,QACnB;AACA,aAAK,MAAM;AACX,mBAAW,KAAK,QAAS,GAAE,UAAU;AACrC,eAAO,UAAU;AAAA,MACnB;AAAA,MACA,UAAU;AACR,mBAAW,QAAQ,OAAO;AACxB,gBAAM,OAAO,QAAQ,IAAI,CAAC;AAC1B,kBAAQ,IAAI,EAAE,SAAS,QAAQ;AAC9B,UAAC,QAAQ,IAAI,EAAE,SAA8B,QAAQ;AAAA,QACxD;AACA,cAAM,OAAO,KAAK;AAClB,mBAAW,KAAK,QAAS,GAAE,SAAS,QAAQ;AAC5C,iBAAS,QAAQ;AAChB,QAAC,OAAO,SAA8B,QAAQ;AAC/C,kBAAU,QAAQ;AAClB,mBAAW,QAAQ;AACnB,eAAO,QAAQ;AAAA,MACjB;AAAA,IACF;AAAA,EACF;;;;;;ACvyBO,WAAS,SAASC,QAA0B,WAAW,UAAkB;AAC9E,QAAIA,WAAU,OAAW,QAAO;AAChC,QAAI,OAAOA,WAAU,SAAU,QAAO,KAAK,MAAMA,MAAK;AACtD,UAAM,QAAQ,sBAAsB,KAAKA,MAAK;AAC9C,QAAI,CAAC,MAAO,OAAM,IAAI,MAAM,wBAAwBA,MAAK,8BAA8B;AACvF,WAAO,SAAS,MAAM,CAAC,GAAa,EAAE;EACxC;AAGO,MAAM,eAAe,CAAC,MAAuB,IAAI,UAAU,IAAI,eAAe,KAAK,IAAI,IAAI,eAAe,cAAc,GAAG;AAM3H,WAAS,cAAc,KAAa,OAAoD;AAC7F,UAAM,KAAM,OAAO,KAAM,OAAO;AAChC,UAAM,KAAM,OAAO,IAAK,OAAO;AAC/B,UAAM,KAAK,MAAM,OAAO;AACxB,WAAO,UAAU,WAAW,CAAC,aAAa,CAAC,GAAG,aAAa,CAAC,GAAG,aAAa,CAAC,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC;EAC5F;ACnBO,WAAS,cAAc,OAAwB;AACpD,QAAI,UAAU,KAAM,QAAO;AAC3B,QAAI,OAAO,UAAU,SAAU,QAAO,OAAO,GAAG,OAAO,EAAE,IAAI,OAAO,OAAO,SAAS,KAAK,IAAI,OAAO,KAAK,IAAI,KAAK,UAAU,OAAO,KAAK,CAAC;AACzI,QAAI,OAAO,UAAU,SAAU,QAAO,KAAK,UAAU,KAAK;AAC1D,QAAI,OAAO,UAAU,UAAW,QAAO,QAAQ,SAAS;AACxD,QAAI,OAAO,UAAU,WAAY,OAAM,IAAI,MAAM,gFAAgF;AACjI,QAAI,YAAY,OAAO,KAAK,EAAG,QAAO,cAAc,MAAM,KAAK,KAAqC,CAAC;AACrG,QAAI,MAAM,QAAQ,KAAK,EAAG,QAAO,IAAI,MAAM,IAAI,CAAC,SAAU,SAAS,SAAY,SAAS,cAAc,IAAI,CAAE,EAAE,KAAK,GAAG,CAAC;AACvH,QAAI,OAAO,UAAU,UAAU;AAC7B,YAAM,SAAS;AACf,YAAM,OAAO,OAAO,KAAK,MAAM,EAAE,OAAO,CAAC,QAAQ,OAAO,GAAG,MAAM,MAAS,EAAE,KAAK;AACjF,aAAO,IAAI,KAAK,IAAI,CAAC,QAAQ,GAAG,KAAK,UAAU,GAAG,CAAC,IAAI,cAAc,OAAO,GAAG,CAAC,CAAC,EAAE,EAAE,KAAK,GAAG,CAAC;IAChG;AACA,WAAO;EACT;AAEA,MAAM,OAAO,CAAC,MAAc,QAAgB,UAA0B;AACpE,QAAI,OAAO;AACX,aAAS,QAAQ,GAAG,QAAQ,KAAK,QAAQ,SAAS;AAChD,cAAQ,KAAK,WAAW,KAAK;AAC7B,aAAO,KAAK,KAAK,MAAM,KAAK;IAC9B;AACA,YAAQ,SAAS,GAAG,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG;EAClD;AAGO,MAAM,SAAS,CAAC,SAAyB,KAAK,MAAM,YAAY,QAAU,IAAI,KAAK,MAAM,UAAY,UAAU;AAG/G,MAAM,YAAY,CAAC,UAA2B,OAAO,cAAc,KAAK,CAAC;ACvBzE,MAAM,YAAY,MAAe,CAAC,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;AAGhF,WAAS,oBAAoB,GAAW,GAAW,GAAW,QAAuB,OAAyC;AACnI,UAAM,MAAM,KAAK;AACjB,UAAM,MAAM,KAAK;AACjB,UAAM,KAAK,IAAI,IAAI,CAAC;AACpB,UAAM,KAAK,IAAI,IAAI,CAAC;AACpB,UAAM,KAAK,IAAI,IAAI,CAAC;AACpB,UAAM,KAAK,IAAI,IAAI,CAAC;AACpB,UAAM,KAAK,IAAI,IAAI,CAAC;AACpB,UAAM,KAAK,IAAI,IAAI,CAAC;AACpB,YAAQ,OAAO;MACb,KAAK;AACH,eAAO,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,IAAI,KAAK,KAAK,KAAK,KAAK,KAAK,IAAI,KAAK,KAAK,KAAK,KAAK,KAAK,IAAI,KAAK,KAAK,KAAK,KAAK,KAAK,EAAE;MAC5H,KAAK;AACH,eAAO,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,IAAI,KAAK,KAAK,KAAK,KAAK,KAAK,IAAI,KAAK,KAAK,KAAK,KAAK,KAAK,IAAI,KAAK,KAAK,KAAK,KAAK,KAAK,EAAE;MAC5H,KAAK;AACH,eAAO,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,IAAI,KAAK,KAAK,KAAK,KAAK,KAAK,IAAI,KAAK,KAAK,KAAK,KAAK,KAAK,IAAI,KAAK,KAAK,KAAK,KAAK,KAAK,EAAE;MAC5H,KAAK;AACH,eAAO,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,IAAI,KAAK,KAAK,KAAK,KAAK,KAAK,IAAI,KAAK,KAAK,KAAK,KAAK,KAAK,IAAI,KAAK,KAAK,KAAK,KAAK,KAAK,EAAE;MAC5H,KAAK;AACH,eAAO,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,IAAI,KAAK,KAAK,KAAK,KAAK,KAAK,IAAI,KAAK,KAAK,KAAK,KAAK,KAAK,IAAI,KAAK,KAAK,KAAK,KAAK,KAAK,EAAE;MAC5H,KAAK;AACH,eAAO,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,IAAI,KAAK,KAAK,KAAK,KAAK,KAAK,IAAI,KAAK,KAAK,KAAK,KAAK,KAAK,IAAI,KAAK,KAAK,KAAK,KAAK,KAAK,EAAE;MAC5H;AACE,cAAM,IAAI,MAAM,wCAAwC,OAAO,KAAK,CAAC,GAAG;IAC5E;EACF;AAGO,WAAS,QAAQ,IAAY,IAAY,IAAY,GAA8C,IAAY,IAAY,IAAqB;AACrJ,UAAM,CAAC,GAAG,GAAG,GAAG,CAAC,IAAI;AACrB,UAAM,KAAK,IAAI;AACf,UAAM,KAAK,IAAI;AACf,UAAM,KAAK,IAAI;AACf,UAAM,KAAK,IAAI;AACf,UAAM,KAAK,IAAI;AACf,UAAM,KAAK,IAAI;AACf,UAAM,KAAK,IAAI;AACf,UAAM,KAAK,IAAI;AACf,UAAM,KAAK,IAAI;AACf,UAAM,KAAK,IAAI;AACf,UAAM,KAAK,IAAI;AACf,UAAM,KAAK,IAAI;AACf,WAAO;OACJ,KAAK,KAAK,OAAO;OAAK,KAAK,MAAM;OAAK,KAAK,MAAM;MAAI;OACrD,KAAK,MAAM;OAAK,KAAK,KAAK,OAAO;OAAK,KAAK,MAAM;MAAI;OACrD,KAAK,MAAM;OAAK,KAAK,MAAM;OAAK,KAAK,KAAK,OAAO;MAAI;MACtD;MAAI;MAAI;MAAI;IACd;EACF;AAGO,WAAS,cAAc,OAAe,IAAI,GAAG,IAAI,GAAG,IAAI,GAAY;AACzE,UAAM,IAAI,KAAK,IAAI,KAAK;AACxB,UAAM,IAAI,KAAK,IAAI,KAAK;AACxB,WAAO,CAAC,GAAG,GAAG,CAAC,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;EACzD;AAGO,WAAS,SAAS,IAAuB,IAAgC;AAC9E,UAAM,MAAM,GAAG,CAAC,GAAI,MAAM,GAAG,CAAC,GAAI,MAAM,GAAG,CAAC,GAAI,MAAM,GAAG,EAAE;AAC3D,UAAM,MAAM,GAAG,CAAC,GAAI,MAAM,GAAG,CAAC,GAAI,MAAM,GAAG,CAAC,GAAI,MAAM,GAAG,EAAE;AAC3D,UAAM,MAAM,GAAG,CAAC,GAAI,MAAM,GAAG,CAAC,GAAI,MAAM,GAAG,EAAE,GAAI,MAAM,GAAG,EAAE;AAC5D,UAAM,MAAM,GAAG,CAAC,GAAI,MAAM,GAAG,CAAC,GAAI,MAAM,GAAG,EAAE,GAAI,MAAM,GAAG,EAAE;AAC5D,UAAM,MAAM,GAAG,CAAC,GAAI,MAAM,GAAG,CAAC,GAAI,MAAM,GAAG,CAAC,GAAI,MAAM,GAAG,EAAE;AAC3D,UAAM,MAAM,GAAG,CAAC,GAAI,MAAM,GAAG,CAAC,GAAI,MAAM,GAAG,CAAC,GAAI,MAAM,GAAG,EAAE;AAC3D,UAAM,MAAM,GAAG,CAAC,GAAI,MAAM,GAAG,CAAC,GAAI,MAAM,GAAG,EAAE,GAAI,MAAM,GAAG,EAAE;AAC5D,UAAM,MAAM,GAAG,CAAC,GAAI,MAAM,GAAG,CAAC,GAAI,MAAM,GAAG,EAAE,GAAI,MAAM,GAAG,EAAE;AAC5D,WAAO;MACL,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM;MAC1C,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM;MAC1C,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM;MAC1C,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM;MAC1C,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM;MAC1C,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM;MAC1C,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM;MAC1C,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM;MAC1C,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM;MAC1C,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM;MAC1C,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM;MAC1C,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM;MAC1C,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM;MAC1C,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM;MAC1C,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM;MAC1C,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM;IAC5C;EACF;AAGO,WAAS,aAAa,IAAgC;AAE3D,UAAM,MAAM,GAAG,CAAC,GAAI,MAAM,GAAG,CAAC,GAAI,MAAM,GAAG,CAAC;AAC5C,UAAM,MAAM,GAAG,CAAC,GAAI,MAAM,GAAG,CAAC,GAAI,MAAM,GAAG,CAAC;AAC5C,UAAM,MAAM,GAAG,CAAC,GAAI,MAAM,GAAG,CAAC,GAAI,MAAM,GAAG,EAAE;AAE7C,UAAM,MAAM,MAAM,MAAM,MAAM;AAC9B,UAAM,MAAM,MAAM,MAAM,MAAM;AAC9B,UAAM,MAAM,MAAM,MAAM,MAAM;AAC9B,UAAM,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM;AAC1C,QAAI,QAAQ,EAAG,QAAO,CAAC,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;AAChD,UAAM,SAAS,IAAI;AACnB,UAAM,KAAK;MACT,MAAM;OACL,MAAM,MAAM,MAAM,OAAO;OACzB,MAAM,MAAM,MAAM,OAAO;MAC1B,MAAM;OACL,MAAM,MAAM,MAAM,OAAO;OACzB,MAAM,MAAM,MAAM,OAAO;MAC1B,MAAM;OACL,MAAM,MAAM,MAAM,OAAO;OACzB,MAAM,MAAM,MAAM,OAAO;IAC5B;AAEA,QAAI,MAAM,GAAG,CAAC;AACd,OAAG,CAAC,IAAI,GAAG,CAAC;AACZ,OAAG,CAAC,IAAI;AACR,UAAM,GAAG,CAAC;AACV,OAAG,CAAC,IAAI,GAAG,CAAC;AACZ,OAAG,CAAC,IAAI;AACR,UAAM,GAAG,CAAC;AACV,OAAG,CAAC,IAAI,GAAG,CAAC;AACZ,OAAG,CAAC,IAAI;AACR,WAAO;EACT;AAGO,MAAM,WAAW,CAAC,MAAkC,EAAE,CAAC,MAAM,KAAK,EAAE,CAAC,MAAM,KAAK,EAAE,EAAE,MAAM,KAAK,EAAE,EAAE,MAAM;AAGzG,WAAS,eAAe,GAAsB,GAAW,GAAW,GAAqC;AAC9G,UAAM,IAAI,KAAK,EAAE,CAAC,IAAK,IAAI,EAAE,CAAC,IAAK,IAAI,EAAE,EAAE,IAAK,IAAI,EAAE,EAAE;AACxD,WAAO,EAAE,EAAE,CAAC,IAAK,IAAI,EAAE,CAAC,IAAK,IAAI,EAAE,CAAC,IAAK,IAAI,EAAE,EAAE,KAAM,IAAI,EAAE,CAAC,IAAK,IAAI,EAAE,CAAC,IAAK,IAAI,EAAE,CAAC,IAAK,IAAI,EAAE,EAAE,KAAM,IAAI,EAAE,CAAC,IAAK,IAAI,EAAE,CAAC,IAAK,IAAI,EAAE,EAAE,IAAK,IAAI,EAAE,EAAE,KAAM,CAAC;EAC/J;AAEA,MAAM,SAAS,CAAC,UAA4C,UAAU,SAAY,CAAC,GAAG,GAAG,CAAC,IAAI,OAAO,UAAU,WAAW,CAAC,OAAO,OAAO,KAAK,IAAI;AAO3I,WAAS,SAAS,GAA8B;AACrD,QAAI,EAAE,WAAW,OAAW,QAAO,CAAC,GAAG,EAAE,MAAM;AAC/C,QAAI,EAAE,YAAY,QAAW;AAC3B,YAAMC,KAAI,EAAE,YAAY,CAAC,GAAG,GAAG,CAAC;AAChC,aAAO,cAAc,EAAE,SAASA,GAAE,CAAC,GAAGA,GAAE,CAAC,GAAGA,GAAE,CAAC,CAAC;IAClD;AACA,QAAI,EAAE,aAAa,UAAa,EAAE,aAAa,UAAa,EAAE,UAAU,OAAW,QAAO;AAC1F,UAAM,IAAI,EAAE,YAAY,CAAC,GAAG,GAAG,CAAC;AAChC,UAAM,IAAI,EAAE,YAAY,CAAC,GAAG,GAAG,CAAC;AAChC,UAAM,IAAI,OAAO,EAAE,KAAK;AACxB,WAAO,QAAQ,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,oBAAoB,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,iBAAiB,KAAK,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC;EACpH;AAGO,MAAM,SAAS,CAAC,MAA6B,IAAI,CAAC,GAAG,CAAC,IAAI,UAAU;ACvJpE,WAAS,YAAY,OAAmC;AAC7D,aAAS,IAAI,MAAM,SAAS,GAAG,KAAK,GAAG,EAAE,EAAG,KAAK,MAAM,CAAC,KAAgB,MAAO,QAAO;AACtF,WAAO;EACT;AAEO,MAAM,aAAa,CAAC,YAA2D,YAAY,OAAO,IAAI,IAAI,YAAY,OAAO,IAAI,IAAI,YAAY,OAAO;AAExJ,WAAS,KAAK,MAAc,YAAqC,GAA6B;AACnG,WAAO;MACL;MACA;MACA,WAAW,IAAI,aAAa,EAAE,QAAQ;MACtC,SAAS,IAAI,aAAa,EAAE,OAAO;MACnC,KAAK,IAAI,aAAa,EAAE,GAAG;MAC3B,GAAI,EAAE,UAAU,EAAE,SAAS,WAAW,EAAE,OAAO,EAAE,IAAI,CAAC;MACtD,QAAQ,EAAE;IACZ;EACF;AAEA,MAAM,aAAa,CAAC,GAAW,GAAW,MAAwC;AAChF,UAAM,SAAS,KAAK,KAAK,IAAI,IAAI,IAAI,IAAI,IAAI,CAAC;AAC9C,UAAM,IAAI,KAAK,UAAU;AACzB,WAAO,CAAC,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;EAC7B;AAEO,WAAS,IAAI,QAAQ,GAAG,SAAS,GAAG,QAAQ,GAAG,gBAAgB,GAAG,iBAAiB,GAAG,gBAAgB,GAAqB;AAChI,UAAM,aAAa,EAAE,OAAO,QAAQ,OAAO,eAAe,gBAAgB,cAAc;AACxF,oBAAgB,KAAK,MAAM,aAAa;AACxC,qBAAiB,KAAK,MAAM,cAAc;AAC1C,oBAAgB,KAAK,MAAM,aAAa;AACxC,UAAM,UAAoB,CAAC;AAC3B,UAAM,WAAqB,CAAC;AAC5B,UAAM,UAAoB,CAAC;AAC3B,UAAM,MAAgB,CAAC;AACvB,UAAM,SAA0B,CAAC;AACjC,QAAI,mBAAmB;AACvB,QAAI,aAAa;AAEjB,UAAM,aAAa,CAAC,GAAS,GAAS,GAAS,MAAc,MAAcC,QAAeC,SAAgBC,QAAe,OAAe,OAAe,kBAAgC;AACrL,YAAM,eAAeF,SAAQ;AAC7B,YAAM,gBAAgBC,UAAS;AAC/B,YAAM,YAAYD,SAAQ;AAC1B,YAAM,aAAaC,UAAS;AAC5B,YAAM,YAAYC,SAAQ;AAC1B,YAAM,SAAS,QAAQ;AACvB,YAAM,SAAS,QAAQ;AACvB,UAAI,gBAAgB;AACpB,UAAI,aAAa;AACjB,YAAM,SAAS,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,EAAE;AAClC,eAAS,KAAK,GAAG,KAAK,QAAQ,MAAM;AAClC,cAAM,IAAI,KAAK,gBAAgB;AAC/B,iBAAS,KAAK,GAAG,KAAK,QAAQ,MAAM;AAClC,gBAAM,IAAI,KAAK,eAAe;AAC9B,iBAAO,CAAC,IAAI,IAAI;AAChB,iBAAO,CAAC,IAAI,IAAI;AAChB,iBAAO,CAAC,IAAI;AACZ,mBAAS,KAAK,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;AAC1C,iBAAO,CAAC,IAAI;AACZ,iBAAO,CAAC,IAAI;AACZ,iBAAO,CAAC,IAAIA,SAAQ,IAAI,IAAI;AAC5B,kBAAQ,KAAK,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;AACzC,cAAI,KAAK,KAAK,KAAK;AACnB,cAAI,KAAK,IAAI,KAAK,KAAK;AACvB,2BAAiB;QACnB;MACF;AACA,eAAS,KAAK,GAAG,KAAK,OAAO,MAAM;AACjC,iBAAS,KAAK,GAAG,KAAK,OAAO,MAAM;AACjC,gBAAM,IAAI,mBAAmB,KAAK,SAAS;AAC3C,gBAAM,IAAI,mBAAmB,KAAK,UAAU,KAAK;AACjD,gBAAM,IAAI,oBAAoB,KAAK,KAAK,UAAU,KAAK;AACvD,gBAAM,IAAI,oBAAoB,KAAK,KAAK,SAAS;AACjD,kBAAQ,KAAK,GAAG,GAAG,CAAC;AACpB,kBAAQ,KAAK,GAAG,GAAG,CAAC;AACpB,wBAAc;QAChB;MACF;AACA,aAAO,KAAK,EAAE,OAAO,YAAY,OAAO,YAAY,cAAc,CAAC;AACnE,oBAAc;AACd,0BAAoB;IACtB;AACA,eAAW,KAAK,KAAK,KAAK,IAAI,IAAI,OAAO,QAAQ,OAAO,eAAe,gBAAgB,CAAC;AACxF,eAAW,KAAK,KAAK,KAAK,GAAG,IAAI,OAAO,QAAQ,CAAC,OAAO,eAAe,gBAAgB,CAAC;AACxF,eAAW,KAAK,KAAK,KAAK,GAAG,GAAG,OAAO,OAAO,QAAQ,eAAe,eAAe,CAAC;AACrF,eAAW,KAAK,KAAK,KAAK,GAAG,IAAI,OAAO,OAAO,CAAC,QAAQ,eAAe,eAAe,CAAC;AACvF,eAAW,KAAK,KAAK,KAAK,GAAG,IAAI,OAAO,QAAQ,OAAO,eAAe,gBAAgB,CAAC;AACvF,eAAW,KAAK,KAAK,KAAK,IAAI,IAAI,OAAO,QAAQ,CAAC,OAAO,eAAe,gBAAgB,CAAC;AACzF,WAAO,KAAK,eAAe,YAAY,EAAE,UAAU,SAAS,KAAK,SAAS,OAAO,CAAC;EACpF;AAEA,WAAS,eAAe,WAAmB,cAAsB,QAAgB,gBAAwB,gBAAwB,WAAoB,YAAoB,aAA6B;AACpM,qBAAiB,KAAK,MAAM,cAAc;AAC1C,qBAAiB,KAAK,MAAM,cAAc;AAC1C,UAAM,UAAoB,CAAC;AAC3B,UAAM,WAAqB,CAAC;AAC5B,UAAM,UAAoB,CAAC;AAC3B,UAAM,MAAgB,CAAC;AACvB,UAAM,SAA0B,CAAC;AACjC,QAAI,QAAQ;AACZ,UAAMC,cAAyB,CAAC;AAChC,UAAM,aAAa,SAAS;AAC5B,QAAI,aAAa;AAEjB,UAAM,gBAAgB,MAAY;AAChC,UAAI,aAAa;AACjB,YAAM,SAAS,eAAe,aAAa;AAC3C,eAAS,IAAI,GAAG,KAAK,gBAAgB,KAAK;AACxC,cAAM,WAAqB,CAAC;AAC5B,cAAM,IAAI,IAAI;AACd,cAAM,SAAS,KAAK,eAAe,aAAa;AAChD,iBAAS,IAAI,GAAG,KAAK,gBAAgB,KAAK;AACxC,gBAAM,IAAI,IAAI;AACd,gBAAM,QAAQ,IAAI,cAAc;AAChC,gBAAM,WAAW,KAAK,IAAI,KAAK;AAC/B,gBAAM,WAAW,KAAK,IAAI,KAAK;AAC/B,mBAAS,KAAK,SAAS,UAAU,CAAC,IAAI,SAAS,YAAY,SAAS,QAAQ;AAC5E,gBAAM,IAAI,WAAW,UAAU,OAAO,QAAQ;AAC9C,kBAAQ,KAAK,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC;AAC7B,cAAI,KAAK,GAAG,IAAI,CAAC;AACjB,mBAAS,KAAK,OAAO;QACvB;AACAA,oBAAW,KAAK,QAAQ;MAC1B;AACA,eAAS,IAAI,GAAG,IAAI,gBAAgB,KAAK;AACvC,iBAAS,IAAI,GAAG,IAAI,gBAAgB,KAAK;AACvC,gBAAM,IAAIA,YAAW,CAAC,EAAG,CAAC;AAC1B,gBAAM,IAAIA,YAAW,IAAI,CAAC,EAAG,CAAC;AAC9B,gBAAM,IAAIA,YAAW,IAAI,CAAC,EAAG,IAAI,CAAC;AAClC,gBAAM,IAAIA,YAAW,CAAC,EAAG,IAAI,CAAC;AAC9B,kBAAQ,KAAK,GAAG,GAAG,CAAC;AACpB,kBAAQ,KAAK,GAAG,GAAG,CAAC;AACpB,wBAAc;QAChB;MACF;AACA,aAAO,KAAK,EAAE,OAAO,YAAY,OAAO,YAAY,eAAe,EAAE,CAAC;AACtE,oBAAc;IAChB;AAEA,UAAM,cAAc,CAAC,QAAuB;AAC1C,YAAM,mBAAmB;AACzB,UAAI,aAAa;AACjB,YAAM,SAAS,QAAQ,OAAO,YAAY;AAC1C,YAAMC,QAAO,QAAQ,OAAO,IAAI;AAChC,eAAS,IAAI,GAAG,KAAK,gBAAgB,KAAK;AACxC,iBAAS,KAAK,GAAG,aAAaA,OAAM,CAAC;AACrC,gBAAQ,KAAK,GAAGA,OAAM,CAAC;AACvB,YAAI,KAAK,KAAK,GAAG;AACjB;MACF;AACA,YAAM,iBAAiB;AACvB,eAAS,IAAI,GAAG,KAAK,gBAAgB,KAAK;AACxC,cAAM,IAAI,IAAI;AACd,cAAM,QAAQ,IAAI,cAAc;AAChC,cAAM,WAAW,KAAK,IAAI,KAAK;AAC/B,cAAM,WAAW,KAAK,IAAI,KAAK;AAC/B,iBAAS,KAAK,SAAS,UAAU,aAAaA,OAAM,SAAS,QAAQ;AACrE,gBAAQ,KAAK,GAAGA,OAAM,CAAC;AACvB,YAAI,KAAK,WAAW,MAAM,KAAK,WAAW,MAAMA,QAAO,GAAG;AAC1D;MACF;AACA,eAAS,IAAI,GAAG,IAAI,gBAAgB,KAAK;AACvC,cAAM,IAAI,mBAAmB;AAC7B,cAAM,IAAI,iBAAiB;AAC3B,YAAI,QAAQ,KAAM,SAAQ,KAAK,GAAG,IAAI,GAAG,CAAC;YACrC,SAAQ,KAAK,IAAI,GAAG,GAAG,CAAC;AAC7B,sBAAc;MAChB;AACA,aAAO,KAAK,EAAE,OAAO,YAAY,OAAO,YAAY,eAAe,QAAQ,OAAO,IAAI,EAAE,CAAC;AACzF,oBAAc;IAChB;AAEA,kBAAc;AACd,QAAI,cAAc,OAAO;AACvB,UAAI,YAAY,EAAG,aAAY,IAAI;AACnC,UAAI,eAAe,EAAG,aAAY,KAAK;IACzC;AACA,WAAO,EAAE,UAAU,SAAS,KAAK,SAAS,OAAO;EACnD;AAEO,WAAS,SAAS,YAAY,GAAG,eAAe,GAAG,SAAS,GAAG,iBAAiB,IAAI,iBAAiB,GAAG,YAAY,OAAO,aAAa,GAAG,cAAc,KAAK,KAAK,GAAqB;AAC7L,UAAM,aAAa,EAAE,WAAW,cAAc,QAAQ,gBAAgB,gBAAgB,WAAW,YAAY,YAAY;AACzH,WAAO,KAAK,oBAAoB,YAAY,eAAe,WAAW,cAAc,QAAQ,gBAAgB,gBAAgB,WAAW,YAAY,WAAW,CAAC;EACjK;AAEO,WAAS,KAAK,SAAS,GAAG,SAAS,GAAG,iBAAiB,IAAI,iBAAiB,GAAG,YAAY,OAAO,aAAa,GAAG,cAAc,KAAK,KAAK,GAAqB;AACpK,UAAM,aAAa,EAAE,QAAQ,QAAQ,gBAAgB,gBAAgB,WAAW,YAAY,YAAY;AACxG,WAAO,KAAK,gBAAgB,YAAY,eAAe,GAAG,QAAQ,QAAQ,gBAAgB,gBAAgB,WAAW,YAAY,WAAW,CAAC;EAC/I;AAEO,WAAS,OAAO,SAAS,GAAG,gBAAgB,IAAI,iBAAiB,IAAI,WAAW,GAAG,YAAY,KAAK,KAAK,GAAG,aAAa,GAAG,cAAc,KAAK,IAAsB;AAC1K,UAAM,aAAa,EAAE,QAAQ,eAAe,gBAAgB,UAAU,WAAW,YAAY,YAAY;AACzG,oBAAgB,KAAK,IAAI,GAAG,KAAK,MAAM,aAAa,CAAC;AACrD,qBAAiB,KAAK,IAAI,GAAG,KAAK,MAAM,cAAc,CAAC;AACvD,UAAM,WAAW,KAAK,IAAI,aAAa,aAAa,KAAK,EAAE;AAC3D,QAAI,QAAQ;AACZ,UAAM,OAAmB,CAAC;AAC1B,UAAM,UAAoB,CAAC;AAC3B,UAAM,WAAqB,CAAC;AAC5B,UAAM,UAAoB,CAAC;AAC3B,UAAM,MAAgB,CAAC;AACvB,aAAS,KAAK,GAAG,MAAM,gBAAgB,MAAM;AAC3C,YAAM,cAAwB,CAAC;AAC/B,YAAM,IAAI,KAAK;AACf,UAAI,UAAU;AACd,UAAI,OAAO,KAAK,eAAe,EAAG,WAAU,MAAM;eACzC,OAAO,kBAAkB,aAAa,KAAK,GAAI,WAAU,OAAO;AACzE,eAAS,KAAK,GAAG,MAAM,eAAe,MAAM;AAC1C,cAAM,IAAI,KAAK;AACf,cAAM,IAAI,CAAC,SAAS,KAAK,IAAI,WAAW,IAAI,SAAS,IAAI,KAAK,IAAI,aAAa,IAAI,WAAW;AAC9F,cAAM,IAAI,SAAS,KAAK,IAAI,aAAa,IAAI,WAAW;AACxD,cAAM,IAAI,SAAS,KAAK,IAAI,WAAW,IAAI,SAAS,IAAI,KAAK,IAAI,aAAa,IAAI,WAAW;AAC7F,iBAAS,KAAK,GAAG,GAAG,CAAC;AACrB,cAAM,IAAI,WAAW,GAAG,GAAG,CAAC;AAC5B,gBAAQ,KAAK,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC;AAC7B,YAAI,KAAK,IAAI,SAAS,IAAI,CAAC;AAC3B,oBAAY,KAAK,OAAO;MAC1B;AACA,WAAK,KAAK,WAAW;IACvB;AACA,aAAS,KAAK,GAAG,KAAK,gBAAgB,MAAM;AAC1C,eAAS,KAAK,GAAG,KAAK,eAAe,MAAM;AACzC,cAAM,IAAI,KAAK,EAAE,EAAG,KAAK,CAAC;AAC1B,cAAM,IAAI,KAAK,EAAE,EAAG,EAAE;AACtB,cAAM,IAAI,KAAK,KAAK,CAAC,EAAG,EAAE;AAC1B,cAAM,IAAI,KAAK,KAAK,CAAC,EAAG,KAAK,CAAC;AAC9B,YAAI,OAAO,KAAK,aAAa,EAAG,SAAQ,KAAK,GAAG,GAAG,CAAC;AACpD,YAAI,OAAO,iBAAiB,KAAK,WAAW,KAAK,GAAI,SAAQ,KAAK,GAAG,GAAG,CAAC;MAC3E;IACF;AACA,WAAO,KAAK,kBAAkB,YAAY,EAAE,UAAU,SAAS,KAAK,SAAS,QAAQ,CAAC,EAAE,CAAC;EAC3F;AAEO,WAAS,MAAM,QAAQ,GAAG,SAAS,GAAG,gBAAgB,GAAG,iBAAiB,GAAqB;AACpG,UAAM,aAAa,EAAE,OAAO,QAAQ,eAAe,eAAe;AAClE,UAAM,YAAY,QAAQ;AAC1B,UAAM,aAAa,SAAS;AAC5B,UAAM,QAAQ,KAAK,MAAM,aAAa;AACtC,UAAM,QAAQ,KAAK,MAAM,cAAc;AACvC,UAAM,SAAS,QAAQ;AACvB,UAAM,SAAS,QAAQ;AACvB,UAAM,eAAe,QAAQ;AAC7B,UAAM,gBAAgB,SAAS;AAC/B,UAAM,UAAoB,CAAC;AAC3B,UAAM,WAAqB,CAAC;AAC5B,UAAM,UAAoB,CAAC;AAC3B,UAAM,MAAgB,CAAC;AACvB,aAAS,KAAK,GAAG,KAAK,QAAQ,MAAM;AAClC,YAAM,IAAI,KAAK,gBAAgB;AAC/B,eAAS,KAAK,GAAG,KAAK,QAAQ,MAAM;AAClC,cAAM,IAAI,KAAK,eAAe;AAC9B,iBAAS,KAAK,GAAG,CAAC,GAAG,CAAC;AACtB,gBAAQ,KAAK,GAAG,GAAG,CAAC;AACpB,YAAI,KAAK,KAAK,KAAK;AACnB,YAAI,KAAK,IAAI,KAAK,KAAK;MACzB;IACF;AACA,aAAS,KAAK,GAAG,KAAK,OAAO,MAAM;AACjC,eAAS,KAAK,GAAG,KAAK,OAAO,MAAM;AACjC,cAAM,IAAI,KAAK,SAAS;AACxB,cAAM,IAAI,KAAK,UAAU,KAAK;AAC9B,cAAM,IAAI,KAAK,IAAI,UAAU,KAAK;AAClC,cAAM,IAAI,KAAK,IAAI,SAAS;AAC5B,gBAAQ,KAAK,GAAG,GAAG,CAAC;AACpB,gBAAQ,KAAK,GAAG,GAAG,CAAC;MACtB;IACF;AACA,WAAO,KAAK,iBAAiB,YAAY,EAAE,UAAU,SAAS,KAAK,SAAS,QAAQ,CAAC,EAAE,CAAC;EAC1F;AAEO,WAAS,OAAO,SAAS,GAAG,WAAW,IAAI,aAAa,GAAG,cAAc,KAAK,KAAK,GAAqB;AAC7G,UAAM,aAAa,EAAE,QAAQ,UAAU,YAAY,YAAY;AAC/D,eAAW,KAAK,IAAI,GAAG,QAAQ;AAC/B,UAAM,UAAoB,CAAC;AAC3B,UAAM,WAAqB,CAAC;AAC5B,UAAM,UAAoB,CAAC;AAC3B,UAAM,MAAgB,CAAC;AACvB,aAAS,KAAK,GAAG,GAAG,CAAC;AACrB,YAAQ,KAAK,GAAG,GAAG,CAAC;AACpB,QAAI,KAAK,KAAK,GAAG;AACjB,aAAS,IAAI,GAAG,IAAI,GAAG,KAAK,UAAU,KAAK,KAAK,GAAG;AACjD,YAAM,UAAU,aAAc,IAAI,WAAY;AAE9C,eAAS,KAAK,SAAS,KAAK,IAAI,OAAO,GAAG,SAAS,KAAK,IAAI,OAAO,GAAG,CAAC;AACvE,cAAQ,KAAK,GAAG,GAAG,CAAC;AACpB,UAAI,MAAO,SAAS,CAAC,IAAe,SAAS,KAAK,IAAK,SAAS,IAAI,CAAC,IAAe,SAAS,KAAK,CAAC;IACrG;AACA,aAAS,IAAI,GAAG,KAAK,UAAU,IAAK,SAAQ,KAAK,GAAG,IAAI,GAAG,CAAC;AAC5D,WAAO,KAAK,kBAAkB,YAAY,EAAE,UAAU,SAAS,KAAK,SAAS,QAAQ,CAAC,EAAE,CAAC;EAC3F;AAEO,WAAS,KAAK,cAAc,KAAK,cAAc,GAAG,gBAAgB,IAAI,cAAc,GAAG,aAAa,GAAG,cAAc,KAAK,KAAK,GAAqB;AACzJ,UAAM,aAAa,EAAE,aAAa,aAAa,eAAe,aAAa,YAAY,YAAY;AACnG,oBAAgB,KAAK,IAAI,GAAG,aAAa;AACzC,kBAAc,KAAK,IAAI,GAAG,WAAW;AACrC,UAAM,UAAoB,CAAC;AAC3B,UAAM,WAAqB,CAAC;AAC5B,UAAM,UAAoB,CAAC;AAC3B,UAAM,MAAgB,CAAC;AACvB,QAAI,SAAS;AACb,UAAM,cAAc,cAAc,eAAe;AACjD,aAAS,IAAI,GAAG,KAAK,aAAa,KAAK;AACrC,eAAS,IAAI,GAAG,KAAK,eAAe,KAAK;AACvC,cAAM,UAAU,aAAc,IAAI,gBAAiB;AACnD,cAAM,IAAI,SAAS,KAAK,IAAI,OAAO;AACnC,cAAM,IAAI,SAAS,KAAK,IAAI,OAAO;AACnC,iBAAS,KAAK,GAAG,GAAG,CAAC;AACrB,gBAAQ,KAAK,GAAG,GAAG,CAAC;AACpB,YAAI,MAAM,IAAI,cAAc,KAAK,IAAI,IAAI,cAAc,KAAK,CAAC;MAC/D;AACA,gBAAU;IACZ;AACA,aAAS,IAAI,GAAG,IAAI,aAAa,KAAK;AACpC,YAAM,oBAAoB,KAAK,gBAAgB;AAC/C,eAAS,IAAI,GAAG,IAAI,eAAe,KAAK;AACtC,cAAM,UAAU,IAAI;AACpB,cAAM,IAAI;AACV,cAAM,IAAI,UAAU,gBAAgB;AACpC,cAAM,IAAI,UAAU,gBAAgB;AACpC,cAAM,IAAI,UAAU;AACpB,gBAAQ,KAAK,GAAG,GAAG,CAAC;AACpB,gBAAQ,KAAK,GAAG,GAAG,CAAC;MACtB;IACF;AACA,WAAO,KAAK,gBAAgB,YAAY,EAAE,UAAU,SAAS,KAAK,SAAS,QAAQ,CAAC,EAAE,CAAC;EACzF;AAEO,WAAS,MAAM,QAAkD,WAAW,IAAI,WAAW,GAAG,YAAY,KAAK,KAAK,GAAqB;AAC9I,UAAM,aAAa,EAAE,QAAQ,OAAO,IAAI,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,EAAE,EAAE,GAAG,UAAU,UAAU,UAAU;AAC/F,eAAW,KAAK,MAAM,QAAQ;AAC9B,gBAAY,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,KAAK,GAAG,SAAS,CAAC;AACxD,UAAM,UAAoB,CAAC;AAC3B,UAAM,WAAqB,CAAC;AAC5B,UAAM,MAAgB,CAAC;AACvB,UAAM,cAAwB,CAAC;AAC/B,UAAM,UAAoB,CAAC;AAC3B,UAAM,kBAAkB,IAAM;AAC9B,UAAM,SAAS,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,EAAE;AAClC,UAAM,YAAY,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,EAAE;AACrC,UAAM,aAAa,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,EAAE;AACtC,QAAI,KAAK;AACT,QAAI,KAAK;AACT,UAAM,OAAO,OAAO,SAAS;AAC7B,UAAM,KAAK,CAAC,MAAuB,OAAO,CAAC,EAAgC,CAAC;AAC5E,UAAM,KAAK,CAAC,MAAuB,OAAO,CAAC,EAAgC,CAAC;AAC5E,UAAM,gBAAgB,MAAY;AAChC,YAAM,IAAI,WAAW,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;AACjD,aAAO,IAAI,EAAE,CAAC;AACd,aAAO,IAAI,EAAE,CAAC;AACd,aAAO,IAAI,EAAE,CAAC;IAChB;AACA,aAAS,IAAI,GAAG,KAAK,MAAM,KAAK;AAC9B,UAAI,MAAM,GAAG;AACX,aAAK,GAAG,IAAI,CAAC,IAAI,GAAG,CAAC;AACrB,aAAK,GAAG,IAAI,CAAC,IAAI,GAAG,CAAC;AACrB,eAAO,IAAI,KAAK;AAChB,eAAO,IAAI,CAAC;AACZ,eAAO,IAAI,KAAK;AAChB,mBAAW,IAAI,OAAO;AACtB,mBAAW,IAAI,OAAO;AACtB,mBAAW,IAAI,OAAO;AACtB,sBAAc;AACd,oBAAY,KAAK,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;MAC/C,WAAW,MAAM,MAAM;AACrB,oBAAY,KAAK,WAAW,GAAG,WAAW,GAAG,WAAW,CAAC;MAC3D,OAAO;AACL,aAAK,GAAG,IAAI,CAAC,IAAI,GAAG,CAAC;AACrB,aAAK,GAAG,IAAI,CAAC,IAAI,GAAG,CAAC;AACrB,eAAO,IAAI,KAAK;AAChB,eAAO,IAAI,CAAC;AACZ,eAAO,IAAI,KAAK;AAChB,kBAAU,IAAI,OAAO;AACrB,kBAAU,IAAI,OAAO;AACrB,kBAAU,IAAI,OAAO;AACrB,eAAO,KAAK,WAAW;AACvB,eAAO,KAAK,WAAW;AACvB,eAAO,KAAK,WAAW;AACvB,sBAAc;AACd,oBAAY,KAAK,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;AAC7C,mBAAW,IAAI,UAAU;AACzB,mBAAW,IAAI,UAAU;AACzB,mBAAW,IAAI,UAAU;MAC3B;IACF;AACA,aAAS,IAAI,GAAG,KAAK,UAAU,KAAK;AAClC,YAAM,MAAM,WAAW,IAAI,kBAAkB;AAC7C,YAAM,MAAM,KAAK,IAAI,GAAG;AACxB,YAAM,MAAM,KAAK,IAAI,GAAG;AACxB,eAAS,IAAI,GAAG,KAAK,MAAM,KAAK;AAC9B,iBAAS,KAAK,GAAG,CAAC,IAAI,KAAK,GAAG,CAAC,GAAG,GAAG,CAAC,IAAI,GAAG;AAC7C,YAAI,KAAK,IAAI,UAAU,KAAK,OAAO,SAAS,EAAE;AAC9C,cAAM,IAAK,YAAY,IAAI,IAAI,CAAC,IAAe;AAC/C,cAAM,IAAI,YAAY,IAAI,IAAI,CAAC;AAC/B,cAAM,IAAK,YAAY,IAAI,IAAI,CAAC,IAAe;AAC/C,gBAAQ,KAAK,GAAG,GAAG,CAAC;MACtB;IACF;AACA,aAAS,IAAI,GAAG,IAAI,UAAU,KAAK;AACjC,eAAS,IAAI,GAAG,IAAI,OAAO,SAAS,GAAG,KAAK;AAC1C,cAAM,OAAO,IAAI,IAAI,OAAO;AAC5B,cAAM,IAAI;AACV,cAAM,IAAI,OAAO,OAAO;AACxB,cAAM,IAAI,OAAO,OAAO,SAAS;AACjC,cAAM,IAAI,OAAO;AACjB,gBAAQ,KAAK,GAAG,GAAG,CAAC;AACpB,gBAAQ,KAAK,GAAG,GAAG,CAAC;MACtB;IACF;AACA,WAAO,KAAK,iBAAiB,YAAY,EAAE,UAAU,SAAS,KAAK,SAAS,QAAQ,CAAC,EAAE,CAAC;EAC1F;AAEO,WAAS,MAAM,SAAS,GAAG,OAAO,KAAK,iBAAiB,IAAI,kBAAkB,IAAI,MAAM,KAAK,KAAK,GAAqB;AAC5H,UAAM,aAAa,EAAE,QAAQ,MAAM,gBAAgB,iBAAiB,IAAI;AACxE,qBAAiB,KAAK,MAAM,cAAc;AAC1C,sBAAkB,KAAK,MAAM,eAAe;AAC5C,UAAM,UAAoB,CAAC;AAC3B,UAAM,WAAqB,CAAC;AAC5B,UAAM,UAAoB,CAAC;AAC3B,UAAM,MAAgB,CAAC;AACvB,aAAS,IAAI,GAAG,KAAK,gBAAgB,KAAK;AACxC,eAAS,IAAI,GAAG,KAAK,iBAAiB,KAAK;AACzC,cAAM,IAAK,IAAI,kBAAmB;AAClC,cAAM,IAAK,IAAI,iBAAkB,KAAK,KAAK;AAC3C,cAAM,KAAK,SAAS,OAAO,KAAK,IAAI,CAAC,KAAK,KAAK,IAAI,CAAC;AACpD,cAAM,KAAK,SAAS,OAAO,KAAK,IAAI,CAAC,KAAK,KAAK,IAAI,CAAC;AACpD,cAAM,IAAI,OAAO,KAAK,IAAI,CAAC;AAC3B,iBAAS,KAAK,GAAG,GAAG,CAAC;AACrB,cAAM,KAAK,SAAS,KAAK,IAAI,CAAC;AAC9B,cAAM,KAAK,SAAS,KAAK,IAAI,CAAC;AAC9B,cAAM,IAAI,WAAW,IAAI,IAAI,IAAI,IAAI,IAAI,CAAC;AAC1C,gBAAQ,KAAK,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC;AAC7B,YAAI,KAAK,IAAI,eAAe;AAC5B,YAAI,KAAK,IAAI,cAAc;MAC7B;IACF;AACA,aAAS,IAAI,GAAG,KAAK,gBAAgB,KAAK;AACxC,eAAS,IAAI,GAAG,KAAK,iBAAiB,KAAK;AACzC,cAAM,KAAK,kBAAkB,KAAK,IAAI,IAAI;AAC1C,cAAM,KAAK,kBAAkB,MAAM,IAAI,KAAK,IAAI;AAChD,cAAM,KAAK,kBAAkB,MAAM,IAAI,KAAK;AAC5C,cAAM,KAAK,kBAAkB,KAAK,IAAI;AACtC,gBAAQ,KAAK,GAAG,GAAG,CAAC;AACpB,gBAAQ,KAAK,GAAG,GAAG,CAAC;MACtB;IACF;AACA,WAAO,KAAK,iBAAiB,YAAY,EAAE,UAAU,SAAS,KAAK,SAAS,QAAQ,CAAC,EAAE,CAAC;EAC1F;ACpcO,WAAS,WAAW,MAAY,UAAiD;AACtF,UAAM,CAAC,GAAG,GAAG,CAAC,IAAI;AAClB,UAAM,KAAK,IAAI;AACf,UAAM,KAAK,IAAI;AACf,UAAM,KAAK,IAAI;AACf,UAAM,QAAQ,SACX,IAAI,CAAC,UAAU,EAAE,IAAI,KAAK,IAAI,CAAC,IAAI,KAAK,IAAI,KAAK,IAAI,CAAC,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,KAAK,IAAI,IAAI,KAAK,IAAI,KAAK,IAAI,CAAC,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,KAAK,IAAI,CAAC,IAAI,KAAK,IAAI,KAAK,IAAI,CAAC,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,KAAK,IAAI,IAAI,KAAK,IAAI,KAAK,IAAI,CAAC,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,EAC5O,OAAO,CAAC,SAAS,KAAK,KAAK,KAAK,MAAM,KAAK,KAAK,KAAK,EAAE;AAC1D,UAAM,OAAO,CAAC,IAAY,IAAY,WAA+B,CAAC,GAAG,oBAAI,IAAI,CAAC,IAAI,IAAI,GAAG,OAAO,OAAO,CAAC,MAAM,IAAI,MAAM,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC;AAC3J,UAAM,KAAK,KAAK,CAAC,IAAI,IAAI,MAAM,QAAQ,CAAC,SAAS,CAAC,KAAK,IAAI,KAAK,EAAE,CAAC,CAAC;AACpE,UAAM,KAAK,KAAK,CAAC,IAAI,IAAI,MAAM,QAAQ,CAAC,SAAS,CAAC,KAAK,IAAI,KAAK,EAAE,CAAC,CAAC;AACpE,UAAM,KAAK,GAAG,SAAS;AACvB,UAAM,KAAK,GAAG,SAAS;AACvB,UAAM,QAAQ,CAAC,GAAW,MAAuB;AAC/C,UAAI,IAAI,KAAK,IAAI,KAAK,KAAK,MAAM,KAAK,GAAI,QAAO;AACjD,YAAM,MAAO,GAAG,CAAC,IAAgB,GAAG,IAAI,CAAC,KAAgB;AACzD,YAAM,MAAO,GAAG,CAAC,IAAgB,GAAG,IAAI,CAAC,KAAgB;AACzD,aAAO,CAAC,MAAM,KAAK,CAAC,SAAS,KAAK,KAAK,MAAM,KAAK,KAAK,MAAM,KAAK,KAAK,MAAM,KAAK,KAAK,EAAE;IAC3F;AACA,UAAM,YAAsB,CAAC;AAC7B,UAAM,UAAoB,CAAC;AAC3B,UAAM,MAAgB,CAAC;AACvB,UAAM,UAAoB,CAAC;AAC3B,UAAM,OAAO,CAAC,SAAyD,QAA2C,OAAqD;AACrK,YAAM,OAAO,UAAU,SAAS;AAChC,eAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,kBAAU,KAAK,GAAI,QAAQ,CAAC,CAA8B;AAC1D,gBAAQ,KAAK,GAAG,MAAM;AACtB,YAAI,KAAK,GAAI,GAAG,CAAC,CAAsB;MACzC;AACA,cAAQ,KAAK,MAAM,OAAO,GAAG,OAAO,GAAG,MAAM,OAAO,GAAG,OAAO,CAAC;IACjE;AACA,UAAM,KAAK,CAAC,OAAuB,IAAI,MAAM;AAC7C,UAAM,KAAK,CAAC,OAAuB,IAAI,MAAM;AAC7C,aAAS,IAAI,GAAG,IAAI,IAAI,KAAK;AAC3B,eAAS,IAAI,GAAG,IAAI,IAAI,KAAK;AAC3B,YAAI,CAAC,MAAM,GAAG,CAAC,EAAG;AAClB,cAAM,KAAK,GAAG,CAAC;AACf,cAAM,KAAK,GAAG,IAAI,CAAC;AACnB,cAAM,KAAK,GAAG,CAAC;AACf,cAAM,KAAK,GAAG,IAAI,CAAC;AACnB,aAAK,CAAC,CAAC,IAAI,IAAI,EAAE,GAAG,CAAC,IAAI,IAAI,EAAE,GAAG,CAAC,IAAI,IAAI,EAAE,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,GAAG,GAAG,EAAE,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,GAAG,EAAE,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,GAAG,EAAE,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,GAAG,EAAE,CAAC,CAAC,CAAC;AAClJ,aAAK,CAAC,CAAC,IAAI,IAAI,CAAC,EAAE,GAAG,CAAC,IAAI,IAAI,CAAC,EAAE,GAAG,CAAC,IAAI,IAAI,CAAC,EAAE,GAAG,CAAC,IAAI,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,GAAG,EAAE,GAAG,CAAC,CAAC,IAAI,GAAG,EAAE,GAAG,GAAG,EAAE,CAAC,GAAG,CAAC,IAAI,GAAG,EAAE,GAAG,GAAG,EAAE,CAAC,GAAG,CAAC,IAAI,GAAG,EAAE,GAAG,GAAG,EAAE,CAAC,GAAG,CAAC,IAAI,GAAG,EAAE,GAAG,GAAG,EAAE,CAAC,CAAC,CAAC;AACvK,cAAM,KAAK,CAAC,OAAuB,IAAI,MAAM;AAC7C,YAAI,CAAC,MAAM,IAAI,GAAG,CAAC,EAAG,MAAK,CAAC,CAAC,IAAI,IAAI,CAAC,EAAE,GAAG,CAAC,IAAI,IAAI,EAAE,GAAG,CAAC,IAAI,IAAI,EAAE,GAAG,CAAC,IAAI,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,EAAE,GAAG,GAAG,EAAE,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,GAAG,EAAE,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,GAAG,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,GAAG,GAAG,EAAE,CAAC,CAAC,CAAC;AAC7K,YAAI,CAAC,MAAM,IAAI,GAAG,CAAC,EAAG,MAAK,CAAC,CAAC,IAAI,IAAI,EAAE,GAAG,CAAC,IAAI,IAAI,CAAC,EAAE,GAAG,CAAC,IAAI,IAAI,CAAC,EAAE,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,GAAG,GAAG,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,GAAG,GAAG,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,GAAG,GAAG,EAAE,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,GAAG,EAAE,CAAC,CAAC,CAAC;AAC5K,YAAI,CAAC,MAAM,GAAG,IAAI,CAAC,EAAG,MAAK,CAAC,CAAC,IAAI,IAAI,CAAC,EAAE,GAAG,CAAC,IAAI,IAAI,CAAC,EAAE,GAAG,CAAC,IAAI,IAAI,EAAE,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,GAAG,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,GAAG,EAAE,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,GAAG,EAAE,CAAC,CAAC,CAAC;AAC7K,YAAI,CAAC,MAAM,GAAG,IAAI,CAAC,EAAG,MAAK,CAAC,CAAC,IAAI,IAAI,EAAE,GAAG,CAAC,IAAI,IAAI,EAAE,GAAG,CAAC,IAAI,IAAI,CAAC,EAAE,GAAG,CAAC,IAAI,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,GAAG,GAAG,EAAE,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,GAAG,EAAE,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;MAC9K;IACF;AACA,WAAO;MACL,MAAM;MACN,YAAY,EAAE,MAAM,CAAC,GAAG,GAAG,CAAC,GAAG,UAAU,MAAM,IAAI,CAAC,UAAU,EAAE,KAAK,CAAC,KAAK,IAAI,KAAK,EAAE,GAAG,KAAK,CAAC,KAAK,IAAI,KAAK,EAAE,EAAE,EAAE,EAAE;MACrH,WAAW,IAAI,aAAa,SAAS;MACrC,SAAS,IAAI,aAAa,OAAO;MACjC,KAAK,IAAI,aAAa,GAAG;MACzB,SAAS,WAAW,OAAO;MAC3B,QAAQ,CAAC;IACX;EACF;AC9DA,MAAM,SAAS;IAEd,aAAa,SAAW,MAAM,aAAa,MAAM,GAAI;AAEpD,YAAM,WAAW,eAAe,YAAY;AAC5C,YAAM,WAAW,WAAW,YAAa,CAAE,IAAI,MAAM,KAAK;AAC1D,UAAI,YAAY,WAAY,MAAM,GAAG,UAAU,KAAK,IAAK;AACzD,YAAM,YAAY,CAAC;AAEnB,UAAK,CAAE,aAAa,UAAU,SAAS,UAAU,KAAO,QAAO;AAE/D,UAAI,MAAM,MAAM,MAAM,MAAM,GAAG,GAAG;AAElC,UAAK,SAAW,aAAY,eAAgB,MAAM,aAAa,WAAW,GAAI;AAG9E,UAAK,KAAK,SAAS,KAAK,KAAM;AAE7B,eAAO,OAAO,KAAM,CAAE;AACtB,eAAO,OAAO,KAAM,CAAE;AAEtB,iBAAU,IAAI,KAAK,IAAI,UAAU,KAAK,KAAM;AAE3C,cAAI,KAAM,CAAE;AACZ,cAAI,KAAM,IAAI,CAAE;AAChB,cAAK,IAAI,KAAO,QAAO;AACvB,cAAK,IAAI,KAAO,QAAO;AACvB,cAAK,IAAI,KAAO,QAAO;AACvB,cAAK,IAAI,KAAO,QAAO;QAExB;AAGA,kBAAU,KAAK,IAAK,OAAO,MAAM,OAAO,IAAK;AAC7C,kBAAU,YAAY,IAAI,QAAQ,UAAU;MAE7C;AAEA,mBAAc,WAAW,WAAW,KAAK,MAAM,MAAM,SAAS,CAAE;AAEhE,aAAO;IAER;EAED;AAGA,WAAS,WAAY,MAAM,OAAO,KAAK,KAAK,WAAY;AAEvD,QAAI,GAAG;AAEP,QAAK,cAAgB,WAAY,MAAM,OAAO,KAAK,GAAI,IAAI,GAAM;AAEhE,WAAM,IAAI,OAAO,IAAI,KAAK,KAAK,IAAM,QAAO,WAAY,GAAG,KAAM,CAAE,GAAG,KAAM,IAAI,CAAE,GAAG,IAAK;IAE3F,OAAO;AAEN,WAAM,IAAI,MAAM,KAAK,KAAK,OAAO,KAAK,IAAM,QAAO,WAAY,GAAG,KAAM,CAAE,GAAG,KAAM,IAAI,CAAE,GAAG,IAAK;IAElG;AAEA,QAAK,QAAQ,OAAQ,MAAM,KAAK,IAAK,GAAI;AAExC,iBAAY,IAAK;AACjB,aAAO,KAAK;IAEb;AAEA,WAAO;EAER;AAGA,WAAS,aAAc,OAAO,KAAM;AAEnC,QAAK,CAAE,MAAQ,QAAO;AACtB,QAAK,CAAE,IAAM,OAAM;AAEnB,QAAI,IAAI,OACP;AACD,OAAG;AAEF,cAAQ;AAER,UAAK,CAAE,EAAE,YAAa,OAAQ,GAAG,EAAE,IAAK,KAAK,KAAM,EAAE,MAAM,GAAG,EAAE,IAAK,MAAM,IAAM;AAEhF,mBAAY,CAAE;AACd,YAAI,MAAM,EAAE;AACZ,YAAK,MAAM,EAAE,KAAO;AACpB,gBAAQ;MAET,OAAO;AAEN,YAAI,EAAE;MAEP;IAED,SAAU,SAAS,MAAM;AAEzB,WAAO;EAER;AAGA,WAAS,aAAc,KAAK,WAAW,KAAK,MAAM,MAAM,SAAS,MAAO;AAEvE,QAAK,CAAE,IAAM;AAGb,QAAK,CAAE,QAAQ,QAAU,YAAY,KAAK,MAAM,MAAM,OAAQ;AAE9D,QAAI,OAAO,KACV,MAAM;AAGP,WAAQ,IAAI,SAAS,IAAI,MAAO;AAE/B,aAAO,IAAI;AACX,aAAO,IAAI;AAEX,UAAK,UAAU,YAAa,KAAK,MAAM,MAAM,OAAQ,IAAI,MAAO,GAAI,GAAI;AAGvE,kBAAU,KAAM,KAAK,IAAI,MAAM,CAAE;AACjC,kBAAU,KAAM,IAAI,IAAI,MAAM,CAAE;AAChC,kBAAU,KAAM,KAAK,IAAI,MAAM,CAAE;AAEjC,mBAAY,GAAI;AAGhB,cAAM,KAAK;AACX,eAAO,KAAK;AAEZ;MAED;AAEA,YAAM;AAGN,UAAK,QAAQ,MAAO;AAGnB,YAAK,CAAE,MAAO;AAEb,uBAAc,aAAc,GAAI,GAAG,WAAW,KAAK,MAAM,MAAM,SAAS,CAAE;QAI3E,WAAY,SAAS,GAAI;AAExB,gBAAM,uBAAwB,aAAc,GAAI,GAAG,WAAW,GAAI;AAClE,uBAAc,KAAK,WAAW,KAAK,MAAM,MAAM,SAAS,CAAE;QAI3D,WAAY,SAAS,GAAI;AAExB,sBAAa,KAAK,WAAW,KAAK,MAAM,MAAM,OAAQ;QAEvD;AAEA;MAED;IAED;EAED;AAGA,WAAS,MAAO,KAAM;AAErB,UAAM,IAAI,IAAI,MACb,IAAI,KACJ,IAAI,IAAI;AAET,QAAK,KAAM,GAAG,GAAG,CAAE,KAAK,EAAI,QAAO;AAGnC,UAAM,KAAK,EAAE,GAAG,KAAK,EAAE,GAAG,KAAK,EAAE,GAAG,KAAK,EAAE,GAAG,KAAK,EAAE,GAAG,KAAK,EAAE;AAG/D,UAAM,KAAK,KAAK,KAAO,KAAK,KAAK,KAAK,KAAS,KAAK,KAAK,KAAK,IAC7D,KAAK,KAAK,KAAO,KAAK,KAAK,KAAK,KAAS,KAAK,KAAK,KAAK,IACxD,KAAK,KAAK,KAAO,KAAK,KAAK,KAAK,KAAS,KAAK,KAAK,KAAK,IACxD,KAAK,KAAK,KAAO,KAAK,KAAK,KAAK,KAAS,KAAK,KAAK,KAAK;AAEzD,QAAI,IAAI,EAAE;AACV,WAAQ,MAAM,GAAI;AAEjB,UAAK,EAAE,KAAK,MAAM,EAAE,KAAK,MAAM,EAAE,KAAK,MAAM,EAAE,KAAK,MAClD,gBAAiB,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,EAAE,GAAG,EAAE,CAAE,KAClD,KAAM,EAAE,MAAM,GAAG,EAAE,IAAK,KAAK,EAAI,QAAO;AACzC,UAAI,EAAE;IAEP;AAEA,WAAO;EAER;AAEA,WAAS,YAAa,KAAK,MAAM,MAAM,SAAU;AAEhD,UAAM,IAAI,IAAI,MACb,IAAI,KACJ,IAAI,IAAI;AAET,QAAK,KAAM,GAAG,GAAG,CAAE,KAAK,EAAI,QAAO;AAEnC,UAAM,KAAK,EAAE,GAAG,KAAK,EAAE,GAAG,KAAK,EAAE,GAAG,KAAK,EAAE,GAAG,KAAK,EAAE,GAAG,KAAK,EAAE;AAG/D,UAAM,KAAK,KAAK,KAAO,KAAK,KAAK,KAAK,KAAS,KAAK,KAAK,KAAK,IAC7D,KAAK,KAAK,KAAO,KAAK,KAAK,KAAK,KAAS,KAAK,KAAK,KAAK,IACxD,KAAK,KAAK,KAAO,KAAK,KAAK,KAAK,KAAS,KAAK,KAAK,KAAK,IACxD,KAAK,KAAK,KAAO,KAAK,KAAK,KAAK,KAAS,KAAK,KAAK,KAAK;AAGzD,UAAM,OAAO,OAAQ,IAAI,IAAI,MAAM,MAAM,OAAQ,GAChD,OAAO,OAAQ,IAAI,IAAI,MAAM,MAAM,OAAQ;AAE5C,QAAI,IAAI,IAAI,OACX,IAAI,IAAI;AAGT,WAAQ,KAAK,EAAE,KAAK,QAAQ,KAAK,EAAE,KAAK,MAAO;AAE9C,UAAK,EAAE,KAAK,MAAM,EAAE,KAAK,MAAM,EAAE,KAAK,MAAM,EAAE,KAAK,MAAM,MAAM,KAAK,MAAM,KACzE,gBAAiB,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,EAAE,GAAG,EAAE,CAAE,KAAK,KAAM,EAAE,MAAM,GAAG,EAAE,IAAK,KAAK,EAAI,QAAO;AAChG,UAAI,EAAE;AAEN,UAAK,EAAE,KAAK,MAAM,EAAE,KAAK,MAAM,EAAE,KAAK,MAAM,EAAE,KAAK,MAAM,MAAM,KAAK,MAAM,KACzE,gBAAiB,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,EAAE,GAAG,EAAE,CAAE,KAAK,KAAM,EAAE,MAAM,GAAG,EAAE,IAAK,KAAK,EAAI,QAAO;AAChG,UAAI,EAAE;IAEP;AAGA,WAAQ,KAAK,EAAE,KAAK,MAAO;AAE1B,UAAK,EAAE,KAAK,MAAM,EAAE,KAAK,MAAM,EAAE,KAAK,MAAM,EAAE,KAAK,MAAM,MAAM,KAAK,MAAM,KACzE,gBAAiB,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,EAAE,GAAG,EAAE,CAAE,KAAK,KAAM,EAAE,MAAM,GAAG,EAAE,IAAK,KAAK,EAAI,QAAO;AAChG,UAAI,EAAE;IAEP;AAGA,WAAQ,KAAK,EAAE,KAAK,MAAO;AAE1B,UAAK,EAAE,KAAK,MAAM,EAAE,KAAK,MAAM,EAAE,KAAK,MAAM,EAAE,KAAK,MAAM,MAAM,KAAK,MAAM,KACzE,gBAAiB,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,EAAE,GAAG,EAAE,CAAE,KAAK,KAAM,EAAE,MAAM,GAAG,EAAE,IAAK,KAAK,EAAI,QAAO;AAChG,UAAI,EAAE;IAEP;AAEA,WAAO;EAER;AAGA,WAAS,uBAAwB,OAAO,WAAW,KAAM;AAExD,QAAI,IAAI;AACR,OAAG;AAEF,YAAM,IAAI,EAAE,MACX,IAAI,EAAE,KAAK;AAEZ,UAAK,CAAE,OAAQ,GAAG,CAAE,KAAK,WAAY,GAAG,GAAG,EAAE,MAAM,CAAE,KAAK,cAAe,GAAG,CAAE,KAAK,cAAe,GAAG,CAAE,GAAI;AAE1G,kBAAU,KAAM,EAAE,IAAI,MAAM,CAAE;AAC9B,kBAAU,KAAM,EAAE,IAAI,MAAM,CAAE;AAC9B,kBAAU,KAAM,EAAE,IAAI,MAAM,CAAE;AAG9B,mBAAY,CAAE;AACd,mBAAY,EAAE,IAAK;AAEnB,YAAI,QAAQ;MAEb;AAEA,UAAI,EAAE;IAEP,SAAU,MAAM;AAEhB,WAAO,aAAc,CAAE;EAExB;AAGA,WAAS,YAAa,OAAO,WAAW,KAAK,MAAM,MAAM,SAAU;AAGlE,QAAI,IAAI;AACR,OAAG;AAEF,UAAI,IAAI,EAAE,KAAK;AACf,aAAQ,MAAM,EAAE,MAAO;AAEtB,YAAK,EAAE,MAAM,EAAE,KAAK,gBAAiB,GAAG,CAAE,GAAI;AAG7C,cAAI,IAAI,aAAc,GAAG,CAAE;AAG3B,cAAI,aAAc,GAAG,EAAE,IAAK;AAC5B,cAAI,aAAc,GAAG,EAAE,IAAK;AAG5B,uBAAc,GAAG,WAAW,KAAK,MAAM,MAAM,SAAS,CAAE;AACxD,uBAAc,GAAG,WAAW,KAAK,MAAM,MAAM,SAAS,CAAE;AACxD;QAED;AAEA,YAAI,EAAE;MAEP;AAEA,UAAI,EAAE;IAEP,SAAU,MAAM;EAEjB;AAGA,WAAS,eAAgB,MAAM,aAAa,WAAW,KAAM;AAE5D,UAAM,QAAQ,CAAC;AACf,QAAI,GAAG,KAAK,OAAO,KAAKC;AAExB,SAAM,IAAI,GAAG,MAAM,YAAY,QAAQ,IAAI,KAAK,KAAO;AAEtD,cAAQ,YAAa,CAAE,IAAI;AAC3B,YAAM,IAAI,MAAM,IAAI,YAAa,IAAI,CAAE,IAAI,MAAM,KAAK;AACtDA,cAAO,WAAY,MAAM,OAAO,KAAK,KAAK,KAAM;AAChD,UAAKA,UAASA,MAAK,KAAOA,OAAK,UAAU;AACzC,YAAM,KAAM,YAAaA,KAAK,CAAE;IAEjC;AAEA,UAAM,KAAM,QAAS;AAGrB,SAAM,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAO;AAErC,kBAAY,cAAe,MAAO,CAAE,GAAG,SAAU;IAElD;AAEA,WAAO;EAER;AAEA,WAAS,SAAU,GAAG,GAAI;AAEzB,WAAO,EAAE,IAAI,EAAE;EAEhB;AAGA,WAAS,cAAe,MAAM,WAAY;AAEzC,UAAM,SAAS,eAAgB,MAAM,SAAU;AAC/C,QAAK,CAAE,QAAS;AAEf,aAAO;IAER;AAEA,UAAM,gBAAgB,aAAc,QAAQ,IAAK;AAGjD,iBAAc,eAAe,cAAc,IAAK;AAChD,WAAO,aAAc,QAAQ,OAAO,IAAK;EAE1C;AAGA,WAAS,eAAgB,MAAM,WAAY;AAE1C,QAAI,IAAI,WACP,KAAK,WACL;AAED,UAAM,KAAK,KAAK,GAAG,KAAK,KAAK;AAI7B,OAAG;AAEF,UAAK,MAAM,EAAE,KAAK,MAAM,EAAE,KAAK,KAAK,EAAE,KAAK,MAAM,EAAE,GAAI;AAEtD,cAAM,IAAI,EAAE,KAAM,KAAK,EAAE,MAAQ,EAAE,KAAK,IAAI,EAAE,MAAQ,EAAE,KAAK,IAAI,EAAE;AACnE,YAAK,KAAK,MAAM,IAAI,IAAK;AAExB,eAAK;AACL,cAAI,EAAE,IAAI,EAAE,KAAK,IAAI,IAAI,EAAE;AAC3B,cAAK,MAAM,GAAK,QAAO;QAExB;MAED;AAEA,UAAI,EAAE;IAEP,SAAU,MAAM;AAEhB,QAAK,CAAE,EAAI,QAAO;AAMlB,UAAM,OAAO,GACZ,KAAK,EAAE,GACP,KAAK,EAAE;AACR,QAAI,SAAS,UAAU;AAEvB,QAAI;AAEJ,OAAG;AAEF,UAAK,MAAM,EAAE,KAAK,EAAE,KAAK,MAAM,OAAO,EAAE,KACtC,gBAAiB,KAAK,KAAK,KAAK,IAAI,IAAI,IAAI,IAAI,KAAK,KAAK,KAAK,IAAI,IAAI,EAAE,GAAG,EAAE,CAAE,GAAI;AAErF,cAAM,KAAK,IAAK,KAAK,EAAE,CAAE,KAAM,KAAK,EAAE;AAEtC,YAAK,cAAe,GAAG,IAAK,MAAO,MAAM,UAAY,QAAQ,WAAY,EAAE,IAAI,EAAE,KAAO,EAAE,MAAM,EAAE,KAAK,qBAAsB,GAAG,CAAE,KAAY;AAE7I,cAAI;AACJ,mBAAS;QAEV;MAED;AAEA,UAAI,EAAE;IAEP,SAAU,MAAM;AAEhB,WAAO;EAER;AAGA,WAAS,qBAAsB,GAAG,GAAI;AAErC,WAAO,KAAM,EAAE,MAAM,GAAG,EAAE,IAAK,IAAI,KAAK,KAAM,EAAE,MAAM,GAAG,EAAE,IAAK,IAAI;EAErE;AAGA,WAAS,WAAY,OAAO,MAAM,MAAM,SAAU;AAEjD,QAAI,IAAI;AACR,OAAG;AAEF,UAAK,EAAE,MAAM,EAAI,GAAE,IAAI,OAAQ,EAAE,GAAG,EAAE,GAAG,MAAM,MAAM,OAAQ;AAC7D,QAAE,QAAQ,EAAE;AACZ,QAAE,QAAQ,EAAE;AACZ,UAAI,EAAE;IAEP,SAAU,MAAM;AAEhB,MAAE,MAAM,QAAQ;AAChB,MAAE,QAAQ;AAEV,eAAY,CAAE;EAEf;AAIA,WAAS,WAAYA,OAAO;AAE3B,QAAI,GAAG,GAAG,GAAG,GAAG,MAAM,WAAW,OAAO,OACvC,SAAS;AAEV,OAAG;AAEF,UAAIA;AACJA,cAAO;AACP,aAAO;AACP,kBAAY;AAEZ,aAAQ,GAAI;AAEX;AACA,YAAI;AACJ,gBAAQ;AACR,aAAM,IAAI,GAAG,IAAI,QAAQ,KAAO;AAE/B;AACA,cAAI,EAAE;AACN,cAAK,CAAE,EAAI;QAEZ;AAEA,gBAAQ;AAER,eAAQ,QAAQ,KAAO,QAAQ,KAAK,GAAM;AAEzC,cAAK,UAAU,MAAO,UAAU,KAAK,CAAE,KAAK,EAAE,KAAK,EAAE,IAAM;AAE1D,gBAAI;AACJ,gBAAI,EAAE;AACN;UAED,OAAO;AAEN,gBAAI;AACJ,gBAAI,EAAE;AACN;UAED;AAEA,cAAK,KAAO,MAAK,QAAQ;cACpBA,SAAO;AAEZ,YAAE,QAAQ;AACV,iBAAO;QAER;AAEA,YAAI;MAEL;AAEA,WAAK,QAAQ;AACb,gBAAU;IAEX,SAAU,YAAY;AAEtB,WAAOA;EAER;AAGA,WAAS,OAAQ,GAAG,GAAG,MAAM,MAAM,SAAU;AAG5C,SAAM,IAAI,QAAS,UAAU;AAC7B,SAAM,IAAI,QAAS,UAAU;AAE7B,SAAM,IAAM,KAAK,KAAQ;AACzB,SAAM,IAAM,KAAK,KAAQ;AACzB,SAAM,IAAM,KAAK,KAAQ;AACzB,SAAM,IAAM,KAAK,KAAQ;AAEzB,SAAM,IAAM,KAAK,KAAQ;AACzB,SAAM,IAAM,KAAK,KAAQ;AACzB,SAAM,IAAM,KAAK,KAAQ;AACzB,SAAM,IAAM,KAAK,KAAQ;AAEzB,WAAO,IAAM,KAAK;EAEnB;AAGA,WAAS,YAAa,OAAQ;AAE7B,QAAI,IAAI,OACP,WAAW;AACZ,OAAG;AAEF,UAAK,EAAE,IAAI,SAAS,KAAO,EAAE,MAAM,SAAS,KAAK,EAAE,IAAI,SAAS,EAAM,YAAW;AACjF,UAAI,EAAE;IAEP,SAAU,MAAM;AAEhB,WAAO;EAER;AAGA,WAAS,gBAAiB,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAK;AAE1D,YAAS,KAAK,OAAS,KAAK,QAAU,KAAK,OAAS,KAAK,QAC7C,KAAK,OAAS,KAAK,QAAU,KAAK,OAAS,KAAK,QAChD,KAAK,OAAS,KAAK,QAAU,KAAK,OAAS,KAAK;EAE7D;AAGA,WAAS,gBAAiB,GAAG,GAAI;AAEhC,WAAO,EAAE,KAAK,MAAM,EAAE,KAAK,EAAE,KAAK,MAAM,EAAE,KAAK,CAAE,kBAAmB,GAAG,CAAE;KAC7D,cAAe,GAAG,CAAE,KAAK,cAAe,GAAG,CAAE,KAAK,aAAc,GAAG,CAAE;KACpE,KAAM,EAAE,MAAM,GAAG,EAAE,IAAK,KAAK,KAAM,GAAG,EAAE,MAAM,CAAE;IAClD,OAAQ,GAAG,CAAE,KAAK,KAAM,EAAE,MAAM,GAAG,EAAE,IAAK,IAAI,KAAK,KAAM,EAAE,MAAM,GAAG,EAAE,IAAK,IAAI;EAE3F;AAGA,WAAS,KAAM,GAAG,GAAG,GAAI;AAExB,YAAS,EAAE,IAAI,EAAE,MAAQ,EAAE,IAAI,EAAE,MAAQ,EAAE,IAAI,EAAE,MAAQ,EAAE,IAAI,EAAE;EAElE;AAGA,WAAS,OAAQ,IAAI,IAAK;AAEzB,WAAO,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG;EAErC;AAGA,WAAS,WAAY,IAAI,IAAI,IAAI,IAAK;AAErC,UAAM,KAAK,KAAM,KAAM,IAAI,IAAI,EAAG,CAAE;AACpC,UAAM,KAAK,KAAM,KAAM,IAAI,IAAI,EAAG,CAAE;AACpC,UAAM,KAAK,KAAM,KAAM,IAAI,IAAI,EAAG,CAAE;AACpC,UAAM,KAAK,KAAM,KAAM,IAAI,IAAI,EAAG,CAAE;AAEpC,QAAK,OAAO,MAAM,OAAO,GAAK,QAAO;AAErC,QAAK,OAAO,KAAK,UAAW,IAAI,IAAI,EAAG,EAAI,QAAO;AAClD,QAAK,OAAO,KAAK,UAAW,IAAI,IAAI,EAAG,EAAI,QAAO;AAClD,QAAK,OAAO,KAAK,UAAW,IAAI,IAAI,EAAG,EAAI,QAAO;AAClD,QAAK,OAAO,KAAK,UAAW,IAAI,IAAI,EAAG,EAAI,QAAO;AAElD,WAAO;EAER;AAGA,WAAS,UAAW,GAAG,GAAG,GAAI;AAE7B,WAAO,EAAE,KAAK,KAAK,IAAK,EAAE,GAAG,EAAE,CAAE,KAAK,EAAE,KAAK,KAAK,IAAK,EAAE,GAAG,EAAE,CAAE,KAAK,EAAE,KAAK,KAAK,IAAK,EAAE,GAAG,EAAE,CAAE,KAAK,EAAE,KAAK,KAAK,IAAK,EAAE,GAAG,EAAE,CAAE;EAE/H;AAEA,WAAS,KAAMC,MAAM;AAEpB,WAAOA,OAAM,IAAI,IAAIA,OAAM,IAAI,KAAM;EAEtC;AAGA,WAAS,kBAAmB,GAAG,GAAI;AAElC,QAAI,IAAI;AACR,OAAG;AAEF,UAAK,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,MAAM,EAAE,KACrE,WAAY,GAAG,EAAE,MAAM,GAAG,CAAE,EAAI,QAAO;AACxC,UAAI,EAAE;IAEP,SAAU,MAAM;AAEhB,WAAO;EAER;AAGA,WAAS,cAAe,GAAG,GAAI;AAE9B,WAAO,KAAM,EAAE,MAAM,GAAG,EAAE,IAAK,IAAI,IAClC,KAAM,GAAG,GAAG,EAAE,IAAK,KAAK,KAAK,KAAM,GAAG,EAAE,MAAM,CAAE,KAAK,IACrD,KAAM,GAAG,GAAG,EAAE,IAAK,IAAI,KAAK,KAAM,GAAG,EAAE,MAAM,CAAE,IAAI;EAErD;AAGA,WAAS,aAAc,GAAG,GAAI;AAE7B,QAAI,IAAI,GACP,SAAS;AACV,UAAM,MAAO,EAAE,IAAI,EAAE,KAAM,GAC1B,MAAO,EAAE,IAAI,EAAE,KAAM;AACtB,OAAG;AAEF,UAAS,EAAE,IAAI,OAAW,EAAE,KAAK,IAAI,MAAU,EAAE,KAAK,MAAM,EAAE,KAC3D,MAAO,EAAE,KAAK,IAAI,EAAE,MAAQ,KAAK,EAAE,MAAQ,EAAE,KAAK,IAAI,EAAE,KAAM,EAAE;AAClE,iBAAS,CAAE;AACZ,UAAI,EAAE;IAEP,SAAU,MAAM;AAEhB,WAAO;EAER;AAIA,WAAS,aAAc,GAAG,GAAI;AAE7B,UAAM,KAAK,IAAI,KAAM,EAAE,GAAG,EAAE,GAAG,EAAE,CAAE,GAClC,KAAK,IAAI,KAAM,EAAE,GAAG,EAAE,GAAG,EAAE,CAAE,GAC7B,KAAK,EAAE,MACP,KAAK,EAAE;AAER,MAAE,OAAO;AACT,MAAE,OAAO;AAET,OAAG,OAAO;AACV,OAAG,OAAO;AAEV,OAAG,OAAO;AACV,OAAG,OAAO;AAEV,OAAG,OAAO;AACV,OAAG,OAAO;AAEV,WAAO;EAER;AAGA,WAAS,WAAY,GAAG,GAAG,GAAG,MAAO;AAEpC,UAAM,IAAI,IAAI,KAAM,GAAG,GAAG,CAAE;AAE5B,QAAK,CAAE,MAAO;AAEb,QAAE,OAAO;AACT,QAAE,OAAO;IAEV,OAAO;AAEN,QAAE,OAAO,KAAK;AACd,QAAE,OAAO;AACT,WAAK,KAAK,OAAO;AACjB,WAAK,OAAO;IAEb;AAEA,WAAO;EAER;AAEA,WAAS,WAAY,GAAI;AAExB,MAAE,KAAK,OAAO,EAAE;AAChB,MAAE,KAAK,OAAO,EAAE;AAEhB,QAAK,EAAE,MAAQ,GAAE,MAAM,QAAQ,EAAE;AACjC,QAAK,EAAE,MAAQ,GAAE,MAAM,QAAQ,EAAE;EAElC;AAEA,WAAS,KAAM,GAAG,GAAG,GAAI;AAGxB,SAAK,IAAI;AAGT,SAAK,IAAI;AACT,SAAK,IAAI;AAGT,SAAK,OAAO;AACZ,SAAK,OAAO;AAGZ,SAAK,IAAI;AAGT,SAAK,QAAQ;AACb,SAAK,QAAQ;AAGb,SAAK,UAAU;EAEhB;AAEA,WAAS,WAAY,MAAM,OAAO,KAAK,KAAM;AAE5C,QAAI,MAAM;AACV,aAAU,IAAI,OAAO,IAAI,MAAM,KAAK,IAAI,KAAK,KAAK,KAAM;AAEvD,cAAS,KAAM,CAAE,IAAI,KAAM,CAAE,MAAQ,KAAM,IAAI,CAAE,IAAI,KAAM,IAAI,CAAE;AACjE,UAAI;IAEL;AAEA,WAAO;EAER;ACvwBA,MAAMC,UAAS,CAAC,GAAM,MAAkB,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE;AAGjE,MAAM,YAAY,CAAC,IAAO,IAAO,MAAkB,MAAM,IAAI,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,EAAE,IAAI,EAAE,IAAI,GAAG,IAAI,GAAG,KAAK,IAAI,GAAG,GAAG,IAAI,GAAG,IAAI,GAAG,KAAK,IAAI,GAAG,EAAE;AAG/I,WAAS,WAAW,QAA8B;AAChD,UAAM,MAAW,CAAC;AAClB,QAAI;AACJ,aAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,YAAM,KAAK,EAAE,GAAI,OAAO,IAAI,CAAC,EAAW,CAAC,GAAG,GAAI,OAAO,IAAI,CAAC,EAAW,CAAC,EAAE;AAC1E,YAAM,KAAK,EAAE,GAAI,OAAO,CAAC,EAAW,CAAC,GAAG,GAAI,OAAO,CAAC,EAAW,CAAC,EAAE;AAClE,eAAS,IAAI,GAAG,KAAK,GAAG,KAAK;AAC3B,cAAM,QAAQ,UAAU,IAAI,IAAI,IAAI,CAAC;AACrC,YAAI,QAAQA,QAAO,MAAM,KAAK,EAAG;AACjC,YAAI,KAAK,KAAK;AACd,eAAO;MACT;IACF;AACA,WAAO;EACT;AAEA,WAASC,MAAK,SAA+B;AAC3C,UAAM,IAAI,QAAQ;AAClB,QAAI,IAAI;AACR,aAAS,IAAI,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,IAAK,MAAM,QAAQ,CAAC,EAAQ,IAAK,QAAQ,CAAC,EAAQ,IAAK,QAAQ,CAAC,EAAQ,IAAK,QAAQ,CAAC,EAAQ;AACpI,WAAO,IAAI;EACb;AACA,MAAM,cAAc,CAAC,QAA+BA,MAAK,GAAG,IAAI;AAEhE,WAAS,gBAAgB,QAAmB;AAC1C,UAAM,IAAI,OAAO;AACjB,QAAI,IAAI,KAAKD,QAAO,OAAO,IAAI,CAAC,GAAQ,OAAO,CAAC,CAAM,EAAG,QAAO,IAAI;EACtE;AAEA,WAAS,iBAAiB,SAAc,OAA0B;AAChE,UAAM,WAAqB,CAAC;AAC5B,UAAM,cAAwB,CAAC;AAC/B,UAAM,QAAoB,CAAC;AAC3B,oBAAgB,OAAO;AACvB,eAAW,KAAK,QAAS,UAAS,KAAK,EAAE,GAAG,EAAE,CAAC;AAC/C,QAAI,YAAY,QAAQ;AACxB,UAAM,QAAQ,eAAe;AAC7B,aAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,kBAAY,KAAK,SAAS;AAC1B,mBAAc,MAAM,CAAC,EAAU;AAC/B,iBAAW,KAAK,MAAM,CAAC,EAAU,UAAS,KAAK,EAAE,GAAG,EAAE,CAAC;IACzD;AACA,UAAM,YAAsB,OAAO,YAAY,UAAU,WAAW;AACpE,aAAS,IAAI,GAAG,IAAI,UAAU,QAAQ,KAAK,EAAG,OAAM,KAAK,UAAU,MAAM,GAAG,IAAI,CAAC,CAAC;AAClF,WAAO;EACT;AAGO,WAAS,qBAAqB,WAAyB,SAA2C;AACvG,UAAM,UAAU,IAAI,aAAa,UAAU,MAAM;AACjD,UAAM,MAAM,CAAC,OAAqB,MAAwC,CAAC,MAAM,IAAI,CAAC,GAAa,MAAM,IAAI,IAAI,CAAC,GAAa,MAAM,IAAI,IAAI,CAAC,CAAW;AACzJ,UAAM,OAAO,CAAC,IAAY,IAAY,OAAyC;AAC7E,YAAM,KAAK,IAAI,WAAW,EAAE;AAC5B,YAAM,KAAK,IAAI,WAAW,EAAE;AAC5B,YAAM,KAAK,IAAI,WAAW,EAAE;AAC5B,YAAM,KAAK,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,GAAG,GAAG,CAAC,IAAI,GAAG,CAAC,GAAG,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC;AACvD,YAAM,KAAK,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,GAAG,GAAG,CAAC,IAAI,GAAG,CAAC,GAAG,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC;AAEvD,aAAO,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,IAAI,GAAG,CAAC,IAAI,GAAG,CAAC,GAAG,GAAG,CAAC,IAAI,GAAG,CAAC,IAAI,GAAG,CAAC,IAAI,GAAG,CAAC,GAAG,GAAG,CAAC,IAAI,GAAG,CAAC,IAAI,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC;IACrG;AACA,UAAM,MAAM,CAAC,GAAW,GAAW,GAAW,MAAoB;AAChE,cAAQ,IAAI,CAAC,IAAI;AACjB,cAAQ,IAAI,IAAI,CAAC,IAAI;AACrB,cAAQ,IAAI,IAAI,CAAC,IAAI;IACvB;AACA,QAAI,SAAS;AACX,eAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK,GAAG;AAC1C,cAAM,KAAK,QAAQ,CAAC;AACpB,cAAM,KAAK,QAAQ,IAAI,CAAC;AACxB,cAAM,KAAK,QAAQ,IAAI,CAAC;AACxB,cAAM,KAAK,KAAK,IAAI,IAAI,EAAE;AAC1B,mBAAW,KAAK,CAAC,IAAI,IAAI,EAAE,GAAG;AAC5B,gBAAM,IAAI,IAAI,SAAS,CAAC;AACxB,cAAI,GAAG,EAAE,CAAC,IAAI,GAAG,CAAC,GAAG,EAAE,CAAC,IAAI,GAAG,CAAC,GAAG,EAAE,CAAC,IAAI,GAAG,CAAC,CAAC;QACjD;MACF;IACF,OAAO;AACL,eAAS,IAAI,GAAG,IAAI,UAAU,SAAS,GAAG,KAAK,GAAG;AAChD,cAAM,KAAK,KAAK,GAAG,IAAI,GAAG,IAAI,CAAC;AAC/B,YAAI,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC;AAC1B,YAAI,IAAI,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC;AAC9B,YAAI,IAAI,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC;MAChC;IACF;AACA,aAAS,IAAI,GAAG,IAAI,QAAQ,SAAS,GAAG,KAAK;AAC3C,YAAM,CAAC,GAAG,GAAG,CAAC,IAAI,IAAI,SAAS,CAAC;AAChC,YAAM,IAAI,KAAK,KAAK,KAAK,IAAI,IAAI,IAAI,IAAI,IAAI,CAAC,KAAK;AACnD,UAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IAC5B;AACA,WAAO;EACT;AAEO,WAAS,QAAQ,SAAuB,QAAQ,GAAG,QAAQ,GAAG,QAAuC,QAA6B;AACvI,UAAM,aAAa,EAAE,OAAO,SAAS,OAAO,OAAO,OAAO,SAAS,CAAC,EAAE;AACtE,UAAM,gBAA0B,CAAC;AACjC,UAAM,UAAoB,CAAC;AAC3B,UAAM,SAA0B,CAAC;AACjC,UAAM,cAAwB,CAAC;AAE/B,UAAM,eAAe,UAAU;AAC/B,UAAM,OAAkB,UAAU,SAAS,UAAU,SAAY,CAAC,IAAI;AACtE,QAAI,iBAAiB,KAAK,cAAc,SAAY,KAAK,YAAY;AACrE,QAAI,YAAY,KAAK,SAAS,SAAY,KAAK,OAAO,iBAAiB;AACvE,QAAI,cAAc,KAAK,WAAW,SAAY,KAAK,SAAS;AAC5D,QAAI,gBAAgB,KAAK,aAAa,SAAY,KAAK,WAAW;AAClE,QAAI,CAAC,cAAc;AACjB,sBAAgB;AAChB,uBAAiB;AACjB,kBAAY;AACZ,oBAAc;IAChB;AAEA,QAAI,WAAW,WAAW,QAAQ,KAAK;AACvC,UAAM,SAAS,QAAQ,SAAS,CAAC,GAAG,IAAI,UAAU;AAClD,UAAM,UAAU,CAAC,YAAY,QAAQ;AACrC,QAAI,SAAS;AACX,iBAAW,SAAS,QAAQ;AAC5B,eAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,cAAM,QAAQ,MAAM,CAAC;AACrB,YAAI,YAAY,KAAK,EAAG,OAAM,CAAC,IAAI,MAAM,QAAQ;MACnD;IACF;AACA,UAAM,QAAQ,iBAAiB,UAAU,KAAK;AAC9C,UAAM,UAAU;AAChB,eAAW,SAAS,MAAO,YAAW,SAAS,OAAO,KAAK;AAE3D,UAAM,WAAW,CAAC,IAAO,KAAQ,UAAqB,EAAE,GAAG,GAAG,IAAI,IAAI,IAAI,MAAM,GAAG,GAAG,IAAI,IAAI,IAAI,KAAK;AACvG,UAAM,OAAO,SAAS;AACtB,UAAM,OAAO,MAAM;AAEnB,UAAM,cAAc,CAAC,MAAS,QAAW,WAAiB;AACxD,UAAI;AACJ,UAAI;AACJ,UAAI;AACJ,YAAM,SAAS,KAAK,IAAI,OAAO;AAC/B,YAAM,SAAS,KAAK,IAAI,OAAO;AAC/B,YAAM,SAAS,OAAO,IAAI,KAAK;AAC/B,YAAM,SAAS,OAAO,IAAI,KAAK;AAC/B,YAAM,aAAa,SAAS,SAAS,SAAS;AAC9C,YAAM,aAAa,SAAS,SAAS,SAAS;AAC9C,UAAI,KAAK,IAAI,UAAU,IAAI,OAAO,SAAS;AACzC,cAAM,WAAW,KAAK,KAAK,UAAU;AACrC,cAAM,WAAW,KAAK,KAAK,SAAS,SAAS,SAAS,MAAM;AAC5D,cAAM,eAAe,OAAO,IAAI,SAAS;AACzC,cAAM,eAAe,OAAO,IAAI,SAAS;AACzC,cAAM,eAAe,OAAO,IAAI,SAAS;AACzC,cAAM,eAAe,OAAO,IAAI,SAAS;AACzC,cAAM,OAAO,eAAe,gBAAgB,UAAU,eAAe,gBAAgB,WAAW,SAAS,SAAS,SAAS;AAC3H,kBAAU,eAAe,SAAS,KAAK,KAAK;AAC5C,kBAAU,eAAe,SAAS,KAAK,KAAK;AAC5C,cAAM,cAAc,UAAU,UAAU,UAAU;AAClD,YAAI,eAAe,EAAG,QAAO,EAAE,GAAG,SAAS,GAAG,QAAQ;AACtD,mBAAW,KAAK,KAAK,cAAc,CAAC;MACtC,OAAO;AACL,YAAI,cAAc;AAClB,YAAI,SAAS,OAAO,SAAS;AAC3B,cAAI,SAAS,OAAO,QAAS,eAAc;QAC7C,WAAW,SAAS,CAAC,OAAO,SAAS;AACnC,cAAI,SAAS,CAAC,OAAO,QAAS,eAAc;QAC9C,WAAW,KAAK,KAAK,MAAM,MAAM,KAAK,KAAK,MAAM,GAAG;AAClD,wBAAc;QAChB;AACA,YAAI,aAAa;AACf,oBAAU,CAAC;AACX,oBAAU;AACV,qBAAW,KAAK,KAAK,UAAU;QACjC,OAAO;AACL,oBAAU;AACV,oBAAU;AACV,qBAAW,KAAK,KAAK,aAAa,CAAC;QACrC;MACF;AACA,aAAO,EAAE,GAAG,UAAU,UAAU,GAAG,UAAU,SAAS;IACxD;AAEA,UAAM,YAAY,CAACE,UAA4B;AAC7C,YAAM,MAAW,CAAC;AAClB,eAAS,IAAI,GAAG,KAAKA,MAAK,QAAQ,IAAI,KAAK,GAAG,IAAI,IAAI,GAAG,IAAI,IAAI,KAAK,KAAK,KAAK;AAC9E,YAAI,MAAM,GAAI,KAAI;AAClB,YAAI,MAAM,GAAI,KAAI;AAClB,YAAI,CAAC,IAAI,YAAYA,MAAK,CAAC,GAAQA,MAAK,CAAC,GAAQA,MAAK,CAAC,CAAM;MAC/D;AACA,aAAO;IACT;AACA,UAAM,mBAAmB,UAAU,OAAO;AAC1C,UAAM,iBAAwB,CAAC;AAC/B,QAAI,oBAAoB,iBAAiB,OAAO;AAChD,eAAW,SAAS,OAAO;AACzB,YAAM,mBAAmB,UAAU,KAAK;AACxC,qBAAe,KAAK,gBAAgB;AACpC,0BAAoB,kBAAkB,OAAO,gBAAgB;IAC/D;AAEA,UAAM,IAAI,CAAC,GAAW,GAAW,MAAoB;AACnD,kBAAY,KAAK,CAAC;AAClB,kBAAY,KAAK,CAAC;AAClB,kBAAY,KAAK,CAAC;IACpB;AACA,UAAM,aAAa,CAAC,GAAW,QAAqC;AAClE,YAAM,IAAI,IAAI;AACd,YAAM,IAAI,iBAAiB,KAAK,IAAK,IAAI,KAAK,KAAM,CAAC;AACrD,YAAMC,MAAK,YAAY,KAAK,IAAK,IAAI,KAAK,KAAM,CAAC,IAAI;AACrD,eAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;AACvC,cAAM,OAAO,SAAS,QAAQ,CAAC,GAAQ,iBAAiB,CAAC,GAAQA,GAAE;AACnE,UAAE,KAAK,GAAG,KAAK,GAAG,IAAI,CAAC,CAAC;MAC1B;AACA,eAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,cAAM,QAAQ,MAAM,CAAC;AACrB,cAAM,mBAAmB,eAAe,CAAC;AACzC,iBAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,gBAAM,OAAO,SAAS,MAAM,CAAC,GAAQ,iBAAiB,CAAC,GAAQA,GAAE;AACjE,YAAE,KAAK,GAAG,KAAK,GAAG,IAAI,CAAC,CAAC;QAC1B;MACF;IACF;AACA,aAAS,IAAI,GAAG,IAAI,eAAe,IAAK,YAAW,GAAG,CAAC,MAAM,CAAC,CAAC;AAC/D,UAAM,KAAK,YAAY;AACvB,aAAS,IAAI,GAAG,IAAI,MAAM,KAAK;AAC7B,YAAM,OAAO,eAAe,SAAS,SAAS,CAAC,GAAQ,kBAAkB,CAAC,GAAQ,EAAE,IAAK,SAAS,CAAC;AACnG,QAAE,KAAK,GAAG,KAAK,GAAG,CAAC;IACrB;AACA,aAAS,IAAI,GAAG,KAAK,OAAO,KAAK;AAC/B,eAAS,IAAI,GAAG,IAAI,MAAM,KAAK;AAC7B,cAAM,OAAO,eAAe,SAAS,SAAS,CAAC,GAAQ,kBAAkB,CAAC,GAAQ,EAAE,IAAK,SAAS,CAAC;AACnG,UAAE,KAAK,GAAG,KAAK,GAAI,QAAQ,QAAS,CAAC;MACvC;IACF;AACA,aAAS,IAAI,gBAAgB,GAAG,KAAK,GAAG,IAAK,YAAW,GAAG,CAAC,MAAM,QAAQ,CAAC;AAE3E,UAAM,YAAY,CAAC,UAAwB;AACzC,oBAAc,KAAK,YAAY,QAAQ,IAAI,CAAC,CAAW;AACvD,oBAAc,KAAK,YAAY,QAAQ,IAAI,CAAC,CAAW;AACvD,oBAAc,KAAK,YAAY,QAAQ,IAAI,CAAC,CAAW;IACzD;AACA,UAAM,QAAQ,CAAC,GAAW,MAAoB;AAC5C,cAAQ,KAAK,CAAC;AACd,cAAQ,KAAK,CAAC;IAChB;AACA,UAAM,KAAK,CAAC,OAAe,SAAyB,cAAc,QAAQ,IAAI,IAAI;AAClF,UAAM,KAAK,CAAC,GAAW,GAAW,MAAoB;AACpD,gBAAU,CAAC;AACX,gBAAU,CAAC;AACX,gBAAU,CAAC;AACX,YAAM,YAAY,cAAc,SAAS;AACzC,iBAAW,SAAS,CAAC,YAAY,GAAG,YAAY,GAAG,YAAY,CAAC,EAAG,OAAM,GAAG,OAAO,CAAC,GAAG,GAAG,OAAO,CAAC,CAAC;IACrG;AACA,UAAM,KAAK,CAAC,GAAW,GAAW,GAAW,MAAoB;AAC/D,gBAAU,CAAC;AACX,gBAAU,CAAC;AACX,gBAAU,CAAC;AACX,gBAAU,CAAC;AACX,gBAAU,CAAC;AACX,gBAAU,CAAC;AACX,YAAM,YAAY,cAAc,SAAS;AACzC,YAAM,KAAK,YAAY;AACvB,YAAM,KAAK,YAAY;AACvB,YAAM,KAAK,YAAY;AACvB,YAAM,KAAK,YAAY;AACvB,YAAM,MAAM,KAAK,IAAI,GAAG,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC,IAAI,KAAK,IAAI,GAAG,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;AAC5E,YAAM,KAAK,CAAC,UAAoC,CAAC,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,OAAO,CAAC,GAAG,IAAI,GAAG,OAAO,CAAC,CAAC;AACpG,YAAM,MAAM,CAAC,GAAG,EAAE,GAAG,GAAG,EAAE,GAAG,GAAG,EAAE,GAAG,GAAG,EAAE,CAAC;AAC3C,iBAAW,KAAK,CAAC,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC,EAAG,OAAM,GAAI,IAAI,CAAC,CAAsB;IAC3E;AAGA,QAAI,QAAQ,cAAc,SAAS;AACnC,QAAI,cAAc;AAChB,UAAI,SAAS;AACb,eAAS,IAAI,GAAG,IAAI,MAAM,KAAK;AAC7B,cAAM,OAAO,MAAM,CAAC;AACpB,WAAI,KAAK,CAAC,IAAe,QAAS,KAAK,CAAC,IAAe,QAAS,KAAK,CAAC,IAAe,MAAM;MAC7F;AACA,YAAM,QAAQ,QAAQ,gBAAgB;AACtC,eAAS,OAAO;AAChB,eAAS,IAAI,GAAG,IAAI,MAAM,KAAK;AAC7B,cAAM,OAAO,MAAM,CAAC;AACpB,WAAI,KAAK,CAAC,IAAe,QAAS,KAAK,CAAC,IAAe,QAAS,KAAK,CAAC,IAAe,MAAM;MAC7F;IACF,OAAO;AACL,eAAS,IAAI,GAAG,IAAI,MAAM,KAAK;AAC7B,cAAM,OAAO,MAAM,CAAC;AACpB,WAAG,KAAK,CAAC,GAAa,KAAK,CAAC,GAAa,KAAK,CAAC,CAAW;MAC5D;AACA,eAAS,IAAI,GAAG,IAAI,MAAM,KAAK;AAC7B,cAAM,OAAO,MAAM,CAAC;AACpB,WAAI,KAAK,CAAC,IAAe,OAAO,OAAQ,KAAK,CAAC,IAAe,OAAO,OAAQ,KAAK,CAAC,IAAe,OAAO,KAAK;MAC/G;IACF;AACA,WAAO,KAAK,EAAE,OAAO,OAAO,cAAc,SAAS,IAAI,OAAO,eAAe,EAAE,CAAC;AAGhF,YAAQ,cAAc,SAAS;AAC/B,UAAM,YAAY,CAACD,OAAoBE,iBAA8B;AACnE,UAAI,IAAIF,MAAK;AACb,aAAO,EAAE,KAAK,GAAG;AACf,cAAM,IAAI;AACV,YAAI,IAAI,IAAI;AACZ,YAAI,IAAI,EAAG,KAAIA,MAAK,SAAS;AAC7B,iBAAS,IAAI,GAAG,KAAK,QAAQ,gBAAgB,GAAG,IAAI,IAAI,KAAK;AAC3D,gBAAM,QAAQ,OAAO;AACrB,gBAAM,QAAQ,QAAQ,IAAI;AAC1B,aAAGE,eAAc,IAAI,OAAOA,eAAc,IAAI,OAAOA,eAAc,IAAI,OAAOA,eAAc,IAAI,KAAK;QACvG;MACF;IACF;AACA,QAAI,cAAc;AAClB,cAAU,SAAS,WAAW;AAC9B,mBAAe,QAAQ;AACvB,eAAW,SAAS,OAAO;AACzB,gBAAU,OAAO,WAAW;AAC5B,qBAAe,MAAM;IACvB;AACA,WAAO,KAAK,EAAE,OAAO,OAAO,cAAc,SAAS,IAAI,OAAO,eAAe,EAAE,CAAC;AAEhF,UAAM,YAAY,IAAI,aAAa,aAAa;AAChD,WAAO,EAAE,MAAM,mBAAmB,YAAY,WAAW,SAAS,qBAAqB,SAAS,GAAG,KAAK,IAAI,aAAa,OAAO,GAAG,OAAO;EAC5I;AC7TA,MAAM,iBAA6E;IACjF,KAAK,CAAC,QAAQ,UAAU;IACxB,UAAU,CAAC,aAAa,gBAAgB,UAAU,kBAAkB,kBAAkB,aAAa,cAAc,aAAa;IAC9H,MAAM,CAAC,UAAU,UAAU,kBAAkB,kBAAkB,aAAa,cAAc,aAAa;IACvG,QAAQ,CAAC,UAAU,iBAAiB,kBAAkB,YAAY,aAAa,cAAc,aAAa;IAC1G,OAAO,CAAC,SAAS,UAAU,iBAAiB,gBAAgB;IAC5D,QAAQ,CAAC,UAAU,YAAY,cAAc,aAAa;IAC1D,MAAM,CAAC,eAAe,eAAe,iBAAiB,eAAe,cAAc,aAAa;IAChG,OAAO,CAAC,UAAU,YAAY,YAAY,WAAW;IACrD,OAAO,CAAC,UAAU,QAAQ,kBAAkB,mBAAmB,KAAK;IACpE,SAAS,CAAC,SAAS,SAAS,SAAS,OAAO;IAC5C,YAAY,CAAC,QAAQ,UAAU;IAC/B,MAAM,CAAC,aAAa,WAAW,OAAO,SAAS;EACjD;AAGA,MAAM,eAAoF;IACxF,UAAU,CAAC,gBAAgB;IAC3B,MAAM,CAAC,gBAAgB;IACvB,QAAQ,CAAC,iBAAiB,gBAAgB;IAC1C,QAAQ,CAAC,UAAU;IACnB,MAAM,CAAC,eAAe;IACtB,OAAO,CAAC,UAAU;IAClB,OAAO,CAAC,kBAAkB,iBAAiB;EAC7C;AAEA,MAAM,mBAAqD,EAAE,gBAAgB,IAAI,eAAe,IAAI,gBAAgB,IAAI,UAAU,IAAI,eAAe,IAAI,iBAAiB,GAAG;AAC7K,MAAM,iBAAiB,CAAC,MAAc,QAAyB,SAAS,YAAY,QAAQ,aAAa,KAAK,SAAS,WAAW,QAAQ,mBAAmB,KAAM,iBAAiB,GAAG;AAGhL,WAAS,oBAAoB,MAAqB,UAAyB,CAAC,GAA4B;AAC7G,UAAM,MAA+B,CAAC;AACtC,UAAM,SAAS;AACf,eAAW,OAAO,eAAe,KAAK,IAAI,EAAG,KAAI,OAAO,GAAG,MAAM,OAAW,KAAI,GAAG,IAAI,OAAO,GAAG;AACjG,UAAM,SAAS,QAAQ,UAAU;AACjC,QAAI,WAAW,GAAG;AAChB,YAAM,QAAQ,QAAQ,eAAe;AACrC,iBAAW,OAAO,aAAa,KAAK,IAAI,KAAK,CAAC,GAAG;AAC/C,cAAM,QAAS,IAAI,GAAG,KAA4B,eAAe,KAAK,MAAM,GAAG;AAC/E,YAAI,GAAG,IAAI,KAAK,IAAI,OAAO,KAAK,MAAM,QAAQ,MAAM,CAAC;MACvD;IACF;AACA,WAAO;EACT;AAEA,WAAS,KAAK,GAA+B;AAC3C,UAAM,YAAY,IAAI,aAAa,EAAE,SAAqB;AAC1D,UAAM,UAAU,EAAE,UAAU,WAAW,EAAE,OAAmB,IAAI;AAChE,WAAO;MACL,MAAM;MACN,YAAY,CAAC;MACb;MACA,SAAS,EAAE,UAAU,IAAI,aAAa,EAAE,OAAmB,IAAI,qBAAqB,WAAW,OAAO;MACtG,KAAK,EAAE,MAAM,IAAI,aAAa,EAAE,GAAe,IAAI,IAAI,aAAc,UAAU,SAAS,IAAK,CAAC;MAC9F,GAAI,UAAU,EAAE,QAAQ,IAAI,CAAC;MAC7B,QAAQ,CAAC;IACX;EACF;AAGA,WAAS,KAAK,GAAoC;AAChD,YAAQ,EAAE,MAAM;MACd,KAAK,OAAO;AACV,cAAM,OAAO,EAAE,QAAQ,CAAC,GAAG,GAAG,CAAC;AAC/B,cAAM,WAAW,EAAE,YAAY,CAAC,GAAG,GAAG,CAAC;AACvC,eAAO,IAAI,KAAK,CAAC,GAAG,KAAK,CAAC,GAAG,KAAK,CAAC,GAAG,SAAS,CAAC,GAAG,SAAS,CAAC,GAAG,SAAS,CAAC,CAAC;MAC7E;MACA,KAAK;AACH,eAAO,SAAS,EAAE,WAAW,EAAE,cAAc,EAAE,QAAQ,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW;MACrI,KAAK;AACH,eAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW;MAC9G,KAAK;AACH,eAAO,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,gBAAgB,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW;MACjH,KAAK;AACH,eAAO,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,cAAc;MACnE,KAAK;AACH,eAAO,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,YAAY,EAAE,WAAW;MACjE,KAAK;AACH,eAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,eAAe,EAAE,aAAa,EAAE,YAAY,EAAE,WAAW;MACvG,KAAK;AACH,eAAO,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,UAAU,EAAE,SAAS;MAC5D,KAAK;AACH,eAAO,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,GAAG;MAC3E,KAAK;AACH,eAAO,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK;MACnD,KAAK;AACH,eAAO,WAAW,EAAE,MAAM,EAAE,YAAY,CAAC,CAAC;MAC5C,KAAK;AACH,eAAO,KAAK,CAAC;MACf;AACE,cAAM,IAAI,MAAM,wCAAwC,OAAQ,EAAwB,IAAI,CAAC,GAAG;IACpG;EACF;AAGA,MAAM,YAAY,oBAAI,IAA8B;AACpD,MAAI,gBAAgB;AACpB,MAAM,QAAQ,EAAE,MAAM,GAAG,QAAQ,EAAE;AAkB5B,WAAS,eAAe,MAAqB,UAAyB,CAAC,GAAqB;AACjG,UAAM,aAAa,oBAAoB,MAAM,OAAO;AAEpD,QAAI,KAAK,SAAS,UAAU,kBAAkB,EAAG,QAAO,KAAK,EAAE,GAAG,MAAM,GAAG,WAAW,CAAkB;AACxG,UAAM,MAAM,GAAG,KAAK,IAAI,IAAI,cAAc,UAAU,CAAC;AACrD,UAAM,SAAS,UAAU,IAAI,GAAG;AAChC,QAAI,QAAQ;AACV,YAAM;AACN,gBAAU,OAAO,GAAG;AACpB,gBAAU,IAAI,KAAK,MAAM;AACzB,aAAO;IACT;AACA,UAAM;AACN,UAAM,QAAQ,KAAK,EAAE,GAAG,MAAM,GAAG,WAAW,CAAkB;AAC9D,cAAU,IAAI,KAAK,KAAK;AACxB,QAAI,UAAU,OAAO,cAAe,WAAU,OAAO,UAAU,KAAK,EAAE,KAAK,EAAE,KAAe;AAC5F,WAAO;EACT;ACnJO,WAAS,WAAW,MAA4B;AACrD,QAAI,QAAQ,SAAS;AACrB,WAAO,MAAM;AACX,cAAS,QAAQ,eAAgB;AACjC,UAAI,IAAI;AACR,UAAI,KAAK,KAAK,IAAK,MAAM,IAAK,IAAI,CAAC;AACnC,WAAK,IAAI,KAAK,KAAK,IAAK,MAAM,GAAI,IAAI,EAAE;AACxC,eAAS,IAAK,MAAM,QAAS,KAAK;IACpC;EACF;AAGO,MAAM,YAAY,CAAC,MAAuB,WAAmB,SAClE,WAAW,SAAS,OAAO,GAAG,OAAO,IAAI,CAAC,KAAS,SAAS,KAAS,IAAI,EAAE,EAAE,MAAM,GAAG,CAAC,GAAG,EAAE,CAAC;ACd/F,MAAM,iBAAiB;AAgBhB,MAAM,iBAAiB,CAAC,gBAA8D;IAC3F,SAAS,MAAM,WAAW;AACxB,UAAI,CAAC,eAAe,KAAK,IAAI,EAAG,OAAM,IAAI,MAAM,gCAAgC,IAAI,0EAA0E;AAC9J,iBAAW,IAAI,MAAM,SAAS;IAChC;IACA,YAAY,CAAC,SAAS,WAAW,OAAO,IAAI;IAC5C,KAAK,CAAC,SAAS,WAAW,IAAI,IAAI;IAClC,MAAM,MAAM,CAAC,GAAG,WAAW,KAAK,CAAC,EAAE,KAAK;IACxC,QAAQ,MAAM,OAAO,YAAY,UAAU;EAC7C;AAEA,MAAI;AAGG,MAAM,yBAAyB,MAAwB,sBAAA,oBAAsB,eAAe,oBAAI,IAAI,CAAC;AAOrG,MAAM,eAAe,CAAC,SAA6C,uBAAuB,EAAE,IAAI,IAAI;AE/B3G,MAAM,MAAa,CAAC,MAAO,OAAO,MAAM,YAAY,OAAO,SAAS,CAAC,IAAI,OAAO;AAChF,MAAM,OAAc,CAAC,MAAO,OAAO,MAAM,YAAY,OAAO;AAC5D,MAAM,MAAa,CAAC,MAAO,OAAO,MAAM,WAAW,OAAO;AAC1D,MAAM,MAAM,CAAC,QAAuB,CAAC,MAAO,OAAO,MAAM,YAAY,OAAO,UAAU,CAAC,KAAK,KAAK,MAAM,OAAO,0BAA0B,GAAG;AAC3I,MAAM,QAAQ,CAAC,IAAY,IAAY,SAAS,UAAiB,CAAC,MAChE,OAAO,MAAM,aAAa,SAAS,IAAI,KAAK,KAAK,OAAO,KAAK,KAAK,OAAO,wBAAwB,SAAS,MAAM,GAAG,GAAG,EAAE,KAAK,EAAE;AACjI,MAAM,QAAQ,CAAC,GAAW,OAAc,QAAe,CAAC,MAAM;AAC5D,QAAI,CAAC,MAAM,QAAQ,CAAC,KAAK,EAAE,WAAW,EAAG,QAAO,wBAAwB,CAAC;AACzE,eAAW,KAAK,EAAG,KAAI,KAAK,CAAC,EAAG,QAAO,wBAAwB,CAAC;AAChE,WAAO;EACT;AACA,MAAM,OAAO,CAAC,MAAa,MAAM,MAAa,CAAC,MAAM;AACnD,QAAI,CAAC,MAAM,QAAQ,CAAC,EAAG,QAAO;AAC9B,QAAI,EAAE,SAAS,IAAK,QAAO,qBAAqB,GAAG;AACnD,aAAS,IAAI,GAAG,IAAI,EAAE,QAAQ,KAAK;AACjC,YAAM,IAAI,KAAK,EAAE,CAAC,CAAC;AACnB,UAAI,EAAG,QAAO,IAAI,CAAC,MAAM,CAAC;IAC5B;AACA,WAAO;EACT;AACA,MAAM,QAAe,CAAC,MACnB,OAAO,MAAM,YAAY,OAAO,UAAU,CAAC,KAAK,KAAK,KAAK,KAAK,YAAc,OAAO,MAAM,YAAY,oBAAoB,KAAK,CAAC,IAAK,OAAO;AAC/I,MAAM,QAAQ,CAAC,WAAsC,CAAC,MAAO,OAAO,SAAS,CAAC,IAAI,OAAO,mBAAmB,OAAO,IAAI,CAAC,MAAM,KAAK,UAAU,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC;AAC3J,MAAM,SAAS,CAAC,QAAyC,WAA8B,CAAC,MAAa,CAAC,MAAM;AAC1G,QAAI,CAAC,KAAK,OAAO,MAAM,YAAY,MAAM,QAAQ,CAAC,EAAG,QAAO;AAC5D,UAAM,SAAS;AACf,eAAW,OAAO,SAAU,KAAI,OAAO,GAAG,MAAM,OAAW,QAAO,GAAG,GAAG;AACxE,eAAW,OAAO,OAAO,KAAK,MAAM,GAAG;AACrC,UAAI,OAAO,GAAG,MAAM,OAAW;AAC/B,YAAM,QAAQ,OAAO,GAAG;AACxB,UAAI,CAAC,MAAO,QAAO,GAAG,GAAG;AACzB,YAAM,IAAI,MAAM,OAAO,GAAG,CAAC;AAC3B,UAAI,EAAG,QAAO,GAAG,GAAG,KAAK,CAAC;IAC5B;AACA,WAAO;EACT;AAEA,MAAM,SAAS,CAAC,OAAO,OAAO,OAAO,OAAO,OAAO,KAAK;AACxD,MAAM,YAAmC;IACvC,UAAU,MAAM,CAAC;IACjB,UAAU,MAAM,CAAC;IACjB,eAAe,MAAM,MAAM;IAC3B,OAAO,CAAC,MAAO,OAAO,MAAM,WAAW,IAAI,CAAC,IAAI,MAAM,CAAC,EAAE,CAAC;IAC1D,SAAS;IACT,QAAQ,MAAM,EAAE;EAClB;AACA,MAAM,KAAK,OAAO,EAAE,IAAI,KAAK,IAAI,KAAK,IAAI,KAAK,IAAI,IAAI,GAAG,CAAC,MAAM,MAAM,MAAM,IAAI,CAAC;AAClF,MAAM,WAAW,OAAO,EAAE,GAAG,WAAW,MAAM,CAAC;AAC/C,MAAM,SAAgC;IACpC,GAAG;IACH,MAAM;IACN,MAAM;IACN,MAAM;IACN,UAAU;IACV;IACA,IAAI;IACJ,WAAW,KAAK,QAAQ;IACxB,WAAW,MAAM,GAAG,GAAG,IAAI;IAC3B,MAAM,KAAK,GAAG;EAChB;AACA,MAAM,OAAO,MAAM,CAAC;AACpB,MAAM,QAAe,CAAC,MAAO,MAAM,QAAQ,OAAO,OAAO,EAAE,WAAW,KAAK,MAAM,KAAK,QAAQ,KAAK,UAAU,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;AACxH,MAAM,QAA0F;IAC9F,OAAO,EAAE,QAAQ,CAAC,EAAE;IACpB,KAAK,EAAE,QAAQ,EAAE,MAAM,MAAM,CAAC,GAAG,UAAU,MAAM,GAAG,IAAI,CAAC,CAAC,EAAE,EAAE;IAC9D,UAAU,EAAE,QAAQ,EAAE,WAAW,KAAK,cAAc,KAAK,QAAQ,KAAK,gBAAgB,IAAI,CAAC,GAAG,gBAAgB,IAAI,CAAC,GAAG,WAAW,MAAM,YAAY,KAAK,aAAa,IAAI,EAAE;IAC3K,MAAM,EAAE,QAAQ,EAAE,QAAQ,KAAK,QAAQ,KAAK,gBAAgB,IAAI,CAAC,GAAG,gBAAgB,IAAI,CAAC,GAAG,WAAW,MAAM,YAAY,KAAK,aAAa,IAAI,EAAE;IACjJ,QAAQ,EAAE,QAAQ,EAAE,QAAQ,KAAK,eAAe,IAAI,CAAC,GAAG,gBAAgB,IAAI,CAAC,GAAG,UAAU,KAAK,WAAW,KAAK,YAAY,KAAK,aAAa,IAAI,EAAE;IACnJ,OAAO,EAAE,QAAQ,EAAE,OAAO,KAAK,QAAQ,KAAK,eAAe,IAAI,CAAC,GAAG,gBAAgB,IAAI,CAAC,EAAE,EAAE;IAC5F,QAAQ,EAAE,QAAQ,EAAE,QAAQ,KAAK,UAAU,IAAI,CAAC,GAAG,YAAY,KAAK,aAAa,IAAI,EAAE;IACvF,MAAM,EAAE,QAAQ,EAAE,aAAa,KAAK,aAAa,KAAK,eAAe,IAAI,CAAC,GAAG,aAAa,IAAI,CAAC,GAAG,YAAY,KAAK,aAAa,IAAI,EAAE;IACtI,OAAO,EAAE,QAAQ,EAAE,QAAQ,KAAK,MAAM,CAAC,GAAG,UAAU,IAAI,CAAC,GAAG,UAAU,KAAK,WAAW,IAAI,GAAG,UAAU,CAAC,QAAQ,EAAE;IAClH,OAAO,EAAE,QAAQ,EAAE,QAAQ,KAAK,MAAM,KAAK,gBAAgB,IAAI,CAAC,GAAG,iBAAiB,IAAI,CAAC,GAAG,KAAK,IAAI,EAAE;IACvG,SAAS;MACP,QAAQ,EAAE,OAAO,OAAO,EAAE,OAAO,KAAK,MAAM,CAAC,GAAG,OAAO,KAAK,KAAK,MAAM,CAAC,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC,GAAG,OAAO,KAAK,OAAO,IAAI,CAAC,GAAG,OAAO,MAAM;MAClI,UAAU,CAAC,OAAO;IACpB;IACA,YAAY,EAAE,QAAQ,EAAE,MAAM,MAAM,CAAC,GAAG,UAAU,KAAK,OAAO,EAAE,KAAK,MAAM,KAAK,KAAK,GAAG,CAAC,OAAO,KAAK,CAAC,CAAC,EAAE,GAAG,UAAU,CAAC,QAAQ,UAAU,EAAE;IAC3I,MAAM;MACJ,QAAQ;QACN,WAAW,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC,MAAO,EAAgB,SAAS,MAAM,IAAI,OAAO;QAC9E,SAAS,KAAK,GAAG;QACjB,KAAK,KAAK,GAAG;QACb,SAAS,KAAK,IAAI,CAAC,CAAC;MACtB;MACA,UAAU,CAAC,WAAW;IACxB;IACA,OAAO,EAAE,QAAQ,EAAE,MAAM,MAAM,KAAK,EAAE;IACtC,QAAQ,EAAE,QAAQ,EAAE,WAAW,KAAK,QAAQ,MAAM,KAAK,GAAG,UAAU,CAAC,WAAW,EAAE;EACpF;AAEA,WAAS,aAAa,MAAe,MAAc,QAAwB;AACzE,QAAI,CAAC,QAAQ,OAAO,SAAS,YAAY,MAAM,QAAQ,IAAI,GAAG;AAC5D,aAAO,KAAK,GAAG,IAAI,0BAA0B;AAC7C;IACF;AACA,UAAM,SAAS;AACf,UAAM,OAAO,OAAO,OAAO,SAAS,WAAW,MAAM,OAAO,IAAI,IAAI;AACpE,QAAI,CAAC,MAAM;AACT,aAAO,KAAK,GAAG,IAAI,0BAA0B,OAAO,KAAK,KAAK,EAAE,KAAK,IAAI,CAAC,EAAE;AAC5E;IACF;AACA,eAAW,OAAO,KAAK,YAAY,CAAC,EAAG,KAAI,OAAO,GAAG,MAAM,OAAW,QAAO,KAAK,GAAG,IAAI,IAAI,GAAG,YAAY;AAC5G,eAAW,OAAO,OAAO,KAAK,MAAM,GAAG;AACrC,UAAI,OAAO,GAAG,MAAM,UAAa,QAAQ,WAAY;AACrD,YAAM,QAAQ,KAAK,OAAO,GAAG,KAAK,OAAO,GAAG;AAC5C,UAAI,CAAC,OAAO;AACV,eAAO,KAAK,GAAG,IAAI,IAAI,GAAG,yBAAyB,OAAO,OAAO,IAAI,CAAC,EAAE;AACxE;MACF;AACA,YAAM,IAAI,MAAM,OAAO,GAAG,CAAC;AAC3B,UAAI,EAAG,QAAO,KAAK,GAAG,IAAI,IAAI,GAAG,KAAK,CAAC,EAAE;IAC3C;AACA,UAAM,YAAY,CAAC,UAAU,SAAS,EAAE,OAAO,CAAC,QAAQ,OAAO,GAAG,MAAM,MAAS;AACjF,QAAI,OAAO,WAAW,WAAc,OAAO,aAAa,UAAa,OAAO,aAAa,UAAa,OAAO,UAAU,UAAa,OAAO,YAAY;AACrJ,aAAO,KAAK,GAAG,IAAI,uEAAuE;aACnF,UAAU,SAAS,SAAS,MAAM,OAAO,aAAa,UAAa,OAAO,UAAU,QAAY,QAAO,KAAK,GAAG,IAAI,qDAAqD;AACjL,QAAI,OAAO,aAAa,QAAW;AACjC,UAAI,OAAO,SAAS,QAAS,QAAO,KAAK,GAAG,IAAI,sCAAsC;eAC7E,CAAC,MAAM,QAAQ,OAAO,QAAQ,EAAG,QAAO,KAAK,GAAG,IAAI,8BAA8B;UACtF,QAAO,SAAS,QAAQ,CAAC,OAAO,UAAU,aAAa,OAAO,GAAG,IAAI,aAAa,KAAK,KAAK,MAAM,CAAC;IAC1G;EACF;AAGO,WAAS,cAAc,QAA2B;AACvD,UAAM,SAAmB,CAAC;AAC1B,QAAI,CAAC,UAAU,OAAO,WAAW,YAAY,MAAM,QAAQ,MAAM,EAAG,QAAO,CAAC,yBAAyB;AACrG,UAAM,SAAS;AACf,eAAW,OAAO,OAAO,KAAK,MAAM,EAAG,KAAI,CAAC,CAAC,WAAW,QAAQ,aAAa,QAAQ,OAAO,cAAc,EAAE,SAAS,GAAG,KAAK,OAAO,GAAG,MAAM,OAAW,QAAO,KAAK,GAAG,GAAG,iBAAiB;AAC3L,QAAI,OAAO,YAAY,EAAG,QAAO,KAAK,qBAAqB;AAC3D,QAAI,OAAO,SAAS,UAAa,OAAO,OAAO,SAAS,SAAU,QAAO,KAAK,yBAAyB;AACvG,QAAI,OAAO,cAAc,QAAW;AAClC,UAAI,CAAC,OAAO,aAAa,OAAO,OAAO,cAAc,YAAY,MAAM,QAAQ,OAAO,SAAS,EAAG,QAAO,KAAK,+BAA+B;WACxI;AACH,cAAM,WAAW,OAAO;UACtB;UACA,WAAW,MAAM,GAAG,CAAC;UACrB,WAAW,MAAM,GAAG,CAAC;UACrB,SAAS,MAAM,GAAG,CAAC;UACnB,aAAa;UACb,UAAU;UACV,mBAAmB,CAAC,MAAO,OAAO,MAAM,YAAY,KAAK,IAAI,OAAO;UACpE,MAAM,MAAM,CAAC,SAAS,QAAQ,QAAQ,CAAC;UACvC,aAAa;UACb,cAAc;UACd,YAAY;QACd,CAAC;AACD,mBAAW,CAAC,IAAI,IAAI,KAAK,OAAO,QAAQ,OAAO,SAAS,GAAG;AACzD,gBAAM,IAAI,SAAS,IAAI;AACvB,cAAI,EAAG,QAAO,KAAK,aAAa,EAAE,KAAK,CAAC,EAAE;QAC5C;MACF;IACF;AACA,QAAI,OAAO,QAAQ,QAAW;AAC5B,YAAM,IAAI,KAAK,OAAO,EAAE,UAAU,CAAC,MAAO,OAAO,MAAM,YAAY,KAAK,IAAI,OAAO,0BAA2B,QAAQ,MAAM,GAAG,GAAG,IAAI,GAAG,SAAS,CAAC,MAAO,OAAO,MAAM,YAAY,KAAK,IAAI,OAAO,0BAA2B,aAAa,IAAI,CAAC,EAAE,GAAG,CAAC,YAAY,QAAQ,CAAC,CAAC,EAAE,OAAO,GAAG;AACxR,UAAI,EAAG,QAAO,KAAK,MAAM,EAAE,WAAW,GAAG,IAAI,KAAK,IAAI,GAAG,CAAC,EAAE;IAC9D;AACA,QAAI,OAAO,iBAAiB,UAAa,EAAE,OAAO,OAAO,iBAAiB,YAAY,OAAO,eAAe,GAAI,QAAO,KAAK,qCAAqC;AACjK,QAAI,OAAO,SAAS,OAAW,QAAO,KAAK,gBAAgB;QACtD,cAAa,OAAO,MAAM,QAAQ,MAAM;AAC7C,WAAO;EACT;AAGO,WAAS,iBAAiB,QAAgD;AAC/E,UAAM,SAAS,cAAc,MAAM;AACnC,QAAI,OAAO,OAAQ,OAAM,IAAI,MAAM;IAAiC,OAAO,MAAM,GAAG,EAAE,EAAE,KAAK,MAAM,CAAC,GAAG,OAAO,SAAS,KAAK;QAAW,OAAO,SAAS,EAAE,UAAU,EAAE,EAAE;EACzK;ACtJA,MAAM,kBAAkB,oBAAI,IAAI,CAAC,OAAO,YAAY,QAAQ,UAAU,SAAS,UAAU,QAAQ,SAAS,SAAS,WAAW,cAAc,MAAM,CAAC;AAC5I,MAAM,kBAAkB,CAAC,SAA2C,gBAAgB,IAAI,KAAK,IAAI;AAGjG,WAAS,WAAW,UAA2B,CAAC,GAA4B;AACjF,WAAO;MACL,MAAM,QAAQ,QAAQ;MACtB,QAAQ,QAAQ,UAAU;MAC1B,SAAS,QAAQ,WAAW;MAC5B,WAAW,QAAQ;MACnB,kBAAkB,QAAQ;MAC1B,eAAe,QAAQ;MACvB,YAAY,QAAQ;MACpB,QAAQ,QAAQ;IAClB;EACF;AAGO,MAAM,WAAW,CAAC,QAAqB,UAA2B,CAAC,MAAc,UAAU,EAAE,QAAQ,SAAS,WAAW,OAAO,EAAE,CAAC;AAE1I,MAAM,cAAsB,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,EAAE;AAEzD,MAAM,YAAN,MAAgB;IAGd,YAAY,WAAW,KAAK;AAF5B,oBAAA,MAAA,OAAA;AACA,oBAAA,MAAA,UAAS,CAAA;AAEP,WAAK,QAAQ,IAAI,aAAa,QAAQ;IACxC;IACA,QAAQ,OAAqB;AAC3B,UAAI,KAAK,SAAS,SAAS,KAAK,MAAM,OAAQ;AAC9C,UAAI,WAAW,KAAK,MAAM,SAAS;AACnC,aAAO,WAAW,KAAK,SAAS,MAAO,aAAY;AACnD,YAAM,OAAO,IAAI,aAAa,QAAQ;AACtC,WAAK,IAAI,KAAK,MAAM,SAAS,GAAG,KAAK,MAAM,CAAC;AAC5C,WAAK,QAAQ;IACf;IACA,OAAqB;AACnB,aAAO,KAAK,MAAM,MAAM,GAAG,KAAK,MAAM;IACxC;EACF;AACA,MAAM,YAAN,MAAgB;IAGd,YAAY,WAAW,KAAK;AAF5B,oBAAA,MAAA,OAAA;AACA,oBAAA,MAAA,UAAS,CAAA;AAEP,WAAK,QAAQ,IAAI,YAAY,QAAQ;IACvC;IACA,QAAQ,OAAqB;AAC3B,UAAI,KAAK,SAAS,SAAS,KAAK,MAAM,OAAQ;AAC9C,UAAI,WAAW,KAAK,MAAM,SAAS;AACnC,aAAO,WAAW,KAAK,SAAS,MAAO,aAAY;AACnD,YAAM,OAAO,IAAI,YAAY,QAAQ;AACrC,WAAK,IAAI,KAAK,MAAM,SAAS,GAAG,KAAK,MAAM,CAAC;AAC5C,WAAK,QAAQ;IACf;EACF;AAGA,MAAM,cAAN,MAAkB;IAQhB,YACW,UACA,MACT;AAFS,oBAAA,MAAA,YAAA,QAAA;AACA,oBAAA,MAAA,QAAA,IAAA;AATX,oBAAA,MAAS,aAAY,IAAI,UAAU,CAAA;AACnC,oBAAA,MAAS,WAAU,IAAI,UAAU,CAAA;AACjC,oBAAA,MAAS,OAAM,IAAI,UAAU,GAAG,CAAA;AAChC,oBAAA,MAAS,UAAS,IAAI,UAAU,CAAA;AAChC,oBAAA,MAAS,WAAU,IAAI,UAAU,CAAA;AACjC,oBAAA,MAAS,UAAuB,CAAC,CAAA;AACjC,oBAAA,MAAA,YAAW,CAAA;IAIR;EACL;AAsCA,WAAS,KAAK,KAAkB,GAAqB,OAA0BC,MAA+B,GAAW,SAAwB;AAC/I,UAAM,IAAI;AACV,UAAM,KAAK,aAAa,KAAK;AAC7B,UAAM,SAAS,SAAS,KAAK;AAC7B,UAAM,SAAS,EAAE;AACjB,UAAM,QAAQ,OAAO,SAAS;AAC9B,UAAM,QAAkC,UAAU,OAAO,EAAE,WAAW;AACtE,UAAM,UAAU,QAAQ,MAAM,SAAS;AACvC,QAAI,UAAU,QAAQ,UAAU,CAAC;AACjC,QAAI,QAAQ,QAAQ,UAAU,CAAC;AAC/B,QAAI,IAAI,QAAQ,UAAU,CAAC;AAC3B,QAAIA,KAAK,KAAI,OAAO,QAAQ,UAAU,CAAC;AACvC,UAAM,IAAI,IAAI,UAAU;AACxB,UAAM,IAAI,IAAI,QAAQ;AACtB,UAAM,IAAI,IAAI,IAAI;AAClB,UAAM,IAAI,IAAI,OAAO;AACrB,QAAI,KAAK,IAAI,UAAU;AACvB,QAAI,KAAK,IAAI,QAAQ;AACrB,QAAI,KAAK,IAAI,IAAI;AACjB,QAAI,KAAK,IAAI,OAAO;AACpB,UAAM,KAAK,EAAE,KAAK,EAAE;AACpB,UAAM,KAAK,EAAE,KAAK,EAAE;AACpB,UAAM,KAAK,EAAE;AACb,UAAM,KAAK,EAAE;AACb,aAAS,IAAI,GAAG,IAAI,SAAS,KAAK;AAChC,YAAM,IAAI,QAAS,MAAM,CAAC,IAAe;AACzC,YAAM,IAAI,OAAO,IAAI,CAAC;AACtB,YAAM,IAAI,OAAO,IAAI,IAAI,CAAC;AAC1B,YAAM,IAAI,OAAO,IAAI,IAAI,CAAC;AAC1B,UAAI,QAAQ;AACV,UAAE,IAAI,IAAI,EAAE,CAAC,IAAK,IAAI,EAAE,CAAC,IAAK,IAAI,EAAE,CAAC,IAAK,IAAI,EAAE,EAAE;AAClD,UAAE,IAAI,IAAI,EAAE,CAAC,IAAK,IAAI,EAAE,CAAC,IAAK,IAAI,EAAE,CAAC,IAAK,IAAI,EAAE,EAAE;AAClD,UAAE,IAAI,IAAI,EAAE,CAAC,IAAK,IAAI,EAAE,CAAC,IAAK,IAAI,EAAE,EAAE,IAAK,IAAI,EAAE,EAAE;MACrD,OAAO;AACL,cAAM,IAAI,eAAe,GAAG,GAAG,GAAG,CAAC;AACnC,UAAE,IAAI,IAAI,EAAE,CAAC;AACb,UAAE,IAAI,IAAI,EAAE,CAAC;AACb,UAAE,IAAI,IAAI,EAAE,CAAC;MACf;AACA,YAAM,KAAK,GAAG,IAAI,CAAC;AACnB,YAAM,KAAK,GAAG,IAAI,IAAI,CAAC;AACvB,YAAM,KAAK,GAAG,IAAI,IAAI,CAAC;AACvB,YAAM,KAAK,GAAG,CAAC,IAAK,KAAK,GAAG,CAAC,IAAK,KAAK,GAAG,CAAC,IAAK;AAChD,YAAM,KAAK,GAAG,CAAC,IAAK,KAAK,GAAG,CAAC,IAAK,KAAK,GAAG,CAAC,IAAK;AAChD,YAAM,KAAK,GAAG,CAAC,IAAK,KAAK,GAAG,CAAC,IAAK,KAAK,GAAG,CAAC,IAAK;AAChD,YAAM,IAAI,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,EAAE,KAAK;AACzD,QAAE,IAAI,IAAI,KAAK;AACf,QAAE,IAAI,IAAI,KAAK;AACf,QAAE,IAAI,IAAI,KAAK;AACf,QAAE,IAAI,IAAI,EAAE,KAAM,GAAG,IAAI,CAAC,IAAe;AACzC,QAAE,IAAI,IAAI,EAAE,KAAM,GAAG,IAAI,IAAI,CAAC,IAAe;AAC7C,UAAIA,MAAK;AACP,UAAE,IAAI,IAAIA,KAAI,CAAC;AACf,UAAE,IAAI,IAAIA,KAAI,CAAC;AACf,UAAE,IAAI,IAAIA,KAAI,CAAC;MACjB;IACF;AACA,QAAI,UAAU,SAAS;AACvB,QAAI,QAAQ,SAAS;AACrB,QAAI,IAAI,SAAS;AACjB,QAAI,OAAO,SAAS;AACpB,QAAI,SAAS;AACX,YAAM,OAAO,IAAI;AACjB,YAAM,MAAM,EAAE;AACd,YAAM,IAAI,MAAM,IAAI,SAAS;AAC7B,UAAI,QAAQ,QAAQ,CAAC;AACrB,YAAM,IAAI,IAAI,QAAQ;AACtB,UAAI,KAAK,IAAI,QAAQ;AACrB,eAAS,IAAI,GAAG,IAAI,GAAG,IAAK,GAAE,IAAI,IAAI,QAAQ,MAAO,IAAI,CAAC,IAAe;AACzE,UAAI,QAAQ,SAAS;IACvB;AACA,QAAI,YAAY;EAClB;AAEA,WAAS,SAAS,WAAiC;AACjD,UAAM,MAAgC,CAAC,UAAU,UAAU,QAAQ;AACnE,UAAM,MAAgC,CAAC,WAAW,WAAW,SAAS;AACtE,aAAS,IAAI,GAAG,IAAI,UAAU,QAAQ,KAAK,GAAG;AAC5C,eAAS,OAAO,GAAG,OAAO,GAAG,QAAQ;AACnC,cAAM,IAAI,UAAU,IAAI,IAAI;AAC5B,YAAI,IAAI,IAAI,IAAI,EAAI,KAAI,IAAI,IAAI;AAChC,YAAI,IAAI,IAAI,IAAI,EAAI,KAAI,IAAI,IAAI;MAClC;IACF;AACA,WAAO,EAAE,KAAK,IAAI;EACpB;AAEA,WAAS,OAAO,KAAkB,SAAkB,YAAiC;AACnF,UAAM,YAAY,IAAI,UAAU,KAAK;AACrC,UAAM,UAAU,UAAW,IAAI,WAAW,QAAQ,IAAI,QAAQ,MAAM,MAAM,GAAG,IAAI,QAAQ,MAAM,IAAI,YAAY,KAAK,IAAI,QAAQ,MAAM,SAAS,GAAG,IAAI,QAAQ,MAAM,CAAC,IAAK;AAC1K,WAAO;MACL,UAAU,IAAI;MACd,MAAM,IAAI;MACV;MACA,SAAS,IAAI,QAAQ,KAAK;MAC1B,KAAK,IAAI,IAAI,KAAK;MAClB,GAAI,aAAa,EAAE,QAAQ,IAAI,OAAO,KAAK,EAAE,IAAI,CAAC;MAClD,GAAI,UAAU,EAAE,QAAQ,IAAI,CAAC;MAC7B,QAAQ,IAAI;MACZ,QAAQ,SAAS,SAAS;IAC5B;EACF;AAEA,MAAM,WAAW,CAAC,GAAqB,UAAqC;AAC1E,QAAI,IAAI;AACR,UAAM,IAAI,EAAE;AACZ,aAAS,IAAI,GAAG,IAAI,EAAE,QAAQ,KAAK,EAAG,KAAI,KAAK,IAAI,GAAG,KAAK,MAAM,EAAE,CAAC,GAAa,EAAE,IAAI,CAAC,GAAa,EAAE,IAAI,CAAC,CAAW,CAAC;AACxH,UAAM,KAAK,KAAK,MAAM,MAAM,CAAC,GAAI,MAAM,CAAC,GAAI,MAAM,CAAC,CAAE;AACrD,UAAM,KAAK,KAAK,MAAM,MAAM,CAAC,GAAI,MAAM,CAAC,GAAI,MAAM,CAAC,CAAE;AACrD,UAAM,KAAK,KAAK,MAAM,MAAM,CAAC,GAAI,MAAM,CAAC,GAAI,MAAM,EAAE,CAAE;AACtD,WAAO,IAAI,KAAK,IAAI,IAAI,IAAI,EAAE;EAChC;AAUA,WAAS,aAAa,MAA8C,UAAoB,QAAgB,MAAyB;AAC/H,UAAM,YAAY,SAAS,aAAa,KAAK,SAAS,MAAM,SAAS,qBAAqB,SAAY,aAAa,KAAK,SAAS;AACjI,QAAI,CAAC,UAAW,OAAM,IAAI,MAAM,8BAA8B,KAAK,SAAS,gEAAgE,IAAI,EAAE;AAClJ,UAAM,YAAY,UAAU,KAAK,QAAQ,EAAE,QAAQ,UAAU,SAAS,MAAM,KAAK,WAAW,IAAI,GAAG,MAAM,SAAS,MAAM,QAAQ,KAAK,CAAC;AACtI,UAAM,EAAE,MAAM,OAAO,WAAW,YAAY,QAAQ,SAAS,GAAG,OAAO,IAAI;AAC3E,WAAO,EAAE,GAAG,QAAQ,MAAM,SAAS,UAAU,CAAC,SAAS,EAAE;EAC3D;AAGA,MAAM,UAAU,CAAC,MAAqB,UAAkBC,QAAe,IAAY,UACjF,GAAG,KAAK,IAAI,IAAI,cAAc,oBAAoB,MAAM,KAAK,CAAC,CAAC,IAAI,QAAQ,IAAIA,MAAK,IAAI,GAAG,EAAE,IAAI,GAAG,EAAE,IAAI,GAAG,EAAE,IAAI,GAAG,EAAE;AAE1H,WAAS,SAAS,QAAqB,UAAoB,OAA+B;AACxF,UAAM,YAAY,MAAM,QAAQ;AAChC,UAAM,OAAe,EAAE,MAAM,MAAM,MAAM,oBAAI,IAAI,EAAE;AACnD,UAAM,UAAoB,CAAC,IAAI;AAC/B,UAAM,cAAc,oBAAI,IAAoB;AAC5C,UAAM,QAAqB,CAAC;AAC5B,UAAM,SAAuB,CAAC;AAC9B,UAAM,YAA8B,CAAC;AACrC,QAAI,aAAa;AACjB,UAAM,QAAQ,SAAS,WAAW,SAAS,OAAO,SAAS;AAC3D,UAAM,WAAW,oBAAI,IAA+B;AACpD,UAAM,QAAQ,CAAC,QAA0C;AACvD,UAAI,CAAC,MAAO,QAAO;AACnB,UAAI,IAAI,SAAS,IAAI,GAAG;AACxB,UAAI,CAAC,GAAG;AACN,YAAI,cAAc,KAAK,KAAK;AAC5B,iBAAS,IAAI,KAAK,CAAC;MACrB;AACA,aAAO;IACT;AACA,UAAM,kBAAkB,CAAC,OAAuB;AAC9C,UAAI,MAAM;AACV,UAAI,SAAS,aAAa,CAAC,SAAS,UAAU,IAAI,GAAG,EAAG,OAAM,SAAS,YAAY;AACnF,aAAO,SAAS,QAAQ,GAAG,KAAK;IAClC;AACA,UAAM,QAAQ,CAAC,QAAgB,aAAkC;AAC/D,UAAI,MAAM,OAAO,KAAK,IAAI,QAAQ;AAClC,UAAI,CAAC,KAAK;AACR,cAAM,IAAI,YAAY,UAAU,OAAO,IAAI;AAC3C,eAAO,KAAK,IAAI,UAAU,GAAG;MAC/B;AACA,aAAO;IACT;AAGA,UAAM,aAAa,oBAAI,IAAoB;AAC3C,UAAM,aAAa,oBAAI,IAA+G;AAEtI,UAAM,QAAQ,CAAC,MAAiB,WAA6E,SAAuB;AAClI,UAAI,KAAK,cAAc,UAAa,MAAM,SAAS,KAAK,UAAW;AACnE,UAAI,KAAK,SAAS,SAAU,QAAO,MAAM,aAAa,MAAM,UAAU,MAAM,QAAQ,IAAI,GAAG,WAAW,IAAI;AAC1G,YAAM,OAAO;QACX,UAAU,KAAK,aAAa,SAAY,gBAAgB,KAAK,QAAQ,IAAI,UAAU;QACnF,OAAO,KAAK,UAAU,SAAY,SAAS,KAAK,KAAK,IAAI,UAAU;QACnE,IAAI,KAAK,MAAM,UAAU;QACzB,QAAQ,UAAU,UAAW,KAAK,SAAS,UAAa,CAAC;MAC3D;AACA,UAAI,KAAK,UAAW;AACpB,UAAI,KAAK,SAAS,SAAS;AACzB,aAAK,UAAU,QAAQ,CAAC,OAAO,UAAU,MAAM,OAAO,MAAM,GAAG,IAAI,IAAI,KAAK,EAAE,CAAC;AAC/E;MACF;AACA,UAAI,gBAAgB,IAAI,KAAK,KAAK,SAAS,UAAa,CAAC,KAAK,QAAQ;AACpE,cAAM,MAAM,QAAQ,MAAM,KAAK,UAAU,KAAK,OAAO,KAAK,IAAI,KAAK;AACnE,mBAAW,IAAI,MAAM,WAAW,IAAI,GAAG,KAAK,KAAK,CAAC;MACpD;IACF;AACA,UAAM,eAAe,gBAAgB,SAAS;AAC9C,QAAI,SAAS,YAAY,OAAW,OAAM,OAAO,MAAM,EAAE,UAAU,cAAc,OAAO,UAAU,IAAI,aAAa,QAAQ,MAAM,GAAG,MAAM;AAG1I,UAAM,QAAQ,CAAC,MAAiB,QAAwB,WAAsB,SAAuB;AACnG,UAAI,KAAK,cAAc,UAAa,MAAM,SAAS,KAAK,UAAW;AACnE,UAAI,KAAK,SAAS,SAAU,QAAO,MAAM,aAAa,MAAM,UAAU,MAAM,QAAQ,IAAI,GAAG,QAAQ,WAAW,IAAI;AAClH,UAAI,SAAS,UAAU;AACvB,UAAI,cAAc;AAClB,UAAI,KAAK,SAAS,QAAW;AAC3B,YAAI,WAAW;AAEb,wBAAc;QAChB,OAAO;AACL,cAAI,aAAa,YAAY,IAAI,KAAK,IAAI;AAC1C,cAAI,CAAC,YAAY;AACf,yBAAa,EAAE,MAAM,KAAK,MAAM,MAAM,oBAAI,IAAI,EAAE;AAChD,wBAAY,IAAI,KAAK,MAAM,UAAU;AACrC,oBAAQ,KAAK,UAAU;AACvB,kBAAM,EAAE,UAAU,UAAU,eAAe,OAAO,SAAS,OAAO,IAAI;AACtE,kBAAM,KAAK;cACT,IAAI,KAAK;cACT,GAAI,WAAW,EAAE,SAAS,IAAI,CAAC;cAC/B,GAAI,WAAW,EAAE,SAAS,IAAI,CAAC;cAC/B,GAAI,gBAAgB,EAAE,cAAc,IAAI,CAAC;cACzC,GAAI,UAAU,SAAY,EAAE,MAAM,IAAI,CAAC;cACvC,GAAI,YAAY,SAAY,EAAE,QAAQ,IAAI,CAAC;cAC3C,GAAI,SAAS,EAAE,OAAO,IAAI,EAAE,QAAQ,OAAO,SAAS,IAAI,CAAC,EAAE;YAC7D,CAAC;UACH;AACA,mBAAS;QACX;MACF;AACA,YAAM,SAAS,KAAK,SAAS,UAAa,CAAC;AAC3C,YAAM,QAAQ,SAAS,OAAO,SAAS,IAAI;AAC3C,YAAM,QAAQ,SAAS,OAAO,QAAS,cAAc,SAAS,aAAa,KAAK,IAAI,QAAS;AAC7F,YAAM,OAAkB;QACtB,UAAU,KAAK,aAAa,SAAY,gBAAgB,KAAK,QAAQ,IAAI,UAAU;QACnF,OAAO,KAAK,UAAU,SAAY,SAAS,KAAK,KAAK,IAAI,UAAU;QACnE,IAAI,KAAK,MAAM,UAAU;QACzB;MACF;AAEA,UAAI,KAAK,WAAW;AAClB,sBAAc,MAAM,aAAa,MAAM,IAAI;AAC3C;MACF;AAEA,YAAM,WAAW,KAAK,SAAS,SAAY,IAAI,IAAI,CAAC,GAAG,OAAO,IAAI,EAAE,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,GAAG,EAAE,UAAU,GAAG,EAAE,QAAQ,OAAO,CAAC,CAAC,CAAC,IAAI;AAEpI,UAAI,KAAK,SAAS,SAAS;AACzB,aAAK,UAAU,QAAQ,CAAC,OAAO,UAAU,MAAM,OAAO,OAAO,MAAM,GAAG,IAAI,IAAI,KAAK,EAAE,CAAC;MACxF,WAAW,KAAK,SAAS,SAAS;AAChC,cAAM,IAAI,KAAK,YAAY,CAAC,GAAG,GAAG,CAAC;AACnC,eAAO,KAAK;UACV,GAAI,KAAK,SAAS,SAAY,EAAE,MAAM,KAAK,KAAK,IAAI,CAAC;UACrD,MAAM,OAAO;UACb,UAAU,SAAS,eAAe,QAAQ,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC;UAC/E,GAAI,KAAK,SAAS,SAAY,EAAE,MAAM,KAAK,KAAK,IAAI,CAAC;UACrD,GAAI,KAAK,OAAO,EAAE,MAAM,KAAK,KAAK,IAAI,CAAC;QACzC,CAAC;MACH,WAAW,gBAAgB,IAAI,GAAG;AAChC,cAAM,WAAW,eAAe,MAAM,KAAK;AAC3C,cAAM,IAAI,SAAS,UAAU;AAC7B,YAAI,MAAM,YAAY,UAAa,SAAS,UAAU,CAAC,IAAI,MAAM,QAAS;AAC1E;AACA,YAAI,SAAS,YAAY,UAAa,KAAK,SAAS,UAAa,OAAO,SAAS,MAAM;AACrF,gBAAM,MAAM,QAAQ,MAAM,KAAK,UAAU,KAAK,OAAO,KAAK,IAAI,KAAK;AACnE,eAAK,WAAW,IAAI,GAAG,KAAK,MAAM,SAAS,SAAS;AAClD,gBAAI,QAAQ,WAAW,IAAI,GAAG;AAC9B,gBAAI,CAAC,OAAO;AACV,sBAAQ,EAAE,MAAM,UAAU,KAAK,UAAU,OAAO,KAAK,OAAO,MAAM,MAAM,UAAU,CAAC,EAAE;AACrF,yBAAW,IAAI,KAAK,KAAK;YAC3B;AACA,kBAAM,SAAS,KAAK,GAAG,CAAC;AACxB;UACF;QACF;AACA,aAAK,MAAM,QAAQ,KAAK,QAAQ,GAAG,UAAU,GAAG,MAAM,KAAK,KAAK,GAAG,KAAK,IAAI,SAAS,OAAO;MAC9F;AAEA,UAAI,UAAU;AACZ,mBAAW,CAAC,UAAU,GAAG,KAAK,OAAO,MAAM;AACzC,gBAAM,SAAS,SAAS,IAAI,QAAQ,KAAK,EAAE,GAAG,GAAG,GAAG,EAAE;AACtD,cAAI,IAAI,aAAa,OAAO,EAAG;AAC/B,cAAI,OAAO,KAAK;YACd,MAAM,KAAK;YACX,GAAI,KAAK,OAAO,EAAE,MAAM,KAAK,KAAK,IAAI,CAAC;YACvC,aAAa,OAAO;YACpB,aAAa,IAAI,WAAW,OAAO;YACnC,YAAY,OAAO;YACnB,YAAY,IAAI,QAAQ,SAAS,OAAO;UAC1C,CAAC;QACH;MACF;IACF;AAEA,UAAM,gBAAgB,CAAC,MAAiB,aAA6B,WAAsB,SAAuB;AAChH,YAAM,YAAoB,EAAE,MAAM,UAAU,OAAO,MAAM,MAAM,oBAAI,IAAI,EAAE;AACzE,YAAM,EAAE,WAAW,GAAG,OAAO,IAAI;AACjC,YAAM,SAAS,MAAM;AACrB,YAAM,QAAqB,MAAM,EAAE,GAAG,WAAW,QAAQ,UAAU,GAAG,IAAI;AAC1E,UAAI,MAAM,WAAW,OAAQ,OAAM,IAAI,MAAM,qDAAqD,IAAI,GAAG;AACzG,YAAM,WAAW,IAAI,aAAa,UAAU,SAAS,EAAE;AACvD,YAAM,YAAY,UAAU,KAAK,CAAC,aAAa,SAAS,UAAU,MAAS;AAC3E,YAAM,SAAS,YAAY,IAAI,aAAa,UAAU,SAAS,CAAC,IAAI;AACpE,gBAAU,QAAQ,CAAC,UAAU,UAAU;AACrC,cAAM,QAAQ,SAAS,QAAQ;AAC/B,cAAM,IAAI,QAAS,cAAc,SAAS,aAAa,KAAK,IAAI,QAAS,eAAe,UAAU;AAClG,iBAAS,IAAI,GAAG,QAAQ,EAAE;AAC1B,YAAI,OAAQ,QAAO,IAAI,cAAc,SAAS,SAAS,KAAK,GAAG,SAAS,MAAM,GAAG,QAAQ,CAAC;MAC5F,CAAC;AACD,iBAAW,OAAO,UAAU,KAAK,OAAO,GAAG;AACzC,cAAM,QAAQ,OAAO,KAAK,SAAS,SAAS,KAAK;AACjD,kBAAU,KAAK;UACb,UAAU,IAAI;UACd,MAAM,UAAU;UAChB,GAAI,KAAK,SAAS,SAAY,EAAE,MAAM,KAAK,KAAK,IAAI,CAAC;UACrD,UAAU,EAAE,MAAM,kBAAkB,YAAY,CAAC,GAAG,WAAW,MAAM,WAAW,SAAS,MAAM,SAAS,KAAK,MAAM,KAAK,GAAI,MAAM,UAAU,EAAE,SAAS,MAAM,QAAQ,IAAI,CAAC,GAAI,QAAQ,CAAC,EAAE;UACzL,UAAU,SAAS,MAAM;UACzB,GAAI,SAAS,EAAE,QAAQ,OAAO,MAAM,EAAE,IAAI,CAAC;UAC3C,OAAO,UAAU;QACnB,CAAC;MACH;IACF;AAEA,UAAM,OAAO,MAAM,MAAM,EAAE,UAAU,cAAc,OAAO,UAAU,IAAI,aAAa,QAAQ,KAAK,GAAG,MAAM;AAE3G,eAAW,SAAS,WAAW,OAAO,GAAG;AACvC,YAAM,WAAW,eAAe,MAAM,MAAM,KAAK;AACjD,YAAM,IAAI,MAAM,SAAS,SAAS;AAClC,YAAMD,OAAM,cAAc,MAAM,OAAO,SAAS,MAAM;AACtD,YAAM,SAAS,IAAI,aAAa,IAAI,CAAC;AACrC,eAAS,IAAI,GAAG,IAAI,GAAG,IAAK,QAAO,IAAIA,MAAK,IAAI,CAAC;AACjD,gBAAU,KAAK;QACb,UAAU,MAAM;QAChB,MAAM;QACN,UAAU,EAAE,MAAM,SAAS,MAAM,YAAY,SAAS,YAAY,WAAW,SAAS,UAAU,MAAM,GAAG,SAAS,SAAS,QAAQ,MAAM,GAAG,KAAK,SAAS,IAAI,MAAM,GAAG,GAAI,SAAS,UAAU,EAAE,SAAS,SAAS,QAAQ,MAAM,EAAE,IAAI,CAAC,GAAI,QAAQ,SAAS,OAAO,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE;QAC1R,UAAU,IAAI,aAAa,MAAM,QAAQ;QACzC,GAAI,QAAQ,EAAE,OAAO,IAAI,CAAC;QAC1B,OAAO;MACT,CAAC;IACH;AAEA,UAAM,UAAwB,CAAC;AAC/B,eAAW,UAAU,QAAS,YAAW,OAAO,OAAO,KAAK,OAAO,EAAG,KAAI,IAAI,WAAW,MAAM,CAAC,SAAS,WAAW,IAAI,QAAQ,SAAS,GAAI,SAAQ,KAAK,OAAO,KAAK,SAAS,SAAS,UAAU,IAAI,CAAC;AACvM,WAAO,EAAE,OAAO,EAAE,UAAU,MAAM,UAAU,QAAQ,MAAM,QAAQ,SAAS,UAAU,GAAG,OAAO,QAAQ,WAAW;EACpH;AAMO,WAAS,SAAS,QAAqB,UAA2B,CAAC,GAAiB;AACzF,QAAI,QAAQ,aAAa,MAAO,kBAAiB,MAAM;AACvD,UAAM,WAAqB;MACzB,QAAQ,QAAQ,UAAU;MAC1B,SAAS,QAAQ,WAAW;MAC5B,GAAI,QAAQ,YAAY,EAAE,WAAW,IAAI,IAAI,QAAQ,SAAS,EAAE,IAAI,CAAC;MACrE,GAAI,QAAQ,qBAAqB,SAAY,EAAE,UAAU,QAAQ,iBAAiB,IAAI,CAAC;MACvF,GAAI,QAAQ,gBAAgB,EAAE,OAAO,QAAQ,cAAc,IAAI,CAAC;MAChE,MAAM,QAAQ,QAAQ;MACtB,GAAI,QAAQ,aAAa,EAAE,YAAY,QAAQ,WAAW,IAAI,CAAC;MAC/D,GAAI,QAAQ,qBAAqB,EAAE,oBAAoB,KAAK,IAAI,CAAC;MACjE,GAAI,QAAQ,aAAa,EAAE,SAAS,KAAK,IAAI,GAAG,QAAQ,WAAW,QAAQ,EAAE,IAAI,CAAC;IACpF;AACA,UAAM,QAAqB,CAAC,EAAE,UAAU,GAAG,QAAQ,GAAG,OAAO,EAAE,GAAG,IAAI,OAAO,OAAO,CAAC,GAAG,IAAI,CAAC,MAAM,WAAW,EAAE,GAAG,MAAM,OAAO,QAAQ,EAAE,EAAE,CAAC;AAC7I,UAAM,SAAS,QAAQ,SAAS,MAAM,OAAO,CAAC,SAAS,QAAQ,OAAQ,SAAS,KAAK,KAAK,CAAC,IAAI;AAC/F,QAAI,QAAqB,CAAC;AAC1B,QAAI,SAAuB,CAAC;AAC5B,QAAI,aAAa;AACjB,UAAM,SAAuB,CAAC;AAC9B,eAAW,QAAQ,QAAQ;AACzB,YAAM,SAAS,SAAS,QAAQ,UAAU,IAAI;AAC9C,UAAI,KAAK,UAAU,KAAK,OAAO,WAAW,GAAG;AAC3C,gBAAQ,OAAO;AACf,iBAAS,OAAO;AAChB,qBAAa,OAAO;MACtB;AACA,aAAO,KAAK,OAAO,KAAK;IAC1B;AACA,UAAM,QAAQ,OAAO,CAAC;AACtB,UAAM,WAAW,QAAQ,MAAM,QAAQ,OAAO,CAAC,KAAK,UAAU,MAAM,MAAM,UAAU,SAAS,GAAG,CAAC,IAAI;AACrG,UAAM,YAAY,QACd,MAAM,QAAQ,OAAO,CAAC,KAAK,UAAU,OAAO,MAAM,UAAU,MAAM,QAAQ,SAAS,MAAM,UAAU,SAAS,KAAK,GAAG,CAAC,IACrH,MAAM,UAAU,OAAO,CAAC,KAAK,UAAU,OAAQ,MAAM,SAAS,UAAU,MAAM,SAAS,QAAQ,SAAS,MAAM,SAAS,UAAU,SAAS,KAAK,IAAK,MAAM,OAAO,CAAC,IAClK;AACJ,WAAO;MACL,KAAK,QAAQ,QAAQ,QAAQ,KAAK,QAAQ,OAAO,SAAS,QAAQ,OAAO;MACzE,GAAI,OAAO,SAAS,SAAY,EAAE,MAAM,OAAO,KAAK,IAAI,CAAC;MACzD;MACA;MACA;MACA,GAAI,OAAO,YAAY,EAAE,WAAW,OAAO,UAAU,IAAI,CAAC;MAC1D,GAAI,OAAO,iBAAiB,SAAY,EAAE,cAAc,OAAO,aAAa,IAAI,CAAC;MACjF,OAAO,EAAE,UAAU,WAAW,SAAS,QAAQ,MAAM,QAAQ,SAAS,GAAG,WAAW,QAAQ,MAAM,UAAU,OAAO,CAAC,KAAK,UAAU,MAAM,MAAM,OAAO,CAAC,IAAI,GAAG,WAAW;IAC3K;EACF;;;AO9fO,WAAS,SAAS,GAAS,GAAS,KAAa,IAAI,IAAY;AACtE,UAAM,QAAQ,KAAK,IAAI,GAAG,KAAK,MAAM,CAAC,CAAC;AACvC,UAAM,OAAO,KAAK,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,KAAK,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC;AACrG,UAAM,QAAQ,KAAK,IAAI,GAAG,GAAG;AAC7B,UAAM,MAAc,CAAC;AAErB,QAAI,QAA+B,MAAM;AACzC,QAAI,QAAQ,QAAQ,OAAO,MAAM;AAC/B,UAAI,KAAK;AACT,UAAI,KAAK,OAAO;AAChB,eAAS,IAAI,GAAG,IAAI,IAAI,KAAK;AAC3B,cAAME,MAAK,KAAK,MAAM;AACtB,cAAM,IAAIA,MAAK,KAAK,KAAK,QAAQ,IAAIA,GAAE,IAAI;AAC3C,YAAI,IAAI,MAAO,MAAKA;YACf,MAAKA;MACZ;AACA,YAAM,KAAK,KAAK,MAAM;AACtB,cAAQ,CAAC,MAAM,KAAK,KAAK,MAAO,IAAI,OAAO,OAAQ,CAAC,IAAI,KAAK,KAAK,QAAQ,IAAI,EAAE;IAClF;AACA,aAAS,IAAI,GAAG,IAAI,OAAO,KAAK;AAC9B,YAAM,IAAI,KAAK,QAAQ;AACvB,UAAI,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,GAAG,EAAE,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,IAAI,MAAM,CAAC,GAAG,EAAE,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC;IACpG;AACA,WAAO;EACT;AAGO,WAAS,WAAW,OAAiB,QAAyB,QAAgB,UAA4B,iBAAiB,GAAiB;AACjJ,QAAI,OAAO,SAAS,EAAG,OAAM,IAAI,MAAM,sCAAsC;AAC7E,UAAM,QAAQ,IAAI,MAAM,iBAAiB,OAAO,IAAI,CAAC,MAAM,IAAI,MAAM,QAAQ,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,OAAO,aAAa;AACrH,UAAM,WAAW,KAAK,IAAI,IAAI,OAAO,SAAS,KAAK,CAAC;AACpD,UAAMC,QAAO,IAAI,MAAM,KAAK,IAAI,MAAM,aAAa,OAAO,UAAU,QAAQ,gBAAgB,KAAK,GAAG,QAAQ;AAC5G,IAAAA,MAAK,OAAO;AACZ,IAAAA,MAAK,aAAa;AAClB,WAAOA;EACT;;;AEvCA,MAAM,gBAAgB,KAAK,KAAK;AK6IzB,MAAM,iBAAiB;IAC5B,OAAO;IACP,QAAQ;IACR,QAAQ;IACR,MAAM;IACN,OAAO;IACP,WAAW;IACX,aAAa;IACb,MAAM;IACN,QAAQ;EACV;AKpJA,MAAM,sBAAsB,IAAI;;;ASPhC,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;AgB9BtE,MAAM,QAAQ,MAAM;AAClB,UAAM,IAAI,CAAC,GAAG,GAAG,GAAG,MAAM,KAAK,GAAG,KAAK,KAAK,GAAG,GAAG,GAAG,CAAC;AACtD,UAAM,YAAsB,CAAC;AAC7B,UAAM,UAAoB,CAAC;AAC3B,UAAM,MAAgB,CAAC;AACvB,UAAM,UAAoB,CAAC;AAC3B,eAAW,QAAQ,CAAC,GAAG,EAAE,GAAG;AAC1B,YAAM,OAAO,UAAU,SAAS;AAChC,eAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,kBAAU,KAAK,EAAE,IAAI,CAAC,GAAa,EAAE,IAAI,IAAI,CAAC,GAAa,EAAE,IAAI,IAAI,CAAC,CAAW;AACjF,gBAAQ,KAAK,GAAG,GAAG,IAAI;AACvB,YAAI,KAAM,EAAE,IAAI,CAAC,IAAe,KAAK,EAAE,IAAI,IAAI,CAAC,CAAW;MAC7D;AACA,UAAI,SAAS,EAAG,SAAQ,KAAK,MAAM,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;UAC9E,SAAQ,KAAK,MAAM,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;IAC1E;AACA,WAAO,EAAE,WAAW,SAAS,KAAK,QAAQ;EAC5C,GAAG;AAcI,WAAS,oBAAoB,UAAgC,CAAC,GAAkB;AACrF,UAAM,MAAM,QAAQ,oBAAoB;AACxC,UAAMC,OAAM,QAAQ,eAAe;AACnC,UAAM,CAAC,SAAS,OAAO,IAAI,QAAQ,kBAAkB,CAAC,IAAI,CAAC;AAC3D,UAAM,OAAoB,CAAC;AAC3B,UAAM,QAAwB,CAAC;AAC/B,UAAM,eAAe,oBAAI,IAAyB;AAClD,QAAI,UAAU;AAEd,QAAI,YAA0H;AAE9H,UAAM,OAAO,CAAC,MAAyC;AACrD,YAAM,IAAI,GAAG,KAAK;AAClB,YAAM,IAAI,GAAG,KAAK;AAClB,YAAM,KAAK,GAAG,MAAM;AACpB,YAAM,OAAO;AACb,UAAI,QAAQ,KAAK,SAAS,WAAW,QAAQ,QAAQ,SAAS,CAAC,MAAM,KAAK,SAAS,OAAO,GAAG,KAAK,GAAG,CAAC,KAAK,OAAO,GAAG,KAAK,GAAG,CAAC,KAAK,OAAO,GAAG,KAAK,IAAI,EAAE,EAAG,QAAO,KAAK,MAAM;AAC7K,YAAM,QAAQ,EAAE,MAAM,SAAkB,UAAU,CAAC,GAAG,GAAG,CAAC,GAAW,SAAS,IAAI,UAAU,CAAC,EAAiB;AAC9G,cAAQ,KAAK,KAAK;AAClB,kBAAY,EAAE,MAAM,SAAS,OAAO,GAAG,GAAG,GAAG;AAC7C,aAAO,MAAM;IACf;AAEA,UAAM,MAAwB;MAC5B,IAAI,GAAG,UAAU,GAAG,GAAG,GAAG,GAAG,GAAG,GAAGC,QAAO,IAAI,IAAI,IAAI;AACpD,aAAK,CAAC,EAAE,KAAK,EAAE,MAAM,OAAO,UAAU,OAAAA,QAAO,UAAU,CAAC,GAAG,IAAI,IAAI,GAAG,CAAC,GAAG,UAAU,CAAC,IAAI,IAAI,EAAE,GAAG,eAAe,OAAO,OAAO,CAAC,GAAG,GAAG,CAAC,EAAE,CAAC;MAC5I;MACA,SAAS,GAAG,UAAU,GAAG,GAAG,GAAG,GAAG,GAAGA,QAAO,IAAI,IAAI,OAAuB;AACzE,cAAM,SAAS,OAAO,KAAK,OAAO;AAClC,cAAM,KAAK,UAAU,WAAW,IAAI,IAAI,IAAI,SAAS,IAAI,IAAI,IAAI;AACjE,cAAM,KAAK,UAAU,WAAW,IAAI,IAAI;AACxC,cAAM,QAAQ,EAAE,UAAU,OAAAA,QAAO,UAAU,CAAC,GAAG,IAAI,CAAC,GAAW,UAAU,CAAC,IAAI,GAAG,EAAE,GAAW,eAAe,OAAgB,OAAO,CAAC,GAAG,IAAI,CAAC,EAAU;AACvJ,cAAMC,QAAO,KAAK,CAAC;AACnB,gBAAQ,OAAO;UACb,KAAK;AACH,YAAAA,MAAK,KAAK,EAAE,MAAM,YAAY,WAAW,GAAG,cAAc,GAAG,QAAQ,GAAG,gBAAgBF,MAAK,GAAG,MAAM,CAAC;AACvG;UACF,KAAK;AACH,YAAAE,MAAK,KAAK,EAAE,MAAM,YAAY,WAAW,GAAG,cAAc,GAAG,QAAQ,GAAG,gBAAgB,KAAK,GAAG,MAAM,CAAC;AACvG;UACF,KAAK;AACH,YAAAA,MAAK,KAAK,EAAE,MAAM,UAAU,QAAQ,GAAG,eAAe,SAAS,gBAAgB,SAAS,GAAG,MAAM,CAAC;AAClG;UACF;AACE,YAAAA,MAAK,KAAK,EAAE,MAAM,YAAY,WAAW,GAAG,cAAc,GAAG,QAAQ,GAAG,gBAAgB,KAAK,GAAG,MAAM,CAAC;QAC3G;MACF;MACA,KAAK,GAAG,GAAG,GAAG,GAAG,GAAG,GAAGD,QAAO,IAAI,IAAI;AACpC,aAAK,CAAC,EAAE,KAAK,EAAE,MAAM,QAAQ,GAAG,MAAM,UAAU,SAAS,OAAAA,QAAO,UAAU,CAAC,GAAG,GAAG,CAAC,GAAG,UAAU,CAAC,IAAI,IAAI,CAAC,GAAG,eAAe,OAAO,OAAO,CAAC,GAAG,GAAG,CAAC,EAAE,CAAC;MACtJ;MACA,MAAM,GAAG,UAAU,GAAG,GAAG,GAAG,GAAG,GAAGA,QAAO,IAAI,IAAI,IAAI,IAAS;AAC5D,aAAK,CAAC,EAAE,KAAK;UACX,MAAM;UACN,OAAO;UACP,QAAQ;UACR;UACA,OAAAA;UACA,UAAU,CAAC,GAAG,GAAG,CAAC;UAClB,UAAU,CAAC,IAAI,IAAI,EAAE;UACrB,eAAe;UACf,OAAO,CAAC,GAAG,GAAG,CAAC;UACf,GAAI,KAAK,EAAE,IAAI,EAAE,IAAI,GAAG,IAAI,IAAI,GAAG,IAAI,IAAI,GAAG,IAAI,IAAI,GAAG,GAAG,EAAE,IAAI,CAAC;QACrE,CAAC;MACH;MACA,MAAM,GAAG,GAAG,GAAG,GAAGA,QAAOE,QAAO,MAAM;AACpC,aAAK,CAAC,EAAE,KAAK,EAAE,MAAM,SAAS,UAAU,CAAC,GAAG,GAAG,CAAC,GAAG,MAAM,EAAE,OAAO,MAAM,OAAAF,QAAO,OAAAE,QAAO,KAAK,EAAE,CAAC;MAChG;MACA,KAAK,IAAI,OAAO,IAAI,MAAM;AACxB,YAAI,WAAW,aAAa,IAAI,EAAE;AAClC,YAAI,CAAC,UAAU;AACb,qBAAW,CAAC;AACZ,uBAAa,IAAI,IAAI,QAAQ;AAC7B,gBAAM,KAAK,EAAE,IAAI,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,GAAG,GAAG,CAAC;AAC5D,kBAAQ,KAAK,EAAE,MAAM,SAAS,MAAM,IAAI,UAAU,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,GAAG,UAAU,CAAC,GAAG,IAAI,CAAC,GAAG,SAAS,CAAC;QACpH;AACA,cAAM,QAAQ;AACd,kBAAU;AACV,YAAI;AACF,eAAK;QACP,UAAA;AACE,oBAAU;QACZ;MACF;IACF;AAEA,WAAO;MACL;MACA;MACA,QAAQ,OAAO,EAAE,SAAS,GAAG,GAAI,QAAQ,SAAS,SAAY,EAAE,MAAM,QAAQ,KAAK,IAAI,CAAC,GAAI,MAAM,EAAE,MAAM,SAAS,UAAU,KAAK,EAAE;IACtI;EACF;;;AClJO,MAAM,gBAAwC,CAAC,SAAS,SAAS,QAAQ,SAAS,UAAU,SAAS,SAAS,YAAY,iBAAiB,aAAa;;;AKF/J,MAAM,eAAqB,CAAC,GAAG,GAAG,CAAC;AAM5B,WAAS,OAAO,QAAgB,GAAiB;AACtD,UAAM,KAAK,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,CAAC,CAAC;AACrC,UAAM,OAAO,OAAO,QAAQ;AAC5B,UAAM,OAAa,EAAE,UAAU,CAAC,GAAG,GAAG,CAAC,GAAG,UAAU,EAAE,MAAM,CAAC,KAAK,CAAC,GAAG,KAAK,CAAC,GAAG,KAAK,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,OAAO,CAAC,GAAG,GAAG,CAAC,EAAE;AACtH,YAAQ,OAAO,MAAM;MACnB,KAAK;MACL,KAAK;MACL,KAAK;AACH,aAAK,SAAS,SAAS,OAAO,SAAS,KAAK,KAAK,KAAK;AACtD;MACF,KAAK,SAAS;AACZ,cAAM,KAAK,OAAO,YAAY,OAAO;AACrC,aAAK,WAAW,CAAC,KAAK,CAAC,IAAI,GAAG,KAAK,CAAC,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC;AACtD;MACF;MACA,KAAK,SAAS;AACZ,cAAM,IAAI,MAAM,OAAO,SAAS,OAAO,KAAK;AAC5C,aAAK,QAAQ,CAAC,KAAK,CAAC,MAAM,IAAI,IAAI,GAAG,KAAK,CAAC,MAAM,IAAI,IAAI,GAAG,KAAK,CAAC,MAAM,IAAI,IAAI,CAAC;AACjF;MACF;IACF;AACA,WAAO;EACT;;;AEpBO,WAASC,QAAO,QAAgB,GAAiB;AACtD,UAAM,IAAI,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,OAAO,SAAS,CAAC,IAAI,IAAI,CAAC,CAAC;AAC7D,UAAM,OAAO,OAAa,QAAQ,CAAC;AACnC,UAAM,CAAC,GAAG,GAAG,CAAC,IAAI,KAAK,SAAS;AAChC,UAAM,IAAI,KAAK,MAAM,GAAG,GAAG,CAAC,KAAK;AACjC,UAAM,IAAI,KAAK,IAAI,KAAK,SAAS,QAAQ,CAAC;AAC1C,WAAO;AAAA,MACL,UAAU,CAAC,KAAK,SAAS,CAAC,GAAG,KAAK,SAAS,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC;AAAA,MAC/D,YAAY,CAAE,IAAI,IAAK,GAAI,IAAI,IAAK,GAAI,IAAI,IAAK,GAAG,KAAK,IAAI,KAAK,SAAS,QAAQ,CAAC,CAAC;AAAA,MACrF,OAAO,CAAC,KAAK,MAAM,CAAC,GAAG,KAAK,MAAM,CAAC,GAAG,KAAK,MAAM,CAAC,CAAC;AAAA,IACrD;AAAA,EACF;AASO,WAAS,aAAa,OAAuF;AAClH,QAAI,OAAO,MAAM,SAAS;AAC1B,QAAI,CAAC,MAAM;AACT,YAAM,IAAI,MAAM;AAChB,YAAM,IAAI,MAAM;AAChB,YAAM,IAAI,MAAM;AAChB,aAAO,EAAE,UAAU,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,GAAG,YAAY,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,GAAG,OAAO,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,EAAE;AAC7F,YAAM,SAAS,OAAO;AAAA,IACxB;AACA,WAAO;AAAA,EACT;AAMO,WAAS,UAA8C,OAAiB,OAAU,QAAgB,GAAc;AACrH,UAAM,OAAO,aAAa,KAAK;AAC/B,UAAM,OAAOA,QAAO,QAAQ,CAAC;AAC7B,UAAM,QAAQ,IAAI,MAAM,WAAW,KAAK,WAAW,CAAC,GAAG,KAAK,WAAW,CAAC,GAAG,KAAK,WAAW,CAAC,GAAG,KAAK,WAAW,CAAC,CAAC;AACjH,UAAM,QAAQ,IAAI,MAAM,WAAW,KAAK,WAAW,CAAC,GAAG,KAAK,WAAW,CAAC,GAAG,KAAK,WAAW,CAAC,GAAG,KAAK,WAAW,CAAC,CAAC;AACjH,UAAM,WAAW,KAAK,KAAK,EAAE,SAAS,KAAK;AAC3C,UAAM,SAAS,IAAI,MAAM,QAAQ,KAAK,SAAS,CAAC,GAAG,KAAK,SAAS,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,EAAE,gBAAgB,KAAK;AAC5G,UAAM,SAAS,IAAI,KAAK,SAAS,CAAC,IAAI,OAAO,GAAG,KAAK,SAAS,CAAC,IAAI,OAAO,GAAG,KAAK,SAAS,CAAC,IAAI,OAAO,CAAC;AACxG,UAAM,MAAM,IAAI,KAAK,MAAM,CAAC,IAAI,KAAK,MAAM,CAAC,GAAG,KAAK,MAAM,CAAC,IAAI,KAAK,MAAM,CAAC,GAAG,KAAK,MAAM,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC;AAC3G,UAAM,aAAa;AACnB,WAAO;AAAA,EACT;AAaO,WAAS,WAAW,OAAiB,OAAc,UAAkB,OAAiD,UAAuB,CAAC,GAAS;AAC5J,UAAM,MAAM,MAAM,IAAI,QAAQ;AAC9B,QAAI,CAAC,IAAK;AACV,UAAM,WAAW,oBAAI,IAAoB;AACzC,eAAW,KAAK,IAAI,KAAK,SAAS,CAAC,GAAG;AACpC,eAAS,IAAI,EAAE,IAAI,EAAE,MAAM;AAC3B,YAAM,IAAI,MAAM,EAAE,EAAE;AACpB,UAAI,CAAC,EAAG;AACR,YAAM,SAAS,EAAE,SAAS;AAC1B,UAAI,QAAQ;AAEV,eAAO,QAAQ,EAAE,QAAQ,MAAM,MAAM,UAAU,EAAE,EAAE,CAAC;AACpD;AAAA,MACF;AACA,gBAAU,OAAO,GAAG,EAAE,QAAQ,MAAM,MAAM,UAAU,EAAE,EAAE,CAAC;AAAA,IAC3D;AACA,eAAW,QAAQ,IAAI,KAAK,QAAQ,CAAC,GAAG;AACtC,YAAM,MAAM,KAAK,QAAQ,KAAK;AAC9B,YAAM,IAAI,MAAM,GAAG,KAAK,MAAM,KAAK,EAAE;AACrC,UAAI,CAAC,EAAG;AACR,YAAM,EAAE,IAAI,GAAG,IAAI,MAAM,KAAK,UAAU,KAAK,EAAE;AAC/C,YAAM,QAAQ,EAAE,SAAS;AACzB,UAAI,OAAO,SAAS,QAAQ;AAC1B,cAAM,IAAI,MAAM,QAAQ,KAAK,EAAE;AAC/B,YAAI,MAAM,OAAW,OAAM,QAAQ,GAAG,IAAI,EAAE;AAC5C,YAAI,QAAQ,SAAS,OAAW,OAAM,UAAU,QAAQ,IAAI;AAC5D;AAAA,MACF;AACA,UAAI,SAAS;AACb,QAAE,SAAS,CAAC,MAAM;AAChB,cAAM,OAAO,EAAE,SAAS;AACxB,YAAI,MAAM;AACR,eAAK,IAAI,IAAI,IAAI,QAAQ,IAAI;AAC7B,mBAAS;AAAA,QACX;AAAA,MACF,CAAC;AACD,UAAI,OAAQ;AACZ,mBAAa,CAAC;AAEd,UAAI,CAAC,SAAS,IAAI,GAAG,EAAG,WAAU,OAAO,GAAG,EAAE,MAAM,SAAS,OAAO,EAAE,GAAG,CAAC;AAC1E,YAAM,MAAM,IAAI,MAAM,WAAW,EAAE,aAAa,IAAI,MAAM,MAAM,IAAI,GAAG,IAAI,KAAK,CAAC;AACjF,YAAM,SAAS,IAAI,MAAM,QAAQ,KAAK,OAAO,CAAC,GAAG,KAAK,OAAO,CAAC,GAAG,KAAK,OAAO,CAAC,CAAC;AAC/E,QAAE,SAAS,IAAI,MAAM,EAAE,gBAAgB,GAAG,EAAE,IAAI,MAAM;AACtD,QAAE,WAAW,YAAY,GAAG;AAC5B,QAAE,aAAa;AAAA,IACjB;AAAA,EACF;;;AC/FA,MAAM;AAAA;AAAA,IAAkB;AAAA;AAAA;AAAA;AAIxB,WAAS,aAAyC,UAAa,KAAa,aAAqG,SAA+B;AAC9M,UAAM,WAAW,SAAS;AAC1B,aAAS,kBAAkB,CAAC,QAAQ,aAAa;AAC/C,gBAAU,KAAK,UAAU,QAAQ,QAAQ;AACzC,kBAAY,MAAM;AAAA,IACpB;AACA,UAAM,YAAY,SAAS;AAC3B,aAAS,wBAAwB,aAAa,UAAU,MAAM,GAAG,UAAU,KAAK,OAAO,CAAC,IAAI,GAAG,KAAK,MAAM;AAC1G,WAAO;AAAA,EACT;AAMA,WAAS,kBAAkB,MAA0C;AACnE,UAAM,QAAQ,KAAK,MAAM;AACzB,QAAI,KAAK,QAAS,OAAM,UAAU,EAAE,GAAI,MAAM,WAAW,CAAC,GAAI,GAAG,KAAK,QAAQ;AAC9E,QAAI,KAAK,oBAAoB,OAAO,eAAe,IAAI,EAAE,gBAAiB,OAAM,kBAAkB,KAAK;AACvG,UAAM,WAAW,EAAE,GAAG,KAAK,UAAU,YAAY,KAAK,KAAK;AAC3D,WAAO;AAAA,EACT;AAGA,WAAS,WAAW,OAAiB,MAAwB,KAAa,aAAqG;AAC7K,WAAO;AAAA,MACL,UAAU,aAAa,kBAAkB,IAAI,GAAG,KAAK,aAAa,IAAI;AAAA,MACtE,OAAO,aAAa,IAAI,MAAM,kBAAkB,EAAE,cAAc,MAAM,iBAAiB,CAAC,GAAG,KAAK,WAAW;AAAA,MAC3G,UAAU,aAAa,IAAI,MAAM,qBAAqB,GAAG,KAAK,WAAW;AAAA,IAC3E;AAAA,EACF;AAUA,WAAS,OAAO,OAAiB,QAAiB,YAA4D;AAC5G,QAAI,QAAQ;AACZ,QAAI,MAAM;AACV,eAAW,KAAK,QAAQ;AACtB,eAAS,EAAE,SAAS,aAAa,UAAU,EAAE;AAC7C,aAAO,EAAE,SAAS,MAAO;AAAA,IAC3B;AACA,UAAM,MAAM,IAAI,aAAa,QAAQ,CAAC;AACtC,UAAM,MAAM,IAAI,aAAa,QAAQ,CAAC;AACtC,UAAM,MAAM,IAAI,aAAa,QAAQ,CAAC;AACtC,UAAM,KAAK,IAAI,aAAa,QAAQ,CAAC;AACrC,UAAM,QAAQ,IAAI,YAAY,GAAG;AACjC,UAAM,QAAsC,CAAC;AAC7C,eAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,UAAU,EAAG,OAAM,CAAC,IAAI,IAAI,aAAa,QAAQ,CAAC;AACtF,UAAM,IAAI,IAAI,MAAM,QAAQ;AAC5B,UAAM,KAAK,IAAI,MAAM,QAAQ;AAC7B,QAAI,KAAK;AACT,QAAI,KAAK;AACT,eAAW,KAAK,QAAQ;AACtB,YAAM,IAAI,EAAE;AACZ,YAAM,QAAQ,EAAE,aAAa,UAAU,EAAE;AACzC,SAAG,gBAAgB,EAAE,MAAM;AAC3B,YAAM,KAAK,EAAE,aAAa,UAAU;AACpC,YAAM,KAAK,EAAE,aAAa,QAAQ;AAClC,YAAM,KAAK,EAAE,aAAa,OAAO;AACjC,YAAM,KAAK,EAAE,aAAa,IAAI;AAC9B,eAAS,IAAI,GAAG,IAAI,OAAO,KAAK;AAC9B,UAAE,oBAAoB,IAAI,CAAC,EAAE,aAAa,EAAE,MAAM;AAClD,YAAI,IAAI,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,IAAI,KAAK,KAAK,CAAC;AACrC,UAAE,oBAAoB,IAAI,CAAC,EAAE,aAAa,EAAE,EAAE,UAAU;AACxD,YAAI,IAAI,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,IAAI,KAAK,KAAK,CAAC;AACrC,YAAI,IAAI,CAAC,GAAG,KAAK,CAAC,GAAG,GAAG,KAAK,CAAC,GAAG,GAAG,KAAK,CAAC,CAAC,IAAI,KAAK,KAAK,CAAC;AAC1D,WAAG,IAAI,CAAC,GAAG,KAAK,CAAC,GAAG,GAAG,KAAK,CAAC,CAAC,IAAI,KAAK,KAAK,CAAC;AAAA,MAC/C;AACA,iBAAW,CAAC,GAAG,MAAM,KAAK,OAAO,QAAQ,EAAE,KAAK,EAAG,OAAM,CAAC,EAAE,IAAI,QAAQ,KAAK,WAAW,CAAC,CAAC;AAC1F,YAAM,KAAK,EAAE;AACb,eAAS,IAAI,GAAG,IAAI,GAAG,OAAO,IAAK,OAAM,KAAK,CAAC,IAAI,GAAG,KAAK,CAAC,IAAI;AAChE,YAAM;AACN,YAAM,GAAG;AAAA,IACX;AACA,UAAM,MAAM,IAAI,MAAM,eAAe;AACrC,QAAI,aAAa,YAAY,IAAI,MAAM,gBAAgB,KAAK,CAAC,CAAC;AAC9D,QAAI,aAAa,UAAU,IAAI,MAAM,gBAAgB,KAAK,CAAC,CAAC;AAC5D,QAAI,aAAa,SAAS,IAAI,MAAM,gBAAgB,KAAK,CAAC,CAAC;AAC3D,QAAI,aAAa,MAAM,IAAI,MAAM,gBAAgB,IAAI,CAAC,CAAC;AACvD,eAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,UAAU,EAAG,KAAI,aAAa,GAAG,IAAI,MAAM,gBAAgB,MAAM,CAAC,GAAG,CAAC,CAAC;AAC3G,QAAI,SAAS,IAAI,MAAM,gBAAgB,QAAQ,QAAQ,QAAQ,IAAI,YAAY,KAAK,GAAG,CAAC,CAAC;AACzF,QAAI,sBAAsB;AAC1B,QAAI,mBAAmB;AACvB,WAAO;AAAA,EACT;AAEA,WAAS,SAAS,OAAyC;AACzD,WAAO,MAAM,SAAS,OAAO,CAAC,MAA0B,EAAmB,MAAM;AAAA,EACnF;AAaO,WAAS,WAAW,OAAiB,MAAqB,OAAsC,MAAkB,UAAwB,CAAC,GAAS;AACzJ,UAAM,YAAY,QAAQ,iBAAiB;AAC3C,UAAM,OAAO,oBAAI,IAAY;AAC7B,eAAW,KAAK,KAAK,WAAW,CAAC,EAAG,KAAI,EAAE,KAAM,MAAK,IAAI,EAAE,IAAI;AAC/D,eAAW,CAAC,IAAI,CAAC,KAAK,OAAO,QAAQ,KAAK,EAAG,KAAI,KAAK,IAAI,EAAE,SAAS,CAAC,IAAI,QAAQ,EAAE,SAAS,OAAQ,MAAK,IAAI,EAAE;AAEhH,UAAM,OAAO,CAAC,QAAmC,IAAI,IAAI,CAAC,OAAO,MAAM,EAAE,CAAC;AAC1E,UAAM,SAAS,CAAC,KAAe,WAAgC;AAC7D,iBAAW,MAAM,KAAK;AACpB,cAAM,IAAI,MAAM,EAAE;AAClB,UAAE,iBAAiB;AACnB,mBAAW,KAAK,SAAS,CAAC,EAAG,GAAE,SAAS,QAAQ;AAChD,cAAM,EAAE,IAAI;AAAA,MACd;AACA,WAAK,IAAI,MAAM;AAAA,IACjB;AACA,UAAM,aAAa,CAAC,QAAyB,UAAyE;AACpH,YAAM,MAAM,oBAAI,IAA6E;AAC7F,iBAAW,KAAK,QAAQ;AACtB,UAAE,aAAa;AACf,mBAAWC,SAAQ,SAAS,CAAC,GAAG;AAC9B,gBAAM,MAAM,GAAG,OAAOA,MAAK,SAAS,QAAQ,CAAC,IAAKA,MAAK,SAA8B,IAAI;AACzF,cAAI,IAAI,IAAI,IAAI,GAAG;AACnB,cAAI,CAAC,GAAG;AACN,gBAAI,EAAE,MAAMA,MAAK,UAA8B,QAAQ,CAAC,GAAG,MAAAA,MAAK;AAChE,gBAAI,IAAI,KAAK,CAAC;AAAA,UAChB;AACA,YAAE,OAAO,KAAK,EAAE,UAAUA,MAAK,UAAU,QAAQ,EAAE,QAAQ,OAAO,MAAM,GAAGA,MAAK,SAAS,aAAa,UAAU,EAAE,KAAK,EAAE,CAAC;AAAA,QAC5H;AAAA,MACF;AACA,aAAO;AAAA,IACT;AACA,UAAM,aAAa,CAAC,MAAc,KAAa,UAAkC,UAAwB,KAAoC,UAA+B;AAC1K,YAAMA,QAAO,IAAI,MAAM,KAAK,UAAU,IAAI,QAAQ;AAClD,MAAAA,MAAK,OAAO,GAAG,IAAI,IAAI,GAAG;AAC1B,MAAAA,MAAK,SAAS,WAAW;AACzB,MAAAA,MAAK,aAAa,SAAS;AAC3B,MAAAA,MAAK,gBAAgB,SAAS;AAC9B,MAAAA,MAAK,sBAAsB,IAAI;AAC/B,MAAAA,MAAK,yBAAyB,IAAI;AAClC,YAAM,IAAIA,KAAI;AAAA,IAChB;AAGA,UAAM,UAAU,KAAK,QAAQ,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,SAAS,WAAW,MAAM,EAAE,QAAQ,EAAE,EAAE,KAAK,CAAC,KAAK,IAAI,EAAE,QAAQ,EAAE,EAAE,CAAC;AACvH,UAAM,YAAY,CAAC,MAAqC,EAAE,QAAQ,EAAE;AACpE,UAAM,UAAU,oBAAI,IAAY;AAChC,eAAW,KAAK,KAAK,gBAAgB,CAAC,GAAG;AACvC,YAAM,IAAI,EAAE;AACZ,UAAI,EAAE,OAAO,iBAAiB,EAAE,KAAM,SAAQ,IAAI,EAAE,IAAI;AAAA,IAC1D;AACA,UAAM,QAAQ,OAAO,OAAO,CAAC,MAAM,CAAC,QAAQ,IAAI,UAAU,CAAC,CAAC,CAAC;AAC7D,QAAI,MAAM,UAAU,GAAG;AACrB,YAAM,MAAM,MAAM,IAAI,SAAS;AAC/B,YAAM,QAAgC,CAAC;AACvC,YAAM,QAAQ,CAAC,GAAG,MAAO,MAAM,EAAE,EAAE,IAAI,CAAE;AACzC,YAAM,cAAc,IAAI,IAAI,MAAM,IAAI,CAAC,GAAG,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;AAClE,YAAM,eAAe,IAAI,IAAI,MAAM,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;AAC1E,YAAM,IAAI,MAAM;AAChB,YAAM,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,KAAK,EAAE,OAAO,MAAM,KAAK,EAAE,QAAQ,EAAE,GAAG,MAAM,IAAI,MAAM,QAAQ,CAAC,EAAE,EAAE;AAC5G,YAAM,SAAS,KAAK,GAAG;AACvB,YAAM,WAAW,IAAI,IAAI,OAAO,IAAI,CAAC,GAAG,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;AAC1D,YAAM,OAAO,WAAW,QAAQ,CAAC,GAAG,UAAU;AAC5C,cAAM,KAAK,SAAS,IAAI,CAAC;AACzB,eAAO,EAAE,QAAQ,IAAI,MAAM,KAAK,EAAE,KAAK,QAAQ,aAAa,EAAE,CAAC,GAAG,YAAY,IAAI,MAAM,KAAK,EAAE,KAAK,aAAa,IAAI,EAAE,KAAK,CAAC,EAAE;AAAA,MACjI,CAAC;AACD,YAAM,SAAS,IAAI,MAAM,MAAM;AAC/B,aAAO,OAAO;AACd,YAAMC,SAAQ,CAAC,WAAuF;AACpG,eAAO,SAAS,WAAW,SAAS;AACpC,eAAO,SAAS,UAAU,SAAS;AACnC,eAAO,eACL;AAAA;AAAA;AAAA,uBAAuG,CAAC;AAAA,IACxG,OAAO,aAAa;AAAA,UAClB;AAAA,UACA;AAAA;AAAA;AAAA;AAAA,wBAIc,UAAU,QAAQ,CAAC,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA,QAKpC;AAAA,MACJ;AACA,iBAAW,KAAK,KAAK,OAAO,GAAG;AAC7B,cAAM,MAAM,OAAO,EAAE,KAAK,SAAS,QAAQ;AAC3C,cAAM,MAAM,WAAW,OAAO,EAAE,MAAM,aAAa,CAAC,IAAI,SAAS,IAAIA,MAAK;AAC1E,mBAAW,eAAe,KAAK,OAAO,OAAO,EAAE,QAAQ,EAAE,QAAQ,GAAG,YAAY,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM;AAAA,MAC3G;AACA,YAAM,QAAmB;AAAA,QACvB,MAAM;AAAA,QACN;AAAA,QACA,MAAM,GAAG,IAAI,IAAI;AACf,mBAAS,IAAI,MAAM,CAAC,EAAE,IAAI,IAAI,EAAE;AAAA,QAClC;AAAA,QACA,QAAQ,GAAG;AACT,mBAAS,KAAK,QAAQ;AAAA,QACxB;AAAA,MACF;AACA,aAAO,SAAS,SAAS;AACzB,aAAO,KAAK,MAAM;AAClB,iBAAW,MAAM,IAAK,MAAK,IAAI,EAAE;AAAA,IACnC;AAGA,UAAM,cAAc,oBAAI,IAAsB;AAC9C,eAAW,KAAK,KAAK,SAAS,CAAC,GAAG;AAChC,UAAI,CAAC,EAAE,SAAS,CAAC,MAAM,EAAE,EAAE,KAAK,KAAK,IAAI,EAAE,EAAE,EAAG;AAChD,YAAM,MAAM,GAAG,EAAE,KAAK,IAAI,KAAK,UAAU,EAAE,MAAM,CAAC;AAClD,YAAMC,QAAO,YAAY,IAAI,GAAG;AAChC,UAAIA,MAAM,CAAAA,MAAK,KAAK,EAAE,EAAE;AAAA,UACnB,aAAY,IAAI,KAAK,CAAC,EAAE,EAAE,CAAC;AAAA,IAClC;AACA,eAAW,CAAC,KAAK,GAAG,KAAK,aAAa;AACpC,UAAI,IAAI,SAAS,EAAG;AACpB,YAAM,UAAU,KAAK,SAAS,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,OAAO,IAAI,CAAC,CAAC,EAAG;AAChE,YAAM,WAAW,EAAE,GAAG,EAAE,OAAO,IAAI,MAAM,QAAQ,GAAG,GAAG,GAAG,CAAC,EAAE,GAAG,KAAK,EAAE,OAAO,IAAI,MAAM,QAAQ,EAAE,GAAG,OAAO,EAAE,OAAO,IAAI,MAAM,QAAQ,GAAG,GAAG,CAAC,EAAE,EAAE;AAClJ,YAAM,SAAS,KAAK,GAAG;AACvB,YAAM,OAAO,WAAW,QAAQ,CAAC,GAAG,UAAU;AAC5C,cAAM,KAAK,IAAI,MAAc,QAAQ,CAAC;AACtC,iBAAS,IAAI,GAAG,IAAI,OAAO,KAAK;AAC9B,aAAG,IAAI,CAAC,IAAI,EAAE,SAAS;AACvB,aAAG,IAAI,IAAI,CAAC,IAAI,EAAE,SAAS;AAC3B,aAAG,IAAI,IAAI,CAAC,IAAI,EAAE,SAAS;AAAA,QAC7B;AACA,eAAO,EAAE,YAAY,GAAG;AAAA,MAC1B,CAAC;AACD,YAAM,SAAS,IAAI,MAAM,MAAM;AAC/B,aAAO,OAAO,SAAS,IAAI,MAAM,GAAG,EAAE,CAAC,CAAC;AACxC,YAAMD,SAAQ,CAAC,WAAuF;AACpG,eAAO,SAAS,SAAS,SAAS;AAClC,eAAO,SAAS,WAAW,SAAS;AACpC,eAAO,SAAS,aAAa,SAAS;AACtC,YAAI,MAAM;AAAA;AAAA;AAAA;AAAA,EAAuG,IAAI,GAAG,OAAO,YAAY;AAC3I,cAAM,IAAI,QAAQ,2BAA2B,2HAA2H;AACxK,cAAM,IAAI,QAAQ,iCAAiC,kFAAkF;AACrI,eAAO,eAAe;AAAA,MACxB;AACA,iBAAW,KAAK,KAAK,OAAO,GAAG;AAC7B,cAAM,MAAM,OAAO,EAAE,KAAK,SAAS,QAAQ;AAC3C,cAAM,MAAM,WAAW,OAAO,EAAE,MAAM,aAAaA,MAAK;AACxD,mBAAW,OAAO,MAAM,KAAK,OAAO,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM;AAAA,MAC9F;AACA,YAAM,QAAmB;AAAA,QACvB,MAAM;AAAA,QACN;AAAA,QACA,MAAM,GAAG,GAAG;AACV,gBAAM,OAAOE,QAAO,GAAG,CAAC;AACxB,mBAAS,EAAE,MAAM,IAAI,KAAK,WAAW,CAAC,GAAG,KAAK,WAAW,CAAC,GAAG,KAAK,WAAW,CAAC,GAAG,KAAK,WAAW,CAAC,CAAC;AACnG,mBAAS,IAAI,MAAM,IAAI,KAAK,SAAS,CAAC,GAAG,KAAK,SAAS,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC;AAC3E,mBAAS,MAAM,MAAM,IAAI,KAAK,MAAM,CAAC,GAAG,KAAK,MAAM,CAAC,GAAG,KAAK,MAAM,CAAC,CAAC;AAAA,QACtE;AAAA,MACF;AACA,YAAM,MAAM,QAAQ,CAAC;AACrB,aAAO,SAAS,SAAS;AACzB,aAAO,KAAK,MAAM;AAAA,IACpB;AAAA,EACF;AAEA,WAAS,QAAQ,KAA0B,IAAoB;AAC7D,WAAO,IAAI,IAAI,EAAE,KAAK;AAAA,EACxB;;;AC3OO,WAAS,uBAAuB,OAAiB,UAAmB,QAA+C;AAExH,UAAM,UAAU,CAAmB,MAAY;AAC7C,YAAM,MAAM,CAAC;AACb,iBAAW,KAAK,OAAO,KAAK,CAAC,EAAqB,KAAI,EAAE,CAAC,MAAM,OAAW,KAAI,CAAC,IAAI,EAAE,CAAC;AACtF,aAAO;AAAA,IACT;AACA,UAAM,MAAM,CAAC,MACX,YAAY,SACR,IAAI,MAAM,oBAAoB,QAAQ,EAAE,cAAc,MAAM,aAAa,EAAE,aAAa,SAAS,EAAE,SAAS,YAAY,EAAE,YAAY,MAAM,EAAE,KAAK,CAAC,CAAC,IACrJ,IAAI,MAAM,qBAAqB,QAAQ,EAAE,cAAc,MAAM,WAAW,MAAM,WAAW,GAAG,GAAG,EAAE,CAAC,CAAC;AAGzG,UAAM,QAAQ,YAAY,SAAS,IAAI,CAAC,CAAC,IAAI;AAC7C,WAAO;AAAA,MACL,OAAO,SAAS,IAAI,CAAC,CAAC;AAAA,MACtB,OAAO,SAAS,IAAI,EAAE,WAAW,MAAM,WAAW,IAAI,CAAC;AAAA,MACvD,MAAM,SAAS,IAAI,EAAE,WAAW,IAAI,CAAC;AAAA,MACrC,OAAO,IAAI,EAAE,WAAW,MAAM,aAAa,MAAM,SAAS,MAAM,YAAY,MAAM,CAAC;AAAA,MACnF,QAAQ,SAAS,IAAI,EAAE,WAAW,EAAE,CAAC;AAAA,MACrC,OAAO,IAAI,EAAE,WAAW,KAAK,MAAM,MAAM,WAAW,CAAC;AAAA,MACrD,OAAO,IAAI,EAAE,WAAW,KAAK,aAAa,MAAM,SAAS,KAAK,YAAY,MAAM,CAAC;AAAA,MACjF,UAAU,IAAI,MAAM,kBAAkB,EAAE,cAAc,KAAK,CAAC;AAAA,MAC5D,eAAe,IAAI,EAAE,WAAW,IAAI,CAAC;AAAA,MACrC,aAAa,IAAI,EAAE,WAAW,EAAE,CAAC;AAAA,IACnC;AAAA,EACF;AAGA,MAAM,cAAc,oBAAI,QAAmC;AAC3D,WAAS,aAAa,OAAoC;AACxD,UAAM,QAAQ,YAAY,IAAI,MAAM,KAAK;AACzC,QAAI,MAAO,QAAO;AAClB,UAAM,QAAQ,iBAAiB,KAAK;AACpC,gBAAY,IAAI,MAAM,OAAO,KAAK;AAClC,WAAO;AAAA,EACT;AACA,WAAS,iBAAiB,OAAoC;AAC5D,UAAMC,SAAQ,IAAI,MAAM,MAAM;AAC9B,aAAS,IAAI,GAAG,IAAI,KAAK,KAAK;AAC5B,MAAAA,OAAM,OAAO,IAAI,KAAQ;AACzB,UAAIA,OAAM,MAAM,cAAc,IAAI,OAAU,QAAQ,EAAE,CAAC,GAAG;AACxD,YAAIA,OAAM,MAAM,IAAI,IAAK,OAAM,IAAI,MAAM,kFAAkF;AAC3H,eAAO;AAAA,MACT;AAAA,IACF;AACA,WAAO;AAAA,EACT;AASO,WAAS,cAAc,OAAiB,UAA0B,CAAC,GAAY;AACpF,UAAM,UAAmB,QAAQ,WAAW;AAC5C,UAAM,UAAU,QAAQ,YAAY;AACpC,UAAM,YAAY,EAAE,GAAG,uBAAuB,OAAO,OAAO,GAAG,GAAG,eAAe,QAAQ,SAAS,EAAE;AACpG,QAAI,QAAQ,kBAAmB,YAAW,KAAK,cAAe,WAAU,CAAC,EAAE,OAAO,MAAM;AACxF,QAAI,WAAW;AAEf,UAAM,WAAW,YAAY,SAAS,oBAAoB,EAAE,kBAAkB,IAAI,gBAAgB,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,oBAAoB,EAAE,kBAAkB,GAAG,gBAAgB,CAAC,GAAG,CAAC,EAAE,CAAC;AAGlL,UAAM,gBAAwC,CAAC;AAC/C,eAAW,KAAK,eAAe;AAC7B,YAAM,QAAQ,cAAc,KAAK,CAAC,MAAM,UAAU,CAAC,MAAM,UAAU,CAAC,CAAC,KAAK;AAC1E,UAAI,UAAU,EAAG,eAAc,CAAC,IAAI;AAAA,IACtC;AAEA,UAAM,YAA6B,CAAC;AACpC,UAAM,aAA6B,CAAC;AAEpC,UAAM,QAAQ,CAAC,SAAgC,QAA+B,IAAmB,MAAqB,gBAAqD;AACzK,iBAAW,SAAS,SAAS;AAC3B,cAAM,IAAI,IAAI,MAAM,eAAe;AACnC,UAAE,aAAa,YAAY,IAAI,MAAM,gBAAgB,MAAM,WAAW,CAAC,CAAC;AACxE,UAAE,aAAa,UAAU,IAAI,MAAM,gBAAgB,MAAM,SAAS,CAAC,CAAC;AACpE,UAAE,aAAa,SAAS,IAAI,MAAM,gBAAgB,MAAM,QAAwB,CAAC,CAAC;AAClF,UAAE,aAAa,MAAM,IAAI,MAAM,gBAAgB,MAAM,KAAK,CAAC,CAAC;AAC5D,UAAE,SAAS,IAAI,MAAM,gBAAgB,MAAM,SAAsC,CAAC,CAAC;AACnF,UAAE,sBAAsB;AACxB,UAAE,mBAAmB;AACrB,cAAM,MAAM,MAAM;AAClB,cAAMC,QAAO,IAAI,MAAM,KAAK,GAAG,cAAc,KAAK,EAAE,KAAK,UAAU,GAAG,CAAC;AACvE,QAAAA,MAAK,OAAO,OAAO,OAAO,UAAU,GAAG,KAAK,QAAQ,EAAE;AACtD,YAAI,OAAO,KAAM,CAAAA,MAAK,SAAS,OAAO;AACtC,QAAAA,MAAK,SAAS,WAAW;AACzB,QAAAA,MAAK,aAAa;AAClB,QAAAA,MAAK,gBAAgB;AACrB,aAAK,IAAIA,KAAI;AAAA,MACf;AACA,iBAAW,SAAS,QAAQ;AAC1B,cAAM,IAAI,MAAM;AAChB,YAAI,QAAQ,QAAQ;AAClB,gBAAM,OAAO,IAAI,MAAM,SAAS;AAChC,eAAK,OAAO;AACZ,eAAK,SAAS,IAAI,MAAM,SAAS,CAAC,GAAG,MAAM,SAAS,CAAC,GAAG,MAAM,SAAS,CAAC,CAAC;AACzE,eAAK,IAAI,IAAI;AACb,qBAAW,KAAK,QAAQ,OAAO,UAAU,EAAE,OAAO,sBAAsB,UAAU,eAAe,MAAM,OAAO,EAAE,OAAO,WAAW,GAAG,UAAU,EAAE,OAAO,UAAU,CAAC,GAAG,GAAG,CAAC,GAAG,QAAQ,KAAK,CAAC,CAAC;AAC5L;AAAA,QACF;AAEA,cAAM,QAAQ,IAAI,MAAM,WAAW,EAAE,OAAO,GAAG,EAAE,KAAK;AACtD,cAAM,SAAS,IAAI,MAAM,SAAS,CAAC,GAAG,MAAM,SAAS,CAAC,GAAG,MAAM,SAAS,CAAC,CAAC;AAC1E,aAAK,IAAI,KAAK;AACd,kBAAU,KAAK,KAAK;AAAA,MACtB;AAAA,IACF;AAEA,WAAO;AAAA,MACL,KAAK,SAAS;AAAA,MACd;AAAA,MACA,OAAO,MAAM,eAAe;AAC1B,cAAM,cAAc,eAAe,eAAe,QAAQ;AAC1D,YAAI,SAAU,OAAM,IAAI,MAAM,4CAA4C;AAC1E,mBAAW;AACX,cAAM,UAAU,SAAS,SAAS,OAAO,GAAG;AAAA,UAC1C,QAAQ,aAAa,KAAK;AAAA,UAC1B,WAAW;AAAA,UACX,kBAAkB;AAAA,UAClB;AAAA,UACA,UAAU;AAAA,UACV,KAAK;AAAA,QACP,CAAC;AACD,cAAM,UAAU,QAAQ,OAAO,CAAC,GAAG,WAAW,CAAC;AAC/C,cAAM,KAAK,CAAC,QAAwE,EAAE,SAAS,QAAQ,OAAO,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,QAAQ,QAAQ,OAAO,OAAO,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE;AACzL,cAAM,QAAQ,cAAc,IAAI,MAAM,MAAM,GAAG,EAAE,OAAO,4BAA4B,MAAM,aAAa,GAAI,OAAO,EAAE,IAAI,KAAK,KAAK,IAAI,CAAC,EAAG,CAAC;AAC3I,cAAM,OAAO;AACb,cAAM,QAAuC,CAAC;AAC9C,cAAM,OAAO,GAAG,IAAI;AACpB,cAAM,KAAK,SAAS,KAAK,QAAQ,MAAM,OAAO,WAAW;AACzD,mBAAW,QAAQ,SAAS,OAAO;AACjC,gBAAM,IAAI,IAAI,MAAM,MAAM;AAC1B,YAAE,OAAO,QAAQ,KAAK,EAAE;AACxB,YAAE,SAAS,OAAO,KAAK;AACvB,YAAE,SAAS,IAAI,KAAK,MAAM,CAAC,GAAG,KAAK,MAAM,CAAC,GAAG,KAAK,MAAM,CAAC,CAAC;AAC1D,YAAE,SAAS,IAAI,KAAK;AACpB,YAAE,aAAa;AACf,gBAAM,QAAQ,GAAG,KAAK,EAAE;AACxB,gBAAM,MAAM,SAAS,MAAM,QAAQ,KAAK,IAAI,GAAG,WAAW;AAC1D,uBAAa,CAAC;AACd,gBAAM,IAAI,CAAC;AACX,gBAAM,KAAK,EAAE,IAAI;AAAA,QACnB;AACA,YAAI,SAAS,QAAQ,cAAc,YAAY,QAAS,YAAW,OAAO,OAAO,OAAO,MAAM,EAAE,eAAe,QAAQ,cAAc,CAAC;AACtI,YAAI,KAAM,gBAAe,OAAO,OAAO,IAAI;AAC3C,eAAO,EAAE,OAAO,OAAO,QAAQ,WAAW,SAAS,WAAW;AAAA,MAChE;AAAA,IACF;AAAA,EACF;AAQA,WAAS,eAAe,OAAsB,OAAsC,MAAwB;AAC1G,UAAM,OAAO,KAAK;AAClB,UAAM,aAAa,IAAI,IAAsB,OAAO,OAAO,KAAK,CAAC;AACjE,QAAI,MAAM;AACR,iBAAW,SAAS,MAAM,UAAU;AAClC,YAAI,CAAC,WAAW,IAAI,KAAK,KAAM,MAAuB,OAAQ,OAAM,SAAS,sBAAsB,KAAK;AAAA,MAC1G;AAAA,IACF;AACA,UAAM,OAAO,oBAAI,IAAsB;AACvC,eAAW,MAAM,KAAK,SAAS,CAAC,GAAG;AACjC,YAAM,IAAI,MAAM,GAAG,EAAE;AACrB,YAAM,IAAI,GAAG;AACb,UAAI,CAAC,KAAK,CAAC,KAAK,KAAK,IAAI,CAAC,EAAG;AAC7B,WAAK,IAAI,CAAC;AACV,iBAAW,KAAK,CAAC,GAAG,GAAG,EAAE,QAAQ,GAAG;AAClC,YAAI,MAAM,KAAK,CAAE,EAAmB,OAAQ;AAC5C,UAAE,SAAS,sBAAsB,EAAE;AACnC,UAAE,SAAS,kBAAkB,GAAG;AAChC,YAAI,EAAE,MAAO,GAAE,SAAS,mBAAmB,EAAE;AAC7C,YAAI,EAAE,MAAO,GAAE,SAAS,mBAAmB,EAAE;AAAA,MAC/C;AAAA,IACF;AAAA,EACF;AAEA,WAAS,eAAiC,GAA8B;AACtE,UAAM,MAAkB,CAAC;AACzB,QAAI;AAAG,iBAAW,KAAK,OAAO,KAAK,CAAC,EAAqB,KAAI,EAAE,CAAC,MAAM,OAAW,KAAI,CAAC,IAAI,EAAE,CAAC;AAAA;AAC7F,WAAO;AAAA,EACT;;;ACnMO,WAAS,cAAc,OAAiB,UAAyB,CAAC,GAAY;AACnF,UAAM,MAAM,KAAK,IAAI,GAAG,KAAK,MAAM,QAAQ,OAAO,CAAC,CAAC;AACpD,UAAM,aAAa,QAAQ,cAAc;AACzC,UAAM,SAAS,QAAQ,UAAU;AACjC,UAAM,cAAc,IAAI,MAAM,qBAAqB,EAAE,OAAO,UAAU,WAAW,GAAG,WAAW,MAAM,QAAQ,QAAQ,UAAU,KAAK,CAAC;AACrI,UAAM,UAAU,oBAAI,IAAmB;AACvC,UAAM,QAAgB,CAAC;AACvB,UAAM,WAAW,IAAI,MAAM,cAAc,GAAG,CAAC;AAE7C,UAAM,cAAc,CAAC,GAAU,QAA0C;AACvE,UAAI,EAAE,OAAQ,QAAO,IAAI,IAAI,EAAE,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,EAAE,UAAU;AAC9E,QAAE,KAAK,kBAAkB,MAAM,KAAK;AACpC,aAAO,IAAI,IAAI,GAAG,GAAG,CAAC,EAAE,mBAAmB,EAAE,KAAK,WAAW;AAAA,IAC/D;AAEA,UAAM,UAAU,CAAC,OAA4B;AAC3C,UAAI,CAAC,QAAQ,UAAW,QAAO;AAC/B,YAAM,WAAW,MAAM,KAAK,CAACC,OAAMA,GAAE,OAAO,EAAE;AAC9C,UAAI,SAAU,QAAO;AACrB,YAAM,OAAO,MAAM,KAAK,CAACA,OAAMA,GAAE,OAAO,IAAI;AAC5C,UAAI,KAAM,QAAO;AACjB,UAAI,MAAM,UAAU,IAAK,QAAO;AAChC,YAAM,YAAY,IAAI,QAAQ,UAAU,UAAU,EAAE,cAAc,YAAY,eAAe,YAAY,UAAU,KAAM,CAAC;AAC1H,gBAAU,OAAO;AACjB,UAAI,QAAQ,aAAa,OAAO;AAC9B,cAAM,WAAW,UAAU;AAC3B,kBAAU,iBAAiB,SAA8B,UAAU,OAAO,QAAQ,KAAK,KAAK,KAAK;AAC/F,gBAAM,OAAO,SAAS;AACtB,cAAI,CAAC,KAAK,UAAW,QAAO,SAAS,KAAK,MAAM,UAAU,OAAO,QAAQ,KAAK,KAAK,GAAG;AACtF,gBAAM,SAAS,EAAE,OAAO,KAAK,OAAO,OAAO,WAAW,KAAK,OAAO,WAAW,QAAQ,KAAK,OAAO,QAAQ,OAAO,KAAK,OAAO,MAAM;AAClI,mBAAS,KAAK,MAAM,UAAU,OAAO,QAAQ,KAAK,KAAK,GAAG;AAC1D,eAAK,OAAO,SAAS,OAAO;AAC5B,eAAK,OAAO,aAAa,OAAO;AAChC,eAAK,OAAO,UAAU,OAAO;AAC7B,eAAK,OAAO,SAAS,OAAO;AAAA,QAC9B;AAAA,MACF;AACA,gBAAU,UAAU;AACpB,YAAM,IAAU,EAAE,WAAW,IAAI,KAAK;AACtC,YAAM,KAAK,CAAC;AACZ,aAAO;AAAA,IACT;AAEA,UAAM,UAAU,CAAC,MAAkB;AACjC,QAAE,KAAK;AACP,QAAE,UAAU,UAAU;AAAA,IACxB;AAEA,UAAM,MAAM,IAAI,MAAM,QAAQ;AAC9B,UAAM,OAAO,IAAI,MAAM,WAAW;AAClC,UAAM,MAAM,IAAI,MAAM,QAAQ;AAC9B,UAAM,IAAI,IAAI,MAAM,QAAQ;AAC5B,UAAM,QAAQ,IAAI,MAAM,QAAQ;AAChC,UAAMC,OAAM,IAAI,MAAM,KAAK;AAC3B,UAAM,OAAO,IAAI,MAAM,QAAQ;AAC/B,UAAM,SAAS,IAAI,MAAM,QAAQ;AACjC,UAAM,OAAO,IAAI,MAAM,WAAW,GAAG,GAAG,GAAG,CAAC;AAE5C,WAAO;AAAA,MACL;AAAA,MACA,SAAS,IAAIC,OAAM,QAAQ;AACzB,cAAM,WAAW,QAAQ,IAAI,EAAE;AAC/B,gBAAQ,IAAI,IAAI,EAAE,MAAAA,OAAM,QAAQ,UAAU,MAAM,UAAU,WAAW,SAAS,WAAWA,MAAK,SAAS,CAAC;AACxG,QAAAA,MAAK,WAAW;AAAA,MAClB;AAAA,MACA,WAAW,IAAI;AACb,cAAM,IAAI,QAAQ,IAAI,EAAE;AACxB,cAAM,IAAI,MAAM,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE;AACvC,YAAI,EAAG,SAAQ,CAAC;AAChB,YAAI,EAAG,GAAE,KAAK,WAAW,EAAE;AAC3B,gBAAQ,OAAO,EAAE;AAAA,MACnB;AAAA,MACA,OAAO,QAAQ,YAAY;AACzB,cAAM,MAAM,OAAO,iBAAiB,KAAK;AACzC,cAAM,SAA2C,CAAC;AAClD,mBAAW,KAAK,YAAY;AAC1B,gBAAM,IAAI,QAAQ,IAAI,EAAE,EAAE;AAC1B,cAAI,CAAC,EAAG;AACR,YAAE,IAAI,EAAE,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;AAC3C,gBAAM,KAAK,IAAI,IAAI,EAAE,OAAO,CAAC;AAC7B,gBAAM,KAAK,IAAI,IAAI,EAAE,OAAO,CAAC;AAC7B,gBAAM,KAAK,IAAI,IAAI,EAAE,OAAO,CAAC;AAC7B,cAAI,EAAE,IAAI,KAAK,EAAE,IAAI,KAAK,EAAE,IAAI,MAAM,EAAG;AACzC,iBAAO,KAAK,EAAE,IAAI,EAAE,IAAI,GAAG,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG,CAAC;AAAA,QAC1D;AACA,eAAO,KAAK,CAAC,GAAG,MAAM,EAAE,IAAI,EAAE,CAAC;AAC/B,cAAM,SAAS,oBAAI,IAAY;AAC/B,iBAAS,IAAI,GAAG,IAAI,OAAO,UAAU,OAAO,OAAO,KAAK,IAAK,QAAO,IAAI,OAAO,CAAC,EAAE,EAAE;AACpF,mBAAW,KAAK,MAAO,KAAI,EAAE,OAAO,QAAQ,CAAC,OAAO,IAAI,EAAE,EAAE,EAAG,SAAQ,CAAC;AACxE,mBAAW,MAAM,QAAQ;AACvB,gBAAM,IAAI,QAAQ,EAAE;AACpB,cAAI,CAAC,EAAG;AACR,gBAAM,IAAI,QAAQ,IAAI,EAAE;AACxB,YAAE,KAAK,kBAAkB,MAAM,KAAK;AACpC,YAAE,KAAK,YAAY,UAAU,KAAK,MAAM,GAAG;AAC3C,cAAI,CAAC,EAAE,KAAK,SAAS,YAAa,GAAE,KAAK,SAAS,mBAAmB;AACrE,UAAAD,KAAI,KAAK,EAAE,KAAK,SAAS,WAAY;AACrC,UAAAA,KAAI,QAAQ,IAAI;AAChB,sBAAY,GAAG,CAAC;AAChB,YAAE,UAAU,SAAS,KAAK,GAAG,EAAE,gBAAgB,GAAG,MAAM;AACxD,YAAE,UAAU,WAAW,KAAK,IAAI;AAEhC,cAAI,OAAO,IAAI,GAAG,GAAG,CAAC,EAAE,gBAAgB,IAAI,EAAE,IAAI,CAAC,IAAI,EAAG,GAAE,UAAU,WAAW,SAAS,IAAI;AAC9F,YAAE,UAAU,MAAM,IAAI,KAAK,IAAI,KAAK,IAAI,IAAI,GAAG,IAAI,GAAG,KAAK,IAAI,KAAK,IAAI,IAAI,GAAG,IAAI,GAAG,CAAC;AACvF,YAAE,KAAK;AACP,YAAE,UAAU,UAAU;AACtB,cAAI,CAAC,EAAE,UAAU,QAAQ;AACvB,gBAAI,MAAwB,EAAE;AAC9B,mBAAO,IAAI,OAAQ,OAAM,IAAI;AAC5B,aAAC,QAAQ,SAAS,KAAK,IAAI,EAAE,SAAS;AAAA,UACzC;AAAA,QACF;AACA,eAAO,MAAM,OAAO,CAAC,MAAM,EAAE,OAAO,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE,EAAY;AAAA,MACrE;AAAA,MACA,SAAS;AACP,eAAO,MAAM,OAAO,CAAC,MAAM,EAAE,OAAO,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE,EAAY;AAAA,MACrE;AAAA,MACA,UAAU;AACR,mBAAW,KAAK,MAAO,GAAE,UAAU,iBAAiB;AACpD,cAAM,SAAS;AACf,mBAAW,KAAK,QAAQ,OAAO,EAAG,GAAE,KAAK,WAAW,EAAE;AACtD,gBAAQ,MAAM;AACd,oBAAY,QAAQ;AACpB,iBAAS,QAAQ;AAAA,MACnB;AAAA,IACF;AAAA,EACF;;;AC9JA,MAAM,OAAO,CAAC,WAAW,WAAW,WAAW,WAAW,WAAW,WAAW,SAAS;AAEzF,WAASE,UAAS,MAA4B;AAC5C,QAAI,IAAI,SAAS;AACjB,WAAO,MAAM;AACX,UAAK,IAAI,eAAgB;AACzB,UAAI,IAAI;AACR,UAAI,KAAK,KAAK,IAAK,MAAM,IAAK,IAAI,CAAC;AACnC,WAAK,IAAI,KAAK,KAAK,IAAK,MAAM,GAAI,IAAI,EAAE;AACxC,eAAS,IAAK,MAAM,QAAS,KAAK;AAAA,IACpC;AAAA,EACF;AAMO,WAAS,aAAa,OAAiB,SAAgC;AAC5E,UAAM,IAAI,QAAQ,KAAK;AACvB,UAAM,IAAI,QAAQ,KAAK;AACvB,UAAM,SAAS,QAAQ,SAAS,cAAc,QAAQ;AACtD,WAAO,QAAQ;AACf,WAAO,SAAS;AAEhB,UAAM,MAAM,OAAO,WAAW,MAAM,EAAE,oBAAoB,KAAK,CAAC;AAChE,UAAM,OAAOA,UAAS,QAAQ,QAAQ,CAAC;AACvC,UAAM,SAAS,IAAI,KAAK,IAAI,GAAG,QAAQ,OAAO,EAAE;AAChD,UAAM,YAAY,QAAQ,aAAa;AAEvC,UAAM,UAAU,IAAI,MAAM,cAAc,MAAM;AAC9C,YAAQ,aAAa,MAAM;AAC3B,UAAM,WAAW,IAAI,MAAM,qBAAqB;AAAA,MAC9C,OAAO;AAAA,MACP,WAAW;AAAA,MACX,WAAW;AAAA,MACX,KAAK;AAAA,MACL,UAAU;AAAA,MACV,aAAa;AAAA,MACb,mBAAmB;AAAA,IACrB,CAAC;AAED,QAAI,KAAK;AACT,QAAIC,WAAU;AACd,QAAI,QAAQ;AACZ,QAAI,MAAM;AAEV,UAAM,OAAO,CAAC,OAAe,GAAW,GAAW,IAAY,OAAqB;AAClF,UAAI,CAAC,IAAK;AACV,UAAI,YAAY;AAChB,UAAI,SAAS,GAAG,GAAG,IAAI,EAAE;AAAA,IAC3B;AAEA,UAAM,WAAW,MAAY;AAC3B,YAAM,IAAI,KAAK;AACf,eAAS,IAAI,GAAG,IAAI,GAAG,IAAK,MAAK,MAAM,IAAIA,YAAW,CAAC,GAAG,KAAK,MAAO,IAAI,IAAK,CAAC,GAAG,GAAG,KAAK,KAAK,IAAI,CAAC,GAAG,KAAK,MAAM,IAAI,IAAI,CAAC;AAC5H,WAAK,WAAW,GAAG,KAAK,MAAM,IAAI,IAAI,GAAG,GAAG,IAAI,KAAK,MAAM,IAAI,IAAI,CAAC;AACpE,YAAM,IAAM,QAAQ,MAAO,IAAI,MAAO;AACtC,WAAK,WAAW,GAAG,KAAK,MAAM,IAAI,GAAG,GAAG,IAAI,KAAK,MAAM,IAAI,GAAG,CAAC;AAAA,IACjE;AAEA,UAAM,YAAY,MAAY;AAC5B,YAAM,OAAO,KAAK,IAAI,GAAG,KAAK,MAAM,IAAI,EAAE,CAAC;AAC3C,eAAS,IAAI,GAAG,IAAI,GAAG,KAAK,MAAM;AAChC,iBAAS,IAAI,GAAG,IAAI,GAAG,KAAK,MAAM;AAChC,gBAAM,IAAI,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE;AACtC,eAAK,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,GAAG,GAAG,MAAM,IAAI;AAAA,QAC9C;AAAA,MACF;AAAA,IACF;AAEA,UAAM,YAAY,MAAY;AAC5B,YAAM,MAAOA,WAAU,KAAM;AAC7B,WAAK,QAAQ,MAAM,OAAO,GAAG,QAAQ,KAAK,KAAK,KAAK,IAAI,QAAQ,GAAG,CAAC,MAAM,GAAG,GAAG,GAAG,KAAK,MAAM,IAAI,IAAI,CAAC;AACvG,WAAK,QAAQ,MAAM,OAAO,GAAG,aAAa,GAAG,KAAK,MAAM,IAAI,IAAI,GAAG,GAAG,IAAI,KAAK,MAAM,IAAI,IAAI,CAAC;AAC9F,YAAM,MAAM,MAAM,MAAM,KAAK,IAAI,QAAQ,GAAG,KAAK;AACjD,YAAM,MAAM,MAAM,OAAO,KAAK,IAAI,QAAQ,GAAG,KAAK;AAClD,WAAK,WAAW,KAAK,GAAG,KAAK,GAAG,IAAI,EAAE;AACtC,YAAM,KAAM,QAAQ,MAAO,IAAI,MAAM;AACrC,WAAK,WAAW,IAAI,KAAK,MAAM,IAAI,GAAG,GAAG,IAAI,EAAE;AAAA,IACjD;AAEA,UAAM,OAAO,MAAY;AACvB,UAAI,CAAC,IAAI;AACP,aAAK,WAAW,GAAG,GAAG,GAAG,CAAC;AAAA,MAC5B,WAAWA,WAAU,MAAM,GAAG;AAC5B,iBAAS;AAAA,MACX,WAAWA,WAAU,MAAM,GAAG;AAC5B,kBAAU;AAAA,MACZ,OAAO;AACL,kBAAU;AAAA,MACZ;AACA,cAAQ,cAAc;AAAA,IACxB;AAEA,UAAM,QAAQ,MAAY;AACxB,eAAS,oBAAoB,KAAK,YAAY;AAC9C,eAAS,MAAM,OAAO,KAAK,UAAW,CAAQ;AAC9C,WAAK;AAAA,IACP;AACA,UAAM;AAEN,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA,IAAI,KAAK;AACP,eAAO;AAAA,MACT;AAAA,MACA,IAAI,UAAU;AACZ,eAAOA;AAAA,MACT;AAAA,MACA,MAAM,OAAO;AACX,aAAK,CAAC,CAAC;AACP,cAAM;AACN,cAAM;AAAA,MACR;AAAA,MACA,WAAW,GAAG;AACZ,QAAAA,WAAU,KAAK,IAAI,GAAG,KAAK,MAAM,CAAC,CAAC;AACnC,YAAI,GAAI,MAAK;AAAA,MACf;AAAA,MACA,OAAO,IAAI;AACT,YAAI,CAAC,GAAI;AACT,iBAAS;AACT,eAAO;AACP,YAAI,OAAO,QAAQ;AACjB,iBAAO;AACP,eAAK;AAAA,QACP;AAAA,MACF;AAAA,MACA,UAAU;AACR,gBAAQ,QAAQ;AAChB,iBAAS,QAAQ;AAAA,MACnB;AAAA,IACF;AAAA,EACF;;;AC5IA,MAAM;AAAA;AAAA,IAA0B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAWhC,MAAM;AAAA;AAAA,IAA6B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAS5B,WAAS,gBAAgB,QAA8E,UAAwB,WAAyB;AAC7J,WAAO,SAAS,WAAW,SAAS;AACpC,WAAO,SAAS,SAAS,SAAS;AAClC,WAAO,SAAS,SAAS,SAAS;AAClC,WAAO,SAAS,cAAc,SAAS;AACvC,WAAO,SAAS,gBAAgB,EAAE,OAAO,UAAU;AACnD,WAAO,eAAe,kBAAkB,OAAO,aAAa,QAAQ,2BAA2B,YAAY;AAAA,EAC7G;AAEA,WAAS,MAAkC,UAAa,UAAwB,WAAsB;AACpG,UAAM,WAAW,SAAS;AAC1B,aAAS,kBAAkB,CAAC,QAAQ,aAAa;AAC/C,gBAAU,KAAK,UAAU,QAAQ,QAAQ;AACzC,sBAAgB,QAAQ,UAAU,SAAS;AAAA,IAC7C;AACA,aAAS,wBAAwB,MAAM,QAAQ,SAAS;AACxD,WAAO;AAAA,EACT;AAaO,WAAS,aAAa,OAAiB,MAAwB,UAAuB,CAAC,GAAiB;AAC7G,UAAM,YAAY,QAAQ,aAAa;AACvC,UAAM,WAAyB,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,SAAS,EAAE,OAAO,QAAQ,WAAW,EAAE,EAAE;AAClI,UAAM,SAAqB;AAAA,MACzB;AAAA,MACA,IAAI,IAAI,IAAI,MAAM;AAChB,iBAAS,GAAG,QAAQ;AACpB,iBAAS,GAAG,QAAQ;AACpB,YAAI,SAAS,OAAW,UAAS,KAAK,QAAQ;AAAA,MAChD;AAAA,IACF;AACA,UAAM,WAAW,MAAM,KAAK,MAAM,GAAG,UAAU,SAAS;AACxD,UAAM,QAAQ,MAAM,IAAI,MAAM,kBAAkB,EAAE,cAAc,MAAM,iBAAiB,CAAC,GAAG,UAAU,SAAS;AAC9G,UAAM,WAAW,MAAM,IAAI,MAAM,qBAAqB,GAAG,UAAU,SAAS;AAC5E,aAAS,SAAS,OAAO;AACzB,aAAS,SAAS,gBAAgB;AAClC,aAAS,SAAS,mBAAmB;AACrC,WAAO;AAAA,EACT;AAMO,WAAS,WAAW,OAAiBC,OAAoB,UAAuB,CAAC,GAAe;AACrG,QAAI,UAAU,QAAQ;AACtB,QAAI,YAAY,QAAW;AACzB,UAAI,CAACA,MAAK,SAAS,YAAa,CAAAA,MAAK,SAAS,mBAAmB;AACjE,gBAAUA,MAAK,SAAS,cAAcA,MAAK,SAAS,YAAY,IAAI,IAAI;AAAA,IAC1E;AACA,UAAM,OAAO,MAAM,QAAQA,MAAK,QAAQ,IAAIA,MAAK,SAAS,CAAC,IAAIA,MAAK;AACpE,UAAM,IAAI,aAAa,OAAO,MAAM,EAAE,GAAG,SAAS,QAAQ,CAAC;AAC3D,MAAE,SAAS,KAAK,SAAS,QAAQ,QAAQ;AACzC,IAAAA,MAAK,WAAW;AAChB,IAAAA,MAAK,sBAAsB,EAAE,SAAS;AACtC,IAAAA,MAAK,yBAAyB,EAAE,SAAS;AACzC,IAAAA,MAAK,SAAS,OAAO,EAAE,SAAS;AAChC,WAAO,EAAE,SAAS;AAAA,EACpB;;;ACtEO,WAAS,mBAAmB,OAAiB,UAA+B,CAAC,GAAiB;AACnG,WAAO;AAAA,MACL;AAAA,MACA,eAAe,CAAC,IAAI,CAAC,MAAM,cAAc,OAAO,EAAE,SAAS,QAAQ,SAAS,WAAW,QAAQ,WAAW,GAAI,QAAQ,SAAS,EAAE,QAAQ,QAAQ,OAAO,IAAI,CAAC,GAAI,GAAG,EAAE,CAAC;AAAA,MACvK,WAAW,CAAC,OAAO,QAAQ,MAAM,UAAU,OAAO,OAAO,QAAQ,CAAC;AAAA,MAClE,YAAY,CAAC,OAAO,UAAU,OAAO,MAAM,WAAW,OAAO,OAAO,UAAU,OAAO,CAAC;AAAA,MACtF,cAAc,CAAC,MAAM,MAAM,aAAa,OAAO,MAAM,CAAC;AAAA,MACtD,YAAY,CAACC,OAAM,MAAM,WAAW,OAAOA,OAAM,CAAC;AAAA,MAClD,gBAAgB,CAAC,GAAsC,IAA6B,CAAC,MAClF,EAAuB,aACpB,cAAc,OAAO,GAAuB,EAAE,UAAU,QAAQ,UAAU,GAAG,EAAE,CAAC,IAChF,cAAc,OAAO,EAAE,UAAU,QAAQ,UAAU,GAAI,EAAqB,CAAC;AAAA,MACnF,cAAc,CAAC,IAAI,CAAC,MAAM;AACxB,cAAMC,YAAW,EAAE,YAAY,QAAQ;AACvC,YAAI,CAACA,UAAU,OAAM,IAAI,MAAM,6EAA6E;AAC5G,eAAO,aAAa,OAAO,EAAE,GAAG,GAAG,UAAAA,UAAS,CAAC;AAAA,MAC/C;AAAA,MACA,eAAe,CAAC,IAAI,CAAC,MAAM,cAAc,OAAO,EAAE,WAAW,QAAQ,WAAW,QAAQ,QAAQ,QAAQ,GAAG,EAAE,CAAC;AAAA,MAC9G,YAAY,CAAC,QAAQ,QAAQ,aAAa,WAAW,OAAO,QAAQ,QAAQ,QAAQ;AAAA,MACpF;AAAA,IACF;AAAA,EACF;","names":["poseOf","object","tex","a","b","mesh","n","color","p","width","height","depth","indexArray","sign","list","num","equals","area","ring","bs","layeroffset","rgb","color","c","mesh","hex","color","list","range","poseOf","mesh","patch","list","poseOf","color","mesh","s","box","mesh","mulberry","channel","mesh","mesh","document"]}
1
+ {"version":3,"sources":["../src/index.ts","../../game-contracts/src/blast-shove.ts","../../game-contracts/src/blast-impulse.ts","../../game-contracts/src/blast-knock.ts","../../game-contracts/src/impact-cost.ts","../../game-contracts/src/impact.ts","../../game-contracts/src/body.ts","../../game-contracts/src/spec.ts","../../game-contracts/src/weapons.ts","../../game-contracts/src/ignition.ts","../../game-contracts/src/seal.ts","../../game-contracts/src/twist.ts","../../game-contracts/src/animation.ts","../../game-contracts/src/tool-animation.ts","../../game-contracts/src/frame.ts","../../game-contracts/src/warn.ts","../../game-contracts/src/registry.ts","../../game-contracts/src/bench.ts","../../game-contracts/src/environment.ts","../../game-contracts/src/categories.ts","../../game-contracts/src/origin.ts","../../game-contracts/src/footprint.ts","../../game-contracts/src/foley.ts","../../game-contracts/src/index.ts","../src/routed-particles.ts","../src/effects.ts","../../game-shapes/src/color.ts","../../game-shapes/src/hash.ts","../../game-shapes/src/math.ts","../../game-shapes/src/primitives/three-generators.ts","../../game-shapes/src/primitives/difference.ts","../../game-shapes/src/primitives/earcut.ts","../../game-shapes/src/primitives/extrude.ts","../../game-shapes/src/primitives/index.ts","../../game-shapes/src/random.ts","../../game-shapes/src/registry.ts","../../game-shapes/schema/shape.schema.json","../../game-shapes/src/schema.ts","../../game-shapes/src/generate.ts","../../game-shapes/src/merge.ts","../../game-shapes/src/worker.ts","../../game-shapes-three/src/geometry.ts","../../game-shapes-three/src/namespace.ts","../../game-shapes-three/src/object.ts","../../game-shapes-three/src/merge-by-material.ts","../../game-shapes-three/src/cord.ts","../../game-shapes-three/src/release.ts","../../game-shapes-three/src/guards.ts","../../game-render-three/src/lights/slots.ts","../../game-render-three/src/lights/arbiter.ts","../../game-render-three/src/fog.ts","../../game-render-three/src/lights/scene-state.ts","../../game-render-three/src/lights/budget.ts","../../game-render-three/src/lights/types.ts","../../game-render-three/src/lights/guard.ts","../../game-render-three/src/program-key.ts","../../game-render-three/src/pools/anchors.ts","../../game-render-three/src/probes.ts","../../game-render-three/src/warmup.ts","../../game-render-three/src/pools/pooled.ts","../../game-render-three/src/pools/geometry.ts","../../game-render-three/src/pools/material.ts","../../game-render-three/src/pools/render-pools.ts","../../game-render-three/src/frame-pacer.ts","../../game-render-three/src/static-batcher.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","../../game-props3d/src/shape-recorder.ts","../src/types.ts","../../game-interactables/src/colliders.ts","../../game-interactables/src/rng.ts","../../game-interactables/src/loot.ts","../../game-interactables/src/locale.ts","../../game-interactables/src/math.ts","../../game-interactables/src/world.ts","../src/pose.ts","../src/merge.ts","../src/mesh.ts","../src/mirrors.ts","../src/screen.ts","../src/sway.ts","../src/adapter.ts"],"sourcesContent":["export { createThreeAdapter } from './adapter'\nexport type { ThreeAdapter, ThreeAdapterOptions } from './adapter'\nexport { createMeshApi, createDefaultMaterials } from './mesh'\nexport type { FinishOptions, MaterialMap, MeshApi, MeshApiOptions, MeshResult } from './mesh'\nexport { mergeParts } from './merge'\nexport type { MergedGroup, MergeOptions, PoseBatch, SwayBatch } from './merge'\nexport { applyPose, poseOf, rememberRest, syncObject } from './pose'\nexport type { Pose, SyncOptions } from './pose'\nexport { attachSway, patchSwayShader, swayMaterial } from './sway'\nexport type { SwayHandle, SwayOptions, SwayUniforms } from './sway'\nexport { createEffects } from './effects'\nexport type { Effects, EffectsOptions, EffectsStats, PerspectiveLike, PoolKind } from './effects'\nexport { createScreen } from './screen'\nexport type { Screen, ScreenOptions } from './screen'\nexport { createMirrors } from './mirrors'\nexport type { MirrorCandidate, MirrorOptions, Mirrors, ReflectorConstructor } from './mirrors'\nexport { catenary, createCord } from './cord'\nexport { MATERIAL_KEYS } from './types'\nexport type { CanvasFactory, HouseholdApi, MaterialKey, Quality, ThreeLib, Vec3 } from './types'\n","import type { Vec3 } from './destruction'\n\n/**\n * The numbers of the shove a blast gives to a body that moves (a loose item, a piece of furniture, a person): all of them a game value, with a default that reads well in a room,\n * not a physical table. A host reads them once and lends them to the modules that move their own bodies (`ModuleHost.blastShove`).\n */\nexport interface BlastShoveOptions {\n /**\n * The share of the blast energy that reaches a body as motion: the speed at the centre is `sqrt(2 * energy * coupling / mass)`, before the falloff (default 0.02).\n */\n readonly coupling?: number\n /** Fastest a body is thrown, in m/s, at the centre of the blast (default 20). */\n readonly maxSpeed?: number\n /** How much of the push goes up: the direction is `(away, lift)` normalised from the horizontal outward direction (default 0.3). */\n readonly lift?: number\n /** Below this speed (m/s) nothing moves: a heavy thing far from a small blast stays where it is (default 0.3). */\n readonly minSpeed?: number\n /** Exponent of the falloff `(1 - distance / radius) ^ power`, the same as the damage of the destruction world (default 2). */\n readonly falloffPower?: number\n}\n\n/** The defaults of `BlastShoveOptions`. */\nexport const DEFAULT_BLAST_SHOVE: Readonly<Required<BlastShoveOptions>> = { coupling: 0.02, maxSpeed: 20, lift: 0.3, minSpeed: 0.3, falloffPower: 2 }\n\n/** A blast as the shove reads it: where, how much energy (J) and how far it reaches (m). `reach` (0..1) is the share that got through what stood in the way. */\nexport interface BlastSource {\n readonly point: Vec3\n readonly energy: number\n readonly radius: number\n readonly reach?: number\n /**\n * Kilograms of TNT equivalent of the charge. With it a body is pushed by the blast wave itself (`blast-impulse`), and the shove of loose items keeps this module's rule; without it the body\n * takes the charge from `energy` by `BlastImpulseOptions.tntPerJoule`.\n */\n readonly tnt?: number\n /** Inside this radius (metres) the blast is lethal: a body there that the blast reaches (not behind cover) goes down, wounded, wherever the wave pushed it. */\n readonly lethalRadius?: number\n}\n\n/** What a blast does to one body: how fast it leaves (m/s), the unit direction it leaves in, and how far from the centre it was. */\nexport interface BlastShove {\n readonly speed: number\n readonly direction: Vec3\n readonly distance: number\n /** The falloff applied, 0..1. */\n readonly falloff: number\n}\n\nconst clampShare = (share: number): number => Math.min(1, Math.max(0, share))\n\n/**\n * The shove a blast gives to a body of `mass` kg whose centre is at `position`: away from the centre, lifted a little, with the speed\n * `sqrt(reach) * min(maxSpeed, sqrt(2 * energy * coupling / mass)) * (1 - distance / radius) ^ 2`. Undefined when the body is outside the radius or the speed is under `minSpeed`.\n * The same rule moves the loose items and furniture of the test room and the people of the characters module, so a game that tunes the options tunes all of them.\n */\nexport function blastShove(blast: BlastSource, target: { readonly position: Vec3; readonly mass: number }, options: BlastShoveOptions = {}): BlastShove | undefined {\n const o = { ...DEFAULT_BLAST_SHOVE, ...options }\n const dx = target.position[0] - blast.point[0]\n const dy = target.position[1] - blast.point[1]\n const dz = target.position[2] - blast.point[2]\n const distance = Math.hypot(dx, dy, dz)\n if (!(blast.radius > 0) || distance > blast.radius) return undefined\n const falloff = (1 - distance / blast.radius) ** o.falloffPower\n const mass = Math.max(target.mass, 0.05)\n // cover takes its share off the push itself, past the cap too: a light thing behind a cupboard is not thrown as fast as one in the open\n const reach = clampShare(blast.reach ?? 1)\n const speed = Math.sqrt(reach) * Math.min(o.maxSpeed, Math.sqrt((2 * blast.energy * o.coupling) / mass)) * falloff\n if (!(speed >= o.minSpeed)) return undefined\n // straight above or below the centre there is no \"away\": the push goes up\n const direction: Vec3 = distance > 1e-4 ? [dx / distance, dy / distance + o.lift, dz / distance] : [0, 1, 0]\n const length = Math.hypot(direction[0], direction[1], direction[2])\n return { speed, direction: [direction[0] / length, direction[1] / length, direction[2] / length], distance, falloff }\n}\n","/**\n * The push a blast gives a standing body, from the physics of the blast wave and not from a game coupling: the impulse of the wave per square metre at a distance from a charge of\n * `tnt` kg of TNT equivalent (Kinney and Graham's fit of the side-on impulse, scaled by the cube root of the charge), reflected on the body, over the area the body shows the wave,\n * divided by its mass. A hand grenade at 3 m gives a person a few tenths of a metre per second; what floors that person is the fragments, not the wave. A big charge close throws.\n */\n\n/** The numbers of the body a blast wave pushes. All have a default for a standing adult. */\nexport interface BlastImpulseOptions {\n /** Square metres of the body the wave meets: about 1.75 m tall by 0.4 m wide (default 0.7). */\n readonly bodyArea?: number\n /** How much of the push goes up: the direction is `(away, lift)` normalised (default 0.15: the wave runs along the ground). */\n readonly lift?: number\n /** Below this speed (m/s) the body does not move (default 0.3). */\n readonly minSpeed?: number\n /**\n * Kilograms of TNT per joule of the blast `energy`, for a blast that does not say its charge (`BlastSource.tnt`). Default 0.24 / 200000: the arsenal's energy scale, where the M67 (180 g\n * of Composition B, 0.24 kg of TNT) is 200 kJ. It is a game equivalence for blasts without a charge, not a physical constant.\n */\n readonly tntPerJoule?: number\n}\n\nexport const DEFAULT_BLAST_IMPULSE: Readonly<Required<BlastImpulseOptions>> = { bodyArea: 0.7, lift: 0.15, minSpeed: 0.3, tntPerJoule: 0.24 / 200000 }\n\n/** Closest distance (m) the fit is read at: inside it the charge is in contact and the body is torn apart anyway. */\nconst NEAREST = 0.3\n\n/**\n * Side-on (incident) specific impulse of the wave, in Pa·s, at `distance` metres from `tnt` kg of TNT in free air: Kinney and Graham, \"Explosive Shocks in Air\" (1985),\n * `I/W^(1/3) = 0.067 sqrt(1 + (Z/0.23)^4) / (Z^2 (1 + (Z/1.55)^3)^(1/3))` bar·ms per kg^(1/3), with the scaled distance `Z = R / W^(1/3)`.\n */\nexport function sideOnImpulse(tnt: number, distance: number): number {\n if (!(tnt > 0)) return 0\n const w3 = Math.cbrt(tnt)\n const z = Math.max(distance, NEAREST) / w3\n const scaled = (0.067 * Math.sqrt(1 + (z / 0.23) ** 4)) / (z * z * Math.cbrt(1 + (z / 1.55) ** 3))\n return scaled * w3 * 100 // bar·ms to Pa·s\n}\n\n/**\n * The impulse per square metre a body facing the wave takes, Pa·s: the side-on impulse reflected. The reflection doubles a weak wave and grows up to about six times close to the charge,\n * where the wave is strong (the ratio of reflected to side-on impulse of the Kingery-Bulmash charts, fitted as `2 + 4 / (1 + Z^2)`).\n */\nexport function bodyImpulse(tnt: number, distance: number): number {\n if (!(tnt > 0)) return 0\n const z = Math.max(distance, NEAREST) / Math.cbrt(tnt)\n return sideOnImpulse(tnt, distance) * (2 + 4 / (1 + z * z))\n}\n\n/** The speed (m/s) a blast wave gives a free body of `mass` kg at `distance` metres from `tnt` kg of TNT, `reach` (0..1) of it getting through what stands in the way. */\nexport function blastBodySpeed(tnt: number, distance: number, mass: number, reach = 1, options: BlastImpulseOptions = {}): number {\n const area = options.bodyArea ?? DEFAULT_BLAST_IMPULSE.bodyArea\n return (bodyImpulse(tnt, distance) * area * Math.max(0, reach)) / Math.max(mass, 1)\n}\n","import { blastBodySpeed, DEFAULT_BLAST_IMPULSE } from './blast-impulse'\nimport type { BlastImpulseOptions } from './blast-impulse'\nimport type { BlastShoveOptions, BlastSource } from './blast-shove'\nimport type { Vec3 } from './destruction'\n\n/** From this speed (m/s) a blast takes a standing body off its feet. Below it the body is only pushed. */\nexport const KNOCK_DOWN_SPEED = 2.5\n/** The angle of a body lying where it fell (the same tilt a dead body takes). */\nexport const KNOCKED_DOWN_ANGLE = 1.35\n/** Seconds the body lies after it lands: this plus the landing speed times `STAND_UP_PER_SPEED`. */\nconst LIE_SECONDS = 1.6\n/** Seconds a body floored by its wounds (inside the lethal radius) lies before it gets up, when it is not dead. */\nconst WOUNDED_LIE_SECONDS = 6\n/** The share of the blast that has to get through for its fragments to reach a body: behind cover that stops half of it the fragments stop too. */\nexport const FRAGMENT_COVER = 0.5\n/** Seconds a body is stunned at the edge of the lethal radius (the ringing ears, the dizziness); it falls with the square of the way to the edge of the blast. */\nconst STUN_SECONDS = 4\nconst STAND_UP_PER_SPEED = 0.25\nconst FALL_SECONDS = 0.25\nconst RISE_SECONDS = 0.6\n/** m/s squared that slows a body sliding on the ground. */\nconst GROUND_FRICTION = 7\nconst GRAVITY = 9.81\n/** Height of the chest above the feet: where the blast takes the body. */\nexport const KNOCK_CHEST = 1.1\nconst CHEST = KNOCK_CHEST\n\n/** What a blast is doing to one body: its velocity (m/s), whether it is in the air, and how far down it is (0 on its feet, 1 lying). */\nexport interface Knock {\n velocity: [number, number, number]\n airborne: boolean\n /** 0..1 */\n lying: number\n /** Seconds left to lie on the ground after it stopped. */\n lieLeft: number\n /** Knocked off its feet by this blast. */\n down: boolean\n /** Floored by its wounds: inside the lethal radius and not behind cover. It lies longer and falls where it stood. */\n wounded?: boolean\n /** Seconds of stun the blast gave the body: dizziness and ringing ears. */\n stunSeconds?: number\n /** The horizontal unit direction away from the blast, even when the push is too weak to move the body: the way it falls. */\n away?: Vec3\n}\n\n/** The mass of a body in kg from its height (m) and its build (1 = average): 70 kg at 1.75 m. */\nexport const bodyMass = (height: number, build = 1): number => 70 * (height / 1.75) ** 2 * build\n\n/**\n * What a blast does to a body standing at `feet`, of `mass` kg. Undefined when the blast does not reach it or does nothing to it.\n *\n * The body is pushed by the blast wave (`blastBodySpeed`, from the charge `blast.tnt`, or from `energy` by `tntPerJoule`), taken at the chest and cut by `reach`, the share that got through\n * cover. It is floored when that push is `KNOCK_DOWN_SPEED` or more, or when it stands inside `lethalRadius` with the blast reaching it (the fragments and the overpressure fell it where it\n * stands). It is stunned for a while inside the radius. `options` are the shove numbers of the host's loose items; only their `minSpeed` applies to a body.\n */\nexport function knockOf(blast: BlastSource, feet: Vec3, mass: number, options?: BlastShoveOptions, impulse: BlastImpulseOptions = {}): Knock | undefined {\n const o = { ...DEFAULT_BLAST_IMPULSE, ...impulse }\n const chest: Vec3 = [feet[0], feet[1] + CHEST, feet[2]]\n const dx = chest[0] - blast.point[0]\n const dy = chest[1] - blast.point[1]\n const dz = chest[2] - blast.point[2]\n const distance = Math.hypot(dx, dy, dz)\n if (!(blast.radius > 0) || distance > blast.radius) return undefined\n const reach = Math.max(0, Math.min(1, blast.reach ?? 1))\n const tnt = blast.tnt ?? blast.energy * o.tntPerJoule\n let speed = blastBodySpeed(tnt, distance, mass, reach, o)\n const minSpeed = impulse.minSpeed ?? options?.minSpeed ?? o.minSpeed\n if (speed < minSpeed) speed = 0\n const struck = reach >= FRAGMENT_COVER\n const wounded = struck && blast.lethalRadius !== undefined && distance <= blast.lethalRadius\n const edge = 1 - distance / blast.radius\n const stunSeconds = struck ? STUN_SECONDS * edge * edge * reach : 0\n if (speed === 0 && !wounded && stunSeconds < 0.25) return undefined\n // straight above or below the centre there is no \"away\": the push goes up\n const horizontal = Math.hypot(dx, dz)\n const away: Vec3 = horizontal > 1e-4 ? [dx / horizontal, o.lift, dz / horizontal] : [0, 1, 0]\n const length = Math.hypot(away[0], away[1], away[2])\n const velocity: [number, number, number] = [(away[0] / length) * speed, (away[1] / length) * speed, (away[2] / length) * speed]\n return {\n velocity,\n airborne: velocity[1] > 0.5,\n lying: 0,\n lieLeft: wounded ? WOUNDED_LIE_SECONDS : 0,\n away: horizontal > 1e-4 ? [dx / horizontal, 0, dz / horizontal] : [0, 0, 0],\n down: speed >= KNOCK_DOWN_SPEED || wounded,\n ...(wounded ? { wounded } : {}),\n ...(stunSeconds >= 0.25 ? { stunSeconds } : {}),\n }\n}\n\n/**\n * Moves a body one step: it slides on the ground and slows, falls when it was knocked down, lies a while and gets up (a dead body stays down). Moves `position` in place; returns true when the\n * body is on its feet and still, and the knock can be dropped.\n */\nexport function stepKnock(knock: Knock, position: { x: number; y: number; z: number }, dt: number, groundAt: (x: number, z: number) => number, alive: boolean): boolean {\n const v = knock.velocity\n position.x += v[0] * dt\n position.z += v[2] * dt\n const ground = groundAt(position.x, position.z)\n if (knock.airborne || position.y > ground + 1e-4) {\n v[1] -= GRAVITY * dt\n position.y += v[1] * dt\n if (position.y <= ground) {\n position.y = ground\n v[1] = 0\n knock.airborne = false\n }\n } else position.y = ground\n const horizontal = Math.hypot(v[0], v[2])\n if (!knock.airborne && horizontal > 0) {\n const slowed = Math.max(0, horizontal - GROUND_FRICTION * dt)\n v[0] *= slowed / horizontal\n v[2] *= slowed / horizontal\n }\n const moving = horizontal > 0.05 || knock.airborne\n if (knock.down) {\n // a body floored where it stands (its wounds, not the push) still takes the time of a fall to reach the ground\n if (moving || knock.lying < 1) knock.lying = Math.min(1, knock.lying + dt / FALL_SECONDS)\n else if (alive) {\n knock.lieLeft = knock.lieLeft > 0 ? knock.lieLeft : LIE_SECONDS + Math.hypot(v[0], v[2]) * STAND_UP_PER_SPEED\n knock.lieLeft -= dt\n if (knock.lieLeft <= 0) knock.down = false\n }\n } else if (knock.lying > 0) knock.lying = Math.max(0, knock.lying - dt / RISE_SECONDS)\n return !moving && !knock.down && knock.lying === 0\n}\n\n/** Where `launchBody` leaves the throw on the body's root until the module that animates it takes it. */\nconst LAUNCHED = 'launched'\n\n/**\n * Throws a body at `velocity` (m/s, world): an occupant leaving a car through the glass, a pedestrian run over. The module that animates the body\n * takes it on its next frame (`takeLaunch`) and moves it as it moves a body a blast knocked down: flying, landing, sliding, lying, getting up.\n */\nexport function launchBody(root: { userData: Record<string, unknown> }, velocity: Vec3): void {\n root.userData[LAUNCHED] = [velocity[0], velocity[1], velocity[2]]\n}\n\n/** The throw `launchBody` left on the root, as a knock (knocked down, in the air), once; undefined when nothing threw it. */\nexport function takeLaunch(root: { userData: Record<string, unknown> }): Knock | undefined {\n const velocity = root.userData[LAUNCHED] as Vec3 | undefined\n if (!velocity) return undefined\n delete root.userData[LAUNCHED]\n return { velocity: [velocity[0], velocity[1], velocity[2]], airborne: true, lying: 0, lieLeft: 0, down: true }\n}\n","export interface CostOptions {\n /** Reads of `state()` and of `describe()` to time. Default 10 000. */\n reads?: number\n /** Ceiling, in milliseconds, for all the reads of one method. Default 50 (5 microseconds per read). */\n ceilingMs?: number\n /** Ceiling of heap growth, in bytes, over all the reads of one method (only where `process.memoryUsage` exists). Default 1 MiB. */\n allocationBytes?: number\n}\n\nconst DEFAULT_COST_READS = 10_000\nconst DEFAULT_COST_CEILING_MS = 50\nconst DEFAULT_COST_ALLOCATION_BYTES = 1024 * 1024\n\nconst heapUsedBytes = (): number | undefined => {\n const nodeProcess = (globalThis as { process?: { memoryUsage?: () => { heapUsed: number } } }).process\n return nodeProcess?.memoryUsage?.().heapUsed\n}\n\ntype CheckReadCostParams = { readonly name: string; readonly read: () => unknown; readonly options: CostOptions; readonly problems: string[] }\n\nexport const checkReadCost = ({ name, read, options, problems }: CheckReadCostParams): void => {\n const reads = options.reads ?? DEFAULT_COST_READS\n const ceilingMs = options.ceilingMs ?? DEFAULT_COST_CEILING_MS\n const allocationBytes = options.allocationBytes ?? DEFAULT_COST_ALLOCATION_BYTES\n if (read() !== read()) problems.push(`${name}() returns a new object on each read: cache it between changes (cost contract)`)\n // a first pass lets the engine compile and optimise the loop: that code and its feedback are heap too, and would read as the method allocating\n for (let i = 0; i < reads; i++) read()\n const heapBefore = heapUsedBytes()\n const started = performance.now()\n for (let i = 0; i < reads; i++) read()\n const elapsedMs = performance.now() - started\n const heapAfter = heapUsedBytes()\n if (elapsedMs > ceilingMs) problems.push(`${name}() took ${elapsedMs.toFixed(1)} ms for ${reads} reads, over the ${ceilingMs} ms ceiling (cost contract)`)\n if (heapBefore !== undefined && heapAfter !== undefined && heapAfter - heapBefore > allocationBytes)\n problems.push(`${name}() grew the heap by ${heapAfter - heapBefore} bytes over ${reads} reads, over the ${allocationBytes} byte ceiling (cost contract)`)\n}\n","import { checkReadCost } from './impact-cost'\nimport type { CostOptions } from './impact-cost'\nimport type { DamageType, DestructibleSpec, DestructionEvent, Hit, JointSpec, ObjectState, ToolKind, Vec3 } from './destruction'\n\n/**\n * What a game module implements for an object whose behaviour it owns (a vehicle with its own panel deformation, a creature, a machine). The\n * world (`DestructionWorld.register`) hosts it next to its own objects: raycast, shoot, explode, hit, disassemble, extinguish, update, state,\n * reset and `on` all route to it.\n *\n * Space: the impl receives hits in WORLD space and returns events in WORLD space (it owns its transform; the transform given to `register` or\n * `setTransform` is only used by the world for raycasts and blast range). Every event must carry `object === id` and name parts listed by\n * `describe()`.\n */\n/** World-space ray for `DestructibleImpl.surfaceRay`. */\nexport interface SurfaceRay {\n readonly origin: Vec3\n readonly direction: Vec3\n readonly maxDistance: number\n ignored?(part: string): boolean\n}\n\nexport interface SurfaceRayHit {\n readonly part: string\n readonly point: Vec3\n readonly normal: Vec3\n readonly distance: number\n /** Thickness the ray crosses in this part, metres. */\n readonly pathLen: number\n}\n\nexport interface DestructibleImpl {\n readonly id: string\n readonly name?: string\n /**\n * Parts, joints and structure in the impl's local space, for raycast, inspector and log.\n * COST CONTRACT (D8): called per frame by the renderer, so it is O(1), allocation-free and cached between changes: the same object comes\n * back until a hit, tool, fire or pose change alters it. `checkDestructible(impl, { cost: true })` measures it.\n */\n describe(): DestructibleSpec\n /** Apply one hit (see `IMPACT_CONTRACT` for the fields per type) and return the events it caused. */\n hit(hit: Hit): DestructionEvent[]\n /**\n * The real surface a ray meets, for an impl whose drawn shape is not its boxes (a car body): the boxes of `describe()` only say where to look, and a shot that crosses\n * them without touching the drawn surface goes on. `ignored` answers for the parts the round already went through. Null when the ray meets nothing.\n */\n surfaceRay?(ray: SurfaceRay): SurfaceRayHit | null\n /** Advance fire, melting, collapse and other timed behaviour. */\n update?(dt: number): DestructionEvent[]\n /**\n * COST CONTRACT (D8): the host reads it per part per frame, so it is O(1), allocation-free and cached between changes (see `describe()`).\n * The cached object is shared with every reader: treat it as read-only, copy before changing it.\n */\n state(): Readonly<ObjectState>\n reset?(): void\n /** The world took the object out (`DestructionWorld.remove`): the impl lets go of what it joined (shared queues, listeners). */\n dispose?(): void\n /** Same contract as `DestructionWorld.disassemble`: progress 0..1, or -1 when the tool cannot undo the joint. */\n disassemble?(jointId: string, tool: ToolKind, dt: number): number\n extinguish?(partId?: string): void\n /** Optional: an animation moved a part; `position` / `rotation` (Euler XYZ, radians) are object-local. Keep raycast and hit shapes in sync. */\n setPartPose?(partId: string, position: Vec3, rotation?: Vec3): void\n /** Plain JSON of the impl's own state, stored in `DestructionSnapshot.impls`. */\n serialize?(): unknown\n load?(data: unknown): void\n}\n\n/** The fields a weapon or hazard must put in a `Hit`, per damage type. THE impact contract. */\nexport interface ImpactContract {\n required: string[]\n optional: string[]\n note: string\n}\n\nexport const IMPACT_CONTRACT: Record<DamageType, ImpactContract> = {\n ballistic: { required: ['type', 'point', 'direction', 'energy', 'caliber'], optional: ['space', 'source'], note: 'bullets and pellets: joules and diameter in millimetres' },\n blunt: { required: ['type', 'point', 'energy'], optional: ['direction', 'radius', 'space', 'source'], note: 'contact radius in metres (default 3 cm)' },\n slash: { required: ['type', 'point', 'energy'], optional: ['direction', 'radius', 'space', 'source'], note: 'axes, knives, machetes' },\n explosive: { required: ['type', 'point', 'energy', 'radius'], optional: ['lethalRadius', 'space', 'source'], note: 'point is the centre of the blast, radius the range in metres' },\n fire: { required: ['type', 'point', 'temperature', 'radius'], optional: ['duration', 'space', 'source'], note: 'flame temperature in °C; duration in seconds (call every frame with `dt`)' },\n heat: { required: ['type', 'point', 'temperature', 'radius'], optional: ['duration', 'space', 'source'], note: 'heat without flame (cutting torch, hot plate)' },\n}\n\n/** Problems with a hit according to the impact contract (empty when it is valid). */\nexport const checkHit = (hit: Hit): string[] => {\n const c = IMPACT_CONTRACT[hit.type]\n if (!c) return [`unknown damage type \"${String(hit.type)}\"`]\n const out: string[] = []\n for (const f of c.required) {\n const v = (hit as unknown as Record<string, unknown>)[f]\n if (v === undefined || (Array.isArray(v) ? v.some((n) => !Number.isFinite(n)) : typeof v === 'number' && !Number.isFinite(v))) out.push(`${hit.type} hit needs \"${f}\"`)\n }\n return out\n}\n\nexport interface CheckOptions {\n /**\n * Ids of every material the impl may use (the contracts package does not know the materials of your world: pass the ids of\n * `world.materials`, or the built-in plus your own). When omitted, the material check is skipped.\n */\n materials?: Iterable<string>\n /** Opt-in cost measurement (D8): `true` for the defaults, or the limits to use. */\n cost?: boolean | CostOptions\n}\n\nconst finiteJson = (v: unknown, path: string, out: string[]) => {\n if (typeof v === 'number') {\n if (!Number.isFinite(v)) out.push(`${path} is not a finite number`)\n } else if (Array.isArray(v)) v.forEach((x, i) => finiteJson(x, `${path}[${i}]`, out))\n else if (v && typeof v === 'object') for (const [k, x] of Object.entries(v)) finiteJson(x, `${path}.${k}`, out)\n else if (typeof v === 'function' || typeof v === 'symbol' || typeof v === 'bigint') out.push(`${path} is not JSON`)\n}\n\n/**\n * Contract checker for the test room (it needs no world): `describe()` is valid, a 500 J ballistic hit at a part centre returns well-formed events of this impl, `state()`\n * matches `describe()`, hit points never go negative, `update(0.016)` returns an array and every event is JSON-serializable. It hits the impl\n * (and calls `reset()` afterwards when there is one). With `options.cost` it also times `state()` and `describe()` (the cost contract of D8).\n */\nexport const checkDestructible = (impl: DestructibleImpl, options: CheckOptions = {}): { ok: boolean; problems: string[] } => {\n const problems: string[] = []\n const known = options.materials ? new Set(options.materials) : null\n let spec: DestructibleSpec | undefined\n try {\n spec = impl.describe()\n } catch (e) {\n return { ok: false, problems: [`describe() threw: ${String(e)}`] }\n }\n if (!spec || !Array.isArray(spec.parts) || spec.parts.length === 0) return { ok: false, problems: ['describe() must return at least one part'] }\n const ids = new Set<string>()\n for (const p of spec.parts) {\n if (ids.has(p.id)) problems.push(`duplicate part id \"${p.id}\"`)\n ids.add(p.id)\n if (known && !known.has(p.material)) problems.push(`part \"${p.id}\" has unknown material \"${p.material}\"`)\n if (!p.shape || !['box', 'cylinder', 'sphere'].includes(p.shape.kind)) problems.push(`part \"${p.id}\" has no valid shape`)\n if (!Array.isArray(p.position) || p.position.length !== 3) problems.push(`part \"${p.id}\" has no position`)\n }\n const jointIds = new Set<string>()\n for (const j of (spec.joints ?? []) as JointSpec[]) {\n if (!ids.has(j.a) || !ids.has(j.b)) problems.push(`joint \"${j.id ?? `${j.a}~${j.b}`}\" references an unknown part`)\n const jid = j.id ?? `${j.a}~${j.b}`\n if (jointIds.has(jid)) problems.push(`duplicate joint id \"${jid}\"`)\n jointIds.add(jid)\n }\n const checkState = (label: string) => {\n let st: ObjectState\n try {\n st = impl.state()\n } catch (e) {\n problems.push(`state() threw ${label}: ${String(e)}`)\n return\n }\n const sids = st.parts.map((p) => p.id).sort().join(',')\n if (sids !== [...ids].sort().join(',')) problems.push(`state() part ids do not match describe() ${label}`)\n for (const p of st.parts) if (!(p.hp >= 0)) problems.push(`part \"${p.id}\" has negative or invalid hp ${label}`)\n }\n const checkEvents = (events: unknown, label: string) => {\n if (!Array.isArray(events)) {\n problems.push(`${label} must return an array`)\n return\n }\n const evs = events as DestructionEvent[]\n evs.forEach((e, i) => {\n if (e.object !== impl.id) problems.push(`${label} event ${i} (${e.type}) has object \"${String(e.object)}\", expected \"${impl.id}\"`)\n const part = (e as { part?: string }).part\n if (part !== undefined && !ids.has(part)) problems.push(`${label} event ${i} (${e.type}) names unknown part \"${part}\"`)\n })\n const bad: string[] = []\n finiteJson(evs, 'events', bad)\n problems.push(...bad.map((b) => `${label}: ${b}`))\n try {\n const s = JSON.stringify(evs)\n if (JSON.stringify(JSON.parse(s)) !== s) problems.push(`${label} events do not survive a JSON round trip`)\n } catch (e) {\n problems.push(`${label} events are not JSON-serializable: ${String(e)}`)\n }\n }\n checkState('before the hit')\n const target = spec.parts[0]\n try {\n const hit: Hit = { type: 'ballistic', point: [target.position[0], target.position[1], target.position[2]], direction: [0, 0, -1], energy: 500, caliber: 9 }\n checkEvents(impl.hit(hit), 'hit()')\n } catch (e) {\n problems.push(`hit() threw: ${String(e)}`)\n }\n checkState('after the hit')\n if (impl.update) {\n try {\n checkEvents(impl.update(0.016), 'update()')\n } catch (e) {\n problems.push(`update() threw: ${String(e)}`)\n }\n checkState('after update')\n }\n try {\n impl.reset?.()\n } catch (e) {\n problems.push(`reset() threw: ${String(e)}`)\n }\n if (options.cost) {\n const cost = options.cost === true ? {} : options.cost\n try {\n checkReadCost({ name: 'state', read: () => impl.state(), options: cost, problems })\n checkReadCost({ name: 'describe', read: () => impl.describe(), options: cost, problems })\n } catch (e) {\n problems.push(`cost measurement threw: ${String(e)}`)\n }\n }\n return { ok: problems.length === 0, problems }\n}\n","/** The slots of a body an item can cover (the item slot vocabulary: game-items imports it from here). */\nexport type BodySlot = 'head' | 'eyes' | 'torso' | 'arms' | 'hands' | 'legs' | 'feet' | 'over'\n\n/** The 11 segments of the detailed body model (the Arsenal ids). */\nexport type SegmentKind = 'head' | 'jaw' | 'neck' | 'torso' | 'pelvis' | 'upperArm' | 'forearm' | 'hand' | 'thigh' | 'shin' | 'foot'\n\n/** The 6 regions of the crowd model (the game regions 0..5). */\nexport type BodyRegion = 'head' | 'torso' | 'armL' | 'armR' | 'legL' | 'legR'\n\n/** Bits of the `missing` mask of a body. The instanced body shader reads them, so the values are part of the contract. */\nexport const MISSING_BITS = {\n head: 1,\n armL: 2,\n armR: 4,\n legL: 8,\n legR: 16,\n armLBroken: 32,\n armRBroken: 64,\n legLBroken: 128,\n legRBroken: 256,\n jaw: 512,\n} as const\n","import type { DestructibleSpec, PartSpec } from './destruction'\n\nexport interface CheckSpecOptions {\n /** Known material ids. When given, every part's material must be one of them. */\n materials?: Iterable<string>\n}\n\nexport interface SpecReport {\n /** False when there is at least one problem (warnings do not count). */\n ok: boolean\n problems: string[]\n /** Things that are legal but probably wrong, e.g. structural parts that cannot reach an anchored part. */\n warnings: string[]\n}\n\nconst STRUCTURE_ROLES = ['foundation', 'column', 'beam', 'slab', 'wall', 'partition', 'roof', 'stair', 'none']\nconst BEARS = [true, false, 'a_on_b', 'b_on_a']\n\nconst finite = (n: unknown): n is number => typeof n === 'number' && Number.isFinite(n)\nconst positive = (n: unknown): boolean => finite(n) && n > 0\nconst vec3 = (v: unknown): boolean => Array.isArray(v) && v.length === 3 && v.every(finite)\nconst nonNegative = (n: unknown): boolean => n === undefined || (finite(n) && n >= 0)\n\nconst FACES = ['both', '+', '-']\nconst BONDS = ['running', 'stack']\n\n/** Layers, reinforcement, units and breach: known materials, finite positive sizes. */\nconst checkStagedFields = (p: PartSpec | undefined, label: string, materials: Set<string> | null, problems: string[]): void => {\n const known = (m: unknown, what: string): void => {\n if (materials && !materials.has(m as string)) problems.push(`${label} ${what} has unknown material \"${String(m)}\"`)\n }\n ;(p?.layers ?? []).forEach((l, i) => {\n known(l?.material, `layer ${i}`)\n if (!positive(l?.thickness)) problems.push(`${label} layer ${i} thickness must be finite and positive`)\n if (!FACES.includes(l?.faces)) problems.push(`${label} layer ${i} has an invalid faces \"${String(l?.faces)}\"`)\n })\n const r = p?.reinforcement\n if (r) {\n known(r.material, 'reinforcement')\n if (!positive(r.spacing) || !positive(r.diameter)) problems.push(`${label} reinforcement spacing and diameter must be finite and positive`)\n if (r.residualCapacity !== undefined && !(finite(r.residualCapacity) && r.residualCapacity >= 0 && r.residualCapacity <= 1)) problems.push(`${label} reinforcement residualCapacity must be between 0 and 1`)\n }\n const u = p?.units\n if (u) {\n known(u.mortar, 'units mortar')\n if (!vec3(u.size) || !u.size.every(positive)) problems.push(`${label} units size must be three finite positive numbers`)\n if (!BONDS.includes(u.bond)) problems.push(`${label} units has an invalid bond \"${String(u.bond)}\"`)\n }\n if (p?.breach && p.breach.area !== undefined && !positive(p.breach.area)) problems.push(`${label} breach area must be finite and positive`)\n}\n\n/**\n * Static checks on a `DestructibleSpec` (no world needed), for items that give a `spec` instead of an `impl`: unique part ids, known materials\n * (when `options.materials` is given), joints that reference existing, different parts, finite positive sizes, non-negative mass / hp / capacity,\n * valid structure roles and `bears` directions. A structural part with no path of joints to an anchored part is a warning, not a problem: it\n * is allowed, it just falls.\n */\nexport const checkSpec = (spec: DestructibleSpec, options: CheckSpecOptions = {}): SpecReport => {\n const problems: string[] = []\n const warnings: string[] = []\n const materials = options.materials ? new Set(options.materials) : null\n const parts = Array.isArray(spec?.parts) ? spec.parts : []\n if (!Array.isArray(spec?.parts) || parts.length === 0) problems.push('the spec needs at least one part')\n const ids = new Set<string>()\n for (const p of parts) {\n const id = String(p?.id ?? '')\n const label = `part \"${id}\"`\n if (id === '') problems.push('a part has no id')\n else if (ids.has(id)) problems.push(`duplicate part id \"${id}\"`)\n ids.add(id)\n if (materials && !materials.has(p?.material)) problems.push(`${label} has unknown material \"${String(p?.material)}\"`)\n else if (typeof p?.material !== 'string' || p.material === '') problems.push(`${label} needs a material`)\n const s = p?.shape\n if (s?.kind === 'box') {\n if (!Array.isArray(s.size) || s.size.length !== 3 || !s.size.every(positive)) problems.push(`${label} box size must be three finite positive numbers`)\n } else if (s?.kind === 'cylinder') {\n if (!positive(s.radius) || !positive(s.height)) problems.push(`${label} cylinder radius and height must be finite and positive`)\n } else if (s?.kind === 'sphere') {\n if (!positive(s.radius)) problems.push(`${label} sphere radius must be finite and positive`)\n } else problems.push(`${label} has an unknown shape`)\n if (!vec3(p?.position)) problems.push(`${label} needs a finite position [x, y, z]`)\n if (p?.rotation !== undefined && !vec3(p.rotation)) problems.push(`${label} has an invalid rotation`)\n for (const f of ['mass', 'hp', 'thickness'] as const) if (!nonNegative(p?.[f])) problems.push(`${label} ${f} must be a finite number >= 0`)\n if (p?.fill !== undefined && !(finite(p.fill) && p.fill >= 0 && p.fill <= 1)) problems.push(`${label} fill must be between 0 and 1`)\n if (p?.structure) {\n if (!STRUCTURE_ROLES.includes(p.structure.role)) problems.push(`${label} has an invalid structure role \"${String(p.structure.role)}\"`)\n if (!nonNegative(p.structure.capacity)) problems.push(`${label} structure capacity must be a finite number >= 0`)\n }\n if (p?.contains && !nonNegative(p.contains.liters)) problems.push(`${label} contains.liters must be a finite number >= 0`)\n checkStagedFields(p, label, materials, problems)\n }\n\n const jointIds = new Set<string>()\n const edges: Array<[string, string]> = []\n for (const j of spec?.joints ?? []) {\n const id = j.id ?? `${j.a}~${j.b}`\n const label = `joint \"${id}\"`\n if (jointIds.has(id)) problems.push(`duplicate joint id \"${id}\"`)\n jointIds.add(id)\n if (!ids.has(j.a)) problems.push(`${label} references unknown part \"${String(j.a)}\"`)\n if (!ids.has(j.b)) problems.push(`${label} references unknown part \"${String(j.b)}\"`)\n if (j.a === j.b) problems.push(`${label} joins part \"${String(j.a)}\" to itself`)\n if (j.bears !== undefined && !BEARS.includes(j.bears)) problems.push(`${label} has an invalid bears \"${String(j.bears)}\"`)\n if (!nonNegative(j.strength)) problems.push(`${label} strength must be a finite number >= 0`)\n if (!nonNegative(j.seconds)) problems.push(`${label} seconds must be a finite number >= 0`)\n if (j.bears !== false && ids.has(j.a) && ids.has(j.b) && j.a !== j.b) edges.push([j.a, j.b])\n }\n\n // structural parts must reach an anchored part over the joints (only when the spec lists its joints: otherwise they are inferred later)\n const structural = parts.filter((p) => p?.structure && p.structure.role !== 'none')\n if (structural.length && spec.joints) {\n const anchored = parts.filter((p) => p.anchored || p.structure?.role === 'foundation').map((p) => p.id)\n const adj = new Map<string, string[]>()\n for (const [a, b] of edges) {\n adj.set(a, [...(adj.get(a) ?? []), b])\n adj.set(b, [...(adj.get(b) ?? []), a])\n }\n const seen = new Set(anchored)\n const queue = [...anchored]\n while (queue.length) {\n for (const n of adj.get(queue.pop()!) ?? []) {\n if (seen.has(n)) continue\n seen.add(n)\n queue.push(n)\n }\n }\n const lost = structural.filter((p) => !seen.has(p.id)).map((p) => p.id)\n if (lost.length) warnings.push(`structural part(s) ${lost.map((i) => `\"${i}\"`).join(', ')} cannot reach an anchored part, so they will fall`)\n }\n return { ok: problems.length === 0, problems, warnings }\n}\n","import type { BallisticExtras, Hit, HitIgnition, Vec3 } from './destruction'\n\n/**\n * A ballistic shot (one per pellet) as a weapon module produces it. Plain data: any damage model can consume it (`shotToHit` makes the `Hit`).\n */\nexport interface Shot {\n origin: Vec3\n /** Unit vector. */\n direction: Vec3\n /** Muzzle energy in joules. */\n energy: number\n /** Bullet diameter in millimetres. */\n caliber: number\n /** snake_case calibre id, e.g. `9x19`. */\n caliberId: string\n /** Muzzle speed in m/s. */\n speed: number\n /** Projectile mass in grams (one pellet for a shotgun load). */\n mass?: number\n /** Energy at a distance in metres, with the calibre's drag. */\n energyAt(distance: number): number\n pellet: number\n /** snake_case weapon id. */\n weapon: string\n source?: string\n /** Kind of the round that was loaded (`fmj`, `hp`, `ap`, `buck`, `slug`, `incendiary`, `tracer`...). */\n ammo?: string\n /** Millimetres of steel, concrete, brick and wood the round goes through at the muzzle (the ammo table of the weapons module). `shotToHit` scales it by the energy left. */\n penetration?: Readonly<Record<string, number>>\n incendiary?: boolean\n tracer?: boolean\n /** Chances that the round lights flammable vapour or fuel. */\n ignition?: HitIgnition\n}\n\n/**\n * The fields of the round that a damage model takes besides the geometry: the penetration by material scaled by the energy that is left at `distance`\n * (linear in the energy: half the energy goes through half as much), the incendiary and tracer flags and the ignition chances. Pass it as the sixth\n * argument of `DestructionWorld.shoot` or spread it in a `Hit`.\n */\nexport const shotExtras = (shot: Shot, distance = 0): BallisticExtras => {\n const out: { -readonly [K in keyof BallisticExtras]: BallisticExtras[K] } = {}\n if (shot.penetration) {\n const k = shot.energy > 0 ? Math.min(1, Math.max(0, shot.energyAt(distance) / shot.energy)) : 1\n const scaled: Record<string, number> = {}\n for (const key of Object.keys(shot.penetration)) scaled[key] = shot.penetration[key]! * k\n out.penetration = scaled\n }\n if (shot.incendiary) out.incendiary = true\n if (shot.tracer) out.tracer = true\n if (shot.ignition) out.ignition = { spark: shot.ignition.spark, flame: shot.ignition.flame }\n return out\n}\n\n/** One use of a non-firearm (melee swing, flame tick, heat tick, throw, tool tick, extinguisher tick). */\nexport type Use =\n | { kind: 'melee'; damage: 'blunt' | 'slash'; energy: number; radius: number; reach: number; origin: Vec3; direction: Vec3 }\n | { kind: 'flame'; temperature: number; radius: number; range: number; cone: boolean; duration: number; origin: Vec3; direction: Vec3 }\n | { kind: 'heat'; temperature: number; radius: number; range: number; duration: number; origin: Vec3; direction: Vec3 }\n | {\n kind: 'throw'\n energy: number\n radius: number\n fuse: number\n velocity: Vec3\n origin: Vec3\n fire?: { temperature: number; radius: number; seconds: number }\n /** Explosive id (`m67`, `molotov`, `pg7v`...): the host looks the numbers up in the arsenal. Absent on a plain thrown object. */\n explosive?: string\n /** What sets it off: `timer` after `fuse` seconds, `impact` on the first hit once armed. */\n trigger?: 'timer' | 'impact'\n /** Metres the projectile must fly before an impact sets it off; closer, it hits without exploding (rocket launchers). */\n armDistance?: number\n /** Radius in metres of the wounding area (`radius` is the lethal one). */\n injuryRadius?: number\n /** Seconds the blast stuns whoever is inside the injury radius (flashbang). */\n stunSeconds?: number\n /** Seconds the thrower held it after pulling the pin: `fuse` is what was left of the full fuse when it left the hand. */\n cooked?: number\n /** Went off in the thrower's hand (held past the fuse): no velocity, `fuse` is 0 and the blast is at `origin`. */\n inHand?: boolean\n }\n /** An explosive set down where the trigger is pulled (mine, C4, charge): it waits for its trigger instead of flying. */\n | {\n kind: 'place'\n explosive: string\n /** `wire`: something trips it; `remote`: the player fires it; `timer`: after `fuse` seconds. */\n triggers: readonly ('timer' | 'wire' | 'remote')[]\n energy: number\n /** Lethal radius in metres. */\n radius: number\n injuryRadius: number\n /** Cone the blast goes through, in front of `direction` (mine); absent for a blast in every direction. */\n cone?: { halfAngleDeg: number; length: number }\n /** The wire a `wire` charge stretches from where it is set down: metres out, degrees from its facing (counter-clockwise from above) and metres above it. */\n tripwire?: { length: number; angleDeg: number; height: number }\n fuse?: number\n origin: Vec3\n direction: Vec3\n }\n /** The blast behind a rocket launcher: dangerous inside `radius` metres, in the cone around `direction` (pointing away from the target). */\n | { kind: 'backblast'; radius: number; halfAngleDeg: number; origin: Vec3; direction: Vec3 }\n | { kind: 'tool'; tool: string; reach: number; duration: number; origin: Vec3; direction: Vec3 }\n | { kind: 'extinguish'; range: number; coolC: number; duration: number; origin: Vec3; direction: Vec3 }\n | { kind: 'hand'; reach: number; origin: Vec3; direction: Vec3 }\n\n/**\n * The `Hit` a ballistic shot makes at `point` (world space): the energy is what is left of the shot at that distance from its origin\n * (`shot.energyAt`), the direction and calibre come from the shot. `normal` (the surface normal at the point, when the caller knows it) does not\n * change the hit; it is accepted so that callers can pass what their raycast returned and so that a stricter implementation could use it.\n */\nexport const shotToHit = (shot: Shot, point: Vec3, normal?: Vec3): Hit => {\n void normal\n const dx = point[0] - shot.origin[0]\n const dy = point[1] - shot.origin[1]\n const dz = point[2] - shot.origin[2]\n const distance = Math.hypot(dx, dy, dz)\n return {\n type: 'ballistic',\n point: [point[0], point[1], point[2]],\n direction: [shot.direction[0], shot.direction[1], shot.direction[2]],\n energy: shot.energyAt(distance),\n caliber: shot.caliber,\n ...(shot.mass === undefined ? {} : { mass: shot.mass }),\n ...shotExtras(shot, distance),\n source: shot.source ?? shot.weapon,\n }\n}\n\n/**\n * The `Hit` a non-firearm `Use` makes at `point` (world space), or `null` for uses that do not damage by themselves.\n *\n * - `melee` -> `blunt` / `slash` with the swing's `energy`, contact `radius` and `direction`;\n * - `flame` -> `fire` and `heat` -> `heat`, with `temperature`, `duration` and a contact radius of at most 0.3 m (one ray of the cone);\n * - `throw` -> the blast (`explosive`, `energy`, `radius`, centred on `point`, the place it detonates). A thrown item with no blast energy but\n * a `fire` patch (a molotov) gives that patch as a `fire` hit (`temperature`, `radius`, `seconds` as `duration`). No energy and no fire -> `null`;\n * - `tool`, `extinguish` and `hand` -> `null` (tools work on joints, the extinguisher cools, the hand only grabs).\n *\n * `normal` does not change the hit (same as `shotToHit`).\n */\nexport const useToHit = (use: Use, point: Vec3, normal?: Vec3): Hit | null => {\n void normal\n const p: Vec3 = [point[0], point[1], point[2]]\n switch (use.kind) {\n case 'melee':\n return { type: use.damage, point: p, direction: [use.direction[0], use.direction[1], use.direction[2]], energy: use.energy, radius: use.radius }\n case 'flame':\n case 'heat':\n return {\n type: use.kind === 'flame' ? 'fire' : 'heat',\n point: p,\n direction: [use.direction[0], use.direction[1], use.direction[2]],\n temperature: use.temperature,\n radius: Math.min(use.radius, 0.3),\n duration: use.duration,\n }\n case 'throw':\n if (use.energy > 0) return { type: 'explosive', point: p, energy: use.energy, radius: use.radius }\n if (use.fire) return { type: 'fire', point: p, temperature: use.fire.temperature, radius: use.fire.radius, duration: use.fire.seconds }\n return null\n default:\n return null\n }\n}\n","import type { Hit } from './destruction'\n\n/**\n * How a container of flammable fluid (a fuel tank, a jerrycan, a gas bottle) is set alight by what hits it. A normal bullet that goes through only\n * makes a leak; the leak lights when something supplies a flame: a spark of a hard round on metal, an incendiary or tracer round, a fire nearby\n * or a blast. These are the numbers of the target side; the numbers of the round (core, jacket, incendiary, tracer) are in the weapons module\n * (`rules.ignition` of `@adatechnology/game-weapons`) and reach the target in `Hit.ignition`. Every number is a default to be changed per game.\n */\nexport interface IgnitionRules {\n /**\n * How much of the spark and of the flame of a round reaches the vapour, by how full the container is: points `[filled, factor]`, `filled` 0..1\n * (litres over capacity), `factor` 0..1, linear between points. A full container holds a mixture too rich to burn (the vapour has no air) and\n * mostly leaks; a nearly empty one holds the vapour/air mixture of a flammable range and lights the easiest; a dry one has little vapour left.\n * Default: 0.6 empty, 1 at 10 percent, 0.6 at 35, 0.3 at 65, 0.15 at 90 and 0.12 full.\n */\n fillCurve: ReadonlyArray<readonly [number, number]>\n /** 0..1: how much of the fill factor also weighs on the flame of an incendiary or tracer (1: as much as on a spark; 0: not at all). Default 0.5. */\n flameFillInfluence: number\n /** Chance of the flame of a round that is flagged `incendiary` or `tracer` but carries no `ignition` numbers (a host that passes only the flags). */\n flameDefaults: { incendiary: number; tracer: number }\n /**\n * What a spark needs to strike: a factor 0..1 by material id of the struck part. A material that is not listed but is metal takes\n * `sparkMetalDefault`; a part that is not metal never sparks. Default: steel and iron 1, aluminium 0.25.\n */\n sparkTarget: Readonly<Record<string, number>>\n sparkMetalDefault: number\n /**\n * Factor 0..1 on the spark by fluid (default 1): diesel has a flash point of 52 degrees C and more, so a spark that lights petrol vapour does\n * not light it (a flame does). Default: diesel 0.15.\n */\n sparkFluid: Readonly<Record<string, number>>\n /** A leaking container of flammable fluid within `radius` metres of a burning part or a burning fuel stain lights at `chancePerSecond`. */\n fireNear: { radius: number; chancePerSecond: number }\n}\n\n/** Defaults of `IgnitionRules`. */\nexport const DEFAULT_IGNITION_RULES: IgnitionRules = {\n fillCurve: [\n [0, 0.6],\n [0.1, 1],\n [0.35, 0.6],\n [0.65, 0.3],\n [0.9, 0.15],\n [1, 0.12],\n ],\n flameFillInfluence: 0.5,\n flameDefaults: { incendiary: 0.7, tracer: 0.2 },\n sparkTarget: { steel: 1, iron: 1, aluminium: 0.25 },\n sparkMetalDefault: 0.5,\n sparkFluid: { diesel: 0.15 },\n fireNear: { radius: 1.5, chancePerSecond: 0.5 },\n}\n\n/** Merges a partial `IgnitionRules` over the defaults (an object is merged one level deep). */\nexport const resolveIgnitionRules = (rules?: Partial<IgnitionRules>): IgnitionRules => ({\n ...DEFAULT_IGNITION_RULES,\n ...rules,\n flameDefaults: { ...DEFAULT_IGNITION_RULES.flameDefaults, ...rules?.flameDefaults },\n sparkTarget: { ...DEFAULT_IGNITION_RULES.sparkTarget, ...rules?.sparkTarget },\n sparkFluid: { ...DEFAULT_IGNITION_RULES.sparkFluid, ...rules?.sparkFluid },\n fireNear: { ...DEFAULT_IGNITION_RULES.fireNear, ...rules?.fireNear },\n})\n\n/** Linear interpolation of the fill curve at `fill` (0..1); outside the points it keeps the first or the last. */\nexport const fillFactor = (fill: number, curve: IgnitionRules['fillCurve'] = DEFAULT_IGNITION_RULES.fillCurve): number => {\n if (curve.length === 0) return 1\n const f = Math.min(1, Math.max(0, fill))\n if (f <= curve[0]![0]) return curve[0]![1]\n for (let i = 1; i < curve.length; i++) {\n const [x1, y1] = curve[i]!\n const [x0, y0] = curve[i - 1]!\n if (f <= x1) return x1 === x0 ? y1 : y0 + ((y1 - y0) * (f - x0)) / (x1 - x0)\n }\n return curve[curve.length - 1]![1]\n}\n\n/** The factor of what a spark strikes: 0 when the struck part is not metal. */\nexport const sparkTargetFactor = (materialId: string, metal: boolean, rules: IgnitionRules = DEFAULT_IGNITION_RULES): number =>\n !metal ? 0 : rules.sparkTarget[materialId] ?? rules.sparkMetalDefault\n\n/**\n * Chance that one round that went through the wall of a container lights the fluid that leaks: the spark (only on metal) and the flame (any\n * wall), each weighed by the fill of the container; the two are independent. 0 when the round carries nothing (`Hit.ignition` absent and not\n * flagged `incendiary` or `tracer`): a plain bullet on a plain wall only leaks.\n */\nexport const ignitionChance = (hit: Pick<Hit, 'incendiary' | 'tracer' | 'ignition'>, target: { sparkTarget: number; fill: number; fluid?: string }, rules: IgnitionRules = DEFAULT_IGNITION_RULES): number => {\n const k = fillFactor(target.fill, rules.fillCurve)\n const spark = (hit.ignition?.spark ?? 0) * target.sparkTarget * (target.fluid === undefined ? 1 : rules.sparkFluid[target.fluid] ?? 1) * k\n const flameBase = hit.ignition?.flame ?? Math.max(hit.incendiary ? rules.flameDefaults.incendiary : 0, hit.tracer ? rules.flameDefaults.tracer : 0)\n const flame = flameBase * (1 - rules.flameFillInfluence * (1 - k))\n return 1 - (1 - Math.min(1, Math.max(0, spark))) * (1 - Math.min(1, Math.max(0, flame)))\n}\n","/**\n * The seal of a closed container (a food tin, a drink can, a jar, a bottle with a cap): what opens it and whether a breach spoils what is inside.\n * Plain data and one pure check, shared by the item rules (`resolveUse` of game-items) and any host that lets the player open and eat (the test room).\n */\nexport interface SealSpec {\n /** Ids of the tools that open it (items or weapons: `can_opener`, `knife`); any one of them is enough. */\n readonly openWith?: readonly string[]\n /** It opens by hand (a pull tab, a ring pull, a screw cap): no tool needed. */\n readonly byHand?: boolean\n /** A hole through the container or a leak spoils what is inside: it can no longer be eaten or drunk. */\n readonly spoilsWhenBreached?: boolean\n}\n\n/** The state of one container: opened, holed through (a shot, a blade) or leaking. */\nexport interface SealState {\n readonly opened?: boolean\n readonly breached?: boolean\n readonly leaked?: boolean\n}\n\n/** Why a sealed container cannot be eaten or drunk now. */\nexport type SealBlock = 'sealed' | 'breached' | 'leaked'\n\n/** Whether it can be opened with the tools at hand, and with which one (undefined when it opens by hand). */\nexport const sealOpener = (seal: SealSpec, tools: Iterable<string>): { readonly ok: boolean; readonly tool?: string } => {\n if (seal.byHand || !seal.openWith || seal.openWith.length === 0) return { ok: true }\n const have = new Set(tools)\n const tool = seal.openWith.find((id) => have.has(id))\n return tool === undefined ? { ok: false } : { ok: true, tool }\n}\n\n/** What stops eating or drinking it now, or undefined when nothing does. A spoiled container (breached, leaked) is blocked even once open. */\nexport const sealBlock = (seal: SealSpec, state: SealState): SealBlock | undefined => {\n if (seal.spoilsWhenBreached && state.breached) return 'breached'\n if (seal.spoilsWhenBreached && state.leaked) return 'leaked'\n if (!state.opened) return 'sealed'\n return undefined\n}\n","/** How a hand turns something round in strokes (a screw cap, a tap handle, a knob): what `twistStroke` answers for one moment of the turning. */\nexport interface TwistStroke {\n /** Radians the wrist is turned from the middle of its stroke, positive the way the part turns. */\n readonly angle: number\n /** Radians the part has turned in all, from 0 to `strokes * reach`. */\n readonly turned: number\n /** How closed the fingers are, 0 open to 1 closed: closed on the part while it turns, open while the wrist turns back. */\n readonly grip: number\n /** 0..1, how far the hand rises off the part while the wrist turns back (1 is `TWIST_LIFT` metres). */\n readonly lift: number\n}\n\nexport interface TwistOptions {\n /** Strokes of the wrist. Default 3. */\n readonly strokes?: number\n /** Radians the part turns in one stroke (a wrist turns about a quarter round with its hold). Default 1.6. */\n readonly reach?: number\n}\n\n/** Metres a hand rises off the part between two strokes. */\nexport const TWIST_LIFT = 0.012\n/** Share of each stroke spent turning the part; the rest turns the wrist back open. */\nconst TURNING = 0.7\n\nconst smooth = (k: number): number => k * k * (3 - 2 * k)\n\n/**\n * The hand turning a part round at `share` (0..1) of the whole turning: in each stroke the wrist turns with the part, then lets go, rises a little and turns back.\n * Pure and the same for every host, so first and third person turn the same hand the same way.\n */\nexport const twistStroke = (share: number, options: TwistOptions = {}): TwistStroke => {\n const strokes = Math.max(1, Math.round(options.strokes ?? 3))\n const reach = options.reach ?? 1.6\n const along = Math.min(1, Math.max(0, share)) * strokes\n const index = Math.min(strokes - 1, Math.floor(along))\n const phase = along - index\n if (phase <= TURNING) {\n const k = smooth(phase / TURNING)\n return { angle: reach * (k - 0.5), turned: reach * (index + k), grip: 1, lift: 0 }\n }\n const k = (phase - TURNING) / (1 - TURNING)\n return { angle: reach * (0.5 - smooth(k)), turned: reach * (index + 1), grip: 0.35, lift: Math.sin(k * Math.PI) }\n}\n","import type { Vec3 } from './destruction'\n\n/** Pose of one part (or of the whole item) at a key: position offset in metres and Euler XYZ rotation in radians, both relative to the rest pose. */\nexport interface AnimationPose {\n position?: Vec3\n rotation?: Vec3\n}\n\nexport interface AnimationKey {\n /** Seconds from the start of the clip. */\n t: number\n /** Pose of the item's root. */\n position?: Vec3\n rotation?: Vec3\n /** Poses of named parts: the key is a destruction part id (a pistol `reload` moves `mag`; a car door `open` rotates `door_l`). */\n parts?: Record<string, AnimationPose>\n}\n\nexport interface AnimationEventSpec {\n /** Seconds from the start of the clip. */\n t: number\n /** snake_case type the host maps (`sound`, `eject_casing`, `door_latch`...). */\n type: string\n data?: Record<string, unknown>\n /**\n * Seconds of real time the event fires before `t`, not stretched with the clip: a recorded sound whose hit comes that long after its start\n * (the click of a magazine seating) starts early so the hit lands on `t`. The player passes the lead it could apply as `data.lead`.\n */\n lead?: number\n}\n\n/** A use animation a module brings with its item. Poses between keys are interpolated by the player (the test room does it linearly). */\nexport interface AnimationClipSpec {\n /** snake_case: `reload`, `open`, `drink`... Unique within the item. */\n id: string\n /** Seconds. */\n duration: number\n keys: AnimationKey[]\n events?: AnimationEventSpec[]\n}\n\n/** What an item exposes when it brings its own animations. */\nexport interface ItemAnimationContract {\n animations: AnimationClipSpec[]\n}\n\nconst finite3 = (v: unknown): boolean => Array.isArray(v) && v.length === 3 && v.every((n) => typeof n === 'number' && Number.isFinite(n))\n\n/**\n * Problems with a clip (empty when it is valid): a snake_case id, a positive finite duration, at least one key, key and event times inside\n * `[0, duration]` and not going backwards, finite vectors, every named part one of `partIds`, event types that are non-empty strings and\n * JSON-serializable event data.\n */\nexport const checkAnimation = (clip: AnimationClipSpec, partIds: Iterable<string>): { ok: boolean; problems: string[] } => {\n const problems: string[] = []\n const parts = new Set(partIds)\n const id = String(clip?.id ?? '')\n if (!/^[a-z][a-z0-9_]*$/.test(id)) problems.push(`clip id \"${id}\" must be snake_case`)\n const label = `clip \"${id}\"`\n if (!(typeof clip?.duration === 'number' && Number.isFinite(clip.duration) && clip.duration > 0)) problems.push(`${label} needs a positive finite duration`)\n const dur = typeof clip?.duration === 'number' && clip.duration > 0 ? clip.duration : Infinity\n if (!Array.isArray(clip?.keys) || clip.keys.length === 0) {\n problems.push(`${label} needs at least one key`)\n return { ok: false, problems }\n }\n let prev = -Infinity\n clip.keys.forEach((k, i) => {\n if (!(typeof k.t === 'number' && Number.isFinite(k.t)) || k.t < 0 || k.t > dur + 1e-9) problems.push(`${label} key ${i} has t=${String(k.t)} outside 0..${dur}`)\n else if (k.t < prev) problems.push(`${label} key ${i} goes back in time (${k.t} < ${prev})`)\n else prev = k.t\n for (const f of ['position', 'rotation'] as const) if (k[f] !== undefined && !finite3(k[f])) problems.push(`${label} key ${i} has an invalid ${f}`)\n for (const [pid, pose] of Object.entries(k.parts ?? {})) {\n if (!parts.has(pid)) problems.push(`${label} key ${i} moves unknown part \"${pid}\"`)\n for (const f of ['position', 'rotation'] as const) if (pose?.[f] !== undefined && !finite3(pose[f])) problems.push(`${label} key ${i} part \"${pid}\" has an invalid ${f}`)\n }\n })\n let prevEv = -Infinity\n ;(clip.events ?? []).forEach((e, i) => {\n if (!(typeof e.t === 'number' && Number.isFinite(e.t)) || e.t < 0 || e.t > dur + 1e-9) problems.push(`${label} event ${i} has t=${String(e.t)} outside 0..${dur}`)\n else if (e.t < prevEv) problems.push(`${label} event ${i} goes back in time`)\n else prevEv = e.t\n if (typeof e.type !== 'string' || e.type === '') problems.push(`${label} event ${i} needs a type`)\n try {\n JSON.stringify(e.data ?? {})\n } catch {\n problems.push(`${label} event ${i} data is not JSON`)\n }\n })\n return { ok: problems.length === 0, problems }\n}\n","import type { AnimationClipSpec, AnimationEventSpec, AnimationPose } from './animation'\nimport { checkAnimation } from './animation'\nimport type { HostEvent } from './module'\n\n/** Clips a tool can bring, one per move of its cycle. A tool brings the ones it has; a missing clip is simply not played. */\nexport const TOOL_CLIP = {\n DRAW: 'draw',\n SHOT: 'shot',\n MISFIRE: 'misfire',\n EMPTY: 'empty',\n CYCLE: 'cycle',\n RELOAD: 'reload',\n FEED: 'feed',\n FAULT: 'fault',\n CLEAR: 'clear',\n SAFETY_ON: 'safety_on',\n SAFETY_OFF: 'safety_off',\n MODE: 'mode',\n /** A throwable with the pin out, held until it is thrown. */\n COOK: 'cook',\n /** The stock folds, unfolds, slides in or out (variants `fold`, `unfold`, `collapse`, `extend`). */\n STOCK: 'stock',\n /** A light or a laser is switched (variants `on`, `off`). */\n TOGGLE: 'toggle',\n} as const\nexport type ToolClipId = (typeof TOOL_CLIP)[keyof typeof TOOL_CLIP]\n\n/** What a renderer does when a rule event arrives. `variant` picks `<clip>_<variant>` when the tool has it, else `<clip>`. */\nexport type ToolAnimationStep =\n | { readonly type: 'play'; readonly clip: ToolClipId; readonly variant?: string; readonly duration?: number; readonly repeat?: number }\n /** Move the clip that is playing to the marker (a clip event `stage` with `data.stage`) the rule just reached. */\n | { readonly type: 'sync'; readonly stage: string }\n /** The rule says the phase is over: the clip jumps to its end if it is still running. */\n | { readonly type: 'finish'; readonly clip: ToolClipId }\n\nconst stockVariant = (event: Extract<HostEvent, { type: 'stock' }>): string => {\n const isFolding = event.kind === undefined ? false : event.kind.includes('fold')\n if (event.state === 'collapsed') return isFolding ? 'fold' : 'collapse'\n return isFolding ? 'unfold' : 'extend'\n}\n\n/** The move a host event asks of the tool in hand, or null when it has no motion. */\nexport const toolAnimationStep = (event: HostEvent): ToolAnimationStep | null => {\n switch (event.type) {\n case 'tool':\n return { type: 'play', clip: TOOL_CLIP.DRAW, ...(event.duration === undefined ? {} : { duration: event.duration }) }\n case 'shot':\n return { type: 'play', clip: TOOL_CLIP.SHOT, repeat: Math.max(1, event.rounds ?? 1) }\n case 'misfire':\n return { type: 'play', clip: TOOL_CLIP.MISFIRE }\n case 'empty':\n return { type: 'play', clip: TOOL_CLIP.EMPTY }\n case 'mode':\n return { type: 'play', clip: TOOL_CLIP.MODE, variant: event.mode }\n case 'fault':\n return { type: 'play', clip: TOOL_CLIP.FAULT, variant: event.fault }\n case 'clear':\n if (event.phase === 'end') return { type: 'finish', clip: TOOL_CLIP.CLEAR }\n return { type: 'play', clip: TOOL_CLIP.CLEAR, variant: event.fault, ...(event.duration === undefined ? {} : { duration: event.duration }) }\n case 'safety':\n return { type: 'play', clip: event.on ? TOOL_CLIP.SAFETY_ON : TOOL_CLIP.SAFETY_OFF }\n case 'cycle':\n if (event.phase === 'end') return { type: 'finish', clip: TOOL_CLIP.CYCLE }\n return { type: 'play', clip: TOOL_CLIP.CYCLE, ...(event.duration === undefined ? {} : { duration: event.duration }) }\n case 'reload':\n if (event.phase === 'end') return { type: 'finish', clip: TOOL_CLIP.RELOAD }\n return { type: 'play', clip: TOOL_CLIP.RELOAD, ...(event.kind === undefined ? {} : { variant: event.kind }), ...(event.duration === undefined ? {} : { duration: event.duration }) }\n case 'stage':\n return { type: 'sync', stage: event.stage }\n case 'feed':\n return { type: 'play', clip: TOOL_CLIP.FEED }\n case 'cook':\n if (event.phase === 'start') return { type: 'play', clip: TOOL_CLIP.COOK, duration: event.fuse }\n return { type: 'finish', clip: TOOL_CLIP.COOK }\n case 'stock':\n return { type: 'play', clip: TOOL_CLIP.STOCK, variant: stockVariant(event), ...(event.duration === undefined ? {} : { duration: event.duration }) }\n case 'toggle':\n return { type: 'play', clip: TOOL_CLIP.TOGGLE, variant: event.on ? 'on' : 'off' }\n default:\n return null\n }\n}\n\n/** Where a renderer puts the poses: every part of the tool by id (the ids the clips move), and the whole tool. Poses are offsets from rest. */\nexport interface ToolRig {\n setPartPose(partId: string, pose: AnimationPose): void\n setRootPose?(pose: AnimationPose): void\n}\n\n/** Clips by id, with the variants as `<clip>_<variant>`. */\nexport type ToolClipSet = Readonly<Record<string, AnimationClipSpec>>\n\n/** A renderer's tool animator: feed it the host events and the frame time. */\nexport interface ToolAnimator {\n handle(event: HostEvent): void\n update(dt: number): void\n /** Ids of the clips playing now. */\n readonly playing: readonly string[]\n dispose(): void\n}\n\nexport const STAGE_EVENT = 'stage'\n\n/** The clip with its times (keys and events) scaled so that it lasts `duration` seconds. */\nexport const scaleClip = (clip: AnimationClipSpec, duration: number): AnimationClipSpec => {\n const factor = clip.duration > 0 ? duration / clip.duration : 1\n return {\n ...clip,\n duration,\n keys: clip.keys.map((key) => ({ ...key, t: key.t * factor })),\n ...(clip.events ? { events: clip.events.map((event) => ({ ...event, t: event.t * factor })) } : {}),\n }\n}\n\nconst ZERO: [number, number, number] = [0, 0, 0]\nconst mix = (from: readonly number[], to: readonly number[], fraction: number): [number, number, number] => [\n (from[0] ?? 0) + ((to[0] ?? 0) - (from[0] ?? 0)) * fraction,\n (from[1] ?? 0) + ((to[1] ?? 0) - (from[1] ?? 0)) * fraction,\n (from[2] ?? 0) + ((to[2] ?? 0) - (from[2] ?? 0)) * fraction,\n]\n\n/** Pose of the whole tool (`partId` undefined) or of one part at `time`, linear between keys; a key that does not name it means rest. */\nexport const sampleClip = (clip: AnimationClipSpec, partId: string | undefined, time: number): { position: [number, number, number]; rotation: [number, number, number] } => {\n const points = clip.keys.map((key) => {\n const pose = partId === undefined ? key : key.parts?.[partId]\n return { t: key.t, position: pose?.position ?? ZERO, rotation: pose?.rotation ?? ZERO }\n })\n const first = points[0]\n if (!first || first.t > 0) points.unshift({ t: 0, position: ZERO, rotation: ZERO })\n const last = points[points.length - 1]\n if (!last) return { position: ZERO, rotation: ZERO }\n if (time >= last.t) return { position: mix(last.position, last.position, 0), rotation: mix(last.rotation, last.rotation, 0) }\n const index = Math.max(0, points.findIndex((point, i) => time >= point.t && time < (points[i + 1]?.t ?? Infinity)))\n const from = points[index]\n const to = points[index + 1]\n if (!from || !to) return { position: ZERO, rotation: ZERO }\n const fraction = to.t > from.t ? (time - from.t) / (to.t - from.t) : 0\n return { position: mix(from.position, to.position, fraction), rotation: mix(from.rotation, to.rotation, fraction) }\n}\n\n/** Problems with a clip set (empty when valid): each key is the clip id (or `<clip>_<variant>`), every clip passes `checkAnimation`, and a `stage` event names its stage. */\nexport const checkToolClips = (clips: ToolClipSet, partIds: Iterable<string>): { ok: boolean; problems: string[] } => {\n const parts = [...partIds]\n const problems: string[] = []\n for (const [key, clip] of Object.entries(clips)) {\n if (clip.id !== key) problems.push(`clip \"${key}\" has the id \"${clip.id}\"`)\n if (!(clip.duration > 0)) problems.push(`clip \"${key}\" has no duration`)\n problems.push(...checkAnimation(clip, parts).problems)\n for (const event of clip.events ?? []) if (event.type === STAGE_EVENT && typeof event.data?.stage !== 'string') problems.push(`clip \"${key}\" has a stage event without data.stage`)\n }\n return { ok: problems.length === 0, problems }\n}\n\ninterface Instance {\n readonly name: string\n readonly clip: AnimationClipSpec\n time: number\n delay: number\n fired: number\n}\n\nexport interface ToolClipPlayerOptions {\n clips: ToolClipSet\n rig: ToolRig\n /** Called for each clip event as its time passes (`eject_casing`, `drop_magazine`, `stage`...). */\n onEvent?(event: AnimationEventSpec, clip: string): void\n /** Seconds between the rounds of a `repeat` (a burst): default 0.1. */\n repeatInterval?(): number\n}\n\n/** The default player of a tool's clips: runs the steps of `toolAnimationStep` against a rig, scaled to the duration the rule gives. */\nexport interface ToolClipPlayer {\n apply(step: ToolAnimationStep): void\n update(dt: number): void\n readonly playing: readonly string[]\n /** The clips being played with the time each has reached and the clip itself (scaled to the duration the rule gave), for a renderer that has to follow what the clip does now (the hands working a part). */\n readonly running: ReadonlyArray<{ readonly name: string; readonly time: number; readonly clip: AnimationClipSpec }>\n}\n\n/**\n * The clip with every event that has a `lead` moved that many seconds earlier, the lead it got in `data.lead`. A led event never goes before 0\n * nor before the event written ahead of it (a fast clip keeps its order: the lead shrinks). Run after `scaleClip`: the lead is real time, the\n * clip's stretch does not touch it.\n */\nexport const leadClipEvents = (clip: AnimationClipSpec): AnimationClipSpec => {\n const events = clip.events ?? []\n if (!events.some((event) => (event.lead ?? 0) > 0)) return clip\n let floor = 0\n const led = events.map((event) => {\n const lead = Math.max(0, Math.min(event.t - floor, event.lead ?? 0))\n const t = event.t - lead\n floor = Math.max(floor, t)\n if (event.lead === undefined) return event\n const moved: AnimationEventSpec = { ...event, t, data: lead > 0 ? { ...event.data, lead } : { ...event.data } }\n delete moved.lead\n return moved\n })\n return { ...clip, events: led }\n}\n\nexport const createToolClipPlayer = (options: ToolClipPlayerOptions): ToolClipPlayer => {\n const { clips, rig, onEvent } = options\n let instances: Instance[] = []\n\n const find = (clip: string, variant?: string): { name: string; clip: AnimationClipSpec } | undefined => {\n const name = variant !== undefined && clips[`${clip}_${variant}`] ? `${clip}_${variant}` : clip\n const found = clips[name]\n return found ? { name, clip: found } : undefined\n }\n\n const render = (instance: Instance): void => {\n const time = Math.min(instance.time, instance.clip.duration)\n const root = sampleClip(instance.clip, undefined, time)\n if (instance.clip.keys.some((key) => key.position || key.rotation)) rig.setRootPose?.(root)\n const touched = new Set(instance.clip.keys.flatMap((key) => Object.keys(key.parts ?? {})))\n for (const id of touched) rig.setPartPose(id, sampleClip(instance.clip, id, time))\n }\n\n const fireEvents = (instance: Instance, upTo: number): void => {\n const events = instance.clip.events ?? []\n while (instance.fired < events.length && (events[instance.fired]?.t ?? Infinity) <= upTo + 1e-9) {\n const event = events[instance.fired]\n instance.fired += 1\n if (event) onEvent?.(event, instance.name)\n }\n }\n\n return {\n apply(step) {\n if (step.type === 'sync') {\n for (const instance of instances) {\n const marker = (instance.clip.events ?? []).find((event) => event.type === STAGE_EVENT && event.data?.stage === step.stage)\n if (marker && instance.time < marker.t) instance.time = marker.t\n }\n return\n }\n if (step.type === 'finish') {\n for (const instance of instances) {\n if (instance.name !== step.clip && !instance.name.startsWith(`${step.clip}_`)) continue\n fireEvents(instance, instance.clip.duration)\n instance.time = instance.clip.duration\n }\n return\n }\n const found = find(step.clip, step.variant)\n if (!found) return\n const clip = leadClipEvents(step.duration && step.duration > 0 ? scaleClip(found.clip, step.duration) : found.clip)\n const interval = options.repeatInterval?.() ?? 0.1\n instances = instances.filter((existing) => existing.name !== found.name || existing.delay > 0)\n for (let round = 0; round < (step.repeat ?? 1); round++) {\n instances.push({ name: found.name, clip, time: 0, delay: round * interval, fired: 0 })\n }\n },\n update(dt) {\n for (const instance of instances) {\n const carried = instance.delay > 0 ? dt - instance.delay : dt\n if (instance.delay > 0) {\n instance.delay = Math.max(0, instance.delay - dt)\n if (instance.delay > 0) continue\n }\n instance.time += carried\n fireEvents(instance, instance.time)\n render(instance)\n }\n instances = instances.filter((instance) => instance.delay > 0 || instance.time < instance.clip.duration)\n },\n get playing() {\n return instances.map((instance) => instance.name)\n },\n get running() {\n return instances.filter((instance) => instance.delay <= 0).map((instance) => ({ name: instance.name, time: instance.time, clip: instance.clip }))\n },\n }\n}\n","export const FRAME_PHASES = ['input', 'fixedSimulate', 'lateSimulate', 'present', 'render', 'idle'] as const\nexport type FramePhase = (typeof FRAME_PHASES)[number]\n\n/**\n * Presentation work a system leaves for later frames, run in small units within a shared time budget.\n * Never simulation: nothing that consumes a seeded generator or decides an outcome may wait in a queue.\n */\nexport interface DeferredQueue {\n /** Stable name for stats and logs (see `DEFERRED_QUEUE_ID`). */\n readonly id: string\n /**\n * Units waiting. 0 means idle; the budget skips the queue without calling `run`. `frame` is the budget's frame counter: a queue may\n * report 0 for the rest of a frame (the crease queue does in the frame a panel was torn). Same value for repeated calls in one frame.\n */\n pending(frame?: number): number\n /**\n * Runs units in order until `clock() >= deadline`. Contract: when `pending() > 0` it runs **at least one** unit, even with a deadline\n * already past; it never runs a unit after it saw the deadline pass. Returns how many units it ran.\n */\n run(deadline: number, clock: () => number): number\n /** Runs everything now (synchronous step, clear, dispose). */\n flush(): void\n /**\n * Called by `budget.register`: from now on the owner stops draining on its own (no MessageChannel, no drain inside `update`).\n * Never called by a getter; a queue nobody registered keeps today's internal draining.\n */\n attach?(): void\n /** Called by the unregister function and by `budget.dispose`: the owner goes back to its internal draining. */\n detach?(): void\n}\n\n/** Where a module registers a deferred queue with the host's budget. */\nexport interface DeferredQueueOptions {\n /** Higher runs first. See DEFERRED_PRIORITY. */\n readonly priority: number\n /** `present` runs before the render (the work must show this frame); `idle` runs after it. Default `idle`. */\n readonly phase?: 'present' | 'idle'\n /** Starvation guard: the queue runs at least one unit every this many frames, even with the budget spent. Default 4. */\n readonly minEveryFrames?: number\n /** Milliseconds of every frame kept for this queue before the others share the rest (when it has work). Default 0. */\n readonly reserveMs?: number\n}\n\n/** The ids of the kit's queues: what `DeferredQueue.id` says and what `DEFERRED_PRIORITY` is keyed by. */\nexport const DEFERRED_QUEUE_ID = {\n DESTRUCTION_EVENTS: 'destruction.events',\n BREACH_PAINT: 'floorplan.breach-paint',\n AUDIO_VOICES: 'audio.voices',\n VEHICLE_CREASE: 'vehicles.crease',\n} as const\n\n/** Default priorities and reserves of the kit's queues; a host may override any of them. */\nexport const DEFERRED_PRIORITY = {\n [DEFERRED_QUEUE_ID.DESTRUCTION_EVENTS]: { priority: 300, phase: 'present' },\n [DEFERRED_QUEUE_ID.BREACH_PAINT]: { priority: 250, phase: 'present' },\n [DEFERRED_QUEUE_ID.AUDIO_VOICES]: { priority: 200, phase: 'idle', reserveMs: 1 },\n [DEFERRED_QUEUE_ID.VEHICLE_CREASE]: { priority: 100, phase: 'idle' },\n} as const satisfies Readonly<Record<string, DeferredQueueOptions>>\n\nexport interface DeferredQueueReport {\n readonly ok: boolean\n readonly problems: readonly string[]\n}\n\n/**\n * Checks the `DeferredQueue` contract. `fill` leaves the queue with at least two units waiting (the same call is made on a fresh queue).\n * It cannot prove the queue only carries presentation work: that is a rule of review.\n */\nexport function checkDeferredQueue(queue: DeferredQueue, fill: () => void): DeferredQueueReport {\n const problems: string[] = []\n fill()\n const waiting = queue.pending(1)\n if (waiting < 2) problems.push(`fill() must leave at least 2 units waiting (pending is ${waiting})`)\n if (queue.pending(1) !== waiting) problems.push('pending(frame) changed between two calls in the same frame')\n const ran = queue.run(0, () => 1)\n if (ran !== 1) problems.push(`run with a deadline already past must run exactly one unit (ran ${ran})`)\n if (waiting >= 2 && queue.pending(2) !== waiting - 1) problems.push(`one unit ran but pending went from ${waiting} to ${queue.pending(2)}`)\n try {\n queue.attach?.()\n queue.attach?.()\n queue.detach?.()\n queue.detach?.()\n } catch (error) {\n problems.push(`attach/detach must be idempotent: ${error instanceof Error ? error.message : String(error)}`)\n }\n queue.flush()\n if (queue.pending(3) !== 0) problems.push(`flush must leave nothing pending (pending is ${queue.pending(3)})`)\n if (queue.run(0, () => 1) !== 0) problems.push('run on an empty queue must run no unit')\n return { ok: problems.length === 0, problems }\n}\n","const WARNED_KEY = Symbol.for('@adatechnology/game-contracts.warned')\n\ntype WarnedHolder = { [WARNED_KEY]?: Set<string> }\n\nconst warnedKeys = (): Set<string> => {\n const holder = globalThis as WarnedHolder\n return (holder[WARNED_KEY] ??= new Set<string>())\n}\n\ndeclare const process: { readonly env: { readonly NODE_ENV?: string } }\n\n// The literal `process.env.NODE_ENV` is what bundlers replace; where there is no `process` the reference throws and counts as development.\nconst isProduction = (): boolean => {\n try {\n return process.env.NODE_ENV === 'production'\n } catch {\n return false\n }\n}\n\nconst isQuiet = (): boolean => (globalThis as { __GAME_KIT_QUIET__?: unknown }).__GAME_KIT_QUIET__ === true\n\n/**\n * Warns on the console the first time `key` is seen, and only outside production and when `globalThis.__GAME_KIT_QUIET__` is not `true`: for a silent fallback that keeps working (an adapter that made its own effects\n * world or audio engine because the host lent none). The seen keys live on `globalThis`, so several bundles of the kit on one page warn once together.\n */\nexport const warnOnce = (key: string, message: string): void => {\n if (isQuiet() || isProduction()) return\n const seen = warnedKeys()\n if (seen.has(key)) return\n seen.add(key)\n console.warn(`[game-kit] ${message}`)\n}\n\n/** Forgets what `warnOnce` has seen (tests that count warnings). */\nexport const resetWarnOnce = (): void => warnedKeys().clear()\n","import { warnOnce } from './warn'\n\ntype PlainObject = Record<string, unknown>\ntype Entry = { readonly id: string }\n\n/** Keys that would reach the prototype of the result when a patch comes from parsed JSON. */\nconst UNSAFE_KEYS: ReadonlySet<string> = new Set(['__proto__', 'constructor', 'prototype'])\n\nconst isPlainObject = (value: unknown): value is PlainObject =>\n typeof value === 'object' && value !== null && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype\n\n/** Recursively optional; arrays and functions are replaced as a whole, objects are merged. */\nexport type DeepPartial<TValue> = TValue extends (...args: never[]) => unknown\n ? TValue\n : TValue extends readonly unknown[]\n ? TValue\n : TValue extends object\n ? { [TKey in keyof TValue]?: DeepPartial<TValue[TKey]> }\n : TValue\n\n/** Merges `patch` over `base`: plain objects merge field by field, arrays, functions and primitives replace. `undefined` in the patch is ignored. */\nexport const deepMerge = <TBase extends object>(base: TBase, patch: object | undefined): TBase => {\n if (!patch) return base\n const result: PlainObject = { ...(base as PlainObject) }\n for (const [key, value] of Object.entries(patch)) {\n if (value === undefined || UNSAFE_KEYS.has(key)) continue\n const current = result[key]\n result[key] = isPlainObject(current) && isPlainObject(value) ? deepMerge(current, value) : value\n }\n return result as TBase\n}\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 patch for an id that does not exist is ignored (and warned about once, in development). */\nexport const applyPatches = <TEntry extends Entry>(entries: Map<string, TEntry>, patches: Readonly<Record<string, DeepPartial<TEntry> | 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, deepMerge(entry, patch))\n else warnOnce(`registry.patch.${id}`, `patch for \"${id}\" ignored: no entry has that id`)\n }\n}\n\n/** Takes the ids out; an id that does not exist is ignored (and warned about once, in development). */\nexport const removeIds = (entries: Map<string, unknown>, ids: readonly string[] | undefined): void => {\n for (const id of ids ?? []) {\n if (!entries.delete(id)) warnOnce(`registry.remove.${id}`, `remove of \"${id}\" ignored: no entry has that id`)\n }\n}\n","import type { Vec3 } from './destruction'\n\n/**\n * The bench of a module: controls, layers and counters it declares as data. The host keeps the values, draws them in a generic way and hands\n * every change back through `ModuleHost.bench`. Nothing here is specific to a kind of module.\n */\nexport type ControlValue = number | boolean | string\n\nexport interface ChoiceOption {\n readonly id: string\n readonly name: string\n /** Heading the host lists the option under (`optgroup`, a grouped row of buttons). Options of a group must sit next to each other. */\n readonly group?: string\n}\n\ninterface ControlBase {\n /** snake_case, unique across the module's controls, layers and metrics. */\n readonly id: string\n /** Display name, already translated for the locale the host asked for. */\n readonly name: string\n readonly hint?: string\n readonly hidden?: boolean\n /**\n * The change (or the press, for an action) takes a noticeable time, a third of a second or more: the host shows its loading indicator while it works, with this text, or the control's name for `true`.\n * Only the host's own controls panel does it; a change made through `TestRoom.bench().set / act` runs at once.\n */\n readonly slow?: boolean | string\n}\n\nexport interface NumberControl extends ControlBase {\n readonly kind: 'number'\n readonly min: number\n readonly max: number\n readonly step?: number\n readonly unit?: string\n readonly default: number\n}\n\nexport interface BooleanControl extends ControlBase {\n readonly kind: 'boolean'\n readonly default: boolean\n}\n\nexport interface ChoiceControl extends ControlBase {\n readonly kind: 'choice'\n readonly options: readonly ChoiceOption[]\n readonly default: string\n}\n\n/** A button: no value, only the press. */\nexport interface ActionControl extends ControlBase {\n readonly kind: 'action'\n}\n\nexport type ModuleControl = NumberControl | BooleanControl | ChoiceControl | ActionControl\n\n/** Something of the module the viewer turns on or off (smoke, flash, casings). */\nexport interface ModuleLayer {\n readonly id: string\n readonly name: string\n /** On unless `false`. */\n readonly default?: boolean\n}\n\n/** Pulled: the room calls read only when it samples (~4 Hz, or on TestRoom.metrics()). */\nexport interface ModuleMetric {\n readonly id: string\n readonly name: string\n readonly unit?: string\n /** Budget: the host shows a bar and warns past it. */\n readonly max?: number\n readonly digits?: number\n read(): number | string\n}\n\nexport type BenchChange =\n | { readonly kind: 'control'; readonly id: string; readonly value: ControlValue }\n | { readonly kind: 'layer'; readonly id: string; readonly on: boolean }\n | { readonly kind: 'action'; readonly id: string }\n\nexport interface ControlPatch {\n readonly hidden?: boolean\n readonly disabled?: boolean\n readonly options?: readonly ChoiceOption[]\n readonly value?: ControlValue\n}\n\n/** What the host lends a module that declares a bench: the current values and the changes. */\nexport interface ModuleBench {\n value(id: string): ControlValue | undefined\n layer(id: string): boolean\n /** Right after attach returns, the host sends one change per control (not actions) and per layer, in declaration order. */\n onChange(listener: (change: BenchChange) => void): () => void\n /** `value` sets without emitting (no echo loops); `options` falls back to the first option if the value is no longer offered. */\n patch(id: string, patch: ControlPatch): void\n /** Move the host camera (a near / middle / far choice). */\n look(eye: Vec3, target: Vec3): void\n /** Shows a sheet on the host's panel, or takes it down with `null`. A host without a sheet area ignores it. */\n sheet?(sheet: BenchSheet | null): void\n}\n\n/**\n * A card of text the module puts on the host's panel (the sheet of an effect, of a weapon): what it is, its numbers, its steps, its colours and,\n * optionally, a strip of segments over time. Names and texts are already translated for the host's locale.\n */\nexport interface BenchSheet {\n readonly kicker?: string\n readonly title: string\n readonly body?: string\n readonly facts?: readonly { readonly label: string; readonly value: string }[]\n readonly steps?: readonly { readonly at: string; readonly text: string }[]\n readonly swatches?: readonly { readonly color: string; readonly name: string }[]\n readonly note?: string\n /** A strip of named segments (seconds), with tick marks and a playhead that follows the module metric `metric`. */\n readonly timeline?: {\n readonly segments: readonly { readonly name: string; readonly start: number; readonly end: number; readonly color: string }[]\n readonly ticks: readonly number[]\n readonly metric: string\n }\n}\n\nexport interface BenchReport {\n readonly ok: boolean\n readonly problems: readonly string[]\n}\n\nexport interface BenchDeclaration {\n readonly controls?: readonly ModuleControl[]\n readonly layers?: readonly ModuleLayer[]\n readonly metrics?: readonly ModuleMetric[]\n}\n\nconst SNAKE_CASE = /^[a-z][a-z0-9_]*$/\nconst finite = (n: unknown): n is number => typeof n === 'number' && Number.isFinite(n)\n\nfunction checkControl(control: ModuleControl, problems: string[]): void {\n const label = `control \"${control.id}\"`\n if (control.kind === 'number') {\n if (!finite(control.min) || !finite(control.max) || control.min >= control.max) problems.push(`${label} needs min < max`)\n if (control.step !== undefined && !(finite(control.step) && control.step > 0)) problems.push(`${label} needs a step > 0`)\n if (!finite(control.default) || control.default < control.min || control.default > control.max) problems.push(`${label} default ${String(control.default)} is outside ${control.min}..${control.max}`)\n return\n }\n if (control.kind !== 'choice') return\n if (control.options.length === 0) {\n problems.push(`${label} needs at least one option`)\n return\n }\n const seen = new Set<string>()\n const groups = new Set<string>()\n let previousGroup: string | undefined\n for (const option of control.options) {\n if (seen.has(option.id)) problems.push(`${label} repeats option \"${option.id}\"`)\n seen.add(option.id)\n if (option.group !== undefined && (typeof option.group !== 'string' || option.group === '')) problems.push(`${label} option \"${option.id}\" has an empty group`)\n else if (option.group !== undefined && option.group !== previousGroup && groups.has(option.group)) problems.push(`${label} group \"${option.group}\" is split: its options must be next to each other`)\n if (typeof option.group === 'string' && option.group !== '') groups.add(option.group)\n previousGroup = option.group\n }\n if (!seen.has(control.default)) problems.push(`${label} default \"${control.default}\" is not among its options`)\n}\n\n/** snake_case ids unique across controls, layers and metrics; min < max; step > 0; default in range; options non-empty, unique, default among them, each group in one run; metric max > 0. */\nexport function checkBench(declared: BenchDeclaration): BenchReport {\n const problems: string[] = []\n const ids = new Set<string>()\n const claim = (kind: string, id: string): void => {\n if (!SNAKE_CASE.test(id)) problems.push(`${kind} id \"${id}\" must be snake_case`)\n if (ids.has(id)) problems.push(`duplicate id \"${id}\" across controls, layers and metrics`)\n ids.add(id)\n }\n for (const control of declared.controls ?? []) {\n claim('control', control.id)\n checkControl(control, problems)\n }\n for (const layer of declared.layers ?? []) claim('layer', layer.id)\n for (const metric of declared.metrics ?? []) {\n claim('metric', metric.id)\n if (metric.max !== undefined && !(finite(metric.max) && metric.max > 0)) problems.push(`metric \"${metric.id}\" needs a max > 0`)\n }\n return { ok: problems.length === 0, problems }\n}\n","/** The weather, light and ground as the world is right now. Written by whoever drives the weather, read by whoever reacts to it. */\nexport interface EnvironmentState {\n /** Rain in mm/h; 0 is dry. */\n readonly rain: number\n /** Wind in m/s along x and z, and its strength (the length of that vector). */\n readonly windX: number\n readonly windZ: number\n readonly windSpeed: number\n /** Strongest gust in m/s. */\n readonly gust: number\n /** Render fog far distance in metres. */\n readonly fogFar: number\n /** How far one can see for gameplay, in metres (not the render fog). */\n readonly visibility: number\n /** 0 is full daylight, 1 is the darkest night. */\n readonly nightFactor: number\n readonly temperatureC: number\n /** Wet ground, 0..1: rises with rain and dries after it stops. */\n readonly wetness: number\n}\n\nexport type EnvironmentKey = keyof EnvironmentState\n\n/** A calm dry day (the normal world, before anything went wrong). */\nexport const DEFAULT_ENVIRONMENT: EnvironmentState = {\n rain: 0,\n windX: 0,\n windZ: 0,\n windSpeed: 0,\n gust: 0,\n fogFar: 220,\n visibility: 220,\n nightFactor: 0,\n temperatureC: 20,\n wetness: 0,\n}\n\n/** The host's environment (`ModuleHost.environment`): a store the weather module writes to and the others read. */\nexport interface EnvironmentBus {\n get(): EnvironmentState\n /** Merges `patch` into the state; listeners hear it only when a value really changed. */\n set(patch: Partial<EnvironmentState>): void\n /** Hears every change with the keys that changed. Returns the unsubscribe function. */\n on(listener: (state: EnvironmentState, changed: readonly EnvironmentKey[]) => void): () => void\n}\n\n/** A plain store that implements the bus; the test room and tests use it, a game may bring its own. */\nexport function createEnvironmentBus(initial: Partial<EnvironmentState> = {}): EnvironmentBus {\n let state: EnvironmentState = { ...DEFAULT_ENVIRONMENT, ...initial }\n const listeners = new Set<(state: EnvironmentState, changed: readonly EnvironmentKey[]) => void>()\n return {\n get: () => state,\n set(patch) {\n const changed = (Object.keys(patch) as EnvironmentKey[]).filter((key) => patch[key] !== undefined && patch[key] !== state[key])\n if (changed.length === 0) return\n const next = { ...state } as { -readonly [Key in EnvironmentKey]: number }\n for (const key of changed) next[key] = patch[key] as number\n state = next\n for (const listener of [...listeners]) listener(state, changed)\n },\n on(listener) {\n listeners.add(listener)\n return () => void listeners.delete(listener)\n },\n }\n}\n","import { applyPatches, deepMerge, mergeById, removeIds } from './registry'\nimport type { DeepPartial } from './registry'\n\n/** One category (kind) of items. Defaults and required contracts are inherited from the `extends` chain. */\nexport interface CategoryDef<TDefaults extends object = Record<string, unknown>> {\n readonly id: string\n /** Parent category id; the child inherits its defaults and its required contracts. */\n readonly extends?: string\n /** Field values every entry of this kind starts with; objects merge down the chain, arrays replace. */\n readonly defaults?: DeepPartial<TDefaults>\n /** Contract ids (`ContractChecker.id`) every entry of this kind, or of a descendant, must satisfy. */\n readonly requires?: readonly string[]\n /** An abstract category only groups: entries cannot declare it directly, only its descendants. */\n readonly abstract?: boolean\n}\n\n/** Where a checker is being run: the entry's kind and the chain of categories root first. */\nexport interface ContractContext {\n readonly kind: string\n readonly chain: readonly string[]\n}\n\n/** A named contract: returns the problems of an entry, each a short phrase like `missing leftover.despawnSeconds`. Empty means it holds. */\nexport interface ContractChecker<TEntry> {\n readonly id: string\n check(entry: TEntry, ctx: ContractContext): string[]\n}\n\nexport interface CategoryOptions {\n /** Categories added to the shipped ones; the same id replaces. */\n readonly extra?: readonly CategoryDef[]\n /** Partial changes by id, applied after `extra`. */\n readonly patches?: Readonly<Record<string, DeepPartial<CategoryDef> | Partial<CategoryDef>>>\n /** Ids taken out of the tree (their children are then reported by `check()`). */\n readonly remove?: readonly string[]\n}\n\nexport interface CategoryRegistry {\n get(id: string): CategoryDef | undefined\n has(id: string): boolean\n ids(): string[]\n /** Root first, ending at `id`. Throws on an unknown id, an unknown parent or a cycle. */\n chain(id: string): string[]\n /** `defaults` merged along the chain, root first, so the closest category wins. */\n defaults(id: string): Record<string, unknown>\n /** Union of `requires` along the chain, order kept, without duplicates. */\n requires(id: string): string[]\n /** True when `id` is `ancestorId` or descends from it. */\n isA(id: string, ancestorId: string): boolean\n /** Problems in the tree itself: duplicate ids, unknown `extends`, cycles. */\n check(): string[]\n}\n\nexport interface CategorizedOptions {\n /** Field of the entry that names its category. Default `kind`. */\n readonly kindField?: string\n /** Known gaps by entry id: problems starting with one of these texts go to `allowed`, so the list can shrink over time. */\n readonly allow?: Readonly<Record<string, readonly string[]>>\n}\n\nexport interface CategorizedReport {\n readonly ok: boolean\n readonly problems: string[]\n readonly allowed: string[]\n}\n\nexport const defineCategories = (defs: readonly CategoryDef[], options: CategoryOptions = {}): CategoryRegistry => {\n const problems: string[] = []\n const duplicates = (list: readonly CategoryDef[]): void => {\n const ids = new Set<string>()\n for (const def of list) {\n if (ids.has(def.id)) problems.push(`duplicate category \"${def.id}\"`)\n ids.add(def.id)\n }\n }\n duplicates(defs)\n duplicates(options.extra ?? [])\n\n const entries = mergeById(defs, options.extra)\n applyPatches(entries, options.patches)\n removeIds(entries, options.remove)\n\n const chain = (id: string): string[] => {\n const path: string[] = []\n let current: string | undefined = id\n while (current !== undefined) {\n const def = entries.get(current)\n if (!def) throw new Error(path.length === 0 ? `unknown category \"${current}\"` : `category \"${path[0]}\" has unknown parent \"${current}\"`)\n if (path.includes(current)) throw new Error(`category cycle: ${[...path, current].reverse().join(' -> ')}`)\n path.push(current)\n current = def.extends\n }\n return path.reverse()\n }\n\n const registry: CategoryRegistry = {\n get: (id) => entries.get(id),\n has: (id) => entries.has(id),\n ids: () => [...entries.keys()],\n chain,\n defaults: (id) => chain(id).reduce<Record<string, unknown>>((acc, ancestor) => deepMerge(acc, entries.get(ancestor)?.defaults), {}),\n requires: (id) => {\n const out: string[] = []\n for (const ancestor of chain(id)) for (const contract of entries.get(ancestor)?.requires ?? []) if (!out.includes(contract)) out.push(contract)\n return out\n },\n isA: (id, ancestorId) => {\n try {\n return chain(id).includes(ancestorId)\n } catch {\n return false\n }\n },\n check: () => {\n const found = [...problems]\n for (const [id, def] of entries) {\n if (def.extends !== undefined && !entries.has(def.extends)) found.push(`category \"${id}\" extends unknown \"${def.extends}\"`)\n else {\n try {\n chain(id)\n } catch (error) {\n const message = (error as Error).message\n // The same cycle is found from each of its members; the dedupe keeps one line per distinct message.\n if (message.startsWith('category cycle') && !found.includes(message)) found.push(message)\n }\n }\n }\n return found\n },\n }\n return registry\n}\n\n/** The entry with the defaults of its category underneath: the entry wins, objects merge, arrays replace. */\nexport const resolveEntry = <TEntry extends object>(registry: CategoryRegistry, entry: TEntry, kindField = 'kind'): TEntry =>\n deepMerge(registry.defaults(String((entry as Record<string, unknown>)[kindField])) as TEntry, entry)\n\n/** Checks that each entry has a known, concrete kind and satisfies every contract its chain requires. */\nexport const checkCategorized = <TEntry extends { readonly id: string }>(\n entries: readonly TEntry[],\n registry: CategoryRegistry,\n checkers: readonly ContractChecker<TEntry>[],\n options: CategorizedOptions = {},\n): CategorizedReport => {\n const kindField = options.kindField ?? 'kind'\n const byId = new Map(checkers.map((checker) => [checker.id, checker]))\n const problems: string[] = [...registry.check()]\n const allowed: string[] = []\n\n for (const entry of entries) {\n const kind = String((entry as Record<string, unknown>)[kindField])\n if (!registry.has(kind)) {\n problems.push(`\"${entry.id}\" has unknown kind \"${kind}\"`)\n continue\n }\n if (registry.get(kind)?.abstract) {\n problems.push(`\"${entry.id}\" has abstract kind \"${kind}\"`)\n continue\n }\n let path: string[]\n try {\n path = registry.chain(kind)\n } catch (error) {\n problems.push(`\"${entry.id}\" (${kind}): ${(error as Error).message}`)\n continue\n }\n const resolved = resolveEntry(registry, entry, kindField)\n for (const contract of registry.requires(kind)) {\n const owner = path.find((ancestor) => registry.get(ancestor)?.requires?.includes(contract)) ?? kind\n const checker = byId.get(contract)\n if (!checker) {\n problems.push(`kind \"${kind}\" requires contract \"${contract}\" but no checker is registered`)\n continue\n }\n const prefix = `\"${entry.id}\" (${kind}): `\n for (const problem of checker.check(resolved, { kind, chain: path })) {\n const text = `${prefix}${problem}, required by ${owner}`\n if ((options.allow?.[entry.id] ?? []).some((start) => problem.startsWith(start))) allowed.push(text)\n else problems.push(text)\n }\n }\n }\n return { ok: problems.length === 0, problems: [...new Set(problems)], allowed }\n}\n","/**\n * Where a drawn object comes from. Every builder of the kit that draws a piece (a car of the fleet, a house or a wall, a piece of furniture, a person, an\n * animal, a plant, an item, a weapon) marks the root it returns, and every renderer that draws for the room (effects, debris, liquids, the room itself)\n * marks its own roots, so a host can tell a piece of the kit from something drawn on the spot. Duck-typed on three.js objects (`userData`, `parent`,\n * `children`), so the contracts stay free of three.js.\n */\nexport const KIT_ORIGIN_KEY = 'kitOrigin'\n\n/** What drew an object: the package and the kind of piece (`vehicle`, `structure`, `furniture`, `person`... or `effect`, `room` for what is not a piece). */\nexport interface KitOrigin {\n readonly owner: string\n readonly kind: string\n /** The catalogue id of the piece, when it has one. */\n readonly id?: string\n /** Drawn as a simple stand-in shape because the kit has no model of the piece yet (an item with no team model): a model still to make. */\n readonly standIn?: boolean\n}\n\ninterface Node {\n userData?: Record<string, unknown>\n parent?: Node | null\n children?: readonly Node[]\n name?: string\n visible?: boolean\n isMesh?: boolean\n isPoints?: boolean\n isLine?: boolean\n isSprite?: boolean\n}\n\n/** Marks `object` as drawn by `origin.owner` and returns it. */\nexport function markKitOrigin<T>(object: T, origin: KitOrigin): T {\n const node = object as unknown as Node\n node.userData = node.userData ?? {}\n node.userData[KIT_ORIGIN_KEY] = { ...origin }\n return object\n}\n\n/** The origin of the object or of its nearest marked ancestor; null when nothing up the tree is marked. */\nexport function kitOriginOf(object: unknown): KitOrigin | null {\n for (let node = object as Node | null | undefined; node; node = node.parent) {\n const origin = node.userData?.[KIT_ORIGIN_KEY] as KitOrigin | undefined\n if (origin && typeof origin.owner === 'string') return origin\n }\n return null\n}\n\n/** A drawn thing that no builder of the kit marked: what was drawn on the spot. */\nexport interface UnmarkedDrawing {\n /** The names of the object and its named ancestors, outermost first. */\n readonly path: string\n readonly object: unknown\n}\n\n/** Every visible mesh, line, point cloud or sprite under `root` with no marked ancestor (`root` itself not counted as one). */\nexport function unmarkedDrawings(root: unknown): UnmarkedDrawing[] {\n const out: UnmarkedDrawing[] = []\n const pathOf = (node: Node): string => {\n const names: string[] = []\n for (let n: Node | null | undefined = node; n && n !== root; n = n.parent) if (n.name) names.push(n.name)\n return names.reverse().join(' > ') || '(no name)'\n }\n const walk = (node: Node, marked: boolean): void => {\n if (node.visible === false) return\n const here = marked || (node !== root && Boolean((node.userData?.[KIT_ORIGIN_KEY] as KitOrigin | undefined)?.owner))\n if (!here && (node.isMesh || node.isPoints || node.isLine || node.isSprite)) out.push({ path: pathOf(node), object: node })\n for (const child of node.children ?? []) walk(child, here)\n }\n walk(root as Node, false)\n return out\n}\n","/** A floor rectangle in an object's own space, metres (x across, z along). */\nexport interface Footprint {\n readonly minX: number\n readonly maxX: number\n readonly minZ: number\n readonly maxZ: number\n}\n\n/** What `footprintBlocker` reads of an object: its world matrix (three.js `Object3D`, duck-typed so the contracts stay free of three.js). */\nexport interface FootprintObject {\n readonly matrixWorld: { readonly elements: ArrayLike<number> }\n readonly parent?: unknown\n updateWorldMatrix?(updateParents: boolean, updateChildren: boolean): void\n}\n\n/**\n * The `ModuleHost.addBlocker` function of a solid object standing on the floor: the walking player stays out of `footprint`, a rectangle in the object's own space,\n * so the object blocks turned with it and follows it when it moves. An object out of the scene (no parent) blocks nothing.\n */\nexport function footprintBlocker(object: FootprintObject | (() => FootprintObject | null | undefined), footprint: Footprint | (() => Footprint | null | undefined)): (x: number, z: number, radius: number) => boolean {\n return (x, z, radius) => {\n const o = typeof object === 'function' ? object() : object\n const fp = typeof footprint === 'function' ? footprint() : footprint\n if (!o || !fp || !o.parent) return false\n o.updateWorldMatrix?.(true, false)\n const e = o.matrixWorld.elements\n // the world point in the object's own floor axes: its x and z columns are at right angles, so a projection on each (over its squared length, for a scale) inverts the matrix\n const dx = x - (e[12] as number)\n const dz = z - (e[14] as number)\n const ax = (e[0] as number) ** 2 + (e[1] as number) ** 2 + (e[2] as number) ** 2\n const az = (e[8] as number) ** 2 + (e[9] as number) ** 2 + (e[10] as number) ** 2\n const lx = (dx * (e[0] as number) + dz * (e[2] as number)) / (ax || 1)\n const lz = (dx * (e[8] as number) + dz * (e[10] as number)) / (az || 1)\n return lx > fp.minX - radius && lx < fp.maxX + radius && lz > fp.minZ - radius && lz < fp.maxZ + radius\n }\n}\n","/**\n * The sounds of a body (footsteps, jumps, landings, blows, hurt), as ids every package agrees on: the rule that knows what the body did\n * (`game-player`, a host's own player) names the sound, and the bank that knows how it sounds (`game-audio`'s `BODY_FOLEY_BANK`) answers it.\n * A game swaps the sound of an id (a sample instead of the synthesis) without touching the rule.\n */\n\n/** What the feet touch, by how it sounds (not by what it is made of: a wooden floor and a plywood board both sound `wood`). */\nexport const SURFACE_IDS = ['concrete', 'asphalt', 'wood', 'tile', 'dirt', 'grass', 'gravel', 'sand', 'metal', 'water', 'glass', 'carpet'] as const\nexport type SurfaceId = (typeof SURFACE_IDS)[number]\n\n/** The surface a body stands on when nobody says (the test room's floor is a concrete slab). */\nexport const DEFAULT_SURFACE: SurfaceId = 'concrete'\n\n/** How the feet move: a run hits harder and shorter; a crouched step is the walk, quieter. */\nexport const GAIT_IDS = ['walk', 'run'] as const\nexport type GaitId = (typeof GAIT_IDS)[number]\n\n/** How hard a landing is: `light` for a jump on the spot or a step down (up to about 5 m/s), `heavy` for a drop of more than a metre and a half. */\nexport const LANDING_IDS = ['light', 'heavy'] as const\nexport type LandingId = (typeof LANDING_IDS)[number]\n\n/** The fall speed (m/s) from which a landing is `heavy`. */\nexport const HEAVY_LANDING_SPEED = 5.5\n\nexport type FootstepSoundId = `footstep_${SurfaceId}_${GaitId}`\nexport type LandSoundId = `land_${SurfaceId}_${LandingId}`\n\n/**\n * How a blow is struck, by the body part or the tool that does it. `blade` stabs or slashes, `blunt` is a club, a bat, a pipe or a hammer, `chop` an\n * axe or a machete driven down.\n */\nexport const BLOW_IDS = ['punch', 'kick', 'shove', 'blade', 'blunt', 'chop'] as const\nexport type BlowId = (typeof BLOW_IDS)[number]\n\n/** What the blow meets: a body (flesh and bone, the head apart), something else (the material answers with its own impact), or nothing. */\nexport type BlowTarget = 'body' | 'head' | 'object' | 'air'\n\n/** The sounds of the body that are not a footstep or a landing. */\nexport const BODY_SOUND_IDS = [\n 'jump',\n 'cloth_rustle',\n 'body_fall',\n 'fall_damage',\n 'player_hurt',\n 'player_breath_run',\n 'player_death',\n 'bite',\n 'grab',\n 'punch_swing',\n 'punch_hit_body',\n 'punch_hit_face',\n 'punch_hit_object',\n 'kick_swing',\n 'kick_hit',\n 'kick_hit_object',\n 'shove_two_hands',\n 'knife_swing',\n 'knife_stab',\n 'knife_slash',\n 'knife_hit_object',\n 'blunt_swing',\n 'blunt_hit',\n 'blunt_hit_object',\n 'axe_swing',\n 'axe_chop',\n 'axe_hit_object',\n] as const\nexport type BodySoundId = (typeof BODY_SOUND_IDS)[number]\n\n/** Every sound of a body: what a host plays for the player, a survivor or a zombie. */\nexport type BodyFoleyId = FootstepSoundId | LandSoundId | BodySoundId\n\nexport const footstepSoundId = (surface: SurfaceId, gait: GaitId): FootstepSoundId => `footstep_${surface}_${gait}`\nexport const landSoundId = (surface: SurfaceId, fallSpeed: number): LandSoundId => `land_${surface}_${fallSpeed >= HEAVY_LANDING_SPEED ? 'heavy' : 'light'}`\n\n/** Every footstep and landing id, in the order of the surfaces. */\nexport const FOOTSTEP_SOUND_IDS: readonly FootstepSoundId[] = SURFACE_IDS.flatMap((surface) => GAIT_IDS.map((gait) => footstepSoundId(surface, gait)))\nexport const LAND_SOUND_IDS: readonly LandSoundId[] = SURFACE_IDS.flatMap((surface) => LANDING_IDS.map((landing): LandSoundId => `land_${surface}_${landing}`))\nexport const BODY_FOLEY_IDS: readonly BodyFoleyId[] = [...FOOTSTEP_SOUND_IDS, ...LAND_SOUND_IDS, ...BODY_SOUND_IDS]\n\nconst SWING: Readonly<Record<BlowId, BodySoundId | null>> = {\n punch: 'punch_swing',\n kick: 'kick_swing',\n // two hands pushed out do not whistle: the shove is one sound, at the contact\n shove: null,\n blade: 'knife_swing',\n blunt: 'blunt_swing',\n chop: 'axe_swing',\n}\n\n/**\n * Seconds from the start of a swing's sound to its loudest point (the limb or the tool at full speed, just before the contact). A host that knows when the\n * blow lands starts the swing this long before; one that does not plays the swing now and the hit this long after.\n */\nexport const SWING_LEAD_SECONDS: Readonly<Record<BlowId, number>> = { punch: 0.07, kick: 0.09, shove: 0, blade: 0.05, blunt: 0.1, chop: 0.11 }\n\n/** The swing through the air, played as the blow starts (`null`: the blow has none). */\nexport const swingSoundId = (blow: BlowId): BodySoundId | null => SWING[blow]\n\nconst HIT: Readonly<Record<BlowId, Readonly<Record<Exclude<BlowTarget, 'air'>, BodySoundId>>>> = {\n punch: { body: 'punch_hit_body', head: 'punch_hit_face', object: 'punch_hit_object' },\n kick: { body: 'kick_hit', head: 'kick_hit', object: 'kick_hit_object' },\n shove: { body: 'shove_two_hands', head: 'shove_two_hands', object: 'shove_two_hands' },\n blade: { body: 'knife_stab', head: 'knife_stab', object: 'knife_hit_object' },\n blunt: { body: 'blunt_hit', head: 'blunt_hit', object: 'blunt_hit_object' },\n chop: { body: 'axe_chop', head: 'axe_chop', object: 'axe_hit_object' },\n}\n\n/**\n * The body's side of a blow that lands: the knuckles, the shoe, the blade or the club meeting what it hit. On an object the material's own impact\n * plays too (the wood, the metal); this is the part that says it was a punch and not a shot.\n */\nexport const hitSoundId = (blow: BlowId, target: Exclude<BlowTarget, 'air'>): BodySoundId => HIT[blow][target]\n\n/**\n * The surface a material sounds like underfoot, by the material ids of `game-destruction` and `game-terrain` (unknown ids: `fallback`). Data, so\n * a game with its own materials passes its own table to `surfaceOfMaterial`.\n */\nexport const MATERIAL_SURFACES: Readonly<Record<string, SurfaceId>> = {\n concrete: 'concrete',\n mortar: 'concrete',\n stone: 'concrete',\n brick: 'concrete',\n cobble: 'concrete',\n paver: 'concrete',\n plaster: 'concrete',\n asphalt: 'asphalt',\n wood_soft: 'wood',\n wood_hard: 'wood',\n green_wood: 'wood',\n plywood: 'wood',\n mdf: 'wood',\n ceramic: 'tile',\n plastic: 'tile',\n soil: 'dirt',\n mud: 'dirt',\n clay: 'dirt',\n wax: 'dirt',\n flesh_fruit: 'dirt',\n turf: 'grass',\n foliage: 'grass',\n cactus: 'grass',\n gravel: 'gravel',\n sand: 'sand',\n // no snow surface yet: packed snow crunches closest to sand underfoot\n snow: 'sand',\n steel: 'metal',\n cast_iron: 'metal',\n aluminium: 'metal',\n copper: 'metal',\n water: 'water',\n glass: 'glass',\n tempered_glass: 'glass',\n fabric: 'carpet',\n foam: 'carpet',\n leather: 'carpet',\n rubber: 'carpet',\n paper: 'carpet',\n}\n\nexport const isSurfaceId = (value: unknown): value is SurfaceId => typeof value === 'string' && (SURFACE_IDS as readonly string[]).includes(value)\n\n/** The surface of a material id; a surface id passes through. */\nexport function surfaceOfMaterial(material: string | undefined, table: Readonly<Record<string, SurfaceId>> = MATERIAL_SURFACES, fallback: SurfaceId = DEFAULT_SURFACE): SurfaceId {\n if (material === undefined) return fallback\n if (isSurfaceId(material)) return material\n return table[material] ?? fallback\n}\n","/**\n * @adatechnology/game-contracts: the contracts every game module (vehicles, furniture, plants, houses, weapons) depends on, and nothing else.\n * Types plus tiny pure checkers, no runtime dependencies. The default destruction world, the renderers and the test room are optional.\n */\nexport type {\n ChainDelay,\n Crack,\n DamageStage,\n DamageType,\n DebrisKind,\n Dent,\n DestructibleSpec,\n BlastOptions,\n BlastReport,\n ConfinementOptions,\n ExplosionChain,\n FragmentHitOptions,\n ShelterOptions,\n DestructionEvent,\n DestructionEventType,\n FluidKind,\n Fragment,\n FragmentMark,\n FractureStyle,\n BallisticExtras,\n Hit,\n HitIgnition,\n HitResult,\n Hole,\n JointKind,\n JointSpec,\n JointState,\n MaterialId,\n ObjectState,\n PartShape,\n PartSpec,\n PartBreach,\n PartState,\n PartStatus,\n PartUnits,\n Quat,\n RayHit,\n Reinforcement,\n TensionRebar,\n TunedMassDamper,\n WindExposure,\n WindState,\n PileSpec,\n SoilLayer,\n SoilProfile,\n SoilState,\n StructureRole,\n SurfaceLayer,\n ToolKind,\n Vec3,\n} from './destruction'\nexport type { BusyOptions, BusySink, BusyTask } from './busy'\nexport { blastShove, DEFAULT_BLAST_SHOVE } from './blast-shove'\nexport { bodyMass, FRAGMENT_COVER, KNOCK_CHEST, KNOCK_DOWN_SPEED, KNOCKED_DOWN_ANGLE, knockOf, launchBody, stepKnock, takeLaunch } from './blast-knock'\nexport { blastBodySpeed, bodyImpulse, DEFAULT_BLAST_IMPULSE, sideOnImpulse } from './blast-impulse'\nexport type { BlastImpulseOptions } from './blast-impulse'\nexport type { Knock } from './blast-knock'\nexport type { BlastShove, BlastShoveOptions, BlastSource } from './blast-shove'\nexport { IMPACT_CONTRACT, checkDestructible, checkHit } from './impact'\nexport type { CheckOptions, DestructibleImpl, ImpactContract, SurfaceRay, SurfaceRayHit } from './impact'\nexport type { CostOptions } from './impact-cost'\nexport { MISSING_BITS } from './body'\nexport type { BodyRegion, BodySlot, SegmentKind } from './body'\nexport { checkSpec } from './spec'\nexport type { CheckSpecOptions, SpecReport } from './spec'\nexport { shotToHit, shotExtras, useToHit } from './weapons'\nexport { DEFAULT_IGNITION_RULES, fillFactor, ignitionChance, resolveIgnitionRules, sparkTargetFactor } from './ignition'\nexport type { IgnitionRules } from './ignition'\nexport type { Shot, Use } from './weapons'\nexport type { ContainerSpec, ItemUseDef, LiquidId } from './fluids'\nexport { sealBlock, sealOpener } from './seal'\nexport type { SealBlock, SealSpec, SealState } from './seal'\nexport type { HandPose, HandTargetSource, HandTargets, MuzzlePose, MuzzleSource } from './hands'\nexport { TWIST_LIFT, twistStroke } from './twist'\nexport type { TwistOptions, TwistStroke } from './twist'\nexport type { EffectHandle, EffectRequest, EffectSink, Enclosure, EnclosureBox } from './effects'\nexport type { WarmUpProbes } from './warm-up'\nexport { checkAnimation } from './animation'\nexport type { AnimationClipSpec, AnimationEventSpec, AnimationKey, AnimationPose, ItemAnimationContract } from './animation'\nexport { STAGE_EVENT, TOOL_CLIP, checkToolClips, createToolClipPlayer, leadClipEvents, sampleClip, scaleClip, toolAnimationStep } from './tool-animation'\nexport type { ToolAnimationStep, ToolAnimator, ToolClipId, ToolClipPlayer, ToolClipPlayerOptions, ToolClipSet, ToolRig } from './tool-animation'\nexport { DEFERRED_PRIORITY, DEFERRED_QUEUE_ID, FRAME_PHASES, checkDeferredQueue } from './frame'\nexport type { DeferredQueue, DeferredQueueOptions, DeferredQueueReport, FramePhase } from './frame'\nexport type { FrameOptions, AreaPlacement, GroundLayout, GroundZone, HostEvent, HostGraphics, HostTool, HostToolAttachment, ModuleArea, ModuleHost, ModuleView, ModuleItemFluid, ModuleItemSeal, ModuleScenario, PlayerState, ItemSeat, ItemSounds, TestableItem, TestableModule, Thumbnail, ToolCommand, ToolCommandResult, HostReach } from './module'\nexport { applyPatches, deepMerge, mergeById, removeIds } from './registry'\nexport type { DeepPartial } from './registry'\nimport { resetWarnOnce as resetWarnOnceForTests } from './warn'\n\nexport { warnOnce } from './warn'\n/** @deprecated Import it from `@adatechnology/game-contracts/testing`. */\nexport const resetWarnOnce = resetWarnOnceForTests\nexport { checkBench } from './bench'\nexport type {\n ActionControl,\n BenchChange,\n BenchDeclaration,\n BenchReport,\n BenchSheet,\n BooleanControl,\n ChoiceControl,\n ChoiceOption,\n ControlPatch,\n ControlValue,\n ModuleBench,\n ModuleControl,\n ModuleLayer,\n ModuleMetric,\n NumberControl,\n} from './bench'\nexport type { AudioListener, AudioSink, LoopHandle, SoundHandle, SoundRequest } from './audio'\nexport { DEFAULT_ENVIRONMENT, createEnvironmentBus } from './environment'\nexport type { EnvironmentBus, EnvironmentKey, EnvironmentState } from './environment'\nexport { checkCategorized, defineCategories, resolveEntry } from './categories'\nexport type { CategorizedOptions, CategorizedReport, CategoryDef, CategoryOptions, CategoryRegistry, ContractChecker, ContractContext } from './categories'\nexport { KIT_ORIGIN_KEY, kitOriginOf, markKitOrigin, unmarkedDrawings } from './origin'\nexport type { KitOrigin, UnmarkedDrawing } from './origin'\nexport { footprintBlocker } from './footprint'\nexport type { Footprint, FootprintObject } from './footprint'\nexport {\n BLOW_IDS,\n BODY_FOLEY_IDS,\n BODY_SOUND_IDS,\n DEFAULT_SURFACE,\n FOOTSTEP_SOUND_IDS,\n GAIT_IDS,\n HEAVY_LANDING_SPEED,\n LANDING_IDS,\n LAND_SOUND_IDS,\n MATERIAL_SURFACES,\n SURFACE_IDS,\n SWING_LEAD_SECONDS,\n footstepSoundId,\n hitSoundId,\n isSurfaceId,\n landSoundId,\n surfaceOfMaterial,\n swingSoundId,\n} from './foley'\nexport type { BlowId, BlowTarget, BodyFoleyId, BodySoundId, FootstepSoundId, GaitId, LandSoundId, LandingId, SurfaceId } from './foley'\n","/** The kinds of particle pool of the effects, as `effects.ts` draws them. */\nexport type PoolKind = 'smoke' | 'steam' | 'fire' | 'spark' | 'water'\n\n/** What `EffectSink.play` of `game-contracts` takes, as a structural type. */\nexport interface ParticleEffectRequest {\n readonly effect: string\n readonly point: [number, number, number]\n readonly values: Readonly<Record<string, number>>\n}\n\n/** The host's effects world as far as the particles need it (`EffectSink` of `game-contracts` fits). */\nexport interface ParticleEffects {\n play(request: ParticleEffectRequest): unknown\n}\n\nexport interface RoutedParticle {\n readonly kind: PoolKind\n readonly flag: number\n readonly position: [number, number, number]\n readonly velocity: [number, number, number]\n readonly life: number\n readonly startSize: number\n readonly endSize: number\n readonly tint: number\n}\n\nconst channel = (value: number): number => Math.round(Math.min(1, Math.max(0, value)) * 255)\nconst rgb = (red: number, green: number, blue: number): number => (channel(red) << 16) | (channel(green) << 8) | channel(blue)\n\nconst EFFECT_OF: Readonly<Partial<Record<PoolKind, string>>> = { smoke: 'particle_puff', steam: 'particle_puff', fire: 'particle_glow', spark: 'particle_streak' }\n\n/** True for the kinds the effects world draws; water stays local because it ends at a height of its own. */\nexport const isRoutedKind = (kind: PoolKind): boolean => EFFECT_OF[kind] !== undefined\n\n/** The numbers of the local particle at birth: the colour and peak opacity the local shader gives it, its life, its two sizes (as a ratio). */\nexport function routedParticleRequest(particle: RoutedParticle): ParticleEffectRequest {\n const { kind, flag, tint } = particle\n const look = lookOf(kind, flag, tint)\n return {\n effect: EFFECT_OF[kind]!,\n point: particle.position,\n values: {\n vx: particle.velocity[0],\n vy: particle.velocity[1],\n vz: particle.velocity[2],\n life: particle.life,\n size: particle.startSize,\n grow: particle.endSize / particle.startSize,\n color: look.color,\n alpha: look.alpha,\n gravity: 0,\n drag: 0,\n },\n }\n}\n\nfunction lookOf(kind: PoolKind, flag: number, tint: number): { color: number; alpha: number } {\n if (kind === 'smoke') return { color: rgb(tint, tint, tint), alpha: 0.55 }\n if (kind === 'steam') return { color: rgb(tint, tint, tint), alpha: 0.4 }\n if (kind === 'spark') return { color: rgb(1, 0.75, 0.3), alpha: 1 }\n if (flag) return { color: rgb(0.25 + 0.15 * tint, 0.5 + 0.2 * tint, 1), alpha: 0.95 }\n return { color: rgb(1, Math.max(0.15, 0.85 - tint * 0.1), 0.25), alpha: 0.8 }\n}\n","import type { ActiveEmitter, EmitterKind } from '@adatechnology/game-interactables'\nimport { warnOnce } from '@adatechnology/game-contracts'\nimport { isRoutedKind, routedParticleRequest } from './routed-particles'\nimport type { ParticleEffects, PoolKind } from './routed-particles'\nimport type { CanvasFactory, ThreeLib, ThreeNS } from './types'\n\nexport interface EffectsOptions {\n /** Where the particle objects are added (the `Points` of every kind, the water streams and the flame ring). Required. */\n scene: ThreeNS.Object3D\n /**\n * Camera whose vertical field of view sizes the sprites, read again every `update` so zoom and fov changes follow. Required: pass the\n * camera the scene is rendered with (a `PerspectiveCamera` fits), or a function returning the current one (`null` = 90 degree lens).\n */\n camera: PerspectiveLike | (() => PerspectiveLike | null) | null\n /** Total particles alive at once, shared by all kinds (split by weight). Default 800. */\n maxParticles?: number\n /** Used to paint the soft sprite on a canvas. Without it (or without a 2D context) a data texture is generated. */\n document?: CanvasFactory\n /** Seed of the random spread. Default 1. */\n seed?: number\n /** Wind vector [x, z] in m/s that bends smoke and steam. Default [0, 0]. */\n wind?: [number, number]\n /** Viewport height in pixels, used to size the point sprites. Update with `setViewHeight` on resize. Default 720. */\n viewHeight?: number\n /** Height (m) water streams fall to when the emitter has no `reach`. Default 0. */\n floorY?: number\n /** Most water streams drawn at once (one draw call each). Default 6. */\n maxStreams?: number\n /** Most gas flame rings drawn at once (all rings share one draw call). Default 16. */\n maxFlames?: number\n /**\n * The host's effects world, read at each particle. With one, smoke, steam, fire and sparks go through it and its one budget (`maxParticles` then only caps water);\n * without one every kind is drawn here with its own cap (deprecated, removed in the next major).\n */\n effects?: () => ParticleEffects | undefined\n}\n\n/** What the effects read from a camera. */\nexport interface PerspectiveLike {\n /** Vertical field of view in degrees. */\n fov: number\n zoom?: number\n}\n\nexport interface EffectsStats {\n alive: number\n capacity: number\n byKind: Record<PoolKind, number>\n}\n\nexport type { PoolKind }\n\nexport interface Effects {\n /**\n * Spawns for the active emitters and advances every particle by `dt` seconds, sizing the sprites for the camera given to\n * `createEffects` (or `setCamera`). No allocations per frame once warmed up. `camera` overrides it for this call (deprecated).\n */\n update(dt: number, emitters: readonly ActiveEmitter[], camera?: PerspectiveLike): void\n /** Replace the camera used for the sprite size (or `null` to go back to a 90 degree lens). */\n setCamera(camera: PerspectiveLike | (() => PerspectiveLike | null) | null): void\n setWind(x: number, z: number): void\n setViewHeight(px: number): void\n stats(): EffectsStats\n /** The Points objects (one per kind). */\n objects: Record<PoolKind, ThreeNS.Points>\n /** Water stream meshes (tapered, translucent, following the gravity arc) and the one mesh holding every gas flame ring. */\n meshes: { group: ThreeNS.Group; streams: ThreeNS.Mesh[]; flames: ThreeNS.Mesh }\n clear(): void\n dispose(): void\n}\n\nconst POOLS: PoolKind[] = ['smoke', 'steam', 'fire', 'water', 'spark']\nconst WEIGHT: Record<PoolKind, number> = { smoke: 0.27, steam: 0.15, fire: 0.2, water: 0.28, spark: 0.1 }\n\ninterface Pool {\n kind: PoolKind\n cap: number\n count: number\n pos: Float32Array\n vel: Float32Array\n age: Float32Array\n life: Float32Array\n size0: Float32Array\n size1: Float32Array\n tint: Float32Array\n flags: Uint8Array\n /** Water only: the height at which the particle ends (splashes, droplets). */\n ground: Float32Array\n aSize: ThreeNS.BufferAttribute\n aAlpha: ThreeNS.BufferAttribute\n aColor: ThreeNS.BufferAttribute\n aPos: ThreeNS.BufferAttribute\n points: ThreeNS.Points\n}\n\nconst VERT = /* glsl */ `\nattribute float aSize;\nattribute float aAlpha;\nattribute vec3 aColor;\nuniform float uScale;\nvarying float vAlpha;\nvarying vec3 vColor;\nvoid main() {\n vAlpha = aAlpha;\n vColor = aColor;\n vec4 mv = modelViewMatrix * vec4(position, 1.0);\n gl_PointSize = max(aSize * uScale / max(-mv.z, 0.05), 1.0);\n gl_Position = projectionMatrix * mv;\n}\n`\nconst FRAG = /* glsl */ `\nuniform sampler2D uMap;\nvarying float vAlpha;\nvarying vec3 vColor;\nvoid main() {\n vec4 t = texture2D(uMap, gl_PointCoord);\n gl_FragColor = vec4(vColor, vAlpha * t.a);\n}\n`\n\nfunction mulberry(seed: number): () => number {\n let a = seed >>> 0\n return () => {\n a = (a + 0x6d2b79f5) >>> 0\n let t = a\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/** Soft round sprite (alpha only matters). Canvas when possible, otherwise generated data. */\nfunction makeSprite(THREE: ThreeLib, doc: CanvasFactory | undefined): ThreeNS.Texture {\n const N = 64\n try {\n const canvas = doc?.createElement('canvas')\n const ctx = canvas?.getContext?.('2d')\n if (canvas && ctx && ctx.createRadialGradient) {\n canvas.width = N\n canvas.height = N\n const g = ctx.createRadialGradient(N / 2, N / 2, 0, N / 2, N / 2, N / 2)\n g.addColorStop(0, 'rgba(255,255,255,1)')\n g.addColorStop(0.4, 'rgba(255,255,255,0.55)')\n g.addColorStop(1, 'rgba(255,255,255,0)')\n ctx.fillStyle = g\n ctx.fillRect(0, 0, N, N)\n const tex = new THREE.CanvasTexture(canvas)\n return tex\n }\n } catch {\n // fall through to the data texture\n }\n const data = new Uint8Array(N * N * 4)\n for (let y = 0; y < N; y++) {\n for (let x = 0; x < N; x++) {\n const d = Math.hypot(x + 0.5 - N / 2, y + 0.5 - N / 2) / (N / 2)\n const a = d >= 1 ? 0 : Math.pow(1 - d, 1.6)\n const i = (y * N + x) * 4\n data[i] = data[i + 1] = data[i + 2] = 255\n data[i + 3] = Math.round(a * 255)\n }\n }\n const tex = new THREE.DataTexture(data, N, N, THREE.RGBAFormat)\n tex.needsUpdate = true\n return tex\n}\n\n/** Vertical streaks of water (alpha only matters); scrolled along the stream to show the flow. */\nfunction makeStreaks(THREE: ThreeLib): ThreeNS.DataTexture {\n const W = 16\n const H = 64\n const data = new Uint8Array(W * H * 4)\n const hash = (x: number, y: number): number => {\n let h = Math.imul(x + 1, 374761393) ^ Math.imul(y + 7, 668265263)\n h = Math.imul(h ^ (h >>> 13), 1274126177)\n return ((h ^ (h >>> 16)) >>> 0) / 4294967296\n }\n for (let y = 0; y < H; y++) {\n for (let x = 0; x < W; x++) {\n const col = 0.35 + 0.65 * hash(x, 0)\n const row = 0.55 + 0.45 * Math.sin(((y / H) * 3 + hash(x, 1) * 6) * Math.PI * 2)\n const i = (y * W + x) * 4\n data[i] = data[i + 1] = data[i + 2] = 255\n data[i + 3] = Math.round(255 * Math.min(1, col * row * 1.3))\n }\n }\n const tex = new THREE.DataTexture(data, W, H, THREE.RGBAFormat)\n tex.wrapS = THREE.RepeatWrapping\n tex.wrapT = THREE.RepeatWrapping\n tex.needsUpdate = true\n return tex\n}\n\n/**\n * Pooled particle effects: smoke, steam, fire (and the blue gas `flame` ring), water streams with drips, and sparks.\n * One `Points` per kind, fixed-size buffers, swap-remove; the total alive never exceeds `maxParticles`.\n */\nexport function createEffects(THREE: ThreeLib, options: EffectsOptions): Effects\n/** @deprecated Pass `{ scene, camera, ... }` as the one options argument. */\nexport function createEffects(THREE: ThreeLib, scene: ThreeNS.Object3D, options?: Partial<EffectsOptions>): Effects\nexport function createEffects(THREE: ThreeLib, a: EffectsOptions | ThreeNS.Object3D, b: Partial<EffectsOptions> = {}): Effects {\n const legacy = !!(a as ThreeNS.Object3D).isObject3D\n const options: Partial<EffectsOptions> = legacy ? { ...b, scene: a as ThreeNS.Object3D } : (a as EffectsOptions)\n const scene = options.scene\n if (!scene) throw new Error('createEffects: options.scene is required')\n if (!legacy && options.camera === undefined) throw new Error('createEffects: options.camera is required (pass null for a 90 degree lens)')\n const total = Math.max(POOLS.length, Math.floor(options.maxParticles ?? 800))\n const rand = mulberry(options.seed ?? 1)\n const wind: [number, number] = [options.wind?.[0] ?? 0, options.wind?.[1] ?? 0]\n const sprite = makeSprite(THREE, options.document)\n let viewHeight = options.viewHeight ?? 720\n let cameraSource: PerspectiveLike | (() => PerspectiveLike | null) | null = options.camera ?? null\n let camera: PerspectiveLike | null = typeof cameraSource === 'function' ? cameraSource() : cameraSource\n const scale = { value: 0 }\n /** Point size in pixels is size * viewHeight / (2 tan(fov / 2)) / distance. */\n const refreshScale = (cam: PerspectiveLike | null): void => {\n const fov = cam ? 2 * Math.atan(Math.tan((Math.min(Math.max(cam.fov, 1), 175) * Math.PI) / 360) / Math.max(cam.zoom ?? 1, 0.01)) : Math.PI / 2\n scale.value = viewHeight / (2 * Math.tan(fov / 2))\n }\n refreshScale(camera)\n\n const pools = {} as Record<PoolKind, Pool>\n const objects = {} as Record<PoolKind, ThreeNS.Points>\n let used = 0\n POOLS.forEach((kind, i) => {\n const cap = i === POOLS.length - 1 ? Math.max(1, total - used) : Math.max(1, Math.floor(total * WEIGHT[kind]))\n used += cap\n const geo = new THREE.BufferGeometry()\n const aPos = new THREE.BufferAttribute(new Float32Array(cap * 3), 3)\n const aSize = new THREE.BufferAttribute(new Float32Array(cap), 1)\n const aAlpha = new THREE.BufferAttribute(new Float32Array(cap), 1)\n const aColor = new THREE.BufferAttribute(new Float32Array(cap * 3), 3)\n for (const a of [aPos, aSize, aAlpha, aColor]) a.setUsage(THREE.DynamicDrawUsage)\n geo.setAttribute('position', aPos)\n geo.setAttribute('aSize', aSize)\n geo.setAttribute('aAlpha', aAlpha)\n geo.setAttribute('aColor', aColor)\n geo.setDrawRange(0, 0)\n const additive = kind === 'fire' || kind === 'spark'\n const mat = new THREE.ShaderMaterial({\n uniforms: { uMap: { value: sprite }, uScale: scale },\n vertexShader: VERT,\n fragmentShader: FRAG,\n transparent: true,\n depthWrite: false,\n blending: additive ? THREE.AdditiveBlending : THREE.NormalBlending,\n })\n const points = new THREE.Points(geo, mat)\n points.frustumCulled = false\n points.name = `effects:${kind}`\n scene.add(points)\n objects[kind] = points\n pools[kind] = {\n kind,\n cap,\n count: 0,\n pos: new Float32Array(cap * 3),\n vel: new Float32Array(cap * 3),\n age: new Float32Array(cap),\n life: new Float32Array(cap),\n size0: new Float32Array(cap),\n size1: new Float32Array(cap),\n tint: new Float32Array(cap),\n flags: new Uint8Array(cap),\n ground: new Float32Array(cap).fill(-1e9),\n aSize,\n aAlpha,\n aColor,\n aPos,\n points,\n }\n })\n\n /** Fractional spawns carried over per emitter id (the Map only grows with distinct ids and is pruned by stamp). */\n const debt = new Map<string, { carry: number; stamp: number }>()\n let stamp = 0\n let clock = 0\n\n const spawn = (p: Pool, x: number, y: number, z: number, vx: number, vy: number, vz: number, life: number, s0: number, s1: number, tint: number, flag = 0, ground = -1e9): void => {\n const sink = isRoutedKind(p.kind) ? options.effects?.() : undefined\n if (sink) {\n sink.play(routedParticleRequest({ kind: p.kind, flag, position: [x, y, z], velocity: [vx, vy, vz], life, startSize: s0, endSize: s1, tint }))\n return\n }\n if (isRoutedKind(p.kind)) warnOnce('game-interactables-three.own-particles', 'game-interactables-three drew its smoke, steam, fire and sparks with pools of its own because the host lent no effects world (options.effects); pass the host effects so the particles share one budget')\n if (p.count >= p.cap) return\n const i = p.count++\n p.pos[i * 3] = x\n p.pos[i * 3 + 1] = y\n p.pos[i * 3 + 2] = z\n p.vel[i * 3] = vx\n p.vel[i * 3 + 1] = vy\n p.vel[i * 3 + 2] = vz\n p.age[i] = 0\n p.life[i] = life\n p.size0[i] = s0\n p.size1[i] = s1\n p.tint[i] = tint\n p.flags[i] = flag\n p.ground[i] = ground\n }\n\n const emitFrom = (e: ActiveEmitter, n: number): void => {\n const k = Math.min(1, Math.max(0, e.intensity))\n const size = e.size > 0 ? e.size : 0.2\n const [px, py, pz] = e.pos\n for (let j = 0; j < n; j++) {\n switch (e.kind) {\n case 'smoke':\n // thick, slow, wide plume\n spawn(pools.smoke, px + (rand() - 0.5) * size * 0.6, py, pz + (rand() - 0.5) * size * 0.6, (rand() - 0.5) * 0.2, 0.45 + rand() * 0.45, (rand() - 0.5) * 0.2, 2.8 + rand() * 1.8, size * 0.6, size * 2.6, 0.3 + rand() * 0.25, 0)\n break\n case 'steam':\n spawn(pools.steam, px + (rand() - 0.5) * size * 0.5, py, pz + (rand() - 0.5) * size * 0.5, (rand() - 0.5) * 0.12, 0.3 + rand() * 0.3, (rand() - 0.5) * 0.12, 1.3 + rand() * 1.1, size * 0.3, size * 1.3, 0.92, 0)\n break\n case 'fire':\n spawn(pools.fire, px + (rand() - 0.5) * size * 0.7, py, pz + (rand() - 0.5) * size * 0.7, (rand() - 0.5) * 0.25, 0.8 + rand() * 0.9 * k, (rand() - 0.5) * 0.25, 0.35 + rand() * 0.4, size * 0.75, size * 0.15, rand(), 0)\n break\n case 'flame': {\n // soft blue glow over the ring (the ring of little flames itself is a mesh)\n const a = rand() * Math.PI * 2\n const r = size * 0.8\n spawn(pools.fire, px + Math.cos(a) * r, py + 0.01, pz + Math.sin(a) * r, 0, 0.06 + rand() * 0.1, 0, 0.14 + rand() * 0.12, size * 0.55, size * 0.15, rand(), 1)\n break\n }\n case 'spark': {\n const a = rand() * Math.PI * 2\n const up = 0.4 + rand() * 0.6\n const sp = 1.2 + rand() * 2\n spawn(pools.spark, px, py, pz, Math.cos(a) * sp * (1 - up * 0.5), up * sp * 1.4, Math.sin(a) * sp * (1 - up * 0.5), 0.35 + rand() * 0.5, size * 0.18, size * 0.05, rand(), 0)\n break\n }\n case 'water':\n break\n }\n }\n }\n\n // ---- water: a tapered translucent stream along the gravity arc, droplets around it and a splash where it lands ----------------\n interface Arc {\n x: number\n y: number\n z: number\n vx: number\n vy: number\n vz: number\n /** Flight time to the landing height. */\n t: number\n stopY: number\n r0: number\n r1: number\n }\n const arc: Arc = { x: 0, y: 0, z: 0, vx: 0, vy: 0, vz: 0, t: 0, stopY: 0, r0: 0, r1: 0 }\n const floorY = options.floorY ?? 0\n const arcOf = (e: ActiveEmitter, k: number): boolean => {\n let [dx, dy, dz] = e.dir\n const len = Math.hypot(dx, dy, dz)\n if (len < 1e-6) {\n dx = 0\n dy = -1\n dz = 0\n } else {\n dx /= len\n dy /= len\n dz /= len\n }\n const speed = 0.7 + 1.8 * k\n const stopY = e.reach !== undefined ? Math.max(e.pos[1] - e.reach, floorY) : floorY\n const drop = e.pos[1] - stopY\n if (drop < 0.02) return false\n const vy = dy * speed\n arc.x = e.pos[0]\n arc.y = e.pos[1]\n arc.z = e.pos[2]\n arc.vx = dx * speed\n arc.vy = vy\n arc.vz = dz * speed\n arc.t = Math.min(2.5, (vy + Math.sqrt(vy * vy + 19.6 * drop)) / 9.8)\n arc.stopY = stopY\n const size = e.size > 0 ? e.size : 0.05\n if (size < 0.1) {\n // a tap: a thin jet that narrows as it speeds up\n arc.r0 = Math.max(0.004, size * 0.5)\n arc.r1 = arc.r0 * 0.55\n } else {\n // a shower or a leak: a spray that opens a little\n arc.r0 = size * 0.45\n arc.r1 = arc.r0 * 1.2\n }\n return true\n }\n\n const emitWater = (e: ActiveEmitter, k: number, nSpray: number, nSplash: number, nMist: number): void => {\n const size = e.size > 0 ? e.size : 0.05\n const wide = size >= 0.1\n for (let j = 0; j < nSpray; j++) {\n const s = rand()\n const t = s * arc.t\n const r = (arc.r0 + (arc.r1 - arc.r0) * s) * (0.7 + rand() * 0.5)\n const a = rand() * Math.PI * 2\n const ox = Math.cos(a) * r\n const oz = Math.sin(a) * r\n spawn(\n pools.water,\n arc.x + arc.vx * t + ox,\n arc.y + arc.vy * t - 4.9 * t * t,\n arc.z + arc.vz * t + oz,\n arc.vx + (wide ? ox * 1.2 : 0) + (rand() - 0.5) * 0.25,\n arc.vy - 9.8 * t,\n arc.vz + (wide ? oz * 1.2 : 0) + (rand() - 0.5) * 0.25,\n 0.22 + rand() * 0.25,\n 0.011 + rand() * 0.012,\n 0.009 + rand() * 0.008,\n rand(),\n 2,\n arc.stopY,\n )\n }\n const ex = arc.x + arc.vx * arc.t\n const ez = arc.z + arc.vz * arc.t\n for (let j = 0; j < nSplash; j++) {\n const a = rand() * Math.PI * 2\n const r = arc.r1 * Math.sqrt(rand())\n const sp = (wide ? 0.5 : 0.25) + rand() * 0.7\n spawn(pools.water, ex + Math.cos(a) * r, arc.stopY + 0.005, ez + Math.sin(a) * r, Math.cos(a) * sp, 0.9 + rand() * 1.3, Math.sin(a) * sp, 0.3 + rand() * 0.25, 0.014 + rand() * 0.018, 0.01, rand(), 3, arc.stopY)\n }\n for (let j = 0; j < nMist; j++) {\n const a = rand() * Math.PI * 2\n const r = arc.r1 * 1.4 * Math.sqrt(rand())\n spawn(pools.water, ex + Math.cos(a) * r, arc.stopY + 0.02, ez + Math.sin(a) * r, Math.cos(a) * 0.15, 0.18 + rand() * 0.15, Math.sin(a) * 0.15, 0.7 + rand() * 0.5, 0.06 + size * 0.2, 0.2 + size * 0.5, rand(), 4, arc.stopY)\n }\n void k\n }\n\n const emitDrip = (e: ActiveEmitter, n: number): void => {\n const size = e.size > 0 ? e.size : 0.05\n const stopY = e.reach !== undefined ? Math.max(e.pos[1] - e.reach, floorY) : floorY\n for (let j = 0; j < n; j++) {\n const sx = (rand() - 0.5) * 0.01\n const sz = (rand() - 0.5) * 0.01\n spawn(pools.water, e.pos[0] + sx, e.pos[1], e.pos[2] + sz, e.dir[0] * 0.08, e.dir[1] * 0.08, e.dir[2] * 0.08, 1.4, size * 0.22, size * 0.2, rand(), 1, stopY)\n }\n }\n\n const RATE: Record<EmitterKind, number> = { smoke: 42, steam: 48, fire: 150, flame: 55, spark: 40, water: 0 }\n\n // Stream meshes ----------------------------------------------------------------------------------------------------------------\n const SEG = 12\n const SIDES = 8\n const maxStreams = Math.max(0, Math.floor(options.maxStreams ?? 6))\n const streaksTex = makeStreaks(THREE)\n const streamMat = new THREE.MeshBasicMaterial({ color: 0x9cccff, map: streaksTex, vertexColors: true, transparent: true, opacity: 0.8, depthWrite: false, side: THREE.DoubleSide })\n const streamIndex: number[] = []\n for (let i = 0; i < SEG; i++) {\n for (let m = 0; m < SIDES; m++) {\n const a = i * (SIDES + 1) + m\n const b = a + SIDES + 1\n streamIndex.push(a, b, a + 1, a + 1, b, b + 1)\n }\n }\n const group = new THREE.Group()\n group.name = 'effects:meshes'\n scene.add(group)\n const streams: ThreeNS.Mesh[] = []\n for (let i = 0; i < maxStreams; i++) {\n const geo = new THREE.BufferGeometry()\n const n = (SEG + 1) * (SIDES + 1)\n geo.setAttribute('position', new THREE.BufferAttribute(new Float32Array(n * 3), 3).setUsage(THREE.DynamicDrawUsage))\n geo.setAttribute('color', new THREE.BufferAttribute(new Float32Array(n * 4), 4).setUsage(THREE.DynamicDrawUsage))\n const uv = new Float32Array(n * 2)\n geo.setAttribute('uv', new THREE.BufferAttribute(uv, 2).setUsage(THREE.DynamicDrawUsage))\n geo.setIndex(streamIndex)\n const mesh = new THREE.Mesh(geo, streamMat)\n mesh.frustumCulled = false\n mesh.visible = false\n mesh.name = `effects:stream${i}`\n group.add(mesh)\n streams.push(mesh)\n }\n\n const writeStream = (mesh: ThreeNS.Mesh, k: number): void => {\n const geo = mesh.geometry\n const pos = (geo.getAttribute('position') as ThreeNS.BufferAttribute).array as Float32Array\n const col = (geo.getAttribute('color') as ThreeNS.BufferAttribute).array as Float32Array\n const uv = (geo.getAttribute('uv') as ThreeNS.BufferAttribute).array as Float32Array\n for (let i = 0; i <= SEG; i++) {\n const s = i / SEG\n const t = s * arc.t\n const cx = arc.x + arc.vx * t\n const cy = arc.y + arc.vy * t - 4.9 * t * t\n const cz = arc.z + arc.vz * t\n let tx = arc.vx\n let ty = arc.vy - 9.8 * t\n let tz = arc.vz\n const tl = Math.hypot(tx, ty, tz) || 1\n tx /= tl\n ty /= tl\n tz /= tl\n // binormal = tangent x up, normal = tangent x binormal\n let bx = tz\n const by = 0\n let bz = -tx\n const bl = Math.hypot(bx, bz)\n if (bl < 1e-4) {\n bx = 1\n bz = 0\n } else {\n bx /= bl\n bz /= bl\n }\n const nx = ty * bz - tz * by\n const ny = tz * bx - tx * bz\n const nz = tx * by - ty * bx\n const r = arc.r0 + (arc.r1 - arc.r0) * s\n const alpha = Math.min(1, s * 7) * (0.95 - 0.3 * s) * Math.min(1, 0.55 + k)\n for (let m = 0; m <= SIDES; m++) {\n const a = (m / SIDES) * Math.PI * 2\n const ca = Math.cos(a) * r\n const sa = Math.sin(a) * r\n const v = i * (SIDES + 1) + m\n pos[v * 3] = cx + bx * ca + nx * sa\n pos[v * 3 + 1] = cy + by * ca + ny * sa\n pos[v * 3 + 2] = cz + bz * ca + nz * sa\n col[v * 4] = col[v * 4 + 1] = col[v * 4 + 2] = 1\n col[v * 4 + 3] = alpha\n uv[v * 2] = (m / SIDES) * 3\n uv[v * 2 + 1] = s * arc.t * 2.2\n }\n }\n ;(geo.getAttribute('position') as ThreeNS.BufferAttribute).needsUpdate = true\n ;(geo.getAttribute('color') as ThreeNS.BufferAttribute).needsUpdate = true\n ;(geo.getAttribute('uv') as ThreeNS.BufferAttribute).needsUpdate = true\n mesh.visible = true\n }\n\n // Gas flame rings: every burner is a ring of small blue cones; all rings share one dynamic mesh (one draw call) ------------------\n const CONES = 16\n const maxFlames = Math.max(0, Math.floor(options.maxFlames ?? 16))\n const flameGeo = new THREE.BufferGeometry()\n const flamePos = new THREE.BufferAttribute(new Float32Array(maxFlames * CONES * 4 * 3), 3).setUsage(THREE.DynamicDrawUsage)\n const flameCol = new THREE.BufferAttribute(new Float32Array(maxFlames * CONES * 4 * 3), 3).setUsage(THREE.DynamicDrawUsage)\n flameGeo.setAttribute('position', flamePos)\n flameGeo.setAttribute('color', flameCol)\n const flameIdx: number[] = []\n for (let i = 0; i < maxFlames * CONES; i++) {\n const v = i * 4\n flameIdx.push(v, v + 1, v + 3, v + 1, v + 2, v + 3, v + 2, v, v + 3)\n }\n flameGeo.setIndex(flameIdx)\n flameGeo.setDrawRange(0, 0)\n const flames = new THREE.Mesh(\n flameGeo,\n new THREE.MeshBasicMaterial({ vertexColors: true, transparent: true, blending: THREE.AdditiveBlending, depthWrite: false, side: THREE.DoubleSide }),\n )\n flames.frustumCulled = false\n flames.visible = false\n flames.name = 'effects:flames'\n group.add(flames)\n\n const hashOf = (id: string): number => {\n let h = 2166136261\n for (let i = 0; i < id.length; i++) h = Math.imul(h ^ id.charCodeAt(i), 16777619)\n return ((h >>> 0) % 1000) / 1000\n }\n\n const writeFlame = (slot: number, e: ActiveEmitter, time: number): void => {\n const pa = flamePos.array as Float32Array\n const ca = flameCol.array as Float32Array\n const k = Math.min(1, Math.max(0, e.intensity))\n const size = e.size > 0 ? e.size : 0.05\n const R = size * 0.8\n const bw = Math.max(0.004, size * 0.13)\n const phase = hashOf(e.id) * 40\n for (let c = 0; c < CONES; c++) {\n const a = (c / CONES) * Math.PI * 2\n const ux = Math.cos(a)\n const uz = Math.sin(a)\n const bx = e.pos[0] + ux * R\n const bz = e.pos[2] + uz * R\n const by = e.pos[1]\n const h = (0.025 + size * 0.45) * k * (0.55 + 0.45 * Math.abs(Math.sin(time * 19 + c * 2.3 + phase)))\n const v = (slot * CONES + c) * 4\n // base triangle: tangent +, tangent -, radial out; apex leans slightly inwards\n const tx = -uz * bw\n const tz = ux * bw\n pa.set([bx + tx, by, bz + tz, bx - tx, by, bz - tz, bx + ux * bw * 0.9, by, bz + uz * bw * 0.9, bx - ux * h * 0.12, by + h, bz - uz * h * 0.12], v * 3)\n const lo = 0.3 * k\n ca.set([0.2 * k, 0.45 * k, 1 * k, 0.2 * k, 0.45 * k, 1 * k, 0.2 * k, 0.45 * k, 1 * k, 0.02 * lo, 0.1 * lo, 0.5 * lo], v * 3)\n }\n }\n\n const advance = (p: Pool, dt: number): void => {\n let i = 0\n const swayX = wind[0]\n const swayZ = wind[1]\n while (i < p.count) {\n p.age[i] += dt\n const age = p.age[i]\n const life = p.life[i]\n if (age >= life || (p.kind === 'water' && p.pos[i * 3 + 1] < p.ground[i])) {\n // swap-remove\n const last = --p.count\n if (i !== last) {\n p.pos.copyWithin(i * 3, last * 3, last * 3 + 3)\n p.vel.copyWithin(i * 3, last * 3, last * 3 + 3)\n p.age[i] = p.age[last]\n p.life[i] = p.life[last]\n p.size0[i] = p.size0[last]\n p.size1[i] = p.size1[last]\n p.tint[i] = p.tint[last]\n p.flags[i] = p.flags[last]\n p.ground[i] = p.ground[last]\n }\n continue\n }\n const j = i * 3\n switch (p.kind) {\n case 'smoke':\n case 'steam':\n p.vel[j] += (swayX - p.vel[j]) * Math.min(1, dt * 0.8)\n p.vel[j + 2] += (swayZ - p.vel[j + 2]) * Math.min(1, dt * 0.8)\n break\n case 'water':\n if (p.flags[i] !== 4) p.vel[j + 1] -= 9.8 * dt\n break\n case 'spark':\n p.vel[j + 1] -= 7 * dt\n break\n case 'fire':\n p.vel[j] += (swayX * 0.1 - p.vel[j]) * Math.min(1, dt * 2)\n break\n }\n p.pos[j] += p.vel[j] * dt\n p.pos[j + 1] += p.vel[j + 1] * dt\n p.pos[j + 2] += p.vel[j + 2] * dt\n if (p.kind === 'water' && p.pos[j + 1] < p.ground[i]) {\n // landed: stay on the surface and fade out this frame\n p.pos[j + 1] = p.ground[i]\n p.age[i] = p.life[i]\n }\n i++\n }\n }\n\n const write = (p: Pool): void => {\n const n = p.count\n const pa = p.aPos.array as Float32Array\n const sa = p.aSize.array as Float32Array\n const aa = p.aAlpha.array as Float32Array\n const ca = p.aColor.array as Float32Array\n for (let i = 0; i < n; i++) {\n const u = p.age[i] / p.life[i]\n const j = i * 3\n pa[j] = p.pos[j]\n pa[j + 1] = p.pos[j + 1]\n pa[j + 2] = p.pos[j + 2]\n sa[i] = p.size0[i] + (p.size1[i] - p.size0[i]) * u\n let alpha: number\n switch (p.kind) {\n case 'smoke':\n alpha = 0.55 * Math.min(1, u * 8) * (1 - u)\n ca[j] = ca[j + 1] = ca[j + 2] = p.tint[i]\n break\n case 'steam':\n alpha = 0.4 * Math.min(1, u * 6) * (1 - u)\n ca[j] = ca[j + 1] = ca[j + 2] = p.tint[i]\n break\n case 'fire':\n alpha = (p.flags[i] ? 0.95 : 0.8) * (1 - u * u)\n if (p.flags[i]) {\n ca[j] = 0.25 + 0.15 * p.tint[i]\n ca[j + 1] = 0.5 + 0.2 * p.tint[i]\n ca[j + 2] = 1\n } else {\n // yellow core -> orange -> red as it rises\n ca[j] = 1\n ca[j + 1] = Math.max(0.15, 0.85 - u * 0.7 - p.tint[i] * 0.1)\n ca[j + 2] = Math.max(0, 0.25 - u * 0.25)\n }\n break\n case 'spark':\n alpha = 1 - u\n ca[j] = 1\n ca[j + 1] = 0.75 - u * 0.4\n ca[j + 2] = 0.3\n break\n default: {\n const f = p.flags[i]\n alpha = f === 4 ? 0.16 * (1 - u) : f === 3 ? 0.8 * (1 - u * 0.5) : f === 2 ? 0.65 * (1 - u * 0.6) : 0.8\n ca[j] = f === 4 ? 0.85 : 0.5\n ca[j + 1] = f === 4 ? 0.92 : 0.74\n ca[j + 2] = 1\n }\n }\n aa[i] = alpha\n }\n p.aPos.needsUpdate = true\n p.aSize.needsUpdate = true\n p.aAlpha.needsUpdate = true\n p.aColor.needsUpdate = true\n p.points.geometry.setDrawRange(0, n)\n }\n\n return {\n objects,\n meshes: { group, streams, flames },\n update(dt, emitters, cam) {\n camera = cam ?? (typeof cameraSource === 'function' ? cameraSource() : cameraSource)\n refreshScale(camera)\n const step = Math.min(Math.max(dt, 0), 0.1)\n stamp++\n clock += step\n streaksTex.offset.y = -(clock * 2.4) % 1\n let nStreams = 0\n let nFlames = 0\n const due = (key: string, rate: number): number => {\n let d = debt.get(key)\n if (!d) {\n d = { carry: 0, stamp }\n debt.set(key, d)\n }\n d.stamp = stamp\n d.carry += rate * step\n const n = Math.floor(d.carry)\n d.carry -= n\n return n\n }\n for (let i = 0; i < emitters.length; i++) {\n const e = emitters[i]\n if (!(e.intensity > 0)) continue\n const k = Math.min(1, e.intensity)\n const size = e.size > 0 ? e.size : 0.05\n if (e.kind === 'water') {\n if (k < 0.25) {\n const n = due(e.id, 3 + k * 10)\n if (n > 0) emitDrip(e, n)\n } else if (arcOf(e, k)) {\n const nSpray = due(e.id, (60 + 260 * Math.min(size, 0.4)) * k)\n const nSplash = due(`${e.id}#s`, (45 + 90 * Math.min(size, 0.4)) * k)\n const nMist = due(`${e.id}#m`, 6 * k * (1 + size * 4))\n emitWater(e, k, nSpray, nSplash, nMist)\n if (nStreams < streams.length) writeStream(streams[nStreams++], k)\n }\n continue\n }\n if (e.kind === 'flame' && nFlames < maxFlames) writeFlame(nFlames++, e, clock)\n const n = due(e.id, RATE[e.kind] * k)\n if (n > 0) emitFrom(e, n)\n }\n for (let i = nStreams; i < streams.length; i++) streams[i].visible = false\n flames.visible = nFlames > 0\n flameGeo.setDrawRange(0, nFlames * CONES * 9)\n flamePos.needsUpdate = true\n flameCol.needsUpdate = true\n if (debt.size > emitters.length * 3 + 8) {\n for (const [id, d] of debt) if (d.stamp !== stamp) debt.delete(id)\n }\n for (const kind of POOLS) {\n advance(pools[kind], step)\n write(pools[kind])\n }\n },\n setWind(x, z) {\n wind[0] = x\n wind[1] = z\n },\n setCamera(cam) {\n cameraSource = cam\n camera = typeof cam === 'function' ? cam() : cam\n refreshScale(camera)\n },\n setViewHeight(px) {\n viewHeight = px\n refreshScale(camera)\n },\n stats() {\n const byKind = {} as Record<PoolKind, number>\n let alive = 0\n let capacity = 0\n for (const kind of POOLS) {\n byKind[kind] = pools[kind].count\n alive += pools[kind].count\n capacity += pools[kind].cap\n }\n return { alive, capacity, byKind }\n },\n clear() {\n for (const kind of POOLS) {\n pools[kind].count = 0\n write(pools[kind])\n }\n debt.clear()\n for (const m of streams) m.visible = false\n flames.visible = false\n },\n dispose() {\n for (const kind of POOLS) {\n scene.remove(objects[kind])\n objects[kind].geometry.dispose()\n ;(objects[kind].material as ThreeNS.Material).dispose()\n }\n scene.remove(group)\n for (const m of streams) m.geometry.dispose()\n flameGeo.dispose()\n ;(flames.material as ThreeNS.Material).dispose()\n streamMat.dispose()\n streaksTex.dispose()\n sprite.dispose()\n },\n }\n}\n","import type { Color } from './types'\n\n/** A colour as `0xRRGGBB`: numbers as given (floored, as three's `setHex`), `'#rrggbb'` strings parsed. */\nexport function colorHex(color: Color | undefined, fallback = 0xffffff): number {\n if (color === undefined) return fallback\n if (typeof color === 'number') return Math.floor(color)\n const match = /^#([0-9a-fA-F]{6})$/.exec(color)\n if (!match) throw new Error(`game-shapes: colour \"${color}\" is not 0xRRGGBB or #rrggbb`)\n return parseInt(match[1] as string, 16)\n}\n\n/** three's `SRGBToLinear` (r160 `ColorManagement`), the same constants. */\nexport const srgbToLinear = (c: number): number => (c < 0.04045 ? c * 0.0773993808 : Math.pow(c * 0.9478672986 + 0.0521327014, 2.4))\n\n/**\n * The rgb floats of a hex colour as three's `Color.setHex` stores them: the channels over 255 (`'srgb'`), converted to the linear working space\n * when colour management is on (`'linear'`).\n */\nexport function colorChannels(hex: number, space: 'linear' | 'srgb'): [number, number, number] {\n const r = ((hex >> 16) & 255) / 255\n const g = ((hex >> 8) & 255) / 255\n const b = (hex & 255) / 255\n return space === 'linear' ? [srgbToLinear(r), srgbToLinear(g), srgbToLinear(b)] : [r, g, b]\n}\n","/**\n * Canonical JSON (keys sorted, `undefined` left out, `-0` kept as `-0`, typed arrays as plain lists) and a 64-bit FNV-1a of it in two 32-bit\n * lanes: the key a shape is cached and shared by.\n */\nexport function canonicalJson(value: unknown): string {\n if (value === null) return 'null'\n if (typeof value === 'number') return Object.is(value, -0) ? '-0' : Number.isFinite(value) ? String(value) : JSON.stringify(String(value))\n if (typeof value === 'string') return JSON.stringify(value)\n if (typeof value === 'boolean') return value ? 'true' : 'false'\n if (typeof value === 'function') throw new Error('game-shapes: a function cannot be part of a shape (register it as a generator)')\n if (ArrayBuffer.isView(value)) return canonicalJson(Array.from(value as unknown as ArrayLike<number>))\n if (Array.isArray(value)) return `[${value.map((item) => (item === undefined ? 'null' : canonicalJson(item))).join(',')}]`\n if (typeof value === 'object') {\n const record = value as Record<string, unknown>\n const keys = Object.keys(record).filter((key) => record[key] !== undefined).sort()\n return `{${keys.map((key) => `${JSON.stringify(key)}:${canonicalJson(record[key])}`).join(',')}}`\n }\n return 'null'\n}\n\nconst lane = (text: string, offset: number, prime: number): string => {\n let hash = offset\n for (let index = 0; index < text.length; index++) {\n hash ^= text.charCodeAt(index)\n hash = Math.imul(hash, prime)\n }\n return (hash >>> 0).toString(16).padStart(8, '0')\n}\n\n/** 64-bit FNV-1a of a text as 16 hex digits (two independent 32-bit lanes). */\nexport const hash64 = (text: string): string => lane(text, 0x811c9dc5, 0x01000193) + lane(text, 0x050c5d1f, 0x5bd1e995)\n\n/** `hash64(canonicalJson(value))`. */\nexport const hashValue = (value: unknown): string => hash64(canonicalJson(value))\n","/*\n * Matrix and quaternion arithmetic as three.js r160 does it (MIT, https://github.com/mrdoob/three.js): the same expressions in the same order, so a\n * vertex baked here has the same bits as one baked by `Matrix4.compose` / `multiplyMatrices` / `Matrix3.getNormalMatrix` / `Vector3.normalize`.\n * Matrices are column-major arrays of 16 (4 x 4) or 9 (3 x 3) numbers, the layout of three's `elements`.\n */\nimport type { Mat4, RotationOrder, Transform, Vec3 } from './types'\n\nexport type Matrix4 = number[]\nexport type Matrix3 = number[]\n\nexport const identity4 = (): Matrix4 => [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]\n\n/** `Quaternion.setFromEuler` (x, y, z, w). */\nexport function quaternionFromEuler(x: number, y: number, z: number, order: RotationOrder = 'XYZ'): [number, number, number, number] {\n const cos = Math.cos\n const sin = Math.sin\n const c1 = cos(x / 2)\n const c2 = cos(y / 2)\n const c3 = cos(z / 2)\n const s1 = sin(x / 2)\n const s2 = sin(y / 2)\n const s3 = sin(z / 2)\n switch (order) {\n case 'XYZ':\n return [s1 * c2 * c3 + c1 * s2 * s3, c1 * s2 * c3 - s1 * c2 * s3, c1 * c2 * s3 + s1 * s2 * c3, c1 * c2 * c3 - s1 * s2 * s3]\n case 'YXZ':\n return [s1 * c2 * c3 + c1 * s2 * s3, c1 * s2 * c3 - s1 * c2 * s3, c1 * c2 * s3 - s1 * s2 * c3, c1 * c2 * c3 + s1 * s2 * s3]\n case 'ZXY':\n return [s1 * c2 * c3 - c1 * s2 * s3, c1 * s2 * c3 + s1 * c2 * s3, c1 * c2 * s3 + s1 * s2 * c3, c1 * c2 * c3 - s1 * s2 * s3]\n case 'ZYX':\n return [s1 * c2 * c3 - c1 * s2 * s3, c1 * s2 * c3 + s1 * c2 * s3, c1 * c2 * s3 - s1 * s2 * c3, c1 * c2 * c3 + s1 * s2 * s3]\n case 'YZX':\n return [s1 * c2 * c3 + c1 * s2 * s3, c1 * s2 * c3 + s1 * c2 * s3, c1 * c2 * s3 - s1 * s2 * c3, c1 * c2 * c3 - s1 * s2 * s3]\n case 'XZY':\n return [s1 * c2 * c3 - c1 * s2 * s3, c1 * s2 * c3 - s1 * c2 * s3, c1 * c2 * s3 + s1 * s2 * c3, c1 * c2 * c3 + s1 * s2 * s3]\n default:\n throw new Error(`game-shapes: unknown rotation order \"${String(order)}\"`)\n }\n}\n\n/** `Matrix4.compose(position, quaternion, scale)`. */\nexport function compose(px: number, py: number, pz: number, q: readonly [number, number, number, number], sx: number, sy: number, sz: number): Matrix4 {\n const [x, y, z, w] = q\n const x2 = x + x\n const y2 = y + y\n const z2 = z + z\n const xx = x * x2\n const xy = x * y2\n const xz = x * z2\n const yy = y * y2\n const yz = y * z2\n const zz = z * z2\n const wx = w * x2\n const wy = w * y2\n const wz = w * z2\n return [\n (1 - (yy + zz)) * sx, (xy + wz) * sx, (xz - wy) * sx, 0,\n (xy - wz) * sy, (1 - (xx + zz)) * sy, (yz + wx) * sy, 0,\n (xz + wy) * sz, (yz - wx) * sz, (1 - (xx + yy)) * sz, 0,\n px, py, pz, 1,\n ]\n}\n\n/** `Matrix4.makeRotationY(theta)` followed by `setPosition(x, y, z)`. */\nexport function headingMatrix(theta: number, x = 0, y = 0, z = 0): Matrix4 {\n const c = Math.cos(theta)\n const s = Math.sin(theta)\n return [c, 0, -s, 0, 0, 1, 0, 0, s, 0, c, 0, x, y, z, 1]\n}\n\n/** `Matrix4.multiplyMatrices(a, b)`: a x b. */\nexport function multiply(ae: readonly number[], be: readonly number[]): Matrix4 {\n const a11 = ae[0]!, a12 = ae[4]!, a13 = ae[8]!, a14 = ae[12]!\n const a21 = ae[1]!, a22 = ae[5]!, a23 = ae[9]!, a24 = ae[13]!\n const a31 = ae[2]!, a32 = ae[6]!, a33 = ae[10]!, a34 = ae[14]!\n const a41 = ae[3]!, a42 = ae[7]!, a43 = ae[11]!, a44 = ae[15]!\n const b11 = be[0]!, b12 = be[4]!, b13 = be[8]!, b14 = be[12]!\n const b21 = be[1]!, b22 = be[5]!, b23 = be[9]!, b24 = be[13]!\n const b31 = be[2]!, b32 = be[6]!, b33 = be[10]!, b34 = be[14]!\n const b41 = be[3]!, b42 = be[7]!, b43 = be[11]!, b44 = be[15]!\n return [\n a11 * b11 + a12 * b21 + a13 * b31 + a14 * b41,\n a21 * b11 + a22 * b21 + a23 * b31 + a24 * b41,\n a31 * b11 + a32 * b21 + a33 * b31 + a34 * b41,\n a41 * b11 + a42 * b21 + a43 * b31 + a44 * b41,\n a11 * b12 + a12 * b22 + a13 * b32 + a14 * b42,\n a21 * b12 + a22 * b22 + a23 * b32 + a24 * b42,\n a31 * b12 + a32 * b22 + a33 * b32 + a34 * b42,\n a41 * b12 + a42 * b22 + a43 * b32 + a44 * b42,\n a11 * b13 + a12 * b23 + a13 * b33 + a14 * b43,\n a21 * b13 + a22 * b23 + a23 * b33 + a24 * b43,\n a31 * b13 + a32 * b23 + a33 * b33 + a34 * b43,\n a41 * b13 + a42 * b23 + a43 * b33 + a44 * b43,\n a11 * b14 + a12 * b24 + a13 * b34 + a14 * b44,\n a21 * b14 + a22 * b24 + a23 * b34 + a24 * b44,\n a31 * b14 + a32 * b24 + a33 * b34 + a34 * b44,\n a41 * b14 + a42 * b24 + a43 * b34 + a44 * b44,\n ]\n}\n\n/** `Matrix3.getNormalMatrix(m)`: the inverse transpose of the upper 3 x 3 (all zeros when singular, as three). */\nexport function normalMatrix(me: readonly number[]): Matrix3 {\n // setFromMatrix4\n const n11 = me[0]!, n21 = me[1]!, n31 = me[2]!\n const n12 = me[4]!, n22 = me[5]!, n32 = me[6]!\n const n13 = me[8]!, n23 = me[9]!, n33 = me[10]!\n // invert\n const t11 = n33 * n22 - n32 * n23\n const t12 = n32 * n13 - n33 * n12\n const t13 = n23 * n12 - n22 * n13\n const det = n11 * t11 + n21 * t12 + n31 * t13\n if (det === 0) return [0, 0, 0, 0, 0, 0, 0, 0, 0]\n const detInv = 1 / det\n const te = [\n t11 * detInv,\n (n31 * n23 - n33 * n21) * detInv,\n (n32 * n21 - n31 * n22) * detInv,\n t12 * detInv,\n (n33 * n11 - n31 * n13) * detInv,\n (n31 * n12 - n32 * n11) * detInv,\n t13 * detInv,\n (n21 * n13 - n23 * n11) * detInv,\n (n22 * n11 - n21 * n12) * detInv,\n ]\n // transpose\n let tmp = te[1]!\n te[1] = te[3]!\n te[3] = tmp\n tmp = te[2]!\n te[2] = te[6]!\n te[6] = tmp\n tmp = te[5]!\n te[5] = te[7]!\n te[7] = tmp\n return te\n}\n\n/** True when the last row is (0, 0, 0, 1): `applyMatrix4`'s divide by w is then a multiply by exactly 1. */\nexport const isAffine = (e: readonly number[]): boolean => e[3] === 0 && e[7] === 0 && e[11] === 0 && e[15] === 1\n\n/** `Vector3.applyMatrix4`. */\nexport function transformPoint(e: readonly number[], x: number, y: number, z: number): [number, number, number] {\n const w = 1 / (e[3]! * x + e[7]! * y + e[11]! * z + e[15]!)\n return [(e[0]! * x + e[4]! * y + e[8]! * z + e[12]!) * w, (e[1]! * x + e[5]! * y + e[9]! * z + e[13]!) * w, (e[2]! * x + e[6]! * y + e[10]! * z + e[14]!) * w]\n}\n\nconst toVec3 = (scale: number | Vec3 | undefined): Vec3 => (scale === undefined ? [1, 1, 1] : typeof scale === 'number' ? [scale, scale, scale] : scale)\n\n/** True when the transform has no field set. */\nexport const isIdentityTransform = (t: Transform): boolean =>\n t.matrix === undefined && t.heading === undefined && t.position === undefined && t.rotation === undefined && t.scale === undefined\n\n/** The local matrix of a transform, or `null` when it has none. */\nexport function matrixOf(t: Transform): Matrix4 | null {\n if (t.matrix !== undefined) return [...t.matrix]\n if (t.heading !== undefined) {\n const p = t.position ?? [0, 0, 0]\n return headingMatrix(t.heading, p[0], p[1], p[2])\n }\n if (t.position === undefined && t.rotation === undefined && t.scale === undefined) return null\n const p = t.position ?? [0, 0, 0]\n const r = t.rotation ?? [0, 0, 0]\n const s = toVec3(t.scale)\n return compose(p[0], p[1], p[2], quaternionFromEuler(r[0], r[1], r[2], t.rotationOrder ?? 'XYZ'), s[0], s[1], s[2])\n}\n\n/** The matrix as given, as a plain array (for `ShapePart.matrix`). */\nexport const asMat4 = (m: Matrix4 | null): Mat4 => (m ? [...m] : identity4())\n","/*\n * The geometry generators of three.js r160 (MIT, https://github.com/mrdoob/three.js, src/geometries/*.js), ported line by line: the same loops,\n * the same expressions in the same order, numbers pushed into plain arrays and stored as Float32 at the end, exactly as `Float32BufferAttribute`\n * does. A test compares every array of every generator with three's, bit for bit, over a grid of parameters.\n */\nimport type { GeometryGroup, PrimitiveBuffers } from '../types'\n\nexport interface Arrays {\n vertices: number[]\n normals: number[]\n uvs: number[]\n indices: number[] | null\n groups: GeometryGroup[]\n}\n\n/** three's `arrayNeedsUint32`: 32-bit indices once any index reaches 65535. */\nexport function needsUint32(array: ArrayLike<number>): boolean {\n for (let i = array.length - 1; i >= 0; --i) if ((array[i] as number) >= 65535) return true\n return false\n}\n\nexport const indexArray = (indices: ArrayLike<number>): Uint16Array | Uint32Array => (needsUint32(indices) ? new Uint32Array(indices) : new Uint16Array(indices))\n\nexport function pack(type: string, parameters: Record<string, unknown>, a: Arrays): PrimitiveBuffers {\n return {\n type,\n parameters,\n positions: new Float32Array(a.vertices),\n normals: new Float32Array(a.normals),\n uvs: new Float32Array(a.uvs),\n ...(a.indices ? { indices: indexArray(a.indices) } : {}),\n groups: a.groups,\n }\n}\n\nconst normalize3 = (x: number, y: number, z: number): [number, number, number] => {\n const length = Math.sqrt(x * x + y * y + z * z)\n const s = 1 / (length || 1)\n return [x * s, y * s, z * s]\n}\n\nexport function box(width = 1, height = 1, depth = 1, widthSegments = 1, heightSegments = 1, depthSegments = 1): PrimitiveBuffers {\n const parameters = { width, height, depth, widthSegments, heightSegments, depthSegments }\n widthSegments = Math.floor(widthSegments)\n heightSegments = Math.floor(heightSegments)\n depthSegments = Math.floor(depthSegments)\n const indices: number[] = []\n const vertices: number[] = []\n const normals: number[] = []\n const uvs: number[] = []\n const groups: GeometryGroup[] = []\n let numberOfVertices = 0\n let groupStart = 0\n type Axis = 'x' | 'y' | 'z'\n const buildPlane = (u: Axis, v: Axis, w: Axis, udir: number, vdir: number, width: number, height: number, depth: number, gridX: number, gridY: number, materialIndex: number): void => {\n const segmentWidth = width / gridX\n const segmentHeight = height / gridY\n const widthHalf = width / 2\n const heightHalf = height / 2\n const depthHalf = depth / 2\n const gridX1 = gridX + 1\n const gridY1 = gridY + 1\n let vertexCounter = 0\n let groupCount = 0\n const vector = { x: 0, y: 0, z: 0 }\n for (let iy = 0; iy < gridY1; iy++) {\n const y = iy * segmentHeight - heightHalf\n for (let ix = 0; ix < gridX1; ix++) {\n const x = ix * segmentWidth - widthHalf\n vector[u] = x * udir\n vector[v] = y * vdir\n vector[w] = depthHalf\n vertices.push(vector.x, vector.y, vector.z)\n vector[u] = 0\n vector[v] = 0\n vector[w] = depth > 0 ? 1 : -1\n normals.push(vector.x, vector.y, vector.z)\n uvs.push(ix / gridX)\n uvs.push(1 - iy / gridY)\n vertexCounter += 1\n }\n }\n for (let iy = 0; iy < gridY; iy++) {\n for (let ix = 0; ix < gridX; ix++) {\n const a = numberOfVertices + ix + gridX1 * iy\n const b = numberOfVertices + ix + gridX1 * (iy + 1)\n const c = numberOfVertices + (ix + 1) + gridX1 * (iy + 1)\n const d = numberOfVertices + (ix + 1) + gridX1 * iy\n indices.push(a, b, d)\n indices.push(b, c, d)\n groupCount += 6\n }\n }\n groups.push({ start: groupStart, count: groupCount, materialIndex })\n groupStart += groupCount\n numberOfVertices += vertexCounter\n }\n buildPlane('z', 'y', 'x', -1, -1, depth, height, width, depthSegments, heightSegments, 0)\n buildPlane('z', 'y', 'x', 1, -1, depth, height, -width, depthSegments, heightSegments, 1)\n buildPlane('x', 'z', 'y', 1, 1, width, depth, height, widthSegments, depthSegments, 2)\n buildPlane('x', 'z', 'y', 1, -1, width, depth, -height, widthSegments, depthSegments, 3)\n buildPlane('x', 'y', 'z', 1, -1, width, height, depth, widthSegments, heightSegments, 4)\n buildPlane('x', 'y', 'z', -1, -1, width, height, -depth, widthSegments, heightSegments, 5)\n return pack('BoxGeometry', parameters, { vertices, normals, uvs, indices, groups })\n}\n\nfunction cylinderArrays(radiusTop: number, radiusBottom: number, height: number, radialSegments: number, heightSegments: number, openEnded: boolean, thetaStart: number, thetaLength: number): Arrays {\n radialSegments = Math.floor(radialSegments)\n heightSegments = Math.floor(heightSegments)\n const indices: number[] = []\n const vertices: number[] = []\n const normals: number[] = []\n const uvs: number[] = []\n const groups: GeometryGroup[] = []\n let index = 0\n const indexArray: number[][] = []\n const halfHeight = height / 2\n let groupStart = 0\n\n const generateTorso = (): void => {\n let groupCount = 0\n const slope = (radiusBottom - radiusTop) / height\n for (let y = 0; y <= heightSegments; y++) {\n const indexRow: number[] = []\n const v = y / heightSegments\n const radius = v * (radiusBottom - radiusTop) + radiusTop\n for (let x = 0; x <= radialSegments; x++) {\n const u = x / radialSegments\n const theta = u * thetaLength + thetaStart\n const sinTheta = Math.sin(theta)\n const cosTheta = Math.cos(theta)\n vertices.push(radius * sinTheta, -v * height + halfHeight, radius * cosTheta)\n const n = normalize3(sinTheta, slope, cosTheta)\n normals.push(n[0], n[1], n[2])\n uvs.push(u, 1 - v)\n indexRow.push(index++)\n }\n indexArray.push(indexRow)\n }\n for (let x = 0; x < radialSegments; x++) {\n for (let y = 0; y < heightSegments; y++) {\n const a = indexArray[y]![x]!\n const b = indexArray[y + 1]![x]!\n const c = indexArray[y + 1]![x + 1]!\n const d = indexArray[y]![x + 1]!\n indices.push(a, b, d)\n indices.push(b, c, d)\n groupCount += 6\n }\n }\n groups.push({ start: groupStart, count: groupCount, materialIndex: 0 })\n groupStart += groupCount\n }\n\n const generateCap = (top: boolean): void => {\n const centerIndexStart = index\n let groupCount = 0\n const radius = top === true ? radiusTop : radiusBottom\n const sign = top === true ? 1 : -1\n for (let x = 1; x <= radialSegments; x++) {\n vertices.push(0, halfHeight * sign, 0)\n normals.push(0, sign, 0)\n uvs.push(0.5, 0.5)\n index++\n }\n const centerIndexEnd = index\n for (let x = 0; x <= radialSegments; x++) {\n const u = x / radialSegments\n const theta = u * thetaLength + thetaStart\n const cosTheta = Math.cos(theta)\n const sinTheta = Math.sin(theta)\n vertices.push(radius * sinTheta, halfHeight * sign, radius * cosTheta)\n normals.push(0, sign, 0)\n uvs.push(cosTheta * 0.5 + 0.5, sinTheta * 0.5 * sign + 0.5)\n index++\n }\n for (let x = 0; x < radialSegments; x++) {\n const c = centerIndexStart + x\n const i = centerIndexEnd + x\n if (top === true) indices.push(i, i + 1, c)\n else indices.push(i + 1, i, c)\n groupCount += 3\n }\n groups.push({ start: groupStart, count: groupCount, materialIndex: top === true ? 1 : 2 })\n groupStart += groupCount\n }\n\n generateTorso()\n if (openEnded === false) {\n if (radiusTop > 0) generateCap(true)\n if (radiusBottom > 0) generateCap(false)\n }\n return { vertices, normals, uvs, indices, groups }\n}\n\nexport function cylinder(radiusTop = 1, radiusBottom = 1, height = 1, radialSegments = 32, heightSegments = 1, openEnded = false, thetaStart = 0, thetaLength = Math.PI * 2): PrimitiveBuffers {\n const parameters = { radiusTop, radiusBottom, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength }\n return pack('CylinderGeometry', parameters, cylinderArrays(radiusTop, radiusBottom, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength))\n}\n\nexport function cone(radius = 1, height = 1, radialSegments = 32, heightSegments = 1, openEnded = false, thetaStart = 0, thetaLength = Math.PI * 2): PrimitiveBuffers {\n const parameters = { radius, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength }\n return pack('ConeGeometry', parameters, cylinderArrays(0, radius, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength))\n}\n\nexport function sphere(radius = 1, widthSegments = 32, heightSegments = 16, phiStart = 0, phiLength = Math.PI * 2, thetaStart = 0, thetaLength = Math.PI): PrimitiveBuffers {\n const parameters = { radius, widthSegments, heightSegments, phiStart, phiLength, thetaStart, thetaLength }\n widthSegments = Math.max(3, Math.floor(widthSegments))\n heightSegments = Math.max(2, Math.floor(heightSegments))\n const thetaEnd = Math.min(thetaStart + thetaLength, Math.PI)\n let index = 0\n const grid: number[][] = []\n const indices: number[] = []\n const vertices: number[] = []\n const normals: number[] = []\n const uvs: number[] = []\n for (let iy = 0; iy <= heightSegments; iy++) {\n const verticesRow: number[] = []\n const v = iy / heightSegments\n let uOffset = 0\n if (iy === 0 && thetaStart === 0) uOffset = 0.5 / widthSegments\n else if (iy === heightSegments && thetaEnd === Math.PI) uOffset = -0.5 / widthSegments\n for (let ix = 0; ix <= widthSegments; ix++) {\n const u = ix / widthSegments\n const x = -radius * Math.cos(phiStart + u * phiLength) * Math.sin(thetaStart + v * thetaLength)\n const y = radius * Math.cos(thetaStart + v * thetaLength)\n const z = radius * Math.sin(phiStart + u * phiLength) * Math.sin(thetaStart + v * thetaLength)\n vertices.push(x, y, z)\n const n = normalize3(x, y, z)\n normals.push(n[0], n[1], n[2])\n uvs.push(u + uOffset, 1 - v)\n verticesRow.push(index++)\n }\n grid.push(verticesRow)\n }\n for (let iy = 0; iy < heightSegments; iy++) {\n for (let ix = 0; ix < widthSegments; ix++) {\n const a = grid[iy]![ix + 1]!\n const b = grid[iy]![ix]!\n const c = grid[iy + 1]![ix]!\n const d = grid[iy + 1]![ix + 1]!\n if (iy !== 0 || thetaStart > 0) indices.push(a, b, d)\n if (iy !== heightSegments - 1 || thetaEnd < Math.PI) indices.push(b, c, d)\n }\n }\n return pack('SphereGeometry', parameters, { vertices, normals, uvs, indices, groups: [] })\n}\n\nexport function plane(width = 1, height = 1, widthSegments = 1, heightSegments = 1): PrimitiveBuffers {\n const parameters = { width, height, widthSegments, heightSegments }\n const widthHalf = width / 2\n const heightHalf = height / 2\n const gridX = Math.floor(widthSegments)\n const gridY = Math.floor(heightSegments)\n const gridX1 = gridX + 1\n const gridY1 = gridY + 1\n const segmentWidth = width / gridX\n const segmentHeight = height / gridY\n const indices: number[] = []\n const vertices: number[] = []\n const normals: number[] = []\n const uvs: number[] = []\n for (let iy = 0; iy < gridY1; iy++) {\n const y = iy * segmentHeight - heightHalf\n for (let ix = 0; ix < gridX1; ix++) {\n const x = ix * segmentWidth - widthHalf\n vertices.push(x, -y, 0)\n normals.push(0, 0, 1)\n uvs.push(ix / gridX)\n uvs.push(1 - iy / gridY)\n }\n }\n for (let iy = 0; iy < gridY; iy++) {\n for (let ix = 0; ix < gridX; ix++) {\n const a = ix + gridX1 * iy\n const b = ix + gridX1 * (iy + 1)\n const c = ix + 1 + gridX1 * (iy + 1)\n const d = ix + 1 + gridX1 * iy\n indices.push(a, b, d)\n indices.push(b, c, d)\n }\n }\n return pack('PlaneGeometry', parameters, { vertices, normals, uvs, indices, groups: [] })\n}\n\nexport function circle(radius = 1, segments = 32, thetaStart = 0, thetaLength = Math.PI * 2): PrimitiveBuffers {\n const parameters = { radius, segments, thetaStart, thetaLength }\n segments = Math.max(3, segments)\n const indices: number[] = []\n const vertices: number[] = []\n const normals: number[] = []\n const uvs: number[] = []\n vertices.push(0, 0, 0)\n normals.push(0, 0, 1)\n uvs.push(0.5, 0.5)\n for (let s = 0, i = 3; s <= segments; s++, i += 3) {\n const segment = thetaStart + (s / segments) * thetaLength\n // the vector's z stays 0, as three's Vector3\n vertices.push(radius * Math.cos(segment), radius * Math.sin(segment), 0)\n normals.push(0, 0, 1)\n uvs.push(((vertices[i] as number) / radius + 1) / 2, ((vertices[i + 1] as number) / radius + 1) / 2)\n }\n for (let i = 1; i <= segments; i++) indices.push(i, i + 1, 0)\n return pack('CircleGeometry', parameters, { vertices, normals, uvs, indices, groups: [] })\n}\n\nexport function ring(innerRadius = 0.5, outerRadius = 1, thetaSegments = 32, phiSegments = 1, thetaStart = 0, thetaLength = Math.PI * 2): PrimitiveBuffers {\n const parameters = { innerRadius, outerRadius, thetaSegments, phiSegments, thetaStart, thetaLength }\n thetaSegments = Math.max(3, thetaSegments)\n phiSegments = Math.max(1, phiSegments)\n const indices: number[] = []\n const vertices: number[] = []\n const normals: number[] = []\n const uvs: number[] = []\n let radius = innerRadius\n const radiusStep = (outerRadius - innerRadius) / phiSegments\n for (let j = 0; j <= phiSegments; j++) {\n for (let i = 0; i <= thetaSegments; i++) {\n const segment = thetaStart + (i / thetaSegments) * thetaLength\n const x = radius * Math.cos(segment)\n const y = radius * Math.sin(segment)\n vertices.push(x, y, 0)\n normals.push(0, 0, 1)\n uvs.push((x / outerRadius + 1) / 2, (y / outerRadius + 1) / 2)\n }\n radius += radiusStep\n }\n for (let j = 0; j < phiSegments; j++) {\n const thetaSegmentLevel = j * (thetaSegments + 1)\n for (let i = 0; i < thetaSegments; i++) {\n const segment = i + thetaSegmentLevel\n const a = segment\n const b = segment + thetaSegments + 1\n const c = segment + thetaSegments + 2\n const d = segment + 1\n indices.push(a, b, d)\n indices.push(b, c, d)\n }\n }\n return pack('RingGeometry', parameters, { vertices, normals, uvs, indices, groups: [] })\n}\n\nexport function lathe(points: ReadonlyArray<readonly [number, number]>, segments = 12, phiStart = 0, phiLength = Math.PI * 2): PrimitiveBuffers {\n const parameters = { points: points.map(([x, y]) => ({ x, y })), segments, phiStart, phiLength }\n segments = Math.floor(segments)\n phiLength = Math.max(0, Math.min(Math.PI * 2, phiLength))\n const indices: number[] = []\n const vertices: number[] = []\n const uvs: number[] = []\n const initNormals: number[] = []\n const normals: number[] = []\n const inverseSegments = 1.0 / segments\n const normal = { x: 0, y: 0, z: 0 }\n const curNormal = { x: 0, y: 0, z: 0 }\n const prevNormal = { x: 0, y: 0, z: 0 }\n let dx = 0\n let dy = 0\n const last = points.length - 1\n const px = (j: number): number => (points[j] as readonly [number, number])[0]\n const py = (j: number): number => (points[j] as readonly [number, number])[1]\n const setNormalized = (): void => {\n const n = normalize3(normal.x, normal.y, normal.z)\n normal.x = n[0]\n normal.y = n[1]\n normal.z = n[2]\n }\n for (let j = 0; j <= last; j++) {\n if (j === 0) {\n dx = px(j + 1) - px(j)\n dy = py(j + 1) - py(j)\n normal.x = dy * 1.0\n normal.y = -dx\n normal.z = dy * 0.0\n prevNormal.x = normal.x\n prevNormal.y = normal.y\n prevNormal.z = normal.z\n setNormalized()\n initNormals.push(normal.x, normal.y, normal.z)\n } else if (j === last) {\n initNormals.push(prevNormal.x, prevNormal.y, prevNormal.z)\n } else {\n dx = px(j + 1) - px(j)\n dy = py(j + 1) - py(j)\n normal.x = dy * 1.0\n normal.y = -dx\n normal.z = dy * 0.0\n curNormal.x = normal.x\n curNormal.y = normal.y\n curNormal.z = normal.z\n normal.x += prevNormal.x\n normal.y += prevNormal.y\n normal.z += prevNormal.z\n setNormalized()\n initNormals.push(normal.x, normal.y, normal.z)\n prevNormal.x = curNormal.x\n prevNormal.y = curNormal.y\n prevNormal.z = curNormal.z\n }\n }\n for (let i = 0; i <= segments; i++) {\n const phi = phiStart + i * inverseSegments * phiLength\n const sin = Math.sin(phi)\n const cos = Math.cos(phi)\n for (let j = 0; j <= last; j++) {\n vertices.push(px(j) * sin, py(j), px(j) * cos)\n uvs.push(i / segments, j / (points.length - 1))\n const x = (initNormals[3 * j + 0] as number) * sin\n const y = initNormals[3 * j + 1] as number\n const z = (initNormals[3 * j + 0] as number) * cos\n normals.push(x, y, z)\n }\n }\n for (let i = 0; i < segments; i++) {\n for (let j = 0; j < points.length - 1; j++) {\n const base = j + i * points.length\n const a = base\n const b = base + points.length\n const c = base + points.length + 1\n const d = base + 1\n indices.push(a, b, d)\n indices.push(c, d, b)\n }\n }\n return pack('LatheGeometry', parameters, { vertices, normals, uvs, indices, groups: [] })\n}\n\nexport function torus(radius = 1, tube = 0.4, radialSegments = 12, tubularSegments = 48, arc = Math.PI * 2): PrimitiveBuffers {\n const parameters = { radius, tube, radialSegments, tubularSegments, arc }\n radialSegments = Math.floor(radialSegments)\n tubularSegments = Math.floor(tubularSegments)\n const indices: number[] = []\n const vertices: number[] = []\n const normals: number[] = []\n const uvs: number[] = []\n for (let j = 0; j <= radialSegments; j++) {\n for (let i = 0; i <= tubularSegments; i++) {\n const u = (i / tubularSegments) * arc\n const v = (j / radialSegments) * Math.PI * 2\n const x = (radius + tube * Math.cos(v)) * Math.cos(u)\n const y = (radius + tube * Math.cos(v)) * Math.sin(u)\n const z = tube * Math.sin(v)\n vertices.push(x, y, z)\n const cx = radius * Math.cos(u)\n const cy = radius * Math.sin(u)\n const n = normalize3(x - cx, y - cy, z - 0)\n normals.push(n[0], n[1], n[2])\n uvs.push(i / tubularSegments)\n uvs.push(j / radialSegments)\n }\n }\n for (let j = 1; j <= radialSegments; j++) {\n for (let i = 1; i <= tubularSegments; i++) {\n const a = (tubularSegments + 1) * j + i - 1\n const b = (tubularSegments + 1) * (j - 1) + i - 1\n const c = (tubularSegments + 1) * (j - 1) + i\n const d = (tubularSegments + 1) * j + i\n indices.push(a, b, d)\n indices.push(b, c, d)\n }\n }\n return pack('TorusGeometry', parameters, { vertices, normals, uvs, indices, groups: [] })\n}\n","import type { HoleSpec, PrimitiveBuffers, Vec3 } from '../types'\nimport { indexArray } from './three-generators'\n\n/**\n * A box (centred, `size = [w, h, d]`) minus rectangular prisms that go all the way through it along z: the cheap, exact boolean (walls with\n * windows and doors). The face is cut into a grid at every hole edge; each solid cell gets its front and back quads, and every cell edge between\n * solid and empty (a hole or the outside) gets a quad over the depth facing the empty side, so the outer sides and the reveals come out of the\n * same rule. Front uvs map the face to [0, 1]; side uvs run along the edge and the depth.\n */\nexport function difference(size: Vec3, subtract: readonly HoleSpec[]): PrimitiveBuffers {\n const [w, h, d] = size\n const hw = w / 2\n const hh = h / 2\n const hd = d / 2\n const holes = subtract\n .map((hole) => ({ x0: Math.max(-hw, Math.min(hole.min[0], hole.max[0])), x1: Math.min(hw, Math.max(hole.min[0], hole.max[0])), y0: Math.max(-hh, Math.min(hole.min[1], hole.max[1])), y1: Math.min(hh, Math.max(hole.min[1], hole.max[1])) }))\n .filter((hole) => hole.x1 > hole.x0 && hole.y1 > hole.y0)\n const cuts = (lo: number, hi: number, values: number[]): number[] => [...new Set([lo, hi, ...values.filter((v) => v > lo && v < hi)])].sort((a, b) => a - b)\n const xs = cuts(-hw, hw, holes.flatMap((hole) => [hole.x0, hole.x1]))\n const ys = cuts(-hh, hh, holes.flatMap((hole) => [hole.y0, hole.y1]))\n const nx = xs.length - 1\n const ny = ys.length - 1\n const solid = (i: number, j: number): boolean => {\n if (i < 0 || j < 0 || i >= nx || j >= ny) return false\n const cx = ((xs[i] as number) + (xs[i + 1] as number)) / 2\n const cy = ((ys[j] as number) + (ys[j + 1] as number)) / 2\n return !holes.some((hole) => cx > hole.x0 && cx < hole.x1 && cy > hole.y0 && cy < hole.y1)\n }\n const positions: number[] = []\n const normals: number[] = []\n const uvs: number[] = []\n const indices: number[] = []\n const quad = (corners: readonly (readonly [number, number, number])[], normal: readonly [number, number, number], uv: readonly (readonly [number, number])[]): void => {\n const base = positions.length / 3\n for (let k = 0; k < 4; k++) {\n positions.push(...(corners[k] as [number, number, number]))\n normals.push(...normal)\n uvs.push(...(uv[k] as [number, number]))\n }\n indices.push(base, base + 1, base + 2, base, base + 2, base + 3)\n }\n const fu = (x: number): number => (x + hw) / w\n const fv = (y: number): number => (y + hh) / h\n for (let j = 0; j < ny; j++) {\n for (let i = 0; i < nx; i++) {\n if (!solid(i, j)) continue\n const x0 = xs[i] as number\n const x1 = xs[i + 1] as number\n const y0 = ys[j] as number\n const y1 = ys[j + 1] as number\n quad([[x0, y0, hd], [x1, y0, hd], [x1, y1, hd], [x0, y1, hd]], [0, 0, 1], [[fu(x0), fv(y0)], [fu(x1), fv(y0)], [fu(x1), fv(y1)], [fu(x0), fv(y1)]])\n quad([[x1, y0, -hd], [x0, y0, -hd], [x0, y1, -hd], [x1, y1, -hd]], [0, 0, -1], [[1 - fu(x1), fv(y0)], [1 - fu(x0), fv(y0)], [1 - fu(x0), fv(y1)], [1 - fu(x1), fv(y1)]])\n const zu = (z: number): number => (z + hd) / d\n if (!solid(i - 1, j)) quad([[x0, y0, -hd], [x0, y0, hd], [x0, y1, hd], [x0, y1, -hd]], [-1, 0, 0], [[zu(-hd), fv(y0)], [zu(hd), fv(y0)], [zu(hd), fv(y1)], [zu(-hd), fv(y1)]])\n if (!solid(i + 1, j)) quad([[x1, y0, hd], [x1, y0, -hd], [x1, y1, -hd], [x1, y1, hd]], [1, 0, 0], [[zu(hd), fv(y0)], [zu(-hd), fv(y0)], [zu(-hd), fv(y1)], [zu(hd), fv(y1)]])\n if (!solid(i, j - 1)) quad([[x0, y0, -hd], [x1, y0, -hd], [x1, y0, hd], [x0, y0, hd]], [0, -1, 0], [[fu(x0), zu(-hd)], [fu(x1), zu(-hd)], [fu(x1), zu(hd)], [fu(x0), zu(hd)]])\n if (!solid(i, j + 1)) quad([[x0, y1, hd], [x1, y1, hd], [x1, y1, -hd], [x0, y1, -hd]], [0, 1, 0], [[fu(x0), zu(hd)], [fu(x1), zu(hd)], [fu(x1), zu(-hd)], [fu(x0), zu(-hd)]])\n }\n }\n return {\n type: 'BufferGeometry',\n parameters: { size: [w, h, d], subtract: holes.map((hole) => ({ min: [hole.x0, hole.y0], max: [hole.x1, hole.y1] })) },\n positions: new Float32Array(positions),\n normals: new Float32Array(normals),\n uvs: new Float32Array(uvs),\n indices: indexArray(indices),\n groups: [],\n }\n}\n","// Copy of three.js r160 src/extras/Earcut.js (MIT), itself a port of mapbox/earcut v2.2.4 (ISC), with types added and the logic kept as is so triangulation matches three\n/**\n * Port from https://github.com/mapbox/earcut (v2.2.4)\n */\n\nconst Earcut = {\n\n\ttriangulate: function ( data: ArrayLike<number>, holeIndices?: ArrayLike<number> | null, dim = 2 ): number[] {\n\n\t\tconst hasHoles = holeIndices && holeIndices.length;\n\t\tconst outerLen = hasHoles ? holeIndices[ 0 ] * dim : data.length;\n\t\tlet outerNode = linkedList( data, 0, outerLen, dim, true );\n\t\tconst triangles: number[] = [];\n\n\t\tif ( ! outerNode || outerNode.next === outerNode.prev ) return triangles;\n\n\t\tlet minX, minY, maxX, maxY, x, y, invSize;\n\n\t\tif ( hasHoles ) outerNode = eliminateHoles( data, holeIndices, outerNode, dim );\n\n\t\t// if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox\n\t\tif ( data.length > 80 * dim ) {\n\n\t\t\tminX = maxX = data[ 0 ];\n\t\t\tminY = maxY = data[ 1 ];\n\n\t\t\tfor ( let i = dim; i < outerLen; i += dim ) {\n\n\t\t\t\tx = data[ i ];\n\t\t\t\ty = data[ i + 1 ];\n\t\t\t\tif ( x < minX ) minX = x;\n\t\t\t\tif ( y < minY ) minY = y;\n\t\t\t\tif ( x > maxX ) maxX = x;\n\t\t\t\tif ( y > maxY ) maxY = y;\n\n\t\t\t}\n\n\t\t\t// minX, minY and invSize are later used to transform coords into integers for z-order calculation\n\t\t\tinvSize = Math.max( maxX - minX, maxY - minY );\n\t\t\tinvSize = invSize !== 0 ? 32767 / invSize : 0;\n\n\t\t}\n\n\t\tearcutLinked( outerNode, triangles, dim, minX, minY, invSize, 0 );\n\n\t\treturn triangles;\n\n\t}\n\n};\n\n// create a circular doubly linked list from polygon points in the specified winding order\nfunction linkedList( data: ArrayLike<number>, start: number, end: number, dim: number, clockwise: boolean ): EarNode | undefined {\n\n\tlet i: number, last: EarNode | undefined;\n\n\tif ( clockwise === ( signedArea( data, start, end, dim ) > 0 ) ) {\n\n\t\tfor ( i = start; i < end; i += dim ) last = insertNode( i, data[ i ], data[ i + 1 ], last );\n\n\t} else {\n\n\t\tfor ( i = end - dim; i >= start; i -= dim ) last = insertNode( i, data[ i ], data[ i + 1 ], last );\n\n\t}\n\n\tif ( last && equals( last, last.next ) ) {\n\n\t\tremoveNode( last );\n\t\tlast = last.next;\n\n\t}\n\n\treturn last;\n\n}\n\n// eliminate colinear or duplicate points\nfunction filterPoints( start: EarNode | null | undefined, end?: EarNode | null ): EarNode | null | undefined {\n\n\tif ( ! start ) return start;\n\tif ( ! end ) end = start;\n\n\tlet p = start,\n\t\tagain;\n\tdo {\n\n\t\tagain = false;\n\n\t\tif ( ! p.steiner && ( equals( p, p.next ) || area( p.prev, p, p.next ) === 0 ) ) {\n\n\t\t\tremoveNode( p );\n\t\t\tp = end = p.prev;\n\t\t\tif ( p === p.next ) break;\n\t\t\tagain = true;\n\n\t\t} else {\n\n\t\t\tp = p.next;\n\n\t\t}\n\n\t} while ( again || p !== end );\n\n\treturn end;\n\n}\n\n// main ear slicing loop which triangulates a polygon (given as a linked list)\nfunction earcutLinked( ear: EarNode | null | undefined, triangles: number[], dim: number, minX: number | undefined, minY: number | undefined, invSize: number | undefined, pass: number ): void {\n\n\tif ( ! ear ) return;\n\n\t// interlink polygon nodes in z-order\n\tif ( ! pass && invSize ) indexCurve( ear, minX as number, minY as number, invSize ); // set with invSize\n\n\tlet stop = ear,\n\t\tprev, next;\n\n\t// iterate through ears, slicing them one by one\n\twhile ( ear.prev !== ear.next ) {\n\n\t\tprev = ear.prev;\n\t\tnext = ear.next;\n\n\t\tif ( invSize ? isEarHashed( ear, minX as number, minY as number, invSize ) : isEar( ear ) ) {\n\n\t\t\t// cut off the triangle\n\t\t\ttriangles.push( prev.i / dim | 0 );\n\t\t\ttriangles.push( ear.i / dim | 0 );\n\t\t\ttriangles.push( next.i / dim | 0 );\n\n\t\t\tremoveNode( ear );\n\n\t\t\t// skipping the next vertex leads to less sliver triangles\n\t\t\tear = next.next;\n\t\t\tstop = next.next;\n\n\t\t\tcontinue;\n\n\t\t}\n\n\t\tear = next;\n\n\t\t// if we looped through the whole remaining polygon and can't find any more ears\n\t\tif ( ear === stop ) {\n\n\t\t\t// try filtering points and slicing again\n\t\t\tif ( ! pass ) {\n\n\t\t\t\tearcutLinked( filterPoints( ear ), triangles, dim, minX, minY, invSize, 1 );\n\n\t\t\t\t// if this didn't work, try curing all small self-intersections locally\n\n\t\t\t} else if ( pass === 1 ) {\n\n\t\t\t\tear = cureLocalIntersections( filterPoints( ear )!, triangles, dim );\n\t\t\t\tearcutLinked( ear, triangles, dim, minX, minY, invSize, 2 );\n\n\t\t\t\t// as a last resort, try splitting the remaining polygon into two\n\n\t\t\t} else if ( pass === 2 ) {\n\n\t\t\t\tsplitEarcut( ear, triangles, dim, minX, minY, invSize );\n\n\t\t\t}\n\n\t\t\tbreak;\n\n\t\t}\n\n\t}\n\n}\n\n// check whether a polygon node forms a valid ear with adjacent nodes\nfunction isEar( ear: EarNode ): boolean {\n\n\tconst a = ear.prev,\n\t\tb = ear,\n\t\tc = ear.next;\n\n\tif ( area( a, b, c ) >= 0 ) return false; // reflex, can't be an ear\n\n\t// now make sure we don't have other points inside the potential ear\n\tconst ax = a.x, bx = b.x, cx = c.x, ay = a.y, by = b.y, cy = c.y;\n\n\t// triangle bbox; min & max are calculated like this for speed\n\tconst x0 = ax < bx ? ( ax < cx ? ax : cx ) : ( bx < cx ? bx : cx ),\n\t\ty0 = ay < by ? ( ay < cy ? ay : cy ) : ( by < cy ? by : cy ),\n\t\tx1 = ax > bx ? ( ax > cx ? ax : cx ) : ( bx > cx ? bx : cx ),\n\t\ty1 = ay > by ? ( ay > cy ? ay : cy ) : ( by > cy ? by : cy );\n\n\tlet p = c.next;\n\twhile ( p !== a ) {\n\n\t\tif ( p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 &&\n\t\t\tpointInTriangle( ax, ay, bx, by, cx, cy, p.x, p.y ) &&\n\t\t\tarea( p.prev, p, p.next ) >= 0 ) return false;\n\t\tp = p.next;\n\n\t}\n\n\treturn true;\n\n}\n\nfunction isEarHashed( ear: EarNode, minX: number, minY: number, invSize: number ): boolean {\n\n\tconst a = ear.prev,\n\t\tb = ear,\n\t\tc = ear.next;\n\n\tif ( area( a, b, c ) >= 0 ) return false; // reflex, can't be an ear\n\n\tconst ax = a.x, bx = b.x, cx = c.x, ay = a.y, by = b.y, cy = c.y;\n\n\t// triangle bbox; min & max are calculated like this for speed\n\tconst x0 = ax < bx ? ( ax < cx ? ax : cx ) : ( bx < cx ? bx : cx ),\n\t\ty0 = ay < by ? ( ay < cy ? ay : cy ) : ( by < cy ? by : cy ),\n\t\tx1 = ax > bx ? ( ax > cx ? ax : cx ) : ( bx > cx ? bx : cx ),\n\t\ty1 = ay > by ? ( ay > cy ? ay : cy ) : ( by > cy ? by : cy );\n\n\t// z-order range for the current triangle bbox;\n\tconst minZ = zOrder( x0, y0, minX, minY, invSize ),\n\t\tmaxZ = zOrder( x1, y1, minX, minY, invSize );\n\n\tlet p = ear.prevZ,\n\t\tn = ear.nextZ;\n\n\t// look for points inside the triangle in both directions\n\twhile ( p && p.z >= minZ && n && n.z <= maxZ ) {\n\n\t\tif ( p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && p !== a && p !== c &&\n\t\t\tpointInTriangle( ax, ay, bx, by, cx, cy, p.x, p.y ) && area( p.prev, p, p.next ) >= 0 ) return false;\n\t\tp = p.prevZ;\n\n\t\tif ( n.x >= x0 && n.x <= x1 && n.y >= y0 && n.y <= y1 && n !== a && n !== c &&\n\t\t\tpointInTriangle( ax, ay, bx, by, cx, cy, n.x, n.y ) && area( n.prev, n, n.next ) >= 0 ) return false;\n\t\tn = n.nextZ;\n\n\t}\n\n\t// look for remaining points in decreasing z-order\n\twhile ( p && p.z >= minZ ) {\n\n\t\tif ( p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && p !== a && p !== c &&\n\t\t\tpointInTriangle( ax, ay, bx, by, cx, cy, p.x, p.y ) && area( p.prev, p, p.next ) >= 0 ) return false;\n\t\tp = p.prevZ;\n\n\t}\n\n\t// look for remaining points in increasing z-order\n\twhile ( n && n.z <= maxZ ) {\n\n\t\tif ( n.x >= x0 && n.x <= x1 && n.y >= y0 && n.y <= y1 && n !== a && n !== c &&\n\t\t\tpointInTriangle( ax, ay, bx, by, cx, cy, n.x, n.y ) && area( n.prev, n, n.next ) >= 0 ) return false;\n\t\tn = n.nextZ;\n\n\t}\n\n\treturn true;\n\n}\n\n// go through all polygon nodes and cure small local self-intersections\nfunction cureLocalIntersections( start: EarNode, triangles: number[], dim: number ): EarNode {\n\n\tlet p = start;\n\tdo {\n\n\t\tconst a = p.prev,\n\t\t\tb = p.next.next;\n\n\t\tif ( ! equals( a, b ) && intersects( a, p, p.next, b ) && locallyInside( a, b ) && locallyInside( b, a ) ) {\n\n\t\t\ttriangles.push( a.i / dim | 0 );\n\t\t\ttriangles.push( p.i / dim | 0 );\n\t\t\ttriangles.push( b.i / dim | 0 );\n\n\t\t\t// remove two nodes involved\n\t\t\tremoveNode( p );\n\t\t\tremoveNode( p.next );\n\n\t\t\tp = start = b;\n\n\t\t}\n\n\t\tp = p.next;\n\n\t} while ( p !== start );\n\n\treturn filterPoints( p )!;\n\n}\n\n// try splitting polygon into two and triangulate them independently\nfunction splitEarcut( start: EarNode, triangles: number[], dim: number, minX: number | undefined, minY: number | undefined, invSize: number | undefined ): void {\n\n\t// look for a valid diagonal that divides the polygon into two\n\tlet a = start;\n\tdo {\n\n\t\tlet b = a.next.next;\n\t\twhile ( b !== a.prev ) {\n\n\t\t\tif ( a.i !== b.i && isValidDiagonal( a, b ) ) {\n\n\t\t\t\t// split the polygon in two by the diagonal\n\t\t\t\tlet c = splitPolygon( a, b );\n\n\t\t\t\t// filter colinear points around the cuts\n\t\t\t\ta = filterPoints( a, a.next )!;\n\t\t\t\tc = filterPoints( c, c.next )!;\n\n\t\t\t\t// run earcut on each half\n\t\t\t\tearcutLinked( a, triangles, dim, minX, minY, invSize, 0 );\n\t\t\t\tearcutLinked( c, triangles, dim, minX, minY, invSize, 0 );\n\t\t\t\treturn;\n\n\t\t\t}\n\n\t\t\tb = b.next;\n\n\t\t}\n\n\t\ta = a.next;\n\n\t} while ( a !== start );\n\n}\n\n// link every hole into the outer loop, producing a single-ring polygon without holes\nfunction eliminateHoles( data: ArrayLike<number>, holeIndices: ArrayLike<number>, outerNode: EarNode, dim: number ): EarNode {\n\n\tconst queue: EarNode[] = [];\n\tlet i, len, start, end, list;\n\n\tfor ( i = 0, len = holeIndices.length; i < len; i ++ ) {\n\n\t\tstart = holeIndices[ i ] * dim;\n\t\tend = i < len - 1 ? holeIndices[ i + 1 ] * dim : data.length;\n\t\tlist = linkedList( data, start, end, dim, false )!;\n\t\tif ( list === list.next ) list.steiner = true;\n\t\tqueue.push( getLeftmost( list ) );\n\n\t}\n\n\tqueue.sort( compareX );\n\n\t// process holes from left to right\n\tfor ( i = 0; i < queue.length; i ++ ) {\n\n\t\touterNode = eliminateHole( queue[ i ], outerNode );\n\n\t}\n\n\treturn outerNode;\n\n}\n\nfunction compareX( a: EarNode, b: EarNode ): number {\n\n\treturn a.x - b.x;\n\n}\n\n// find a bridge between vertices that connects hole with an outer ring and link it\nfunction eliminateHole( hole: EarNode, outerNode: EarNode ): EarNode {\n\n\tconst bridge = findHoleBridge( hole, outerNode );\n\tif ( ! bridge ) {\n\n\t\treturn outerNode;\n\n\t}\n\n\tconst bridgeReverse = splitPolygon( bridge, hole );\n\n\t// filter collinear points around the cuts\n\tfilterPoints( bridgeReverse, bridgeReverse.next );\n\treturn filterPoints( bridge, bridge.next )!;\n\n}\n\n// David Eberly's algorithm for finding a bridge between hole and outer polygon\nfunction findHoleBridge( hole: EarNode, outerNode: EarNode ): EarNode | null {\n\n\tlet p = outerNode,\n\t\tqx = - Infinity,\n\t\tm;\n\n\tconst hx = hole.x, hy = hole.y;\n\n\t// find a segment intersected by a ray from the hole's leftmost point to the left;\n\t// segment's endpoint with lesser x will be potential connection point\n\tdo {\n\n\t\tif ( hy <= p.y && hy >= p.next.y && p.next.y !== p.y ) {\n\n\t\t\tconst x = p.x + ( hy - p.y ) * ( p.next.x - p.x ) / ( p.next.y - p.y );\n\t\t\tif ( x <= hx && x > qx ) {\n\n\t\t\t\tqx = x;\n\t\t\t\tm = p.x < p.next.x ? p : p.next;\n\t\t\t\tif ( x === hx ) return m; // hole touches outer segment; pick leftmost endpoint\n\n\t\t\t}\n\n\t\t}\n\n\t\tp = p.next;\n\n\t} while ( p !== outerNode );\n\n\tif ( ! m ) return null;\n\n\t// look for points inside the triangle of hole point, segment intersection and endpoint;\n\t// if there are no points found, we have a valid connection;\n\t// otherwise choose the point of the minimum angle with the ray as connection point\n\n\tconst stop = m,\n\t\tmx = m.x,\n\t\tmy = m.y;\n\tlet tanMin = Infinity, tan;\n\n\tp = m;\n\n\tdo {\n\n\t\tif ( hx >= p.x && p.x >= mx && hx !== p.x &&\n\t\t\t\tpointInTriangle( hy < my ? hx : qx, hy, mx, my, hy < my ? qx : hx, hy, p.x, p.y ) ) {\n\n\t\t\ttan = Math.abs( hy - p.y ) / ( hx - p.x ); // tangential\n\n\t\t\tif ( locallyInside( p, hole ) && ( tan < tanMin || ( tan === tanMin && ( p.x > m.x || ( p.x === m.x && sectorContainsSector( m, p ) ) ) ) ) ) {\n\n\t\t\t\tm = p;\n\t\t\t\ttanMin = tan;\n\n\t\t\t}\n\n\t\t}\n\n\t\tp = p.next;\n\n\t} while ( p !== stop );\n\n\treturn m;\n\n}\n\n// whether sector in vertex m contains sector in vertex p in the same coordinates\nfunction sectorContainsSector( m: EarNode, p: EarNode ): boolean {\n\n\treturn area( m.prev, m, p.prev ) < 0 && area( p.next, m, m.next ) < 0;\n\n}\n\n// interlink polygon nodes in z-order\nfunction indexCurve( start: EarNode, minX: number, minY: number, invSize: number ): void {\n\n\tlet p = start;\n\tdo {\n\n\t\tif ( p.z === 0 ) p.z = zOrder( p.x, p.y, minX, minY, invSize );\n\t\tp.prevZ = p.prev;\n\t\tp.nextZ = p.next;\n\t\tp = p.next;\n\n\t} while ( p !== start );\n\n\tp.prevZ!.nextZ = null;\n\tp.prevZ = null;\n\n\tsortLinked( p );\n\n}\n\n// Simon Tatham's linked list merge sort algorithm\n// http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html\nfunction sortLinked( list: EarNode | null ): EarNode | null {\n\n\tlet i, p: EarNode | null, q: EarNode | null, e: EarNode, tail: EarNode | null, numMerges, pSize, qSize,\n\t\tinSize = 1;\n\n\tdo {\n\n\t\tp = list;\n\t\tlist = null;\n\t\ttail = null;\n\t\tnumMerges = 0;\n\n\t\twhile ( p ) {\n\n\t\t\tnumMerges ++;\n\t\t\tq = p;\n\t\t\tpSize = 0;\n\t\t\tfor ( i = 0; i < inSize; i ++ ) {\n\n\t\t\t\tpSize ++;\n\t\t\t\tq = q!.nextZ;\n\t\t\t\tif ( ! q ) break;\n\n\t\t\t}\n\n\t\t\tqSize = inSize;\n\n\t\t\twhile ( pSize > 0 || ( qSize > 0 && q ) ) {\n\n\t\t\t\tif ( pSize !== 0 && ( qSize === 0 || ! q || p!.z <= q.z ) ) {\n\n\t\t\t\t\te = p!;\n\t\t\t\t\tp = p!.nextZ;\n\t\t\t\t\tpSize --;\n\n\t\t\t\t} else {\n\n\t\t\t\t\te = q!;\n\t\t\t\t\tq = q!.nextZ;\n\t\t\t\t\tqSize --;\n\n\t\t\t\t}\n\n\t\t\t\tif ( tail ) tail.nextZ = e;\n\t\t\t\telse list = e;\n\n\t\t\t\te.prevZ = tail;\n\t\t\t\ttail = e;\n\n\t\t\t}\n\n\t\t\tp = q;\n\n\t\t}\n\n\t\ttail!.nextZ = null;\n\t\tinSize *= 2;\n\n\t} while ( numMerges > 1 );\n\n\treturn list;\n\n}\n\n// z-order of a point given coords and inverse of the longer side of data bbox\nfunction zOrder( x: number, y: number, minX: number, minY: number, invSize: number ): number {\n\n\t// coords are transformed into non-negative 15-bit integer range\n\tx = ( x - minX ) * invSize | 0;\n\ty = ( y - minY ) * invSize | 0;\n\n\tx = ( x | ( x << 8 ) ) & 0x00FF00FF;\n\tx = ( x | ( x << 4 ) ) & 0x0F0F0F0F;\n\tx = ( x | ( x << 2 ) ) & 0x33333333;\n\tx = ( x | ( x << 1 ) ) & 0x55555555;\n\n\ty = ( y | ( y << 8 ) ) & 0x00FF00FF;\n\ty = ( y | ( y << 4 ) ) & 0x0F0F0F0F;\n\ty = ( y | ( y << 2 ) ) & 0x33333333;\n\ty = ( y | ( y << 1 ) ) & 0x55555555;\n\n\treturn x | ( y << 1 );\n\n}\n\n// find the leftmost node of a polygon ring\nfunction getLeftmost( start: EarNode ): EarNode {\n\n\tlet p = start,\n\t\tleftmost = start;\n\tdo {\n\n\t\tif ( p.x < leftmost.x || ( p.x === leftmost.x && p.y < leftmost.y ) ) leftmost = p;\n\t\tp = p.next;\n\n\t} while ( p !== start );\n\n\treturn leftmost;\n\n}\n\n// check if a point lies within a convex triangle\nfunction pointInTriangle( ax: number, ay: number, bx: number, by: number, cx: number, cy: number, px: number, py: number ): boolean {\n\n\treturn ( cx - px ) * ( ay - py ) >= ( ax - px ) * ( cy - py ) &&\n ( ax - px ) * ( by - py ) >= ( bx - px ) * ( ay - py ) &&\n ( bx - px ) * ( cy - py ) >= ( cx - px ) * ( by - py );\n\n}\n\n// check if a diagonal between two polygon nodes is valid (lies in polygon interior)\nfunction isValidDiagonal( a: EarNode, b: EarNode ): boolean | number { // truthy: `area` can be the answer\n\n\treturn a.next.i !== b.i && a.prev.i !== b.i && ! intersectsPolygon( a, b ) && // dones't intersect other edges\n ( locallyInside( a, b ) && locallyInside( b, a ) && middleInside( a, b ) && // locally visible\n ( area( a.prev, a, b.prev ) || area( a, b.prev, b ) ) || // does not create opposite-facing sectors\n equals( a, b ) && area( a.prev, a, a.next ) > 0 && area( b.prev, b, b.next ) > 0 ); // special zero-length case\n\n}\n\n// signed area of a triangle\nfunction area( p: EarNode, q: EarNode, r: EarNode ): number {\n\n\treturn ( q.y - p.y ) * ( r.x - q.x ) - ( q.x - p.x ) * ( r.y - q.y );\n\n}\n\n// check if two points are equal\nfunction equals( p1: EarNode, p2: EarNode ): boolean {\n\n\treturn p1.x === p2.x && p1.y === p2.y;\n\n}\n\n// check if two segments intersect\nfunction intersects( p1: EarNode, q1: EarNode, p2: EarNode, q2: EarNode ): boolean {\n\n\tconst o1 = sign( area( p1, q1, p2 ) );\n\tconst o2 = sign( area( p1, q1, q2 ) );\n\tconst o3 = sign( area( p2, q2, p1 ) );\n\tconst o4 = sign( area( p2, q2, q1 ) );\n\n\tif ( o1 !== o2 && o3 !== o4 ) return true; // general case\n\n\tif ( o1 === 0 && onSegment( p1, p2, q1 ) ) return true; // p1, q1 and p2 are collinear and p2 lies on p1q1\n\tif ( o2 === 0 && onSegment( p1, q2, q1 ) ) return true; // p1, q1 and q2 are collinear and q2 lies on p1q1\n\tif ( o3 === 0 && onSegment( p2, p1, q2 ) ) return true; // p2, q2 and p1 are collinear and p1 lies on p2q2\n\tif ( o4 === 0 && onSegment( p2, q1, q2 ) ) return true; // p2, q2 and q1 are collinear and q1 lies on p2q2\n\n\treturn false;\n\n}\n\n// for collinear points p, q, r, check if point q lies on segment pr\nfunction onSegment( p: EarNode, q: EarNode, r: EarNode ): boolean {\n\n\treturn q.x <= Math.max( p.x, r.x ) && q.x >= Math.min( p.x, r.x ) && q.y <= Math.max( p.y, r.y ) && q.y >= Math.min( p.y, r.y );\n\n}\n\nfunction sign( num: number ): number {\n\n\treturn num > 0 ? 1 : num < 0 ? - 1 : 0;\n\n}\n\n// check if a polygon diagonal intersects any polygon segments\nfunction intersectsPolygon( a: EarNode, b: EarNode ): boolean {\n\n\tlet p = a;\n\tdo {\n\n\t\tif ( p.i !== a.i && p.next.i !== a.i && p.i !== b.i && p.next.i !== b.i &&\n\t\t\tintersects( p, p.next, a, b ) ) return true;\n\t\tp = p.next;\n\n\t} while ( p !== a );\n\n\treturn false;\n\n}\n\n// check if a polygon diagonal is locally inside the polygon\nfunction locallyInside( a: EarNode, b: EarNode ): boolean {\n\n\treturn area( a.prev, a, a.next ) < 0 ?\n\t\tarea( a, b, a.next ) >= 0 && area( a, a.prev, b ) >= 0 :\n\t\tarea( a, b, a.prev ) < 0 || area( a, a.next, b ) < 0;\n\n}\n\n// check if the middle point of a polygon diagonal is inside the polygon\nfunction middleInside( a: EarNode, b: EarNode ): boolean {\n\n\tlet p = a,\n\t\tinside = false;\n\tconst px = ( a.x + b.x ) / 2,\n\t\tpy = ( a.y + b.y ) / 2;\n\tdo {\n\n\t\tif ( ( ( p.y > py ) !== ( p.next.y > py ) ) && p.next.y !== p.y &&\n\t\t\t( px < ( p.next.x - p.x ) * ( py - p.y ) / ( p.next.y - p.y ) + p.x ) )\n\t\t\tinside = ! inside;\n\t\tp = p.next;\n\n\t} while ( p !== a );\n\n\treturn inside;\n\n}\n\n// link two polygon vertices with a bridge; if the vertices belong to the same ring, it splits polygon into two;\n// if one belongs to the outer ring and another to a hole, it merges it into a single ring\nfunction splitPolygon( a: EarNode, b: EarNode ): EarNode {\n\n\tconst a2 = new Node( a.i, a.x, a.y ),\n\t\tb2 = new Node( b.i, b.x, b.y ),\n\t\tan = a.next,\n\t\tbp = b.prev;\n\n\ta.next = b;\n\tb.prev = a;\n\n\ta2.next = an;\n\tan.prev = a2;\n\n\tb2.next = a2;\n\ta2.prev = b2;\n\n\tbp.next = b2;\n\tb2.prev = bp;\n\n\treturn b2;\n\n}\n\n// create a node and optionally link it with previous one (in a circular doubly linked list)\nfunction insertNode( i: number, x: number, y: number, last: EarNode | null | undefined ): EarNode {\n\n\tconst p = new Node( i, x, y );\n\n\tif ( ! last ) {\n\n\t\tp.prev = p;\n\t\tp.next = p;\n\n\t} else {\n\n\t\tp.next = last.next;\n\t\tp.prev = last;\n\t\tlast.next.prev = p;\n\t\tlast.next = p;\n\n\t}\n\n\treturn p;\n\n}\n\nfunction removeNode( p: EarNode ): void {\n\n\tp.next.prev = p.prev;\n\tp.prev.next = p.next;\n\n\tif ( p.prevZ ) p.prevZ.nextZ = p.nextZ;\n\tif ( p.nextZ ) p.nextZ.prevZ = p.prevZ;\n\n}\n\nclass Node {\n\n\t// vertex index in coordinates array\n\tdeclare i: number;\n\t// vertex coordinates\n\tdeclare x: number;\n\tdeclare y: number;\n\t// previous and next vertex nodes in a polygon ring (set as soon as the node is linked: a node in a ring always has both)\n\tdeclare prev: EarNode;\n\tdeclare next: EarNode;\n\t// z-order curve value\n\tdeclare z: number;\n\t// previous and next nodes in z-order\n\tdeclare prevZ: EarNode | null;\n\tdeclare nextZ: EarNode | null;\n\t// indicates whether this is a steiner point\n\tdeclare steiner: boolean;\n\n\tconstructor( i: number, x: number, y: number ) {\n\n\t\t// vertex index in coordinates array\n\t\tthis.i = i;\n\n\t\t// vertex coordinates\n\t\tthis.x = x;\n\t\tthis.y = y;\n\n\t\t// previous and next vertex nodes in a polygon ring\n\t\tthis.prev = null as unknown as EarNode;\n\t\tthis.next = null as unknown as EarNode;\n\n\t\t// z-order curve value\n\t\tthis.z = 0;\n\n\t\t// previous and next nodes in z-order\n\t\tthis.prevZ = null;\n\t\tthis.nextZ = null;\n\n\t\t// indicates whether this is a steiner point\n\t\tthis.steiner = false;\n\n\t}\n\n}\n\n/** A vertex of the polygon ring. */\ntype EarNode = Node;\n\nfunction signedArea( data: ArrayLike<number>, start: number, end: number, dim: number ): number {\n\n\tlet sum = 0;\n\tfor ( let i = start, j = end - dim; i < end; i += dim ) {\n\n\t\tsum += ( data[ j ] - data[ i ] ) * ( data[ i + 1 ] + data[ j + 1 ] );\n\t\tj = i;\n\n\t}\n\n\treturn sum;\n\n}\n\nexport { Earcut };\n","/*\n * three.js r160 `ExtrudeGeometry` (MIT) for straight-edged shapes, ported line by line: the outline is sampled as `Shape.extractPoints` samples a\n * path of `LineCurve`s, triangulated by three's `ShapeUtils.triangulateShape` (Earcut), bevelled and walled in the same order, with the\n * `WorldUVGenerator` uvs and `computeVertexNormals` on the Float32 positions. Path extrusion (`extrudePath`) is not supported.\n */\nimport type { BevelSpec, GeometryGroup, PrimitiveBuffers, ShapeOutline, Vec2 } from '../types'\nimport { Earcut } from './earcut'\n\ninterface P {\n x: number\n y: number\n}\n\nconst equals = (a: P, b: P): boolean => a.x === b.x && a.y === b.y\n\n/** `LineCurve.getPoint(t)`. */\nconst linePoint = (v1: P, v2: P, t: number): P => (t === 1 ? { x: v2.x, y: v2.y } : { x: (v2.x - v1.x) * t + v1.x, y: (v2.y - v1.y) * t + v1.y })\n\n/** `Path.setFromPoints(points).getPoints()`: one `LineCurve` per edge, resolution 1, consecutive duplicates dropped, not closed. */\nfunction pathPoints(points: readonly Vec2[]): P[] {\n const out: P[] = []\n let last: P | undefined\n for (let i = 1; i < points.length; i++) {\n const v1 = { x: (points[i - 1] as Vec2)[0], y: (points[i - 1] as Vec2)[1] }\n const v2 = { x: (points[i] as Vec2)[0], y: (points[i] as Vec2)[1] }\n for (let d = 0; d <= 1; d++) {\n const point = linePoint(v1, v2, d / 1)\n if (last && equals(last, point)) continue\n out.push(point)\n last = point\n }\n }\n return out\n}\n\nfunction area(contour: readonly P[]): number {\n const n = contour.length\n let a = 0.0\n for (let p = n - 1, q = 0; q < n; p = q++) a += (contour[p] as P).x * (contour[q] as P).y - (contour[q] as P).x * (contour[p] as P).y\n return a * 0.5\n}\nconst isClockWise = (pts: readonly P[]): boolean => area(pts) < 0\n\nfunction removeDupEndPts(points: P[]): void {\n const l = points.length\n if (l > 2 && equals(points[l - 1] as P, points[0] as P)) points.pop()\n}\n\nfunction triangulateShape(contour: P[], holes: P[][]): number[][] {\n const vertices: number[] = []\n const holeIndices: number[] = []\n const faces: number[][] = []\n removeDupEndPts(contour)\n for (const p of contour) vertices.push(p.x, p.y)\n let holeIndex = contour.length\n holes.forEach(removeDupEndPts)\n for (let i = 0; i < holes.length; i++) {\n holeIndices.push(holeIndex)\n holeIndex += (holes[i] as P[]).length\n for (const p of holes[i] as P[]) vertices.push(p.x, p.y)\n }\n const triangles: number[] = Earcut.triangulate(vertices, holeIndices)\n for (let i = 0; i < triangles.length; i += 3) faces.push(triangles.slice(i, i + 3))\n return faces\n}\n\n/** `BufferGeometry.computeVertexNormals` for non-indexed Float32 positions, then `normalizeNormals`, both stored as Float32. */\nexport function computeVertexNormals(positions: Float32Array, indices?: ArrayLike<number>): Float32Array {\n const normals = new Float32Array(positions.length)\n const get = (array: Float32Array, i: number): [number, number, number] => [array[i * 3] as number, array[i * 3 + 1] as number, array[i * 3 + 2] as number]\n const face = (vA: number, vB: number, vC: number): [number, number, number] => {\n const pA = get(positions, vA)\n const pB = get(positions, vB)\n const pC = get(positions, vC)\n const cb = [pC[0] - pB[0], pC[1] - pB[1], pC[2] - pB[2]] as const\n const ab = [pA[0] - pB[0], pA[1] - pB[1], pA[2] - pB[2]] as const\n // Vector3.cross\n return [cb[1] * ab[2] - cb[2] * ab[1], cb[2] * ab[0] - cb[0] * ab[2], cb[0] * ab[1] - cb[1] * ab[0]]\n }\n const set = (i: number, x: number, y: number, z: number): void => {\n normals[i * 3] = x\n normals[i * 3 + 1] = y\n normals[i * 3 + 2] = z\n }\n if (indices) {\n for (let i = 0; i < indices.length; i += 3) {\n const vA = indices[i] as number\n const vB = indices[i + 1] as number\n const vC = indices[i + 2] as number\n const cb = face(vA, vB, vC)\n for (const v of [vA, vB, vC]) {\n const n = get(normals, v)\n set(v, n[0] + cb[0], n[1] + cb[1], n[2] + cb[2])\n }\n }\n } else {\n for (let i = 0; i < positions.length / 3; i += 3) {\n const cb = face(i, i + 1, i + 2)\n set(i, cb[0], cb[1], cb[2])\n set(i + 1, cb[0], cb[1], cb[2])\n set(i + 2, cb[0], cb[1], cb[2])\n }\n }\n for (let i = 0; i < normals.length / 3; i++) {\n const [x, y, z] = get(normals, i)\n const s = 1 / (Math.sqrt(x * x + y * y + z * z) || 1)\n set(i, x * s, y * s, z * s)\n }\n return normals\n}\n\nexport function extrude(outline: ShapeOutline, depth = 1, steps = 1, bevel: false | BevelSpec | undefined = undefined): PrimitiveBuffers {\n const parameters = { shape: outline, depth, steps, bevel: bevel ?? {} }\n const verticesArray: number[] = []\n const uvArray: number[] = []\n const groups: GeometryGroup[] = []\n const placeholder: number[] = []\n\n const bevelEnabled = bevel !== false\n const spec: BevelSpec = bevel === false || bevel === undefined ? {} : bevel\n let bevelThickness = spec.thickness !== undefined ? spec.thickness : 0.2\n let bevelSize = spec.size !== undefined ? spec.size : bevelThickness - 0.1\n let bevelOffset = spec.offset !== undefined ? spec.offset : 0\n let bevelSegments = spec.segments !== undefined ? spec.segments : 3\n if (!bevelEnabled) {\n bevelSegments = 0\n bevelThickness = 0\n bevelSize = 0\n bevelOffset = 0\n }\n\n let vertices = pathPoints(outline.outer)\n const holes = (outline.holes ?? []).map(pathPoints)\n const reverse = !isClockWise(vertices)\n if (reverse) {\n vertices = vertices.reverse()\n for (let h = 0; h < holes.length; h++) {\n const ahole = holes[h] as P[]\n if (isClockWise(ahole)) holes[h] = ahole.reverse()\n }\n }\n const faces = triangulateShape(vertices, holes)\n const contour = vertices\n for (const ahole of holes) vertices = vertices.concat(ahole)\n\n const scalePt2 = (pt: P, vec: P, size: number): P => ({ x: pt.x + vec.x * size, y: pt.y + vec.y * size })\n const vlen = vertices.length\n const flen = faces.length\n\n const getBevelVec = (inPt: P, inPrev: P, inNext: P): P => {\n let vTransX: number\n let vTransY: number\n let shrinkBy: number\n const vPrevX = inPt.x - inPrev.x\n const vPrevY = inPt.y - inPrev.y\n const vNextX = inNext.x - inPt.x\n const vNextY = inNext.y - inPt.y\n const vPrevLensq = vPrevX * vPrevX + vPrevY * vPrevY\n const collinear0 = vPrevX * vNextY - vPrevY * vNextX\n if (Math.abs(collinear0) > Number.EPSILON) {\n const vPrevLen = Math.sqrt(vPrevLensq)\n const vNextLen = Math.sqrt(vNextX * vNextX + vNextY * vNextY)\n const ptPrevShiftX = inPrev.x - vPrevY / vPrevLen\n const ptPrevShiftY = inPrev.y + vPrevX / vPrevLen\n const ptNextShiftX = inNext.x - vNextY / vNextLen\n const ptNextShiftY = inNext.y + vNextX / vNextLen\n const sf = ((ptNextShiftX - ptPrevShiftX) * vNextY - (ptNextShiftY - ptPrevShiftY) * vNextX) / (vPrevX * vNextY - vPrevY * vNextX)\n vTransX = ptPrevShiftX + vPrevX * sf - inPt.x\n vTransY = ptPrevShiftY + vPrevY * sf - inPt.y\n const vTransLensq = vTransX * vTransX + vTransY * vTransY\n if (vTransLensq <= 2) return { x: vTransX, y: vTransY }\n shrinkBy = Math.sqrt(vTransLensq / 2)\n } else {\n let directionEq = false\n if (vPrevX > Number.EPSILON) {\n if (vNextX > Number.EPSILON) directionEq = true\n } else if (vPrevX < -Number.EPSILON) {\n if (vNextX < -Number.EPSILON) directionEq = true\n } else if (Math.sign(vPrevY) === Math.sign(vNextY)) {\n directionEq = true\n }\n if (directionEq) {\n vTransX = -vPrevY\n vTransY = vPrevX\n shrinkBy = Math.sqrt(vPrevLensq)\n } else {\n vTransX = vPrevX\n vTransY = vPrevY\n shrinkBy = Math.sqrt(vPrevLensq / 2)\n }\n }\n return { x: vTransX / shrinkBy, y: vTransY / shrinkBy }\n }\n\n const movements = (ring: readonly P[]): P[] => {\n const out: P[] = []\n for (let i = 0, il = ring.length, j = il - 1, k = i + 1; i < il; i++, j++, k++) {\n if (j === il) j = 0\n if (k === il) k = 0\n out[i] = getBevelVec(ring[i] as P, ring[j] as P, ring[k] as P)\n }\n return out\n }\n const contourMovements = movements(contour)\n const holesMovements: P[][] = []\n let verticesMovements = contourMovements.concat()\n for (const ahole of holes) {\n const oneHoleMovements = movements(ahole)\n holesMovements.push(oneHoleMovements)\n verticesMovements = verticesMovements.concat(oneHoleMovements)\n }\n\n const v = (x: number, y: number, z: number): void => {\n placeholder.push(x)\n placeholder.push(y)\n placeholder.push(z)\n }\n const bevelLayer = (b: number, zOf: (z: number) => number): void => {\n const t = b / bevelSegments\n const z = bevelThickness * Math.cos((t * Math.PI) / 2)\n const bs = bevelSize * Math.sin((t * Math.PI) / 2) + bevelOffset\n for (let i = 0; i < contour.length; i++) {\n const vert = scalePt2(contour[i] as P, contourMovements[i] as P, bs)\n v(vert.x, vert.y, zOf(z))\n }\n for (let h = 0; h < holes.length; h++) {\n const ahole = holes[h] as P[]\n const oneHoleMovements = holesMovements[h] as P[]\n for (let i = 0; i < ahole.length; i++) {\n const vert = scalePt2(ahole[i] as P, oneHoleMovements[i] as P, bs)\n v(vert.x, vert.y, zOf(z))\n }\n }\n }\n for (let b = 0; b < bevelSegments; b++) bevelLayer(b, (z) => -z)\n const bs = bevelSize + bevelOffset\n for (let i = 0; i < vlen; i++) {\n const vert = bevelEnabled ? scalePt2(vertices[i] as P, verticesMovements[i] as P, bs) : (vertices[i] as P)\n v(vert.x, vert.y, 0)\n }\n for (let s = 1; s <= steps; s++) {\n for (let i = 0; i < vlen; i++) {\n const vert = bevelEnabled ? scalePt2(vertices[i] as P, verticesMovements[i] as P, bs) : (vertices[i] as P)\n v(vert.x, vert.y, (depth / steps) * s)\n }\n }\n for (let b = bevelSegments - 1; b >= 0; b--) bevelLayer(b, (z) => depth + z)\n\n const addVertex = (index: number): void => {\n verticesArray.push(placeholder[index * 3 + 0] as number)\n verticesArray.push(placeholder[index * 3 + 1] as number)\n verticesArray.push(placeholder[index * 3 + 2] as number)\n }\n const addUV = (x: number, y: number): void => {\n uvArray.push(x)\n uvArray.push(y)\n }\n const at = (index: number, axis: number): number => verticesArray[index * 3 + axis] as number\n const f3 = (a: number, b: number, c: number): void => {\n addVertex(a)\n addVertex(b)\n addVertex(c)\n const nextIndex = verticesArray.length / 3\n for (const index of [nextIndex - 3, nextIndex - 2, nextIndex - 1]) addUV(at(index, 0), at(index, 1))\n }\n const f4 = (a: number, b: number, c: number, d: number): void => {\n addVertex(a)\n addVertex(b)\n addVertex(d)\n addVertex(b)\n addVertex(c)\n addVertex(d)\n const nextIndex = verticesArray.length / 3\n const iA = nextIndex - 6\n const iB = nextIndex - 3\n const iC = nextIndex - 2\n const iD = nextIndex - 1\n const byX = Math.abs(at(iA, 1) - at(iB, 1)) < Math.abs(at(iA, 0) - at(iB, 0))\n const uv = (index: number): [number, number] => [byX ? at(index, 0) : at(index, 1), 1 - at(index, 2)]\n const uvs = [uv(iA), uv(iB), uv(iC), uv(iD)]\n for (const k of [0, 1, 3, 1, 2, 3]) addUV(...(uvs[k] as [number, number]))\n }\n\n // lid faces\n let start = verticesArray.length / 3\n if (bevelEnabled) {\n let offset = 0\n for (let i = 0; i < flen; i++) {\n const face = faces[i] as number[]\n f3((face[2] as number) + offset, (face[1] as number) + offset, (face[0] as number) + offset)\n }\n const layer = steps + bevelSegments * 2\n offset = vlen * layer\n for (let i = 0; i < flen; i++) {\n const face = faces[i] as number[]\n f3((face[0] as number) + offset, (face[1] as number) + offset, (face[2] as number) + offset)\n }\n } else {\n for (let i = 0; i < flen; i++) {\n const face = faces[i] as number[]\n f3(face[2] as number, face[1] as number, face[0] as number)\n }\n for (let i = 0; i < flen; i++) {\n const face = faces[i] as number[]\n f3((face[0] as number) + vlen * steps, (face[1] as number) + vlen * steps, (face[2] as number) + vlen * steps)\n }\n }\n groups.push({ start, count: verticesArray.length / 3 - start, materialIndex: 0 })\n\n // side faces\n start = verticesArray.length / 3\n const sidewalls = (ring: readonly P[], layeroffset: number): void => {\n let i = ring.length\n while (--i >= 0) {\n const j = i\n let k = i - 1\n if (k < 0) k = ring.length - 1\n for (let s = 0, sl = steps + bevelSegments * 2; s < sl; s++) {\n const slen1 = vlen * s\n const slen2 = vlen * (s + 1)\n f4(layeroffset + j + slen1, layeroffset + k + slen1, layeroffset + k + slen2, layeroffset + j + slen2)\n }\n }\n }\n let layeroffset = 0\n sidewalls(contour, layeroffset)\n layeroffset += contour.length\n for (const ahole of holes) {\n sidewalls(ahole, layeroffset)\n layeroffset += ahole.length\n }\n groups.push({ start, count: verticesArray.length / 3 - start, materialIndex: 1 })\n\n const positions = new Float32Array(verticesArray)\n return { type: 'ExtrudeGeometry', parameters, positions, normals: computeVertexNormals(positions), uvs: new Float32Array(uvArray), groups }\n}\n","import { canonicalJson } from '../hash'\nimport type { MeshNode, PrimitiveBuffers, PrimitiveNode } from '../types'\nimport { difference } from './difference'\nimport { computeVertexNormals, extrude } from './extrude'\nimport { box, circle, cone, cylinder, indexArray, lathe, plane, ring, sphere, torus } from './three-generators'\n\nexport { box, circle, cone, cylinder, lathe, plane, ring, sphere, torus } from './three-generators'\nexport { computeVertexNormals, extrude } from './extrude'\nexport { difference } from './difference'\n\n/** How a level of detail changes a primitive. */\nexport interface DetailOptions {\n /** Multiplies the segment counts of round primitives (1 leaves them as given). */\n readonly detail?: number\n /** Floor of the scaled segment counts (default 3). */\n readonly minSegments?: number\n}\n\nconst PARAMETER_KEYS: Readonly<Record<PrimitiveNode['kind'], readonly string[]>> = {\n box: ['size', 'segments'],\n cylinder: ['radiusTop', 'radiusBottom', 'height', 'radialSegments', 'heightSegments', 'openEnded', 'thetaStart', 'thetaLength'],\n cone: ['radius', 'height', 'radialSegments', 'heightSegments', 'openEnded', 'thetaStart', 'thetaLength'],\n sphere: ['radius', 'widthSegments', 'heightSegments', 'phiStart', 'phiLength', 'thetaStart', 'thetaLength'],\n plane: ['width', 'height', 'widthSegments', 'heightSegments'],\n circle: ['radius', 'segments', 'thetaStart', 'thetaLength'],\n ring: ['innerRadius', 'outerRadius', 'thetaSegments', 'phiSegments', 'thetaStart', 'thetaLength'],\n lathe: ['points', 'segments', 'phiStart', 'phiLength'],\n torus: ['radius', 'tube', 'radialSegments', 'tubularSegments', 'arc'],\n extrude: ['shape', 'depth', 'steps', 'bevel'],\n difference: ['size', 'subtract'],\n mesh: ['positions', 'normals', 'uvs', 'indices'],\n}\n\n/** The segment fields `detail` scales, per kind. */\nconst SEGMENT_KEYS: Readonly<Partial<Record<PrimitiveNode['kind'], readonly string[]>>> = {\n cylinder: ['radialSegments'],\n cone: ['radialSegments'],\n sphere: ['widthSegments', 'heightSegments'],\n circle: ['segments'],\n ring: ['thetaSegments'],\n lathe: ['segments'],\n torus: ['radialSegments', 'tubularSegments'],\n}\n/** three's defaults of the segment fields, so a scaled default is scaled from the right number. */\nconst SEGMENT_DEFAULTS: Readonly<Record<string, number>> = { radialSegments: 32, widthSegments: 32, heightSegments: 16, segments: 12, thetaSegments: 32, tubularSegments: 48 }\nconst segmentDefault = (kind: string, key: string): number => (kind === 'circle' && key === 'segments' ? 32 : kind === 'torus' && key === 'radialSegments' ? 12 : (SEGMENT_DEFAULTS[key] as number))\n\n/** The parameters of a primitive node (no transform, colour or name), with the level of detail applied: what its geometry depends on. */\nexport function primitiveParameters(node: PrimitiveNode, options: DetailOptions = {}): Record<string, unknown> {\n const out: Record<string, unknown> = {}\n const source = node as unknown as Record<string, unknown>\n for (const key of PARAMETER_KEYS[node.kind]) if (source[key] !== undefined) out[key] = source[key]\n const detail = options.detail ?? 1\n if (detail !== 1) {\n const floor = options.minSegments ?? 3\n for (const key of SEGMENT_KEYS[node.kind] ?? []) {\n const given = (out[key] as number | undefined) ?? segmentDefault(node.kind, key)\n out[key] = Math.max(floor, Math.round(given * detail))\n }\n }\n return out\n}\n\nfunction mesh(p: MeshNode): PrimitiveBuffers {\n const positions = new Float32Array(p.positions as number[])\n const indices = p.indices ? indexArray(p.indices as number[]) : undefined\n return {\n type: 'BufferGeometry',\n parameters: {},\n positions,\n normals: p.normals ? new Float32Array(p.normals as number[]) : computeVertexNormals(positions, indices),\n uvs: p.uvs ? new Float32Array(p.uvs as number[]) : new Float32Array((positions.length / 3) * 2),\n ...(indices ? { indices } : {}),\n groups: [],\n }\n}\n\n/** Builds a node whose parameters were already resolved (`primitiveParameters` merged over it, so the detail level is applied). */\nfunction make(p: PrimitiveNode): PrimitiveBuffers {\n switch (p.kind) {\n case 'box': {\n const size = p.size ?? [1, 1, 1]\n const segments = p.segments ?? [1, 1, 1]\n return box(size[0], size[1], size[2], segments[0], segments[1], segments[2])\n }\n case 'cylinder':\n return cylinder(p.radiusTop, p.radiusBottom, p.height, p.radialSegments, p.heightSegments, p.openEnded, p.thetaStart, p.thetaLength)\n case 'cone':\n return cone(p.radius, p.height, p.radialSegments, p.heightSegments, p.openEnded, p.thetaStart, p.thetaLength)\n case 'sphere':\n return sphere(p.radius, p.widthSegments, p.heightSegments, p.phiStart, p.phiLength, p.thetaStart, p.thetaLength)\n case 'plane':\n return plane(p.width, p.height, p.widthSegments, p.heightSegments)\n case 'circle':\n return circle(p.radius, p.segments, p.thetaStart, p.thetaLength)\n case 'ring':\n return ring(p.innerRadius, p.outerRadius, p.thetaSegments, p.phiSegments, p.thetaStart, p.thetaLength)\n case 'lathe':\n return lathe(p.points, p.segments, p.phiStart, p.phiLength)\n case 'torus':\n return torus(p.radius, p.tube, p.radialSegments, p.tubularSegments, p.arc)\n case 'extrude':\n return extrude(p.shape, p.depth, p.steps, p.bevel)\n case 'difference':\n return difference(p.size, p.subtract ?? [])\n case 'mesh':\n return mesh(p)\n default:\n throw new Error(`game-shapes: unknown primitive kind \"${String((p as { kind: unknown }).kind)}\"`)\n }\n}\n\n/** Templates by their parameter text: the same cylinder is generated once per process. Read-only by contract. */\nconst templates = new Map<string, PrimitiveBuffers>()\nlet templateLimit = 4096\nconst stats = { hits: 0, misses: 0 }\n\n/** Sets how many primitive templates are kept (least recently used out; 0 turns the cache off). */\nexport function setTemplateCacheSize(size: number): void {\n templateLimit = Math.max(0, Math.floor(size))\n while (templates.size > templateLimit) templates.delete(templates.keys().next().value as string)\n}\nexport function clearTemplateCache(): void {\n templates.clear()\n stats.hits = 0\n stats.misses = 0\n}\nexport const templateCacheStats = (): { size: number; hits: number; misses: number } => ({ size: templates.size, ...stats })\n\n/**\n * The geometry of one primitive on its own, not baked: the arrays three's generator of the same kind builds, bit for bit (meshes and differences\n * are `BufferGeometry`). The result comes from a shared template cache and must not be written to (copy it with `clonePrimitive`).\n */\nexport function buildPrimitive(node: PrimitiveNode, options: DetailOptions = {}): PrimitiveBuffers {\n const parameters = primitiveParameters(node, options)\n // raw meshes are not worth a key of their whole arrays; they are cheap to wrap\n if (node.kind === 'mesh' || templateLimit === 0) return make({ ...node, ...parameters } as PrimitiveNode)\n const key = `${node.kind}:${canonicalJson(parameters)}`\n const cached = templates.get(key)\n if (cached) {\n stats.hits++\n templates.delete(key)\n templates.set(key, cached)\n return cached\n }\n stats.misses++\n const built = make({ ...node, ...parameters } as PrimitiveNode)\n templates.set(key, built)\n if (templates.size > templateLimit) templates.delete(templates.keys().next().value as string)\n return built\n}\n\n/** A copy that owns its arrays. */\nexport function clonePrimitive(p: PrimitiveBuffers): PrimitiveBuffers {\n return {\n type: p.type,\n parameters: p.parameters,\n positions: p.positions.slice(),\n normals: p.normals.slice(),\n uvs: p.uvs.slice(),\n ...(p.indices ? { indices: p.indices.slice() } : {}),\n groups: p.groups.map((group) => ({ ...group })),\n }\n}\n","import { hash64 } from './hash'\n\n/** mulberry32: a small, fast 32-bit seeded stream in [0, 1). */\nexport function mulberry32(seed: number): () => number {\n let state = seed >>> 0\n return () => {\n state = (state + 0x6d2b79f5) >>> 0\n let t = state\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/** The stream of one node: its own per (seed, generator, path), so adding a node never shifts another node's draws. */\nexport const streamFor = (seed: number | string, generator: string, path: string): (() => number) =>\n mulberry32(parseInt(hash64(`${String(seed)}\\u0000${generator}\\u0000${path}`).slice(0, 8), 16))\n","import type { ShapeGenerator } from './types'\n\nconst GENERATOR_NAME = /^[a-zA-Z][\\w.:-]*$/\n\n/** The generators of one set: what `{ kind: 'custom', generator: name, params }` nodes look up. */\nexport interface ShapeGenerators {\n /**\n * Registers a generator: a pure function of its params and a seeded stream that returns a node. A worker that generates shapes with custom nodes must\n * register the same generators (import the same module in the worker script).\n */\n register(name: string, generator: ShapeGenerator): void\n unregister(name: string): boolean\n get(name: string): ShapeGenerator | undefined\n list(): string[]\n /** The generators by name, as `GenerateOptions.generators` takes them. */\n record(): Readonly<Record<string, ShapeGenerator>>\n}\n\nexport const generatorsOver = (generators: Map<string, ShapeGenerator>): ShapeGenerators => ({\n register(name, generator) {\n if (!GENERATOR_NAME.test(name)) throw new Error(`game-shapes: generator name \"${name}\" must start with a letter and use letters, digits, \".\", \":\", \"-\" or \"_\"`)\n generators.set(name, generator)\n },\n unregister: (name) => generators.delete(name),\n get: (name) => generators.get(name),\n list: () => [...generators.keys()].sort(),\n record: () => Object.fromEntries(generators),\n})\n\nlet defaultGenerators: ShapeGenerators | undefined\n\n/** The set behind `registerGenerator`, `getGenerator` and the rest of the global functions, created on first use. */\nexport const defaultShapeGenerators = (): ShapeGenerators => (defaultGenerators ??= generatorsOver(new Map()))\n\n/** @deprecated use `createShapes(...).register`. */\nexport const registerGenerator = (name: string, generator: ShapeGenerator): void => defaultShapeGenerators().register(name, generator)\n/** @deprecated use `createShapes(...).unregister`. */\nexport const unregisterGenerator = (name: string): boolean => defaultShapeGenerators().unregister(name)\n/** @deprecated use `createShapes(...).get`. */\nexport const getGenerator = (name: string): ShapeGenerator | undefined => defaultShapeGenerators().get(name)\n/** @deprecated use `createShapes(...).list`. */\nexport const listGenerators = (): string[] => defaultShapeGenerators().list()\n","{\n \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n \"$id\": \"https://github.com/Andersonfrfilho/adatechnology-game-kit/packages/game-shapes/schema/shape.schema.json\",\n \"title\": \"game-shapes shape\",\n \"type\": \"object\",\n \"required\": [\n \"version\",\n \"root\"\n ],\n \"additionalProperties\": false,\n \"properties\": {\n \"version\": {\n \"const\": 1\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"materials\": {\n \"type\": \"object\",\n \"additionalProperties\": {\n \"$ref\": \"#/$defs/material\"\n }\n },\n \"root\": {\n \"$ref\": \"#/$defs/node\"\n },\n \"lod\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/$defs/lodLevel\"\n }\n },\n \"cullDistance\": {\n \"type\": \"number\",\n \"exclusiveMinimum\": 0\n }\n },\n \"$defs\": {\n \"number\": {\n \"type\": \"number\"\n },\n \"count\": {\n \"type\": \"integer\",\n \"minimum\": 0\n },\n \"vec2\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"number\"\n },\n \"minItems\": 2,\n \"maxItems\": 2\n },\n \"vec3\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"number\"\n },\n \"minItems\": 3,\n \"maxItems\": 3\n },\n \"mat4\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"number\"\n },\n \"minItems\": 16,\n \"maxItems\": 16\n },\n \"color\": {\n \"oneOf\": [\n {\n \"type\": \"integer\",\n \"minimum\": 0,\n \"maximum\": 16777215\n },\n {\n \"type\": \"string\",\n \"pattern\": \"^#[0-9a-fA-F]{6}$\"\n }\n ]\n },\n \"uvRect\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\n \"u0\",\n \"v0\",\n \"u1\",\n \"v1\"\n ],\n \"properties\": {\n \"u0\": {\n \"type\": \"number\"\n },\n \"v0\": {\n \"type\": \"number\"\n },\n \"u1\": {\n \"type\": \"number\"\n },\n \"v1\": {\n \"type\": \"number\"\n }\n }\n },\n \"rotationOrder\": {\n \"enum\": [\n \"XYZ\",\n \"YXZ\",\n \"ZXY\",\n \"ZYX\",\n \"YZX\",\n \"XZY\"\n ]\n },\n \"transform\": {\n \"type\": \"object\",\n \"properties\": {\n \"position\": {\n \"$ref\": \"#/$defs/vec3\"\n },\n \"rotation\": {\n \"$ref\": \"#/$defs/vec3\"\n },\n \"rotationOrder\": {\n \"$ref\": \"#/$defs/rotationOrder\"\n },\n \"scale\": {\n \"oneOf\": [\n {\n \"type\": \"number\"\n },\n {\n \"$ref\": \"#/$defs/vec3\"\n }\n ]\n },\n \"heading\": {\n \"type\": \"number\"\n },\n \"matrix\": {\n \"$ref\": \"#/$defs/mat4\"\n }\n }\n },\n \"instance\": {\n \"allOf\": [\n {\n \"$ref\": \"#/$defs/transform\"\n }\n ],\n \"properties\": {\n \"position\": true,\n \"rotation\": true,\n \"rotationOrder\": true,\n \"scale\": true,\n \"heading\": true,\n \"matrix\": true,\n \"color\": {\n \"$ref\": \"#/$defs/color\"\n }\n },\n \"unevaluatedProperties\": false\n },\n \"material\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"properties\": {\n \"color\": {\n \"$ref\": \"#/$defs/color\"\n },\n \"roughness\": {\n \"type\": \"number\",\n \"minimum\": 0,\n \"maximum\": 1\n },\n \"metalness\": {\n \"type\": \"number\",\n \"minimum\": 0,\n \"maximum\": 1\n },\n \"opacity\": {\n \"type\": \"number\",\n \"minimum\": 0,\n \"maximum\": 1\n },\n \"transparent\": {\n \"type\": \"boolean\"\n },\n \"emissive\": {\n \"$ref\": \"#/$defs/color\"\n },\n \"emissiveIntensity\": {\n \"type\": \"number\",\n \"minimum\": 0\n },\n \"side\": {\n \"enum\": [\n \"front\",\n \"back\",\n \"double\"\n ]\n },\n \"flatShading\": {\n \"type\": \"boolean\"\n },\n \"vertexColors\": {\n \"type\": \"boolean\"\n },\n \"depthWrite\": {\n \"type\": \"boolean\"\n }\n }\n },\n \"lodLevel\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"required\": [\n \"distance\",\n \"detail\"\n ],\n \"properties\": {\n \"distance\": {\n \"type\": \"number\",\n \"minimum\": 0\n },\n \"detail\": {\n \"type\": \"number\",\n \"exclusiveMinimum\": 0,\n \"maximum\": 1\n },\n \"minSize\": {\n \"type\": \"number\",\n \"minimum\": 0\n },\n \"minSegments\": {\n \"type\": \"integer\",\n \"minimum\": 3\n }\n }\n },\n \"common\": {\n \"allOf\": [\n {\n \"$ref\": \"#/$defs/transform\"\n }\n ],\n \"properties\": {\n \"kind\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"part\": {\n \"type\": \"string\"\n },\n \"material\": {\n \"type\": \"string\"\n },\n \"color\": {\n \"$ref\": \"#/$defs/color\"\n },\n \"uv\": {\n \"$ref\": \"#/$defs/uvRect\"\n },\n \"instances\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/$defs/instance\"\n }\n },\n \"minDetail\": {\n \"type\": \"number\",\n \"exclusiveMinimum\": 0,\n \"maximum\": 1\n },\n \"tags\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n }\n },\n \"node\": {\n \"type\": \"object\",\n \"required\": [\n \"kind\"\n ],\n \"allOf\": [\n {\n \"$ref\": \"#/$defs/common\"\n }\n ],\n \"oneOf\": [\n {\n \"properties\": {\n \"kind\": {\n \"const\": \"group\"\n },\n \"children\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/$defs/node\"\n }\n }\n }\n },\n {\n \"properties\": {\n \"kind\": {\n \"const\": \"box\"\n },\n \"size\": {\n \"$ref\": \"#/$defs/vec3\"\n },\n \"segments\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"integer\",\n \"minimum\": 1\n },\n \"minItems\": 3,\n \"maxItems\": 3\n }\n }\n },\n {\n \"properties\": {\n \"kind\": {\n \"const\": \"cylinder\"\n },\n \"radiusTop\": {\n \"type\": \"number\"\n },\n \"radiusBottom\": {\n \"type\": \"number\"\n },\n \"height\": {\n \"type\": \"number\"\n },\n \"radialSegments\": {\n \"type\": \"integer\",\n \"minimum\": 1\n },\n \"heightSegments\": {\n \"type\": \"integer\",\n \"minimum\": 1\n },\n \"openEnded\": {\n \"type\": \"boolean\"\n },\n \"thetaStart\": {\n \"type\": \"number\"\n },\n \"thetaLength\": {\n \"type\": \"number\"\n }\n }\n },\n {\n \"properties\": {\n \"kind\": {\n \"const\": \"cone\"\n },\n \"radius\": {\n \"type\": \"number\"\n },\n \"height\": {\n \"type\": \"number\"\n },\n \"radialSegments\": {\n \"type\": \"integer\",\n \"minimum\": 1\n },\n \"heightSegments\": {\n \"type\": \"integer\",\n \"minimum\": 1\n },\n \"openEnded\": {\n \"type\": \"boolean\"\n },\n \"thetaStart\": {\n \"type\": \"number\"\n },\n \"thetaLength\": {\n \"type\": \"number\"\n }\n }\n },\n {\n \"properties\": {\n \"kind\": {\n \"const\": \"sphere\"\n },\n \"radius\": {\n \"type\": \"number\"\n },\n \"widthSegments\": {\n \"type\": \"integer\",\n \"minimum\": 3\n },\n \"heightSegments\": {\n \"type\": \"integer\",\n \"minimum\": 2\n },\n \"phiStart\": {\n \"type\": \"number\"\n },\n \"phiLength\": {\n \"type\": \"number\"\n },\n \"thetaStart\": {\n \"type\": \"number\"\n },\n \"thetaLength\": {\n \"type\": \"number\"\n }\n }\n },\n {\n \"properties\": {\n \"kind\": {\n \"const\": \"plane\"\n },\n \"width\": {\n \"type\": \"number\"\n },\n \"height\": {\n \"type\": \"number\"\n },\n \"widthSegments\": {\n \"type\": \"integer\",\n \"minimum\": 1\n },\n \"heightSegments\": {\n \"type\": \"integer\",\n \"minimum\": 1\n }\n }\n },\n {\n \"properties\": {\n \"kind\": {\n \"const\": \"circle\"\n },\n \"radius\": {\n \"type\": \"number\"\n },\n \"segments\": {\n \"type\": \"integer\",\n \"minimum\": 3\n },\n \"thetaStart\": {\n \"type\": \"number\"\n },\n \"thetaLength\": {\n \"type\": \"number\"\n }\n }\n },\n {\n \"properties\": {\n \"kind\": {\n \"const\": \"ring\"\n },\n \"innerRadius\": {\n \"type\": \"number\"\n },\n \"outerRadius\": {\n \"type\": \"number\"\n },\n \"thetaSegments\": {\n \"type\": \"integer\",\n \"minimum\": 3\n },\n \"phiSegments\": {\n \"type\": \"integer\",\n \"minimum\": 1\n },\n \"thetaStart\": {\n \"type\": \"number\"\n },\n \"thetaLength\": {\n \"type\": \"number\"\n }\n }\n },\n {\n \"required\": [\n \"points\"\n ],\n \"properties\": {\n \"kind\": {\n \"const\": \"lathe\"\n },\n \"points\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/$defs/vec2\"\n },\n \"minItems\": 2\n },\n \"segments\": {\n \"type\": \"integer\",\n \"minimum\": 1\n },\n \"phiStart\": {\n \"type\": \"number\"\n },\n \"phiLength\": {\n \"type\": \"number\"\n }\n }\n },\n {\n \"properties\": {\n \"kind\": {\n \"const\": \"torus\"\n },\n \"radius\": {\n \"type\": \"number\"\n },\n \"tube\": {\n \"type\": \"number\"\n },\n \"radialSegments\": {\n \"type\": \"integer\",\n \"minimum\": 1\n },\n \"tubularSegments\": {\n \"type\": \"integer\",\n \"minimum\": 1\n },\n \"arc\": {\n \"type\": \"number\"\n }\n }\n },\n {\n \"required\": [\n \"shape\"\n ],\n \"properties\": {\n \"kind\": {\n \"const\": \"extrude\"\n },\n \"shape\": {\n \"type\": \"object\",\n \"required\": [\n \"outer\"\n ],\n \"additionalProperties\": false,\n \"properties\": {\n \"outer\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/$defs/vec2\"\n },\n \"minItems\": 3\n },\n \"holes\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/$defs/vec2\"\n },\n \"minItems\": 3\n }\n }\n }\n },\n \"depth\": {\n \"type\": \"number\"\n },\n \"steps\": {\n \"type\": \"integer\",\n \"minimum\": 1\n },\n \"bevel\": {\n \"oneOf\": [\n {\n \"const\": false\n },\n {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"properties\": {\n \"thickness\": {\n \"type\": \"number\"\n },\n \"size\": {\n \"type\": \"number\"\n },\n \"offset\": {\n \"type\": \"number\"\n },\n \"segments\": {\n \"type\": \"integer\",\n \"minimum\": 1\n }\n }\n }\n ]\n }\n }\n },\n {\n \"required\": [\n \"size\",\n \"subtract\"\n ],\n \"properties\": {\n \"kind\": {\n \"const\": \"difference\"\n },\n \"size\": {\n \"$ref\": \"#/$defs/vec3\"\n },\n \"subtract\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"min\",\n \"max\"\n ],\n \"additionalProperties\": false,\n \"properties\": {\n \"min\": {\n \"$ref\": \"#/$defs/vec2\"\n },\n \"max\": {\n \"$ref\": \"#/$defs/vec2\"\n }\n }\n }\n }\n }\n },\n {\n \"required\": [\n \"positions\"\n ],\n \"properties\": {\n \"kind\": {\n \"const\": \"mesh\"\n },\n \"positions\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"number\"\n }\n },\n \"normals\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"number\"\n }\n },\n \"uvs\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"number\"\n }\n },\n \"indices\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/$defs/count\"\n }\n }\n }\n },\n {\n \"properties\": {\n \"kind\": {\n \"const\": \"point\"\n },\n \"data\": {}\n }\n },\n {\n \"required\": [\n \"generator\"\n ],\n \"properties\": {\n \"kind\": {\n \"const\": \"custom\"\n },\n \"generator\": {\n \"type\": \"string\"\n },\n \"params\": {}\n }\n }\n ]\n }\n }\n}\n","import schemaJson from '../schema/shape.schema.json'\nimport type { ShapeSchema } from './types'\n\n/** The JSON Schema (draft 2020-12) of a shape document; the same file ships as `@adatechnology/game-shapes/schema.json`. */\nexport const SHAPE_SCHEMA: Readonly<Record<string, unknown>> = schemaJson\n\ntype Check = (value: unknown) => string | null\n\nconst num: Check = (v) => (typeof v === 'number' && Number.isFinite(v) ? null : 'expected a finite number')\nconst bool: Check = (v) => (typeof v === 'boolean' ? null : 'expected a boolean')\nconst str: Check = (v) => (typeof v === 'string' ? null : 'expected a string')\nconst int = (min: number): Check => (v) => (typeof v === 'number' && Number.isInteger(v) && v >= min ? null : `expected an integer >= ${min}`)\nconst range = (lo: number, hi: number, loOpen = false): Check => (v) =>\n typeof v === 'number' && (loOpen ? v > lo : v >= lo) && v <= hi ? null : `expected a number in ${loOpen ? '(' : '['}${lo}, ${hi}]`\nconst tuple = (n: number, item: Check = num): Check => (v) => {\n if (!Array.isArray(v) || v.length !== n) return `expected an array of ${n}`\n for (const x of v) if (item(x)) return `expected an array of ${n} numbers`\n return null\n}\nconst list = (item: Check, min = 0): Check => (v) => {\n if (!Array.isArray(v)) return 'expected an array'\n if (v.length < min) return `expected at least ${min} items`\n for (let i = 0; i < v.length; i++) {\n const e = item(v[i])\n if (e) return `[${i}]: ${e}`\n }\n return null\n}\nconst color: Check = (v) =>\n (typeof v === 'number' && Number.isInteger(v) && v >= 0 && v <= 0xffffff) || (typeof v === 'string' && /^#[0-9a-fA-F]{6}$/.test(v)) ? null : 'expected 0xRRGGBB or \"#rrggbb\"'\nconst oneOf = (values: readonly unknown[]): Check => (v) => (values.includes(v) ? null : `expected one of ${values.map((x) => JSON.stringify(x)).join(', ')}`)\nconst object = (fields: Readonly<Record<string, Check>>, required: readonly string[] = []): Check => (v) => {\n if (!v || typeof v !== 'object' || Array.isArray(v)) return 'expected an object'\n const record = v as Record<string, unknown>\n for (const key of required) if (record[key] === undefined) return `${key}: required`\n for (const key of Object.keys(record)) {\n if (record[key] === undefined) continue\n const check = fields[key]\n if (!check) return `${key}: unknown field`\n const e = check(record[key])\n if (e) return `${key}: ${e}`\n }\n return null\n}\n\nconst ORDERS = ['XYZ', 'YXZ', 'ZXY', 'ZYX', 'YZX', 'XZY'] as const\nconst TRANSFORM: Record<string, Check> = {\n position: tuple(3),\n rotation: tuple(3),\n rotationOrder: oneOf(ORDERS),\n scale: (v) => (typeof v === 'number' ? num(v) : tuple(3)(v)),\n heading: num,\n matrix: tuple(16),\n}\nconst UV = object({ u0: num, v0: num, u1: num, v1: num }, ['u0', 'v0', 'u1', 'v1'])\nconst INSTANCE = object({ ...TRANSFORM, color })\nconst COMMON: Record<string, Check> = {\n ...TRANSFORM,\n kind: str,\n name: str,\n part: str,\n material: str,\n color,\n uv: UV,\n instances: list(INSTANCE),\n minDetail: range(0, 1, true),\n tags: list(str),\n}\nconst vec2 = tuple(2)\nconst BEVEL: Check = (v) => (v === false ? null : object({ thickness: num, size: num, offset: num, segments: int(1) })(v))\nconst KINDS: Readonly<Record<string, { fields: Record<string, Check>; required?: string[] }>> = {\n group: { fields: {} },\n box: { fields: { size: tuple(3), segments: tuple(3, int(1)) } },\n cylinder: { fields: { radiusTop: num, radiusBottom: num, height: num, radialSegments: int(1), heightSegments: int(1), openEnded: bool, thetaStart: num, thetaLength: num } },\n cone: { fields: { radius: num, height: num, radialSegments: int(1), heightSegments: int(1), openEnded: bool, thetaStart: num, thetaLength: num } },\n sphere: { fields: { radius: num, widthSegments: int(3), heightSegments: int(2), phiStart: num, phiLength: num, thetaStart: num, thetaLength: num } },\n plane: { fields: { width: num, height: num, widthSegments: int(1), heightSegments: int(1) } },\n circle: { fields: { radius: num, segments: int(3), thetaStart: num, thetaLength: num } },\n ring: { fields: { innerRadius: num, outerRadius: num, thetaSegments: int(3), phiSegments: int(1), thetaStart: num, thetaLength: num } },\n lathe: { fields: { points: list(vec2, 2), segments: int(1), phiStart: num, phiLength: num }, required: ['points'] },\n torus: { fields: { radius: num, tube: num, radialSegments: int(1), tubularSegments: int(1), arc: num } },\n extrude: {\n fields: { shape: object({ outer: list(vec2, 3), holes: list(list(vec2, 3)) }, ['outer']), depth: num, steps: int(1), bevel: BEVEL },\n required: ['shape'],\n },\n difference: { fields: { size: tuple(3), subtract: list(object({ min: vec2, max: vec2 }, ['min', 'max'])) }, required: ['size', 'subtract'] },\n mesh: {\n fields: {\n positions: (v) => list(num)(v) ?? ((v as unknown[]).length % 3 === 0 ? null : 'expected a multiple of 3 numbers'),\n normals: list(num),\n uvs: list(num),\n indices: list(int(0)),\n },\n required: ['positions'],\n },\n point: { fields: { data: () => null } },\n custom: { fields: { generator: str, params: () => null }, required: ['generator'] },\n}\n\nfunction validateNode(node: unknown, path: string, errors: string[]): void {\n if (!node || typeof node !== 'object' || Array.isArray(node)) {\n errors.push(`${path}: expected a node object`)\n return\n }\n const record = node as Record<string, unknown>\n const spec = typeof record.kind === 'string' ? KINDS[record.kind] : undefined\n if (!spec) {\n errors.push(`${path}.kind: expected one of ${Object.keys(KINDS).join(', ')}`)\n return\n }\n for (const key of spec.required ?? []) if (record[key] === undefined) errors.push(`${path}.${key}: required`)\n for (const key of Object.keys(record)) {\n if (record[key] === undefined || key === 'children') continue\n const check = spec.fields[key] ?? COMMON[key]\n if (!check) {\n errors.push(`${path}.${key}: unknown field for a ${String(record.kind)}`)\n continue\n }\n const e = check(record[key])\n if (e) errors.push(`${path}.${key}: ${e}`)\n }\n const exclusive = ['matrix', 'heading'].filter((key) => record[key] !== undefined)\n if (record.matrix !== undefined && (record.position !== undefined || record.rotation !== undefined || record.scale !== undefined || record.heading !== undefined))\n errors.push(`${path}.matrix: cannot be combined with position, rotation, scale or heading`)\n else if (exclusive.includes('heading') && (record.rotation !== undefined || record.scale !== undefined)) errors.push(`${path}.heading: cannot be combined with rotation or scale`)\n if (record.children !== undefined) {\n if (record.kind !== 'group') errors.push(`${path}.children: only a group has children`)\n else if (!Array.isArray(record.children)) errors.push(`${path}.children: expected an array`)\n else record.children.forEach((child, index) => validateNode(child, `${path}.children[${index}]`, errors))\n }\n}\n\n/** Every rule of the JSON Schema, checked without a schema engine: one message per problem, with its path (`root.children[3].radialSegments: ...`). */\nexport function validateShape(schema: unknown): string[] {\n const errors: string[] = []\n if (!schema || typeof schema !== 'object' || Array.isArray(schema)) return ['expected a shape object']\n const record = schema as Record<string, unknown>\n for (const key of Object.keys(record)) if (!['version', 'name', 'materials', 'root', 'lod', 'cullDistance'].includes(key) && record[key] !== undefined) errors.push(`${key}: unknown field`)\n if (record.version !== 1) errors.push('version: expected 1')\n if (record.name !== undefined && typeof record.name !== 'string') errors.push('name: expected a string')\n if (record.materials !== undefined) {\n if (!record.materials || typeof record.materials !== 'object' || Array.isArray(record.materials)) errors.push('materials: expected an object')\n else {\n const material = object({\n color,\n roughness: range(0, 1),\n metalness: range(0, 1),\n opacity: range(0, 1),\n transparent: bool,\n emissive: color,\n emissiveIntensity: (v) => (typeof v === 'number' && v >= 0 ? null : 'expected a number >= 0'),\n side: oneOf(['front', 'back', 'double']),\n flatShading: bool,\n vertexColors: bool,\n depthWrite: bool,\n })\n for (const [id, hint] of Object.entries(record.materials)) {\n const e = material(hint)\n if (e) errors.push(`materials.${id}: ${e}`)\n }\n }\n }\n if (record.lod !== undefined) {\n const e = list(object({ distance: (v) => (typeof v === 'number' && v >= 0 ? null : 'expected a number >= 0'), detail: range(0, 1, true), minSize: (v) => (typeof v === 'number' && v >= 0 ? null : 'expected a number >= 0'), minSegments: int(3) }, ['distance', 'detail']))(record.lod)\n if (e) errors.push(`lod${e.startsWith('[') ? '' : ': '}${e}`)\n }\n if (record.cullDistance !== undefined && !(typeof record.cullDistance === 'number' && record.cullDistance > 0)) errors.push('cullDistance: expected a number > 0')\n if (record.root === undefined) errors.push('root: required')\n else validateNode(record.root, 'root', errors)\n return errors\n}\n\n/** Throws with every problem when the schema is not valid. */\nexport function assertValidShape(schema: unknown): asserts schema is ShapeSchema {\n const errors = validateShape(schema)\n if (errors.length) throw new Error(`game-shapes: invalid shape\\n ${errors.slice(0, 20).join('\\n ')}${errors.length > 20 ? `\\n ... ${errors.length - 20} more` : ''}`)\n}\n","import { colorChannels, colorHex } from './color'\nimport { hashValue } from './hash'\nimport { asMat4, identity4, isAffine, matrixOf, multiply, normalMatrix, transformPoint } from './math'\nimport type { Matrix4 } from './math'\nimport { buildPrimitive, primitiveParameters } from './primitives'\nimport { canonicalJson } from './hash'\nimport { streamFor } from './random'\nimport { getGenerator } from './registry'\nimport { assertValidShape } from './schema'\nimport type {\n Bounds,\n GenerateOptions,\n InstancedBatch,\n NamedRange,\n PrimitiveBuffers,\n PrimitiveNode,\n ShapeBatch,\n ShapeBuffers,\n ShapeLevel,\n ShapeNode,\n ShapePart,\n ShapePoint,\n ShapeSchema,\n UvRect,\n} from './types'\n\nconst PRIMITIVE_KINDS = new Set(['box', 'cylinder', 'cone', 'sphere', 'plane', 'circle', 'ring', 'lathe', 'torus', 'extrude', 'difference', 'mesh'])\nexport const isPrimitiveNode = (node: ShapeNode): node is PrimitiveNode => PRIMITIVE_KINDS.has(node.kind)\n\n/** The options that change the output, as they enter the cache key. */\nexport function keyOptions(options: GenerateOptions = {}): Record<string, unknown> {\n return {\n seed: options.seed ?? 0,\n colors: options.colors ?? 'linear',\n indexed: options.indexed ?? true,\n materials: options.materials,\n fallbackMaterial: options.fallbackMaterial,\n materialAlias: options.materialAlias,\n instancing: options.instancing,\n levels: options.levels,\n }\n}\n\n/** The cache key of a shape under some options: 64-bit FNV-1a of the canonical schema text and the options that change the output. */\nexport const shapeKey = (schema: ShapeSchema, options: GenerateOptions = {}): string => hashValue({ schema, options: keyOptions(options) })\n\nconst IDENTITY_UV: UvRect = { u0: 0, v0: 0, u1: 1, v1: 1 }\n\nclass FloatList {\n array: Float32Array\n length = 0\n constructor(capacity = 256) {\n this.array = new Float32Array(capacity)\n }\n reserve(extra: number): void {\n if (this.length + extra <= this.array.length) return\n let capacity = this.array.length * 2\n while (capacity < this.length + extra) capacity *= 2\n const next = new Float32Array(capacity)\n next.set(this.array.subarray(0, this.length))\n this.array = next\n }\n done(): Float32Array {\n return this.array.slice(0, this.length)\n }\n}\nclass IndexList {\n array: Uint32Array\n length = 0\n constructor(capacity = 256) {\n this.array = new Uint32Array(capacity)\n }\n reserve(extra: number): void {\n if (this.length + extra <= this.array.length) return\n let capacity = this.array.length * 2\n while (capacity < this.length + extra) capacity *= 2\n const next = new Uint32Array(capacity)\n next.set(this.array.subarray(0, this.length))\n this.array = next\n }\n}\n\n/** Growing buffers of one (target, material). */\nclass Accumulator {\n readonly positions = new FloatList()\n readonly normals = new FloatList()\n readonly uvs = new FloatList(170)\n readonly colors = new FloatList()\n readonly indices = new IndexList()\n readonly ranges: NamedRange[] = []\n vertices = 0\n constructor(\n readonly material: string,\n readonly part: string | null,\n ) {}\n}\n\ninterface Target {\n readonly part: string | null\n readonly accs: Map<string, Accumulator>\n}\n\ninterface Settings {\n readonly colors: 'linear' | 'srgb' | 'none'\n readonly indexed: boolean\n readonly materials?: ReadonlySet<string>\n readonly fallback?: string\n readonly alias?: Readonly<Record<string, string>>\n readonly seed: number | string\n readonly generators?: GenerateOptions['generators']\n readonly isolatedGenerators?: boolean\n readonly autoMin?: number\n}\n\ninterface LevelSpec {\n readonly distance: number\n readonly detail: number\n readonly minSize?: number\n readonly minSegments?: number\n readonly index: number\n}\n\ninterface Inherited {\n readonly material: string\n readonly color: number\n readonly uv: UvRect\n readonly target: Target\n}\n\n/**\n * Bakes a template into an accumulator: positions by the world matrix, normals by its normal matrix and normalised, uvs into the atlas cell,\n * one colour for all, indices offset. The arithmetic is three's (`createMeshApi`, `BufferGeometry.applyMatrix4`), so the bits match.\n */\nfunction bake(acc: Accumulator, t: PrimitiveBuffers, world: readonly number[], rgb: readonly number[] | null, u: UvRect, indexed: boolean): void {\n const e = world\n const nm = normalMatrix(world)\n const affine = isAffine(world)\n const source = t.positions\n const count = source.length / 3\n const order: ArrayLike<number> | null = indexed ? null : t.indices ?? null\n const emitted = order ? order.length : count\n acc.positions.reserve(emitted * 3)\n acc.normals.reserve(emitted * 3)\n acc.uvs.reserve(emitted * 2)\n if (rgb) acc.colors.reserve(emitted * 3)\n const P = acc.positions.array\n const N = acc.normals.array\n const U = acc.uvs.array\n const C = acc.colors.array\n let pi = acc.positions.length\n let ni = acc.normals.length\n let ui = acc.uvs.length\n let ci = acc.colors.length\n const du = u.u1 - u.u0\n const dv = u.v1 - u.v0\n const tn = t.normals\n const tu = t.uvs\n for (let k = 0; k < emitted; k++) {\n const i = order ? (order[k] as number) : k\n const x = source[i * 3] as number\n const y = source[i * 3 + 1] as number\n const z = source[i * 3 + 2] as number\n if (affine) {\n P[pi++] = e[0]! * x + e[4]! * y + e[8]! * z + e[12]!\n P[pi++] = e[1]! * x + e[5]! * y + e[9]! * z + e[13]!\n P[pi++] = e[2]! * x + e[6]! * y + e[10]! * z + e[14]!\n } else {\n const p = transformPoint(e, x, y, z)\n P[pi++] = p[0]\n P[pi++] = p[1]\n P[pi++] = p[2]\n }\n const nx = tn[i * 3] as number\n const ny = tn[i * 3 + 1] as number\n const nz = tn[i * 3 + 2] as number\n const vx = nm[0]! * nx + nm[3]! * ny + nm[6]! * nz\n const vy = nm[1]! * nx + nm[4]! * ny + nm[7]! * nz\n const vz = nm[2]! * nx + nm[5]! * ny + nm[8]! * nz\n const s = 1 / (Math.sqrt(vx * vx + vy * vy + vz * vz) || 1)\n N[ni++] = vx * s\n N[ni++] = vy * s\n N[ni++] = vz * s\n U[ui++] = u.u0 + (tu[i * 2] as number) * du\n U[ui++] = u.v0 + (tu[i * 2 + 1] as number) * dv\n if (rgb) {\n C[ci++] = rgb[0]!\n C[ci++] = rgb[1]!\n C[ci++] = rgb[2]!\n }\n }\n acc.positions.length = pi\n acc.normals.length = ni\n acc.uvs.length = ui\n acc.colors.length = ci\n if (indexed) {\n const base = acc.vertices\n const idx = t.indices\n const n = idx ? idx.length : count\n acc.indices.reserve(n)\n const I = acc.indices.array\n let ii = acc.indices.length\n for (let k = 0; k < n; k++) I[ii++] = base + (idx ? (idx[k] as number) : k)\n acc.indices.length = ii\n }\n acc.vertices += emitted\n}\n\nfunction boundsOf(positions: Float32Array): Bounds {\n const min: [number, number, number] = [Infinity, Infinity, Infinity]\n const max: [number, number, number] = [-Infinity, -Infinity, -Infinity]\n for (let i = 0; i < positions.length; i += 3) {\n for (let axis = 0; axis < 3; axis++) {\n const v = positions[i + axis] as number\n if (v < min[axis]!) min[axis] = v\n if (v > max[axis]!) max[axis] = v\n }\n }\n return { min, max }\n}\n\nfunction finish(acc: Accumulator, indexed: boolean, withColors: boolean): ShapeBatch {\n const positions = acc.positions.done()\n const indices = indexed ? (acc.vertices > 65535 ? acc.indices.array.slice(0, acc.indices.length) : Uint16Array.from(acc.indices.array.subarray(0, acc.indices.length))) : undefined\n return {\n material: acc.material,\n part: acc.part,\n positions,\n normals: acc.normals.done(),\n uvs: acc.uvs.done(),\n ...(withColors ? { colors: acc.colors.done() } : {}),\n ...(indices ? { indices } : {}),\n ranges: acc.ranges,\n bounds: boundsOf(positions),\n }\n}\n\nconst radiusOf = (t: PrimitiveBuffers, world: readonly number[]): number => {\n let r = 0\n const p = t.positions\n for (let i = 0; i < p.length; i += 3) r = Math.max(r, Math.hypot(p[i] as number, p[i + 1] as number, p[i + 2] as number))\n const sx = Math.hypot(world[0]!, world[1]!, world[2]!)\n const sy = Math.hypot(world[4]!, world[5]!, world[6]!)\n const sz = Math.hypot(world[8]!, world[9]!, world[10]!)\n return r * Math.max(sx, sy, sz)\n}\n\ninterface LevelResult {\n readonly level: ShapeLevel\n readonly parts: ShapePart[]\n readonly points: ShapePoint[]\n readonly primitives: number\n}\n\n/** Expands a custom node into the node its generator returns (wrapped so that the custom node's own fields still apply). */\nfunction expandCustom(node: Extract<ShapeNode, { kind: 'custom' }>, settings: Settings, detail: number, path: string): ShapeNode {\n const generator = settings.generators?.[node.generator] ?? (settings.isolatedGenerators ? undefined : getGenerator(node.generator))\n if (!generator) throw new Error(`game-shapes: no generator \"${node.generator}\" (register it with registerGenerator, in the worker too) at ${path}`)\n const generated = generator(node.params, { random: streamFor(settings.seed, node.generator, path), seed: settings.seed, detail, path })\n const { kind: _kind, generator: _generator, params: _params, ...common } = node\n return { ...common, kind: 'group', children: [generated] }\n}\n\n/** The key two leaf primitives share when one can stand for the other as an instance. */\nconst leafKey = (node: PrimitiveNode, material: string, color: number, uv: UvRect, level: LevelSpec): string =>\n `${node.kind}|${canonicalJson(primitiveParameters(node, level))}|${material}|${color}|${uv.u0},${uv.v0},${uv.u1},${uv.v1}`\n\nfunction runLevel(schema: ShapeSchema, settings: Settings, level: LevelSpec): LevelResult {\n const bakeParts = level.index > 0\n const root: Target = { part: null, accs: new Map() }\n const targets: Target[] = [root]\n const partTargets = new Map<string, Target>()\n const parts: ShapePart[] = []\n const points: ShapePoint[] = []\n const instanced: InstancedBatch[] = []\n let primitives = 0\n const space = settings.colors === 'none' ? null : settings.colors\n const channels = new Map<number, readonly number[]>()\n const rgbOf = (hex: number): readonly number[] | null => {\n if (!space) return null\n let c = channels.get(hex)\n if (!c) {\n c = colorChannels(hex, space)\n channels.set(hex, c)\n }\n return c\n }\n const resolveMaterial = (id: string): string => {\n let out = id\n if (settings.materials && !settings.materials.has(out)) out = settings.fallback ?? out\n return settings.alias?.[out] ?? out\n }\n const accOf = (target: Target, material: string): Accumulator => {\n let acc = target.accs.get(material)\n if (!acc) {\n acc = new Accumulator(material, target.part)\n target.accs.set(material, acc)\n }\n return acc\n }\n\n // automatic instancing: count the leaves first\n const autoCounts = new Map<string, number>()\n const autoGroups = new Map<string, { node: PrimitiveNode; material: string; color: number; part: string | null; matrices: number[] }>()\n\n const count = (node: ShapeNode, inherited: { material: string; color: number; uv: UvRect; inPart: boolean }, path: string): void => {\n if (node.minDetail !== undefined && level.detail < node.minDetail) return\n if (node.kind === 'custom') return count(expandCustom(node, settings, level.detail, path), inherited, path)\n const next = {\n material: node.material !== undefined ? resolveMaterial(node.material) : inherited.material,\n color: node.color !== undefined ? colorHex(node.color) : inherited.color,\n uv: node.uv ?? inherited.uv,\n inPart: inherited.inPart || (node.part !== undefined && !bakeParts),\n }\n if (node.instances) return\n if (node.kind === 'group') {\n node.children?.forEach((child, index) => count(child, next, `${path}/${index}`))\n return\n }\n if (isPrimitiveNode(node) && node.name === undefined && !next.inPart) {\n const key = leafKey(node, next.material, next.color, next.uv, level)\n autoCounts.set(key, (autoCounts.get(key) ?? 0) + 1)\n }\n }\n const rootMaterial = resolveMaterial('default')\n if (settings.autoMin !== undefined) count(schema.root, { material: rootMaterial, color: 0xffffff, uv: IDENTITY_UV, inPart: false }, 'root')\n\n /** Draws a subtree into `collector` (a target) under `parent` (null: identity). */\n const visit = (node: ShapeNode, parent: Matrix4 | null, inherited: Inherited, path: string): void => {\n if (node.minDetail !== undefined && level.detail < node.minDetail) return\n if (node.kind === 'custom') return visit(expandCustom(node, settings, level.detail, path), parent, inherited, path)\n let target = inherited.target\n let parentWorld = parent\n if (node.part !== undefined) {\n if (bakeParts) {\n // far levels freeze the parts at rest: the part's pivot is relative to the root\n parentWorld = null\n } else {\n let partTarget = partTargets.get(node.part)\n if (!partTarget) {\n partTarget = { part: node.part, accs: new Map() }\n partTargets.set(node.part, partTarget)\n targets.push(partTarget)\n const { position, rotation, rotationOrder, scale, heading, matrix } = node\n parts.push({\n id: node.part,\n ...(position ? { position } : {}),\n ...(rotation ? { rotation } : {}),\n ...(rotationOrder ? { rotationOrder } : {}),\n ...(scale !== undefined ? { scale } : {}),\n ...(heading !== undefined ? { heading } : {}),\n ...(matrix ? { matrix } : { matrix: asMat4(matrixOf(node)) }),\n })\n }\n target = partTarget\n }\n }\n const isPart = node.part !== undefined && !bakeParts\n const local = isPart ? null : matrixOf(node)\n const world = isPart ? null : local ? (parentWorld ? multiply(parentWorld, local) : local) : parentWorld\n const next: Inherited = {\n material: node.material !== undefined ? resolveMaterial(node.material) : inherited.material,\n color: node.color !== undefined ? colorHex(node.color) : inherited.color,\n uv: node.uv ?? inherited.uv,\n target,\n }\n\n if (node.instances) {\n drawInstanced(node, parentWorld, next, path)\n return\n }\n\n const snapshot = node.name !== undefined ? new Map([...target.accs].map(([m, a]) => [m, { v: a.vertices, i: a.indices.length }])) : null\n\n if (node.kind === 'group') {\n node.children?.forEach((child, index) => visit(child, world, next, `${path}/${index}`))\n } else if (node.kind === 'point') {\n const p = node.position ?? [0, 0, 0]\n points.push({\n ...(node.name !== undefined ? { name: node.name } : {}),\n part: target.part,\n position: parent ? transformPoint(parent, p[0], p[1], p[2]) : [p[0], p[1], p[2]],\n ...(node.data !== undefined ? { data: node.data } : {}),\n ...(node.tags ? { tags: node.tags } : {}),\n })\n } else if (isPrimitiveNode(node)) {\n const template = buildPrimitive(node, level)\n const m = world ?? identity4()\n if (level.minSize !== undefined && radiusOf(template, m) < level.minSize) return\n primitives++\n if (settings.autoMin !== undefined && node.name === undefined && target.part === null) {\n const key = leafKey(node, next.material, next.color, next.uv, level)\n if ((autoCounts.get(key) ?? 0) >= settings.autoMin) {\n let group = autoGroups.get(key)\n if (!group) {\n group = { node, material: next.material, color: next.color, part: null, matrices: [] }\n autoGroups.set(key, group)\n }\n group.matrices.push(...m)\n return\n }\n }\n bake(accOf(target, next.material), template, m, rgbOf(next.color), next.uv, settings.indexed)\n }\n\n if (snapshot) {\n for (const [material, acc] of target.accs) {\n const before = snapshot.get(material) ?? { v: 0, i: 0 }\n if (acc.vertices === before.v) continue\n acc.ranges.push({\n name: node.name as string,\n ...(node.tags ? { tags: node.tags } : {}),\n vertexStart: before.v,\n vertexCount: acc.vertices - before.v,\n indexStart: before.i,\n indexCount: acc.indices.length - before.i,\n })\n }\n }\n }\n\n const drawInstanced = (node: ShapeNode, parentWorld: Matrix4 | null, inherited: Inherited, path: string): void => {\n const collector: Target = { part: inherited.target.part, accs: new Map() }\n const { instances, ...single } = node as ShapeNode & { instances: NonNullable<ShapeNode['instances']> }\n const before = parts.length\n visit(single as ShapeNode, null, { ...inherited, target: collector }, path)\n if (parts.length !== before) throw new Error(`game-shapes: an instanced node cannot hold parts (${path})`)\n const matrices = new Float32Array(instances.length * 16)\n const hasColors = instances.some((instance) => instance.color !== undefined)\n const colors = hasColors ? new Float32Array(instances.length * 3) : undefined\n instances.forEach((instance, index) => {\n const local = matrixOf(instance)\n const m = local ? (parentWorld ? multiply(parentWorld, local) : local) : parentWorld ?? identity4()\n matrices.set(m, index * 16)\n if (colors) colors.set(colorChannels(colorHex(instance.color), space ?? 'srgb'), index * 3)\n })\n for (const acc of collector.accs.values()) {\n const batch = finish(acc, settings.indexed, false)\n instanced.push({\n material: acc.material,\n part: collector.part,\n ...(node.name !== undefined ? { name: node.name } : {}),\n geometry: { type: 'BufferGeometry', parameters: {}, positions: batch.positions, normals: batch.normals, uvs: batch.uvs, ...(batch.indices ? { indices: batch.indices } : {}), groups: [] },\n matrices: matrices.slice(),\n ...(colors ? { colors: colors.slice() } : {}),\n count: instances.length,\n })\n }\n }\n\n visit(schema.root, null, { material: rootMaterial, color: 0xffffff, uv: IDENTITY_UV, target: root }, 'root')\n\n for (const group of autoGroups.values()) {\n const template = buildPrimitive(group.node, level)\n const n = group.matrices.length / 16\n const rgb = colorChannels(group.color, space ?? 'srgb')\n const colors = new Float32Array(n * 3)\n for (let i = 0; i < n; i++) colors.set(rgb, i * 3)\n instanced.push({\n material: group.material,\n part: null,\n geometry: { type: template.type, parameters: template.parameters, positions: template.positions.slice(), normals: template.normals.slice(), uvs: template.uvs.slice(), ...(template.indices ? { indices: template.indices.slice() } : {}), groups: template.groups.map((g) => ({ ...g })) },\n matrices: new Float32Array(group.matrices),\n ...(space ? { colors } : {}),\n count: n,\n })\n }\n\n const batches: ShapeBatch[] = []\n for (const target of targets) for (const acc of target.accs.values()) if (acc.vertices > 0 && (!settings.indexed || acc.indices.length > 0)) batches.push(finish(acc, settings.indexed, space !== null))\n return { level: { distance: level.distance, detail: level.detail, batches, instanced }, parts, points, primitives }\n}\n\n/**\n * Turns a shape into buffers: one batch per (part, material) per level, baked; instanced nodes as instance batches; parts with their pivots;\n * points. Pure: the same schema and options give the same bytes, here or in a worker.\n */\nexport function generate(schema: ShapeSchema, options: GenerateOptions = {}): ShapeBuffers {\n if (options.validate !== false) assertValidShape(schema)\n const settings: Settings = {\n colors: options.colors ?? 'linear',\n indexed: options.indexed ?? true,\n ...(options.materials ? { materials: new Set(options.materials) } : {}),\n ...(options.fallbackMaterial !== undefined ? { fallback: options.fallbackMaterial } : {}),\n ...(options.materialAlias ? { alias: options.materialAlias } : {}),\n seed: options.seed ?? 0,\n ...(options.generators ? { generators: options.generators } : {}),\n ...(options.isolatedGenerators ? { isolatedGenerators: true } : {}),\n ...(options.instancing ? { autoMin: Math.max(2, options.instancing.minCount) } : {}),\n }\n const specs: LevelSpec[] = [{ distance: 0, detail: 1, index: 0 }, ...(schema.lod ?? []).map((rule, index) => ({ ...rule, index: index + 1 }))]\n const wanted = options.levels ? specs.filter((spec) => options.levels!.includes(spec.index)) : specs\n let parts: ShapePart[] = []\n let points: ShapePoint[] = []\n let primitives = 0\n const levels: ShapeLevel[] = []\n for (const spec of wanted) {\n const result = runLevel(schema, settings, spec)\n if (spec.index === 0 || levels.length === 0) {\n parts = result.parts\n points = result.points\n primitives = result.primitives\n }\n levels.push(result.level)\n }\n const first = levels[0]\n const vertices = first ? first.batches.reduce((sum, batch) => sum + batch.positions.length / 3, 0) : 0\n const triangles = first\n ? first.batches.reduce((sum, batch) => sum + (batch.indices ? batch.indices.length : batch.positions.length / 3) / 3, 0) +\n first.instanced.reduce((sum, batch) => sum + ((batch.geometry.indices ? batch.geometry.indices.length : batch.geometry.positions.length / 3) / 3) * batch.count, 0)\n : 0\n return {\n key: options.key === false ? '' : options.key ?? shapeKey(schema, options),\n ...(schema.name !== undefined ? { name: schema.name } : {}),\n levels,\n parts,\n points,\n ...(schema.materials ? { materials: schema.materials } : {}),\n ...(schema.cullDistance !== undefined ? { cullDistance: schema.cullDistance } : {}),\n stats: { vertices, triangles, batches: first ? first.batches.length : 0, instances: first ? first.instanced.reduce((sum, batch) => sum + batch.count, 0) : 0, primitives },\n }\n}\n","import { normalMatrix, transformPoint } from './math'\nimport type { InstancedBatch, PrimitiveBuffers, ShapeBatch, ShapeBuffers } from './types'\n\n/** One piece for `mergeTransformed`: arrays (any may be missing) and the matrix that takes them into the merged space. */\nexport interface TransformedPiece {\n readonly positions: ArrayLike<number>\n readonly normals?: ArrayLike<number>\n readonly uvs?: ArrayLike<number>\n readonly indices?: ArrayLike<number> | null\n /** Column-major 4 x 4. */\n readonly matrix: readonly number[]\n}\n\nexport interface MergedArrays {\n readonly positions: Float32Array\n readonly normals: Float32Array\n readonly uvs: Float32Array\n}\n\n/**\n * Joins pieces into one non-indexed set of arrays, each piece de-indexed, its missing normals and uvs zero-filled, its positions by\n * `Vector3.applyMatrix4` and its normals by the normal matrix and normalised: what three's `toNonIndexed` + `applyMatrix4` + a join do,\n * bit for bit (the port of `mergeByMaterial`).\n */\nexport function mergeTransformed(pieces: readonly TransformedPiece[]): MergedArrays {\n let total = 0\n for (const piece of pieces) total += piece.indices ? piece.indices.length : piece.positions.length / 3\n const positions = new Float32Array(total * 3)\n const normals = new Float32Array(total * 3)\n const uvs = new Float32Array(total * 2)\n let at = 0\n for (const piece of pieces) {\n const e = piece.matrix\n const nm = normalMatrix(e)\n const count = piece.indices ? piece.indices.length : piece.positions.length / 3\n // de-index into Float32 first, as toNonIndexed does, then transform the stored values\n const p = new Float32Array(count * 3)\n const n = new Float32Array(count * 3)\n for (let k = 0; k < count; k++) {\n const i = piece.indices ? (piece.indices[k] as number) : k\n for (let c = 0; c < 3; c++) {\n p[k * 3 + c] = piece.positions[i * 3 + c] as number\n if (piece.normals) n[k * 3 + c] = piece.normals[i * 3 + c] as number\n }\n if (piece.uvs) {\n uvs[(at + k) * 2] = piece.uvs[i * 2] as number\n uvs[(at + k) * 2 + 1] = piece.uvs[i * 2 + 1] as number\n }\n }\n for (let k = 0; k < count; k++) {\n const q = transformPoint(e, p[k * 3] as number, p[k * 3 + 1] as number, p[k * 3 + 2] as number)\n positions[(at + k) * 3] = q[0]\n positions[(at + k) * 3 + 1] = q[1]\n positions[(at + k) * 3 + 2] = q[2]\n const x = n[k * 3] as number\n const y = n[k * 3 + 1] as number\n const z = n[k * 3 + 2] as number\n const vx = nm[0]! * x + nm[3]! * y + nm[6]! * z\n const vy = nm[1]! * x + nm[4]! * y + nm[7]! * z\n const vz = nm[2]! * x + nm[5]! * y + nm[8]! * z\n const s = 1 / (Math.sqrt(vx * vx + vy * vy + vz * vz) || 1)\n normals[(at + k) * 3] = vx * s\n normals[(at + k) * 3 + 1] = vy * s\n normals[(at + k) * 3 + 2] = vz * s\n }\n at += count\n }\n return { positions, normals, uvs }\n}\n\nconst primitiveArrays = (p: PrimitiveBuffers): ArrayBuffer[] => [p.positions, p.normals, p.uvs, ...(p.indices ? [p.indices] : [])].map((a) => a.buffer as ArrayBuffer)\nconst batchArrays = (b: ShapeBatch): ArrayBuffer[] => [b.positions, b.normals, b.uvs, ...(b.colors ? [b.colors] : []), ...(b.indices ? [b.indices] : [])].map((a) => a.buffer as ArrayBuffer)\nconst instancedArrays = (b: InstancedBatch): ArrayBuffer[] => [...primitiveArrays(b.geometry), b.matrices.buffer as ArrayBuffer, ...(b.colors ? [b.colors.buffer as ArrayBuffer] : [])]\n\n/** Every `ArrayBuffer` of a result, for `postMessage(buffers, transferablesOf(buffers))`. */\nexport function transferablesOf(buffers: ShapeBuffers): ArrayBuffer[] {\n const out = new Set<ArrayBuffer>()\n for (const level of buffers.levels) {\n for (const batch of level.batches) for (const a of batchArrays(batch)) out.add(a)\n for (const batch of level.instanced) for (const a of instancedArrays(batch)) out.add(a)\n }\n return [...out]\n}\n\nconst copyPrimitive = (p: PrimitiveBuffers): PrimitiveBuffers => ({\n ...p,\n positions: p.positions.slice(),\n normals: p.normals.slice(),\n uvs: p.uvs.slice(),\n ...(p.indices ? { indices: p.indices.slice() } : {}),\n groups: p.groups.map((g) => ({ ...g })),\n})\n\n/** A deep copy whose arrays are its own (cached results are shared and read-only; copy before deforming). */\nexport function cloneBuffers(buffers: ShapeBuffers): ShapeBuffers {\n return {\n ...buffers,\n levels: buffers.levels.map((level) => ({\n ...level,\n batches: level.batches.map((b) => ({\n ...b,\n positions: b.positions.slice(),\n normals: b.normals.slice(),\n uvs: b.uvs.slice(),\n ...(b.colors ? { colors: b.colors.slice() } : {}),\n ...(b.indices ? { indices: b.indices.slice() } : {}),\n ranges: b.ranges.map((r) => ({ ...r })),\n bounds: { min: [...b.bounds.min], max: [...b.bounds.max] },\n })),\n instanced: level.instanced.map((b) => ({ ...b, geometry: copyPrimitive(b.geometry), matrices: b.matrices.slice(), ...(b.colors ? { colors: b.colors.slice() } : {}) })),\n })),\n }\n}\n\n/** Bytes held by the arrays of a result (what the cache counts). */\nexport function byteSizeOf(buffers: ShapeBuffers): number {\n return transferablesOf(buffers).reduce((sum, buffer) => sum + buffer.byteLength, 0)\n}\n","import { generate } from './generate'\nimport { transferablesOf } from './merge'\nimport type { GenerateOptions, ShapeBuffers, ShapeGenerator, ShapeSchema } from './types'\n\n/** A job the pool posts: `options` without `generators` (functions do not cross threads; register them in the worker script). */\nexport interface ShapeJob {\n readonly id: number\n readonly schema: ShapeSchema\n readonly options?: Omit<GenerateOptions, 'generators'>\n}\n\nexport type ShapeReply = { readonly id: number; readonly ok: true; readonly buffers: ShapeBuffers } | { readonly id: number; readonly ok: false; readonly error: string }\n\n/** What a worker scope looks like: `self` in a browser worker, a `worker_threads` `parentPort` wrapper in Node, or an in-thread fake. */\nexport interface WorkerScopeLike {\n onmessage: ((event: { data: unknown }) => void) | null\n postMessage(message: unknown, transfer?: ArrayBuffer[]): void\n}\n\nexport interface ShapeWorkerOptions {\n /** Generators of the `custom` nodes, the same ones the main thread registers. */\n readonly generators?: Readonly<Record<string, ShapeGenerator>>\n /** Transfer the arrays back (default true: zero copy, the worker keeps nothing). */\n readonly transfer?: boolean\n}\n\n/**\n * The worker side of `createShapePool`: answers each job with `generate(schema, options)`, transferring every array.\n *\n * ```ts\n * // shape-worker.ts\n * import { installShapeWorker } from '@adatechnology/game-shapes/worker'\n * installShapeWorker(self as never, { generators: myGenerators })\n * ```\n */\nexport function installShapeWorker(scope: WorkerScopeLike, options: ShapeWorkerOptions = {}): void {\n const transfer = options.transfer !== false\n scope.onmessage = (event) => {\n const job = event.data as ShapeJob\n try {\n const buffers = generate(job.schema, { ...(job.options ?? {}), ...(options.generators ? { generators: options.generators } : {}) })\n const reply: ShapeReply = { id: job.id, ok: true, buffers }\n scope.postMessage(reply, transfer ? transferablesOf(buffers) : [])\n } catch (error) {\n const reply: ShapeReply = { id: job.id, ok: false, error: error instanceof Error ? error.message : String(error) }\n scope.postMessage(reply)\n }\n }\n}\n","import type { PrimitiveBuffers, ShapeBatch } from '@adatechnology/game-shapes'\nimport type * as THREE_NS from 'three'\n\nexport type Three = typeof THREE_NS\n\nexport interface ToGeometryOptions {\n /** Copy the arrays into the attributes (default true). Without a copy the geometry shares the buffers (a cached result is read-only: do not deform it). */\n readonly copy?: boolean\n}\n\n/** The order in which three's generator of each type sets its attributes (toJSON and attribute iteration keep it). */\nconst UV_BEFORE_NORMAL = new Set(['LatheGeometry', 'ExtrudeGeometry'])\n/** Types whose constructor sets the index before the attributes. */\nconst INDEX_FIRST = new Set(['BoxGeometry', 'CylinderGeometry', 'ConeGeometry', 'SphereGeometry', 'PlaneGeometry', 'CircleGeometry', 'RingGeometry', 'LatheGeometry', 'TorusGeometry'])\n\nconst isPrimitive = (value: PrimitiveBuffers | ShapeBatch): value is PrimitiveBuffers => typeof (value as PrimitiveBuffers).type === 'string'\n\n/** Three's own parameters object for a type (lathe points as `Vector2`). */\nfunction threeParameters(THREE: Three, p: PrimitiveBuffers): Record<string, unknown> {\n if (p.type === 'LatheGeometry') {\n const points = (p.parameters.points as Array<{ x: number; y: number }>).map(({ x, y }) => new THREE.Vector2(x, y))\n return { ...p.parameters, points }\n }\n return { ...p.parameters }\n}\n\n/**\n * A `BufferGeometry` from game-shapes buffers. A primitive becomes an instance of the three class it reproduces (`type`, `parameters`, prototype:\n * `instanceof THREE.CylinderGeometry`, `clone()` and `toJSON()` behave as three's), with the same attributes, index and groups. A batch gets\n * `position`, `normal`, `uv`, `color` (when present) and its index.\n */\nexport function toGeometry(THREE: Three, source: PrimitiveBuffers | ShapeBatch, options: ToGeometryOptions = {}): THREE_NS.BufferGeometry {\n const copy = options.copy !== false\n const primitive = isPrimitive(source) ? source : null\n const ThreeClass = primitive && primitive.type !== 'BufferGeometry' ? ((THREE as unknown as Record<string, unknown>)[primitive.type] as (new () => THREE_NS.BufferGeometry) | undefined) : undefined\n // the BufferGeometry constructor only, with the prototype of the class it stands for\n const geometry = (ThreeClass ? Reflect.construct(THREE.BufferGeometry, [], ThreeClass) : new THREE.BufferGeometry()) as THREE_NS.BufferGeometry & { parameters?: Record<string, unknown> }\n if (primitive && ThreeClass) {\n Object.assign(geometry, { type: primitive.type, parameters: threeParameters(THREE, primitive) })\n }\n const attribute = (array: Float32Array, size: number): THREE_NS.BufferAttribute => (copy ? new THREE.Float32BufferAttribute(array, size) : new THREE.BufferAttribute(array, size))\n const index = source.indices\n const setIndex = (): void => {\n if (!index) return\n geometry.setIndex(copy ? (index instanceof Uint32Array ? new THREE.Uint32BufferAttribute(index, 1) : new THREE.Uint16BufferAttribute(index, 1)) : new THREE.BufferAttribute(index, 1))\n }\n const type = primitive?.type ?? 'BufferGeometry'\n if (INDEX_FIRST.has(type)) setIndex()\n geometry.setAttribute('position', attribute(source.positions, 3))\n if (UV_BEFORE_NORMAL.has(type)) {\n geometry.setAttribute('uv', attribute(source.uvs, 2))\n geometry.setAttribute('normal', attribute(source.normals, 3))\n } else {\n geometry.setAttribute('normal', attribute(source.normals, 3))\n geometry.setAttribute('uv', attribute(source.uvs, 2))\n }\n if (!primitive && (source as ShapeBatch).colors) geometry.setAttribute('color', attribute((source as ShapeBatch).colors as Float32Array, 3))\n if (!INDEX_FIRST.has(type)) setIndex()\n if (primitive) for (const group of primitive.groups) geometry.addGroup(group.start, group.count, group.materialIndex)\n return geometry\n}\n","import { box, circle, cone, cylinder, extrude, lathe, plane, ring, sphere, torus } from '@adatechnology/game-shapes'\nimport type { BevelSpec, PrimitiveBuffers, PrimitiveNode, ShapeOutline, Vec2 } from '@adatechnology/game-shapes'\nimport { buildPrimitive } from '@adatechnology/game-shapes'\nimport type * as THREE_NS from 'three'\n\nimport { toGeometry } from './geometry'\nimport type { Three } from './geometry'\n\n/** The geometry constructors `shapeGeometries` replaces. */\nexport const SHAPE_GEOMETRY_TYPES = ['BoxGeometry', 'CylinderGeometry', 'ConeGeometry', 'SphereGeometry', 'PlaneGeometry', 'CircleGeometry', 'RingGeometry', 'LatheGeometry', 'TorusGeometry', 'ExtrudeGeometry'] as const\n\ntype Curve = { isLineCurve?: boolean; v1?: { x: number; y: number }; v2?: { x: number; y: number } }\ntype PathLike = { curves: Curve[]; autoClose?: boolean; holes?: PathLike[] }\n\n/** The point list of a path made only of contiguous `LineCurve`s (what `moveTo` + `lineTo` builds), or null. */\nfunction linePoints(path: PathLike): Vec2[] | null {\n if (path.autoClose || path.curves.length === 0) return null\n const points: Vec2[] = []\n for (const [index, curve] of path.curves.entries()) {\n if (!curve.isLineCurve || !curve.v1 || !curve.v2) return null\n if (index === 0) points.push([curve.v1.x, curve.v1.y])\n else {\n const last = points[points.length - 1] as Vec2\n if (last[0] !== curve.v1.x || last[1] !== curve.v1.y) return null\n }\n points.push([curve.v2.x, curve.v2.y])\n }\n return points\n}\n\ntype ExtrudeOptions = { depth?: number; steps?: number; bevelEnabled?: boolean; bevelThickness?: number; bevelSize?: number; bevelOffset?: number; bevelSegments?: number; extrudePath?: unknown; UVGenerator?: unknown; curveSegments?: number }\n\n/** An extrusion game-shapes can make exactly: one straight-edged shape, no path, the world uvs. */\nfunction extrudeOf(shapes: unknown, options: ExtrudeOptions = {}): PrimitiveBuffers | null {\n if (Array.isArray(shapes) || !shapes || options.extrudePath !== undefined || options.UVGenerator !== undefined) return null\n const shape = shapes as PathLike\n const outer = linePoints(shape)\n if (!outer) return null\n const holes: Vec2[][] = []\n for (const hole of shape.holes ?? []) {\n const points = linePoints(hole)\n if (!points) return null\n holes.push(points)\n }\n const outline: ShapeOutline = holes.length ? { outer, holes } : { outer }\n const bevel: false | BevelSpec =\n options.bevelEnabled === false\n ? false\n : {\n ...(options.bevelThickness !== undefined ? { thickness: options.bevelThickness } : {}),\n ...(options.bevelSize !== undefined ? { size: options.bevelSize } : {}),\n ...(options.bevelOffset !== undefined ? { offset: options.bevelOffset } : {}),\n ...(options.bevelSegments !== undefined ? { segments: options.bevelSegments } : {}),\n }\n return extrude(outline, options.depth, options.steps, bevel)\n}\n\nexport interface ShapeGeometriesOptions {\n /** Go through the template cache of game-shapes (default true): the same primitive is generated once per process. */\n readonly cache?: boolean\n}\n\nconst namespaces = new WeakMap<object, Map<string, Three>>()\n/** The namespaces this module made: wrapping one again gives it back (a package handing its `T` to another). */\nconst shims = new WeakSet<object>()\n\n/**\n * A three namespace whose `BoxGeometry`, `CylinderGeometry`, `ConeGeometry`, `SphereGeometry`, `PlaneGeometry`, `CircleGeometry`, `RingGeometry`,\n * `LatheGeometry`, `TorusGeometry` and (straight-edged) `ExtrudeGeometry` are made by game-shapes, bit-identical to three's and instances of three's\n * classes; everything else is three's own. Hand it to code ported from the teams as `T`, so it keeps reading `new T.CylinderGeometry(...)`.\n * An extrusion with curves, several shapes, a path or a custom uv generator falls back to three.\n */\nexport function shapeGeometries(THREE: Three, options: ShapeGeometriesOptions = {}): Three {\n if (shims.has(THREE)) return THREE\n const cache = options.cache !== false\n const key = cache ? 'cache' : 'nocache'\n let byKey = namespaces.get(THREE)\n if (!byKey) {\n byKey = new Map()\n namespaces.set(THREE, byKey)\n }\n const existing = byKey.get(key)\n if (existing) return existing\n const made = (p: PrimitiveBuffers): THREE_NS.BufferGeometry => toGeometry(THREE, p)\n const viaCache = (node: PrimitiveNode, direct: () => PrimitiveBuffers): PrimitiveBuffers => (cache ? buildPrimitive(node) : direct())\n const defined = <T extends object>(o: T): T => Object.fromEntries(Object.entries(o).filter(([, v]) => v !== undefined)) as T\n const constructors = {\n BoxGeometry: (width?: number, height?: number, depth?: number, ws = 1, hs = 1, ds = 1) =>\n made(viaCache({ kind: 'box', size: [width ?? 1, height ?? 1, depth ?? 1], segments: [ws, hs, ds] }, () => box(width, height, depth, ws, hs, ds))),\n CylinderGeometry: (radiusTop?: number, radiusBottom?: number, height?: number, radialSegments?: number, heightSegments?: number, openEnded?: boolean, thetaStart?: number, thetaLength?: number) =>\n made(viaCache({ kind: 'cylinder', ...defined({ radiusTop, radiusBottom, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength }) }, () => cylinder(radiusTop, radiusBottom, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength))),\n ConeGeometry: (radius?: number, height?: number, radialSegments?: number, heightSegments?: number, openEnded?: boolean, thetaStart?: number, thetaLength?: number) =>\n made(viaCache({ kind: 'cone', ...defined({ radius, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength }) }, () => cone(radius, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength))),\n SphereGeometry: (radius?: number, widthSegments?: number, heightSegments?: number, phiStart?: number, phiLength?: number, thetaStart?: number, thetaLength?: number) =>\n made(viaCache({ kind: 'sphere', ...defined({ radius, widthSegments, heightSegments, phiStart, phiLength, thetaStart, thetaLength }) }, () => sphere(radius, widthSegments, heightSegments, phiStart, phiLength, thetaStart, thetaLength))),\n PlaneGeometry: (width?: number, height?: number, widthSegments?: number, heightSegments?: number) =>\n made(viaCache({ kind: 'plane', ...defined({ width, height, widthSegments, heightSegments }) }, () => plane(width, height, widthSegments, heightSegments))),\n CircleGeometry: (radius?: number, segments?: number, thetaStart?: number, thetaLength?: number) =>\n made(viaCache({ kind: 'circle', ...defined({ radius, segments, thetaStart, thetaLength }) }, () => circle(radius, segments, thetaStart, thetaLength))),\n RingGeometry: (innerRadius?: number, outerRadius?: number, thetaSegments?: number, phiSegments?: number, thetaStart?: number, thetaLength?: number) =>\n made(viaCache({ kind: 'ring', ...defined({ innerRadius, outerRadius, thetaSegments, phiSegments, thetaStart, thetaLength }) }, () => ring(innerRadius, outerRadius, thetaSegments, phiSegments, thetaStart, thetaLength))),\n LatheGeometry: (points?: Array<{ x: number; y: number }>, segments?: number, phiStart?: number, phiLength?: number) => {\n if (!points) return new THREE.LatheGeometry(undefined, segments, phiStart, phiLength)\n const profile = points.map((p) => [p.x, p.y] as const)\n return made(viaCache({ kind: 'lathe', points: profile, ...defined({ segments, phiStart, phiLength }) }, () => lathe(profile, segments, phiStart, phiLength)))\n },\n TorusGeometry: (radius?: number, tube?: number, radialSegments?: number, tubularSegments?: number, arc?: number) =>\n made(viaCache({ kind: 'torus', ...defined({ radius, tube, radialSegments, tubularSegments, arc }) }, () => torus(radius, tube, radialSegments, tubularSegments, arc))),\n ExtrudeGeometry: (shapes?: unknown, extrudeOptions?: ExtrudeOptions) => {\n const buffers = shapes === undefined ? null : extrudeOf(shapes, extrudeOptions)\n if (!buffers) return new THREE.ExtrudeGeometry(shapes as THREE_NS.Shape, extrudeOptions as THREE_NS.ExtrudeGeometryOptions)\n const geometry = toGeometry(THREE, buffers) as THREE_NS.BufferGeometry & { parameters: unknown }\n geometry.parameters = { shapes, options: extrudeOptions ?? {} }\n return geometry\n },\n } satisfies Record<(typeof SHAPE_GEOMETRY_TYPES)[number], (...args: never[]) => THREE_NS.BufferGeometry>\n const namespace: Record<string, unknown> = { ...(THREE as unknown as Record<string, unknown>) }\n for (const type of SHAPE_GEOMETRY_TYPES) {\n // three hands the constructor's own arguments through; each replacement takes the same ones\n const make = constructors[type] as (...args: unknown[]) => THREE_NS.BufferGeometry\n // callable with `new` (the returned object wins), and `instanceof` checks three's prototype\n const Constructor = function (this: unknown, ...args: unknown[]) {\n return make(...args)\n } as unknown as { prototype: unknown }\n Constructor.prototype = (THREE as unknown as Record<string, { prototype: unknown }>)[type]!.prototype\n Object.defineProperty(Constructor, 'name', { value: type })\n namespace[type] = Constructor\n }\n const result = namespace as unknown as Three\n byKey.set(key, result)\n shims.add(result)\n return result\n}\n","import { colorHex } from '@adatechnology/game-shapes'\nimport type { MaterialHint, ShapeBuffers, ShapeLevel, ShapePart, ShapePoint } from '@adatechnology/game-shapes'\nimport type * as THREE_NS from 'three'\n\nimport { toGeometry } from './geometry'\nimport type { Three } from './geometry'\n\n/** A `MeshStandardMaterial` per material id from the schema's hints (vertex colours on unless the hint says otherwise). */\nexport function createMaterials(THREE: Three, hints: Readonly<Record<string, MaterialHint>> = {}, ids: Iterable<string> = []): Record<string, THREE_NS.Material> {\n const out: Record<string, THREE_NS.Material> = {}\n const all = new Set([...Object.keys(hints), ...ids])\n for (const id of all) {\n const hint = hints[id] ?? {}\n const parameters: THREE_NS.MeshStandardMaterialParameters = { vertexColors: hint.vertexColors ?? true }\n if (hint.color !== undefined) parameters.color = colorHex(hint.color)\n if (hint.roughness !== undefined) parameters.roughness = hint.roughness\n if (hint.metalness !== undefined) parameters.metalness = hint.metalness\n if (hint.opacity !== undefined) parameters.opacity = hint.opacity\n if (hint.transparent !== undefined) parameters.transparent = hint.transparent\n if (hint.emissive !== undefined) parameters.emissive = colorHex(hint.emissive)\n if (hint.emissiveIntensity !== undefined) parameters.emissiveIntensity = hint.emissiveIntensity\n if (hint.flatShading !== undefined) parameters.flatShading = hint.flatShading\n if (hint.depthWrite !== undefined) parameters.depthWrite = hint.depthWrite\n if (hint.side !== undefined) parameters.side = hint.side === 'double' ? THREE.DoubleSide : hint.side === 'back' ? THREE.BackSide : THREE.FrontSide\n const material = new THREE.MeshStandardMaterial(parameters)\n material.name = id\n out[id] = material\n }\n return out\n}\n\nexport interface ToObjectOptions {\n /** Materials by id; missing ids get one from the schema hints (`createMaterials`). */\n readonly materials?: Readonly<Record<string, THREE_NS.Material>>\n /** The material of one draw (wins over `materials`): per material id and part (`null` for the static body). */\n readonly materialFor?: (material: string, part: string | null) => THREE_NS.Material | undefined\n /** Cast and receive shadows (default true). */\n readonly shadows?: boolean\n /** Copy the arrays into the attributes (default true; false shares them, read-only). */\n readonly copy?: boolean\n /** Name of the root. */\n readonly name?: string\n}\n\nexport interface ShapeObject {\n /** A `Group`, or a `LOD` when the shape has levels or a cull distance. */\n readonly object: THREE_NS.Object3D\n /** The level 0 group (the root itself without LOD). */\n readonly near: THREE_NS.Group\n /** Part groups of level 0 by id, at their pivots; animate these. */\n readonly parts: Record<string, THREE_NS.Group>\n readonly meshes: THREE_NS.Mesh[]\n readonly instanced: THREE_NS.InstancedMesh[]\n readonly points: readonly ShapePoint[]\n readonly materials: Record<string, THREE_NS.Material>\n}\n\nfunction placePart(THREE: Three, group: THREE_NS.Group, part: ShapePart): void {\n if (part.matrix && part.position === undefined && part.rotation === undefined && part.scale === undefined && part.heading === undefined) {\n new THREE.Matrix4().fromArray(part.matrix as number[]).decompose(group.position, group.quaternion, group.scale)\n return\n }\n if (part.position) group.position.set(part.position[0], part.position[1], part.position[2])\n if (part.heading !== undefined) group.rotation.y = part.heading\n else if (part.rotation) group.rotation.set(part.rotation[0], part.rotation[1], part.rotation[2], part.rotationOrder ?? 'XYZ')\n if (part.scale !== undefined) {\n if (typeof part.scale === 'number') group.scale.setScalar(part.scale)\n else group.scale.set(part.scale[0], part.scale[1], part.scale[2])\n }\n group.updateMatrix()\n}\n\n/**\n * The three objects of a shape: one mesh per batch (`static:<material>` or `part:<id>`, `userData.material`, `userData.part`), part groups at their\n * pivots, an `InstancedMesh` per instanced batch, and a `LOD` (levels at their distances, nothing from `cullDistance`) when there are levels.\n */\nexport function toObject3D(THREE: Three, buffers: ShapeBuffers, options: ToObjectOptions = {}): ShapeObject {\n const shadows = options.shadows !== false\n const ids = new Set<string>()\n for (const level of buffers.levels) {\n for (const b of level.batches) ids.add(b.material)\n for (const b of level.instanced) ids.add(b.material)\n }\n const made = createMaterials(THREE, buffers.materials, [...ids].filter((id) => !options.materials?.[id]))\n const materials: Record<string, THREE_NS.Material> = { ...made, ...(options.materials ?? {}) }\n const materialOf = (id: string, part: string | null): THREE_NS.Material => options.materialFor?.(id, part) ?? (materials[id] as THREE_NS.Material)\n const meshes: THREE_NS.Mesh[] = []\n const instanced: THREE_NS.InstancedMesh[] = []\n\n const build = (level: ShapeLevel, withParts: boolean): { group: THREE_NS.Group; parts: Record<string, THREE_NS.Group> } => {\n const group = new THREE.Group()\n group.name = options.name ?? buffers.name ?? 'shape'\n const parts: Record<string, THREE_NS.Group> = {}\n if (withParts) {\n for (const part of buffers.parts) {\n const g = new THREE.Group()\n g.name = `part:${part.id}`\n g.userData.part = part.id\n placePart(THREE, g, part)\n parts[part.id] = g\n }\n }\n const into = (part: string | null): THREE_NS.Object3D => (part !== null && parts[part]) || group\n for (const batch of level.batches) {\n const mesh = new THREE.Mesh(toGeometry(THREE, batch, { copy: options.copy !== false }), materialOf(batch.material, batch.part))\n mesh.geometry.computeBoundingSphere()\n mesh.geometry.computeBoundingBox()\n mesh.name = batch.part === null ? `static:${batch.material}` : `part:${batch.part}`\n if (batch.part !== null) mesh.userData.part = batch.part\n mesh.userData.material = batch.material\n if (batch.ranges.length) mesh.userData.ranges = batch.ranges\n mesh.castShadow = shadows\n mesh.receiveShadow = shadows\n into(batch.part).add(mesh)\n meshes.push(mesh)\n }\n for (const batch of level.instanced) {\n const mesh = new THREE.InstancedMesh(toGeometry(THREE, batch.geometry, { copy: options.copy !== false }), materialOf(batch.material, batch.part), batch.count)\n mesh.instanceMatrix.array.set(batch.matrices)\n mesh.instanceMatrix.needsUpdate = true\n if (batch.colors) mesh.instanceColor = new THREE.InstancedBufferAttribute(batch.colors.slice(), 3)\n mesh.name = `instanced:${batch.name ?? batch.material}`\n mesh.userData.material = batch.material\n if (batch.part !== null) mesh.userData.part = batch.part\n mesh.castShadow = shadows\n mesh.receiveShadow = shadows\n mesh.computeBoundingSphere()\n into(batch.part).add(mesh)\n instanced.push(mesh)\n }\n for (const part of Object.values(parts)) group.add(part)\n return { group, parts }\n }\n\n const levels = buffers.levels\n const first = build(levels[0] ?? { distance: 0, detail: 1, batches: [], instanced: [] }, true)\n let object: THREE_NS.Object3D = first.group\n if (levels.length > 1 || buffers.cullDistance !== undefined) {\n const lod = new THREE.LOD()\n lod.name = first.group.name\n lod.addLevel(first.group, 0)\n for (const level of levels.slice(1)) lod.addLevel(build(level, false).group, level.distance)\n if (buffers.cullDistance !== undefined) lod.addLevel(new THREE.Group(), buffers.cullDistance)\n object = lod\n }\n object.userData.points = buffers.points\n return { object, near: first.group, parts: first.parts, meshes, instanced, points: buffers.points, materials }\n}\n","import { mergeTransformed } from '@adatechnology/game-shapes'\nimport type { TransformedPiece } from '@adatechnology/game-shapes'\nimport type * as THREE_NS from 'three'\n\nimport type { Three } from './geometry'\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 plain, non-interleaved, not normalised Float32 attribute: what the core merge reads directly. */\nconst plain = (attribute: THREE_NS.BufferAttribute | THREE_NS.InterleavedBufferAttribute | undefined): attribute is THREE_NS.BufferAttribute =>\n attribute !== undefined && !(attribute as THREE_NS.InterleavedBufferAttribute).isInterleavedBufferAttribute && attribute.array instanceof Float32Array && !attribute.normalized\n\n/** Arrays of a geometry the core merge can take as they are; for anything else, the arrays three's own `toNonIndexed` makes (then not indexed). */\nfunction piece(geometry: THREE_NS.BufferGeometry, matrix: THREE_NS.Matrix4): TransformedPiece {\n const position = geometry.getAttribute('position')\n const normal = geometry.getAttribute('normal')\n const uv = geometry.getAttribute('uv')\n const ok = plain(position) && (normal === undefined || plain(normal)) && (uv === undefined || plain(uv)) && position.itemSize === 3 && (uv === undefined || uv.itemSize === 2)\n if (ok) {\n return { positions: position.array, ...(normal ? { normals: normal.array } : {}), ...(uv ? { uvs: uv.array } : {}), indices: geometry.index ? geometry.index.array : null, matrix: matrix.elements }\n }\n const flat = geometry.index ? geometry.toNonIndexed() : geometry\n const read = (name: string, size: number): Float32Array | undefined => {\n const attribute = flat.getAttribute(name)\n if (!attribute) return undefined\n const out = new Float32Array(attribute.count * size)\n for (let i = 0; i < attribute.count; i++) for (let c = 0; c < size; c++) out[i * size + c] = c === 0 ? attribute.getX(i) : c === 1 ? attribute.getY(i) : attribute.getZ(i)\n return out\n }\n const positions = read('position', 3) as Float32Array\n const normals = read('normal', 3)\n const uvs = read('uv', 2)\n if (flat !== geometry) flat.dispose()\n return { positions, ...(normals ? { normals } : {}), ...(uvs ? { uvs } : {}), indices: null, matrix: matrix.elements }\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 non-indexed\n * mesh per material (position, normal, uv), no shadow cast. The bake is game-shapes' `mergeTransformed`, bit-identical to three's\n * `toNonIndexed` + `applyMatrix4` + join, so the result is the one `game-props3d`'s `mergeByMaterial` always gave.\n */\nexport function mergeByMaterial(THREE: Three, 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, TransformedPiece[]>()\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(piece(mesh.geometry, new THREE.Matrix4().multiplyMatrices(toSpace, mesh.matrixWorld)))\n byMaterial.set(mesh.material, list)\n })\n return [...byMaterial].map(([material, pieces]) => {\n const merged = mergeTransformed(pieces)\n const geometry = new THREE.BufferGeometry()\n geometry.setAttribute('position', new THREE.BufferAttribute(merged.positions, 3))\n geometry.setAttribute('normal', new THREE.BufferAttribute(merged.normals, 3))\n geometry.setAttribute('uv', new THREE.BufferAttribute(merged.uvs, 2))\n const mesh = new THREE.Mesh(geometry, material)\n mesh.castShadow = false\n mesh.receiveShadow = true\n return mesh\n })\n}\n","import type * as ThreeNS from 'three'\n\nexport type Vec3 = [number, number, number]\ntype ThreeLib = typeof ThreeNS\n\n/**\n * Points of a hanging cord between `a` and `b` (n points including both ends, n >= 2). The deepest point sits `sag` meters below\n * the straight line between the ends, on a real catenary (y = a cosh(x/a)); when the ends differ in height the curve is\n * measured against the straight line between them.\n */\nexport function catenary(a: Vec3, b: Vec3, sag: number, n = 12): Vec3[] {\n const count = Math.max(2, Math.floor(n))\n const span = Math.hypot(b[0] - a[0], b[2] - a[2]) || Math.hypot(b[0] - a[0], b[1] - a[1], b[2] - a[2])\n const depth = Math.max(0, sag)\n const out: Vec3[] = []\n // Solve depth = c (cosh(span / 2c) - 1) for c by bisection (depth grows as c shrinks).\n let shape: (s: number) => number = () => 0\n if (depth > 1e-6 && span > 1e-6) {\n let lo = 1e-4\n let hi = span * 1000\n for (let i = 0; i < 80; i++) {\n const c = (lo + hi) / 2\n const d = c * (Math.cosh(span / (2 * c)) - 1)\n if (d > depth) lo = c\n else hi = c\n }\n const c = (lo + hi) / 2\n shape = (s) => c * (Math.cosh(((s - 0.5) * span) / c) - Math.cosh(span / (2 * c)))\n }\n for (let i = 0; i < count; i++) {\n const s = i / (count - 1)\n out.push([a[0] + (b[0] - a[0]) * s, a[1] + (b[1] - a[1]) * s + shape(s), a[2] + (b[2] - a[2]) * s])\n }\n return out\n}\n\n/** A thin tube through `points` (Catmull-Rom smoothed) for wires, cords and conduits. */\nexport function createCord(THREE: ThreeLib, points: readonly Vec3[], radius: number, material: ThreeNS.Material, radialSegments = 5): ThreeNS.Mesh {\n if (points.length < 2) throw new Error('createCord needs at least two points')\n const curve = new THREE.CatmullRomCurve3(points.map((p) => new THREE.Vector3(p[0], p[1], p[2])), false, 'centripetal')\n const segments = Math.max(4, (points.length - 1) * 4)\n const mesh = new THREE.Mesh(new THREE.TubeGeometry(curve, segments, radius, radialSegments, false), material)\n mesh.name = 'cord'\n mesh.castShadow = true\n return mesh\n}\n","import type * as THREE_NS from 'three'\n\n/** What `releaseObject3D` freed. */\nexport interface ReleaseReport {\n geometries: number\n materials: number\n textures: number\n}\n\nexport interface ReleaseOptions {\n /**\n * Trees that stay drawn (usually the scene): a geometry, material or texture any of them still uses is kept, so a resource shared by a clone that is\n * still on screen (a catalogue model, a material of the kit's palette) is not freed under it.\n */\n keep?: THREE_NS.Object3D | readonly THREE_NS.Object3D[]\n}\n\ntype Disposable = { dispose(): void; userData?: Record<string, unknown> }\ntype Owner = {\n geometry?: Disposable\n material?: THREE_NS.Material | THREE_NS.Material[]\n skeleton?: { dispose(): void }\n traverse(fn: (o: THREE_NS.Object3D) => void): void\n}\n\nconst isTexture = (v: unknown): v is THREE_NS.Texture => !!v && typeof v === 'object' && (v as { isTexture?: boolean }).isTexture === true\n\nfunction texturesOf(material: THREE_NS.Material, out: Set<THREE_NS.Texture>): void {\n for (const value of Object.values(material)) if (isTexture(value)) out.add(value)\n const uniforms = (material as { uniforms?: Record<string, { value?: unknown }> }).uniforms\n if (uniforms) for (const u of Object.values(uniforms)) if (isTexture(u?.value)) out.add(u.value)\n}\n\nfunction collect(roots: readonly THREE_NS.Object3D[]): { geometries: Set<Disposable>; materials: Set<THREE_NS.Material>; textures: Set<THREE_NS.Texture>; skeletons: Set<{ dispose(): void }> } {\n const geometries = new Set<Disposable>()\n const materials = new Set<THREE_NS.Material>()\n const textures = new Set<THREE_NS.Texture>()\n const skeletons = new Set<{ dispose(): void }>()\n for (const root of roots) {\n root.traverse((object) => {\n const o = object as unknown as Owner\n if (o.geometry && typeof o.geometry.dispose === 'function') geometries.add(o.geometry)\n if (o.skeleton && typeof o.skeleton.dispose === 'function') skeletons.add(o.skeleton)\n const list = Array.isArray(o.material) ? o.material : o.material ? [o.material] : []\n for (const m of list) {\n if (!m || typeof m.dispose !== 'function') continue\n materials.add(m)\n texturesOf(m, textures)\n }\n })\n }\n return { geometries, materials, textures, skeletons }\n}\n\nconst marked = new WeakSet<object>()\n/**\n * Marks geometries, materials or textures as shared on purpose (a palette, a model kit, a catalog): `releaseObject3D` and `discardObject3D` never free\n * them. Same as `userData.shared = true`, without touching the resource (its bytes and userData stay as they were, for digests and goldens).\n */\nexport function markShared<T extends object>(...resources: T[]): T[] {\n for (const resource of resources) marked.add(resource)\n return resources\n}\nconst shared = (r: { userData?: Record<string, unknown> }): boolean => r.userData?.shared === true || marked.has(r)\n\n/**\n * Free the GPU side of trees taken out of the scene: their geometries, materials, textures (maps and shader uniforms) and skeletons. three.js keeps a removed\n * mesh's buffers, programs and textures on the GPU (and the JS objects in its caches) until `dispose()`, so every object a game removes without this stays\n * in memory for good. A resource marked `userData.shared = true`, or still used by a tree in `keep`, is left alone. Disposing a resource that is used again\n * later is safe: three.js uploads it again on the next draw.\n */\nexport function releaseObject3D(roots: THREE_NS.Object3D | readonly THREE_NS.Object3D[], options: ReleaseOptions = {}): ReleaseReport {\n const list = (Array.isArray(roots) ? roots : [roots]) as THREE_NS.Object3D[]\n const report: ReleaseReport = { geometries: 0, materials: 0, textures: 0 }\n if (list.length === 0) return report\n const found = collect(list)\n const keepRoots = options.keep === undefined ? [] : ((Array.isArray(options.keep) ? options.keep : [options.keep]) as THREE_NS.Object3D[])\n // a released tree still hanging under a kept one is not \"kept\": skip it while walking the kept trees\n const releasing = new Set<THREE_NS.Object3D>(list)\n for (const keep of keepRoots) {\n const walk = (o: THREE_NS.Object3D): void => {\n if (releasing.has(o)) return\n const owner = o as unknown as Owner\n if (owner.geometry) found.geometries.delete(owner.geometry)\n if (owner.skeleton) found.skeletons.delete(owner.skeleton)\n const mats = Array.isArray(owner.material) ? owner.material : owner.material ? [owner.material] : []\n for (const m of mats) {\n if (!m) continue\n found.materials.delete(m)\n const t = new Set<THREE_NS.Texture>()\n texturesOf(m, t)\n for (const x of t) found.textures.delete(x)\n }\n for (const c of o.children) walk(c)\n }\n walk(keep)\n }\n for (const g of found.geometries) {\n if (shared(g)) continue\n g.dispose()\n report.geometries++\n }\n for (const m of found.materials) {\n if (shared(m as unknown as Disposable)) continue\n m.dispose()\n report.materials++\n }\n for (const t of found.textures) {\n if (shared(t as unknown as Disposable)) continue\n t.dispose()\n report.textures++\n }\n for (const s of found.skeletons) s.dispose()\n return report\n}\n\n/**\n * Take `objects` out of their trees and free what only they used (`releaseObject3D`, keeping whatever the rest of those trees, usually the scene,\n * still draws). The one call for pieces, fragments, helpers or bodies that leave the game for good. A list costs one walk of each scene, so pass\n * the whole list instead of calling it once per object.\n */\nexport function discardObject3D(objects: THREE_NS.Object3D | readonly THREE_NS.Object3D[], options: ReleaseOptions = {}): ReleaseReport {\n const list = (Array.isArray(objects) ? objects : [objects]) as THREE_NS.Object3D[]\n const keep = options.keep === undefined ? [] : Array.isArray(options.keep) ? [...(options.keep as THREE_NS.Object3D[])] : [options.keep as THREE_NS.Object3D]\n for (const object of list) {\n let top = object\n while (top.parent) top = top.parent\n object.removeFromParent()\n if (top !== object && !keep.includes(top)) keep.push(top)\n }\n return releaseObject3D(list, { keep })\n}\n","import type * as THREE_NS from 'three'\n\n/** A mesh (or a subclass: skinned, instanced) found while walking a tree; three marks them with `isMesh`. */\nexport function isMesh(o: THREE_NS.Object3D | null | undefined): o is THREE_NS.Mesh {\n return !!o && (o as Partial<THREE_NS.Mesh>).isMesh === true\n}\n","import type { Group, Object3D, PointLight, SpotLight } from 'three'\n\nimport type { LightKind, LightPatch, LinearRgb, ThreeForLights } from './types'\n\nconst PARKED_Y = -50\nconst DEFAULT_DECAY = 2\nconst DEFAULT_ANGLE = Math.PI / 3\nconst POSITION_EPSILON = 1e-6\n\nexport type Vec3 = [number, number, number]\n\nexport type LightValues = {\n color: number | LinearRgb\n intensity: number\n distance: number\n decay: number\n position: Vec3\n target: Vec3 | undefined\n angle: number\n penumbra: number\n}\n\n/** Whoever owns a slot: a request, a source or a lease. */\nexport interface Holder {\n readonly kind: 'request' | 'source' | 'lease'\n readonly owner: string\n readonly lightKind: LightKind\n priority: number\n urgent: boolean\n values: LightValues\n slot: Slot | null\n alive: boolean\n onLost: (() => void) | undefined\n /** Sources only. */\n follow: Object3D | undefined\n shadow: boolean\n wanted: boolean\n distance: number\n /** Sources only: `values` with the world position and direction of `follow` of this frame. */\n resolved: LightValues\n}\n\nexport interface Slot {\n readonly index: number\n readonly kind: LightKind\n readonly shadow: boolean\n readonly light: PointLight | SpotLight\n holder: Holder | null\n pending: Holder | null\n leased: boolean\n /** 0..1 multiplier on the holder's intensity. */\n fade: number\n fadeTarget: number\n fadeOutMs: number\n baseIntensity: number\n lastPosition: Vec3\n}\n\nexport function createValues(base: Partial<LightValues> & Pick<LightValues, 'color' | 'intensity' | 'distance' | 'position'>): LightValues {\n return {\n decay: DEFAULT_DECAY,\n target: undefined,\n angle: DEFAULT_ANGLE,\n penumbra: 0,\n ...base,\n position: [...base.position],\n }\n}\n\nexport function mergeValues(values: LightValues, patch: LightPatch): void {\n if (patch.color !== undefined) values.color = patch.color\n if (patch.intensity !== undefined) values.intensity = patch.intensity\n if (patch.distance !== undefined) values.distance = patch.distance\n if (patch.decay !== undefined) values.decay = patch.decay\n if (patch.position !== undefined) values.position = [...patch.position]\n if (patch.target !== undefined) values.target = [...patch.target]\n if (patch.angle !== undefined) values.angle = patch.angle\n if (patch.penumbra !== undefined) values.penumbra = patch.penumbra\n}\n\nexport type ShadowSettings = { readonly bias?: number; readonly normalBias?: number; readonly near?: number }\n\nexport function createSlots(THREE: ThreeForLights, group: Group, kind: LightKind, count: number, shadows: number, mapSize: number, settings: ShadowSettings = {}): Slot[] {\n const slots: Slot[] = []\n for (let index = 0; index < count; index++) {\n const light = kind === 'point' ? new THREE.PointLight(0xffffff, 0, 0, DEFAULT_DECAY) : new THREE.SpotLight(0xffffff, 0, 0, DEFAULT_ANGLE, 0, DEFAULT_DECAY)\n light.position.set(0, PARKED_Y, 0)\n light.layers.enableAll()\n const shadow = index < shadows\n if (shadow) {\n light.castShadow = true\n light.shadow.autoUpdate = false\n light.shadow.mapSize.set(mapSize, mapSize)\n if (settings.bias !== undefined) light.shadow.bias = settings.bias\n if (settings.normalBias !== undefined) light.shadow.normalBias = settings.normalBias\n if (settings.near !== undefined) light.shadow.camera.near = settings.near\n }\n group.add(light)\n if ('target' in light) group.add(light.target)\n slots.push({ index, kind, shadow, light, holder: null, pending: null, leased: false, fade: 0, fadeTarget: 0, fadeOutMs: 0, baseIntensity: 0, lastPosition: [0, PARKED_Y, 0] })\n }\n return slots\n}\n\nexport function isFree(slot: Slot): boolean {\n return !slot.leased && slot.holder === null && slot.pending === null\n}\n\nexport function applyIntensity(slot: Slot): void {\n slot.light.intensity = slot.baseIntensity * slot.fade\n}\n\nexport function markShadow(slot: Slot): void {\n if (slot.shadow) slot.light.shadow.needsUpdate = true\n}\n\nexport function writeLight(THREE: ThreeForLights, slot: Slot, values: LightValues): void {\n const { light } = slot\n if (typeof values.color === 'number') light.color.set(values.color)\n else light.color.setRGB(values.color[0], values.color[1], values.color[2], THREE.LinearSRGBColorSpace)\n light.distance = values.distance\n light.decay = values.decay\n const [x, y, z] = values.position\n light.position.set(x, y, z)\n if ('angle' in light) {\n light.angle = values.angle\n light.penumbra = values.penumbra\n const [tx, ty, tz] = values.target ?? [x, y - 1, z]\n light.target.position.set(tx, ty, tz)\n }\n const last = slot.lastPosition\n if (slot.shadow && (Math.abs(last[0] - x) > POSITION_EPSILON || Math.abs(last[1] - y) > POSITION_EPSILON || Math.abs(last[2] - z) > POSITION_EPSILON)) markShadow(slot)\n slot.lastPosition = [x, y, z]\n slot.baseIntensity = values.intensity\n applyIntensity(slot)\n}\n\n/** The holder's writes apply from now on; its light fades in (requests that cut a victim start at full). */\nexport function install(THREE: ThreeForLights, slot: Slot, holder: Holder, startAtFull: boolean): void {\n slot.holder = holder\n slot.pending = null\n slot.fade = startAtFull ? 1 : 0\n slot.fadeTarget = 1\n holder.slot = slot\n markShadow(slot)\n writeLight(THREE, slot, holder.values)\n}\n\n/** Drops the holder: the light keeps its last values and fades to 0 (at once with `fadeOutMs` 0). */\nexport function vacate(slot: Slot, fadeOutMs: number): void {\n slot.holder = null\n slot.fadeTarget = 0\n slot.fadeOutMs = fadeOutMs\n if (fadeOutMs <= 0) {\n slot.fade = 0\n applyIntensity(slot)\n }\n}\n\nexport function stepFade(slot: Slot, dtSeconds: number, fadeInMs: number): void {\n if (slot.fade < slot.fadeTarget) {\n slot.fade = fadeInMs <= 0 ? slot.fadeTarget : Math.min(slot.fadeTarget, slot.fade + (dtSeconds * 1000) / fadeInMs)\n } else if (slot.fade > slot.fadeTarget) {\n slot.fade = slot.fadeOutMs <= 0 ? slot.fadeTarget : Math.max(slot.fadeTarget, slot.fade - (dtSeconds * 1000) / slot.fadeOutMs)\n }\n applyIntensity(slot)\n}\n","import { type Holder, type Slot, install, isFree, stepFade, vacate } from './slots'\nimport type { LightKind, ThreeForLights } from './types'\n\nexport type ArbiterCore = {\n readonly THREE: ThreeForLights\n readonly slots: Record<LightKind, readonly Slot[]>\n readonly urgentReserve: number\n readonly fadeMs: number\n readonly hysteresis: number\n preempted: number\n refused: number\n}\n\n/** The slots requests and sources compete for: not leased, and shadow slots only on their own pass. */\nexport function poolOf(core: ArbiterCore, kind: LightKind, shadow: boolean): Slot[] {\n return core.slots[kind].filter((slot) => !slot.leased && slot.shadow === shadow)\n}\n\nfunction contribution(holder: Holder): number {\n return holder.values.intensity / Math.max(1, holder.distance) ** 2\n}\n\n/** Lowest priority first; on a tie, the smallest perceived contribution. */\nfunction isWeaker(a: Holder, b: Holder): boolean {\n if (a.priority !== b.priority) return a.priority < b.priority\n return contribution(a) < contribution(b)\n}\n\nfunction pickFree(slots: readonly Slot[]): Slot | undefined {\n let best: Slot | undefined\n for (const slot of slots) if (isFree(slot) && (best === undefined || slot.fade < best.fade)) best = slot\n return best\n}\n\n/** Gives a free slot to the holder: at once when its light is dark or the holder is urgent, else after the slot finishes fading out. */\nfunction place(core: ArbiterCore, slot: Slot, holder: Holder): void {\n holder.slot = slot\n if (slot.fade > 0 && !holder.urgent) {\n slot.pending = holder\n slot.fadeTarget = 0\n return\n }\n install(core.THREE, slot, holder, holder.kind === 'request')\n}\n\n/** Takes the slot from its holder; a request is told and dies, a source goes back to the queue. */\nfunction evict(core: ArbiterCore, slot: Slot, challenger: Holder, cut: boolean): void {\n const victim = slot.holder\n if (victim === null) return\n victim.slot = null\n if (victim.kind === 'request') {\n victim.alive = false\n victim.onLost?.()\n }\n core.preempted++\n vacate(slot, cut ? 0 : core.fadeMs)\n if (cut) {\n install(core.THREE, slot, challenger, true)\n return\n }\n slot.pending = challenger\n challenger.slot = slot\n}\n\nfunction weakestHolder(slots: readonly Slot[]): Slot | undefined {\n let weakest: Slot | undefined\n for (const slot of slots) {\n if (slot.holder === null || slot.pending !== null) continue\n if (weakest === undefined || isWeaker(slot.holder, weakest.holder as Holder)) weakest = slot\n }\n return weakest\n}\n\n/** Slots free to a holder that has no right to the urgent reserve. */\nfunction freeBeyondReserve(core: ArbiterCore, pool: readonly Slot[], reserve: number): Slot | undefined {\n const free = pool.filter(isFree)\n return free.length > reserve ? pickFree(pool) : undefined\n}\n\nexport function admitRequest(core: ArbiterCore, holder: Holder): boolean {\n const pool = poolOf(core, holder.lightKind, false)\n const reserve = holder.lightKind === 'point' ? core.urgentReserve : 0\n const free = holder.urgent ? pickFree(pool) : freeBeyondReserve(core, pool, reserve)\n if (free !== undefined) {\n place(core, free, holder)\n return true\n }\n const victim = weakestHolder(pool)\n if (victim === undefined || (victim.holder as Holder).priority >= holder.priority) {\n core.refused++\n return false\n }\n evict(core, victim, holder, holder.urgent)\n return true\n}\n\nfunction beats(challenger: Holder, incumbent: Holder, hysteresis: number): boolean {\n if (challenger.priority !== incumbent.priority) return challenger.priority > incumbent.priority\n return challenger.distance < incumbent.distance * (1 - hysteresis)\n}\n\nfunction bestFirst(a: Holder, b: Holder): number {\n return b.priority - a.priority || a.distance - b.distance\n}\n\n/** Hands the pool's slots to the wanted sources: best first, an incumbent only yields to a clearly better challenger. */\nexport function arbitrateSources(core: ArbiterCore, pool: readonly Slot[], sources: readonly Holder[], reserve: number): void {\n for (const slot of pool) {\n const holder = slot.holder\n if (slot.pending?.kind === 'source' && !slot.pending.wanted) {\n slot.pending.slot = null\n slot.pending = null\n }\n if (holder?.kind === 'source' && !holder.wanted) {\n holder.slot = null\n vacate(slot, core.fadeMs)\n }\n }\n const challengers = sources.filter((source) => source.wanted && source.slot === null).sort(bestFirst)\n for (const challenger of challengers) {\n const free = freeBeyondReserve(core, pool, reserve)\n if (free !== undefined) {\n place(core, free, challenger)\n continue\n }\n const incumbentSlots = pool.filter((slot) => slot.holder?.kind === 'source')\n let worst: Slot | undefined\n for (const slot of incumbentSlots) {\n if (slot.pending !== null) continue\n if (worst === undefined || bestFirst(slot.holder as Holder, worst.holder as Holder) > 0) worst = slot\n }\n if (worst === undefined || !beats(challenger, worst.holder as Holder, core.hysteresis)) break\n evict(core, worst, challenger, false)\n }\n}\n\nexport function stepSlots(core: ArbiterCore, dtSeconds: number): void {\n for (const kind of ['point', 'spot'] as const) {\n for (const slot of core.slots[kind]) {\n stepFade(slot, dtSeconds, core.fadeMs)\n if (slot.pending !== null && slot.fade <= 0) install(core.THREE, slot, slot.pending, false)\n }\n }\n}\n","import type { Fog, FogBase, FogExp2, Scene } from 'three'\n\nimport type { ThreeLib } from './three-lib'\n\n/** Both exact in half precision (multiples of 32 under 65 504), so the neutral fog survives a `mediump` fragment shader. */\nexport const NEUTRAL_FOG_NEAR = 6.0e4\nexport const NEUTRAL_FOG_FAR = 6.4e4\n\nexport interface FogOptions {\n /** `clear()` leaves a neutral linear Fog instead of `null`: the fog presence and type never change, so no program recompiles. */\n readonly stable?: boolean\n /** Colour of the neutral fog. Default 0x000000. */\n readonly color?: number\n}\n\nexport interface FogController {\n useLinear(color: number, near: number, far: number): Fog\n useExp2(color: number, density: number): FogExp2\n /** Back to no fog: the original fog (or `null`); when `stable`, the host's own linear fog or the neutral one, never `null`. */\n clear(): void\n}\n\nfunction isLinearFog(fog: FogBase | null): fog is Fog {\n return (fog as Partial<Fog> | null)?.isFog === true\n}\n\nfunction isExp2Fog(fog: FogBase | null): fog is FogExp2 {\n return (fog as Partial<FogExp2> | null)?.isFogExp2 === true\n}\n\nexport function isNeutralFog(fog: unknown): boolean {\n const candidate = fog as Partial<Fog> | null | undefined\n return candidate?.isFog === true && candidate.near === NEUTRAL_FOG_NEAR && candidate.far === NEUTRAL_FOG_FAR\n}\n\n/** Writes into the scene's existing fog object when its type matches, as the weather layer does, so nothing is reallocated per change. */\nexport function createFog(THREE: Pick<ThreeLib, 'Fog' | 'FogExp2'>, scene: Scene, options: FogOptions = {}): FogController {\n const original = scene.fog\n const hostLinear = isLinearFog(original) ? { color: original.color.getHex(), near: original.near, far: original.far } : undefined\n const resting = hostLinear ?? { color: options.color ?? 0, near: NEUTRAL_FOG_NEAR, far: NEUTRAL_FOG_FAR }\n return {\n useLinear(color, near, far) {\n const current = scene.fog\n if (isLinearFog(current)) {\n current.color.set(color)\n current.near = near\n current.far = far\n return current\n }\n const fog = new THREE.Fog(color, near, far)\n scene.fog = fog\n return fog\n },\n useExp2(color, density) {\n const current = scene.fog\n if (isExp2Fog(current)) {\n current.color.set(color)\n current.density = density\n return current\n }\n const fog = new THREE.FogExp2(color, density)\n scene.fog = fog\n return fog\n },\n clear() {\n if (options.stable !== true) {\n scene.fog = original\n return\n }\n const current = scene.fog\n if (isLinearFog(current)) {\n current.color.set(resting.color)\n current.near = resting.near\n current.far = resting.far\n return\n }\n scene.fog = new THREE.Fog(resting.color, resting.near, resting.far)\n },\n }\n}\n","import type { Object3D, Scene } from 'three'\n\nimport { NEUTRAL_FOG_NEAR } from '../fog'\nimport type { FogState, LightCheck, RendererState, SealOptions } from './types'\n\ntype Flags = Record<string, unknown>\ntype Counter = { count: number; shadows: number }\n\nconst LIGHT_FLAGS = [\n ['point', 'isPointLight'],\n ['spot', 'isSpotLight'],\n ['hemi', 'isHemisphereLight'],\n ['dir', 'isDirectionalLight'],\n ['ambient', 'isAmbientLight'],\n ['rect', 'isRectAreaLight'],\n] as const\n\n/** The visible lights the way the renderer collects them: they decide the shader's light counts. */\nexport function lightSignature(scene: Object3D): string {\n const counters: Record<string, Counter> = {}\n for (const [name] of LIGHT_FLAGS) counters[name] = { count: 0, shadows: 0 }\n scene.traverseVisible((object) => {\n const flags = object as unknown as Flags\n for (const [name, flag] of LIGHT_FLAGS) {\n if (flags[flag] !== true) continue\n const counter = counters[name] as Counter\n counter.count++\n if ((flags as { castShadow?: boolean }).castShadow === true) counter.shadows++\n }\n })\n return LIGHT_FLAGS.map(([name]) => `${name}:${(counters[name] as Counter).count}/${(counters[name] as Counter).shadows}`).join(' ')\n}\n\nexport function fogStateOf(scene: Scene): FogState {\n const fog = scene.fog as Flags | null\n if (fog === null) return 'none'\n return fog.isFogExp2 === true ? 'exp2' : 'linear'\n}\n\n/** Every PointLight/SpotLight under `scene` that is not under `group`, as a path of child indices. */\nexport function findStrays(scene: Object3D, group: Object3D): string[] {\n const strays: string[] = []\n const visit = (object: Object3D, path: string): void => {\n if (object === group) return\n const flags = object as unknown as Flags\n if (flags.isPointLight === true || flags.isSpotLight === true) strays.push(`${path}${object.name === '' ? object.type : object.name}`)\n object.children.forEach((child, index) => visit(child, `${path}${index}/`))\n }\n visit(scene, '')\n return strays\n}\n\nexport type Sealed = {\n readonly lights: string\n readonly environment: string\n readonly toneMapping: number\n readonly outputColorSpace: string\n readonly targetColorSpace: string\n readonly shadowMapType: number\n readonly fogStates: readonly FogState[]\n readonly cameras: SealOptions['cameras']\n readonly target: SealOptions['target']\n}\n\nfunction environmentOf(scene: Scene): string {\n return scene.environment === null ? 'none' : `texture:${scene.environment.mapping}`\n}\n\nfunction targetColorSpaceOf(target: SealOptions['target']): string {\n return target ? target.texture.colorSpace : 'canvas'\n}\n\nexport function captureSealed(renderer: RendererState, scene: Scene, options: SealOptions): Sealed {\n return {\n lights: lightSignature(scene),\n environment: environmentOf(scene),\n toneMapping: renderer.toneMapping,\n outputColorSpace: renderer.outputColorSpace,\n targetColorSpace: targetColorSpaceOf(options.target),\n shadowMapType: renderer.shadowMap.type,\n fogStates: options.fogStates ?? [fogStateOf(scene)],\n cameras: options.cameras,\n target: options.target,\n }\n}\n\ntype Described = {\n readonly lights: string\n readonly fog: string\n readonly environment: string\n readonly toneMapping: number\n readonly outputColorSpace: string\n readonly targetColorSpace: string\n readonly shadowMapType: number\n}\n\nfunction describe(state: Described): string {\n const { lights, fog, environment, toneMapping, outputColorSpace, targetColorSpace, shadowMapType } = state\n return `${lights} | fog:${fog} env:${environment} tone:${toneMapping} out:${outputColorSpace}/${targetColorSpace} shadow:${shadowMapType}`\n}\n\nfunction farBeyondNeutralFog(scene: Scene, sealed: Sealed): boolean {\n const fog = scene.fog as { isFog?: boolean; near?: number } | null\n if (fog?.isFog !== true || fog.near !== NEUTRAL_FOG_NEAR) return false\n return (sealed.cameras ?? []).some((camera) => ((camera as unknown as { far?: number }).far ?? 0) >= NEUTRAL_FOG_NEAR)\n}\n\nexport function checkSealed(sealed: Sealed | null, renderer: RendererState, scene: Scene, group: Object3D): LightCheck {\n const strays = findStrays(scene, group)\n const lights = lightSignature(scene)\n if (sealed === null) return { ok: strays.length === 0, expected: lights, actual: lights, strays }\n const fog = fogStateOf(scene)\n const fogAllowed = sealed.fogStates.includes(fog)\n const expected = describe({ ...sealed, fog: fogAllowed ? fog : sealed.fogStates.join('|') })\n const actual = describe({\n lights,\n fog,\n environment: environmentOf(scene),\n toneMapping: renderer.toneMapping,\n outputColorSpace: renderer.outputColorSpace,\n targetColorSpace: targetColorSpaceOf(sealed.target),\n shadowMapType: renderer.shadowMap.type,\n })\n const ok = strays.length === 0 && expected === actual && !farBeyondNeutralFog(scene, sealed)\n return { ok, expected, actual, strays }\n}\n","import type { HemisphereLight, Object3D, Scene } from 'three'\n\nimport { type ArbiterCore, admitRequest, arbitrateSources, poolOf, stepSlots } from './arbiter'\nimport { type Sealed, captureSealed, checkSealed } from './scene-state'\nimport { type Holder, type Slot, type Vec3, createSlots, createValues, isFree, markShadow, mergeValues, vacate, writeLight } from './slots'\nimport type {\n LightBudget,\n LightBudgetOptions,\n LightHandle,\n LightKind,\n LightLease,\n LightPatch,\n LightRequest,\n LightSource,\n RendererState,\n SourceHandle,\n ThreeForLights,\n} from './types'\n\nconst DEFAULTS = { point: 16, spot: 4, pointShadows: 2, spotShadows: 0, shadowMapSize: 512, urgentReserve: 2, fadeMs: 120, hysteresis: 0.15, hemisphere: 0 }\nconst ZERO: Vec3 = [0, 0, 0]\nconst ALL_LAYERS_MASK = -1\n\nfunction newHolder(kind: Holder['kind'], lightKind: LightKind, request: LightRequest | LightSource, urgent: boolean): Holder {\n return {\n kind,\n owner: request.owner,\n lightKind,\n priority: request.priority,\n urgent,\n values: createValues({ color: request.color, intensity: request.intensity, distance: request.distance, position: [...request.position], ...optional(request) }),\n slot: null,\n alive: true,\n onLost: undefined,\n follow: undefined,\n shadow: false,\n wanted: true,\n distance: 0,\n resolved: createValues({ color: request.color, intensity: 0, distance: 0, position: ZERO }),\n }\n}\n\nfunction optional(request: LightRequest | LightSource): Partial<Holder['values']> {\n const values: Partial<Holder['values']> = {}\n if (request.decay !== undefined) values.decay = request.decay\n if (request.target !== undefined) values.target = [...request.target]\n if (request.angle !== undefined) values.angle = request.angle\n if (request.penumbra !== undefined) values.penumbra = request.penumbra\n return values\n}\n\nfunction isAttached(object: Object3D, root: Object3D): boolean {\n for (let node: Object3D | null = object; node !== null; node = node.parent) if (node === root) return true\n return false\n}\n\nexport function createLightBudget(THREE: ThreeForLights, scene: Object3D, options: LightBudgetOptions = {}): LightBudget {\n const opts = { ...DEFAULTS, ...options }\n const pointShadows = Math.min(opts.pointShadows, opts.point)\n const spotShadows = Math.min(opts.spotShadows, opts.spot)\n const group = new THREE.Group()\n group.name = 'light-budget'\n const core: ArbiterCore = {\n THREE,\n slots: {\n point: createSlots(THREE, group, 'point', opts.point, pointShadows, opts.shadowMapSize, options.shadowSettings),\n spot: createSlots(THREE, group, 'spot', opts.spot, spotShadows, opts.shadowMapSize, options.shadowSettings),\n },\n urgentReserve: opts.urgentReserve,\n fadeMs: opts.fadeMs,\n hysteresis: opts.hysteresis,\n preempted: 0,\n refused: 0,\n }\n const hemispheres: HemisphereLight[] = []\n for (let index = 0; index < opts.hemisphere; index++) {\n const light = new THREE.HemisphereLight(0xffffff, 0xffffff, 0)\n light.layers.enableAll()\n group.add(light)\n hemispheres.push(light)\n }\n scene.add(group)\n scene.children.splice(scene.children.indexOf(group), 1)\n scene.children.unshift(group)\n\n const sources = new Set<Holder>()\n const world = new THREE.Vector3()\n const direction = new THREE.Vector3()\n let sealed: Sealed | null = null\n let leasedHemispheres = 0\n\n const allSlots = (): Slot[] => [...core.slots.point, ...core.slots.spot]\n\n function writeHolder(holder: Holder): void {\n if (holder.slot !== null && holder.slot.holder === holder) writeLight(THREE, holder.slot, holder.values)\n }\n\n function makeHandle(holder: Holder, release: ((fadeMs: number | undefined) => void) | null): LightHandle {\n return {\n get slot() {\n return holder.slot?.index ?? -1\n },\n kind: holder.lightKind,\n get alive() {\n return holder.alive\n },\n update(patch: LightPatch) {\n if (!holder.alive) return\n mergeValues(holder.values, patch)\n writeHolder(holder)\n },\n release(releaseOptions) {\n if (!holder.alive || release === null) return\n holder.alive = false\n release(releaseOptions?.fadeMs)\n },\n }\n }\n\n function dropHolder(holder: Holder, fadeMs: number): void {\n const slot = holder.slot\n holder.slot = null\n if (slot === null) return\n if (slot.pending === holder) slot.pending = null\n else if (slot.holder === holder) vacate(slot, fadeMs)\n }\n\n function resolveSource(holder: Holder, focus: readonly [number, number, number]): void {\n const { follow, values, resolved } = holder\n holder.wanted = values.intensity > 0\n if (follow === undefined) {\n holder.distance = Math.hypot(values.position[0] - focus[0], values.position[1] - focus[1], values.position[2] - focus[2])\n Object.assign(resolved, values)\n return\n }\n holder.wanted = holder.wanted && isAttached(follow, scene)\n if (!holder.wanted) return\n follow.getWorldPosition(world)\n const [ox, oy, oz] = values.position\n if (ox !== 0 || oy !== 0 || oz !== 0) world.set(ox, oy, oz).applyMatrix4(follow.matrixWorld)\n const axis = follow.matrixWorld.elements\n direction.set(-(axis[8] as number), -(axis[9] as number), -(axis[10] as number))\n holder.distance = Math.hypot(world.x - focus[0], world.y - focus[1], world.z - focus[2])\n Object.assign(resolved, values)\n resolved.position = [world.x, world.y, world.z]\n resolved.target = values.target ?? [world.x + direction.x, world.y + direction.y, world.z + direction.z]\n }\n\n return {\n group,\n sizes: { point: opts.point, spot: opts.spot, pointShadows, spotShadows },\n acquire(request) {\n const holder = newHolder('request', request.kind ?? 'point', request, request.urgent === true)\n holder.onLost = request.onLost\n if (!admitRequest(core, holder)) return null\n return makeHandle(holder, (fadeMs) => dropHolder(holder, fadeMs ?? opts.fadeMs))\n },\n addSource(source) {\n const holder = newHolder('source', source.kind ?? 'point', source, false)\n holder.follow = source.follow\n holder.shadow = source.shadow === true\n sources.add(holder)\n return {\n get slot() {\n return holder.slot !== null && holder.slot.holder === holder ? holder.slot.index : -1\n },\n update(patch: Partial<LightSource>) {\n mergeValues(holder.values, patch)\n if (patch.priority !== undefined) holder.priority = patch.priority\n if ('follow' in patch) holder.follow = patch.follow\n if (holder.follow === undefined) writeHolder(holder)\n },\n remove() {\n holder.alive = false\n holder.wanted = false\n sources.delete(holder)\n dropHolder(holder, opts.fadeMs)\n },\n } satisfies SourceHandle\n },\n lease({ owner, point, shadows = 0 }) {\n const shadowSlots = core.slots.point.filter((slot) => slot.shadow && isFree(slot))\n const plainSlots = core.slots.point.filter((slot) => !slot.shadow && isFree(slot))\n if (shadowSlots.length < shadows || plainSlots.length < point - shadows) return null\n const taken = [...shadowSlots.slice(0, shadows), ...plainSlots.slice(0, point - shadows)]\n const holders = taken.map((slot) => {\n const holder = newHolder('lease', 'point', { owner, priority: Number.POSITIVE_INFINITY, color: 0xffffff, intensity: 0, distance: 0, position: ZERO }, false)\n slot.leased = true\n slot.holder = holder\n slot.fade = 1\n slot.fadeTarget = 1\n holder.slot = slot\n markShadow(slot)\n return holder\n })\n const handles = holders.map((holder) => makeHandle(holder, null))\n return {\n handles,\n invalidateShadow(index) {\n for (const [at, holder] of holders.entries()) if ((index === undefined || index === at) && holder.slot !== null) markShadow(holder.slot)\n },\n release() {\n for (const holder of holders) {\n const slot = holder.slot\n holder.alive = false\n holder.slot = null\n if (slot === null) continue\n slot.leased = false\n slot.holder = null\n vacate(slot, 0)\n }\n },\n } satisfies LightLease\n },\n leaseHemisphere() {\n const light = hemispheres[leasedHemispheres]\n if (light === undefined) return null\n leasedHemispheres++\n return light\n },\n present(focus, dtSeconds) {\n for (const holder of sources) resolveSource(holder, focus)\n for (const kind of ['point', 'spot'] as const) {\n const reserve = kind === 'point' ? core.urgentReserve : 0\n const kindSources = [...sources].filter((holder) => holder.lightKind === kind)\n arbitrateSources(core, poolOf(core, kind, true), kindSources.filter((holder) => holder.shadow), 0)\n arbitrateSources(core, poolOf(core, kind, false), kindSources.filter((holder) => !holder.shadow), reserve)\n }\n stepSlots(core, dtSeconds)\n for (const holder of sources) if (holder.wanted && holder.slot?.holder === holder) writeLight(THREE, holder.slot, holder.resolved)\n },\n seal(renderer, scene, sealOptions = {}) {\n sealed = captureSealed(renderer, scene, sealOptions)\n },\n check(renderer: RendererState, checkedScene: Scene) {\n const result = checkSealed(sealed, renderer, checkedScene, group)\n const layersIntact = allSlots().every((slot) => slot.light.visible && slot.light.layers.mask === ALL_LAYERS_MASK)\n const isFirst = checkedScene.children[0] === group\n if (!layersIntact) return { ...result, ok: false, actual: `${result.actual} | budget lights hidden or off a layer` }\n return isFirst ? result : { ...result, ok: false, actual: `${result.actual} | budget group is not the first child of the scene` }\n },\n stats() {\n const slots = allSlots()\n return {\n lit: slots.filter((slot) => slot.light.intensity > 0).length,\n free: slots.filter(isFree).length,\n leased: slots.filter((slot) => slot.leased).length,\n sources: sources.size,\n sourcesWithoutSlot: [...sources].filter((holder) => holder.wanted && holder.slot === null).length,\n preempted: core.preempted,\n refused: core.refused,\n }\n },\n dispose() {\n group.removeFromParent()\n for (const slot of allSlots()) slot.light.dispose()\n sources.clear()\n },\n }\n}\n","import type { Camera, Group, HemisphereLight, Object3D, Scene, WebGLRenderTarget } from 'three'\n\nimport type { ThreeLib } from '../three-lib'\n\nexport type LightKind = 'point' | 'spot'\nexport type LinearRgb = readonly [number, number, number]\nexport type FogState = 'none' | 'linear' | 'exp2'\nexport type ThreeForLights = Pick<\n ThreeLib,\n 'Group' | 'PointLight' | 'SpotLight' | 'HemisphereLight' | 'Object3D' | 'Vector3' | 'Color' | 'LinearSRGBColorSpace'\n>\n/** What the budget reads of a renderer: a structural subset, so a fake works in Node. */\nexport type RendererState = {\n readonly toneMapping: number\n readonly outputColorSpace: string\n readonly shadowMap: { readonly type: number }\n}\n\nexport interface LightBudgetOptions {\n /** Point lights created at boot. Default 16. */\n readonly point?: number\n /** Spot lights created at boot. Default 4. */\n readonly spot?: number\n /** How many of the point lights cast shadows (cube maps). Default 2. Fixed for the life of the budget. */\n readonly pointShadows?: number\n /** How many of the spot lights cast shadows. Default 0. */\n readonly spotShadows?: number\n /** Shadow map size of the shadow slots. Default 512. */\n readonly shadowMapSize?: number\n /** Bias, normal bias and camera near plane of the shadow slots. Defaults: three.js's (0, 0, 0.5). The house's room lights use -0.0012, 0.05 and 0.12. */\n readonly shadowSettings?: { readonly bias?: number; readonly normalBias?: number; readonly near?: number }\n /** Point slots only `urgent` requests may take while others are free. Default 2. */\n readonly urgentReserve?: number\n /** Milliseconds a light fades out when a source loses its slot, and fades in when it gets one. Default 120. */\n readonly fadeMs?: number\n /** A source keeps its slot until a challenger of the same priority is this much closer (0.15 = 15%). Default 0.15. */\n readonly hysteresis?: number\n /** HemisphereLights created at boot for adapters that flash the sky; leased with `leaseHemisphere`. Default 0. */\n readonly hemisphere?: number\n}\n\n/** A light that lives as long as the request: a blast, a rocket in flight, a muzzle flash, a flare in the hand. */\nexport interface LightRequest {\n readonly owner: string\n readonly kind?: LightKind\n /** Higher wins a slot; see LIGHT_PRIORITY. */\n readonly priority: number\n /** Hex (sRGB, like `new Color(hex)`) or linear RGB. */\n readonly color: number | LinearRgb\n readonly intensity: number\n readonly distance: number\n readonly decay?: number\n readonly position: readonly [number, number, number]\n /** Spot only. */\n readonly target?: readonly [number, number, number]\n readonly angle?: number\n readonly penumbra?: number\n /** Takes a slot at once: cuts the lowest victim instead of fading it, and may use the urgent reserve. */\n readonly urgent?: boolean\n /** Called once when a higher priority took the slot; the handle is dead from then on. */\n readonly onLost?: () => void\n}\n\nexport type LightPatch = Partial<Pick<LightRequest, 'color' | 'intensity' | 'distance' | 'decay' | 'position' | 'target' | 'angle' | 'penumbra'>>\n\nexport interface LightHandle {\n /** Index of this light among the scene's point (or spot) lights in the shader; stable while the handle lives. */\n readonly slot: number\n readonly kind: LightKind\n readonly alive: boolean\n update(patch: LightPatch): void\n /** Gives the slot back; with fadeMs the light fades first. Idempotent. */\n release(options?: { readonly fadeMs?: number }): void\n}\n\n/** A light that exists in the world for a long time and competes for a slot every frame: a lamp, a street light, a headlight. */\nexport interface LightSource extends Omit<LightRequest, 'urgent' | 'onLost'> {\n /** Wants a shadow slot; a shadow source competes for shadow slots only. */\n readonly shadow?: boolean\n /**\n * The object the light sits on. Its world position (and, for a spot, its world -z as the direction when `target` is absent) is read\n * once per `present`; `position` is then an offset in its local space. A source whose `follow` left the scene counts as off.\n */\n readonly follow?: Object3D\n}\n\nexport interface SourceHandle {\n /** Slot now, or -1 while it has none (its light is not drawn). */\n readonly slot: number\n update(patch: Partial<LightSource>): void\n remove(): void\n}\n\n/** Slots a client keeps for itself for good: the room lights of a house (their masks are indexed by slot). */\nexport interface LightLease {\n readonly handles: readonly LightHandle[]\n /** Redraws the shadow map of one leased slot (`handles[index]`), or of all of them, on the next render: the walls it shades changed. */\n invalidateShadow(index?: number): void\n release(): void\n}\n\nexport interface SealOptions {\n /** Fog states the game will switch between at run time. Default: only the state at seal time. */\n readonly fogStates?: readonly FogState[]\n /** Cameras whose `far` must stay under the neutral fog's near (read at every `check`). */\n readonly cameras?: readonly Camera[]\n /** Where the game really draws; its colour space is part of the sealed state. */\n readonly target?: WebGLRenderTarget | null\n}\n\nexport interface LightCheck {\n readonly ok: boolean\n readonly expected: string\n readonly actual: string\n /** Paths of PointLight/SpotLight found outside the budget's group. */\n readonly strays: readonly string[]\n}\n\nexport interface LightBudgetStats {\n readonly lit: number\n readonly free: number\n readonly leased: number\n readonly sources: number\n readonly sourcesWithoutSlot: number\n readonly preempted: number\n readonly refused: number\n}\n\nexport interface LightBudget {\n /** The group holding every light; first child of the scene. Never hidden, never removed while the budget lives. */\n readonly group: Group\n readonly sizes: { readonly point: number; readonly spot: number; readonly pointShadows: number; readonly spotShadows: number }\n acquire(request: LightRequest): LightHandle | null\n addSource(source: LightSource): SourceHandle\n /** Takes `point` slots (of which `shadows` shadow slots) for good. null when not enough unleased slots remain. */\n lease(options: { readonly owner: string; readonly point: number; readonly shadows?: number }): LightLease | null\n /** Takes one of the boot HemisphereLights for good. null when none is left. Its intensity starts at 0. */\n leaseHemisphere(owner: string): HemisphereLight | null\n /** Once per frame, with the camera (or the player) as focus. `dtSeconds` is real time: fades do not stop in pause. */\n present(focus: readonly [number, number, number], dtSeconds: number): void\n /** After this, any change in the program-key state of the scene is a bug and `check()` reports it. */\n seal(renderer: RendererState, scene: Scene, options?: SealOptions): void\n check(renderer: RendererState, scene: Scene): LightCheck\n stats(): LightBudgetStats\n dispose(): void\n}\n\nexport const LIGHT_PRIORITY = {\n blast: 900,\n rocket: 800,\n muzzle: 750,\n fire: 600,\n flare: 500,\n headlight: 400,\n weaponLight: 400,\n lamp: 200,\n street: 150,\n} as const\n","import type { LightBudget } from './types'\n\n/** For adapters that receive the budget as `unknown` through the host (the contract does not know three). */\nexport function isLightBudget(value: unknown): value is LightBudget {\n const candidate = value as Partial<LightBudget> | null | undefined\n return typeof candidate === 'object' && candidate !== null && typeof candidate.acquire === 'function' && typeof candidate.addSource === 'function' && typeof candidate.lease === 'function' && typeof candidate.present === 'function'\n}\n","import type { Material } from 'three'\n\n/** What `programKeysOf` reads of a renderer: `properties` is internal to three r160 and may be absent in another version. */\nexport type PropertiesReader = { readonly properties?: { get(object: object): unknown } }\n\n/**\n * Every program cache key three linked for `material` (one per state it was drawn in: fog type, shadow, light counts). Reads three's\n * internal `properties.get(material).programs` Map (r160). `[]` for a material never drawn; `undefined` when this three does not have\n * that internal, which sends the pools to their conservative mode.\n */\nexport function programKeysOf(renderer: PropertiesReader, material: Material): readonly string[] | undefined {\n const properties = renderer.properties\n if (typeof properties?.get !== 'function') return undefined\n const programs = (properties.get(material) as { programs?: unknown } | undefined)?.programs\n if (programs === undefined) return []\n if (!(programs instanceof Map)) return undefined\n return [...programs.keys()].map(String)\n}\n","import type { Material } from 'three'\n\nimport { type PropertiesReader, programKeysOf } from '../program-key'\n\n/** Materials kept alive and never disposed, one per program family: what keeps a program linked once its users are gone. */\nexport interface MaterialAnchors {\n /**\n * Keeps `material` if any of its program keys has no anchor yet; returns true when it was kept (the caller must then not dispose it).\n * A material whose keys are all covered returns false and may be disposed. A material never drawn is not kept.\n */\n offer(material: Material): boolean\n readonly size: number\n /** Where program keys are read from. Until bound (or when three has no readable keys) every offered material is kept. */\n bind(renderer: PropertiesReader): void\n}\n\nexport function createMaterialAnchors(renderer?: PropertiesReader): MaterialAnchors {\n const anchors = new Set<Material>()\n let reader = renderer\n\n function coveredKeys(): Set<string> {\n const covered = new Set<string>()\n if (reader === undefined) return covered\n for (const anchor of anchors) for (const key of programKeysOf(reader, anchor) ?? []) covered.add(key)\n return covered\n }\n\n return {\n get size() {\n return anchors.size\n },\n bind(next) {\n reader = next\n },\n offer(material) {\n if (anchors.has(material)) return true\n const keys = reader === undefined ? undefined : programKeysOf(reader, material)\n if (keys === undefined) {\n anchors.add(material)\n return true\n }\n if (keys.length === 0) return false\n const covered = coveredKeys()\n if (keys.every((key) => covered.has(key))) return false\n anchors.add(material)\n return true\n },\n }\n}\n","import type { Object3D } from 'three'\n\nimport type { ThreeLib } from './three-lib'\nimport type { WarmupProbe } from './warmup-types'\n\n/** The sprite probes the room builds by hand today: one sprite material with fog and one without. */\nexport function spriteProbes(THREE: Pick<ThreeLib, 'Group' | 'Sprite' | 'SpriteMaterial' | 'DataTexture'>): WarmupProbe {\n return {\n id: 'sprite',\n build() {\n const group = new THREE.Group()\n const texture = new THREE.DataTexture(new Uint8Array([255, 255, 255, 255]), 1, 1)\n texture.needsUpdate = true\n const materials = [true, false].map((fog) => new THREE.SpriteMaterial({ map: texture, fog, transparent: true }))\n for (const material of materials) group.add(new THREE.Sprite(material))\n return { group, materials, dispose: () => texture.dispose() }\n },\n }\n}\n\nexport function hasMissingDistanceMaterial(root: Object3D): string | undefined {\n let found: string | undefined\n root.traverse((object) => {\n const mesh = object as { isInstancedMesh?: boolean; castShadow?: boolean; customDepthMaterial?: unknown; customDistanceMaterial?: unknown }\n if (found === undefined && mesh.isInstancedMesh === true && mesh.castShadow === true && mesh.customDepthMaterial && !mesh.customDistanceMaterial) found = object.name || object.type\n })\n return found\n}\n","import type { BufferGeometry, Camera, Material, Object3D, Scene } from 'three'\n\nimport { fogStateOf, lightSignature } from './lights/scene-state'\nimport type { FogState } from './lights/types'\nimport { createMaterialAnchors } from './pools/anchors'\nimport { hasMissingDistanceMaterial } from './probes'\nimport type { ThreeLib } from './three-lib'\nimport type { EffectWarmup, ProgramWarmup, WarmupContext, WarmupOptions, WarmupProbe, WarmupReport, WarmupRenderer } from './warmup-types'\n\nconst EFFECT_STEP_SECONDS = 1 / 50\nconst DEFAULT_EFFECT_FRAMES = 3\nconst WARM_FOG_DENSITY = 0.01\nconst WARM_FOG_FAR = 100\n\ntype LinkCounter = { count: number }\ntype BuiltProbe = { readonly probe: WarmupProbe; readonly built: ReturnType<WarmupProbe['build']> }\n\nfunction register<TItem>(items: Set<TItem>, item: TItem): () => void {\n items.add(item)\n return () => void items.delete(item)\n}\n\nfunction countLinks(context: WarmupContext, counters: WeakMap<WarmupContext, LinkCounter>): void {\n const existing = counters.get(context)\n if (existing !== undefined) {\n existing.count = 0\n return\n }\n const counter: LinkCounter = { count: 0 }\n counters.set(context, counter)\n const original = context.linkProgram.bind(context)\n context.linkProgram = (program) => {\n counter.count++\n original(program)\n }\n}\n\nfunction failedProgram(renderer: WarmupRenderer): string | undefined {\n for (const program of renderer.info.programs ?? []) {\n const diagnostics = (program as { diagnostics?: { runnable?: boolean; programLog?: string } }).diagnostics\n if (diagnostics?.runnable === false) return diagnostics.programLog ?? 'program failed to link'\n }\n return undefined\n}\n\n/**\n * Finishes every program the renderer holds: three.js links a program when a material first compiles, but reads its link status, its logs and its\n * uniform locations only on the program's first draw (`getUniforms`), and that read waits for the driver to end the link: ~7 ms a program on the\n * RTX 5070 laptop, 43 ms for the six shadow-receiving programs the horde chain's first blast drew for the first time (perf.mjs, 2026-11-03).\n * A program linked here and drawn only in play (a hidden part of a vehicle, an effect of another area) pays that at load instead.\n */\nfunction finishPrograms(renderer: WarmupRenderer): number {\n let finished = 0\n for (const program of renderer.info.programs ?? []) {\n const linked = program as { getUniforms?: () => unknown; getAttributes?: () => unknown }\n if (typeof linked.getUniforms !== 'function') continue\n linked.getUniforms()\n linked.getAttributes?.()\n finished++\n }\n return finished\n}\n\nexport function createProgramWarmup(THREE: Pick<ThreeLib, 'Group' | 'Sprite' | 'SpriteMaterial' | 'DataTexture' | 'Fog' | 'FogExp2' | 'Mesh' | 'Points' | 'LineSegments' | 'BufferGeometry' | 'BufferAttribute'>): ProgramWarmup {\n const probes = new Set<WarmupProbe>()\n const effects = new Set<EffectWarmup>()\n const textures = new Set<Parameters<WarmupRenderer['initTexture']>[0]>()\n const anchors = createMaterialAnchors()\n const linkCounters = new WeakMap<WarmupContext, LinkCounter>()\n\n function setFog(scene: Scene, state: FogState): void {\n const color = scene.fog?.color.getHex() ?? 0\n if (state === 'none') scene.fog = null\n else if (state === 'linear' && (scene.fog as { isFog?: boolean } | null)?.isFog !== true) scene.fog = new THREE.Fog(color, 1, WARM_FOG_FAR)\n else if (state === 'exp2' && (scene.fog as { isFogExp2?: boolean } | null)?.isFogExp2 !== true) scene.fog = new THREE.FogExp2(color, WARM_FOG_DENSITY)\n }\n\n function draw(renderer: WarmupRenderer, scene: Scene, camera: Camera, target: WarmupOptions['target']): void {\n renderer.setRenderTarget(target ?? null)\n renderer.compile(scene, camera)\n renderer.render(scene, camera)\n }\n\n /**\n * Hides every drawable of the scene outside `root` and returns the undo. An object with a light under it stays: the lights are what the programs\n * are keyed by, and hiding one would warm programs for a light count the game never draws.\n */\n function drawOnly(scene: Scene, root: Object3D): () => void {\n const hidden: Object3D[] = []\n const visit = (object: Object3D): void => {\n if (object === root || !object.visible) return\n const drawable = object as Object3D & { isMesh?: boolean; isPoints?: boolean; isLine?: boolean; isSprite?: boolean }\n if (drawable.isMesh === true || drawable.isPoints === true || drawable.isLine === true || drawable.isSprite === true) {\n if (!holdsLight(object) && !holdsObject(object, root)) {\n object.visible = false\n hidden.push(object)\n return\n }\n }\n for (const child of object.children) visit(child)\n }\n for (const child of scene.children) visit(child)\n return () => hidden.forEach((object) => (object.visible = true))\n }\n\n function holdsLight(object: Object3D): boolean {\n let found = false\n object.traverse((each) => {\n if ((each as { isLight?: boolean }).isLight === true) found = true\n })\n return found\n }\n\n function holdsObject(object: Object3D, target: Object3D): boolean {\n for (let at: Object3D | null = target; at !== null; at = at.parent) if (at === object) return true\n return false\n }\n\n function playEffect(renderer: WarmupRenderer, scene: Scene, camera: Camera, effect: EffectWarmup, target: WarmupOptions['target']): void {\n const root = effect.root?.()\n const restore = root === null || root === undefined ? (): void => undefined : drawOnly(scene, root)\n try {\n playFrames(renderer, scene, camera, effect, target)\n } finally {\n restore()\n }\n }\n\n function playFrames(renderer: WarmupRenderer, scene: Scene, camera: Camera, effect: EffectWarmup, target: WarmupOptions['target']): void {\n effect.play()\n try {\n for (let frame = 0; frame < (effect.frames ?? DEFAULT_EFFECT_FRAMES); frame++) {\n effect.step(EFFECT_STEP_SECONDS)\n renderer.setRenderTarget(target ?? null)\n renderer.render(scene, camera)\n }\n } finally {\n effect.stop()\n }\n }\n\n /** One object per material, of the kind its program is for, each a single triangle: drawn once so that the program is used, not only linked. */\n function materialsProbe(materials: readonly Material[]): BuiltProbe {\n const geometry = new THREE.BufferGeometry()\n geometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array([0, 0, 0, 0.001, 0, 0, 0, 0.001, 0]), 3))\n const group = new THREE.Group()\n for (const material of materials) group.add(drawableOf(material, geometry))\n return { probe: { id: 'materials', build: () => ({ group, materials: [], dispose: () => geometry.dispose() }) }, built: { group, materials: [], dispose: () => geometry.dispose() } }\n }\n\n function drawableOf(material: Material, geometry: BufferGeometry): Object3D {\n const flags = material as { isSpriteMaterial?: boolean; isPointsMaterial?: boolean; isLineBasicMaterial?: boolean }\n const object: Object3D =\n flags.isSpriteMaterial === true ? new THREE.Sprite(material as never)\n : flags.isPointsMaterial === true ? new THREE.Points(geometry, material)\n : flags.isLineBasicMaterial === true ? new THREE.LineSegments(geometry, material)\n : new THREE.Mesh(geometry, material)\n object.frustumCulled = false\n return object\n }\n\n function buildProbes(materials: readonly Material[] | undefined): BuiltProbe[] {\n const built = [...probes].map((probe) => ({ probe, built: probe.build() }))\n if (materials !== undefined && materials.length > 0) built.push(materialsProbe(materials))\n return built\n }\n\n function addProbes(scene: Scene, built: readonly BuiltProbe[]): void {\n for (const { probe, built: item } of built) {\n const missing = hasMissingDistanceMaterial(item.group)\n if (missing !== undefined) throw new Error(`warmup probe \"${probe.id}\": instanced mesh \"${missing}\" casts shadows with a customDepthMaterial but no customDistanceMaterial`)\n scene.add(item.group)\n }\n }\n\n function removeProbes(scene: Scene, built: readonly BuiltProbe[]): void {\n for (const { built: item } of built) {\n scene.remove(item.group)\n item.dispose()\n for (const material of item.materials) anchors.offer(material)\n }\n }\n\n function drawStates(renderer: WarmupRenderer, scene: Scene, camera: Camera, states: readonly FogState[], target: WarmupOptions['target']): void {\n for (const state of states) {\n setFog(scene, state)\n draw(renderer, scene, camera, target)\n }\n }\n\n /** Makes every instanced batch draw at least once: three skips an empty instanced batch, and a driver finishes a program's state on its first real draw (a 200 ms stall). Returns the undo. */\n function forceDraws(scene: Scene): () => void {\n const undo: Array<() => void> = []\n scene.traverse((object) => {\n const drawable = object as Object3D & { isInstancedMesh?: boolean; count?: number; geometry?: { isInstancedBufferGeometry?: boolean; instanceCount?: number } }\n const { geometry } = drawable\n if (geometry?.isInstancedBufferGeometry === true && geometry.instanceCount === 0) {\n geometry.instanceCount = 1\n undo.push(() => (geometry.instanceCount = 0))\n }\n if (drawable.isInstancedMesh === true && drawable.count === 0) {\n drawable.count = 1\n undo.push(() => (drawable.count = 0))\n }\n })\n return () => undo.forEach((restore) => restore())\n }\n\n return {\n anchors,\n register: (probe) => register(probes, probe),\n registerEffect: (effect) => register(effects, effect),\n registerTexture: (texture) => register(textures, texture),\n warmUp(renderer, scene, camera, options = {}): WarmupReport {\n const started = performance.now()\n const { lights, target } = options\n anchors.bind(renderer)\n const originalFog = scene.fog\n const states = options.fogStates ?? [fogStateOf(scene)]\n lights?.seal(renderer, scene, { fogStates: options.fogStates, cameras: [camera], target })\n scene.traverse((object) => {\n const light = object as { castShadow?: boolean; shadow?: { needsUpdate: boolean } }\n if (light.castShadow === true && light.shadow !== undefined) light.shadow.needsUpdate = true\n })\n const built = buildProbes(options.materials)\n let restoreDraws = (): void => undefined\n try {\n addProbes(scene, built)\n restoreDraws = forceDraws(scene)\n drawStates(renderer, scene, camera, states, target)\n scene.fog = originalFog\n for (const effect of effects) playEffect(renderer, scene, camera, effect, target)\n for (const texture of textures) renderer.initTexture(texture)\n } finally {\n restoreDraws()\n scene.fog = originalFog\n removeProbes(scene, built)\n }\n // also what lets failedProgram see a broken link: three only fills `diagnostics` on the first use\n finishPrograms(renderer)\n const context = renderer.getContext()\n const uniformVectors = Number(context.getParameter(context.MAX_FRAGMENT_UNIFORM_VECTORS))\n const failure = failedProgram(renderer)\n if (failure !== undefined) {\n throw new Error(`a shader program failed to link (MAX_FRAGMENT_UNIFORM_VECTORS ${uniformVectors}, lights: ${lightSignature(scene)}): ${failure}`)\n }\n countLinks(context, linkCounters)\n // the GPU process is still working through all of the above: wait here, at load, or the first upload of play stalls on it\n if (context.RGBA !== undefined && context.UNSIGNED_BYTE !== undefined) context.readPixels?.(0, 0, 1, 1, context.RGBA, context.UNSIGNED_BYTE, new Uint8Array(4))\n return {\n programs: renderer.info.programs?.length ?? 0,\n geometries: renderer.info.memory.geometries,\n textures: renderer.info.memory.textures,\n milliseconds: performance.now() - started,\n lightSignature: lightSignature(scene),\n uniformVectors,\n anchors: anchors.size,\n }\n },\n linkedSince(renderer) {\n return linkCounters.get(renderer.getContext())?.count ?? 0\n },\n }\n}\n","const POOLED = '__pooled'\n\ntype Disposable = { dispose(): void; userData: Record<string, unknown> }\n\nfunction isDevelopment(): boolean {\n return typeof process === 'undefined' || process.env.NODE_ENV !== 'production'\n}\n\n/** Marks a pool resource and, in development, warns when something disposes it directly instead of releasing it to the pool. */\nexport function markPooled(resource: Disposable, key: string, owner: string): { disposeForPool(): void } {\n resource.userData[POOLED] = key\n const original = resource.dispose.bind(resource)\n let fromPool = false\n if (isDevelopment()) {\n resource.dispose = () => {\n if (!fromPool) console.warn(`[game-render-three] ${owner} \"${key}\" was disposed directly; release it to its pool instead`)\n original()\n }\n }\n return {\n disposeForPool() {\n fromPool = true\n original()\n fromPool = false\n },\n }\n}\n\nexport function pooledKey(resource: { userData: Record<string, unknown> }): string | undefined {\n const key = resource.userData[POOLED]\n return typeof key === 'string' ? key : undefined\n}\n\n/** A number as a short stable string, rounded to `step`. */\nexport function roundedNumber(value: number, step: number): string {\n return String(Number((Math.round(value / step) * step).toFixed(6)))\n}\n","import type { BufferGeometry } from 'three'\n\nimport { markPooled, pooledKey, roundedNumber } from './pooled'\n\nexport interface GeometryPool {\n /** The geometry for `key`, built once with `build`. Counts one use. */\n acquire(key: string, build: () => BufferGeometry): BufferGeometry\n release(geometry: BufferGeometry): void\n /** Never disposed while the pool lives (unit shapes, effect geometries warmed at load). */\n retain(key: string): void\n /** Disposes unretained geometries with no use, oldest first, keeping at most `keep` idle ones. Only at load points, never in play. */\n trim(keep?: number): number\n stats(): { readonly entries: number; readonly retained: number; readonly inUse: number }\n dispose(): void\n}\n\ntype Entry = { geometry: BufferGeometry; uses: number; retained: boolean; idleSince: number; disposeForPool(): void }\n\nconst DEFAULT_KEY_STEP = 1e-3\n\n/** Builds a key from a kind and its numbers, rounded to `step`, so near-equal sizes share one geometry. */\nexport function geometryKey(kind: string, params: readonly number[], step: number = DEFAULT_KEY_STEP): string {\n return [kind, ...params.map((value) => roundedNumber(value, step))].join('|')\n}\n\nexport function createGeometryPool(): GeometryPool {\n const entries = new Map<string, Entry>()\n let clock = 0\n\n function disposeEntry(key: string, entry: Entry): void {\n entries.delete(key)\n entry.disposeForPool()\n }\n\n return {\n acquire(key, build) {\n let entry = entries.get(key)\n if (entry === undefined) {\n const geometry = build()\n entry = { geometry, uses: 0, retained: false, idleSince: 0, ...markPooled(geometry, key, 'geometry') }\n entries.set(key, entry)\n }\n entry.uses++\n return entry.geometry\n },\n release(geometry) {\n const key = pooledKey(geometry)\n const entry = key === undefined ? undefined : entries.get(key)\n if (entry === undefined || entry.uses === 0) return\n entry.uses--\n if (entry.uses === 0) entry.idleSince = ++clock\n },\n retain(key) {\n const entry = entries.get(key)\n if (entry !== undefined) entry.retained = true\n },\n trim(keep = 0) {\n const idle = [...entries].filter(([, entry]) => entry.uses === 0 && !entry.retained).sort((a, b) => a[1].idleSince - b[1].idleSince)\n const excess = idle.slice(0, Math.max(0, idle.length - keep))\n for (const [key, entry] of excess) disposeEntry(key, entry)\n return excess.length\n },\n stats() {\n const all = [...entries.values()]\n return { entries: all.length, retained: all.filter((entry) => entry.retained).length, inUse: all.filter((entry) => entry.uses > 0).length }\n },\n dispose() {\n for (const [key, entry] of [...entries]) disposeEntry(key, entry)\n },\n }\n}\n","import type { Material } from 'three'\n\nimport type { PropertiesReader } from '../program-key'\nimport { type MaterialAnchors, createMaterialAnchors } from './anchors'\nimport { markPooled, pooledKey, roundedNumber } from './pooled'\n\nexport interface MaterialPool {\n /**\n * The material for `key`, built once. Shared: callers must not change it. Per-instance variety goes into instance attributes, not\n * into new materials.\n */\n acquire<TMaterial extends Material>(key: string, build: () => TMaterial): TMaterial\n release(material: Material): void\n /** Declares a variant that must never be disposed (its program stays linked). Effects register theirs at load. */\n register<TMaterial extends Material>(key: string, build: () => TMaterial): TMaterial\n /**\n * A material the caller owns and changes per frame (a puff's opacity): not shared. On `release` the first one of its program family is\n * handed to the anchors, so disposing the others never frees the program.\n */\n own<TMaterial extends Material>(build: () => TMaterial): TMaterial\n /** The materials declared with `register`: the ones that must never be disposed and whose programs the warmup draws once. */\n registeredMaterials?(): readonly Material[]\n /** Disposes unregistered materials with no use, oldest first, keeping at most `keep` idle ones; a family's anchor is kept alive. */\n trim(keep?: number): number\n stats(): { readonly entries: number; readonly registered: number; readonly inUse: number; readonly families: number }\n /**\n * The end of the pool: disposes every material it made, in use or not, registered or anchored (the guarantee that a program stays linked\n * holds while the pool lives, not after it). The anchors object itself is not touched, and the pool is empty and usable again afterwards.\n */\n dispose(): void\n}\n\nexport type MaterialPoolOptions = { readonly anchors?: MaterialAnchors; readonly renderer?: PropertiesReader }\n\ntype Entry = { material: Material; uses: number; registered: boolean; idleSince: number; disposeForPool(): void }\n\nconst DEFAULT_KEY_STEP = 1e-3\n\n/** Key from the material class and every parameter that changes its look, sorted, numbers rounded. */\nexport function materialKey(type: string, params: Readonly<Record<string, unknown>>): string {\n const parts = Object.keys(params)\n .sort()\n .map((name) => {\n const value = params[name]\n return `${name}=${typeof value === 'number' ? roundedNumber(value, DEFAULT_KEY_STEP) : JSON.stringify(value)}`\n })\n return [type, ...parts].join('|')\n}\n\nexport function createMaterialPool(options: MaterialPoolOptions = {}): MaterialPool {\n const anchors = options.anchors ?? createMaterialAnchors()\n if (options.renderer !== undefined) anchors.bind(options.renderer)\n const entries = new Map<string, Entry>()\n const owned = new Map<Material, () => void>()\n let clock = 0\n\n function createEntry(key: string, material: Material, registered: boolean): Entry {\n const entry: Entry = { material, uses: 0, registered, idleSince: 0, ...markPooled(material, key, 'material') }\n entries.set(key, entry)\n return entry\n }\n\n /** Disposes the material unless it is the anchor of a program family. */\n function retire(material: Material, disposeForPool: () => void): boolean {\n if (anchors.offer(material)) return false\n disposeForPool()\n return true\n }\n\n return {\n acquire(key, build) {\n const entry = entries.get(key) ?? createEntry(key, build(), false)\n entry.uses++\n return entry.material as ReturnType<typeof build>\n },\n register(key, build) {\n const entry = entries.get(key) ?? createEntry(key, build(), true)\n entry.registered = true\n return entry.material as ReturnType<typeof build>\n },\n own(build) {\n const material = build()\n owned.set(material, markPooled(material, 'own', 'material').disposeForPool)\n return material\n },\n release(material) {\n const disposeOwned = owned.get(material)\n if (disposeOwned !== undefined) {\n owned.delete(material)\n retire(material, disposeOwned)\n return\n }\n const key = pooledKey(material)\n const entry = key === undefined ? undefined : entries.get(key)\n if (entry === undefined || entry.uses === 0) return\n entry.uses--\n if (entry.uses === 0) entry.idleSince = ++clock\n },\n trim(keep = 0) {\n const idle = [...entries].filter(([, entry]) => entry.uses === 0 && !entry.registered).sort((a, b) => a[1].idleSince - b[1].idleSince)\n let disposed = 0\n for (const [key, entry] of idle.slice(0, Math.max(0, idle.length - keep))) {\n entries.delete(key)\n if (retire(entry.material, entry.disposeForPool)) disposed++\n }\n return disposed\n },\n registeredMaterials() {\n return [...entries.values()].filter((entry) => entry.registered).map((entry) => entry.material)\n },\n stats() {\n const all = [...entries.values()]\n return {\n entries: all.length,\n registered: all.filter((entry) => entry.registered).length,\n inUse: all.filter((entry) => entry.uses > 0).length,\n families: anchors.size,\n }\n },\n dispose() {\n for (const entry of entries.values()) entry.disposeForPool()\n for (const disposeOwned of owned.values()) disposeOwned()\n entries.clear()\n owned.clear()\n },\n }\n}\n","import type { GeometryPool } from './geometry'\nimport type { MaterialPool } from './material'\n\n/** The pools a host lends its adapters (`host.pools`): geometries and materials that outlive the effect that asked for them. */\nexport interface RenderPools {\n readonly geometries: GeometryPool\n readonly materials: MaterialPool\n}\n\n/** For adapters that receive the pools as `unknown` through the host (the contract does not know three). */\nexport function isRenderPools(value: unknown): value is RenderPools {\n const candidate = value as Partial<RenderPools> | null | undefined\n return typeof candidate === 'object' && candidate !== null && typeof candidate.geometries?.acquire === 'function' && typeof candidate.materials?.acquire === 'function'\n}\n","/** The part of a WebGL2 context the pacer reads; the renderer's `getContext()` satisfies it. */\nexport type FramePacerContext = {\n readonly SYNC_GPU_COMMANDS_COMPLETE: number\n readonly TIMEOUT_EXPIRED: number\n fenceSync(condition: number, flags: number): WebGLSync | null\n clientWaitSync(sync: WebGLSync, flags: number, timeout: number): number\n deleteSync(sync: WebGLSync | null): void\n flush(): void\n}\n\nexport type FramePacerOptions = {\n /** Most frames in a row the pacer may hold back before it lets one through (default 3), so a stuck fence cannot freeze the picture. */\n readonly maxSkips?: number\n /** Frames the GPU may be behind before a draw is held back (default 2). The browser reports a fence only some 20 ms after the GPU finished it, so 1 would cap every GPU at one frame per report. */\n readonly framesInFlight?: number\n}\n\nexport type FramePacer = {\n /** True when fewer than `framesInFlight` drawn frames are unfinished: draw this frame. False: the GPU is too far behind, skip the draw and keep simulating. */\n ready(): boolean\n /** Fences the frame just drawn; call it right after the render. */\n submitted(): void\n /** Draws held back so far. */\n readonly skipped: number\n dispose(): void\n}\n\nconst DEFAULT_MAX_SKIPS = 3\nconst DEFAULT_FRAMES_IN_FLIGHT = 2\n\n/**\n * Keeps the GPU a few frames behind the CPU at most. A frame loop that draws as fast as it can (no vsync, as the perf runs do, or a slow GPU) fills the driver's queue and\n * then blocks in the compositor for the length of the whole queue: a run of 5 ms frames and a 150 ms one. Holding back the draw while the last frame is unfinished spends that\n * time on the next simulation step instead, and the longest frame is the GPU's own. A context without fences (WebGL1) is never held back.\n */\nexport function createFramePacer(context: FramePacerContext | null, options: FramePacerOptions = {}): FramePacer {\n const maxSkips = options.maxSkips ?? DEFAULT_MAX_SKIPS\n const framesInFlight = options.framesInFlight ?? DEFAULT_FRAMES_IN_FLIGHT\n const fences: WebGLSync[] = []\n let skippedInRow = 0\n let skipped = 0\n const dropOldest = (): void => {\n const oldest = fences.shift()\n if (oldest && context) context.deleteSync(oldest)\n }\n return {\n ready() {\n if (!context) return true\n while (fences.length > 0 && context.clientWaitSync(fences[0] as WebGLSync, 0, 0) !== context.TIMEOUT_EXPIRED) dropOldest()\n if (fences.length >= framesInFlight && skippedInRow < maxSkips) {\n skippedInRow++\n skipped++\n return false\n }\n skippedInRow = 0\n if (fences.length >= framesInFlight) dropOldest()\n return true\n },\n submitted() {\n if (!context || typeof context.fenceSync !== 'function') return\n const fence = context.fenceSync(context.SYNC_GPU_COMMANDS_COMPLETE, 0)\n if (fence) fences.push(fence)\n context.flush()\n },\n get skipped() {\n return skipped\n },\n dispose() {\n while (fences.length > 0) dropOldest()\n },\n }\n}\n","import type { Bone, BufferAttribute, BufferGeometry, Material, Matrix3, Matrix4, Mesh, Object3D, SkinnedMesh } from 'three'\n\nimport type { ThreeLib } from './three-lib'\n\n/** The layer a batched mesh is moved to: no camera draws it, a raycaster that enables every layer still finds it. */\nexport const BATCHED_LAYER = 30\n\nconst MATRIX_EPSILON = 1e-6\n\nexport type StaticBatcherOptions = {\n /** Which meshes may be batched (default every shown mesh with one material and no instancing, skinning or morphing). */\n readonly include?: (mesh: Mesh) => boolean\n /** Milliseconds of merging per `update` (default all at once, in the call): batches are made a material at a time over the next frames. */\n readonly budgetMs?: number\n /**\n * The meshes move on their own (a body posed bone by bone): each batch is skinned to its meshes, so a mesh carries its part of the batch\n * wherever it goes, and only a new geometry, material, dent or hiding gives it its own draw back.\n */\n readonly followMeshes?: boolean\n}\n\nexport type StaticBatcherStats = { readonly batches: number; readonly batched: number; readonly released: number }\n\nexport type StaticBatcher = {\n /** The batch meshes, a child of the root. */\n readonly group: Object3D\n /**\n * Gives back to its own draw every batched mesh that changed since the batch was made (moved, dented, hidden, given another geometry or\n * material); returns how many it released. Call it once per frame, before the render.\n */\n update(): number\n stats(): StaticBatcherStats\n /** Every mesh draws on its own again and the batches are disposed. */\n dispose(): void\n}\n\ntype Source = {\n readonly mesh: Mesh\n readonly geometry: BufferGeometry\n readonly material: Material\n readonly version: number\n readonly local: Matrix4\n /** The mesh's world matrix when last seen unchanged: equal again, nothing moved and the local check is skipped. */\n readonly world: Matrix4\n readonly mask: number\n readonly batch: Batch\n readonly start: number\n readonly count: number\n released: boolean\n}\n\ntype Batch = { readonly mesh: Mesh; live: number; readonly index: { array: ArrayLike<number> & { [n: number]: number }; needsUpdate: boolean } }\n\nconst ATTRIBUTES = ['position', 'normal', 'uv', 'color'] as const\n\n/** Visible all the way up to `root`; a mesh no longer under it (its model was rebuilt) is not shown. */\nconst isShown = (object: Object3D, root: Object3D): boolean => {\n for (let node: Object3D | null = object; node; node = node.parent) {\n if (node === root) return true\n if (!node.visible) return false\n }\n return false\n}\n\nconst isBatchable = (mesh: Mesh): boolean => {\n const flags = mesh as Mesh & { isInstancedMesh?: boolean; isSkinnedMesh?: boolean }\n if (flags.isInstancedMesh === true || flags.isSkinnedMesh === true || Array.isArray(mesh.material)) return false\n const geometry = mesh.geometry\n return geometry.attributes.position !== undefined && Object.keys(geometry.morphAttributes).length === 0\n}\n\nconst signatureOf = (mesh: Mesh): string => {\n const { attributes } = mesh.geometry\n const present = ATTRIBUTES.filter((name) => attributes[name] !== undefined).map((name) => `${name}${attributes[name]!.itemSize}`)\n return `${(mesh.material as Material).uuid}|${present.join(',')}|${mesh.castShadow ? 1 : 0}${mesh.receiveShadow ? 1 : 0}`\n}\n\nconst sameMatrix = (a: Matrix4, b: Matrix4): boolean => a.elements.every((value, index) => Math.abs(value - (b.elements[index] as number)) < MATRIX_EPSILON)\n\n/**\n * Draws the still, undamaged meshes of an object (a vehicle, a piece of furniture) as one batch per material, and gives each mesh its own draw\n * back the moment it changes. The meshes stay where they are: what hits, dents or cuts them works on them as before, they only leave the\n * camera's layer while batched.\n */\nexport function createStaticBatcher(THREE: ThreeLib, root: Object3D, options: StaticBatcherOptions = {}): StaticBatcher {\n const include = options.include ?? (() => true)\n const follow = options.followMeshes === true\n root.updateMatrixWorld(true)\n const inverse = new THREE.Matrix4().copy(root.matrixWorld).invert()\n const groups = new Map<string, Mesh[]>()\n root.traverse((object) => {\n const mesh = object as Mesh\n if (!mesh.isMesh || !isShown(mesh, root) || !isBatchable(mesh) || !include(mesh)) return\n const key = signatureOf(mesh)\n groups.set(key, [...(groups.get(key) ?? []), mesh])\n })\n const group = new THREE.Group()\n group.name = 'static-batches'\n const sources: Source[] = []\n const pending = [...groups.values()].filter((meshes) => meshes.length >= 2)\n const mergeSome = (deadline: number): void => {\n while (pending.length > 0) {\n const meshes = pending.shift() as Mesh[]\n // a mesh that changed while it waited is left to draw on its own\n if (meshes.every((mesh) => isShown(mesh, root))) sources.push(...mergeGroup(THREE, { meshes, inverse: new THREE.Matrix4().copy(root.matrixWorld).invert(), group, follow }))\n if (performance.now() >= deadline) return\n }\n }\n if (options.budgetMs === undefined) mergeSome(Number.POSITIVE_INFINITY)\n root.add(group)\n const scratch = new THREE.Matrix4()\n let released = 0\n\n const release = (source: Source): void => {\n source.released = true\n source.mesh.layers.mask = source.mask\n const { index } = source.batch\n const first = index.array[source.start] as number\n for (let at = source.start; at < source.start + source.count; at++) index.array[at] = first\n index.needsUpdate = true\n // a batch whose meshes all draw on their own again is an empty draw: it leaves the screen\n if (--source.batch.live === 0) source.batch.mesh.visible = false\n released++\n }\n const hasChanged = (source: Source): boolean => {\n const { mesh } = source\n if (mesh.geometry !== source.geometry || mesh.material !== source.material || !isShown(mesh, root)) return true\n if ((mesh.geometry.attributes.position as BufferAttribute).version !== source.version) return true\n if (follow || sameMatrix(mesh.matrixWorld, source.world)) return false\n if (!sameMatrix(scratch.multiplyMatrices(inverse, mesh.matrixWorld), source.local)) return true\n // the whole object moved, the mesh did not move within it\n source.world.copy(mesh.matrixWorld)\n return false\n }\n\n return {\n group,\n update() {\n if (pending.length > 0) mergeSome(performance.now() + (options.budgetMs ?? Number.POSITIVE_INFINITY))\n const before = released\n inverse.copy(root.matrixWorld).invert()\n for (const source of sources) if (!source.released && hasChanged(source)) release(source)\n return released - before\n },\n stats: () => ({ batches: group.children.length, batched: sources.filter((source) => !source.released).length, released }),\n dispose() {\n for (const source of sources) if (!source.released) source.mesh.layers.mask = source.mask\n group.removeFromParent()\n for (const child of group.children) {\n ;(child as Mesh).geometry.dispose()\n ;(child as SkinnedMesh).skeleton?.dispose()\n }\n },\n }\n}\n\n/** Copies `attribute` into `target` at `offset` vertices: positions through `matrix`, normals through its normal matrix, the rest as they are. */\nfunction copyAttribute(name: string, attribute: BufferAttribute, target: Float32Array, offset: number, matrix: Matrix4, normalMatrix: Matrix3): void {\n const size = attribute.itemSize\n const source = attribute.array as ArrayLike<number>\n const flat = !(attribute as { isInterleavedBufferAttribute?: boolean }).isInterleavedBufferAttribute && source.length === attribute.count * size\n if (name !== 'position' && name !== 'normal') {\n if (flat) target.set(source as Float32Array, offset * size)\n else for (let at = 0; at < attribute.count; at++) for (let k = 0; k < size; k++) target[(offset + at) * size + k] = attribute.getComponent(at, k)\n return\n }\n const e = (name === 'position' ? matrix : normalMatrix).elements\n const isNormal = name === 'normal'\n for (let at = 0; at < attribute.count; at++) {\n const x = attribute.getX(at)\n const y = attribute.getY(at)\n const z = attribute.getZ(at)\n const out = (offset + at) * 3\n if (isNormal) {\n const nx = e[0]! * x + e[3]! * y + e[6]! * z\n const ny = e[1]! * x + e[4]! * y + e[7]! * z\n const nz = e[2]! * x + e[5]! * y + e[8]! * z\n const length = Math.hypot(nx, ny, nz) || 1\n target[out] = nx / length\n target[out + 1] = ny / length\n target[out + 2] = nz / length\n } else {\n target[out] = e[0]! * x + e[4]! * y + e[8]! * z + e[12]!\n target[out + 1] = e[1]! * x + e[5]! * y + e[9]! * z + e[13]!\n target[out + 2] = e[2]! * x + e[6]! * y + e[10]! * z + e[14]!\n }\n }\n}\n\ntype MergeGroupParams = { readonly meshes: readonly Mesh[]; readonly inverse: Matrix4; readonly group: Object3D; readonly follow: boolean }\n\n/** A skinned batch whose bone `n` is mesh `n` itself (no inverse, no bind offset): every vertex lands where its own mesh puts it. */\nfunction skinnedBatch(THREE: ThreeLib, merged: BufferGeometry, material: Material, meshes: readonly Mesh[]): Mesh {\n const vertices = (merged.attributes.position as BufferAttribute).count\n const skinIndex = new Uint16Array(vertices * 4)\n const skinWeight = new Float32Array(vertices * 4)\n let at = 0\n meshes.forEach((mesh, bone) => {\n for (let k = 0; k < (mesh.geometry.attributes.position as BufferAttribute).count; k++, at++) {\n skinIndex[at * 4] = bone\n skinWeight[at * 4] = 1\n }\n })\n merged.setAttribute('skinIndex', new THREE.BufferAttribute(skinIndex, 4))\n merged.setAttribute('skinWeight', new THREE.BufferAttribute(skinWeight, 4))\n const mesh = new THREE.SkinnedMesh(merged, material)\n const identities = meshes.map(() => new THREE.Matrix4())\n mesh.bind(new THREE.Skeleton(meshes as unknown as Bone[], identities), new THREE.Matrix4())\n // the parts move apart as the body is posed: the rest-pose bounds would cull the moved ones\n mesh.frustumCulled = false\n return mesh\n}\n\nfunction mergeGroup(THREE: ThreeLib, { meshes, inverse: rootInverse, group, follow }: MergeGroupParams): Source[] {\n const identity = new THREE.Matrix4()\n const inverse = follow ? identity : rootInverse\n const first = meshes[0] as Mesh\n const names = ATTRIBUTES.filter((name) => first.geometry.attributes[name] !== undefined)\n const pieces = meshes.map((mesh) => {\n const local = new THREE.Matrix4().multiplyMatrices(inverse, mesh.matrixWorld)\n const vertices = (mesh.geometry.attributes.position as BufferAttribute).count\n const indexCount = mesh.geometry.index ? mesh.geometry.index.count : vertices\n return { mesh, local, vertices, indexCount }\n })\n const totalVertices = pieces.reduce((sum, piece) => sum + piece.vertices, 0)\n const totalIndices = pieces.reduce((sum, piece) => sum + piece.indexCount, 0)\n const merged = new THREE.BufferGeometry()\n const normalMatrix = new THREE.Matrix3()\n for (const name of names) {\n const size = first.geometry.attributes[name]!.itemSize\n const array = new Float32Array(totalVertices * size)\n let offset = 0\n for (const piece of pieces) {\n const matrix = follow ? identity : piece.local\n copyAttribute(name, piece.mesh.geometry.attributes[name] as BufferAttribute, array, offset, matrix, normalMatrix.getNormalMatrix(matrix))\n offset += piece.vertices\n }\n merged.setAttribute(name, new THREE.BufferAttribute(array, size))\n }\n const indexArray = totalVertices > 65535 ? new Uint32Array(totalIndices) : new Uint16Array(totalIndices)\n const indexAttribute = new THREE.BufferAttribute(indexArray, 1)\n merged.setIndex(indexAttribute)\n const mesh = follow ? skinnedBatch(THREE, merged, first.material as Material, meshes) : new THREE.Mesh(merged, first.material)\n mesh.name = `batch:${(first.material as Material).name || (first.material as Material).type}`\n mesh.castShadow = first.castShadow\n mesh.receiveShadow = first.receiveShadow\n mesh.raycast = () => undefined\n merged.computeBoundingBox()\n merged.computeBoundingSphere()\n group.add(mesh)\n const batch: Batch = { mesh, live: pieces.length, index: indexAttribute as unknown as Batch['index'] }\n const sources: Source[] = []\n let vertexOffset = 0\n let indexOffset = 0\n for (const piece of pieces) {\n const index = piece.mesh.geometry.index\n if (index) for (let at = 0; at < piece.indexCount; at++) indexArray[indexOffset + at] = index.getX(at) + vertexOffset\n else for (let at = 0; at < piece.indexCount; at++) indexArray[indexOffset + at] = at + vertexOffset\n sources.push({\n mesh: piece.mesh,\n geometry: piece.mesh.geometry,\n material: piece.mesh.material as Material,\n version: (piece.mesh.geometry.attributes.position as BufferAttribute).version,\n local: piece.local,\n world: piece.mesh.matrixWorld.clone(),\n mask: piece.mesh.layers.mask,\n batch,\n start: indexOffset,\n count: piece.indexCount,\n released: false,\n })\n piece.mesh.layers.set(BATCHED_LAYER)\n vertexOffset += piece.vertices\n indexOffset += piece.indexCount\n }\n return sources\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","/**\n * Every visible mesh under `root`, baked into `space` and joined per material. It lives in `@adatechnology/game-shapes-three` now (on the\n * game-shapes merge, the same bytes as the version this package had); this re-export keeps the old import working.\n */\nexport { mergeByMaterial } from '@adatechnology/game-shapes-three'\n","import type { GroupNode, ShapeNode, ShapeSchema, Vec3 } from '@adatechnology/game-shapes'\n\nimport type { CylinderShape, PropItem, PropsApi, Uv } from './types'\n\nexport interface ShapeRecorderOptions {\n /** Radial segments of `cyl` and `cone` (default 12). */\n readonly cylinderSegments?: number\n /** Radial segments of `cyl6` (default 6). */\n readonly hexSegments?: number\n /** Width and height segments of `sphere` (default 12 x 8). */\n readonly sphereSegments?: readonly [number, number]\n /** Name of the schema. */\n readonly name?: string\n}\n\n/** A part as the api received it: the first call's pivot and turn (later calls with the same id draw into it). */\nexport interface RecordedPart {\n readonly id: string\n readonly pivot: Vec3\n readonly ry: number\n}\n\n/** The props api plus parts (the `HouseholdApi` contract): a part draws in its own frame, at `pivot`, turned by `ry`. */\nexport interface ShapeRecorderApi extends PropsApi {\n light(M: PropItem, x: number, y: number, z: number, color: number, range: number, kind: string): void\n part(id: string, pivot: Vec3, ry: number, draw: () => void): void\n}\n\nexport interface ShapeRecorder {\n readonly api: ShapeRecorderApi\n /** The game-shapes schema of everything drawn so far. */\n schema(): ShapeSchema\n /** The parts in the order of their first call. */\n readonly parts: readonly RecordedPart[]\n}\n\n/** The double-sided diamond of `leaf`: base at the origin, tip at +y, in the xy plane (front face counter-clockwise seen from +z). */\nconst LEAF = (() => {\n const f = [0, 0, 0, -0.5, 0.4, 0, 0.5, 0.4, 0, 0, 1, 0]\n const positions: number[] = []\n const normals: number[] = []\n const uvs: number[] = []\n const indices: number[] = []\n for (const side of [1, -1]) {\n const base = positions.length / 3\n for (let i = 0; i < 4; i++) {\n positions.push(f[i * 3] as number, f[i * 3 + 1] as number, f[i * 3 + 2] as number)\n normals.push(0, 0, side)\n uvs.push((f[i * 3] as number) + 0.5, f[i * 3 + 1] as number)\n }\n if (side === 1) indices.push(base, base + 2, base + 1, base + 1, base + 2, base + 3)\n else indices.push(base, base + 1, base + 2, base + 1, base + 3, base + 2)\n }\n return { positions, normals, uvs, indices }\n})()\n\n/**\n * An api that turns what a builder draws into a `game-shapes` schema, with the arithmetic of the interactables mesh api: each primitive\n * under its item frame (`heading` = `M.ry`, at `M.x, 0, M.z`), placed by `compose(position, euler YXZ, scale)` over a unit template (a box\n * with its base at `y`, a cylinder centred as the api says, a sphere of radius `h / 2`, the double-sided leaf, a unit plane with the atlas\n * cell as its uv), parts as part nodes, lights as points (`data: { light: true, color, range, kind }`).\n *\n * ```ts\n * const recorder = createShapeRecorder()\n * props.builders.sofa_l(item, recorder.api)\n * const buffers = generate(recorder.schema(), { validate: false, key: false })\n * ```\n */\nexport function createShapeRecorder(options: ShapeRecorderOptions = {}): ShapeRecorder {\n const cyl = options.cylinderSegments ?? 12\n const hex = options.hexSegments ?? 6\n const [sphereW, sphereH] = options.sphereSegments ?? [12, 8]\n const root: ShapeNode[] = []\n const parts: RecordedPart[] = []\n const partChildren = new Map<string, ShapeNode[]>()\n let current = root\n // consecutive primitives of the same item frame share one frame group (one matrix, the same products)\n let lastFrame: { list: ShapeNode[]; group: GroupNode & { children: ShapeNode[] }; x: number; z: number; ry: number } | null = null\n\n const into = (M: PropItem | undefined): ShapeNode[] => {\n const x = M?.x ?? 0\n const z = M?.z ?? 0\n const ry = M?.ry ?? 0\n const last = lastFrame\n if (last && last.list === current && current[current.length - 1] === last.group && Object.is(last.x, x) && Object.is(last.z, z) && Object.is(last.ry, ry)) return last.group.children\n const group = { kind: 'group' as const, position: [x, 0, z] as Vec3, heading: ry, children: [] as ShapeNode[] }\n current.push(group)\n lastFrame = { list: current, group, x, z, ry }\n return group.children\n }\n\n const api: ShapeRecorderApi = {\n box(M, material, x, y, z, w, h, d, color, ry, rx, rz) {\n into(M).push({ kind: 'box', material, color, position: [x, y + h / 2, z], rotation: [rx, ry, rz], rotationOrder: 'YXZ', scale: [w, h, d] })\n },\n cylinder(M, material, x, y, z, r, h, color, rx, rz, shape?: CylinderShape) {\n const tilted = rx !== 0 || rz !== 0\n const cy = shape === 'sphere' ? y + h / 2 : tilted ? y : y + h / 2\n const sy = shape === 'sphere' ? h / 2 : h\n const place = { material, color, position: [x, cy, z] as Vec3, rotation: [rx, 0, rz] as Vec3, rotationOrder: 'YXZ' as const, scale: [r, sy, r] as Vec3 }\n const list = into(M)\n switch (shape) {\n case 'cyl6':\n list.push({ kind: 'cylinder', radiusTop: 1, radiusBottom: 1, height: 1, radialSegments: hex, ...place })\n break\n case 'cone':\n list.push({ kind: 'cylinder', radiusTop: 0, radiusBottom: 1, height: 1, radialSegments: cyl, ...place })\n break\n case 'sphere':\n list.push({ kind: 'sphere', radius: 1, widthSegments: sphereW, heightSegments: sphereH, ...place })\n break\n default:\n list.push({ kind: 'cylinder', radiusTop: 1, radiusBottom: 1, height: 1, radialSegments: cyl, ...place })\n }\n },\n leaf(M, x, y, z, w, h, color, ry, rx) {\n into(M).push({ kind: 'mesh', ...LEAF, material: 'plant', color, position: [x, y, z], rotation: [rx, ry, 0], rotationOrder: 'YXZ', scale: [w, h, 1] })\n },\n plane(M, material, x, y, z, w, h, color, ry, rx, rz, uv?: Uv) {\n into(M).push({\n kind: 'plane',\n width: 1,\n height: 1,\n material,\n color,\n position: [x, y, z],\n rotation: [rx, ry, rz],\n rotationOrder: 'YXZ',\n scale: [w, h, 1],\n ...(uv ? { uv: { u0: uv.u0, v0: uv.v0, u1: uv.u1, v1: uv.v1 } } : {}),\n })\n },\n light(M, x, y, z, color, range, kind) {\n into(M).push({ kind: 'point', position: [x, y, z], data: { light: true, color, range, kind } })\n },\n part(id, pivot, ry, draw) {\n let children = partChildren.get(id)\n if (!children) {\n children = []\n partChildren.set(id, children)\n parts.push({ id, pivot: [pivot[0], pivot[1], pivot[2]], ry })\n current.push({ kind: 'group', part: id, position: [pivot[0], pivot[1], pivot[2]], rotation: [0, ry, 0], children })\n }\n const outer = current\n current = children\n try {\n draw()\n } finally {\n current = outer\n }\n },\n }\n\n return {\n api,\n parts,\n schema: () => ({ version: 1, ...(options.name !== undefined ? { name: options.name } : {}), root: { kind: 'group', children: root } }),\n }\n}\n","import type * as ThreeNS from 'three'\nimport type { PropsApi } from '@adatechnology/game-props3d'\n\n/** The host's `THREE` namespace (or any object with the same classes). Always passed in, never imported at runtime. */\nexport type ThreeLib = typeof ThreeNS\nexport type { ThreeNS }\n\nexport type Vec3 = [number, number, number]\n\n/** Material keys the mesh api understands (same as `PropsApi` materials). */\nexport type MaterialKey = 'plain' | 'metal' | 'wood' | 'glass' | 'fabric' | 'plant' | 'water' | 'emissive' | 'atlas_picture' | 'atlas_floor'\n\nexport const MATERIAL_KEYS: readonly MaterialKey[] = ['plain', 'metal', 'wood', 'glass', 'fabric', 'plant', 'water', 'emissive', 'atlas_picture', 'atlas_floor']\n\nexport type Quality = 'high' | 'game'\n\n/**\n * What content packs draw through: the props primitives plus `part`.\n * `part(id, pivot, ry, draw)` runs `draw()` and routes every primitive drawn inside it into the movable part `id`.\n * `pivot` is object-local; the primitives drawn inside `draw` are in the part frame (relative to the pivot, turned by `ry`): the part's\n * group origin sits at `pivot`, turned by `ry` around +y.\n */\nexport interface HouseholdApi extends PropsApi {\n part(id: string, pivot: Vec3, ry: number, draw: () => void): void\n}\n\n/** Anything that can create a canvas (the global `document`, or a fake in tests). */\nexport interface CanvasFactory {\n createElement(tag: 'canvas'): HTMLCanvasElement\n}\n","import type { ColliderOptions, WorldCollider } from './types'\n\ninterface Box {\n ids: string[]\n object: string\n u0: number\n u1: number\n v0: number\n v1: number\n y0: number\n y1: number\n}\n\nconst volume = (b: Box): number => (b.u1 - b.u0) * (b.v1 - b.v0) * (b.y1 - b.y0)\nconst overlap = (a0: number, a1: number, b0: number, b1: number): number => Math.max(0, Math.min(a1, b1) - Math.max(a0, b0))\n\nfunction tryMerge(a: Box, b: Box, gap: number, slack: number): Box | null {\n if (a.u0 - gap > b.u1 || b.u0 - gap > a.u1) return null\n if (a.v0 - gap > b.v1 || b.v0 - gap > a.v1) return null\n if (a.y0 - gap > b.y1 || b.y0 - gap > a.y1) return null\n const u0 = Math.min(a.u0, b.u0)\n const u1 = Math.max(a.u1, b.u1)\n const v0 = Math.min(a.v0, b.v0)\n const v1 = Math.max(a.v1, b.v1)\n const y0 = Math.min(a.y0, b.y0)\n const y1 = Math.max(a.y1, b.y1)\n const hull = (u1 - u0) * (v1 - v0) * (y1 - y0)\n const common = overlap(a.u0, a.u1, b.u0, b.u1) * overlap(a.v0, a.v1, b.v0, b.v1) * overlap(a.y0, a.y1, b.y0, b.y1)\n const real = volume(a) + volume(b) - common\n if (hull > real * (1 + slack) + 1e-9) return null\n return { ids: [...a.ids, ...b.ids], object: a.object, u0, u1, v0, v1, y0, y1 }\n}\n\n/**\n * Union overlapping or adjacent colliders of the same object (same rotation) into fewer OBBs.\n * A merge happens only when the merged box adds at most `slack` extra volume over the real union, so coverage is never lost\n * and never grows noticeably. Colliders of different objects or rotations are left alone.\n */\nexport function mergeColliders(list: WorldCollider[], options: Pick<ColliderOptions, 'gap' | 'slack'> = {}): WorldCollider[] {\n const gap = options.gap ?? 0.02\n const slack = options.slack ?? 0.1\n const groups = new Map<string, WorldCollider[]>()\n for (const c of list) {\n const key = `${c.object}|${Math.round(c.ry * 1000)}`\n const g = groups.get(key)\n if (g) g.push(c)\n else groups.set(key, [c])\n }\n const out: WorldCollider[] = []\n for (const g of groups.values()) {\n const ry = g[0]?.ry ?? 0\n const cs = Math.cos(ry)\n const sn = Math.sin(ry)\n let boxes: Box[] = g.map((c) => {\n const u = c.cx * cs - c.cz * sn\n const v = c.cx * sn + c.cz * cs\n return { ids: [c.id], object: c.object, u0: u - c.hw, u1: u + c.hw, v0: v - c.hd, v1: v + c.hd, y0: c.y0, y1: c.y1 }\n })\n let changed = boxes.length > 1\n while (changed) {\n changed = false\n outer: for (let i = 0; i < boxes.length; i++) {\n for (let j = i + 1; j < boxes.length; j++) {\n const m = tryMerge(boxes[i] as Box, boxes[j] as Box, gap, slack)\n if (!m) continue\n boxes = boxes.filter((_, k) => k !== i && k !== j)\n boxes.push(m)\n changed = true\n break outer\n }\n }\n }\n for (const b of boxes) {\n const u = (b.u0 + b.u1) / 2\n const v = (b.v0 + b.v1) / 2\n out.push({\n id: b.ids.length === 1 ? (b.ids[0] as string) : `${b.ids[0]}+${b.ids.length - 1}`,\n object: b.object,\n cx: u * cs + v * sn,\n cz: -u * sn + v * cs,\n hw: (b.u1 - b.u0) / 2,\n hd: (b.v1 - b.v0) / 2,\n ry,\n y0: b.y0,\n y1: b.y1,\n })\n }\n }\n return out\n}\n","/** FNV-1a hash of a string to a 32-bit unsigned integer. */\nexport function hashString(text: string): number {\n let h = 2166136261\n for (let i = 0; i < text.length; i++) {\n h ^= text.charCodeAt(i)\n h = Math.imul(h, 16777619)\n }\n return h >>> 0\n}\n\n/** Deterministic random generator (mulberry32). Returns a function producing floats in [0, 1). Numbers and strings are accepted as seeds. */\nexport function rng(seed: number | string = 1): () => number {\n let a = (typeof seed === 'number' ? Math.floor(seed) >>> 0 : hashString(seed)) || 0x9e3779b9\n return () => {\n a = (a + 0x6d2b79f5) >>> 0\n let t = a\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","import { rng } from './rng'\nimport type { ItemCatalog, ItemStack, LootTables, SlotSpec, Vec3 } from './types'\n\n/** True when an item of `size` [w, h, d] fits in a slot of `max`; width and depth may be swapped (the item is turned). */\nexport function fitsSize(size: Vec3, max: Vec3): boolean {\n const eps = 1e-9\n if (size[1] > max[1] + eps) return false\n return (size[0] <= max[0] + eps && size[2] <= max[2] + eps) || (size[2] <= max[0] + eps && size[0] <= max[2] + eps)\n}\n\n/** Does the item type fit the slot. Types missing from the catalog are accepted (the host owns unknown items). */\nexport function fitsSlot(type: string, slot: SlotSpec, items?: ItemCatalog): boolean {\n const def = items?.[type]\n return def ? fitsSize(def.size, slot.max) : true\n}\n\n/**\n * Deterministic contents for a container: one entry per slot. A slot is filled with probability `fill`; the entry is a weighted pick\n * among the table entries whose item fits the slot. Same seed, same table and same slots always give the same result.\n */\nexport function generateLoot(\n tables: LootTables | undefined,\n tableId: string,\n slots: SlotSpec[],\n seed: number | string,\n items?: ItemCatalog,\n): Array<ItemStack | null> {\n const table = tables?.[tableId]\n const out: Array<ItemStack | null> = slots.map(() => null)\n if (!table) return out\n const rand = rng(seed)\n slots.forEach((slot, i) => {\n // Always draw the same number of random values per slot so a change in one slot does not shift the others.\n const roll = rand()\n const pick = rand()\n const q = rand()\n if (roll >= table.fill) return\n const candidates = table.entries.filter((e) => e.weight > 0 && fitsSlot(e.type, slot, items))\n const total = candidates.reduce((s, e) => s + e.weight, 0)\n if (total <= 0) return\n let r = pick * total\n let chosen = candidates[candidates.length - 1]\n for (const e of candidates) {\n r -= e.weight\n if (r < 0) {\n chosen = e\n break\n }\n }\n const [lo, hi] = chosen.qty ?? [1, 1]\n const def = items?.[chosen.type]\n const stackMax = def ? (def.stack ?? 1) : Number.POSITIVE_INFINITY\n const qty = Math.max(1, Math.min(stackMax, lo + Math.floor(q * (hi - lo + 1))))\n out[i] = { type: chosen.type, qty }\n })\n return out\n}\n","import type { InteractablesLocale, Strings } from './types'\n\n/** Engine-level prompts and messages. Content packs add their own keys through `WorldOptions.strings`. */\nexport const locales: Record<InteractablesLocale, Strings> = {\n en: {\n 'prompt.open': 'Open {name}',\n 'prompt.close': 'Close {name}',\n 'prompt.turn_on': 'Turn on {name}',\n 'prompt.turn_off': 'Turn off {name}',\n 'prompt.take': 'Take',\n 'prompt.sit': 'Sit',\n 'prompt.lie_down': 'Lie down',\n 'prompt.get_up': 'Get up',\n 'prompt.light': 'Light',\n 'prompt.change_channel': 'Change channel',\n 'prompt.read': 'Read',\n 'prompt.breaker_on': 'Switch on {circuit}',\n 'prompt.breaker_off': 'Switch off {circuit}',\n 'prompt.open_valve': 'Open valve',\n 'prompt.close_valve': 'Close valve',\n no_power: 'No power',\n no_gas: 'No gas',\n no_water: 'No water',\n locked: 'Locked',\n requires_open: 'Open it first',\n already_lit: 'Already lit',\n needs_ignition: 'Light it first',\n turn_on_first: 'Turn it on first',\n empty: 'Empty',\n full: 'Full',\n },\n 'pt-BR': {\n 'prompt.open': 'Abrir {name}',\n 'prompt.close': 'Fechar {name}',\n 'prompt.turn_on': 'Ligar {name}',\n 'prompt.turn_off': 'Desligar {name}',\n 'prompt.take': 'Pegar',\n 'prompt.sit': 'Sentar',\n 'prompt.lie_down': 'Deitar',\n 'prompt.get_up': 'Levantar',\n 'prompt.light': 'Acender',\n 'prompt.change_channel': 'Trocar de canal',\n 'prompt.read': 'Ler',\n 'prompt.breaker_on': 'Ligar {circuit}',\n 'prompt.breaker_off': 'Desligar {circuit}',\n 'prompt.open_valve': 'Abrir registro',\n 'prompt.close_valve': 'Fechar registro',\n no_power: 'Sem energia',\n no_gas: 'Sem gás',\n no_water: 'Sem água',\n locked: 'Trancado',\n requires_open: 'Abra primeiro',\n already_lit: 'Já está aceso',\n needs_ignition: 'Acenda primeiro',\n turn_on_first: 'Ligue primeiro',\n empty: 'Vazio',\n full: 'Cheio',\n },\n}\n\n/** Pick a built-in locale for an arbitrary tag (`pt`, `pt-br`, `en-GB`...). Unknown tags give `en`. */\nexport function pickLocale(locale: string | undefined): InteractablesLocale {\n if (!locale) return 'en'\n const l = locale.toLowerCase()\n if (l === 'pt-br' || l === 'pt') return 'pt-BR'\n return 'en'\n}\n\n/**\n * Merged table for a locale: built-in `en`, then the built-in table of `locale`, then the extra strings for `en` and for `locale`.\n * Lookup therefore falls back to English, and the caller never gets undefined for engine keys.\n */\nexport function resolveStrings(locale?: string, extra?: Partial<Record<InteractablesLocale, Strings>>): Strings {\n const loc = pickLocale(locale)\n return { ...locales.en, ...locales[loc], ...(extra?.en ?? {}), ...(loc === 'en' ? {} : (extra?.[loc] ?? {})) }\n}\n\n/** Replace `{name}` placeholders. Unknown placeholders are left untouched. */\nexport function format(text: string, vars?: Record<string, string | number>): string {\n if (!vars) return text\n return text.replace(/\\{(\\w+)\\}/g, (m, k: string) => (k in vars ? String(vars[k]) : m))\n}\n","import type { Motion, Transform, Vec3 } from './types'\n\nexport interface Pose {\n /** Translation relative to the pivot. */\n position: Vec3\n /** Rotation around `axis` (in the part frame) by `angle` radians. */\n rotation: { axis: Vec3; angle: number }\n scale: Vec3\n}\n\nconst DEFAULT_AXIS: Vec3 = [0, 1, 0]\n\n/**\n * Pose of a part at animation value `t` (0..1), relative to its pivot, so any renderer can apply it:\n * `position`, then `rotation` around the pivot, then `scale` along the axis.\n */\nexport function poseOf(motion: Motion, t: number): Pose {\n const tt = Math.max(0, Math.min(1, t))\n const axis = motion.axis ?? DEFAULT_AXIS\n const pose: Pose = { position: [0, 0, 0], rotation: { axis: [axis[0], axis[1], axis[2]], angle: 0 }, scale: [1, 1, 1] }\n switch (motion.kind) {\n case 'hinge':\n case 'rocker':\n case 'knob':\n pose.rotation.angle = (motion.angle ?? Math.PI / 2) * tt\n break\n case 'slide': {\n const d = (motion.distance ?? 0.4) * tt\n pose.position = [axis[0] * d, axis[1] * d, axis[2] * d]\n break\n }\n case 'scale': {\n const k = 1 + ((motion.scale ?? 0.2) - 1) * tt\n pose.scale = [axis[0] !== 0 ? k : 1, axis[1] !== 0 ? k : 1, axis[2] !== 0 ? k : 1]\n break\n }\n }\n return pose\n}\n\n/** Rotate a vector around +y by `ry` radians (same convention as three.js `rotation.y`). */\nexport function rotY(v: Vec3, ry: number): Vec3 {\n const c = Math.cos(ry)\n const s = Math.sin(ry)\n return [c * v[0] + s * v[2], v[1], -s * v[0] + c * v[2]]\n}\n\n/** Object-local point to world space. */\nexport function localToWorld(transform: Transform, v: Vec3): Vec3 {\n const r = rotY(v, transform.ry)\n return [transform.x + r[0], transform.y + r[1], transform.z + r[2]]\n}\n\n/** World point to object-local space. */\nexport function worldToLocal(transform: Transform, v: Vec3): Vec3 {\n return rotY([v[0] - transform.x, v[1] - transform.y, v[2] - transform.z], -transform.ry)\n}\n\n/** Rodrigues rotation of `v` around a unit-length-normalised `axis` by `angle`. */\nexport function rotateAxis(v: Vec3, axis: Vec3, angle: number): Vec3 {\n const len = Math.hypot(axis[0], axis[1], axis[2]) || 1\n const k: Vec3 = [axis[0] / len, axis[1] / len, axis[2] / len]\n const c = Math.cos(angle)\n const s = Math.sin(angle)\n const dot = k[0] * v[0] + k[1] * v[1] + k[2] * v[2]\n const cross: Vec3 = [k[1] * v[2] - k[2] * v[1], k[2] * v[0] - k[0] * v[2], k[0] * v[1] - k[1] * v[0]]\n return [v[0] * c + cross[0] * s + k[0] * dot * (1 - c), v[1] * c + cross[1] * s + k[1] * dot * (1 - c), v[2] * c + cross[2] * s + k[2] * dot * (1 - c)]\n}\n\nexport function clamp(v: number, lo: number, hi: number): number {\n return v < lo ? lo : v > hi ? hi : v\n}\n\nexport function dist3(a: Vec3, b: Vec3): number {\n return Math.hypot(a[0] - b[0], a[1] - b[1], a[2] - b[2])\n}\n","import { mergeColliders } from './colliders'\nimport { fitsSlot, generateLoot } from './loot'\nimport { format, resolveStrings } from './locale'\nimport { clamp, dist3, localToWorld, rotateAxis, rotY, worldToLocal } from './math'\nimport type {\n ActiveEmitter,\n BurnerSpec,\n ColliderOptions,\n DeviceSpec,\n FixtureSpec,\n FuelSourceSpec,\n Interactable,\n InteractionSpec,\n ItemStack,\n ObjectSpec,\n PartSpec,\n SeatResult,\n SoftSpec,\n TankSpec,\n TargetOptions,\n Transform,\n Vec3,\n World,\n WorldCollider,\n WorldEvent,\n WorldObject,\n WorldOptions,\n WorldState,\n} from './types'\n\n// ---------------------------------------------------------------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------------------------------------------------------------\n\nconst DEFAULT_ACTOR = 'player'\nconst DEFAULT_RADIUS = 1.2\nconst AUTO_RANGE = 2\nconst SMOKE_TAIL = 20\nconst FLUSH_SECONDS = 2\nconst ACTOR_HEIGHT = 1.7\nconst PUSH_GAIN = 1.2\nconst SWEEP_STEPS = 24\nconst FLOW_DEFAULTS: Record<string, number> = { tap: 6, shower: 8, utility_sink: 6, flush: 0, hose: 10, filter: 1 }\n\n// ---------------------------------------------------------------------------------------------------------------------------------\n// Internal state\n// ---------------------------------------------------------------------------------------------------------------------------------\n\ninterface PartState {\n spec: PartSpec\n t: number\n target: number\n maxT: number\n}\n\ninterface DeviceState {\n spec: DeviceSpec\n on: boolean\n channel: number\n}\n\ninterface FuelState {\n spec: FuelSourceSpec\n level: number\n}\n\ninterface BurnerState {\n spec: BurnerSpec\n on: boolean\n lit: boolean\n /** Seconds of smoke left after a solid-fuel fire went out. */\n smoke: number\n}\n\ninterface TankState {\n spec: TankSpec\n level: number\n}\n\ninterface FixtureState {\n spec: FixtureSpec\n on: boolean\n flowing: boolean\n /** Delivered / wanted in the last step, 0..1. */\n frac: number\n /** Seconds left of a flush. */\n timer: number\n}\n\ninterface SoftState {\n spec: SoftSpec\n ax: number\n az: number\n vax: number\n vaz: number\n phase: number\n}\n\ninterface DamageState {\n max: number\n hp: number\n}\n\ninterface ObjState {\n id: string\n public: WorldObject\n spec: ObjectSpec\n parts: Map<string, PartState>\n contents: Map<string, Array<ItemStack | null>>\n devices: Map<string, DeviceState>\n fuels: Map<string, FuelState>\n burners: Map<string, BurnerState>\n tanks: Map<string, TankState>\n fixtures: Map<string, FixtureState>\n soft: Map<string, SoftState>\n hp: Map<string, DamageState>\n interactions: InteractionSpec[]\n}\n\nfunction cloneStack(s: ItemStack | null): ItemStack | null {\n return s ? { type: s.type, qty: s.qty, ...(s.data ? { data: JSON.parse(JSON.stringify(s.data)) as Record<string, unknown> } : {}) } : null\n}\n\nfunction humanize(id: string): string {\n const s = id.replace(/[_-]+/g, ' ').trim()\n return s.charAt(0).toUpperCase() + s.slice(1)\n}\n\n// ---------------------------------------------------------------------------------------------------------------------------------\n// World\n// ---------------------------------------------------------------------------------------------------------------------------------\n\nexport function createWorld(options: WorldOptions = {}): World {\n const seed = options.seed ?? 1\n const objs = new Map<string, ObjState>()\n const breakers = new Map<string, boolean>()\n const seats = new Map<string, { object: string; seat: string }>()\n const plugs = new Map<string, string | null>()\n let locale = options.locale ?? 'en'\n let table = resolveStrings(locale, options.strings)\n let grid = options.grid ?? true\n let rain = options.rain ?? 0\n let wind: [number, number] = options.wind ? [options.wind[0], options.wind[1]] : [0, 0]\n let defaultTank: string | null = null\n let defaultTankExplicit = false\n let time = 0\n\n // ------------------------------------------------------------------ text & events\n function t(key: string, vars?: Record<string, string | number>): string {\n return format(table[key] ?? key, vars)\n }\n\n function emit(e: WorldEvent): void {\n options.onEvent?.(e)\n }\n\n function message(key: string): void {\n emit({ type: 'message', key, text: t(key) })\n }\n\n // ------------------------------------------------------------------ lookup helpers\n function gid(o: ObjState, local: string): string {\n return `${o.id}:${local}`\n }\n\n /** Find the first entity matching a world-wide id `objectId:localId`. */\n function findGlobal<T>(id: string, pick: (o: ObjState, local: string) => T | undefined): { o: ObjState; value: T; local: string } | null {\n for (const o of objs.values()) {\n if (!id.startsWith(o.id + ':')) continue\n const local = id.slice(o.id.length + 1)\n const value = pick(o, local)\n if (value !== undefined) return { o, value, local }\n }\n return null\n }\n\n function objectOrThrow(id: string): ObjState {\n const o = objs.get(id)\n if (!o) throw new Error(`Unknown object \"${id}\"`)\n return o\n }\n\n function originOf(o: ObjState): Vec3 {\n const tr = o.public.transform\n return [tr.x, tr.y, tr.z]\n }\n\n function toWorld(o: ObjState, v: Vec3): Vec3 {\n return localToWorld(o.public.transform, v)\n }\n\n // ------------------------------------------------------------------ parts\n function openThreshold(p: PartState): number {\n return Math.min(0.5, p.maxT * 0.9)\n }\n\n function isOpen(o: ObjState, partId: string | undefined): boolean {\n if (!partId) return true\n const p = o.parts.get(partId)\n if (!p) return true\n return p.maxT >= 0.1 && p.t >= openThreshold(p)\n }\n\n function targetIsOpen(p: PartState): boolean {\n return p.maxT >= 0.1 && p.target >= openThreshold(p)\n }\n\n function writeT(o: ObjState, p: PartState, v: number): void {\n p.t = v\n o.public.parts[p.spec.id] = v\n }\n\n function gated(o: ObjState, p: PartState): boolean {\n return !!p.spec.requires && o.parts.has(p.spec.requires) && !isOpen(o, p.spec.requires)\n }\n\n function setOnePart(o: ObjState, p: PartState, target: number, instant: boolean): void {\n const next = clamp(target, 0, p.maxT)\n const changed = next !== p.target\n p.target = next\n if (instant) writeT(o, p, next)\n if (changed || instant) emit({ type: 'part', object: o.id, part: p.spec.id, t: p.t, target: p.target })\n }\n\n function setPartTarget(o: ObjState, partId: string, target: number, instant = false): void {\n const p = o.parts.get(partId)\n if (!p) return\n const group = p.spec.group\n const members = group ? [...o.parts.values()].filter((q) => q.spec.group === group) : [p]\n for (const q of members) {\n setOne(o, q, target, instant)\n }\n }\n\n function setOne(o: ObjState, p: PartState, target: number, instant: boolean): void {\n setOnePart(o, p, target, instant)\n // a part that other parts require closes them with it (the drawer behind a door closes with the door)\n if (!targetIsOpen(p)) {\n for (const q of o.parts.values()) {\n if (q.spec.requires === p.spec.id && q.target > 0) setPartTarget(o, q.spec.id, 0, instant)\n }\n }\n }\n\n /** Frame of a part right now: rotation (axis in object-local, angle) and translation, both about its pivot. */\n function partFrame(o: ObjState, partId: string | undefined): { pivot: Vec3; axis: Vec3; angle: number; offset: Vec3 } | null {\n if (!partId) return null\n const p = o.parts.get(partId)\n if (!p) return null\n const m = p.spec.motion\n const axis = rotY(m.axis ?? [0, 1, 0], p.spec.ry ?? 0)\n const frame = { pivot: p.spec.pivot, axis, angle: 0, offset: [0, 0, 0] as Vec3 }\n if (m.kind === 'slide') {\n const d = (m.distance ?? 0.4) * p.t\n frame.offset = [axis[0] * d, axis[1] * d, axis[2] * d]\n } else if (m.kind !== 'scale') {\n frame.angle = (m.angle ?? Math.PI / 2) * p.t\n }\n return frame\n }\n\n function applyPart(o: ObjState, partId: string | undefined, local: Vec3): Vec3 {\n const f = partFrame(o, partId)\n if (!f) return local\n const rel: Vec3 = [local[0] - f.pivot[0], local[1] - f.pivot[1], local[2] - f.pivot[2]]\n const r = f.angle !== 0 ? rotateAxis(rel, f.axis, f.angle) : rel\n return [f.pivot[0] + r[0] + f.offset[0], f.pivot[1] + r[1] + f.offset[1], f.pivot[2] + r[2] + f.offset[2]]\n }\n\n /** Moves the full closed-pose slab of a part and returns the largest t whose pose keeps every sampled point clear. */\n function fitBounds(o: ObjState, p: PartState, b: NonNullable<PartSpec['bounds']>, blockedAt: (x: number, z: number) => boolean): number {\n const STEP = 0.05\n const ANGLES = 48\n const m = p.spec.motion\n const axis = rotY(m.axis ?? [0, 1, 0], p.spec.ry ?? 0)\n const lo: Vec3 = [Math.min(b[0], b[3]), Math.min(b[1], b[4]), Math.min(b[2], b[5])]\n const hi: Vec3 = [Math.max(b[0], b[3]), Math.max(b[1], b[4]), Math.max(b[2], b[5])]\n const axisPoints = (i: number): number[] => {\n const n = Math.max(1, Math.ceil((hi[i] - lo[i]) / STEP))\n return Array.from({ length: n + 1 }, (_, k) => lo[i] + ((hi[i] - lo[i]) * k) / n)\n }\n const xs = axisPoints(0)\n const ys = axisPoints(1)\n const zs = axisPoints(2)\n // `blocked` only sees x/z, so only the floor footprint matters. About a vertical axis (and for slides) y never changes\n // the footprint, so the x/z outline is enough; otherwise the footprint boundary is the projection of the box edges.\n const flat = m.kind === 'slide' || Math.abs(axis[1]) > 0.999\n const seen = new Set<string>()\n const pts: Vec3[] = []\n const add = (x: number, y: number, z: number): void => {\n const key = `${x.toFixed(5)},${y.toFixed(5)},${z.toFixed(5)}`\n if (seen.has(key)) return\n seen.add(key)\n pts.push([x, y, z])\n }\n if (flat) {\n for (const x of xs) for (const z of [lo[2], hi[2]]) add(x, lo[1], z)\n for (const z of zs) for (const x of [lo[0], hi[0]]) add(x, lo[1], z)\n } else {\n for (const x of xs) for (const y of [lo[1], hi[1]]) for (const z of [lo[2], hi[2]]) add(x, y, z)\n for (const y of ys) for (const x of [lo[0], hi[0]]) for (const z of [lo[2], hi[2]]) add(x, y, z)\n for (const z of zs) for (const x of [lo[0], hi[0]]) for (const y of [lo[1], hi[1]]) add(x, y, z)\n }\n const tr = o.public.transform\n const pivot = p.spec.pivot\n for (let i = 1; i <= ANGLES; i++) {\n const tt = i / ANGLES\n const angle = m.kind === 'slide' ? 0 : (m.angle ?? Math.PI / 2) * tt\n const d = m.kind === 'slide' ? (m.distance ?? 0.4) * tt : 0\n for (const [x, y, z] of pts) {\n let q: Vec3 = [x, y, z]\n if (angle !== 0) {\n const r = rotateAxis([x - pivot[0], y - pivot[1], z - pivot[2]], axis, angle)\n q = [pivot[0] + r[0], pivot[1] + r[1], pivot[2] + r[2]]\n }\n const w = localToWorld(tr, [q[0] + axis[0] * d, 0, q[2] + axis[2] * d])\n if (blockedAt(w[0], w[2])) return (i - 1) / ANGLES\n }\n }\n return 1\n }\n\n function fitSweep(objectId: string, partId: string, blocked: (x: number, z: number) => boolean): number {\n const o = objectOrThrow(objectId)\n const p = o.parts.get(partId)\n if (!p) throw new Error(`Unknown part \"${partId}\" on \"${objectId}\"`)\n const sweep = p.spec.sweep\n let result = 1\n const bounds = p.spec.bounds\n const mk = p.spec.motion.kind\n if (bounds && (mk === 'hinge' || mk === 'rocker' || mk === 'knob' || mk === 'slide')) {\n result = fitBounds(o, p, bounds, blocked)\n } else if (sweep) {\n const x0 = Math.min(sweep[0], sweep[2])\n const x1 = Math.max(sweep[0], sweep[2])\n const z0 = Math.min(sweep[1], sweep[3])\n const z1 = Math.max(sweep[1], sweep[3])\n const tr = o.public.transform\n const hit = (lx: number, lz: number): boolean => {\n const w = localToWorld(tr, [lx, 0, lz])\n return blocked(w[0], w[2])\n }\n const m = p.spec.motion\n const axis = rotY(m.axis ?? [0, 1, 0], p.spec.ry ?? 0)\n if (m.kind === 'hinge' || m.kind === 'rocker' || m.kind === 'knob') {\n if (Math.abs(axis[1]) >= 0.5) {\n const angle = m.angle ?? Math.PI / 2\n const total = -angle * Math.sign(axis[1])\n const [px, , pz] = p.spec.pivot\n const cx = (x0 + x1) / 2\n const cz = (z0 + z1) / 2\n const w = x1 - x0\n const d = z1 - z0\n const radius = Math.abs(angle) > 1.7 ? Math.min(w, d) : Math.max(w, d)\n const tol = 0.05\n const inRect = (x: number, z: number) => x >= x0 - tol && x <= x1 + tol && z >= z0 - tol && z <= z1 + tol\n const centered = Math.hypot(cx - px, cz - pz) < 1e-6\n const mid = Math.atan2(cz - pz, cx - px)\n for (let i = 1; i <= SWEEP_STEPS && result === 1; i++) {\n const tt = i / SWEEP_STEPS\n let isHit = false\n if (centered) {\n for (let x = x0; x <= x1 + 1e-9 && !isHit; x += 0.1) for (let z = z0; z <= z1 + 1e-9 && !isHit; z += 0.1) isHit = hit(x, z)\n } else {\n const phi = mid - total / 2 + total * tt\n const steps = Math.max(2, Math.ceil(radius / 0.1))\n for (let k = 1; k <= steps && !isHit; k++) {\n const r = (radius * k) / steps\n const x = px + Math.cos(phi) * r\n const z = pz + Math.sin(phi) * r\n if (inRect(x, z)) isHit = hit(x, z)\n }\n }\n if (isHit) result = (i - 1) / SWEEP_STEPS\n }\n }\n } else if (m.kind === 'slide') {\n const len = Math.hypot(axis[0], axis[2])\n if (len > 1e-6) {\n const dx = axis[0] / len\n const dz = axis[2] / len\n const proj = [x0 * dx + z0 * dz, x1 * dx + z0 * dz, x0 * dx + z1 * dz, x1 * dx + z1 * dz]\n const pmin = Math.min(...proj)\n const pmax = Math.max(...proj)\n const nx = Math.max(1, Math.ceil((x1 - x0) / 0.1))\n const nz = Math.max(1, Math.ceil((z1 - z0) / 0.1))\n for (let i = 1; i <= SWEEP_STEPS && result === 1; i++) {\n const limit = pmin + (pmax - pmin) * (i / SWEEP_STEPS)\n let isHit = false\n for (let a = 0; a <= nx && !isHit; a++) {\n for (let b = 0; b <= nz && !isHit; b++) {\n const x = x0 + ((x1 - x0) * a) / nx\n const z = z0 + ((z1 - z0) * b) / nz\n if (x * dx + z * dz <= limit + 1e-9 && hit(x, z)) isHit = true\n }\n }\n if (isHit) result = (i - 1) / SWEEP_STEPS\n }\n }\n }\n }\n p.maxT = result\n if (p.target > result) p.target = result\n if (p.t > result) writeT(o, p, result)\n return result\n }\n\n // ------------------------------------------------------------------ containers\n function containerOf(o: ObjState, id: string) {\n return o.spec.containers?.find((c) => c.id === id)\n }\n\n function accessible(objectId: string, containerId: string): boolean {\n const o = objs.get(objectId)\n if (!o) return false\n const c = containerOf(o, containerId)\n if (!c) return false\n return isOpen(o, c.access)\n }\n\n function contents(objectId: string, containerId: string): Array<ItemStack | null> {\n return (objs.get(objectId)?.contents.get(containerId) ?? []).map(cloneStack)\n }\n\n function take(objectId: string, containerId: string, slot: number, actor: string = DEFAULT_ACTOR): ItemStack | null {\n const o = objs.get(objectId)\n if (!o) return null\n const list = o.contents.get(containerId)\n if (!list || !accessible(objectId, containerId)) return null\n const stack = list[slot]\n if (!stack) return null\n const def = options.items?.[stack.type]\n if (def && def.pickable === false) return null\n list[slot] = null\n emit({ type: 'pickup', object: objectId, container: containerId, slot, stack: cloneStack(stack) as ItemStack, actor })\n return stack\n }\n\n function put(objectId: string, containerId: string, stack: ItemStack): number | null {\n const o = objs.get(objectId)\n if (!o) return null\n const c = containerOf(o, containerId)\n const list = o.contents.get(containerId)\n if (!c || !list || !accessible(objectId, containerId)) return null\n const maxStack = options.items?.[stack.type]?.stack ?? 1\n for (let i = 0; i < list.length; i++) {\n const cur = list[i]\n if (cur && cur.type === stack.type && !cur.data && !stack.data && cur.qty + stack.qty <= maxStack) {\n cur.qty += stack.qty\n return i\n }\n }\n for (let i = 0; i < list.length; i++) {\n if (!list[i] && fitsSlot(stack.type, c.slots[i], options.items)) {\n list[i] = cloneStack(stack)\n return i\n }\n }\n message('full')\n return null\n }\n\n function slotPosition(objectId: string, containerId: string, slot: number): Vec3 {\n const o = objectOrThrow(objectId)\n const c = containerOf(o, containerId)\n const s = c?.slots[slot]\n if (!c || !s) throw new Error(`Unknown slot ${slot} of \"${containerId}\" on \"${objectId}\"`)\n return toWorld(o, applyPart(o, s.part, s.pos))\n }\n\n // ------------------------------------------------------------------ power\n function rebuildPlugs(): void {\n plugs.clear()\n const sockets: Array<{ id: string; pos: Vec3; circuit?: string; local: string }> = []\n for (const o of objs.values()) {\n for (const s of o.spec.sockets ?? []) sockets.push({ id: gid(o, s.id), local: s.id, pos: toWorld(o, s.pos), circuit: s.circuit })\n }\n const used = new Set<string>()\n const autos: Array<{ o: ObjState; d: DeviceState }> = []\n for (const o of objs.values()) {\n for (const d of o.devices.values()) {\n const key = gid(o, d.spec.id)\n const want = d.spec.socket\n if (want === undefined || d.spec.circuit) {\n plugs.set(key, null)\n } else if (want === 'auto') {\n plugs.set(key, null)\n autos.push({ o, d })\n } else {\n const s = sockets.find((x) => x.id === want) ?? sockets.find((x) => x.id === gid(o, want)) ?? sockets.find((x) => x.local === want)\n plugs.set(key, s ? s.id : null)\n if (s) used.add(s.id)\n }\n }\n }\n for (const { o, d } of autos) {\n const from = d.spec.plug ? toWorld(o, d.spec.plug) : originOf(o)\n let best: (typeof sockets)[number] | null = null\n let bestD = AUTO_RANGE\n for (const s of sockets) {\n if (used.has(s.id)) continue\n const dd = dist3(from, s.pos)\n if (dd <= bestD) {\n best = s\n bestD = dd\n }\n }\n if (best) {\n used.add(best.id)\n plugs.set(gid(o, d.spec.id), best.id)\n }\n }\n }\n\n function breaker(circuit: string): boolean {\n return breakers.get(circuit) ?? true\n }\n\n function devicePowered(o: ObjState, d: DeviceState): boolean {\n if (d.spec.electric === false) return true\n if (!grid) return false\n if (d.spec.circuit) return breaker(d.spec.circuit)\n if (d.spec.socket === undefined) return false\n const sid = plugs.get(gid(o, d.spec.id))\n if (!sid) return false\n const found = findGlobal(sid, (oo, local) => oo.spec.sockets?.find((s) => s.id === local))\n if (!found) return false\n return found.value.circuit ? breaker(found.value.circuit) : true\n }\n\n function getDevice(objectId: string, deviceId: string): { o: ObjState; d: DeviceState } | null {\n const o = objs.get(objectId)\n const d = o?.devices.get(deviceId)\n return o && d ? { o, d } : null\n }\n\n function powerSnapshot(): Map<string, boolean> {\n const m = new Map<string, boolean>()\n for (const o of objs.values()) for (const d of o.devices.values()) m.set(gid(o, d.spec.id), d.on && devicePowered(o, d))\n return m\n }\n\n function emitPowerChanges(before: Map<string, boolean>): void {\n for (const o of objs.values()) {\n for (const d of o.devices.values()) {\n const key = gid(o, d.spec.id)\n const now = d.on && devicePowered(o, d)\n if (before.get(key) !== now && d.on) emit({ type: 'device', object: o.id, device: d.spec.id, on: d.on, powered: devicePowered(o, d), channel: d.spec.kind === 'screen' ? d.channel : undefined })\n }\n }\n }\n\n function setDeviceOn(o: ObjState, d: DeviceState, on: boolean): void {\n d.on = on\n if (d.spec.part) setPartTarget(o, d.spec.part, on ? 1 : 0)\n const powered = devicePowered(o, d)\n emit({ type: 'device', object: o.id, device: d.spec.id, on, powered, channel: d.spec.kind === 'screen' ? d.channel : undefined })\n if (on && !powered) message('no_power')\n }\n\n function setDevice(objectId: string, deviceId: string, on: boolean): boolean {\n const f = getDevice(objectId, deviceId)\n if (!f) return false\n if (f.d.on !== on) setDeviceOn(f.o, f.d, on)\n return true\n }\n\n function setFixture(objectId: string, fixtureId: string, on: boolean): boolean {\n const fx = objs.get(objectId)?.fixtures.get(fixtureId)\n if (!fx) return false\n if (fx.on !== on) toggleFixture(objs.get(objectId) as ObjState, fixtureId)\n return fx.on === on\n }\n\n function setBurner(objectId: string, burnerId: string, on: boolean): boolean {\n const o = objs.get(objectId)\n const b = o?.burners.get(burnerId)\n if (!o || !b) return false\n const isOn = b.on || b.lit\n if (on === isOn) return true\n if (!on) return toggleBurner(o, burnerId)\n return b.spec.needsIgnition ? ignite(o, burnerId) : toggleBurner(o, burnerId)\n }\n\n function resolveDevice(o: ObjState, id: string): { o: ObjState; d: DeviceState } | null {\n if (id.includes(':')) {\n const f = findGlobal(id, (oo, local) => oo.devices.get(local))\n return f ? { o: f.o, d: f.value } : null\n }\n const local = o.devices.get(id)\n if (local) return { o, d: local }\n for (const oo of objs.values()) {\n const d = oo.devices.get(id)\n if (d) return { o: oo, d }\n }\n return null\n }\n\n function switchIsOn(o: ObjState, spec: { part?: string; controls: string[] }): boolean {\n if (spec.part && o.parts.has(spec.part)) return targetIsOpen(o.parts.get(spec.part) as PartState)\n return spec.controls.some((id) => resolveDevice(o, id)?.d.on)\n }\n\n function toggleSwitch(o: ObjState, id: string): boolean {\n const sw = o.spec.switches?.find((s) => s.id === id)\n if (!sw) return false\n const on = !switchIsOn(o, sw)\n if (sw.part) setPartTarget(o, sw.part, on ? 1 : 0)\n let unpowered = false\n for (const ref of sw.controls) {\n const r = resolveDevice(o, ref)\n if (!r) continue\n r.d.on = on\n if (r.d.spec.part) setPartTarget(r.o, r.d.spec.part, on ? 1 : 0)\n const powered = devicePowered(r.o, r.d)\n if (on && !powered) unpowered = true\n emit({ type: 'device', object: r.o.id, device: r.d.spec.id, on, powered })\n }\n emit({ type: 'switch', object: o.id, switch: sw.id, on })\n if (unpowered) message('no_power')\n return true\n }\n\n // ------------------------------------------------------------------ fuel\n function resolveFuel(o: ObjState, b: BurnerState): { o: ObjState; f: FuelState } | null {\n const want = b.spec.fuel\n if (want === 'auto') {\n const from = toWorld(o, b.spec.pos)\n let best: { o: ObjState; f: FuelState } | null = null\n let bestD = AUTO_RANGE\n for (const oo of objs.values()) {\n for (const f of oo.fuels.values()) {\n if (f.spec.kind !== 'gas') continue\n const d = dist3(from, originOf(oo))\n if (d <= bestD) {\n best = { o: oo, f }\n bestD = d\n }\n }\n }\n return best\n }\n if (want.includes(':')) {\n const f = findGlobal(want, (oo, local) => oo.fuels.get(local))\n return f ? { o: f.o, f: f.value } : null\n }\n const local = o.fuels.get(want)\n return local ? { o, f: local } : null\n }\n\n function valveOpen(o: ObjState, f: FuelState): boolean {\n const v = f.spec.valve\n if (!v || !o.parts.has(v)) return true\n return targetIsOpen(o.parts.get(v) as PartState)\n }\n\n function canBurn(b: BurnerState, ref: { o: ObjState; f: FuelState } | null): boolean {\n return !!ref && ref.f.level > 0 && valveOpen(ref.o, ref.f)\n }\n\n function isSolid(ref: { f: FuelState } | null): boolean {\n return !!ref && ref.f.spec.kind !== 'gas'\n }\n\n function emitBurner(o: ObjState, b: BurnerState): void {\n emit({ type: 'burner', object: o.id, burner: b.spec.id, on: b.on, lit: b.lit })\n }\n\n function extinguish(o: ObjState, b: BurnerState, turnOff: boolean): void {\n const ref = resolveFuel(o, b)\n const wasLit = b.lit\n b.lit = false\n if (turnOff) b.on = false\n if (b.spec.part) setPartTarget(o, b.spec.part, b.on ? 1 : 0)\n if (wasLit && isSolid(ref)) b.smoke = SMOKE_TAIL\n emitBurner(o, b)\n }\n\n function toggleBurner(o: ObjState, id: string): boolean {\n const b = o.burners.get(id)\n if (!b) return false\n if (b.on || b.lit) {\n extinguish(o, b, true)\n return true\n }\n if (b.spec.needsIgnition) return false\n const ref = resolveFuel(o, b)\n b.on = true\n b.lit = canBurn(b, ref)\n if (b.lit) b.smoke = 0\n if (b.spec.part) setPartTarget(o, b.spec.part, 1)\n emitBurner(o, b)\n if (!b.lit) message(ref && ref.f.level <= 0 ? 'empty' : 'no_gas')\n return true\n }\n\n function ignite(o: ObjState, id: string): boolean {\n const b = o.burners.get(id)\n if (!b || b.lit) return false\n const ref = resolveFuel(o, b)\n if (!canBurn(b, ref)) {\n message(ref && ref.f.level <= 0 ? 'empty' : 'no_gas')\n return false\n }\n b.on = true\n b.lit = true\n b.smoke = 0\n if (b.spec.part) setPartTarget(o, b.spec.part, 1)\n emitBurner(o, b)\n return true\n }\n\n function toggleFuel(o: ObjState, id: string): boolean {\n const f = o.fuels.get(id)\n if (!f || !f.spec.valve || !o.parts.has(f.spec.valve)) return false\n const p = o.parts.get(f.spec.valve) as PartState\n setPartTarget(o, f.spec.valve, targetIsOpen(p) ? 0 : 1)\n return true\n }\n\n // ------------------------------------------------------------------ water\n function resolveTank(o: ObjState, id: string | undefined): { o: ObjState; t: TankState } | null {\n const want = id === undefined || id === 'auto' ? defaultTank : id\n if (!want) return null\n const f = findGlobal(want, (oo, local) => oo.tanks.get(local))\n if (f) return { o: f.o, t: f.value }\n const local = o.tanks.get(want)\n return local ? { o, t: local } : null\n }\n\n function drawWater(o: ObjState, fx: FixtureState, liters: number): number {\n if (liters <= 0) return 0\n if (options.waterSupply) return Math.max(0, Math.min(liters, options.waterSupply.draw(gid(o, fx.spec.id), liters)))\n const tank = resolveTank(o, fx.spec.tank)\n if (!tank) return 0\n const got = Math.min(liters, tank.t.level)\n tank.t.level -= got\n return got\n }\n\n function emitFixture(o: ObjState, fx: FixtureState): void {\n emit({ type: 'fixture', object: o.id, fixture: fx.spec.id, on: fx.on, flowing: fx.flowing })\n }\n\n function flowOf(fx: FixtureState): number {\n return fx.spec.flow ?? FLOW_DEFAULTS[fx.spec.kind] ?? 6\n }\n\n function stepFixture(o: ObjState, fx: FixtureState, dt: number): void {\n if (!fx.on) return\n if (fx.spec.kind === 'flush') {\n fx.timer -= dt\n if (fx.timer <= 0) {\n fx.on = false\n fx.flowing = false\n fx.frac = 0\n if (fx.spec.part) setPartTarget(o, fx.spec.part, 0)\n emitFixture(o, fx)\n }\n return\n }\n if (dt <= 0) return\n const wanted = (flowOf(fx) / 60) * dt\n const got = drawWater(o, fx, wanted)\n const flowing = got > 1e-9\n fx.frac = wanted > 0 ? Math.min(1, got / wanted) : 0\n if (flowing !== fx.flowing) {\n fx.flowing = flowing\n emitFixture(o, fx)\n }\n }\n\n function toggleFixture(o: ObjState, id: string): boolean {\n const fx = o.fixtures.get(id)\n if (!fx) return false\n if (fx.on) {\n fx.on = false\n fx.flowing = false\n fx.frac = 0\n if (fx.spec.part) setPartTarget(o, fx.spec.part, 0)\n emitFixture(o, fx)\n return true\n }\n fx.on = true\n if (fx.spec.part) setPartTarget(o, fx.spec.part, 1)\n if (fx.spec.kind === 'flush') {\n const volume = fx.spec.volume ?? 6\n const got = drawWater(o, fx, volume)\n fx.flowing = got > 1e-9\n fx.frac = fx.flowing ? 1 : 0\n fx.timer = FLUSH_SECONDS\n if (!fx.flowing) {\n fx.on = false\n if (fx.spec.part) setPartTarget(o, fx.spec.part, 0)\n }\n } else {\n fx.flowing = false\n fx.frac = 0\n stepFixture(o, fx, 1 / 60)\n }\n emitFixture(o, fx)\n if (!fx.flowing) message('no_water')\n return true\n }\n\n // ------------------------------------------------------------------ seats\n function seatTaken(objectId: string, seatId: string): string | null {\n for (const [actor, s] of seats) if (s.object === objectId && s.seat === seatId) return actor\n return null\n }\n\n function stand(actor: string = DEFAULT_ACTOR): Vec3 | null {\n const s = seats.get(actor)\n if (!s) return null\n seats.delete(actor)\n const o = objs.get(s.object)\n const spec = o?.spec.seats?.find((x) => x.id === s.seat)\n emit({ type: 'seat', object: s.object, seat: s.seat, actor, seated: false })\n if (!o || !spec) return null\n return toWorld(o, spec.exit)\n }\n\n function sit(objectId: string, seatId: string, actor: string = DEFAULT_ACTOR): SeatResult | null {\n const o = objs.get(objectId)\n const seat = o?.spec.seats?.find((s) => s.id === seatId)\n if (!o || !seat) return null\n const holder = seatTaken(objectId, seatId)\n if (holder && holder !== actor) return null\n if (seats.has(actor) && !holder) stand(actor)\n seats.set(actor, { object: objectId, seat: seatId })\n const eye = seat.eye ?? (seat.pose === 'lie' ? 0.18 : 0.62)\n emit({ type: 'seat', object: objectId, seat: seatId, actor, seated: true })\n return { eye: toWorld(o, [seat.pos[0], seat.pos[1] + eye, seat.pos[2]]), yaw: o.public.transform.ry + seat.ry + Math.PI, pose: seat.pose }\n }\n\n // ------------------------------------------------------------------ soft volumes\n function push(pos: Vec3, radius: number, vel: Vec3): void {\n for (const o of objs.values()) {\n if (!o.soft.size) continue\n const tr = o.public.transform\n const lp = worldToLocal(tr, pos)\n const lv = rotY(vel, -tr.ry)\n for (const s of o.soft.values()) {\n const [w, h, d] = s.spec.size\n const hx = w / 2\n const hz = Math.max(d, 0.1) / 2\n const dx = Math.max(Math.abs(lp[0] - s.spec.anchor[0]) - hx, 0)\n const dz = Math.max(Math.abs(lp[2] - s.spec.anchor[2]) - hz, 0)\n if (Math.hypot(dx, dz) > radius) continue\n const top = tr.y + s.spec.anchor[1]\n if (pos[1] + ACTOR_HEIGHT < top - h || pos[1] > top) continue\n const cap = 8\n s.vax = clamp(s.vax - lv[2] * PUSH_GAIN, -cap, cap)\n s.vaz = clamp(s.vaz + lv[0] * PUSH_GAIN, -cap, cap)\n if (s.spec.kind === 'panel' && s.spec.part && lv[2] < -0.05 && lp[2] > s.spec.anchor[2]) setPartTarget(o, s.spec.part, 1)\n }\n }\n }\n\n function stepSoft(dt: number): void {\n const sub = Math.max(1, Math.ceil(dt / (1 / 120)))\n const h = dt / sub\n for (const o of objs.values()) {\n if (!o.soft.size) continue\n const wl = rotY([wind[0], 0, wind[1]], -o.public.transform.ry)\n for (const s of o.soft.values()) {\n const k = s.spec.stiffness ?? 30\n const c = s.spec.damping ?? 4\n const max = s.spec.maxAngle ?? 0.7\n const gain = s.spec.wind ?? (s.spec.kind === 'panel' ? 0 : 1)\n const gust = gain * (0.6 + 0.4 * Math.sin(time * 1.7 + s.phase)) * 0.1\n const fx = -wl[2] * gust\n const fz = wl[0] * gust\n for (let i = 0; i < sub; i++) {\n s.vax += (-k * s.ax - c * s.vax + fx) * h\n s.vaz += (-k * s.az - c * s.vaz + fz) * h\n s.ax += s.vax * h\n s.az += s.vaz * h\n }\n if (Math.abs(s.ax) > max) {\n s.ax = Math.sign(s.ax) * max\n s.vax = 0\n }\n if (Math.abs(s.az) > max) {\n s.az = Math.sign(s.az) * max\n s.vaz = 0\n }\n }\n }\n }\n\n // ------------------------------------------------------------------ damage\n function damage(objectId: string, target: string, amount: number, _kind?: string): { hp: number; destroyed: boolean } | null {\n const o = objs.get(objectId)\n if (o && options.destruction) {\n const pos = toWorld(o, [0, 0.5, 0])\n const type = _kind === 'gas' ? 'explosive' : _kind === 'fabric' ? 'slash' : 'blunt'\n const result = options.destruction.hitPart(objectId, target, { type, point: pos, energy: Math.max(0, amount), source: 'interactables' }) as { events?: Array<{ type?: string; part?: string }> } | null | undefined\n const destroyed = !!result?.events?.some((e) => e.type === 'fracture' && e.part === target)\n emit({ type: 'damage', object: objectId, target, hp: -1, destroyed })\n return { hp: -1, destroyed }\n }\n const st = o?.hp.get(target)\n if (!o || !st) return null\n if (st.hp <= 0) return { hp: 0, destroyed: true }\n const before = st.hp\n st.hp = Math.max(0, st.hp - Math.max(0, amount))\n const destroyed = st.hp <= 0\n emit({ type: 'damage', object: objectId, target, hp: st.hp, destroyed })\n const pos = toWorld(o, [0, 0.5, 0])\n const fuel = o.fuels.get(target)\n if (fuel && destroyed && fuel.spec.kind === 'gas' && fuel.level > 0) {\n emit({ type: 'explosion', object: objectId, pos, radius: 2 + fuel.level / 5 })\n fuel.level = 0\n }\n const tank = o.tanks.get(target)\n if (tank && before === st.max && st.hp < before) emit({ type: 'leak', object: objectId, target, pos })\n return { hp: st.hp, destroyed }\n }\n\n // ------------------------------------------------------------------ interactions\n function deriveInteractions(spec: ObjectSpec): InteractionSpec[] {\n const out: InteractionSpec[] = []\n const used = new Set<string>()\n const partPos = (id: string | undefined, fallback: Vec3): Vec3 => spec.parts?.find((p) => p.id === id)?.pivot ?? fallback\n const partLabel = (id: string | undefined): string => spec.parts?.find((p) => p.id === id)?.label ?? ''\n const claim = (id: string | undefined) => {\n if (id) used.add(id)\n }\n for (const d of spec.devices ?? []) {\n claim(d.part)\n const pos = d.part ? partPos(d.part, [0, 1, 0]) : (d.plug ?? [0, 1, 0])\n // a hard-wired lamp (circuit, no plug, no part to press) is driven by switches and `setDevice`: it never steals the interact key\n if (d.kind === 'lamp' && !d.part && !d.plug && d.electric !== false) continue\n out.push({ id: `device.${d.id}`, pos, action: { do: 'toggle_device', device: d.id }, label: partLabel(d.part) })\n if (d.kind === 'screen' && (d.channels ?? 1) > 1) out.push({ id: `channel.${d.id}`, pos, action: { do: 'cycle_device', device: d.id }, label: '' })\n }\n for (const s of spec.switches ?? []) {\n claim(s.part)\n out.push({ id: `switch.${s.id}`, pos: partPos(s.part, [0, 1.2, 0]), action: { do: 'toggle_switch', switch: s.id }, label: partLabel(s.part) })\n }\n const driven = new Set((spec.devices ?? []).map((d) => d.fixture))\n for (const f of spec.fixtures ?? []) {\n claim(f.part)\n if (driven.has(f.id)) continue\n out.push({ id: `fixture.${f.id}`, pos: f.part ? partPos(f.part, f.outlet) : f.outlet, action: { do: 'toggle_fixture', fixture: f.id }, label: partLabel(f.part) })\n }\n for (const b of spec.burners ?? []) {\n claim(b.part)\n const pos = b.part ? partPos(b.part, b.pos) : b.pos\n if (b.needsIgnition) out.push({ id: `ignite.${b.id}`, pos, action: { do: 'ignite', burner: b.id }, label: '' })\n out.push({ id: `burner.${b.id}`, pos, action: { do: 'toggle_burner', burner: b.id }, label: partLabel(b.part) })\n }\n for (const f of spec.fuels ?? []) {\n if (!f.valve) continue\n claim(f.valve)\n out.push({ id: `fuel.${f.id}`, pos: partPos(f.valve, [0, 0.5, 0]), action: { do: 'toggle_fuel', fuel: f.id }, label: partLabel(f.valve) })\n }\n for (const c of spec.containers ?? []) {\n if (!c.access) out.push({ id: `open.${c.id}`, pos: c.volume.center, action: { do: 'open_container', container: c.id }, label: '' })\n }\n for (const p of spec.parts ?? []) {\n if (used.has(p.id)) continue\n if (p.label) out.push({ id: `part.${p.id}`, pos: p.pivot, action: { do: 'toggle_part', part: p.id }, label: p.label })\n }\n for (const c of spec.containers ?? []) {\n if (c.access && !spec.parts?.find((p) => p.id === c.access)?.label) {\n out.push({ id: `open.${c.id}`, pos: partPos(c.access, c.volume.center), action: { do: 'open_container', container: c.id }, label: '' })\n }\n }\n for (const s of spec.seats ?? []) out.push({ id: `seat.${s.id}`, pos: s.pos, action: { do: 'sit', seat: s.id }, label: '' })\n return out\n }\n\n function typeName(o: ObjState): string {\n return table[`type.${o.spec.type}`] ?? humanize(o.spec.type)\n }\n\n function promptOf(o: ObjState, i: InteractionSpec): { label: string; verb: string; object: string } {\n const a = i.action\n let state: string | null = null\n let def = ''\n switch (a.do) {\n case 'toggle_part': {\n const p = o.parts.get(a.part)\n const open = !!p && targetIsOpen(p)\n state = open ? 'open' : 'closed'\n def = open ? 'prompt.close' : 'prompt.open'\n break\n }\n case 'open_container': {\n const c = containerOf(o, a.container)\n const p = c?.access ? o.parts.get(c.access) : undefined\n const open = p ? targetIsOpen(p) : false\n state = p ? (open ? 'open' : 'closed') : null\n def = open ? 'prompt.close' : 'prompt.open'\n break\n }\n case 'toggle_device': {\n const on = !!o.devices.get(a.device)?.on\n state = on ? 'on' : 'off'\n def = on ? 'prompt.turn_off' : 'prompt.turn_on'\n break\n }\n case 'toggle_switch': {\n const sw = o.spec.switches?.find((s) => s.id === a.switch)\n const on = !!sw && switchIsOn(o, sw)\n state = on ? 'on' : 'off'\n def = on ? 'prompt.turn_off' : 'prompt.turn_on'\n break\n }\n case 'toggle_breaker': {\n const on = breaker(a.circuit)\n state = on ? 'on' : 'off'\n def = on ? 'prompt.breaker_off' : 'prompt.breaker_on'\n break\n }\n case 'toggle_fixture': {\n const on = !!o.fixtures.get(a.fixture)?.on\n state = on ? 'on' : 'off'\n def = on ? 'prompt.turn_off' : 'prompt.turn_on'\n break\n }\n case 'toggle_burner': {\n const b = o.burners.get(a.burner)\n const on = !!b && (b.on || b.lit)\n state = on ? 'on' : 'off'\n def = on ? 'prompt.turn_off' : 'prompt.turn_on'\n break\n }\n case 'toggle_fuel': {\n const f = o.fuels.get(a.fuel)\n const open = !!f && valveOpen(o, f)\n state = open ? 'open' : 'closed'\n def = open ? 'prompt.close_valve' : 'prompt.open_valve'\n break\n }\n case 'take':\n def = 'prompt.take'\n break\n case 'read':\n def = 'prompt.read'\n break\n case 'ignite':\n def = 'prompt.light'\n break\n case 'cycle_device':\n def = 'prompt.change_channel'\n break\n case 'sit': {\n const seat = o.spec.seats?.find((s) => s.id === a.seat)\n const mine = seats.get(DEFAULT_ACTOR)\n def = mine && mine.object === o.id && mine.seat === a.seat ? 'prompt.get_up' : seat?.pose === 'lie' ? 'prompt.lie_down' : 'prompt.sit'\n break\n }\n }\n let key = def\n if (i.label) {\n if (state && table[`${i.label}.${state}`] !== undefined) key = `${i.label}.${state}`\n else if (table[i.label] !== undefined) key = i.label\n }\n const vars = { name: typeName(o), circuit: a.do === 'toggle_breaker' ? (a.name ?? a.circuit) : '' }\n const label = t(key, vars)\n // structured prompt: explicit `<key>.verb` / `<key>.object` entries win; otherwise the first word is the verb and the rest the object\n let verb: string\n let object: string\n if (table[`${key}.verb`] !== undefined) {\n verb = t(`${key}.verb`, vars)\n object = table[`${key}.object`] ? t(`${key}.object`, vars) : vars.name\n } else {\n const sp = label.indexOf(' ')\n verb = sp < 0 ? label : label.slice(0, sp)\n object = sp < 0 ? vars.name : label.slice(sp + 1)\n }\n return { label, verb, object }\n }\n\n /** Locale key of the reason an interaction cannot be used right now, or null when it can. */\n function unavailableReason(o: ObjState, i: InteractionSpec): string | null {\n const a = i.action\n if (!requirementMet(i)) return 'requires_open'\n switch (a.do) {\n case 'toggle_part': {\n const p = o.parts.get(a.part)\n return p && gated(o, p) ? 'requires_open' : null\n }\n case 'open_container': {\n const c = containerOf(o, a.container)\n const p = c?.access ? o.parts.get(c.access) : undefined\n return p && gated(o, p) ? 'requires_open' : null\n }\n case 'toggle_breaker': {\n const rocker = a.part ? o.parts.get(a.part) : undefined\n return rocker && gated(o, rocker) ? 'requires_open' : null\n }\n case 'take':\n case 'read':\n return accessible(o.id, a.container) ? null : 'requires_open'\n case 'ignite':\n return o.burners.get(a.burner)?.lit ? 'already_lit' : null\n case 'toggle_burner': {\n const b = o.burners.get(a.burner)\n return !b?.spec.needsIgnition || b.lit ? null : 'needs_ignition'\n }\n case 'cycle_device': {\n const d = o.devices.get(a.device)\n if (!d || !d.on) return 'turn_on_first'\n return devicePowered(o, d) ? null : 'no_power'\n }\n default:\n return null\n }\n }\n\n function requirementMet(i: InteractionSpec): boolean {\n if (!i.requires) return true\n const p = objs.get(i.requires.object)?.parts.get(i.requires.part)\n return !!p && targetIsOpen(p)\n }\n\n function interactablesOf(o: ObjState): Array<{ item: Interactable; spec: InteractionSpec; o: ObjState }> {\n return o.interactions.map((i) => {\n const id = gid(o, i.id)\n const p = promptOf(o, i)\n const item: Interactable = {\n id,\n object: o.id,\n kind: i.action.do,\n pos: toWorld(o, i.pos),\n radius: i.radius ?? DEFAULT_RADIUS,\n label: p.label,\n verb: p.verb,\n noun: p.object,\n available: true,\n act: (actor) => act(id, actor),\n }\n const reason = unavailableReason(o, i) ?? (options.canInteract?.(item, DEFAULT_ACTOR) === false ? 'locked' : null)\n if (reason) {\n item.available = false\n item.reason = reason\n }\n return { item, spec: i, o }\n })\n }\n\n function interactables(): Interactable[] {\n const out: Interactable[] = []\n for (const o of objs.values()) for (const e of interactablesOf(o)) out.push(e.item)\n return out\n }\n\n function target(eye: { x: number; y: number; z: number; yaw: number; pitch: number }, opts: TargetOptions = {}): Interactable | null {\n const reach = opts.reach ?? 1.8\n const cone = opts.cone ?? 0.45\n const dir: Vec3 = [-Math.sin(eye.yaw) * Math.cos(eye.pitch), Math.sin(eye.pitch), -Math.cos(eye.yaw) * Math.cos(eye.pitch)]\n let best: Interactable | null = null\n let bestScore = Infinity\n for (const o of objs.values()) {\n for (const e of interactablesOf(o)) {\n if (!e.item.available && !opts.includeUnavailable) continue\n if (opts.kinds && !opts.kinds.includes(e.item.kind)) continue\n if (opts.filter && !opts.filter(e.item)) continue\n const v: Vec3 = [e.item.pos[0] - eye.x, e.item.pos[1] - eye.y, e.item.pos[2] - eye.z]\n const d = Math.hypot(v[0], v[1], v[2])\n if (d > reach) continue\n const cos = d < 1e-6 ? 1 : clamp((v[0] * dir[0] + v[1] * dir[1] + v[2] * dir[2]) / d, -1, 1)\n const angle = Math.acos(cos)\n if (angle > cone) continue\n const score = (angle / cone) * 0.6 + (d / reach) * 0.4\n if (score < bestScore) {\n bestScore = score\n best = e.item\n }\n }\n }\n return best\n }\n\n function act(interactableId: string, actor: string = DEFAULT_ACTOR): boolean {\n let found: { item: Interactable; spec: InteractionSpec; o: ObjState } | null = null\n for (const o of objs.values()) {\n if (!interactableId.startsWith(o.id + ':')) continue\n const local = interactableId.slice(o.id.length + 1)\n if (!o.interactions.some((i) => i.id === local)) continue\n found = interactablesOf(o).find((e) => e.spec.id === local) ?? null\n if (found) break\n }\n if (!found) return false\n if (!requirementMet(found.spec)) return false\n if (options.canInteract?.(found.item, actor) === false) return false\n return execute(found.o, found.spec.action, actor)\n }\n\n function execute(o: ObjState, a: InteractionSpec['action'], actor: string): boolean {\n switch (a.do) {\n case 'toggle_part': {\n const p = o.parts.get(a.part)\n if (!p || gated(o, p)) return false\n setPartTarget(o, a.part, targetIsOpen(p) ? 0 : 1)\n return true\n }\n case 'open_container': {\n const c = containerOf(o, a.container)\n if (!c) return false\n if (c.access) return execute(o, { do: 'toggle_part', part: c.access }, actor)\n emit({ type: 'container', object: o.id, container: c.id, open: true })\n return true\n }\n case 'take': {\n if (!accessible(o.id, a.container)) return false\n const list = o.contents.get(a.container)\n if (!list) return false\n const slot = a.slot ?? list.findIndex((s) => s !== null)\n if (slot < 0 || !list[slot]) {\n message('empty')\n return false\n }\n return take(o.id, a.container, slot, actor) !== null\n }\n case 'read': {\n if (!accessible(o.id, a.container)) return false\n const stack = o.contents.get(a.container)?.[a.slot]\n if (!stack) {\n message('empty')\n return false\n }\n const key = typeof stack.data?.text === 'string' ? stack.data.text : null\n if (!key) return false\n message(key)\n return true\n }\n case 'toggle_device': {\n const d = o.devices.get(a.device)\n if (!d) return false\n setDeviceOn(o, d, !d.on)\n return true\n }\n case 'cycle_device': {\n const d = o.devices.get(a.device)\n if (!d || !d.on) return false\n if (!devicePowered(o, d)) {\n message('no_power')\n return false\n }\n d.channel = (d.channel + 1) % Math.max(1, d.spec.channels ?? 1)\n emit({ type: 'device', object: o.id, device: d.spec.id, on: true, powered: true, channel: d.channel })\n return true\n }\n case 'toggle_switch':\n return toggleSwitch(o, a.switch)\n case 'toggle_breaker': {\n const rocker = a.part ? o.parts.get(a.part) : undefined\n if (rocker && gated(o, rocker)) return false\n const on = !breaker(a.circuit)\n if (a.part) setPartTarget(o, a.part, on ? 1 : 0)\n world.power.setBreaker(a.circuit, on)\n return true\n }\n case 'toggle_fixture':\n return toggleFixture(o, a.fixture)\n case 'toggle_burner':\n return toggleBurner(o, a.burner)\n case 'ignite':\n return ignite(o, a.burner)\n case 'toggle_fuel':\n return toggleFuel(o, a.fuel)\n case 'sit': {\n const mine = seats.get(actor)\n if (mine && mine.object === o.id && mine.seat === a.seat) {\n stand(actor)\n return true\n }\n return sit(o.id, a.seat, actor) !== null\n }\n }\n }\n\n // ------------------------------------------------------------------ emitters, mirrors, colliders\n function emitters(): ActiveEmitter[] {\n const out: ActiveEmitter[] = []\n for (const o of objs.values()) {\n for (const e of o.spec.emitters ?? []) {\n let intensity = 0\n const b = o.burners.get(e.source)\n const fx = o.fixtures.get(e.source)\n const dev = o.devices.get(e.source)\n const fuel = o.fuels.get(e.source)\n if (b) {\n const ref = resolveFuel(o, b)\n const frac = ref ? clamp(ref.f.level / ref.f.spec.capacity, 0, 1) : 0\n if (b.lit) intensity = 0.35 + 0.65 * frac\n else if (e.kind === 'smoke' && b.smoke > 0) intensity = 0.6 * (b.smoke / SMOKE_TAIL)\n } else if (fx) {\n if (fx.on && fx.flowing) intensity = fx.frac\n } else if (dev) {\n if (dev.on && devicePowered(o, dev)) intensity = 1\n } else if (fuel) {\n const lit = [...o.burners.values()].some((bb) => bb.lit && resolveFuel(o, bb)?.f === fuel)\n if (lit) intensity = 0.35 + 0.65 * clamp(fuel.level / fuel.spec.capacity, 0, 1)\n }\n if (intensity <= 0) continue\n const dir = rotY(e.dir ?? [0, 1, 0], o.public.transform.ry)\n out.push({ id: gid(o, e.id), object: o.id, kind: e.kind, pos: toWorld(o, e.pos), dir, size: e.size ?? 0.3, ...(e.reach !== undefined ? { reach: e.reach } : {}), intensity: clamp(intensity, 0, 1) })\n }\n }\n return out\n }\n\n function mirrors(eye: Vec3, maxDistance = 10, count = 4) {\n const out: Array<{ id: string; center: Vec3; normal: Vec3; w: number; h: number; distance: number }> = []\n for (const o of objs.values()) {\n for (const m of o.spec.mirrors ?? []) {\n const center = toWorld(o, applyPart(o, m.part, m.center))\n const f = partFrame(o, m.part)\n const ln = f && f.angle !== 0 ? rotateAxis(m.normal, f.axis, f.angle) : m.normal\n const normal = rotY(ln, o.public.transform.ry)\n const len = Math.hypot(normal[0], normal[1], normal[2]) || 1\n const n: Vec3 = [normal[0] / len, normal[1] / len, normal[2] / len]\n const to: Vec3 = [eye[0] - center[0], eye[1] - center[1], eye[2] - center[2]]\n if (n[0] * to[0] + n[1] * to[1] + n[2] * to[2] <= 0) continue\n const distance = Math.hypot(to[0], to[1], to[2])\n if (distance > maxDistance) continue\n out.push({ id: gid(o, m.id), center, normal: n, w: m.w, h: m.h, distance })\n }\n }\n return out.sort((a, b) => a.distance - b.distance).slice(0, count)\n }\n\n function colliders(opts: ColliderOptions = {}): WorldCollider[] {\n const out: WorldCollider[] = []\n const fixed: WorldCollider[] = []\n for (const o of objs.values()) {\n const tr = o.public.transform\n for (const c of o.spec.colliders ?? []) {\n if (c.part && c.offWhenOpen && (o.parts.get(c.part)?.t ?? 0) > 0.5) continue\n let extra = 0\n if (c.part) {\n const f = partFrame(o, c.part)\n if (f && f.angle !== 0 && Math.abs(f.axis[1]) >= 0.5) extra = f.angle * Math.sign(f.axis[1])\n }\n const center = toWorld(o, applyPart(o, c.part, c.center))\n ;(c.part ? out : fixed).push({\n id: gid(o, c.id),\n object: o.id,\n cx: center[0],\n cz: center[2],\n hw: c.half[0],\n hd: c.half[2],\n ry: tr.ry + (c.ry ?? 0) + extra,\n y0: center[1] - c.half[1],\n y1: center[1] + c.half[1],\n })\n }\n }\n if (opts.merge) return [...mergeColliders(fixed, { gap: opts.gap, slack: opts.slack }), ...out]\n return [...fixed, ...out]\n }\n\n // ------------------------------------------------------------------ update\n function update(dt: number): void {\n if (!(dt > 0)) return\n time += dt\n for (const o of objs.values()) {\n for (const p of o.parts.values()) {\n if (p.t === p.target) continue\n if (gated(o, p) && p.target > p.t) continue // a gated part may still close\n const step = (p.spec.motion.speed ?? 2.5) * dt\n const next = p.t < p.target ? Math.min(p.target, p.t + step) : Math.max(p.target, p.t - step)\n writeT(o, p, next)\n if (next === p.target) emit({ type: 'part', object: o.id, part: p.spec.id, t: next, target: p.target })\n }\n }\n stepSoft(dt)\n for (const o of objs.values()) {\n for (const b of o.burners.values()) {\n if (b.lit) {\n const ref = resolveFuel(o, b)\n if (!ref || !valveOpen(ref.o, ref.f)) {\n extinguish(o, b, !!b.spec.needsIgnition)\n } else {\n ref.f.level = Math.max(0, ref.f.level - b.spec.rate * dt)\n if (ref.f.level <= 0) extinguish(o, b, !!b.spec.needsIgnition)\n }\n } else if (b.on && !b.spec.needsIgnition && canBurn(b, resolveFuel(o, b))) {\n // a gas knob left open without gas lights as soon as the bottle valve opens\n b.lit = true\n b.smoke = 0\n emitBurner(o, b)\n } else if (b.smoke > 0) {\n b.smoke = Math.max(0, b.smoke - dt)\n }\n }\n if (rain > 0) {\n for (const tk of o.tanks.values()) {\n if (tk.spec.rainArea) tk.level = Math.min(tk.spec.capacity, tk.level + ((rain * tk.spec.rainArea) / 3600) * dt)\n }\n }\n for (const tk of o.tanks.values()) {\n const st = o.hp.get(tk.spec.id)\n if (st && st.hp < st.max && tk.level > 0) {\n const rate = 0.5 + 2.5 * (1 - st.hp / st.max)\n tk.level = Math.max(0, tk.level - rate * dt)\n }\n }\n }\n for (const o of objs.values()) {\n for (const d of o.devices.values()) {\n const fx = d.spec.fixture ? o.fixtures.get(d.spec.fixture) : undefined\n if (!fx) continue\n const run = d.on && devicePowered(o, d)\n if (fx.on === run) continue\n fx.on = run\n if (!run) {\n fx.flowing = false\n fx.frac = 0\n emitFixture(o, fx)\n }\n }\n for (const fx of o.fixtures.values()) stepFixture(o, fx, dt)\n }\n }\n\n // ------------------------------------------------------------------ add / remove\n function add(id: string, spec: ObjectSpec, transform: Transform): WorldObject {\n if (objs.has(id)) throw new Error(`Object \"${id}\" already exists`)\n const pub: WorldObject = { id, spec, transform: { ...transform }, parts: {} }\n const o: ObjState = {\n id,\n public: pub,\n spec,\n parts: new Map(),\n contents: new Map(),\n devices: new Map(),\n fuels: new Map(),\n burners: new Map(),\n tanks: new Map(),\n fixtures: new Map(),\n soft: new Map(),\n hp: new Map(),\n interactions: spec.interactions ?? deriveInteractions(spec),\n }\n for (const p of spec.parts ?? []) {\n const t0 = clamp(p.t0 ?? 0, 0, 1)\n o.parts.set(p.id, { spec: p, t: t0, target: t0, maxT: 1 })\n pub.parts[p.id] = t0\n }\n for (const c of spec.containers ?? []) {\n let list: Array<ItemStack | null>\n if (c.contents) list = c.slots.map((_, i) => cloneStack(c.contents?.[i] ?? null))\n else if (c.loot) list = generateLoot(options.loot, c.loot, c.slots, `${seed}|${id}|${c.id}`, options.items)\n else list = c.slots.map(() => null)\n o.contents.set(c.id, list)\n }\n for (const d of spec.devices ?? []) {\n o.devices.set(d.id, { spec: d, on: !!d.on, channel: 0 })\n if (d.on && d.part) {\n const p = o.parts.get(d.part)\n if (p) {\n p.target = p.maxT\n writeT(o, p, p.maxT)\n }\n }\n }\n for (const f of spec.fuels ?? []) o.fuels.set(f.id, { spec: f, level: clamp(f.level ?? f.capacity, 0, f.capacity) })\n for (const b of spec.burners ?? []) o.burners.set(b.id, { spec: b, on: false, lit: false, smoke: 0 })\n for (const tk of spec.tanks ?? []) o.tanks.set(tk.id, { spec: tk, level: clamp(tk.level ?? tk.capacity, 0, tk.capacity) })\n for (const f of spec.fixtures ?? []) o.fixtures.set(f.id, { spec: f, on: false, flowing: false, frac: 0, timer: 0 })\n let n = 0\n for (const s of spec.soft ?? []) {\n o.soft.set(s.id, { spec: s, ax: 0, az: 0, vax: 0, vaz: 0, phase: (n++ * 2.399963) % (Math.PI * 2) })\n }\n for (const d of spec.damage ?? []) o.hp.set(d.target, { max: d.hp, hp: d.hp })\n objs.set(id, o)\n if (!defaultTankExplicit && defaultTank === null && o.tanks.size) defaultTank = gid(o, [...o.tanks.keys()][0])\n rebuildPlugs()\n return pub\n }\n\n function remove(id: string): void {\n const o = objs.get(id)\n if (!o) return\n for (const [actor, s] of [...seats]) if (s.object === id) seats.delete(actor)\n objs.delete(id)\n if (defaultTank && defaultTank.startsWith(id + ':')) defaultTank = null\n rebuildPlugs()\n }\n\n // ------------------------------------------------------------------ save / load\n function serialize(): WorldState {\n const objects: WorldState['objects'] = {}\n for (const o of objs.values()) {\n objects[o.id] = {\n parts: Object.fromEntries([...o.parts].map(([k, p]) => [k, p.t])),\n contents: Object.fromEntries([...o.contents].map(([k, v]) => [k, v.map(cloneStack)])),\n devices: Object.fromEntries([...o.devices].map(([k, d]) => [k, { on: d.on, channel: d.channel }])),\n fuels: Object.fromEntries([...o.fuels].map(([k, f]) => [k, { level: f.level, open: valveOpen(o, f) }])),\n burners: Object.fromEntries([...o.burners].map(([k, b]) => [k, { on: b.on, lit: b.lit }])),\n tanks: Object.fromEntries([...o.tanks].map(([k, tk]) => [k, tk.level])),\n fixtures: Object.fromEntries([...o.fixtures].map(([k, f]) => [k, f.on])),\n hp: Object.fromEntries([...o.hp].map(([k, h]) => [k, h.hp])),\n }\n }\n return {\n version: 1,\n objects,\n grid,\n breakers: Object.fromEntries(breakers),\n seats: Object.fromEntries([...seats].map(([a, s]) => [a, { object: s.object, seat: s.seat }])),\n defaultTank,\n }\n }\n\n function load(state: WorldState): void {\n for (const [id, st] of Object.entries(state.objects)) {\n const o = objs.get(id)\n if (!o) continue\n for (const [k, v] of Object.entries(st.parts)) {\n const p = o.parts.get(k)\n if (p) {\n // keep the limit found by fitSweep: a loaded save must not let a door open through a wall again\n p.target = clamp(v, 0, p.maxT)\n writeT(o, p, p.target)\n }\n }\n for (const [k, v] of Object.entries(st.contents)) {\n const c = containerOf(o, k)\n if (c) o.contents.set(k, c.slots.map((_, i) => cloneStack(v[i] ?? null)))\n }\n for (const [k, v] of Object.entries(st.devices)) {\n const d = o.devices.get(k)\n if (d) {\n d.on = v.on\n d.channel = v.channel\n }\n }\n for (const [k, v] of Object.entries(st.fuels)) {\n const f = o.fuels.get(k)\n if (f) f.level = v.level\n }\n for (const [k, v] of Object.entries(st.burners)) {\n const b = o.burners.get(k)\n if (b) {\n b.on = v.on\n b.lit = v.lit\n b.smoke = 0\n }\n }\n for (const [k, v] of Object.entries(st.tanks)) {\n const tk = o.tanks.get(k)\n if (tk) tk.level = v\n }\n for (const [k, v] of Object.entries(st.fixtures)) {\n const f = o.fixtures.get(k)\n if (f) {\n f.on = v\n f.flowing = false\n f.frac = 0\n f.timer = v && f.spec.kind === 'flush' ? FLUSH_SECONDS : 0\n }\n }\n for (const [k, v] of Object.entries(st.hp)) {\n const h = o.hp.get(k)\n if (h) h.hp = v\n }\n }\n grid = state.grid\n breakers.clear()\n for (const [k, v] of Object.entries(state.breakers)) breakers.set(k, v)\n seats.clear()\n for (const [a, s] of Object.entries(state.seats)) if (objs.has(s.object)) seats.set(a, { object: s.object, seat: s.seat })\n defaultTank = state.defaultTank\n defaultTankExplicit = true\n }\n\n // ------------------------------------------------------------------ public surface\n const world: World = {\n add,\n remove,\n get: (id) => objs.get(id)?.public,\n objects: () => [...objs.values()].map((o) => o.public),\n update,\n interactables,\n target,\n act,\n partT: (objectId, partId) => objs.get(objectId)?.parts.get(partId)?.t ?? 0,\n setDevice,\n setFixture,\n setBurner,\n setPart: (objectId, partId, targetT, instant) => {\n const o = objs.get(objectId)\n if (o) setPartTarget(o, partId, targetT, instant)\n },\n fitSweep,\n contents,\n accessible,\n take,\n put,\n slotPosition,\n power: {\n get grid() {\n return grid\n },\n set grid(on: boolean) {\n world.power.setGrid(on)\n },\n setGrid(on) {\n if (on === grid) return\n const before = powerSnapshot()\n grid = on\n emit({ type: 'power', grid })\n emitPowerChanges(before)\n },\n setBreaker(circuit, on) {\n if (breaker(circuit) === on) {\n breakers.set(circuit, on)\n return\n }\n const before = powerSnapshot()\n breakers.set(circuit, on)\n emitPowerChanges(before)\n },\n breaker,\n isPowered: (objectId, deviceId) => {\n const r = getDevice(objectId, deviceId)\n return r ? devicePowered(r.o, r.d) : false\n },\n isOn: (objectId, deviceId) => getDevice(objectId, deviceId)?.d.on ?? false,\n pluggedInto: (objectId, deviceId) => plugs.get(`${objectId}:${deviceId}`) ?? null,\n channel: (objectId, deviceId) => getDevice(objectId, deviceId)?.d.channel ?? 0,\n },\n fuelLevel: (objectId, fuelId) => objs.get(objectId)?.fuels.get(fuelId)?.level ?? 0,\n isLit: (objectId, burnerId) => objs.get(objectId)?.burners.get(burnerId)?.lit ?? false,\n water: {\n setDefaultTank(tankId) {\n defaultTank = tankId\n defaultTankExplicit = true\n },\n level(tankId) {\n return findGlobal(tankId, (oo, local) => oo.tanks.get(local))?.value.level ?? 0\n },\n refill(tankId, liters) {\n const f = findGlobal(tankId, (oo, local) => oo.tanks.get(local))\n if (!f) return 0\n const added = Math.max(0, Math.min(liters, f.value.spec.capacity - f.value.level))\n f.value.level += added\n return added\n },\n setRain(mm) {\n rain = Math.max(0, mm)\n },\n isFlowing: (objectId, fixtureId) => {\n const fx = objs.get(objectId)?.fixtures.get(fixtureId)\n return !!fx && fx.on && fx.flowing\n },\n },\n sit,\n stand,\n seatOf: (actor = DEFAULT_ACTOR) => {\n const s = seats.get(actor)\n return s ? { object: s.object, seat: s.seat } : null\n },\n push,\n sway: (objectId, softId) => {\n const s = objs.get(objectId)?.soft.get(softId)\n return s ? { ax: s.ax, az: s.az } : { ax: 0, az: 0 }\n },\n setWind: (x, z) => {\n wind = [x, z]\n },\n emitters,\n mirrors,\n colliders,\n damage,\n hp: (objectId, target) => (options.destruction ? null : (objs.get(objectId)?.hp.get(target)?.hp ?? null)),\n t,\n setLocale: (l) => {\n locale = l\n table = resolveStrings(locale, options.strings)\n },\n serialize,\n load,\n }\n return world\n}\n","import { poseOf as enginePoseOf } from '@adatechnology/game-interactables'\nimport type { Motion, World } from '@adatechnology/game-interactables'\nimport type { ThreeLib, ThreeNS, Vec3 } from './types'\n\n/** Pure pose of a part at `t`, in the part's own frame (the frame at rest: pivot at the origin, already turned by the part's `ry`). */\nexport interface Pose {\n /** Translation, part frame. */\n position: Vec3\n /** Rotation as a quaternion [x, y, z, w], part frame. */\n quaternion: [number, number, number, number]\n /** Scale per local axis. */\n scale: Vec3\n}\n\n/**\n * The engine's pose math, as a quaternion. This is the engine's own `poseOf` (same default angles and distances), so the rendered\n * part and the engine's `slotPosition`, colliders, mirrors and `fitSweep` always agree. `t` is clamped to [0, 1].\n */\nexport function poseOf(motion: Motion, t: number): Pose {\n const k = Math.min(1, Math.max(0, Number.isFinite(t) ? t : 0))\n const pose = enginePoseOf(motion, k)\n const [x, y, z] = pose.rotation.axis\n const l = Math.hypot(x, y, z) || 1\n const s = Math.sin(pose.rotation.angle / 2)\n return {\n position: [pose.position[0], pose.position[1], pose.position[2]],\n quaternion: [(x / l) * s, (y / l) * s, (z / l) * s, Math.cos(pose.rotation.angle / 2)],\n scale: [pose.scale[0], pose.scale[1], pose.scale[2]],\n }\n}\n\ninterface Rest {\n position: Vec3\n quaternion: [number, number, number, number]\n scale: Vec3\n}\n\n/** Remembers the rest transform of a part group in `userData.rest` (once). */\nexport function rememberRest(group: Pick<ThreeNS.Object3D, 'position' | 'quaternion' | 'scale' | 'userData'>): Rest {\n let rest = group.userData.rest as Rest | undefined\n if (!rest) {\n const p = group.position\n const q = group.quaternion\n const s = group.scale\n rest = { position: [p.x, p.y, p.z], quaternion: [q.x, q.y, q.z, q.w], scale: [s.x, s.y, s.z] }\n group.userData.rest = rest\n }\n return rest\n}\n\n/**\n * Poses `group` for a motion at `t`. The rest pose (position at the pivot, rotation `ry`) is saved in `group.userData.rest` the first\n * time, so calling this every frame never drifts. The motion axis lives in the part frame (after `ry`).\n */\nexport function applyPose<G extends import('three').Object3D>(THREE: ThreeLib, group: G, motion: Motion, t: number): G {\n const rest = rememberRest(group)\n const pose = poseOf(motion, t)\n const restQ = new THREE.Quaternion(rest.quaternion[0], rest.quaternion[1], rest.quaternion[2], rest.quaternion[3])\n const delta = new THREE.Quaternion(pose.quaternion[0], pose.quaternion[1], pose.quaternion[2], pose.quaternion[3])\n group.quaternion.copy(restQ).multiply(delta)\n const offset = new THREE.Vector3(pose.position[0], pose.position[1], pose.position[2]).applyQuaternion(restQ)\n group.position.set(rest.position[0] + offset.x, rest.position[1] + offset.y, rest.position[2] + offset.z)\n group.scale.set(rest.scale[0] * pose.scale[0], rest.scale[1] * pose.scale[1], rest.scale[2] * pose.scale[2])\n group.updateMatrix()\n return group\n}\n\nexport interface SyncOptions {\n /** Seconds, drives the idle flutter of sway shaders. Default 0. */\n time?: number\n}\n\n/**\n * Per-frame sync of one world object: every part group takes the motion of its `PartSpec` at the world's current t,\n * and every soft volume swings. A soft volume drives the group in `parts` under its `part` (or, failing that, its own id):\n * meshes patched with `attachSway` get the swing through their shader uniforms (so shadows follow), any other group is rotated\n * around the anchor line.\n */\nexport function syncObject(THREE: ThreeLib, world: World, objectId: string, parts: Record<string, import('three').Object3D>, options: SyncOptions = {}): void {\n const obj = world.get(objectId)\n if (!obj) return\n const motionOf = new Map<string, Motion>()\n for (const p of obj.spec.parts ?? []) {\n motionOf.set(p.id, p.motion)\n const g = parts[p.id]\n if (!g) continue\n const merged = g.userData.merged as { kind: string; apply?: (m: Motion, t: number) => void } | undefined\n if (merged) {\n // parts merged into one mesh: the batch poses every vertex around its own pivot (cloth batches stay at rest)\n merged.apply?.(p.motion, world.partT(objectId, p.id))\n continue\n }\n applyPose(THREE, g, p.motion, world.partT(objectId, p.id))\n }\n for (const soft of obj.spec.soft ?? []) {\n const key = soft.part ?? soft.id\n const g = parts[key] ?? parts[soft.id]\n if (!g) continue\n const { ax, az } = world.sway(objectId, soft.id)\n const batch = g.userData.merged as { kind: string; index?: Record<string, number>; setAt?: (i: number, ax: number, az: number) => void; setTime?: (t: number) => void } | undefined\n if (batch?.kind === 'sway') {\n const i = batch.index?.[soft.id]\n if (i !== undefined) batch.setAt?.(i, ax, az)\n if (options.time !== undefined) batch.setTime?.(options.time)\n continue\n }\n let shaded = false\n g.traverse((o) => {\n const sway = o.userData.sway as { set(ax: number, az: number, time?: number): void } | undefined\n if (sway) {\n sway.set(ax, az, options.time)\n shaded = true\n }\n })\n if (shaded) continue\n rememberRest(g)\n // rotate around the anchor line: the part's pose (if it has a motion) is already applied above\n if (!motionOf.has(key)) applyPose(THREE, g, { kind: 'hinge', angle: 0 }, 0)\n const rot = new THREE.Quaternion().setFromEuler(new THREE.Euler(ax, 0, az, 'XYZ'))\n const anchor = new THREE.Vector3(soft.anchor[0], soft.anchor[1], soft.anchor[2])\n g.position.sub(anchor).applyQuaternion(rot).add(anchor)\n g.quaternion.premultiply(rot)\n g.updateMatrix()\n }\n}\n","import type { Motion, ObjectSpec } from '@adatechnology/game-interactables'\nimport { poseOf } from './pose'\nimport type { ThreeLib, ThreeNS } from './types'\n\nexport interface MergeOptions {\n /** Idle flutter of merged cloth (see `SwayOptions.amplitude`). Default 0.04. */\n swayAmplitude?: number\n}\n\n/** Posed by `syncObject` in place of the part groups it replaced. */\nexport interface PoseBatch {\n kind: 'pose'\n /** Part ids that moved into the batch. */\n ids: string[]\n /** Applies the motion at `t` to every vertex, each around its own pivot. */\n apply(motion: Motion, t: number): void\n}\n\n/** Cloth hanging from a line: every merged cloth swings with its own (ax, az). */\nexport interface SwayBatch {\n kind: 'sway'\n /** Soft volume id -> index. */\n index: Record<string, number>\n setAt(index: number, ax: number, az: number): void\n setTime(time: number): void\n}\n\nexport type MergedGroup = ThreeNS.Group & { userData: { merged: PoseBatch | SwayBatch } }\n\nconst QROT = /* glsl */ `\nvec3 batchRotate(vec4 q, vec3 v) { return v + 2.0 * cross(q.xyz, cross(q.xyz, v) + q.w * v); }\n`\n\nfunction patchProgram<M extends ThreeNS.Material>(material: M, key: string, patchShader: (shader: { uniforms: Record<string, ThreeNS.IUniform>; vertexShader: string }) => void, inherit?: ThreeNS.Material): M {\n const previous = material.onBeforeCompile\n material.onBeforeCompile = (shader, renderer) => {\n previous?.call(material, shader, renderer)\n patchShader(shader)\n }\n const inherited = inherit?.customProgramCacheKey\n material.customProgramCacheKey = inherited && inherit ? () => `${inherited.call(inherit)}|${key}` : () => key\n return material\n}\n\n/**\n * `Material.clone()` drops `onBeforeCompile`, `customProgramCacheKey` and (for some classes) `defines`. A merged group must still look\n * like the material the host passed in (a light mask, a fog patch...), so the clone gets them back before the batch patch is chained on.\n */\nfunction cloneKeepingHooks(base: ThreeNS.Material): ThreeNS.Material {\n const clone = base.clone()\n if (base.defines) clone.defines = { ...(clone.defines ?? {}), ...base.defines }\n if (base.onBeforeCompile !== Object.getPrototypeOf(base).onBeforeCompile) clone.onBeforeCompile = base.onBeforeCompile\n clone.userData = { ...base.userData, mergedFrom: base.uuid }\n return clone\n}\n\n/** Material, depth material and distance material patched by the same function, so shadows follow the animation. */\nfunction patchedSet(THREE: ThreeLib, base: ThreeNS.Material, key: string, patchShader: (shader: { uniforms: Record<string, ThreeNS.IUniform>; vertexShader: string }) => void) {\n return {\n material: patchProgram(cloneKeepingHooks(base), key, patchShader, base),\n depth: patchProgram(new THREE.MeshDepthMaterial({ depthPacking: THREE.RGBADepthPacking }), key, patchShader),\n distance: patchProgram(new THREE.MeshDistanceMaterial(), key, patchShader),\n }\n}\n\ninterface Piece {\n geometry: ThreeNS.BufferGeometry\n matrix: ThreeNS.Matrix4\n /** Per-vertex extra attributes of this piece. */\n extra: Record<string, number[]>\n}\n\n/** Concatenates pieces (positions and normals moved by their matrices) into one geometry with the extra attributes. */\nfunction concat(THREE: ThreeLib, pieces: Piece[], extraSizes: Record<string, number>): ThreeNS.BufferGeometry {\n let verts = 0\n let idx = 0\n for (const p of pieces) {\n verts += p.geometry.getAttribute('position').count\n idx += p.geometry.index!.count\n }\n const pos = new Float32Array(verts * 3)\n const nor = new Float32Array(verts * 3)\n const col = new Float32Array(verts * 3)\n const uv = new Float32Array(verts * 2)\n const index = new Uint32Array(idx)\n const extra: Record<string, Float32Array> = {}\n for (const [k, n] of Object.entries(extraSizes)) extra[k] = new Float32Array(verts * n)\n const v = new THREE.Vector3()\n const n3 = new THREE.Matrix3()\n let vo = 0\n let io = 0\n for (const p of pieces) {\n const g = p.geometry\n const count = g.getAttribute('position').count\n n3.getNormalMatrix(p.matrix)\n const gp = g.getAttribute('position')\n const gn = g.getAttribute('normal')\n const gc = g.getAttribute('color')\n const gu = g.getAttribute('uv')\n for (let i = 0; i < count; i++) {\n v.fromBufferAttribute(gp, i).applyMatrix4(p.matrix)\n pos.set([v.x, v.y, v.z], (vo + i) * 3)\n v.fromBufferAttribute(gn, i).applyMatrix3(n3).normalize()\n nor.set([v.x, v.y, v.z], (vo + i) * 3)\n col.set([gc.getX(i), gc.getY(i), gc.getZ(i)], (vo + i) * 3)\n uv.set([gu.getX(i), gu.getY(i)], (vo + i) * 2)\n }\n for (const [k, values] of Object.entries(p.extra)) extra[k].set(values, vo * extraSizes[k])\n const gi = g.index!\n for (let i = 0; i < gi.count; i++) index[io + i] = gi.getX(i) + vo\n vo += count\n io += gi.count\n }\n const out = new THREE.BufferGeometry()\n out.setAttribute('position', new THREE.BufferAttribute(pos, 3))\n out.setAttribute('normal', new THREE.BufferAttribute(nor, 3))\n out.setAttribute('color', new THREE.BufferAttribute(col, 3))\n out.setAttribute('uv', new THREE.BufferAttribute(uv, 2))\n for (const [k, n] of Object.entries(extraSizes)) out.setAttribute(k, new THREE.BufferAttribute(extra[k], n))\n out.setIndex(new THREE.BufferAttribute(verts > 65535 ? index : new Uint16Array(index), 1))\n out.computeBoundingSphere()\n out.computeBoundingBox()\n return out\n}\n\nfunction meshesOf(group: ThreeNS.Object3D): ThreeNS.Mesh[] {\n return group.children.filter((c): c is ThreeNS.Mesh => (c as ThreeNS.Mesh).isMesh)\n}\n\n/**\n * Merges parts after `finish()` to cut draw calls (the `mergeParts` option of `createMeshApi`):\n *\n * - parts of one `PartSpec.group` with the same motion (blind slats) become one mesh per material whose vertices carry their own pivot;\n * `syncObject` poses them with one set of uniforms and the shadow materials follow;\n * - `cloth` soft volumes (clothes on a line) become one mesh per material with a per-vertex cloth index and anchor height; each\n * cloth keeps its own swing.\n *\n * The merged group is added to `root`, the part groups are removed, and `parts[id]` points at the merged group for every member.\n * Parts that carry a mirror, that are turned (`ry`) or that stand alone are left as they are.\n */\nexport function mergeParts(THREE: ThreeLib, root: ThreeNS.Group, parts: Record<string, ThreeNS.Group>, spec: ObjectSpec, options: MergeOptions = {}): void {\n const amplitude = options.swayAmplitude ?? 0.04\n const skip = new Set<string>()\n for (const m of spec.mirrors ?? []) if (m.part) skip.add(m.part)\n for (const [id, g] of Object.entries(parts)) if (Math.abs(g.rotation.y) > 1e-9 || g.userData.merged) skip.add(id)\n\n const take = (ids: string[]): ThreeNS.Group[] => ids.map((id) => parts[id])\n const retire = (ids: string[], merged: ThreeNS.Group): void => {\n for (const id of ids) {\n const g = parts[id]\n g.removeFromParent()\n for (const m of meshesOf(g)) m.geometry.dispose()\n parts[id] = merged\n }\n root.add(merged)\n }\n const byMaterial = (groups: ThreeNS.Group[], extra: (g: ThreeNS.Group, count: number) => Record<string, number[]>) => {\n const map = new Map<string, { base: ThreeNS.Material; pieces: Piece[]; mesh: ThreeNS.Mesh }>()\n for (const g of groups) {\n g.updateMatrix()\n for (const mesh of meshesOf(g)) {\n const key = `${String(mesh.userData.material)}|${(mesh.material as ThreeNS.Material).uuid}`\n let e = map.get(key)\n if (!e) {\n e = { base: mesh.material as ThreeNS.Material, pieces: [], mesh }\n map.set(key, e)\n }\n e.pieces.push({ geometry: mesh.geometry, matrix: g.matrix, extra: extra(g, mesh.geometry.getAttribute('position').count) })\n }\n }\n return map\n }\n const finishMesh = (name: string, key: string, geometry: ThreeNS.BufferGeometry, template: ThreeNS.Mesh, set: ReturnType<typeof patchedSet>, group: ThreeNS.Group): void => {\n const mesh = new THREE.Mesh(geometry, set.material)\n mesh.name = `${name}:${key}`\n mesh.userData.material = key\n mesh.castShadow = template.castShadow\n mesh.receiveShadow = template.receiveShadow\n mesh.customDepthMaterial = set.depth\n mesh.customDistanceMaterial = set.distance\n group.add(mesh)\n }\n\n // ---- cloth: one swing per soft volume, merged per material -------------------------------------------------------------------\n const cloths = (spec.soft ?? []).filter((s) => s.kind === 'cloth' && parts[s.part ?? s.id] && !skip.has(s.part ?? s.id))\n const clothPart = (s: { id: string; part?: string }) => s.part ?? s.id\n const toggled = new Set<string>()\n for (const i of spec.interactions ?? []) {\n const a = i.action as { do: string; part?: string }\n if (a.do === 'toggle_part' && a.part) toggled.add(a.part)\n }\n const swing = cloths.filter((s) => !toggled.has(clothPart(s)))\n if (swing.length >= 2) {\n const ids = swing.map(clothPart)\n const index: Record<string, number> = {}\n swing.forEach((s, i) => (index[s.id] = i))\n const indexOfPart = new Map(swing.map((s, i) => [clothPart(s), i]))\n const anchorOfPart = new Map(swing.map((s) => [clothPart(s), s.anchor[1]]))\n const n = swing.length\n const uniforms = { time: { value: 0 }, axz: { value: Array.from({ length: n }, () => new THREE.Vector2()) } }\n const groups = take(ids)\n const partIdOf = new Map(groups.map((g, i) => [g, ids[i]]))\n const plan = byMaterial(groups, (g, count) => {\n const id = partIdOf.get(g)!\n return { swayId: new Array(count).fill(indexOf(indexOfPart, id)), swayAnchor: new Array(count).fill(anchorOfPart.get(id) ?? 0) }\n })\n const merged = new THREE.Group() as unknown as MergedGroup\n merged.name = 'merge:cloth'\n const patch = (shader: { uniforms: Record<string, ThreeNS.IUniform>; vertexShader: string }): void => {\n shader.uniforms.swayTime = uniforms.time\n shader.uniforms.swayAxz = uniforms.axz\n shader.vertexShader =\n `attribute float swayId;\\nattribute float swayAnchor;\\nuniform float swayTime;\\nuniform vec2 swayAxz[${n}];\\n` +\n shader.vertexShader.replace(\n '#include <begin_vertex>',\n `#include <begin_vertex>\n{\n vec2 swayA = swayAxz[int(swayId + 0.5)];\n float swayHang = max(swayAnchor - position.y, 0.0);\n float swayFlutter = ${amplitude.toFixed(4)} * sin(swayTime * 1.9 + swayId * 1.7 + position.x * 4.0 + position.z * 3.0 + swayHang * 3.0);\n transformed.x += swayHang * (sin(swayA.y) + swayFlutter);\n transformed.z += swayHang * (-sin(swayA.x) + swayFlutter * 0.6);\n transformed.y += swayHang * (1.0 - cos(swayA.y) * cos(swayA.x));\n}`,\n )\n }\n for (const e of plan.values()) {\n const key = String(e.mesh.userData.material)\n const set = patchedSet(THREE, e.base, `swayBatch:${n}:${amplitude}`, patch)\n finishMesh('merge:cloth', key, concat(THREE, e.pieces, { swayId: 1, swayAnchor: 1 }), e.mesh, set, merged)\n }\n const batch: SwayBatch = {\n kind: 'sway',\n index,\n setAt(i, ax, az) {\n uniforms.axz.value[i].set(ax, az)\n },\n setTime(t) {\n uniforms.time.value = t\n },\n }\n merged.userData.merged = batch\n retire(ids, merged)\n for (const id of ids) skip.add(id)\n }\n\n // ---- grouped parts with identical motion (blind slats) -----------------------------------------------------------------------\n const bySignature = new Map<string, string[]>()\n for (const p of spec.parts ?? []) {\n if (!p.group || !parts[p.id] || skip.has(p.id)) continue\n const sig = `${p.group}|${JSON.stringify(p.motion)}`\n const list = bySignature.get(sig)\n if (list) list.push(p.id)\n else bySignature.set(sig, [p.id])\n }\n for (const [sig, ids] of bySignature) {\n if (ids.length < 2) continue\n const motion = (spec.parts ?? []).find((p) => p.id === ids[0])!.motion\n const uniforms = { q: { value: new THREE.Vector4(0, 0, 0, 1) }, pos: { value: new THREE.Vector3() }, scale: { value: new THREE.Vector3(1, 1, 1) } }\n const groups = take(ids)\n const plan = byMaterial(groups, (g, count) => {\n const pv = new Array<number>(count * 3)\n for (let i = 0; i < count; i++) {\n pv[i * 3] = g.position.x\n pv[i * 3 + 1] = g.position.y\n pv[i * 3 + 2] = g.position.z\n }\n return { batchPivot: pv }\n })\n const merged = new THREE.Group() as unknown as MergedGroup\n merged.name = `merge:${sig.split('|')[0]}`\n const patch = (shader: { uniforms: Record<string, ThreeNS.IUniform>; vertexShader: string }): void => {\n shader.uniforms.batchQ = uniforms.q\n shader.uniforms.batchPos = uniforms.pos\n shader.uniforms.batchScale = uniforms.scale\n let src = `attribute vec3 batchPivot;\\nuniform vec4 batchQ;\\nuniform vec3 batchPos;\\nuniform vec3 batchScale;\\n${QROT}${shader.vertexShader}`\n src = src.replace('#include <begin_vertex>', '#include <begin_vertex>\\ntransformed = batchPivot + batchRotate(batchQ, (position - batchPivot) * batchScale) + batchPos;')\n src = src.replace('#include <beginnormal_vertex>', '#include <beginnormal_vertex>\\nobjectNormal = batchRotate(batchQ, objectNormal);')\n shader.vertexShader = src\n }\n for (const e of plan.values()) {\n const key = String(e.mesh.userData.material)\n const set = patchedSet(THREE, e.base, 'poseBatch', patch)\n finishMesh(merged.name, key, concat(THREE, e.pieces, { batchPivot: 3 }), e.mesh, set, merged)\n }\n const batch: PoseBatch = {\n kind: 'pose',\n ids,\n apply(m, t) {\n const pose = poseOf(m, t)\n uniforms.q.value.set(pose.quaternion[0], pose.quaternion[1], pose.quaternion[2], pose.quaternion[3])\n uniforms.pos.value.set(pose.position[0], pose.position[1], pose.position[2])\n uniforms.scale.value.set(pose.scale[0], pose.scale[1], pose.scale[2])\n },\n }\n batch.apply(motion, 0)\n merged.userData.merged = batch\n retire(ids, merged)\n }\n}\n\nfunction indexOf(map: Map<string, number>, id: string): number {\n return map.get(id) ?? 0\n}\n","import { markKitOrigin } from '@adatechnology/game-contracts'\nimport { LIGHT_PRIORITY } from '@adatechnology/game-render-three'\nimport type { LightBudget, SourceHandle } from '@adatechnology/game-render-three'\nimport type { ObjectSpec } from '@adatechnology/game-interactables'\nimport { createShapeRecorder } from '@adatechnology/game-props3d'\nimport { colorChannels, generate } from '@adatechnology/game-shapes'\nimport type { ShapeBatch, ShapePoint } from '@adatechnology/game-shapes'\nimport type { HouseholdApi, MaterialKey, Quality, ThreeLib, ThreeNS } from './types'\nimport { MATERIAL_KEYS } from './types'\nimport { mergeParts } from './merge'\nimport { rememberRest } from './pose'\n\nexport type MaterialMap = Partial<Record<MaterialKey, ThreeNS.Material>>\n\nexport interface MeshApiOptions {\n /** Materials per key. Missing keys get a default (vertex-colored) material. */\n materials?: MaterialMap\n /** `high`: smoother cylinders and spheres, standard materials. `game`: fewer segments, Lambert materials. Default `high`. */\n quality?: Quality\n /** Cast and receive shadows on the merged meshes. Default true. */\n shadows?: boolean\n /** Make `plane` primitives double sided (applies to the default materials only). Default false. */\n doubleSidedPlanes?: boolean\n /**\n * Cut draw calls: `finish(spec)` merges the parts of one `PartSpec.group` that share a motion (blind slats) and the `cloth` soft\n * volumes (clothes on a line) into one mesh per material (see `mergeParts`). Needs the spec in `finish`. Default: on in `game` quality.\n */\n mergeParts?: boolean\n /** Idle flutter of merged cloth. Default 0.04. */\n swayAmplitude?: number\n /**\n * Material of one draw: called per mesh with its material key and the part id (`null` for the static body). Return the material the\n * host wants there (a per-room light mask, a flickering emissive...); the default is `materials[key]`. Merged groups (blind slats,\n * clothes) keep what you return, including its `onBeforeCompile`, `customProgramCacheKey`, `defines` and `userData`.\n */\n materialFor?: (key: MaterialKey, partId: string | null) => ThreeNS.Material | undefined\n /**\n * The host's light budget: each `api.light` becomes a source of it (it competes for a slot by distance, and following its bulb) instead of a `PointLight` of its own, so lamps coming and going\n * never change the lit materials' program key. Without it the lamps are real lights (deprecated, removed in the next major).\n */\n lights?: LightBudget\n}\n\nexport interface MeshResult {\n /** Root group: static meshes (one per material) plus one child group per part (merged parts share one group, see `mergeParts`). */\n group: ThreeNS.Group\n /** Part groups by id. Origin at the pivot, rotated by the part's `ry`; `userData.rest` holds the rest pose. A merged part maps to the shared group (`userData.merged`). */\n parts: Record<string, ThreeNS.Group>\n /** Real lights requested by the builders (already children of their group). Empty with `MeshApiOptions.lights`. */\n lights: ThreeNS.Light[]\n /** With `MeshApiOptions.lights`: the lamps as sources of the budget (`remove()` one when its lamp breaks or the object goes). Empty without it. */\n sources: SourceHandle[]\n}\n\nexport interface FinishOptions {\n /** Same as `MeshApiOptions.materialFor`; wins over it for this call. */\n materialFor?: MeshApiOptions['materialFor']\n}\n\nexport interface MeshApi {\n api: HouseholdApi\n /** Builds the groups from everything drawn so far. Call once; the api is spent afterwards. Pass the spec the pack returned to allow `mergeParts`. */\n finish(spec?: ObjectSpec, finishOptions?: FinishOptions): MeshResult\n /** Materials in use (defaults merged with the given ones). */\n materials: Record<MaterialKey, ThreeNS.Material>\n}\n\n/** Default materials: vertex colors everywhere (in `game` quality plain, metal, wood and fabric are one shared Lambert material), glass and water transparent, emissive unlit. */\nexport function createDefaultMaterials(THREE: ThreeLib, quality: Quality = 'high'): Record<MaterialKey, ThreeNS.Material> {\n // three warns about every parameter that is present but undefined: only pass the ones that are set\n const defined = <T extends object>(o: T): T => {\n const out = {} as T\n for (const k of Object.keys(o) as Array<keyof T>) if (o[k] !== undefined) out[k] = o[k]\n return out\n }\n const std = (p: ThreeNS.MeshStandardMaterialParameters): ThreeNS.Material =>\n quality === 'game'\n ? new THREE.MeshLambertMaterial(defined({ vertexColors: true, transparent: p.transparent, opacity: p.opacity, depthWrite: p.depthWrite, side: p.side }))\n : new THREE.MeshStandardMaterial(defined({ vertexColors: true, roughness: 0.85, metalness: 0, ...p }))\n // Lambert has no roughness or metalness: in `game` quality the opaque default materials are one and the same instance, so the\n // mesh api (which merges per material instance) draws plain, metal, wood and fabric together\n const solid = quality === 'game' ? std({}) : null\n return {\n plain: solid ?? std({}),\n metal: solid ?? std({ roughness: 0.35, metalness: 0.7 }),\n wood: solid ?? std({ roughness: 0.8 }),\n glass: std({ roughness: 0.05, transparent: true, opacity: 0.35, depthWrite: false }),\n fabric: solid ?? std({ roughness: 1 }),\n plant: std({ roughness: 0.9, side: THREE.DoubleSide }),\n water: std({ roughness: 0.1, transparent: true, opacity: 0.7, depthWrite: false }),\n emissive: new THREE.MeshBasicMaterial({ vertexColors: true }),\n atlas_picture: std({ roughness: 0.9 }),\n atlas_floor: std({ roughness: 1 }),\n }\n}\n\n/** The channels three stores for a hex here: linear (three r152+ with colour management on) or the raw sRGB numbers (off, or an older three). */\nconst colorSpaces = new WeakMap<object, 'linear' | 'srgb'>()\nfunction colorSpaceOf(THREE: ThreeLib): 'linear' | 'srgb' {\n const known = colorSpaces.get(THREE.Color)\n if (known) return known\n const space = detectColorSpace(THREE)\n colorSpaces.set(THREE.Color, space)\n return space\n}\nfunction detectColorSpace(THREE: ThreeLib): 'linear' | 'srgb' {\n const color = new THREE.Color()\n for (let c = 0; c < 256; c++) {\n color.setHex(c * 0x010101)\n if (color.r !== colorChannels(c * 0x010101, 'linear')[0]) {\n if (color.r !== c / 255) throw new Error('createMeshApi: this three.js stores colours in a space game-shapes does not know')\n return 'srgb'\n }\n }\n return 'linear'\n}\n\n/**\n * Mesh api for content packs. Every primitive is baked (vertex colors) into one `BufferGeometry` per material and per target\n * (the root, or a part), so the draw calls are the number of materials in use plus the same again for each part.\n *\n * The builders draw into a `game-shapes` schema (`createShapeRecorder` of game-props3d: unit templates, the item frame, parts and lights) and\n * `generate` bakes it; the bytes are the ones this api made with its own templates before (tests/exact-golden.test.ts).\n */\nexport function createMeshApi(THREE: ThreeLib, options: MeshApiOptions = {}): MeshApi {\n const quality: Quality = options.quality ?? 'high'\n const shadows = options.shadows !== false\n const materials = { ...createDefaultMaterials(THREE, quality), ...stripUndefined(options.materials) } as Record<MaterialKey, ThreeNS.Material>\n if (options.doubleSidedPlanes) for (const k of MATERIAL_KEYS) materials[k].side = THREE.DoubleSide\n let finished = false\n\n const recorder = quality === 'high' ? createShapeRecorder({ cylinderSegments: 12, sphereSegments: [12, 8] }) : createShapeRecorder({ cylinderSegments: 8, sphereSegments: [8, 6] })\n\n /** Keys that share a material instance share the geometry too: the first key stands for all of them. */\n const materialAlias: Record<string, string> = {}\n for (const k of MATERIAL_KEYS) {\n const first = MATERIAL_KEYS.find((o) => materials[o] === materials[k]) ?? k\n if (first !== k) materialAlias[k] = first\n }\n\n const lightsOut: ThreeNS.Light[] = []\n const sourcesOut: SourceHandle[] = []\n\n const build = (batches: readonly ShapeBatch[], points: readonly ShapePoint[], id: string | null, into: ThreeNS.Group, materialFor: MeshApiOptions['materialFor']): void => {\n for (const batch of batches) {\n const g = new THREE.BufferGeometry()\n g.setAttribute('position', new THREE.BufferAttribute(batch.positions, 3))\n g.setAttribute('normal', new THREE.BufferAttribute(batch.normals, 3))\n g.setAttribute('color', new THREE.BufferAttribute(batch.colors as Float32Array, 3))\n g.setAttribute('uv', new THREE.BufferAttribute(batch.uvs, 2))\n g.setIndex(new THREE.BufferAttribute(batch.indices as Uint16Array | Uint32Array, 1))\n g.computeBoundingSphere()\n g.computeBoundingBox()\n const key = batch.material as MaterialKey\n const mesh = new THREE.Mesh(g, materialFor?.(key, id) ?? materials[key])\n mesh.name = id === null ? `static:${key}` : `part:${id}`\n if (id !== null) mesh.userData.part = id\n mesh.userData.material = key\n mesh.castShadow = shadows\n mesh.receiveShadow = shadows\n into.add(mesh)\n }\n for (const point of points) {\n const l = point.data as { color: number; range: number }\n if (options.lights) {\n const bulb = new THREE.Object3D()\n bulb.name = 'lamp'\n bulb.position.set(point.position[0], point.position[1], point.position[2])\n into.add(bulb)\n sourcesOut.push(options.lights.addSource({ owner: 'interactables.lamp', priority: LIGHT_PRIORITY.lamp, color: l.color, intensity: 1, distance: l.range, position: [0, 0, 0], follow: bulb }))\n continue\n }\n // eslint-disable-next-line no-restricted-syntax -- D2: legacy path without LightBudget, removed in the next major\n const light = new THREE.PointLight(l.color, 1, l.range)\n light.position.set(point.position[0], point.position[1], point.position[2])\n into.add(light)\n lightsOut.push(light)\n }\n }\n\n return {\n api: recorder.api,\n materials,\n finish(spec, finishOptions) {\n const materialFor = finishOptions?.materialFor ?? options.materialFor\n if (finished) throw new Error('createMeshApi: finish() was already called')\n finished = true\n const buffers = generate(recorder.schema(), {\n colors: colorSpaceOf(THREE),\n materials: MATERIAL_KEYS,\n fallbackMaterial: 'plain',\n materialAlias,\n validate: false,\n key: false,\n })\n const batches = buffers.levels[0]?.batches ?? []\n const of = (id: string | null): { batches: ShapeBatch[]; points: ShapePoint[] } => ({ batches: batches.filter((b) => b.part === id), points: buffers.points.filter((p) => p.part === id) })\n const group = markKitOrigin(new THREE.Group(), { owner: 'game-interactables-three', kind: 'furniture', ...(spec ? { id: spec.type } : {}) })\n group.name = 'interactable'\n const parts: Record<string, ThreeNS.Group> = {}\n const body = of(null)\n build(body.batches, body.points, null, group, materialFor)\n for (const part of recorder.parts) {\n const g = new THREE.Group()\n g.name = `part:${part.id}`\n g.userData.part = part.id\n g.position.set(part.pivot[0], part.pivot[1], part.pivot[2])\n g.rotation.y = part.ry\n g.updateMatrix()\n const drawn = of(part.id)\n build(drawn.batches, drawn.points, part.id, g, materialFor)\n rememberRest(g)\n group.add(g)\n parts[part.id] = g\n }\n if (spec && (options.mergeParts ?? quality === 'game')) mergeParts(THREE, group, parts, spec, { swayAmplitude: options.swayAmplitude })\n if (spec) tagDestruction(group, parts, spec)\n return { group, parts, lights: lightsOut, sources: sourcesOut }\n },\n }\n}\n\n/**\n * Copies the spec's destruction tags onto the objects: `userData.destructionMaterial` (SDK material id) and, on part groups,\n * `userData.destructionPart` (the part id, the key @adatechnology/game-destruction-three binds by), `userData.destructionPiece` and\n * `userData.destructionJoint`. Static meshes get the object default; the meshes of a part get the part's tags.\n * Parts merged into a shared group (blind slats) carry the tags of the first part of the group.\n */\nfunction tagDestruction(group: ThreeNS.Group, parts: Record<string, ThreeNS.Group>, spec: ObjectSpec): void {\n const body = spec.destruction\n const partGroups = new Set<ThreeNS.Object3D>(Object.values(parts))\n if (body) {\n for (const child of group.children) {\n if (!partGroups.has(child) && (child as ThreeNS.Mesh).isMesh) child.userData.destructionMaterial = body.material\n }\n }\n const done = new Set<ThreeNS.Object3D>()\n for (const ps of spec.parts ?? []) {\n const g = parts[ps.id]\n const d = ps.destruction\n if (!g || !d || done.has(g)) continue\n done.add(g)\n for (const o of [g, ...g.children]) {\n if (o !== g && !(o as ThreeNS.Mesh).isMesh) continue\n o.userData.destructionMaterial = d.material\n o.userData.destructionPart = ps.id\n if (d.piece) o.userData.destructionPiece = d.piece\n if (d.joint) o.userData.destructionJoint = d.joint\n }\n }\n}\n\nfunction stripUndefined<T extends object>(o: T | undefined): Partial<T> {\n const out: Partial<T> = {}\n if (o) for (const k of Object.keys(o) as Array<keyof T>) if (o[k] !== undefined) out[k] = o[k]\n return out\n}\n","import type { ThreeLib, ThreeNS, Vec3 } from './types'\n\n/** `Reflector` from `three/examples/jsm/objects/Reflector.js`, passed by the host. */\nexport type ReflectorConstructor = new (geometry: ThreeNS.BufferGeometry, options?: { textureWidth?: number; textureHeight?: number; clipBias?: number; color?: ThreeNS.ColorRepresentation }) => ThreeNS.Mesh\n\nexport interface MirrorOptions {\n /** Without it every mirror shows the env map only. */\n Reflector?: ReflectorConstructor\n /** Cube / equirect env map for the cheap look. */\n envMap?: ThreeNS.Texture | null\n /** Real reflectors alive at once. Default 1. */\n max?: number\n /** Reflector texture size in pixels. Default 512. */\n resolution?: number\n /** Where reflectors are added. Default: the root ancestor of the registered mesh. */\n scene?: ThreeNS.Object3D\n /** Distance in front of the plane, meters. Default 0.001. */\n offset?: number\n /**\n * three's `Reflector` renders the scene again inside the main `render()`, and that nested render resets `renderer.info` when\n * `info.autoReset` is on, so the counters would only show the reflector pass. With `keepInfo` (default true) the reflector adds\n * what was counted before it back, so `renderer.info.render.calls` covers the whole frame. Skipped when `info.autoReset` is off.\n */\n keepInfo?: boolean\n}\n\n/** The mirror entries of `world.mirrors(...)` (only these fields are read). */\nexport interface MirrorCandidate {\n id: string\n center: Vec3\n normal: Vec3\n}\n\nexport interface Mirrors {\n /** Registers the display mesh of a mirror (world id `objectId:mirrorId`). It gets the env-map material. `normal` is the world normal; default: the mesh's +z. */\n register(id: string, mesh: ThreeNS.Mesh, normal?: Vec3): void\n unregister(id: string): void\n /** Activates real reflectors for the nearest facing registered mirrors among `candidates`. Returns the active ids. */\n update(camera: ThreeNS.Camera, candidates: readonly MirrorCandidate[]): string[]\n /** Ids that currently have a real reflector. */\n active(): string[]\n /** The shared env-map material registered meshes use. */\n envMaterial: ThreeNS.Material\n dispose(): void\n}\n\ninterface Entry {\n mesh: ThreeNS.Mesh\n normal: Vec3 | null\n original: ThreeNS.Material | ThreeNS.Material[]\n}\n\ninterface Slot {\n reflector: ThreeNS.Mesh\n id: string | null\n}\n\n/**\n * Mirrors: every registered mirror shows an env-map material; the nearest `max` that face the camera get a real `Reflector`\n * (pooled and reused), placed `offset` in front of the plane with the mesh's rotation and size.\n */\nexport function createMirrors(THREE: ThreeLib, options: MirrorOptions = {}): Mirrors {\n const max = Math.max(0, Math.floor(options.max ?? 1))\n const resolution = options.resolution ?? 512\n const offset = options.offset ?? 0.001\n const envMaterial = new THREE.MeshStandardMaterial({ color: 0xdfe6ea, metalness: 1, roughness: 0.04, envMap: options.envMap ?? null })\n const entries = new Map<string, Entry>()\n const slots: Slot[] = []\n const geometry = new THREE.PlaneGeometry(1, 1)\n\n const worldNormal = (e: Entry, out: ThreeNS.Vector3): ThreeNS.Vector3 => {\n if (e.normal) return out.set(e.normal[0], e.normal[1], e.normal[2]).normalize()\n e.mesh.updateWorldMatrix(true, false)\n return out.set(0, 0, 1).transformDirection(e.mesh.matrixWorld)\n }\n\n const slotFor = (id: string): Slot | null => {\n if (!options.Reflector) return null\n const existing = slots.find((s) => s.id === id)\n if (existing) return existing\n const free = slots.find((s) => s.id === null)\n if (free) return free\n if (slots.length >= max) return null\n const reflector = new options.Reflector(geometry, { textureWidth: resolution, textureHeight: resolution, clipBias: 0.003 })\n reflector.name = 'mirror:reflector'\n if (options.keepInfo !== false) {\n const original = reflector.onBeforeRender\n reflector.onBeforeRender = function (this: ThreeNS.Mesh, renderer, scene, camera, geo, mat, grp) {\n const info = renderer.info\n if (!info.autoReset) return original.call(this, renderer, scene, camera, geo, mat, grp)\n const before = { calls: info.render.calls, triangles: info.render.triangles, points: info.render.points, lines: info.render.lines }\n original.call(this, renderer, scene, camera, geo, mat, grp)\n info.render.calls += before.calls\n info.render.triangles += before.triangles\n info.render.points += before.points\n info.render.lines += before.lines\n }\n }\n reflector.visible = false\n const s: Slot = { reflector, id: null }\n slots.push(s)\n return s\n }\n\n const release = (s: Slot): void => {\n s.id = null\n s.reflector.visible = false\n }\n\n const pos = new THREE.Vector3()\n const quat = new THREE.Quaternion()\n const scl = new THREE.Vector3()\n const n = new THREE.Vector3()\n const toEye = new THREE.Vector3()\n const box = new THREE.Box3()\n const size = new THREE.Vector3()\n const facing = new THREE.Vector3()\n const flip = new THREE.Quaternion(0, 1, 0, 0)\n\n return {\n envMaterial,\n register(id, mesh, normal) {\n const previous = entries.get(id)\n entries.set(id, { mesh, normal: normal ?? null, original: previous ? previous.original : mesh.material })\n mesh.material = envMaterial\n },\n unregister(id) {\n const e = entries.get(id)\n const s = slots.find((x) => x.id === id)\n if (s) release(s)\n if (e) e.mesh.material = e.original\n entries.delete(id)\n },\n update(camera, candidates) {\n const eye = camera.getWorldPosition(toEye)\n const wanted: Array<{ id: string; d: number }> = []\n for (const c of candidates) {\n const e = entries.get(c.id)\n if (!e) continue\n n.set(c.normal[0], c.normal[1], c.normal[2])\n const dx = eye.x - c.center[0]\n const dy = eye.y - c.center[1]\n const dz = eye.z - c.center[2]\n if (n.x * dx + n.y * dy + n.z * dz <= 0) continue\n wanted.push({ id: c.id, d: dx * dx + dy * dy + dz * dz })\n }\n wanted.sort((a, b) => a.d - b.d)\n const chosen = new Set<string>()\n for (let i = 0; i < wanted.length && chosen.size < max; i++) chosen.add(wanted[i].id)\n for (const s of slots) if (s.id !== null && !chosen.has(s.id)) release(s)\n for (const id of chosen) {\n const s = slotFor(id)\n if (!s) continue\n const e = entries.get(id)!\n e.mesh.updateWorldMatrix(true, false)\n e.mesh.matrixWorld.decompose(pos, quat, scl)\n if (!e.mesh.geometry.boundingBox) e.mesh.geometry.computeBoundingBox()\n box.copy(e.mesh.geometry.boundingBox!)\n box.getSize(size)\n worldNormal(e, n)\n s.reflector.position.copy(pos).addScaledVector(n, offset)\n s.reflector.quaternion.copy(quat)\n // a Reflector only renders for a camera in front of its +z: when the registered normal points the other way, turn it around\n if (facing.set(0, 0, 1).applyQuaternion(quat).dot(n) < 0) s.reflector.quaternion.multiply(flip)\n s.reflector.scale.set(Math.max(size.x * scl.x, 1e-3), Math.max(size.y * scl.y, 1e-3), 1)\n s.id = id\n s.reflector.visible = true\n if (!s.reflector.parent) {\n let top: ThreeNS.Object3D = e.mesh\n while (top.parent) top = top.parent\n ;(options.scene ?? top).add(s.reflector)\n }\n }\n return slots.filter((s) => s.id !== null).map((s) => s.id as string)\n },\n active() {\n return slots.filter((s) => s.id !== null).map((s) => s.id as string)\n },\n dispose() {\n for (const s of slots) s.reflector.removeFromParent()\n slots.length = 0\n for (const e of entries.values()) e.mesh.material = e.original\n entries.clear()\n envMaterial.dispose()\n geometry.dispose()\n },\n }\n}\n","import type { CanvasFactory, ThreeLib, ThreeNS } from './types'\n\nexport interface ScreenOptions {\n /** Where the canvas is created (the global `document`, or a fake). */\n document: CanvasFactory\n /** Canvas size in pixels. Default 128 x 72. */\n w?: number\n h?: number\n /** Seed of the static noise. Default 1. */\n seed?: number\n /** Redraws per second while on. Default 15. */\n fps?: number\n /** Emissive strength while on. Default 1. */\n intensity?: number\n}\n\nexport interface Screen {\n material: ThreeNS.MeshStandardMaterial\n texture: ThreeNS.CanvasTexture\n canvas: HTMLCanvasElement\n readonly on: boolean\n readonly channel: number\n setOn(on: boolean): void\n setChannel(channel: number): void\n /** Advance the animation; redraws the canvas at `fps` while on. */\n update(dt: number): void\n dispose(): void\n}\n\nconst BARS = ['#c0c0c0', '#c0c000', '#00c0c0', '#00c000', '#c000c0', '#c00000', '#0000c0']\n\nfunction mulberry(seed: number): () => number {\n let a = seed >>> 0\n return () => {\n a = (a + 0x6d2b79f5) >>> 0\n let t = a\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/**\n * A screen material backed by a canvas texture. Off: black canvas, no emission. On: the channel's content (0 color bars,\n * 1 static noise, 2 animated landscape, higher numbers repeat them with shifted hues) lights the surface.\n */\nexport function createScreen(THREE: ThreeLib, options: ScreenOptions): Screen {\n const w = options.w ?? 128\n const h = options.h ?? 72\n const canvas = options.document.createElement('canvas')\n canvas.width = w\n canvas.height = h\n // the canvas is uploaded to the GPU every redraw: tell the browser so it keeps a CPU-backed canvas (no readback warning)\n const ctx = canvas.getContext('2d', { willReadFrequently: true })\n const rand = mulberry(options.seed ?? 1)\n const period = 1 / Math.max(1, options.fps ?? 15)\n const intensity = options.intensity ?? 1\n\n const texture = new THREE.CanvasTexture(canvas)\n texture.colorSpace = THREE.SRGBColorSpace\n const material = new THREE.MeshStandardMaterial({\n color: 0x050505,\n roughness: 0.25,\n metalness: 0,\n map: texture,\n emissive: 0xffffff,\n emissiveMap: texture,\n emissiveIntensity: 0,\n })\n\n let on = false\n let channel = 0\n let clock = 0\n let acc = 0\n\n const fill = (style: string, x: number, y: number, rw: number, rh: number): void => {\n if (!ctx) return\n ctx.fillStyle = style\n ctx.fillRect(x, y, rw, rh)\n }\n\n const drawBars = (): void => {\n const n = BARS.length\n for (let i = 0; i < n; i++) fill(BARS[(i + channel) % n], Math.floor((i * w) / n), 0, Math.ceil(w / n), Math.floor(h * 0.75))\n fill('#101010', 0, Math.floor(h * 0.75), w, h - Math.floor(h * 0.75))\n const x = ((clock * 20) % (w + 12)) - 12\n fill('#ffffff', x, Math.floor(h * 0.8), 12, Math.floor(h * 0.1))\n }\n\n const drawNoise = (): void => {\n const cell = Math.max(2, Math.floor(w / 48))\n for (let y = 0; y < h; y += cell) {\n for (let x = 0; x < w; x += cell) {\n const g = Math.floor(rand() * 220 + 20)\n fill(`rgb(${g},${g},${g})`, x, y, cell, cell)\n }\n }\n }\n\n const drawScene = (): void => {\n const hue = (channel * 40) % 360\n fill(`hsl(${(200 + hue) % 360},60%,${55 + 10 * Math.sin(clock * 0.3)}%)`, 0, 0, w, Math.floor(h * 0.65))\n fill(`hsl(${(110 + hue) % 360},40%,32%)`, 0, Math.floor(h * 0.65), w, h - Math.floor(h * 0.65))\n const sx = (0.5 + 0.4 * Math.sin(clock * 0.5)) * w\n const sy = (0.3 - 0.15 * Math.cos(clock * 0.5)) * h\n fill('#ffe680', sx - 5, sy - 5, 10, 10)\n const bx = (clock * 25) % (w + 16) - 16\n fill('#e03030', bx, Math.floor(h * 0.7), 16, 10)\n }\n\n const draw = (): void => {\n if (!on) {\n fill('#000000', 0, 0, w, h)\n } else if (channel % 3 === 0) {\n drawBars()\n } else if (channel % 3 === 1) {\n drawNoise()\n } else {\n drawScene()\n }\n texture.needsUpdate = true\n }\n\n const apply = (): void => {\n material.emissiveIntensity = on ? intensity : 0\n material.color.setHex(on ? 0x111111 : 0x000000)\n draw()\n }\n apply()\n\n return {\n material,\n texture,\n canvas,\n get on() {\n return on\n },\n get channel() {\n return channel\n },\n setOn(value) {\n on = !!value\n acc = 0\n apply()\n },\n setChannel(n) {\n channel = Math.max(0, Math.floor(n))\n if (on) draw()\n },\n update(dt) {\n if (!on) return\n clock += dt\n acc += dt\n if (acc >= period) {\n acc %= period\n draw()\n }\n },\n dispose() {\n texture.dispose()\n material.dispose()\n },\n }\n}\n","import type { ThreeLib, ThreeNS } from './types'\n\nexport interface SwayOptions {\n /** Strength of the idle flutter (wind) in meters per meter of hang, 0..1. Default 0.04. */\n amplitude?: number\n /** Local y (geometry space) of the line the cloth hangs from. Default: top of the mesh's bounding box. */\n anchorY?: number\n}\n\nexport interface SwayUniforms {\n time: { value: number }\n ax: { value: number }\n az: { value: number }\n anchorY: { value: number }\n}\n\n/** Shared state of one swaying mesh: the three materials read the same uniforms. */\nexport interface SwayHandle {\n uniforms: SwayUniforms\n /** Sets the swing (radians around x and z) and, optionally, the clock for the idle flutter. */\n set(ax: number, az: number, time?: number): void\n}\n\nconst VERTEX_PATCH = /* glsl */ `\n#include <begin_vertex>\n{\n float swayHang = max(swayAnchorY - position.y, 0.0);\n float swayFlutter = swayAmplitude * sin(swayTime * 1.9 + position.x * 4.0 + position.z * 3.0 + swayHang * 3.0);\n transformed.x += swayHang * (sin(swayAz) + swayFlutter);\n transformed.z += swayHang * (-sin(swayAx) + swayFlutter * 0.6);\n transformed.y += swayHang * (1.0 - cos(swayAz) * cos(swayAx));\n}\n`\n\nconst VERTEX_UNIFORMS = /* glsl */ `\nuniform float swayTime;\nuniform float swayAx;\nuniform float swayAz;\nuniform float swayAnchorY;\nuniform float swayAmplitude;\n`\n\n/** Patches a vertex shader so the vertices hang from the anchor line and swing with the shared uniforms. */\nexport function patchSwayShader(shader: { uniforms: Record<string, ThreeNS.IUniform>; vertexShader: string }, uniforms: SwayUniforms, amplitude: number): void {\n shader.uniforms.swayTime = uniforms.time\n shader.uniforms.swayAx = uniforms.ax\n shader.uniforms.swayAz = uniforms.az\n shader.uniforms.swayAnchorY = uniforms.anchorY\n shader.uniforms.swayAmplitude = { value: amplitude }\n shader.vertexShader = VERTEX_UNIFORMS + shader.vertexShader.replace('#include <begin_vertex>', VERTEX_PATCH)\n}\n\nfunction patch<M extends ThreeNS.Material>(material: M, uniforms: SwayUniforms, amplitude: number): M {\n const previous = material.onBeforeCompile\n material.onBeforeCompile = (shader, renderer) => {\n previous?.call(material, shader, renderer)\n patchSwayShader(shader, uniforms, amplitude)\n }\n material.customProgramCacheKey = () => `sway:${amplitude}`\n return material\n}\n\n/**\n * Clones `base` and patches it with a vertex-shader sway (uniforms `time`, `ax`, `az`, `anchorY`). The returned material carries\n * `userData.sway` (a `SwayHandle`), `customDepthMaterial` and `customDistanceMaterial` patched with the same uniforms, so\n * directional / spot shadow maps and point-light shadow cubes follow the animation. Assign it to a mesh, and set\n * `mesh.customDepthMaterial` / `mesh.customDistanceMaterial` from the material (or just use `attachSway`).\n */\n/** A material `swayMaterial` made: it carries its sway handle and the shadow materials that follow it. */\nexport type SwayMaterial = ThreeNS.Material & {\n userData: { sway: SwayHandle; depthMaterial: ThreeNS.MeshDepthMaterial; distanceMaterial: ThreeNS.MeshDistanceMaterial }\n}\n\nexport function swayMaterial(THREE: ThreeLib, base: ThreeNS.Material, options: SwayOptions = {}): SwayMaterial {\n const amplitude = options.amplitude ?? 0.04\n const uniforms: SwayUniforms = { time: { value: 0 }, ax: { value: 0 }, az: { value: 0 }, anchorY: { value: options.anchorY ?? 0 } }\n const handle: SwayHandle = {\n uniforms,\n set(ax, az, time) {\n uniforms.ax.value = ax\n uniforms.az.value = az\n if (time !== undefined) uniforms.time.value = time\n },\n }\n const material = patch(base.clone(), uniforms, amplitude)\n const depth = patch(new THREE.MeshDepthMaterial({ depthPacking: THREE.RGBADepthPacking }), uniforms, amplitude)\n const distance = patch(new THREE.MeshDistanceMaterial(), uniforms, amplitude)\n material.userData.sway = handle\n material.userData.depthMaterial = depth\n material.userData.distanceMaterial = distance\n return material as SwayMaterial // userData filled just above\n}\n\n/**\n * Makes a mesh sway: replaces its material with `swayMaterial(...)` and sets `customDepthMaterial` and `customDistanceMaterial`.\n * `mesh.userData.sway` is the handle `syncObject` drives. Returns the handle.\n */\nexport function attachSway(THREE: ThreeLib, mesh: ThreeNS.Mesh, options: SwayOptions = {}): SwayHandle {\n let anchorY = options.anchorY\n if (anchorY === undefined) {\n if (!mesh.geometry.boundingBox) mesh.geometry.computeBoundingBox()\n anchorY = mesh.geometry.boundingBox ? mesh.geometry.boundingBox.max.y : 0\n }\n const base = Array.isArray(mesh.material) ? mesh.material[0] : mesh.material\n const m = swayMaterial(THREE, base, { ...options, anchorY })\n m.userData.sway.uniforms.anchorY.value = anchorY\n mesh.material = m\n mesh.customDepthMaterial = m.userData.depthMaterial\n mesh.customDistanceMaterial = m.userData.distanceMaterial\n mesh.userData.sway = m.userData.sway\n return m.userData.sway\n}\n","import type { Motion, World } from '@adatechnology/game-interactables'\nimport type { LightBudget } from '@adatechnology/game-render-three'\nimport { createEffects, type Effects, type EffectsOptions } from './effects'\nimport { createCord, catenary } from '@adatechnology/game-shapes-three'\nimport { createMeshApi, type MeshApi, type MeshApiOptions } from './mesh'\nimport { createMirrors, type MirrorOptions, type Mirrors } from './mirrors'\nimport { applyPose, syncObject, type SyncOptions } from './pose'\nimport { createScreen, type Screen, type ScreenOptions } from './screen'\nimport { attachSway, swayMaterial, type SwayHandle, type SwayOptions } from './sway'\nimport type { CanvasFactory, Quality, ThreeLib, ThreeNS, Vec3 } from './types'\n\nexport interface ThreeAdapterOptions {\n /** Default for `createMeshApi` / `createScreen` / `createEffects`. */\n document?: CanvasFactory\n quality?: Quality\n materials?: MeshApiOptions['materials']\n /** `Reflector` and env map for `createMirrors`. */\n Reflector?: MirrorOptions['Reflector']\n envMap?: MirrorOptions['envMap']\n /** The host's light budget: the lamps of every mesh api this adapter makes become sources of it (see `MeshApiOptions.lights`). */\n lights?: LightBudget\n}\n\n/** The package functions with `THREE` and the common options already bound. */\nexport interface ThreeAdapter {\n THREE: ThreeLib\n createMeshApi(options?: MeshApiOptions): MeshApi\n applyPose<G extends ThreeNS.Object3D>(group: G, motion: Motion, t: number): G\n syncObject(world: World, objectId: string, parts: Record<string, ThreeNS.Object3D>, options?: SyncOptions): void\n swayMaterial(base: ThreeNS.Material, options?: SwayOptions): ReturnType<typeof swayMaterial>\n attachSway(mesh: ThreeNS.Mesh, options?: SwayOptions): SwayHandle\n createEffects(options: EffectsOptions): Effects\n /** @deprecated Pass `{ scene, camera, ... }`. */\n createEffects(scene: ThreeNS.Object3D, options?: Partial<EffectsOptions>): Effects\n createScreen(options?: Partial<ScreenOptions>): Screen\n createMirrors(options?: MirrorOptions): Mirrors\n createCord(points: readonly Vec3[], radius: number, material: ThreeNS.Material): ThreeNS.Mesh\n catenary: typeof catenary\n}\n\n/** Binds the host's `THREE` (and shared options) once, so the rest of the host code never passes it around. */\nexport function createThreeAdapter(THREE: ThreeLib, options: ThreeAdapterOptions = {}): ThreeAdapter {\n return {\n THREE,\n createMeshApi: (o = {}) => createMeshApi(THREE, { quality: options.quality, materials: options.materials, ...(options.lights ? { lights: options.lights } : {}), ...o }),\n applyPose: (group, motion, t) => applyPose(THREE, group, motion, t),\n syncObject: (world, objectId, parts, o) => syncObject(THREE, world, objectId, parts, o),\n swayMaterial: (base, o) => swayMaterial(THREE, base, o),\n attachSway: (mesh, o) => attachSway(THREE, mesh, o),\n createEffects: ((a: EffectsOptions | ThreeNS.Object3D, o: Partial<EffectsOptions> = {}) =>\n (a as ThreeNS.Object3D).isObject3D\n ? createEffects(THREE, a as ThreeNS.Object3D, { document: options.document, ...o })\n : createEffects(THREE, { document: options.document, ...(a as EffectsOptions) })) as ThreeAdapter['createEffects'],\n createScreen: (o = {}) => {\n const document = o.document ?? options.document\n if (!document) throw new Error('createScreen needs a document (option of createThreeAdapter or of the call)')\n return createScreen(THREE, { ...o, document })\n },\n createMirrors: (o = {}) => createMirrors(THREE, { Reflector: options.Reflector, envMap: options.envMap, ...o }),\n createCord: (points, radius, material) => createCord(THREE, points, radius, material),\n catenary,\n }\n}\n"],"mappings":"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","names":["poseOf","object","tex","a","b","mesh","n","color","p","width","height","depth","indexArray","sign","list","num","equals","area","ring","bs","layeroffset","rgb","color","c","mesh","hex","color","list","range","poseOf","mesh","patch","list","poseOf","color","mesh","s","box","mesh","mulberry","channel","mesh","mesh","document"]}