@almadar/ui 5.139.0 → 5.141.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.
@@ -8813,9 +8813,26 @@ var init_imageCache = __esm({
8813
8813
  });
8814
8814
 
8815
8815
  // lib/webPainter2d.ts
8816
+ function makeNoiseTile(alpha, color) {
8817
+ const tile = document.createElement("canvas");
8818
+ tile.width = NOISE_CELLS;
8819
+ tile.height = NOISE_CELLS;
8820
+ const t = tile.getContext("2d");
8821
+ if (t) {
8822
+ t.fillStyle = color;
8823
+ for (let y = 0; y < NOISE_CELLS; y++) {
8824
+ for (let x = 0; x < NOISE_CELLS; x++) {
8825
+ t.globalAlpha = noiseHash(x, y) * alpha;
8826
+ t.fillRect(x, y, 1, 1);
8827
+ }
8828
+ }
8829
+ }
8830
+ return tile;
8831
+ }
8816
8832
  function createWebPainter(ctx, onAssetLoad) {
8817
8833
  let vw = 0;
8818
8834
  let vh = 0;
8835
+ const patternCache = /* @__PURE__ */ new Map();
8819
8836
  const tracePoly = (points, closed) => {
8820
8837
  if (points.length === 0) return;
8821
8838
  ctx.beginPath();
@@ -8823,6 +8840,31 @@ function createWebPainter(ctx, onAssetLoad) {
8823
8840
  for (let i = 1; i < points.length; i++) ctx.lineTo(points[i].x, points[i].y);
8824
8841
  if (closed) ctx.closePath();
8825
8842
  };
8843
+ const toCanvasPattern = (style) => {
8844
+ const key = JSON.stringify(style);
8845
+ let pattern = patternCache.get(key);
8846
+ if (pattern === void 0) {
8847
+ if (style.kind === "noise") {
8848
+ pattern = ctx.createPattern(makeNoiseTile(style.alpha ?? 0.12, style.color ?? "#000000"), "repeat");
8849
+ } else {
8850
+ const img = getOrLoadImage(style.url, onAssetLoad);
8851
+ if (!img) return "rgba(0,0,0,0)";
8852
+ pattern = ctx.createPattern(img, "repeat");
8853
+ }
8854
+ if (pattern && style.scale !== void 0 && style.scale !== 1) {
8855
+ pattern.setTransform(new DOMMatrix().scale(style.scale));
8856
+ }
8857
+ patternCache.set(key, pattern);
8858
+ }
8859
+ return pattern ?? "rgba(0,0,0,0)";
8860
+ };
8861
+ const toCanvasStyle = (style) => {
8862
+ if (typeof style === "string") return style;
8863
+ if (style.kind === "noise" || style.kind === "image") return toCanvasPattern(style);
8864
+ const g = style.kind === "linear" ? ctx.createLinearGradient(style.x1, style.y1, style.x2, style.y2) : style.kind === "conic" ? ctx.createConicGradient(style.angle, style.cx, style.cy) : ctx.createRadialGradient(style.cx, style.cy, 0, style.cx, style.cy, style.r);
8865
+ for (const stop of style.stops) g.addColorStop(stop.offset, stop.color);
8866
+ return g;
8867
+ };
8826
8868
  return {
8827
8869
  setViewport(width, height, dpr) {
8828
8870
  vw = width;
@@ -8854,6 +8896,19 @@ function createWebPainter(ctx, onAssetLoad) {
8854
8896
  ctx.shadowColor = shadow ? shadow.color : "transparent";
8855
8897
  ctx.shadowBlur = shadow ? shadow.blur : 0;
8856
8898
  },
8899
+ setBlend(mode) {
8900
+ ctx.globalCompositeOperation = mode ?? "source-over";
8901
+ },
8902
+ setLineDash(pattern, offset = 0) {
8903
+ ctx.setLineDash(pattern ? [...pattern] : []);
8904
+ ctx.lineDashOffset = offset;
8905
+ },
8906
+ setBlur(px) {
8907
+ ctx.filter = px && px > 0 ? `blur(${px}px)` : "none";
8908
+ },
8909
+ clipPath(d) {
8910
+ ctx.clip(new Path2D(d));
8911
+ },
8857
8912
  resolveTexture(url) {
8858
8913
  const img = getOrLoadImage(url, onAssetLoad);
8859
8914
  if (!img) return null;
@@ -8876,47 +8931,47 @@ function createWebPainter(ctx, onAssetLoad) {
8876
8931
  ctx.drawImage(img, dest.x, dest.y, dw, dh);
8877
8932
  }
8878
8933
  },
8879
- fillRect(x, y, w, h, color) {
8880
- ctx.fillStyle = color;
8934
+ fillRect(x, y, w, h, style) {
8935
+ ctx.fillStyle = toCanvasStyle(style);
8881
8936
  ctx.fillRect(x, y, w, h);
8882
8937
  },
8883
- strokeRect(x, y, w, h, color, lineWidth = 1) {
8884
- ctx.strokeStyle = color;
8938
+ strokeRect(x, y, w, h, style, lineWidth = 1) {
8939
+ ctx.strokeStyle = toCanvasStyle(style);
8885
8940
  ctx.lineWidth = lineWidth;
8886
8941
  ctx.strokeRect(x, y, w, h);
8887
8942
  },
8888
- fillPoly(points, color) {
8943
+ fillPoly(points, style) {
8889
8944
  if (points.length === 0) return;
8890
8945
  tracePoly(points, true);
8891
- ctx.fillStyle = color;
8946
+ ctx.fillStyle = toCanvasStyle(style);
8892
8947
  ctx.fill();
8893
8948
  },
8894
- strokePoly(points, color, lineWidth = 1, closed = false) {
8949
+ strokePoly(points, style, lineWidth = 1, closed = false) {
8895
8950
  if (points.length === 0) return;
8896
8951
  tracePoly(points, closed);
8897
- ctx.strokeStyle = color;
8952
+ ctx.strokeStyle = toCanvasStyle(style);
8898
8953
  ctx.lineWidth = lineWidth;
8899
8954
  ctx.stroke();
8900
8955
  },
8901
- fillEllipse(cx, cy, rx, ry, color) {
8956
+ fillEllipse(cx, cy, rx, ry, style) {
8902
8957
  ctx.beginPath();
8903
8958
  ctx.ellipse(cx, cy, rx, ry, 0, 0, Math.PI * 2);
8904
- ctx.fillStyle = color;
8959
+ ctx.fillStyle = toCanvasStyle(style);
8905
8960
  ctx.fill();
8906
8961
  },
8907
- strokeEllipse(cx, cy, rx, ry, color, lineWidth = 1) {
8962
+ strokeEllipse(cx, cy, rx, ry, style, lineWidth = 1) {
8908
8963
  ctx.beginPath();
8909
8964
  ctx.ellipse(cx, cy, rx, ry, 0, 0, Math.PI * 2);
8910
- ctx.strokeStyle = color;
8965
+ ctx.strokeStyle = toCanvasStyle(style);
8911
8966
  ctx.lineWidth = lineWidth;
8912
8967
  ctx.stroke();
8913
8968
  },
8914
- fillPath(d, color) {
8915
- ctx.fillStyle = color;
8969
+ fillPath(d, style) {
8970
+ ctx.fillStyle = toCanvasStyle(style);
8916
8971
  ctx.fill(new Path2D(d));
8917
8972
  },
8918
- strokePath(d, color, lineWidth = 1) {
8919
- ctx.strokeStyle = color;
8973
+ strokePath(d, style, lineWidth = 1) {
8974
+ ctx.strokeStyle = toCanvasStyle(style);
8920
8975
  ctx.lineWidth = lineWidth;
8921
8976
  ctx.stroke(new Path2D(d));
8922
8977
  },
@@ -8929,13 +8984,18 @@ function createWebPainter(ctx, onAssetLoad) {
8929
8984
  }
8930
8985
  };
8931
8986
  }
8932
- var handleByImage, imageByHandle;
8987
+ var handleByImage, imageByHandle, noiseHash, NOISE_CELLS;
8933
8988
  var init_webPainter2d = __esm({
8934
8989
  "lib/webPainter2d.ts"() {
8935
8990
  "use client";
8936
8991
  init_imageCache();
8937
8992
  handleByImage = /* @__PURE__ */ new WeakMap();
8938
8993
  imageByHandle = /* @__PURE__ */ new WeakMap();
8994
+ noiseHash = (x, y) => {
8995
+ const s = Math.sin(x * 12.9898 + y * 78.233) * 43758.5453;
8996
+ return s - Math.floor(s);
8997
+ };
8998
+ NOISE_CELLS = 32;
8939
8999
  }
8940
9000
  });
8941
9001
 
@@ -9091,6 +9151,101 @@ var init_registry = __esm({
9091
9151
  DrawableRegistryContext = createContext(null);
9092
9152
  }
9093
9153
  });
9154
+ function isAnimatedShape(node) {
9155
+ return Boolean(node.animation && node.animation.durationMs > 0 && node.animation.keyframes.length > 0);
9156
+ }
9157
+ function applyShapeAnimation(node, timeMs) {
9158
+ const anim = node.animation;
9159
+ if (!anim || !(anim.durationMs > 0) || anim.keyframes.length === 0) return node;
9160
+ const frames = [...anim.keyframes].sort((a, b) => a.at - b.at);
9161
+ const cycle = timeMs / anim.durationMs;
9162
+ const t = anim.loop === false ? Math.min(cycle, 1) : cycle - Math.floor(cycle);
9163
+ const out = { ...node };
9164
+ const trackValue = (key) => {
9165
+ const defined = frames.filter((f3) => f3[key] !== void 0);
9166
+ if (defined.length === 0) return void 0;
9167
+ let prev;
9168
+ let next;
9169
+ for (const f3 of defined) {
9170
+ if (f3.at <= t) prev = f3;
9171
+ else if (!next) next = f3;
9172
+ }
9173
+ if (!prev) return defined[0][key];
9174
+ if (!next) return prev[key];
9175
+ const span = next.at - prev.at;
9176
+ const k = span > 0 ? (t - prev.at) / span : 1;
9177
+ const a = prev[key];
9178
+ const b = next[key];
9179
+ if (typeof a === "number" && typeof b === "number") return lerp(a, b, k);
9180
+ return a;
9181
+ };
9182
+ for (const key of NUMERIC_TRACKS) {
9183
+ const v = trackValue(key);
9184
+ if (v !== void 0) out[key] = v;
9185
+ }
9186
+ const fill = trackValue("fill");
9187
+ if (fill !== void 0) out.fill = fill;
9188
+ const stroke = trackValue("stroke");
9189
+ if (stroke !== void 0) out.stroke = stroke;
9190
+ const shadowFrames = frames.filter((f3) => f3.shadow !== void 0);
9191
+ if (shadowFrames.length > 0) {
9192
+ const sh = trackValue("shadow");
9193
+ if (sh !== void 0) {
9194
+ let prevSh;
9195
+ let nextSh;
9196
+ for (const f3 of shadowFrames) {
9197
+ if (f3.at <= t) prevSh = f3;
9198
+ else if (!nextSh) nextSh = f3;
9199
+ }
9200
+ if (prevSh?.shadow && nextSh?.shadow) {
9201
+ const span = nextSh.at - prevSh.at;
9202
+ const k = span > 0 ? (t - prevSh.at) / span : 1;
9203
+ out.shadow = { color: prevSh.shadow.color, blur: lerp(prevSh.shadow.blur, nextSh.shadow.blur, k) };
9204
+ } else {
9205
+ out.shadow = sh;
9206
+ }
9207
+ }
9208
+ }
9209
+ return out;
9210
+ }
9211
+ function gradientStyle(g, ox, oy, scale) {
9212
+ if (g.kind === "linear") {
9213
+ if (!g.from || !g.to) return void 0;
9214
+ return {
9215
+ kind: "linear",
9216
+ x1: ox + g.from.x * scale,
9217
+ y1: oy + g.from.y * scale,
9218
+ x2: ox + g.to.x * scale,
9219
+ y2: oy + g.to.y * scale,
9220
+ stops: g.stops
9221
+ };
9222
+ }
9223
+ if (g.kind === "conic") {
9224
+ if (!g.center) return void 0;
9225
+ return {
9226
+ kind: "conic",
9227
+ cx: ox + g.center.x * scale,
9228
+ cy: oy + g.center.y * scale,
9229
+ angle: g.angle ?? 0,
9230
+ stops: g.stops
9231
+ };
9232
+ }
9233
+ if (!g.center || g.radius === void 0) return void 0;
9234
+ return {
9235
+ kind: "radial",
9236
+ cx: ox + g.center.x * scale,
9237
+ cy: oy + g.center.y * scale,
9238
+ r: g.radius * scale,
9239
+ stops: g.stops
9240
+ };
9241
+ }
9242
+ function patternStyle(p, unit) {
9243
+ if (p.kind === "image") {
9244
+ if (!p.url) return void 0;
9245
+ return { kind: "image", url: p.url, scale: (p.scale ?? 1) / unit };
9246
+ }
9247
+ return { kind: "noise", scale: (p.scale ?? 2) / unit, alpha: p.alpha, color: p.color };
9248
+ }
9094
9249
  function DrawShape(props) {
9095
9250
  const register = useContext(DrawableRegistryContext);
9096
9251
  if (register) {
@@ -9101,25 +9256,66 @@ function DrawShape(props) {
9101
9256
  ...opacity !== void 0 && opacity > 0 ? { opacity } : {}
9102
9257
  };
9103
9258
  register(node);
9104
- return /* @__PURE__ */ jsx("div", { "data-draw-shape-debug": "", style: { position: "absolute", width: 4, height: 4, background: "red", zIndex: 9999 } });
9105
9259
  }
9106
9260
  return null;
9107
9261
  }
9108
- var paintShape;
9262
+ var NUMERIC_TRACKS, lerp, paintShape;
9109
9263
  var init_DrawShape = __esm({
9110
9264
  "components/game/atoms/DrawShape.tsx"() {
9111
9265
  "use client";
9112
9266
  init_contract();
9113
9267
  init_registry();
9114
- paintShape = (painter, node, dctx) => {
9268
+ NUMERIC_TRACKS = [
9269
+ "offsetX",
9270
+ "offsetY",
9271
+ "rotate",
9272
+ "opacity",
9273
+ "radiusX",
9274
+ "radiusY",
9275
+ "width",
9276
+ "height",
9277
+ "strokeWidth",
9278
+ "strokeDashOffset",
9279
+ "blur"
9280
+ ];
9281
+ lerp = (a, b, k) => a + (b - a) * k;
9282
+ paintShape = (painter, rawNode, dctx) => {
9283
+ const node = dctx.time > 0 ? applyShapeAnimation(rawNode, dctx.time) : rawNode;
9115
9284
  if (!isValidScenePos(node.position)) return;
9116
9285
  painter.save();
9117
9286
  if (node.opacity !== void 0 && node.opacity !== 1) painter.setAlpha(node.opacity);
9287
+ const origin = dctx.projector.project(node.position);
9288
+ const tileWidth = dctx.projector.tileWidth;
9289
+ const groupScale = dctx.groupScale ?? 1;
9290
+ const strokePx = (node.strokeWidth ?? 1) / groupScale;
9291
+ if (node.blendMode) painter.setBlend(node.blendMode);
9292
+ if (node.strokeDash && node.strokeDash.length > 0) {
9293
+ painter.setLineDash(
9294
+ node.strokeDash.map((v) => v / groupScale),
9295
+ (node.strokeDashOffset ?? 0) / groupScale
9296
+ );
9297
+ }
9298
+ if (node.blur !== void 0 && node.blur > 0) painter.setBlur(node.blur / groupScale);
9299
+ if (node.rotate) {
9300
+ const pivot = node.pivot ?? { x: 0.5, y: 0.5 };
9301
+ const px = origin.x + pivot.x * tileWidth;
9302
+ const py = origin.y + pivot.y * tileWidth;
9303
+ painter.translate(px, py);
9304
+ painter.rotate(node.rotate);
9305
+ painter.translate(-px, -py);
9306
+ }
9307
+ if (node.shadow) painter.setShadow({ color: node.shadow.color, blur: node.shadow.blur * tileWidth * groupScale });
9308
+ const fill = node.fill === "none" ? void 0 : node.fill;
9309
+ const stroke = node.stroke === "none" ? void 0 : node.stroke;
9310
+ const pxFill = node.gradient ? gradientStyle(node.gradient, origin.x, origin.y, tileWidth) ?? fill : fill;
9311
+ const pxStroke = node.strokeGradient ? gradientStyle(node.strokeGradient, origin.x, origin.y, tileWidth) ?? stroke : stroke;
9312
+ const patFill = node.fillPattern ? patternStyle(node.fillPattern, groupScale) : void 0;
9118
9313
  switch (node.shape) {
9119
9314
  case "cell": {
9120
9315
  const pts = dctx.projector.cellPath(node.position);
9121
- if (node.fill) painter.fillPoly(pts, node.fill);
9122
- if (node.stroke) painter.strokePoly(pts, node.stroke, node.strokeWidth ?? 1, true);
9316
+ if (pxFill) painter.fillPoly(pts, pxFill);
9317
+ if (patFill) painter.fillPoly(pts, patFill);
9318
+ if (pxStroke) painter.strokePoly(pts, pxStroke, strokePx, true);
9123
9319
  break;
9124
9320
  }
9125
9321
  case "rect": {
@@ -9129,8 +9325,9 @@ var init_DrawShape = __esm({
9129
9325
  const y = p.y + (node.offsetY ?? 0) * tw;
9130
9326
  const w = (node.width ?? 0) * tw;
9131
9327
  const h = (node.height ?? 0) * tw;
9132
- if (node.fill) painter.fillRect(x, y, w, h, node.fill);
9133
- if (node.stroke) painter.strokeRect(x, y, w, h, node.stroke, node.strokeWidth ?? 1);
9328
+ if (pxFill) painter.fillRect(x, y, w, h, pxFill);
9329
+ if (patFill) painter.fillRect(x, y, w, h, patFill);
9330
+ if (pxStroke) painter.strokeRect(x, y, w, h, pxStroke, strokePx);
9134
9331
  break;
9135
9332
  }
9136
9333
  case "ellipse": {
@@ -9140,26 +9337,38 @@ var init_DrawShape = __esm({
9140
9337
  const cy = p.y + (node.offsetY ?? 0) * tw;
9141
9338
  const rx = (node.radiusX ?? 0) * tw;
9142
9339
  const ry = (node.radiusY ?? rx) * tw;
9143
- if (node.fill) painter.fillEllipse(cx, cy, rx, ry, node.fill);
9144
- if (node.stroke) painter.strokeEllipse(cx, cy, rx, ry, node.stroke, node.strokeWidth ?? 1);
9340
+ if (pxFill) painter.fillEllipse(cx, cy, rx, ry, pxFill);
9341
+ if (patFill) painter.fillEllipse(cx, cy, rx, ry, patFill);
9342
+ if (pxStroke) painter.strokeEllipse(cx, cy, rx, ry, pxStroke, strokePx);
9145
9343
  break;
9146
9344
  }
9147
9345
  case "poly": {
9148
- const base = dctx.projector.project(node.position);
9149
- const tw = dctx.projector.tileWidth;
9150
- const pts = (node.points ?? []).map((pt) => ({ x: base.x + pt.x * tw, y: base.y + pt.y * tw }));
9151
- if (node.fill) painter.fillPoly(pts, node.fill);
9152
- if (node.stroke) painter.strokePoly(pts, node.stroke, node.strokeWidth ?? 1, true);
9346
+ const pts = (node.points ?? []).map((pt) => ({
9347
+ x: origin.x + ((node.offsetX ?? 0) + pt.x) * tileWidth,
9348
+ y: origin.y + ((node.offsetY ?? 0) + pt.y) * tileWidth
9349
+ }));
9350
+ if (pxFill) painter.fillPoly(pts, pxFill);
9351
+ if (patFill) painter.fillPoly(pts, patFill);
9352
+ if (pxStroke) painter.strokePoly(pts, pxStroke, strokePx, true);
9153
9353
  break;
9154
9354
  }
9155
9355
  case "path": {
9156
9356
  if (!node.d) break;
9157
- const base = dctx.projector.project(node.position);
9158
- const tw = dctx.projector.tileWidth;
9159
- painter.translate(base.x, base.y);
9160
- painter.scale(tw, tw);
9161
- if (node.fill) painter.fillPath(node.d, node.fill);
9162
- if (node.stroke) painter.strokePath(node.d, node.stroke, (node.strokeWidth ?? 1) / tw);
9357
+ painter.translate(origin.x + (node.offsetX ?? 0) * tileWidth, origin.y + (node.offsetY ?? 0) * tileWidth);
9358
+ painter.scale(tileWidth, tileWidth);
9359
+ if (node.strokeDash && node.strokeDash.length > 0) {
9360
+ painter.setLineDash(
9361
+ node.strokeDash.map((v) => v / (groupScale * tileWidth)),
9362
+ (node.strokeDashOffset ?? 0) / (groupScale * tileWidth)
9363
+ );
9364
+ }
9365
+ if (node.blur !== void 0 && node.blur > 0) painter.setBlur(node.blur / (groupScale * tileWidth));
9366
+ const localFill = node.gradient ? gradientStyle(node.gradient, 0, 0, 1) ?? fill : fill;
9367
+ const localStroke = node.strokeGradient ? gradientStyle(node.strokeGradient, 0, 0, 1) ?? stroke : stroke;
9368
+ const localPat = node.fillPattern ? patternStyle(node.fillPattern, groupScale * tileWidth) : void 0;
9369
+ if (localFill) painter.fillPath(node.d, localFill);
9370
+ if (localPat) painter.fillPath(node.d, localPat);
9371
+ if (localStroke) painter.strokePath(node.d, localStroke, strokePx / tileWidth);
9163
9372
  break;
9164
9373
  }
9165
9374
  }
@@ -9240,8 +9449,6 @@ var init_DrawTextLayer = __esm({
9240
9449
  };
9241
9450
  }
9242
9451
  });
9243
-
9244
- // lib/drawable/paintDispatch.ts
9245
9452
  function paintDrawable(painter, node, dctx) {
9246
9453
  switch (node.type) {
9247
9454
  case "draw-sprite":
@@ -9262,10 +9469,20 @@ function paintDrawable(painter, node, dctx) {
9262
9469
  if (node.scale !== void 0) painter.scale(node.scale, node.scale);
9263
9470
  if (node.rotate !== void 0) painter.rotate(node.rotate);
9264
9471
  if (node.opacity !== void 0 && node.opacity !== 1) painter.setAlpha(node.opacity);
9265
- for (const item of node.items) paintDrawable(painter, item, dctx);
9472
+ if (node.clip) {
9473
+ const tw = dctx.projector.tileWidth;
9474
+ painter.scale(tw, tw);
9475
+ painter.clipPath(node.clip);
9476
+ painter.scale(1 / tw, 1 / tw);
9477
+ }
9478
+ const childCtx = node.scale !== void 0 && node.scale !== 1 ? { ...dctx, groupScale: (dctx.groupScale ?? 1) * node.scale } : dctx;
9479
+ for (const item of node.items) paintDrawable(painter, item, childCtx);
9266
9480
  painter.restore();
9267
9481
  break;
9268
9482
  }
9483
+ case "draw-mesh":
9484
+ warnUnsupported2d("draw-mesh");
9485
+ break;
9269
9486
  case "draw-sprite-layer":
9270
9487
  paintSpriteLayer(painter, node, dctx);
9271
9488
  break;
@@ -9277,6 +9494,7 @@ function paintDrawable(painter, node, dctx) {
9277
9494
  break;
9278
9495
  }
9279
9496
  }
9497
+ var paint2dLog, warnedUnsupported2d, warnUnsupported2d;
9280
9498
  var init_paintDispatch = __esm({
9281
9499
  "lib/drawable/paintDispatch.ts"() {
9282
9500
  init_contract();
@@ -9286,6 +9504,13 @@ var init_paintDispatch = __esm({
9286
9504
  init_DrawSpriteLayer();
9287
9505
  init_DrawShapeLayer();
9288
9506
  init_DrawTextLayer();
9507
+ paint2dLog = createLogger("almadar:ui:drawable-2d");
9508
+ warnedUnsupported2d = /* @__PURE__ */ new Set();
9509
+ warnUnsupported2d = (kind) => {
9510
+ if (warnedUnsupported2d.has(kind)) return;
9511
+ warnedUnsupported2d.add(kind);
9512
+ paint2dLog.warn("unsupported drawable kind on the 2D painter \u2014 skipped", { kind });
9513
+ };
9289
9514
  }
9290
9515
  });
9291
9516
 
@@ -9300,6 +9525,7 @@ function collectDrawnItems(nodes) {
9300
9525
  case "draw-shape":
9301
9526
  case "draw-text":
9302
9527
  case "draw-group":
9528
+ case "draw-mesh":
9303
9529
  if (isValidScenePos(n.position)) out.push({ pos: n.position, id: n.id });
9304
9530
  break;
9305
9531
  case "draw-sprite-layer":
@@ -9371,7 +9597,6 @@ function Canvas2D({
9371
9597
  childDrawablesRef.current.push(node);
9372
9598
  }, []);
9373
9599
  const hasJsxChildren = React86.Children.count(children) > 0;
9374
- drawables && drawables.length > 0 ? drawables : childDrawablesRef.current;
9375
9600
  function isDrawableLayer(node) {
9376
9601
  return node.type === "draw-sprite-layer" || node.type === "draw-shape-layer" || node.type === "draw-text-layer";
9377
9602
  }
@@ -9520,11 +9745,24 @@ function Canvas2D({
9520
9745
  }, [showMinimap, scenePositions]);
9521
9746
  const miniMapWidth = gridExtent.width || 10;
9522
9747
  const miniMapHeight = gridExtent.height || 10;
9523
- const draw = useCallback(() => {
9748
+ const drawableIsAnimated = (node) => {
9749
+ if (node.type === "draw-shape") return isAnimatedShape(node);
9750
+ if (node.type === "draw-group") return Array.isArray(node.items) && node.items.some(drawableIsAnimated);
9751
+ if (node.type === "draw-shape-layer") return Array.isArray(node.items) && node.items.some(isAnimatedShape);
9752
+ return false;
9753
+ };
9754
+ const animRafRef = useRef(0);
9755
+ const drawTimeRef = useRef(() => void 0);
9756
+ const draw = useCallback((timeMs = 0) => {
9524
9757
  const canvas = canvasRef.current;
9525
9758
  if (!canvas) return;
9526
9759
  const ctx = canvas.getContext("2d");
9527
9760
  if (!ctx) return;
9761
+ const scheduleAnimation = (nodes) => {
9762
+ if (!nodes.some(drawableIsAnimated)) return;
9763
+ cancelAnimationFrame(animRafRef.current);
9764
+ animRafRef.current = requestAnimationFrame(() => drawTimeRef.current(performance.now()));
9765
+ };
9528
9766
  const dpr = window.devicePixelRatio || 1;
9529
9767
  canvas.width = viewportSize.width * dpr;
9530
9768
  canvas.height = viewportSize.height * dpr;
@@ -9557,14 +9795,24 @@ function Canvas2D({
9557
9795
  if (!drawables || drawables.length === 0) {
9558
9796
  const childDrawables = childDrawablesRef.current;
9559
9797
  if (childDrawables.length === 0) return;
9798
+ const cam0 = cameraRef.current;
9799
+ if (camera !== "follow" && dragDistance() === 0) {
9800
+ const focus = cameraPos ?? defaultGridFocus;
9801
+ if (focus) {
9802
+ const p = projector.anchorPoint(focus, "center");
9803
+ cam0.x = p.x - viewportSize.width / 2;
9804
+ cam0.y = p.y - viewportSize.height / 2;
9805
+ }
9806
+ }
9560
9807
  const painter0 = createWebPainter(ctx, bumpAtlas);
9561
9808
  painter0.save();
9562
9809
  painter0.translate(viewportSize.width / 2, viewportSize.height / 2);
9563
- painter0.scale(cameraRef.current.zoom, cameraRef.current.zoom);
9564
- painter0.translate(-viewportSize.width / 2, -viewportSize.height / 2);
9565
- const dctx0 = { projector, time: 0, invalidate: bumpAtlas };
9810
+ painter0.scale(cam0.zoom, cam0.zoom);
9811
+ painter0.translate(-viewportSize.width / 2 - cam0.x, -viewportSize.height / 2 - cam0.y);
9812
+ const dctx0 = { projector, time: timeMs, invalidate: bumpAtlas };
9566
9813
  for (const node of childDrawables) paintDrawable(painter0, node, dctx0);
9567
9814
  painter0.restore();
9815
+ scheduleAnimation(childDrawables);
9568
9816
  return;
9569
9817
  }
9570
9818
  const cam = cameraRef.current;
@@ -9584,10 +9832,15 @@ function Canvas2D({
9584
9832
  painter.translate(viewportSize.width / 2, viewportSize.height / 2);
9585
9833
  painter.scale(cam.zoom, cam.zoom);
9586
9834
  painter.translate(-viewportSize.width / 2 - cam.x, -viewportSize.height / 2 - cam.y);
9587
- const dctx = { projector, time: 0, invalidate: bumpAtlas };
9835
+ const dctx = { projector, time: timeMs, invalidate: bumpAtlas };
9588
9836
  for (const node of drawables) paintDrawable(painter, node, dctx);
9589
9837
  painter.restore();
9838
+ scheduleAnimation(drawables);
9590
9839
  }, [viewportSize, backgroundImage, bgColor, drawables, projector, cameraRef, bumpAtlas, getImage, cameraPos, defaultGridFocus, camera, dragDistance]);
9840
+ useEffect(() => {
9841
+ drawTimeRef.current = draw;
9842
+ }, [draw]);
9843
+ useEffect(() => () => cancelAnimationFrame(animRafRef.current), []);
9591
9844
  useEffect(() => {
9592
9845
  if (camera !== "follow" || !followTarget) return;
9593
9846
  const p = projector.anchorPoint(followTarget, "center");
@@ -9810,15 +10063,7 @@ function Canvas2D({
9810
10063
  mapHeight: miniMapHeight
9811
10064
  }
9812
10065
  ) }),
9813
- hasJsxChildren && /* @__PURE__ */ jsx("div", { "aria-hidden": "true", style: { position: "absolute", width: 0, height: 0, overflow: "hidden" }, children }),
9814
- /* @__PURE__ */ jsxs("div", { "data-debug": "", style: { position: "absolute", top: 0, left: 0, background: "yellow", color: "black", zIndex: 9999, fontSize: 24, padding: 8 }, children: [
9815
- "C=",
9816
- React86.Children.count(children),
9817
- " J=",
9818
- String(hasJsxChildren),
9819
- " D=",
9820
- drawables?.length ?? -1
9821
- ] })
10066
+ hasJsxChildren && /* @__PURE__ */ jsx("div", { "aria-hidden": "true", style: { position: "absolute", width: 0, height: 0, overflow: "hidden" }, children })
9822
10067
  ]
9823
10068
  }
9824
10069
  ) });
@@ -9843,6 +10088,7 @@ var init_Canvas2D = __esm({
9843
10088
  init_webPainter2d();
9844
10089
  init_projector();
9845
10090
  init_paintDispatch();
10091
+ init_DrawShape();
9846
10092
  init_registry();
9847
10093
  init_hitTest();
9848
10094
  init_isometric();
@@ -9897,6 +10143,7 @@ function Canvas({
9897
10143
  isLoading,
9898
10144
  cameraMode: to3DCameraMode(camera?.mode),
9899
10145
  ...zoom !== void 0 ? { scale: zoom } : {},
10146
+ ...camera?.fov !== void 0 ? { fov: camera.fov } : {},
9900
10147
  ...camera?.target !== void 0 ? { followTarget: camera.target } : {},
9901
10148
  unitScale,
9902
10149
  backgroundColor,
@@ -38724,14 +38971,26 @@ var init_DetailPanel = __esm({
38724
38971
  DetailPanel.displayName = "DetailPanel";
38725
38972
  }
38726
38973
  });
38727
-
38728
- // components/game/atoms/DrawGroup.tsx
38729
- function DrawGroup(_props) {
38974
+ function DrawGroup(props) {
38975
+ const register = useContext(DrawableRegistryContext);
38976
+ if (register) register({ ...props, type: "draw-group" });
38730
38977
  return null;
38731
38978
  }
38732
38979
  var init_DrawGroup = __esm({
38733
38980
  "components/game/atoms/DrawGroup.tsx"() {
38734
38981
  "use client";
38982
+ init_registry();
38983
+ }
38984
+ });
38985
+ function DrawMesh(props) {
38986
+ const register = useContext(DrawableRegistryContext);
38987
+ if (register) register({ ...props, type: "draw-mesh" });
38988
+ return null;
38989
+ }
38990
+ var init_DrawMesh = __esm({
38991
+ "components/game/atoms/DrawMesh.tsx"() {
38992
+ "use client";
38993
+ init_registry();
38735
38994
  }
38736
38995
  });
38737
38996
  function extractTitle(children) {
@@ -44438,6 +44697,7 @@ var init_component_registry_generated = __esm({
44438
44697
  init_DocTOC();
44439
44698
  init_DocumentViewer();
44440
44699
  init_DrawGroup();
44700
+ init_DrawMesh();
44441
44701
  init_DrawShape();
44442
44702
  init_DrawShapeLayer();
44443
44703
  init_DrawSprite();
@@ -44706,6 +44966,7 @@ var init_component_registry_generated = __esm({
44706
44966
  "DocTOC": DocTOC,
44707
44967
  "DocumentViewer": DocumentViewer,
44708
44968
  "DrawGroup": DrawGroup,
44969
+ "DrawMesh": DrawMesh,
44709
44970
  "DrawShape": DrawShape,
44710
44971
  "DrawShapeLayer": DrawShapeLayer,
44711
44972
  "DrawSprite": DrawSprite,
@@ -45614,8 +45875,13 @@ function SlotContentRenderer({
45614
45875
  const isSingleChild = typeof childrenConfig === "string" || typeof childrenConfig === "object" && childrenConfig !== null && !Array.isArray(childrenConfig) && "type" in childrenConfig;
45615
45876
  const hasChildren = PATTERNS_WITH_CHILDREN.has(content.pattern) || Array.isArray(childrenConfig) && childrenConfig.length > 0 || isSingleChild;
45616
45877
  const isDrawHost = isDrawHostPattern(content.pattern);
45878
+ const arr = Array.isArray(childrenConfig) ? childrenConfig : childrenConfig ? [childrenConfig] : [];
45879
+ const hasTraitChildren = arr.some(
45880
+ (c) => typeof c === "string" && TRAIT_BINDING_RE.test(c)
45881
+ );
45882
+ const drawHostUsesReactChildren = isDrawHost && hasTraitChildren;
45617
45883
  const myPath = patternPath ?? "root";
45618
- const renderedChildren = hasChildren && !isDrawHost ? renderPatternChildren(childrenConfig, onDismiss, content.id, myPath, content.sourceTrait, {
45884
+ const renderedChildren = hasChildren && (!isDrawHost || drawHostUsesReactChildren) ? renderPatternChildren(childrenConfig, onDismiss, content.id, myPath, content.sourceTrait, {
45619
45885
  slot: content.slot,
45620
45886
  transitionEvent: content.transitionEvent,
45621
45887
  fromState: content.fromState,
@@ -45676,7 +45942,7 @@ function SlotContentRenderer({
45676
45942
  for (const [k, v] of Object.entries(nodeSlotOverrides)) {
45677
45943
  finalProps[k] = v;
45678
45944
  }
45679
- if (isDrawHost && Array.isArray(childrenConfig) && childrenConfig.length > 0) {
45945
+ if (isDrawHost && !drawHostUsesReactChildren && Array.isArray(childrenConfig) && childrenConfig.length > 0) {
45680
45946
  finalProps.drawables = toDrawableNodes(childrenConfig);
45681
45947
  }
45682
45948
  const entityVal = finalProps.entity;
@@ -45855,6 +46121,8 @@ var init_UISlotRenderer = __esm({
45855
46121
  "vstack",
45856
46122
  "hstack",
45857
46123
  "box",
46124
+ "canvas",
46125
+ "canvas-2d",
45858
46126
  "grid",
45859
46127
  "center",
45860
46128
  "card",