@almadar/ui 5.49.1 → 5.50.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 +498 -214
- package/dist/avl/index.js +500 -216
- package/dist/components/game/molecules/three/index.cjs +251 -3
- package/dist/components/game/molecules/three/index.js +253 -5
- package/dist/components/game/molecules/useUnitSpriteAtlas.d.ts +30 -0
- package/dist/components/game/organisms/types/isometric.d.ts +4 -0
- package/dist/components/game/organisms/types/spriteAnimation.d.ts +24 -0
- package/dist/components/game/organisms/utils/spriteAnimation.d.ts +29 -1
- package/dist/components/index.cjs +377 -141
- package/dist/components/index.js +378 -140
- package/dist/providers/index.cjs +289 -28
- package/dist/providers/index.js +290 -29
- package/dist/runtime/index.cjs +498 -214
- package/dist/runtime/index.js +500 -216
- package/package.json +1 -1
package/dist/avl/index.cjs
CHANGED
|
@@ -39,7 +39,7 @@ var core = require('@almadar/core');
|
|
|
39
39
|
var core$1 = require('@dnd-kit/core');
|
|
40
40
|
var sortable = require('@dnd-kit/sortable');
|
|
41
41
|
var utilities = require('@dnd-kit/utilities');
|
|
42
|
-
var
|
|
42
|
+
var THREE3 = require('three');
|
|
43
43
|
var GLTFLoader_js = require('three/examples/jsm/loaders/GLTFLoader.js');
|
|
44
44
|
var OBJLoader_js = require('three/examples/jsm/loaders/OBJLoader.js');
|
|
45
45
|
var GLTFLoader = require('three/examples/jsm/loaders/GLTFLoader');
|
|
@@ -94,7 +94,7 @@ var ReactMarkdown__default = /*#__PURE__*/_interopDefault(ReactMarkdown);
|
|
|
94
94
|
var remarkGfm__default = /*#__PURE__*/_interopDefault(remarkGfm);
|
|
95
95
|
var remarkMath__default = /*#__PURE__*/_interopDefault(remarkMath);
|
|
96
96
|
var rehypeKatex__default = /*#__PURE__*/_interopDefault(rehypeKatex);
|
|
97
|
-
var
|
|
97
|
+
var THREE3__namespace = /*#__PURE__*/_interopNamespace(THREE3);
|
|
98
98
|
|
|
99
99
|
var __defProp = Object.defineProperty;
|
|
100
100
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
@@ -10839,6 +10839,52 @@ var init_ControlButton = __esm({
|
|
|
10839
10839
|
ControlButton.displayName = "ControlButton";
|
|
10840
10840
|
}
|
|
10841
10841
|
});
|
|
10842
|
+
|
|
10843
|
+
// components/game/organisms/utils/spriteAnimation.ts
|
|
10844
|
+
function inferDirection(dx, dy) {
|
|
10845
|
+
if (dx === 0 && dy === 0) return "se";
|
|
10846
|
+
if (dx >= 0 && dy >= 0) return "se";
|
|
10847
|
+
if (dx <= 0 && dy >= 0) return "sw";
|
|
10848
|
+
if (dx >= 0 && dy <= 0) return "ne";
|
|
10849
|
+
return "nw";
|
|
10850
|
+
}
|
|
10851
|
+
function resolveSheetDirection(facing) {
|
|
10852
|
+
switch (facing) {
|
|
10853
|
+
case "se":
|
|
10854
|
+
return { sheetDir: "se", flipX: false };
|
|
10855
|
+
case "sw":
|
|
10856
|
+
return { sheetDir: "sw", flipX: false };
|
|
10857
|
+
case "ne":
|
|
10858
|
+
return { sheetDir: "sw", flipX: true };
|
|
10859
|
+
case "nw":
|
|
10860
|
+
return { sheetDir: "se", flipX: true };
|
|
10861
|
+
}
|
|
10862
|
+
}
|
|
10863
|
+
function frameRect(frame, row, columns, frameWidth, frameHeight) {
|
|
10864
|
+
return {
|
|
10865
|
+
sx: frame % columns * frameWidth,
|
|
10866
|
+
sy: row * frameHeight,
|
|
10867
|
+
sw: frameWidth,
|
|
10868
|
+
sh: frameHeight
|
|
10869
|
+
};
|
|
10870
|
+
}
|
|
10871
|
+
function getCurrentFrameFromDef(def, elapsed) {
|
|
10872
|
+
const frameDuration = 1e3 / def.frameRate;
|
|
10873
|
+
const totalDuration = def.frames * frameDuration;
|
|
10874
|
+
if (def.loop) {
|
|
10875
|
+
const frame2 = Math.floor(elapsed % totalDuration / frameDuration);
|
|
10876
|
+
return { frame: frame2, finished: false };
|
|
10877
|
+
}
|
|
10878
|
+
if (elapsed >= totalDuration) {
|
|
10879
|
+
return { frame: def.frames - 1, finished: true };
|
|
10880
|
+
}
|
|
10881
|
+
const frame = Math.floor(elapsed / frameDuration);
|
|
10882
|
+
return { frame, finished: false };
|
|
10883
|
+
}
|
|
10884
|
+
var init_spriteAnimation = __esm({
|
|
10885
|
+
"components/game/organisms/utils/spriteAnimation.ts"() {
|
|
10886
|
+
}
|
|
10887
|
+
});
|
|
10842
10888
|
function Sprite({
|
|
10843
10889
|
spritesheet = "https://almadar-kflow-assets.web.app/shared/isometric-blocks/Spritesheet/allTiles_sheet.png",
|
|
10844
10890
|
frameWidth = 64,
|
|
@@ -10859,12 +10905,8 @@ function Sprite({
|
|
|
10859
10905
|
}) {
|
|
10860
10906
|
const eventBus = useEventBus();
|
|
10861
10907
|
const sourcePosition = React90.useMemo(() => {
|
|
10862
|
-
const
|
|
10863
|
-
|
|
10864
|
-
return {
|
|
10865
|
-
x: frameX * frameWidth,
|
|
10866
|
-
y: frameY * frameHeight
|
|
10867
|
-
};
|
|
10908
|
+
const { sx, sy } = frameRect(frame, Math.floor(frame / columns), columns, frameWidth, frameHeight);
|
|
10909
|
+
return { x: sx, y: sy };
|
|
10868
10910
|
}, [frame, columns, frameWidth, frameHeight]);
|
|
10869
10911
|
const transform = React90.useMemo(() => {
|
|
10870
10912
|
const transforms = [
|
|
@@ -10912,6 +10954,7 @@ var init_Sprite = __esm({
|
|
|
10912
10954
|
"components/game/atoms/Sprite.tsx"() {
|
|
10913
10955
|
"use client";
|
|
10914
10956
|
init_useEventBus();
|
|
10957
|
+
init_spriteAnimation();
|
|
10915
10958
|
}
|
|
10916
10959
|
});
|
|
10917
10960
|
function StateIndicator({
|
|
@@ -14303,6 +14346,163 @@ var init_useCamera = __esm({
|
|
|
14303
14346
|
"use client";
|
|
14304
14347
|
}
|
|
14305
14348
|
});
|
|
14349
|
+
function unitAtlasUrl(unit) {
|
|
14350
|
+
if (unit.spriteSheet) return unit.spriteSheet;
|
|
14351
|
+
const sprite = unit.sprite;
|
|
14352
|
+
if (!sprite) return null;
|
|
14353
|
+
const match = /^(.*-sprite-sheet)(?:-(?:se|sw))?(?:-v\d+)?\.png$/.exec(sprite);
|
|
14354
|
+
if (!match) return null;
|
|
14355
|
+
return `${match[1]}.json`;
|
|
14356
|
+
}
|
|
14357
|
+
function resolveSheetUrl(atlasUrl, relativeSheetPath) {
|
|
14358
|
+
try {
|
|
14359
|
+
return new URL(relativeSheetPath, atlasUrl).toString();
|
|
14360
|
+
} catch {
|
|
14361
|
+
const base = atlasUrl.slice(0, atlasUrl.lastIndexOf("/") + 1);
|
|
14362
|
+
return `${base}${relativeSheetPath.replace(/^\.\//, "")}`;
|
|
14363
|
+
}
|
|
14364
|
+
}
|
|
14365
|
+
function useUnitSpriteAtlas(units) {
|
|
14366
|
+
const atlasCacheRef = React90.useRef(/* @__PURE__ */ new Map());
|
|
14367
|
+
const loadingRef = React90.useRef(/* @__PURE__ */ new Set());
|
|
14368
|
+
const [pendingCount, setPendingCount] = React90.useState(0);
|
|
14369
|
+
const [, forceTick] = React90.useState(0);
|
|
14370
|
+
const animStatesRef = React90.useRef(/* @__PURE__ */ new Map());
|
|
14371
|
+
const lastTickRef = React90.useRef(0);
|
|
14372
|
+
const rafRef = React90.useRef(0);
|
|
14373
|
+
const atlasUrls = React90.useMemo(() => {
|
|
14374
|
+
const set = /* @__PURE__ */ new Set();
|
|
14375
|
+
for (const unit of units) {
|
|
14376
|
+
const url = unitAtlasUrl(unit);
|
|
14377
|
+
if (url) set.add(url);
|
|
14378
|
+
}
|
|
14379
|
+
return [...set];
|
|
14380
|
+
}, [units]);
|
|
14381
|
+
React90.useEffect(() => {
|
|
14382
|
+
const cache = atlasCacheRef.current;
|
|
14383
|
+
const loading = loadingRef.current;
|
|
14384
|
+
const toLoad = atlasUrls.filter((url) => !cache.has(url) && !loading.has(url));
|
|
14385
|
+
if (toLoad.length === 0) return;
|
|
14386
|
+
let cancelled = false;
|
|
14387
|
+
setPendingCount((prev) => prev + toLoad.length);
|
|
14388
|
+
for (const url of toLoad) {
|
|
14389
|
+
loading.add(url);
|
|
14390
|
+
fetch(url).then((res) => res.ok ? res.json() : Promise.reject(new Error(String(res.status)))).then((atlas) => {
|
|
14391
|
+
if (cancelled) return;
|
|
14392
|
+
cache.set(url, atlas);
|
|
14393
|
+
}).catch(() => {
|
|
14394
|
+
}).finally(() => {
|
|
14395
|
+
if (cancelled) return;
|
|
14396
|
+
loading.delete(url);
|
|
14397
|
+
setPendingCount((prev) => Math.max(0, prev - 1));
|
|
14398
|
+
forceTick((n) => n + 1);
|
|
14399
|
+
});
|
|
14400
|
+
}
|
|
14401
|
+
return () => {
|
|
14402
|
+
cancelled = true;
|
|
14403
|
+
};
|
|
14404
|
+
}, [atlasUrls]);
|
|
14405
|
+
const sheetUrls = React90.useMemo(() => {
|
|
14406
|
+
const urls = /* @__PURE__ */ new Set();
|
|
14407
|
+
for (const unit of units) {
|
|
14408
|
+
const atlasUrl = unitAtlasUrl(unit);
|
|
14409
|
+
if (!atlasUrl) continue;
|
|
14410
|
+
const atlas = atlasCacheRef.current.get(atlasUrl);
|
|
14411
|
+
if (!atlas) continue;
|
|
14412
|
+
for (const rel of Object.values(atlas.sheets)) {
|
|
14413
|
+
if (rel) urls.add(resolveSheetUrl(atlasUrl, rel));
|
|
14414
|
+
}
|
|
14415
|
+
}
|
|
14416
|
+
return [...urls];
|
|
14417
|
+
}, [units, pendingCount]);
|
|
14418
|
+
React90.useEffect(() => {
|
|
14419
|
+
const hasAtlasUnits = units.some((u) => unitAtlasUrl(u) !== null);
|
|
14420
|
+
if (!hasAtlasUnits) return;
|
|
14421
|
+
let running = true;
|
|
14422
|
+
const tick = (ts) => {
|
|
14423
|
+
if (!running) return;
|
|
14424
|
+
const last = lastTickRef.current || ts;
|
|
14425
|
+
const delta = ts - last;
|
|
14426
|
+
lastTickRef.current = ts;
|
|
14427
|
+
const states = animStatesRef.current;
|
|
14428
|
+
const currentIds = /* @__PURE__ */ new Set();
|
|
14429
|
+
for (const unit of units) {
|
|
14430
|
+
if (unitAtlasUrl(unit) === null) continue;
|
|
14431
|
+
currentIds.add(unit.id);
|
|
14432
|
+
let state = states.get(unit.id);
|
|
14433
|
+
if (!state) {
|
|
14434
|
+
state = { animation: "idle", direction: "se", elapsed: 0, walkHold: 0, prev: null };
|
|
14435
|
+
states.set(unit.id, state);
|
|
14436
|
+
}
|
|
14437
|
+
const posX = unit.position?.x ?? unit.x ?? 0;
|
|
14438
|
+
const posY = unit.position?.y ?? unit.y ?? 0;
|
|
14439
|
+
if (state.prev) {
|
|
14440
|
+
const dx = posX - state.prev.x;
|
|
14441
|
+
const dy = posY - state.prev.y;
|
|
14442
|
+
if (dx !== 0 || dy !== 0) {
|
|
14443
|
+
state.animation = "walk";
|
|
14444
|
+
state.direction = inferDirection(dx, dy);
|
|
14445
|
+
state.walkHold = WALK_HOLD_MS;
|
|
14446
|
+
} else if (state.animation === "walk") {
|
|
14447
|
+
state.walkHold -= delta;
|
|
14448
|
+
if (state.walkHold <= 0) state.animation = "idle";
|
|
14449
|
+
}
|
|
14450
|
+
}
|
|
14451
|
+
state.prev = { x: posX, y: posY };
|
|
14452
|
+
state.elapsed += delta;
|
|
14453
|
+
}
|
|
14454
|
+
for (const id of states.keys()) {
|
|
14455
|
+
if (!currentIds.has(id)) states.delete(id);
|
|
14456
|
+
}
|
|
14457
|
+
rafRef.current = requestAnimationFrame(tick);
|
|
14458
|
+
};
|
|
14459
|
+
rafRef.current = requestAnimationFrame(tick);
|
|
14460
|
+
return () => {
|
|
14461
|
+
running = false;
|
|
14462
|
+
cancelAnimationFrame(rafRef.current);
|
|
14463
|
+
lastTickRef.current = 0;
|
|
14464
|
+
};
|
|
14465
|
+
}, [units]);
|
|
14466
|
+
const resolveUnitFrame = React90.useCallback((unitId) => {
|
|
14467
|
+
const unit = units.find((u) => u.id === unitId);
|
|
14468
|
+
if (!unit) return null;
|
|
14469
|
+
const atlasUrl = unitAtlasUrl(unit);
|
|
14470
|
+
if (!atlasUrl) return null;
|
|
14471
|
+
const atlas = atlasCacheRef.current.get(atlasUrl);
|
|
14472
|
+
if (!atlas) return null;
|
|
14473
|
+
const state = animStatesRef.current.get(unitId);
|
|
14474
|
+
const animation = state?.animation ?? "idle";
|
|
14475
|
+
const direction = state?.direction ?? "se";
|
|
14476
|
+
const elapsed = state?.elapsed ?? 0;
|
|
14477
|
+
const def = atlas.animations[animation] ?? atlas.animations.idle;
|
|
14478
|
+
if (!def) return null;
|
|
14479
|
+
const { sheetDir, flipX } = resolveSheetDirection(direction);
|
|
14480
|
+
const rel = atlas.sheets[sheetDir] ?? atlas.sheets.se ?? atlas.sheets.sw;
|
|
14481
|
+
if (!rel) return null;
|
|
14482
|
+
const sheetUrl = resolveSheetUrl(atlasUrl, rel);
|
|
14483
|
+
const isIdle = animation === "idle";
|
|
14484
|
+
const frame = isIdle ? 0 : getCurrentFrameFromDef(def, elapsed).frame;
|
|
14485
|
+
const rect = frameRect(frame, def.row, atlas.columns, atlas.frameWidth, atlas.frameHeight);
|
|
14486
|
+
return {
|
|
14487
|
+
sheetUrl,
|
|
14488
|
+
sx: rect.sx,
|
|
14489
|
+
sy: rect.sy,
|
|
14490
|
+
sw: rect.sw,
|
|
14491
|
+
sh: rect.sh,
|
|
14492
|
+
flipX,
|
|
14493
|
+
applyBreathing: isIdle
|
|
14494
|
+
};
|
|
14495
|
+
}, [units]);
|
|
14496
|
+
return { sheetUrls, resolveUnitFrame, pendingCount };
|
|
14497
|
+
}
|
|
14498
|
+
var WALK_HOLD_MS;
|
|
14499
|
+
var init_useUnitSpriteAtlas = __esm({
|
|
14500
|
+
"components/game/molecules/useUnitSpriteAtlas.ts"() {
|
|
14501
|
+
"use client";
|
|
14502
|
+
init_spriteAnimation();
|
|
14503
|
+
WALK_HOLD_MS = 600;
|
|
14504
|
+
}
|
|
14505
|
+
});
|
|
14306
14506
|
|
|
14307
14507
|
// components/game/organisms/utils/isometric.ts
|
|
14308
14508
|
function isoToScreen(tileX, tileY, scale, baseOffsetX) {
|
|
@@ -14417,6 +14617,10 @@ function IsometricCanvas({
|
|
|
14417
14617
|
() => unitsProp.map((u) => u.position ? u : { ...u, position: { x: u.x ?? 0, y: u.y ?? 0 } }),
|
|
14418
14618
|
[unitsProp]
|
|
14419
14619
|
);
|
|
14620
|
+
const { sheetUrls: atlasSheetUrls, resolveUnitFrame: resolveUnitFrameInternal, pendingCount: atlasPending } = useUnitSpriteAtlas(units);
|
|
14621
|
+
const resolveFrameForUnit = React90.useCallback((unitId) => {
|
|
14622
|
+
return resolveUnitFrame?.(unitId) ?? resolveUnitFrameInternal(unitId);
|
|
14623
|
+
}, [resolveUnitFrame, resolveUnitFrameInternal]);
|
|
14420
14624
|
const features = React90.useMemo(
|
|
14421
14625
|
() => featuresProp.map((f3) => {
|
|
14422
14626
|
if (f3.type) return f3;
|
|
@@ -14500,9 +14704,10 @@ function IsometricCanvas({
|
|
|
14500
14704
|
}
|
|
14501
14705
|
}
|
|
14502
14706
|
if (effectSpriteUrls) urls.push(...effectSpriteUrls);
|
|
14707
|
+
if (atlasSheetUrls.length) urls.push(...atlasSheetUrls);
|
|
14503
14708
|
if (backgroundImage) urls.push(backgroundImage);
|
|
14504
14709
|
return [...new Set(urls.filter(Boolean))];
|
|
14505
|
-
}, [sortedTiles, features, units, getTerrainSprite, getFeatureSprite, getUnitSprite, effectSpriteUrls, backgroundImage, assetManifest, resolveManifestUrl]);
|
|
14710
|
+
}, [sortedTiles, features, units, getTerrainSprite, getFeatureSprite, getUnitSprite, effectSpriteUrls, atlasSheetUrls, backgroundImage, assetManifest, resolveManifestUrl]);
|
|
14506
14711
|
const { getImage, pendingCount } = useImageCache(spriteUrls);
|
|
14507
14712
|
React90.useEffect(() => {
|
|
14508
14713
|
if (typeof window === "undefined") return;
|
|
@@ -14789,7 +14994,7 @@ function IsometricCanvas({
|
|
|
14789
14994
|
ctx.lineWidth = 3;
|
|
14790
14995
|
ctx.stroke();
|
|
14791
14996
|
}
|
|
14792
|
-
const frame =
|
|
14997
|
+
const frame = resolveFrameForUnit(unit.id);
|
|
14793
14998
|
const frameImg = frame ? getImage(frame.sheetUrl) : null;
|
|
14794
14999
|
if (frame && frameImg) {
|
|
14795
15000
|
const frameAr = frame.sw / frame.sh;
|
|
@@ -14899,7 +15104,7 @@ function IsometricCanvas({
|
|
|
14899
15104
|
resolveTerrainSpriteUrl,
|
|
14900
15105
|
resolveFeatureSpriteUrl,
|
|
14901
15106
|
resolveUnitSpriteUrl,
|
|
14902
|
-
|
|
15107
|
+
resolveFrameForUnit,
|
|
14903
15108
|
getImage,
|
|
14904
15109
|
gridWidth,
|
|
14905
15110
|
gridHeight,
|
|
@@ -14931,7 +15136,7 @@ function IsometricCanvas({
|
|
|
14931
15136
|
};
|
|
14932
15137
|
}, [selectedUnitId, units, scale, baseOffsetX, scaledTileWidth, scaledDiamondTopY, scaledFloorHeight, viewportSize, targetCameraRef]);
|
|
14933
15138
|
React90.useEffect(() => {
|
|
14934
|
-
const hasAnimations = units.length > 0 || validMoves.length > 0 || attackTargets.length > 0 || selectedUnitId != null || targetCameraRef.current != null || hasActiveEffects2 || pendingCount > 0;
|
|
15139
|
+
const hasAnimations = units.length > 0 || validMoves.length > 0 || attackTargets.length > 0 || selectedUnitId != null || targetCameraRef.current != null || hasActiveEffects2 || pendingCount > 0 || atlasPending > 0;
|
|
14935
15140
|
draw(animTimeRef.current);
|
|
14936
15141
|
if (!hasAnimations) return;
|
|
14937
15142
|
let running = true;
|
|
@@ -14947,7 +15152,7 @@ function IsometricCanvas({
|
|
|
14947
15152
|
running = false;
|
|
14948
15153
|
cancelAnimationFrame(rafIdRef.current);
|
|
14949
15154
|
};
|
|
14950
|
-
}, [draw, units.length, validMoves.length, attackTargets.length, selectedUnitId, hasActiveEffects2, pendingCount, lerpToTarget, targetCameraRef]);
|
|
15155
|
+
}, [draw, units.length, validMoves.length, attackTargets.length, selectedUnitId, hasActiveEffects2, pendingCount, atlasPending, lerpToTarget, targetCameraRef]);
|
|
14951
15156
|
const handleMouseMoveWithCamera = React90.useCallback((e) => {
|
|
14952
15157
|
if (enableCamera) {
|
|
14953
15158
|
const wasPanning = handleMouseMove(e, () => draw(animTimeRef.current));
|
|
@@ -15092,6 +15297,7 @@ var init_IsometricCanvas = __esm({
|
|
|
15092
15297
|
init_ErrorState();
|
|
15093
15298
|
init_useImageCache();
|
|
15094
15299
|
init_useCamera();
|
|
15300
|
+
init_useUnitSpriteAtlas();
|
|
15095
15301
|
init_verificationRegistry();
|
|
15096
15302
|
init_isometric();
|
|
15097
15303
|
IsometricCanvas.displayName = "IsometricCanvas";
|
|
@@ -31845,13 +32051,13 @@ var init_MapView = __esm({
|
|
|
31845
32051
|
shadowSize: [41, 41]
|
|
31846
32052
|
});
|
|
31847
32053
|
L.Marker.prototype.options.icon = defaultIcon;
|
|
31848
|
-
const { useEffect:
|
|
32054
|
+
const { useEffect: useEffect85, useRef: useRef77, useCallback: useCallback125, useState: useState121 } = React90__namespace.default;
|
|
31849
32055
|
const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
|
|
31850
32056
|
const { useEventBus: useEventBus4 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
|
|
31851
32057
|
function MapUpdater({ centerLat, centerLng, zoom }) {
|
|
31852
32058
|
const map = useMap();
|
|
31853
|
-
const prevRef =
|
|
31854
|
-
|
|
32059
|
+
const prevRef = useRef77({ centerLat, centerLng, zoom });
|
|
32060
|
+
useEffect85(() => {
|
|
31855
32061
|
const prev = prevRef.current;
|
|
31856
32062
|
if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
|
|
31857
32063
|
map.setView([centerLat, centerLng], zoom);
|
|
@@ -31862,7 +32068,7 @@ var init_MapView = __esm({
|
|
|
31862
32068
|
}
|
|
31863
32069
|
function MapClickHandler({ onMapClick }) {
|
|
31864
32070
|
const map = useMap();
|
|
31865
|
-
|
|
32071
|
+
useEffect85(() => {
|
|
31866
32072
|
if (!onMapClick) return;
|
|
31867
32073
|
const handler = (e) => {
|
|
31868
32074
|
onMapClick(e.latlng.lat, e.latlng.lng);
|
|
@@ -31890,8 +32096,8 @@ var init_MapView = __esm({
|
|
|
31890
32096
|
showAttribution = true
|
|
31891
32097
|
}) {
|
|
31892
32098
|
const eventBus = useEventBus4();
|
|
31893
|
-
const [clickedPosition, setClickedPosition] =
|
|
31894
|
-
const handleMapClick =
|
|
32099
|
+
const [clickedPosition, setClickedPosition] = useState121(null);
|
|
32100
|
+
const handleMapClick = useCallback125((lat, lng) => {
|
|
31895
32101
|
if (showClickedPin) {
|
|
31896
32102
|
setClickedPosition({ lat, lng });
|
|
31897
32103
|
}
|
|
@@ -31900,7 +32106,7 @@ var init_MapView = __esm({
|
|
|
31900
32106
|
eventBus.emit(`UI:${mapClickEvent}`, { latitude: lat, longitude: lng });
|
|
31901
32107
|
}
|
|
31902
32108
|
}, [onMapClick, mapClickEvent, eventBus, showClickedPin]);
|
|
31903
|
-
const handleMarkerClick =
|
|
32109
|
+
const handleMarkerClick = useCallback125((marker) => {
|
|
31904
32110
|
onMarkerClick?.(marker);
|
|
31905
32111
|
if (markerClickEvent) {
|
|
31906
32112
|
eventBus.emit(`UI:${markerClickEvent}`, { ...marker });
|
|
@@ -43209,7 +43415,7 @@ var init_AssetLoader = __esm({
|
|
|
43209
43415
|
__publicField(this, "textureCache");
|
|
43210
43416
|
__publicField(this, "loadingPromises");
|
|
43211
43417
|
this.objLoader = new OBJLoader_js.OBJLoader();
|
|
43212
|
-
this.textureLoader = new
|
|
43418
|
+
this.textureLoader = new THREE3__namespace.TextureLoader();
|
|
43213
43419
|
this.modelCache = /* @__PURE__ */ new Map();
|
|
43214
43420
|
this.textureCache = /* @__PURE__ */ new Map();
|
|
43215
43421
|
this.loadingPromises = /* @__PURE__ */ new Map();
|
|
@@ -43283,7 +43489,7 @@ var init_AssetLoader = __esm({
|
|
|
43283
43489
|
return this.loadingPromises.get(`texture:${url}`);
|
|
43284
43490
|
}
|
|
43285
43491
|
const loadPromise = this.textureLoader.loadAsync(url).then((texture) => {
|
|
43286
|
-
texture.colorSpace =
|
|
43492
|
+
texture.colorSpace = THREE3__namespace.SRGBColorSpace;
|
|
43287
43493
|
this.textureCache.set(url, texture);
|
|
43288
43494
|
this.loadingPromises.delete(`texture:${url}`);
|
|
43289
43495
|
return texture;
|
|
@@ -43357,7 +43563,7 @@ var init_AssetLoader = __esm({
|
|
|
43357
43563
|
});
|
|
43358
43564
|
this.modelCache.forEach((model) => {
|
|
43359
43565
|
model.scene.traverse((child) => {
|
|
43360
|
-
if (child instanceof
|
|
43566
|
+
if (child instanceof THREE3__namespace.Mesh) {
|
|
43361
43567
|
child.geometry.dispose();
|
|
43362
43568
|
if (Array.isArray(child.material)) {
|
|
43363
43569
|
child.material.forEach((m) => m.dispose());
|
|
@@ -43913,7 +44119,7 @@ function ModelLoader({
|
|
|
43913
44119
|
if (!loadedModel) return null;
|
|
43914
44120
|
const cloned = loadedModel.clone();
|
|
43915
44121
|
cloned.traverse((child) => {
|
|
43916
|
-
if (child instanceof
|
|
44122
|
+
if (child instanceof THREE3__namespace.Mesh) {
|
|
43917
44123
|
child.castShadow = castShadow;
|
|
43918
44124
|
child.receiveShadow = receiveShadow;
|
|
43919
44125
|
}
|
|
@@ -44027,6 +44233,47 @@ function CameraController({
|
|
|
44027
44233
|
}, [camera.position, onCameraChange]);
|
|
44028
44234
|
return null;
|
|
44029
44235
|
}
|
|
44236
|
+
function UnitSpriteBillboard({
|
|
44237
|
+
sheetUrl,
|
|
44238
|
+
resolveFrame,
|
|
44239
|
+
height = 1.2
|
|
44240
|
+
}) {
|
|
44241
|
+
const texture = fiber.useLoader(THREE3__namespace.TextureLoader, sheetUrl);
|
|
44242
|
+
const meshRef = React90.useRef(null);
|
|
44243
|
+
const matRef = React90.useRef(null);
|
|
44244
|
+
const [aspect, setAspect] = React90.useState(1);
|
|
44245
|
+
fiber.useFrame(() => {
|
|
44246
|
+
const frame = resolveFrame();
|
|
44247
|
+
if (!frame || !texture.image) return;
|
|
44248
|
+
const imgW = texture.image.width;
|
|
44249
|
+
const imgH = texture.image.height;
|
|
44250
|
+
if (!imgW || !imgH) return;
|
|
44251
|
+
texture.repeat.set((frame.flipX ? -1 : 1) * (frame.sw / imgW), frame.sh / imgH);
|
|
44252
|
+
texture.offset.set(
|
|
44253
|
+
frame.flipX ? (frame.sx + frame.sw) / imgW : frame.sx / imgW,
|
|
44254
|
+
1 - (frame.sy + frame.sh) / imgH
|
|
44255
|
+
);
|
|
44256
|
+
texture.magFilter = THREE3__namespace.NearestFilter;
|
|
44257
|
+
texture.minFilter = THREE3__namespace.NearestFilter;
|
|
44258
|
+
texture.needsUpdate = true;
|
|
44259
|
+
const nextAspect = frame.sw / frame.sh;
|
|
44260
|
+
if (Math.abs(nextAspect - aspect) > 1e-3) setAspect(nextAspect);
|
|
44261
|
+
if (matRef.current) matRef.current.needsUpdate = true;
|
|
44262
|
+
});
|
|
44263
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("mesh", { ref: meshRef, position: [0, height / 2, 0], children: [
|
|
44264
|
+
/* @__PURE__ */ jsxRuntime.jsx("planeGeometry", { args: [height * aspect, height] }),
|
|
44265
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
44266
|
+
"meshBasicMaterial",
|
|
44267
|
+
{
|
|
44268
|
+
ref: matRef,
|
|
44269
|
+
map: texture,
|
|
44270
|
+
transparent: true,
|
|
44271
|
+
alphaTest: 0.1,
|
|
44272
|
+
side: THREE3__namespace.DoubleSide
|
|
44273
|
+
}
|
|
44274
|
+
)
|
|
44275
|
+
] });
|
|
44276
|
+
}
|
|
44030
44277
|
var DEFAULT_GRID_CONFIG, GameCanvas3D;
|
|
44031
44278
|
var init_GameCanvas3D2 = __esm({
|
|
44032
44279
|
"components/game/molecules/GameCanvas3D.tsx"() {
|
|
@@ -44037,6 +44284,7 @@ var init_GameCanvas3D2 = __esm({
|
|
|
44037
44284
|
init_Canvas3DLoadingState2();
|
|
44038
44285
|
init_Canvas3DErrorBoundary2();
|
|
44039
44286
|
init_ModelLoader();
|
|
44287
|
+
init_useUnitSpriteAtlas();
|
|
44040
44288
|
init_cn();
|
|
44041
44289
|
init_GameCanvas3D();
|
|
44042
44290
|
DEFAULT_GRID_CONFIG = {
|
|
@@ -44093,8 +44341,10 @@ var init_GameCanvas3D2 = __esm({
|
|
|
44093
44341
|
const controlsRef = React90.useRef(null);
|
|
44094
44342
|
const [hoveredTile, setHoveredTile] = React90.useState(null);
|
|
44095
44343
|
const [internalError, setInternalError] = React90.useState(null);
|
|
44344
|
+
const { sheetUrls: atlasSheetUrls, resolveUnitFrame } = useUnitSpriteAtlas(units);
|
|
44345
|
+
const preloadUrls = React90.useMemo(() => [...preloadAssets, ...atlasSheetUrls], [preloadAssets, atlasSheetUrls]);
|
|
44096
44346
|
const { isLoading: assetsLoading, progress, loaded, total } = useAssetLoader({
|
|
44097
|
-
preloadUrls
|
|
44347
|
+
preloadUrls,
|
|
44098
44348
|
loader: customAssetLoader
|
|
44099
44349
|
});
|
|
44100
44350
|
const eventHandlers = useGameCanvas3DEvents({
|
|
@@ -44153,7 +44403,7 @@ var init_GameCanvas3D2 = __esm({
|
|
|
44153
44403
|
getCameraPosition: () => {
|
|
44154
44404
|
if (controlsRef.current) {
|
|
44155
44405
|
const pos = controlsRef.current.object.position;
|
|
44156
|
-
return new
|
|
44406
|
+
return new THREE3__namespace.Vector3(pos.x, pos.y, pos.z);
|
|
44157
44407
|
}
|
|
44158
44408
|
return null;
|
|
44159
44409
|
},
|
|
@@ -44305,6 +44555,8 @@ var init_GameCanvas3D2 = __esm({
|
|
|
44305
44555
|
({ unit, position }) => {
|
|
44306
44556
|
const isSelected = selectedUnitId === unit.id;
|
|
44307
44557
|
const color = unit.faction === "player" ? 4491519 : unit.faction === "enemy" ? 16729156 : 16777028;
|
|
44558
|
+
const hasAtlas = unitAtlasUrl(unit) !== null;
|
|
44559
|
+
const initialFrame = hasAtlas ? resolveUnitFrame(unit.id) : null;
|
|
44308
44560
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
44309
44561
|
"group",
|
|
44310
44562
|
{
|
|
@@ -44316,7 +44568,16 @@ var init_GameCanvas3D2 = __esm({
|
|
|
44316
44568
|
/* @__PURE__ */ jsxRuntime.jsx("ringGeometry", { args: [0.4, 0.5, 32] }),
|
|
44317
44569
|
/* @__PURE__ */ jsxRuntime.jsx("meshBasicMaterial", { color: "#ffff00", transparent: true, opacity: 0.8 })
|
|
44318
44570
|
] }),
|
|
44319
|
-
|
|
44571
|
+
hasAtlas && initialFrame ? (
|
|
44572
|
+
/* Animated sprite-sheet billboard — single cropped frame, by state */
|
|
44573
|
+
/* @__PURE__ */ jsxRuntime.jsx(drei.Billboard, { children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
44574
|
+
UnitSpriteBillboard,
|
|
44575
|
+
{
|
|
44576
|
+
sheetUrl: initialFrame.sheetUrl,
|
|
44577
|
+
resolveFrame: () => resolveUnitFrame(unit.id)
|
|
44578
|
+
}
|
|
44579
|
+
) })
|
|
44580
|
+
) : unit.modelUrl ? (
|
|
44320
44581
|
/* GLB unit model (box fallback while loading / on error) */
|
|
44321
44582
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
44322
44583
|
ModelLoader,
|
|
@@ -44370,7 +44631,7 @@ var init_GameCanvas3D2 = __esm({
|
|
|
44370
44631
|
}
|
|
44371
44632
|
);
|
|
44372
44633
|
},
|
|
44373
|
-
[selectedUnitId, handleUnitClick]
|
|
44634
|
+
[selectedUnitId, handleUnitClick, resolveUnitFrame]
|
|
44374
44635
|
);
|
|
44375
44636
|
const DefaultFeatureRenderer = React90.useCallback(
|
|
44376
44637
|
({
|
|
@@ -57144,6 +57405,27 @@ init_verificationRegistry();
|
|
|
57144
57405
|
var crossTraitLog = logger.createLogger("almadar:ui:cross-trait");
|
|
57145
57406
|
var flushLog = logger.createLogger("almadar:ui:slot-flush");
|
|
57146
57407
|
var stateLog = logger.createLogger("almadar:ui:state-transitions");
|
|
57408
|
+
var tickLog = logger.createLogger("almadar:ui:tick-effects");
|
|
57409
|
+
var SYNC_TICK_OPERATORS = /* @__PURE__ */ new Set([
|
|
57410
|
+
"set",
|
|
57411
|
+
"emit",
|
|
57412
|
+
"render-ui",
|
|
57413
|
+
"render",
|
|
57414
|
+
"navigate",
|
|
57415
|
+
"notify",
|
|
57416
|
+
"log",
|
|
57417
|
+
// Synchronous structural forms that wrap sync effects. A tick authored as
|
|
57418
|
+
// a single top-level `(let ((..)) (do (set ..) (render-ui ..)))` or
|
|
57419
|
+
// `(if cond (set ..) ..)` is one of these at its head — the EffectExecutor
|
|
57420
|
+
// resolves the binding values / condition through the canonical evaluator
|
|
57421
|
+
// and runs the wrapped sync effects. Without these in the allow-list the
|
|
57422
|
+
// whole tick is filtered out (its head op isn't `set`/`emit`/...), so the
|
|
57423
|
+
// physics/gameflow/AI tick never runs.
|
|
57424
|
+
"let",
|
|
57425
|
+
"if",
|
|
57426
|
+
"do",
|
|
57427
|
+
"when"
|
|
57428
|
+
]);
|
|
57147
57429
|
function toTraitDefinition(binding) {
|
|
57148
57430
|
return {
|
|
57149
57431
|
name: binding.trait.name,
|
|
@@ -57340,50 +57622,196 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
57340
57622
|
for (const unreg of snapshotUnregs) unreg();
|
|
57341
57623
|
};
|
|
57342
57624
|
}, [traitBindings]);
|
|
57343
|
-
const
|
|
57344
|
-
const
|
|
57345
|
-
|
|
57346
|
-
const
|
|
57347
|
-
|
|
57348
|
-
|
|
57349
|
-
|
|
57350
|
-
|
|
57351
|
-
if (
|
|
57352
|
-
const passed = runtime.interpolateValue(tick.guard, evalCtx);
|
|
57353
|
-
if (!passed) return;
|
|
57354
|
-
}
|
|
57625
|
+
const executeTransitionEffects = React90.useCallback(async (params) => {
|
|
57626
|
+
const { binding, previousState, newState, payload, flushEvent, syncOnly, log: log15 } = params;
|
|
57627
|
+
const traitName = binding.trait.name;
|
|
57628
|
+
const linkedEntity = binding.linkedEntity || "";
|
|
57629
|
+
const entityId = payload?.entityId;
|
|
57630
|
+
const effects = syncOnly ? params.effects.filter(
|
|
57631
|
+
(e) => Array.isArray(e) && SYNC_TICK_OPERATORS.has(String(e[0]))
|
|
57632
|
+
) : params.effects;
|
|
57633
|
+
if (effects.length === 0) return [];
|
|
57355
57634
|
const pendingSlots = /* @__PURE__ */ new Map();
|
|
57356
57635
|
({
|
|
57357
|
-
trait: binding.trait.name,
|
|
57358
|
-
transition: `${currentState}->tick:${tick.name}`,
|
|
57359
|
-
effects: tick.effects,
|
|
57360
57636
|
traitDefinition: binding.trait
|
|
57361
57637
|
});
|
|
57362
|
-
|
|
57363
|
-
|
|
57364
|
-
|
|
57365
|
-
|
|
57366
|
-
|
|
57367
|
-
|
|
57368
|
-
|
|
57638
|
+
const clientHandlers = createClientEffectHandlers({
|
|
57639
|
+
eventBus,
|
|
57640
|
+
slotSetter: {
|
|
57641
|
+
addPattern: (slot, pattern, props) => {
|
|
57642
|
+
const existing = pendingSlots.get(slot) || [];
|
|
57643
|
+
existing.push({ pattern, props: props || {} });
|
|
57644
|
+
pendingSlots.set(slot, existing);
|
|
57645
|
+
},
|
|
57646
|
+
clearSlot: (slot) => {
|
|
57369
57647
|
pendingSlots.set(slot, []);
|
|
57370
|
-
continue;
|
|
57371
57648
|
}
|
|
57372
|
-
|
|
57373
|
-
|
|
57374
|
-
|
|
57375
|
-
|
|
57376
|
-
|
|
57649
|
+
},
|
|
57650
|
+
navigate: optionsRef.current?.navigate,
|
|
57651
|
+
notify: optionsRef.current?.notify,
|
|
57652
|
+
callService: optionsRef.current?.callService
|
|
57653
|
+
});
|
|
57654
|
+
const persistence = syncOnly ? void 0 : optionsRef.current?.persistence;
|
|
57655
|
+
let handlers = clientHandlers;
|
|
57656
|
+
if (persistence) {
|
|
57657
|
+
const sharedBindings = {
|
|
57658
|
+
// Seed `@entity` from the trait's scalar field state (a real
|
|
57659
|
+
// EntityRow), the same source the executor's own bindingCtx
|
|
57660
|
+
// uses below. `@payload.*` resolves from `payload` separately,
|
|
57661
|
+
// so dropping the prior `payload as EntityRow` cast loses
|
|
57662
|
+
// nothing — it just stops mislabelling the payload as an entity.
|
|
57663
|
+
entity: traitFieldStatesRef.current.get(traitName) ?? {},
|
|
57664
|
+
payload: payload || {},
|
|
57665
|
+
state: previousState
|
|
57666
|
+
};
|
|
57667
|
+
const sharedDeclared = runtime.collectDeclaredConfigDefaults(binding.trait);
|
|
57668
|
+
const sharedCallSite = binding.config;
|
|
57669
|
+
if (sharedDeclared || sharedCallSite) {
|
|
57670
|
+
sharedBindings.config = {
|
|
57671
|
+
...sharedDeclared ?? {},
|
|
57672
|
+
...sharedCallSite ?? {}
|
|
57673
|
+
};
|
|
57674
|
+
}
|
|
57675
|
+
const serverHandlers = runtime.createServerEffectHandlers({
|
|
57676
|
+
persistence,
|
|
57677
|
+
eventBus,
|
|
57678
|
+
entityType: linkedEntity,
|
|
57679
|
+
entityId,
|
|
57680
|
+
bindings: sharedBindings,
|
|
57681
|
+
context: {
|
|
57682
|
+
traitName,
|
|
57683
|
+
state: previousState,
|
|
57684
|
+
transition: `${previousState}->${newState}`,
|
|
57685
|
+
linkedEntity,
|
|
57686
|
+
entityId
|
|
57687
|
+
},
|
|
57688
|
+
source: { trait: traitName },
|
|
57689
|
+
callService: optionsRef.current?.callService
|
|
57690
|
+
});
|
|
57691
|
+
handlers = {
|
|
57692
|
+
...serverHandlers,
|
|
57693
|
+
emit: clientHandlers.emit,
|
|
57694
|
+
renderUI: clientHandlers.renderUI,
|
|
57695
|
+
navigate: clientHandlers.navigate,
|
|
57696
|
+
notify: clientHandlers.notify
|
|
57697
|
+
};
|
|
57698
|
+
}
|
|
57699
|
+
const baseSet = handlers.set;
|
|
57700
|
+
handlers = {
|
|
57701
|
+
...handlers,
|
|
57702
|
+
set: async (targetId, field, value) => {
|
|
57703
|
+
let fieldState = traitFieldStatesRef.current.get(traitName);
|
|
57704
|
+
if (!fieldState) {
|
|
57705
|
+
fieldState = {};
|
|
57706
|
+
traitFieldStatesRef.current.set(traitName, fieldState);
|
|
57707
|
+
}
|
|
57708
|
+
fieldState[field] = value;
|
|
57709
|
+
log15.debug("set:write", {
|
|
57710
|
+
traitName,
|
|
57711
|
+
field,
|
|
57712
|
+
value: JSON.stringify(value),
|
|
57713
|
+
transition: `${previousState}->${newState}`
|
|
57714
|
+
});
|
|
57715
|
+
if (baseSet) await baseSet(targetId, field, value);
|
|
57377
57716
|
}
|
|
57717
|
+
};
|
|
57718
|
+
const entityForBinding = traitFieldStatesRef.current.get(traitName) ?? {};
|
|
57719
|
+
const bindingCtx = {
|
|
57720
|
+
entity: entityForBinding,
|
|
57721
|
+
payload: payload || {},
|
|
57722
|
+
state: previousState
|
|
57723
|
+
};
|
|
57724
|
+
const declaredDefaults = runtime.collectDeclaredConfigDefaults(binding.trait);
|
|
57725
|
+
const callSiteConfig = binding.config;
|
|
57726
|
+
if (declaredDefaults || callSiteConfig) {
|
|
57727
|
+
bindingCtx.config = {
|
|
57728
|
+
...declaredDefaults ?? {},
|
|
57729
|
+
...callSiteConfig ?? {}
|
|
57730
|
+
};
|
|
57378
57731
|
}
|
|
57379
|
-
|
|
57380
|
-
|
|
57381
|
-
|
|
57382
|
-
|
|
57383
|
-
|
|
57732
|
+
const effectContext = {
|
|
57733
|
+
traitName,
|
|
57734
|
+
state: previousState,
|
|
57735
|
+
transition: `${previousState}->${newState}`,
|
|
57736
|
+
linkedEntity,
|
|
57737
|
+
entityId
|
|
57738
|
+
};
|
|
57739
|
+
const emittedDuringExec = [];
|
|
57740
|
+
const baseEmit = handlers.emit;
|
|
57741
|
+
const trackingHandlers = {
|
|
57742
|
+
...handlers,
|
|
57743
|
+
emit: (event, eventPayload, source) => {
|
|
57744
|
+
emittedDuringExec.push(event);
|
|
57745
|
+
baseEmit(event, eventPayload, source);
|
|
57746
|
+
}
|
|
57747
|
+
};
|
|
57748
|
+
const executor = new runtime.EffectExecutor({ handlers: trackingHandlers, bindings: bindingCtx, context: effectContext });
|
|
57749
|
+
try {
|
|
57750
|
+
await executor.executeAll(effects);
|
|
57751
|
+
log15.debug("effects:executed", () => ({
|
|
57752
|
+
traitName,
|
|
57753
|
+
transition: `${previousState}->${newState}`,
|
|
57754
|
+
event: flushEvent,
|
|
57755
|
+
effectCount: effects.length,
|
|
57756
|
+
emitted: emittedDuringExec.join(","),
|
|
57757
|
+
entityAfter: JSON.stringify(traitFieldStatesRef.current.get(traitName) ?? {}),
|
|
57758
|
+
slotsTouched: Array.from(pendingSlots.keys()).join(",")
|
|
57759
|
+
}));
|
|
57760
|
+
for (const [slot, patterns] of pendingSlots) {
|
|
57761
|
+
log15.debug("flush:slot", {
|
|
57762
|
+
traitName,
|
|
57763
|
+
slot,
|
|
57764
|
+
patternCount: patterns.length,
|
|
57765
|
+
event: flushEvent,
|
|
57766
|
+
transition: `${previousState}->${newState}`,
|
|
57767
|
+
cleared: patterns.length === 0
|
|
57768
|
+
});
|
|
57769
|
+
flushSlot(traitName, slot, patterns, {
|
|
57770
|
+
event: flushEvent,
|
|
57771
|
+
state: previousState,
|
|
57772
|
+
entity: binding.linkedEntity
|
|
57773
|
+
});
|
|
57774
|
+
}
|
|
57775
|
+
} catch (error) {
|
|
57776
|
+
log15.error("effects:error", {
|
|
57777
|
+
traitName,
|
|
57778
|
+
transition: `${previousState}->${newState}`,
|
|
57779
|
+
event: flushEvent,
|
|
57780
|
+
error: error instanceof Error ? error.message : String(error),
|
|
57781
|
+
effectCount: effects.length
|
|
57384
57782
|
});
|
|
57385
57783
|
}
|
|
57386
|
-
|
|
57784
|
+
return emittedDuringExec;
|
|
57785
|
+
}, [eventBus, flushSlot]);
|
|
57786
|
+
const runTickEffects = React90.useCallback((tick, binding) => {
|
|
57787
|
+
const traitName = binding.trait.name;
|
|
57788
|
+
const currentState = traitStatesRef.current.get(traitName)?.currentState ?? "";
|
|
57789
|
+
if (tick.appliesTo.length > 0 && !tick.appliesTo.includes(currentState)) return;
|
|
57790
|
+
if (tick.guard !== void 0) {
|
|
57791
|
+
const guardCtx = {
|
|
57792
|
+
entity: traitFieldStatesRef.current.get(traitName) ?? {},
|
|
57793
|
+
payload: {},
|
|
57794
|
+
state: currentState
|
|
57795
|
+
};
|
|
57796
|
+
if (binding.config) {
|
|
57797
|
+
guardCtx.config = binding.config;
|
|
57798
|
+
}
|
|
57799
|
+
const passed = runtime.interpolateValue(tick.guard, runtime.createContextFromBindings(guardCtx));
|
|
57800
|
+
if (!passed) {
|
|
57801
|
+
tickLog.debug("guard-blocked", { traitName, tick: tick.name, state: currentState });
|
|
57802
|
+
return;
|
|
57803
|
+
}
|
|
57804
|
+
}
|
|
57805
|
+
void executeTransitionEffects({
|
|
57806
|
+
binding,
|
|
57807
|
+
effects: tick.effects,
|
|
57808
|
+
previousState: currentState,
|
|
57809
|
+
newState: currentState,
|
|
57810
|
+
flushEvent: `tick:${tick.name}`,
|
|
57811
|
+
syncOnly: true,
|
|
57812
|
+
log: tickLog
|
|
57813
|
+
});
|
|
57814
|
+
}, [executeTransitionEffects]);
|
|
57387
57815
|
React90.useEffect(() => {
|
|
57388
57816
|
const hasFrameTicks = traitBindingsRef.current.some(
|
|
57389
57817
|
(b) => b.trait.ticks?.some((t) => t.interval === "frame")
|
|
@@ -57494,161 +57922,17 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
57494
57922
|
transition: `${result.previousState} -> ${result.newState}`,
|
|
57495
57923
|
effects: JSON.stringify(result.effects)
|
|
57496
57924
|
}));
|
|
57497
|
-
const
|
|
57498
|
-
|
|
57499
|
-
const pendingSlots = /* @__PURE__ */ new Map();
|
|
57500
|
-
const slotSource = {
|
|
57501
|
-
trait: binding.trait.name,
|
|
57502
|
-
state: result.previousState,
|
|
57503
|
-
transition: `${result.previousState}->${result.newState}`,
|
|
57925
|
+
const emittedDuringExec = await executeTransitionEffects({
|
|
57926
|
+
binding,
|
|
57504
57927
|
effects: result.effects,
|
|
57505
|
-
|
|
57506
|
-
|
|
57507
|
-
|
|
57508
|
-
|
|
57509
|
-
|
|
57510
|
-
|
|
57511
|
-
const existing = pendingSlots.get(slot) || [];
|
|
57512
|
-
existing.push({ pattern, props: props || {} });
|
|
57513
|
-
pendingSlots.set(slot, existing);
|
|
57514
|
-
},
|
|
57515
|
-
clearSlot: (slot) => {
|
|
57516
|
-
pendingSlots.set(slot, []);
|
|
57517
|
-
}
|
|
57518
|
-
},
|
|
57519
|
-
navigate: optionsRef.current?.navigate,
|
|
57520
|
-
notify: optionsRef.current?.notify,
|
|
57521
|
-
callService: optionsRef.current?.callService
|
|
57928
|
+
previousState: result.previousState,
|
|
57929
|
+
newState: result.newState,
|
|
57930
|
+
payload,
|
|
57931
|
+
flushEvent: eventKey,
|
|
57932
|
+
syncOnly: false,
|
|
57933
|
+
log: stateLog
|
|
57522
57934
|
});
|
|
57523
|
-
const persistence = optionsRef.current?.persistence;
|
|
57524
|
-
let handlers = clientHandlers;
|
|
57525
|
-
if (persistence) {
|
|
57526
|
-
const sharedBindings = {
|
|
57527
|
-
entity: payload ?? {},
|
|
57528
|
-
payload: payload || {},
|
|
57529
|
-
state: result.previousState
|
|
57530
|
-
};
|
|
57531
|
-
const sharedDeclared = runtime.collectDeclaredConfigDefaults(
|
|
57532
|
-
binding.trait
|
|
57533
|
-
);
|
|
57534
|
-
const sharedCallSite = binding.config;
|
|
57535
|
-
if (sharedDeclared || sharedCallSite) {
|
|
57536
|
-
sharedBindings.config = {
|
|
57537
|
-
...sharedDeclared ?? {},
|
|
57538
|
-
...sharedCallSite ?? {}
|
|
57539
|
-
};
|
|
57540
|
-
}
|
|
57541
|
-
const serverHandlers = runtime.createServerEffectHandlers({
|
|
57542
|
-
persistence,
|
|
57543
|
-
eventBus,
|
|
57544
|
-
entityType: linkedEntity,
|
|
57545
|
-
entityId,
|
|
57546
|
-
bindings: sharedBindings,
|
|
57547
|
-
context: {
|
|
57548
|
-
traitName: binding.trait.name,
|
|
57549
|
-
state: result.previousState,
|
|
57550
|
-
transition: `${result.previousState}->${result.newState}`,
|
|
57551
|
-
linkedEntity,
|
|
57552
|
-
entityId
|
|
57553
|
-
},
|
|
57554
|
-
source: { trait: binding.trait.name },
|
|
57555
|
-
callService: optionsRef.current?.callService
|
|
57556
|
-
});
|
|
57557
|
-
handlers = {
|
|
57558
|
-
...serverHandlers,
|
|
57559
|
-
// Client handlers own UI + emit: keep the slot setter
|
|
57560
|
-
// and pre-prefixed UI:* emit path intact.
|
|
57561
|
-
emit: clientHandlers.emit,
|
|
57562
|
-
renderUI: clientHandlers.renderUI,
|
|
57563
|
-
navigate: clientHandlers.navigate,
|
|
57564
|
-
notify: clientHandlers.notify
|
|
57565
|
-
};
|
|
57566
|
-
}
|
|
57567
|
-
const baseSet = handlers.set;
|
|
57568
|
-
handlers = {
|
|
57569
|
-
...handlers,
|
|
57570
|
-
set: async (targetId, field, value) => {
|
|
57571
|
-
let fieldState = traitFieldStatesRef.current.get(traitName);
|
|
57572
|
-
if (!fieldState) {
|
|
57573
|
-
fieldState = {};
|
|
57574
|
-
traitFieldStatesRef.current.set(traitName, fieldState);
|
|
57575
|
-
}
|
|
57576
|
-
fieldState[field] = value;
|
|
57577
|
-
if (baseSet) await baseSet(targetId, field, value);
|
|
57578
|
-
}
|
|
57579
|
-
};
|
|
57580
|
-
const entityForBinding = traitFieldStatesRef.current.get(traitName) ?? {};
|
|
57581
|
-
const bindingCtx = {
|
|
57582
|
-
entity: entityForBinding,
|
|
57583
|
-
payload: payload || {},
|
|
57584
|
-
state: result.previousState
|
|
57585
|
-
};
|
|
57586
|
-
const declaredDefaults = runtime.collectDeclaredConfigDefaults(
|
|
57587
|
-
binding.trait
|
|
57588
|
-
);
|
|
57589
|
-
const callSiteConfig = binding.config;
|
|
57590
|
-
if (declaredDefaults || callSiteConfig) {
|
|
57591
|
-
bindingCtx.config = {
|
|
57592
|
-
...declaredDefaults ?? {},
|
|
57593
|
-
...callSiteConfig ?? {}
|
|
57594
|
-
};
|
|
57595
|
-
}
|
|
57596
|
-
const effectContext = {
|
|
57597
|
-
traitName: binding.trait.name,
|
|
57598
|
-
state: result.previousState,
|
|
57599
|
-
transition: `${result.previousState}->${result.newState}`,
|
|
57600
|
-
linkedEntity,
|
|
57601
|
-
entityId
|
|
57602
|
-
};
|
|
57603
|
-
const emittedDuringExec = [];
|
|
57604
57935
|
emittedByTrait.set(traitName, emittedDuringExec);
|
|
57605
|
-
const baseEmit = handlers.emit;
|
|
57606
|
-
const trackingHandlers = {
|
|
57607
|
-
...handlers,
|
|
57608
|
-
emit: (event, eventPayload, source) => {
|
|
57609
|
-
emittedDuringExec.push(event);
|
|
57610
|
-
baseEmit(event, eventPayload, source);
|
|
57611
|
-
}
|
|
57612
|
-
};
|
|
57613
|
-
const executor = new runtime.EffectExecutor({ handlers: trackingHandlers, bindings: bindingCtx, context: effectContext });
|
|
57614
|
-
try {
|
|
57615
|
-
await executor.executeAll(result.effects);
|
|
57616
|
-
stateLog.debug("transition:render-ui-dispatched", () => ({
|
|
57617
|
-
traitName,
|
|
57618
|
-
fromState: result.previousState,
|
|
57619
|
-
toState: result.newState,
|
|
57620
|
-
event: eventKey,
|
|
57621
|
-
slotsTouched: Array.from(pendingSlots.keys()).join(","),
|
|
57622
|
-
patternTypes: Array.from(pendingSlots.entries()).map(
|
|
57623
|
-
([slot, patterns]) => `${slot}:[${patterns.map((p2) => p2.pattern?.type ?? "null").join(",")}]`
|
|
57624
|
-
).join(";")
|
|
57625
|
-
}));
|
|
57626
|
-
void slotSource;
|
|
57627
|
-
for (const [slot, patterns] of pendingSlots) {
|
|
57628
|
-
stateLog.debug("flush:slot", {
|
|
57629
|
-
traitName,
|
|
57630
|
-
slot,
|
|
57631
|
-
patternCount: patterns.length,
|
|
57632
|
-
event: eventKey,
|
|
57633
|
-
transition: `${result.previousState}->${result.newState}`,
|
|
57634
|
-
cleared: patterns.length === 0
|
|
57635
|
-
});
|
|
57636
|
-
flushSlot(traitName, slot, patterns, {
|
|
57637
|
-
event: eventKey,
|
|
57638
|
-
state: result.previousState,
|
|
57639
|
-
entity: binding.linkedEntity
|
|
57640
|
-
});
|
|
57641
|
-
}
|
|
57642
|
-
} catch (error) {
|
|
57643
|
-
stateLog.error("transition:effect-error", {
|
|
57644
|
-
traitName,
|
|
57645
|
-
fromState: result.previousState,
|
|
57646
|
-
toState: result.newState,
|
|
57647
|
-
event: eventKey,
|
|
57648
|
-
error: error instanceof Error ? error.message : String(error),
|
|
57649
|
-
effectCount: result.effects.length
|
|
57650
|
-
});
|
|
57651
|
-
}
|
|
57652
57936
|
} else if (!result.executed) {
|
|
57653
57937
|
if (result.guardResult === false) {
|
|
57654
57938
|
stateLog.debug("guard-blocked-transition", {
|