@idle-screens/saver-metaquarium 0.20.0 → 0.22.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-42RIVUT4.js} +524 -86
- package/dist/chunk-42RIVUT4.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-BJY5Z3J5.js} +2 -2
- package/dist/{manifest-BzLzMuur.d.ts → manifest-nIf510iL.d.ts} +71 -5
- package/dist/manifest.d.ts +1 -1
- package/dist/manifest.js +1 -1
- package/dist/{tank-7WG3RET5.js → tank-EOBD2TLR.js} +1442 -539
- package/dist/tank-EOBD2TLR.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-BJY5Z3J5.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 };
|
|
@@ -1615,7 +1620,7 @@ function shardGeometry(rng) {
|
|
|
1615
1620
|
for (let i = 0; i < n; i += 1) tris.push([top[i], tip, top[(i + 1) % n]]);
|
|
1616
1621
|
const positions = new Float32Array(tris.length * 9);
|
|
1617
1622
|
const normals = new Float32Array(tris.length * 9);
|
|
1618
|
-
const
|
|
1623
|
+
const smooth4 = new Float32Array(tris.length * 9);
|
|
1619
1624
|
tris.forEach((tri, t) => {
|
|
1620
1625
|
const [a, b, c] = tri;
|
|
1621
1626
|
const ux = b[0] - a[0], uy = b[1] - a[1], uz = b[2] - a[2];
|
|
@@ -1643,12 +1648,12 @@ function shardGeometry(rng) {
|
|
|
1643
1648
|
normals[o + 2] = nz;
|
|
1644
1649
|
const sy = (p[1] - 0.55) * 0.25;
|
|
1645
1650
|
const sl = Math.hypot(p[0], sy, p[2]) || 1;
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1651
|
+
smooth4[o] = p[0] / sl;
|
|
1652
|
+
smooth4[o + 1] = sy / sl;
|
|
1653
|
+
smooth4[o + 2] = p[2] / sl;
|
|
1649
1654
|
});
|
|
1650
1655
|
});
|
|
1651
|
-
return { positions, normals, smooth:
|
|
1656
|
+
return { positions, normals, smooth: smooth4, triangles: tris.length };
|
|
1652
1657
|
}
|
|
1653
1658
|
var HABIT_RINGS = {
|
|
1654
1659
|
lotus: [[1, 0], [6, 30], [9, 55], [16, 85]],
|
|
@@ -2495,10 +2500,190 @@ function parseSignMix(value) {
|
|
|
2495
2500
|
return { entries, problems };
|
|
2496
2501
|
}
|
|
2497
2502
|
|
|
2503
|
+
// src/choreo.ts
|
|
2504
|
+
var STARFISH_MOVE_NAMES = [
|
|
2505
|
+
"march",
|
|
2506
|
+
"jacks",
|
|
2507
|
+
"reach",
|
|
2508
|
+
"kick",
|
|
2509
|
+
"twist",
|
|
2510
|
+
"circles",
|
|
2511
|
+
"disco",
|
|
2512
|
+
"spin",
|
|
2513
|
+
"mambo",
|
|
2514
|
+
"sway",
|
|
2515
|
+
"twirl",
|
|
2516
|
+
"dip",
|
|
2517
|
+
"hold",
|
|
2518
|
+
"walk",
|
|
2519
|
+
"lie",
|
|
2520
|
+
"crawl",
|
|
2521
|
+
"curl",
|
|
2522
|
+
"wave",
|
|
2523
|
+
"pop",
|
|
2524
|
+
"rise",
|
|
2525
|
+
"sit"
|
|
2526
|
+
];
|
|
2527
|
+
var CHOREO_MARKS = {
|
|
2528
|
+
home: [0, 0],
|
|
2529
|
+
left: [-2, 0],
|
|
2530
|
+
right: [2, 0],
|
|
2531
|
+
front: [0, 1.2],
|
|
2532
|
+
back: [0, -1.5],
|
|
2533
|
+
centre: "centre"
|
|
2534
|
+
};
|
|
2535
|
+
var MAX_CUES = 256;
|
|
2536
|
+
function parseChoreography(value, moves) {
|
|
2537
|
+
const lines = /* @__PURE__ */ new Map(), problems = [];
|
|
2538
|
+
const known = new Set(moves);
|
|
2539
|
+
for (const rawLine of value.split("|").map((s) => s.trim()).filter(Boolean)) {
|
|
2540
|
+
const head = /^([a-z]|\*)\s*:\s*(?=\d|\.)/.exec(rawLine);
|
|
2541
|
+
const actor = head ? head[1] === "*" ? "*" : head[1].charCodeAt(0) - 97 : "*";
|
|
2542
|
+
const body = head ? rawLine.slice(head[0].length) : rawLine;
|
|
2543
|
+
if (lines.has(actor)) problems.push(`dancer ${actor === "*" ? "*" : String.fromCharCode(97 + actor)} has two lines; the later one wins`);
|
|
2544
|
+
const cues = [];
|
|
2545
|
+
let b0 = 0;
|
|
2546
|
+
for (const raw of body.split(/,\s*(?=(?:\d+(?:\.\d+)?|\.\d+)\s*:)/).map((s) => s.trim()).filter(Boolean)) {
|
|
2547
|
+
if (cues.length >= MAX_CUES) {
|
|
2548
|
+
problems.push(`more than ${MAX_CUES} cues on one line; the rest are dropped`);
|
|
2549
|
+
break;
|
|
2550
|
+
}
|
|
2551
|
+
const m = /^(\d+(?:\.\d+)?|\.\d+)\s*:\s*([a-z][a-z_]*)([^a-z_].*)?$/.exec(raw);
|
|
2552
|
+
if (!m) {
|
|
2553
|
+
problems.push(`"${raw}" is not <beats>:<move>`);
|
|
2554
|
+
continue;
|
|
2555
|
+
}
|
|
2556
|
+
const beats = Number(m[1]);
|
|
2557
|
+
if (!(beats > 0)) {
|
|
2558
|
+
problems.push(`"${raw}": a cue needs some beats`);
|
|
2559
|
+
continue;
|
|
2560
|
+
}
|
|
2561
|
+
if (!known.has(m[2])) {
|
|
2562
|
+
problems.push(`"${raw}": no move "${m[2]}" (moves: ${moves.join(" ")})`);
|
|
2563
|
+
continue;
|
|
2564
|
+
}
|
|
2565
|
+
const cue = { b0, beats: Math.min(beats, 512), move: m[2], turn: 0, lift: 0 };
|
|
2566
|
+
for (const tok of (m[3] ?? "").replace(/^[,\s]+/, "").split(/\s+/).filter(Boolean)) {
|
|
2567
|
+
const mod = /^([>@~^/%*])(.+)$/.exec(tok);
|
|
2568
|
+
if (!mod) {
|
|
2569
|
+
problems.push(`"${raw}": "${tok}" is not >to, @face, ~turn, ^leap, /lean, %crouch or *groove`);
|
|
2570
|
+
continue;
|
|
2571
|
+
}
|
|
2572
|
+
const [, op, arg] = mod;
|
|
2573
|
+
if (op === ">") {
|
|
2574
|
+
const mark = CHOREO_MARKS[arg];
|
|
2575
|
+
const xy = /^(-?\d+(?:\.\d+)?),(-?\d+(?:\.\d+)?)$/.exec(arg);
|
|
2576
|
+
if (mark) cue.to = mark;
|
|
2577
|
+
else if (xy) cue.to = [clamp(Number(xy[1]), -6, 6), clamp(Number(xy[2]), -6, 6)];
|
|
2578
|
+
else problems.push(`"${raw}": travel to "${arg}" \u2014 a mark (${Object.keys(CHOREO_MARKS).join(" ")}) or side,depth`);
|
|
2579
|
+
} else if (op === "@") {
|
|
2580
|
+
const named = { cam: "cam", room: 0, front: 0, back: 180, left: -90, right: 90 };
|
|
2581
|
+
if (arg in named) cue.face = named[arg];
|
|
2582
|
+
else if (Number.isFinite(Number(arg))) cue.face = Number(arg);
|
|
2583
|
+
else problems.push(`"${raw}": face "${arg}" \u2014 degrees, cam, room, left, right or back`);
|
|
2584
|
+
} else if (op === "/") {
|
|
2585
|
+
if (Number.isFinite(Number(arg))) cue.lean = clamp(Number(arg), -60, 60);
|
|
2586
|
+
else problems.push(`"${raw}": lean "${arg}" is not degrees`);
|
|
2587
|
+
} else if (op === "%") {
|
|
2588
|
+
if (Number.isFinite(Number(arg)) && Number(arg) > 0) cue.squash = clamp(Number(arg), 0.4, 1.5);
|
|
2589
|
+
else problems.push(`"${raw}": crouch "${arg}" is not a height scale`);
|
|
2590
|
+
} else if (op === "*") {
|
|
2591
|
+
if (Number.isFinite(Number(arg)) && Number(arg) > 0) cue.groove = clamp(Number(arg), 0.02, 0.3);
|
|
2592
|
+
else problems.push(`"${raw}": groove "${arg}" is not a depth`);
|
|
2593
|
+
} else if (op === "~") {
|
|
2594
|
+
if (Number.isFinite(Number(arg))) cue.turn = clamp(Number(arg), -1440, 1440);
|
|
2595
|
+
else problems.push(`"${raw}": turn "${arg}" is not degrees`);
|
|
2596
|
+
} else {
|
|
2597
|
+
if (Number.isFinite(Number(arg)) && Number(arg) >= 0) cue.lift = Math.min(Number(arg), 3);
|
|
2598
|
+
else problems.push(`"${raw}": leap "${arg}" is not a height`);
|
|
2599
|
+
}
|
|
2600
|
+
}
|
|
2601
|
+
cues.push(cue);
|
|
2602
|
+
b0 += cue.beats;
|
|
2603
|
+
}
|
|
2604
|
+
if (cues.length) lines.set(actor, { cues, beats: b0 });
|
|
2605
|
+
else problems.push(`line "${rawLine}" has no cues`);
|
|
2606
|
+
}
|
|
2607
|
+
return { lines, problems };
|
|
2608
|
+
}
|
|
2609
|
+
var CHOREO_HANDOVER = 0.5;
|
|
2610
|
+
var clamp = (v, lo, hi) => Math.min(hi, Math.max(lo, v));
|
|
2611
|
+
var smooth3 = (u) => {
|
|
2612
|
+
const c = clamp(u, 0, 1);
|
|
2613
|
+
return c * c * (3 - 2 * c);
|
|
2614
|
+
};
|
|
2615
|
+
var posMod = (v, m) => (v % m + m) % m;
|
|
2616
|
+
function choreoLine(sheet, slot) {
|
|
2617
|
+
return sheet?.lines.get(slot) ?? sheet?.lines.get("*") ?? null;
|
|
2618
|
+
}
|
|
2619
|
+
function choreoAt(line, beats) {
|
|
2620
|
+
const b = posMod(beats, line.beats);
|
|
2621
|
+
const n = line.cues.length;
|
|
2622
|
+
let i = 0;
|
|
2623
|
+
while (i < n - 1 && b >= line.cues[i].b0 + line.cues[i].beats) i++;
|
|
2624
|
+
const cue = line.cues[i], next = line.cues[(i + 1) % n], prev = line.cues[(i + n - 1) % n];
|
|
2625
|
+
let side = 0, depth = 0, centre = 0, face = 0, faceCam = 0, turn = 0;
|
|
2626
|
+
const end = (c) => {
|
|
2627
|
+
if (c.to === "centre") centre = 1;
|
|
2628
|
+
else if (c.to) {
|
|
2629
|
+
side = c.to[0];
|
|
2630
|
+
depth = c.to[1];
|
|
2631
|
+
centre = 0;
|
|
2632
|
+
}
|
|
2633
|
+
turn += c.turn;
|
|
2634
|
+
};
|
|
2635
|
+
const facing = (c) => {
|
|
2636
|
+
if (c.face === "cam") faceCam = 1;
|
|
2637
|
+
else if (c.face !== void 0) {
|
|
2638
|
+
face = c.face;
|
|
2639
|
+
faceCam = 0;
|
|
2640
|
+
}
|
|
2641
|
+
};
|
|
2642
|
+
for (let k = 0; k < i; k++) {
|
|
2643
|
+
end(line.cues[k]);
|
|
2644
|
+
facing(line.cues[k]);
|
|
2645
|
+
}
|
|
2646
|
+
facing(cue);
|
|
2647
|
+
const beat = b - cue.b0, u = clamp(beat / cue.beats, 0, 1);
|
|
2648
|
+
const s = smooth3(u);
|
|
2649
|
+
if (cue.to === "centre") centre += (1 - centre) * s;
|
|
2650
|
+
else if (cue.to) {
|
|
2651
|
+
side += (cue.to[0] - side) * s;
|
|
2652
|
+
depth += (cue.to[1] - depth) * s;
|
|
2653
|
+
centre *= 1 - s;
|
|
2654
|
+
}
|
|
2655
|
+
turn += cue.turn * s;
|
|
2656
|
+
const lift = cue.lift > 0 ? cue.lift * Math.sin(Math.PI * u) : 0;
|
|
2657
|
+
const last = i === n - 1;
|
|
2658
|
+
const h = Math.min(CHOREO_HANDOVER, cue.beats / 2, next.beats / 2);
|
|
2659
|
+
const ahead = smooth3((beat - (cue.beats - h)) / h);
|
|
2660
|
+
if (ahead > 0) {
|
|
2661
|
+
if (next.face === "cam") faceCam += (1 - faceCam) * ahead;
|
|
2662
|
+
else if (next.face !== void 0 && !last) {
|
|
2663
|
+
face += angleTo(face, next.face) * ahead;
|
|
2664
|
+
faceCam *= 1 - ahead;
|
|
2665
|
+
} else if (last) {
|
|
2666
|
+
face += angleTo(face, next.face === void 0 ? 0 : next.face) * ahead;
|
|
2667
|
+
faceCam *= 1 - ahead;
|
|
2668
|
+
}
|
|
2669
|
+
}
|
|
2670
|
+
const lean = (cue.lean ?? 0) + ((next.lean ?? 0) - (cue.lean ?? 0)) * ahead;
|
|
2671
|
+
let squash = (cue.squash ?? 1) + ((next.squash ?? 1) - (cue.squash ?? 1)) * ahead;
|
|
2672
|
+
if (next.lift > 0) squash *= 1 - 0.16 * ahead;
|
|
2673
|
+
if (cue.lift > 0) squash *= u < 0.15 ? 0.84 + 0.26 * smooth3(u / 0.15) : 1 + 0.1 * Math.sin(Math.PI * u) - 0.16 * smooth3((u - 0.85) / 0.15);
|
|
2674
|
+
if (prev.lift > 0 && n > 1) squash *= 0.84 + 0.16 * smooth3(beat / Math.min(0.75, cue.beats));
|
|
2675
|
+
const groove = cue.groove ?? 0;
|
|
2676
|
+
if (groove > 0 && cue.lift === 0) squash *= 1 - groove * (1 + Math.cos(2 * Math.PI * posMod(beats, 1))) / 2;
|
|
2677
|
+
return { cue, beat, u, next, ahead, side, depth, centre, face, faceCam, turn, lift, lean, squash };
|
|
2678
|
+
}
|
|
2679
|
+
function angleTo(a, b) {
|
|
2680
|
+
return posMod(b - a + 180, 360) - 180;
|
|
2681
|
+
}
|
|
2682
|
+
|
|
2498
2683
|
// src/vignette.ts
|
|
2499
2684
|
var DANCES = ["toprock", "footwork", "windmill", "headspin", "freeze", "drop"];
|
|
2500
|
-
var GESTURES = ["talk", "nod", "shake", "hop", "spin", "wiggle", "rest", "peek", "bow", "ink", ...DANCES];
|
|
2501
|
-
var BREED_GESTURES = { ink: ["octopus"] };
|
|
2685
|
+
var GESTURES = ["talk", "nod", "shake", "hop", "spin", "wiggle", "rest", "peek", "bow", "ink", "puff", ...DANCES];
|
|
2686
|
+
var BREED_GESTURES = { ink: ["octopus"], puff: ["blowfish"] };
|
|
2502
2687
|
var OPEN_MARKS = {
|
|
2503
2688
|
centre: { x: 0, y: 36, z: 0 },
|
|
2504
2689
|
left: { x: -70, y: 34, z: 0 },
|
|
@@ -2506,30 +2691,32 @@ var OPEN_MARKS = {
|
|
|
2506
2691
|
front: { x: 0, y: 30, z: 60 },
|
|
2507
2692
|
back: { x: 0, y: 40, z: -70 },
|
|
2508
2693
|
high: { x: 10, y: 62, z: -10 },
|
|
2509
|
-
low: { x: -10, y: 18, z: 10 }
|
|
2694
|
+
low: { x: -10, y: 18, z: 10 },
|
|
2695
|
+
// Behind the mic stand (`stageSet: club` stands it here, facing front).
|
|
2696
|
+
mic: { x: 0, y: 36, z: -4, solo: true }
|
|
2510
2697
|
};
|
|
2511
|
-
var ACTOR = "
|
|
2698
|
+
var ACTOR = "abcd";
|
|
2512
2699
|
var CRUISE = 17;
|
|
2513
2700
|
var STANDOFF = 20;
|
|
2514
2701
|
function spot(mark, actor, actors) {
|
|
2515
|
-
if (actors === 1) return mark;
|
|
2702
|
+
if (actors === 1 || mark.solo) return mark;
|
|
2516
2703
|
const a = actor / actors * Math.PI * 2 + 0.6;
|
|
2517
2704
|
return { x: mark.x + Math.cos(a) * STANDOFF, y: mark.y + (actor % 2 ? 2.5 : 0), z: mark.z + Math.sin(a) * STANDOFF };
|
|
2518
2705
|
}
|
|
2519
|
-
function parseVignette(script, marks) {
|
|
2706
|
+
function parseVignette(script, marks, audience = []) {
|
|
2520
2707
|
const problems = [];
|
|
2521
2708
|
const raw = String(script ?? "").split(/[|\n]/).map((b) => b.trim()).filter(Boolean);
|
|
2522
2709
|
let actors = 0;
|
|
2523
2710
|
for (const beat of raw) {
|
|
2524
2711
|
for (const cue of beat.replace(/^\d+(?:\.\d+)?s\s{0,16}:/, "").split(",")) {
|
|
2525
2712
|
for (const word of cue.trim().split(/\s+/)) {
|
|
2526
|
-
if (!/^[
|
|
2713
|
+
if (!/^[abcd]$/.test(word)) break;
|
|
2527
2714
|
actors = Math.max(actors, ACTOR.indexOf(word) + 1);
|
|
2528
2715
|
}
|
|
2529
2716
|
}
|
|
2530
2717
|
}
|
|
2531
|
-
if (!raw.length || !actors) return { actors: 0, beats: [], duration: 0, problems: raw.length ? ["no actor (a, b, c) named"] : [] };
|
|
2532
2718
|
const centre = marks.centre ?? marks.rug ?? OPEN_MARKS.centre;
|
|
2719
|
+
if (!raw.length || !actors) return { actors: 0, beats: [], duration: 0, problems: raw.length ? ["no actor (a\u2013d) named"] : [], seats: /* @__PURE__ */ new Map(), centre };
|
|
2533
2720
|
let at = Array.from({ length: actors }, (_, i) => spot(centre, i, actors));
|
|
2534
2721
|
const start = [];
|
|
2535
2722
|
const beats = [];
|
|
@@ -2547,7 +2734,7 @@ function parseVignette(script, marks) {
|
|
|
2547
2734
|
for (const cue of body.split(",").map((c) => c.trim()).filter(Boolean)) {
|
|
2548
2735
|
const words = cue.split(/\s+/);
|
|
2549
2736
|
const who = [];
|
|
2550
|
-
while (words.length && /^[
|
|
2737
|
+
while (words.length && /^[abcd]$/.test(words[0])) who.push(ACTOR.indexOf(words.shift()));
|
|
2551
2738
|
if (!who.length) {
|
|
2552
2739
|
problems.push(`beat ${bi + 1}: "${cue}" names no actor`);
|
|
2553
2740
|
continue;
|
|
@@ -2555,7 +2742,7 @@ function parseVignette(script, marks) {
|
|
|
2555
2742
|
for (let w = 0; w < words.length; w += 1) {
|
|
2556
2743
|
const word = words[w];
|
|
2557
2744
|
const target = (name) => {
|
|
2558
|
-
if (/^[
|
|
2745
|
+
if (/^[abcd]$/.test(name) && ACTOR.indexOf(name) < actors) return { actor: ACTOR.indexOf(name), point: null };
|
|
2559
2746
|
const mk = marks[name];
|
|
2560
2747
|
if (mk) return { actor: null, point: mk };
|
|
2561
2748
|
problems.push(`beat ${bi + 1}: no mark or actor "${name}" (marks: ${Object.keys(marks).join(", ")})`);
|
|
@@ -2627,7 +2814,9 @@ function parseVignette(script, marks) {
|
|
|
2627
2814
|
beats.push({ t0, dur: length, actors: start.map((p) => ({ to: p, moved: true, faceActor: null, facePoint: null, gesture: null, circle: null, follow: null })) });
|
|
2628
2815
|
t0 += length;
|
|
2629
2816
|
}
|
|
2630
|
-
|
|
2817
|
+
const seats = /* @__PURE__ */ new Map();
|
|
2818
|
+
for (const slot of audience) if (slot >= actors && !seats.has(slot)) seats.set(slot, seats.size);
|
|
2819
|
+
return { actors, beats, duration: t0, problems, seats, centre };
|
|
2631
2820
|
}
|
|
2632
2821
|
var smoother = (u) => {
|
|
2633
2822
|
const c = Math.min(1, Math.max(0, u));
|
|
@@ -2682,7 +2871,10 @@ function poseAcross(v, actor, tSec, dt, own) {
|
|
|
2682
2871
|
return poseOf(v, actor, snapBetween(v, actor, tSec, tSec + dt) ? tSec : tSec + dt, own);
|
|
2683
2872
|
}
|
|
2684
2873
|
function poseOf(v, actor, tSec, own) {
|
|
2685
|
-
if (
|
|
2874
|
+
if (!v.duration) return null;
|
|
2875
|
+
const seat = v.seats.get(actor);
|
|
2876
|
+
if (seat !== void 0) return crowdPose(v, seat, tSec, own);
|
|
2877
|
+
if (actor >= v.actors) return null;
|
|
2686
2878
|
const t = (tSec % v.duration + v.duration) % v.duration;
|
|
2687
2879
|
let bi = v.beats.findIndex((b) => t < b.t0 + b.dur);
|
|
2688
2880
|
if (bi < 0) bi = v.beats.length - 1;
|
|
@@ -2776,6 +2968,7 @@ function poseOf(v, actor, tSec, own) {
|
|
|
2776
2968
|
pitch = -0.08 * e;
|
|
2777
2969
|
break;
|
|
2778
2970
|
// ink: the body holds still; the octopus squirts its cloud (the tank's InkLayer) and pales.
|
|
2971
|
+
// puff: the body holds still; the blowfish gulps itself round (puffer.ts PufferInput.puffCue).
|
|
2779
2972
|
// The dances, for a fish with no clips for them: the whole body does what it can.
|
|
2780
2973
|
case "toprock":
|
|
2781
2974
|
roll += Math.sin(bt * Math.PI * 2) * 0.3 * e;
|
|
@@ -2819,6 +3012,152 @@ function poseOf(v, actor, tSec, own) {
|
|
|
2819
3012
|
}
|
|
2820
3013
|
return { x, y: Math.max(9, y), z, fx, fy, fz, roll, doing, lookAt: target ?? null, ...gesture ? { gesture } : {} };
|
|
2821
3014
|
}
|
|
3015
|
+
var CROWD = {
|
|
3016
|
+
/** Rows out from the stage's middle, front (nearest the camera) first: distance, height, seats.
|
|
3017
|
+
* Raked: each row higher than the one before, so the back rows see over the front. */
|
|
3018
|
+
rows: [{ r: 48, y: 15, n: 6 }, { r: 64, y: 21, n: 8 }, { r: 80, y: 27, n: 10 }],
|
|
3019
|
+
/** How far round each row reaches, radians either side of straight out front. */
|
|
3020
|
+
arc: 0.7,
|
|
3021
|
+
/** A laugh: it breaks this long before the talk ends, and rolls on this long. */
|
|
3022
|
+
lead: 0.45,
|
|
3023
|
+
laugh: 2.8,
|
|
3024
|
+
/** Seconds a seat can be late to a laugh or the applause. */
|
|
3025
|
+
late: 0.55,
|
|
3026
|
+
/** One seat in this many is a tough crowd: it chuckles where the rest laugh. */
|
|
3027
|
+
tough: 5,
|
|
3028
|
+
/** Seconds the room's heads take to follow the stage: they look where it was, a moment ago. */
|
|
3029
|
+
follow: 1.05
|
|
3030
|
+
};
|
|
3031
|
+
var seatHash = (seat, k = 0) => {
|
|
3032
|
+
const s = Math.sin(seat * 127.1 + k * 311.7 + 74.7) * 43758.5453;
|
|
3033
|
+
return s - Math.floor(s);
|
|
3034
|
+
};
|
|
3035
|
+
function crowdSeat(v, seat) {
|
|
3036
|
+
let row = 0, first = 0;
|
|
3037
|
+
while (row < CROWD.rows.length - 1 && seat >= first + CROWD.rows[row].n) {
|
|
3038
|
+
first += CROWD.rows[row].n;
|
|
3039
|
+
row += 1;
|
|
3040
|
+
}
|
|
3041
|
+
const r = CROWD.rows[row];
|
|
3042
|
+
const inRow = Math.min(r.n, v.seats.size - first);
|
|
3043
|
+
const mid = (inRow - 1) / 2;
|
|
3044
|
+
const order = Array.from({ length: inRow }, (_, p) => p).sort((p, q) => Math.abs(p - mid) - Math.abs(q - mid) || q - p);
|
|
3045
|
+
const i = order[seat - first] ?? seat - first;
|
|
3046
|
+
const step = CROWD.arc * 2 / Math.max(1, r.n - 1);
|
|
3047
|
+
const a = (i - (inRow - 1) / 2) * step + (seatHash(seat, 1) - 0.5) * step * 0.3;
|
|
3048
|
+
const d = r.r + (seatHash(seat, 2) - 0.5) * 5;
|
|
3049
|
+
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 };
|
|
3050
|
+
}
|
|
3051
|
+
var REACTIONS = /* @__PURE__ */ new WeakMap();
|
|
3052
|
+
function reactionsOf(v) {
|
|
3053
|
+
let r = REACTIONS.get(v);
|
|
3054
|
+
if (r) return r;
|
|
3055
|
+
r = { laughs: [], claps: [] };
|
|
3056
|
+
for (const b of v.beats) {
|
|
3057
|
+
if (b.actors.some((a) => a.gesture === "talk")) r.laughs.push(b.t0 + b.dur);
|
|
3058
|
+
if (b.actors.some((a) => a.gesture === "bow")) r.claps.push([b.t0, b.t0 + b.dur]);
|
|
3059
|
+
}
|
|
3060
|
+
REACTIONS.set(v, r);
|
|
3061
|
+
return r;
|
|
3062
|
+
}
|
|
3063
|
+
function holderAt(v, tSec) {
|
|
3064
|
+
const t = (tSec % v.duration + v.duration) % v.duration;
|
|
3065
|
+
const beat = v.beats.find((b) => t < b.t0 + b.dur) ?? v.beats[v.beats.length - 1];
|
|
3066
|
+
const talking = beat.actors.findIndex((a) => a.gesture === "talk");
|
|
3067
|
+
if (talking >= 0) return talking;
|
|
3068
|
+
let best = 0, bestD = Infinity;
|
|
3069
|
+
beat.actors.forEach((a, i) => {
|
|
3070
|
+
const d = Math.hypot(a.to.x - v.centre.x, a.to.z - v.centre.z);
|
|
3071
|
+
if (d < bestD) {
|
|
3072
|
+
bestD = d;
|
|
3073
|
+
best = i;
|
|
3074
|
+
}
|
|
3075
|
+
});
|
|
3076
|
+
return best;
|
|
3077
|
+
}
|
|
3078
|
+
function within(v, t, start, len, out) {
|
|
3079
|
+
for (const shift of [-1, 0, 1]) {
|
|
3080
|
+
const u = (t - start - shift * v.duration) / len;
|
|
3081
|
+
if (u > 0 && u < 1) {
|
|
3082
|
+
if (out) out.shift = shift;
|
|
3083
|
+
return u;
|
|
3084
|
+
}
|
|
3085
|
+
}
|
|
3086
|
+
return -1;
|
|
3087
|
+
}
|
|
3088
|
+
function crowdPose(v, seat, tSec, own) {
|
|
3089
|
+
const p = crowdSeat(v, seat);
|
|
3090
|
+
const t = (tSec % v.duration + v.duration) % v.duration;
|
|
3091
|
+
let gx = 0, gy = 0, gz = 0;
|
|
3092
|
+
const taps = 4;
|
|
3093
|
+
for (let k = 0; k < taps; k++) {
|
|
3094
|
+
const tk = tSec - k / (taps - 1) * CROWD.follow;
|
|
3095
|
+
const m = placeOf(v, holderAt(v, tk), tk);
|
|
3096
|
+
gx += m.x / taps;
|
|
3097
|
+
gy += m.y / taps;
|
|
3098
|
+
gz += m.z / taps;
|
|
3099
|
+
}
|
|
3100
|
+
const look = { x: gx, y: gy, z: gz };
|
|
3101
|
+
let fx = gx - p.x, fy = (gy - p.y) * 0.4, fz = gz - p.z;
|
|
3102
|
+
let fl = Math.hypot(fx, fy, fz) || 1;
|
|
3103
|
+
fx /= fl;
|
|
3104
|
+
fy /= fl;
|
|
3105
|
+
fz /= fl;
|
|
3106
|
+
const { x, z } = p;
|
|
3107
|
+
let y = p.y + Math.sin(tSec * 1.1 + seat * 2.3) * 0.7;
|
|
3108
|
+
let roll = Math.sin(tSec * 0.7 + seat) * 0.03;
|
|
3109
|
+
let pitch = 0;
|
|
3110
|
+
let doing = "watching";
|
|
3111
|
+
let gesture;
|
|
3112
|
+
const { laughs, claps } = reactionsOf(v);
|
|
3113
|
+
const late = seatHash(seat, 4) * CROWD.late;
|
|
3114
|
+
let lu = -1, lat = 0, lk = 0;
|
|
3115
|
+
const loop = { shift: 0 }, lap = Math.floor(tSec / v.duration);
|
|
3116
|
+
let burst;
|
|
3117
|
+
for (let k = 0; k < laughs.length; k++) {
|
|
3118
|
+
const at = laughs[k];
|
|
3119
|
+
const len = CROWD.laugh * (0.8 + 0.45 * seatHash(seat, 10 + k));
|
|
3120
|
+
const u = within(v, t, at - CROWD.lead + late, len, loop);
|
|
3121
|
+
if (u > lu) {
|
|
3122
|
+
lu = u;
|
|
3123
|
+
lat = u * len;
|
|
3124
|
+
lk = k;
|
|
3125
|
+
}
|
|
3126
|
+
}
|
|
3127
|
+
if (lu > 0) {
|
|
3128
|
+
const e = Math.min(1, lu * 6) * (1 - smoother((lu - 0.35) / 0.65));
|
|
3129
|
+
const tough = seatHash(seat, 5) < 1 / CROWD.tough;
|
|
3130
|
+
const amp = tough ? 0.4 : 0.75 + 0.5 * seatHash(seat, 6);
|
|
3131
|
+
const hz = 5.5 + 2.5 * seatHash(seat, 7);
|
|
3132
|
+
doing = "laughing";
|
|
3133
|
+
pitch = (0.3 + Math.sin(lat * hz * 2) * 0.1) * e * amp;
|
|
3134
|
+
y += Math.abs(Math.sin(lat * hz)) * 3.4 * e * amp;
|
|
3135
|
+
roll += Math.sin(lat * hz) * 0.16 * e * amp;
|
|
3136
|
+
if (own?.("talk")) gesture = { name: "talk", at: lat, bar: lat, weight: e * amp };
|
|
3137
|
+
if (!tough) {
|
|
3138
|
+
const start = laughs[lk] - CROWD.lead + late + (lap + loop.shift) * v.duration;
|
|
3139
|
+
burst = { key: `crowd:${seat}:${lk}:${lap + loop.shift}`, t: start + 0.25 };
|
|
3140
|
+
}
|
|
3141
|
+
}
|
|
3142
|
+
for (const [a, b] of claps) {
|
|
3143
|
+
const u = within(v, t, a + late, b - a);
|
|
3144
|
+
if (u <= 0) continue;
|
|
3145
|
+
const e = bell(u) ** 0.6, ct = u * (b - a);
|
|
3146
|
+
doing = "applauding";
|
|
3147
|
+
y += Math.abs(Math.sin(ct * 6.5)) * 1.3 * e;
|
|
3148
|
+
roll += Math.sin(ct * 13) * 0.22 * e;
|
|
3149
|
+
if (own?.("wiggle")) gesture = { name: "wiggle", at: ct, bar: ct, weight: e };
|
|
3150
|
+
break;
|
|
3151
|
+
}
|
|
3152
|
+
if (pitch) {
|
|
3153
|
+
fy += Math.tan(pitch) * Math.hypot(fx, fz);
|
|
3154
|
+
fl = Math.hypot(fx, fy, fz) || 1;
|
|
3155
|
+
fx /= fl;
|
|
3156
|
+
fy /= fl;
|
|
3157
|
+
fz /= fl;
|
|
3158
|
+
}
|
|
3159
|
+
return { x, y: Math.max(9, y), z, fx, fy, fz, roll, doing, lookAt: look, ...gesture ? { gesture } : {}, ...burst ? { burst } : {} };
|
|
3160
|
+
}
|
|
2822
3161
|
var polar = (deg, r, y) => ({ x: Math.cos(deg * Math.PI / 180) * r, y, z: Math.sin(deg * Math.PI / 180) * r });
|
|
2823
3162
|
var INTERIOR_MARK_POLAR = {
|
|
2824
3163
|
door: { deg: 28, r: 124, y: 22 },
|
|
@@ -2909,11 +3248,48 @@ var VIGNETTES = {
|
|
|
2909
3248
|
"6s: a @c bow, b @c bow, c >front bow",
|
|
2910
3249
|
"5s: a >left, b >right, c @front shake",
|
|
2911
3250
|
"5s: c >back"
|
|
3251
|
+
].join(" | "),
|
|
3252
|
+
// Open mic: a host (d) brings on three comics (a b c) in turn — each two bits and a bow — and sends them off;
|
|
3253
|
+
// the two waiting stand in the wings and watch whoever has the mic, nodding along when the room laughs. Then
|
|
3254
|
+
// a goodnight from the host, and the company bows from where it stands. Written for an `@audience` out front (it laughs as each talk
|
|
3255
|
+
// ends), `stageSet: club` and one spot standing on the mic (`spotRig: @mic`): whoever steps up is lit.
|
|
3256
|
+
// 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:-
|
|
3257
|
+
openmic: [
|
|
3258
|
+
"4s: a =left, b =right, c =left, d =right",
|
|
3259
|
+
"5s: d >mic @front, a @mic nod, b @mic nod, c @mic nod",
|
|
3260
|
+
"5s: d @front talk, a @mic, b @mic, c @mic",
|
|
3261
|
+
"3s: d @a wiggle, a @mic nod, b @mic nod, c @mic nod",
|
|
3262
|
+
"5s: d >right, a >mic @front, b @mic, c @mic",
|
|
3263
|
+
"6s: a @front talk, b @mic, c @mic, d @mic nod",
|
|
3264
|
+
"3s: a @front nod, b @mic nod, c @mic nod, d @mic nod",
|
|
3265
|
+
"6s: a @front talk, b @mic, c @mic, d @mic",
|
|
3266
|
+
"3s: a @front bow, b @mic nod, c @mic nod, d @mic nod",
|
|
3267
|
+
"5s: a >left, d >mic @front, b @mic, c @mic",
|
|
3268
|
+
"4s: d @front talk, a @mic nod, b @mic, c @mic",
|
|
3269
|
+
"3s: d @b wiggle, a @mic, b @mic nod, c @mic",
|
|
3270
|
+
"5s: d >right, b >mic @front, a @mic, c @mic",
|
|
3271
|
+
"6s: b @front talk, a @mic, c @mic, d @mic nod",
|
|
3272
|
+
"3s: b @front rest, a @mic nod, c @mic nod, d @mic nod",
|
|
3273
|
+
"6s: b @front talk, a @mic, c @mic, d @mic",
|
|
3274
|
+
"3s: b @front bow, a @mic nod, c @mic nod, d @mic nod",
|
|
3275
|
+
"5s: b >right, d >mic @front, a @mic, c @mic",
|
|
3276
|
+
"4s: d @front talk, a @mic, b @mic nod, c @mic",
|
|
3277
|
+
"3s: d @c wiggle, a @mic, b @mic, c @mic nod",
|
|
3278
|
+
"5s: d >right, c >mic @front, a @mic, b @mic",
|
|
3279
|
+
"6s: c @front talk, a @mic, b @mic, d @mic nod",
|
|
3280
|
+
"3s: c @front shake, a @mic nod, b @mic nod, d @mic nod",
|
|
3281
|
+
"6s: c @front talk, a @mic, b @mic, d @mic",
|
|
3282
|
+
"3s: c @front bow, a @mic nod, b @mic nod, d @mic nod",
|
|
3283
|
+
"5s: c >left, d >mic @front, a @mic, b @mic",
|
|
3284
|
+
"5s: d @front talk, a @mic nod, b @mic, c @mic nod",
|
|
3285
|
+
"4s: a @front bow, b @front bow, c @front bow, d @front bow",
|
|
3286
|
+
"5s: d >right, a @mic, b @mic, c @mic"
|
|
2912
3287
|
].join(" | ")
|
|
2913
3288
|
};
|
|
2914
3289
|
var VIGNETTE_CUES = {
|
|
2915
3290
|
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:-"
|
|
3291
|
+
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:-",
|
|
3292
|
+
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
3293
|
};
|
|
2918
3294
|
function resolveVignette(value) {
|
|
2919
3295
|
const v = String(value ?? "").trim();
|
|
@@ -2926,23 +3302,23 @@ var PARAM_DOCS = {
|
|
|
2926
3302
|
cameraAzimuth: "Orbit bearing in degrees. 0 looks at the village from the front.",
|
|
2927
3303
|
cameraElevation: "Degrees above the floor. 8\u201315 is eye level with the fish; 40+ looks down on a castle.",
|
|
2928
3304
|
cameraDistance: "Distance from the tank centre. 80 is a close-up, 170\u2013220 a village, 300+ a landmark.",
|
|
2929
|
-
shot: "A
|
|
3305
|
+
shot: "A framing: `orbit`, `hero` (three-quarter), `front` (level, long lens), `low`, `top` (a map), `surface`, `macro` (nearest landmark), `cast` (the mix's first three). Azimuth and autoRotate turn it, distance scales it.",
|
|
2930
3306
|
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
|
|
3307
|
+
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
3308
|
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
|
|
3309
|
+
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
3310
|
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
3311
|
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
|
-
nearFade: "A creature at the lens dissolves
|
|
3312
|
+
nearFade: "A creature at the lens dissolves, not sliced, 0\u20131. The followed fish never fades.",
|
|
2937
3313
|
// cast
|
|
2938
3314
|
fishCount: "How many fish when `fishMix` is empty (all are `fishUrl`).",
|
|
2939
3315
|
finish: "A final full-screen pass: gentle grade, soft vignette, dither against banding, and restrained bloom on high-end devices (0 = none; never on the low tier).",
|
|
2940
3316
|
tiltShift: "Tilt-shift, 0\u20131: sharp through the middle, soft above and below, like a model. Mid/high tiers.",
|
|
2941
|
-
depthOfField: "Depth of field, 0\u20131: the subject
|
|
3317
|
+
depthOfField: "Depth of field, 0\u20131: the subject (followed fish, `cast` leads) sharp, the rest soft. High tier.",
|
|
2942
3318
|
ambientOcclusion: "Ambient occlusion, 0\u20131: crevices darken. High tier.",
|
|
2943
|
-
highlights: "Bright light rolls off instead of clipping, 0\u20131: a fish in a shaft stays a fish.
|
|
3319
|
+
highlights: "Bright light rolls off instead of clipping, 0\u20131: a fish in a shaft stays a fish. Mid/high tiers.",
|
|
2944
3320
|
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,
|
|
3321
|
+
fishMix: "The cast: `id[:count][@style]`, comma-separated: `100:2@drift, 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: `starfishDance`, `choreography`), snail. `dot` (a box on legs), `dolphin` (flips, breaches) play vignette gestures. Overrides fishCount/fishUrl.",
|
|
2946
3322
|
dracoPath: "Where the Draco decoder lives; leave empty on a channel. Only the page's own origin or the shipped copy is used.",
|
|
2947
3323
|
swimSpeed: "Global swim speed multiplier. 0.4 is contemplative, 1 lively.",
|
|
2948
3324
|
swimStyle: "How the cast swims unless a fishMix entry says otherwise (`@style`). `auto` picks by breed.",
|
|
@@ -2965,15 +3341,18 @@ var PARAM_DOCS = {
|
|
|
2965
3341
|
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
3342
|
fishGlow: "How strongly GLOW- parts of a fish bloom and light their neighbours and the floor.",
|
|
2967
3343
|
fishMetal: "`on` (default) gives METAL- parts a polished, reflective finish.",
|
|
2968
|
-
snailShell: "
|
|
2969
|
-
starfishDance: "Starfish dance: `aerobics
|
|
2970
|
-
danceTempo: "
|
|
2971
|
-
|
|
3344
|
+
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.",
|
|
3345
|
+
starfishDance: "Starfish dance: `aerobics` (a class in unison), `freestyle` (a move each per eight counts), `duet` (couples), `off`. Cast a few: `starfish:7`.",
|
|
3346
|
+
danceTempo: "Dance BPM; glides on the beat.",
|
|
3347
|
+
choreography: "Starfish dance cues, on the beat (see grammar).",
|
|
3348
|
+
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
3349
|
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
|
-
fishShadows: "Soft shadows
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
3350
|
+
fishShadows: "Soft shadows under the swimmers, 0\u20131: dark near the sand, faint high up.",
|
|
3351
|
+
fishAO: "A fish's own corners darken, 0\u20131: solid fish. All tiers.",
|
|
3352
|
+
fishFadeIn: "Seconds an arriving fish takes to fade in (0 pops in).",
|
|
3353
|
+
sceneryShadows: "Soft shadows under rocks, homes, castle, pyramids, geodes, lamps, 0\u20131: nothing floats.",
|
|
3354
|
+
voxelAO: "Plant cubes darken where they touch each other and the sand, 0\u20131. All tiers.",
|
|
3355
|
+
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
3356
|
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
3357
|
eyeLife: "Eyes blink, look, and emote (0 = painted on). Redraws each token's own pixel-grid eye; black and white only.",
|
|
2979
3358
|
// water and room
|
|
@@ -2983,21 +3362,21 @@ var PARAM_DOCS = {
|
|
|
2983
3362
|
floorColor: "Sea-floor colour.",
|
|
2984
3363
|
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
3364
|
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`:
|
|
3365
|
+
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
3366
|
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
3367
|
moteDensity: "Suspended motes.",
|
|
2989
3368
|
moteColor: "Their colour.",
|
|
2990
3369
|
environment: "A room preset (floor, ceiling, light shafts). `void` for the mineral-world scenes.",
|
|
2991
3370
|
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
3371
|
waterY: "Height of the water ceiling; -1 = the environment's own.",
|
|
2993
|
-
rayStrength: "
|
|
3372
|
+
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
3373
|
raySoftness: "Shafts as soft volume, not flat sheets, 0\u20131. 1 for anything cinematic.",
|
|
2995
|
-
floorDetail: "A seabed, not a coloured plane, 0\u20131: ripples, silt, grain;
|
|
3374
|
+
floorDetail: "A seabed, not a coloured plane, 0\u20131: ripples, silt, grain; its far edge fades into the fog.",
|
|
2996
3375
|
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
3376
|
causticScale: "Size of the caustic cells: below 1 a fine shimmer, above 1 broad slow bands.",
|
|
2998
|
-
surfaceMirror: "The surface from below: a window
|
|
3377
|
+
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
3378
|
// world
|
|
3000
|
-
propMix: "Crystals: `crystal[:count][@habit][/palette][*size]`. Habits lotus
|
|
3379
|
+
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
3380
|
envProps: "`on` lets the environment preset plant its own crystals when `propMix` is empty.",
|
|
3002
3381
|
crystalScale: "Size of the whole mineral world (crystals, rocks, homes, flora).",
|
|
3003
3382
|
crystalWild: "Organic individuality: lean, bald sides, branching. 0 is a catalogue crystal, 1 is coral.",
|
|
@@ -3006,30 +3385,32 @@ var PARAM_DOCS = {
|
|
|
3006
3385
|
crystalTint: "How much nearby light sources colour a passing fish. 0.5\u20130.7 ties the cast to the place.",
|
|
3007
3386
|
rockDensity: "Faceted boulders, an arch and a back ridge around the crystals.",
|
|
3008
3387
|
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.
|
|
3388
|
+
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
3389
|
stoneStyle: "`weathered`: river-grey rocks and homes with silt on top, not dark holes in a lit room. `classic` (default): dark slate.",
|
|
3011
3390
|
interior: "`geode` sets the whole scene INSIDE a geode home: furniture, chandelier, marks like `table`, `bed`, `door`.",
|
|
3012
3391
|
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
|
|
3392
|
+
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
3393
|
geodeMix: "Which geodes: `kind[:weight]`, comma-separated. geode cathedral cluster cavern (the cavern is one at most).",
|
|
3015
3394
|
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
|
-
|
|
3395
|
+
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`.",
|
|
3396
|
+
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`.",
|
|
3397
|
+
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.",
|
|
3398
|
+
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.",
|
|
3399
|
+
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.",
|
|
3400
|
+
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
3401
|
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
|
|
3402
|
+
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.",
|
|
3403
|
+
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
3404
|
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
|
|
3405
|
+
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
3406
|
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
3407
|
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
3408
|
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
|
|
3409
|
+
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.",
|
|
3410
|
+
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.",
|
|
3411
|
+
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
3412
|
bubbleVents: "Bubbles from chimneys, fissures and crystals.",
|
|
3032
|
-
bubbleStyle: "
|
|
3413
|
+
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
3414
|
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
3415
|
co2Mist: "A fine haze of tiny bubbles from the vents, drifting on a slow current (0 = none).",
|
|
3035
3416
|
marineSnow: "Drifting lit particles; gives the water depth.",
|
|
@@ -3041,19 +3422,19 @@ var PARAM_DOCS = {
|
|
|
3041
3422
|
landmark: "One thing bigger than everything else. `castle`: voxel walls, crystal-spired towers, gate, road, plaza. `citadel`: two storeys. Marks: `gate`, `plaza`, `courtyard`.",
|
|
3042
3423
|
// stage
|
|
3043
3424
|
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
|
|
3425
|
+
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
3426
|
spotColor: "Colour of the single `followSpot`.",
|
|
3046
3427
|
spotShadow: "The spotted fish's shadow in its pool. 0 none, 1 true size.",
|
|
3047
|
-
spotRig: "Up to three spots: `slot[/color][*radius]
|
|
3048
|
-
spotCues: "
|
|
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."
|
|
3428
|
+
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.",
|
|
3429
|
+
spotCues: "Looping cues for the rig: `8s:a, 12s:a+b, 4s:-` (`-` blackout, cues \u2265 2 s). Spots cross-fade.",
|
|
3430
|
+
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
3431
|
};
|
|
3051
3432
|
var GRAMMAR = `
|
|
3052
3433
|
fishMix id[:count][@style][*size], \u2026 100:2@drift, 257:1, seahorse:3, shark:1@patrol*1.5
|
|
3053
3434
|
id = a minted token (1\u2013512) or a breed (betafish angelfish seahorse seaturtle). Minted fish are
|
|
3054
3435
|
individuals: a count casts DISTINCT neighbours. Bundled creatures (a count is copies): shark
|
|
3055
3436
|
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.
|
|
3437
|
+
breed's own size. Styles: loop school drift hover patrol bottom surface follow pair chase audience.
|
|
3057
3438
|
propMix crystal[#id][:count][@habit][/palette][*size], \u2026 crystal:3@druse/rainbow, crystal:1@spire/cyan*6
|
|
3058
3439
|
habits lotus spire druse scatter coral \xB7 palettes env rainbow blue hotpink purple seafoam yellow orange cyan white glass
|
|
3059
3440
|
floraMix species[:weight], \u2026 kelp:3, whip, seapen \xB7 staghorn:2, brain, anemone:2, clam
|
|
@@ -3069,17 +3450,19 @@ pyramidShow auto | solo | routine | <sec>s:<routine>, \u2026 8s:rings, 8s:bea
|
|
|
3069
3450
|
geodeMix kind[:weight], \u2026 geode cathedral cluster cavern geode:3, cathedral, cluster
|
|
3070
3451
|
geodeMineral world | mineral, \u2026 amethyst agate celestine citrine carnelian rose emerald quartz smoky
|
|
3071
3452
|
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
|
|
3453
|
+
spotRig slot|@mark[/color][*radius], \u2026 (\u22643; they become a, b, c) 0/#ffd27a*24, 1/#ff8ad0*24, 2/#7fdcff*24
|
|
3073
3454
|
spotCues <sec>s:<spots>, \u2026 loops 4s:-, 7s:a, 7s:b, 12s:a+b
|
|
3455
|
+
choreography [a|*:] <beats>:<move> [>to @face ~turn ^leap /lean %crouch *groove],\u2026|\u2026 moves ${STARFISH_MOVE_NAMES.join(" ")}
|
|
3074
3456
|
signs kind[@place][>target][/#color][*size][:text], \u2026 plank@gate:"Welcome to the reef", neon@home1/#ff4fa0:OPEN, led@plaza:TIDE 4.2M
|
|
3075
3457
|
kinds plank arrow ring porthole neon led \xB7 places gate plaza courtyard hub fountain home1 home2 home3 centre left right front back
|
|
3076
3458
|
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
|
|
3459
|
+
vignette beats separated by |, cues by comma; actors a b c d are fish slots 0 1 2 3
|
|
3078
3460
|
[<sec>s:] actor =mark be there, no travel (a later beat's = snaps at its start) actor >mark swim there
|
|
3079
3461
|
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
|
-
actor follow actor gestures: talk nod shake hop spin wiggle rest peek bow \xB7 ink (octopus
|
|
3462
|
+
actor follow actor gestures: talk nod shake hop spin wiggle rest peek bow \xB7 ink (octopus) \xB7 puff (blowfish)
|
|
3081
3463
|
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
3464
|
Give every beat a duration and end where you began and a spotCues sheet stays in step forever.
|
|
3465
|
+
Fish cast @audience sit out front facing the stage: they laugh as each talk ends and applaud each bow.
|
|
3083
3466
|
Marks outdoors: ${Object.keys(OPEN_MARKS).join(" ")} \u2014 plus the world's own:
|
|
3084
3467
|
home1 home1in \u2026 (geodeHomes), gate plaza courtyard (landmark), hub (paths), fountain (fountain).
|
|
3085
3468
|
Marks indoors (interior: geode): ${Object.keys(INTERIOR_MARKS).join(" ")}
|
|
@@ -3089,7 +3472,7 @@ vignette beats separated by |, cues by comma; actors a b c are fish slots 0 1 2
|
|
|
3089
3472
|
var NIGHT = { fogColor: "#0a0a1a", fogNear: 120, fogFar: 700 };
|
|
3090
3473
|
var STAGE = { fogColor: "#01030a", floorColor: "#0a1322", fogNear: 120, fogFar: 650, autoRotate: 0 };
|
|
3091
3474
|
var ALIVE = { eyeLife: 1 };
|
|
3092
|
-
var CINEMATIC = { highlights: 1, raySoftness: 1, nearFade: 1, finish: 0.6 };
|
|
3475
|
+
var CINEMATIC = { highlights: 1, raySoftness: 1, nearFade: 1, voxelAO: 1, fishAO: 1, finish: 0.6 };
|
|
3093
3476
|
var RECIPES = [
|
|
3094
3477
|
{
|
|
3095
3478
|
id: "geode-harbor",
|
|
@@ -3273,6 +3656,28 @@ var RECIPES = [
|
|
|
3273
3656
|
cameraAzimuth: 0
|
|
3274
3657
|
}
|
|
3275
3658
|
},
|
|
3659
|
+
{
|
|
3660
|
+
id: "open-mic",
|
|
3661
|
+
label: "Open mic",
|
|
3662
|
+
what: "A comedy club: a host brings three comic dolphins to the mic in turn; a seated crowd laughs and applauds.",
|
|
3663
|
+
params: {
|
|
3664
|
+
...ALIVE,
|
|
3665
|
+
...STAGE,
|
|
3666
|
+
floorColor: "#1a1210",
|
|
3667
|
+
vignette: "openmic",
|
|
3668
|
+
stageSet: "club",
|
|
3669
|
+
spotRig: "@mic/#ffc890*26",
|
|
3670
|
+
spotCues: VIGNETTE_CUES.openmic,
|
|
3671
|
+
spotStrength: 0.3,
|
|
3672
|
+
fishMix: "dolphin:4, dori:2@audience*0.7, crab:2@audience*0.6, octopus:1@audience*0.7, babyfish:5@audience",
|
|
3673
|
+
dolphinCast: "showman, drifter, bigguy, host",
|
|
3674
|
+
signs: "neon@back/#ff4fa0:OPEN MIC",
|
|
3675
|
+
finish: 0.6,
|
|
3676
|
+
cameraDistance: 125,
|
|
3677
|
+
cameraElevation: 12,
|
|
3678
|
+
cameraAzimuth: 0
|
|
3679
|
+
}
|
|
3680
|
+
},
|
|
3276
3681
|
{
|
|
3277
3682
|
id: "follow-spot",
|
|
3278
3683
|
label: "Follow-spot",
|
|
@@ -3327,7 +3732,7 @@ var RECIPES = [
|
|
|
3327
3732
|
{
|
|
3328
3733
|
id: "reef-characters",
|
|
3329
3734
|
label: "Reef characters",
|
|
3330
|
-
what: "The bundled characters on a reef: tangs that look back, blowfish that puff and flirt, an octopus that
|
|
3735
|
+
what: "The bundled characters on a reef: tangs that look back, blowfish that puff and flirt, an octopus that inks, a crab.",
|
|
3331
3736
|
params: {
|
|
3332
3737
|
...ALIVE,
|
|
3333
3738
|
environment: "reef",
|
|
@@ -3346,7 +3751,7 @@ var RECIPES = [
|
|
|
3346
3751
|
{
|
|
3347
3752
|
id: "starfish-class",
|
|
3348
3753
|
label: "Starfish aerobics",
|
|
3349
|
-
what: "Seven starfish stand up on a pale ice floor, face the camera and dance
|
|
3754
|
+
what: "Seven starfish stand up on a pale ice floor, face the camera and dance aerobics in unison, an instructor in front.",
|
|
3350
3755
|
params: { environment: "ice", fishMix: "starfish:7", starfishDance: "aerobics", danceTempo: 120, shot: "front", autoRotate: 0 }
|
|
3351
3756
|
},
|
|
3352
3757
|
{
|
|
@@ -3370,7 +3775,7 @@ var RECIPES = [
|
|
|
3370
3775
|
{
|
|
3371
3776
|
id: "dot-delivers",
|
|
3372
3777
|
label: "Dot delivers",
|
|
3373
|
-
what: "Dot the courier
|
|
3778
|
+
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
3779
|
params: {
|
|
3375
3780
|
...ALIVE,
|
|
3376
3781
|
geodeHomes: 3,
|
|
@@ -3388,7 +3793,7 @@ var RECIPES = [
|
|
|
3388
3793
|
{
|
|
3389
3794
|
id: "snail-night",
|
|
3390
3795
|
label: "Snails by night",
|
|
3391
|
-
what: "Glowing snails on a dark floor: each spiral breathes light,
|
|
3796
|
+
what: "Glowing snails on a dark floor: each spiral breathes light, feelers pool green on the sand, the camera beside one.",
|
|
3392
3797
|
params: {
|
|
3393
3798
|
...ALIVE,
|
|
3394
3799
|
...NIGHT,
|
|
@@ -3407,7 +3812,7 @@ var RECIPES = [
|
|
|
3407
3812
|
{
|
|
3408
3813
|
id: "reef-cinematic",
|
|
3409
3814
|
label: "Reef, cinematic",
|
|
3410
|
-
what: "A small reef cast framed close wherever it swims, in soft shafts, each fish over its shadow.
|
|
3815
|
+
what: "A small reef cast framed close wherever it swims, in soft shafts, each fish over its shadow.",
|
|
3411
3816
|
params: {
|
|
3412
3817
|
...ALIVE,
|
|
3413
3818
|
...CINEMATIC,
|
|
@@ -3432,7 +3837,7 @@ var RECIPES = [
|
|
|
3432
3837
|
{
|
|
3433
3838
|
id: "night-cinematic",
|
|
3434
3839
|
label: "Deep night, cinematic",
|
|
3435
|
-
what: "The deep at night: glowfish under jellyfish lanterns, a shark sliding through,
|
|
3840
|
+
what: "The deep at night: glowfish under jellyfish lanterns, a shark sliding through, outlines rimmed in light.",
|
|
3436
3841
|
params: {
|
|
3437
3842
|
...ALIVE,
|
|
3438
3843
|
...CINEMATIC,
|
|
@@ -3456,7 +3861,7 @@ var RECIPES = [
|
|
|
3456
3861
|
{
|
|
3457
3862
|
id: "village-cinematic",
|
|
3458
3863
|
label: "Village, cinematic",
|
|
3459
|
-
what: "Dot, Puff and Hex meet in a sunlit village of stone homes, seen
|
|
3864
|
+
what: "Dot, Puff and Hex meet in a sunlit village of stone homes, seen through a tilt-shift lens.",
|
|
3460
3865
|
params: {
|
|
3461
3866
|
...ALIVE,
|
|
3462
3867
|
...CINEMATIC,
|
|
@@ -3504,6 +3909,7 @@ function validateMetaquariumParams(params) {
|
|
|
3504
3909
|
if (str("townProps")) push("townProps", parseTownProps(str("townProps")).problems);
|
|
3505
3910
|
if (str("floorCover")) push("floorCover", parseFloorCover(str("floorCover")).problems);
|
|
3506
3911
|
if (str("pyramidShow")) push("pyramidShow", parsePyramidShow(str("pyramidShow")).problems);
|
|
3912
|
+
if (str("choreography")) push("choreography", parseChoreography(str("choreography"), STARFISH_MOVE_NAMES).problems);
|
|
3507
3913
|
if (str("geodeMineral")) push("geodeMineral", parseGeodeMineral(str("geodeMineral")).problems);
|
|
3508
3914
|
const rig = parseSpotRig(str("spotRig"));
|
|
3509
3915
|
push("spotRig", rig.problems);
|
|
@@ -3513,6 +3919,9 @@ function validateMetaquariumParams(params) {
|
|
|
3513
3919
|
else push("spotCues", parseSpotCues(str("spotCues"), spots).problems);
|
|
3514
3920
|
}
|
|
3515
3921
|
const world = worldMarks(params);
|
|
3922
|
+
for (const s of rig.spots) {
|
|
3923
|
+
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` });
|
|
3924
|
+
}
|
|
3516
3925
|
if (str("vignette")) {
|
|
3517
3926
|
const indoors = params.interior === "geode";
|
|
3518
3927
|
push("vignette", parseVignette(resolveVignette(str("vignette")), indoors ? INTERIOR_MARKS : { ...OPEN_MARKS, ...world }).problems);
|
|
@@ -3598,17 +4007,17 @@ function dependencyProblems(params) {
|
|
|
3598
4007
|
const spot2 = Math.round(num("followSpot", -1));
|
|
3599
4008
|
if (rig.length && set("followSpot")) out.push({ path: "followSpot", also: ["spotRig"], message: "ignored: a spotRig replaces the single followSpot" });
|
|
3600
4009
|
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) });
|
|
4010
|
+
for (const s of rig) if (!s.mark && s.slot >= cast) out.push({ path: "spotRig", also: castParams, message: noFish(s.slot) });
|
|
3602
4011
|
if (str("vignette")) {
|
|
3603
4012
|
const v = parseVignette(resolveVignette(str("vignette")), { ...OPEN_MARKS, ...INTERIOR_MARKS });
|
|
3604
4013
|
const { actors } = v;
|
|
3605
|
-
if (actors > cast) out.push({ path: "vignette", also: castParams, message: `needs ${actors} fish (actors a\u2013${"
|
|
4014
|
+
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
4015
|
const slotBreeds = expandFishMixSlots(mix, 3).map((sl) => sl.breed);
|
|
3607
4016
|
for (const [g, breeds] of Object.entries(BREED_GESTURES)) {
|
|
3608
4017
|
for (let a = 0; a < actors; a++) {
|
|
3609
4018
|
if (!v.beats.some((b) => b.actors[a]?.gesture === g)) continue;
|
|
3610
4019
|
const breed = slotBreeds[a] ?? "fish";
|
|
3611
|
-
if (!breeds.includes(breed)) out.push({ path: "vignette", also: ["fishMix"], message: `"${"
|
|
4020
|
+
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
4021
|
}
|
|
3613
4022
|
}
|
|
3614
4023
|
}
|
|
@@ -3617,7 +4026,9 @@ function dependencyProblems(params) {
|
|
|
3617
4026
|
if (dance !== "off" && !starfishSwims) {
|
|
3618
4027
|
out.push({ path: "starfishDance", also: ["fishMix"], message: "no starfish in the cast to dance \u2014 add e.g. starfish:7 (slots past the cap never swim)" });
|
|
3619
4028
|
}
|
|
3620
|
-
|
|
4029
|
+
const choreographed = !!str("choreography").trim();
|
|
4030
|
+
if (set("danceTempo") && dance === "off" && !choreographed) out.push({ path: "danceTempo", also: ["starfishDance"], message: "does nothing while starfishDance is off" });
|
|
4031
|
+
if (choreographed && !starfishSwims) out.push({ path: "choreography", also: ["fishMix"], message: "no starfish in the cast to dance it \u2014 add e.g. starfish:5" });
|
|
3621
4032
|
if (follow < 0) {
|
|
3622
4033
|
for (const k of ["followDistance", "followAngle", "followAvoid"]) if (set(k)) out.push({ path: k, also: ["cameraFollow"], message: "does nothing while cameraFollow is -1 (the orbit camera)" });
|
|
3623
4034
|
} else if (follow < cast) {
|
|
@@ -3760,7 +4171,7 @@ function describeMetaquarium(params, timed = []) {
|
|
|
3760
4171
|
if (Math.round(num("shoal") * CAST_CAP2 * 2.5) >= 3) world.push(`a school of ${str("shoalKind")}`);
|
|
3761
4172
|
const stage = [];
|
|
3762
4173
|
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(", ")}`);
|
|
4174
|
+
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
4175
|
else if (num("followSpot") >= 0) stage.push(`follow-spot on ${slotName(num("followSpot"))}`);
|
|
3765
4176
|
if (has("spotCues") && (rig.length || num("followSpot") >= 0)) {
|
|
3766
4177
|
const sheet = parseSpotCues(str("spotCues"), Math.max(1, rig.length));
|
|
@@ -3771,6 +4182,7 @@ function describeMetaquarium(params, timed = []) {
|
|
|
3771
4182
|
stage.push(VIGNETTES[v] ? `vignette: ${v}` : `vignette: ${v.split(/[|\n]/).filter((b) => b.trim()).length} beats`);
|
|
3772
4183
|
}
|
|
3773
4184
|
if (str("starfishDance") !== "off") stage.push(`starfish ${str("starfishDance")}${has("danceTempo") ? ` at ${num("danceTempo")} bpm` : ""}`);
|
|
4185
|
+
if (str("choreography").trim()) stage.push(`a starfish choreography${has("danceTempo") ? ` at ${num("danceTempo")} bpm` : ""}`);
|
|
3774
4186
|
const camera = [];
|
|
3775
4187
|
const follow = Math.round(num("cameraFollow"));
|
|
3776
4188
|
if (follow >= 0) {
|
|
@@ -4060,6 +4472,10 @@ var METAQUARIUM_PARAMS = {
|
|
|
4060
4472
|
* mode), brighter on top: what separates a dark fish from dark water.
|
|
4061
4473
|
* 0 is none. */
|
|
4062
4474
|
fishRim: { type: "number", default: 0, min: 0, max: 1, ease: "smooth" },
|
|
4475
|
+
/** The fish's own corners darken — a fin's root, the step at the head, the
|
|
4476
|
+
* gap under a shell — read off the voxels they are built from. Baked once
|
|
4477
|
+
* per breed, the first frame it is on; every tier. 0 is off. */
|
|
4478
|
+
fishAO: { type: "number", default: 0, min: 0, max: 1, ease: "smooth" },
|
|
4063
4479
|
/** A soft shadow on the ground under every swimmer: small and dark near
|
|
4064
4480
|
* the sand, wide and faint high up — what puts a fish at a height instead
|
|
4065
4481
|
* of floating in front of the floor. 0 is none (floor walkers always
|
|
@@ -4073,6 +4489,10 @@ var METAQUARIUM_PARAMS = {
|
|
|
4073
4489
|
* homes, the castle, pyramids, wild geodes, lamps — so it stands on the
|
|
4074
4490
|
* sand instead of floating over it. 0 is none. */
|
|
4075
4491
|
sceneryShadows: { type: "number", default: 0, min: 0, max: 1, ease: "smooth" },
|
|
4492
|
+
/** The plants' cubes darken where they meet each other and the sand — the
|
|
4493
|
+
* corner shading every voxel renderer has — so a coral reads solid instead
|
|
4494
|
+
* of as flat cut-outs. Baked once, the first frame it is on; every tier. 0 is off. */
|
|
4495
|
+
voxelAO: { type: "number", default: 0, min: 0, max: 1, ease: "smooth" },
|
|
4076
4496
|
/** A creature that comes too close to the camera dissolves (an ordered
|
|
4077
4497
|
* dither by depth) instead of being cut by the lens: at 1 it is whole
|
|
4078
4498
|
* beyond about a fish and a half and gone inside about half that. 0 is off. */
|
|
@@ -4124,6 +4544,13 @@ var METAQUARIUM_PARAMS = {
|
|
|
4124
4544
|
/** The dance's tempo, beats per minute. Every move is a bar of four beats.
|
|
4125
4545
|
* Integrated when steered, so a glide speeds the class up on the beat. */
|
|
4126
4546
|
danceTempo: { type: "number", default: 128, min: 60, max: 180, ease: "smooth" },
|
|
4547
|
+
/** An authored dance on the beat (choreo.ts): a cue sheet per dancer —
|
|
4548
|
+
* `a: 8:lie, 4:rise, 4:reach >right @cam | *: 4:march ~360, 4:jacks ^0.5`.
|
|
4549
|
+
* Set, every starfish dances it (in the class's places, or the couples'
|
|
4550
|
+
* under `starfishDance: duet`), facing `cameraAzimuth`; a dancer it leaves
|
|
4551
|
+
* out keeps `starfishDance`'s routine, or stands and waits. Beats on the
|
|
4552
|
+
* `danceTempo` clock from the start, so a sheet lines up with music. */
|
|
4553
|
+
choreography: { type: "string", default: "", ease: "step" },
|
|
4127
4554
|
/** Eye life, 0..1: blinks on a personal clock, idle saccades, pupils that
|
|
4128
4555
|
* lead a turn or a climb, eyes on whoever a vignette has it talking to, a
|
|
4129
4556
|
* glance at the camera now and then, wide for a hop and shut for a rest.
|
|
@@ -4250,6 +4677,13 @@ var METAQUARIUM_PARAMS = {
|
|
|
4250
4677
|
* fountain home1…, or centre left right front back), facing the front; an LED board
|
|
4251
4678
|
* scrolls a long line. Empty = none. */
|
|
4252
4679
|
signs: { type: "string", default: "", ease: "step" },
|
|
4680
|
+
/** The open-mic set (club.ts): `club` is a brick back wall, a mic on a stand at the `mic` mark and a
|
|
4681
|
+
* stool; `mic` the stand alone. The `openmic` vignette is written for it, with an `@audience` out front. */
|
|
4682
|
+
stageSet: { type: "enum", default: "none", options: ["none", "club", "mic"], ease: "step" },
|
|
4683
|
+
/** EXPERIMENTAL. The dolphins' cast — comic personas, in the order the dolphins come in fishMix:
|
|
4684
|
+
* `showman, drifter, bigguy` (dolphin-comics.ts). Each its own look (skin, finish, shirt, prop) and
|
|
4685
|
+
* act (tempo, size of gesture, slouch, lean, lids). Unknown names, and dolphins past the list: classic. */
|
|
4686
|
+
dolphinCast: { type: "string", default: "", ease: "step" },
|
|
4253
4687
|
/** How unstable the lit signs' power is (neon and LED boards): 0 steady; higher, more
|
|
4254
4688
|
* hum, stutters, a dead row or tube, a slipped row, now and then a reboot. Each sign
|
|
4255
4689
|
* has its own supply — most steady, some dodgy. Flash-safe at any setting. */
|
|
@@ -4516,6 +4950,10 @@ export {
|
|
|
4516
4950
|
SIGN_TEXT_MAX,
|
|
4517
4951
|
MAX_SIGNS,
|
|
4518
4952
|
parseSignMix,
|
|
4953
|
+
STARFISH_MOVE_NAMES,
|
|
4954
|
+
parseChoreography,
|
|
4955
|
+
choreoLine,
|
|
4956
|
+
choreoAt,
|
|
4519
4957
|
GESTURES,
|
|
4520
4958
|
OPEN_MARKS,
|
|
4521
4959
|
parseVignette,
|
|
@@ -4542,4 +4980,4 @@ export {
|
|
|
4542
4980
|
paramSpaceWith,
|
|
4543
4981
|
coerceNum
|
|
4544
4982
|
};
|
|
4545
|
-
//# sourceMappingURL=chunk-
|
|
4983
|
+
//# sourceMappingURL=chunk-42RIVUT4.js.map
|