@almadar/ui 5.42.0 → 5.43.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.
@@ -31086,6 +31086,78 @@ var init_GameOverScreen = __esm({
31086
31086
  GameOverScreen.displayName = "GameOverScreen";
31087
31087
  }
31088
31088
  });
31089
+ function useRenderInterpolation(options = {}) {
31090
+ const tickIntervalMs = options.tickIntervalMs ?? 1e3 / 30;
31091
+ const prevRef = React91.useRef(null);
31092
+ const currRef = React91.useRef(null);
31093
+ const rafRef = React91.useRef(null);
31094
+ const onSnapshot = React91.useCallback((entities) => {
31095
+ prevRef.current = currRef.current;
31096
+ currRef.current = { entities, arrivedAt: performance.now() };
31097
+ }, []);
31098
+ const getInterpolated = React91.useCallback((now2) => {
31099
+ const out = /* @__PURE__ */ new Map();
31100
+ const curr = currRef.current;
31101
+ if (!curr) return out;
31102
+ const prev = prevRef.current;
31103
+ if (!prev) {
31104
+ for (const e of curr.entities) out.set(e.id, { x: e.x, y: e.y });
31105
+ return out;
31106
+ }
31107
+ const rawAlpha = (now2 - curr.arrivedAt) / tickIntervalMs;
31108
+ const alpha = rawAlpha < 0 ? 0 : rawAlpha > 1 ? 1 : rawAlpha;
31109
+ const prevMap = /* @__PURE__ */ new Map();
31110
+ for (const e of prev.entities) prevMap.set(e.id, e);
31111
+ for (const c of curr.entities) {
31112
+ const p2 = prevMap.get(c.id);
31113
+ if (!p2) {
31114
+ out.set(c.id, { x: c.x, y: c.y });
31115
+ } else {
31116
+ out.set(c.id, {
31117
+ x: p2.x + (c.x - p2.x) * alpha,
31118
+ y: p2.y + (c.y - p2.y) * alpha
31119
+ });
31120
+ }
31121
+ }
31122
+ return out;
31123
+ }, [tickIntervalMs]);
31124
+ const startLoop = React91.useCallback(
31125
+ (draw) => {
31126
+ let active = true;
31127
+ const loop = () => {
31128
+ if (!active) return;
31129
+ try {
31130
+ draw(getInterpolated(performance.now()));
31131
+ } catch {
31132
+ }
31133
+ rafRef.current = requestAnimationFrame(loop);
31134
+ };
31135
+ rafRef.current = requestAnimationFrame(loop);
31136
+ return () => {
31137
+ active = false;
31138
+ if (rafRef.current !== null) {
31139
+ cancelAnimationFrame(rafRef.current);
31140
+ rafRef.current = null;
31141
+ }
31142
+ };
31143
+ },
31144
+ [getInterpolated]
31145
+ );
31146
+ React91.useEffect(() => {
31147
+ return () => {
31148
+ if (rafRef.current !== null) {
31149
+ cancelAnimationFrame(rafRef.current);
31150
+ rafRef.current = null;
31151
+ }
31152
+ };
31153
+ }, []);
31154
+ return { onSnapshot, getInterpolated, startLoop };
31155
+ }
31156
+ var init_useRenderInterpolation = __esm({
31157
+ "hooks/useRenderInterpolation.ts"() {
31158
+ "use client";
31159
+ }
31160
+ });
31089
31161
  function PlatformerCanvas({
31090
31162
  player,
31091
31163
  platforms = [
@@ -31154,8 +31226,43 @@ function PlatformerCanvas({
31154
31226
  y: 336,
31155
31227
  width: 32,
31156
31228
  height: 48,
31229
+ vx: 0,
31230
+ vy: 0,
31231
+ grounded: true,
31157
31232
  facingRight: true
31158
31233
  };
31234
+ const playerRef = React91.useRef(resolvedPlayer);
31235
+ playerRef.current = resolvedPlayer;
31236
+ const interp = useRenderInterpolation();
31237
+ React91.useEffect(() => {
31238
+ interp.onSnapshot([{ id: "player", x: resolvedPlayer.x, y: resolvedPlayer.y }]);
31239
+ }, [resolvedPlayer.x, resolvedPlayer.y]);
31240
+ const propsRef = React91.useRef({
31241
+ platforms,
31242
+ worldWidth,
31243
+ worldHeight,
31244
+ canvasWidth,
31245
+ canvasHeight,
31246
+ followCamera,
31247
+ bgColor,
31248
+ playerSprite,
31249
+ tileSprites,
31250
+ backgroundImage,
31251
+ assetBaseUrl
31252
+ });
31253
+ propsRef.current = {
31254
+ platforms,
31255
+ worldWidth,
31256
+ worldHeight,
31257
+ canvasWidth,
31258
+ canvasHeight,
31259
+ followCamera,
31260
+ bgColor,
31261
+ playerSprite,
31262
+ tileSprites,
31263
+ backgroundImage,
31264
+ assetBaseUrl
31265
+ };
31159
31266
  const handleKeyDown = React91.useCallback((e) => {
31160
31267
  if (keysRef.current.has(e.code)) return;
31161
31268
  keysRef.current.add(e.code);
@@ -31204,124 +31311,149 @@ function PlatformerCanvas({
31204
31311
  canvas.width = canvasWidth * dpr;
31205
31312
  canvas.height = canvasHeight * dpr;
31206
31313
  ctx.scale(dpr, dpr);
31207
- let camX = 0;
31208
- let camY = 0;
31209
- if (followCamera) {
31210
- camX = Math.max(0, Math.min(resolvedPlayer.x - canvasWidth / 2, worldWidth - canvasWidth));
31211
- camY = Math.max(0, Math.min(resolvedPlayer.y - canvasHeight / 2 - 50, worldHeight - canvasHeight));
31212
- }
31213
- const bgImg = backgroundImage ? loadImage(backgroundImage) : null;
31214
- if (bgImg) {
31215
- ctx.drawImage(bgImg, 0, 0, canvasWidth, canvasHeight);
31216
- } else if (bgColor) {
31217
- ctx.fillStyle = bgColor;
31218
- ctx.fillRect(0, 0, canvasWidth, canvasHeight);
31219
- } else {
31220
- const grad = ctx.createLinearGradient(0, 0, 0, canvasHeight);
31221
- grad.addColorStop(0, SKY_GRADIENT_TOP);
31222
- grad.addColorStop(1, SKY_GRADIENT_BOTTOM);
31223
- ctx.fillStyle = grad;
31224
- ctx.fillRect(0, 0, canvasWidth, canvasHeight);
31225
- }
31226
- ctx.strokeStyle = GRID_COLOR;
31227
- ctx.lineWidth = 1;
31228
- const gridSize = 32;
31229
- for (let gx = -camX % gridSize; gx < canvasWidth; gx += gridSize) {
31230
- ctx.beginPath();
31231
- ctx.moveTo(gx, 0);
31232
- ctx.lineTo(gx, canvasHeight);
31233
- ctx.stroke();
31234
- }
31235
- for (let gy = -camY % gridSize; gy < canvasHeight; gy += gridSize) {
31236
- ctx.beginPath();
31237
- ctx.moveTo(0, gy);
31238
- ctx.lineTo(canvasWidth, gy);
31239
- ctx.stroke();
31240
- }
31241
- for (const plat of platforms) {
31242
- const px = plat.x - camX;
31243
- const py = plat.y - camY;
31244
- const platType = plat.type ?? "ground";
31245
- const spriteUrl = tileSprites?.[platType];
31246
- const tileImg = spriteUrl ? loadImage(spriteUrl) : null;
31247
- if (tileImg) {
31248
- const tileW = tileImg.naturalWidth;
31249
- const tileH = tileImg.naturalHeight;
31250
- const scaleH = plat.height / tileH;
31251
- const scaledW = tileW * scaleH;
31252
- for (let tx = 0; tx < plat.width; tx += scaledW) {
31253
- const drawW = Math.min(scaledW, plat.width - tx);
31254
- const srcW = drawW / scaleH;
31255
- ctx.drawImage(tileImg, 0, 0, srcW, tileH, px + tx, py, drawW, plat.height);
31256
- }
31314
+ }, [canvasWidth, canvasHeight]);
31315
+ React91.useEffect(() => {
31316
+ const drawFrame = (positions) => {
31317
+ const canvas = canvasRef.current;
31318
+ if (!canvas) return;
31319
+ const ctx = canvas.getContext("2d");
31320
+ if (!ctx) return;
31321
+ const {
31322
+ platforms: plats,
31323
+ worldWidth: ww,
31324
+ worldHeight: wh,
31325
+ canvasWidth: cw,
31326
+ canvasHeight: ch,
31327
+ followCamera: fc,
31328
+ bgColor: bg,
31329
+ playerSprite: pSprite,
31330
+ tileSprites: tSprites,
31331
+ backgroundImage: bgImg
31332
+ } = propsRef.current;
31333
+ const auth = playerRef.current;
31334
+ const interped = positions.get("player");
31335
+ const px = interped?.x ?? auth.x;
31336
+ const py = interped?.y ?? auth.y;
31337
+ let camX = 0;
31338
+ let camY = 0;
31339
+ if (fc) {
31340
+ camX = Math.max(0, Math.min(px - cw / 2, ww - cw));
31341
+ camY = Math.max(0, Math.min(py - ch / 2 - 50, wh - ch));
31342
+ }
31343
+ const bgImage = bgImg ? loadImage(bgImg) : null;
31344
+ if (bgImage) {
31345
+ ctx.drawImage(bgImage, 0, 0, cw, ch);
31346
+ } else if (bg) {
31347
+ ctx.fillStyle = bg;
31348
+ ctx.fillRect(0, 0, cw, ch);
31257
31349
  } else {
31258
- const color = PLATFORM_COLORS[platType] ?? PLATFORM_COLORS.ground;
31259
- ctx.fillStyle = color;
31260
- ctx.fillRect(px, py, plat.width, plat.height);
31261
- ctx.fillStyle = "rgba(255, 255, 255, 0.15)";
31262
- ctx.fillRect(px, py, plat.width, 3);
31263
- ctx.fillStyle = "rgba(0, 0, 0, 0.3)";
31264
- ctx.fillRect(px, py + plat.height - 2, plat.width, 2);
31265
- if (platType === "hazard") {
31266
- ctx.strokeStyle = "#e74c3c";
31267
- ctx.lineWidth = 2;
31268
- for (let sx = px; sx < px + plat.width; sx += 12) {
31350
+ const grad = ctx.createLinearGradient(0, 0, 0, ch);
31351
+ grad.addColorStop(0, SKY_GRADIENT_TOP);
31352
+ grad.addColorStop(1, SKY_GRADIENT_BOTTOM);
31353
+ ctx.fillStyle = grad;
31354
+ ctx.fillRect(0, 0, cw, ch);
31355
+ }
31356
+ ctx.strokeStyle = GRID_COLOR;
31357
+ ctx.lineWidth = 1;
31358
+ const gridSize = 32;
31359
+ for (let gx = -camX % gridSize; gx < cw; gx += gridSize) {
31360
+ ctx.beginPath();
31361
+ ctx.moveTo(gx, 0);
31362
+ ctx.lineTo(gx, ch);
31363
+ ctx.stroke();
31364
+ }
31365
+ for (let gy = -camY % gridSize; gy < ch; gy += gridSize) {
31366
+ ctx.beginPath();
31367
+ ctx.moveTo(0, gy);
31368
+ ctx.lineTo(cw, gy);
31369
+ ctx.stroke();
31370
+ }
31371
+ for (const plat of plats) {
31372
+ const platX = plat.x - camX;
31373
+ const platY = plat.y - camY;
31374
+ const platType = plat.type ?? "ground";
31375
+ const spriteUrl = tSprites?.[platType];
31376
+ const tileImg = spriteUrl ? loadImage(spriteUrl) : null;
31377
+ if (tileImg) {
31378
+ const tileW = tileImg.naturalWidth;
31379
+ const tileH = tileImg.naturalHeight;
31380
+ const scaleH = plat.height / tileH;
31381
+ const scaledW = tileW * scaleH;
31382
+ for (let tx = 0; tx < plat.width; tx += scaledW) {
31383
+ const drawW = Math.min(scaledW, plat.width - tx);
31384
+ const srcW = drawW / scaleH;
31385
+ ctx.drawImage(tileImg, 0, 0, srcW, tileH, platX + tx, platY, drawW, plat.height);
31386
+ }
31387
+ } else {
31388
+ const color = PLATFORM_COLORS[platType] ?? PLATFORM_COLORS.ground;
31389
+ ctx.fillStyle = color;
31390
+ ctx.fillRect(platX, platY, plat.width, plat.height);
31391
+ ctx.fillStyle = "rgba(255, 255, 255, 0.15)";
31392
+ ctx.fillRect(platX, platY, plat.width, 3);
31393
+ ctx.fillStyle = "rgba(0, 0, 0, 0.3)";
31394
+ ctx.fillRect(platX, platY + plat.height - 2, plat.width, 2);
31395
+ if (platType === "hazard") {
31396
+ ctx.strokeStyle = "#e74c3c";
31397
+ ctx.lineWidth = 2;
31398
+ for (let sx = platX; sx < platX + plat.width; sx += 12) {
31399
+ ctx.beginPath();
31400
+ ctx.moveTo(sx, platY);
31401
+ ctx.lineTo(sx + 6, platY + plat.height);
31402
+ ctx.stroke();
31403
+ }
31404
+ }
31405
+ if (platType === "goal") {
31406
+ ctx.fillStyle = "rgba(241, 196, 15, 0.5)";
31269
31407
  ctx.beginPath();
31270
- ctx.moveTo(sx, py);
31271
- ctx.lineTo(sx + 6, py + plat.height);
31272
- ctx.stroke();
31408
+ ctx.arc(platX + plat.width / 2, platY - 10, 8, 0, Math.PI * 2);
31409
+ ctx.fill();
31273
31410
  }
31274
31411
  }
31275
- if (platType === "goal") {
31276
- ctx.fillStyle = "rgba(241, 196, 15, 0.5)";
31277
- ctx.beginPath();
31278
- ctx.arc(px + plat.width / 2, py - 10, 8, 0, Math.PI * 2);
31279
- ctx.fill();
31280
- }
31281
31412
  }
31282
- }
31283
- const pw = resolvedPlayer.width ?? 24;
31284
- const ph = resolvedPlayer.height ?? 32;
31285
- const ppx = resolvedPlayer.x - camX;
31286
- const ppy = resolvedPlayer.y - camY;
31287
- const facingRight = resolvedPlayer.facingRight ?? true;
31288
- const playerImg = playerSprite ? loadImage(playerSprite) : null;
31289
- if (playerImg) {
31290
- ctx.save();
31291
- if (!facingRight) {
31292
- ctx.translate(ppx + pw, ppy);
31293
- ctx.scale(-1, 1);
31294
- ctx.drawImage(playerImg, 0, 0, pw, ph);
31413
+ const pw = auth.width ?? 24;
31414
+ const ph = auth.height ?? 32;
31415
+ const ppx = px - camX;
31416
+ const ppy = py - camY;
31417
+ const facingRight = auth.facingRight ?? true;
31418
+ const playerImg = pSprite ? loadImage(pSprite) : null;
31419
+ if (playerImg) {
31420
+ ctx.save();
31421
+ if (!facingRight) {
31422
+ ctx.translate(ppx + pw, ppy);
31423
+ ctx.scale(-1, 1);
31424
+ ctx.drawImage(playerImg, 0, 0, pw, ph);
31425
+ } else {
31426
+ ctx.drawImage(playerImg, ppx, ppy, pw, ph);
31427
+ }
31428
+ ctx.restore();
31295
31429
  } else {
31296
- ctx.drawImage(playerImg, ppx, ppy, pw, ph);
31430
+ ctx.fillStyle = PLAYER_COLOR;
31431
+ const radius = Math.min(pw, ph) * 0.25;
31432
+ ctx.beginPath();
31433
+ ctx.moveTo(ppx + radius, ppy);
31434
+ ctx.lineTo(ppx + pw - radius, ppy);
31435
+ ctx.quadraticCurveTo(ppx + pw, ppy, ppx + pw, ppy + radius);
31436
+ ctx.lineTo(ppx + pw, ppy + ph - radius);
31437
+ ctx.quadraticCurveTo(ppx + pw, ppy + ph, ppx + pw - radius, ppy + ph);
31438
+ ctx.lineTo(ppx + radius, ppy + ph);
31439
+ ctx.quadraticCurveTo(ppx, ppy + ph, ppx, ppy + ph - radius);
31440
+ ctx.lineTo(ppx, ppy + radius);
31441
+ ctx.quadraticCurveTo(ppx, ppy, ppx + radius, ppy);
31442
+ ctx.fill();
31443
+ const eyeY = ppy + ph * 0.3;
31444
+ const eyeSize = 3;
31445
+ const eyeOffsetX = facingRight ? pw * 0.55 : pw * 0.2;
31446
+ ctx.fillStyle = PLAYER_EYE_COLOR;
31447
+ ctx.beginPath();
31448
+ ctx.arc(ppx + eyeOffsetX, eyeY, eyeSize, 0, Math.PI * 2);
31449
+ ctx.fill();
31450
+ ctx.beginPath();
31451
+ ctx.arc(ppx + eyeOffsetX + 7, eyeY, eyeSize, 0, Math.PI * 2);
31452
+ ctx.fill();
31297
31453
  }
31298
- ctx.restore();
31299
- } else {
31300
- ctx.fillStyle = PLAYER_COLOR;
31301
- const radius = Math.min(pw, ph) * 0.25;
31302
- ctx.beginPath();
31303
- ctx.moveTo(ppx + radius, ppy);
31304
- ctx.lineTo(ppx + pw - radius, ppy);
31305
- ctx.quadraticCurveTo(ppx + pw, ppy, ppx + pw, ppy + radius);
31306
- ctx.lineTo(ppx + pw, ppy + ph - radius);
31307
- ctx.quadraticCurveTo(ppx + pw, ppy + ph, ppx + pw - radius, ppy + ph);
31308
- ctx.lineTo(ppx + radius, ppy + ph);
31309
- ctx.quadraticCurveTo(ppx, ppy + ph, ppx, ppy + ph - radius);
31310
- ctx.lineTo(ppx, ppy + radius);
31311
- ctx.quadraticCurveTo(ppx, ppy, ppx + radius, ppy);
31312
- ctx.fill();
31313
- const eyeY = ppy + ph * 0.3;
31314
- const eyeSize = 3;
31315
- const eyeOffsetX = facingRight ? pw * 0.55 : pw * 0.2;
31316
- ctx.fillStyle = PLAYER_EYE_COLOR;
31317
- ctx.beginPath();
31318
- ctx.arc(ppx + eyeOffsetX, eyeY, eyeSize, 0, Math.PI * 2);
31319
- ctx.fill();
31320
- ctx.beginPath();
31321
- ctx.arc(ppx + eyeOffsetX + 7, eyeY, eyeSize, 0, Math.PI * 2);
31322
- ctx.fill();
31323
- }
31324
- }, [player, platforms, worldWidth, worldHeight, canvasWidth, canvasHeight, followCamera, bgColor, playerSprite, tileSprites, backgroundImage, assetBaseUrl, loadedImages]);
31454
+ };
31455
+ return interp.startLoop(drawFrame);
31456
+ }, [interp.startLoop, loadImage]);
31325
31457
  return /* @__PURE__ */ jsxRuntime.jsx(
31326
31458
  "canvas",
31327
31459
  {
@@ -31339,6 +31471,7 @@ var init_PlatformerCanvas = __esm({
31339
31471
  init_cn();
31340
31472
  init_useEventBus();
31341
31473
  init_verificationRegistry();
31474
+ init_useRenderInterpolation();
31342
31475
  PLATFORM_COLORS = {
31343
31476
  ground: "#4a7c59",
31344
31477
  platform: "#7c6b4a",
@@ -31721,13 +31854,13 @@ var init_MapView = __esm({
31721
31854
  shadowSize: [41, 41]
31722
31855
  });
31723
31856
  L.Marker.prototype.options.icon = defaultIcon;
31724
- const { useEffect: useEffect78, useRef: useRef70, useCallback: useCallback117, useState: useState115 } = React91__namespace.default;
31857
+ const { useEffect: useEffect79, useRef: useRef71, useCallback: useCallback118, useState: useState115 } = React91__namespace.default;
31725
31858
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
31726
31859
  const { useEventBus: useEventBus3 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
31727
31860
  function MapUpdater({ centerLat, centerLng, zoom }) {
31728
31861
  const map = useMap();
31729
- const prevRef = useRef70({ centerLat, centerLng, zoom });
31730
- useEffect78(() => {
31862
+ const prevRef = useRef71({ centerLat, centerLng, zoom });
31863
+ useEffect79(() => {
31731
31864
  const prev = prevRef.current;
31732
31865
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
31733
31866
  map.setView([centerLat, centerLng], zoom);
@@ -31738,7 +31871,7 @@ var init_MapView = __esm({
31738
31871
  }
31739
31872
  function MapClickHandler({ onMapClick }) {
31740
31873
  const map = useMap();
31741
- useEffect78(() => {
31874
+ useEffect79(() => {
31742
31875
  if (!onMapClick) return;
31743
31876
  const handler = (e) => {
31744
31877
  onMapClick(e.latlng.lat, e.latlng.lng);
@@ -31767,7 +31900,7 @@ var init_MapView = __esm({
31767
31900
  }) {
31768
31901
  const eventBus = useEventBus3();
31769
31902
  const [clickedPosition, setClickedPosition] = useState115(null);
31770
- const handleMapClick = useCallback117((lat, lng) => {
31903
+ const handleMapClick = useCallback118((lat, lng) => {
31771
31904
  if (showClickedPin) {
31772
31905
  setClickedPosition({ lat, lng });
31773
31906
  }
@@ -31776,7 +31909,7 @@ var init_MapView = __esm({
31776
31909
  eventBus.emit(`UI:${mapClickEvent}`, { latitude: lat, longitude: lng });
31777
31910
  }
31778
31911
  }, [onMapClick, mapClickEvent, eventBus, showClickedPin]);
31779
- const handleMarkerClick = useCallback117((marker) => {
31912
+ const handleMarkerClick = useCallback118((marker) => {
31780
31913
  onMarkerClick?.(marker);
31781
31914
  if (markerClickEvent) {
31782
31915
  eventBus.emit(`UI:${markerClickEvent}`, { ...marker });
@@ -47239,11 +47372,13 @@ function SimulationCanvas({
47239
47372
  height = 400,
47240
47373
  running,
47241
47374
  speed = 1,
47375
+ bodies: externalBodies,
47242
47376
  className
47243
47377
  }) {
47244
47378
  const preset = React91.useMemo(() => resolvePreset(presetProp), [presetProp]);
47245
47379
  const canvasRef = React91.useRef(null);
47246
47380
  const bodiesRef = React91.useRef(structuredClone(preset.bodies));
47381
+ const interp = useRenderInterpolation();
47247
47382
  React91.useEffect(() => {
47248
47383
  bodiesRef.current = structuredClone(preset.bodies);
47249
47384
  }, [preset]);
@@ -47341,6 +47476,67 @@ function SimulationCanvas({
47341
47476
  }
47342
47477
  }, [width, height, preset]);
47343
47478
  React91.useEffect(() => {
47479
+ if (!externalBodies) return;
47480
+ interp.onSnapshot(externalBodies.map((b) => ({ id: b.id, x: b.x, y: b.y })));
47481
+ }, [externalBodies]);
47482
+ const presetRef = React91.useRef(preset);
47483
+ presetRef.current = preset;
47484
+ const widthRef = React91.useRef(width);
47485
+ widthRef.current = width;
47486
+ const heightRef = React91.useRef(height);
47487
+ heightRef.current = height;
47488
+ React91.useEffect(() => {
47489
+ if (!externalBodies) return;
47490
+ const drawInterpolated = (positions) => {
47491
+ const canvas = canvasRef.current;
47492
+ if (!canvas) return;
47493
+ const ctx = canvas.getContext("2d");
47494
+ if (!ctx) return;
47495
+ const bodies = bodiesRef.current;
47496
+ const p2 = presetRef.current;
47497
+ const w = widthRef.current;
47498
+ const h = heightRef.current;
47499
+ ctx.clearRect(0, 0, w, h);
47500
+ ctx.fillStyle = p2.backgroundColor ?? "#1a1a2e";
47501
+ ctx.fillRect(0, 0, w, h);
47502
+ if (p2.constraints) {
47503
+ for (const c of p2.constraints) {
47504
+ const a = bodies[c.bodyA];
47505
+ const b = bodies[c.bodyB];
47506
+ if (a && b) {
47507
+ const aPos = positions.get(a.id) ?? a;
47508
+ const bPos = positions.get(b.id) ?? b;
47509
+ ctx.beginPath();
47510
+ ctx.moveTo(aPos.x, aPos.y);
47511
+ ctx.lineTo(bPos.x, bPos.y);
47512
+ ctx.strokeStyle = "#533483";
47513
+ ctx.lineWidth = 1;
47514
+ ctx.setLineDash([4, 4]);
47515
+ ctx.stroke();
47516
+ ctx.setLineDash([]);
47517
+ }
47518
+ }
47519
+ }
47520
+ for (const body of bodies) {
47521
+ const pos = positions.get(body.id) ?? body;
47522
+ ctx.beginPath();
47523
+ ctx.arc(pos.x, pos.y, body.radius, 0, Math.PI * 2);
47524
+ ctx.fillStyle = body.color ?? "#e94560";
47525
+ ctx.fill();
47526
+ if (p2.showVelocity) {
47527
+ ctx.beginPath();
47528
+ ctx.moveTo(pos.x, pos.y);
47529
+ ctx.lineTo(pos.x + body.vx * 0.1, pos.y + body.vy * 0.1);
47530
+ ctx.strokeStyle = "#16213e";
47531
+ ctx.lineWidth = 2;
47532
+ ctx.stroke();
47533
+ }
47534
+ }
47535
+ };
47536
+ return interp.startLoop(drawInterpolated);
47537
+ }, [externalBodies !== void 0, interp.startLoop]);
47538
+ React91.useEffect(() => {
47539
+ if (externalBodies !== void 0) return;
47344
47540
  if (!running) return;
47345
47541
  let raf;
47346
47542
  const loop = () => {
@@ -47350,10 +47546,11 @@ function SimulationCanvas({
47350
47546
  };
47351
47547
  raf = requestAnimationFrame(loop);
47352
47548
  return () => cancelAnimationFrame(raf);
47353
- }, [running, step, draw]);
47549
+ }, [running, step, draw, externalBodies]);
47354
47550
  React91.useEffect(() => {
47551
+ if (externalBodies !== void 0) return;
47355
47552
  draw();
47356
- }, [draw]);
47553
+ }, [draw, externalBodies]);
47357
47554
  React91.useEffect(() => {
47358
47555
  if (typeof window === "undefined") return;
47359
47556
  const canvas = canvasRef.current;
@@ -47379,6 +47576,7 @@ var init_SimulationCanvas = __esm({
47379
47576
  init_atoms2();
47380
47577
  init_verificationRegistry();
47381
47578
  init_presets();
47579
+ init_useRenderInterpolation();
47382
47580
  SimulationCanvas.displayName = "SimulationCanvas";
47383
47581
  }
47384
47582
  });