@almadar/ui 5.71.0 → 5.72.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.
@@ -15,12 +15,10 @@ export interface ScoreDisplayProps {
15
15
  size?: 'sm' | 'md' | 'lg' | 'xl';
16
16
  /** Additional CSS classes */
17
17
  className?: string;
18
- /** Animation on value change */
19
- animated?: boolean;
20
18
  /** Number formatting locale */
21
19
  locale?: string;
22
20
  }
23
- export declare function ScoreDisplay({ assetUrl, value, score, label, icon, size, className, animated, locale, }: ScoreDisplayProps): import("react/jsx-runtime").JSX.Element;
21
+ export declare function ScoreDisplay({ assetUrl, value, score, label, icon, size, className, locale, }: ScoreDisplayProps): import("react/jsx-runtime").JSX.Element;
24
22
  export declare namespace ScoreDisplay {
25
23
  var displayName: string;
26
24
  }
@@ -5,6 +5,7 @@
5
5
  */
6
6
  import React from 'react';
7
7
  import type { EventKey, AssetUrl } from "@almadar/core";
8
+ import type { AnimationName } from '../organisms/types/spriteAnimation';
8
9
  export interface SpriteProps {
9
10
  /** Spritesheet image URL */
10
11
  spritesheet: AssetUrl;
@@ -32,6 +33,10 @@ export interface SpriteProps {
32
33
  zIndex?: number;
33
34
  /** Number of columns in spritesheet (for frame calculation) */
34
35
  columns?: number;
36
+ /** Animation state name — drives lolo-ui config knob for animation selection */
37
+ animState?: AnimationName;
38
+ /** Frames per second — drives lolo-ui config knob for animation speed */
39
+ frameRate?: number;
35
40
  /** Optional className */
36
41
  className?: string;
37
42
  /** Optional onClick handler */
@@ -56,7 +61,7 @@ export interface SpriteProps {
56
61
  * />
57
62
  * ```
58
63
  */
59
- export declare function Sprite({ spritesheet, frameWidth, frameHeight, frame, x, y, scale, flipX, flipY, rotation, opacity, zIndex, columns, className, onClick, action, }: SpriteProps): React.JSX.Element;
64
+ export declare function Sprite({ spritesheet, frameWidth, frameHeight, frame, x, y, scale, flipX, flipY, rotation, opacity, zIndex, columns, animState: _animState, frameRate: _frameRate, className, onClick, action, }: SpriteProps): React.JSX.Element;
60
65
  /**
61
66
  * Canvas-based sprite renderer for better performance in game loops
62
67
  */
@@ -9,7 +9,7 @@ export interface XPBarProps {
9
9
  showLabel?: boolean;
10
10
  /** Size variant */
11
11
  size?: 'sm' | 'md' | 'lg';
12
- /** Animate the fill bar */
12
+ /** Animate the fill bar with a CSS transition */
13
13
  animated?: boolean;
14
14
  /** Additional CSS classes */
15
15
  className?: string;
@@ -46,8 +46,6 @@ export interface GameCanvas3DProps {
46
46
  isLoading?: boolean;
47
47
  /** Error state */
48
48
  error?: string | null;
49
- /** Entity name for schema-driven auto-fetch */
50
- entity?: string;
51
49
  /** Array of tiles to render */
52
50
  tiles?: IsometricTile[];
53
51
  /** Array of units to render */
@@ -25,7 +25,7 @@
25
25
  */
26
26
  import * as React from 'react';
27
27
  import type { AssetUrl, EventEmit } from '@almadar/core';
28
- import type { IsometricTile, IsometricUnit, IsometricFeature } from '../organisms/types/isometric';
28
+ import type { IsometricTile, IsometricUnit, IsometricFeature, ActiveEffect } from '../organisms/types/isometric';
29
29
  import type { ResolvedFrame } from '../organisms/types/spriteAnimation';
30
30
  import type { TileLayout } from '../organisms/utils/isometric';
31
31
  import type { UiError } from '../../core/atoms/types';
@@ -52,6 +52,8 @@ export interface IsometricCanvasProps {
52
52
  units?: IsometricUnit[];
53
53
  /** Array of features (resources, portals, buildings, etc.) */
54
54
  features?: IsometricFeature[];
55
+ /** Active visual effects to draw after units. Sprite URLs resolved via assetManifest.effects[fx.key]. */
56
+ effects?: ActiveEffect[];
55
57
  /** Currently selected unit ID */
56
58
  selectedUnitId?: string | null;
57
59
  /** Valid move positions (shown as pulsing green highlights) */
@@ -132,7 +134,7 @@ export interface IsometricCanvasProps {
132
134
  effects?: Record<string, string>;
133
135
  };
134
136
  }
135
- export declare function IsometricCanvas({ className, isLoading, error, tiles: _tilesPropRaw, units: _unitsPropRaw, features: _featuresPropRaw, selectedUnitId, validMoves, attackTargets, hoveredTile, onTileClick, onUnitClick, onTileHover, onTileLeave, tileClickEvent, unitClickEvent, tileHoverEvent, tileLeaveEvent, tileLayout, scale, debug, backgroundImage, showMinimap, enableCamera, unitScale, spriteHeightRatio, spriteMaxWidthRatio, getTerrainSprite, getFeatureSprite, getUnitSprite, resolveUnitFrame, effectSpriteUrls, onDrawEffects, hasActiveEffects, diamondTopY: diamondTopYProp, assetBaseUrl, assetManifest, }: IsometricCanvasProps): React.JSX.Element;
137
+ export declare function IsometricCanvas({ className, isLoading, error, tiles: _tilesPropRaw, units: _unitsPropRaw, features: _featuresPropRaw, effects: _effectsPropRaw, selectedUnitId, validMoves, attackTargets, hoveredTile, onTileClick, onUnitClick, onTileHover, onTileLeave, tileClickEvent, unitClickEvent, tileHoverEvent, tileLeaveEvent, tileLayout, scale, debug, backgroundImage, showMinimap, enableCamera, unitScale, spriteHeightRatio, spriteMaxWidthRatio, getTerrainSprite, getFeatureSprite, getUnitSprite, resolveUnitFrame, effectSpriteUrls, onDrawEffects, hasActiveEffects: _hasActiveEffects, diamondTopY: diamondTopYProp, assetBaseUrl, assetManifest, }: IsometricCanvasProps): React.JSX.Element;
136
138
  export declare namespace IsometricCanvas {
137
139
  var displayName: string;
138
140
  }
@@ -1,4 +1,5 @@
1
1
  import type { AssetUrl, EventEmit } from '@almadar/core';
2
+ import type { ActiveEffect } from '../organisms/types/isometric';
2
3
  export interface PlatformerPlatform {
3
4
  x: number;
4
5
  y: number;
@@ -48,10 +49,12 @@ export interface PlatformerCanvasProps {
48
49
  }>;
49
50
  jumpEvent?: EventEmit<Record<string, never>>;
50
51
  stopEvent?: EventEmit<Record<string, never>>;
52
+ /** Active visual effects (key, world x/y, ttl) — drawn after the player */
53
+ effects?: ActiveEffect[];
51
54
  /** Additional CSS classes */
52
55
  className?: string;
53
56
  }
54
- export declare function PlatformerCanvas({ player, platforms, worldWidth, worldHeight, canvasWidth, canvasHeight, followCamera, bgColor, playerSprite, tileSprites, backgroundImage, assetBaseUrl, leftEvent, rightEvent, jumpEvent, stopEvent, className, }: PlatformerCanvasProps): import("react/jsx-runtime").JSX.Element;
57
+ export declare function PlatformerCanvas({ player, platforms, worldWidth, worldHeight, canvasWidth, canvasHeight, followCamera, bgColor, playerSprite, tileSprites, backgroundImage, assetBaseUrl, leftEvent, rightEvent, jumpEvent, stopEvent, effects, className, }: PlatformerCanvasProps): import("react/jsx-runtime").JSX.Element;
55
58
  export declare namespace PlatformerCanvas {
56
59
  var displayName: string;
57
60
  }
@@ -9,6 +9,8 @@ import type { AssetUrl } from '@almadar/core';
9
9
  export interface StatBadgeProps {
10
10
  /** Sprite image URL — takes precedence over icon when provided */
11
11
  assetUrl?: AssetUrl;
12
+ /** Asset-image icon URL (board ui/ PNG); takes precedence over the Lucide icon. */
13
+ iconUrl?: AssetUrl;
12
14
  /** Stat label */
13
15
  label: string;
14
16
  /** Current value (defaults to 0 if not provided) */
@@ -30,7 +32,7 @@ export interface StatBadgeProps {
30
32
  /** Additional CSS classes */
31
33
  className?: string;
32
34
  }
33
- export declare function StatBadge({ assetUrl, label, value, max, format, icon, size, variant, className, source: _source, field: _field, }: StatBadgeProps): import("react/jsx-runtime").JSX.Element;
35
+ export declare function StatBadge({ assetUrl, iconUrl, label, value, max, format, icon, size, variant, className, source: _source, field: _field, }: StatBadgeProps): import("react/jsx-runtime").JSX.Element;
34
36
  export declare namespace StatBadge {
35
37
  var displayName: string;
36
38
  }
@@ -2293,13 +2293,6 @@ function preloadFeatures(urls) {
2293
2293
  }
2294
2294
 
2295
2295
  // components/game/organisms/utils/spriteAnimation.ts
2296
- function inferDirection(dx, dy) {
2297
- if (dx === 0 && dy === 0) return "se";
2298
- if (dx >= 0 && dy >= 0) return "se";
2299
- if (dx <= 0 && dy >= 0) return "sw";
2300
- if (dx >= 0 && dy <= 0) return "ne";
2301
- return "nw";
2302
- }
2303
2296
  function resolveSheetDirection(facing) {
2304
2297
  switch (facing) {
2305
2298
  case "se":
@@ -2320,22 +2313,8 @@ function frameRect(frame, row, columns, frameWidth, frameHeight) {
2320
2313
  sh: frameHeight
2321
2314
  };
2322
2315
  }
2323
- function getCurrentFrameFromDef(def, elapsed) {
2324
- const frameDuration = 1e3 / def.frameRate;
2325
- const totalDuration = def.frames * frameDuration;
2326
- if (def.loop) {
2327
- const frame2 = Math.floor(elapsed % totalDuration / frameDuration);
2328
- return { frame: frame2, finished: false };
2329
- }
2330
- if (elapsed >= totalDuration) {
2331
- return { frame: def.frames - 1, finished: true };
2332
- }
2333
- const frame = Math.floor(elapsed / frameDuration);
2334
- return { frame, finished: false };
2335
- }
2336
2316
 
2337
2317
  // components/game/molecules/useUnitSpriteAtlas.ts
2338
- var WALK_HOLD_MS = 600;
2339
2318
  function unitAtlasUrl(unit) {
2340
2319
  if (unit.spriteSheet) return unit.spriteSheet;
2341
2320
  const sprite = unit.sprite;
@@ -2357,9 +2336,6 @@ function useUnitSpriteAtlas(units) {
2357
2336
  const loadingRef = React11.useRef(/* @__PURE__ */ new Set());
2358
2337
  const [pendingCount, setPendingCount] = React11.useState(0);
2359
2338
  const [, forceTick] = React11.useState(0);
2360
- const animStatesRef = React11.useRef(/* @__PURE__ */ new Map());
2361
- const lastTickRef = React11.useRef(0);
2362
- const rafRef = React11.useRef(0);
2363
2339
  const atlasUrls = React11.useMemo(() => {
2364
2340
  const set = /* @__PURE__ */ new Set();
2365
2341
  for (const unit of units) {
@@ -2405,54 +2381,6 @@ function useUnitSpriteAtlas(units) {
2405
2381
  }
2406
2382
  return [...urls];
2407
2383
  }, [units, pendingCount]);
2408
- React11.useEffect(() => {
2409
- const hasAtlasUnits = units.some((u) => unitAtlasUrl(u) !== null);
2410
- if (!hasAtlasUnits) return;
2411
- let running = true;
2412
- const tick = (ts) => {
2413
- if (!running) return;
2414
- const last = lastTickRef.current || ts;
2415
- const delta = ts - last;
2416
- lastTickRef.current = ts;
2417
- const states = animStatesRef.current;
2418
- const currentIds = /* @__PURE__ */ new Set();
2419
- for (const unit of units) {
2420
- if (unitAtlasUrl(unit) === null) continue;
2421
- currentIds.add(unit.id);
2422
- let state = states.get(unit.id);
2423
- if (!state) {
2424
- state = { animation: "idle", direction: "se", elapsed: 0, walkHold: 0, prev: null };
2425
- states.set(unit.id, state);
2426
- }
2427
- const posX = unit.position?.x ?? unit.x ?? 0;
2428
- const posY = unit.position?.y ?? unit.y ?? 0;
2429
- if (state.prev) {
2430
- const dx = posX - state.prev.x;
2431
- const dy = posY - state.prev.y;
2432
- if (dx !== 0 || dy !== 0) {
2433
- state.animation = "walk";
2434
- state.direction = inferDirection(dx, dy);
2435
- state.walkHold = WALK_HOLD_MS;
2436
- } else if (state.animation === "walk") {
2437
- state.walkHold -= delta;
2438
- if (state.walkHold <= 0) state.animation = "idle";
2439
- }
2440
- }
2441
- state.prev = { x: posX, y: posY };
2442
- state.elapsed += delta;
2443
- }
2444
- for (const id of states.keys()) {
2445
- if (!currentIds.has(id)) states.delete(id);
2446
- }
2447
- rafRef.current = requestAnimationFrame(tick);
2448
- };
2449
- rafRef.current = requestAnimationFrame(tick);
2450
- return () => {
2451
- running = false;
2452
- cancelAnimationFrame(rafRef.current);
2453
- lastTickRef.current = 0;
2454
- };
2455
- }, [units]);
2456
2384
  const resolveUnitFrame = React11.useCallback((unitId) => {
2457
2385
  const unit = units.find((u) => u.id === unitId);
2458
2386
  if (!unit) return null;
@@ -2460,18 +2388,14 @@ function useUnitSpriteAtlas(units) {
2460
2388
  if (!atlasUrl) return null;
2461
2389
  const atlas = atlasCacheRef.current.get(atlasUrl);
2462
2390
  if (!atlas) return null;
2463
- const state = animStatesRef.current.get(unitId);
2464
- const animation = state?.animation ?? "idle";
2465
- const direction = state?.direction ?? "se";
2466
- const elapsed = state?.elapsed ?? 0;
2391
+ const animation = unit.animation ?? "idle";
2392
+ const frame = unit.frame ?? 0;
2467
2393
  const def = atlas.animations[animation] ?? atlas.animations.idle;
2468
2394
  if (!def) return null;
2469
- const { sheetDir, flipX } = resolveSheetDirection(direction);
2395
+ const { sheetDir, flipX } = resolveSheetDirection("se");
2470
2396
  const rel = atlas.sheets[sheetDir] ?? atlas.sheets.se ?? atlas.sheets.sw;
2471
2397
  if (!rel) return null;
2472
2398
  const sheetUrl = resolveSheetUrl(atlasUrl, rel);
2473
- const isIdle = animation === "idle";
2474
- const frame = isIdle ? 0 : getCurrentFrameFromDef(def, elapsed).frame;
2475
2399
  const rect = frameRect(frame, def.row, atlas.columns, atlas.frameWidth, atlas.frameHeight);
2476
2400
  return {
2477
2401
  sheetUrl,
@@ -2480,7 +2404,7 @@ function useUnitSpriteAtlas(units) {
2480
2404
  sw: rect.sw,
2481
2405
  sh: rect.sh,
2482
2406
  flipX,
2483
- applyBreathing: isIdle
2407
+ applyBreathing: animation === "idle"
2484
2408
  };
2485
2409
  }, [units]);
2486
2410
  return { sheetUrls, resolveUnitFrame, pendingCount };
@@ -2576,7 +2500,6 @@ var GameCanvas3D = React11.forwardRef(
2576
2500
  className,
2577
2501
  isLoading: externalLoading,
2578
2502
  error: externalError,
2579
- entity,
2580
2503
  preloadAssets = [],
2581
2504
  tileClickEvent,
2582
2505
  unitClickEvent,
@@ -2269,13 +2269,6 @@ function preloadFeatures(urls) {
2269
2269
  }
2270
2270
 
2271
2271
  // components/game/organisms/utils/spriteAnimation.ts
2272
- function inferDirection(dx, dy) {
2273
- if (dx === 0 && dy === 0) return "se";
2274
- if (dx >= 0 && dy >= 0) return "se";
2275
- if (dx <= 0 && dy >= 0) return "sw";
2276
- if (dx >= 0 && dy <= 0) return "ne";
2277
- return "nw";
2278
- }
2279
2272
  function resolveSheetDirection(facing) {
2280
2273
  switch (facing) {
2281
2274
  case "se":
@@ -2296,22 +2289,8 @@ function frameRect(frame, row, columns, frameWidth, frameHeight) {
2296
2289
  sh: frameHeight
2297
2290
  };
2298
2291
  }
2299
- function getCurrentFrameFromDef(def, elapsed) {
2300
- const frameDuration = 1e3 / def.frameRate;
2301
- const totalDuration = def.frames * frameDuration;
2302
- if (def.loop) {
2303
- const frame2 = Math.floor(elapsed % totalDuration / frameDuration);
2304
- return { frame: frame2, finished: false };
2305
- }
2306
- if (elapsed >= totalDuration) {
2307
- return { frame: def.frames - 1, finished: true };
2308
- }
2309
- const frame = Math.floor(elapsed / frameDuration);
2310
- return { frame, finished: false };
2311
- }
2312
2292
 
2313
2293
  // components/game/molecules/useUnitSpriteAtlas.ts
2314
- var WALK_HOLD_MS = 600;
2315
2294
  function unitAtlasUrl(unit) {
2316
2295
  if (unit.spriteSheet) return unit.spriteSheet;
2317
2296
  const sprite = unit.sprite;
@@ -2333,9 +2312,6 @@ function useUnitSpriteAtlas(units) {
2333
2312
  const loadingRef = useRef(/* @__PURE__ */ new Set());
2334
2313
  const [pendingCount, setPendingCount] = useState(0);
2335
2314
  const [, forceTick] = useState(0);
2336
- const animStatesRef = useRef(/* @__PURE__ */ new Map());
2337
- const lastTickRef = useRef(0);
2338
- const rafRef = useRef(0);
2339
2315
  const atlasUrls = useMemo(() => {
2340
2316
  const set = /* @__PURE__ */ new Set();
2341
2317
  for (const unit of units) {
@@ -2381,54 +2357,6 @@ function useUnitSpriteAtlas(units) {
2381
2357
  }
2382
2358
  return [...urls];
2383
2359
  }, [units, pendingCount]);
2384
- useEffect(() => {
2385
- const hasAtlasUnits = units.some((u) => unitAtlasUrl(u) !== null);
2386
- if (!hasAtlasUnits) return;
2387
- let running = true;
2388
- const tick = (ts) => {
2389
- if (!running) return;
2390
- const last = lastTickRef.current || ts;
2391
- const delta = ts - last;
2392
- lastTickRef.current = ts;
2393
- const states = animStatesRef.current;
2394
- const currentIds = /* @__PURE__ */ new Set();
2395
- for (const unit of units) {
2396
- if (unitAtlasUrl(unit) === null) continue;
2397
- currentIds.add(unit.id);
2398
- let state = states.get(unit.id);
2399
- if (!state) {
2400
- state = { animation: "idle", direction: "se", elapsed: 0, walkHold: 0, prev: null };
2401
- states.set(unit.id, state);
2402
- }
2403
- const posX = unit.position?.x ?? unit.x ?? 0;
2404
- const posY = unit.position?.y ?? unit.y ?? 0;
2405
- if (state.prev) {
2406
- const dx = posX - state.prev.x;
2407
- const dy = posY - state.prev.y;
2408
- if (dx !== 0 || dy !== 0) {
2409
- state.animation = "walk";
2410
- state.direction = inferDirection(dx, dy);
2411
- state.walkHold = WALK_HOLD_MS;
2412
- } else if (state.animation === "walk") {
2413
- state.walkHold -= delta;
2414
- if (state.walkHold <= 0) state.animation = "idle";
2415
- }
2416
- }
2417
- state.prev = { x: posX, y: posY };
2418
- state.elapsed += delta;
2419
- }
2420
- for (const id of states.keys()) {
2421
- if (!currentIds.has(id)) states.delete(id);
2422
- }
2423
- rafRef.current = requestAnimationFrame(tick);
2424
- };
2425
- rafRef.current = requestAnimationFrame(tick);
2426
- return () => {
2427
- running = false;
2428
- cancelAnimationFrame(rafRef.current);
2429
- lastTickRef.current = 0;
2430
- };
2431
- }, [units]);
2432
2360
  const resolveUnitFrame = useCallback((unitId) => {
2433
2361
  const unit = units.find((u) => u.id === unitId);
2434
2362
  if (!unit) return null;
@@ -2436,18 +2364,14 @@ function useUnitSpriteAtlas(units) {
2436
2364
  if (!atlasUrl) return null;
2437
2365
  const atlas = atlasCacheRef.current.get(atlasUrl);
2438
2366
  if (!atlas) return null;
2439
- const state = animStatesRef.current.get(unitId);
2440
- const animation = state?.animation ?? "idle";
2441
- const direction = state?.direction ?? "se";
2442
- const elapsed = state?.elapsed ?? 0;
2367
+ const animation = unit.animation ?? "idle";
2368
+ const frame = unit.frame ?? 0;
2443
2369
  const def = atlas.animations[animation] ?? atlas.animations.idle;
2444
2370
  if (!def) return null;
2445
- const { sheetDir, flipX } = resolveSheetDirection(direction);
2371
+ const { sheetDir, flipX } = resolveSheetDirection("se");
2446
2372
  const rel = atlas.sheets[sheetDir] ?? atlas.sheets.se ?? atlas.sheets.sw;
2447
2373
  if (!rel) return null;
2448
2374
  const sheetUrl = resolveSheetUrl(atlasUrl, rel);
2449
- const isIdle = animation === "idle";
2450
- const frame = isIdle ? 0 : getCurrentFrameFromDef(def, elapsed).frame;
2451
2375
  const rect = frameRect(frame, def.row, atlas.columns, atlas.frameWidth, atlas.frameHeight);
2452
2376
  return {
2453
2377
  sheetUrl,
@@ -2456,7 +2380,7 @@ function useUnitSpriteAtlas(units) {
2456
2380
  sw: rect.sw,
2457
2381
  sh: rect.sh,
2458
2382
  flipX,
2459
- applyBreathing: isIdle
2383
+ applyBreathing: animation === "idle"
2460
2384
  };
2461
2385
  }, [units]);
2462
2386
  return { sheetUrls, resolveUnitFrame, pendingCount };
@@ -2552,7 +2476,6 @@ var GameCanvas3D = forwardRef(
2552
2476
  className,
2553
2477
  isLoading: externalLoading,
2554
2478
  error: externalError,
2555
- entity,
2556
2479
  preloadAssets = [],
2557
2480
  tileClickEvent,
2558
2481
  unitClickEvent,
@@ -23,8 +23,9 @@ export interface UseUnitSpriteAtlasResult {
23
23
  pendingCount: number;
24
24
  }
25
25
  /**
26
- * Load per-unit sprite atlases and drive single-frame animation.
26
+ * Load per-unit sprite atlases and expose a pure frame resolver.
27
+ * Frame is computed from each unit's own `animation` + `frame` props — no internal clock.
27
28
  *
28
- * @param units - Units to animate. Only units with a `spriteSheet` URL participate.
29
+ * @param units - Units whose atlases should be loaded.
29
30
  */
30
31
  export declare function useUnitSpriteAtlas(units: IsometricUnit[]): UseUnitSpriteAtlasResult;
@@ -1,4 +1,5 @@
1
1
  import type { AssetUrl } from '@almadar/core';
2
+ import type { AnimationName } from './spriteAnimation';
2
3
  /**
3
4
  * Isometric Game Types
4
5
  *
@@ -7,6 +8,17 @@ import type { AssetUrl } from '@almadar/core';
7
8
  *
8
9
  * @packageDocumentation
9
10
  */
11
+ /** An active visual effect on the board. x,y are grid tile coords; ttl = remaining ticks. */
12
+ export interface ActiveEffect {
13
+ /** Unique key — matched against assetManifest.effects for sprite lookup */
14
+ key: string;
15
+ /** Grid x coordinate */
16
+ x: number;
17
+ /** Grid y coordinate */
18
+ y: number;
19
+ /** Remaining ticks — used as a fade/scale hint (alpha = min(1, ttl/4)) */
20
+ ttl: number;
21
+ }
10
22
  /** A single isometric tile on the grid */
11
23
  export interface IsometricTile {
12
24
  /** Optional unique identifier (required for 3D rendering) */
@@ -85,6 +97,10 @@ export type IsometricUnit = {
85
97
  };
86
98
  /** Elevation offset for 3D rendering */
87
99
  elevation?: number;
100
+ /** Animation name — driven by the LOLO state machine; renderer defaults to 'idle' */
101
+ animation?: AnimationName;
102
+ /** Frame index within the animation — driven by the LOLO state machine; renderer defaults to 0 */
103
+ frame?: number;
88
104
  };
89
105
  /** A map feature (resource, building, portal, etc.) */
90
106
  export type IsometricFeature = {