@almadar/ui 5.72.0 → 5.73.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.
Files changed (52) hide show
  1. package/dist/avl/index.cjs +402 -362
  2. package/dist/avl/index.js +402 -362
  3. package/dist/components/game/atoms/ActionButton.d.ts +3 -3
  4. package/dist/components/game/atoms/ChoiceButton.d.ts +3 -3
  5. package/dist/components/game/atoms/ComboCounter.d.ts +3 -3
  6. package/dist/components/game/atoms/ControlButton.d.ts +3 -3
  7. package/dist/components/game/atoms/DamageNumber.d.ts +3 -3
  8. package/dist/components/game/atoms/DialogueBubble.d.ts +3 -3
  9. package/dist/components/game/atoms/ItemSlot.d.ts +3 -3
  10. package/dist/components/game/atoms/ResourceCounter.d.ts +3 -3
  11. package/dist/components/game/atoms/ScoreDisplay.d.ts +3 -3
  12. package/dist/components/game/atoms/Sprite.d.ts +3 -20
  13. package/dist/components/game/atoms/StateIndicator.d.ts +2 -2
  14. package/dist/components/game/atoms/StatusEffect.d.ts +3 -3
  15. package/dist/components/game/atoms/TurnIndicator.d.ts +3 -3
  16. package/dist/components/game/atoms/WaypointMarker.d.ts +3 -3
  17. package/dist/components/game/molecules/CardHand.d.ts +2 -2
  18. package/dist/components/game/molecules/DialogueBox.d.ts +3 -3
  19. package/dist/components/game/molecules/EnemyPlate.d.ts +3 -3
  20. package/dist/components/game/molecules/GameCanvas2D.d.ts +3 -3
  21. package/dist/components/game/molecules/HealthPanel.d.ts +3 -3
  22. package/dist/components/game/molecules/IsometricCanvas.d.ts +8 -12
  23. package/dist/components/game/molecules/PowerupSlots.d.ts +3 -3
  24. package/dist/components/game/molecules/StatBadge.d.ts +5 -5
  25. package/dist/components/game/molecules/TurnPanel.d.ts +3 -3
  26. package/dist/components/game/molecules/three/index.cjs +51 -28
  27. package/dist/components/game/molecules/three/index.js +51 -28
  28. package/dist/components/game/molecules/useUnitSpriteAtlas.d.ts +3 -10
  29. package/dist/components/game/organisms/BattleBoard.d.ts +5 -6
  30. package/dist/components/game/organisms/CardBattlerBoard.d.ts +3 -4
  31. package/dist/components/game/organisms/CastleBoard.d.ts +5 -6
  32. package/dist/components/game/organisms/CityBuilderBoard.d.ts +6 -7
  33. package/dist/components/game/organisms/FishingBoard.d.ts +4 -6
  34. package/dist/components/game/organisms/PlatformerBoard.d.ts +5 -5
  35. package/dist/components/game/organisms/RoguelikeBoard.d.ts +4 -5
  36. package/dist/components/game/organisms/TopDownShooterBoard.d.ts +5 -6
  37. package/dist/components/game/organisms/TowerDefenseBoard.d.ts +5 -6
  38. package/dist/components/game/organisms/VisualNovelBoard.d.ts +4 -5
  39. package/dist/components/game/organisms/WorldMapBoard.d.ts +5 -6
  40. package/dist/components/game/organisms/types/effects.d.ts +7 -8
  41. package/dist/components/game/organisms/types/isometric.d.ts +8 -8
  42. package/dist/components/game/organisms/types/spriteAnimation.d.ts +3 -4
  43. package/dist/components/game/organisms/utils/makeAsset.d.ts +5 -0
  44. package/dist/components/game/templates/PlatformerTemplate.d.ts +5 -4
  45. package/dist/components/game/templates/game3dAssetManifest.d.ts +6 -8
  46. package/dist/components/index.cjs +417 -363
  47. package/dist/components/index.js +417 -363
  48. package/dist/providers/index.cjs +402 -362
  49. package/dist/providers/index.js +402 -362
  50. package/dist/runtime/index.cjs +402 -362
  51. package/dist/runtime/index.js +402 -362
  52. package/package.json +2 -2
@@ -1,8 +1,8 @@
1
1
  import { type IconInput } from '../../core/atoms/Icon';
2
- import type { AssetUrl } from '@almadar/core';
2
+ import type { Asset } from '@almadar/core';
3
3
  export interface ActionButtonProps {
4
- /** Sprite image URL — takes precedence over icon when provided */
5
- assetUrl?: AssetUrl;
4
+ /** Sprite asset — takes precedence over icon when provided */
5
+ assetUrl?: Asset;
6
6
  /** Button label text */
7
7
  label: string;
8
8
  /** Icon displayed before the label */
@@ -1,12 +1,12 @@
1
1
  import { type IconInput } from '../../core/atoms/Icon';
2
- import type { AssetUrl } from '@almadar/core';
2
+ import type { Asset } from '@almadar/core';
3
3
  export interface ChoiceButtonProps {
4
4
  /** Choice text content */
5
5
  text: string;
6
6
  /** Choice index number (displayed as prefix) */
7
7
  index?: number;
8
- /** Sprite image URL — takes precedence over icon when provided */
9
- assetUrl?: AssetUrl;
8
+ /** Sprite asset — takes precedence over icon when provided */
9
+ assetUrl?: Asset;
10
10
  /** Icon displayed before the text */
11
11
  icon?: IconInput;
12
12
  /** Whether the choice is disabled */
@@ -1,7 +1,7 @@
1
- import type { AssetUrl } from '@almadar/core';
1
+ import type { Asset } from '@almadar/core';
2
2
  export interface ComboCounterProps {
3
- /** Sprite image URL displayed alongside the combo number */
4
- assetUrl?: AssetUrl;
3
+ /** Sprite asset displayed alongside the combo number */
4
+ assetUrl?: Asset;
5
5
  /** Current combo count */
6
6
  combo: number;
7
7
  /** Score multiplier */
@@ -1,8 +1,8 @@
1
- import type { EventKey, AssetUrl } from "@almadar/core";
1
+ import type { EventKey, Asset } from "@almadar/core";
2
2
  import { type IconInput } from '../../core/atoms/Icon';
3
3
  export interface ControlButtonProps {
4
- /** Sprite image URL — takes precedence over icon when provided */
5
- assetUrl?: AssetUrl;
4
+ /** Sprite asset — takes precedence over icon when provided */
5
+ assetUrl?: Asset;
6
6
  /** Button label text */
7
7
  label?: string;
8
8
  /** Icon component or emoji */
@@ -1,7 +1,7 @@
1
- import type { AssetUrl } from '@almadar/core';
1
+ import type { Asset } from '@almadar/core';
2
2
  export interface DamageNumberProps {
3
- /** Sprite image URL — displayed as a hit-effect icon alongside the number */
4
- assetUrl?: AssetUrl;
3
+ /** Sprite asset — displayed as a hit-effect icon alongside the number */
4
+ assetUrl?: Asset;
5
5
  /** The damage/heal value to display */
6
6
  value: number;
7
7
  /** Type of number display */
@@ -1,11 +1,11 @@
1
- import type { AssetUrl } from '@almadar/core';
1
+ import type { Asset } from '@almadar/core';
2
2
  export interface DialogueBubbleProps {
3
3
  /** Speaker name displayed at the top */
4
4
  speaker?: string;
5
5
  /** Dialogue text content */
6
6
  text: string;
7
- /** URL for the speaker portrait image */
8
- portrait?: AssetUrl;
7
+ /** Speaker portrait asset */
8
+ portrait?: Asset;
9
9
  /** Position of the bubble on screen */
10
10
  position?: 'top' | 'bottom';
11
11
  /** Additional CSS classes */
@@ -1,8 +1,8 @@
1
1
  import { type IconInput } from '../../core/atoms/Icon';
2
- import type { AssetUrl } from '@almadar/core';
2
+ import type { Asset } from '@almadar/core';
3
3
  export interface ItemSlotProps {
4
- /** Sprite image URL — takes precedence over icon when provided */
5
- assetUrl?: AssetUrl;
4
+ /** Sprite asset — takes precedence over icon when provided */
5
+ assetUrl?: Asset;
6
6
  /** Icon component or emoji — shown only when assetUrl is absent */
7
7
  icon?: IconInput;
8
8
  /** Item label */
@@ -1,9 +1,9 @@
1
1
  import type { ColorToken } from '../../core/atoms/types';
2
2
  import { type IconInput } from '../../core/atoms/Icon';
3
- import type { AssetUrl } from '@almadar/core';
3
+ import type { Asset } from '@almadar/core';
4
4
  export interface ResourceCounterProps {
5
- /** Sprite image URL — takes precedence over icon when provided */
6
- assetUrl?: AssetUrl;
5
+ /** Sprite asset — takes precedence over icon when provided */
6
+ assetUrl?: Asset;
7
7
  /** Icon component or emoji */
8
8
  icon?: IconInput;
9
9
  /** Resource label */
@@ -1,8 +1,8 @@
1
1
  import { type IconInput } from '../../core/atoms/Icon';
2
- import type { AssetUrl } from '@almadar/core';
2
+ import type { Asset } from '@almadar/core';
3
3
  export interface ScoreDisplayProps {
4
- /** Sprite image URL — takes precedence over icon when provided */
5
- assetUrl?: AssetUrl;
4
+ /** Sprite asset — takes precedence over icon when provided */
5
+ assetUrl?: Asset;
6
6
  /** Current score value */
7
7
  value: number;
8
8
  /** Alias for value — common schema binding name */
@@ -4,11 +4,11 @@
4
4
  * Renders a single frame from a spritesheet with transform support.
5
5
  */
6
6
  import React from 'react';
7
- import type { EventKey, AssetUrl } from "@almadar/core";
7
+ import type { EventKey, Asset } from "@almadar/core";
8
8
  import type { AnimationName } from '../organisms/types/spriteAnimation';
9
9
  export interface SpriteProps {
10
- /** Spritesheet image URL */
11
- spritesheet: AssetUrl;
10
+ /** Spritesheet asset */
11
+ spritesheet: Asset;
12
12
  /** Width of each frame in pixels */
13
13
  frameWidth: number;
14
14
  /** Height of each frame in pixels */
@@ -44,23 +44,6 @@ export interface SpriteProps {
44
44
  /** Declarative event name emitted on click via useEventBus */
45
45
  action?: EventKey;
46
46
  }
47
- /**
48
- * Sprite component for rendering spritesheet frames
49
- *
50
- * @example
51
- * ```tsx
52
- * <Sprite
53
- * spritesheet="/sprites/player.png"
54
- * frameWidth={32}
55
- * frameHeight={32}
56
- * frame={currentFrame}
57
- * x={player.x}
58
- * y={player.y}
59
- * flipX={player.facingLeft}
60
- * scale={2}
61
- * />
62
- * ```
63
- */
64
47
  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;
65
48
  /**
66
49
  * Canvas-based sprite renderer for better performance in game loops
@@ -1,13 +1,13 @@
1
1
  import React from 'react';
2
2
  import { type IconInput } from '../../core/atoms/Icon';
3
- import type { AssetUrl } from '@almadar/core';
3
+ import type { Asset } from '@almadar/core';
4
4
  export interface StateStyle {
5
5
  icon: IconInput;
6
6
  bgClass: string;
7
7
  }
8
8
  export interface StateIndicatorProps {
9
9
  /** Sprite image URL — takes precedence over the state icon when provided */
10
- assetUrl?: AssetUrl;
10
+ assetUrl?: Asset;
11
11
  /** The current state name */
12
12
  state: string;
13
13
  /** Optional label override (defaults to capitalized state name) */
@@ -1,8 +1,8 @@
1
1
  import { type IconInput } from '../../core/atoms/Icon';
2
- import type { AssetUrl } from '@almadar/core';
2
+ import type { Asset } from '@almadar/core';
3
3
  export interface StatusEffectProps {
4
- /** Sprite image URL — takes precedence over icon when provided */
5
- assetUrl?: AssetUrl;
4
+ /** Sprite asset — takes precedence over icon when provided */
5
+ assetUrl?: Asset;
6
6
  /** Lucide icon name or component */
7
7
  icon: IconInput;
8
8
  /** Label describing the effect */
@@ -1,7 +1,7 @@
1
- import type { AssetUrl } from '@almadar/core';
1
+ import type { Asset } from '@almadar/core';
2
2
  export interface TurnIndicatorProps {
3
- /** Sprite image URL — takes precedence over the dot indicator when provided */
4
- assetUrl?: AssetUrl;
3
+ /** Sprite asset — takes precedence over the dot indicator when provided */
4
+ assetUrl?: Asset;
5
5
  /** Current turn number */
6
6
  currentTurn: number;
7
7
  /** Maximum number of turns */
@@ -1,8 +1,8 @@
1
1
  import { type IconInput } from '../../core/atoms/Icon';
2
- import type { AssetUrl } from '@almadar/core';
2
+ import type { Asset } from '@almadar/core';
3
3
  export interface WaypointMarkerProps {
4
- /** Sprite image URL — takes precedence over icon when provided */
5
- assetUrl?: AssetUrl;
4
+ /** Sprite asset — takes precedence over icon when provided */
5
+ assetUrl?: Asset;
6
6
  /** Label text below the marker */
7
7
  label?: string;
8
8
  /** Custom icon to render inside the marker */
@@ -1,9 +1,9 @@
1
1
  import * as React from 'react';
2
- import type { AssetUrl, EventEmit } from '@almadar/core';
2
+ import type { Asset, EventEmit } from '@almadar/core';
3
3
  /** One playable card (icon + title + a few stat numbers). UI value DTO. */
4
4
  export interface CardHandCard {
5
5
  id: string;
6
- iconUrl?: AssetUrl;
6
+ iconUrl?: Asset;
7
7
  title?: string;
8
8
  cost?: number;
9
9
  attack?: number;
@@ -12,7 +12,7 @@
12
12
  * concern analogous to Form's `formData`.
13
13
  */
14
14
  import React from 'react';
15
- import type { AssetUrl, EventEmit, EventPayload } from '@almadar/core';
15
+ import type { Asset, EventEmit, EventPayload } from '@almadar/core';
16
16
  export type DialogueChoice = EventPayload & {
17
17
  text: string;
18
18
  action?: string;
@@ -54,9 +54,9 @@ export interface DialogueBoxProps {
54
54
  /** Declarative event: emits UI:{advanceEvent} when dialogue is advanced */
55
55
  advanceEvent?: EventEmit<Record<string, never>>;
56
56
  /** Optional full-frame scene image rendered behind the dialogue (visual-novel mode). */
57
- backgroundImage?: AssetUrl;
57
+ backgroundImage?: Asset;
58
58
  /** Optional larger character portrait rendered standing over the scene (visual-novel mode). */
59
- portraitUrl?: AssetUrl;
59
+ portraitUrl?: Asset;
60
60
  /** Multiplier for the large `portraitUrl` height relative to its default (default 1). */
61
61
  portraitScale?: number;
62
62
  /** Optional className */
@@ -1,5 +1,5 @@
1
1
  import type { IconInput } from '../../core/atoms';
2
- import type { AssetUrl } from '@almadar/core';
2
+ import type { Asset } from '@almadar/core';
3
3
  export interface EnemyPlateEffect {
4
4
  /** Effect icon */
5
5
  icon: IconInput;
@@ -9,8 +9,8 @@ export interface EnemyPlateEffect {
9
9
  variant?: 'buff' | 'debuff' | 'neutral';
10
10
  }
11
11
  export interface EnemyPlateProps {
12
- /** Portrait sprite URL — takes precedence over the default avatar slot */
13
- assetUrl?: AssetUrl;
12
+ /** Portrait sprite asset — takes precedence over the default avatar slot */
13
+ assetUrl?: Asset;
14
14
  /** Enemy name */
15
15
  name: string;
16
16
  /** Current health */
@@ -1,4 +1,4 @@
1
- import type { AssetUrl } from '@almadar/core';
1
+ import type { Asset, AssetUrl } from '@almadar/core';
2
2
  export interface GameCanvas2DProps {
3
3
  /** Canvas width in pixels */
4
4
  width?: number;
@@ -14,8 +14,8 @@ export interface GameCanvas2DProps {
14
14
  drawEvent?: string;
15
15
  /** Target frames per second */
16
16
  fps?: number;
17
- /** Background image URL */
18
- backgroundImage?: AssetUrl;
17
+ /** Background image asset */
18
+ backgroundImage?: Asset;
19
19
  /** Base URL prefix for asset URLs */
20
20
  assetBaseUrl?: AssetUrl;
21
21
  /** Additional CSS classes */
@@ -1,9 +1,9 @@
1
1
  import type { IconInput } from '../../core/atoms';
2
- import type { AssetUrl } from '@almadar/core';
2
+ import type { Asset } from '@almadar/core';
3
3
  export interface HealthEffect {
4
4
  icon: IconInput;
5
- /** Sprite image URL — takes precedence over icon when provided */
6
- assetUrl?: AssetUrl;
5
+ /** Sprite asset — takes precedence over icon when provided */
6
+ assetUrl?: Asset;
7
7
  label?: string;
8
8
  variant?: 'buff' | 'debuff' | 'neutral';
9
9
  }
@@ -24,7 +24,7 @@
24
24
  * @packageDocumentation
25
25
  */
26
26
  import * as React from 'react';
27
- import type { AssetUrl, EventEmit } from '@almadar/core';
27
+ import type { Asset, AssetUrl, EventEmit } from '@almadar/core';
28
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';
@@ -121,20 +121,16 @@ export interface IsometricCanvasProps {
121
121
  onDrawEffects?: (ctx: CanvasRenderingContext2D, animTime: number, getImage: (url: string) => HTMLImageElement | undefined) => void;
122
122
  /** Whether there are active effects — keeps RAF loop alive */
123
123
  hasActiveEffects?: boolean;
124
- /** Base URL for remote asset resolution. When set, manifest paths
125
- * are prefixed with this URL. Example: "https://trait-wars-assets.web.app" */
126
- assetBaseUrl?: AssetUrl;
127
- /** Manifest mapping entity keys to relative sprite paths.
128
- * Combined with assetBaseUrl to produce full URLs.
129
- * Used as a fallback when inline URLs and callbacks don't resolve. */
124
+ /** Manifest mapping entity keys to resolved Asset objects.
125
+ * Used as a fallback when inline assets and callbacks don't resolve. */
130
126
  assetManifest?: {
131
- terrains?: Record<string, string>;
132
- units?: Record<string, string>;
133
- features?: Record<string, string>;
134
- effects?: Record<string, string>;
127
+ terrains?: Record<string, Asset>;
128
+ units?: Record<string, Asset>;
129
+ features?: Record<string, Asset>;
130
+ effects?: Record<string, Asset>;
135
131
  };
136
132
  }
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;
133
+ 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, assetManifest, }: IsometricCanvasProps): React.JSX.Element;
138
134
  export declare namespace IsometricCanvas {
139
135
  var displayName: string;
140
136
  }
@@ -1,10 +1,10 @@
1
1
  import type { IconInput } from '../../core/atoms';
2
- import type { AssetUrl } from '@almadar/core';
2
+ import type { Asset } from '@almadar/core';
3
3
  export interface ActivePowerup {
4
4
  /** Unique powerup ID */
5
5
  id: string;
6
- /** Sprite image URL — takes precedence over icon when provided */
7
- assetUrl?: AssetUrl;
6
+ /** Sprite asset — takes precedence over icon when provided */
7
+ assetUrl?: Asset;
8
8
  /** Icon component or emoji */
9
9
  icon?: IconInput;
10
10
  /** Powerup label */
@@ -1,5 +1,5 @@
1
1
  import { type IconInput } from '../../core/atoms/Icon';
2
- import type { AssetUrl } from '@almadar/core';
2
+ import type { Asset } from '@almadar/core';
3
3
  /**
4
4
  * StatBadge — game stat display molecule (distinct from core Badge atom).
5
5
  * Badge shows a text label/status token. StatBadge shows a numeric value
@@ -7,10 +7,10 @@ import type { AssetUrl } from '@almadar/core';
7
7
  * a named label — purpose-built for HUD stat rows, not general status tags.
8
8
  */
9
9
  export interface StatBadgeProps {
10
- /** Sprite image URL — takes precedence over icon when provided */
11
- assetUrl?: AssetUrl;
12
- /** Asset-image icon URL (board ui/ PNG); takes precedence over the Lucide icon. */
13
- iconUrl?: AssetUrl;
10
+ /** Sprite asset — takes precedence over icon when provided */
11
+ assetUrl?: Asset;
12
+ /** Asset image (board ui/ PNG); takes precedence over the Lucide icon. */
13
+ iconUrl?: Asset;
14
14
  /** Stat label */
15
15
  label: string;
16
16
  /** Current value (defaults to 0 if not provided) */
@@ -1,10 +1,10 @@
1
1
  import type { IconInput } from '../../core/atoms';
2
- import type { AssetUrl } from '@almadar/core';
2
+ import type { Asset } from '@almadar/core';
3
3
  export interface TurnPanelAction {
4
4
  /** Action button label */
5
5
  label: string;
6
- /** Sprite image URL — takes precedence over icon when provided */
7
- assetUrl?: AssetUrl;
6
+ /** Sprite asset — takes precedence over icon when provided */
7
+ assetUrl?: Asset;
8
8
  /** Icon for the button */
9
9
  icon?: IconInput;
10
10
  /** Event name to emit when clicked */
@@ -1779,7 +1779,7 @@ function TileRenderer({
1779
1779
  children: /* @__PURE__ */ jsxRuntime.jsx(
1780
1780
  ModelLoader,
1781
1781
  {
1782
- url: tile.modelUrl,
1782
+ url: tile.modelUrl?.url,
1783
1783
  position,
1784
1784
  scale: cellSize * 0.9,
1785
1785
  fallbackGeometry: "box",
@@ -2181,7 +2181,7 @@ function FeatureModel({
2181
2181
  onHover
2182
2182
  }) {
2183
2183
  const groupRef = React11.useRef(null);
2184
- const { model: loadedModel, isLoading } = useGLTFModel2(feature.assetUrl);
2184
+ const { model: loadedModel, isLoading } = useGLTFModel2(feature.assetUrl?.url);
2185
2185
  const model = React11.useMemo(() => {
2186
2186
  if (!loadedModel) return null;
2187
2187
  const cloned = loadedModel.clone();
@@ -2316,12 +2316,7 @@ function frameRect(frame, row, columns, frameWidth, frameHeight) {
2316
2316
 
2317
2317
  // components/game/molecules/useUnitSpriteAtlas.ts
2318
2318
  function unitAtlasUrl(unit) {
2319
- if (unit.spriteSheet) return unit.spriteSheet;
2320
- const sprite = unit.sprite;
2321
- if (!sprite) return null;
2322
- const match = /^(.*-sprite-sheet)(?:-(?:se|sw))?(?:-v\d+)?\.png$/.exec(sprite);
2323
- if (!match) return null;
2324
- return `${match[1]}.json`;
2319
+ return unit.spriteSheet?.url ?? null;
2325
2320
  }
2326
2321
  function resolveSheetUrl(atlasUrl, relativeSheetPath) {
2327
2322
  try {
@@ -2732,7 +2727,7 @@ var GameCanvas3D = React11.forwardRef(
2732
2727
  children: /* @__PURE__ */ jsxRuntime.jsx(
2733
2728
  ModelLoader,
2734
2729
  {
2735
- url: tile.modelUrl,
2730
+ url: tile.modelUrl.url,
2736
2731
  scale: 0.95,
2737
2732
  fallbackGeometry: "box",
2738
2733
  castShadow: true,
@@ -2797,7 +2792,7 @@ var GameCanvas3D = React11.forwardRef(
2797
2792
  /* @__PURE__ */ jsxRuntime.jsx(
2798
2793
  ModelLoader,
2799
2794
  {
2800
- url: unit.modelUrl,
2795
+ url: unit.modelUrl.url,
2801
2796
  scale: modelScale,
2802
2797
  fallbackGeometry: "box",
2803
2798
  castShadow: true
@@ -2857,7 +2852,7 @@ var GameCanvas3D = React11.forwardRef(
2857
2852
  return /* @__PURE__ */ jsxRuntime.jsx(
2858
2853
  ModelLoader,
2859
2854
  {
2860
- url: feature.assetUrl,
2855
+ url: feature.assetUrl.url,
2861
2856
  position,
2862
2857
  scale: 0.5,
2863
2858
  rotation: [0, feature.rotation ?? 0, 0],
@@ -4225,7 +4220,18 @@ function GameBoard3D({
4225
4220
  const row = boardEntity(entity);
4226
4221
  const eventBus = useEventBus();
4227
4222
  const entityUnits = row ? rows(row.units) : [];
4228
- const units = entityUnits;
4223
+ const units = entityUnits.map((r) => ({
4224
+ id: str(r.id),
4225
+ x: num(r.x),
4226
+ y: num(r.y),
4227
+ z: num(r.z),
4228
+ faction: str(r.faction) || "neutral",
4229
+ team: str(r.team) || str(r.faction) || "neutral",
4230
+ unitType: r.unitType == null ? void 0 : str(r.unitType),
4231
+ name: r.name == null ? void 0 : str(r.name),
4232
+ health: r.health == null ? void 0 : num(r.health),
4233
+ maxHealth: r.maxHealth == null ? void 0 : num(r.maxHealth)
4234
+ }));
4229
4235
  const selectedUnitId = row ? str(row.selectedUnitId) || null : null;
4230
4236
  const phase = row ? str(row.phase) : "observation";
4231
4237
  const result = row ? str(row.result) : "none";
@@ -4617,16 +4623,31 @@ var Box = React11__default.default.forwardRef(
4617
4623
  );
4618
4624
  Box.displayName = "Box";
4619
4625
 
4620
- // components/game/templates/game3dAssetManifest.ts
4621
- function resolveManifestUrl(manifest, relative) {
4622
- if (relative == null) return void 0;
4623
- if (/^https?:\/\//.test(relative)) return relative;
4624
- const base = manifest?.baseUrl;
4625
- if (base == null) return relative;
4626
- const cleanBase = base.replace(/\/$/, "");
4627
- const cleanRel = relative.replace(/^\//, "");
4628
- return `${cleanBase}/${cleanRel}`;
4626
+ // components/game/organisms/utils/makeAsset.ts
4627
+ var ROLE_DEFAULTS = {
4628
+ tile: { dimension: "2d", animations: ["static"], aspect: "1:1" },
4629
+ decoration: { dimension: "2d", animations: ["static"], aspect: "1:1" },
4630
+ effect: { dimension: "2d", animations: ["static"], aspect: "1:1" },
4631
+ item: { dimension: "2d", animations: ["static"], aspect: "5:7" },
4632
+ player: { dimension: "2d", animations: ["idle", "walk", "attack", "hit", "death"], aspect: "1:1" },
4633
+ enemy: { dimension: "2d", animations: ["idle", "walk", "attack", "hit", "death"], aspect: "1:1" },
4634
+ npc: { dimension: "2d", animations: ["idle", "walk", "attack", "hit", "death"], aspect: "1:1" },
4635
+ projectile: { dimension: "2d", animations: ["static"], aspect: "1:1" },
4636
+ vehicle: { dimension: "2d", animations: ["idle", "walk"], aspect: "1:1" },
4637
+ ui: { dimension: "2d", animations: ["static"], aspect: "1:1" }
4638
+ };
4639
+ function makeAsset(url, role, overrides = {}) {
4640
+ const defaults = ROLE_DEFAULTS[role] ?? { dimension: "2d", animations: ["static"], aspect: "1:1" };
4641
+ return {
4642
+ url,
4643
+ role,
4644
+ category: url.split("/").pop()?.replace(/\.[^.]+$/, "") ?? role,
4645
+ ...defaults,
4646
+ ...overrides
4647
+ };
4629
4648
  }
4649
+
4650
+ // components/game/templates/game3dAssetManifest.ts
4630
4651
  function tileKind(type, passable) {
4631
4652
  if (passable === false) return "wall";
4632
4653
  if (type === "dirt" || type === "road") return "dirt";
@@ -4642,7 +4663,7 @@ function resolveTilesWithModels(layout, manifest) {
4642
4663
  z: t.z,
4643
4664
  type: t.type,
4644
4665
  passable: t.passable,
4645
- modelUrl: resolveManifestUrl(manifest, manifest?.models?.[kind])
4666
+ modelUrl: manifest?.models?.[kind]
4646
4667
  };
4647
4668
  });
4648
4669
  }
@@ -4651,7 +4672,7 @@ function resolvePropTiles(tiles, manifest) {
4651
4672
  const kind = tileKind(t.type ?? "", t.passable);
4652
4673
  return {
4653
4674
  ...t,
4654
- modelUrl: t.modelUrl ?? resolveManifestUrl(manifest, manifest?.models?.[kind])
4675
+ modelUrl: t.modelUrl ?? manifest?.models?.[kind]
4655
4676
  };
4656
4677
  });
4657
4678
  }
@@ -4662,7 +4683,8 @@ function resolveEntityTiles(entity, manifest) {
4662
4683
  const type = str(r.type) || str(r.terrain);
4663
4684
  const passable = r.passable !== false;
4664
4685
  const kind = tileKind(type, passable);
4665
- const explicitModel = r.modelUrl == null ? void 0 : str(r.modelUrl);
4686
+ const explicitModelUrl = r.modelUrl == null ? void 0 : str(r.modelUrl);
4687
+ const explicitModel = explicitModelUrl ? makeAsset(explicitModelUrl, "decoration", { dimension: "3d" }) : void 0;
4666
4688
  return {
4667
4689
  id: r.id == null ? void 0 : str(r.id),
4668
4690
  x: num(r.x),
@@ -4670,14 +4692,14 @@ function resolveEntityTiles(entity, manifest) {
4670
4692
  z: r.z == null ? void 0 : num(r.z),
4671
4693
  type,
4672
4694
  passable,
4673
- modelUrl: explicitModel ?? resolveManifestUrl(manifest, manifest?.models?.[kind])
4695
+ modelUrl: explicitModel ?? manifest?.models?.[kind]
4674
4696
  };
4675
4697
  });
4676
4698
  }
4677
4699
  function resolveFeaturesWithModels(features, manifest) {
4678
4700
  return features.map((f) => ({
4679
4701
  ...f,
4680
- assetUrl: f.assetUrl ?? resolveManifestUrl(manifest, manifest?.features?.[f.type])
4702
+ assetUrl: f.assetUrl ?? manifest?.features?.[f.type]
4681
4703
  }));
4682
4704
  }
4683
4705
  function resolveEntityFeatures(entity, manifest) {
@@ -4685,7 +4707,8 @@ function resolveEntityFeatures(entity, manifest) {
4685
4707
  if (!row) return [];
4686
4708
  return rows(row.features).map((r) => {
4687
4709
  const type = str(r.type);
4688
- const explicit = r.assetUrl == null ? void 0 : str(r.assetUrl);
4710
+ const explicitUrl = r.assetUrl == null ? void 0 : str(r.assetUrl);
4711
+ const explicit = explicitUrl ? makeAsset(explicitUrl, "decoration", { dimension: "3d" }) : void 0;
4689
4712
  return {
4690
4713
  id: r.id == null ? void 0 : str(r.id),
4691
4714
  x: num(r.x),
@@ -4693,7 +4716,7 @@ function resolveEntityFeatures(entity, manifest) {
4693
4716
  z: r.z == null ? void 0 : num(r.z),
4694
4717
  type,
4695
4718
  color: r.color == null ? void 0 : str(r.color),
4696
- assetUrl: explicit ?? resolveManifestUrl(manifest, manifest?.features?.[type])
4719
+ assetUrl: explicit ?? manifest?.features?.[type]
4697
4720
  };
4698
4721
  });
4699
4722
  }