@idle-screens/saver-metaquarium 0.7.1 → 0.7.2

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/README.md CHANGED
@@ -8,7 +8,9 @@ paths through a dark, fogged tank.
8
8
  ```
9
9
  mount
10
10
  ├─ WebGLRenderer (stencil off, high-performance, sRGB, linear tone)
11
- ├─ Scene: fog (near 60, far 500) + flat MeshBasicMaterial floor
11
+ ├─ Scene: fog (fogNear/fogFar) + terrain floor, and — when `environment`
12
+ │ is not `void` — a terrain silhouette and light shafts (a water
13
+ │ ceiling too, for the rooms that have one: reef, kelp, ice, lagoon)
12
14
  ├─ PerspectiveCamera on param-steered spherical orbit
13
15
  └─ populate():
14
16
  for each fish index:
@@ -16,11 +18,13 @@ mount
16
18
  2. SkeletonUtils.clone() → per-fish skinned mesh
17
19
  3. applyNpcMaterials(): seeded palette body + glow colors (all unlit)
18
20
  4. compileSwimPlan(rng.fork(i), BOUNDS) → closed Catmull-Rom loop
19
- with arc-length table + speed-wobble harmonics
21
+ on the chosen pathShape, with arc-length table + speed-wobble
22
+ harmonics; swimStyle assigns depth band, formation slot or bond
20
23
  5. add to scene
21
24
 
22
25
  frame loop (rAF or renderFrame(t)):
23
- 1. governor: median frame time > 21ms → step render scale down 0.8×
26
+ 1. governor: median frame time > 21ms → render scale ×0.8 (floor 0.56);
27
+ back under 14ms → step it up again
24
28
  2. setState(t):
25
29
  - sample control track → live params
26
30
  - camera orbit from cameraAzimuth + autoRotate * t
@@ -31,7 +35,8 @@ frame loop (rAF or renderFrame(t)):
31
35
  group.position ← pose.xyz
32
36
  group.lookAt ← pose.forward
33
37
  group.rotateZ ← pose.roll (bank into turns)
34
- mixer.setTime ← distance * 0.045 (tail beat)
38
+ + maneuver displacement (seeded per-fish event schedule)
39
+ mixer.setTime ← beat * 0.045, wrapped to clip length (tail beat)
35
40
  3. renderer.render(scene, camera)
36
41
  ```
37
42
 
@@ -39,8 +44,10 @@ frame loop (rAF or renderFrame(t)):
39
44
 
40
45
  - **Deterministic**: seeded RNG only, closed-form swim. `renderFrame(t, seed)`
41
46
  is frame-addressable — same inputs, same frame.
42
- - **Steerable**: camera, fish count, swim speed, fog color — all live via
43
- control track.
47
+ - **Steerable**: camera, cast, room, swim style, maneuvers and palette — every
48
+ param below rides the control track.
49
+ - **Additive by default**: each param's default reproduces the behaviour that
50
+ existed before it was added, so a bump never changes a scene already running.
44
51
  - **Device-tiered**: `@idle-screens/capabilities` scales pixel ratio, AA, and
45
52
  fish cap per device.
46
53
  - **Adaptive governor**: steps render resolution down when frames exceed budget.
@@ -48,33 +55,82 @@ frame loop (rAF or renderFrame(t)):
48
55
 
49
56
  ## Params
50
57
 
58
+ The paramSpace in `src/manifest.ts` is the source of truth — it carries the
59
+ bounds, eases and the reasoning behind each default. Every param defaults to
60
+ the behaviour that existed before it was added, so a scene already on a wall
61
+ never changes because a dependency was bumped.
62
+
63
+ ### Camera
64
+
65
+ | Param | Type | Default | Description |
66
+ |-------|------|---------|-------------|
67
+ | cameraAzimuth | number | 35 | Orbit angle (degrees), 0–360 |
68
+ | cameraElevation | number | 15 | Height angle above the waterline, −5–60 |
69
+ | cameraDistance | number | 110 | Distance from tank center, 80–400 |
70
+ | autoRotate | number | 0 | Continuous orbit speed (deg/s), 0–12 |
71
+
72
+ ### Cast
73
+
74
+ | Param | Type | Default | Description |
75
+ |-------|------|---------|-------------|
76
+ | fishCount | number | 1 | Visible fish, 1–24 (step). Default 1 = hero mode; the pool grows on demand and never shrinks |
77
+ | fishUrl | string | `ipfs://…/fish_257_….glb` | GLB model URL, single-breed mode (`ipfs://` supported; the playground overrides to a local asset) |
78
+ | fishMix | string | `""` | Mixed population DSL: `id[:count][@style]` comma-separated, catalog ids or breed aliases (`"257:2,100:1"`, `"457:3@hover,257:6@school"`). A minted id is an INDIVIDUAL — no id twice in a scene. Non-empty overrides fishUrl + fishCount; counts absolute, tier-capped |
79
+ | dracoPath | string | `""` | Where the Draco decoder lives (most Metaquarium models are Draco-compressed). Empty = the copy shipped beside this package |
80
+
81
+ ### Motion
82
+
51
83
  | Param | Type | Default | Description |
52
84
  |-------|------|---------|-------------|
53
- | cameraAzimuth | number | 35 | Orbit angle (degrees) |
54
- | cameraElevation | number | 15 | Height angle above waterline |
55
- | cameraDistance | number | 110 | Distance from tank center |
56
- | autoRotate | number | 0 | Orbit speed (deg/s) |
57
- | fishCount | number | 1 | Visible fish (step) |
58
- | swimSpeed | number | 1 | Swim time-scale multiplier |
59
- | fogColor | color | #030009 | Atmosphere / background |
60
- | fishUrl | string | ipfs://…/fish_257_….glb | GLB model URL, single-breed mode (playground can override to a local asset) |
61
- | fishMix | string | "" | Mixed population DSL: `id[:count][@style]` comma-separated, catalog ids or breed aliases (`"257:2,100:1"`, `"457:3@hover,257:6@school"` — per-token swim style; untagged tokens follow `swimStyle`). Non-empty overrides fishUrl + fishCount; counts absolute, tier-capped |
62
- | fogNear | number | 60 | Fog start distance (the old hardcoded Fog near) |
63
- | fogFar | number | 500 | Fog full-opacity distance; tank enforces > near + 20 |
64
- | moteDensity | number | 0 | Plankton motes, 0–1 of the tier budget (400/250/120). 0 = off |
65
- | moteColor | color | #7fd6ff | Mote tint |
66
- | floorColor | color | #0a1d33 | Floor disc color (the old hardcoded navy) |
67
- | swimStyle | enum | loop | `loop` (pre-style), `school`, `drift`, `hover`, `patrol`, `bottom`, `surface`; relationship styles `follow` / `pair` / `chase` bond a fish to the nearest preceding unbonded fish in the mix (and swim in its depth band); `auto` gives each untagged token its breed's default |
68
- | lightSeek | number | 0 | Free fish drawn toward the room's light shafts, each to its own pool (needs rays) |
69
- | formationBreathe | number | 0 | The school relaxes outward and back on a ~15 s cycle; only ever expands |
85
+ | swimSpeed | number | 1 | Swim time-scale multiplier, 0.2–3 |
86
+ | swimStyle | enum | `loop` | `loop` (pre-style), `school`, `drift`, `hover`, `patrol`, `bottom`, `surface`; relationship styles `follow` / `pair` / `chase` bond a fish to the nearest preceding unbonded fish; `auto` gives each untagged token its breed's default |
87
+ | pathShape | enum | `wander` | The shape a loop is drawn on: `wander`, `orbit`, `eight`, `helix`, `canyon`, `crossing` (camera-relative parade lane) |
88
+ | formationShape | enum | `phalanx` | How a `school` holds together: `phalanx`, `line`, `ring`, `wedge`, `ball`, `wheel`. Ignored by non-formation styles |
89
+ | swimVariance | number | 0 | Per-fish spread, 0–1: 0 a uniform shoal, 1 every fish its own animal (±40% speed, ±25% size, own phase) |
90
+ | bodyWiggle | number | 0 | Procedural body yaw for models with no animation clip, 0–1. Clipped models ignore it; 0.3–0.4 recommended for a clip-less cast |
91
+ | maneuver | enum | `none` | Named event layered over the swim style: `dart`, `startle`, `graze`, `curious`, `zoomies`. Each fish runs its own seeded schedule |
92
+ | maneuverRate | number | 0.5 | How often events fire, 0–3: 0 never, 1 the maneuver's own tempo (~14–20 s per fish), 3 nearly back to back |
93
+ | maneuverIntensity | number | 0.7 | How hard — scales the surge, the kick and the tail flurry together, 0–1 |
94
+ | lightSeek | number | 0 | Free fish drawn toward the room's light shafts, each to its own pool, 0–1 (needs rays) |
95
+ | formationBreathe | number | 0 | The school relaxes outward and back on a ~15 s cycle, 0–1; only ever expands |
96
+
97
+ ### The room
98
+
99
+ | Param | Type | Default | Description |
100
+ |-------|------|---------|-------------|
101
+ | environment | enum | `void` | The ROOM: `void` (exactly the pre-environment scene), `abyss`, `reef`, `kelp`, `ice`, `vent`, `lagoon`, `universe`. Adds terrain and light shafts, plus a water ceiling for the rooms that have one (reef, kelp, ice, lagoon); never overrides your palette params |
102
+ | floorKind | enum | `auto` | Override the environment's terrain: `auto`, `flat`, `dunes`, `ridges`, `basin` |
103
+ | waterY | number | −1 | Water-ceiling height, −1–220. −1 follows the environment (step, not smooth — the sentinel can't be interpolated through) |
104
+ | rayStrength | number | −1 | Light-shaft strength, −1–1. −1 follows the environment, 0 = off (step, same sentinel reason) |
105
+
106
+ ### Palette
107
+
108
+ | Param | Type | Default | Description |
109
+ |-------|------|---------|-------------|
110
+ | fogColor | color | `#030009` | Water / atmosphere (background + fog) |
111
+ | fogNear | number | 60 | Fog start distance, 20–200 |
112
+ | fogFar | number | 500 | Fog full-opacity distance, 120–1100; the tank enforces far > near + 20 |
113
+ | floorColor | color | `#0a1d33` | Floor disc color |
114
+ | moteDensity | number | 0 | Plankton motes, 0–1 of the tier budget. 0 = off |
115
+ | moteColor | color | `#7fd6ff` | Mote tint |
70
116
 
71
117
  ## File map
72
118
 
73
- | File | Lines | Role |
74
- |------|-------|------|
75
- | tank.ts | ~480 | Renderer, scene, fish spawn, setState, governor, dispose |
76
- | plan.ts | ~180 | Catmull-Rom swim: compile, arc-length, pose-at-distance |
77
- | materials.ts | ~90 | Seeded palette coat (unlit MeshBasicMaterial) |
78
- | manifest.ts | ~100 | Param space, palettes, manifest metadata |
79
- | quality.ts | ~35 | Device-tier quality caps |
80
- | metaquarium.ts | ~40 | Plugin factory + demo track |
119
+ `ls src/` is the truth if this drifts; the roles below are what each module owns.
120
+
121
+ | File | Role |
122
+ |------|------|
123
+ | tank.ts | Renderer, scene, fish spawn, environments, setState, governor, dispose |
124
+ | plan.ts | Catmull-Rom swim: compile, arc-length, pose-at-distance, path shapes |
125
+ | swim.ts | Swim styles, formations, depth bands, relationship bonds |
126
+ | maneuver.ts | Named seeded events (dart, startle, graze, curious, zoomies) |
127
+ | environments.ts | The named rooms: water ceiling, terrain, light shafts, cost budget |
128
+ | materials.ts | Seeded palette coat (unlit MeshBasicMaterial) |
129
+ | ipfs.ts | `ipfs://` resolution, gateway ladder, fishMix parsing, fish catalog |
130
+ | farm.ts | Metaquarium farm lookup (breed aliases, minted token ids) |
131
+ | asset-cids.ts | Pinned CIDs for the default catalog |
132
+ | tank-draco.ts | Draco decoder wiring, scoped to a decode |
133
+ | runtime.ts | three.js runtime resolution seam |
134
+ | manifest.ts | Param space, palettes, manifest metadata (zero-dep subpath) |
135
+ | quality.ts | Device-tier quality caps |
136
+ | metaquarium.ts | Plugin factory + demo track |
package/dist/index.js CHANGED
@@ -27,7 +27,7 @@ function createMetaquarium(opts = {}) {
27
27
  },
28
28
  async mount(ctx) {
29
29
  const [{ mountTank }, caps] = await Promise.all([
30
- import("./tank-AC45HAIZ.js"),
30
+ import("./tank-BLHLRKT2.js"),
31
31
  detectCapabilities()
32
32
  ]);
33
33
  return mountTank(ctx, space, computeTier(caps), opts.catalog);
@@ -284,14 +284,17 @@ var LogicalClock = class {
284
284
  base = 0;
285
285
  origin = null;
286
286
  current = 0;
287
+ paused = true;
287
288
  resume() {
289
+ this.paused = false;
288
290
  this.origin = null;
289
291
  }
290
292
  pause() {
291
293
  this.base = this.current;
292
- this.origin = null;
294
+ this.paused = true;
293
295
  }
294
296
  sample(now) {
297
+ if (this.paused) return this.current;
295
298
  if (this.origin === null) this.origin = now;
296
299
  this.current = this.base + now - this.origin;
297
300
  return this.current;
@@ -1574,4 +1577,4 @@ export {
1574
1577
  __withDracoLoaderForTest,
1575
1578
  mountTank
1576
1579
  };
1577
- //# sourceMappingURL=tank-AC45HAIZ.js.map
1580
+ //# sourceMappingURL=tank-BLHLRKT2.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/tank.ts","../src/tank-draco.ts","../src/materials.ts","../src/runtime.ts"],"sourcesContent":["import type { CapabilityTier } from '@idle-screens/capabilities';\nimport {\n defaultParams,\n integrateParam,\n sampleTrack,\n type ControlTrack,\n type ParamSpace,\n type ParamValue,\n type SaverContext,\n type SaverInstance,\n type SaverLayer,\n type Rng,\n} from '@idle-screens/core';\nimport {\n AdditiveBlending,\n AnimationMixer,\n Box3,\n BufferAttribute,\n BufferGeometry,\n CircleGeometry,\n Color,\n ConeGeometry,\n Fog,\n Group,\n HemisphereLight,\n LinearToneMapping,\n MathUtils,\n Mesh,\n MeshBasicMaterial,\n PerspectiveCamera,\n Points,\n Scene,\n ShaderMaterial,\n DoubleSide,\n PlaneGeometry,\n CylinderGeometry,\n SphereGeometry,\n SRGBColorSpace,\n Vector3,\n WebGLRenderer,\n type AnimationClip,\n type Material,\n type Object3D,\n type SkinnedMesh,\n} from 'three';\nimport { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js';\nimport { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';\nimport { clone as cloneSkinned } from 'three/examples/jsm/utils/SkeletonUtils.js';\nimport { needsDraco } from './tank-draco';\nimport { affordableLayers, environmentOf, FLOOR_KINDS, type EnvironmentPreset, type FloorKind } from './environments';\nimport {\n anchorFraction, bandRange, FISH_LENGTH, fishHash, fishVariation, FORMATION_SHAPES,\n formationExtent, formationSlot, swimStyleOf, type FormationShape, type SwimStyleSpec, autoStyleFor, formationBreathe, idleSway, fitBreath } from './swim';\nimport { maneuverAt, maneuverSpecOf } from './maneuver';\nimport { expandFishMixSlots, FISH_CATALOG, parseFishMix, resolveIpfsUrls, type FishEntry } from './ipfs';\nimport { coerceNum, METAQUARIUM_PARAMS, withDefaults } from './manifest';\nimport {\n addGlowHalos,\n applyNpcMaterials,\n eyeNoseSign,\n forceOpaque,\n MIAMI_VICE_COLORS,\n} from './materials';\nimport {\n compileSwimPlan,\n PATH_SHAPES,\n type PathShape,\n distanceAt,\n swimPoseAtDistance,\n type SwimPlan,\n type SwimPose,\n type TankBounds,\n} from './plan';\nimport {\n effectivePixelRatio,\n probeSoftwareGL,\n qualityFor,\n type TankQuality,\n} from './quality';\nimport { LogicalClock, rateOffset } from './runtime';\n\nconst BOUNDS: TankBounds = { radius: 120, yMin: 15, yMax: 72 };\nconst MAX_FISH = METAQUARIUM_PARAMS.fishCount.max ?? 24;\nconst GLB_CONCURRENCY = 3;\n\n// ---------------------------------------------------------------------------\n// Template cache — shared across instances, never disposed by them.\n// ---------------------------------------------------------------------------\n\ninterface FishTemplate {\n scene: Object3D;\n clip: AnimationClip | null;\n norm: number;\n yaw: number;\n /** True when this template was decoded with Draco (not a fallback blob). */\n draco: boolean;\n}\n\n\n/** One decoder per decoder path, created on first use and kept for the page.\n * Keyed by path so two tanks with different `dracoPath` values do not share\n * a loader (setDecoderPath is ignored after the first decode) and so a path\n * change never disposes a worker another tank is still parsing on. */\nconst DRACO_BY_PATH = new Map<string, DRACOLoader>();\nfunction dracoDecoderPath(path: string): string {\n const base = path || new URL('./draco/', import.meta.url).href;\n // Trailing slash is load-bearing — DRACOLoader concatenates the filename\n // straight onto this, and a missing slash yields `…/dracodraco_decoder.wasm`,\n // which a dev server answers with index.html and the decoder dies on\n // \"Unexpected token '<'\".\n return base.endsWith('/') ? base : `${base}/`;\n}\nfunction dracoLoader(path: string): DRACOLoader {\n const normalized = dracoDecoderPath(path);\n let loader = DRACO_BY_PATH.get(normalized);\n if (!loader) {\n loader = new DRACOLoader();\n // Default: the copy tsup ships beside this module. Vite and friends\n // rewrite `import.meta.url` asset URLs at build time; hosts that bundle\n // the package into a single chunk must copy dist/draco next to that\n // chunk, or set the dracoPath param to a URL they actually serve.\n loader.setDecoderPath(normalized);\n // ONE decoder worker, not three.js's default pool of four.\n //\n // DRACOLoader spawns workers lazily up to its limit and never releases\n // them, so a saver that decodes a handful of fish would leave four\n // decoder workers alive for the life of the page. On a 2-core CI runner\n // under software GL that starved every OTHER worker-based saver — the\n // symptom was 17 unrelated worker e2e tests failing while everything\n // passed locally on a machine with cores to spare. A tank decodes a few\n // small models once and caches the templates; serial decoding is fine.\n loader.setWorkerLimit(1);\n DRACO_BY_PATH.set(normalized, loader);\n }\n return loader;\n}\n\n/** Active decode tasks per decoder path. A template no longer needs Draco\n * after parsing, so lifetime follows the work instead of any tank. */\nconst DRACO_USERS = new Map<string, number>();\n\nfunction retainDraco(path: string): void {\n const k = dracoDecoderPath(path);\n DRACO_USERS.set(k, (DRACO_USERS.get(k) ?? 0) + 1);\n}\n\n/** Release one tank's claim; the last one out disposes the worker. Safe\n * against mid-parse teardown because a parse always happens between a\n * retain and its release. */\nfunction releaseDraco(path: string): void {\n const k = dracoDecoderPath(path);\n const n = (DRACO_USERS.get(k) ?? 0) - 1;\n if (n > 0) { DRACO_USERS.set(k, n); return; }\n DRACO_USERS.delete(k);\n DRACO_BY_PATH.get(k)?.dispose();\n DRACO_BY_PATH.delete(k);\n}\n\nasync function withDracoLoader<T>(\n path: string,\n task: (loader: DRACOLoader) => Promise<T>,\n): Promise<T> {\n retainDraco(path);\n try {\n return await task(dracoLoader(path));\n } finally {\n releaseDraco(path);\n }\n}\n\n/** Test seam for the shared decoder lifetime; not exported by the package. */\nexport const __withDracoLoaderForTest = withDracoLoader;\n\n/**\n * The water ceiling: one translucent plane above the fish, rippling in the\n * vertex shader off a single `uTime` uniform.\n *\n * Closed-form on purpose — the displacement is `sin(ωt + φ)` of the vertex's\n * own position, so the surface is a pure function of t like everything else\n * in this saver, and scrubbing to any frame reproduces it exactly. One draw\n * call, no per-frame CPU work.\n */\nfunction buildWaterCeiling(y: number, color: string, opacity: number): {\n mesh: Mesh; material: ShaderMaterial;\n} {\n const geo = new PlaneGeometry(1400, 1400, 48, 48);\n geo.rotateX(-Math.PI / 2);\n geo.userData.mqOwned = true;\n const mat = new ShaderMaterial({\n transparent: true,\n depthWrite: false,\n side: DoubleSide,\n uniforms: {\n uTime: { value: 0 },\n uColor: { value: new Color(color) },\n uOpacity: { value: opacity },\n },\n vertexShader: `\n uniform float uTime;\n varying float vRipple;\n void main() {\n vec3 p = position;\n float r = sin(p.x * 0.012 + uTime * 0.5) * cos(p.z * 0.014 - uTime * 0.37);\n p.y += r * 6.0;\n vRipple = r;\n gl_Position = projectionMatrix * modelViewMatrix * vec4(p, 1.0);\n }`,\n fragmentShader: `\n uniform vec3 uColor;\n uniform float uOpacity;\n varying float vRipple;\n void main() {\n // Caustic-ish banding: the ripple itself modulates brightness, so the\n // surface reads as moving water rather than a tinted sheet of glass.\n float band = 0.65 + 0.35 * vRipple;\n gl_FragColor = vec4(uColor * band, uOpacity * band);\n }`,\n });\n mat.userData.mqOwned = true;\n const mesh = new Mesh(geo, mat);\n mesh.position.y = y;\n mesh.frustumCulled = false;\n return { mesh, material: mat };\n}\n\n/**\n * Terrain: the floor as a silhouette instead of a disc.\n *\n * Generated from the mount seed — never fetched — so determinism holds, the\n * offline hosts work, and it costs no network. Built ONCE at mount and never\n * touched per frame, which is why the cost model prices it at zero: it is\n * geometry, not animation.\n *\n * The three shapes are the original aquarium's silhouettes read back as\n * height functions: dunes (long soft swells), ridges (sharp parallel ranges,\n * its `spikey`), basin (a bowl that falls away from the camera — what makes\n * an abyss feel deep).\n */\n/** Seabed height as a closed-form function of (x, z).\n *\n * Returned rather than baked straight into vertices so the swim volume can be\n * clamped against the SAME expression the mesh is built from. A `dunes` or\n * `ridges` floor reaches +46, well above the bottom of the fish's depth band,\n * so without this a bottom-hugger swims through the hill it is hugging. */\nfunction terrainHeightFn(kind: FloorKind, rng: Rng): (x: number, z: number) => number {\n const R = 620;\n // Two seeded octaves — enough for a silhouette, cheap enough to build in a\n // frame. Phases come from the rng so two tanks are never the same hill.\n const a = rng.next() * Math.PI * 2;\n const b = rng.next() * Math.PI * 2;\n const c = rng.next() * Math.PI * 2;\n return (x, z) => {\n const d = Math.sqrt(x * x + z * z) / R;\n let h = 0;\n if (kind === 'dunes') {\n // Wavelengths sized to the VISIBLE footprint. The originals (0.011 →\n // 571-unit swells) put less than one swell in frame at any camera, so\n // a QA A/B showed dunes rendering as a smooth tilt while ridges (whose\n // |sin| halves its period) showed real relief. 2-3 swells in frame now.\n h = Math.sin(x * 0.028 + a) * 24 + Math.cos(z * 0.023 + b) * 18\n + Math.sin((x + z) * 0.011 + b) * 6;\n } else if (kind === 'ridges') {\n h = Math.abs(Math.sin(x * 0.02 + a)) * 54 - 18 + Math.sin(z * 0.006 + c) * 10;\n } else if (kind === 'basin') {\n // The bowl stays, but a shorter ripple gives the near floor a surface —\n // d² alone is near-constant inside the tank radius.\n h = d * d * 150 - 60 + Math.sin(x * 0.024 + a) * 7 + Math.cos(z * 0.02 + c) * 5;\n }\n // Feather the rim to nothing so the terrain never shows a cut edge.\n return h * Math.max(0, 1 - d * d);\n };\n}\n\nfunction buildTerrain(height: (x: number, z: number) => number, color: string): Mesh {\n const R = 620;\n const geo = new PlaneGeometry(R * 2, R * 2, 72, 72);\n geo.rotateX(-Math.PI / 2);\n const pos = geo.attributes.position!;\n for (let i = 0; i < pos.count; i++) {\n pos.setY(i, height(pos.getX(i), pos.getZ(i)));\n }\n geo.computeVertexNormals();\n // Shading is BAKED as a grayscale vertex-color lambert term — an unlit\n // heightfield has no relief (a QA pass measured flat→dunes at 2.6/255 mean\n // pixel difference: hills nobody can see). Baking keeps the zero-per-frame\n // cost and leaves material.color as the pure tint, so floorColor steering\n // multiplies through unchanged.\n const normal = geo.attributes.normal!;\n const shadeArr = new Float32Array(pos.count * 3);\n // Fixed low sun from the north-west — direction is aesthetic, not physical.\n const lx = -0.45, ly = 0.78, lz = -0.43;\n for (let i = 0; i < pos.count; i++) {\n const lambert = Math.max(0, normal.getX(i) * lx + normal.getY(i) * ly + normal.getZ(i) * lz);\n const v = 0.45 + 0.55 * lambert;\n shadeArr[i * 3] = v;\n shadeArr[i * 3 + 1] = v;\n shadeArr[i * 3 + 2] = v;\n }\n geo.setAttribute('color', new BufferAttribute(shadeArr, 3));\n geo.userData.mqOwned = true;\n const mat = new MeshBasicMaterial({ color: new Color(color), vertexColors: true });\n mat.userData.mqOwned = true;\n const mesh = new Mesh(geo, mat);\n mesh.frustumCulled = false;\n return mesh;\n}\n\n/**\n * Light shafts: a few additive cones that drift closed-form.\n *\n * `y` may be NEGATIVE. The original's raysLightY swings +/-2000, and that sign\n * is the whole difference between a sunlit surface and an abyssal glow coming\n * up out of the dark — one of the few knobs in that settings file that changes\n * what a place MEANS rather than how it looks.\n *\n * Rays are the only per-frame work in the room, which is why they are the\n * layer the cost model drops first on a weak device.\n */\nfunction buildRays(count: number, color: string, y: number, strength: number, rng: Rng): {\n group: Group; material: ShaderMaterial;\n} {\n const group = new Group();\n const mat = new ShaderMaterial({\n transparent: true,\n depthWrite: false,\n blending: AdditiveBlending,\n side: DoubleSide,\n uniforms: { uTime: { value: 0 }, uColor: { value: new Color(color) }, uStrength: { value: strength } },\n vertexShader: `\n varying float vY;\n void main() {\n vY = uv.y;\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n }`,\n fragmentShader: `\n uniform vec3 uColor; uniform float uStrength; uniform float uTime;\n varying float vY;\n void main() {\n // Fade along the shaft and breathe slowly, so it reads as light in\n // suspended matter rather than a solid cone.\n float fade = smoothstep(0.0, 0.3, vY) * (1.0 - smoothstep(0.72, 1.0, vY));\n float breathe = 0.75 + 0.25 * sin(uTime * 0.4);\n // 0.13, not more: with the shaft finally IN frame, alpha is the whole\n // dial — 0.34 turned ice's near-white shafts into pyramids that\n // dwarfed the fish. Faint is what light through water looks like.\n gl_FragColor = vec4(uColor, fade * uStrength * 0.13 * breathe);\n }`,\n });\n mat.userData.mqOwned = true;\n // The shaft spans the TANK (y 0..150), whatever the light's own distance.\n // The old geometry spanned the SOURCE — height |y|·0.85 centred at y·0.45 —\n // which for vent's y=-520 put every shaft at y -455..-13, entirely under\n // the seabed, and for from-above sources put the bright end hundreds of\n // units past the ceiling. A QA pass measured rayStrength 0→1 at a 0.07/255\n // pixel difference: the dial did nothing. The fade now peaks IN the fish\n // band and closes at both ends so shafts still read as light, not walls.\n const geo = new CylinderGeometry(7, 42, 150, 10, 1, true);\n geo.userData.mqOwned = true;\n for (let i = 0; i < count; i++) {\n const m = new Mesh(geo, mat);\n const a = rng.next() * Math.PI * 2;\n const r = 45 + rng.next() * 130;\n m.position.set(Math.cos(a) * r, 72, Math.sin(a) * r);\n // Shafts from below are inverted so the wide end still faces the light.\n if (y < 0) m.rotation.z = Math.PI;\n m.rotation.y = rng.next() * Math.PI;\n // A slight lean per shaft — parallel columns read as architecture.\n m.rotation.x = (rng.next() - 0.5) * 0.18;\n m.frustumCulled = false;\n group.add(m);\n }\n return { group, material: mat };\n}\n\nconst TEMPLATE_CACHE_CAP = 8;\nconst TEMPLATE_CACHE = new Map<string, Promise<FishTemplate | null>>();\n// Eviction deliberately does NOT dispose the evicted template's GPU\n// resources: another live instance may still hold clones sharing its\n// geometry. The leak is bounded (>8 distinct URLs in one session, a few MB\n// each) and freed on context loss; refcounted disposal lands with fishMix,\n// where multi-URL sessions become normal.\n\n/**\n * Dispose only GPU resources this tank created — coat/glow materials\n * (tagged `mqOwned` by applyNpcMaterials), tank-built geometry (floor,\n * fallback fish), and each clone's Skeleton boneTexture. Template-shared\n * geometry and eyes/textured materials are never touched: SkeletonUtils.clone\n * shares them with the cached template, and disposing them here corrupts\n * every other clone of the same fish.\n */\nfunction disposeOwned(root: Object3D): void {\n root.traverse((o) => {\n const skinned = o as Partial<SkinnedMesh>;\n if (skinned.isSkinnedMesh && skinned.skeleton) skinned.skeleton.dispose();\n const mesh = o as Partial<Mesh>;\n if (mesh.geometry?.userData?.mqOwned) mesh.geometry.dispose();\n const mats: Material[] = Array.isArray(mesh.material)\n ? mesh.material\n : mesh.material\n ? [mesh.material]\n : [];\n for (const m of mats) {\n if (!m.userData?.mqOwned) continue;\n // A texture is disposed only when IT is owned, not because its material\n // is: the metallic-atlas path creates an owned material wearing the\n // TEMPLATE'S shared texture, and freeing that map here would corrupt\n // every other clone of the same cached fish. No path creates owned\n // textures today, so this line waits for one that does.\n const tex = (m as Partial<MeshBasicMaterial>).map;\n if (tex?.userData?.mqOwned) tex.dispose();\n m.dispose();\n }\n });\n}\n\n// ---------------------------------------------------------------------------\n// Fish instance — one per visible fish in the scene.\n// ---------------------------------------------------------------------------\n\ninterface Fish {\n /** Spawn slot. Visibility and phase math key off this — never off array\n * or arrival order, which is GLB-completion order and network-dependent.\n * (With one URL every load awaits the same cached promise, so the two\n * coincide; with mixed URLs they will not.) */\n index: number;\n /** Template URL this fish spawned from — reconcile's diff key. */\n url: string;\n group: Group;\n plan: SwimPlan;\n body: Object3D | null;\n baseScale: number;\n /** The model's own forward yaw from its template. Body wiggle oscillates\n * AROUND it — writing rotation.y absolutely would spin every fish to face\n * whatever the wiggle happened to be. */\n baseYaw: number;\n mixer: AnimationMixer | null;\n clipDuration: number;\n tail: Object3D | null;\n}\n\n/** One fish as `inspect()` reports it — the analytic view of a frame. */\ninterface InspectFish {\n index: number;\n url: string;\n breed: string | null;\n style: string;\n band: string;\n bond: string;\n /** Formation seat, or null for a free fish. */\n seat: number | null;\n x: number;\n y: number;\n z: number;\n /** Compass heading in the ground plane, degrees (0 = +z, 90 = +x). */\n heading: number;\n /** A maneuver event is displacing this fish right now. */\n maneuvering: boolean;\n}\n\n/** The leader a bonded fish rides: plan, where along it, and the light pull\n * it took this frame so the file moves as one. */\ninterface LeaderFrame {\n plan: SwimPlan;\n /** The leader's style — a follower swims in its leader's depth band, so an\n * escort behind a surface turtle skims with it instead of riding the raw\n * spline thirty units below. */\n style: SwimStyleSpec;\n d: number;\n effort: number;\n phase: number;\n pullX: number;\n pullZ: number;\n}\n\n// ---------------------------------------------------------------------------\n// TankInstance — the studs: renderer, fog, floor, fish on spline plans.\n// ---------------------------------------------------------------------------\n\nclass TankInstance implements SaverInstance {\n private readonly ctxSaver: SaverContext;\n private readonly space: ParamSpace;\n private readonly canvas: HTMLCanvasElement;\n private readonly ownsCanvas: boolean;\n private readonly renderer: WebGLRenderer;\n private quality: TankQuality;\n private govScale = 1;\n private frameTimes: number[] = [];\n private lastFrameAt = 0;\n private lastGovCheck = 0;\n\n private readonly scene = new Scene();\n private readonly camera: PerspectiveCamera;\n private readonly fogColor = new Color();\n private readonly floorMat: MeshBasicMaterial;\n private readonly motes: Points;\n private readonly moteMat: ShaderMaterial;\n /** The room. Rebuilt only when the environment inputs change — never per\n * frame — so a static tank costs nothing beyond its draw calls. */\n private room: Object3D | null = null;\n private roomKey = '';\n private waterMat: ShaderMaterial | null = null;\n private terrainMat: MeshBasicMaterial | null = null;\n /** World-space seabed height, or null on a flat floor. Set by buildRoom. */\n private floorHeightAt: ((x: number, z: number) => number) | null = null;\n private rayMat: ShaderMaterial | null = null;\n private ceiling: Mesh | null = null;\n private presetWaterY = 0;\n /** The active room's palette — consulted only where the author left the\n * matching param at its manifest default. */\n private roomPalette: { fog: string; floor: string; mote: string } | null = null;\n private presetRayStrength = 0;\n private readonly floorDisc: Mesh;\n /** Sparse, indexed by spawn slot — holes are still-loading fish. */\n private fish: Array<Fish | undefined> = [];\n /** Desired per-slot template URLs — the single source of truth for the\n * population. reconcile() diffs fish against it; spawn workers re-check\n * wantKey after every await, so a stale load can never seat a fish. */\n private wantUrls: string[] = [];\n /** Per-slot style from `@style` tokens (MQ30); null = the scene's swimStyle. */\n private wantStyles: Array<SwimStyleSpec | null> = [];\n /** Per-slot breed from the mix, for `swimStyle: 'auto'` (MQ33). */\n private wantBreeds: string[] = [];\n /** Ground-plane centres of the room's light shafts — what `lightSeek`\n * pulls toward (MQ32). Empty when the room has no rays. */\n private rayPools: Array<[number, number]> = [];\n /** What the last rendered frame looked like, in numbers — `inspect()`. */\n private lastFish: InspectFish[] = [];\n private lastFrameT = 0;\n /** `inspect()` answers null until a frame has actually been rendered. */\n private rendered = false;\n private wantKey = '';\n private wantInputKey = '';\n /** Last fishMix string whose parse problems were warned — once per mix,\n * not per reconcile, or steering any other param would repeat it. */\n private warnedMix = '';\n private mixMode = false;\n private disposed = false;\n\n private w: number;\n private h: number;\n private frameId: number | null = null;\n private paused = false;\n private t = 0;\n private readonly clock = new LogicalClock();\n\n private params: Record<string, ParamValue>;\n private track: ControlTrack | null = null;\n /** Does the track steer swimSpeed? Decided once per applyTrack. When it\n * does, distance comes from the closed-form integral of the speed curve\n * (speed changes glide); when it does not, the legacy constant-speed\n * formula is kept bit-for-bit. */\n private speedTracked = false;\n private autoRotateTracked = false;\n private readonly thumbnail: boolean;\n private readonly catalog: FishEntry[];\n /** One shared route for formation styles, compiled at mount so switching\n * into `school` never respawns a fish. */\n private carrierPlan: SwimPlan;\n /** Shape every live plan was compiled on — setState recompiles when the\n * steered value moves. Plans are cheap (one arc table); rebuilding them\n * beats respawning fish, which would drop GLBs mid-scene. */\n private pathShape: PathShape = 'wander';\n /** The camera azimuth every `crossing` lane in the tank is laid against.\n * Owned here so spawn-time and steer-time compiles agree. */\n private laneAzimuth = 0;\n constructor(\n ctx: SaverContext,\n space: ParamSpace,\n quality: TankQuality,\n catalog: FishEntry[] = FISH_CATALOG,\n ) {\n this.ctxSaver = ctx;\n this.space = space;\n this.quality = quality;\n this.catalog = catalog;\n this.carrierPlan = compileSwimPlan(ctx.rng.fork(0x5c1), BOUNDS, this.pathShape);\n this.thumbnail = ctx.dpr < 0.5;\n this.params = defaultParams(space);\n this.w = ctx.width;\n this.h = ctx.height;\n\n if (ctx.surface instanceof HTMLCanvasElement) {\n this.canvas = ctx.surface;\n this.ownsCanvas = false;\n } else {\n this.canvas = document.createElement('canvas');\n this.canvas.style.cssText = 'display:block;width:100%;height:100%';\n ctx.host.appendChild(this.canvas);\n this.ownsCanvas = true;\n }\n this.canvas.addEventListener('webglcontextlost', (e) => e.preventDefault());\n\n if (probeSoftwareGL()) this.quality = qualityFor('minimal');\n\n this.renderer = new WebGLRenderer({\n canvas: this.canvas,\n antialias: this.quality.antialias,\n stencil: false,\n powerPreference: 'high-performance',\n });\n this.renderer.setPixelRatio(this.pr());\n this.renderer.setSize(this.w, this.h, false);\n this.renderer.outputColorSpace = SRGBColorSpace;\n this.renderer.toneMapping = LinearToneMapping;\n this.renderer.toneMappingExposure = 1.0;\n\n this.camera = new PerspectiveCamera(55, this.w / this.h, 1, 1200);\n\n const fogHex = String(this.space.fogColor?.default ?? '#030009');\n this.fogColor.set(fogHex);\n this.scene.fog = new Fog(this.fogColor.getHex(), 60, 500);\n this.scene.background = this.fogColor;\n\n const floor = new Mesh(\n new CircleGeometry(600, 32),\n new MeshBasicMaterial({ color: 0x0a1d33 }),\n );\n floor.geometry.userData.mqOwned = true;\n floor.material.userData.mqOwned = true;\n floor.rotation.x = -Math.PI / 2;\n this.scene.add(floor);\n this.floorMat = floor.material;\n this.floorDisc = floor;\n\n // Plankton motes: seeded base positions, closed-form drift in the vertex\n // shader (one uTime uniform — zero CPU per frame, pure in t). Draw range\n // scales with moteDensity; the default 0 renders nothing.\n const moteCap = this.quality.moteCap;\n const moteRng = ctx.rng.fork(0x407e);\n const pos = new Float32Array(moteCap * 3);\n const ph = new Float32Array(moteCap * 3);\n for (let i = 0; i < moteCap; i++) {\n const a = moteRng.next() * Math.PI * 2;\n const r = Math.sqrt(moteRng.next()) * BOUNDS.radius * 1.15;\n pos[i * 3] = Math.cos(a) * r;\n pos[i * 3 + 1] = BOUNDS.yMin + moteRng.next() * (BOUNDS.yMax - BOUNDS.yMin + 20);\n pos[i * 3 + 2] = Math.sin(a) * r;\n ph[i * 3] = moteRng.next() * Math.PI * 2;\n ph[i * 3 + 1] = moteRng.next() * Math.PI * 2;\n ph[i * 3 + 2] = moteRng.next() * Math.PI * 2;\n }\n const moteGeo = new BufferGeometry();\n moteGeo.setAttribute('position', new BufferAttribute(pos, 3));\n moteGeo.setAttribute('aPhase', new BufferAttribute(ph, 3));\n moteGeo.userData.mqOwned = true;\n this.moteMat = new ShaderMaterial({\n transparent: true,\n depthWrite: false,\n blending: AdditiveBlending,\n uniforms: { uTime: { value: 0 }, uColor: { value: new Color('#7fd6ff') } },\n vertexShader: `\n attribute vec3 aPhase;\n uniform float uTime;\n varying float vFade;\n void main() {\n vec3 p = position + vec3(\n sin(uTime * 0.11 + aPhase.x) * 6.0,\n sin(uTime * 0.07 + aPhase.y) * 4.0,\n sin(uTime * 0.13 + aPhase.z) * 6.0);\n vec4 mv = modelViewMatrix * vec4(p, 1.0);\n gl_Position = projectionMatrix * mv;\n // 520/z with a 1.5..7 clamp: ~2.6px at a 200-unit camera, ~6px in\n // macro. The old 180/z capped at 4 rendered ONE dim pixel at wall\n // distances — moteDensity 1 was invisible from any normal camera.\n gl_PointSize = clamp(520.0 / max(1.0, -mv.z), 1.5, 7.0);\n vFade = 0.35 + 0.3 * sin(uTime * 0.9 + aPhase.x * 7.0);\n }`,\n fragmentShader: `\n uniform vec3 uColor;\n varying float vFade;\n void main() {\n vec2 c = gl_PointCoord - 0.5;\n float d = length(c);\n if (d > 0.5) discard;\n gl_FragColor = vec4(uColor, (1.0 - d * 2.0) * vFade * 0.8);\n }`,\n });\n this.moteMat.userData.mqOwned = true;\n this.motes = new Points(moteGeo, this.moteMat);\n this.motes.frustumCulled = false;\n this.motes.visible = false;\n this.scene.add(this.motes);\n\n // Gentle hemisphere so MeshStandardMaterial eyes render their authored detail.\n // MeshBasicMaterial body/glow coats ignore it — zero visual cost for them.\n this.scene.add(new HemisphereLight(0xffffff, 0x4466aa, 1.5));\n\n this.reconcile();\n\n this.paused = ctx.reducedMotion;\n if (this.paused) this.renderStill();\n else this.start();\n }\n\n // ---- fish sourcing ----\n\n private str(key: string): string {\n const v = this.params[key];\n return typeof v === 'string' ? v : String(this.space[key]?.default ?? '');\n }\n\n /** Cheap per-frame room params: move the ceiling, set the shaft strength.\n * No allocation, no teardown — this is what the structure key protects. */\n private applyRoomParams(waterY: number, rayStrength: number): void {\n if (this.ceiling) this.ceiling.position.y = waterY >= 0 ? waterY : this.presetWaterY;\n if (this.rayMat) {\n const s = rayStrength >= 0 ? rayStrength : this.presetRayStrength;\n this.rayMat.uniforms.uStrength!.value = s;\n this.rayMat.visible = s > 0;\n }\n }\n\n /**\n * Build (or rebuild) the room for the current environment params.\n *\n * Keyed like reconcile(): one string compare per frame, real work only when\n * the author actually changes something.\n */\n private buildRoom(): void {\n const envName = this.str('environment');\n const floorOverride = this.str('floorKind');\n const waterY = this.num('waterY');\n const rayStrength = this.num('rayStrength');\n // STRUCTURE key only. waterY and rayStrength move a mesh and set a\n // uniform — rebuilding a 5,000-vertex terrain because someone nudged the\n // ceiling would throw away the whole point of building it once.\n const key = `${envName}|${floorOverride}|${this.quality.envBudget}`;\n if (key === this.roomKey) {\n this.applyRoomParams(waterY, rayStrength);\n return;\n }\n this.roomKey = key;\n\n if (this.room) {\n this.scene.remove(this.room);\n disposeOwned(this.room);\n this.room = null;\n this.waterMat = null;\n this.terrainMat = null;\n this.rayMat = null;\n this.ceiling = null;\n this.rayPools = [];\n }\n const preset: EnvironmentPreset = environmentOf(envName);\n const can = affordableLayers(this.quality.envBudget, preset);\n // The environment decides WHETHER there is a ceiling; waterY (-1 = auto)\n // only moves it. Turning it off is what `void` and `abyss` are for.\n\n const group = new Group();\n // Terrain replaces the disc rather than sitting on it, so `flat` stays\n // byte-for-byte the original floor and every published scene is safe.\n // The classic steering lane validates nothing, so an unknown floorKind\n // must fall back rather than cast — otherwise a typo hides the disc AND\n // builds a zero-height plane, i.e. an invisible floor.\n const kind: FloorKind = FLOOR_KINDS.includes(floorOverride as FloorKind)\n ? (floorOverride as FloorKind)\n : preset.floor;\n if (kind !== 'flat') {\n const floorHex = String(this.params.floorColor ?? preset.palette?.floor ?? this.space.floorColor?.default ?? '#0a1d33');\n const height = terrainHeightFn(kind, this.ctxSaver.rng.fork(0x7e88 ^ preset.seedSalt));\n const terrain = buildTerrain(height, floorHex);\n terrain.position.y = -2;\n // World-space seabed, for the swim clamp. Same expression, same seed.\n this.floorHeightAt = (x, z) => height(x, z) + terrain.position.y;\n group.add(terrain);\n this.terrainMat = terrain.material as MeshBasicMaterial;\n }\n // A flat floor has no hills to avoid; clear any the previous room left.\n if (kind === 'flat') this.floorHeightAt = null;\n this.floorDisc.visible = kind === 'flat';\n if (can.water && preset.water) {\n const y = waterY >= 0 ? waterY : preset.water.y;\n const { mesh, material } = buildWaterCeiling(y, preset.water.color, preset.water.opacity);\n group.add(mesh);\n this.waterMat = material;\n this.ceiling = mesh;\n this.presetWaterY = preset.water.y;\n }\n if (can.rayCount > 0 && preset.rays) {\n // Built even at strength 0: a QA pass found that publishing with\n // rayStrength 0 left rayMat null forever, so later steering hit a dead\n // `if (this.rayMat)` and the dial did nothing until the whole room was\n // rebuilt. Strength is a uniform; existence is the room's business.\n const strength = rayStrength >= 0 ? rayStrength : preset.rays.strength;\n const { group: rg, material } = buildRays(\n can.rayCount, preset.rays.color, preset.rays.y, strength, this.ctxSaver.rng.fork(0x8a1),\n );\n material.visible = strength > 0;\n group.add(rg);\n // Where the light falls, for light-seeking fish. Read once per room —\n // the shafts never move.\n this.rayPools = rg.children.map((m) => [m.position.x, m.position.z]);\n this.rayMat = material;\n this.presetRayStrength = preset.rays.strength;\n }\n this.room = group;\n this.scene.add(group);\n this.applyRoomParams(waterY, rayStrength);\n this.roomPalette = preset.palette ?? null;\n this.ctxSaver.host.dataset.mqEnv = preset.name;\n }\n\n /**\n * The carrier's frame for this instant — position, heading basis, and the\n * inward pull that keeps the shoal in the glass. Once per frame, shared by\n * every fish in the formation.\n *\n * Heading comes from a CHORD across the spline, not the tangent at a point.\n * A rigid formation turns as a body, so a fish 60 units off-centre travels\n * the carrier's angular velocity times its offset: on the tight corners of\n * a seeded loop the tangent swings fast enough to fling the outer ranks at\n * 112 units/s, against a cruise of 8. That is a whip, not a shoal, and it\n * is the one thing that looks unmistakably like a bug. Averaging the\n * heading over a body-length chord costs one extra spline sample per frame\n * and keeps the turn inside what a fish could actually swim.\n */\n private carrierFrame(\n ext: { side: number; up: number; back: number; reach: number },\n style: { speedMul: number },\n tSec: number,\n warpSec: number,\n speed: number,\n ): {\n lead: number; pose: SwimPose; x: number; y: number; z: number;\n fwdX: number; fwdZ: number; rx: number; rz: number;\n } {\n // The carrier moves at the STYLE's speed, deliberately without any\n // per-fish multiplier: a formation whose members each chose their own pace\n // is not a formation.\n const lead = this.speedTracked\n ? distanceAt(this.carrierPlan, warpSec, style.speedMul)\n : distanceAt(this.carrierPlan, tSec, speed * style.speedMul);\n const c = swimPoseAtDistance(this.carrierPlan, lead);\n const chord = FISH_LENGTH * 2.5;\n const a = swimPoseAtDistance(this.carrierPlan, lead + chord);\n const b = swimPoseAtDistance(this.carrierPlan, lead - chord);\n let fwdX = a.x - b.x, fwdZ = a.z - b.z;\n let hl = Math.hypot(fwdX, fwdZ);\n if (hl < 1e-3) {\n // Degenerate chord (a hairpin doubling back on itself) — fall back to\n // the tangent rather than dividing by nothing.\n fwdX = c.fx; fwdZ = c.fz; hl = Math.hypot(fwdX, fwdZ) || 1;\n }\n fwdX /= hl; fwdZ /= hl;\n // Keep the shoal in the tank by moving its CENTRE, never by pulling\n // individual fish toward the middle — that squashing turned a legal\n // lattice back into a pile.\n const maxR = Math.max(0, BOUNDS.radius - ext.reach);\n const cr = Math.hypot(c.x, c.z);\n const cs = cr > maxR && cr > 0 ? maxR / cr : 1;\n return {\n lead,\n pose: c,\n x: c.x * cs,\n y: Math.min(BOUNDS.yMax - ext.up, Math.max(BOUNDS.yMin + ext.up, c.y)),\n z: c.z * cs,\n fwdX,\n fwdZ,\n // Right-hand normal in the ground plane.\n rx: fwdZ,\n rz: -fwdX,\n };\n }\n\n /** Ceiling the pool may ever reach on this device. */\n private poolCapNow(): number {\n const poolCap = this.thumbnail ? Math.min(6, MAX_FISH) : MAX_FISH;\n return Math.min(poolCap, this.quality.fishCap);\n }\n\n /**\n * One mechanism for every population change — initial mount, fishCount\n * growth, fishUrl swap, and fishMix (mixed breeds in one tank): compute the\n * desired per-slot URL list, tear down slots whose fish no longer match,\n * and queue spawns for the holes. Cheap when nothing changed (one string\n * compare per frame). Non-empty fishMix wins over fishUrl/fishCount; an\n * unparseable mix degrades to single-breed mode rather than a blank tank.\n */\n private reconcile(): void {\n const cap = this.poolCapNow();\n const mixStr = this.str('fishMix').trim();\n const url = this.str('fishUrl');\n const n = Math.min(Math.max(1, Math.round(this.num('fishCount'))), cap);\n const inputKey = `${mixStr}\\u0000${url}\\u0000${n}\\u0000${cap}`;\n if (inputKey === this.wantInputKey) return;\n this.wantInputKey = inputKey;\n\n let want: string[] = [];\n let styles: Array<SwimStyleSpec | null> = [];\n let breeds: string[] = [];\n this.mixMode = false;\n if (mixStr !== '') {\n const parsed = parseFishMix(mixStr, this.catalog);\n if (parsed.problems.length > 0 && mixStr !== this.warnedMix) {\n this.warnedMix = mixStr;\n const fallback = parsed.entries.length === 0 ? ' — falling back to fishUrl/fishCount' : '';\n console.warn(`[metaquarium] fishMix \"${mixStr}\": ${parsed.problems.join('; ')}${fallback}`);\n }\n const slots = expandFishMixSlots(parsed.entries, cap);\n want = slots.map((sl) => sl.url);\n styles = slots.map((sl) => (sl.style ? swimStyleOf(sl.style) : null));\n breeds = slots.map((sl) => sl.breed);\n this.mixMode = want.length > 0;\n }\n if (!this.mixMode) {\n // Single-breed: the pool never shrinks while the url is stable\n // (steering fishCount down just hides); a url change — or leaving mix\n // mode, however the mix happened to start — rebuilds at n. Without the\n // mode check, a mix whose first breed equals fishUrl would keep its\n // slots on clear while any other mix would not (review, PR #72).\n const wasSingle = this.wantKey === '' || this.wantKey.startsWith('one:');\n const stable = wasSingle && this.wantUrls.length > 0 && this.wantUrls[0] === url;\n const len = stable ? Math.max(n, Math.min(this.wantUrls.length, cap)) : n;\n want = Array.from({ length: len }, () => url);\n }\n this.wantUrls = want;\n this.wantStyles = this.mixMode ? styles : [];\n this.wantBreeds = this.mixMode ? breeds : [];\n this.wantKey = (this.mixMode ? 'mix:' : 'one:') + want.join('|');\n if (this.mixMode) this.ctxSaver.host.dataset.mqMix = mixStr;\n else delete this.ctxSaver.host.dataset.mqMix;\n\n const missing: number[] = [];\n for (let i = 0; i < Math.max(this.fish.length, want.length); i++) {\n const f = this.fish[i];\n const wantUrl = want[i];\n if (f && (wantUrl === undefined || f.url !== wantUrl)) {\n f.mixer?.stopAllAction();\n this.scene.remove(f.group);\n disposeOwned(f.group);\n this.fish[i] = undefined;\n }\n if (wantUrl !== undefined && !this.fish[i]) missing.push(i);\n }\n this.fish.length = Math.min(this.fish.length, want.length);\n if (missing.length) void this.spawnMissing(missing);\n this.ctxSaver.host.dataset.mqFish = String(this.loadedCount());\n }\n\n /** Bounded-concurrency spawn of the queued slots. Guarded by wantKey: any\n * reconcile that changes the population invalidates in-flight loads. */\n private async spawnMissing(slots: number[]): Promise<void> {\n const key = this.wantKey;\n let next = 0;\n const worker = async (): Promise<void> => {\n while (next < slots.length && !this.disposed && this.wantKey === key) {\n const i = slots[next++]!;\n const slotUrl = this.wantUrls[i]!;\n const tpl = await this.template(slotUrl, this.str('dracoPath'));\n if (this.disposed || this.wantKey !== key) return;\n if (!this.fish[i]) {\n this.spawn(tpl, i, slotUrl);\n if (!tpl) this.healLater(i, slotUrl, key);\n }\n }\n };\n await Promise.all(Array.from({ length: GLB_CONCURRENCY }, worker));\n if (!this.disposed && this.paused) this.renderStill();\n }\n\n private template(url: string, dracoPath = ''): Promise<FishTemplate | null> {\n const key = url;\n let p = TEMPLATE_CACHE.get(key);\n if (!p) {\n p = (async (): Promise<FishTemplate | null> => {\n try {\n // Gateway ladder (MQ21): try each candidate with its own timeout so\n // one flaky gateway degrades to the next instead of to a fallback\n // blob. Non-ipfs URLs have a single candidate.\n const buf = await (async (): Promise<ArrayBuffer> => {\n let lastErr: unknown = new Error('no gateway candidates');\n for (const candidate of resolveIpfsUrls(url)) {\n const ctl = new AbortController();\n const timer = setTimeout(() => ctl.abort(), 12_000);\n try {\n const res = await fetch(candidate, { signal: ctl.signal });\n if (!res.ok) throw new Error(`fish glb ${res.status}`);\n return await res.arrayBuffer();\n } catch (e) {\n lastErr = e;\n } finally {\n clearTimeout(timer);\n }\n }\n throw lastErr;\n })();\n const loader = new GLTFLoader();\n const draco = needsDraco(buf);\n const gltf = draco\n ? await withDracoLoader(dracoPath, async (decoder) => {\n loader.setDRACOLoader(decoder);\n return loader.parseAsync(buf, '');\n })\n : await loader.parseAsync(buf, '');\n const scene = gltf.scene;\n forceOpaque(scene);\n const size = new Box3().setFromObject(scene).getSize(new Vector3());\n const longX = size.x > size.z;\n const nose = eyeNoseSign(scene, longX ? 'x' : 'z') || 1;\n const yaw = longX\n ? nose > 0\n ? -Math.PI / 2\n : Math.PI / 2\n : nose > 0\n ? 0\n : Math.PI;\n return {\n scene,\n clip: gltf.animations[0] ?? null,\n norm: FISH_LENGTH / (Math.max(size.x, size.y, size.z) || 1),\n yaw,\n draco,\n };\n } catch {\n TEMPLATE_CACHE.delete(key);\n return null;\n }\n })();\n if (TEMPLATE_CACHE.size >= TEMPLATE_CACHE_CAP) {\n const oldest = TEMPLATE_CACHE.keys().next().value;\n if (oldest !== undefined) TEMPLATE_CACHE.delete(oldest);\n }\n TEMPLATE_CACHE.set(key, p);\n }\n return p;\n }\n\n private spawn(tpl: FishTemplate | null, index: number, url: string): void {\n const rng = this.ctxSaver.rng.fork(0x715);\n const plan = compileSwimPlan(rng.fork(index), BOUNDS, this.pathShape, { cameraAzimuthDeg: this.laneAzimuth });\n const group = new Group();\n let mixer: AnimationMixer | null = null;\n let tail: Object3D | null = null;\n let bodyNode: Object3D | null = null;\n let clipDuration = 0;\n\n if (tpl) {\n const body = cloneSkinned(tpl.scene);\n applyNpcMaterials(body, this.ctxSaver.rng.fork(0xc0a7 + index));\n // Selective bloom on the GLOW parts — same fork, so a fish's halo color\n // agrees with the coat pass when both fall through to the seeded pick.\n addGlowHalos(body, this.ctxSaver.rng.fork(0xc0a7 + index));\n body.scale.setScalar(tpl.norm);\n body.rotation.y = tpl.yaw;\n group.add(body);\n bodyNode = body;\n if (tpl.clip) {\n mixer = new AnimationMixer(body);\n mixer.clipAction(tpl.clip).play();\n clipDuration = tpl.clip.duration;\n }\n } else {\n const coat = this.ctxSaver.rng\n .fork(0xc0a7 + index)\n .pick(MIAMI_VICE_COLORS);\n const mat = new MeshBasicMaterial({ color: new Color(coat) });\n mat.userData.mqOwned = true;\n const body = new Mesh(new SphereGeometry(FISH_LENGTH / 2, 12, 8), mat);\n body.geometry.userData.mqOwned = true;\n body.scale.set(1, 0.55, 0.4);\n const tailMesh = new Mesh(\n new ConeGeometry(FISH_LENGTH * 0.22, FISH_LENGTH * 0.5, 8),\n mat,\n );\n tailMesh.geometry.userData.mqOwned = true;\n tailMesh.rotation.z = Math.PI / 2;\n tailMesh.position.x = -FISH_LENGTH * 0.62;\n group.add(body, tailMesh);\n tail = tailMesh;\n }\n\n const baseScale = plan.cruise > 10 ? 1.1 : 0.8 + (index % 5) * 0.1;\n group.scale.multiplyScalar(baseScale);\n // Hidden until setState has placed it. A group joins the scene at the\n // ORIGIN, so a fish that finished loading after this frame's setState was\n // drawn at the centre of the tank and then jumped to its spline on the\n // next one — a 60-unit dart, once per fish, as the scene came up. That is\n // the \"fish start out faster than they swim\" everyone sees at mount.\n group.visible = false;\n this.scene.add(group);\n this.fish[index] = {\n index,\n url,\n baseYaw: tpl ? tpl.yaw : 0,\n group,\n plan,\n body: bodyNode,\n baseScale,\n mixer,\n clipDuration,\n tail,\n };\n this.ctxSaver.host.dataset.mqFish = String(this.loadedCount());\n if (tpl?.draco) this.ctxSaver.host.dataset.mqDraco = '1';\n }\n\n /** One delayed second chance for a slot that spawned as a fallback blob:\n * the failed template self-evicted from the cache, so a later fetch can\n * succeed (gateway flakes are transient). Bounded to a single retry per\n * spawn, guarded by wantKey, and skipped after dispose. Population loading\n * was never pure in t (it is network), so the timer breaks nothing. */\n private healLater(index: number, url: string, key: string): void {\n setTimeout(() => {\n void (async (): Promise<void> => {\n if (this.disposed || this.wantKey !== key) return;\n const tpl = await this.template(url, this.str('dracoPath'));\n if (!tpl || this.disposed || this.wantKey !== key) return;\n const blob = this.fish[index];\n if (!blob || blob.body) return; // real fish arrived meanwhile\n blob.mixer?.stopAllAction();\n this.scene.remove(blob.group);\n disposeOwned(blob.group);\n this.fish[index] = undefined;\n this.spawn(tpl, index, url);\n if (this.paused) this.renderStill();\n })();\n }, 15_000);\n }\n\n /** Fish actually spawned — the sparse array's holes are loads in flight. */\n private loadedCount(): number {\n let n = 0;\n for (const f of this.fish) if (f) n++;\n return n;\n }\n\n // ---- params / state ----\n\n private applyParams(t: number): void {\n if (this.track) this.params = sampleTrack(this.space, this.track, t);\n }\n\n private num(key: string): number {\n // Clamped, string-coercing read — the classic steering lane is\n // unvalidated server-side (MQ17), so hostile or stringified values reach\n // us as-is. Note the swimSpeed distance integral (integrateParam) does\n // not clamp; range enforcement at intake is MQ17's half of this fix.\n return coerceNum(this.space[key], this.params[key]);\n }\n\n private setState(t: number): void {\n const tSec = t / 1000;\n this.applyParams(t);\n const speed = this.num('swimSpeed');\n // Warped swim time: ∫ speed dτ. With a steered speed this makes changes\n // glide (MQ11 — multiplying the whole elapsed integral teleported every\n // fish proportionally to elapsed time). Only UNSTEERED tracks keep the\n // legacy closed form bit-for-bit; a steered track uses warped-time\n // semantics throughout, where the speed-wobble cycle scales with speed\n // (deliberate — the same way tail beat and clip phase already do, since\n // both are distance-driven). Both paths are pure functions of (t, track).\n // Bounds clamp the integrated curve to the declared range, matching the\n // clamped num() reads — the classic lane is intake-unvalidated (MQ17).\n const speedDef = this.space.swimSpeed;\n const warpSec = this.speedTracked && this.track\n ? integrateParam(this.space, this.track, 'swimSpeed', t, {\n ...(speedDef?.min !== undefined ? { min: speedDef.min } : {}),\n ...(speedDef?.max !== undefined ? { max: speedDef.max } : {}),\n }) / 1000\n : tSec * speed;\n\n this.reconcile();\n\n // Camera orbit\n const rotation = rateOffset(\n this.space, this.track, 'autoRotate', t, this.num('autoRotate'), this.autoRotateTracked,\n );\n const az = MathUtils.degToRad(this.num('cameraAzimuth') + rotation);\n const el = MathUtils.degToRad(this.num('cameraElevation'));\n const dist = this.num('cameraDistance');\n this.camera.position.set(\n Math.cos(el) * Math.sin(az) * dist,\n Math.max(10, 15 + Math.sin(el) * dist),\n Math.cos(el) * Math.cos(az) * dist,\n );\n this.camera.lookAt(0, 35, 0);\n\n // Fog color\n const fogHex = String(\n this.params.fogColor ?? this.roomPalette?.fog ?? this.space.fogColor?.default ?? '#030009',\n );\n this.fogColor.set(fogHex);\n (this.scene.fog as Fog).color.copy(this.fogColor);\n\n // Atmosphere — every default reproduces the pre-atmosphere constants, so\n // this block is provably invisible until steered.\n const fog = this.scene.fog as Fog;\n fog.near = this.num('fogNear');\n fog.far = Math.max(this.num('fogFar'), fog.near + 20);\n const floorHex = String(this.params.floorColor ?? this.roomPalette?.floor ?? this.space.floorColor?.default ?? '#0a1d33');\n this.floorMat.color.set(floorHex);\n // Terrain follows floorColor as well — the environment supplies the SHAPE,\n // the author keeps the palette.\n this.terrainMat?.color.set(floorHex);\n const density = this.num('moteDensity');\n const active = Math.round(density * this.quality.moteCap);\n this.motes.visible = active > 0;\n if (active > 0) {\n this.motes.geometry.setDrawRange(0, active);\n this.moteMat.uniforms.uTime!.value = tSec;\n (this.moteMat.uniforms.uColor!.value as Color).set(\n String(this.params.moteColor ?? this.roomPalette?.mote ?? this.space.moteColor?.default ?? '#7fd6ff'),\n );\n }\n this.ctxSaver.host.dataset.mqMotes = String(active);\n\n // The room: rebuilt only on change, then driven by the same clock as\n // everything else so it stays pure in t.\n this.buildRoom();\n if (this.waterMat) this.waterMat.uniforms.uTime!.value = tSec;\n if (this.rayMat) this.rayMat.uniforms.uTime!.value = tSec;\n\n const sceneStyleName = this.str('swimStyle');\n // `auto` is not a style: each untagged fish resolves to its breed's\n // default (MQ33). swimStyleOf('auto') falls back to loop, which is what\n // a fish with no known breed (single-breed fishUrl mode) should do.\n const autoMode = sceneStyleName === 'auto';\n const sceneStyle = swimStyleOf(sceneStyleName);\n const variance = this.num('swimVariance');\n const wiggle = this.num('bodyWiggle');\n const seek = this.num('lightSeek');\n const pools = this.rayPools;\n const lattice = formationBreathe(tSec, this.num('formationBreathe'));\n\n const spec = maneuverSpecOf(this.str('maneuver'));\n const mnvRate = this.num('maneuverRate');\n const mnvIntensity = this.num('maneuverIntensity');\n\n // Path shape: recompile plans in place when steered. Same forks as spawn,\n // so a shape round-trip lands every fish back on its exact original loop.\n const shapeRaw = this.str('pathShape');\n const shape = (PATH_SHAPES as readonly string[]).includes(shapeRaw)\n ? (shapeRaw as PathShape)\n : 'wander';\n // `crossing` is laid against the camera, and the camera moves: a steered\n // cameraAzimuth or an autoRotate orbit would otherwise leave the parade\n // running across a view that is no longer there, and a fish spawned later\n // would lay its lane at a different angle from the shoal's. One lane\n // azimuth is owned by the tank (`laneAzimuth`), used by every compile —\n // spawn included — and re-laid when the EFFECTIVE azimuth has turned by\n // more than 2°. Re-laying rotates the whole lane while every fish keeps\n // its arc distance, so the parade turns with the camera instead of\n // jumping. Other shapes ignore the azimuth and are never re-laid for it.\n const azEff = this.num('cameraAzimuth') + this.num('autoRotate') * tSec;\n const laneMoved = shape === 'crossing'\n && Math.abs(((azEff - this.laneAzimuth + 540) % 360) - 180) > 2;\n if (shape !== this.pathShape || laneMoved) {\n this.pathShape = shape;\n this.laneAzimuth = azEff;\n const planOpts = { cameraAzimuthDeg: this.laneAzimuth };\n this.carrierPlan = compileSwimPlan(this.ctxSaver.rng.fork(0x5c1), BOUNDS, shape, planOpts);\n const rng = this.ctxSaver.rng.fork(0x715);\n for (const f of this.fish) {\n if (f) f.plan = compileSwimPlan(rng.fork(f.index), BOUNDS, shape, planOpts);\n }\n }\n\n // Fish. Mix mode: the DSL defines the population absolutely (fishCount\n // is documented as ignored). Single mode: fishCount is the dial;\n // reconcile has already queued any missing slots.\n const visible = this.mixMode\n ? this.wantUrls.length\n : Math.min(Math.round(this.num('fishCount')), this.quality.fishCap);\n // Once per frame, not once per fish: the extent walks every slot, so\n // computing it inside the loop made the formation O(n^2) every frame for\n // a value that is identical across the shoal.\n const fshapeRaw = this.str('formationShape');\n const fshape = (FORMATION_SHAPES as readonly string[]).includes(fshapeRaw)\n ? (fshapeRaw as FormationShape)\n : 'phalanx';\n // Effective style per slot: a token's `@style` wins, else the scene's.\n // Formation seats are handed out over the SUBSET of fish whose effective\n // style forms — a `@school` trio inside a hovering tank is a school of\n // three, seated 0..2, not three fish holding seats in a school of eight.\n const styleAt = (i: number): SwimStyleSpec =>\n this.wantStyles[i] ?? (autoMode ? autoStyleFor(this.wantBreeds[i]) : sceneStyle);\n const formationSeat = new Map<number, number>();\n for (let i = 0; i < visible; i++) {\n if (styleAt(i).formation) formationSeat.set(i, formationSeat.size);\n }\n const fcount = formationSeat.size;\n const formationStyle = fcount > 0 ? styleAt(formationSeat.keys().next().value as number) : null;\n const extent0 = fcount > 0 ? formationExtent(fcount, variance, fshape) : null;\n // Breathing scales seats AND extent together, so the carrier's inward\n // pull still measures the shoal it is actually keeping in the glass —\n // and only as far as the glass allows (fitBreath: a full breath on a\n // tall ring or stacked wedge would otherwise push seats out of the\n // water column, because the carrier moves the centre, not the fish).\n const breath = extent0\n ? fitBreath(extent0, lattice, { yRange: BOUNDS.yMax - BOUNDS.yMin, radius: BOUNDS.radius })\n : lattice;\n const extent = extent0\n ? { side: extent0.side * breath, up: extent0.up * breath, back: extent0.back * breath, reach: extent0.reach * breath }\n : null;\n const carrier = extent && formationStyle ? this.carrierFrame(extent, formationStyle, tSec, warpSec, speed) : null;\n // Relationships (MQ31), resolved in SLOT order: a bonded fish rides the\n // most recent unbonded free fish before it. `pair` seats two on one\n // plan; a third opens a new pair. Nothing here accumulates — a follower\n // is the leader's closed form sampled at a lag.\n let leader: LeaderFrame | null = null;\n let followRank = 0;\n let pendingPair: LeaderFrame | null = null;\n // Proximity startle (MQ31): free fish flinch in a wave from a SENTINEL —\n // the first free fish in slot order — delayed by ground distance, the\n // same 90 u/s the formation wave uses. The sentinel is fixed per scene so\n // no fish changes epicentre mid-flinch.\n let sentinel: { x: number; z: number } | null = null;\n const report: InspectFish[] = [];\n for (const f of this.fish) {\n if (!f) continue;\n f.group.visible = f.index < visible;\n if (!f.group.visible) continue;\n const style = styleAt(f.index);\n const seat = formationSeat.get(f.index) ?? f.index;\n const bond = style.bond ?? 'none';\n // Whose depth band this fish swims in: its own, or its leader's.\n let bandStyle: SwimStyleSpec = style;\n // A chaser's extra tail work — kept OUT of `mnv`, which may be the\n // shared idle constant.\n let flurryBoost = 0;\n\n // Style + per-fish variation. Both are pure functions of (index, t), so\n // a scene stays frame-addressable no matter how varied it looks.\n const varn = fishVariation(f.index, variance);\n const styleSpeed = style.speedMul * varn.speedMul;\n // `effort` is how hard the fish is working; `d` is where that puts it.\n // They differ for styles that hold station: a hovering fish still beats\n // its tail, so animation must not scale with travel or it looks stuffed.\n //\n // Speed goes in distanceAt's THIRD argument, never folded into time.\n // distanceAt scales the whole integral (cruise + wobble) by that\n // argument; multiplying TIME instead compresses the wobble's phase, so a\n // scene with swimSpeed != 1 and no speed track would have silently\n // changed trajectory — breaking `loop`'s no-op promise for exactly the\n // scenes least likely to be looking.\n const effort = this.speedTracked\n ? distanceAt(f.plan, warpSec, styleSpeed)\n : distanceAt(f.plan, tSec, speed * styleSpeed);\n // Every style spreads its cast along the loop, not only the\n // station-keepers; `loop` alone stays anchorless. The rule and the bug\n // that earned it live in swim.ts's `anchorFraction`, where a test can\n // reach them.\n const anchor = anchorFraction(style, f.index, variance) * f.plan.totalLength;\n // Maneuvers displace, never re-rate: `along` moves the fish on its own\n // path, the kick is added to the pose after banding. Both closed-form.\n // Contagious events (startle) propagate through a formation as a wave:\n // seat distance from a per-schedule epicentre becomes a start delay, so\n // the flinch crosses the lattice at ~90 units/s instead of every fish\n // twitching on its own private clock. Free fish keep their own schedule.\n let seatDelay: number | null = null;\n if (spec?.contagious && style.formation) {\n const slot0 = formationSlot(seat, fcount, variance, undefined, fshape);\n seatDelay = Math.hypot(slot0.side, slot0.up, slot0.back) / 90;\n } else if (spec?.contagious) {\n // Undisplaced position — where the fish would be with no event in\n // flight — so the delay itself never depends on the flinch it times.\n const base = swimPoseAtDistance(f.plan, anchor + effort * style.travel);\n if (!sentinel) {\n sentinel = { x: base.x, z: base.z };\n seatDelay = 0;\n } else {\n seatDelay = Math.hypot(base.x - sentinel.x, base.z - sentinel.z) / 90;\n }\n }\n const mnv = maneuverAt(spec, f.index, tSec, mnvRate, mnvIntensity, seatDelay);\n const d = anchor + effort * style.travel + mnv.along * FISH_LENGTH;\n\n // What drives the animation. For a free fish that is its own effort; for\n // a fish in formation it is the carrier's, because the carrier is what\n // is actually moving it. Beating to its own unused loop made a shoal\n // whose tails were out of step with its travel — fish moonwalking.\n let beat = effort;\n let pose;\n if (style.formation) {\n // Carrier school: ONE route, fish held in slots in its local frame, so\n // the shoal turns as a body.\n //\n // Measured over 4 seeds x 600 frames at 8 fish, variance 0.6\n // (src/swim.test.ts holds the spacing half of this as a gate):\n //\n // loop 27.8% of fish-frames with a neighbour inside a body\n // length, closest approach 1.3u, polarisation 0.38\n // school 0.0%, closest approach 27.3u, polarisation 1.00\n //\n // The earlier 0.85-vs-boids'-0.87 figure in this comment described the\n // spike's boids prototype, not this port, and the port's first draft\n // measured WORSE than loop at 27.3%. Rigid offsets from one arc sample\n // are what actually fixed it.\n const slot = formationSlot(seat, fcount, variance, undefined, fshape);\n slot.side *= breath;\n slot.up *= breath;\n slot.back *= breath;\n // A seated fish darts AHEAD of its slot and settles back — the\n // closing displacement, because the permanent one would walk it out\n // of the school forever.\n const seatBack = slot.back - mnv.alongBump * FISH_LENGTH;\n const cf = carrier ?? this.carrierFrame(\n extent ?? formationExtent(fcount, variance, fshape), formationStyle ?? style, tSec, warpSec, speed,\n );\n beat = cf.lead;\n // A seated fish can lead too: a bonded fish after a school trails\n // the CARRIER route behind the whole formation, which is what\n // \"follow the school\" should mean. Retires any half-formed pair.\n leader = {\n plan: this.carrierPlan, style, d: cf.lead - (extent?.back ?? 0), effort: cf.lead,\n phase: varn.phase, pullX: 0, pullZ: 0,\n };\n followRank = 0;\n pendingPair = null;\n // A rigid body means every fish points EXACTLY the same way, which\n // measures as polarisation 1.00 and looks like a formation flight.\n // A few degrees of per-fish yaw, scaled by variance, buys back the\n // look of animals without touching the spacing guarantee.\n const yaw = Math.sin(varn.phase) * 0.12 * variance;\n const cy2 = Math.cos(yaw), sy2 = Math.sin(yaw);\n pose = {\n ...cf.pose,\n x: cf.x + cf.rx * slot.side - cf.fwdX * seatBack,\n y: cf.y + slot.up,\n z: cf.z + cf.rz * slot.side - cf.fwdZ * seatBack,\n fx: cf.fwdX * cy2 - cf.fwdZ * sy2,\n fz: cf.fwdX * sy2 + cf.fwdZ * cy2,\n };\n } else {\n // A pair is two CONSECUTIVE pair fish; anything else between them\n // retires the half-formed one, so a later `@pair` never bonds\n // across an unrelated fish.\n if (bond !== 'pair') pendingPair = null;\n const rel = bond === 'pair' ? pendingPair : bond === 'follow' || bond === 'chase' ? leader : null;\n if (rel) bandStyle = rel.style;\n let flurryExtra = 0;\n if (rel) {\n // Ride the leader's route behind it. A chaser's lag breathes —\n // closing in, falling back — and its tail works hardest as it\n // closes; a follower keeps a fixed station in the file.\n let lag = 0;\n if (bond === 'follow') {\n lag = FISH_LENGTH * 1.7 * (followRank + 1);\n } else if (bond === 'chase') {\n const c = 0.5 + 0.5 * Math.sin(tSec * 0.7 + varn.phase);\n lag = FISH_LENGTH * (1.3 + 1.6 * c);\n flurryExtra = (1 - c) * 0.8;\n }\n pose = swimPoseAtDistance(rel.plan, rel.d - lag);\n beat = rel.effort - lag;\n const hl = Math.hypot(pose.fx, pose.fz) || 1;\n const rxn = pose.fz / hl, rzn = -pose.fx / hl;\n let ox = rel.pullX, oz = rel.pullZ, oy = 0;\n if (bond === 'pair') {\n // The second of the pair, opposite its partner on the shared orbit.\n const th = tSec * 0.6 + rel.phase + Math.PI;\n const R = FISH_LENGTH * 1.1;\n ox += rxn * R * Math.cos(th);\n oz += rzn * R * Math.cos(th);\n oy = R * 0.5 * Math.sin(th);\n pendingPair = null;\n } else {\n // Off the leader's exact line, so a file is not a stack.\n const off = (fishHash(f.index, 61) - 0.5) * FISH_LENGTH * (bond === 'follow' ? 0.8 : 0.5);\n ox += rxn * off;\n oz += rzn * off;\n followRank += 1;\n }\n pose = { ...pose, x: pose.x + ox, y: pose.y + oy, z: pose.z + oz };\n } else {\n pose = swimPoseAtDistance(f.plan, d);\n // Light-seeking: each free fish is drawn toward ITS shaft (chosen\n // by index, so the choice never flips as it moves) by a per-fish\n // appetite. The loop shrinks toward the pool — still the same\n // closed form, translated — and followers take the same pull.\n let pullX = 0, pullZ = 0;\n if (seek > 0 && pools.length > 0) {\n const pool = pools[Math.floor(fishHash(f.index, 53) * pools.length) % pools.length]!;\n const k = seek * 0.7 * (0.5 + 0.5 * fishHash(f.index, 59));\n // A shaft can stand at the glass or beyond it (rooms place their\n // lights, the tank does not); the pull is toward it, never past\n // the radius.\n const tx = (pool[0] - pose.x) * k;\n const tz = (pool[1] - pose.z) * k;\n const tr = Math.hypot(pose.x + tx, pose.z + tz);\n const ts = tr > BOUNDS.radius ? BOUNDS.radius / tr : 1;\n pullX = (pose.x + tx) * ts - pose.x;\n pullZ = (pose.z + tz) * ts - pose.z;\n }\n const me: LeaderFrame = { plan: f.plan, style, d, effort, phase: varn.phase, pullX, pullZ };\n let oy = 0;\n if (bond === 'pair') {\n // First of a pair: takes one side of the shared orbit and waits\n // for a partner. An odd pair fish simply swims its own route.\n const th = tSec * 0.6 + varn.phase;\n const R = FISH_LENGTH * 1.1;\n const hl = Math.hypot(pose.fx, pose.fz) || 1;\n pullX += (pose.fz / hl) * R * Math.cos(th);\n pullZ += (-pose.fx / hl) * R * Math.cos(th);\n oy = R * 0.5 * Math.sin(th);\n pendingPair = me;\n } else {\n leader = me;\n followRank = 0;\n }\n // Idle micro-motion: a station-keeper turns in place (MQ36).\n const sway = idleSway(style, tSec, varn.phase);\n let fx = pose.fx, fz = pose.fz;\n if (sway !== 0) {\n const cs = Math.cos(sway), sn = Math.sin(sway);\n fx = pose.fx * cs - pose.fz * sn;\n fz = pose.fx * sn + pose.fz * cs;\n }\n pose = { ...pose, x: pose.x + pullX, y: pose.y + oy, z: pose.z + pullZ, fx, fz };\n }\n flurryBoost = flurryExtra;\n }\n\n // Depth band, then bob. Clamping BEFORE the bob keeps a bottom-hugger\n // from being lifted out of its band by its own motion.\n let y = pose.y;\n if (style.bobAmp > 0) {\n y += Math.sin(tSec * style.bobHz * Math.PI * 2 + varn.phase) * style.bobAmp;\n }\n // Clamp AFTER the bob. Clamping first let a fish bob straight back out\n // of the band it was just put in — a bottom-hugger that leaves the floor\n // is not band-limited, it is just a fish.\n const band = bandRange(bandStyle.band);\n if (band) {\n let lo = BOUNDS.yMin + (BOUNDS.yMax - BOUNDS.yMin) * band.lo;\n let hi = BOUNDS.yMin + (BOUNDS.yMax - BOUNDS.yMin) * band.hi;\n // A surface skimmer skims the WATER, not the top of the abstract swim\n // volume. Env water planes sit at 118-170 while yMax is 72, so\n // \"surface\" turtles cruised mid-frame far below the ceiling they were\n // named for. Ride just under the actual plane, capped so a steered\n // waterY of 220 cannot fly the cast out of every camera's frame.\n if (bandStyle.band === 'ceiling' && this.ceiling) {\n const wparam = this.num('waterY');\n const wy = wparam >= 0 ? wparam : this.presetWaterY;\n const ceilY = Math.min(wy - FISH_LENGTH * 0.8, BOUNDS.yMax + 60);\n if (ceilY > hi) {\n lo = ceilY - (hi - lo);\n hi = ceilY;\n }\n }\n y = Math.min(hi, Math.max(lo, y));\n }\n\n\n // Inside a depth band a fish swims LEVEL. Without this its heading still\n // points along the unclamped spline, so a bottom-hugger noses down into\n // a floor it can never reach and a skimmer climbs at an invisible lid.\n // Level, not merely flatter: a fraction of the spline's climb still reads\n // as a fish nosing into a floor it cannot reach.\n // Maneuver pitch applies AFTER the band level-lock, deliberately: the\n // level rule zeroes exactly the styles (bottom, drift) a grazer lives\n // in, and grazing IS a posture — nose-down over the substrate is the\n // one maneuver shape a still image can name.\n const fy = (band ? 0 : pose.fy)\n + (mnv.pitch !== 0 ? Math.tan(mnv.pitch) * (Math.hypot(pose.fx, pose.fz) || 1) : 0);\n let px = pose.x, pz = pose.z;\n if (mnv.side !== 0 || mnv.up !== 0) {\n // Kick along the fish's right-hand normal, closing back to zero by\n // the event's end — the fish leaves its line and comes home to it.\n // In a formation the kick is scaled UP: QA measured startle's 2.8\n // body lengths as 1.5 phalanx column pitches — the scattered fish\n // landed on roughly another seat and read as mis-seated. 1.8x clears\n // the lattice.\n const kickScale = style.formation ? 1.8 : 1;\n const hl2 = Math.hypot(pose.fx, pose.fz) || 1;\n px += (pose.fz / hl2) * mnv.side * FISH_LENGTH * kickScale;\n pz += (-pose.fx / hl2) * mnv.side * FISH_LENGTH * kickScale;\n y = Math.min(BOUNDS.yMax + 60, Math.max(BOUNDS.yMin, y + mnv.up * FISH_LENGTH * kickScale));\n // A startle must not kick a fish through the glass.\n const kr = Math.hypot(px, pz);\n if (kr > BOUNDS.radius) {\n px *= BOUNDS.radius / kr;\n pz *= BOUNDS.radius / kr;\n }\n }\n // The seabed clamps LAST and samples the DISPLACED position: `dunes`\n // and `ridges` rise to +46, and both the floor band and a grazing\n // nose-down kick can otherwise put a fish inside the hill it is\n // feeding over. Half a body length of clearance, never above the\n // tank's own ceiling.\n if (this.floorHeightAt) {\n const clear = this.floorHeightAt(px, pz) + FISH_LENGTH * 0.5;\n if (y < clear) y = Math.min(BOUNDS.yMax, clear);\n }\n f.group.position.set(px, y, pz);\n f.group.lookAt(px + pose.fx, y + fy, pz + pose.fz);\n f.group.rotateZ(pose.roll);\n\n const breathe = 1 + Math.sin(tSec * 2.1 + f.index) * 0.008;\n f.group.scale.setScalar(f.baseScale * breathe * varn.scaleMul);\n\n // Most of the breed library carries NO animation clip, so those fish\n // translated along their spline completely rigidly — gliding cardboard.\n // A distance-driven yaw on the body fixes the whole library at once and\n // costs one sin per fish. Clipped models skip it: their clip is better.\n if (f.body && !f.mixer) {\n // Write every frame, scaled by wiggle. Skipping the write at 0 left the\n // last offset latched, so turning the dial down stopped the motion but\n // never returned the fish to its own heading.\n const w = Math.min(1.6, wiggle + (mnv.flurry + flurryBoost) * 0.6);\n f.body.rotation.y = f.baseYaw + Math.sin(beat * 0.06 + varn.phase) * 0.55 * w;\n }\n\n if (f.mixer && f.clipDuration > 0) {\n f.mixer.setTime(\n (((beat * 0.045) % f.clipDuration) + f.clipDuration) % f.clipDuration,\n );\n } else if (f.tail) {\n // warpSec === tSec·speed when speed is constant — same phase as before.\n f.tail.rotation.y = Math.sin(warpSec * 6 + f.index) * 0.5;\n }\n\n report.push({\n index: f.index,\n url: f.url,\n breed: this.wantBreeds[f.index] ?? null,\n style: style.name,\n band: bandStyle.band,\n bond,\n seat: style.formation ? seat : null,\n x: Math.round(px * 10) / 10,\n y: Math.round(y * 10) / 10,\n z: Math.round(pz * 10) / 10,\n heading: Math.round(((Math.atan2(pose.fx, pose.fz) * 180) / Math.PI + 360) % 360),\n maneuvering: Math.abs(mnv.side) > 0.02 || Math.abs(mnv.up) > 0.02 || mnv.flurry > 0.05 || Math.abs(mnv.pitch) > 0.02,\n });\n }\n this.lastFish = report;\n this.lastFrameT = t;\n this.rendered = true;\n }\n\n /**\n * The frame in numbers (MQ38): what is in the tank, where, doing what —\n * for `perceiveChannel`, which cannot read a classic saver's pixels into\n * meaning. Reads the last rendered frame; never simulates. Small on\n * purpose: 24 fish × a dozen fields.\n */\n inspect(): Record<string, unknown> | null {\n // \"Nothing yet\" is null by contract — not an empty tank at t=0.\n if (!this.rendered) return null;\n const fish = this.lastFish;\n const byStyle: Record<string, number> = {};\n for (const f of fish) byStyle[f.style] = (byStyle[f.style] ?? 0) + 1;\n const maneuvering = fish.filter((f) => f.maneuvering).length;\n const cx = fish.length ? fish.reduce((a, f) => a + f.x, 0) / fish.length : 0;\n const cy = fish.length ? fish.reduce((a, f) => a + f.y, 0) / fish.length : 0;\n const cz = fish.length ? fish.reduce((a, f) => a + f.z, 0) / fish.length : 0;\n const spread = fish.length\n ? Math.sqrt(fish.reduce((a, f) => a + (f.x - cx) ** 2 + (f.y - cy) ** 2 + (f.z - cz) ** 2, 0) / fish.length)\n : 0;\n return {\n saver: 'metaquarium',\n t: this.lastFrameT,\n camera: {\n azimuth: this.num('cameraAzimuth'),\n elevation: this.num('cameraElevation'),\n distance: this.num('cameraDistance'),\n autoRotate: this.num('autoRotate'),\n },\n room: {\n environment: this.str('environment'),\n floorKind: this.str('floorKind'),\n waterY: this.ceiling ? (this.num('waterY') >= 0 ? this.num('waterY') : this.presetWaterY) : null,\n rayStrength: this.rayMat ? (this.num('rayStrength') >= 0 ? this.num('rayStrength') : this.presetRayStrength) : 0,\n rayPools: this.rayPools.length,\n },\n cast: {\n fishMix: this.str('fishMix'),\n mixMode: this.mixMode,\n wanted: this.wantUrls.length,\n loaded: this.loadedCount(),\n visible: fish.length,\n cap: this.quality.fishCap,\n byStyle,\n swimStyle: this.str('swimStyle'),\n pathShape: this.pathShape,\n formationShape: this.str('formationShape'),\n },\n maneuver: {\n name: this.str('maneuver'),\n rate: this.num('maneuverRate'),\n intensity: this.num('maneuverIntensity'),\n active: maneuvering,\n },\n motion: {\n centroid: { x: Math.round(cx * 10) / 10, y: Math.round(cy * 10) / 10, z: Math.round(cz * 10) / 10 },\n spread: Math.round(spread * 10) / 10,\n lightSeek: this.num('lightSeek'),\n formationBreathe: this.num('formationBreathe'),\n },\n quality: { fishCap: this.quality.fishCap, envBudget: this.quality.envBudget, governor: Math.round(this.govScale * 100) / 100 },\n fish,\n };\n }\n\n // ---- render ----\n\n private renderScene(): void {\n if (this.renderer.getContext()?.isContextLost?.()) return;\n this.renderer.render(this.scene, this.camera);\n }\n\n private start(): void {\n if (this.frameId !== null || typeof requestAnimationFrame === 'undefined')\n return;\n this.clock.resume();\n this.frameId = requestAnimationFrame((now) => this.loop(now));\n }\n\n private stop(): void {\n if (this.frameId !== null) {\n cancelAnimationFrame(this.frameId);\n this.frameId = null;\n }\n this.clock.pause();\n }\n\n private loop(now: number): void {\n this.frameId = requestAnimationFrame((n) => this.loop(n));\n this.governFrame(now);\n this.t = this.clock.sample(now);\n this.setState(this.t);\n this.renderScene();\n }\n\n private pr(): number {\n return Math.max(\n 0.5,\n effectivePixelRatio(this.w, this.h, this.ctxSaver.dpr, this.quality) *\n this.govScale,\n );\n }\n\n private applyPr(): void {\n const pr = this.pr();\n this.renderer.setPixelRatio(pr);\n this.renderer.setSize(this.w, this.h, false);\n }\n\n private governFrame(now: number): void {\n if (this.lastFrameAt > 0) {\n const dt = now - this.lastFrameAt;\n if (dt < 250) this.frameTimes.push(dt);\n }\n this.lastFrameAt = now;\n if (now - this.lastGovCheck < 2000 || this.frameTimes.length < 30) return;\n this.lastGovCheck = now;\n const sorted = [...this.frameTimes].sort((a, b) => a - b);\n const median = sorted[sorted.length >> 1]!;\n this.frameTimes = [];\n if (median > 21 && this.govScale > 0.56) {\n this.govScale *= 0.8;\n this.applyPr();\n } else if (median < 14 && this.govScale < 1) {\n this.govScale = Math.min(1, this.govScale * 1.15);\n this.applyPr();\n }\n }\n\n private renderStill(): void {\n this.setState(this.t);\n this.renderScene();\n }\n\n // ---- SaverInstance ----\n\n setPaused(paused: boolean): void {\n if (this.paused === paused) return;\n this.paused = paused;\n if (paused) {\n this.stop();\n this.renderStill();\n } else {\n this.start();\n }\n }\n\n resize(width: number, height: number, dpr?: number): void {\n this.w = width;\n this.h = height;\n if (dpr !== undefined) this.ctxSaver.dpr = dpr;\n this.applyPr();\n this.camera.aspect = width / height;\n this.camera.updateProjectionMatrix();\n if (this.paused) this.renderStill();\n }\n\n applyTrack(track: ControlTrack): void {\n this.track = track;\n this.speedTracked = track.deltas.some((d) => d.path === 'swimSpeed');\n this.autoRotateTracked = track.deltas.some((d) => d.path === 'autoRotate');\n if (this.paused) this.renderStill();\n }\n\n renderFrame(t: number, _seed: number): void {\n this.t = t;\n this.clock.seek(t);\n this.setState(t);\n this.renderScene();\n }\n\n /**\n * Snapshot the current frame (SaverInstance.capture). Renders fresh and\n * reads in the SAME task: a WebGL buffer without preserveDrawingBuffer is\n * cleared once the frame is presented, so any read from outside the render\n * tick sees black — which also makes this work in a hidden tab, where rAF\n * never fires and there is no render tick to race at all.\n */\n async capture(): Promise<ImageBitmap | null> {\n if (this.disposed || this.renderer.getContext()?.isContextLost?.()) return null;\n this.renderStill();\n try {\n // createImageBitmap snapshots the source at CALL time; the await only\n // covers the decode. Called here, pre-present, the buffer is intact.\n return await createImageBitmap(this.canvas);\n } catch {\n return null;\n }\n }\n\n composition(): SaverLayer[] {\n return [\n {\n id: 'tank',\n label: 'Tank',\n kind: 'surface',\n el: this.canvas,\n description:\n 'WebGL2 aquarium: floor, fog, fish on Catmull-Rom spline paths',\n },\n ];\n }\n\n dispose(): void {\n this.disposed = true;\n this.stop();\n for (const f of this.fish) {\n if (!f) continue;\n f.mixer?.stopAllAction();\n }\n // One ownership-aware pass over the whole scene (fish, floor): frees what\n // this instance created, leaves template-shared resources for the cache.\n // The context loss below reclaims this context's GPU side regardless.\n disposeOwned(this.scene);\n for (const f of this.fish) {\n if (!f) continue;\n this.scene.remove(f.group);\n }\n this.fish = [];\n this.renderer.dispose();\n this.renderer.forceContextLoss();\n if (this.ownsCanvas) this.canvas.remove();\n delete this.ctxSaver.host.dataset.mqFish;\n delete this.ctxSaver.host.dataset.mqMix;\n delete this.ctxSaver.host.dataset.mqMotes;\n delete this.ctxSaver.host.dataset.mqEnv;\n delete this.ctxSaver.host.dataset.mqDraco;\n delete this.ctxSaver.host.dataset.mqBackend;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Factory — single entry point for mounting a tank.\n// ---------------------------------------------------------------------------\n\nexport function mountTank(\n ctx: SaverContext,\n space: ParamSpace = METAQUARIUM_PARAMS,\n tier: CapabilityTier = 'standard',\n catalog?: FishEntry[],\n): SaverInstance {\n const resolved = withDefaults(space, ctx.params);\n const inst = new TankInstance(ctx, resolved, qualityFor(tier), catalog);\n ctx.host.dataset.mqBackend = 'webgl2';\n return inst;\n}\n","/**\n * Does this GLB require Draco? Read from the container rather than trying and\n * catching, because a GLTFLoader without a decoder fails deep inside parse\n * with no usable signal — which is exactly how a Draco model became a silent\n * fallback blob on the wall. A GLB is: 12-byte header, then length-prefixed\n * chunks; the first is JSON. Cheap, synchronous, no allocation beyond the\n * chunk itself.\n */\nexport function needsDraco(buf: ArrayBuffer): boolean {\n try {\n const dv = new DataView(buf);\n if (dv.getUint32(0, true) !== 0x46546c67) return false; // 'glTF'\n const jsonLen = dv.getUint32(12, true);\n const json = new TextDecoder().decode(new Uint8Array(buf, 20, jsonLen));\n return json.includes('KHR_draco_mesh_compression');\n } catch {\n return false;\n }\n}\n\n/** Test seam — the check is pure and three-free on purpose. */\nexport const __needsDracoForTest = needsDraco;\n","import type { Rng } from '@idle-screens/core';\nimport {\n AdditiveBlending,\n Color,\n MeshBasicMaterial,\n type Material,\n type Mesh,\n type MeshStandardMaterial,\n type Object3D,\n} from 'three';\nimport { MIAMI_VICE_COLORS, BLOOM_COLORS } from './manifest';\n\nexport { MIAMI_VICE_COLORS, BLOOM_COLORS };\n\nconst BODY_COATS = MIAMI_VICE_COLORS.filter((c) => c !== '#1c1c1c');\n\nfunction materialsOf(mesh: Mesh): Material[] {\n return Array.isArray(mesh.material) ? mesh.material : [mesh.material];\n}\n\nfunction isEyes(m: Material): boolean {\n return m.name.startsWith('EYES-') || /eye/i.test(m.name);\n}\n\nfunction isGlow(m: Material): boolean {\n return m.name.startsWith('GLOW-') || /glow/i.test(m.name);\n}\n\n/**\n * Colors the GLOW material names spell out. The collection has three naming\n * generations — `GLOW-Orange` (betafish, textured), `GLOW Blue.001` (later\n * breeds, authored emissive), `GLOW-FINS`/`GLOW-claws` (NPC set) — and for\n * the textured generation the NAME is the only color signal the material\n * carries, so this table is how those fins get an honest halo instead of a\n * random one. Longest-prefix wins so `darkblue` beats `blue`.\n */\nconst GLOW_NAME_COLORS: readonly (readonly [string, string])[] = [\n ['darkblue', '#2244ff'],\n ['lightblue', '#7fd4ff'],\n ['crystal', '#cfeaff'],\n ['orange', '#ff7a00'],\n ['purple', '#a45dff'],\n ['yellow', '#ffe93c'],\n ['white', '#ffffff'],\n ['green', '#3bff6e'],\n ['blue', '#2266ff'],\n ['teal', '#00ffc8'],\n ['pink', '#ff5ad0'],\n ['red', '#ff3b30'],\n];\n\n/**\n * The color a GLOW material should bloom with, in trust order: the authored\n * emissive (the later minted generations carry the real color there), then\n * the color spelled in the name, then a seeded pick — never a hardcoded\n * default, so two fish never share a fallback by accident.\n */\nexport function glowColorOf(m: Material, rng: Rng): Color {\n const em = (m as Partial<MeshStandardMaterial>).emissive;\n if (em && em.r + em.g + em.b > 0.02) return em.clone();\n const token = m.name.toLowerCase().replace(/^glow[\\s_-]*/, '').replace(/[^a-z]/g, '');\n for (const [word, hex] of GLOW_NAME_COLORS) {\n if (token.startsWith(word)) return new Color(hex);\n }\n return new Color(rng.pick(BLOOM_COLORS));\n}\n\nexport function forceOpaque(root: Object3D): void {\n root.traverse((node) => {\n const mesh = node as Mesh;\n if (!mesh.isMesh || !mesh.material) return;\n for (const m of materialsOf(mesh)) {\n m.transparent = false;\n m.opacity = 1;\n m.depthWrite = true;\n m.alphaTest = 0;\n m.needsUpdate = true;\n }\n });\n}\n\nexport function eyeNoseSign(root: Object3D, axis: 'x' | 'z'): number {\n let eyeSum = 0;\n let eyeN = 0;\n let bodySum = 0;\n let bodyN = 0;\n root.updateMatrixWorld(true);\n root.traverse((node) => {\n const mesh = node as Mesh;\n if (!mesh.isMesh || !mesh.material || !mesh.geometry) return;\n mesh.geometry.computeBoundingBox();\n const bb = mesh.geometry.boundingBox;\n if (!bb) return;\n const centerLocal = bb.min.clone().add(bb.max).multiplyScalar(0.5);\n const center = mesh.localToWorld(centerLocal);\n const v = axis === 'x' ? center.x : center.z;\n if (materialsOf(mesh).some(isEyes)) {\n eyeSum += v;\n eyeN++;\n } else {\n bodySum += v;\n bodyN++;\n }\n });\n if (eyeN === 0 || bodyN === 0) return 0;\n return Math.sign(eyeSum / eyeN - bodySum / bodyN);\n}\n\n/** Rec. 709 luminance of a material's own color — how `eyes`/`eyes2` (the NPC\n * set names neither white nor black) get sorted into sclera vs pupil. */\nfunction colorLuminance(m: Material): number {\n const c = (m as Partial<MeshStandardMaterial>).color;\n return c ? 0.2126 * c.r + 0.7152 * c.g + 0.0722 * c.b : 1;\n}\n\n/**\n * Seeded palette coat, informed by what the materials actually are:\n *\n * - EYE/EYES → unlit pure white (sclera) or pure black (pupil). The GLBs ship\n * these as 0.8-gray PBR materials, which the hemisphere light renders dim\n * gray — eyes should read as the brightest point on a fish. Name decides\n * (`EYE-WHITE`/`EYE-Black`); when the name says neither (NPC `eyes`/`eyes2`)\n * the authored color's luminance does.\n * - GLOW (untextured) → unlit basic in the material's OWN color: authored\n * emissive first, name-spelled color second, seeded pick last.\n * - GLOW (textured, the betafish generation) → texture kept; the halo pass\n * (addGlowHalos) still blooms it by its name color.\n * - PrimaryColor/SecondaryColor (NPC set) → a two-tone coat: two DISTINCT\n * seeded picks, so an NPC reads as one animal in two colors rather than a\n * patchwork of independent picks.\n * - other untextured → seeded palette coat; textured → untouched (the atlas\n * IS the look).\n */\nexport function applyNpcMaterials(root: Object3D, rng: Rng): void {\n const coatA = rng.pick(BODY_COATS);\n const coatB = rng.pick(BODY_COATS.filter((c) => c !== coatA));\n root.traverse((node) => {\n const mesh = node as Mesh;\n if (!mesh.isMesh || !mesh.material) return;\n const replaced = materialsOf(mesh).map((m) => {\n if (isEyes(m)) {\n const white = /black/i.test(m.name)\n ? false\n : /white/i.test(m.name) || colorLuminance(m) >= 0.5;\n const eye = new MeshBasicMaterial({ color: white ? 0xffffff : 0x000000 });\n eye.name = m.name;\n eye.userData.mqOwned = true;\n return eye;\n }\n if (isGlow(m) && !(m as Partial<MeshBasicMaterial>).map) {\n const glow = new MeshBasicMaterial({ color: glowColorOf(m, rng) });\n glow.name = m.name;\n // The halo pass reads this back so shell and core NEVER disagree —\n // resolving twice would replay the rng differently in the fallback.\n glow.userData.mqGlowColor = glow.color.getHex();\n // Ours to dispose at fish teardown. Textured materials stay the\n // template's — disposing those would corrupt every other clone.\n glow.userData.mqOwned = true;\n return glow;\n }\n const map = (m as Partial<MeshBasicMaterial>).map;\n if (map) {\n // The metal trap: glTF's DEFAULT metallicFactor is 1.0, and a pure\n // metal under our hemisphere light (no environment map) renders\n // BLACK — the jellyfish shipped that way. Unlit-basic the atlas so\n // the texture reads at full brightness; non-metal atlases keep their\n // authored material untouched.\n const metalness = (m as Partial<import('three').MeshStandardMaterial>).metalness ?? 0;\n if (metalness >= 0.5) {\n const atlas = new MeshBasicMaterial({ map });\n atlas.name = m.name;\n atlas.userData.mqOwned = true;\n return atlas;\n }\n return m;\n }\n const coat = /primary/i.test(m.name)\n ? coatA\n : /secondary/i.test(m.name)\n ? coatB\n : rng.pick(BODY_COATS);\n const body = new MeshBasicMaterial({ color: new Color(coat) });\n body.name = m.name;\n body.userData.mqOwned = true;\n return body;\n });\n mesh.material = Array.isArray(mesh.material) ? replaced : replaced[0]!;\n });\n}\n\n/** Shells a glow part may cast. Few parts → two shells (tight + wide) for a\n * soft falloff; many parts → one, so a five-fin fish costs five extra draws,\n * not ten. */\nconst HALO_PART_CAP = 6;\n\nfunction haloMaterial(color: Color, push: number, opacity: number): MeshBasicMaterial {\n const mat = new MeshBasicMaterial({\n color,\n transparent: true,\n opacity,\n blending: AdditiveBlending,\n depthWrite: false,\n // NOT fogged: additive blending MIXES toward the fog color, and against a\n // lit environment fog (lagoon pink) a distant shell would ADD pink boxes.\n fog: false,\n });\n mat.onBeforeCompile = (shader) => {\n shader.uniforms.uHaloPush = { value: push };\n shader.vertexShader = `uniform float uHaloPush;\\n${shader.vertexShader.replace(\n '#include <begin_vertex>',\n '#include <begin_vertex>\\n\\ttransformed += normal * uHaloPush;',\n )}`;\n };\n // One program for every halo — only the uniform differs.\n mat.customProgramCacheKey = () => 'mq-glow-halo';\n mat.userData.mqOwned = true;\n return mat;\n}\n\n/**\n * Selective bloom, the cheap honest way: for each mesh wearing a GLOW\n * material, add additive shells of the same geometry pushed out along the\n * vertex normals. No EffectComposer, no extra render target — the cost is a\n * couple of extra draws per glow part, and it composes with fog and the\n * governor untouched. Shells share the source's geometry (template-owned,\n * never tagged), so only the halo materials are ours to dispose.\n *\n * Runs AFTER applyNpcMaterials, so untextured glow parts already carry their\n * resolved color — the shell samples the same resolution order and matches.\n */\nexport function addGlowHalos(root: Object3D, rng: Rng): number {\n const glowMeshes: Mesh[] = [];\n root.traverse((node) => {\n const mesh = node as Mesh;\n if (!mesh.isMesh || !mesh.material || mesh.userData.mqHalo) return;\n // Multi-material meshes are skipped: a single-material shell over grouped\n // geometry would bloom the whole body, not the glow slots. GLTF primitives\n // arrive single-material, so in practice this skips nothing.\n if (Array.isArray(mesh.material)) return;\n if (isGlow(mesh.material)) glowMeshes.push(mesh);\n });\n const parts = glowMeshes.slice(0, HALO_PART_CAP);\n if (parts.length === 0) return 0;\n // Push is quoted against the WHOLE MODEL, never the part. Crystal-finned\n // breeds carry a glow part LARGER than their body (seahorse: 27.7 vs 15.3\n // half-diagonal), and part-proportional shells turned those into a\n // displaced ghost of the entire fish — the \"shadow\" a viewer reported from\n // the wall. Against the model, the rim stays a rim: ~1.5% and 3.5% of the\n // fish, well under one voxel, so the cube-normal face separation is\n // subpixel too.\n let modelR = 0;\n for (const mesh of parts) {\n mesh.geometry.computeBoundingSphere();\n modelR = Math.max(modelR, mesh.geometry.boundingSphere?.radius ?? 1);\n }\n root.traverse((node) => {\n const mesh = node as Mesh;\n if (mesh.isMesh && mesh.geometry) {\n mesh.geometry.computeBoundingSphere();\n modelR = Math.max(modelR, mesh.geometry.boundingSphere?.radius ?? 0);\n }\n });\n const shells: readonly (readonly [number, number])[] =\n parts.length <= 3 ? [[0.015, 0.3], [0.035, 0.14]] : [[0.025, 0.26]];\n let added = 0;\n for (const mesh of parts) {\n const stored = (mesh.material as Material).userData.mqGlowColor as number | undefined;\n const color = stored !== undefined ? new Color(stored) : glowColorOf(mesh.material as Material, rng);\n const partR = mesh.geometry.boundingSphere?.radius ?? 1;\n // A glow part that IS most of the silhouette gets one faint veil, not a\n // bright double — its shell already traces the whole fish.\n const large = partR > modelR * 0.55;\n const partShells = large ? [shells[0]!] : shells;\n const dim = large ? 0.55 : 1;\n for (const [k, opacity] of partShells) {\n const halo = mesh.clone();\n halo.material = haloMaterial(color, modelR * k, opacity * dim);\n halo.userData.mqHalo = true;\n halo.renderOrder = 2;\n mesh.parent?.add(halo);\n added++;\n }\n }\n return added;\n}\n","import { integrateParam, type ControlTrack, type ParamSpace } from '@idle-screens/core';\n\n/** Logical media clock that excludes time spent paused. */\nexport class LogicalClock {\n private base = 0;\n private origin: number | null = null;\n private current = 0;\n\n resume(): void {\n this.origin = null;\n }\n\n pause(): void {\n this.base = this.current;\n this.origin = null;\n }\n\n sample(now: number): number {\n if (this.origin === null) this.origin = now;\n this.current = this.base + now - this.origin;\n return this.current;\n }\n\n seek(t: number): void {\n this.base = t;\n this.current = t;\n this.origin = null;\n }\n}\n\n/** Integrate a rate parameter without rescaling all motion accumulated before a steer. */\nexport function rateOffset(\n space: ParamSpace,\n track: ControlTrack | null,\n path: string,\n t: number,\n sampledRate: number,\n tracked: boolean,\n): number {\n if (!tracked || !track) return sampledRate * (t / 1000);\n const def = space[path];\n return integrateParam(space, track, path, t, {\n ...(def?.min !== undefined ? { min: def.min } : {}),\n ...(def?.max !== undefined ? { max: def.max } : {}),\n }) / 1000;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACA;AAAA,EACE;AAAA,EACA,kBAAAA;AAAA,EACA;AAAA,OAQK;AACP;AAAA,EACE,oBAAAC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,SAAAC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,qBAAAC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAKK;AACP,SAAS,mBAAmB;AAC5B,SAAS,kBAAkB;AAC3B,SAAS,SAAS,oBAAoB;;;ACvC/B,SAAS,WAAW,KAA2B;AACpD,MAAI;AACF,UAAM,KAAK,IAAI,SAAS,GAAG;AAC3B,QAAI,GAAG,UAAU,GAAG,IAAI,MAAM,WAAY,QAAO;AACjD,UAAM,UAAU,GAAG,UAAU,IAAI,IAAI;AACrC,UAAM,OAAO,IAAI,YAAY,EAAE,OAAO,IAAI,WAAW,KAAK,IAAI,OAAO,CAAC;AACtE,WAAO,KAAK,SAAS,4BAA4B;AAAA,EACnD,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;ACjBA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAKK;AAKP,IAAM,aAAa,kBAAkB,OAAO,CAAC,MAAM,MAAM,SAAS;AAElE,SAAS,YAAY,MAAwB;AAC3C,SAAO,MAAM,QAAQ,KAAK,QAAQ,IAAI,KAAK,WAAW,CAAC,KAAK,QAAQ;AACtE;AAEA,SAAS,OAAO,GAAsB;AACpC,SAAO,EAAE,KAAK,WAAW,OAAO,KAAK,OAAO,KAAK,EAAE,IAAI;AACzD;AAEA,SAAS,OAAO,GAAsB;AACpC,SAAO,EAAE,KAAK,WAAW,OAAO,KAAK,QAAQ,KAAK,EAAE,IAAI;AAC1D;AAUA,IAAM,mBAA2D;AAAA,EAC/D,CAAC,YAAY,SAAS;AAAA,EACtB,CAAC,aAAa,SAAS;AAAA,EACvB,CAAC,WAAW,SAAS;AAAA,EACrB,CAAC,UAAU,SAAS;AAAA,EACpB,CAAC,UAAU,SAAS;AAAA,EACpB,CAAC,UAAU,SAAS;AAAA,EACpB,CAAC,SAAS,SAAS;AAAA,EACnB,CAAC,SAAS,SAAS;AAAA,EACnB,CAAC,QAAQ,SAAS;AAAA,EAClB,CAAC,QAAQ,SAAS;AAAA,EAClB,CAAC,QAAQ,SAAS;AAAA,EAClB,CAAC,OAAO,SAAS;AACnB;AAQO,SAAS,YAAY,GAAa,KAAiB;AACxD,QAAM,KAAM,EAAoC;AAChD,MAAI,MAAM,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,KAAM,QAAO,GAAG,MAAM;AACrD,QAAM,QAAQ,EAAE,KAAK,YAAY,EAAE,QAAQ,gBAAgB,EAAE,EAAE,QAAQ,WAAW,EAAE;AACpF,aAAW,CAAC,MAAM,GAAG,KAAK,kBAAkB;AAC1C,QAAI,MAAM,WAAW,IAAI,EAAG,QAAO,IAAI,MAAM,GAAG;AAAA,EAClD;AACA,SAAO,IAAI,MAAM,IAAI,KAAK,YAAY,CAAC;AACzC;AAEO,SAAS,YAAY,MAAsB;AAChD,OAAK,SAAS,CAAC,SAAS;AACtB,UAAM,OAAO;AACb,QAAI,CAAC,KAAK,UAAU,CAAC,KAAK,SAAU;AACpC,eAAW,KAAK,YAAY,IAAI,GAAG;AACjC,QAAE,cAAc;AAChB,QAAE,UAAU;AACZ,QAAE,aAAa;AACf,QAAE,YAAY;AACd,QAAE,cAAc;AAAA,IAClB;AAAA,EACF,CAAC;AACH;AAEO,SAAS,YAAY,MAAgB,MAAyB;AACnE,MAAI,SAAS;AACb,MAAI,OAAO;AACX,MAAI,UAAU;AACd,MAAI,QAAQ;AACZ,OAAK,kBAAkB,IAAI;AAC3B,OAAK,SAAS,CAAC,SAAS;AACtB,UAAM,OAAO;AACb,QAAI,CAAC,KAAK,UAAU,CAAC,KAAK,YAAY,CAAC,KAAK,SAAU;AACtD,SAAK,SAAS,mBAAmB;AACjC,UAAM,KAAK,KAAK,SAAS;AACzB,QAAI,CAAC,GAAI;AACT,UAAM,cAAc,GAAG,IAAI,MAAM,EAAE,IAAI,GAAG,GAAG,EAAE,eAAe,GAAG;AACjE,UAAM,SAAS,KAAK,aAAa,WAAW;AAC5C,UAAM,IAAI,SAAS,MAAM,OAAO,IAAI,OAAO;AAC3C,QAAI,YAAY,IAAI,EAAE,KAAK,MAAM,GAAG;AAClC,gBAAU;AACV;AAAA,IACF,OAAO;AACL,iBAAW;AACX;AAAA,IACF;AAAA,EACF,CAAC;AACD,MAAI,SAAS,KAAK,UAAU,EAAG,QAAO;AACtC,SAAO,KAAK,KAAK,SAAS,OAAO,UAAU,KAAK;AAClD;AAIA,SAAS,eAAe,GAAqB;AAC3C,QAAM,IAAK,EAAoC;AAC/C,SAAO,IAAI,SAAS,EAAE,IAAI,SAAS,EAAE,IAAI,SAAS,EAAE,IAAI;AAC1D;AAoBO,SAAS,kBAAkB,MAAgB,KAAgB;AAChE,QAAM,QAAQ,IAAI,KAAK,UAAU;AACjC,QAAM,QAAQ,IAAI,KAAK,WAAW,OAAO,CAAC,MAAM,MAAM,KAAK,CAAC;AAC5D,OAAK,SAAS,CAAC,SAAS;AACtB,UAAM,OAAO;AACb,QAAI,CAAC,KAAK,UAAU,CAAC,KAAK,SAAU;AACpC,UAAM,WAAW,YAAY,IAAI,EAAE,IAAI,CAAC,MAAM;AAC5C,UAAI,OAAO,CAAC,GAAG;AACb,cAAM,QAAQ,SAAS,KAAK,EAAE,IAAI,IAC9B,QACA,SAAS,KAAK,EAAE,IAAI,KAAK,eAAe,CAAC,KAAK;AAClD,cAAM,MAAM,IAAI,kBAAkB,EAAE,OAAO,QAAQ,WAAW,EAAS,CAAC;AACxE,YAAI,OAAO,EAAE;AACb,YAAI,SAAS,UAAU;AACvB,eAAO;AAAA,MACT;AACA,UAAI,OAAO,CAAC,KAAK,CAAE,EAAiC,KAAK;AACvD,cAAM,OAAO,IAAI,kBAAkB,EAAE,OAAO,YAAY,GAAG,GAAG,EAAE,CAAC;AACjE,aAAK,OAAO,EAAE;AAGd,aAAK,SAAS,cAAc,KAAK,MAAM,OAAO;AAG9C,aAAK,SAAS,UAAU;AACxB,eAAO;AAAA,MACT;AACA,YAAM,MAAO,EAAiC;AAC9C,UAAI,KAAK;AAMP,cAAM,YAAa,EAAoD,aAAa;AACpF,YAAI,aAAa,KAAK;AACpB,gBAAM,QAAQ,IAAI,kBAAkB,EAAE,IAAI,CAAC;AAC3C,gBAAM,OAAO,EAAE;AACf,gBAAM,SAAS,UAAU;AACzB,iBAAO;AAAA,QACT;AACA,eAAO;AAAA,MACT;AACA,YAAM,OAAO,WAAW,KAAK,EAAE,IAAI,IAC/B,QACA,aAAa,KAAK,EAAE,IAAI,IACxB,QACA,IAAI,KAAK,UAAU;AACvB,YAAM,OAAO,IAAI,kBAAkB,EAAE,OAAO,IAAI,MAAM,IAAI,EAAE,CAAC;AAC7D,WAAK,OAAO,EAAE;AACd,WAAK,SAAS,UAAU;AACxB,aAAO;AAAA,IACT,CAAC;AACD,SAAK,WAAW,MAAM,QAAQ,KAAK,QAAQ,IAAI,WAAW,SAAS,CAAC;AAAA,EACtE,CAAC;AACH;AAKA,IAAM,gBAAgB;AAEtB,SAAS,aAAa,OAAc,MAAc,SAAoC;AACpF,QAAM,MAAM,IAAI,kBAAkB;AAAA,IAChC;AAAA,IACA,aAAa;AAAA,IACb;AAAA,IACA,UAAU;AAAA,IACV,YAAY;AAAA;AAAA;AAAA,IAGZ,KAAK;AAAA,EACP,CAAC;AACD,MAAI,kBAAkB,CAAC,WAAW;AAChC,WAAO,SAAS,YAAY,EAAE,OAAO,KAAK;AAC1C,WAAO,eAAe;AAAA,EAA6B,OAAO,aAAa;AAAA,MACrE;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AAEA,MAAI,wBAAwB,MAAM;AAClC,MAAI,SAAS,UAAU;AACvB,SAAO;AACT;AAaO,SAAS,aAAa,MAAgB,KAAkB;AAC7D,QAAM,aAAqB,CAAC;AAC5B,OAAK,SAAS,CAAC,SAAS;AACtB,UAAM,OAAO;AACb,QAAI,CAAC,KAAK,UAAU,CAAC,KAAK,YAAY,KAAK,SAAS,OAAQ;AAI5D,QAAI,MAAM,QAAQ,KAAK,QAAQ,EAAG;AAClC,QAAI,OAAO,KAAK,QAAQ,EAAG,YAAW,KAAK,IAAI;AAAA,EACjD,CAAC;AACD,QAAM,QAAQ,WAAW,MAAM,GAAG,aAAa;AAC/C,MAAI,MAAM,WAAW,EAAG,QAAO;AAQ/B,MAAI,SAAS;AACb,aAAW,QAAQ,OAAO;AACxB,SAAK,SAAS,sBAAsB;AACpC,aAAS,KAAK,IAAI,QAAQ,KAAK,SAAS,gBAAgB,UAAU,CAAC;AAAA,EACrE;AACA,OAAK,SAAS,CAAC,SAAS;AACtB,UAAM,OAAO;AACb,QAAI,KAAK,UAAU,KAAK,UAAU;AAChC,WAAK,SAAS,sBAAsB;AACpC,eAAS,KAAK,IAAI,QAAQ,KAAK,SAAS,gBAAgB,UAAU,CAAC;AAAA,IACrE;AAAA,EACF,CAAC;AACD,QAAM,SACJ,MAAM,UAAU,IAAI,CAAC,CAAC,OAAO,GAAG,GAAG,CAAC,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC,OAAO,IAAI,CAAC;AACpE,MAAI,QAAQ;AACZ,aAAW,QAAQ,OAAO;AACxB,UAAM,SAAU,KAAK,SAAsB,SAAS;AACpD,UAAM,QAAQ,WAAW,SAAY,IAAI,MAAM,MAAM,IAAI,YAAY,KAAK,UAAsB,GAAG;AACnG,UAAM,QAAQ,KAAK,SAAS,gBAAgB,UAAU;AAGtD,UAAM,QAAQ,QAAQ,SAAS;AAC/B,UAAM,aAAa,QAAQ,CAAC,OAAO,CAAC,CAAE,IAAI;AAC1C,UAAM,MAAM,QAAQ,OAAO;AAC3B,eAAW,CAAC,GAAG,OAAO,KAAK,YAAY;AACrC,YAAM,OAAO,KAAK,MAAM;AACxB,WAAK,WAAW,aAAa,OAAO,SAAS,GAAG,UAAU,GAAG;AAC7D,WAAK,SAAS,SAAS;AACvB,WAAK,cAAc;AACnB,WAAK,QAAQ,IAAI,IAAI;AACrB;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;;;AC5RA,SAAS,sBAA0D;AAG5D,IAAM,eAAN,MAAmB;AAAA,EAChB,OAAO;AAAA,EACP,SAAwB;AAAA,EACxB,UAAU;AAAA,EAElB,SAAe;AACb,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,QAAc;AACZ,SAAK,OAAO,KAAK;AACjB,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,OAAO,KAAqB;AAC1B,QAAI,KAAK,WAAW,KAAM,MAAK,SAAS;AACxC,SAAK,UAAU,KAAK,OAAO,MAAM,KAAK;AACtC,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,KAAK,GAAiB;AACpB,SAAK,OAAO;AACZ,SAAK,UAAU;AACf,SAAK,SAAS;AAAA,EAChB;AACF;AAGO,SAAS,WACd,OACA,OACA,MACA,GACA,aACA,SACQ;AACR,MAAI,CAAC,WAAW,CAAC,MAAO,QAAO,eAAe,IAAI;AAClD,QAAM,MAAM,MAAM,IAAI;AACtB,SAAO,eAAe,OAAO,OAAO,MAAM,GAAG;AAAA,IAC3C,GAAI,KAAK,QAAQ,SAAY,EAAE,KAAK,IAAI,IAAI,IAAI,CAAC;AAAA,IACjD,GAAI,KAAK,QAAQ,SAAY,EAAE,KAAK,IAAI,IAAI,IAAI,CAAC;AAAA,EACnD,CAAC,IAAI;AACP;;;AHoCA,IAAM,SAAqB,EAAE,QAAQ,KAAK,MAAM,IAAI,MAAM,GAAG;AAC7D,IAAM,WAAW,mBAAmB,UAAU,OAAO;AACrD,IAAM,kBAAkB;AAoBxB,IAAM,gBAAgB,oBAAI,IAAyB;AACnD,SAAS,iBAAiB,MAAsB;AAC9C,QAAM,OAAO,QAAQ,IAAI,IAAI,YAAY,YAAY,GAAG,EAAE;AAK1D,SAAO,KAAK,SAAS,GAAG,IAAI,OAAO,GAAG,IAAI;AAC5C;AACA,SAAS,YAAY,MAA2B;AAC9C,QAAM,aAAa,iBAAiB,IAAI;AACxC,MAAI,SAAS,cAAc,IAAI,UAAU;AACzC,MAAI,CAAC,QAAQ;AACX,aAAS,IAAI,YAAY;AAKzB,WAAO,eAAe,UAAU;AAUhC,WAAO,eAAe,CAAC;AACvB,kBAAc,IAAI,YAAY,MAAM;AAAA,EACtC;AACA,SAAO;AACT;AAIA,IAAM,cAAc,oBAAI,IAAoB;AAE5C,SAAS,YAAY,MAAoB;AACvC,QAAM,IAAI,iBAAiB,IAAI;AAC/B,cAAY,IAAI,IAAI,YAAY,IAAI,CAAC,KAAK,KAAK,CAAC;AAClD;AAKA,SAAS,aAAa,MAAoB;AACxC,QAAM,IAAI,iBAAiB,IAAI;AAC/B,QAAM,KAAK,YAAY,IAAI,CAAC,KAAK,KAAK;AACtC,MAAI,IAAI,GAAG;AAAE,gBAAY,IAAI,GAAG,CAAC;AAAG;AAAA,EAAQ;AAC5C,cAAY,OAAO,CAAC;AACpB,gBAAc,IAAI,CAAC,GAAG,QAAQ;AAC9B,gBAAc,OAAO,CAAC;AACxB;AAEA,eAAe,gBACb,MACA,MACY;AACZ,cAAY,IAAI;AAChB,MAAI;AACF,WAAO,MAAM,KAAK,YAAY,IAAI,CAAC;AAAA,EACrC,UAAE;AACA,iBAAa,IAAI;AAAA,EACnB;AACF;AAGO,IAAM,2BAA2B;AAWxC,SAAS,kBAAkB,GAAW,OAAe,SAEnD;AACA,QAAM,MAAM,IAAI,cAAc,MAAM,MAAM,IAAI,EAAE;AAChD,MAAI,QAAQ,CAAC,KAAK,KAAK,CAAC;AACxB,MAAI,SAAS,UAAU;AACvB,QAAM,MAAM,IAAI,eAAe;AAAA,IAC7B,aAAa;AAAA,IACb,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,UAAU;AAAA,MACR,OAAO,EAAE,OAAO,EAAE;AAAA,MAClB,QAAQ,EAAE,OAAO,IAAIC,OAAM,KAAK,EAAE;AAAA,MAClC,UAAU,EAAE,OAAO,QAAQ;AAAA,IAC7B;AAAA,IACA,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAUd,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUlB,CAAC;AACD,MAAI,SAAS,UAAU;AACvB,QAAM,OAAO,IAAI,KAAK,KAAK,GAAG;AAC9B,OAAK,SAAS,IAAI;AAClB,OAAK,gBAAgB;AACrB,SAAO,EAAE,MAAM,UAAU,IAAI;AAC/B;AAqBA,SAAS,gBAAgB,MAAiB,KAA4C;AACpF,QAAM,IAAI;AAGV,QAAM,IAAI,IAAI,KAAK,IAAI,KAAK,KAAK;AACjC,QAAM,IAAI,IAAI,KAAK,IAAI,KAAK,KAAK;AACjC,QAAM,IAAI,IAAI,KAAK,IAAI,KAAK,KAAK;AACjC,SAAO,CAAC,GAAG,MAAM;AACf,UAAM,IAAI,KAAK,KAAK,IAAI,IAAI,IAAI,CAAC,IAAI;AACrC,QAAI,IAAI;AACR,QAAI,SAAS,SAAS;AAKpB,UAAI,KAAK,IAAI,IAAI,QAAQ,CAAC,IAAI,KAAK,KAAK,IAAI,IAAI,QAAQ,CAAC,IAAI,KACzD,KAAK,KAAK,IAAI,KAAK,QAAQ,CAAC,IAAI;AAAA,IACtC,WAAW,SAAS,UAAU;AAC5B,UAAI,KAAK,IAAI,KAAK,IAAI,IAAI,OAAO,CAAC,CAAC,IAAI,KAAK,KAAK,KAAK,IAAI,IAAI,OAAQ,CAAC,IAAI;AAAA,IAC7E,WAAW,SAAS,SAAS;AAG3B,UAAI,IAAI,IAAI,MAAM,KAAK,KAAK,IAAI,IAAI,QAAQ,CAAC,IAAI,IAAI,KAAK,IAAI,IAAI,OAAO,CAAC,IAAI;AAAA,IAChF;AAEA,WAAO,IAAI,KAAK,IAAI,GAAG,IAAI,IAAI,CAAC;AAAA,EAClC;AACF;AAEA,SAAS,aAAa,QAA0C,OAAqB;AACnF,QAAM,IAAI;AACV,QAAM,MAAM,IAAI,cAAc,IAAI,GAAG,IAAI,GAAG,IAAI,EAAE;AAClD,MAAI,QAAQ,CAAC,KAAK,KAAK,CAAC;AACxB,QAAM,MAAM,IAAI,WAAW;AAC3B,WAAS,IAAI,GAAG,IAAI,IAAI,OAAO,KAAK;AAClC,QAAI,KAAK,GAAG,OAAO,IAAI,KAAK,CAAC,GAAG,IAAI,KAAK,CAAC,CAAC,CAAC;AAAA,EAC9C;AACA,MAAI,qBAAqB;AAMzB,QAAM,SAAS,IAAI,WAAW;AAC9B,QAAM,WAAW,IAAI,aAAa,IAAI,QAAQ,CAAC;AAE/C,QAAM,KAAK,OAAO,KAAK,MAAM,KAAK;AAClC,WAAS,IAAI,GAAG,IAAI,IAAI,OAAO,KAAK;AAClC,UAAM,UAAU,KAAK,IAAI,GAAG,OAAO,KAAK,CAAC,IAAI,KAAK,OAAO,KAAK,CAAC,IAAI,KAAK,OAAO,KAAK,CAAC,IAAI,EAAE;AAC3F,UAAM,IAAI,OAAO,OAAO;AACxB,aAAS,IAAI,CAAC,IAAI;AAClB,aAAS,IAAI,IAAI,CAAC,IAAI;AACtB,aAAS,IAAI,IAAI,CAAC,IAAI;AAAA,EACxB;AACA,MAAI,aAAa,SAAS,IAAI,gBAAgB,UAAU,CAAC,CAAC;AAC1D,MAAI,SAAS,UAAU;AACvB,QAAM,MAAM,IAAIC,mBAAkB,EAAE,OAAO,IAAID,OAAM,KAAK,GAAG,cAAc,KAAK,CAAC;AACjF,MAAI,SAAS,UAAU;AACvB,QAAM,OAAO,IAAI,KAAK,KAAK,GAAG;AAC9B,OAAK,gBAAgB;AACrB,SAAO;AACT;AAaA,SAAS,UAAU,OAAe,OAAe,GAAW,UAAkB,KAE5E;AACA,QAAM,QAAQ,IAAI,MAAM;AACxB,QAAM,MAAM,IAAI,eAAe;AAAA,IAC7B,aAAa;AAAA,IACb,YAAY;AAAA,IACZ,UAAUE;AAAA,IACV,MAAM;AAAA,IACN,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,QAAQ,EAAE,OAAO,IAAIF,OAAM,KAAK,EAAE,GAAG,WAAW,EAAE,OAAO,SAAS,EAAE;AAAA,IACrG,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAMd,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAalB,CAAC;AACD,MAAI,SAAS,UAAU;AAQvB,QAAM,MAAM,IAAI,iBAAiB,GAAG,IAAI,KAAK,IAAI,GAAG,IAAI;AACxD,MAAI,SAAS,UAAU;AACvB,WAAS,IAAI,GAAG,IAAI,OAAO,KAAK;AAC9B,UAAM,IAAI,IAAI,KAAK,KAAK,GAAG;AAC3B,UAAM,IAAI,IAAI,KAAK,IAAI,KAAK,KAAK;AACjC,UAAM,IAAI,KAAK,IAAI,KAAK,IAAI;AAC5B,MAAE,SAAS,IAAI,KAAK,IAAI,CAAC,IAAI,GAAG,IAAI,KAAK,IAAI,CAAC,IAAI,CAAC;AAEnD,QAAI,IAAI,EAAG,GAAE,SAAS,IAAI,KAAK;AAC/B,MAAE,SAAS,IAAI,IAAI,KAAK,IAAI,KAAK;AAEjC,MAAE,SAAS,KAAK,IAAI,KAAK,IAAI,OAAO;AACpC,MAAE,gBAAgB;AAClB,UAAM,IAAI,CAAC;AAAA,EACb;AACA,SAAO,EAAE,OAAO,UAAU,IAAI;AAChC;AAEA,IAAM,qBAAqB;AAC3B,IAAM,iBAAiB,oBAAI,IAA0C;AAerE,SAAS,aAAa,MAAsB;AAC1C,OAAK,SAAS,CAAC,MAAM;AACnB,UAAM,UAAU;AAChB,QAAI,QAAQ,iBAAiB,QAAQ,SAAU,SAAQ,SAAS,QAAQ;AACxE,UAAM,OAAO;AACb,QAAI,KAAK,UAAU,UAAU,QAAS,MAAK,SAAS,QAAQ;AAC5D,UAAM,OAAmB,MAAM,QAAQ,KAAK,QAAQ,IAChD,KAAK,WACL,KAAK,WACL,CAAC,KAAK,QAAQ,IACd,CAAC;AACL,eAAW,KAAK,MAAM;AACpB,UAAI,CAAC,EAAE,UAAU,QAAS;AAM1B,YAAM,MAAO,EAAiC;AAC9C,UAAI,KAAK,UAAU,QAAS,KAAI,QAAQ;AACxC,QAAE,QAAQ;AAAA,IACZ;AAAA,EACF,CAAC;AACH;AAiEA,IAAM,eAAN,MAA4C;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACT;AAAA,EACA,WAAW;AAAA,EACX,aAAuB,CAAC;AAAA,EACxB,cAAc;AAAA,EACd,eAAe;AAAA,EAEN,QAAQ,IAAI,MAAM;AAAA,EAClB;AAAA,EACA,WAAW,IAAIA,OAAM;AAAA,EACrB;AAAA,EACA;AAAA,EACA;AAAA;AAAA;AAAA,EAGT,OAAwB;AAAA,EACxB,UAAU;AAAA,EACV,WAAkC;AAAA,EAClC,aAAuC;AAAA;AAAA,EAEvC,gBAA2D;AAAA,EAC3D,SAAgC;AAAA,EAChC,UAAuB;AAAA,EACvB,eAAe;AAAA;AAAA;AAAA,EAGf,cAAmE;AAAA,EACnE,oBAAoB;AAAA,EACX;AAAA;AAAA,EAET,OAAgC,CAAC;AAAA;AAAA;AAAA;AAAA,EAIjC,WAAqB,CAAC;AAAA;AAAA,EAEtB,aAA0C,CAAC;AAAA;AAAA,EAE3C,aAAuB,CAAC;AAAA;AAAA;AAAA,EAGxB,WAAoC,CAAC;AAAA;AAAA,EAErC,WAA0B,CAAC;AAAA,EAC3B,aAAa;AAAA;AAAA,EAEb,WAAW;AAAA,EACX,UAAU;AAAA,EACV,eAAe;AAAA;AAAA;AAAA,EAGf,YAAY;AAAA,EACZ,UAAU;AAAA,EACV,WAAW;AAAA,EAEX;AAAA,EACA;AAAA,EACA,UAAyB;AAAA,EACzB,SAAS;AAAA,EACT,IAAI;AAAA,EACK,QAAQ,IAAI,aAAa;AAAA,EAElC;AAAA,EACA,QAA6B;AAAA;AAAA;AAAA;AAAA;AAAA,EAK7B,eAAe;AAAA,EACf,oBAAoB;AAAA,EACX;AAAA,EACA;AAAA;AAAA;AAAA,EAGT;AAAA;AAAA;AAAA;AAAA,EAIA,YAAuB;AAAA;AAAA;AAAA,EAGvB,cAAc;AAAA,EACtB,YACE,KACA,OACA,SACA,UAAuB,cACvB;AACA,SAAK,WAAW;AAChB,SAAK,QAAQ;AACb,SAAK,UAAU;AACf,SAAK,UAAU;AACf,SAAK,cAAc,gBAAgB,IAAI,IAAI,KAAK,IAAK,GAAG,QAAQ,KAAK,SAAS;AAC9E,SAAK,YAAY,IAAI,MAAM;AAC3B,SAAK,SAAS,cAAc,KAAK;AACjC,SAAK,IAAI,IAAI;AACb,SAAK,IAAI,IAAI;AAEb,QAAI,IAAI,mBAAmB,mBAAmB;AAC5C,WAAK,SAAS,IAAI;AAClB,WAAK,aAAa;AAAA,IACpB,OAAO;AACL,WAAK,SAAS,SAAS,cAAc,QAAQ;AAC7C,WAAK,OAAO,MAAM,UAAU;AAC5B,UAAI,KAAK,YAAY,KAAK,MAAM;AAChC,WAAK,aAAa;AAAA,IACpB;AACA,SAAK,OAAO,iBAAiB,oBAAoB,CAAC,MAAM,EAAE,eAAe,CAAC;AAE1E,QAAI,gBAAgB,EAAG,MAAK,UAAU,WAAW,SAAS;AAE1D,SAAK,WAAW,IAAI,cAAc;AAAA,MAChC,QAAQ,KAAK;AAAA,MACb,WAAW,KAAK,QAAQ;AAAA,MACxB,SAAS;AAAA,MACT,iBAAiB;AAAA,IACnB,CAAC;AACD,SAAK,SAAS,cAAc,KAAK,GAAG,CAAC;AACrC,SAAK,SAAS,QAAQ,KAAK,GAAG,KAAK,GAAG,KAAK;AAC3C,SAAK,SAAS,mBAAmB;AACjC,SAAK,SAAS,cAAc;AAC5B,SAAK,SAAS,sBAAsB;AAEpC,SAAK,SAAS,IAAI,kBAAkB,IAAI,KAAK,IAAI,KAAK,GAAG,GAAG,IAAI;AAEhE,UAAM,SAAS,OAAO,KAAK,MAAM,UAAU,WAAW,SAAS;AAC/D,SAAK,SAAS,IAAI,MAAM;AACxB,SAAK,MAAM,MAAM,IAAI,IAAI,KAAK,SAAS,OAAO,GAAG,IAAI,GAAG;AACxD,SAAK,MAAM,aAAa,KAAK;AAE7B,UAAM,QAAQ,IAAI;AAAA,MAChB,IAAI,eAAe,KAAK,EAAE;AAAA,MAC1B,IAAIC,mBAAkB,EAAE,OAAO,OAAS,CAAC;AAAA,IAC3C;AACA,UAAM,SAAS,SAAS,UAAU;AAClC,UAAM,SAAS,SAAS,UAAU;AAClC,UAAM,SAAS,IAAI,CAAC,KAAK,KAAK;AAC9B,SAAK,MAAM,IAAI,KAAK;AACpB,SAAK,WAAW,MAAM;AACtB,SAAK,YAAY;AAKjB,UAAM,UAAU,KAAK,QAAQ;AAC7B,UAAM,UAAU,IAAI,IAAI,KAAK,KAAM;AACnC,UAAM,MAAM,IAAI,aAAa,UAAU,CAAC;AACxC,UAAM,KAAK,IAAI,aAAa,UAAU,CAAC;AACvC,aAAS,IAAI,GAAG,IAAI,SAAS,KAAK;AAChC,YAAM,IAAI,QAAQ,KAAK,IAAI,KAAK,KAAK;AACrC,YAAM,IAAI,KAAK,KAAK,QAAQ,KAAK,CAAC,IAAI,OAAO,SAAS;AACtD,UAAI,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI;AAC3B,UAAI,IAAI,IAAI,CAAC,IAAI,OAAO,OAAO,QAAQ,KAAK,KAAK,OAAO,OAAO,OAAO,OAAO;AAC7E,UAAI,IAAI,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI;AAC/B,SAAG,IAAI,CAAC,IAAI,QAAQ,KAAK,IAAI,KAAK,KAAK;AACvC,SAAG,IAAI,IAAI,CAAC,IAAI,QAAQ,KAAK,IAAI,KAAK,KAAK;AAC3C,SAAG,IAAI,IAAI,CAAC,IAAI,QAAQ,KAAK,IAAI,KAAK,KAAK;AAAA,IAC7C;AACA,UAAM,UAAU,IAAI,eAAe;AACnC,YAAQ,aAAa,YAAY,IAAI,gBAAgB,KAAK,CAAC,CAAC;AAC5D,YAAQ,aAAa,UAAU,IAAI,gBAAgB,IAAI,CAAC,CAAC;AACzD,YAAQ,SAAS,UAAU;AAC3B,SAAK,UAAU,IAAI,eAAe;AAAA,MAChC,aAAa;AAAA,MACb,YAAY;AAAA,MACZ,UAAUC;AAAA,MACV,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,QAAQ,EAAE,OAAO,IAAIF,OAAM,SAAS,EAAE,EAAE;AAAA,MACzE,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAiBd,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IASlB,CAAC;AACD,SAAK,QAAQ,SAAS,UAAU;AAChC,SAAK,QAAQ,IAAI,OAAO,SAAS,KAAK,OAAO;AAC7C,SAAK,MAAM,gBAAgB;AAC3B,SAAK,MAAM,UAAU;AACrB,SAAK,MAAM,IAAI,KAAK,KAAK;AAIzB,SAAK,MAAM,IAAI,IAAI,gBAAgB,UAAU,SAAU,GAAG,CAAC;AAE3D,SAAK,UAAU;AAEf,SAAK,SAAS,IAAI;AAClB,QAAI,KAAK,OAAQ,MAAK,YAAY;AAAA,QAC7B,MAAK,MAAM;AAAA,EAClB;AAAA;AAAA,EAIQ,IAAI,KAAqB;AAC/B,UAAM,IAAI,KAAK,OAAO,GAAG;AACzB,WAAO,OAAO,MAAM,WAAW,IAAI,OAAO,KAAK,MAAM,GAAG,GAAG,WAAW,EAAE;AAAA,EAC1E;AAAA;AAAA;AAAA,EAIQ,gBAAgB,QAAgB,aAA2B;AACjE,QAAI,KAAK,QAAS,MAAK,QAAQ,SAAS,IAAI,UAAU,IAAI,SAAS,KAAK;AACxE,QAAI,KAAK,QAAQ;AACf,YAAM,IAAI,eAAe,IAAI,cAAc,KAAK;AAChD,WAAK,OAAO,SAAS,UAAW,QAAQ;AACxC,WAAK,OAAO,UAAU,IAAI;AAAA,IAC5B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,YAAkB;AACxB,UAAM,UAAU,KAAK,IAAI,aAAa;AACtC,UAAM,gBAAgB,KAAK,IAAI,WAAW;AAC1C,UAAM,SAAS,KAAK,IAAI,QAAQ;AAChC,UAAM,cAAc,KAAK,IAAI,aAAa;AAI1C,UAAM,MAAM,GAAG,OAAO,IAAI,aAAa,IAAI,KAAK,QAAQ,SAAS;AACjE,QAAI,QAAQ,KAAK,SAAS;AACxB,WAAK,gBAAgB,QAAQ,WAAW;AACxC;AAAA,IACF;AACA,SAAK,UAAU;AAEf,QAAI,KAAK,MAAM;AACb,WAAK,MAAM,OAAO,KAAK,IAAI;AAC3B,mBAAa,KAAK,IAAI;AACtB,WAAK,OAAO;AACZ,WAAK,WAAW;AAChB,WAAK,aAAa;AAClB,WAAK,SAAS;AACd,WAAK,UAAU;AACf,WAAK,WAAW,CAAC;AAAA,IACnB;AACA,UAAM,SAA4B,cAAc,OAAO;AACvD,UAAM,MAAM,iBAAiB,KAAK,QAAQ,WAAW,MAAM;AAI3D,UAAM,QAAQ,IAAI,MAAM;AAMxB,UAAM,OAAkB,YAAY,SAAS,aAA0B,IAClE,gBACD,OAAO;AACX,QAAI,SAAS,QAAQ;AACnB,YAAM,WAAW,OAAO,KAAK,OAAO,cAAc,OAAO,SAAS,SAAS,KAAK,MAAM,YAAY,WAAW,SAAS;AACtH,YAAM,SAAS,gBAAgB,MAAM,KAAK,SAAS,IAAI,KAAK,QAAS,OAAO,QAAQ,CAAC;AACrF,YAAM,UAAU,aAAa,QAAQ,QAAQ;AAC7C,cAAQ,SAAS,IAAI;AAErB,WAAK,gBAAgB,CAAC,GAAG,MAAM,OAAO,GAAG,CAAC,IAAI,QAAQ,SAAS;AAC/D,YAAM,IAAI,OAAO;AACjB,WAAK,aAAa,QAAQ;AAAA,IAC5B;AAEA,QAAI,SAAS,OAAQ,MAAK,gBAAgB;AAC1C,SAAK,UAAU,UAAU,SAAS;AAClC,QAAI,IAAI,SAAS,OAAO,OAAO;AAC7B,YAAM,IAAI,UAAU,IAAI,SAAS,OAAO,MAAM;AAC9C,YAAM,EAAE,MAAM,SAAS,IAAI,kBAAkB,GAAG,OAAO,MAAM,OAAO,OAAO,MAAM,OAAO;AACxF,YAAM,IAAI,IAAI;AACd,WAAK,WAAW;AAChB,WAAK,UAAU;AACf,WAAK,eAAe,OAAO,MAAM;AAAA,IACnC;AACA,QAAI,IAAI,WAAW,KAAK,OAAO,MAAM;AAKnC,YAAM,WAAW,eAAe,IAAI,cAAc,OAAO,KAAK;AAC9D,YAAM,EAAE,OAAO,IAAI,SAAS,IAAI;AAAA,QAC9B,IAAI;AAAA,QAAU,OAAO,KAAK;AAAA,QAAO,OAAO,KAAK;AAAA,QAAG;AAAA,QAAU,KAAK,SAAS,IAAI,KAAK,IAAK;AAAA,MACxF;AACA,eAAS,UAAU,WAAW;AAC9B,YAAM,IAAI,EAAE;AAGZ,WAAK,WAAW,GAAG,SAAS,IAAI,CAAC,MAAM,CAAC,EAAE,SAAS,GAAG,EAAE,SAAS,CAAC,CAAC;AACnE,WAAK,SAAS;AACd,WAAK,oBAAoB,OAAO,KAAK;AAAA,IACvC;AACA,SAAK,OAAO;AACZ,SAAK,MAAM,IAAI,KAAK;AACpB,SAAK,gBAAgB,QAAQ,WAAW;AACxC,SAAK,cAAc,OAAO,WAAW;AACrC,SAAK,SAAS,KAAK,QAAQ,QAAQ,OAAO;AAAA,EAC5C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgBQ,aACN,KACA,OACA,MACA,SACA,OAIA;AAIA,UAAM,OAAO,KAAK,eACd,WAAW,KAAK,aAAa,SAAS,MAAM,QAAQ,IACpD,WAAW,KAAK,aAAa,MAAM,QAAQ,MAAM,QAAQ;AAC7D,UAAM,IAAI,mBAAmB,KAAK,aAAa,IAAI;AACnD,UAAM,QAAQ,cAAc;AAC5B,UAAM,IAAI,mBAAmB,KAAK,aAAa,OAAO,KAAK;AAC3D,UAAM,IAAI,mBAAmB,KAAK,aAAa,OAAO,KAAK;AAC3D,QAAI,OAAO,EAAE,IAAI,EAAE,GAAG,OAAO,EAAE,IAAI,EAAE;AACrC,QAAI,KAAK,KAAK,MAAM,MAAM,IAAI;AAC9B,QAAI,KAAK,MAAM;AAGb,aAAO,EAAE;AAAI,aAAO,EAAE;AAAI,WAAK,KAAK,MAAM,MAAM,IAAI,KAAK;AAAA,IAC3D;AACA,YAAQ;AAAI,YAAQ;AAIpB,UAAM,OAAO,KAAK,IAAI,GAAG,OAAO,SAAS,IAAI,KAAK;AAClD,UAAM,KAAK,KAAK,MAAM,EAAE,GAAG,EAAE,CAAC;AAC9B,UAAM,KAAK,KAAK,QAAQ,KAAK,IAAI,OAAO,KAAK;AAC7C,WAAO;AAAA,MACL;AAAA,MACA,MAAM;AAAA,MACN,GAAG,EAAE,IAAI;AAAA,MACT,GAAG,KAAK,IAAI,OAAO,OAAO,IAAI,IAAI,KAAK,IAAI,OAAO,OAAO,IAAI,IAAI,EAAE,CAAC,CAAC;AAAA,MACrE,GAAG,EAAE,IAAI;AAAA,MACT;AAAA,MACA;AAAA;AAAA,MAEA,IAAI;AAAA,MACJ,IAAI,CAAC;AAAA,IACP;AAAA,EACF;AAAA;AAAA,EAGQ,aAAqB;AAC3B,UAAM,UAAU,KAAK,YAAY,KAAK,IAAI,GAAG,QAAQ,IAAI;AACzD,WAAO,KAAK,IAAI,SAAS,KAAK,QAAQ,OAAO;AAAA,EAC/C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUQ,YAAkB;AACxB,UAAM,MAAM,KAAK,WAAW;AAC5B,UAAM,SAAS,KAAK,IAAI,SAAS,EAAE,KAAK;AACxC,UAAM,MAAM,KAAK,IAAI,SAAS;AAC9B,UAAM,IAAI,KAAK,IAAI,KAAK,IAAI,GAAG,KAAK,MAAM,KAAK,IAAI,WAAW,CAAC,CAAC,GAAG,GAAG;AACtE,UAAM,WAAW,GAAG,MAAM,KAAS,GAAG,KAAS,CAAC,KAAS,GAAG;AAC5D,QAAI,aAAa,KAAK,aAAc;AACpC,SAAK,eAAe;AAEpB,QAAI,OAAiB,CAAC;AACtB,QAAI,SAAsC,CAAC;AAC3C,QAAI,SAAmB,CAAC;AACxB,SAAK,UAAU;AACf,QAAI,WAAW,IAAI;AACjB,YAAM,SAAS,aAAa,QAAQ,KAAK,OAAO;AAChD,UAAI,OAAO,SAAS,SAAS,KAAK,WAAW,KAAK,WAAW;AAC3D,aAAK,YAAY;AACjB,cAAM,WAAW,OAAO,QAAQ,WAAW,IAAI,8CAAyC;AACxF,gBAAQ,KAAK,0BAA0B,MAAM,MAAM,OAAO,SAAS,KAAK,IAAI,CAAC,GAAG,QAAQ,EAAE;AAAA,MAC5F;AACA,YAAM,QAAQ,mBAAmB,OAAO,SAAS,GAAG;AACpD,aAAO,MAAM,IAAI,CAAC,OAAO,GAAG,GAAG;AAC/B,eAAS,MAAM,IAAI,CAAC,OAAQ,GAAG,QAAQ,YAAY,GAAG,KAAK,IAAI,IAAK;AACpE,eAAS,MAAM,IAAI,CAAC,OAAO,GAAG,KAAK;AACnC,WAAK,UAAU,KAAK,SAAS;AAAA,IAC/B;AACA,QAAI,CAAC,KAAK,SAAS;AAMjB,YAAM,YAAY,KAAK,YAAY,MAAM,KAAK,QAAQ,WAAW,MAAM;AACvE,YAAM,SAAS,aAAa,KAAK,SAAS,SAAS,KAAK,KAAK,SAAS,CAAC,MAAM;AAC7E,YAAM,MAAM,SAAS,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,SAAS,QAAQ,GAAG,CAAC,IAAI;AACxE,aAAO,MAAM,KAAK,EAAE,QAAQ,IAAI,GAAG,MAAM,GAAG;AAAA,IAC9C;AACA,SAAK,WAAW;AAChB,SAAK,aAAa,KAAK,UAAU,SAAS,CAAC;AAC3C,SAAK,aAAa,KAAK,UAAU,SAAS,CAAC;AAC3C,SAAK,WAAW,KAAK,UAAU,SAAS,UAAU,KAAK,KAAK,GAAG;AAC/D,QAAI,KAAK,QAAS,MAAK,SAAS,KAAK,QAAQ,QAAQ;AAAA,QAChD,QAAO,KAAK,SAAS,KAAK,QAAQ;AAEvC,UAAM,UAAoB,CAAC;AAC3B,aAAS,IAAI,GAAG,IAAI,KAAK,IAAI,KAAK,KAAK,QAAQ,KAAK,MAAM,GAAG,KAAK;AAChE,YAAM,IAAI,KAAK,KAAK,CAAC;AACrB,YAAM,UAAU,KAAK,CAAC;AACtB,UAAI,MAAM,YAAY,UAAa,EAAE,QAAQ,UAAU;AACrD,UAAE,OAAO,cAAc;AACvB,aAAK,MAAM,OAAO,EAAE,KAAK;AACzB,qBAAa,EAAE,KAAK;AACpB,aAAK,KAAK,CAAC,IAAI;AAAA,MACjB;AACA,UAAI,YAAY,UAAa,CAAC,KAAK,KAAK,CAAC,EAAG,SAAQ,KAAK,CAAC;AAAA,IAC5D;AACA,SAAK,KAAK,SAAS,KAAK,IAAI,KAAK,KAAK,QAAQ,KAAK,MAAM;AACzD,QAAI,QAAQ,OAAQ,MAAK,KAAK,aAAa,OAAO;AAClD,SAAK,SAAS,KAAK,QAAQ,SAAS,OAAO,KAAK,YAAY,CAAC;AAAA,EAC/D;AAAA;AAAA;AAAA,EAIA,MAAc,aAAa,OAAgC;AACzD,UAAM,MAAM,KAAK;AACjB,QAAI,OAAO;AACX,UAAM,SAAS,YAA2B;AACxC,aAAO,OAAO,MAAM,UAAU,CAAC,KAAK,YAAY,KAAK,YAAY,KAAK;AACpE,cAAM,IAAI,MAAM,MAAM;AACtB,cAAM,UAAU,KAAK,SAAS,CAAC;AAC/B,cAAM,MAAM,MAAM,KAAK,SAAS,SAAS,KAAK,IAAI,WAAW,CAAC;AAC9D,YAAI,KAAK,YAAY,KAAK,YAAY,IAAK;AAC3C,YAAI,CAAC,KAAK,KAAK,CAAC,GAAG;AACjB,eAAK,MAAM,KAAK,GAAG,OAAO;AAC1B,cAAI,CAAC,IAAK,MAAK,UAAU,GAAG,SAAS,GAAG;AAAA,QAC1C;AAAA,MACF;AAAA,IACF;AACA,UAAM,QAAQ,IAAI,MAAM,KAAK,EAAE,QAAQ,gBAAgB,GAAG,MAAM,CAAC;AACjE,QAAI,CAAC,KAAK,YAAY,KAAK,OAAQ,MAAK,YAAY;AAAA,EACtD;AAAA,EAEQ,SAAS,KAAa,YAAY,IAAkC;AAC1E,UAAM,MAAM;AACZ,QAAI,IAAI,eAAe,IAAI,GAAG;AAC9B,QAAI,CAAC,GAAG;AACN,WAAK,YAA0C;AAC7C,YAAI;AAIF,gBAAM,MAAM,OAAO,YAAkC;AACnD,gBAAI,UAAmB,IAAI,MAAM,uBAAuB;AACxD,uBAAW,aAAa,gBAAgB,GAAG,GAAG;AAC5C,oBAAM,MAAM,IAAI,gBAAgB;AAChC,oBAAM,QAAQ,WAAW,MAAM,IAAI,MAAM,GAAG,IAAM;AAClD,kBAAI;AACF,sBAAM,MAAM,MAAM,MAAM,WAAW,EAAE,QAAQ,IAAI,OAAO,CAAC;AACzD,oBAAI,CAAC,IAAI,GAAI,OAAM,IAAI,MAAM,YAAY,IAAI,MAAM,EAAE;AACrD,uBAAO,MAAM,IAAI,YAAY;AAAA,cAC/B,SAAS,GAAG;AACV,0BAAU;AAAA,cACZ,UAAE;AACA,6BAAa,KAAK;AAAA,cACpB;AAAA,YACF;AACA,kBAAM;AAAA,UACR,GAAG;AACH,gBAAM,SAAS,IAAI,WAAW;AAC9B,gBAAM,QAAQ,WAAW,GAAG;AAC5B,gBAAM,OAAO,QACT,MAAM,gBAAgB,WAAW,OAAO,YAAY;AAClD,mBAAO,eAAe,OAAO;AAC7B,mBAAO,OAAO,WAAW,KAAK,EAAE;AAAA,UAClC,CAAC,IACD,MAAM,OAAO,WAAW,KAAK,EAAE;AACnC,gBAAM,QAAQ,KAAK;AACnB,sBAAY,KAAK;AACjB,gBAAM,OAAO,IAAI,KAAK,EAAE,cAAc,KAAK,EAAE,QAAQ,IAAI,QAAQ,CAAC;AAClE,gBAAM,QAAQ,KAAK,IAAI,KAAK;AAC5B,gBAAM,OAAO,YAAY,OAAO,QAAQ,MAAM,GAAG,KAAK;AACtD,gBAAM,MAAM,QACR,OAAO,IACL,CAAC,KAAK,KAAK,IACX,KAAK,KAAK,IACZ,OAAO,IACP,IACA,KAAK;AACT,iBAAO;AAAA,YACL;AAAA,YACA,MAAM,KAAK,WAAW,CAAC,KAAK;AAAA,YAC5B,MAAM,eAAe,KAAK,IAAI,KAAK,GAAG,KAAK,GAAG,KAAK,CAAC,KAAK;AAAA,YACzD;AAAA,YACA;AAAA,UACF;AAAA,QACF,QAAQ;AACN,yBAAe,OAAO,GAAG;AACzB,iBAAO;AAAA,QACT;AAAA,MACF,GAAG;AACH,UAAI,eAAe,QAAQ,oBAAoB;AAC7C,cAAM,SAAS,eAAe,KAAK,EAAE,KAAK,EAAE;AAC5C,YAAI,WAAW,OAAW,gBAAe,OAAO,MAAM;AAAA,MACxD;AACA,qBAAe,IAAI,KAAK,CAAC;AAAA,IAC3B;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,MAAM,KAA0B,OAAe,KAAmB;AACxE,UAAM,MAAM,KAAK,SAAS,IAAI,KAAK,IAAK;AACxC,UAAM,OAAO,gBAAgB,IAAI,KAAK,KAAK,GAAG,QAAQ,KAAK,WAAW,EAAE,kBAAkB,KAAK,YAAY,CAAC;AAC5G,UAAM,QAAQ,IAAI,MAAM;AACxB,QAAI,QAA+B;AACnC,QAAI,OAAwB;AAC5B,QAAI,WAA4B;AAChC,QAAI,eAAe;AAEnB,QAAI,KAAK;AACP,YAAM,OAAO,aAAa,IAAI,KAAK;AACnC,wBAAkB,MAAM,KAAK,SAAS,IAAI,KAAK,QAAS,KAAK,CAAC;AAG9D,mBAAa,MAAM,KAAK,SAAS,IAAI,KAAK,QAAS,KAAK,CAAC;AACzD,WAAK,MAAM,UAAU,IAAI,IAAI;AAC7B,WAAK,SAAS,IAAI,IAAI;AACtB,YAAM,IAAI,IAAI;AACd,iBAAW;AACX,UAAI,IAAI,MAAM;AACZ,gBAAQ,IAAI,eAAe,IAAI;AAC/B,cAAM,WAAW,IAAI,IAAI,EAAE,KAAK;AAChC,uBAAe,IAAI,KAAK;AAAA,MAC1B;AAAA,IACF,OAAO;AACL,YAAM,OAAO,KAAK,SAAS,IACxB,KAAK,QAAS,KAAK,EACnB,KAAK,iBAAiB;AACzB,YAAM,MAAM,IAAIC,mBAAkB,EAAE,OAAO,IAAID,OAAM,IAAI,EAAE,CAAC;AAC5D,UAAI,SAAS,UAAU;AACvB,YAAM,OAAO,IAAI,KAAK,IAAI,eAAe,cAAc,GAAG,IAAI,CAAC,GAAG,GAAG;AACrE,WAAK,SAAS,SAAS,UAAU;AACjC,WAAK,MAAM,IAAI,GAAG,MAAM,GAAG;AAC3B,YAAM,WAAW,IAAI;AAAA,QACnB,IAAI,aAAa,cAAc,MAAM,cAAc,KAAK,CAAC;AAAA,QACzD;AAAA,MACF;AACA,eAAS,SAAS,SAAS,UAAU;AACrC,eAAS,SAAS,IAAI,KAAK,KAAK;AAChC,eAAS,SAAS,IAAI,CAAC,cAAc;AACrC,YAAM,IAAI,MAAM,QAAQ;AACxB,aAAO;AAAA,IACT;AAEA,UAAM,YAAY,KAAK,SAAS,KAAK,MAAM,MAAO,QAAQ,IAAK;AAC/D,UAAM,MAAM,eAAe,SAAS;AAMpC,UAAM,UAAU;AAChB,SAAK,MAAM,IAAI,KAAK;AACpB,SAAK,KAAK,KAAK,IAAI;AAAA,MACjB;AAAA,MACA;AAAA,MACA,SAAS,MAAM,IAAI,MAAM;AAAA,MACzB;AAAA,MACA;AAAA,MACA,MAAM;AAAA,MACN;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AACA,SAAK,SAAS,KAAK,QAAQ,SAAS,OAAO,KAAK,YAAY,CAAC;AAC7D,QAAI,KAAK,MAAO,MAAK,SAAS,KAAK,QAAQ,UAAU;AAAA,EACvD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,UAAU,OAAe,KAAa,KAAmB;AAC/D,eAAW,MAAM;AACf,YAAM,YAA2B;AAC/B,YAAI,KAAK,YAAY,KAAK,YAAY,IAAK;AAC3C,cAAM,MAAM,MAAM,KAAK,SAAS,KAAK,KAAK,IAAI,WAAW,CAAC;AAC1D,YAAI,CAAC,OAAO,KAAK,YAAY,KAAK,YAAY,IAAK;AACnD,cAAM,OAAO,KAAK,KAAK,KAAK;AAC5B,YAAI,CAAC,QAAQ,KAAK,KAAM;AACxB,aAAK,OAAO,cAAc;AAC1B,aAAK,MAAM,OAAO,KAAK,KAAK;AAC5B,qBAAa,KAAK,KAAK;AACvB,aAAK,KAAK,KAAK,IAAI;AACnB,aAAK,MAAM,KAAK,OAAO,GAAG;AAC1B,YAAI,KAAK,OAAQ,MAAK,YAAY;AAAA,MACpC,GAAG;AAAA,IACL,GAAG,IAAM;AAAA,EACX;AAAA;AAAA,EAGQ,cAAsB;AAC5B,QAAI,IAAI;AACR,eAAW,KAAK,KAAK,KAAM,KAAI,EAAG;AAClC,WAAO;AAAA,EACT;AAAA;AAAA,EAIQ,YAAY,GAAiB;AACnC,QAAI,KAAK,MAAO,MAAK,SAAS,YAAY,KAAK,OAAO,KAAK,OAAO,CAAC;AAAA,EACrE;AAAA,EAEQ,IAAI,KAAqB;AAK/B,WAAO,UAAU,KAAK,MAAM,GAAG,GAAG,KAAK,OAAO,GAAG,CAAC;AAAA,EACpD;AAAA,EAEQ,SAAS,GAAiB;AAChC,UAAM,OAAO,IAAI;AACjB,SAAK,YAAY,CAAC;AAClB,UAAM,QAAQ,KAAK,IAAI,WAAW;AAUlC,UAAM,WAAW,KAAK,MAAM;AAC5B,UAAM,UAAU,KAAK,gBAAgB,KAAK,QACtCG,gBAAe,KAAK,OAAO,KAAK,OAAO,aAAa,GAAG;AAAA,MACrD,GAAI,UAAU,QAAQ,SAAY,EAAE,KAAK,SAAS,IAAI,IAAI,CAAC;AAAA,MAC3D,GAAI,UAAU,QAAQ,SAAY,EAAE,KAAK,SAAS,IAAI,IAAI,CAAC;AAAA,IAC7D,CAAC,IAAI,MACL,OAAO;AAEX,SAAK,UAAU;AAGf,UAAM,WAAW;AAAA,MACf,KAAK;AAAA,MAAO,KAAK;AAAA,MAAO;AAAA,MAAc;AAAA,MAAG,KAAK,IAAI,YAAY;AAAA,MAAG,KAAK;AAAA,IACxE;AACA,UAAM,KAAK,UAAU,SAAS,KAAK,IAAI,eAAe,IAAI,QAAQ;AAClE,UAAM,KAAK,UAAU,SAAS,KAAK,IAAI,iBAAiB,CAAC;AACzD,UAAM,OAAO,KAAK,IAAI,gBAAgB;AACtC,SAAK,OAAO,SAAS;AAAA,MACnB,KAAK,IAAI,EAAE,IAAI,KAAK,IAAI,EAAE,IAAI;AAAA,MAC9B,KAAK,IAAI,IAAI,KAAK,KAAK,IAAI,EAAE,IAAI,IAAI;AAAA,MACrC,KAAK,IAAI,EAAE,IAAI,KAAK,IAAI,EAAE,IAAI;AAAA,IAChC;AACA,SAAK,OAAO,OAAO,GAAG,IAAI,CAAC;AAG3B,UAAM,SAAS;AAAA,MACb,KAAK,OAAO,YAAY,KAAK,aAAa,OAAO,KAAK,MAAM,UAAU,WAAW;AAAA,IACnF;AACA,SAAK,SAAS,IAAI,MAAM;AACxB,IAAC,KAAK,MAAM,IAAY,MAAM,KAAK,KAAK,QAAQ;AAIhD,UAAM,MAAM,KAAK,MAAM;AACvB,QAAI,OAAO,KAAK,IAAI,SAAS;AAC7B,QAAI,MAAM,KAAK,IAAI,KAAK,IAAI,QAAQ,GAAG,IAAI,OAAO,EAAE;AACpD,UAAM,WAAW,OAAO,KAAK,OAAO,cAAc,KAAK,aAAa,SAAS,KAAK,MAAM,YAAY,WAAW,SAAS;AACxH,SAAK,SAAS,MAAM,IAAI,QAAQ;AAGhC,SAAK,YAAY,MAAM,IAAI,QAAQ;AACnC,UAAM,UAAU,KAAK,IAAI,aAAa;AACtC,UAAM,SAAS,KAAK,MAAM,UAAU,KAAK,QAAQ,OAAO;AACxD,SAAK,MAAM,UAAU,SAAS;AAC9B,QAAI,SAAS,GAAG;AACd,WAAK,MAAM,SAAS,aAAa,GAAG,MAAM;AAC1C,WAAK,QAAQ,SAAS,MAAO,QAAQ;AACrC,MAAC,KAAK,QAAQ,SAAS,OAAQ,MAAgB;AAAA,QAC7C,OAAO,KAAK,OAAO,aAAa,KAAK,aAAa,QAAQ,KAAK,MAAM,WAAW,WAAW,SAAS;AAAA,MACtG;AAAA,IACF;AACA,SAAK,SAAS,KAAK,QAAQ,UAAU,OAAO,MAAM;AAIlD,SAAK,UAAU;AACf,QAAI,KAAK,SAAU,MAAK,SAAS,SAAS,MAAO,QAAQ;AACzD,QAAI,KAAK,OAAQ,MAAK,OAAO,SAAS,MAAO,QAAQ;AAErD,UAAM,iBAAiB,KAAK,IAAI,WAAW;AAI3C,UAAM,WAAW,mBAAmB;AACpC,UAAM,aAAa,YAAY,cAAc;AAC7C,UAAM,WAAW,KAAK,IAAI,cAAc;AACxC,UAAM,SAAS,KAAK,IAAI,YAAY;AACpC,UAAM,OAAO,KAAK,IAAI,WAAW;AACjC,UAAM,QAAQ,KAAK;AACnB,UAAM,UAAU,iBAAiB,MAAM,KAAK,IAAI,kBAAkB,CAAC;AAEnE,UAAM,OAAO,eAAe,KAAK,IAAI,UAAU,CAAC;AAChD,UAAM,UAAU,KAAK,IAAI,cAAc;AACvC,UAAM,eAAe,KAAK,IAAI,mBAAmB;AAIjD,UAAM,WAAW,KAAK,IAAI,WAAW;AACrC,UAAM,QAAS,YAAkC,SAAS,QAAQ,IAC7D,WACD;AAUJ,UAAM,QAAQ,KAAK,IAAI,eAAe,IAAI,KAAK,IAAI,YAAY,IAAI;AACnE,UAAM,YAAY,UAAU,cACvB,KAAK,KAAM,QAAQ,KAAK,cAAc,OAAO,MAAO,GAAG,IAAI;AAChE,QAAI,UAAU,KAAK,aAAa,WAAW;AACzC,WAAK,YAAY;AACjB,WAAK,cAAc;AACnB,YAAM,WAAW,EAAE,kBAAkB,KAAK,YAAY;AACtD,WAAK,cAAc,gBAAgB,KAAK,SAAS,IAAI,KAAK,IAAK,GAAG,QAAQ,OAAO,QAAQ;AACzF,YAAM,MAAM,KAAK,SAAS,IAAI,KAAK,IAAK;AACxC,iBAAW,KAAK,KAAK,MAAM;AACzB,YAAI,EAAG,GAAE,OAAO,gBAAgB,IAAI,KAAK,EAAE,KAAK,GAAG,QAAQ,OAAO,QAAQ;AAAA,MAC5E;AAAA,IACF;AAKA,UAAM,UAAU,KAAK,UACjB,KAAK,SAAS,SACd,KAAK,IAAI,KAAK,MAAM,KAAK,IAAI,WAAW,CAAC,GAAG,KAAK,QAAQ,OAAO;AAIpE,UAAM,YAAY,KAAK,IAAI,gBAAgB;AAC3C,UAAM,SAAU,iBAAuC,SAAS,SAAS,IACpE,YACD;AAKJ,UAAM,UAAU,CAAC,MACf,KAAK,WAAW,CAAC,MAAM,WAAW,aAAa,KAAK,WAAW,CAAC,CAAC,IAAI;AACvE,UAAM,gBAAgB,oBAAI,IAAoB;AAC9C,aAAS,IAAI,GAAG,IAAI,SAAS,KAAK;AAChC,UAAI,QAAQ,CAAC,EAAE,UAAW,eAAc,IAAI,GAAG,cAAc,IAAI;AAAA,IACnE;AACA,UAAM,SAAS,cAAc;AAC7B,UAAM,iBAAiB,SAAS,IAAI,QAAQ,cAAc,KAAK,EAAE,KAAK,EAAE,KAAe,IAAI;AAC3F,UAAM,UAAU,SAAS,IAAI,gBAAgB,QAAQ,UAAU,MAAM,IAAI;AAMzE,UAAM,SAAS,UACX,UAAU,SAAS,SAAS,EAAE,QAAQ,OAAO,OAAO,OAAO,MAAM,QAAQ,OAAO,OAAO,CAAC,IACxF;AACJ,UAAM,SAAS,UACX,EAAE,MAAM,QAAQ,OAAO,QAAQ,IAAI,QAAQ,KAAK,QAAQ,MAAM,QAAQ,OAAO,QAAQ,OAAO,QAAQ,QAAQ,OAAO,IACnH;AACJ,UAAM,UAAU,UAAU,iBAAiB,KAAK,aAAa,QAAQ,gBAAgB,MAAM,SAAS,KAAK,IAAI;AAK7G,QAAI,SAA6B;AACjC,QAAI,aAAa;AACjB,QAAI,cAAkC;AAKtC,QAAI,WAA4C;AAChD,UAAM,SAAwB,CAAC;AAC/B,eAAW,KAAK,KAAK,MAAM;AACzB,UAAI,CAAC,EAAG;AACR,QAAE,MAAM,UAAU,EAAE,QAAQ;AAC5B,UAAI,CAAC,EAAE,MAAM,QAAS;AACtB,YAAM,QAAQ,QAAQ,EAAE,KAAK;AAC7B,YAAM,OAAO,cAAc,IAAI,EAAE,KAAK,KAAK,EAAE;AAC7C,YAAM,OAAO,MAAM,QAAQ;AAE3B,UAAI,YAA2B;AAG/B,UAAI,cAAc;AAIlB,YAAM,OAAO,cAAc,EAAE,OAAO,QAAQ;AAC5C,YAAM,aAAa,MAAM,WAAW,KAAK;AAWzC,YAAM,SAAS,KAAK,eAChB,WAAW,EAAE,MAAM,SAAS,UAAU,IACtC,WAAW,EAAE,MAAM,MAAM,QAAQ,UAAU;AAK/C,YAAM,SAAS,eAAe,OAAO,EAAE,OAAO,QAAQ,IAAI,EAAE,KAAK;AAOjE,UAAI,YAA2B;AAC/B,UAAI,MAAM,cAAc,MAAM,WAAW;AACvC,cAAM,QAAQ,cAAc,MAAM,QAAQ,UAAU,QAAW,MAAM;AACrE,oBAAY,KAAK,MAAM,MAAM,MAAM,MAAM,IAAI,MAAM,IAAI,IAAI;AAAA,MAC7D,WAAW,MAAM,YAAY;AAG3B,cAAM,OAAO,mBAAmB,EAAE,MAAM,SAAS,SAAS,MAAM,MAAM;AACtE,YAAI,CAAC,UAAU;AACb,qBAAW,EAAE,GAAG,KAAK,GAAG,GAAG,KAAK,EAAE;AAClC,sBAAY;AAAA,QACd,OAAO;AACL,sBAAY,KAAK,MAAM,KAAK,IAAI,SAAS,GAAG,KAAK,IAAI,SAAS,CAAC,IAAI;AAAA,QACrE;AAAA,MACF;AACA,YAAM,MAAM,WAAW,MAAM,EAAE,OAAO,MAAM,SAAS,cAAc,SAAS;AAC5E,YAAM,IAAI,SAAS,SAAS,MAAM,SAAS,IAAI,QAAQ;AAMvD,UAAI,OAAO;AACX,UAAI;AACJ,UAAI,MAAM,WAAW;AAenB,cAAM,OAAO,cAAc,MAAM,QAAQ,UAAU,QAAW,MAAM;AACpE,aAAK,QAAQ;AACb,aAAK,MAAM;AACX,aAAK,QAAQ;AAIb,cAAM,WAAW,KAAK,OAAO,IAAI,YAAY;AAC7C,cAAM,KAAK,WAAW,KAAK;AAAA,UACzB,UAAU,gBAAgB,QAAQ,UAAU,MAAM;AAAA,UAAG,kBAAkB;AAAA,UAAO;AAAA,UAAM;AAAA,UAAS;AAAA,QAC/F;AACA,eAAO,GAAG;AAIV,iBAAS;AAAA,UACP,MAAM,KAAK;AAAA,UAAa;AAAA,UAAO,GAAG,GAAG,QAAQ,QAAQ,QAAQ;AAAA,UAAI,QAAQ,GAAG;AAAA,UAC5E,OAAO,KAAK;AAAA,UAAO,OAAO;AAAA,UAAG,OAAO;AAAA,QACtC;AACA,qBAAa;AACb,sBAAc;AAKd,cAAM,MAAM,KAAK,IAAI,KAAK,KAAK,IAAI,OAAO;AAC1C,cAAM,MAAM,KAAK,IAAI,GAAG,GAAG,MAAM,KAAK,IAAI,GAAG;AAC7C,eAAO;AAAA,UACL,GAAG,GAAG;AAAA,UACN,GAAG,GAAG,IAAI,GAAG,KAAK,KAAK,OAAO,GAAG,OAAO;AAAA,UACxC,GAAG,GAAG,IAAI,KAAK;AAAA,UACf,GAAG,GAAG,IAAI,GAAG,KAAK,KAAK,OAAO,GAAG,OAAO;AAAA,UACxC,IAAI,GAAG,OAAO,MAAM,GAAG,OAAO;AAAA,UAC9B,IAAI,GAAG,OAAO,MAAM,GAAG,OAAO;AAAA,QAChC;AAAA,MACF,OAAO;AAIL,YAAI,SAAS,OAAQ,eAAc;AACnC,cAAM,MAAM,SAAS,SAAS,cAAc,SAAS,YAAY,SAAS,UAAU,SAAS;AAC7F,YAAI,IAAK,aAAY,IAAI;AACzB,YAAI,cAAc;AAClB,YAAI,KAAK;AAIP,cAAI,MAAM;AACV,cAAI,SAAS,UAAU;AACrB,kBAAM,cAAc,OAAO,aAAa;AAAA,UAC1C,WAAW,SAAS,SAAS;AAC3B,kBAAM,IAAI,MAAM,MAAM,KAAK,IAAI,OAAO,MAAM,KAAK,KAAK;AACtD,kBAAM,eAAe,MAAM,MAAM;AACjC,2BAAe,IAAI,KAAK;AAAA,UAC1B;AACA,iBAAO,mBAAmB,IAAI,MAAM,IAAI,IAAI,GAAG;AAC/C,iBAAO,IAAI,SAAS;AACpB,gBAAM,KAAK,KAAK,MAAM,KAAK,IAAI,KAAK,EAAE,KAAK;AAC3C,gBAAM,MAAM,KAAK,KAAK,IAAI,MAAM,CAAC,KAAK,KAAK;AAC3C,cAAI,KAAK,IAAI,OAAO,KAAK,IAAI,OAAO,KAAK;AACzC,cAAI,SAAS,QAAQ;AAEnB,kBAAM,KAAK,OAAO,MAAM,IAAI,QAAQ,KAAK;AACzC,kBAAM,IAAI,cAAc;AACxB,kBAAM,MAAM,IAAI,KAAK,IAAI,EAAE;AAC3B,kBAAM,MAAM,IAAI,KAAK,IAAI,EAAE;AAC3B,iBAAK,IAAI,MAAM,KAAK,IAAI,EAAE;AAC1B,0BAAc;AAAA,UAChB,OAAO;AAEL,kBAAM,OAAO,SAAS,EAAE,OAAO,EAAE,IAAI,OAAO,eAAe,SAAS,WAAW,MAAM;AACrF,kBAAM,MAAM;AACZ,kBAAM,MAAM;AACZ,0BAAc;AAAA,UAChB;AACA,iBAAO,EAAE,GAAG,MAAM,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,IAAI,GAAG;AAAA,QACnE,OAAO;AACL,iBAAO,mBAAmB,EAAE,MAAM,CAAC;AAKnC,cAAI,QAAQ,GAAG,QAAQ;AACvB,cAAI,OAAO,KAAK,MAAM,SAAS,GAAG;AAChC,kBAAM,OAAO,MAAM,KAAK,MAAM,SAAS,EAAE,OAAO,EAAE,IAAI,MAAM,MAAM,IAAI,MAAM,MAAM;AAClF,kBAAM,IAAI,OAAO,OAAO,MAAM,MAAM,SAAS,EAAE,OAAO,EAAE;AAIxD,kBAAM,MAAM,KAAK,CAAC,IAAI,KAAK,KAAK;AAChC,kBAAM,MAAM,KAAK,CAAC,IAAI,KAAK,KAAK;AAChC,kBAAM,KAAK,KAAK,MAAM,KAAK,IAAI,IAAI,KAAK,IAAI,EAAE;AAC9C,kBAAM,KAAK,KAAK,OAAO,SAAS,OAAO,SAAS,KAAK;AACrD,qBAAS,KAAK,IAAI,MAAM,KAAK,KAAK;AAClC,qBAAS,KAAK,IAAI,MAAM,KAAK,KAAK;AAAA,UACpC;AACA,gBAAM,KAAkB,EAAE,MAAM,EAAE,MAAM,OAAO,GAAG,QAAQ,OAAO,KAAK,OAAO,OAAO,MAAM;AAC1F,cAAI,KAAK;AACT,cAAI,SAAS,QAAQ;AAGnB,kBAAM,KAAK,OAAO,MAAM,KAAK;AAC7B,kBAAM,IAAI,cAAc;AACxB,kBAAM,KAAK,KAAK,MAAM,KAAK,IAAI,KAAK,EAAE,KAAK;AAC3C,qBAAU,KAAK,KAAK,KAAM,IAAI,KAAK,IAAI,EAAE;AACzC,qBAAU,CAAC,KAAK,KAAK,KAAM,IAAI,KAAK,IAAI,EAAE;AAC1C,iBAAK,IAAI,MAAM,KAAK,IAAI,EAAE;AAC1B,0BAAc;AAAA,UAChB,OAAO;AACL,qBAAS;AACT,yBAAa;AAAA,UACf;AAEA,gBAAM,OAAO,SAAS,OAAO,MAAM,KAAK,KAAK;AAC7C,cAAI,KAAK,KAAK,IAAI,KAAK,KAAK;AAC5B,cAAI,SAAS,GAAG;AACd,kBAAM,KAAK,KAAK,IAAI,IAAI,GAAG,KAAK,KAAK,IAAI,IAAI;AAC7C,iBAAK,KAAK,KAAK,KAAK,KAAK,KAAK;AAC9B,iBAAK,KAAK,KAAK,KAAK,KAAK,KAAK;AAAA,UAChC;AACA,iBAAO,EAAE,GAAG,MAAM,GAAG,KAAK,IAAI,OAAO,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,IAAI,OAAO,IAAI,GAAG;AAAA,QACjF;AACA,sBAAc;AAAA,MAChB;AAIA,UAAI,IAAI,KAAK;AACb,UAAI,MAAM,SAAS,GAAG;AACpB,aAAK,KAAK,IAAI,OAAO,MAAM,QAAQ,KAAK,KAAK,IAAI,KAAK,KAAK,IAAI,MAAM;AAAA,MACvE;AAIA,YAAM,OAAO,UAAU,UAAU,IAAI;AACrC,UAAI,MAAM;AACR,YAAI,KAAK,OAAO,QAAQ,OAAO,OAAO,OAAO,QAAQ,KAAK;AAC1D,YAAI,KAAK,OAAO,QAAQ,OAAO,OAAO,OAAO,QAAQ,KAAK;AAM1D,YAAI,UAAU,SAAS,aAAa,KAAK,SAAS;AAChD,gBAAM,SAAS,KAAK,IAAI,QAAQ;AAChC,gBAAM,KAAK,UAAU,IAAI,SAAS,KAAK;AACvC,gBAAM,QAAQ,KAAK,IAAI,KAAK,cAAc,KAAK,OAAO,OAAO,EAAE;AAC/D,cAAI,QAAQ,IAAI;AACd,iBAAK,SAAS,KAAK;AACnB,iBAAK;AAAA,UACP;AAAA,QACF;AACA,YAAI,KAAK,IAAI,IAAI,KAAK,IAAI,IAAI,CAAC,CAAC;AAAA,MAClC;AAYA,YAAM,MAAM,OAAO,IAAI,KAAK,OACvB,IAAI,UAAU,IAAI,KAAK,IAAI,IAAI,KAAK,KAAK,KAAK,MAAM,KAAK,IAAI,KAAK,EAAE,KAAK,KAAK;AACnF,UAAI,KAAK,KAAK,GAAG,KAAK,KAAK;AAC3B,UAAI,IAAI,SAAS,KAAK,IAAI,OAAO,GAAG;AAOlC,cAAM,YAAY,MAAM,YAAY,MAAM;AAC1C,cAAM,MAAM,KAAK,MAAM,KAAK,IAAI,KAAK,EAAE,KAAK;AAC5C,cAAO,KAAK,KAAK,MAAO,IAAI,OAAO,cAAc;AACjD,cAAO,CAAC,KAAK,KAAK,MAAO,IAAI,OAAO,cAAc;AAClD,YAAI,KAAK,IAAI,OAAO,OAAO,IAAI,KAAK,IAAI,OAAO,MAAM,IAAI,IAAI,KAAK,cAAc,SAAS,CAAC;AAE1F,cAAM,KAAK,KAAK,MAAM,IAAI,EAAE;AAC5B,YAAI,KAAK,OAAO,QAAQ;AACtB,gBAAM,OAAO,SAAS;AACtB,gBAAM,OAAO,SAAS;AAAA,QACxB;AAAA,MACF;AAMA,UAAI,KAAK,eAAe;AACtB,cAAM,QAAQ,KAAK,cAAc,IAAI,EAAE,IAAI,cAAc;AACzD,YAAI,IAAI,MAAO,KAAI,KAAK,IAAI,OAAO,MAAM,KAAK;AAAA,MAChD;AACA,QAAE,MAAM,SAAS,IAAI,IAAI,GAAG,EAAE;AAC9B,QAAE,MAAM,OAAO,KAAK,KAAK,IAAI,IAAI,IAAI,KAAK,KAAK,EAAE;AACjD,QAAE,MAAM,QAAQ,KAAK,IAAI;AAEzB,YAAM,UAAU,IAAI,KAAK,IAAI,OAAO,MAAM,EAAE,KAAK,IAAI;AACrD,QAAE,MAAM,MAAM,UAAU,EAAE,YAAY,UAAU,KAAK,QAAQ;AAM7D,UAAI,EAAE,QAAQ,CAAC,EAAE,OAAO;AAItB,cAAM,IAAI,KAAK,IAAI,KAAK,UAAU,IAAI,SAAS,eAAe,GAAG;AACjE,UAAE,KAAK,SAAS,IAAI,EAAE,UAAU,KAAK,IAAI,OAAO,OAAO,KAAK,KAAK,IAAI,OAAO;AAAA,MAC9E;AAEA,UAAI,EAAE,SAAS,EAAE,eAAe,GAAG;AACjC,UAAE,MAAM;AAAA,WACH,OAAO,QAAS,EAAE,eAAgB,EAAE,gBAAgB,EAAE;AAAA,QAC3D;AAAA,MACF,WAAW,EAAE,MAAM;AAEjB,UAAE,KAAK,SAAS,IAAI,KAAK,IAAI,UAAU,IAAI,EAAE,KAAK,IAAI;AAAA,MACxD;AAEA,aAAO,KAAK;AAAA,QACV,OAAO,EAAE;AAAA,QACT,KAAK,EAAE;AAAA,QACP,OAAO,KAAK,WAAW,EAAE,KAAK,KAAK;AAAA,QACnC,OAAO,MAAM;AAAA,QACb,MAAM,UAAU;AAAA,QAChB;AAAA,QACA,MAAM,MAAM,YAAY,OAAO;AAAA,QAC/B,GAAG,KAAK,MAAM,KAAK,EAAE,IAAI;AAAA,QACzB,GAAG,KAAK,MAAM,IAAI,EAAE,IAAI;AAAA,QACxB,GAAG,KAAK,MAAM,KAAK,EAAE,IAAI;AAAA,QACzB,SAAS,KAAK,OAAQ,KAAK,MAAM,KAAK,IAAI,KAAK,EAAE,IAAI,MAAO,KAAK,KAAK,OAAO,GAAG;AAAA,QAChF,aAAa,KAAK,IAAI,IAAI,IAAI,IAAI,QAAQ,KAAK,IAAI,IAAI,EAAE,IAAI,QAAQ,IAAI,SAAS,QAAQ,KAAK,IAAI,IAAI,KAAK,IAAI;AAAA,MAClH,CAAC;AAAA,IACH;AACA,SAAK,WAAW;AAChB,SAAK,aAAa;AAClB,SAAK,WAAW;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,UAA0C;AAExC,QAAI,CAAC,KAAK,SAAU,QAAO;AAC3B,UAAM,OAAO,KAAK;AAClB,UAAM,UAAkC,CAAC;AACzC,eAAW,KAAK,KAAM,SAAQ,EAAE,KAAK,KAAK,QAAQ,EAAE,KAAK,KAAK,KAAK;AACnE,UAAM,cAAc,KAAK,OAAO,CAAC,MAAM,EAAE,WAAW,EAAE;AACtD,UAAM,KAAK,KAAK,SAAS,KAAK,OAAO,CAAC,GAAG,MAAM,IAAI,EAAE,GAAG,CAAC,IAAI,KAAK,SAAS;AAC3E,UAAM,KAAK,KAAK,SAAS,KAAK,OAAO,CAAC,GAAG,MAAM,IAAI,EAAE,GAAG,CAAC,IAAI,KAAK,SAAS;AAC3E,UAAM,KAAK,KAAK,SAAS,KAAK,OAAO,CAAC,GAAG,MAAM,IAAI,EAAE,GAAG,CAAC,IAAI,KAAK,SAAS;AAC3E,UAAM,SAAS,KAAK,SAChB,KAAK,KAAK,KAAK,OAAO,CAAC,GAAG,MAAM,KAAK,EAAE,IAAI,OAAO,KAAK,EAAE,IAAI,OAAO,KAAK,EAAE,IAAI,OAAO,GAAG,CAAC,IAAI,KAAK,MAAM,IACzG;AACJ,WAAO;AAAA,MACL,OAAO;AAAA,MACP,GAAG,KAAK;AAAA,MACR,QAAQ;AAAA,QACN,SAAS,KAAK,IAAI,eAAe;AAAA,QACjC,WAAW,KAAK,IAAI,iBAAiB;AAAA,QACrC,UAAU,KAAK,IAAI,gBAAgB;AAAA,QACnC,YAAY,KAAK,IAAI,YAAY;AAAA,MACnC;AAAA,MACA,MAAM;AAAA,QACJ,aAAa,KAAK,IAAI,aAAa;AAAA,QACnC,WAAW,KAAK,IAAI,WAAW;AAAA,QAC/B,QAAQ,KAAK,UAAW,KAAK,IAAI,QAAQ,KAAK,IAAI,KAAK,IAAI,QAAQ,IAAI,KAAK,eAAgB;AAAA,QAC5F,aAAa,KAAK,SAAU,KAAK,IAAI,aAAa,KAAK,IAAI,KAAK,IAAI,aAAa,IAAI,KAAK,oBAAqB;AAAA,QAC/G,UAAU,KAAK,SAAS;AAAA,MAC1B;AAAA,MACA,MAAM;AAAA,QACJ,SAAS,KAAK,IAAI,SAAS;AAAA,QAC3B,SAAS,KAAK;AAAA,QACd,QAAQ,KAAK,SAAS;AAAA,QACtB,QAAQ,KAAK,YAAY;AAAA,QACzB,SAAS,KAAK;AAAA,QACd,KAAK,KAAK,QAAQ;AAAA,QAClB;AAAA,QACA,WAAW,KAAK,IAAI,WAAW;AAAA,QAC/B,WAAW,KAAK;AAAA,QAChB,gBAAgB,KAAK,IAAI,gBAAgB;AAAA,MAC3C;AAAA,MACA,UAAU;AAAA,QACR,MAAM,KAAK,IAAI,UAAU;AAAA,QACzB,MAAM,KAAK,IAAI,cAAc;AAAA,QAC7B,WAAW,KAAK,IAAI,mBAAmB;AAAA,QACvC,QAAQ;AAAA,MACV;AAAA,MACA,QAAQ;AAAA,QACN,UAAU,EAAE,GAAG,KAAK,MAAM,KAAK,EAAE,IAAI,IAAI,GAAG,KAAK,MAAM,KAAK,EAAE,IAAI,IAAI,GAAG,KAAK,MAAM,KAAK,EAAE,IAAI,GAAG;AAAA,QAClG,QAAQ,KAAK,MAAM,SAAS,EAAE,IAAI;AAAA,QAClC,WAAW,KAAK,IAAI,WAAW;AAAA,QAC/B,kBAAkB,KAAK,IAAI,kBAAkB;AAAA,MAC/C;AAAA,MACA,SAAS,EAAE,SAAS,KAAK,QAAQ,SAAS,WAAW,KAAK,QAAQ,WAAW,UAAU,KAAK,MAAM,KAAK,WAAW,GAAG,IAAI,IAAI;AAAA,MAC7H;AAAA,IACF;AAAA,EACF;AAAA;AAAA,EAIQ,cAAoB;AAC1B,QAAI,KAAK,SAAS,WAAW,GAAG,gBAAgB,EAAG;AACnD,SAAK,SAAS,OAAO,KAAK,OAAO,KAAK,MAAM;AAAA,EAC9C;AAAA,EAEQ,QAAc;AACpB,QAAI,KAAK,YAAY,QAAQ,OAAO,0BAA0B;AAC5D;AACF,SAAK,MAAM,OAAO;AAClB,SAAK,UAAU,sBAAsB,CAAC,QAAQ,KAAK,KAAK,GAAG,CAAC;AAAA,EAC9D;AAAA,EAEQ,OAAa;AACnB,QAAI,KAAK,YAAY,MAAM;AACzB,2BAAqB,KAAK,OAAO;AACjC,WAAK,UAAU;AAAA,IACjB;AACA,SAAK,MAAM,MAAM;AAAA,EACnB;AAAA,EAEQ,KAAK,KAAmB;AAC9B,SAAK,UAAU,sBAAsB,CAAC,MAAM,KAAK,KAAK,CAAC,CAAC;AACxD,SAAK,YAAY,GAAG;AACpB,SAAK,IAAI,KAAK,MAAM,OAAO,GAAG;AAC9B,SAAK,SAAS,KAAK,CAAC;AACpB,SAAK,YAAY;AAAA,EACnB;AAAA,EAEQ,KAAa;AACnB,WAAO,KAAK;AAAA,MACV;AAAA,MACA,oBAAoB,KAAK,GAAG,KAAK,GAAG,KAAK,SAAS,KAAK,KAAK,OAAO,IACjE,KAAK;AAAA,IACT;AAAA,EACF;AAAA,EAEQ,UAAgB;AACtB,UAAM,KAAK,KAAK,GAAG;AACnB,SAAK,SAAS,cAAc,EAAE;AAC9B,SAAK,SAAS,QAAQ,KAAK,GAAG,KAAK,GAAG,KAAK;AAAA,EAC7C;AAAA,EAEQ,YAAY,KAAmB;AACrC,QAAI,KAAK,cAAc,GAAG;AACxB,YAAM,KAAK,MAAM,KAAK;AACtB,UAAI,KAAK,IAAK,MAAK,WAAW,KAAK,EAAE;AAAA,IACvC;AACA,SAAK,cAAc;AACnB,QAAI,MAAM,KAAK,eAAe,OAAQ,KAAK,WAAW,SAAS,GAAI;AACnE,SAAK,eAAe;AACpB,UAAM,SAAS,CAAC,GAAG,KAAK,UAAU,EAAE,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC;AACxD,UAAM,SAAS,OAAO,OAAO,UAAU,CAAC;AACxC,SAAK,aAAa,CAAC;AACnB,QAAI,SAAS,MAAM,KAAK,WAAW,MAAM;AACvC,WAAK,YAAY;AACjB,WAAK,QAAQ;AAAA,IACf,WAAW,SAAS,MAAM,KAAK,WAAW,GAAG;AAC3C,WAAK,WAAW,KAAK,IAAI,GAAG,KAAK,WAAW,IAAI;AAChD,WAAK,QAAQ;AAAA,IACf;AAAA,EACF;AAAA,EAEQ,cAAoB;AAC1B,SAAK,SAAS,KAAK,CAAC;AACpB,SAAK,YAAY;AAAA,EACnB;AAAA;AAAA,EAIA,UAAU,QAAuB;AAC/B,QAAI,KAAK,WAAW,OAAQ;AAC5B,SAAK,SAAS;AACd,QAAI,QAAQ;AACV,WAAK,KAAK;AACV,WAAK,YAAY;AAAA,IACnB,OAAO;AACL,WAAK,MAAM;AAAA,IACb;AAAA,EACF;AAAA,EAEA,OAAO,OAAe,QAAgB,KAAoB;AACxD,SAAK,IAAI;AACT,SAAK,IAAI;AACT,QAAI,QAAQ,OAAW,MAAK,SAAS,MAAM;AAC3C,SAAK,QAAQ;AACb,SAAK,OAAO,SAAS,QAAQ;AAC7B,SAAK,OAAO,uBAAuB;AACnC,QAAI,KAAK,OAAQ,MAAK,YAAY;AAAA,EACpC;AAAA,EAEA,WAAW,OAA2B;AACpC,SAAK,QAAQ;AACb,SAAK,eAAe,MAAM,OAAO,KAAK,CAAC,MAAM,EAAE,SAAS,WAAW;AACnE,SAAK,oBAAoB,MAAM,OAAO,KAAK,CAAC,MAAM,EAAE,SAAS,YAAY;AACzE,QAAI,KAAK,OAAQ,MAAK,YAAY;AAAA,EACpC;AAAA,EAEA,YAAY,GAAW,OAAqB;AAC1C,SAAK,IAAI;AACT,SAAK,MAAM,KAAK,CAAC;AACjB,SAAK,SAAS,CAAC;AACf,SAAK,YAAY;AAAA,EACnB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,UAAuC;AAC3C,QAAI,KAAK,YAAY,KAAK,SAAS,WAAW,GAAG,gBAAgB,EAAG,QAAO;AAC3E,SAAK,YAAY;AACjB,QAAI;AAGF,aAAO,MAAM,kBAAkB,KAAK,MAAM;AAAA,IAC5C,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,cAA4B;AAC1B,WAAO;AAAA,MACL;AAAA,QACE,IAAI;AAAA,QACJ,OAAO;AAAA,QACP,MAAM;AAAA,QACN,IAAI,KAAK;AAAA,QACT,aACE;AAAA,MACJ;AAAA,IACF;AAAA,EACF;AAAA,EAEA,UAAgB;AACd,SAAK,WAAW;AAChB,SAAK,KAAK;AACV,eAAW,KAAK,KAAK,MAAM;AACzB,UAAI,CAAC,EAAG;AACR,QAAE,OAAO,cAAc;AAAA,IACzB;AAIA,iBAAa,KAAK,KAAK;AACvB,eAAW,KAAK,KAAK,MAAM;AACzB,UAAI,CAAC,EAAG;AACR,WAAK,MAAM,OAAO,EAAE,KAAK;AAAA,IAC3B;AACA,SAAK,OAAO,CAAC;AACb,SAAK,SAAS,QAAQ;AACtB,SAAK,SAAS,iBAAiB;AAC/B,QAAI,KAAK,WAAY,MAAK,OAAO,OAAO;AACxC,WAAO,KAAK,SAAS,KAAK,QAAQ;AAClC,WAAO,KAAK,SAAS,KAAK,QAAQ;AAClC,WAAO,KAAK,SAAS,KAAK,QAAQ;AAClC,WAAO,KAAK,SAAS,KAAK,QAAQ;AAClC,WAAO,KAAK,SAAS,KAAK,QAAQ;AAClC,WAAO,KAAK,SAAS,KAAK,QAAQ;AAAA,EACpC;AACF;AAMO,SAAS,UACd,KACA,QAAoB,oBACpB,OAAuB,YACvB,SACe;AACf,QAAM,WAAW,aAAa,OAAO,IAAI,MAAM;AAC/C,QAAM,OAAO,IAAI,aAAa,KAAK,UAAU,WAAW,IAAI,GAAG,OAAO;AACtE,MAAI,KAAK,QAAQ,YAAY;AAC7B,SAAO;AACT;","names":["integrateParam","AdditiveBlending","Color","MeshBasicMaterial","Color","MeshBasicMaterial","AdditiveBlending","integrateParam"]}
1
+ {"version":3,"sources":["../src/tank.ts","../src/tank-draco.ts","../src/materials.ts","../src/runtime.ts"],"sourcesContent":["import type { CapabilityTier } from '@idle-screens/capabilities';\nimport {\n defaultParams,\n integrateParam,\n sampleTrack,\n type ControlTrack,\n type ParamSpace,\n type ParamValue,\n type SaverContext,\n type SaverInstance,\n type SaverLayer,\n type Rng,\n} from '@idle-screens/core';\nimport {\n AdditiveBlending,\n AnimationMixer,\n Box3,\n BufferAttribute,\n BufferGeometry,\n CircleGeometry,\n Color,\n ConeGeometry,\n Fog,\n Group,\n HemisphereLight,\n LinearToneMapping,\n MathUtils,\n Mesh,\n MeshBasicMaterial,\n PerspectiveCamera,\n Points,\n Scene,\n ShaderMaterial,\n DoubleSide,\n PlaneGeometry,\n CylinderGeometry,\n SphereGeometry,\n SRGBColorSpace,\n Vector3,\n WebGLRenderer,\n type AnimationClip,\n type Material,\n type Object3D,\n type SkinnedMesh,\n} from 'three';\nimport { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js';\nimport { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';\nimport { clone as cloneSkinned } from 'three/examples/jsm/utils/SkeletonUtils.js';\nimport { needsDraco } from './tank-draco';\nimport { affordableLayers, environmentOf, FLOOR_KINDS, type EnvironmentPreset, type FloorKind } from './environments';\nimport {\n anchorFraction, bandRange, FISH_LENGTH, fishHash, fishVariation, FORMATION_SHAPES,\n formationExtent, formationSlot, swimStyleOf, type FormationShape, type SwimStyleSpec, autoStyleFor, formationBreathe, idleSway, fitBreath } from './swim';\nimport { maneuverAt, maneuverSpecOf } from './maneuver';\nimport { expandFishMixSlots, FISH_CATALOG, parseFishMix, resolveIpfsUrls, type FishEntry } from './ipfs';\nimport { coerceNum, METAQUARIUM_PARAMS, withDefaults } from './manifest';\nimport {\n addGlowHalos,\n applyNpcMaterials,\n eyeNoseSign,\n forceOpaque,\n MIAMI_VICE_COLORS,\n} from './materials';\nimport {\n compileSwimPlan,\n PATH_SHAPES,\n type PathShape,\n distanceAt,\n swimPoseAtDistance,\n type SwimPlan,\n type SwimPose,\n type TankBounds,\n} from './plan';\nimport {\n effectivePixelRatio,\n probeSoftwareGL,\n qualityFor,\n type TankQuality,\n} from './quality';\nimport { LogicalClock, rateOffset } from './runtime';\n\nconst BOUNDS: TankBounds = { radius: 120, yMin: 15, yMax: 72 };\nconst MAX_FISH = METAQUARIUM_PARAMS.fishCount.max ?? 24;\nconst GLB_CONCURRENCY = 3;\n\n// ---------------------------------------------------------------------------\n// Template cache — shared across instances, never disposed by them.\n// ---------------------------------------------------------------------------\n\ninterface FishTemplate {\n scene: Object3D;\n clip: AnimationClip | null;\n norm: number;\n yaw: number;\n /** True when this template was decoded with Draco (not a fallback blob). */\n draco: boolean;\n}\n\n\n/** One decoder per decoder path, created on first use and kept for the page.\n * Keyed by path so two tanks with different `dracoPath` values do not share\n * a loader (setDecoderPath is ignored after the first decode) and so a path\n * change never disposes a worker another tank is still parsing on. */\nconst DRACO_BY_PATH = new Map<string, DRACOLoader>();\nfunction dracoDecoderPath(path: string): string {\n const base = path || new URL('./draco/', import.meta.url).href;\n // Trailing slash is load-bearing — DRACOLoader concatenates the filename\n // straight onto this, and a missing slash yields `…/dracodraco_decoder.wasm`,\n // which a dev server answers with index.html and the decoder dies on\n // \"Unexpected token '<'\".\n return base.endsWith('/') ? base : `${base}/`;\n}\nfunction dracoLoader(path: string): DRACOLoader {\n const normalized = dracoDecoderPath(path);\n let loader = DRACO_BY_PATH.get(normalized);\n if (!loader) {\n loader = new DRACOLoader();\n // Default: the copy tsup ships beside this module. Vite and friends\n // rewrite `import.meta.url` asset URLs at build time; hosts that bundle\n // the package into a single chunk must copy dist/draco next to that\n // chunk, or set the dracoPath param to a URL they actually serve.\n loader.setDecoderPath(normalized);\n // ONE decoder worker, not three.js's default pool of four.\n //\n // DRACOLoader spawns workers lazily up to its limit and never releases\n // them, so a saver that decodes a handful of fish would leave four\n // decoder workers alive for the life of the page. On a 2-core CI runner\n // under software GL that starved every OTHER worker-based saver — the\n // symptom was 17 unrelated worker e2e tests failing while everything\n // passed locally on a machine with cores to spare. A tank decodes a few\n // small models once and caches the templates; serial decoding is fine.\n loader.setWorkerLimit(1);\n DRACO_BY_PATH.set(normalized, loader);\n }\n return loader;\n}\n\n/** Active decode tasks per decoder path. A template no longer needs Draco\n * after parsing, so lifetime follows the work instead of any tank. */\nconst DRACO_USERS = new Map<string, number>();\n\nfunction retainDraco(path: string): void {\n const k = dracoDecoderPath(path);\n DRACO_USERS.set(k, (DRACO_USERS.get(k) ?? 0) + 1);\n}\n\n/** Release one tank's claim; the last one out disposes the worker. Safe\n * against mid-parse teardown because a parse always happens between a\n * retain and its release. */\nfunction releaseDraco(path: string): void {\n const k = dracoDecoderPath(path);\n const n = (DRACO_USERS.get(k) ?? 0) - 1;\n if (n > 0) { DRACO_USERS.set(k, n); return; }\n DRACO_USERS.delete(k);\n DRACO_BY_PATH.get(k)?.dispose();\n DRACO_BY_PATH.delete(k);\n}\n\nasync function withDracoLoader<T>(\n path: string,\n task: (loader: DRACOLoader) => Promise<T>,\n): Promise<T> {\n retainDraco(path);\n try {\n return await task(dracoLoader(path));\n } finally {\n releaseDraco(path);\n }\n}\n\n/** Test seam for the shared decoder lifetime; not exported by the package. */\nexport const __withDracoLoaderForTest = withDracoLoader;\n\n/**\n * The water ceiling: one translucent plane above the fish, rippling in the\n * vertex shader off a single `uTime` uniform.\n *\n * Closed-form on purpose — the displacement is `sin(ωt + φ)` of the vertex's\n * own position, so the surface is a pure function of t like everything else\n * in this saver, and scrubbing to any frame reproduces it exactly. One draw\n * call, no per-frame CPU work.\n */\nfunction buildWaterCeiling(y: number, color: string, opacity: number): {\n mesh: Mesh; material: ShaderMaterial;\n} {\n const geo = new PlaneGeometry(1400, 1400, 48, 48);\n geo.rotateX(-Math.PI / 2);\n geo.userData.mqOwned = true;\n const mat = new ShaderMaterial({\n transparent: true,\n depthWrite: false,\n side: DoubleSide,\n uniforms: {\n uTime: { value: 0 },\n uColor: { value: new Color(color) },\n uOpacity: { value: opacity },\n },\n vertexShader: `\n uniform float uTime;\n varying float vRipple;\n void main() {\n vec3 p = position;\n float r = sin(p.x * 0.012 + uTime * 0.5) * cos(p.z * 0.014 - uTime * 0.37);\n p.y += r * 6.0;\n vRipple = r;\n gl_Position = projectionMatrix * modelViewMatrix * vec4(p, 1.0);\n }`,\n fragmentShader: `\n uniform vec3 uColor;\n uniform float uOpacity;\n varying float vRipple;\n void main() {\n // Caustic-ish banding: the ripple itself modulates brightness, so the\n // surface reads as moving water rather than a tinted sheet of glass.\n float band = 0.65 + 0.35 * vRipple;\n gl_FragColor = vec4(uColor * band, uOpacity * band);\n }`,\n });\n mat.userData.mqOwned = true;\n const mesh = new Mesh(geo, mat);\n mesh.position.y = y;\n mesh.frustumCulled = false;\n return { mesh, material: mat };\n}\n\n/**\n * Terrain: the floor as a silhouette instead of a disc.\n *\n * Generated from the mount seed — never fetched — so determinism holds, the\n * offline hosts work, and it costs no network. Built ONCE at mount and never\n * touched per frame, which is why the cost model prices it at zero: it is\n * geometry, not animation.\n *\n * The three shapes are the original aquarium's silhouettes read back as\n * height functions: dunes (long soft swells), ridges (sharp parallel ranges,\n * its `spikey`), basin (a bowl that falls away from the camera — what makes\n * an abyss feel deep).\n */\n/** Seabed height as a closed-form function of (x, z).\n *\n * Returned rather than baked straight into vertices so the swim volume can be\n * clamped against the SAME expression the mesh is built from. A `dunes` or\n * `ridges` floor reaches +46, well above the bottom of the fish's depth band,\n * so without this a bottom-hugger swims through the hill it is hugging. */\nfunction terrainHeightFn(kind: FloorKind, rng: Rng): (x: number, z: number) => number {\n const R = 620;\n // Two seeded octaves — enough for a silhouette, cheap enough to build in a\n // frame. Phases come from the rng so two tanks are never the same hill.\n const a = rng.next() * Math.PI * 2;\n const b = rng.next() * Math.PI * 2;\n const c = rng.next() * Math.PI * 2;\n return (x, z) => {\n const d = Math.sqrt(x * x + z * z) / R;\n let h = 0;\n if (kind === 'dunes') {\n // Wavelengths sized to the VISIBLE footprint. The originals (0.011 →\n // 571-unit swells) put less than one swell in frame at any camera, so\n // a QA A/B showed dunes rendering as a smooth tilt while ridges (whose\n // |sin| halves its period) showed real relief. 2-3 swells in frame now.\n h = Math.sin(x * 0.028 + a) * 24 + Math.cos(z * 0.023 + b) * 18\n + Math.sin((x + z) * 0.011 + b) * 6;\n } else if (kind === 'ridges') {\n h = Math.abs(Math.sin(x * 0.02 + a)) * 54 - 18 + Math.sin(z * 0.006 + c) * 10;\n } else if (kind === 'basin') {\n // The bowl stays, but a shorter ripple gives the near floor a surface —\n // d² alone is near-constant inside the tank radius.\n h = d * d * 150 - 60 + Math.sin(x * 0.024 + a) * 7 + Math.cos(z * 0.02 + c) * 5;\n }\n // Feather the rim to nothing so the terrain never shows a cut edge.\n return h * Math.max(0, 1 - d * d);\n };\n}\n\nfunction buildTerrain(height: (x: number, z: number) => number, color: string): Mesh {\n const R = 620;\n const geo = new PlaneGeometry(R * 2, R * 2, 72, 72);\n geo.rotateX(-Math.PI / 2);\n const pos = geo.attributes.position!;\n for (let i = 0; i < pos.count; i++) {\n pos.setY(i, height(pos.getX(i), pos.getZ(i)));\n }\n geo.computeVertexNormals();\n // Shading is BAKED as a grayscale vertex-color lambert term — an unlit\n // heightfield has no relief (a QA pass measured flat→dunes at 2.6/255 mean\n // pixel difference: hills nobody can see). Baking keeps the zero-per-frame\n // cost and leaves material.color as the pure tint, so floorColor steering\n // multiplies through unchanged.\n const normal = geo.attributes.normal!;\n const shadeArr = new Float32Array(pos.count * 3);\n // Fixed low sun from the north-west — direction is aesthetic, not physical.\n const lx = -0.45, ly = 0.78, lz = -0.43;\n for (let i = 0; i < pos.count; i++) {\n const lambert = Math.max(0, normal.getX(i) * lx + normal.getY(i) * ly + normal.getZ(i) * lz);\n const v = 0.45 + 0.55 * lambert;\n shadeArr[i * 3] = v;\n shadeArr[i * 3 + 1] = v;\n shadeArr[i * 3 + 2] = v;\n }\n geo.setAttribute('color', new BufferAttribute(shadeArr, 3));\n geo.userData.mqOwned = true;\n const mat = new MeshBasicMaterial({ color: new Color(color), vertexColors: true });\n mat.userData.mqOwned = true;\n const mesh = new Mesh(geo, mat);\n mesh.frustumCulled = false;\n return mesh;\n}\n\n/**\n * Light shafts: a few additive cones that drift closed-form.\n *\n * `y` may be NEGATIVE. The original's raysLightY swings +/-2000, and that sign\n * is the whole difference between a sunlit surface and an abyssal glow coming\n * up out of the dark — one of the few knobs in that settings file that changes\n * what a place MEANS rather than how it looks.\n *\n * Rays are the only per-frame work in the room, which is why they are the\n * layer the cost model drops first on a weak device.\n */\nfunction buildRays(count: number, color: string, y: number, strength: number, rng: Rng): {\n group: Group; material: ShaderMaterial;\n} {\n const group = new Group();\n const mat = new ShaderMaterial({\n transparent: true,\n depthWrite: false,\n blending: AdditiveBlending,\n side: DoubleSide,\n uniforms: { uTime: { value: 0 }, uColor: { value: new Color(color) }, uStrength: { value: strength } },\n vertexShader: `\n varying float vY;\n void main() {\n vY = uv.y;\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n }`,\n fragmentShader: `\n uniform vec3 uColor; uniform float uStrength; uniform float uTime;\n varying float vY;\n void main() {\n // Fade along the shaft and breathe slowly, so it reads as light in\n // suspended matter rather than a solid cone.\n float fade = smoothstep(0.0, 0.3, vY) * (1.0 - smoothstep(0.72, 1.0, vY));\n float breathe = 0.75 + 0.25 * sin(uTime * 0.4);\n // 0.13, not more: with the shaft finally IN frame, alpha is the whole\n // dial — 0.34 turned ice's near-white shafts into pyramids that\n // dwarfed the fish. Faint is what light through water looks like.\n gl_FragColor = vec4(uColor, fade * uStrength * 0.13 * breathe);\n }`,\n });\n mat.userData.mqOwned = true;\n // The shaft spans the TANK (y 0..150), whatever the light's own distance.\n // The old geometry spanned the SOURCE — height |y|·0.85 centred at y·0.45 —\n // which for vent's y=-520 put every shaft at y -455..-13, entirely under\n // the seabed, and for from-above sources put the bright end hundreds of\n // units past the ceiling. A QA pass measured rayStrength 0→1 at a 0.07/255\n // pixel difference: the dial did nothing. The fade now peaks IN the fish\n // band and closes at both ends so shafts still read as light, not walls.\n const geo = new CylinderGeometry(7, 42, 150, 10, 1, true);\n geo.userData.mqOwned = true;\n for (let i = 0; i < count; i++) {\n const m = new Mesh(geo, mat);\n const a = rng.next() * Math.PI * 2;\n const r = 45 + rng.next() * 130;\n m.position.set(Math.cos(a) * r, 72, Math.sin(a) * r);\n // Shafts from below are inverted so the wide end still faces the light.\n if (y < 0) m.rotation.z = Math.PI;\n m.rotation.y = rng.next() * Math.PI;\n // A slight lean per shaft — parallel columns read as architecture.\n m.rotation.x = (rng.next() - 0.5) * 0.18;\n m.frustumCulled = false;\n group.add(m);\n }\n return { group, material: mat };\n}\n\nconst TEMPLATE_CACHE_CAP = 8;\nconst TEMPLATE_CACHE = new Map<string, Promise<FishTemplate | null>>();\n// Eviction deliberately does NOT dispose the evicted template's GPU\n// resources: another live instance may still hold clones sharing its\n// geometry. The leak is bounded (>8 distinct URLs in one session, a few MB\n// each) and freed on context loss; refcounted disposal lands with fishMix,\n// where multi-URL sessions become normal.\n\n/**\n * Dispose only GPU resources this tank created — coat/glow materials\n * (tagged `mqOwned` by applyNpcMaterials), tank-built geometry (floor,\n * fallback fish), and each clone's Skeleton boneTexture. Template-shared\n * geometry and eyes/textured materials are never touched: SkeletonUtils.clone\n * shares them with the cached template, and disposing them here corrupts\n * every other clone of the same fish.\n */\nfunction disposeOwned(root: Object3D): void {\n root.traverse((o) => {\n const skinned = o as Partial<SkinnedMesh>;\n if (skinned.isSkinnedMesh && skinned.skeleton) skinned.skeleton.dispose();\n const mesh = o as Partial<Mesh>;\n if (mesh.geometry?.userData?.mqOwned) mesh.geometry.dispose();\n const mats: Material[] = Array.isArray(mesh.material)\n ? mesh.material\n : mesh.material\n ? [mesh.material]\n : [];\n for (const m of mats) {\n if (!m.userData?.mqOwned) continue;\n // A texture is disposed only when IT is owned, not because its material\n // is: the metallic-atlas path creates an owned material wearing the\n // TEMPLATE'S shared texture, and freeing that map here would corrupt\n // every other clone of the same cached fish. No path creates owned\n // textures today, so this line waits for one that does.\n const tex = (m as Partial<MeshBasicMaterial>).map;\n if (tex?.userData?.mqOwned) tex.dispose();\n m.dispose();\n }\n });\n}\n\n// ---------------------------------------------------------------------------\n// Fish instance — one per visible fish in the scene.\n// ---------------------------------------------------------------------------\n\ninterface Fish {\n /** Spawn slot. Visibility and phase math key off this — never off array\n * or arrival order, which is GLB-completion order and network-dependent.\n * (With one URL every load awaits the same cached promise, so the two\n * coincide; with mixed URLs they will not.) */\n index: number;\n /** Template URL this fish spawned from — reconcile's diff key. */\n url: string;\n group: Group;\n plan: SwimPlan;\n body: Object3D | null;\n baseScale: number;\n /** The model's own forward yaw from its template. Body wiggle oscillates\n * AROUND it — writing rotation.y absolutely would spin every fish to face\n * whatever the wiggle happened to be. */\n baseYaw: number;\n mixer: AnimationMixer | null;\n clipDuration: number;\n tail: Object3D | null;\n}\n\n/** One fish as `inspect()` reports it — the analytic view of a frame. */\ninterface InspectFish {\n index: number;\n url: string;\n breed: string | null;\n style: string;\n band: string;\n bond: string;\n /** Formation seat, or null for a free fish. */\n seat: number | null;\n x: number;\n y: number;\n z: number;\n /** Compass heading in the ground plane, degrees (0 = +z, 90 = +x). */\n heading: number;\n /** A maneuver event is displacing this fish right now. */\n maneuvering: boolean;\n}\n\n/** The leader a bonded fish rides: plan, where along it, and the light pull\n * it took this frame so the file moves as one. */\ninterface LeaderFrame {\n plan: SwimPlan;\n /** The leader's style — a follower swims in its leader's depth band, so an\n * escort behind a surface turtle skims with it instead of riding the raw\n * spline thirty units below. */\n style: SwimStyleSpec;\n d: number;\n effort: number;\n phase: number;\n pullX: number;\n pullZ: number;\n}\n\n// ---------------------------------------------------------------------------\n// TankInstance — the studs: renderer, fog, floor, fish on spline plans.\n// ---------------------------------------------------------------------------\n\nclass TankInstance implements SaverInstance {\n private readonly ctxSaver: SaverContext;\n private readonly space: ParamSpace;\n private readonly canvas: HTMLCanvasElement;\n private readonly ownsCanvas: boolean;\n private readonly renderer: WebGLRenderer;\n private quality: TankQuality;\n private govScale = 1;\n private frameTimes: number[] = [];\n private lastFrameAt = 0;\n private lastGovCheck = 0;\n\n private readonly scene = new Scene();\n private readonly camera: PerspectiveCamera;\n private readonly fogColor = new Color();\n private readonly floorMat: MeshBasicMaterial;\n private readonly motes: Points;\n private readonly moteMat: ShaderMaterial;\n /** The room. Rebuilt only when the environment inputs change — never per\n * frame — so a static tank costs nothing beyond its draw calls. */\n private room: Object3D | null = null;\n private roomKey = '';\n private waterMat: ShaderMaterial | null = null;\n private terrainMat: MeshBasicMaterial | null = null;\n /** World-space seabed height, or null on a flat floor. Set by buildRoom. */\n private floorHeightAt: ((x: number, z: number) => number) | null = null;\n private rayMat: ShaderMaterial | null = null;\n private ceiling: Mesh | null = null;\n private presetWaterY = 0;\n /** The active room's palette — consulted only where the author left the\n * matching param at its manifest default. */\n private roomPalette: { fog: string; floor: string; mote: string } | null = null;\n private presetRayStrength = 0;\n private readonly floorDisc: Mesh;\n /** Sparse, indexed by spawn slot — holes are still-loading fish. */\n private fish: Array<Fish | undefined> = [];\n /** Desired per-slot template URLs — the single source of truth for the\n * population. reconcile() diffs fish against it; spawn workers re-check\n * wantKey after every await, so a stale load can never seat a fish. */\n private wantUrls: string[] = [];\n /** Per-slot style from `@style` tokens (MQ30); null = the scene's swimStyle. */\n private wantStyles: Array<SwimStyleSpec | null> = [];\n /** Per-slot breed from the mix, for `swimStyle: 'auto'` (MQ33). */\n private wantBreeds: string[] = [];\n /** Ground-plane centres of the room's light shafts — what `lightSeek`\n * pulls toward (MQ32). Empty when the room has no rays. */\n private rayPools: Array<[number, number]> = [];\n /** What the last rendered frame looked like, in numbers — `inspect()`. */\n private lastFish: InspectFish[] = [];\n private lastFrameT = 0;\n /** `inspect()` answers null until a frame has actually been rendered. */\n private rendered = false;\n private wantKey = '';\n private wantInputKey = '';\n /** Last fishMix string whose parse problems were warned — once per mix,\n * not per reconcile, or steering any other param would repeat it. */\n private warnedMix = '';\n private mixMode = false;\n private disposed = false;\n\n private w: number;\n private h: number;\n private frameId: number | null = null;\n private paused = false;\n private t = 0;\n private readonly clock = new LogicalClock();\n\n private params: Record<string, ParamValue>;\n private track: ControlTrack | null = null;\n /** Does the track steer swimSpeed? Decided once per applyTrack. When it\n * does, distance comes from the closed-form integral of the speed curve\n * (speed changes glide); when it does not, the legacy constant-speed\n * formula is kept bit-for-bit. */\n private speedTracked = false;\n private autoRotateTracked = false;\n private readonly thumbnail: boolean;\n private readonly catalog: FishEntry[];\n /** One shared route for formation styles, compiled at mount so switching\n * into `school` never respawns a fish. */\n private carrierPlan: SwimPlan;\n /** Shape every live plan was compiled on — setState recompiles when the\n * steered value moves. Plans are cheap (one arc table); rebuilding them\n * beats respawning fish, which would drop GLBs mid-scene. */\n private pathShape: PathShape = 'wander';\n /** The camera azimuth every `crossing` lane in the tank is laid against.\n * Owned here so spawn-time and steer-time compiles agree. */\n private laneAzimuth = 0;\n constructor(\n ctx: SaverContext,\n space: ParamSpace,\n quality: TankQuality,\n catalog: FishEntry[] = FISH_CATALOG,\n ) {\n this.ctxSaver = ctx;\n this.space = space;\n this.quality = quality;\n this.catalog = catalog;\n this.carrierPlan = compileSwimPlan(ctx.rng.fork(0x5c1), BOUNDS, this.pathShape);\n this.thumbnail = ctx.dpr < 0.5;\n this.params = defaultParams(space);\n this.w = ctx.width;\n this.h = ctx.height;\n\n if (ctx.surface instanceof HTMLCanvasElement) {\n this.canvas = ctx.surface;\n this.ownsCanvas = false;\n } else {\n this.canvas = document.createElement('canvas');\n this.canvas.style.cssText = 'display:block;width:100%;height:100%';\n ctx.host.appendChild(this.canvas);\n this.ownsCanvas = true;\n }\n this.canvas.addEventListener('webglcontextlost', (e) => e.preventDefault());\n\n if (probeSoftwareGL()) this.quality = qualityFor('minimal');\n\n this.renderer = new WebGLRenderer({\n canvas: this.canvas,\n antialias: this.quality.antialias,\n stencil: false,\n powerPreference: 'high-performance',\n });\n this.renderer.setPixelRatio(this.pr());\n this.renderer.setSize(this.w, this.h, false);\n this.renderer.outputColorSpace = SRGBColorSpace;\n this.renderer.toneMapping = LinearToneMapping;\n this.renderer.toneMappingExposure = 1.0;\n\n this.camera = new PerspectiveCamera(55, this.w / this.h, 1, 1200);\n\n const fogHex = String(this.space.fogColor?.default ?? '#030009');\n this.fogColor.set(fogHex);\n this.scene.fog = new Fog(this.fogColor.getHex(), 60, 500);\n this.scene.background = this.fogColor;\n\n const floor = new Mesh(\n new CircleGeometry(600, 32),\n new MeshBasicMaterial({ color: 0x0a1d33 }),\n );\n floor.geometry.userData.mqOwned = true;\n floor.material.userData.mqOwned = true;\n floor.rotation.x = -Math.PI / 2;\n this.scene.add(floor);\n this.floorMat = floor.material;\n this.floorDisc = floor;\n\n // Plankton motes: seeded base positions, closed-form drift in the vertex\n // shader (one uTime uniform — zero CPU per frame, pure in t). Draw range\n // scales with moteDensity; the default 0 renders nothing.\n const moteCap = this.quality.moteCap;\n const moteRng = ctx.rng.fork(0x407e);\n const pos = new Float32Array(moteCap * 3);\n const ph = new Float32Array(moteCap * 3);\n for (let i = 0; i < moteCap; i++) {\n const a = moteRng.next() * Math.PI * 2;\n const r = Math.sqrt(moteRng.next()) * BOUNDS.radius * 1.15;\n pos[i * 3] = Math.cos(a) * r;\n pos[i * 3 + 1] = BOUNDS.yMin + moteRng.next() * (BOUNDS.yMax - BOUNDS.yMin + 20);\n pos[i * 3 + 2] = Math.sin(a) * r;\n ph[i * 3] = moteRng.next() * Math.PI * 2;\n ph[i * 3 + 1] = moteRng.next() * Math.PI * 2;\n ph[i * 3 + 2] = moteRng.next() * Math.PI * 2;\n }\n const moteGeo = new BufferGeometry();\n moteGeo.setAttribute('position', new BufferAttribute(pos, 3));\n moteGeo.setAttribute('aPhase', new BufferAttribute(ph, 3));\n moteGeo.userData.mqOwned = true;\n this.moteMat = new ShaderMaterial({\n transparent: true,\n depthWrite: false,\n blending: AdditiveBlending,\n uniforms: { uTime: { value: 0 }, uColor: { value: new Color('#7fd6ff') } },\n vertexShader: `\n attribute vec3 aPhase;\n uniform float uTime;\n varying float vFade;\n void main() {\n vec3 p = position + vec3(\n sin(uTime * 0.11 + aPhase.x) * 6.0,\n sin(uTime * 0.07 + aPhase.y) * 4.0,\n sin(uTime * 0.13 + aPhase.z) * 6.0);\n vec4 mv = modelViewMatrix * vec4(p, 1.0);\n gl_Position = projectionMatrix * mv;\n // 520/z with a 1.5..7 clamp: ~2.6px at a 200-unit camera, ~6px in\n // macro. The old 180/z capped at 4 rendered ONE dim pixel at wall\n // distances — moteDensity 1 was invisible from any normal camera.\n gl_PointSize = clamp(520.0 / max(1.0, -mv.z), 1.5, 7.0);\n vFade = 0.35 + 0.3 * sin(uTime * 0.9 + aPhase.x * 7.0);\n }`,\n fragmentShader: `\n uniform vec3 uColor;\n varying float vFade;\n void main() {\n vec2 c = gl_PointCoord - 0.5;\n float d = length(c);\n if (d > 0.5) discard;\n gl_FragColor = vec4(uColor, (1.0 - d * 2.0) * vFade * 0.8);\n }`,\n });\n this.moteMat.userData.mqOwned = true;\n this.motes = new Points(moteGeo, this.moteMat);\n this.motes.frustumCulled = false;\n this.motes.visible = false;\n this.scene.add(this.motes);\n\n // Gentle hemisphere so MeshStandardMaterial eyes render their authored detail.\n // MeshBasicMaterial body/glow coats ignore it — zero visual cost for them.\n this.scene.add(new HemisphereLight(0xffffff, 0x4466aa, 1.5));\n\n this.reconcile();\n\n this.paused = ctx.reducedMotion;\n if (this.paused) this.renderStill();\n else this.start();\n }\n\n // ---- fish sourcing ----\n\n private str(key: string): string {\n const v = this.params[key];\n return typeof v === 'string' ? v : String(this.space[key]?.default ?? '');\n }\n\n /** Cheap per-frame room params: move the ceiling, set the shaft strength.\n * No allocation, no teardown — this is what the structure key protects. */\n private applyRoomParams(waterY: number, rayStrength: number): void {\n if (this.ceiling) this.ceiling.position.y = waterY >= 0 ? waterY : this.presetWaterY;\n if (this.rayMat) {\n const s = rayStrength >= 0 ? rayStrength : this.presetRayStrength;\n this.rayMat.uniforms.uStrength!.value = s;\n this.rayMat.visible = s > 0;\n }\n }\n\n /**\n * Build (or rebuild) the room for the current environment params.\n *\n * Keyed like reconcile(): one string compare per frame, real work only when\n * the author actually changes something.\n */\n private buildRoom(): void {\n const envName = this.str('environment');\n const floorOverride = this.str('floorKind');\n const waterY = this.num('waterY');\n const rayStrength = this.num('rayStrength');\n // STRUCTURE key only. waterY and rayStrength move a mesh and set a\n // uniform — rebuilding a 5,000-vertex terrain because someone nudged the\n // ceiling would throw away the whole point of building it once.\n const key = `${envName}|${floorOverride}|${this.quality.envBudget}`;\n if (key === this.roomKey) {\n this.applyRoomParams(waterY, rayStrength);\n return;\n }\n this.roomKey = key;\n\n if (this.room) {\n this.scene.remove(this.room);\n disposeOwned(this.room);\n this.room = null;\n this.waterMat = null;\n this.terrainMat = null;\n this.rayMat = null;\n this.ceiling = null;\n this.rayPools = [];\n }\n const preset: EnvironmentPreset = environmentOf(envName);\n const can = affordableLayers(this.quality.envBudget, preset);\n // The environment decides WHETHER there is a ceiling; waterY (-1 = auto)\n // only moves it. Turning it off is what `void` and `abyss` are for.\n\n const group = new Group();\n // Terrain replaces the disc rather than sitting on it, so `flat` stays\n // byte-for-byte the original floor and every published scene is safe.\n // The classic steering lane validates nothing, so an unknown floorKind\n // must fall back rather than cast — otherwise a typo hides the disc AND\n // builds a zero-height plane, i.e. an invisible floor.\n const kind: FloorKind = FLOOR_KINDS.includes(floorOverride as FloorKind)\n ? (floorOverride as FloorKind)\n : preset.floor;\n if (kind !== 'flat') {\n const floorHex = String(this.params.floorColor ?? preset.palette?.floor ?? this.space.floorColor?.default ?? '#0a1d33');\n const height = terrainHeightFn(kind, this.ctxSaver.rng.fork(0x7e88 ^ preset.seedSalt));\n const terrain = buildTerrain(height, floorHex);\n terrain.position.y = -2;\n // World-space seabed, for the swim clamp. Same expression, same seed.\n this.floorHeightAt = (x, z) => height(x, z) + terrain.position.y;\n group.add(terrain);\n this.terrainMat = terrain.material as MeshBasicMaterial;\n }\n // A flat floor has no hills to avoid; clear any the previous room left.\n if (kind === 'flat') this.floorHeightAt = null;\n this.floorDisc.visible = kind === 'flat';\n if (can.water && preset.water) {\n const y = waterY >= 0 ? waterY : preset.water.y;\n const { mesh, material } = buildWaterCeiling(y, preset.water.color, preset.water.opacity);\n group.add(mesh);\n this.waterMat = material;\n this.ceiling = mesh;\n this.presetWaterY = preset.water.y;\n }\n if (can.rayCount > 0 && preset.rays) {\n // Built even at strength 0: a QA pass found that publishing with\n // rayStrength 0 left rayMat null forever, so later steering hit a dead\n // `if (this.rayMat)` and the dial did nothing until the whole room was\n // rebuilt. Strength is a uniform; existence is the room's business.\n const strength = rayStrength >= 0 ? rayStrength : preset.rays.strength;\n const { group: rg, material } = buildRays(\n can.rayCount, preset.rays.color, preset.rays.y, strength, this.ctxSaver.rng.fork(0x8a1),\n );\n material.visible = strength > 0;\n group.add(rg);\n // Where the light falls, for light-seeking fish. Read once per room —\n // the shafts never move.\n this.rayPools = rg.children.map((m) => [m.position.x, m.position.z]);\n this.rayMat = material;\n this.presetRayStrength = preset.rays.strength;\n }\n this.room = group;\n this.scene.add(group);\n this.applyRoomParams(waterY, rayStrength);\n this.roomPalette = preset.palette ?? null;\n this.ctxSaver.host.dataset.mqEnv = preset.name;\n }\n\n /**\n * The carrier's frame for this instant — position, heading basis, and the\n * inward pull that keeps the shoal in the glass. Once per frame, shared by\n * every fish in the formation.\n *\n * Heading comes from a CHORD across the spline, not the tangent at a point.\n * A rigid formation turns as a body, so a fish 60 units off-centre travels\n * the carrier's angular velocity times its offset: on the tight corners of\n * a seeded loop the tangent swings fast enough to fling the outer ranks at\n * 112 units/s, against a cruise of 8. That is a whip, not a shoal, and it\n * is the one thing that looks unmistakably like a bug. Averaging the\n * heading over a body-length chord costs one extra spline sample per frame\n * and keeps the turn inside what a fish could actually swim.\n */\n private carrierFrame(\n ext: { side: number; up: number; back: number; reach: number },\n style: { speedMul: number },\n tSec: number,\n warpSec: number,\n speed: number,\n ): {\n lead: number; pose: SwimPose; x: number; y: number; z: number;\n fwdX: number; fwdZ: number; rx: number; rz: number;\n } {\n // The carrier moves at the STYLE's speed, deliberately without any\n // per-fish multiplier: a formation whose members each chose their own pace\n // is not a formation.\n const lead = this.speedTracked\n ? distanceAt(this.carrierPlan, warpSec, style.speedMul)\n : distanceAt(this.carrierPlan, tSec, speed * style.speedMul);\n const c = swimPoseAtDistance(this.carrierPlan, lead);\n const chord = FISH_LENGTH * 2.5;\n const a = swimPoseAtDistance(this.carrierPlan, lead + chord);\n const b = swimPoseAtDistance(this.carrierPlan, lead - chord);\n let fwdX = a.x - b.x, fwdZ = a.z - b.z;\n let hl = Math.hypot(fwdX, fwdZ);\n if (hl < 1e-3) {\n // Degenerate chord (a hairpin doubling back on itself) — fall back to\n // the tangent rather than dividing by nothing.\n fwdX = c.fx; fwdZ = c.fz; hl = Math.hypot(fwdX, fwdZ) || 1;\n }\n fwdX /= hl; fwdZ /= hl;\n // Keep the shoal in the tank by moving its CENTRE, never by pulling\n // individual fish toward the middle — that squashing turned a legal\n // lattice back into a pile.\n const maxR = Math.max(0, BOUNDS.radius - ext.reach);\n const cr = Math.hypot(c.x, c.z);\n const cs = cr > maxR && cr > 0 ? maxR / cr : 1;\n return {\n lead,\n pose: c,\n x: c.x * cs,\n y: Math.min(BOUNDS.yMax - ext.up, Math.max(BOUNDS.yMin + ext.up, c.y)),\n z: c.z * cs,\n fwdX,\n fwdZ,\n // Right-hand normal in the ground plane.\n rx: fwdZ,\n rz: -fwdX,\n };\n }\n\n /** Ceiling the pool may ever reach on this device. */\n private poolCapNow(): number {\n const poolCap = this.thumbnail ? Math.min(6, MAX_FISH) : MAX_FISH;\n return Math.min(poolCap, this.quality.fishCap);\n }\n\n /**\n * One mechanism for every population change — initial mount, fishCount\n * growth, fishUrl swap, and fishMix (mixed breeds in one tank): compute the\n * desired per-slot URL list, tear down slots whose fish no longer match,\n * and queue spawns for the holes. Cheap when nothing changed (one string\n * compare per frame). Non-empty fishMix wins over fishUrl/fishCount; an\n * unparseable mix degrades to single-breed mode rather than a blank tank.\n */\n private reconcile(): void {\n const cap = this.poolCapNow();\n const mixStr = this.str('fishMix').trim();\n const url = this.str('fishUrl');\n const n = Math.min(Math.max(1, Math.round(this.num('fishCount'))), cap);\n const inputKey = `${mixStr}\\u0000${url}\\u0000${n}\\u0000${cap}`;\n if (inputKey === this.wantInputKey) return;\n this.wantInputKey = inputKey;\n\n let want: string[] = [];\n let styles: Array<SwimStyleSpec | null> = [];\n let breeds: string[] = [];\n this.mixMode = false;\n if (mixStr !== '') {\n const parsed = parseFishMix(mixStr, this.catalog);\n if (parsed.problems.length > 0 && mixStr !== this.warnedMix) {\n this.warnedMix = mixStr;\n const fallback = parsed.entries.length === 0 ? ' — falling back to fishUrl/fishCount' : '';\n console.warn(`[metaquarium] fishMix \"${mixStr}\": ${parsed.problems.join('; ')}${fallback}`);\n }\n const slots = expandFishMixSlots(parsed.entries, cap);\n want = slots.map((sl) => sl.url);\n styles = slots.map((sl) => (sl.style ? swimStyleOf(sl.style) : null));\n breeds = slots.map((sl) => sl.breed);\n this.mixMode = want.length > 0;\n }\n if (!this.mixMode) {\n // Single-breed: the pool never shrinks while the url is stable\n // (steering fishCount down just hides); a url change — or leaving mix\n // mode, however the mix happened to start — rebuilds at n. Without the\n // mode check, a mix whose first breed equals fishUrl would keep its\n // slots on clear while any other mix would not (review, PR #72).\n const wasSingle = this.wantKey === '' || this.wantKey.startsWith('one:');\n const stable = wasSingle && this.wantUrls.length > 0 && this.wantUrls[0] === url;\n const len = stable ? Math.max(n, Math.min(this.wantUrls.length, cap)) : n;\n want = Array.from({ length: len }, () => url);\n }\n this.wantUrls = want;\n this.wantStyles = this.mixMode ? styles : [];\n this.wantBreeds = this.mixMode ? breeds : [];\n this.wantKey = (this.mixMode ? 'mix:' : 'one:') + want.join('|');\n if (this.mixMode) this.ctxSaver.host.dataset.mqMix = mixStr;\n else delete this.ctxSaver.host.dataset.mqMix;\n\n const missing: number[] = [];\n for (let i = 0; i < Math.max(this.fish.length, want.length); i++) {\n const f = this.fish[i];\n const wantUrl = want[i];\n if (f && (wantUrl === undefined || f.url !== wantUrl)) {\n f.mixer?.stopAllAction();\n this.scene.remove(f.group);\n disposeOwned(f.group);\n this.fish[i] = undefined;\n }\n if (wantUrl !== undefined && !this.fish[i]) missing.push(i);\n }\n this.fish.length = Math.min(this.fish.length, want.length);\n if (missing.length) void this.spawnMissing(missing);\n this.ctxSaver.host.dataset.mqFish = String(this.loadedCount());\n }\n\n /** Bounded-concurrency spawn of the queued slots. Guarded by wantKey: any\n * reconcile that changes the population invalidates in-flight loads. */\n private async spawnMissing(slots: number[]): Promise<void> {\n const key = this.wantKey;\n let next = 0;\n const worker = async (): Promise<void> => {\n while (next < slots.length && !this.disposed && this.wantKey === key) {\n const i = slots[next++]!;\n const slotUrl = this.wantUrls[i]!;\n const tpl = await this.template(slotUrl, this.str('dracoPath'));\n if (this.disposed || this.wantKey !== key) return;\n if (!this.fish[i]) {\n this.spawn(tpl, i, slotUrl);\n if (!tpl) this.healLater(i, slotUrl, key);\n }\n }\n };\n await Promise.all(Array.from({ length: GLB_CONCURRENCY }, worker));\n if (!this.disposed && this.paused) this.renderStill();\n }\n\n private template(url: string, dracoPath = ''): Promise<FishTemplate | null> {\n const key = url;\n let p = TEMPLATE_CACHE.get(key);\n if (!p) {\n p = (async (): Promise<FishTemplate | null> => {\n try {\n // Gateway ladder (MQ21): try each candidate with its own timeout so\n // one flaky gateway degrades to the next instead of to a fallback\n // blob. Non-ipfs URLs have a single candidate.\n const buf = await (async (): Promise<ArrayBuffer> => {\n let lastErr: unknown = new Error('no gateway candidates');\n for (const candidate of resolveIpfsUrls(url)) {\n const ctl = new AbortController();\n const timer = setTimeout(() => ctl.abort(), 12_000);\n try {\n const res = await fetch(candidate, { signal: ctl.signal });\n if (!res.ok) throw new Error(`fish glb ${res.status}`);\n return await res.arrayBuffer();\n } catch (e) {\n lastErr = e;\n } finally {\n clearTimeout(timer);\n }\n }\n throw lastErr;\n })();\n const loader = new GLTFLoader();\n const draco = needsDraco(buf);\n const gltf = draco\n ? await withDracoLoader(dracoPath, async (decoder) => {\n loader.setDRACOLoader(decoder);\n return loader.parseAsync(buf, '');\n })\n : await loader.parseAsync(buf, '');\n const scene = gltf.scene;\n forceOpaque(scene);\n const size = new Box3().setFromObject(scene).getSize(new Vector3());\n const longX = size.x > size.z;\n const nose = eyeNoseSign(scene, longX ? 'x' : 'z') || 1;\n const yaw = longX\n ? nose > 0\n ? -Math.PI / 2\n : Math.PI / 2\n : nose > 0\n ? 0\n : Math.PI;\n return {\n scene,\n clip: gltf.animations[0] ?? null,\n norm: FISH_LENGTH / (Math.max(size.x, size.y, size.z) || 1),\n yaw,\n draco,\n };\n } catch {\n TEMPLATE_CACHE.delete(key);\n return null;\n }\n })();\n if (TEMPLATE_CACHE.size >= TEMPLATE_CACHE_CAP) {\n const oldest = TEMPLATE_CACHE.keys().next().value;\n if (oldest !== undefined) TEMPLATE_CACHE.delete(oldest);\n }\n TEMPLATE_CACHE.set(key, p);\n }\n return p;\n }\n\n private spawn(tpl: FishTemplate | null, index: number, url: string): void {\n const rng = this.ctxSaver.rng.fork(0x715);\n const plan = compileSwimPlan(rng.fork(index), BOUNDS, this.pathShape, { cameraAzimuthDeg: this.laneAzimuth });\n const group = new Group();\n let mixer: AnimationMixer | null = null;\n let tail: Object3D | null = null;\n let bodyNode: Object3D | null = null;\n let clipDuration = 0;\n\n if (tpl) {\n const body = cloneSkinned(tpl.scene);\n applyNpcMaterials(body, this.ctxSaver.rng.fork(0xc0a7 + index));\n // Selective bloom on the GLOW parts — same fork, so a fish's halo color\n // agrees with the coat pass when both fall through to the seeded pick.\n addGlowHalos(body, this.ctxSaver.rng.fork(0xc0a7 + index));\n body.scale.setScalar(tpl.norm);\n body.rotation.y = tpl.yaw;\n group.add(body);\n bodyNode = body;\n if (tpl.clip) {\n mixer = new AnimationMixer(body);\n mixer.clipAction(tpl.clip).play();\n clipDuration = tpl.clip.duration;\n }\n } else {\n const coat = this.ctxSaver.rng\n .fork(0xc0a7 + index)\n .pick(MIAMI_VICE_COLORS);\n const mat = new MeshBasicMaterial({ color: new Color(coat) });\n mat.userData.mqOwned = true;\n const body = new Mesh(new SphereGeometry(FISH_LENGTH / 2, 12, 8), mat);\n body.geometry.userData.mqOwned = true;\n body.scale.set(1, 0.55, 0.4);\n const tailMesh = new Mesh(\n new ConeGeometry(FISH_LENGTH * 0.22, FISH_LENGTH * 0.5, 8),\n mat,\n );\n tailMesh.geometry.userData.mqOwned = true;\n tailMesh.rotation.z = Math.PI / 2;\n tailMesh.position.x = -FISH_LENGTH * 0.62;\n group.add(body, tailMesh);\n tail = tailMesh;\n }\n\n const baseScale = plan.cruise > 10 ? 1.1 : 0.8 + (index % 5) * 0.1;\n group.scale.multiplyScalar(baseScale);\n // Hidden until setState has placed it. A group joins the scene at the\n // ORIGIN, so a fish that finished loading after this frame's setState was\n // drawn at the centre of the tank and then jumped to its spline on the\n // next one — a 60-unit dart, once per fish, as the scene came up. That is\n // the \"fish start out faster than they swim\" everyone sees at mount.\n group.visible = false;\n this.scene.add(group);\n this.fish[index] = {\n index,\n url,\n baseYaw: tpl ? tpl.yaw : 0,\n group,\n plan,\n body: bodyNode,\n baseScale,\n mixer,\n clipDuration,\n tail,\n };\n this.ctxSaver.host.dataset.mqFish = String(this.loadedCount());\n if (tpl?.draco) this.ctxSaver.host.dataset.mqDraco = '1';\n }\n\n /** One delayed second chance for a slot that spawned as a fallback blob:\n * the failed template self-evicted from the cache, so a later fetch can\n * succeed (gateway flakes are transient). Bounded to a single retry per\n * spawn, guarded by wantKey, and skipped after dispose. Population loading\n * was never pure in t (it is network), so the timer breaks nothing. */\n private healLater(index: number, url: string, key: string): void {\n setTimeout(() => {\n void (async (): Promise<void> => {\n if (this.disposed || this.wantKey !== key) return;\n const tpl = await this.template(url, this.str('dracoPath'));\n if (!tpl || this.disposed || this.wantKey !== key) return;\n const blob = this.fish[index];\n if (!blob || blob.body) return; // real fish arrived meanwhile\n blob.mixer?.stopAllAction();\n this.scene.remove(blob.group);\n disposeOwned(blob.group);\n this.fish[index] = undefined;\n this.spawn(tpl, index, url);\n if (this.paused) this.renderStill();\n })();\n }, 15_000);\n }\n\n /** Fish actually spawned — the sparse array's holes are loads in flight. */\n private loadedCount(): number {\n let n = 0;\n for (const f of this.fish) if (f) n++;\n return n;\n }\n\n // ---- params / state ----\n\n private applyParams(t: number): void {\n if (this.track) this.params = sampleTrack(this.space, this.track, t);\n }\n\n private num(key: string): number {\n // Clamped, string-coercing read — the classic steering lane is\n // unvalidated server-side (MQ17), so hostile or stringified values reach\n // us as-is. Note the swimSpeed distance integral (integrateParam) does\n // not clamp; range enforcement at intake is MQ17's half of this fix.\n return coerceNum(this.space[key], this.params[key]);\n }\n\n private setState(t: number): void {\n const tSec = t / 1000;\n this.applyParams(t);\n const speed = this.num('swimSpeed');\n // Warped swim time: ∫ speed dτ. With a steered speed this makes changes\n // glide (MQ11 — multiplying the whole elapsed integral teleported every\n // fish proportionally to elapsed time). Only UNSTEERED tracks keep the\n // legacy closed form bit-for-bit; a steered track uses warped-time\n // semantics throughout, where the speed-wobble cycle scales with speed\n // (deliberate — the same way tail beat and clip phase already do, since\n // both are distance-driven). Both paths are pure functions of (t, track).\n // Bounds clamp the integrated curve to the declared range, matching the\n // clamped num() reads — the classic lane is intake-unvalidated (MQ17).\n const speedDef = this.space.swimSpeed;\n const warpSec = this.speedTracked && this.track\n ? integrateParam(this.space, this.track, 'swimSpeed', t, {\n ...(speedDef?.min !== undefined ? { min: speedDef.min } : {}),\n ...(speedDef?.max !== undefined ? { max: speedDef.max } : {}),\n }) / 1000\n : tSec * speed;\n\n this.reconcile();\n\n // Camera orbit\n const rotation = rateOffset(\n this.space, this.track, 'autoRotate', t, this.num('autoRotate'), this.autoRotateTracked,\n );\n const az = MathUtils.degToRad(this.num('cameraAzimuth') + rotation);\n const el = MathUtils.degToRad(this.num('cameraElevation'));\n const dist = this.num('cameraDistance');\n this.camera.position.set(\n Math.cos(el) * Math.sin(az) * dist,\n Math.max(10, 15 + Math.sin(el) * dist),\n Math.cos(el) * Math.cos(az) * dist,\n );\n this.camera.lookAt(0, 35, 0);\n\n // Fog color\n const fogHex = String(\n this.params.fogColor ?? this.roomPalette?.fog ?? this.space.fogColor?.default ?? '#030009',\n );\n this.fogColor.set(fogHex);\n (this.scene.fog as Fog).color.copy(this.fogColor);\n\n // Atmosphere — every default reproduces the pre-atmosphere constants, so\n // this block is provably invisible until steered.\n const fog = this.scene.fog as Fog;\n fog.near = this.num('fogNear');\n fog.far = Math.max(this.num('fogFar'), fog.near + 20);\n const floorHex = String(this.params.floorColor ?? this.roomPalette?.floor ?? this.space.floorColor?.default ?? '#0a1d33');\n this.floorMat.color.set(floorHex);\n // Terrain follows floorColor as well — the environment supplies the SHAPE,\n // the author keeps the palette.\n this.terrainMat?.color.set(floorHex);\n const density = this.num('moteDensity');\n const active = Math.round(density * this.quality.moteCap);\n this.motes.visible = active > 0;\n if (active > 0) {\n this.motes.geometry.setDrawRange(0, active);\n this.moteMat.uniforms.uTime!.value = tSec;\n (this.moteMat.uniforms.uColor!.value as Color).set(\n String(this.params.moteColor ?? this.roomPalette?.mote ?? this.space.moteColor?.default ?? '#7fd6ff'),\n );\n }\n this.ctxSaver.host.dataset.mqMotes = String(active);\n\n // The room: rebuilt only on change, then driven by the same clock as\n // everything else so it stays pure in t.\n this.buildRoom();\n if (this.waterMat) this.waterMat.uniforms.uTime!.value = tSec;\n if (this.rayMat) this.rayMat.uniforms.uTime!.value = tSec;\n\n const sceneStyleName = this.str('swimStyle');\n // `auto` is not a style: each untagged fish resolves to its breed's\n // default (MQ33). swimStyleOf('auto') falls back to loop, which is what\n // a fish with no known breed (single-breed fishUrl mode) should do.\n const autoMode = sceneStyleName === 'auto';\n const sceneStyle = swimStyleOf(sceneStyleName);\n const variance = this.num('swimVariance');\n const wiggle = this.num('bodyWiggle');\n const seek = this.num('lightSeek');\n const pools = this.rayPools;\n const lattice = formationBreathe(tSec, this.num('formationBreathe'));\n\n const spec = maneuverSpecOf(this.str('maneuver'));\n const mnvRate = this.num('maneuverRate');\n const mnvIntensity = this.num('maneuverIntensity');\n\n // Path shape: recompile plans in place when steered. Same forks as spawn,\n // so a shape round-trip lands every fish back on its exact original loop.\n const shapeRaw = this.str('pathShape');\n const shape = (PATH_SHAPES as readonly string[]).includes(shapeRaw)\n ? (shapeRaw as PathShape)\n : 'wander';\n // `crossing` is laid against the camera, and the camera moves: a steered\n // cameraAzimuth or an autoRotate orbit would otherwise leave the parade\n // running across a view that is no longer there, and a fish spawned later\n // would lay its lane at a different angle from the shoal's. One lane\n // azimuth is owned by the tank (`laneAzimuth`), used by every compile —\n // spawn included — and re-laid when the EFFECTIVE azimuth has turned by\n // more than 2°. Re-laying rotates the whole lane while every fish keeps\n // its arc distance, so the parade turns with the camera instead of\n // jumping. Other shapes ignore the azimuth and are never re-laid for it.\n const azEff = this.num('cameraAzimuth') + this.num('autoRotate') * tSec;\n const laneMoved = shape === 'crossing'\n && Math.abs(((azEff - this.laneAzimuth + 540) % 360) - 180) > 2;\n if (shape !== this.pathShape || laneMoved) {\n this.pathShape = shape;\n this.laneAzimuth = azEff;\n const planOpts = { cameraAzimuthDeg: this.laneAzimuth };\n this.carrierPlan = compileSwimPlan(this.ctxSaver.rng.fork(0x5c1), BOUNDS, shape, planOpts);\n const rng = this.ctxSaver.rng.fork(0x715);\n for (const f of this.fish) {\n if (f) f.plan = compileSwimPlan(rng.fork(f.index), BOUNDS, shape, planOpts);\n }\n }\n\n // Fish. Mix mode: the DSL defines the population absolutely (fishCount\n // is documented as ignored). Single mode: fishCount is the dial;\n // reconcile has already queued any missing slots.\n const visible = this.mixMode\n ? this.wantUrls.length\n : Math.min(Math.round(this.num('fishCount')), this.quality.fishCap);\n // Once per frame, not once per fish: the extent walks every slot, so\n // computing it inside the loop made the formation O(n^2) every frame for\n // a value that is identical across the shoal.\n const fshapeRaw = this.str('formationShape');\n const fshape = (FORMATION_SHAPES as readonly string[]).includes(fshapeRaw)\n ? (fshapeRaw as FormationShape)\n : 'phalanx';\n // Effective style per slot: a token's `@style` wins, else the scene's.\n // Formation seats are handed out over the SUBSET of fish whose effective\n // style forms — a `@school` trio inside a hovering tank is a school of\n // three, seated 0..2, not three fish holding seats in a school of eight.\n const styleAt = (i: number): SwimStyleSpec =>\n this.wantStyles[i] ?? (autoMode ? autoStyleFor(this.wantBreeds[i]) : sceneStyle);\n const formationSeat = new Map<number, number>();\n for (let i = 0; i < visible; i++) {\n if (styleAt(i).formation) formationSeat.set(i, formationSeat.size);\n }\n const fcount = formationSeat.size;\n const formationStyle = fcount > 0 ? styleAt(formationSeat.keys().next().value as number) : null;\n const extent0 = fcount > 0 ? formationExtent(fcount, variance, fshape) : null;\n // Breathing scales seats AND extent together, so the carrier's inward\n // pull still measures the shoal it is actually keeping in the glass —\n // and only as far as the glass allows (fitBreath: a full breath on a\n // tall ring or stacked wedge would otherwise push seats out of the\n // water column, because the carrier moves the centre, not the fish).\n const breath = extent0\n ? fitBreath(extent0, lattice, { yRange: BOUNDS.yMax - BOUNDS.yMin, radius: BOUNDS.radius })\n : lattice;\n const extent = extent0\n ? { side: extent0.side * breath, up: extent0.up * breath, back: extent0.back * breath, reach: extent0.reach * breath }\n : null;\n const carrier = extent && formationStyle ? this.carrierFrame(extent, formationStyle, tSec, warpSec, speed) : null;\n // Relationships (MQ31), resolved in SLOT order: a bonded fish rides the\n // most recent unbonded free fish before it. `pair` seats two on one\n // plan; a third opens a new pair. Nothing here accumulates — a follower\n // is the leader's closed form sampled at a lag.\n let leader: LeaderFrame | null = null;\n let followRank = 0;\n let pendingPair: LeaderFrame | null = null;\n // Proximity startle (MQ31): free fish flinch in a wave from a SENTINEL —\n // the first free fish in slot order — delayed by ground distance, the\n // same 90 u/s the formation wave uses. The sentinel is fixed per scene so\n // no fish changes epicentre mid-flinch.\n let sentinel: { x: number; z: number } | null = null;\n const report: InspectFish[] = [];\n for (const f of this.fish) {\n if (!f) continue;\n f.group.visible = f.index < visible;\n if (!f.group.visible) continue;\n const style = styleAt(f.index);\n const seat = formationSeat.get(f.index) ?? f.index;\n const bond = style.bond ?? 'none';\n // Whose depth band this fish swims in: its own, or its leader's.\n let bandStyle: SwimStyleSpec = style;\n // A chaser's extra tail work — kept OUT of `mnv`, which may be the\n // shared idle constant.\n let flurryBoost = 0;\n\n // Style + per-fish variation. Both are pure functions of (index, t), so\n // a scene stays frame-addressable no matter how varied it looks.\n const varn = fishVariation(f.index, variance);\n const styleSpeed = style.speedMul * varn.speedMul;\n // `effort` is how hard the fish is working; `d` is where that puts it.\n // They differ for styles that hold station: a hovering fish still beats\n // its tail, so animation must not scale with travel or it looks stuffed.\n //\n // Speed goes in distanceAt's THIRD argument, never folded into time.\n // distanceAt scales the whole integral (cruise + wobble) by that\n // argument; multiplying TIME instead compresses the wobble's phase, so a\n // scene with swimSpeed != 1 and no speed track would have silently\n // changed trajectory — breaking `loop`'s no-op promise for exactly the\n // scenes least likely to be looking.\n const effort = this.speedTracked\n ? distanceAt(f.plan, warpSec, styleSpeed)\n : distanceAt(f.plan, tSec, speed * styleSpeed);\n // Every style spreads its cast along the loop, not only the\n // station-keepers; `loop` alone stays anchorless. The rule and the bug\n // that earned it live in swim.ts's `anchorFraction`, where a test can\n // reach them.\n const anchor = anchorFraction(style, f.index, variance) * f.plan.totalLength;\n // Maneuvers displace, never re-rate: `along` moves the fish on its own\n // path, the kick is added to the pose after banding. Both closed-form.\n // Contagious events (startle) propagate through a formation as a wave:\n // seat distance from a per-schedule epicentre becomes a start delay, so\n // the flinch crosses the lattice at ~90 units/s instead of every fish\n // twitching on its own private clock. Free fish keep their own schedule.\n let seatDelay: number | null = null;\n if (spec?.contagious && style.formation) {\n const slot0 = formationSlot(seat, fcount, variance, undefined, fshape);\n seatDelay = Math.hypot(slot0.side, slot0.up, slot0.back) / 90;\n } else if (spec?.contagious) {\n // Undisplaced position — where the fish would be with no event in\n // flight — so the delay itself never depends on the flinch it times.\n const base = swimPoseAtDistance(f.plan, anchor + effort * style.travel);\n if (!sentinel) {\n sentinel = { x: base.x, z: base.z };\n seatDelay = 0;\n } else {\n seatDelay = Math.hypot(base.x - sentinel.x, base.z - sentinel.z) / 90;\n }\n }\n const mnv = maneuverAt(spec, f.index, tSec, mnvRate, mnvIntensity, seatDelay);\n const d = anchor + effort * style.travel + mnv.along * FISH_LENGTH;\n\n // What drives the animation. For a free fish that is its own effort; for\n // a fish in formation it is the carrier's, because the carrier is what\n // is actually moving it. Beating to its own unused loop made a shoal\n // whose tails were out of step with its travel — fish moonwalking.\n let beat = effort;\n let pose;\n if (style.formation) {\n // Carrier school: ONE route, fish held in slots in its local frame, so\n // the shoal turns as a body.\n //\n // Measured over 4 seeds x 600 frames at 8 fish, variance 0.6\n // (src/swim.test.ts holds the spacing half of this as a gate):\n //\n // loop 27.8% of fish-frames with a neighbour inside a body\n // length, closest approach 1.3u, polarisation 0.38\n // school 0.0%, closest approach 27.3u, polarisation 1.00\n //\n // The earlier 0.85-vs-boids'-0.87 figure in this comment described the\n // spike's boids prototype, not this port, and the port's first draft\n // measured WORSE than loop at 27.3%. Rigid offsets from one arc sample\n // are what actually fixed it.\n const slot = formationSlot(seat, fcount, variance, undefined, fshape);\n slot.side *= breath;\n slot.up *= breath;\n slot.back *= breath;\n // A seated fish darts AHEAD of its slot and settles back — the\n // closing displacement, because the permanent one would walk it out\n // of the school forever.\n const seatBack = slot.back - mnv.alongBump * FISH_LENGTH;\n const cf = carrier ?? this.carrierFrame(\n extent ?? formationExtent(fcount, variance, fshape), formationStyle ?? style, tSec, warpSec, speed,\n );\n beat = cf.lead;\n // A seated fish can lead too: a bonded fish after a school trails\n // the CARRIER route behind the whole formation, which is what\n // \"follow the school\" should mean. Retires any half-formed pair.\n leader = {\n plan: this.carrierPlan, style, d: cf.lead - (extent?.back ?? 0), effort: cf.lead,\n phase: varn.phase, pullX: 0, pullZ: 0,\n };\n followRank = 0;\n pendingPair = null;\n // A rigid body means every fish points EXACTLY the same way, which\n // measures as polarisation 1.00 and looks like a formation flight.\n // A few degrees of per-fish yaw, scaled by variance, buys back the\n // look of animals without touching the spacing guarantee.\n const yaw = Math.sin(varn.phase) * 0.12 * variance;\n const cy2 = Math.cos(yaw), sy2 = Math.sin(yaw);\n pose = {\n ...cf.pose,\n x: cf.x + cf.rx * slot.side - cf.fwdX * seatBack,\n y: cf.y + slot.up,\n z: cf.z + cf.rz * slot.side - cf.fwdZ * seatBack,\n fx: cf.fwdX * cy2 - cf.fwdZ * sy2,\n fz: cf.fwdX * sy2 + cf.fwdZ * cy2,\n };\n } else {\n // A pair is two CONSECUTIVE pair fish; anything else between them\n // retires the half-formed one, so a later `@pair` never bonds\n // across an unrelated fish.\n if (bond !== 'pair') pendingPair = null;\n const rel = bond === 'pair' ? pendingPair : bond === 'follow' || bond === 'chase' ? leader : null;\n if (rel) bandStyle = rel.style;\n let flurryExtra = 0;\n if (rel) {\n // Ride the leader's route behind it. A chaser's lag breathes —\n // closing in, falling back — and its tail works hardest as it\n // closes; a follower keeps a fixed station in the file.\n let lag = 0;\n if (bond === 'follow') {\n lag = FISH_LENGTH * 1.7 * (followRank + 1);\n } else if (bond === 'chase') {\n const c = 0.5 + 0.5 * Math.sin(tSec * 0.7 + varn.phase);\n lag = FISH_LENGTH * (1.3 + 1.6 * c);\n flurryExtra = (1 - c) * 0.8;\n }\n pose = swimPoseAtDistance(rel.plan, rel.d - lag);\n beat = rel.effort - lag;\n const hl = Math.hypot(pose.fx, pose.fz) || 1;\n const rxn = pose.fz / hl, rzn = -pose.fx / hl;\n let ox = rel.pullX, oz = rel.pullZ, oy = 0;\n if (bond === 'pair') {\n // The second of the pair, opposite its partner on the shared orbit.\n const th = tSec * 0.6 + rel.phase + Math.PI;\n const R = FISH_LENGTH * 1.1;\n ox += rxn * R * Math.cos(th);\n oz += rzn * R * Math.cos(th);\n oy = R * 0.5 * Math.sin(th);\n pendingPair = null;\n } else {\n // Off the leader's exact line, so a file is not a stack.\n const off = (fishHash(f.index, 61) - 0.5) * FISH_LENGTH * (bond === 'follow' ? 0.8 : 0.5);\n ox += rxn * off;\n oz += rzn * off;\n followRank += 1;\n }\n pose = { ...pose, x: pose.x + ox, y: pose.y + oy, z: pose.z + oz };\n } else {\n pose = swimPoseAtDistance(f.plan, d);\n // Light-seeking: each free fish is drawn toward ITS shaft (chosen\n // by index, so the choice never flips as it moves) by a per-fish\n // appetite. The loop shrinks toward the pool — still the same\n // closed form, translated — and followers take the same pull.\n let pullX = 0, pullZ = 0;\n if (seek > 0 && pools.length > 0) {\n const pool = pools[Math.floor(fishHash(f.index, 53) * pools.length) % pools.length]!;\n const k = seek * 0.7 * (0.5 + 0.5 * fishHash(f.index, 59));\n // A shaft can stand at the glass or beyond it (rooms place their\n // lights, the tank does not); the pull is toward it, never past\n // the radius.\n const tx = (pool[0] - pose.x) * k;\n const tz = (pool[1] - pose.z) * k;\n const tr = Math.hypot(pose.x + tx, pose.z + tz);\n const ts = tr > BOUNDS.radius ? BOUNDS.radius / tr : 1;\n pullX = (pose.x + tx) * ts - pose.x;\n pullZ = (pose.z + tz) * ts - pose.z;\n }\n const me: LeaderFrame = { plan: f.plan, style, d, effort, phase: varn.phase, pullX, pullZ };\n let oy = 0;\n if (bond === 'pair') {\n // First of a pair: takes one side of the shared orbit and waits\n // for a partner. An odd pair fish simply swims its own route.\n const th = tSec * 0.6 + varn.phase;\n const R = FISH_LENGTH * 1.1;\n const hl = Math.hypot(pose.fx, pose.fz) || 1;\n pullX += (pose.fz / hl) * R * Math.cos(th);\n pullZ += (-pose.fx / hl) * R * Math.cos(th);\n oy = R * 0.5 * Math.sin(th);\n pendingPair = me;\n } else {\n leader = me;\n followRank = 0;\n }\n // Idle micro-motion: a station-keeper turns in place (MQ36).\n const sway = idleSway(style, tSec, varn.phase);\n let fx = pose.fx, fz = pose.fz;\n if (sway !== 0) {\n const cs = Math.cos(sway), sn = Math.sin(sway);\n fx = pose.fx * cs - pose.fz * sn;\n fz = pose.fx * sn + pose.fz * cs;\n }\n pose = { ...pose, x: pose.x + pullX, y: pose.y + oy, z: pose.z + pullZ, fx, fz };\n }\n flurryBoost = flurryExtra;\n }\n\n // Depth band, then bob. Clamping BEFORE the bob keeps a bottom-hugger\n // from being lifted out of its band by its own motion.\n let y = pose.y;\n if (style.bobAmp > 0) {\n y += Math.sin(tSec * style.bobHz * Math.PI * 2 + varn.phase) * style.bobAmp;\n }\n // Clamp AFTER the bob. Clamping first let a fish bob straight back out\n // of the band it was just put in — a bottom-hugger that leaves the floor\n // is not band-limited, it is just a fish.\n const band = bandRange(bandStyle.band);\n if (band) {\n let lo = BOUNDS.yMin + (BOUNDS.yMax - BOUNDS.yMin) * band.lo;\n let hi = BOUNDS.yMin + (BOUNDS.yMax - BOUNDS.yMin) * band.hi;\n // A surface skimmer skims the WATER, not the top of the abstract swim\n // volume. Env water planes sit at 118-170 while yMax is 72, so\n // \"surface\" turtles cruised mid-frame far below the ceiling they were\n // named for. Ride just under the actual plane, capped so a steered\n // waterY of 220 cannot fly the cast out of every camera's frame.\n if (bandStyle.band === 'ceiling' && this.ceiling) {\n const wparam = this.num('waterY');\n const wy = wparam >= 0 ? wparam : this.presetWaterY;\n const ceilY = Math.min(wy - FISH_LENGTH * 0.8, BOUNDS.yMax + 60);\n if (ceilY > hi) {\n lo = ceilY - (hi - lo);\n hi = ceilY;\n }\n }\n y = Math.min(hi, Math.max(lo, y));\n }\n\n\n // Inside a depth band a fish swims LEVEL. Without this its heading still\n // points along the unclamped spline, so a bottom-hugger noses down into\n // a floor it can never reach and a skimmer climbs at an invisible lid.\n // Level, not merely flatter: a fraction of the spline's climb still reads\n // as a fish nosing into a floor it cannot reach.\n // Maneuver pitch applies AFTER the band level-lock, deliberately: the\n // level rule zeroes exactly the styles (bottom, drift) a grazer lives\n // in, and grazing IS a posture — nose-down over the substrate is the\n // one maneuver shape a still image can name.\n const fy = (band ? 0 : pose.fy)\n + (mnv.pitch !== 0 ? Math.tan(mnv.pitch) * (Math.hypot(pose.fx, pose.fz) || 1) : 0);\n let px = pose.x, pz = pose.z;\n if (mnv.side !== 0 || mnv.up !== 0) {\n // Kick along the fish's right-hand normal, closing back to zero by\n // the event's end — the fish leaves its line and comes home to it.\n // In a formation the kick is scaled UP: QA measured startle's 2.8\n // body lengths as 1.5 phalanx column pitches — the scattered fish\n // landed on roughly another seat and read as mis-seated. 1.8x clears\n // the lattice.\n const kickScale = style.formation ? 1.8 : 1;\n const hl2 = Math.hypot(pose.fx, pose.fz) || 1;\n px += (pose.fz / hl2) * mnv.side * FISH_LENGTH * kickScale;\n pz += (-pose.fx / hl2) * mnv.side * FISH_LENGTH * kickScale;\n y = Math.min(BOUNDS.yMax + 60, Math.max(BOUNDS.yMin, y + mnv.up * FISH_LENGTH * kickScale));\n // A startle must not kick a fish through the glass.\n const kr = Math.hypot(px, pz);\n if (kr > BOUNDS.radius) {\n px *= BOUNDS.radius / kr;\n pz *= BOUNDS.radius / kr;\n }\n }\n // The seabed clamps LAST and samples the DISPLACED position: `dunes`\n // and `ridges` rise to +46, and both the floor band and a grazing\n // nose-down kick can otherwise put a fish inside the hill it is\n // feeding over. Half a body length of clearance, never above the\n // tank's own ceiling.\n if (this.floorHeightAt) {\n const clear = this.floorHeightAt(px, pz) + FISH_LENGTH * 0.5;\n if (y < clear) y = Math.min(BOUNDS.yMax, clear);\n }\n f.group.position.set(px, y, pz);\n f.group.lookAt(px + pose.fx, y + fy, pz + pose.fz);\n f.group.rotateZ(pose.roll);\n\n const breathe = 1 + Math.sin(tSec * 2.1 + f.index) * 0.008;\n f.group.scale.setScalar(f.baseScale * breathe * varn.scaleMul);\n\n // Most of the breed library carries NO animation clip, so those fish\n // translated along their spline completely rigidly — gliding cardboard.\n // A distance-driven yaw on the body fixes the whole library at once and\n // costs one sin per fish. Clipped models skip it: their clip is better.\n if (f.body && !f.mixer) {\n // Write every frame, scaled by wiggle. Skipping the write at 0 left the\n // last offset latched, so turning the dial down stopped the motion but\n // never returned the fish to its own heading.\n const w = Math.min(1.6, wiggle + (mnv.flurry + flurryBoost) * 0.6);\n f.body.rotation.y = f.baseYaw + Math.sin(beat * 0.06 + varn.phase) * 0.55 * w;\n }\n\n if (f.mixer && f.clipDuration > 0) {\n f.mixer.setTime(\n (((beat * 0.045) % f.clipDuration) + f.clipDuration) % f.clipDuration,\n );\n } else if (f.tail) {\n // warpSec === tSec·speed when speed is constant — same phase as before.\n f.tail.rotation.y = Math.sin(warpSec * 6 + f.index) * 0.5;\n }\n\n report.push({\n index: f.index,\n url: f.url,\n breed: this.wantBreeds[f.index] ?? null,\n style: style.name,\n band: bandStyle.band,\n bond,\n seat: style.formation ? seat : null,\n x: Math.round(px * 10) / 10,\n y: Math.round(y * 10) / 10,\n z: Math.round(pz * 10) / 10,\n heading: Math.round(((Math.atan2(pose.fx, pose.fz) * 180) / Math.PI + 360) % 360),\n maneuvering: Math.abs(mnv.side) > 0.02 || Math.abs(mnv.up) > 0.02 || mnv.flurry > 0.05 || Math.abs(mnv.pitch) > 0.02,\n });\n }\n this.lastFish = report;\n this.lastFrameT = t;\n this.rendered = true;\n }\n\n /**\n * The frame in numbers (MQ38): what is in the tank, where, doing what —\n * for `perceiveChannel`, which cannot read a classic saver's pixels into\n * meaning. Reads the last rendered frame; never simulates. Small on\n * purpose: 24 fish × a dozen fields.\n */\n inspect(): Record<string, unknown> | null {\n // \"Nothing yet\" is null by contract — not an empty tank at t=0.\n if (!this.rendered) return null;\n const fish = this.lastFish;\n const byStyle: Record<string, number> = {};\n for (const f of fish) byStyle[f.style] = (byStyle[f.style] ?? 0) + 1;\n const maneuvering = fish.filter((f) => f.maneuvering).length;\n const cx = fish.length ? fish.reduce((a, f) => a + f.x, 0) / fish.length : 0;\n const cy = fish.length ? fish.reduce((a, f) => a + f.y, 0) / fish.length : 0;\n const cz = fish.length ? fish.reduce((a, f) => a + f.z, 0) / fish.length : 0;\n const spread = fish.length\n ? Math.sqrt(fish.reduce((a, f) => a + (f.x - cx) ** 2 + (f.y - cy) ** 2 + (f.z - cz) ** 2, 0) / fish.length)\n : 0;\n return {\n saver: 'metaquarium',\n t: this.lastFrameT,\n camera: {\n azimuth: this.num('cameraAzimuth'),\n elevation: this.num('cameraElevation'),\n distance: this.num('cameraDistance'),\n autoRotate: this.num('autoRotate'),\n },\n room: {\n environment: this.str('environment'),\n floorKind: this.str('floorKind'),\n waterY: this.ceiling ? (this.num('waterY') >= 0 ? this.num('waterY') : this.presetWaterY) : null,\n rayStrength: this.rayMat ? (this.num('rayStrength') >= 0 ? this.num('rayStrength') : this.presetRayStrength) : 0,\n rayPools: this.rayPools.length,\n },\n cast: {\n fishMix: this.str('fishMix'),\n mixMode: this.mixMode,\n wanted: this.wantUrls.length,\n loaded: this.loadedCount(),\n visible: fish.length,\n cap: this.quality.fishCap,\n byStyle,\n swimStyle: this.str('swimStyle'),\n pathShape: this.pathShape,\n formationShape: this.str('formationShape'),\n },\n maneuver: {\n name: this.str('maneuver'),\n rate: this.num('maneuverRate'),\n intensity: this.num('maneuverIntensity'),\n active: maneuvering,\n },\n motion: {\n centroid: { x: Math.round(cx * 10) / 10, y: Math.round(cy * 10) / 10, z: Math.round(cz * 10) / 10 },\n spread: Math.round(spread * 10) / 10,\n lightSeek: this.num('lightSeek'),\n formationBreathe: this.num('formationBreathe'),\n },\n quality: { fishCap: this.quality.fishCap, envBudget: this.quality.envBudget, governor: Math.round(this.govScale * 100) / 100 },\n fish,\n };\n }\n\n // ---- render ----\n\n private renderScene(): void {\n if (this.renderer.getContext()?.isContextLost?.()) return;\n this.renderer.render(this.scene, this.camera);\n }\n\n private start(): void {\n if (this.frameId !== null || typeof requestAnimationFrame === 'undefined')\n return;\n this.clock.resume();\n this.frameId = requestAnimationFrame((now) => this.loop(now));\n }\n\n private stop(): void {\n if (this.frameId !== null) {\n cancelAnimationFrame(this.frameId);\n this.frameId = null;\n }\n this.clock.pause();\n }\n\n private loop(now: number): void {\n this.frameId = requestAnimationFrame((n) => this.loop(n));\n this.governFrame(now);\n this.t = this.clock.sample(now);\n this.setState(this.t);\n this.renderScene();\n }\n\n private pr(): number {\n return Math.max(\n 0.5,\n effectivePixelRatio(this.w, this.h, this.ctxSaver.dpr, this.quality) *\n this.govScale,\n );\n }\n\n private applyPr(): void {\n const pr = this.pr();\n this.renderer.setPixelRatio(pr);\n this.renderer.setSize(this.w, this.h, false);\n }\n\n private governFrame(now: number): void {\n if (this.lastFrameAt > 0) {\n const dt = now - this.lastFrameAt;\n if (dt < 250) this.frameTimes.push(dt);\n }\n this.lastFrameAt = now;\n if (now - this.lastGovCheck < 2000 || this.frameTimes.length < 30) return;\n this.lastGovCheck = now;\n const sorted = [...this.frameTimes].sort((a, b) => a - b);\n const median = sorted[sorted.length >> 1]!;\n this.frameTimes = [];\n if (median > 21 && this.govScale > 0.56) {\n this.govScale *= 0.8;\n this.applyPr();\n } else if (median < 14 && this.govScale < 1) {\n this.govScale = Math.min(1, this.govScale * 1.15);\n this.applyPr();\n }\n }\n\n private renderStill(): void {\n this.setState(this.t);\n this.renderScene();\n }\n\n // ---- SaverInstance ----\n\n setPaused(paused: boolean): void {\n if (this.paused === paused) return;\n this.paused = paused;\n if (paused) {\n this.stop();\n this.renderStill();\n } else {\n this.start();\n }\n }\n\n resize(width: number, height: number, dpr?: number): void {\n this.w = width;\n this.h = height;\n if (dpr !== undefined) this.ctxSaver.dpr = dpr;\n this.applyPr();\n this.camera.aspect = width / height;\n this.camera.updateProjectionMatrix();\n if (this.paused) this.renderStill();\n }\n\n applyTrack(track: ControlTrack): void {\n this.track = track;\n this.speedTracked = track.deltas.some((d) => d.path === 'swimSpeed');\n this.autoRotateTracked = track.deltas.some((d) => d.path === 'autoRotate');\n if (this.paused) this.renderStill();\n }\n\n renderFrame(t: number, _seed: number): void {\n this.t = t;\n this.clock.seek(t);\n this.setState(t);\n this.renderScene();\n }\n\n /**\n * Snapshot the current frame (SaverInstance.capture). Renders fresh and\n * reads in the SAME task: a WebGL buffer without preserveDrawingBuffer is\n * cleared once the frame is presented, so any read from outside the render\n * tick sees black — which also makes this work in a hidden tab, where rAF\n * never fires and there is no render tick to race at all.\n */\n async capture(): Promise<ImageBitmap | null> {\n if (this.disposed || this.renderer.getContext()?.isContextLost?.()) return null;\n this.renderStill();\n try {\n // createImageBitmap snapshots the source at CALL time; the await only\n // covers the decode. Called here, pre-present, the buffer is intact.\n return await createImageBitmap(this.canvas);\n } catch {\n return null;\n }\n }\n\n composition(): SaverLayer[] {\n return [\n {\n id: 'tank',\n label: 'Tank',\n kind: 'surface',\n el: this.canvas,\n description:\n 'WebGL2 aquarium: floor, fog, fish on Catmull-Rom spline paths',\n },\n ];\n }\n\n dispose(): void {\n this.disposed = true;\n this.stop();\n for (const f of this.fish) {\n if (!f) continue;\n f.mixer?.stopAllAction();\n }\n // One ownership-aware pass over the whole scene (fish, floor): frees what\n // this instance created, leaves template-shared resources for the cache.\n // The context loss below reclaims this context's GPU side regardless.\n disposeOwned(this.scene);\n for (const f of this.fish) {\n if (!f) continue;\n this.scene.remove(f.group);\n }\n this.fish = [];\n this.renderer.dispose();\n this.renderer.forceContextLoss();\n if (this.ownsCanvas) this.canvas.remove();\n delete this.ctxSaver.host.dataset.mqFish;\n delete this.ctxSaver.host.dataset.mqMix;\n delete this.ctxSaver.host.dataset.mqMotes;\n delete this.ctxSaver.host.dataset.mqEnv;\n delete this.ctxSaver.host.dataset.mqDraco;\n delete this.ctxSaver.host.dataset.mqBackend;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Factory — single entry point for mounting a tank.\n// ---------------------------------------------------------------------------\n\nexport function mountTank(\n ctx: SaverContext,\n space: ParamSpace = METAQUARIUM_PARAMS,\n tier: CapabilityTier = 'standard',\n catalog?: FishEntry[],\n): SaverInstance {\n const resolved = withDefaults(space, ctx.params);\n const inst = new TankInstance(ctx, resolved, qualityFor(tier), catalog);\n ctx.host.dataset.mqBackend = 'webgl2';\n return inst;\n}\n","/**\n * Does this GLB require Draco? Read from the container rather than trying and\n * catching, because a GLTFLoader without a decoder fails deep inside parse\n * with no usable signal — which is exactly how a Draco model became a silent\n * fallback blob on the wall. A GLB is: 12-byte header, then length-prefixed\n * chunks; the first is JSON. Cheap, synchronous, no allocation beyond the\n * chunk itself.\n */\nexport function needsDraco(buf: ArrayBuffer): boolean {\n try {\n const dv = new DataView(buf);\n if (dv.getUint32(0, true) !== 0x46546c67) return false; // 'glTF'\n const jsonLen = dv.getUint32(12, true);\n const json = new TextDecoder().decode(new Uint8Array(buf, 20, jsonLen));\n return json.includes('KHR_draco_mesh_compression');\n } catch {\n return false;\n }\n}\n\n/** Test seam — the check is pure and three-free on purpose. */\nexport const __needsDracoForTest = needsDraco;\n","import type { Rng } from '@idle-screens/core';\nimport {\n AdditiveBlending,\n Color,\n MeshBasicMaterial,\n type Material,\n type Mesh,\n type MeshStandardMaterial,\n type Object3D,\n} from 'three';\nimport { MIAMI_VICE_COLORS, BLOOM_COLORS } from './manifest';\n\nexport { MIAMI_VICE_COLORS, BLOOM_COLORS };\n\nconst BODY_COATS = MIAMI_VICE_COLORS.filter((c) => c !== '#1c1c1c');\n\nfunction materialsOf(mesh: Mesh): Material[] {\n return Array.isArray(mesh.material) ? mesh.material : [mesh.material];\n}\n\nfunction isEyes(m: Material): boolean {\n return m.name.startsWith('EYES-') || /eye/i.test(m.name);\n}\n\nfunction isGlow(m: Material): boolean {\n return m.name.startsWith('GLOW-') || /glow/i.test(m.name);\n}\n\n/**\n * Colors the GLOW material names spell out. The collection has three naming\n * generations — `GLOW-Orange` (betafish, textured), `GLOW Blue.001` (later\n * breeds, authored emissive), `GLOW-FINS`/`GLOW-claws` (NPC set) — and for\n * the textured generation the NAME is the only color signal the material\n * carries, so this table is how those fins get an honest halo instead of a\n * random one. Longest-prefix wins so `darkblue` beats `blue`.\n */\nconst GLOW_NAME_COLORS: readonly (readonly [string, string])[] = [\n ['darkblue', '#2244ff'],\n ['lightblue', '#7fd4ff'],\n ['crystal', '#cfeaff'],\n ['orange', '#ff7a00'],\n ['purple', '#a45dff'],\n ['yellow', '#ffe93c'],\n ['white', '#ffffff'],\n ['green', '#3bff6e'],\n ['blue', '#2266ff'],\n ['teal', '#00ffc8'],\n ['pink', '#ff5ad0'],\n ['red', '#ff3b30'],\n];\n\n/**\n * The color a GLOW material should bloom with, in trust order: the authored\n * emissive (the later minted generations carry the real color there), then\n * the color spelled in the name, then a seeded pick — never a hardcoded\n * default, so two fish never share a fallback by accident.\n */\nexport function glowColorOf(m: Material, rng: Rng): Color {\n const em = (m as Partial<MeshStandardMaterial>).emissive;\n if (em && em.r + em.g + em.b > 0.02) return em.clone();\n const token = m.name.toLowerCase().replace(/^glow[\\s_-]*/, '').replace(/[^a-z]/g, '');\n for (const [word, hex] of GLOW_NAME_COLORS) {\n if (token.startsWith(word)) return new Color(hex);\n }\n return new Color(rng.pick(BLOOM_COLORS));\n}\n\nexport function forceOpaque(root: Object3D): void {\n root.traverse((node) => {\n const mesh = node as Mesh;\n if (!mesh.isMesh || !mesh.material) return;\n for (const m of materialsOf(mesh)) {\n m.transparent = false;\n m.opacity = 1;\n m.depthWrite = true;\n m.alphaTest = 0;\n m.needsUpdate = true;\n }\n });\n}\n\nexport function eyeNoseSign(root: Object3D, axis: 'x' | 'z'): number {\n let eyeSum = 0;\n let eyeN = 0;\n let bodySum = 0;\n let bodyN = 0;\n root.updateMatrixWorld(true);\n root.traverse((node) => {\n const mesh = node as Mesh;\n if (!mesh.isMesh || !mesh.material || !mesh.geometry) return;\n mesh.geometry.computeBoundingBox();\n const bb = mesh.geometry.boundingBox;\n if (!bb) return;\n const centerLocal = bb.min.clone().add(bb.max).multiplyScalar(0.5);\n const center = mesh.localToWorld(centerLocal);\n const v = axis === 'x' ? center.x : center.z;\n if (materialsOf(mesh).some(isEyes)) {\n eyeSum += v;\n eyeN++;\n } else {\n bodySum += v;\n bodyN++;\n }\n });\n if (eyeN === 0 || bodyN === 0) return 0;\n return Math.sign(eyeSum / eyeN - bodySum / bodyN);\n}\n\n/** Rec. 709 luminance of a material's own color — how `eyes`/`eyes2` (the NPC\n * set names neither white nor black) get sorted into sclera vs pupil. */\nfunction colorLuminance(m: Material): number {\n const c = (m as Partial<MeshStandardMaterial>).color;\n return c ? 0.2126 * c.r + 0.7152 * c.g + 0.0722 * c.b : 1;\n}\n\n/**\n * Seeded palette coat, informed by what the materials actually are:\n *\n * - EYE/EYES → unlit pure white (sclera) or pure black (pupil). The GLBs ship\n * these as 0.8-gray PBR materials, which the hemisphere light renders dim\n * gray — eyes should read as the brightest point on a fish. Name decides\n * (`EYE-WHITE`/`EYE-Black`); when the name says neither (NPC `eyes`/`eyes2`)\n * the authored color's luminance does.\n * - GLOW (untextured) → unlit basic in the material's OWN color: authored\n * emissive first, name-spelled color second, seeded pick last.\n * - GLOW (textured, the betafish generation) → texture kept; the halo pass\n * (addGlowHalos) still blooms it by its name color.\n * - PrimaryColor/SecondaryColor (NPC set) → a two-tone coat: two DISTINCT\n * seeded picks, so an NPC reads as one animal in two colors rather than a\n * patchwork of independent picks.\n * - other untextured → seeded palette coat; textured → untouched (the atlas\n * IS the look).\n */\nexport function applyNpcMaterials(root: Object3D, rng: Rng): void {\n const coatA = rng.pick(BODY_COATS);\n const coatB = rng.pick(BODY_COATS.filter((c) => c !== coatA));\n root.traverse((node) => {\n const mesh = node as Mesh;\n if (!mesh.isMesh || !mesh.material) return;\n const replaced = materialsOf(mesh).map((m) => {\n if (isEyes(m)) {\n const white = /black/i.test(m.name)\n ? false\n : /white/i.test(m.name) || colorLuminance(m) >= 0.5;\n const eye = new MeshBasicMaterial({ color: white ? 0xffffff : 0x000000 });\n eye.name = m.name;\n eye.userData.mqOwned = true;\n return eye;\n }\n if (isGlow(m) && !(m as Partial<MeshBasicMaterial>).map) {\n const glow = new MeshBasicMaterial({ color: glowColorOf(m, rng) });\n glow.name = m.name;\n // The halo pass reads this back so shell and core NEVER disagree —\n // resolving twice would replay the rng differently in the fallback.\n glow.userData.mqGlowColor = glow.color.getHex();\n // Ours to dispose at fish teardown. Textured materials stay the\n // template's — disposing those would corrupt every other clone.\n glow.userData.mqOwned = true;\n return glow;\n }\n const map = (m as Partial<MeshBasicMaterial>).map;\n if (map) {\n // The metal trap: glTF's DEFAULT metallicFactor is 1.0, and a pure\n // metal under our hemisphere light (no environment map) renders\n // BLACK — the jellyfish shipped that way. Unlit-basic the atlas so\n // the texture reads at full brightness; non-metal atlases keep their\n // authored material untouched.\n const metalness = (m as Partial<import('three').MeshStandardMaterial>).metalness ?? 0;\n if (metalness >= 0.5) {\n const atlas = new MeshBasicMaterial({ map });\n atlas.name = m.name;\n atlas.userData.mqOwned = true;\n return atlas;\n }\n return m;\n }\n const coat = /primary/i.test(m.name)\n ? coatA\n : /secondary/i.test(m.name)\n ? coatB\n : rng.pick(BODY_COATS);\n const body = new MeshBasicMaterial({ color: new Color(coat) });\n body.name = m.name;\n body.userData.mqOwned = true;\n return body;\n });\n mesh.material = Array.isArray(mesh.material) ? replaced : replaced[0]!;\n });\n}\n\n/** Shells a glow part may cast. Few parts → two shells (tight + wide) for a\n * soft falloff; many parts → one, so a five-fin fish costs five extra draws,\n * not ten. */\nconst HALO_PART_CAP = 6;\n\nfunction haloMaterial(color: Color, push: number, opacity: number): MeshBasicMaterial {\n const mat = new MeshBasicMaterial({\n color,\n transparent: true,\n opacity,\n blending: AdditiveBlending,\n depthWrite: false,\n // NOT fogged: additive blending MIXES toward the fog color, and against a\n // lit environment fog (lagoon pink) a distant shell would ADD pink boxes.\n fog: false,\n });\n mat.onBeforeCompile = (shader) => {\n shader.uniforms.uHaloPush = { value: push };\n shader.vertexShader = `uniform float uHaloPush;\\n${shader.vertexShader.replace(\n '#include <begin_vertex>',\n '#include <begin_vertex>\\n\\ttransformed += normal * uHaloPush;',\n )}`;\n };\n // One program for every halo — only the uniform differs.\n mat.customProgramCacheKey = () => 'mq-glow-halo';\n mat.userData.mqOwned = true;\n return mat;\n}\n\n/**\n * Selective bloom, the cheap honest way: for each mesh wearing a GLOW\n * material, add additive shells of the same geometry pushed out along the\n * vertex normals. No EffectComposer, no extra render target — the cost is a\n * couple of extra draws per glow part, and it composes with fog and the\n * governor untouched. Shells share the source's geometry (template-owned,\n * never tagged), so only the halo materials are ours to dispose.\n *\n * Runs AFTER applyNpcMaterials, so untextured glow parts already carry their\n * resolved color — the shell samples the same resolution order and matches.\n */\nexport function addGlowHalos(root: Object3D, rng: Rng): number {\n const glowMeshes: Mesh[] = [];\n root.traverse((node) => {\n const mesh = node as Mesh;\n if (!mesh.isMesh || !mesh.material || mesh.userData.mqHalo) return;\n // Multi-material meshes are skipped: a single-material shell over grouped\n // geometry would bloom the whole body, not the glow slots. GLTF primitives\n // arrive single-material, so in practice this skips nothing.\n if (Array.isArray(mesh.material)) return;\n if (isGlow(mesh.material)) glowMeshes.push(mesh);\n });\n const parts = glowMeshes.slice(0, HALO_PART_CAP);\n if (parts.length === 0) return 0;\n // Push is quoted against the WHOLE MODEL, never the part. Crystal-finned\n // breeds carry a glow part LARGER than their body (seahorse: 27.7 vs 15.3\n // half-diagonal), and part-proportional shells turned those into a\n // displaced ghost of the entire fish — the \"shadow\" a viewer reported from\n // the wall. Against the model, the rim stays a rim: ~1.5% and 3.5% of the\n // fish, well under one voxel, so the cube-normal face separation is\n // subpixel too.\n let modelR = 0;\n for (const mesh of parts) {\n mesh.geometry.computeBoundingSphere();\n modelR = Math.max(modelR, mesh.geometry.boundingSphere?.radius ?? 1);\n }\n root.traverse((node) => {\n const mesh = node as Mesh;\n if (mesh.isMesh && mesh.geometry) {\n mesh.geometry.computeBoundingSphere();\n modelR = Math.max(modelR, mesh.geometry.boundingSphere?.radius ?? 0);\n }\n });\n const shells: readonly (readonly [number, number])[] =\n parts.length <= 3 ? [[0.015, 0.3], [0.035, 0.14]] : [[0.025, 0.26]];\n let added = 0;\n for (const mesh of parts) {\n const stored = (mesh.material as Material).userData.mqGlowColor as number | undefined;\n const color = stored !== undefined ? new Color(stored) : glowColorOf(mesh.material as Material, rng);\n const partR = mesh.geometry.boundingSphere?.radius ?? 1;\n // A glow part that IS most of the silhouette gets one faint veil, not a\n // bright double — its shell already traces the whole fish.\n const large = partR > modelR * 0.55;\n const partShells = large ? [shells[0]!] : shells;\n const dim = large ? 0.55 : 1;\n for (const [k, opacity] of partShells) {\n const halo = mesh.clone();\n halo.material = haloMaterial(color, modelR * k, opacity * dim);\n halo.userData.mqHalo = true;\n halo.renderOrder = 2;\n mesh.parent?.add(halo);\n added++;\n }\n }\n return added;\n}\n","import { integrateParam, type ControlTrack, type ParamSpace } from '@idle-screens/core';\n\n/** Logical media clock that excludes time spent paused. */\nexport class LogicalClock {\n private base = 0;\n private origin: number | null = null;\n private current = 0;\n private paused = true;\n\n resume(): void {\n this.paused = false;\n this.origin = null;\n }\n\n pause(): void {\n this.base = this.current;\n this.paused = true;\n }\n\n sample(now: number): number {\n // A sample taken while paused (or before the first resume) must not\n // advance — origin==null alone can't tell \"just resumed\" from \"still\n // paused\", since both leave it null.\n if (this.paused) return this.current;\n if (this.origin === null) this.origin = now;\n this.current = this.base + now - this.origin;\n return this.current;\n }\n\n seek(t: number): void {\n this.base = t;\n this.current = t;\n this.origin = null;\n }\n}\n\n/** Integrate a rate parameter without rescaling all motion accumulated before a steer. */\nexport function rateOffset(\n space: ParamSpace,\n track: ControlTrack | null,\n path: string,\n t: number,\n sampledRate: number,\n tracked: boolean,\n): number {\n if (!tracked || !track) return sampledRate * (t / 1000);\n const def = space[path];\n return integrateParam(space, track, path, t, {\n ...(def?.min !== undefined ? { min: def.min } : {}),\n ...(def?.max !== undefined ? { max: def.max } : {}),\n }) / 1000;\n}\n"],"mappings":"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","names":["integrateParam","AdditiveBlending","Color","MeshBasicMaterial","Color","MeshBasicMaterial","AdditiveBlending","integrateParam"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@idle-screens/saver-metaquarium",
3
- "version": "0.7.1",
3
+ "version": "0.7.2",
4
4
  "description": "Metaquarium screensaver for idle-screens: a three.js fish tank with seeded analytic swim, GLB fish streamed from the Metaquarium farm, seeded neon palette coats, and a steerable camera. The screen is a tank.",
5
5
  "license": "MIT",
6
6
  "author": "Shawn Partridge",