@idle-screens/saver-metaquarium 0.1.0 → 0.3.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":[]}