@idle-screens/saver-metaquarium 0.4.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-BWIJDJPQ.js → chunk-52CJC2KV.js} +8 -3
- package/dist/{chunk-BWIJDJPQ.js.map → chunk-52CJC2KV.js.map} +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/{manifest--GHWtt5W.d.ts → manifest-CapCN3BJ.d.ts} +3 -1
- package/dist/manifest.d.ts +1 -1
- package/dist/manifest.js +1 -1
- package/dist/{tank-2TH4D2EE.js → tank-HV27OOXH.js} +12 -3
- package/dist/tank-HV27OOXH.js.map +1 -0
- package/package.json +1 -1
- package/dist/tank-2TH4D2EE.js.map +0 -1
|
@@ -828,11 +828,16 @@ function parseFishMix(mix, catalog = FISH_CATALOG) {
|
|
|
828
828
|
let count = 1;
|
|
829
829
|
if (countRaw !== void 0) {
|
|
830
830
|
const n = Number(countRaw.trim());
|
|
831
|
-
if (!Number.isInteger(n) || n < 1
|
|
831
|
+
if (!Number.isInteger(n) || n < 1) {
|
|
832
832
|
problems.push(`"${token}": count must be an integer 1-24`);
|
|
833
833
|
continue;
|
|
834
834
|
}
|
|
835
|
-
|
|
835
|
+
if (n > 24) {
|
|
836
|
+
problems.push(`"${token}": count clamped to 24`);
|
|
837
|
+
count = 24;
|
|
838
|
+
} else {
|
|
839
|
+
count = n;
|
|
840
|
+
}
|
|
836
841
|
}
|
|
837
842
|
entries.push({ id: fish.id, url: fish.ipfs3d, count });
|
|
838
843
|
}
|
|
@@ -1063,4 +1068,4 @@ export {
|
|
|
1063
1068
|
paramSpaceWith,
|
|
1064
1069
|
coerceNum
|
|
1065
1070
|
};
|
|
1066
|
-
//# sourceMappingURL=chunk-
|
|
1071
|
+
//# sourceMappingURL=chunk-52CJC2KV.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/environments.ts","../src/swim.ts","../src/asset-cids.ts","../src/farm.ts","../src/ipfs.ts","../src/manifest.ts"],"sourcesContent":["/**\n * Environments — the tank as a room.\n *\n * The original aquarium switches whole VENUES, not backdrops: turning its\n * EXHIBIT knob moves from open shader terrain under a water ceiling to an\n * indoor hall with walls and a striped floor. Our tank has one room — a disc\n * under fog — so the gap was never \"more knobs\", it was that there is no room.\n *\n * This is that room, as data. An `environment` names a PLACE; an agent picks\n * one instead of dialling thirty numbers, and the values below are the taste.\n * Design and the browser session behind it:\n * `idle-mono/docs/metaquarium-environment-spec.md`.\n *\n * Zero-dep on purpose — the server validates through `./manifest` without\n * pulling three.js.\n */\n\nexport type EnvironmentName =\n | 'void' | 'abyss' | 'reef' | 'kelp' | 'ice' | 'vent' | 'lagoon' | 'universe';\n\n/** Terrain silhouette. `flat` is the original disc; the rest are generated\n * from the mount seed — never fetched, so determinism and offline both hold. */\nexport type FloorKind = 'flat' | 'dunes' | 'ridges' | 'basin';\n\n/** Runtime guard for the same set — the steering lane is unvalidated, so a\n * bad value must fall back rather than build an invisible floor. */\nexport const FLOOR_KINDS: readonly FloorKind[] = ['flat', 'dunes', 'ridges', 'basin'];\n\n/** A translucent plane ABOVE the fish. The single strongest identity cue in\n * the original: it is what makes a scene read as *under* something. */\nexport interface WaterCeiling {\n /** World Y. The tank's fish swim between y=15 and y=72. */\n y: number;\n color: string;\n opacity: number;\n}\n\n/** Volumetric shafts. `y` may be NEGATIVE — the original's raysLightY swings\n * ±2000, and light from below is the difference between a sunlit surface and\n * an abyssal glow. */\nexport interface Rays {\n strength: number;\n color: string;\n y: number;\n}\n\nexport interface EnvironmentPreset {\n name: EnvironmentName;\n label: string;\n water: WaterCeiling | null;\n floor: FloorKind;\n rays: Rays | null;\n}\n\n/**\n * The catalogue. `void` is EXACTLY today's scene — no ceiling, flat floor, no\n * rays — so the default is a no-op and every already-published scene renders\n * unchanged. That is the whole reason the enum is safe to ship first.\n *\n * Deliberately NOT set here: fog, floor colour, motes. Those are already\n * steerable params, and an environment that quietly overrode them would make\n * \"did the author choose this, or did the preset?\" unanswerable. An\n * environment supplies the ROOM; the palette stays the author's.\n */\nexport const ENVIRONMENTS: readonly EnvironmentPreset[] = [\n { name: 'void', label: 'Void', water: null, floor: 'flat', rays: null },\n { name: 'abyss', label: 'Abyss', water: null, floor: 'basin',\n rays: { strength: 0.35, color: '#0e4a6e', y: -620 } },\n { name: 'reef', label: 'Reef', water: { y: 150, color: '#2ad4ff', opacity: 0.18 },\n floor: 'dunes', rays: { strength: 0.5, color: '#bfefff', y: 900 } },\n { name: 'kelp', label: 'Kelp forest', water: { y: 170, color: '#1e6f5c', opacity: 0.2 },\n floor: 'ridges', rays: { strength: 0.4, color: '#9ef5d0', y: 820 } },\n { name: 'ice', label: 'Under ice', water: { y: 132, color: '#cfefff', opacity: 0.32 },\n floor: 'flat', rays: { strength: 0.6, color: '#eaf8ff', y: 1000 } },\n { name: 'vent', label: 'Hydrothermal vent', water: null, floor: 'ridges',\n rays: { strength: 0.45, color: '#ff7a3c', y: -520 } },\n { name: 'lagoon', label: 'Lagoon', water: { y: 118, color: '#7ff3d0', opacity: 0.22 },\n floor: 'dunes', rays: { strength: 0.55, color: '#fff3b0', y: 950 } },\n { name: 'universe', label: 'Universe', water: null, floor: 'ridges',\n rays: { strength: 0.3, color: '#c39bff', y: 1200 } },\n];\n\nexport const ENVIRONMENT_NAMES: readonly EnvironmentName[] =\n ENVIRONMENTS.map((e) => e.name);\n\nexport function environmentOf(name: string): EnvironmentPreset {\n return ENVIRONMENTS.find((e) => e.name === name) ?? ENVIRONMENTS[0]!;\n}\n\n// ---------------------------------------------------------------------------\n// Cost budget\n// ---------------------------------------------------------------------------\n\n/**\n * What each layer costs, and what a tier can afford.\n *\n * The arch research asked for a cost model \"before rich scenes ship\", and this\n * is that, scoped to one saver. The floor is free because it is generated once\n * at mount and never touched per frame; the ceiling is one extra draw call;\n * rays are the only per-frame work worth rationing.\n */\nexport const LAYER_COST = { floor: 0, water: 1, rays: 2 } as const;\n\n/** Shafts at full and at reduced count. A rays-only place keeps its identity\n * on a weak device by running fewer shafts rather than none. */\nexport const RAY_COUNT = { full: 5, reduced: 2 } as const;\n\n/**\n * Layers in priority order until the budget runs out: a device that can only\n * afford one thing keeps the ceiling, the cue that carries most of the read.\n *\n * But `abyss`, `vent` and `universe` have NO ceiling — their whole identity is\n * the glow — so spending ceiling-first would silently leave them as bare\n * terrain on exactly the devices that most need a recognisable scene. When\n * nothing higher-priority claims the budget, rays run at a reduced shaft count\n * instead of not at all.\n */\nexport function affordableLayers(budget: number, preset: EnvironmentPreset): {\n floor: boolean; water: boolean; rayCount: number;\n} {\n let left = budget;\n const water = preset.water !== null && left >= LAYER_COST.water;\n if (water) left -= LAYER_COST.water;\n let rayCount = 0;\n if (preset.rays !== null && left > 0) {\n rayCount = left >= LAYER_COST.rays ? RAY_COUNT.full : RAY_COUNT.reduced;\n }\n return { floor: true, water, rayCount };\n}\n","/**\n * Swim styles — a few places a fish can be, not a pile of motion knobs.\n *\n * Same shape as environments, for the same reason: a curated set is how you\n * ship taste, and an agent picks a BEHAVIOUR rather than tuning six numbers\n * until it looks accidental. The uniqueness lives in two dials\n * (`swimVariance`, `bodyWiggle`) that spread a school out without anyone\n * having to author per-fish values.\n *\n * Everything here is pure and closed-form in distance/time, so\n * `renderFrame(t, seed)` stays frame-addressable — a style that needed\n * accumulated state would not be shippable at all.\n *\n * Zero-dep: the server validates through `./manifest` without three.js.\n */\n\nexport type SwimStyle =\n | 'loop' | 'school' | 'drift' | 'hover' | 'patrol' | 'bottom' | 'surface';\n\n/** Where in the water column a style lives. */\nexport type DepthBand = 'free' | 'floor' | 'ceiling' | 'mid';\n\nexport interface SwimStyleSpec {\n name: SwimStyle;\n label: string;\n /** Tempo baked into the style — a patrolling shark is not a darting shoal.\n * Multiplies the author's swimSpeed rather than replacing it. */\n speedMul: number;\n band: DepthBand;\n /** Vertical bob, the thing that separates \"alive but holding station\" from\n * \"parked\". Amplitude in world units, rate in Hz. */\n bobAmp: number;\n bobHz: number;\n /** Ride one shared route in formation (the carrier school) instead of each\n * fish owning a loop. */\n formation: boolean;\n /** How FAST a fish works its own loop, relative to how hard it is swimming.\n * Below 1 it stays in a patch of water instead of touring the tank — what\n * makes hovering read as hovering without any new path maths.\n *\n * Not a leash: a hover fish still gets all the way round eventually (about\n * 80 minutes). At any timescale anyone is watching it reads as staying\n * put, which is the effect being bought — but the loop is not fenced. */\n travel: number;\n}\n\n/**\n * The catalogue. `loop` is exactly the pre-style behaviour, so the default\n * changes nothing and every published scene renders as before. That holds for\n * every param in this file's orbit, `bodyWiggle` included: each one defaults\n * to the old look and has to be asked for.\n *\n * Deliberately small. Each entry is a silhouette of movement a viewer can\n * name — patrol, hover, drift — because a set you can name is a set you can\n * choose from; twelve variations on \"swim\" would just be noise.\n */\nexport const SWIM_STYLES: readonly SwimStyleSpec[] = [\n { name: 'loop', label: 'Loop', speedMul: 1, band: 'free', bobAmp: 0, bobHz: 0, formation: false, travel: 1 },\n { name: 'school', label: 'School', speedMul: 1, band: 'free', bobAmp: 1.5, bobHz: 0.5, formation: true, travel: 1 },\n { name: 'drift', label: 'Drift', speedMul: 0.35, band: 'mid', bobAmp: 5, bobHz: 0.13, formation: false, travel: 0.6 },\n { name: 'hover', label: 'Hover', speedMul: 0.2, band: 'free', bobAmp: 7, bobHz: 0.3, formation: false, travel: 0.12 },\n { name: 'patrol', label: 'Patrol', speedMul: 0.55, band: 'mid', bobAmp: 1, bobHz: 0.09, formation: false, travel: 1 },\n { name: 'bottom', label: 'Bottom-hugger', speedMul: 0.7, band: 'floor', bobAmp: 2, bobHz: 0.4, formation: false, travel: 1 },\n { name: 'surface', label: 'Surface-skimmer', speedMul: 0.9, band: 'ceiling', bobAmp: 3, bobHz: 0.55, formation: false, travel: 1 },\n];\n\nexport const SWIM_STYLE_NAMES: readonly SwimStyle[] = SWIM_STYLES.map((s) => s.name);\n\nexport function swimStyleOf(name: string): SwimStyleSpec {\n return SWIM_STYLES.find((s) => s.name === name) ?? SWIM_STYLES[0]!;\n}\n\n// ---------------------------------------------------------------------------\n// Per-fish uniqueness\n// ---------------------------------------------------------------------------\n\n/** Deterministic 0..1 hash of a fish index and a salt. Not an rng: a fish's\n * variation must be recoverable from its index alone, so nothing depends on\n * spawn order or on how many times the tank has been rebuilt. */\nexport function fishHash(index: number, salt: number): number {\n const x = Math.sin((index + 1) * 127.1 + salt * 311.7) * 43758.5453;\n return x - Math.floor(x);\n}\n\n/**\n * Per-fish spread. `variance` 0 means a uniform shoal in the things an author\n * is asking about — size and pace; 1 means every fish is visibly its own\n * animal. This is the \"expandable uniqueness\" dial: one number an author\n * turns up, rather than per-fish values nobody wants to write.\n *\n * Two fields deliberately IGNORE `variance` — `phase` and `anchor`. Both are\n * desynchronisation, not flavour: a shoal that bobs and beats its tail in\n * perfect unison reads as one animation played N times, and a station-keeping\n * style with a shared anchor knots every fish into the same corner. Turning\n * variance to 0 should give you a uniform population, not a chorus line.\n */\nexport function fishVariation(index: number, variance: number): {\n speedMul: number; scaleMul: number; phase: number; anchor: number;\n} {\n const v = Math.max(0, Math.min(1, variance));\n return {\n // ±40% speed and ±25% size at full variance — enough to read as a mixed\n // population, not so much that one fish looks broken.\n speedMul: 1 + (fishHash(index, 1) - 0.5) * 0.8 * v,\n scaleMul: 1 + (fishHash(index, 2) - 0.5) * 0.5 * v,\n // Independent of `variance` — see the note above; synchronised bobbing is\n // a machine, not a shoal.\n phase: fishHash(index, 3) * Math.PI * 2,\n // Where along its own route a fish holds station, 0..1. Styles that only\n // work a patch of water (hover, drift) start every fish at distance 0\n // without this, so the whole shoal knots up in one corner — which is\n // exactly what it did the first time I looked at `hover` on screen.\n // Independent of `variance`: spreading is correctness, not flavour.\n anchor: fishHash(index, 7),\n };\n}\n\n/** Nose-to-tail length of a fish at scale 1, in tank units. Formation spacing\n * is quoted in these so the lattice cannot silently start interpenetrating\n * when the fish change size — a review measured 36% of fish-frames with a\n * neighbour inside one body length when the pitch was a bare number. */\nexport const FISH_LENGTH = 18;\n\n/** Half-width the formation lattice is allowed to occupy, in tank units. The\n * tank still clamps the final position — this keeps the shape sane, the\n * clamp keeps it legal. */\nconst FORMATION_HALF_WIDTH = 62;\n\n/**\n * Formation slot for the carrier school, in the carrier's local frame.\n *\n * A jittered lattice, NOT rejection sampling: the spike's sampler could\n * exhaust its candidate box and fail to place a fish, which is a crash\n * wearing a costume. A lattice always terminates, and the jitter keeps it\n * from looking like a parade ground.\n */\nexport function formationSlot(\n index: number, count: number, variance: number, halfWidth = FORMATION_HALF_WIDTH,\n): { side: number; up: number; back: number } {\n const cols = Math.max(1, Math.ceil(Math.sqrt(Math.max(1, count))));\n const col = index % cols;\n const row = Math.floor(index / cols);\n const j = 0.35 + 0.65 * Math.max(0, Math.min(1, variance));\n // Spacing shrinks as the cast grows so the formation's own width stays\n // inside `halfWidth`. At 24 fish a fixed 26-unit column pitch spread the\n // shoal wider than the tank, and the outer ranks swam through the glass.\n const span = Math.max(1, cols - 1);\n // Spacing is quoted in body lengths, and the jitter is a FRACTION of the\n // pitch rather than a fixed number of units. Both matter: a bare 26/22 was\n // narrower than a fish is long once jitter ate into it, so the shoal\n // interpenetrated at every variance including 0.\n const pitch = Math.min(FISH_LENGTH * 1.9, (halfWidth * 2) / span);\n // Jitter is bounded so the CLOSEST possible pair still clears a body\n // length: adjacent columns are `pitch` apart and jitter can close at most\n // `jit`, so pitch - jit must stay above FISH_LENGTH. That is the whole\n // reason the spacing is quoted in body lengths at all.\n const jit = pitch * 0.35;\n return {\n side: (col - (cols - 1) / 2) * pitch + (fishHash(index, 4) - 0.5) * jit * j,\n up: (fishHash(index, 5) - 0.5) * FISH_LENGTH * j,\n back: row * FISH_LENGTH * 1.6 + fishHash(index, 6) * FISH_LENGTH * 0.4 * j,\n };\n}\n\n/**\n * How far the formation reaches from its own centre, per axis. The tank uses\n * it to pull the WHOLE shoal inside the glass by moving its centre, instead of\n * squashing individual fish toward the middle — squashing is what turned a\n * legal lattice back into a pile.\n */\nexport function formationExtent(count: number, variance: number): {\n side: number; up: number; back: number;\n} {\n let side = 0, up = 0, back = 0;\n for (let i = 0; i < count; i += 1) {\n const s = formationSlot(i, count, variance);\n side = Math.max(side, Math.abs(s.side));\n up = Math.max(up, Math.abs(s.up));\n back = Math.max(back, Math.abs(s.back));\n }\n return { side, up, back };\n}\n\n/** Depth band as a fraction of the tank's vertical extent — the tank owns the\n * actual bounds, this owns the intent. */\nexport function bandRange(band: DepthBand): { lo: number; hi: number } | null {\n switch (band) {\n // Tight, because 0..0.3 of a 57-unit volume is nearly two body lengths of\n // headroom — measured on screen it read as hovering in the lower third\n // rather than hugging anything.\n case 'floor': return { lo: 0, hi: 0.14 };\n case 'ceiling': return { lo: 0.7, hi: 1 };\n case 'mid': return { lo: 0.3, hi: 0.7 };\n default: return null;\n }\n}\n","/**\n * ASSET_CIDS — the per-fish IPFS asset directory CID for all 512 minted fish,\n * indexed by `tokenId - 1`.\n *\n * Baked deliberately. Each fish's asset directory hash is unguessable, so the\n * alternative is a metadata fetch per fish before a single frame can render —\n * an intermediary (the AWS farm API, or a gateway round-trip) on the critical\n * path of a screensaver. This table makes asset resolution PURE: id in, URL\n * out, offline, no network, no CORS question, no third party.\n *\n * Provenance: the farm's `/y2k/stream/cache` (512 entries, complete, no gaps),\n * cross-checked against the on-chain metadata directories 2026-08-15. The\n * collection is closed at 512 by `totalSupply()`, so this table is final —\n * it can only change if assets are re-pinned under new CIDs, which is a\n * deliberate migration, not drift.\n *\n * Regenerate: scripts/fetch-farm-manifest.mjs (documented in\n * idle-mono/docs/metaquarium-farm-api.md).\n */\nexport const ASSET_CIDS: readonly string[] = [\n 'QmZCzKVLJ1MYRzaZEFyuMCVmrPR3urQYMFR9RWerJYpCEr', 'Qma79eBTFDb2i1CtF96R6coxtnEnTu8qgW8xTTt9wdKdsW', 'QmZrjPzPrkHWNfYJWCKadGxqrhwVJu3zsovCAuYRw8EzAt', 'QmRzCWYvpRMuGX7MUrXFDQ6SEzxvfiJy2XCc2KFT9nZRwH',\n 'QmYz6VAVyJb4XdhNkcByQsTMedyLUPuy13q835XBqBvFnN', 'QmNadqTMC59SV41Ljqs3wftQRMnKzP5HfHhJhNUGaop187', 'QmVo9GNRMZRhdykEfsasiKCkSxJ3NZMnJW2teu3ydM3j3C', 'QmNY8rACjywo9MCnq5ZRLqDovCeD3rThQkquVhBNZpDx5G',\n 'QmYSmYXaGe9sjyccj3BDfaWpjrRHfQut2KUj4PPqfNP2JQ', 'QmeXJpHtuo2xdyLLHVjYn4DnkmAmYQChfwYMNgH738342g', 'QmXQ8sWSv8wVzpizPy7XAEg4fkGULegqhYmmmNxQxFhURF', 'QmSGLEWtsJfzRDtyg6qirHjVAWmojFu4DTNMjgkgGYhaQX',\n 'QmQz6mraCUgznbC2gAhv4xfN94baioDXvSwjxUY9HFdcUc', 'QmRNkt5myZfwJ4ja9s6mxkSvcAUaFmmKQT6EpVVU9EpxXJ', 'QmUNQ4m28QSNadjS78XjufrY6tXBsdoAJXV3wP25Mw8ECE', 'QmYy8J3L34p6db2qpqsxC8fMS635KretGDoGTh7Ro8t36N',\n 'QmZPL8gWr26b2CNNo6LGFJ8dn1sQMcpb1bnrmjJN3mRBPP', 'Qmbsuivz8SHZS89ss8PeCuaeZBv1m5Vwe6b841YkSt57Gr', 'QmYcF95XchfmViqJsHgzKTP33gVxCUHzq7dmxnWuh4JbbE', 'QmVuzAQutncvQBCGY43tapphaCNNoawVydEfHo48nuQG53',\n 'QmWL4zmX8QnSwcEhrxb6Ek9FWnodT75fK5jQE9G3nhrTHT', 'QmWdgysw3gxrJ3Hy129Ej7NcGW5D2utvWTMie2CA4tXjf5', 'QmW84KhqabL5niFwknUvU2dx5KCgkW9gEU4513m82fguSK', 'QmYtfYGfPRQdd2v2e2mVezgiY9qZGXmLUn4TLnhBUMfsk3',\n 'QmTgu5xQ7a2ZDnSbSeDZwVWYJpoi2LHSEA7Q172ykHgEcn', 'QmbUSdfjzd8EVsJtzt8iXmCwLPeDC84ScriwtEau6W5WUz', 'Qmd3y3u9kzHtKPvrsBraQX9VYfWKHzRgTE2Wchhrm9ub8M', 'QmbxDsnC14zmzKShpRTscTaKJayTSaCaSxW2gKRc7g8Xfq',\n 'QmeKFPkwPTUU8GFVpNfFjsBKr3nfsRMo138EiDBYyoi5iB', 'QmUT7B543xj7TYkgbSspBDrHsJHG2yJTvWPy6iVEcP54HM', 'QmNzFPTar1xFa5QJVAuksyDpJTwK9mARRbNWGZ456Ja3sE', 'QmPiYEDAJPUA7pjqjBz3xTCFcVEFa57DJoZnFaoU49FGZ2',\n 'QmTsngNyu277DGD3qg1u89nTSBHp6QpeyNjoQPNEaNaqtU', 'QmSK68pRT68B4oBxP2LjVCNJBYmwsSpkykGmgW5Fgc9HAz', 'QmTVjYdCrexSTbMkmzBC6rNbYQF8AddQi3gPbGQqwz8iqU', 'QmdFFcK9hHWKyShCX8gHLUdrWf4aKMtJonfmKGDac3BdrX',\n 'QmeYJm1xn5hnkpZSfrYcMbpmT4u8XmaSmehhnyoDttLdFB', 'QmNbydhwhNHemQwebQvhDihnc3n1QiNFPueR46ZWZSsxYa', 'QmYy1uXan36v31H4znXmL855XrStjxtyjsxEaeUngdRzRt', 'QmW7yf2JMmpM3bAauiomXhWm75ur4Lu5GASywFanSS7yp4',\n 'QmPJYvY9bEpEhDoyH5CfGkpMGgk9cppPe22vD2yechR2Pv', 'QmYEbrDdFnfqvnkfnUwDC6tXoC1zwFmaAfsJmjM3bUd8ST', 'QmSBQYv4Gbd3SykKyieHcNwT4uaRE6GPsaJkB4RH7Z5QMT', 'QmTcsXsMTqfgad6VgQxqgR5htFPhRovdB4CrNoF8RjVR9v',\n 'QmdK65oNZpQibD2TP9QurwtuHMjfQTQBEgTx3v8ywy1q6q', 'Qma23MMRv6Jv95QKg1SuSgUcmKiBbrgLQYcTGonT3FVmLP', 'QmU38DaWwsKvxh69YrrKpLwnYBuNd5RMxE4MLVUjZDj5ov', 'QmNRASdujvSTCJYHNUVfqSDWeR8DPqdbXsz9PmNTAkzZVR',\n 'QmfAMeBwHY4RXqSNwiqmv85nKNcfMxn6gg5gouhfjeqsnF', 'QmbcpM2J9kCJ8kEaN7avcum6BpcebHSML9f4iEUmwDQkGf', 'QmTrmBe8r7F6mEaUscVuzDg7WS2iP6rmDLhEx5BprrSMRT', 'QmdRWCzJuza5Eq6n8f8YPcFPxbbDBUxn9CF5NASTQrkzzv',\n 'QmSRCQUAE4L8aUTB3kxp7SLjKwk5WkCocRJc6GEUjtMqVg', 'QmdvomiXzh97Jx7MHhbb2b2y8Fr5UrUMybdiWcGCJ8gCKM', 'QmNpRThE4ors6zBb68gz98pJUneDzNN3hdVo43hLzQaSCf', 'QmfSGJm4RqUVvYsrbS5txWypPb3rRoQQS8kpcZnbf5drqm',\n 'QmUH8DBSDSev99CpiYNxd4wc7xPfFYZ2SQ1gb5zdcry3gX', 'QmZc5WQo9vvMHhUNwLg9nXCrynXMPu2L9KYAV6LD3NDm4y', 'QmYtDyPEd2BcgJLgZjj6JfpVhy3ZYA23GEGRGB4zRqrP2R', 'QmZeG9tvLDPRbBVJMZs76caJBWfHcCY599vA7PeY9yWk3n',\n 'QmdpQFyGFRgdK5FH8tQZFWcCb5YS27BG2nYH2jkfrP9c6x', 'QmaWVfUWr41XpGrhzU8EZA6khTeMVgSZ5FU1sQACRGzw6v', 'QmW8Nanwbwoe5ZWaSAN5w6SbHGYAzE1Uje5wNJySkULWZF', 'QmThgMpfZXYxrneAzYeUJwBa5gAjG3rGYXKLyx6kJSEi8D',\n 'QmQbdofeVFNqsMfYrJxoQbobyz8PXX3ghdtTKK2NtTv6kQ', 'QmbmNr5NUKCfxxPrkCwQhpYMyvYCWsMEfFmYJLUf8MWVzM', 'QmWtDac1r2sNyUUx2LcN7VeJwcdxno6x4iRp2VD7zHRhyo', 'QmTDxjfns7kPpQVWzDbhJfdczUb4RZ5jiu7KetmhhjNo2Z',\n 'QmcMVUXcDjQgEm9YJsAQykmnnyYDFowqiLaEkUyuD43rKr', 'QmWQudrmXa9TvPe4jC6JYCyUuogCmficYDytbis1dQTK8A', 'QmazJBD9H1dJeM6SjxpKAEoJqL9QY7ie2v8p3Qk5sHc77M', 'QmexHy2WQbF2w2bToTa8V2JTZZxG1f8C7TjfxVFjvvLCY9',\n 'QmXq23WvtUdbNGRAzHJen2JLGGsTkMQx5CcyJjw6BAoscS', 'QmRRgSeYAwBBhTMBCUTEVPRMH3B9if16oiuntaQBjNSser', 'QmTB273BcmsW4Gkm3MyUEkK4zH8QFkQvnTnDdapyGzYXMv', 'QmZok97rbnUDoEKiwJ1gvwBzKhhFeqWbmw92KfppvmcwJA',\n 'QmUDPUMg946iVUZM7iA7xENaP7VYp7KHfXBM9EkiWLcZU5', 'QmZXqUUaRDDFwG2HVZWvXSoa4j3eVBHLWdtTEMe9meD1c5', 'QmRTiH27NJzrRnsQw8kNTSc9iUG813HweZN4HznQCgkqry', 'Qmc3K2yyS5tG7yhDVaFVUjPRnuP7MahjA8LQJDqv9ZhN1s',\n 'QmYVdyMo7izizLFMk4FEb9WUqRuL39Wi6Xw8h6aaPqe65f', 'QmVwt4bK9Ycj7VL4hLnbHojLYuGvm4FXbGeduwk5hWwFzX', 'QmcQhyTqt6cGj6UJx1dEGqPHjdncY6Jpy5DXE9ZuNJ9uY7', 'QmV6gJiqNJpk6JuTYuA2y78TMRKrGRWD4wmVyriqocgyvn',\n 'QmUxej92evQZ5GPX8Uk9tRDnDEAPuPaXL6Sym1Yyo2jjRP', 'QmXqxKWJ98KQHB5Awze5Jye7oj6Axy5o9DxfMiXuNWu2VX', 'QmQkwjEDJy28EmU7mmpTCTY2hddZMy4NWvpFcRLAhqCr6n', 'QmYyLKD2FNxBUgHYnCStcfLEjphsi6ih6h9SiMtZp7wpHW',\n 'QmS4V15bkLER8ewFD1Q4FtmbneXTyGBmmCMQGxiXKes4vC', 'QmTFbhX5V7QrJANzPQEG9JViYBZXSoFB2ADHX4dSecRgv3', 'QmQEz3G568uXLwdVMQ8coYrWC4RefJk2c62oMmcBHr8dnV', 'QmSgJhpyvrjpWHH941JXtmTXC3pMkraMpm5s8PTUPkYPcg',\n 'QmeToFndkRpgGyApV3R9Vq9KLNnhLv9Qxw78suGA7vpZ1T', 'QmX2PU9gusQtiaKJqfbmPmDQQAgm24T8gromYuApo8tBTj', 'Qmcg8NCsqR3YwU1PCqwMXW81f4CXRKnXjEMBesgTJTEKgQ', 'QmVeYwMzpmr73zeU35UifpjkKGFT1BZ3DPz1F1rMfCxLrz',\n 'QmaN7WkmRZsMtygd2pJkedYdpYsRAAYRD78Q8Wi3VbXeqz', 'QmNUfD91TkPwdo5eakkSmTZYyymdce74XAfK5BjLNbGs4T', 'QmVgupkRbxhFvy4QJySzaZrrkQLiTTNYKPod8zDbWnMqre', 'Qmb5Uu8u154QTzoGpB6ypwVfPZ8NUsU519tQmrgE8yQrWV',\n 'QmYZkLdy6tF6gwcfUZeqoHPBjCYuA1cYfxwRnxaXciEJTM', 'QmVpfmBfjCwqEtPf6pbva9eTohrVHafsFd7toVpjFLqE99', 'Qmf8RFSgbSdoYCSqkJqFHZy8hktR1LsoPvVg8DZHe9m7f4', 'QmepjQCNSbbEoVGrQMbQdtsrwhELikDpieaY1DvSjUhGWD',\n 'QmZDDPcyzGVuMSB3ys6kpvvqKEFnWPrsFW8k58sieBaczS', 'QmbfFcEEeKLkeFtH32N2DSi6UaHJdRhTwLxXx3RkN1Ds2q', 'QmaZ1ha9dbtr3Dq76sDHS5UGyb6BXAGeVfbDriQjZCt3MX', 'QmWDPX4dKPi17AwzBua723Ui3kKj6KY8SKcSiajhQNNzb2',\n 'Qmdq5ncejjNb8uUN8658A7wtDCwpN2KCrmzvsYSDhVDLQK', 'QmTu8KgApeJEt2H8heoT9t39GR1wChQgKXQ6EQLrLuYhy3', 'Qmd2C7Xpf8mMJt8TfbGzq1UyjdBrkKyTviP7k1LCmdGY4y', 'QmVmakJTh7jMEfwzxy8jBQAeLqqhM2ujHo4K6PVtWGs68S',\n 'QmaPCMjMXeHrwU99M9bsqmTstXfVunytEjLzX9HXHq2sQm', 'QmS4VYuTajrsp2DJjVFvnKqPjUvfC1k2hA7BM7raSDpNah', 'QmNwcJ5GaXG669jNs4pdnkEHunKJX6DF1dQCaBhobZJWVK', 'QmZG9d2rygrkSiPnJVCLBsjyAD2JakTcDh8bSZbau9UXGd',\n 'QmdLsuo5siCvG5KduhizhPVQmSyK3jGVmAXkZdkxLxiypv', 'Qmed2hoAfDto8cRd1XL5C91ZeDCWSVd3K55u574tstx2sr', 'Qme2K2z3nNQ1MDPY2ejbcWczetGrwv6WVyLBtDdRCBBx4R', 'QmatpBQ8KwYUtHpEYdd67Vd5Q4WDuFs5ooNSG1C8eiSyAB',\n 'QmZ6cPQGycdfrwTwxfdSkE43cQBDjSoCZfGETtie9L2JKN', 'QmaVSjydLiF9Bno1FRyUj4KQvpHPmwEuBitpdCHMmLDYSQ', 'QmdqCKe5ANxPxQAsbMLW4JKxFndh1Rx7W653qQVVhh2R52', 'QmVPwhrB17kgB99iUPMbCT8sqadxrctX3WG4bg5twtQzUy',\n 'QmWYpdLh4mqBZC4wBBzrQLZyM3mfehzyx8jd5vU5KjK7ZD', 'QmUtWPrCifM4FM3Wbd4u1JMjfqQ8AsRzgUEF2L7vaC2bpv', 'QmYuBEuU8cpdg7Udb9wN7ChHcimqqG9F2BogjxX8NM48BF', 'QmVHFng7aLG2zAHeqnKUVoJdTohwZnBMsSeFUPsPbx586b',\n 'QmVRdRQuHZLwe1T9EzKdTt5ZFfv8vcr1kjiJ17scnwzXv7', 'Qma29scmx6K4NkMDXnRSuBGUwctShHhMdMaz9CFkz1Bj15', 'QmRC6oC1MV4tJ3WKBpTW3vvWi8Lw1VnjWQthjXrqgifdYd', 'QmX3BTbCb4snjaKH1apuopkY3ZBvCqnyh2682zTPrqCAsV',\n 'QmdQy5FVneE6kqgA2LCS3k3FMsPLCyKauWhCnuFyyZxa6E', 'QmfCoMLjGJvDE6v5M45LU3zLbXtoYWXRzqzFFvrQ3QU41A', 'QmVidfqfiJuRimwk5V2XUi1cQHR4hBMThXQZ2SME5S3Wrc', 'QmVbg3iagkRapLoctTZonA5rLfbo2m4w5m4RUaNtbtuwmB',\n 'QmerFeVE5Z6ThcgxRrDSPRtawnZo6a7QPPQbT4pG91AwsX', 'Qmc3vudn5pZkbD1ALfLgtU5UQQfaY1AucuwMWFa38yN9LH', 'QmU2mnSibwokwZSp6aXoAf1htdWvrBSzvpMwnTmnkyRTah', 'QmeqML1nzTEBAHzEXxyY6EB6mQpm1MTTSS349XfpW4A2Lx',\n 'Qmd99kaKTB2sCqQhaBVF62KjvXtz7rpR1sNJQ2bDBP1zCC', 'QmV3Jr4bKhQLmHZDeZu5xfD8JJKxvpnr3nnom27McRfCSe', 'Qmc4Vqnmy8aeMdgdGMNT8PH7zkbF5arvFxn6DxZdFYhiPm', 'QmRRkLXR53fkbrkwtpxJLvjsaxi89FNUSokLphezLwTfaS',\n 'QmNN7c1SzYAi2gMfbQq7BZdUgKbwcF8kLxQCuXo1ax5a6m', 'QmWZGWfTZWAA8iQK8XKSsRBe2ZzGFySu84Dawy2Mp9SZsG', 'QmXT6UzMd6JNMrAJBMpPesXiL3cXrAo8cF7uXAr8n6UFbk', 'QmfUXVC95xRcrVK3TNDubMM6VbvQ9TXoVJUAc6bKdi7RY9',\n 'QmaodAiK9cSVrouFiNS6Mp5tukUs7BHekc1NCH2FQV9RAc', 'QmPgdA4MnCtDUE21aZBV6Djvwrw4DxhbkY899TwAxCQ8q6', 'QmWZkMRH3iGpaPwJknobWreN69wzLojDCQZDSpxZP3VFGa', 'QmahGxWxRG4qhzUvD7b87efTYFDWKugJDuwkHDezcmCz2M',\n 'QmRd22d8hvns8H7rMhD4Xy8w8cCZgRbBar6gSWxqh9ZR59', 'Qmayv6CkimPPn3ZWTZFQEvTNh4FTRyk2HEvp1rXgaYwKpG', 'QmcPBk58JNwU293Der8QewpDDtQExSgRCXWQRYCwJ66SzL', 'QmR2NLzG5orqxMXXwAu7Z77vkqGSeHwBdsR91FVjzdsNdC',\n 'QmRTfSCzSKofkLLmoYae4Q92wpGmWjuEJniyZbHkMH7bbP', 'QmemaoPwdn2HcQ8h7A2QRgHPj3ADjfdvFFk9NyS1yQBQQi', 'QmYcNg1nTVdtVNqg1KSGTWwruUT4NgrjDH8hyJnxbND67i', 'QmXL4drp3Gz9XjDvhGPtMGNhasrHFMY5bNhqZuvJny1si3',\n 'QmRcRCYgbPwfXn7Po9Ve1GqsfYtSLHLfBnve8SvxTUPkHv', 'QmTMrkwMcMzytnPWLFCZ8r8RLStYWw96EjKmDHLc4UQ5dm', 'QmWyqnRMYgZbhJ99eyg49PgJo34eNkWWnY5U7CfNgZmPvG', 'Qmer4aFfhMMTD5mEY7sfWKzwdao8hj7qvCTVHbBacF35Zr',\n 'QmTaxfrktGTf3AfZaPZdBiTfPMBBamshM6o2qterQAhny7', 'QmUsdqReKyfnFQi7p6cQZWh2khq1QVR136JJ7717N7aUGy', 'QmfPYW9neLP8PnmbN28RkWHBX9THiEf74LrL12Epu1vG3v', 'QmQQSJFdRv2qEzmCbYDjUFxTnKgGr5PB4EQjVpyYXGD7yq',\n 'QmRjSVLsAFPnZpKS2RhCUo9fbCrniyr6NyRNUHWkoFd1C3', 'QmZ7N3xFgNUk6jzhEvPFwaUw6No9sgTxKcCyMeUSQQ3LFy', 'QmQfrqNdEyYDpzqncKBUMFVX315HjNtWt1B6qCx5tHkq1T', 'QmeAMQ2L89NkjzWpfTvCJ8pL8ik9iZGYu2nVWsnKwcxhU6',\n 'QmZEeDCLDT1bTKwma5CRVGA1n5aN9c6hD1KPKV6jAGsiKC', 'QmVkaXTC16mSLP3cNc1Kcd4cfjeb2DSHevf6FBcPs3ck3Y', 'QmdmQhqoGUwvrdMvcshpa88UWUSMWf26NJactVxun3rFvv', 'QmPhGekoLzQyYjh6dcFVNVtQdJJgtx5HncdW7YYMMnQkYi',\n 'Qmafd51KbHsTC8rHZTuHHD5FCUgj7swgDLQ3gexxCJev1H', 'QmNfjQLmNpGDBejVJTo8DZFjQpdjQyhE2ooe2DTzXig8Cs', 'Qmc54nYmDWddHahn1y9aFbxpZMJ19mN1iT7S3W5NvYJKat', 'QmNnNN5am3rajEqYAzND7EaXbn7hQPtDGrEo4hnoapFpuf',\n 'QmZMnjzmNBkNBMVcjJoFNfn2Aj932P8UQ39hH33y3VyeUp', 'QmbNiSMfRQ2HZ7nsi95EJdjLth2mdGbHwB2c6Jjiwv9jQm', 'QmQ2PpMuQSTWKxxeonx1xdsgCVRNnUsBrqqFLMCR8Ki8G9', 'QmVrUe4YQra5ywChr3LW8n26mmuH3ff98p7DPqzJtxdYYa',\n 'Qmbq4LqYwbgY8w3oWug7EYuUTeNGzQU6BwprtPe4rVSxhh', 'QmbcJnZsgBFyZLgF1CoN4BpAYXzgv5fxeCPhyTokJLqwov', 'QmPe5SeU1eK7qh9VuCKeWx9v8tdC4XguMeQ1Ep4VmG2uSh', 'QmQzVJzJowHoMpq3wuUQATJ2Vh3ketQSeJ6u1wvU8zXbzF',\n 'QmegVuYAB4eFLUeE7rYmFXWudMrZMM7oJ5WhuKfZhwsMfp', 'QmYQshiPnL7CmyJJEuyRSMLTS41mkFnPnAbbGfwtSuJY4d', 'QmPJbq1oFrTQrQDs5iUc9egwzxdiYRZ1PFfxemK993pjqe', 'QmWZKsea1nQ1yxCJXq3CAn6Zv9gT5MiHn5xepxyPdkbjRr',\n 'QmPFzAe3VzxX7MnpCmHjtGapR9PaPkTaGFU9fR8NG9Vofv', 'QmW9p5NyXUoU3ASKizFf9VkGsGjTyKC889AFeJRp9hQRxv', 'QmWQob2X7mL3t5xcF1GDnN9hZ6U27khYoDMf8k44QZ3Asu', 'QmeQoqz1v258i3YqjD94ji3abY7svB5WoV8qnBDR15Znzu',\n 'QmW9LzFyD9sEtw4QPQ1NfiSUksXDB7w2EJbbtjYkENcPx7', 'QmUvFmCDXpSYjDQRuUZ1cQejiGd3nR2qNuPdxCSA2R54Fx', 'QmPgYampLSWCmaqiZnGaf2sa9WpjbGrTUiz8mEBR9nLmxW', 'QmPcjfQkv7heSQ7g3ivfuzNorFRMJhk6tGtSAVGEwsKtqU',\n 'QmZPLWj97VidVYocCpyjco86FvF9ihxMDzm1269bXB2mXg', 'QmVru99h1EaLvQhdhnfZBGsH366fm7MsK8ukqio7cTLKQE', 'QmYcbGS1NZgfDQZE5Uz2u3qp2ypxJF8bknuKRxFU1FZLHG', 'QmYdhTFHpZB1Fgc4WEg3Ku6SGzVwxgDku3ngqJBuCiPtxT',\n 'QmS4QYGY89HvTYeNA9uMPPctXw1xC4L6ZoggDuKCqGtzjx', 'QmXnwXhkf9z5XFdTxqaCK5gLQE7RLSZ7W9GvB4gGwWyiQX', 'QmVpZ2jKCtJRhfapuRTy1JsGrkj4PKJHUMs2AAAjPhBu1J', 'QmZdqfWkT1JCefp5KmMiTokuaq5zYQ289ienRAa1V6Ekhq',\n 'QmV4nkJz6o1SyhnyfjNYdksR2T6WcaQuVJGpiaJGkv6isr', 'QmeKDp5F2g5qziF91FvP7utftj2vYAjtJTHpDYZ2mFCEDs', 'QmaPow2mt4tf37JeG15TjLbJSZJ2yngtugK8nrAjPZaaJj', 'QmNx96iE8gjP4F4HwzkFN55faUetx9ZVj5ETYWY4SrkNjT',\n 'QmVE8U8Nq1kpGKheEk2wW8MQhohY5kuvzu3eP3bnmdjuP2', 'Qme4h3KizSaqthbXRevpC6VVj77cpfHLYAFYVq44Ugd5z5', 'QmZDxCPGDuYSN2HnEcjVF1FUgx29brci1s6AYnmxfbTtc5', 'QmfYKPSuAiNyXDkE3NQ7J3ADV93j81mVbAVCpf1xF6wo5f',\n 'QmTyrAkEup6TZuYihiZCDfYjtnEdDSrcNzZ77uvmha4mR8', 'Qmcdt69vR8kSap9gZUtdTT91JUo5ePD5H9FwRhTRZjeZhU', 'QmWt6dUfMs3qMPYJgSCyjLUjSzJHj2pAdQDriPP1eDkfuZ', 'QmUA9AJR9gfBqXUSV2z3CXk1DGjDtorvLV8CwPGw1SZCvo',\n 'QmPHoQuKDqh2c5uW8utNrbERTXg3jUdoMXpHcrjmWtdoVk', 'QmYsGmRm92LHxRK2kcXep5kLdruiCjsXzra5boRy4nAUs4', 'QmebzhY566vN8n9h1P6jdrMQPoKcnFdm9UxC6BbWc3puWV', 'QmRst9PxEpfXTadfSGbTE2zxBQdVA676VmSVBPs5b3kDjU',\n 'QmdxEMY7vm5Ga4DBCL4eCttBsZLLQXyaUga583H243KFJ4', 'QmPMYtjAesq8aGDQfwPCgvDeLrMbh8mNZvqweuhZvtqomT', 'QmWkxMNGdTuvgdgFgY9KkkiADBJ466sCXfwr6Z67vxeFbj', 'QmQPZntLdCCT5PdYRqgqdfDe2mrWqpRYTXRL5rGKQFSHF2',\n 'QmSoQR2AbwtXXpp9qwJwTt4ywkXBt339RU5FUWWHt7NJcN', 'QmdAoQqgkENgHXAas3bCvDyYCJf247uTj8WjEqHc21JXST', 'QmbJueSqMWqV7xKXukXSxyHj5TPXxjBK6oYMJdBNJaGSFh', 'QmUjvGyGssPE3r6Mjuz3xNKPevC4Nq9tTrbzdjDrUCBtoh',\n 'QmUwomacb9wepczydDRuKWc8Pwf7cKPF8q7NbmkXXmkyec', 'QmcAwhhDMNEnq2GXmR5ZiHPniTCYEfikVCW8oaA7qJ79Ww', 'QmdkEUeuRfQJjBWbvvKgNFRNzjSJq1s5tofNrrUmAqnV6a', 'QmWUzo5o9ki26XC4dicECwvNKvKpMsQ7wr2fz4bcGp4w4Q',\n 'QmNnbd2F4WLRuAKPEEi4trTxiNVeYn7z2ZctroUcirSszf', 'QmTxUZJoJktDCkg5mdU7qA27sJo1gs2kWyhxA4xZED3aVj', 'Qmew6YW3ttjCDMfpvZz4WjCTJTx86hBxZvV1NB6MHNENtA', 'QmShVcWV9eR2zkBriASQWfcd6Ue35SU4TSvLAF2Cz7caKA',\n 'QmYY6cixMbkYv64jssd2sSbq9vzXwAXFJwjYXw6J4X8pn9', 'QmYE1sBR8FbzGz8BWx72sV3c9wq4Mm276nYBXAiASBGGDj', 'QmVzPtCUZt9TtNu7e4P7g6FfxMr5Z3CCb5DXQ3KpiRUCCZ', 'QmYqkhEm1XXp2WqKpejvC7BjNXM1FMc2uGH2pVucacaxtg',\n 'QmTwdq6xAaeJ62i9Vr4AvJsmhoNpq6AiF5tHwTpo5zCqNC', 'QmXjTMRXXpqRYcf3qm8NbNFUKN9yEGEvDjy8McLaGcRfHT', 'QmdjfULZ9vMwxd8kQrTNjWQ43h1NZ5KCHCEmau5KguTcid', 'QmfDTne3NS2hgnJZjwEpMPfcWns4TcGrVsJck3JJcwrDdZ',\n 'QmfGuMepibrSLCztNy4TNGPXj34wWv5zWFFDxSh1E2LzqL', 'QmQ45eU49MEzvikt1iX4YzSa7NvyPV8qhz8Cp3cPbtp98g', 'QmTkMujFKq2s1qFXvQPG7gUnV7UxRr83Y4ARQRu2oTL3xw', 'QmW647YTF5b4FvTzM4vGYmU1dQYNfiwFgPFmHHApfe6YP7',\n 'Qmd4Awoyx9DAvDWNyYPYnH8cZy79KDHBQCd2Uvgs2jXv3P', 'QmYK7UQHrXLF6kd7LrX8YZcvR8V6W9fCYYPPW6YwJv1zVL', 'QmXtu363znTk86aVuKBL3414s2eMvTjdnhhSbdRWgJPg6D', 'QmWtxrWBwwdzLgVDPCT37hao6p9tDUyYHcq2wD8289prjp',\n 'QmaHbEQAP6k2zopJHJBzyaK62zNX5yH8yASDjkaG4DY9Dp', 'QmUZGF3ge3d9rzrtxrD6V4qx2gLtGeeNLuCb8fQeNyUkwJ', 'QmfBBnNrVrkffMKoESvq3cB6nAWGpfMPjduTgw1unahvPf', 'QmXsFMnKWaXJ7r4tnunZrehsTcdXeesbkz4Ck7ynZzCTDj',\n 'QmbB8wwtq1w76YrMueifMuvUymzzC7chgcu4Zp76HSZpRw', 'QmW3UwAySQQEGeXY7TNKcmSaGWWpDsNhrSySVRdYnBnfgE', 'Qmf5FYbchHe2gCHFsmVKmDPSKDfR3RWruu1Kwy7gUX5Jhd', 'QmdwubNVvqAUQV5Rft616cox4HP1bAe99hchJ4n9gKZ2vA',\n 'QmUF2n8omGU5ko3XjsWZMt5dwS1ZoPnCf3RJyXXobYCJJW', 'QmdDogp7wJMLwHG2JYV3YTCFtLagceR5ANkeC4caf8CAMR', 'QmRpnJN76aw2E8rbBxEQsDccW7KDT9wLBQF7ogDgtScgg7', 'QmSomBzNykXFfZaHcAed6gxgs4HcNkjyMGbk8LMXWLsh8i',\n 'Qmf8cGMJGFBiws7nSJ4yrF9yHtxWeJJEzCvpehLomNxzgm', 'QmY7UjDL2qz3JvgVwpoWkmtTNQzV6YAdkGDe6Ammt8f5tQ', 'QmWbrqL55LSfjXRewWBf37yKToJRBdrmCrRnc8PeBr2bae', 'QmYxjjBtYW3ppcCAai5t8UekS6nuknRgwH2xKUhFK8Tnrj',\n 'QmTVo65272gTQvTrYY2ECidpW6yHAMFMkZngzsu3y4pYhY', 'QmcNxGZBV3BXQEgPFa2FgoYCkWcGeakB41ysMXQoKtG3qe', 'QmQJ2y13JEmSA6nDDLWbGnibP5Mzuwdb5G6PfRdkYgT9DT', 'QmTqVinHaSUWBZTJ3fUpSTisWWn9n11Go4AtN3nMc3T3Um',\n 'QmaSV69r69MtNyXUSgzrdxQpbFtcw6KBLCb8WCHXypkX9D', 'QmbjEQ1QpUAYcYZhyZWNHqjHa25e6a7kwrAZVYbfYtkQ3u', 'QmW6r3iPj4b1mqGDRB13LiKEFaGn7e7MZpUgjAv2RG27WY', 'Qmaa6DgT6CYQEMnzaA12yhgssV6WERGcztsqWQCJTCPtm3',\n 'QmYfgJvNM9LZPkSxMbhGjGgSoFXtU8z8r3t8omg8Kaau2d', 'QmVV3omLAKReiKEF69pXhZgTdsoPbyVR2ugoevv1x9CAgo', 'QmX9o8vNPFV54LETBYdnAwWLTfjw4cminZtnXcK9jm7zPU', 'QmPNhKGc1sb3PydoGxUaxQUrHELp1AwStCJ4WVYkJkfJAP',\n 'QmXX2ANymzA8d3pzghY5Z9aBJZVHqN4xpchhywFNUAf21w', 'QmVEuGnPZE1PAwcZeXqcoJuoc2P1A2nytxag59o9GauDTm', 'Qmc96xroeXHSYf1S5bBDjY3S1PAB4kXwCBQgLRsgyp8VZt', 'QmRoYGay8SnJWVwqzJ43vYtVJd6q7GD773kXmsLbdhmLZD',\n 'QmR1BHc59QoeCnEjjLrynnAH5wpu9nRWX2e6diArvp5Cse', 'QmbRDNBHtZA6Bqs7mpLAzdQd1HrNkZw2TujFNjEUZ16dUU', 'QmTTHRx4KLEDa2W4skQPhARjSfKPQpopAJUhqubn3azyKS', 'QmbFeX3MjvZituGhnTq1jQTCWnGqtNyVs5TGpKt3vBucpb',\n 'Qmd4CbU4aeSbBzq5mk3533GhbwVV3C5oVNp15B6saVEDCZ', 'QmWTDFP9CrdByzLoqzr4Pn3QcxCyKnyhFnfWHLX2JxQ2kf', 'QmXW1ou24MdJq9BHCi1T45TaAbhHouWGNpsmL2XzYkY5if', 'Qmami4ifqxEDmDFSK6hPJX6FQ7bJ9b38Nz2aMg8tkAPRvc',\n 'QmRnf5bkjXSQmWPTLBDXRyCPj1SxL5QDZq5LGR1zL1fShP', 'QmRyQZAhTomP4ir9b1sFuZRivoone2ukwHtV48DXwA32ko', 'QmbDsXyUiBzMA52vmHhfY6qCGk2NSbHRAyGBCXN5bMm5Yx', 'QmZwhZyzjrc7mgMHLQH98xBbqn2CgSMt9yJ6cH2ZKjJ5yy',\n 'QmduL2hm9bV5GN9U2oxLByqjiEQ5rXEARLNnGvf9sbmRdn', 'QmcxLTpyAknVj5QZgsxdgrg7NSqChD93ut2RPBFT1uUTdr', 'QmdvaG9MiBm85djwjYrPJg3wmh38dne72YDcSzXaPgBNDE', 'QmXF9A1poQfKR1mkfwv6wUNk42S3v4a7K4AtCaVmoJcHHZ',\n 'QmVebS69oAGwJJMRHUtTxbNgCjW4RZg8CgMbW67bndqP2z', 'Qmbz2ADm3f3asYgKwUArUBWTB2Grtbe2fuaQxGF7pfjDJY', 'QmYr5qFAnmTKSeVegzjn3Nt6SUAyMLJ8hpZhK5qKwFe3bt', 'QmdsAdwp8xaJF2Qc6shZPwd4P4kiT5qK5H1awxZU6MdiyS',\n 'QmRkqCuvbnyqzKSjY5ozN2oEGT2E59Zw8iHhd3pETfemiS', 'QmVhXYiLs5AMSnqGLrYC7kUAzVTTiyu9qDphb8TFH7biqk', 'QmNycssFwkGPSsukKwA34GjFoKWbef1e1guVkWHUdvTgWd', 'QmTmuRdadBzwCsdHDMXAqXvDcGkCQDfvoMBsqonJNRpSiB',\n 'QmRoZV32mXZmFwwqvX5ykMoyNjyjZvjVT3NkLVJcLCj1Rn', 'QmeiLPe3djTfMmAKdNCnMQBdes4vFaxbzPH5k26rnjkTCs', 'QmY3FVhPNBxjgKZYi9mSiqAB1GXaKDMpRPoFLoHaW9UFBc', 'QmWyhhKhuB9zwURUCaF5RssuKT2uZi4gPcXLwHRPkfs6zN',\n 'QmW7DzVmvSKYLWXzoSvF5UFoy9z8ATGUcs49VyvH8EsBZn', 'QmTv4wHn3pjs23pt1ESBpK533TwDmepSBse2ULvf5DzBiC', 'QmTnbkeTsXAruaPkSrmGVBTyeBcztWRTAfmQJa1FDDu5Zo', 'QmR4Kw7wjEuC6j7XgwWKSJYUb8fqq4CPnUBNC4ojaS2Eib',\n 'QmRLVSNbfXdCJ83i4L6d52A4XoSqteb4UEF5qNXb1A5YBf', 'QmejCD17oPmHGhnLxohaToQqUnThGE1qy7fu9XoHT8JkXR', 'QmQn5eTB4VSNBY4PPhBx1qqULMdEdnBDkmU7ZRVsQPupPK', 'QmYwSY81XmqvToA5nHWnCZYTzS3eShnrEV7XmrVXFRqLfb',\n 'QmRmYSraWyDCuXtBgitfPgZj5wpGr11dfrDg8QzdfEevpM', 'QmYhLgHZFQxGyPegdKn3TYoEYRuhYzrmfhRh4nHH2m1VL6', 'QmX1cN8i2chRMsLxJxbLs1u72gZoSEdPLensxFN6WaeGp7', 'QmTv6mimaoDKCPNBhyQZTzwT719K18S63FkU8oMKbtsSgc',\n 'QmQmfsRBNXHidS3oUnpxkkTwWKnZ6z8vreaujAfY2W1P9L', 'QmZLF9SyRjgPtcnixyiEUGQFyVRtKaTzjR477bKBTDmvLp', 'QmeKWJuHuynrbYsBmjrcu9N4Kmd1xi93X8AfNEbVN2vvHk', 'QmWLDLsXWUetW24eXrbfPrWVdXhzoADKZi9hKvcLPQvnZA',\n 'QmZfhbN1UELRfmQtGRxFJPtUhJpsF2wwBnacw7EdC12kvg', 'Qmf75aHP7UuHkA9QU49dqzU1iVBHq9kryQVYwaBPGkM9WF', 'QmTvHegn544abqaYaxDhhv7pZ8DEo6nZVyEQ3XmPrtXamD', 'QmbSE9wMdyLtqSpJcp5WHHQAzMJjWDyZqBGMxx1hCBFTTt',\n 'QmcJurMQ5LmRT8P2g92i7dLmMp7h28P8kkPJ3pvLsUGUuT', 'QmPdDu9c5i4A9XhRXMWZaWw1uBPdPGU4FmSXRRRzBrcvCH', 'QmaGhZoJhufXUb26RFy8VNXfSnrTFd9PVkzSL2ENtYETFB', 'QmPseR1x9WKsocnXqbacTvEZyQwVcdYLbLsFd78U44TT7x',\n 'QmSZmmipeHyH9E1oCyZ7XMB5q6L8t3iCo7em5VS12tqrME', 'QmdUs3DUxrJBtKc2QU7Uu1hcw4cZsKDifDb43NsMsmMWYU', 'QmXsxzWCc3bEtytUgGHx9eorMUAeCGsudFhsR6v1TbqvfB', 'QmQ5AAH23EUGpShhnsmWhQt44DnTAzZ5rCy5ZXNwM7ys7w',\n 'QmPjk1HGXEvhzmJBED9CL5TC4QzBju8rGc33EeKdBNbBFZ', 'QmXfhKkGZSKU2a8Zb1z3JsaykCLeGCaeMA2ipydq6KdWCw', 'QmNy8pmg5hE9m9rHCZtmZgNDeYwpDpNk6GEYGjjCjHkHZJ', 'QmUFxK9QdQbRMbQfUxv3nv66XRdoEePaKazVjXuzwCdrMV',\n 'QmUMJkRPkQoFt3dAHPVAu2ogGFvswUQAXH38bzXLDFkc7Z', 'QmWrhGtxZUXKDSJsuoCkBLFfSNNy6pHD5gRnHLGNKhZGi8', 'Qmb7YAP3xcytesVbvezovg48R9E5nJ8XgAPzATf8sFQNkf', 'QmRdWrcxmqdsyZTnpRzBt3fPDnYycArqv1jJM87FEqJeay',\n 'QmSvMoy3SeAREVFTcr3LRzw2vHAq159nTjCrcXNLRSmAyk', 'QmSvof6bsEDAuMvvQgeCXFLhATfewz6uA9QwcUhEYy2AvY', 'QmYgqwNStNFuQBeXsjybtrgCswdzApm5Rm1s23JyEU9WfW', 'QmPvQCbgwBsgzagm7w13G4rxai1DXJhKrh14HKd5PQw72F',\n 'Qmb9QQCRuoRnu2YieCGM8H5Zf9XKdLGEmSJM8oCdvPqqrN', 'QmcTXbXZrwwt5KSVYHb9ffsJfc5D1LQb7KzLvHoatQy7tj', 'QmRWspnUxueYQqAEvSVb9JWPorGiMHKFB8aXGKLQJepjnM', 'QmeXdubuRVN3T4kh5QjjDWXDPegrChgS2DCXNLaxUYndsH',\n 'QmQtLz5n14mz2vHncQxQgtYFBjZR27vy3GtARaXRphuW2u', 'QmeBmXdDQpqwJaJhN7B4RYoQ5fJwC6mtQDK6hj4yghyVm1', 'Qmchuv1DxrQ1byEx1s2isumNnQoe1uDoq5RVRKPyFbRqMh', 'QmRWKEjcQQo73De2nHqJfz6hTkixaeiTcUkkCvxNoziTpv',\n 'QmcNhE5LGJ3okZKpsmbWHRsMrwooEGoTD5j3UCq592ep9N', 'QmezYGsfsN8UE98PPAafQHdRbVtAd5bMW8xHGmR9XeWXDC', 'QmYVzAikao1frNNH9yphk3w43p8yeo3vVyEEmYReeFYM2J', 'QmUqVej93jpSMDMAnEH6SVSZ6uTWzVn8uE3zZe2besb7g5',\n 'QmVrkEL7jTsnJVhSjHgpXQqf1tVZNBWhXqVHgKeGsP9nMC', 'Qmabyk6rg9YiaZor7XSAWy288H6Zmkj9m4crE6wQqo1XSJ', 'QmcMrXvn2VJQjh2HbKxeswDv6bXX4F1PKDRkbZ2xAzvpjv', 'QmaJ1SBMs9WW3Xfzcq8hgs5MY8NvSQGtGU8jGLGK2uMnAE',\n 'QmTqVH42fgCKHg6Fe3jq7WyT4A2hFTqmbucoqXeRviRGVH', 'QmZTenfCMdDDFZKDx4kDrgWQ7ZQjQ6oQkzSp6U4dfH3x7V', 'QmRFE81ssXNF5oFfFMhb2cZu27ZrSuSokzduWy4XEFzyZ4', 'QmVpqFAddYg7D1LxvJeW7rMhV9pTDc5WXZEmRUkmkfobe1',\n 'QmVnC6KpkunQKyUwJNgUzpE2F7sqhA6bV7jSkEBStGcz6p', 'QmTjd8GAaafuwybbwTRECkuhc6rM3WLg4qrodLdqc8RWAz', 'QmZPjBexGowyGDYLAFF7LjQuwYfTSi25XAY6rPeTDUvhDE', 'QmeCuEHVfYMr8ChxydaTnAQhPP21SfRNmhbrrnrmFujNP1',\n 'QmeQk3tUaABArcy4BF6LidJccKSUPsanSq1C5BUmKxwGSi', 'QmVGseX7vWj6HH3vybSSWLbUY2yeKgNwgnUYwriFHpZGsx', 'Qmeoc99jqk7TdazUBvP7VxZL6EP5AjjWxk3Ug8oJHapxhZ', 'QmZpVWbcMi78uSHLmwwdKmje35CkQ9sTWhUssEF5qR91LR',\n 'QmWUzizz8BdqphbYQtwTz9ah5RD3m3GqoALDHqc21SuCgt', 'QmXohnh1V9Bp5vDBwrjLZoHcfEdkqfR6CwXZDnfmwZ32mb', 'QmPbRi5FhBtsQa1xPAixKdpw3PeFeszYyUDvqw7W6kFmqL', 'QmS33cEacDz6iLr3JAqQXNHoPz21sdvWShxfWKJUtVJFuY',\n 'QmXjbU7kKPFeQHvMNwAr2D5DTaUJ1WVeELgqM3ATSZ4Ksr', 'QmRrDSDwrryprbm71xKAxTtP6iWH6aFf9KTDRRGRPiuAx2', 'QmWuZzVQULPWbPMgnc2uvoNicW2tA8YQ4Z9s1RXH2xmPPX', 'QmVDmCvVZ9me6bwVs9C329ibY9JgmBzZFnrkSyy2E4xtpd',\n 'QmahqoEozVJF62G6vYihJLEa1AMEbmfve9k2d7TWoErdcy', 'QmZLWPy49X96iHZSfg3peGC3CmP7s8c9CMC6mTJ3wmmdQA', 'Qmcfi34hZ2avTeB3rVPCpC5X1eayvYyiVv7m2TAijMBmb9', 'QmNtiB3EqUf1379cD6NVBv3J4VxeRsUWs2482ZyZxjY9gK',\n 'QmNdoAVYna3P8Mej5tQj7bo5CWQRUXwxAQgmnEwFHTqSV2', 'QmPsepvYGHSqR4oZoD5tjQgGRWcc1HE2kDBJgMhwxCSFdW', 'QmbmRRWKoV11bayoaRXCGgK16vTEd4KVnRSZqcyuoARCxW', 'QmbQg3RiG36gKxqDX64qveewm9iSo1RvL8p2CK9WbupWhX',\n 'QmcF2EBQrW9HUpYQ8i7mZ6bSGzSTxRgznjQpyPpo5U2T2m', 'QmUhu79Q9Gv3bMEtvqmyrC2R3eRHfVfg32zLTjrgqkqv6B', 'QmP8ij2oqUKZbVm1zvUiGYfd6tatgSz74Y63vuWtAWhksa', 'QmcrKzdYMReMDoAX2v69U2yPRYN1VSJyTqYLNVicQtcJkV',\n 'QmdQkSp8woSGtDEJx6xmfCpQc4dmpmC9ogxRggctfiusyu', 'QmZzchmmu9n6L7K59yTLvFnjU5LTyPF4BwgxjT2HYquNXW', 'QmdzuRTgi5bnGXaTmb3RYGcThA1qNR8Whx1tmtqrQDR3KK', 'QmPG37vSFgCmUcEbxdQDhdExb8JgZBXdUu7wuWH76gt3mX',\n 'QmQjHzyms2nvVissqcQdJ1h1NVJbqJcWWvMccEecxhqdTN', 'QmSPCZnFqQtPuu2wgHGqPqDP5M49TLEtL6x7atTadsGifx', 'QmTM1BwXakX9YkYxSaLdtyxdGYfc5mDYYERGphURZqHsuL', 'QmbYBQaxcv1yDnszX2RcZtLRK4rW2vJLeoyC7NY5hv9dcf',\n 'QmWg5TrRuvZeougTbNvcw8CGzvFUTBTVZbVr1mAFnpUoqs', 'QmcSyeHikrbSi4bje2JU79FepKS9mrTGHyfdgQNLrNcR9x', 'QmSycpuBiHdXaLmYRT6srCZcW8mcCHHevjyK36nY6q5jGm', 'QmaDXEGRGqQ6AiD61s4e56qcveaWHRobK447aNYd6Fciru',\n 'Qmd8h9crMyMwxheB8aZnbsCx9FQkVaV7Psd1NDvLCMRGWT', 'QmXRbBgvXhK7qNPZsmNsBnMpzwZTi5PFvHxZbPuR79hkrb', 'QmT4tMC1rDNHJY3h2fXhrPM6zb2MHWrhpWrusKP76p6KSQ', 'QmPxDqqghekjUxCbo3mz2KdxHk9z3BsyJKMcJFxpikNCk3',\n 'QmfSJiWBj1JVWYqi59USbJJwBZ9SAahvqu1iXSF7Pr2JVR', 'QmcDEJorb2v5JAFjpDVyM8twgFXdh9qBZFJ3uHKUZ2eMqK', 'QmbHqJWd5GPLiCA7UJedGzLzzkjMNm4WG7RVY7im911SAQ', 'QmarmJnvsaBnXqyS2piyY57VyYXjDYbhMHVomjeBzRV2K3',\n 'QmbYCnSCyokDrm82MVSmfZe6bumJ9PaNZWpPioX7uxKmbo', 'QmPgFMCk6EgKqEc3Hbcrmef9dHyk4fBe6uX3Sh6of67GZV', 'QmeZPLoLqAta24hnhfBxMSTTUzd6mxgEmHGBVJqZMxmsVr', 'QmTJSKFa22b5ecUXEkcgrXnKSEA6WtjpfDafWtpTQFAGGF',\n 'QmeHUBJZ3xJDPpDLJ7PMGubnuR9Fh5M3N1JMmnL22RUY42', 'QmYbk4GneMQHqftuDXvKh3xBL6qZiorpLeTrNsb3rbD9XR', 'QmbButr18q9TRDXkm3mcq1iRL2fYgvB8dimx6jCcZv9Pjq', 'QmbiVK9BKuVuEYQzJpuD1a1bBU9c6C7CgmJsTrjXbwgsWR',\n 'QmT6veKQdJdP8QjvqRFhAeuqr3aMgkkuWzRn81THxyMydW', 'QmPwE7CZDQ52ofjcGuMqP74VWNXWRbY2havDRUFd49LCnV', 'QmWqsrtgeSPY6jmxPNiuZ3589odmpR9omXZJ1cGWeLxFd5', 'QmUXMSfzLncfosqrwx5P7m8UDE4nqQ5RciZf9YXrcdoJvs',\n 'QmWHr8JZcBytZYHyhP123pTFUPyaK5ZwEAA64uj2y2WLzF', 'QmXrLBqEkTUH5K8zpp6z96tL8tC3uQvkYzSpWHawJDWyat', 'QmbbCEUjF3iQJDRyfireVh9bUq4EACveM67tJAwpr8MU3v', 'QmcKbbrrJSRh7QNzdg1y8GVZNNotr1ZgeMY6EmhwS4v1pC',\n 'QmdDqBiN3z626Qu5jA4St3vpbLScobEYhuQG4RCtXHzyif', 'QmRxab77NQPzSgLFLFE6BeaN36zPDgeL8gPSe2VdfkA6P6', 'Qma7TTNV7B8udte8YY1H4WD6nTBiiw1SAzMktWDEkkjDWu', 'QmVEz4JWsDWpQrvtZnVbyPVWfHaudCkXZwC1zcs1vQKWGC',\n 'QmWpJWo9QZkCr1i5BJvqSfrwemjaNeZ8hAR8oQ5WvNh5Cu', 'QmfPoCoNeXdEuknusGvEhvYSL5wUmsmFP19b1xQBqRjecf', 'QmPRL9h9m8EZDRn6QzMpP97CRHEnyuToHYyBa9Br7nrH18', 'QmVEAsxcZ1h2fqsXLCWNui2CJEAwkSb8LoerCLGN7wFQ8y',\n 'QmPDPFoeLzbPHamsVAPtSPy1wcBHggxj48iFbobAUp4gme', 'Qmckvd9arXsXxDRTarHu2KUBnNJqHmh4tnW3S1RZ1VT89t', 'QmPVJEh2LeJyF3PUJxmYAC3YLNDNQJ1CQ32fEzkxRsdiko', 'QmaqVzchifsjNx6Hy1RD6wb2Z36BiJNPzgSyisEBzh8T6Y',\n 'QmaBx9PXB2nRkDmKhFz1QJjRsuZshgcpzMMmhLt5T4qSyK', 'QmNPoTBjpm7UQkUWxyqb6B131qQkvCMqGyh3FU8rTyytk8', 'QmX8zsBdjvjNHXTXmNdSLBjMLceiQgA7xKn5HKUTdR1HRw', 'QmZxTrZPtwryMoZSUByFoLxMJ1hnAfLeXwesLsqQzUZzyV',\n 'QmVvEaCa6zRp8Z9YkkZVYBn2owSdwZxupEQacjfd1b2HA2', 'QmdjQEZzEhigLyfij6ZtJPPHUQR1TdsMRnGPgbxBRmw8op', 'QmYF7w26Fjufgr1DC85dM2bycqckSFdMENHnZMmPhQDtVi', 'QmVnxHS4rty2k9esBVjM54iBKj5EhroE62TBpFCQ7ZfSMB',\n 'QmbBUkh7bQdmH92NmaycDakjb6jU5mYJooU8nTDCwNEJzu', 'QmfJYmwnagstpwJWTc33AZsLoFVsy64TjXAhdaWjwys9Eu', 'QmSL23H5bR3JiqYzAYiXfbN6Yu7TH8QhE2dsggbeef4VBR', 'Qmbgzd2gfdpgBTEEHUjJdcJAtHzuuQxSVC13fDZPwQ3knx',\n 'QmeQBNX3PQvQMrj3VnbeELzbdWc5wGfQSAF8fXSNYoWrAg', 'Qmcm1bx64SHmiECXYs5TGTEp1gHeyEsBzp12y5Wr9Tduee', 'QmSzgefWsFPS3qwDvmyTLZV8P6ZBSEnW7yEejaooDKXXUi', 'QmaELnqmjdMPZ8HqsfW3tDuz4uGHUn9txJtJkkJHYSR9Lq',\n 'QmXwR5VjTSrKzjHzo2FrXnWS3GSo76oPVrd5WhV32sLvub', 'QmZVP8g8tSNDriRH32jKFrD34RfGro2xY29zwqtNkTqVz5', 'QmP211gF8mXzCGExNTNPr4a3Jn4MH7iZ5rr5oavpsYhx6Y', 'QmWE4zzqoXS9yRWFYokMb3pRdTeehRqBgEuqwMgRYka51R',\n 'QmUSyyUqCZnTQmFYwNUhWGYm4CSJCuGgxFCbnD1T8mRZrM', 'Qmb7jwtvCbPxfUZAhhqE5ng6et2ViYspQiArLqf6bUX1Nq', 'QmfSyDYeshn4Y5JyP3EzDEgsgL5Z8o4AouZWDfToJRJSmH', 'QmPobNHxbr6w2nmdTxFfoVH8FP6QTbgp8wUb6UtxAv8S1R',\n 'QmPXF8ffhF3zdS1Tng3rVN6FZfq3hxMch5BZcWWfnfEhSN', 'Qmc581Phidr2Jx2AYwtgjaYeDUrC2fWx7bowfq5hN8QRyz', 'QmZcMEbiiaTaHizVzJMEFhBHmwVpBt7RFcy4r62BAuJiga', 'Qmek8PjkwBA8x9zBLsrJPY1quKobbNLRXgkXN8F3iaHMx6',\n 'QmaCGKbEog5LmnVrNGj1QqauAqmYmtHLPdCgaw793JgvTv', 'QmQ6XBjWGQpep3NFhWeHn3p4KVvQxE4wG9vVRka8HNbnQr', 'Qme8RgJ2sp7qWNqTHbFHgbRejLqugjcbzG2LPjMVaEF5dD', 'QmeE6VgtuYCsMAmkoKzeeTpcDuhJnzBpL8yf4pKRGJB81g',\n 'QmPoREZCTBDWa8c14fyLTB2S8hVoZq2a7V7gH4cte4ES9D', 'QmfQuoGraszAniiXM3CF4ut78w6u73bowUZJDeT4ukrsZb', 'QmeMyKGcYLq7XLrQes42rSiWufA49x6DsWh8SMk8QZCXR6', 'QmcF7MsVaTtrGfeM9ZSq9PUSCoTT67k5B7J46L7CH6EhZR',\n 'QmZaqVVqLKD4SGFEDMYHCrPTcSVuduAwHN6Yjs6CMQhous', 'QmUwBcPAkyw2arFBD8Mqj6pJe1wgpSHE9NLSXFiEVQqcu9', 'QmaN7ky7s6zBdqWodEEqD4844ajqpobXCChwxccqS8rjWx', 'QmZECsRwqvdjbz7DYrZcZ9wno9RoBuNXX1GDWQNaC4icYq',\n 'QmY3xWVqdSm6XoHMvPYLamkHLC6VVRnupcp7FmZGqFwCL4', 'QmbGFhAS7YFp2ir2VXQBmcagB5nYfcbempcrj7U3ZS5ySx', 'QmczBQnqVcBs4tnY6nw8H6T2JNGMuCCtezWjbCY99Wokz7', 'QmadwMSsweUH26bAbrWvDPQGQHDy31ds2GCAU2KsasMZgQ',\n 'QmTBNvoUiwPw9HSUmy1qKCWPBKkBRAensgooYVqMmsviaE', 'QmRR7sPfkzuhYATFjGiLBVyo9QkBF35V9i8vk8exthuUoa', 'QmedEbGk9ZMD3pecvy5mc5S41cXxQb83cfkE7JQjNbJUb3', 'QmZaHP72PQ6UQ4BqW8rjLDN6BFmPX4U8S3P3PtMCjVDudP',\n 'Qmam4H1YnqinKNpwVMqFtVmtjCVm4YTvHNA1gucUoonrz5', 'Qmdgg92V3KbEuiJ1PLG7Huyph8SoYZNgonTCdbvvteERyF', 'Qmezb3CHKTtcxc7qxrFKkgsQKypC2pEiBw1M5jgD657tah', 'QmW6ZHWgtFpeY6P6BGvmQRNLbV5oCYBsK8z3pQju8L8AMQ',\n 'QmRj9wPp9dPxbDuUjStKArQB26X56qpXuUS2vAwLTMGTR1', 'QmNPJotqYDKtNG8XgHkbHamxm2QMdMn2FtFRrKy8t3fBhd', 'QmSXV1k82avNPDXYHRufH6RzBfyUSjnDmQoQ9idjx9bGHX', 'QmPGq6baCU7n5oq6c1wKfP4Scqg23Dfs7fwS3T8SDPpGTW',\n 'QmTeNQtppRWfQB2PQNohdx2PP9DAos1rKBqg7ypCrs6rXs', 'QmW9iAGca5Nst3Ck78RWs7PePgSB35mMvRKhFDdHM6CaZ6', 'QmcoEw3jgjT18PT6E4Jhf6AF2jWaqFK14T7cSZmbE5imTi', 'QmRgprite9rBeUfFCiCta42BvcjfqdpkAsXpguG5huz6RR',\n];\n","/**\n * The Metaquarium farm, in-house.\n *\n * Everything needed to turn a fish token id into asset URLs lives here as\n * DATA — no AWS API, no metadata round-trip, no third-party resolver on the\n * path to a rendered frame. `fishAssets(85)` is a pure function.\n *\n * Why this is not a client: the farm's AWS endpoint requires an allowlisted\n * `Origin` header that does not include idlescreens.com, and its media lives\n * behind gateways that have proven unreliable. Depending on either at render\n * time makes an ambient screen only as good as someone else's uptime. The\n * collection is closed (512, `totalSupply()`), so the mapping is a constant.\n *\n * Zero-dep on purpose — servers can validate fish references through the\n * `./manifest` subpath without pulling three.js.\n *\n * Source of truth and provenance: `idle-mono/docs/metaquarium-farm-api.md`.\n */\n\nimport { ASSET_CIDS } from './asset-cids';\n\nexport { ASSET_CIDS };\n\nexport const TOTAL_SUPPLY = 512;\n\nexport function isMintedId(id: number): boolean {\n return Number.isInteger(id) && id >= 1 && id <= TOTAL_SUPPLY;\n}\n\n/** ERC-721, Ethereum mainnet. Metadata is per-token but collapses to the four\n * directory CIDs in {@link BREEDS}, so `tokenURI` is never needed at runtime. */\nexport const CONTRACT = '0x680cCc4fE7aa62172D20899Ab87C5304545431CB';\n\n// ---------------------------------------------------------------------------\n// Breeds\n// ---------------------------------------------------------------------------\n\n/** Every breed the Metaquarium was designed around. Four were minted as the\n * 512-fish collection; the rest exist as finished models in the original\n * aquarium and are the natural stock for future tank life (props, NPCs,\n * wildlife that isn't a token). Kept here so the knowledge isn't stranded\n * in the source project. */\nexport type Breed =\n | 'betafish' | 'angelfish' | 'seahorse' | 'seaturtle' // minted\n | 'blowfish' | 'hackerfish' | 'glowfish' | 'babyfish' // designed, unminted\n | 'shark' | 'crab' | 'jellyfish' | 'dori';\n\nexport interface BreedInfo {\n breed: Breed;\n minted: boolean;\n /** Inclusive token range, minted breeds only. Ranges are contiguous and\n * in mint order — this is what makes id → metadata a pure function. */\n range?: readonly [number, number];\n count: number;\n /** Metadata directory CID (one per minted breed, holding every\n * `fish_<n>_of_the_metaquarium.json` in its range). */\n metadataCid?: string;\n /** Model basename in the original aquarium's NPC set, for unminted breeds\n * whose art exists but has no token behind it. */\n model?: string;\n}\n\nexport const BREEDS: readonly BreedInfo[] = [\n { breed: 'betafish', minted: true, range: [1, 256], count: 256,\n metadataCid: 'QmZSs1ZHsW4B4ZLip8N8EqdUaXCv5ra5M5bWz1RsFSkKb4', model: 'beta-fish' },\n { breed: 'angelfish', minted: true, range: [257, 456], count: 200,\n metadataCid: 'QmaQXcFof5qb6cigk4eo59zyMUdP4CV6pVAupPZys4jquT', model: 'angel-fish' },\n { breed: 'seahorse', minted: true, range: [457, 496], count: 40,\n metadataCid: 'Qme8G5eXaAkk1dzVr5VDt8sgauVez9kmxxDNaYbSQLyggx', model: 'sea-horse' },\n { breed: 'seaturtle', minted: true, range: [497, 512], count: 16,\n metadataCid: 'QmZTntNpvwxe8Mii7s1XTpTrbaci4ZgYUXs9sEbsBodtaF', model: 'sea-turtle' },\n { breed: 'blowfish', minted: false, count: 0, model: 'blow-fish' },\n { breed: 'hackerfish', minted: false, count: 0, model: 'hacker-fish' },\n { breed: 'glowfish', minted: false, count: 0, model: 'glow-fish' },\n { breed: 'babyfish', minted: false, count: 0, model: 'baby-fish' },\n { breed: 'shark', minted: false, count: 0, model: 'shark' },\n { breed: 'crab', minted: false, count: 0, model: 'crab' },\n { breed: 'jellyfish', minted: false, count: 0, model: 'jellyfish' },\n { breed: 'dori', minted: false, count: 0, model: 'dori' },\n];\n\n/** Breed of a minted token, or null when the id is outside 1..512. */\nexport function breedOf(id: number): Breed | null {\n if (!isMintedId(id)) return null;\n for (const b of BREEDS) {\n if (b.range && id >= b.range[0] && id <= b.range[1]) return b.breed;\n }\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Traits\n// ---------------------------------------------------------------------------\n\n/**\n * The twelve trait types every fish carries, in the order the metadata emits\n * them. Documented because several are steering-relevant and would otherwise\n * be rediscovered by reading JSON: `primary color`/`secondary color` are the\n * fish's own hexes (an authored palette the tank currently overrides with its\n * seeded coats), `eyes` is a 9-bit pixel mask, `track`/`voice` name the audio\n * the original aquarium played for that fish.\n */\nexport const TRAIT_TYPES = [\n 'breed', 'track', 'primary color', 'secondary color', 'eyes', 'voice',\n 'credit score', 'astrological sign', 'mystery digit', 'attack power',\n 'birthday', 'left finned',\n] as const;\nexport type TraitType = (typeof TRAIT_TYPES)[number];\n\n/** A fish's metadata as the farm publishes it. `token_id` is deliberately\n * absent — the live API does not return it; derive from `name`. */\nexport interface FishMetadata {\n name: string;\n description?: string;\n external_url?: string;\n image?: string;\n favicon?: string;\n transparent_icon?: string;\n /** GLB. Note the filename suffix differs by metadata generation — see\n * {@link fishAssets}, which builds the current one. */\n '3d'?: string;\n animation_url?: string;\n video_512?: string;\n born_in?: string;\n background_color?: string;\n attributes?: Array<{ trait_type: TraitType | string; value: string | number }>;\n}\n\n/** Token id from a metadata `name` (\"Fish 85 of the Metaquarium\" → 85). */\nexport function idFromName(name: string): number | null {\n const m = /Fish\\s+(\\d+)\\s+of the Metaquarium/i.exec(name);\n return m ? Number(m[1]) : null;\n}\n\n// ---------------------------------------------------------------------------\n// URL construction\n// ---------------------------------------------------------------------------\n\nexport type AssetKind = '3d' | 'image' | 'transparent_icon' | 'favicon' | 'video';\n\n/** Filename suffix per asset kind, current generation. (An earlier metadata\n * batch named the model `_glb.glb`; the directories themselves hold\n * `_3d.glb`, which is what these build.) */\nconst SUFFIX: Record<AssetKind, string> = {\n '3d': '_3d.glb',\n image: '_image.jpg',\n transparent_icon: '_transparent_icon.png',\n favicon: '_favicon.png',\n video: '_video.mp4',\n};\n\n/** The asset directory CID for a token, or null when the id isn't minted. */\nexport function assetCid(id: number): string | null {\n return isMintedId(id) ? (ASSET_CIDS[id - 1] ?? null) : null;\n}\n\n/** `ipfs://<cid>/<file>` for one asset — the scheme the tank's gateway ladder\n * resolves. Pure: no fetch, no metadata lookup. */\nexport function fishAsset(id: number, kind: AssetKind = '3d'): string | null {\n const cid = assetCid(id);\n return cid ? `ipfs://${cid}/fish_${id}_of_the_metaquarium${SUFFIX[kind]}` : null;\n}\n\n/** Every asset URL for a fish, plus its breed — one call, no network. */\nexport function fishAssets(id: number): {\n id: number; breed: Breed; cid: string;\n model: string; image: string; icon: string; favicon: string; video: string;\n} | null {\n const cid = assetCid(id);\n const breed = breedOf(id);\n if (!cid || !breed) return null;\n return {\n id, breed, cid,\n model: fishAsset(id, '3d')!,\n image: fishAsset(id, 'image')!,\n icon: fishAsset(id, 'transparent_icon')!,\n favicon: fishAsset(id, 'favicon')!,\n video: fishAsset(id, 'video')!,\n };\n}\n\n/** Canonical metadata URL for a token — the range formula, no RPC. Useful for\n * traits/description; never needed just to render. */\nexport function fishMetadataUrl(id: number): string | null {\n const b = BREEDS.find((x) => x.breed === breedOf(id));\n return b?.metadataCid\n ? `ipfs://${b.metadataCid}/fish_${id}_of_the_metaquarium.json`\n : null;\n}\n\n/** All minted ids of a breed — the seam for \"a tank of seahorses\". */\nexport function idsOfBreed(breed: Breed): number[] {\n const b = BREEDS.find((x) => x.breed === breed);\n if (!b?.range) return [];\n const out: number[] = [];\n for (let i = b.range[0]; i <= b.range[1]; i++) out.push(i);\n return out;\n}\n","/** Ordered gateway candidates. dweb.link stays primary (flaky but usually\n * fast); our own node at hermosaai.com is second — flip it to the front\n * once the tunnel is verified serving /ipfs/ (it timed out on the\n * 2026-08-15 check); ipfs.io is the last resort. Each is tried in order\n * with a per-gateway timeout by the tank's template loader (MQ21). */\nexport const IPFS_GATEWAYS = [\n 'https://dweb.link/ipfs/',\n 'https://hermosaai.com/ipfs/',\n 'https://ipfs.io/ipfs/',\n];\n\nexport function resolveIpfsUrl(url: string): string {\n return resolveIpfsUrls(url)[0]!;\n}\n\n/** All gateway candidates for a URL — one entry for non-ipfs URLs. */\nexport function resolveIpfsUrls(url: string): string[] {\n if (!url.startsWith('ipfs://')) return [url];\n const stripped = url.slice('ipfs://'.length);\n return IPFS_GATEWAYS.map((g) => `${g}${stripped}`);\n}\n\nexport interface FishEntry {\n id: number;\n name: string;\n breed: string;\n ipfs3d: string;\n localGlb: string;\n}\n\nexport const FISH_CATALOG: FishEntry[] = [\n { id: 257, name: 'Fish 257', breed: 'angelfish', ipfs3d: 'ipfs://QmaHbEQAP6k2zopJHJBzyaK62zNX5yH8yASDjkaG4DY9Dp/fish_257_of_the_metaquarium_3d.glb', localGlb: '/assets/metaquarium/fish-257-angelfish.glb' },\n { id: 258, name: 'Fish 258', breed: 'angelfish', ipfs3d: 'ipfs://QmUZGF3ge3d9rzrtxrD6V4qx2gLtGeeNLuCb8fQeNyUkwJ/fish_258_of_the_metaquarium_3d.glb', localGlb: '' },\n { id: 259, name: 'Fish 259', breed: 'angelfish', ipfs3d: 'ipfs://QmfBBnNrVrkffMKoESvq3cB6nAWGpfMPjduTgw1unahvPf/fish_259_of_the_metaquarium_3d.glb', localGlb: '' },\n { id: 100, name: 'Fish 100', breed: 'betafish', ipfs3d: 'ipfs://Qmb5Uu8u154QTzoGpB6ypwVfPZ8NUsU519tQmrgE8yQrWV/fish_100_of_the_metaquarium_3d.glb', localGlb: '/assets/metaquarium/fish-100-betafish.glb' },\n { id: 457, name: 'Fish 457', breed: 'seahorse', ipfs3d: 'ipfs://QmVvEaCa6zRp8Z9YkkZVYBn2owSdwZxupEQacjfd1b2HA2/fish_457_of_the_metaquarium_3d.glb', localGlb: '' },\n { id: 497, name: 'Fish 497', breed: 'seaturtle', ipfs3d: 'ipfs://QmTBNvoUiwPw9HSUmy1qKCWPBKkBRAensgooYVqMmsviaE/fish_497_of_the_metaquarium_3d.glb', localGlb: '' },\n];\n\nexport const DEFAULT_FISH = FISH_CATALOG[0]!;\n\nimport { BREEDS, breedOf, fishAsset, TOTAL_SUPPLY } from './farm';\n\nexport interface FishMixEntry {\n id: number;\n url: string;\n count: number;\n}\n\nexport interface FishMixResult {\n entries: FishMixEntry[];\n problems: string[];\n}\n\n/**\n * Parse the `fishMix` DSL: comma-separated `id[:count]` where `id` is a\n * catalog token id (`257`) or breed alias (`betafish`; picks the breed's\n * first entry). Against the default catalog, any minted id 1–512 also\n * resolves via the in-house farm table. A caller-supplied catalog is a\n * closed world — ids not in it are problems, not IPFS fallbacks. Counts\n * are absolute; the tank clamps the expanded total to its tier cap. Raw\n * URLs are deliberately NOT accepted — `:` and `,` stay unambiguous, the\n * validation surface stays finite, and custom URLs remain `fishUrl`'s job\n * (single-breed mode).\n *\n * Zero-dep and pure, so the Worker (via the manifest subpath), the\n * playground, and the tank all validate with the same code. Never throws:\n * bad tokens land in `problems` and good ones still parse, so one typo\n * degrades a mix instead of blanking the tank.\n */\nexport function parseFishMix(\n mix: string,\n catalog: FishEntry[] = FISH_CATALOG,\n): FishMixResult {\n const entries: FishMixEntry[] = [];\n const problems: string[] = [];\n for (const rawToken of mix.split(',')) {\n const token = rawToken.trim();\n if (token === '') continue;\n const [idRaw, countRaw, ...extra] = token.split(':');\n if (extra.length > 0) {\n problems.push(`\"${token}\": too many ':' — expected id[:count]`);\n continue;\n }\n const key = (idRaw ?? '').trim().toLowerCase();\n let fish = /^\\d+$/.test(key)\n ? catalog.find((f) => f.id === Number(key))\n : catalog.find((f) => f.breed.toLowerCase() === key);\n // Farm fallback is only for the default catalog. A custom catalog is a\n // closed world (playground offline e2e, a future pack) — leaking a minted\n // id out to IPFS would silently undo that constraint.\n if (!fish && catalog === FISH_CATALOG && /^\\d+$/.test(key)) {\n const n = Number(key);\n const url = fishAsset(n, '3d');\n const breed = breedOf(n);\n if (url && breed) fish = { id: n, name: `Fish ${n}`, breed, ipfs3d: url, localGlb: '' };\n }\n if (!fish) {\n problems.push(\n /^\\d+$/.test(key)\n ? catalog === FISH_CATALOG\n ? `\"${key}\": not a minted token id (1-${TOTAL_SUPPLY})`\n : `\"${key}\": not a catalog id`\n : `\"${key}\": not a breed (${BREEDS.filter((b) => b.minted).map((b) => b.breed).join(', ')})`,\n );\n continue;\n }\n let count = 1;\n if (countRaw !== undefined) {\n const n = Number(countRaw.trim());\n if (!Number.isInteger(n) || n < 1 || n > 24) {\n problems.push(`\"${token}\": count must be an integer 1-24`);\n continue;\n }\n count = n;\n }\n entries.push({ id: fish.id, url: fish.ipfs3d, count });\n }\n return { entries, problems };\n}\n\n/** Expand a parsed mix into the ordered per-slot URL list the tank spawns\n * from: `\"257:2,100:1\"` → [url257, url257, url100]. Slot order IS the\n * DSL order, so fish 0..N are stable for a given string. */\nexport function expandFishMix(entries: FishMixEntry[], cap: number): string[] {\n const urls: string[] = [];\n for (const e of entries) {\n for (let i = 0; i < e.count && urls.length < cap; i++) urls.push(e.url);\n }\n return urls;\n}\n","import type { ParamSpace, ParamValue, SaverManifest } from '@idle-screens/core';\nimport { ENVIRONMENT_NAMES } from './environments';\nimport { SWIM_STYLE_NAMES } from './swim';\n\n/**\n * Zero-dependency manifest module (type-only imports). The channel server\n * imports `@idle-screens/saver-metaquarium/manifest` to validate published\n * params against this paramSpace without pulling three.js into the Worker.\n */\nexport const METAQUARIUM_PARAMS = {\n /** Camera orbit angle, degrees. */\n cameraAzimuth: { type: 'number', default: 35, min: 0, max: 360, ease: 'smooth' },\n /** Camera height angle, degrees above the waterline plane. */\n cameraElevation: { type: 'number', default: 15, min: -5, max: 60, ease: 'smooth' },\n /** Camera distance from tank center, world units. */\n cameraDistance: { type: 'number', default: 110, min: 80, max: 400, ease: 'smooth' },\n /** Continuous orbit speed, degrees/second. Zero by default: the tank is\n * still, letting the fish movement carry the scene. Steer up for an orbit. */\n autoRotate: { type: 'number', default: 0, min: 0, max: 12, ease: 'smooth' },\n /** Visible fish. Default 1 = hero mode: one fish center-stage in its own\n * tank; the pool grows on demand when steered up (never shrinks). */\n fishCount: { type: 'number', default: 1, min: 1, max: 24, ease: 'step' },\n /** Swim time-scale multiplier. */\n swimSpeed: { type: 'number', default: 1, min: 0.2, max: 3, ease: 'smooth' },\n /** Water/atmosphere color (background + fog). */\n fogColor: { type: 'color', default: '#030009', ease: 'smooth' },\n /** GLB model URL for the fish (supports ipfs:// scheme). Package default\n * is the IPFS hero fish so npm / channel hosts work without playground\n * static assets; the playground overrides this to a local GLB. */\n fishUrl: { type: 'string', default: 'ipfs://QmaHbEQAP6k2zopJHJBzyaK62zNX5yH8yASDjkaG4DY9Dp/fish_257_of_the_metaquarium_3d.glb' },\n /** Mixed population DSL: comma-separated `id[:count]` of a minted token id\n * (1-512, default catalog) or breed name — e.g. \"257:3,100:2,seaturtle:1\".\n * A custom catalog is a closed world (no farm/IPFS fallback). Counts are\n * absolute; the expanded total clamps to the device tier's fish cap.\n * Non-empty mix OVERRIDES fishUrl and fishCount; empty string = single-\n * breed mode. Unknown ids/bad counts degrade (good tokens still parse);\n * raw URLs are not accepted here — use fishUrl for a custom single-breed GLB. */\n fishMix: { type: 'string', default: '', ease: 'step' },\n /** Fog start distance, world units. Default matches the tank's original\n * hardcoded Fog(60, 500) — steering below pulls the murk close. */\n fogNear: { type: 'number', default: 60, min: 20, max: 200, ease: 'smooth' },\n /** Fog full-opacity distance. Kept under the camera far plane (1200);\n * the tank enforces far > near + 20. */\n fogFar: { type: 'number', default: 500, min: 120, max: 1100, ease: 'smooth' },\n /** Plankton mote density, 0-1 of the device tier's mote budget. Default 0\n * = off, so the baseline look is untouched until steered. */\n moteDensity: { type: 'number', default: 0, min: 0, max: 1, ease: 'smooth' },\n /** Mote tint. */\n moteColor: { type: 'color', default: '#7fd6ff', ease: 'smooth' },\n /** Floor disc color. Default is the original hardcoded navy. */\n floorColor: { type: 'color', default: '#0a1d33', ease: 'smooth' },\n /** Where the Draco decoder lives, for the many Metaquarium models that are\n * Draco-compressed. Empty = the copy shipped beside this package (no CDN,\n * no network beyond your own host). Override when a host serves the\n * decoder from its own static path. */\n dracoPath: { type: 'string', default: '', ease: 'step' },\n /** The ROOM: a named place rather than thirty numbers. `void` is exactly\n * the pre-environment scene, so the default changes nothing. Each other\n * value adds a water ceiling, a terrain silhouette and light shafts,\n * tier-budgeted. Palette params (fog, floor colour, motes) stay yours —\n * an environment never overrides them. */\n environment: { type: 'enum', default: 'void', options: [...ENVIRONMENT_NAMES], ease: 'step' },\n /** Override the environment's terrain. `auto` follows the environment. */\n floorKind: { type: 'enum', default: 'auto', options: ['auto', 'flat', 'dunes', 'ridges', 'basin'], ease: 'step' },\n /** Water-ceiling height. -1 = follow the environment; fish swim to y=72.\n * STEP, not smooth: -1 is a sentinel, so a ramp from -1 to a real height\n * passes through negatives that read as \"auto\" — the ceiling would jump\n * rather than glide. A value you cannot interpolate through must not\n * advertise that it can. */\n waterY: { type: 'number', default: -1, min: -1, max: 220, ease: 'step' },\n /** Light-shaft strength. -1 = follow the environment, 0 = off. Step for the\n * same sentinel reason: ramping -1 → 0 would read as FULL strength until it\n * snapped off. */\n rayStrength: { type: 'number', default: -1, min: -1, max: 1, ease: 'step' },\n /** How the fish move. `loop` is exactly the pre-style behaviour, so the\n * default changes nothing. A small named set on purpose: a silhouette of\n * movement you can name is one you can choose from. */\n swimStyle: { type: 'enum', default: 'loop', options: [...SWIM_STYLE_NAMES], ease: 'step' },\n /** Per-fish spread: 0 a uniform shoal, 1 every fish visibly its own animal\n * (±40% speed, ±25% size, own phase). The uniqueness dial — one number\n * instead of per-fish values nobody wants to author. */\n swimVariance: { type: 'number', default: 0, min: 0, max: 1, ease: 'smooth' },\n /** Procedural body yaw for models that carry NO animation clip — most of\n * the breed library. Distance-driven like the tail beat, so it speeds up\n * with the fish and stays frame-addressable. Clipped models ignore it:\n * their own clip is the better animation.\n *\n * Defaults to 0, off, even though a rigidly gliding fish is the worse\n * look. Every param this saver has added defaults to the previous\n * behaviour, and a scene already on someone's wall should not start moving\n * differently because a dependency was bumped. 0.3–0.4 is the recommended\n * value for a clip-less cast — the studio swim variants all set it. */\n bodyWiggle: { type: 'number', default: 0, min: 0, max: 1, ease: 'smooth' },\n} satisfies ParamSpace;\n\n/** The original's Miami-Vice body palette (scss-variables.ts) — seeded fish\n * coats draw from this. Zero-dep so the server/manifest lane can surface it. */\nexport const MIAMI_VICE_COLORS = [\n '#ff6ad5', // Hot Pink\n '#c774e8', // Light Purple\n '#ad8cff', // Lavender\n '#8795e8', // Periwinkle Blue\n '#94d0ff', // Light Sky Blue\n '#4fb4f4', // Azure\n '#00a6fb', // Bright Blue\n '#0085a1', // Cerulean\n '#0b3d91', // Yale Blue\n '#1c1c1c', // Off Black\n];\n\n/** The original's emissive/glow palette (`GLOW-*` parts, bloom layer). */\nexport const BLOOM_COLORS = [\n '#FF69B4', // Hot Pink\n '#FFFF00', // Yellow\n '#00FFFF', // Cyan\n '#00FF00', // Lime\n '#FF4500', // OrangeRed\n '#7FFF00', // Chartreuse\n];\n\nexport const metaquariumManifest: SaverManifest = {\n id: 'metaquarium',\n label: 'Metaquarium',\n description:\n 'A living fish tank: skinned GLB fish swim seeded Catmull-Rom paths through a dark, fogged aquarium.',\n minBackend: 'webgl2',\n costTier: 'medium',\n motionIntensity: 'calm',\n reducedMotionFallback: 'static',\n timeModel: 'closed-form',\n palette: ['#030009', '#ff6ad5', '#94d0ff', '#00a6fb', '#00FFFF'],\n paramSpace: METAQUARIUM_PARAMS,\n attribution: {\n source: 'Metaquarium (metaquarium.xyz) — original artwork and fish models by Shawn Partridge',\n license: 'MIT port of first-party artwork',\n url: 'https://metaquarium.xyz',\n },\n a11y: {\n flashSafe: true,\n notes: 'Slow ambient swim in a dark fogged tank; no strobing.',\n },\n};\n\n/** Options for {@link createMetaquarium}-style variants: a distinct id/label and\n * overridden param defaults (e.g. a farm-connected tank). */\nexport interface MetaquariumOptions {\n /** Override the fish catalog fishMix ids resolve against — the seam the\n * playground uses to point ids at bundled local GLBs (offline e2e), and\n * the future farm/pack extension point. */\n catalog?: import('./ipfs').FishEntry[];\n id?: string;\n label?: string;\n params?: Partial<Record<keyof typeof METAQUARIUM_PARAMS, ParamValue>>;\n}\n\n/** Clone a paramSpace with default overrides; unknown keys are ignored. */\nexport function withDefaults(\n space: ParamSpace,\n overrides?: Record<string, ParamValue>,\n): ParamSpace {\n if (!overrides) return space;\n const out: ParamSpace = {};\n for (const [k, def] of Object.entries(space)) {\n const o = overrides[k];\n out[k] = o === undefined ? def : { ...def, default: o };\n }\n return out;\n}\n\n/** Clone the metaquarium paramSpace with per-variant default overrides. */\nexport function paramSpaceWith(overrides: MetaquariumOptions['params']): ParamSpace {\n return withDefaults(METAQUARIUM_PARAMS, overrides as Record<string, ParamValue> | undefined);\n}\n\n/**\n * Defensive read of a tracked number param. The server validates nothing on\n * the classic steering lane (any path, any value reaches the saver), and MCP\n * harnesses with untyped `value` params stringify numbers — so \"22\" arrives\n * where 22 was meant. Finite numbers pass; finite numeric strings coerce;\n * anything else falls back to the def's default; the result clamps to the\n * def's min/max. Zero-dep on purpose: hosts can reuse it to sanitize.\n */\nexport function coerceNum(\n def: { default?: ParamValue; min?: number; max?: number } | undefined,\n v: ParamValue | undefined,\n): number {\n let n: number | null = null;\n if (typeof v === 'number' && Number.isFinite(v)) n = v;\n else if (typeof v === 'string' && v.trim() !== '') {\n const parsed = Number(v);\n if (Number.isFinite(parsed)) n = parsed;\n }\n if (n === null) {\n const d = def?.default;\n if (typeof d === 'number' && Number.isFinite(d)) n = d;\n else if (typeof d === 'string' && d.trim() !== '' && Number.isFinite(Number(d))) n = Number(d);\n else n = 0;\n }\n if (def?.min !== undefined && n < def.min) n = def.min;\n if (def?.max !== undefined && n > def.max) n = def.max;\n return n;\n}\n\nexport * from './farm';\nexport { parseFishMix, expandFishMix, type FishMixEntry, type FishMixResult, type FishEntry, FISH_CATALOG } from './ipfs';\n\nexport * from './environments';\nexport * from './swim';\n"],"mappings":"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,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAA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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/environments.ts","../src/swim.ts","../src/asset-cids.ts","../src/farm.ts","../src/ipfs.ts","../src/manifest.ts"],"sourcesContent":["/**\n * Environments — the tank as a room.\n *\n * The original aquarium switches whole VENUES, not backdrops: turning its\n * EXHIBIT knob moves from open shader terrain under a water ceiling to an\n * indoor hall with walls and a striped floor. Our tank has one room — a disc\n * under fog — so the gap was never \"more knobs\", it was that there is no room.\n *\n * This is that room, as data. An `environment` names a PLACE; an agent picks\n * one instead of dialling thirty numbers, and the values below are the taste.\n * Design and the browser session behind it:\n * `idle-mono/docs/metaquarium-environment-spec.md`.\n *\n * Zero-dep on purpose — the server validates through `./manifest` without\n * pulling three.js.\n */\n\nexport type EnvironmentName =\n | 'void' | 'abyss' | 'reef' | 'kelp' | 'ice' | 'vent' | 'lagoon' | 'universe';\n\n/** Terrain silhouette. `flat` is the original disc; the rest are generated\n * from the mount seed — never fetched, so determinism and offline both hold. */\nexport type FloorKind = 'flat' | 'dunes' | 'ridges' | 'basin';\n\n/** Runtime guard for the same set — the steering lane is unvalidated, so a\n * bad value must fall back rather than build an invisible floor. */\nexport const FLOOR_KINDS: readonly FloorKind[] = ['flat', 'dunes', 'ridges', 'basin'];\n\n/** A translucent plane ABOVE the fish. The single strongest identity cue in\n * the original: it is what makes a scene read as *under* something. */\nexport interface WaterCeiling {\n /** World Y. The tank's fish swim between y=15 and y=72. */\n y: number;\n color: string;\n opacity: number;\n}\n\n/** Volumetric shafts. `y` may be NEGATIVE — the original's raysLightY swings\n * ±2000, and light from below is the difference between a sunlit surface and\n * an abyssal glow. */\nexport interface Rays {\n strength: number;\n color: string;\n y: number;\n}\n\nexport interface EnvironmentPreset {\n name: EnvironmentName;\n label: string;\n water: WaterCeiling | null;\n floor: FloorKind;\n rays: Rays | null;\n}\n\n/**\n * The catalogue. `void` is EXACTLY today's scene — no ceiling, flat floor, no\n * rays — so the default is a no-op and every already-published scene renders\n * unchanged. That is the whole reason the enum is safe to ship first.\n *\n * Deliberately NOT set here: fog, floor colour, motes. Those are already\n * steerable params, and an environment that quietly overrode them would make\n * \"did the author choose this, or did the preset?\" unanswerable. An\n * environment supplies the ROOM; the palette stays the author's.\n */\nexport const ENVIRONMENTS: readonly EnvironmentPreset[] = [\n { name: 'void', label: 'Void', water: null, floor: 'flat', rays: null },\n { name: 'abyss', label: 'Abyss', water: null, floor: 'basin',\n rays: { strength: 0.35, color: '#0e4a6e', y: -620 } },\n { name: 'reef', label: 'Reef', water: { y: 150, color: '#2ad4ff', opacity: 0.18 },\n floor: 'dunes', rays: { strength: 0.5, color: '#bfefff', y: 900 } },\n { name: 'kelp', label: 'Kelp forest', water: { y: 170, color: '#1e6f5c', opacity: 0.2 },\n floor: 'ridges', rays: { strength: 0.4, color: '#9ef5d0', y: 820 } },\n { name: 'ice', label: 'Under ice', water: { y: 132, color: '#cfefff', opacity: 0.32 },\n floor: 'flat', rays: { strength: 0.6, color: '#eaf8ff', y: 1000 } },\n { name: 'vent', label: 'Hydrothermal vent', water: null, floor: 'ridges',\n rays: { strength: 0.45, color: '#ff7a3c', y: -520 } },\n { name: 'lagoon', label: 'Lagoon', water: { y: 118, color: '#7ff3d0', opacity: 0.22 },\n floor: 'dunes', rays: { strength: 0.55, color: '#fff3b0', y: 950 } },\n { name: 'universe', label: 'Universe', water: null, floor: 'ridges',\n rays: { strength: 0.3, color: '#c39bff', y: 1200 } },\n];\n\nexport const ENVIRONMENT_NAMES: readonly EnvironmentName[] =\n ENVIRONMENTS.map((e) => e.name);\n\nexport function environmentOf(name: string): EnvironmentPreset {\n return ENVIRONMENTS.find((e) => e.name === name) ?? ENVIRONMENTS[0]!;\n}\n\n// ---------------------------------------------------------------------------\n// Cost budget\n// ---------------------------------------------------------------------------\n\n/**\n * What each layer costs, and what a tier can afford.\n *\n * The arch research asked for a cost model \"before rich scenes ship\", and this\n * is that, scoped to one saver. The floor is free because it is generated once\n * at mount and never touched per frame; the ceiling is one extra draw call;\n * rays are the only per-frame work worth rationing.\n */\nexport const LAYER_COST = { floor: 0, water: 1, rays: 2 } as const;\n\n/** Shafts at full and at reduced count. A rays-only place keeps its identity\n * on a weak device by running fewer shafts rather than none. */\nexport const RAY_COUNT = { full: 5, reduced: 2 } as const;\n\n/**\n * Layers in priority order until the budget runs out: a device that can only\n * afford one thing keeps the ceiling, the cue that carries most of the read.\n *\n * But `abyss`, `vent` and `universe` have NO ceiling — their whole identity is\n * the glow — so spending ceiling-first would silently leave them as bare\n * terrain on exactly the devices that most need a recognisable scene. When\n * nothing higher-priority claims the budget, rays run at a reduced shaft count\n * instead of not at all.\n */\nexport function affordableLayers(budget: number, preset: EnvironmentPreset): {\n floor: boolean; water: boolean; rayCount: number;\n} {\n let left = budget;\n const water = preset.water !== null && left >= LAYER_COST.water;\n if (water) left -= LAYER_COST.water;\n let rayCount = 0;\n if (preset.rays !== null && left > 0) {\n rayCount = left >= LAYER_COST.rays ? RAY_COUNT.full : RAY_COUNT.reduced;\n }\n return { floor: true, water, rayCount };\n}\n","/**\n * Swim styles — a few places a fish can be, not a pile of motion knobs.\n *\n * Same shape as environments, for the same reason: a curated set is how you\n * ship taste, and an agent picks a BEHAVIOUR rather than tuning six numbers\n * until it looks accidental. The uniqueness lives in two dials\n * (`swimVariance`, `bodyWiggle`) that spread a school out without anyone\n * having to author per-fish values.\n *\n * Everything here is pure and closed-form in distance/time, so\n * `renderFrame(t, seed)` stays frame-addressable — a style that needed\n * accumulated state would not be shippable at all.\n *\n * Zero-dep: the server validates through `./manifest` without three.js.\n */\n\nexport type SwimStyle =\n | 'loop' | 'school' | 'drift' | 'hover' | 'patrol' | 'bottom' | 'surface';\n\n/** Where in the water column a style lives. */\nexport type DepthBand = 'free' | 'floor' | 'ceiling' | 'mid';\n\nexport interface SwimStyleSpec {\n name: SwimStyle;\n label: string;\n /** Tempo baked into the style — a patrolling shark is not a darting shoal.\n * Multiplies the author's swimSpeed rather than replacing it. */\n speedMul: number;\n band: DepthBand;\n /** Vertical bob, the thing that separates \"alive but holding station\" from\n * \"parked\". Amplitude in world units, rate in Hz. */\n bobAmp: number;\n bobHz: number;\n /** Ride one shared route in formation (the carrier school) instead of each\n * fish owning a loop. */\n formation: boolean;\n /** How FAST a fish works its own loop, relative to how hard it is swimming.\n * Below 1 it stays in a patch of water instead of touring the tank — what\n * makes hovering read as hovering without any new path maths.\n *\n * Not a leash: a hover fish still gets all the way round eventually (about\n * 80 minutes). At any timescale anyone is watching it reads as staying\n * put, which is the effect being bought — but the loop is not fenced. */\n travel: number;\n}\n\n/**\n * The catalogue. `loop` is exactly the pre-style behaviour, so the default\n * changes nothing and every published scene renders as before. That holds for\n * every param in this file's orbit, `bodyWiggle` included: each one defaults\n * to the old look and has to be asked for.\n *\n * Deliberately small. Each entry is a silhouette of movement a viewer can\n * name — patrol, hover, drift — because a set you can name is a set you can\n * choose from; twelve variations on \"swim\" would just be noise.\n */\nexport const SWIM_STYLES: readonly SwimStyleSpec[] = [\n { name: 'loop', label: 'Loop', speedMul: 1, band: 'free', bobAmp: 0, bobHz: 0, formation: false, travel: 1 },\n { name: 'school', label: 'School', speedMul: 1, band: 'free', bobAmp: 1.5, bobHz: 0.5, formation: true, travel: 1 },\n { name: 'drift', label: 'Drift', speedMul: 0.35, band: 'mid', bobAmp: 5, bobHz: 0.13, formation: false, travel: 0.6 },\n { name: 'hover', label: 'Hover', speedMul: 0.2, band: 'free', bobAmp: 7, bobHz: 0.3, formation: false, travel: 0.12 },\n { name: 'patrol', label: 'Patrol', speedMul: 0.55, band: 'mid', bobAmp: 1, bobHz: 0.09, formation: false, travel: 1 },\n { name: 'bottom', label: 'Bottom-hugger', speedMul: 0.7, band: 'floor', bobAmp: 2, bobHz: 0.4, formation: false, travel: 1 },\n { name: 'surface', label: 'Surface-skimmer', speedMul: 0.9, band: 'ceiling', bobAmp: 3, bobHz: 0.55, formation: false, travel: 1 },\n];\n\nexport const SWIM_STYLE_NAMES: readonly SwimStyle[] = SWIM_STYLES.map((s) => s.name);\n\nexport function swimStyleOf(name: string): SwimStyleSpec {\n return SWIM_STYLES.find((s) => s.name === name) ?? SWIM_STYLES[0]!;\n}\n\n// ---------------------------------------------------------------------------\n// Per-fish uniqueness\n// ---------------------------------------------------------------------------\n\n/** Deterministic 0..1 hash of a fish index and a salt. Not an rng: a fish's\n * variation must be recoverable from its index alone, so nothing depends on\n * spawn order or on how many times the tank has been rebuilt. */\nexport function fishHash(index: number, salt: number): number {\n const x = Math.sin((index + 1) * 127.1 + salt * 311.7) * 43758.5453;\n return x - Math.floor(x);\n}\n\n/**\n * Per-fish spread. `variance` 0 means a uniform shoal in the things an author\n * is asking about — size and pace; 1 means every fish is visibly its own\n * animal. This is the \"expandable uniqueness\" dial: one number an author\n * turns up, rather than per-fish values nobody wants to write.\n *\n * Two fields deliberately IGNORE `variance` — `phase` and `anchor`. Both are\n * desynchronisation, not flavour: a shoal that bobs and beats its tail in\n * perfect unison reads as one animation played N times, and a station-keeping\n * style with a shared anchor knots every fish into the same corner. Turning\n * variance to 0 should give you a uniform population, not a chorus line.\n */\nexport function fishVariation(index: number, variance: number): {\n speedMul: number; scaleMul: number; phase: number; anchor: number;\n} {\n const v = Math.max(0, Math.min(1, variance));\n return {\n // ±40% speed and ±25% size at full variance — enough to read as a mixed\n // population, not so much that one fish looks broken.\n speedMul: 1 + (fishHash(index, 1) - 0.5) * 0.8 * v,\n scaleMul: 1 + (fishHash(index, 2) - 0.5) * 0.5 * v,\n // Independent of `variance` — see the note above; synchronised bobbing is\n // a machine, not a shoal.\n phase: fishHash(index, 3) * Math.PI * 2,\n // Where along its own route a fish holds station, 0..1. Styles that only\n // work a patch of water (hover, drift) start every fish at distance 0\n // without this, so the whole shoal knots up in one corner — which is\n // exactly what it did the first time I looked at `hover` on screen.\n // Independent of `variance`: spreading is correctness, not flavour.\n anchor: fishHash(index, 7),\n };\n}\n\n/** Nose-to-tail length of a fish at scale 1, in tank units. Formation spacing\n * is quoted in these so the lattice cannot silently start interpenetrating\n * when the fish change size — a review measured 36% of fish-frames with a\n * neighbour inside one body length when the pitch was a bare number. */\nexport const FISH_LENGTH = 18;\n\n/** Half-width the formation lattice is allowed to occupy, in tank units. The\n * tank still clamps the final position — this keeps the shape sane, the\n * clamp keeps it legal. */\nconst FORMATION_HALF_WIDTH = 62;\n\n/**\n * Formation slot for the carrier school, in the carrier's local frame.\n *\n * A jittered lattice, NOT rejection sampling: the spike's sampler could\n * exhaust its candidate box and fail to place a fish, which is a crash\n * wearing a costume. A lattice always terminates, and the jitter keeps it\n * from looking like a parade ground.\n */\nexport function formationSlot(\n index: number, count: number, variance: number, halfWidth = FORMATION_HALF_WIDTH,\n): { side: number; up: number; back: number } {\n const cols = Math.max(1, Math.ceil(Math.sqrt(Math.max(1, count))));\n const col = index % cols;\n const row = Math.floor(index / cols);\n const j = 0.35 + 0.65 * Math.max(0, Math.min(1, variance));\n // Spacing shrinks as the cast grows so the formation's own width stays\n // inside `halfWidth`. At 24 fish a fixed 26-unit column pitch spread the\n // shoal wider than the tank, and the outer ranks swam through the glass.\n const span = Math.max(1, cols - 1);\n // Spacing is quoted in body lengths, and the jitter is a FRACTION of the\n // pitch rather than a fixed number of units. Both matter: a bare 26/22 was\n // narrower than a fish is long once jitter ate into it, so the shoal\n // interpenetrated at every variance including 0.\n const pitch = Math.min(FISH_LENGTH * 1.9, (halfWidth * 2) / span);\n // Jitter is bounded so the CLOSEST possible pair still clears a body\n // length: adjacent columns are `pitch` apart and jitter can close at most\n // `jit`, so pitch - jit must stay above FISH_LENGTH. That is the whole\n // reason the spacing is quoted in body lengths at all.\n const jit = pitch * 0.35;\n return {\n side: (col - (cols - 1) / 2) * pitch + (fishHash(index, 4) - 0.5) * jit * j,\n up: (fishHash(index, 5) - 0.5) * FISH_LENGTH * j,\n back: row * FISH_LENGTH * 1.6 + fishHash(index, 6) * FISH_LENGTH * 0.4 * j,\n };\n}\n\n/**\n * How far the formation reaches from its own centre, per axis. The tank uses\n * it to pull the WHOLE shoal inside the glass by moving its centre, instead of\n * squashing individual fish toward the middle — squashing is what turned a\n * legal lattice back into a pile.\n */\nexport function formationExtent(count: number, variance: number): {\n side: number; up: number; back: number;\n} {\n let side = 0, up = 0, back = 0;\n for (let i = 0; i < count; i += 1) {\n const s = formationSlot(i, count, variance);\n side = Math.max(side, Math.abs(s.side));\n up = Math.max(up, Math.abs(s.up));\n back = Math.max(back, Math.abs(s.back));\n }\n return { side, up, back };\n}\n\n/** Depth band as a fraction of the tank's vertical extent — the tank owns the\n * actual bounds, this owns the intent. */\nexport function bandRange(band: DepthBand): { lo: number; hi: number } | null {\n switch (band) {\n // Tight, because 0..0.3 of a 57-unit volume is nearly two body lengths of\n // headroom — measured on screen it read as hovering in the lower third\n // rather than hugging anything.\n case 'floor': return { lo: 0, hi: 0.14 };\n case 'ceiling': return { lo: 0.7, hi: 1 };\n case 'mid': return { lo: 0.3, hi: 0.7 };\n default: return null;\n }\n}\n","/**\n * ASSET_CIDS — the per-fish IPFS asset directory CID for all 512 minted fish,\n * indexed by `tokenId - 1`.\n *\n * Baked deliberately. Each fish's asset directory hash is unguessable, so the\n * alternative is a metadata fetch per fish before a single frame can render —\n * an intermediary (the AWS farm API, or a gateway round-trip) on the critical\n * path of a screensaver. This table makes asset resolution PURE: id in, URL\n * out, offline, no network, no CORS question, no third party.\n *\n * Provenance: the farm's `/y2k/stream/cache` (512 entries, complete, no gaps),\n * cross-checked against the on-chain metadata directories 2026-08-15. The\n * collection is closed at 512 by `totalSupply()`, so this table is final —\n * it can only change if assets are re-pinned under new CIDs, which is a\n * deliberate migration, not drift.\n *\n * Regenerate: scripts/fetch-farm-manifest.mjs (documented in\n * idle-mono/docs/metaquarium-farm-api.md).\n */\nexport const ASSET_CIDS: readonly string[] = [\n 'QmZCzKVLJ1MYRzaZEFyuMCVmrPR3urQYMFR9RWerJYpCEr', 'Qma79eBTFDb2i1CtF96R6coxtnEnTu8qgW8xTTt9wdKdsW', 'QmZrjPzPrkHWNfYJWCKadGxqrhwVJu3zsovCAuYRw8EzAt', 'QmRzCWYvpRMuGX7MUrXFDQ6SEzxvfiJy2XCc2KFT9nZRwH',\n 'QmYz6VAVyJb4XdhNkcByQsTMedyLUPuy13q835XBqBvFnN', 'QmNadqTMC59SV41Ljqs3wftQRMnKzP5HfHhJhNUGaop187', 'QmVo9GNRMZRhdykEfsasiKCkSxJ3NZMnJW2teu3ydM3j3C', 'QmNY8rACjywo9MCnq5ZRLqDovCeD3rThQkquVhBNZpDx5G',\n 'QmYSmYXaGe9sjyccj3BDfaWpjrRHfQut2KUj4PPqfNP2JQ', 'QmeXJpHtuo2xdyLLHVjYn4DnkmAmYQChfwYMNgH738342g', 'QmXQ8sWSv8wVzpizPy7XAEg4fkGULegqhYmmmNxQxFhURF', 'QmSGLEWtsJfzRDtyg6qirHjVAWmojFu4DTNMjgkgGYhaQX',\n 'QmQz6mraCUgznbC2gAhv4xfN94baioDXvSwjxUY9HFdcUc', 'QmRNkt5myZfwJ4ja9s6mxkSvcAUaFmmKQT6EpVVU9EpxXJ', 'QmUNQ4m28QSNadjS78XjufrY6tXBsdoAJXV3wP25Mw8ECE', 'QmYy8J3L34p6db2qpqsxC8fMS635KretGDoGTh7Ro8t36N',\n 'QmZPL8gWr26b2CNNo6LGFJ8dn1sQMcpb1bnrmjJN3mRBPP', 'Qmbsuivz8SHZS89ss8PeCuaeZBv1m5Vwe6b841YkSt57Gr', 'QmYcF95XchfmViqJsHgzKTP33gVxCUHzq7dmxnWuh4JbbE', 'QmVuzAQutncvQBCGY43tapphaCNNoawVydEfHo48nuQG53',\n 'QmWL4zmX8QnSwcEhrxb6Ek9FWnodT75fK5jQE9G3nhrTHT', 'QmWdgysw3gxrJ3Hy129Ej7NcGW5D2utvWTMie2CA4tXjf5', 'QmW84KhqabL5niFwknUvU2dx5KCgkW9gEU4513m82fguSK', 'QmYtfYGfPRQdd2v2e2mVezgiY9qZGXmLUn4TLnhBUMfsk3',\n 'QmTgu5xQ7a2ZDnSbSeDZwVWYJpoi2LHSEA7Q172ykHgEcn', 'QmbUSdfjzd8EVsJtzt8iXmCwLPeDC84ScriwtEau6W5WUz', 'Qmd3y3u9kzHtKPvrsBraQX9VYfWKHzRgTE2Wchhrm9ub8M', 'QmbxDsnC14zmzKShpRTscTaKJayTSaCaSxW2gKRc7g8Xfq',\n 'QmeKFPkwPTUU8GFVpNfFjsBKr3nfsRMo138EiDBYyoi5iB', 'QmUT7B543xj7TYkgbSspBDrHsJHG2yJTvWPy6iVEcP54HM', 'QmNzFPTar1xFa5QJVAuksyDpJTwK9mARRbNWGZ456Ja3sE', 'QmPiYEDAJPUA7pjqjBz3xTCFcVEFa57DJoZnFaoU49FGZ2',\n 'QmTsngNyu277DGD3qg1u89nTSBHp6QpeyNjoQPNEaNaqtU', 'QmSK68pRT68B4oBxP2LjVCNJBYmwsSpkykGmgW5Fgc9HAz', 'QmTVjYdCrexSTbMkmzBC6rNbYQF8AddQi3gPbGQqwz8iqU', 'QmdFFcK9hHWKyShCX8gHLUdrWf4aKMtJonfmKGDac3BdrX',\n 'QmeYJm1xn5hnkpZSfrYcMbpmT4u8XmaSmehhnyoDttLdFB', 'QmNbydhwhNHemQwebQvhDihnc3n1QiNFPueR46ZWZSsxYa', 'QmYy1uXan36v31H4znXmL855XrStjxtyjsxEaeUngdRzRt', 'QmW7yf2JMmpM3bAauiomXhWm75ur4Lu5GASywFanSS7yp4',\n 'QmPJYvY9bEpEhDoyH5CfGkpMGgk9cppPe22vD2yechR2Pv', 'QmYEbrDdFnfqvnkfnUwDC6tXoC1zwFmaAfsJmjM3bUd8ST', 'QmSBQYv4Gbd3SykKyieHcNwT4uaRE6GPsaJkB4RH7Z5QMT', 'QmTcsXsMTqfgad6VgQxqgR5htFPhRovdB4CrNoF8RjVR9v',\n 'QmdK65oNZpQibD2TP9QurwtuHMjfQTQBEgTx3v8ywy1q6q', 'Qma23MMRv6Jv95QKg1SuSgUcmKiBbrgLQYcTGonT3FVmLP', 'QmU38DaWwsKvxh69YrrKpLwnYBuNd5RMxE4MLVUjZDj5ov', 'QmNRASdujvSTCJYHNUVfqSDWeR8DPqdbXsz9PmNTAkzZVR',\n 'QmfAMeBwHY4RXqSNwiqmv85nKNcfMxn6gg5gouhfjeqsnF', 'QmbcpM2J9kCJ8kEaN7avcum6BpcebHSML9f4iEUmwDQkGf', 'QmTrmBe8r7F6mEaUscVuzDg7WS2iP6rmDLhEx5BprrSMRT', 'QmdRWCzJuza5Eq6n8f8YPcFPxbbDBUxn9CF5NASTQrkzzv',\n 'QmSRCQUAE4L8aUTB3kxp7SLjKwk5WkCocRJc6GEUjtMqVg', 'QmdvomiXzh97Jx7MHhbb2b2y8Fr5UrUMybdiWcGCJ8gCKM', 'QmNpRThE4ors6zBb68gz98pJUneDzNN3hdVo43hLzQaSCf', 'QmfSGJm4RqUVvYsrbS5txWypPb3rRoQQS8kpcZnbf5drqm',\n 'QmUH8DBSDSev99CpiYNxd4wc7xPfFYZ2SQ1gb5zdcry3gX', 'QmZc5WQo9vvMHhUNwLg9nXCrynXMPu2L9KYAV6LD3NDm4y', 'QmYtDyPEd2BcgJLgZjj6JfpVhy3ZYA23GEGRGB4zRqrP2R', 'QmZeG9tvLDPRbBVJMZs76caJBWfHcCY599vA7PeY9yWk3n',\n 'QmdpQFyGFRgdK5FH8tQZFWcCb5YS27BG2nYH2jkfrP9c6x', 'QmaWVfUWr41XpGrhzU8EZA6khTeMVgSZ5FU1sQACRGzw6v', 'QmW8Nanwbwoe5ZWaSAN5w6SbHGYAzE1Uje5wNJySkULWZF', 'QmThgMpfZXYxrneAzYeUJwBa5gAjG3rGYXKLyx6kJSEi8D',\n 'QmQbdofeVFNqsMfYrJxoQbobyz8PXX3ghdtTKK2NtTv6kQ', 'QmbmNr5NUKCfxxPrkCwQhpYMyvYCWsMEfFmYJLUf8MWVzM', 'QmWtDac1r2sNyUUx2LcN7VeJwcdxno6x4iRp2VD7zHRhyo', 'QmTDxjfns7kPpQVWzDbhJfdczUb4RZ5jiu7KetmhhjNo2Z',\n 'QmcMVUXcDjQgEm9YJsAQykmnnyYDFowqiLaEkUyuD43rKr', 'QmWQudrmXa9TvPe4jC6JYCyUuogCmficYDytbis1dQTK8A', 'QmazJBD9H1dJeM6SjxpKAEoJqL9QY7ie2v8p3Qk5sHc77M', 'QmexHy2WQbF2w2bToTa8V2JTZZxG1f8C7TjfxVFjvvLCY9',\n 'QmXq23WvtUdbNGRAzHJen2JLGGsTkMQx5CcyJjw6BAoscS', 'QmRRgSeYAwBBhTMBCUTEVPRMH3B9if16oiuntaQBjNSser', 'QmTB273BcmsW4Gkm3MyUEkK4zH8QFkQvnTnDdapyGzYXMv', 'QmZok97rbnUDoEKiwJ1gvwBzKhhFeqWbmw92KfppvmcwJA',\n 'QmUDPUMg946iVUZM7iA7xENaP7VYp7KHfXBM9EkiWLcZU5', 'QmZXqUUaRDDFwG2HVZWvXSoa4j3eVBHLWdtTEMe9meD1c5', 'QmRTiH27NJzrRnsQw8kNTSc9iUG813HweZN4HznQCgkqry', 'Qmc3K2yyS5tG7yhDVaFVUjPRnuP7MahjA8LQJDqv9ZhN1s',\n 'QmYVdyMo7izizLFMk4FEb9WUqRuL39Wi6Xw8h6aaPqe65f', 'QmVwt4bK9Ycj7VL4hLnbHojLYuGvm4FXbGeduwk5hWwFzX', 'QmcQhyTqt6cGj6UJx1dEGqPHjdncY6Jpy5DXE9ZuNJ9uY7', 'QmV6gJiqNJpk6JuTYuA2y78TMRKrGRWD4wmVyriqocgyvn',\n 'QmUxej92evQZ5GPX8Uk9tRDnDEAPuPaXL6Sym1Yyo2jjRP', 'QmXqxKWJ98KQHB5Awze5Jye7oj6Axy5o9DxfMiXuNWu2VX', 'QmQkwjEDJy28EmU7mmpTCTY2hddZMy4NWvpFcRLAhqCr6n', 'QmYyLKD2FNxBUgHYnCStcfLEjphsi6ih6h9SiMtZp7wpHW',\n 'QmS4V15bkLER8ewFD1Q4FtmbneXTyGBmmCMQGxiXKes4vC', 'QmTFbhX5V7QrJANzPQEG9JViYBZXSoFB2ADHX4dSecRgv3', 'QmQEz3G568uXLwdVMQ8coYrWC4RefJk2c62oMmcBHr8dnV', 'QmSgJhpyvrjpWHH941JXtmTXC3pMkraMpm5s8PTUPkYPcg',\n 'QmeToFndkRpgGyApV3R9Vq9KLNnhLv9Qxw78suGA7vpZ1T', 'QmX2PU9gusQtiaKJqfbmPmDQQAgm24T8gromYuApo8tBTj', 'Qmcg8NCsqR3YwU1PCqwMXW81f4CXRKnXjEMBesgTJTEKgQ', 'QmVeYwMzpmr73zeU35UifpjkKGFT1BZ3DPz1F1rMfCxLrz',\n 'QmaN7WkmRZsMtygd2pJkedYdpYsRAAYRD78Q8Wi3VbXeqz', 'QmNUfD91TkPwdo5eakkSmTZYyymdce74XAfK5BjLNbGs4T', 'QmVgupkRbxhFvy4QJySzaZrrkQLiTTNYKPod8zDbWnMqre', 'Qmb5Uu8u154QTzoGpB6ypwVfPZ8NUsU519tQmrgE8yQrWV',\n 'QmYZkLdy6tF6gwcfUZeqoHPBjCYuA1cYfxwRnxaXciEJTM', 'QmVpfmBfjCwqEtPf6pbva9eTohrVHafsFd7toVpjFLqE99', 'Qmf8RFSgbSdoYCSqkJqFHZy8hktR1LsoPvVg8DZHe9m7f4', 'QmepjQCNSbbEoVGrQMbQdtsrwhELikDpieaY1DvSjUhGWD',\n 'QmZDDPcyzGVuMSB3ys6kpvvqKEFnWPrsFW8k58sieBaczS', 'QmbfFcEEeKLkeFtH32N2DSi6UaHJdRhTwLxXx3RkN1Ds2q', 'QmaZ1ha9dbtr3Dq76sDHS5UGyb6BXAGeVfbDriQjZCt3MX', 'QmWDPX4dKPi17AwzBua723Ui3kKj6KY8SKcSiajhQNNzb2',\n 'Qmdq5ncejjNb8uUN8658A7wtDCwpN2KCrmzvsYSDhVDLQK', 'QmTu8KgApeJEt2H8heoT9t39GR1wChQgKXQ6EQLrLuYhy3', 'Qmd2C7Xpf8mMJt8TfbGzq1UyjdBrkKyTviP7k1LCmdGY4y', 'QmVmakJTh7jMEfwzxy8jBQAeLqqhM2ujHo4K6PVtWGs68S',\n 'QmaPCMjMXeHrwU99M9bsqmTstXfVunytEjLzX9HXHq2sQm', 'QmS4VYuTajrsp2DJjVFvnKqPjUvfC1k2hA7BM7raSDpNah', 'QmNwcJ5GaXG669jNs4pdnkEHunKJX6DF1dQCaBhobZJWVK', 'QmZG9d2rygrkSiPnJVCLBsjyAD2JakTcDh8bSZbau9UXGd',\n 'QmdLsuo5siCvG5KduhizhPVQmSyK3jGVmAXkZdkxLxiypv', 'Qmed2hoAfDto8cRd1XL5C91ZeDCWSVd3K55u574tstx2sr', 'Qme2K2z3nNQ1MDPY2ejbcWczetGrwv6WVyLBtDdRCBBx4R', 'QmatpBQ8KwYUtHpEYdd67Vd5Q4WDuFs5ooNSG1C8eiSyAB',\n 'QmZ6cPQGycdfrwTwxfdSkE43cQBDjSoCZfGETtie9L2JKN', 'QmaVSjydLiF9Bno1FRyUj4KQvpHPmwEuBitpdCHMmLDYSQ', 'QmdqCKe5ANxPxQAsbMLW4JKxFndh1Rx7W653qQVVhh2R52', 'QmVPwhrB17kgB99iUPMbCT8sqadxrctX3WG4bg5twtQzUy',\n 'QmWYpdLh4mqBZC4wBBzrQLZyM3mfehzyx8jd5vU5KjK7ZD', 'QmUtWPrCifM4FM3Wbd4u1JMjfqQ8AsRzgUEF2L7vaC2bpv', 'QmYuBEuU8cpdg7Udb9wN7ChHcimqqG9F2BogjxX8NM48BF', 'QmVHFng7aLG2zAHeqnKUVoJdTohwZnBMsSeFUPsPbx586b',\n 'QmVRdRQuHZLwe1T9EzKdTt5ZFfv8vcr1kjiJ17scnwzXv7', 'Qma29scmx6K4NkMDXnRSuBGUwctShHhMdMaz9CFkz1Bj15', 'QmRC6oC1MV4tJ3WKBpTW3vvWi8Lw1VnjWQthjXrqgifdYd', 'QmX3BTbCb4snjaKH1apuopkY3ZBvCqnyh2682zTPrqCAsV',\n 'QmdQy5FVneE6kqgA2LCS3k3FMsPLCyKauWhCnuFyyZxa6E', 'QmfCoMLjGJvDE6v5M45LU3zLbXtoYWXRzqzFFvrQ3QU41A', 'QmVidfqfiJuRimwk5V2XUi1cQHR4hBMThXQZ2SME5S3Wrc', 'QmVbg3iagkRapLoctTZonA5rLfbo2m4w5m4RUaNtbtuwmB',\n 'QmerFeVE5Z6ThcgxRrDSPRtawnZo6a7QPPQbT4pG91AwsX', 'Qmc3vudn5pZkbD1ALfLgtU5UQQfaY1AucuwMWFa38yN9LH', 'QmU2mnSibwokwZSp6aXoAf1htdWvrBSzvpMwnTmnkyRTah', 'QmeqML1nzTEBAHzEXxyY6EB6mQpm1MTTSS349XfpW4A2Lx',\n 'Qmd99kaKTB2sCqQhaBVF62KjvXtz7rpR1sNJQ2bDBP1zCC', 'QmV3Jr4bKhQLmHZDeZu5xfD8JJKxvpnr3nnom27McRfCSe', 'Qmc4Vqnmy8aeMdgdGMNT8PH7zkbF5arvFxn6DxZdFYhiPm', 'QmRRkLXR53fkbrkwtpxJLvjsaxi89FNUSokLphezLwTfaS',\n 'QmNN7c1SzYAi2gMfbQq7BZdUgKbwcF8kLxQCuXo1ax5a6m', 'QmWZGWfTZWAA8iQK8XKSsRBe2ZzGFySu84Dawy2Mp9SZsG', 'QmXT6UzMd6JNMrAJBMpPesXiL3cXrAo8cF7uXAr8n6UFbk', 'QmfUXVC95xRcrVK3TNDubMM6VbvQ9TXoVJUAc6bKdi7RY9',\n 'QmaodAiK9cSVrouFiNS6Mp5tukUs7BHekc1NCH2FQV9RAc', 'QmPgdA4MnCtDUE21aZBV6Djvwrw4DxhbkY899TwAxCQ8q6', 'QmWZkMRH3iGpaPwJknobWreN69wzLojDCQZDSpxZP3VFGa', 'QmahGxWxRG4qhzUvD7b87efTYFDWKugJDuwkHDezcmCz2M',\n 'QmRd22d8hvns8H7rMhD4Xy8w8cCZgRbBar6gSWxqh9ZR59', 'Qmayv6CkimPPn3ZWTZFQEvTNh4FTRyk2HEvp1rXgaYwKpG', 'QmcPBk58JNwU293Der8QewpDDtQExSgRCXWQRYCwJ66SzL', 'QmR2NLzG5orqxMXXwAu7Z77vkqGSeHwBdsR91FVjzdsNdC',\n 'QmRTfSCzSKofkLLmoYae4Q92wpGmWjuEJniyZbHkMH7bbP', 'QmemaoPwdn2HcQ8h7A2QRgHPj3ADjfdvFFk9NyS1yQBQQi', 'QmYcNg1nTVdtVNqg1KSGTWwruUT4NgrjDH8hyJnxbND67i', 'QmXL4drp3Gz9XjDvhGPtMGNhasrHFMY5bNhqZuvJny1si3',\n 'QmRcRCYgbPwfXn7Po9Ve1GqsfYtSLHLfBnve8SvxTUPkHv', 'QmTMrkwMcMzytnPWLFCZ8r8RLStYWw96EjKmDHLc4UQ5dm', 'QmWyqnRMYgZbhJ99eyg49PgJo34eNkWWnY5U7CfNgZmPvG', 'Qmer4aFfhMMTD5mEY7sfWKzwdao8hj7qvCTVHbBacF35Zr',\n 'QmTaxfrktGTf3AfZaPZdBiTfPMBBamshM6o2qterQAhny7', 'QmUsdqReKyfnFQi7p6cQZWh2khq1QVR136JJ7717N7aUGy', 'QmfPYW9neLP8PnmbN28RkWHBX9THiEf74LrL12Epu1vG3v', 'QmQQSJFdRv2qEzmCbYDjUFxTnKgGr5PB4EQjVpyYXGD7yq',\n 'QmRjSVLsAFPnZpKS2RhCUo9fbCrniyr6NyRNUHWkoFd1C3', 'QmZ7N3xFgNUk6jzhEvPFwaUw6No9sgTxKcCyMeUSQQ3LFy', 'QmQfrqNdEyYDpzqncKBUMFVX315HjNtWt1B6qCx5tHkq1T', 'QmeAMQ2L89NkjzWpfTvCJ8pL8ik9iZGYu2nVWsnKwcxhU6',\n 'QmZEeDCLDT1bTKwma5CRVGA1n5aN9c6hD1KPKV6jAGsiKC', 'QmVkaXTC16mSLP3cNc1Kcd4cfjeb2DSHevf6FBcPs3ck3Y', 'QmdmQhqoGUwvrdMvcshpa88UWUSMWf26NJactVxun3rFvv', 'QmPhGekoLzQyYjh6dcFVNVtQdJJgtx5HncdW7YYMMnQkYi',\n 'Qmafd51KbHsTC8rHZTuHHD5FCUgj7swgDLQ3gexxCJev1H', 'QmNfjQLmNpGDBejVJTo8DZFjQpdjQyhE2ooe2DTzXig8Cs', 'Qmc54nYmDWddHahn1y9aFbxpZMJ19mN1iT7S3W5NvYJKat', 'QmNnNN5am3rajEqYAzND7EaXbn7hQPtDGrEo4hnoapFpuf',\n 'QmZMnjzmNBkNBMVcjJoFNfn2Aj932P8UQ39hH33y3VyeUp', 'QmbNiSMfRQ2HZ7nsi95EJdjLth2mdGbHwB2c6Jjiwv9jQm', 'QmQ2PpMuQSTWKxxeonx1xdsgCVRNnUsBrqqFLMCR8Ki8G9', 'QmVrUe4YQra5ywChr3LW8n26mmuH3ff98p7DPqzJtxdYYa',\n 'Qmbq4LqYwbgY8w3oWug7EYuUTeNGzQU6BwprtPe4rVSxhh', 'QmbcJnZsgBFyZLgF1CoN4BpAYXzgv5fxeCPhyTokJLqwov', 'QmPe5SeU1eK7qh9VuCKeWx9v8tdC4XguMeQ1Ep4VmG2uSh', 'QmQzVJzJowHoMpq3wuUQATJ2Vh3ketQSeJ6u1wvU8zXbzF',\n 'QmegVuYAB4eFLUeE7rYmFXWudMrZMM7oJ5WhuKfZhwsMfp', 'QmYQshiPnL7CmyJJEuyRSMLTS41mkFnPnAbbGfwtSuJY4d', 'QmPJbq1oFrTQrQDs5iUc9egwzxdiYRZ1PFfxemK993pjqe', 'QmWZKsea1nQ1yxCJXq3CAn6Zv9gT5MiHn5xepxyPdkbjRr',\n 'QmPFzAe3VzxX7MnpCmHjtGapR9PaPkTaGFU9fR8NG9Vofv', 'QmW9p5NyXUoU3ASKizFf9VkGsGjTyKC889AFeJRp9hQRxv', 'QmWQob2X7mL3t5xcF1GDnN9hZ6U27khYoDMf8k44QZ3Asu', 'QmeQoqz1v258i3YqjD94ji3abY7svB5WoV8qnBDR15Znzu',\n 'QmW9LzFyD9sEtw4QPQ1NfiSUksXDB7w2EJbbtjYkENcPx7', 'QmUvFmCDXpSYjDQRuUZ1cQejiGd3nR2qNuPdxCSA2R54Fx', 'QmPgYampLSWCmaqiZnGaf2sa9WpjbGrTUiz8mEBR9nLmxW', 'QmPcjfQkv7heSQ7g3ivfuzNorFRMJhk6tGtSAVGEwsKtqU',\n 'QmZPLWj97VidVYocCpyjco86FvF9ihxMDzm1269bXB2mXg', 'QmVru99h1EaLvQhdhnfZBGsH366fm7MsK8ukqio7cTLKQE', 'QmYcbGS1NZgfDQZE5Uz2u3qp2ypxJF8bknuKRxFU1FZLHG', 'QmYdhTFHpZB1Fgc4WEg3Ku6SGzVwxgDku3ngqJBuCiPtxT',\n 'QmS4QYGY89HvTYeNA9uMPPctXw1xC4L6ZoggDuKCqGtzjx', 'QmXnwXhkf9z5XFdTxqaCK5gLQE7RLSZ7W9GvB4gGwWyiQX', 'QmVpZ2jKCtJRhfapuRTy1JsGrkj4PKJHUMs2AAAjPhBu1J', 'QmZdqfWkT1JCefp5KmMiTokuaq5zYQ289ienRAa1V6Ekhq',\n 'QmV4nkJz6o1SyhnyfjNYdksR2T6WcaQuVJGpiaJGkv6isr', 'QmeKDp5F2g5qziF91FvP7utftj2vYAjtJTHpDYZ2mFCEDs', 'QmaPow2mt4tf37JeG15TjLbJSZJ2yngtugK8nrAjPZaaJj', 'QmNx96iE8gjP4F4HwzkFN55faUetx9ZVj5ETYWY4SrkNjT',\n 'QmVE8U8Nq1kpGKheEk2wW8MQhohY5kuvzu3eP3bnmdjuP2', 'Qme4h3KizSaqthbXRevpC6VVj77cpfHLYAFYVq44Ugd5z5', 'QmZDxCPGDuYSN2HnEcjVF1FUgx29brci1s6AYnmxfbTtc5', 'QmfYKPSuAiNyXDkE3NQ7J3ADV93j81mVbAVCpf1xF6wo5f',\n 'QmTyrAkEup6TZuYihiZCDfYjtnEdDSrcNzZ77uvmha4mR8', 'Qmcdt69vR8kSap9gZUtdTT91JUo5ePD5H9FwRhTRZjeZhU', 'QmWt6dUfMs3qMPYJgSCyjLUjSzJHj2pAdQDriPP1eDkfuZ', 'QmUA9AJR9gfBqXUSV2z3CXk1DGjDtorvLV8CwPGw1SZCvo',\n 'QmPHoQuKDqh2c5uW8utNrbERTXg3jUdoMXpHcrjmWtdoVk', 'QmYsGmRm92LHxRK2kcXep5kLdruiCjsXzra5boRy4nAUs4', 'QmebzhY566vN8n9h1P6jdrMQPoKcnFdm9UxC6BbWc3puWV', 'QmRst9PxEpfXTadfSGbTE2zxBQdVA676VmSVBPs5b3kDjU',\n 'QmdxEMY7vm5Ga4DBCL4eCttBsZLLQXyaUga583H243KFJ4', 'QmPMYtjAesq8aGDQfwPCgvDeLrMbh8mNZvqweuhZvtqomT', 'QmWkxMNGdTuvgdgFgY9KkkiADBJ466sCXfwr6Z67vxeFbj', 'QmQPZntLdCCT5PdYRqgqdfDe2mrWqpRYTXRL5rGKQFSHF2',\n 'QmSoQR2AbwtXXpp9qwJwTt4ywkXBt339RU5FUWWHt7NJcN', 'QmdAoQqgkENgHXAas3bCvDyYCJf247uTj8WjEqHc21JXST', 'QmbJueSqMWqV7xKXukXSxyHj5TPXxjBK6oYMJdBNJaGSFh', 'QmUjvGyGssPE3r6Mjuz3xNKPevC4Nq9tTrbzdjDrUCBtoh',\n 'QmUwomacb9wepczydDRuKWc8Pwf7cKPF8q7NbmkXXmkyec', 'QmcAwhhDMNEnq2GXmR5ZiHPniTCYEfikVCW8oaA7qJ79Ww', 'QmdkEUeuRfQJjBWbvvKgNFRNzjSJq1s5tofNrrUmAqnV6a', 'QmWUzo5o9ki26XC4dicECwvNKvKpMsQ7wr2fz4bcGp4w4Q',\n 'QmNnbd2F4WLRuAKPEEi4trTxiNVeYn7z2ZctroUcirSszf', 'QmTxUZJoJktDCkg5mdU7qA27sJo1gs2kWyhxA4xZED3aVj', 'Qmew6YW3ttjCDMfpvZz4WjCTJTx86hBxZvV1NB6MHNENtA', 'QmShVcWV9eR2zkBriASQWfcd6Ue35SU4TSvLAF2Cz7caKA',\n 'QmYY6cixMbkYv64jssd2sSbq9vzXwAXFJwjYXw6J4X8pn9', 'QmYE1sBR8FbzGz8BWx72sV3c9wq4Mm276nYBXAiASBGGDj', 'QmVzPtCUZt9TtNu7e4P7g6FfxMr5Z3CCb5DXQ3KpiRUCCZ', 'QmYqkhEm1XXp2WqKpejvC7BjNXM1FMc2uGH2pVucacaxtg',\n 'QmTwdq6xAaeJ62i9Vr4AvJsmhoNpq6AiF5tHwTpo5zCqNC', 'QmXjTMRXXpqRYcf3qm8NbNFUKN9yEGEvDjy8McLaGcRfHT', 'QmdjfULZ9vMwxd8kQrTNjWQ43h1NZ5KCHCEmau5KguTcid', 'QmfDTne3NS2hgnJZjwEpMPfcWns4TcGrVsJck3JJcwrDdZ',\n 'QmfGuMepibrSLCztNy4TNGPXj34wWv5zWFFDxSh1E2LzqL', 'QmQ45eU49MEzvikt1iX4YzSa7NvyPV8qhz8Cp3cPbtp98g', 'QmTkMujFKq2s1qFXvQPG7gUnV7UxRr83Y4ARQRu2oTL3xw', 'QmW647YTF5b4FvTzM4vGYmU1dQYNfiwFgPFmHHApfe6YP7',\n 'Qmd4Awoyx9DAvDWNyYPYnH8cZy79KDHBQCd2Uvgs2jXv3P', 'QmYK7UQHrXLF6kd7LrX8YZcvR8V6W9fCYYPPW6YwJv1zVL', 'QmXtu363znTk86aVuKBL3414s2eMvTjdnhhSbdRWgJPg6D', 'QmWtxrWBwwdzLgVDPCT37hao6p9tDUyYHcq2wD8289prjp',\n 'QmaHbEQAP6k2zopJHJBzyaK62zNX5yH8yASDjkaG4DY9Dp', 'QmUZGF3ge3d9rzrtxrD6V4qx2gLtGeeNLuCb8fQeNyUkwJ', 'QmfBBnNrVrkffMKoESvq3cB6nAWGpfMPjduTgw1unahvPf', 'QmXsFMnKWaXJ7r4tnunZrehsTcdXeesbkz4Ck7ynZzCTDj',\n 'QmbB8wwtq1w76YrMueifMuvUymzzC7chgcu4Zp76HSZpRw', 'QmW3UwAySQQEGeXY7TNKcmSaGWWpDsNhrSySVRdYnBnfgE', 'Qmf5FYbchHe2gCHFsmVKmDPSKDfR3RWruu1Kwy7gUX5Jhd', 'QmdwubNVvqAUQV5Rft616cox4HP1bAe99hchJ4n9gKZ2vA',\n 'QmUF2n8omGU5ko3XjsWZMt5dwS1ZoPnCf3RJyXXobYCJJW', 'QmdDogp7wJMLwHG2JYV3YTCFtLagceR5ANkeC4caf8CAMR', 'QmRpnJN76aw2E8rbBxEQsDccW7KDT9wLBQF7ogDgtScgg7', 'QmSomBzNykXFfZaHcAed6gxgs4HcNkjyMGbk8LMXWLsh8i',\n 'Qmf8cGMJGFBiws7nSJ4yrF9yHtxWeJJEzCvpehLomNxzgm', 'QmY7UjDL2qz3JvgVwpoWkmtTNQzV6YAdkGDe6Ammt8f5tQ', 'QmWbrqL55LSfjXRewWBf37yKToJRBdrmCrRnc8PeBr2bae', 'QmYxjjBtYW3ppcCAai5t8UekS6nuknRgwH2xKUhFK8Tnrj',\n 'QmTVo65272gTQvTrYY2ECidpW6yHAMFMkZngzsu3y4pYhY', 'QmcNxGZBV3BXQEgPFa2FgoYCkWcGeakB41ysMXQoKtG3qe', 'QmQJ2y13JEmSA6nDDLWbGnibP5Mzuwdb5G6PfRdkYgT9DT', 'QmTqVinHaSUWBZTJ3fUpSTisWWn9n11Go4AtN3nMc3T3Um',\n 'QmaSV69r69MtNyXUSgzrdxQpbFtcw6KBLCb8WCHXypkX9D', 'QmbjEQ1QpUAYcYZhyZWNHqjHa25e6a7kwrAZVYbfYtkQ3u', 'QmW6r3iPj4b1mqGDRB13LiKEFaGn7e7MZpUgjAv2RG27WY', 'Qmaa6DgT6CYQEMnzaA12yhgssV6WERGcztsqWQCJTCPtm3',\n 'QmYfgJvNM9LZPkSxMbhGjGgSoFXtU8z8r3t8omg8Kaau2d', 'QmVV3omLAKReiKEF69pXhZgTdsoPbyVR2ugoevv1x9CAgo', 'QmX9o8vNPFV54LETBYdnAwWLTfjw4cminZtnXcK9jm7zPU', 'QmPNhKGc1sb3PydoGxUaxQUrHELp1AwStCJ4WVYkJkfJAP',\n 'QmXX2ANymzA8d3pzghY5Z9aBJZVHqN4xpchhywFNUAf21w', 'QmVEuGnPZE1PAwcZeXqcoJuoc2P1A2nytxag59o9GauDTm', 'Qmc96xroeXHSYf1S5bBDjY3S1PAB4kXwCBQgLRsgyp8VZt', 'QmRoYGay8SnJWVwqzJ43vYtVJd6q7GD773kXmsLbdhmLZD',\n 'QmR1BHc59QoeCnEjjLrynnAH5wpu9nRWX2e6diArvp5Cse', 'QmbRDNBHtZA6Bqs7mpLAzdQd1HrNkZw2TujFNjEUZ16dUU', 'QmTTHRx4KLEDa2W4skQPhARjSfKPQpopAJUhqubn3azyKS', 'QmbFeX3MjvZituGhnTq1jQTCWnGqtNyVs5TGpKt3vBucpb',\n 'Qmd4CbU4aeSbBzq5mk3533GhbwVV3C5oVNp15B6saVEDCZ', 'QmWTDFP9CrdByzLoqzr4Pn3QcxCyKnyhFnfWHLX2JxQ2kf', 'QmXW1ou24MdJq9BHCi1T45TaAbhHouWGNpsmL2XzYkY5if', 'Qmami4ifqxEDmDFSK6hPJX6FQ7bJ9b38Nz2aMg8tkAPRvc',\n 'QmRnf5bkjXSQmWPTLBDXRyCPj1SxL5QDZq5LGR1zL1fShP', 'QmRyQZAhTomP4ir9b1sFuZRivoone2ukwHtV48DXwA32ko', 'QmbDsXyUiBzMA52vmHhfY6qCGk2NSbHRAyGBCXN5bMm5Yx', 'QmZwhZyzjrc7mgMHLQH98xBbqn2CgSMt9yJ6cH2ZKjJ5yy',\n 'QmduL2hm9bV5GN9U2oxLByqjiEQ5rXEARLNnGvf9sbmRdn', 'QmcxLTpyAknVj5QZgsxdgrg7NSqChD93ut2RPBFT1uUTdr', 'QmdvaG9MiBm85djwjYrPJg3wmh38dne72YDcSzXaPgBNDE', 'QmXF9A1poQfKR1mkfwv6wUNk42S3v4a7K4AtCaVmoJcHHZ',\n 'QmVebS69oAGwJJMRHUtTxbNgCjW4RZg8CgMbW67bndqP2z', 'Qmbz2ADm3f3asYgKwUArUBWTB2Grtbe2fuaQxGF7pfjDJY', 'QmYr5qFAnmTKSeVegzjn3Nt6SUAyMLJ8hpZhK5qKwFe3bt', 'QmdsAdwp8xaJF2Qc6shZPwd4P4kiT5qK5H1awxZU6MdiyS',\n 'QmRkqCuvbnyqzKSjY5ozN2oEGT2E59Zw8iHhd3pETfemiS', 'QmVhXYiLs5AMSnqGLrYC7kUAzVTTiyu9qDphb8TFH7biqk', 'QmNycssFwkGPSsukKwA34GjFoKWbef1e1guVkWHUdvTgWd', 'QmTmuRdadBzwCsdHDMXAqXvDcGkCQDfvoMBsqonJNRpSiB',\n 'QmRoZV32mXZmFwwqvX5ykMoyNjyjZvjVT3NkLVJcLCj1Rn', 'QmeiLPe3djTfMmAKdNCnMQBdes4vFaxbzPH5k26rnjkTCs', 'QmY3FVhPNBxjgKZYi9mSiqAB1GXaKDMpRPoFLoHaW9UFBc', 'QmWyhhKhuB9zwURUCaF5RssuKT2uZi4gPcXLwHRPkfs6zN',\n 'QmW7DzVmvSKYLWXzoSvF5UFoy9z8ATGUcs49VyvH8EsBZn', 'QmTv4wHn3pjs23pt1ESBpK533TwDmepSBse2ULvf5DzBiC', 'QmTnbkeTsXAruaPkSrmGVBTyeBcztWRTAfmQJa1FDDu5Zo', 'QmR4Kw7wjEuC6j7XgwWKSJYUb8fqq4CPnUBNC4ojaS2Eib',\n 'QmRLVSNbfXdCJ83i4L6d52A4XoSqteb4UEF5qNXb1A5YBf', 'QmejCD17oPmHGhnLxohaToQqUnThGE1qy7fu9XoHT8JkXR', 'QmQn5eTB4VSNBY4PPhBx1qqULMdEdnBDkmU7ZRVsQPupPK', 'QmYwSY81XmqvToA5nHWnCZYTzS3eShnrEV7XmrVXFRqLfb',\n 'QmRmYSraWyDCuXtBgitfPgZj5wpGr11dfrDg8QzdfEevpM', 'QmYhLgHZFQxGyPegdKn3TYoEYRuhYzrmfhRh4nHH2m1VL6', 'QmX1cN8i2chRMsLxJxbLs1u72gZoSEdPLensxFN6WaeGp7', 'QmTv6mimaoDKCPNBhyQZTzwT719K18S63FkU8oMKbtsSgc',\n 'QmQmfsRBNXHidS3oUnpxkkTwWKnZ6z8vreaujAfY2W1P9L', 'QmZLF9SyRjgPtcnixyiEUGQFyVRtKaTzjR477bKBTDmvLp', 'QmeKWJuHuynrbYsBmjrcu9N4Kmd1xi93X8AfNEbVN2vvHk', 'QmWLDLsXWUetW24eXrbfPrWVdXhzoADKZi9hKvcLPQvnZA',\n 'QmZfhbN1UELRfmQtGRxFJPtUhJpsF2wwBnacw7EdC12kvg', 'Qmf75aHP7UuHkA9QU49dqzU1iVBHq9kryQVYwaBPGkM9WF', 'QmTvHegn544abqaYaxDhhv7pZ8DEo6nZVyEQ3XmPrtXamD', 'QmbSE9wMdyLtqSpJcp5WHHQAzMJjWDyZqBGMxx1hCBFTTt',\n 'QmcJurMQ5LmRT8P2g92i7dLmMp7h28P8kkPJ3pvLsUGUuT', 'QmPdDu9c5i4A9XhRXMWZaWw1uBPdPGU4FmSXRRRzBrcvCH', 'QmaGhZoJhufXUb26RFy8VNXfSnrTFd9PVkzSL2ENtYETFB', 'QmPseR1x9WKsocnXqbacTvEZyQwVcdYLbLsFd78U44TT7x',\n 'QmSZmmipeHyH9E1oCyZ7XMB5q6L8t3iCo7em5VS12tqrME', 'QmdUs3DUxrJBtKc2QU7Uu1hcw4cZsKDifDb43NsMsmMWYU', 'QmXsxzWCc3bEtytUgGHx9eorMUAeCGsudFhsR6v1TbqvfB', 'QmQ5AAH23EUGpShhnsmWhQt44DnTAzZ5rCy5ZXNwM7ys7w',\n 'QmPjk1HGXEvhzmJBED9CL5TC4QzBju8rGc33EeKdBNbBFZ', 'QmXfhKkGZSKU2a8Zb1z3JsaykCLeGCaeMA2ipydq6KdWCw', 'QmNy8pmg5hE9m9rHCZtmZgNDeYwpDpNk6GEYGjjCjHkHZJ', 'QmUFxK9QdQbRMbQfUxv3nv66XRdoEePaKazVjXuzwCdrMV',\n 'QmUMJkRPkQoFt3dAHPVAu2ogGFvswUQAXH38bzXLDFkc7Z', 'QmWrhGtxZUXKDSJsuoCkBLFfSNNy6pHD5gRnHLGNKhZGi8', 'Qmb7YAP3xcytesVbvezovg48R9E5nJ8XgAPzATf8sFQNkf', 'QmRdWrcxmqdsyZTnpRzBt3fPDnYycArqv1jJM87FEqJeay',\n 'QmSvMoy3SeAREVFTcr3LRzw2vHAq159nTjCrcXNLRSmAyk', 'QmSvof6bsEDAuMvvQgeCXFLhATfewz6uA9QwcUhEYy2AvY', 'QmYgqwNStNFuQBeXsjybtrgCswdzApm5Rm1s23JyEU9WfW', 'QmPvQCbgwBsgzagm7w13G4rxai1DXJhKrh14HKd5PQw72F',\n 'Qmb9QQCRuoRnu2YieCGM8H5Zf9XKdLGEmSJM8oCdvPqqrN', 'QmcTXbXZrwwt5KSVYHb9ffsJfc5D1LQb7KzLvHoatQy7tj', 'QmRWspnUxueYQqAEvSVb9JWPorGiMHKFB8aXGKLQJepjnM', 'QmeXdubuRVN3T4kh5QjjDWXDPegrChgS2DCXNLaxUYndsH',\n 'QmQtLz5n14mz2vHncQxQgtYFBjZR27vy3GtARaXRphuW2u', 'QmeBmXdDQpqwJaJhN7B4RYoQ5fJwC6mtQDK6hj4yghyVm1', 'Qmchuv1DxrQ1byEx1s2isumNnQoe1uDoq5RVRKPyFbRqMh', 'QmRWKEjcQQo73De2nHqJfz6hTkixaeiTcUkkCvxNoziTpv',\n 'QmcNhE5LGJ3okZKpsmbWHRsMrwooEGoTD5j3UCq592ep9N', 'QmezYGsfsN8UE98PPAafQHdRbVtAd5bMW8xHGmR9XeWXDC', 'QmYVzAikao1frNNH9yphk3w43p8yeo3vVyEEmYReeFYM2J', 'QmUqVej93jpSMDMAnEH6SVSZ6uTWzVn8uE3zZe2besb7g5',\n 'QmVrkEL7jTsnJVhSjHgpXQqf1tVZNBWhXqVHgKeGsP9nMC', 'Qmabyk6rg9YiaZor7XSAWy288H6Zmkj9m4crE6wQqo1XSJ', 'QmcMrXvn2VJQjh2HbKxeswDv6bXX4F1PKDRkbZ2xAzvpjv', 'QmaJ1SBMs9WW3Xfzcq8hgs5MY8NvSQGtGU8jGLGK2uMnAE',\n 'QmTqVH42fgCKHg6Fe3jq7WyT4A2hFTqmbucoqXeRviRGVH', 'QmZTenfCMdDDFZKDx4kDrgWQ7ZQjQ6oQkzSp6U4dfH3x7V', 'QmRFE81ssXNF5oFfFMhb2cZu27ZrSuSokzduWy4XEFzyZ4', 'QmVpqFAddYg7D1LxvJeW7rMhV9pTDc5WXZEmRUkmkfobe1',\n 'QmVnC6KpkunQKyUwJNgUzpE2F7sqhA6bV7jSkEBStGcz6p', 'QmTjd8GAaafuwybbwTRECkuhc6rM3WLg4qrodLdqc8RWAz', 'QmZPjBexGowyGDYLAFF7LjQuwYfTSi25XAY6rPeTDUvhDE', 'QmeCuEHVfYMr8ChxydaTnAQhPP21SfRNmhbrrnrmFujNP1',\n 'QmeQk3tUaABArcy4BF6LidJccKSUPsanSq1C5BUmKxwGSi', 'QmVGseX7vWj6HH3vybSSWLbUY2yeKgNwgnUYwriFHpZGsx', 'Qmeoc99jqk7TdazUBvP7VxZL6EP5AjjWxk3Ug8oJHapxhZ', 'QmZpVWbcMi78uSHLmwwdKmje35CkQ9sTWhUssEF5qR91LR',\n 'QmWUzizz8BdqphbYQtwTz9ah5RD3m3GqoALDHqc21SuCgt', 'QmXohnh1V9Bp5vDBwrjLZoHcfEdkqfR6CwXZDnfmwZ32mb', 'QmPbRi5FhBtsQa1xPAixKdpw3PeFeszYyUDvqw7W6kFmqL', 'QmS33cEacDz6iLr3JAqQXNHoPz21sdvWShxfWKJUtVJFuY',\n 'QmXjbU7kKPFeQHvMNwAr2D5DTaUJ1WVeELgqM3ATSZ4Ksr', 'QmRrDSDwrryprbm71xKAxTtP6iWH6aFf9KTDRRGRPiuAx2', 'QmWuZzVQULPWbPMgnc2uvoNicW2tA8YQ4Z9s1RXH2xmPPX', 'QmVDmCvVZ9me6bwVs9C329ibY9JgmBzZFnrkSyy2E4xtpd',\n 'QmahqoEozVJF62G6vYihJLEa1AMEbmfve9k2d7TWoErdcy', 'QmZLWPy49X96iHZSfg3peGC3CmP7s8c9CMC6mTJ3wmmdQA', 'Qmcfi34hZ2avTeB3rVPCpC5X1eayvYyiVv7m2TAijMBmb9', 'QmNtiB3EqUf1379cD6NVBv3J4VxeRsUWs2482ZyZxjY9gK',\n 'QmNdoAVYna3P8Mej5tQj7bo5CWQRUXwxAQgmnEwFHTqSV2', 'QmPsepvYGHSqR4oZoD5tjQgGRWcc1HE2kDBJgMhwxCSFdW', 'QmbmRRWKoV11bayoaRXCGgK16vTEd4KVnRSZqcyuoARCxW', 'QmbQg3RiG36gKxqDX64qveewm9iSo1RvL8p2CK9WbupWhX',\n 'QmcF2EBQrW9HUpYQ8i7mZ6bSGzSTxRgznjQpyPpo5U2T2m', 'QmUhu79Q9Gv3bMEtvqmyrC2R3eRHfVfg32zLTjrgqkqv6B', 'QmP8ij2oqUKZbVm1zvUiGYfd6tatgSz74Y63vuWtAWhksa', 'QmcrKzdYMReMDoAX2v69U2yPRYN1VSJyTqYLNVicQtcJkV',\n 'QmdQkSp8woSGtDEJx6xmfCpQc4dmpmC9ogxRggctfiusyu', 'QmZzchmmu9n6L7K59yTLvFnjU5LTyPF4BwgxjT2HYquNXW', 'QmdzuRTgi5bnGXaTmb3RYGcThA1qNR8Whx1tmtqrQDR3KK', 'QmPG37vSFgCmUcEbxdQDhdExb8JgZBXdUu7wuWH76gt3mX',\n 'QmQjHzyms2nvVissqcQdJ1h1NVJbqJcWWvMccEecxhqdTN', 'QmSPCZnFqQtPuu2wgHGqPqDP5M49TLEtL6x7atTadsGifx', 'QmTM1BwXakX9YkYxSaLdtyxdGYfc5mDYYERGphURZqHsuL', 'QmbYBQaxcv1yDnszX2RcZtLRK4rW2vJLeoyC7NY5hv9dcf',\n 'QmWg5TrRuvZeougTbNvcw8CGzvFUTBTVZbVr1mAFnpUoqs', 'QmcSyeHikrbSi4bje2JU79FepKS9mrTGHyfdgQNLrNcR9x', 'QmSycpuBiHdXaLmYRT6srCZcW8mcCHHevjyK36nY6q5jGm', 'QmaDXEGRGqQ6AiD61s4e56qcveaWHRobK447aNYd6Fciru',\n 'Qmd8h9crMyMwxheB8aZnbsCx9FQkVaV7Psd1NDvLCMRGWT', 'QmXRbBgvXhK7qNPZsmNsBnMpzwZTi5PFvHxZbPuR79hkrb', 'QmT4tMC1rDNHJY3h2fXhrPM6zb2MHWrhpWrusKP76p6KSQ', 'QmPxDqqghekjUxCbo3mz2KdxHk9z3BsyJKMcJFxpikNCk3',\n 'QmfSJiWBj1JVWYqi59USbJJwBZ9SAahvqu1iXSF7Pr2JVR', 'QmcDEJorb2v5JAFjpDVyM8twgFXdh9qBZFJ3uHKUZ2eMqK', 'QmbHqJWd5GPLiCA7UJedGzLzzkjMNm4WG7RVY7im911SAQ', 'QmarmJnvsaBnXqyS2piyY57VyYXjDYbhMHVomjeBzRV2K3',\n 'QmbYCnSCyokDrm82MVSmfZe6bumJ9PaNZWpPioX7uxKmbo', 'QmPgFMCk6EgKqEc3Hbcrmef9dHyk4fBe6uX3Sh6of67GZV', 'QmeZPLoLqAta24hnhfBxMSTTUzd6mxgEmHGBVJqZMxmsVr', 'QmTJSKFa22b5ecUXEkcgrXnKSEA6WtjpfDafWtpTQFAGGF',\n 'QmeHUBJZ3xJDPpDLJ7PMGubnuR9Fh5M3N1JMmnL22RUY42', 'QmYbk4GneMQHqftuDXvKh3xBL6qZiorpLeTrNsb3rbD9XR', 'QmbButr18q9TRDXkm3mcq1iRL2fYgvB8dimx6jCcZv9Pjq', 'QmbiVK9BKuVuEYQzJpuD1a1bBU9c6C7CgmJsTrjXbwgsWR',\n 'QmT6veKQdJdP8QjvqRFhAeuqr3aMgkkuWzRn81THxyMydW', 'QmPwE7CZDQ52ofjcGuMqP74VWNXWRbY2havDRUFd49LCnV', 'QmWqsrtgeSPY6jmxPNiuZ3589odmpR9omXZJ1cGWeLxFd5', 'QmUXMSfzLncfosqrwx5P7m8UDE4nqQ5RciZf9YXrcdoJvs',\n 'QmWHr8JZcBytZYHyhP123pTFUPyaK5ZwEAA64uj2y2WLzF', 'QmXrLBqEkTUH5K8zpp6z96tL8tC3uQvkYzSpWHawJDWyat', 'QmbbCEUjF3iQJDRyfireVh9bUq4EACveM67tJAwpr8MU3v', 'QmcKbbrrJSRh7QNzdg1y8GVZNNotr1ZgeMY6EmhwS4v1pC',\n 'QmdDqBiN3z626Qu5jA4St3vpbLScobEYhuQG4RCtXHzyif', 'QmRxab77NQPzSgLFLFE6BeaN36zPDgeL8gPSe2VdfkA6P6', 'Qma7TTNV7B8udte8YY1H4WD6nTBiiw1SAzMktWDEkkjDWu', 'QmVEz4JWsDWpQrvtZnVbyPVWfHaudCkXZwC1zcs1vQKWGC',\n 'QmWpJWo9QZkCr1i5BJvqSfrwemjaNeZ8hAR8oQ5WvNh5Cu', 'QmfPoCoNeXdEuknusGvEhvYSL5wUmsmFP19b1xQBqRjecf', 'QmPRL9h9m8EZDRn6QzMpP97CRHEnyuToHYyBa9Br7nrH18', 'QmVEAsxcZ1h2fqsXLCWNui2CJEAwkSb8LoerCLGN7wFQ8y',\n 'QmPDPFoeLzbPHamsVAPtSPy1wcBHggxj48iFbobAUp4gme', 'Qmckvd9arXsXxDRTarHu2KUBnNJqHmh4tnW3S1RZ1VT89t', 'QmPVJEh2LeJyF3PUJxmYAC3YLNDNQJ1CQ32fEzkxRsdiko', 'QmaqVzchifsjNx6Hy1RD6wb2Z36BiJNPzgSyisEBzh8T6Y',\n 'QmaBx9PXB2nRkDmKhFz1QJjRsuZshgcpzMMmhLt5T4qSyK', 'QmNPoTBjpm7UQkUWxyqb6B131qQkvCMqGyh3FU8rTyytk8', 'QmX8zsBdjvjNHXTXmNdSLBjMLceiQgA7xKn5HKUTdR1HRw', 'QmZxTrZPtwryMoZSUByFoLxMJ1hnAfLeXwesLsqQzUZzyV',\n 'QmVvEaCa6zRp8Z9YkkZVYBn2owSdwZxupEQacjfd1b2HA2', 'QmdjQEZzEhigLyfij6ZtJPPHUQR1TdsMRnGPgbxBRmw8op', 'QmYF7w26Fjufgr1DC85dM2bycqckSFdMENHnZMmPhQDtVi', 'QmVnxHS4rty2k9esBVjM54iBKj5EhroE62TBpFCQ7ZfSMB',\n 'QmbBUkh7bQdmH92NmaycDakjb6jU5mYJooU8nTDCwNEJzu', 'QmfJYmwnagstpwJWTc33AZsLoFVsy64TjXAhdaWjwys9Eu', 'QmSL23H5bR3JiqYzAYiXfbN6Yu7TH8QhE2dsggbeef4VBR', 'Qmbgzd2gfdpgBTEEHUjJdcJAtHzuuQxSVC13fDZPwQ3knx',\n 'QmeQBNX3PQvQMrj3VnbeELzbdWc5wGfQSAF8fXSNYoWrAg', 'Qmcm1bx64SHmiECXYs5TGTEp1gHeyEsBzp12y5Wr9Tduee', 'QmSzgefWsFPS3qwDvmyTLZV8P6ZBSEnW7yEejaooDKXXUi', 'QmaELnqmjdMPZ8HqsfW3tDuz4uGHUn9txJtJkkJHYSR9Lq',\n 'QmXwR5VjTSrKzjHzo2FrXnWS3GSo76oPVrd5WhV32sLvub', 'QmZVP8g8tSNDriRH32jKFrD34RfGro2xY29zwqtNkTqVz5', 'QmP211gF8mXzCGExNTNPr4a3Jn4MH7iZ5rr5oavpsYhx6Y', 'QmWE4zzqoXS9yRWFYokMb3pRdTeehRqBgEuqwMgRYka51R',\n 'QmUSyyUqCZnTQmFYwNUhWGYm4CSJCuGgxFCbnD1T8mRZrM', 'Qmb7jwtvCbPxfUZAhhqE5ng6et2ViYspQiArLqf6bUX1Nq', 'QmfSyDYeshn4Y5JyP3EzDEgsgL5Z8o4AouZWDfToJRJSmH', 'QmPobNHxbr6w2nmdTxFfoVH8FP6QTbgp8wUb6UtxAv8S1R',\n 'QmPXF8ffhF3zdS1Tng3rVN6FZfq3hxMch5BZcWWfnfEhSN', 'Qmc581Phidr2Jx2AYwtgjaYeDUrC2fWx7bowfq5hN8QRyz', 'QmZcMEbiiaTaHizVzJMEFhBHmwVpBt7RFcy4r62BAuJiga', 'Qmek8PjkwBA8x9zBLsrJPY1quKobbNLRXgkXN8F3iaHMx6',\n 'QmaCGKbEog5LmnVrNGj1QqauAqmYmtHLPdCgaw793JgvTv', 'QmQ6XBjWGQpep3NFhWeHn3p4KVvQxE4wG9vVRka8HNbnQr', 'Qme8RgJ2sp7qWNqTHbFHgbRejLqugjcbzG2LPjMVaEF5dD', 'QmeE6VgtuYCsMAmkoKzeeTpcDuhJnzBpL8yf4pKRGJB81g',\n 'QmPoREZCTBDWa8c14fyLTB2S8hVoZq2a7V7gH4cte4ES9D', 'QmfQuoGraszAniiXM3CF4ut78w6u73bowUZJDeT4ukrsZb', 'QmeMyKGcYLq7XLrQes42rSiWufA49x6DsWh8SMk8QZCXR6', 'QmcF7MsVaTtrGfeM9ZSq9PUSCoTT67k5B7J46L7CH6EhZR',\n 'QmZaqVVqLKD4SGFEDMYHCrPTcSVuduAwHN6Yjs6CMQhous', 'QmUwBcPAkyw2arFBD8Mqj6pJe1wgpSHE9NLSXFiEVQqcu9', 'QmaN7ky7s6zBdqWodEEqD4844ajqpobXCChwxccqS8rjWx', 'QmZECsRwqvdjbz7DYrZcZ9wno9RoBuNXX1GDWQNaC4icYq',\n 'QmY3xWVqdSm6XoHMvPYLamkHLC6VVRnupcp7FmZGqFwCL4', 'QmbGFhAS7YFp2ir2VXQBmcagB5nYfcbempcrj7U3ZS5ySx', 'QmczBQnqVcBs4tnY6nw8H6T2JNGMuCCtezWjbCY99Wokz7', 'QmadwMSsweUH26bAbrWvDPQGQHDy31ds2GCAU2KsasMZgQ',\n 'QmTBNvoUiwPw9HSUmy1qKCWPBKkBRAensgooYVqMmsviaE', 'QmRR7sPfkzuhYATFjGiLBVyo9QkBF35V9i8vk8exthuUoa', 'QmedEbGk9ZMD3pecvy5mc5S41cXxQb83cfkE7JQjNbJUb3', 'QmZaHP72PQ6UQ4BqW8rjLDN6BFmPX4U8S3P3PtMCjVDudP',\n 'Qmam4H1YnqinKNpwVMqFtVmtjCVm4YTvHNA1gucUoonrz5', 'Qmdgg92V3KbEuiJ1PLG7Huyph8SoYZNgonTCdbvvteERyF', 'Qmezb3CHKTtcxc7qxrFKkgsQKypC2pEiBw1M5jgD657tah', 'QmW6ZHWgtFpeY6P6BGvmQRNLbV5oCYBsK8z3pQju8L8AMQ',\n 'QmRj9wPp9dPxbDuUjStKArQB26X56qpXuUS2vAwLTMGTR1', 'QmNPJotqYDKtNG8XgHkbHamxm2QMdMn2FtFRrKy8t3fBhd', 'QmSXV1k82avNPDXYHRufH6RzBfyUSjnDmQoQ9idjx9bGHX', 'QmPGq6baCU7n5oq6c1wKfP4Scqg23Dfs7fwS3T8SDPpGTW',\n 'QmTeNQtppRWfQB2PQNohdx2PP9DAos1rKBqg7ypCrs6rXs', 'QmW9iAGca5Nst3Ck78RWs7PePgSB35mMvRKhFDdHM6CaZ6', 'QmcoEw3jgjT18PT6E4Jhf6AF2jWaqFK14T7cSZmbE5imTi', 'QmRgprite9rBeUfFCiCta42BvcjfqdpkAsXpguG5huz6RR',\n];\n","/**\n * The Metaquarium farm, in-house.\n *\n * Everything needed to turn a fish token id into asset URLs lives here as\n * DATA — no AWS API, no metadata round-trip, no third-party resolver on the\n * path to a rendered frame. `fishAssets(85)` is a pure function.\n *\n * Why this is not a client: the farm's AWS endpoint requires an allowlisted\n * `Origin` header that does not include idlescreens.com, and its media lives\n * behind gateways that have proven unreliable. Depending on either at render\n * time makes an ambient screen only as good as someone else's uptime. The\n * collection is closed (512, `totalSupply()`), so the mapping is a constant.\n *\n * Zero-dep on purpose — servers can validate fish references through the\n * `./manifest` subpath without pulling three.js.\n *\n * Source of truth and provenance: `idle-mono/docs/metaquarium-farm-api.md`.\n */\n\nimport { ASSET_CIDS } from './asset-cids';\n\nexport { ASSET_CIDS };\n\nexport const TOTAL_SUPPLY = 512;\n\nexport function isMintedId(id: number): boolean {\n return Number.isInteger(id) && id >= 1 && id <= TOTAL_SUPPLY;\n}\n\n/** ERC-721, Ethereum mainnet. Metadata is per-token but collapses to the four\n * directory CIDs in {@link BREEDS}, so `tokenURI` is never needed at runtime. */\nexport const CONTRACT = '0x680cCc4fE7aa62172D20899Ab87C5304545431CB';\n\n// ---------------------------------------------------------------------------\n// Breeds\n// ---------------------------------------------------------------------------\n\n/** Every breed the Metaquarium was designed around. Four were minted as the\n * 512-fish collection; the rest exist as finished models in the original\n * aquarium and are the natural stock for future tank life (props, NPCs,\n * wildlife that isn't a token). Kept here so the knowledge isn't stranded\n * in the source project. */\nexport type Breed =\n | 'betafish' | 'angelfish' | 'seahorse' | 'seaturtle' // minted\n | 'blowfish' | 'hackerfish' | 'glowfish' | 'babyfish' // designed, unminted\n | 'shark' | 'crab' | 'jellyfish' | 'dori';\n\nexport interface BreedInfo {\n breed: Breed;\n minted: boolean;\n /** Inclusive token range, minted breeds only. Ranges are contiguous and\n * in mint order — this is what makes id → metadata a pure function. */\n range?: readonly [number, number];\n count: number;\n /** Metadata directory CID (one per minted breed, holding every\n * `fish_<n>_of_the_metaquarium.json` in its range). */\n metadataCid?: string;\n /** Model basename in the original aquarium's NPC set, for unminted breeds\n * whose art exists but has no token behind it. */\n model?: string;\n}\n\nexport const BREEDS: readonly BreedInfo[] = [\n { breed: 'betafish', minted: true, range: [1, 256], count: 256,\n metadataCid: 'QmZSs1ZHsW4B4ZLip8N8EqdUaXCv5ra5M5bWz1RsFSkKb4', model: 'beta-fish' },\n { breed: 'angelfish', minted: true, range: [257, 456], count: 200,\n metadataCid: 'QmaQXcFof5qb6cigk4eo59zyMUdP4CV6pVAupPZys4jquT', model: 'angel-fish' },\n { breed: 'seahorse', minted: true, range: [457, 496], count: 40,\n metadataCid: 'Qme8G5eXaAkk1dzVr5VDt8sgauVez9kmxxDNaYbSQLyggx', model: 'sea-horse' },\n { breed: 'seaturtle', minted: true, range: [497, 512], count: 16,\n metadataCid: 'QmZTntNpvwxe8Mii7s1XTpTrbaci4ZgYUXs9sEbsBodtaF', model: 'sea-turtle' },\n { breed: 'blowfish', minted: false, count: 0, model: 'blow-fish' },\n { breed: 'hackerfish', minted: false, count: 0, model: 'hacker-fish' },\n { breed: 'glowfish', minted: false, count: 0, model: 'glow-fish' },\n { breed: 'babyfish', minted: false, count: 0, model: 'baby-fish' },\n { breed: 'shark', minted: false, count: 0, model: 'shark' },\n { breed: 'crab', minted: false, count: 0, model: 'crab' },\n { breed: 'jellyfish', minted: false, count: 0, model: 'jellyfish' },\n { breed: 'dori', minted: false, count: 0, model: 'dori' },\n];\n\n/** Breed of a minted token, or null when the id is outside 1..512. */\nexport function breedOf(id: number): Breed | null {\n if (!isMintedId(id)) return null;\n for (const b of BREEDS) {\n if (b.range && id >= b.range[0] && id <= b.range[1]) return b.breed;\n }\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Traits\n// ---------------------------------------------------------------------------\n\n/**\n * The twelve trait types every fish carries, in the order the metadata emits\n * them. Documented because several are steering-relevant and would otherwise\n * be rediscovered by reading JSON: `primary color`/`secondary color` are the\n * fish's own hexes (an authored palette the tank currently overrides with its\n * seeded coats), `eyes` is a 9-bit pixel mask, `track`/`voice` name the audio\n * the original aquarium played for that fish.\n */\nexport const TRAIT_TYPES = [\n 'breed', 'track', 'primary color', 'secondary color', 'eyes', 'voice',\n 'credit score', 'astrological sign', 'mystery digit', 'attack power',\n 'birthday', 'left finned',\n] as const;\nexport type TraitType = (typeof TRAIT_TYPES)[number];\n\n/** A fish's metadata as the farm publishes it. `token_id` is deliberately\n * absent — the live API does not return it; derive from `name`. */\nexport interface FishMetadata {\n name: string;\n description?: string;\n external_url?: string;\n image?: string;\n favicon?: string;\n transparent_icon?: string;\n /** GLB. Note the filename suffix differs by metadata generation — see\n * {@link fishAssets}, which builds the current one. */\n '3d'?: string;\n animation_url?: string;\n video_512?: string;\n born_in?: string;\n background_color?: string;\n attributes?: Array<{ trait_type: TraitType | string; value: string | number }>;\n}\n\n/** Token id from a metadata `name` (\"Fish 85 of the Metaquarium\" → 85). */\nexport function idFromName(name: string): number | null {\n const m = /Fish\\s+(\\d+)\\s+of the Metaquarium/i.exec(name);\n return m ? Number(m[1]) : null;\n}\n\n// ---------------------------------------------------------------------------\n// URL construction\n// ---------------------------------------------------------------------------\n\nexport type AssetKind = '3d' | 'image' | 'transparent_icon' | 'favicon' | 'video';\n\n/** Filename suffix per asset kind, current generation. (An earlier metadata\n * batch named the model `_glb.glb`; the directories themselves hold\n * `_3d.glb`, which is what these build.) */\nconst SUFFIX: Record<AssetKind, string> = {\n '3d': '_3d.glb',\n image: '_image.jpg',\n transparent_icon: '_transparent_icon.png',\n favicon: '_favicon.png',\n video: '_video.mp4',\n};\n\n/** The asset directory CID for a token, or null when the id isn't minted. */\nexport function assetCid(id: number): string | null {\n return isMintedId(id) ? (ASSET_CIDS[id - 1] ?? null) : null;\n}\n\n/** `ipfs://<cid>/<file>` for one asset — the scheme the tank's gateway ladder\n * resolves. Pure: no fetch, no metadata lookup. */\nexport function fishAsset(id: number, kind: AssetKind = '3d'): string | null {\n const cid = assetCid(id);\n return cid ? `ipfs://${cid}/fish_${id}_of_the_metaquarium${SUFFIX[kind]}` : null;\n}\n\n/** Every asset URL for a fish, plus its breed — one call, no network. */\nexport function fishAssets(id: number): {\n id: number; breed: Breed; cid: string;\n model: string; image: string; icon: string; favicon: string; video: string;\n} | null {\n const cid = assetCid(id);\n const breed = breedOf(id);\n if (!cid || !breed) return null;\n return {\n id, breed, cid,\n model: fishAsset(id, '3d')!,\n image: fishAsset(id, 'image')!,\n icon: fishAsset(id, 'transparent_icon')!,\n favicon: fishAsset(id, 'favicon')!,\n video: fishAsset(id, 'video')!,\n };\n}\n\n/** Canonical metadata URL for a token — the range formula, no RPC. Useful for\n * traits/description; never needed just to render. */\nexport function fishMetadataUrl(id: number): string | null {\n const b = BREEDS.find((x) => x.breed === breedOf(id));\n return b?.metadataCid\n ? `ipfs://${b.metadataCid}/fish_${id}_of_the_metaquarium.json`\n : null;\n}\n\n/** All minted ids of a breed — the seam for \"a tank of seahorses\". */\nexport function idsOfBreed(breed: Breed): number[] {\n const b = BREEDS.find((x) => x.breed === breed);\n if (!b?.range) return [];\n const out: number[] = [];\n for (let i = b.range[0]; i <= b.range[1]; i++) out.push(i);\n return out;\n}\n","/** Ordered gateway candidates. dweb.link stays primary (flaky but usually\n * fast); our own node at hermosaai.com is second — flip it to the front\n * once the tunnel is verified serving /ipfs/ (it timed out on the\n * 2026-08-15 check); ipfs.io is the last resort. Each is tried in order\n * with a per-gateway timeout by the tank's template loader (MQ21). */\nexport const IPFS_GATEWAYS = [\n 'https://dweb.link/ipfs/',\n 'https://hermosaai.com/ipfs/',\n 'https://ipfs.io/ipfs/',\n];\n\nexport function resolveIpfsUrl(url: string): string {\n return resolveIpfsUrls(url)[0]!;\n}\n\n/** All gateway candidates for a URL — one entry for non-ipfs URLs. */\nexport function resolveIpfsUrls(url: string): string[] {\n if (!url.startsWith('ipfs://')) return [url];\n const stripped = url.slice('ipfs://'.length);\n return IPFS_GATEWAYS.map((g) => `${g}${stripped}`);\n}\n\nexport interface FishEntry {\n id: number;\n name: string;\n breed: string;\n ipfs3d: string;\n localGlb: string;\n}\n\nexport const FISH_CATALOG: FishEntry[] = [\n { id: 257, name: 'Fish 257', breed: 'angelfish', ipfs3d: 'ipfs://QmaHbEQAP6k2zopJHJBzyaK62zNX5yH8yASDjkaG4DY9Dp/fish_257_of_the_metaquarium_3d.glb', localGlb: '/assets/metaquarium/fish-257-angelfish.glb' },\n { id: 258, name: 'Fish 258', breed: 'angelfish', ipfs3d: 'ipfs://QmUZGF3ge3d9rzrtxrD6V4qx2gLtGeeNLuCb8fQeNyUkwJ/fish_258_of_the_metaquarium_3d.glb', localGlb: '' },\n { id: 259, name: 'Fish 259', breed: 'angelfish', ipfs3d: 'ipfs://QmfBBnNrVrkffMKoESvq3cB6nAWGpfMPjduTgw1unahvPf/fish_259_of_the_metaquarium_3d.glb', localGlb: '' },\n { id: 100, name: 'Fish 100', breed: 'betafish', ipfs3d: 'ipfs://Qmb5Uu8u154QTzoGpB6ypwVfPZ8NUsU519tQmrgE8yQrWV/fish_100_of_the_metaquarium_3d.glb', localGlb: '/assets/metaquarium/fish-100-betafish.glb' },\n { id: 457, name: 'Fish 457', breed: 'seahorse', ipfs3d: 'ipfs://QmVvEaCa6zRp8Z9YkkZVYBn2owSdwZxupEQacjfd1b2HA2/fish_457_of_the_metaquarium_3d.glb', localGlb: '' },\n { id: 497, name: 'Fish 497', breed: 'seaturtle', ipfs3d: 'ipfs://QmTBNvoUiwPw9HSUmy1qKCWPBKkBRAensgooYVqMmsviaE/fish_497_of_the_metaquarium_3d.glb', localGlb: '' },\n];\n\nexport const DEFAULT_FISH = FISH_CATALOG[0]!;\n\nimport { BREEDS, breedOf, fishAsset, TOTAL_SUPPLY } from './farm';\n\nexport interface FishMixEntry {\n id: number;\n url: string;\n count: number;\n}\n\nexport interface FishMixResult {\n entries: FishMixEntry[];\n problems: string[];\n}\n\n/**\n * Parse the `fishMix` DSL: comma-separated `id[:count]` where `id` is a\n * catalog token id (`257`) or breed alias (`betafish`; picks the breed's\n * first entry). Against the default catalog, any minted id 1–512 also\n * resolves via the in-house farm table. A caller-supplied catalog is a\n * closed world — ids not in it are problems, not IPFS fallbacks. Counts\n * are absolute; a count above 24 clamps to 24 (recorded as a problem so a\n * validator can mention it), and the tank clamps the expanded total to its\n * tier cap. Raw\n * URLs are deliberately NOT accepted — `:` and `,` stay unambiguous, the\n * validation surface stays finite, and custom URLs remain `fishUrl`'s job\n * (single-breed mode).\n *\n * Zero-dep and pure, so the Worker (via the manifest subpath), the\n * playground, and the tank all validate with the same code. Never throws:\n * bad tokens land in `problems` and good ones still parse, so one typo\n * degrades a mix instead of blanking the tank.\n */\nexport function parseFishMix(\n mix: string,\n catalog: FishEntry[] = FISH_CATALOG,\n): FishMixResult {\n const entries: FishMixEntry[] = [];\n const problems: string[] = [];\n for (const rawToken of mix.split(',')) {\n const token = rawToken.trim();\n if (token === '') continue;\n const [idRaw, countRaw, ...extra] = token.split(':');\n if (extra.length > 0) {\n problems.push(`\"${token}\": too many ':' — expected id[:count]`);\n continue;\n }\n const key = (idRaw ?? '').trim().toLowerCase();\n let fish = /^\\d+$/.test(key)\n ? catalog.find((f) => f.id === Number(key))\n : catalog.find((f) => f.breed.toLowerCase() === key);\n // Farm fallback is only for the default catalog. A custom catalog is a\n // closed world (playground offline e2e, a future pack) — leaking a minted\n // id out to IPFS would silently undo that constraint.\n if (!fish && catalog === FISH_CATALOG && /^\\d+$/.test(key)) {\n const n = Number(key);\n const url = fishAsset(n, '3d');\n const breed = breedOf(n);\n if (url && breed) fish = { id: n, name: `Fish ${n}`, breed, ipfs3d: url, localGlb: '' };\n }\n if (!fish) {\n problems.push(\n /^\\d+$/.test(key)\n ? catalog === FISH_CATALOG\n ? `\"${key}\": not a minted token id (1-${TOTAL_SUPPLY})`\n : `\"${key}\": not a catalog id`\n : `\"${key}\": not a breed (${BREEDS.filter((b) => b.minted).map((b) => b.breed).join(', ')})`,\n );\n continue;\n }\n let count = 1;\n if (countRaw !== undefined) {\n const n = Number(countRaw.trim());\n if (!Number.isInteger(n) || n < 1) {\n problems.push(`\"${token}\": count must be an integer 1-24`);\n continue;\n }\n if (n > 24) {\n // Oversized is clear intent (\"lots of these\"), so clamp instead of\n // dropping the token — but still record it, so a validator can say so.\n problems.push(`\"${token}\": count clamped to 24`);\n count = 24;\n } else {\n count = n;\n }\n }\n entries.push({ id: fish.id, url: fish.ipfs3d, count });\n }\n return { entries, problems };\n}\n\n/** Expand a parsed mix into the ordered per-slot URL list the tank spawns\n * from: `\"257:2,100:1\"` → [url257, url257, url100]. Slot order IS the\n * DSL order, so fish 0..N are stable for a given string. */\nexport function expandFishMix(entries: FishMixEntry[], cap: number): string[] {\n const urls: string[] = [];\n for (const e of entries) {\n for (let i = 0; i < e.count && urls.length < cap; i++) urls.push(e.url);\n }\n return urls;\n}\n","import type { ParamSpace, ParamValue, SaverManifest } from '@idle-screens/core';\nimport { ENVIRONMENT_NAMES } from './environments';\nimport { SWIM_STYLE_NAMES } from './swim';\n\n/**\n * Zero-dependency manifest module (type-only imports). The channel server\n * imports `@idle-screens/saver-metaquarium/manifest` to validate published\n * params against this paramSpace without pulling three.js into the Worker.\n */\nexport const METAQUARIUM_PARAMS = {\n /** Camera orbit angle, degrees. */\n cameraAzimuth: { type: 'number', default: 35, min: 0, max: 360, ease: 'smooth' },\n /** Camera height angle, degrees above the waterline plane. */\n cameraElevation: { type: 'number', default: 15, min: -5, max: 60, ease: 'smooth' },\n /** Camera distance from tank center, world units. */\n cameraDistance: { type: 'number', default: 110, min: 80, max: 400, ease: 'smooth' },\n /** Continuous orbit speed, degrees/second. Zero by default: the tank is\n * still, letting the fish movement carry the scene. Steer up for an orbit. */\n autoRotate: { type: 'number', default: 0, min: 0, max: 12, ease: 'smooth' },\n /** Visible fish. Default 1 = hero mode: one fish center-stage in its own\n * tank; the pool grows on demand when steered up (never shrinks). */\n fishCount: { type: 'number', default: 1, min: 1, max: 24, ease: 'step' },\n /** Swim time-scale multiplier. */\n swimSpeed: { type: 'number', default: 1, min: 0.2, max: 3, ease: 'smooth' },\n /** Water/atmosphere color (background + fog). */\n fogColor: { type: 'color', default: '#030009', ease: 'smooth' },\n /** GLB model URL for the fish (supports ipfs:// scheme). Package default\n * is the IPFS hero fish so npm / channel hosts work without playground\n * static assets; the playground overrides this to a local GLB. */\n fishUrl: { type: 'string', default: 'ipfs://QmaHbEQAP6k2zopJHJBzyaK62zNX5yH8yASDjkaG4DY9Dp/fish_257_of_the_metaquarium_3d.glb' },\n /** Mixed population DSL: comma-separated `id[:count]` of a minted token id\n * (1-512, default catalog) or breed name — e.g. \"257:3,100:2,seaturtle:1\".\n * A custom catalog is a closed world (no farm/IPFS fallback). Counts are\n * absolute; the expanded total clamps to the device tier's fish cap.\n * Non-empty mix OVERRIDES fishUrl and fishCount; empty string = single-\n * breed mode. Unknown ids/bad counts degrade (good tokens still parse);\n * raw URLs are not accepted here — use fishUrl for a custom single-breed GLB. */\n fishMix: { type: 'string', default: '', ease: 'step' },\n /** Fog start distance, world units. Default matches the tank's original\n * hardcoded Fog(60, 500) — steering below pulls the murk close. */\n fogNear: { type: 'number', default: 60, min: 20, max: 200, ease: 'smooth' },\n /** Fog full-opacity distance. Kept under the camera far plane (1200);\n * the tank enforces far > near + 20. */\n fogFar: { type: 'number', default: 500, min: 120, max: 1100, ease: 'smooth' },\n /** Plankton mote density, 0-1 of the device tier's mote budget. Default 0\n * = off, so the baseline look is untouched until steered. */\n moteDensity: { type: 'number', default: 0, min: 0, max: 1, ease: 'smooth' },\n /** Mote tint. */\n moteColor: { type: 'color', default: '#7fd6ff', ease: 'smooth' },\n /** Floor disc color. Default is the original hardcoded navy. */\n floorColor: { type: 'color', default: '#0a1d33', ease: 'smooth' },\n /** Where the Draco decoder lives, for the many Metaquarium models that are\n * Draco-compressed. Empty = the copy shipped beside this package (no CDN,\n * no network beyond your own host). Override when a host serves the\n * decoder from its own static path. */\n dracoPath: { type: 'string', default: '', ease: 'step' },\n /** The ROOM: a named place rather than thirty numbers. `void` is exactly\n * the pre-environment scene, so the default changes nothing. Each other\n * value adds a water ceiling, a terrain silhouette and light shafts,\n * tier-budgeted. Palette params (fog, floor colour, motes) stay yours —\n * an environment never overrides them. */\n environment: { type: 'enum', default: 'void', options: [...ENVIRONMENT_NAMES], ease: 'step' },\n /** Override the environment's terrain. `auto` follows the environment. */\n floorKind: { type: 'enum', default: 'auto', options: ['auto', 'flat', 'dunes', 'ridges', 'basin'], ease: 'step' },\n /** Water-ceiling height. -1 = follow the environment; fish swim to y=72.\n * STEP, not smooth: -1 is a sentinel, so a ramp from -1 to a real height\n * passes through negatives that read as \"auto\" — the ceiling would jump\n * rather than glide. A value you cannot interpolate through must not\n * advertise that it can. */\n waterY: { type: 'number', default: -1, min: -1, max: 220, ease: 'step' },\n /** Light-shaft strength. -1 = follow the environment, 0 = off. Step for the\n * same sentinel reason: ramping -1 → 0 would read as FULL strength until it\n * snapped off. */\n rayStrength: { type: 'number', default: -1, min: -1, max: 1, ease: 'step' },\n /** How the fish move. `loop` is exactly the pre-style behaviour, so the\n * default changes nothing. A small named set on purpose: a silhouette of\n * movement you can name is one you can choose from. */\n swimStyle: { type: 'enum', default: 'loop', options: [...SWIM_STYLE_NAMES], ease: 'step' },\n /** Per-fish spread: 0 a uniform shoal, 1 every fish visibly its own animal\n * (±40% speed, ±25% size, own phase). The uniqueness dial — one number\n * instead of per-fish values nobody wants to author. */\n swimVariance: { type: 'number', default: 0, min: 0, max: 1, ease: 'smooth' },\n /** Procedural body yaw for models that carry NO animation clip — most of\n * the breed library. Distance-driven like the tail beat, so it speeds up\n * with the fish and stays frame-addressable. Clipped models ignore it:\n * their own clip is the better animation.\n *\n * Defaults to 0, off, even though a rigidly gliding fish is the worse\n * look. Every param this saver has added defaults to the previous\n * behaviour, and a scene already on someone's wall should not start moving\n * differently because a dependency was bumped. 0.3–0.4 is the recommended\n * value for a clip-less cast — the studio swim variants all set it. */\n bodyWiggle: { type: 'number', default: 0, min: 0, max: 1, ease: 'smooth' },\n} satisfies ParamSpace;\n\n/** The original's Miami-Vice body palette (scss-variables.ts) — seeded fish\n * coats draw from this. Zero-dep so the server/manifest lane can surface it. */\nexport const MIAMI_VICE_COLORS = [\n '#ff6ad5', // Hot Pink\n '#c774e8', // Light Purple\n '#ad8cff', // Lavender\n '#8795e8', // Periwinkle Blue\n '#94d0ff', // Light Sky Blue\n '#4fb4f4', // Azure\n '#00a6fb', // Bright Blue\n '#0085a1', // Cerulean\n '#0b3d91', // Yale Blue\n '#1c1c1c', // Off Black\n];\n\n/** The original's emissive/glow palette (`GLOW-*` parts, bloom layer). */\nexport const BLOOM_COLORS = [\n '#FF69B4', // Hot Pink\n '#FFFF00', // Yellow\n '#00FFFF', // Cyan\n '#00FF00', // Lime\n '#FF4500', // OrangeRed\n '#7FFF00', // Chartreuse\n];\n\nexport const metaquariumManifest: SaverManifest = {\n id: 'metaquarium',\n label: 'Metaquarium',\n description:\n 'A living fish tank: skinned GLB fish swim seeded Catmull-Rom paths through a dark, fogged aquarium.',\n minBackend: 'webgl2',\n costTier: 'medium',\n motionIntensity: 'calm',\n reducedMotionFallback: 'static',\n timeModel: 'closed-form',\n palette: ['#030009', '#ff6ad5', '#94d0ff', '#00a6fb', '#00FFFF'],\n paramSpace: METAQUARIUM_PARAMS,\n attribution: {\n source: 'Metaquarium (metaquarium.xyz) — original artwork and fish models by Shawn Partridge',\n license: 'MIT port of first-party artwork',\n url: 'https://metaquarium.xyz',\n },\n a11y: {\n flashSafe: true,\n notes: 'Slow ambient swim in a dark fogged tank; no strobing.',\n },\n};\n\n/** Options for {@link createMetaquarium}-style variants: a distinct id/label and\n * overridden param defaults (e.g. a farm-connected tank). */\nexport interface MetaquariumOptions {\n /** Override the fish catalog fishMix ids resolve against — the seam the\n * playground uses to point ids at bundled local GLBs (offline e2e), and\n * the future farm/pack extension point. */\n catalog?: import('./ipfs').FishEntry[];\n id?: string;\n label?: string;\n params?: Partial<Record<keyof typeof METAQUARIUM_PARAMS, ParamValue>>;\n}\n\n/** Clone a paramSpace with default overrides; unknown keys are ignored. */\nexport function withDefaults(\n space: ParamSpace,\n overrides?: Record<string, ParamValue>,\n): ParamSpace {\n if (!overrides) return space;\n const out: ParamSpace = {};\n for (const [k, def] of Object.entries(space)) {\n const o = overrides[k];\n out[k] = o === undefined ? def : { ...def, default: o };\n }\n return out;\n}\n\n/** Clone the metaquarium paramSpace with per-variant default overrides. */\nexport function paramSpaceWith(overrides: MetaquariumOptions['params']): ParamSpace {\n return withDefaults(METAQUARIUM_PARAMS, overrides as Record<string, ParamValue> | undefined);\n}\n\n/**\n * Defensive read of a tracked number param. The server validates nothing on\n * the classic steering lane (any path, any value reaches the saver), and MCP\n * harnesses with untyped `value` params stringify numbers — so \"22\" arrives\n * where 22 was meant. Finite numbers pass; finite numeric strings coerce;\n * anything else falls back to the def's default; the result clamps to the\n * def's min/max. Zero-dep on purpose: hosts can reuse it to sanitize.\n */\nexport function coerceNum(\n def: { default?: ParamValue; min?: number; max?: number } | undefined,\n v: ParamValue | undefined,\n): number {\n let n: number | null = null;\n if (typeof v === 'number' && Number.isFinite(v)) n = v;\n else if (typeof v === 'string' && v.trim() !== '') {\n const parsed = Number(v);\n if (Number.isFinite(parsed)) n = parsed;\n }\n if (n === null) {\n const d = def?.default;\n if (typeof d === 'number' && Number.isFinite(d)) n = d;\n else if (typeof d === 'string' && d.trim() !== '' && Number.isFinite(Number(d))) n = Number(d);\n else n = 0;\n }\n if (def?.min !== undefined && n < def.min) n = def.min;\n if (def?.max !== undefined && n > def.max) n = def.max;\n return n;\n}\n\nexport * from './farm';\nexport { parseFishMix, expandFishMix, type FishMixEntry, type FishMixResult, type FishEntry, FISH_CATALOG } from './ipfs';\n\nexport * from './environments';\nexport * from './swim';\n"],"mappings":"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,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAAkD;AAAA,EAAkD;AAAA,EAAkD;AAAA,EACtJ;AAAA,EAA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","names":[]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { SaverPlugin, ControlTrack } from '@idle-screens/core';
|
|
2
|
-
import { M as MetaquariumOptions } from './manifest
|
|
3
|
-
export { B as BLOOM_COLORS, D as DEFAULT_FISH, F as FISH_CATALOG, a as FishEntry, b as METAQUARIUM_PARAMS, c as MIAMI_VICE_COLORS, m as metaquariumManifest, p as paramSpaceWith, r as resolveIpfsUrl, w as withDefaults } from './manifest
|
|
2
|
+
import { M as MetaquariumOptions } from './manifest-CapCN3BJ.js';
|
|
3
|
+
export { B as BLOOM_COLORS, D as DEFAULT_FISH, F as FISH_CATALOG, a as FishEntry, b as METAQUARIUM_PARAMS, c as MIAMI_VICE_COLORS, m as metaquariumManifest, p as paramSpaceWith, r as resolveIpfsUrl, w as withDefaults } from './manifest-CapCN3BJ.js';
|
|
4
4
|
import { CapabilityTier } from '@idle-screens/capabilities';
|
|
5
5
|
|
|
6
6
|
declare function createMetaquarium(opts?: MetaquariumOptions): SaverPlugin;
|
package/dist/index.js
CHANGED
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
paramSpaceWith,
|
|
12
12
|
resolveIpfsUrl,
|
|
13
13
|
withDefaults
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-52CJC2KV.js";
|
|
15
15
|
|
|
16
16
|
// src/metaquarium.ts
|
|
17
17
|
import { detectCapabilities, computeTier } from "@idle-screens/capabilities";
|
|
@@ -26,7 +26,7 @@ function createMetaquarium(opts = {}) {
|
|
|
26
26
|
},
|
|
27
27
|
async mount(ctx) {
|
|
28
28
|
const [{ mountTank }, caps] = await Promise.all([
|
|
29
|
-
import("./tank-
|
|
29
|
+
import("./tank-HV27OOXH.js"),
|
|
30
30
|
detectCapabilities()
|
|
31
31
|
]);
|
|
32
32
|
return mountTank(ctx, space, computeTier(caps), opts.catalog);
|
|
@@ -25,7 +25,9 @@ interface FishMixResult {
|
|
|
25
25
|
* first entry). Against the default catalog, any minted id 1–512 also
|
|
26
26
|
* resolves via the in-house farm table. A caller-supplied catalog is a
|
|
27
27
|
* closed world — ids not in it are problems, not IPFS fallbacks. Counts
|
|
28
|
-
* are absolute;
|
|
28
|
+
* are absolute; a count above 24 clamps to 24 (recorded as a problem so a
|
|
29
|
+
* validator can mention it), and the tank clamps the expanded total to its
|
|
30
|
+
* tier cap. Raw
|
|
29
31
|
* URLs are deliberately NOT accepted — `:` and `,` stay unambiguous, the
|
|
30
32
|
* validation surface stays finite, and custom URLs remain `fishUrl`'s job
|
|
31
33
|
* (single-breed mode).
|
package/dist/manifest.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { A as ASSET_CIDS, d as AssetKind, B as BLOOM_COLORS, e as BREEDS, f as Breed, g as BreedInfo, C as CONTRACT, h as DepthBand, E as ENVIRONMENTS, i as ENVIRONMENT_NAMES, j as EnvironmentName, k as EnvironmentPreset, F as FISH_CATALOG, l as FISH_LENGTH, n as FLOOR_KINDS, a as FishEntry, o as FishMetadata, q as FishMixEntry, s as FishMixResult, t as FloorKind, L as LAYER_COST, b as METAQUARIUM_PARAMS, c as MIAMI_VICE_COLORS, M as MetaquariumOptions, R as RAY_COUNT, u as Rays, S as SWIM_STYLES, v as SWIM_STYLE_NAMES, x as SwimStyle, y as SwimStyleSpec, T as TOTAL_SUPPLY, z as TRAIT_TYPES, G as TraitType, W as WaterCeiling, H as affordableLayers, I as assetCid, J as bandRange, K as breedOf, N as coerceNum, O as environmentOf, P as expandFishMix, Q as fishAsset, U as fishAssets, V as fishHash, X as fishMetadataUrl, Y as fishVariation, Z as formationExtent, _ as formationSlot, $ as idFromName, a0 as idsOfBreed, a1 as isMintedId, m as metaquariumManifest, p as paramSpaceWith, a2 as parseFishMix, a3 as swimStyleOf, w as withDefaults } from './manifest
|
|
1
|
+
export { A as ASSET_CIDS, d as AssetKind, B as BLOOM_COLORS, e as BREEDS, f as Breed, g as BreedInfo, C as CONTRACT, h as DepthBand, E as ENVIRONMENTS, i as ENVIRONMENT_NAMES, j as EnvironmentName, k as EnvironmentPreset, F as FISH_CATALOG, l as FISH_LENGTH, n as FLOOR_KINDS, a as FishEntry, o as FishMetadata, q as FishMixEntry, s as FishMixResult, t as FloorKind, L as LAYER_COST, b as METAQUARIUM_PARAMS, c as MIAMI_VICE_COLORS, M as MetaquariumOptions, R as RAY_COUNT, u as Rays, S as SWIM_STYLES, v as SWIM_STYLE_NAMES, x as SwimStyle, y as SwimStyleSpec, T as TOTAL_SUPPLY, z as TRAIT_TYPES, G as TraitType, W as WaterCeiling, H as affordableLayers, I as assetCid, J as bandRange, K as breedOf, N as coerceNum, O as environmentOf, P as expandFishMix, Q as fishAsset, U as fishAssets, V as fishHash, X as fishMetadataUrl, Y as fishVariation, Z as formationExtent, _ as formationSlot, $ as idFromName, a0 as idsOfBreed, a1 as isMintedId, m as metaquariumManifest, p as paramSpaceWith, a2 as parseFishMix, a3 as swimStyleOf, w as withDefaults } from './manifest-CapCN3BJ.js';
|
|
2
2
|
import '@idle-screens/core';
|
package/dist/manifest.js
CHANGED
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
resolveIpfsUrls,
|
|
26
26
|
swimStyleOf,
|
|
27
27
|
withDefaults
|
|
28
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-52CJC2KV.js";
|
|
29
29
|
|
|
30
30
|
// src/tank.ts
|
|
31
31
|
import {
|
|
@@ -360,6 +360,9 @@ var TankInstance = class {
|
|
|
360
360
|
wantUrls = [];
|
|
361
361
|
wantKey = "";
|
|
362
362
|
wantInputKey = "";
|
|
363
|
+
/** Last fishMix string whose parse problems were warned — once per mix,
|
|
364
|
+
* not per reconcile, or steering any other param would repeat it. */
|
|
365
|
+
warnedMix = "";
|
|
363
366
|
mixMode = false;
|
|
364
367
|
disposed = false;
|
|
365
368
|
w;
|
|
@@ -595,7 +598,13 @@ var TankInstance = class {
|
|
|
595
598
|
let want = [];
|
|
596
599
|
this.mixMode = false;
|
|
597
600
|
if (mixStr !== "") {
|
|
598
|
-
|
|
601
|
+
const parsed = parseFishMix(mixStr, this.catalog);
|
|
602
|
+
if (parsed.problems.length > 0 && mixStr !== this.warnedMix) {
|
|
603
|
+
this.warnedMix = mixStr;
|
|
604
|
+
const fallback = parsed.entries.length === 0 ? " \u2014 falling back to fishUrl/fishCount" : "";
|
|
605
|
+
console.warn(`[metaquarium] fishMix "${mixStr}": ${parsed.problems.join("; ")}${fallback}`);
|
|
606
|
+
}
|
|
607
|
+
want = expandFishMix(parsed.entries, cap);
|
|
599
608
|
this.mixMode = want.length > 0;
|
|
600
609
|
}
|
|
601
610
|
if (!this.mixMode) {
|
|
@@ -1052,4 +1061,4 @@ function mountTank(ctx, space = METAQUARIUM_PARAMS, tier = "standard", catalog)
|
|
|
1052
1061
|
export {
|
|
1053
1062
|
mountTank
|
|
1054
1063
|
};
|
|
1055
|
-
//# sourceMappingURL=tank-
|
|
1064
|
+
//# sourceMappingURL=tank-HV27OOXH.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/tank.ts","../src/tank-draco.ts","../src/materials.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 bandRange, FISH_LENGTH, fishVariation, formationExtent, formationSlot, swimStyleOf,\n} from './swim';\nimport { expandFishMix, FISH_CATALOG, parseFishMix, resolveIpfsUrls, type FishEntry } from './ipfs';\nimport { coerceNum, METAQUARIUM_PARAMS, withDefaults } from './manifest';\nimport {\n applyNpcMaterials,\n eyeNoseSign,\n forceOpaque,\n MIAMI_VICE_COLORS,\n} from './materials';\nimport {\n compileSwimPlan,\n distanceAt,\n swimPoseAtDistance,\n type SwimPlan,\n type TankBounds,\n} from './plan';\nimport {\n effectivePixelRatio,\n probeSoftwareGL,\n qualityFor,\n type TankQuality,\n} from './quality';\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/** Tanks currently able to decode, per decoder path. The decoder outlives any\n * single tank (templates are cached page-wide) but must not outlive the LAST\n * one, or its worker leaks for the life of the page. */\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\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 h = Math.sin(x * 0.011 + a) * 26 + Math.cos(z * 0.009 + b) * 20;\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 h = d * d * 150 - 60 + Math.sin(x * 0.008 + a) * 8;\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 geo.userData.mqOwned = true;\n const mat = new MeshBasicMaterial({ color: new Color(color) });\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, 1.0, vY);\n float breathe = 0.75 + 0.25 * sin(uTime * 0.4);\n gl_FragColor = vec4(uColor, fade * uStrength * 0.16 * breathe);\n }`,\n });\n mat.userData.mqOwned = true;\n const geo = new CylinderGeometry(6, 92, Math.abs(y) * 0.85, 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 = 60 + rng.next() * 190;\n m.position.set(Math.cos(a) * r, y * 0.45, 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 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 const tex = (m as Partial<MeshBasicMaterial>).map;\n if (tex) 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// ---------------------------------------------------------------------------\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 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 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 startT = 0;\n private t = 0;\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 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 readonly carrierPlan: SwimPlan;\n /** Decoder paths this tank retained, released on dispose. */\n private readonly dracoPaths = new Set<string>();\n\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);\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 gl_PointSize = clamp(180.0 / max(1.0, -mv.z), 1.0, 4.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.5);\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 }\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 ?? this.space.floorColor?.default ?? '#0a1d33');\n const height = terrainHeightFn(kind, this.ctxSaver.rng.fork(0x7e88));\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 // -1 = follow the environment; 0 is a real author choice for \"off\".\n const strength = rayStrength >= 0 ? rayStrength : preset.rays.strength;\n if (strength > 0) {\n const { group: rg, material } = buildRays(\n can.rayCount, preset.rays.color, preset.rays.y, strength, this.ctxSaver.rng.fork(0x8a1),\n );\n group.add(rg);\n this.rayMat = material;\n this.presetRayStrength = preset.rays.strength;\n }\n }\n this.room = group;\n this.scene.add(group);\n this.applyRoomParams(waterY, rayStrength);\n this.ctxSaver.host.dataset.mqEnv = preset.name;\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 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 want = expandFishMix(parsed.entries, cap);\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.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.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 if (draco) {\n // Retain ONCE per path per tank — the release side is one call per\n // unique path at dispose, so retaining per model would leak the\n // refcount and the worker would never be freed.\n const key = dracoDecoderPath(dracoPath);\n if (!this.dracoPaths.has(key)) {\n this.dracoPaths.add(key);\n retainDraco(dracoPath);\n }\n loader.setDRACOLoader(dracoLoader(dracoPath));\n }\n const gltf = 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);\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 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 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 az = MathUtils.degToRad(\n this.num('cameraAzimuth') + this.num('autoRotate') * tSec,\n );\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.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.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.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 style = swimStyleOf(this.str('swimStyle'));\n const variance = this.num('swimVariance');\n const wiggle = this.num('bodyWiggle');\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 extent = style.formation ? formationExtent(visible, variance) : null;\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\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 const anchor = style.travel < 1 ? varn.anchor * f.plan.totalLength : 0;\n const d = anchor + effort * style.travel;\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(f.index, visible, variance);\n // The carrier moves at the STYLE's speed, deliberately without the\n // per-fish multiplier: a formation whose members each chose their own\n // pace is not a formation. Per-fish speed still varies the tail beat\n // through `effort`, which is where it reads anyway.\n const lead = this.speedTracked\n ? distanceAt(this.carrierPlan, warpSec, style.speedMul)\n : distanceAt(this.carrierPlan, tSec, speed * style.speedMul);\n beat = lead;\n // ONE arc sample for the whole shoal, offset rigidly. Sampling each\n // row at `lead - back` instead put the rows at different points on a\n // curving spline, so they converged on the inside of every turn: a\n // lattice with a body length of clearance measured 27% of fish-frames\n // with a neighbour inside one, exactly as if there were no lattice.\n const c = swimPoseAtDistance(this.carrierPlan, lead);\n const ext = extent ?? formationExtent(visible, variance);\n // Keep the shoal in the tank by moving its CENTRE, never by pulling\n // individual fish toward the middle — that squashing was the other\n // half of the pile.\n const maxR = Math.max(0, BOUNDS.radius - Math.hypot(ext.side, ext.back));\n const cr = Math.hypot(c.x, c.z);\n const cs = cr > maxR && cr > 0 ? maxR / cr : 1;\n const hl = Math.hypot(c.fx, c.fz) || 1;\n const fwdX = c.fx / hl, fwdZ = c.fz / hl;\n // Right-hand normal in the ground plane.\n const rx = fwdZ, rz = -fwdX;\n const cy = Math.min(\n BOUNDS.yMax - ext.up,\n Math.max(BOUNDS.yMin + ext.up, c.y),\n );\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 ...c,\n x: c.x * cs + rx * slot.side - fwdX * slot.back,\n y: cy + slot.up,\n z: c.z * cs + rz * slot.side - fwdZ * slot.back,\n fx: fwdX * cy2 - fwdZ * sy2,\n fz: fwdX * sy2 + fwdZ * cy2,\n };\n } else {\n pose = swimPoseAtDistance(f.plan, d);\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(style.band);\n if (band) {\n const lo = BOUNDS.yMin + (BOUNDS.yMax - BOUNDS.yMin) * band.lo;\n const hi = BOUNDS.yMin + (BOUNDS.yMax - BOUNDS.yMin) * band.hi;\n y = Math.min(hi, Math.max(lo, y));\n }\n // Then the seabed, which outranks the band: `dunes` and `ridges` rise to\n // +46 while the floor band tops out at 23, so a bottom-hugger over a\n // hill was inside it. Half a body length of clearance, and never above\n // the tank's own ceiling.\n if (this.floorHeightAt) {\n const clear = this.floorHeightAt(pose.x, pose.z) + FISH_LENGTH * 0.5;\n if (y < clear) y = Math.min(BOUNDS.yMax, clear);\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 const fy = band ? 0 : pose.fy;\n f.group.position.set(pose.x, y, pose.z);\n f.group.lookAt(pose.x + pose.fx, y + fy, pose.z + 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 f.body.rotation.y = f.baseYaw + Math.sin(beat * 0.06 + varn.phase) * 0.55 * wiggle;\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 }\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.startT = 0;\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 }\n\n private loop(now: number): void {\n this.frameId = requestAnimationFrame((n) => this.loop(n));\n if (this.startT === 0) this.startT = now;\n this.governFrame(now);\n this.t = now - this.startT;\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 if (this.paused) this.renderStill();\n }\n\n renderFrame(t: number, _seed: number): void {\n this.t = t;\n this.setState(t);\n this.renderScene();\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 p of this.dracoPaths) releaseDraco(p);\n this.dracoPaths.clear();\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 Color,\n MeshBasicMaterial,\n type Material,\n type Mesh,\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\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/**\n * Seeded palette coat: body → unlit palette color, GLOW-* → colored emissive\n * MeshBasicMaterial, EYES-* → untouched, textured materials → untouched\n * (their atlas IS the look). All unlit — no scene lights needed.\n */\nexport function applyNpcMaterials(root: Object3D, rng: Rng): void {\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)) return m;\n if ((m as Partial<MeshBasicMaterial>).map) return m;\n if (isGlow(m)) {\n const glow = new MeshBasicMaterial({ color: 0x000000 });\n glow.color.set(rng.pick(BLOOM_COLORS));\n glow.name = m.name;\n // Ours to dispose at fish teardown. Eyes/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 body = new MeshBasicMaterial({ color: new Color(rng.pick(BODY_COATS)) });\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"],"mappings":"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","names":["Color","MeshBasicMaterial","Color","MeshBasicMaterial","key"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@idle-screens/saver-metaquarium",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.1",
|
|
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",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/tank.ts","../src/tank-draco.ts","../src/materials.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 bandRange, FISH_LENGTH, fishVariation, formationExtent, formationSlot, swimStyleOf,\n} from './swim';\nimport { expandFishMix, FISH_CATALOG, parseFishMix, resolveIpfsUrls, type FishEntry } from './ipfs';\nimport { coerceNum, METAQUARIUM_PARAMS, withDefaults } from './manifest';\nimport {\n applyNpcMaterials,\n eyeNoseSign,\n forceOpaque,\n MIAMI_VICE_COLORS,\n} from './materials';\nimport {\n compileSwimPlan,\n distanceAt,\n swimPoseAtDistance,\n type SwimPlan,\n type TankBounds,\n} from './plan';\nimport {\n effectivePixelRatio,\n probeSoftwareGL,\n qualityFor,\n type TankQuality,\n} from './quality';\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/** Tanks currently able to decode, per decoder path. The decoder outlives any\n * single tank (templates are cached page-wide) but must not outlive the LAST\n * one, or its worker leaks for the life of the page. */\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\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 h = Math.sin(x * 0.011 + a) * 26 + Math.cos(z * 0.009 + b) * 20;\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 h = d * d * 150 - 60 + Math.sin(x * 0.008 + a) * 8;\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 geo.userData.mqOwned = true;\n const mat = new MeshBasicMaterial({ color: new Color(color) });\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, 1.0, vY);\n float breathe = 0.75 + 0.25 * sin(uTime * 0.4);\n gl_FragColor = vec4(uColor, fade * uStrength * 0.16 * breathe);\n }`,\n });\n mat.userData.mqOwned = true;\n const geo = new CylinderGeometry(6, 92, Math.abs(y) * 0.85, 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 = 60 + rng.next() * 190;\n m.position.set(Math.cos(a) * r, y * 0.45, 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 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 const tex = (m as Partial<MeshBasicMaterial>).map;\n if (tex) 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// ---------------------------------------------------------------------------\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 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 private wantKey = '';\n private wantInputKey = '';\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 startT = 0;\n private t = 0;\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 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 readonly carrierPlan: SwimPlan;\n /** Decoder paths this tank retained, released on dispose. */\n private readonly dracoPaths = new Set<string>();\n\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);\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 gl_PointSize = clamp(180.0 / max(1.0, -mv.z), 1.0, 4.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.5);\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 }\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 ?? this.space.floorColor?.default ?? '#0a1d33');\n const height = terrainHeightFn(kind, this.ctxSaver.rng.fork(0x7e88));\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 // -1 = follow the environment; 0 is a real author choice for \"off\".\n const strength = rayStrength >= 0 ? rayStrength : preset.rays.strength;\n if (strength > 0) {\n const { group: rg, material } = buildRays(\n can.rayCount, preset.rays.color, preset.rays.y, strength, this.ctxSaver.rng.fork(0x8a1),\n );\n group.add(rg);\n this.rayMat = material;\n this.presetRayStrength = preset.rays.strength;\n }\n }\n this.room = group;\n this.scene.add(group);\n this.applyRoomParams(waterY, rayStrength);\n this.ctxSaver.host.dataset.mqEnv = preset.name;\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 this.mixMode = false;\n if (mixStr !== '') {\n want = expandFishMix(parseFishMix(mixStr, this.catalog).entries, cap);\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.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.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 if (draco) {\n // Retain ONCE per path per tank — the release side is one call per\n // unique path at dispose, so retaining per model would leak the\n // refcount and the worker would never be freed.\n const key = dracoDecoderPath(dracoPath);\n if (!this.dracoPaths.has(key)) {\n this.dracoPaths.add(key);\n retainDraco(dracoPath);\n }\n loader.setDRACOLoader(dracoLoader(dracoPath));\n }\n const gltf = 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);\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 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 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 az = MathUtils.degToRad(\n this.num('cameraAzimuth') + this.num('autoRotate') * tSec,\n );\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.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.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.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 style = swimStyleOf(this.str('swimStyle'));\n const variance = this.num('swimVariance');\n const wiggle = this.num('bodyWiggle');\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 extent = style.formation ? formationExtent(visible, variance) : null;\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\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 const anchor = style.travel < 1 ? varn.anchor * f.plan.totalLength : 0;\n const d = anchor + effort * style.travel;\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(f.index, visible, variance);\n // The carrier moves at the STYLE's speed, deliberately without the\n // per-fish multiplier: a formation whose members each chose their own\n // pace is not a formation. Per-fish speed still varies the tail beat\n // through `effort`, which is where it reads anyway.\n const lead = this.speedTracked\n ? distanceAt(this.carrierPlan, warpSec, style.speedMul)\n : distanceAt(this.carrierPlan, tSec, speed * style.speedMul);\n beat = lead;\n // ONE arc sample for the whole shoal, offset rigidly. Sampling each\n // row at `lead - back` instead put the rows at different points on a\n // curving spline, so they converged on the inside of every turn: a\n // lattice with a body length of clearance measured 27% of fish-frames\n // with a neighbour inside one, exactly as if there were no lattice.\n const c = swimPoseAtDistance(this.carrierPlan, lead);\n const ext = extent ?? formationExtent(visible, variance);\n // Keep the shoal in the tank by moving its CENTRE, never by pulling\n // individual fish toward the middle — that squashing was the other\n // half of the pile.\n const maxR = Math.max(0, BOUNDS.radius - Math.hypot(ext.side, ext.back));\n const cr = Math.hypot(c.x, c.z);\n const cs = cr > maxR && cr > 0 ? maxR / cr : 1;\n const hl = Math.hypot(c.fx, c.fz) || 1;\n const fwdX = c.fx / hl, fwdZ = c.fz / hl;\n // Right-hand normal in the ground plane.\n const rx = fwdZ, rz = -fwdX;\n const cy = Math.min(\n BOUNDS.yMax - ext.up,\n Math.max(BOUNDS.yMin + ext.up, c.y),\n );\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 ...c,\n x: c.x * cs + rx * slot.side - fwdX * slot.back,\n y: cy + slot.up,\n z: c.z * cs + rz * slot.side - fwdZ * slot.back,\n fx: fwdX * cy2 - fwdZ * sy2,\n fz: fwdX * sy2 + fwdZ * cy2,\n };\n } else {\n pose = swimPoseAtDistance(f.plan, d);\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(style.band);\n if (band) {\n const lo = BOUNDS.yMin + (BOUNDS.yMax - BOUNDS.yMin) * band.lo;\n const hi = BOUNDS.yMin + (BOUNDS.yMax - BOUNDS.yMin) * band.hi;\n y = Math.min(hi, Math.max(lo, y));\n }\n // Then the seabed, which outranks the band: `dunes` and `ridges` rise to\n // +46 while the floor band tops out at 23, so a bottom-hugger over a\n // hill was inside it. Half a body length of clearance, and never above\n // the tank's own ceiling.\n if (this.floorHeightAt) {\n const clear = this.floorHeightAt(pose.x, pose.z) + FISH_LENGTH * 0.5;\n if (y < clear) y = Math.min(BOUNDS.yMax, clear);\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 const fy = band ? 0 : pose.fy;\n f.group.position.set(pose.x, y, pose.z);\n f.group.lookAt(pose.x + pose.fx, y + fy, pose.z + 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 f.body.rotation.y = f.baseYaw + Math.sin(beat * 0.06 + varn.phase) * 0.55 * wiggle;\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 }\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.startT = 0;\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 }\n\n private loop(now: number): void {\n this.frameId = requestAnimationFrame((n) => this.loop(n));\n if (this.startT === 0) this.startT = now;\n this.governFrame(now);\n this.t = now - this.startT;\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 if (this.paused) this.renderStill();\n }\n\n renderFrame(t: number, _seed: number): void {\n this.t = t;\n this.setState(t);\n this.renderScene();\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 p of this.dracoPaths) releaseDraco(p);\n this.dracoPaths.clear();\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 Color,\n MeshBasicMaterial,\n type Material,\n type Mesh,\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\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/**\n * Seeded palette coat: body → unlit palette color, GLOW-* → colored emissive\n * MeshBasicMaterial, EYES-* → untouched, textured materials → untouched\n * (their atlas IS the look). All unlit — no scene lights needed.\n */\nexport function applyNpcMaterials(root: Object3D, rng: Rng): void {\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)) return m;\n if ((m as Partial<MeshBasicMaterial>).map) return m;\n if (isGlow(m)) {\n const glow = new MeshBasicMaterial({ color: 0x000000 });\n glow.color.set(rng.pick(BLOOM_COLORS));\n glow.name = m.name;\n // Ours to dispose at fish teardown. Eyes/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 body = new MeshBasicMaterial({ color: new Color(rng.pick(BODY_COATS)) });\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"],"mappings":"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","names":["Color","MeshBasicMaterial","Color","MeshBasicMaterial","key"]}
|