@almadar/ui 5.41.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.
- package/dist/avl/index.cjs +329 -123
- package/dist/avl/index.js +329 -123
- package/dist/components/game/organisms/UncontrolledBattleBoard.d.ts +9 -1
- package/dist/components/game/organisms/physics-sim/SimulationCanvas.d.ts +8 -2
- package/dist/components/game/organisms/puzzles/sequencer/SequencerBoard.d.ts +16 -1
- package/dist/components/index.cjs +329 -123
- package/dist/components/index.js +329 -123
- package/dist/hooks/index.cjs +68 -0
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.js +68 -1
- package/dist/hooks/useRenderInterpolation.d.ts +33 -0
- package/dist/providers/index.cjs +329 -123
- package/dist/providers/index.js +329 -123
- package/dist/runtime/index.cjs +329 -123
- package/dist/runtime/index.js +329 -123
- package/package.json +1 -1
package/dist/avl/index.js
CHANGED
|
@@ -31040,6 +31040,78 @@ var init_GameOverScreen = __esm({
|
|
|
31040
31040
|
GameOverScreen.displayName = "GameOverScreen";
|
|
31041
31041
|
}
|
|
31042
31042
|
});
|
|
31043
|
+
function useRenderInterpolation(options = {}) {
|
|
31044
|
+
const tickIntervalMs = options.tickIntervalMs ?? 1e3 / 30;
|
|
31045
|
+
const prevRef = useRef(null);
|
|
31046
|
+
const currRef = useRef(null);
|
|
31047
|
+
const rafRef = useRef(null);
|
|
31048
|
+
const onSnapshot = useCallback((entities) => {
|
|
31049
|
+
prevRef.current = currRef.current;
|
|
31050
|
+
currRef.current = { entities, arrivedAt: performance.now() };
|
|
31051
|
+
}, []);
|
|
31052
|
+
const getInterpolated = useCallback((now2) => {
|
|
31053
|
+
const out = /* @__PURE__ */ new Map();
|
|
31054
|
+
const curr = currRef.current;
|
|
31055
|
+
if (!curr) return out;
|
|
31056
|
+
const prev = prevRef.current;
|
|
31057
|
+
if (!prev) {
|
|
31058
|
+
for (const e of curr.entities) out.set(e.id, { x: e.x, y: e.y });
|
|
31059
|
+
return out;
|
|
31060
|
+
}
|
|
31061
|
+
const rawAlpha = (now2 - curr.arrivedAt) / tickIntervalMs;
|
|
31062
|
+
const alpha = rawAlpha < 0 ? 0 : rawAlpha > 1 ? 1 : rawAlpha;
|
|
31063
|
+
const prevMap = /* @__PURE__ */ new Map();
|
|
31064
|
+
for (const e of prev.entities) prevMap.set(e.id, e);
|
|
31065
|
+
for (const c of curr.entities) {
|
|
31066
|
+
const p2 = prevMap.get(c.id);
|
|
31067
|
+
if (!p2) {
|
|
31068
|
+
out.set(c.id, { x: c.x, y: c.y });
|
|
31069
|
+
} else {
|
|
31070
|
+
out.set(c.id, {
|
|
31071
|
+
x: p2.x + (c.x - p2.x) * alpha,
|
|
31072
|
+
y: p2.y + (c.y - p2.y) * alpha
|
|
31073
|
+
});
|
|
31074
|
+
}
|
|
31075
|
+
}
|
|
31076
|
+
return out;
|
|
31077
|
+
}, [tickIntervalMs]);
|
|
31078
|
+
const startLoop = useCallback(
|
|
31079
|
+
(draw) => {
|
|
31080
|
+
let active = true;
|
|
31081
|
+
const loop = () => {
|
|
31082
|
+
if (!active) return;
|
|
31083
|
+
try {
|
|
31084
|
+
draw(getInterpolated(performance.now()));
|
|
31085
|
+
} catch {
|
|
31086
|
+
}
|
|
31087
|
+
rafRef.current = requestAnimationFrame(loop);
|
|
31088
|
+
};
|
|
31089
|
+
rafRef.current = requestAnimationFrame(loop);
|
|
31090
|
+
return () => {
|
|
31091
|
+
active = false;
|
|
31092
|
+
if (rafRef.current !== null) {
|
|
31093
|
+
cancelAnimationFrame(rafRef.current);
|
|
31094
|
+
rafRef.current = null;
|
|
31095
|
+
}
|
|
31096
|
+
};
|
|
31097
|
+
},
|
|
31098
|
+
[getInterpolated]
|
|
31099
|
+
);
|
|
31100
|
+
useEffect(() => {
|
|
31101
|
+
return () => {
|
|
31102
|
+
if (rafRef.current !== null) {
|
|
31103
|
+
cancelAnimationFrame(rafRef.current);
|
|
31104
|
+
rafRef.current = null;
|
|
31105
|
+
}
|
|
31106
|
+
};
|
|
31107
|
+
}, []);
|
|
31108
|
+
return { onSnapshot, getInterpolated, startLoop };
|
|
31109
|
+
}
|
|
31110
|
+
var init_useRenderInterpolation = __esm({
|
|
31111
|
+
"hooks/useRenderInterpolation.ts"() {
|
|
31112
|
+
"use client";
|
|
31113
|
+
}
|
|
31114
|
+
});
|
|
31043
31115
|
function PlatformerCanvas({
|
|
31044
31116
|
player,
|
|
31045
31117
|
platforms = [
|
|
@@ -31108,8 +31180,43 @@ function PlatformerCanvas({
|
|
|
31108
31180
|
y: 336,
|
|
31109
31181
|
width: 32,
|
|
31110
31182
|
height: 48,
|
|
31183
|
+
vx: 0,
|
|
31184
|
+
vy: 0,
|
|
31185
|
+
grounded: true,
|
|
31111
31186
|
facingRight: true
|
|
31112
31187
|
};
|
|
31188
|
+
const playerRef = useRef(resolvedPlayer);
|
|
31189
|
+
playerRef.current = resolvedPlayer;
|
|
31190
|
+
const interp = useRenderInterpolation();
|
|
31191
|
+
useEffect(() => {
|
|
31192
|
+
interp.onSnapshot([{ id: "player", x: resolvedPlayer.x, y: resolvedPlayer.y }]);
|
|
31193
|
+
}, [resolvedPlayer.x, resolvedPlayer.y]);
|
|
31194
|
+
const propsRef = useRef({
|
|
31195
|
+
platforms,
|
|
31196
|
+
worldWidth,
|
|
31197
|
+
worldHeight,
|
|
31198
|
+
canvasWidth,
|
|
31199
|
+
canvasHeight,
|
|
31200
|
+
followCamera,
|
|
31201
|
+
bgColor,
|
|
31202
|
+
playerSprite,
|
|
31203
|
+
tileSprites,
|
|
31204
|
+
backgroundImage,
|
|
31205
|
+
assetBaseUrl
|
|
31206
|
+
});
|
|
31207
|
+
propsRef.current = {
|
|
31208
|
+
platforms,
|
|
31209
|
+
worldWidth,
|
|
31210
|
+
worldHeight,
|
|
31211
|
+
canvasWidth,
|
|
31212
|
+
canvasHeight,
|
|
31213
|
+
followCamera,
|
|
31214
|
+
bgColor,
|
|
31215
|
+
playerSprite,
|
|
31216
|
+
tileSprites,
|
|
31217
|
+
backgroundImage,
|
|
31218
|
+
assetBaseUrl
|
|
31219
|
+
};
|
|
31113
31220
|
const handleKeyDown = useCallback((e) => {
|
|
31114
31221
|
if (keysRef.current.has(e.code)) return;
|
|
31115
31222
|
keysRef.current.add(e.code);
|
|
@@ -31158,124 +31265,149 @@ function PlatformerCanvas({
|
|
|
31158
31265
|
canvas.width = canvasWidth * dpr;
|
|
31159
31266
|
canvas.height = canvasHeight * dpr;
|
|
31160
31267
|
ctx.scale(dpr, dpr);
|
|
31161
|
-
|
|
31162
|
-
|
|
31163
|
-
|
|
31164
|
-
|
|
31165
|
-
|
|
31166
|
-
|
|
31167
|
-
|
|
31168
|
-
|
|
31169
|
-
|
|
31170
|
-
|
|
31171
|
-
|
|
31172
|
-
|
|
31173
|
-
|
|
31174
|
-
|
|
31175
|
-
|
|
31176
|
-
|
|
31177
|
-
|
|
31178
|
-
|
|
31179
|
-
|
|
31180
|
-
|
|
31181
|
-
|
|
31182
|
-
|
|
31183
|
-
|
|
31184
|
-
|
|
31185
|
-
|
|
31186
|
-
|
|
31187
|
-
|
|
31188
|
-
|
|
31189
|
-
|
|
31190
|
-
|
|
31191
|
-
|
|
31192
|
-
|
|
31193
|
-
|
|
31194
|
-
|
|
31195
|
-
|
|
31196
|
-
const px = plat.x - camX;
|
|
31197
|
-
const py = plat.y - camY;
|
|
31198
|
-
const platType = plat.type ?? "ground";
|
|
31199
|
-
const spriteUrl = tileSprites?.[platType];
|
|
31200
|
-
const tileImg = spriteUrl ? loadImage(spriteUrl) : null;
|
|
31201
|
-
if (tileImg) {
|
|
31202
|
-
const tileW = tileImg.naturalWidth;
|
|
31203
|
-
const tileH = tileImg.naturalHeight;
|
|
31204
|
-
const scaleH = plat.height / tileH;
|
|
31205
|
-
const scaledW = tileW * scaleH;
|
|
31206
|
-
for (let tx = 0; tx < plat.width; tx += scaledW) {
|
|
31207
|
-
const drawW = Math.min(scaledW, plat.width - tx);
|
|
31208
|
-
const srcW = drawW / scaleH;
|
|
31209
|
-
ctx.drawImage(tileImg, 0, 0, srcW, tileH, px + tx, py, drawW, plat.height);
|
|
31210
|
-
}
|
|
31268
|
+
}, [canvasWidth, canvasHeight]);
|
|
31269
|
+
useEffect(() => {
|
|
31270
|
+
const drawFrame = (positions) => {
|
|
31271
|
+
const canvas = canvasRef.current;
|
|
31272
|
+
if (!canvas) return;
|
|
31273
|
+
const ctx = canvas.getContext("2d");
|
|
31274
|
+
if (!ctx) return;
|
|
31275
|
+
const {
|
|
31276
|
+
platforms: plats,
|
|
31277
|
+
worldWidth: ww,
|
|
31278
|
+
worldHeight: wh,
|
|
31279
|
+
canvasWidth: cw,
|
|
31280
|
+
canvasHeight: ch,
|
|
31281
|
+
followCamera: fc,
|
|
31282
|
+
bgColor: bg,
|
|
31283
|
+
playerSprite: pSprite,
|
|
31284
|
+
tileSprites: tSprites,
|
|
31285
|
+
backgroundImage: bgImg
|
|
31286
|
+
} = propsRef.current;
|
|
31287
|
+
const auth = playerRef.current;
|
|
31288
|
+
const interped = positions.get("player");
|
|
31289
|
+
const px = interped?.x ?? auth.x;
|
|
31290
|
+
const py = interped?.y ?? auth.y;
|
|
31291
|
+
let camX = 0;
|
|
31292
|
+
let camY = 0;
|
|
31293
|
+
if (fc) {
|
|
31294
|
+
camX = Math.max(0, Math.min(px - cw / 2, ww - cw));
|
|
31295
|
+
camY = Math.max(0, Math.min(py - ch / 2 - 50, wh - ch));
|
|
31296
|
+
}
|
|
31297
|
+
const bgImage = bgImg ? loadImage(bgImg) : null;
|
|
31298
|
+
if (bgImage) {
|
|
31299
|
+
ctx.drawImage(bgImage, 0, 0, cw, ch);
|
|
31300
|
+
} else if (bg) {
|
|
31301
|
+
ctx.fillStyle = bg;
|
|
31302
|
+
ctx.fillRect(0, 0, cw, ch);
|
|
31211
31303
|
} else {
|
|
31212
|
-
const
|
|
31213
|
-
|
|
31214
|
-
|
|
31215
|
-
ctx.fillStyle =
|
|
31216
|
-
ctx.fillRect(
|
|
31217
|
-
|
|
31218
|
-
|
|
31219
|
-
|
|
31220
|
-
|
|
31221
|
-
|
|
31222
|
-
|
|
31304
|
+
const grad = ctx.createLinearGradient(0, 0, 0, ch);
|
|
31305
|
+
grad.addColorStop(0, SKY_GRADIENT_TOP);
|
|
31306
|
+
grad.addColorStop(1, SKY_GRADIENT_BOTTOM);
|
|
31307
|
+
ctx.fillStyle = grad;
|
|
31308
|
+
ctx.fillRect(0, 0, cw, ch);
|
|
31309
|
+
}
|
|
31310
|
+
ctx.strokeStyle = GRID_COLOR;
|
|
31311
|
+
ctx.lineWidth = 1;
|
|
31312
|
+
const gridSize = 32;
|
|
31313
|
+
for (let gx = -camX % gridSize; gx < cw; gx += gridSize) {
|
|
31314
|
+
ctx.beginPath();
|
|
31315
|
+
ctx.moveTo(gx, 0);
|
|
31316
|
+
ctx.lineTo(gx, ch);
|
|
31317
|
+
ctx.stroke();
|
|
31318
|
+
}
|
|
31319
|
+
for (let gy = -camY % gridSize; gy < ch; gy += gridSize) {
|
|
31320
|
+
ctx.beginPath();
|
|
31321
|
+
ctx.moveTo(0, gy);
|
|
31322
|
+
ctx.lineTo(cw, gy);
|
|
31323
|
+
ctx.stroke();
|
|
31324
|
+
}
|
|
31325
|
+
for (const plat of plats) {
|
|
31326
|
+
const platX = plat.x - camX;
|
|
31327
|
+
const platY = plat.y - camY;
|
|
31328
|
+
const platType = plat.type ?? "ground";
|
|
31329
|
+
const spriteUrl = tSprites?.[platType];
|
|
31330
|
+
const tileImg = spriteUrl ? loadImage(spriteUrl) : null;
|
|
31331
|
+
if (tileImg) {
|
|
31332
|
+
const tileW = tileImg.naturalWidth;
|
|
31333
|
+
const tileH = tileImg.naturalHeight;
|
|
31334
|
+
const scaleH = plat.height / tileH;
|
|
31335
|
+
const scaledW = tileW * scaleH;
|
|
31336
|
+
for (let tx = 0; tx < plat.width; tx += scaledW) {
|
|
31337
|
+
const drawW = Math.min(scaledW, plat.width - tx);
|
|
31338
|
+
const srcW = drawW / scaleH;
|
|
31339
|
+
ctx.drawImage(tileImg, 0, 0, srcW, tileH, platX + tx, platY, drawW, plat.height);
|
|
31340
|
+
}
|
|
31341
|
+
} else {
|
|
31342
|
+
const color = PLATFORM_COLORS[platType] ?? PLATFORM_COLORS.ground;
|
|
31343
|
+
ctx.fillStyle = color;
|
|
31344
|
+
ctx.fillRect(platX, platY, plat.width, plat.height);
|
|
31345
|
+
ctx.fillStyle = "rgba(255, 255, 255, 0.15)";
|
|
31346
|
+
ctx.fillRect(platX, platY, plat.width, 3);
|
|
31347
|
+
ctx.fillStyle = "rgba(0, 0, 0, 0.3)";
|
|
31348
|
+
ctx.fillRect(platX, platY + plat.height - 2, plat.width, 2);
|
|
31349
|
+
if (platType === "hazard") {
|
|
31350
|
+
ctx.strokeStyle = "#e74c3c";
|
|
31351
|
+
ctx.lineWidth = 2;
|
|
31352
|
+
for (let sx = platX; sx < platX + plat.width; sx += 12) {
|
|
31353
|
+
ctx.beginPath();
|
|
31354
|
+
ctx.moveTo(sx, platY);
|
|
31355
|
+
ctx.lineTo(sx + 6, platY + plat.height);
|
|
31356
|
+
ctx.stroke();
|
|
31357
|
+
}
|
|
31358
|
+
}
|
|
31359
|
+
if (platType === "goal") {
|
|
31360
|
+
ctx.fillStyle = "rgba(241, 196, 15, 0.5)";
|
|
31223
31361
|
ctx.beginPath();
|
|
31224
|
-
ctx.
|
|
31225
|
-
ctx.
|
|
31226
|
-
ctx.stroke();
|
|
31362
|
+
ctx.arc(platX + plat.width / 2, platY - 10, 8, 0, Math.PI * 2);
|
|
31363
|
+
ctx.fill();
|
|
31227
31364
|
}
|
|
31228
31365
|
}
|
|
31229
|
-
if (platType === "goal") {
|
|
31230
|
-
ctx.fillStyle = "rgba(241, 196, 15, 0.5)";
|
|
31231
|
-
ctx.beginPath();
|
|
31232
|
-
ctx.arc(px + plat.width / 2, py - 10, 8, 0, Math.PI * 2);
|
|
31233
|
-
ctx.fill();
|
|
31234
|
-
}
|
|
31235
31366
|
}
|
|
31236
|
-
|
|
31237
|
-
|
|
31238
|
-
|
|
31239
|
-
|
|
31240
|
-
|
|
31241
|
-
|
|
31242
|
-
|
|
31243
|
-
|
|
31244
|
-
|
|
31245
|
-
|
|
31246
|
-
|
|
31247
|
-
|
|
31248
|
-
|
|
31367
|
+
const pw = auth.width ?? 24;
|
|
31368
|
+
const ph = auth.height ?? 32;
|
|
31369
|
+
const ppx = px - camX;
|
|
31370
|
+
const ppy = py - camY;
|
|
31371
|
+
const facingRight = auth.facingRight ?? true;
|
|
31372
|
+
const playerImg = pSprite ? loadImage(pSprite) : null;
|
|
31373
|
+
if (playerImg) {
|
|
31374
|
+
ctx.save();
|
|
31375
|
+
if (!facingRight) {
|
|
31376
|
+
ctx.translate(ppx + pw, ppy);
|
|
31377
|
+
ctx.scale(-1, 1);
|
|
31378
|
+
ctx.drawImage(playerImg, 0, 0, pw, ph);
|
|
31379
|
+
} else {
|
|
31380
|
+
ctx.drawImage(playerImg, ppx, ppy, pw, ph);
|
|
31381
|
+
}
|
|
31382
|
+
ctx.restore();
|
|
31249
31383
|
} else {
|
|
31250
|
-
ctx.
|
|
31384
|
+
ctx.fillStyle = PLAYER_COLOR;
|
|
31385
|
+
const radius = Math.min(pw, ph) * 0.25;
|
|
31386
|
+
ctx.beginPath();
|
|
31387
|
+
ctx.moveTo(ppx + radius, ppy);
|
|
31388
|
+
ctx.lineTo(ppx + pw - radius, ppy);
|
|
31389
|
+
ctx.quadraticCurveTo(ppx + pw, ppy, ppx + pw, ppy + radius);
|
|
31390
|
+
ctx.lineTo(ppx + pw, ppy + ph - radius);
|
|
31391
|
+
ctx.quadraticCurveTo(ppx + pw, ppy + ph, ppx + pw - radius, ppy + ph);
|
|
31392
|
+
ctx.lineTo(ppx + radius, ppy + ph);
|
|
31393
|
+
ctx.quadraticCurveTo(ppx, ppy + ph, ppx, ppy + ph - radius);
|
|
31394
|
+
ctx.lineTo(ppx, ppy + radius);
|
|
31395
|
+
ctx.quadraticCurveTo(ppx, ppy, ppx + radius, ppy);
|
|
31396
|
+
ctx.fill();
|
|
31397
|
+
const eyeY = ppy + ph * 0.3;
|
|
31398
|
+
const eyeSize = 3;
|
|
31399
|
+
const eyeOffsetX = facingRight ? pw * 0.55 : pw * 0.2;
|
|
31400
|
+
ctx.fillStyle = PLAYER_EYE_COLOR;
|
|
31401
|
+
ctx.beginPath();
|
|
31402
|
+
ctx.arc(ppx + eyeOffsetX, eyeY, eyeSize, 0, Math.PI * 2);
|
|
31403
|
+
ctx.fill();
|
|
31404
|
+
ctx.beginPath();
|
|
31405
|
+
ctx.arc(ppx + eyeOffsetX + 7, eyeY, eyeSize, 0, Math.PI * 2);
|
|
31406
|
+
ctx.fill();
|
|
31251
31407
|
}
|
|
31252
|
-
|
|
31253
|
-
|
|
31254
|
-
|
|
31255
|
-
const radius = Math.min(pw, ph) * 0.25;
|
|
31256
|
-
ctx.beginPath();
|
|
31257
|
-
ctx.moveTo(ppx + radius, ppy);
|
|
31258
|
-
ctx.lineTo(ppx + pw - radius, ppy);
|
|
31259
|
-
ctx.quadraticCurveTo(ppx + pw, ppy, ppx + pw, ppy + radius);
|
|
31260
|
-
ctx.lineTo(ppx + pw, ppy + ph - radius);
|
|
31261
|
-
ctx.quadraticCurveTo(ppx + pw, ppy + ph, ppx + pw - radius, ppy + ph);
|
|
31262
|
-
ctx.lineTo(ppx + radius, ppy + ph);
|
|
31263
|
-
ctx.quadraticCurveTo(ppx, ppy + ph, ppx, ppy + ph - radius);
|
|
31264
|
-
ctx.lineTo(ppx, ppy + radius);
|
|
31265
|
-
ctx.quadraticCurveTo(ppx, ppy, ppx + radius, ppy);
|
|
31266
|
-
ctx.fill();
|
|
31267
|
-
const eyeY = ppy + ph * 0.3;
|
|
31268
|
-
const eyeSize = 3;
|
|
31269
|
-
const eyeOffsetX = facingRight ? pw * 0.55 : pw * 0.2;
|
|
31270
|
-
ctx.fillStyle = PLAYER_EYE_COLOR;
|
|
31271
|
-
ctx.beginPath();
|
|
31272
|
-
ctx.arc(ppx + eyeOffsetX, eyeY, eyeSize, 0, Math.PI * 2);
|
|
31273
|
-
ctx.fill();
|
|
31274
|
-
ctx.beginPath();
|
|
31275
|
-
ctx.arc(ppx + eyeOffsetX + 7, eyeY, eyeSize, 0, Math.PI * 2);
|
|
31276
|
-
ctx.fill();
|
|
31277
|
-
}
|
|
31278
|
-
}, [player, platforms, worldWidth, worldHeight, canvasWidth, canvasHeight, followCamera, bgColor, playerSprite, tileSprites, backgroundImage, assetBaseUrl, loadedImages]);
|
|
31408
|
+
};
|
|
31409
|
+
return interp.startLoop(drawFrame);
|
|
31410
|
+
}, [interp.startLoop, loadImage]);
|
|
31279
31411
|
return /* @__PURE__ */ jsx(
|
|
31280
31412
|
"canvas",
|
|
31281
31413
|
{
|
|
@@ -31293,6 +31425,7 @@ var init_PlatformerCanvas = __esm({
|
|
|
31293
31425
|
init_cn();
|
|
31294
31426
|
init_useEventBus();
|
|
31295
31427
|
init_verificationRegistry();
|
|
31428
|
+
init_useRenderInterpolation();
|
|
31296
31429
|
PLATFORM_COLORS = {
|
|
31297
31430
|
ground: "#4a7c59",
|
|
31298
31431
|
platform: "#7c6b4a",
|
|
@@ -31675,13 +31808,13 @@ var init_MapView = __esm({
|
|
|
31675
31808
|
shadowSize: [41, 41]
|
|
31676
31809
|
});
|
|
31677
31810
|
L.Marker.prototype.options.icon = defaultIcon;
|
|
31678
|
-
const { useEffect:
|
|
31811
|
+
const { useEffect: useEffect79, useRef: useRef71, useCallback: useCallback118, useState: useState115 } = React91__default;
|
|
31679
31812
|
const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
|
|
31680
31813
|
const { useEventBus: useEventBus3 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
|
|
31681
31814
|
function MapUpdater({ centerLat, centerLng, zoom }) {
|
|
31682
31815
|
const map = useMap();
|
|
31683
|
-
const prevRef =
|
|
31684
|
-
|
|
31816
|
+
const prevRef = useRef71({ centerLat, centerLng, zoom });
|
|
31817
|
+
useEffect79(() => {
|
|
31685
31818
|
const prev = prevRef.current;
|
|
31686
31819
|
if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
|
|
31687
31820
|
map.setView([centerLat, centerLng], zoom);
|
|
@@ -31692,7 +31825,7 @@ var init_MapView = __esm({
|
|
|
31692
31825
|
}
|
|
31693
31826
|
function MapClickHandler({ onMapClick }) {
|
|
31694
31827
|
const map = useMap();
|
|
31695
|
-
|
|
31828
|
+
useEffect79(() => {
|
|
31696
31829
|
if (!onMapClick) return;
|
|
31697
31830
|
const handler = (e) => {
|
|
31698
31831
|
onMapClick(e.latlng.lat, e.latlng.lng);
|
|
@@ -31721,7 +31854,7 @@ var init_MapView = __esm({
|
|
|
31721
31854
|
}) {
|
|
31722
31855
|
const eventBus = useEventBus3();
|
|
31723
31856
|
const [clickedPosition, setClickedPosition] = useState115(null);
|
|
31724
|
-
const handleMapClick =
|
|
31857
|
+
const handleMapClick = useCallback118((lat, lng) => {
|
|
31725
31858
|
if (showClickedPin) {
|
|
31726
31859
|
setClickedPosition({ lat, lng });
|
|
31727
31860
|
}
|
|
@@ -31730,7 +31863,7 @@ var init_MapView = __esm({
|
|
|
31730
31863
|
eventBus.emit(`UI:${mapClickEvent}`, { latitude: lat, longitude: lng });
|
|
31731
31864
|
}
|
|
31732
31865
|
}, [onMapClick, mapClickEvent, eventBus, showClickedPin]);
|
|
31733
|
-
const handleMarkerClick =
|
|
31866
|
+
const handleMarkerClick = useCallback118((marker) => {
|
|
31734
31867
|
onMarkerClick?.(marker);
|
|
31735
31868
|
if (markerClickEvent) {
|
|
31736
31869
|
eventBus.emit(`UI:${markerClickEvent}`, { ...marker });
|
|
@@ -46825,6 +46958,10 @@ function SequencerBoard({
|
|
|
46825
46958
|
stepDurationMs = 1e3,
|
|
46826
46959
|
playEvent,
|
|
46827
46960
|
completeEvent,
|
|
46961
|
+
placeEvent,
|
|
46962
|
+
removeEvent,
|
|
46963
|
+
checkEvent,
|
|
46964
|
+
playAgainEvent,
|
|
46828
46965
|
className
|
|
46829
46966
|
}) {
|
|
46830
46967
|
const { emit } = useEventBus();
|
|
@@ -46860,7 +46997,8 @@ function SequencerBoard({
|
|
|
46860
46997
|
return next;
|
|
46861
46998
|
});
|
|
46862
46999
|
emit("UI:PLAY_SOUND", { key: "drop_slot" });
|
|
46863
|
-
|
|
47000
|
+
if (placeEvent) emit(`UI:${placeEvent}`, { slotIndex: index, actionId: item.id });
|
|
47001
|
+
}, [emit, placeEvent]);
|
|
46864
47002
|
const handleSlotRemove = useCallback((index) => {
|
|
46865
47003
|
setSlots((prev) => {
|
|
46866
47004
|
const next = [...prev];
|
|
@@ -46873,7 +47011,8 @@ function SequencerBoard({
|
|
|
46873
47011
|
return next;
|
|
46874
47012
|
});
|
|
46875
47013
|
emit("UI:PLAY_SOUND", { key: "back" });
|
|
46876
|
-
|
|
47014
|
+
if (removeEvent) emit(`UI:${removeEvent}`, { slotIndex: index });
|
|
47015
|
+
}, [emit, removeEvent]);
|
|
46877
47016
|
const handleReset = useCallback(() => {
|
|
46878
47017
|
if (timerRef.current) clearTimeout(timerRef.current);
|
|
46879
47018
|
setSlots(Array.from({ length: maxSlots }, () => void 0));
|
|
@@ -46881,7 +47020,8 @@ function SequencerBoard({
|
|
|
46881
47020
|
setCurrentStep(-1);
|
|
46882
47021
|
setAttempts(0);
|
|
46883
47022
|
setSlotFeedback(Array.from({ length: maxSlots }, () => null));
|
|
46884
|
-
|
|
47023
|
+
if (playAgainEvent) emit(`UI:${playAgainEvent}`, {});
|
|
47024
|
+
}, [maxSlots, playAgainEvent, emit]);
|
|
46885
47025
|
const filledSlots = slots.filter((s) => !!s);
|
|
46886
47026
|
const canPlay = filledSlots.length > 0 && playState === "idle";
|
|
46887
47027
|
const handlePlay = useCallback(() => {
|
|
@@ -46903,6 +47043,7 @@ function SequencerBoard({
|
|
|
46903
47043
|
const success = solutions.some(
|
|
46904
47044
|
(sol) => sol.length === playerIds.length && sol.every((id, i) => id === playerIds[i])
|
|
46905
47045
|
);
|
|
47046
|
+
if (checkEvent) emit(`UI:${checkEvent}`, { sequence: playerIds });
|
|
46906
47047
|
if (success) {
|
|
46907
47048
|
setPlayState("success");
|
|
46908
47049
|
setCurrentStep(-1);
|
|
@@ -46930,7 +47071,7 @@ function SequencerBoard({
|
|
|
46930
47071
|
}
|
|
46931
47072
|
};
|
|
46932
47073
|
timerRef.current = setTimeout(advance, stepDurationMs);
|
|
46933
|
-
}, [canPlay, slots, maxSlots, solutions, stepDurationMs, playEvent, completeEvent, emit]);
|
|
47074
|
+
}, [canPlay, slots, maxSlots, solutions, stepDurationMs, playEvent, completeEvent, checkEvent, emit]);
|
|
46934
47075
|
const machine = {
|
|
46935
47076
|
name: str(resolved?.title),
|
|
46936
47077
|
description: str(resolved?.description),
|
|
@@ -47185,11 +47326,13 @@ function SimulationCanvas({
|
|
|
47185
47326
|
height = 400,
|
|
47186
47327
|
running,
|
|
47187
47328
|
speed = 1,
|
|
47329
|
+
bodies: externalBodies,
|
|
47188
47330
|
className
|
|
47189
47331
|
}) {
|
|
47190
47332
|
const preset = useMemo(() => resolvePreset(presetProp), [presetProp]);
|
|
47191
47333
|
const canvasRef = useRef(null);
|
|
47192
47334
|
const bodiesRef = useRef(structuredClone(preset.bodies));
|
|
47335
|
+
const interp = useRenderInterpolation();
|
|
47193
47336
|
useEffect(() => {
|
|
47194
47337
|
bodiesRef.current = structuredClone(preset.bodies);
|
|
47195
47338
|
}, [preset]);
|
|
@@ -47287,6 +47430,67 @@ function SimulationCanvas({
|
|
|
47287
47430
|
}
|
|
47288
47431
|
}, [width, height, preset]);
|
|
47289
47432
|
useEffect(() => {
|
|
47433
|
+
if (!externalBodies) return;
|
|
47434
|
+
interp.onSnapshot(externalBodies.map((b) => ({ id: b.id, x: b.x, y: b.y })));
|
|
47435
|
+
}, [externalBodies]);
|
|
47436
|
+
const presetRef = useRef(preset);
|
|
47437
|
+
presetRef.current = preset;
|
|
47438
|
+
const widthRef = useRef(width);
|
|
47439
|
+
widthRef.current = width;
|
|
47440
|
+
const heightRef = useRef(height);
|
|
47441
|
+
heightRef.current = height;
|
|
47442
|
+
useEffect(() => {
|
|
47443
|
+
if (!externalBodies) return;
|
|
47444
|
+
const drawInterpolated = (positions) => {
|
|
47445
|
+
const canvas = canvasRef.current;
|
|
47446
|
+
if (!canvas) return;
|
|
47447
|
+
const ctx = canvas.getContext("2d");
|
|
47448
|
+
if (!ctx) return;
|
|
47449
|
+
const bodies = bodiesRef.current;
|
|
47450
|
+
const p2 = presetRef.current;
|
|
47451
|
+
const w = widthRef.current;
|
|
47452
|
+
const h = heightRef.current;
|
|
47453
|
+
ctx.clearRect(0, 0, w, h);
|
|
47454
|
+
ctx.fillStyle = p2.backgroundColor ?? "#1a1a2e";
|
|
47455
|
+
ctx.fillRect(0, 0, w, h);
|
|
47456
|
+
if (p2.constraints) {
|
|
47457
|
+
for (const c of p2.constraints) {
|
|
47458
|
+
const a = bodies[c.bodyA];
|
|
47459
|
+
const b = bodies[c.bodyB];
|
|
47460
|
+
if (a && b) {
|
|
47461
|
+
const aPos = positions.get(a.id) ?? a;
|
|
47462
|
+
const bPos = positions.get(b.id) ?? b;
|
|
47463
|
+
ctx.beginPath();
|
|
47464
|
+
ctx.moveTo(aPos.x, aPos.y);
|
|
47465
|
+
ctx.lineTo(bPos.x, bPos.y);
|
|
47466
|
+
ctx.strokeStyle = "#533483";
|
|
47467
|
+
ctx.lineWidth = 1;
|
|
47468
|
+
ctx.setLineDash([4, 4]);
|
|
47469
|
+
ctx.stroke();
|
|
47470
|
+
ctx.setLineDash([]);
|
|
47471
|
+
}
|
|
47472
|
+
}
|
|
47473
|
+
}
|
|
47474
|
+
for (const body of bodies) {
|
|
47475
|
+
const pos = positions.get(body.id) ?? body;
|
|
47476
|
+
ctx.beginPath();
|
|
47477
|
+
ctx.arc(pos.x, pos.y, body.radius, 0, Math.PI * 2);
|
|
47478
|
+
ctx.fillStyle = body.color ?? "#e94560";
|
|
47479
|
+
ctx.fill();
|
|
47480
|
+
if (p2.showVelocity) {
|
|
47481
|
+
ctx.beginPath();
|
|
47482
|
+
ctx.moveTo(pos.x, pos.y);
|
|
47483
|
+
ctx.lineTo(pos.x + body.vx * 0.1, pos.y + body.vy * 0.1);
|
|
47484
|
+
ctx.strokeStyle = "#16213e";
|
|
47485
|
+
ctx.lineWidth = 2;
|
|
47486
|
+
ctx.stroke();
|
|
47487
|
+
}
|
|
47488
|
+
}
|
|
47489
|
+
};
|
|
47490
|
+
return interp.startLoop(drawInterpolated);
|
|
47491
|
+
}, [externalBodies !== void 0, interp.startLoop]);
|
|
47492
|
+
useEffect(() => {
|
|
47493
|
+
if (externalBodies !== void 0) return;
|
|
47290
47494
|
if (!running) return;
|
|
47291
47495
|
let raf;
|
|
47292
47496
|
const loop = () => {
|
|
@@ -47296,10 +47500,11 @@ function SimulationCanvas({
|
|
|
47296
47500
|
};
|
|
47297
47501
|
raf = requestAnimationFrame(loop);
|
|
47298
47502
|
return () => cancelAnimationFrame(raf);
|
|
47299
|
-
}, [running, step, draw]);
|
|
47503
|
+
}, [running, step, draw, externalBodies]);
|
|
47300
47504
|
useEffect(() => {
|
|
47505
|
+
if (externalBodies !== void 0) return;
|
|
47301
47506
|
draw();
|
|
47302
|
-
}, [draw]);
|
|
47507
|
+
}, [draw, externalBodies]);
|
|
47303
47508
|
useEffect(() => {
|
|
47304
47509
|
if (typeof window === "undefined") return;
|
|
47305
47510
|
const canvas = canvasRef.current;
|
|
@@ -47325,6 +47530,7 @@ var init_SimulationCanvas = __esm({
|
|
|
47325
47530
|
init_atoms2();
|
|
47326
47531
|
init_verificationRegistry();
|
|
47327
47532
|
init_presets();
|
|
47533
|
+
init_useRenderInterpolation();
|
|
47328
47534
|
SimulationCanvas.displayName = "SimulationCanvas";
|
|
47329
47535
|
}
|
|
47330
47536
|
});
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* @packageDocumentation
|
|
15
15
|
*/
|
|
16
16
|
import * as React from 'react';
|
|
17
|
-
import type { EntityRow } from '@almadar/core';
|
|
17
|
+
import type { EntityRow, EventEmit } from '@almadar/core';
|
|
18
18
|
import { type BattleBoardProps } from './BattleBoard';
|
|
19
19
|
import type { IsometricTile, IsometricUnit, IsometricFeature } from './types/isometric';
|
|
20
20
|
export interface UncontrolledBattleBoardProps extends Omit<BattleBoardProps, 'entity'> {
|
|
@@ -27,6 +27,14 @@ export interface UncontrolledBattleBoardProps extends Omit<BattleBoardProps, 'en
|
|
|
27
27
|
features?: IsometricFeature[];
|
|
28
28
|
/** Direct asset manifest — takes priority over entity-derived manifest. */
|
|
29
29
|
assetManifest?: BattleBoardProps['assetManifest'];
|
|
30
|
+
/** Emits UI:{stepEvent} with { attackerId, targetId, damage } each battle round */
|
|
31
|
+
stepEvent?: EventEmit<{
|
|
32
|
+
attackerId: string;
|
|
33
|
+
targetId: string;
|
|
34
|
+
damage: number;
|
|
35
|
+
}>;
|
|
36
|
+
/** Emits UI:{playAgainEvent} with {} on restart */
|
|
37
|
+
playAgainEvent?: EventEmit<Record<string, never>>;
|
|
30
38
|
}
|
|
31
39
|
export declare function UncontrolledBattleBoard({ entity, tiles, units, features, assetManifest, ...rest }: UncontrolledBattleBoardProps): React.JSX.Element | null;
|
|
32
40
|
export declare namespace UncontrolledBattleBoard {
|
|
@@ -3,18 +3,24 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Self-contained 2D physics canvas for educational presets.
|
|
5
5
|
* Runs its own Euler integration loop — no external physics hook needed.
|
|
6
|
+
* When `bodies` prop is provided, an external model drives positions at ~30Hz
|
|
7
|
+
* and the interpolation bridge smooths them to 60fps.
|
|
6
8
|
*/
|
|
7
9
|
import React from 'react';
|
|
8
|
-
import type { PhysicsPreset } from './presets/types';
|
|
10
|
+
import type { PhysicsPreset, PhysicsBody } from './presets/types';
|
|
9
11
|
export interface SimulationCanvasProps {
|
|
10
12
|
preset: string | PhysicsPreset;
|
|
11
13
|
width?: number;
|
|
12
14
|
height?: number;
|
|
13
15
|
running: boolean;
|
|
14
16
|
speed?: number;
|
|
17
|
+
/** External model-authoritative body snapshots (~30Hz). When provided,
|
|
18
|
+
* the self-simulation loop is bypassed and positions are interpolated
|
|
19
|
+
* to 60fps between consecutive snapshots. */
|
|
20
|
+
bodies?: readonly Pick<PhysicsBody, 'id' | 'x' | 'y'>[];
|
|
15
21
|
className?: string;
|
|
16
22
|
}
|
|
17
|
-
export declare function SimulationCanvas({ preset: presetProp, width, height, running, speed, className, }: SimulationCanvasProps): React.JSX.Element;
|
|
23
|
+
export declare function SimulationCanvas({ preset: presetProp, width, height, running, speed, bodies: externalBodies, className, }: SimulationCanvasProps): React.JSX.Element;
|
|
18
24
|
export declare namespace SimulationCanvas {
|
|
19
25
|
var displayName: string;
|
|
20
26
|
}
|
|
@@ -42,8 +42,23 @@ export interface SequencerBoardProps extends DisplayStateProps {
|
|
|
42
42
|
success: boolean;
|
|
43
43
|
sequence: string[];
|
|
44
44
|
}>;
|
|
45
|
+
/** Emits UI:{placeEvent} with { slotIndex, actionId } when an action is dropped into a slot */
|
|
46
|
+
placeEvent?: EventEmit<{
|
|
47
|
+
slotIndex: number;
|
|
48
|
+
actionId: string;
|
|
49
|
+
}>;
|
|
50
|
+
/** Emits UI:{removeEvent} with { slotIndex } when an action is removed from a slot */
|
|
51
|
+
removeEvent?: EventEmit<{
|
|
52
|
+
slotIndex: number;
|
|
53
|
+
}>;
|
|
54
|
+
/** Emits UI:{checkEvent} with { sequence } when the player submits the sequence */
|
|
55
|
+
checkEvent?: EventEmit<{
|
|
56
|
+
sequence: string[];
|
|
57
|
+
}>;
|
|
58
|
+
/** Emits UI:{playAgainEvent} with {} on reset */
|
|
59
|
+
playAgainEvent?: EventEmit<Record<string, never>>;
|
|
45
60
|
}
|
|
46
|
-
export declare function SequencerBoard({ entity, categoryColors, stepDurationMs, playEvent, completeEvent, className, }: SequencerBoardProps): React.JSX.Element | null;
|
|
61
|
+
export declare function SequencerBoard({ entity, categoryColors, stepDurationMs, playEvent, completeEvent, placeEvent, removeEvent, checkEvent, playAgainEvent, className, }: SequencerBoardProps): React.JSX.Element | null;
|
|
47
62
|
export declare namespace SequencerBoard {
|
|
48
63
|
var displayName: string;
|
|
49
64
|
}
|