@almadar/ui 5.49.1 → 5.51.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 +519 -215
- package/dist/avl/index.js +521 -217
- 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 +519 -215
- package/dist/runtime/index.js +521 -217
- package/package.json +1 -1
package/dist/runtime/index.cjs
CHANGED
|
@@ -41,7 +41,7 @@ var core$1 = require('@dnd-kit/core');
|
|
|
41
41
|
var sortable = require('@dnd-kit/sortable');
|
|
42
42
|
var utilities = require('@dnd-kit/utilities');
|
|
43
43
|
var react = require('@xyflow/react');
|
|
44
|
-
var
|
|
44
|
+
var THREE3 = require('three');
|
|
45
45
|
var GLTFLoader_js = require('three/examples/jsm/loaders/GLTFLoader.js');
|
|
46
46
|
var OBJLoader_js = require('three/examples/jsm/loaders/OBJLoader.js');
|
|
47
47
|
var GLTFLoader = require('three/examples/jsm/loaders/GLTFLoader');
|
|
@@ -96,7 +96,7 @@ var ReactMarkdown__default = /*#__PURE__*/_interopDefault(ReactMarkdown);
|
|
|
96
96
|
var remarkGfm__default = /*#__PURE__*/_interopDefault(remarkGfm);
|
|
97
97
|
var remarkMath__default = /*#__PURE__*/_interopDefault(remarkMath);
|
|
98
98
|
var rehypeKatex__default = /*#__PURE__*/_interopDefault(rehypeKatex);
|
|
99
|
-
var
|
|
99
|
+
var THREE3__namespace = /*#__PURE__*/_interopNamespace(THREE3);
|
|
100
100
|
|
|
101
101
|
var __defProp = Object.defineProperty;
|
|
102
102
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
@@ -7100,6 +7100,52 @@ var init_ControlButton = __esm({
|
|
|
7100
7100
|
ControlButton.displayName = "ControlButton";
|
|
7101
7101
|
}
|
|
7102
7102
|
});
|
|
7103
|
+
|
|
7104
|
+
// components/game/organisms/utils/spriteAnimation.ts
|
|
7105
|
+
function inferDirection(dx, dy) {
|
|
7106
|
+
if (dx === 0 && dy === 0) return "se";
|
|
7107
|
+
if (dx >= 0 && dy >= 0) return "se";
|
|
7108
|
+
if (dx <= 0 && dy >= 0) return "sw";
|
|
7109
|
+
if (dx >= 0 && dy <= 0) return "ne";
|
|
7110
|
+
return "nw";
|
|
7111
|
+
}
|
|
7112
|
+
function resolveSheetDirection(facing) {
|
|
7113
|
+
switch (facing) {
|
|
7114
|
+
case "se":
|
|
7115
|
+
return { sheetDir: "se", flipX: false };
|
|
7116
|
+
case "sw":
|
|
7117
|
+
return { sheetDir: "sw", flipX: false };
|
|
7118
|
+
case "ne":
|
|
7119
|
+
return { sheetDir: "sw", flipX: true };
|
|
7120
|
+
case "nw":
|
|
7121
|
+
return { sheetDir: "se", flipX: true };
|
|
7122
|
+
}
|
|
7123
|
+
}
|
|
7124
|
+
function frameRect(frame, row, columns, frameWidth, frameHeight) {
|
|
7125
|
+
return {
|
|
7126
|
+
sx: frame % columns * frameWidth,
|
|
7127
|
+
sy: row * frameHeight,
|
|
7128
|
+
sw: frameWidth,
|
|
7129
|
+
sh: frameHeight
|
|
7130
|
+
};
|
|
7131
|
+
}
|
|
7132
|
+
function getCurrentFrameFromDef(def, elapsed) {
|
|
7133
|
+
const frameDuration = 1e3 / def.frameRate;
|
|
7134
|
+
const totalDuration = def.frames * frameDuration;
|
|
7135
|
+
if (def.loop) {
|
|
7136
|
+
const frame2 = Math.floor(elapsed % totalDuration / frameDuration);
|
|
7137
|
+
return { frame: frame2, finished: false };
|
|
7138
|
+
}
|
|
7139
|
+
if (elapsed >= totalDuration) {
|
|
7140
|
+
return { frame: def.frames - 1, finished: true };
|
|
7141
|
+
}
|
|
7142
|
+
const frame = Math.floor(elapsed / frameDuration);
|
|
7143
|
+
return { frame, finished: false };
|
|
7144
|
+
}
|
|
7145
|
+
var init_spriteAnimation = __esm({
|
|
7146
|
+
"components/game/organisms/utils/spriteAnimation.ts"() {
|
|
7147
|
+
}
|
|
7148
|
+
});
|
|
7103
7149
|
function Sprite({
|
|
7104
7150
|
spritesheet = "https://almadar-kflow-assets.web.app/shared/isometric-blocks/Spritesheet/allTiles_sheet.png",
|
|
7105
7151
|
frameWidth = 64,
|
|
@@ -7120,12 +7166,8 @@ function Sprite({
|
|
|
7120
7166
|
}) {
|
|
7121
7167
|
const eventBus = useEventBus();
|
|
7122
7168
|
const sourcePosition = React81.useMemo(() => {
|
|
7123
|
-
const
|
|
7124
|
-
|
|
7125
|
-
return {
|
|
7126
|
-
x: frameX * frameWidth,
|
|
7127
|
-
y: frameY * frameHeight
|
|
7128
|
-
};
|
|
7169
|
+
const { sx, sy } = frameRect(frame, Math.floor(frame / columns), columns, frameWidth, frameHeight);
|
|
7170
|
+
return { x: sx, y: sy };
|
|
7129
7171
|
}, [frame, columns, frameWidth, frameHeight]);
|
|
7130
7172
|
const transform = React81.useMemo(() => {
|
|
7131
7173
|
const transforms = [
|
|
@@ -7173,6 +7215,7 @@ var init_Sprite = __esm({
|
|
|
7173
7215
|
"components/game/atoms/Sprite.tsx"() {
|
|
7174
7216
|
"use client";
|
|
7175
7217
|
init_useEventBus();
|
|
7218
|
+
init_spriteAnimation();
|
|
7176
7219
|
}
|
|
7177
7220
|
});
|
|
7178
7221
|
function StateIndicator({
|
|
@@ -10564,6 +10607,163 @@ var init_useCamera = __esm({
|
|
|
10564
10607
|
"use client";
|
|
10565
10608
|
}
|
|
10566
10609
|
});
|
|
10610
|
+
function unitAtlasUrl(unit) {
|
|
10611
|
+
if (unit.spriteSheet) return unit.spriteSheet;
|
|
10612
|
+
const sprite = unit.sprite;
|
|
10613
|
+
if (!sprite) return null;
|
|
10614
|
+
const match = /^(.*-sprite-sheet)(?:-(?:se|sw))?(?:-v\d+)?\.png$/.exec(sprite);
|
|
10615
|
+
if (!match) return null;
|
|
10616
|
+
return `${match[1]}.json`;
|
|
10617
|
+
}
|
|
10618
|
+
function resolveSheetUrl(atlasUrl, relativeSheetPath) {
|
|
10619
|
+
try {
|
|
10620
|
+
return new URL(relativeSheetPath, atlasUrl).toString();
|
|
10621
|
+
} catch {
|
|
10622
|
+
const base = atlasUrl.slice(0, atlasUrl.lastIndexOf("/") + 1);
|
|
10623
|
+
return `${base}${relativeSheetPath.replace(/^\.\//, "")}`;
|
|
10624
|
+
}
|
|
10625
|
+
}
|
|
10626
|
+
function useUnitSpriteAtlas(units) {
|
|
10627
|
+
const atlasCacheRef = React81.useRef(/* @__PURE__ */ new Map());
|
|
10628
|
+
const loadingRef = React81.useRef(/* @__PURE__ */ new Set());
|
|
10629
|
+
const [pendingCount, setPendingCount] = React81.useState(0);
|
|
10630
|
+
const [, forceTick] = React81.useState(0);
|
|
10631
|
+
const animStatesRef = React81.useRef(/* @__PURE__ */ new Map());
|
|
10632
|
+
const lastTickRef = React81.useRef(0);
|
|
10633
|
+
const rafRef = React81.useRef(0);
|
|
10634
|
+
const atlasUrls = React81.useMemo(() => {
|
|
10635
|
+
const set = /* @__PURE__ */ new Set();
|
|
10636
|
+
for (const unit of units) {
|
|
10637
|
+
const url = unitAtlasUrl(unit);
|
|
10638
|
+
if (url) set.add(url);
|
|
10639
|
+
}
|
|
10640
|
+
return [...set];
|
|
10641
|
+
}, [units]);
|
|
10642
|
+
React81.useEffect(() => {
|
|
10643
|
+
const cache = atlasCacheRef.current;
|
|
10644
|
+
const loading = loadingRef.current;
|
|
10645
|
+
const toLoad = atlasUrls.filter((url) => !cache.has(url) && !loading.has(url));
|
|
10646
|
+
if (toLoad.length === 0) return;
|
|
10647
|
+
let cancelled = false;
|
|
10648
|
+
setPendingCount((prev) => prev + toLoad.length);
|
|
10649
|
+
for (const url of toLoad) {
|
|
10650
|
+
loading.add(url);
|
|
10651
|
+
fetch(url).then((res) => res.ok ? res.json() : Promise.reject(new Error(String(res.status)))).then((atlas) => {
|
|
10652
|
+
if (cancelled) return;
|
|
10653
|
+
cache.set(url, atlas);
|
|
10654
|
+
}).catch(() => {
|
|
10655
|
+
}).finally(() => {
|
|
10656
|
+
if (cancelled) return;
|
|
10657
|
+
loading.delete(url);
|
|
10658
|
+
setPendingCount((prev) => Math.max(0, prev - 1));
|
|
10659
|
+
forceTick((n) => n + 1);
|
|
10660
|
+
});
|
|
10661
|
+
}
|
|
10662
|
+
return () => {
|
|
10663
|
+
cancelled = true;
|
|
10664
|
+
};
|
|
10665
|
+
}, [atlasUrls]);
|
|
10666
|
+
const sheetUrls = React81.useMemo(() => {
|
|
10667
|
+
const urls = /* @__PURE__ */ new Set();
|
|
10668
|
+
for (const unit of units) {
|
|
10669
|
+
const atlasUrl = unitAtlasUrl(unit);
|
|
10670
|
+
if (!atlasUrl) continue;
|
|
10671
|
+
const atlas = atlasCacheRef.current.get(atlasUrl);
|
|
10672
|
+
if (!atlas) continue;
|
|
10673
|
+
for (const rel of Object.values(atlas.sheets)) {
|
|
10674
|
+
if (rel) urls.add(resolveSheetUrl(atlasUrl, rel));
|
|
10675
|
+
}
|
|
10676
|
+
}
|
|
10677
|
+
return [...urls];
|
|
10678
|
+
}, [units, pendingCount]);
|
|
10679
|
+
React81.useEffect(() => {
|
|
10680
|
+
const hasAtlasUnits = units.some((u) => unitAtlasUrl(u) !== null);
|
|
10681
|
+
if (!hasAtlasUnits) return;
|
|
10682
|
+
let running = true;
|
|
10683
|
+
const tick = (ts) => {
|
|
10684
|
+
if (!running) return;
|
|
10685
|
+
const last = lastTickRef.current || ts;
|
|
10686
|
+
const delta = ts - last;
|
|
10687
|
+
lastTickRef.current = ts;
|
|
10688
|
+
const states = animStatesRef.current;
|
|
10689
|
+
const currentIds = /* @__PURE__ */ new Set();
|
|
10690
|
+
for (const unit of units) {
|
|
10691
|
+
if (unitAtlasUrl(unit) === null) continue;
|
|
10692
|
+
currentIds.add(unit.id);
|
|
10693
|
+
let state = states.get(unit.id);
|
|
10694
|
+
if (!state) {
|
|
10695
|
+
state = { animation: "idle", direction: "se", elapsed: 0, walkHold: 0, prev: null };
|
|
10696
|
+
states.set(unit.id, state);
|
|
10697
|
+
}
|
|
10698
|
+
const posX = unit.position?.x ?? unit.x ?? 0;
|
|
10699
|
+
const posY = unit.position?.y ?? unit.y ?? 0;
|
|
10700
|
+
if (state.prev) {
|
|
10701
|
+
const dx = posX - state.prev.x;
|
|
10702
|
+
const dy = posY - state.prev.y;
|
|
10703
|
+
if (dx !== 0 || dy !== 0) {
|
|
10704
|
+
state.animation = "walk";
|
|
10705
|
+
state.direction = inferDirection(dx, dy);
|
|
10706
|
+
state.walkHold = WALK_HOLD_MS;
|
|
10707
|
+
} else if (state.animation === "walk") {
|
|
10708
|
+
state.walkHold -= delta;
|
|
10709
|
+
if (state.walkHold <= 0) state.animation = "idle";
|
|
10710
|
+
}
|
|
10711
|
+
}
|
|
10712
|
+
state.prev = { x: posX, y: posY };
|
|
10713
|
+
state.elapsed += delta;
|
|
10714
|
+
}
|
|
10715
|
+
for (const id of states.keys()) {
|
|
10716
|
+
if (!currentIds.has(id)) states.delete(id);
|
|
10717
|
+
}
|
|
10718
|
+
rafRef.current = requestAnimationFrame(tick);
|
|
10719
|
+
};
|
|
10720
|
+
rafRef.current = requestAnimationFrame(tick);
|
|
10721
|
+
return () => {
|
|
10722
|
+
running = false;
|
|
10723
|
+
cancelAnimationFrame(rafRef.current);
|
|
10724
|
+
lastTickRef.current = 0;
|
|
10725
|
+
};
|
|
10726
|
+
}, [units]);
|
|
10727
|
+
const resolveUnitFrame = React81.useCallback((unitId) => {
|
|
10728
|
+
const unit = units.find((u) => u.id === unitId);
|
|
10729
|
+
if (!unit) return null;
|
|
10730
|
+
const atlasUrl = unitAtlasUrl(unit);
|
|
10731
|
+
if (!atlasUrl) return null;
|
|
10732
|
+
const atlas = atlasCacheRef.current.get(atlasUrl);
|
|
10733
|
+
if (!atlas) return null;
|
|
10734
|
+
const state = animStatesRef.current.get(unitId);
|
|
10735
|
+
const animation = state?.animation ?? "idle";
|
|
10736
|
+
const direction = state?.direction ?? "se";
|
|
10737
|
+
const elapsed = state?.elapsed ?? 0;
|
|
10738
|
+
const def = atlas.animations[animation] ?? atlas.animations.idle;
|
|
10739
|
+
if (!def) return null;
|
|
10740
|
+
const { sheetDir, flipX } = resolveSheetDirection(direction);
|
|
10741
|
+
const rel = atlas.sheets[sheetDir] ?? atlas.sheets.se ?? atlas.sheets.sw;
|
|
10742
|
+
if (!rel) return null;
|
|
10743
|
+
const sheetUrl = resolveSheetUrl(atlasUrl, rel);
|
|
10744
|
+
const isIdle = animation === "idle";
|
|
10745
|
+
const frame = isIdle ? 0 : getCurrentFrameFromDef(def, elapsed).frame;
|
|
10746
|
+
const rect = frameRect(frame, def.row, atlas.columns, atlas.frameWidth, atlas.frameHeight);
|
|
10747
|
+
return {
|
|
10748
|
+
sheetUrl,
|
|
10749
|
+
sx: rect.sx,
|
|
10750
|
+
sy: rect.sy,
|
|
10751
|
+
sw: rect.sw,
|
|
10752
|
+
sh: rect.sh,
|
|
10753
|
+
flipX,
|
|
10754
|
+
applyBreathing: isIdle
|
|
10755
|
+
};
|
|
10756
|
+
}, [units]);
|
|
10757
|
+
return { sheetUrls, resolveUnitFrame, pendingCount };
|
|
10758
|
+
}
|
|
10759
|
+
var WALK_HOLD_MS;
|
|
10760
|
+
var init_useUnitSpriteAtlas = __esm({
|
|
10761
|
+
"components/game/molecules/useUnitSpriteAtlas.ts"() {
|
|
10762
|
+
"use client";
|
|
10763
|
+
init_spriteAnimation();
|
|
10764
|
+
WALK_HOLD_MS = 600;
|
|
10765
|
+
}
|
|
10766
|
+
});
|
|
10567
10767
|
|
|
10568
10768
|
// components/game/organisms/utils/isometric.ts
|
|
10569
10769
|
function isoToScreen(tileX, tileY, scale, baseOffsetX) {
|
|
@@ -10678,6 +10878,10 @@ function IsometricCanvas({
|
|
|
10678
10878
|
() => unitsProp.map((u) => u.position ? u : { ...u, position: { x: u.x ?? 0, y: u.y ?? 0 } }),
|
|
10679
10879
|
[unitsProp]
|
|
10680
10880
|
);
|
|
10881
|
+
const { sheetUrls: atlasSheetUrls, resolveUnitFrame: resolveUnitFrameInternal, pendingCount: atlasPending } = useUnitSpriteAtlas(units);
|
|
10882
|
+
const resolveFrameForUnit = React81.useCallback((unitId) => {
|
|
10883
|
+
return resolveUnitFrame?.(unitId) ?? resolveUnitFrameInternal(unitId);
|
|
10884
|
+
}, [resolveUnitFrame, resolveUnitFrameInternal]);
|
|
10681
10885
|
const features = React81.useMemo(
|
|
10682
10886
|
() => featuresProp.map((f3) => {
|
|
10683
10887
|
if (f3.type) return f3;
|
|
@@ -10761,9 +10965,10 @@ function IsometricCanvas({
|
|
|
10761
10965
|
}
|
|
10762
10966
|
}
|
|
10763
10967
|
if (effectSpriteUrls) urls.push(...effectSpriteUrls);
|
|
10968
|
+
if (atlasSheetUrls.length) urls.push(...atlasSheetUrls);
|
|
10764
10969
|
if (backgroundImage) urls.push(backgroundImage);
|
|
10765
10970
|
return [...new Set(urls.filter(Boolean))];
|
|
10766
|
-
}, [sortedTiles, features, units, getTerrainSprite, getFeatureSprite, getUnitSprite, effectSpriteUrls, backgroundImage, assetManifest, resolveManifestUrl]);
|
|
10971
|
+
}, [sortedTiles, features, units, getTerrainSprite, getFeatureSprite, getUnitSprite, effectSpriteUrls, atlasSheetUrls, backgroundImage, assetManifest, resolveManifestUrl]);
|
|
10767
10972
|
const { getImage, pendingCount } = useImageCache(spriteUrls);
|
|
10768
10973
|
React81.useEffect(() => {
|
|
10769
10974
|
if (typeof window === "undefined") return;
|
|
@@ -11050,7 +11255,7 @@ function IsometricCanvas({
|
|
|
11050
11255
|
ctx.lineWidth = 3;
|
|
11051
11256
|
ctx.stroke();
|
|
11052
11257
|
}
|
|
11053
|
-
const frame =
|
|
11258
|
+
const frame = resolveFrameForUnit(unit.id);
|
|
11054
11259
|
const frameImg = frame ? getImage(frame.sheetUrl) : null;
|
|
11055
11260
|
if (frame && frameImg) {
|
|
11056
11261
|
const frameAr = frame.sw / frame.sh;
|
|
@@ -11160,7 +11365,7 @@ function IsometricCanvas({
|
|
|
11160
11365
|
resolveTerrainSpriteUrl,
|
|
11161
11366
|
resolveFeatureSpriteUrl,
|
|
11162
11367
|
resolveUnitSpriteUrl,
|
|
11163
|
-
|
|
11368
|
+
resolveFrameForUnit,
|
|
11164
11369
|
getImage,
|
|
11165
11370
|
gridWidth,
|
|
11166
11371
|
gridHeight,
|
|
@@ -11192,7 +11397,7 @@ function IsometricCanvas({
|
|
|
11192
11397
|
};
|
|
11193
11398
|
}, [selectedUnitId, units, scale, baseOffsetX, scaledTileWidth, scaledDiamondTopY, scaledFloorHeight, viewportSize, targetCameraRef]);
|
|
11194
11399
|
React81.useEffect(() => {
|
|
11195
|
-
const hasAnimations = units.length > 0 || validMoves.length > 0 || attackTargets.length > 0 || selectedUnitId != null || targetCameraRef.current != null || hasActiveEffects2 || pendingCount > 0;
|
|
11400
|
+
const hasAnimations = units.length > 0 || validMoves.length > 0 || attackTargets.length > 0 || selectedUnitId != null || targetCameraRef.current != null || hasActiveEffects2 || pendingCount > 0 || atlasPending > 0;
|
|
11196
11401
|
draw(animTimeRef.current);
|
|
11197
11402
|
if (!hasAnimations) return;
|
|
11198
11403
|
let running = true;
|
|
@@ -11208,7 +11413,7 @@ function IsometricCanvas({
|
|
|
11208
11413
|
running = false;
|
|
11209
11414
|
cancelAnimationFrame(rafIdRef.current);
|
|
11210
11415
|
};
|
|
11211
|
-
}, [draw, units.length, validMoves.length, attackTargets.length, selectedUnitId, hasActiveEffects2, pendingCount, lerpToTarget, targetCameraRef]);
|
|
11416
|
+
}, [draw, units.length, validMoves.length, attackTargets.length, selectedUnitId, hasActiveEffects2, pendingCount, atlasPending, lerpToTarget, targetCameraRef]);
|
|
11212
11417
|
const handleMouseMoveWithCamera = React81.useCallback((e) => {
|
|
11213
11418
|
if (enableCamera) {
|
|
11214
11419
|
const wasPanning = handleMouseMove(e, () => draw(animTimeRef.current));
|
|
@@ -11353,6 +11558,7 @@ var init_IsometricCanvas = __esm({
|
|
|
11353
11558
|
init_ErrorState();
|
|
11354
11559
|
init_useImageCache();
|
|
11355
11560
|
init_useCamera();
|
|
11561
|
+
init_useUnitSpriteAtlas();
|
|
11356
11562
|
init_verificationRegistry();
|
|
11357
11563
|
init_isometric();
|
|
11358
11564
|
IsometricCanvas.displayName = "IsometricCanvas";
|
|
@@ -28935,13 +29141,13 @@ var init_MapView = __esm({
|
|
|
28935
29141
|
shadowSize: [41, 41]
|
|
28936
29142
|
});
|
|
28937
29143
|
L.Marker.prototype.options.icon = defaultIcon;
|
|
28938
|
-
const { useEffect:
|
|
29144
|
+
const { useEffect: useEffect81, useRef: useRef75, useCallback: useCallback119, useState: useState113 } = React81__namespace.default;
|
|
28939
29145
|
const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
|
|
28940
29146
|
const { useEventBus: useEventBus4 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
|
|
28941
29147
|
function MapUpdater({ centerLat, centerLng, zoom }) {
|
|
28942
29148
|
const map = useMap();
|
|
28943
|
-
const prevRef =
|
|
28944
|
-
|
|
29149
|
+
const prevRef = useRef75({ centerLat, centerLng, zoom });
|
|
29150
|
+
useEffect81(() => {
|
|
28945
29151
|
const prev = prevRef.current;
|
|
28946
29152
|
if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
|
|
28947
29153
|
map.setView([centerLat, centerLng], zoom);
|
|
@@ -28952,7 +29158,7 @@ var init_MapView = __esm({
|
|
|
28952
29158
|
}
|
|
28953
29159
|
function MapClickHandler({ onMapClick }) {
|
|
28954
29160
|
const map = useMap();
|
|
28955
|
-
|
|
29161
|
+
useEffect81(() => {
|
|
28956
29162
|
if (!onMapClick) return;
|
|
28957
29163
|
const handler = (e) => {
|
|
28958
29164
|
onMapClick(e.latlng.lat, e.latlng.lng);
|
|
@@ -28980,8 +29186,8 @@ var init_MapView = __esm({
|
|
|
28980
29186
|
showAttribution = true
|
|
28981
29187
|
}) {
|
|
28982
29188
|
const eventBus = useEventBus4();
|
|
28983
|
-
const [clickedPosition, setClickedPosition] =
|
|
28984
|
-
const handleMapClick =
|
|
29189
|
+
const [clickedPosition, setClickedPosition] = useState113(null);
|
|
29190
|
+
const handleMapClick = useCallback119((lat, lng) => {
|
|
28985
29191
|
if (showClickedPin) {
|
|
28986
29192
|
setClickedPosition({ lat, lng });
|
|
28987
29193
|
}
|
|
@@ -28990,7 +29196,7 @@ var init_MapView = __esm({
|
|
|
28990
29196
|
eventBus.emit(`UI:${mapClickEvent}`, { latitude: lat, longitude: lng });
|
|
28991
29197
|
}
|
|
28992
29198
|
}, [onMapClick, mapClickEvent, eventBus, showClickedPin]);
|
|
28993
|
-
const handleMarkerClick =
|
|
29199
|
+
const handleMarkerClick = useCallback119((marker) => {
|
|
28994
29200
|
onMarkerClick?.(marker);
|
|
28995
29201
|
if (markerClickEvent) {
|
|
28996
29202
|
eventBus.emit(`UI:${markerClickEvent}`, { ...marker });
|
|
@@ -40708,7 +40914,7 @@ var init_AssetLoader = __esm({
|
|
|
40708
40914
|
__publicField(this, "textureCache");
|
|
40709
40915
|
__publicField(this, "loadingPromises");
|
|
40710
40916
|
this.objLoader = new OBJLoader_js.OBJLoader();
|
|
40711
|
-
this.textureLoader = new
|
|
40917
|
+
this.textureLoader = new THREE3__namespace.TextureLoader();
|
|
40712
40918
|
this.modelCache = /* @__PURE__ */ new Map();
|
|
40713
40919
|
this.textureCache = /* @__PURE__ */ new Map();
|
|
40714
40920
|
this.loadingPromises = /* @__PURE__ */ new Map();
|
|
@@ -40782,7 +40988,7 @@ var init_AssetLoader = __esm({
|
|
|
40782
40988
|
return this.loadingPromises.get(`texture:${url}`);
|
|
40783
40989
|
}
|
|
40784
40990
|
const loadPromise = this.textureLoader.loadAsync(url).then((texture) => {
|
|
40785
|
-
texture.colorSpace =
|
|
40991
|
+
texture.colorSpace = THREE3__namespace.SRGBColorSpace;
|
|
40786
40992
|
this.textureCache.set(url, texture);
|
|
40787
40993
|
this.loadingPromises.delete(`texture:${url}`);
|
|
40788
40994
|
return texture;
|
|
@@ -40856,7 +41062,7 @@ var init_AssetLoader = __esm({
|
|
|
40856
41062
|
});
|
|
40857
41063
|
this.modelCache.forEach((model) => {
|
|
40858
41064
|
model.scene.traverse((child) => {
|
|
40859
|
-
if (child instanceof
|
|
41065
|
+
if (child instanceof THREE3__namespace.Mesh) {
|
|
40860
41066
|
child.geometry.dispose();
|
|
40861
41067
|
if (Array.isArray(child.material)) {
|
|
40862
41068
|
child.material.forEach((m) => m.dispose());
|
|
@@ -41412,7 +41618,7 @@ function ModelLoader({
|
|
|
41412
41618
|
if (!loadedModel) return null;
|
|
41413
41619
|
const cloned = loadedModel.clone();
|
|
41414
41620
|
cloned.traverse((child) => {
|
|
41415
|
-
if (child instanceof
|
|
41621
|
+
if (child instanceof THREE3__namespace.Mesh) {
|
|
41416
41622
|
child.castShadow = castShadow;
|
|
41417
41623
|
child.receiveShadow = receiveShadow;
|
|
41418
41624
|
}
|
|
@@ -41526,6 +41732,47 @@ function CameraController({
|
|
|
41526
41732
|
}, [camera.position, onCameraChange]);
|
|
41527
41733
|
return null;
|
|
41528
41734
|
}
|
|
41735
|
+
function UnitSpriteBillboard({
|
|
41736
|
+
sheetUrl,
|
|
41737
|
+
resolveFrame,
|
|
41738
|
+
height = 1.2
|
|
41739
|
+
}) {
|
|
41740
|
+
const texture = fiber.useLoader(THREE3__namespace.TextureLoader, sheetUrl);
|
|
41741
|
+
const meshRef = React81.useRef(null);
|
|
41742
|
+
const matRef = React81.useRef(null);
|
|
41743
|
+
const [aspect, setAspect] = React81.useState(1);
|
|
41744
|
+
fiber.useFrame(() => {
|
|
41745
|
+
const frame = resolveFrame();
|
|
41746
|
+
if (!frame || !texture.image) return;
|
|
41747
|
+
const imgW = texture.image.width;
|
|
41748
|
+
const imgH = texture.image.height;
|
|
41749
|
+
if (!imgW || !imgH) return;
|
|
41750
|
+
texture.repeat.set((frame.flipX ? -1 : 1) * (frame.sw / imgW), frame.sh / imgH);
|
|
41751
|
+
texture.offset.set(
|
|
41752
|
+
frame.flipX ? (frame.sx + frame.sw) / imgW : frame.sx / imgW,
|
|
41753
|
+
1 - (frame.sy + frame.sh) / imgH
|
|
41754
|
+
);
|
|
41755
|
+
texture.magFilter = THREE3__namespace.NearestFilter;
|
|
41756
|
+
texture.minFilter = THREE3__namespace.NearestFilter;
|
|
41757
|
+
texture.needsUpdate = true;
|
|
41758
|
+
const nextAspect = frame.sw / frame.sh;
|
|
41759
|
+
if (Math.abs(nextAspect - aspect) > 1e-3) setAspect(nextAspect);
|
|
41760
|
+
if (matRef.current) matRef.current.needsUpdate = true;
|
|
41761
|
+
});
|
|
41762
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("mesh", { ref: meshRef, position: [0, height / 2, 0], children: [
|
|
41763
|
+
/* @__PURE__ */ jsxRuntime.jsx("planeGeometry", { args: [height * aspect, height] }),
|
|
41764
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
41765
|
+
"meshBasicMaterial",
|
|
41766
|
+
{
|
|
41767
|
+
ref: matRef,
|
|
41768
|
+
map: texture,
|
|
41769
|
+
transparent: true,
|
|
41770
|
+
alphaTest: 0.1,
|
|
41771
|
+
side: THREE3__namespace.DoubleSide
|
|
41772
|
+
}
|
|
41773
|
+
)
|
|
41774
|
+
] });
|
|
41775
|
+
}
|
|
41529
41776
|
var DEFAULT_GRID_CONFIG, GameCanvas3D;
|
|
41530
41777
|
var init_GameCanvas3D2 = __esm({
|
|
41531
41778
|
"components/game/molecules/GameCanvas3D.tsx"() {
|
|
@@ -41536,6 +41783,7 @@ var init_GameCanvas3D2 = __esm({
|
|
|
41536
41783
|
init_Canvas3DLoadingState2();
|
|
41537
41784
|
init_Canvas3DErrorBoundary2();
|
|
41538
41785
|
init_ModelLoader();
|
|
41786
|
+
init_useUnitSpriteAtlas();
|
|
41539
41787
|
init_cn();
|
|
41540
41788
|
init_GameCanvas3D();
|
|
41541
41789
|
DEFAULT_GRID_CONFIG = {
|
|
@@ -41592,8 +41840,10 @@ var init_GameCanvas3D2 = __esm({
|
|
|
41592
41840
|
const controlsRef = React81.useRef(null);
|
|
41593
41841
|
const [hoveredTile, setHoveredTile] = React81.useState(null);
|
|
41594
41842
|
const [internalError, setInternalError] = React81.useState(null);
|
|
41843
|
+
const { sheetUrls: atlasSheetUrls, resolveUnitFrame } = useUnitSpriteAtlas(units);
|
|
41844
|
+
const preloadUrls = React81.useMemo(() => [...preloadAssets, ...atlasSheetUrls], [preloadAssets, atlasSheetUrls]);
|
|
41595
41845
|
const { isLoading: assetsLoading, progress, loaded, total } = useAssetLoader({
|
|
41596
|
-
preloadUrls
|
|
41846
|
+
preloadUrls,
|
|
41597
41847
|
loader: customAssetLoader
|
|
41598
41848
|
});
|
|
41599
41849
|
const eventHandlers = useGameCanvas3DEvents({
|
|
@@ -41652,7 +41902,7 @@ var init_GameCanvas3D2 = __esm({
|
|
|
41652
41902
|
getCameraPosition: () => {
|
|
41653
41903
|
if (controlsRef.current) {
|
|
41654
41904
|
const pos = controlsRef.current.object.position;
|
|
41655
|
-
return new
|
|
41905
|
+
return new THREE3__namespace.Vector3(pos.x, pos.y, pos.z);
|
|
41656
41906
|
}
|
|
41657
41907
|
return null;
|
|
41658
41908
|
},
|
|
@@ -41804,6 +42054,8 @@ var init_GameCanvas3D2 = __esm({
|
|
|
41804
42054
|
({ unit, position }) => {
|
|
41805
42055
|
const isSelected = selectedUnitId === unit.id;
|
|
41806
42056
|
const color = unit.faction === "player" ? 4491519 : unit.faction === "enemy" ? 16729156 : 16777028;
|
|
42057
|
+
const hasAtlas = unitAtlasUrl(unit) !== null;
|
|
42058
|
+
const initialFrame = hasAtlas ? resolveUnitFrame(unit.id) : null;
|
|
41807
42059
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
41808
42060
|
"group",
|
|
41809
42061
|
{
|
|
@@ -41815,7 +42067,16 @@ var init_GameCanvas3D2 = __esm({
|
|
|
41815
42067
|
/* @__PURE__ */ jsxRuntime.jsx("ringGeometry", { args: [0.4, 0.5, 32] }),
|
|
41816
42068
|
/* @__PURE__ */ jsxRuntime.jsx("meshBasicMaterial", { color: "#ffff00", transparent: true, opacity: 0.8 })
|
|
41817
42069
|
] }),
|
|
41818
|
-
|
|
42070
|
+
hasAtlas && initialFrame ? (
|
|
42071
|
+
/* Animated sprite-sheet billboard — single cropped frame, by state */
|
|
42072
|
+
/* @__PURE__ */ jsxRuntime.jsx(drei.Billboard, { children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
42073
|
+
UnitSpriteBillboard,
|
|
42074
|
+
{
|
|
42075
|
+
sheetUrl: initialFrame.sheetUrl,
|
|
42076
|
+
resolveFrame: () => resolveUnitFrame(unit.id)
|
|
42077
|
+
}
|
|
42078
|
+
) })
|
|
42079
|
+
) : unit.modelUrl ? (
|
|
41819
42080
|
/* GLB unit model (box fallback while loading / on error) */
|
|
41820
42081
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
41821
42082
|
ModelLoader,
|
|
@@ -41869,7 +42130,7 @@ var init_GameCanvas3D2 = __esm({
|
|
|
41869
42130
|
}
|
|
41870
42131
|
);
|
|
41871
42132
|
},
|
|
41872
|
-
[selectedUnitId, handleUnitClick]
|
|
42133
|
+
[selectedUnitId, handleUnitClick, resolveUnitFrame]
|
|
41873
42134
|
);
|
|
41874
42135
|
const DefaultFeatureRenderer = React81.useCallback(
|
|
41875
42136
|
({
|
|
@@ -51198,6 +51459,28 @@ init_verificationRegistry();
|
|
|
51198
51459
|
var crossTraitLog = logger.createLogger("almadar:ui:cross-trait");
|
|
51199
51460
|
var flushLog = logger.createLogger("almadar:ui:slot-flush");
|
|
51200
51461
|
var stateLog = logger.createLogger("almadar:ui:state-transitions");
|
|
51462
|
+
var tickLog = logger.createLogger("almadar:ui:tick-effects");
|
|
51463
|
+
var SYNC_TICK_OPERATORS = /* @__PURE__ */ new Set([
|
|
51464
|
+
"set",
|
|
51465
|
+
"emit",
|
|
51466
|
+
"render-ui",
|
|
51467
|
+
"render",
|
|
51468
|
+
"navigate",
|
|
51469
|
+
"notify",
|
|
51470
|
+
"log",
|
|
51471
|
+
// Synchronous structural forms that wrap sync effects. A tick authored as
|
|
51472
|
+
// a single top-level `(let ((..)) (do (set ..) (render-ui ..)))` or
|
|
51473
|
+
// `(if cond (set ..) ..)` is one of these at its head — the EffectExecutor
|
|
51474
|
+
// resolves the binding values / condition through the canonical evaluator
|
|
51475
|
+
// and runs the wrapped sync effects. Without these in the allow-list the
|
|
51476
|
+
// whole tick is filtered out (its head op isn't `set`/`emit`/...), so the
|
|
51477
|
+
// physics/gameflow/AI tick never runs.
|
|
51478
|
+
"let",
|
|
51479
|
+
"if",
|
|
51480
|
+
"do",
|
|
51481
|
+
"when"
|
|
51482
|
+
]);
|
|
51483
|
+
var LIFECYCLE_EVENTS = ["INIT", "LOAD", "$MOUNT"];
|
|
51201
51484
|
function toTraitDefinition(binding) {
|
|
51202
51485
|
return {
|
|
51203
51486
|
name: binding.trait.name,
|
|
@@ -51230,6 +51513,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
51230
51513
|
});
|
|
51231
51514
|
const eventQueueRef = React81.useRef([]);
|
|
51232
51515
|
const processingRef = React81.useRef(false);
|
|
51516
|
+
const initedTraitsRef = React81.useRef(/* @__PURE__ */ new Set());
|
|
51233
51517
|
const traitBindingsRef = React81.useRef(traitBindings);
|
|
51234
51518
|
const managerRef = React81.useRef(manager);
|
|
51235
51519
|
const uiSlotsRef = React81.useRef(uiSlots);
|
|
@@ -51394,50 +51678,196 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
51394
51678
|
for (const unreg of snapshotUnregs) unreg();
|
|
51395
51679
|
};
|
|
51396
51680
|
}, [traitBindings]);
|
|
51397
|
-
const
|
|
51398
|
-
const
|
|
51399
|
-
|
|
51400
|
-
const
|
|
51401
|
-
|
|
51402
|
-
|
|
51403
|
-
|
|
51404
|
-
|
|
51405
|
-
if (
|
|
51406
|
-
const passed = runtime.interpolateValue(tick.guard, evalCtx);
|
|
51407
|
-
if (!passed) return;
|
|
51408
|
-
}
|
|
51681
|
+
const executeTransitionEffects = React81.useCallback(async (params) => {
|
|
51682
|
+
const { binding, previousState, newState, payload, flushEvent, syncOnly, log: log14 } = params;
|
|
51683
|
+
const traitName = binding.trait.name;
|
|
51684
|
+
const linkedEntity = binding.linkedEntity || "";
|
|
51685
|
+
const entityId = payload?.entityId;
|
|
51686
|
+
const effects = syncOnly ? params.effects.filter(
|
|
51687
|
+
(e) => Array.isArray(e) && SYNC_TICK_OPERATORS.has(String(e[0]))
|
|
51688
|
+
) : params.effects;
|
|
51689
|
+
if (effects.length === 0) return [];
|
|
51409
51690
|
const pendingSlots = /* @__PURE__ */ new Map();
|
|
51410
51691
|
({
|
|
51411
|
-
trait: binding.trait.name,
|
|
51412
|
-
transition: `${currentState}->tick:${tick.name}`,
|
|
51413
|
-
effects: tick.effects,
|
|
51414
51692
|
traitDefinition: binding.trait
|
|
51415
51693
|
});
|
|
51416
|
-
|
|
51417
|
-
|
|
51418
|
-
|
|
51419
|
-
|
|
51420
|
-
|
|
51421
|
-
|
|
51422
|
-
|
|
51694
|
+
const clientHandlers = createClientEffectHandlers({
|
|
51695
|
+
eventBus,
|
|
51696
|
+
slotSetter: {
|
|
51697
|
+
addPattern: (slot, pattern, props) => {
|
|
51698
|
+
const existing = pendingSlots.get(slot) || [];
|
|
51699
|
+
existing.push({ pattern, props: props || {} });
|
|
51700
|
+
pendingSlots.set(slot, existing);
|
|
51701
|
+
},
|
|
51702
|
+
clearSlot: (slot) => {
|
|
51423
51703
|
pendingSlots.set(slot, []);
|
|
51424
|
-
continue;
|
|
51425
51704
|
}
|
|
51426
|
-
|
|
51427
|
-
|
|
51428
|
-
|
|
51429
|
-
|
|
51430
|
-
|
|
51705
|
+
},
|
|
51706
|
+
navigate: optionsRef.current?.navigate,
|
|
51707
|
+
notify: optionsRef.current?.notify,
|
|
51708
|
+
callService: optionsRef.current?.callService
|
|
51709
|
+
});
|
|
51710
|
+
const persistence = syncOnly ? void 0 : optionsRef.current?.persistence;
|
|
51711
|
+
let handlers = clientHandlers;
|
|
51712
|
+
if (persistence) {
|
|
51713
|
+
const sharedBindings = {
|
|
51714
|
+
// Seed `@entity` from the trait's scalar field state (a real
|
|
51715
|
+
// EntityRow), the same source the executor's own bindingCtx
|
|
51716
|
+
// uses below. `@payload.*` resolves from `payload` separately,
|
|
51717
|
+
// so dropping the prior `payload as EntityRow` cast loses
|
|
51718
|
+
// nothing — it just stops mislabelling the payload as an entity.
|
|
51719
|
+
entity: traitFieldStatesRef.current.get(traitName) ?? {},
|
|
51720
|
+
payload: payload || {},
|
|
51721
|
+
state: previousState
|
|
51722
|
+
};
|
|
51723
|
+
const sharedDeclared = runtime.collectDeclaredConfigDefaults(binding.trait);
|
|
51724
|
+
const sharedCallSite = binding.config;
|
|
51725
|
+
if (sharedDeclared || sharedCallSite) {
|
|
51726
|
+
sharedBindings.config = {
|
|
51727
|
+
...sharedDeclared ?? {},
|
|
51728
|
+
...sharedCallSite ?? {}
|
|
51729
|
+
};
|
|
51431
51730
|
}
|
|
51731
|
+
const serverHandlers = runtime.createServerEffectHandlers({
|
|
51732
|
+
persistence,
|
|
51733
|
+
eventBus,
|
|
51734
|
+
entityType: linkedEntity,
|
|
51735
|
+
entityId,
|
|
51736
|
+
bindings: sharedBindings,
|
|
51737
|
+
context: {
|
|
51738
|
+
traitName,
|
|
51739
|
+
state: previousState,
|
|
51740
|
+
transition: `${previousState}->${newState}`,
|
|
51741
|
+
linkedEntity,
|
|
51742
|
+
entityId
|
|
51743
|
+
},
|
|
51744
|
+
source: { trait: traitName },
|
|
51745
|
+
callService: optionsRef.current?.callService
|
|
51746
|
+
});
|
|
51747
|
+
handlers = {
|
|
51748
|
+
...serverHandlers,
|
|
51749
|
+
emit: clientHandlers.emit,
|
|
51750
|
+
renderUI: clientHandlers.renderUI,
|
|
51751
|
+
navigate: clientHandlers.navigate,
|
|
51752
|
+
notify: clientHandlers.notify
|
|
51753
|
+
};
|
|
51754
|
+
}
|
|
51755
|
+
const baseSet = handlers.set;
|
|
51756
|
+
handlers = {
|
|
51757
|
+
...handlers,
|
|
51758
|
+
set: async (targetId, field, value) => {
|
|
51759
|
+
let fieldState = traitFieldStatesRef.current.get(traitName);
|
|
51760
|
+
if (!fieldState) {
|
|
51761
|
+
fieldState = {};
|
|
51762
|
+
traitFieldStatesRef.current.set(traitName, fieldState);
|
|
51763
|
+
}
|
|
51764
|
+
fieldState[field] = value;
|
|
51765
|
+
log14.debug("set:write", {
|
|
51766
|
+
traitName,
|
|
51767
|
+
field,
|
|
51768
|
+
value: JSON.stringify(value),
|
|
51769
|
+
transition: `${previousState}->${newState}`
|
|
51770
|
+
});
|
|
51771
|
+
if (baseSet) await baseSet(targetId, field, value);
|
|
51772
|
+
}
|
|
51773
|
+
};
|
|
51774
|
+
const entityForBinding = traitFieldStatesRef.current.get(traitName) ?? {};
|
|
51775
|
+
const bindingCtx = {
|
|
51776
|
+
entity: entityForBinding,
|
|
51777
|
+
payload: payload || {},
|
|
51778
|
+
state: previousState
|
|
51779
|
+
};
|
|
51780
|
+
const declaredDefaults = runtime.collectDeclaredConfigDefaults(binding.trait);
|
|
51781
|
+
const callSiteConfig = binding.config;
|
|
51782
|
+
if (declaredDefaults || callSiteConfig) {
|
|
51783
|
+
bindingCtx.config = {
|
|
51784
|
+
...declaredDefaults ?? {},
|
|
51785
|
+
...callSiteConfig ?? {}
|
|
51786
|
+
};
|
|
51432
51787
|
}
|
|
51433
|
-
|
|
51434
|
-
|
|
51435
|
-
|
|
51436
|
-
|
|
51437
|
-
|
|
51788
|
+
const effectContext = {
|
|
51789
|
+
traitName,
|
|
51790
|
+
state: previousState,
|
|
51791
|
+
transition: `${previousState}->${newState}`,
|
|
51792
|
+
linkedEntity,
|
|
51793
|
+
entityId
|
|
51794
|
+
};
|
|
51795
|
+
const emittedDuringExec = [];
|
|
51796
|
+
const baseEmit = handlers.emit;
|
|
51797
|
+
const trackingHandlers = {
|
|
51798
|
+
...handlers,
|
|
51799
|
+
emit: (event, eventPayload, source) => {
|
|
51800
|
+
emittedDuringExec.push(event);
|
|
51801
|
+
baseEmit(event, eventPayload, source);
|
|
51802
|
+
}
|
|
51803
|
+
};
|
|
51804
|
+
const executor = new runtime.EffectExecutor({ handlers: trackingHandlers, bindings: bindingCtx, context: effectContext });
|
|
51805
|
+
try {
|
|
51806
|
+
await executor.executeAll(effects);
|
|
51807
|
+
log14.debug("effects:executed", () => ({
|
|
51808
|
+
traitName,
|
|
51809
|
+
transition: `${previousState}->${newState}`,
|
|
51810
|
+
event: flushEvent,
|
|
51811
|
+
effectCount: effects.length,
|
|
51812
|
+
emitted: emittedDuringExec.join(","),
|
|
51813
|
+
entityAfter: JSON.stringify(traitFieldStatesRef.current.get(traitName) ?? {}),
|
|
51814
|
+
slotsTouched: Array.from(pendingSlots.keys()).join(",")
|
|
51815
|
+
}));
|
|
51816
|
+
for (const [slot, patterns] of pendingSlots) {
|
|
51817
|
+
log14.debug("flush:slot", {
|
|
51818
|
+
traitName,
|
|
51819
|
+
slot,
|
|
51820
|
+
patternCount: patterns.length,
|
|
51821
|
+
event: flushEvent,
|
|
51822
|
+
transition: `${previousState}->${newState}`,
|
|
51823
|
+
cleared: patterns.length === 0
|
|
51824
|
+
});
|
|
51825
|
+
flushSlot(traitName, slot, patterns, {
|
|
51826
|
+
event: flushEvent,
|
|
51827
|
+
state: previousState,
|
|
51828
|
+
entity: binding.linkedEntity
|
|
51829
|
+
});
|
|
51830
|
+
}
|
|
51831
|
+
} catch (error) {
|
|
51832
|
+
log14.error("effects:error", {
|
|
51833
|
+
traitName,
|
|
51834
|
+
transition: `${previousState}->${newState}`,
|
|
51835
|
+
event: flushEvent,
|
|
51836
|
+
error: error instanceof Error ? error.message : String(error),
|
|
51837
|
+
effectCount: effects.length
|
|
51438
51838
|
});
|
|
51439
51839
|
}
|
|
51440
|
-
|
|
51840
|
+
return emittedDuringExec;
|
|
51841
|
+
}, [eventBus, flushSlot]);
|
|
51842
|
+
const runTickEffects = React81.useCallback((tick, binding) => {
|
|
51843
|
+
const traitName = binding.trait.name;
|
|
51844
|
+
const currentState = traitStatesRef.current.get(traitName)?.currentState ?? "";
|
|
51845
|
+
if (tick.appliesTo.length > 0 && !tick.appliesTo.includes(currentState)) return;
|
|
51846
|
+
if (tick.guard !== void 0) {
|
|
51847
|
+
const guardCtx = {
|
|
51848
|
+
entity: traitFieldStatesRef.current.get(traitName) ?? {},
|
|
51849
|
+
payload: {},
|
|
51850
|
+
state: currentState
|
|
51851
|
+
};
|
|
51852
|
+
if (binding.config) {
|
|
51853
|
+
guardCtx.config = binding.config;
|
|
51854
|
+
}
|
|
51855
|
+
const passed = runtime.interpolateValue(tick.guard, runtime.createContextFromBindings(guardCtx));
|
|
51856
|
+
if (!passed) {
|
|
51857
|
+
tickLog.debug("guard-blocked", { traitName, tick: tick.name, state: currentState });
|
|
51858
|
+
return;
|
|
51859
|
+
}
|
|
51860
|
+
}
|
|
51861
|
+
void executeTransitionEffects({
|
|
51862
|
+
binding,
|
|
51863
|
+
effects: tick.effects,
|
|
51864
|
+
previousState: currentState,
|
|
51865
|
+
newState: currentState,
|
|
51866
|
+
flushEvent: `tick:${tick.name}`,
|
|
51867
|
+
syncOnly: true,
|
|
51868
|
+
log: tickLog
|
|
51869
|
+
});
|
|
51870
|
+
}, [executeTransitionEffects]);
|
|
51441
51871
|
React81.useEffect(() => {
|
|
51442
51872
|
const hasFrameTicks = traitBindingsRef.current.some(
|
|
51443
51873
|
(b) => b.trait.ticks?.some((t) => t.interval === "frame")
|
|
@@ -51548,161 +51978,17 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
51548
51978
|
transition: `${result.previousState} -> ${result.newState}`,
|
|
51549
51979
|
effects: JSON.stringify(result.effects)
|
|
51550
51980
|
}));
|
|
51551
|
-
const
|
|
51552
|
-
|
|
51553
|
-
const pendingSlots = /* @__PURE__ */ new Map();
|
|
51554
|
-
const slotSource = {
|
|
51555
|
-
trait: binding.trait.name,
|
|
51556
|
-
state: result.previousState,
|
|
51557
|
-
transition: `${result.previousState}->${result.newState}`,
|
|
51981
|
+
const emittedDuringExec = await executeTransitionEffects({
|
|
51982
|
+
binding,
|
|
51558
51983
|
effects: result.effects,
|
|
51559
|
-
|
|
51560
|
-
|
|
51561
|
-
|
|
51562
|
-
|
|
51563
|
-
|
|
51564
|
-
|
|
51565
|
-
const existing = pendingSlots.get(slot) || [];
|
|
51566
|
-
existing.push({ pattern, props: props || {} });
|
|
51567
|
-
pendingSlots.set(slot, existing);
|
|
51568
|
-
},
|
|
51569
|
-
clearSlot: (slot) => {
|
|
51570
|
-
pendingSlots.set(slot, []);
|
|
51571
|
-
}
|
|
51572
|
-
},
|
|
51573
|
-
navigate: optionsRef.current?.navigate,
|
|
51574
|
-
notify: optionsRef.current?.notify,
|
|
51575
|
-
callService: optionsRef.current?.callService
|
|
51984
|
+
previousState: result.previousState,
|
|
51985
|
+
newState: result.newState,
|
|
51986
|
+
payload,
|
|
51987
|
+
flushEvent: eventKey,
|
|
51988
|
+
syncOnly: false,
|
|
51989
|
+
log: stateLog
|
|
51576
51990
|
});
|
|
51577
|
-
const persistence = optionsRef.current?.persistence;
|
|
51578
|
-
let handlers = clientHandlers;
|
|
51579
|
-
if (persistence) {
|
|
51580
|
-
const sharedBindings = {
|
|
51581
|
-
entity: payload ?? {},
|
|
51582
|
-
payload: payload || {},
|
|
51583
|
-
state: result.previousState
|
|
51584
|
-
};
|
|
51585
|
-
const sharedDeclared = runtime.collectDeclaredConfigDefaults(
|
|
51586
|
-
binding.trait
|
|
51587
|
-
);
|
|
51588
|
-
const sharedCallSite = binding.config;
|
|
51589
|
-
if (sharedDeclared || sharedCallSite) {
|
|
51590
|
-
sharedBindings.config = {
|
|
51591
|
-
...sharedDeclared ?? {},
|
|
51592
|
-
...sharedCallSite ?? {}
|
|
51593
|
-
};
|
|
51594
|
-
}
|
|
51595
|
-
const serverHandlers = runtime.createServerEffectHandlers({
|
|
51596
|
-
persistence,
|
|
51597
|
-
eventBus,
|
|
51598
|
-
entityType: linkedEntity,
|
|
51599
|
-
entityId,
|
|
51600
|
-
bindings: sharedBindings,
|
|
51601
|
-
context: {
|
|
51602
|
-
traitName: binding.trait.name,
|
|
51603
|
-
state: result.previousState,
|
|
51604
|
-
transition: `${result.previousState}->${result.newState}`,
|
|
51605
|
-
linkedEntity,
|
|
51606
|
-
entityId
|
|
51607
|
-
},
|
|
51608
|
-
source: { trait: binding.trait.name },
|
|
51609
|
-
callService: optionsRef.current?.callService
|
|
51610
|
-
});
|
|
51611
|
-
handlers = {
|
|
51612
|
-
...serverHandlers,
|
|
51613
|
-
// Client handlers own UI + emit: keep the slot setter
|
|
51614
|
-
// and pre-prefixed UI:* emit path intact.
|
|
51615
|
-
emit: clientHandlers.emit,
|
|
51616
|
-
renderUI: clientHandlers.renderUI,
|
|
51617
|
-
navigate: clientHandlers.navigate,
|
|
51618
|
-
notify: clientHandlers.notify
|
|
51619
|
-
};
|
|
51620
|
-
}
|
|
51621
|
-
const baseSet = handlers.set;
|
|
51622
|
-
handlers = {
|
|
51623
|
-
...handlers,
|
|
51624
|
-
set: async (targetId, field, value) => {
|
|
51625
|
-
let fieldState = traitFieldStatesRef.current.get(traitName);
|
|
51626
|
-
if (!fieldState) {
|
|
51627
|
-
fieldState = {};
|
|
51628
|
-
traitFieldStatesRef.current.set(traitName, fieldState);
|
|
51629
|
-
}
|
|
51630
|
-
fieldState[field] = value;
|
|
51631
|
-
if (baseSet) await baseSet(targetId, field, value);
|
|
51632
|
-
}
|
|
51633
|
-
};
|
|
51634
|
-
const entityForBinding = traitFieldStatesRef.current.get(traitName) ?? {};
|
|
51635
|
-
const bindingCtx = {
|
|
51636
|
-
entity: entityForBinding,
|
|
51637
|
-
payload: payload || {},
|
|
51638
|
-
state: result.previousState
|
|
51639
|
-
};
|
|
51640
|
-
const declaredDefaults = runtime.collectDeclaredConfigDefaults(
|
|
51641
|
-
binding.trait
|
|
51642
|
-
);
|
|
51643
|
-
const callSiteConfig = binding.config;
|
|
51644
|
-
if (declaredDefaults || callSiteConfig) {
|
|
51645
|
-
bindingCtx.config = {
|
|
51646
|
-
...declaredDefaults ?? {},
|
|
51647
|
-
...callSiteConfig ?? {}
|
|
51648
|
-
};
|
|
51649
|
-
}
|
|
51650
|
-
const effectContext = {
|
|
51651
|
-
traitName: binding.trait.name,
|
|
51652
|
-
state: result.previousState,
|
|
51653
|
-
transition: `${result.previousState}->${result.newState}`,
|
|
51654
|
-
linkedEntity,
|
|
51655
|
-
entityId
|
|
51656
|
-
};
|
|
51657
|
-
const emittedDuringExec = [];
|
|
51658
51991
|
emittedByTrait.set(traitName, emittedDuringExec);
|
|
51659
|
-
const baseEmit = handlers.emit;
|
|
51660
|
-
const trackingHandlers = {
|
|
51661
|
-
...handlers,
|
|
51662
|
-
emit: (event, eventPayload, source) => {
|
|
51663
|
-
emittedDuringExec.push(event);
|
|
51664
|
-
baseEmit(event, eventPayload, source);
|
|
51665
|
-
}
|
|
51666
|
-
};
|
|
51667
|
-
const executor = new runtime.EffectExecutor({ handlers: trackingHandlers, bindings: bindingCtx, context: effectContext });
|
|
51668
|
-
try {
|
|
51669
|
-
await executor.executeAll(result.effects);
|
|
51670
|
-
stateLog.debug("transition:render-ui-dispatched", () => ({
|
|
51671
|
-
traitName,
|
|
51672
|
-
fromState: result.previousState,
|
|
51673
|
-
toState: result.newState,
|
|
51674
|
-
event: eventKey,
|
|
51675
|
-
slotsTouched: Array.from(pendingSlots.keys()).join(","),
|
|
51676
|
-
patternTypes: Array.from(pendingSlots.entries()).map(
|
|
51677
|
-
([slot, patterns]) => `${slot}:[${patterns.map((p2) => p2.pattern?.type ?? "null").join(",")}]`
|
|
51678
|
-
).join(";")
|
|
51679
|
-
}));
|
|
51680
|
-
void slotSource;
|
|
51681
|
-
for (const [slot, patterns] of pendingSlots) {
|
|
51682
|
-
stateLog.debug("flush:slot", {
|
|
51683
|
-
traitName,
|
|
51684
|
-
slot,
|
|
51685
|
-
patternCount: patterns.length,
|
|
51686
|
-
event: eventKey,
|
|
51687
|
-
transition: `${result.previousState}->${result.newState}`,
|
|
51688
|
-
cleared: patterns.length === 0
|
|
51689
|
-
});
|
|
51690
|
-
flushSlot(traitName, slot, patterns, {
|
|
51691
|
-
event: eventKey,
|
|
51692
|
-
state: result.previousState,
|
|
51693
|
-
entity: binding.linkedEntity
|
|
51694
|
-
});
|
|
51695
|
-
}
|
|
51696
|
-
} catch (error) {
|
|
51697
|
-
stateLog.error("transition:effect-error", {
|
|
51698
|
-
traitName,
|
|
51699
|
-
fromState: result.previousState,
|
|
51700
|
-
toState: result.newState,
|
|
51701
|
-
event: eventKey,
|
|
51702
|
-
error: error instanceof Error ? error.message : String(error),
|
|
51703
|
-
effectCount: result.effects.length
|
|
51704
|
-
});
|
|
51705
|
-
}
|
|
51706
51992
|
} else if (!result.executed) {
|
|
51707
51993
|
if (result.guardResult === false) {
|
|
51708
51994
|
stateLog.debug("guard-blocked-transition", {
|
|
@@ -51840,7 +52126,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
51840
52126
|
if (!orbitalName) continue;
|
|
51841
52127
|
for (const transition of binding.trait.transitions) {
|
|
51842
52128
|
const eventKey = transition.event;
|
|
51843
|
-
if (eventKey
|
|
52129
|
+
if (LIFECYCLE_EVENTS.includes(eventKey)) {
|
|
51844
52130
|
continue;
|
|
51845
52131
|
}
|
|
51846
52132
|
const selfBusKey = `UI:${orbitalName}.${traitName}.${eventKey}`;
|
|
@@ -51895,6 +52181,24 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
51895
52181
|
crossTraitLog.debug("cleanup:done", {});
|
|
51896
52182
|
};
|
|
51897
52183
|
}, [traitBindings, eventBus, enqueueAndDrain]);
|
|
52184
|
+
React81.useEffect(() => {
|
|
52185
|
+
const mgr = managerRef.current;
|
|
52186
|
+
const inited = initedTraitsRef.current;
|
|
52187
|
+
for (const binding of traitBindings) {
|
|
52188
|
+
const traitName = binding.trait.name;
|
|
52189
|
+
if (inited.has(traitName)) continue;
|
|
52190
|
+
const lifecycleEvent = LIFECYCLE_EVENTS.find(
|
|
52191
|
+
(evt) => mgr.canHandleEvent(traitName, evt)
|
|
52192
|
+
);
|
|
52193
|
+
if (lifecycleEvent === void 0) continue;
|
|
52194
|
+
inited.add(traitName);
|
|
52195
|
+
stateLog.debug("mount:fire-lifecycle", { traitName, event: lifecycleEvent });
|
|
52196
|
+
enqueueAndDrain(lifecycleEvent, {});
|
|
52197
|
+
}
|
|
52198
|
+
return () => {
|
|
52199
|
+
initedTraitsRef.current = /* @__PURE__ */ new Set();
|
|
52200
|
+
};
|
|
52201
|
+
}, [traitBindings, enqueueAndDrain]);
|
|
51898
52202
|
return {
|
|
51899
52203
|
traitStates,
|
|
51900
52204
|
sendEvent,
|