@moood/render 0.1.2 → 0.1.3

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/engine/engine.js CHANGED
@@ -352,8 +352,9 @@ const Moood = (() => {
352
352
  const STATE = { values: {}, ver: 0, was: key => STATE.values[key], sample: null };
353
353
  // what the shown scene's code shares (kit.js): the world's modules it lists and the scene's lib
354
354
  let SHARED = { kit: {}, lib: {} };
355
- // Where an element is now, as its painting code says (e.at): fractions of the stage as seen
356
- // from the start — speech bubbles and widgets follow it (stage.screenOf).
355
+ // Where an element is now, as its painting code says (e.at): the point it marks, in metres from
356
+ // its anchor as painted — speech bubbles and widgets follow it (stage.screenOf) wherever the
357
+ // element is put (its `place`)
357
358
  const FOCUS = new Map();
358
359
  // A platformer's hero as it moves now (camera.mode platform): its painting code reads e.hero —
359
360
  // { vx, vy (m/s), ground } — to move its legs; the engine moves and turns the painting itself.
@@ -376,22 +377,19 @@ const Moood = (() => {
376
377
  compiled.set(code, fn);
377
378
  }
378
379
  if (!fn) return;
379
- // sky and ground elements are painted in stage heights from the stage's top-left corner, as
380
- // seen from the start (x 0…W/H, y 0…1, with margins); others in metres from their anchor
381
- const flat = o.sky || o.ground;
382
- const ppm = flat ? H : o.size * H / (o.height || 2), x = flat ? 0 : o.x * W, y = flat ? 0 : o.y * H;
380
+ const [x, y, ppm] = anchorOf(o, W, H);
383
381
  ctx.save();
384
382
  ctx.translate(x, y);
385
383
  ctx.scale(ppm, ppm);
386
384
  ctx.fillStyle = ctx.strokeStyle = o.color || '#000000';
387
385
  ctx.lineCap = ctx.lineJoin = 'round';
388
386
  const period = o.period || 2;
389
- // a point the code names, as painted — through whatever it did to the canvas since (translate,
390
- // scale, rotate): [x, y, how much it is scaled]
387
+ // a point the code names, in its units from the anchor as painted — through whatever it did to
388
+ // the canvas since (translate, scale, rotate): [x, y, how much it is scaled]
391
389
  const base = ctx.getTransform(), unbase = base.inverse();
392
390
  const painted = (lx, ly) => {
393
391
  const now = ctx.getTransform(), q = unbase.transformPoint(now.transformPoint({ x: lx, y: ly }));
394
- return [x + q.x * ppm, y + q.y * ppm, Math.hypot(now.a, now.b) / Math.hypot(base.a, base.b)];
392
+ return [q.x, q.y, Math.hypot(now.a, now.b) / Math.hypot(base.a, base.b)];
395
393
  };
396
394
  const read = { time: false, state: false };
397
395
  READS.set(o, read);
@@ -404,11 +402,11 @@ const Moood = (() => {
404
402
  light(lx, ly, r, color = '#ffd08a', strength = 0.6) {
405
403
  try {
406
404
  const [px, py, k] = painted(lx, ly);
407
- lightsOut.push({ x: px, y: py, r: r * k * ppm, col: rgb(color), a: strength, flicker: o.flicker ?? 0.3 });
405
+ lightsOut.push({ x: x + px * ppm, y: y + py * ppm, r: r * k * ppm, col: rgb(color), a: strength, flicker: o.flicker ?? 0.3 });
408
406
  } catch (err) { reportError(o, err); }
409
407
  },
410
408
  // where this element is now (its head, say): what speaks or hovers over it follows this point
411
- at(lx, ly) { if (o.id) { const [px, py] = painted(lx, ly); FOCUS.set(o.id, { x: px / W, y: py / H }); } },
409
+ at(lx, ly) { if (o.id) { const [px, py] = painted(lx, ly); FOCUS.set(o.id, { x: px, y: py }); } },
412
410
  anim: MooodKit.anim, kit: SHARED.kit, lib: SHARED.lib,
413
411
  was: (key, ago = 0) => { read.state = true; return STATE.was(key, ago); },
414
412
  speed: key => { read.state = true; const a = STATE.was(key, 0), b = STATE.was(key, 0.05); return typeof a === 'number' && typeof b === 'number' ? (a - b) / 0.05 : 0; },
@@ -417,6 +415,12 @@ const Moood = (() => {
417
415
  try { fn(ctx, e); } catch (err) { reportError(o, err); }
418
416
  ctx.restore();
419
417
  }
418
+ // Where a custom element's painting has its origin on the W × H stage, and its scale: [x, y, px a
419
+ // unit]. Sky and ground elements are painted in stage heights from the stage's top-left corner, as
420
+ // seen from the start (x 0…W/H, y 0…1, with margins); others in metres from their anchor.
421
+ function anchorOf(o, W, H) {
422
+ return o.sky || o.ground ? [0, 0, H] : [o.x * W, o.y * H, o.size * H / (o.height || 2)];
423
+ }
420
424
  // The box an object's painting fits in (pixels of the W × H stage): built-in kinds a fixed frame
421
425
  // around the base, custom ones their `box` in metres (default: a little wider than tall).
422
426
  function objectBox(o, W, H) {
@@ -545,6 +549,15 @@ const Moood = (() => {
545
549
  const land = (spec.layers || []).filter(L => LAND.has(L.kind));
546
550
  return land.length ? Math.min(...land.map(L => L.y)) : 0.6;
547
551
  }
552
+ // An element that moves through the world has `place`: its across, distance, elevation or x are the
553
+ // names of numbers in the state (metres) — it is where they say now, the rest as the spec has it.
554
+ const PLACE = ['across', 'distance', 'elevation', 'x'];
555
+ function placed(o) {
556
+ if (!o.place) return o;
557
+ const n = { ...o };
558
+ for (const f of PLACE) { const k = o.place[f]; if (typeof k === 'string' && typeof STATE.values[k] === 'number') n[f] = STATE.values[k]; }
559
+ return n;
560
+ }
548
561
  function depths(spec) {
549
562
  const layers = spec.layers || [];
550
563
  // An object stands where its base is — unless it is attached to a bigger one (a window on
@@ -589,21 +602,27 @@ const Moood = (() => {
589
602
  const paintZ = o => (o.plane ? planeZ(o) : custom(o) ? sightZ(o) : own(host(o) || o));
590
603
  const objectX = o => (bearing(o) ? 0.5 : typeof o.across === 'number' && !o.sky ? 0.5 + o.across / EYE / STAGE / paintZ(o) : o.x ?? 0.5);
591
604
  const objectY = o => (o.sky || o.ground ? 0 : custom(o) ? yh + (1 - (o.elevation || 0) / EYE) / sightZ(o) : o.kind === 'bird' ? o.y : o.y + shift(o));
605
+ const objectSize = o => (o.sky || o.ground ? 1 : custom(o) ? (o.height || 2) / EYE / sightZ(o) : o.size);
606
+ // where it stands in the world (eye heights: X to the right, Z ahead, Y its anchor's height) and
607
+ // the distance Zp it is painted for
608
+ const objectAt = o => {
609
+ const Zp = paintZ(o), Y = 1 - (objectY(o) - yh) * Zp;
610
+ if (!bearing(o)) return { X: (objectX(o) - 0.5) * STAGE * Zp, Y, Z: Zp, Zp };
611
+ const b = o.bearing * Math.PI / 180, d = o.distance / EYE;
612
+ return { X: d * Math.sin(b), Y, Z: d * Math.cos(b), Zp };
613
+ };
614
+ // ground planes are drawn right after the sky, under everything standing on them
615
+ // a horizontal plane (gl.js) is ordered by its far edge, before what stands on it
616
+ const objectZ = o => (o.sky ? Infinity : o.ground ? Z_FAR * 4 : o.plane ? clamp(planeZ(o) + (o.depth || 4) / 2 / EYE, Z_NEAR, Z_FAR) : custom(o) ? customZ(o) : o.kind === 'bird' ? 30 : own(host(o) || o));
592
617
  return {
593
- yh, Z, objectX,
594
- // where it stands in the world (eye heights: X to the right, Z ahead, Y its anchor's
595
- // height) and the distance Zp it is painted for
596
- objectAt: o => {
597
- const Zp = paintZ(o), Y = 1 - (objectY(o) - yh) * Zp;
598
- if (!bearing(o)) return { X: (objectX(o) - 0.5) * STAGE * Zp, Y, Z: Zp, Zp };
599
- const b = o.bearing * Math.PI / 180, d = o.distance / EYE;
600
- return { X: d * Math.sin(b), Y, Z: d * Math.cos(b), Zp };
618
+ yh, Z, objectX, objectAt, objectZ,
619
+ // an element where it is now (its `place`), the one truth the renderers, the picking and
620
+ // whatever follows it go by: { o: as painted on the stage (x, y, size: fractions of it),
621
+ // Z: the depth it is drawn at, at: where it stands in the world }
622
+ now: o => {
623
+ const n = placed(o);
624
+ return { o: { ...o, x: objectX(n), y: objectY(n), size: objectSize(n), _yh: yh }, Z: objectZ(n), at: objectAt(n) };
601
625
  },
602
- // ground planes are drawn right after the sky, under everything standing on them
603
- // a horizontal plane (gl.js) is ordered by its far edge, before what stands on it
604
- objectZ: o => (o.sky ? Infinity : o.ground ? Z_FAR * 4 : o.plane ? clamp(planeZ(o) + (o.depth || 4) / 2 / EYE, Z_NEAR, Z_FAR) : custom(o) ? customZ(o) : o.kind === 'bird' ? 30 : own(host(o) || o)),
605
- objectY,
606
- objectSize: o => (o.sky || o.ground ? 1 : custom(o) ? (o.height || 2) / EYE / sightZ(o) : o.size),
607
626
  };
608
627
  }
609
628
  // Screen transform [a b c d e f] for a plane at distance Z (Infinity = the sky): maps the
@@ -635,10 +654,11 @@ const Moood = (() => {
635
654
  return { m: [s, 0, 0, s, q[0] - s * ax, q[1] - s * ay], D: q[2] };
636
655
  }
637
656
  // Where an element's painting (as depths() places it on the SW-wide stage) goes on the frame from
638
- // camera cam: { m: the transform, D: its distance along the gaze }, or null behind the eye
657
+ // camera cam, where it is now: { m: the transform, D: its distance along the gaze }, or null behind the eye
639
658
  function placeOf(o, cam, W, H, dz, round) {
640
- if (!round) { const Z = dz.objectZ(o); return { m: view(cam, Z, W, H, dz.yh), D: Z - cam.z }; }
641
- return billboard(cam, dz.objectAt(o), dz.objectX(o) * STAGE * H, dz.objectY(o) * H, W, H, dz.yh);
659
+ const n = dz.now(o);
660
+ if (!round) return { m: view(cam, n.Z, W, H, dz.yh), D: n.Z - cam.z };
661
+ return billboard(cam, n.at, n.o.x * STAGE * H, n.o.y * H, W, H, dz.yh);
642
662
  }
643
663
  // The hero of a platformer h = { dx, dy (metres from where it is painted), flip } as a transform
644
664
  // of its painting: its anchor painted at x = ax, `ppm` painted px per metre
@@ -779,9 +799,12 @@ const Moood = (() => {
779
799
  if (L.kind === 'water') return { L, Z };
780
800
  return { L, Z, ...staticLayer(L, SW, H, seed + i * 101) };
781
801
  }).sort((a, b) => b.Z - a.Z);
782
- // looking around, each also stands somewhere in the world (p, depths().objectAt)
783
- const round = camSetup(spec).round;
784
- const objects = (spec.objects || []).map(o => ({ o: { ...o, x: dz.objectX(o), y: dz.objectY(o), size: dz.objectSize(o), _yh: dz.yh }, Z: dz.objectZ(o), p: round && !o.sky && !o.ground ? dz.objectAt(o) : null })).sort((a, b) => (b.Z - a.Z) || 0);
802
+ // looking around, each also stands somewhere in the world (p, depths().objectAt); what moves
803
+ // through the world (`place`) is put where the state has it as each frame starts (move)
804
+ const round = camSetup(spec).round, farFirst = (a, b) => (b.Z - a.Z) || 0;
805
+ const at = o => { const n = dz.now(o); return { o: n.o, Z: n.Z, p: round && !o.sky && !o.ground ? n.at : null }; };
806
+ const objects = (spec.objects || []).map(o => ({ from: o, ...at(o) })).sort(farFirst), moving = objects.filter(ob => ob.from.place);
807
+ const move = () => { if (!moving.length) return; for (const ob of moving) Object.assign(ob, at(ob.from)); objects.sort(farFirst); };
785
808
  const sky = !spec.sky ? '#000000' : (() => {
786
809
  const c = document.createElement('canvas').getContext('2d');
787
810
  const g = c.createLinearGradient(0, 0, 0, H);
@@ -795,7 +818,7 @@ const Moood = (() => {
795
818
  v.addColorStop(0, 'rgba(0,0,0,0)');
796
819
  v.addColorStop(1, `rgba(0,0,0,${0.75 * clamp(spec.light?.vignette ?? 0.5, 0, 1)})`);
797
820
  vg.fillStyle = v; vg.fillRect(0, 0, W, H);
798
- return { W, H, SW, u, x0, span, yh: dz.yh, round, lim: limits(spec, W, H), sky, stars, layers, particles, objects, vignette, scratch: layer(W, H) };
821
+ return { W, H, SW, u, x0, span, yh: dz.yh, round, lim: limits(spec, W, H), sky, stars, layers, particles, objects, move, vignette, scratch: layer(W, H) };
799
822
  }
800
823
 
801
824
  // Looking around, with Canvas 2D (the fallback): the sky turned as a flat band (not bent into a
@@ -852,6 +875,7 @@ const Moood = (() => {
852
875
  // S.out: where the frame lies in the canvas (pixels); the scene goes on around it as painted
853
876
  function frame(ctx, S, spec, cam, t, dt) {
854
877
  const { W, H, SW, u } = S, out = S.out;
878
+ S.move();
855
879
  ctx.setTransform(1, 0, 0, 1, 0, 0);
856
880
  ctx.fillStyle = '#000000';
857
881
  ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
@@ -1048,7 +1072,7 @@ const Moood = (() => {
1048
1072
  const pick = (clientX, clientY) => {
1049
1073
  const [cx, cy] = local(clientX, clientY), Hc = box.h, Wc = box.w, yh = S?.yh ?? 0.6, dz = spec ? depths(spec) : null;
1050
1074
  const at = D => ({ Z: target.z + D, x: target.x + (cx - Wc / 2) / Hc * D });
1051
- const near = (spec?.objects || []).filter(o => o.kind !== 'bird' && !o.sky && !o.ground && !o.plane).map(o => ({ o: { ...o, x: dz.objectX(o), y: dz.objectY(o), size: dz.objectSize(o), _yh: yh }, Z: dz.objectZ(o) }))
1075
+ const near = (spec?.objects || []).filter(o => o.kind !== 'bird' && !o.sky && !o.ground && !o.plane).map(o => dz.now(o))
1052
1076
  .filter(({ Z }) => fadeNear(Z, target) > 0).sort((a, b) => a.Z - b.Z);
1053
1077
  for (const { o, Z } of near) {
1054
1078
  const m = view(target, Z, Wc, Hc, yh), b = objectBox(o, STAGE * Hc, Hc);
@@ -1114,32 +1138,31 @@ const Moood = (() => {
1114
1138
  glide.vy += ((target.y - y0) / dt - glide.vy) * k;
1115
1139
  lastMove = now;
1116
1140
  };
1117
- // where in the canvas (CSS px) an element is from camera c — the point its code marks (e.at),
1118
- // else its anchor (its top, with over) — lifted by `lift` metres: [x, y, its distance]; null
1119
- // when out of sight
1141
+ // where in the canvas (CSS px) an element is from camera c — where its `place` has it now: the
1142
+ // point its code marks (e.at), else its anchor (its top, with over) — lifted by `lift` metres:
1143
+ // [x, y, its distance]; null when out of sight
1120
1144
  function seenFrom(c, id, lift = 0, over = false) {
1121
1145
  const o = (spec?.objects || []).find(o => o.id === id);
1122
1146
  if (!o || o.sky) return null;
1123
1147
  const W = box.w, H = box.h, dz = depths(spec), yh = dz.yh;
1124
- if (round()) {
1125
- // looking around: its anchor (or the point its code marks) seen from where you stand
1126
- const p = dz.objectAt(o);
1127
- if (o.plane || !FOCUS.has(id)) {
1128
- const q = project(c, p.X, p.Y + (lift + (over && !o.plane ? o.height ?? EYE : 0)) / EYE, p.Z, W, H, yh);
1148
+ if (o.plane) {
1149
+ const p = dz.objectAt(o), Z = p.Zp, up = lift / EYE;
1150
+ if (round()) {
1151
+ const q = project(c, p.X, p.Y + up, p.Z, W, H, yh);
1129
1152
  return q && q[2] > 0.3 ? [box.x + q[0], box.y + q[1], q[2]] : null;
1130
1153
  }
1131
- const b = billboard(c, p, dz.objectX(o) * STAGE * H, dz.objectY(o) * H, W, H, yh), f = FOCUS.get(id);
1132
- if (!b || b.D < 0.3) return null;
1133
- return [box.x + b.m[0] * f.x * STAGE * H + b.m[4], box.y + b.m[3] * (f.y * H - (lift / EYE) * H / p.Zp) + b.m[5], b.D];
1154
+ if (fadeNear(Z, c) < 0.3) return null;
1155
+ const m = view(c, Z, W, H, yh), px = dz.objectX(o) * STAGE * H, py = (yh + (1 - (o.elevation || 0) / EYE) / Z - up / Z) * H;
1156
+ return [box.x + m[0] * px + m[4], box.y + m[3] * py + m[5], Z - c.z];
1134
1157
  }
1135
- const Z = o.plane ? (o.distance > 0 ? o.distance : 10) / EYE : dz.objectZ(o);
1136
- if (Z - c.z < 0.3 || fadeNear(Z, c) < 0.3) return null;
1137
- const f = FOCUS.get(id) || { x: dz.objectX(o), y: o.plane ? yh + (1 - (o.elevation || 0) / EYE) / Z : dz.objectY(o) };
1158
+ const at = placeOf(o, c, W, H, dz, round());
1159
+ // behind the eye, or faded out as it is passed (fadeNear)
1160
+ if (!at || at.D < 0.48) return null;
1161
+ const n = dz.now(o), [ax, ay, ppm] = anchorOf(n.o, STAGE * H, H), f = FOCUS.get(id) || { x: 0, y: 0 };
1138
1162
  if (over && !FOCUS.has(id)) lift += o.height ?? EYE;
1139
- const m = view(c, Z, W, H, yh);
1140
- let px = f.x * STAGE * H, py = f.y * H - (lift / EYE) * H / Z;
1141
- if (c.hero && id === c.hero.id) { const hm = heroMatrix(c.hero, dz.objectX(o) * STAGE * H, dz.objectSize(o) * H / (o.height || 2)); px = hm[0] * px + hm[4]; py += hm[5]; }
1142
- return [box.x + m[0] * px + m[4], box.y + m[3] * py + m[5], Z - c.z];
1163
+ let px = ax + f.x * ppm, py = ay + f.y * ppm - lift / EYE * H / n.at.Zp;
1164
+ if (c.hero && id === c.hero.id) { const hm = heroMatrix(c.hero, ax, ppm); px = hm[0] * px + hm[4]; py += hm[5]; }
1165
+ return [box.x + at.m[0] * px + at.m[4], box.y + at.m[3] * py + at.m[5], at.D];
1143
1166
  }
1144
1167
  // the camera keeps the elements it follows in the visible part of the screen: together when
1145
1168
  // they fit, else the first of them
@@ -1 +1 @@
1
- {"engine":"717ecc3e74f1"}
1
+ {"engine":"8bdd9745d005"}
package/engine/gl.js CHANGED
@@ -453,7 +453,7 @@ const MooodGL = (() => {
453
453
  const objects = (spec.objects || []).map(o0 => {
454
454
  if (o0.plane) return planeOf(o0, dz, tex);
455
455
  // looking around, the sky is the full circle, 2π stage heights wide (engine.js)
456
- const o = { ...o0, x: dz.objectX(o0), y: dz.objectY(o0), size: dz.objectSize(o0), _yh: dz.yh, _sw: ROUND && o0.sky ? I.TAU * H : SW }, Z = dz.objectZ(o0);
456
+ const n = dz.now(o0), o = { ...n.o, _sw: ROUND && o0.sky ? I.TAU * H : SW }, Z = n.Z;
457
457
  const x = o.x * SW, y = o.y * H, s = Math.max(2, o.size * H);
458
458
  if (o.kind === 'bird') {
459
459
  const canvas = paint(s * 1.2 * SS, s * 0.6 * SS, (g) => {
@@ -472,7 +472,7 @@ const MooodGL = (() => {
472
472
  const live = (o.kind === 'custom' || !o.kind) ? liveOf(o, o.frames, tex) : null;
473
473
  return {
474
474
  o, Z, x, y, s, bx, by, bw, bh, tex: frames[0], frames, period: o.period || 2, detail: null,
475
- p: ROUND && !o.sky ? dz.objectAt(o0) : null,
475
+ p: ROUND && !o.sky ? n.at : null,
476
476
  live, ground: !!o.ground, from: o0.place ? o0 : null,
477
477
  reflect: o.reflect ?? (o.kind === 'boat' || o.kind === 'bridge'),
478
478
  lights: lights.map(l => ({ x: l.x, y: l.y, r: l.r, c: col(l.col), a: l.a, dot: l.dot, fire: o.kind === 'campfire', flicker: l.flicker })),
@@ -873,22 +873,18 @@ const MooodGL = (() => {
873
873
  }
874
874
  }
875
875
 
876
- // An element that moves through the world (its `place`: {across, distance, elevation, x} — the
877
- // names of numbers in the state, metres): put where the state says now — its size, its depth,
878
- // its haze and what it is drawn in front of or behind follow.
876
+ // An element that moves through the world (its `place`): put where the state has it now
877
+ // (depths().now) — its size, its depth, its haze and what it is drawn in front of or behind follow.
879
878
  function placeMoving(S) {
880
- const v = I.STATE.values;
881
879
  let moved = false;
882
880
  for (const ob of S.objects) {
883
881
  if (!ob.from) continue;
884
- const o1 = { ...ob.from };
885
- for (const [f, k] of Object.entries(ob.from.place)) if (typeof k === 'string' && typeof v[k] === 'number') o1[f] = v[k];
886
- const key = `${o1.x}|${o1.across}|${o1.distance}|${o1.elevation}`;
882
+ const n = S.dz.now(ob.from), key = `${n.o.x}|${n.o.y}|${n.o.size}`;
887
883
  if (key === ob.placed) continue;
888
884
  ob.placed = key; moved = true;
889
- const o = { ...ob.o, x: S.dz.objectX(o1), y: S.dz.objectY(o1), size: S.dz.objectSize(o1) };
890
- Object.assign(ob, { o, Z: S.dz.objectZ(o1), x: o.x * SW, y: o.y * H, s: Math.max(2, o.size * H) }, I.objectBox(o, SW, H));
891
- if (ROUND && !o.sky) ob.p = S.dz.objectAt(o1);
885
+ const o = { ...n.o, _sw: ob.o._sw };
886
+ Object.assign(ob, { o, Z: n.Z, x: o.x * SW, y: o.y * H, s: Math.max(2, o.size * H) }, I.objectBox(o, SW, H));
887
+ if (ROUND && !o.sky) ob.p = n.at;
892
888
  }
893
889
  if (!moved) return;
894
890
  for (const item of S.order) if (item.ob) item.Z = item.ob.Z;
@@ -7,10 +7,11 @@
7
7
  // widgets — MooodState is global: get / set / on.
8
8
  // The painting code reads the current values as e.state and is repainted when they change. A
9
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.
10
+ // default — it starts and stops gently; linear, in, out, back, spring, anticipate…). A value set
11
+ // is heard once, where it is set to (on('change')); a value moving there is heard each frame as it
12
+ // moves (on('move')). What a value was a moment ago stays known for a while (was): what lags
13
+ // behind — a coat's tail, hair, a head that follows — reads it. A script may tell that something
14
+ // happened (scene.tell): the game decides what it means.
14
15
  const MooodState = (() => {
15
16
  let stage = null, tell = null, save = null, t0 = performance.now();
16
17
  let values = {}, cur = {}, tweens = new Map(), listeners = [], timers = [], raf = 0, pending = {}, flushTimer = 0;
@@ -44,12 +45,14 @@ const MooodState = (() => {
44
45
  }
45
46
 
46
47
  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
+ // the moving values, where they are now (the stage asks as it paints a frame): each heard moving
49
+ // from where it was last seen
48
50
  function sample() {
49
51
  if (!tweens.size) return;
50
- const now = performance.now();
52
+ const now = performance.now(), before = cur, moving = [...tweens.keys()];
51
53
  for (const [k, tw] of tweens) if (now >= tw.end) tweens.delete(k);
52
54
  publish();
55
+ for (const k of moving) if (cur[k] !== before[k]) emit('move', k, cur[k], before[k], k);
53
56
  }
54
57
  function tick() {
55
58
  raf = 0;
@@ -167,7 +170,9 @@ const MooodState = (() => {
167
170
  }
168
171
  return end;
169
172
  },
170
- // on('change', fn(value, old, key)) · on('change', key, fn) · on('load', fn(state))
173
+ // on('change', fn(value, old, key)) · on('change', key, fn) — a value set (with `over`: where it
174
+ // goes, once); on('move', [key,] fn(value, last, key)) — a value moving, each frame (set with
175
+ // `over`, path), till it stops; on('load', fn(state))
171
176
  on(ev, a, b) {
172
177
  const fn = typeof a === 'function' ? a : b, key = typeof a === 'function' ? null : String(a);
173
178
  listeners.push({ ev, key, fn, who });
package/engine/select.js CHANGED
@@ -51,12 +51,12 @@ const MooodSelect = (() => {
51
51
  // so a plane's view() maps painted coordinates straight to where they are in the frame;
52
52
  // the frame lies at (x, y) in the canvas.
53
53
  const size = () => { const f = stage.frame(); return { W: f.w, H: f.h, SW: I.STAGE * f.h, x: f.x, y: f.y }; };
54
- // objects stand where the engine puts them (it may move them to their real distance)
55
- const placed = (dz, o) => ({ ...o, x: dz.objectX(o), y: dz.objectY(o), size: dz.objectSize(o), _yh: dz.yh });
54
+ // objects stand where the engine puts them now (its depths().now: their real distance, their `place`)
55
+ const placed = (dz, o) => dz.now(o).o;
56
56
  // a platformer's hero is drawn moved and turned (engine.js heroMatrix): so is its outline
57
57
  function heroed(m, o, cam, SW, H) {
58
58
  if (!o || !cam.hero || o.id !== cam.hero.id) return m;
59
- const dz = I.depths(spec), h = I.heroMatrix(cam.hero, dz.objectX(o) * SW, dz.objectSize(o) * H / (o.height || 2));
59
+ const p = placed(I.depths(spec), o), h = I.heroMatrix(cam.hero, p.x * SW, p.size * H / (o.height || 2));
60
60
  return [m[0] * h[0], 0, 0, m[3], m[0] * h[4] + m[4], m[3] * h[5] + m[5]];
61
61
  }
62
62
  const layerSeed = i => I.hash(JSON.stringify(spec.layers || []) + (spec.title || '')) + i * 101;
@@ -125,7 +125,7 @@ const MooodSelect = (() => {
125
125
  const c = I.layer(b.bw * k, b.bh * k), g = c.getContext('2d');
126
126
  g.setTransform(k, 0, 0, k, -b.bx * k, -b.by * k);
127
127
  I.drawObject(g, o, SW, H, 0, []);
128
- return { c, x: b.bx, y: b.by, k, Z: dz.objectZ(it), obj: it };
128
+ return { c, x: b.bx, y: b.by, k, Z: dz.now(it).Z, obj: it };
129
129
  }
130
130
  if (sel.startsWith('layers') && LAND.has(it.kind)) {
131
131
  if (it.kind === 'water') {
@@ -239,7 +239,7 @@ const MooodSelect = (() => {
239
239
  if (!coverage.has(path)) {
240
240
  const { W, H, SW } = size(), dz = I.depths(spec), it = item(spec, path), n = 64, c = I.layer(n, n * H / W), g = c.getContext('2d');
241
241
  // from the start — looking around, turned toward it
242
- const round = I.camSetup(spec).round, p = round && !it.sky ? dz.objectAt(it) : null;
242
+ const round = I.camSetup(spec).round, p = round && !it.sky ? dz.now(it).at : null;
243
243
  const at = I.placeOf(it, { x: 0, y: 0, z: 0, h: 1, yaw: p ? Math.atan2(p.X, p.Z) : 0 }, W, H, dz, round && !it.sky), k = n / W;
244
244
  const m = at ? at.m : [0, 0, 0, 0, 0, 0];
245
245
  g.setTransform(m[0] * k, 0, 0, m[3] * k, m[4] * k, m[5] * k);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moood/render",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "A moood scene rendered locally — the site's own engine, in a browser already installed — at a moment, with a state: a PNG and the errors its code threw.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",