@almadar/ui 5.60.0 → 5.62.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 (41) hide show
  1. package/dist/avl/index.cjs +3233 -1757
  2. package/dist/avl/index.js +2057 -581
  3. package/dist/components/core/molecules/GraphCanvas.d.ts +6 -0
  4. package/dist/components/core/templates/index.d.ts +4 -0
  5. package/dist/components/game/molecules/CardHand.d.ts +35 -0
  6. package/dist/components/game/molecules/DialogueBox.d.ts +8 -2
  7. package/dist/components/game/molecules/index.d.ts +1 -0
  8. package/dist/components/game/molecules/three/index.cjs +229 -120
  9. package/dist/components/game/molecules/three/index.js +229 -120
  10. package/dist/components/game/organisms/CardBattlerBoard.d.ts +46 -0
  11. package/dist/components/game/organisms/CityBuilderBoard.d.ts +63 -0
  12. package/dist/components/game/organisms/TopDownShooterBoard.d.ts +64 -0
  13. package/dist/components/game/organisms/TowerDefenseBoard.d.ts +10 -1
  14. package/dist/components/game/organisms/TraitSlot.d.ts +1 -3
  15. package/dist/components/game/organisms/VisualNovelBoard.d.ts +53 -0
  16. package/dist/components/game/organisms/index.d.ts +4 -0
  17. package/dist/components/game/templates/CardBattlerTemplate.d.ts +35 -0
  18. package/dist/components/game/templates/CityBuilderTemplate.d.ts +40 -0
  19. package/dist/components/game/templates/GameCanvas3DBattleTemplate.d.ts +5 -2
  20. package/dist/components/game/templates/GameCanvas3DCastleTemplate.d.ts +5 -2
  21. package/dist/components/game/templates/GameCanvas3DWorldMapTemplate.d.ts +5 -2
  22. package/dist/components/game/templates/TopDownShooterTemplate.d.ts +42 -0
  23. package/dist/components/game/templates/VisualNovelTemplate.d.ts +28 -0
  24. package/dist/components/game/templates/game3dAssetManifest.d.ts +50 -0
  25. package/dist/components/game/templates/index.d.ts +4 -0
  26. package/dist/components/index.cjs +3048 -1547
  27. package/dist/components/index.js +2019 -527
  28. package/dist/docs/index.cjs +105 -1
  29. package/dist/docs/index.js +105 -1
  30. package/dist/hooks/index.cjs +105 -1
  31. package/dist/hooks/index.js +105 -1
  32. package/dist/locales/index.cjs +315 -3
  33. package/dist/locales/index.js +315 -3
  34. package/dist/providers/index.cjs +3034 -1558
  35. package/dist/providers/index.js +2025 -549
  36. package/dist/runtime/index.cjs +3085 -1609
  37. package/dist/runtime/index.js +2024 -548
  38. package/locales/ar.json +105 -1
  39. package/locales/en.json +105 -1
  40. package/locales/sl.json +105 -1
  41. package/package.json +1 -1
@@ -55,6 +55,8 @@ export interface GraphCanvasProps {
55
55
  actions?: readonly GraphAction[];
56
56
  /** On node click */
57
57
  onNodeClick?: (node: GraphNode) => void;
58
+ /** On node double-click (e.g. to drill in) */
59
+ onNodeDoubleClick?: (node: GraphNode) => void;
58
60
  /** Node click event */
59
61
  nodeClickEvent?: EventEmit<{
60
62
  id: string;
@@ -65,6 +67,10 @@ export interface GraphCanvasProps {
65
67
  repulsion?: number;
66
68
  /** Force-sim target edge length (larger ⇒ more spread out) */
67
69
  linkDistance?: number;
70
+ /** Minimum empty gap (px) enforced between node edges so nodes never overlap. */
71
+ nodeSpacing?: number;
72
+ /** Base opacity for links when nothing is hovered (kept faint so dense graphs stay readable; incident links brighten on hover). */
73
+ linkOpacity?: number;
68
74
  /** Layout algorithm */
69
75
  layout?: "force" | "circular" | "grid";
70
76
  /** Entity name for schema-driven auto-fetch */
@@ -11,6 +11,10 @@ export { WorldMapTemplate, type WorldMapTemplateProps, type WorldMapSlotContext,
11
11
  export { PlatformerTemplate, type PlatformerTemplateProps, } from '../../game/templates/PlatformerTemplate';
12
12
  export { TowerDefenseTemplate, type TowerDefenseTemplateProps, } from '../../game/templates/TowerDefenseTemplate';
13
13
  export { RoguelikeTemplate, type RoguelikeTemplateProps, } from '../../game/templates/RoguelikeTemplate';
14
+ export { TopDownShooterTemplate, type TopDownShooterTemplateProps, } from '../../game/templates/TopDownShooterTemplate';
15
+ export { CityBuilderTemplate, type CityBuilderTemplateProps, } from '../../game/templates/CityBuilderTemplate';
16
+ export { VisualNovelTemplate, type VisualNovelTemplateProps, } from '../../game/templates/VisualNovelTemplate';
17
+ export { CardBattlerTemplate, type CardBattlerTemplateProps, } from '../../game/templates/CardBattlerTemplate';
14
18
  export { LandingPageTemplate, type LandingPageTemplateProps, type LandingPageEntity, } from '../../marketing/templates/LandingPageTemplate';
15
19
  export { PricingPageTemplate, type PricingPageTemplateProps, type PricingPageEntity, } from '../../marketing/templates/PricingPageTemplate';
16
20
  export { FeatureDetailPageTemplate, type FeatureDetailPageTemplateProps, type FeatureDetailPageEntity, type FeatureDetailSection, } from '../../marketing/templates/FeatureDetailPageTemplate';
@@ -0,0 +1,35 @@
1
+ import * as React from 'react';
2
+ import type { AssetUrl, EventEmit } from '@almadar/core';
3
+ /** One playable card (icon + title + a few stat numbers). UI value DTO. */
4
+ export interface CardHandCard {
5
+ id: string;
6
+ iconUrl?: AssetUrl;
7
+ title?: string;
8
+ cost?: number;
9
+ attack?: number;
10
+ defense?: number;
11
+ disabled?: boolean;
12
+ }
13
+ export interface CardHandProps {
14
+ /** Cards to lay out as a horizontal hand (or grid when wrapped). */
15
+ cards: CardHandCard[];
16
+ /** Currently selected card id. */
17
+ selectedId?: string;
18
+ /** Card-art size variant. */
19
+ size?: 'sm' | 'md' | 'lg';
20
+ /** Direct callback when a (non-disabled) card is clicked. */
21
+ onCardClick?: (id: string) => void;
22
+ /** Event-bus event emitted with `{ cardId }` on click. */
23
+ cardClickEvent?: EventEmit<{
24
+ cardId: string;
25
+ }>;
26
+ /** Empty-state label when there are no cards. */
27
+ emptyLabel?: string;
28
+ className?: string;
29
+ }
30
+ /** Pure card-hand molecule: renders a row/grid of cards from props alone. */
31
+ export declare function CardHand({ cards, selectedId, size, onCardClick, cardClickEvent, emptyLabel, className, }: CardHandProps): React.JSX.Element;
32
+ export declare namespace CardHand {
33
+ var displayName: string;
34
+ }
35
+ export default CardHand;
@@ -12,7 +12,7 @@
12
12
  * concern analogous to Form's `formData`.
13
13
  */
14
14
  import React from 'react';
15
- import type { EventEmit, EventPayload } from '@almadar/core';
15
+ import type { AssetUrl, EventEmit, EventPayload } from '@almadar/core';
16
16
  export type DialogueChoice = EventPayload & {
17
17
  text: string;
18
18
  action?: string;
@@ -53,8 +53,14 @@ export interface DialogueBoxProps {
53
53
  }>;
54
54
  /** Declarative event: emits UI:{advanceEvent} when dialogue is advanced */
55
55
  advanceEvent?: EventEmit<Record<string, never>>;
56
+ /** Optional full-frame scene image rendered behind the dialogue (visual-novel mode). */
57
+ backgroundImage?: AssetUrl;
58
+ /** Optional larger character portrait rendered standing over the scene (visual-novel mode). */
59
+ portraitUrl?: AssetUrl;
60
+ /** Multiplier for the large `portraitUrl` height relative to its default (default 1). */
61
+ portraitScale?: number;
56
62
  /** Optional className */
57
63
  className?: string;
58
64
  }
59
- export declare function DialogueBox({ dialogue, typewriterSpeed, position, onComplete, onChoice, onAdvance, completeEvent, choiceEvent, advanceEvent, className, }: DialogueBoxProps): React.JSX.Element;
65
+ export declare function DialogueBox({ dialogue, typewriterSpeed, position, onComplete, onChoice, onAdvance, completeEvent, choiceEvent, advanceEvent, backgroundImage, portraitUrl, portraitScale, className, }: DialogueBoxProps): React.JSX.Element;
60
66
  export default DialogueBox;
@@ -9,6 +9,7 @@ export { DPad, type DPadProps, type DPadDirection } from './DPad';
9
9
  export { ActionButtons, type ActionButtonsProps, type ActionButtonConfig } from './ActionButtons';
10
10
  export { StatBadge, type StatBadgeProps } from './StatBadge';
11
11
  export { InventoryGrid, type InventoryGridProps, type InventoryGridItem } from './InventoryGrid';
12
+ export { CardHand, type CardHandProps, type CardHandCard } from './CardHand';
12
13
  export { QuestTracker, type QuestTrackerProps, type Quest } from './QuestTracker';
13
14
  export { CraftingRecipe, type CraftingRecipeProps, type CraftingIngredient } from './CraftingRecipe';
14
15
  export { PowerupSlots, type PowerupSlotsProps, type ActivePowerup } from './PowerupSlots';
@@ -401,12 +401,18 @@ function ModelLoader({
401
401
  });
402
402
  return cloned;
403
403
  }, [loadedModel, castShadow, receiveShadow]);
404
+ const normFactor = React11.useMemo(() => {
405
+ if (!model) return 1;
406
+ const box = new THREE6__namespace.Box3().setFromObject(model);
407
+ const size = new THREE6__namespace.Vector3();
408
+ box.getSize(size);
409
+ const maxDim = Math.max(size.x, size.y, size.z);
410
+ return maxDim > 0 && Number.isFinite(maxDim) ? 1 / maxDim : 1;
411
+ }, [model]);
404
412
  const scaleArray = React11.useMemo(() => {
405
- if (typeof scale === "number") {
406
- return [scale, scale, scale];
407
- }
408
- return scale;
409
- }, [scale]);
413
+ const base = typeof scale === "number" ? [scale, scale, scale] : scale;
414
+ return [base[0] * normFactor, base[1] * normFactor, base[2] * normFactor];
415
+ }, [scale, normFactor]);
410
416
  const rotationRad = React11.useMemo(() => {
411
417
  return [
412
418
  rotation[0] * Math.PI / 180,
@@ -2590,6 +2596,29 @@ var GameCanvas3D = React11.forwardRef(
2590
2596
  const controlsRef = React11.useRef(null);
2591
2597
  const [hoveredTile, setHoveredTile] = React11.useState(null);
2592
2598
  const [internalError, setInternalError] = React11.useState(null);
2599
+ React11.useEffect(() => {
2600
+ const el = containerRef.current;
2601
+ if (!el) return;
2602
+ let node = el;
2603
+ let depth = 0;
2604
+ while (node && depth < 8) {
2605
+ const cs = window.getComputedStyle(node);
2606
+ const rect = node.getBoundingClientRect();
2607
+ console.log("[almadar:ui:game:3d-height]", {
2608
+ depth,
2609
+ tag: node.tagName,
2610
+ id: node.id || void 0,
2611
+ className: node.className?.slice?.(0, 60) || void 0,
2612
+ rectHeight: rect.height,
2613
+ computedHeight: cs.height,
2614
+ computedMinHeight: cs.minHeight,
2615
+ computedFlex: cs.flex,
2616
+ computedOverflow: cs.overflow
2617
+ });
2618
+ node = node.parentElement;
2619
+ depth++;
2620
+ }
2621
+ }, []);
2593
2622
  const { sheetUrls: atlasSheetUrls, resolveUnitFrame } = useUnitSpriteAtlas(units);
2594
2623
  const preloadUrls = React11.useMemo(() => [...preloadAssets, ...atlasSheetUrls], [preloadAssets, atlasSheetUrls]);
2595
2624
  const { isLoading: assetsLoading, progress, loaded, total } = useAssetLoader({
@@ -2718,24 +2747,28 @@ var GameCanvas3D = React11.forwardRef(
2718
2747
  const cameraConfig = React11.useMemo(() => {
2719
2748
  const size = Math.max(
2720
2749
  gridBounds.maxX - gridBounds.minX,
2721
- gridBounds.maxZ - gridBounds.minZ
2750
+ gridBounds.maxZ - gridBounds.minZ,
2751
+ 4
2752
+ // minimum framing distance so a tiny board isn't zoomed in
2722
2753
  );
2723
- const distance = size * 1.5;
2754
+ const cx = (gridBounds.minX + gridBounds.maxX) / 2;
2755
+ const cz = (gridBounds.minZ + gridBounds.maxZ) / 2;
2756
+ const d = size * 1.5;
2724
2757
  switch (cameraMode) {
2725
2758
  case "isometric":
2726
2759
  return {
2727
- position: [distance, distance * 0.8, distance],
2760
+ position: [cx + d, d * 0.8, cz + d],
2728
2761
  fov: 45
2729
2762
  };
2730
2763
  case "top-down":
2731
2764
  return {
2732
- position: [0, distance * 2, 0],
2765
+ position: [cx, d * 2, cz],
2733
2766
  fov: 45
2734
2767
  };
2735
2768
  case "perspective":
2736
2769
  default:
2737
2770
  return {
2738
- position: [distance, distance, distance],
2771
+ position: [cx + d, d, cz + d],
2739
2772
  fov: 45
2740
2773
  };
2741
2774
  }
@@ -2974,7 +3007,8 @@ var GameCanvas3D = React11.forwardRef(
2974
3007
  "div",
2975
3008
  {
2976
3009
  ref: containerRef,
2977
- className: cn("game-canvas-3d relative w-full h-full min-h-[85vh] overflow-hidden", className),
3010
+ className: cn("game-canvas-3d relative w-full overflow-hidden", className),
3011
+ style: { height: "85vh" },
2978
3012
  "data-orientation": orientation,
2979
3013
  "data-camera-mode": cameraMode,
2980
3014
  "data-overlay": overlay,
@@ -2989,7 +3023,7 @@ var GameCanvas3D = React11.forwardRef(
2989
3023
  near: 0.1,
2990
3024
  far: 1e3
2991
3025
  },
2992
- style: { background: backgroundColor },
3026
+ style: { background: backgroundColor, position: "absolute", inset: 0 },
2993
3027
  onClick: (e) => {
2994
3028
  if (e.target === e.currentTarget) {
2995
3029
  eventHandlers.handleCanvasClick(e);
@@ -4590,36 +4624,113 @@ var Box = React11__default.default.forwardRef(
4590
4624
  }
4591
4625
  );
4592
4626
  Box.displayName = "Box";
4593
- var CDN = "https://almadar-kflow-assets.web.app/shared/3d/dungeon/floor";
4594
- var FLOOR_WALL = `${CDN}/template-floor-detail-a.glb`;
4595
- var FLOOR_DIRT = `${CDN}/template-floor-detail.glb`;
4596
- var FLOOR_OPEN = `${CDN}/template-floor.glb`;
4597
- var DEFAULT_3D_BATTLE_TILES = [
4598
- { id: "t00", x: 0, y: 0, z: 0, type: "stone", passable: false, modelUrl: FLOOR_WALL },
4599
- { id: "t10", x: 1, y: 0, z: 0, type: "stone", passable: false, modelUrl: FLOOR_WALL },
4600
- { id: "t20", x: 2, y: 0, z: 0, type: "stone", passable: false, modelUrl: FLOOR_WALL },
4601
- { id: "t30", x: 3, y: 0, z: 0, type: "stone", passable: false, modelUrl: FLOOR_WALL },
4602
- { id: "t40", x: 4, y: 0, z: 0, type: "stone", passable: false, modelUrl: FLOOR_WALL },
4603
- { id: "t01", x: 0, y: 1, z: 1, type: "stone", passable: false, modelUrl: FLOOR_WALL },
4604
- { id: "t11", x: 1, y: 1, z: 1, type: "dirt", passable: true, modelUrl: FLOOR_DIRT },
4605
- { id: "t21", x: 2, y: 1, z: 1, type: "grass", passable: true, modelUrl: FLOOR_OPEN },
4606
- { id: "t31", x: 3, y: 1, z: 1, type: "grass", passable: true, modelUrl: FLOOR_OPEN },
4607
- { id: "t41", x: 4, y: 1, z: 1, type: "stone", passable: false, modelUrl: FLOOR_WALL },
4608
- { id: "t02", x: 0, y: 2, z: 2, type: "stone", passable: false, modelUrl: FLOOR_WALL },
4609
- { id: "t12", x: 1, y: 2, z: 2, type: "grass", passable: true, modelUrl: FLOOR_OPEN },
4610
- { id: "t22", x: 2, y: 2, z: 2, type: "dirt", passable: true, modelUrl: FLOOR_DIRT },
4611
- { id: "t32", x: 3, y: 2, z: 2, type: "grass", passable: true, modelUrl: FLOOR_OPEN },
4612
- { id: "t42", x: 4, y: 2, z: 2, type: "stone", passable: false, modelUrl: FLOOR_WALL },
4613
- { id: "t03", x: 0, y: 3, z: 3, type: "stone", passable: false, modelUrl: FLOOR_WALL },
4614
- { id: "t13", x: 1, y: 3, z: 3, type: "grass", passable: true, modelUrl: FLOOR_OPEN },
4615
- { id: "t23", x: 2, y: 3, z: 3, type: "grass", passable: true, modelUrl: FLOOR_OPEN },
4616
- { id: "t33", x: 3, y: 3, z: 3, type: "dirt", passable: true, modelUrl: FLOOR_DIRT },
4617
- { id: "t43", x: 4, y: 3, z: 3, type: "stone", passable: false, modelUrl: FLOOR_WALL },
4618
- { id: "t04", x: 0, y: 4, z: 4, type: "stone", passable: false, modelUrl: FLOOR_WALL },
4619
- { id: "t14", x: 1, y: 4, z: 4, type: "stone", passable: false, modelUrl: FLOOR_WALL },
4620
- { id: "t24", x: 2, y: 4, z: 4, type: "stone", passable: false, modelUrl: FLOOR_WALL },
4621
- { id: "t34", x: 3, y: 4, z: 4, type: "stone", passable: false, modelUrl: FLOOR_WALL },
4622
- { id: "t44", x: 4, y: 4, z: 4, type: "stone", passable: false, modelUrl: FLOOR_WALL }
4627
+
4628
+ // components/game/templates/game3dAssetManifest.ts
4629
+ function resolveManifestUrl(manifest, relative) {
4630
+ if (relative == null) return void 0;
4631
+ if (/^https?:\/\//.test(relative)) return relative;
4632
+ const base = manifest?.baseUrl;
4633
+ if (base == null) return relative;
4634
+ const cleanBase = base.replace(/\/$/, "");
4635
+ const cleanRel = relative.replace(/^\//, "");
4636
+ return `${cleanBase}/${cleanRel}`;
4637
+ }
4638
+ function tileKind(type, passable) {
4639
+ if (passable === false) return "wall";
4640
+ if (type === "dirt" || type === "road") return "dirt";
4641
+ return "open";
4642
+ }
4643
+ function resolveTilesWithModels(layout, manifest) {
4644
+ return layout.map((t) => {
4645
+ const kind = t.kind || tileKind(t.type, t.passable);
4646
+ return {
4647
+ id: t.id,
4648
+ x: t.x,
4649
+ y: t.y,
4650
+ z: t.z,
4651
+ type: t.type,
4652
+ passable: t.passable,
4653
+ modelUrl: resolveManifestUrl(manifest, manifest?.models?.[kind])
4654
+ };
4655
+ });
4656
+ }
4657
+ function resolvePropTiles(tiles, manifest) {
4658
+ return tiles.map((t) => {
4659
+ const kind = tileKind(t.type ?? "", t.passable);
4660
+ return {
4661
+ ...t,
4662
+ modelUrl: t.modelUrl ?? resolveManifestUrl(manifest, manifest?.models?.[kind])
4663
+ };
4664
+ });
4665
+ }
4666
+ function resolveEntityTiles(entity, manifest) {
4667
+ const row = boardEntity(entity);
4668
+ if (!row) return [];
4669
+ return rows(row.tiles).map((r) => {
4670
+ const type = str(r.type) || str(r.terrain);
4671
+ const passable = r.passable !== false;
4672
+ const kind = tileKind(type, passable);
4673
+ const explicitModel = r.modelUrl == null ? void 0 : str(r.modelUrl);
4674
+ return {
4675
+ id: r.id == null ? void 0 : str(r.id),
4676
+ x: num(r.x),
4677
+ y: num(r.y),
4678
+ z: r.z == null ? void 0 : num(r.z),
4679
+ type,
4680
+ passable,
4681
+ modelUrl: explicitModel ?? resolveManifestUrl(manifest, manifest?.models?.[kind])
4682
+ };
4683
+ });
4684
+ }
4685
+ function resolveFeaturesWithModels(features, manifest) {
4686
+ return features.map((f) => ({
4687
+ ...f,
4688
+ assetUrl: f.assetUrl ?? resolveManifestUrl(manifest, manifest?.features?.[f.type])
4689
+ }));
4690
+ }
4691
+ function resolveEntityFeatures(entity, manifest) {
4692
+ const row = boardEntity(entity);
4693
+ if (!row) return [];
4694
+ return rows(row.features).map((r) => {
4695
+ const type = str(r.type);
4696
+ const explicit = r.assetUrl == null ? void 0 : str(r.assetUrl);
4697
+ return {
4698
+ id: r.id == null ? void 0 : str(r.id),
4699
+ x: num(r.x),
4700
+ y: num(r.y),
4701
+ z: r.z == null ? void 0 : num(r.z),
4702
+ type,
4703
+ color: r.color == null ? void 0 : str(r.color),
4704
+ assetUrl: explicit ?? resolveManifestUrl(manifest, manifest?.features?.[type])
4705
+ };
4706
+ });
4707
+ }
4708
+ var DEFAULT_3D_BATTLE_LAYOUT = [
4709
+ { id: "t00", x: 0, y: 0, z: 0, type: "stone", passable: false, kind: "wall" },
4710
+ { id: "t10", x: 1, y: 0, z: 0, type: "stone", passable: false, kind: "wall" },
4711
+ { id: "t20", x: 2, y: 0, z: 0, type: "stone", passable: false, kind: "wall" },
4712
+ { id: "t30", x: 3, y: 0, z: 0, type: "stone", passable: false, kind: "wall" },
4713
+ { id: "t40", x: 4, y: 0, z: 0, type: "stone", passable: false, kind: "wall" },
4714
+ { id: "t01", x: 0, y: 1, z: 1, type: "stone", passable: false, kind: "wall" },
4715
+ { id: "t11", x: 1, y: 1, z: 1, type: "dirt", passable: true, kind: "dirt" },
4716
+ { id: "t21", x: 2, y: 1, z: 1, type: "grass", passable: true, kind: "open" },
4717
+ { id: "t31", x: 3, y: 1, z: 1, type: "grass", passable: true, kind: "open" },
4718
+ { id: "t41", x: 4, y: 1, z: 1, type: "stone", passable: false, kind: "wall" },
4719
+ { id: "t02", x: 0, y: 2, z: 2, type: "stone", passable: false, kind: "wall" },
4720
+ { id: "t12", x: 1, y: 2, z: 2, type: "grass", passable: true, kind: "open" },
4721
+ { id: "t22", x: 2, y: 2, z: 2, type: "dirt", passable: true, kind: "dirt" },
4722
+ { id: "t32", x: 3, y: 2, z: 2, type: "grass", passable: true, kind: "open" },
4723
+ { id: "t42", x: 4, y: 2, z: 2, type: "stone", passable: false, kind: "wall" },
4724
+ { id: "t03", x: 0, y: 3, z: 3, type: "stone", passable: false, kind: "wall" },
4725
+ { id: "t13", x: 1, y: 3, z: 3, type: "grass", passable: true, kind: "open" },
4726
+ { id: "t23", x: 2, y: 3, z: 3, type: "grass", passable: true, kind: "open" },
4727
+ { id: "t33", x: 3, y: 3, z: 3, type: "dirt", passable: true, kind: "dirt" },
4728
+ { id: "t43", x: 4, y: 3, z: 3, type: "stone", passable: false, kind: "wall" },
4729
+ { id: "t04", x: 0, y: 4, z: 4, type: "stone", passable: false, kind: "wall" },
4730
+ { id: "t14", x: 1, y: 4, z: 4, type: "stone", passable: false, kind: "wall" },
4731
+ { id: "t24", x: 2, y: 4, z: 4, type: "stone", passable: false, kind: "wall" },
4732
+ { id: "t34", x: 3, y: 4, z: 4, type: "stone", passable: false, kind: "wall" },
4733
+ { id: "t44", x: 4, y: 4, z: 4, type: "stone", passable: false, kind: "wall" }
4623
4734
  ];
4624
4735
  var DEFAULT_3D_BATTLE_FEATURES = [
4625
4736
  { id: "f1", x: 2, y: 2, z: 2, type: "gold_mine", color: "#f4c542" },
@@ -4627,8 +4738,9 @@ var DEFAULT_3D_BATTLE_FEATURES = [
4627
4738
  ];
4628
4739
  function GameCanvas3DBattleTemplate({
4629
4740
  entity,
4630
- tiles: propTiles = DEFAULT_3D_BATTLE_TILES,
4741
+ tiles: propTiles,
4631
4742
  features: propFeatures = DEFAULT_3D_BATTLE_FEATURES,
4743
+ assetManifest,
4632
4744
  cameraMode = "perspective",
4633
4745
  backgroundColor = "#2a1a1a",
4634
4746
  tileClickEvent,
@@ -4642,11 +4754,12 @@ function GameCanvas3DBattleTemplate({
4642
4754
  scale,
4643
4755
  className
4644
4756
  }) {
4645
- const resolved = entity && typeof entity === "object" && !Array.isArray(entity) ? entity : void 0;
4646
- const tiles = resolved ? Array.isArray(resolved.tiles) ? resolved.tiles : [] : propTiles;
4647
- const features = resolved ? Array.isArray(resolved.features) ? resolved.features : [] : propFeatures;
4648
- const currentTurn = resolved?.currentTeam;
4649
- const round = resolved?.turn == null ? void 0 : Number(resolved.turn);
4757
+ const row = boardEntity(entity);
4758
+ const hasEntityTiles = !!row && Array.isArray(row.tiles) && row.tiles.length > 0;
4759
+ const tiles = hasEntityTiles ? resolveEntityTiles(entity, assetManifest) : propTiles ? resolvePropTiles(propTiles, assetManifest) : resolveTilesWithModels(DEFAULT_3D_BATTLE_LAYOUT, assetManifest);
4760
+ const features = row && Array.isArray(row.features) && row.features.length > 0 ? resolveEntityFeatures(entity, assetManifest) : resolveFeaturesWithModels(propFeatures, assetManifest);
4761
+ const currentTurn = row?.currentTeam == null ? void 0 : String(row.currentTeam);
4762
+ const round = row?.turn == null ? void 0 : Number(row.turn);
4650
4763
  return /* @__PURE__ */ jsxRuntime.jsxs(
4651
4764
  Box,
4652
4765
  {
@@ -4692,36 +4805,32 @@ function GameCanvas3DBattleTemplate({
4692
4805
  );
4693
4806
  }
4694
4807
  GameCanvas3DBattleTemplate.displayName = "GameCanvas3DBattleTemplate";
4695
- var CDN2 = "https://almadar-kflow-assets.web.app/shared/3d/dungeon/floor";
4696
- var FLOOR_WALL2 = `${CDN2}/template-floor-detail-a.glb`;
4697
- var FLOOR_DIRT2 = `${CDN2}/template-floor-detail.glb`;
4698
- var FLOOR_OPEN2 = `${CDN2}/template-floor.glb`;
4699
- var DEFAULT_3D_CASTLE_TILES = [
4700
- { id: "t00", x: 0, y: 0, z: 0, type: "wall", passable: false, modelUrl: FLOOR_WALL2 },
4701
- { id: "t10", x: 1, y: 0, z: 0, type: "wall", passable: false, modelUrl: FLOOR_WALL2 },
4702
- { id: "t20", x: 2, y: 0, z: 0, type: "wall", passable: false, modelUrl: FLOOR_WALL2 },
4703
- { id: "t30", x: 3, y: 0, z: 0, type: "wall", passable: false, modelUrl: FLOOR_WALL2 },
4704
- { id: "t40", x: 4, y: 0, z: 0, type: "wall", passable: false, modelUrl: FLOOR_WALL2 },
4705
- { id: "t01", x: 0, y: 1, z: 1, type: "wall", passable: false, modelUrl: FLOOR_WALL2 },
4706
- { id: "t11", x: 1, y: 1, z: 1, type: "floor", passable: true, modelUrl: FLOOR_DIRT2 },
4707
- { id: "t21", x: 2, y: 1, z: 1, type: "floor", passable: true, modelUrl: FLOOR_OPEN2 },
4708
- { id: "t31", x: 3, y: 1, z: 1, type: "floor", passable: true, modelUrl: FLOOR_OPEN2 },
4709
- { id: "t41", x: 4, y: 1, z: 1, type: "wall", passable: false, modelUrl: FLOOR_WALL2 },
4710
- { id: "t02", x: 0, y: 2, z: 2, type: "wall", passable: false, modelUrl: FLOOR_WALL2 },
4711
- { id: "t12", x: 1, y: 2, z: 2, type: "floor", passable: true, modelUrl: FLOOR_OPEN2 },
4712
- { id: "t22", x: 2, y: 2, z: 2, type: "floor", passable: true, modelUrl: FLOOR_DIRT2 },
4713
- { id: "t32", x: 3, y: 2, z: 2, type: "floor", passable: true, modelUrl: FLOOR_OPEN2 },
4714
- { id: "t42", x: 4, y: 2, z: 2, type: "wall", passable: false, modelUrl: FLOOR_WALL2 },
4715
- { id: "t03", x: 0, y: 3, z: 3, type: "wall", passable: false, modelUrl: FLOOR_WALL2 },
4716
- { id: "t13", x: 1, y: 3, z: 3, type: "floor", passable: true, modelUrl: FLOOR_OPEN2 },
4717
- { id: "t23", x: 2, y: 3, z: 3, type: "floor", passable: true, modelUrl: FLOOR_OPEN2 },
4718
- { id: "t33", x: 3, y: 3, z: 3, type: "floor", passable: true, modelUrl: FLOOR_DIRT2 },
4719
- { id: "t43", x: 4, y: 3, z: 3, type: "wall", passable: false, modelUrl: FLOOR_WALL2 },
4720
- { id: "t04", x: 0, y: 4, z: 4, type: "wall", passable: false, modelUrl: FLOOR_WALL2 },
4721
- { id: "t14", x: 1, y: 4, z: 4, type: "wall", passable: false, modelUrl: FLOOR_WALL2 },
4722
- { id: "t24", x: 2, y: 4, z: 4, type: "wall", passable: false, modelUrl: FLOOR_WALL2 },
4723
- { id: "t34", x: 3, y: 4, z: 4, type: "wall", passable: false, modelUrl: FLOOR_WALL2 },
4724
- { id: "t44", x: 4, y: 4, z: 4, type: "wall", passable: false, modelUrl: FLOOR_WALL2 }
4808
+ var DEFAULT_3D_CASTLE_LAYOUT = [
4809
+ { id: "t00", x: 0, y: 0, z: 0, type: "wall", passable: false, kind: "wall" },
4810
+ { id: "t10", x: 1, y: 0, z: 0, type: "wall", passable: false, kind: "wall" },
4811
+ { id: "t20", x: 2, y: 0, z: 0, type: "wall", passable: false, kind: "wall" },
4812
+ { id: "t30", x: 3, y: 0, z: 0, type: "wall", passable: false, kind: "wall" },
4813
+ { id: "t40", x: 4, y: 0, z: 0, type: "wall", passable: false, kind: "wall" },
4814
+ { id: "t01", x: 0, y: 1, z: 1, type: "wall", passable: false, kind: "wall" },
4815
+ { id: "t11", x: 1, y: 1, z: 1, type: "floor", passable: true, kind: "dirt" },
4816
+ { id: "t21", x: 2, y: 1, z: 1, type: "floor", passable: true, kind: "open" },
4817
+ { id: "t31", x: 3, y: 1, z: 1, type: "floor", passable: true, kind: "open" },
4818
+ { id: "t41", x: 4, y: 1, z: 1, type: "wall", passable: false, kind: "wall" },
4819
+ { id: "t02", x: 0, y: 2, z: 2, type: "wall", passable: false, kind: "wall" },
4820
+ { id: "t12", x: 1, y: 2, z: 2, type: "floor", passable: true, kind: "open" },
4821
+ { id: "t22", x: 2, y: 2, z: 2, type: "floor", passable: true, kind: "dirt" },
4822
+ { id: "t32", x: 3, y: 2, z: 2, type: "floor", passable: true, kind: "open" },
4823
+ { id: "t42", x: 4, y: 2, z: 2, type: "wall", passable: false, kind: "wall" },
4824
+ { id: "t03", x: 0, y: 3, z: 3, type: "wall", passable: false, kind: "wall" },
4825
+ { id: "t13", x: 1, y: 3, z: 3, type: "floor", passable: true, kind: "open" },
4826
+ { id: "t23", x: 2, y: 3, z: 3, type: "floor", passable: true, kind: "open" },
4827
+ { id: "t33", x: 3, y: 3, z: 3, type: "floor", passable: true, kind: "dirt" },
4828
+ { id: "t43", x: 4, y: 3, z: 3, type: "wall", passable: false, kind: "wall" },
4829
+ { id: "t04", x: 0, y: 4, z: 4, type: "wall", passable: false, kind: "wall" },
4830
+ { id: "t14", x: 1, y: 4, z: 4, type: "wall", passable: false, kind: "wall" },
4831
+ { id: "t24", x: 2, y: 4, z: 4, type: "wall", passable: false, kind: "wall" },
4832
+ { id: "t34", x: 3, y: 4, z: 4, type: "wall", passable: false, kind: "wall" },
4833
+ { id: "t44", x: 4, y: 4, z: 4, type: "wall", passable: false, kind: "wall" }
4725
4834
  ];
4726
4835
  var DEFAULT_3D_CASTLE_FEATURES = [
4727
4836
  { id: "f1", x: 2, y: 2, z: 2, type: "chest", color: "#f4c542" },
@@ -4729,8 +4838,9 @@ var DEFAULT_3D_CASTLE_FEATURES = [
4729
4838
  ];
4730
4839
  function GameCanvas3DCastleTemplate({
4731
4840
  entity,
4732
- tiles: propTiles = DEFAULT_3D_CASTLE_TILES,
4841
+ tiles: propTiles,
4733
4842
  features: propFeatures = DEFAULT_3D_CASTLE_FEATURES,
4843
+ assetManifest,
4734
4844
  cameraMode = "isometric",
4735
4845
  backgroundColor = "#1e1e2e",
4736
4846
  buildingClickEvent,
@@ -4745,13 +4855,14 @@ function GameCanvas3DCastleTemplate({
4745
4855
  scale,
4746
4856
  className
4747
4857
  }) {
4748
- const resolved = entity && typeof entity === "object" && !Array.isArray(entity) ? entity : void 0;
4749
- const tiles = resolved ? Array.isArray(resolved.tiles) ? resolved.tiles : [] : propTiles;
4750
- const features = resolved ? Array.isArray(resolved.features) ? resolved.features : [] : propFeatures;
4751
- const name = resolved?.name == null ? void 0 : String(resolved.name);
4752
- const level = resolved?.level == null ? void 0 : Number(resolved.level);
4753
- const owner = resolved?.owner == null ? void 0 : String(resolved.owner);
4754
- const unitCount = resolved && Array.isArray(resolved.units) ? resolved.units.length : 0;
4858
+ const row = boardEntity(entity);
4859
+ const hasEntityTiles = !!row && Array.isArray(row.tiles) && row.tiles.length > 0;
4860
+ const tiles = hasEntityTiles ? resolveEntityTiles(entity, assetManifest) : propTiles ? resolvePropTiles(propTiles, assetManifest) : resolveTilesWithModels(DEFAULT_3D_CASTLE_LAYOUT, assetManifest);
4861
+ const features = row && Array.isArray(row.features) && row.features.length > 0 ? resolveEntityFeatures(entity, assetManifest) : resolveFeaturesWithModels(propFeatures, assetManifest);
4862
+ const name = row?.name == null ? void 0 : str(row.name);
4863
+ const level = row?.level == null ? void 0 : num(row.level);
4864
+ const owner = row?.owner == null ? void 0 : str(row.owner);
4865
+ const unitCount = row && Array.isArray(row.units) ? row.units.length : 0;
4755
4866
  return /* @__PURE__ */ jsxRuntime.jsxs(
4756
4867
  VStack,
4757
4868
  {
@@ -4797,36 +4908,32 @@ function GameCanvas3DCastleTemplate({
4797
4908
  );
4798
4909
  }
4799
4910
  GameCanvas3DCastleTemplate.displayName = "GameCanvas3DCastleTemplate";
4800
- var CDN3 = "https://almadar-kflow-assets.web.app/shared/3d/dungeon/floor";
4801
- var FLOOR_WALL3 = `${CDN3}/template-floor-detail-a.glb`;
4802
- var FLOOR_DIRT3 = `${CDN3}/template-floor-detail.glb`;
4803
- var FLOOR_OPEN3 = `${CDN3}/template-floor.glb`;
4804
- var DEFAULT_3D_WORLDMAP_TILES = [
4805
- { id: "t00", x: 0, y: 0, z: 0, type: "mountain", passable: false, modelUrl: FLOOR_WALL3 },
4806
- { id: "t10", x: 1, y: 0, z: 0, type: "mountain", passable: false, modelUrl: FLOOR_WALL3 },
4807
- { id: "t20", x: 2, y: 0, z: 0, type: "water", passable: false, modelUrl: FLOOR_WALL3 },
4808
- { id: "t30", x: 3, y: 0, z: 0, type: "mountain", passable: false, modelUrl: FLOOR_WALL3 },
4809
- { id: "t40", x: 4, y: 0, z: 0, type: "mountain", passable: false, modelUrl: FLOOR_WALL3 },
4810
- { id: "t01", x: 0, y: 1, z: 1, type: "mountain", passable: false, modelUrl: FLOOR_WALL3 },
4811
- { id: "t11", x: 1, y: 1, z: 1, type: "grass", passable: true, modelUrl: FLOOR_OPEN3 },
4812
- { id: "t21", x: 2, y: 1, z: 1, type: "grass", passable: true, modelUrl: FLOOR_OPEN3 },
4813
- { id: "t31", x: 3, y: 1, z: 1, type: "grass", passable: true, modelUrl: FLOOR_OPEN3 },
4814
- { id: "t41", x: 4, y: 1, z: 1, type: "water", passable: false, modelUrl: FLOOR_WALL3 },
4815
- { id: "t02", x: 0, y: 2, z: 2, type: "water", passable: false, modelUrl: FLOOR_WALL3 },
4816
- { id: "t12", x: 1, y: 2, z: 2, type: "grass", passable: true, modelUrl: FLOOR_OPEN3 },
4817
- { id: "t22", x: 2, y: 2, z: 2, type: "road", passable: true, modelUrl: FLOOR_DIRT3 },
4818
- { id: "t32", x: 3, y: 2, z: 2, type: "grass", passable: true, modelUrl: FLOOR_OPEN3 },
4819
- { id: "t42", x: 4, y: 2, z: 2, type: "mountain", passable: false, modelUrl: FLOOR_WALL3 },
4820
- { id: "t03", x: 0, y: 3, z: 3, type: "mountain", passable: false, modelUrl: FLOOR_WALL3 },
4821
- { id: "t13", x: 1, y: 3, z: 3, type: "grass", passable: true, modelUrl: FLOOR_OPEN3 },
4822
- { id: "t23", x: 2, y: 3, z: 3, type: "grass", passable: true, modelUrl: FLOOR_OPEN3 },
4823
- { id: "t33", x: 3, y: 3, z: 3, type: "road", passable: true, modelUrl: FLOOR_DIRT3 },
4824
- { id: "t43", x: 4, y: 3, z: 3, type: "mountain", passable: false, modelUrl: FLOOR_WALL3 },
4825
- { id: "t04", x: 0, y: 4, z: 4, type: "mountain", passable: false, modelUrl: FLOOR_WALL3 },
4826
- { id: "t14", x: 1, y: 4, z: 4, type: "water", passable: false, modelUrl: FLOOR_WALL3 },
4827
- { id: "t24", x: 2, y: 4, z: 4, type: "grass", passable: true, modelUrl: FLOOR_OPEN3 },
4828
- { id: "t34", x: 3, y: 4, z: 4, type: "mountain", passable: false, modelUrl: FLOOR_WALL3 },
4829
- { id: "t44", x: 4, y: 4, z: 4, type: "mountain", passable: false, modelUrl: FLOOR_WALL3 }
4911
+ var DEFAULT_3D_WORLDMAP_LAYOUT = [
4912
+ { id: "t00", x: 0, y: 0, z: 0, type: "mountain", passable: false, kind: "wall" },
4913
+ { id: "t10", x: 1, y: 0, z: 0, type: "mountain", passable: false, kind: "wall" },
4914
+ { id: "t20", x: 2, y: 0, z: 0, type: "water", passable: false, kind: "wall" },
4915
+ { id: "t30", x: 3, y: 0, z: 0, type: "mountain", passable: false, kind: "wall" },
4916
+ { id: "t40", x: 4, y: 0, z: 0, type: "mountain", passable: false, kind: "wall" },
4917
+ { id: "t01", x: 0, y: 1, z: 1, type: "mountain", passable: false, kind: "wall" },
4918
+ { id: "t11", x: 1, y: 1, z: 1, type: "grass", passable: true, kind: "open" },
4919
+ { id: "t21", x: 2, y: 1, z: 1, type: "grass", passable: true, kind: "open" },
4920
+ { id: "t31", x: 3, y: 1, z: 1, type: "grass", passable: true, kind: "open" },
4921
+ { id: "t41", x: 4, y: 1, z: 1, type: "water", passable: false, kind: "wall" },
4922
+ { id: "t02", x: 0, y: 2, z: 2, type: "water", passable: false, kind: "wall" },
4923
+ { id: "t12", x: 1, y: 2, z: 2, type: "grass", passable: true, kind: "open" },
4924
+ { id: "t22", x: 2, y: 2, z: 2, type: "road", passable: true, kind: "dirt" },
4925
+ { id: "t32", x: 3, y: 2, z: 2, type: "grass", passable: true, kind: "open" },
4926
+ { id: "t42", x: 4, y: 2, z: 2, type: "mountain", passable: false, kind: "wall" },
4927
+ { id: "t03", x: 0, y: 3, z: 3, type: "mountain", passable: false, kind: "wall" },
4928
+ { id: "t13", x: 1, y: 3, z: 3, type: "grass", passable: true, kind: "open" },
4929
+ { id: "t23", x: 2, y: 3, z: 3, type: "grass", passable: true, kind: "open" },
4930
+ { id: "t33", x: 3, y: 3, z: 3, type: "road", passable: true, kind: "dirt" },
4931
+ { id: "t43", x: 4, y: 3, z: 3, type: "mountain", passable: false, kind: "wall" },
4932
+ { id: "t04", x: 0, y: 4, z: 4, type: "mountain", passable: false, kind: "wall" },
4933
+ { id: "t14", x: 1, y: 4, z: 4, type: "water", passable: false, kind: "wall" },
4934
+ { id: "t24", x: 2, y: 4, z: 4, type: "grass", passable: true, kind: "open" },
4935
+ { id: "t34", x: 3, y: 4, z: 4, type: "mountain", passable: false, kind: "wall" },
4936
+ { id: "t44", x: 4, y: 4, z: 4, type: "mountain", passable: false, kind: "wall" }
4830
4937
  ];
4831
4938
  var DEFAULT_3D_WORLDMAP_FEATURES = [
4832
4939
  { id: "f1", x: 2, y: 2, z: 2, type: "capital", color: "#f4c542" },
@@ -4834,8 +4941,9 @@ var DEFAULT_3D_WORLDMAP_FEATURES = [
4834
4941
  ];
4835
4942
  function GameCanvas3DWorldMapTemplate({
4836
4943
  entity,
4837
- tiles: propTiles = DEFAULT_3D_WORLDMAP_TILES,
4944
+ tiles: propTiles,
4838
4945
  features: propFeatures = DEFAULT_3D_WORLDMAP_FEATURES,
4946
+ assetManifest,
4839
4947
  cameraMode = "isometric",
4840
4948
  backgroundColor = "#1a1a2e",
4841
4949
  tileClickEvent,
@@ -4849,9 +4957,10 @@ function GameCanvas3DWorldMapTemplate({
4849
4957
  scale,
4850
4958
  className
4851
4959
  }) {
4852
- const resolved = entity && typeof entity === "object" && !Array.isArray(entity) ? entity : void 0;
4853
- const tiles = resolved ? Array.isArray(resolved.tiles) ? resolved.tiles : [] : propTiles;
4854
- const features = resolved ? Array.isArray(resolved.features) ? resolved.features : [] : propFeatures;
4960
+ const row = boardEntity(entity);
4961
+ const hasEntityTiles = !!row && Array.isArray(row.tiles) && row.tiles.length > 0;
4962
+ const tiles = hasEntityTiles ? resolveEntityTiles(entity, assetManifest) : propTiles ? resolvePropTiles(propTiles, assetManifest) : resolveTilesWithModels(DEFAULT_3D_WORLDMAP_LAYOUT, assetManifest);
4963
+ const features = row && Array.isArray(row.features) && row.features.length > 0 ? resolveEntityFeatures(entity, assetManifest) : resolveFeaturesWithModels(propFeatures, assetManifest);
4855
4964
  return (
4856
4965
  /* GameBoard3D reads selectedUnitId/validMoves/attackTargets from entity */
4857
4966
  /* @__PURE__ */ jsxRuntime.jsx(