@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/avl/index.js
CHANGED
|
@@ -39,15 +39,15 @@ import { FieldTypeSchema, isInlineTrait, isPageReference, isEntityCall, schemaTo
|
|
|
39
39
|
import { useDroppable, useDraggable, DndContext, DragOverlay, useSensors, useSensor, PointerSensor, KeyboardSensor, pointerWithin, rectIntersection, closestCorners } from '@dnd-kit/core';
|
|
40
40
|
import { sortableKeyboardCoordinates, useSortable, arrayMove, SortableContext, rectSortingStrategy, verticalListSortingStrategy } from '@dnd-kit/sortable';
|
|
41
41
|
import { CSS } from '@dnd-kit/utilities';
|
|
42
|
-
import * as
|
|
42
|
+
import * as THREE3 from 'three';
|
|
43
43
|
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
|
|
44
44
|
import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js';
|
|
45
45
|
import { GLTFLoader as GLTFLoader$1 } from 'three/examples/jsm/loaders/GLTFLoader';
|
|
46
|
-
import { Canvas, useThree } from '@react-three/fiber';
|
|
47
|
-
import { Grid as Grid$1, OrbitControls } from '@react-three/drei';
|
|
46
|
+
import { Canvas, useLoader, useFrame, useThree } from '@react-three/fiber';
|
|
47
|
+
import { Billboard, Grid as Grid$1, OrbitControls } from '@react-three/drei';
|
|
48
48
|
import { getPatternDefinition, getComponentForPattern as getComponentForPattern$1, isEntityAwarePattern } from '@almadar/patterns';
|
|
49
49
|
import { OrbitalServerRuntime } from '@almadar/runtime/OrbitalServerRuntime';
|
|
50
|
-
import { InMemoryPersistence, StateMachineManager,
|
|
50
|
+
import { InMemoryPersistence, StateMachineManager, collectDeclaredConfigDefaults, createServerEffectHandlers, EffectExecutor, interpolateValue, createContextFromBindings } from '@almadar/runtime';
|
|
51
51
|
|
|
52
52
|
var __defProp = Object.defineProperty;
|
|
53
53
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
@@ -10792,6 +10792,52 @@ var init_ControlButton = __esm({
|
|
|
10792
10792
|
ControlButton.displayName = "ControlButton";
|
|
10793
10793
|
}
|
|
10794
10794
|
});
|
|
10795
|
+
|
|
10796
|
+
// components/game/organisms/utils/spriteAnimation.ts
|
|
10797
|
+
function inferDirection(dx, dy) {
|
|
10798
|
+
if (dx === 0 && dy === 0) return "se";
|
|
10799
|
+
if (dx >= 0 && dy >= 0) return "se";
|
|
10800
|
+
if (dx <= 0 && dy >= 0) return "sw";
|
|
10801
|
+
if (dx >= 0 && dy <= 0) return "ne";
|
|
10802
|
+
return "nw";
|
|
10803
|
+
}
|
|
10804
|
+
function resolveSheetDirection(facing) {
|
|
10805
|
+
switch (facing) {
|
|
10806
|
+
case "se":
|
|
10807
|
+
return { sheetDir: "se", flipX: false };
|
|
10808
|
+
case "sw":
|
|
10809
|
+
return { sheetDir: "sw", flipX: false };
|
|
10810
|
+
case "ne":
|
|
10811
|
+
return { sheetDir: "sw", flipX: true };
|
|
10812
|
+
case "nw":
|
|
10813
|
+
return { sheetDir: "se", flipX: true };
|
|
10814
|
+
}
|
|
10815
|
+
}
|
|
10816
|
+
function frameRect(frame, row, columns, frameWidth, frameHeight) {
|
|
10817
|
+
return {
|
|
10818
|
+
sx: frame % columns * frameWidth,
|
|
10819
|
+
sy: row * frameHeight,
|
|
10820
|
+
sw: frameWidth,
|
|
10821
|
+
sh: frameHeight
|
|
10822
|
+
};
|
|
10823
|
+
}
|
|
10824
|
+
function getCurrentFrameFromDef(def, elapsed) {
|
|
10825
|
+
const frameDuration = 1e3 / def.frameRate;
|
|
10826
|
+
const totalDuration = def.frames * frameDuration;
|
|
10827
|
+
if (def.loop) {
|
|
10828
|
+
const frame2 = Math.floor(elapsed % totalDuration / frameDuration);
|
|
10829
|
+
return { frame: frame2, finished: false };
|
|
10830
|
+
}
|
|
10831
|
+
if (elapsed >= totalDuration) {
|
|
10832
|
+
return { frame: def.frames - 1, finished: true };
|
|
10833
|
+
}
|
|
10834
|
+
const frame = Math.floor(elapsed / frameDuration);
|
|
10835
|
+
return { frame, finished: false };
|
|
10836
|
+
}
|
|
10837
|
+
var init_spriteAnimation = __esm({
|
|
10838
|
+
"components/game/organisms/utils/spriteAnimation.ts"() {
|
|
10839
|
+
}
|
|
10840
|
+
});
|
|
10795
10841
|
function Sprite({
|
|
10796
10842
|
spritesheet = "https://almadar-kflow-assets.web.app/shared/isometric-blocks/Spritesheet/allTiles_sheet.png",
|
|
10797
10843
|
frameWidth = 64,
|
|
@@ -10812,12 +10858,8 @@ function Sprite({
|
|
|
10812
10858
|
}) {
|
|
10813
10859
|
const eventBus = useEventBus();
|
|
10814
10860
|
const sourcePosition = useMemo(() => {
|
|
10815
|
-
const
|
|
10816
|
-
|
|
10817
|
-
return {
|
|
10818
|
-
x: frameX * frameWidth,
|
|
10819
|
-
y: frameY * frameHeight
|
|
10820
|
-
};
|
|
10861
|
+
const { sx, sy } = frameRect(frame, Math.floor(frame / columns), columns, frameWidth, frameHeight);
|
|
10862
|
+
return { x: sx, y: sy };
|
|
10821
10863
|
}, [frame, columns, frameWidth, frameHeight]);
|
|
10822
10864
|
const transform = useMemo(() => {
|
|
10823
10865
|
const transforms = [
|
|
@@ -10865,6 +10907,7 @@ var init_Sprite = __esm({
|
|
|
10865
10907
|
"components/game/atoms/Sprite.tsx"() {
|
|
10866
10908
|
"use client";
|
|
10867
10909
|
init_useEventBus();
|
|
10910
|
+
init_spriteAnimation();
|
|
10868
10911
|
}
|
|
10869
10912
|
});
|
|
10870
10913
|
function StateIndicator({
|
|
@@ -14256,6 +14299,163 @@ var init_useCamera = __esm({
|
|
|
14256
14299
|
"use client";
|
|
14257
14300
|
}
|
|
14258
14301
|
});
|
|
14302
|
+
function unitAtlasUrl(unit) {
|
|
14303
|
+
if (unit.spriteSheet) return unit.spriteSheet;
|
|
14304
|
+
const sprite = unit.sprite;
|
|
14305
|
+
if (!sprite) return null;
|
|
14306
|
+
const match = /^(.*-sprite-sheet)(?:-(?:se|sw))?(?:-v\d+)?\.png$/.exec(sprite);
|
|
14307
|
+
if (!match) return null;
|
|
14308
|
+
return `${match[1]}.json`;
|
|
14309
|
+
}
|
|
14310
|
+
function resolveSheetUrl(atlasUrl, relativeSheetPath) {
|
|
14311
|
+
try {
|
|
14312
|
+
return new URL(relativeSheetPath, atlasUrl).toString();
|
|
14313
|
+
} catch {
|
|
14314
|
+
const base = atlasUrl.slice(0, atlasUrl.lastIndexOf("/") + 1);
|
|
14315
|
+
return `${base}${relativeSheetPath.replace(/^\.\//, "")}`;
|
|
14316
|
+
}
|
|
14317
|
+
}
|
|
14318
|
+
function useUnitSpriteAtlas(units) {
|
|
14319
|
+
const atlasCacheRef = useRef(/* @__PURE__ */ new Map());
|
|
14320
|
+
const loadingRef = useRef(/* @__PURE__ */ new Set());
|
|
14321
|
+
const [pendingCount, setPendingCount] = useState(0);
|
|
14322
|
+
const [, forceTick] = useState(0);
|
|
14323
|
+
const animStatesRef = useRef(/* @__PURE__ */ new Map());
|
|
14324
|
+
const lastTickRef = useRef(0);
|
|
14325
|
+
const rafRef = useRef(0);
|
|
14326
|
+
const atlasUrls = useMemo(() => {
|
|
14327
|
+
const set = /* @__PURE__ */ new Set();
|
|
14328
|
+
for (const unit of units) {
|
|
14329
|
+
const url = unitAtlasUrl(unit);
|
|
14330
|
+
if (url) set.add(url);
|
|
14331
|
+
}
|
|
14332
|
+
return [...set];
|
|
14333
|
+
}, [units]);
|
|
14334
|
+
useEffect(() => {
|
|
14335
|
+
const cache = atlasCacheRef.current;
|
|
14336
|
+
const loading = loadingRef.current;
|
|
14337
|
+
const toLoad = atlasUrls.filter((url) => !cache.has(url) && !loading.has(url));
|
|
14338
|
+
if (toLoad.length === 0) return;
|
|
14339
|
+
let cancelled = false;
|
|
14340
|
+
setPendingCount((prev) => prev + toLoad.length);
|
|
14341
|
+
for (const url of toLoad) {
|
|
14342
|
+
loading.add(url);
|
|
14343
|
+
fetch(url).then((res) => res.ok ? res.json() : Promise.reject(new Error(String(res.status)))).then((atlas) => {
|
|
14344
|
+
if (cancelled) return;
|
|
14345
|
+
cache.set(url, atlas);
|
|
14346
|
+
}).catch(() => {
|
|
14347
|
+
}).finally(() => {
|
|
14348
|
+
if (cancelled) return;
|
|
14349
|
+
loading.delete(url);
|
|
14350
|
+
setPendingCount((prev) => Math.max(0, prev - 1));
|
|
14351
|
+
forceTick((n) => n + 1);
|
|
14352
|
+
});
|
|
14353
|
+
}
|
|
14354
|
+
return () => {
|
|
14355
|
+
cancelled = true;
|
|
14356
|
+
};
|
|
14357
|
+
}, [atlasUrls]);
|
|
14358
|
+
const sheetUrls = useMemo(() => {
|
|
14359
|
+
const urls = /* @__PURE__ */ new Set();
|
|
14360
|
+
for (const unit of units) {
|
|
14361
|
+
const atlasUrl = unitAtlasUrl(unit);
|
|
14362
|
+
if (!atlasUrl) continue;
|
|
14363
|
+
const atlas = atlasCacheRef.current.get(atlasUrl);
|
|
14364
|
+
if (!atlas) continue;
|
|
14365
|
+
for (const rel of Object.values(atlas.sheets)) {
|
|
14366
|
+
if (rel) urls.add(resolveSheetUrl(atlasUrl, rel));
|
|
14367
|
+
}
|
|
14368
|
+
}
|
|
14369
|
+
return [...urls];
|
|
14370
|
+
}, [units, pendingCount]);
|
|
14371
|
+
useEffect(() => {
|
|
14372
|
+
const hasAtlasUnits = units.some((u) => unitAtlasUrl(u) !== null);
|
|
14373
|
+
if (!hasAtlasUnits) return;
|
|
14374
|
+
let running = true;
|
|
14375
|
+
const tick = (ts) => {
|
|
14376
|
+
if (!running) return;
|
|
14377
|
+
const last = lastTickRef.current || ts;
|
|
14378
|
+
const delta = ts - last;
|
|
14379
|
+
lastTickRef.current = ts;
|
|
14380
|
+
const states = animStatesRef.current;
|
|
14381
|
+
const currentIds = /* @__PURE__ */ new Set();
|
|
14382
|
+
for (const unit of units) {
|
|
14383
|
+
if (unitAtlasUrl(unit) === null) continue;
|
|
14384
|
+
currentIds.add(unit.id);
|
|
14385
|
+
let state = states.get(unit.id);
|
|
14386
|
+
if (!state) {
|
|
14387
|
+
state = { animation: "idle", direction: "se", elapsed: 0, walkHold: 0, prev: null };
|
|
14388
|
+
states.set(unit.id, state);
|
|
14389
|
+
}
|
|
14390
|
+
const posX = unit.position?.x ?? unit.x ?? 0;
|
|
14391
|
+
const posY = unit.position?.y ?? unit.y ?? 0;
|
|
14392
|
+
if (state.prev) {
|
|
14393
|
+
const dx = posX - state.prev.x;
|
|
14394
|
+
const dy = posY - state.prev.y;
|
|
14395
|
+
if (dx !== 0 || dy !== 0) {
|
|
14396
|
+
state.animation = "walk";
|
|
14397
|
+
state.direction = inferDirection(dx, dy);
|
|
14398
|
+
state.walkHold = WALK_HOLD_MS;
|
|
14399
|
+
} else if (state.animation === "walk") {
|
|
14400
|
+
state.walkHold -= delta;
|
|
14401
|
+
if (state.walkHold <= 0) state.animation = "idle";
|
|
14402
|
+
}
|
|
14403
|
+
}
|
|
14404
|
+
state.prev = { x: posX, y: posY };
|
|
14405
|
+
state.elapsed += delta;
|
|
14406
|
+
}
|
|
14407
|
+
for (const id of states.keys()) {
|
|
14408
|
+
if (!currentIds.has(id)) states.delete(id);
|
|
14409
|
+
}
|
|
14410
|
+
rafRef.current = requestAnimationFrame(tick);
|
|
14411
|
+
};
|
|
14412
|
+
rafRef.current = requestAnimationFrame(tick);
|
|
14413
|
+
return () => {
|
|
14414
|
+
running = false;
|
|
14415
|
+
cancelAnimationFrame(rafRef.current);
|
|
14416
|
+
lastTickRef.current = 0;
|
|
14417
|
+
};
|
|
14418
|
+
}, [units]);
|
|
14419
|
+
const resolveUnitFrame = useCallback((unitId) => {
|
|
14420
|
+
const unit = units.find((u) => u.id === unitId);
|
|
14421
|
+
if (!unit) return null;
|
|
14422
|
+
const atlasUrl = unitAtlasUrl(unit);
|
|
14423
|
+
if (!atlasUrl) return null;
|
|
14424
|
+
const atlas = atlasCacheRef.current.get(atlasUrl);
|
|
14425
|
+
if (!atlas) return null;
|
|
14426
|
+
const state = animStatesRef.current.get(unitId);
|
|
14427
|
+
const animation = state?.animation ?? "idle";
|
|
14428
|
+
const direction = state?.direction ?? "se";
|
|
14429
|
+
const elapsed = state?.elapsed ?? 0;
|
|
14430
|
+
const def = atlas.animations[animation] ?? atlas.animations.idle;
|
|
14431
|
+
if (!def) return null;
|
|
14432
|
+
const { sheetDir, flipX } = resolveSheetDirection(direction);
|
|
14433
|
+
const rel = atlas.sheets[sheetDir] ?? atlas.sheets.se ?? atlas.sheets.sw;
|
|
14434
|
+
if (!rel) return null;
|
|
14435
|
+
const sheetUrl = resolveSheetUrl(atlasUrl, rel);
|
|
14436
|
+
const isIdle = animation === "idle";
|
|
14437
|
+
const frame = isIdle ? 0 : getCurrentFrameFromDef(def, elapsed).frame;
|
|
14438
|
+
const rect = frameRect(frame, def.row, atlas.columns, atlas.frameWidth, atlas.frameHeight);
|
|
14439
|
+
return {
|
|
14440
|
+
sheetUrl,
|
|
14441
|
+
sx: rect.sx,
|
|
14442
|
+
sy: rect.sy,
|
|
14443
|
+
sw: rect.sw,
|
|
14444
|
+
sh: rect.sh,
|
|
14445
|
+
flipX,
|
|
14446
|
+
applyBreathing: isIdle
|
|
14447
|
+
};
|
|
14448
|
+
}, [units]);
|
|
14449
|
+
return { sheetUrls, resolveUnitFrame, pendingCount };
|
|
14450
|
+
}
|
|
14451
|
+
var WALK_HOLD_MS;
|
|
14452
|
+
var init_useUnitSpriteAtlas = __esm({
|
|
14453
|
+
"components/game/molecules/useUnitSpriteAtlas.ts"() {
|
|
14454
|
+
"use client";
|
|
14455
|
+
init_spriteAnimation();
|
|
14456
|
+
WALK_HOLD_MS = 600;
|
|
14457
|
+
}
|
|
14458
|
+
});
|
|
14259
14459
|
|
|
14260
14460
|
// components/game/organisms/utils/isometric.ts
|
|
14261
14461
|
function isoToScreen(tileX, tileY, scale, baseOffsetX) {
|
|
@@ -14370,6 +14570,10 @@ function IsometricCanvas({
|
|
|
14370
14570
|
() => unitsProp.map((u) => u.position ? u : { ...u, position: { x: u.x ?? 0, y: u.y ?? 0 } }),
|
|
14371
14571
|
[unitsProp]
|
|
14372
14572
|
);
|
|
14573
|
+
const { sheetUrls: atlasSheetUrls, resolveUnitFrame: resolveUnitFrameInternal, pendingCount: atlasPending } = useUnitSpriteAtlas(units);
|
|
14574
|
+
const resolveFrameForUnit = useCallback((unitId) => {
|
|
14575
|
+
return resolveUnitFrame?.(unitId) ?? resolveUnitFrameInternal(unitId);
|
|
14576
|
+
}, [resolveUnitFrame, resolveUnitFrameInternal]);
|
|
14373
14577
|
const features = useMemo(
|
|
14374
14578
|
() => featuresProp.map((f3) => {
|
|
14375
14579
|
if (f3.type) return f3;
|
|
@@ -14453,9 +14657,10 @@ function IsometricCanvas({
|
|
|
14453
14657
|
}
|
|
14454
14658
|
}
|
|
14455
14659
|
if (effectSpriteUrls) urls.push(...effectSpriteUrls);
|
|
14660
|
+
if (atlasSheetUrls.length) urls.push(...atlasSheetUrls);
|
|
14456
14661
|
if (backgroundImage) urls.push(backgroundImage);
|
|
14457
14662
|
return [...new Set(urls.filter(Boolean))];
|
|
14458
|
-
}, [sortedTiles, features, units, getTerrainSprite, getFeatureSprite, getUnitSprite, effectSpriteUrls, backgroundImage, assetManifest, resolveManifestUrl]);
|
|
14663
|
+
}, [sortedTiles, features, units, getTerrainSprite, getFeatureSprite, getUnitSprite, effectSpriteUrls, atlasSheetUrls, backgroundImage, assetManifest, resolveManifestUrl]);
|
|
14459
14664
|
const { getImage, pendingCount } = useImageCache(spriteUrls);
|
|
14460
14665
|
useEffect(() => {
|
|
14461
14666
|
if (typeof window === "undefined") return;
|
|
@@ -14742,7 +14947,7 @@ function IsometricCanvas({
|
|
|
14742
14947
|
ctx.lineWidth = 3;
|
|
14743
14948
|
ctx.stroke();
|
|
14744
14949
|
}
|
|
14745
|
-
const frame =
|
|
14950
|
+
const frame = resolveFrameForUnit(unit.id);
|
|
14746
14951
|
const frameImg = frame ? getImage(frame.sheetUrl) : null;
|
|
14747
14952
|
if (frame && frameImg) {
|
|
14748
14953
|
const frameAr = frame.sw / frame.sh;
|
|
@@ -14852,7 +15057,7 @@ function IsometricCanvas({
|
|
|
14852
15057
|
resolveTerrainSpriteUrl,
|
|
14853
15058
|
resolveFeatureSpriteUrl,
|
|
14854
15059
|
resolveUnitSpriteUrl,
|
|
14855
|
-
|
|
15060
|
+
resolveFrameForUnit,
|
|
14856
15061
|
getImage,
|
|
14857
15062
|
gridWidth,
|
|
14858
15063
|
gridHeight,
|
|
@@ -14884,7 +15089,7 @@ function IsometricCanvas({
|
|
|
14884
15089
|
};
|
|
14885
15090
|
}, [selectedUnitId, units, scale, baseOffsetX, scaledTileWidth, scaledDiamondTopY, scaledFloorHeight, viewportSize, targetCameraRef]);
|
|
14886
15091
|
useEffect(() => {
|
|
14887
|
-
const hasAnimations = units.length > 0 || validMoves.length > 0 || attackTargets.length > 0 || selectedUnitId != null || targetCameraRef.current != null || hasActiveEffects2 || pendingCount > 0;
|
|
15092
|
+
const hasAnimations = units.length > 0 || validMoves.length > 0 || attackTargets.length > 0 || selectedUnitId != null || targetCameraRef.current != null || hasActiveEffects2 || pendingCount > 0 || atlasPending > 0;
|
|
14888
15093
|
draw(animTimeRef.current);
|
|
14889
15094
|
if (!hasAnimations) return;
|
|
14890
15095
|
let running = true;
|
|
@@ -14900,7 +15105,7 @@ function IsometricCanvas({
|
|
|
14900
15105
|
running = false;
|
|
14901
15106
|
cancelAnimationFrame(rafIdRef.current);
|
|
14902
15107
|
};
|
|
14903
|
-
}, [draw, units.length, validMoves.length, attackTargets.length, selectedUnitId, hasActiveEffects2, pendingCount, lerpToTarget, targetCameraRef]);
|
|
15108
|
+
}, [draw, units.length, validMoves.length, attackTargets.length, selectedUnitId, hasActiveEffects2, pendingCount, atlasPending, lerpToTarget, targetCameraRef]);
|
|
14904
15109
|
const handleMouseMoveWithCamera = useCallback((e) => {
|
|
14905
15110
|
if (enableCamera) {
|
|
14906
15111
|
const wasPanning = handleMouseMove(e, () => draw(animTimeRef.current));
|
|
@@ -15045,6 +15250,7 @@ var init_IsometricCanvas = __esm({
|
|
|
15045
15250
|
init_ErrorState();
|
|
15046
15251
|
init_useImageCache();
|
|
15047
15252
|
init_useCamera();
|
|
15253
|
+
init_useUnitSpriteAtlas();
|
|
15048
15254
|
init_verificationRegistry();
|
|
15049
15255
|
init_isometric();
|
|
15050
15256
|
IsometricCanvas.displayName = "IsometricCanvas";
|
|
@@ -31798,13 +32004,13 @@ var init_MapView = __esm({
|
|
|
31798
32004
|
shadowSize: [41, 41]
|
|
31799
32005
|
});
|
|
31800
32006
|
L.Marker.prototype.options.icon = defaultIcon;
|
|
31801
|
-
const { useEffect:
|
|
32007
|
+
const { useEffect: useEffect85, useRef: useRef77, useCallback: useCallback125, useState: useState121 } = React90__default;
|
|
31802
32008
|
const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
|
|
31803
32009
|
const { useEventBus: useEventBus4 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
|
|
31804
32010
|
function MapUpdater({ centerLat, centerLng, zoom }) {
|
|
31805
32011
|
const map = useMap();
|
|
31806
|
-
const prevRef =
|
|
31807
|
-
|
|
32012
|
+
const prevRef = useRef77({ centerLat, centerLng, zoom });
|
|
32013
|
+
useEffect85(() => {
|
|
31808
32014
|
const prev = prevRef.current;
|
|
31809
32015
|
if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
|
|
31810
32016
|
map.setView([centerLat, centerLng], zoom);
|
|
@@ -31815,7 +32021,7 @@ var init_MapView = __esm({
|
|
|
31815
32021
|
}
|
|
31816
32022
|
function MapClickHandler({ onMapClick }) {
|
|
31817
32023
|
const map = useMap();
|
|
31818
|
-
|
|
32024
|
+
useEffect85(() => {
|
|
31819
32025
|
if (!onMapClick) return;
|
|
31820
32026
|
const handler = (e) => {
|
|
31821
32027
|
onMapClick(e.latlng.lat, e.latlng.lng);
|
|
@@ -31843,8 +32049,8 @@ var init_MapView = __esm({
|
|
|
31843
32049
|
showAttribution = true
|
|
31844
32050
|
}) {
|
|
31845
32051
|
const eventBus = useEventBus4();
|
|
31846
|
-
const [clickedPosition, setClickedPosition] =
|
|
31847
|
-
const handleMapClick =
|
|
32052
|
+
const [clickedPosition, setClickedPosition] = useState121(null);
|
|
32053
|
+
const handleMapClick = useCallback125((lat, lng) => {
|
|
31848
32054
|
if (showClickedPin) {
|
|
31849
32055
|
setClickedPosition({ lat, lng });
|
|
31850
32056
|
}
|
|
@@ -31853,7 +32059,7 @@ var init_MapView = __esm({
|
|
|
31853
32059
|
eventBus.emit(`UI:${mapClickEvent}`, { latitude: lat, longitude: lng });
|
|
31854
32060
|
}
|
|
31855
32061
|
}, [onMapClick, mapClickEvent, eventBus, showClickedPin]);
|
|
31856
|
-
const handleMarkerClick =
|
|
32062
|
+
const handleMarkerClick = useCallback125((marker) => {
|
|
31857
32063
|
onMarkerClick?.(marker);
|
|
31858
32064
|
if (markerClickEvent) {
|
|
31859
32065
|
eventBus.emit(`UI:${markerClickEvent}`, { ...marker });
|
|
@@ -43162,7 +43368,7 @@ var init_AssetLoader = __esm({
|
|
|
43162
43368
|
__publicField(this, "textureCache");
|
|
43163
43369
|
__publicField(this, "loadingPromises");
|
|
43164
43370
|
this.objLoader = new OBJLoader();
|
|
43165
|
-
this.textureLoader = new
|
|
43371
|
+
this.textureLoader = new THREE3.TextureLoader();
|
|
43166
43372
|
this.modelCache = /* @__PURE__ */ new Map();
|
|
43167
43373
|
this.textureCache = /* @__PURE__ */ new Map();
|
|
43168
43374
|
this.loadingPromises = /* @__PURE__ */ new Map();
|
|
@@ -43236,7 +43442,7 @@ var init_AssetLoader = __esm({
|
|
|
43236
43442
|
return this.loadingPromises.get(`texture:${url}`);
|
|
43237
43443
|
}
|
|
43238
43444
|
const loadPromise = this.textureLoader.loadAsync(url).then((texture) => {
|
|
43239
|
-
texture.colorSpace =
|
|
43445
|
+
texture.colorSpace = THREE3.SRGBColorSpace;
|
|
43240
43446
|
this.textureCache.set(url, texture);
|
|
43241
43447
|
this.loadingPromises.delete(`texture:${url}`);
|
|
43242
43448
|
return texture;
|
|
@@ -43310,7 +43516,7 @@ var init_AssetLoader = __esm({
|
|
|
43310
43516
|
});
|
|
43311
43517
|
this.modelCache.forEach((model) => {
|
|
43312
43518
|
model.scene.traverse((child) => {
|
|
43313
|
-
if (child instanceof
|
|
43519
|
+
if (child instanceof THREE3.Mesh) {
|
|
43314
43520
|
child.geometry.dispose();
|
|
43315
43521
|
if (Array.isArray(child.material)) {
|
|
43316
43522
|
child.material.forEach((m) => m.dispose());
|
|
@@ -43866,7 +44072,7 @@ function ModelLoader({
|
|
|
43866
44072
|
if (!loadedModel) return null;
|
|
43867
44073
|
const cloned = loadedModel.clone();
|
|
43868
44074
|
cloned.traverse((child) => {
|
|
43869
|
-
if (child instanceof
|
|
44075
|
+
if (child instanceof THREE3.Mesh) {
|
|
43870
44076
|
child.castShadow = castShadow;
|
|
43871
44077
|
child.receiveShadow = receiveShadow;
|
|
43872
44078
|
}
|
|
@@ -43980,6 +44186,47 @@ function CameraController({
|
|
|
43980
44186
|
}, [camera.position, onCameraChange]);
|
|
43981
44187
|
return null;
|
|
43982
44188
|
}
|
|
44189
|
+
function UnitSpriteBillboard({
|
|
44190
|
+
sheetUrl,
|
|
44191
|
+
resolveFrame,
|
|
44192
|
+
height = 1.2
|
|
44193
|
+
}) {
|
|
44194
|
+
const texture = useLoader(THREE3.TextureLoader, sheetUrl);
|
|
44195
|
+
const meshRef = useRef(null);
|
|
44196
|
+
const matRef = useRef(null);
|
|
44197
|
+
const [aspect, setAspect] = useState(1);
|
|
44198
|
+
useFrame(() => {
|
|
44199
|
+
const frame = resolveFrame();
|
|
44200
|
+
if (!frame || !texture.image) return;
|
|
44201
|
+
const imgW = texture.image.width;
|
|
44202
|
+
const imgH = texture.image.height;
|
|
44203
|
+
if (!imgW || !imgH) return;
|
|
44204
|
+
texture.repeat.set((frame.flipX ? -1 : 1) * (frame.sw / imgW), frame.sh / imgH);
|
|
44205
|
+
texture.offset.set(
|
|
44206
|
+
frame.flipX ? (frame.sx + frame.sw) / imgW : frame.sx / imgW,
|
|
44207
|
+
1 - (frame.sy + frame.sh) / imgH
|
|
44208
|
+
);
|
|
44209
|
+
texture.magFilter = THREE3.NearestFilter;
|
|
44210
|
+
texture.minFilter = THREE3.NearestFilter;
|
|
44211
|
+
texture.needsUpdate = true;
|
|
44212
|
+
const nextAspect = frame.sw / frame.sh;
|
|
44213
|
+
if (Math.abs(nextAspect - aspect) > 1e-3) setAspect(nextAspect);
|
|
44214
|
+
if (matRef.current) matRef.current.needsUpdate = true;
|
|
44215
|
+
});
|
|
44216
|
+
return /* @__PURE__ */ jsxs("mesh", { ref: meshRef, position: [0, height / 2, 0], children: [
|
|
44217
|
+
/* @__PURE__ */ jsx("planeGeometry", { args: [height * aspect, height] }),
|
|
44218
|
+
/* @__PURE__ */ jsx(
|
|
44219
|
+
"meshBasicMaterial",
|
|
44220
|
+
{
|
|
44221
|
+
ref: matRef,
|
|
44222
|
+
map: texture,
|
|
44223
|
+
transparent: true,
|
|
44224
|
+
alphaTest: 0.1,
|
|
44225
|
+
side: THREE3.DoubleSide
|
|
44226
|
+
}
|
|
44227
|
+
)
|
|
44228
|
+
] });
|
|
44229
|
+
}
|
|
43983
44230
|
var DEFAULT_GRID_CONFIG, GameCanvas3D;
|
|
43984
44231
|
var init_GameCanvas3D2 = __esm({
|
|
43985
44232
|
"components/game/molecules/GameCanvas3D.tsx"() {
|
|
@@ -43990,6 +44237,7 @@ var init_GameCanvas3D2 = __esm({
|
|
|
43990
44237
|
init_Canvas3DLoadingState2();
|
|
43991
44238
|
init_Canvas3DErrorBoundary2();
|
|
43992
44239
|
init_ModelLoader();
|
|
44240
|
+
init_useUnitSpriteAtlas();
|
|
43993
44241
|
init_cn();
|
|
43994
44242
|
init_GameCanvas3D();
|
|
43995
44243
|
DEFAULT_GRID_CONFIG = {
|
|
@@ -44046,8 +44294,10 @@ var init_GameCanvas3D2 = __esm({
|
|
|
44046
44294
|
const controlsRef = useRef(null);
|
|
44047
44295
|
const [hoveredTile, setHoveredTile] = useState(null);
|
|
44048
44296
|
const [internalError, setInternalError] = useState(null);
|
|
44297
|
+
const { sheetUrls: atlasSheetUrls, resolveUnitFrame } = useUnitSpriteAtlas(units);
|
|
44298
|
+
const preloadUrls = useMemo(() => [...preloadAssets, ...atlasSheetUrls], [preloadAssets, atlasSheetUrls]);
|
|
44049
44299
|
const { isLoading: assetsLoading, progress, loaded, total } = useAssetLoader({
|
|
44050
|
-
preloadUrls
|
|
44300
|
+
preloadUrls,
|
|
44051
44301
|
loader: customAssetLoader
|
|
44052
44302
|
});
|
|
44053
44303
|
const eventHandlers = useGameCanvas3DEvents({
|
|
@@ -44106,7 +44356,7 @@ var init_GameCanvas3D2 = __esm({
|
|
|
44106
44356
|
getCameraPosition: () => {
|
|
44107
44357
|
if (controlsRef.current) {
|
|
44108
44358
|
const pos = controlsRef.current.object.position;
|
|
44109
|
-
return new
|
|
44359
|
+
return new THREE3.Vector3(pos.x, pos.y, pos.z);
|
|
44110
44360
|
}
|
|
44111
44361
|
return null;
|
|
44112
44362
|
},
|
|
@@ -44258,6 +44508,8 @@ var init_GameCanvas3D2 = __esm({
|
|
|
44258
44508
|
({ unit, position }) => {
|
|
44259
44509
|
const isSelected = selectedUnitId === unit.id;
|
|
44260
44510
|
const color = unit.faction === "player" ? 4491519 : unit.faction === "enemy" ? 16729156 : 16777028;
|
|
44511
|
+
const hasAtlas = unitAtlasUrl(unit) !== null;
|
|
44512
|
+
const initialFrame = hasAtlas ? resolveUnitFrame(unit.id) : null;
|
|
44261
44513
|
return /* @__PURE__ */ jsxs(
|
|
44262
44514
|
"group",
|
|
44263
44515
|
{
|
|
@@ -44269,7 +44521,16 @@ var init_GameCanvas3D2 = __esm({
|
|
|
44269
44521
|
/* @__PURE__ */ jsx("ringGeometry", { args: [0.4, 0.5, 32] }),
|
|
44270
44522
|
/* @__PURE__ */ jsx("meshBasicMaterial", { color: "#ffff00", transparent: true, opacity: 0.8 })
|
|
44271
44523
|
] }),
|
|
44272
|
-
|
|
44524
|
+
hasAtlas && initialFrame ? (
|
|
44525
|
+
/* Animated sprite-sheet billboard — single cropped frame, by state */
|
|
44526
|
+
/* @__PURE__ */ jsx(Billboard, { children: /* @__PURE__ */ jsx(
|
|
44527
|
+
UnitSpriteBillboard,
|
|
44528
|
+
{
|
|
44529
|
+
sheetUrl: initialFrame.sheetUrl,
|
|
44530
|
+
resolveFrame: () => resolveUnitFrame(unit.id)
|
|
44531
|
+
}
|
|
44532
|
+
) })
|
|
44533
|
+
) : unit.modelUrl ? (
|
|
44273
44534
|
/* GLB unit model (box fallback while loading / on error) */
|
|
44274
44535
|
/* @__PURE__ */ jsx(
|
|
44275
44536
|
ModelLoader,
|
|
@@ -44323,7 +44584,7 @@ var init_GameCanvas3D2 = __esm({
|
|
|
44323
44584
|
}
|
|
44324
44585
|
);
|
|
44325
44586
|
},
|
|
44326
|
-
[selectedUnitId, handleUnitClick]
|
|
44587
|
+
[selectedUnitId, handleUnitClick, resolveUnitFrame]
|
|
44327
44588
|
);
|
|
44328
44589
|
const DefaultFeatureRenderer = useCallback(
|
|
44329
44590
|
({
|
|
@@ -57097,6 +57358,28 @@ init_verificationRegistry();
|
|
|
57097
57358
|
var crossTraitLog = createLogger("almadar:ui:cross-trait");
|
|
57098
57359
|
var flushLog = createLogger("almadar:ui:slot-flush");
|
|
57099
57360
|
var stateLog = createLogger("almadar:ui:state-transitions");
|
|
57361
|
+
var tickLog = createLogger("almadar:ui:tick-effects");
|
|
57362
|
+
var SYNC_TICK_OPERATORS = /* @__PURE__ */ new Set([
|
|
57363
|
+
"set",
|
|
57364
|
+
"emit",
|
|
57365
|
+
"render-ui",
|
|
57366
|
+
"render",
|
|
57367
|
+
"navigate",
|
|
57368
|
+
"notify",
|
|
57369
|
+
"log",
|
|
57370
|
+
// Synchronous structural forms that wrap sync effects. A tick authored as
|
|
57371
|
+
// a single top-level `(let ((..)) (do (set ..) (render-ui ..)))` or
|
|
57372
|
+
// `(if cond (set ..) ..)` is one of these at its head — the EffectExecutor
|
|
57373
|
+
// resolves the binding values / condition through the canonical evaluator
|
|
57374
|
+
// and runs the wrapped sync effects. Without these in the allow-list the
|
|
57375
|
+
// whole tick is filtered out (its head op isn't `set`/`emit`/...), so the
|
|
57376
|
+
// physics/gameflow/AI tick never runs.
|
|
57377
|
+
"let",
|
|
57378
|
+
"if",
|
|
57379
|
+
"do",
|
|
57380
|
+
"when"
|
|
57381
|
+
]);
|
|
57382
|
+
var LIFECYCLE_EVENTS = ["INIT", "LOAD", "$MOUNT"];
|
|
57100
57383
|
function toTraitDefinition(binding) {
|
|
57101
57384
|
return {
|
|
57102
57385
|
name: binding.trait.name,
|
|
@@ -57129,6 +57412,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
57129
57412
|
});
|
|
57130
57413
|
const eventQueueRef = useRef([]);
|
|
57131
57414
|
const processingRef = useRef(false);
|
|
57415
|
+
const initedTraitsRef = useRef(/* @__PURE__ */ new Set());
|
|
57132
57416
|
const traitBindingsRef = useRef(traitBindings);
|
|
57133
57417
|
const managerRef = useRef(manager);
|
|
57134
57418
|
const uiSlotsRef = useRef(uiSlots);
|
|
@@ -57293,50 +57577,196 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
57293
57577
|
for (const unreg of snapshotUnregs) unreg();
|
|
57294
57578
|
};
|
|
57295
57579
|
}, [traitBindings]);
|
|
57296
|
-
const
|
|
57297
|
-
const
|
|
57298
|
-
|
|
57299
|
-
const
|
|
57300
|
-
|
|
57301
|
-
|
|
57302
|
-
|
|
57303
|
-
|
|
57304
|
-
if (
|
|
57305
|
-
const passed = interpolateValue(tick.guard, evalCtx);
|
|
57306
|
-
if (!passed) return;
|
|
57307
|
-
}
|
|
57580
|
+
const executeTransitionEffects = useCallback(async (params) => {
|
|
57581
|
+
const { binding, previousState, newState, payload, flushEvent, syncOnly, log: log15 } = params;
|
|
57582
|
+
const traitName = binding.trait.name;
|
|
57583
|
+
const linkedEntity = binding.linkedEntity || "";
|
|
57584
|
+
const entityId = payload?.entityId;
|
|
57585
|
+
const effects = syncOnly ? params.effects.filter(
|
|
57586
|
+
(e) => Array.isArray(e) && SYNC_TICK_OPERATORS.has(String(e[0]))
|
|
57587
|
+
) : params.effects;
|
|
57588
|
+
if (effects.length === 0) return [];
|
|
57308
57589
|
const pendingSlots = /* @__PURE__ */ new Map();
|
|
57309
57590
|
({
|
|
57310
|
-
trait: binding.trait.name,
|
|
57311
|
-
transition: `${currentState}->tick:${tick.name}`,
|
|
57312
|
-
effects: tick.effects,
|
|
57313
57591
|
traitDefinition: binding.trait
|
|
57314
57592
|
});
|
|
57315
|
-
|
|
57316
|
-
|
|
57317
|
-
|
|
57318
|
-
|
|
57319
|
-
|
|
57320
|
-
|
|
57321
|
-
|
|
57593
|
+
const clientHandlers = createClientEffectHandlers({
|
|
57594
|
+
eventBus,
|
|
57595
|
+
slotSetter: {
|
|
57596
|
+
addPattern: (slot, pattern, props) => {
|
|
57597
|
+
const existing = pendingSlots.get(slot) || [];
|
|
57598
|
+
existing.push({ pattern, props: props || {} });
|
|
57599
|
+
pendingSlots.set(slot, existing);
|
|
57600
|
+
},
|
|
57601
|
+
clearSlot: (slot) => {
|
|
57322
57602
|
pendingSlots.set(slot, []);
|
|
57323
|
-
continue;
|
|
57324
57603
|
}
|
|
57325
|
-
|
|
57326
|
-
|
|
57327
|
-
|
|
57328
|
-
|
|
57329
|
-
|
|
57604
|
+
},
|
|
57605
|
+
navigate: optionsRef.current?.navigate,
|
|
57606
|
+
notify: optionsRef.current?.notify,
|
|
57607
|
+
callService: optionsRef.current?.callService
|
|
57608
|
+
});
|
|
57609
|
+
const persistence = syncOnly ? void 0 : optionsRef.current?.persistence;
|
|
57610
|
+
let handlers = clientHandlers;
|
|
57611
|
+
if (persistence) {
|
|
57612
|
+
const sharedBindings = {
|
|
57613
|
+
// Seed `@entity` from the trait's scalar field state (a real
|
|
57614
|
+
// EntityRow), the same source the executor's own bindingCtx
|
|
57615
|
+
// uses below. `@payload.*` resolves from `payload` separately,
|
|
57616
|
+
// so dropping the prior `payload as EntityRow` cast loses
|
|
57617
|
+
// nothing — it just stops mislabelling the payload as an entity.
|
|
57618
|
+
entity: traitFieldStatesRef.current.get(traitName) ?? {},
|
|
57619
|
+
payload: payload || {},
|
|
57620
|
+
state: previousState
|
|
57621
|
+
};
|
|
57622
|
+
const sharedDeclared = collectDeclaredConfigDefaults(binding.trait);
|
|
57623
|
+
const sharedCallSite = binding.config;
|
|
57624
|
+
if (sharedDeclared || sharedCallSite) {
|
|
57625
|
+
sharedBindings.config = {
|
|
57626
|
+
...sharedDeclared ?? {},
|
|
57627
|
+
...sharedCallSite ?? {}
|
|
57628
|
+
};
|
|
57330
57629
|
}
|
|
57630
|
+
const serverHandlers = createServerEffectHandlers({
|
|
57631
|
+
persistence,
|
|
57632
|
+
eventBus,
|
|
57633
|
+
entityType: linkedEntity,
|
|
57634
|
+
entityId,
|
|
57635
|
+
bindings: sharedBindings,
|
|
57636
|
+
context: {
|
|
57637
|
+
traitName,
|
|
57638
|
+
state: previousState,
|
|
57639
|
+
transition: `${previousState}->${newState}`,
|
|
57640
|
+
linkedEntity,
|
|
57641
|
+
entityId
|
|
57642
|
+
},
|
|
57643
|
+
source: { trait: traitName },
|
|
57644
|
+
callService: optionsRef.current?.callService
|
|
57645
|
+
});
|
|
57646
|
+
handlers = {
|
|
57647
|
+
...serverHandlers,
|
|
57648
|
+
emit: clientHandlers.emit,
|
|
57649
|
+
renderUI: clientHandlers.renderUI,
|
|
57650
|
+
navigate: clientHandlers.navigate,
|
|
57651
|
+
notify: clientHandlers.notify
|
|
57652
|
+
};
|
|
57331
57653
|
}
|
|
57332
|
-
|
|
57333
|
-
|
|
57334
|
-
|
|
57335
|
-
|
|
57336
|
-
|
|
57654
|
+
const baseSet = handlers.set;
|
|
57655
|
+
handlers = {
|
|
57656
|
+
...handlers,
|
|
57657
|
+
set: async (targetId, field, value) => {
|
|
57658
|
+
let fieldState = traitFieldStatesRef.current.get(traitName);
|
|
57659
|
+
if (!fieldState) {
|
|
57660
|
+
fieldState = {};
|
|
57661
|
+
traitFieldStatesRef.current.set(traitName, fieldState);
|
|
57662
|
+
}
|
|
57663
|
+
fieldState[field] = value;
|
|
57664
|
+
log15.debug("set:write", {
|
|
57665
|
+
traitName,
|
|
57666
|
+
field,
|
|
57667
|
+
value: JSON.stringify(value),
|
|
57668
|
+
transition: `${previousState}->${newState}`
|
|
57669
|
+
});
|
|
57670
|
+
if (baseSet) await baseSet(targetId, field, value);
|
|
57671
|
+
}
|
|
57672
|
+
};
|
|
57673
|
+
const entityForBinding = traitFieldStatesRef.current.get(traitName) ?? {};
|
|
57674
|
+
const bindingCtx = {
|
|
57675
|
+
entity: entityForBinding,
|
|
57676
|
+
payload: payload || {},
|
|
57677
|
+
state: previousState
|
|
57678
|
+
};
|
|
57679
|
+
const declaredDefaults = collectDeclaredConfigDefaults(binding.trait);
|
|
57680
|
+
const callSiteConfig = binding.config;
|
|
57681
|
+
if (declaredDefaults || callSiteConfig) {
|
|
57682
|
+
bindingCtx.config = {
|
|
57683
|
+
...declaredDefaults ?? {},
|
|
57684
|
+
...callSiteConfig ?? {}
|
|
57685
|
+
};
|
|
57686
|
+
}
|
|
57687
|
+
const effectContext = {
|
|
57688
|
+
traitName,
|
|
57689
|
+
state: previousState,
|
|
57690
|
+
transition: `${previousState}->${newState}`,
|
|
57691
|
+
linkedEntity,
|
|
57692
|
+
entityId
|
|
57693
|
+
};
|
|
57694
|
+
const emittedDuringExec = [];
|
|
57695
|
+
const baseEmit = handlers.emit;
|
|
57696
|
+
const trackingHandlers = {
|
|
57697
|
+
...handlers,
|
|
57698
|
+
emit: (event, eventPayload, source) => {
|
|
57699
|
+
emittedDuringExec.push(event);
|
|
57700
|
+
baseEmit(event, eventPayload, source);
|
|
57701
|
+
}
|
|
57702
|
+
};
|
|
57703
|
+
const executor = new EffectExecutor({ handlers: trackingHandlers, bindings: bindingCtx, context: effectContext });
|
|
57704
|
+
try {
|
|
57705
|
+
await executor.executeAll(effects);
|
|
57706
|
+
log15.debug("effects:executed", () => ({
|
|
57707
|
+
traitName,
|
|
57708
|
+
transition: `${previousState}->${newState}`,
|
|
57709
|
+
event: flushEvent,
|
|
57710
|
+
effectCount: effects.length,
|
|
57711
|
+
emitted: emittedDuringExec.join(","),
|
|
57712
|
+
entityAfter: JSON.stringify(traitFieldStatesRef.current.get(traitName) ?? {}),
|
|
57713
|
+
slotsTouched: Array.from(pendingSlots.keys()).join(",")
|
|
57714
|
+
}));
|
|
57715
|
+
for (const [slot, patterns] of pendingSlots) {
|
|
57716
|
+
log15.debug("flush:slot", {
|
|
57717
|
+
traitName,
|
|
57718
|
+
slot,
|
|
57719
|
+
patternCount: patterns.length,
|
|
57720
|
+
event: flushEvent,
|
|
57721
|
+
transition: `${previousState}->${newState}`,
|
|
57722
|
+
cleared: patterns.length === 0
|
|
57723
|
+
});
|
|
57724
|
+
flushSlot(traitName, slot, patterns, {
|
|
57725
|
+
event: flushEvent,
|
|
57726
|
+
state: previousState,
|
|
57727
|
+
entity: binding.linkedEntity
|
|
57728
|
+
});
|
|
57729
|
+
}
|
|
57730
|
+
} catch (error) {
|
|
57731
|
+
log15.error("effects:error", {
|
|
57732
|
+
traitName,
|
|
57733
|
+
transition: `${previousState}->${newState}`,
|
|
57734
|
+
event: flushEvent,
|
|
57735
|
+
error: error instanceof Error ? error.message : String(error),
|
|
57736
|
+
effectCount: effects.length
|
|
57337
57737
|
});
|
|
57338
57738
|
}
|
|
57339
|
-
|
|
57739
|
+
return emittedDuringExec;
|
|
57740
|
+
}, [eventBus, flushSlot]);
|
|
57741
|
+
const runTickEffects = useCallback((tick, binding) => {
|
|
57742
|
+
const traitName = binding.trait.name;
|
|
57743
|
+
const currentState = traitStatesRef.current.get(traitName)?.currentState ?? "";
|
|
57744
|
+
if (tick.appliesTo.length > 0 && !tick.appliesTo.includes(currentState)) return;
|
|
57745
|
+
if (tick.guard !== void 0) {
|
|
57746
|
+
const guardCtx = {
|
|
57747
|
+
entity: traitFieldStatesRef.current.get(traitName) ?? {},
|
|
57748
|
+
payload: {},
|
|
57749
|
+
state: currentState
|
|
57750
|
+
};
|
|
57751
|
+
if (binding.config) {
|
|
57752
|
+
guardCtx.config = binding.config;
|
|
57753
|
+
}
|
|
57754
|
+
const passed = interpolateValue(tick.guard, createContextFromBindings(guardCtx));
|
|
57755
|
+
if (!passed) {
|
|
57756
|
+
tickLog.debug("guard-blocked", { traitName, tick: tick.name, state: currentState });
|
|
57757
|
+
return;
|
|
57758
|
+
}
|
|
57759
|
+
}
|
|
57760
|
+
void executeTransitionEffects({
|
|
57761
|
+
binding,
|
|
57762
|
+
effects: tick.effects,
|
|
57763
|
+
previousState: currentState,
|
|
57764
|
+
newState: currentState,
|
|
57765
|
+
flushEvent: `tick:${tick.name}`,
|
|
57766
|
+
syncOnly: true,
|
|
57767
|
+
log: tickLog
|
|
57768
|
+
});
|
|
57769
|
+
}, [executeTransitionEffects]);
|
|
57340
57770
|
useEffect(() => {
|
|
57341
57771
|
const hasFrameTicks = traitBindingsRef.current.some(
|
|
57342
57772
|
(b) => b.trait.ticks?.some((t) => t.interval === "frame")
|
|
@@ -57447,161 +57877,17 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
57447
57877
|
transition: `${result.previousState} -> ${result.newState}`,
|
|
57448
57878
|
effects: JSON.stringify(result.effects)
|
|
57449
57879
|
}));
|
|
57450
|
-
const
|
|
57451
|
-
|
|
57452
|
-
const pendingSlots = /* @__PURE__ */ new Map();
|
|
57453
|
-
const slotSource = {
|
|
57454
|
-
trait: binding.trait.name,
|
|
57455
|
-
state: result.previousState,
|
|
57456
|
-
transition: `${result.previousState}->${result.newState}`,
|
|
57880
|
+
const emittedDuringExec = await executeTransitionEffects({
|
|
57881
|
+
binding,
|
|
57457
57882
|
effects: result.effects,
|
|
57458
|
-
|
|
57459
|
-
|
|
57460
|
-
|
|
57461
|
-
|
|
57462
|
-
|
|
57463
|
-
|
|
57464
|
-
const existing = pendingSlots.get(slot) || [];
|
|
57465
|
-
existing.push({ pattern, props: props || {} });
|
|
57466
|
-
pendingSlots.set(slot, existing);
|
|
57467
|
-
},
|
|
57468
|
-
clearSlot: (slot) => {
|
|
57469
|
-
pendingSlots.set(slot, []);
|
|
57470
|
-
}
|
|
57471
|
-
},
|
|
57472
|
-
navigate: optionsRef.current?.navigate,
|
|
57473
|
-
notify: optionsRef.current?.notify,
|
|
57474
|
-
callService: optionsRef.current?.callService
|
|
57883
|
+
previousState: result.previousState,
|
|
57884
|
+
newState: result.newState,
|
|
57885
|
+
payload,
|
|
57886
|
+
flushEvent: eventKey,
|
|
57887
|
+
syncOnly: false,
|
|
57888
|
+
log: stateLog
|
|
57475
57889
|
});
|
|
57476
|
-
const persistence = optionsRef.current?.persistence;
|
|
57477
|
-
let handlers = clientHandlers;
|
|
57478
|
-
if (persistence) {
|
|
57479
|
-
const sharedBindings = {
|
|
57480
|
-
entity: payload ?? {},
|
|
57481
|
-
payload: payload || {},
|
|
57482
|
-
state: result.previousState
|
|
57483
|
-
};
|
|
57484
|
-
const sharedDeclared = collectDeclaredConfigDefaults(
|
|
57485
|
-
binding.trait
|
|
57486
|
-
);
|
|
57487
|
-
const sharedCallSite = binding.config;
|
|
57488
|
-
if (sharedDeclared || sharedCallSite) {
|
|
57489
|
-
sharedBindings.config = {
|
|
57490
|
-
...sharedDeclared ?? {},
|
|
57491
|
-
...sharedCallSite ?? {}
|
|
57492
|
-
};
|
|
57493
|
-
}
|
|
57494
|
-
const serverHandlers = createServerEffectHandlers({
|
|
57495
|
-
persistence,
|
|
57496
|
-
eventBus,
|
|
57497
|
-
entityType: linkedEntity,
|
|
57498
|
-
entityId,
|
|
57499
|
-
bindings: sharedBindings,
|
|
57500
|
-
context: {
|
|
57501
|
-
traitName: binding.trait.name,
|
|
57502
|
-
state: result.previousState,
|
|
57503
|
-
transition: `${result.previousState}->${result.newState}`,
|
|
57504
|
-
linkedEntity,
|
|
57505
|
-
entityId
|
|
57506
|
-
},
|
|
57507
|
-
source: { trait: binding.trait.name },
|
|
57508
|
-
callService: optionsRef.current?.callService
|
|
57509
|
-
});
|
|
57510
|
-
handlers = {
|
|
57511
|
-
...serverHandlers,
|
|
57512
|
-
// Client handlers own UI + emit: keep the slot setter
|
|
57513
|
-
// and pre-prefixed UI:* emit path intact.
|
|
57514
|
-
emit: clientHandlers.emit,
|
|
57515
|
-
renderUI: clientHandlers.renderUI,
|
|
57516
|
-
navigate: clientHandlers.navigate,
|
|
57517
|
-
notify: clientHandlers.notify
|
|
57518
|
-
};
|
|
57519
|
-
}
|
|
57520
|
-
const baseSet = handlers.set;
|
|
57521
|
-
handlers = {
|
|
57522
|
-
...handlers,
|
|
57523
|
-
set: async (targetId, field, value) => {
|
|
57524
|
-
let fieldState = traitFieldStatesRef.current.get(traitName);
|
|
57525
|
-
if (!fieldState) {
|
|
57526
|
-
fieldState = {};
|
|
57527
|
-
traitFieldStatesRef.current.set(traitName, fieldState);
|
|
57528
|
-
}
|
|
57529
|
-
fieldState[field] = value;
|
|
57530
|
-
if (baseSet) await baseSet(targetId, field, value);
|
|
57531
|
-
}
|
|
57532
|
-
};
|
|
57533
|
-
const entityForBinding = traitFieldStatesRef.current.get(traitName) ?? {};
|
|
57534
|
-
const bindingCtx = {
|
|
57535
|
-
entity: entityForBinding,
|
|
57536
|
-
payload: payload || {},
|
|
57537
|
-
state: result.previousState
|
|
57538
|
-
};
|
|
57539
|
-
const declaredDefaults = collectDeclaredConfigDefaults(
|
|
57540
|
-
binding.trait
|
|
57541
|
-
);
|
|
57542
|
-
const callSiteConfig = binding.config;
|
|
57543
|
-
if (declaredDefaults || callSiteConfig) {
|
|
57544
|
-
bindingCtx.config = {
|
|
57545
|
-
...declaredDefaults ?? {},
|
|
57546
|
-
...callSiteConfig ?? {}
|
|
57547
|
-
};
|
|
57548
|
-
}
|
|
57549
|
-
const effectContext = {
|
|
57550
|
-
traitName: binding.trait.name,
|
|
57551
|
-
state: result.previousState,
|
|
57552
|
-
transition: `${result.previousState}->${result.newState}`,
|
|
57553
|
-
linkedEntity,
|
|
57554
|
-
entityId
|
|
57555
|
-
};
|
|
57556
|
-
const emittedDuringExec = [];
|
|
57557
57890
|
emittedByTrait.set(traitName, emittedDuringExec);
|
|
57558
|
-
const baseEmit = handlers.emit;
|
|
57559
|
-
const trackingHandlers = {
|
|
57560
|
-
...handlers,
|
|
57561
|
-
emit: (event, eventPayload, source) => {
|
|
57562
|
-
emittedDuringExec.push(event);
|
|
57563
|
-
baseEmit(event, eventPayload, source);
|
|
57564
|
-
}
|
|
57565
|
-
};
|
|
57566
|
-
const executor = new EffectExecutor({ handlers: trackingHandlers, bindings: bindingCtx, context: effectContext });
|
|
57567
|
-
try {
|
|
57568
|
-
await executor.executeAll(result.effects);
|
|
57569
|
-
stateLog.debug("transition:render-ui-dispatched", () => ({
|
|
57570
|
-
traitName,
|
|
57571
|
-
fromState: result.previousState,
|
|
57572
|
-
toState: result.newState,
|
|
57573
|
-
event: eventKey,
|
|
57574
|
-
slotsTouched: Array.from(pendingSlots.keys()).join(","),
|
|
57575
|
-
patternTypes: Array.from(pendingSlots.entries()).map(
|
|
57576
|
-
([slot, patterns]) => `${slot}:[${patterns.map((p2) => p2.pattern?.type ?? "null").join(",")}]`
|
|
57577
|
-
).join(";")
|
|
57578
|
-
}));
|
|
57579
|
-
void slotSource;
|
|
57580
|
-
for (const [slot, patterns] of pendingSlots) {
|
|
57581
|
-
stateLog.debug("flush:slot", {
|
|
57582
|
-
traitName,
|
|
57583
|
-
slot,
|
|
57584
|
-
patternCount: patterns.length,
|
|
57585
|
-
event: eventKey,
|
|
57586
|
-
transition: `${result.previousState}->${result.newState}`,
|
|
57587
|
-
cleared: patterns.length === 0
|
|
57588
|
-
});
|
|
57589
|
-
flushSlot(traitName, slot, patterns, {
|
|
57590
|
-
event: eventKey,
|
|
57591
|
-
state: result.previousState,
|
|
57592
|
-
entity: binding.linkedEntity
|
|
57593
|
-
});
|
|
57594
|
-
}
|
|
57595
|
-
} catch (error) {
|
|
57596
|
-
stateLog.error("transition:effect-error", {
|
|
57597
|
-
traitName,
|
|
57598
|
-
fromState: result.previousState,
|
|
57599
|
-
toState: result.newState,
|
|
57600
|
-
event: eventKey,
|
|
57601
|
-
error: error instanceof Error ? error.message : String(error),
|
|
57602
|
-
effectCount: result.effects.length
|
|
57603
|
-
});
|
|
57604
|
-
}
|
|
57605
57891
|
} else if (!result.executed) {
|
|
57606
57892
|
if (result.guardResult === false) {
|
|
57607
57893
|
stateLog.debug("guard-blocked-transition", {
|
|
@@ -57739,7 +58025,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
57739
58025
|
if (!orbitalName) continue;
|
|
57740
58026
|
for (const transition of binding.trait.transitions) {
|
|
57741
58027
|
const eventKey = transition.event;
|
|
57742
|
-
if (eventKey
|
|
58028
|
+
if (LIFECYCLE_EVENTS.includes(eventKey)) {
|
|
57743
58029
|
continue;
|
|
57744
58030
|
}
|
|
57745
58031
|
const selfBusKey = `UI:${orbitalName}.${traitName}.${eventKey}`;
|
|
@@ -57794,6 +58080,24 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
|
|
|
57794
58080
|
crossTraitLog.debug("cleanup:done", {});
|
|
57795
58081
|
};
|
|
57796
58082
|
}, [traitBindings, eventBus, enqueueAndDrain]);
|
|
58083
|
+
useEffect(() => {
|
|
58084
|
+
const mgr = managerRef.current;
|
|
58085
|
+
const inited = initedTraitsRef.current;
|
|
58086
|
+
for (const binding of traitBindings) {
|
|
58087
|
+
const traitName = binding.trait.name;
|
|
58088
|
+
if (inited.has(traitName)) continue;
|
|
58089
|
+
const lifecycleEvent = LIFECYCLE_EVENTS.find(
|
|
58090
|
+
(evt) => mgr.canHandleEvent(traitName, evt)
|
|
58091
|
+
);
|
|
58092
|
+
if (lifecycleEvent === void 0) continue;
|
|
58093
|
+
inited.add(traitName);
|
|
58094
|
+
stateLog.debug("mount:fire-lifecycle", { traitName, event: lifecycleEvent });
|
|
58095
|
+
enqueueAndDrain(lifecycleEvent, {});
|
|
58096
|
+
}
|
|
58097
|
+
return () => {
|
|
58098
|
+
initedTraitsRef.current = /* @__PURE__ */ new Set();
|
|
58099
|
+
};
|
|
58100
|
+
}, [traitBindings, enqueueAndDrain]);
|
|
57797
58101
|
return {
|
|
57798
58102
|
traitStates,
|
|
57799
58103
|
sendEvent,
|