@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/runtime/index.js
CHANGED
|
@@ -41,14 +41,14 @@ import { DndContext, pointerWithin, rectIntersection, closestCorners, useSensors
|
|
|
41
41
|
import { useSortable, arrayMove, sortableKeyboardCoordinates, SortableContext, rectSortingStrategy, verticalListSortingStrategy } from '@dnd-kit/sortable';
|
|
42
42
|
import { CSS } from '@dnd-kit/utilities';
|
|
43
43
|
import { useNodeId, ReactFlowProvider, Handle, Position } from '@xyflow/react';
|
|
44
|
-
import * as
|
|
44
|
+
import * as THREE3 from 'three';
|
|
45
45
|
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
|
|
46
46
|
import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js';
|
|
47
47
|
import { GLTFLoader as GLTFLoader$1 } from 'three/examples/jsm/loaders/GLTFLoader';
|
|
48
|
-
import { Canvas, useThree } from '@react-three/fiber';
|
|
49
|
-
import { Grid as Grid$1, OrbitControls } from '@react-three/drei';
|
|
48
|
+
import { Canvas, useLoader, useFrame, useThree } from '@react-three/fiber';
|
|
49
|
+
import { Billboard, Grid as Grid$1, OrbitControls } from '@react-three/drei';
|
|
50
50
|
import { getPatternDefinition, getComponentForPattern as getComponentForPattern$1 } from '@almadar/patterns';
|
|
51
|
-
import { StateMachineManager,
|
|
51
|
+
import { StateMachineManager, collectDeclaredConfigDefaults, createServerEffectHandlers, EffectExecutor, interpolateValue, createContextFromBindings, InMemoryPersistence } from '@almadar/runtime';
|
|
52
52
|
import { OrbitalServerRuntime } from '@almadar/runtime/OrbitalServerRuntime';
|
|
53
53
|
|
|
54
54
|
var __defProp = Object.defineProperty;
|
|
@@ -7053,6 +7053,52 @@ var init_ControlButton = __esm({
|
|
|
7053
7053
|
ControlButton.displayName = "ControlButton";
|
|
7054
7054
|
}
|
|
7055
7055
|
});
|
|
7056
|
+
|
|
7057
|
+
// components/game/organisms/utils/spriteAnimation.ts
|
|
7058
|
+
function inferDirection(dx, dy) {
|
|
7059
|
+
if (dx === 0 && dy === 0) return "se";
|
|
7060
|
+
if (dx >= 0 && dy >= 0) return "se";
|
|
7061
|
+
if (dx <= 0 && dy >= 0) return "sw";
|
|
7062
|
+
if (dx >= 0 && dy <= 0) return "ne";
|
|
7063
|
+
return "nw";
|
|
7064
|
+
}
|
|
7065
|
+
function resolveSheetDirection(facing) {
|
|
7066
|
+
switch (facing) {
|
|
7067
|
+
case "se":
|
|
7068
|
+
return { sheetDir: "se", flipX: false };
|
|
7069
|
+
case "sw":
|
|
7070
|
+
return { sheetDir: "sw", flipX: false };
|
|
7071
|
+
case "ne":
|
|
7072
|
+
return { sheetDir: "sw", flipX: true };
|
|
7073
|
+
case "nw":
|
|
7074
|
+
return { sheetDir: "se", flipX: true };
|
|
7075
|
+
}
|
|
7076
|
+
}
|
|
7077
|
+
function frameRect(frame, row, columns, frameWidth, frameHeight) {
|
|
7078
|
+
return {
|
|
7079
|
+
sx: frame % columns * frameWidth,
|
|
7080
|
+
sy: row * frameHeight,
|
|
7081
|
+
sw: frameWidth,
|
|
7082
|
+
sh: frameHeight
|
|
7083
|
+
};
|
|
7084
|
+
}
|
|
7085
|
+
function getCurrentFrameFromDef(def, elapsed) {
|
|
7086
|
+
const frameDuration = 1e3 / def.frameRate;
|
|
7087
|
+
const totalDuration = def.frames * frameDuration;
|
|
7088
|
+
if (def.loop) {
|
|
7089
|
+
const frame2 = Math.floor(elapsed % totalDuration / frameDuration);
|
|
7090
|
+
return { frame: frame2, finished: false };
|
|
7091
|
+
}
|
|
7092
|
+
if (elapsed >= totalDuration) {
|
|
7093
|
+
return { frame: def.frames - 1, finished: true };
|
|
7094
|
+
}
|
|
7095
|
+
const frame = Math.floor(elapsed / frameDuration);
|
|
7096
|
+
return { frame, finished: false };
|
|
7097
|
+
}
|
|
7098
|
+
var init_spriteAnimation = __esm({
|
|
7099
|
+
"components/game/organisms/utils/spriteAnimation.ts"() {
|
|
7100
|
+
}
|
|
7101
|
+
});
|
|
7056
7102
|
function Sprite({
|
|
7057
7103
|
spritesheet = "https://almadar-kflow-assets.web.app/shared/isometric-blocks/Spritesheet/allTiles_sheet.png",
|
|
7058
7104
|
frameWidth = 64,
|
|
@@ -7073,12 +7119,8 @@ function Sprite({
|
|
|
7073
7119
|
}) {
|
|
7074
7120
|
const eventBus = useEventBus();
|
|
7075
7121
|
const sourcePosition = useMemo(() => {
|
|
7076
|
-
const
|
|
7077
|
-
|
|
7078
|
-
return {
|
|
7079
|
-
x: frameX * frameWidth,
|
|
7080
|
-
y: frameY * frameHeight
|
|
7081
|
-
};
|
|
7122
|
+
const { sx, sy } = frameRect(frame, Math.floor(frame / columns), columns, frameWidth, frameHeight);
|
|
7123
|
+
return { x: sx, y: sy };
|
|
7082
7124
|
}, [frame, columns, frameWidth, frameHeight]);
|
|
7083
7125
|
const transform = useMemo(() => {
|
|
7084
7126
|
const transforms = [
|
|
@@ -7126,6 +7168,7 @@ var init_Sprite = __esm({
|
|
|
7126
7168
|
"components/game/atoms/Sprite.tsx"() {
|
|
7127
7169
|
"use client";
|
|
7128
7170
|
init_useEventBus();
|
|
7171
|
+
init_spriteAnimation();
|
|
7129
7172
|
}
|
|
7130
7173
|
});
|
|
7131
7174
|
function StateIndicator({
|
|
@@ -10517,6 +10560,163 @@ var init_useCamera = __esm({
|
|
|
10517
10560
|
"use client";
|
|
10518
10561
|
}
|
|
10519
10562
|
});
|
|
10563
|
+
function unitAtlasUrl(unit) {
|
|
10564
|
+
if (unit.spriteSheet) return unit.spriteSheet;
|
|
10565
|
+
const sprite = unit.sprite;
|
|
10566
|
+
if (!sprite) return null;
|
|
10567
|
+
const match = /^(.*-sprite-sheet)(?:-(?:se|sw))?(?:-v\d+)?\.png$/.exec(sprite);
|
|
10568
|
+
if (!match) return null;
|
|
10569
|
+
return `${match[1]}.json`;
|
|
10570
|
+
}
|
|
10571
|
+
function resolveSheetUrl(atlasUrl, relativeSheetPath) {
|
|
10572
|
+
try {
|
|
10573
|
+
return new URL(relativeSheetPath, atlasUrl).toString();
|
|
10574
|
+
} catch {
|
|
10575
|
+
const base = atlasUrl.slice(0, atlasUrl.lastIndexOf("/") + 1);
|
|
10576
|
+
return `${base}${relativeSheetPath.replace(/^\.\//, "")}`;
|
|
10577
|
+
}
|
|
10578
|
+
}
|
|
10579
|
+
function useUnitSpriteAtlas(units) {
|
|
10580
|
+
const atlasCacheRef = useRef(/* @__PURE__ */ new Map());
|
|
10581
|
+
const loadingRef = useRef(/* @__PURE__ */ new Set());
|
|
10582
|
+
const [pendingCount, setPendingCount] = useState(0);
|
|
10583
|
+
const [, forceTick] = useState(0);
|
|
10584
|
+
const animStatesRef = useRef(/* @__PURE__ */ new Map());
|
|
10585
|
+
const lastTickRef = useRef(0);
|
|
10586
|
+
const rafRef = useRef(0);
|
|
10587
|
+
const atlasUrls = useMemo(() => {
|
|
10588
|
+
const set = /* @__PURE__ */ new Set();
|
|
10589
|
+
for (const unit of units) {
|
|
10590
|
+
const url = unitAtlasUrl(unit);
|
|
10591
|
+
if (url) set.add(url);
|
|
10592
|
+
}
|
|
10593
|
+
return [...set];
|
|
10594
|
+
}, [units]);
|
|
10595
|
+
useEffect(() => {
|
|
10596
|
+
const cache = atlasCacheRef.current;
|
|
10597
|
+
const loading = loadingRef.current;
|
|
10598
|
+
const toLoad = atlasUrls.filter((url) => !cache.has(url) && !loading.has(url));
|
|
10599
|
+
if (toLoad.length === 0) return;
|
|
10600
|
+
let cancelled = false;
|
|
10601
|
+
setPendingCount((prev) => prev + toLoad.length);
|
|
10602
|
+
for (const url of toLoad) {
|
|
10603
|
+
loading.add(url);
|
|
10604
|
+
fetch(url).then((res) => res.ok ? res.json() : Promise.reject(new Error(String(res.status)))).then((atlas) => {
|
|
10605
|
+
if (cancelled) return;
|
|
10606
|
+
cache.set(url, atlas);
|
|
10607
|
+
}).catch(() => {
|
|
10608
|
+
}).finally(() => {
|
|
10609
|
+
if (cancelled) return;
|
|
10610
|
+
loading.delete(url);
|
|
10611
|
+
setPendingCount((prev) => Math.max(0, prev - 1));
|
|
10612
|
+
forceTick((n) => n + 1);
|
|
10613
|
+
});
|
|
10614
|
+
}
|
|
10615
|
+
return () => {
|
|
10616
|
+
cancelled = true;
|
|
10617
|
+
};
|
|
10618
|
+
}, [atlasUrls]);
|
|
10619
|
+
const sheetUrls = useMemo(() => {
|
|
10620
|
+
const urls = /* @__PURE__ */ new Set();
|
|
10621
|
+
for (const unit of units) {
|
|
10622
|
+
const atlasUrl = unitAtlasUrl(unit);
|
|
10623
|
+
if (!atlasUrl) continue;
|
|
10624
|
+
const atlas = atlasCacheRef.current.get(atlasUrl);
|
|
10625
|
+
if (!atlas) continue;
|
|
10626
|
+
for (const rel of Object.values(atlas.sheets)) {
|
|
10627
|
+
if (rel) urls.add(resolveSheetUrl(atlasUrl, rel));
|
|
10628
|
+
}
|
|
10629
|
+
}
|
|
10630
|
+
return [...urls];
|
|
10631
|
+
}, [units, pendingCount]);
|
|
10632
|
+
useEffect(() => {
|
|
10633
|
+
const hasAtlasUnits = units.some((u) => unitAtlasUrl(u) !== null);
|
|
10634
|
+
if (!hasAtlasUnits) return;
|
|
10635
|
+
let running = true;
|
|
10636
|
+
const tick = (ts) => {
|
|
10637
|
+
if (!running) return;
|
|
10638
|
+
const last = lastTickRef.current || ts;
|
|
10639
|
+
const delta = ts - last;
|
|
10640
|
+
lastTickRef.current = ts;
|
|
10641
|
+
const states = animStatesRef.current;
|
|
10642
|
+
const currentIds = /* @__PURE__ */ new Set();
|
|
10643
|
+
for (const unit of units) {
|
|
10644
|
+
if (unitAtlasUrl(unit) === null) continue;
|
|
10645
|
+
currentIds.add(unit.id);
|
|
10646
|
+
let state = states.get(unit.id);
|
|
10647
|
+
if (!state) {
|
|
10648
|
+
state = { animation: "idle", direction: "se", elapsed: 0, walkHold: 0, prev: null };
|
|
10649
|
+
states.set(unit.id, state);
|
|
10650
|
+
}
|
|
10651
|
+
const posX = unit.position?.x ?? unit.x ?? 0;
|
|
10652
|
+
const posY = unit.position?.y ?? unit.y ?? 0;
|
|
10653
|
+
if (state.prev) {
|
|
10654
|
+
const dx = posX - state.prev.x;
|
|
10655
|
+
const dy = posY - state.prev.y;
|
|
10656
|
+
if (dx !== 0 || dy !== 0) {
|
|
10657
|
+
state.animation = "walk";
|
|
10658
|
+
state.direction = inferDirection(dx, dy);
|
|
10659
|
+
state.walkHold = WALK_HOLD_MS;
|
|
10660
|
+
} else if (state.animation === "walk") {
|
|
10661
|
+
state.walkHold -= delta;
|
|
10662
|
+
if (state.walkHold <= 0) state.animation = "idle";
|
|
10663
|
+
}
|
|
10664
|
+
}
|
|
10665
|
+
state.prev = { x: posX, y: posY };
|
|
10666
|
+
state.elapsed += delta;
|
|
10667
|
+
}
|
|
10668
|
+
for (const id of states.keys()) {
|
|
10669
|
+
if (!currentIds.has(id)) states.delete(id);
|
|
10670
|
+
}
|
|
10671
|
+
rafRef.current = requestAnimationFrame(tick);
|
|
10672
|
+
};
|
|
10673
|
+
rafRef.current = requestAnimationFrame(tick);
|
|
10674
|
+
return () => {
|
|
10675
|
+
running = false;
|
|
10676
|
+
cancelAnimationFrame(rafRef.current);
|
|
10677
|
+
lastTickRef.current = 0;
|
|
10678
|
+
};
|
|
10679
|
+
}, [units]);
|
|
10680
|
+
const resolveUnitFrame = useCallback((unitId) => {
|
|
10681
|
+
const unit = units.find((u) => u.id === unitId);
|
|
10682
|
+
if (!unit) return null;
|
|
10683
|
+
const atlasUrl = unitAtlasUrl(unit);
|
|
10684
|
+
if (!atlasUrl) return null;
|
|
10685
|
+
const atlas = atlasCacheRef.current.get(atlasUrl);
|
|
10686
|
+
if (!atlas) return null;
|
|
10687
|
+
const state = animStatesRef.current.get(unitId);
|
|
10688
|
+
const animation = state?.animation ?? "idle";
|
|
10689
|
+
const direction = state?.direction ?? "se";
|
|
10690
|
+
const elapsed = state?.elapsed ?? 0;
|
|
10691
|
+
const def = atlas.animations[animation] ?? atlas.animations.idle;
|
|
10692
|
+
if (!def) return null;
|
|
10693
|
+
const { sheetDir, flipX } = resolveSheetDirection(direction);
|
|
10694
|
+
const rel = atlas.sheets[sheetDir] ?? atlas.sheets.se ?? atlas.sheets.sw;
|
|
10695
|
+
if (!rel) return null;
|
|
10696
|
+
const sheetUrl = resolveSheetUrl(atlasUrl, rel);
|
|
10697
|
+
const isIdle = animation === "idle";
|
|
10698
|
+
const frame = isIdle ? 0 : getCurrentFrameFromDef(def, elapsed).frame;
|
|
10699
|
+
const rect = frameRect(frame, def.row, atlas.columns, atlas.frameWidth, atlas.frameHeight);
|
|
10700
|
+
return {
|
|
10701
|
+
sheetUrl,
|
|
10702
|
+
sx: rect.sx,
|
|
10703
|
+
sy: rect.sy,
|
|
10704
|
+
sw: rect.sw,
|
|
10705
|
+
sh: rect.sh,
|
|
10706
|
+
flipX,
|
|
10707
|
+
applyBreathing: isIdle
|
|
10708
|
+
};
|
|
10709
|
+
}, [units]);
|
|
10710
|
+
return { sheetUrls, resolveUnitFrame, pendingCount };
|
|
10711
|
+
}
|
|
10712
|
+
var WALK_HOLD_MS;
|
|
10713
|
+
var init_useUnitSpriteAtlas = __esm({
|
|
10714
|
+
"components/game/molecules/useUnitSpriteAtlas.ts"() {
|
|
10715
|
+
"use client";
|
|
10716
|
+
init_spriteAnimation();
|
|
10717
|
+
WALK_HOLD_MS = 600;
|
|
10718
|
+
}
|
|
10719
|
+
});
|
|
10520
10720
|
|
|
10521
10721
|
// components/game/organisms/utils/isometric.ts
|
|
10522
10722
|
function isoToScreen(tileX, tileY, scale, baseOffsetX) {
|
|
@@ -10631,6 +10831,10 @@ function IsometricCanvas({
|
|
|
10631
10831
|
() => unitsProp.map((u) => u.position ? u : { ...u, position: { x: u.x ?? 0, y: u.y ?? 0 } }),
|
|
10632
10832
|
[unitsProp]
|
|
10633
10833
|
);
|
|
10834
|
+
const { sheetUrls: atlasSheetUrls, resolveUnitFrame: resolveUnitFrameInternal, pendingCount: atlasPending } = useUnitSpriteAtlas(units);
|
|
10835
|
+
const resolveFrameForUnit = useCallback((unitId) => {
|
|
10836
|
+
return resolveUnitFrame?.(unitId) ?? resolveUnitFrameInternal(unitId);
|
|
10837
|
+
}, [resolveUnitFrame, resolveUnitFrameInternal]);
|
|
10634
10838
|
const features = useMemo(
|
|
10635
10839
|
() => featuresProp.map((f3) => {
|
|
10636
10840
|
if (f3.type) return f3;
|
|
@@ -10714,9 +10918,10 @@ function IsometricCanvas({
|
|
|
10714
10918
|
}
|
|
10715
10919
|
}
|
|
10716
10920
|
if (effectSpriteUrls) urls.push(...effectSpriteUrls);
|
|
10921
|
+
if (atlasSheetUrls.length) urls.push(...atlasSheetUrls);
|
|
10717
10922
|
if (backgroundImage) urls.push(backgroundImage);
|
|
10718
10923
|
return [...new Set(urls.filter(Boolean))];
|
|
10719
|
-
}, [sortedTiles, features, units, getTerrainSprite, getFeatureSprite, getUnitSprite, effectSpriteUrls, backgroundImage, assetManifest, resolveManifestUrl]);
|
|
10924
|
+
}, [sortedTiles, features, units, getTerrainSprite, getFeatureSprite, getUnitSprite, effectSpriteUrls, atlasSheetUrls, backgroundImage, assetManifest, resolveManifestUrl]);
|
|
10720
10925
|
const { getImage, pendingCount } = useImageCache(spriteUrls);
|
|
10721
10926
|
useEffect(() => {
|
|
10722
10927
|
if (typeof window === "undefined") return;
|
|
@@ -11003,7 +11208,7 @@ function IsometricCanvas({
|
|
|
11003
11208
|
ctx.lineWidth = 3;
|
|
11004
11209
|
ctx.stroke();
|
|
11005
11210
|
}
|
|
11006
|
-
const frame =
|
|
11211
|
+
const frame = resolveFrameForUnit(unit.id);
|
|
11007
11212
|
const frameImg = frame ? getImage(frame.sheetUrl) : null;
|
|
11008
11213
|
if (frame && frameImg) {
|
|
11009
11214
|
const frameAr = frame.sw / frame.sh;
|
|
@@ -11113,7 +11318,7 @@ function IsometricCanvas({
|
|
|
11113
11318
|
resolveTerrainSpriteUrl,
|
|
11114
11319
|
resolveFeatureSpriteUrl,
|
|
11115
11320
|
resolveUnitSpriteUrl,
|
|
11116
|
-
|
|
11321
|
+
resolveFrameForUnit,
|
|
11117
11322
|
getImage,
|
|
11118
11323
|
gridWidth,
|
|
11119
11324
|
gridHeight,
|
|
@@ -11145,7 +11350,7 @@ function IsometricCanvas({
|
|
|
11145
11350
|
};
|
|
11146
11351
|
}, [selectedUnitId, units, scale, baseOffsetX, scaledTileWidth, scaledDiamondTopY, scaledFloorHeight, viewportSize, targetCameraRef]);
|
|
11147
11352
|
useEffect(() => {
|
|
11148
|
-
const hasAnimations = units.length > 0 || validMoves.length > 0 || attackTargets.length > 0 || selectedUnitId != null || targetCameraRef.current != null || hasActiveEffects2 || pendingCount > 0;
|
|
11353
|
+
const hasAnimations = units.length > 0 || validMoves.length > 0 || attackTargets.length > 0 || selectedUnitId != null || targetCameraRef.current != null || hasActiveEffects2 || pendingCount > 0 || atlasPending > 0;
|
|
11149
11354
|
draw(animTimeRef.current);
|
|
11150
11355
|
if (!hasAnimations) return;
|
|
11151
11356
|
let running = true;
|
|
@@ -11161,7 +11366,7 @@ function IsometricCanvas({
|
|
|
11161
11366
|
running = false;
|
|
11162
11367
|
cancelAnimationFrame(rafIdRef.current);
|
|
11163
11368
|
};
|
|
11164
|
-
}, [draw, units.length, validMoves.length, attackTargets.length, selectedUnitId, hasActiveEffects2, pendingCount, lerpToTarget, targetCameraRef]);
|
|
11369
|
+
}, [draw, units.length, validMoves.length, attackTargets.length, selectedUnitId, hasActiveEffects2, pendingCount, atlasPending, lerpToTarget, targetCameraRef]);
|
|
11165
11370
|
const handleMouseMoveWithCamera = useCallback((e) => {
|
|
11166
11371
|
if (enableCamera) {
|
|
11167
11372
|
const wasPanning = handleMouseMove(e, () => draw(animTimeRef.current));
|
|
@@ -11306,6 +11511,7 @@ var init_IsometricCanvas = __esm({
|
|
|
11306
11511
|
init_ErrorState();
|
|
11307
11512
|
init_useImageCache();
|
|
11308
11513
|
init_useCamera();
|
|
11514
|
+
init_useUnitSpriteAtlas();
|
|
11309
11515
|
init_verificationRegistry();
|
|
11310
11516
|
init_isometric();
|
|
11311
11517
|
IsometricCanvas.displayName = "IsometricCanvas";
|
|
@@ -28888,13 +29094,13 @@ var init_MapView = __esm({
|
|
|
28888
29094
|
shadowSize: [41, 41]
|
|
28889
29095
|
});
|
|
28890
29096
|
L.Marker.prototype.options.icon = defaultIcon;
|
|
28891
|
-
const { useEffect:
|
|
29097
|
+
const { useEffect: useEffect81, useRef: useRef75, useCallback: useCallback119, useState: useState113 } = React81__default;
|
|
28892
29098
|
const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
|
|
28893
29099
|
const { useEventBus: useEventBus4 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
|
|
28894
29100
|
function MapUpdater({ centerLat, centerLng, zoom }) {
|
|
28895
29101
|
const map = useMap();
|
|
28896
|
-
const prevRef =
|
|
28897
|
-
|
|
29102
|
+
const prevRef = useRef75({ centerLat, centerLng, zoom });
|
|
29103
|
+
useEffect81(() => {
|
|
28898
29104
|
const prev = prevRef.current;
|
|
28899
29105
|
if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
|
|
28900
29106
|
map.setView([centerLat, centerLng], zoom);
|
|
@@ -28905,7 +29111,7 @@ var init_MapView = __esm({
|
|
|
28905
29111
|
}
|
|
28906
29112
|
function MapClickHandler({ onMapClick }) {
|
|
28907
29113
|
const map = useMap();
|
|
28908
|
-
|
|
29114
|
+
useEffect81(() => {
|
|
28909
29115
|
if (!onMapClick) return;
|
|
28910
29116
|
const handler = (e) => {
|
|
28911
29117
|
onMapClick(e.latlng.lat, e.latlng.lng);
|
|
@@ -28933,8 +29139,8 @@ var init_MapView = __esm({
|
|
|
28933
29139
|
showAttribution = true
|
|
28934
29140
|
}) {
|
|
28935
29141
|
const eventBus = useEventBus4();
|
|
28936
|
-
const [clickedPosition, setClickedPosition] =
|
|
28937
|
-
const handleMapClick =
|
|
29142
|
+
const [clickedPosition, setClickedPosition] = useState113(null);
|
|
29143
|
+
const handleMapClick = useCallback119((lat, lng) => {
|
|
28938
29144
|
if (showClickedPin) {
|
|
28939
29145
|
setClickedPosition({ lat, lng });
|
|
28940
29146
|
}
|
|
@@ -28943,7 +29149,7 @@ var init_MapView = __esm({
|
|
|
28943
29149
|
eventBus.emit(`UI:${mapClickEvent}`, { latitude: lat, longitude: lng });
|
|
28944
29150
|
}
|
|
28945
29151
|
}, [onMapClick, mapClickEvent, eventBus, showClickedPin]);
|
|
28946
|
-
const handleMarkerClick =
|
|
29152
|
+
const handleMarkerClick = useCallback119((marker) => {
|
|
28947
29153
|
onMarkerClick?.(marker);
|
|
28948
29154
|
if (markerClickEvent) {
|
|
28949
29155
|
eventBus.emit(`UI:${markerClickEvent}`, { ...marker });
|
|
@@ -40661,7 +40867,7 @@ var init_AssetLoader = __esm({
|
|
|
40661
40867
|
__publicField(this, "textureCache");
|
|
40662
40868
|
__publicField(this, "loadingPromises");
|
|
40663
40869
|
this.objLoader = new OBJLoader();
|
|
40664
|
-
this.textureLoader = new
|
|
40870
|
+
this.textureLoader = new THREE3.TextureLoader();
|
|
40665
40871
|
this.modelCache = /* @__PURE__ */ new Map();
|
|
40666
40872
|
this.textureCache = /* @__PURE__ */ new Map();
|
|
40667
40873
|
this.loadingPromises = /* @__PURE__ */ new Map();
|
|
@@ -40735,7 +40941,7 @@ var init_AssetLoader = __esm({
|
|
|
40735
40941
|
return this.loadingPromises.get(`texture:${url}`);
|
|
40736
40942
|
}
|
|
40737
40943
|
const loadPromise = this.textureLoader.loadAsync(url).then((texture) => {
|
|
40738
|
-
texture.colorSpace =
|
|
40944
|
+
texture.colorSpace = THREE3.SRGBColorSpace;
|
|
40739
40945
|
this.textureCache.set(url, texture);
|
|
40740
40946
|
this.loadingPromises.delete(`texture:${url}`);
|
|
40741
40947
|
return texture;
|
|
@@ -40809,7 +41015,7 @@ var init_AssetLoader = __esm({
|
|
|
40809
41015
|
});
|
|
40810
41016
|
this.modelCache.forEach((model) => {
|
|
40811
41017
|
model.scene.traverse((child) => {
|
|
40812
|
-
if (child instanceof
|
|
41018
|
+
if (child instanceof THREE3.Mesh) {
|
|
40813
41019
|
child.geometry.dispose();
|
|
40814
41020
|
if (Array.isArray(child.material)) {
|
|
40815
41021
|
child.material.forEach((m) => m.dispose());
|
|
@@ -41365,7 +41571,7 @@ function ModelLoader({
|
|
|
41365
41571
|
if (!loadedModel) return null;
|
|
41366
41572
|
const cloned = loadedModel.clone();
|
|
41367
41573
|
cloned.traverse((child) => {
|
|
41368
|
-
if (child instanceof
|
|
41574
|
+
if (child instanceof THREE3.Mesh) {
|
|
41369
41575
|
child.castShadow = castShadow;
|
|
41370
41576
|
child.receiveShadow = receiveShadow;
|
|
41371
41577
|
}
|
|
@@ -41479,6 +41685,47 @@ function CameraController({
|
|
|
41479
41685
|
}, [camera.position, onCameraChange]);
|
|
41480
41686
|
return null;
|
|
41481
41687
|
}
|
|
41688
|
+
function UnitSpriteBillboard({
|
|
41689
|
+
sheetUrl,
|
|
41690
|
+
resolveFrame,
|
|
41691
|
+
height = 1.2
|
|
41692
|
+
}) {
|
|
41693
|
+
const texture = useLoader(THREE3.TextureLoader, sheetUrl);
|
|
41694
|
+
const meshRef = useRef(null);
|
|
41695
|
+
const matRef = useRef(null);
|
|
41696
|
+
const [aspect, setAspect] = useState(1);
|
|
41697
|
+
useFrame(() => {
|
|
41698
|
+
const frame = resolveFrame();
|
|
41699
|
+
if (!frame || !texture.image) return;
|
|
41700
|
+
const imgW = texture.image.width;
|
|
41701
|
+
const imgH = texture.image.height;
|
|
41702
|
+
if (!imgW || !imgH) return;
|
|
41703
|
+
texture.repeat.set((frame.flipX ? -1 : 1) * (frame.sw / imgW), frame.sh / imgH);
|
|
41704
|
+
texture.offset.set(
|
|
41705
|
+
frame.flipX ? (frame.sx + frame.sw) / imgW : frame.sx / imgW,
|
|
41706
|
+
1 - (frame.sy + frame.sh) / imgH
|
|
41707
|
+
);
|
|
41708
|
+
texture.magFilter = THREE3.NearestFilter;
|
|
41709
|
+
texture.minFilter = THREE3.NearestFilter;
|
|
41710
|
+
texture.needsUpdate = true;
|
|
41711
|
+
const nextAspect = frame.sw / frame.sh;
|
|
41712
|
+
if (Math.abs(nextAspect - aspect) > 1e-3) setAspect(nextAspect);
|
|
41713
|
+
if (matRef.current) matRef.current.needsUpdate = true;
|
|
41714
|
+
});
|
|
41715
|
+
return /* @__PURE__ */ jsxs("mesh", { ref: meshRef, position: [0, height / 2, 0], children: [
|
|
41716
|
+
/* @__PURE__ */ jsx("planeGeometry", { args: [height * aspect, height] }),
|
|
41717
|
+
/* @__PURE__ */ jsx(
|
|
41718
|
+
"meshBasicMaterial",
|
|
41719
|
+
{
|
|
41720
|
+
ref: matRef,
|
|
41721
|
+
map: texture,
|
|
41722
|
+
transparent: true,
|
|
41723
|
+
alphaTest: 0.1,
|
|
41724
|
+
side: THREE3.DoubleSide
|
|
41725
|
+
}
|
|
41726
|
+
)
|
|
41727
|
+
] });
|
|
41728
|
+
}
|
|
41482
41729
|
var DEFAULT_GRID_CONFIG, GameCanvas3D;
|
|
41483
41730
|
var init_GameCanvas3D2 = __esm({
|
|
41484
41731
|
"components/game/molecules/GameCanvas3D.tsx"() {
|
|
@@ -41489,6 +41736,7 @@ var init_GameCanvas3D2 = __esm({
|
|
|
41489
41736
|
init_Canvas3DLoadingState2();
|
|
41490
41737
|
init_Canvas3DErrorBoundary2();
|
|
41491
41738
|
init_ModelLoader();
|
|
41739
|
+
init_useUnitSpriteAtlas();
|
|
41492
41740
|
init_cn();
|
|
41493
41741
|
init_GameCanvas3D();
|
|
41494
41742
|
DEFAULT_GRID_CONFIG = {
|
|
@@ -41545,8 +41793,10 @@ var init_GameCanvas3D2 = __esm({
|
|
|
41545
41793
|
const controlsRef = useRef(null);
|
|
41546
41794
|
const [hoveredTile, setHoveredTile] = useState(null);
|
|
41547
41795
|
const [internalError, setInternalError] = useState(null);
|
|
41796
|
+
const { sheetUrls: atlasSheetUrls, resolveUnitFrame } = useUnitSpriteAtlas(units);
|
|
41797
|
+
const preloadUrls = useMemo(() => [...preloadAssets, ...atlasSheetUrls], [preloadAssets, atlasSheetUrls]);
|
|
41548
41798
|
const { isLoading: assetsLoading, progress, loaded, total } = useAssetLoader({
|
|
41549
|
-
preloadUrls
|
|
41799
|
+
preloadUrls,
|
|
41550
41800
|
loader: customAssetLoader
|
|
41551
41801
|
});
|
|
41552
41802
|
const eventHandlers = useGameCanvas3DEvents({
|
|
@@ -41605,7 +41855,7 @@ var init_GameCanvas3D2 = __esm({
|
|
|
41605
41855
|
getCameraPosition: () => {
|
|
41606
41856
|
if (controlsRef.current) {
|
|
41607
41857
|
const pos = controlsRef.current.object.position;
|
|
41608
|
-
return new
|
|
41858
|
+
return new THREE3.Vector3(pos.x, pos.y, pos.z);
|
|
41609
41859
|
}
|
|
41610
41860
|
return null;
|
|
41611
41861
|
},
|
|
@@ -41757,6 +42007,8 @@ var init_GameCanvas3D2 = __esm({
|
|
|
41757
42007
|
({ unit, position }) => {
|
|
41758
42008
|
const isSelected = selectedUnitId === unit.id;
|
|
41759
42009
|
const color = unit.faction === "player" ? 4491519 : unit.faction === "enemy" ? 16729156 : 16777028;
|
|
42010
|
+
const hasAtlas = unitAtlasUrl(unit) !== null;
|
|
42011
|
+
const initialFrame = hasAtlas ? resolveUnitFrame(unit.id) : null;
|
|
41760
42012
|
return /* @__PURE__ */ jsxs(
|
|
41761
42013
|
"group",
|
|
41762
42014
|
{
|
|
@@ -41768,7 +42020,16 @@ var init_GameCanvas3D2 = __esm({
|
|
|
41768
42020
|
/* @__PURE__ */ jsx("ringGeometry", { args: [0.4, 0.5, 32] }),
|
|
41769
42021
|
/* @__PURE__ */ jsx("meshBasicMaterial", { color: "#ffff00", transparent: true, opacity: 0.8 })
|
|
41770
42022
|
] }),
|
|
41771
|
-
|
|
42023
|
+
hasAtlas && initialFrame ? (
|
|
42024
|
+
/* Animated sprite-sheet billboard — single cropped frame, by state */
|
|
42025
|
+
/* @__PURE__ */ jsx(Billboard, { children: /* @__PURE__ */ jsx(
|
|
42026
|
+
UnitSpriteBillboard,
|
|
42027
|
+
{
|
|
42028
|
+
sheetUrl: initialFrame.sheetUrl,
|
|
42029
|
+
resolveFrame: () => resolveUnitFrame(unit.id)
|
|
42030
|
+
}
|
|
42031
|
+
) })
|
|
42032
|
+
) : unit.modelUrl ? (
|
|
41772
42033
|
/* GLB unit model (box fallback while loading / on error) */
|
|
41773
42034
|
/* @__PURE__ */ jsx(
|
|
41774
42035
|
ModelLoader,
|
|
@@ -41822,7 +42083,7 @@ var init_GameCanvas3D2 = __esm({
|
|
|
41822
42083
|
}
|
|
41823
42084
|
);
|
|
41824
42085
|
},
|
|
41825
|
-
[selectedUnitId, handleUnitClick]
|
|
42086
|
+
[selectedUnitId, handleUnitClick, resolveUnitFrame]
|
|
41826
42087
|
);
|
|
41827
42088
|
const DefaultFeatureRenderer = useCallback(
|
|
41828
42089
|
({
|
|
@@ -51151,6 +51412,27 @@ init_verificationRegistry();
|
|
|
51151
51412
|
var crossTraitLog = createLogger("almadar:ui:cross-trait");
|
|
51152
51413
|
var flushLog = createLogger("almadar:ui:slot-flush");
|
|
51153
51414
|
var stateLog = createLogger("almadar:ui:state-transitions");
|
|
51415
|
+
var tickLog = createLogger("almadar:ui:tick-effects");
|
|
51416
|
+
var SYNC_TICK_OPERATORS = /* @__PURE__ */ new Set([
|
|
51417
|
+
"set",
|
|
51418
|
+
"emit",
|
|
51419
|
+
"render-ui",
|
|
51420
|
+
"render",
|
|
51421
|
+
"navigate",
|
|
51422
|
+
"notify",
|
|
51423
|
+
"log",
|
|
51424
|
+
// Synchronous structural forms that wrap sync effects. A tick authored as
|
|
51425
|
+
// a single top-level `(let ((..)) (do (set ..) (render-ui ..)))` or
|
|
51426
|
+
// `(if cond (set ..) ..)` is one of these at its head — the EffectExecutor
|
|
51427
|
+
// resolves the binding values / condition through the canonical evaluator
|
|
51428
|
+
// and runs the wrapped sync effects. Without these in the allow-list the
|
|
51429
|
+
// whole tick is filtered out (its head op isn't `set`/`emit`/...), so the
|
|
51430
|
+
// physics/gameflow/AI tick never runs.
|
|
51431
|
+
"let",
|
|
51432
|
+
"if",
|
|
51433
|
+
"do",
|
|
51434
|
+
"when"
|
|
51435
|
+
]);
|
|
51154
51436
|
function toTraitDefinition(binding) {
|
|
51155
51437
|
return {
|
|
51156
51438
|
name: binding.trait.name,
|
|
@@ -51347,50 +51629,196 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
51347
51629
|
for (const unreg of snapshotUnregs) unreg();
|
|
51348
51630
|
};
|
|
51349
51631
|
}, [traitBindings]);
|
|
51350
|
-
const
|
|
51351
|
-
const
|
|
51352
|
-
|
|
51353
|
-
const
|
|
51354
|
-
|
|
51355
|
-
|
|
51356
|
-
|
|
51357
|
-
|
|
51358
|
-
if (
|
|
51359
|
-
const passed = interpolateValue(tick.guard, evalCtx);
|
|
51360
|
-
if (!passed) return;
|
|
51361
|
-
}
|
|
51632
|
+
const executeTransitionEffects = useCallback(async (params) => {
|
|
51633
|
+
const { binding, previousState, newState, payload, flushEvent, syncOnly, log: log14 } = params;
|
|
51634
|
+
const traitName = binding.trait.name;
|
|
51635
|
+
const linkedEntity = binding.linkedEntity || "";
|
|
51636
|
+
const entityId = payload?.entityId;
|
|
51637
|
+
const effects = syncOnly ? params.effects.filter(
|
|
51638
|
+
(e) => Array.isArray(e) && SYNC_TICK_OPERATORS.has(String(e[0]))
|
|
51639
|
+
) : params.effects;
|
|
51640
|
+
if (effects.length === 0) return [];
|
|
51362
51641
|
const pendingSlots = /* @__PURE__ */ new Map();
|
|
51363
51642
|
({
|
|
51364
|
-
trait: binding.trait.name,
|
|
51365
|
-
transition: `${currentState}->tick:${tick.name}`,
|
|
51366
|
-
effects: tick.effects,
|
|
51367
51643
|
traitDefinition: binding.trait
|
|
51368
51644
|
});
|
|
51369
|
-
|
|
51370
|
-
|
|
51371
|
-
|
|
51372
|
-
|
|
51373
|
-
|
|
51374
|
-
|
|
51375
|
-
|
|
51645
|
+
const clientHandlers = createClientEffectHandlers({
|
|
51646
|
+
eventBus,
|
|
51647
|
+
slotSetter: {
|
|
51648
|
+
addPattern: (slot, pattern, props) => {
|
|
51649
|
+
const existing = pendingSlots.get(slot) || [];
|
|
51650
|
+
existing.push({ pattern, props: props || {} });
|
|
51651
|
+
pendingSlots.set(slot, existing);
|
|
51652
|
+
},
|
|
51653
|
+
clearSlot: (slot) => {
|
|
51376
51654
|
pendingSlots.set(slot, []);
|
|
51377
|
-
continue;
|
|
51378
51655
|
}
|
|
51379
|
-
|
|
51380
|
-
|
|
51381
|
-
|
|
51382
|
-
|
|
51383
|
-
|
|
51656
|
+
},
|
|
51657
|
+
navigate: optionsRef.current?.navigate,
|
|
51658
|
+
notify: optionsRef.current?.notify,
|
|
51659
|
+
callService: optionsRef.current?.callService
|
|
51660
|
+
});
|
|
51661
|
+
const persistence = syncOnly ? void 0 : optionsRef.current?.persistence;
|
|
51662
|
+
let handlers = clientHandlers;
|
|
51663
|
+
if (persistence) {
|
|
51664
|
+
const sharedBindings = {
|
|
51665
|
+
// Seed `@entity` from the trait's scalar field state (a real
|
|
51666
|
+
// EntityRow), the same source the executor's own bindingCtx
|
|
51667
|
+
// uses below. `@payload.*` resolves from `payload` separately,
|
|
51668
|
+
// so dropping the prior `payload as EntityRow` cast loses
|
|
51669
|
+
// nothing — it just stops mislabelling the payload as an entity.
|
|
51670
|
+
entity: traitFieldStatesRef.current.get(traitName) ?? {},
|
|
51671
|
+
payload: payload || {},
|
|
51672
|
+
state: previousState
|
|
51673
|
+
};
|
|
51674
|
+
const sharedDeclared = collectDeclaredConfigDefaults(binding.trait);
|
|
51675
|
+
const sharedCallSite = binding.config;
|
|
51676
|
+
if (sharedDeclared || sharedCallSite) {
|
|
51677
|
+
sharedBindings.config = {
|
|
51678
|
+
...sharedDeclared ?? {},
|
|
51679
|
+
...sharedCallSite ?? {}
|
|
51680
|
+
};
|
|
51384
51681
|
}
|
|
51682
|
+
const serverHandlers = createServerEffectHandlers({
|
|
51683
|
+
persistence,
|
|
51684
|
+
eventBus,
|
|
51685
|
+
entityType: linkedEntity,
|
|
51686
|
+
entityId,
|
|
51687
|
+
bindings: sharedBindings,
|
|
51688
|
+
context: {
|
|
51689
|
+
traitName,
|
|
51690
|
+
state: previousState,
|
|
51691
|
+
transition: `${previousState}->${newState}`,
|
|
51692
|
+
linkedEntity,
|
|
51693
|
+
entityId
|
|
51694
|
+
},
|
|
51695
|
+
source: { trait: traitName },
|
|
51696
|
+
callService: optionsRef.current?.callService
|
|
51697
|
+
});
|
|
51698
|
+
handlers = {
|
|
51699
|
+
...serverHandlers,
|
|
51700
|
+
emit: clientHandlers.emit,
|
|
51701
|
+
renderUI: clientHandlers.renderUI,
|
|
51702
|
+
navigate: clientHandlers.navigate,
|
|
51703
|
+
notify: clientHandlers.notify
|
|
51704
|
+
};
|
|
51385
51705
|
}
|
|
51386
|
-
|
|
51387
|
-
|
|
51388
|
-
|
|
51389
|
-
|
|
51390
|
-
|
|
51706
|
+
const baseSet = handlers.set;
|
|
51707
|
+
handlers = {
|
|
51708
|
+
...handlers,
|
|
51709
|
+
set: async (targetId, field, value) => {
|
|
51710
|
+
let fieldState = traitFieldStatesRef.current.get(traitName);
|
|
51711
|
+
if (!fieldState) {
|
|
51712
|
+
fieldState = {};
|
|
51713
|
+
traitFieldStatesRef.current.set(traitName, fieldState);
|
|
51714
|
+
}
|
|
51715
|
+
fieldState[field] = value;
|
|
51716
|
+
log14.debug("set:write", {
|
|
51717
|
+
traitName,
|
|
51718
|
+
field,
|
|
51719
|
+
value: JSON.stringify(value),
|
|
51720
|
+
transition: `${previousState}->${newState}`
|
|
51721
|
+
});
|
|
51722
|
+
if (baseSet) await baseSet(targetId, field, value);
|
|
51723
|
+
}
|
|
51724
|
+
};
|
|
51725
|
+
const entityForBinding = traitFieldStatesRef.current.get(traitName) ?? {};
|
|
51726
|
+
const bindingCtx = {
|
|
51727
|
+
entity: entityForBinding,
|
|
51728
|
+
payload: payload || {},
|
|
51729
|
+
state: previousState
|
|
51730
|
+
};
|
|
51731
|
+
const declaredDefaults = collectDeclaredConfigDefaults(binding.trait);
|
|
51732
|
+
const callSiteConfig = binding.config;
|
|
51733
|
+
if (declaredDefaults || callSiteConfig) {
|
|
51734
|
+
bindingCtx.config = {
|
|
51735
|
+
...declaredDefaults ?? {},
|
|
51736
|
+
...callSiteConfig ?? {}
|
|
51737
|
+
};
|
|
51738
|
+
}
|
|
51739
|
+
const effectContext = {
|
|
51740
|
+
traitName,
|
|
51741
|
+
state: previousState,
|
|
51742
|
+
transition: `${previousState}->${newState}`,
|
|
51743
|
+
linkedEntity,
|
|
51744
|
+
entityId
|
|
51745
|
+
};
|
|
51746
|
+
const emittedDuringExec = [];
|
|
51747
|
+
const baseEmit = handlers.emit;
|
|
51748
|
+
const trackingHandlers = {
|
|
51749
|
+
...handlers,
|
|
51750
|
+
emit: (event, eventPayload, source) => {
|
|
51751
|
+
emittedDuringExec.push(event);
|
|
51752
|
+
baseEmit(event, eventPayload, source);
|
|
51753
|
+
}
|
|
51754
|
+
};
|
|
51755
|
+
const executor = new EffectExecutor({ handlers: trackingHandlers, bindings: bindingCtx, context: effectContext });
|
|
51756
|
+
try {
|
|
51757
|
+
await executor.executeAll(effects);
|
|
51758
|
+
log14.debug("effects:executed", () => ({
|
|
51759
|
+
traitName,
|
|
51760
|
+
transition: `${previousState}->${newState}`,
|
|
51761
|
+
event: flushEvent,
|
|
51762
|
+
effectCount: effects.length,
|
|
51763
|
+
emitted: emittedDuringExec.join(","),
|
|
51764
|
+
entityAfter: JSON.stringify(traitFieldStatesRef.current.get(traitName) ?? {}),
|
|
51765
|
+
slotsTouched: Array.from(pendingSlots.keys()).join(",")
|
|
51766
|
+
}));
|
|
51767
|
+
for (const [slot, patterns] of pendingSlots) {
|
|
51768
|
+
log14.debug("flush:slot", {
|
|
51769
|
+
traitName,
|
|
51770
|
+
slot,
|
|
51771
|
+
patternCount: patterns.length,
|
|
51772
|
+
event: flushEvent,
|
|
51773
|
+
transition: `${previousState}->${newState}`,
|
|
51774
|
+
cleared: patterns.length === 0
|
|
51775
|
+
});
|
|
51776
|
+
flushSlot(traitName, slot, patterns, {
|
|
51777
|
+
event: flushEvent,
|
|
51778
|
+
state: previousState,
|
|
51779
|
+
entity: binding.linkedEntity
|
|
51780
|
+
});
|
|
51781
|
+
}
|
|
51782
|
+
} catch (error) {
|
|
51783
|
+
log14.error("effects:error", {
|
|
51784
|
+
traitName,
|
|
51785
|
+
transition: `${previousState}->${newState}`,
|
|
51786
|
+
event: flushEvent,
|
|
51787
|
+
error: error instanceof Error ? error.message : String(error),
|
|
51788
|
+
effectCount: effects.length
|
|
51391
51789
|
});
|
|
51392
51790
|
}
|
|
51393
|
-
|
|
51791
|
+
return emittedDuringExec;
|
|
51792
|
+
}, [eventBus, flushSlot]);
|
|
51793
|
+
const runTickEffects = useCallback((tick, binding) => {
|
|
51794
|
+
const traitName = binding.trait.name;
|
|
51795
|
+
const currentState = traitStatesRef.current.get(traitName)?.currentState ?? "";
|
|
51796
|
+
if (tick.appliesTo.length > 0 && !tick.appliesTo.includes(currentState)) return;
|
|
51797
|
+
if (tick.guard !== void 0) {
|
|
51798
|
+
const guardCtx = {
|
|
51799
|
+
entity: traitFieldStatesRef.current.get(traitName) ?? {},
|
|
51800
|
+
payload: {},
|
|
51801
|
+
state: currentState
|
|
51802
|
+
};
|
|
51803
|
+
if (binding.config) {
|
|
51804
|
+
guardCtx.config = binding.config;
|
|
51805
|
+
}
|
|
51806
|
+
const passed = interpolateValue(tick.guard, createContextFromBindings(guardCtx));
|
|
51807
|
+
if (!passed) {
|
|
51808
|
+
tickLog.debug("guard-blocked", { traitName, tick: tick.name, state: currentState });
|
|
51809
|
+
return;
|
|
51810
|
+
}
|
|
51811
|
+
}
|
|
51812
|
+
void executeTransitionEffects({
|
|
51813
|
+
binding,
|
|
51814
|
+
effects: tick.effects,
|
|
51815
|
+
previousState: currentState,
|
|
51816
|
+
newState: currentState,
|
|
51817
|
+
flushEvent: `tick:${tick.name}`,
|
|
51818
|
+
syncOnly: true,
|
|
51819
|
+
log: tickLog
|
|
51820
|
+
});
|
|
51821
|
+
}, [executeTransitionEffects]);
|
|
51394
51822
|
useEffect(() => {
|
|
51395
51823
|
const hasFrameTicks = traitBindingsRef.current.some(
|
|
51396
51824
|
(b) => b.trait.ticks?.some((t) => t.interval === "frame")
|
|
@@ -51501,161 +51929,17 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
51501
51929
|
transition: `${result.previousState} -> ${result.newState}`,
|
|
51502
51930
|
effects: JSON.stringify(result.effects)
|
|
51503
51931
|
}));
|
|
51504
|
-
const
|
|
51505
|
-
|
|
51506
|
-
const pendingSlots = /* @__PURE__ */ new Map();
|
|
51507
|
-
const slotSource = {
|
|
51508
|
-
trait: binding.trait.name,
|
|
51509
|
-
state: result.previousState,
|
|
51510
|
-
transition: `${result.previousState}->${result.newState}`,
|
|
51932
|
+
const emittedDuringExec = await executeTransitionEffects({
|
|
51933
|
+
binding,
|
|
51511
51934
|
effects: result.effects,
|
|
51512
|
-
|
|
51513
|
-
|
|
51514
|
-
|
|
51515
|
-
|
|
51516
|
-
|
|
51517
|
-
|
|
51518
|
-
const existing = pendingSlots.get(slot) || [];
|
|
51519
|
-
existing.push({ pattern, props: props || {} });
|
|
51520
|
-
pendingSlots.set(slot, existing);
|
|
51521
|
-
},
|
|
51522
|
-
clearSlot: (slot) => {
|
|
51523
|
-
pendingSlots.set(slot, []);
|
|
51524
|
-
}
|
|
51525
|
-
},
|
|
51526
|
-
navigate: optionsRef.current?.navigate,
|
|
51527
|
-
notify: optionsRef.current?.notify,
|
|
51528
|
-
callService: optionsRef.current?.callService
|
|
51935
|
+
previousState: result.previousState,
|
|
51936
|
+
newState: result.newState,
|
|
51937
|
+
payload,
|
|
51938
|
+
flushEvent: eventKey,
|
|
51939
|
+
syncOnly: false,
|
|
51940
|
+
log: stateLog
|
|
51529
51941
|
});
|
|
51530
|
-
const persistence = optionsRef.current?.persistence;
|
|
51531
|
-
let handlers = clientHandlers;
|
|
51532
|
-
if (persistence) {
|
|
51533
|
-
const sharedBindings = {
|
|
51534
|
-
entity: payload ?? {},
|
|
51535
|
-
payload: payload || {},
|
|
51536
|
-
state: result.previousState
|
|
51537
|
-
};
|
|
51538
|
-
const sharedDeclared = collectDeclaredConfigDefaults(
|
|
51539
|
-
binding.trait
|
|
51540
|
-
);
|
|
51541
|
-
const sharedCallSite = binding.config;
|
|
51542
|
-
if (sharedDeclared || sharedCallSite) {
|
|
51543
|
-
sharedBindings.config = {
|
|
51544
|
-
...sharedDeclared ?? {},
|
|
51545
|
-
...sharedCallSite ?? {}
|
|
51546
|
-
};
|
|
51547
|
-
}
|
|
51548
|
-
const serverHandlers = createServerEffectHandlers({
|
|
51549
|
-
persistence,
|
|
51550
|
-
eventBus,
|
|
51551
|
-
entityType: linkedEntity,
|
|
51552
|
-
entityId,
|
|
51553
|
-
bindings: sharedBindings,
|
|
51554
|
-
context: {
|
|
51555
|
-
traitName: binding.trait.name,
|
|
51556
|
-
state: result.previousState,
|
|
51557
|
-
transition: `${result.previousState}->${result.newState}`,
|
|
51558
|
-
linkedEntity,
|
|
51559
|
-
entityId
|
|
51560
|
-
},
|
|
51561
|
-
source: { trait: binding.trait.name },
|
|
51562
|
-
callService: optionsRef.current?.callService
|
|
51563
|
-
});
|
|
51564
|
-
handlers = {
|
|
51565
|
-
...serverHandlers,
|
|
51566
|
-
// Client handlers own UI + emit: keep the slot setter
|
|
51567
|
-
// and pre-prefixed UI:* emit path intact.
|
|
51568
|
-
emit: clientHandlers.emit,
|
|
51569
|
-
renderUI: clientHandlers.renderUI,
|
|
51570
|
-
navigate: clientHandlers.navigate,
|
|
51571
|
-
notify: clientHandlers.notify
|
|
51572
|
-
};
|
|
51573
|
-
}
|
|
51574
|
-
const baseSet = handlers.set;
|
|
51575
|
-
handlers = {
|
|
51576
|
-
...handlers,
|
|
51577
|
-
set: async (targetId, field, value) => {
|
|
51578
|
-
let fieldState = traitFieldStatesRef.current.get(traitName);
|
|
51579
|
-
if (!fieldState) {
|
|
51580
|
-
fieldState = {};
|
|
51581
|
-
traitFieldStatesRef.current.set(traitName, fieldState);
|
|
51582
|
-
}
|
|
51583
|
-
fieldState[field] = value;
|
|
51584
|
-
if (baseSet) await baseSet(targetId, field, value);
|
|
51585
|
-
}
|
|
51586
|
-
};
|
|
51587
|
-
const entityForBinding = traitFieldStatesRef.current.get(traitName) ?? {};
|
|
51588
|
-
const bindingCtx = {
|
|
51589
|
-
entity: entityForBinding,
|
|
51590
|
-
payload: payload || {},
|
|
51591
|
-
state: result.previousState
|
|
51592
|
-
};
|
|
51593
|
-
const declaredDefaults = collectDeclaredConfigDefaults(
|
|
51594
|
-
binding.trait
|
|
51595
|
-
);
|
|
51596
|
-
const callSiteConfig = binding.config;
|
|
51597
|
-
if (declaredDefaults || callSiteConfig) {
|
|
51598
|
-
bindingCtx.config = {
|
|
51599
|
-
...declaredDefaults ?? {},
|
|
51600
|
-
...callSiteConfig ?? {}
|
|
51601
|
-
};
|
|
51602
|
-
}
|
|
51603
|
-
const effectContext = {
|
|
51604
|
-
traitName: binding.trait.name,
|
|
51605
|
-
state: result.previousState,
|
|
51606
|
-
transition: `${result.previousState}->${result.newState}`,
|
|
51607
|
-
linkedEntity,
|
|
51608
|
-
entityId
|
|
51609
|
-
};
|
|
51610
|
-
const emittedDuringExec = [];
|
|
51611
51942
|
emittedByTrait.set(traitName, emittedDuringExec);
|
|
51612
|
-
const baseEmit = handlers.emit;
|
|
51613
|
-
const trackingHandlers = {
|
|
51614
|
-
...handlers,
|
|
51615
|
-
emit: (event, eventPayload, source) => {
|
|
51616
|
-
emittedDuringExec.push(event);
|
|
51617
|
-
baseEmit(event, eventPayload, source);
|
|
51618
|
-
}
|
|
51619
|
-
};
|
|
51620
|
-
const executor = new EffectExecutor({ handlers: trackingHandlers, bindings: bindingCtx, context: effectContext });
|
|
51621
|
-
try {
|
|
51622
|
-
await executor.executeAll(result.effects);
|
|
51623
|
-
stateLog.debug("transition:render-ui-dispatched", () => ({
|
|
51624
|
-
traitName,
|
|
51625
|
-
fromState: result.previousState,
|
|
51626
|
-
toState: result.newState,
|
|
51627
|
-
event: eventKey,
|
|
51628
|
-
slotsTouched: Array.from(pendingSlots.keys()).join(","),
|
|
51629
|
-
patternTypes: Array.from(pendingSlots.entries()).map(
|
|
51630
|
-
([slot, patterns]) => `${slot}:[${patterns.map((p2) => p2.pattern?.type ?? "null").join(",")}]`
|
|
51631
|
-
).join(";")
|
|
51632
|
-
}));
|
|
51633
|
-
void slotSource;
|
|
51634
|
-
for (const [slot, patterns] of pendingSlots) {
|
|
51635
|
-
stateLog.debug("flush:slot", {
|
|
51636
|
-
traitName,
|
|
51637
|
-
slot,
|
|
51638
|
-
patternCount: patterns.length,
|
|
51639
|
-
event: eventKey,
|
|
51640
|
-
transition: `${result.previousState}->${result.newState}`,
|
|
51641
|
-
cleared: patterns.length === 0
|
|
51642
|
-
});
|
|
51643
|
-
flushSlot(traitName, slot, patterns, {
|
|
51644
|
-
event: eventKey,
|
|
51645
|
-
state: result.previousState,
|
|
51646
|
-
entity: binding.linkedEntity
|
|
51647
|
-
});
|
|
51648
|
-
}
|
|
51649
|
-
} catch (error) {
|
|
51650
|
-
stateLog.error("transition:effect-error", {
|
|
51651
|
-
traitName,
|
|
51652
|
-
fromState: result.previousState,
|
|
51653
|
-
toState: result.newState,
|
|
51654
|
-
event: eventKey,
|
|
51655
|
-
error: error instanceof Error ? error.message : String(error),
|
|
51656
|
-
effectCount: result.effects.length
|
|
51657
|
-
});
|
|
51658
|
-
}
|
|
51659
51943
|
} else if (!result.executed) {
|
|
51660
51944
|
if (result.guardResult === false) {
|
|
51661
51945
|
stateLog.debug("guard-blocked-transition", {
|