@moood/render 0.0.0 → 0.1.2

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.
@@ -1,220 +1,220 @@
1
- // The scene's state and its scripts — the scene's own mechanics, knowing nothing of the game
2
- // played on it. The state is a set of values (spec.state holds where they start: "time": "20:00",
3
- // "dawn": 0, "girl": "waiting" …) — the scene's parameters. Whatever changes them, the scene reacts:
4
- // scripts — spec.script and any element's `script`: JS run in the page (a clock that ticks, a
5
- // ship that passes at 00:50, someone walking to where the state says);
6
- // the game — whatever runs on the scene sets them (its agents, its master): the page passes them on;
7
- // widgets — MooodState is global: get / set / on.
8
- // The painting code reads the current values as e.state and is repainted when they change. A
9
- // number may be set to move there over some seconds, along an easing curve (kit.js: smooth by
10
- // default — it starts and stops gently; linear, in, out, back, spring, anticipate…). What a value
11
- // was a moment ago stays known for a while (was): what lags behind — a coat's tail, hair, a
12
- // head that follows — reads it. A script may tell that something happened (scene.tell): the
13
- // game decides what it means.
14
- const MooodState = (() => {
15
- let stage = null, tell = null, save = null, t0 = performance.now();
16
- let values = {}, cur = {}, tweens = new Map(), listeners = [], timers = [], raf = 0, pending = {}, flushTimer = 0;
17
- // each number's recent moves, to say what it was a moment ago: key → [{start, end, from, to, curve}]
18
- // (a jump: start = end); kept for PAST seconds
19
- const PAST = 10;
20
- // the engine's own values (engine.js, gl.js: the camera a film moves, its fade): 0 until set
21
- const ENGINE = { camera_x: 0, camera_h: 0, camera_z: 0, camera_look: 0, fade: 0 };
22
- let past = new Map();
23
- const report = (who, err) => (window.__mooodErrors = window.__mooodErrors || []).push(`script ${who}: ${err && err.message || err}`);
24
-
25
- // where a move is at `now` (a move along a path: where its path is, `way` 0…1 of it)
26
- const along = (m, now) => {
27
- if (now >= m.end) return m.to;
28
- if (now <= m.start) return m.from;
29
- const u = m.curve((now - m.start) / (m.end - m.start));
30
- return m.way ? m.way(u) : m.from + (m.to - m.from) * u;
31
- };
32
- // what a number was `ago` seconds ago (as it is now if it hasn't moved since)
33
- function was(key, ago = 0) {
34
- const now = performance.now(), at = now - ago * 1000, moves = past.get(key);
35
- if (!moves || !moves.length) return cur[key];
36
- for (let i = moves.length - 1; i >= 0; i--) if (moves[i].start <= at) return along(moves[i], at);
37
- return moves[0].from;
38
- }
39
- function remember(key, move) {
40
- const moves = past.get(key) || [], gone = move.start - PAST * 1000;
41
- while (moves.length > 1 && moves[1].end < gone) moves.shift();
42
- moves.push(move);
43
- past.set(key, moves);
44
- }
45
-
46
- function publish() { cur = { ...values }; for (const [k, tw] of tweens) cur[k] = along(tw, performance.now()); stage?.setState(cur, was, sample); }
47
- // the moving values, where they are now (the stage asks as it paints a frame)
48
- function sample() {
49
- if (!tweens.size) return;
50
- const now = performance.now();
51
- for (const [k, tw] of tweens) if (now >= tw.end) tweens.delete(k);
52
- publish();
53
- }
54
- function tick() {
55
- raf = 0;
56
- sample();
57
- if (tweens.size) raf = requestAnimationFrame(tick);
58
- }
59
- function emit(ev, key, ...args) {
60
- for (const l of listeners.slice()) {
61
- if (l.ev !== ev || (l.key != null && l.key !== key)) continue;
62
- try { l.fn(...args); } catch (err) { report(l.who, err); }
63
- }
64
- }
65
- // changes go to the game's keeping (if it keeps them), a few at a time
66
- function keep(changes) {
67
- if (!save) return;
68
- Object.assign(pending, changes);
69
- clearTimeout(flushTimer);
70
- flushTimer = setTimeout(() => { const body = pending; pending = {}; save(body); }, 500);
71
- }
72
- // set a value; a number with `over` seconds moves there along `ease` (a curve's name, or u → u);
73
- // null removes it. kept: false — the game set it and keeps it itself
74
- function set(key, value, over = 0, ease = 'smooth', { kept = true } = {}) {
75
- key = String(key);
76
- const old = key in cur ? cur[key] : key in values ? values[key] : ENGINE[key];
77
- if (value === undefined) value = null;
78
- const now = performance.now();
79
- if (typeof value === 'number' && typeof old === 'number' && over > 0 && value !== old) {
80
- const move = { start: now, end: now + over * 1000, from: old, to: value, curve: MooodKit.anim.easeOf(ease) };
81
- tweens.set(key, move);
82
- remember(key, move);
83
- if (!raf) raf = requestAnimationFrame(tick);
84
- } else {
85
- tweens.delete(key);
86
- if (typeof value === 'number') remember(key, { start: now, end: now, from: typeof old === 'number' ? old : value, to: value, curve: MooodKit.anim.ease.linear });
87
- }
88
- if (value === null) delete values[key]; else values[key] = value;
89
- publish();
90
- if (kept) keep({ [key]: value });
91
- if (JSON.stringify(old) !== JSON.stringify(value)) emit('change', key, value, old, key);
92
- }
93
- // several numbers moved together through points, as one movement: keys ['cart_a', 'cart_d'], points
94
- // [[a, d], …] (from where they are now); over `over` seconds or at `speed` (their units a second,
95
- // along the way), along `ease` — so it slows nowhere but where the curve says (a path cut into
96
- // set()s stops at every corner). Returns its seconds.
97
- function path(keys, points, { over = 0, speed = 0, ease = 'smooth' } = {}) {
98
- keys = keys.map(String);
99
- const from = keys.map(k => Number(k in cur ? cur[k] : k in values ? values[k] : ENGINE[k]) || 0);
100
- const pts = [from, ...points.map(p => keys.map((_, i) => Number(p[i]) || 0))], to = pts[pts.length - 1];
101
- const ends = [0];
102
- for (let i = 1; i < pts.length; i++) ends.push(ends[i - 1] + Math.hypot(...pts[i].map((v, j) => v - pts[i - 1][j])));
103
- const length = ends[ends.length - 1], seconds = over > 0 ? over : speed > 0 ? length / speed : 0;
104
- if (!(seconds > 0 && length > 0)) { keys.forEach((k, i) => set(k, to[i])); return 0; }
105
- const at = (u, j) => {
106
- const d = u * length;
107
- let i = 1;
108
- while (i < ends.length - 1 && ends[i] < d) i++;
109
- const f = (d - ends[i - 1]) / ((ends[i] - ends[i - 1]) || 1);
110
- return pts[i - 1][j] + (pts[i][j] - pts[i - 1][j]) * f;
111
- };
112
- const now = performance.now(), curve = MooodKit.anim.easeOf(ease), old = keys.map(k => cur[k]);
113
- keys.forEach((k, j) => {
114
- const move = { start: now, end: now + seconds * 1000, from: from[j], to: to[j], curve, way: u => at(u, j) };
115
- tweens.set(k, move);
116
- remember(k, move);
117
- values[k] = to[j];
118
- });
119
- if (!raf) raf = requestAnimationFrame(tick);
120
- publish();
121
- keep(Object.fromEntries(keys.map((k, j) => [k, to[j]])));
122
- keys.forEach((k, j) => { if (old[j] !== to[j]) emit('change', k, to[j], old[j], k); });
123
- return seconds;
124
- }
125
- // an angle in degrees, turned the short way round (from 170 to −170 is 20°, not 340°)
126
- function turn(key, degrees, over = 0, ease = 'smooth') {
127
- const old = Number(key in cur ? cur[key] : values[key]) || 0;
128
- set(key, old + ((((degrees - old) % 360) + 540) % 360 - 180), over, ease);
129
- }
130
-
131
- // the API a script gets: for the scene (id null) or for one element
132
- function api(who, id, element, shared) {
133
- const own = fn => (...a) => { try { fn(...a); } catch (err) { report(who, err); } };
134
- const after = (seconds, fn) => { const h = setTimeout(own(fn), Math.max(0, seconds) * 1000); timers.push(() => clearTimeout(h)); };
135
- return {
136
- id, element,
137
- anim: MooodKit.anim, kit: shared.kit, lib: shared.lib,
138
- get state() { return cur; },
139
- get time() { return (performance.now() - t0) / 1000; }, // seconds since the scene opened
140
- get: key => cur[key],
141
- set: (key, value, over, ease) => set(key, value, over, ease),
142
- turn: (key, degrees, over, ease) => turn(key, degrees, over, ease),
143
- path: (keys, points, opts) => path(keys, points, opts),
144
- was: (key, ago) => was(key, ago),
145
- // a platformer's hero now: { across, elevation (metres), ground } — or null
146
- hero: () => stage?.hero?.() ?? null,
147
- // something happened in the scene (about one of its elements, if `about`): the game hears it
148
- tell: (text, about = null) => tell?.(String(text), about),
149
- every(seconds, fn) { const h = setInterval(own(fn), Math.max(0.05, seconds) * 1000); timers.push(() => clearInterval(h)); },
150
- after,
151
- // a timeline: rows [at, key, value, over?, ease?] — or [at, fn] — where `at` is the second it
152
- // starts (from now), or '+s': s seconds after the row before it ends ('+0': as it ends); a
153
- // key in degrees turned the short way: [at, key, {turn: degrees}, over?, ease?]; numbers
154
- // moved together through points (path, above): [at, [keys], {path: points}, over, ease?].
155
- // Returns when the last row ends (seconds from now).
156
- play(rows) {
157
- let end = 0, last = 0;
158
- for (const row of rows) {
159
- const [at, a, b, over = 0, ease] = row;
160
- const start = typeof at === 'string' && at.startsWith('+') ? last + Number(at.slice(1)) : Number(at);
161
- if (typeof a === 'function') after(start, a);
162
- else if (Array.isArray(a) && b && typeof b === 'object' && Array.isArray(b.path)) after(start, () => path(a, b.path, { over, ease }));
163
- else if (b && typeof b === 'object' && 'turn' in b) after(start, () => turn(a, b.turn, over, ease));
164
- else after(start, () => set(a, b, over, ease));
165
- last = start + (typeof a === 'function' ? 0 : over);
166
- end = Math.max(end, last);
167
- }
168
- return end;
169
- },
170
- // on('change', fn(value, old, key)) · on('change', key, fn) · on('load', fn(state))
171
- on(ev, a, b) {
172
- const fn = typeof a === 'function' ? a : b, key = typeof a === 'function' ? null : String(a);
173
- listeners.push({ ev, key, fn, who });
174
- },
175
- };
176
- }
177
-
178
- // (re)starts the scene: its starting values, `state` on top (what the game kept), then the
179
- // scripts — once what they share (the world's modules, the scene's lib) is here. opts: stage;
180
- // modules — where the spec's modules come from (kit.js: {world, refs} or {texts});
181
- // tell(text, about) — what the scripts tell; save(changes) — the game keeps the values the scene
182
- // changes (none: they live as long as the page)
183
- let run = 0;
184
- function start(spec, state = {}, opts = {}) {
185
- stop();
186
- stage = opts.stage || stage;
187
- tell = opts.tell || null;
188
- save = opts.save || null;
189
- values = { ...(spec?.state && typeof spec.state === 'object' ? spec.state : {}), ...state };
190
- past = new Map();
191
- t0 = performance.now();
192
- publish();
193
- emit('load', null, cur);
194
- const mine = ++run;
195
- const go = shared => {
196
- if (mine !== run) return; // started again meanwhile
197
- const exec = (code, who, id, element) => {
198
- if (typeof code !== 'string' || !code.trim()) return;
199
- try { new Function('scene', code)(api(who, id, element, shared)); } catch (err) { report(who, err); }
200
- };
201
- exec(spec?.script, 'scene', null, null);
202
- for (const o of spec?.objects || []) exec(o.script, o.name || o.id, o.id || null, o);
203
- };
204
- const shared = MooodKit.shared(spec, opts.modules);
205
- if (shared instanceof Promise) shared.then(go); else go(shared);
206
- }
207
- function stop() {
208
- for (const off of timers) off();
209
- // the scripts' listeners go; widgets' stay (a widget lives on while its html is the same)
210
- timers = []; listeners = listeners.filter(l => l.who === 'widget'); tweens.clear();
211
- cancelAnimationFrame(raf); raf = 0;
212
- }
213
-
214
- return {
215
- start, stop, set, turn, path, was,
216
- get: key => cur[key],
217
- all: () => cur,
218
- on: (ev, a, b) => api('widget', null, null, { kit: {}, lib: {} }).on(ev, a, b),
219
- };
220
- })();
1
+ // The scene's state and its scripts — the scene's own mechanics, knowing nothing of the game
2
+ // played on it. The state is a set of values (spec.state holds where they start: "time": "20:00",
3
+ // "dawn": 0, "girl": "waiting" …) — the scene's parameters. Whatever changes them, the scene reacts:
4
+ // scripts — spec.script and any element's `script`: JS run in the page (a clock that ticks, a
5
+ // ship that passes at 00:50, someone walking to where the state says);
6
+ // the game — whatever runs on the scene sets them (its agents, its master): the page passes them on;
7
+ // widgets — MooodState is global: get / set / on.
8
+ // The painting code reads the current values as e.state and is repainted when they change. A
9
+ // number may be set to move there over some seconds, along an easing curve (kit.js: smooth by
10
+ // default — it starts and stops gently; linear, in, out, back, spring, anticipate…). What a value
11
+ // was a moment ago stays known for a while (was): what lags behind — a coat's tail, hair, a
12
+ // head that follows — reads it. A script may tell that something happened (scene.tell): the
13
+ // game decides what it means.
14
+ const MooodState = (() => {
15
+ let stage = null, tell = null, save = null, t0 = performance.now();
16
+ let values = {}, cur = {}, tweens = new Map(), listeners = [], timers = [], raf = 0, pending = {}, flushTimer = 0;
17
+ // each number's recent moves, to say what it was a moment ago: key → [{start, end, from, to, curve}]
18
+ // (a jump: start = end); kept for PAST seconds
19
+ const PAST = 10;
20
+ // the engine's own values (engine.js, gl.js: the camera a film moves, its fade): 0 until set
21
+ const ENGINE = { camera_x: 0, camera_h: 0, camera_z: 0, camera_look: 0, fade: 0 };
22
+ let past = new Map();
23
+ const report = (who, err) => (window.__mooodErrors = window.__mooodErrors || []).push(`script ${who}: ${err && err.message || err}`);
24
+
25
+ // where a move is at `now` (a move along a path: where its path is, `way` 0…1 of it)
26
+ const along = (m, now) => {
27
+ if (now >= m.end) return m.to;
28
+ if (now <= m.start) return m.from;
29
+ const u = m.curve((now - m.start) / (m.end - m.start));
30
+ return m.way ? m.way(u) : m.from + (m.to - m.from) * u;
31
+ };
32
+ // what a number was `ago` seconds ago (as it is now if it hasn't moved since)
33
+ function was(key, ago = 0) {
34
+ const now = performance.now(), at = now - ago * 1000, moves = past.get(key);
35
+ if (!moves || !moves.length) return cur[key];
36
+ for (let i = moves.length - 1; i >= 0; i--) if (moves[i].start <= at) return along(moves[i], at);
37
+ return moves[0].from;
38
+ }
39
+ function remember(key, move) {
40
+ const moves = past.get(key) || [], gone = move.start - PAST * 1000;
41
+ while (moves.length > 1 && moves[1].end < gone) moves.shift();
42
+ moves.push(move);
43
+ past.set(key, moves);
44
+ }
45
+
46
+ function publish() { cur = { ...values }; for (const [k, tw] of tweens) cur[k] = along(tw, performance.now()); stage?.setState(cur, was, sample); }
47
+ // the moving values, where they are now (the stage asks as it paints a frame)
48
+ function sample() {
49
+ if (!tweens.size) return;
50
+ const now = performance.now();
51
+ for (const [k, tw] of tweens) if (now >= tw.end) tweens.delete(k);
52
+ publish();
53
+ }
54
+ function tick() {
55
+ raf = 0;
56
+ sample();
57
+ if (tweens.size) raf = requestAnimationFrame(tick);
58
+ }
59
+ function emit(ev, key, ...args) {
60
+ for (const l of listeners.slice()) {
61
+ if (l.ev !== ev || (l.key != null && l.key !== key)) continue;
62
+ try { l.fn(...args); } catch (err) { report(l.who, err); }
63
+ }
64
+ }
65
+ // changes go to the game's keeping (if it keeps them), a few at a time
66
+ function keep(changes) {
67
+ if (!save) return;
68
+ Object.assign(pending, changes);
69
+ clearTimeout(flushTimer);
70
+ flushTimer = setTimeout(() => { const body = pending; pending = {}; save(body); }, 500);
71
+ }
72
+ // set a value; a number with `over` seconds moves there along `ease` (a curve's name, or u → u);
73
+ // null removes it. kept: false — the game set it and keeps it itself
74
+ function set(key, value, over = 0, ease = 'smooth', { kept = true } = {}) {
75
+ key = String(key);
76
+ const old = key in cur ? cur[key] : key in values ? values[key] : ENGINE[key];
77
+ if (value === undefined) value = null;
78
+ const now = performance.now();
79
+ if (typeof value === 'number' && typeof old === 'number' && over > 0 && value !== old) {
80
+ const move = { start: now, end: now + over * 1000, from: old, to: value, curve: MooodKit.anim.easeOf(ease) };
81
+ tweens.set(key, move);
82
+ remember(key, move);
83
+ if (!raf) raf = requestAnimationFrame(tick);
84
+ } else {
85
+ tweens.delete(key);
86
+ if (typeof value === 'number') remember(key, { start: now, end: now, from: typeof old === 'number' ? old : value, to: value, curve: MooodKit.anim.ease.linear });
87
+ }
88
+ if (value === null) delete values[key]; else values[key] = value;
89
+ publish();
90
+ if (kept) keep({ [key]: value });
91
+ if (JSON.stringify(old) !== JSON.stringify(value)) emit('change', key, value, old, key);
92
+ }
93
+ // several numbers moved together through points, as one movement: keys ['cart_a', 'cart_d'], points
94
+ // [[a, d], …] (from where they are now); over `over` seconds or at `speed` (their units a second,
95
+ // along the way), along `ease` — so it slows nowhere but where the curve says (a path cut into
96
+ // set()s stops at every corner). Returns its seconds.
97
+ function path(keys, points, { over = 0, speed = 0, ease = 'smooth' } = {}) {
98
+ keys = keys.map(String);
99
+ const from = keys.map(k => Number(k in cur ? cur[k] : k in values ? values[k] : ENGINE[k]) || 0);
100
+ const pts = [from, ...points.map(p => keys.map((_, i) => Number(p[i]) || 0))], to = pts[pts.length - 1];
101
+ const ends = [0];
102
+ for (let i = 1; i < pts.length; i++) ends.push(ends[i - 1] + Math.hypot(...pts[i].map((v, j) => v - pts[i - 1][j])));
103
+ const length = ends[ends.length - 1], seconds = over > 0 ? over : speed > 0 ? length / speed : 0;
104
+ if (!(seconds > 0 && length > 0)) { keys.forEach((k, i) => set(k, to[i])); return 0; }
105
+ const at = (u, j) => {
106
+ const d = u * length;
107
+ let i = 1;
108
+ while (i < ends.length - 1 && ends[i] < d) i++;
109
+ const f = (d - ends[i - 1]) / ((ends[i] - ends[i - 1]) || 1);
110
+ return pts[i - 1][j] + (pts[i][j] - pts[i - 1][j]) * f;
111
+ };
112
+ const now = performance.now(), curve = MooodKit.anim.easeOf(ease), old = keys.map(k => cur[k]);
113
+ keys.forEach((k, j) => {
114
+ const move = { start: now, end: now + seconds * 1000, from: from[j], to: to[j], curve, way: u => at(u, j) };
115
+ tweens.set(k, move);
116
+ remember(k, move);
117
+ values[k] = to[j];
118
+ });
119
+ if (!raf) raf = requestAnimationFrame(tick);
120
+ publish();
121
+ keep(Object.fromEntries(keys.map((k, j) => [k, to[j]])));
122
+ keys.forEach((k, j) => { if (old[j] !== to[j]) emit('change', k, to[j], old[j], k); });
123
+ return seconds;
124
+ }
125
+ // an angle in degrees, turned the short way round (from 170 to −170 is 20°, not 340°)
126
+ function turn(key, degrees, over = 0, ease = 'smooth') {
127
+ const old = Number(key in cur ? cur[key] : values[key]) || 0;
128
+ set(key, old + ((((degrees - old) % 360) + 540) % 360 - 180), over, ease);
129
+ }
130
+
131
+ // the API a script gets: for the scene (id null) or for one element
132
+ function api(who, id, element, shared) {
133
+ const own = fn => (...a) => { try { fn(...a); } catch (err) { report(who, err); } };
134
+ const after = (seconds, fn) => { const h = setTimeout(own(fn), Math.max(0, seconds) * 1000); timers.push(() => clearTimeout(h)); };
135
+ return {
136
+ id, element,
137
+ anim: MooodKit.anim, kit: shared.kit, lib: shared.lib,
138
+ get state() { return cur; },
139
+ get time() { return (performance.now() - t0) / 1000; }, // seconds since the scene opened
140
+ get: key => cur[key],
141
+ set: (key, value, over, ease) => set(key, value, over, ease),
142
+ turn: (key, degrees, over, ease) => turn(key, degrees, over, ease),
143
+ path: (keys, points, opts) => path(keys, points, opts),
144
+ was: (key, ago) => was(key, ago),
145
+ // a platformer's hero now: { across, elevation (metres), ground } — or null
146
+ hero: () => stage?.hero?.() ?? null,
147
+ // something happened in the scene (about one of its elements, if `about`): the game hears it
148
+ tell: (text, about = null) => tell?.(String(text), about),
149
+ every(seconds, fn) { const h = setInterval(own(fn), Math.max(0.05, seconds) * 1000); timers.push(() => clearInterval(h)); },
150
+ after,
151
+ // a timeline: rows [at, key, value, over?, ease?] — or [at, fn] — where `at` is the second it
152
+ // starts (from now), or '+s': s seconds after the row before it ends ('+0': as it ends); a
153
+ // key in degrees turned the short way: [at, key, {turn: degrees}, over?, ease?]; numbers
154
+ // moved together through points (path, above): [at, [keys], {path: points}, over, ease?].
155
+ // Returns when the last row ends (seconds from now).
156
+ play(rows) {
157
+ let end = 0, last = 0;
158
+ for (const row of rows) {
159
+ const [at, a, b, over = 0, ease] = row;
160
+ const start = typeof at === 'string' && at.startsWith('+') ? last + Number(at.slice(1)) : Number(at);
161
+ if (typeof a === 'function') after(start, a);
162
+ else if (Array.isArray(a) && b && typeof b === 'object' && Array.isArray(b.path)) after(start, () => path(a, b.path, { over, ease }));
163
+ else if (b && typeof b === 'object' && 'turn' in b) after(start, () => turn(a, b.turn, over, ease));
164
+ else after(start, () => set(a, b, over, ease));
165
+ last = start + (typeof a === 'function' ? 0 : over);
166
+ end = Math.max(end, last);
167
+ }
168
+ return end;
169
+ },
170
+ // on('change', fn(value, old, key)) · on('change', key, fn) · on('load', fn(state))
171
+ on(ev, a, b) {
172
+ const fn = typeof a === 'function' ? a : b, key = typeof a === 'function' ? null : String(a);
173
+ listeners.push({ ev, key, fn, who });
174
+ },
175
+ };
176
+ }
177
+
178
+ // (re)starts the scene: its starting values, `state` on top (what the game kept), then the
179
+ // scripts — once what they share (the world's modules, the scene's lib) is here. opts: stage;
180
+ // modules — where the spec's modules come from (kit.js: {world, refs} or {texts});
181
+ // tell(text, about) — what the scripts tell; save(changes) — the game keeps the values the scene
182
+ // changes (none: they live as long as the page)
183
+ let run = 0;
184
+ function start(spec, state = {}, opts = {}) {
185
+ stop();
186
+ stage = opts.stage || stage;
187
+ tell = opts.tell || null;
188
+ save = opts.save || null;
189
+ values = { ...(spec?.state && typeof spec.state === 'object' ? spec.state : {}), ...state };
190
+ past = new Map();
191
+ t0 = performance.now();
192
+ publish();
193
+ emit('load', null, cur);
194
+ const mine = ++run;
195
+ const go = shared => {
196
+ if (mine !== run) return; // started again meanwhile
197
+ const exec = (code, who, id, element) => {
198
+ if (typeof code !== 'string' || !code.trim()) return;
199
+ try { new Function('scene', code)(api(who, id, element, shared)); } catch (err) { report(who, err); }
200
+ };
201
+ exec(spec?.script, 'scene', null, null);
202
+ for (const o of spec?.objects || []) exec(o.script, o.name || o.id, o.id || null, o);
203
+ };
204
+ const shared = MooodKit.shared(spec, opts.modules);
205
+ if (shared instanceof Promise) shared.then(go); else go(shared);
206
+ }
207
+ function stop() {
208
+ for (const off of timers) off();
209
+ // the scripts' listeners go; widgets' stay (a widget lives on while its html is the same)
210
+ timers = []; listeners = listeners.filter(l => l.who === 'widget'); tweens.clear();
211
+ cancelAnimationFrame(raf); raf = 0;
212
+ }
213
+
214
+ return {
215
+ start, stop, set, turn, path, was,
216
+ get: key => cur[key],
217
+ all: () => cur,
218
+ on: (ev, a, b) => api('widget', null, null, { kit: {}, lib: {} }).on(ev, a, b),
219
+ };
220
+ })();