@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.
@@ -15697,9 +15697,26 @@ var init_imageCache = __esm({
15697
15697
  });
15698
15698
 
15699
15699
  // lib/webPainter2d.ts
15700
+ function makeNoiseTile(alpha, color) {
15701
+ const tile = document.createElement("canvas");
15702
+ tile.width = NOISE_CELLS;
15703
+ tile.height = NOISE_CELLS;
15704
+ const t = tile.getContext("2d");
15705
+ if (t) {
15706
+ t.fillStyle = color;
15707
+ for (let y = 0; y < NOISE_CELLS; y++) {
15708
+ for (let x = 0; x < NOISE_CELLS; x++) {
15709
+ t.globalAlpha = noiseHash(x, y) * alpha;
15710
+ t.fillRect(x, y, 1, 1);
15711
+ }
15712
+ }
15713
+ }
15714
+ return tile;
15715
+ }
15700
15716
  function createWebPainter(ctx, onAssetLoad) {
15701
15717
  let vw = 0;
15702
15718
  let vh = 0;
15719
+ const patternCache = /* @__PURE__ */ new Map();
15703
15720
  const tracePoly = (points, closed) => {
15704
15721
  if (points.length === 0) return;
15705
15722
  ctx.beginPath();
@@ -15707,6 +15724,31 @@ function createWebPainter(ctx, onAssetLoad) {
15707
15724
  for (let i = 1; i < points.length; i++) ctx.lineTo(points[i].x, points[i].y);
15708
15725
  if (closed) ctx.closePath();
15709
15726
  };
15727
+ const toCanvasPattern = (style) => {
15728
+ const key = JSON.stringify(style);
15729
+ let pattern = patternCache.get(key);
15730
+ if (pattern === void 0) {
15731
+ if (style.kind === "noise") {
15732
+ pattern = ctx.createPattern(makeNoiseTile(style.alpha ?? 0.12, style.color ?? "#000000"), "repeat");
15733
+ } else {
15734
+ const img = getOrLoadImage(style.url, onAssetLoad);
15735
+ if (!img) return "rgba(0,0,0,0)";
15736
+ pattern = ctx.createPattern(img, "repeat");
15737
+ }
15738
+ if (pattern && style.scale !== void 0 && style.scale !== 1) {
15739
+ pattern.setTransform(new DOMMatrix().scale(style.scale));
15740
+ }
15741
+ patternCache.set(key, pattern);
15742
+ }
15743
+ return pattern ?? "rgba(0,0,0,0)";
15744
+ };
15745
+ const toCanvasStyle = (style) => {
15746
+ if (typeof style === "string") return style;
15747
+ if (style.kind === "noise" || style.kind === "image") return toCanvasPattern(style);
15748
+ 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);
15749
+ for (const stop of style.stops) g.addColorStop(stop.offset, stop.color);
15750
+ return g;
15751
+ };
15710
15752
  return {
15711
15753
  setViewport(width, height, dpr) {
15712
15754
  vw = width;
@@ -15738,6 +15780,19 @@ function createWebPainter(ctx, onAssetLoad) {
15738
15780
  ctx.shadowColor = shadow ? shadow.color : "transparent";
15739
15781
  ctx.shadowBlur = shadow ? shadow.blur : 0;
15740
15782
  },
15783
+ setBlend(mode) {
15784
+ ctx.globalCompositeOperation = mode ?? "source-over";
15785
+ },
15786
+ setLineDash(pattern, offset = 0) {
15787
+ ctx.setLineDash(pattern ? [...pattern] : []);
15788
+ ctx.lineDashOffset = offset;
15789
+ },
15790
+ setBlur(px) {
15791
+ ctx.filter = px && px > 0 ? `blur(${px}px)` : "none";
15792
+ },
15793
+ clipPath(d) {
15794
+ ctx.clip(new Path2D(d));
15795
+ },
15741
15796
  resolveTexture(url) {
15742
15797
  const img = getOrLoadImage(url, onAssetLoad);
15743
15798
  if (!img) return null;
@@ -15760,47 +15815,47 @@ function createWebPainter(ctx, onAssetLoad) {
15760
15815
  ctx.drawImage(img, dest.x, dest.y, dw, dh);
15761
15816
  }
15762
15817
  },
15763
- fillRect(x, y, w, h, color) {
15764
- ctx.fillStyle = color;
15818
+ fillRect(x, y, w, h, style) {
15819
+ ctx.fillStyle = toCanvasStyle(style);
15765
15820
  ctx.fillRect(x, y, w, h);
15766
15821
  },
15767
- strokeRect(x, y, w, h, color, lineWidth = 1) {
15768
- ctx.strokeStyle = color;
15822
+ strokeRect(x, y, w, h, style, lineWidth = 1) {
15823
+ ctx.strokeStyle = toCanvasStyle(style);
15769
15824
  ctx.lineWidth = lineWidth;
15770
15825
  ctx.strokeRect(x, y, w, h);
15771
15826
  },
15772
- fillPoly(points, color) {
15827
+ fillPoly(points, style) {
15773
15828
  if (points.length === 0) return;
15774
15829
  tracePoly(points, true);
15775
- ctx.fillStyle = color;
15830
+ ctx.fillStyle = toCanvasStyle(style);
15776
15831
  ctx.fill();
15777
15832
  },
15778
- strokePoly(points, color, lineWidth = 1, closed = false) {
15833
+ strokePoly(points, style, lineWidth = 1, closed = false) {
15779
15834
  if (points.length === 0) return;
15780
15835
  tracePoly(points, closed);
15781
- ctx.strokeStyle = color;
15836
+ ctx.strokeStyle = toCanvasStyle(style);
15782
15837
  ctx.lineWidth = lineWidth;
15783
15838
  ctx.stroke();
15784
15839
  },
15785
- fillEllipse(cx, cy, rx, ry, color) {
15840
+ fillEllipse(cx, cy, rx, ry, style) {
15786
15841
  ctx.beginPath();
15787
15842
  ctx.ellipse(cx, cy, rx, ry, 0, 0, Math.PI * 2);
15788
- ctx.fillStyle = color;
15843
+ ctx.fillStyle = toCanvasStyle(style);
15789
15844
  ctx.fill();
15790
15845
  },
15791
- strokeEllipse(cx, cy, rx, ry, color, lineWidth = 1) {
15846
+ strokeEllipse(cx, cy, rx, ry, style, lineWidth = 1) {
15792
15847
  ctx.beginPath();
15793
15848
  ctx.ellipse(cx, cy, rx, ry, 0, 0, Math.PI * 2);
15794
- ctx.strokeStyle = color;
15849
+ ctx.strokeStyle = toCanvasStyle(style);
15795
15850
  ctx.lineWidth = lineWidth;
15796
15851
  ctx.stroke();
15797
15852
  },
15798
- fillPath(d, color) {
15799
- ctx.fillStyle = color;
15853
+ fillPath(d, style) {
15854
+ ctx.fillStyle = toCanvasStyle(style);
15800
15855
  ctx.fill(new Path2D(d));
15801
15856
  },
15802
- strokePath(d, color, lineWidth = 1) {
15803
- ctx.strokeStyle = color;
15857
+ strokePath(d, style, lineWidth = 1) {
15858
+ ctx.strokeStyle = toCanvasStyle(style);
15804
15859
  ctx.lineWidth = lineWidth;
15805
15860
  ctx.stroke(new Path2D(d));
15806
15861
  },
@@ -15813,13 +15868,18 @@ function createWebPainter(ctx, onAssetLoad) {
15813
15868
  }
15814
15869
  };
15815
15870
  }
15816
- var handleByImage, imageByHandle;
15871
+ var handleByImage, imageByHandle, noiseHash, NOISE_CELLS;
15817
15872
  var init_webPainter2d = __esm({
15818
15873
  "lib/webPainter2d.ts"() {
15819
15874
  "use client";
15820
15875
  init_imageCache();
15821
15876
  handleByImage = /* @__PURE__ */ new WeakMap();
15822
15877
  imageByHandle = /* @__PURE__ */ new WeakMap();
15878
+ noiseHash = (x, y) => {
15879
+ const s = Math.sin(x * 12.9898 + y * 78.233) * 43758.5453;
15880
+ return s - Math.floor(s);
15881
+ };
15882
+ NOISE_CELLS = 32;
15823
15883
  }
15824
15884
  });
15825
15885
 
@@ -16037,6 +16097,101 @@ var init_registry = __esm({
16037
16097
  DrawableRegistryContext = React76.createContext(null);
16038
16098
  }
16039
16099
  });
16100
+ function isAnimatedShape(node) {
16101
+ return Boolean(node.animation && node.animation.durationMs > 0 && node.animation.keyframes.length > 0);
16102
+ }
16103
+ function applyShapeAnimation(node, timeMs) {
16104
+ const anim = node.animation;
16105
+ if (!anim || !(anim.durationMs > 0) || anim.keyframes.length === 0) return node;
16106
+ const frames = [...anim.keyframes].sort((a, b) => a.at - b.at);
16107
+ const cycle = timeMs / anim.durationMs;
16108
+ const t = anim.loop === false ? Math.min(cycle, 1) : cycle - Math.floor(cycle);
16109
+ const out = { ...node };
16110
+ const trackValue = (key) => {
16111
+ const defined = frames.filter((f3) => f3[key] !== void 0);
16112
+ if (defined.length === 0) return void 0;
16113
+ let prev;
16114
+ let next;
16115
+ for (const f3 of defined) {
16116
+ if (f3.at <= t) prev = f3;
16117
+ else if (!next) next = f3;
16118
+ }
16119
+ if (!prev) return defined[0][key];
16120
+ if (!next) return prev[key];
16121
+ const span = next.at - prev.at;
16122
+ const k = span > 0 ? (t - prev.at) / span : 1;
16123
+ const a = prev[key];
16124
+ const b = next[key];
16125
+ if (typeof a === "number" && typeof b === "number") return lerp(a, b, k);
16126
+ return a;
16127
+ };
16128
+ for (const key of NUMERIC_TRACKS) {
16129
+ const v = trackValue(key);
16130
+ if (v !== void 0) out[key] = v;
16131
+ }
16132
+ const fill = trackValue("fill");
16133
+ if (fill !== void 0) out.fill = fill;
16134
+ const stroke = trackValue("stroke");
16135
+ if (stroke !== void 0) out.stroke = stroke;
16136
+ const shadowFrames = frames.filter((f3) => f3.shadow !== void 0);
16137
+ if (shadowFrames.length > 0) {
16138
+ const sh = trackValue("shadow");
16139
+ if (sh !== void 0) {
16140
+ let prevSh;
16141
+ let nextSh;
16142
+ for (const f3 of shadowFrames) {
16143
+ if (f3.at <= t) prevSh = f3;
16144
+ else if (!nextSh) nextSh = f3;
16145
+ }
16146
+ if (prevSh?.shadow && nextSh?.shadow) {
16147
+ const span = nextSh.at - prevSh.at;
16148
+ const k = span > 0 ? (t - prevSh.at) / span : 1;
16149
+ out.shadow = { color: prevSh.shadow.color, blur: lerp(prevSh.shadow.blur, nextSh.shadow.blur, k) };
16150
+ } else {
16151
+ out.shadow = sh;
16152
+ }
16153
+ }
16154
+ }
16155
+ return out;
16156
+ }
16157
+ function gradientStyle(g, ox, oy, scale) {
16158
+ if (g.kind === "linear") {
16159
+ if (!g.from || !g.to) return void 0;
16160
+ return {
16161
+ kind: "linear",
16162
+ x1: ox + g.from.x * scale,
16163
+ y1: oy + g.from.y * scale,
16164
+ x2: ox + g.to.x * scale,
16165
+ y2: oy + g.to.y * scale,
16166
+ stops: g.stops
16167
+ };
16168
+ }
16169
+ if (g.kind === "conic") {
16170
+ if (!g.center) return void 0;
16171
+ return {
16172
+ kind: "conic",
16173
+ cx: ox + g.center.x * scale,
16174
+ cy: oy + g.center.y * scale,
16175
+ angle: g.angle ?? 0,
16176
+ stops: g.stops
16177
+ };
16178
+ }
16179
+ if (!g.center || g.radius === void 0) return void 0;
16180
+ return {
16181
+ kind: "radial",
16182
+ cx: ox + g.center.x * scale,
16183
+ cy: oy + g.center.y * scale,
16184
+ r: g.radius * scale,
16185
+ stops: g.stops
16186
+ };
16187
+ }
16188
+ function patternStyle(p, unit) {
16189
+ if (p.kind === "image") {
16190
+ if (!p.url) return void 0;
16191
+ return { kind: "image", url: p.url, scale: (p.scale ?? 1) / unit };
16192
+ }
16193
+ return { kind: "noise", scale: (p.scale ?? 2) / unit, alpha: p.alpha, color: p.color };
16194
+ }
16040
16195
  function DrawShape(props) {
16041
16196
  const register = React76.useContext(DrawableRegistryContext);
16042
16197
  if (register) {
@@ -16047,25 +16202,66 @@ function DrawShape(props) {
16047
16202
  ...opacity !== void 0 && opacity > 0 ? { opacity } : {}
16048
16203
  };
16049
16204
  register(node);
16050
- return /* @__PURE__ */ jsxRuntime.jsx("div", { "data-draw-shape-debug": "", style: { position: "absolute", width: 4, height: 4, background: "red", zIndex: 9999 } });
16051
16205
  }
16052
16206
  return null;
16053
16207
  }
16054
- var paintShape;
16208
+ var NUMERIC_TRACKS, lerp, paintShape;
16055
16209
  var init_DrawShape = __esm({
16056
16210
  "components/game/atoms/DrawShape.tsx"() {
16057
16211
  "use client";
16058
16212
  init_contract();
16059
16213
  init_registry();
16060
- paintShape = (painter, node, dctx) => {
16214
+ NUMERIC_TRACKS = [
16215
+ "offsetX",
16216
+ "offsetY",
16217
+ "rotate",
16218
+ "opacity",
16219
+ "radiusX",
16220
+ "radiusY",
16221
+ "width",
16222
+ "height",
16223
+ "strokeWidth",
16224
+ "strokeDashOffset",
16225
+ "blur"
16226
+ ];
16227
+ lerp = (a, b, k) => a + (b - a) * k;
16228
+ paintShape = (painter, rawNode, dctx) => {
16229
+ const node = dctx.time > 0 ? applyShapeAnimation(rawNode, dctx.time) : rawNode;
16061
16230
  if (!isValidScenePos(node.position)) return;
16062
16231
  painter.save();
16063
16232
  if (node.opacity !== void 0 && node.opacity !== 1) painter.setAlpha(node.opacity);
16233
+ const origin = dctx.projector.project(node.position);
16234
+ const tileWidth = dctx.projector.tileWidth;
16235
+ const groupScale = dctx.groupScale ?? 1;
16236
+ const strokePx = (node.strokeWidth ?? 1) / groupScale;
16237
+ if (node.blendMode) painter.setBlend(node.blendMode);
16238
+ if (node.strokeDash && node.strokeDash.length > 0) {
16239
+ painter.setLineDash(
16240
+ node.strokeDash.map((v) => v / groupScale),
16241
+ (node.strokeDashOffset ?? 0) / groupScale
16242
+ );
16243
+ }
16244
+ if (node.blur !== void 0 && node.blur > 0) painter.setBlur(node.blur / groupScale);
16245
+ if (node.rotate) {
16246
+ const pivot = node.pivot ?? { x: 0.5, y: 0.5 };
16247
+ const px = origin.x + pivot.x * tileWidth;
16248
+ const py = origin.y + pivot.y * tileWidth;
16249
+ painter.translate(px, py);
16250
+ painter.rotate(node.rotate);
16251
+ painter.translate(-px, -py);
16252
+ }
16253
+ if (node.shadow) painter.setShadow({ color: node.shadow.color, blur: node.shadow.blur * tileWidth * groupScale });
16254
+ const fill = node.fill === "none" ? void 0 : node.fill;
16255
+ const stroke = node.stroke === "none" ? void 0 : node.stroke;
16256
+ const pxFill = node.gradient ? gradientStyle(node.gradient, origin.x, origin.y, tileWidth) ?? fill : fill;
16257
+ const pxStroke = node.strokeGradient ? gradientStyle(node.strokeGradient, origin.x, origin.y, tileWidth) ?? stroke : stroke;
16258
+ const patFill = node.fillPattern ? patternStyle(node.fillPattern, groupScale) : void 0;
16064
16259
  switch (node.shape) {
16065
16260
  case "cell": {
16066
16261
  const pts = dctx.projector.cellPath(node.position);
16067
- if (node.fill) painter.fillPoly(pts, node.fill);
16068
- if (node.stroke) painter.strokePoly(pts, node.stroke, node.strokeWidth ?? 1, true);
16262
+ if (pxFill) painter.fillPoly(pts, pxFill);
16263
+ if (patFill) painter.fillPoly(pts, patFill);
16264
+ if (pxStroke) painter.strokePoly(pts, pxStroke, strokePx, true);
16069
16265
  break;
16070
16266
  }
16071
16267
  case "rect": {
@@ -16075,8 +16271,9 @@ var init_DrawShape = __esm({
16075
16271
  const y = p.y + (node.offsetY ?? 0) * tw;
16076
16272
  const w = (node.width ?? 0) * tw;
16077
16273
  const h = (node.height ?? 0) * tw;
16078
- if (node.fill) painter.fillRect(x, y, w, h, node.fill);
16079
- if (node.stroke) painter.strokeRect(x, y, w, h, node.stroke, node.strokeWidth ?? 1);
16274
+ if (pxFill) painter.fillRect(x, y, w, h, pxFill);
16275
+ if (patFill) painter.fillRect(x, y, w, h, patFill);
16276
+ if (pxStroke) painter.strokeRect(x, y, w, h, pxStroke, strokePx);
16080
16277
  break;
16081
16278
  }
16082
16279
  case "ellipse": {
@@ -16086,26 +16283,38 @@ var init_DrawShape = __esm({
16086
16283
  const cy = p.y + (node.offsetY ?? 0) * tw;
16087
16284
  const rx = (node.radiusX ?? 0) * tw;
16088
16285
  const ry = (node.radiusY ?? rx) * tw;
16089
- if (node.fill) painter.fillEllipse(cx, cy, rx, ry, node.fill);
16090
- if (node.stroke) painter.strokeEllipse(cx, cy, rx, ry, node.stroke, node.strokeWidth ?? 1);
16286
+ if (pxFill) painter.fillEllipse(cx, cy, rx, ry, pxFill);
16287
+ if (patFill) painter.fillEllipse(cx, cy, rx, ry, patFill);
16288
+ if (pxStroke) painter.strokeEllipse(cx, cy, rx, ry, pxStroke, strokePx);
16091
16289
  break;
16092
16290
  }
16093
16291
  case "poly": {
16094
- const base = dctx.projector.project(node.position);
16095
- const tw = dctx.projector.tileWidth;
16096
- const pts = (node.points ?? []).map((pt) => ({ x: base.x + pt.x * tw, y: base.y + pt.y * tw }));
16097
- if (node.fill) painter.fillPoly(pts, node.fill);
16098
- if (node.stroke) painter.strokePoly(pts, node.stroke, node.strokeWidth ?? 1, true);
16292
+ const pts = (node.points ?? []).map((pt) => ({
16293
+ x: origin.x + ((node.offsetX ?? 0) + pt.x) * tileWidth,
16294
+ y: origin.y + ((node.offsetY ?? 0) + pt.y) * tileWidth
16295
+ }));
16296
+ if (pxFill) painter.fillPoly(pts, pxFill);
16297
+ if (patFill) painter.fillPoly(pts, patFill);
16298
+ if (pxStroke) painter.strokePoly(pts, pxStroke, strokePx, true);
16099
16299
  break;
16100
16300
  }
16101
16301
  case "path": {
16102
16302
  if (!node.d) break;
16103
- const base = dctx.projector.project(node.position);
16104
- const tw = dctx.projector.tileWidth;
16105
- painter.translate(base.x, base.y);
16106
- painter.scale(tw, tw);
16107
- if (node.fill) painter.fillPath(node.d, node.fill);
16108
- if (node.stroke) painter.strokePath(node.d, node.stroke, (node.strokeWidth ?? 1) / tw);
16303
+ painter.translate(origin.x + (node.offsetX ?? 0) * tileWidth, origin.y + (node.offsetY ?? 0) * tileWidth);
16304
+ painter.scale(tileWidth, tileWidth);
16305
+ if (node.strokeDash && node.strokeDash.length > 0) {
16306
+ painter.setLineDash(
16307
+ node.strokeDash.map((v) => v / (groupScale * tileWidth)),
16308
+ (node.strokeDashOffset ?? 0) / (groupScale * tileWidth)
16309
+ );
16310
+ }
16311
+ if (node.blur !== void 0 && node.blur > 0) painter.setBlur(node.blur / (groupScale * tileWidth));
16312
+ const localFill = node.gradient ? gradientStyle(node.gradient, 0, 0, 1) ?? fill : fill;
16313
+ const localStroke = node.strokeGradient ? gradientStyle(node.strokeGradient, 0, 0, 1) ?? stroke : stroke;
16314
+ const localPat = node.fillPattern ? patternStyle(node.fillPattern, groupScale * tileWidth) : void 0;
16315
+ if (localFill) painter.fillPath(node.d, localFill);
16316
+ if (localPat) painter.fillPath(node.d, localPat);
16317
+ if (localStroke) painter.strokePath(node.d, localStroke, strokePx / tileWidth);
16109
16318
  break;
16110
16319
  }
16111
16320
  }
@@ -16186,8 +16395,6 @@ var init_DrawTextLayer = __esm({
16186
16395
  };
16187
16396
  }
16188
16397
  });
16189
-
16190
- // lib/drawable/paintDispatch.ts
16191
16398
  function paintDrawable(painter, node, dctx) {
16192
16399
  switch (node.type) {
16193
16400
  case "draw-sprite":
@@ -16208,10 +16415,20 @@ function paintDrawable(painter, node, dctx) {
16208
16415
  if (node.scale !== void 0) painter.scale(node.scale, node.scale);
16209
16416
  if (node.rotate !== void 0) painter.rotate(node.rotate);
16210
16417
  if (node.opacity !== void 0 && node.opacity !== 1) painter.setAlpha(node.opacity);
16211
- for (const item of node.items) paintDrawable(painter, item, dctx);
16418
+ if (node.clip) {
16419
+ const tw = dctx.projector.tileWidth;
16420
+ painter.scale(tw, tw);
16421
+ painter.clipPath(node.clip);
16422
+ painter.scale(1 / tw, 1 / tw);
16423
+ }
16424
+ const childCtx = node.scale !== void 0 && node.scale !== 1 ? { ...dctx, groupScale: (dctx.groupScale ?? 1) * node.scale } : dctx;
16425
+ for (const item of node.items) paintDrawable(painter, item, childCtx);
16212
16426
  painter.restore();
16213
16427
  break;
16214
16428
  }
16429
+ case "draw-mesh":
16430
+ warnUnsupported2d("draw-mesh");
16431
+ break;
16215
16432
  case "draw-sprite-layer":
16216
16433
  paintSpriteLayer(painter, node, dctx);
16217
16434
  break;
@@ -16223,6 +16440,7 @@ function paintDrawable(painter, node, dctx) {
16223
16440
  break;
16224
16441
  }
16225
16442
  }
16443
+ var paint2dLog, warnedUnsupported2d, warnUnsupported2d;
16226
16444
  var init_paintDispatch = __esm({
16227
16445
  "lib/drawable/paintDispatch.ts"() {
16228
16446
  init_contract();
@@ -16232,6 +16450,13 @@ var init_paintDispatch = __esm({
16232
16450
  init_DrawSpriteLayer();
16233
16451
  init_DrawShapeLayer();
16234
16452
  init_DrawTextLayer();
16453
+ paint2dLog = logger.createLogger("almadar:ui:drawable-2d");
16454
+ warnedUnsupported2d = /* @__PURE__ */ new Set();
16455
+ warnUnsupported2d = (kind) => {
16456
+ if (warnedUnsupported2d.has(kind)) return;
16457
+ warnedUnsupported2d.add(kind);
16458
+ paint2dLog.warn("unsupported drawable kind on the 2D painter \u2014 skipped", { kind });
16459
+ };
16235
16460
  }
16236
16461
  });
16237
16462
 
@@ -16246,6 +16471,7 @@ function collectDrawnItems(nodes) {
16246
16471
  case "draw-shape":
16247
16472
  case "draw-text":
16248
16473
  case "draw-group":
16474
+ case "draw-mesh":
16249
16475
  if (isValidScenePos(n.position)) out.push({ pos: n.position, id: n.id });
16250
16476
  break;
16251
16477
  case "draw-sprite-layer":
@@ -16317,7 +16543,6 @@ function Canvas2D({
16317
16543
  childDrawablesRef.current.push(node);
16318
16544
  }, []);
16319
16545
  const hasJsxChildren = React76__namespace.Children.count(children) > 0;
16320
- drawables && drawables.length > 0 ? drawables : childDrawablesRef.current;
16321
16546
  function isDrawableLayer(node) {
16322
16547
  return node.type === "draw-sprite-layer" || node.type === "draw-shape-layer" || node.type === "draw-text-layer";
16323
16548
  }
@@ -16466,11 +16691,24 @@ function Canvas2D({
16466
16691
  }, [showMinimap, scenePositions]);
16467
16692
  const miniMapWidth = gridExtent.width || 10;
16468
16693
  const miniMapHeight = gridExtent.height || 10;
16469
- const draw = React76.useCallback(() => {
16694
+ const drawableIsAnimated = (node) => {
16695
+ if (node.type === "draw-shape") return isAnimatedShape(node);
16696
+ if (node.type === "draw-group") return Array.isArray(node.items) && node.items.some(drawableIsAnimated);
16697
+ if (node.type === "draw-shape-layer") return Array.isArray(node.items) && node.items.some(isAnimatedShape);
16698
+ return false;
16699
+ };
16700
+ const animRafRef = React76.useRef(0);
16701
+ const drawTimeRef = React76.useRef(() => void 0);
16702
+ const draw = React76.useCallback((timeMs = 0) => {
16470
16703
  const canvas = canvasRef.current;
16471
16704
  if (!canvas) return;
16472
16705
  const ctx = canvas.getContext("2d");
16473
16706
  if (!ctx) return;
16707
+ const scheduleAnimation = (nodes) => {
16708
+ if (!nodes.some(drawableIsAnimated)) return;
16709
+ cancelAnimationFrame(animRafRef.current);
16710
+ animRafRef.current = requestAnimationFrame(() => drawTimeRef.current(performance.now()));
16711
+ };
16474
16712
  const dpr = window.devicePixelRatio || 1;
16475
16713
  canvas.width = viewportSize.width * dpr;
16476
16714
  canvas.height = viewportSize.height * dpr;
@@ -16503,14 +16741,24 @@ function Canvas2D({
16503
16741
  if (!drawables || drawables.length === 0) {
16504
16742
  const childDrawables = childDrawablesRef.current;
16505
16743
  if (childDrawables.length === 0) return;
16744
+ const cam0 = cameraRef.current;
16745
+ if (camera !== "follow" && dragDistance() === 0) {
16746
+ const focus = cameraPos ?? defaultGridFocus;
16747
+ if (focus) {
16748
+ const p = projector.anchorPoint(focus, "center");
16749
+ cam0.x = p.x - viewportSize.width / 2;
16750
+ cam0.y = p.y - viewportSize.height / 2;
16751
+ }
16752
+ }
16506
16753
  const painter0 = createWebPainter(ctx, bumpAtlas);
16507
16754
  painter0.save();
16508
16755
  painter0.translate(viewportSize.width / 2, viewportSize.height / 2);
16509
- painter0.scale(cameraRef.current.zoom, cameraRef.current.zoom);
16510
- painter0.translate(-viewportSize.width / 2, -viewportSize.height / 2);
16511
- const dctx0 = { projector, time: 0, invalidate: bumpAtlas };
16756
+ painter0.scale(cam0.zoom, cam0.zoom);
16757
+ painter0.translate(-viewportSize.width / 2 - cam0.x, -viewportSize.height / 2 - cam0.y);
16758
+ const dctx0 = { projector, time: timeMs, invalidate: bumpAtlas };
16512
16759
  for (const node of childDrawables) paintDrawable(painter0, node, dctx0);
16513
16760
  painter0.restore();
16761
+ scheduleAnimation(childDrawables);
16514
16762
  return;
16515
16763
  }
16516
16764
  const cam = cameraRef.current;
@@ -16530,10 +16778,15 @@ function Canvas2D({
16530
16778
  painter.translate(viewportSize.width / 2, viewportSize.height / 2);
16531
16779
  painter.scale(cam.zoom, cam.zoom);
16532
16780
  painter.translate(-viewportSize.width / 2 - cam.x, -viewportSize.height / 2 - cam.y);
16533
- const dctx = { projector, time: 0, invalidate: bumpAtlas };
16781
+ const dctx = { projector, time: timeMs, invalidate: bumpAtlas };
16534
16782
  for (const node of drawables) paintDrawable(painter, node, dctx);
16535
16783
  painter.restore();
16784
+ scheduleAnimation(drawables);
16536
16785
  }, [viewportSize, backgroundImage, bgColor, drawables, projector, cameraRef, bumpAtlas, getImage, cameraPos, defaultGridFocus, camera, dragDistance]);
16786
+ React76.useEffect(() => {
16787
+ drawTimeRef.current = draw;
16788
+ }, [draw]);
16789
+ React76.useEffect(() => () => cancelAnimationFrame(animRafRef.current), []);
16537
16790
  React76.useEffect(() => {
16538
16791
  if (camera !== "follow" || !followTarget) return;
16539
16792
  const p = projector.anchorPoint(followTarget, "center");
@@ -16756,15 +17009,7 @@ function Canvas2D({
16756
17009
  mapHeight: miniMapHeight
16757
17010
  }
16758
17011
  ) }),
16759
- hasJsxChildren && /* @__PURE__ */ jsxRuntime.jsx("div", { "aria-hidden": "true", style: { position: "absolute", width: 0, height: 0, overflow: "hidden" }, children }),
16760
- /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-debug": "", style: { position: "absolute", top: 0, left: 0, background: "yellow", color: "black", zIndex: 9999, fontSize: 24, padding: 8 }, children: [
16761
- "C=",
16762
- React76__namespace.Children.count(children),
16763
- " J=",
16764
- String(hasJsxChildren),
16765
- " D=",
16766
- drawables?.length ?? -1
16767
- ] })
17012
+ hasJsxChildren && /* @__PURE__ */ jsxRuntime.jsx("div", { "aria-hidden": "true", style: { position: "absolute", width: 0, height: 0, overflow: "hidden" }, children })
16768
17013
  ]
16769
17014
  }
16770
17015
  ) });
@@ -16789,6 +17034,7 @@ var init_Canvas2D = __esm({
16789
17034
  init_webPainter2d();
16790
17035
  init_projector();
16791
17036
  init_paintDispatch();
17037
+ init_DrawShape();
16792
17038
  init_registry();
16793
17039
  init_hitTest();
16794
17040
  init_isometric();
@@ -16843,6 +17089,7 @@ function Canvas({
16843
17089
  isLoading,
16844
17090
  cameraMode: to3DCameraMode(camera?.mode),
16845
17091
  ...zoom !== void 0 ? { scale: zoom } : {},
17092
+ ...camera?.fov !== void 0 ? { fov: camera.fov } : {},
16846
17093
  ...camera?.target !== void 0 ? { followTarget: camera.target } : {},
16847
17094
  unitScale,
16848
17095
  backgroundColor,
@@ -40079,14 +40326,26 @@ var init_DetailPanel = __esm({
40079
40326
  exports.DetailPanel.displayName = "DetailPanel";
40080
40327
  }
40081
40328
  });
40082
-
40083
- // components/game/atoms/DrawGroup.tsx
40084
- function DrawGroup(_props) {
40329
+ function DrawGroup(props) {
40330
+ const register = React76.useContext(DrawableRegistryContext);
40331
+ if (register) register({ ...props, type: "draw-group" });
40085
40332
  return null;
40086
40333
  }
40087
40334
  var init_DrawGroup = __esm({
40088
40335
  "components/game/atoms/DrawGroup.tsx"() {
40089
40336
  "use client";
40337
+ init_registry();
40338
+ }
40339
+ });
40340
+ function DrawMesh(props) {
40341
+ const register = React76.useContext(DrawableRegistryContext);
40342
+ if (register) register({ ...props, type: "draw-mesh" });
40343
+ return null;
40344
+ }
40345
+ var init_DrawMesh = __esm({
40346
+ "components/game/atoms/DrawMesh.tsx"() {
40347
+ "use client";
40348
+ init_registry();
40090
40349
  }
40091
40350
  });
40092
40351
  function extractTitle(children) {
@@ -45793,6 +46052,7 @@ var init_component_registry_generated = __esm({
45793
46052
  init_DocTOC();
45794
46053
  init_DocumentViewer();
45795
46054
  init_DrawGroup();
46055
+ init_DrawMesh();
45796
46056
  init_DrawShape();
45797
46057
  init_DrawShapeLayer();
45798
46058
  init_DrawSprite();
@@ -46061,6 +46321,7 @@ var init_component_registry_generated = __esm({
46061
46321
  "DocTOC": exports.DocTOC,
46062
46322
  "DocumentViewer": exports.DocumentViewer,
46063
46323
  "DrawGroup": DrawGroup,
46324
+ "DrawMesh": DrawMesh,
46064
46325
  "DrawShape": DrawShape,
46065
46326
  "DrawShapeLayer": DrawShapeLayer,
46066
46327
  "DrawSprite": DrawSprite,
@@ -46969,8 +47230,13 @@ function SlotContentRenderer({
46969
47230
  const isSingleChild = typeof childrenConfig === "string" || typeof childrenConfig === "object" && childrenConfig !== null && !Array.isArray(childrenConfig) && "type" in childrenConfig;
46970
47231
  const hasChildren = PATTERNS_WITH_CHILDREN.has(content.pattern) || Array.isArray(childrenConfig) && childrenConfig.length > 0 || isSingleChild;
46971
47232
  const isDrawHost = patterns.isDrawHostPattern(content.pattern);
47233
+ const arr = Array.isArray(childrenConfig) ? childrenConfig : childrenConfig ? [childrenConfig] : [];
47234
+ const hasTraitChildren = arr.some(
47235
+ (c) => typeof c === "string" && TRAIT_BINDING_RE.test(c)
47236
+ );
47237
+ const drawHostUsesReactChildren = isDrawHost && hasTraitChildren;
46972
47238
  const myPath = patternPath ?? "root";
46973
- const renderedChildren = hasChildren && !isDrawHost ? renderPatternChildren(childrenConfig, onDismiss, content.id, myPath, content.sourceTrait, {
47239
+ const renderedChildren = hasChildren && (!isDrawHost || drawHostUsesReactChildren) ? renderPatternChildren(childrenConfig, onDismiss, content.id, myPath, content.sourceTrait, {
46974
47240
  slot: content.slot,
46975
47241
  transitionEvent: content.transitionEvent,
46976
47242
  fromState: content.fromState,
@@ -47031,7 +47297,7 @@ function SlotContentRenderer({
47031
47297
  for (const [k, v] of Object.entries(nodeSlotOverrides)) {
47032
47298
  finalProps[k] = v;
47033
47299
  }
47034
- if (isDrawHost && Array.isArray(childrenConfig) && childrenConfig.length > 0) {
47300
+ if (isDrawHost && !drawHostUsesReactChildren && Array.isArray(childrenConfig) && childrenConfig.length > 0) {
47035
47301
  finalProps.drawables = toDrawableNodes(childrenConfig);
47036
47302
  }
47037
47303
  const entityVal = finalProps.entity;
@@ -47210,6 +47476,8 @@ var init_UISlotRenderer = __esm({
47210
47476
  "vstack",
47211
47477
  "hstack",
47212
47478
  "box",
47479
+ "canvas",
47480
+ "canvas-2d",
47213
47481
  "grid",
47214
47482
  "center",
47215
47483
  "card",
@@ -8,9 +8,9 @@ export { A as AudioManifest, G as GameAudioContext, a as GameAudioContextValue,
8
8
  import { SExpr } from '@almadar/evaluator';
9
9
  import { U as UnitAnimationState, F as FacingDirection, S as SpriteSheetUrls, d as SpriteFrameDims, R as ResolvedFrame, a as IsometricUnit, C as CameraState, e as FieldInfo, f as OrbitalTraitInfo, g as OrbitalPageInfo, T as TraitLevelData, E as ExternalLink } from '../avl-schema-parser-B8Onmfsu.cjs';
10
10
  export { B as BoardTile, G as GameAction, h as GamePhase, i as GameState, j as GameUnit, b as IsometricFeature, I as IsometricTile, P as Position, k as UnitTrait, l as calculateAttackTargets, m as calculateValidMoves, n as createInitialGameState } from '../avl-schema-parser-B8Onmfsu.cjs';
11
- import { D as DrawableNode } from '../paintDispatch-DXygiK7M.cjs';
12
- import { C as ContentSegment, b as DomLayoutData, d as DomStateNode, V as VisualizerConfig, e as DomTransitionLabel } from '../cn-CL0kdshO.cjs';
13
- export { n as cn } from '../cn-CL0kdshO.cjs';
11
+ import { D as DrawableNode } from '../paintDispatch-BQn5Lyx1.cjs';
12
+ import { C as ContentSegment, b as DomLayoutData, d as DomStateNode, V as VisualizerConfig, e as DomTransitionLabel } from '../cn-rwc3svaX.cjs';
13
+ export { n as cn } from '../cn-rwc3svaX.cjs';
14
14
  import { b as SlotContent } from '../useUISlots-BesZYMks.cjs';
15
15
  export { D as DEFAULT_SLOTS, S as SlotAnimation, a as SlotChangeCallback, c as SlotRenderConfig, U as UISlotManager, u as useUISlotManager } from '../useUISlots-BesZYMks.cjs';
16
16
  export { ALMADAR_DND_MIME, AuthContextValue, AuthUser, CanvasGestureCallbacks, CanvasGestureHandlers, CompileResult, CompileStage, DragReorderResult, DraggablePayload, Extension, ExtensionManifest, FileSystemFile, FileSystemStatus, GitHubRepo, GitHubStatus, HistoryChangeSummary, HistoryTimelineItem, I18nContextValue, I18nProvider, InfiniteScrollOptions, InfiniteScrollResult, LongPressHandlers, LongPressOptions, OpenFile, Positioned, PullToRefreshOptions, PullToRefreshResult, QuerySingletonEntity, QuerySingletonResult, QuerySingletonState, QueryState, RenderInterpolationHandle, RenderInterpolationOptions, RevertResult, SelectedFile, SharedEntityStore, SharedEntityStoreContext, SharedEntitySubscriber, SharedEntityWriter, SwipeGestureOptions, SwipeGestureResult, SwipeHandlers, TapRevealOptions, TapRevealResult, TraitListenSpec, TranslateFunction, UseCanvasGesturesOptions, UseCompileResult, UseDraggableOptions, UseDraggableResult, UseDropZoneOptions, UseDropZoneResult, UseExtensionsOptions, UseExtensionsResult, UseFileEditorOptions, UseFileEditorResult, UseFileSystemResult, UseOrbitalHistoryOptions, UseOrbitalHistoryResult, createSharedEntityStore, createTranslate, parseQueryBinding, runTickFrame, useAgentChat, useAuthContext, useCanvasGestures, useCompile, useConnectGitHub, useDeepAgentGeneration, useDisconnectGitHub, useDragReorder, useDraggable, useDropZone, useExtensions, useFileEditor, useFileSystem, useGitHubBranches, useGitHubRepo, useGitHubRepos, useGitHubStatus, useInfiniteScroll, useLongPress, useMediaQuery, useOrbitalHistory, usePreview, usePullToRefresh, useQuerySingleton, useRenderInterpolation, useSharedEntitySnapshot, useSharedEntityStore, useSharedEntityStoreContext, useSwipeGesture, useTapReveal, useTraitListens, useTranslate, useUIEvents, useValidation } from '../hooks/index.cjs';