@idle-screens/saver-metaquarium 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -57,7 +57,13 @@ frame loop (rAF or renderFrame(t)):
57
57
  | fishCount | number | 1 | Visible fish (step) |
58
58
  | swimSpeed | number | 1 | Swim time-scale multiplier |
59
59
  | fogColor | color | #030009 | Atmosphere / background |
60
- | fishUrl | string | ipfs://…/fish_257_….glb | GLB model URL (playground can override to a local asset) |
60
+ | fishUrl | string | ipfs://…/fish_257_….glb | GLB model URL, single-breed mode (playground can override to a local asset) |
61
+ | fishMix | string | "" | Mixed population DSL: `id[:count]` comma-separated, catalog ids or breed aliases (`"257:2,100:1"`). Non-empty overrides fishUrl + fishCount; counts absolute, tier-capped |
62
+ | fogNear | number | 60 | Fog start distance (the old hardcoded Fog near) |
63
+ | fogFar | number | 500 | Fog full-opacity distance; tank enforces > near + 20 |
64
+ | moteDensity | number | 0 | Plankton motes, 0–1 of the tier budget (400/250/120). 0 = off |
65
+ | moteColor | color | #7fd6ff | Mote tint |
66
+ | floorColor | color | #0a1d33 | Floor disc color (the old hardcoded navy) |
61
67
 
62
68
  ## File map
63
69
 
@@ -1,20 +1,3 @@
1
- // src/ipfs.ts
2
- var IPFS_GATEWAY = "https://dweb.link/ipfs/";
3
- function resolveIpfsUrl(url) {
4
- if (!url.startsWith("ipfs://")) return url;
5
- const stripped = url.slice("ipfs://".length);
6
- return `${IPFS_GATEWAY}${stripped}`;
7
- }
8
- var FISH_CATALOG = [
9
- { id: 257, name: "Fish 257", breed: "angelfish", ipfs3d: "ipfs://QmaHbEQAP6k2zopJHJBzyaK62zNX5yH8yASDjkaG4DY9Dp/fish_257_of_the_metaquarium_3d.glb", localGlb: "/assets/metaquarium/fish-257-angelfish.glb" },
10
- { id: 258, name: "Fish 258", breed: "angelfish", ipfs3d: "ipfs://QmUZGF3ge3d9rzrtxrD6V4qx2gLtGeeNLuCb8fQeNyUkwJ/fish_258_of_the_metaquarium_3d.glb", localGlb: "" },
11
- { id: 259, name: "Fish 259", breed: "angelfish", ipfs3d: "ipfs://QmfBBnNrVrkffMKoESvq3cB6nAWGpfMPjduTgw1unahvPf/fish_259_of_the_metaquarium_3d.glb", localGlb: "" },
12
- { id: 100, name: "Fish 100", breed: "betafish", ipfs3d: "ipfs://Qmb5Uu8u154QTzoGpB6ypwVfPZ8NUsU519tQmrgE8yQrWV/fish_100_of_the_metaquarium_3d.glb", localGlb: "/assets/metaquarium/fish-100-betafish.glb" },
13
- { id: 457, name: "Fish 457", breed: "seahorse", ipfs3d: "ipfs://QmVvEaCa6zRp8Z9YkkZVYBn2owSdwZxupEQacjfd1b2HA2/fish_457_of_the_metaquarium_3d.glb", localGlb: "" },
14
- { id: 497, name: "Fish 497", breed: "seaturtle", ipfs3d: "ipfs://QmTBNvoUiwPw9HSUmy1qKCWPBKkBRAensgooYVqMmsviaE/fish_497_of_the_metaquarium_3d.glb", localGlb: "" }
15
- ];
16
- var DEFAULT_FISH = FISH_CATALOG[0];
17
-
18
1
  // src/plan.ts
19
2
  var ARC_SAMPLES = 1024;
20
3
  function catmullRom(p0, p1, p2, p3, u) {
@@ -113,11 +96,11 @@ function swimPoseAtDistance(plan, dist) {
113
96
  function qualityFor(tier) {
114
97
  switch (tier) {
115
98
  case "high":
116
- return { maxPixelRatio: 1.25, antialias: true, fishCap: 24, pixelBudget: 18e5 };
99
+ return { maxPixelRatio: 1.25, antialias: true, fishCap: 24, pixelBudget: 18e5, moteCap: 400 };
117
100
  case "standard":
118
- return { maxPixelRatio: 1, antialias: true, fishCap: 16, pixelBudget: 12e5 };
101
+ return { maxPixelRatio: 1, antialias: true, fishCap: 16, pixelBudget: 12e5, moteCap: 250 };
119
102
  default:
120
- return { maxPixelRatio: 1, antialias: false, fishCap: 8, pixelBudget: 9e5 };
103
+ return { maxPixelRatio: 1, antialias: false, fishCap: 8, pixelBudget: 9e5, moteCap: 120 };
121
104
  }
122
105
  }
123
106
  function effectivePixelRatio(width, height, dpr, q) {
@@ -140,9 +123,6 @@ function probeSoftwareGL() {
140
123
  }
141
124
 
142
125
  export {
143
- resolveIpfsUrl,
144
- FISH_CATALOG,
145
- DEFAULT_FISH,
146
126
  compileSwimPlan,
147
127
  distanceAt,
148
128
  swimPoseAtDistance,
@@ -150,4 +130,4 @@ export {
150
130
  effectivePixelRatio,
151
131
  probeSoftwareGL
152
132
  };
153
- //# sourceMappingURL=chunk-YWDLFCBS.js.map
133
+ //# sourceMappingURL=chunk-F74UWBMY.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/plan.ts","../src/quality.ts"],"sourcesContent":["import type { Rng } from '@idle-screens/core';\n\nexport interface TankBounds {\n radius: number;\n yMin: number;\n yMax: number;\n}\n\n/**\n * Catmull-Rom spline swim: a seeded closed loop of waypoints with arc-length\n * parameterization. Pose is a pure function of (plan, distance), so\n * `renderFrame(t, seed)` stays frame-addressable and scrubbing works.\n */\n\nexport interface SwimPlan {\n /** Closed-loop control points. */\n points: Array<[number, number, number]>;\n /** Arc-length lookup: cumulative length at N uniform spline params. */\n arc: Float32Array;\n totalLength: number;\n /** Cruise speed, units/sec, before the swimSpeed param scales it. */\n cruise: number;\n /** Speed-wobble harmonics (closed-form integral → distance). */\n wobble: Array<{ amp: number; w: number; phase: number }>;\n}\n\nexport interface SwimPose {\n x: number;\n y: number;\n z: number;\n /** Unit forward tangent — the fish noses along this. */\n fx: number;\n fy: number;\n fz: number;\n /** Bank roll (radians): lean into horizontal turns. */\n roll: number;\n /** Distance travelled (drives the tail-beat clip phase). */\n dist: number;\n}\n\nconst ARC_SAMPLES = 1024;\n\nfunction catmullRom(\n p0: number,\n p1: number,\n p2: number,\n p3: number,\n u: number,\n): number {\n const u2 = u * u;\n const u3 = u2 * u;\n return (\n 0.5 *\n (2 * p1 +\n (-p0 + p2) * u +\n (2 * p0 - 5 * p1 + 4 * p2 - p3) * u2 +\n (-p0 + 3 * p1 - 3 * p2 + p3) * u3)\n );\n}\n\n/** Point on the closed loop at global param `g` ∈ [0, n). */\nfunction splineAt(points: SwimPlan['points'], g: number): [number, number, number] {\n const n = points.length;\n const i = Math.floor(g) % n;\n const u = g - Math.floor(g);\n const p0 = points[(i - 1 + n) % n]!;\n const p1 = points[i]!;\n const p2 = points[(i + 1) % n]!;\n const p3 = points[(i + 2) % n]!;\n return [\n catmullRom(p0[0], p1[0], p2[0], p3[0], u),\n catmullRom(p0[1], p1[1], p2[1], p3[1], u),\n catmullRom(p0[2], p1[2], p2[2], p3[2], u),\n ];\n}\n\n/**\n * Compile a seeded itinerary through the tank volume. Waypoints alternate\n * around the ring (angle jitter, radius breathing, depth changes) so the\n * loop reads as purposeful wandering, never a circle.\n */\nexport function compileSwimPlan(rng: Rng, bounds: TankBounds): SwimPlan {\n const n = 16 + rng.int(0, 4);\n const points: SwimPlan['points'] = [];\n const ySpan = bounds.yMax - bounds.yMin;\n for (let i = 0; i < n; i++) {\n const baseAngle = (i / n) * Math.PI * 2;\n const angle = baseAngle + rng.range(-0.25, 0.25);\n const inner = i % 2 === 0;\n const radius = inner\n ? bounds.radius * rng.range(0.15, 0.45)\n : bounds.radius * rng.range(0.55, 0.85);\n const y = bounds.yMin + ySpan * rng.range(0.2, 0.8);\n points.push([Math.cos(angle) * radius, y, Math.sin(angle) * radius]);\n }\n\n // Arc-length table over the closed loop.\n const arc = new Float32Array(ARC_SAMPLES + 1);\n let prev = splineAt(points, 0);\n let total = 0;\n for (let s = 1; s <= ARC_SAMPLES; s++) {\n const g = (s / ARC_SAMPLES) * n;\n const p = splineAt(points, g);\n total += Math.hypot(p[0] - prev[0], p[1] - prev[1], p[2] - prev[2]);\n arc[s] = total;\n prev = p;\n }\n\n // Cruise + gentle speed wobble: sometimes dawdling, sometimes keen — the\n // integral of each harmonic is closed-form, so distance(t) never drifts.\n const cruise = 8 + rng.range(0, 4);\n const wobble = [\n { amp: cruise * 0.18, w: 0.11 + rng.range(0, 0.05), phase: rng.range(0, Math.PI * 2) },\n { amp: cruise * 0.1, w: 0.043 + rng.range(0, 0.02), phase: rng.range(0, Math.PI * 2) },\n ];\n\n return { points, arc, totalLength: total, cruise, wobble };\n}\n\n/** Distance travelled by time `tSec` at `speed` scale — pure closed form. */\nexport function distanceAt(plan: SwimPlan, tSec: number, speed: number): number {\n let d = plan.cruise * tSec;\n for (const h of plan.wobble) {\n // ∫ amp·sin(w·t + φ) dt = −(amp/w)·cos(w·t + φ); anchored so d(0) = 0.\n d += (h.amp / h.w) * (Math.cos(h.phase) - Math.cos(h.w * tSec + h.phase));\n }\n return d * speed;\n}\n\n/** Global spline param for a given arc distance (binary search + lerp). */\nfunction paramForDistance(plan: SwimPlan, dist: number): number {\n const n = plan.points.length;\n const d = ((dist % plan.totalLength) + plan.totalLength) % plan.totalLength;\n const arc = plan.arc;\n let lo = 0;\n let hi = ARC_SAMPLES;\n while (lo + 1 < hi) {\n const mid = (lo + hi) >> 1;\n if (arc[mid]! <= d) lo = mid;\n else hi = mid;\n }\n const seg = arc[hi]! - arc[lo]! || 1;\n const frac = (d - arc[lo]!) / seg;\n return ((lo + frac) / ARC_SAMPLES) * n;\n}\n\n/** Pose at `tSec` — position, unit forward tangent, bank roll, distance. */\nexport function swimPoseAt(plan: SwimPlan, tSec: number, speed = 1): SwimPose {\n return swimPoseAtDistance(plan, distanceAt(plan, tSec, speed));\n}\n\n/** Pose at an explicit arc distance — the seam for behavior-modulated speed.\n * When the track steers swimSpeed, the tank feeds this the closed-form\n * integral of the speed curve (core's `integrateParam`) so speed changes\n * glide instead of teleporting, while staying a pure function of (t, track)\n * — scrubbing and renderFrame(t) keep working. */\nexport function swimPoseAtDistance(plan: SwimPlan, dist: number): SwimPose {\n const g = paramForDistance(plan, dist);\n const [x, y, z] = splineAt(plan.points, g);\n\n const step = (plan.points.length / ARC_SAMPLES) * 8;\n const ahead = splineAt(plan.points, g + step);\n const behind = splineAt(plan.points, g - step + plan.points.length);\n let fx = ahead[0] - behind[0];\n let fy = ahead[1] - behind[1];\n let fz = ahead[2] - behind[2];\n const m = Math.hypot(fx, fy, fz) || 1;\n fx /= m;\n fy /= m;\n fz /= m;\n\n // Bank: signed change of horizontal heading over the step → lean into it.\n const h0 = Math.atan2(z - behind[2], x - behind[0]);\n const h1 = Math.atan2(ahead[2] - z, ahead[0] - x);\n let turn = h1 - h0;\n if (turn > Math.PI) turn -= Math.PI * 2;\n if (turn < -Math.PI) turn += Math.PI * 2;\n const roll = Math.max(-0.35, Math.min(0.35, turn * 3.5));\n\n return { x, y, z, fx, fy, fz, roll, dist };\n}\n","import type { CapabilityTier } from '@idle-screens/capabilities';\n\nexport interface TankQuality {\n maxPixelRatio: number;\n antialias: boolean;\n fishCap: number;\n pixelBudget: number;\n /** Plankton motes at moteDensity=1 — scaled down by the param, zero cost\n * at the default 0. Vertex-shader driven, so the per-frame cost is GPU\n * vertex work only. */\n moteCap: number;\n}\n\nexport function qualityFor(tier: CapabilityTier): TankQuality {\n switch (tier) {\n case 'high':\n return { maxPixelRatio: 1.25, antialias: true, fishCap: 24, pixelBudget: 1_800_000, moteCap: 400 };\n case 'standard':\n return { maxPixelRatio: 1, antialias: true, fishCap: 16, pixelBudget: 1_200_000, moteCap: 250 };\n default:\n return { maxPixelRatio: 1, antialias: false, fishCap: 8, pixelBudget: 900_000, moteCap: 120 };\n }\n}\n\nexport function effectivePixelRatio(\n width: number,\n height: number,\n dpr: number,\n q: TankQuality,\n): number {\n const area = Math.max(1, width * height);\n return Math.max(0.5, Math.min(dpr, q.maxPixelRatio, Math.sqrt(q.pixelBudget / area)));\n}\n\nexport function isSoftwareGL(renderer: string): boolean {\n return /swiftshader|llvmpipe|software|swangle/i.test(renderer);\n}\n\n/** Probe the default GL context for a software renderer before creating a\n * WebGLRenderer so antialias/quality tier match from the first frame. */\nexport function probeSoftwareGL(): boolean {\n if (typeof document === 'undefined') return false;\n const canvas = document.createElement('canvas');\n const gl = canvas.getContext('webgl2') ?? canvas.getContext('webgl');\n if (!gl) return false;\n const dbg = gl.getExtension('WEBGL_debug_renderer_info');\n const renderer = String(\n dbg ? gl.getParameter(dbg.UNMASKED_RENDERER_WEBGL) : gl.getParameter(gl.RENDERER),\n );\n return isSoftwareGL(renderer);\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,199 @@
1
+ // src/ipfs.ts
2
+ var IPFS_GATEWAY = "https://dweb.link/ipfs/";
3
+ function resolveIpfsUrl(url) {
4
+ if (!url.startsWith("ipfs://")) return url;
5
+ const stripped = url.slice("ipfs://".length);
6
+ return `${IPFS_GATEWAY}${stripped}`;
7
+ }
8
+ var FISH_CATALOG = [
9
+ { id: 257, name: "Fish 257", breed: "angelfish", ipfs3d: "ipfs://QmaHbEQAP6k2zopJHJBzyaK62zNX5yH8yASDjkaG4DY9Dp/fish_257_of_the_metaquarium_3d.glb", localGlb: "/assets/metaquarium/fish-257-angelfish.glb" },
10
+ { id: 258, name: "Fish 258", breed: "angelfish", ipfs3d: "ipfs://QmUZGF3ge3d9rzrtxrD6V4qx2gLtGeeNLuCb8fQeNyUkwJ/fish_258_of_the_metaquarium_3d.glb", localGlb: "" },
11
+ { id: 259, name: "Fish 259", breed: "angelfish", ipfs3d: "ipfs://QmfBBnNrVrkffMKoESvq3cB6nAWGpfMPjduTgw1unahvPf/fish_259_of_the_metaquarium_3d.glb", localGlb: "" },
12
+ { id: 100, name: "Fish 100", breed: "betafish", ipfs3d: "ipfs://Qmb5Uu8u154QTzoGpB6ypwVfPZ8NUsU519tQmrgE8yQrWV/fish_100_of_the_metaquarium_3d.glb", localGlb: "/assets/metaquarium/fish-100-betafish.glb" },
13
+ { id: 457, name: "Fish 457", breed: "seahorse", ipfs3d: "ipfs://QmVvEaCa6zRp8Z9YkkZVYBn2owSdwZxupEQacjfd1b2HA2/fish_457_of_the_metaquarium_3d.glb", localGlb: "" },
14
+ { id: 497, name: "Fish 497", breed: "seaturtle", ipfs3d: "ipfs://QmTBNvoUiwPw9HSUmy1qKCWPBKkBRAensgooYVqMmsviaE/fish_497_of_the_metaquarium_3d.glb", localGlb: "" }
15
+ ];
16
+ var DEFAULT_FISH = FISH_CATALOG[0];
17
+ function parseFishMix(mix, catalog = FISH_CATALOG) {
18
+ const entries = [];
19
+ const problems = [];
20
+ for (const rawToken of mix.split(",")) {
21
+ const token = rawToken.trim();
22
+ if (token === "") continue;
23
+ const [idRaw, countRaw, ...extra] = token.split(":");
24
+ if (extra.length > 0) {
25
+ problems.push(`"${token}": too many ':' \u2014 expected id[:count]`);
26
+ continue;
27
+ }
28
+ const key = (idRaw ?? "").trim().toLowerCase();
29
+ const fish = /^\d+$/.test(key) ? catalog.find((f) => f.id === Number(key)) : catalog.find((f) => f.breed.toLowerCase() === key);
30
+ if (!fish) {
31
+ problems.push(`"${key}": not a catalog id or breed (have ${catalog.map((f) => f.id).join(", ")})`);
32
+ continue;
33
+ }
34
+ let count = 1;
35
+ if (countRaw !== void 0) {
36
+ const n = Number(countRaw.trim());
37
+ if (!Number.isInteger(n) || n < 1 || n > 24) {
38
+ problems.push(`"${token}": count must be an integer 1-24`);
39
+ continue;
40
+ }
41
+ count = n;
42
+ }
43
+ entries.push({ id: fish.id, url: fish.ipfs3d, count });
44
+ }
45
+ return { entries, problems };
46
+ }
47
+ function expandFishMix(entries, cap) {
48
+ const urls = [];
49
+ for (const e of entries) {
50
+ for (let i = 0; i < e.count && urls.length < cap; i++) urls.push(e.url);
51
+ }
52
+ return urls;
53
+ }
54
+
55
+ // src/manifest.ts
56
+ var METAQUARIUM_PARAMS = {
57
+ /** Camera orbit angle, degrees. */
58
+ cameraAzimuth: { type: "number", default: 35, min: 0, max: 360, ease: "smooth" },
59
+ /** Camera height angle, degrees above the waterline plane. */
60
+ cameraElevation: { type: "number", default: 15, min: -5, max: 60, ease: "smooth" },
61
+ /** Camera distance from tank center, world units. */
62
+ cameraDistance: { type: "number", default: 110, min: 80, max: 400, ease: "smooth" },
63
+ /** Continuous orbit speed, degrees/second. Zero by default: the tank is
64
+ * still, letting the fish movement carry the scene. Steer up for an orbit. */
65
+ autoRotate: { type: "number", default: 0, min: 0, max: 12, ease: "smooth" },
66
+ /** Visible fish. Default 1 = hero mode: one fish center-stage in its own
67
+ * tank; the pool grows on demand when steered up (never shrinks). */
68
+ fishCount: { type: "number", default: 1, min: 1, max: 24, ease: "step" },
69
+ /** Swim time-scale multiplier. */
70
+ swimSpeed: { type: "number", default: 1, min: 0.2, max: 3, ease: "smooth" },
71
+ /** Water/atmosphere color (background + fog). */
72
+ fogColor: { type: "color", default: "#030009", ease: "smooth" },
73
+ /** GLB model URL for the fish (supports ipfs:// scheme). Package default
74
+ * is the IPFS hero fish so npm / channel hosts work without playground
75
+ * static assets; the playground overrides this to a local GLB. */
76
+ fishUrl: { type: "string", default: "ipfs://QmaHbEQAP6k2zopJHJBzyaK62zNX5yH8yASDjkaG4DY9Dp/fish_257_of_the_metaquarium_3d.glb" },
77
+ /** Mixed population DSL: comma-separated `id[:count]` of catalog token ids
78
+ * or breed aliases — e.g. "257:3,100:2,seaturtle:1". Counts are absolute;
79
+ * the expanded total clamps to the device tier's fish cap. Non-empty mix
80
+ * OVERRIDES fishUrl and fishCount; empty string = single-breed mode.
81
+ * Unknown ids/bad counts degrade (good tokens still parse); raw URLs are
82
+ * not accepted here — use fishUrl for a custom single-breed GLB. */
83
+ fishMix: { type: "string", default: "", ease: "step" },
84
+ /** Fog start distance, world units. Default matches the tank's original
85
+ * hardcoded Fog(60, 500) — steering below pulls the murk close. */
86
+ fogNear: { type: "number", default: 60, min: 20, max: 200, ease: "smooth" },
87
+ /** Fog full-opacity distance. Kept under the camera far plane (1200);
88
+ * the tank enforces far > near + 20. */
89
+ fogFar: { type: "number", default: 500, min: 120, max: 1100, ease: "smooth" },
90
+ /** Plankton mote density, 0-1 of the device tier's mote budget. Default 0
91
+ * = off, so the baseline look is untouched until steered. */
92
+ moteDensity: { type: "number", default: 0, min: 0, max: 1, ease: "smooth" },
93
+ /** Mote tint. */
94
+ moteColor: { type: "color", default: "#7fd6ff", ease: "smooth" },
95
+ /** Floor disc color. Default is the original hardcoded navy. */
96
+ floorColor: { type: "color", default: "#0a1d33", ease: "smooth" }
97
+ };
98
+ var MIAMI_VICE_COLORS = [
99
+ "#ff6ad5",
100
+ // Hot Pink
101
+ "#c774e8",
102
+ // Light Purple
103
+ "#ad8cff",
104
+ // Lavender
105
+ "#8795e8",
106
+ // Periwinkle Blue
107
+ "#94d0ff",
108
+ // Light Sky Blue
109
+ "#4fb4f4",
110
+ // Azure
111
+ "#00a6fb",
112
+ // Bright Blue
113
+ "#0085a1",
114
+ // Cerulean
115
+ "#0b3d91",
116
+ // Yale Blue
117
+ "#1c1c1c"
118
+ // Off Black
119
+ ];
120
+ var BLOOM_COLORS = [
121
+ "#FF69B4",
122
+ // Hot Pink
123
+ "#FFFF00",
124
+ // Yellow
125
+ "#00FFFF",
126
+ // Cyan
127
+ "#00FF00",
128
+ // Lime
129
+ "#FF4500",
130
+ // OrangeRed
131
+ "#7FFF00"
132
+ // Chartreuse
133
+ ];
134
+ var metaquariumManifest = {
135
+ id: "metaquarium",
136
+ label: "Metaquarium",
137
+ description: "A living fish tank: skinned GLB fish swim seeded Catmull-Rom paths through a dark, fogged aquarium.",
138
+ minBackend: "webgl2",
139
+ costTier: "medium",
140
+ motionIntensity: "calm",
141
+ reducedMotionFallback: "static",
142
+ timeModel: "closed-form",
143
+ palette: ["#030009", "#ff6ad5", "#94d0ff", "#00a6fb", "#00FFFF"],
144
+ paramSpace: METAQUARIUM_PARAMS,
145
+ attribution: {
146
+ source: "Metaquarium (metaquarium.xyz) \u2014 original artwork and fish models by Shawn Partridge",
147
+ license: "MIT port of first-party artwork",
148
+ url: "https://metaquarium.xyz"
149
+ },
150
+ a11y: {
151
+ flashSafe: true,
152
+ notes: "Slow ambient swim in a dark fogged tank; no strobing."
153
+ }
154
+ };
155
+ function withDefaults(space, overrides) {
156
+ if (!overrides) return space;
157
+ const out = {};
158
+ for (const [k, def] of Object.entries(space)) {
159
+ const o = overrides[k];
160
+ out[k] = o === void 0 ? def : { ...def, default: o };
161
+ }
162
+ return out;
163
+ }
164
+ function paramSpaceWith(overrides) {
165
+ return withDefaults(METAQUARIUM_PARAMS, overrides);
166
+ }
167
+ function coerceNum(def, v) {
168
+ let n = null;
169
+ if (typeof v === "number" && Number.isFinite(v)) n = v;
170
+ else if (typeof v === "string" && v.trim() !== "") {
171
+ const parsed = Number(v);
172
+ if (Number.isFinite(parsed)) n = parsed;
173
+ }
174
+ if (n === null) {
175
+ const d = def?.default;
176
+ if (typeof d === "number" && Number.isFinite(d)) n = d;
177
+ else if (typeof d === "string" && d.trim() !== "" && Number.isFinite(Number(d))) n = Number(d);
178
+ else n = 0;
179
+ }
180
+ if (def?.min !== void 0 && n < def.min) n = def.min;
181
+ if (def?.max !== void 0 && n > def.max) n = def.max;
182
+ return n;
183
+ }
184
+
185
+ export {
186
+ resolveIpfsUrl,
187
+ FISH_CATALOG,
188
+ DEFAULT_FISH,
189
+ parseFishMix,
190
+ expandFishMix,
191
+ METAQUARIUM_PARAMS,
192
+ MIAMI_VICE_COLORS,
193
+ BLOOM_COLORS,
194
+ metaquariumManifest,
195
+ withDefaults,
196
+ paramSpaceWith,
197
+ coerceNum
198
+ };
199
+ //# sourceMappingURL=chunk-MK5SRAXJ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/ipfs.ts","../src/manifest.ts"],"sourcesContent":["const IPFS_GATEWAY = 'https://dweb.link/ipfs/';\n\nexport function resolveIpfsUrl(url: string): string {\n if (!url.startsWith('ipfs://')) return url;\n const stripped = url.slice('ipfs://'.length);\n return `${IPFS_GATEWAY}${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\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). Counts are absolute; the tank clamps the expanded total to\n * its tier cap. Raw URLs are deliberately NOT accepted — `:` and `,` stay\n * unambiguous, the validation surface stays finite, and custom URLs remain\n * `fishUrl`'s job (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 const fish = /^\\d+$/.test(key)\n ? catalog.find((f) => f.id === Number(key))\n : catalog.find((f) => f.breed.toLowerCase() === key);\n if (!fish) {\n problems.push(`\"${key}\": not a catalog id or breed (have ${catalog.map((f) => f.id).join(', ')})`);\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';\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 catalog token ids\n * or breed aliases — e.g. \"257:3,100:2,seaturtle:1\". Counts are absolute;\n * the expanded total clamps to the device tier's fish cap. Non-empty mix\n * OVERRIDES fishUrl and fishCount; empty string = single-breed mode.\n * Unknown ids/bad counts degrade (good tokens still parse); raw URLs are\n * 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} 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 { parseFishMix, expandFishMix, type FishMixEntry, type FishMixResult, type FishEntry, FISH_CATALOG } from './ipfs';\n"],"mappings":";AAAA,IAAM,eAAe;AAEd,SAAS,eAAe,KAAqB;AAClD,MAAI,CAAC,IAAI,WAAW,SAAS,EAAG,QAAO;AACvC,QAAM,WAAW,IAAI,MAAM,UAAU,MAAM;AAC3C,SAAO,GAAG,YAAY,GAAG,QAAQ;AACnC;AAUO,IAAM,eAA4B;AAAA,EACvC,EAAE,IAAI,KAAK,MAAM,YAAY,OAAO,aAAa,QAAQ,4FAA4F,UAAU,6CAA6C;AAAA,EAC5M,EAAE,IAAI,KAAK,MAAM,YAAY,OAAO,aAAa,QAAQ,4FAA4F,UAAU,GAAG;AAAA,EAClK,EAAE,IAAI,KAAK,MAAM,YAAY,OAAO,aAAa,QAAQ,4FAA4F,UAAU,GAAG;AAAA,EAClK,EAAE,IAAI,KAAK,MAAM,YAAY,OAAO,YAAY,QAAQ,4FAA4F,UAAU,4CAA4C;AAAA,EAC1M,EAAE,IAAI,KAAK,MAAM,YAAY,OAAO,YAAY,QAAQ,4FAA4F,UAAU,GAAG;AAAA,EACjK,EAAE,IAAI,KAAK,MAAM,YAAY,OAAO,aAAa,QAAQ,4FAA4F,UAAU,GAAG;AACpK;AAEO,IAAM,eAAe,aAAa,CAAC;AA0BnC,SAAS,aACd,KACA,UAAuB,cACR;AACf,QAAM,UAA0B,CAAC;AACjC,QAAM,WAAqB,CAAC;AAC5B,aAAW,YAAY,IAAI,MAAM,GAAG,GAAG;AACrC,UAAM,QAAQ,SAAS,KAAK;AAC5B,QAAI,UAAU,GAAI;AAClB,UAAM,CAAC,OAAO,UAAU,GAAG,KAAK,IAAI,MAAM,MAAM,GAAG;AACnD,QAAI,MAAM,SAAS,GAAG;AACpB,eAAS,KAAK,IAAI,KAAK,4CAAuC;AAC9D;AAAA,IACF;AACA,UAAM,OAAO,SAAS,IAAI,KAAK,EAAE,YAAY;AAC7C,UAAM,OAAO,QAAQ,KAAK,GAAG,IACzB,QAAQ,KAAK,CAAC,MAAM,EAAE,OAAO,OAAO,GAAG,CAAC,IACxC,QAAQ,KAAK,CAAC,MAAM,EAAE,MAAM,YAAY,MAAM,GAAG;AACrD,QAAI,CAAC,MAAM;AACT,eAAS,KAAK,IAAI,GAAG,sCAAsC,QAAQ,IAAI,CAAC,MAAM,EAAE,EAAE,EAAE,KAAK,IAAI,CAAC,GAAG;AACjG;AAAA,IACF;AACA,QAAI,QAAQ;AACZ,QAAI,aAAa,QAAW;AAC1B,YAAM,IAAI,OAAO,SAAS,KAAK,CAAC;AAChC,UAAI,CAAC,OAAO,UAAU,CAAC,KAAK,IAAI,KAAK,IAAI,IAAI;AAC3C,iBAAS,KAAK,IAAI,KAAK,kCAAkC;AACzD;AAAA,MACF;AACA,cAAQ;AAAA,IACV;AACA,YAAQ,KAAK,EAAE,IAAI,KAAK,IAAI,KAAK,KAAK,QAAQ,MAAM,CAAC;AAAA,EACvD;AACA,SAAO,EAAE,SAAS,SAAS;AAC7B;AAKO,SAAS,cAAc,SAAyB,KAAuB;AAC5E,QAAM,OAAiB,CAAC;AACxB,aAAW,KAAK,SAAS;AACvB,aAAS,IAAI,GAAG,IAAI,EAAE,SAAS,KAAK,SAAS,KAAK,IAAK,MAAK,KAAK,EAAE,GAAG;AAAA,EACxE;AACA,SAAO;AACT;;;ACzFO,IAAM,qBAAqB;AAAA;AAAA,EAEhC,eAAe,EAAE,MAAM,UAAU,SAAS,IAAI,KAAK,GAAG,KAAK,KAAK,MAAM,SAAS;AAAA;AAAA,EAE/E,iBAAiB,EAAE,MAAM,UAAU,SAAS,IAAI,KAAK,IAAI,KAAK,IAAI,MAAM,SAAS;AAAA;AAAA,EAEjF,gBAAgB,EAAE,MAAM,UAAU,SAAS,KAAK,KAAK,IAAI,KAAK,KAAK,MAAM,SAAS;AAAA;AAAA;AAAA,EAGlF,YAAY,EAAE,MAAM,UAAU,SAAS,GAAG,KAAK,GAAG,KAAK,IAAI,MAAM,SAAS;AAAA;AAAA;AAAA,EAG1E,WAAW,EAAE,MAAM,UAAU,SAAS,GAAG,KAAK,GAAG,KAAK,IAAI,MAAM,OAAO;AAAA;AAAA,EAEvE,WAAW,EAAE,MAAM,UAAU,SAAS,GAAG,KAAK,KAAK,KAAK,GAAG,MAAM,SAAS;AAAA;AAAA,EAE1E,UAAU,EAAE,MAAM,SAAS,SAAS,WAAW,MAAM,SAAS;AAAA;AAAA;AAAA;AAAA,EAI9D,SAAS,EAAE,MAAM,UAAU,SAAS,2FAA2F;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAO/H,SAAS,EAAE,MAAM,UAAU,SAAS,IAAI,MAAM,OAAO;AAAA;AAAA;AAAA,EAGrD,SAAS,EAAE,MAAM,UAAU,SAAS,IAAI,KAAK,IAAI,KAAK,KAAK,MAAM,SAAS;AAAA;AAAA;AAAA,EAG1E,QAAQ,EAAE,MAAM,UAAU,SAAS,KAAK,KAAK,KAAK,KAAK,MAAM,MAAM,SAAS;AAAA;AAAA;AAAA,EAG5E,aAAa,EAAE,MAAM,UAAU,SAAS,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,SAAS;AAAA;AAAA,EAE1E,WAAW,EAAE,MAAM,SAAS,SAAS,WAAW,MAAM,SAAS;AAAA;AAAA,EAE/D,YAAY,EAAE,MAAM,SAAS,SAAS,WAAW,MAAM,SAAS;AAClE;AAIO,IAAM,oBAAoB;AAAA,EAC/B;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AACF;AAGO,IAAM,eAAe;AAAA,EAC1B;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AACF;AAEO,IAAM,sBAAqC;AAAA,EAChD,IAAI;AAAA,EACJ,OAAO;AAAA,EACP,aACE;AAAA,EACF,YAAY;AAAA,EACZ,UAAU;AAAA,EACV,iBAAiB;AAAA,EACjB,uBAAuB;AAAA,EACvB,WAAW;AAAA,EACX,SAAS,CAAC,WAAW,WAAW,WAAW,WAAW,SAAS;AAAA,EAC/D,YAAY;AAAA,EACZ,aAAa;AAAA,IACX,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,KAAK;AAAA,EACP;AAAA,EACA,MAAM;AAAA,IACJ,WAAW;AAAA,IACX,OAAO;AAAA,EACT;AACF;AAeO,SAAS,aACd,OACA,WACY;AACZ,MAAI,CAAC,UAAW,QAAO;AACvB,QAAM,MAAkB,CAAC;AACzB,aAAW,CAAC,GAAG,GAAG,KAAK,OAAO,QAAQ,KAAK,GAAG;AAC5C,UAAM,IAAI,UAAU,CAAC;AACrB,QAAI,CAAC,IAAI,MAAM,SAAY,MAAM,EAAE,GAAG,KAAK,SAAS,EAAE;AAAA,EACxD;AACA,SAAO;AACT;AAGO,SAAS,eAAe,WAAqD;AAClF,SAAO,aAAa,oBAAoB,SAAmD;AAC7F;AAUO,SAAS,UACd,KACA,GACQ;AACR,MAAI,IAAmB;AACvB,MAAI,OAAO,MAAM,YAAY,OAAO,SAAS,CAAC,EAAG,KAAI;AAAA,WAC5C,OAAO,MAAM,YAAY,EAAE,KAAK,MAAM,IAAI;AACjD,UAAM,SAAS,OAAO,CAAC;AACvB,QAAI,OAAO,SAAS,MAAM,EAAG,KAAI;AAAA,EACnC;AACA,MAAI,MAAM,MAAM;AACd,UAAM,IAAI,KAAK;AACf,QAAI,OAAO,MAAM,YAAY,OAAO,SAAS,CAAC,EAAG,KAAI;AAAA,aAC5C,OAAO,MAAM,YAAY,EAAE,KAAK,MAAM,MAAM,OAAO,SAAS,OAAO,CAAC,CAAC,EAAG,KAAI,OAAO,CAAC;AAAA,QACxF,KAAI;AAAA,EACX;AACA,MAAI,KAAK,QAAQ,UAAa,IAAI,IAAI,IAAK,KAAI,IAAI;AACnD,MAAI,KAAK,QAAQ,UAAa,IAAI,IAAI,IAAK,KAAI,IAAI;AACnD,SAAO;AACT;","names":[]}
package/dist/index.d.ts CHANGED
@@ -1,10 +1,15 @@
1
1
  import { SaverPlugin, ControlTrack } from '@idle-screens/core';
2
- import { MetaquariumOptions } from './manifest.js';
3
- export { BLOOM_COLORS, METAQUARIUM_PARAMS, MIAMI_VICE_COLORS, metaquariumManifest, paramSpaceWith, withDefaults } from './manifest.js';
2
+ import { M as MetaquariumOptions } from './manifest-RJG2p7A4.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-RJG2p7A4.js';
4
4
  import { CapabilityTier } from '@idle-screens/capabilities';
5
5
 
6
6
  declare function createMetaquarium(opts?: MetaquariumOptions): SaverPlugin;
7
7
  declare const metaquarium: SaverPlugin;
8
+ /** A 40s looping tour of every steerable feature: camera orbit, fog color
9
+ * and depth, the swimSpeed glide (closed-form integral — no teleport),
10
+ * fishCount growth (pool spawns on demand), plankton motes, floor tint.
11
+ * Every param returns to its default before the loop ends, so wraps are
12
+ * seamless and the resting tank is the published default. */
8
13
  declare const demoTrack: ControlTrack;
9
14
 
10
15
  interface TankQuality {
@@ -12,6 +17,10 @@ interface TankQuality {
12
17
  antialias: boolean;
13
18
  fishCap: number;
14
19
  pixelBudget: number;
20
+ /** Plankton motes at moteDensity=1 — scaled down by the param, zero cost
21
+ * at the default 0. Vertex-shader driven, so the per-frame cost is GPU
22
+ * vertex work only. */
23
+ moteCap: number;
15
24
  }
16
25
  declare function qualityFor(tier: CapabilityTier): TankQuality;
17
26
 
@@ -54,15 +63,4 @@ interface SwimPose {
54
63
  dist: number;
55
64
  }
56
65
 
57
- declare function resolveIpfsUrl(url: string): string;
58
- interface FishEntry {
59
- id: number;
60
- name: string;
61
- breed: string;
62
- ipfs3d: string;
63
- localGlb: string;
64
- }
65
- declare const FISH_CATALOG: FishEntry[];
66
- declare const DEFAULT_FISH: FishEntry;
67
-
68
- export { DEFAULT_FISH, FISH_CATALOG, type FishEntry, MetaquariumOptions, type SwimPlan, type SwimPose, type TankBounds, type TankQuality, createMetaquarium, demoTrack, metaquarium, qualityFor, resolveIpfsUrl };
66
+ export { MetaquariumOptions, type SwimPlan, type SwimPose, type TankBounds, type TankQuality, createMetaquarium, demoTrack, metaquarium, qualityFor };
package/dist/index.js CHANGED
@@ -1,17 +1,17 @@
1
1
  import {
2
- DEFAULT_FISH,
3
- FISH_CATALOG,
4
- qualityFor,
5
- resolveIpfsUrl
6
- } from "./chunk-YWDLFCBS.js";
2
+ qualityFor
3
+ } from "./chunk-F74UWBMY.js";
7
4
  import {
8
5
  BLOOM_COLORS,
6
+ DEFAULT_FISH,
7
+ FISH_CATALOG,
9
8
  METAQUARIUM_PARAMS,
10
9
  MIAMI_VICE_COLORS,
11
10
  metaquariumManifest,
12
11
  paramSpaceWith,
12
+ resolveIpfsUrl,
13
13
  withDefaults
14
- } from "./chunk-4G3BQRZ3.js";
14
+ } from "./chunk-MK5SRAXJ.js";
15
15
 
16
16
  // src/metaquarium.ts
17
17
  import { detectCapabilities, computeTier } from "@idle-screens/capabilities";
@@ -26,10 +26,10 @@ function createMetaquarium(opts = {}) {
26
26
  },
27
27
  async mount(ctx) {
28
28
  const [{ mountTank }, caps] = await Promise.all([
29
- import("./tank-CQSFZYVF.js"),
29
+ import("./tank-EO7B7ATP.js"),
30
30
  detectCapabilities()
31
31
  ]);
32
- return mountTank(ctx, space, computeTier(caps));
32
+ return mountTank(ctx, space, computeTier(caps), opts.catalog);
33
33
  }
34
34
  };
35
35
  }
@@ -37,13 +37,25 @@ var metaquarium = createMetaquarium();
37
37
  var demoTrack = {
38
38
  program: "metaquarium",
39
39
  seed: 42,
40
- duration: 3e4,
40
+ duration: 4e4,
41
41
  loop: true,
42
42
  deltas: [
43
43
  { t: 5e3, path: "autoRotate", value: 3, dur: 4e3 },
44
+ { t: 6e3, path: "fishCount", value: 5, ease: "step" },
45
+ { t: 8e3, path: "swimSpeed", value: 2.4, dur: 3e3 },
46
+ { t: 1e4, path: "moteDensity", value: 0.8, dur: 4e3 },
47
+ { t: 12e3, path: "fogNear", value: 32, dur: 4e3 },
48
+ { t: 12e3, path: "fogFar", value: 280, dur: 4e3 },
44
49
  { t: 15e3, path: "fogColor", value: "#04002c", dur: 5e3 },
45
- { t: 22e3, path: "fogColor", value: "#030009", dur: 5e3 },
46
- { t: 25e3, path: "autoRotate", value: 0, dur: 4e3 }
50
+ { t: 16e3, path: "floorColor", value: "#0d3330", dur: 4e3 },
51
+ { t: 2e4, path: "swimSpeed", value: 1, dur: 4e3 },
52
+ { t: 24e3, path: "fogColor", value: "#030009", dur: 5e3 },
53
+ { t: 27e3, path: "fogNear", value: 60, dur: 5e3 },
54
+ { t: 27e3, path: "fogFar", value: 500, dur: 5e3 },
55
+ { t: 29e3, path: "floorColor", value: "#0a1d33", dur: 4e3 },
56
+ { t: 31e3, path: "moteDensity", value: 0, dur: 5e3 },
57
+ { t: 33e3, path: "autoRotate", value: 0, dur: 4e3 },
58
+ { t: 36e3, path: "fishCount", value: 1, ease: "step" }
47
59
  ]
48
60
  };
49
61
  export {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/metaquarium.ts"],"sourcesContent":["import type { ControlTrack, SaverContext, SaverInstance, SaverPlugin } from '@idle-screens/core';\nimport { detectCapabilities, computeTier } from '@idle-screens/capabilities';\nimport {\n metaquariumManifest,\n paramSpaceWith,\n type MetaquariumOptions,\n} from './manifest';\n\nexport function createMetaquarium(opts: MetaquariumOptions = {}): SaverPlugin {\n const space = paramSpaceWith(opts.params);\n return {\n manifest: {\n ...metaquariumManifest,\n id: opts.id ?? metaquariumManifest.id,\n label: opts.label ?? metaquariumManifest.label,\n paramSpace: space,\n },\n async mount(ctx: SaverContext): Promise<SaverInstance> {\n const [{ mountTank }, caps] = await Promise.all([\n import('./tank'),\n detectCapabilities(),\n ]);\n return mountTank(ctx, space, computeTier(caps));\n },\n };\n}\n\nexport const metaquarium: SaverPlugin = createMetaquarium();\n\nexport const demoTrack: ControlTrack = {\n program: 'metaquarium',\n seed: 42,\n duration: 30_000,\n loop: true,\n deltas: [\n { t: 5_000, path: 'autoRotate', value: 3, dur: 4_000 },\n { t: 15_000, path: 'fogColor', value: '#04002c', dur: 5_000 },\n { t: 22_000, path: 'fogColor', value: '#030009', dur: 5_000 },\n { t: 25_000, path: 'autoRotate', value: 0, dur: 4_000 },\n ],\n};\n"],"mappings":";;;;;;;;;;;;;;;;AACA,SAAS,oBAAoB,mBAAmB;AAOzC,SAAS,kBAAkB,OAA2B,CAAC,GAAgB;AAC5E,QAAM,QAAQ,eAAe,KAAK,MAAM;AACxC,SAAO;AAAA,IACL,UAAU;AAAA,MACR,GAAG;AAAA,MACH,IAAI,KAAK,MAAM,oBAAoB;AAAA,MACnC,OAAO,KAAK,SAAS,oBAAoB;AAAA,MACzC,YAAY;AAAA,IACd;AAAA,IACA,MAAM,MAAM,KAA2C;AACrD,YAAM,CAAC,EAAE,UAAU,GAAG,IAAI,IAAI,MAAM,QAAQ,IAAI;AAAA,QAC9C,OAAO,oBAAQ;AAAA,QACf,mBAAmB;AAAA,MACrB,CAAC;AACD,aAAO,UAAU,KAAK,OAAO,YAAY,IAAI,CAAC;AAAA,IAChD;AAAA,EACF;AACF;AAEO,IAAM,cAA2B,kBAAkB;AAEnD,IAAM,YAA0B;AAAA,EACrC,SAAS;AAAA,EACT,MAAM;AAAA,EACN,UAAU;AAAA,EACV,MAAM;AAAA,EACN,QAAQ;AAAA,IACN,EAAE,GAAG,KAAO,MAAM,cAAc,OAAO,GAAG,KAAK,IAAM;AAAA,IACrD,EAAE,GAAG,MAAQ,MAAM,YAAY,OAAO,WAAW,KAAK,IAAM;AAAA,IAC5D,EAAE,GAAG,MAAQ,MAAM,YAAY,OAAO,WAAW,KAAK,IAAM;AAAA,IAC5D,EAAE,GAAG,MAAQ,MAAM,cAAc,OAAO,GAAG,KAAK,IAAM;AAAA,EACxD;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/metaquarium.ts"],"sourcesContent":["import type { ControlTrack, SaverContext, SaverInstance, SaverPlugin } from '@idle-screens/core';\nimport { detectCapabilities, computeTier } from '@idle-screens/capabilities';\nimport {\n metaquariumManifest,\n paramSpaceWith,\n type MetaquariumOptions,\n} from './manifest';\n\nexport function createMetaquarium(opts: MetaquariumOptions = {}): SaverPlugin {\n const space = paramSpaceWith(opts.params);\n return {\n manifest: {\n ...metaquariumManifest,\n id: opts.id ?? metaquariumManifest.id,\n label: opts.label ?? metaquariumManifest.label,\n paramSpace: space,\n },\n async mount(ctx: SaverContext): Promise<SaverInstance> {\n const [{ mountTank }, caps] = await Promise.all([\n import('./tank'),\n detectCapabilities(),\n ]);\n return mountTank(ctx, space, computeTier(caps), opts.catalog);\n },\n };\n}\n\nexport const metaquarium: SaverPlugin = createMetaquarium();\n\n/** A 40s looping tour of every steerable feature: camera orbit, fog color\n * and depth, the swimSpeed glide (closed-form integral — no teleport),\n * fishCount growth (pool spawns on demand), plankton motes, floor tint.\n * Every param returns to its default before the loop ends, so wraps are\n * seamless and the resting tank is the published default. */\nexport const demoTrack: ControlTrack = {\n program: 'metaquarium',\n seed: 42,\n duration: 40_000,\n loop: true,\n deltas: [\n { t: 5_000, path: 'autoRotate', value: 3, dur: 4_000 },\n { t: 6_000, path: 'fishCount', value: 5, ease: 'step' },\n { t: 8_000, path: 'swimSpeed', value: 2.4, dur: 3_000 },\n { t: 10_000, path: 'moteDensity', value: 0.8, dur: 4_000 },\n { t: 12_000, path: 'fogNear', value: 32, dur: 4_000 },\n { t: 12_000, path: 'fogFar', value: 280, dur: 4_000 },\n { t: 15_000, path: 'fogColor', value: '#04002c', dur: 5_000 },\n { t: 16_000, path: 'floorColor', value: '#0d3330', dur: 4_000 },\n { t: 20_000, path: 'swimSpeed', value: 1, dur: 4_000 },\n { t: 24_000, path: 'fogColor', value: '#030009', dur: 5_000 },\n { t: 27_000, path: 'fogNear', value: 60, dur: 5_000 },\n { t: 27_000, path: 'fogFar', value: 500, dur: 5_000 },\n { t: 29_000, path: 'floorColor', value: '#0a1d33', dur: 4_000 },\n { t: 31_000, path: 'moteDensity', value: 0, dur: 5_000 },\n { t: 33_000, path: 'autoRotate', value: 0, dur: 4_000 },\n { t: 36_000, path: 'fishCount', value: 1, ease: 'step' },\n ],\n};\n"],"mappings":";;;;;;;;;;;;;;;;AACA,SAAS,oBAAoB,mBAAmB;AAOzC,SAAS,kBAAkB,OAA2B,CAAC,GAAgB;AAC5E,QAAM,QAAQ,eAAe,KAAK,MAAM;AACxC,SAAO;AAAA,IACL,UAAU;AAAA,MACR,GAAG;AAAA,MACH,IAAI,KAAK,MAAM,oBAAoB;AAAA,MACnC,OAAO,KAAK,SAAS,oBAAoB;AAAA,MACzC,YAAY;AAAA,IACd;AAAA,IACA,MAAM,MAAM,KAA2C;AACrD,YAAM,CAAC,EAAE,UAAU,GAAG,IAAI,IAAI,MAAM,QAAQ,IAAI;AAAA,QAC9C,OAAO,oBAAQ;AAAA,QACf,mBAAmB;AAAA,MACrB,CAAC;AACD,aAAO,UAAU,KAAK,OAAO,YAAY,IAAI,GAAG,KAAK,OAAO;AAAA,IAC9D;AAAA,EACF;AACF;AAEO,IAAM,cAA2B,kBAAkB;AAOnD,IAAM,YAA0B;AAAA,EACrC,SAAS;AAAA,EACT,MAAM;AAAA,EACN,UAAU;AAAA,EACV,MAAM;AAAA,EACN,QAAQ;AAAA,IACN,EAAE,GAAG,KAAO,MAAM,cAAc,OAAO,GAAG,KAAK,IAAM;AAAA,IACrD,EAAE,GAAG,KAAO,MAAM,aAAa,OAAO,GAAG,MAAM,OAAO;AAAA,IACtD,EAAE,GAAG,KAAO,MAAM,aAAa,OAAO,KAAK,KAAK,IAAM;AAAA,IACtD,EAAE,GAAG,KAAQ,MAAM,eAAe,OAAO,KAAK,KAAK,IAAM;AAAA,IACzD,EAAE,GAAG,MAAQ,MAAM,WAAW,OAAO,IAAI,KAAK,IAAM;AAAA,IACpD,EAAE,GAAG,MAAQ,MAAM,UAAU,OAAO,KAAK,KAAK,IAAM;AAAA,IACpD,EAAE,GAAG,MAAQ,MAAM,YAAY,OAAO,WAAW,KAAK,IAAM;AAAA,IAC5D,EAAE,GAAG,MAAQ,MAAM,cAAc,OAAO,WAAW,KAAK,IAAM;AAAA,IAC9D,EAAE,GAAG,KAAQ,MAAM,aAAa,OAAO,GAAG,KAAK,IAAM;AAAA,IACrD,EAAE,GAAG,MAAQ,MAAM,YAAY,OAAO,WAAW,KAAK,IAAM;AAAA,IAC5D,EAAE,GAAG,MAAQ,MAAM,WAAW,OAAO,IAAI,KAAK,IAAM;AAAA,IACpD,EAAE,GAAG,MAAQ,MAAM,UAAU,OAAO,KAAK,KAAK,IAAM;AAAA,IACpD,EAAE,GAAG,MAAQ,MAAM,cAAc,OAAO,WAAW,KAAK,IAAM;AAAA,IAC9D,EAAE,GAAG,MAAQ,MAAM,eAAe,OAAO,GAAG,KAAK,IAAM;AAAA,IACvD,EAAE,GAAG,MAAQ,MAAM,cAAc,OAAO,GAAG,KAAK,IAAM;AAAA,IACtD,EAAE,GAAG,MAAQ,MAAM,aAAa,OAAO,GAAG,MAAM,OAAO;AAAA,EACzD;AACF;","names":[]}