@idle-screens/saver-metaquarium 0.8.0 → 0.9.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 +87 -0
- package/dist/{chunk-SBRO3JPO.js → chunk-L27AMQZ4.js} +5 -4
- package/dist/chunk-L27AMQZ4.js.map +1 -0
- package/dist/{chunk-BX2S4OKL.js → chunk-PHXRMLJT.js} +1329 -3
- package/dist/chunk-PHXRMLJT.js.map +1 -0
- package/dist/index.d.ts +12 -2
- package/dist/index.js +45 -4
- package/dist/index.js.map +1 -1
- package/dist/{lofi-tank-ZPGVXG7U.js → lofi-tank-L6LRMM2S.js} +2 -2
- package/dist/{manifest-B166cOEM.d.ts → manifest-BRCIoDi8.d.ts} +529 -3
- package/dist/manifest.d.ts +1 -1
- package/dist/manifest.js +45 -1
- package/dist/tank-3LJ4HW3C.js +5698 -0
- package/dist/tank-3LJ4HW3C.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-BX2S4OKL.js.map +0 -1
- package/dist/chunk-SBRO3JPO.js.map +0 -1
- package/dist/tank-G7B4NL3F.js +0 -1548
- package/dist/tank-G7B4NL3F.js.map +0 -1
- /package/dist/{lofi-tank-ZPGVXG7U.js.map → lofi-tank-L6LRMM2S.js.map} +0 -0
package/README.md
CHANGED
|
@@ -101,6 +101,27 @@ never changes because a dependency was bumped.
|
|
|
101
101
|
| maneuverIntensity | number | 0.7 | How hard — scales the surge, the kick and the tail flurry together, 0–1 |
|
|
102
102
|
| lightSeek | number | 0 | Free fish drawn toward the room's light shafts, each to its own pool, 0–1 (needs rays) |
|
|
103
103
|
| formationBreathe | number | 0 | The school relaxes outward and back on a ~15 s cycle, 0–1; only ever expands |
|
|
104
|
+
| *look* | | | *The next three are the renderer's defaults, not something a scene sets: every tank is lit, glowing and reflective with no params. They exist to opt out.* |
|
|
105
|
+
| fishGlow | number | 0.6 | The fish's own `GLOW-*` parts as light sources: bloom card, white-hot breathing core, colour on the floor under low swimmers. 0 is the flat colour + thin halo |
|
|
106
|
+
| fishLighting | enum | lit | `lit`: fish take light — key + fill so voxel faces shade, a generated studio environment for metal to reflect, point lights riding the glow parts nearest the camera (4 / 3 / 0 by tier). `flat` is the original unlit look |
|
|
107
|
+
| fishMetal | enum | on | Metallic plates wear a generated chrome matcap (reflection with no env map, no lights); `off` is the flat unlit atlas |
|
|
108
|
+
| eyeLife | number | 0 | Eyes blink, look and emote — each token's own pixel-grid eye redrawn in a fragment function, black and white only. 0 is the stock eye program, byte for byte; a scene opts in with 1 |
|
|
109
|
+
|
|
110
|
+
### Scenery
|
|
111
|
+
|
|
112
|
+
Crystals and the props built from them. Everything here is generated from the
|
|
113
|
+
seed (nothing fetched); the world layers that stand on it are under
|
|
114
|
+
[Mineral worlds](#mineral-worlds).
|
|
115
|
+
|
|
116
|
+
| Param | Type | Default | Description |
|
|
117
|
+
|-------|------|---------|-------------|
|
|
118
|
+
| propMix | string | `''` | Scenery, `kind[#id][:count][@habit][/palette][*size]` (`*6` = a tower-sized crystal, planted past the swim space). Kind `crystal` (generated, never fetched); habits `lotus · spire · druse · scatter · coral`; palettes `env · rainbow · glass ·` a named colour. Empty builds nothing |
|
|
119
|
+
| envProps | enum | off | `on` lets a named environment bring its own crystals when `propMix` is empty |
|
|
120
|
+
| crystalScale | number | 1 | Cluster size, 0.4–2.5 (rebuilds the layout) |
|
|
121
|
+
| crystalWild | number | 0.7 | How individual each cluster is: 0 is the regular measured rosette; toward 1 clusters lean, go bald on one side, grow lopsided and branch like coral |
|
|
122
|
+
| crystalGlow | number | 0.8 | Halo, glow card and the floor pools, 0–1 |
|
|
123
|
+
| crystalPulse | number | 0.3 | Slow breathing of the glow (0.12 Hz, ≤15 %) |
|
|
124
|
+
| crystalTint | number | 0 | Opt-in: fish near a cluster pick up its colour. 0 never touches a fish material |
|
|
104
125
|
|
|
105
126
|
### The room
|
|
106
127
|
|
|
@@ -144,3 +165,69 @@ never changes because a dependency was bumped.
|
|
|
144
165
|
| manifest.ts | Param space, palettes, manifest metadata (zero-dep subpath) |
|
|
145
166
|
| quality.ts | Device-tier quality caps |
|
|
146
167
|
| metaquarium.ts | Plugin factory + demo track |
|
|
168
|
+
|
|
169
|
+
## Mineral worlds
|
|
170
|
+
|
|
171
|
+
The WebGL tank has a set of independent, opt-in world parameters. They work with
|
|
172
|
+
`createMetaquarium({ params })`, the normal control track, and the playground's
|
|
173
|
+
**World** controls. They do not change the 2D SaverSpec format or the lofi renderer.
|
|
174
|
+
Every one of them defaults to off — 0, `none`, −1 for `followSpot`, empty for
|
|
175
|
+
`vignette` — except `rockVeins`, which defaults to 0.7 but only applies once
|
|
176
|
+
`rockDensity` is above zero. So existing scenes retain their composition.
|
|
177
|
+
|
|
178
|
+
| Parameter | Range | Effect |
|
|
179
|
+
| --- | --- | --- |
|
|
180
|
+
| `rockDensity` | 0–1 | Crystal-root boulders, sparse satellite rocks, a distant ridge and an open arch. Any positive value provides foundations; density adds satellites. |
|
|
181
|
+
| `rockVeins` | 0–1 (0.7) | How fractured the stone is. Fissures are cut from the rock's own facets (so they always lie on the surface), fork, carry a white-hot core, and sprout small crystals. 0 is plain stone. |
|
|
182
|
+
| `geodeHomes` | 0–3 | Geode homes: a broken boulder with an agate rind and a throat of crystal teeth, and a voxel house recessed inside — round door, lit window, lamp, steps, a chimney that vents bubbles. Habits cycle cottage / hall / tower; each home lights the floor at its door. Weak devices retain at most two. |
|
|
183
|
+
| `interior` | `none` · `geode` | Sets the scene INSIDE a geode home: crystal-lined dome with agate strata, plank floor and rug, voxel furniture, a chandelier / lamp / stove / window that light the floor and the fish. Use with the `void` environment; the default orbit camera stays indoors (keep `cameraDistance` ≲ 130). |
|
|
184
|
+
| `followSpot` | -1…23 | A follow-spot on one fish (its cast slot; -1 off): a beam from the rig, a pool of moving caustics on the floor beneath it, a light that rides with the fish, and the house lights down — a performer on a stage. `spotStrength` 0–1, `spotColor`. Closed-form: it follows the fish's own deterministic path. |
|
|
185
|
+
| `vignette` | string | A small scene for the first 2–3 fish: a preset (`tea`, `bedtime`, `seek`) or a script of beats — `a =table, b =door \| b >table @a \| a @b talk, b @a nod \| a b circle rug`. `=mark` start there, `>mark` go there, `@x` face a mark or actor, `circle x`, `follow x`, gestures `talk nod shake hop spin wiggle bow peek rest`, leading `6s:` sets a beat's length. Marks indoors: rug table bed shelf stove lamp armchair door window chest chandelier; outdoors: centre left right front back high low. Closed-form, looping; other fish swim as usual. |
|
|
186
|
+
| `floraDensity` | 0–1 | Three voxel species — kelp, reed clumps, lantern bulbs — that lean toward and take the colour of the nearest crystal; sway grows with height, a gust travels across the field, tips breathe. |
|
|
187
|
+
| `bubbleVents` | 0–1 | Bubbles in puffs from geode chimneys, fissure crowns and crystal bases; they quicken, swell and wander as they rise. |
|
|
188
|
+
| `marineSnow` | 0–1 | Slowly sinking particles sampling the crystals' coloured light field. Separate from the original single-colour `moteDensity`. |
|
|
189
|
+
|
|
190
|
+
Minerals are faceted; living flora and the inhabitants' furnishings are voxel.
|
|
191
|
+
`crystalScale` also scales the world geometry. The layers work without crystals,
|
|
192
|
+
using a seeded set of fallback anchors. With crystals, their layout supplies the
|
|
193
|
+
anchors and palette. Rock foundations lift crystal roots; floor-hugging fish use
|
|
194
|
+
scenery clearance in addition to terrain and crystal clearance. Normal depth
|
|
195
|
+
buffering lets fish pass behind the scenery; this is not a general solid-body
|
|
196
|
+
collision simulation.
|
|
197
|
+
|
|
198
|
+
```ts
|
|
199
|
+
createMetaquarium({ params: {
|
|
200
|
+
propMix: 'crystal:2@druse/hotpink,crystal:2@spire/orange',
|
|
201
|
+
rockDensity: 0.55, geodeHomes: 3, floraDensity: 0.35,
|
|
202
|
+
bubbleVents: 0.85, marineSnow: 0.6,
|
|
203
|
+
cameraDistance: 220, cameraElevation: 11, cameraAzimuth: 8,
|
|
204
|
+
fogColor: '#080718', floorColor: '#10182b',
|
|
205
|
+
} });
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
In the playground, **Metaquarium → Worlds** offers **mineral garden**, **geode
|
|
209
|
+
harbor**, and **moonlit grove**. Direct link suffixes are
|
|
210
|
+
`?saver=metaquarium-world-mineral-garden#dev`,
|
|
211
|
+
`?saver=metaquarium-world-geode-harbor#dev`, and
|
|
212
|
+
`?saver=metaquarium-world-moonlit-grove#dev`.
|
|
213
|
+
|
|
214
|
+
All animation is analytic in the scene clock (pause/seek/replay safe), with
|
|
215
|
+
independent seeded forks per layer. Geometry is built only when structural
|
|
216
|
+
parameters change. The complete world adds at most eight batched draws, no
|
|
217
|
+
textures, downloads, shadow maps or additional lights. Flora, shards, snow and
|
|
218
|
+
bubbles scale with the existing device prop budget. `inspect().props.scenery`
|
|
219
|
+
reports actual populations; prop draw-call/triangle totals include the world.
|
|
220
|
+
|
|
221
|
+
## For agents (MCP)
|
|
222
|
+
|
|
223
|
+
Everything an agent needs to author a scene is data, importable without three.js:
|
|
224
|
+
|
|
225
|
+
```ts
|
|
226
|
+
import { RECIPES, recipeTrack, PARAM_DOCS, GRAMMAR, validateMetaquariumParams } from '@idle-screens/saver-metaquarium/manifest';
|
|
227
|
+
|
|
228
|
+
const scene = RECIPES.find((r) => r.id === 'stage-duet')!;
|
|
229
|
+
publishScene({ spec: { id: 'metaquarium' }, track: recipeTrack(scene.params) });
|
|
230
|
+
validateMetaquariumParams({ vignette: 'a >home1', geodeHomes: 0 }); // → [{ path: 'vignette', message: 'beat 1: no mark "home1" …' }]
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
`PARAM_DOCS` has one line per param (a test keeps it complete), `GRAMMAR` documents the five small DSLs and the marks a vignette can name, and the playground mounts every recipe unchanged on its "recipes" shelf.
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
function qualityFor(tier) {
|
|
3
3
|
switch (tier) {
|
|
4
4
|
case "high":
|
|
5
|
-
return { maxPixelRatio: 1.25, antialias: true, fishCap: 24, pixelBudget: 18e5, moteCap: 400, envBudget: 3 };
|
|
5
|
+
return { maxPixelRatio: 1.25, antialias: true, fishCap: 24, pixelBudget: 18e5, moteCap: 400, envBudget: 3, props: { clusters: 12, shards: 32, halo: true }, glowLights: 4 };
|
|
6
6
|
case "standard":
|
|
7
|
-
return { maxPixelRatio: 1, antialias: true, fishCap: 16, pixelBudget: 12e5, moteCap: 250, envBudget: 2 };
|
|
7
|
+
return { maxPixelRatio: 1, antialias: true, fishCap: 16, pixelBudget: 12e5, moteCap: 250, envBudget: 2, props: { clusters: 8, shards: 20, halo: true }, glowLights: 3 };
|
|
8
8
|
default:
|
|
9
|
-
return { maxPixelRatio: 1, antialias: false, fishCap: 8, pixelBudget: 9e5, moteCap: 120, envBudget: 1 };
|
|
9
|
+
return { maxPixelRatio: 1, antialias: false, fishCap: 8, pixelBudget: 9e5, moteCap: 120, envBudget: 1, props: { clusters: 4, shards: 12, halo: false }, glowLights: 0 };
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
12
|
function effectivePixelRatio(width, height, dpr, q) {
|
|
@@ -25,6 +25,7 @@ function probeSoftwareGL() {
|
|
|
25
25
|
const renderer = String(
|
|
26
26
|
dbg ? gl.getParameter(dbg.UNMASKED_RENDERER_WEBGL) : gl.getParameter(gl.RENDERER)
|
|
27
27
|
);
|
|
28
|
+
gl.getExtension("WEBGL_lose_context")?.loseContext();
|
|
28
29
|
return isSoftwareGL(renderer);
|
|
29
30
|
}
|
|
30
31
|
|
|
@@ -33,4 +34,4 @@ export {
|
|
|
33
34
|
effectivePixelRatio,
|
|
34
35
|
probeSoftwareGL
|
|
35
36
|
};
|
|
36
|
-
//# sourceMappingURL=chunk-
|
|
37
|
+
//# sourceMappingURL=chunk-L27AMQZ4.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/quality.ts"],"sourcesContent":["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 /** Budget for environment layers (see environments.ts LAYER_COST): the\n * ceiling costs 1, rays 2, terrain is free. A weak device keeps the\n * ceiling and loses the rays. */\n envBudget: number;\n /** Scenery budget: clusters in the scene, shards in one cluster, and\n * whether shards get their halo shell (it doubles their draw calls). */\n props: { clusters: number; shards: number; halo: boolean };\n /** Point lights that ride glow parts in lit mode. Every one is a term in\n * every lit fragment, so the weakest tier lights by key + fill alone. */\n glowLights: 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, envBudget: 3, props: { clusters: 12, shards: 32, halo: true }, glowLights: 4 };\n case 'standard':\n return { maxPixelRatio: 1, antialias: true, fishCap: 16, pixelBudget: 1_200_000, moteCap: 250, envBudget: 2, props: { clusters: 8, shards: 20, halo: true }, glowLights: 3 };\n default:\n return { maxPixelRatio: 1, antialias: false, fishCap: 8, pixelBudget: 900_000, moteCap: 120, envBudget: 1, props: { clusters: 4, shards: 12, halo: false }, glowLights: 0 };\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 // This scratch context is never used again — lose it explicitly rather than\n // leaving it for GC. Harmless on its own, but every tank mount calls this\n // before creating its real WebGLRenderer, and now also builds a per-mount\n // PMREM environment (studio.ts) that is heavy enough to pile up un-GC'd\n // contexts faster than Chrome reclaims them, tipping repeated mount/dispose\n // cycles over the browser's live-context cap (MQ3).\n gl.getExtension('WEBGL_lose_context')?.loseContext();\n return isSoftwareGL(renderer);\n}\n"],"mappings":";AAuBO,SAAS,WAAW,MAAmC;AAC5D,UAAQ,MAAM;AAAA,IACZ,KAAK;AACH,aAAO,EAAE,eAAe,MAAM,WAAW,MAAM,SAAS,IAAI,aAAa,MAAW,SAAS,KAAK,WAAW,GAAG,OAAO,EAAE,UAAU,IAAI,QAAQ,IAAI,MAAM,KAAK,GAAG,YAAY,EAAE;AAAA,IACjL,KAAK;AACH,aAAO,EAAE,eAAe,GAAG,WAAW,MAAM,SAAS,IAAI,aAAa,MAAW,SAAS,KAAK,WAAW,GAAG,OAAO,EAAE,UAAU,GAAG,QAAQ,IAAI,MAAM,KAAK,GAAG,YAAY,EAAE;AAAA,IAC7K;AACE,aAAO,EAAE,eAAe,GAAG,WAAW,OAAO,SAAS,GAAG,aAAa,KAAS,SAAS,KAAK,WAAW,GAAG,OAAO,EAAE,UAAU,GAAG,QAAQ,IAAI,MAAM,MAAM,GAAG,YAAY,EAAE;AAAA,EAC9K;AACF;AAEO,SAAS,oBACd,OACA,QACA,KACA,GACQ;AACR,QAAM,OAAO,KAAK,IAAI,GAAG,QAAQ,MAAM;AACvC,SAAO,KAAK,IAAI,KAAK,KAAK,IAAI,KAAK,EAAE,eAAe,KAAK,KAAK,EAAE,cAAc,IAAI,CAAC,CAAC;AACtF;AAEO,SAAS,aAAa,UAA2B;AACtD,SAAO,yCAAyC,KAAK,QAAQ;AAC/D;AAIO,SAAS,kBAA2B;AACzC,MAAI,OAAO,aAAa,YAAa,QAAO;AAC5C,QAAM,SAAS,SAAS,cAAc,QAAQ;AAC9C,QAAM,KAAK,OAAO,WAAW,QAAQ,KAAK,OAAO,WAAW,OAAO;AACnE,MAAI,CAAC,GAAI,QAAO;AAChB,QAAM,MAAM,GAAG,aAAa,2BAA2B;AACvD,QAAM,WAAW;AAAA,IACf,MAAM,GAAG,aAAa,IAAI,uBAAuB,IAAI,GAAG,aAAa,GAAG,QAAQ;AAAA,EAClF;AAOA,KAAG,aAAa,oBAAoB,GAAG,YAAY;AACnD,SAAO,aAAa,QAAQ;AAC9B;","names":[]}
|