@enmaq/mdgl 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/eval.js ADDED
@@ -0,0 +1,496 @@
1
+ "use strict";
2
+ // MDGL 0.1 evaluator + terminal-cell renderer. Pure and seekable: compile() a checked
3
+ // document once, then frameNs()/fallbackFrame() are functions of (scene, time) only.
4
+ // Implemented from SPEC.md alone. Choices SPEC leaves open are marked IMPL-DEFINED.
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.compileDocument = compileDocument;
7
+ exports.compile = compile;
8
+ exports.frameNs = frameNs;
9
+ exports.fallbackFrame = fallbackFrame;
10
+ const check_1 = require("./check");
11
+ const parse_1 = require("./parse");
12
+ const unicode_1 = require("./unicode");
13
+ const I4 = () => [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
14
+ const mul = (a, b) => {
15
+ const o = new Array(16).fill(0);
16
+ for (let c = 0; c < 4; c++)
17
+ for (let r = 0; r < 4; r++)
18
+ for (let k = 0; k < 4; k++)
19
+ o[c * 4 + r] += a[k * 4 + r] * b[c * 4 + k];
20
+ return o;
21
+ };
22
+ const trans = (x, y, z) => { const m = I4(); m[12] = x; m[13] = y; m[14] = z; return m; };
23
+ const scaleM = (s) => { const m = I4(); m[0] = s; m[5] = s; m[10] = s; return m; };
24
+ const rotM = (xd, yd, zd) => {
25
+ const [x, y, z] = [xd, yd, zd].map((d) => (d * Math.PI) / 180);
26
+ const [cx, sx, cy, sy, cz, sz] = [Math.cos(x), Math.sin(x), Math.cos(y), Math.sin(y), Math.cos(z), Math.sin(z)];
27
+ // SPEC rows (intrinsic XYZ, R = Rx*Ry*Rz)
28
+ const rows = [
29
+ [cy * cz, -cy * sz, sy, 0],
30
+ [cx * sz + sx * sy * cz, cx * cz - sx * sy * sz, -sx * cy, 0],
31
+ [sx * sz - cx * sy * cz, sx * cz + cx * sy * sz, cx * cy, 0],
32
+ [0, 0, 0, 1],
33
+ ];
34
+ const m = new Array(16);
35
+ for (let r = 0; r < 4; r++)
36
+ for (let c = 0; c < 4; c++)
37
+ m[c * 4 + r] = rows[r][c];
38
+ return m;
39
+ };
40
+ const xf = (m, v) => {
41
+ const o = [0, 0, 0, 0];
42
+ for (let r = 0; r < 4; r++)
43
+ o[r] = m[r] * v[0] + m[4 + r] * v[1] + m[8 + r] * v[2] + m[12 + r];
44
+ return o;
45
+ };
46
+ /** half away from zero */
47
+ const rnd = (v) => (v < 0 ? -Math.floor(-v + 0.5) : Math.floor(v + 0.5));
48
+ // ---- easing (SPEC: CSS cubic-bezier controls with numeric X solve; bounce = standard ease-out-bounce) ----
49
+ function bezier(x1, y1, x2, y2) {
50
+ const cx = (s) => 3 * (1 - s) * (1 - s) * s * x1 + 3 * (1 - s) * s * s * x2 + s * s * s;
51
+ const cy = (s) => 3 * (1 - s) * (1 - s) * s * y1 + 3 * (1 - s) * s * s * y2 + s * s * s;
52
+ return (u) => {
53
+ if (u <= 0)
54
+ return 0;
55
+ if (u >= 1)
56
+ return 1;
57
+ let lo = 0, hi = 1;
58
+ for (let i = 0; i < 80; i++) {
59
+ const m = (lo + hi) / 2;
60
+ if (cx(m) < u)
61
+ lo = m;
62
+ else
63
+ hi = m;
64
+ }
65
+ return cy((lo + hi) / 2);
66
+ };
67
+ }
68
+ const bounce = (u) => {
69
+ const n1 = 7.5625, d1 = 2.75;
70
+ if (u < 1 / d1)
71
+ return n1 * u * u;
72
+ if (u < 2 / d1) {
73
+ u -= 1.5 / d1;
74
+ return n1 * u * u + 0.75;
75
+ }
76
+ if (u < 2.5 / d1) {
77
+ u -= 2.25 / d1;
78
+ return n1 * u * u + 0.9375;
79
+ }
80
+ u -= 2.625 / d1;
81
+ return n1 * u * u + 0.984375;
82
+ };
83
+ const EASE = {
84
+ linear: (u) => u,
85
+ ease: bezier(0.25, 0.1, 0.25, 1),
86
+ 'ease-in': bezier(0.42, 0, 1, 1),
87
+ 'ease-out': bezier(0, 0, 0.58, 1),
88
+ 'ease-in-out': bezier(0.42, 0, 0.58, 1),
89
+ bounce,
90
+ };
91
+ // ---- colours ----
92
+ const RGB = {
93
+ normal: [255, 255, 255], white: [255, 255, 255], black: [0, 0, 0], red: [255, 0, 0], green: [0, 255, 0], yellow: [255, 255, 0],
94
+ blue: [0, 0, 255], magenta: [255, 0, 255], cyan: [0, 255, 255], accent: [0, 255, 255],
95
+ };
96
+ const hex2 = (n) => n.toString(16).padStart(2, '0');
97
+ /** IMPL-DEFINED rounding: nearest, ties up (SPEC gives only the source-over formula). */
98
+ function blend(fg, alpha, bg) {
99
+ if (alpha >= 1)
100
+ return fg;
101
+ const s = RGB[fg] ?? RGB.normal;
102
+ let d = [0, 0, 0];
103
+ if (bg !== '') {
104
+ if (bg[0] === '#')
105
+ d = [1, 3, 5].map((i) => parseInt(bg.slice(i, i + 2), 16));
106
+ else
107
+ d = RGB[bg] ?? [0, 0, 0];
108
+ }
109
+ return '#' + [0, 1, 2].map((i) => hex2(Math.max(0, Math.min(255, Math.floor(s[i] * alpha + d[i] * (1 - alpha) + 0.5))))).join('');
110
+ }
111
+ function sizeOf(sh) {
112
+ if (sh.kind === 'text') {
113
+ const g = (0, unicode_1.graphemes)(sh.text);
114
+ return [g.length, 1, g];
115
+ }
116
+ if (sh.kind === 'rect')
117
+ return [sh.width, sh.height, []];
118
+ if (sh.kind === 'circle')
119
+ return [2 * sh.radius, 2 * sh.radius, []];
120
+ return [sh.size, sh.size, []];
121
+ }
122
+ function compileDocument(doc) {
123
+ const by = new Map();
124
+ const actors = doc.actors.map((a, index) => {
125
+ const [w, h, clusters] = sizeOf(a.shape);
126
+ const act = { name: a.name, index, z: a.z ?? 0, parent: null, shape: a.shape, w, h, clusters, atPoint: a.at, anchorPoint: a.anchor, color: a.color ?? 'normal', acts: [] };
127
+ by.set(a.name, act);
128
+ return act;
129
+ });
130
+ doc.actors.forEach((a, i) => { if (a.parent !== undefined)
131
+ actors[i].parent = by.get(a.parent); });
132
+ const sorted = [...doc.actions].sort((p, q) => p.beginNs - q.beginNs); // stable: source order breaks ties
133
+ for (const a of sorted)
134
+ by.get(a.actor).acts.push(a);
135
+ const s = doc.scene;
136
+ const order = [...actors].sort((p, q) => p.z - q.z || p.index - q.index);
137
+ return { width: s.width, height: s.height, fps: s.fps, durationNs: s.duration, periodNs: s.period, repeat: s.repeat, fallback: s.fallback, actors, order };
138
+ }
139
+ function compile(source) {
140
+ const r = (0, check_1.parseChecked)(source);
141
+ return r.ok ? { ok: true, scene: compileDocument(r.doc) } : { ok: false, diagnostics: r.diagnostics };
142
+ }
143
+ // ---- argument helpers ----
144
+ const arg = (a, k) => a.args.find((g) => g.key === k)?.value;
145
+ const has = (a, k) => a.args.some((g) => g.key === k);
146
+ const secs = (v) => (0, parse_1.decimalToNs)(v.raw.replace(/^\+/, '').replace(/s$/, ''));
147
+ const num = (v) => v.value;
148
+ const deg = (v) => { const n = v; return n.suffix === 'rad' ? (n.value * 180) / Math.PI : n.value; };
149
+ function coord(c, size, box, anchorNames) {
150
+ if (c.kind === 'num')
151
+ return c.percent ? (c.value / 100) * (box - size) : c.value;
152
+ const rem = box - size;
153
+ const base = c.name === anchorNames[0] ? 0 : c.name === anchorNames[1] ? rem : rem / 2;
154
+ return base + c.offset;
155
+ }
156
+ const resolve = (p, w, h, bw, bh) => [coord(p.x, w, bw, ['left', 'right']), coord(p.y, h, bh, ['top', 'bottom'])];
157
+ function evalActor(sc, a, T) {
158
+ const box = a.parent ? [a.parent.w, a.parent.h] : [sc.width, sc.height];
159
+ const [x0, y0] = resolve(a.atPoint, a.w, a.h, box[0], box[1]);
160
+ const st = { opacity: 1, x: x0, y: y0, scale: 1, color: a.color, reveal: 1, count: null, rot: [0, 0, 0] };
161
+ const done = {};
162
+ for (const ac of a.acts) {
163
+ const b = ac.beginNs, e = ac.endNs, dur = ac.durNs;
164
+ const started = T >= b, over = T >= e;
165
+ const ease = EASE[arg(ac, 'ease')?.value ?? 'linear'];
166
+ const p = started && !over && dur > 0 ? ease((T - b) / dur) : over ? 1 : 0;
167
+ const prop = ac.property;
168
+ if (done[prop])
169
+ continue;
170
+ if (T < b && !(ac.verb === 'fade' || ac.verb === 'wipe' || ac.verb === 'type' || ac.verb === 'slide')) {
171
+ done[prop] = true;
172
+ continue;
173
+ }
174
+ switch (ac.verb) {
175
+ case 'fade': {
176
+ const inn = has(ac, 'in');
177
+ st.opacity = over ? (inn ? 1 : 0) : !started ? (inn ? 0 : st.opacity) : inn ? p : 1 - p;
178
+ break;
179
+ }
180
+ case 'blink': {
181
+ if (started && !over) {
182
+ const half = dur / (2 * num(arg(ac, 'times')));
183
+ if (Math.floor((T - b) / half) % 2 === 1)
184
+ st.opacity = 0;
185
+ }
186
+ break;
187
+ }
188
+ case 'wipe': {
189
+ const inn = has(ac, 'in');
190
+ st.count = null;
191
+ st.reveal = over ? (inn ? 1 : 0) : !started ? (inn ? 0 : st.reveal) : inn ? p : st.reveal * (1 - p);
192
+ break;
193
+ }
194
+ case 'type': {
195
+ const per = secs(arg(ac, 'per_char'));
196
+ st.count = over ? a.clusters.length : !started ? 0 : Math.min(a.clusters.length, Math.floor((T - b) / per));
197
+ st.reveal = 1;
198
+ break;
199
+ }
200
+ case 'move': {
201
+ const [tx, ty] = resolve(arg(ac, 'to').value, a.w, a.h, box[0], box[1]);
202
+ if (over) {
203
+ st.x = tx;
204
+ st.y = ty;
205
+ }
206
+ else {
207
+ st.x += (tx - st.x) * p;
208
+ st.y += (ty - st.y) * p;
209
+ }
210
+ break;
211
+ }
212
+ case 'slide': {
213
+ const edge = arg(ac, 'to').value;
214
+ const inn = has(ac, 'in');
215
+ let ex = st.x, ey = st.y;
216
+ if (edge === 'left')
217
+ ex = -a.w;
218
+ else if (edge === 'right')
219
+ ex = box[0];
220
+ else if (edge === 'top')
221
+ ey = -a.h;
222
+ else
223
+ ey = box[1];
224
+ if (inn) {
225
+ if (over) { /* ends at current origin */ }
226
+ else {
227
+ st.x = ex + (st.x - ex) * p;
228
+ st.y = ey + (st.y - ey) * p;
229
+ }
230
+ }
231
+ else if (over) {
232
+ st.x = ex;
233
+ st.y = ey;
234
+ }
235
+ else if (started) {
236
+ st.x += (ex - st.x) * p;
237
+ st.y += (ey - st.y) * p;
238
+ }
239
+ break;
240
+ }
241
+ case 'scale': {
242
+ const t = num(arg(ac, 'to'));
243
+ st.scale = over ? t : st.scale + (t - st.scale) * p;
244
+ break;
245
+ }
246
+ case 'color': {
247
+ const t = arg(ac, 'to').value;
248
+ if (over || p >= 0.5)
249
+ st.color = t;
250
+ break;
251
+ }
252
+ case 'rotate': {
253
+ ['x', 'y', 'z'].forEach((k, i) => { if (has(ac, k)) {
254
+ const t = deg(arg(ac, k));
255
+ st.rot[i] = over ? t : st.rot[i] + (t - st.rot[i]) * p;
256
+ } });
257
+ break;
258
+ }
259
+ }
260
+ if (T < b)
261
+ done[prop] = true;
262
+ }
263
+ return st;
264
+ }
265
+ // ---- raster ----
266
+ const BIT = [[1, 8], [2, 16], [4, 32], [64, 128]]; // [row][col]
267
+ class Grid {
268
+ constructor(w, h) {
269
+ this.w = w;
270
+ this.h = h;
271
+ this.cells = Array.from({ length: h }, () => Array.from({ length: w }, () => ({ r: ' ', fg: 'normal', bg: '' })));
272
+ }
273
+ put(col, row, r, color, alpha) {
274
+ if (col < 0 || row < 0 || col >= this.w || row >= this.h || r === ' ' || alpha <= 0)
275
+ return;
276
+ const c = this.cells[row][col];
277
+ c.r = r;
278
+ c.fg = alpha >= 1 ? color : blend(color, alpha, c.bg);
279
+ }
280
+ }
281
+ const q9 = (v) => Math.round(v * 1e9) / 1e9;
282
+ function worldMatrices(sc, T) {
283
+ const out = new Map();
284
+ const get = (a) => {
285
+ const hit = out.get(a);
286
+ if (hit)
287
+ return hit;
288
+ const st = evalActor(sc, a, T);
289
+ const par = a.parent ? get(a.parent) : null;
290
+ let ax = a.w / 2, ay = a.h / 2;
291
+ if (a.anchorPoint) {
292
+ ax = coord(a.anchorPoint.x, 0, a.w, ['left', 'right']);
293
+ ay = coord(a.anchorPoint.y, 0, a.h, ['top', 'bottom']);
294
+ }
295
+ let local = mul(trans(st.x + ax, -2 * (st.y + ay), 0), mul(rotM(st.rot[0], st.rot[1], st.rot[2]), mul(scaleM(st.scale), trans(-ax, 2 * ay, 0))));
296
+ if (par)
297
+ local = mul(par.m, local);
298
+ const fr = st.count !== null ? (a.clusters.length ? st.count / a.clusters.length : 1) : st.reveal;
299
+ const r = { m: local, st, alpha: st.opacity * (par ? par.alpha : 1), frac: fr * (par ? par.frac : 1), ax, ay };
300
+ out.set(a, r);
301
+ return r;
302
+ };
303
+ for (const a of sc.actors)
304
+ get(a);
305
+ return out;
306
+ }
307
+ /** cell-space point (col,row) of a local cell-space point through the model matrix */
308
+ function toCell(m, cx, cy) {
309
+ const p = xf(m, [cx, -2 * cy, 0]);
310
+ return [p[0], -p[1] / 2];
311
+ }
312
+ const dotOf = (col, row) => [2 * col - 0.5, 4 * row - 0.5];
313
+ function line(dots, W, H, a, b) {
314
+ let x0 = q9(a[0]), y0 = q9(a[1]), x1 = q9(b[0]), y1 = q9(b[1]);
315
+ // clip the quantized segment to the stage dot box [0,2W-1]x[0,4H-1] before stepping (Liang-Barsky)
316
+ let t0 = 0, t1 = 1;
317
+ const dx0 = x1 - x0, dy0 = y1 - y0;
318
+ for (const [p, qd] of [[-dx0, x0], [dx0, 2 * W - 1 - x0], [-dy0, y0], [dy0, 4 * H - 1 - y0]]) {
319
+ if (p === 0) {
320
+ if (qd < 0)
321
+ return;
322
+ continue;
323
+ }
324
+ const r = qd / p;
325
+ if (p < 0) {
326
+ if (r > t1)
327
+ return;
328
+ if (r > t0)
329
+ t0 = r;
330
+ }
331
+ else {
332
+ if (r < t0)
333
+ return;
334
+ if (r < t1)
335
+ t1 = r;
336
+ }
337
+ }
338
+ [x0, y0, x1, y1] = [x0 + dx0 * t0, y0 + dy0 * t0, x0 + dx0 * t1, y0 + dy0 * t1];
339
+ const n = Math.min(800, Math.max(1, Math.ceil(Math.max(Math.abs(x1 - x0), Math.abs(y1 - y0))))); // reference profile: capped 800, min 1
340
+ for (let i = 0; i <= n; i++) {
341
+ const t = i / n;
342
+ const dx = Math.round(x0 + (x1 - x0) * t), dy = Math.round(y0 + (y1 - y0) * t);
343
+ if (dx >= 0 && dy >= 0 && dx < W * 2 && dy < H * 4)
344
+ dots.add(dy * W * 2 + dx);
345
+ }
346
+ }
347
+ function paintDots(g, dots, color, alpha) {
348
+ const bits = new Map();
349
+ for (const d of dots) {
350
+ const dy = Math.floor(d / (g.w * 2)), dx = d % (g.w * 2);
351
+ const key = Math.floor(dy / 4) * g.w + Math.floor(dx / 2);
352
+ bits.set(key, (bits.get(key) ?? 0) | BIT[dy % 4][dx % 2]);
353
+ }
354
+ for (const [key, b] of bits)
355
+ g.put(key % g.w, Math.floor(key / g.w), String.fromCodePoint(0x2800 + b), color, alpha);
356
+ }
357
+ function clipLine(p0, p1) {
358
+ let t0 = 0, t1 = 1;
359
+ for (let axis = 0; axis < 3; axis++) {
360
+ for (const sgn of [1, -1]) {
361
+ const d0 = p0[3] - sgn * p0[axis], d1 = p1[3] - sgn * p1[axis]; // >= 0 inside: w - s*c
362
+ if (d0 < 0 && d1 < 0)
363
+ return null;
364
+ if (d0 < 0)
365
+ t0 = Math.max(t0, d0 / (d0 - d1));
366
+ else if (d1 < 0)
367
+ t1 = Math.min(t1, d0 / (d0 - d1));
368
+ }
369
+ }
370
+ if (t0 > t1)
371
+ return null;
372
+ const at = (t) => p0.map((v, i) => v + (p1[i] - v) * t);
373
+ return [at(t0), at(t1)];
374
+ }
375
+ function renderActor(sc, g, a, w) {
376
+ const color = w.st.color, alpha = w.alpha;
377
+ if (alpha <= 0 || w.frac <= 0 && a.shape.kind !== 'text')
378
+ return;
379
+ const sh = a.shape;
380
+ if (sh.kind === 'text') {
381
+ const n = a.clusters.length;
382
+ const shown = w.st.count !== null ? Math.min(w.st.count, Math.floor(w.frac * n + 1e-9) === n ? n : Math.floor(w.frac * n + 1e-9)) : Math.floor(w.frac * n + 1e-9);
383
+ const cnt = w.st.count !== null && w.frac >= 1 ? w.st.count : shown;
384
+ for (let i = 0; i < Math.min(cnt, n); i++) {
385
+ const [c, r] = toCell(w.m, i, 0);
386
+ g.put(rnd(c), rnd(r), (0, unicode_1.cellRune)(a.clusters[i]), color, alpha);
387
+ }
388
+ return;
389
+ }
390
+ const dots = new Set();
391
+ const cellToDot = (cx, cy) => { const [c, r] = toCell(w.m, cx, cy); return dotOf(c, r); };
392
+ // local dot (dx,dy) -> local cell-space centre -> model -> dot
393
+ const dotThrough = (dx, dy) => cellToDot((dx + 0.5) / 2, (dy + 0.5) / 4);
394
+ const clipX = w.frac < 1 ? w.frac * 2 * a.w : Infinity; // IMPL-DEFINED left-to-right reveal on local dot columns
395
+ if (sh.kind === 'rect') {
396
+ const W = 2 * sh.width - 1, H = 4 * sh.height - 1;
397
+ const corners = [[0, 0], [W, 0], [W, H], [0, H]];
398
+ for (let i = 0; i < 4; i++)
399
+ line(dots, g.w, g.h, dotThrough(...corners[i]), dotThrough(...corners[(i + 1) % 4]));
400
+ }
401
+ else if (sh.kind === 'circle') {
402
+ const rx = 2 * sh.radius - 0.5, ry = 4 * sh.radius - 0.5; // dot radii of the 2N x 2N cell bounding box
403
+ // 720 chords, k=1..720, angle k*pi/360, starting at the rightmost point (reference profile, not SPEC)
404
+ let prev = dotThrough(2 * rx, ry);
405
+ for (let k = 1; k <= 720; k++) {
406
+ const t = (k * Math.PI) / 360;
407
+ const lx = rx + rx * Math.cos(t), ly = ry + ry * Math.sin(t);
408
+ const cur = dotThrough(lx, ly);
409
+ if (lx <= clipX)
410
+ line(dots, g.w, g.h, prev, cur);
411
+ prev = cur;
412
+ }
413
+ }
414
+ else {
415
+ cubeDots(dots, g, a, sh.size, w.m);
416
+ }
417
+ paintDots(g, dots, color, alpha);
418
+ if (sh.kind === 'rect' && sh.title) {
419
+ const t = (0, unicode_1.graphemes)(sh.title);
420
+ for (let i = 0; i < Math.min(t.length, sh.width - 2); i++) {
421
+ const [c, r] = toCell(w.m, 1 + i, 0);
422
+ g.put(rnd(c), rnd(r), (0, unicode_1.cellRune)(t[i]), color, alpha);
423
+ }
424
+ }
425
+ }
426
+ function cubeDots(dots, g, a, size, M) {
427
+ const n = size;
428
+ const verts = [];
429
+ for (const dx of [0, 1])
430
+ for (const dy of [0, 1])
431
+ for (const dz of [0, 1])
432
+ verts.push([n / 2 + (dx ? n / 2 : -n / 2), -n + (dy ? n / 2 : -n / 2), dz ? n / 2 : -n / 2]);
433
+ const centre = xf(M, [n / 2, -n, 0]);
434
+ const tm = trans(-centre[0], -centre[1], -(centre[2] + 3 * n));
435
+ const fov = (50 * Math.PI) / 180, qq = 1 / Math.tan(fov / 2), asp = n / (2 * n), near = 0.1, far = 1000;
436
+ const P = [qq / asp, 0, 0, 0, 0, qq, 0, 0, 0, 0, -(far + near) / (far - near), -1, 0, 0, (-2 * far * near) / (far - near), 0];
437
+ const PV = mul(P, tm);
438
+ const clip = verts.map((v) => xf(PV, xf(M, v).slice(0, 3)));
439
+ // viewport: n x n cells centred on the transformed cube centre (cell space)
440
+ const [ccol, crow] = (() => { const p = centre; return [p[0], -p[1] / 2]; })();
441
+ const left = ccol - n / 2, top = crow - n / 2;
442
+ const edges = [];
443
+ for (let i = 0; i < 8; i++)
444
+ for (const bit of [1, 2, 4])
445
+ if (!(i & bit))
446
+ edges.push([i, i | bit]);
447
+ for (const [i, j] of edges) {
448
+ const c = clipLine(clip[i], clip[j]);
449
+ if (!c)
450
+ continue;
451
+ const pr = c.map((p) => {
452
+ const ndcX = p[0] / p[3], ndcY = p[1] / p[3];
453
+ const col = left + ((ndcX + 1) * n) / 2, row = top + ((1 - ndcY) * n) / 2;
454
+ return dotOf(col, row);
455
+ });
456
+ line(dots, g.w, g.h, pr[0], pr[1]);
457
+ }
458
+ }
459
+ function compose(sc, T, W, H) {
460
+ const g = new Grid(Math.min(W, sc.width), Math.min(H, sc.height));
461
+ // paint on the full stage then crop, so clipping/clamping stays stage-relative
462
+ const full = new Grid(sc.width, sc.height);
463
+ const wm = worldMatrices(sc, T);
464
+ for (const a of sc.order)
465
+ renderActor(sc, full, a, wm.get(a));
466
+ for (let r = 0; r < g.h; r++)
467
+ for (let c = 0; c < g.w; c++)
468
+ g.cells[r][c] = full.cells[r][c];
469
+ return g;
470
+ }
471
+ /** Frame at absolute time `tNs` (integer nanoseconds). `Done` per SPEC; repeat semantics included. */
472
+ function frameNs(sc, tNs, width = sc.width, height = sc.height) {
473
+ const D = sc.durationNs;
474
+ let local = tNs, done = D === 0;
475
+ if (D > 0) {
476
+ const total = sc.repeat === Infinity ? Infinity : D * sc.repeat;
477
+ if (tNs >= total) {
478
+ local = D;
479
+ done = true;
480
+ }
481
+ else {
482
+ local = tNs % D;
483
+ }
484
+ }
485
+ else
486
+ local = 0;
487
+ const g = compose(sc, local, width, height);
488
+ return { width: g.w, height: g.h, cells: g.cells, done };
489
+ }
490
+ /** Fallback sample of the simple timeline (no repeat); `done` is omitted. */
491
+ function fallbackFrame(sc, width = sc.width, height = sc.height) {
492
+ const f = sc.fallback;
493
+ const T = f.kind === 'start' ? 0 : f.kind === 'end' ? Infinity : f.ns;
494
+ const g = compose(sc, T, width, height);
495
+ return { width: g.w, height: g.h, cells: g.cells };
496
+ }
package/dist/index.d.ts CHANGED
@@ -1,16 +1,15 @@
1
1
  export declare const VERSION: string;
2
- /** One diagnostic. `line` is 1-based. */
3
- export interface Diag {
4
- line: number;
5
- col: number;
6
- message: string;
7
- }
8
- /** Alias of {@link Diag}. */
9
- export type Problem = Diag;
10
- /** Result of {@link check}: an empty array means no limit/header problems. */
11
- export type CheckResult = Diag[];
12
- /**
13
- * Subset check. Returns diagnostics; [] means "no limit/header problems found"
14
- * (NOT "valid MDGL"). Throws TypeError for non-string input.
15
- */
16
- export declare function check(source: string): CheckResult;
2
+ export { check, checkDocument, parseChecked } from './check';
3
+ export type { CheckedAction, CheckedDocument, CheckedScene, Fallback, ParseResult } from './check';
4
+ export { parse } from './parse';
5
+ export type { ActionStmt, ActorStmt, Arg, Coord, Diag, Document, Point, SceneStmt, Shape, Stmt, Value } from './parse';
6
+ /** Alias of Diag. */
7
+ export type Problem = import('./parse').Diag;
8
+ /** Result of check(): empty means valid. */
9
+ export type CheckResult = import('./parse').Diag[];
10
+ export { UNICODE_VERSION, cellRune, clusterWidth, graphemeCount, graphemeRanges, graphemes } from './unicode';
11
+ export { compile, compileDocument, frameNs, fallbackFrame } from './eval';
12
+ export type { Cell, Compiled, CompileResult, Frame } from './eval';
13
+ export { handleLine, mathInt, MAX_REQUEST_LINE } from "./adapter";
14
+ export { play, plainFallback, plainText, sanitizeRune, SYNC_BEGIN, SYNC_END, HIDE_CURSOR, SHOW_CURSOR, RESET } from './play';
15
+ export type { Clock, PlayDeps, PlayEnd, PlayHandle, PlayResult } from './play';
package/dist/index.js CHANGED
@@ -1,100 +1,38 @@
1
1
  "use strict";
2
- // MDGL 0.0.1 -- SUBSET limit/header checker. NOT a conformant MDGL implementation.
3
- // Checks only: input <= 256 KiB, line <= 16 KiB, scene header (width 1..200,
4
- // height 1..60, fallback; no unknown/duplicate keys), statement order,
5
- // <= 64 actors, <= 1024 actions. Does NOT parse actor/action bodies or check
6
- // grammar, timing, references or rendering. An empty result means
7
- // "no limit/header problems found", NOT "valid MDGL". Reference: the Go module.
8
- // Spec: https://github.com/enmaq/mdgl/blob/main/SPEC.md
9
2
  Object.defineProperty(exports, "__esModule", { value: true });
10
- exports.VERSION = void 0;
11
- exports.check = check;
12
- exports.VERSION = '0.2.0';
13
- const MAX_INPUT = 256 * 1024;
14
- const MAX_LINE = 16384;
15
- const MAX_ACTORS = 64;
16
- const MAX_ACTIONS = 1024;
17
- const KEYS = ['width', 'height', 'period', 'repeat', 'fallback', 'fps'];
18
- const d = (line, message) => [{ line, col: 1, message }];
19
- const bytes = (s) => Buffer.byteLength(s, 'utf8');
20
- function sceneHeader(n, rest) {
21
- const seen = new Map();
22
- for (const tok of rest.split(/[ \t\r]+/).filter(Boolean)) {
23
- const i = tok.indexOf('=');
24
- if (i <= 0 || i === tok.length - 1)
25
- return d(n, `malformed scene attribute ${JSON.stringify(tok)}`);
26
- const k = tok.slice(0, i);
27
- if (!KEYS.includes(k))
28
- return d(n, `unknown scene attribute ${JSON.stringify(k)}`);
29
- if (seen.has(k))
30
- return d(n, `duplicate scene attribute ${JSON.stringify(k)}`);
31
- seen.set(k, tok.slice(i + 1));
32
- }
33
- const out = ['width', 'height', 'fallback']
34
- .filter((k) => !seen.has(k))
35
- .map((k) => ({ line: n, col: 1, message: `scene header missing ${k}=` }));
36
- if (out.length)
37
- return out;
38
- for (const [k, max] of [['width', 200], ['height', 60]]) {
39
- const v = seen.get(k);
40
- if (!/^[0-9]{1,9}$/.test(v) || Number(v) < 1 || Number(v) > max) {
41
- return d(n, `${k} must be an integer 1..${max}`);
42
- }
43
- }
44
- return [];
45
- }
46
- /**
47
- * Subset check. Returns diagnostics; [] means "no limit/header problems found"
48
- * (NOT "valid MDGL"). Throws TypeError for non-string input.
49
- */
50
- function check(source) {
51
- if (typeof source !== 'string')
52
- throw new TypeError('mdgl.check: source must be a string');
53
- if (bytes(source) > MAX_INPUT)
54
- return d(1, 'input exceeds 256 KiB');
55
- let scene = false, action = false, version = false, actors = 0, actions = 0;
56
- const lines = source.split('\n');
57
- for (let i = 0; i < lines.length; i++) {
58
- const n = i + 1;
59
- if (bytes(lines[i]) > MAX_LINE)
60
- return d(n, 'line exceeds 16 KiB');
61
- const t = lines[i].replace(/^[ \t\r]+|[ \t\r]+$/g, '');
62
- if (t === '' || t[0] === '#')
63
- continue;
64
- const head = t[0] === '@' ? '@' : t.split(/[ \t]/)[0];
65
- switch (head) {
66
- case 'mdgl':
67
- if (scene || version || t.slice(4).trim() !== '0.1')
68
- return d(n, 'expected initial mdgl 0.1');
69
- version = true;
70
- break;
71
- case 'scene': {
72
- if (scene)
73
- return d(n, 'duplicate scene');
74
- scene = true;
75
- const r = sceneHeader(n, t.slice(5));
76
- if (r.length)
77
- return r;
78
- break;
79
- }
80
- case 'actor':
81
- if (!scene)
82
- return d(n, 'scene must precede actors');
83
- if (action)
84
- return d(n, 'actors must precede actions');
85
- if (++actors > MAX_ACTORS)
86
- return d(n, 'actor limit 64 exceeded');
87
- break;
88
- case '@':
89
- if (!scene)
90
- return d(n, 'scene must precede actions');
91
- action = true;
92
- if (++actions > MAX_ACTIONS)
93
- return d(n, 'action limit 1024 exceeded');
94
- break;
95
- default:
96
- return d(n, 'unsupported statement (expected mdgl, scene, actor or @)');
97
- }
98
- }
99
- return scene ? [] : d(1, 'missing scene');
100
- }
3
+ exports.RESET = exports.SHOW_CURSOR = exports.HIDE_CURSOR = exports.SYNC_END = exports.SYNC_BEGIN = exports.sanitizeRune = exports.plainText = exports.plainFallback = exports.play = exports.MAX_REQUEST_LINE = exports.mathInt = exports.handleLine = exports.fallbackFrame = exports.frameNs = exports.compileDocument = exports.compile = exports.graphemes = exports.graphemeRanges = exports.graphemeCount = exports.clusterWidth = exports.cellRune = exports.UNICODE_VERSION = exports.parse = exports.parseChecked = exports.checkDocument = exports.check = exports.VERSION = void 0;
4
+ // @enmaq/mdgl: MDGL 0.1 parser and checker (development tree; evaluator and
5
+ // rendering are not implemented yet). Spec: ../SPEC.md
6
+ exports.VERSION = '0.3.0';
7
+ var check_1 = require("./check");
8
+ Object.defineProperty(exports, "check", { enumerable: true, get: function () { return check_1.check; } });
9
+ Object.defineProperty(exports, "checkDocument", { enumerable: true, get: function () { return check_1.checkDocument; } });
10
+ Object.defineProperty(exports, "parseChecked", { enumerable: true, get: function () { return check_1.parseChecked; } });
11
+ var parse_1 = require("./parse");
12
+ Object.defineProperty(exports, "parse", { enumerable: true, get: function () { return parse_1.parse; } });
13
+ var unicode_1 = require("./unicode");
14
+ Object.defineProperty(exports, "UNICODE_VERSION", { enumerable: true, get: function () { return unicode_1.UNICODE_VERSION; } });
15
+ Object.defineProperty(exports, "cellRune", { enumerable: true, get: function () { return unicode_1.cellRune; } });
16
+ Object.defineProperty(exports, "clusterWidth", { enumerable: true, get: function () { return unicode_1.clusterWidth; } });
17
+ Object.defineProperty(exports, "graphemeCount", { enumerable: true, get: function () { return unicode_1.graphemeCount; } });
18
+ Object.defineProperty(exports, "graphemeRanges", { enumerable: true, get: function () { return unicode_1.graphemeRanges; } });
19
+ Object.defineProperty(exports, "graphemes", { enumerable: true, get: function () { return unicode_1.graphemes; } });
20
+ var eval_1 = require("./eval");
21
+ Object.defineProperty(exports, "compile", { enumerable: true, get: function () { return eval_1.compile; } });
22
+ Object.defineProperty(exports, "compileDocument", { enumerable: true, get: function () { return eval_1.compileDocument; } });
23
+ Object.defineProperty(exports, "frameNs", { enumerable: true, get: function () { return eval_1.frameNs; } });
24
+ Object.defineProperty(exports, "fallbackFrame", { enumerable: true, get: function () { return eval_1.fallbackFrame; } });
25
+ var adapter_1 = require("./adapter");
26
+ Object.defineProperty(exports, "handleLine", { enumerable: true, get: function () { return adapter_1.handleLine; } });
27
+ Object.defineProperty(exports, "mathInt", { enumerable: true, get: function () { return adapter_1.mathInt; } });
28
+ Object.defineProperty(exports, "MAX_REQUEST_LINE", { enumerable: true, get: function () { return adapter_1.MAX_REQUEST_LINE; } });
29
+ var play_1 = require("./play");
30
+ Object.defineProperty(exports, "play", { enumerable: true, get: function () { return play_1.play; } });
31
+ Object.defineProperty(exports, "plainFallback", { enumerable: true, get: function () { return play_1.plainFallback; } });
32
+ Object.defineProperty(exports, "plainText", { enumerable: true, get: function () { return play_1.plainText; } });
33
+ Object.defineProperty(exports, "sanitizeRune", { enumerable: true, get: function () { return play_1.sanitizeRune; } });
34
+ Object.defineProperty(exports, "SYNC_BEGIN", { enumerable: true, get: function () { return play_1.SYNC_BEGIN; } });
35
+ Object.defineProperty(exports, "SYNC_END", { enumerable: true, get: function () { return play_1.SYNC_END; } });
36
+ Object.defineProperty(exports, "HIDE_CURSOR", { enumerable: true, get: function () { return play_1.HIDE_CURSOR; } });
37
+ Object.defineProperty(exports, "SHOW_CURSOR", { enumerable: true, get: function () { return play_1.SHOW_CURSOR; } });
38
+ Object.defineProperty(exports, "RESET", { enumerable: true, get: function () { return play_1.RESET; } });