@scenography/engine 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1,5 +1,48 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  let three = require("three");
3
+ //#region src/loading.ts
4
+ const pending = /* @__PURE__ */ new Set();
5
+ const listeners = /* @__PURE__ */ new Set();
6
+ let state = {
7
+ loaded: 0,
8
+ total: 0,
9
+ done: true
10
+ };
11
+ const notify = () => {
12
+ for (const listener of listeners) listener(state);
13
+ };
14
+ /** Begin tracking a new initial asset set; resets the previous one. */
15
+ const startLoading = (urls) => {
16
+ pending.clear();
17
+ for (const url of urls) pending.add(url);
18
+ state = {
19
+ loaded: 0,
20
+ total: pending.size,
21
+ done: pending.size === 0
22
+ };
23
+ notify();
24
+ };
25
+ /** Mark one initial asset as loaded; unknown URLs (lazy assets) are ignored. */
26
+ const trackAsset = (url) => {
27
+ if (!pending.delete(url)) return;
28
+ state = {
29
+ loaded: state.total - pending.size,
30
+ total: state.total,
31
+ done: pending.size === 0
32
+ };
33
+ notify();
34
+ };
35
+ /** Current loading progress. */
36
+ const loadingState = () => state;
37
+ /** Subscribe to progress; the listener runs immediately with the current state. */
38
+ const onLoading = (listener) => {
39
+ listeners.add(listener);
40
+ listener(state);
41
+ return () => {
42
+ listeners.delete(listener);
43
+ };
44
+ };
45
+ //#endregion
3
46
  //#region src/environment/EnvironmentController.ts
4
47
  /** Load an equirectangular HDRI, picking the loader by extension. */
5
48
  const loadHDRI = (src, onLoad) => {
@@ -31,6 +74,7 @@ var EnvironmentController = class {
31
74
  this.clear();
32
75
  if (!env) return;
33
76
  loadHDRI(env.src, (tex) => {
77
+ trackAsset(env.src);
34
78
  if (this.hash !== hash) {
35
79
  tex.dispose();
36
80
  return;
@@ -140,7 +184,7 @@ var TextureCache = class {
140
184
  //#endregion
141
185
  //#region src/builders/texture.ts
142
186
  const textureLoader = new three.TextureLoader();
143
- const cache = new TextureCache((src) => textureLoader.load(src));
187
+ const cache = new TextureCache((src) => textureLoader.load(src, () => trackAsset(src)));
144
188
  const ktx2Entries = /* @__PURE__ */ new Map();
145
189
  const ktx2Owners = /* @__PURE__ */ new Map();
146
190
  /**
@@ -167,6 +211,7 @@ const loadKtx2 = (material, slot, src, repeat, srgb) => {
167
211
  loader.then((ktx2) => ktx2.load(src, (texture) => {
168
212
  configureTexture(texture, repeat, srgb);
169
213
  texture.needsUpdate = true;
214
+ trackAsset(src);
170
215
  created.texture = texture;
171
216
  ktx2Owners.set(texture, created);
172
217
  for (const assign of created.waiters) assign(texture);
@@ -198,6 +243,7 @@ const loadMap = (material, slot, src, repeat, srgb) => {
198
243
  new EXRLoader().load(src, (texture) => {
199
244
  configureTexture(texture, repeat, srgb);
200
245
  texture.needsUpdate = true;
246
+ trackAsset(src);
201
247
  material[slot] = texture;
202
248
  material.needsUpdate = true;
203
249
  });
@@ -877,6 +923,15 @@ function createGallery(scene, options) {
877
923
  const elementPlugins = allPlugins.filter((p) => !isSystemPlugin(p));
878
924
  const capabilities = new CapabilityRegistry();
879
925
  if (options.ktx2Path) configureKtx2(renderer, options.ktx2Path);
926
+ const assets = /* @__PURE__ */ new Set();
927
+ for (const plugin of elementPlugins) {
928
+ const { kind, cardinality } = plugin.data;
929
+ const raw = scene[kind];
930
+ const values = cardinality === "many" ? Array.isArray(raw) ? raw : [] : raw !== void 0 ? [raw] : [];
931
+ for (const value of values) for (const url of plugin.data.assets?.(value) ?? []) assets.add(url);
932
+ }
933
+ if (scene.environment?.src) assets.add(scene.environment.src);
934
+ startLoading(assets);
880
935
  const manager = new SceneManager(threeScene, elementPlugins, capabilities, {
881
936
  camera,
882
937
  renderer
@@ -1099,6 +1154,10 @@ function createGallery(scene, options) {
1099
1154
  renderer
1100
1155
  }
1101
1156
  },
1157
+ loading: {
1158
+ get: loadingState,
1159
+ subscribe: onLoading
1160
+ },
1102
1161
  setScene(next) {
1103
1162
  manager.reconcile(next);
1104
1163
  interaction.setRadii(radiiOf(next));
@@ -1198,5 +1257,6 @@ exports.createGallery = createGallery;
1198
1257
  exports.getKtx2Loader = getKtx2Loader;
1199
1258
  exports.isSystemPlugin = isSystemPlugin;
1200
1259
  exports.modeOf = modeOf;
1260
+ exports.trackAsset = trackAsset;
1201
1261
 
1202
1262
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["EquirectangularReflectionMapping","RepeatWrapping","SRGBColorSpace","NoColorSpace","TextureLoader","Vector2","Raycaster","Vector2","Group","Vector3","MeshBasicMaterial","Mesh","BoxGeometry","Quaternion","FLAG_KEYS","PerspectiveCamera","WebGLRenderer","PCFShadowMap","StereoCamera","Vector2","ThreeScene","PerspectiveCamera","Clock","WebGLRenderTarget","HalfFloatType"],"sources":["../src/environment/EnvironmentController.ts","../src/builders/ktx2.ts","../src/builders/textureCache.ts","../src/builders/texture.ts","../src/interaction/InteractionController.ts","../src/input/InputBus.ts","../src/input/controlSettings.ts","../src/input/controlStorage.ts","../src/plugin/CapabilityRegistry.ts","../src/plugin/systemPlugin.ts","../src/plugin/roamControl.ts","../src/plugin/terrain.ts","../src/plugin/colliders.ts","../src/plugin/edges.ts","../src/plugin/eyeHeight.ts","../src/SceneManager.ts","../src/createGallery.ts"],"sourcesContent":["import { EquirectangularReflectionMapping, type Scene as ThreeScene, type Texture } from \"three\";\nimport type { Environment } from \"@scenography/scene\";\n\n/** Load an equirectangular HDRI, picking the loader by extension. */\nconst loadHDRI = (src: string, onLoad: (tex: Texture) => void): void => {\n if (src.toLowerCase().endsWith(\".exr\")) {\n void import(\"three/examples/jsm/loaders/EXRLoader.js\").then(({ EXRLoader }) => {\n new EXRLoader().load(src, onLoad);\n });\n } else {\n void import(\"three/examples/jsm/loaders/RGBELoader.js\").then(({ RGBELoader }) => {\n new RGBELoader().load(src, onLoad);\n });\n }\n};\n\n/**\n * Owns the scene's HDRI sky / image-based lighting and keeps it in sync with the\n * `Environment` definition. Loads the equirectangular map on demand (its loader\n * ships in its own chunk) and assigns it to `scene.background` and/or\n * `scene.environment`. Reconciles by a JSON hash so hot edits only reload when\n * the definition actually changes.\n */\nexport class EnvironmentController {\n private readonly scene: ThreeScene;\n private hash: string | null = null;\n private texture: Texture | null = null;\n\n constructor(scene: ThreeScene) {\n this.scene = scene;\n }\n\n reconcile(env: Environment | undefined): void {\n const hash = env ? JSON.stringify(env) : null;\n if (hash === this.hash) return;\n this.hash = hash;\n this.clear();\n if (!env) return;\n\n loadHDRI(env.src, (tex) => {\n // A newer reconcile superseded this load before it finished.\n if (this.hash !== hash) {\n tex.dispose();\n return;\n }\n tex.mapping = EquirectangularReflectionMapping;\n this.texture = tex;\n if (env.background ?? true) {\n this.scene.background = tex;\n this.scene.backgroundIntensity = env.intensity ?? 1;\n }\n if (env.lighting ?? true) this.scene.environment = tex;\n });\n }\n\n /** Set the visible sky background brightness (0 = black). Live, for behavior. */\n setBackgroundIntensity(value: number): void {\n this.scene.backgroundIntensity = value;\n }\n\n private clear(): void {\n this.scene.background = null;\n this.scene.environment = null;\n this.texture?.dispose();\n this.texture = null;\n }\n\n dispose(): void {\n this.clear();\n this.hash = null;\n }\n}\n","import type { CompressedTexture, WebGLRenderer } from \"three\";\n\n/** The part of three's KTX2Loader the engine and plugins use. */\nexport interface Ktx2Loader {\n load(\n url: string,\n onLoad: (texture: CompressedTexture) => void,\n onProgress?: (event: ProgressEvent) => void,\n onError?: (error: unknown) => void,\n ): void;\n}\n\nlet loader: Promise<Ktx2Loader> | null = null;\n\n/**\n * Configure the shared KTX2 loader. Called by the kernel once the renderer\n * exists; `.ktx2` textures and glTF models then decode through it.\n */\nexport const configureKtx2 = (renderer: WebGLRenderer, path: string): void => {\n loader = import(\"three/examples/jsm/loaders/KTX2Loader.js\").then(({ KTX2Loader }) => {\n const instance = new KTX2Loader();\n instance.setTranscoderPath(path);\n instance.detectSupport(renderer);\n return instance as unknown as Ktx2Loader;\n });\n};\n\n/** The configured loader, or null when the gallery did not set `ktx2Path`. */\nexport const getKtx2Loader = (): Promise<Ktx2Loader> | null => loader;\n","import { NoColorSpace, RepeatWrapping, SRGBColorSpace, type Texture } from \"three\";\nimport type { Vec2 } from \"@scenography/scene\";\n\n/** Creates the texture object for a source; its image may still be loading. */\nexport type TextureSource = (src: string) => Texture;\n\ntype Entry = {\n key: string;\n texture: Texture;\n refs: number;\n};\n\n/** Two surfaces can share a texture only at the same tiling and color space. */\nexport const textureKey = (src: string, repeat: Vec2, srgb: boolean): string =>\n `${src}|${repeat[0]}x${repeat[1]}|${srgb ? \"srgb\" : \"linear\"}`;\n\n/** Tiling and color space of a texture; the image arrives later from the loader. */\nexport const configureTexture = (texture: Texture, repeat: Vec2, srgb: boolean): void => {\n texture.wrapS = RepeatWrapping;\n texture.wrapT = RepeatWrapping;\n texture.repeat.set(repeat[0], repeat[1]);\n texture.colorSpace = srgb ? SRGBColorSpace : NoColorSpace;\n};\n\n/**\n * Shares textures between materials: the same source at the same tiling and\n * color space becomes one texture, loaded once and uploaded to the GPU once.\n * Reference counted, so releasing a material keeps the texture alive while\n * another one still samples it.\n */\nexport class TextureCache {\n private readonly entries = new Map<string, Entry>();\n private readonly owners = new Map<Texture, Entry>();\n\n constructor(private readonly source: TextureSource) {}\n\n /** The shared texture for a source, created on first use. */\n acquire(src: string, repeat: Vec2, srgb: boolean): Texture {\n const key = textureKey(src, repeat, srgb);\n const existing = this.entries.get(key);\n if (existing) {\n existing.refs += 1;\n return existing.texture;\n }\n const texture = this.source(src);\n configureTexture(texture, repeat, srgb);\n const entry = { key, texture, refs: 1 };\n this.entries.set(key, entry);\n this.owners.set(texture, entry);\n return texture;\n }\n\n /** Drop one reference; the texture is disposed when the last one goes. */\n release(texture: Texture): void {\n const entry = this.owners.get(texture);\n if (!entry) {\n texture.dispose();\n return;\n }\n entry.refs -= 1;\n if (entry.refs > 0) return;\n this.entries.delete(entry.key);\n this.owners.delete(texture);\n texture.dispose();\n }\n\n /** Textures currently shared. */\n get size(): number {\n return this.entries.size;\n }\n}\n","import { MeshStandardMaterial, TextureLoader, Vector2, type Texture } from \"three\";\nimport type { SurfaceTexture, Vec2 } from \"@scenography/scene\";\n\nimport { getKtx2Loader } from \"./ktx2\";\nimport { configureTexture, TextureCache, textureKey } from \"./textureCache\";\n\nconst textureLoader = new TextureLoader();\nconst cache = new TextureCache((src) => textureLoader.load(src));\n\ntype MapSlot = \"map\" | \"normalMap\" | \"roughnessMap\" | \"aoMap\";\n\ntype Ktx2Entry = {\n key: string;\n refs: number;\n texture?: Texture;\n waiters: Array<(texture: Texture) => void>;\n};\n\nconst ktx2Entries = new Map<string, Ktx2Entry>();\nconst ktx2Owners = new Map<Texture, Ktx2Entry>();\n\n/**\n * KTX2 textures resolve asynchronously, so the material slot is filled through\n * a waiter list. One entry per source and tiling is shared by every material\n * that samples it, just like the image cache.\n */\nconst loadKtx2 = (\n material: MeshStandardMaterial,\n slot: MapSlot,\n src: string,\n repeat: Vec2,\n srgb: boolean,\n): void => {\n const loader = getKtx2Loader();\n if (!loader) {\n console.warn(`KTX2 texture \"${src}\" needs the gallery's ktx2Path option.`);\n return;\n }\n const key = textureKey(src, repeat, srgb);\n let entry = ktx2Entries.get(key);\n if (!entry) {\n entry = { key, refs: 0, waiters: [] };\n ktx2Entries.set(key, entry);\n const created = entry;\n void loader.then((ktx2) =>\n ktx2.load(\n src,\n (texture) => {\n configureTexture(texture, repeat, srgb);\n texture.needsUpdate = true;\n created.texture = texture;\n ktx2Owners.set(texture, created);\n for (const assign of created.waiters) assign(texture);\n created.waiters = [];\n },\n undefined,\n (error) => console.error(`Failed to load KTX2 texture \"${src}\".`, error),\n ),\n );\n }\n entry.refs += 1;\n if (entry.texture) {\n material[slot] = entry.texture;\n material.needsUpdate = true;\n } else {\n entry.waiters.push((texture) => {\n material[slot] = texture;\n material.needsUpdate = true;\n });\n }\n};\n\n/**\n * Load one map into `material[slot]`. Textures are shared: every material that\n * samples the same source at the same tiling reuses one texture object. `.exr`\n * maps go through EXRLoader, imported on demand, and are not shared because\n * they resolve asynchronously; `.ktx2` maps share through their own cache.\n */\nconst loadMap = (\n material: MeshStandardMaterial,\n slot: MapSlot,\n src: string,\n repeat: Vec2,\n srgb: boolean,\n): void => {\n if (src.toLowerCase().endsWith(\".ktx2\")) {\n loadKtx2(material, slot, src, repeat, srgb);\n return;\n }\n if (src.toLowerCase().endsWith(\".exr\")) {\n void import(\"three/examples/jsm/loaders/EXRLoader.js\").then(({ EXRLoader }) => {\n new EXRLoader().load(src, (texture) => {\n configureTexture(texture, repeat, srgb);\n texture.needsUpdate = true;\n material[slot] = texture;\n material.needsUpdate = true;\n });\n });\n return;\n }\n material[slot] = cache.acquire(src, repeat, srgb);\n};\n\n/**\n * Apply a `SurfaceTexture` to a standard material. Returns whether a second UV\n * set (`uv1`) is needed — three samples `aoMap` from it, so the geometry must\n * provide one.\n */\nexport const applyTexture = (\n material: MeshStandardMaterial,\n texture: SurfaceTexture,\n): boolean => {\n const repeat = texture.repeat ?? [1, 1];\n loadMap(material, \"map\", texture.src, repeat, true);\n if (texture.normal) {\n loadMap(material, \"normalMap\", texture.normal, repeat, false);\n const scale = texture.normalScale ?? 1;\n material.normalScale = new Vector2(scale, scale);\n }\n if (texture.roughness) loadMap(material, \"roughnessMap\", texture.roughness, repeat, false);\n if (texture.ao) loadMap(material, \"aoMap\", texture.ao, repeat, false);\n material.needsUpdate = true;\n return texture.ao !== undefined;\n};\n\n/**\n * Release one reference to a texture assigned by `applyTexture`. Shared\n * textures stay alive while another material still samples them.\n */\nexport const releaseTexture = (texture: Texture): void => {\n const ktx2 = ktx2Owners.get(texture);\n if (!ktx2) {\n cache.release(texture);\n return;\n }\n ktx2.refs -= 1;\n if (ktx2.refs > 0) return;\n ktx2Owners.delete(texture);\n ktx2Entries.delete(ktx2.key);\n texture.dispose();\n};\n","import {\n BoxGeometry,\n Group,\n Mesh,\n MeshBasicMaterial,\n Quaternion,\n Raycaster,\n Vector2,\n Vector3,\n type Object3D,\n type PerspectiveCamera,\n type Scene as ThreeScene,\n} from \"three\";\n\nconst HIGHLIGHT_COLOR = \"#ef4444\";\nconst HIGHLIGHT_THICKNESS = 1.5; // world units (cm)\nconst HIGHLIGHT_MARGIN = 1.5; // gap between the piece and the frame\nconst CLICK_SLOP = 4; // px of movement still counted as a click, not a drag\nconst PICK_INTERVAL = 60; // ms between hover raycasts while the pointer is over the canvas\n\ninterface PickInfo {\n width: number;\n height: number;\n frontOffset: number;\n}\n\ninterface Pick {\n id: string;\n holder: Object3D;\n info: PickInfo;\n}\n\n/**\n * Pointer picking for artworks: hovering a piece's click target (its label, or\n * the piece itself when it has none) draws a pulsing red frame and shows a\n * pointer cursor; clicking it emits `artwork:select { id }`. Targets are marked\n * in the scene graph (`userData.pickTarget`/`pickInfo`) by the artwork builder,\n * so this just raycasts and walks up to find them. An optional per-id radius\n * gates interaction to when the camera is reasonably close.\n */\nexport class InteractionController {\n private readonly raycaster = new Raycaster();\n private readonly pointer = new Vector2();\n private over = false;\n private downPos: { x: number; y: number } | null = null;\n private radii = new Map<string, number>();\n private hovered: string | null = null;\n private time = 0;\n private pickElapsed = 0;\n private pointerMoved = false;\n\n private readonly highlight = new Group();\n private highlightMat: MeshBasicMaterial | null = null;\n private readonly handlers: Array<[EventTarget, string, EventListener]> = [];\n\n constructor(\n private readonly camera: PerspectiveCamera,\n private readonly canvas: HTMLCanvasElement,\n private readonly scene: ThreeScene,\n private readonly emit: (event: string, payload?: unknown) => void,\n private readonly canInteract: () => boolean,\n ) {\n this.highlight.visible = false;\n this.scene.add(this.highlight);\n }\n\n /** Per-artwork max interaction distance (XZ). Absent id = no distance gate. */\n setRadii(radii: Map<string, number>): void {\n this.radii = radii;\n }\n\n attach(): void {\n const bind = (type: string, listener: EventListener): void => {\n this.canvas.addEventListener(type, listener);\n this.handlers.push([this.canvas, type, listener]);\n };\n bind(\"pointermove\", (e) => this.onMove(e as PointerEvent));\n bind(\"pointerdown\", (e) => this.onDown(e as PointerEvent));\n bind(\"pointerup\", (e) => this.onUp(e as PointerEvent));\n bind(\"pointerleave\", () => {\n this.over = false;\n });\n }\n\n detach(): void {\n for (const [target, type, listener] of this.handlers) target.removeEventListener(type, listener);\n this.handlers.length = 0;\n this.scene.remove(this.highlight);\n this.clearHighlight();\n }\n\n private setPointer(e: PointerEvent): void {\n const rect = this.canvas.getBoundingClientRect();\n this.pointer.set(\n ((e.clientX - rect.left) / rect.width) * 2 - 1,\n -((e.clientY - rect.top) / rect.height) * 2 + 1,\n );\n this.over = true;\n this.pointerMoved = true;\n }\n\n private onMove(e: PointerEvent): void {\n this.setPointer(e);\n }\n\n private onDown(e: PointerEvent): void {\n this.downPos = { x: e.clientX, y: e.clientY };\n }\n\n private onUp(e: PointerEvent): void {\n const down = this.downPos;\n this.downPos = null;\n if (!down || !this.canInteract()) return;\n if (Math.hypot(e.clientX - down.x, e.clientY - down.y) > CLICK_SLOP) return; // a drag, not a click\n this.setPointer(e);\n const pick = this.raycast();\n if (pick) this.emit(\"artwork:select\", { id: pick.id });\n }\n\n /** Resolve the nearest pickable artwork under the pointer, honoring the gate. */\n private raycast(): Pick | null {\n this.raycaster.setFromCamera(this.pointer, this.camera);\n const hits = this.raycaster.intersectObjects(this.scene.children, true);\n for (const hit of hits) {\n let node: Object3D | null = hit.object;\n while (node && node.userData.pickTarget === undefined) node = node.parent;\n if (!node) continue;\n const id = node.userData.pickTarget as string;\n const radius = this.radii.get(id);\n if (radius !== undefined) {\n const p = node.getWorldPosition(new Vector3());\n if (Math.hypot(this.camera.position.x - p.x, this.camera.position.z - p.z) > radius) continue;\n }\n return { id, holder: node, info: node.userData.pickInfo as PickInfo };\n }\n return null;\n }\n\n /**\n * Recompute hover state and animate the highlight. Call once per frame.\n * Hover picking is throttled: a full-scene raycast runs at most once every\n * `PICK_INTERVAL` ms, or immediately after the pointer moves. The camera can\n * move without the pointer, so the interval keeps hovering correct while\n * walking.\n */\n update(deltaMs: number): void {\n this.time += deltaMs;\n\n if (!this.over || !this.canInteract()) {\n // No picking while the visitor cannot interact; pick at once when they can.\n this.pickElapsed = PICK_INTERVAL;\n this.pointerMoved = false;\n this.applyPick(null);\n } else {\n this.pickElapsed += deltaMs;\n if (this.pointerMoved) this.pickElapsed = PICK_INTERVAL;\n this.pointerMoved = false;\n if (this.pickElapsed >= PICK_INTERVAL) {\n this.pickElapsed = 0;\n this.applyPick(this.raycast());\n }\n }\n\n if (this.highlight.visible && this.highlightMat) {\n this.highlightMat.opacity = 0.45 + 0.45 * (0.5 + 0.5 * Math.sin(this.time / 320));\n }\n }\n\n /** Swap the hovered piece, touching the canvas and the highlight only on change. */\n private applyPick(pick: Pick | null): void {\n if ((pick?.id ?? null) === this.hovered) return;\n this.hovered = pick?.id ?? null;\n this.canvas.style.cursor = pick ? \"pointer\" : \"\";\n if (pick) this.buildHighlight(pick);\n else this.highlight.visible = false;\n }\n\n private clearHighlight(): void {\n for (const child of [...this.highlight.children]) {\n this.highlight.remove(child);\n (child as Mesh).geometry?.dispose();\n }\n this.highlightMat?.dispose();\n this.highlightMat = null;\n }\n\n /** Position a fresh pulsing red frame over the picked target's front face. */\n private buildHighlight(pick: Pick): void {\n this.clearHighlight();\n const mat = new MeshBasicMaterial({ color: HIGHLIGHT_COLOR, transparent: true, depthTest: false });\n this.highlightMat = mat;\n\n const w = pick.info.width + 2 * HIGHLIGHT_MARGIN;\n const h = pick.info.height + 2 * HIGHLIGHT_MARGIN;\n const t = HIGHLIGHT_THICKNESS;\n // Four bars forming a border (horizontals span the corners; verticals fill in).\n const bars: [number, number, number, number][] = [\n [w + 2 * t, t, 0, h / 2 + t / 2],\n [w + 2 * t, t, 0, -(h / 2 + t / 2)],\n [t, h, -(w / 2 + t / 2), 0],\n [t, h, w / 2 + t / 2, 0],\n ];\n for (const [bw, bh, x, y] of bars) {\n const bar = new Mesh(new BoxGeometry(bw, bh, 0.5), mat);\n bar.position.set(x, y, 0);\n bar.renderOrder = 999;\n this.highlight.add(bar);\n }\n\n const pos = pick.holder.getWorldPosition(new Vector3());\n const quat = pick.holder.getWorldQuaternion(new Quaternion());\n const normal = new Vector3(0, 0, 1).applyQuaternion(quat);\n pos.addScaledVector(normal, pick.info.frontOffset);\n this.highlight.position.copy(pos);\n this.highlight.quaternion.copy(quat);\n this.highlight.visible = true;\n }\n}\n","/**\n * The shared analog-input channel: the current movement and look intent, each\n * axis in `-1..1` and held until a producer changes it. Producers (on-screen\n * joysticks in the overlay, a gamepad…) write to it; whatever camera system is\n * active reads it and interprets it its own way — free-roam walks with it, a\n * fixed camera might only use the look axes to glance around. This is what makes\n * the joysticks reusable across camera types. Pure state — no three.js.\n */\nexport class InputBus {\n /** Forward (`+1`) / backward (`-1`). */\n moveForward = 0;\n /** Strafe right (`+1`) / left (`-1`). */\n moveStrafe = 0;\n /** Look right (`+1`) / left (`-1`). */\n lookX = 0;\n /** Look up (`+1`) / down (`-1`). */\n lookY = 0;\n\n /** Set the movement intent (forward, strafe). */\n setMove(forward: number, strafe = 0): void {\n this.moveForward = forward;\n this.moveStrafe = strafe;\n }\n\n /** Set the look intent (turn, pitch). */\n setLook(x: number, y = 0): void {\n this.lookX = x;\n this.lookY = y;\n }\n}\n","/**\n * The visitor's camera-control preferences: a set of orthogonal capability flags\n * plus a derived, friendly \"mode\". Pure state — no three.js, no DOM. The engine\n * wires the flags to effects (gyroscope head-look, stereo split); consumers read\n * them (the analog look-invert axes); the overlay drives it from a mode picker\n * (the `ScanEye` button) and the settings panel. Persistence and device\n * feature-detection live in those callers, not here, so this stays trivially\n * unit-testable.\n */\n\n/**\n * Orthogonal control capabilities. `gyro` / `splitScreen` / `joysticks` are what a\n * mode fixes; the two `invertLook*` axes are independent look-feel preferences\n * (some people feel the right stick inverted), kept separate from the mode.\n */\nexport interface ControlFlags {\n /** Orient the camera from the device gyroscope (head-look). */\n gyro: boolean;\n /** Split the view into a stereo left/right eye pair (phone-in-a-headset). */\n splitScreen: boolean;\n /** Show the on-screen virtual joysticks. */\n joysticks: boolean;\n /** Invert the horizontal (turn) axis of analog look. */\n invertLookX: boolean;\n /** Invert the vertical (pitch) axis of analog look. */\n invertLookY: boolean;\n}\n\n/** A named preset over the mode-defining flags; `custom` when none matches. */\nexport type ControlMode = \"touch\" | \"vr\" | \"cardboard\" | \"custom\";\n\n/** The three flags a mode fixes (the look-invert axes stay a separate preference). */\ntype ModeFlags = Pick<ControlFlags, \"gyro\" | \"splitScreen\" | \"joysticks\">;\n\n/**\n * Mode presets. Touch = two joysticks, no gyro. VR = the gyro aims and a single\n * move joystick walks you, no split. Cardboard = gyro aims, stereo split, gaze\n * select (no joysticks). The move-only single stick in VR is derived downstream\n * from `gyro && !splitScreen`, so it needs no flag of its own.\n */\nexport const MODE_PRESETS: Record<Exclude<ControlMode, \"custom\">, ModeFlags> = {\n touch: { gyro: false, splitScreen: false, joysticks: true },\n vr: { gyro: true, splitScreen: false, joysticks: true },\n cardboard: { gyro: true, splitScreen: true, joysticks: false },\n};\n\n/** Default flags: plain touch, no look inversion. */\nexport const DEFAULT_CONTROL_FLAGS: ControlFlags = {\n ...MODE_PRESETS.touch,\n invertLookX: false,\n invertLookY: false,\n};\n\nconst FLAG_KEYS: readonly (keyof ControlFlags)[] = [\n \"gyro\",\n \"splitScreen\",\n \"joysticks\",\n \"invertLookX\",\n \"invertLookY\",\n];\n\nconst equalFlags = (a: ControlFlags, b: ControlFlags): boolean =>\n FLAG_KEYS.every((key) => a[key] === b[key]);\n\n/** The mode a flag set represents, or `custom` when no preset matches. */\nexport function modeOf(flags: ControlFlags): ControlMode {\n for (const mode of [\"touch\", \"vr\", \"cardboard\"] as const) {\n const preset = MODE_PRESETS[mode];\n if (\n flags.gyro === preset.gyro &&\n flags.splitScreen === preset.splitScreen &&\n flags.joysticks === preset.joysticks\n )\n return mode;\n }\n return \"custom\";\n}\n\n/** Apply a mode's preset onto `flags`, leaving the look-invert axes untouched. */\nexport function applyMode(\n flags: ControlFlags,\n mode: Exclude<ControlMode, \"custom\">,\n): ControlFlags {\n return { ...flags, ...MODE_PRESETS[mode] };\n}\n\n/**\n * Live control-preferences state: holds the flags, derives the mode, and notifies\n * subscribers on every real change. Callers apply the effects — the engine wires\n * gyro and split, the overlay redraws — so this object is just the shared source\n * of truth they all read and write.\n */\nexport class ControlSettings {\n private flags: ControlFlags;\n private readonly subscribers = new Set<(flags: ControlFlags) => void>();\n\n constructor(initial: Partial<ControlFlags> = {}) {\n this.flags = { ...DEFAULT_CONTROL_FLAGS, ...initial };\n }\n\n /** The current flags (a copy — mutate through `setMode` / `setFlag`). */\n get(): ControlFlags {\n return { ...this.flags };\n }\n\n /** The mode the current flags represent (`custom` when hand-tuned). */\n getMode(): ControlMode {\n return modeOf(this.flags);\n }\n\n /** Switch to a mode's preset (keeps the look-invert axes). No-op if unchanged. */\n setMode(mode: Exclude<ControlMode, \"custom\">): void {\n this.replace(applyMode(this.flags, mode));\n }\n\n /** Set one flag. Flipping a mode-defining flag can drop you into `custom`. */\n setFlag<K extends keyof ControlFlags>(key: K, value: ControlFlags[K]): void {\n this.replace({ ...this.flags, [key]: value });\n }\n\n /** Subscribe to changes; returns an unsubscribe. Fires on every real change. */\n subscribe(fn: (flags: ControlFlags) => void): () => void {\n this.subscribers.add(fn);\n return () => this.subscribers.delete(fn);\n }\n\n private replace(next: ControlFlags): void {\n if (equalFlags(this.flags, next)) return;\n this.flags = next;\n const snapshot = this.get();\n for (const fn of this.subscribers) fn(snapshot);\n }\n}\n","import { DEFAULT_CONTROL_FLAGS, type ControlFlags } from \"./controlSettings\";\n\n/**\n * localStorage persistence for the control flags, kept out of the pure\n * `ControlSettings` so that stays DOM-free and unit-testable. Both functions are\n * best-effort and guarded: no `localStorage` (SSR, privacy mode) or malformed\n * data just falls back to defaults, never throws.\n */\nconst STORAGE_KEY = \"scenography:controls\";\n\nconst FLAG_KEYS = Object.keys(DEFAULT_CONTROL_FLAGS) as (keyof ControlFlags)[];\n\n/** Read persisted control flags; `null` when absent, unavailable or malformed. */\nexport function loadControlFlags(): Partial<ControlFlags> | null {\n try {\n const raw = globalThis.localStorage?.getItem(STORAGE_KEY);\n if (!raw) return null;\n const parsed = JSON.parse(raw) as Record<string, unknown>;\n const flags: Partial<ControlFlags> = {};\n // Copy only known boolean keys, so stale/garbage fields can't leak in.\n for (const key of FLAG_KEYS) {\n if (typeof parsed[key] === \"boolean\") flags[key] = parsed[key] as boolean;\n }\n return flags;\n } catch {\n return null;\n }\n}\n\n/** Persist control flags (best-effort; ignores a full/blocked store). */\nexport function saveControlFlags(flags: ControlFlags): void {\n try {\n globalThis.localStorage?.setItem(STORAGE_KEY, JSON.stringify(flags));\n } catch {\n /* storage unavailable or full — persistence is best-effort */\n }\n}\n","/**\n * Shared runtime contracts between plugins, keyed by name. A plugin (or the\n * kernel) *provides* a capability — e.g. the floor provides `terrain` — and\n * other plugins *consume* it if present, without depending on the provider's\n * package. Each name keeps a monotonic version counter so consumers can hash\n * their dependencies cheaply and rebuild when a provider changes.\n */\nexport class CapabilityRegistry {\n private readonly values = new Map<string, unknown>();\n private readonly versions = new Map<string, number>();\n /** Last change-token seen per name, to suppress redundant version bumps. */\n private readonly tokens = new Map<string, string>();\n\n /**\n * Publish (or replace) the capability under `name`. Pass a `token` (a cheap\n * hash of the inputs) to dedupe: re-providing with an unchanged token is a\n * no-op, so consumers that depend on this capability don't rebuild when the\n * provider recomputes an identical value.\n */\n provide<T>(name: string, value: T, token?: string): void {\n if (token !== undefined && this.values.has(name) && this.tokens.get(name) === token) return;\n this.values.set(name, value);\n if (token !== undefined) this.tokens.set(name, token);\n else this.tokens.delete(name);\n this.bump(name);\n }\n\n /** Remove the capability; consumers see `undefined` and a version bump. */\n revoke(name: string): void {\n if (this.values.delete(name)) {\n this.tokens.delete(name);\n this.bump(name);\n }\n }\n\n get<T>(name: string): T | undefined {\n return this.values.get(name) as T | undefined;\n }\n\n /** Monotonic change counter for `name` (`0` if never provided). */\n version(name: string): number {\n return this.versions.get(name) ?? 0;\n }\n\n private bump(name: string): void {\n this.versions.set(name, (this.versions.get(name) ?? 0) + 1);\n }\n}\n","import type {\n Object3D,\n PerspectiveCamera,\n Scene as ThreeScene,\n WebGLRenderer,\n} from \"three\";\nimport type { Scene } from \"@scenography/scene\";\n\nimport type { CapabilityRegistry } from \"./CapabilityRegistry\";\nimport type { ControlFlags, InputBus } from \"../input\";\n\n/**\n * What the kernel hands a system on setup. A *system* plugin — unlike an element\n * plugin — produces no scene object; it drives shared runtime state (the camera,\n * input…) each frame. The camera is a kernel primitive (the renderer needs one),\n * so systems receive it and drive it, publishing/consuming capabilities to\n * cooperate (e.g. the camera plugin publishes `roamControl`).\n */\nexport interface SystemContext {\n /** The current scene (its data keys, start pose, boundary…); also `setScene` on hot-swap. */\n scene: Scene;\n /** The render camera the system may drive. */\n camera: PerspectiveCamera;\n /** The canvas, for pointer/keyboard listeners. */\n canvas: HTMLElement;\n /** The three.js scene graph, for systems that manage their own objects (sky…). */\n scene3d: ThreeScene;\n /** The renderer, for systems that need it (e.g. sky IBL generation). */\n renderer: WebGLRenderer;\n /** Shared analog input (joysticks feed it); systems interpret it. */\n input: InputBus;\n /** The visitor's current control flags (e.g. look-invert axes), read live. */\n controlFlags: () => ControlFlags;\n /** Capability registry for cross-plugin contracts (terrain, colliders…). */\n capabilities: CapabilityRegistry;\n /** Emit a gallery event. */\n emit: (event: string, payload?: unknown) => void;\n /** Resolve a live scene object by id (e.g. for a framing system). */\n resolve: (id: string) => Object3D | undefined;\n}\n\n/** A running system: its per-frame update, hot-swap and teardown hooks. */\nexport interface SystemInstance<Api = unknown> {\n /** Advance the system by the frame delta (ms). */\n update?(deltaMs: number): void;\n /** React to a hot-swapped scene definition. */\n setScene?(scene: Scene): void;\n /** Tear down (detach listeners, revoke capabilities). */\n dispose?(): void;\n /**\n * Public API the system exposes to userland/overlay. The kernel maps known\n * system names onto `ctx` (e.g. a `focus` system's api → `ctx.camera`).\n */\n api?: Api;\n}\n\n/**\n * A system plugin: sets up once against the kernel handles and returns its\n * running instance. Registered alongside element plugins in `plugins`; the\n * kernel tells them apart by shape (`setup` vs `build`).\n */\nexport interface SystemPlugin<Api = unknown> {\n name: string;\n setup(ctx: SystemContext): SystemInstance<Api>;\n}\n\n/** Narrow a mixed plugin to a system plugin (has `setup`, not `build`). */\nexport const isSystemPlugin = (plugin: unknown): plugin is SystemPlugin =>\n typeof plugin === \"object\" && plugin !== null && \"setup\" in plugin;\n","import type { Vector3 } from \"three\";\n\n/** Capability name under which a roaming camera system exposes pose control. */\nexport const ROAM_CONTROL = \"roamControl\";\n\n/**\n * Handle a camera plugin publishes so other camera systems can cooperate with\n * it — chiefly a framing/tour system that suspends free-roam while it animates\n * the camera, then reads/writes the pose to hand control back cleanly.\n */\nexport interface RoamControl {\n /** Enable/disable free-roam input (disabled while another system owns the camera). */\n setEnabled(enabled: boolean): void;\n /** Current pose: world position plus yaw (`phi`) and pitch (`theta`) in radians. */\n getPose(): { position: Vector3; phi: number; theta: number };\n /** Force the pose so free-roam resumes from it. */\n setPose(position: Vector3, phi: number, theta: number): void;\n /** Current eye height above the ground (world units). */\n getEyeHeight(): number;\n}\n","import type { Vec2 } from \"@scenography/scene\";\n\n/** Capability name under which the floor publishes its terrain. */\nexport const TERRAIN = \"terrain\";\n\n/**\n * The geometry a terrain consumer relies on — the fields needed to sample the\n * same ground a floor was displaced by (e.g. the water shader). A floor\n * definition satisfies this structurally; the kernel stays floor-agnostic.\n */\nexport interface TerrainDefinition {\n /** Extent on the ground plane, `[width (x), depth (z)]`. */\n size: Vec2;\n /** Center on the ground plane `[x, z]`. Defaults to `[0, 0]`. */\n center?: Vec2;\n /** Base vertical position (world Y). Defaults to `0`. */\n y?: number;\n /** Grayscale heightmap URL that displaces the ground. */\n heightmap?: string;\n /** Max displacement (world units) at white in the heightmap. */\n heightScale?: number;\n}\n\n/**\n * The walkable ground, published by the floor: its definition (for shaders that\n * sample the same heightmap), the live height function (heightmap-aware once the\n * image loads) and a promise that resolves once that sampling is in effect.\n */\nexport interface TerrainCapability {\n definition: TerrainDefinition;\n heightAt: (x: number, z: number) => number;\n /** Resolves once `heightAt` reflects the heightmap (immediately when there is none). */\n ready: Promise<void>;\n}\n","/** Capability name under which walkable-space obstacles are published. */\nexport const COLLIDERS = \"colliders\";\n\n/**\n * A collision obstacle as an oriented box on the XZ plane. The camera controls\n * consume these to keep the visitor out of walls (and any other plugin that\n * wants to block movement can provide more of them). Purely a movement contract\n * — no geometry, no three.js.\n */\nexport interface Collider {\n /** Box center `[x, z]`. */\n cx: number;\n cz: number;\n /** Unit direction along the box's length. */\n dx: number;\n dz: number;\n /** Half the box length. */\n halfLength: number;\n /** Half the box thickness. */\n halfThickness: number;\n}\n","import type { Vec2 } from \"@scenography/scene\";\n\n/** Capability name under which named wall edges are published for hanging. */\nexport const EDGES = \"edges\";\n\n/** A named anchor an artwork can hang on: a wall segment resolved to geometry. */\nexport interface EdgeAnchor {\n a: Vec2;\n b: Vec2;\n height: number;\n thickness: number;\n}\n\n/** The `name -> anchor` index a provider (e.g. the wall plugin) publishes. */\nexport type EdgeIndex = ReadonlyMap<string, EdgeAnchor>;\n","/**\n * Capability name under which the visitor's eye height (world units) is\n * published — the default vertical placement for wall-hung artworks. The kernel\n * provides it from the camera start; a camera plugin will own it later.\n */\nexport const EYE_HEIGHT = \"eyeHeight\";\n","import {\n PerspectiveCamera,\n type Object3D,\n type Scene as ThreeScene,\n type Texture,\n type WebGLRenderer,\n} from \"three\";\nimport type { Scene } from \"@scenography/scene\";\n\nimport { releaseTexture } from \"./builders\";\nimport {\n CapabilityRegistry,\n EYE_HEIGHT,\n type BuiltElement,\n type EngineContext,\n type EnginePlugin,\n} from \"./plugin\";\n\n/** A mounted plugin element: what the plugin built plus its content hash. */\ntype MountedElement = BuiltElement & { hash: string };\n\nconst TEXTURE_SLOTS = [\"map\", \"normalMap\", \"roughnessMap\", \"aoMap\", \"metalnessMap\"];\n\nconst disposeObject = (object: Object3D): void => {\n const materials = new Set<unknown>();\n object.traverse((child) => {\n // Release any HTML video element backing a VideoTexture.\n const video = child.userData?.video as HTMLVideoElement | undefined;\n if (video) {\n video.pause();\n video.removeAttribute(\"src\");\n video.load();\n }\n const mesh = child as { geometry?: { dispose?: () => void }; material?: unknown };\n mesh.geometry?.dispose?.();\n const material = mesh.material;\n if (Array.isArray(material)) for (const entry of material) materials.add(entry);\n else if (material) materials.add(material);\n });\n // A material can be shared by several meshes; dispose it, and the textures it\n // samples, once. Textures come from a shared cache, so they are released\n // rather than disposed outright.\n for (const entry of materials) {\n const material = entry as Record<string, unknown> & { dispose?: () => void };\n for (const slot of TEXTURE_SLOTS) {\n const texture = material[slot] as Texture | undefined;\n if (texture) releaseTexture(texture);\n }\n material.dispose?.();\n }\n};\n\n/**\n * Owns the live three.js scene graph and keeps it in sync with a `Scene`\n * definition. `reconcile` diffs the incoming definition against what is already\n * mounted — by `id` — and only adds/removes/rebuilds what changed. This is what\n * makes hot scene edits cheap: add a wing or drop one while moving and only the\n * touched objects are rebuilt.\n */\nexport class SceneManager {\n private readonly root: ThreeScene;\n private readonly plugins: EnginePlugin[];\n private readonly capabilities: CapabilityRegistry;\n private readonly view: { camera: PerspectiveCamera; renderer: WebGLRenderer };\n /** Mounted plugin elements, per kind, keyed by element id (\"\" for singletons). */\n private readonly pluginElements = new Map<string, Map<string, MountedElement>>();\n\n constructor(\n root: ThreeScene,\n plugins: EnginePlugin[] = [],\n capabilities: CapabilityRegistry = new CapabilityRegistry(),\n view: { camera: PerspectiveCamera; renderer: WebGLRenderer } = {\n camera: new PerspectiveCamera(),\n renderer: {} as WebGLRenderer,\n },\n ) {\n this.root = root;\n this.plugins = plugins;\n this.capabilities = capabilities;\n this.view = view;\n }\n\n /** The build context handed to every plugin. */\n private context(): EngineContext {\n return { capabilities: this.capabilities, ...this.view };\n }\n\n reconcile(scene: Scene): void {\n // Eye height (default hang height for wall art) — a camera concern the\n // kernel owns for now; a camera plugin will publish it later.\n const eye = scene.camera.start.position[1];\n this.capabilities.provide(EYE_HEIGHT, eye, String(eye));\n // Plugins go last so kernel-provided capabilities (terrain, eyeHeight) are fresh.\n this.reconcilePlugins(scene);\n }\n\n /**\n * Reconcile every registered plugin's elements by content: an element\n * rebuilds when its definition changes or when a capability it depends on\n * was re-provided (the capability versions are part of the hash).\n */\n private reconcilePlugins(scene: Scene): void {\n for (const plugin of this.plugins) {\n const { kind, cardinality } = plugin.data;\n let mounted = this.pluginElements.get(kind);\n if (!mounted) {\n mounted = new Map();\n this.pluginElements.set(kind, mounted);\n }\n\n const raw = (scene as unknown as Record<string, unknown>)[kind];\n const items: Array<[string, unknown]> =\n cardinality === \"many\"\n ? Array.isArray(raw)\n ? raw.map((item, i) => [String((item as { id?: string }).id ?? i), item])\n : []\n : raw !== undefined\n ? [[\"\", raw]]\n : [];\n const deps = (plugin.dependsOn ?? []).map((name) => this.capabilities.version(name));\n\n const seen = new Set<string>();\n for (const [key, value] of items) {\n seen.add(key);\n const hash = JSON.stringify({ value, deps });\n const existing = mounted.get(key);\n if (existing?.hash === hash) continue;\n if (existing) this.removePluginElement(mounted, key, existing);\n const built = plugin.build(value as never, this.context());\n if (built.object) this.root.add(built.object);\n mounted.set(key, { ...built, hash });\n }\n for (const [key, entry] of mounted)\n if (!seen.has(key)) this.removePluginElement(mounted, key, entry);\n\n // Publish this plugin's aggregate capabilities now that its elements are\n // in sync, so a later plugin that consumes them builds against fresh data.\n plugin.provide?.(\n items.map(([, value]) => value),\n this.context(),\n );\n }\n }\n\n private removePluginElement(\n mounted: Map<string, MountedElement>,\n key: string,\n entry: MountedElement,\n ): void {\n if (entry.object) {\n this.root.remove(entry.object);\n disposeObject(entry.object);\n }\n entry.dispose?.();\n mounted.delete(key);\n }\n\n\n /** Advance time-based plugin elements (e.g. water ripple). Call once per frame. */\n updateElements(deltaMs: number): void {\n for (const mounted of this.pluginElements.values())\n for (const entry of mounted.values()) entry.update?.(deltaMs);\n }\n\n /** Run every element's auxiliary pre-render pass (e.g. water reflection). */\n beforeRender(renderer: WebGLRenderer, scene: ThreeScene, camera: PerspectiveCamera): void {\n for (const mounted of this.pluginElements.values())\n for (const entry of mounted.values()) entry.beforeRender?.(renderer, scene, camera);\n }\n\n\n /** Look up the live three.js object for any plugin element `id`. */\n get(id: string): Object3D | undefined {\n for (const mounted of this.pluginElements.values()) {\n const object = mounted.get(id)?.object;\n if (object) return object;\n }\n return undefined;\n }\n\n /** Live objects of one plugin kind, keyed by `id` — for that plugin's controller. */\n elementsOf(kind: string): ReadonlyMap<string, Object3D> {\n const out = new Map<string, Object3D>();\n const mounted = this.pluginElements.get(kind);\n if (mounted) for (const [id, entry] of mounted) if (entry.object) out.set(id, entry.object);\n return out;\n }\n\n dispose(): void {\n for (const [kind, mounted] of this.pluginElements) {\n for (const [key, entry] of mounted) this.removePluginElement(mounted, key, entry);\n this.pluginElements.delete(kind);\n }\n }\n}\n","import {\n Clock,\n HalfFloatType,\n PCFShadowMap,\n PerspectiveCamera,\n Scene as ThreeScene,\n StereoCamera,\n Vector2,\n WebGLRenderer,\n WebGLRenderTarget,\n type Object3D,\n} from \"three\";\nimport type { EffectComposer } from \"three/examples/jsm/postprocessing/EffectComposer.js\";\nimport type { Pass } from \"three/examples/jsm/postprocessing/Pass.js\";\nimport type { Scene } from \"@scenography/scene\";\n\nimport { EnvironmentController } from \"./environment\";\nimport { configureKtx2 } from \"./builders\";\nimport type { CameraApi } from \"./camera\";\nimport type { LightsApi, MediaController, SkyApi, VideoApi } from \"./api\";\nimport { InteractionController } from \"./interaction\";\nimport {\n ControlSettings,\n InputBus,\n MODE_PRESETS,\n loadControlFlags,\n saveControlFlags,\n type ControlFlags,\n type ControlMode,\n} from \"./input\";\nimport {\n CapabilityRegistry,\n isSystemPlugin,\n type ElementController,\n type EnginePlugin,\n type SystemInstance,\n type SystemPlugin,\n} from \"./plugin\";\nimport { SceneManager } from \"./SceneManager\";\n\n/** Maps a plugin kind/name onto the `ctx` slot its api fills. */\nconst CTX_SLOT = {\n camera: \"focus\",\n lights: \"lights\",\n video: \"videos\",\n sky: \"sky\",\n media: \"audio\",\n} as const;\n/** The system the cardboard render toggle drives (gyroscope head-look). */\nconst CARDBOARD_SYSTEM = \"cardboard\";\n\n/** MSAA sample count for antialiasing. `0` disables it. */\nexport type Antialias = 0 | 2 | 4 | 8;\n\n/** Toggleable rendering features. All can be flipped live via `setRender`. */\nexport interface RenderOptions {\n /**\n * Antialiasing as MSAA sample count (`0` off, `2`/`4`/`8`). Applied to the\n * post-process render target so it survives SSAO/bloom and toggles live,\n * unlike the canvas's native MSAA. Defaults to `4`.\n */\n antialias?: Antialias;\n /** Screen-space ambient occlusion as a post-process. Defaults to `false`. */\n ssao?: boolean;\n /**\n * Bloom strength: a soft glow bleeding out of the brightest areas. `0` off;\n * gentle around `0.3`, strong around `1`. Defaults to `0`.\n */\n bloom?: number;\n /** Real-time shadow mapping (PCF). Defaults to `true`. */\n shadows?: boolean;\n /**\n * Cardboard / stereoscopic mode: split the view into a left/right eye pair for\n * a phone-in-a-headset viewer. Skips post-processing while active. Defaults to\n * `false`.\n */\n cardboard?: boolean;\n /**\n * Interocular distance for cardboard mode, in world units. The physical ≈6.4 cm\n * is too much parallax to fuse comfortably through cardboard lenses, so this\n * defaults to `3`; tune it live for the viewer. `0` flattens to mono.\n */\n eyeSeparation?: number;\n}\n\nexport interface GalleryOptions extends RenderOptions {\n /** Canvas the gallery renders into. */\n canvas: HTMLCanvasElement;\n /** Background color. Defaults to a soft gray. */\n background?: string;\n /**\n * Base path of the KTX2 transcoder files (`basis_transcoder.js` and\n * `basis_transcoder.wasm`). Without it, `.ktx2` textures are skipped.\n */\n ktx2Path?: string;\n /**\n * Runtime plugins: element plugins materialize a scene key; system plugins\n * (e.g. a camera) drive shared runtime state. Mixed freely — the kernel tells\n * them apart by shape.\n */\n plugins?: (EnginePlugin | SystemPlugin)[];\n}\n\nconst DEFAULT_EYE_SEPARATION = 3;\n\n/**\n * Drive the free-roam camera from outside the engine — e.g. on-screen touch\n * controls in the overlay. Inputs are analog (`-1..1`) and held until changed.\n */\nexport interface ControlsApi {\n /** Walk forward (`+1`) / back (`-1`); optional strafe right (`+1`) / left (`-1`). */\n setMoveInput(forward: number, strafe?: number): void;\n /** Turn right (`+1`) / left (`-1`); optional look up (`+1`) / down (`-1`). */\n setLookInput(x: number, y?: number): void;\n}\n\n/**\n * The visitor's camera-control preferences (view mode + capability flags). The\n * engine applies the effects that touch rendering/input — gyroscope head-look and\n * the stereo split — as the flags change; the overlay drives it from the mode\n * picker and settings, and reads it to show the right joysticks. See\n * `ControlSettings`.\n */\nexport interface ViewControlsApi {\n /** Current capability flags (a copy). */\n get(): ControlFlags;\n /** The mode the flags represent (`custom` when hand-tuned in settings). */\n getMode(): ControlMode;\n /** Switch to a mode's preset (keeps the look-invert axes). */\n setMode(mode: Exclude<ControlMode, \"custom\">): void;\n /** Set one capability flag (may drop the mode into `custom`). */\n setFlag<K extends keyof ControlFlags>(key: K, value: ControlFlags[K]): void;\n /** Subscribe to changes; returns an unsubscribe. */\n subscribe(fn: (flags: ControlFlags) => void): () => void;\n}\n\n/** Control the HDRI sky / environment at runtime, for behavior code. */\nexport interface EnvironmentApi {\n /** Set the visible sky background brightness (`0` = black). */\n setBackgroundIntensity(value: number): void;\n}\n\n/**\n * High-level API handed to behavior code. Deliberately NOT raw three.js: this is\n * the supported surface. Reach into `three` only as a documented escape hatch.\n */\nexport interface GalleryContext {\n /** Resolve the live object for a wall/artwork/light `id`. */\n entity(id: string): Object3D | undefined;\n /** Control scene lights (dim, set intensity/color) by `id`. */\n readonly lights: LightsApi;\n /** Control background audio (play, pause, fade) by `id`. */\n readonly media: MediaController;\n /** Drive the camera externally (on-screen touch controls, etc.). */\n readonly controls: ControlsApi;\n /** The visitor's camera-control preferences (view mode + capability flags). */\n readonly viewControls: ViewControlsApi;\n /** Control the HDRI sky / environment at runtime. */\n readonly environment: EnvironmentApi;\n /** Drive the procedural sky's time of day. */\n readonly sky: SkyApi;\n /** Animate the camera to frame artworks (e.g. when an info panel opens/closes). */\n readonly camera: CameraApi;\n /** Control video screens (play, pause, show/hide) by `id`. */\n readonly video: VideoApi;\n /** Escape hatch to the underlying three.js objects for advanced use. */\n readonly three: {\n scene: ThreeScene;\n camera: PerspectiveCamera;\n renderer: WebGLRenderer;\n };\n}\n\nexport interface Gallery {\n /** Apply a new scene definition, reconciling only what changed (hot-swap). */\n setScene(scene: Scene): void;\n /** Start the render loop. */\n start(): void;\n /** Stop the render loop. */\n stop(): void;\n /** Tear everything down and release GPU resources. */\n dispose(): void;\n /** Subscribe to a named event; returns an unsubscribe function. */\n on(event: string, handler: (payload?: unknown) => void): () => void;\n /** Emit a named event to subscribers. */\n emit(event: string, payload?: unknown): void;\n /** Run a handler every frame with the frame delta in ms; returns unsubscribe. */\n onFrame(handler: (deltaMs: number) => void): () => void;\n /** Resize the renderer/camera to the given pixel size. */\n resize(width: number, height: number): void;\n /** Toggle rendering features (antialias, ssao, bloom, shadows) live. */\n setRender(options: RenderOptions): void;\n readonly ctx: GalleryContext;\n}\n\n/**\n * Create and run a gallery from a scene definition. Sets up the three.js\n * renderer, camera, lights and free-roam controls, materializes the scene, and\n * starts the render loop. `setScene` later reconciles changes in place.\n */\nexport function createGallery(scene: Scene, options: GalleryOptions): Gallery {\n const { canvas } = options;\n const width = canvas.clientWidth || window.innerWidth;\n const height = canvas.clientHeight || window.innerHeight;\n\n // Native MSAA is off: antialiasing is an SMAA post-pass so it survives SSAO\n // and toggles live. PCF shadow filtering set up once; shadows enabled below.\n const renderer = new WebGLRenderer({ canvas, antialias: false });\n renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));\n renderer.setSize(width, height, false);\n renderer.shadowMap.type = PCFShadowMap;\n\n // Live-toggleable render features.\n const render: Required<RenderOptions> = {\n antialias: options.antialias ?? 4,\n ssao: options.ssao ?? false,\n bloom: options.bloom ?? 0,\n shadows: options.shadows ?? true,\n cardboard: options.cardboard ?? false,\n eyeSeparation: options.eyeSeparation ?? DEFAULT_EYE_SEPARATION,\n };\n\n // Stereoscopic (cardboard) rendering. StereoCamera is core three (not the\n // WebGL-only StereoEffect addon); we render each eye with a scissored viewport,\n // which is portable to WebGPU. aspect 0.5 keeps each half undistorted.\n const stereo = new StereoCamera();\n stereo.aspect = 0.5;\n stereo.eyeSep = render.eyeSeparation;\n const stereoSize = new Vector2();\n\n const threeScene = new ThreeScene();\n\n const camera = new PerspectiveCamera(scene.camera.fov ?? 55, width / height, 0.1, 5000);\n const { position } = scene.camera.start;\n camera.position.set(position[0], position[1], position[2]);\n\n // Split element plugins (materialize a scene key) from system plugins (drive\n // shared state like the camera). Elements go to the manager; systems are set\n // up below, once elements have published their capabilities.\n const allPlugins = options.plugins ?? [];\n const systemPlugins = allPlugins.filter(isSystemPlugin);\n const elementPlugins = allPlugins.filter((p): p is EnginePlugin => !isSystemPlugin(p));\n\n const capabilities = new CapabilityRegistry();\n if (options.ktx2Path) configureKtx2(renderer, options.ktx2Path);\n const manager = new SceneManager(threeScene, elementPlugins, capabilities, {\n camera,\n renderer,\n });\n manager.reconcile(scene);\n\n const input = new InputBus();\n // The visitor's control preferences (view mode + flags). Created early so system\n // plugins can read the flags (e.g. fpc reads the look-invert axes); the effects\n // that need the head-look system / render are wired below, once they exist. A\n // persisted choice (localStorage) wins over the initial cardboard option so the\n // visitor's last mode is restored across reloads.\n const controlSettings = new ControlSettings(\n loadControlFlags() ?? (options.cardboard ? MODE_PRESETS.cardboard : {}),\n );\n const environment = new EnvironmentController(threeScene);\n environment.reconcile(scene.environment);\n\n const listeners = new Map<string, Set<(payload?: unknown) => void>>();\n const emit = (event: string, payload?: unknown): void => {\n listeners.get(event)?.forEach((handler) => handler(payload));\n };\n const frameHandlers = new Set<(deltaMs: number) => void>();\n\n // Plugins publish their public apis here, keyed by kind/name; the kernel maps\n // them onto `ctx` slots (see `CTX_SLOT`). Element-plugin controllers (lights,\n // video) and system apis (camera, sky, media) both land in this one map.\n const apis = new Map<string, unknown>();\n\n // Element-plugin controllers: an aggregate api over each plugin's live objects\n // (e.g. `ctx.lights`). They advance in the render loop and tear down on dispose.\n const controllers: ElementController[] = [];\n for (const plugin of elementPlugins) {\n const controller = plugin.controller?.(() => manager.elementsOf(plugin.data.kind));\n if (!controller) continue;\n if (controller.api !== undefined) apis.set(plugin.data.kind, controller.api);\n controllers.push(controller);\n }\n\n // System plugins (camera, sky, audio…): drive shared runtime state each frame\n // and may expose an api. They get the scene graph + renderer for subsystems\n // that manage their own objects (sky) or listen on the camera (audio).\n const systems: SystemInstance[] = systemPlugins.map((plugin) => {\n const instance = plugin.setup({\n scene,\n camera,\n canvas,\n scene3d: threeScene,\n renderer,\n input,\n controlFlags: () => controlSettings.get(),\n capabilities,\n emit,\n resolve: (id) => manager.get(id),\n });\n if (instance.api !== undefined) apis.set(plugin.name, instance.api);\n return instance;\n });\n\n // `ctx.camera` is filled by the framing system (\"focus\"). Without it, a no-op\n // keeps callers (the overlay) safe.\n const cameraApi: CameraApi = (apis.get(CTX_SLOT.camera) as CameraApi | undefined) ?? {\n focusArtwork: () => {},\n centerArtwork: () => {},\n release: () => {},\n reset: () => {},\n isAnimating: () => false,\n isFocused: () => false,\n };\n\n // Cardboard render (stereo split) is a kernel feature; the gyroscope head-look\n // that rides with it is the `@scenography/plugin-cardboard` system. The render\n // toggle below drives its `setEnabled`.\n type HeadTracking = { setEnabled(enabled: boolean): void };\n const headTracking = apis.get(CARDBOARD_SYSTEM) as HeadTracking | undefined;\n // Apply the restored (or seeded) flags on load: enable gyro head-look and sync\n // the stereo render. (On iOS the gyro permission can't be granted outside a user\n // gesture, so a restored gyro mode only truly tracks once the visitor interacts —\n // enable() fails gracefully, nothing breaks.)\n const initialFlags = controlSettings.get();\n render.cardboard = initialFlags.splitScreen;\n if (initialFlags.gyro) headTracking?.setEnabled(true);\n\n // gyro and splitScreen are *orthogonal* here — unlike the legacy `cardboard`\n // render toggle that fused them — so a \"VR\" mode can aim with the gyro without\n // the stereo split. Apply those two effects (head-look system + stereo render)\n // as the flags change; the others are read by the overlay and input consumers.\n // render.cardboard / the initial setEnabled above already reflect the seed, so\n // the subscriber only handles subsequent changes.\n let prevFlags = controlSettings.get();\n controlSettings.subscribe((flags) => {\n if (flags.gyro !== prevFlags.gyro) headTracking?.setEnabled(flags.gyro);\n if (flags.splitScreen !== prevFlags.splitScreen) render.cardboard = flags.splitScreen;\n prevFlags = flags;\n saveControlFlags(flags); // remember the choice across reloads\n emit(\"controls:change\", { flags, mode: controlSettings.getMode() });\n });\n\n // No-op fallbacks so the overlay/behavior stay safe when a plugin is absent.\n const lightsApi = (apis.get(CTX_SLOT.lights) as LightsApi | undefined) ?? {\n set: () => {},\n dimAll: () => {},\n restore: () => {},\n };\n const videoApi = (apis.get(CTX_SLOT.video) as VideoApi | undefined) ?? {\n play: () => {},\n pause: () => {},\n setVisible: () => {},\n };\n const skyApi = (apis.get(CTX_SLOT.sky) as SkyApi | undefined) ?? {\n setTime: () => {},\n getTime: () => 12,\n setAzimuth: () => {},\n getAzimuth: () => 0,\n };\n const mediaApi = (apis.get(CTX_SLOT.media) as MediaController | undefined) ?? {\n play: () => {},\n pause: () => {},\n stop: () => {},\n setVolume: () => {},\n playing: () => false,\n setMuted: () => {},\n muted: () => false,\n };\n\n // Per-element interaction-distance gate, derived from each pickable element's\n // `infoRadius`. Read structurally: `infoRadius` is a convention a plugin (the\n // artwork plugin) can put on its elements, not a kernel field. A later\n // interaction pass can move this onto the built object's userData entirely.\n type Gated = { id: string; infoRadius?: number };\n const radiiOf = (s: Scene): Map<string, number> => {\n const gated = (s as { artworks?: Gated[] }).artworks ?? [];\n return new Map(\n gated.filter((a) => a.infoRadius !== undefined).map((a) => [a.id, a.infoRadius as number]),\n );\n };\n\n // Picking is suppressed while the camera animates and while a piece stays\n // focused: once its info panel opens, hovering must not keep highlighting it.\n const interaction = new InteractionController(\n camera,\n canvas,\n threeScene,\n emit,\n () => !cameraApi.isAnimating() && !cameraApi.isFocused(),\n );\n interaction.setRadii(radiiOf(scene));\n interaction.attach();\n const clock = new Clock();\n let rafId: number | null = null;\n\n // Post-processing (MSAA antialias + SSAO + bloom). The whole chain is imported\n // on demand so it only ships when a gallery uses it; until it resolves (or when\n // everything is off) we render straight to the canvas. Antialiasing is MSAA on\n // the composer's render target (`samples`), so changing the sample count\n // rebuilds the composer; SSAO/bloom just flip `.enabled`.\n // Bloom spread/threshold are fixed; its strength is the live `render.bloom`.\n const BLOOM = { radius: 0.4, threshold: 0.85 };\n type PostClasses = {\n EffectComposer: typeof import(\"three/examples/jsm/postprocessing/EffectComposer.js\").EffectComposer;\n RenderPass: typeof import(\"three/examples/jsm/postprocessing/RenderPass.js\").RenderPass;\n SSAOPass: typeof import(\"three/examples/jsm/postprocessing/SSAOPass.js\").SSAOPass;\n UnrealBloomPass: typeof import(\"three/examples/jsm/postprocessing/UnrealBloomPass.js\").UnrealBloomPass;\n OutputPass: typeof import(\"three/examples/jsm/postprocessing/OutputPass.js\").OutputPass;\n };\n let postClasses: PostClasses | null = null;\n let composer: EffectComposer | null = null;\n let ssaoPass: (Pass & { kernelRadius: number }) | null = null;\n let bloomPass: (Pass & { setSize(w: number, h: number): void; strength: number }) | null = null;\n let composerLoading = false;\n\n const pixelRatio = Math.min(window.devicePixelRatio, 2);\n const needsComposer = (): boolean => render.antialias > 0 || render.ssao || render.bloom > 0;\n\n // (Re)build the composer from the loaded classes at the current sample count.\n const buildComposer = (): void => {\n if (!postClasses) return;\n composer?.dispose();\n const { EffectComposer, RenderPass, SSAOPass, UnrealBloomPass, OutputPass } = postClasses;\n // Multisampled target → MSAA that survives the post chain. `samples` is\n // preserved across resizes (setSize only changes width/height). HalfFloat\n // matches the composer's own default and gives bloom its HDR range.\n const target = new WebGLRenderTarget(1, 1, { samples: render.antialias, type: HalfFloatType });\n const c = new EffectComposer(renderer, target);\n c.setPixelRatio(pixelRatio);\n c.setSize(width, height);\n c.addPass(new RenderPass(threeScene, camera));\n const ssao = new SSAOPass(threeScene, camera, width, height);\n ssao.kernelRadius = 16;\n ssao.enabled = render.ssao;\n c.addPass(ssao);\n const bloom = new UnrealBloomPass(new Vector2(width, height), render.bloom, BLOOM.radius, BLOOM.threshold);\n bloom.enabled = render.bloom > 0;\n c.addPass(bloom);\n // Restores tone mapping / sRGB that the composer would otherwise skip.\n c.addPass(new OutputPass());\n composer = c;\n ssaoPass = ssao;\n bloomPass = bloom;\n };\n\n const ensureComposer = (): void => {\n if (!needsComposer()) return;\n if (postClasses) {\n if (!composer) buildComposer();\n return;\n }\n if (composerLoading) return;\n composerLoading = true;\n void Promise.all([\n import(\"three/examples/jsm/postprocessing/EffectComposer.js\"),\n import(\"three/examples/jsm/postprocessing/RenderPass.js\"),\n import(\"three/examples/jsm/postprocessing/SSAOPass.js\"),\n import(\"three/examples/jsm/postprocessing/UnrealBloomPass.js\"),\n import(\"three/examples/jsm/postprocessing/OutputPass.js\"),\n ]).then(([{ EffectComposer }, { RenderPass }, { SSAOPass }, { UnrealBloomPass }, { OutputPass }]) => {\n postClasses = { EffectComposer, RenderPass, SSAOPass, UnrealBloomPass, OutputPass };\n composerLoading = false;\n buildComposer();\n });\n };\n\n // Recompile materials so a shadowMap.enabled change takes effect.\n const refreshShadows = (): void => {\n renderer.shadowMap.enabled = render.shadows;\n threeScene.traverse((obj) => {\n const mat = (obj as { material?: unknown }).material;\n if (!mat) return;\n for (const m of Array.isArray(mat) ? mat : [mat]) (m as { needsUpdate: boolean }).needsUpdate = true;\n });\n };\n refreshShadows();\n ensureComposer();\n\n // Split the canvas into a left/right eye pair. Mirrors what StereoEffect does\n // internally, but stays in our loop and uses only setViewport/setScissor.\n const renderStereo = (): void => {\n camera.updateWorldMatrix(true, false);\n stereo.update(camera);\n renderer.getSize(stereoSize);\n const w = stereoSize.x;\n const h = stereoSize.y;\n if (renderer.autoClear) renderer.clear();\n renderer.setScissorTest(true);\n renderer.setScissor(0, 0, w / 2, h);\n renderer.setViewport(0, 0, w / 2, h);\n renderer.render(threeScene, stereo.cameraL);\n renderer.setScissor(w / 2, 0, w / 2, h);\n renderer.setViewport(w / 2, 0, w / 2, h);\n renderer.render(threeScene, stereo.cameraR);\n renderer.setScissorTest(false);\n renderer.setViewport(0, 0, w, h);\n };\n\n const renderFrame = (): void => {\n rafId = requestAnimationFrame(renderFrame);\n const delta = clock.getDelta();\n const deltaMs = delta * 1000;\n // Systems drive the camera in registration order: the roaming camera first,\n // then the framing system, which may override it during a focus animation.\n for (const system of systems) system.update?.(deltaMs);\n interaction.update(deltaMs);\n manager.updateElements(deltaMs);\n for (const controller of controllers) controller.update?.(deltaMs);\n // Behavior runs after the camera moved this frame, before rendering. (The\n // cardboard system, last in `systems`, already had the final say on\n // orientation above.)\n for (const handler of frameHandlers) handler(deltaMs);\n // Auxiliary passes (e.g. the water's planar reflection) render into their\n // own targets from the current camera, before the main frame.\n manager.beforeRender(renderer, threeScene, camera);\n if (render.cardboard) renderStereo();\n else if (composer && needsComposer()) composer.render(delta);\n else renderer.render(threeScene, camera);\n };\n\n const ctx: GalleryContext = {\n entity: (id) => manager.get(id),\n lights: lightsApi,\n media: mediaApi,\n controls: {\n setMoveInput: (forward, strafe) => input.setMove(forward, strafe),\n setLookInput: (x, y) => input.setLook(x, y),\n },\n viewControls: {\n get: () => controlSettings.get(),\n getMode: () => controlSettings.getMode(),\n setMode: (mode) => controlSettings.setMode(mode),\n setFlag: (key, value) => controlSettings.setFlag(key, value),\n subscribe: (fn) => controlSettings.subscribe(fn),\n },\n environment: {\n setBackgroundIntensity: (v) => environment.setBackgroundIntensity(v),\n },\n sky: skyApi,\n camera: cameraApi,\n video: videoApi,\n three: { scene: threeScene, camera, renderer },\n };\n\n return {\n ctx,\n setScene(next) {\n manager.reconcile(next);\n interaction.setRadii(radiiOf(next));\n environment.reconcile(next.environment);\n for (const system of systems) system.setScene?.(next);\n const fov = next.camera.fov ?? 55;\n if (camera.fov !== fov) {\n camera.fov = fov;\n camera.updateProjectionMatrix();\n }\n },\n start() {\n if (rafId === null) {\n clock.start();\n renderFrame();\n }\n },\n stop() {\n if (rafId !== null) {\n cancelAnimationFrame(rafId);\n rafId = null;\n }\n },\n dispose() {\n this.stop();\n for (const system of systems) system.dispose?.();\n for (const controller of controllers) controller.dispose?.();\n interaction.detach();\n environment.dispose();\n manager.dispose();\n composer?.dispose();\n renderer.dispose();\n listeners.clear();\n frameHandlers.clear();\n },\n on(event, handler) {\n const set = listeners.get(event) ?? new Set();\n set.add(handler);\n listeners.set(event, set);\n return () => set.delete(handler);\n },\n emit(event, payload) {\n emit(event, payload);\n },\n onFrame(handler) {\n frameHandlers.add(handler);\n return () => frameHandlers.delete(handler);\n },\n resize(w, h) {\n camera.aspect = w / h;\n camera.updateProjectionMatrix();\n renderer.setSize(w, h, false);\n composer?.setSize(w, h);\n ssaoPass?.setSize(w, h);\n bloomPass?.setSize(w, h);\n },\n setRender(next) {\n if (next.shadows !== undefined && next.shadows !== render.shadows) {\n render.shadows = next.shadows;\n refreshShadows();\n }\n if (next.ssao !== undefined) render.ssao = next.ssao;\n if (next.bloom !== undefined) render.bloom = next.bloom;\n // The sample count lives on the render target, so a change rebuilds the\n // composer (cheap, and only on user toggle).\n const aaChanged = next.antialias !== undefined && next.antialias !== render.antialias;\n if (next.antialias !== undefined) render.antialias = next.antialias;\n if (next.cardboard !== undefined && next.cardboard !== render.cardboard) {\n // Legacy stereo toggle: route through the control state so mode/flags and\n // the render stay one source of truth (the subscriber applies gyro +\n // split; setEnabled thus still runs in this click gesture, which iOS needs\n // for the motion-permission prompt). Superseded by `ctx.viewControls` once\n // the mode picker replaces the cardboard button.\n controlSettings.setMode(next.cardboard ? \"cardboard\" : \"touch\");\n }\n if (next.eyeSeparation !== undefined) {\n render.eyeSeparation = next.eyeSeparation;\n stereo.eyeSep = next.eyeSeparation;\n }\n if (ssaoPass) ssaoPass.enabled = render.ssao;\n if (bloomPass) {\n bloomPass.strength = render.bloom;\n bloomPass.enabled = render.bloom > 0;\n }\n if (aaChanged && composer) buildComposer();\n ensureComposer(); // load/build the chain if an effect was just turned on\n },\n };\n}\n"],"mappings":";;;;AAIA,MAAM,YAAY,KAAa,WAAyC;CACtE,IAAI,IAAI,YAAY,CAAC,CAAC,SAAS,MAAM,GACnC,OAAY,0CAA0C,CAAC,MAAM,EAAE,gBAAgB;EAC7E,IAAI,UAAU,CAAC,CAAC,KAAK,KAAK,MAAM;CAClC,CAAC;MAED,OAAY,2CAA2C,CAAC,MAAM,EAAE,iBAAiB;EAC/E,IAAI,WAAW,CAAC,CAAC,KAAK,KAAK,MAAM;CACnC,CAAC;AAEL;;;;;;;;AASA,IAAa,wBAAb,MAAmC;CACjC;CACA,OAA8B;CAC9B,UAAkC;CAElC,YAAY,OAAmB;EAC7B,KAAK,QAAQ;CACf;CAEA,UAAU,KAAoC;EAC5C,MAAM,OAAO,MAAM,KAAK,UAAU,GAAG,IAAI;EACzC,IAAI,SAAS,KAAK,MAAM;EACxB,KAAK,OAAO;EACZ,KAAK,MAAM;EACX,IAAI,CAAC,KAAK;EAEV,SAAS,IAAI,MAAM,QAAQ;GAEzB,IAAI,KAAK,SAAS,MAAM;IACtB,IAAI,QAAQ;IACZ;GACF;GACA,IAAI,UAAUA,MAAAA;GACd,KAAK,UAAU;GACf,IAAI,IAAI,cAAc,MAAM;IAC1B,KAAK,MAAM,aAAa;IACxB,KAAK,MAAM,sBAAsB,IAAI,aAAa;GACpD;GACA,IAAI,IAAI,YAAY,MAAM,KAAK,MAAM,cAAc;EACrD,CAAC;CACH;;CAGA,uBAAuB,OAAqB;EAC1C,KAAK,MAAM,sBAAsB;CACnC;CAEA,QAAsB;EACpB,KAAK,MAAM,aAAa;EACxB,KAAK,MAAM,cAAc;EACzB,KAAK,SAAS,QAAQ;EACtB,KAAK,UAAU;CACjB;CAEA,UAAgB;EACd,KAAK,MAAM;EACX,KAAK,OAAO;CACd;AACF;;;AC3DA,IAAI,SAAqC;;;;;AAMzC,MAAa,iBAAiB,UAAyB,SAAuB;CAC5E,SAAS,OAAO,2CAA2C,CAAC,MAAM,EAAE,iBAAiB;EACnF,MAAM,WAAW,IAAI,WAAW;EAChC,SAAS,kBAAkB,IAAI;EAC/B,SAAS,cAAc,QAAQ;EAC/B,OAAO;CACT,CAAC;AACH;;AAGA,MAAa,sBAAkD;;;;ACf/D,MAAa,cAAc,KAAa,QAAc,SACpD,GAAG,IAAI,GAAG,OAAO,GAAG,GAAG,OAAO,GAAG,GAAG,OAAO,SAAS;;AAGtD,MAAa,oBAAoB,SAAkB,QAAc,SAAwB;CACvF,QAAQ,QAAQC,MAAAA;CAChB,QAAQ,QAAQA,MAAAA;CAChB,QAAQ,OAAO,IAAI,OAAO,IAAI,OAAO,EAAE;CACvC,QAAQ,aAAa,OAAOC,MAAAA,iBAAiBC,MAAAA;AAC/C;;;;;;;AAQA,IAAa,eAAb,MAA0B;CAIK;CAH7B,0BAA2B,IAAI,IAAmB;CAClD,yBAA0B,IAAI,IAAoB;CAElD,YAAY,QAAwC;EAAvB,KAAA,SAAA;CAAwB;;CAGrD,QAAQ,KAAa,QAAc,MAAwB;EACzD,MAAM,MAAM,WAAW,KAAK,QAAQ,IAAI;EACxC,MAAM,WAAW,KAAK,QAAQ,IAAI,GAAG;EACrC,IAAI,UAAU;GACZ,SAAS,QAAQ;GACjB,OAAO,SAAS;EAClB;EACA,MAAM,UAAU,KAAK,OAAO,GAAG;EAC/B,iBAAiB,SAAS,QAAQ,IAAI;EACtC,MAAM,QAAQ;GAAE;GAAK;GAAS,MAAM;EAAE;EACtC,KAAK,QAAQ,IAAI,KAAK,KAAK;EAC3B,KAAK,OAAO,IAAI,SAAS,KAAK;EAC9B,OAAO;CACT;;CAGA,QAAQ,SAAwB;EAC9B,MAAM,QAAQ,KAAK,OAAO,IAAI,OAAO;EACrC,IAAI,CAAC,OAAO;GACV,QAAQ,QAAQ;GAChB;EACF;EACA,MAAM,QAAQ;EACd,IAAI,MAAM,OAAO,GAAG;EACpB,KAAK,QAAQ,OAAO,MAAM,GAAG;EAC7B,KAAK,OAAO,OAAO,OAAO;EAC1B,QAAQ,QAAQ;CAClB;;CAGA,IAAI,OAAe;EACjB,OAAO,KAAK,QAAQ;CACtB;AACF;;;AChEA,MAAM,gBAAgB,IAAIC,MAAAA,cAAc;AACxC,MAAM,QAAQ,IAAI,cAAc,QAAQ,cAAc,KAAK,GAAG,CAAC;AAW/D,MAAM,8BAAc,IAAI,IAAuB;AAC/C,MAAM,6BAAa,IAAI,IAAwB;;;;;;AAO/C,MAAM,YACJ,UACA,MACA,KACA,QACA,SACS;CACT,MAAM,SAAS,cAAc;CAC7B,IAAI,CAAC,QAAQ;EACX,QAAQ,KAAK,iBAAiB,IAAI,uCAAuC;EACzE;CACF;CACA,MAAM,MAAM,WAAW,KAAK,QAAQ,IAAI;CACxC,IAAI,QAAQ,YAAY,IAAI,GAAG;CAC/B,IAAI,CAAC,OAAO;EACV,QAAQ;GAAE;GAAK,MAAM;GAAG,SAAS,CAAC;EAAE;EACpC,YAAY,IAAI,KAAK,KAAK;EAC1B,MAAM,UAAU;EAChB,OAAY,MAAM,SAChB,KAAK,KACH,MACC,YAAY;GACX,iBAAiB,SAAS,QAAQ,IAAI;GACtC,QAAQ,cAAc;GACtB,QAAQ,UAAU;GAClB,WAAW,IAAI,SAAS,OAAO;GAC/B,KAAK,MAAM,UAAU,QAAQ,SAAS,OAAO,OAAO;GACpD,QAAQ,UAAU,CAAC;EACrB,GACA,KAAA,IACC,UAAU,QAAQ,MAAM,gCAAgC,IAAI,KAAK,KAAK,CACzE,CACF;CACF;CACA,MAAM,QAAQ;CACd,IAAI,MAAM,SAAS;EACjB,SAAS,QAAQ,MAAM;EACvB,SAAS,cAAc;CACzB,OACE,MAAM,QAAQ,MAAM,YAAY;EAC9B,SAAS,QAAQ;EACjB,SAAS,cAAc;CACzB,CAAC;AAEL;;;;;;;AAQA,MAAM,WACJ,UACA,MACA,KACA,QACA,SACS;CACT,IAAI,IAAI,YAAY,CAAC,CAAC,SAAS,OAAO,GAAG;EACvC,SAAS,UAAU,MAAM,KAAK,QAAQ,IAAI;EAC1C;CACF;CACA,IAAI,IAAI,YAAY,CAAC,CAAC,SAAS,MAAM,GAAG;EACtC,OAAY,0CAA0C,CAAC,MAAM,EAAE,gBAAgB;GAC7E,IAAI,UAAU,CAAC,CAAC,KAAK,MAAM,YAAY;IACrC,iBAAiB,SAAS,QAAQ,IAAI;IACtC,QAAQ,cAAc;IACtB,SAAS,QAAQ;IACjB,SAAS,cAAc;GACzB,CAAC;EACH,CAAC;EACD;CACF;CACA,SAAS,QAAQ,MAAM,QAAQ,KAAK,QAAQ,IAAI;AAClD;;;;;;AAOA,MAAa,gBACX,UACA,YACY;CACZ,MAAM,SAAS,QAAQ,UAAU,CAAC,GAAG,CAAC;CACtC,QAAQ,UAAU,OAAO,QAAQ,KAAK,QAAQ,IAAI;CAClD,IAAI,QAAQ,QAAQ;EAClB,QAAQ,UAAU,aAAa,QAAQ,QAAQ,QAAQ,KAAK;EAC5D,MAAM,QAAQ,QAAQ,eAAe;EACrC,SAAS,cAAc,IAAIC,MAAAA,QAAQ,OAAO,KAAK;CACjD;CACA,IAAI,QAAQ,WAAW,QAAQ,UAAU,gBAAgB,QAAQ,WAAW,QAAQ,KAAK;CACzF,IAAI,QAAQ,IAAI,QAAQ,UAAU,SAAS,QAAQ,IAAI,QAAQ,KAAK;CACpE,SAAS,cAAc;CACvB,OAAO,QAAQ,OAAO,KAAA;AACxB;;;;;AAMA,MAAa,kBAAkB,YAA2B;CACxD,MAAM,OAAO,WAAW,IAAI,OAAO;CACnC,IAAI,CAAC,MAAM;EACT,MAAM,QAAQ,OAAO;EACrB;CACF;CACA,KAAK,QAAQ;CACb,IAAI,KAAK,OAAO,GAAG;CACnB,WAAW,OAAO,OAAO;CACzB,YAAY,OAAO,KAAK,GAAG;CAC3B,QAAQ,QAAQ;AAClB;;;AC9HA,MAAM,kBAAkB;AACxB,MAAM,sBAAsB;AAC5B,MAAM,mBAAmB;AACzB,MAAM,aAAa;AACnB,MAAM,gBAAgB;;;;;;;;;AAsBtB,IAAa,wBAAb,MAAmC;CAgBd;CACA;CACA;CACA;CACA;CAnBnB,YAA6B,IAAIC,MAAAA,UAAU;CAC3C,UAA2B,IAAIC,MAAAA,QAAQ;CACvC,OAAe;CACf,UAAmD;CACnD,wBAAgB,IAAI,IAAoB;CACxC,UAAiC;CACjC,OAAe;CACf,cAAsB;CACtB,eAAuB;CAEvB,YAA6B,IAAIC,MAAAA,MAAM;CACvC,eAAiD;CACjD,WAAyE,CAAC;CAE1E,YACE,QACA,QACA,OACA,MACA,aACA;EALiB,KAAA,SAAA;EACA,KAAA,SAAA;EACA,KAAA,QAAA;EACA,KAAA,OAAA;EACA,KAAA,cAAA;EAEjB,KAAK,UAAU,UAAU;EACzB,KAAK,MAAM,IAAI,KAAK,SAAS;CAC/B;;CAGA,SAAS,OAAkC;EACzC,KAAK,QAAQ;CACf;CAEA,SAAe;EACb,MAAM,QAAQ,MAAc,aAAkC;GAC5D,KAAK,OAAO,iBAAiB,MAAM,QAAQ;GAC3C,KAAK,SAAS,KAAK;IAAC,KAAK;IAAQ;IAAM;GAAQ,CAAC;EAClD;EACA,KAAK,gBAAgB,MAAM,KAAK,OAAO,CAAiB,CAAC;EACzD,KAAK,gBAAgB,MAAM,KAAK,OAAO,CAAiB,CAAC;EACzD,KAAK,cAAc,MAAM,KAAK,KAAK,CAAiB,CAAC;EACrD,KAAK,sBAAsB;GACzB,KAAK,OAAO;EACd,CAAC;CACH;CAEA,SAAe;EACb,KAAK,MAAM,CAAC,QAAQ,MAAM,aAAa,KAAK,UAAU,OAAO,oBAAoB,MAAM,QAAQ;EAC/F,KAAK,SAAS,SAAS;EACvB,KAAK,MAAM,OAAO,KAAK,SAAS;EAChC,KAAK,eAAe;CACtB;CAEA,WAAmB,GAAuB;EACxC,MAAM,OAAO,KAAK,OAAO,sBAAsB;EAC/C,KAAK,QAAQ,KACT,EAAE,UAAU,KAAK,QAAQ,KAAK,QAAS,IAAI,GAC7C,GAAG,EAAE,UAAU,KAAK,OAAO,KAAK,UAAU,IAAI,CAChD;EACA,KAAK,OAAO;EACZ,KAAK,eAAe;CACtB;CAEA,OAAe,GAAuB;EACpC,KAAK,WAAW,CAAC;CACnB;CAEA,OAAe,GAAuB;EACpC,KAAK,UAAU;GAAE,GAAG,EAAE;GAAS,GAAG,EAAE;EAAQ;CAC9C;CAEA,KAAa,GAAuB;EAClC,MAAM,OAAO,KAAK;EAClB,KAAK,UAAU;EACf,IAAI,CAAC,QAAQ,CAAC,KAAK,YAAY,GAAG;EAClC,IAAI,KAAK,MAAM,EAAE,UAAU,KAAK,GAAG,EAAE,UAAU,KAAK,CAAC,IAAI,YAAY;EACrE,KAAK,WAAW,CAAC;EACjB,MAAM,OAAO,KAAK,QAAQ;EAC1B,IAAI,MAAM,KAAK,KAAK,kBAAkB,EAAE,IAAI,KAAK,GAAG,CAAC;CACvD;;CAGA,UAA+B;EAC7B,KAAK,UAAU,cAAc,KAAK,SAAS,KAAK,MAAM;EACtD,MAAM,OAAO,KAAK,UAAU,iBAAiB,KAAK,MAAM,UAAU,IAAI;EACtE,KAAK,MAAM,OAAO,MAAM;GACtB,IAAI,OAAwB,IAAI;GAChC,OAAO,QAAQ,KAAK,SAAS,eAAe,KAAA,GAAW,OAAO,KAAK;GACnE,IAAI,CAAC,MAAM;GACX,MAAM,KAAK,KAAK,SAAS;GACzB,MAAM,SAAS,KAAK,MAAM,IAAI,EAAE;GAChC,IAAI,WAAW,KAAA,GAAW;IACxB,MAAM,IAAI,KAAK,iBAAiB,IAAIC,MAAAA,QAAQ,CAAC;IAC7C,IAAI,KAAK,MAAM,KAAK,OAAO,SAAS,IAAI,EAAE,GAAG,KAAK,OAAO,SAAS,IAAI,EAAE,CAAC,IAAI,QAAQ;GACvF;GACA,OAAO;IAAE;IAAI,QAAQ;IAAM,MAAM,KAAK,SAAS;GAAqB;EACtE;EACA,OAAO;CACT;;;;;;;;CASA,OAAO,SAAuB;EAC5B,KAAK,QAAQ;EAEb,IAAI,CAAC,KAAK,QAAQ,CAAC,KAAK,YAAY,GAAG;GAErC,KAAK,cAAc;GACnB,KAAK,eAAe;GACpB,KAAK,UAAU,IAAI;EACrB,OAAO;GACL,KAAK,eAAe;GACpB,IAAI,KAAK,cAAc,KAAK,cAAc;GAC1C,KAAK,eAAe;GACpB,IAAI,KAAK,eAAe,eAAe;IACrC,KAAK,cAAc;IACnB,KAAK,UAAU,KAAK,QAAQ,CAAC;GAC/B;EACF;EAEA,IAAI,KAAK,UAAU,WAAW,KAAK,cACjC,KAAK,aAAa,UAAU,MAAO,OAAQ,KAAM,KAAM,KAAK,IAAI,KAAK,OAAO,GAAG;CAEnF;;CAGA,UAAkB,MAAyB;EACzC,KAAK,MAAM,MAAM,UAAU,KAAK,SAAS;EACzC,KAAK,UAAU,MAAM,MAAM;EAC3B,KAAK,OAAO,MAAM,SAAS,OAAO,YAAY;EAC9C,IAAI,MAAM,KAAK,eAAe,IAAI;OAC7B,KAAK,UAAU,UAAU;CAChC;CAEA,iBAA+B;EAC7B,KAAK,MAAM,SAAS,CAAC,GAAG,KAAK,UAAU,QAAQ,GAAG;GAChD,KAAK,UAAU,OAAO,KAAK;GAC3B,MAAgB,UAAU,QAAQ;EACpC;EACA,KAAK,cAAc,QAAQ;EAC3B,KAAK,eAAe;CACtB;;CAGA,eAAuB,MAAkB;EACvC,KAAK,eAAe;EACpB,MAAM,MAAM,IAAIC,MAAAA,kBAAkB;GAAE,OAAO;GAAiB,aAAa;GAAM,WAAW;EAAM,CAAC;EACjG,KAAK,eAAe;EAEpB,MAAM,IAAI,KAAK,KAAK,QAAQ,IAAI;EAChC,MAAM,IAAI,KAAK,KAAK,SAAS,IAAI;EACjC,MAAM,IAAI;EAEV,MAAM,OAA2C;GAC/C;IAAC,IAAI,IAAI;IAAG;IAAG;IAAG,IAAI,IAAI,IAAI;GAAC;GAC/B;IAAC,IAAI,IAAI;IAAG;IAAG;IAAG,EAAE,IAAI,IAAI,IAAI;GAAE;GAClC;IAAC;IAAG;IAAG,EAAE,IAAI,IAAI,IAAI;IAAI;GAAC;GAC1B;IAAC;IAAG;IAAG,IAAI,IAAI,IAAI;IAAG;GAAC;EACzB;EACA,KAAK,MAAM,CAAC,IAAI,IAAI,GAAG,MAAM,MAAM;GACjC,MAAM,MAAM,IAAIC,MAAAA,KAAK,IAAIC,MAAAA,YAAY,IAAI,IAAI,EAAG,GAAG,GAAG;GACtD,IAAI,SAAS,IAAI,GAAG,GAAG,CAAC;GACxB,IAAI,cAAc;GAClB,KAAK,UAAU,IAAI,GAAG;EACxB;EAEA,MAAM,MAAM,KAAK,OAAO,iBAAiB,IAAIH,MAAAA,QAAQ,CAAC;EACtD,MAAM,OAAO,KAAK,OAAO,mBAAmB,IAAII,MAAAA,WAAW,CAAC;EAC5D,MAAM,SAAS,IAAIJ,MAAAA,QAAQ,GAAG,GAAG,CAAC,CAAC,CAAC,gBAAgB,IAAI;EACxD,IAAI,gBAAgB,QAAQ,KAAK,KAAK,WAAW;EACjD,KAAK,UAAU,SAAS,KAAK,GAAG;EAChC,KAAK,UAAU,WAAW,KAAK,IAAI;EACnC,KAAK,UAAU,UAAU;CAC3B;AACF;;;;;;;;;;;ACjNA,IAAa,WAAb,MAAsB;;CAEpB,cAAc;;CAEd,aAAa;;CAEb,QAAQ;;CAER,QAAQ;;CAGR,QAAQ,SAAiB,SAAS,GAAS;EACzC,KAAK,cAAc;EACnB,KAAK,aAAa;CACpB;;CAGA,QAAQ,GAAW,IAAI,GAAS;EAC9B,KAAK,QAAQ;EACb,KAAK,QAAQ;CACf;AACF;;;;;;;;;ACWA,MAAa,eAAkE;CAC7E,OAAO;EAAE,MAAM;EAAO,aAAa;EAAO,WAAW;CAAK;CAC1D,IAAI;EAAE,MAAM;EAAM,aAAa;EAAO,WAAW;CAAK;CACtD,WAAW;EAAE,MAAM;EAAM,aAAa;EAAM,WAAW;CAAM;AAC/D;;AAGA,MAAa,wBAAsC;CACjD,GAAG,aAAa;CAChB,aAAa;CACb,aAAa;AACf;AAEA,MAAMK,cAA6C;CACjD;CACA;CACA;CACA;CACA;AACF;AAEA,MAAM,cAAc,GAAiB,MACnCA,YAAU,OAAO,QAAQ,EAAE,SAAS,EAAE,IAAI;;AAG5C,SAAgB,OAAO,OAAkC;CACvD,KAAK,MAAM,QAAQ;EAAC;EAAS;EAAM;CAAW,GAAY;EACxD,MAAM,SAAS,aAAa;EAC5B,IACE,MAAM,SAAS,OAAO,QACtB,MAAM,gBAAgB,OAAO,eAC7B,MAAM,cAAc,OAAO,WAE3B,OAAO;CACX;CACA,OAAO;AACT;;AAGA,SAAgB,UACd,OACA,MACc;CACd,OAAO;EAAE,GAAG;EAAO,GAAG,aAAa;CAAM;AAC3C;;;;;;;AAQA,IAAa,kBAAb,MAA6B;CAC3B;CACA,8BAA+B,IAAI,IAAmC;CAEtE,YAAY,UAAiC,CAAC,GAAG;EAC/C,KAAK,QAAQ;GAAE,GAAG;GAAuB,GAAG;EAAQ;CACtD;;CAGA,MAAoB;EAClB,OAAO,EAAE,GAAG,KAAK,MAAM;CACzB;;CAGA,UAAuB;EACrB,OAAO,OAAO,KAAK,KAAK;CAC1B;;CAGA,QAAQ,MAA4C;EAClD,KAAK,QAAQ,UAAU,KAAK,OAAO,IAAI,CAAC;CAC1C;;CAGA,QAAsC,KAAQ,OAA8B;EAC1E,KAAK,QAAQ;GAAE,GAAG,KAAK;IAAQ,MAAM;EAAM,CAAC;CAC9C;;CAGA,UAAU,IAA+C;EACvD,KAAK,YAAY,IAAI,EAAE;EACvB,aAAa,KAAK,YAAY,OAAO,EAAE;CACzC;CAEA,QAAgB,MAA0B;EACxC,IAAI,WAAW,KAAK,OAAO,IAAI,GAAG;EAClC,KAAK,QAAQ;EACb,MAAM,WAAW,KAAK,IAAI;EAC1B,KAAK,MAAM,MAAM,KAAK,aAAa,GAAG,QAAQ;CAChD;AACF;;;;;;;;;AC5HA,MAAM,cAAc;AAEpB,MAAM,YAAY,OAAO,KAAK,qBAAqB;;AAGnD,SAAgB,mBAAiD;CAC/D,IAAI;EACF,MAAM,MAAM,WAAW,cAAc,QAAQ,WAAW;EACxD,IAAI,CAAC,KAAK,OAAO;EACjB,MAAM,SAAS,KAAK,MAAM,GAAG;EAC7B,MAAM,QAA+B,CAAC;EAEtC,KAAK,MAAM,OAAO,WAChB,IAAI,OAAO,OAAO,SAAS,WAAW,MAAM,OAAO,OAAO;EAE5D,OAAO;CACT,QAAQ;EACN,OAAO;CACT;AACF;;AAGA,SAAgB,iBAAiB,OAA2B;CAC1D,IAAI;EACF,WAAW,cAAc,QAAQ,aAAa,KAAK,UAAU,KAAK,CAAC;CACrE,QAAQ,CAER;AACF;;;;;;;;;;AC7BA,IAAa,qBAAb,MAAgC;CAC9B,yBAA0B,IAAI,IAAqB;CACnD,2BAA4B,IAAI,IAAoB;;CAEpD,yBAA0B,IAAI,IAAoB;;;;;;;CAQlD,QAAW,MAAc,OAAU,OAAsB;EACvD,IAAI,UAAU,KAAA,KAAa,KAAK,OAAO,IAAI,IAAI,KAAK,KAAK,OAAO,IAAI,IAAI,MAAM,OAAO;EACrF,KAAK,OAAO,IAAI,MAAM,KAAK;EAC3B,IAAI,UAAU,KAAA,GAAW,KAAK,OAAO,IAAI,MAAM,KAAK;OAC/C,KAAK,OAAO,OAAO,IAAI;EAC5B,KAAK,KAAK,IAAI;CAChB;;CAGA,OAAO,MAAoB;EACzB,IAAI,KAAK,OAAO,OAAO,IAAI,GAAG;GAC5B,KAAK,OAAO,OAAO,IAAI;GACvB,KAAK,KAAK,IAAI;EAChB;CACF;CAEA,IAAO,MAA6B;EAClC,OAAO,KAAK,OAAO,IAAI,IAAI;CAC7B;;CAGA,QAAQ,MAAsB;EAC5B,OAAO,KAAK,SAAS,IAAI,IAAI,KAAK;CACpC;CAEA,KAAa,MAAoB;EAC/B,KAAK,SAAS,IAAI,OAAO,KAAK,SAAS,IAAI,IAAI,KAAK,KAAK,CAAC;CAC5D;AACF;;;;ACoBA,MAAa,kBAAkB,WAC7B,OAAO,WAAW,YAAY,WAAW,QAAQ,WAAW;;;;ACjE9D,MAAa,eAAe;;;;ACA5B,MAAa,UAAU;;;;ACFvB,MAAa,YAAY;;;;ACEzB,MAAa,QAAQ;;;;;;;;ACErB,MAAa,aAAa;;;ACgB1B,MAAM,gBAAgB;CAAC;CAAO;CAAa;CAAgB;CAAS;AAAc;AAElF,MAAM,iBAAiB,WAA2B;CAChD,MAAM,4BAAY,IAAI,IAAa;CACnC,OAAO,UAAU,UAAU;EAEzB,MAAM,QAAQ,MAAM,UAAU;EAC9B,IAAI,OAAO;GACT,MAAM,MAAM;GACZ,MAAM,gBAAgB,KAAK;GAC3B,MAAM,KAAK;EACb;EACA,MAAM,OAAO;EACb,KAAK,UAAU,UAAU;EACzB,MAAM,WAAW,KAAK;EACtB,IAAI,MAAM,QAAQ,QAAQ,GAAG,KAAK,MAAM,SAAS,UAAU,UAAU,IAAI,KAAK;OACzE,IAAI,UAAU,UAAU,IAAI,QAAQ;CAC3C,CAAC;CAID,KAAK,MAAM,SAAS,WAAW;EAC7B,MAAM,WAAW;EACjB,KAAK,MAAM,QAAQ,eAAe;GAChC,MAAM,UAAU,SAAS;GACzB,IAAI,SAAS,eAAe,OAAO;EACrC;EACA,SAAS,UAAU;CACrB;AACF;;;;;;;;AASA,IAAa,eAAb,MAA0B;CACxB;CACA;CACA;CACA;;CAEA,iCAAkC,IAAI,IAAyC;CAE/E,YACE,MACA,UAA0B,CAAC,GAC3B,eAAmC,IAAI,mBAAmB,GAC1D,OAA+D;EAC7D,QAAQ,IAAIC,MAAAA,kBAAkB;EAC9B,UAAU,CAAC;CACb,GACA;EACA,KAAK,OAAO;EACZ,KAAK,UAAU;EACf,KAAK,eAAe;EACpB,KAAK,OAAO;CACd;;CAGA,UAAiC;EAC/B,OAAO;GAAE,cAAc,KAAK;GAAc,GAAG,KAAK;EAAK;CACzD;CAEA,UAAU,OAAoB;EAG5B,MAAM,MAAM,MAAM,OAAO,MAAM,SAAS;EACxC,KAAK,aAAa,QAAQ,YAAY,KAAK,OAAO,GAAG,CAAC;EAEtD,KAAK,iBAAiB,KAAK;CAC7B;;;;;;CAOA,iBAAyB,OAAoB;EAC3C,KAAK,MAAM,UAAU,KAAK,SAAS;GACjC,MAAM,EAAE,MAAM,gBAAgB,OAAO;GACrC,IAAI,UAAU,KAAK,eAAe,IAAI,IAAI;GAC1C,IAAI,CAAC,SAAS;IACZ,0BAAU,IAAI,IAAI;IAClB,KAAK,eAAe,IAAI,MAAM,OAAO;GACvC;GAEA,MAAM,MAAO,MAA6C;GAC1D,MAAM,QACJ,gBAAgB,SACZ,MAAM,QAAQ,GAAG,IACf,IAAI,KAAK,MAAM,MAAM,CAAC,OAAQ,KAAyB,MAAM,CAAC,GAAG,IAAI,CAAC,IACtE,CAAC,IACH,QAAQ,KAAA,IACN,CAAC,CAAC,IAAI,GAAG,CAAC,IACV,CAAC;GACT,MAAM,QAAQ,OAAO,aAAa,CAAC,EAAA,CAAG,KAAK,SAAS,KAAK,aAAa,QAAQ,IAAI,CAAC;GAEnF,MAAM,uBAAO,IAAI,IAAY;GAC7B,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO;IAChC,KAAK,IAAI,GAAG;IACZ,MAAM,OAAO,KAAK,UAAU;KAAE;KAAO;IAAK,CAAC;IAC3C,MAAM,WAAW,QAAQ,IAAI,GAAG;IAChC,IAAI,UAAU,SAAS,MAAM;IAC7B,IAAI,UAAU,KAAK,oBAAoB,SAAS,KAAK,QAAQ;IAC7D,MAAM,QAAQ,OAAO,MAAM,OAAgB,KAAK,QAAQ,CAAC;IACzD,IAAI,MAAM,QAAQ,KAAK,KAAK,IAAI,MAAM,MAAM;IAC5C,QAAQ,IAAI,KAAK;KAAE,GAAG;KAAO;IAAK,CAAC;GACrC;GACA,KAAK,MAAM,CAAC,KAAK,UAAU,SACzB,IAAI,CAAC,KAAK,IAAI,GAAG,GAAG,KAAK,oBAAoB,SAAS,KAAK,KAAK;GAIlE,OAAO,UACL,MAAM,KAAK,GAAG,WAAW,KAAK,GAC9B,KAAK,QAAQ,CACf;EACF;CACF;CAEA,oBACE,SACA,KACA,OACM;EACN,IAAI,MAAM,QAAQ;GAChB,KAAK,KAAK,OAAO,MAAM,MAAM;GAC7B,cAAc,MAAM,MAAM;EAC5B;EACA,MAAM,UAAU;EAChB,QAAQ,OAAO,GAAG;CACpB;;CAIA,eAAe,SAAuB;EACpC,KAAK,MAAM,WAAW,KAAK,eAAe,OAAO,GAC/C,KAAK,MAAM,SAAS,QAAQ,OAAO,GAAG,MAAM,SAAS,OAAO;CAChE;;CAGA,aAAa,UAAyB,OAAmB,QAAiC;EACxF,KAAK,MAAM,WAAW,KAAK,eAAe,OAAO,GAC/C,KAAK,MAAM,SAAS,QAAQ,OAAO,GAAG,MAAM,eAAe,UAAU,OAAO,MAAM;CACtF;;CAIA,IAAI,IAAkC;EACpC,KAAK,MAAM,WAAW,KAAK,eAAe,OAAO,GAAG;GAClD,MAAM,SAAS,QAAQ,IAAI,EAAE,CAAC,EAAE;GAChC,IAAI,QAAQ,OAAO;EACrB;CAEF;;CAGA,WAAW,MAA6C;EACtD,MAAM,sBAAM,IAAI,IAAsB;EACtC,MAAM,UAAU,KAAK,eAAe,IAAI,IAAI;EAC5C,IAAI;QAAc,MAAM,CAAC,IAAI,UAAU,SAAS,IAAI,MAAM,QAAQ,IAAI,IAAI,IAAI,MAAM,MAAM;EAAA;EAC1F,OAAO;CACT;CAEA,UAAgB;EACd,KAAK,MAAM,CAAC,MAAM,YAAY,KAAK,gBAAgB;GACjD,KAAK,MAAM,CAAC,KAAK,UAAU,SAAS,KAAK,oBAAoB,SAAS,KAAK,KAAK;GAChF,KAAK,eAAe,OAAO,IAAI;EACjC;CACF;AACF;;;;ACzJA,MAAM,WAAW;CACf,QAAQ;CACR,QAAQ;CACR,OAAO;CACP,KAAK;CACL,OAAO;AACT;;AAEA,MAAM,mBAAmB;AAsDzB,MAAM,yBAAyB;;;;;;AAiG/B,SAAgB,cAAc,OAAc,SAAkC;CAC5E,MAAM,EAAE,WAAW;CACnB,MAAM,QAAQ,OAAO,eAAe,OAAO;CAC3C,MAAM,SAAS,OAAO,gBAAgB,OAAO;CAI7C,MAAM,WAAW,IAAIC,MAAAA,cAAc;EAAE;EAAQ,WAAW;CAAM,CAAC;CAC/D,SAAS,cAAc,KAAK,IAAI,OAAO,kBAAkB,CAAC,CAAC;CAC3D,SAAS,QAAQ,OAAO,QAAQ,KAAK;CACrC,SAAS,UAAU,OAAOC,MAAAA;CAG1B,MAAM,SAAkC;EACtC,WAAW,QAAQ,aAAa;EAChC,MAAM,QAAQ,QAAQ;EACtB,OAAO,QAAQ,SAAS;EACxB,SAAS,QAAQ,WAAW;EAC5B,WAAW,QAAQ,aAAa;EAChC,eAAe,QAAQ,iBAAiB;CAC1C;CAKA,MAAM,SAAS,IAAIC,MAAAA,aAAa;CAChC,OAAO,SAAS;CAChB,OAAO,SAAS,OAAO;CACvB,MAAM,aAAa,IAAIC,MAAAA,QAAQ;CAE/B,MAAM,aAAa,IAAIC,MAAAA,MAAW;CAElC,MAAM,SAAS,IAAIC,MAAAA,kBAAkB,MAAM,OAAO,OAAO,IAAI,QAAQ,QAAQ,IAAK,GAAI;CACtF,MAAM,EAAE,aAAa,MAAM,OAAO;CAClC,OAAO,SAAS,IAAI,SAAS,IAAI,SAAS,IAAI,SAAS,EAAE;CAKzD,MAAM,aAAa,QAAQ,WAAW,CAAC;CACvC,MAAM,gBAAgB,WAAW,OAAO,cAAc;CACtD,MAAM,iBAAiB,WAAW,QAAQ,MAAyB,CAAC,eAAe,CAAC,CAAC;CAErF,MAAM,eAAe,IAAI,mBAAmB;CAC5C,IAAI,QAAQ,UAAU,cAAc,UAAU,QAAQ,QAAQ;CAC9D,MAAM,UAAU,IAAI,aAAa,YAAY,gBAAgB,cAAc;EACzE;EACA;CACF,CAAC;CACD,QAAQ,UAAU,KAAK;CAEvB,MAAM,QAAQ,IAAI,SAAS;CAM3B,MAAM,kBAAkB,IAAI,gBAC1B,iBAAiB,MAAM,QAAQ,YAAY,aAAa,YAAY,CAAC,EACvE;CACA,MAAM,cAAc,IAAI,sBAAsB,UAAU;CACxD,YAAY,UAAU,MAAM,WAAW;CAEvC,MAAM,4BAAY,IAAI,IAA8C;CACpE,MAAM,QAAQ,OAAe,YAA4B;EACvD,UAAU,IAAI,KAAK,CAAC,EAAE,SAAS,YAAY,QAAQ,OAAO,CAAC;CAC7D;CACA,MAAM,gCAAgB,IAAI,IAA+B;CAKzD,MAAM,uBAAO,IAAI,IAAqB;CAItC,MAAM,cAAmC,CAAC;CAC1C,KAAK,MAAM,UAAU,gBAAgB;EACnC,MAAM,aAAa,OAAO,mBAAmB,QAAQ,WAAW,OAAO,KAAK,IAAI,CAAC;EACjF,IAAI,CAAC,YAAY;EACjB,IAAI,WAAW,QAAQ,KAAA,GAAW,KAAK,IAAI,OAAO,KAAK,MAAM,WAAW,GAAG;EAC3E,YAAY,KAAK,UAAU;CAC7B;CAKA,MAAM,UAA4B,cAAc,KAAK,WAAW;EAC9D,MAAM,WAAW,OAAO,MAAM;GAC5B;GACA;GACA;GACA,SAAS;GACT;GACA;GACA,oBAAoB,gBAAgB,IAAI;GACxC;GACA;GACA,UAAU,OAAO,QAAQ,IAAI,EAAE;EACjC,CAAC;EACD,IAAI,SAAS,QAAQ,KAAA,GAAW,KAAK,IAAI,OAAO,MAAM,SAAS,GAAG;EAClE,OAAO;CACT,CAAC;CAID,MAAM,YAAwB,KAAK,IAAI,SAAS,MAAM,KAA+B;EACnF,oBAAoB,CAAC;EACrB,qBAAqB,CAAC;EACtB,eAAe,CAAC;EAChB,aAAa,CAAC;EACd,mBAAmB;EACnB,iBAAiB;CACnB;CAMA,MAAM,eAAe,KAAK,IAAI,gBAAgB;CAK9C,MAAM,eAAe,gBAAgB,IAAI;CACzC,OAAO,YAAY,aAAa;CAChC,IAAI,aAAa,MAAM,cAAc,WAAW,IAAI;CAQpD,IAAI,YAAY,gBAAgB,IAAI;CACpC,gBAAgB,WAAW,UAAU;EACnC,IAAI,MAAM,SAAS,UAAU,MAAM,cAAc,WAAW,MAAM,IAAI;EACtE,IAAI,MAAM,gBAAgB,UAAU,aAAa,OAAO,YAAY,MAAM;EAC1E,YAAY;EACZ,iBAAiB,KAAK;EACtB,KAAK,mBAAmB;GAAE;GAAO,MAAM,gBAAgB,QAAQ;EAAE,CAAC;CACpE,CAAC;CAGD,MAAM,YAAa,KAAK,IAAI,SAAS,MAAM,KAA+B;EACxE,WAAW,CAAC;EACZ,cAAc,CAAC;EACf,eAAe,CAAC;CAClB;CACA,MAAM,WAAY,KAAK,IAAI,SAAS,KAAK,KAA8B;EACrE,YAAY,CAAC;EACb,aAAa,CAAC;EACd,kBAAkB,CAAC;CACrB;CACA,MAAM,SAAU,KAAK,IAAI,SAAS,GAAG,KAA4B;EAC/D,eAAe,CAAC;EAChB,eAAe;EACf,kBAAkB,CAAC;EACnB,kBAAkB;CACpB;CACA,MAAM,WAAY,KAAK,IAAI,SAAS,KAAK,KAAqC;EAC5E,YAAY,CAAC;EACb,aAAa,CAAC;EACd,YAAY,CAAC;EACb,iBAAiB,CAAC;EAClB,eAAe;EACf,gBAAgB,CAAC;EACjB,aAAa;CACf;CAOA,MAAM,WAAW,MAAkC;EACjD,MAAM,QAAS,EAA6B,YAAY,CAAC;EACzD,OAAO,IAAI,IACT,MAAM,QAAQ,MAAM,EAAE,eAAe,KAAA,CAAS,CAAC,CAAC,KAAK,MAAM,CAAC,EAAE,IAAI,EAAE,UAAoB,CAAC,CAC3F;CACF;CAIA,MAAM,cAAc,IAAI,sBACtB,QACA,QACA,YACA,YACM,CAAC,UAAU,YAAY,KAAK,CAAC,UAAU,UAAU,CACzD;CACA,YAAY,SAAS,QAAQ,KAAK,CAAC;CACnC,YAAY,OAAO;CACnB,MAAM,QAAQ,IAAIC,MAAAA,MAAM;CACxB,IAAI,QAAuB;CAQ3B,MAAM,QAAQ;EAAE,QAAQ;EAAK,WAAW;CAAK;CAQ7C,IAAI,cAAkC;CACtC,IAAI,WAAkC;CACtC,IAAI,WAAqD;CACzD,IAAI,YAAuF;CAC3F,IAAI,kBAAkB;CAEtB,MAAM,aAAa,KAAK,IAAI,OAAO,kBAAkB,CAAC;CACtD,MAAM,sBAA+B,OAAO,YAAY,KAAK,OAAO,QAAQ,OAAO,QAAQ;CAG3F,MAAM,sBAA4B;EAChC,IAAI,CAAC,aAAa;EAClB,UAAU,QAAQ;EAClB,MAAM,EAAE,gBAAgB,YAAY,UAAU,iBAAiB,eAAe;EAK9E,MAAM,IAAI,IAAI,eAAe,UAAU,IADpBC,MAAAA,kBAAkB,GAAG,GAAG;GAAE,SAAS,OAAO;GAAW,MAAMC,MAAAA;EAAc,CAChD,CAAC;EAC7C,EAAE,cAAc,UAAU;EAC1B,EAAE,QAAQ,OAAO,MAAM;EACvB,EAAE,QAAQ,IAAI,WAAW,YAAY,MAAM,CAAC;EAC5C,MAAM,OAAO,IAAI,SAAS,YAAY,QAAQ,OAAO,MAAM;EAC3D,KAAK,eAAe;EACpB,KAAK,UAAU,OAAO;EACtB,EAAE,QAAQ,IAAI;EACd,MAAM,QAAQ,IAAI,gBAAgB,IAAIL,MAAAA,QAAQ,OAAO,MAAM,GAAG,OAAO,OAAO,MAAM,QAAQ,MAAM,SAAS;EACzG,MAAM,UAAU,OAAO,QAAQ;EAC/B,EAAE,QAAQ,KAAK;EAEf,EAAE,QAAQ,IAAI,WAAW,CAAC;EAC1B,WAAW;EACX,WAAW;EACX,YAAY;CACd;CAEA,MAAM,uBAA6B;EACjC,IAAI,CAAC,cAAc,GAAG;EACtB,IAAI,aAAa;GACf,IAAI,CAAC,UAAU,cAAc;GAC7B;EACF;EACA,IAAI,iBAAiB;EACrB,kBAAkB;EAClB,QAAa,IAAI;GACf,OAAO;GACP,OAAO;GACP,OAAO;GACP,OAAO;GACP,OAAO;EACT,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,kBAAkB,EAAE,cAAc,EAAE,YAAY,EAAE,mBAAmB,EAAE,kBAAkB;GACnG,cAAc;IAAE;IAAgB;IAAY;IAAU;IAAiB;GAAW;GAClF,kBAAkB;GAClB,cAAc;EAChB,CAAC;CACH;CAGA,MAAM,uBAA6B;EACjC,SAAS,UAAU,UAAU,OAAO;EACpC,WAAW,UAAU,QAAQ;GAC3B,MAAM,MAAO,IAA+B;GAC5C,IAAI,CAAC,KAAK;GACV,KAAK,MAAM,KAAK,MAAM,QAAQ,GAAG,IAAI,MAAM,CAAC,GAAG,GAAG,EAAgC,cAAc;EAClG,CAAC;CACH;CACA,eAAe;CACf,eAAe;CAIf,MAAM,qBAA2B;EAC/B,OAAO,kBAAkB,MAAM,KAAK;EACpC,OAAO,OAAO,MAAM;EACpB,SAAS,QAAQ,UAAU;EAC3B,MAAM,IAAI,WAAW;EACrB,MAAM,IAAI,WAAW;EACrB,IAAI,SAAS,WAAW,SAAS,MAAM;EACvC,SAAS,eAAe,IAAI;EAC5B,SAAS,WAAW,GAAG,GAAG,IAAI,GAAG,CAAC;EAClC,SAAS,YAAY,GAAG,GAAG,IAAI,GAAG,CAAC;EACnC,SAAS,OAAO,YAAY,OAAO,OAAO;EAC1C,SAAS,WAAW,IAAI,GAAG,GAAG,IAAI,GAAG,CAAC;EACtC,SAAS,YAAY,IAAI,GAAG,GAAG,IAAI,GAAG,CAAC;EACvC,SAAS,OAAO,YAAY,OAAO,OAAO;EAC1C,SAAS,eAAe,KAAK;EAC7B,SAAS,YAAY,GAAG,GAAG,GAAG,CAAC;CACjC;CAEA,MAAM,oBAA0B;EAC9B,QAAQ,sBAAsB,WAAW;EACzC,MAAM,QAAQ,MAAM,SAAS;EAC7B,MAAM,UAAU,QAAQ;EAGxB,KAAK,MAAM,UAAU,SAAS,OAAO,SAAS,OAAO;EACrD,YAAY,OAAO,OAAO;EAC1B,QAAQ,eAAe,OAAO;EAC9B,KAAK,MAAM,cAAc,aAAa,WAAW,SAAS,OAAO;EAIjE,KAAK,MAAM,WAAW,eAAe,QAAQ,OAAO;EAGpD,QAAQ,aAAa,UAAU,YAAY,MAAM;EACjD,IAAI,OAAO,WAAW,aAAa;OAC9B,IAAI,YAAY,cAAc,GAAG,SAAS,OAAO,KAAK;OACtD,SAAS,OAAO,YAAY,MAAM;CACzC;CA0BA,OAAO;EACL,KAAA;GAxBA,SAAS,OAAO,QAAQ,IAAI,EAAE;GAC9B,QAAQ;GACR,OAAO;GACP,UAAU;IACR,eAAe,SAAS,WAAW,MAAM,QAAQ,SAAS,MAAM;IAChE,eAAe,GAAG,MAAM,MAAM,QAAQ,GAAG,CAAC;GAC5C;GACA,cAAc;IACZ,WAAW,gBAAgB,IAAI;IAC/B,eAAe,gBAAgB,QAAQ;IACvC,UAAU,SAAS,gBAAgB,QAAQ,IAAI;IAC/C,UAAU,KAAK,UAAU,gBAAgB,QAAQ,KAAK,KAAK;IAC3D,YAAY,OAAO,gBAAgB,UAAU,EAAE;GACjD;GACA,aAAa,EACX,yBAAyB,MAAM,YAAY,uBAAuB,CAAC,EACrE;GACA,KAAK;GACL,QAAQ;GACR,OAAO;GACP,OAAO;IAAE,OAAO;IAAY;IAAQ;GAAS;EAI3C;EACF,SAAS,MAAM;GACb,QAAQ,UAAU,IAAI;GACtB,YAAY,SAAS,QAAQ,IAAI,CAAC;GAClC,YAAY,UAAU,KAAK,WAAW;GACtC,KAAK,MAAM,UAAU,SAAS,OAAO,WAAW,IAAI;GACpD,MAAM,MAAM,KAAK,OAAO,OAAO;GAC/B,IAAI,OAAO,QAAQ,KAAK;IACtB,OAAO,MAAM;IACb,OAAO,uBAAuB;GAChC;EACF;EACA,QAAQ;GACN,IAAI,UAAU,MAAM;IAClB,MAAM,MAAM;IACZ,YAAY;GACd;EACF;EACA,OAAO;GACL,IAAI,UAAU,MAAM;IAClB,qBAAqB,KAAK;IAC1B,QAAQ;GACV;EACF;EACA,UAAU;GACR,KAAK,KAAK;GACV,KAAK,MAAM,UAAU,SAAS,OAAO,UAAU;GAC/C,KAAK,MAAM,cAAc,aAAa,WAAW,UAAU;GAC3D,YAAY,OAAO;GACnB,YAAY,QAAQ;GACpB,QAAQ,QAAQ;GAChB,UAAU,QAAQ;GAClB,SAAS,QAAQ;GACjB,UAAU,MAAM;GAChB,cAAc,MAAM;EACtB;EACA,GAAG,OAAO,SAAS;GACjB,MAAM,MAAM,UAAU,IAAI,KAAK,qBAAK,IAAI,IAAI;GAC5C,IAAI,IAAI,OAAO;GACf,UAAU,IAAI,OAAO,GAAG;GACxB,aAAa,IAAI,OAAO,OAAO;EACjC;EACA,KAAK,OAAO,SAAS;GACnB,KAAK,OAAO,OAAO;EACrB;EACA,QAAQ,SAAS;GACf,cAAc,IAAI,OAAO;GACzB,aAAa,cAAc,OAAO,OAAO;EAC3C;EACA,OAAO,GAAG,GAAG;GACX,OAAO,SAAS,IAAI;GACpB,OAAO,uBAAuB;GAC9B,SAAS,QAAQ,GAAG,GAAG,KAAK;GAC5B,UAAU,QAAQ,GAAG,CAAC;GACtB,UAAU,QAAQ,GAAG,CAAC;GACtB,WAAW,QAAQ,GAAG,CAAC;EACzB;EACA,UAAU,MAAM;GACd,IAAI,KAAK,YAAY,KAAA,KAAa,KAAK,YAAY,OAAO,SAAS;IACjE,OAAO,UAAU,KAAK;IACtB,eAAe;GACjB;GACA,IAAI,KAAK,SAAS,KAAA,GAAW,OAAO,OAAO,KAAK;GAChD,IAAI,KAAK,UAAU,KAAA,GAAW,OAAO,QAAQ,KAAK;GAGlD,MAAM,YAAY,KAAK,cAAc,KAAA,KAAa,KAAK,cAAc,OAAO;GAC5E,IAAI,KAAK,cAAc,KAAA,GAAW,OAAO,YAAY,KAAK;GAC1D,IAAI,KAAK,cAAc,KAAA,KAAa,KAAK,cAAc,OAAO,WAM5D,gBAAgB,QAAQ,KAAK,YAAY,cAAc,OAAO;GAEhE,IAAI,KAAK,kBAAkB,KAAA,GAAW;IACpC,OAAO,gBAAgB,KAAK;IAC5B,OAAO,SAAS,KAAK;GACvB;GACA,IAAI,UAAU,SAAS,UAAU,OAAO;GACxC,IAAI,WAAW;IACb,UAAU,WAAW,OAAO;IAC5B,UAAU,UAAU,OAAO,QAAQ;GACrC;GACA,IAAI,aAAa,UAAU,cAAc;GACzC,eAAe;EACjB;CACF;AACF"}
1
+ {"version":3,"file":"index.cjs","names":["EquirectangularReflectionMapping","RepeatWrapping","SRGBColorSpace","NoColorSpace","TextureLoader","Vector2","Raycaster","Vector2","Group","Vector3","MeshBasicMaterial","Mesh","BoxGeometry","Quaternion","FLAG_KEYS","PerspectiveCamera","WebGLRenderer","PCFShadowMap","StereoCamera","Vector2","ThreeScene","PerspectiveCamera","Clock","WebGLRenderTarget","HalfFloatType"],"sources":["../src/loading.ts","../src/environment/EnvironmentController.ts","../src/builders/ktx2.ts","../src/builders/textureCache.ts","../src/builders/texture.ts","../src/interaction/InteractionController.ts","../src/input/InputBus.ts","../src/input/controlSettings.ts","../src/input/controlStorage.ts","../src/plugin/CapabilityRegistry.ts","../src/plugin/systemPlugin.ts","../src/plugin/roamControl.ts","../src/plugin/terrain.ts","../src/plugin/colliders.ts","../src/plugin/edges.ts","../src/plugin/eyeHeight.ts","../src/SceneManager.ts","../src/createGallery.ts"],"sourcesContent":["export interface LoadingState {\n loaded: number;\n total: number;\n done: boolean;\n}\n\ntype Listener = (state: LoadingState) => void;\n\nconst pending = new Set<string>();\nconst listeners = new Set<Listener>();\n\nlet state: LoadingState = { loaded: 0, total: 0, done: true };\n\nconst notify = (): void => {\n for (const listener of listeners) listener(state);\n};\n\n/** Begin tracking a new initial asset set; resets the previous one. */\nexport const startLoading = (urls: Iterable<string>): void => {\n pending.clear();\n for (const url of urls) pending.add(url);\n state = { loaded: 0, total: pending.size, done: pending.size === 0 };\n notify();\n};\n\n/** Mark one initial asset as loaded; unknown URLs (lazy assets) are ignored. */\nexport const trackAsset = (url: string): void => {\n if (!pending.delete(url)) return;\n state = {\n loaded: state.total - pending.size,\n total: state.total,\n done: pending.size === 0,\n };\n notify();\n};\n\n/** Current loading progress. */\nexport const loadingState = (): LoadingState => state;\n\n/** Subscribe to progress; the listener runs immediately with the current state. */\nexport const onLoading = (listener: Listener): (() => void) => {\n listeners.add(listener);\n listener(state);\n return () => {\n listeners.delete(listener);\n };\n};\n","import { EquirectangularReflectionMapping, type Scene as ThreeScene, type Texture } from \"three\";\nimport type { Environment } from \"@scenography/scene\";\n\nimport { trackAsset } from \"../loading\";\n\n/** Load an equirectangular HDRI, picking the loader by extension. */\nconst loadHDRI = (src: string, onLoad: (tex: Texture) => void): void => {\n if (src.toLowerCase().endsWith(\".exr\")) {\n void import(\"three/examples/jsm/loaders/EXRLoader.js\").then(({ EXRLoader }) => {\n new EXRLoader().load(src, onLoad);\n });\n } else {\n void import(\"three/examples/jsm/loaders/RGBELoader.js\").then(({ RGBELoader }) => {\n new RGBELoader().load(src, onLoad);\n });\n }\n};\n\n/**\n * Owns the scene's HDRI sky / image-based lighting and keeps it in sync with the\n * `Environment` definition. Loads the equirectangular map on demand (its loader\n * ships in its own chunk) and assigns it to `scene.background` and/or\n * `scene.environment`. Reconciles by a JSON hash so hot edits only reload when\n * the definition actually changes.\n */\nexport class EnvironmentController {\n private readonly scene: ThreeScene;\n private hash: string | null = null;\n private texture: Texture | null = null;\n\n constructor(scene: ThreeScene) {\n this.scene = scene;\n }\n\n reconcile(env: Environment | undefined): void {\n const hash = env ? JSON.stringify(env) : null;\n if (hash === this.hash) return;\n this.hash = hash;\n this.clear();\n if (!env) return;\n\n loadHDRI(env.src, (tex) => {\n trackAsset(env.src);\n // A newer reconcile superseded this load before it finished.\n if (this.hash !== hash) {\n tex.dispose();\n return;\n }\n tex.mapping = EquirectangularReflectionMapping;\n this.texture = tex;\n if (env.background ?? true) {\n this.scene.background = tex;\n this.scene.backgroundIntensity = env.intensity ?? 1;\n }\n if (env.lighting ?? true) this.scene.environment = tex;\n });\n }\n\n /** Set the visible sky background brightness (0 = black). Live, for behavior. */\n setBackgroundIntensity(value: number): void {\n this.scene.backgroundIntensity = value;\n }\n\n private clear(): void {\n this.scene.background = null;\n this.scene.environment = null;\n this.texture?.dispose();\n this.texture = null;\n }\n\n dispose(): void {\n this.clear();\n this.hash = null;\n }\n}\n","import type { CompressedTexture, WebGLRenderer } from \"three\";\n\n/** The part of three's KTX2Loader the engine and plugins use. */\nexport interface Ktx2Loader {\n load(\n url: string,\n onLoad: (texture: CompressedTexture) => void,\n onProgress?: (event: ProgressEvent) => void,\n onError?: (error: unknown) => void,\n ): void;\n}\n\nlet loader: Promise<Ktx2Loader> | null = null;\n\n/**\n * Configure the shared KTX2 loader. Called by the kernel once the renderer\n * exists; `.ktx2` textures and glTF models then decode through it.\n */\nexport const configureKtx2 = (renderer: WebGLRenderer, path: string): void => {\n loader = import(\"three/examples/jsm/loaders/KTX2Loader.js\").then(({ KTX2Loader }) => {\n const instance = new KTX2Loader();\n instance.setTranscoderPath(path);\n instance.detectSupport(renderer);\n return instance as unknown as Ktx2Loader;\n });\n};\n\n/** The configured loader, or null when the gallery did not set `ktx2Path`. */\nexport const getKtx2Loader = (): Promise<Ktx2Loader> | null => loader;\n","import { NoColorSpace, RepeatWrapping, SRGBColorSpace, type Texture } from \"three\";\nimport type { Vec2 } from \"@scenography/scene\";\n\n/** Creates the texture object for a source; its image may still be loading. */\nexport type TextureSource = (src: string) => Texture;\n\ntype Entry = {\n key: string;\n texture: Texture;\n refs: number;\n};\n\n/** Two surfaces can share a texture only at the same tiling and color space. */\nexport const textureKey = (src: string, repeat: Vec2, srgb: boolean): string =>\n `${src}|${repeat[0]}x${repeat[1]}|${srgb ? \"srgb\" : \"linear\"}`;\n\n/** Tiling and color space of a texture; the image arrives later from the loader. */\nexport const configureTexture = (texture: Texture, repeat: Vec2, srgb: boolean): void => {\n texture.wrapS = RepeatWrapping;\n texture.wrapT = RepeatWrapping;\n texture.repeat.set(repeat[0], repeat[1]);\n texture.colorSpace = srgb ? SRGBColorSpace : NoColorSpace;\n};\n\n/**\n * Shares textures between materials: the same source at the same tiling and\n * color space becomes one texture, loaded once and uploaded to the GPU once.\n * Reference counted, so releasing a material keeps the texture alive while\n * another one still samples it.\n */\nexport class TextureCache {\n private readonly entries = new Map<string, Entry>();\n private readonly owners = new Map<Texture, Entry>();\n\n constructor(private readonly source: TextureSource) {}\n\n /** The shared texture for a source, created on first use. */\n acquire(src: string, repeat: Vec2, srgb: boolean): Texture {\n const key = textureKey(src, repeat, srgb);\n const existing = this.entries.get(key);\n if (existing) {\n existing.refs += 1;\n return existing.texture;\n }\n const texture = this.source(src);\n configureTexture(texture, repeat, srgb);\n const entry = { key, texture, refs: 1 };\n this.entries.set(key, entry);\n this.owners.set(texture, entry);\n return texture;\n }\n\n /** Drop one reference; the texture is disposed when the last one goes. */\n release(texture: Texture): void {\n const entry = this.owners.get(texture);\n if (!entry) {\n texture.dispose();\n return;\n }\n entry.refs -= 1;\n if (entry.refs > 0) return;\n this.entries.delete(entry.key);\n this.owners.delete(texture);\n texture.dispose();\n }\n\n /** Textures currently shared. */\n get size(): number {\n return this.entries.size;\n }\n}\n","import { MeshStandardMaterial, TextureLoader, Vector2, type Texture } from \"three\";\nimport type { SurfaceTexture, Vec2 } from \"@scenography/scene\";\n\nimport { getKtx2Loader } from \"./ktx2\";\nimport { trackAsset } from \"../loading\";\nimport { configureTexture, TextureCache, textureKey } from \"./textureCache\";\n\nconst textureLoader = new TextureLoader();\nconst cache = new TextureCache((src) => textureLoader.load(src, () => trackAsset(src)));\n\ntype MapSlot = \"map\" | \"normalMap\" | \"roughnessMap\" | \"aoMap\";\n\ntype Ktx2Entry = {\n key: string;\n refs: number;\n texture?: Texture;\n waiters: Array<(texture: Texture) => void>;\n};\n\nconst ktx2Entries = new Map<string, Ktx2Entry>();\nconst ktx2Owners = new Map<Texture, Ktx2Entry>();\n\n/**\n * KTX2 textures resolve asynchronously, so the material slot is filled through\n * a waiter list. One entry per source and tiling is shared by every material\n * that samples it, just like the image cache.\n */\nconst loadKtx2 = (\n material: MeshStandardMaterial,\n slot: MapSlot,\n src: string,\n repeat: Vec2,\n srgb: boolean,\n): void => {\n const loader = getKtx2Loader();\n if (!loader) {\n console.warn(`KTX2 texture \"${src}\" needs the gallery's ktx2Path option.`);\n return;\n }\n const key = textureKey(src, repeat, srgb);\n let entry = ktx2Entries.get(key);\n if (!entry) {\n entry = { key, refs: 0, waiters: [] };\n ktx2Entries.set(key, entry);\n const created = entry;\n void loader.then((ktx2) =>\n ktx2.load(\n src,\n (texture) => {\n configureTexture(texture, repeat, srgb);\n texture.needsUpdate = true;\n trackAsset(src);\n created.texture = texture;\n ktx2Owners.set(texture, created);\n for (const assign of created.waiters) assign(texture);\n created.waiters = [];\n },\n undefined,\n (error) => console.error(`Failed to load KTX2 texture \"${src}\".`, error),\n ),\n );\n }\n entry.refs += 1;\n if (entry.texture) {\n material[slot] = entry.texture;\n material.needsUpdate = true;\n } else {\n entry.waiters.push((texture) => {\n material[slot] = texture;\n material.needsUpdate = true;\n });\n }\n};\n\n/**\n * Load one map into `material[slot]`. Textures are shared: every material that\n * samples the same source at the same tiling reuses one texture object. `.exr`\n * maps go through EXRLoader, imported on demand, and are not shared because\n * they resolve asynchronously; `.ktx2` maps share through their own cache.\n */\nconst loadMap = (\n material: MeshStandardMaterial,\n slot: MapSlot,\n src: string,\n repeat: Vec2,\n srgb: boolean,\n): void => {\n if (src.toLowerCase().endsWith(\".ktx2\")) {\n loadKtx2(material, slot, src, repeat, srgb);\n return;\n }\n if (src.toLowerCase().endsWith(\".exr\")) {\n void import(\"three/examples/jsm/loaders/EXRLoader.js\").then(({ EXRLoader }) => {\n new EXRLoader().load(src, (texture) => {\n configureTexture(texture, repeat, srgb);\n texture.needsUpdate = true;\n trackAsset(src);\n material[slot] = texture;\n material.needsUpdate = true;\n });\n });\n return;\n }\n material[slot] = cache.acquire(src, repeat, srgb);\n};\n\n/**\n * Apply a `SurfaceTexture` to a standard material. Returns whether a second UV\n * set (`uv1`) is needed — three samples `aoMap` from it, so the geometry must\n * provide one.\n */\nexport const applyTexture = (\n material: MeshStandardMaterial,\n texture: SurfaceTexture,\n): boolean => {\n const repeat = texture.repeat ?? [1, 1];\n loadMap(material, \"map\", texture.src, repeat, true);\n if (texture.normal) {\n loadMap(material, \"normalMap\", texture.normal, repeat, false);\n const scale = texture.normalScale ?? 1;\n material.normalScale = new Vector2(scale, scale);\n }\n if (texture.roughness) loadMap(material, \"roughnessMap\", texture.roughness, repeat, false);\n if (texture.ao) loadMap(material, \"aoMap\", texture.ao, repeat, false);\n material.needsUpdate = true;\n return texture.ao !== undefined;\n};\n\n/**\n * Release one reference to a texture assigned by `applyTexture`. Shared\n * textures stay alive while another material still samples them.\n */\nexport const releaseTexture = (texture: Texture): void => {\n const ktx2 = ktx2Owners.get(texture);\n if (!ktx2) {\n cache.release(texture);\n return;\n }\n ktx2.refs -= 1;\n if (ktx2.refs > 0) return;\n ktx2Owners.delete(texture);\n ktx2Entries.delete(ktx2.key);\n texture.dispose();\n};\n","import {\n BoxGeometry,\n Group,\n Mesh,\n MeshBasicMaterial,\n Quaternion,\n Raycaster,\n Vector2,\n Vector3,\n type Object3D,\n type PerspectiveCamera,\n type Scene as ThreeScene,\n} from \"three\";\n\nconst HIGHLIGHT_COLOR = \"#ef4444\";\nconst HIGHLIGHT_THICKNESS = 1.5; // world units (cm)\nconst HIGHLIGHT_MARGIN = 1.5; // gap between the piece and the frame\nconst CLICK_SLOP = 4; // px of movement still counted as a click, not a drag\nconst PICK_INTERVAL = 60; // ms between hover raycasts while the pointer is over the canvas\n\ninterface PickInfo {\n width: number;\n height: number;\n frontOffset: number;\n}\n\ninterface Pick {\n id: string;\n holder: Object3D;\n info: PickInfo;\n}\n\n/**\n * Pointer picking for artworks: hovering a piece's click target (its label, or\n * the piece itself when it has none) draws a pulsing red frame and shows a\n * pointer cursor; clicking it emits `artwork:select { id }`. Targets are marked\n * in the scene graph (`userData.pickTarget`/`pickInfo`) by the artwork builder,\n * so this just raycasts and walks up to find them. An optional per-id radius\n * gates interaction to when the camera is reasonably close.\n */\nexport class InteractionController {\n private readonly raycaster = new Raycaster();\n private readonly pointer = new Vector2();\n private over = false;\n private downPos: { x: number; y: number } | null = null;\n private radii = new Map<string, number>();\n private hovered: string | null = null;\n private time = 0;\n private pickElapsed = 0;\n private pointerMoved = false;\n\n private readonly highlight = new Group();\n private highlightMat: MeshBasicMaterial | null = null;\n private readonly handlers: Array<[EventTarget, string, EventListener]> = [];\n\n constructor(\n private readonly camera: PerspectiveCamera,\n private readonly canvas: HTMLCanvasElement,\n private readonly scene: ThreeScene,\n private readonly emit: (event: string, payload?: unknown) => void,\n private readonly canInteract: () => boolean,\n ) {\n this.highlight.visible = false;\n this.scene.add(this.highlight);\n }\n\n /** Per-artwork max interaction distance (XZ). Absent id = no distance gate. */\n setRadii(radii: Map<string, number>): void {\n this.radii = radii;\n }\n\n attach(): void {\n const bind = (type: string, listener: EventListener): void => {\n this.canvas.addEventListener(type, listener);\n this.handlers.push([this.canvas, type, listener]);\n };\n bind(\"pointermove\", (e) => this.onMove(e as PointerEvent));\n bind(\"pointerdown\", (e) => this.onDown(e as PointerEvent));\n bind(\"pointerup\", (e) => this.onUp(e as PointerEvent));\n bind(\"pointerleave\", () => {\n this.over = false;\n });\n }\n\n detach(): void {\n for (const [target, type, listener] of this.handlers) target.removeEventListener(type, listener);\n this.handlers.length = 0;\n this.scene.remove(this.highlight);\n this.clearHighlight();\n }\n\n private setPointer(e: PointerEvent): void {\n const rect = this.canvas.getBoundingClientRect();\n this.pointer.set(\n ((e.clientX - rect.left) / rect.width) * 2 - 1,\n -((e.clientY - rect.top) / rect.height) * 2 + 1,\n );\n this.over = true;\n this.pointerMoved = true;\n }\n\n private onMove(e: PointerEvent): void {\n this.setPointer(e);\n }\n\n private onDown(e: PointerEvent): void {\n this.downPos = { x: e.clientX, y: e.clientY };\n }\n\n private onUp(e: PointerEvent): void {\n const down = this.downPos;\n this.downPos = null;\n if (!down || !this.canInteract()) return;\n if (Math.hypot(e.clientX - down.x, e.clientY - down.y) > CLICK_SLOP) return; // a drag, not a click\n this.setPointer(e);\n const pick = this.raycast();\n if (pick) this.emit(\"artwork:select\", { id: pick.id });\n }\n\n /** Resolve the nearest pickable artwork under the pointer, honoring the gate. */\n private raycast(): Pick | null {\n this.raycaster.setFromCamera(this.pointer, this.camera);\n const hits = this.raycaster.intersectObjects(this.scene.children, true);\n for (const hit of hits) {\n let node: Object3D | null = hit.object;\n while (node && node.userData.pickTarget === undefined) node = node.parent;\n if (!node) continue;\n const id = node.userData.pickTarget as string;\n const radius = this.radii.get(id);\n if (radius !== undefined) {\n const p = node.getWorldPosition(new Vector3());\n if (Math.hypot(this.camera.position.x - p.x, this.camera.position.z - p.z) > radius) continue;\n }\n return { id, holder: node, info: node.userData.pickInfo as PickInfo };\n }\n return null;\n }\n\n /**\n * Recompute hover state and animate the highlight. Call once per frame.\n * Hover picking is throttled: a full-scene raycast runs at most once every\n * `PICK_INTERVAL` ms, or immediately after the pointer moves. The camera can\n * move without the pointer, so the interval keeps hovering correct while\n * walking.\n */\n update(deltaMs: number): void {\n this.time += deltaMs;\n\n if (!this.over || !this.canInteract()) {\n // No picking while the visitor cannot interact; pick at once when they can.\n this.pickElapsed = PICK_INTERVAL;\n this.pointerMoved = false;\n this.applyPick(null);\n } else {\n this.pickElapsed += deltaMs;\n if (this.pointerMoved) this.pickElapsed = PICK_INTERVAL;\n this.pointerMoved = false;\n if (this.pickElapsed >= PICK_INTERVAL) {\n this.pickElapsed = 0;\n this.applyPick(this.raycast());\n }\n }\n\n if (this.highlight.visible && this.highlightMat) {\n this.highlightMat.opacity = 0.45 + 0.45 * (0.5 + 0.5 * Math.sin(this.time / 320));\n }\n }\n\n /** Swap the hovered piece, touching the canvas and the highlight only on change. */\n private applyPick(pick: Pick | null): void {\n if ((pick?.id ?? null) === this.hovered) return;\n this.hovered = pick?.id ?? null;\n this.canvas.style.cursor = pick ? \"pointer\" : \"\";\n if (pick) this.buildHighlight(pick);\n else this.highlight.visible = false;\n }\n\n private clearHighlight(): void {\n for (const child of [...this.highlight.children]) {\n this.highlight.remove(child);\n (child as Mesh).geometry?.dispose();\n }\n this.highlightMat?.dispose();\n this.highlightMat = null;\n }\n\n /** Position a fresh pulsing red frame over the picked target's front face. */\n private buildHighlight(pick: Pick): void {\n this.clearHighlight();\n const mat = new MeshBasicMaterial({ color: HIGHLIGHT_COLOR, transparent: true, depthTest: false });\n this.highlightMat = mat;\n\n const w = pick.info.width + 2 * HIGHLIGHT_MARGIN;\n const h = pick.info.height + 2 * HIGHLIGHT_MARGIN;\n const t = HIGHLIGHT_THICKNESS;\n // Four bars forming a border (horizontals span the corners; verticals fill in).\n const bars: [number, number, number, number][] = [\n [w + 2 * t, t, 0, h / 2 + t / 2],\n [w + 2 * t, t, 0, -(h / 2 + t / 2)],\n [t, h, -(w / 2 + t / 2), 0],\n [t, h, w / 2 + t / 2, 0],\n ];\n for (const [bw, bh, x, y] of bars) {\n const bar = new Mesh(new BoxGeometry(bw, bh, 0.5), mat);\n bar.position.set(x, y, 0);\n bar.renderOrder = 999;\n this.highlight.add(bar);\n }\n\n const pos = pick.holder.getWorldPosition(new Vector3());\n const quat = pick.holder.getWorldQuaternion(new Quaternion());\n const normal = new Vector3(0, 0, 1).applyQuaternion(quat);\n pos.addScaledVector(normal, pick.info.frontOffset);\n this.highlight.position.copy(pos);\n this.highlight.quaternion.copy(quat);\n this.highlight.visible = true;\n }\n}\n","/**\n * The shared analog-input channel: the current movement and look intent, each\n * axis in `-1..1` and held until a producer changes it. Producers (on-screen\n * joysticks in the overlay, a gamepad…) write to it; whatever camera system is\n * active reads it and interprets it its own way — free-roam walks with it, a\n * fixed camera might only use the look axes to glance around. This is what makes\n * the joysticks reusable across camera types. Pure state — no three.js.\n */\nexport class InputBus {\n /** Forward (`+1`) / backward (`-1`). */\n moveForward = 0;\n /** Strafe right (`+1`) / left (`-1`). */\n moveStrafe = 0;\n /** Look right (`+1`) / left (`-1`). */\n lookX = 0;\n /** Look up (`+1`) / down (`-1`). */\n lookY = 0;\n\n /** Set the movement intent (forward, strafe). */\n setMove(forward: number, strafe = 0): void {\n this.moveForward = forward;\n this.moveStrafe = strafe;\n }\n\n /** Set the look intent (turn, pitch). */\n setLook(x: number, y = 0): void {\n this.lookX = x;\n this.lookY = y;\n }\n}\n","/**\n * The visitor's camera-control preferences: a set of orthogonal capability flags\n * plus a derived, friendly \"mode\". Pure state — no three.js, no DOM. The engine\n * wires the flags to effects (gyroscope head-look, stereo split); consumers read\n * them (the analog look-invert axes); the overlay drives it from a mode picker\n * (the `ScanEye` button) and the settings panel. Persistence and device\n * feature-detection live in those callers, not here, so this stays trivially\n * unit-testable.\n */\n\n/**\n * Orthogonal control capabilities. `gyro` / `splitScreen` / `joysticks` are what a\n * mode fixes; the two `invertLook*` axes are independent look-feel preferences\n * (some people feel the right stick inverted), kept separate from the mode.\n */\nexport interface ControlFlags {\n /** Orient the camera from the device gyroscope (head-look). */\n gyro: boolean;\n /** Split the view into a stereo left/right eye pair (phone-in-a-headset). */\n splitScreen: boolean;\n /** Show the on-screen virtual joysticks. */\n joysticks: boolean;\n /** Invert the horizontal (turn) axis of analog look. */\n invertLookX: boolean;\n /** Invert the vertical (pitch) axis of analog look. */\n invertLookY: boolean;\n}\n\n/** A named preset over the mode-defining flags; `custom` when none matches. */\nexport type ControlMode = \"touch\" | \"vr\" | \"cardboard\" | \"custom\";\n\n/** The three flags a mode fixes (the look-invert axes stay a separate preference). */\ntype ModeFlags = Pick<ControlFlags, \"gyro\" | \"splitScreen\" | \"joysticks\">;\n\n/**\n * Mode presets. Touch = two joysticks, no gyro. VR = the gyro aims and a single\n * move joystick walks you, no split. Cardboard = gyro aims, stereo split, gaze\n * select (no joysticks). The move-only single stick in VR is derived downstream\n * from `gyro && !splitScreen`, so it needs no flag of its own.\n */\nexport const MODE_PRESETS: Record<Exclude<ControlMode, \"custom\">, ModeFlags> = {\n touch: { gyro: false, splitScreen: false, joysticks: true },\n vr: { gyro: true, splitScreen: false, joysticks: true },\n cardboard: { gyro: true, splitScreen: true, joysticks: false },\n};\n\n/** Default flags: plain touch, no look inversion. */\nexport const DEFAULT_CONTROL_FLAGS: ControlFlags = {\n ...MODE_PRESETS.touch,\n invertLookX: false,\n invertLookY: false,\n};\n\nconst FLAG_KEYS: readonly (keyof ControlFlags)[] = [\n \"gyro\",\n \"splitScreen\",\n \"joysticks\",\n \"invertLookX\",\n \"invertLookY\",\n];\n\nconst equalFlags = (a: ControlFlags, b: ControlFlags): boolean =>\n FLAG_KEYS.every((key) => a[key] === b[key]);\n\n/** The mode a flag set represents, or `custom` when no preset matches. */\nexport function modeOf(flags: ControlFlags): ControlMode {\n for (const mode of [\"touch\", \"vr\", \"cardboard\"] as const) {\n const preset = MODE_PRESETS[mode];\n if (\n flags.gyro === preset.gyro &&\n flags.splitScreen === preset.splitScreen &&\n flags.joysticks === preset.joysticks\n )\n return mode;\n }\n return \"custom\";\n}\n\n/** Apply a mode's preset onto `flags`, leaving the look-invert axes untouched. */\nexport function applyMode(\n flags: ControlFlags,\n mode: Exclude<ControlMode, \"custom\">,\n): ControlFlags {\n return { ...flags, ...MODE_PRESETS[mode] };\n}\n\n/**\n * Live control-preferences state: holds the flags, derives the mode, and notifies\n * subscribers on every real change. Callers apply the effects — the engine wires\n * gyro and split, the overlay redraws — so this object is just the shared source\n * of truth they all read and write.\n */\nexport class ControlSettings {\n private flags: ControlFlags;\n private readonly subscribers = new Set<(flags: ControlFlags) => void>();\n\n constructor(initial: Partial<ControlFlags> = {}) {\n this.flags = { ...DEFAULT_CONTROL_FLAGS, ...initial };\n }\n\n /** The current flags (a copy — mutate through `setMode` / `setFlag`). */\n get(): ControlFlags {\n return { ...this.flags };\n }\n\n /** The mode the current flags represent (`custom` when hand-tuned). */\n getMode(): ControlMode {\n return modeOf(this.flags);\n }\n\n /** Switch to a mode's preset (keeps the look-invert axes). No-op if unchanged. */\n setMode(mode: Exclude<ControlMode, \"custom\">): void {\n this.replace(applyMode(this.flags, mode));\n }\n\n /** Set one flag. Flipping a mode-defining flag can drop you into `custom`. */\n setFlag<K extends keyof ControlFlags>(key: K, value: ControlFlags[K]): void {\n this.replace({ ...this.flags, [key]: value });\n }\n\n /** Subscribe to changes; returns an unsubscribe. Fires on every real change. */\n subscribe(fn: (flags: ControlFlags) => void): () => void {\n this.subscribers.add(fn);\n return () => this.subscribers.delete(fn);\n }\n\n private replace(next: ControlFlags): void {\n if (equalFlags(this.flags, next)) return;\n this.flags = next;\n const snapshot = this.get();\n for (const fn of this.subscribers) fn(snapshot);\n }\n}\n","import { DEFAULT_CONTROL_FLAGS, type ControlFlags } from \"./controlSettings\";\n\n/**\n * localStorage persistence for the control flags, kept out of the pure\n * `ControlSettings` so that stays DOM-free and unit-testable. Both functions are\n * best-effort and guarded: no `localStorage` (SSR, privacy mode) or malformed\n * data just falls back to defaults, never throws.\n */\nconst STORAGE_KEY = \"scenography:controls\";\n\nconst FLAG_KEYS = Object.keys(DEFAULT_CONTROL_FLAGS) as (keyof ControlFlags)[];\n\n/** Read persisted control flags; `null` when absent, unavailable or malformed. */\nexport function loadControlFlags(): Partial<ControlFlags> | null {\n try {\n const raw = globalThis.localStorage?.getItem(STORAGE_KEY);\n if (!raw) return null;\n const parsed = JSON.parse(raw) as Record<string, unknown>;\n const flags: Partial<ControlFlags> = {};\n // Copy only known boolean keys, so stale/garbage fields can't leak in.\n for (const key of FLAG_KEYS) {\n if (typeof parsed[key] === \"boolean\") flags[key] = parsed[key] as boolean;\n }\n return flags;\n } catch {\n return null;\n }\n}\n\n/** Persist control flags (best-effort; ignores a full/blocked store). */\nexport function saveControlFlags(flags: ControlFlags): void {\n try {\n globalThis.localStorage?.setItem(STORAGE_KEY, JSON.stringify(flags));\n } catch {\n /* storage unavailable or full — persistence is best-effort */\n }\n}\n","/**\n * Shared runtime contracts between plugins, keyed by name. A plugin (or the\n * kernel) *provides* a capability — e.g. the floor provides `terrain` — and\n * other plugins *consume* it if present, without depending on the provider's\n * package. Each name keeps a monotonic version counter so consumers can hash\n * their dependencies cheaply and rebuild when a provider changes.\n */\nexport class CapabilityRegistry {\n private readonly values = new Map<string, unknown>();\n private readonly versions = new Map<string, number>();\n /** Last change-token seen per name, to suppress redundant version bumps. */\n private readonly tokens = new Map<string, string>();\n\n /**\n * Publish (or replace) the capability under `name`. Pass a `token` (a cheap\n * hash of the inputs) to dedupe: re-providing with an unchanged token is a\n * no-op, so consumers that depend on this capability don't rebuild when the\n * provider recomputes an identical value.\n */\n provide<T>(name: string, value: T, token?: string): void {\n if (token !== undefined && this.values.has(name) && this.tokens.get(name) === token) return;\n this.values.set(name, value);\n if (token !== undefined) this.tokens.set(name, token);\n else this.tokens.delete(name);\n this.bump(name);\n }\n\n /** Remove the capability; consumers see `undefined` and a version bump. */\n revoke(name: string): void {\n if (this.values.delete(name)) {\n this.tokens.delete(name);\n this.bump(name);\n }\n }\n\n get<T>(name: string): T | undefined {\n return this.values.get(name) as T | undefined;\n }\n\n /** Monotonic change counter for `name` (`0` if never provided). */\n version(name: string): number {\n return this.versions.get(name) ?? 0;\n }\n\n private bump(name: string): void {\n this.versions.set(name, (this.versions.get(name) ?? 0) + 1);\n }\n}\n","import type {\n Object3D,\n PerspectiveCamera,\n Scene as ThreeScene,\n WebGLRenderer,\n} from \"three\";\nimport type { Scene } from \"@scenography/scene\";\n\nimport type { CapabilityRegistry } from \"./CapabilityRegistry\";\nimport type { ControlFlags, InputBus } from \"../input\";\n\n/**\n * What the kernel hands a system on setup. A *system* plugin — unlike an element\n * plugin — produces no scene object; it drives shared runtime state (the camera,\n * input…) each frame. The camera is a kernel primitive (the renderer needs one),\n * so systems receive it and drive it, publishing/consuming capabilities to\n * cooperate (e.g. the camera plugin publishes `roamControl`).\n */\nexport interface SystemContext {\n /** The current scene (its data keys, start pose, boundary…); also `setScene` on hot-swap. */\n scene: Scene;\n /** The render camera the system may drive. */\n camera: PerspectiveCamera;\n /** The canvas, for pointer/keyboard listeners. */\n canvas: HTMLElement;\n /** The three.js scene graph, for systems that manage their own objects (sky…). */\n scene3d: ThreeScene;\n /** The renderer, for systems that need it (e.g. sky IBL generation). */\n renderer: WebGLRenderer;\n /** Shared analog input (joysticks feed it); systems interpret it. */\n input: InputBus;\n /** The visitor's current control flags (e.g. look-invert axes), read live. */\n controlFlags: () => ControlFlags;\n /** Capability registry for cross-plugin contracts (terrain, colliders…). */\n capabilities: CapabilityRegistry;\n /** Emit a gallery event. */\n emit: (event: string, payload?: unknown) => void;\n /** Resolve a live scene object by id (e.g. for a framing system). */\n resolve: (id: string) => Object3D | undefined;\n}\n\n/** A running system: its per-frame update, hot-swap and teardown hooks. */\nexport interface SystemInstance<Api = unknown> {\n /** Advance the system by the frame delta (ms). */\n update?(deltaMs: number): void;\n /** React to a hot-swapped scene definition. */\n setScene?(scene: Scene): void;\n /** Tear down (detach listeners, revoke capabilities). */\n dispose?(): void;\n /**\n * Public API the system exposes to userland/overlay. The kernel maps known\n * system names onto `ctx` (e.g. a `focus` system's api → `ctx.camera`).\n */\n api?: Api;\n}\n\n/**\n * A system plugin: sets up once against the kernel handles and returns its\n * running instance. Registered alongside element plugins in `plugins`; the\n * kernel tells them apart by shape (`setup` vs `build`).\n */\nexport interface SystemPlugin<Api = unknown> {\n name: string;\n setup(ctx: SystemContext): SystemInstance<Api>;\n}\n\n/** Narrow a mixed plugin to a system plugin (has `setup`, not `build`). */\nexport const isSystemPlugin = (plugin: unknown): plugin is SystemPlugin =>\n typeof plugin === \"object\" && plugin !== null && \"setup\" in plugin;\n","import type { Vector3 } from \"three\";\n\n/** Capability name under which a roaming camera system exposes pose control. */\nexport const ROAM_CONTROL = \"roamControl\";\n\n/**\n * Handle a camera plugin publishes so other camera systems can cooperate with\n * it — chiefly a framing/tour system that suspends free-roam while it animates\n * the camera, then reads/writes the pose to hand control back cleanly.\n */\nexport interface RoamControl {\n /** Enable/disable free-roam input (disabled while another system owns the camera). */\n setEnabled(enabled: boolean): void;\n /** Current pose: world position plus yaw (`phi`) and pitch (`theta`) in radians. */\n getPose(): { position: Vector3; phi: number; theta: number };\n /** Force the pose so free-roam resumes from it. */\n setPose(position: Vector3, phi: number, theta: number): void;\n /** Current eye height above the ground (world units). */\n getEyeHeight(): number;\n}\n","import type { Vec2 } from \"@scenography/scene\";\n\n/** Capability name under which the floor publishes its terrain. */\nexport const TERRAIN = \"terrain\";\n\n/**\n * The geometry a terrain consumer relies on — the fields needed to sample the\n * same ground a floor was displaced by (e.g. the water shader). A floor\n * definition satisfies this structurally; the kernel stays floor-agnostic.\n */\nexport interface TerrainDefinition {\n /** Extent on the ground plane, `[width (x), depth (z)]`. */\n size: Vec2;\n /** Center on the ground plane `[x, z]`. Defaults to `[0, 0]`. */\n center?: Vec2;\n /** Base vertical position (world Y). Defaults to `0`. */\n y?: number;\n /** Grayscale heightmap URL that displaces the ground. */\n heightmap?: string;\n /** Max displacement (world units) at white in the heightmap. */\n heightScale?: number;\n}\n\n/**\n * The walkable ground, published by the floor: its definition (for shaders that\n * sample the same heightmap), the live height function (heightmap-aware once the\n * image loads) and a promise that resolves once that sampling is in effect.\n */\nexport interface TerrainCapability {\n definition: TerrainDefinition;\n heightAt: (x: number, z: number) => number;\n /** Resolves once `heightAt` reflects the heightmap (immediately when there is none). */\n ready: Promise<void>;\n}\n","/** Capability name under which walkable-space obstacles are published. */\nexport const COLLIDERS = \"colliders\";\n\n/**\n * A collision obstacle as an oriented box on the XZ plane. The camera controls\n * consume these to keep the visitor out of walls (and any other plugin that\n * wants to block movement can provide more of them). Purely a movement contract\n * — no geometry, no three.js.\n */\nexport interface Collider {\n /** Box center `[x, z]`. */\n cx: number;\n cz: number;\n /** Unit direction along the box's length. */\n dx: number;\n dz: number;\n /** Half the box length. */\n halfLength: number;\n /** Half the box thickness. */\n halfThickness: number;\n}\n","import type { Vec2 } from \"@scenography/scene\";\n\n/** Capability name under which named wall edges are published for hanging. */\nexport const EDGES = \"edges\";\n\n/** A named anchor an artwork can hang on: a wall segment resolved to geometry. */\nexport interface EdgeAnchor {\n a: Vec2;\n b: Vec2;\n height: number;\n thickness: number;\n}\n\n/** The `name -> anchor` index a provider (e.g. the wall plugin) publishes. */\nexport type EdgeIndex = ReadonlyMap<string, EdgeAnchor>;\n","/**\n * Capability name under which the visitor's eye height (world units) is\n * published — the default vertical placement for wall-hung artworks. The kernel\n * provides it from the camera start; a camera plugin will own it later.\n */\nexport const EYE_HEIGHT = \"eyeHeight\";\n","import {\n PerspectiveCamera,\n type Object3D,\n type Scene as ThreeScene,\n type Texture,\n type WebGLRenderer,\n} from \"three\";\nimport type { Scene } from \"@scenography/scene\";\n\nimport { releaseTexture } from \"./builders\";\nimport {\n CapabilityRegistry,\n EYE_HEIGHT,\n type BuiltElement,\n type EngineContext,\n type EnginePlugin,\n} from \"./plugin\";\n\n/** A mounted plugin element: what the plugin built plus its content hash. */\ntype MountedElement = BuiltElement & { hash: string };\n\nconst TEXTURE_SLOTS = [\"map\", \"normalMap\", \"roughnessMap\", \"aoMap\", \"metalnessMap\"];\n\nconst disposeObject = (object: Object3D): void => {\n const materials = new Set<unknown>();\n object.traverse((child) => {\n // Release any HTML video element backing a VideoTexture.\n const video = child.userData?.video as HTMLVideoElement | undefined;\n if (video) {\n video.pause();\n video.removeAttribute(\"src\");\n video.load();\n }\n const mesh = child as { geometry?: { dispose?: () => void }; material?: unknown };\n mesh.geometry?.dispose?.();\n const material = mesh.material;\n if (Array.isArray(material)) for (const entry of material) materials.add(entry);\n else if (material) materials.add(material);\n });\n // A material can be shared by several meshes; dispose it, and the textures it\n // samples, once. Textures come from a shared cache, so they are released\n // rather than disposed outright.\n for (const entry of materials) {\n const material = entry as Record<string, unknown> & { dispose?: () => void };\n for (const slot of TEXTURE_SLOTS) {\n const texture = material[slot] as Texture | undefined;\n if (texture) releaseTexture(texture);\n }\n material.dispose?.();\n }\n};\n\n/**\n * Owns the live three.js scene graph and keeps it in sync with a `Scene`\n * definition. `reconcile` diffs the incoming definition against what is already\n * mounted — by `id` — and only adds/removes/rebuilds what changed. This is what\n * makes hot scene edits cheap: add a wing or drop one while moving and only the\n * touched objects are rebuilt.\n */\nexport class SceneManager {\n private readonly root: ThreeScene;\n private readonly plugins: EnginePlugin[];\n private readonly capabilities: CapabilityRegistry;\n private readonly view: { camera: PerspectiveCamera; renderer: WebGLRenderer };\n /** Mounted plugin elements, per kind, keyed by element id (\"\" for singletons). */\n private readonly pluginElements = new Map<string, Map<string, MountedElement>>();\n\n constructor(\n root: ThreeScene,\n plugins: EnginePlugin[] = [],\n capabilities: CapabilityRegistry = new CapabilityRegistry(),\n view: { camera: PerspectiveCamera; renderer: WebGLRenderer } = {\n camera: new PerspectiveCamera(),\n renderer: {} as WebGLRenderer,\n },\n ) {\n this.root = root;\n this.plugins = plugins;\n this.capabilities = capabilities;\n this.view = view;\n }\n\n /** The build context handed to every plugin. */\n private context(): EngineContext {\n return { capabilities: this.capabilities, ...this.view };\n }\n\n reconcile(scene: Scene): void {\n // Eye height (default hang height for wall art) — a camera concern the\n // kernel owns for now; a camera plugin will publish it later.\n const eye = scene.camera.start.position[1];\n this.capabilities.provide(EYE_HEIGHT, eye, String(eye));\n // Plugins go last so kernel-provided capabilities (terrain, eyeHeight) are fresh.\n this.reconcilePlugins(scene);\n }\n\n /**\n * Reconcile every registered plugin's elements by content: an element\n * rebuilds when its definition changes or when a capability it depends on\n * was re-provided (the capability versions are part of the hash).\n */\n private reconcilePlugins(scene: Scene): void {\n for (const plugin of this.plugins) {\n const { kind, cardinality } = plugin.data;\n let mounted = this.pluginElements.get(kind);\n if (!mounted) {\n mounted = new Map();\n this.pluginElements.set(kind, mounted);\n }\n\n const raw = (scene as unknown as Record<string, unknown>)[kind];\n const items: Array<[string, unknown]> =\n cardinality === \"many\"\n ? Array.isArray(raw)\n ? raw.map((item, i) => [String((item as { id?: string }).id ?? i), item])\n : []\n : raw !== undefined\n ? [[\"\", raw]]\n : [];\n const deps = (plugin.dependsOn ?? []).map((name) => this.capabilities.version(name));\n\n const seen = new Set<string>();\n for (const [key, value] of items) {\n seen.add(key);\n const hash = JSON.stringify({ value, deps });\n const existing = mounted.get(key);\n if (existing?.hash === hash) continue;\n if (existing) this.removePluginElement(mounted, key, existing);\n const built = plugin.build(value as never, this.context());\n if (built.object) this.root.add(built.object);\n mounted.set(key, { ...built, hash });\n }\n for (const [key, entry] of mounted)\n if (!seen.has(key)) this.removePluginElement(mounted, key, entry);\n\n // Publish this plugin's aggregate capabilities now that its elements are\n // in sync, so a later plugin that consumes them builds against fresh data.\n plugin.provide?.(\n items.map(([, value]) => value),\n this.context(),\n );\n }\n }\n\n private removePluginElement(\n mounted: Map<string, MountedElement>,\n key: string,\n entry: MountedElement,\n ): void {\n if (entry.object) {\n this.root.remove(entry.object);\n disposeObject(entry.object);\n }\n entry.dispose?.();\n mounted.delete(key);\n }\n\n\n /** Advance time-based plugin elements (e.g. water ripple). Call once per frame. */\n updateElements(deltaMs: number): void {\n for (const mounted of this.pluginElements.values())\n for (const entry of mounted.values()) entry.update?.(deltaMs);\n }\n\n /** Run every element's auxiliary pre-render pass (e.g. water reflection). */\n beforeRender(renderer: WebGLRenderer, scene: ThreeScene, camera: PerspectiveCamera): void {\n for (const mounted of this.pluginElements.values())\n for (const entry of mounted.values()) entry.beforeRender?.(renderer, scene, camera);\n }\n\n\n /** Look up the live three.js object for any plugin element `id`. */\n get(id: string): Object3D | undefined {\n for (const mounted of this.pluginElements.values()) {\n const object = mounted.get(id)?.object;\n if (object) return object;\n }\n return undefined;\n }\n\n /** Live objects of one plugin kind, keyed by `id` — for that plugin's controller. */\n elementsOf(kind: string): ReadonlyMap<string, Object3D> {\n const out = new Map<string, Object3D>();\n const mounted = this.pluginElements.get(kind);\n if (mounted) for (const [id, entry] of mounted) if (entry.object) out.set(id, entry.object);\n return out;\n }\n\n dispose(): void {\n for (const [kind, mounted] of this.pluginElements) {\n for (const [key, entry] of mounted) this.removePluginElement(mounted, key, entry);\n this.pluginElements.delete(kind);\n }\n }\n}\n","import {\n Clock,\n HalfFloatType,\n PCFShadowMap,\n PerspectiveCamera,\n Scene as ThreeScene,\n StereoCamera,\n Vector2,\n WebGLRenderer,\n WebGLRenderTarget,\n type Object3D,\n} from \"three\";\nimport type { EffectComposer } from \"three/examples/jsm/postprocessing/EffectComposer.js\";\nimport type { Pass } from \"three/examples/jsm/postprocessing/Pass.js\";\nimport type { Scene } from \"@scenography/scene\";\n\nimport { EnvironmentController } from \"./environment\";\nimport { configureKtx2 } from \"./builders\";\nimport type { CameraApi } from \"./camera\";\nimport type { LightsApi, MediaController, SkyApi, VideoApi } from \"./api\";\nimport { InteractionController } from \"./interaction\";\nimport { loadingState, onLoading, startLoading, type LoadingState } from \"./loading\";\nimport {\n ControlSettings,\n InputBus,\n MODE_PRESETS,\n loadControlFlags,\n saveControlFlags,\n type ControlFlags,\n type ControlMode,\n} from \"./input\";\nimport {\n CapabilityRegistry,\n isSystemPlugin,\n type ElementController,\n type EnginePlugin,\n type SystemInstance,\n type SystemPlugin,\n} from \"./plugin\";\nimport { SceneManager } from \"./SceneManager\";\n\n/** Maps a plugin kind/name onto the `ctx` slot its api fills. */\nconst CTX_SLOT = {\n camera: \"focus\",\n lights: \"lights\",\n video: \"videos\",\n sky: \"sky\",\n media: \"audio\",\n} as const;\n/** The system the cardboard render toggle drives (gyroscope head-look). */\nconst CARDBOARD_SYSTEM = \"cardboard\";\n\n/** MSAA sample count for antialiasing. `0` disables it. */\nexport type Antialias = 0 | 2 | 4 | 8;\n\n/** Toggleable rendering features. All can be flipped live via `setRender`. */\nexport interface RenderOptions {\n /**\n * Antialiasing as MSAA sample count (`0` off, `2`/`4`/`8`). Applied to the\n * post-process render target so it survives SSAO/bloom and toggles live,\n * unlike the canvas's native MSAA. Defaults to `4`.\n */\n antialias?: Antialias;\n /** Screen-space ambient occlusion as a post-process. Defaults to `false`. */\n ssao?: boolean;\n /**\n * Bloom strength: a soft glow bleeding out of the brightest areas. `0` off;\n * gentle around `0.3`, strong around `1`. Defaults to `0`.\n */\n bloom?: number;\n /** Real-time shadow mapping (PCF). Defaults to `true`. */\n shadows?: boolean;\n /**\n * Cardboard / stereoscopic mode: split the view into a left/right eye pair for\n * a phone-in-a-headset viewer. Skips post-processing while active. Defaults to\n * `false`.\n */\n cardboard?: boolean;\n /**\n * Interocular distance for cardboard mode, in world units. The physical ≈6.4 cm\n * is too much parallax to fuse comfortably through cardboard lenses, so this\n * defaults to `3`; tune it live for the viewer. `0` flattens to mono.\n */\n eyeSeparation?: number;\n}\n\nexport interface GalleryOptions extends RenderOptions {\n /** Canvas the gallery renders into. */\n canvas: HTMLCanvasElement;\n /** Background color. Defaults to a soft gray. */\n background?: string;\n /**\n * Base path of the KTX2 transcoder files (`basis_transcoder.js` and\n * `basis_transcoder.wasm`). Without it, `.ktx2` textures are skipped.\n */\n ktx2Path?: string;\n /**\n * Runtime plugins: element plugins materialize a scene key; system plugins\n * (e.g. a camera) drive shared runtime state. Mixed freely — the kernel tells\n * them apart by shape.\n */\n plugins?: (EnginePlugin | SystemPlugin)[];\n}\n\nconst DEFAULT_EYE_SEPARATION = 3;\n\n/**\n * Drive the free-roam camera from outside the engine — e.g. on-screen touch\n * controls in the overlay. Inputs are analog (`-1..1`) and held until changed.\n */\nexport interface ControlsApi {\n /** Walk forward (`+1`) / back (`-1`); optional strafe right (`+1`) / left (`-1`). */\n setMoveInput(forward: number, strafe?: number): void;\n /** Turn right (`+1`) / left (`-1`); optional look up (`+1`) / down (`-1`). */\n setLookInput(x: number, y?: number): void;\n}\n\n/**\n * The visitor's camera-control preferences (view mode + capability flags). The\n * engine applies the effects that touch rendering/input — gyroscope head-look and\n * the stereo split — as the flags change; the overlay drives it from the mode\n * picker and settings, and reads it to show the right joysticks. See\n * `ControlSettings`.\n */\nexport interface ViewControlsApi {\n /** Current capability flags (a copy). */\n get(): ControlFlags;\n /** The mode the flags represent (`custom` when hand-tuned in settings). */\n getMode(): ControlMode;\n /** Switch to a mode's preset (keeps the look-invert axes). */\n setMode(mode: Exclude<ControlMode, \"custom\">): void;\n /** Set one capability flag (may drop the mode into `custom`). */\n setFlag<K extends keyof ControlFlags>(key: K, value: ControlFlags[K]): void;\n /** Subscribe to changes; returns an unsubscribe. */\n subscribe(fn: (flags: ControlFlags) => void): () => void;\n}\n\n/** Control the HDRI sky / environment at runtime, for behavior code. */\nexport interface EnvironmentApi {\n /** Set the visible sky background brightness (`0` = black). */\n setBackgroundIntensity(value: number): void;\n}\n\n/**\n * High-level API handed to behavior code. Deliberately NOT raw three.js: this is\n * the supported surface. Reach into `three` only as a documented escape hatch.\n */\nexport interface GalleryContext {\n /** Resolve the live object for a wall/artwork/light `id`. */\n entity(id: string): Object3D | undefined;\n /** Control scene lights (dim, set intensity/color) by `id`. */\n readonly lights: LightsApi;\n /** Control background audio (play, pause, fade) by `id`. */\n readonly media: MediaController;\n /** Drive the camera externally (on-screen touch controls, etc.). */\n readonly controls: ControlsApi;\n /** The visitor's camera-control preferences (view mode + capability flags). */\n readonly viewControls: ViewControlsApi;\n /** Control the HDRI sky / environment at runtime. */\n readonly environment: EnvironmentApi;\n /** Drive the procedural sky's time of day. */\n readonly sky: SkyApi;\n /** Animate the camera to frame artworks (e.g. when an info panel opens/closes). */\n readonly camera: CameraApi;\n /** Control video screens (play, pause, show/hide) by `id`. */\n readonly video: VideoApi;\n /** Escape hatch to the underlying three.js objects for advanced use. */\n readonly three: {\n scene: ThreeScene;\n camera: PerspectiveCamera;\n renderer: WebGLRenderer;\n };\n}\n\nexport interface Gallery {\n /** Apply a new scene definition, reconciling only what changed (hot-swap). */\n setScene(scene: Scene): void;\n /** Start the render loop. */\n start(): void;\n /** Stop the render loop. */\n stop(): void;\n /** Tear everything down and release GPU resources. */\n dispose(): void;\n /** Subscribe to a named event; returns an unsubscribe function. */\n on(event: string, handler: (payload?: unknown) => void): () => void;\n /** Emit a named event to subscribers. */\n emit(event: string, payload?: unknown): void;\n /** Run a handler every frame with the frame delta in ms; returns unsubscribe. */\n onFrame(handler: (deltaMs: number) => void): () => void;\n /** Resize the renderer/camera to the given pixel size. */\n resize(width: number, height: number): void;\n /** Toggle rendering features (antialias, ssao, bloom, shadows) live. */\n setRender(options: RenderOptions): void;\n /** Initial visual asset loading progress. */\n readonly loading: {\n get(): LoadingState;\n subscribe(listener: (state: LoadingState) => void): () => void;\n };\n readonly ctx: GalleryContext;\n}\n\n/**\n * Create and run a gallery from a scene definition. Sets up the three.js\n * renderer, camera, lights and free-roam controls, materializes the scene, and\n * starts the render loop. `setScene` later reconciles changes in place.\n */\nexport function createGallery(scene: Scene, options: GalleryOptions): Gallery {\n const { canvas } = options;\n const width = canvas.clientWidth || window.innerWidth;\n const height = canvas.clientHeight || window.innerHeight;\n\n // Native MSAA is off: antialiasing is an SMAA post-pass so it survives SSAO\n // and toggles live. PCF shadow filtering set up once; shadows enabled below.\n const renderer = new WebGLRenderer({ canvas, antialias: false });\n renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));\n renderer.setSize(width, height, false);\n renderer.shadowMap.type = PCFShadowMap;\n\n // Live-toggleable render features.\n const render: Required<RenderOptions> = {\n antialias: options.antialias ?? 4,\n ssao: options.ssao ?? false,\n bloom: options.bloom ?? 0,\n shadows: options.shadows ?? true,\n cardboard: options.cardboard ?? false,\n eyeSeparation: options.eyeSeparation ?? DEFAULT_EYE_SEPARATION,\n };\n\n // Stereoscopic (cardboard) rendering. StereoCamera is core three (not the\n // WebGL-only StereoEffect addon); we render each eye with a scissored viewport,\n // which is portable to WebGPU. aspect 0.5 keeps each half undistorted.\n const stereo = new StereoCamera();\n stereo.aspect = 0.5;\n stereo.eyeSep = render.eyeSeparation;\n const stereoSize = new Vector2();\n\n const threeScene = new ThreeScene();\n\n const camera = new PerspectiveCamera(scene.camera.fov ?? 55, width / height, 0.1, 5000);\n const { position } = scene.camera.start;\n camera.position.set(position[0], position[1], position[2]);\n\n // Split element plugins (materialize a scene key) from system plugins (drive\n // shared state like the camera). Elements go to the manager; systems are set\n // up below, once elements have published their capabilities.\n const allPlugins = options.plugins ?? [];\n const systemPlugins = allPlugins.filter(isSystemPlugin);\n const elementPlugins = allPlugins.filter((p): p is EnginePlugin => !isSystemPlugin(p));\n\n const capabilities = new CapabilityRegistry();\n if (options.ktx2Path) configureKtx2(renderer, options.ktx2Path);\n\n // Count the initial visual assets the scene will fetch, so the app can show\n // progress. Lazy detail assets (e.g. artwork nativeSrc) are not included.\n const assets = new Set<string>();\n for (const plugin of elementPlugins) {\n const { kind, cardinality } = plugin.data;\n const raw = (scene as unknown as Record<string, unknown>)[kind];\n const values =\n cardinality === \"many\"\n ? Array.isArray(raw)\n ? raw\n : []\n : raw !== undefined\n ? [raw]\n : [];\n for (const value of values) {\n for (const url of plugin.data.assets?.(value as never) ?? []) assets.add(url);\n }\n }\n if (scene.environment?.src) assets.add(scene.environment.src);\n startLoading(assets);\n\n const manager = new SceneManager(threeScene, elementPlugins, capabilities, {\n camera,\n renderer,\n });\n manager.reconcile(scene);\n\n const input = new InputBus();\n // The visitor's control preferences (view mode + flags). Created early so system\n // plugins can read the flags (e.g. fpc reads the look-invert axes); the effects\n // that need the head-look system / render are wired below, once they exist. A\n // persisted choice (localStorage) wins over the initial cardboard option so the\n // visitor's last mode is restored across reloads.\n const controlSettings = new ControlSettings(\n loadControlFlags() ?? (options.cardboard ? MODE_PRESETS.cardboard : {}),\n );\n const environment = new EnvironmentController(threeScene);\n environment.reconcile(scene.environment);\n\n const listeners = new Map<string, Set<(payload?: unknown) => void>>();\n const emit = (event: string, payload?: unknown): void => {\n listeners.get(event)?.forEach((handler) => handler(payload));\n };\n const frameHandlers = new Set<(deltaMs: number) => void>();\n\n // Plugins publish their public apis here, keyed by kind/name; the kernel maps\n // them onto `ctx` slots (see `CTX_SLOT`). Element-plugin controllers (lights,\n // video) and system apis (camera, sky, media) both land in this one map.\n const apis = new Map<string, unknown>();\n\n // Element-plugin controllers: an aggregate api over each plugin's live objects\n // (e.g. `ctx.lights`). They advance in the render loop and tear down on dispose.\n const controllers: ElementController[] = [];\n for (const plugin of elementPlugins) {\n const controller = plugin.controller?.(() => manager.elementsOf(plugin.data.kind));\n if (!controller) continue;\n if (controller.api !== undefined) apis.set(plugin.data.kind, controller.api);\n controllers.push(controller);\n }\n\n // System plugins (camera, sky, audio…): drive shared runtime state each frame\n // and may expose an api. They get the scene graph + renderer for subsystems\n // that manage their own objects (sky) or listen on the camera (audio).\n const systems: SystemInstance[] = systemPlugins.map((plugin) => {\n const instance = plugin.setup({\n scene,\n camera,\n canvas,\n scene3d: threeScene,\n renderer,\n input,\n controlFlags: () => controlSettings.get(),\n capabilities,\n emit,\n resolve: (id) => manager.get(id),\n });\n if (instance.api !== undefined) apis.set(plugin.name, instance.api);\n return instance;\n });\n\n // `ctx.camera` is filled by the framing system (\"focus\"). Without it, a no-op\n // keeps callers (the overlay) safe.\n const cameraApi: CameraApi = (apis.get(CTX_SLOT.camera) as CameraApi | undefined) ?? {\n focusArtwork: () => {},\n centerArtwork: () => {},\n release: () => {},\n reset: () => {},\n isAnimating: () => false,\n isFocused: () => false,\n };\n\n // Cardboard render (stereo split) is a kernel feature; the gyroscope head-look\n // that rides with it is the `@scenography/plugin-cardboard` system. The render\n // toggle below drives its `setEnabled`.\n type HeadTracking = { setEnabled(enabled: boolean): void };\n const headTracking = apis.get(CARDBOARD_SYSTEM) as HeadTracking | undefined;\n // Apply the restored (or seeded) flags on load: enable gyro head-look and sync\n // the stereo render. (On iOS the gyro permission can't be granted outside a user\n // gesture, so a restored gyro mode only truly tracks once the visitor interacts —\n // enable() fails gracefully, nothing breaks.)\n const initialFlags = controlSettings.get();\n render.cardboard = initialFlags.splitScreen;\n if (initialFlags.gyro) headTracking?.setEnabled(true);\n\n // gyro and splitScreen are *orthogonal* here — unlike the legacy `cardboard`\n // render toggle that fused them — so a \"VR\" mode can aim with the gyro without\n // the stereo split. Apply those two effects (head-look system + stereo render)\n // as the flags change; the others are read by the overlay and input consumers.\n // render.cardboard / the initial setEnabled above already reflect the seed, so\n // the subscriber only handles subsequent changes.\n let prevFlags = controlSettings.get();\n controlSettings.subscribe((flags) => {\n if (flags.gyro !== prevFlags.gyro) headTracking?.setEnabled(flags.gyro);\n if (flags.splitScreen !== prevFlags.splitScreen) render.cardboard = flags.splitScreen;\n prevFlags = flags;\n saveControlFlags(flags); // remember the choice across reloads\n emit(\"controls:change\", { flags, mode: controlSettings.getMode() });\n });\n\n // No-op fallbacks so the overlay/behavior stay safe when a plugin is absent.\n const lightsApi = (apis.get(CTX_SLOT.lights) as LightsApi | undefined) ?? {\n set: () => {},\n dimAll: () => {},\n restore: () => {},\n };\n const videoApi = (apis.get(CTX_SLOT.video) as VideoApi | undefined) ?? {\n play: () => {},\n pause: () => {},\n setVisible: () => {},\n };\n const skyApi = (apis.get(CTX_SLOT.sky) as SkyApi | undefined) ?? {\n setTime: () => {},\n getTime: () => 12,\n setAzimuth: () => {},\n getAzimuth: () => 0,\n };\n const mediaApi = (apis.get(CTX_SLOT.media) as MediaController | undefined) ?? {\n play: () => {},\n pause: () => {},\n stop: () => {},\n setVolume: () => {},\n playing: () => false,\n setMuted: () => {},\n muted: () => false,\n };\n\n // Per-element interaction-distance gate, derived from each pickable element's\n // `infoRadius`. Read structurally: `infoRadius` is a convention a plugin (the\n // artwork plugin) can put on its elements, not a kernel field. A later\n // interaction pass can move this onto the built object's userData entirely.\n type Gated = { id: string; infoRadius?: number };\n const radiiOf = (s: Scene): Map<string, number> => {\n const gated = (s as { artworks?: Gated[] }).artworks ?? [];\n return new Map(\n gated.filter((a) => a.infoRadius !== undefined).map((a) => [a.id, a.infoRadius as number]),\n );\n };\n\n // Picking is suppressed while the camera animates and while a piece stays\n // focused: once its info panel opens, hovering must not keep highlighting it.\n const interaction = new InteractionController(\n camera,\n canvas,\n threeScene,\n emit,\n () => !cameraApi.isAnimating() && !cameraApi.isFocused(),\n );\n interaction.setRadii(radiiOf(scene));\n interaction.attach();\n const clock = new Clock();\n let rafId: number | null = null;\n\n // Post-processing (MSAA antialias + SSAO + bloom). The whole chain is imported\n // on demand so it only ships when a gallery uses it; until it resolves (or when\n // everything is off) we render straight to the canvas. Antialiasing is MSAA on\n // the composer's render target (`samples`), so changing the sample count\n // rebuilds the composer; SSAO/bloom just flip `.enabled`.\n // Bloom spread/threshold are fixed; its strength is the live `render.bloom`.\n const BLOOM = { radius: 0.4, threshold: 0.85 };\n type PostClasses = {\n EffectComposer: typeof import(\"three/examples/jsm/postprocessing/EffectComposer.js\").EffectComposer;\n RenderPass: typeof import(\"three/examples/jsm/postprocessing/RenderPass.js\").RenderPass;\n SSAOPass: typeof import(\"three/examples/jsm/postprocessing/SSAOPass.js\").SSAOPass;\n UnrealBloomPass: typeof import(\"three/examples/jsm/postprocessing/UnrealBloomPass.js\").UnrealBloomPass;\n OutputPass: typeof import(\"three/examples/jsm/postprocessing/OutputPass.js\").OutputPass;\n };\n let postClasses: PostClasses | null = null;\n let composer: EffectComposer | null = null;\n let ssaoPass: (Pass & { kernelRadius: number }) | null = null;\n let bloomPass: (Pass & { setSize(w: number, h: number): void; strength: number }) | null = null;\n let composerLoading = false;\n\n const pixelRatio = Math.min(window.devicePixelRatio, 2);\n const needsComposer = (): boolean => render.antialias > 0 || render.ssao || render.bloom > 0;\n\n // (Re)build the composer from the loaded classes at the current sample count.\n const buildComposer = (): void => {\n if (!postClasses) return;\n composer?.dispose();\n const { EffectComposer, RenderPass, SSAOPass, UnrealBloomPass, OutputPass } = postClasses;\n // Multisampled target → MSAA that survives the post chain. `samples` is\n // preserved across resizes (setSize only changes width/height). HalfFloat\n // matches the composer's own default and gives bloom its HDR range.\n const target = new WebGLRenderTarget(1, 1, { samples: render.antialias, type: HalfFloatType });\n const c = new EffectComposer(renderer, target);\n c.setPixelRatio(pixelRatio);\n c.setSize(width, height);\n c.addPass(new RenderPass(threeScene, camera));\n const ssao = new SSAOPass(threeScene, camera, width, height);\n ssao.kernelRadius = 16;\n ssao.enabled = render.ssao;\n c.addPass(ssao);\n const bloom = new UnrealBloomPass(new Vector2(width, height), render.bloom, BLOOM.radius, BLOOM.threshold);\n bloom.enabled = render.bloom > 0;\n c.addPass(bloom);\n // Restores tone mapping / sRGB that the composer would otherwise skip.\n c.addPass(new OutputPass());\n composer = c;\n ssaoPass = ssao;\n bloomPass = bloom;\n };\n\n const ensureComposer = (): void => {\n if (!needsComposer()) return;\n if (postClasses) {\n if (!composer) buildComposer();\n return;\n }\n if (composerLoading) return;\n composerLoading = true;\n void Promise.all([\n import(\"three/examples/jsm/postprocessing/EffectComposer.js\"),\n import(\"three/examples/jsm/postprocessing/RenderPass.js\"),\n import(\"three/examples/jsm/postprocessing/SSAOPass.js\"),\n import(\"three/examples/jsm/postprocessing/UnrealBloomPass.js\"),\n import(\"three/examples/jsm/postprocessing/OutputPass.js\"),\n ]).then(([{ EffectComposer }, { RenderPass }, { SSAOPass }, { UnrealBloomPass }, { OutputPass }]) => {\n postClasses = { EffectComposer, RenderPass, SSAOPass, UnrealBloomPass, OutputPass };\n composerLoading = false;\n buildComposer();\n });\n };\n\n // Recompile materials so a shadowMap.enabled change takes effect.\n const refreshShadows = (): void => {\n renderer.shadowMap.enabled = render.shadows;\n threeScene.traverse((obj) => {\n const mat = (obj as { material?: unknown }).material;\n if (!mat) return;\n for (const m of Array.isArray(mat) ? mat : [mat]) (m as { needsUpdate: boolean }).needsUpdate = true;\n });\n };\n refreshShadows();\n ensureComposer();\n\n // Split the canvas into a left/right eye pair. Mirrors what StereoEffect does\n // internally, but stays in our loop and uses only setViewport/setScissor.\n const renderStereo = (): void => {\n camera.updateWorldMatrix(true, false);\n stereo.update(camera);\n renderer.getSize(stereoSize);\n const w = stereoSize.x;\n const h = stereoSize.y;\n if (renderer.autoClear) renderer.clear();\n renderer.setScissorTest(true);\n renderer.setScissor(0, 0, w / 2, h);\n renderer.setViewport(0, 0, w / 2, h);\n renderer.render(threeScene, stereo.cameraL);\n renderer.setScissor(w / 2, 0, w / 2, h);\n renderer.setViewport(w / 2, 0, w / 2, h);\n renderer.render(threeScene, stereo.cameraR);\n renderer.setScissorTest(false);\n renderer.setViewport(0, 0, w, h);\n };\n\n const renderFrame = (): void => {\n rafId = requestAnimationFrame(renderFrame);\n const delta = clock.getDelta();\n const deltaMs = delta * 1000;\n // Systems drive the camera in registration order: the roaming camera first,\n // then the framing system, which may override it during a focus animation.\n for (const system of systems) system.update?.(deltaMs);\n interaction.update(deltaMs);\n manager.updateElements(deltaMs);\n for (const controller of controllers) controller.update?.(deltaMs);\n // Behavior runs after the camera moved this frame, before rendering. (The\n // cardboard system, last in `systems`, already had the final say on\n // orientation above.)\n for (const handler of frameHandlers) handler(deltaMs);\n // Auxiliary passes (e.g. the water's planar reflection) render into their\n // own targets from the current camera, before the main frame.\n manager.beforeRender(renderer, threeScene, camera);\n if (render.cardboard) renderStereo();\n else if (composer && needsComposer()) composer.render(delta);\n else renderer.render(threeScene, camera);\n };\n\n const ctx: GalleryContext = {\n entity: (id) => manager.get(id),\n lights: lightsApi,\n media: mediaApi,\n controls: {\n setMoveInput: (forward, strafe) => input.setMove(forward, strafe),\n setLookInput: (x, y) => input.setLook(x, y),\n },\n viewControls: {\n get: () => controlSettings.get(),\n getMode: () => controlSettings.getMode(),\n setMode: (mode) => controlSettings.setMode(mode),\n setFlag: (key, value) => controlSettings.setFlag(key, value),\n subscribe: (fn) => controlSettings.subscribe(fn),\n },\n environment: {\n setBackgroundIntensity: (v) => environment.setBackgroundIntensity(v),\n },\n sky: skyApi,\n camera: cameraApi,\n video: videoApi,\n three: { scene: threeScene, camera, renderer },\n };\n\n return {\n ctx,\n loading: { get: loadingState, subscribe: onLoading },\n setScene(next) {\n manager.reconcile(next);\n interaction.setRadii(radiiOf(next));\n environment.reconcile(next.environment);\n for (const system of systems) system.setScene?.(next);\n const fov = next.camera.fov ?? 55;\n if (camera.fov !== fov) {\n camera.fov = fov;\n camera.updateProjectionMatrix();\n }\n },\n start() {\n if (rafId === null) {\n clock.start();\n renderFrame();\n }\n },\n stop() {\n if (rafId !== null) {\n cancelAnimationFrame(rafId);\n rafId = null;\n }\n },\n dispose() {\n this.stop();\n for (const system of systems) system.dispose?.();\n for (const controller of controllers) controller.dispose?.();\n interaction.detach();\n environment.dispose();\n manager.dispose();\n composer?.dispose();\n renderer.dispose();\n listeners.clear();\n frameHandlers.clear();\n },\n on(event, handler) {\n const set = listeners.get(event) ?? new Set();\n set.add(handler);\n listeners.set(event, set);\n return () => set.delete(handler);\n },\n emit(event, payload) {\n emit(event, payload);\n },\n onFrame(handler) {\n frameHandlers.add(handler);\n return () => frameHandlers.delete(handler);\n },\n resize(w, h) {\n camera.aspect = w / h;\n camera.updateProjectionMatrix();\n renderer.setSize(w, h, false);\n composer?.setSize(w, h);\n ssaoPass?.setSize(w, h);\n bloomPass?.setSize(w, h);\n },\n setRender(next) {\n if (next.shadows !== undefined && next.shadows !== render.shadows) {\n render.shadows = next.shadows;\n refreshShadows();\n }\n if (next.ssao !== undefined) render.ssao = next.ssao;\n if (next.bloom !== undefined) render.bloom = next.bloom;\n // The sample count lives on the render target, so a change rebuilds the\n // composer (cheap, and only on user toggle).\n const aaChanged = next.antialias !== undefined && next.antialias !== render.antialias;\n if (next.antialias !== undefined) render.antialias = next.antialias;\n if (next.cardboard !== undefined && next.cardboard !== render.cardboard) {\n // Legacy stereo toggle: route through the control state so mode/flags and\n // the render stay one source of truth (the subscriber applies gyro +\n // split; setEnabled thus still runs in this click gesture, which iOS needs\n // for the motion-permission prompt). Superseded by `ctx.viewControls` once\n // the mode picker replaces the cardboard button.\n controlSettings.setMode(next.cardboard ? \"cardboard\" : \"touch\");\n }\n if (next.eyeSeparation !== undefined) {\n render.eyeSeparation = next.eyeSeparation;\n stereo.eyeSep = next.eyeSeparation;\n }\n if (ssaoPass) ssaoPass.enabled = render.ssao;\n if (bloomPass) {\n bloomPass.strength = render.bloom;\n bloomPass.enabled = render.bloom > 0;\n }\n if (aaChanged && composer) buildComposer();\n ensureComposer(); // load/build the chain if an effect was just turned on\n },\n };\n}\n"],"mappings":"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"}
package/dist/index.d.cts CHANGED
@@ -111,6 +111,15 @@ interface SkyApi {
111
111
  getAzimuth(): number;
112
112
  }
113
113
  //#endregion
114
+ //#region src/loading.d.ts
115
+ interface LoadingState {
116
+ loaded: number;
117
+ total: number;
118
+ done: boolean;
119
+ }
120
+ /** Mark one initial asset as loaded; unknown URLs (lazy assets) are ignored. */
121
+ declare const trackAsset: (url: string) => void;
122
+ //#endregion
114
123
  //#region src/input/InputBus.d.ts
115
124
  /**
116
125
  * The shared analog-input channel: the current movement and look intent, each
@@ -582,6 +591,11 @@ interface Gallery {
582
591
  resize(width: number, height: number): void;
583
592
  /** Toggle rendering features (antialias, ssao, bloom, shadows) live. */
584
593
  setRender(options: RenderOptions): void;
594
+ /** Initial visual asset loading progress. */
595
+ readonly loading: {
596
+ get(): LoadingState;
597
+ subscribe(listener: (state: LoadingState) => void): () => void;
598
+ };
585
599
  readonly ctx: GalleryContext;
586
600
  }
587
601
  /**
@@ -700,5 +714,5 @@ declare class InteractionController {
700
714
  private buildHighlight;
701
715
  }
702
716
  //#endregion
703
- export { type Antialias, type BuiltElement, COLLIDERS, type CameraApi, CapabilityRegistry, type Collider, type ControlFlags, type ControlMode, ControlSettings, type ControlsApi, DEFAULT_CONTROL_FLAGS, EDGES, EYE_HEIGHT, type EdgeAnchor, type EdgeIndex, type ElementController, type EngineContext, type EnginePlugin, type EnvironmentApi, type FocusOptions, type Gallery, type GalleryContext, type GalleryOptions, InputBus, InteractionController, type LightSet, type LightSetOptions, type LightsApi, MODE_PRESETS, type MediaController, ROAM_CONTROL, type RenderOptions, type RoamControl, SceneManager, type SkyApi, type SystemContext, type SystemInstance, type SystemPlugin, TERRAIN, type TerrainCapability, type TerrainDefinition, type VideoApi, applyMode, applyTexture, createGallery, getKtx2Loader, isSystemPlugin, modeOf };
717
+ export { type Antialias, type BuiltElement, COLLIDERS, type CameraApi, CapabilityRegistry, type Collider, type ControlFlags, type ControlMode, ControlSettings, type ControlsApi, DEFAULT_CONTROL_FLAGS, EDGES, EYE_HEIGHT, type EdgeAnchor, type EdgeIndex, type ElementController, type EngineContext, type EnginePlugin, type EnvironmentApi, type FocusOptions, type Gallery, type GalleryContext, type GalleryOptions, InputBus, InteractionController, type LightSet, type LightSetOptions, type LightsApi, type LoadingState, MODE_PRESETS, type MediaController, ROAM_CONTROL, type RenderOptions, type RoamControl, SceneManager, type SkyApi, type SystemContext, type SystemInstance, type SystemPlugin, TERRAIN, type TerrainCapability, type TerrainDefinition, type VideoApi, applyMode, applyTexture, createGallery, getKtx2Loader, isSystemPlugin, modeOf, trackAsset };
704
718
  //# sourceMappingURL=index.d.cts.map
package/dist/index.d.mts CHANGED
@@ -111,6 +111,15 @@ interface SkyApi {
111
111
  getAzimuth(): number;
112
112
  }
113
113
  //#endregion
114
+ //#region src/loading.d.ts
115
+ interface LoadingState {
116
+ loaded: number;
117
+ total: number;
118
+ done: boolean;
119
+ }
120
+ /** Mark one initial asset as loaded; unknown URLs (lazy assets) are ignored. */
121
+ declare const trackAsset: (url: string) => void;
122
+ //#endregion
114
123
  //#region src/input/InputBus.d.ts
115
124
  /**
116
125
  * The shared analog-input channel: the current movement and look intent, each
@@ -582,6 +591,11 @@ interface Gallery {
582
591
  resize(width: number, height: number): void;
583
592
  /** Toggle rendering features (antialias, ssao, bloom, shadows) live. */
584
593
  setRender(options: RenderOptions): void;
594
+ /** Initial visual asset loading progress. */
595
+ readonly loading: {
596
+ get(): LoadingState;
597
+ subscribe(listener: (state: LoadingState) => void): () => void;
598
+ };
585
599
  readonly ctx: GalleryContext;
586
600
  }
587
601
  /**
@@ -700,5 +714,5 @@ declare class InteractionController {
700
714
  private buildHighlight;
701
715
  }
702
716
  //#endregion
703
- export { type Antialias, type BuiltElement, COLLIDERS, type CameraApi, CapabilityRegistry, type Collider, type ControlFlags, type ControlMode, ControlSettings, type ControlsApi, DEFAULT_CONTROL_FLAGS, EDGES, EYE_HEIGHT, type EdgeAnchor, type EdgeIndex, type ElementController, type EngineContext, type EnginePlugin, type EnvironmentApi, type FocusOptions, type Gallery, type GalleryContext, type GalleryOptions, InputBus, InteractionController, type LightSet, type LightSetOptions, type LightsApi, MODE_PRESETS, type MediaController, ROAM_CONTROL, type RenderOptions, type RoamControl, SceneManager, type SkyApi, type SystemContext, type SystemInstance, type SystemPlugin, TERRAIN, type TerrainCapability, type TerrainDefinition, type VideoApi, applyMode, applyTexture, createGallery, getKtx2Loader, isSystemPlugin, modeOf };
717
+ export { type Antialias, type BuiltElement, COLLIDERS, type CameraApi, CapabilityRegistry, type Collider, type ControlFlags, type ControlMode, ControlSettings, type ControlsApi, DEFAULT_CONTROL_FLAGS, EDGES, EYE_HEIGHT, type EdgeAnchor, type EdgeIndex, type ElementController, type EngineContext, type EnginePlugin, type EnvironmentApi, type FocusOptions, type Gallery, type GalleryContext, type GalleryOptions, InputBus, InteractionController, type LightSet, type LightSetOptions, type LightsApi, type LoadingState, MODE_PRESETS, type MediaController, ROAM_CONTROL, type RenderOptions, type RoamControl, SceneManager, type SkyApi, type SystemContext, type SystemInstance, type SystemPlugin, TERRAIN, type TerrainCapability, type TerrainDefinition, type VideoApi, applyMode, applyTexture, createGallery, getKtx2Loader, isSystemPlugin, modeOf, trackAsset };
704
718
  //# sourceMappingURL=index.d.mts.map
package/dist/index.mjs CHANGED
@@ -1,4 +1,47 @@
1
1
  import { BoxGeometry, Clock, EquirectangularReflectionMapping, Group, HalfFloatType, Mesh, MeshBasicMaterial, NoColorSpace, PCFShadowMap, PerspectiveCamera, Quaternion, Raycaster, RepeatWrapping, SRGBColorSpace, Scene, StereoCamera, TextureLoader, Vector2, Vector3, WebGLRenderTarget, WebGLRenderer } from "three";
2
+ //#region src/loading.ts
3
+ const pending = /* @__PURE__ */ new Set();
4
+ const listeners = /* @__PURE__ */ new Set();
5
+ let state = {
6
+ loaded: 0,
7
+ total: 0,
8
+ done: true
9
+ };
10
+ const notify = () => {
11
+ for (const listener of listeners) listener(state);
12
+ };
13
+ /** Begin tracking a new initial asset set; resets the previous one. */
14
+ const startLoading = (urls) => {
15
+ pending.clear();
16
+ for (const url of urls) pending.add(url);
17
+ state = {
18
+ loaded: 0,
19
+ total: pending.size,
20
+ done: pending.size === 0
21
+ };
22
+ notify();
23
+ };
24
+ /** Mark one initial asset as loaded; unknown URLs (lazy assets) are ignored. */
25
+ const trackAsset = (url) => {
26
+ if (!pending.delete(url)) return;
27
+ state = {
28
+ loaded: state.total - pending.size,
29
+ total: state.total,
30
+ done: pending.size === 0
31
+ };
32
+ notify();
33
+ };
34
+ /** Current loading progress. */
35
+ const loadingState = () => state;
36
+ /** Subscribe to progress; the listener runs immediately with the current state. */
37
+ const onLoading = (listener) => {
38
+ listeners.add(listener);
39
+ listener(state);
40
+ return () => {
41
+ listeners.delete(listener);
42
+ };
43
+ };
44
+ //#endregion
2
45
  //#region src/environment/EnvironmentController.ts
3
46
  /** Load an equirectangular HDRI, picking the loader by extension. */
4
47
  const loadHDRI = (src, onLoad) => {
@@ -30,6 +73,7 @@ var EnvironmentController = class {
30
73
  this.clear();
31
74
  if (!env) return;
32
75
  loadHDRI(env.src, (tex) => {
76
+ trackAsset(env.src);
33
77
  if (this.hash !== hash) {
34
78
  tex.dispose();
35
79
  return;
@@ -139,7 +183,7 @@ var TextureCache = class {
139
183
  //#endregion
140
184
  //#region src/builders/texture.ts
141
185
  const textureLoader = new TextureLoader();
142
- const cache = new TextureCache((src) => textureLoader.load(src));
186
+ const cache = new TextureCache((src) => textureLoader.load(src, () => trackAsset(src)));
143
187
  const ktx2Entries = /* @__PURE__ */ new Map();
144
188
  const ktx2Owners = /* @__PURE__ */ new Map();
145
189
  /**
@@ -166,6 +210,7 @@ const loadKtx2 = (material, slot, src, repeat, srgb) => {
166
210
  loader.then((ktx2) => ktx2.load(src, (texture) => {
167
211
  configureTexture(texture, repeat, srgb);
168
212
  texture.needsUpdate = true;
213
+ trackAsset(src);
169
214
  created.texture = texture;
170
215
  ktx2Owners.set(texture, created);
171
216
  for (const assign of created.waiters) assign(texture);
@@ -197,6 +242,7 @@ const loadMap = (material, slot, src, repeat, srgb) => {
197
242
  new EXRLoader().load(src, (texture) => {
198
243
  configureTexture(texture, repeat, srgb);
199
244
  texture.needsUpdate = true;
245
+ trackAsset(src);
200
246
  material[slot] = texture;
201
247
  material.needsUpdate = true;
202
248
  });
@@ -876,6 +922,15 @@ function createGallery(scene, options) {
876
922
  const elementPlugins = allPlugins.filter((p) => !isSystemPlugin(p));
877
923
  const capabilities = new CapabilityRegistry();
878
924
  if (options.ktx2Path) configureKtx2(renderer, options.ktx2Path);
925
+ const assets = /* @__PURE__ */ new Set();
926
+ for (const plugin of elementPlugins) {
927
+ const { kind, cardinality } = plugin.data;
928
+ const raw = scene[kind];
929
+ const values = cardinality === "many" ? Array.isArray(raw) ? raw : [] : raw !== void 0 ? [raw] : [];
930
+ for (const value of values) for (const url of plugin.data.assets?.(value) ?? []) assets.add(url);
931
+ }
932
+ if (scene.environment?.src) assets.add(scene.environment.src);
933
+ startLoading(assets);
879
934
  const manager = new SceneManager(threeScene, elementPlugins, capabilities, {
880
935
  camera,
881
936
  renderer
@@ -1098,6 +1153,10 @@ function createGallery(scene, options) {
1098
1153
  renderer
1099
1154
  }
1100
1155
  },
1156
+ loading: {
1157
+ get: loadingState,
1158
+ subscribe: onLoading
1159
+ },
1101
1160
  setScene(next) {
1102
1161
  manager.reconcile(next);
1103
1162
  interaction.setRadii(radiiOf(next));
@@ -1179,6 +1238,6 @@ function createGallery(scene, options) {
1179
1238
  };
1180
1239
  }
1181
1240
  //#endregion
1182
- export { COLLIDERS, CapabilityRegistry, ControlSettings, DEFAULT_CONTROL_FLAGS, EDGES, EYE_HEIGHT, InputBus, InteractionController, MODE_PRESETS, ROAM_CONTROL, SceneManager, TERRAIN, applyMode, applyTexture, createGallery, getKtx2Loader, isSystemPlugin, modeOf };
1241
+ export { COLLIDERS, CapabilityRegistry, ControlSettings, DEFAULT_CONTROL_FLAGS, EDGES, EYE_HEIGHT, InputBus, InteractionController, MODE_PRESETS, ROAM_CONTROL, SceneManager, TERRAIN, applyMode, applyTexture, createGallery, getKtx2Loader, isSystemPlugin, modeOf, trackAsset };
1183
1242
 
1184
1243
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["FLAG_KEYS","ThreeScene"],"sources":["../src/environment/EnvironmentController.ts","../src/builders/ktx2.ts","../src/builders/textureCache.ts","../src/builders/texture.ts","../src/interaction/InteractionController.ts","../src/input/InputBus.ts","../src/input/controlSettings.ts","../src/input/controlStorage.ts","../src/plugin/CapabilityRegistry.ts","../src/plugin/systemPlugin.ts","../src/plugin/roamControl.ts","../src/plugin/terrain.ts","../src/plugin/colliders.ts","../src/plugin/edges.ts","../src/plugin/eyeHeight.ts","../src/SceneManager.ts","../src/createGallery.ts"],"sourcesContent":["import { EquirectangularReflectionMapping, type Scene as ThreeScene, type Texture } from \"three\";\nimport type { Environment } from \"@scenography/scene\";\n\n/** Load an equirectangular HDRI, picking the loader by extension. */\nconst loadHDRI = (src: string, onLoad: (tex: Texture) => void): void => {\n if (src.toLowerCase().endsWith(\".exr\")) {\n void import(\"three/examples/jsm/loaders/EXRLoader.js\").then(({ EXRLoader }) => {\n new EXRLoader().load(src, onLoad);\n });\n } else {\n void import(\"three/examples/jsm/loaders/RGBELoader.js\").then(({ RGBELoader }) => {\n new RGBELoader().load(src, onLoad);\n });\n }\n};\n\n/**\n * Owns the scene's HDRI sky / image-based lighting and keeps it in sync with the\n * `Environment` definition. Loads the equirectangular map on demand (its loader\n * ships in its own chunk) and assigns it to `scene.background` and/or\n * `scene.environment`. Reconciles by a JSON hash so hot edits only reload when\n * the definition actually changes.\n */\nexport class EnvironmentController {\n private readonly scene: ThreeScene;\n private hash: string | null = null;\n private texture: Texture | null = null;\n\n constructor(scene: ThreeScene) {\n this.scene = scene;\n }\n\n reconcile(env: Environment | undefined): void {\n const hash = env ? JSON.stringify(env) : null;\n if (hash === this.hash) return;\n this.hash = hash;\n this.clear();\n if (!env) return;\n\n loadHDRI(env.src, (tex) => {\n // A newer reconcile superseded this load before it finished.\n if (this.hash !== hash) {\n tex.dispose();\n return;\n }\n tex.mapping = EquirectangularReflectionMapping;\n this.texture = tex;\n if (env.background ?? true) {\n this.scene.background = tex;\n this.scene.backgroundIntensity = env.intensity ?? 1;\n }\n if (env.lighting ?? true) this.scene.environment = tex;\n });\n }\n\n /** Set the visible sky background brightness (0 = black). Live, for behavior. */\n setBackgroundIntensity(value: number): void {\n this.scene.backgroundIntensity = value;\n }\n\n private clear(): void {\n this.scene.background = null;\n this.scene.environment = null;\n this.texture?.dispose();\n this.texture = null;\n }\n\n dispose(): void {\n this.clear();\n this.hash = null;\n }\n}\n","import type { CompressedTexture, WebGLRenderer } from \"three\";\n\n/** The part of three's KTX2Loader the engine and plugins use. */\nexport interface Ktx2Loader {\n load(\n url: string,\n onLoad: (texture: CompressedTexture) => void,\n onProgress?: (event: ProgressEvent) => void,\n onError?: (error: unknown) => void,\n ): void;\n}\n\nlet loader: Promise<Ktx2Loader> | null = null;\n\n/**\n * Configure the shared KTX2 loader. Called by the kernel once the renderer\n * exists; `.ktx2` textures and glTF models then decode through it.\n */\nexport const configureKtx2 = (renderer: WebGLRenderer, path: string): void => {\n loader = import(\"three/examples/jsm/loaders/KTX2Loader.js\").then(({ KTX2Loader }) => {\n const instance = new KTX2Loader();\n instance.setTranscoderPath(path);\n instance.detectSupport(renderer);\n return instance as unknown as Ktx2Loader;\n });\n};\n\n/** The configured loader, or null when the gallery did not set `ktx2Path`. */\nexport const getKtx2Loader = (): Promise<Ktx2Loader> | null => loader;\n","import { NoColorSpace, RepeatWrapping, SRGBColorSpace, type Texture } from \"three\";\nimport type { Vec2 } from \"@scenography/scene\";\n\n/** Creates the texture object for a source; its image may still be loading. */\nexport type TextureSource = (src: string) => Texture;\n\ntype Entry = {\n key: string;\n texture: Texture;\n refs: number;\n};\n\n/** Two surfaces can share a texture only at the same tiling and color space. */\nexport const textureKey = (src: string, repeat: Vec2, srgb: boolean): string =>\n `${src}|${repeat[0]}x${repeat[1]}|${srgb ? \"srgb\" : \"linear\"}`;\n\n/** Tiling and color space of a texture; the image arrives later from the loader. */\nexport const configureTexture = (texture: Texture, repeat: Vec2, srgb: boolean): void => {\n texture.wrapS = RepeatWrapping;\n texture.wrapT = RepeatWrapping;\n texture.repeat.set(repeat[0], repeat[1]);\n texture.colorSpace = srgb ? SRGBColorSpace : NoColorSpace;\n};\n\n/**\n * Shares textures between materials: the same source at the same tiling and\n * color space becomes one texture, loaded once and uploaded to the GPU once.\n * Reference counted, so releasing a material keeps the texture alive while\n * another one still samples it.\n */\nexport class TextureCache {\n private readonly entries = new Map<string, Entry>();\n private readonly owners = new Map<Texture, Entry>();\n\n constructor(private readonly source: TextureSource) {}\n\n /** The shared texture for a source, created on first use. */\n acquire(src: string, repeat: Vec2, srgb: boolean): Texture {\n const key = textureKey(src, repeat, srgb);\n const existing = this.entries.get(key);\n if (existing) {\n existing.refs += 1;\n return existing.texture;\n }\n const texture = this.source(src);\n configureTexture(texture, repeat, srgb);\n const entry = { key, texture, refs: 1 };\n this.entries.set(key, entry);\n this.owners.set(texture, entry);\n return texture;\n }\n\n /** Drop one reference; the texture is disposed when the last one goes. */\n release(texture: Texture): void {\n const entry = this.owners.get(texture);\n if (!entry) {\n texture.dispose();\n return;\n }\n entry.refs -= 1;\n if (entry.refs > 0) return;\n this.entries.delete(entry.key);\n this.owners.delete(texture);\n texture.dispose();\n }\n\n /** Textures currently shared. */\n get size(): number {\n return this.entries.size;\n }\n}\n","import { MeshStandardMaterial, TextureLoader, Vector2, type Texture } from \"three\";\nimport type { SurfaceTexture, Vec2 } from \"@scenography/scene\";\n\nimport { getKtx2Loader } from \"./ktx2\";\nimport { configureTexture, TextureCache, textureKey } from \"./textureCache\";\n\nconst textureLoader = new TextureLoader();\nconst cache = new TextureCache((src) => textureLoader.load(src));\n\ntype MapSlot = \"map\" | \"normalMap\" | \"roughnessMap\" | \"aoMap\";\n\ntype Ktx2Entry = {\n key: string;\n refs: number;\n texture?: Texture;\n waiters: Array<(texture: Texture) => void>;\n};\n\nconst ktx2Entries = new Map<string, Ktx2Entry>();\nconst ktx2Owners = new Map<Texture, Ktx2Entry>();\n\n/**\n * KTX2 textures resolve asynchronously, so the material slot is filled through\n * a waiter list. One entry per source and tiling is shared by every material\n * that samples it, just like the image cache.\n */\nconst loadKtx2 = (\n material: MeshStandardMaterial,\n slot: MapSlot,\n src: string,\n repeat: Vec2,\n srgb: boolean,\n): void => {\n const loader = getKtx2Loader();\n if (!loader) {\n console.warn(`KTX2 texture \"${src}\" needs the gallery's ktx2Path option.`);\n return;\n }\n const key = textureKey(src, repeat, srgb);\n let entry = ktx2Entries.get(key);\n if (!entry) {\n entry = { key, refs: 0, waiters: [] };\n ktx2Entries.set(key, entry);\n const created = entry;\n void loader.then((ktx2) =>\n ktx2.load(\n src,\n (texture) => {\n configureTexture(texture, repeat, srgb);\n texture.needsUpdate = true;\n created.texture = texture;\n ktx2Owners.set(texture, created);\n for (const assign of created.waiters) assign(texture);\n created.waiters = [];\n },\n undefined,\n (error) => console.error(`Failed to load KTX2 texture \"${src}\".`, error),\n ),\n );\n }\n entry.refs += 1;\n if (entry.texture) {\n material[slot] = entry.texture;\n material.needsUpdate = true;\n } else {\n entry.waiters.push((texture) => {\n material[slot] = texture;\n material.needsUpdate = true;\n });\n }\n};\n\n/**\n * Load one map into `material[slot]`. Textures are shared: every material that\n * samples the same source at the same tiling reuses one texture object. `.exr`\n * maps go through EXRLoader, imported on demand, and are not shared because\n * they resolve asynchronously; `.ktx2` maps share through their own cache.\n */\nconst loadMap = (\n material: MeshStandardMaterial,\n slot: MapSlot,\n src: string,\n repeat: Vec2,\n srgb: boolean,\n): void => {\n if (src.toLowerCase().endsWith(\".ktx2\")) {\n loadKtx2(material, slot, src, repeat, srgb);\n return;\n }\n if (src.toLowerCase().endsWith(\".exr\")) {\n void import(\"three/examples/jsm/loaders/EXRLoader.js\").then(({ EXRLoader }) => {\n new EXRLoader().load(src, (texture) => {\n configureTexture(texture, repeat, srgb);\n texture.needsUpdate = true;\n material[slot] = texture;\n material.needsUpdate = true;\n });\n });\n return;\n }\n material[slot] = cache.acquire(src, repeat, srgb);\n};\n\n/**\n * Apply a `SurfaceTexture` to a standard material. Returns whether a second UV\n * set (`uv1`) is needed — three samples `aoMap` from it, so the geometry must\n * provide one.\n */\nexport const applyTexture = (\n material: MeshStandardMaterial,\n texture: SurfaceTexture,\n): boolean => {\n const repeat = texture.repeat ?? [1, 1];\n loadMap(material, \"map\", texture.src, repeat, true);\n if (texture.normal) {\n loadMap(material, \"normalMap\", texture.normal, repeat, false);\n const scale = texture.normalScale ?? 1;\n material.normalScale = new Vector2(scale, scale);\n }\n if (texture.roughness) loadMap(material, \"roughnessMap\", texture.roughness, repeat, false);\n if (texture.ao) loadMap(material, \"aoMap\", texture.ao, repeat, false);\n material.needsUpdate = true;\n return texture.ao !== undefined;\n};\n\n/**\n * Release one reference to a texture assigned by `applyTexture`. Shared\n * textures stay alive while another material still samples them.\n */\nexport const releaseTexture = (texture: Texture): void => {\n const ktx2 = ktx2Owners.get(texture);\n if (!ktx2) {\n cache.release(texture);\n return;\n }\n ktx2.refs -= 1;\n if (ktx2.refs > 0) return;\n ktx2Owners.delete(texture);\n ktx2Entries.delete(ktx2.key);\n texture.dispose();\n};\n","import {\n BoxGeometry,\n Group,\n Mesh,\n MeshBasicMaterial,\n Quaternion,\n Raycaster,\n Vector2,\n Vector3,\n type Object3D,\n type PerspectiveCamera,\n type Scene as ThreeScene,\n} from \"three\";\n\nconst HIGHLIGHT_COLOR = \"#ef4444\";\nconst HIGHLIGHT_THICKNESS = 1.5; // world units (cm)\nconst HIGHLIGHT_MARGIN = 1.5; // gap between the piece and the frame\nconst CLICK_SLOP = 4; // px of movement still counted as a click, not a drag\nconst PICK_INTERVAL = 60; // ms between hover raycasts while the pointer is over the canvas\n\ninterface PickInfo {\n width: number;\n height: number;\n frontOffset: number;\n}\n\ninterface Pick {\n id: string;\n holder: Object3D;\n info: PickInfo;\n}\n\n/**\n * Pointer picking for artworks: hovering a piece's click target (its label, or\n * the piece itself when it has none) draws a pulsing red frame and shows a\n * pointer cursor; clicking it emits `artwork:select { id }`. Targets are marked\n * in the scene graph (`userData.pickTarget`/`pickInfo`) by the artwork builder,\n * so this just raycasts and walks up to find them. An optional per-id radius\n * gates interaction to when the camera is reasonably close.\n */\nexport class InteractionController {\n private readonly raycaster = new Raycaster();\n private readonly pointer = new Vector2();\n private over = false;\n private downPos: { x: number; y: number } | null = null;\n private radii = new Map<string, number>();\n private hovered: string | null = null;\n private time = 0;\n private pickElapsed = 0;\n private pointerMoved = false;\n\n private readonly highlight = new Group();\n private highlightMat: MeshBasicMaterial | null = null;\n private readonly handlers: Array<[EventTarget, string, EventListener]> = [];\n\n constructor(\n private readonly camera: PerspectiveCamera,\n private readonly canvas: HTMLCanvasElement,\n private readonly scene: ThreeScene,\n private readonly emit: (event: string, payload?: unknown) => void,\n private readonly canInteract: () => boolean,\n ) {\n this.highlight.visible = false;\n this.scene.add(this.highlight);\n }\n\n /** Per-artwork max interaction distance (XZ). Absent id = no distance gate. */\n setRadii(radii: Map<string, number>): void {\n this.radii = radii;\n }\n\n attach(): void {\n const bind = (type: string, listener: EventListener): void => {\n this.canvas.addEventListener(type, listener);\n this.handlers.push([this.canvas, type, listener]);\n };\n bind(\"pointermove\", (e) => this.onMove(e as PointerEvent));\n bind(\"pointerdown\", (e) => this.onDown(e as PointerEvent));\n bind(\"pointerup\", (e) => this.onUp(e as PointerEvent));\n bind(\"pointerleave\", () => {\n this.over = false;\n });\n }\n\n detach(): void {\n for (const [target, type, listener] of this.handlers) target.removeEventListener(type, listener);\n this.handlers.length = 0;\n this.scene.remove(this.highlight);\n this.clearHighlight();\n }\n\n private setPointer(e: PointerEvent): void {\n const rect = this.canvas.getBoundingClientRect();\n this.pointer.set(\n ((e.clientX - rect.left) / rect.width) * 2 - 1,\n -((e.clientY - rect.top) / rect.height) * 2 + 1,\n );\n this.over = true;\n this.pointerMoved = true;\n }\n\n private onMove(e: PointerEvent): void {\n this.setPointer(e);\n }\n\n private onDown(e: PointerEvent): void {\n this.downPos = { x: e.clientX, y: e.clientY };\n }\n\n private onUp(e: PointerEvent): void {\n const down = this.downPos;\n this.downPos = null;\n if (!down || !this.canInteract()) return;\n if (Math.hypot(e.clientX - down.x, e.clientY - down.y) > CLICK_SLOP) return; // a drag, not a click\n this.setPointer(e);\n const pick = this.raycast();\n if (pick) this.emit(\"artwork:select\", { id: pick.id });\n }\n\n /** Resolve the nearest pickable artwork under the pointer, honoring the gate. */\n private raycast(): Pick | null {\n this.raycaster.setFromCamera(this.pointer, this.camera);\n const hits = this.raycaster.intersectObjects(this.scene.children, true);\n for (const hit of hits) {\n let node: Object3D | null = hit.object;\n while (node && node.userData.pickTarget === undefined) node = node.parent;\n if (!node) continue;\n const id = node.userData.pickTarget as string;\n const radius = this.radii.get(id);\n if (radius !== undefined) {\n const p = node.getWorldPosition(new Vector3());\n if (Math.hypot(this.camera.position.x - p.x, this.camera.position.z - p.z) > radius) continue;\n }\n return { id, holder: node, info: node.userData.pickInfo as PickInfo };\n }\n return null;\n }\n\n /**\n * Recompute hover state and animate the highlight. Call once per frame.\n * Hover picking is throttled: a full-scene raycast runs at most once every\n * `PICK_INTERVAL` ms, or immediately after the pointer moves. The camera can\n * move without the pointer, so the interval keeps hovering correct while\n * walking.\n */\n update(deltaMs: number): void {\n this.time += deltaMs;\n\n if (!this.over || !this.canInteract()) {\n // No picking while the visitor cannot interact; pick at once when they can.\n this.pickElapsed = PICK_INTERVAL;\n this.pointerMoved = false;\n this.applyPick(null);\n } else {\n this.pickElapsed += deltaMs;\n if (this.pointerMoved) this.pickElapsed = PICK_INTERVAL;\n this.pointerMoved = false;\n if (this.pickElapsed >= PICK_INTERVAL) {\n this.pickElapsed = 0;\n this.applyPick(this.raycast());\n }\n }\n\n if (this.highlight.visible && this.highlightMat) {\n this.highlightMat.opacity = 0.45 + 0.45 * (0.5 + 0.5 * Math.sin(this.time / 320));\n }\n }\n\n /** Swap the hovered piece, touching the canvas and the highlight only on change. */\n private applyPick(pick: Pick | null): void {\n if ((pick?.id ?? null) === this.hovered) return;\n this.hovered = pick?.id ?? null;\n this.canvas.style.cursor = pick ? \"pointer\" : \"\";\n if (pick) this.buildHighlight(pick);\n else this.highlight.visible = false;\n }\n\n private clearHighlight(): void {\n for (const child of [...this.highlight.children]) {\n this.highlight.remove(child);\n (child as Mesh).geometry?.dispose();\n }\n this.highlightMat?.dispose();\n this.highlightMat = null;\n }\n\n /** Position a fresh pulsing red frame over the picked target's front face. */\n private buildHighlight(pick: Pick): void {\n this.clearHighlight();\n const mat = new MeshBasicMaterial({ color: HIGHLIGHT_COLOR, transparent: true, depthTest: false });\n this.highlightMat = mat;\n\n const w = pick.info.width + 2 * HIGHLIGHT_MARGIN;\n const h = pick.info.height + 2 * HIGHLIGHT_MARGIN;\n const t = HIGHLIGHT_THICKNESS;\n // Four bars forming a border (horizontals span the corners; verticals fill in).\n const bars: [number, number, number, number][] = [\n [w + 2 * t, t, 0, h / 2 + t / 2],\n [w + 2 * t, t, 0, -(h / 2 + t / 2)],\n [t, h, -(w / 2 + t / 2), 0],\n [t, h, w / 2 + t / 2, 0],\n ];\n for (const [bw, bh, x, y] of bars) {\n const bar = new Mesh(new BoxGeometry(bw, bh, 0.5), mat);\n bar.position.set(x, y, 0);\n bar.renderOrder = 999;\n this.highlight.add(bar);\n }\n\n const pos = pick.holder.getWorldPosition(new Vector3());\n const quat = pick.holder.getWorldQuaternion(new Quaternion());\n const normal = new Vector3(0, 0, 1).applyQuaternion(quat);\n pos.addScaledVector(normal, pick.info.frontOffset);\n this.highlight.position.copy(pos);\n this.highlight.quaternion.copy(quat);\n this.highlight.visible = true;\n }\n}\n","/**\n * The shared analog-input channel: the current movement and look intent, each\n * axis in `-1..1` and held until a producer changes it. Producers (on-screen\n * joysticks in the overlay, a gamepad…) write to it; whatever camera system is\n * active reads it and interprets it its own way — free-roam walks with it, a\n * fixed camera might only use the look axes to glance around. This is what makes\n * the joysticks reusable across camera types. Pure state — no three.js.\n */\nexport class InputBus {\n /** Forward (`+1`) / backward (`-1`). */\n moveForward = 0;\n /** Strafe right (`+1`) / left (`-1`). */\n moveStrafe = 0;\n /** Look right (`+1`) / left (`-1`). */\n lookX = 0;\n /** Look up (`+1`) / down (`-1`). */\n lookY = 0;\n\n /** Set the movement intent (forward, strafe). */\n setMove(forward: number, strafe = 0): void {\n this.moveForward = forward;\n this.moveStrafe = strafe;\n }\n\n /** Set the look intent (turn, pitch). */\n setLook(x: number, y = 0): void {\n this.lookX = x;\n this.lookY = y;\n }\n}\n","/**\n * The visitor's camera-control preferences: a set of orthogonal capability flags\n * plus a derived, friendly \"mode\". Pure state — no three.js, no DOM. The engine\n * wires the flags to effects (gyroscope head-look, stereo split); consumers read\n * them (the analog look-invert axes); the overlay drives it from a mode picker\n * (the `ScanEye` button) and the settings panel. Persistence and device\n * feature-detection live in those callers, not here, so this stays trivially\n * unit-testable.\n */\n\n/**\n * Orthogonal control capabilities. `gyro` / `splitScreen` / `joysticks` are what a\n * mode fixes; the two `invertLook*` axes are independent look-feel preferences\n * (some people feel the right stick inverted), kept separate from the mode.\n */\nexport interface ControlFlags {\n /** Orient the camera from the device gyroscope (head-look). */\n gyro: boolean;\n /** Split the view into a stereo left/right eye pair (phone-in-a-headset). */\n splitScreen: boolean;\n /** Show the on-screen virtual joysticks. */\n joysticks: boolean;\n /** Invert the horizontal (turn) axis of analog look. */\n invertLookX: boolean;\n /** Invert the vertical (pitch) axis of analog look. */\n invertLookY: boolean;\n}\n\n/** A named preset over the mode-defining flags; `custom` when none matches. */\nexport type ControlMode = \"touch\" | \"vr\" | \"cardboard\" | \"custom\";\n\n/** The three flags a mode fixes (the look-invert axes stay a separate preference). */\ntype ModeFlags = Pick<ControlFlags, \"gyro\" | \"splitScreen\" | \"joysticks\">;\n\n/**\n * Mode presets. Touch = two joysticks, no gyro. VR = the gyro aims and a single\n * move joystick walks you, no split. Cardboard = gyro aims, stereo split, gaze\n * select (no joysticks). The move-only single stick in VR is derived downstream\n * from `gyro && !splitScreen`, so it needs no flag of its own.\n */\nexport const MODE_PRESETS: Record<Exclude<ControlMode, \"custom\">, ModeFlags> = {\n touch: { gyro: false, splitScreen: false, joysticks: true },\n vr: { gyro: true, splitScreen: false, joysticks: true },\n cardboard: { gyro: true, splitScreen: true, joysticks: false },\n};\n\n/** Default flags: plain touch, no look inversion. */\nexport const DEFAULT_CONTROL_FLAGS: ControlFlags = {\n ...MODE_PRESETS.touch,\n invertLookX: false,\n invertLookY: false,\n};\n\nconst FLAG_KEYS: readonly (keyof ControlFlags)[] = [\n \"gyro\",\n \"splitScreen\",\n \"joysticks\",\n \"invertLookX\",\n \"invertLookY\",\n];\n\nconst equalFlags = (a: ControlFlags, b: ControlFlags): boolean =>\n FLAG_KEYS.every((key) => a[key] === b[key]);\n\n/** The mode a flag set represents, or `custom` when no preset matches. */\nexport function modeOf(flags: ControlFlags): ControlMode {\n for (const mode of [\"touch\", \"vr\", \"cardboard\"] as const) {\n const preset = MODE_PRESETS[mode];\n if (\n flags.gyro === preset.gyro &&\n flags.splitScreen === preset.splitScreen &&\n flags.joysticks === preset.joysticks\n )\n return mode;\n }\n return \"custom\";\n}\n\n/** Apply a mode's preset onto `flags`, leaving the look-invert axes untouched. */\nexport function applyMode(\n flags: ControlFlags,\n mode: Exclude<ControlMode, \"custom\">,\n): ControlFlags {\n return { ...flags, ...MODE_PRESETS[mode] };\n}\n\n/**\n * Live control-preferences state: holds the flags, derives the mode, and notifies\n * subscribers on every real change. Callers apply the effects — the engine wires\n * gyro and split, the overlay redraws — so this object is just the shared source\n * of truth they all read and write.\n */\nexport class ControlSettings {\n private flags: ControlFlags;\n private readonly subscribers = new Set<(flags: ControlFlags) => void>();\n\n constructor(initial: Partial<ControlFlags> = {}) {\n this.flags = { ...DEFAULT_CONTROL_FLAGS, ...initial };\n }\n\n /** The current flags (a copy — mutate through `setMode` / `setFlag`). */\n get(): ControlFlags {\n return { ...this.flags };\n }\n\n /** The mode the current flags represent (`custom` when hand-tuned). */\n getMode(): ControlMode {\n return modeOf(this.flags);\n }\n\n /** Switch to a mode's preset (keeps the look-invert axes). No-op if unchanged. */\n setMode(mode: Exclude<ControlMode, \"custom\">): void {\n this.replace(applyMode(this.flags, mode));\n }\n\n /** Set one flag. Flipping a mode-defining flag can drop you into `custom`. */\n setFlag<K extends keyof ControlFlags>(key: K, value: ControlFlags[K]): void {\n this.replace({ ...this.flags, [key]: value });\n }\n\n /** Subscribe to changes; returns an unsubscribe. Fires on every real change. */\n subscribe(fn: (flags: ControlFlags) => void): () => void {\n this.subscribers.add(fn);\n return () => this.subscribers.delete(fn);\n }\n\n private replace(next: ControlFlags): void {\n if (equalFlags(this.flags, next)) return;\n this.flags = next;\n const snapshot = this.get();\n for (const fn of this.subscribers) fn(snapshot);\n }\n}\n","import { DEFAULT_CONTROL_FLAGS, type ControlFlags } from \"./controlSettings\";\n\n/**\n * localStorage persistence for the control flags, kept out of the pure\n * `ControlSettings` so that stays DOM-free and unit-testable. Both functions are\n * best-effort and guarded: no `localStorage` (SSR, privacy mode) or malformed\n * data just falls back to defaults, never throws.\n */\nconst STORAGE_KEY = \"scenography:controls\";\n\nconst FLAG_KEYS = Object.keys(DEFAULT_CONTROL_FLAGS) as (keyof ControlFlags)[];\n\n/** Read persisted control flags; `null` when absent, unavailable or malformed. */\nexport function loadControlFlags(): Partial<ControlFlags> | null {\n try {\n const raw = globalThis.localStorage?.getItem(STORAGE_KEY);\n if (!raw) return null;\n const parsed = JSON.parse(raw) as Record<string, unknown>;\n const flags: Partial<ControlFlags> = {};\n // Copy only known boolean keys, so stale/garbage fields can't leak in.\n for (const key of FLAG_KEYS) {\n if (typeof parsed[key] === \"boolean\") flags[key] = parsed[key] as boolean;\n }\n return flags;\n } catch {\n return null;\n }\n}\n\n/** Persist control flags (best-effort; ignores a full/blocked store). */\nexport function saveControlFlags(flags: ControlFlags): void {\n try {\n globalThis.localStorage?.setItem(STORAGE_KEY, JSON.stringify(flags));\n } catch {\n /* storage unavailable or full — persistence is best-effort */\n }\n}\n","/**\n * Shared runtime contracts between plugins, keyed by name. A plugin (or the\n * kernel) *provides* a capability — e.g. the floor provides `terrain` — and\n * other plugins *consume* it if present, without depending on the provider's\n * package. Each name keeps a monotonic version counter so consumers can hash\n * their dependencies cheaply and rebuild when a provider changes.\n */\nexport class CapabilityRegistry {\n private readonly values = new Map<string, unknown>();\n private readonly versions = new Map<string, number>();\n /** Last change-token seen per name, to suppress redundant version bumps. */\n private readonly tokens = new Map<string, string>();\n\n /**\n * Publish (or replace) the capability under `name`. Pass a `token` (a cheap\n * hash of the inputs) to dedupe: re-providing with an unchanged token is a\n * no-op, so consumers that depend on this capability don't rebuild when the\n * provider recomputes an identical value.\n */\n provide<T>(name: string, value: T, token?: string): void {\n if (token !== undefined && this.values.has(name) && this.tokens.get(name) === token) return;\n this.values.set(name, value);\n if (token !== undefined) this.tokens.set(name, token);\n else this.tokens.delete(name);\n this.bump(name);\n }\n\n /** Remove the capability; consumers see `undefined` and a version bump. */\n revoke(name: string): void {\n if (this.values.delete(name)) {\n this.tokens.delete(name);\n this.bump(name);\n }\n }\n\n get<T>(name: string): T | undefined {\n return this.values.get(name) as T | undefined;\n }\n\n /** Monotonic change counter for `name` (`0` if never provided). */\n version(name: string): number {\n return this.versions.get(name) ?? 0;\n }\n\n private bump(name: string): void {\n this.versions.set(name, (this.versions.get(name) ?? 0) + 1);\n }\n}\n","import type {\n Object3D,\n PerspectiveCamera,\n Scene as ThreeScene,\n WebGLRenderer,\n} from \"three\";\nimport type { Scene } from \"@scenography/scene\";\n\nimport type { CapabilityRegistry } from \"./CapabilityRegistry\";\nimport type { ControlFlags, InputBus } from \"../input\";\n\n/**\n * What the kernel hands a system on setup. A *system* plugin — unlike an element\n * plugin — produces no scene object; it drives shared runtime state (the camera,\n * input…) each frame. The camera is a kernel primitive (the renderer needs one),\n * so systems receive it and drive it, publishing/consuming capabilities to\n * cooperate (e.g. the camera plugin publishes `roamControl`).\n */\nexport interface SystemContext {\n /** The current scene (its data keys, start pose, boundary…); also `setScene` on hot-swap. */\n scene: Scene;\n /** The render camera the system may drive. */\n camera: PerspectiveCamera;\n /** The canvas, for pointer/keyboard listeners. */\n canvas: HTMLElement;\n /** The three.js scene graph, for systems that manage their own objects (sky…). */\n scene3d: ThreeScene;\n /** The renderer, for systems that need it (e.g. sky IBL generation). */\n renderer: WebGLRenderer;\n /** Shared analog input (joysticks feed it); systems interpret it. */\n input: InputBus;\n /** The visitor's current control flags (e.g. look-invert axes), read live. */\n controlFlags: () => ControlFlags;\n /** Capability registry for cross-plugin contracts (terrain, colliders…). */\n capabilities: CapabilityRegistry;\n /** Emit a gallery event. */\n emit: (event: string, payload?: unknown) => void;\n /** Resolve a live scene object by id (e.g. for a framing system). */\n resolve: (id: string) => Object3D | undefined;\n}\n\n/** A running system: its per-frame update, hot-swap and teardown hooks. */\nexport interface SystemInstance<Api = unknown> {\n /** Advance the system by the frame delta (ms). */\n update?(deltaMs: number): void;\n /** React to a hot-swapped scene definition. */\n setScene?(scene: Scene): void;\n /** Tear down (detach listeners, revoke capabilities). */\n dispose?(): void;\n /**\n * Public API the system exposes to userland/overlay. The kernel maps known\n * system names onto `ctx` (e.g. a `focus` system's api → `ctx.camera`).\n */\n api?: Api;\n}\n\n/**\n * A system plugin: sets up once against the kernel handles and returns its\n * running instance. Registered alongside element plugins in `plugins`; the\n * kernel tells them apart by shape (`setup` vs `build`).\n */\nexport interface SystemPlugin<Api = unknown> {\n name: string;\n setup(ctx: SystemContext): SystemInstance<Api>;\n}\n\n/** Narrow a mixed plugin to a system plugin (has `setup`, not `build`). */\nexport const isSystemPlugin = (plugin: unknown): plugin is SystemPlugin =>\n typeof plugin === \"object\" && plugin !== null && \"setup\" in plugin;\n","import type { Vector3 } from \"three\";\n\n/** Capability name under which a roaming camera system exposes pose control. */\nexport const ROAM_CONTROL = \"roamControl\";\n\n/**\n * Handle a camera plugin publishes so other camera systems can cooperate with\n * it — chiefly a framing/tour system that suspends free-roam while it animates\n * the camera, then reads/writes the pose to hand control back cleanly.\n */\nexport interface RoamControl {\n /** Enable/disable free-roam input (disabled while another system owns the camera). */\n setEnabled(enabled: boolean): void;\n /** Current pose: world position plus yaw (`phi`) and pitch (`theta`) in radians. */\n getPose(): { position: Vector3; phi: number; theta: number };\n /** Force the pose so free-roam resumes from it. */\n setPose(position: Vector3, phi: number, theta: number): void;\n /** Current eye height above the ground (world units). */\n getEyeHeight(): number;\n}\n","import type { Vec2 } from \"@scenography/scene\";\n\n/** Capability name under which the floor publishes its terrain. */\nexport const TERRAIN = \"terrain\";\n\n/**\n * The geometry a terrain consumer relies on — the fields needed to sample the\n * same ground a floor was displaced by (e.g. the water shader). A floor\n * definition satisfies this structurally; the kernel stays floor-agnostic.\n */\nexport interface TerrainDefinition {\n /** Extent on the ground plane, `[width (x), depth (z)]`. */\n size: Vec2;\n /** Center on the ground plane `[x, z]`. Defaults to `[0, 0]`. */\n center?: Vec2;\n /** Base vertical position (world Y). Defaults to `0`. */\n y?: number;\n /** Grayscale heightmap URL that displaces the ground. */\n heightmap?: string;\n /** Max displacement (world units) at white in the heightmap. */\n heightScale?: number;\n}\n\n/**\n * The walkable ground, published by the floor: its definition (for shaders that\n * sample the same heightmap), the live height function (heightmap-aware once the\n * image loads) and a promise that resolves once that sampling is in effect.\n */\nexport interface TerrainCapability {\n definition: TerrainDefinition;\n heightAt: (x: number, z: number) => number;\n /** Resolves once `heightAt` reflects the heightmap (immediately when there is none). */\n ready: Promise<void>;\n}\n","/** Capability name under which walkable-space obstacles are published. */\nexport const COLLIDERS = \"colliders\";\n\n/**\n * A collision obstacle as an oriented box on the XZ plane. The camera controls\n * consume these to keep the visitor out of walls (and any other plugin that\n * wants to block movement can provide more of them). Purely a movement contract\n * — no geometry, no three.js.\n */\nexport interface Collider {\n /** Box center `[x, z]`. */\n cx: number;\n cz: number;\n /** Unit direction along the box's length. */\n dx: number;\n dz: number;\n /** Half the box length. */\n halfLength: number;\n /** Half the box thickness. */\n halfThickness: number;\n}\n","import type { Vec2 } from \"@scenography/scene\";\n\n/** Capability name under which named wall edges are published for hanging. */\nexport const EDGES = \"edges\";\n\n/** A named anchor an artwork can hang on: a wall segment resolved to geometry. */\nexport interface EdgeAnchor {\n a: Vec2;\n b: Vec2;\n height: number;\n thickness: number;\n}\n\n/** The `name -> anchor` index a provider (e.g. the wall plugin) publishes. */\nexport type EdgeIndex = ReadonlyMap<string, EdgeAnchor>;\n","/**\n * Capability name under which the visitor's eye height (world units) is\n * published — the default vertical placement for wall-hung artworks. The kernel\n * provides it from the camera start; a camera plugin will own it later.\n */\nexport const EYE_HEIGHT = \"eyeHeight\";\n","import {\n PerspectiveCamera,\n type Object3D,\n type Scene as ThreeScene,\n type Texture,\n type WebGLRenderer,\n} from \"three\";\nimport type { Scene } from \"@scenography/scene\";\n\nimport { releaseTexture } from \"./builders\";\nimport {\n CapabilityRegistry,\n EYE_HEIGHT,\n type BuiltElement,\n type EngineContext,\n type EnginePlugin,\n} from \"./plugin\";\n\n/** A mounted plugin element: what the plugin built plus its content hash. */\ntype MountedElement = BuiltElement & { hash: string };\n\nconst TEXTURE_SLOTS = [\"map\", \"normalMap\", \"roughnessMap\", \"aoMap\", \"metalnessMap\"];\n\nconst disposeObject = (object: Object3D): void => {\n const materials = new Set<unknown>();\n object.traverse((child) => {\n // Release any HTML video element backing a VideoTexture.\n const video = child.userData?.video as HTMLVideoElement | undefined;\n if (video) {\n video.pause();\n video.removeAttribute(\"src\");\n video.load();\n }\n const mesh = child as { geometry?: { dispose?: () => void }; material?: unknown };\n mesh.geometry?.dispose?.();\n const material = mesh.material;\n if (Array.isArray(material)) for (const entry of material) materials.add(entry);\n else if (material) materials.add(material);\n });\n // A material can be shared by several meshes; dispose it, and the textures it\n // samples, once. Textures come from a shared cache, so they are released\n // rather than disposed outright.\n for (const entry of materials) {\n const material = entry as Record<string, unknown> & { dispose?: () => void };\n for (const slot of TEXTURE_SLOTS) {\n const texture = material[slot] as Texture | undefined;\n if (texture) releaseTexture(texture);\n }\n material.dispose?.();\n }\n};\n\n/**\n * Owns the live three.js scene graph and keeps it in sync with a `Scene`\n * definition. `reconcile` diffs the incoming definition against what is already\n * mounted — by `id` — and only adds/removes/rebuilds what changed. This is what\n * makes hot scene edits cheap: add a wing or drop one while moving and only the\n * touched objects are rebuilt.\n */\nexport class SceneManager {\n private readonly root: ThreeScene;\n private readonly plugins: EnginePlugin[];\n private readonly capabilities: CapabilityRegistry;\n private readonly view: { camera: PerspectiveCamera; renderer: WebGLRenderer };\n /** Mounted plugin elements, per kind, keyed by element id (\"\" for singletons). */\n private readonly pluginElements = new Map<string, Map<string, MountedElement>>();\n\n constructor(\n root: ThreeScene,\n plugins: EnginePlugin[] = [],\n capabilities: CapabilityRegistry = new CapabilityRegistry(),\n view: { camera: PerspectiveCamera; renderer: WebGLRenderer } = {\n camera: new PerspectiveCamera(),\n renderer: {} as WebGLRenderer,\n },\n ) {\n this.root = root;\n this.plugins = plugins;\n this.capabilities = capabilities;\n this.view = view;\n }\n\n /** The build context handed to every plugin. */\n private context(): EngineContext {\n return { capabilities: this.capabilities, ...this.view };\n }\n\n reconcile(scene: Scene): void {\n // Eye height (default hang height for wall art) — a camera concern the\n // kernel owns for now; a camera plugin will publish it later.\n const eye = scene.camera.start.position[1];\n this.capabilities.provide(EYE_HEIGHT, eye, String(eye));\n // Plugins go last so kernel-provided capabilities (terrain, eyeHeight) are fresh.\n this.reconcilePlugins(scene);\n }\n\n /**\n * Reconcile every registered plugin's elements by content: an element\n * rebuilds when its definition changes or when a capability it depends on\n * was re-provided (the capability versions are part of the hash).\n */\n private reconcilePlugins(scene: Scene): void {\n for (const plugin of this.plugins) {\n const { kind, cardinality } = plugin.data;\n let mounted = this.pluginElements.get(kind);\n if (!mounted) {\n mounted = new Map();\n this.pluginElements.set(kind, mounted);\n }\n\n const raw = (scene as unknown as Record<string, unknown>)[kind];\n const items: Array<[string, unknown]> =\n cardinality === \"many\"\n ? Array.isArray(raw)\n ? raw.map((item, i) => [String((item as { id?: string }).id ?? i), item])\n : []\n : raw !== undefined\n ? [[\"\", raw]]\n : [];\n const deps = (plugin.dependsOn ?? []).map((name) => this.capabilities.version(name));\n\n const seen = new Set<string>();\n for (const [key, value] of items) {\n seen.add(key);\n const hash = JSON.stringify({ value, deps });\n const existing = mounted.get(key);\n if (existing?.hash === hash) continue;\n if (existing) this.removePluginElement(mounted, key, existing);\n const built = plugin.build(value as never, this.context());\n if (built.object) this.root.add(built.object);\n mounted.set(key, { ...built, hash });\n }\n for (const [key, entry] of mounted)\n if (!seen.has(key)) this.removePluginElement(mounted, key, entry);\n\n // Publish this plugin's aggregate capabilities now that its elements are\n // in sync, so a later plugin that consumes them builds against fresh data.\n plugin.provide?.(\n items.map(([, value]) => value),\n this.context(),\n );\n }\n }\n\n private removePluginElement(\n mounted: Map<string, MountedElement>,\n key: string,\n entry: MountedElement,\n ): void {\n if (entry.object) {\n this.root.remove(entry.object);\n disposeObject(entry.object);\n }\n entry.dispose?.();\n mounted.delete(key);\n }\n\n\n /** Advance time-based plugin elements (e.g. water ripple). Call once per frame. */\n updateElements(deltaMs: number): void {\n for (const mounted of this.pluginElements.values())\n for (const entry of mounted.values()) entry.update?.(deltaMs);\n }\n\n /** Run every element's auxiliary pre-render pass (e.g. water reflection). */\n beforeRender(renderer: WebGLRenderer, scene: ThreeScene, camera: PerspectiveCamera): void {\n for (const mounted of this.pluginElements.values())\n for (const entry of mounted.values()) entry.beforeRender?.(renderer, scene, camera);\n }\n\n\n /** Look up the live three.js object for any plugin element `id`. */\n get(id: string): Object3D | undefined {\n for (const mounted of this.pluginElements.values()) {\n const object = mounted.get(id)?.object;\n if (object) return object;\n }\n return undefined;\n }\n\n /** Live objects of one plugin kind, keyed by `id` — for that plugin's controller. */\n elementsOf(kind: string): ReadonlyMap<string, Object3D> {\n const out = new Map<string, Object3D>();\n const mounted = this.pluginElements.get(kind);\n if (mounted) for (const [id, entry] of mounted) if (entry.object) out.set(id, entry.object);\n return out;\n }\n\n dispose(): void {\n for (const [kind, mounted] of this.pluginElements) {\n for (const [key, entry] of mounted) this.removePluginElement(mounted, key, entry);\n this.pluginElements.delete(kind);\n }\n }\n}\n","import {\n Clock,\n HalfFloatType,\n PCFShadowMap,\n PerspectiveCamera,\n Scene as ThreeScene,\n StereoCamera,\n Vector2,\n WebGLRenderer,\n WebGLRenderTarget,\n type Object3D,\n} from \"three\";\nimport type { EffectComposer } from \"three/examples/jsm/postprocessing/EffectComposer.js\";\nimport type { Pass } from \"three/examples/jsm/postprocessing/Pass.js\";\nimport type { Scene } from \"@scenography/scene\";\n\nimport { EnvironmentController } from \"./environment\";\nimport { configureKtx2 } from \"./builders\";\nimport type { CameraApi } from \"./camera\";\nimport type { LightsApi, MediaController, SkyApi, VideoApi } from \"./api\";\nimport { InteractionController } from \"./interaction\";\nimport {\n ControlSettings,\n InputBus,\n MODE_PRESETS,\n loadControlFlags,\n saveControlFlags,\n type ControlFlags,\n type ControlMode,\n} from \"./input\";\nimport {\n CapabilityRegistry,\n isSystemPlugin,\n type ElementController,\n type EnginePlugin,\n type SystemInstance,\n type SystemPlugin,\n} from \"./plugin\";\nimport { SceneManager } from \"./SceneManager\";\n\n/** Maps a plugin kind/name onto the `ctx` slot its api fills. */\nconst CTX_SLOT = {\n camera: \"focus\",\n lights: \"lights\",\n video: \"videos\",\n sky: \"sky\",\n media: \"audio\",\n} as const;\n/** The system the cardboard render toggle drives (gyroscope head-look). */\nconst CARDBOARD_SYSTEM = \"cardboard\";\n\n/** MSAA sample count for antialiasing. `0` disables it. */\nexport type Antialias = 0 | 2 | 4 | 8;\n\n/** Toggleable rendering features. All can be flipped live via `setRender`. */\nexport interface RenderOptions {\n /**\n * Antialiasing as MSAA sample count (`0` off, `2`/`4`/`8`). Applied to the\n * post-process render target so it survives SSAO/bloom and toggles live,\n * unlike the canvas's native MSAA. Defaults to `4`.\n */\n antialias?: Antialias;\n /** Screen-space ambient occlusion as a post-process. Defaults to `false`. */\n ssao?: boolean;\n /**\n * Bloom strength: a soft glow bleeding out of the brightest areas. `0` off;\n * gentle around `0.3`, strong around `1`. Defaults to `0`.\n */\n bloom?: number;\n /** Real-time shadow mapping (PCF). Defaults to `true`. */\n shadows?: boolean;\n /**\n * Cardboard / stereoscopic mode: split the view into a left/right eye pair for\n * a phone-in-a-headset viewer. Skips post-processing while active. Defaults to\n * `false`.\n */\n cardboard?: boolean;\n /**\n * Interocular distance for cardboard mode, in world units. The physical ≈6.4 cm\n * is too much parallax to fuse comfortably through cardboard lenses, so this\n * defaults to `3`; tune it live for the viewer. `0` flattens to mono.\n */\n eyeSeparation?: number;\n}\n\nexport interface GalleryOptions extends RenderOptions {\n /** Canvas the gallery renders into. */\n canvas: HTMLCanvasElement;\n /** Background color. Defaults to a soft gray. */\n background?: string;\n /**\n * Base path of the KTX2 transcoder files (`basis_transcoder.js` and\n * `basis_transcoder.wasm`). Without it, `.ktx2` textures are skipped.\n */\n ktx2Path?: string;\n /**\n * Runtime plugins: element plugins materialize a scene key; system plugins\n * (e.g. a camera) drive shared runtime state. Mixed freely — the kernel tells\n * them apart by shape.\n */\n plugins?: (EnginePlugin | SystemPlugin)[];\n}\n\nconst DEFAULT_EYE_SEPARATION = 3;\n\n/**\n * Drive the free-roam camera from outside the engine — e.g. on-screen touch\n * controls in the overlay. Inputs are analog (`-1..1`) and held until changed.\n */\nexport interface ControlsApi {\n /** Walk forward (`+1`) / back (`-1`); optional strafe right (`+1`) / left (`-1`). */\n setMoveInput(forward: number, strafe?: number): void;\n /** Turn right (`+1`) / left (`-1`); optional look up (`+1`) / down (`-1`). */\n setLookInput(x: number, y?: number): void;\n}\n\n/**\n * The visitor's camera-control preferences (view mode + capability flags). The\n * engine applies the effects that touch rendering/input — gyroscope head-look and\n * the stereo split — as the flags change; the overlay drives it from the mode\n * picker and settings, and reads it to show the right joysticks. See\n * `ControlSettings`.\n */\nexport interface ViewControlsApi {\n /** Current capability flags (a copy). */\n get(): ControlFlags;\n /** The mode the flags represent (`custom` when hand-tuned in settings). */\n getMode(): ControlMode;\n /** Switch to a mode's preset (keeps the look-invert axes). */\n setMode(mode: Exclude<ControlMode, \"custom\">): void;\n /** Set one capability flag (may drop the mode into `custom`). */\n setFlag<K extends keyof ControlFlags>(key: K, value: ControlFlags[K]): void;\n /** Subscribe to changes; returns an unsubscribe. */\n subscribe(fn: (flags: ControlFlags) => void): () => void;\n}\n\n/** Control the HDRI sky / environment at runtime, for behavior code. */\nexport interface EnvironmentApi {\n /** Set the visible sky background brightness (`0` = black). */\n setBackgroundIntensity(value: number): void;\n}\n\n/**\n * High-level API handed to behavior code. Deliberately NOT raw three.js: this is\n * the supported surface. Reach into `three` only as a documented escape hatch.\n */\nexport interface GalleryContext {\n /** Resolve the live object for a wall/artwork/light `id`. */\n entity(id: string): Object3D | undefined;\n /** Control scene lights (dim, set intensity/color) by `id`. */\n readonly lights: LightsApi;\n /** Control background audio (play, pause, fade) by `id`. */\n readonly media: MediaController;\n /** Drive the camera externally (on-screen touch controls, etc.). */\n readonly controls: ControlsApi;\n /** The visitor's camera-control preferences (view mode + capability flags). */\n readonly viewControls: ViewControlsApi;\n /** Control the HDRI sky / environment at runtime. */\n readonly environment: EnvironmentApi;\n /** Drive the procedural sky's time of day. */\n readonly sky: SkyApi;\n /** Animate the camera to frame artworks (e.g. when an info panel opens/closes). */\n readonly camera: CameraApi;\n /** Control video screens (play, pause, show/hide) by `id`. */\n readonly video: VideoApi;\n /** Escape hatch to the underlying three.js objects for advanced use. */\n readonly three: {\n scene: ThreeScene;\n camera: PerspectiveCamera;\n renderer: WebGLRenderer;\n };\n}\n\nexport interface Gallery {\n /** Apply a new scene definition, reconciling only what changed (hot-swap). */\n setScene(scene: Scene): void;\n /** Start the render loop. */\n start(): void;\n /** Stop the render loop. */\n stop(): void;\n /** Tear everything down and release GPU resources. */\n dispose(): void;\n /** Subscribe to a named event; returns an unsubscribe function. */\n on(event: string, handler: (payload?: unknown) => void): () => void;\n /** Emit a named event to subscribers. */\n emit(event: string, payload?: unknown): void;\n /** Run a handler every frame with the frame delta in ms; returns unsubscribe. */\n onFrame(handler: (deltaMs: number) => void): () => void;\n /** Resize the renderer/camera to the given pixel size. */\n resize(width: number, height: number): void;\n /** Toggle rendering features (antialias, ssao, bloom, shadows) live. */\n setRender(options: RenderOptions): void;\n readonly ctx: GalleryContext;\n}\n\n/**\n * Create and run a gallery from a scene definition. Sets up the three.js\n * renderer, camera, lights and free-roam controls, materializes the scene, and\n * starts the render loop. `setScene` later reconciles changes in place.\n */\nexport function createGallery(scene: Scene, options: GalleryOptions): Gallery {\n const { canvas } = options;\n const width = canvas.clientWidth || window.innerWidth;\n const height = canvas.clientHeight || window.innerHeight;\n\n // Native MSAA is off: antialiasing is an SMAA post-pass so it survives SSAO\n // and toggles live. PCF shadow filtering set up once; shadows enabled below.\n const renderer = new WebGLRenderer({ canvas, antialias: false });\n renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));\n renderer.setSize(width, height, false);\n renderer.shadowMap.type = PCFShadowMap;\n\n // Live-toggleable render features.\n const render: Required<RenderOptions> = {\n antialias: options.antialias ?? 4,\n ssao: options.ssao ?? false,\n bloom: options.bloom ?? 0,\n shadows: options.shadows ?? true,\n cardboard: options.cardboard ?? false,\n eyeSeparation: options.eyeSeparation ?? DEFAULT_EYE_SEPARATION,\n };\n\n // Stereoscopic (cardboard) rendering. StereoCamera is core three (not the\n // WebGL-only StereoEffect addon); we render each eye with a scissored viewport,\n // which is portable to WebGPU. aspect 0.5 keeps each half undistorted.\n const stereo = new StereoCamera();\n stereo.aspect = 0.5;\n stereo.eyeSep = render.eyeSeparation;\n const stereoSize = new Vector2();\n\n const threeScene = new ThreeScene();\n\n const camera = new PerspectiveCamera(scene.camera.fov ?? 55, width / height, 0.1, 5000);\n const { position } = scene.camera.start;\n camera.position.set(position[0], position[1], position[2]);\n\n // Split element plugins (materialize a scene key) from system plugins (drive\n // shared state like the camera). Elements go to the manager; systems are set\n // up below, once elements have published their capabilities.\n const allPlugins = options.plugins ?? [];\n const systemPlugins = allPlugins.filter(isSystemPlugin);\n const elementPlugins = allPlugins.filter((p): p is EnginePlugin => !isSystemPlugin(p));\n\n const capabilities = new CapabilityRegistry();\n if (options.ktx2Path) configureKtx2(renderer, options.ktx2Path);\n const manager = new SceneManager(threeScene, elementPlugins, capabilities, {\n camera,\n renderer,\n });\n manager.reconcile(scene);\n\n const input = new InputBus();\n // The visitor's control preferences (view mode + flags). Created early so system\n // plugins can read the flags (e.g. fpc reads the look-invert axes); the effects\n // that need the head-look system / render are wired below, once they exist. A\n // persisted choice (localStorage) wins over the initial cardboard option so the\n // visitor's last mode is restored across reloads.\n const controlSettings = new ControlSettings(\n loadControlFlags() ?? (options.cardboard ? MODE_PRESETS.cardboard : {}),\n );\n const environment = new EnvironmentController(threeScene);\n environment.reconcile(scene.environment);\n\n const listeners = new Map<string, Set<(payload?: unknown) => void>>();\n const emit = (event: string, payload?: unknown): void => {\n listeners.get(event)?.forEach((handler) => handler(payload));\n };\n const frameHandlers = new Set<(deltaMs: number) => void>();\n\n // Plugins publish their public apis here, keyed by kind/name; the kernel maps\n // them onto `ctx` slots (see `CTX_SLOT`). Element-plugin controllers (lights,\n // video) and system apis (camera, sky, media) both land in this one map.\n const apis = new Map<string, unknown>();\n\n // Element-plugin controllers: an aggregate api over each plugin's live objects\n // (e.g. `ctx.lights`). They advance in the render loop and tear down on dispose.\n const controllers: ElementController[] = [];\n for (const plugin of elementPlugins) {\n const controller = plugin.controller?.(() => manager.elementsOf(plugin.data.kind));\n if (!controller) continue;\n if (controller.api !== undefined) apis.set(plugin.data.kind, controller.api);\n controllers.push(controller);\n }\n\n // System plugins (camera, sky, audio…): drive shared runtime state each frame\n // and may expose an api. They get the scene graph + renderer for subsystems\n // that manage their own objects (sky) or listen on the camera (audio).\n const systems: SystemInstance[] = systemPlugins.map((plugin) => {\n const instance = plugin.setup({\n scene,\n camera,\n canvas,\n scene3d: threeScene,\n renderer,\n input,\n controlFlags: () => controlSettings.get(),\n capabilities,\n emit,\n resolve: (id) => manager.get(id),\n });\n if (instance.api !== undefined) apis.set(plugin.name, instance.api);\n return instance;\n });\n\n // `ctx.camera` is filled by the framing system (\"focus\"). Without it, a no-op\n // keeps callers (the overlay) safe.\n const cameraApi: CameraApi = (apis.get(CTX_SLOT.camera) as CameraApi | undefined) ?? {\n focusArtwork: () => {},\n centerArtwork: () => {},\n release: () => {},\n reset: () => {},\n isAnimating: () => false,\n isFocused: () => false,\n };\n\n // Cardboard render (stereo split) is a kernel feature; the gyroscope head-look\n // that rides with it is the `@scenography/plugin-cardboard` system. The render\n // toggle below drives its `setEnabled`.\n type HeadTracking = { setEnabled(enabled: boolean): void };\n const headTracking = apis.get(CARDBOARD_SYSTEM) as HeadTracking | undefined;\n // Apply the restored (or seeded) flags on load: enable gyro head-look and sync\n // the stereo render. (On iOS the gyro permission can't be granted outside a user\n // gesture, so a restored gyro mode only truly tracks once the visitor interacts —\n // enable() fails gracefully, nothing breaks.)\n const initialFlags = controlSettings.get();\n render.cardboard = initialFlags.splitScreen;\n if (initialFlags.gyro) headTracking?.setEnabled(true);\n\n // gyro and splitScreen are *orthogonal* here — unlike the legacy `cardboard`\n // render toggle that fused them — so a \"VR\" mode can aim with the gyro without\n // the stereo split. Apply those two effects (head-look system + stereo render)\n // as the flags change; the others are read by the overlay and input consumers.\n // render.cardboard / the initial setEnabled above already reflect the seed, so\n // the subscriber only handles subsequent changes.\n let prevFlags = controlSettings.get();\n controlSettings.subscribe((flags) => {\n if (flags.gyro !== prevFlags.gyro) headTracking?.setEnabled(flags.gyro);\n if (flags.splitScreen !== prevFlags.splitScreen) render.cardboard = flags.splitScreen;\n prevFlags = flags;\n saveControlFlags(flags); // remember the choice across reloads\n emit(\"controls:change\", { flags, mode: controlSettings.getMode() });\n });\n\n // No-op fallbacks so the overlay/behavior stay safe when a plugin is absent.\n const lightsApi = (apis.get(CTX_SLOT.lights) as LightsApi | undefined) ?? {\n set: () => {},\n dimAll: () => {},\n restore: () => {},\n };\n const videoApi = (apis.get(CTX_SLOT.video) as VideoApi | undefined) ?? {\n play: () => {},\n pause: () => {},\n setVisible: () => {},\n };\n const skyApi = (apis.get(CTX_SLOT.sky) as SkyApi | undefined) ?? {\n setTime: () => {},\n getTime: () => 12,\n setAzimuth: () => {},\n getAzimuth: () => 0,\n };\n const mediaApi = (apis.get(CTX_SLOT.media) as MediaController | undefined) ?? {\n play: () => {},\n pause: () => {},\n stop: () => {},\n setVolume: () => {},\n playing: () => false,\n setMuted: () => {},\n muted: () => false,\n };\n\n // Per-element interaction-distance gate, derived from each pickable element's\n // `infoRadius`. Read structurally: `infoRadius` is a convention a plugin (the\n // artwork plugin) can put on its elements, not a kernel field. A later\n // interaction pass can move this onto the built object's userData entirely.\n type Gated = { id: string; infoRadius?: number };\n const radiiOf = (s: Scene): Map<string, number> => {\n const gated = (s as { artworks?: Gated[] }).artworks ?? [];\n return new Map(\n gated.filter((a) => a.infoRadius !== undefined).map((a) => [a.id, a.infoRadius as number]),\n );\n };\n\n // Picking is suppressed while the camera animates and while a piece stays\n // focused: once its info panel opens, hovering must not keep highlighting it.\n const interaction = new InteractionController(\n camera,\n canvas,\n threeScene,\n emit,\n () => !cameraApi.isAnimating() && !cameraApi.isFocused(),\n );\n interaction.setRadii(radiiOf(scene));\n interaction.attach();\n const clock = new Clock();\n let rafId: number | null = null;\n\n // Post-processing (MSAA antialias + SSAO + bloom). The whole chain is imported\n // on demand so it only ships when a gallery uses it; until it resolves (or when\n // everything is off) we render straight to the canvas. Antialiasing is MSAA on\n // the composer's render target (`samples`), so changing the sample count\n // rebuilds the composer; SSAO/bloom just flip `.enabled`.\n // Bloom spread/threshold are fixed; its strength is the live `render.bloom`.\n const BLOOM = { radius: 0.4, threshold: 0.85 };\n type PostClasses = {\n EffectComposer: typeof import(\"three/examples/jsm/postprocessing/EffectComposer.js\").EffectComposer;\n RenderPass: typeof import(\"three/examples/jsm/postprocessing/RenderPass.js\").RenderPass;\n SSAOPass: typeof import(\"three/examples/jsm/postprocessing/SSAOPass.js\").SSAOPass;\n UnrealBloomPass: typeof import(\"three/examples/jsm/postprocessing/UnrealBloomPass.js\").UnrealBloomPass;\n OutputPass: typeof import(\"three/examples/jsm/postprocessing/OutputPass.js\").OutputPass;\n };\n let postClasses: PostClasses | null = null;\n let composer: EffectComposer | null = null;\n let ssaoPass: (Pass & { kernelRadius: number }) | null = null;\n let bloomPass: (Pass & { setSize(w: number, h: number): void; strength: number }) | null = null;\n let composerLoading = false;\n\n const pixelRatio = Math.min(window.devicePixelRatio, 2);\n const needsComposer = (): boolean => render.antialias > 0 || render.ssao || render.bloom > 0;\n\n // (Re)build the composer from the loaded classes at the current sample count.\n const buildComposer = (): void => {\n if (!postClasses) return;\n composer?.dispose();\n const { EffectComposer, RenderPass, SSAOPass, UnrealBloomPass, OutputPass } = postClasses;\n // Multisampled target → MSAA that survives the post chain. `samples` is\n // preserved across resizes (setSize only changes width/height). HalfFloat\n // matches the composer's own default and gives bloom its HDR range.\n const target = new WebGLRenderTarget(1, 1, { samples: render.antialias, type: HalfFloatType });\n const c = new EffectComposer(renderer, target);\n c.setPixelRatio(pixelRatio);\n c.setSize(width, height);\n c.addPass(new RenderPass(threeScene, camera));\n const ssao = new SSAOPass(threeScene, camera, width, height);\n ssao.kernelRadius = 16;\n ssao.enabled = render.ssao;\n c.addPass(ssao);\n const bloom = new UnrealBloomPass(new Vector2(width, height), render.bloom, BLOOM.radius, BLOOM.threshold);\n bloom.enabled = render.bloom > 0;\n c.addPass(bloom);\n // Restores tone mapping / sRGB that the composer would otherwise skip.\n c.addPass(new OutputPass());\n composer = c;\n ssaoPass = ssao;\n bloomPass = bloom;\n };\n\n const ensureComposer = (): void => {\n if (!needsComposer()) return;\n if (postClasses) {\n if (!composer) buildComposer();\n return;\n }\n if (composerLoading) return;\n composerLoading = true;\n void Promise.all([\n import(\"three/examples/jsm/postprocessing/EffectComposer.js\"),\n import(\"three/examples/jsm/postprocessing/RenderPass.js\"),\n import(\"three/examples/jsm/postprocessing/SSAOPass.js\"),\n import(\"three/examples/jsm/postprocessing/UnrealBloomPass.js\"),\n import(\"three/examples/jsm/postprocessing/OutputPass.js\"),\n ]).then(([{ EffectComposer }, { RenderPass }, { SSAOPass }, { UnrealBloomPass }, { OutputPass }]) => {\n postClasses = { EffectComposer, RenderPass, SSAOPass, UnrealBloomPass, OutputPass };\n composerLoading = false;\n buildComposer();\n });\n };\n\n // Recompile materials so a shadowMap.enabled change takes effect.\n const refreshShadows = (): void => {\n renderer.shadowMap.enabled = render.shadows;\n threeScene.traverse((obj) => {\n const mat = (obj as { material?: unknown }).material;\n if (!mat) return;\n for (const m of Array.isArray(mat) ? mat : [mat]) (m as { needsUpdate: boolean }).needsUpdate = true;\n });\n };\n refreshShadows();\n ensureComposer();\n\n // Split the canvas into a left/right eye pair. Mirrors what StereoEffect does\n // internally, but stays in our loop and uses only setViewport/setScissor.\n const renderStereo = (): void => {\n camera.updateWorldMatrix(true, false);\n stereo.update(camera);\n renderer.getSize(stereoSize);\n const w = stereoSize.x;\n const h = stereoSize.y;\n if (renderer.autoClear) renderer.clear();\n renderer.setScissorTest(true);\n renderer.setScissor(0, 0, w / 2, h);\n renderer.setViewport(0, 0, w / 2, h);\n renderer.render(threeScene, stereo.cameraL);\n renderer.setScissor(w / 2, 0, w / 2, h);\n renderer.setViewport(w / 2, 0, w / 2, h);\n renderer.render(threeScene, stereo.cameraR);\n renderer.setScissorTest(false);\n renderer.setViewport(0, 0, w, h);\n };\n\n const renderFrame = (): void => {\n rafId = requestAnimationFrame(renderFrame);\n const delta = clock.getDelta();\n const deltaMs = delta * 1000;\n // Systems drive the camera in registration order: the roaming camera first,\n // then the framing system, which may override it during a focus animation.\n for (const system of systems) system.update?.(deltaMs);\n interaction.update(deltaMs);\n manager.updateElements(deltaMs);\n for (const controller of controllers) controller.update?.(deltaMs);\n // Behavior runs after the camera moved this frame, before rendering. (The\n // cardboard system, last in `systems`, already had the final say on\n // orientation above.)\n for (const handler of frameHandlers) handler(deltaMs);\n // Auxiliary passes (e.g. the water's planar reflection) render into their\n // own targets from the current camera, before the main frame.\n manager.beforeRender(renderer, threeScene, camera);\n if (render.cardboard) renderStereo();\n else if (composer && needsComposer()) composer.render(delta);\n else renderer.render(threeScene, camera);\n };\n\n const ctx: GalleryContext = {\n entity: (id) => manager.get(id),\n lights: lightsApi,\n media: mediaApi,\n controls: {\n setMoveInput: (forward, strafe) => input.setMove(forward, strafe),\n setLookInput: (x, y) => input.setLook(x, y),\n },\n viewControls: {\n get: () => controlSettings.get(),\n getMode: () => controlSettings.getMode(),\n setMode: (mode) => controlSettings.setMode(mode),\n setFlag: (key, value) => controlSettings.setFlag(key, value),\n subscribe: (fn) => controlSettings.subscribe(fn),\n },\n environment: {\n setBackgroundIntensity: (v) => environment.setBackgroundIntensity(v),\n },\n sky: skyApi,\n camera: cameraApi,\n video: videoApi,\n three: { scene: threeScene, camera, renderer },\n };\n\n return {\n ctx,\n setScene(next) {\n manager.reconcile(next);\n interaction.setRadii(radiiOf(next));\n environment.reconcile(next.environment);\n for (const system of systems) system.setScene?.(next);\n const fov = next.camera.fov ?? 55;\n if (camera.fov !== fov) {\n camera.fov = fov;\n camera.updateProjectionMatrix();\n }\n },\n start() {\n if (rafId === null) {\n clock.start();\n renderFrame();\n }\n },\n stop() {\n if (rafId !== null) {\n cancelAnimationFrame(rafId);\n rafId = null;\n }\n },\n dispose() {\n this.stop();\n for (const system of systems) system.dispose?.();\n for (const controller of controllers) controller.dispose?.();\n interaction.detach();\n environment.dispose();\n manager.dispose();\n composer?.dispose();\n renderer.dispose();\n listeners.clear();\n frameHandlers.clear();\n },\n on(event, handler) {\n const set = listeners.get(event) ?? new Set();\n set.add(handler);\n listeners.set(event, set);\n return () => set.delete(handler);\n },\n emit(event, payload) {\n emit(event, payload);\n },\n onFrame(handler) {\n frameHandlers.add(handler);\n return () => frameHandlers.delete(handler);\n },\n resize(w, h) {\n camera.aspect = w / h;\n camera.updateProjectionMatrix();\n renderer.setSize(w, h, false);\n composer?.setSize(w, h);\n ssaoPass?.setSize(w, h);\n bloomPass?.setSize(w, h);\n },\n setRender(next) {\n if (next.shadows !== undefined && next.shadows !== render.shadows) {\n render.shadows = next.shadows;\n refreshShadows();\n }\n if (next.ssao !== undefined) render.ssao = next.ssao;\n if (next.bloom !== undefined) render.bloom = next.bloom;\n // The sample count lives on the render target, so a change rebuilds the\n // composer (cheap, and only on user toggle).\n const aaChanged = next.antialias !== undefined && next.antialias !== render.antialias;\n if (next.antialias !== undefined) render.antialias = next.antialias;\n if (next.cardboard !== undefined && next.cardboard !== render.cardboard) {\n // Legacy stereo toggle: route through the control state so mode/flags and\n // the render stay one source of truth (the subscriber applies gyro +\n // split; setEnabled thus still runs in this click gesture, which iOS needs\n // for the motion-permission prompt). Superseded by `ctx.viewControls` once\n // the mode picker replaces the cardboard button.\n controlSettings.setMode(next.cardboard ? \"cardboard\" : \"touch\");\n }\n if (next.eyeSeparation !== undefined) {\n render.eyeSeparation = next.eyeSeparation;\n stereo.eyeSep = next.eyeSeparation;\n }\n if (ssaoPass) ssaoPass.enabled = render.ssao;\n if (bloomPass) {\n bloomPass.strength = render.bloom;\n bloomPass.enabled = render.bloom > 0;\n }\n if (aaChanged && composer) buildComposer();\n ensureComposer(); // load/build the chain if an effect was just turned on\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":["FLAG_KEYS","ThreeScene"],"sources":["../src/loading.ts","../src/environment/EnvironmentController.ts","../src/builders/ktx2.ts","../src/builders/textureCache.ts","../src/builders/texture.ts","../src/interaction/InteractionController.ts","../src/input/InputBus.ts","../src/input/controlSettings.ts","../src/input/controlStorage.ts","../src/plugin/CapabilityRegistry.ts","../src/plugin/systemPlugin.ts","../src/plugin/roamControl.ts","../src/plugin/terrain.ts","../src/plugin/colliders.ts","../src/plugin/edges.ts","../src/plugin/eyeHeight.ts","../src/SceneManager.ts","../src/createGallery.ts"],"sourcesContent":["export interface LoadingState {\n loaded: number;\n total: number;\n done: boolean;\n}\n\ntype Listener = (state: LoadingState) => void;\n\nconst pending = new Set<string>();\nconst listeners = new Set<Listener>();\n\nlet state: LoadingState = { loaded: 0, total: 0, done: true };\n\nconst notify = (): void => {\n for (const listener of listeners) listener(state);\n};\n\n/** Begin tracking a new initial asset set; resets the previous one. */\nexport const startLoading = (urls: Iterable<string>): void => {\n pending.clear();\n for (const url of urls) pending.add(url);\n state = { loaded: 0, total: pending.size, done: pending.size === 0 };\n notify();\n};\n\n/** Mark one initial asset as loaded; unknown URLs (lazy assets) are ignored. */\nexport const trackAsset = (url: string): void => {\n if (!pending.delete(url)) return;\n state = {\n loaded: state.total - pending.size,\n total: state.total,\n done: pending.size === 0,\n };\n notify();\n};\n\n/** Current loading progress. */\nexport const loadingState = (): LoadingState => state;\n\n/** Subscribe to progress; the listener runs immediately with the current state. */\nexport const onLoading = (listener: Listener): (() => void) => {\n listeners.add(listener);\n listener(state);\n return () => {\n listeners.delete(listener);\n };\n};\n","import { EquirectangularReflectionMapping, type Scene as ThreeScene, type Texture } from \"three\";\nimport type { Environment } from \"@scenography/scene\";\n\nimport { trackAsset } from \"../loading\";\n\n/** Load an equirectangular HDRI, picking the loader by extension. */\nconst loadHDRI = (src: string, onLoad: (tex: Texture) => void): void => {\n if (src.toLowerCase().endsWith(\".exr\")) {\n void import(\"three/examples/jsm/loaders/EXRLoader.js\").then(({ EXRLoader }) => {\n new EXRLoader().load(src, onLoad);\n });\n } else {\n void import(\"three/examples/jsm/loaders/RGBELoader.js\").then(({ RGBELoader }) => {\n new RGBELoader().load(src, onLoad);\n });\n }\n};\n\n/**\n * Owns the scene's HDRI sky / image-based lighting and keeps it in sync with the\n * `Environment` definition. Loads the equirectangular map on demand (its loader\n * ships in its own chunk) and assigns it to `scene.background` and/or\n * `scene.environment`. Reconciles by a JSON hash so hot edits only reload when\n * the definition actually changes.\n */\nexport class EnvironmentController {\n private readonly scene: ThreeScene;\n private hash: string | null = null;\n private texture: Texture | null = null;\n\n constructor(scene: ThreeScene) {\n this.scene = scene;\n }\n\n reconcile(env: Environment | undefined): void {\n const hash = env ? JSON.stringify(env) : null;\n if (hash === this.hash) return;\n this.hash = hash;\n this.clear();\n if (!env) return;\n\n loadHDRI(env.src, (tex) => {\n trackAsset(env.src);\n // A newer reconcile superseded this load before it finished.\n if (this.hash !== hash) {\n tex.dispose();\n return;\n }\n tex.mapping = EquirectangularReflectionMapping;\n this.texture = tex;\n if (env.background ?? true) {\n this.scene.background = tex;\n this.scene.backgroundIntensity = env.intensity ?? 1;\n }\n if (env.lighting ?? true) this.scene.environment = tex;\n });\n }\n\n /** Set the visible sky background brightness (0 = black). Live, for behavior. */\n setBackgroundIntensity(value: number): void {\n this.scene.backgroundIntensity = value;\n }\n\n private clear(): void {\n this.scene.background = null;\n this.scene.environment = null;\n this.texture?.dispose();\n this.texture = null;\n }\n\n dispose(): void {\n this.clear();\n this.hash = null;\n }\n}\n","import type { CompressedTexture, WebGLRenderer } from \"three\";\n\n/** The part of three's KTX2Loader the engine and plugins use. */\nexport interface Ktx2Loader {\n load(\n url: string,\n onLoad: (texture: CompressedTexture) => void,\n onProgress?: (event: ProgressEvent) => void,\n onError?: (error: unknown) => void,\n ): void;\n}\n\nlet loader: Promise<Ktx2Loader> | null = null;\n\n/**\n * Configure the shared KTX2 loader. Called by the kernel once the renderer\n * exists; `.ktx2` textures and glTF models then decode through it.\n */\nexport const configureKtx2 = (renderer: WebGLRenderer, path: string): void => {\n loader = import(\"three/examples/jsm/loaders/KTX2Loader.js\").then(({ KTX2Loader }) => {\n const instance = new KTX2Loader();\n instance.setTranscoderPath(path);\n instance.detectSupport(renderer);\n return instance as unknown as Ktx2Loader;\n });\n};\n\n/** The configured loader, or null when the gallery did not set `ktx2Path`. */\nexport const getKtx2Loader = (): Promise<Ktx2Loader> | null => loader;\n","import { NoColorSpace, RepeatWrapping, SRGBColorSpace, type Texture } from \"three\";\nimport type { Vec2 } from \"@scenography/scene\";\n\n/** Creates the texture object for a source; its image may still be loading. */\nexport type TextureSource = (src: string) => Texture;\n\ntype Entry = {\n key: string;\n texture: Texture;\n refs: number;\n};\n\n/** Two surfaces can share a texture only at the same tiling and color space. */\nexport const textureKey = (src: string, repeat: Vec2, srgb: boolean): string =>\n `${src}|${repeat[0]}x${repeat[1]}|${srgb ? \"srgb\" : \"linear\"}`;\n\n/** Tiling and color space of a texture; the image arrives later from the loader. */\nexport const configureTexture = (texture: Texture, repeat: Vec2, srgb: boolean): void => {\n texture.wrapS = RepeatWrapping;\n texture.wrapT = RepeatWrapping;\n texture.repeat.set(repeat[0], repeat[1]);\n texture.colorSpace = srgb ? SRGBColorSpace : NoColorSpace;\n};\n\n/**\n * Shares textures between materials: the same source at the same tiling and\n * color space becomes one texture, loaded once and uploaded to the GPU once.\n * Reference counted, so releasing a material keeps the texture alive while\n * another one still samples it.\n */\nexport class TextureCache {\n private readonly entries = new Map<string, Entry>();\n private readonly owners = new Map<Texture, Entry>();\n\n constructor(private readonly source: TextureSource) {}\n\n /** The shared texture for a source, created on first use. */\n acquire(src: string, repeat: Vec2, srgb: boolean): Texture {\n const key = textureKey(src, repeat, srgb);\n const existing = this.entries.get(key);\n if (existing) {\n existing.refs += 1;\n return existing.texture;\n }\n const texture = this.source(src);\n configureTexture(texture, repeat, srgb);\n const entry = { key, texture, refs: 1 };\n this.entries.set(key, entry);\n this.owners.set(texture, entry);\n return texture;\n }\n\n /** Drop one reference; the texture is disposed when the last one goes. */\n release(texture: Texture): void {\n const entry = this.owners.get(texture);\n if (!entry) {\n texture.dispose();\n return;\n }\n entry.refs -= 1;\n if (entry.refs > 0) return;\n this.entries.delete(entry.key);\n this.owners.delete(texture);\n texture.dispose();\n }\n\n /** Textures currently shared. */\n get size(): number {\n return this.entries.size;\n }\n}\n","import { MeshStandardMaterial, TextureLoader, Vector2, type Texture } from \"three\";\nimport type { SurfaceTexture, Vec2 } from \"@scenography/scene\";\n\nimport { getKtx2Loader } from \"./ktx2\";\nimport { trackAsset } from \"../loading\";\nimport { configureTexture, TextureCache, textureKey } from \"./textureCache\";\n\nconst textureLoader = new TextureLoader();\nconst cache = new TextureCache((src) => textureLoader.load(src, () => trackAsset(src)));\n\ntype MapSlot = \"map\" | \"normalMap\" | \"roughnessMap\" | \"aoMap\";\n\ntype Ktx2Entry = {\n key: string;\n refs: number;\n texture?: Texture;\n waiters: Array<(texture: Texture) => void>;\n};\n\nconst ktx2Entries = new Map<string, Ktx2Entry>();\nconst ktx2Owners = new Map<Texture, Ktx2Entry>();\n\n/**\n * KTX2 textures resolve asynchronously, so the material slot is filled through\n * a waiter list. One entry per source and tiling is shared by every material\n * that samples it, just like the image cache.\n */\nconst loadKtx2 = (\n material: MeshStandardMaterial,\n slot: MapSlot,\n src: string,\n repeat: Vec2,\n srgb: boolean,\n): void => {\n const loader = getKtx2Loader();\n if (!loader) {\n console.warn(`KTX2 texture \"${src}\" needs the gallery's ktx2Path option.`);\n return;\n }\n const key = textureKey(src, repeat, srgb);\n let entry = ktx2Entries.get(key);\n if (!entry) {\n entry = { key, refs: 0, waiters: [] };\n ktx2Entries.set(key, entry);\n const created = entry;\n void loader.then((ktx2) =>\n ktx2.load(\n src,\n (texture) => {\n configureTexture(texture, repeat, srgb);\n texture.needsUpdate = true;\n trackAsset(src);\n created.texture = texture;\n ktx2Owners.set(texture, created);\n for (const assign of created.waiters) assign(texture);\n created.waiters = [];\n },\n undefined,\n (error) => console.error(`Failed to load KTX2 texture \"${src}\".`, error),\n ),\n );\n }\n entry.refs += 1;\n if (entry.texture) {\n material[slot] = entry.texture;\n material.needsUpdate = true;\n } else {\n entry.waiters.push((texture) => {\n material[slot] = texture;\n material.needsUpdate = true;\n });\n }\n};\n\n/**\n * Load one map into `material[slot]`. Textures are shared: every material that\n * samples the same source at the same tiling reuses one texture object. `.exr`\n * maps go through EXRLoader, imported on demand, and are not shared because\n * they resolve asynchronously; `.ktx2` maps share through their own cache.\n */\nconst loadMap = (\n material: MeshStandardMaterial,\n slot: MapSlot,\n src: string,\n repeat: Vec2,\n srgb: boolean,\n): void => {\n if (src.toLowerCase().endsWith(\".ktx2\")) {\n loadKtx2(material, slot, src, repeat, srgb);\n return;\n }\n if (src.toLowerCase().endsWith(\".exr\")) {\n void import(\"three/examples/jsm/loaders/EXRLoader.js\").then(({ EXRLoader }) => {\n new EXRLoader().load(src, (texture) => {\n configureTexture(texture, repeat, srgb);\n texture.needsUpdate = true;\n trackAsset(src);\n material[slot] = texture;\n material.needsUpdate = true;\n });\n });\n return;\n }\n material[slot] = cache.acquire(src, repeat, srgb);\n};\n\n/**\n * Apply a `SurfaceTexture` to a standard material. Returns whether a second UV\n * set (`uv1`) is needed — three samples `aoMap` from it, so the geometry must\n * provide one.\n */\nexport const applyTexture = (\n material: MeshStandardMaterial,\n texture: SurfaceTexture,\n): boolean => {\n const repeat = texture.repeat ?? [1, 1];\n loadMap(material, \"map\", texture.src, repeat, true);\n if (texture.normal) {\n loadMap(material, \"normalMap\", texture.normal, repeat, false);\n const scale = texture.normalScale ?? 1;\n material.normalScale = new Vector2(scale, scale);\n }\n if (texture.roughness) loadMap(material, \"roughnessMap\", texture.roughness, repeat, false);\n if (texture.ao) loadMap(material, \"aoMap\", texture.ao, repeat, false);\n material.needsUpdate = true;\n return texture.ao !== undefined;\n};\n\n/**\n * Release one reference to a texture assigned by `applyTexture`. Shared\n * textures stay alive while another material still samples them.\n */\nexport const releaseTexture = (texture: Texture): void => {\n const ktx2 = ktx2Owners.get(texture);\n if (!ktx2) {\n cache.release(texture);\n return;\n }\n ktx2.refs -= 1;\n if (ktx2.refs > 0) return;\n ktx2Owners.delete(texture);\n ktx2Entries.delete(ktx2.key);\n texture.dispose();\n};\n","import {\n BoxGeometry,\n Group,\n Mesh,\n MeshBasicMaterial,\n Quaternion,\n Raycaster,\n Vector2,\n Vector3,\n type Object3D,\n type PerspectiveCamera,\n type Scene as ThreeScene,\n} from \"three\";\n\nconst HIGHLIGHT_COLOR = \"#ef4444\";\nconst HIGHLIGHT_THICKNESS = 1.5; // world units (cm)\nconst HIGHLIGHT_MARGIN = 1.5; // gap between the piece and the frame\nconst CLICK_SLOP = 4; // px of movement still counted as a click, not a drag\nconst PICK_INTERVAL = 60; // ms between hover raycasts while the pointer is over the canvas\n\ninterface PickInfo {\n width: number;\n height: number;\n frontOffset: number;\n}\n\ninterface Pick {\n id: string;\n holder: Object3D;\n info: PickInfo;\n}\n\n/**\n * Pointer picking for artworks: hovering a piece's click target (its label, or\n * the piece itself when it has none) draws a pulsing red frame and shows a\n * pointer cursor; clicking it emits `artwork:select { id }`. Targets are marked\n * in the scene graph (`userData.pickTarget`/`pickInfo`) by the artwork builder,\n * so this just raycasts and walks up to find them. An optional per-id radius\n * gates interaction to when the camera is reasonably close.\n */\nexport class InteractionController {\n private readonly raycaster = new Raycaster();\n private readonly pointer = new Vector2();\n private over = false;\n private downPos: { x: number; y: number } | null = null;\n private radii = new Map<string, number>();\n private hovered: string | null = null;\n private time = 0;\n private pickElapsed = 0;\n private pointerMoved = false;\n\n private readonly highlight = new Group();\n private highlightMat: MeshBasicMaterial | null = null;\n private readonly handlers: Array<[EventTarget, string, EventListener]> = [];\n\n constructor(\n private readonly camera: PerspectiveCamera,\n private readonly canvas: HTMLCanvasElement,\n private readonly scene: ThreeScene,\n private readonly emit: (event: string, payload?: unknown) => void,\n private readonly canInteract: () => boolean,\n ) {\n this.highlight.visible = false;\n this.scene.add(this.highlight);\n }\n\n /** Per-artwork max interaction distance (XZ). Absent id = no distance gate. */\n setRadii(radii: Map<string, number>): void {\n this.radii = radii;\n }\n\n attach(): void {\n const bind = (type: string, listener: EventListener): void => {\n this.canvas.addEventListener(type, listener);\n this.handlers.push([this.canvas, type, listener]);\n };\n bind(\"pointermove\", (e) => this.onMove(e as PointerEvent));\n bind(\"pointerdown\", (e) => this.onDown(e as PointerEvent));\n bind(\"pointerup\", (e) => this.onUp(e as PointerEvent));\n bind(\"pointerleave\", () => {\n this.over = false;\n });\n }\n\n detach(): void {\n for (const [target, type, listener] of this.handlers) target.removeEventListener(type, listener);\n this.handlers.length = 0;\n this.scene.remove(this.highlight);\n this.clearHighlight();\n }\n\n private setPointer(e: PointerEvent): void {\n const rect = this.canvas.getBoundingClientRect();\n this.pointer.set(\n ((e.clientX - rect.left) / rect.width) * 2 - 1,\n -((e.clientY - rect.top) / rect.height) * 2 + 1,\n );\n this.over = true;\n this.pointerMoved = true;\n }\n\n private onMove(e: PointerEvent): void {\n this.setPointer(e);\n }\n\n private onDown(e: PointerEvent): void {\n this.downPos = { x: e.clientX, y: e.clientY };\n }\n\n private onUp(e: PointerEvent): void {\n const down = this.downPos;\n this.downPos = null;\n if (!down || !this.canInteract()) return;\n if (Math.hypot(e.clientX - down.x, e.clientY - down.y) > CLICK_SLOP) return; // a drag, not a click\n this.setPointer(e);\n const pick = this.raycast();\n if (pick) this.emit(\"artwork:select\", { id: pick.id });\n }\n\n /** Resolve the nearest pickable artwork under the pointer, honoring the gate. */\n private raycast(): Pick | null {\n this.raycaster.setFromCamera(this.pointer, this.camera);\n const hits = this.raycaster.intersectObjects(this.scene.children, true);\n for (const hit of hits) {\n let node: Object3D | null = hit.object;\n while (node && node.userData.pickTarget === undefined) node = node.parent;\n if (!node) continue;\n const id = node.userData.pickTarget as string;\n const radius = this.radii.get(id);\n if (radius !== undefined) {\n const p = node.getWorldPosition(new Vector3());\n if (Math.hypot(this.camera.position.x - p.x, this.camera.position.z - p.z) > radius) continue;\n }\n return { id, holder: node, info: node.userData.pickInfo as PickInfo };\n }\n return null;\n }\n\n /**\n * Recompute hover state and animate the highlight. Call once per frame.\n * Hover picking is throttled: a full-scene raycast runs at most once every\n * `PICK_INTERVAL` ms, or immediately after the pointer moves. The camera can\n * move without the pointer, so the interval keeps hovering correct while\n * walking.\n */\n update(deltaMs: number): void {\n this.time += deltaMs;\n\n if (!this.over || !this.canInteract()) {\n // No picking while the visitor cannot interact; pick at once when they can.\n this.pickElapsed = PICK_INTERVAL;\n this.pointerMoved = false;\n this.applyPick(null);\n } else {\n this.pickElapsed += deltaMs;\n if (this.pointerMoved) this.pickElapsed = PICK_INTERVAL;\n this.pointerMoved = false;\n if (this.pickElapsed >= PICK_INTERVAL) {\n this.pickElapsed = 0;\n this.applyPick(this.raycast());\n }\n }\n\n if (this.highlight.visible && this.highlightMat) {\n this.highlightMat.opacity = 0.45 + 0.45 * (0.5 + 0.5 * Math.sin(this.time / 320));\n }\n }\n\n /** Swap the hovered piece, touching the canvas and the highlight only on change. */\n private applyPick(pick: Pick | null): void {\n if ((pick?.id ?? null) === this.hovered) return;\n this.hovered = pick?.id ?? null;\n this.canvas.style.cursor = pick ? \"pointer\" : \"\";\n if (pick) this.buildHighlight(pick);\n else this.highlight.visible = false;\n }\n\n private clearHighlight(): void {\n for (const child of [...this.highlight.children]) {\n this.highlight.remove(child);\n (child as Mesh).geometry?.dispose();\n }\n this.highlightMat?.dispose();\n this.highlightMat = null;\n }\n\n /** Position a fresh pulsing red frame over the picked target's front face. */\n private buildHighlight(pick: Pick): void {\n this.clearHighlight();\n const mat = new MeshBasicMaterial({ color: HIGHLIGHT_COLOR, transparent: true, depthTest: false });\n this.highlightMat = mat;\n\n const w = pick.info.width + 2 * HIGHLIGHT_MARGIN;\n const h = pick.info.height + 2 * HIGHLIGHT_MARGIN;\n const t = HIGHLIGHT_THICKNESS;\n // Four bars forming a border (horizontals span the corners; verticals fill in).\n const bars: [number, number, number, number][] = [\n [w + 2 * t, t, 0, h / 2 + t / 2],\n [w + 2 * t, t, 0, -(h / 2 + t / 2)],\n [t, h, -(w / 2 + t / 2), 0],\n [t, h, w / 2 + t / 2, 0],\n ];\n for (const [bw, bh, x, y] of bars) {\n const bar = new Mesh(new BoxGeometry(bw, bh, 0.5), mat);\n bar.position.set(x, y, 0);\n bar.renderOrder = 999;\n this.highlight.add(bar);\n }\n\n const pos = pick.holder.getWorldPosition(new Vector3());\n const quat = pick.holder.getWorldQuaternion(new Quaternion());\n const normal = new Vector3(0, 0, 1).applyQuaternion(quat);\n pos.addScaledVector(normal, pick.info.frontOffset);\n this.highlight.position.copy(pos);\n this.highlight.quaternion.copy(quat);\n this.highlight.visible = true;\n }\n}\n","/**\n * The shared analog-input channel: the current movement and look intent, each\n * axis in `-1..1` and held until a producer changes it. Producers (on-screen\n * joysticks in the overlay, a gamepad…) write to it; whatever camera system is\n * active reads it and interprets it its own way — free-roam walks with it, a\n * fixed camera might only use the look axes to glance around. This is what makes\n * the joysticks reusable across camera types. Pure state — no three.js.\n */\nexport class InputBus {\n /** Forward (`+1`) / backward (`-1`). */\n moveForward = 0;\n /** Strafe right (`+1`) / left (`-1`). */\n moveStrafe = 0;\n /** Look right (`+1`) / left (`-1`). */\n lookX = 0;\n /** Look up (`+1`) / down (`-1`). */\n lookY = 0;\n\n /** Set the movement intent (forward, strafe). */\n setMove(forward: number, strafe = 0): void {\n this.moveForward = forward;\n this.moveStrafe = strafe;\n }\n\n /** Set the look intent (turn, pitch). */\n setLook(x: number, y = 0): void {\n this.lookX = x;\n this.lookY = y;\n }\n}\n","/**\n * The visitor's camera-control preferences: a set of orthogonal capability flags\n * plus a derived, friendly \"mode\". Pure state — no three.js, no DOM. The engine\n * wires the flags to effects (gyroscope head-look, stereo split); consumers read\n * them (the analog look-invert axes); the overlay drives it from a mode picker\n * (the `ScanEye` button) and the settings panel. Persistence and device\n * feature-detection live in those callers, not here, so this stays trivially\n * unit-testable.\n */\n\n/**\n * Orthogonal control capabilities. `gyro` / `splitScreen` / `joysticks` are what a\n * mode fixes; the two `invertLook*` axes are independent look-feel preferences\n * (some people feel the right stick inverted), kept separate from the mode.\n */\nexport interface ControlFlags {\n /** Orient the camera from the device gyroscope (head-look). */\n gyro: boolean;\n /** Split the view into a stereo left/right eye pair (phone-in-a-headset). */\n splitScreen: boolean;\n /** Show the on-screen virtual joysticks. */\n joysticks: boolean;\n /** Invert the horizontal (turn) axis of analog look. */\n invertLookX: boolean;\n /** Invert the vertical (pitch) axis of analog look. */\n invertLookY: boolean;\n}\n\n/** A named preset over the mode-defining flags; `custom` when none matches. */\nexport type ControlMode = \"touch\" | \"vr\" | \"cardboard\" | \"custom\";\n\n/** The three flags a mode fixes (the look-invert axes stay a separate preference). */\ntype ModeFlags = Pick<ControlFlags, \"gyro\" | \"splitScreen\" | \"joysticks\">;\n\n/**\n * Mode presets. Touch = two joysticks, no gyro. VR = the gyro aims and a single\n * move joystick walks you, no split. Cardboard = gyro aims, stereo split, gaze\n * select (no joysticks). The move-only single stick in VR is derived downstream\n * from `gyro && !splitScreen`, so it needs no flag of its own.\n */\nexport const MODE_PRESETS: Record<Exclude<ControlMode, \"custom\">, ModeFlags> = {\n touch: { gyro: false, splitScreen: false, joysticks: true },\n vr: { gyro: true, splitScreen: false, joysticks: true },\n cardboard: { gyro: true, splitScreen: true, joysticks: false },\n};\n\n/** Default flags: plain touch, no look inversion. */\nexport const DEFAULT_CONTROL_FLAGS: ControlFlags = {\n ...MODE_PRESETS.touch,\n invertLookX: false,\n invertLookY: false,\n};\n\nconst FLAG_KEYS: readonly (keyof ControlFlags)[] = [\n \"gyro\",\n \"splitScreen\",\n \"joysticks\",\n \"invertLookX\",\n \"invertLookY\",\n];\n\nconst equalFlags = (a: ControlFlags, b: ControlFlags): boolean =>\n FLAG_KEYS.every((key) => a[key] === b[key]);\n\n/** The mode a flag set represents, or `custom` when no preset matches. */\nexport function modeOf(flags: ControlFlags): ControlMode {\n for (const mode of [\"touch\", \"vr\", \"cardboard\"] as const) {\n const preset = MODE_PRESETS[mode];\n if (\n flags.gyro === preset.gyro &&\n flags.splitScreen === preset.splitScreen &&\n flags.joysticks === preset.joysticks\n )\n return mode;\n }\n return \"custom\";\n}\n\n/** Apply a mode's preset onto `flags`, leaving the look-invert axes untouched. */\nexport function applyMode(\n flags: ControlFlags,\n mode: Exclude<ControlMode, \"custom\">,\n): ControlFlags {\n return { ...flags, ...MODE_PRESETS[mode] };\n}\n\n/**\n * Live control-preferences state: holds the flags, derives the mode, and notifies\n * subscribers on every real change. Callers apply the effects — the engine wires\n * gyro and split, the overlay redraws — so this object is just the shared source\n * of truth they all read and write.\n */\nexport class ControlSettings {\n private flags: ControlFlags;\n private readonly subscribers = new Set<(flags: ControlFlags) => void>();\n\n constructor(initial: Partial<ControlFlags> = {}) {\n this.flags = { ...DEFAULT_CONTROL_FLAGS, ...initial };\n }\n\n /** The current flags (a copy — mutate through `setMode` / `setFlag`). */\n get(): ControlFlags {\n return { ...this.flags };\n }\n\n /** The mode the current flags represent (`custom` when hand-tuned). */\n getMode(): ControlMode {\n return modeOf(this.flags);\n }\n\n /** Switch to a mode's preset (keeps the look-invert axes). No-op if unchanged. */\n setMode(mode: Exclude<ControlMode, \"custom\">): void {\n this.replace(applyMode(this.flags, mode));\n }\n\n /** Set one flag. Flipping a mode-defining flag can drop you into `custom`. */\n setFlag<K extends keyof ControlFlags>(key: K, value: ControlFlags[K]): void {\n this.replace({ ...this.flags, [key]: value });\n }\n\n /** Subscribe to changes; returns an unsubscribe. Fires on every real change. */\n subscribe(fn: (flags: ControlFlags) => void): () => void {\n this.subscribers.add(fn);\n return () => this.subscribers.delete(fn);\n }\n\n private replace(next: ControlFlags): void {\n if (equalFlags(this.flags, next)) return;\n this.flags = next;\n const snapshot = this.get();\n for (const fn of this.subscribers) fn(snapshot);\n }\n}\n","import { DEFAULT_CONTROL_FLAGS, type ControlFlags } from \"./controlSettings\";\n\n/**\n * localStorage persistence for the control flags, kept out of the pure\n * `ControlSettings` so that stays DOM-free and unit-testable. Both functions are\n * best-effort and guarded: no `localStorage` (SSR, privacy mode) or malformed\n * data just falls back to defaults, never throws.\n */\nconst STORAGE_KEY = \"scenography:controls\";\n\nconst FLAG_KEYS = Object.keys(DEFAULT_CONTROL_FLAGS) as (keyof ControlFlags)[];\n\n/** Read persisted control flags; `null` when absent, unavailable or malformed. */\nexport function loadControlFlags(): Partial<ControlFlags> | null {\n try {\n const raw = globalThis.localStorage?.getItem(STORAGE_KEY);\n if (!raw) return null;\n const parsed = JSON.parse(raw) as Record<string, unknown>;\n const flags: Partial<ControlFlags> = {};\n // Copy only known boolean keys, so stale/garbage fields can't leak in.\n for (const key of FLAG_KEYS) {\n if (typeof parsed[key] === \"boolean\") flags[key] = parsed[key] as boolean;\n }\n return flags;\n } catch {\n return null;\n }\n}\n\n/** Persist control flags (best-effort; ignores a full/blocked store). */\nexport function saveControlFlags(flags: ControlFlags): void {\n try {\n globalThis.localStorage?.setItem(STORAGE_KEY, JSON.stringify(flags));\n } catch {\n /* storage unavailable or full — persistence is best-effort */\n }\n}\n","/**\n * Shared runtime contracts between plugins, keyed by name. A plugin (or the\n * kernel) *provides* a capability — e.g. the floor provides `terrain` — and\n * other plugins *consume* it if present, without depending on the provider's\n * package. Each name keeps a monotonic version counter so consumers can hash\n * their dependencies cheaply and rebuild when a provider changes.\n */\nexport class CapabilityRegistry {\n private readonly values = new Map<string, unknown>();\n private readonly versions = new Map<string, number>();\n /** Last change-token seen per name, to suppress redundant version bumps. */\n private readonly tokens = new Map<string, string>();\n\n /**\n * Publish (or replace) the capability under `name`. Pass a `token` (a cheap\n * hash of the inputs) to dedupe: re-providing with an unchanged token is a\n * no-op, so consumers that depend on this capability don't rebuild when the\n * provider recomputes an identical value.\n */\n provide<T>(name: string, value: T, token?: string): void {\n if (token !== undefined && this.values.has(name) && this.tokens.get(name) === token) return;\n this.values.set(name, value);\n if (token !== undefined) this.tokens.set(name, token);\n else this.tokens.delete(name);\n this.bump(name);\n }\n\n /** Remove the capability; consumers see `undefined` and a version bump. */\n revoke(name: string): void {\n if (this.values.delete(name)) {\n this.tokens.delete(name);\n this.bump(name);\n }\n }\n\n get<T>(name: string): T | undefined {\n return this.values.get(name) as T | undefined;\n }\n\n /** Monotonic change counter for `name` (`0` if never provided). */\n version(name: string): number {\n return this.versions.get(name) ?? 0;\n }\n\n private bump(name: string): void {\n this.versions.set(name, (this.versions.get(name) ?? 0) + 1);\n }\n}\n","import type {\n Object3D,\n PerspectiveCamera,\n Scene as ThreeScene,\n WebGLRenderer,\n} from \"three\";\nimport type { Scene } from \"@scenography/scene\";\n\nimport type { CapabilityRegistry } from \"./CapabilityRegistry\";\nimport type { ControlFlags, InputBus } from \"../input\";\n\n/**\n * What the kernel hands a system on setup. A *system* plugin — unlike an element\n * plugin — produces no scene object; it drives shared runtime state (the camera,\n * input…) each frame. The camera is a kernel primitive (the renderer needs one),\n * so systems receive it and drive it, publishing/consuming capabilities to\n * cooperate (e.g. the camera plugin publishes `roamControl`).\n */\nexport interface SystemContext {\n /** The current scene (its data keys, start pose, boundary…); also `setScene` on hot-swap. */\n scene: Scene;\n /** The render camera the system may drive. */\n camera: PerspectiveCamera;\n /** The canvas, for pointer/keyboard listeners. */\n canvas: HTMLElement;\n /** The three.js scene graph, for systems that manage their own objects (sky…). */\n scene3d: ThreeScene;\n /** The renderer, for systems that need it (e.g. sky IBL generation). */\n renderer: WebGLRenderer;\n /** Shared analog input (joysticks feed it); systems interpret it. */\n input: InputBus;\n /** The visitor's current control flags (e.g. look-invert axes), read live. */\n controlFlags: () => ControlFlags;\n /** Capability registry for cross-plugin contracts (terrain, colliders…). */\n capabilities: CapabilityRegistry;\n /** Emit a gallery event. */\n emit: (event: string, payload?: unknown) => void;\n /** Resolve a live scene object by id (e.g. for a framing system). */\n resolve: (id: string) => Object3D | undefined;\n}\n\n/** A running system: its per-frame update, hot-swap and teardown hooks. */\nexport interface SystemInstance<Api = unknown> {\n /** Advance the system by the frame delta (ms). */\n update?(deltaMs: number): void;\n /** React to a hot-swapped scene definition. */\n setScene?(scene: Scene): void;\n /** Tear down (detach listeners, revoke capabilities). */\n dispose?(): void;\n /**\n * Public API the system exposes to userland/overlay. The kernel maps known\n * system names onto `ctx` (e.g. a `focus` system's api → `ctx.camera`).\n */\n api?: Api;\n}\n\n/**\n * A system plugin: sets up once against the kernel handles and returns its\n * running instance. Registered alongside element plugins in `plugins`; the\n * kernel tells them apart by shape (`setup` vs `build`).\n */\nexport interface SystemPlugin<Api = unknown> {\n name: string;\n setup(ctx: SystemContext): SystemInstance<Api>;\n}\n\n/** Narrow a mixed plugin to a system plugin (has `setup`, not `build`). */\nexport const isSystemPlugin = (plugin: unknown): plugin is SystemPlugin =>\n typeof plugin === \"object\" && plugin !== null && \"setup\" in plugin;\n","import type { Vector3 } from \"three\";\n\n/** Capability name under which a roaming camera system exposes pose control. */\nexport const ROAM_CONTROL = \"roamControl\";\n\n/**\n * Handle a camera plugin publishes so other camera systems can cooperate with\n * it — chiefly a framing/tour system that suspends free-roam while it animates\n * the camera, then reads/writes the pose to hand control back cleanly.\n */\nexport interface RoamControl {\n /** Enable/disable free-roam input (disabled while another system owns the camera). */\n setEnabled(enabled: boolean): void;\n /** Current pose: world position plus yaw (`phi`) and pitch (`theta`) in radians. */\n getPose(): { position: Vector3; phi: number; theta: number };\n /** Force the pose so free-roam resumes from it. */\n setPose(position: Vector3, phi: number, theta: number): void;\n /** Current eye height above the ground (world units). */\n getEyeHeight(): number;\n}\n","import type { Vec2 } from \"@scenography/scene\";\n\n/** Capability name under which the floor publishes its terrain. */\nexport const TERRAIN = \"terrain\";\n\n/**\n * The geometry a terrain consumer relies on — the fields needed to sample the\n * same ground a floor was displaced by (e.g. the water shader). A floor\n * definition satisfies this structurally; the kernel stays floor-agnostic.\n */\nexport interface TerrainDefinition {\n /** Extent on the ground plane, `[width (x), depth (z)]`. */\n size: Vec2;\n /** Center on the ground plane `[x, z]`. Defaults to `[0, 0]`. */\n center?: Vec2;\n /** Base vertical position (world Y). Defaults to `0`. */\n y?: number;\n /** Grayscale heightmap URL that displaces the ground. */\n heightmap?: string;\n /** Max displacement (world units) at white in the heightmap. */\n heightScale?: number;\n}\n\n/**\n * The walkable ground, published by the floor: its definition (for shaders that\n * sample the same heightmap), the live height function (heightmap-aware once the\n * image loads) and a promise that resolves once that sampling is in effect.\n */\nexport interface TerrainCapability {\n definition: TerrainDefinition;\n heightAt: (x: number, z: number) => number;\n /** Resolves once `heightAt` reflects the heightmap (immediately when there is none). */\n ready: Promise<void>;\n}\n","/** Capability name under which walkable-space obstacles are published. */\nexport const COLLIDERS = \"colliders\";\n\n/**\n * A collision obstacle as an oriented box on the XZ plane. The camera controls\n * consume these to keep the visitor out of walls (and any other plugin that\n * wants to block movement can provide more of them). Purely a movement contract\n * — no geometry, no three.js.\n */\nexport interface Collider {\n /** Box center `[x, z]`. */\n cx: number;\n cz: number;\n /** Unit direction along the box's length. */\n dx: number;\n dz: number;\n /** Half the box length. */\n halfLength: number;\n /** Half the box thickness. */\n halfThickness: number;\n}\n","import type { Vec2 } from \"@scenography/scene\";\n\n/** Capability name under which named wall edges are published for hanging. */\nexport const EDGES = \"edges\";\n\n/** A named anchor an artwork can hang on: a wall segment resolved to geometry. */\nexport interface EdgeAnchor {\n a: Vec2;\n b: Vec2;\n height: number;\n thickness: number;\n}\n\n/** The `name -> anchor` index a provider (e.g. the wall plugin) publishes. */\nexport type EdgeIndex = ReadonlyMap<string, EdgeAnchor>;\n","/**\n * Capability name under which the visitor's eye height (world units) is\n * published — the default vertical placement for wall-hung artworks. The kernel\n * provides it from the camera start; a camera plugin will own it later.\n */\nexport const EYE_HEIGHT = \"eyeHeight\";\n","import {\n PerspectiveCamera,\n type Object3D,\n type Scene as ThreeScene,\n type Texture,\n type WebGLRenderer,\n} from \"three\";\nimport type { Scene } from \"@scenography/scene\";\n\nimport { releaseTexture } from \"./builders\";\nimport {\n CapabilityRegistry,\n EYE_HEIGHT,\n type BuiltElement,\n type EngineContext,\n type EnginePlugin,\n} from \"./plugin\";\n\n/** A mounted plugin element: what the plugin built plus its content hash. */\ntype MountedElement = BuiltElement & { hash: string };\n\nconst TEXTURE_SLOTS = [\"map\", \"normalMap\", \"roughnessMap\", \"aoMap\", \"metalnessMap\"];\n\nconst disposeObject = (object: Object3D): void => {\n const materials = new Set<unknown>();\n object.traverse((child) => {\n // Release any HTML video element backing a VideoTexture.\n const video = child.userData?.video as HTMLVideoElement | undefined;\n if (video) {\n video.pause();\n video.removeAttribute(\"src\");\n video.load();\n }\n const mesh = child as { geometry?: { dispose?: () => void }; material?: unknown };\n mesh.geometry?.dispose?.();\n const material = mesh.material;\n if (Array.isArray(material)) for (const entry of material) materials.add(entry);\n else if (material) materials.add(material);\n });\n // A material can be shared by several meshes; dispose it, and the textures it\n // samples, once. Textures come from a shared cache, so they are released\n // rather than disposed outright.\n for (const entry of materials) {\n const material = entry as Record<string, unknown> & { dispose?: () => void };\n for (const slot of TEXTURE_SLOTS) {\n const texture = material[slot] as Texture | undefined;\n if (texture) releaseTexture(texture);\n }\n material.dispose?.();\n }\n};\n\n/**\n * Owns the live three.js scene graph and keeps it in sync with a `Scene`\n * definition. `reconcile` diffs the incoming definition against what is already\n * mounted — by `id` — and only adds/removes/rebuilds what changed. This is what\n * makes hot scene edits cheap: add a wing or drop one while moving and only the\n * touched objects are rebuilt.\n */\nexport class SceneManager {\n private readonly root: ThreeScene;\n private readonly plugins: EnginePlugin[];\n private readonly capabilities: CapabilityRegistry;\n private readonly view: { camera: PerspectiveCamera; renderer: WebGLRenderer };\n /** Mounted plugin elements, per kind, keyed by element id (\"\" for singletons). */\n private readonly pluginElements = new Map<string, Map<string, MountedElement>>();\n\n constructor(\n root: ThreeScene,\n plugins: EnginePlugin[] = [],\n capabilities: CapabilityRegistry = new CapabilityRegistry(),\n view: { camera: PerspectiveCamera; renderer: WebGLRenderer } = {\n camera: new PerspectiveCamera(),\n renderer: {} as WebGLRenderer,\n },\n ) {\n this.root = root;\n this.plugins = plugins;\n this.capabilities = capabilities;\n this.view = view;\n }\n\n /** The build context handed to every plugin. */\n private context(): EngineContext {\n return { capabilities: this.capabilities, ...this.view };\n }\n\n reconcile(scene: Scene): void {\n // Eye height (default hang height for wall art) — a camera concern the\n // kernel owns for now; a camera plugin will publish it later.\n const eye = scene.camera.start.position[1];\n this.capabilities.provide(EYE_HEIGHT, eye, String(eye));\n // Plugins go last so kernel-provided capabilities (terrain, eyeHeight) are fresh.\n this.reconcilePlugins(scene);\n }\n\n /**\n * Reconcile every registered plugin's elements by content: an element\n * rebuilds when its definition changes or when a capability it depends on\n * was re-provided (the capability versions are part of the hash).\n */\n private reconcilePlugins(scene: Scene): void {\n for (const plugin of this.plugins) {\n const { kind, cardinality } = plugin.data;\n let mounted = this.pluginElements.get(kind);\n if (!mounted) {\n mounted = new Map();\n this.pluginElements.set(kind, mounted);\n }\n\n const raw = (scene as unknown as Record<string, unknown>)[kind];\n const items: Array<[string, unknown]> =\n cardinality === \"many\"\n ? Array.isArray(raw)\n ? raw.map((item, i) => [String((item as { id?: string }).id ?? i), item])\n : []\n : raw !== undefined\n ? [[\"\", raw]]\n : [];\n const deps = (plugin.dependsOn ?? []).map((name) => this.capabilities.version(name));\n\n const seen = new Set<string>();\n for (const [key, value] of items) {\n seen.add(key);\n const hash = JSON.stringify({ value, deps });\n const existing = mounted.get(key);\n if (existing?.hash === hash) continue;\n if (existing) this.removePluginElement(mounted, key, existing);\n const built = plugin.build(value as never, this.context());\n if (built.object) this.root.add(built.object);\n mounted.set(key, { ...built, hash });\n }\n for (const [key, entry] of mounted)\n if (!seen.has(key)) this.removePluginElement(mounted, key, entry);\n\n // Publish this plugin's aggregate capabilities now that its elements are\n // in sync, so a later plugin that consumes them builds against fresh data.\n plugin.provide?.(\n items.map(([, value]) => value),\n this.context(),\n );\n }\n }\n\n private removePluginElement(\n mounted: Map<string, MountedElement>,\n key: string,\n entry: MountedElement,\n ): void {\n if (entry.object) {\n this.root.remove(entry.object);\n disposeObject(entry.object);\n }\n entry.dispose?.();\n mounted.delete(key);\n }\n\n\n /** Advance time-based plugin elements (e.g. water ripple). Call once per frame. */\n updateElements(deltaMs: number): void {\n for (const mounted of this.pluginElements.values())\n for (const entry of mounted.values()) entry.update?.(deltaMs);\n }\n\n /** Run every element's auxiliary pre-render pass (e.g. water reflection). */\n beforeRender(renderer: WebGLRenderer, scene: ThreeScene, camera: PerspectiveCamera): void {\n for (const mounted of this.pluginElements.values())\n for (const entry of mounted.values()) entry.beforeRender?.(renderer, scene, camera);\n }\n\n\n /** Look up the live three.js object for any plugin element `id`. */\n get(id: string): Object3D | undefined {\n for (const mounted of this.pluginElements.values()) {\n const object = mounted.get(id)?.object;\n if (object) return object;\n }\n return undefined;\n }\n\n /** Live objects of one plugin kind, keyed by `id` — for that plugin's controller. */\n elementsOf(kind: string): ReadonlyMap<string, Object3D> {\n const out = new Map<string, Object3D>();\n const mounted = this.pluginElements.get(kind);\n if (mounted) for (const [id, entry] of mounted) if (entry.object) out.set(id, entry.object);\n return out;\n }\n\n dispose(): void {\n for (const [kind, mounted] of this.pluginElements) {\n for (const [key, entry] of mounted) this.removePluginElement(mounted, key, entry);\n this.pluginElements.delete(kind);\n }\n }\n}\n","import {\n Clock,\n HalfFloatType,\n PCFShadowMap,\n PerspectiveCamera,\n Scene as ThreeScene,\n StereoCamera,\n Vector2,\n WebGLRenderer,\n WebGLRenderTarget,\n type Object3D,\n} from \"three\";\nimport type { EffectComposer } from \"three/examples/jsm/postprocessing/EffectComposer.js\";\nimport type { Pass } from \"three/examples/jsm/postprocessing/Pass.js\";\nimport type { Scene } from \"@scenography/scene\";\n\nimport { EnvironmentController } from \"./environment\";\nimport { configureKtx2 } from \"./builders\";\nimport type { CameraApi } from \"./camera\";\nimport type { LightsApi, MediaController, SkyApi, VideoApi } from \"./api\";\nimport { InteractionController } from \"./interaction\";\nimport { loadingState, onLoading, startLoading, type LoadingState } from \"./loading\";\nimport {\n ControlSettings,\n InputBus,\n MODE_PRESETS,\n loadControlFlags,\n saveControlFlags,\n type ControlFlags,\n type ControlMode,\n} from \"./input\";\nimport {\n CapabilityRegistry,\n isSystemPlugin,\n type ElementController,\n type EnginePlugin,\n type SystemInstance,\n type SystemPlugin,\n} from \"./plugin\";\nimport { SceneManager } from \"./SceneManager\";\n\n/** Maps a plugin kind/name onto the `ctx` slot its api fills. */\nconst CTX_SLOT = {\n camera: \"focus\",\n lights: \"lights\",\n video: \"videos\",\n sky: \"sky\",\n media: \"audio\",\n} as const;\n/** The system the cardboard render toggle drives (gyroscope head-look). */\nconst CARDBOARD_SYSTEM = \"cardboard\";\n\n/** MSAA sample count for antialiasing. `0` disables it. */\nexport type Antialias = 0 | 2 | 4 | 8;\n\n/** Toggleable rendering features. All can be flipped live via `setRender`. */\nexport interface RenderOptions {\n /**\n * Antialiasing as MSAA sample count (`0` off, `2`/`4`/`8`). Applied to the\n * post-process render target so it survives SSAO/bloom and toggles live,\n * unlike the canvas's native MSAA. Defaults to `4`.\n */\n antialias?: Antialias;\n /** Screen-space ambient occlusion as a post-process. Defaults to `false`. */\n ssao?: boolean;\n /**\n * Bloom strength: a soft glow bleeding out of the brightest areas. `0` off;\n * gentle around `0.3`, strong around `1`. Defaults to `0`.\n */\n bloom?: number;\n /** Real-time shadow mapping (PCF). Defaults to `true`. */\n shadows?: boolean;\n /**\n * Cardboard / stereoscopic mode: split the view into a left/right eye pair for\n * a phone-in-a-headset viewer. Skips post-processing while active. Defaults to\n * `false`.\n */\n cardboard?: boolean;\n /**\n * Interocular distance for cardboard mode, in world units. The physical ≈6.4 cm\n * is too much parallax to fuse comfortably through cardboard lenses, so this\n * defaults to `3`; tune it live for the viewer. `0` flattens to mono.\n */\n eyeSeparation?: number;\n}\n\nexport interface GalleryOptions extends RenderOptions {\n /** Canvas the gallery renders into. */\n canvas: HTMLCanvasElement;\n /** Background color. Defaults to a soft gray. */\n background?: string;\n /**\n * Base path of the KTX2 transcoder files (`basis_transcoder.js` and\n * `basis_transcoder.wasm`). Without it, `.ktx2` textures are skipped.\n */\n ktx2Path?: string;\n /**\n * Runtime plugins: element plugins materialize a scene key; system plugins\n * (e.g. a camera) drive shared runtime state. Mixed freely — the kernel tells\n * them apart by shape.\n */\n plugins?: (EnginePlugin | SystemPlugin)[];\n}\n\nconst DEFAULT_EYE_SEPARATION = 3;\n\n/**\n * Drive the free-roam camera from outside the engine — e.g. on-screen touch\n * controls in the overlay. Inputs are analog (`-1..1`) and held until changed.\n */\nexport interface ControlsApi {\n /** Walk forward (`+1`) / back (`-1`); optional strafe right (`+1`) / left (`-1`). */\n setMoveInput(forward: number, strafe?: number): void;\n /** Turn right (`+1`) / left (`-1`); optional look up (`+1`) / down (`-1`). */\n setLookInput(x: number, y?: number): void;\n}\n\n/**\n * The visitor's camera-control preferences (view mode + capability flags). The\n * engine applies the effects that touch rendering/input — gyroscope head-look and\n * the stereo split — as the flags change; the overlay drives it from the mode\n * picker and settings, and reads it to show the right joysticks. See\n * `ControlSettings`.\n */\nexport interface ViewControlsApi {\n /** Current capability flags (a copy). */\n get(): ControlFlags;\n /** The mode the flags represent (`custom` when hand-tuned in settings). */\n getMode(): ControlMode;\n /** Switch to a mode's preset (keeps the look-invert axes). */\n setMode(mode: Exclude<ControlMode, \"custom\">): void;\n /** Set one capability flag (may drop the mode into `custom`). */\n setFlag<K extends keyof ControlFlags>(key: K, value: ControlFlags[K]): void;\n /** Subscribe to changes; returns an unsubscribe. */\n subscribe(fn: (flags: ControlFlags) => void): () => void;\n}\n\n/** Control the HDRI sky / environment at runtime, for behavior code. */\nexport interface EnvironmentApi {\n /** Set the visible sky background brightness (`0` = black). */\n setBackgroundIntensity(value: number): void;\n}\n\n/**\n * High-level API handed to behavior code. Deliberately NOT raw three.js: this is\n * the supported surface. Reach into `three` only as a documented escape hatch.\n */\nexport interface GalleryContext {\n /** Resolve the live object for a wall/artwork/light `id`. */\n entity(id: string): Object3D | undefined;\n /** Control scene lights (dim, set intensity/color) by `id`. */\n readonly lights: LightsApi;\n /** Control background audio (play, pause, fade) by `id`. */\n readonly media: MediaController;\n /** Drive the camera externally (on-screen touch controls, etc.). */\n readonly controls: ControlsApi;\n /** The visitor's camera-control preferences (view mode + capability flags). */\n readonly viewControls: ViewControlsApi;\n /** Control the HDRI sky / environment at runtime. */\n readonly environment: EnvironmentApi;\n /** Drive the procedural sky's time of day. */\n readonly sky: SkyApi;\n /** Animate the camera to frame artworks (e.g. when an info panel opens/closes). */\n readonly camera: CameraApi;\n /** Control video screens (play, pause, show/hide) by `id`. */\n readonly video: VideoApi;\n /** Escape hatch to the underlying three.js objects for advanced use. */\n readonly three: {\n scene: ThreeScene;\n camera: PerspectiveCamera;\n renderer: WebGLRenderer;\n };\n}\n\nexport interface Gallery {\n /** Apply a new scene definition, reconciling only what changed (hot-swap). */\n setScene(scene: Scene): void;\n /** Start the render loop. */\n start(): void;\n /** Stop the render loop. */\n stop(): void;\n /** Tear everything down and release GPU resources. */\n dispose(): void;\n /** Subscribe to a named event; returns an unsubscribe function. */\n on(event: string, handler: (payload?: unknown) => void): () => void;\n /** Emit a named event to subscribers. */\n emit(event: string, payload?: unknown): void;\n /** Run a handler every frame with the frame delta in ms; returns unsubscribe. */\n onFrame(handler: (deltaMs: number) => void): () => void;\n /** Resize the renderer/camera to the given pixel size. */\n resize(width: number, height: number): void;\n /** Toggle rendering features (antialias, ssao, bloom, shadows) live. */\n setRender(options: RenderOptions): void;\n /** Initial visual asset loading progress. */\n readonly loading: {\n get(): LoadingState;\n subscribe(listener: (state: LoadingState) => void): () => void;\n };\n readonly ctx: GalleryContext;\n}\n\n/**\n * Create and run a gallery from a scene definition. Sets up the three.js\n * renderer, camera, lights and free-roam controls, materializes the scene, and\n * starts the render loop. `setScene` later reconciles changes in place.\n */\nexport function createGallery(scene: Scene, options: GalleryOptions): Gallery {\n const { canvas } = options;\n const width = canvas.clientWidth || window.innerWidth;\n const height = canvas.clientHeight || window.innerHeight;\n\n // Native MSAA is off: antialiasing is an SMAA post-pass so it survives SSAO\n // and toggles live. PCF shadow filtering set up once; shadows enabled below.\n const renderer = new WebGLRenderer({ canvas, antialias: false });\n renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));\n renderer.setSize(width, height, false);\n renderer.shadowMap.type = PCFShadowMap;\n\n // Live-toggleable render features.\n const render: Required<RenderOptions> = {\n antialias: options.antialias ?? 4,\n ssao: options.ssao ?? false,\n bloom: options.bloom ?? 0,\n shadows: options.shadows ?? true,\n cardboard: options.cardboard ?? false,\n eyeSeparation: options.eyeSeparation ?? DEFAULT_EYE_SEPARATION,\n };\n\n // Stereoscopic (cardboard) rendering. StereoCamera is core three (not the\n // WebGL-only StereoEffect addon); we render each eye with a scissored viewport,\n // which is portable to WebGPU. aspect 0.5 keeps each half undistorted.\n const stereo = new StereoCamera();\n stereo.aspect = 0.5;\n stereo.eyeSep = render.eyeSeparation;\n const stereoSize = new Vector2();\n\n const threeScene = new ThreeScene();\n\n const camera = new PerspectiveCamera(scene.camera.fov ?? 55, width / height, 0.1, 5000);\n const { position } = scene.camera.start;\n camera.position.set(position[0], position[1], position[2]);\n\n // Split element plugins (materialize a scene key) from system plugins (drive\n // shared state like the camera). Elements go to the manager; systems are set\n // up below, once elements have published their capabilities.\n const allPlugins = options.plugins ?? [];\n const systemPlugins = allPlugins.filter(isSystemPlugin);\n const elementPlugins = allPlugins.filter((p): p is EnginePlugin => !isSystemPlugin(p));\n\n const capabilities = new CapabilityRegistry();\n if (options.ktx2Path) configureKtx2(renderer, options.ktx2Path);\n\n // Count the initial visual assets the scene will fetch, so the app can show\n // progress. Lazy detail assets (e.g. artwork nativeSrc) are not included.\n const assets = new Set<string>();\n for (const plugin of elementPlugins) {\n const { kind, cardinality } = plugin.data;\n const raw = (scene as unknown as Record<string, unknown>)[kind];\n const values =\n cardinality === \"many\"\n ? Array.isArray(raw)\n ? raw\n : []\n : raw !== undefined\n ? [raw]\n : [];\n for (const value of values) {\n for (const url of plugin.data.assets?.(value as never) ?? []) assets.add(url);\n }\n }\n if (scene.environment?.src) assets.add(scene.environment.src);\n startLoading(assets);\n\n const manager = new SceneManager(threeScene, elementPlugins, capabilities, {\n camera,\n renderer,\n });\n manager.reconcile(scene);\n\n const input = new InputBus();\n // The visitor's control preferences (view mode + flags). Created early so system\n // plugins can read the flags (e.g. fpc reads the look-invert axes); the effects\n // that need the head-look system / render are wired below, once they exist. A\n // persisted choice (localStorage) wins over the initial cardboard option so the\n // visitor's last mode is restored across reloads.\n const controlSettings = new ControlSettings(\n loadControlFlags() ?? (options.cardboard ? MODE_PRESETS.cardboard : {}),\n );\n const environment = new EnvironmentController(threeScene);\n environment.reconcile(scene.environment);\n\n const listeners = new Map<string, Set<(payload?: unknown) => void>>();\n const emit = (event: string, payload?: unknown): void => {\n listeners.get(event)?.forEach((handler) => handler(payload));\n };\n const frameHandlers = new Set<(deltaMs: number) => void>();\n\n // Plugins publish their public apis here, keyed by kind/name; the kernel maps\n // them onto `ctx` slots (see `CTX_SLOT`). Element-plugin controllers (lights,\n // video) and system apis (camera, sky, media) both land in this one map.\n const apis = new Map<string, unknown>();\n\n // Element-plugin controllers: an aggregate api over each plugin's live objects\n // (e.g. `ctx.lights`). They advance in the render loop and tear down on dispose.\n const controllers: ElementController[] = [];\n for (const plugin of elementPlugins) {\n const controller = plugin.controller?.(() => manager.elementsOf(plugin.data.kind));\n if (!controller) continue;\n if (controller.api !== undefined) apis.set(plugin.data.kind, controller.api);\n controllers.push(controller);\n }\n\n // System plugins (camera, sky, audio…): drive shared runtime state each frame\n // and may expose an api. They get the scene graph + renderer for subsystems\n // that manage their own objects (sky) or listen on the camera (audio).\n const systems: SystemInstance[] = systemPlugins.map((plugin) => {\n const instance = plugin.setup({\n scene,\n camera,\n canvas,\n scene3d: threeScene,\n renderer,\n input,\n controlFlags: () => controlSettings.get(),\n capabilities,\n emit,\n resolve: (id) => manager.get(id),\n });\n if (instance.api !== undefined) apis.set(plugin.name, instance.api);\n return instance;\n });\n\n // `ctx.camera` is filled by the framing system (\"focus\"). Without it, a no-op\n // keeps callers (the overlay) safe.\n const cameraApi: CameraApi = (apis.get(CTX_SLOT.camera) as CameraApi | undefined) ?? {\n focusArtwork: () => {},\n centerArtwork: () => {},\n release: () => {},\n reset: () => {},\n isAnimating: () => false,\n isFocused: () => false,\n };\n\n // Cardboard render (stereo split) is a kernel feature; the gyroscope head-look\n // that rides with it is the `@scenography/plugin-cardboard` system. The render\n // toggle below drives its `setEnabled`.\n type HeadTracking = { setEnabled(enabled: boolean): void };\n const headTracking = apis.get(CARDBOARD_SYSTEM) as HeadTracking | undefined;\n // Apply the restored (or seeded) flags on load: enable gyro head-look and sync\n // the stereo render. (On iOS the gyro permission can't be granted outside a user\n // gesture, so a restored gyro mode only truly tracks once the visitor interacts —\n // enable() fails gracefully, nothing breaks.)\n const initialFlags = controlSettings.get();\n render.cardboard = initialFlags.splitScreen;\n if (initialFlags.gyro) headTracking?.setEnabled(true);\n\n // gyro and splitScreen are *orthogonal* here — unlike the legacy `cardboard`\n // render toggle that fused them — so a \"VR\" mode can aim with the gyro without\n // the stereo split. Apply those two effects (head-look system + stereo render)\n // as the flags change; the others are read by the overlay and input consumers.\n // render.cardboard / the initial setEnabled above already reflect the seed, so\n // the subscriber only handles subsequent changes.\n let prevFlags = controlSettings.get();\n controlSettings.subscribe((flags) => {\n if (flags.gyro !== prevFlags.gyro) headTracking?.setEnabled(flags.gyro);\n if (flags.splitScreen !== prevFlags.splitScreen) render.cardboard = flags.splitScreen;\n prevFlags = flags;\n saveControlFlags(flags); // remember the choice across reloads\n emit(\"controls:change\", { flags, mode: controlSettings.getMode() });\n });\n\n // No-op fallbacks so the overlay/behavior stay safe when a plugin is absent.\n const lightsApi = (apis.get(CTX_SLOT.lights) as LightsApi | undefined) ?? {\n set: () => {},\n dimAll: () => {},\n restore: () => {},\n };\n const videoApi = (apis.get(CTX_SLOT.video) as VideoApi | undefined) ?? {\n play: () => {},\n pause: () => {},\n setVisible: () => {},\n };\n const skyApi = (apis.get(CTX_SLOT.sky) as SkyApi | undefined) ?? {\n setTime: () => {},\n getTime: () => 12,\n setAzimuth: () => {},\n getAzimuth: () => 0,\n };\n const mediaApi = (apis.get(CTX_SLOT.media) as MediaController | undefined) ?? {\n play: () => {},\n pause: () => {},\n stop: () => {},\n setVolume: () => {},\n playing: () => false,\n setMuted: () => {},\n muted: () => false,\n };\n\n // Per-element interaction-distance gate, derived from each pickable element's\n // `infoRadius`. Read structurally: `infoRadius` is a convention a plugin (the\n // artwork plugin) can put on its elements, not a kernel field. A later\n // interaction pass can move this onto the built object's userData entirely.\n type Gated = { id: string; infoRadius?: number };\n const radiiOf = (s: Scene): Map<string, number> => {\n const gated = (s as { artworks?: Gated[] }).artworks ?? [];\n return new Map(\n gated.filter((a) => a.infoRadius !== undefined).map((a) => [a.id, a.infoRadius as number]),\n );\n };\n\n // Picking is suppressed while the camera animates and while a piece stays\n // focused: once its info panel opens, hovering must not keep highlighting it.\n const interaction = new InteractionController(\n camera,\n canvas,\n threeScene,\n emit,\n () => !cameraApi.isAnimating() && !cameraApi.isFocused(),\n );\n interaction.setRadii(radiiOf(scene));\n interaction.attach();\n const clock = new Clock();\n let rafId: number | null = null;\n\n // Post-processing (MSAA antialias + SSAO + bloom). The whole chain is imported\n // on demand so it only ships when a gallery uses it; until it resolves (or when\n // everything is off) we render straight to the canvas. Antialiasing is MSAA on\n // the composer's render target (`samples`), so changing the sample count\n // rebuilds the composer; SSAO/bloom just flip `.enabled`.\n // Bloom spread/threshold are fixed; its strength is the live `render.bloom`.\n const BLOOM = { radius: 0.4, threshold: 0.85 };\n type PostClasses = {\n EffectComposer: typeof import(\"three/examples/jsm/postprocessing/EffectComposer.js\").EffectComposer;\n RenderPass: typeof import(\"three/examples/jsm/postprocessing/RenderPass.js\").RenderPass;\n SSAOPass: typeof import(\"three/examples/jsm/postprocessing/SSAOPass.js\").SSAOPass;\n UnrealBloomPass: typeof import(\"three/examples/jsm/postprocessing/UnrealBloomPass.js\").UnrealBloomPass;\n OutputPass: typeof import(\"three/examples/jsm/postprocessing/OutputPass.js\").OutputPass;\n };\n let postClasses: PostClasses | null = null;\n let composer: EffectComposer | null = null;\n let ssaoPass: (Pass & { kernelRadius: number }) | null = null;\n let bloomPass: (Pass & { setSize(w: number, h: number): void; strength: number }) | null = null;\n let composerLoading = false;\n\n const pixelRatio = Math.min(window.devicePixelRatio, 2);\n const needsComposer = (): boolean => render.antialias > 0 || render.ssao || render.bloom > 0;\n\n // (Re)build the composer from the loaded classes at the current sample count.\n const buildComposer = (): void => {\n if (!postClasses) return;\n composer?.dispose();\n const { EffectComposer, RenderPass, SSAOPass, UnrealBloomPass, OutputPass } = postClasses;\n // Multisampled target → MSAA that survives the post chain. `samples` is\n // preserved across resizes (setSize only changes width/height). HalfFloat\n // matches the composer's own default and gives bloom its HDR range.\n const target = new WebGLRenderTarget(1, 1, { samples: render.antialias, type: HalfFloatType });\n const c = new EffectComposer(renderer, target);\n c.setPixelRatio(pixelRatio);\n c.setSize(width, height);\n c.addPass(new RenderPass(threeScene, camera));\n const ssao = new SSAOPass(threeScene, camera, width, height);\n ssao.kernelRadius = 16;\n ssao.enabled = render.ssao;\n c.addPass(ssao);\n const bloom = new UnrealBloomPass(new Vector2(width, height), render.bloom, BLOOM.radius, BLOOM.threshold);\n bloom.enabled = render.bloom > 0;\n c.addPass(bloom);\n // Restores tone mapping / sRGB that the composer would otherwise skip.\n c.addPass(new OutputPass());\n composer = c;\n ssaoPass = ssao;\n bloomPass = bloom;\n };\n\n const ensureComposer = (): void => {\n if (!needsComposer()) return;\n if (postClasses) {\n if (!composer) buildComposer();\n return;\n }\n if (composerLoading) return;\n composerLoading = true;\n void Promise.all([\n import(\"three/examples/jsm/postprocessing/EffectComposer.js\"),\n import(\"three/examples/jsm/postprocessing/RenderPass.js\"),\n import(\"three/examples/jsm/postprocessing/SSAOPass.js\"),\n import(\"three/examples/jsm/postprocessing/UnrealBloomPass.js\"),\n import(\"three/examples/jsm/postprocessing/OutputPass.js\"),\n ]).then(([{ EffectComposer }, { RenderPass }, { SSAOPass }, { UnrealBloomPass }, { OutputPass }]) => {\n postClasses = { EffectComposer, RenderPass, SSAOPass, UnrealBloomPass, OutputPass };\n composerLoading = false;\n buildComposer();\n });\n };\n\n // Recompile materials so a shadowMap.enabled change takes effect.\n const refreshShadows = (): void => {\n renderer.shadowMap.enabled = render.shadows;\n threeScene.traverse((obj) => {\n const mat = (obj as { material?: unknown }).material;\n if (!mat) return;\n for (const m of Array.isArray(mat) ? mat : [mat]) (m as { needsUpdate: boolean }).needsUpdate = true;\n });\n };\n refreshShadows();\n ensureComposer();\n\n // Split the canvas into a left/right eye pair. Mirrors what StereoEffect does\n // internally, but stays in our loop and uses only setViewport/setScissor.\n const renderStereo = (): void => {\n camera.updateWorldMatrix(true, false);\n stereo.update(camera);\n renderer.getSize(stereoSize);\n const w = stereoSize.x;\n const h = stereoSize.y;\n if (renderer.autoClear) renderer.clear();\n renderer.setScissorTest(true);\n renderer.setScissor(0, 0, w / 2, h);\n renderer.setViewport(0, 0, w / 2, h);\n renderer.render(threeScene, stereo.cameraL);\n renderer.setScissor(w / 2, 0, w / 2, h);\n renderer.setViewport(w / 2, 0, w / 2, h);\n renderer.render(threeScene, stereo.cameraR);\n renderer.setScissorTest(false);\n renderer.setViewport(0, 0, w, h);\n };\n\n const renderFrame = (): void => {\n rafId = requestAnimationFrame(renderFrame);\n const delta = clock.getDelta();\n const deltaMs = delta * 1000;\n // Systems drive the camera in registration order: the roaming camera first,\n // then the framing system, which may override it during a focus animation.\n for (const system of systems) system.update?.(deltaMs);\n interaction.update(deltaMs);\n manager.updateElements(deltaMs);\n for (const controller of controllers) controller.update?.(deltaMs);\n // Behavior runs after the camera moved this frame, before rendering. (The\n // cardboard system, last in `systems`, already had the final say on\n // orientation above.)\n for (const handler of frameHandlers) handler(deltaMs);\n // Auxiliary passes (e.g. the water's planar reflection) render into their\n // own targets from the current camera, before the main frame.\n manager.beforeRender(renderer, threeScene, camera);\n if (render.cardboard) renderStereo();\n else if (composer && needsComposer()) composer.render(delta);\n else renderer.render(threeScene, camera);\n };\n\n const ctx: GalleryContext = {\n entity: (id) => manager.get(id),\n lights: lightsApi,\n media: mediaApi,\n controls: {\n setMoveInput: (forward, strafe) => input.setMove(forward, strafe),\n setLookInput: (x, y) => input.setLook(x, y),\n },\n viewControls: {\n get: () => controlSettings.get(),\n getMode: () => controlSettings.getMode(),\n setMode: (mode) => controlSettings.setMode(mode),\n setFlag: (key, value) => controlSettings.setFlag(key, value),\n subscribe: (fn) => controlSettings.subscribe(fn),\n },\n environment: {\n setBackgroundIntensity: (v) => environment.setBackgroundIntensity(v),\n },\n sky: skyApi,\n camera: cameraApi,\n video: videoApi,\n three: { scene: threeScene, camera, renderer },\n };\n\n return {\n ctx,\n loading: { get: loadingState, subscribe: onLoading },\n setScene(next) {\n manager.reconcile(next);\n interaction.setRadii(radiiOf(next));\n environment.reconcile(next.environment);\n for (const system of systems) system.setScene?.(next);\n const fov = next.camera.fov ?? 55;\n if (camera.fov !== fov) {\n camera.fov = fov;\n camera.updateProjectionMatrix();\n }\n },\n start() {\n if (rafId === null) {\n clock.start();\n renderFrame();\n }\n },\n stop() {\n if (rafId !== null) {\n cancelAnimationFrame(rafId);\n rafId = null;\n }\n },\n dispose() {\n this.stop();\n for (const system of systems) system.dispose?.();\n for (const controller of controllers) controller.dispose?.();\n interaction.detach();\n environment.dispose();\n manager.dispose();\n composer?.dispose();\n renderer.dispose();\n listeners.clear();\n frameHandlers.clear();\n },\n on(event, handler) {\n const set = listeners.get(event) ?? new Set();\n set.add(handler);\n listeners.set(event, set);\n return () => set.delete(handler);\n },\n emit(event, payload) {\n emit(event, payload);\n },\n onFrame(handler) {\n frameHandlers.add(handler);\n return () => frameHandlers.delete(handler);\n },\n resize(w, h) {\n camera.aspect = w / h;\n camera.updateProjectionMatrix();\n renderer.setSize(w, h, false);\n composer?.setSize(w, h);\n ssaoPass?.setSize(w, h);\n bloomPass?.setSize(w, h);\n },\n setRender(next) {\n if (next.shadows !== undefined && next.shadows !== render.shadows) {\n render.shadows = next.shadows;\n refreshShadows();\n }\n if (next.ssao !== undefined) render.ssao = next.ssao;\n if (next.bloom !== undefined) render.bloom = next.bloom;\n // The sample count lives on the render target, so a change rebuilds the\n // composer (cheap, and only on user toggle).\n const aaChanged = next.antialias !== undefined && next.antialias !== render.antialias;\n if (next.antialias !== undefined) render.antialias = next.antialias;\n if (next.cardboard !== undefined && next.cardboard !== render.cardboard) {\n // Legacy stereo toggle: route through the control state so mode/flags and\n // the render stay one source of truth (the subscriber applies gyro +\n // split; setEnabled thus still runs in this click gesture, which iOS needs\n // for the motion-permission prompt). Superseded by `ctx.viewControls` once\n // the mode picker replaces the cardboard button.\n controlSettings.setMode(next.cardboard ? \"cardboard\" : \"touch\");\n }\n if (next.eyeSeparation !== undefined) {\n render.eyeSeparation = next.eyeSeparation;\n stereo.eyeSep = next.eyeSeparation;\n }\n if (ssaoPass) ssaoPass.enabled = render.ssao;\n if (bloomPass) {\n bloomPass.strength = render.bloom;\n bloomPass.enabled = render.bloom > 0;\n }\n if (aaChanged && composer) buildComposer();\n ensureComposer(); // load/build the chain if an effect was just turned on\n },\n };\n}\n"],"mappings":";;AAQA,MAAM,0BAAU,IAAI,IAAY;AAChC,MAAM,4BAAY,IAAI,IAAc;AAEpC,IAAI,QAAsB;CAAE,QAAQ;CAAG,OAAO;CAAG,MAAM;AAAK;AAE5D,MAAM,eAAqB;CACzB,KAAK,MAAM,YAAY,WAAW,SAAS,KAAK;AAClD;;AAGA,MAAa,gBAAgB,SAAiC;CAC5D,QAAQ,MAAM;CACd,KAAK,MAAM,OAAO,MAAM,QAAQ,IAAI,GAAG;CACvC,QAAQ;EAAE,QAAQ;EAAG,OAAO,QAAQ;EAAM,MAAM,QAAQ,SAAS;CAAE;CACnE,OAAO;AACT;;AAGA,MAAa,cAAc,QAAsB;CAC/C,IAAI,CAAC,QAAQ,OAAO,GAAG,GAAG;CAC1B,QAAQ;EACN,QAAQ,MAAM,QAAQ,QAAQ;EAC9B,OAAO,MAAM;EACb,MAAM,QAAQ,SAAS;CACzB;CACA,OAAO;AACT;;AAGA,MAAa,qBAAmC;;AAGhD,MAAa,aAAa,aAAqC;CAC7D,UAAU,IAAI,QAAQ;CACtB,SAAS,KAAK;CACd,aAAa;EACX,UAAU,OAAO,QAAQ;CAC3B;AACF;;;;ACxCA,MAAM,YAAY,KAAa,WAAyC;CACtE,IAAI,IAAI,YAAY,CAAC,CAAC,SAAS,MAAM,GACnC,OAAY,0CAA0C,CAAC,MAAM,EAAE,gBAAgB;EAC7E,IAAI,UAAU,CAAC,CAAC,KAAK,KAAK,MAAM;CAClC,CAAC;MAED,OAAY,2CAA2C,CAAC,MAAM,EAAE,iBAAiB;EAC/E,IAAI,WAAW,CAAC,CAAC,KAAK,KAAK,MAAM;CACnC,CAAC;AAEL;;;;;;;;AASA,IAAa,wBAAb,MAAmC;CACjC;CACA,OAA8B;CAC9B,UAAkC;CAElC,YAAY,OAAmB;EAC7B,KAAK,QAAQ;CACf;CAEA,UAAU,KAAoC;EAC5C,MAAM,OAAO,MAAM,KAAK,UAAU,GAAG,IAAI;EACzC,IAAI,SAAS,KAAK,MAAM;EACxB,KAAK,OAAO;EACZ,KAAK,MAAM;EACX,IAAI,CAAC,KAAK;EAEV,SAAS,IAAI,MAAM,QAAQ;GACzB,WAAW,IAAI,GAAG;GAElB,IAAI,KAAK,SAAS,MAAM;IACtB,IAAI,QAAQ;IACZ;GACF;GACA,IAAI,UAAU;GACd,KAAK,UAAU;GACf,IAAI,IAAI,cAAc,MAAM;IAC1B,KAAK,MAAM,aAAa;IACxB,KAAK,MAAM,sBAAsB,IAAI,aAAa;GACpD;GACA,IAAI,IAAI,YAAY,MAAM,KAAK,MAAM,cAAc;EACrD,CAAC;CACH;;CAGA,uBAAuB,OAAqB;EAC1C,KAAK,MAAM,sBAAsB;CACnC;CAEA,QAAsB;EACpB,KAAK,MAAM,aAAa;EACxB,KAAK,MAAM,cAAc;EACzB,KAAK,SAAS,QAAQ;EACtB,KAAK,UAAU;CACjB;CAEA,UAAgB;EACd,KAAK,MAAM;EACX,KAAK,OAAO;CACd;AACF;;;AC9DA,IAAI,SAAqC;;;;;AAMzC,MAAa,iBAAiB,UAAyB,SAAuB;CAC5E,SAAS,OAAO,2CAA2C,CAAC,MAAM,EAAE,iBAAiB;EACnF,MAAM,WAAW,IAAI,WAAW;EAChC,SAAS,kBAAkB,IAAI;EAC/B,SAAS,cAAc,QAAQ;EAC/B,OAAO;CACT,CAAC;AACH;;AAGA,MAAa,sBAAkD;;;;ACf/D,MAAa,cAAc,KAAa,QAAc,SACpD,GAAG,IAAI,GAAG,OAAO,GAAG,GAAG,OAAO,GAAG,GAAG,OAAO,SAAS;;AAGtD,MAAa,oBAAoB,SAAkB,QAAc,SAAwB;CACvF,QAAQ,QAAQ;CAChB,QAAQ,QAAQ;CAChB,QAAQ,OAAO,IAAI,OAAO,IAAI,OAAO,EAAE;CACvC,QAAQ,aAAa,OAAO,iBAAiB;AAC/C;;;;;;;AAQA,IAAa,eAAb,MAA0B;CAIK;CAH7B,0BAA2B,IAAI,IAAmB;CAClD,yBAA0B,IAAI,IAAoB;CAElD,YAAY,QAAwC;EAAvB,KAAA,SAAA;CAAwB;;CAGrD,QAAQ,KAAa,QAAc,MAAwB;EACzD,MAAM,MAAM,WAAW,KAAK,QAAQ,IAAI;EACxC,MAAM,WAAW,KAAK,QAAQ,IAAI,GAAG;EACrC,IAAI,UAAU;GACZ,SAAS,QAAQ;GACjB,OAAO,SAAS;EAClB;EACA,MAAM,UAAU,KAAK,OAAO,GAAG;EAC/B,iBAAiB,SAAS,QAAQ,IAAI;EACtC,MAAM,QAAQ;GAAE;GAAK;GAAS,MAAM;EAAE;EACtC,KAAK,QAAQ,IAAI,KAAK,KAAK;EAC3B,KAAK,OAAO,IAAI,SAAS,KAAK;EAC9B,OAAO;CACT;;CAGA,QAAQ,SAAwB;EAC9B,MAAM,QAAQ,KAAK,OAAO,IAAI,OAAO;EACrC,IAAI,CAAC,OAAO;GACV,QAAQ,QAAQ;GAChB;EACF;EACA,MAAM,QAAQ;EACd,IAAI,MAAM,OAAO,GAAG;EACpB,KAAK,QAAQ,OAAO,MAAM,GAAG;EAC7B,KAAK,OAAO,OAAO,OAAO;EAC1B,QAAQ,QAAQ;CAClB;;CAGA,IAAI,OAAe;EACjB,OAAO,KAAK,QAAQ;CACtB;AACF;;;AC/DA,MAAM,gBAAgB,IAAI,cAAc;AACxC,MAAM,QAAQ,IAAI,cAAc,QAAQ,cAAc,KAAK,WAAW,WAAW,GAAG,CAAC,CAAC;AAWtF,MAAM,8BAAc,IAAI,IAAuB;AAC/C,MAAM,6BAAa,IAAI,IAAwB;;;;;;AAO/C,MAAM,YACJ,UACA,MACA,KACA,QACA,SACS;CACT,MAAM,SAAS,cAAc;CAC7B,IAAI,CAAC,QAAQ;EACX,QAAQ,KAAK,iBAAiB,IAAI,uCAAuC;EACzE;CACF;CACA,MAAM,MAAM,WAAW,KAAK,QAAQ,IAAI;CACxC,IAAI,QAAQ,YAAY,IAAI,GAAG;CAC/B,IAAI,CAAC,OAAO;EACV,QAAQ;GAAE;GAAK,MAAM;GAAG,SAAS,CAAC;EAAE;EACpC,YAAY,IAAI,KAAK,KAAK;EAC1B,MAAM,UAAU;EAChB,OAAY,MAAM,SAChB,KAAK,KACH,MACC,YAAY;GACX,iBAAiB,SAAS,QAAQ,IAAI;GACtC,QAAQ,cAAc;GACtB,WAAW,GAAG;GACd,QAAQ,UAAU;GAClB,WAAW,IAAI,SAAS,OAAO;GAC/B,KAAK,MAAM,UAAU,QAAQ,SAAS,OAAO,OAAO;GACpD,QAAQ,UAAU,CAAC;EACrB,GACA,KAAA,IACC,UAAU,QAAQ,MAAM,gCAAgC,IAAI,KAAK,KAAK,CACzE,CACF;CACF;CACA,MAAM,QAAQ;CACd,IAAI,MAAM,SAAS;EACjB,SAAS,QAAQ,MAAM;EACvB,SAAS,cAAc;CACzB,OACE,MAAM,QAAQ,MAAM,YAAY;EAC9B,SAAS,QAAQ;EACjB,SAAS,cAAc;CACzB,CAAC;AAEL;;;;;;;AAQA,MAAM,WACJ,UACA,MACA,KACA,QACA,SACS;CACT,IAAI,IAAI,YAAY,CAAC,CAAC,SAAS,OAAO,GAAG;EACvC,SAAS,UAAU,MAAM,KAAK,QAAQ,IAAI;EAC1C;CACF;CACA,IAAI,IAAI,YAAY,CAAC,CAAC,SAAS,MAAM,GAAG;EACtC,OAAY,0CAA0C,CAAC,MAAM,EAAE,gBAAgB;GAC7E,IAAI,UAAU,CAAC,CAAC,KAAK,MAAM,YAAY;IACrC,iBAAiB,SAAS,QAAQ,IAAI;IACtC,QAAQ,cAAc;IACtB,WAAW,GAAG;IACd,SAAS,QAAQ;IACjB,SAAS,cAAc;GACzB,CAAC;EACH,CAAC;EACD;CACF;CACA,SAAS,QAAQ,MAAM,QAAQ,KAAK,QAAQ,IAAI;AAClD;;;;;;AAOA,MAAa,gBACX,UACA,YACY;CACZ,MAAM,SAAS,QAAQ,UAAU,CAAC,GAAG,CAAC;CACtC,QAAQ,UAAU,OAAO,QAAQ,KAAK,QAAQ,IAAI;CAClD,IAAI,QAAQ,QAAQ;EAClB,QAAQ,UAAU,aAAa,QAAQ,QAAQ,QAAQ,KAAK;EAC5D,MAAM,QAAQ,QAAQ,eAAe;EACrC,SAAS,cAAc,IAAI,QAAQ,OAAO,KAAK;CACjD;CACA,IAAI,QAAQ,WAAW,QAAQ,UAAU,gBAAgB,QAAQ,WAAW,QAAQ,KAAK;CACzF,IAAI,QAAQ,IAAI,QAAQ,UAAU,SAAS,QAAQ,IAAI,QAAQ,KAAK;CACpE,SAAS,cAAc;CACvB,OAAO,QAAQ,OAAO,KAAA;AACxB;;;;;AAMA,MAAa,kBAAkB,YAA2B;CACxD,MAAM,OAAO,WAAW,IAAI,OAAO;CACnC,IAAI,CAAC,MAAM;EACT,MAAM,QAAQ,OAAO;EACrB;CACF;CACA,KAAK,QAAQ;CACb,IAAI,KAAK,OAAO,GAAG;CACnB,WAAW,OAAO,OAAO;CACzB,YAAY,OAAO,KAAK,GAAG;CAC3B,QAAQ,QAAQ;AAClB;;;ACjIA,MAAM,kBAAkB;AACxB,MAAM,sBAAsB;AAC5B,MAAM,mBAAmB;AACzB,MAAM,aAAa;AACnB,MAAM,gBAAgB;;;;;;;;;AAsBtB,IAAa,wBAAb,MAAmC;CAgBd;CACA;CACA;CACA;CACA;CAnBnB,YAA6B,IAAI,UAAU;CAC3C,UAA2B,IAAI,QAAQ;CACvC,OAAe;CACf,UAAmD;CACnD,wBAAgB,IAAI,IAAoB;CACxC,UAAiC;CACjC,OAAe;CACf,cAAsB;CACtB,eAAuB;CAEvB,YAA6B,IAAI,MAAM;CACvC,eAAiD;CACjD,WAAyE,CAAC;CAE1E,YACE,QACA,QACA,OACA,MACA,aACA;EALiB,KAAA,SAAA;EACA,KAAA,SAAA;EACA,KAAA,QAAA;EACA,KAAA,OAAA;EACA,KAAA,cAAA;EAEjB,KAAK,UAAU,UAAU;EACzB,KAAK,MAAM,IAAI,KAAK,SAAS;CAC/B;;CAGA,SAAS,OAAkC;EACzC,KAAK,QAAQ;CACf;CAEA,SAAe;EACb,MAAM,QAAQ,MAAc,aAAkC;GAC5D,KAAK,OAAO,iBAAiB,MAAM,QAAQ;GAC3C,KAAK,SAAS,KAAK;IAAC,KAAK;IAAQ;IAAM;GAAQ,CAAC;EAClD;EACA,KAAK,gBAAgB,MAAM,KAAK,OAAO,CAAiB,CAAC;EACzD,KAAK,gBAAgB,MAAM,KAAK,OAAO,CAAiB,CAAC;EACzD,KAAK,cAAc,MAAM,KAAK,KAAK,CAAiB,CAAC;EACrD,KAAK,sBAAsB;GACzB,KAAK,OAAO;EACd,CAAC;CACH;CAEA,SAAe;EACb,KAAK,MAAM,CAAC,QAAQ,MAAM,aAAa,KAAK,UAAU,OAAO,oBAAoB,MAAM,QAAQ;EAC/F,KAAK,SAAS,SAAS;EACvB,KAAK,MAAM,OAAO,KAAK,SAAS;EAChC,KAAK,eAAe;CACtB;CAEA,WAAmB,GAAuB;EACxC,MAAM,OAAO,KAAK,OAAO,sBAAsB;EAC/C,KAAK,QAAQ,KACT,EAAE,UAAU,KAAK,QAAQ,KAAK,QAAS,IAAI,GAC7C,GAAG,EAAE,UAAU,KAAK,OAAO,KAAK,UAAU,IAAI,CAChD;EACA,KAAK,OAAO;EACZ,KAAK,eAAe;CACtB;CAEA,OAAe,GAAuB;EACpC,KAAK,WAAW,CAAC;CACnB;CAEA,OAAe,GAAuB;EACpC,KAAK,UAAU;GAAE,GAAG,EAAE;GAAS,GAAG,EAAE;EAAQ;CAC9C;CAEA,KAAa,GAAuB;EAClC,MAAM,OAAO,KAAK;EAClB,KAAK,UAAU;EACf,IAAI,CAAC,QAAQ,CAAC,KAAK,YAAY,GAAG;EAClC,IAAI,KAAK,MAAM,EAAE,UAAU,KAAK,GAAG,EAAE,UAAU,KAAK,CAAC,IAAI,YAAY;EACrE,KAAK,WAAW,CAAC;EACjB,MAAM,OAAO,KAAK,QAAQ;EAC1B,IAAI,MAAM,KAAK,KAAK,kBAAkB,EAAE,IAAI,KAAK,GAAG,CAAC;CACvD;;CAGA,UAA+B;EAC7B,KAAK,UAAU,cAAc,KAAK,SAAS,KAAK,MAAM;EACtD,MAAM,OAAO,KAAK,UAAU,iBAAiB,KAAK,MAAM,UAAU,IAAI;EACtE,KAAK,MAAM,OAAO,MAAM;GACtB,IAAI,OAAwB,IAAI;GAChC,OAAO,QAAQ,KAAK,SAAS,eAAe,KAAA,GAAW,OAAO,KAAK;GACnE,IAAI,CAAC,MAAM;GACX,MAAM,KAAK,KAAK,SAAS;GACzB,MAAM,SAAS,KAAK,MAAM,IAAI,EAAE;GAChC,IAAI,WAAW,KAAA,GAAW;IACxB,MAAM,IAAI,KAAK,iBAAiB,IAAI,QAAQ,CAAC;IAC7C,IAAI,KAAK,MAAM,KAAK,OAAO,SAAS,IAAI,EAAE,GAAG,KAAK,OAAO,SAAS,IAAI,EAAE,CAAC,IAAI,QAAQ;GACvF;GACA,OAAO;IAAE;IAAI,QAAQ;IAAM,MAAM,KAAK,SAAS;GAAqB;EACtE;EACA,OAAO;CACT;;;;;;;;CASA,OAAO,SAAuB;EAC5B,KAAK,QAAQ;EAEb,IAAI,CAAC,KAAK,QAAQ,CAAC,KAAK,YAAY,GAAG;GAErC,KAAK,cAAc;GACnB,KAAK,eAAe;GACpB,KAAK,UAAU,IAAI;EACrB,OAAO;GACL,KAAK,eAAe;GACpB,IAAI,KAAK,cAAc,KAAK,cAAc;GAC1C,KAAK,eAAe;GACpB,IAAI,KAAK,eAAe,eAAe;IACrC,KAAK,cAAc;IACnB,KAAK,UAAU,KAAK,QAAQ,CAAC;GAC/B;EACF;EAEA,IAAI,KAAK,UAAU,WAAW,KAAK,cACjC,KAAK,aAAa,UAAU,MAAO,OAAQ,KAAM,KAAM,KAAK,IAAI,KAAK,OAAO,GAAG;CAEnF;;CAGA,UAAkB,MAAyB;EACzC,KAAK,MAAM,MAAM,UAAU,KAAK,SAAS;EACzC,KAAK,UAAU,MAAM,MAAM;EAC3B,KAAK,OAAO,MAAM,SAAS,OAAO,YAAY;EAC9C,IAAI,MAAM,KAAK,eAAe,IAAI;OAC7B,KAAK,UAAU,UAAU;CAChC;CAEA,iBAA+B;EAC7B,KAAK,MAAM,SAAS,CAAC,GAAG,KAAK,UAAU,QAAQ,GAAG;GAChD,KAAK,UAAU,OAAO,KAAK;GAC3B,MAAgB,UAAU,QAAQ;EACpC;EACA,KAAK,cAAc,QAAQ;EAC3B,KAAK,eAAe;CACtB;;CAGA,eAAuB,MAAkB;EACvC,KAAK,eAAe;EACpB,MAAM,MAAM,IAAI,kBAAkB;GAAE,OAAO;GAAiB,aAAa;GAAM,WAAW;EAAM,CAAC;EACjG,KAAK,eAAe;EAEpB,MAAM,IAAI,KAAK,KAAK,QAAQ,IAAI;EAChC,MAAM,IAAI,KAAK,KAAK,SAAS,IAAI;EACjC,MAAM,IAAI;EAEV,MAAM,OAA2C;GAC/C;IAAC,IAAI,IAAI;IAAG;IAAG;IAAG,IAAI,IAAI,IAAI;GAAC;GAC/B;IAAC,IAAI,IAAI;IAAG;IAAG;IAAG,EAAE,IAAI,IAAI,IAAI;GAAE;GAClC;IAAC;IAAG;IAAG,EAAE,IAAI,IAAI,IAAI;IAAI;GAAC;GAC1B;IAAC;IAAG;IAAG,IAAI,IAAI,IAAI;IAAG;GAAC;EACzB;EACA,KAAK,MAAM,CAAC,IAAI,IAAI,GAAG,MAAM,MAAM;GACjC,MAAM,MAAM,IAAI,KAAK,IAAI,YAAY,IAAI,IAAI,EAAG,GAAG,GAAG;GACtD,IAAI,SAAS,IAAI,GAAG,GAAG,CAAC;GACxB,IAAI,cAAc;GAClB,KAAK,UAAU,IAAI,GAAG;EACxB;EAEA,MAAM,MAAM,KAAK,OAAO,iBAAiB,IAAI,QAAQ,CAAC;EACtD,MAAM,OAAO,KAAK,OAAO,mBAAmB,IAAI,WAAW,CAAC;EAC5D,MAAM,SAAS,IAAI,QAAQ,GAAG,GAAG,CAAC,CAAC,CAAC,gBAAgB,IAAI;EACxD,IAAI,gBAAgB,QAAQ,KAAK,KAAK,WAAW;EACjD,KAAK,UAAU,SAAS,KAAK,GAAG;EAChC,KAAK,UAAU,WAAW,KAAK,IAAI;EACnC,KAAK,UAAU,UAAU;CAC3B;AACF;;;;;;;;;;;ACjNA,IAAa,WAAb,MAAsB;;CAEpB,cAAc;;CAEd,aAAa;;CAEb,QAAQ;;CAER,QAAQ;;CAGR,QAAQ,SAAiB,SAAS,GAAS;EACzC,KAAK,cAAc;EACnB,KAAK,aAAa;CACpB;;CAGA,QAAQ,GAAW,IAAI,GAAS;EAC9B,KAAK,QAAQ;EACb,KAAK,QAAQ;CACf;AACF;;;;;;;;;ACWA,MAAa,eAAkE;CAC7E,OAAO;EAAE,MAAM;EAAO,aAAa;EAAO,WAAW;CAAK;CAC1D,IAAI;EAAE,MAAM;EAAM,aAAa;EAAO,WAAW;CAAK;CACtD,WAAW;EAAE,MAAM;EAAM,aAAa;EAAM,WAAW;CAAM;AAC/D;;AAGA,MAAa,wBAAsC;CACjD,GAAG,aAAa;CAChB,aAAa;CACb,aAAa;AACf;AAEA,MAAMA,cAA6C;CACjD;CACA;CACA;CACA;CACA;AACF;AAEA,MAAM,cAAc,GAAiB,MACnCA,YAAU,OAAO,QAAQ,EAAE,SAAS,EAAE,IAAI;;AAG5C,SAAgB,OAAO,OAAkC;CACvD,KAAK,MAAM,QAAQ;EAAC;EAAS;EAAM;CAAW,GAAY;EACxD,MAAM,SAAS,aAAa;EAC5B,IACE,MAAM,SAAS,OAAO,QACtB,MAAM,gBAAgB,OAAO,eAC7B,MAAM,cAAc,OAAO,WAE3B,OAAO;CACX;CACA,OAAO;AACT;;AAGA,SAAgB,UACd,OACA,MACc;CACd,OAAO;EAAE,GAAG;EAAO,GAAG,aAAa;CAAM;AAC3C;;;;;;;AAQA,IAAa,kBAAb,MAA6B;CAC3B;CACA,8BAA+B,IAAI,IAAmC;CAEtE,YAAY,UAAiC,CAAC,GAAG;EAC/C,KAAK,QAAQ;GAAE,GAAG;GAAuB,GAAG;EAAQ;CACtD;;CAGA,MAAoB;EAClB,OAAO,EAAE,GAAG,KAAK,MAAM;CACzB;;CAGA,UAAuB;EACrB,OAAO,OAAO,KAAK,KAAK;CAC1B;;CAGA,QAAQ,MAA4C;EAClD,KAAK,QAAQ,UAAU,KAAK,OAAO,IAAI,CAAC;CAC1C;;CAGA,QAAsC,KAAQ,OAA8B;EAC1E,KAAK,QAAQ;GAAE,GAAG,KAAK;IAAQ,MAAM;EAAM,CAAC;CAC9C;;CAGA,UAAU,IAA+C;EACvD,KAAK,YAAY,IAAI,EAAE;EACvB,aAAa,KAAK,YAAY,OAAO,EAAE;CACzC;CAEA,QAAgB,MAA0B;EACxC,IAAI,WAAW,KAAK,OAAO,IAAI,GAAG;EAClC,KAAK,QAAQ;EACb,MAAM,WAAW,KAAK,IAAI;EAC1B,KAAK,MAAM,MAAM,KAAK,aAAa,GAAG,QAAQ;CAChD;AACF;;;;;;;;;AC5HA,MAAM,cAAc;AAEpB,MAAM,YAAY,OAAO,KAAK,qBAAqB;;AAGnD,SAAgB,mBAAiD;CAC/D,IAAI;EACF,MAAM,MAAM,WAAW,cAAc,QAAQ,WAAW;EACxD,IAAI,CAAC,KAAK,OAAO;EACjB,MAAM,SAAS,KAAK,MAAM,GAAG;EAC7B,MAAM,QAA+B,CAAC;EAEtC,KAAK,MAAM,OAAO,WAChB,IAAI,OAAO,OAAO,SAAS,WAAW,MAAM,OAAO,OAAO;EAE5D,OAAO;CACT,QAAQ;EACN,OAAO;CACT;AACF;;AAGA,SAAgB,iBAAiB,OAA2B;CAC1D,IAAI;EACF,WAAW,cAAc,QAAQ,aAAa,KAAK,UAAU,KAAK,CAAC;CACrE,QAAQ,CAER;AACF;;;;;;;;;;AC7BA,IAAa,qBAAb,MAAgC;CAC9B,yBAA0B,IAAI,IAAqB;CACnD,2BAA4B,IAAI,IAAoB;;CAEpD,yBAA0B,IAAI,IAAoB;;;;;;;CAQlD,QAAW,MAAc,OAAU,OAAsB;EACvD,IAAI,UAAU,KAAA,KAAa,KAAK,OAAO,IAAI,IAAI,KAAK,KAAK,OAAO,IAAI,IAAI,MAAM,OAAO;EACrF,KAAK,OAAO,IAAI,MAAM,KAAK;EAC3B,IAAI,UAAU,KAAA,GAAW,KAAK,OAAO,IAAI,MAAM,KAAK;OAC/C,KAAK,OAAO,OAAO,IAAI;EAC5B,KAAK,KAAK,IAAI;CAChB;;CAGA,OAAO,MAAoB;EACzB,IAAI,KAAK,OAAO,OAAO,IAAI,GAAG;GAC5B,KAAK,OAAO,OAAO,IAAI;GACvB,KAAK,KAAK,IAAI;EAChB;CACF;CAEA,IAAO,MAA6B;EAClC,OAAO,KAAK,OAAO,IAAI,IAAI;CAC7B;;CAGA,QAAQ,MAAsB;EAC5B,OAAO,KAAK,SAAS,IAAI,IAAI,KAAK;CACpC;CAEA,KAAa,MAAoB;EAC/B,KAAK,SAAS,IAAI,OAAO,KAAK,SAAS,IAAI,IAAI,KAAK,KAAK,CAAC;CAC5D;AACF;;;;ACoBA,MAAa,kBAAkB,WAC7B,OAAO,WAAW,YAAY,WAAW,QAAQ,WAAW;;;;ACjE9D,MAAa,eAAe;;;;ACA5B,MAAa,UAAU;;;;ACFvB,MAAa,YAAY;;;;ACEzB,MAAa,QAAQ;;;;;;;;ACErB,MAAa,aAAa;;;ACgB1B,MAAM,gBAAgB;CAAC;CAAO;CAAa;CAAgB;CAAS;AAAc;AAElF,MAAM,iBAAiB,WAA2B;CAChD,MAAM,4BAAY,IAAI,IAAa;CACnC,OAAO,UAAU,UAAU;EAEzB,MAAM,QAAQ,MAAM,UAAU;EAC9B,IAAI,OAAO;GACT,MAAM,MAAM;GACZ,MAAM,gBAAgB,KAAK;GAC3B,MAAM,KAAK;EACb;EACA,MAAM,OAAO;EACb,KAAK,UAAU,UAAU;EACzB,MAAM,WAAW,KAAK;EACtB,IAAI,MAAM,QAAQ,QAAQ,GAAG,KAAK,MAAM,SAAS,UAAU,UAAU,IAAI,KAAK;OACzE,IAAI,UAAU,UAAU,IAAI,QAAQ;CAC3C,CAAC;CAID,KAAK,MAAM,SAAS,WAAW;EAC7B,MAAM,WAAW;EACjB,KAAK,MAAM,QAAQ,eAAe;GAChC,MAAM,UAAU,SAAS;GACzB,IAAI,SAAS,eAAe,OAAO;EACrC;EACA,SAAS,UAAU;CACrB;AACF;;;;;;;;AASA,IAAa,eAAb,MAA0B;CACxB;CACA;CACA;CACA;;CAEA,iCAAkC,IAAI,IAAyC;CAE/E,YACE,MACA,UAA0B,CAAC,GAC3B,eAAmC,IAAI,mBAAmB,GAC1D,OAA+D;EAC7D,QAAQ,IAAI,kBAAkB;EAC9B,UAAU,CAAC;CACb,GACA;EACA,KAAK,OAAO;EACZ,KAAK,UAAU;EACf,KAAK,eAAe;EACpB,KAAK,OAAO;CACd;;CAGA,UAAiC;EAC/B,OAAO;GAAE,cAAc,KAAK;GAAc,GAAG,KAAK;EAAK;CACzD;CAEA,UAAU,OAAoB;EAG5B,MAAM,MAAM,MAAM,OAAO,MAAM,SAAS;EACxC,KAAK,aAAa,QAAQ,YAAY,KAAK,OAAO,GAAG,CAAC;EAEtD,KAAK,iBAAiB,KAAK;CAC7B;;;;;;CAOA,iBAAyB,OAAoB;EAC3C,KAAK,MAAM,UAAU,KAAK,SAAS;GACjC,MAAM,EAAE,MAAM,gBAAgB,OAAO;GACrC,IAAI,UAAU,KAAK,eAAe,IAAI,IAAI;GAC1C,IAAI,CAAC,SAAS;IACZ,0BAAU,IAAI,IAAI;IAClB,KAAK,eAAe,IAAI,MAAM,OAAO;GACvC;GAEA,MAAM,MAAO,MAA6C;GAC1D,MAAM,QACJ,gBAAgB,SACZ,MAAM,QAAQ,GAAG,IACf,IAAI,KAAK,MAAM,MAAM,CAAC,OAAQ,KAAyB,MAAM,CAAC,GAAG,IAAI,CAAC,IACtE,CAAC,IACH,QAAQ,KAAA,IACN,CAAC,CAAC,IAAI,GAAG,CAAC,IACV,CAAC;GACT,MAAM,QAAQ,OAAO,aAAa,CAAC,EAAA,CAAG,KAAK,SAAS,KAAK,aAAa,QAAQ,IAAI,CAAC;GAEnF,MAAM,uBAAO,IAAI,IAAY;GAC7B,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO;IAChC,KAAK,IAAI,GAAG;IACZ,MAAM,OAAO,KAAK,UAAU;KAAE;KAAO;IAAK,CAAC;IAC3C,MAAM,WAAW,QAAQ,IAAI,GAAG;IAChC,IAAI,UAAU,SAAS,MAAM;IAC7B,IAAI,UAAU,KAAK,oBAAoB,SAAS,KAAK,QAAQ;IAC7D,MAAM,QAAQ,OAAO,MAAM,OAAgB,KAAK,QAAQ,CAAC;IACzD,IAAI,MAAM,QAAQ,KAAK,KAAK,IAAI,MAAM,MAAM;IAC5C,QAAQ,IAAI,KAAK;KAAE,GAAG;KAAO;IAAK,CAAC;GACrC;GACA,KAAK,MAAM,CAAC,KAAK,UAAU,SACzB,IAAI,CAAC,KAAK,IAAI,GAAG,GAAG,KAAK,oBAAoB,SAAS,KAAK,KAAK;GAIlE,OAAO,UACL,MAAM,KAAK,GAAG,WAAW,KAAK,GAC9B,KAAK,QAAQ,CACf;EACF;CACF;CAEA,oBACE,SACA,KACA,OACM;EACN,IAAI,MAAM,QAAQ;GAChB,KAAK,KAAK,OAAO,MAAM,MAAM;GAC7B,cAAc,MAAM,MAAM;EAC5B;EACA,MAAM,UAAU;EAChB,QAAQ,OAAO,GAAG;CACpB;;CAIA,eAAe,SAAuB;EACpC,KAAK,MAAM,WAAW,KAAK,eAAe,OAAO,GAC/C,KAAK,MAAM,SAAS,QAAQ,OAAO,GAAG,MAAM,SAAS,OAAO;CAChE;;CAGA,aAAa,UAAyB,OAAmB,QAAiC;EACxF,KAAK,MAAM,WAAW,KAAK,eAAe,OAAO,GAC/C,KAAK,MAAM,SAAS,QAAQ,OAAO,GAAG,MAAM,eAAe,UAAU,OAAO,MAAM;CACtF;;CAIA,IAAI,IAAkC;EACpC,KAAK,MAAM,WAAW,KAAK,eAAe,OAAO,GAAG;GAClD,MAAM,SAAS,QAAQ,IAAI,EAAE,CAAC,EAAE;GAChC,IAAI,QAAQ,OAAO;EACrB;CAEF;;CAGA,WAAW,MAA6C;EACtD,MAAM,sBAAM,IAAI,IAAsB;EACtC,MAAM,UAAU,KAAK,eAAe,IAAI,IAAI;EAC5C,IAAI;QAAc,MAAM,CAAC,IAAI,UAAU,SAAS,IAAI,MAAM,QAAQ,IAAI,IAAI,IAAI,MAAM,MAAM;EAAA;EAC1F,OAAO;CACT;CAEA,UAAgB;EACd,KAAK,MAAM,CAAC,MAAM,YAAY,KAAK,gBAAgB;GACjD,KAAK,MAAM,CAAC,KAAK,UAAU,SAAS,KAAK,oBAAoB,SAAS,KAAK,KAAK;GAChF,KAAK,eAAe,OAAO,IAAI;EACjC;CACF;AACF;;;;ACxJA,MAAM,WAAW;CACf,QAAQ;CACR,QAAQ;CACR,OAAO;CACP,KAAK;CACL,OAAO;AACT;;AAEA,MAAM,mBAAmB;AAsDzB,MAAM,yBAAyB;;;;;;AAsG/B,SAAgB,cAAc,OAAc,SAAkC;CAC5E,MAAM,EAAE,WAAW;CACnB,MAAM,QAAQ,OAAO,eAAe,OAAO;CAC3C,MAAM,SAAS,OAAO,gBAAgB,OAAO;CAI7C,MAAM,WAAW,IAAI,cAAc;EAAE;EAAQ,WAAW;CAAM,CAAC;CAC/D,SAAS,cAAc,KAAK,IAAI,OAAO,kBAAkB,CAAC,CAAC;CAC3D,SAAS,QAAQ,OAAO,QAAQ,KAAK;CACrC,SAAS,UAAU,OAAO;CAG1B,MAAM,SAAkC;EACtC,WAAW,QAAQ,aAAa;EAChC,MAAM,QAAQ,QAAQ;EACtB,OAAO,QAAQ,SAAS;EACxB,SAAS,QAAQ,WAAW;EAC5B,WAAW,QAAQ,aAAa;EAChC,eAAe,QAAQ,iBAAiB;CAC1C;CAKA,MAAM,SAAS,IAAI,aAAa;CAChC,OAAO,SAAS;CAChB,OAAO,SAAS,OAAO;CACvB,MAAM,aAAa,IAAI,QAAQ;CAE/B,MAAM,aAAa,IAAIC,MAAW;CAElC,MAAM,SAAS,IAAI,kBAAkB,MAAM,OAAO,OAAO,IAAI,QAAQ,QAAQ,IAAK,GAAI;CACtF,MAAM,EAAE,aAAa,MAAM,OAAO;CAClC,OAAO,SAAS,IAAI,SAAS,IAAI,SAAS,IAAI,SAAS,EAAE;CAKzD,MAAM,aAAa,QAAQ,WAAW,CAAC;CACvC,MAAM,gBAAgB,WAAW,OAAO,cAAc;CACtD,MAAM,iBAAiB,WAAW,QAAQ,MAAyB,CAAC,eAAe,CAAC,CAAC;CAErF,MAAM,eAAe,IAAI,mBAAmB;CAC5C,IAAI,QAAQ,UAAU,cAAc,UAAU,QAAQ,QAAQ;CAI9D,MAAM,yBAAS,IAAI,IAAY;CAC/B,KAAK,MAAM,UAAU,gBAAgB;EACnC,MAAM,EAAE,MAAM,gBAAgB,OAAO;EACrC,MAAM,MAAO,MAA6C;EAC1D,MAAM,SACJ,gBAAgB,SACZ,MAAM,QAAQ,GAAG,IACf,MACA,CAAC,IACH,QAAQ,KAAA,IACN,CAAC,GAAG,IACJ,CAAC;EACT,KAAK,MAAM,SAAS,QAClB,KAAK,MAAM,OAAO,OAAO,KAAK,SAAS,KAAc,KAAK,CAAC,GAAG,OAAO,IAAI,GAAG;CAEhF;CACA,IAAI,MAAM,aAAa,KAAK,OAAO,IAAI,MAAM,YAAY,GAAG;CAC5D,aAAa,MAAM;CAEnB,MAAM,UAAU,IAAI,aAAa,YAAY,gBAAgB,cAAc;EACzE;EACA;CACF,CAAC;CACD,QAAQ,UAAU,KAAK;CAEvB,MAAM,QAAQ,IAAI,SAAS;CAM3B,MAAM,kBAAkB,IAAI,gBAC1B,iBAAiB,MAAM,QAAQ,YAAY,aAAa,YAAY,CAAC,EACvE;CACA,MAAM,cAAc,IAAI,sBAAsB,UAAU;CACxD,YAAY,UAAU,MAAM,WAAW;CAEvC,MAAM,4BAAY,IAAI,IAA8C;CACpE,MAAM,QAAQ,OAAe,YAA4B;EACvD,UAAU,IAAI,KAAK,CAAC,EAAE,SAAS,YAAY,QAAQ,OAAO,CAAC;CAC7D;CACA,MAAM,gCAAgB,IAAI,IAA+B;CAKzD,MAAM,uBAAO,IAAI,IAAqB;CAItC,MAAM,cAAmC,CAAC;CAC1C,KAAK,MAAM,UAAU,gBAAgB;EACnC,MAAM,aAAa,OAAO,mBAAmB,QAAQ,WAAW,OAAO,KAAK,IAAI,CAAC;EACjF,IAAI,CAAC,YAAY;EACjB,IAAI,WAAW,QAAQ,KAAA,GAAW,KAAK,IAAI,OAAO,KAAK,MAAM,WAAW,GAAG;EAC3E,YAAY,KAAK,UAAU;CAC7B;CAKA,MAAM,UAA4B,cAAc,KAAK,WAAW;EAC9D,MAAM,WAAW,OAAO,MAAM;GAC5B;GACA;GACA;GACA,SAAS;GACT;GACA;GACA,oBAAoB,gBAAgB,IAAI;GACxC;GACA;GACA,UAAU,OAAO,QAAQ,IAAI,EAAE;EACjC,CAAC;EACD,IAAI,SAAS,QAAQ,KAAA,GAAW,KAAK,IAAI,OAAO,MAAM,SAAS,GAAG;EAClE,OAAO;CACT,CAAC;CAID,MAAM,YAAwB,KAAK,IAAI,SAAS,MAAM,KAA+B;EACnF,oBAAoB,CAAC;EACrB,qBAAqB,CAAC;EACtB,eAAe,CAAC;EAChB,aAAa,CAAC;EACd,mBAAmB;EACnB,iBAAiB;CACnB;CAMA,MAAM,eAAe,KAAK,IAAI,gBAAgB;CAK9C,MAAM,eAAe,gBAAgB,IAAI;CACzC,OAAO,YAAY,aAAa;CAChC,IAAI,aAAa,MAAM,cAAc,WAAW,IAAI;CAQpD,IAAI,YAAY,gBAAgB,IAAI;CACpC,gBAAgB,WAAW,UAAU;EACnC,IAAI,MAAM,SAAS,UAAU,MAAM,cAAc,WAAW,MAAM,IAAI;EACtE,IAAI,MAAM,gBAAgB,UAAU,aAAa,OAAO,YAAY,MAAM;EAC1E,YAAY;EACZ,iBAAiB,KAAK;EACtB,KAAK,mBAAmB;GAAE;GAAO,MAAM,gBAAgB,QAAQ;EAAE,CAAC;CACpE,CAAC;CAGD,MAAM,YAAa,KAAK,IAAI,SAAS,MAAM,KAA+B;EACxE,WAAW,CAAC;EACZ,cAAc,CAAC;EACf,eAAe,CAAC;CAClB;CACA,MAAM,WAAY,KAAK,IAAI,SAAS,KAAK,KAA8B;EACrE,YAAY,CAAC;EACb,aAAa,CAAC;EACd,kBAAkB,CAAC;CACrB;CACA,MAAM,SAAU,KAAK,IAAI,SAAS,GAAG,KAA4B;EAC/D,eAAe,CAAC;EAChB,eAAe;EACf,kBAAkB,CAAC;EACnB,kBAAkB;CACpB;CACA,MAAM,WAAY,KAAK,IAAI,SAAS,KAAK,KAAqC;EAC5E,YAAY,CAAC;EACb,aAAa,CAAC;EACd,YAAY,CAAC;EACb,iBAAiB,CAAC;EAClB,eAAe;EACf,gBAAgB,CAAC;EACjB,aAAa;CACf;CAOA,MAAM,WAAW,MAAkC;EACjD,MAAM,QAAS,EAA6B,YAAY,CAAC;EACzD,OAAO,IAAI,IACT,MAAM,QAAQ,MAAM,EAAE,eAAe,KAAA,CAAS,CAAC,CAAC,KAAK,MAAM,CAAC,EAAE,IAAI,EAAE,UAAoB,CAAC,CAC3F;CACF;CAIA,MAAM,cAAc,IAAI,sBACtB,QACA,QACA,YACA,YACM,CAAC,UAAU,YAAY,KAAK,CAAC,UAAU,UAAU,CACzD;CACA,YAAY,SAAS,QAAQ,KAAK,CAAC;CACnC,YAAY,OAAO;CACnB,MAAM,QAAQ,IAAI,MAAM;CACxB,IAAI,QAAuB;CAQ3B,MAAM,QAAQ;EAAE,QAAQ;EAAK,WAAW;CAAK;CAQ7C,IAAI,cAAkC;CACtC,IAAI,WAAkC;CACtC,IAAI,WAAqD;CACzD,IAAI,YAAuF;CAC3F,IAAI,kBAAkB;CAEtB,MAAM,aAAa,KAAK,IAAI,OAAO,kBAAkB,CAAC;CACtD,MAAM,sBAA+B,OAAO,YAAY,KAAK,OAAO,QAAQ,OAAO,QAAQ;CAG3F,MAAM,sBAA4B;EAChC,IAAI,CAAC,aAAa;EAClB,UAAU,QAAQ;EAClB,MAAM,EAAE,gBAAgB,YAAY,UAAU,iBAAiB,eAAe;EAK9E,MAAM,IAAI,IAAI,eAAe,UAAU,IADpB,kBAAkB,GAAG,GAAG;GAAE,SAAS,OAAO;GAAW,MAAM;EAAc,CAChD,CAAC;EAC7C,EAAE,cAAc,UAAU;EAC1B,EAAE,QAAQ,OAAO,MAAM;EACvB,EAAE,QAAQ,IAAI,WAAW,YAAY,MAAM,CAAC;EAC5C,MAAM,OAAO,IAAI,SAAS,YAAY,QAAQ,OAAO,MAAM;EAC3D,KAAK,eAAe;EACpB,KAAK,UAAU,OAAO;EACtB,EAAE,QAAQ,IAAI;EACd,MAAM,QAAQ,IAAI,gBAAgB,IAAI,QAAQ,OAAO,MAAM,GAAG,OAAO,OAAO,MAAM,QAAQ,MAAM,SAAS;EACzG,MAAM,UAAU,OAAO,QAAQ;EAC/B,EAAE,QAAQ,KAAK;EAEf,EAAE,QAAQ,IAAI,WAAW,CAAC;EAC1B,WAAW;EACX,WAAW;EACX,YAAY;CACd;CAEA,MAAM,uBAA6B;EACjC,IAAI,CAAC,cAAc,GAAG;EACtB,IAAI,aAAa;GACf,IAAI,CAAC,UAAU,cAAc;GAC7B;EACF;EACA,IAAI,iBAAiB;EACrB,kBAAkB;EAClB,QAAa,IAAI;GACf,OAAO;GACP,OAAO;GACP,OAAO;GACP,OAAO;GACP,OAAO;EACT,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,kBAAkB,EAAE,cAAc,EAAE,YAAY,EAAE,mBAAmB,EAAE,kBAAkB;GACnG,cAAc;IAAE;IAAgB;IAAY;IAAU;IAAiB;GAAW;GAClF,kBAAkB;GAClB,cAAc;EAChB,CAAC;CACH;CAGA,MAAM,uBAA6B;EACjC,SAAS,UAAU,UAAU,OAAO;EACpC,WAAW,UAAU,QAAQ;GAC3B,MAAM,MAAO,IAA+B;GAC5C,IAAI,CAAC,KAAK;GACV,KAAK,MAAM,KAAK,MAAM,QAAQ,GAAG,IAAI,MAAM,CAAC,GAAG,GAAG,EAAgC,cAAc;EAClG,CAAC;CACH;CACA,eAAe;CACf,eAAe;CAIf,MAAM,qBAA2B;EAC/B,OAAO,kBAAkB,MAAM,KAAK;EACpC,OAAO,OAAO,MAAM;EACpB,SAAS,QAAQ,UAAU;EAC3B,MAAM,IAAI,WAAW;EACrB,MAAM,IAAI,WAAW;EACrB,IAAI,SAAS,WAAW,SAAS,MAAM;EACvC,SAAS,eAAe,IAAI;EAC5B,SAAS,WAAW,GAAG,GAAG,IAAI,GAAG,CAAC;EAClC,SAAS,YAAY,GAAG,GAAG,IAAI,GAAG,CAAC;EACnC,SAAS,OAAO,YAAY,OAAO,OAAO;EAC1C,SAAS,WAAW,IAAI,GAAG,GAAG,IAAI,GAAG,CAAC;EACtC,SAAS,YAAY,IAAI,GAAG,GAAG,IAAI,GAAG,CAAC;EACvC,SAAS,OAAO,YAAY,OAAO,OAAO;EAC1C,SAAS,eAAe,KAAK;EAC7B,SAAS,YAAY,GAAG,GAAG,GAAG,CAAC;CACjC;CAEA,MAAM,oBAA0B;EAC9B,QAAQ,sBAAsB,WAAW;EACzC,MAAM,QAAQ,MAAM,SAAS;EAC7B,MAAM,UAAU,QAAQ;EAGxB,KAAK,MAAM,UAAU,SAAS,OAAO,SAAS,OAAO;EACrD,YAAY,OAAO,OAAO;EAC1B,QAAQ,eAAe,OAAO;EAC9B,KAAK,MAAM,cAAc,aAAa,WAAW,SAAS,OAAO;EAIjE,KAAK,MAAM,WAAW,eAAe,QAAQ,OAAO;EAGpD,QAAQ,aAAa,UAAU,YAAY,MAAM;EACjD,IAAI,OAAO,WAAW,aAAa;OAC9B,IAAI,YAAY,cAAc,GAAG,SAAS,OAAO,KAAK;OACtD,SAAS,OAAO,YAAY,MAAM;CACzC;CA0BA,OAAO;EACL,KAAA;GAxBA,SAAS,OAAO,QAAQ,IAAI,EAAE;GAC9B,QAAQ;GACR,OAAO;GACP,UAAU;IACR,eAAe,SAAS,WAAW,MAAM,QAAQ,SAAS,MAAM;IAChE,eAAe,GAAG,MAAM,MAAM,QAAQ,GAAG,CAAC;GAC5C;GACA,cAAc;IACZ,WAAW,gBAAgB,IAAI;IAC/B,eAAe,gBAAgB,QAAQ;IACvC,UAAU,SAAS,gBAAgB,QAAQ,IAAI;IAC/C,UAAU,KAAK,UAAU,gBAAgB,QAAQ,KAAK,KAAK;IAC3D,YAAY,OAAO,gBAAgB,UAAU,EAAE;GACjD;GACA,aAAa,EACX,yBAAyB,MAAM,YAAY,uBAAuB,CAAC,EACrE;GACA,KAAK;GACL,QAAQ;GACR,OAAO;GACP,OAAO;IAAE,OAAO;IAAY;IAAQ;GAAS;EAI3C;EACF,SAAS;GAAE,KAAK;GAAc,WAAW;EAAU;EACnD,SAAS,MAAM;GACb,QAAQ,UAAU,IAAI;GACtB,YAAY,SAAS,QAAQ,IAAI,CAAC;GAClC,YAAY,UAAU,KAAK,WAAW;GACtC,KAAK,MAAM,UAAU,SAAS,OAAO,WAAW,IAAI;GACpD,MAAM,MAAM,KAAK,OAAO,OAAO;GAC/B,IAAI,OAAO,QAAQ,KAAK;IACtB,OAAO,MAAM;IACb,OAAO,uBAAuB;GAChC;EACF;EACA,QAAQ;GACN,IAAI,UAAU,MAAM;IAClB,MAAM,MAAM;IACZ,YAAY;GACd;EACF;EACA,OAAO;GACL,IAAI,UAAU,MAAM;IAClB,qBAAqB,KAAK;IAC1B,QAAQ;GACV;EACF;EACA,UAAU;GACR,KAAK,KAAK;GACV,KAAK,MAAM,UAAU,SAAS,OAAO,UAAU;GAC/C,KAAK,MAAM,cAAc,aAAa,WAAW,UAAU;GAC3D,YAAY,OAAO;GACnB,YAAY,QAAQ;GACpB,QAAQ,QAAQ;GAChB,UAAU,QAAQ;GAClB,SAAS,QAAQ;GACjB,UAAU,MAAM;GAChB,cAAc,MAAM;EACtB;EACA,GAAG,OAAO,SAAS;GACjB,MAAM,MAAM,UAAU,IAAI,KAAK,qBAAK,IAAI,IAAI;GAC5C,IAAI,IAAI,OAAO;GACf,UAAU,IAAI,OAAO,GAAG;GACxB,aAAa,IAAI,OAAO,OAAO;EACjC;EACA,KAAK,OAAO,SAAS;GACnB,KAAK,OAAO,OAAO;EACrB;EACA,QAAQ,SAAS;GACf,cAAc,IAAI,OAAO;GACzB,aAAa,cAAc,OAAO,OAAO;EAC3C;EACA,OAAO,GAAG,GAAG;GACX,OAAO,SAAS,IAAI;GACpB,OAAO,uBAAuB;GAC9B,SAAS,QAAQ,GAAG,GAAG,KAAK;GAC5B,UAAU,QAAQ,GAAG,CAAC;GACtB,UAAU,QAAQ,GAAG,CAAC;GACtB,WAAW,QAAQ,GAAG,CAAC;EACzB;EACA,UAAU,MAAM;GACd,IAAI,KAAK,YAAY,KAAA,KAAa,KAAK,YAAY,OAAO,SAAS;IACjE,OAAO,UAAU,KAAK;IACtB,eAAe;GACjB;GACA,IAAI,KAAK,SAAS,KAAA,GAAW,OAAO,OAAO,KAAK;GAChD,IAAI,KAAK,UAAU,KAAA,GAAW,OAAO,QAAQ,KAAK;GAGlD,MAAM,YAAY,KAAK,cAAc,KAAA,KAAa,KAAK,cAAc,OAAO;GAC5E,IAAI,KAAK,cAAc,KAAA,GAAW,OAAO,YAAY,KAAK;GAC1D,IAAI,KAAK,cAAc,KAAA,KAAa,KAAK,cAAc,OAAO,WAM5D,gBAAgB,QAAQ,KAAK,YAAY,cAAc,OAAO;GAEhE,IAAI,KAAK,kBAAkB,KAAA,GAAW;IACpC,OAAO,gBAAgB,KAAK;IAC5B,OAAO,SAAS,KAAK;GACvB;GACA,IAAI,UAAU,SAAS,UAAU,OAAO;GACxC,IAAI,WAAW;IACb,UAAU,WAAW,OAAO;IAC5B,UAAU,UAAU,OAAO,QAAQ;GACrC;GACA,IAAI,aAAa,UAAU,cAAc;GACzC,eAAe;EACjB;CACF;AACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scenography/engine",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "The scenography runtime: materializes a scene definition with three.js and runs it. Reconciles by id for hot scene changes.",
5
5
  "license": "MIT",
6
6
  "files": [
@@ -23,7 +23,7 @@
23
23
  }
24
24
  },
25
25
  "dependencies": {
26
- "@scenography/scene": "0.6.0"
26
+ "@scenography/scene": "0.7.0"
27
27
  },
28
28
  "devDependencies": {
29
29
  "@types/three": "^0.160.0",