@idle-screens/saver-metaquarium 0.20.0 → 0.21.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/dist/{chunk-IIYEE6GT.js → chunk-5DJ5WBVN.js} +297 -62
- package/dist/chunk-5DJ5WBVN.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +3 -3
- package/dist/{lofi-tank-MZHA7IRP.js → lofi-tank-Z2GDMEPV.js} +2 -2
- package/dist/{manifest-BzLzMuur.d.ts → manifest-CiE6U104.d.ts} +49 -4
- package/dist/manifest.d.ts +1 -1
- package/dist/manifest.js +1 -1
- package/dist/{tank-7WG3RET5.js → tank-3CC56AJN.js} +1064 -425
- package/dist/tank-3CC56AJN.js.map +1 -0
- package/package.json +2 -2
- package/dist/chunk-IIYEE6GT.js.map +0 -1
- package/dist/tank-7WG3RET5.js.map +0 -1
- /package/dist/{lofi-tank-MZHA7IRP.js.map → lofi-tank-Z2GDMEPV.js.map} +0 -0
|
@@ -107,7 +107,10 @@ var SWIM_STYLES = [
|
|
|
107
107
|
// is — the follower samples the SAME closed form at `d - lag`.
|
|
108
108
|
{ name: "follow", label: "Follower", speedMul: 1, band: "free", bobAmp: 1.2, bobHz: 0.5, formation: false, travel: 1, bond: "follow" },
|
|
109
109
|
{ name: "pair", label: "Pair", speedMul: 0.8, band: "free", bobAmp: 2.5, bobHz: 0.35, formation: false, travel: 1, bond: "pair" },
|
|
110
|
-
{ name: "chase", label: "Chaser", speedMul: 1, band: "free", bobAmp: 1, bobHz: 0.6, formation: false, travel: 1, bond: "chase" }
|
|
110
|
+
{ name: "chase", label: "Chaser", speedMul: 1, band: "free", bobAmp: 1, bobHz: 0.6, formation: false, travel: 1, bond: "chase" },
|
|
111
|
+
// A seat in a vignette's audience (vignette.ts crowdPose): the script places it, out front facing the
|
|
112
|
+
// stage. With no vignette playing there is no show to watch, and it hovers.
|
|
113
|
+
{ name: "audience", label: "Audience", speedMul: 0.55, band: "free", bobAmp: 4, bobHz: 0.3, formation: false, travel: 0.12 }
|
|
111
114
|
];
|
|
112
115
|
var AUTO_STYLE_BY_BREED = {
|
|
113
116
|
angelfish: "school",
|
|
@@ -206,12 +209,12 @@ function formationSlot(index, count, variance, halfWidth = FORMATION_HALF_WIDTH,
|
|
|
206
209
|
const FILE = 6;
|
|
207
210
|
const files = Math.ceil(Math.max(1, count) / FILE);
|
|
208
211
|
const col2 = Math.floor(index / FILE);
|
|
209
|
-
const
|
|
212
|
+
const within2 = index % FILE;
|
|
210
213
|
const fileLen = Math.min(FILE, count - col2 * FILE);
|
|
211
214
|
return {
|
|
212
215
|
side: (col2 - (files - 1) / 2) * L * 1.6 + (fishHash(index, 4) - 0.5) * L * 0.4 * j,
|
|
213
216
|
up: (fishHash(index, 5) - 0.5) * L * 0.5 * j,
|
|
214
|
-
back: (
|
|
217
|
+
back: (within2 - (fileLen - 1) / 2) * L * 1.7
|
|
215
218
|
};
|
|
216
219
|
}
|
|
217
220
|
if (shape === "ring") {
|
|
@@ -236,9 +239,9 @@ function formationSlot(index, count, variance, halfWidth = FORMATION_HALF_WIDTH,
|
|
|
236
239
|
const VSIZE = 9;
|
|
237
240
|
const layers = Math.ceil(Math.max(1, count) / VSIZE);
|
|
238
241
|
const layer = Math.floor(index / VSIZE);
|
|
239
|
-
const
|
|
240
|
-
const rank =
|
|
241
|
-
const wing =
|
|
242
|
+
const within2 = index % VSIZE;
|
|
243
|
+
const rank = within2 + 1 >> 1;
|
|
244
|
+
const wing = within2 === 0 ? 0 : within2 % 2 === 1 ? -1 : 1;
|
|
242
245
|
const droop = (rank - 2) * L * (layers === 1 ? 0.24 : 0.12);
|
|
243
246
|
const pitch2 = layers === 1 ? 1.5 : 1.2;
|
|
244
247
|
const jit2 = layers === 1 ? 0.2 : 0.15;
|
|
@@ -1415,24 +1418,26 @@ var DEFAULT_COLORS = ["#fff2cf", "#ff8ad0", "#7fdcff"];
|
|
|
1415
1418
|
function parseSpotRig(value) {
|
|
1416
1419
|
const spots = [], problems = [];
|
|
1417
1420
|
for (const raw of value.split(",").map((s) => s.trim()).filter(Boolean)) {
|
|
1418
|
-
const m = /^(\d{1,2})(?:\/(#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})))?(?:\*(\d+(?:\.\d+)?))?$/.exec(raw);
|
|
1421
|
+
const m = /^(?:(\d{1,2})|@([a-z][a-z0-9]{0,15}))(?:\/(#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})))?(?:\*(\d+(?:\.\d+)?))?$/.exec(raw);
|
|
1419
1422
|
if (!m) {
|
|
1420
|
-
problems.push(`"${raw}" is not slot[/color][*radius]`);
|
|
1423
|
+
problems.push(`"${raw}" is not slot[/color][*radius] or @mark[/color][*radius]`);
|
|
1421
1424
|
continue;
|
|
1422
1425
|
}
|
|
1423
1426
|
if (spots.length >= MAX_SPOTS) {
|
|
1424
1427
|
problems.push(`more than ${MAX_SPOTS} spots \u2014 "${raw}" dropped`);
|
|
1425
1428
|
continue;
|
|
1426
1429
|
}
|
|
1427
|
-
const
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
+
const mark = m[2];
|
|
1431
|
+
const slot = mark ? -1 : Math.min(23, Number(m[1]));
|
|
1432
|
+
if (mark ? spots.some((spot2) => spot2.mark === mark) : spots.some((spot2) => spot2.slot === slot)) {
|
|
1433
|
+
problems.push(`${mark ? `mark ${mark}` : `slot ${slot}`} is already spotted \u2014 "${raw}" dropped`);
|
|
1430
1434
|
continue;
|
|
1431
1435
|
}
|
|
1432
1436
|
spots.push({
|
|
1433
1437
|
slot,
|
|
1434
|
-
|
|
1435
|
-
|
|
1438
|
+
...mark ? { mark } : {},
|
|
1439
|
+
color: m[3] ?? DEFAULT_COLORS[spots.length],
|
|
1440
|
+
radius: Math.min(60, Math.max(12, m[4] ? Number(m[4]) : 28))
|
|
1436
1441
|
});
|
|
1437
1442
|
}
|
|
1438
1443
|
return { spots, problems };
|
|
@@ -2506,30 +2511,32 @@ var OPEN_MARKS = {
|
|
|
2506
2511
|
front: { x: 0, y: 30, z: 60 },
|
|
2507
2512
|
back: { x: 0, y: 40, z: -70 },
|
|
2508
2513
|
high: { x: 10, y: 62, z: -10 },
|
|
2509
|
-
low: { x: -10, y: 18, z: 10 }
|
|
2514
|
+
low: { x: -10, y: 18, z: 10 },
|
|
2515
|
+
// Behind the mic stand (`stageSet: club` stands it here, facing front).
|
|
2516
|
+
mic: { x: 0, y: 36, z: -4, solo: true }
|
|
2510
2517
|
};
|
|
2511
|
-
var ACTOR = "
|
|
2518
|
+
var ACTOR = "abcd";
|
|
2512
2519
|
var CRUISE = 17;
|
|
2513
2520
|
var STANDOFF = 20;
|
|
2514
2521
|
function spot(mark, actor, actors) {
|
|
2515
|
-
if (actors === 1) return mark;
|
|
2522
|
+
if (actors === 1 || mark.solo) return mark;
|
|
2516
2523
|
const a = actor / actors * Math.PI * 2 + 0.6;
|
|
2517
2524
|
return { x: mark.x + Math.cos(a) * STANDOFF, y: mark.y + (actor % 2 ? 2.5 : 0), z: mark.z + Math.sin(a) * STANDOFF };
|
|
2518
2525
|
}
|
|
2519
|
-
function parseVignette(script, marks) {
|
|
2526
|
+
function parseVignette(script, marks, audience = []) {
|
|
2520
2527
|
const problems = [];
|
|
2521
2528
|
const raw = String(script ?? "").split(/[|\n]/).map((b) => b.trim()).filter(Boolean);
|
|
2522
2529
|
let actors = 0;
|
|
2523
2530
|
for (const beat of raw) {
|
|
2524
2531
|
for (const cue of beat.replace(/^\d+(?:\.\d+)?s\s{0,16}:/, "").split(",")) {
|
|
2525
2532
|
for (const word of cue.trim().split(/\s+/)) {
|
|
2526
|
-
if (!/^[
|
|
2533
|
+
if (!/^[abcd]$/.test(word)) break;
|
|
2527
2534
|
actors = Math.max(actors, ACTOR.indexOf(word) + 1);
|
|
2528
2535
|
}
|
|
2529
2536
|
}
|
|
2530
2537
|
}
|
|
2531
|
-
if (!raw.length || !actors) return { actors: 0, beats: [], duration: 0, problems: raw.length ? ["no actor (a, b, c) named"] : [] };
|
|
2532
2538
|
const centre = marks.centre ?? marks.rug ?? OPEN_MARKS.centre;
|
|
2539
|
+
if (!raw.length || !actors) return { actors: 0, beats: [], duration: 0, problems: raw.length ? ["no actor (a\u2013d) named"] : [], seats: /* @__PURE__ */ new Map(), centre };
|
|
2533
2540
|
let at = Array.from({ length: actors }, (_, i) => spot(centre, i, actors));
|
|
2534
2541
|
const start = [];
|
|
2535
2542
|
const beats = [];
|
|
@@ -2547,7 +2554,7 @@ function parseVignette(script, marks) {
|
|
|
2547
2554
|
for (const cue of body.split(",").map((c) => c.trim()).filter(Boolean)) {
|
|
2548
2555
|
const words = cue.split(/\s+/);
|
|
2549
2556
|
const who = [];
|
|
2550
|
-
while (words.length && /^[
|
|
2557
|
+
while (words.length && /^[abcd]$/.test(words[0])) who.push(ACTOR.indexOf(words.shift()));
|
|
2551
2558
|
if (!who.length) {
|
|
2552
2559
|
problems.push(`beat ${bi + 1}: "${cue}" names no actor`);
|
|
2553
2560
|
continue;
|
|
@@ -2555,7 +2562,7 @@ function parseVignette(script, marks) {
|
|
|
2555
2562
|
for (let w = 0; w < words.length; w += 1) {
|
|
2556
2563
|
const word = words[w];
|
|
2557
2564
|
const target = (name) => {
|
|
2558
|
-
if (/^[
|
|
2565
|
+
if (/^[abcd]$/.test(name) && ACTOR.indexOf(name) < actors) return { actor: ACTOR.indexOf(name), point: null };
|
|
2559
2566
|
const mk = marks[name];
|
|
2560
2567
|
if (mk) return { actor: null, point: mk };
|
|
2561
2568
|
problems.push(`beat ${bi + 1}: no mark or actor "${name}" (marks: ${Object.keys(marks).join(", ")})`);
|
|
@@ -2627,7 +2634,9 @@ function parseVignette(script, marks) {
|
|
|
2627
2634
|
beats.push({ t0, dur: length, actors: start.map((p) => ({ to: p, moved: true, faceActor: null, facePoint: null, gesture: null, circle: null, follow: null })) });
|
|
2628
2635
|
t0 += length;
|
|
2629
2636
|
}
|
|
2630
|
-
|
|
2637
|
+
const seats = /* @__PURE__ */ new Map();
|
|
2638
|
+
for (const slot of audience) if (slot >= actors && !seats.has(slot)) seats.set(slot, seats.size);
|
|
2639
|
+
return { actors, beats, duration: t0, problems, seats, centre };
|
|
2631
2640
|
}
|
|
2632
2641
|
var smoother = (u) => {
|
|
2633
2642
|
const c = Math.min(1, Math.max(0, u));
|
|
@@ -2682,7 +2691,10 @@ function poseAcross(v, actor, tSec, dt, own) {
|
|
|
2682
2691
|
return poseOf(v, actor, snapBetween(v, actor, tSec, tSec + dt) ? tSec : tSec + dt, own);
|
|
2683
2692
|
}
|
|
2684
2693
|
function poseOf(v, actor, tSec, own) {
|
|
2685
|
-
if (
|
|
2694
|
+
if (!v.duration) return null;
|
|
2695
|
+
const seat = v.seats.get(actor);
|
|
2696
|
+
if (seat !== void 0) return crowdPose(v, seat, tSec, own);
|
|
2697
|
+
if (actor >= v.actors) return null;
|
|
2686
2698
|
const t = (tSec % v.duration + v.duration) % v.duration;
|
|
2687
2699
|
let bi = v.beats.findIndex((b) => t < b.t0 + b.dur);
|
|
2688
2700
|
if (bi < 0) bi = v.beats.length - 1;
|
|
@@ -2819,6 +2831,152 @@ function poseOf(v, actor, tSec, own) {
|
|
|
2819
2831
|
}
|
|
2820
2832
|
return { x, y: Math.max(9, y), z, fx, fy, fz, roll, doing, lookAt: target ?? null, ...gesture ? { gesture } : {} };
|
|
2821
2833
|
}
|
|
2834
|
+
var CROWD = {
|
|
2835
|
+
/** Rows out from the stage's middle, front (nearest the camera) first: distance, height, seats.
|
|
2836
|
+
* Raked: each row higher than the one before, so the back rows see over the front. */
|
|
2837
|
+
rows: [{ r: 48, y: 15, n: 6 }, { r: 64, y: 21, n: 8 }, { r: 80, y: 27, n: 10 }],
|
|
2838
|
+
/** How far round each row reaches, radians either side of straight out front. */
|
|
2839
|
+
arc: 0.7,
|
|
2840
|
+
/** A laugh: it breaks this long before the talk ends, and rolls on this long. */
|
|
2841
|
+
lead: 0.45,
|
|
2842
|
+
laugh: 2.8,
|
|
2843
|
+
/** Seconds a seat can be late to a laugh or the applause. */
|
|
2844
|
+
late: 0.55,
|
|
2845
|
+
/** One seat in this many is a tough crowd: it chuckles where the rest laugh. */
|
|
2846
|
+
tough: 5,
|
|
2847
|
+
/** Seconds the room's heads take to follow the stage: they look where it was, a moment ago. */
|
|
2848
|
+
follow: 1.05
|
|
2849
|
+
};
|
|
2850
|
+
var seatHash = (seat, k = 0) => {
|
|
2851
|
+
const s = Math.sin(seat * 127.1 + k * 311.7 + 74.7) * 43758.5453;
|
|
2852
|
+
return s - Math.floor(s);
|
|
2853
|
+
};
|
|
2854
|
+
function crowdSeat(v, seat) {
|
|
2855
|
+
let row = 0, first = 0;
|
|
2856
|
+
while (row < CROWD.rows.length - 1 && seat >= first + CROWD.rows[row].n) {
|
|
2857
|
+
first += CROWD.rows[row].n;
|
|
2858
|
+
row += 1;
|
|
2859
|
+
}
|
|
2860
|
+
const r = CROWD.rows[row];
|
|
2861
|
+
const inRow = Math.min(r.n, v.seats.size - first);
|
|
2862
|
+
const mid = (inRow - 1) / 2;
|
|
2863
|
+
const order = Array.from({ length: inRow }, (_, p) => p).sort((p, q) => Math.abs(p - mid) - Math.abs(q - mid) || q - p);
|
|
2864
|
+
const i = order[seat - first] ?? seat - first;
|
|
2865
|
+
const step = CROWD.arc * 2 / Math.max(1, r.n - 1);
|
|
2866
|
+
const a = (i - (inRow - 1) / 2) * step + (seatHash(seat, 1) - 0.5) * step * 0.3;
|
|
2867
|
+
const d = r.r + (seatHash(seat, 2) - 0.5) * 5;
|
|
2868
|
+
return { x: v.centre.x + Math.sin(a) * d, y: r.y + (seatHash(seat, 3) - 0.5) * 3, z: v.centre.z + Math.cos(a) * d };
|
|
2869
|
+
}
|
|
2870
|
+
var REACTIONS = /* @__PURE__ */ new WeakMap();
|
|
2871
|
+
function reactionsOf(v) {
|
|
2872
|
+
let r = REACTIONS.get(v);
|
|
2873
|
+
if (r) return r;
|
|
2874
|
+
r = { laughs: [], claps: [] };
|
|
2875
|
+
for (const b of v.beats) {
|
|
2876
|
+
if (b.actors.some((a) => a.gesture === "talk")) r.laughs.push(b.t0 + b.dur);
|
|
2877
|
+
if (b.actors.some((a) => a.gesture === "bow")) r.claps.push([b.t0, b.t0 + b.dur]);
|
|
2878
|
+
}
|
|
2879
|
+
REACTIONS.set(v, r);
|
|
2880
|
+
return r;
|
|
2881
|
+
}
|
|
2882
|
+
function holderAt(v, tSec) {
|
|
2883
|
+
const t = (tSec % v.duration + v.duration) % v.duration;
|
|
2884
|
+
const beat = v.beats.find((b) => t < b.t0 + b.dur) ?? v.beats[v.beats.length - 1];
|
|
2885
|
+
const talking = beat.actors.findIndex((a) => a.gesture === "talk");
|
|
2886
|
+
if (talking >= 0) return talking;
|
|
2887
|
+
let best = 0, bestD = Infinity;
|
|
2888
|
+
beat.actors.forEach((a, i) => {
|
|
2889
|
+
const d = Math.hypot(a.to.x - v.centre.x, a.to.z - v.centre.z);
|
|
2890
|
+
if (d < bestD) {
|
|
2891
|
+
bestD = d;
|
|
2892
|
+
best = i;
|
|
2893
|
+
}
|
|
2894
|
+
});
|
|
2895
|
+
return best;
|
|
2896
|
+
}
|
|
2897
|
+
function within(v, t, start, len, out) {
|
|
2898
|
+
for (const shift of [-1, 0, 1]) {
|
|
2899
|
+
const u = (t - start - shift * v.duration) / len;
|
|
2900
|
+
if (u > 0 && u < 1) {
|
|
2901
|
+
if (out) out.shift = shift;
|
|
2902
|
+
return u;
|
|
2903
|
+
}
|
|
2904
|
+
}
|
|
2905
|
+
return -1;
|
|
2906
|
+
}
|
|
2907
|
+
function crowdPose(v, seat, tSec, own) {
|
|
2908
|
+
const p = crowdSeat(v, seat);
|
|
2909
|
+
const t = (tSec % v.duration + v.duration) % v.duration;
|
|
2910
|
+
let gx = 0, gy = 0, gz = 0;
|
|
2911
|
+
const taps = 4;
|
|
2912
|
+
for (let k = 0; k < taps; k++) {
|
|
2913
|
+
const tk = tSec - k / (taps - 1) * CROWD.follow;
|
|
2914
|
+
const m = placeOf(v, holderAt(v, tk), tk);
|
|
2915
|
+
gx += m.x / taps;
|
|
2916
|
+
gy += m.y / taps;
|
|
2917
|
+
gz += m.z / taps;
|
|
2918
|
+
}
|
|
2919
|
+
const look = { x: gx, y: gy, z: gz };
|
|
2920
|
+
let fx = gx - p.x, fy = (gy - p.y) * 0.4, fz = gz - p.z;
|
|
2921
|
+
let fl = Math.hypot(fx, fy, fz) || 1;
|
|
2922
|
+
fx /= fl;
|
|
2923
|
+
fy /= fl;
|
|
2924
|
+
fz /= fl;
|
|
2925
|
+
const { x, z } = p;
|
|
2926
|
+
let y = p.y + Math.sin(tSec * 1.1 + seat * 2.3) * 0.7;
|
|
2927
|
+
let roll = Math.sin(tSec * 0.7 + seat) * 0.03;
|
|
2928
|
+
let pitch = 0;
|
|
2929
|
+
let doing = "watching";
|
|
2930
|
+
let gesture;
|
|
2931
|
+
const { laughs, claps } = reactionsOf(v);
|
|
2932
|
+
const late = seatHash(seat, 4) * CROWD.late;
|
|
2933
|
+
let lu = -1, lat = 0, lk = 0;
|
|
2934
|
+
const loop = { shift: 0 }, lap = Math.floor(tSec / v.duration);
|
|
2935
|
+
let burst;
|
|
2936
|
+
for (let k = 0; k < laughs.length; k++) {
|
|
2937
|
+
const at = laughs[k];
|
|
2938
|
+
const len = CROWD.laugh * (0.8 + 0.45 * seatHash(seat, 10 + k));
|
|
2939
|
+
const u = within(v, t, at - CROWD.lead + late, len, loop);
|
|
2940
|
+
if (u > lu) {
|
|
2941
|
+
lu = u;
|
|
2942
|
+
lat = u * len;
|
|
2943
|
+
lk = k;
|
|
2944
|
+
}
|
|
2945
|
+
}
|
|
2946
|
+
if (lu > 0) {
|
|
2947
|
+
const e = Math.min(1, lu * 6) * (1 - smoother((lu - 0.35) / 0.65));
|
|
2948
|
+
const tough = seatHash(seat, 5) < 1 / CROWD.tough;
|
|
2949
|
+
const amp = tough ? 0.4 : 0.75 + 0.5 * seatHash(seat, 6);
|
|
2950
|
+
const hz = 5.5 + 2.5 * seatHash(seat, 7);
|
|
2951
|
+
doing = "laughing";
|
|
2952
|
+
pitch = (0.3 + Math.sin(lat * hz * 2) * 0.1) * e * amp;
|
|
2953
|
+
y += Math.abs(Math.sin(lat * hz)) * 3.4 * e * amp;
|
|
2954
|
+
roll += Math.sin(lat * hz) * 0.16 * e * amp;
|
|
2955
|
+
if (own?.("talk")) gesture = { name: "talk", at: lat, bar: lat, weight: e * amp };
|
|
2956
|
+
if (!tough) {
|
|
2957
|
+
const start = laughs[lk] - CROWD.lead + late + (lap + loop.shift) * v.duration;
|
|
2958
|
+
burst = { key: `crowd:${seat}:${lk}:${lap + loop.shift}`, t: start + 0.25 };
|
|
2959
|
+
}
|
|
2960
|
+
}
|
|
2961
|
+
for (const [a, b] of claps) {
|
|
2962
|
+
const u = within(v, t, a + late, b - a);
|
|
2963
|
+
if (u <= 0) continue;
|
|
2964
|
+
const e = bell(u) ** 0.6, ct = u * (b - a);
|
|
2965
|
+
doing = "applauding";
|
|
2966
|
+
y += Math.abs(Math.sin(ct * 6.5)) * 1.3 * e;
|
|
2967
|
+
roll += Math.sin(ct * 13) * 0.22 * e;
|
|
2968
|
+
if (own?.("wiggle")) gesture = { name: "wiggle", at: ct, bar: ct, weight: e };
|
|
2969
|
+
break;
|
|
2970
|
+
}
|
|
2971
|
+
if (pitch) {
|
|
2972
|
+
fy += Math.tan(pitch) * Math.hypot(fx, fz);
|
|
2973
|
+
fl = Math.hypot(fx, fy, fz) || 1;
|
|
2974
|
+
fx /= fl;
|
|
2975
|
+
fy /= fl;
|
|
2976
|
+
fz /= fl;
|
|
2977
|
+
}
|
|
2978
|
+
return { x, y: Math.max(9, y), z, fx, fy, fz, roll, doing, lookAt: look, ...gesture ? { gesture } : {}, ...burst ? { burst } : {} };
|
|
2979
|
+
}
|
|
2822
2980
|
var polar = (deg, r, y) => ({ x: Math.cos(deg * Math.PI / 180) * r, y, z: Math.sin(deg * Math.PI / 180) * r });
|
|
2823
2981
|
var INTERIOR_MARK_POLAR = {
|
|
2824
2982
|
door: { deg: 28, r: 124, y: 22 },
|
|
@@ -2909,11 +3067,48 @@ var VIGNETTES = {
|
|
|
2909
3067
|
"6s: a @c bow, b @c bow, c >front bow",
|
|
2910
3068
|
"5s: a >left, b >right, c @front shake",
|
|
2911
3069
|
"5s: c >back"
|
|
3070
|
+
].join(" | "),
|
|
3071
|
+
// Open mic: a host (d) brings on three comics (a b c) in turn — each two bits and a bow — and sends them off;
|
|
3072
|
+
// the two waiting stand in the wings and watch whoever has the mic, nodding along when the room laughs. Then
|
|
3073
|
+
// a goodnight from the host, and the company bows from where it stands. Written for an `@audience` out front (it laughs as each talk
|
|
3074
|
+
// ends), `stageSet: club` and one spot standing on the mic (`spotRig: @mic`): whoever steps up is lit.
|
|
3075
|
+
// openmic cues: 4s:-, 5s:a, 5s:a, 3s:a, 5s:a, 6s:a, 3s:a, 6s:a, 3s:a, 5s:a, 4s:a, 3s:a, 5s:a, 6s:a, 3s:a, 6s:a, 3s:a, 5s:a, 4s:a, 3s:a, 5s:a, 6s:a, 3s:a, 6s:a, 3s:a, 5s:a, 5s:a, 4s:a, 5s:-
|
|
3076
|
+
openmic: [
|
|
3077
|
+
"4s: a =left, b =right, c =left, d =right",
|
|
3078
|
+
"5s: d >mic @front, a @mic nod, b @mic nod, c @mic nod",
|
|
3079
|
+
"5s: d @front talk, a @mic, b @mic, c @mic",
|
|
3080
|
+
"3s: d @a wiggle, a @mic nod, b @mic nod, c @mic nod",
|
|
3081
|
+
"5s: d >right, a >mic @front, b @mic, c @mic",
|
|
3082
|
+
"6s: a @front talk, b @mic, c @mic, d @mic nod",
|
|
3083
|
+
"3s: a @front nod, b @mic nod, c @mic nod, d @mic nod",
|
|
3084
|
+
"6s: a @front talk, b @mic, c @mic, d @mic",
|
|
3085
|
+
"3s: a @front bow, b @mic nod, c @mic nod, d @mic nod",
|
|
3086
|
+
"5s: a >left, d >mic @front, b @mic, c @mic",
|
|
3087
|
+
"4s: d @front talk, a @mic nod, b @mic, c @mic",
|
|
3088
|
+
"3s: d @b wiggle, a @mic, b @mic nod, c @mic",
|
|
3089
|
+
"5s: d >right, b >mic @front, a @mic, c @mic",
|
|
3090
|
+
"6s: b @front talk, a @mic, c @mic, d @mic nod",
|
|
3091
|
+
"3s: b @front rest, a @mic nod, c @mic nod, d @mic nod",
|
|
3092
|
+
"6s: b @front talk, a @mic, c @mic, d @mic",
|
|
3093
|
+
"3s: b @front bow, a @mic nod, c @mic nod, d @mic nod",
|
|
3094
|
+
"5s: b >right, d >mic @front, a @mic, c @mic",
|
|
3095
|
+
"4s: d @front talk, a @mic, b @mic nod, c @mic",
|
|
3096
|
+
"3s: d @c wiggle, a @mic, b @mic, c @mic nod",
|
|
3097
|
+
"5s: d >right, c >mic @front, a @mic, b @mic",
|
|
3098
|
+
"6s: c @front talk, a @mic, b @mic, d @mic nod",
|
|
3099
|
+
"3s: c @front shake, a @mic nod, b @mic nod, d @mic nod",
|
|
3100
|
+
"6s: c @front talk, a @mic, b @mic, d @mic",
|
|
3101
|
+
"3s: c @front bow, a @mic nod, b @mic nod, d @mic nod",
|
|
3102
|
+
"5s: c >left, d >mic @front, a @mic, b @mic",
|
|
3103
|
+
"5s: d @front talk, a @mic nod, b @mic, c @mic nod",
|
|
3104
|
+
"4s: a @front bow, b @front bow, c @front bow, d @front bow",
|
|
3105
|
+
"5s: d >right, a @mic, b @mic, c @mic"
|
|
2912
3106
|
].join(" | ")
|
|
2913
3107
|
};
|
|
2914
3108
|
var VIGNETTE_CUES = {
|
|
2915
3109
|
duet: "4s:-, 7s:a, 5s:a, 7s:b, 5s:b, 6s:a+b, 8s:a+b, 6s:a+b, 5s:a+b",
|
|
2916
|
-
trio: "4s:-, 6s:a, 6s:b, 6s:c, 7s:a+b, 7s:b+c, 9s:a+b+c, 6s:a+b+c, 5s:c, 5s:-"
|
|
3110
|
+
trio: "4s:-, 6s:a, 6s:b, 6s:c, 7s:a+b, 7s:b+c, 9s:a+b+c, 6s:a+b+c, 5s:c, 5s:-",
|
|
3111
|
+
openmic: "4s:-, 5s:a, 5s:a, 3s:a, 5s:a, 6s:a, 3s:a, 6s:a, 3s:a, 5s:a, 4s:a, 3s:a, 5s:a, 6s:a, 3s:a, 6s:a, 3s:a, 5s:a, 4s:a, 3s:a, 5s:a, 6s:a, 3s:a, 6s:a, 3s:a, 5s:a, 5s:a, 4s:a, 5s:-"
|
|
2917
3112
|
};
|
|
2918
3113
|
function resolveVignette(value) {
|
|
2919
3114
|
const v = String(value ?? "").trim();
|
|
@@ -2926,11 +3121,11 @@ var PARAM_DOCS = {
|
|
|
2926
3121
|
cameraAzimuth: "Orbit bearing in degrees. 0 looks at the village from the front.",
|
|
2927
3122
|
cameraElevation: "Degrees above the floor. 8\u201315 is eye level with the fish; 40+ looks down on a castle.",
|
|
2928
3123
|
cameraDistance: "Distance from the tank centre. 80 is a close-up, 170\u2013220 a village, 300+ a landmark.",
|
|
2929
|
-
shot: "A
|
|
3124
|
+
shot: "A framing: `orbit` (default), `hero` (three-quarter), `front` (level, long lens), `low` (looking up), `top` (a map), `surface` (the underside), `macro` (nearest landmark), `cast` (the mix's first three). Azimuth and autoRotate turn it, distance scales it.",
|
|
2930
3125
|
shotBlend: "Seconds a change of `shot` takes (0 = a cut): the camera moves to the new framing, eased.",
|
|
2931
|
-
cameraFollow: "Ride with one fish (cast slot
|
|
3126
|
+
cameraFollow: "Ride with one fish (cast slot); -1 is the orbit camera, whose params are ignored while following. With `followSpot` on the slot, a spotlit chase.",
|
|
2932
3127
|
followAngle: "Where round the followed fish the chase camera stands, degrees: 0 behind (default), 90 beside, 180 in front looking back at its face.",
|
|
2933
|
-
followDistance: "How far behind the followed fish the camera rides (40\u201370 is a chase). Under ~18 it is the fish's eye
|
|
3128
|
+
followDistance: "How far behind the followed fish the camera rides (40\u201370 is a chase). Under ~18 it is the fish's eye; a floor walker sees from its own head height.",
|
|
2934
3129
|
autoRotate: "Turntable speed, degrees/second. 0 holds the shot (stages, vignettes); 1\u20132 is a slow reveal. frame.state.camera.bearing: where it stands now.",
|
|
2935
3130
|
followAvoid: "Keep the followed fish in view, 0\u20131: the chase camera rises over low clutter or pulls in, eased. For walks through a village.",
|
|
2936
3131
|
nearFade: "A creature at the lens dissolves instead of being sliced, 0\u20131. The followed fish never fades.",
|
|
@@ -2942,7 +3137,7 @@ var PARAM_DOCS = {
|
|
|
2942
3137
|
ambientOcclusion: "Ambient occlusion, 0\u20131: crevices darken. High tier.",
|
|
2943
3138
|
highlights: "Bright light rolls off instead of clipping, 0\u20131: a fish in a shaft stays a fish. Bright rooms want it. Mid/high tiers.",
|
|
2944
3139
|
fishUrl: "The single model used when `fishMix` is empty: ipfs://, https://assets.idlescreens.com/, or a URL on the page's own origin. Any other host is refused and the default fish swims.",
|
|
2945
|
-
fishMix: "The cast: `id[:count][@style]`, comma-separated: `100:2@drift, 257:1, seahorse:3, shark:1@patrol`. Minted breeds plus bundled
|
|
3140
|
+
fishMix: "The cast: `id[:count][@style]`, comma-separated: `100:2@drift, 257:1, seahorse:3, shark:1@patrol`. Minted breeds plus bundled: shark crab dori glowfish babyfish hackerfish blowfish starfish octopus snail dot dolphin. Floor walkers keep their own pace: crab, starfish (dances with `starfishDance`), snail. `dot` (a courier box on legs) and `dolphin` (chatters, flips, breaches; stands on its tail to act) play vignette gestures. Overrides fishCount/fishUrl.",
|
|
2946
3141
|
dracoPath: "Where the Draco decoder lives; leave empty on a channel. Only the page's own origin or the shipped copy is used.",
|
|
2947
3142
|
swimSpeed: "Global swim speed multiplier. 0.4 is contemplative, 1 lively.",
|
|
2948
3143
|
swimStyle: "How the cast swims unless a fishMix entry says otherwise (`@style`). `auto` picks by breed.",
|
|
@@ -2965,15 +3160,16 @@ var PARAM_DOCS = {
|
|
|
2965
3160
|
fishAmbient: "The water's light on the fish in lit mode (backs, flanks, bellies, metal plates). Fixes fish reading dark; 0.6\u20131 in bright tanks. Dims with a follow-spot's house lights.",
|
|
2966
3161
|
fishGlow: "How strongly GLOW- parts of a fish bloom and light their neighbours and the floor.",
|
|
2967
3162
|
fishMetal: "`on` (default) gives METAL- parts a polished, reflective finish.",
|
|
2968
|
-
snailShell: "
|
|
2969
|
-
starfishDance: "Starfish dance: `aerobics`, a class facing the camera
|
|
3163
|
+
snailShell: "Snail shells: `banded`, `pearl` (iridescent), `gold`, `glow` (a spiral breathing light; best at night); `mix` (default), each its own. Lit fish only. frame.state.fish[i].shell says which.",
|
|
3164
|
+
starfishDance: "Starfish dance: `aerobics`, a class in unison facing the camera; `freestyle`, each its own move every eight counts; `duet`, couples face to face (with `spotRig: 0/#ffd27a*26, 1/#ff8ad0*26`); `off` (default). Cast a few (`starfish:7`), `shot: front`, `autoRotate: 0`, a bright room.",
|
|
2970
3165
|
danceTempo: "Beats per minute for the starfish dance (60\u2013180, default 128). Glides on the beat.",
|
|
2971
|
-
fishLook: "`neon` dresses the bundled creatures
|
|
3166
|
+
fishLook: "`neon` dresses the bundled creatures for blacklight: near-black coats, eyes (and a crab's mouth) glowing a neon of their own. Best in a dark room with `finish`. Default `natural`.",
|
|
2972
3167
|
fishRim: "A thin edge of the water's light round each fish, 0\u20131 (lit mode): dark fish on dark water. 0.6\u20131 in dark rooms.",
|
|
2973
3168
|
fishShadows: "Soft shadows on the ground under the swimmers, 0\u20131: dark near the sand, faint high up.",
|
|
2974
3169
|
fishFadeIn: "Seconds an arriving fish takes to dissolve in (0 = it pops in).",
|
|
2975
3170
|
sceneryShadows: "Soft shadows under rocks, homes, castle, pyramids, geodes and lamps, 0\u20131, so nothing floats.",
|
|
2976
|
-
|
|
3171
|
+
voxelAO: "Plant cubes darken where they touch each other and the sand, 0\u20131: solid coral. All tiers.",
|
|
3172
|
+
swimWave: "Fish bend as they swim: a wave nose to tail, the body curling into turns (0 = rigid wiggle). Skips the turtle, crab and fish with a skeleton; the seahorse gets fin, tail and nod instead.",
|
|
2977
3173
|
fishAvoid: "Fish make way for each other: a pair about to meet parts a second ahead, the smaller giving more, over or under before round. 1 (default) on, 0 pass-through. `inspect().crowding` counts pairs still touching.",
|
|
2978
3174
|
eyeLife: "Eyes blink, look, and emote (0 = painted on). Redraws each token's own pixel-grid eye; black and white only.",
|
|
2979
3175
|
// water and room
|
|
@@ -2983,21 +3179,21 @@ var PARAM_DOCS = {
|
|
|
2983
3179
|
floorColor: "Sea-floor colour.",
|
|
2984
3180
|
water: "Water instead of fog: red fades with distance first, then green, blue last \u2014 a red fish goes blue-green before it goes into the murk. 0.6\u20131 for most scenes; 0 is plain fog.",
|
|
2985
3181
|
waterClarity: "With `water` on: 0 murky (colour lost fast, short reach), 0.5 as before, 1 clear (colour kept, reach \xD71.5). Bright tanks want 0.7\u20131.",
|
|
2986
|
-
waterTint: "The sunlit water, `#rrggbb`:
|
|
3182
|
+
waterTint: "The sunlit water, `#rrggbb`: distance fades into it looking up, half on the level, none looking down; the background takes the gradient. Empty = off; with `water` on, reef, kelp, ice and lagoon bring their own.",
|
|
2987
3183
|
dither: "`on` adds an invisible dither that removes colour banding in dark fogged gradients on TVs. Recommended with `water`; `off` (default) is byte-exact with older scenes.",
|
|
2988
3184
|
moteDensity: "Suspended motes.",
|
|
2989
3185
|
moteColor: "Their colour.",
|
|
2990
3186
|
environment: "A room preset (floor, ceiling, light shafts). `void` for the mineral-world scenes.",
|
|
2991
3187
|
floorKind: "Floor shape: `auto` follows the environment, `flat`, or terrain `dunes`, `ridges`, `basin`, `shelf` (the town on a terraced plateau), `trench` (a deep channel across the front), `terraces` (tiers behind the town).",
|
|
2992
3188
|
waterY: "Height of the water ceiling; -1 = the environment's own.",
|
|
2993
|
-
rayStrength: "
|
|
3189
|
+
rayStrength: "Surface light shafts, within device budget: 5 high tier, 2 standard; none on the lowest tier with a surface (abyss, vent, universe keep 2; perceiveChannel then reads 0).",
|
|
2994
3190
|
raySoftness: "Shafts as soft volume, not flat sheets, 0\u20131. 1 for anything cinematic.",
|
|
2995
3191
|
floorDetail: "A seabed, not a coloured plane, 0\u20131: ripples, silt, grain; the floor's far edge fades into the fog.",
|
|
2996
3192
|
caustics: "The dancing net of light from the surface on the floor, rocks, plants and fish (0 = none). Sharp near the top, soft and dim deep down.",
|
|
2997
3193
|
causticScale: "Size of the caustic cells: below 1 a fine shimmer, above 1 broad slow bands.",
|
|
2998
|
-
surfaceMirror: "The surface from below: a window
|
|
3194
|
+
surfaceMirror: "The surface from below: a window to the light in a rippling mirror of the tank (0 = plain). Needs reef, kelp, ice or lagoon; for the `surface` and `low` shots. High tier mirrors the tank, mid the deep water.",
|
|
2999
3195
|
// world
|
|
3000
|
-
propMix: "Crystals: `crystal[:count][@habit][/palette][*size]`. Habits lotus
|
|
3196
|
+
propMix: "Crystals: `crystal[:count][@habit][/palette][*size]`. Habits lotus spire druse scatter coral; palettes env rainbow blue hotpink purple seafoam yellow orange cyan white glass; `*6` is tower-sized.",
|
|
3001
3197
|
envProps: "`on` lets the environment preset plant its own crystals when `propMix` is empty.",
|
|
3002
3198
|
crystalScale: "Size of the whole mineral world (crystals, rocks, homes, flora).",
|
|
3003
3199
|
crystalWild: "Organic individuality: lean, bald sides, branching. 0 is a catalogue crystal, 1 is coral.",
|
|
@@ -3006,30 +3202,32 @@ var PARAM_DOCS = {
|
|
|
3006
3202
|
crystalTint: "How much nearby light sources colour a passing fish. 0.5\u20130.7 ties the cast to the place.",
|
|
3007
3203
|
rockDensity: "Faceted boulders, an arch and a back ridge around the crystals.",
|
|
3008
3204
|
rockVeins: "Crystals bursting out of the rocks: each crown broken open, chips on the rim, a colony of the same crystal growing from the breach. 0 is plain stone.",
|
|
3009
|
-
geodeHomes: "Geode houses (0\u20133) in a crescent: a village.
|
|
3205
|
+
geodeHomes: "Geode houses (0\u20133) in a crescent: a village. Doors are vignette marks `home1`\u2026, `home1in`\u2026. Capped at 2 on basic/minimal tiers; a dropped house takes its marks, and indoors there are none.",
|
|
3010
3206
|
stoneStyle: "`weathered`: river-grey rocks and homes with silt on top, not dark holes in a lit room. `classic` (default): dark slate.",
|
|
3011
3207
|
interior: "`geode` sets the whole scene INSIDE a geode home: furniture, chandelier, marks like `table`, `bed`, `door`.",
|
|
3012
3208
|
floraDensity: "Voxel plants: one tank-wide budget (\xD7 36/72/108 by tier) shared among crystals and geodes, so more crystals means sparser gardens. What grows is `floraMix`; empty, the room picks.",
|
|
3013
|
-
geodes: "Wild geodes on the floor (0 = none): split geodes, thunder eggs, cathedrals, clusters, one cavern. They
|
|
3209
|
+
geodes: "Wild geodes on the floor (0 = none): split geodes, thunder eggs, cathedrals, clusters, one cavern. They pulse with the crystals, take the nearest one's mineral, and wake when a fish comes close.",
|
|
3014
3210
|
geodeMix: "Which geodes: `kind[:weight]`, comma-separated. geode cathedral cluster cavern (the cavern is one at most).",
|
|
3015
3211
|
geodeMineral: "What they are made of: amethyst agate celestine citrine carnelian rose emerald quartz smoky. Empty = the mineral nearest in colour to the nearest crystal; `world` = two from the seed; or a list.",
|
|
3016
|
-
fountain: "The town square's fountain
|
|
3017
|
-
signs:
|
|
3018
|
-
|
|
3019
|
-
|
|
3212
|
+
fountain: "The town square's fountain: `vent` (a crystal-crusted chimney glowing at the mouth) or `geode` (a geode basin on a plinth). A bubble column, a plaza, its own light, and a vignette mark `fountain`.",
|
|
3213
|
+
signs: "Signs, `kind[@place][>target][/#color][*size][:text]`: `plank` on a post, `arrow` to a place, life-`ring`, lit `porthole`, `neon` lettering, scrolling `led`, each beside its place. Original words only. `neon@home1/#ff4fa0:OPEN, arrow@hub>gate:Castle`.",
|
|
3214
|
+
stageSet: "`club`: a brick back wall, a mic on a stand at the `mic` mark, a stool; `mic`: the stand alone. For the `openmic` vignette.",
|
|
3215
|
+
dolphinCast: "Comic dolphins, in fishMix order: `showman` (navy suit, red tie, quick), `drifter` (tan, flannel, trucker cap, slow slouch), `bigguy` (big, slate, dark tee, deadpan), `host` (a bow tie); else `classic`. Types, no likeness.",
|
|
3216
|
+
signFlicker: "How unstable the lit signs' power is, 0\u20131 (default 0.4): hums, stutters, a dead row, now and then a reboot, each sign on its own supply; 0 is steady. Flash-safe.",
|
|
3217
|
+
streetLamps: "Crystal streetlamps along the paths (0\u20131, about 24 at 1): slate posts crowned in the nearest crystal's colour, a halo, light on the floor. Needs paths.",
|
|
3020
3218
|
townProps: "Village props, kind[:count][/style]: fence (a yard per home; driftwood coral stone crystal), mailbox (by each gate: post clam bottle; marks mailbox1\u2026), bench[:n] (shell stone), sock[:n]. Fences and mailboxes need geodeHomes.",
|
|
3021
|
-
floorCover: "
|
|
3022
|
-
pyramids: "Pyramids
|
|
3219
|
+
floorCover: "The village's floor, kind[/style]: lawn (yards; stripes checks), mosaic (at the hub; stone shell glass), mat (at doors; kelp stripe), steps (down walks; slate sand moss), ripples (fine broad). Lawns and mats need geodeHomes, steps paths. Flat ground.",
|
|
3220
|
+
pyramids: "Pyramids of cubes (0 = none, 1 = about a dozen complexes): a great pyramid and its queens, the great ones at the back. Flat ground only (floorKind: flat).",
|
|
3023
3221
|
pyramidMix: "Shapes and stones, `shape|stone[:weight], \u2026`: giza step bent djoser mayan frame; sandstone limestone obsidian jade gold lapis coral crystal nacre basalt, and lit led and neon (every cube a lamp). Empty = every shape in the room's stone.",
|
|
3024
|
-
pyramidLayout: "field = complexes over the floor; stage = one monumental pyramid
|
|
3222
|
+
pyramidLayout: "field = complexes over the floor; stage = one monumental pyramid mid-orbit (lit with led or neon, else stone with lit trim), a small one each side, the field behind. Needs flat ground in the middle.",
|
|
3025
3223
|
pyramidShow: "The lit pyramids' show (led, neon or trim): auto = one wave through every routine; solo = each its own; a routine (rings beam edges equalizer rainbow twinkle faces rain spiral breathe ripple); or cues `8s:rings, 16s:equalizer`. Live, no rebuild.",
|
|
3026
3224
|
pyramidBpm: "The light show's tempo, 0\u2013140 (0 = free-running): the motion steps on the beat and each beat lifts the lamps a little \u2014 never a flash.",
|
|
3027
3225
|
geodeLayout: "`field` (default) scatters them; `gallery` lines them up, a row per kind and a column per mineral, the mega geode behind. Frame with cameraDistance 400, elevation ~26, nothing else in the scene.",
|
|
3028
|
-
floraLayout: "`garden` (default) grows plants round the crystals; `gallery` plants one of each species in two rows across the front (
|
|
3029
|
-
floraPalette: "One colour scheme for the garden: `world`
|
|
3030
|
-
floraMix: "Which plants
|
|
3226
|
+
floraLayout: "`garden` (default) grows plants round the crystals; `gallery` plants one of each species in two rows across the front (only floraMix's, if set). Frame with `shot: front` or `cameraDistance` ~260.",
|
|
3227
|
+
floraPalette: "One colour scheme for the garden: `world` (a hero hue, a neighbour and an accent from the seed) or up to six `#rrggbb`; each colony is pulled most of the way to one. Empty = each species its own.",
|
|
3228
|
+
floraMix: "Which plants, `species[:weight]`: grass tube bulb fan kelp anemone staghorn brain whip barrel shelf seapen clam bubble elder curl pod (clam \u22642, elder 1). `kelp:3, whip, seapen` a kelp forest; `staghorn:2, brain, anemone:2, clam` a reef. Needs floraDensity.",
|
|
3031
3229
|
bubbleVents: "Bubbles from chimneys, fissures and crystals.",
|
|
3032
|
-
bubbleStyle: "
|
|
3230
|
+
bubbleStyle: "Vent bubbles: `classic` loops puffs; `live` grows each at the vent, rises it to the surface and pops it, resting the vent now and then. Distant bubbles dim instead of fattening.",
|
|
3033
3231
|
pearling: "Oxygen pearls on the plants: beads grow on the leaves over half a minute or more, sway with them and let go (0 = none). Needs floraDensity > 0; with no flora there is nothing to pearl on.",
|
|
3034
3232
|
co2Mist: "A fine haze of tiny bubbles from the vents, drifting on a slow current (0 = none).",
|
|
3035
3233
|
marineSnow: "Drifting lit particles; gives the water depth.",
|
|
@@ -3041,19 +3239,19 @@ var PARAM_DOCS = {
|
|
|
3041
3239
|
landmark: "One thing bigger than everything else. `castle`: voxel walls, crystal-spired towers, gate, road, plaza. `citadel`: two storeys. Marks: `gate`, `plaza`, `courtyard`.",
|
|
3042
3240
|
// stage
|
|
3043
3241
|
followSpot: "A follow-spot on one fish slot (0 = first of the cast); -1 off. House lights drop with it.",
|
|
3044
|
-
spotStrength: "How hard the spots are
|
|
3242
|
+
spotStrength: "How hard the spots are and how far the house lights fall: lit fish's key/fill (to 40%) and fishAmbient dim; glow, reflections and scenery keep theirs. A `-` blackout puts out only the spots.",
|
|
3045
3243
|
spotColor: "Colour of the single `followSpot`.",
|
|
3046
3244
|
spotShadow: "The spotted fish's shadow in its pool. 0 none, 1 true size.",
|
|
3047
|
-
spotRig: "Up to three spots: `slot[/color][*radius]
|
|
3245
|
+
spotRig: "Up to three spots: `slot[/color][*radius]` follows a fish, `@mark` stands on a mark (`@mic` lights whoever steps up). They are a, b, c in order. Overrides followSpot.",
|
|
3048
3246
|
spotCues: "A looping cue sheet for the rig: `8s:a, 8s:b, 12s:a+b, 4s:-` (`-` = blackout). Spots cross-fade at each cue.",
|
|
3049
|
-
vignette: "A small scene for the first 2\u20133 fish: a preset (tea\xB7bedtime\xB7seek indoors; duet\xB7trio on the open stage) or a script of beats. A floor creature cast as an actor stays on the floor. Grammar below."
|
|
3247
|
+
vignette: "A small scene for the first 2\u20133 fish: a preset (tea\xB7bedtime\xB7seek indoors; duet\xB7trio\xB7openmic on the open stage) or a script of beats. A floor creature cast as an actor stays on the floor. Grammar below."
|
|
3050
3248
|
};
|
|
3051
3249
|
var GRAMMAR = `
|
|
3052
3250
|
fishMix id[:count][@style][*size], \u2026 100:2@drift, 257:1, seahorse:3, shark:1@patrol*1.5
|
|
3053
3251
|
id = a minted token (1\u2013512) or a breed (betafish angelfish seahorse seaturtle). Minted fish are
|
|
3054
3252
|
individuals: a count casts DISTINCT neighbours. Bundled creatures (a count is copies): shark
|
|
3055
3253
|
crab dori glowfish babyfish hackerfish blowfish starfish octopus snail dot. *size scales that token over its
|
|
3056
|
-
breed's own size. Styles: loop school drift hover patrol bottom surface follow pair chase.
|
|
3254
|
+
breed's own size. Styles: loop school drift hover patrol bottom surface follow pair chase audience.
|
|
3057
3255
|
propMix crystal[#id][:count][@habit][/palette][*size], \u2026 crystal:3@druse/rainbow, crystal:1@spire/cyan*6
|
|
3058
3256
|
habits lotus spire druse scatter coral \xB7 palettes env rainbow blue hotpink purple seafoam yellow orange cyan white glass
|
|
3059
3257
|
floraMix species[:weight], \u2026 kelp:3, whip, seapen \xB7 staghorn:2, brain, anemone:2, clam
|
|
@@ -3069,17 +3267,18 @@ pyramidShow auto | solo | routine | <sec>s:<routine>, \u2026 8s:rings, 8s:bea
|
|
|
3069
3267
|
geodeMix kind[:weight], \u2026 geode cathedral cluster cavern geode:3, cathedral, cluster
|
|
3070
3268
|
geodeMineral world | mineral, \u2026 amethyst agate celestine citrine carnelian rose emerald quartz smoky
|
|
3071
3269
|
floraPalette world | #rrggbb, \u2026 (\u22646) world \xB7 #ff4fa0, #ffb347, #4fd1ff
|
|
3072
|
-
spotRig slot[/color][*radius], \u2026 (\u22643; they become a, b, c) 0/#ffd27a*24, 1/#ff8ad0*24, 2/#7fdcff*24
|
|
3270
|
+
spotRig slot|@mark[/color][*radius], \u2026 (\u22643; they become a, b, c) 0/#ffd27a*24, 1/#ff8ad0*24, 2/#7fdcff*24
|
|
3073
3271
|
spotCues <sec>s:<spots>, \u2026 loops 4s:-, 7s:a, 7s:b, 12s:a+b
|
|
3074
3272
|
signs kind[@place][>target][/#color][*size][:text], \u2026 plank@gate:"Welcome to the reef", neon@home1/#ff4fa0:OPEN, led@plaza:TIDE 4.2M
|
|
3075
3273
|
kinds plank arrow ring porthole neon led \xB7 places gate plaza courtyard hub fountain home1 home2 home3 centre left right front back
|
|
3076
3274
|
an arrow points at >target \xB7 text \u2264 plank 24, arrow 16, ring 10, porthole 4 (empty = a fish), neon 14, led 64 (scrolls) \xB7 \u2264 8 signs
|
|
3077
|
-
vignette beats separated by |, cues by comma; actors a b c are fish slots 0 1 2
|
|
3275
|
+
vignette beats separated by |, cues by comma; actors a b c d are fish slots 0 1 2 3
|
|
3078
3276
|
[<sec>s:] actor =mark be there, no travel (a later beat's = snaps at its start) actor >mark swim there
|
|
3079
3277
|
actor @mark | @actor face it a b circle mark each named actor rings it, back to its spot by the beat's end; others stay put
|
|
3080
3278
|
actor follow actor gestures: talk nod shake hop spin wiggle rest peek bow \xB7 ink (octopus only: a cloud of ink)
|
|
3081
3279
|
dances: toprock footwork windmill headspin freeze drop (a betafish dances them with its own body; 2 s beats keep them on a 120 bpm bar)
|
|
3082
3280
|
Give every beat a duration and end where you began and a spotCues sheet stays in step forever.
|
|
3281
|
+
Fish cast @audience sit out front facing the stage: they laugh as each talk ends and applaud each bow.
|
|
3083
3282
|
Marks outdoors: ${Object.keys(OPEN_MARKS).join(" ")} \u2014 plus the world's own:
|
|
3084
3283
|
home1 home1in \u2026 (geodeHomes), gate plaza courtyard (landmark), hub (paths), fountain (fountain).
|
|
3085
3284
|
Marks indoors (interior: geode): ${Object.keys(INTERIOR_MARKS).join(" ")}
|
|
@@ -3273,6 +3472,28 @@ var RECIPES = [
|
|
|
3273
3472
|
cameraAzimuth: 0
|
|
3274
3473
|
}
|
|
3275
3474
|
},
|
|
3475
|
+
{
|
|
3476
|
+
id: "open-mic",
|
|
3477
|
+
label: "Open mic",
|
|
3478
|
+
what: "A comedy club: a host brings three comic dolphins to the mic in turn; a seated crowd laughs and applauds.",
|
|
3479
|
+
params: {
|
|
3480
|
+
...ALIVE,
|
|
3481
|
+
...STAGE,
|
|
3482
|
+
floorColor: "#1a1210",
|
|
3483
|
+
vignette: "openmic",
|
|
3484
|
+
stageSet: "club",
|
|
3485
|
+
spotRig: "@mic/#ffc890*26",
|
|
3486
|
+
spotCues: VIGNETTE_CUES.openmic,
|
|
3487
|
+
spotStrength: 0.3,
|
|
3488
|
+
fishMix: "dolphin:4, dori:2@audience*0.7, crab:2@audience*0.6, octopus:1@audience*0.7, babyfish:5@audience",
|
|
3489
|
+
dolphinCast: "showman, drifter, bigguy, host",
|
|
3490
|
+
signs: "neon@back/#ff4fa0:OPEN MIC",
|
|
3491
|
+
finish: 0.6,
|
|
3492
|
+
cameraDistance: 125,
|
|
3493
|
+
cameraElevation: 12,
|
|
3494
|
+
cameraAzimuth: 0
|
|
3495
|
+
}
|
|
3496
|
+
},
|
|
3276
3497
|
{
|
|
3277
3498
|
id: "follow-spot",
|
|
3278
3499
|
label: "Follow-spot",
|
|
@@ -3327,7 +3548,7 @@ var RECIPES = [
|
|
|
3327
3548
|
{
|
|
3328
3549
|
id: "reef-characters",
|
|
3329
3550
|
label: "Reef characters",
|
|
3330
|
-
what: "The bundled characters on a reef: tangs that look back, blowfish that puff and flirt, an octopus that
|
|
3551
|
+
what: "The bundled characters on a reef: tangs that look back, blowfish that puff and flirt, an octopus that inks, a crab.",
|
|
3331
3552
|
params: {
|
|
3332
3553
|
...ALIVE,
|
|
3333
3554
|
environment: "reef",
|
|
@@ -3370,7 +3591,7 @@ var RECIPES = [
|
|
|
3370
3591
|
{
|
|
3371
3592
|
id: "dot-delivers",
|
|
3372
3593
|
label: "Dot delivers",
|
|
3373
|
-
what: "Dot the courier
|
|
3594
|
+
what: "Dot the courier knocks at a neighbour's door; a blowfish comes out, they bow and chat, Dot spins for joy and walks home.",
|
|
3374
3595
|
params: {
|
|
3375
3596
|
...ALIVE,
|
|
3376
3597
|
geodeHomes: 3,
|
|
@@ -3513,6 +3734,9 @@ function validateMetaquariumParams(params) {
|
|
|
3513
3734
|
else push("spotCues", parseSpotCues(str("spotCues"), spots).problems);
|
|
3514
3735
|
}
|
|
3515
3736
|
const world = worldMarks(params);
|
|
3737
|
+
for (const s of rig.spots) {
|
|
3738
|
+
if (s.mark && !(s.mark in OPEN_MARKS) && !(s.mark in world)) out.push({ path: "spotRig", message: `no "${s.mark}" mark in this scene \u2014 that spot is dark` });
|
|
3739
|
+
}
|
|
3516
3740
|
if (str("vignette")) {
|
|
3517
3741
|
const indoors = params.interior === "geode";
|
|
3518
3742
|
push("vignette", parseVignette(resolveVignette(str("vignette")), indoors ? INTERIOR_MARKS : { ...OPEN_MARKS, ...world }).problems);
|
|
@@ -3598,17 +3822,17 @@ function dependencyProblems(params) {
|
|
|
3598
3822
|
const spot2 = Math.round(num("followSpot", -1));
|
|
3599
3823
|
if (rig.length && set("followSpot")) out.push({ path: "followSpot", also: ["spotRig"], message: "ignored: a spotRig replaces the single followSpot" });
|
|
3600
3824
|
else if (spot2 >= cast) out.push({ path: "followSpot", also: castParams, message: noFish(spot2) });
|
|
3601
|
-
for (const s of rig) if (s.slot >= cast) out.push({ path: "spotRig", also: castParams, message: noFish(s.slot) });
|
|
3825
|
+
for (const s of rig) if (!s.mark && s.slot >= cast) out.push({ path: "spotRig", also: castParams, message: noFish(s.slot) });
|
|
3602
3826
|
if (str("vignette")) {
|
|
3603
3827
|
const v = parseVignette(resolveVignette(str("vignette")), { ...OPEN_MARKS, ...INTERIOR_MARKS });
|
|
3604
3828
|
const { actors } = v;
|
|
3605
|
-
if (actors > cast) out.push({ path: "vignette", also: castParams, message: `needs ${actors} fish (actors a\u2013${"
|
|
3829
|
+
if (actors > cast) out.push({ path: "vignette", also: castParams, message: `needs ${actors} fish (actors a\u2013${"abcd"[actors - 1]} are slots 0\u2013${actors - 1}), but ${castWhy}` });
|
|
3606
3830
|
const slotBreeds = expandFishMixSlots(mix, 3).map((sl) => sl.breed);
|
|
3607
3831
|
for (const [g, breeds] of Object.entries(BREED_GESTURES)) {
|
|
3608
3832
|
for (let a = 0; a < actors; a++) {
|
|
3609
3833
|
if (!v.beats.some((b) => b.actors[a]?.gesture === g)) continue;
|
|
3610
3834
|
const breed = slotBreeds[a] ?? "fish";
|
|
3611
|
-
if (!breeds.includes(breed)) out.push({ path: "vignette", also: ["fishMix"], message: `"${"
|
|
3835
|
+
if (!breeds.includes(breed)) out.push({ path: "vignette", also: ["fishMix"], message: `"${"abcd"[a]} ${g}" does nothing: ${g} is the ${breeds.join("/")}'s own, and actor ${"abcd"[a]} (slot ${a}) is a ${breed}` });
|
|
3612
3836
|
}
|
|
3613
3837
|
}
|
|
3614
3838
|
}
|
|
@@ -3760,7 +3984,7 @@ function describeMetaquarium(params, timed = []) {
|
|
|
3760
3984
|
if (Math.round(num("shoal") * CAST_CAP2 * 2.5) >= 3) world.push(`a school of ${str("shoalKind")}`);
|
|
3761
3985
|
const stage = [];
|
|
3762
3986
|
const rig = parseSpotRig(str("spotRig")).spots;
|
|
3763
|
-
if (rig.length) stage.push(`${rig.length} spotlight${rig.length === 1 ? "" : "s"} on ${rig.map((s) => slotName(s.slot)).join(", ")}`);
|
|
3987
|
+
if (rig.length) stage.push(`${rig.length} spotlight${rig.length === 1 ? "" : "s"} on ${rig.map((s) => s.mark ? `the ${s.mark} mark` : slotName(s.slot)).join(", ")}`);
|
|
3764
3988
|
else if (num("followSpot") >= 0) stage.push(`follow-spot on ${slotName(num("followSpot"))}`);
|
|
3765
3989
|
if (has("spotCues") && (rig.length || num("followSpot") >= 0)) {
|
|
3766
3990
|
const sheet = parseSpotCues(str("spotCues"), Math.max(1, rig.length));
|
|
@@ -4073,6 +4297,10 @@ var METAQUARIUM_PARAMS = {
|
|
|
4073
4297
|
* homes, the castle, pyramids, wild geodes, lamps — so it stands on the
|
|
4074
4298
|
* sand instead of floating over it. 0 is none. */
|
|
4075
4299
|
sceneryShadows: { type: "number", default: 0, min: 0, max: 1, ease: "smooth" },
|
|
4300
|
+
/** The plants' cubes darken where they meet each other and the sand — the
|
|
4301
|
+
* corner shading every voxel renderer has — so a coral reads solid instead
|
|
4302
|
+
* of as flat cut-outs. Baked once, the first frame it is on; every tier. 0 is off. */
|
|
4303
|
+
voxelAO: { type: "number", default: 0, min: 0, max: 1, ease: "smooth" },
|
|
4076
4304
|
/** A creature that comes too close to the camera dissolves (an ordered
|
|
4077
4305
|
* dither by depth) instead of being cut by the lens: at 1 it is whole
|
|
4078
4306
|
* beyond about a fish and a half and gone inside about half that. 0 is off. */
|
|
@@ -4250,6 +4478,13 @@ var METAQUARIUM_PARAMS = {
|
|
|
4250
4478
|
* fountain home1…, or centre left right front back), facing the front; an LED board
|
|
4251
4479
|
* scrolls a long line. Empty = none. */
|
|
4252
4480
|
signs: { type: "string", default: "", ease: "step" },
|
|
4481
|
+
/** The open-mic set (club.ts): `club` is a brick back wall, a mic on a stand at the `mic` mark and a
|
|
4482
|
+
* stool; `mic` the stand alone. The `openmic` vignette is written for it, with an `@audience` out front. */
|
|
4483
|
+
stageSet: { type: "enum", default: "none", options: ["none", "club", "mic"], ease: "step" },
|
|
4484
|
+
/** EXPERIMENTAL. The dolphins' cast — comic personas, in the order the dolphins come in fishMix:
|
|
4485
|
+
* `showman, drifter, bigguy` (dolphin-comics.ts). Each its own look (skin, finish, shirt, prop) and
|
|
4486
|
+
* act (tempo, size of gesture, slouch, lean, lids). Unknown names, and dolphins past the list: classic. */
|
|
4487
|
+
dolphinCast: { type: "string", default: "", ease: "step" },
|
|
4253
4488
|
/** How unstable the lit signs' power is (neon and LED boards): 0 steady; higher, more
|
|
4254
4489
|
* hum, stutters, a dead row or tube, a slipped row, now and then a reboot. Each sign
|
|
4255
4490
|
* has its own supply — most steady, some dodgy. Flash-safe at any setting. */
|
|
@@ -4542,4 +4777,4 @@ export {
|
|
|
4542
4777
|
paramSpaceWith,
|
|
4543
4778
|
coerceNum
|
|
4544
4779
|
};
|
|
4545
|
-
//# sourceMappingURL=chunk-
|
|
4780
|
+
//# sourceMappingURL=chunk-5DJ5WBVN.js.map
|